Terraform Study - w01

wdb·2023년 7월 8일

Terraform 개요

테라폼은 하시코프(HarshiCorp)에서 만든 오픈 소스 IaC입니다. IaC(Infrastructure as Code), 즉, 인프라를 코드 형태로 정의하는 선언형 프로그래밍 언어입니다. AWS를 비롯한 다양한 클라우드 및 가상화 환경을 지원하고 있습니다.
IaC의 장점은 무엇일까요? AWS 환경에서 배포한 서비스가 대성공을 거두었다고 가정해봅시다. 서비스의 규모가 커지고 글로벌 수요가 늘어난다면 인프라 관리자는 당연하게도 다른 리전으로의 배포를 고려할 것입니다. 현재 리전에 배포되어 있는 인프라를 그대로 다른 리전에 배포하는 작업을 진행해야 한다면 우선 현재 인프라 현황을 파악해야겠죠. 인프라 상태에 대한 정리가 잘되어있다면 다행이겠지만, 만약 문서화가 부실할 경우 콘솔에서 현재 인프라가 어떻게 배포되어 있는지 하나하나 확인하는 작업이 필요합니다. 그리고 같은 상태로 배포하기 위해 또 다시 콘솔에서 서비스 하나하나를 생성해야 할 것입니다.
IaC를 사용한다면 어떨까요? 현재의 인프라는 이미 코드화되어 정리되어 있을 것입니다. 같은 형태로 배포한다면 단지 리전 값만 바꾸어 배포하면 되겠죠. 재활용되어야 하는 인프라가 늘어날 수록 IaC의 장점은 커집니다. IaC가 필요한 이유는 프로그래밍에서 자주 사용되는 기능을 모듈화하여 지속적으로 사용할 수 있는 형태로 만드는 것과 한 맥락입니다. 자주 사용하는 인프라 구조를 IaC를 통해 모듈화한다면 언제든 빠르게 배포할 수 있겠죠. 삭제 역시 훨씬 간편합니다. 사용하고 있는 서비스 화면에 일일히 들어갈 필요가 없습니다. 서로 종속적인 인프라 간의 삭제 순서를 신경쓸 필요도 없습니다. IaC를 이용하면 명령어 하나로 배포한 인프라를 깔끔하게 제거할 수 있습니다. 인프라를 예전 상태로 되돌려야 하는 상황이 온다면 어떨까요? 형상 관리 도구를 이용하여 버저닝 된 IaC를 이용하여 간편하게 되돌릴 수도 있습니다. 현재 상태를 기록하기 위해 별도의 문서를 만들 필요가 없죠.

기본 명령어

테라폼 인프라를 배포하는데 최적화 된 도구입니다. 다른 IaC와 비교된 테라폼 장점은 다양합니다. 우선 얘기할 수 있는 장점은 아주 간단하고 직관적인 명령어를 들 수 있습니다.

# 테라폼을 수행하기 위한 provider(클라우드 서비스 제공자)의 플러그인 초기화
terraform init

# 테라폼을 통해 변경되는 내역을 확인 및 검증
terraform plan 

# 테라폼을 통한 인프라 배포
terraform apply

# 테라폼을 통한 배포한 인프라를 삭제
terraform destroy

tfenv

테라폼을 사용하다보면 여러 버전을 사용해야 하는 상황입 발생할 수 있습니다. 이전에 사용한 테라폼 코드는 이전 버전에서만 작동할 수 있습니다. 하지만 새롭게 배포해야 하는 서비스는 최신의 버전을 사용하고 싶을 수도 있죠. 그런 상황에서 유용하게 이용할 수 있는게 tfenv, 테라폼 버전 관리 도구입니다. tfenv를 이요하면 프로젝트마다 다른 버전의 테라폼을 설치 및 사용할 수 있습니다. 설치 및 사용법은 아래와 같습니다.

##
# 테라폼 버전 목록 확인
tfenv list-remtoe

# 특정 버전의 테라폼 설치
tfenv install 0.x.x

# 최신 버전의 테라폼 설치
tfenv install lastst

# 테라폼 파일 분석 후 최소 요구 버전 설치
tfenv install min-required

# 테라폼 버전 선택
tfenv use 0.x.x

도전과제

01. 아파치 웹 서버 배포

소스코드

