본문 바로가기
Terraform

Terraform을 이용하여 NCP 서버 배포

by 간식주인 2022. 8. 5.

엔지니어라면 특정 CSP의 서버를 통해 여러가지 테스트를 진행하는 경우가 있습니다.

오늘은 NCP에서 공인아이피가 부착된 Ubuntu 20.04 서버를 빠르게 생성하는 테라폼 코드에 대해 다뤄보도록 하겠습니다.

 

요건

 - NCP 콘솔을 이용하지 않고 빠르게 서버를 배포하여 테스트 용도로 사용하고자 함

 

환경

  • 1 VPC
  • 1 Public Subnet
  • 1 Bastion server(ubuntu 20.04 , 2cpu 4GB RAM) + 1 Public IP
  • 1 Network interface
  • 1 ACG(+Rule)

Process

  1. IaC란?
  2. NCP IaC의 종류
  3. 테라폼을 이용한 NCP 인프라 서비스 생성
  4. 테라폼을 이용한 NCP 인프라 서비스 삭제

주의 사항

- 테라폼으로 인프라 서비스 생성 이후 콘솔상에서 변경한 내역이 있다면, 테라폼 코드에도 반영해주어야 합니다. 그렇지 않으면 정합성이 깨지게되어 테라폼을 통한 생성/수정/삭제에 영향을 미칠 수 있습니다.

========================================================================================

 

1. IaC란?

네트워크, 로드밸런서, 저장소, 서버 등의 인프라 자원을 수동 설정(CSP 웹 콘솔 혹은 온프레미스)이 아닌 코드를 이용하여 프로비저닝하고 관리하는 것으로 대표적인 IaC도구로 Terraform, Ncloud cli, CloudFormation, Pulumi, Azure ARM Template 등이 있습니다.

 

좀 더 쉽게 설명을 드리면, 우리가 사용하는 CSP 포탈의 조작을 통해 인프라 서비스를 생성하는 것이 아닌 코드를 통해 인프라 서비스를 배포하고 관리하는 것입니다.

 

2. NCP IaC의 종류

ncp IaC의 종류에는 3가지가 있습니다.

 

첫 번째로 프로그래밍 코드 작성을 통해 인프라 서비스를 제어할 수 있습니다.

NCP의 대부분의 인프라 리소스들은 사용자가 코딩한 API 요청에 의해 생성/변경/삭제가 가능하지만 각 서비스들마다 사용해야하는 API 주소가 다르며, 모든 서비스들에 대해 지원하는 것은 아닙니다.

 

두 번째로 Ncloud CLI가 있습니다.

Code에 비해 몇 줄의 명령어로 인프라 서비스를 제어할 수 있지만, 각 서비스들의 고유 값들을 인수로 필요로 합니다.

 

세 번째로 Terraform NCP Provider가 있습니다.

테라폼의 경우 선언형 코드로 생성할 인프라 서비스 작성이 가능하며, Dependency 체크를 제공하고 배포 속도가 매우 빠릅니다.

 

이번 게시글에서는 테라폼 코드를 이용해 NCP 인프라를 배포해도록 하겠습니다.

 

3. 테라폼을 이용한 NCP 인프라 서비스 생성

먼저 https://www.terraform.io/downloads 해당 링크에서 각 운영체제에 맞게 테라폼을 설치합니다.

 

그런 다음 테라폼 코드를 작성할 폴더를 하나 생성합니다.

 * 테라폼 코드를 작성하기 위해 visual studio code 같은 코드 에디터를 이용하면 편리합니다.

테라폼 코드를 작성하기 위한 module_1 폴더

 

module_1 폴더안에 새로 폴더를 2개 생성 합니다.

각 폴더의 이름은 network_module, server_module로 지정하였습니다.

이후 해당 폴더들은 비슷한 카테고리의 인프라 서비스들의 tf파일을 작성해 저장할 예정입니다.

네트워크 서비스는 network_module   서버 관련 서비스는 server_module

 

본격적으로 테라폼 코드를 tf파일에 작성해보도록 합시다.

module_1 폴더 밑에 작성될 파일은 main.tf , variables.tf 입니다.

나머지 파일은 신경쓰지 않아도 됩니다.

 

먼저  main.tf를 작성합니다.

해당 파일에는 CSP provider의 버전을 명시하고 계정 연결을 위한 키값 및 각 서비스 모듈(폴더) 호출을 위한 코드를 작성합니다.

main.tf

