holssi 님의 블로그
Terraform으로 NCP 서버 구축 본문
terraform으로 서버, subnet, acg, vpc 생성 테스트이다
아래는 terraform 구조이다
- main.tf
ncp provider 설정(인증키 포함), network_module, server_module를 호출함
network_module의 output 값(vpc_no, subnet_id, acg_id) 등을 server_module에 넘김

- variables.tf
ncp 접속을 위한 access_key, secrey_key 및 공통으로 사용할 변수 정의

- .terraform.lock.hcl: 사용하는 provider(ncp, time 등)의 버전을 고정하여 협업시 동일한 환경 보장
- terraform.tfstate: 현재 배포된 인프라 상태 기록
다음은 네트워크 모듈이다
- vpc.tf
논리적으로 분리된 vpc를 만듬

- subnet.tf
vpc를 쪼개서 실제 리소스가 배치될 서브넷 생성, 외부에 접속이 가능하도록 subnet_type을 PUBLIC으로 함

- agc.tf
보안그룹 agc 생성
terraform을 사용하다보면 리소스 간의 생성 순서가 꼬여서 에러가 나는 경우가 있음 -> depends_on, time_sleep으로 해결
terraform은 코드를 분석해 리소스 간의 관계를 스스로 파악함
vpc 생성 후 subnet 만들도록 하고, api는 리소스 생성이 완료되었다고 테라폼에 응답을 보내더라도 실제 클라우드 내부 시스템에 반영되기까지는 몇 초 정도 시간이 걸림

- agc_rule.tf
acg에 구체적인 규칙 넣기

보안상 any 22 포트는 위험해서, 아래와 같이 수정함

- outputs.tf
vpc_no, subnet_id, acg_id를 main.tf에서 쓸 수 있도록 노출

- variables.tf
네트워크 모듈 내부에서 사용할 기본값(cidr블록, 리존 존) 정의

다음은 서버 모듈이다
- servers.tf
# Login Key
resource "ncloud_login_key" "server_key" {
key_name = "${var.name_terra}-key-1"
}
resource "local_file" "ncp_pem" {
filename = "${ncloud_login_key.server_key.key_name}.pem"
content = ncloud_login_key.server_key.private_key
}
# NIC
resource "ncloud_network_interface" "nic" {
name = "${var.name_terra}-nic"
subnet_no = var.subnet_public_id
access_control_groups = [var.acg_id]
}
# KVM 이미지 번호 조회
data "ncloud_server_image_numbers" "kvm_image" {
server_image_name = "rocky-9.6-base"
filter {
name = "hypervisor_type"
values = ["KVM"]
}
}
# KVM 서버 스펙 조회
data "ncloud_server_specs" "kvm_spec" {
filter {
name = "server_spec_code"
values = ["s2-g3"] # 2core 4GB Standard
}
}
# Server
resource "ncloud_server" "server" {
subnet_no = var.subnet_public_id
name = "${var.name_terra}-svr"
server_image_number = data.ncloud_server_image_numbers.kvm_image.image_number_list[0].server_image_number
server_spec_code = data.ncloud_server_specs.kvm_spec.server_spec_list[0].server_spec_code
login_key_name = ncloud_login_key.server_key.key_name
network_interface {
network_interface_no = ncloud_network_interface.nic.id
order = 0
}
}
# Export Root Password
data "ncloud_root_password" "default" {
server_instance_no = ncloud_server.server.instance_no
private_key = ncloud_login_key.server_key.private_key
}
resource "local_file" "bastion_svr_root_pw" {
filename = "${ncloud_server.server.name}-root_password.txt"
content = "${ncloud_server.server.name} => ${data.ncloud_root_password.default.root_password}"
}
# Public IP
resource "ncloud_public_ip" "public_ip" {
server_instance_no = ncloud_server.server.id
description = "for ${ncloud_server.server.name} public ip"
}
서버 접속용 비밀키를 생성하고 로컬 파일(.pem)에 저장하고, 서버에 꽂을 가상 랜카드를 만들고 ACG를 연결함
NCP 이미지 목록에서 Rocky Linux 9.6을 스펙 목록에서 s2-g3를 자동으로 찾아오도록 설계
위 정보들을 조합해 실제 서버를 생성하고 공인 ip를 할당함
서버 생성 후 초기 비밀번호를 가져와 텍스트 파일로 저장함
- variables.tf
네트워크 모듈로부터 넘겨받을 변수들을 선언해주는 통로 역할

