'분류 전체보기'에 해당되는 글 13건

TFC

  • Terraform Cloud(TFC)
  • workflow 구성 환경 제공
  • VCS 연동 기능, 변수 구성
  • RBAC, 원격 실행환경

실습

저장소 포크 : https://github.com/terraform101/terraform-aws-tfc-workflow

 

GitHub - terraform101/terraform-aws-tfc-workflow: [Chapter 8] CICD - TFC Workflow

[Chapter 8] CICD - TFC Workflow. Contribute to terraform101/terraform-aws-tfc-workflow development by creating an account on GitHub.

github.com

저장소 포크 후 cloud 백엔드의 org값 변경

fork한 저장소 clone 후 org 바꿈

terraform init

token생성 후 terraform login 후 terraform init

terraform plan

l

원격지의 tfc에 설정될 value가 없음

add variable

  • Prefix : Type(Terraform variable), Key(prefix), value(사용자 닉네임)

  • Access Key ID : Type(Environment variable), Key(AWS_ACCESS_KEY_ID), value(자신의 값)
  • Secret Access Key : Type(Environment variable), Key(AWS_SECRET_ACCESS_KEY), value(자신의 값), Sensitive 선택

전부 입력

terraform plan : 에러 없네

ahn@GeunChangui-noteubug terraform-aws-tfc-workflow % terraform plan
Running plan in Terraform Cloud. Output will stream here. Pressing Ctrl-C
will stop streaming the logs, but will not stop the plan running remotely.

Preparing the remote plan...

To view this run in a browser, visit:
https://app.terraform.io/app/ahn/terraform-aws-tfc-workflow/runs/run-GD9j8pPk2hreWsiT

Waiting for the plan to start...

Terraform v1.5.6
on linux_amd64
Initializing plugins and modules...
data.aws_ami.ubuntu: Refreshing...
data.aws_ami.ubuntu: Refresh complete after 1s [id=ami-0a7f78325e47a7c27]

