Terraform 101 - 4기 (1주차)

김성중·2024년 6월 15일
1

Terraform

목록 보기
1/7

가시다(gasida) 님이 진행하는 Terraform T101 4기 실습 스터디 게시글입니다.
책 '테라폼으로 시작하는 IaC' 를 참고하였고, 스터디하면서 도움이 될 내용들을 추가적으로 정리하였습니다.

1주차 - 기본 사용 1/3 노션 내용중 오탈자 수정 의견

2.3 (참고) CLI 구성 파일
- 파일 내에서 사용 가능한 설정 값
    - plugin_cache_dir: terraform init         
        → 활용 시 프로바이더의 다운로드 시간되 디스크 공간을 줄일 수 있음
(수정의견)        
        → Workspace를 복수로 사용 시 공통으로 활용되어 저장공간과 다운로드 시간을 단축할 수 있음

Terraform은

  • Terraform은 하시코프에서 제공하는 인프라 환경 배포 툴입니다.

  • Terraform은 Infrastructure as a Code (코드로서 인프라를 정의하기), Reproducible Infrastructure (재현 가능한 인프라) 특성을 가지고 있음

  • AWS든, GCP든, Azure든 클라우드 인프라 환경을 코드로서 정의해 사용함으로써 동일한 인프라를 재현할 수 있음
    클라우드 인프라를 코드로 명시적으로 정의하니 수십번을 실행해도 동일한 환경이 생성됨

  • Terraform - AWS 리소스 생성 및 관리

    terraform {
      required_providers {
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
      }
    }
    
    # Configure the AWS Provider
    provider "aws" {
      region = "us-east-1"
    }
    
    # Create a VPC
    resource "aws_vpc" "example" {
      cidr_block = "10.0.0.0/16"
    }
  • 용어설명

    - 프로비저닝[Provisioning]
    어떤 프로세스나 서비스를 실행하기 위한 준비 단계. 주로 테라폼은 네트워크나 컴퓨팅 자원을 준비하는 작업을 주로 다룸
    
    - 프로바이더[Provider]
    테라폼과 외부 서비스를 연결해주는 기능을 하는 모듈. 테라폼으로 서비스에 접근하여 자원 생성등의 작업을 하기 위해서는 Provider가 먼저 셋업되어야 한다. AWS외 GCP, Azure와 같은 범용 클라우드 서비스 등 다양한 서비스를 지원
    
    - 자원[Resource]
    특정 프로바이더가 제공해주는 조작 가능한 대상의 최소 단위. 예를 들어 AWS 프로바이더는 aws_instance 리소스 타입을 제공하고, 이 리소스 타입을 사용해 Amazon EC2의 가상 머신 리소스를 선언하고 조작하는 것이 가능
    - HCL[Hashicorp Configuration Language] 
    HCL은 테라폼에서 사용하는 설정 언어이다. 파일의 확장자는 .tf를 사용하며 테라폼에서 모든 설정과 리소스 선언은 HCL을 사용해 이루어짐
    
    - 계획[Plan]
    프로젝트 디렉터리 아래의 모든 .tf 파일의 내용을 실제로 적용 가능한지 확인하는 작업. 명령어를 실행하면 어떤 리소스가 생성되고, 수정되고, 삭제될지 계획을 보여줌
    
    - 적용[Apply]
    Plan이 실제로 적용가능한지 확인하는 작업이라면, Apply는 실제로 적용하는 명령어
    
    - 삭제[Destroy]
    tf파일로 생성된 모든 리소스들을 제거
  • Terraform 주요 명령어

    ❯ terraform init : 프로젝트를 초기화 하며 생성된 프로바이더에 맞춰 필요한 플러그인들이 설치
    ❯ terraform plan : 변경되는 상황을 확인(+ 추가, - 삭제, ~ 변경) 시켜줌
    ❯ terraform appy : 변경사항 적용됨
    ❯ terraform state list : 생성된 자원상태 조회
    ❯ terraform output : output으로 지정한 결과값을 조회
    ❯ terraform destroy : 자원 삭제

Terraform 실행 환경 구성

  • tfenv로 Terraform binary 설치 추천(실습환경: macOS)

    # tfenv 설치
    ❯ brew install tfenv
    
    # 설치 가능 버전 리스트 확인
    ❯ tfenv list-remote
    
    # 테라폼 1.8.5 버전 설치
    ❯ tfenv install 1.8.5
    
    # 테라폼 1.5.1 버전 사용 설정 
    ❯ tfenv use 1.8.5
    
    # tfenv로 설치한 버전 확인
    ❯ tfenv list
    
    # 테라폼 버전 정보 확인
    ❯ terraform version
    Terraform v1.8.5
    on darwin_arm64
    + provider registry.terraform.io/hashicorp/local v2.5.1
    
    # 자동완성
    ❯ terraform -install-autocomplete
    
    # 참고 .zshrc 에 아래 추가됨cat ~/.zshrc
    ...
    autoload -U +X bashcompinit && bashcompinit
    complete -o nospace -C /usr/local/bin/terraform terraform
  • Study에서는 1.8.5 을 사용하기로 함

  • IDE : vscode 사용 - 자동 저장 설정 추천 (Editor 창에서 코드 수정 후 터미널창에서 명령어 실행 시 편리함)

  • awscli 는 2.x 이상 버전 사용 필요

  • awscli Page Display - Disable 방법

    • export AWS_PAGER=""
  • (Tip) 실습간 Terraform 빠른 명령어 실행을 위해 Alias 명령어 활용

    alias tfb="terraform init && terraform validate && terraform plan -out tfplan && terraform apply tfplan"alias tfd="terraform destroy -auto-approve && rm -rf .terraform* && rm tfplan"
  • 최신 Amazon Linux 2 AMI ID 가져오기

    # AWS CLIAL2ID=`aws ec2 describe-images --owners amazon --filters "Name=name,Values=amzn2-ami-hvm-2.0.*-x86_64-gp2" "Name=state,Values=available" --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId]' --output text`echo $AL2ID
    ami-0ebb3f23647161078
    
    # SSM 이용
    ❯ aws ssm get-parameters-by-path --path /aws/service/ami-amazon-linux-latest --query 'Parameters[*].[Value, Name]' --output text
    ami-0450ec15bbf42649e   /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-arm64
    ami-0edc5427d49d09d2a   /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64
    ...

실습

AWS Credential 설정

# IAM Console에서 devops 계정 생성시 AdministratorAccess 권한부여, Access key 생성
# CLI Credential 구성
❯ aws configure
AWS Access Key ID [*************]:
AWS Secret Access Key [****************]:
Default region name [ap-northeast-2]:
Default output format [json]:

❯ aws sts get-caller-identity
{
    "UserId": "AIDA**",
    "Account": "53****617837*",
    "Arn": "arn:aws:iam::53****617837*:user/devops"
}

Default VPC 생성

# Default VPC 없는 상태 확인
❯ aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq '.Vpcs[0].VpcId'
null

# Default VPC 실행 명령어
❯ aws ec2 create-default-vpc

# 생성된 VPC 확인
❯ aws ec2 describe-vpcs --filter 'Name=isDefault,Values=true' | jq
{
  "Vpcs": [
    {
      "CidrBlock": "172.31.0.0/16",
      "DhcpOptionsId": "dopt-6bf6b600",
      "State": "available",
      "VpcId": "vpc-026116460be671c3d",
      "OwnerId": "53****617837",
      "InstanceTenancy": "default",
      "CidrBlockAssociationSet": [
        {
          "AssociationId": "vpc-cidr-assoc-048ffb7e67b83afae",
          "CidrBlock": "172.31.0.0/16",
          "CidrBlockState": {
            "State": "associated"
          }
        }
      ],
      "IsDefault": true
    }
  ]
}