terraform {
    required_providers {
        ncloud = {
            source = "navercloudplatform/ncloud"
        }
    }
    required_version = ">= 0.13"
}
provider "ncloud" {
    support_vpc = true
    region = var.region
    access_key = var.access_key
    secret_key = var.secret_key
}
module "network" {
  source  = "./network_module"

}
module "server" {
  source  = "./server_module"
  vpc_no = module.network.vpc_no
  sunbet_public_id = module.network.subnet_public_id

}

 

variables.tf 파일에는 main.tf에서 필요로하는 사용자 입력 값을 지정합니다.

필요한 값으로는 region, access key, secret key 값이 있습니다.

variable "region" {
  default = "KR"
}
variable "access_key" {
    default = "ncp에서 확인한 access key"
}
variable "secret_key" {
    default = "ncp에서 확인한 secret key"
}

 

이후 네트워크 인프라 생성을 위한 tf 파일을 작성합니다.

해당 파일은 network_module 밑에 위치하게될 예정입니다.

 

기본적으로 각 모듈 폴더에는 NCP Provider의 버전이 명시되어야 하므로 providers.tf를 작성합니다.

terraform {
    required_providers {
        ncloud = {
            source = "navercloudplatform/ncloud"
        }
    }
    required_version = ">= 0.13"
}

 

NCP에서 vpc 환경을 구축하기 위해 vpc.tf를 작성합니다.

#VPC
resource "ncloud_vpc" "vpc" {
name = "${var.name_terra}-vpc"
ipv4_cidr_block = var.vpc_cidr
}
/*Attributes Reference

ncloud_vpc.vpc.vpc_no
ncloud_vpc.vpc.ipv4_cidr_block
ncloud_vpc.vpc.default_network_acl_no

*/

 

Subnet을 생성하기 위해 subnet.tf를 작성합니다.

(Subnet의 경우 public subnet을 생성하는 코드입니다.)

#Public Subnet
resource "ncloud_subnet" "subnet_public" {
    name = "${var.name_terra}-public"
    vpc_no = ncloud_vpc.vpc.vpc_no
    subnet = cidrsubnet(ncloud_vpc.vpc.ipv4_cidr_block, 8, 0) // "10.0.0.0/24"
    zone = var.zones
    network_acl_no = ncloud_vpc.vpc.default_network_acl_no
    subnet_type = "PUBLIC" 
}

 

VPC와 Subnet생성을 위한 사용자 입력 값을 지정하기 위해 variables.tf를 작성합니다.

사용자 입력 값의 경우 원하는 이름이나 대역으로 변경하여 사용합니다.

variable "zones" {
  default =  "KR-2"
}
variable "name_terra" {
    default = "tf-kbo"
}
variable "vpc_cidr" {
    default = "10.0.0.0/16"
}

 

VPC, Subnet 생성 후 자동으로 생성된 vpc id, subnet no의 경우 서버와 ACG 생성에 필요하므로 출력 변수로서 사용하기 위해 outputs.tf를 작성합니다.

output "vpc_no" {
  value       = ncloud_vpc.vpc.vpc_no
}
output "subnet_cidr" {
  value       = ncloud_vpc.vpc.ipv4_cidr_block
}
output "subnet_public_id" {
  value       = ncloud_subnet.subnet_public.id
}

 

네트워크 모듈의 tf 파일 작성이 완료되었으면 서버 모듈의 tf 파일을 작성합니다.

 

server_module에서도 프로바이더 버전을 명시하는 providers.tf 파일을 작성합니다.

terraform {
    required_providers {
        ncloud = {
            source = "navercloudplatform/ncloud"
        }
    }
    required_version = ">= 0.13"
}

Server를 배포하기 위해 server.tf를 작성합니다.(tf 이름은 임의 설정이 가능하며 저는 서버의 용도상 bastion_server.tf로 작성하였습니다.)

server.tf에서 생성한 리소스는 다음과 같습니다.

 - 서버 접속에 필요한 pem key 및 pem key로 확인한 관리자 비밀번호 파일(txt)

 - 사설 아이피 부여를 위한 네트워크 인터페이스

 - 네트워크 인터페이스에 매핑될 ACG와 ACG에 적용될 Rule

 - 외부 접속을 위한 공인 아이피

 - 서버 이미지와 스펙을 산정하기 위해 ncp api 서버로 해당 목록 데이터를 요청하고 그에 따른 필터를 적용하여 필요한 이미지와 스펙을 가져옴

 - 서버 1대

