[Terraform] 따라하며 배우는 테라폼 입문(With. AWS)

Jade·2022년 11월 12일
0

본 시리즈는 CloudNet@의 테라폼 기초 입문 스터디(T101)원으로 참여하여 학습한 내용을 정리한 것임을 밝힌다. 실습을 통해 AWS 리소스를 Terraform으로 직접 배포해보는 과정을 연재할 예정이다.

개요

Terraform이란?

  • HashiCorp가 만든 IaC를 위한 툴로, 인프라 리소스를 코드로 관리할 수 있는 오픈소스 프로젝트다.
  • 가장 큰 특징은 롤백이 아닌 멱등성(Idempotency)을 제공한다. 즉, 코드로 정의한 리소스들이 명시된 대로 적용하도록 하므로써 항상 같은 결과값을 낼 수 있다는 뜻이다.

IaC(Infrastructure as Code)란?

  • 인프라를 콘솔에서 수동으로 관리하는 대신 코드를 사용하여 구성하는 방식이다.
  • 엔지니어는 프로비저닝, 구성, 배포 등 반복되는 작업들을 자동화하여 비용 절감 및 업무 자동화, 인적 오류로 인한 위험 부담 감소, 속도 향상 등의 효과를 얻을 수 있다.

테라폼 작동 방식

  • 테라폼은 Go 언어로 개발한 오픈 소스 도구로, 운영 체제마다 바이너리 파일이 존재하는데 Go 코드는 하나의 바이너리 파일로 컴파일되며 terraform이라는 명령어로 실행할 수 있다.
  • terraform 바이너리가 AWS, Azure, GCP 등 공급자를 대신해 API를 호출하여 리소스를 생성한다. 즉, 클라우드 공급자가 제공하는 API 서버를 활용할 뿐만 아니라 AWS에 이미 보유한 API 키 같은 인증 메커니즘도 같이 이용한다는 의미다.

Terraform vs. Ansible

  • 구성 관리 및 프로비전 도구는 이외에도 많지만(셰프, 퍼핏, 솔트스택, 클라우드포메이션 등..) 나는 Terraform과 Ansible의 차이를 중점적으로 비교해보았다. 두가지는 비슷한 툴이지만, 차이점이 분명 존재한다.

오케스트레이션(Orchestration) vs 구성 관리(Configuration Management)

  • 가장 기본적인 차이점은 Terraform은 오케스트레이션 도구이고 Ansible이 구성 관리 도구라는 것이다. 이해를 돕기 위해 상황을 오케스트라에 비유해보자.

  • 오케스트레이션 툴은 지휘자와 비슷하다고 생각하면 된다. 지휘자는 적절한 수의 악기가 있고, 그것들이 함께 잘 연주되고 있는지 확인한다. 만약 문제가 있다면 보통 동작에 문제가 있는 기기를 없애고 다른 기기로 교체한다. 즉, 최종 결과에 초점을 맞추고 있어 환경이 항상 그 상태에 있음을 보증하는데 도움이 된다.

    Terraform은 환경의 상태를 저장하고 있다가 고장나거나 누락된 경우 다시 실행될 때 리소스를 자동으로 제공한다. 이는 매우 안정된 상태를 필요로 하는 환경에 매우 적합하다.

  • 반면 구성관리 툴은 기기의 수리 담당자와 같다. Ansible 또한 각 기기를 구성하여 손상이 없고 올바르게 기능하고 있는지를 확인한다. 하지만 동작에 문제가 있는 경우 기기를 완전히 교체하는 것이 아니라 문제를 복구하기 위한 기능을 한다.

    이런 점에서 Ansible도 어느 정도 인프라스트럭처를 대체하거나 오케스트레이션 태스크를 수행할 수도 있지만, Terraform이 더욱 고도의 관리가 가능하므로 일반적으로 더 우수한 제품으로 간주되고 있다.

