Terraform plan을 적용하고 Minecraft에 접속하기

첫 Terraform 계획 읽기, 비용 승인, VM 기동과 Java Edition 접속

terraform plan은 변경 내용을 계산해 계획 파일에 저장한다. 저장한 계획을 apply하면 서버 리소스가 생기고 과금도 시작될 수 있다.

선수 조건

scripts/preflight.sh, terraform test와 terraform validate가 성공해야 한다. Console의 결제 연결과 예산 알림도 다시 확인한다. 별도 안내가 없는 명령은 Cloud Shell의 minecraft-one-root에서 실행한다.

검증 범위

Terraform 구성은 mock Provider 테스트와 정적 검사를 통과했다. 2026년 7월의 수동 검증에서는 Ubuntu 24.04 컨테이너의 Minecraft 1.21.8과 Java 21, Minecraft 26.2와 Java 25가 각각 Done (...)!까지 도달했다. 저장소에 넣어 둔 자동 컨테이너 시험은 이 중 1.21.8과 Java 21 조합을 고정해 실행한다. 1.21.8의 로그인 프로토콜 시험에서는 빈 whitelist가 접속을 거부하고, 플레이어를 넣은 뒤 로그인을 허용했다. 실제 Google Cloud terraform apply, VM 생성과 Java Edition 클라이언트 접속은 실행하지 않았다. 아래의 plan은 리소스를 만들지 않으며, 독자가 apply한 시점부터 서버 관련 비용이 생길 수 있다. 현재 상태는 로컬과 컨테이너 검증 완료, 실제 Google Cloud apply 미검증이다.

첫 plan 파일

실행: 아직 리소스를 만들지 않는다

terraform plan -out=minecraft.tfplan
scripts/review-plan.sh minecraft.tfplan

계획이 끝나면 Plan: ... to add, ... to change, ... to destroy.가 나온다. 숫자는 Provider 버전과 기존 프로젝트 상태에 따라 달라진다. 합계와 함께 생성, 변경과 삭제 대상의 내용을 읽는다. review-plan.sh는 첫 배포 계획의 생성, 수정, 교체와 삭제 수를 요약한다. 수정, 교체나 삭제가 있거나 필수 리소스 종류가 빠지면 실패한다. 검사가 끝난 뒤에도 terraform show 출력을 직접 읽는다.

확인

terraform show minecraft.tfplan

빈 프로젝트와 새 state에서는 다음 경로가 연결돼야 한다.

flowchart LR
  player["Java Edition 플레이어"] --> ip["고정 외부 IPv4"]
  admin["관리자 공인 IPv4 /32"] --> ssh["SSH 방화벽"]
  ip --> game["게임 방화벽 TCP 25565"]
  ssh --> vm["Spot VM"]
  game --> vm
  vm --> disk["부팅 디스크와 월드"]
  vm --> sa["VM 서비스 계정"]
  sa --> backup["백업 버킷"]

계획에서 아래 항목을 하나씩 찾는다.

  • 게임 TCP 25565만 0.0.0.0/0에 열리는가
  • SSH source_ranges가 내 공인 IPv4 한 개와 /32인가
  • VM의 provisioning_model이 SPOT이고 선점 동작이 STOP인가
  • VM에 minecraft-server 전용 서비스 계정이 연결되는가
  • 부팅 디스크가 VM 삭제와 함께 없어지는가
  • 백업 버킷의 force_destroy가 false인가
  • destroy나 replace가 예상하지 않은 리소스에 붙지 않았는가

같은 이름의 기존 네트워크나 서비스 계정이 있다는 오류가 나면 그 리소스를 지우지 않는다. 새 이름을 쓸지, 소유권을 확인한 뒤 terraform import할지 먼저 결정한다.

비용이 생기기 직전의 승인

terraform apply 전에는 서버 실행 비용 없이 중단할 수 있다. 계획 파일은 만들 대상을 고정하지만 실제 요금은 사용량과 가격표에 따라 달라진다. Compute Engine VM, 디스크, 고정 외부 IPv4, Cloud Storage와 네트워크 사용량에 비용이 붙을 수 있다. Spot VM도 무료가 아니다.

다음 중 하나라도 답하지 못하면 적용하지 않는다.

  • 프로젝트와 결제 계정이 내가 의도한 것인가
  • 예산 알림의 금액과 수신자가 맞는가
  • SSH 허용 주소와 공개 게임 포트가 맞는가
  • 중요한 월드를 올리지 않고 06편의 복원 시험부터 할 수 있는가
  • 사용하지 않을 때 08편에서 VM을 중지하고, 운영을 끝내면 10편에서 정리할 수 있는가

계속하기로 결정했다면 저장한 계획을 그대로 적용한다.

비용 발생: 저장한 계획 적용

terraform apply minecraft.tfplan

정상 결과는 Apply complete!와 출력값이다. 403, 할당량 부족 또는 Spot 용량 부족으로 중간에 실패하면 일부 리소스만 생겼을 수 있다. 오류를 고친 뒤 새 계획을 만들고 다시 읽는다. 이전 minecraft.tfplan을 반복 적용하지 않는다.

