본문으로 건너뛰기

Terraform으로 MySQL 기반 3-Tier 웹 서비스 구축

Terraform으로 카카오클라우드의 네트워크, 웹·애플리케이션 서버, 로드 밸런서와 MySQL을 생성하여 3-Tier 웹 서비스를 배포합니다.

기본 정보
  • 예상 소요 시간: 40~60분 (MySQL 프로비저닝 시간 포함)
  • 권장 운영 체제: macOS, Ubuntu
  • Terraform CLI 버전: 1.14.0 이상 (두 실습의 공통 기준)
  • 사전 준비 사항
연계 튜토리얼

Terraform을 처음 사용한다면 Terraform으로 VM 프로비저닝 및 변경을 먼저 진행하세요. 해당 튜토리얼에서 CLI 설치와 IAM 인증 준비, VM의 생성·변경·삭제 흐름을 확인할 수 있습니다.

시나리오 소개​

이 튜토리얼에서는 Terraform으로 카카오클라우드 라이브러리 웹 서비스에 필요한 네트워크, 서버, 로드 밸런서와 MySQL을 구성합니다.

웹·애플리케이션 서버를 각각 2대씩 생성해 로드 밸런서에 연결하고, user_data로 컨테이너를 자동 배포합니다. 배포 후에는 웹 로드 밸런서의 퍼블릭 IP로 서비스에 접속해 결과를 확인합니다.

Terraform으로 생성할 주요 리소스와 웹 요청 흐름은 다음과 같습니다. 위쪽은 웹 서비스 요청 경로이며, 아래쪽은 Bastion 호스트를 통한 관리 접속 경로입니다.

                         Internet
| HTTP:80
v
+-- VPC: tutorial (10.0.0.0/16) / kr-central-2-a -----+
| |
| tutorial-main [Public ALB: tutorial-web-lb] |
| | HTTP:80 |
| +-------+-------+ |
| v v |
| tutorial-web [Web VM 1] [Web VM 2] |
| | | |
| +-------+-------+ |
| | TCP:8080 |
| v |
| tutorial-alb [Internal NLB: tutorial-app-lb]|
| | TCP:8080 |
| +-------+-------+ |
| v v |
| tutorial-app [App VM 1] [App VM 2] |
| | | |
| +-------+-------+ |
| | TCP:3306 |
| v |
| tutorial-mysql [Managed MySQL: tutorial-mysql]|
| |
| tutorial-main [Bastion: tutorial-bastion] |
+-----------------------------------------------------+

Admin (admin_cidr)
| SSH:22 / Admin UI:81 / Forwarding:10000-10010
v
Bastion -- TCP forwarding --> Web / App VM SSH:22

웹·애플리케이션 서버는 각 로드 밸런서가 요청을 분산하는 대상입니다. Bastion 호스트는 웹 요청 경로에 포함되지 않으며, 관리자 접속을 중계합니다. 구성도에서 Web VM 1/2는 tutorial-web-1/2, App VM 1/2는 tutorial-app-1/2를 나타냅니다.

실습 구성
  • 이 예제는 단일 가용 영역에 배포하며 MySQL은 Primary 인스턴스 1대로 구성합니다. 웹·애플리케이션 서버를 2대씩 생성하지만 가용 영역 장애에 대비한 구성은 포함하지 않습니다.
  • 콘솔에서 리소스를 직접 생성하는 과정은 VM 기반 웹 서비스 구성을 참고하세요.

시작하기 전에​

정보

앞서 Terraform으로 VM 프로비저닝 및 변경 튜토리얼을 진행했다면 생성한 VM을 삭제한 뒤, 이 예제용 작업 디렉터리를 새로 준비하세요.

예제를 실행할 프로젝트에서 다음 항목을 준비합니다. VPC와 서브넷은 Terraform이 생성하므로 미리 생성할 필요가 없습니다.

항목준비 사항
프로젝트 및 권한VPC, Virtual Machine, Load Balancer, MySQL 리소스를 생성·조회·삭제할 수 있는 프로젝트와 IAM 역할
IAM 액세스 키대상 프로젝트에서 발급한 IAM 액세스 키 ID와 보안 액세스 키
키 페어대상 프로젝트에 등록된 키 페어 이름과 해당 프라이빗 키 파일
로컬 도구git, curl, ssh 명령을 실행할 수 있는 환경
관리자 접속 IPBastion 호스트에 접속할 로컬 환경의 공인 IP를 CIDR 형식으로 준비 (예: 203.0.113.10/32)
키 페어 확인