# Default Subnet 확인
❯ aws ec2 describe-subnets \
    --filters "Name=vpc-id,Values=vpc-026116460be671c3d"
{
    "Subnets": [
        {
            "AvailabilityZone": "ap-northeast-2a",
            "AvailabilityZoneId": "apne2-az1",
            "AvailableIpAddressCount": 4091,
            "CidrBlock": "172.31.0.0/20",
            "DefaultForAz": true,
            "MapPublicIpOnLaunch": true,
            "MapCustomerOwnedIpOnLaunch": false,
            "State": "available",
            "SubnetId": "subnet-0dc5a51c96d2db619",
            "VpcId": "vpc-026116460be671c3d",
            "OwnerId": "53****617837",
            "AssignIpv6AddressOnCreation": false,
            "Ipv6CidrBlockAssociationSet": [],
            "SubnetArn": "arn:aws:ec2:ap-northeast-2:53****617837:subnet/subnet-0dc5a51c96d2db619",
            "EnableDns64": false,
            "Ipv6Native": false,
            "PrivateDnsNameOptionsOnLaunch": {
                "HostnameType": "ip-name",
                "EnableResourceNameDnsARecord": false,
                "EnableResourceNameDnsAAAARecord": false
            }
        },
        {
            "AvailabilityZone": "ap-northeast-2b",
            "AvailabilityZoneId": "apne2-az2",
 ...
}

EC2 생성 & 삭제

  • EC2 생성 모니터링

    # 별도 터미널 창을 생성 후 EC2 생성 모니터링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
  • EC2 생성

    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami           = "$AL2ID"
      instance_type = "t2.micro"
    
      tags = {
        Name = "t101-study"
      }
    
    }
    EOT
    
    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be updated in-place
      ~ resource "aws_instance" "example" {
            id                                   = "i-0a238bacf2447df7e"
          ~ tags                                 = {
              + "Name" = "t101-study"
            }
          ~ tags_all                             = {
              + "Name" = "t101-study"
            }
            # (38 unchanged attributes hidden)
    
            # (8 unchanged blocks hidden)
        }
    
    Plan: 0 to add, 1 to change, 0 to destroy.
    
    ───────────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_instance.example: Modifying... [id=i-0a238bacf2447df7e]
    aws_instance.example: Modifications complete after 1s [id=i-0a238bacf2447df7e]
    
    Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
  • 생성된 EC2 TAG 수정

    • Terraform Tag 수정 한다고 EC2가 교체 되지는 않음
    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami           = "$AL2ID"
      instance_type = "t2.micro"
    
      tags = {
        Name = "t101-study"
        Owner = "sjkim"
      }
    
    }
    EOT
    
    ❯ cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami           = "$AL2ID"
      instance_type = "t2.micro"
    
      tags = {
        Name = "t101-study"
        Owner = "sjkim"
      }
    
    }
    EOT
    
    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be updated in-place
      ~ resource "aws_instance" "example" {
            id                                   = "i-0a238bacf2447df7e"
          ~ tags                                 = {
                "Name"  = "t101-study"
              + "Owner" = "sjkim"
            }
          ~ tags_all                             = {
              + "Owner" = "sjkim"
                # (1 unchanged element hidden)
            }
            # (38 unchanged attributes hidden)
    
            # (8 unchanged blocks hidden)
        }
    
    Plan: 0 to add, 1 to change, 0 to destroy.
    
    ───────────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_instance.example: Modifying... [id=i-0a238bacf2447df7e]
    aws_instance.example: Modifications complete after 1s [id=i-0a238bacf2447df7e]
    
    Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
  • EC2 삭제

    ❯ terraform destroy -auto-approve
    aws_instance.example: Refreshing state... [id=i-0a238bacf2447df7e]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      - destroy
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be destroyed
      - resource "aws_instance" "example" {
          - ami                                  = "ami-0ebb3f23647161078" -> null
          - arn                                  = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0a238bacf2447df7e" -> null
          - associate_public_ip_address          = true -> null
          - availability_zone                    = "ap-northeast-2a" -> null
          - cpu_core_count                       = 1 -> null
          - cpu_threads_per_core                 = 1 -> null
          - disable_api_stop                     = false -> null
          - disable_api_termination              = false -> null
          - ebs_optimized                        = false -> null
          - get_password_data                    = false -> null
          - hibernation                          = false -> null
          - id                                   = "i-0a238bacf2447df7e" -> null
          - instance_initiated_shutdown_behavior = "stop" -> null
          - instance_state                       = "running" -> null
          - instance_type                        = "t2.micro" -> null
          - ipv6_address_count                   = 0 -> null
          - ipv6_addresses                       = [] -> null
          - monitoring                           = false -> null
          - placement_partition_number           = 0 -> null
          - primary_network_interface_id         = "eni-0dadaa55ff26aba61" -> null
          - private_dns                          = "ip-172-31-9-51.ap-northeast-2.compute.internal" -> null
          - private_ip                           = "172.31.9.51" -> null
          - public_dns                           = "ec2-52-79-105-86.ap-northeast-2.compute.amazonaws.com" -> null
          - public_ip                            = "52.79.105.86" -> null
          - secondary_private_ips                = [] -> null
          - security_groups                      = [
              - "default",
            ] -> null
          - source_dest_check                    = true -> null
          - subnet_id                            = "subnet-0dc5a51c96d2db619" -> null
          - tags                                 = {
              - "Name"  = "t101-study"
              - "Owner" = "sjkim"
            } -> null
          - tags_all                             = {
              - "Name"  = "t101-study"
              - "Owner" = "sjkim"
            } -> null
          - tenancy                              = "default" -> null
          - user_data_replace_on_change          = false -> null
          - vpc_security_group_ids               = [
              - "sg-0e6cdaaea796e7bbd",
            ] -> null
            # (8 unchanged attributes hidden)
    
          - capacity_reservation_specification {
              - capacity_reservation_preference = "open" -> null
            }
    
          - cpu_options {
              - core_count       = 1 -> null
              - threads_per_core = 1 -> null
                # (1 unchanged attribute hidden)
            }
    
          - credit_specification {
              - cpu_credits = "standard" -> null
            }
    
          - enclave_options {
              - enabled = false -> null
            }
    
          - maintenance_options {
              - auto_recovery = "default" -> null
            }
    
          - metadata_options {
              - http_endpoint               = "enabled" -> null
              - http_protocol_ipv6          = "disabled" -> null
              - http_put_response_hop_limit = 1 -> null
              - http_tokens                 = "optional" -> null
              - instance_metadata_tags      = "disabled" -> null
            }
    
          - private_dns_name_options {
              - enable_resource_name_dns_a_record    = false -> null
              - enable_resource_name_dns_aaaa_record = false -> null
              - hostname_type                        = "ip-name" -> null
            }
    
          - root_block_device {
              - delete_on_termination = true -> null
              - device_name           = "/dev/xvda" -> null
              - encrypted             = true -> null
              - iops                  = 100 -> null
              - kms_key_id            = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a" -> null
              - tags                  = {} -> null
              - tags_all              = {} -> null
              - throughput            = 0 -> null
              - volume_id             = "vol-050af0ecf73e052ff" -> null
              - volume_size           = 8 -> null
              - volume_type           = "gp2" -> null
            }
        }
    
    Plan: 0 to add, 0 to change, 1 to destroy.
    aws_instance.example: Destroying... [id=i-0a238bacf2447df7e]
    aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 10s elapsed]
    aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 20s elapsed]
    aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 30s elapsed]
    aws_instance.example: Still destroying... [id=i-0a238bacf2447df7e, 40s elapsed]
    aws_instance.example: Destruction complete after 40s
    
    Destroy complete! Resources: 1 destroyed.

