본문 바로가기
NCloud/Certification

NCUC 제 5회 Online Meet-Up 연사 후기

by okms1017 2024. 5. 11.
728x90

✍ Posted by Immersive Builder  Seong

 

 

안녕하세요. NCUC(NAVER Cloud User Community)의 부운영자로 활동하고 있는 Seong 입니다. 

* NCUC는 네이버 클라우드 플랫폼 사용자들이 서로 소통하는 자유로운 분위기의 커뮤니티입니다. 

이번에 네이버 클라우드 관련한 포스팅으로 찾아온 것은 정말 오랜만인데요.
NCUC 온라인 밋업에 연사로 참여하여 진행한 발표 내용과 자료를 공유하고자 후기를 작성합니다. 

NCUC에서는 분기마다 온라인 밋업을 개최하고 있는데요,
다양한 네이버 클라우드의 노하우와 사례를 공유하고 있습니다. 

작년에 처음 시작한 NCUC 정기 온라인 밋업은 1~4회를 거쳐 이번이 5회차입니다. 
24년도 1분기이자 제 5회 온라인 밋업은 지난 3월 19일에 열렸습니다. 

 


주최자 : ManVSCloud님

 

[ NCUC 제 5회 Online Meet-Up ]

 

  1. OpenTofu/Terraform Best Practices for Naver Cloud (wojciech barczyński님)
  2. 네이버 클라우드 테라폼 활용기 (v) 
  3. 네이버 클라우드 API 활용하기 (전혁님)
  4. NCAI 자격증 소개 및 준비 (정인창님)

 

제 5회 온라인 밋업은 총 4개의 세부 프로그램으로 구성되어 진행되었고, 

저는 두 번째 세션인 네이버 클라우드 테라폼 활용기를 주제로 발표를 진행하였습니다. 

 


세션은 테라폼의 기본개념네이버 클라우드에서의 활용경험을 위주로 소개하였습니다. 

 

1.1 Terraform 이란 ?

 

  • Terraform은 HashiCorp에서 공개한 인프라스트럭처를 코드로 프로비저닝하고 관리할 수 있는 오픈소스 도구입니다. 
  • HCL(HashiCorp Configuration Language) 기반 Infrastructure as Code(IaC) 
  • 사용자 인터페이스나 명령어를 이용하여 수동 조작하지 않고 코드로 대상 리소스를 관리합니다. 
  • Terraform은 Provider를 통해 대상 리소스와 상호작용합니다. 

Terraform Provider

* Provider 란? 

- Terraform에서 CSP 서비스를 API로 호출할 수 있도록 하는 인터페이스입니다. 

- 테라폼 공식 레지스트리에서 AWS, Azure, GCP, NCloud 등 수천 개 이상의 Provider를 제공하고 있습니다. 

 

▶ 테라폼 공식 레지스트리

 

▶ 네이버 클라우드 프로바이더

▶ 테라폼 기본 사용법 

 

[AEWS2] 8-1. What is Terraform ?

