Terraform state와 남은 Google Cloud 비용 정리

빈 state 객체와 backend 정리, 프로젝트에 남은 과금 리소스와 IAM 점검

서버와 복구 루트의 terraform state list가 모두 비어도 GCS에는 state 객체와 backend 버킷이 남아 있다. 정확한 객체 경로를 확인한 뒤 이 기록을 지운다. 이어서 Terraform 밖의 디스크, 고정 IP, Storage와 함수 관련 과금 항목을 프로젝트 전체에서 찾는다. state 삭제는 실제 리소스를 삭제하지 않는다.

선수 조건

서버 루트와 적용했던 복구 루트에서 terraform state list가 아무것도 출력하지 않아야 한다. 하나라도 남아 있으면 아래 삭제 명령을 실행하지 않는다.

위험 명령

state 객체를 지우면 Terraform의 관리 기록을 복구하기 어려워진다. 아래 삭제 명령은 로컬 검증에서 실행하지 않았다. wildcard나 **를 넣지 않는다.

단, soft-deleted 객체를 찾는 읽기 전용 목록 명령에는 Google Cloud 공식 문법에 따라 **를 사용한다. 이 값을 gcloud storage rm에 복사하지 않는다.

두 Terraform state의 빈 목록

서버 루트부터 확인한다.

확인

cd "${HOME}/minecraft-one-root"
terraform state list

09편을 적용했다면 복구 루트도 확인한다.

cd "${HOME}/minecraft-spot-recovery"
terraform state list

출력이 있다면 10편으로 돌아가 실제 리소스와 destroy 오류를 확인한다. state rm으로 목록만 억지로 비우지 않는다.

backend 버킷과 prefix

두 루트의 backend.hcl에서 버킷과 prefix를 읽어 이후 명령에 사용할 변수로 저장한다.

확인

아래 블록은 두 backend.hcl 파일에서 bucket과 prefix 값을 읽어 변수 네 개에 넣고 마지막에 화면에 보여 준다. sed는 따옴표 안의 값만 꺼내며 파일이나 리소스를 바꾸지 않는다. 통째로 복사해 실행하고 마지막 출력만 확인한다.

cd "${HOME}/minecraft-one-root"
export STATE_BUCKET="$(
  sed -n \
    's/^[[:space:]]*bucket[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
    "${HOME}/minecraft-one-root/backend.hcl"
)"
export SERVER_PREFIX="$(
  sed -n \
    's/^[[:space:]]*prefix[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
    "${HOME}/minecraft-one-root/backend.hcl"
)"

export RECOVERY_BUCKET=""
export RECOVERY_PREFIX=""
if [[ -f "${HOME}/minecraft-spot-recovery/backend.hcl" ]]; then
  export RECOVERY_BUCKET="$(
    sed -n \
      's/^[[:space:]]*bucket[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
      "${HOME}/minecraft-spot-recovery/backend.hcl"
  )"
  export RECOVERY_PREFIX="$(
    sed -n \
      's/^[[:space:]]*prefix[[:space:]]*=[[:space:]]*"\([^"]*\)".*/\1/p' \
      "${HOME}/minecraft-spot-recovery/backend.hcl"
  )"
fi

printf 'Server: %s / %s\nRecovery: %s / %s\n' \
  "${STATE_BUCKET}" \
  "${SERVER_PREFIX}" \
  "${RECOVERY_BUCKET}" \
  "${RECOVERY_PREFIX}"

기본 workspace의 GCS backend 객체는 <prefix>/default.tfstate다. 공개 예시는 서버 minecraft/one-root/default.tfstate, 복구 minecraft/spot-recovery/default.tfstate를 사용한다. STATE_BUCKET과 SERVER_PREFIX가 비어 있으면 아래 명령을 실행하지 않는다. 복구 루트의 backend.hcl이 있다면 RECOVERY_BUCKET과 RECOVERY_PREFIX도 값이 있어야 하고 두 버킷 이름이 같아야 한다. 복구 루트를 만들지 않았을 때만 두 복구 변수가 비어 있는 것이 정상이다.

state 객체의 모든 버전

Object Versioning을 켰으므로 live 객체와 이전 세대를 먼저 읽는다.

확인

gcloud storage ls \
  --recursive \
  --all-versions \
  "gs://${STATE_BUCKET}/${SERVER_PREFIX}/default.tfstate"

if [[ -n "${RECOVERY_PREFIX}" ]]; then
  gcloud storage ls \
    --recursive \
    --all-versions \
    "gs://${STATE_BUCKET}/${RECOVERY_PREFIX}/default.tfstate"
fi

복구 루트를 적용하지 않았다면 두 번째 경로가 없는 것이 정상이다. 다른 프로젝트의 state나 예상하지 않은 prefix가 보이면 삭제하지 않는다.

일반 목록에는 soft-deleted 객체가 나오지 않는다. 삭제 기록도 별도로 확인한다.

확인: soft-deleted state 객체

gcloud storage ls \
  "gs://${STATE_BUCKET}/${SERVER_PREFIX}/**" \
  --soft-deleted \
  --full

if [[ -n "${RECOVERY_PREFIX}" ]]; then
  gcloud storage ls \
    "gs://${STATE_BUCKET}/${RECOVERY_PREFIX}/**" \
    --soft-deleted \
    --full
fi

두 Terraform state가 비었고 10편의 실제 리소스 삭제가 끝났을 때만 live와 noncurrent state 객체를 지운다.

데이터 삭제: 서버 state의 모든 live와 noncurrent 버전