예제는 등록된 키 페어를 조회하며 새 키 페어를 생성하지 않습니다. 기본 이름은 kr2-cloudtech입니다. 다른 키 페어를 사용하려면 terraform.tfvars에 keypair_name을 지정하세요.

리소스 구성​

기본 변수값으로 실행하면 kr-central-2 리전의 kr-central-2-a 가용 영역에 다음 리소스를 생성합니다. 이름의 tutorial 접두사는 project_name 변수로 변경할 수 있습니다.

네트워크​

VPC 1개와 용도별 서브넷 5개를 생성합니다.

구분이름CIDR설명
VPCtutorial10.0.0.0/16전체 리소스가 속하는 VPC
Subnet (main)tutorial-main10.0.0.0/20Bastion, 웹 로드 밸런서
Subnet (web)tutorial-web10.0.16.0/20웹 계층 인스턴스
Subnet (alb)tutorial-alb10.0.32.0/20내부(앱) 로드 밸런서
Subnet (app)tutorial-app10.0.48.0/20애플리케이션 계층 인스턴스
Subnet (mysql)tutorial-mysql10.0.64.0/20관리형 MySQL
안내

예제는 인터넷 게이트웨이(IGW)를 생성해 VPC에 연결하고, 0.0.0.0/0 → IGW 기본 경로를 라우트 테이블에 등록합니다. 네트워크와 계층별 보안 그룹 규칙은 network.tf에서 확인할 수 있습니다.

인스턴스​

Bastion 호스트 1대와 웹·애플리케이션 서버 4대를 생성합니다.

구분이름인스턴스 타입퍼블릭 IP  이미지
Bastiontutorial-bastionm2a.largeYUbuntu 20.04
WEB1tutorial-web-1m2a.largeNUbuntu 20.04
WEB2tutorial-web-2m2a.largeNUbuntu 20.04
WAS1tutorial-app-1m2a.largeNUbuntu 20.04
WAS2tutorial-app-2m2a.largeNUbuntu 20.04
안내

웹 서버는 React 앱을 Nginx로 배포하고, 애플리케이션 서버는 Spring Boot 앱을 실행합니다. 인스턴스 생성 후에도 cloud-init의 설치·빌드 작업이 진행되므로 컨테이너가 준비될 때까지 기다려야 합니다.

로드 밸런서​

외부 요청과 계층 간 요청을 처리할 로드 밸런서 2개를 생성합니다.

구분이름퍼블릭 IP  설명
EXT-LBtutorial-web-lbY웹 서버(ALB, HTTP:80)에 대한 부하 분산
INT-LBtutorial-app-lbN애플리케이션 서버(NLB, TCP:8080)에 대한 부하 분산

데이터베이스​

관리형 MySQL 인스턴스 그룹을 다음 구성으로 생성합니다.

구분이름설명
MySQLtutorial-mysqlPrimary 인스턴스 1대, 데이터 디스크 100 GB, 로그 디스크 100 GB, 백업 미사용

퍼블릭 IP는 웹 로드 밸런서와 Bastion 호스트에 연결됩니다. 웹·애플리케이션 인스턴스에는 퍼블릭 IP를 연결하지 않습니다. Bastion 호스트의 SSH(22), 관리 화면(81), 포트 포워딩 범위(10000~10010)는 admin_cidr로 지정한 IP 대역에서만 접근할 수 있습니다.

시작하기​

Terraform 실행 환경을 확인하고 예제를 준비한 뒤, 리소스를 생성해 웹 서비스의 동작을 확인합니다. 실습을 마치면 생성한 리소스를 삭제합니다.

Step 1. 실행 환경 확인​

VM 튜토리얼의 설치 안내를 참고해 Terraform CLI 1.14.0 이상과 git을 준비하고 버전을 확인합니다.

terraform version
git --version

Provider 버전은 이 예제의 versions.tf를 사용합니다. VM 튜토리얼에서 사용한 .tf 파일, .terraform 디렉터리와 상태 파일을 복사하지 마세요. 이후의 명령은 다운로드한 kakaocloud-3tier-terraform 디렉터리에서 실행합니다.