Terraform used the selected providers to generate the following execution plan. Resource actions
are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_eip.hashicat will be created
  + resource "aws_eip" "hashicat" {
      + allocation_id        = (known after apply)
      + association_id       = (known after apply)
      + carrier_ip           = (known after apply)
      + customer_owned_ip    = (known after apply)
      + domain               = (known after apply)
      + id                   = (known after apply)
      + instance             = (known after apply)
      + network_border_group = (known after apply)
      + network_interface    = (known after apply)
      + private_dns          = (known after apply)
      + private_ip           = (known after apply)
      + public_dns           = (known after apply)
      + public_ip            = (known after apply)
      + public_ipv4_pool     = (known after apply)
      + tags_all             = {
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
      + vpc                  = true
    }

  # aws_eip_association.hashicat will be created
  + resource "aws_eip_association" "hashicat" {
      + allocation_id        = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + network_interface_id = (known after apply)
      + private_ip_address   = (known after apply)
      + public_ip            = (known after apply)
    }

  # aws_instance.hashicat will be created
  + resource "aws_instance" "hashicat" {
      + ami                                  = "ami-0a7f78325e47a7c27"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = "감동맨-ssh-key.pem"
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "감동맨-hashicat-instance"
        }
      + tags_all                             = {
          + "Name"    = "감동맨-hashicat-instance"
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
      + tenancy                              = (known after apply)
      + user_data                            = (known after apply)
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

  # aws_internet_gateway.hashicat will be created
  + resource "aws_internet_gateway" "hashicat" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + tags     = {
          + "Name" = "감동맨-internet-gateway"
        }
      + tags_all = {
          + "Name"    = "감동맨-internet-gateway"
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
      + vpc_id   = (known after apply)
    }

  # aws_key_pair.hashicat will be created
  + resource "aws_key_pair" "hashicat" {
      + arn             = (known after apply)
      + fingerprint     = (known after apply)
      + id              = (known after apply)
      + key_name        = "감동맨-ssh-key.pem"
      + key_name_prefix = (known after apply)
      + key_pair_id     = (known after apply)
      + key_type        = (known after apply)
      + public_key      = (known after apply)
      + tags_all        = {
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
    }

  # aws_route_table.hashicat will be created
  + resource "aws_route_table" "hashicat" {
      + arn              = (known after apply)
      + id               = (known after apply)
      + owner_id         = (known after apply)
      + propagating_vgws = (known after apply)
      + route            = [
          + {
              + carrier_gateway_id         = ""
              + cidr_block                 = "0.0.0.0/0"
              + core_network_arn           = ""
              + destination_prefix_list_id = ""
              + egress_only_gateway_id     = ""
              + gateway_id                 = (known after apply)
              + instance_id                = ""
              + ipv6_cidr_block            = ""
              + local_gateway_id           = ""
              + nat_gateway_id             = ""
              + network_interface_id       = ""
              + transit_gateway_id         = ""
              + vpc_endpoint_id            = ""
              + vpc_peering_connection_id  = ""
            },
        ]
      + tags_all         = {
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
      + vpc_id           = (known after apply)
    }

  # aws_route_table_association.hashicat will be created
  + resource "aws_route_table_association" "hashicat" {
      + id             = (known after apply)
      + route_table_id = (known after apply)
      + subnet_id      = (known after apply)
    }

  # aws_security_group.hashicat will be created
  + resource "aws_security_group" "hashicat" {
      + arn                    = (known after apply)
      + description            = "Managed by Terraform"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 443
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 443
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
      + name                   = "감동맨-security-group"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags                   = {
          + "Name" = "감동맨-security-group"
        }
      + tags_all               = {
          + "Name"    = "감동맨-security-group"
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
        }
      + vpc_id                 = (known after apply)
    }

  # aws_subnet.hashicat will be created
  + resource "aws_subnet" "hashicat" {
      + arn                                            = (known after apply)
      + assign_ipv6_address_on_creation                = false
      + availability_zone                              = (known after apply)
      + availability_zone_id                           = (known after apply)
      + cidr_block                                     = "10.0.10.0/24"
      + enable_dns64                                   = false
      + enable_resource_name_dns_a_record_on_launch    = false
      + enable_resource_name_dns_aaaa_record_on_launch = false
      + id                                             = (known after apply)
      + ipv6_cidr_block_association_id                 = (known after apply)
      + ipv6_native                                    = false
      + map_public_ip_on_launch                        = false
      + owner_id                                       = (known after apply)
      + private_dns_hostname_type_on_launch            = (known after apply)
      + tags                                           = {
          + "name" = "감동맨-subnet"
        }
      + tags_all                                       = {
          + "Owner"   = "jerry & tom"
          + "Project" = "Coffee-Mug-Cake"
          + "name"    = "감동맨-subnet"
        }
      + vpc_id                                         = (known after apply)
    }

  # aws_vpc.hashicat will be created
  + resource "aws_vpc" "hashicat" {
      + arn                                  = (known after apply)
      + cidr_block                           = "10.0.0.0/16"
      + default_network_acl_id               = (known after apply)
      + default_route_table_id               = (known after apply)
      + default_security_group_id            = (known after apply)
      + dhcp_options_id                      = (known after apply)
      + enable_classiclink                   = (known after apply)
      + enable_classiclink_dns_support       = (known after apply)
      + enable_dns_hostnames                 = true
      + enable_dns_support                   = true
      + enable_network_address_usage_metrics = (known after apply)
      + id                                   = (known after apply)
      + instance_tenancy                     = "default"
      + ipv6_association_id                  = (known after apply)
      + ipv6_cidr_block                      = (known after apply)
      + ipv6_cidr_block_network_border_group = (known after apply)
      + main_route_table_id                  = (known after apply)
      + owner_id                             = (known after apply)
      + tags                                 = {
          + "environment" = "Production"
          + "name"        = "감동맨-vpc-ap-northeast-2"
        }
      + tags_all                             = {
          + "Owner"       = "jerry & tom"
          + "Project"     = "Coffee-Mug-Cake"
          + "environment" = "Production"
          + "name"        = "감동맨-vpc-ap-northeast-2"
        }
    }

  # null_resource.configure-cat-app will be created
  + resource "null_resource" "configure-cat-app" {
      + id = (known after apply)
    }

  # tls_private_key.hashicat will be created
  + resource "tls_private_key" "hashicat" {
      + algorithm                     = "RSA"
      + ecdsa_curve                   = "P224"
      + id                            = (known after apply)
      + private_key_openssh           = (sensitive value)
      + private_key_pem               = (sensitive value)
      + private_key_pem_pkcs8         = (sensitive value)
      + public_key_fingerprint_md5    = (known after apply)
      + public_key_fingerprint_sha256 = (known after apply)
      + public_key_openssh            = (known after apply)
      + public_key_pem                = (known after apply)
      + rsa_bits                      = 2048
    }

Plan: 12 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + catapp_ip  = (known after apply)
  + catapp_url = (known after apply)
ahn@GeunChangui-noteubug terraform-aws-tfc-workflow %

 

실제 terraform apply (하고 yes 입력은 하지 않고) overview 확인

  • 실제로 저 run 부분이 ci/cd도구처럼 실행시 planning시 돌다가 현재 planned상태
  •  

See detail을 보면 전체 스트림으로 출력된 계획을 확인 가능

 

내가 직접 확인하고 커멘트를 달고 컨펌하면

CLI쪽에서 승인 처리가 됨을 확인할 수 있다

CLI Yes를 하지 않아도 UI에서 컨펌했음이 나타난다.

리소스 삭제  

terraform destory -auto-approve

 

리소스가 안전하게 삭제 됨을 확인

블로그 이미지

감동맨

rkaehdaos의 블로그

,
기본기 부족
프로그래밍 공부가 더 필요하다냥 ㅠ

 

8. 테라폼

8.1 규모에 따른 워크플로

  • 테라폼 워크플로 : Write → Plan → Apply
  • 워크스페이스 별로 접근 권한을 관리하고 중앙에서 관리되는 실행 환경을 설계하여 규모에 맞는 워크플로 설계가 필요
  • 참고: The Core Terraform Workflow
딥엘로 번역해서 봤는데.. 
밑의 내용이 나오긴 하는데.. 나는
테라폼 클라우드부터 계속 햇갈림 ㅠ
 

The Core Terraform Workflow - Guides | Terraform | HashiCorp Developer

An overview of how individuals, teams, and organizations can use Terraform.

developer.hashicorp.com

8.1.1 개인의 워크 플로우

개인의 워크플로우

  • Write : 프로비저닝  목적에 따라 코드 작성
    • 개인 작업이어도 반복적 사용성 고려.
    • 인수에 할당되는 값을 입력 변수화
    •  반복적 구조가 발생하면?
      • 리소스 단위별로 반복문을 사용할지
      • 다수의 리소스를 모듈화할지 결정
  • Plan : apply 전 plan 통한 리뷰
    • 테라폼의 Plan뿐 아니라, terraform fmt를 통해 코드 형태를 포멧팅하고 변경되는 리소스를 리뷰
    • 또한 테라폼과 함께 동작하는 tfsec이나 terrascan 같은 보안 취약성 점검 툴 등을 활용하는 것도 좋은 방안
  • Apply : 인프라 프로비저닝
    • plan에선  정상이지만 실제 프로비저닝하는 단계에서 인수 값, 생성 순서, 종속성에 따라 오류가 발생 가능
    • 성공적인 완료를 위해 Write > Plan > Apply 단계를 반복하고 성공하는 경우 코드 관리를 위해 VCS에 코드를 병합.

8.1.2 다중 작업자 워크플로

팀의 워크플로

  • Write
    • 여러 작업자의 테라폼 코드가 충돌하지 않도록 형상관리 도구에 익숙해져야 한다.
    • 작업자는 작업 전에 미리 원격 저장소의 코드를 받고(pull, fetch)
      git에서는 브랜치를 활용해 개별적으로 작업한다.
    • 개인의 워크플로에서 고려한 변수화와 더불어 패스워드인증서 같은 민감 데이터가 포함되지 않도록 코드를 설계한다.
      또한 개인 작업 환경에서만 사용되는 변수는 공유하지 않는다.
    • 깃을 사용한다면 작업자 개인의 변수terraform.tfvars 에 선언하고 .gitignore에 추가해 개별적으로 테스트할 수 있는 환경을 구성할 수 있다(이거 레알 짱인듯). 이 단계에서 개별 작업자는 더 작은 단위의 개인별 워크플로(Write > Plan > Apply)를 반복해야 한다.
    • 개별 작업 환경과 별개로 병합되는 코드가 실제 운영 중인 인프라에 즉시 반영되면 실행 후 발생할 오류 예측이 어려워 부담이 될 수 있다. 이를 보완하기 위해 프로비저닝 대상의 환경을 검증과 운영, 또는 그 이상의 환경으로 구성 가능하도록 구조화한다.이때 사용하는 방식은 디렉터리 기반 격리깃 기반의 브랜치 격리다.
성공적인 깃 브렌치 모델 - https://nvie.com/posts/a-successful-git-branching-model
  • Plan
    • 둘 이상의 작업자는 프로비저닝 이전에 팀원 간 리뷰를 거쳐 변경된 내역을 확인하고 공통 저장소에 병합해야 한다.
    • 리뷰 단계에서는 추가, 삭제, 수정된 내역을 관련 작업자가 검증, 질의, 배움의 단계를 거쳐 복기함으로써 코드 상태를 개선 유지하고 작업자 간에 의도를 공유한다.
    • 코드 자체 외에도 테라폼의 Plan 결과를 풀 리퀘스트 단계에 같이 제공하면 영향을 받는 리소스와 서비스 중단에 대한 예측이 더 쉬워진다.
    • CI 툴과 연계하거나 Terraform Cloud/Enterprise의 VCS 통합 기능으로 자동화할 수 있다.
  • Apply
    • 코드가 최종 병합되면 인프라 변경이 수행됨을 알리고 변경되는 대상 환경의 중요도에 따라 승인이 필요할 수 있다.
    • 또한 변경하는 코드가 특정 기능, 버그 픽스, 최종 릴리즈를 위한 병합인가에 따라 이 단계에 추가로 코드 병합이 발생할 수 있다.
    • 관리하는 단위를 나누는 기준은 조직 R&R, 서비스, 인프라 종류 등으로 구분된다.

8.1.3 다수 팀의 워크플로

  • R&R이 분리된 다수 팀 또는 조직의 경우 테라폼의 프로비저닝 대상은 하나이지만 관리하는 리소스가 분리된다.
  • 단일 팀의 워크플로가 유지되고 그 결과에 대해 공유해야 하는 핵심 워크플로가 필요!

다수 팀의 워크플로

  • Write
    • 대상 리소스가 하나의 모듈에서 관리되지 않고 R&R에 의해 워크스페이스가 분리된다.
    • 서로 다른 워크스페이스에서 구성된 리소스 데이터를 권한이 다른 팀에게 공유하기 위해, 저장된 State 접근 권한을 제공하고 output을 통해 공유 대상 데이터를 노출한다.
    • 테라폼 코드 작성 시 다른 워크스페이스에서의 변경 사항을 데이터 소스로 받아 오는 terraform_remote_state 또는 별도 KV-store를 활용하는 코드 구성이 요구된다.
    • 또한 관리 주체가 다른 곳에서 생긴 변경 사항의 영향을 최소화하도록 리모트 데이터 소스의 기본값을 정의하거나 코드적인 보상 로직을 구현하는 작업이 필요하다.
  • Plan
    • 코드 기반으로 진행되는 리뷰는 반영되는 다른 팀의 인프라를 VCS상의 코드 리뷰만으로도 공유받고 영향도를 검토할 수 있다.
    • 병합을 승인하는 단계에 영향을 받는 다른 팀의 작업자도 참여해야 한다.
  • Apply
    • 프로비저닝 실행과 결과에 대한 안내가 관련 팀에 알려져야 하므로 파이프라인 구조에서 자동화하는 것을 추천한다.
    • 실행 후의 영향도가 여러 팀이 관리하는 리소스에 전파될 수 있으므로 코드 롤백 훈련이 필요하다.
    • 생성된 결과에 다른 워크스페이스에서 참조되는 output 값의 업데이트된 내용을 다른 팀이 확인하는 권한 관리가 필요하다

8.2 격리 구조

  • 테라폼 수준의 격리 목표 : State를 분리
    • 테라폼은 파일이나 하위 모듈로 구분하더라도
      동작 기준은 실행하는 루트 모듈에서 코드를 통합하고 하나의 State로 관리한다.
    • 애플리케이션 구조가 모놀리식(+아키텍처)에서 MSA로 변화하는 과정은
      테라폼의 IaC 특성과도 결부된다.

모놀리식과 MSA 비교

  • 테라폼 또한 사용하는 리소스가 적고 구조가 단순하면 모놀리식 방식으로 구성하는 것이 인프라 프로비저닝 구축 속도는 빠를 수 있다.
  • 하지만 유지 보수, 인수인계, 운영의 관점에서는 프로비저닝 단위별로 분류하는,
    마치 MSA와도 같은 분산된 설계가 매몰 비용과 기술 부채를 줄이는 데 효과적이다.
  • 규모가 큰 워크플로를 만들기 위해서는 간단하고 조합 가능한 부분들이 모여 집합을 이루어야 한다.
  • 이러한 집합에서 발생하는 정보는 다른 집합과 교환할 수 있지만, 각 집합은 독립적으로 실행되며 다른 집합에 영향을 받지 않는 격리된 구조가 필요하다.
  • 초기 테라폼 적용 단계에서 단일 또는 소수의 작업자는 단일 대상에 대해 IaC를 적용하고 하나의 루트 모듈에 많은 기능을 포함시킬 가능성이 높다.

8.2.1 루트 모듈 격리(파일/디렉터리)

모놀리식 구조의 파일 디렉토리 격리 구조

  • 단일 작업자가 테라폼으로 프로비저닝을 하는 많은 경우에 관리 편의성 및 배포 단순화를 위해 하나의 루트 디렉터리에 파일로 리소스들을 구분하거나, 디렉터리를 생성하고 하위에 구성 파일 묶음을 위치시켜 루트 모듈에서 하위 디렉터리를 모듈로 읽는 구조를 사용한다.
  • 작업자가 관리하는 영역 또는 프로비저닝되는 리소스 묶음의 독립적인 실행을 위해 단일 루트 모듈 내의 리소스를 다수의 루트 모듈로 분리하고 각 모듈의 State를 참조하도록 격리한다.
  • 관리적인 측면으로는 작업자들의 관리 영역을 분리시키고 깃 기준의 리모트 저장소도 접근 권한을 관리할 수 있다.
  • 협업과 관련해 작업자별로 특정 루트 모듈을 선정해 구성 작업을 진행해 코드 충돌을 최소화하는 환경을 구성하고 인수인계 과정에서 리뷰하는 영역을 최소화할 수 있다.

8.2.2 환경격리 -깃 브랜치

  • 서비스의 테스트, 검증, 운영 배포를 위해 테라폼으로 관리하는 리소스가 환경별로 격리되어야 한다면 디렉터리 구조로 분리하는 방안을 고려할 수 있다.
  • 디렉터리별로 각 환경을 나누는 것은 개인의 관리 편의성은 높지만, 환경의 아키텍처를 고정시키고 코드 수준의 승인 체계를 만들기 위해서는 최종 형상에 대한 환경별 브랜치를 구성하기를 권장한다.
  • 디렉터리 구조 만으로는 환경에 따라 사용자를 격리할 수 없다. 이때 깃의 브랜치 기능을 활용하면 환경 별로 구별된 작업과 협업이 가능하다.

깃 플로 예시

  • 브랜치 전략
    • main : 운영 코드가 관리되며 이곳에는 직접적으로 구성 변경을 수행하지 않음
    • QA : 검증 대상 인프라를 구성하는 코드로, 메인 브랜치와 같이 직접적인 구성 변경을 수행하지 않음
    • DEV : QA 전 단계로 메인 코드 구성과 기능 브랜치의 병합을 담당
    • feature : 새로운 리소스를 추가하고 구현하며 여러 개가 될 수 있음
  • 관리의 편의성을 고려해 Hot-fix와 Release 브랜치를 추가할 수도 있지만 인프라의 특성상 개발, 검증, 운영으로 나눈다.
  • 환경 간에 프로비저닝이 되는 리소스를 갖추고 있다면 운영을 위한 프로비저닝 환경을 안정적으로 유지할 수 있다는 장점이 있다.
  • 디렉터리 구조로 관리하는 환경별 디렉터리 구성 방식에서는 개발할 때 작성한 구성을 다시 복사해 검증 또는 운영에 반영하므로 환경별로 구성이 다른 상황이 발생할 여지가 높고, 모든 디렉터리에 접근 가능할 경우 검증과 운영을 위한 구성을 직접 수정하는 일이 발생할 가능성이 높다.
  • 따라서 작업자가 다수의 환경을 동시에 관리한다면 디렉터리로 구분하더라도 각 디렉터리마다 동일한 깃 저장소의 브랜치별 리모트 구성을 하는 것이 바람직하다.

디렉토리 격리에 깃 브랜치 연결

이 방식은 동일한 브랜치를 변경해가면 작업해 발생하는 실수를 줄일 수 있고, 각 브랜치가 연결되어 있으므로 단일 작업자가 다수의 환경을 관리하는 이점과 각 환경별로 리소스 구성이 동일하게 유지되는 장점이 있다.

블로그 이미지

감동맨

rkaehdaos의 블로그

,

5.State

5.1 State 목적과 의미

  • 상태 파일은 배포할 때마다 변경되는 프라이빗 API private API
  • 오직 테라폼 내부에서 사용하기 위한 것입니다.
  • 테라폼 상태 파일을 직접 편집하거나 직접 읽는 코드로 작성해서는 안된다

팀 단위에서 테라폼 운영 시 문제점

  • 상태 파일을 저장하는 공유 스토리지( Shared storage for state files)
    • 각 팀원이 동일한 테라폼 상태 파일 사용을 위해서, 공유 위치에 저장이 필요
  • 상태 파일 잠금 Locking state files
    • 잠금 기능 없이 두 팀원이 동시에 테라폼 실행 시 여러 테라폼 프로세스가 상태 파일을 동시에 업데이트하여 충돌 가능(경쟁 상태 race condition)
  • 상태 파일 격리 Isolating state files
    • 예를 들면 테스트 dev 와 검증 stage 과 상용 prodction 각 환경에 대한 격리가 필요

상태 파일 공유로 버전 관리 시스템 비추천

  1. 수동 오류 Manual error
    • 테라폼을 실행하기 전에 최신 변경 사항을 가져오거나 실행하고 나서 push 하는 것을 잊기 쉽습니다(?).
    • 팀의 누군가가 이전 버전의 상태 파일로 테라폼을 실행하고, 그 결과 실수로 이전 버전으로 롤백하거나 이전에 배포된 인프라를 복제하는 문제가 발생 할 수 있음.
  2. 잠금 Locking
    • 대부분의 버전 관리 시스템(VCS)은 여러 명의 팀 구성원이 동시에 하나의 상태 파일에 terraform apply 명령을 실행하지 못하게 하는 잠금 기능이 제공되지 않음.
  3. 시크릿 Secrets
    • 테라폼 상태 파일의 모든 데이터는 평문으로 저장됨. 민감 정보가 노출될 위험.

지원되는 원격 백엔드

  • AWS S3, Azure Blob Storage, Google Cloud Storage, Consul, Postgres database 등
  •  위 제약사항에 대한 해결방안!→ 링크 
  1. 수동 오류 해결 : plan/apply 실행 시 마다 해당 백엔드에서 파일을 자동을 로드, apply 후 상태 파일을 백엔드에 자동 저장
  2. 잠금(Lock) : apply 실행 시 테라폼은 자동으로 잠금을 활성화, `-lock-timout=<TIME>` 로 대기 시간 설정 지정 가능
  3. 시크릿 : 대부분 원격 백엔드는 기본적으로 데이터를 보내거나 상태 파일을 저장할 때 암호화(Encryption)하는 기능을 지원

책 내용

  • 소개 및 확인 : 테라폼은 Stateful 애플리케이션. 프로비저닝 결과 State를 저장하고 추적에 활용
    • 개인 1인 : 로컬 환경으로 terraform.tfstate 파일에 JSON 형태로 저장
    • 팀이나 조직 : 공동 관리를 위해 원격 저장소에 저장해 공유 - 링크
    • State에는 작업자가 정의한 코드와 실제 반영된 프로비저닝 결과를 저장하고, 이 정보를 토대로 이후의 리소스 생성, 수정, 삭제에 대한 동작 판단 작업을 수행
    State 역할
    • State에는 테라폼 구성과 실제를 동기화하고 각 리소스에 고유한 아이디(리소스 주소)로 맵핑
    • 리소스 종속성과 같은 메타데이터를 저장하고 추적
    • 테라폼 구성으로 프로비저닝 결과를 캐싱하는 역할을 수행

5.1 실습

resource "random_password" "mypw" {
  length           = 16
  special          = true
  override_special = "!#$%"
}

5.1 실행

terraform init && terraform plan
terraform apply -auto-approve

# State List 및 생성된 Password 확인
terraform state list
terraform state show random_password.mypw

# State에 저장된 result를 확인해보자! -> 어떻게 관리해야할까요?
ls *.tfstate
cat terraform.tfstate | jq
cat terraform.tfstate | jq | grep result
YLGmKyb3jOuI8sEf

# (참고) sensitive value 내용은 테라폼 콘솔에서 보일까요? 
echo "random_password.mypw" | terraform console
echo "random_password.mypw.result" | terraform console
  • 테라폼에서는 JSON 형태로 작성된 State를 통해 속성인수를 읽고 확인할 수 있다. 테라폼에서는 typename으로 고유한 리소스분류하며, 해당 리소스의 속성인수를 구성과 비교해 대상 리소스생성, 수정, 삭제한다.
  • State는 테라폼만을 위한 API로 정의할 수도 있다. Plan을 실행하면 암묵적으로 refresh 동작을 수행하면서 리소스 생성의 대상(클라우드 등)과 State를 기준으로 비교하는 과정을 거친다. 이 작업은 프로비저닝 대상의 응답 속도와 기존 작성된 State의 리소스 양에 따라 속도 차이가 발생한다. 대량의 리소스를 관리해야 하는 경우 Plan 명령에서 -refresh=false 플래그를 사용해 State를 기준으로 실행 계획을 생성하고, 이를 실행에 활용해 대상 환경과의 동기화 과정을 생략할 수 있다.
# 실행 계획 생성 시 저장되어 있는 State와 실제 형상을 비교하는 기본 실행
time terraform plan

# 실행 계획 생성 시 실제 형상과 비교하지 않고 실행 계획을 생성하는 -refresh=false 옵션
time terraform plan -refresh=false

5.2 State 동기화

  • 테라폼 구성과 State 흐름 : Plan 과 Apply 중 각 리소스에 발생할 수 있는 네 가지 사항, 아래 실행 계획 출력 기호와 의미기호 의미
    + Create
    - Destroy
    -/+ Replace
    ~ Updated in-place
    • Replace 동작은 기본값을 삭제 후 생성하지만 lifecycle의 create_before_destroy 옵션을 통해 생성 후 삭제 설정 가능
  • 유형 별 실습 + 문제상황 추가 : 테라폼 구성에 추가된 리소스와 State에 따라 어떤 동작이 발생하는지 다음 표로 살펴본다유형 구성 리소스 정의 State 구성 데이터 실제 리소스 기본 예상 동작
    1 있음     리소스 생성
    2 있음 있음   리소스 생성
    3 있음 있음 있음 동작 없음
    4   있음 있음 리소스 삭제
    5     있음 동작 없음
    6 있음   있음  

유형1 : 신규 리소스 정의 → Apply ⇒ 리소스 생성(처음부터 Terraform 코드로 작업)

locals {
  name = "mytest"
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
}

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
}

#########
terraform init && terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1

ls *.tfstate
cat terraform.tfstate | jq

terraform apply -auto-approve
ls *.tfstate

# iam 사용자 리스트 확인
aws iam list-users | jq

유형2 : 실제 리소스 수동 제거 → Apply ⇒ 리소스 생성

# 실제 리소스 수동 제거
aws iam delete-user --user-name mytest1
aws iam delete-user --user-name mytest2
aws iam list-users | jq

# 아래 명령어 실행 결과 차이는? (refresh 유무)
# 실제 리소스는 제거되었으나 -refrest=false로 인해 변경사항 없으므로 인식
terraform plan
terraform plan -refresh=false
cat terraform.tfstate | jq .serial

#
terraform apply -auto-approve
terraform state list
cat terraform.tfstate | jq .serial

# iam 사용자 리스트 확인
aws iam list-users | jq

유형3 : Apply → Apply - 코드, State, 형상 모두 일치한 경우 ⇒ 변경사항 없음

# Serial 값일 동일!
terraform apply -auto-approve
cat terraform.tfstate | jq .serial
terraform apply -auto-approve
cat terraform.tfstate | jq .serial
terraform apply -auto-approve
cat terraform.tfstate | jq .serial

유형4 : 코드에서 일부 리소스 삭제 → Apply ⇒ 리소스 삭제

locals {
  name = "mytest"
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
}
#####################
# 코드 변경으로 인해 user2 삭제
terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1

ls *.tfstate
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq

유형5 : 리소스만 있으며 코드, State는 없는 경우 → Import 또는 신규코드 작성

유형6 : 실수로 tfstate 파일 삭제 → plan/apply ← 책에는 없는 내용

# 실수로 tfstate 파일 삭제
rm -rf terraform.tfstate*

# 아래 두 명령 결과 차이는?
# Local에 State 파일이 없으므로 두 명령 모두 새로운 리소스 추가(add) 계획
terraform plan
terraform plan -refresh=false

# apply할 경우 어떤 결과 발생?
# 이미 리소스가 있으므로 Error 발생: EntityAlreadyExists: User with name mytest1 already exists.
terraform apply -auto-approve
terraform state list
cat terraform.tfstate | jq

# iam 사용자 리스트 확인
aws iam list-users | jq

# 다음 실습을 위해 iam user 삭제
aws iam delete-user --user-name mytest1

5.3 워크 스페이스

 

 

 

 

소개 : State관리하는 논리적인 가상 공간워크스페이스라고 한다 - 링크

  • CE와 Cloud/Enterprise에서 제공되는 Workspace의 기능을 비교해보자!Component Local Terraform Terraform Cloud/Enterprise
    Terraform configuration On disk In linked version control repository, or periodically uploaded via API/CLI
    Variable values As .tfvars files, as CLI arguments, or in shell environment In workspace
    State On disk or in remote backend In workspace
    Credentials and secrets In shell environment or entered at prompts In workspace, stored as sensitive variables(via Vault)
         
  • 테라폼 구성 파일은 동일하지만 작업자는 서로 다른 State를 갖는 실제 대상을 프로비저닝할 수 있다.
  • 워크스페이스는 기본 default로 정의된다. 로컬 작업 환경의 워크스페이스 관리를 위한 CLI 명령어로 workspace가 있다.
terraform workspace list
* default

실습

resource "aws_instance" "mysrv1" {
  ami           = "ami-0ea4d4b8dc1e46212"
  instance_type = "t2.micro"
  tags = {
    Name = "t101-week4"
  }
}

실행

# [분할/터미널1] 모니터링
export AWS_PAGER=""
while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done

# [분할/터미널2] 배포
terraform init && terraform apply -auto-approve
terraform state list

#
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.private_ip'

# terraform.tfstate에 private 담긴 내용은?
cat terraform.tfstate | jq -r '.resources[0].instances[0].private' | base64 -d | jq

# 워크스페이스 확인
terraform workspace list

# graph 확인
terraform graph > graph.dot

신규 워크스페이스 생성 및 확인

# 새 작업 공간 workspace 생성 : mywork1
terraform workspace new mywork1
terraform workspace show

# 서브 디렉터리 확인 -> 향후 저장되는 State는 여기에!
tree terraform.tfstate.d
terraform.tfstate.d
└── mywork1

# plan 시 어떤 결과 내용이 출력되나요?
terraform plan

# apply 해보자!
terraform apply -auto-approve


# 워크스페이스 확인
terraform workspace list

# State 확인
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'

# graph 확인
terraform graph > graph.dot


# (실습생략)새 작업 공간 workspace 생성 : mywork2
terraform workspace new mywork2

# 서브 디렉터리 확인
tree terraform.tfstate.d
...

# plan & apply
terraform plan && terraform apply -auto-approve
cat terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork1/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'
cat terraform.tfstate.d/mywork2/terraform.tfstate | jq -r '.resources[0].instances[0].attributes.public_ip'

# workspace 정보 확인
terraform workspace show
terraform workspace list

# 실습 리소스 삭제
terraform workspace select default
terraform destroy -auto-approve
terraform workspace select mywork1
terraform destroy -auto-approve
terraform workspace select mywork2
terraform destroy -auto-approve
  • 장점
    • 하나의 루트 모듈에서 다른 환경을 위한 리소스를 동일한 테라폼 구성으로 프로비저닝하고 관리
    • 기존 프로비저닝된 환경에 영향을 주지 않고 변경 사항 실험 가능
    • 깃의 브랜치 전략처럼 동일한 구성에서 서로 다른 리소스 결과 관리
      [참고 : 화해 - Git 브랜치 전략 수립을 위한 전문가의 조언들]
  • 단점
    • State가 동일한 저장소(로컬 또는 백엔드)에 저장되어 State 접근 권한 관리가 불가능(어려움)
    • 모든 환경이 동일한 리소스를 요구하지 않을 수 있으므로 테라폼 구성에 분기 처리가 다수 발생 가능
    • 프로비저닝 대상에 대한 인증 요소를 완벽히 분리하기 어려움⇒ 해결방안 1. 해결하기 위해 루트 모듈을 별도로 구성하는 디렉터리 기반의 레이아웃을 사용할 수 있다. ⇒ 해결방안 2. Terraform Cloud 환경의 워크스페이스를 활용
    • → 가장 큰 단점은 완벽한 격리가 불가능
블로그 이미지

감동맨

rkaehdaos의 블로그

,

3.10 조건문

  • 테라폼의 조건식은 3학 연산자 형태
  • true, false로 확인된는 모든 표현식 사용 가능
  • 비교대상의 형태가 다르면 자동 변환해서 비교 -> 명시적 형태로 해야 오류가 없다
variable "enable_file" {
  default = true
}

resource "local_file" "foo" {
  count    = var.enable_file ? 1 : 0
  content  = "foo!"
  filename = "${path.module}/foo.bar"
}

output "content" {
  value = var.enable_file ? local_file.foo[0].content : ""
}

3.11 함수

  • 테라폼은 프로그래밍 언어적 특성을 지닌다
    -> 값의 유형을 변경하거나 조합할 수 있는 내장 함수 사용 가능 : Built-in Functions
  • 별도의 사용자 정의 함수를 지원하지는 않는다
  • 함수 종류에는 숫자, 문자열, 컬렉션, 인코딩, 파일 시스템, 날짜/시간, 해시/암호화, IP 네트워크, 유형 변환이 있다.
  • 테라폼 코드에 함수를 적용하면 변수, 리소스 속성, 데이터 소스 속성, 출력 값 표현 시 작업을 동적이고 효과적으로 수행할 수 있다
resource "local_file" "foo" {
  content  = upper("foo! bar!")
  filename = "${path.module}/foo.bar"
}

실행

#
terraform init && terraform plan && terraform apply -auto-approve
cat foo.bar ; echo

# 내장 함수 간단 사용
terraform console
>
-----------------
upper("foo!")
max(5, 12, 9)
lower(local_file.foo.content)

cidrnetmask("172.16.0.0/12")

exit
-----------------

3.12 프로비저너

  • 프로바이더와 비슷하게 '제공자'로 해석 되나 다름
  • 프로바이더로 실행되지 않는 커맨드, 파일 복사와 같은 역할 수행 : Provisioners
  • local-exec Provisioner : 테라폼이 실행되는 환경에서 수행할 커맨드를 정의 
  • 리눅스나 윈도우 등 테라폼을 실행하는 환경에 맞게 커맨드를 정의
  • 사용 인수
    • command(req)
      • 실행할 명령어 라인을 입력한다
      • `<<` 연산자를 통해 여러 줄의 커맨드를 입력 가능하다.
    • working_dir(option)
      • command의 명령을 실행할 디렉터리를 지정
      • 상대/절대 경로 설정
    • intgerpreter(option)
      • command의 명령을 실행하는데 필요한 인터프리터를 지정
      • 첫번째 인수는 인터프리터 이름, 그 이후부터는 인터프리터 인수 값
    • environment(option)
      • 실행시 환경 변수는 실행 환경의 값을 상속 받는다
      • 추가 재할당시 해당 인수에 key=value 형태로 설정한다
resource "null_resource" "example1" {
  
  provisioner "local-exec" {
    command = <<EOF
      echo Hello!! > file.txt
      echo $ENV >> file.txt
      EOF
    
    interpreter = [ "bash" , "-c" ]

    working_dir = "/tmp"

    environment = {
      ENV = "world!!"
    }

  }
}

실행

# 
terraform init -upgrade

# 
terraform plan && terraform apply -auto-approve
...
null_resource.example1: Creating...
null_resource.example1: Provisioning with 'local-exec'...
null_resource.example1 (local-exec): Executing: ["bash" "-c" "      echo Hello!! > file.txt\n      echo $ENV >> file.txt\n"]
...

# 
terraform state list
terraform state show null_resource.example1
cat /tmp/file.txt

remote-exec Provisioner(원격지 연결)

  • remote-exec, file 프로비저너를 사용하기 위해서는 원격지에 연결한 SSH, WinRM 연결 정의 필요
  • connection 블록 리소스 선언 시, 해당 리소스 내에 구성된 프로비저너에 대해 공통으로 선언되고
    프로비저너 내 에 선언되는 경우에는 해당 프로비저너에만 적용된다.
# connection 블록으로 원격지 연결 정의
resource "null_resource" "example1" {
  
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = var.host
  }

  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
  }

  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "C:/App/myapp.conf"

    connection {
        type     = "winrm"
        user     = "Administrator"
        password = var.admin_password
        host     = var.host
    }
  }
}

