CLOUDWAVE

IaC : Terraform 실습 - 단일 서버배포, 단일 웹서버배포, 구성가능한 웹서버배포

갬짱 2025. 2. 7. 22:35

 

 

기존의 포트값 = 기존과 동일한 인스턴스의 사용 → destory작업이 전혀 없음

Terraform을 이용한 단일서버 배포 실습

VSCode에서 ssh로 가상머신에 연결하여 작업( 권장 )

 

 

Terraform 코드는 확장자가 .tf 인 파일에 HCL(HashiCorp 구성 언어)로 작성 ( main.tf ) 이때 tf파일에는 ACESS_KEY등의 개인정보를 입력하지 않도록 함 ( 노출의 위험 )

 

 

(1) 공급자 구성

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

Terraform에 AWS를 공급자로 사용할 것이며 인프라를 ap-northeast-2 지역에 배포할 것임을 선언

  • aws provider를 사용하겠다는 선언 → aws 전용 바이너리를 다운받아서 사용가능
  • region을 고정하여 프로비저닝 되는 리소스들의 region값을 고정한다

 

(2) 리소스 생성

리소스 생성 형식 : provider( aws,gcp .. ) + type( resource, vpc .. ) + “name”

resource "<PROVIDER>_<TYPE>" "<NAME>" {
  [ CONFIG ...]
}
  • provider : 공급자 이름 공급자별로 사용하는 바이너리는 호환되지 않음 → 명확하게 프로바이더를 지정
  • type : 생성할 리소스 유형 ( vpc, subnet, instance …. )
  • name : 테라폼 코드 전체에서 리소스를 참조할때 사용할 수 있는 식별자 클라우드에서 리소스의 이름을 지정하여 만드는 것이 아니라 테라폼에서 리소스를 관리할때 메타데이터로 활용하는 이름
resource "aws_instance" "example" {
	ami = "ami-0f3a440bbcff3d043"
	instance_type = "t2.micro"
}

aws_instance 리소스 ⇒ ami & instance_type 두개의 인수만 설정

  • ami : EC2 인스턴스에서 실행할 Amazon 머신 이미지(AMI)
    ami는 리전마다 다르고, 업데이트가 발생하면서 변경가능 ⇒ 필요시마다 정확하게 찾아서 넣어야함( aws cli를 이용하거나 콘솔의 AMI 카탈로그 )
    Amazon Linux 2023 AMI ↔ Amazon Linux2 AMI(HVM) : 호환성이 전혀 안됨(2) AWS 콘솔 AMI 카탈로그에서 AMI번호 검색
    (1) aws ec2 describe-images : CLI 명령어를 이용해 AMI 번호 찾기
    (2) AWS 콘솔 AMI 카탈로그에서 AMI번호 검색

  • instance_type : 실행할 EC2 인스턴스 유형 → 서로 다른 양의 CPU, 메모리, 디스크 공간 및 네트워킹 용량을 제공

 

[ init 명령 ]

tf파일이 작성된 working directory에서 init명령을 수행

terraform init
  • 테라폼 코드를 스캔하고 사용중인 공급자를 파악하여 해당 바이너리 코드를 다운받도록 지시 ⇒ 스크래치 디렉터리인 .terraform 폴더에 다운로드
  • 다운로드한 공급자 코드에 대한 정보를 .terraform.lock.hcl 파일에 기록( 해시값 ) ⇒ init을 여러번 실행하도 멱등성을 보장

Initializing provider plugins : provider API로 요청을 보낼 수 있는 바이너리를 설치하는 작업( 프로바이더의 플러그인 바이너리를 다운로드 )

 

[ plan 명령 ]

실제로 변경하기 전에 Terraform이 수행할 작업을 확인

terraform plan

현재 pwd의 tf파일을 모두 읽어서 조합된 리소스 생성을 인식 → 자동으로 순서구성, 사용자에게 보고

더하기 기호(+) 항목은 모두 생성, 빼기 기호(-) 항목은 모두 삭제, 물결표 기호(~) 항목은 모두 수정

known after apply : 만들고 난 이후에 확인가능한 값

plan의 속성일부( key_name 등 )은 tf파일에서 지정가능

 

[ apply 명령 ]

terraform apply

이름 없는 EC2가 생성

terraform apply -auto-approve옵션을 사용할경우 yes를 입력하지 않아도됨

 

alias tfa='terraform apply -auto-approve'
alias tfd='terraform destroy -auto-approve'

alias를 지정하여 편하게 사용하기

~/.bashrc에 저장 ⇒ 적용 source ~/.bashrc ( 세션이 종료되어도 이용가능 )

 

[ cache directory ]

./.terraform : 바이너리를 다운로드 받는 위치

동일한 벤더의 프로바이더를 사용하는 다른 프로젝트에서는 새롭게 다운로드 받아서 개별적으로 사용하는 것보다(630M) 바이너리를 공유하는 것이 효율적임 → 공유가능한 cache directory구축하여 사용