Step 2. 예제 다운로드 및 입력값 설정​

  1. Terraform 3-Tier 예제를 다운로드합니다.

    예제 다운로드
    git clone --single-branch --branch feat/terraform-3tier-example https://github.com/kakaoenterprise/kakaocloud-tutorials.git kakaocloud-3tier-terraform

    구성은 다음 파일로 나뉩니다. 전체 변수를 확인할 때는 variables.tf, 기본 입력값을 확인할 때는 terraform.tfvars.example을 사용합니다.

    파일확인할 내용
    versions.tf, variables.tfCLI·Provider 요구 버전과 입력 변수
    data.tf, locals.tf, keypair.tf이미지·인스턴스 유형·키 페어 조회와 조회 결과 선택
    network.tfVPC·서브넷·인터넷 게이트웨이·라우트·보안 그룹
    mysql.tfMySQL 생성, 엔드포인트 대기와 조회
    lb_app.tf, app.tf내부 NLB와 애플리케이션 인스턴스
    lb_web.tf, web.tf퍼블릭 ALB와 웹 인스턴스
    bastion.tf, outputs.tfBastion 호스트와 배포 결과 출력
  2. 예제 프로젝트가 있는 경로로 이동합니다.

    설치 경로로 이동
    cd ./kakaocloud-3tier-terraform
  3. 카카오클라우드 인증 정보를 환경 변수로 등록합니다. IAM 액세스 키 발급에서 준비한 값을 입력합니다. 인증 정보는 terraform.tfvars에 작성하지 않습니다. VM 튜토리얼을 마치고 환경 변수를 해제했거나 새 터미널을 열었다면 다시 설정해야 합니다.

    export TF_VAR_application_credential_id="${ACCESS_KEY}"
    export TF_VAR_application_credential_secret="${SECRET_ACCESS_KEY}"
    환경변수설명
    ACCESS_KEY🖌︎IAM 액세스 키 ID
    SECRET_ACCESS_KEY🖌︎보안 액세스 키
  4. 변수 파일을 복사합니다.

    변수 파일 준비
    cp terraform.tfvars.example terraform.tfvars
  5. terraform.tfvars를 편집하여 아래 값을 입력합니다. admin_cidr에는 현재 접속 위치의 공인 IP와 /32를 지정합니다. 예제 파일의 비밀번호를 그대로 사용하지 마세요.

    admin_cidr = "${ADMIN_CIDR}"
    mysql_database_user_password = "${MYSQL_PASSWORD}"
    keypair_name = "${KEYPAIR_NAME}"
    환경변수설명
    ADMIN_CIDR🖌︎관리자 공인 IP의 CIDR, 예: 203.0.113.10/32
    MYSQL_PASSWORD🖌︎MySQL 관리자 비밀번호
    KEYPAIR_NAME🖌︎프로젝트에 등록된 키 페어 이름
    변수입력 내용
    admin_cidrBastion 호스트에 관리 접속을 허용할 IP 대역
    mysql_database_user_password기본값이 없는 필수 변수. MySQL 비밀번호 정책에 맞는 값 입력
    keypair_name등록된 키 페어 이름. 생략하면 kr2-cloudtech 사용
로컬 파일 관리

terraform.tfvars에는 비밀번호가 평문으로 저장됩니다. Terraform 상태 파일과 계획 파일에도 민감한 정보가 포함될 수 있으므로 terraform.tfvars, terraform.tfstate*, 저장한 계획 파일과 프라이빗 키를 버전 관리에 포함하지 마세요.

Step 3. 연결 관계 확인 및 리소스 생성​

먼저 구성에서 값이 전달되는 관계를 확인합니다. Terraform은 파일명의 순서가 아니라 리소스 참조와 depends_on으로 실행 순서를 계산합니다.

연결확인할 내용
네트워크 → 인스턴스각 인스턴스의 subnets가 network.tf에서 생성한 서브넷 ID를 참조
MySQL → 애플리케이션mysql.tf에서 조회한 엔드포인트를 app.tf의 MYSQL_HOST에 전달
내부 NLB → 웹 서버web.tf의 SERVER_ENDPOINT에 내부 NLB의 프라이빗 VIP와 8080 포트를 전달
인스턴스 → 로드 밸런서각 로드 밸런서의 대상 그룹에 해당 계층 인스턴스를 등록
생성 결과 → 출력값outputs.tf에서 접속 주소와 인스턴스 IP를 출력