Provisioner Connection Settings(커넥션 적용 세팅)

Conbnection 적용 인수와 설명

 

원격 연결이 요구되는 프로비저너의 경우
->스크립트 파일을 원격 시스템에 업로드 해 해당 시스템의 기본 쉘에서 실행하도록 한다.
-> script_path 경우 적절한 위치를 지정하도록 한다.
-> 경로는 난수인 `%RAND%` 경로가 포함되어 생성된다.

Unix/Linux/macOS : /tmp/terraform_%RAND%.sh
Windows(cmd) : C:/windows/temp/terraform_%RAND%.cmd
Windows(PowerShell) : C:/windows/temp/terraform_%RAND%.ps1

Connecting through a Bastion Host with SSH(배스천 호스트를 통해 연결)

connection 적용 시 Bastion 호스트를 설정하는 인수와 설명3.1

3.12.4 file 프로비저너

  • 테라폼을 실행하는 시스템에서 연결 대상으로 파일 또는 디렉터리를 복사하는데 사용
  • 사용되는 인수
    • source : 소스 파일 또는 디렉터리로, 현재 작업 중인 디렉터리에 대한 상대 경로 또는 절대 경로로 지정할 수 있다. content와 함께 사용할 수 없다.
    • content : 연결 대상에 복사할 내용을 정의하며 대상이 디렉터리인 경우 tf-file-content 파일이 생성되고, 파일인 경우 해당 파일에 내용이 기록된다. source와 함께 사용할 수 없다.
    • destination : 필수 항목으로 항상 절대 경로로 지정되어야 하며, 파일 또는 디렉터리다.
  • destination 지정 시 주의해야 할 점은 winrm 연결은 디렉터리가 없는 경우 자동으로 생성하지만, ssh 연결의 경우 대상 디렉터리가 존재해야 한다.
  • 디렉터리를 대상으로 하는 경우에는 source 경로 형태에 따라 동작에 차이가 생긴다.
  • destination이 /tmp인 경우 source가 디렉터리로 /foo 처럼 마지막에 /가 없는 경우 대상 디렉터리에 지정한 디렉터리가 업로드되어 연결된 시스템에 /tmp/foo 디렉터리가 업로드된다.
  • source가 디렉터리로 /foo/ 처럼 마지막에 /가 포함되는 경우 source 디렉터리 내의 파일만 /tmp 디렉터리에 업로드된다.

