본 시리즈는 CloudNet@의 테라폼 기초 입문 스터디(T101)원으로 참여하여 학습한 내용을 정리한 것임을 밝힌다. 실습을 통해 AWS 리소스를 Terraform으로 직접 배포해보는 과정을 연재할 예정이다.
terraform
이라는 명령어로 실행할 수 있다.가장 기본적인 차이점은 Terraform은 오케스트레이션 도구이고 Ansible이 구성 관리 도구라는 것이다. 이해를 돕기 위해 상황을 오케스트라에 비유해보자.
오케스트레이션 툴은 지휘자와 비슷하다고 생각하면 된다. 지휘자는 적절한 수의 악기가 있고, 그것들이 함께 잘 연주되고 있는지 확인한다. 만약 문제가 있다면 보통 동작에 문제가 있는 기기를 없애고 다른 기기로 교체한다. 즉, 최종 결과에 초점을 맞추고 있어 환경이 항상 그 상태에 있음을 보증하는데 도움이 된다.
Terraform은 환경의 상태를 저장하고 있다가 고장나거나 누락된 경우 다시 실행될 때 리소스를 자동으로 제공한다. 이는 매우 안정된 상태를 필요로 하는 환경에 매우 적합하다.
반면 구성관리 툴은 기기의 수리 담당자와 같다. Ansible 또한 각 기기를 구성하여 손상이 없고 올바르게 기능하고 있는지를 확인한다. 하지만 동작에 문제가 있는 경우 기기를 완전히 교체하는 것이 아니라 문제를 복구하기 위한 기능을 한다.
이런 점에서 Ansible도 어느 정도 인프라스트럭처를 대체하거나 오케스트레이션 태스크를 수행할 수도 있지만, Terraform이 더욱 고도의 관리가 가능하므로 일반적으로 더 우수한 제품으로 간주되고 있다.
이미지 출처: https://betterprogramming.pub/how-terraform-works-a-visual-intro-6328cddbe067
AWS 계정
AWS IAM User 생성 : 실습 편리를 위해 관리자 수준 권한(AdministratorAccess) 부여, 프로그래밍 방식 액세스 권한 부여(Access/Secret Key)
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=""
(선택) 실습에 편의를 주는 툴 설치 : watch, jq, tree 등
작업 환경: 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
mkdir Study-Terraform
cd Study-Terraform
코드 파일 작성
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
목표: 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대를 배포하면서, 필요한 애플리케이션 설정을 자동화하였다.
현재까지 실습한 보안 그룹과 유저 데이터 코드를 보면 웹 서버 코드에 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.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 명령어를 통해 테라폼으로 생성한 리소스를 삭제하자.
간단하게나마 테라폼의 동작방식을 엿볼 수 있는 즐거운 실습이었다.
다만 코드만으로는 처음부터 전체적인 그림을 그리기가 어려우니 초보라면 반드시 콘솔에서 수동으로 리소스를 생성해보고 이해하는 과정이 선행되어야 할 것 같다.