✍ Posted by Immersive Builder  Seong  1. Terraform 기본 개념  Terraform 이란?HashiCorp에서 공개한 인프라스트럭처를 코드로 프로비저닝하고 관리할 수 있는 오픈소스 도구입니다.  HCL(HashiCorp Configuration

okms1017.tistory.com

 

1.2 Terraform 장점 

 

  • 선언적 구성 : 사용자가 원하는 최종상태를 구성파일(Configuration File)에 선언하면,
    테라폼은 해당상태로 리소스를 변경하고 유지하도록 지시합니다. 
  • 모듈화 : 연관성 있는 리소스를 집합하여 모듈로 구성하면,
    기존 모듈을 활용하여 코드를 재사용하고 리소스를 배포할 수 있습니다. 
  • 인프라 자동화 : Git, CI/CD 도구와 통합하여 인프라의 변경과 업데이트를 자동화할 수 있습니다. 
    또한, Git을 연동하면 인프라 버전 관리 및 협업이 용이합니다. 
  • 멀티클라우드 지원 : AWS/Azure/GCP/NCP 등 다양한 클라우드 프로바이더를 제공하고 있습니다. 
    특정 클라우드에 종속적이지 않고(cloud-agnostic), 동일한 워크로드를 설계할 수 있습니다. 

 

1.3 Terraform 동작 원리

 

  1. 사용자가 작성한 구성 파일(*.tf)과 현재 상태 파일(*.tfstate)을 비교합니다. 
  2. 변경사항이 있을 시, 변경분에 대한 실행계획(Plan)을 생성합니다. 
  3. 이후 해당 실행계획(Plan)을 실행(Apply)합니다. 
  4. 대상 리소스에 생성/대체/변경/삭제분을 반영합니다. 
  5. 반영이 완료되면 상태 파일을 최신 상태로 업데이트합니다. 

 


2.1 Basic Architecture (NaverCloud)

 

테라폼으로 배포할 네이버 클라우드의 기본 아키텍처 구성과 명세는 아래와 같습니다. 

 

  • VPC (1) / ZONE (2) / Subnet (7) 
  • ALB (1) / NLB (1) / Server (4) 
  • Cloud DB for Redis (Standalone) / Cloud DB for MySQL(Active Master/Standby Master) 

 

2.2 리소스 프로비저닝

 

네이버 클라우드 프로바이더에서 제공하는 템플릿을 커스터마이징하여 리소스를 프로비저닝합니다. 

▶ 네이버 클라우드 템플릿 소스

 

Step1. 테라폼 템플릿 다운로드 

 

$ mkdir -p $GOPATH/src/github.com/NaverCloudPlatform 
$ cd $GOPATH/src/github.com/NaverCloudPlatform
$ git clone git@github.com:NaverCloudPlatform/terraform-provider-ncloud.git
$ cd $GOPATH/src/github.com/NaverCloudPlatform/terraform-provider-ncloud
$ make build

 

Step2. 프로바이더 정의 

 

테라폼 블록 내부에 사용할 프로바이더 소스 경로와 버전 정보를 명시합니다. 

명시한 버전 이하로 코드를 실행할 경우 오류가 발생합니다. 

 

 
      terraform {
        required_providers {
          ncloud = {
            source  = "NaverCloudPlatform/ncloud"
            version = "3.0.0"
          }
        }
        required_version = ">= 0.13"
      }
 

 

  • source : 프로바이더 소스 경로  ex) NaverCloudPlatform/ncloud
  • version : 프로바이더 버전 정보 

 

그리고 서비스 제공자마다 고유의 프로바이더명이 존재합니다. 

네이버 클라우드는 "ncloud"라는 프로바이더명을 사용합니다. 

 

 
      provider "ncloud" {
          support_vpc = true     #VPC(o)|Classic(x)
          site        = "public" #public(민간)|gov(공공)|fin(금융)
          region      = "KR"
          #access_key  = var.access_key
          #secret_key  = var.secret_key
        }
 

 

  • support_vpc : VPC 지원 여부  ex) true (VPC) | false (Classic)
  • site : 대상 플랫폼  ex) puclic (민간) | gov (공공) | fin (금융) 
  • region : 대상 리전  ex) KR 
  • access_key와 secret_key는 자격증명을 위한 것이며 노출되지 않도록 환경변수로 설정할 것을 권장합니다. 

 

Step3. 모듈 작성 (모듈화) 

 

서로 연관성이 있는 리소스를 모아 모듈로 구성할 수 있습니다. 

모듈을 미리 구성해놓으면 서로 다른 환경(DEV|STG|PRD)에서 재사용이 가능합니다. 

 

기본 아키텍처 구성에 따라 VPC, Server, LB, CDB 등 4가지 모듈로 구성해볼 수 있습니다. 