예시 - 파일 프로비저너 구성 예시

resource "null_resource" "foo" {
  
  # myapp.conf 파일이 /etc/myapp.conf 로 업로드
  provisioner "file" {
    source      = "conf/myapp.conf"
    destination = "/etc/myapp.conf"
  }
  
  # content의 내용이 /tmp/file.log 파일로 생성
  provisioner "file" {
    content     = "ami used: ${self.ami}"
    destination = "/tmp/file.log"
  }
  
  # configs.d 디렉터리가 /etc/configs.d 로 업로드
  provisioner "file" {
    source      = "conf/configs.d"
    destination = "/etc"
  }
  
  # apps/app1 디렉터리 내의 파일들만 D:/IIS/webapp1 디렉터리 내에 업로드
  provisioner "file" {
    source      = "apps/app1/"
    destination = "D:/IIS/webapp1"
  }

}

3.12.5 remote - exec Provisioner

  • 예 - AWS의 EC2 인스턴스를 생성 해당 VM에서 명령을 실행하고 패키지를 설치하는 등의 동작을 의미한다.
  • 사용하는 인수는 다음과 같고 각 인수는 서로 배타적이다.
    • inline : 명령에 대한 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 명령을 , 로 구분해 구성한다.
    • script : 로컬의 스크립트 경로를 넣고 원격에 복사해 실행한다.
    • scripts : 로컬의 스크립트 경로의 목록으로 [ ] 블록 내에 “ “로 묶인 다수의 스크립트 경로를 , 로 구분해 구성한다
  • script 또는 scripts의 대상 스크립트 실행에 필요한 인수는 관련 구성에서 선언할 수 없으므로 필요할 때 file 프로바이더로 해당 스크립트를 업로드하고 inline 인수를 활용해 스크립트에 인수를 추가한다.

