서버 이전 뒤 외부 접속 경로
외부 IP와 로컬 bind부터 VPC 방화벽, Java TCP, Bedrock UDP, Geyser와 Floodgate까지 이어지는 계층별 진단 순서.
Windows PC의 월드와 설정을 GCP VM으로 옮긴 직후 Minecraft가 리스너를 만들지 못했다.
server.properties에는 이전 환경의 IP가 server-ip로 남아 있었다. 클라이언트가 입력하는
공개 주소와 서버 프로세스가 bind할 로컬 주소를 같은 값으로 취급한 결과였다.
GCP 외부 IPv4는 인터넷에서 VM으로 트래픽을 전달하는 주소다. 게스트 OS 안의 네트워크 인터페이스가 그 주소를 직접 소유하는 것은 아니다. Minecraft가 외부 IP로 bind를 요청하면 커널은 로컬 인터페이스에 없는 주소로 판단해 방화벽 검사보다 앞에서 실패할 수 있다.
외부 주소와 bind 주소
서버 설정은 특정 로컬 주소에 묶을 이유가 없으면 비워 둔다.
server-ip=
server-port=25565
enable-rcon=true
rcon.port=25575
빈 server-ip는 사용 가능한 로컬 인터페이스에서 요청을 받게 한다. Linux에서 흔히 보이는
0.0.0.0:25565는 모든 로컬 IPv4 인터페이스에 bind됐다는 뜻이다. 인터넷 전체에 공개됐다는
뜻은 아니다. 공개 범위는 VPC 방화벽, 라우팅과 외부 주소 연결이 결정한다.
flowchart LR accTitle: 외부 주소에서 Minecraft 프로세스까지의 주소 경로 accDescr: 인터넷 접속은 Google Cloud의 외부 주소와 방화벽 경계, VM의 NIC와 커널 경계를 차례로 지나 wildcard에 bind된 Purpur에 도달한다. CLIENT[인터넷 클라이언트] -->|고정 외부 IP:25565| CLOUD[Google Cloud 경계<br/>edge·VPC firewall·network tag] CLOUD --> GUEST[VM 경계<br/>내부 NIC·guest kernel] GUEST -->|0.0.0.0:25565 bind| MC[Purpur]
주소별 책임은 다음처럼 나뉜다.
| 주소 | 예 | 소유·사용 주체 | 실패 시 증상 |
|---|---|---|---|
| loopback | 127.0.0.1 | 같은 VM의 RCON client·로컬 probe | 외부에서는 접근 불가 |
| 내부 IP | RFC 1918 주소 | VM NIC와 VPC | 내부 경로만 가능 |
| wildcard | 0.0.0.0 | 서버 socket bind | 모든 로컬 NIC에서 대기 |
| 외부 IP | 예약된 GCP 주소 | Cloud network와 access_config | 외부 연결 대상이 사라짐 |
외부 IP를 Minecraft 설정에 복사하지 않는다. Terraform의 nat_ip가 주소를 VM에 연결하고,
Minecraft는 VM 안의 socket만 연다.
resource "google_compute_address" "minecraft" {
name = "minecraft-public-ip"
region = var.region
}
network_interface {
network = var.network
access_config {
nat_ip = google_compute_address.minecraft.address
}
}
regional external address와 VM zone의 region이 맞아야 한다. VM을 다른 region으로 옮기면 기존 주소를 그대로 연결할 수 없고, DNS나 클라이언트 즐겨찾기까지 변경 범위가 이어진다.
Java와 Bedrock의 전송 경로
Java Edition은 TCP로 Purpur에 접속했다. Bedrock Edition은 UDP로 Geyser listener에 접속하고 Geyser가 Java protocol 세션으로 변환했다. Floodgate는 Bedrock 사용자의 인증과 identity 연결을 맡았다.
flowchart LR accTitle: Java와 Bedrock 크로스플레이 경로 accDescr: Java 클라이언트는 TCP로 Purpur에 직접 접속하고 Bedrock 클라이언트는 UDP로 Geyser에 접속해 Floodgate 인증을 거쳐 같은 Purpur 월드에 합류한다. J[Java client] -->|TCP 25565| JFW[Java firewall rule] JFW --> P[Purpur Java listener] B[Bedrock client] -->|UDP 19132| BFW[Bedrock firewall rule] BFW --> G[Geyser UDP listener] G --> F[Floodgate identity] F --> P P --> W[같은 world]
같은 외부 IP를 써도 5-tuple의 protocol과 destination port가 다르다. Java가 접속된다고 Bedrock 경로까지 증명되지 않고, TCP 포트 검사로 UDP listener를 확인할 수도 없다.
resource "google_compute_firewall" "minecraft_bedrock" {
name = "minecraft-bedrock"
network = var.network
source_ranges = ["0.0.0.0/0"]
target_tags = ["minecraft-server"]
allow {
protocol = "udp"
ports = ["19132"]
}
}
target_tags는 VM에 실제 붙은 tag와 정확히 일치해야 한다. 규칙 이름이나 포트가 맞아도 tag가
다르면 적용되지 않는다. source_ranges는 누가 접근할 수 있는지를 정한다. 공개 게임 서버는
게임 포트를 넓게 열 수 있지만 RCON, SSH와 관리용 지도 포트는 같은 규칙에 합치지 않는다.
Geyser 설정에서도 bind와 upstream을 나눈다.
bedrock:
address: 0.0.0.0
port: 19132
remote:
address: 127.0.0.1
port: 25565
auth-type: floodgate
Bedrock listener는 외부 UDP를 받기 위해 모든 로컬 NIC에 bind한다. upstream Java 서버는
같은 VM이므로 loopback을 사용한다. remote.address에 공개 IP를 넣으면 패킷이 불필요하게
외부 경로를 거치거나 방화벽·NAT 조건에 의존한다.
안쪽에서 바깥으로 가는 점검 순서
클라이언트의 “연결 실패”는 많은 실패를 하나의 문장으로 합친다. 가장 안쪽의 프로세스부터 확인하면 각 단계의 가설을 좁힐 수 있다.
flowchart TD accTitle: Minecraft 외부 접속 실패 진단 순서 accDescr: 서비스와 로그, 로컬 리스너, 로컬 프로토콜, 방화벽, 외부 주소, 외부 클라이언트를 안쪽에서 바깥쪽 순서로 확인한다. A[systemd active인가] -- 아니오 --> AL[unit·JVM 로그 확인] A -- 예 --> B[기대 포트에 bind됐는가] B -- 아니오 --> BL[server-ip·포트 충돌 확인] B -- 예 --> C[로컬 protocol 응답인가] C -- 아니오 --> CL[월드·플러그인 초기화 확인] C -- 예 --> D[방화벽 tag·protocol·port 일치] D -- 아니오 --> DL[VPC 규칙 수정] D -- 예 --> E[외부 IP가 해당 VM에 연결] E -- 아니오 --> EL[address·region·access_config 확인] E -- 예 --> F[VM 밖 Java·Bedrock 각각 시험]
VM 안에서는 다음 명령으로 서로 다른 socket을 확인한다.
systemctl is-active minecraft.service
ss -lntp '( sport = :25565 )'
ss -lnup '( sport = :19132 )'
journalctl -u minecraft.service -b --no-pager | tail -n 100
TCP LISTEN은 Java socket이 열렸다는 뜻이다. UDP는 연결 상태가 없어 UNCONN으로 보일
수 있다. 프로세스 이름과 PID가 기대한 Java 프로세스인지도 확인한다. 포트만 열어 둔 다른
프로세스가 있을 수 있기 때문이다.
로컬 protocol probe는 Minecraft가 응답할 정도로 준비됐는지를 검사한다. Java server list ping과 Bedrock status를 각각 사용한다. 그 다음 VM 밖에서 고정 외부 IP로 같은 검사를 반복한다. 로컬 성공·외부 실패이면 원인은 대체로 애플리케이션보다 GCP 방화벽, 주소 연결, 상위 네트워크 또는 클라이언트 경로에 있다.
방화벽 규칙의 적용 범위
VPC 방화벽은 stateful이다. 허용된 ingress 연결의 응답 패킷을 위해 별도 egress 역방향 규칙을 만들 필요는 없다. 하지만 Java TCP 허용이 Bedrock UDP를 포괄하지 않고, IPv4 규칙이 IPv6 경로를 자동 허용하지도 않는다.
규칙을 검토할 때 이름보다 다음 값을 본다.
- 방향: ingress인지 egress인지
- action과 priority: 더 높은 우선순위 deny가 있는지
- protocol: TCP, UDP 또는 둘 다인지
- destination port: Minecraft가 실제 bind한 포트인지
- source range: 공개 사용자 또는 관리 대역인지
- target: network tag나 service account가 VM과 일치하는지
RCON은 같은 VM의 Discord 봇과 백업 스크립트만 사용하므로 외부 VPC ingress 규칙을 만들지
않고 게스트 방화벽에서 로컬 호출만 허용하는 구성이 낫다. 표준 server.properties에는
게임 listener와 별개인 RCON 전용 bind 주소가 없으므로 server-ip=127.0.0.1을 해결책으로
사용하면 게임 listener까지 loopback에 묶일 수 있다. 필요하면 host firewall, loopback
proxy나 IAP/SSH 터널로 RCON 경계를 만든다. 외부 관리가 꼭 필요할 때도 공개 게임 포트와
분리해 VPN 또는 제한된 관리자 대역을 사용한다. RCON은 전송 계층 암호화를 제공하는 관리
API로 가정해서는 안 된다.
이전 뒤에 남는 구성 불일치
월드 파일만 옮겨도 서버가 같은 환경이 되지는 않는다. 다음 네 층의 설정이 함께 움직인다.
| 층 | 이전 대상 | 대표 불일치 |
|---|---|---|
| 게임 | 월드, 플러그인, whitelist, server properties | 이전 IP, 플러그인 port, 버전 차이 |
| 호스트 | 경로, 사용자·그룹, Java, systemd | 파일 소유권, Java 버전, working directory |
| 네트워크 | 내부·외부 IP, protocol, firewall target | TCP/UDP 혼동, tag 누락, region 불일치 |
| 신원 | Floodgate key, RCON·Discord secret | 누락된 key, metadata 노출, 잘못된 권한 |
Geyser와 Floodgate를 함께 옮길 때 Floodgate key와 plugin 설정의 소유권을 확인한다.
key.pem은 Floodgate 구성 요소 사이에서 인증과 전달 데이터를 검증하는 키다. 키가
일치하지 않거나 유실되면 인증과 identity data forwarding이 실패할 수 있지만, Bedrock
사용자의 Java 측 UUID를 이 키에서 생성하는 것은 아니다. 기본 Bedrock UUID는 XUID에서
파생되며 데이터 연속성은 XUID, 계정 연결과 username prefix 설정을 함께 확인해야 한다.
실제 사용자 데이터와 키 내용은 문서나 Terraform state에 넣지 않는다.
이전 완료는 다음 사건을 모두 관찰한 시점이다.
- Purpur가 지정 월드를 열고 Java status에 응답한다.
- Geyser가 UDP listener를 열고 Floodgate를 정상 초기화한다.
- 외부 Java 클라이언트가 기대한 월드에 접속한다.
- 외부 Bedrock 클라이언트가 같은 월드와 기존 identity로 접속한다.
- RCON과 SSH는 공개 게임 경로에서 접근되지 않는다.
참고
Share
아직 댓글이 없습니다. 첫 댓글을 남겨주세요.
검토 대기 중