선언적(Declarative) vs 절차적(Procedural)

  • Terraform은 본질적으로 인프라의 상태를 선언적으로 정의한다. 즉, 구체적인 절차를 기술하는 대신 원하는 바를 선언한다
  • Ansible은 절차적 언어와 선언적 언어를 모두 사용하는 하이브리드 형태다. 원하는 상태를 달성하기 위해 단계별 절차를 기술하는 방식을 사용할 수도 있고, 선언적 구성을 사용할 수 있는 여러 모듈도 존재한다.

마스터리스(Masterless) vs 마스터(Master)

  • Terraform에는 자체 아키텍처에 별도로 마스터 머신이 없다.
  • Ansible에는 전체 인프라의 상태를 저장하고 구성 업데이트를 원격 서버로 푸시하는 마스터 머신이 있다.

어느 것을 선택해야 하나?

  • 비즈니스 요구 사항에 따라 다르겠지만, 일반적으로 전체 IT 인프라를 조율 할 도구를 찾고 있다면 Terraform을, 집중적인 구성관리 도구를 찾고 있다면 Ansible을 사용하는 것이 좋다.
  • 다시 말해 클라우드 프로바이더 및 클라우드 리소스와 긴밀하게 협력하려는 경우 Terraform이, 소프트웨어를 프로비저닝하고 관리하려는 경우 Ansible이 작업에 더 적합한 도구라고 할 수 있다.

테라폼 배포 워크플로

이미지 출처: https://betterprogramming.pub/how-terraform-works-a-visual-intro-6328cddbe067

terraform init (초기화/준비)

  • 테라폼 구성 파일이 포함된 작업 디렉토리를 초기화하는 데 사용된다. 즉, 테라폼 바이너리에는 모든 공급자에 대한 코드가 포함되어 있지 않기 때문에, 이 명령어를 실행하여 테라폼에 코드를 스캔하도록 지시하고, 어느 공급자인지 확인하고, 필요한 코드를 다운로드 하도록 한다.
  • 이 명령은 사용할 작업 디렉토리를 준비하기 위해 몇 가지 다른 초기화 단계를 수행하며 여러 번 실행해도 안전하며(멱등성), 구성 변경사항과 함께 작업 디렉토리를 최신 상태로 유지한다.

terraform plan (예측)

  • 이 명령어를 사용하면 무언가를 실제로 변경하기 전에 테라폼이 수행할 작업을 확인할 수 있다. 이것은 실제 운영 환경에 적용하기 전에 코드의 온전성을 검사할 수 있는 좋은 방법이다.

terraform apply (생성)

  • 실제 리소스를 배포하는 명령어. plan에서 출력 된 내용이 실제로 반영된다.

terraform destroy (삭제)

  • 테라폼으로 생성한 리소스를 삭제하는 명령어이다.

실습

실습 전 사전 준비 사항

  1. AWS 계정

  2. AWS IAM User 생성 : 실습 편리를 위해 관리자 수준 권한(AdministratorAccess) 부여, 프로그래밍 방식 액세스 권한 부여(Access/Secret Key)

  3. AWS CLI v2 설치 및 IAM User 자격 증명 설정 - 참고1, 참고2

    # macOS
    $ brew install awscli
    
    # aws cli 버전 확인
    aws --version
    aws-cli/2.7.31 Python/3.10.7 Darwin/21.6.0 source/x86_64 prompt/off
    
    ---
    # aws configure 로 자격증명 설정 : 방안1
    aws configure list
          Name                    Value             Type    Location
          ----                    -----             ----    --------
       profile                <not set>             None    None
    access_key     ****************DYFF shared-credentials-file
    secret_key     ****************m7Za shared-credentials-file
        region           ap-northeast-2      config-file    ~/.aws/config
    
    # 환경 변수로 자격증명 설정 : 방안2
    Linux or macOS
    export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
    export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    export AWS_DEFAULT_REGION=us-west-2
    
    Windows 
    set AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
    set AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
    set AWS_DEFAULT_REGION=us-west-2
    ## Powershell
    $Env:AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
    $Env:AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
    $Env:AWS_DEFAULT_REGION="us-west-2"
    
    # 페이저 사용 비활성화
    export AWS_PAGER=""
    set AWS_PAGER=""
  4. (선택) 실습에 편의를 주는 툴 설치 : watch, jq, tree 등

  1. AWS 서울 리전(ap-northeast-2)에 default VPC 존재 여부 확인. 없을 경우 default VPC 생성 - 참고