테라폼을 통해 아파치 웹 서버를 배포할 것입니다. 별도로 생성할 리소스는 보안그룹, 키페어이고, AMI 프로바이더로부터 불러와 이용할 것입니다.

우선 보안그룹을 생성해보겠습니다. security_group.tf입니다.

resource "aws_security_group" "web_sg" {
  name        = "${var.prefix}_sg"
  description = "Security Group for apahce web server"

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

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

  tags = {
    Name = "${var.prefix}_sg"
  }
}

웹 서버이기 때문에 인바운드 규칙의 http 프로토콜(80 포트)를 열어줍니다. Linux 환경에 ssh 프로토콜로 접속하기 위해 22번 포트 역시 인바운드를 허용합니다. 아웃바운드는 모두 허용합니다. protocol의 -1 값은 모든 트래픽을 허용한다는 의미입니다.
EC2 환경에 접속할 때 권장되는 방법은 키페어를 통한 방법인데요. EC2에 접속하기 위한 키 페어를 생성해보겠습니다.

ssh-keygen

위의 명령어를 통해 로컬 환경에서 키페어를 생성합니다.

data "local_file" "public_key" {
  filename = "./keypair/apache-web-key.pub"
}

resource "aws_key_pair" "apache_web_key_pair" {
  key_name   = "apache-web-key"
  public_key = data.local_file.public_key.content
}

keypair.tf 파일에서 키 페어를 생성한 경로에서 키페어를 불러와 키페어를 AWS에 등록합니다.
다음으로 ami.tf 파일에서 AMI 정보를 불러옵니다.

# load latest ami
data "aws_ami" "latest_ubuntu_22_04" {
 most_recent = true
 filter {
   name   = "name"
   values = ["ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server*"]
 }
 filter {
   name   = "virtualization-type"
   values = ["hvm"]
 }
 owners = ["099720109477"]
}

data 블록은 공급자로부터 데이터 소스를 조회하여 변수로 사용하기 위한 기능입니다. AMI id는 업데이트가 잦기 때문에 AWS에서 직접 가져오는 것이 편리합니다. 아파치 웹 서버는 ubuntu 22.04 환경을 이용할 것입니다. filter 기능을 이용하여 가져올 AMI 정보를 구체화할 수 있습니다.
이제 EC2를 생성할 준비가 되었습니다. ec2.tf 파일을 작성합니다.

# ec2 - apache web server
resource "aws_instance" "apache_web_key_pair" {
  ami                    = data.aws_ami.latest_ubuntu_22_04.id
  instance_type          = var.instance_type
  vpc_security_group_ids = [aws_security_group.web_sg.id]
  key_name               = aws_key_pair.apache_web_key_pair.key_name
  user_data = templatefile("./template/apache_install.tpl", {
    nickname = var.nickname
  })
  tags = {
    Name = "${var.prefix}-server"
  }
}

resource 블록을 통해 배포할 리소스를 정의합니다. userdata에 들어갈 아파치 설치 스크립트는 template 폴더에서 불러옵니다. template 형식을 이용하면 변수를 통해 값을 지정할 수 있습니다.
웹 서버 배포 코드에 사용할 변수는 varialbe.tf에 선언합니다.

variable "account_profile" {}
variable "region" {}

variable "prefix" {}

variable "ami_id" {}
variable "instance_type" {}
variable "key_name" {}

variable "nickname" {}

변수 값은 terraform.tfvars 파일에 설정해줍니다.

###########################################
################ Default ##################
###########################################

account_profile = "wdb"
region          = "ap-northeast-2"
prefix = "apache-web"

###########################################
################### EC2 ###################
###########################################

ami_id        = "ami-0c9c942bd7bf113a2"
instance_type = "t2.micro"
key_name      = "test-key"

###########################################
################### WEB ###################
###########################################

nickname = "terraform_study_w01_wdb"

02. 테라폼 백엔드 설정

소스코드_1
소스코드_2
테라폼은 실행할 때마다 생성한 인프라에 정보를 테라폼 상태 파일에 저장합니다(.tfstate). 테라폼 백엔드는 로컬이 아닌 외부에 상태 파일을 저장하여 복수의 사람들이 작업하더라도 상태파일의 일관성을 유지할 수 있게 해주는 역할을 합니다.
백엔드로 사용할 수 있는 서비스는 다양하게 있지만, 현재 AWS 환경에서 인프라를 배포하고 있기 때문에 백엔드로 AWS S3와 Dynamo를 이용하여 백엔드를 설정할 것입니다.
우선 백엔드에 필요한 리소스(S3, DynamoDB)를 생성합니다.