모듈별로 주요 리소스 일부는 아래와 같습니다. 

 

  • VPC 모듈 
    • VPC : ncloud_vpc 
    • Subnet : ncloud_subnet
    • NACL : ncloud_network_acl
    • Route Table : ncloud_route_table 
 
        resource "ncloud_vpc" "vpc" {
          name            = var.vpc.name
          ipv4_cidr_block = var.vpc.ipv4_cidr_block
        }
 
        resource "ncloud_subnet" "subnets" {
          for_each = { for subnet in var.subnets : subnet.name => subnet }

          name           = each.value.name
          vpc_no         = ncloud_vpc.vpc.id
          usage_type     = each.value.usage_type
          subnet_type    = each.value.subnet_type
          zone           = each.value.zone
          subnet         = each.value.subnet
          network_acl_no = each.value.network_acl == "default" ? ncloud_vpc.vpc.default_network_acl_no : ncloud_network_acl.network_acls[each.value.network_acl].id
        }
 
        resource "ncloud_network_acl" "network_acls" {
          for_each = { for network_acl in var.network_acls : network_acl.name => network_acl if network_acl.name != "default" }

          name        = each.value.name
          vpc_no      = ncloud_vpc.vpc.id
          description = each.value.description
        }
 
        resource "ncloud_route_table" "public_route_tables" {
          for_each = { for route_table in var.public_route_tables : route_table.name => route_table }

          name                  = each.value.name
          vpc_no                = ncloud_vpc.vpc.id
          supported_subnet_type = "PUBLIC"
          description           = each.value.description
        }
 
        resource "ncloud_route_table" "private_route_tables" {
          for_each = { for route_table in var.private_route_tables : route_table.name => route_table }

          name                  = each.value.name
          vpc_no                = ncloud_vpc.vpc.id
          supported_subnet_type = "PRIVATE"
          description           = each.value.description
        }
 

 

  • Server 모듈 
    • Server : ncloud_server 
    • Server Image : ncloud_server_image 
    • NIC : ncloud_network_interface
    • Block Storage : ncloud_block_storage 
 
        data "ncloud_server_image" "server_image" {
          filter {
            name   = "product_name"
            values = [var.server_image_name]
          }
        }
        locals {
          product_type = {
            "High CPU"      = "HICPU"
            "CPU Intensive" = "CPU"
            "High Memory"   = "HIMEM"
            "GPU"           = "GPU"
            "Standard"      = "STAND"
            "BareMetal"     = "BM"
          }
        }
 
        resource "ncloud_server" "server" {
          name           = var.name
          description    = var.description
          subnet_no      = coalesce(var.subnet_id, one(data.ncloud_subnet.subnet.*.id))
          login_key_name = var.login_key_name

          # server_image_product_code = data.ncloud_server_image.server_image.id
          server_image_product_code = var.image_product_code
          # server_product_code = data.ncloud_server_product.server_product.product_code
          server_product_code = var.product_code
        }
 
        resource "ncloud_network_interface" "default_nic" {
          name        = var.default_network_interface.name
          description = var.default_network_interface.description
          subnet_no   = coalesce(var.subnet_id, one(data.ncloud_subnet.subnet.*.id))
          private_ip  = lookup(var.default_network_interface, "private_ip", "") != "" ? var.default_network_interface.private_ip : null
          access_control_groups = coalesce(var.default_network_interface.access_control_group_ids, values(data.ncloud_access_control_group.acgs).*.id)
        }
 
        resource "ncloud_block_storage" "additional_block_storages" {
          for_each = { for volume in var.additional_block_storages : volume.name => volume }

          name               = each.value.name
          description        = each.value.description
          disk_detail_type   = each.value.disk_type
          size               = each.value.size
          server_instance_no = ncloud_server.server.id
        }
 
 

 

  • LB 모듈
    • LoadBalancer : ncloud_lb 
    • Listener : ncloud_lb_listener
    • Target Group : ncloud_lb_target_group 
 
        resource "ncloud_lb" "lb" {
          name            = var.name
          description     = var.description
          type            = var.type
          network_type    = var.network_type
          subnet_no_list  = coalesce(var.subnet_no_list, coalesce(var.subnet_ids, values(data.ncloud_subnet.subnets).*.id))
          throughput_type = var.throughput_type
          idle_timeout    = var.idle_timeout
        }
 
        resource "ncloud_lb_listener" "lb_listeners" {
          count = length(var.listeners)

          load_balancer_no = ncloud_lb.lb.id
          protocol         = var.listeners[count.index].protocol
          port             = var.listeners[count.index].port
          target_group_no = (
            try(var.listeners[count.index].target_group_no,
              try(var.listeners[count.index].target_group_id,
                data.ncloud_lb_target_group.target_groups[var.listeners[count.index].target_group_name].id
          )))

          tls_min_version_type = (var.listeners[count.index].protocol == "TLS") || (var.listeners[count.index].protocol == "HTTPS") ? var.listeners[count.index].tls_min_version_type : null
          ssl_certificate_no   = (var.listeners[count.index].protocol == "TLS") || (var.listeners[count.index].protocol == "HTTPS") ? var.listeners[count.index].ssl_certificate_no : null
          use_http2            = (var.listeners[count.index].protocol == "HTTPS") ? var.listeners[count.index].use_http2 : null
        }
 
        resource "ncloud_lb_target_group" "target_group" {
          name               = var.name
          description        = var.description
          vpc_no             = coalesce(var.vpc_no, one(data.ncloud_vpc.vpc.*.id))
          protocol           = var.protocol
          port               = var.port
          algorithm_type     = var.algorithm_type
          use_sticky_session = var.use_sticky_session
          use_proxy_protocol = var.use_proxy_protocol
          target_type        = var.target_type

          health_check {
            protocol       = var.health_check.protocol
            port           = var.health_check.port
            cycle          = var.health_check.cycle
            up_threshold   = var.health_check.up_threshold
            down_threshold = var.health_check.down_threshold

            http_method = (var.health_check.protocol == "HTTP" || var.health_check.protocol == "HTTPS") ? var.health_check.http_method : null
            url_path    = (var.health_check.protocol == "HTTP" || var.health_check.protocol == "HTTPS") ? var.health_check.url_path : null
          }
        }
 

 

  • CDB 모듈  => 최근(24.01) 프로바이더에서 지원하기 시작 !  
    • Cloud DB for MySQL : ncloud_mysql 
    • Cloud DB for Redis : ncloud_redis 
  
        resource "ncloud_mysql" "cdb_for_mysql" {
           subnet_no = coalesce(var.subnet_id, one(data.ncloud_subnet.subnet.*.id))
           service_name = var.mysql_service_name
           server_name_prefix = var.mysql_name_prefix
           user_name = var.mysql_user_name
           user_password = var.mysql_password
           database_name = var.mysql_database_name
           host_ip = var.client_ip
        }
 
        resource "ncloud_redis" "cdb_for_redis" {
           service_name = var.redis_service_name
           server_name_prefix = var.redis_name_prefix
           vpc_no = ncloud_vpc.vpc.id
           subnet_no = ncloud_subnet.pri-subnet.id
           config_group_no = ncloud_redis_config_group.example.id
           mode = "SIMPLE"
        }
 

 