테라폼 설치

  • 작업 환경: Mac OS(M1, zsh Shell)

  • 테라폼 버전 v.1.3.2 - 참고

    # 설치 - 링크
    brew install terraform
    terraform version
    Terraform v1.3.2
    
    terraform -help
    
    # 자동완성
    terraform -install-autocomplete
    ## 참고 .zshrc 에 아래 추가됨
    cat ~/.zshrc
    autoload -U +X bashcompinit && bashcompinit
    complete -o nospace -C /usr/local/bin/terraform terraform

default VPC에 EC2 1대 배포

배포 전 준비

  • 로컬 환경 터미널에서 작업 디렉터리 생성 및 이동
    mkdir Study-Terraform
    cd Study-Terraform

EC2 1대 배포 실행

  • 코드 파일 작성

    • provider : Terraform으로 정의할 Infrastructure Provider
    • resource : 실제로 생성할 인프라 자원
    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami           = "ami-0c76973fbe0ee100c"
      instance_type = "t2.micro"
    }
    EOT

    위 코드 내용을 해석하면 AWS라는 공급자(provider) 서울 리전(ap-northeast-2)에, 'example'이라는 식별자를 사용하여 EC2 인스턴스를 생성하겠다는 뜻이다. 해당 인스턴스의 ami는 ami-0c76973fbe0ee100c이며, 인스턴스 타입은 t2.micro로 지정한다.

    이 단계에서는 이미 내 AWS 계정에 생성되어 있는 default VPC를 이용하게 되므로, 별도로 VPC 관련 리소스(서브넷, 인터넷 게이트웨이 등)를 구축할 필요 없이 EC2만 간단히 배포하면 된다.

  • 새 터미널 창 추가 후, Study-Terraform 폴더 위치로 이동하여 초기화를 실행해본다.

    # 초기화
    terraform init
    ls -al
    tree .terraform

    초기화가 성공적으로 진행되었고, 현재 위치인 Study-Terraform에 생성되어 있는 파일 목록을 확인하였다.

    만약 M1에서 init 명령어 실행시 Error: Incompatible provider version 오류가 출력된다면 참고

    # plan 확인
    terrafomrm plan

    Plan 명령어의 출력 결과를 전부 담진 못했으나, apply 실행시 변경될 리소스 목록이 나열되는 것을 알 수 있다.

    # apply 실행
    terraform apply
      # Enter a value: yes 입력

    짝짝짝. 처음으로 코드를 통해 EC2 한 대를 배포하는 경험을 하였다.
    위 스크린샷에도 표시 되었듯 i-06578ce834fe7718b라는 인스턴스 ID를 갖는 인스턴스가 배포되었다.

    # ec2 생성 확인
    export AWS_PAGER=""
    aws ec2 describe-instances --output table

    나의 경우, 계정에 다른 인스턴스도 많기에 위 스크린샷에서 출력된 인스턴스 ID만 추출하여 생성 내용을 확인하였다.
    CLI 뿐만아니라 콘솔에서도 해당 Instance ID로 생성된 EC2 인스턴스를 확인할 수 있었다.

  • 방금 생성된 EC2의 태그 정보를 수정하여 'jinju-tf-study'라는 인스턴스 명을 붙여보자.

    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami           = "ami-0c76973fbe0ee100c"
      instance_type = "t2.micro"
    
      tags = {
        Name = "jinju-tf-study"
      }
    
    }
    EOT
    # 다시 paln 명령어 실행
    terraform plan

    terraform plan 명령 실행 시, 위와 같은 정보가 출력된다.
    기호 '+'는 추가, '-'는 제거, '~'는 항목이 수정된다는 뜻이다.

    # apply 실행
    terraform apply
      # Enter a value: yes 입력

    콘솔에서도 변경된 인스턴스 명을 확인할 수 있다.

  • 리소스 삭제

    # 리소스 삭제
    terraform destroy
    
     # Enter a value: yes 입력
    
    # 혹은 yes 입력이 불필요한 아래 명령어 입력
    terraform destroy -auto-approve
    
    # 초기화
    terraform init