cache directory : 다른 프로젝트 dir에서도 이곳의 바이너리를 참조하여 사용가능, 추가적인 다운로드 필요없음

 

.terraformrc 파일 : Terraform CLI의 설정 파일로, Terraform의 동작 방식을 사용자 정의할 수 있는 설정 옵션을 포함( 사용자 환경에 맞게 플러그인 캐싱, 인증 정보, 리모트 작업 설정 등을 관리 ) 설정에 따라 현재 디렉터리의 .terraform/plugins가 아닌 지정된 캐시 디렉터리($HOME/.terraform.d/plugin-cache)에서 플러그인을 다운로드하고 참조

 

 

Terraform을 이용한 단일 웹서버 배포

 

#!/bin/bash
echo "Hello, World" > index.html
nohub busybox httpd -f -p 8080 &
  • nohup : 터미널 종료와 상관없이 프로세스가 계속 실행 ( 로그는 nohup.out 파일에 저장 )
    -f 옵션으로 실시간 로그확인(데몬 프로세스화X) + &옵션으로 터미널 즉시 반환
  • busybox : 리눅스 에뮬레이터, 각종 테스트 도구 탑재 → 내장된 busybox httpd서버 실행

 

[ 스크립트를 바로 실행하기 위한 EC2 인스턴스를 생성 ]

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

resource "aws_instance" "example" {
  ami = "ami-024ea438ab0376a47"
  instance_type = "t2.micro"
  user_data  =  <<-EOF
                #!/bin/bash
                echo  "Hello, World"  >  index.html
                nohup  busybox  httpd  -f  -p  8080  &
                EOF
  user_data_replace_on_change  =  true

  tags  =  {
    Name  =  "terraform-example"
  }
}

  • 인스턴스의 user data로 해당 스크립트를 입력 Terraform 코드에서 user_data 인수를 설정하여 사용자 데이터에 셸 스크립트를 전달
    • <<-EOF 및 EOF : Terraform의 heredoc 구문으로, 여기저기에 \n 문자를 삽입하지 않고도 여러 줄 문자열을 생성
  • user_data_replace_on_change = true : user_data(초기화 스크립트)가 변경되면 EC2 인스턴스를 교체 ( 구성의 변경이나 업데이트시 새로운 것으로 대체 ) ⇒ user_data는 첫번째 부팅시에만 실행되고 이미 부팅을 거친 인스턴스에서는 수정시에 실행될 수 없기에 이와같이 지정한다.
  • AWS 콘솔에서 리소스를 구분하기 위해 사용되는 tag를 추가 ( EC2 인스턴스에 Name 태그를 지정하여 식별 )

 

 

[ 보안그룹 리소스 추가 ]

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
  • AWS 콘솔에서 보이는 Security Group의 이름은 instance(테라폼 코드 내부 식별용)가 아니라 terraform-example-instance로 지정된다.
  • ingress {} 블록: 인바운드 트래픽 규칙을 정의
    • from_port( 허용 트래픽의 시작포트번호 ) ~ to_port( 허용 트래픽의 끝포트 번호 )
    • protocol : 트래픽의 프로토콜 지정 & cidr_blocks : 트래픽의 CIDR 블록 범위를 지정
  • TCP 프로토콜8080 포트에 대해 모든 IP 주소(0.0.0.0/0)에서 인바운드 트래픽이 허용

 

[ 보안그룹 적용 ]

보안 그룹의 ID를 aws_instance 리소스의 vpc_security _group_ids 인수에 전달하여 EC2 인스턴스에 실제로 사용하도록 지시

vpc_security_groups_ids = [aws_security_group.instance.id] : 해당 보안그룹의 ID를 참조하도록 한다 ⇒ security group의 적용은 가상머신의 삭제, 재기동 없이 자동으로 이루어짐

( ** 테라폼 표현식 : <PROVIDER>_<TYPE> . <NAME> . <ATTRIBUTE> → 해당 값을 반환 )

적용이전에 default security group이 지정됨

적용이후 terraform-example-instance 보안그룹이 지정됨

publicIP:지정포트로 인스턴스에 접속가능

 

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