# Login Key
resource "ncloud_login_key" "bastion_server_key" {
    key_name = "${var.name_terra}-bastion-svr-key"
    /*Attributes Reference
    ncloud_login_key.bastion_server_key.private_key
    */
}
resource "local_file" "ncp_pem" {
  filename = "${ncloud_login_key.bastion_server_key.key_name}.pem"
  content = ncloud_login_key.bastion_server_key.private_key
}
# #Init script(optional)
# resource "ncloud_init_script" "init_script_httpd_install" {
#   name    = "httpd-install"
#   content = "#!/bin/bash\nyum -y install httpd\nsystemctl enable --now httpd\necho $HOSTNAME >> /var/www/html/index.html"
# }
#ACG
resource "ncloud_access_control_group" "bastion_acg_01" {
  name        = "${var.name_terra}-acg01"
  description = "${var.name_terra} Access controle group"
  vpc_no      = var.vpc_no # ncloud_vpc.vpc.id
}
resource "ncloud_access_control_group_rule" "bastion_acg_rule_01" {
  access_control_group_no = ncloud_access_control_group.bastion_acg_01.id
  
  inbound {
    protocol    = "TCP"
    ip_block    = "0.0.0.0/0"
    port_range  = "22"
    description = "accept 22 port(all ip)"
  }
  outbound {
    protocol    = "TCP"
    ip_block    = "0.0.0.0/0" 
    port_range  = "1-65535"
    description = "accept TCP 1-65535 port"
  }
  outbound {
    protocol    = "UDP"
    ip_block    = "0.0.0.0/0" 
    port_range  = "1-65535"
    description = "accept UDP 1-65535 port"
  }
  outbound {
    protocol    = "ICMP"
    ip_block    = "0.0.0.0/0" 
    description = "accept ICMP"
  }
}
#NIC
resource "ncloud_network_interface" "nic_bastion" {
  #count = "1"
  name                  = "${var.name_terra}-bastion-nic"
  subnet_no             = var.sunbet_public_id
  #private_ip            = var.bastion
  access_control_groups = [ncloud_access_control_group.bastion_acg_01.id]
  /*Attributes Reference
    ncloud_network_interface.nic_bastion.id
  */
}
# Block storage (optional)
# resource "ncloud_block_storage" "block_storage" {
#     server_instance_no = ncloud_server.bastion_server.instance_no
#     name = "${ncloud_server.bastion_server.name}-blk01"
#     size = "2000"
# }
# Server - bastion server
resource "ncloud_server" "bastion_server" {
    subnet_no = var.sunbet_public_id
    # ncloud_subnet.subnet_public.id
    name = "${var.name_terra}-bastion-svr"
    server_image_product_code = data.ncloud_server_image.server_image.id
    server_product_code = data.ncloud_server_product.product.id
    login_key_name = ncloud_login_key.bastion_server_key.key_name
    network_interface   {
      network_interface_no = ncloud_network_interface.nic_bastion.id
      order = 0
  }
    #VPC에서는 서버 생성 시 ACG 설정을 지원하지 않으므로 acg가 할당된 nic 연결 필요
    /*Attributes Reference
    ncloud_server.bastion_server.instance_no
    */
}
#Server Image Type & Product Type
data "ncloud_server_image" "server_image" {
  filter {
    name = "product_name"
    values = ["ubuntu-20.04"]
  }
  /* image list
   + "SW.VSVR.OS.LNX64.CNTOS.0703.B050"          = "centos-7.3-64"
   + "SW.VSVR.OS.LNX64.CNTOS.0708.B050"          = "CentOS 7.8 (64-bit)"
   + "SW.VSVR.OS.LNX64.UBNTU.SVR1604.B050"         = "ubuntu-16.04-64-server"
   + "SW.VSVR.OS.LNX64.UBNTU.SVR1804.B050"         = "ubuntu-18.04"
   + "SW.VSVR.OS.LNX64.UBNTU.SVR2004.B050"         = "ubuntu-20.04"
   + "SW.VSVR.OS.WND64.WND.SVR2016EN.B100"         = "Windows Server 2016 (64-bit) English Edition"
   + "SW.VSVR.OS.WND64.WND.SVR2019EN.B100"         = "Windows Server 2019 (64-bit) English Edition"
  */
  /* Attributes Reference
    data.ncloud_server_image.server_image.id
  */
}
data "ncloud_server_product" "product" {
  server_image_product_code = data.ncloud_server_image.server_image.id

  filter {
    name   = "product_code"
    values = ["SSD"]
    regex = true
  }
  filter {
    name   = "cpu_count"
    values = ["2"]
  }
  filter {
    name   = "memory_size"
    values = ["4GB"]
  }
  filter {
    name   = "product_type"
    values = ["HICPU"]
    /* Server Spec Type
    STAND
    HICPU
    HIMEM
    */
  }
  /* Attributes Reference
    data.ncloud_server_product.product.id
  */
}