default VPC에 웹 서버 배포

  • 목표: EC2 1대를 배포하면서 userdata 에 웹 서버 설정 → 간단한 애플리케이션 설정 자동화 하기
    EC2 인스턴스의 User data를 설정하여 특정 스크립트를 실행할 수 있다. 즉 인스턴스가 시작될 때 셸 스크립트 등을 사용자 데이터에 전달하여 특정 작업을 수행하도록 하는 것이다.

  • 배포: Ubuntu 22.04 LTS 사용 (ami-0e9bfdb247cc8de84)

    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "ami-0e9bfdb247cc8de84"
      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

    배포 실행

    # plan
    terraform plan
    
    # apply 실행
    terraform apply -auto-approve

    plan 명령 실행 시, 아래 스크린샷처럼 user_data가 추가 된다는 내역을 확인할 수 있다.
    리소스 생성이 완료 되었다. 콘솔에서 해당 인스턴스의 퍼블릭 아이피 확인 후, CLI에서 해당 웹 서버 접속을 시도해보자. Connection timed out 오류가 출력되는 것을 확인할 수 있다. 왜일까?

    정답은 해당 인스턴스 접속을 위한 보안그룹이 별도로 생성 및 연결되지 않았기 때문이다.
    따라서 아래와 같이 main.tf의 코드를 수정하여 재배포해야 한다.

    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "ami-0e9bfdb247cc8de84"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data = <<-EOF
                  #!/bin/bash
                  echo "Hello, TF Study" > index.html
                  nohup busybox httpd -f -p 8080 &
                  EOF
    
      tags = {
        Name = "jinju-tf-study"
      }
    }
    
    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
    # plan/apply
    terraform plan && terraform apply -auto-approve
    
    # 모니터링
    while true; do curl --connect-timeout 1  http://$PIP:8080/ ; echo "------------------------------"; date; sleep 1; done

    성공! EC2 1대를 배포하면서, 필요한 애플리케이션 설정을 자동화하였다.

입력 변수 사용 해보기

실습 전 학습 - Variable 변수