구성예시

resource "aws_instance" "web" {
  # ...

  # Establishes connection to be used by all
  # generic remote provisioners (i.e. file/remote-exec)
  connection {
    type     = "ssh"
    user     = "root"
    password = var.root_password
    host     = self.public_ip
  }

  provisioner "file" {
    source      = "script.sh"
    destination = "/tmp/script.sh"
  }

  provisioner "remote-exec" {
    inline = [
      "chmod +x /tmp/script.sh",
      "/tmp/script.sh args",
    ]
  }
}

3.13 null_resource와 terraform_data

테라폼 1.4가 릴리즈 되면서 기존 null_resource를 대체하는 terraform_data리소스가 추가 되었다.
두 리소스의 의미와 사용 방식 확인 필요
  • null_resource - 아무 작업도 수행하지 않는 리소스 구현
    • 이런 리소스가 필요한 이유??
      • 테라폼 프로비저닝 동작을 설계하면서 사용자가 의도적으로 프로비저닝하는 동작을 조율해야 하는 상황이 발생함
      • 프로바이더가 제공하는 리소스 수명주기 관리만으로는 이를 해결하기 어렵기 때문.
    • 주로 사용되는 시나리오
      • 프로비저닝 수행 과정에서 명령어 실행
      • 프로비저너와 함께 사용
      • 모듈, 반복문, 데이터 소스, 로컬 변수와 함께 사용
      • 출력을 위한 데이터 가공
    • 예를 들어 다음의 상황을 가정
      • AWS EC2 인스턴스를 프로비저닝하면서 웹서비스를 실행시키고 싶다
      • 웹서비스 설정에는 노출되어야 하는 고정된 외부 IP가 포함된 구성이 필요하다. 따라서 aws_eip 리소스를 생성해야 한다.
    • AWS EC2 인스턴스를 프로비저닝하기 위해 aws_instance 리소스 구성 시 앞서 확인한 프로비저너를 활용하여 웹서비스를 실행하고자 한다

예시

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_security_group" "instance" {
  name = "t101sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

}

resource "aws_instance" "example" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-dbc571b0" 
  private_ip             = "172.31.1.100"
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, T101 Study" > index.html
              nohup busybox httpd -f -p 80 &
              EOF

  tags = {
    Name = "Single-WebSrv"
  }

  provisioner "remote-exec" {
    inline = [
      "echo ${aws_eip.myeip.public_ip}"
     ]
  }
}

resource "aws_eip" "myeip" {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = "172.31.1.100"
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}
  • aws_eip가 생성되는 고정된 IP를 할당하기 위해서는 대상인 aws_instance의 id값이 필요하다
  • aws_instance의 프로비저너 동작에서는 aws_eip가 생성하는 속성 값인 public_ip가 필요하다
  • 상호 참조 발생
    • 테라폼 구성 정의에서 상호 참조가 발생하는 상황
    • 실제 코드에서 plan 을 수행하면 에러가 발생한다.

수정 - 둘 중 하나의 실행 시점을 한 단계 뒤로 미룬다 -> main.tf 수정

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_security_group" "instance" {
  name = "t101sg"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

}

resource "aws_instance" "example" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t2.micro"
  subnet_id              = "subnet-0b92d8356a0cbca38"
  private_ip             = "172.31.0.100"
  key_name               = "kp-kaje" # 각자 자신의 EC2 SSH Keypair 이름 지정
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
              #!/bin/bash
              echo "Hello, T101 Study" > index.html
              nohup busybox httpd -f -p 80 &
              EOF

  tags = {
    Name = "Single-WebSrv"
  }

}

resource "aws_eip" "myeip" {
  #vpc = true
  instance = aws_instance.example.id
  associate_with_private_ip = "172.31.0.100"
}

resource "null_resource" "echomyeip" {
  provisioner "remote-exec" {
    connection {
      host = aws_eip.myeip.public_ip
      type = "ssh"
      user = "ubuntu"
      private_key =  file("/home/kaje/kp-kaje.pem") # 각자 자신의 EC2 SSH Keypair 파일 위치 지정
      #password = "qwe123"
    }
    inline = [
      "echo ${aws_eip.myeip.public_ip}"
      ]
  }
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}

output "eip" {
  value       = aws_eip.myeip.public_ip
  description = "The EIP of the Instance"
}

수정 실행

# 프로비저너 필요로 설치
terraform init -upgrade

# 실행 : EIP 할당 전 (임시) 유동 공인 IP 출력
terraform plan
terraform apply -auto-approve
...
null_resource.echomyeip (remote-exec): Connected!
null_resource.echomyeip (remote-exec): 13.125.25.238
...
Outputs:
eip = "13.125.25.238"
public_ip = "43.201.63.58"

#
terraform state list
terraform state show aws_eip.myeip
terraform state show aws_instance.example
terraform state show null_resource.echomyeip

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 데이터소스 값 확인
echo "aws_instance.example.public_ip" | terraform console
echo "aws_eip.myeip.public_ip" | terraform console