gcloud storage rm \
  --all-versions \
  "gs://${STATE_BUCKET}/${SERVER_PREFIX}/default.tfstate"

데이터 삭제: 복구 state가 존재할 때

if [[ -z "${RECOVERY_PREFIX}" ]]; then
  printf '복구 prefix가 없어 삭제하지 않습니다.\n'
else
  gcloud storage rm \
    --all-versions \
    "gs://${STATE_BUCKET}/${RECOVERY_PREFIX}/default.tfstate"
fi

삭제 명령이 끝나면 두 prefix의 --soft-deleted 조회를 다시 실행한다. 출력된 state 세대의 hard delete 시각을 기록한다. 일반 객체 목록이 비어 있어도 이 보존 사본의 비용은 그 시각까지 남을 수 있다.

빈 backend 버킷 삭제

버킷 전체를 재귀적으로 읽는다.

확인

gcloud storage ls \
  --recursive \
  --all-versions \
  "gs://${STATE_BUCKET}/"

다른 Terraform state가 있으면 backend 버킷을 남긴다. 이 프로젝트 전용이고 출력이 완전히 비었을 때만 빈 버킷을 삭제한다.

데이터 삭제: live와 noncurrent 객체 목록이 비었을 때

gcloud storage buckets delete "gs://${STATE_BUCKET}"

버킷이 비어 있지 않으면 명령이 실패한다. 내용을 강제로 재귀 삭제하는 명령은 이 가이드에서 사용하지 않는다. soft delete 정책이 켜져 있었다면 삭제된 버킷은 보존 기간 동안 soft-deleted 상태로 남는다.

확인: 프로젝트의 soft-deleted 버킷

export PROJECT_ID="$(gcloud config get-value project)"

gcloud storage ls \
  --buckets \
  --soft-deleted \
  --full \
  --project="${PROJECT_ID}"

삭제한 state 버킷이 출력되면 hard delete 시각을 기록한다. 그 시각 전에는 버킷을 영구 삭제할 수 없고 저장 비용이 남을 수 있다.

프로젝트에 남은 과금 리소스

확인

gcloud compute instances list \
  --project="${PROJECT_ID}"

gcloud compute disks list \
  --project="${PROJECT_ID}"

gcloud compute addresses list \
  --project="${PROJECT_ID}"

gcloud functions list \
  --v2 \
  --project="${PROJECT_ID}" \
  --regions="asia-northeast3"

gcloud storage buckets list \
  --project="${PROJECT_ID}"

gcloud artifacts repositories list \
  --project="${PROJECT_ID}" \
  --location="asia-northeast3"

실습 전용 프로젝트라면 VM, 디스크와 고정 IP 목록은 비어 있어야 한다. 공유 프로젝트라면 다른 워크로드는 남을 수 있으므로 삭제 계획에 있던 서버 이름과 주소가 사라졌는지 확인한다. 함수 목록에는 복구 함수가 없어야 한다. 10편에서 보존한 백업 버킷은 남는 것이 정상이며 Storage 비용도 계속 확인한다.

Cloud Functions 배포가 Artifact Registry 저장소나 image를 남겼을 수 있다. 다른 함수가 쓰는 저장소인지 먼저 확인한다. Minecraft 서버 전용이라는 근거가 없으면 삭제하지 않는다.

OS Login 프로젝트 IAM 정리

05편에서 본인 계정에 추가한 roles/compute.osAdminLogin이 다른 VM에 필요하지 않다면 제거한다.

조건부 실행: 이 프로젝트의 다른 VM을 관리하지 않을 때

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

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

다른 VM에도 같은 역할을 사용한다면 남긴다. 조직 관리자가 부여한 다른 역할은 추측해서 제거하지 않는다. VM 서비스 계정의 serviceAccountUser binding은 서비스 계정과 함께 삭제됐는지 IAM 화면에서 확인한다.

마지막 결제 보고서

Google Cloud Console에서 결제 → 보고서를 열고 프로젝트와 날짜를 좁힌다. 보고서는 사용량보다 늦게 갱신될 수 있다. 삭제 직후 한 번, 몇 시간 뒤와 다음 날 다시 본다.

비용이 계속 늘면 다음 순서로 찾는다.

  1. 보존한 백업 버킷의 저장량과 soft delete 보존량을 확인한다.
  2. Compute Engine의 디스크와 외부 IP 목록을 프로젝트 전체로 다시 본다.
  3. Cloud Functions와 Artifact Registry 저장량을 확인한다.
  4. 결제 보고서의 SKU별 비용을 열어 어떤 제품이 청구하는지 찾는다.

활성화된 API는 같은 프로젝트의 다른 워크로드가 사용할 수 있다. 이 정리 과정에서는 API별 사용 주체를 확인하지 않고 임의로 끄지 않는다.

완료 체크

  • 서버와 복구 state가 비어 있는 상태에서 정확한 GCS 객체만 삭제했다.
  • backend 버킷의 모든 객체 버전을 읽고 빈 경우에만 버킷을 삭제했다.
  • soft-deleted state 객체와 버킷의 hard delete 시각을 기록했다.
  • VM, 디스크, 고정 IP와 복구 함수가 목록에 없다.
  • 보존한 백업 버킷과 Artifact Registry 저장소의 용도와 삭제 담당자를 기록했다.
  • 필요하지 않은 OS Login 프로젝트 IAM만 제거했다.
  • 결제 보고서를 삭제 직후와 지연 반영 뒤 다시 확인했다.

참고

Comments

댓글

    이미지 확대