현재까지 실습한 보안 그룹과 유저 데이터 코드를 보면 웹 서버 코드에 8080 포트가 중복되는 것을 알 수 있다. 이는 DRY(Don't Repeat Yourself) 원칙, 즉 반복하지 말라는 원칙을 위반한다.

테라폼은 입력 변수를 정의할 수 있으므로 코드가 중복되지 않고 구성을 관리하기도 쉽다. 변수를 선언하는 구문은 다음과 같다.

variable “NAME” {
[CONFIG …]
}

  • 변수 선언의 본문에는 3개의 매개 변수가 포함될 수 있으며, 모두 선택적 매개 변수임
  • description : 설명, 코드 내용 및 plan/apply 명령어를 실행할 때 설명 볼 수 있음
  • default : 변수 값을 전달하는 여러 가지 방법을 지정하지 않으면 기본값이 전달됨, 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄
    • 변수 값 전달 : 명령 줄(-var 옵션), 파일(-var-file 옵션), 환경 변수(테라폼은 이름이 ‘TFVAR<variable_name>’)
  • type : 전달하려는 변수의 유형 지정, string number bool list map set object tuple 와 유형을 지정하지 않으면 any 유형으로 간주
  • sensitive : 입력 변수가 사용 시 출력 제한(암호 등 민감 데이터의 경우) - 링크
  • validation : 변수 값에 사용자 지정 검사 설정 - 링크

웹 서버의 포트 넘버를 입력 받아 처리해보기

  • 코드 파일 작성

    cat <<EOT > variables.tf
    variable "server_port" {
      description = "The port the server will use for HTTP requests"
      type        = number
    }
    EOT
  • plan (방안1): 대화형으로 변수값 입력
    type의 값을 number로 입력 하였으므로 아래와 같이 plan 명령어 실행시 전달할 값이 number인지 확인하게 된다.

  • plan (방안2): -var 옵션을 사용하여 포트 번호 명시

    terraform plan -var "server_port=8080"

  • plan (방안3): 환경변수

    # 환경변수에 지정
    export TF_VAR_server_port=8080
    terraform plan
    
    # 환경변수 확인
    export | grep TF_VAR_
    
    # 환경변수 지정 삭제
    unset TF_VAR_server_port
  • plan : (방안4) 디폴트값을 미리 지정

    cat <<EOT > variables.tf
    variable "server_port" {
      description = "The port the server will use for HTTP requests"
      type        = number
      default     = 8080
    }
    EOT
    
    # plan
    terraform plan

웹 서버 포트 지정 배포

variables, main.tf 코드 파일 작성

  • variables.tf 파일에는 server_port라는 변수 default 값을 8080으로 지정하고, main.tf 파일에는 var.server_port를 활용한다.

    cat <<EOT > variables.tf
    variable "server_port" {
      description = "The port the server will use for HTTP requests"
      type        = number
      default     = 8080
    }
    EOT
    cat <<EOT > main.tf
    provider "aws" {
      region = "ap-northeast-2"
    }
    
    resource "aws_instance" "example" {
      ami                    = "ami-0e9bfdb247cc8de84"
      instance_type          = "t2.micro"
      vpc_security_group_ids = [aws_security_group.instance.id]
    
      user_data = <<-EOF
                  #!/bin/bash
                  echo "My Web Server - var test" > index.html
                  nohup busybox httpd -f -p \${var.server_port} &
                  EOF
    
      user_data_replace_on_change = true
    
      tags = {
        Name = "jinju-tf-study"
      }
    }
    
    resource "aws_security_group" "instance" {
      name = var.security_group_name
    
      ingress {
        from_port   = var.server_port
        to_port     = var.server_port
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
      }
    }
    
    variable "security_group_name" {
      description = "The name of the security group"
      type        = string
      default     = "terraform-my-instance"
    }
    
    output "public_ip" {
      value       = aws_instance.example.public_ip
      description = "The public IP of the Instance"
    }
    EOT
    # plan/apply
    terraform plan && terraform apply -auto-approve'
    
    # 리소스 상태 확인
    terraform state list

    main.tf에 정의하였던 output 변수가 출력되어 인스턴스의 퍼블릭 IP를 확인할 수 있었다.

    테라폼을 통해 인스턴스와 보안그룹이 생성되었다.

    # 모니터링
    PIP=<각자 자신의 EC2 IP>
    PPT=8080
    while true; do curl --connect-timeout 1  http://$PIP:$PPT/ ; echo "------------------------------"; date; sleep 1; done

    모니터링 결과, 정상 접근 성공!

    # 삭제
    terraform destroy -auto-approve
    # 초기화
    terraform init

    목표로 했던 실습 내용이 완료되었으니 destroy 명령어를 통해 테라폼으로 생성한 리소스를 삭제하자.


    간단하게나마 테라폼의 동작방식을 엿볼 수 있는 즐거운 실습이었다.
    다만 코드만으로는 처음부터 전체적인 그림을 그리기가 어려우니 초보라면 반드시 콘솔에서 수동으로 리소스를 생성해보고 이해하는 과정이 선행되어야 할 것 같다.

profile
우당탕탕 좌충우돌 인프라 여행기

0개의 댓글