접속에 쓸 출력값

실행: minecraft-one-root에서

export PROJECT_ID="$(
  terraform output -raw minecraft_project_id
)"
export MINECRAFT_INSTANCE="$(
  terraform output -raw minecraft_instance_name
)"
export MINECRAFT_ZONE="$(
  terraform output -raw minecraft_zone
)"
export MINECRAFT_IP="$(
  terraform output -raw minecraft_public_ip
)"
export MINECRAFT_PORT="$(
  terraform output -raw minecraft_game_port
)"
export VM_SERVICE_ACCOUNT="$(
  terraform output -raw minecraft_service_account
)"

명령이 값을 출력하지 못하면 apply가 끝난 디렉터리와 backend가 맞는지 확인한다.

OS Login 권한

VM은 metadata의 enable-oslogin = "TRUE"를 사용한다. SSH 키를 VM 설정에 직접 붙이지 않고 Google 계정과 IAM으로 접속자를 정한다.

실행: minecraft-one-root에서

export ADMIN_ACCOUNT="$(
  gcloud auth list --filter=status:ACTIVE \
    --format="value(account)"
)"

gcloud projects add-iam-policy-binding "${PROJECT_ID}" \
  --member="user:${ADMIN_ACCOUNT}" \
  --role="roles/compute.osAdminLogin"

gcloud iam service-accounts add-iam-policy-binding \
  "${VM_SERVICE_ACCOUNT}" \
  --member="user:${ADMIN_ACCOUNT}" \
  --role="roles/iam.serviceAccountUser"

각 결과의 bindings에 현재 계정과 지정한 역할이 보여야 한다. 조직 밖 계정은 조직 관리자가 roles/compute.osLoginExternalUser를 추가해야 할 수 있다.

VM, systemd, Minecraft

Ubuntu의 systemd는 Minecraft 같은 백그라운드 프로그램을 시작하고 상태를 기록한다. systemctl은 서비스 상태를, journalctl은 systemd가 모은 로그를 읽는 명령이다.

1. VM이 실행 중인가

확인: 10초마다 최대 5분

(
  for attempt in $(seq 1 30); do
    status="$(
      gcloud compute instances describe "${MINECRAFT_INSTANCE}" \
        --zone="${MINECRAFT_ZONE}" \
        --format="value(status)"
    )"
    printf 'VM status: %s\n' "${status}"
    [[ "${status}" == "RUNNING" ]] && break
    [[ "${status}" == "TERMINATED" ]] && {
      echo "VM stopped before startup completed." >&2
      exit 1
    }
    sleep 10
  done
  [[ "${status}" == "RUNNING" ]]
)

블록 전체를 괄호로 묶었기 때문에 안의 exit가 실행돼도 현재 Cloud Shell 탭은 닫히지 않는다. 여러 줄이지만 통째로 복사해 한 번에 붙여 넣으면 된다.

RUNNING은 운영체제가 켜졌다는 뜻이다. Java 설치와 서버 다운로드는 아직 진행 중일 수 있다. TERMINATED라면 적용 오류나 Spot 선점 기록을 먼저 확인한다.

2. systemd 서비스가 실행 중인가

첫 부팅은 패키지를 설치하므로 몇 분 걸릴 수 있다. 시작 스크립트 로그를 본다.

gcloud compute ssh를 이 계정에서 처음 실행하면 SSH 키를 만들겠다는 안내와 함께 Enter passphrase (empty for no passphrase):라는 질문이 나온다. 실습에서는 아무 것도 입력하지 않고 Enter를 두 번 눌러 빈 passphrase로 진행해도 된다. 키는 Cloud Shell $HOME/.ssh에 저장되고 같은 세션에서는 다시 묻지 않는다.

확인: SSH가 준비될 때까지 15초 간격으로 최대 5분

(
  for attempt in $(seq 1 20); do
    if gcloud compute ssh "${MINECRAFT_INSTANCE}" \
      --zone="${MINECRAFT_ZONE}" \
      --command="sudo journalctl \
        -b \
        -u google-startup-scripts.service \
        -n 150 \
        --no-pager"; then
      break
    fi
    [[ "${attempt}" -eq 20 ]] && exit 1
    sleep 15
  done
)

SHA-256 mismatch, 다운로드 실패나 패키지 설치 오류가 있으면 Minecraft 서비스를 확인하기 전에 해당 입력을 고친다.

확인: Cloud Shell에서

gcloud compute ssh "${MINECRAFT_INSTANCE}" \
  --zone="${MINECRAFT_ZONE}" \
  --command="sudo systemctl status minecraft --no-pager"

active (running)이면 Java 프로세스가 살아 있다. failed면 다음 로그의 첫 오류부터 읽는다.

3. Minecraft가 월드 로드를 마쳤는가

확인: Cloud Shell에서

gcloud compute ssh "${MINECRAFT_INSTANCE}" \
  --zone="${MINECRAFT_ZONE}" \
  --command="sudo journalctl \
    -b \
    -u minecraft \
    -n 120 \
    --no-pager"

