IaC 검토 및 기본 인프라 구성 - 100-hours-a-week/KTB4-16th-Cloud GitHub Wiki
1. IaC 조사 및 도입 검토
코드형 인프라(IaC)는 수동 프로세스가 아닌 코드를 통해 인프라를 관리하고 프로비저닝하는 것을 말한다.
IaC의 장단점 비교 및 현재의 규모를 기준으로 IaC 도입을 검토한다.
- 장점
- 웹 애플리케이션 배포 자동화: 가장 보편적이고 영향력 있는 IaC 활용 사례 중 하나는 가상 머신, 데이터베이스, 로드 밸런서, 방화벽, 네트워킹 구성을 포함해 웹 애플리케이션에 필요한 모든 구성 요소를 정의하고 프로비저닝하는 것이다.
- 클라우드 배포: IaC를 사용하여 단일 클라우드 또는 여러 클라우드에 걸쳐 전체 클라우드 환경을 설정하고 관리할 수 있다. IaC를 통해 클라우드 인프라를 코드화하여 리소스 할당, 보안 설정, 컴플라이언스에 정확성을 기할 수 있다. 이를 통해 클라우드 운영을 손쉽게 확장하는 동시에 엄격한 조직 표준을 유지 관리할 수 있다.
- 지속적 통합/지속적 제공(CI/CD) 파이프라인: IaC는 소프트웨어 개발 라이프사이클을 자동화하는 데 필수적이다. IaC 접근 방식에서는 인프라 변경 사항을 애플리케이션 코드와 같이 취급하여 CI/CD 파이프라인의 일부로 버전 제어, 테스트 및 자동 배포를 수행한다. 개발자는 프로덕션 환경과 동일한 환경에서 코드를 테스트하여 더 신속하고 안정적으로 배포할 수 있다.
- 재해 복구 및 고가용성: IaC는 재해 복구 기능을 크게 향상시킨다. 코드형 인프라를 정의하고 버전 제어에 저장하면 치명적인 장애 발생 시 다른 리전 또는 클라우드에서 전체 환경을 신속하고 일관되게 재구축할 수 있다. 이를 통해 복구 시간 목표(Recovery Time Objective, RTO)와 가동 중단 시간을 줄임으로써 비즈니스 연속성을 보장할 수 있다.
- 보안 및 컴플라이언스 자동화: IaC는 보안 구성 및 컴플라이언스 규칙을 인프라 정의에 직접 통합한다. 따라서 배포 시 방화벽 규칙, IAM, 암호화 설정과 같은 보안 정책을 수동으로 구성할 필요 없이 자동으로 적용할 수 있다.
- 단점
- 수작업을 자동화하는 것은 복잡할 수 있다 : 수작업에 익숙한 팀은 IaC 툴과 개념을 익히기 위해 시간과 노력을 들여야 할 수 있다. 이로 인해 저항감을 유발하거나 도입 속도가 지연될 수 있다.
- 보안 취약점 : 하드코딩된 비밀 정보, 템플릿 내에서 과도한 권한을 갖는 IAM 롤 또는 기본 이미지 내의 취약점은 인프라 전반에 쉽게 확산되어 보안 위험을 초래할 수 있다.
- 표준화 부족 : 명확한 가이드라인이 없다면 팀이 IaC를 중구난방으로 구현하여 일관성 없는 관행과 코드 중복, 유지 보수의 복잡성으로 이어질 수 있다. 이는 종종 서로 다른 명명 규칙, 모듈 구조, 배포 패턴으로 나타난다.
- 문화적 저항 : IaC와 같이 광범위한 전략을 채택하기 위해서는 조직이 뿌리 깊이 밴 습관에서 벗어나고 코드 중심의 사고방식을 장려해야 한다. 그러려면 임원진의 강력한 지지, 새로운 전략의 이점에 관한 지속적인 커뮤니케이션, 실험과 학습을 적극적으로 장려하는 문화가 필요하다.
- 복잡한 파이프라인 디버깅 : IaC가 오류를 줄이기는 하지만 발생하는 오류는 수동 프로세스를 사용할 때 발생하는 오류보다 디버깅하기가 더 까다로울 수 있으며, 특히 팀이 IaC에 익숙하지 않은 경우 더욱 그렇다.
최종 결정 : v1에서는 IaC 도입을 보류한다.
- 위에서 정리한 장점들은 대규모 운영에서 큰 장점을 보는 사항들이라고 생각한다. 지금 v1 규모에서는 VM 1대만 운영서버로 사용하고 있고 DB도 1대이기 때문에 이점들을 체감하기 어렵다.
- 반대로 단점 중 학습 곡선과 복잡한 파이프라인 디버깅은 지금 조건에서 그대로 리스크가 된다. 인프라 담당 인원이 2명뿐인 상황에서 Terraform 같은 새 도구를 익히는 시간은 v1의 핵심 가치인 속도를 깎아먹는 시간이다.
- 지금까지 설계한 GCP 리소스는 gcloud 명령어 형태로 문서에 남겨뒀다. 완전한 IaC는 아니지만, "설정을 코드로 남겨 재현 가능하게 한다"는 IaC의 핵심 목적 상당 부분은 이미 이 방식으로 충족하고 있다.
재검토 시점
아래 중 하나라도 해당되면 그 시점에 Terraform 등 IaC 도구 도입을 다시 검토한다.
-
VM 인스턴스 총 대수가 5대를 넘어가는 경우 (Backend, AI Server Scale-Out이 되는 시점)
-
스테이징 등 새로운 환경을 반복적으로 복제해야 하는 상황이 생기는 경우
-
인프라 담당 인원이 늘어나서 누가 무엇을 바꿨는지에 대한 추적이 실제 문제로 나타나는 경우
-
참고자료 : https://www.redhat.com/ko/topics/automation/what-is-infrastructure-as-code-iac
-
참고자료 : https://www.ibm.com/kr-ko/think/topics/infrastructure-as-code
2. GCP 프로젝트/서비스 계정 구성
콘솔에서 추가
- https://console.cloud.google.com 접속, 프로젝트 선택
- 왼쪽 메뉴 IAM 및 관리자 → IAM
- 상단 액세스 권한 부여 클릭
- "새 주 구성원"에 팀원 구글 이메일 입력
- 역할(Role) 선택
- 저장
Role
- 클라우드 팀원 : 편집자, Artifact Registry 작성자, Compute OS 관리자 로그인, IAP 보안 터널 사용자
- 다른 과정 팀원 : 뷰어
3. VM 2대 구성
- Compute Engine e2-medium VM 2대 구성 (Ubuntu, Active/Standby)
VM 구성
- 리전 : asia-northeast3 (서울)
- 영역 : asia-northeast3-a
- 머신 유형 : e2-medium (vCPU 2개, 코어 1개, 메모리 4GB)
- 프로비저닝 모델 : 표준
3.1. 방화벽 규칙
#두 VM에 태그 부여
gcloud compute instances add-tags muro-vm-1 --zone=asia-northeast3-a --tags=muro-app
gcloud compute instances add-tags muro-vm-2 --zone=asia-northeast3-a --tags=muro-app
#SSH는 IAP 터널에서만 허용 (0.0.0.0/0으로 절대 열지 않는다 — CD 파이프라인이 쓰는 경로)
gcloud compute firewall-rules create muro-allow-ssh-iap \
--direction=INGRESS --action=ALLOW --rules=tcp:22 \
--source-ranges=35.235.240.0/20 \
--target-tags=muro-app
#웹 트래픽(Nginx)만 80/443으로 전 세계에 공개
gcloud compute firewall-rules create muro-allow-web \
--direction=INGRESS --action=ALLOW --rules=tcp:80,tcp:443 \
--source-ranges=0.0.0.0/0 \
--target-tags=muro-app
- Frontend/Backend/AI 사이의 통신은 같은 VM 안의 Docker 브리지 네트워크를 타기 때문에, 방화벽과는 무관하다.
3.2. active/standby 구분을 위한 라벨링
gcloud compute instances add-labels muro-vm-1 --zone=asia-northeast3-a --labels=role=active
gcloud compute instances add-labels muro-vm-2 --zone=asia-northeast3-a --labels=role=standby
3.3. 고정 IP 예약 및 할당
# 고정 IP 예약
gcloud compute addresses create muro-prod-ip \
--project="project-7b071b48-8637-467a-80d" \
--region="asia-northeast3"
# 예약된 IP 값 확인
gcloud compute addresses describe muro-prod-ip \
--region="asia-northeast3" --format="value(address)"
# 기존 임시 IP 떼고, 고정 IP로 교체
# 기존(임시) access config 제거
gcloud compute instances delete-access-config muro-vm-1 \
--zone="asia-northeast3-a" \
--access-config-name="External NAT"
# 방금 예약한 고정 IP로 새로 붙이기
gcloud compute instances add-access-config muro-vm-1 \
--zone="asia-northeast3-a" \
--access-config-name="External NAT" \
--address="$(gcloud compute addresses describe muro-prod-ip --region=asia-northeast3 --format='value(address)')"
3.4. 라벨/access-config 변경 권한 부여
gcloud projects add-iam-policy-binding "project-7b071b48-8637-467a-80d" \
--member="serviceAccount:github-actions-ci@project-7b071b48-8637-467a-80d.iam.gserviceaccount.com" \
--role="roles/compute.instanceAdmin.v1"
3.5. A 레코드 추가 (active VM에 붙인 고정 IP)
gcloud dns record-sets create mulostudio.com. \
--zone="mulostudio-com" \
--project="project-7b071b48-8637-467a-80d" \
--type="A" \
--ttl="300" \
--rrdatas="34.50.40.240"
3.5. github Secrets 에 고정 IP 및 env 추가
gh secret set MURO_PROD_IP --body "34.50.40.240" --repo 100-hours-a-week/KTB4-16th-FE
gh secret set MURO_PROD_IP --body "34.50.40.240" --repo 100-hours-a-week/KTB4-16th-BE
gh secret set MURO_PROD_IP --body "34.50.40.240" --repo 100-hours-a-week/KTB4-16th-AI
# ── Backend (KTB4-16th-BE) ──
gh secret set DB_PASSWORD --repo 100-hours-a-week/KTB4-16th-BE
gh secret set MYSQL_ROOT_PASSWORD --repo 100-hours-a-week/KTB4-16th-BE
gh secret set JWT_SECRET --repo 100-hours-a-week/KTB4-16th-BE
gh secret set KAKAO_REST_API_KEY --repo 100-hours-a-week/KTB4-16th-BE
gh secret set KMA_SERVICE_KEY --repo 100-hours-a-week/KTB4-16th-BE
gh secret set SPOTIFY_CLIENT_ID --repo 100-hours-a-week/KTB4-16th-BE
gh secret set SPOTIFY_CLIENT_SECRET --repo 100-hours-a-week/KTB4-16th-BE
gh secret set GCS_BUCKET_NAME --repo 100-hours-a-week/KTB4-16th-BE
# GCP_PROJECT_ID는 이미 등록되어 있으면 생략 가능
# ── AI (KTB4-16th-AI) ──
gh secret set APP_ENV --repo 100-hours-a-week/KTB4-16th-AI
gh secret set INTERNAL_TOKEN --repo 100-hours-a-week/KTB4-16th-AI
gh secret set DATABASE_URL --repo 100-hours-a-week/KTB4-16th-AI
gh secret set AI_CLIENT_MODE --repo 100-hours-a-week/KTB4-16th-AI
gh secret set ANTHROPIC_API_KEY --repo 100-hours-a-week/KTB4-16th-AI
gh secret set LLM_MODEL --repo 100-hours-a-week/KTB4-16th-AI
gh secret set OPENAI_API_KEY --repo 100-hours-a-week/KTB4-16th-AI
gh secret set TEXT_EMBEDDING_MODEL --repo 100-hours-a-week/KTB4-16th-AI
gh secret set TEXT_EMBEDDING_DIM --repo 100-hours-a-week/KTB4-16th-AI
gh secret set REPLICATE_API_TOKEN --repo 100-hours-a-week/KTB4-16th-AI
gh secret set CLIP_MODEL --repo 100-hours-a-week/KTB4-16th-AI
gh secret set CLIP_EMBEDDING_DIM --repo 100-hours-a-week/KTB4-16th-AI
gh secret set SPOTIFY_CLIENT_ID --repo 100-hours-a-week/KTB4-16th-AI
gh secret set SPOTIFY_CLIENT_SECRET --repo 100-hours-a-week/KTB4-16th-AI
gh secret set YOUTUBE_API_KEY --repo 100-hours-a-week/KTB4-16th-AI
- 참고자료 : https://docs.cloud.google.com/iap/docs/concepts-overview?hl=ko
- 참고자료 : https://docs.cloud.google.com/iap/docs/tutorial-gce?hl=ko
- 참고자 : https://docs.cloud.google.com/iap/docs/using-tcp-forwarding?hl=ko
4. VM 초기 셋업 스크립트
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
if [ -f /var/lib/muro-setup-done ]; then
echo "이미 초기 셋업 완료됨, 건너뜀"
exit 0
fi
# 0. 타임존 설정
timedatectl set-timezone Asia/Seoul
# 1. Docker + Compose 플러그인 설치
curl -fsSL https://get.docker.com | sh
usermod -aG docker "$(logname 2>/dev/null || echo ubuntu)" || true
# 2. 디버깅용 기본 유틸리티
apt-get update -y
apt-get install -y git vim htop net-tools unzip
# 3. Google Cloud SDK 설치
echo "deb [signed-by=/usr/share/keyrings/cloud.google.gpg] https://packages.cloud.google.com/apt cloud-sdk main" \
> /etc/apt/sources.list.d/google-cloud-sdk.list
curl -fsSL https://packages.cloud.google.com/apt/doc/apt-key.gpg \
| gpg --dearmor -o /usr/share/keyrings/cloud.google.gpg
apt-get update -y
apt-get install -y google-cloud-cli
# 4. Artifact Registry pull 인증
gcloud auth configure-docker asia-northeast3-docker.pkg.dev --quiet
# 5. Ops Agent 설치
curl -sSO https://dl.google.com/cloudagents/add-google-cloud-ops-agent-repo.sh
bash add-google-cloud-ops-agent-repo.sh --also-install
# 6. 배포 디렉터리 준비
APP_USER="$(logname 2>/dev/null || echo ubuntu)"
mkdir -p /home/"$APP_USER"/app/data/nginx
mkdir -p /home/"$APP_USER"/app/data/certbot/conf
mkdir -p /home/"$APP_USER"/app/data/certbot/www
chown -R "$APP_USER":"$APP_USER" /home/"$APP_USER"/app
# 7. 스왑 2GB
if [ ! -f /swapfile ]; then
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
fi
touch /var/lib/muro-setup-done
echo "초기 셋업 완료"
- GCP 콘솔에서 VM 인스턴스 생성 시 [관리, 보안, 디스크, 네트워킹, 단독 테넌시] > [자동화] 항목에서 위 스크립트 내용을 시작 스크립트(Startup Script)란에 붙여넣는다
각 항목 설명
- 스크립트 상단 설정
- set -euo pipefail
- 스크립트 중간에 명령 하나가 실패해도 그냥 다음으로 넘어가면 안된다. 이 옵션을 켜두면 어떤 명령이든 실패하는 즉시 스크립트 전체가 중단된다
- export DEBIAN_FRONTEND=noninteractive
- apt-get install 도중 “이 설정 파일 덮어쓸까요?” 같은 대화형 프롬프트가 뜨면, 사람이 없는 startup-script는 그 자리에서 멈춰버린다. 이 값을 주면 apt가 질문없이 기본값으로 진행한다.
- 이미 셋업 완료 여부 체크 (/var/lib/muro-setup-done)
- startup-script는 VM이 재부팅될 때마다 다시 실행된다. 이 체크가 없으면 VM을 재부팅할 때마다 설치를 처음부터 다시 시도해서 부팅 시간이 의미없이 늘어나고, 이미 떠 있는 서비스에 영향을 줄 수도 있다.
- 타임존 설정 (Asia/Seoul)
- VM은 기본이 UTC다. 로그, 모니터링 타임스탬프가 실제 한국 시간과 차이나면, 장애 발생 시간을 찾아볼 때 대응 속도가 느려진다.
- Docker + Compose 플러그인 설치
- 서비스 전체가 컨테이너로 배포되는 구조이기 때문에 Docker가 필요하다. docker-compose.yml로 4개 서비스를 관리하기 때문에 Compose 플로그인도 같이 필요하다.
- usermod -aG docker는 매번 sudo docker 를 안 쳐도 되게 하는 편의 설정이다. CD 파이프라인이 SSH로 들어와서 docker compose 명령을 실행할 때 권한 문제 없이 바로 되게 해준다
- 디버깅용 기본 유틸리티
- git : 설정 파일이나 스크립트를 저장소에서 직접 받아야 할 상황 대비
- vim : SSH로 들어가서 nginx 설정등을 급하게 고쳐야 할 때
- htop : 지금 이 컨테이너가 CPU를 얼마나 쓰고 있는지를 확인할 때 필요
- net-tools : netstat 등으로 포트가 실제로 열려있는지, 어떤 프로세스가 물고 있는지 확인할 때
- unzip : 압축 파일로 받을 설정/인증서 등을 풀어야 할 상황 대비
- Google Cloud SDK 설치 + Artifact Registry pull 인증
- CD 파이프라인이 각 레포에서 빌드한 이미지를 Registry에 push 해두면, VM은 그걸 pull해야 한다. 그런데 pull 하려면 인증이 필요하다.
- gcloud auth configure-docker가 Docker 에게 "Artifact Registry에 접근할 때 이 VM에 붙어있는 서비스 계정 자격증명을 써라"라고 등록해주는 역할이다. 이게 없으면 docker compose pull 단계에서 인증 실패로 이미지를 못 받아온다.
- Ops Agent 설치
- 두 가지 용도로 쓰인다. 하나는 docker-compose.yml 의 gcplogs 로깅 드라이버가 실제로 로그를 Cloud Logging 까지 보내는 경로이고, 다른 하나는 VM 자체의 CPU/메모리 지표를 Cloud Monitoring 에서 볼 수 있게 해주는 것이다.
- 배포 디렉터리 준비
- docker-compose.yml 에서 nginx/certbot 이 볼륨으로 마운트하는 경로들이다. 미리 만들어두지 않으면 Docker가 자동으로 만들어버리는데, 이 경우 소유자가 root로 잡혀서 나중에 certbot이 인증서를 쓰거나 CD 파이프라인이 SCP로 설정 파일을 올릴 때 권한 오류가 날 수 있다.
- 스왑 2GB
-
e2-medium은 메모리가 4GB뿐인데 4개의 컨테이너를 동시에 띄운다. 특히 AI Server처럼 순간적으로 CPU/메모리를 많이 쓰는 워크로드에서 메모리가 잠깐 부족해지면, OOM이 발생한다. 스왑을 걸어두면 그 순간에 디스크를 임시 메모리처럼 써서 컨테이너가 죽는 걸 막아줄 수 있다. (속도는 느리지만, 죽는 것보단 낫다)
-
참고자료 : https://docs.iwinv.kr/service/compute/virtual-machine/vm-guide/init-script/