예제 디렉터리에서 구성을 검증하고 실행 계획을 확인한 뒤 리소스를 생성합니다.

  1. 현재 작업 디렉터리를 초기 설정합니다.

    디렉터리 초기 설정
    terraform init
  2. 구성의 문법과 참조를 검증하고 실행 계획을 확인합니다. 생성할 리소스와 입력값이 의도한 구성인지 확인하세요.

    계획
    terraform fmt -check
    terraform validate
    terraform plan

    계획에 기존 리소스의 삭제나 교체가 없는지 확인합니다. 기본 변수값에서는 VPC 1개, 서브넷 5개, 보안 그룹 4개, VM 5대, 로드 밸런서 2개, MySQL 인스턴스 그룹 1개와 퍼블릭 IP 2개를 생성합니다. 리스너·대상 그룹·라우트 등도 포함되므로 계획의 총 리소스 수는 이 목록보다 많습니다.

  3. 실행 계획을 적용합니다. 표시된 변경 사항을 확인하고 승인 프롬프트에 yes를 입력합니다.

    클라우드에 적용
    terraform apply
안내

관리형 MySQL이 AVAILABLE 상태가 되어도 엔드포인트가 아직 제공되지 않을 수 있습니다. 예제는 인스턴스 그룹 생성 후 2분간 대기하고 엔드포인트를 다시 조회합니다. 엔드포인트가 비어 있으면 애플리케이션 인스턴스를 생성하기 전에 실행을 중단합니다. 이 경우 콘솔에서 엔드포인트가 생성된 것을 확인한 뒤 terraform apply를 다시 실행하세요.

Step 4. 배포 결과 확인​

리소스 생성이 완료되면 콘솔과 브라우저에서 결과를 확인합니다. 웹·애플리케이션 인스턴스의 컨테이너 빌드는 terraform apply가 끝난 후에도 진행될 수 있습니다.

  1. 콘솔에 접속하여 Load Balancer 메뉴에서 tutorial-web-lb(퍼블릭 ALB)와 tutorial-app-lb(내부 NLB)가 정상적으로 생성되었는지 확인합니다.

  2. Virtual Machine 메뉴에서 Bastion 호스트 1대와 웹·애플리케이션 인스턴스 각 2대가 생성되었는지 확인합니다. 이름은 인스턴스 구성을 참고하세요.

  3. MySQL 메뉴에서 tutorial-mysql 인스턴스 그룹의 상태와 엔드포인트를 확인합니다. terraform apply가 정상적으로 끝났다면 아래처럼 출력된 값과 동일한 엔드포인트를 확인할 수 있습니다.

    출력값으로 확인
    terraform output mysql_endpoint
  4. 웹 서비스가 정상적으로 생성되었는지 브라우저를 통해 로드 밸런서 tutorial-web-lb의 퍼블릭 IP로 접속합니다. 아래 출력값을 사용해 http://{WEB_LOAD_BALANCER_PUBLIC_IP} 주소를 열고 라이브러리 화면이 표시되는지 확인합니다. 회원가입·로그인 등 데이터베이스를 사용하는 동작도 확인하여 웹 화면뿐 아니라 애플리케이션과 MySQL 연결까지 점검합니다.

    웹 서비스 접속 주소 확인
    terraform output -raw web_load_balancer_public_ip

Step 5. 인스턴스 상태 확인 (선택)​