# 출력된 EC2 퍼블릭IP로 curl 접속 확인
MYIP=$(terraform output -raw eip)
while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done
  • 삭제: `terraform destroy -auto-approve`
  • null_resource는 정의된 속성이 ‘id’가 전부이므로, 선언된 내부의 구성이 변경되더라도 새로운 Plan 과정에서 실행 계획에 포함되지 못한다.
  • 따라서 사용자가 null_resource에 정의된 내용을 강제로 다시 실행하기 위한 인수로 trigger가 제공된다.
  • trigger는 임의의 string 형태의 map 데이터를 정의하는데, 정의된 값이 변경되면 null_resource 내부에 정의된 행위를 다시 실행한다.]
  • trigger 예시
resource "null_resource" "foo" {
  triggers = {
    ec2_id = aws_instance.bar.id # instance의 id가 변경되는 경우 재실행
  }
  ...생략...
}

resource "null_resource" "bar" {
  triggers = {
    ec2_id = time() # 테라폼으로 실행 계획을 생성할 떄마다 재실행
  }
  ...생략...
}

terraform_data (번역)

  • 이 리소스 또한 자체적으로 아무것도 수행하지 않는다.
  • null_resource는 별도의 프로바이더 구성이 필요하다는 점과 비교하여
    추가 프로바이더 없이 테라폼 자체에 포함된 기본 수명주기 관리자가 제공된다는 것이 장점이다.
  • 사용 시나리오는 기본 null_resource와 동일하며 강제 재실행을 위한 trigger_replace와 상태 저장을 위한 input 인수와 input에 저장된 값을 출력하는 output 속성이 제공된다.
  • triggers_replace에 정의되는 값이 기존 map 형태에서 tuple로 변경되어 쓰임이 더 간단해졌다
  • terraform_data 리소스의 trigger_replace 정의와 동작 예제
resource "terraform_data" "foo" {
  triggers_replace = [
    aws_instance.foo.id,
    aws_instance.bar.id
  ]

  input = "world"
}

output "terraform_data_output" {
  value = terraform_data.foo.output  # 출력 결과는 "world"
}

3.14 moved block

  • 테라폼의 State에 기록되는 리소스 주소의 이름이 변경되면 기존 리소스는 삭제되고 새로운 리소스가 생성됨을
    앞의 설명에서 확인.
  • 하지만 테라폼 리소스를 선언하다 보면 이름을 변경해야 하는 상황이 발생하기도 하는데, 예를 들면 다음과 같다.
    • 리소스 이름을 변경
    • count로 처리하던 반복문을 for_each로 변경
    • 리소스가 모듈로 이동하여 참조되는 주소가 변경
  • 리소스의 이름은 변경되지만 이미 테라폼으로 프로비저닝된 환경을 그대로 유지하고자 하는 경우 테라폼 1.1 버전부터 moved 블록을 사용할 수 있다.
  • ‘moved’라는 단어가 의미하는 것처럼 테라폼 State에서 옮겨진 대상의 이전 주소와 새 주소를 알리는 역할을 수행한다.
  • moved 블록 이전에는 State를 직접 편집하는 terraform state mv 명령을 사용하여 State를 건드려야 하는 부담이 있었다면, moved 블록은 State에 접근 권한이 없는 사용자라도 변경되는 주소를 리소스 영향 없이 반영할 수 있다.

 

3.15 System Environment Variables for CLI

  • 테라폼은 환경 변수를 통해 실행 방식과 출력 내용에 대한 옵션을 조절 가능
    • 시스템 환경 변수 설정으로, 영구적으로 로컬 환경에 적용되는 옵션이나 별도 서버 환경에서 실행하기 위한 옵션을 부여 가능.
    • 이를 통한 로컬 작업 환경과 다른 환경 구성에서만 사용될 특정 옵션을 적용.
Mac/리눅스/유닉스: export <환경 변수 이름>=<값>
Windows CMD: set <환경 변수 이름>=<값> 
Windows PowerShell: $Env:<환경 변수 이름>='<값>'

3.15.1 TF_LOG

  • 테라폼의 stderr 로그에 대한 레벨을 정의
    • trace, debug, info, warn, error, off를 설정할 수 있고 관련 환경 변수가 없는 경우 off와 동일하다
    • 디버깅을 위한 로그 관련 환경 변수 설명은 다음과 같다
      • TF_LOG: 로깅 레벨 지정 또는 해제
      • TF_LOG_PATH: 로그 출력 파일 위치 지정
      • TF_LOG_CORE: TF_LOG와 별도로 테라폼 자체 코어에 대한 로깅 레벨 지정 또는 해제
      • TF_LOG_PROVIDER: TF_LOG와 별도로 테라폼에서 사용하는 프로바이더에 대한 로깅 레벨 지정 또는 해제
    • 환경에 맞게 TF_LOG를 info로 설정하고, terraform plan 동작을 실행하면 테라폼 출력에 관련 로그가 출력된다
      (TF_LOG=info terraform plan ...)

3.15.2 TF_INPUT

  •  값을 false 또는 0으로 설정하면 테라폼 실행 시 인수에 -input=false 를 추가한 것과 동일한 수행 결과를 확인
    • 환경에 맞게 TF_INPUT을 0으로 설정하고 terraform plan 동작 실행하면 입력받는 동작을 수행하지 않으므로 입력 변수를 입력해야 하는 경우 에러가 출력된다
    • TF_INPUT=0 terraform plan Error : No value for required variable

3.15.3 TF_VAR_name 

TF_VAR_<변수 이름>을 사용하면 입력 시 또는 default로 선언된 변수 값을 대체한다 ← 3.6절에서 확인!

# 3.15.4
# TF_CLI_ARGS="-input=false" terraform apply -auto-approve 는 terraform apply -input=false -auto-approve 와 같다
TF_CLI_ARGS="-input=false" terraform apply -auto-approve
Error: No value for required variable

# TF_CLI_ARGS_apply로 인수를 정의하면 terraform apply 커맨드 수행 시에만 동작한다
export TF_CLI_ARGS_apply="-input=false"
terraform apply -auto-approve
<에러>

terraform plan
<정상 계획 예측 출력>

3.15.5 TF_DATA_DIR

State 저장 백엔드 설정과 같은 작업 디렉터리별 데이터를 보관하는 위치를 지정

  • 데이터는 .terraform 디렉터리 위치에 기록되지만 TF_DATA_DIR에 경로가 정의되면 기본 경로를 대체하여 사용된다.
  • 일관된 테라폼 사용을 위해서 해당 변수는 실행 시마다 일관되게 적용될 수 있도록 설정하는 것이 중요하다.
  • 설정 값이 이전 실행 시에만 적용되는 경우 init 명령으로 수행된 모듈, 아티팩트 등의 파일을 찾지 못한다.
  • 이미 terraform init이 수행된 상태에서 TF_DATA_DIR로 경로를 재지정하고 실행하는 경우 플러그인 설치가 필요하다는 메시지 출력을 확인할 수 있다.
TF_DATA_DIR=./.terraform_tmp terraform plan
Error: Required plugins anr not installed
블로그 이미지

감동맨

rkaehdaos의 블로그

,

3.5 데이터 소스

  • 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용
  • 데이터 소스 블록은 data로 시작, 이후 데이터 소스 유형 정의
data "<리소스 유형>"	"<이름>" {
	<인수> = <값>
}
  •  
    • 첫번째 _를 기준으로 앞은 프로바이더 이름,
      뒤는 프로바이더에서 제공하는 리소스 유형
      ex)local_file - local이라는 이름의 프로바이더에서 제공하는 file이라는 리소스 유형
    • 데이터 소스 유형 선언 뒤에는 고유한 이름을 붙인다
      • 리소스 이름과 마찬가지로 중복 될 수 없다 -> 식별자 역할
    • 이름 뒤 데이터 소스 유형에 대한 구성 인수들은 {} 안에 선언한다
data "local_file" "abc" {
  filename = "${path.module}/abc.txt"
}
  • 데이터 소스  정의시 사용가능한 메타인수
    • depends_on
      • 종속성 선언
      • 선언 구성요소와 생성 시점에 대해 정의
    • count
      • 선언된 개수에 따라 여러 리소스 생성
    • for_each
      • map 또는 set타입의 데이터 배열의 값을 기준으로 여러 리소스 생성
    • lifecycle
      • 리소스의 수명 주기 관리
# 실습 확인을 위해서 abc.txt 파일 생성
echo "t101 study - 2week" > abc.txt

# -auto-approve가 있으면 yes를 묻지 않음
terraform init && terraform plan && terraform apply -auto-approve
terraform state list

# 테라폼 콘솔 : 데이터 소스 참조 확인
echo "data.local_file.abc" | terraform console
  • 데이터 소스 속성 참조

3.6 입력 변수 Variable

  • Input Variable로 테라폼에서는 정의
    • 인프라를 구성하는데 필요한 속성 값을 정의
    • 코드 변경 없이 여러 인프라 생성 가능하게 하는데 목적
  • 변수 선언 방식
    • 변수는 variable로 시작되는 블록으로 구성된다.
    • 변수 블록 뒤의 이름 값은 동일 모듈 내 모든 변수 선언에서 고유해야 하며, 이 이름으로 다른 코드 내에서 참조된다.
    • 테라폼 예약변수 이름으로는 사용이 불가능하다.
# variable 블록 선언의 예
variable "<이름>" {
 <인수> = <값>
}