Terraform 주요 명령어

  • terraform state : Terraform에 의해 생선된 자원 목록과 내용 조회

    ❯ terraform state list
    local_file.abc
    
    ❯ terraform state show local_file.abc
    # local_file.abc:
    resource "local_file" "abc" {
        content              = "step7 file ok"
        content_base64sha256 = "AIR3m2m5d5xNW5ovCHjOSHaCz6wUf8bYGfjNCcnZKho="
        content_base64sha512 = "DgtP0+WsBErpthJ07+GHq2uO4bdaNADPBWepAnDoTtX5kozHIz8WGkLMLoRM/zoJ8pSlLF2IMC8Y1bFCydUZYw=="
        content_md5          = "e7ce0fcc3a0760160958f8a55cdcedf0"
        content_sha1         = "653353c86e0b78f26dfe85f8e86f4f9d07c0d505"
        content_sha256       = "0084779b69b9779c4d5b9a2f0878ce487682cfac147fc6d819f8cd09c9d92a1a"
        content_sha512       = "0e0b4fd3e5ac044ae9b61274efe187ab6b8ee1b75a3400cf0567a90270e84ed5f9928cc7233f161a42cc2e844cff3a09f294a52c5d88302f18d5b142c9d51963"
        directory_permission = "0777"
        file_permission      = "0777"
        filename             = "./step7.txt"
        id                   = "653353c86e0b78f26dfe85f8e86f4f9d07c0d505"
    }
  • 특정 리소스만 재생성

    • terraform -replace=local_file.abc -auto-approbe

      ❯ terraform apply -replace=local_file.abc -auto-approve
      local_file.dev: Refreshing state... [id=15f946cb27f0730866cefea4f0923248d9366cb0]
      local_file.abc: Refreshing state... [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
      
      Terraform used the selected providers to generate the following execution plan. Resource
      actions are indicated with the following symbols:
      -/+ destroy and then create replacement
      
      Terraform will perform the following actions:
      
        # local_file.abc will be replaced, as requested
      -/+ resource "local_file" "abc" {
            ~ content_base64sha256 = "U+Dv8yBGJvPiVspjZXLXzN+OtaGQyd76P6VnvGOGa3Y=" -> (known after apply)
            ~ content_base64sha512 = "J873Ugx5HyDEnYsjdX8iMBjn4I3gft82udsl3lNeWEoqwmNE3mvUZNNz4QRqQ3iaT5SW1y9p3e1Xn2txEBapKg==" -> (known after apply)
            ~ content_md5          = "4edb03f55c86d5e0a76f5627fa506bbf" -> (known after apply)
            ~ content_sha1         = "5678fb68a642f3c6c8004c1bdc21e7142087287b" -> (known after apply)
            ~ content_sha256       = "53e0eff3204626f3e256ca636572d7ccdf8eb5a190c9defa3fa567bc63866b76" -> (known after apply)
            ~ content_sha512       = "27cef7520c791f20c49d8b23757f223018e7e08de07edf36b9db25de535e584a2ac26344de6bd464d373e1046a43789a4f9496d72f69dded579f6b711016a92a" -> (known after apply)
            ~ id                   = "5678fb68a642f3c6c8004c1bdc21e7142087287b" -> (known after apply)
              # (4 unchanged attributes hidden)
          }
      
      Plan: 1 to add, 0 to change, 1 to destroy.
      local_file.abc: Destroying... [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
      local_file.abc: Destruction complete after 0s
      local_file.abc: Creating...
      local_file.abc: Creation complete after 0s [id=5678fb68a642f3c6c8004c1bdc21e7142087287b]
      
      Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
      
      ❯ ls -al
      total 64
      drwxr-xr-x  10 sjkim  staff   320 Jun 15 17:36 .
      drwxr-xr-x   5 sjkim  staff   160 Jun 15 17:11 ..
      drwxr-xr-x   3 sjkim  staff    96 Jun 15 17:15 .terraform
      -rw-r--r--   1 sjkim  staff  1153 Jun 15 17:15 .terraform.lock.hcl
      -rwxr-xr-x   1 sjkim  staff     4 Jun 15 17:36 abc.txt
      -rwxr-xr-x   1 sjkim  staff     4 Jun 15 17:33 def.txt
      -rw-r--r--   1 sjkim  staff   175 Jun 15 17:32 main.tf
      -rw-r--r--   1 sjkim  staff  2962 Jun 15 17:36 terraform.tfstate
      -rw-r--r--   1 sjkim  staff  2962 Jun 15 17:36 terraform.tfstate.backup
      -rw-r--r--   1 sjkim  staff  4742 Jun 15 17:35 tfplan

Ubuntu 웹서버 생성

  • busybox 이용하여 단순 웹서버 동작 가능 (nohup busybox httpd -f -p 8080 & )

  • Ubuntu 22.04 최신 AMI ID 확인

    ❯ aws ec2 describe-images --owners 099720109477 \
        --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" "Name=state,Values=available" \
        --query 'Images|sort_by(@, &CreationDate)[-1].[ImageId, Name]' --output text
    ami-0bcdae8006538619a   ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-20240614
    
    # 변수 지정
    UBUNTUID=ami-0bcdae8006538619a
  • Ubuntu EC2 생성

    echo $UBUNTUID
    ami-0bcdae8006538619a
    ❯ cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "$UBUNTUID"
      instance_type          = "t2.micro"
    
      user_data = <<-EOF
                  #!/bin/bash
                  echo "Hello, T101 Study" > index.html
                  nohup busybox httpd -f -p 8080 &
                  EOF
    
      tags = {
        Name = "terraform-Study-101"
      }
    }
    EOT
    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    
    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_instance.example will be created
      + resource "aws_instance" "example" {
          + ami                                  = "ami-0bcdae8006538619a"
          + arn                                  = (known after apply)
          + associate_public_ip_address          = (known after apply)
          + 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_lifecycle                   = (known after apply)
          + instance_state                       = (known after apply)
          + instance_type                        = "t2.micro"
          + ipv6_address_count                   = (known after apply)
          + ipv6_addresses                       = (known after apply)
          + key_name                             = (known after apply)
          + 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
          + spot_instance_request_id             = (known after apply)
          + subnet_id                            = (known after apply)
          + tags                                 = {
              + "Name" = "terraform-Study-101"
            }
          + tags_all                             = {
              + "Name" = "terraform-Study-101"
            }
          + tenancy                              = (known after apply)
          + user_data                            = "d91ca31904077f0b641b5dd5a783401396ffbf3f"
          + user_data_base64                     = (known after apply)
          + user_data_replace_on_change          = false
          + vpc_security_group_ids               = (known after apply)
        }
    
    Plan: 1 to add, 0 to change, 0 to destroy.
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_instance.example: Creating...
    aws_instance.example: Still creating... [10s elapsed]
    aws_instance.example: Still creating... [20s elapsed]
    aws_instance.example: Still creating... [30s elapsed]
    aws_instance.example: Still creating... [40s elapsed]
    aws_instance.example: Still creating... [50s elapsed]
    aws_instance.example: Creation complete after 51s [id=i-0e849c8e935e39294]
    
    Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
  • Terraform state 확인

    ❯ terraform state list
    aws_instance.example
    
    ❯ terraform state show aws_instance.example
    # aws_instance.example:
    resource "aws_instance" "example" {
        ami                                  = "ami-0bcdae8006538619a"
        arn                                  = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0e849c8e935e39294"
        associate_public_ip_address          = true
        availability_zone                    = "ap-northeast-2a"
        cpu_core_count                       = 1
        cpu_threads_per_core                 = 1
        disable_api_stop                     = false
        disable_api_termination              = false
        ebs_optimized                        = false
        get_password_data                    = false
        hibernation                          = false
        host_id                              = null
        iam_instance_profile                 = null
        id                                   = "i-0e849c8e935e39294"
        instance_initiated_shutdown_behavior = "stop"
        instance_lifecycle                   = null
        instance_state                       = "running"
        instance_type                        = "t2.micro"
        ipv6_address_count                   = 0
        ipv6_addresses                       = []
        key_name                             = null
        monitoring                           = false
        outpost_arn                          = null
        password_data                        = null
        placement_group                      = null
        placement_partition_number           = 0
        primary_network_interface_id         = "eni-0ecb833a51d9976d1"
        private_dns                          = "ip-172-31-14-154.ap-northeast-2.compute.internal"
        private_ip                           = "172.31.14.154"
        public_dns                           = "ec2-54-180-132-65.ap-northeast-2.compute.amazonaws.com"
        public_ip                            = "54.180.132.65"
        secondary_private_ips                = []
        security_groups                      = [
            "default",
        ]
        source_dest_check                    = true
        spot_instance_request_id             = null
        subnet_id                            = "subnet-0dc5a51c96d2db619"
        tags                                 = {
            "Name" = "terraform-Study-101"
        }
        tags_all                             = {
            "Name" = "terraform-Study-101"
        }
        tenancy                              = "default"
        user_data                            = "d91ca31904077f0b641b5dd5a783401396ffbf3f"
        user_data_replace_on_change          = false
        vpc_security_group_ids               = [
            "sg-0e6cdaaea796e7bbd",
        ]
    
        capacity_reservation_specification {
            capacity_reservation_preference = "open"
        }
    
        cpu_options {
            amd_sev_snp      = null
            core_count       = 1
            threads_per_core = 1
        }
    
        credit_specification {
            cpu_credits = "standard"
        }
    
        enclave_options {
            enabled = false
        }
    
        maintenance_options {
            auto_recovery = "default"
        }
    
        metadata_options {
            http_endpoint               = "enabled"
            http_protocol_ipv6          = "disabled"
            http_put_response_hop_limit = 1
            http_tokens                 = "optional"
            instance_metadata_tags      = "disabled"
        }
    
        private_dns_name_options {
            enable_resource_name_dns_a_record    = false
            enable_resource_name_dns_aaaa_record = false
            hostname_type                        = "ip-name"
        }
    
        root_block_device {
            delete_on_termination = true
            device_name           = "/dev/sda1"
            encrypted             = true
            iops                  = 100
            kms_key_id            = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a"
            tags                  = {}
            tags_all              = {}
            throughput            = 0
            volume_id             = "vol-0ccc4d8689ff8f341"
            volume_size           = 8
            volume_type           = "gp2"
        }
    }
  • 웹서버 접속 시도

    PIP=54.180.132.65
    ❯ while true; do curl --connect-timeout 1  http://$PIP:8080/ ; echo "------------------------------"; date; sleep 1; done
    curl: (28) Failed to connect to 54.180.132.65 port 8080 after 1005 ms: Timeout was reached
    ------------------------------
    Sat Jun 15 17:56:12 KST 2024
    curl: (28) Failed to connect to 54.180.132.65 port 8080 after 1004 ms: Timeout was reached
    ------------------------------
    Sat Jun 15 17:56:14 KST 2024
  • Security Group 정책 추가 후 코드 재접속 테스트

    ❯ cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "$UBUNTUID"
      instance_type          = "t2.micro"
      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 8080 &
                  EOF
    
      tags = {
        Name = "Single-WebSrv"
      }
    }
    
    resource "aws_security_group" "instance" {
      name = var.security_group_name
    
      ingress {
        from_port   = 8080
        to_port     = 8080
        protocol    = "tcp"
        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"
    }
    EOT
    
    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    aws_instance.example: Refreshing state... [id=i-0e849c8e935e39294]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      + create
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be updated in-place
      ~ resource "aws_instance" "example" {
            id                                   = "i-0e849c8e935e39294"
          ~ tags                                 = {
              ~ "Name" = "terraform-Study-101" -> "Single-WebSrv"
            }
          ~ tags_all                             = {
              ~ "Name" = "terraform-Study-101" -> "Single-WebSrv"
            }
          ~ vpc_security_group_ids               = [
              - "sg-0e6cdaaea796e7bbd",
            ] -> (known after apply)
            # (38 unchanged attributes hidden)
    
            # (8 unchanged blocks hidden)
        }
    
      # aws_security_group.instance will be created
      + resource "aws_security_group" "instance" {
          + arn                    = (known after apply)
          + description            = "Managed by Terraform"
          + egress                 = (known after apply)
          + id                     = (known after apply)
          + ingress                = [
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 8080
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "tcp"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 8080
                    # (1 unchanged attribute hidden)
                },
            ]
          + name                   = "terraform-example-instance"
          + name_prefix            = (known after apply)
          + owner_id               = (known after apply)
          + revoke_rules_on_delete = false
          + tags_all               = (known after apply)
          + vpc_id                 = (known after apply)
        }
    
    Plan: 1 to add, 1 to change, 0 to destroy.
    
    Changes to Outputs:
      + public_ip = "54.180.132.65"
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_security_group.instance: Creating...
    aws_security_group.instance: Creation complete after 2s [id=sg-050429d8493a5e0fa]
    aws_instance.example: Modifying... [id=i-0e849c8e935e39294]
    aws_instance.example: Modifications complete after 2s [id=i-0e849c8e935e39294]
    
    Apply complete! Resources: 1 added, 1 changed, 0 destroyed.
    
    Outputs:
    
    public_ip = "54.180.132.65"
    
    ❯ PIP=$(terraform output -raw public_ip)
    ❯ while true; do curl --connect-timeout 1  http://$PIP:8080/ ; echo "------------------------------"; date; sleep 1; done
    
    Hello, T101 Study
    ------------------------------
    Sat Jun 15 18:06:59 KST 2024
    Hello, T101 Study
    ------------------------------
    Sat Jun 15 18:07:00 KST 2024
  • Terraform graph

    • vs code > 확장에서 graphviz 설치
    • Terraform graph 명령어 실행
    ❯ terraform graph
    digraph G {
      rankdir = "RL";
      node [shape = rect, fontname = "sans-serif"];
      "aws_instance.example" [label="aws_instance.example"];
      "aws_security_group.instance" [label="aws_security_group.instance"];
      "aws_instance.example" -> "aws_security_group.instance";
    }
    ❯ terraform graph > graph.dot
    
    • graph 확인 > 파일 선택 후 오른쪽 상단 DOT 클릭

    • EC2 User Data 수정 시 Relacement 가 되버림

    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "$UBUNTUID"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data = <<-EOF
                  #!/bin/bash
                  echo "Hello, T101 Study 9090" > index.html
                  nohup busybox httpd -f -p 9090 &
                  EOF
    
      user_data_replace_on_change = true
    
      tags = {
        Name = "Single-WebSrv"
      }
    }
    
    resource "aws_security_group" "instance" {
      name = var.security_group_name
    
      ingress {
        from_port   = 9090
        to_port     = 9090
        protocol    = "tcp"
        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"
    }
    EOT
    
    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
    aws_instance.example: Refreshing state... [id=i-0e849c8e935e39294]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      ~ update in-place
    -/+ destroy and then create replacement
    
    Terraform will perform the following actions:
    
      # aws_instance.example must be replaced
    -/+ resource "aws_instance" "example" {
          ~ arn                                  = "arn:aws:ec2:ap-northeast-2:53****617837:instance/i-0e849c8e935e39294" -> (known after apply)
          ~ associate_public_ip_address          = true -> (known after apply)
          ~ availability_zone                    = "ap-northeast-2a" -> (known after apply)
          ~ cpu_core_count                       = 1 -> (known after apply)
          ~ cpu_threads_per_core                 = 1 -> (known after apply)
          ~ disable_api_stop                     = false -> (known after apply)
          ~ disable_api_termination              = false -> (known after apply)
          ~ ebs_optimized                        = false -> (known after apply)
          - hibernation                          = false -> null
          + host_id                              = (known after apply)
          + host_resource_group_arn              = (known after apply)
          + iam_instance_profile                 = (known after apply)
          ~ id                                   = "i-0e849c8e935e39294" -> (known after apply)
          ~ instance_initiated_shutdown_behavior = "stop" -> (known after apply)
          + instance_lifecycle                   = (known after apply)
          ~ instance_state                       = "running" -> (known after apply)
          ~ ipv6_address_count                   = 0 -> (known after apply)
          ~ ipv6_addresses                       = [] -> (known after apply)
          + key_name                             = (known after apply)
          ~ monitoring                           = false -> (known after apply)
          + outpost_arn                          = (known after apply)
          + password_data                        = (known after apply)
          + placement_group                      = (known after apply)
          ~ placement_partition_number           = 0 -> (known after apply)
          ~ primary_network_interface_id         = "eni-0ecb833a51d9976d1" -> (known after apply)
          ~ private_dns                          = "ip-172-31-14-154.ap-northeast-2.compute.internal" -> (known after apply)
          ~ private_ip                           = "172.31.14.154" -> (known after apply)
          ~ public_dns                           = "ec2-54-180-132-65.ap-northeast-2.compute.amazonaws.com" -> (known after apply)
          ~ public_ip                            = "54.180.132.65" -> (known after apply)
          ~ secondary_private_ips                = [] -> (known after apply)
          ~ security_groups                      = [
              - "terraform-example-instance",
            ] -> (known after apply)
          + spot_instance_request_id             = (known after apply)
          ~ subnet_id                            = "subnet-0dc5a51c96d2db619" -> (known after apply)
            tags                                 = {
                "Name" = "Single-WebSrv"
            }
          ~ tenancy                              = "default" -> (known after apply)
          ~ user_data                            = "d91ca31904077f0b641b5dd5a783401396ffbf3f" -> "efd980ae7b7a847bd5772af117f6a53cc7824934" # forces replacement
          + user_data_base64                     = (known after apply)
          ~ user_data_replace_on_change          = false -> true
            # (6 unchanged attributes hidden)
    
          - capacity_reservation_specification {
              - capacity_reservation_preference = "open" -> null
            }
    
          - cpu_options {
              - core_count       = 1 -> null
              - threads_per_core = 1 -> null
                # (1 unchanged attribute hidden)
            }
    
          - credit_specification {
              - cpu_credits = "standard" -> null
            }
    
          - enclave_options {
              - enabled = false -> null
            }
    
          - maintenance_options {
              - auto_recovery = "default" -> null
            }
    
          - metadata_options {
              - http_endpoint               = "enabled" -> null
              - http_protocol_ipv6          = "disabled" -> null
              - http_put_response_hop_limit = 1 -> null
              - http_tokens                 = "optional" -> null
              - instance_metadata_tags      = "disabled" -> null
            }
    
          - private_dns_name_options {
              - enable_resource_name_dns_a_record    = false -> null
              - enable_resource_name_dns_aaaa_record = false -> null
              - hostname_type                        = "ip-name" -> null
            }
    
          - root_block_device {
              - delete_on_termination = true -> null
              - device_name           = "/dev/sda1" -> null
              - encrypted             = true -> null
              - iops                  = 100 -> null
              - kms_key_id            = "arn:aws:kms:ap-northeast-2:53****617837:key/50a438fb-1baf-4094-b63e-706141a94d9a" -> null
              - tags                  = {} -> null
              - tags_all              = {} -> null
              - throughput            = 0 -> null
              - volume_id             = "vol-0ccc4d8689ff8f341" -> null
              - volume_size           = 8 -> null
              - volume_type           = "gp2" -> null
            }
        }
    
      # aws_security_group.instance will be updated in-place
      ~ resource "aws_security_group" "instance" {
            id                     = "sg-050429d8493a5e0fa"
          ~ ingress                = [
              - {
                  - cidr_blocks      = [
                      - "0.0.0.0/0",
                    ]
                  - from_port        = 8080
                  - ipv6_cidr_blocks = []
                  - prefix_list_ids  = []
                  - protocol         = "tcp"
                  - security_groups  = []
                  - self             = false
                  - to_port          = 8080
                    # (1 unchanged attribute hidden)
                },
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 9090
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "tcp"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 9090
                    # (1 unchanged attribute hidden)
                },
            ]
            name                   = "terraform-example-instance"
            tags                   = {}
            # (8 unchanged attributes hidden)
        }
    
    Plan: 1 to add, 1 to change, 1 to destroy.
    
    Changes to Outputs:
      ~ public_ip = "54.180.132.65" -> (known after apply)
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_instance.example: Destroying... [id=i-0e849c8e935e39294]
    aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 10s elapsed]
    aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 20s elapsed]
    aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 30s elapsed]
    aws_instance.example: Still destroying... [id=i-0e849c8e935e39294, 40s elapsed]
    aws_instance.example: Destruction complete after 40s
    aws_security_group.instance: Modifying... [id=sg-050429d8493a5e0fa]
    aws_security_group.instance: Modifications complete after 1s [id=sg-050429d8493a5e0fa]
    aws_instance.example: Creating...
    aws_instance.example: Still creating... [10s elapsed]
    aws_instance.example: Still creating... [20s elapsed]
    aws_instance.example: Still creating... [30s elapsed]
    aws_instance.example: Creation complete after 32s [id=i-07df1a72af3369d83]
    
    Apply complete! Resources: 1 added, 1 changed, 1 destroyed.
    
    Outputs:
    
    public_ip = "52.79.199.167"
    
    ❯ PIP=$(terraform output -raw public_ip)
    
    ❯ while true; do curl --connect-timeout 1  http://$PIP:9090/ ; echo "------------------------------"; date; sleep 1; done
    Hello, T101 Study 9090
    ------------------------------
    Sat Jun 15 18:24:25 KST 2024
    Hello, T101 Study 9090
    ------------------------------
    Sat Jun 15 18:24:26 KST 2024
    Hello, T101 Study 9090
    ------------------------------
    Sat Jun 15 18:24:27 KST 2024
  • User Data, SG 정책 삭제후 생성되지 않도록 하는 방법

    • aws_instance Block에 user_data_replace_on_change = false 코드 추가 << EC2 교체 대신 재기동

      • ~ update in-place 로 동작 (change, Modifying)

      • EC2 재기동 됨(Instance ID유지), User Data 정상 변경 됨,
        EIP 미사용 시 Public IP는 교체 됨, Terrafrom 재실행 필요함

      • Production 환경에서 필요한 옵션

      • user_data_replace_on_change = true 생략 시 기본 옵션 값

  • aws_instance Block에 user_data_replace_on_change = true 코드 추가 << EC2 교체

    • ~ update in-place, destroy and then create replacement 로 동작 (replaced)
    • User_Data : 정상 반영 됨, User Data 소스 변경 필요 시 해당 옵션 사용
    • Production 환경에선 주의가 필요 함
    • Production 환경에선
      lifecycle의 prevent_destroy 또는 ignore_changes (list)를 적절히 사용하여 자원 보호가 필요 함
  • aws_security_group에 lifecycle { create_before_destroy = true } 코드 추가

    ❯ cat main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "ami-0bcdae8006538619a"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data = <<-EOF
                  #!/bin/bash
                  echo "Hello, T101 Study 8080" > index.html
                  nohup busybox httpd -f -p 8080 &
                  EOF
    
      user_data_replace_on_change = false
    
      tags = {
        Name = "Single-WebSrv"
      }
    }
    
    resource "aws_security_group" "instance" {
      name = var.security_group_name
    
      ingress {
        from_port   = 8080
        to_port     = 8080
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    
      lifecycle {
        create_before_destroy = true
      }
    }
    
    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"
    }
    
    ❯ terraform plan
    aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
    aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be updated in-place
      ~ resource "aws_instance" "example" {
            id                                   = "i-07df1a72af3369d83"
            tags                                 = {
                "Name" = "Single-WebSrv"
            }
          ~ user_data                            = "efd980ae7b7a847bd5772af117f6a53cc7824934" -> "06b7595b2ec1e6bfaf498a14a900d01bbebab3a3"
            # (39 unchanged attributes hidden)
    
            # (8 unchanged blocks hidden)
        }
    
      # aws_security_group.instance will be updated in-place
      ~ resource "aws_security_group" "instance" {
            id                     = "sg-050429d8493a5e0fa"
          ~ ingress                = [
              - {
                  - cidr_blocks      = [
                      - "0.0.0.0/0",
                    ]
                  - from_port        = 9090
                  - ipv6_cidr_blocks = []
                  - prefix_list_ids  = []
                  - protocol         = "tcp"
                  - security_groups  = []
                  - self             = false
                  - to_port          = 9090
                    # (1 unchanged attribute hidden)
                },
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 8080
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "tcp"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 8080
                    # (1 unchanged attribute hidden)
                },
            ]
            name                   = "terraform-example-instance"
            tags                   = {}
            # (8 unchanged attributes hidden)
        }
    
    Plan: 0 to add, 2 to change, 0 to destroy.
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to
    take exactly these actions if you run "terraform apply" now.
    
    ❯ terraform apply -auto-approve
    aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
    aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
    
    Terraform used the selected providers to generate the following execution plan. Resource
    actions are indicated with the following symbols:
      ~ update in-place
    
    Terraform will perform the following actions:
    
      # aws_instance.example will be updated in-place
      ~ resource "aws_instance" "example" {
            id                                   = "i-07df1a72af3369d83"
            tags                                 = {
                "Name" = "Single-WebSrv"
            }
          ~ user_data                            = "efd980ae7b7a847bd5772af117f6a53cc7824934" -> "06b7595b2ec1e6bfaf498a14a900d01bbebab3a3"
            # (39 unchanged attributes hidden)
    
            # (8 unchanged blocks hidden)
        }
    
      # aws_security_group.instance will be updated in-place
      ~ resource "aws_security_group" "instance" {
            id                     = "sg-050429d8493a5e0fa"
          ~ ingress                = [
              - {
                  - cidr_blocks      = [
                      - "0.0.0.0/0",
                    ]
                  - from_port        = 9090
                  - ipv6_cidr_blocks = []
                  - prefix_list_ids  = []
                  - protocol         = "tcp"
                  - security_groups  = []
                  - self             = false
                  - to_port          = 9090
                    # (1 unchanged attribute hidden)
                },
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 8080
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "tcp"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 8080
                    # (1 unchanged attribute hidden)
                },
            ]
            name                   = "terraform-example-instance"
            tags                   = {}
            # (8 unchanged attributes hidden)
        }
    
    Plan: 0 to add, 2 to change, 0 to destroy.
    aws_security_group.instance: Modifying... [id=sg-050429d8493a5e0fa]
    aws_security_group.instance: Modifications complete after 1s [id=sg-050429d8493a5e0fa]
    aws_instance.example: Modifying... [id=i-07df1a72af3369d83]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 10s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 20s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 30s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 40s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 50s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 1m0s elapsed]
    aws_instance.example: Still modifying... [id=i-07df1a72af3369d83, 1m10s elapsed]
    aws_instance.example: Modifications complete after 1m11s [id=i-07df1a72af3369d83]
    
    Apply complete! Resources: 0 added, 2 changed, 0 destroyed.
    
    Outputs:
    
    public_ip = "52.79.199.167"
    ❯ 
    ❯ terraform apply -auto-approve
    aws_security_group.instance: Refreshing state... [id=sg-050429d8493a5e0fa]
    aws_instance.example: Refreshing state... [id=i-07df1a72af3369d83]
    
    Changes to Outputs:
      ~ public_ip = "52.79.199.167" -> "43.201.57.99"
    
    You can apply this plan to save these new output values to the Terraform state, without
    changing any real infrastructure.
    
    Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    public_ip = "43.201.57.99"
    
    