# Export Root Password
data "ncloud_root_password" "default" {
  server_instance_no = ncloud_server.bastion_server.instance_no # ${ncloud_server.vm.id}
  private_key = ncloud_login_key.bastion_server_key.private_key # ${ncloud_login_key.key.private_key}
  /*Attributes Reference
    ncloud_root_password.default.root_password
  */
}
resource "local_file" "bastion_svr_root_pw" {
  filename = "${ncloud_server.bastion_server.name}-root_password.txt"
  content = "${ncloud_server.bastion_server.name} => ${data.ncloud_root_password.default.root_password}"
}
# Public IP
resource "ncloud_public_ip" "public_ip" {
  server_instance_no = ncloud_server.bastion_server.id
  description        = "for ${ncloud_server.bastion_server.name} public ip"
}

 

위의 코드에서 사용한 변수에 사용자 입력 값을 지정하기 위해 variables.tf를 작성합니다.

variable "region" {
  default = "KR"
}
variable "zones" {
  default =  "KR-2"
}
variable name_terra {
  default = "tf-kbo"
}
variable "login_key_name" {
  default = "kbo-login-key"
}
variable "vpc_no" {
  description = "vpc number"
  #type = number
}
variable "sunbet_public_id" {
  description = "public subnet id"
  #type = number
}
# data filter를 사용하지 않을 경우 이미지 및 스펙 코드 명시
# variable "server_image_product_code" {
#   default = "SW.VSVR.OS.LNX64.CNTOS.0708.B050"
# }
# # HDD : CPU 2 ,Memory 4GB , Disk 50GB
# variable "server_product_code" {
#   default = "SVR.VSVR.HICPU.C002.M004.NET.HDD.B050.G002"
# }

 

모든 코드를 잘 작성했다면 폴더와 파일은 다음과 같이 구성되어 있습니다.

 

이후 terraform init -> terraform plan -> terraform apply를 통해 작성한 tf 파일로 ncp 인프라 리소스를 생성할 수 있습니다.

*2023.06.13 추가 

The terraform-provider-ncloud_v2.2.9 plugin crashed! 이슈 방지를 위해

terraform init -upgrade 명령어로 프로바이더를 업그레이드 합니다.

Step 1. Terraform init

 

Step 2. Terraform plan

 

정상적으로 잘 실행되었다면 아래와 같이 서버가 생성된 것을 확인할 수 있습니다.

 

4. 테라폼을 이용한 NCP 인프라 서비스 삭제

생성에 비해 삭제는 비교적 간편한 편입니다.

단, 주의사항으로 테라폼 코드를 통해 인프라 서비스 배포 후 콘솔상에서 변경을 진행하게 되면 정합성이 깨지므로 테라폼을 통한 인프라 제어에 문제가 생길 수 있습니다.

그러므로 콘솔을 통해 변경한 내역을 테라폼 코드에 업데이트 하거나 테라폼 코드로만 인프라를 제어하는 것이 좋습니다.

 

테라폼 코드를 통해 배포한 인프라 서비스를 삭제하기 위해 코드를 실행한 폴더에서 terraform destroy 명령어를 입력합니다.

terraform destroy
yes

 

삭제는 인프라간 의존성에 맞추어 마지막 리소스부터 삭제되며 이상이 없다면 정상적으로 삭제된 것을 확인할 수 있습니다.

 

 

[여담]

요즘들어 CSP 상관 없이 IaC를 사용하려는 추세가 부쩍 늘어난 것 같습니다.

개인적으로 고객사와 커뮤니케이션을 많이하는 엔지니어 입장에서는 고객 담당자 역시 테라폼을 어느정도 이해하고 사용할 줄 알아야 문제가 발생하지 않을 듯 합니다.(사실 구축/운영 하시는분들만 하면 되기는 합니다.)

 

모든 인프라 리소스를 코드를 관리한다는 것은 엄청난 장점이지만 또 엄청난 단점인 것 같기도 합니다.

it 기술이 점점 더 발전하는 만큼 트렌드를 열심히 따라가야될 것 같습니다.