다음 형태의 줄이 준비 완료 신호다.

Done (12.345s)! For help, type "help"

active만 보고 접속하면 월드 생성 중이라 실패할 수 있다. Done이 나오지 않으면 로그의 오류를 먼저 해결한다.

Java Edition 첫 접속

첫 기동에는 white-list=true와 enforce-whitelist=true가 이미 들어 있다. 서버는 실행되지만 빈 whitelist 때문에 플레이어는 들어갈 수 없다. SSH 안에서 Minecraft 콘솔을 한 번 열어 첫 운영자와 친구를 등록한다.

실행: 대화형 SSH

gcloud compute ssh "${MINECRAFT_INSTANCE}" \
  --zone="${MINECRAFT_ZONE}"

VM 프롬프트가 열리면 서비스를 멈추고 같은 JAR를 앞쪽(foreground)에서 실행한다. sudo -u minecraft는 서비스와 같은 minecraft 사용자로 실행한다는 뜻이고, nogui는 그래픽 창 없이 콘솔만 연다는 뜻이다.

sudo systemctl stop minecraft
cd /srv/minecraft
sudo -u minecraft /usr/bin/java \
  -Xms2G -Xmx4G \
  -jar server.jar nogui

이 콘솔은 SSH 연결에 묶여 있다. 등록을 마치기 전에 SSH가 끊기면 서버도 함께 종료되므로, 그때는 다시 SSH로 들어와 sudo systemctl start minecraft부터 실행하고 이 절차를 처음부터 반복한다.

Done (...)! 뒤에 다음 명령을 한 줄씩 입력한다. 꺾쇠괄호는 쓰지 않고 Java Edition 프로필 이름을 넣는다. 프로필 이름은 Minecraft 실행기(런처) 오른쪽 위 계정 메뉴나 게임 안 일시정지 화면에서 확인할 수 있는 대소문자까지 정확한 그 이름이다.

whitelist add <내-플레이어-이름>
op <내-플레이어-이름>
whitelist add <친구-플레이어-이름>
whitelist list
stop

친구가 없다면 친구 줄은 생략한다. Added ... to the whitelist와 운영자 지정 메시지를 확인한다. stop이 끝나 VM 셸 프롬프트가 돌아오면 systemd 서비스를 다시 시작한다.

sudo systemctl start minecraft
sudo systemctl is-active minecraft
exit

active가 아니거나 콘솔 명령에서 이름을 찾지 못하면 게임 접속으로 넘어가지 않는다. 완전 공개 서버를 만들기 위해 whitelist를 끄는 절차는 이 초보자 트랙에서 다루지 않는다.

확인

printf 'Minecraft address: %s:%s\n' \
  "${MINECRAFT_IP}" \
  "${MINECRAFT_PORT}"

Minecraft Java Edition에서 멀티플레이 → 서버 추가를 열고 출력된 주소를 서버 주소에 넣는다. 기본 포트 25565라면 IP:25565 또는 IP만 입력해도 된다. 클라이언트와 서버 버전이 다르면 버전 오류가 난다.

새 월드에 시험용 블록 하나를 놓고 위치를 기억한다. 아직 중요한 월드를 옮기지는 않는다.

접속하지 못했을 때

증상먼저 확인할 곳중단 조건
VM 생성 403배포 계정 IAM과 서비스 계정 User 역할권한을 추측해 더하지 않는다
Spot VM 생성 실패존의 Spot 용량, 머신 유형, 할당량반복 생성하지 않는다
SSH 거부현재 공인 IP, admin_cidr, OS Login방화벽을 전체 공개하지 않는다
시작 스크립트 실패JAR URL과 해시, 패키지 로그Done 전 게임 접속을 시도하지 않는다
서비스 재시작 반복journalctl -u minecraft메모리를 임의로 늘리기 전에 오류를 읽는다
서비스는 정상, 게임만 실패listen 포트와 게임 방화벽SSH 포트 범위를 넓히지 않는다

표의 “먼저 확인할 곳”은 대부분 이 글에 이미 나온 명령으로 볼 수 있다. SSH가 거부되면 curl https://api4.ipify.org로 현재 공인 IP를 admin_cidr와 비교하고 (바뀌었다면 08편의 갱신 절차), 시작 스크립트와 서비스 문제는 위의 journalctl -u google-startup-scripts.service와 journalctl -u minecraft 확인 명령을 다시 실행해 첫 오류 줄부터 읽는다.

Minecraft 네트워크와 크로스플레이에는 방화벽부터 Minecraft 포트까지의 점검 경로가 정리돼 있다.

완료 체크

  • VM 상태가 RUNNING이다.
  • minecraft.service가 active (running)이다.
  • 로그에 Done (...)!이 있다.
  • 첫 운영자와 허용할 플레이어가 whitelist에 있다.
  • Java Edition 클라이언트가 고정 IPv4로 접속한다.

접속한 시험 월드에 둔 블록은 Minecraft 월드 백업과 복원 시험에서 복원 결과를 확인할 기준이 된다.

참고

Comments

댓글

    이미지 확대