- providers.tf
서버 모듈이 ncp 리소스를 다룰 수 있도록 라이브러리 불러옴

에러1: 테라폼은 hasicorp/ 네임스페이스에서 프로바이더를 찾으려 하지만, 네이버 클라우드 프로바이더는 navercloudplatform/ncloud 경로를 사용해야함

해결: main.tf 파일의 가장 윗부분에 아래 내용 추가
에러2: vpc가 완전히 생성되어야 acg를 만들 수 있는대, 현재 ncp에 vpc가 없는 상태에서 acg를 만들려다가 실패

해결: agc.tf에 depends_on, time_sleep 추가
terraform plan 단계에서 이미 삭제된 것들을 state에서 읽을때 에러가 남
1. terraform state list
2. 출력된 결과물을 terraform state rm []
3. 다시 init -> plan -> apply
성공


terraform은 실행 시점에 2가지를 비교함
.tf 파일과 terraform.tfstate를 비교한다. terraform.tfstate은 실제 클라우드에 생성된 현재 상태의 기록이다
그래서 terraform plan 시 이미 해당하는 서버(서버명, 스펙)이 있다면 no changes. your infrastructure matches the configuration 메시지를 띄움
지금까지는 단독형 서버1대를 생성했다면 이제는 여러 서버를 한번에 생성해보려고 한다.
현재 vpc.tf 파일인데 이미 terraform apply를 했기 때문에 vpc가 생성된 상태이다. 한 계정당 vpc를 3개까지밖에 생성이 안되기 때문에 이미 생성된 vpc를 사용하려고 한다.
terraform.tfstate는 현재 상태를 기록하기 때문에 servers.tf 만 수정하면 기존vpc를 이용해서 아무 문제 없이 서버가 잘 생성된다

만들 서버 스펙

1. rocky-9.6-base standard, s4-g3a,
2. rocky-9.6-base standard, s4-g3
3. rocky-8.10-base standard, s4-g2-s50(vcpu 4EA. Memory 16GB, [SSD] Disk 50GB)
4. rocky-8.10-base standard, s4-g2-h50(vcpu 4EA. Memory 16GB, [SSD] Disk 50GB)
스토리지 이름은 각 서버 이름-ssd, 서버이름-hdd으로 할거고, 인증키는 이미 가지고 있는 걸로, acg는 rule이 바뀌어서 새로 생성
근데 rocky8.10은 스토리지 커스텀이 안돼서 위에 요청한대로 코드 수정하기
서버 대수만 늘어나거나 스펙이 계속 바뀌더라도 main.tf에서 리스트만 수정하면 자동으로 반영되는 구조로 바꿈
이를 위해서는 variable, map 구조 쓰기 -> server_module 내부는 수정할 필요 없이 main.tf나 terraform.tfvars 파일만 수정하기
main.tf에 서버별로 스펙을 map 형태로 정의하여 모듈로 전달

server_module/variables.tf에 추가
모듈이 서버 리스트 데이터를 받을 수 있도록 변수 선언

server_module/servers.tf 수정
for_each를 사용해서 입력받은 서버 개수만큼 리소스 생성
NCP의 3세대(g3) 서버는 kvm을 사용하고, 기존 세대(g2)는 XEN을 사용하기 때문에 이미지마다 하이퍼바이저 타입이 다를 수 있다 -> main.tf 리스트에 hypervisor 정보를 추가하고, data 소스에서 이 값을 동적으로 필터링하도록 수정
에러1: 공인ip 생성이 안됨
서버가 생성된 후 ip 부여하도록 depends_on 추가로 해결