도전과제

EC2 웹서버 배포

요구 조건

  • Ubuntu에 apache(httpd) 설치, index.html 생성(닉네임 출력)하는 userdata 통해 설정
  • 웹 포트는 TCP 80
  • userdata 부분은 별도의 파일을 참조

코드

  • user-data.web

    #!/bin/bash
    
    apt-get update -y
    apt install apache2 -y
    
    sudo systemctl start apache2.service
    
    echo '<html><h1>SJKIM - Web Server!</h1></html>' > /var/www/html/index.html
  • main.tf

    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "web" {
      ami                    = "ami-0bcdae8006538619a"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data = file("user-data.web")
    
      user_data_replace_on_change = true
    
      tags = {
        Name = "ec2-study-apache-web"
      }
    }
    
    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 {
        protocol    = "-1"
        from_port   = 0
        to_port     = 0
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    variable "security_group_name" {
      description = "The name of the security group"
      type        = string
      default     = "SG-study-web"
    }
    
    output "public_ip" {
      value       = aws_instance.web.public_ip
      description = "The public IP of the Instance"
    }
  • 코드 실행

    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    Success! The configuration is valid.
    
    
    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_instance.web will be created
      + resource "aws_instance" "web" {
          + ami                                  = "ami-0bcdae8006538619a"
          + arn                                  = (known after apply)
          + associate_public_ip_address          = (known after apply)
          + 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_lifecycle                   = (known after apply)
          + instance_state                       = (known after apply)
          + instance_type                        = "t2.micro"
          + ipv6_address_count                   = (known after apply)
          + ipv6_addresses                       = (known after apply)
          + key_name                             = (known after apply)
          + 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
          + spot_instance_request_id             = (known after apply)
          + subnet_id                            = (known after apply)
          + tags                                 = {
              + "Name" = "ec2-study-apache-web"
            }
          + tags_all                             = {
              + "Name" = "ec2-study-apache-web"
            }
          + tenancy                              = (known after apply)
          + user_data                            = "b7331c1e682f52f3e90cc165fe240f8c8633be6a"
          + user_data_base64                     = (known after apply)
          + user_data_replace_on_change          = true
          + vpc_security_group_ids               = (known after apply)
        }
    
      # aws_security_group.instance will be created
      + resource "aws_security_group" "instance" {
          + arn                    = (known after apply)
          + description            = "Managed by Terraform"
          + egress                 = [
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 0
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "-1"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 0
                    # (1 unchanged attribute hidden)
                },
            ]
          + id                     = (known after apply)
          + ingress                = [
              + {
                  + cidr_blocks      = [
                      + "0.0.0.0/0",
                    ]
                  + from_port        = 80
                  + ipv6_cidr_blocks = []
                  + prefix_list_ids  = []
                  + protocol         = "tcp"
                  + security_groups  = []
                  + self             = false
                  + to_port          = 80
                    # (1 unchanged attribute hidden)
                },
            ]
          + name                   = "SG-study-web"
          + name_prefix            = (known after apply)
          + owner_id               = (known after apply)
          + revoke_rules_on_delete = false
          + tags_all               = (known after apply)
          + vpc_id                 = (known after apply)
        }
    
    Plan: 2 to add, 0 to change, 0 to destroy.
    
    Changes to Outputs:
      + public_ip = (known after apply)
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    aws_security_group.instance: Creating...
    aws_security_group.instance: Creation complete after 3s [id=sg-023e1eeaec43b1129]
    aws_instance.web: Creating...
    aws_instance.web: Still creating... [10s elapsed]
    aws_instance.web: Still creating... [20s elapsed]
    aws_instance.web: Still creating... [30s elapsed]
    aws_instance.web: Still creating... [40s elapsed]
    aws_instance.web: Creation complete after 41s [id=i-0dec33d676696103a]
    
    Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    public_ip = "13.125.197.36"
    
  • 테스트

    ❯ PIP=$(terraform output -raw public_ip)
    ❯ curl http://$PIP
    <html><h1>SJKIM - Web Server!</h1></html>