variable "image_id" {
 type = string
}
안되는 것들 : source, version, providers, count, for_each, lifecycle, depends_on, locals
  • 변수 정의 시 사용 가능한 메타인수
    • default
      • 변수 값을 전달하는 여러 가지 방법을 지정하지 않으면 기본값 전달
      • 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄
    • type
      • 변수에 허용되는 값 타입 정의
      • string, number, bool, list, map, set, object, tuple
      • 타입을 지정하지 않으면 any 유형으로 간주
    • description : 입력 변수의 설명
    • validation : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의
    • sensitive : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한 (암호 등 민감 데이터의 경우)
    • nullable : 변수에 값이 없어도 됨을 지정
  • 변수 유형
    • 기본 유형
      • string, number, bool, any(명시적으로 모든 유형이 허용됨을 선언)
    • 집합 유형
      • list (<유형>): 인덱스 기반 집합
      • map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
      • set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
      • object ({<인수 이름>=<유형>, …})
      • tuple ([<유형>, …])
    • list와 set : 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있다
    • map와 set : 선언된 값이 정렬되는 특징
  • 입력 변수 사용 예시
# number
variable "number_example" {
  description = "An example of a number variable in Terraform"
  type        = number
  default     = 42
}


# list
variable "list_example" {
  description = "An example of a list in Terraform"
  type        = list
  default     = ["a", "b", "c"]
}


# 조건 결합1 : 모든 항목이 number인 list
variable "list_numeric_example" {
  description = "An example of a numeric list in Terraform"
  type        = list(number)
  default     = [1, 2, 3]
}




# 조건 결합2 : 모든 값이 String 인 mapvariable "map_example" {
  description = "An example of a map in Terraform"
  type        = map(string)

  default = {
    key1 = "value1"
    key2 = "value2"
    key3 = "value3"
  }
}

# 구조적 유형 (Structural type)
# object나 tuple 제약 조건을 사용해서 만든 복잡 유형
variable "object_example" {
  description = "An example of a structural type in Terraform"
  type        = object({
    name    = string
    age     = number
    tags    = list(string)
    enabled = bool
  })

  default = {
    name    = "value1"
    age     = 42
    tags    = ["a", "b", "c"]
    enabled = true
  }
}

 

3.7 local 지역 값

variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
  content = "${var.prefix} ${local.name}"
  my_info = {
    age    = 20
    region = "KR"
  }
  my_nums = [1, 2, 3, 4, 5]
}

locals {
  content = "content2" # 중복 선언되었으므로 오류가 발생한다.
}
  • 코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 local (지역 값)
  • 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작
  • 선언된 모듈 내에서만 접근 가능 (입력변수와 차이점)
  • 변수처럼 실행시 입력받을 수 없음
  • 장단점
    • 장점 : 값이나 표현식을 반복적으로 사용할 수 있는 편의 제공
    • 단점
      • 빈번하게 여러 곳에서 사용되는 경우 실제 값에 대한 추적이 어려움
      • 유지 관리 부담 커짐
      • 주의할 것      

3.8 출력 output

output "instance_ip_addr" {
  value = "http://${aws_instance.server.private_ip}"
}
  • 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용된다.
  • 프로그래밍 언어에서 코드 내 요소 간에 제한된 노출을 지원하듯,
    테라폼 모듈 간, 워크스페이스 간 데이터 접근 요소로도 활용 가능
  • 자바의 getter와 비슷한 역할?
  • output의 용도
    • 루트 모듈에서 사용자가 확인하고자 하는 특정 속성 출력
    • 자식 모듈의 특정 값을 정의하고 루트 모듈에서 결과를 참조
    • 서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소
  • 정의시 사용 가능 메타 인수
    • description : 출력 값 설명
    • sensitive : 민감한 출력 값임을 알리고 테라폼의 출력문에서 값 노출을 제한
    • depends_on : value에 담길 값이 특정 구성에 종속성이 있는 경우 생성되는 순서를 임의로 조정
    • precondition : 출력 전에 지정된 조건을 검증
  • 예시
resource "local_file" "abc" {
  content  = "abc123"
  filename = "${path.module}/abc.txt"
}

output "file_id" {
  value = local_file.abc.id
}

output "file_abspath" {
# abspath : 파일 시스템 경로를 포함하는 문자열을 가져와 절대 경로로 변환하는 함수
  value = abspath(local_file.abc.filename)
}
  • 실행
# plan 실행 시, 이미 정해진 속성은 출력을 예측하지만
# file_id는 예측할수 없으므로 known after apply로 표시
terraform init && terraform plan
...
Changes to Outputs:
  + file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
  + file_id      = (known after apply)


# 
terraform apply -auto-approve
...
Outputs:
file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"

#
terraform state list
terraform output

3.9 반복문

list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우
동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.

count : 반복문, 정수 값만큼 리소스나 모듈을 생성

resource "local_file" "abc" {
  count    = 5
  content  = "abc${count.index}"
  filename = "${path.module}/abc${count.index}.txt"
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}

output "filecontent" {
  value = local_file.abc.*.content
}

for_each : 반복문, 선언된 key 값 개수만큼 리소스를 생성

  • each.key , each.value :  인스턴스에 해당하는 map의 key, value
resource "local_file" "abc" {
  for_each = {
    a = "content a"
    b = "content b"
  }
  content  = each.value
  filename = "${path.module}/${each.key}.txt"
}

for : 복합 형식 값의 형태를 변환하는 데 사용 (for_each와 다름에 유의)

variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode(var.names) # 결과 : ["a", "b", "c"]
  filename = "${path.module}/abc.txt"
}

dynamic : 리소스 내부 속성 블록을 동적인 블록으로 생성

variable "names" {
  default = {
    a = "hello a"
    b = "hello b"
    c = "hello c"
  }
}

data "archive_file" "dotfiles" {
  type        = "zip"
  output_path = "${path.module}/dotfiles.zip"

  dynamic "source" {
    for_each = var.names
    content {
      content  = source.value
      filename = "${path.module}/${source.key}.txt"
    }
  }
}
블로그 이미지

감동맨

rkaehdaos의 블로그

,

CHAPTER 1 IaC와 테라폼

1.1 인프라 자동화의 성숙도 변화

인프라 방식

  1. Manual
    1. 문서로 관리
      1. 인프라 관련 모든 정보
      2. 인프라 구성 방법
      3. 인프라 변경 방법
      4. 기존 아키텍처에 대한 내용
    2. 모든 변경을 문서로 남겨야 한다
      그렇지 않으면 기억에 의존한다
    3. 장점?
      1. 정보와 구성을 곧바로 눈으로
        매뉴얼을 보고 확인할 수 있다
      2. 물리적으로 가까이 있는 사람에게
        즉각 정보를 전달 할 수 있다
    4. 단점?
      1. 매뉴얼은 사람이 아닌 인프라가 이해할 수 없다
      2. 결국 사람이 실제 작업을 위한 명령어와 구성 파일을 별도로 준비해야 한다
      3. 변경 사항 반영 어려움 → 재사용성 낮음
      4. 버전 관리 불가능 → 업데이트 위해서는 새로 작성 필요
  2. Script
    1. 반복되는 작업 → 스크립트를 작성해서 자동화(작업자 노하우)
    2. 신규 서버 설정, 특정 기능 수행
    3. 반복 지루한 작업을 줄이고, 미리 정의한 동작 한번에 실행 → 오류를 줄여준다
    4. 단점
      1. 동작하는 코드 증가
      2. 시스템의 응답이나 상태를 고려해야하는 케이스 발생
      3. 스크립트 특성상 상태와 상관없이 실행
      4. 최종 상태가 스크립트 결과와 일치 하지 않는 경우가 종종 발생
  3. VM
    1. 가상화 솔루션 → 가상 머신 → 인프라 운영 관리 편해짐
    2. 가상 머신 이미지 템플릿 → 저장, 반복적 사용 가능
    3. 필요 패키지 설치 + 필요설정 추가한 템플릿 가능
    4. 스냅샷 : 수동적이지만 state를 보관 가능하다
    5. 단점?
      1. 가상화 솔루션 범위에 포함이 되지 않거나
        서로 다른 툴 사용? → 일관된 관리 불가능
      2. 하이퍼 바이저에 의존하게 된다
      3. 이미지 변경 → 수작업
  4. Cloud Infra
    1. 인프라를 더 이상 소유하지 않고
      상품화된 인프라와 데이터 센터를 사용하는 단계
      → 기본적으로 클라우드 리소스를 원격 관리 가능
    2. 빠른 인프라 시작 구성 가능, 뛰어난 확장성
    3. 단점
      1. 클라우드 제공자마다 서로 다른 API
      2. 멀티 클라우드 자동화 위해서는 ?
        → 개별적인 작업이 추가로 필요
  5. Container
    1. 물리적머신 → VM→OS를 가상한 환경 제공
    2. 최근 데브 옵스 흐름 → 더 빠른 성장
    3. k8s같은 오케스트레이션 사용
      → 특정 서버가 아닌 적절한 리소스의 어떤 서버에 배포
      → 리소스 활용율 ↑, 이전보다 더 자동화
    4. 가용한 모든 리소스 확장 가능
    5. 매우 빠른 배포 전략 도입
    6. 역설적인 단점 : 자동화의 역설
      1. 기존 : 물리적 하드웨어가 VM으로 가상화
        → 이를 제어하기 위한 SW 관리 작업이 추가 됨
      2. 컨테이너도 관리와 배포를 위한 제어 시스템 구축
        → 모니터링 노력이 상대적 커짐 → 자동화의 역설
    7. 가장 최신 기술 → 컨테이너 개발/운영 전문 인력이 부족