resource "aws_s3_bucket" "backend_bucket" {
    bucket = "${var.prefix}-bucket-${var.postfix}"
}

resource "aws_s3_bucket_versioning" "name" {
    bucket = aws_s3_bucket.backend_bucket.id

    versioning_configuration {
        status = "Enabled"
    }
}

resource "aws_dynamodb_table" "backend_table" {
  name = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

테라폼 상태 파일의 버전 관리를 위해 S3 버킷의 버전 관리 옵션은 반드시 활성화시켜줍니다.
테라폼 백엔드는 프로바이더를 파일을 정의할 때 설정하면 되는데요. 백엔드 리소스가 생성되면 프로제트의 provider.tf를 아래와 같이 설정해줍니다.

terraform {
  required_version = "~> 1.5.2"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 5.0, <=5.6.2"
    }
  }

  backend "s3" {
    bucket = "tf-backend-bucket-230708"
    key = "test/terraform.tfstate"
    region = "ap-northeast-2"
    dynamodb_table = "terraform-locks"
  }
}

provider "aws" {
  region  = var.region
  profile = var.account_profile
}

bucket에는 백엔드로 생성한 버킷의 이름을, key에는 테라폼 상태 파일이 저장될 S3 버킷의 경로를 입력해줍니다.

프로젝트의 인프라를 배포하면 아래와 같이 백엔드 버킷에 tfstate 파일이 생성되는 것을 확인할 수 있습니다.

03. Lifecycle - Precondition

소스코드

테라폼에는 수명주기(Lifecycle)이 있습니다. 테라폼 수명주기를 이용하면 리소스 배포 시 세부적인 동작을 지정할 수 있습니다. 테라폼의 수명주기는 아래와 같습니다.

  • create_before_destroy: 기본적으로 테라폼은 리소스 삭제 전 새 리소스를 생성하지만 이 옵션을 사용하면 테라폼이 리소스를 먼저 생성한 후 기존의 리소스를 삭제합니다.
  • prevent_destroy: 테라폼이 해당 리소스를 삭제하는 것을 방지할 수 있습니다.
  • ignore_changes: 해당 옵션에 지정된 속성은 코드가 아닌 외부에서 변경 사항이 발생하더라도 테라폼 코드에 맞게 변경하려 하지 않습니다.
  • replace_triggered_by: 다른 속성이 변경되면 이 옵션에 해당하는 리소스도 변경됩니다.

수명 주기를 사용할 때 precondition 혹은 postcondition 블록을 사용하면 조건을 통해 리소스 동작을 제어할 수 있습니다. precondition이 충족되지 않으면 동작이 실행되지 않으며, postcondition의 조건을 만족하지 않으면 동작 실행이 실패합니다.

resource "local_file" "test_file" {
  content  = "lifecycle test"
  filename = "${path.module}/${var.file_name}"

  lifecycle {
    postcondition {
      condition     = contains(["step00.txt", "step01.txt", "step02.txt", "step03.txt", "step04.txt", "step05.txt", "step06.txt", "step07.txt"], "${var.file_name}")
      error_message = "step is over \"step07\""
    }
  }
}

이 코드에서는 postcondition을 통해 리소스 블록에서 사용되는 파일의 이름을 체크하고 있습니다. 테라폼을 실행시켰을 때 step00 ~ step07에 해당하는 이름이 아니라면 에러 메시지를 띄웁니다.

04. 테라폼 공식 문서 실습 - EBS Attach

소스코드
테라폼 공식 문서를 따라 EC2 서버에 EBS를 Attach하는 간단한 실습을 진행하였습니다.EBS 리소스 배포 코드는 아래와 같습니다.

resource "aws_volume_attachment" "ebs_att_apache_web_server" {
  device_name = "/dev/sdh"
  volume_id   = aws_ebs_volume.apache_web_server_volume.id
  instance_id = aws_instance.apache_web_server.id
}

resource "aws_ebs_volume" "apache_web_server_volume" {
  availability_zone = aws_instance.apache_web_server.availability_zone
  size              = 1

tags = {
    Name = "${var.prefix}-ebs"
  }
}

0개의 댓글