웹·애플리케이션 인스턴스에 직접 접속하려면 Bastion 호스트에서 TCP 포트 포워딩을 설정합니다.

  1. 아래 명령으로 Nginx Proxy Manager 접속 주소를 확인하고 브라우저에서 엽니다. Bastion 호스트의 관리 화면은 admin_cidr에 지정한 IP 대역에서 접속합니다.

    Bastion 공인 IP / NPM 접속 주소 확인
    terraform output bastion_public_ip
    terraform output nginx_proxy_manager_url
    • 초기 이메일: admin@example.com
    • 초기 비밀번호: changeme
    안내

    초기 계정으로 로그인한 직후 반드시 비밀번호를 변경하세요.

  2. Streams 탭에서 접속할 인스턴스마다 10000~10010 범위의 포트를 지정하고, 해당 인스턴스의 프라이빗 IP와 22번 포트로 전달하는 규칙을 추가합니다. 프라이빗 IP는 다음 명령으로 확인합니다.

    웹/앱 인스턴스 프라이빗 IP 확인
    terraform output web_instance_private_ips
    terraform output app_instance_private_ips
  3. 규칙을 추가한 뒤 등록한 키 페어의 프라이빗 키로 접속합니다. {PRIVATE_KEY_PATH}, {FORWARDED_PORT}, {BASTION_PUBLIC_IP}를 각각 키 파일 경로, 포워딩 포트와 Bastion 호스트의 퍼블릭 IP로 바꾸세요.

    포트 포워딩을 통한 SSH 접속
    chmod 400 "{PRIVATE_KEY_PATH}"
    ssh -i "{PRIVATE_KEY_PATH}" -p "{FORWARDED_PORT}" ubuntu@{BASTION_PUBLIC_IP}
  4. 인스턴스에서 cloud-init 완료 여부와 컨테이너 상태를 확인합니다.

    초기화 및 컨테이너 상태 확인
    sudo cloud-init status --wait
    sudo docker ps -a

    웹 서버에는 kakaocloud-library-client, 애플리케이션 서버에는 kakaocloud-library-server 컨테이너가 실행되어 있어야 합니다. 설치나 실행이 실패했다면 /var/log/cloud-init-output.log와 해당 컨테이너의 로그를 확인합니다.

Step 6. 구성 유지 상태 확인 및 리소스 삭제​

배포 결과를 확인한 뒤 같은 디렉터리에서 terraform plan을 다시 실행합니다. 구성을 변경하지 않았다면 추가 변경이 없는지 확인합니다. 차이가 표시되면 내용을 검토하고, 변경 의도가 없는 상태에서 바로 적용하지 마세요.

terraform plan

실습을 완료하면 예제 디렉터리에서 생성한 리소스를 삭제합니다. 삭제 계획을 확인하고 승인 프롬프트에 yes를 입력합니다. MySQL 데이터가 필요하다면 삭제 전에 별도로 보관하세요.

생성된 리소스 삭제
terraform destroy

Terraform이 삭제를 완료한 뒤 terraform state list와 콘솔에서 리소스가 남아 있는지 확인합니다. 삭제가 실패하면 오류에 표시된 연결 관계와 리소스 상태를 확인하고 terraform destroy를 다시 실행합니다. 조회만 한 기존 키 페어는 삭제 대상에 포함되지 않습니다.

주의

실습을 모두 완료한 후 생성된 리소스를 반드시 제거해야 합니다. 특히 관리형 MySQL과 로드 밸런서는 계속 실행 중인 동안 비용이 발생하므로 실습 종료 후 바로 삭제하는 것을 권장합니다.

문제 해결​

인증 또는 조회가 실패하는 경우​

VM 튜토리얼의 인증 오류 점검을 참고하세요. 이 예제에는 VM뿐 아니라 VPC, Load Balancer, MySQL을 조회·생성·삭제할 권한도 필요합니다. 키 페어 이름과 이미지·인스턴스 유형은 각각 keypair.tf, data.tf, locals.tf의 조회 조건과 대상 프로젝트에서 사용 가능한 값을 비교합니다.

웹 서비스에 접속되지 않는 경우​

다음 순서로 연결을 확인합니다.

  1. 웹 로드 밸런서의 퍼블릭 IP와 HTTP 80 리스너, 웹 인스턴스의 대상 상태를 확인합니다.
  2. 인스턴스 상태 확인에 따라 웹·애플리케이션 서버의 cloud-init과 컨테이너 로그를 확인합니다.
  3. 내부 NLB의 TCP 8080 리스너와 애플리케이션 인스턴스의 대상 상태를 확인합니다.
  4. MySQL 엔드포인트와 앱에 전달한 계정·비밀번호, 계층별 보안 그룹의 허용 포트를 확인합니다.

실행 도중 오류가 나도 일부 리소스는 이미 생성되어 있을 수 있습니다. 상태 파일을 유지한 채 원인을 수정하고 계획을 다시 확인하거나 terraform destroy로 정리하세요.