에러2: terraform이 ncloud_access_control_group_rule을 생성하려고 하는데, 참조하고 있는 ACG(Access Control Group)의번호를 naver cloud api 시스템에서 아직 인식하지 못하고 있어서 Race condition(경합 상태) 문제 발생함

acg가 default acg 모드로 생성되고 있어서 ncloud vpc는 vpc 생성 직후, default acg가 자동으로 생성되고, terraform이 만든 acg가 실제 반영되기까지 시간이 더 걸린다
이 상태에서 rule api 호출하면 ncloud 내부에서 조회가 안되는 경우가 있음
해결 과정
vpc 생성 -> acg 생성 -> rule 생성을 한 번에 하지 말자
1. vpc 생성
terraform apply -target="module.network.ncloud_vpc.vpc"
2. acg 생성
terraform apply -target="module.network.ncloud_access_control_group.bastion_acg_01"
3. 전체 apply
이렇게 했는데도 같은에러 남
acg가 다른 vpc에서 생성되고 있나?
terraform state show module.network.ncloud_access_control_group.bastion_acg_01
terraform state show module.network.ncloud_vpc.vpc
확인했더니 vpc_no이 같았음
agc_rule파일 depends_on에 추가로 해결

한번에 여러 서버 생성해보자
- main.tf
servers 목록에 image_number, spec_code, hypervisor를 넣어서 map 형식으로 여러개 추가함
(이미 서버 여러대를 만들었고 추가로 만들려고 목록을 지웠더니 ncp에서도 지워짐. terraform은 선언적 도구라서 현재 코드 상태와 실제 클라우드 상태를 똑같이 맞추려고 함. 그래서 코드에 없는 기존 서버는 삭제되고, 코드에 있는 새 서버만 생성됨)

- servers.tf
# 1. Login Key
resource "ncloud_login_key" "server_key" {
key_name = "${var.name_terra}-key-17"
}
# 2. 서버 생성
resource "ncloud_server" "server" {
for_each = var.servers
subnet_no = var.subnet_public_id
name = each.key
# data 소스가 아니라 변수에서 넘겨받은 image_number를 직접 사용
server_image_number = each.value.image_number
server_spec_code = each.value.spec_code
login_key_name = ncloud_login_key.server_key.key_name
network_interface {
network_interface_no = ncloud_network_interface.nic[each.key].id
order = 0
}
}
# 3. NIC 생성
resource "ncloud_network_interface" "nic" {
for_each = var.servers
name = "${each.key}-17"
subnet_no = var.subnet_public_id
access_control_groups = [var.acg_id]
}
# 4. 스토리지
resource "ncloud_block_storage" "storage_ssd" {
for_each = var.servers
server_instance_no = ncloud_server.server[each.key].id
name = "${var.name_terra}-ssd"
size = 10
disk_detail_type = "SSD"
}
resource "ncloud_block_storage" "storage_hdd" {
for_each = var.servers
server_instance_no = ncloud_server.server[each.key].id
name = "${var.name_terra}-hdd"
size = 10
disk_detail_type = "HDD"
}
# 5. Public IP 생성 및 부여
resource "ncloud_public_ip" "public_ip" {
for_each = var.servers
server_instance_no = ncloud_server.server[each.key].id
depends_on = [ncloud_server.server]
}
# 6. Password Export (공인 IP 부여 이후에 실행되도록 권장)
data "ncloud_root_password" "default" {
for_each = var.servers
server_instance_no = ncloud_server.server[each.key].id
private_key = ncloud_login_key.server_key.private_key
# 공인 IP 부여 과정 등 서버 설정이 안정화된 후 패스워드를 가져오기 위함
depends_on = [ncloud_public_ip.public_ip]
}
resource "local_file" "root_pw" {
for_each = var.servers
filename = "${var.name_terra}-${each.key}-pw.txt"
content = "${each.key} => ${data.ncloud_root_password.default[each.key].root_password}"
}
여러 대의 서버를 생성하다보면 특정 서버들이 운영중이더라도 공인ip가 늦게 부여됨
커스텀 이미지로 만들어서 공인ip 생성이 안된건가???