_1.2 프로세스로서의 자동화

  • 자동화: 각 프로세스 작업을 통합하고 재활용성을 높이는 것이 중요하다
  • 이전 자동화 방향성 : “기술 주도형 접근”
    • 강력한 엔지니어적 협조
    • 빅뱅방식 적용
      • 개발 기간을 정하고
      • 모든 시스템을 일괄적으로
      • 구축 또는 업그레이드 하는 개발 방식
    • 느리고 매뉴얼화된 절차적단계를 거쳐 프로세스 진행
    • 단계별로 파편화된 자동화 → 특정 환경에서만 동작
    • 따라서 내부에서 구축된 리소스 자동화에 적합
  • 새로운 자동화 필요 : CI/CD
    • 고정적이지 않고 추상화된 리소스 → 기존 자동화 재적용 어려움
    • 동적 인프라(like 클라우드)+ 다른 플랫폼 기술 융합 자동화 → 프로세스적인 접근법 필요
    • 작은 규모로 독립성을 유지 관리 → 주기적인 변경과 적용 방식 지향 → 빠른 시장 적응과 장애 극복 능력 획득 필요
    • 코드 표현 방식에 기반한 자동화 설계 → 개별 서비스를 릴리즈 하기 위한 반복 작업 효율화
  • 자동화의 역설 어게인
    • 기존 하드웨어 기반에 비해 시간과 인력 소모 → 자동화 할수 있는 API인터페이스 제공
    • 자동화(가상화↑, 컨테이너화) → 일반적으로 업무량이 줄고 더 나은 워라밸이 가능해야 한다
    • 자동화의 역설 → 무수히 많은 반복과 검증 작업 필요 → 이전보다 더 많은 시간과 노력을 요구하기도 한다
    • 더 아이러니 한 것
      • 인프라 관리, 운영을 위한 단계 방식이 어느 것 하나 없어지지 않음
      • VM, 베어메탈 같은 서버 환경이 현재도 존재
      • 기존 서버 환경보다 더 작은 Edge, IOT 인프라도 관리대상 추가

1.3 IaC의 이해

  • 코드로 인프라를 관리 한다는 것
    • “자유롭게 변경”하고
    • “환경을 이해”하고
    • “반복적으로 동일한 상태”를 만들 수 있다
    • 이에 대한 명세를 별도의 문서로 정리하지 않아도
      인프라가 명확하게 정의되어 남게 된다
  • 좋은 코드의 특징을 먼저 살펴보면
    • 잘 작동함
    • 관리가 쉬움
    • 읽기 쉬움
    • 변경이 쉬움
    • 모듈화 됨
    • 간결하고 명확함
    • 테스트 가능함
    • 효율적임
    • 보기 좋음 -우아함
  • 좋은 코드의 특징과 IaC의 관계
    • 인프라도 좋은 코드처럼 관리가 가능
      • 자동화를 위해 → 문서화
      • 인프라 종속성 분석해서 관리
      • 인프라 자원 있을때마다 변경
      • 결과물이나 결과물 만드는 도구를 관리
    • 인프라를 위한 좋은 코드 : 연습이 필요하다
> IaC (Infrastructure as Code)는 코드로 인프라를 관리하는 것으로,
> 좋은 코드의 특징과 비슷합니다.
> 인프라도 코드처럼 잘 작동하고, 관리가 쉽고, 읽기 쉽고, 변경이 쉽고,
> 모듈화되며, 간결하고 명확하며, 테스트 가능하고, 효율적이며, 우아합니다.
> IaC는 이러한 좋은 코드의 특징을 활용하여 인프라를 자동화하고,
> 문서화하고, 인프라 종속성을 분석하여 관리하며, 인프라 자원이 있을 때마다
> 변경하며, 결과물이나 결과물을 만드는 도구를 관리합니다.
> 이러한 과정 없이, 좋은 코드가 좋은 인프라 자동화로 이어지도록 만들어줍니다.
  • IaC 장점
    • 속도와 효율
    • 버전 관리
    • 협업
    • 재사용성
    • 기술의 자산화
  • IaC 우려 측면
    • 코드 문법 학습 - 새로운 도구를 위한 학습 필요
    • 파이프라인 통합 - 기존 워크플로에 자동화를 위한 수고가 추가로 필요
    • 대상 인프라 이해 필요 - 관리 대상이 되는 인프라의 지식이 필요

1.4 테라폼의 특성

  • 테라폼 : 하시코프사
  • 3가지 철학 - 워크플로에 집중, IaC, 실용주의
    • workflow -Workflows, not technologies
      • solution - 기술, 기능 구현을 위해 만들어진다
      • 어떤 기능 하나가 모든 것을 해결해주지 않음
      • 테라폼 : 개발자/관리자가 일하는 방식과 유사한
        workflow를 만들기 위한 도구로 설계
      • workflow대상: 인프라 구성과 배포, 보안구성, 모니터링 도구 설정 등
      • 어떤 기술이 사용되더라도 workflow를 통해
        환경 변화에 크게 구애 받지 않는다
      • 인프라 환경이 바뀌어도 workflow는 유지될 수 있음
    • IaC
      • 의미: 구현/구성되는 모든 것이 코드로 표현되어야 한다
      • ‘문서화된 실행 가능한 텍스트’
      • 기록 뿐 아니라 버전 관리 대상 → 많은 장점
    • 실용주의(pragmatism)
      • 현실적인 해결을 위해 이상을 재평가 해야하는 상황이 많음
      • 실용주의:
        • 새로운 아이디어와 접근 방식, 기술을 다시 평가
        • 타협이 아닌 이전의 것이 틀릴 수 있다는 사실을 받아들이는 적응 능력
        • 혁신에 있어 매우 중요

책에 나오지 않은데 스터디에서 설명해준 내용

- 페이저 비활성화

export AWS_PAGER=""
 

AWS CLI 페이지 매김 옵션 사용 - AWS Command Line Interface

--starting-token 파라미터는 null이거나 비어있을 수 없습니다. 이전 명령이 NextToken 값을 반환하지 않으면 반환할 더 이상의 항목이 없는 것이기 때문에 명령을 다시 호출할 필요가 없습니다.

docs.aws.amazon.com

  • 명령어 사용시 바로 프롬프트가 떨어지도록 한다.

- Amazon Linux 2 최신 ami id 찾기

  • 자주 업데이트가 된다
  • (참고)이미지 타입 : /aws/service/ami-amazon-linux-latest/amzn2-ami-kernel-5.10-hvm-x86_64-gp2
# amazon owner 이미지들 : 어마어마한 양임.. page마다 나오지 중간에 나오면 된다
#aws ec2 describe-images --owners self amazon

# 위의 내용에서 이미지 ID만 필터링 : 내가 직접 세본 것만 200페이지가 넘어간다. 중간에 나오자.
aws ec2 describe-images --owners self amazon --query 'Images[*].[ImageId]' --output text
# 최신 이미지 리스트 - 머신 타입에 따라 다름에 유의
aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest

#위에서 타입들만 필터링해서 리스트
aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query "Parameters[].Name"
# 위에서 이미지 리스트만
aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query "Parameters[].Value"

- AWS Default VPC 생성

  • 조회방법
# vpc 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq
{
...

# vpc id만 확인
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq '.Vpcs[0].VpcId'
"vpc-3912a952"

# 서브넷 확인
#aws ec2 describe-subnets --filter 'Name=vpc-id,Values=vpc-3912a952' --output table
aws ec2 describe-subnets --filter 'Name=vpc-id,Values=vpc-<자신의VPC ID>' --output table

 

 


도전과제1

Ubuntu 에 apache(httpd) 를 설치하고 index.html 생성(닉네임 출력)하는 userdata 를
작성해서 설정 배포 후 웹 접속
 - 해당 테라폼 코드(파일)를 작성

 

 

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_instance" "example" {
  ami                    = "ami-0c9c942bd7bf113a2"
  instance_type          = "t2.micro"
  vpc_security_group_ids = [aws_security_group.instance.id]

  user_data = <<-EOF
              #!/bin/bash
              apt-get update
              apt-get install -y apache2
              service apache2 start
              echo "My Nickname : rkaehdaos" > /var/www/html/index.html
              EOF
  tags = {
    Name = "Single-WebSrv"
  }
}
resource "aws_security_group" "instance" {
  name = var.security_group_name

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  egress {
    from_port       = 0
    to_port         = 0
    protocol        = "-1"
    cidr_blocks     = ["0.0.0.0/0"]
  }		
}

variable "security_group_name" {
  description = "The name of the security group"
  type        = string
  default     = "terraform-example-instance"
}

output "public_ip" {
  value       = aws_instance.example.public_ip
  description = "The public IP of the Instance"
}
첫 주 스터디를 하기 전에 예습을 했어야 했는데...

정말 내가 이상한지 모르겠는데..
IaC는 그냥 프로그램하고는 다르다..
분명히 스터디 하고 영상 다시 보기 할 떄는 맞아 맞아.. 그거지..했는데.. 막상 도전 과제 하나 하려니..
정말 실습에서 아주 조금 비튼 건데.. 지금 몇 시간을 날린건지..새벽 2시가 되서야.. ㅠ
과제는 과제로 치고.. 진짜로 빡집중하자 진짜로.

자고 일어나서
동영상 2번 다시 볼것!!

아냐.
1번 보고 다시  따라 해볼 것!

 

블로그 이미지

감동맨

rkaehdaos의 블로그

,

asdfadsaf

2023. 2. 28. 09:04

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

fff

2023. 2. 21. 16:58

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

ffd

2023. 2. 21. 16:58

보호되어 있는 글입니다.
내용을 보시려면 비밀번호를 입력하세요.

3주차

카테고리 없음 2023. 2. 7. 10:22
블로그 이미지

감동맨

rkaehdaos의 블로그

,