S3 버킷 생성

요구 조건

  • 버전닝 기능 활성화
  • 저장중 암호화 적용
  • 퍼블릭 액세스 차단

코드

  • get-bucket-name.sh

    #!/bin/bash
    # Exit if any of the intermediate steps fail
    set -e
    #t1=`hostname | cut -f1 -d'.'`
    t1=iac-dev
    t2=`date +%s%N`
    BUCKET_NAME=`printf "s3-%s-tfstate-%s" $t1 $t2 | awk '{print tolower($0)}'`
    jq -n --arg bn "$BUCKET_NAME" '{"Name":$bn}'
  • s3-bucket.tf

    data "external" "bucket_name" {
      program = ["bash", "get-bucket-name.sh"]
    }
    
    output "Name" {
      value = data.external.bucket_name.result.Name
      # value = "s3-brain-dev-tfstate-1709366921n"
    }
    
    resource "aws_s3_bucket" "terraform_state" {
      bucket = data.external.bucket_name.result.Name
    
      # This is only here so we can destroy the bucket as part of automated tests. 
      # You should not copy this for production usage
      force_destroy = true
    
      lifecycle {
        ignore_changes = [bucket]
      }
    }
    
    resource "aws_s3_bucket_versioning" "terraform_state_versioning" {
      bucket = aws_s3_bucket.terraform_state.id
      versioning_configuration {
        status = "Enabled"
      }
    }
    
    resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_sse" {
      bucket = aws_s3_bucket.terraform_state.id
    
      rule {
        apply_server_side_encryption_by_default {
          sse_algorithm = "AES256"
        }
      }
    }
    
    resource "aws_s3_bucket_public_access_block" "terraform_state_versioning_pab" {
      bucket = aws_s3_bucket.terraform_state.id
    
      block_public_acls       = true
      block_public_policy     = true
      ignore_public_acls      = true
      restrict_public_buckets = true
    }
  • 코드 실행

    ❯ tfb
    
    Initializing the backend...
    
    Initializing provider plugins...
    - Reusing previous version of hashicorp/tls from the dependency lock file
    ...
    
    data.external.bucket_name: Reading...
    data.external.bucket_name: Read complete after 0s [id=-]
    
    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_s3_bucket.terraform_state will be created
      + resource "aws_s3_bucket" "terraform_state" {
          + acceleration_status         = (known after apply)
          + acl                         = (known after apply)
          + arn                         = (known after apply)
          + bucket                      = "s3-iac-dev-tfstate-1718491103n"
          + bucket_domain_name          = (known after apply)
          + bucket_prefix               = (known after apply)
          + bucket_regional_domain_name = (known after apply)
          + force_destroy               = true
          + hosted_zone_id              = (known after apply)
          + id                          = (known after apply)
          + object_lock_enabled         = (known after apply)
          + policy                      = (known after apply)
          + region                      = (known after apply)
          + request_payer               = (known after apply)
          + tags_all                    = (known after apply)
          + website_domain              = (known after apply)
          + website_endpoint            = (known after apply)
        }
    
      # aws_s3_bucket_public_access_block.terraform_state_versioning_pab will be created
      + resource "aws_s3_bucket_public_access_block" "terraform_state_versioning_pab" {
          + block_public_acls       = true
          + block_public_policy     = true
          + bucket                  = (known after apply)
          + id                      = (known after apply)
          + ignore_public_acls      = true
          + restrict_public_buckets = true
        }
    
      # aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse will be created
      + resource "aws_s3_bucket_server_side_encryption_configuration" "terraform_state_sse" {
          + bucket = (known after apply)
          + id     = (known after apply)
    
          + rule {
              + apply_server_side_encryption_by_default {
                  + sse_algorithm     = "AES256"
                    # (1 unchanged attribute hidden)
                }
            }
        }
    
      # aws_s3_bucket_versioning.terraform_state_versioning will be created
      + resource "aws_s3_bucket_versioning" "terraform_state_versioning" {
          + bucket = (known after apply)
          + id     = (known after apply)
    
          + versioning_configuration {
              + mfa_delete = (known after apply)
              + status     = "Enabled"
            }
        }
    
    
    Plan: 9 to add, 0 to change, 0 to destroy.
    
    Changes to Outputs:
      + Name      = "s3-iac-dev-tfstate-1718491103n"
      + public_ip = (known after apply)
    
    ────────────────────────────────────────────────────────────────────────────────────────
    
    Saved the plan to: tfplan
    
    To perform exactly these actions, run the following command to apply:
        terraform apply "tfplan"
    tls_private_key.ec2_key: Creating...
    aws_security_group.instance: Creating...
    aws_s3_bucket.terraform_state: Creating...
    tls_private_key.ec2_key: Creation complete after 2s [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
    aws_key_pair.ec2_keypair: Creating...
    local_file.ec2_key_local: Creating...
    local_file.ec2_key_local: Creation complete after 0s [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
    aws_s3_bucket.terraform_state: Creation complete after 2s [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Creating...
    aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Creating...
    aws_s3_bucket_versioning.terraform_state_versioning: Creating...
    aws_key_pair.ec2_keypair: Creation complete after 0s [id=mykey]
    aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Creation complete after 0s [id=s3-iac-dev-tfstate-1718491103n]
    aws_security_group.instance: Creation complete after 3s [id=sg-01a4ed3b885e12279]
    aws_instance.web: Creating...
    aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Creation complete after 1s [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_versioning.terraform_state_versioning: Creation complete after 1s [id=s3-iac-dev-tfstate-1718491103n]
    aws_instance.web: Still creating... [10s elapsed]
    aws_instance.web: Still creating... [20s elapsed]
    aws_instance.web: Still creating... [30s elapsed]
    aws_instance.web: Creation complete after 31s [id=i-06c92b3a1ce57e2d6]
    
    Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    Name = "s3-iac-dev-tfstate-1718491103n"
    
    ❯ terraform state list
    data.external.bucket_name
    aws_s3_bucket.terraform_state
    aws_s3_bucket_public_access_block.terraform_state_versioning_pab
    aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse
    aws_s3_bucket_versioning.terraform_state_versioning

실행결과

  • 버킷 확인

    ❯ aws s3 ls | grep tfstate
    2024-06-16 07:38:28 s3-iac-dev-tfstate-1718491103n
  • S3 버킷 정보

S3 버킷 생성 후 그래프

  • Graph 실행 명령어
  ❯ terraform graph > graph.dot
  • Graph vs code에서 확인

DynamoDB & Backend 사용

DynamoDB

  • dynamodb-tables.tf

    resource "aws_dynamodb_table" "terraform_locks" {
      depends_on   = [aws_s3_bucket.terraform_state]
      name         = "ddb-iac-dev-terraform_challenge-locks"
      billing_mode = "PAY_PER_REQUEST"
      hash_key     = "LockID"
    
      attribute {
        name = "LockID"
        type = "S"
      }
    }
  • 코드 실행 및 결과

    ❯ tfb
    
    Initializing the backend...
    ..
    
    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_dynamodb_table.terraform_locks will be created
      + resource "aws_dynamodb_table" "terraform_locks" {
          + arn              = (known after apply)
          + billing_mode     = "PAY_PER_REQUEST"
          + hash_key         = "LockID"
          + id               = (known after apply)
          + name             = "ddb-iac-dev-terraform_challenge-locks"
          + read_capacity    = (known after apply)
          + stream_arn       = (known after apply)
          + stream_label     = (known after apply)
          + stream_view_type = (known after apply)
          + tags_all         = (known after apply)
          + write_capacity   = (known after apply)
    
          + attribute {
              + name = "LockID"
              + type = "S"
            }
        }
    
    Plan: 1 to add, 0 to change, 0 to destroy.
    ...
    
    ❯ terraform state list | grep dynamodb
    aws_dynamodb_table.terraform_locks

Backend - main 수정

  • main.tf

    terraform {
        required_version = "~> 1.8"
        required_providers {
            aws = {
                source = "hashicorp/aws"
    # Lock version to avoid unexpected problems
                version = "~> 5.39"
            }
    
            null = {
                source = "hashicorp/null"
                version = "~> 3.2.2"
            }
        }
    
        backend "s3" {
            bucket = "s3-iac-dev-tfstate-1718491103n"
            key = "terraform/ddb-iac-dev-terraform_challenge_challenge.tfstate"
            region = "ap-northeast-2"
            dynamodb_table = "ddb-iac-dev-terraform_challenge-locks"
            encrypt = "true"
        }
    }
    
    provider "aws" {
        region = "ap-northeast-2"
        shared_credentials_files = ["~/.aws/credentials"]
        profile = "default"
    }
  • 코드 실행결과 - tfstate과 s3로 이관 됨

    ❯ terraform init
    
    Initializing the backend...
    Do you want to copy existing state to the new backend?
      Pre-existing state was found while migrating the previous "local" backend to the
      newly configured "s3" backend. No existing state was found in the newly
      configured "s3" backend. Do you want to copy this state to the new "s3"
      backend? Enter "yes" to copy and "no" to start with an empty state.
    
      Enter a value: yes
    
    
    Successfully configured the backend "s3"! Terraform will automatically
    use this backend unless the backend configuration changes.
    
    Initializing provider plugins...
    - Finding hashicorp/null versions matching "~> 3.2.2"...
    - Reusing previous version of hashicorp/tls from the dependency lock file
    - Reusing previous version of hashicorp/local from the dependency lock file
    - Reusing previous version of hashicorp/external from the dependency lock file
    - Reusing previous version of hashicorp/aws from the dependency lock file
    - Installing hashicorp/null v3.2.2...
    - Installed hashicorp/null v3.2.2 (signed by HashiCorp)
    - Using previously-installed hashicorp/tls v4.0.5
    - Using previously-installed hashicorp/local v2.5.1
    - Using previously-installed hashicorp/external v2.3.3
    - Using previously-installed hashicorp/aws v5.54.1
    
    Terraform has made some changes to the provider dependency selections recorded
    in the .terraform.lock.hcl file. Review those changes and commit them to your
    version control system if they represent changes you intended to make.
    
    Terraform has been successfully initialized!
    
    You may now begin working with Terraform. Try running "terraform plan" to see
    any changes that are required for your infrastructure. All Terraform commands
    should now work.
    
    If you ever set or change modules or backend configuration for Terraform,
    rerun this command to reinitialize your working directory. If you forget, other
    commands will detect it and remind you to do so if necessary.
    
    ❯ terraform plan
    data.external.bucket_name: Reading...
    tls_private_key.ec2_key: Refreshing state... [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
    local_file.ec2_key_local: Refreshing state... [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
    data.external.bucket_name: Read complete after 0s [id=-]
    aws_key_pair.ec2_keypair: Refreshing state... [id=mykey]
    aws_security_group.instance: Refreshing state... [id=sg-01a4ed3b885e12279]
    aws_s3_bucket.terraform_state: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_instance.web: Refreshing state... [id=i-06c92b3a1ce57e2d6]
    aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_versioning.terraform_state_versioning: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_dynamodb_table.terraform_locks: Refreshing state... [id=ddb-iac-dev-terraform_challenge-locks]
    
    Changes to Outputs:
      ~ Name      = "s3-iac-dev-tfstate-1718494211n" -> "s3-iac-dev-tfstate-1718495056n"
    
    You can apply this plan to save these new output values to the Terraform state,
    without changing any real infrastructure.
    
    ──────────────────────────────────────────────────────────────────────────────────
    
    Note: You didn't use the -out option to save this plan, so Terraform can't
    guarantee to take exactly these actions if you run "terraform apply" now.
    
    ❯ terraform apply -auto-approve
    data.external.bucket_name: Reading...
    tls_private_key.ec2_key: Refreshing state... [id=778f75a1910a0fb0a9afbc2266cbcc343be0ba3c]
    local_file.ec2_key_local: Refreshing state... [id=612dde1bc937f145b5a23999cb7bcf3bd1c68814]
    data.external.bucket_name: Read complete after 0s [id=-]
    aws_key_pair.ec2_keypair: Refreshing state... [id=mykey]
    aws_security_group.instance: Refreshing state... [id=sg-01a4ed3b885e12279]
    aws_s3_bucket.terraform_state: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_instance.web: Refreshing state... [id=i-06c92b3a1ce57e2d6]
    aws_s3_bucket_versioning.terraform_state_versioning: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_server_side_encryption_configuration.terraform_state_sse: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_s3_bucket_public_access_block.terraform_state_versioning_pab: Refreshing state... [id=s3-iac-dev-tfstate-1718491103n]
    aws_dynamodb_table.terraform_locks: Refreshing state... [id=ddb-iac-dev-terraform_challenge-locks]
    
    Changes to Outputs:
      ~ Name      = "s3-iac-dev-tfstate-1718494211n" -> "s3-iac-dev-tfstate-1718495080n"
    
    You can apply this plan to save these new output values to the Terraform state,
    without changing any real infrastructure.
    
    Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
    
    Outputs:
    
    Name = "s3-iac-dev-tfstate-1718495080n"
    public_ip = "43.203.239.25"

Pem 키로 서버 원격 접속할 수 있도록 코드 수정

  • keypair.tf

    resource "tls_private_key" "ec2_key" {
      algorithm = "RSA"
      rsa_bits  = 4096
    }
    
    resource "aws_key_pair" "ec2_keypair" {
      key_name   = var.key_name
      public_key = tls_private_key.ec2_key.public_key_openssh
    }
    
    resource "local_file" "ec2_key_local" {
      filename        = "${var.key_name}.pem"
      content         = tls_private_key.ec2_key.private_key_pem
      file_permission = "0400"
    }
    
    variable "key_name" {
      description = "The name of the keypair"
      type        = string
      default     = "mykey"
    }    
  • main.tf의 key_name과 SG Inbound 정책 수정

    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "web" {
      ami                    = "ami-0bcdae8006538619a"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data                   = file("user-data.web")
      key_name                    = var.key_name            ## 추가한 내용
      user_data_replace_on_change = true
    
      tags = {
        Name = "ec2-study-apache-web"
      }
    }
    
    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"]
      }
    
    # MyPC에서만 원격 접속하도록 정책 추가
      ingress {
        from_port   = 22
        to_port     = 22
        protocol    = "tcp"
        cidr_blocks = ["10.10.10.10/32"]
      }
    
      egress {
        protocol    = "-1"
        from_port   = 0
        to_port     = 0
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    variable "security_group_name" {
      description = "The name of the security group"
      type        = string
      default     = "SG-study-web"
    }
    
    output "public_ip" {
      value       = aws_instance.web.public_ip
      description = "The public IP of the Instance"
    }
  • 코드 실행 후 mykey.pem 생성 확인

    ❯ ls -al mykey.pem
    -r--------  1 sjkim  staff  3243 Jun 16 02:35 mykey.pem
  • 서버 원격접속

    ❯ ssh -i "mykey.pem" ubuntu@ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com
    The authenticity of host 'ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com (13.125.235.67)' can't be established.
    ED25519 key fingerprint is SHA256:R1/tcitDGuGyhbsI6JgUgykK6nWejK+Vl5JhsA0G3S0.
    This key is not known by any other names.
    Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
    Warning: Permanently added 'ec2-13-125-235-67.ap-northeast-2.compute.amazonaws.com' (ED25519) to the list of known hosts.
    Welcome to Ubuntu 22.04.4 LTS (GNU/Linux 6.5.0-1020-aws x86_64)
    
     * Documentation:  https://help.ubuntu.com
     * Management:     https://landscape.canonical.com
     * Support:        https://ubuntu.com/pro
    
     System information as of Sat Jun 15 17:36:53 UTC 2024
    
      System load:  0.0               Processes:             102
      Usage of /:   23.6% of 7.57GB   Users logged in:       0
      Memory usage: 23%               IPv4 address for eth0: 172.31.1.13
      Swap usage:   0%
    
    Expanded Security Maintenance for Applications is not enabled.
    
    0 updates can be applied immediately.
    
    Enable ESM Apps to receive additional future security updates.
    See https://ubuntu.com/esm or run: sudo pro status
    
    
    
    The programs included with the Ubuntu system are free software;
    the exact distribution terms for each program are described in the
    individual files in /usr/share/doc/*/copyright.
    
    Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
    applicable law.
    
    To run a command as administrator (user "root"), use "sudo <command>".
    See "man sudo_root" for details.
    
    ubuntu@ip-172-31-1-13:~$ cat /var/www/html/index.html 
    <html><h1>SJKIM - Web Server!</h1></html>
profile
I'm SJ

0개의 댓글