resource "aws_instance" "example" {
  ami                         = "ami-024ea438ab0376a47"
  instance_type               = "t2.micro"
  vpc_security_groups_ids = [aws_security_group.instance.id]
  user_data                   = <<-EOF
                                #!/bin/bash
                                echo "hello world" > index.html
                                nohup busybox httpd -f -p 8080 &
  EOF
  user_data_replace_on_change = true

  tags = {
    Name = "terraform-example"
  }
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

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

 

 

Terraform을 이용한 구성가능한 웹서버 배포

DRY(Don’t Repeat Yourself- 반복하지 않는다) 원칙에 따라 반복되는 값은 변수로 정의 → 관리 및 업데이트 용이

 

[ 입력변수 : variable 키워드 ]

variable "NAME" {
	[CONFIG ...]
}

다양한 config설정

  • type : 변수 유형 제약조건
    number, string, bool, list(인덱스와 데이터로 구성), map(키와 값으로 구성), object(객체유형, 내부속성의 유형제약조건 )
  • description : 변수의 목적, 사용법
  • default : 기본값 → 명령줄, 파일, 환경변수 등으로 값을 제공받아 대체가능
  • validation : 입력변수에 대한 사용자 정의 유효성 검사 규칙 정의
  • sensitive : terrform plan, terraform apply 시에 기록하지 않음 ⇒ 비밀값( 비밀번호, API 키 ) 등에 적용

[ 예시 ] 중복된 포트값(8080)을 server_port라는 변수로 지정하여 활용

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

resource "aws_instance" "example" {
  vpc_security_group_ids = [aws_security_group.instance.id]
  ami                         = "ami-024ea438ab0376a47"
  instance_type               = "t2.micro"
  user_data                   = <<-EOF
                                #!/bin/bash
                                echo "hello world" > index.html
                                nohup busybox httpd -f -p ${var.server_port} &
  EOF
  user_data_replace_on_change = true

  tags = {
    Name = "terraform-example"
  }
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

  ingress {
    from_port   = var.server_port
    to_port     = var.server_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

variable "server_port" {
  description = "The port. the server will user for HTTP requests"
  type = number
}
    • 변수 선언 : variable키워드로 server_port라는 변수 선언 ( 숫자타입 ) ( * 이는 리소스가 아님 )
    • 변수 참조식
      • var.<VARIABLE_NAME> : from_port, to_port의 값을 var.server_port로 지정 ( “”을 사용하지 않으면 참조의 의미 )
      • ${ var.<VARIABLE_NAME> } : userdata에서는 변수 랜더링이 필요함 ${ var.server_port }
    • 다양한 변수 값 지정 방식
      • terraform apply 작업시에 프롬프트로 해당값을 입력받음( 어떤 변수에서도 설정하지 않음 )
      • -var 명령줄 옵션으로 값 지정 terraform plan -var "server_port=8080"
      • 환경변수 TF_VAR_<name>으로 값 설정 ( export ) export TF_VAR_server_port=8080
      • default 값으로 설정

 

 

포트를 변수화하여 변경하고 apply할때 → 테라폼의 판단으로 일부 리소스는 수정, 일부는 삭제 후 재생성

  • 기존의 ec2인스턴스(aws_instance.example)는 destory되고 새로운 포트의 인스턴스가 생성
  • 기존의 보안그룹(aws_security_group.instance)은 단순히 업데이트만 이루어짐( modifying )

기존의 인스턴스는 종료되고 새롭게 생긴 인스턴스의 보안그룹 포트는 8888임

새로운 퍼블릭주소를 가진 인스턴스에 8888포트로 접속

 

[ 출력변수 : output 키워드 ]

프로비저닝의 목표 : 리소스 생성 + 즉시제공 ( terraform이 프로비저닝 이후 사용자가 즉시 리소스를 활용하도록 정보를 전달 )

output "<NAME>" {
	value = <VALUE>
	[CONFIG ...]
}

value값은 생성된 리소스의 속성값으로 지정 <PROVIDER>_<TYPE> . <NAME> . <ATTRIBUTE>

 

[ 예시 ] 새롭게 할당된 public ip를 프로비저닝후 즉시 제공

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

resource "aws_instance" "example" {
  vpc_security_group_ids = [aws_security_group.instance.id]
  ami                         = "ami-024ea438ab0376a47"
  instance_type               = "t2.micro"
  user_data                   = <<-EOF
                                #!/bin/bash
                                echo "hello world" > index.html
                                nohup busybox httpd -f -p ${var.server_port} &
  EOF
  user_data_replace_on_change = true

  tags = {
    Name = "terraform-example"
  }
}

resource "aws_security_group" "instance" {
  name = "terraform-example-instance"

  ingress {
    from_port   = var.server_port
    to_port     = var.server_port
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

variable "server_port" {
  description = "The port. the server will user for HTTP requests"
  type = number
}

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

기존의 포트값 = 기존과 동일한 인스턴스의 사용 → destory작업이 전혀 없음

 

+) output 변수값 활용하기

생성후 추후에도 그 값들을 열람가능( terraform output )

특정 output변수만 열람가능( terraform output <output_NAME> )

 

output변수를 참조가능 ( terraform output -raw <NAME> )

output 값에 대한 테스트 ⇒ 스크립트를 생성하여 활용가능

#!/bin/bash

curl $(terraform output -raw public_ip):8888

#!/bin/bash

curl $(terraform output -raw public_ip):8888

if [ $? == 0 ]
then
  echo "success";
fi

⇒ 파이프라인 형식을 구축하여 순차적으로 개발, 검증 등의 프로세스를 수행할 수 있다. ( CI/CD의 핵심은 자동화된 테스트 )

⇒ input을 투입하면 파이프라인 과정을 거쳐서 원하는 output이 자동으로 생성된다.