Step4. 모듈 호출 

 

모듈은 루트 모듈자식 모듈로 구분됩니다. 

 

  • 루트 모듈 : 테라폼을 실행하고 리소스를 프로비저닝합니다. 
  • 자식 모듈 : 리소스를 정의하고 루트 모듈에 의해 호출됩니다. 

루트 모듈에서 각 모듈(VPC, Server, LB, CDB)을 호출하는 코드를 작성합니다. 

루트 모듈에서 자식 모듈을 호출하는 작성 예시는 아래와 같습니다. (나머지는 스스로 작성해봅시다!)

 

  • VPC 모듈 호출 예시 
 
      module "vpcs" {
          source = "../modules/terraform-ncloud-vpc"
          # VPC
          vpc = {
            name            = "ncuc-vpc"
            ipv4_cidr_block = "192.168.0.0/20"
          }
 

 

  • CDB 모듈 호출 예시 
 
      module "cdbs" {
          source = "../modules/terraform-ncloud-cdb"

          # Cloud DB for MySQL
          vpc_name     = "ncuc-vpc"
          subnet_id    = module.vpcs.subnets[local.resources.db_sbn1.name].id
          service_name = "ncuc"
          name_prefix  = "ncuc5"
          user_name    = "seong"
          #user_name = secrets
          #password = secrets
          database_name = "ncuc-db"
          client_ip     = "192.168.11.1"

          depends_on = [
            module.vpcs
          ]
        }
 

 

소스 경로에 자식 모듈이 위치하는 로컬 디렉터리 경로를 선언합니다. 

그리고 자식 모듈에 전달할 입력 변수의 값들을 함께 선언합니다. 

 

그러면 해당 경로의 모듈을 호출하여 입력 변수의 값들을 전달한 후 실행하게 됩니다. 

 

참고로 Cloud DB for MySQL을 생성하기 위해서는 VPC와 Subnet이 사전에 구성되어야 하는데,

리소스의 우선 순위를 강제할 수 있는 메타인수가 바로 'depends_on' 입니다. 

 

Step5. 리소스 프로비저닝 

 

모든 코드를 작성 완료하였으면 하기 명령어를 순서대로 실행하여 리소스를 프로비저닝합니다. 

전체 리소스가 배포되기까지 소요되는 시간은 약 10분 정도입니다. 

 

  1. terraform init (-upgrade) : 작업 디렉토리를 초기화하고, 프로바이더와 백엔드, 모듈 등 지정된 버전에 맞추어 구성합니다. 
  2. terraform plan : 구성 파일에 대한 실행 계획을 생성합니다. 
  3. terraform apply (-auto-approve) : 실행 계획을 토대로 리소스를 프로비저닝합니다. 
  4. terraform destroy : 생성한 리소스를 삭제합니다. 

 

Step6. 배포 확인 

 

리소스가 정상적으로 배포되었는지 콘솔에 접속하여 확인합니다. 

 

  • VPC 

  • Subnet

  • NACL

  • Route Table

  • Server

  • LoadBalancer

  • Listener

  • Target Group

 

  • Cloud DB for MySQL

 

테라폼으로 배포한 리소스들은 Cloud Activity Tracer 로그에서 요청구분이 API로 표시됩니다. 

프로바이더 플러그인이 결국 API로 통신하기 때문입니다. 

 

 

2.3 기존 리소스 가져오기 (Import)  

테라폼으로 관리하고 있지 않은 기존 리소스를 Import 작업을 통해 상태 파일과 구성 파일로 가져올 수 있습니다. 

 

Q. 인프라 운영자가 실수로 콘솔을 이용해서 NAT-GW를 신규 생성하였습니다. 

코드와 리소스 간 불일치 상태를 해결하기 위해 어떤 작업을 수행하여야 할까요?

 

A. terraform import 명령어를 실행하여 리소스 변경사항을 코드에 반영할 수 있습니다. 

 

1. 구성 파일(*.tf)에 리소스 블록 선언하기 

2. 리소스 상태 가져오기 

terraform import [리소스유형].[리소스명].[리소스ID] 

terraform import ncloud_subnet.test-sbn 137865

terraform import ncloud_natgateway.test-natgw 23172275

3. 상태 파일(*.tfstate) 기반으로 구성 파일(*.tf) 작성하기 

4. 플랜 동작을 확인하면서 구성 파일과 리소스 일치시키기 

=> "Your infrastructure matches the configuration" 문구가 뜨도록 반복한다

5. Apply 수행하여 업데이트 완료 

 

실제 NAT-GW 전용 서브넷과 NAT-GW 를 생성한 후, Import 작업으로 리소스를 가져오는 영상입니다. 

 

 

2.4 Terraform 은 완벽할까 ? 

 

  • 미지원 서비스

네이버 클라우드에서 제공하는 모든 서비스(리소스)를 프로바이더에서 지원하지는 않습니다. 

 

예를 들어 Security Monitoring Service의 경우 관리 주체가 네이버 클라우드이기도 하고,

신청 방식 또한 온라인 문의하기를 통해 진행되는 수동 프로세스이므로 테라폼으로 관리할 수 없습니다. 

 

이처럼 테라폼 도입을 고려하고 있다면 테라폼으로 관리할 수 있는 영역과 관리할 수 없는 영역을 명확히 구분지어야 합니다. 

 

그리고 현재 기준(24.05.11)으로 서버 타입으로 G1/G2만 지원하고 있습니다. 

따라서 하이퍼바이저 타입으로 KVM 서버는 생성할 수 없습니다. (향후 지원 예정으로 정확한 일정은 미정) 

 

  • 인수인계 

테라폼으로 구축한 인프라 서비스를 구축 단계에서 운영 단계로 전환하는 과정에서 담당자 간에 정확한 인수인계가 중요합니다. 인수받는 운영 담당자 또한 테라폼에 대한 이해도와 숙련도가 있어야 합니다. 

 

  • 코드화의 어려움 

기존 네이버 클라우드 환경에서 실 운영 중인 서비스를 대상으로 테라폼을 도입하는 작업은 고려할 요소가 많은 작업입니다. 물론, Import를 사용하여 기존 리소스를 상태 파일과 구성 파일로 가져올 수 있기는 합니다. 그러나 이러한 작업은 리소스를 하나하나 수작업해야 하는 번거로움이 따르며, 코드와 리소스 간에 반드시 일치한다는 보장을 할 수 없습니다. AWS의 경우 리소스를 코드화하는 오픈소스(Terraformer)가 존재하지만, 해당 오픈소스가 현재 네이버 클라우드를 지원하지는 않는 것으로 알고 있습니다. 

 


3. 발표 자료 

 

네이버 클라우드 테라폼 활용기(공유용).pdf
4.85MB

 


이상으로 네이버 클라우드 테라폼 활용기를 마치도록 하겠습니다. 

테라폼을 처음 입문하는 분들 또는 네이버 클라우드 환경에 테라폼 도입을 고려하는 분들에게 많은 도움이 되셨으면 좋겠습니다. 

 

이외에도 더 많은 네이버 클라우드의 노하우와 활용 사례가 궁금하시다면,

하기 링크를 방문해주세요 ! 

 

 

[ NCUC 참여 링크 ]

 

1) NCUC 오픈채팅방 : https://open.kakao.com/o/gRNIEFOc

2) NCUC 페이스북 : https://www.facebook.com/groups/ncpusergroup

3) NCUC 이벤트 : https://www.meetup.com/ko-KR/navercloud-user-community/

4) NaverCloud 공식 블로그 : https://blog.naver.com/n_cloudplatform

728x90