Terraform으로 위 AWS 아키텍처를 구현해보려고 한다.
( 아래에 사용된 코드는 Git Hub에서 참조 가능합니다. )
1. Terraform 설치
위 공식문서를 참고하여 Terraform을 설치한다. 나는 MacBook이므로 MacOS 방식으로 설치하였다.
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
brew update
brew upgrade hashicorp/tap/terraform
설치가 완료되면 버전정보로 설치가 되었는지 확인한다.
terraform --version
2. AWS Provider 생성하기
main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "ap-northeast-2" # Region 서울
access_key = "your_access_key" # IAM Access Key
secret_key = "your_secret_key" # IAM Secret Key
}
AWS Provider란, AWS가 제공하는 API와 통신하는 플러그인을 의미한다. Terraform은 개발자가 AWS API를 쉽게 사용할 수 있도록 플러그인을 제공한다. required_providers 블록에 사용할 Provider를 명시하고 provider 블록에 Provider 설정을 한다.
IAM은 AdministratorAccess 권한을 가진 계정으로 생성하고 AccessKey와 SecretKey를 발급받는다.
3. RSA 개인키, 공개키 생성하기
main.tf
resource "tls_private_key" "rsa_4096" {
algorithm = "RSA"
rsa_bits = 4096
}
variable "key_name" {
description = "pem file"
type = string
default = "lok_pem"
}
resource "aws_key_pair" "key_pair" {
key_name = var.key_name
public_key = tls_private_key.rsa_4096.public_key_openssh
}
resource "local_file" "private_key" {
content = tls_private_key.rsa_4096.private_key_pem
filename = var.key_name
}
로컬에서 EC2 인스턴스에 SSH 원격접속을 할 때 필요한 키를 생성해보자.
로컬에는 PEM 파일형식의 개인키가 있고 EC2는 암호화 된 공개키를 갖는다. SSH프로토콜로 개인키를 전달하여 EC2인스턴스의 공개키를 RSA 방식의 알고리즘으로 복호화에 성공하면 원격접속이 가능해지는 원리이다.
tls_private_key 리소스 : private key를 PEM 파일 형태로 생성 가능한 리소스이다. 알고리즘은 RSA 방식을 선택한다.
aws_key_pair 리소스 : tls_private_key 리소스가 생성한 암호화된 public key를 갖고 있는 리소스이다. EC2가 해당 리소스를 참조한다local_file 리소스 : tls_private_key 리소스가 생성한 PEM 파일(개인키)을 로컬환경에 생성한다.
4. VPC 생성하기
main.tf
#VPC 생성하기
resource "aws_vpc" "demoVPC" {
cidr_block = "10.10.0.0/16"
}
#서브넷 생성하기 ( 가용영역A )
resource "aws_subnet" "demoSubnet_a" {
vpc_id = aws_vpc.demoVPC.id
cidr_block = "10.10.1.0/24"
availability_zone = "ap-northeast-2a"
tags = {
Name = "demoSubnet_a"
}
}
#서브넷 생성하기2 ( 가용영역B )
resource "aws_subnet" "demoSubnet_b" {
vpc_id = aws_vpc.demoVPC.id
cidr_block = "10.10.2.0/24"
availability_zone = "ap-northeast-2c"
tags = {
Name = "demoSubnet_b"
}
}
VPC와 Subnet에 대한 자세한 설명은 아래 포스팅에 정리해놓았다.
VPC와 서브넷의 CIDR을 설정하고 서브넷에는 가용영역을 설정한다.
5. 인터넷 게이트웨이와 라우팅 테이블 설정하기
#인터넷 게이트웨이 생성하기
resource "aws_internet_gateway" "demo-igw" {
vpc_id = aws_vpc.demoVPC.id
tags = {
Name = "main"
}
}
#라우팅 테이블 생성하기
resource "aws_route_table" "demo-rt" {
vpc_id = aws_vpc.demoVPC.id
route {
cidr_block = "0.0.0.0/0" #인터넷 게이트웨이
gateway_id = aws_internet_gateway.demo-igw.id
}
tags = {
Name = "demo-rt"
}
}
# 라우팅 테이블 어소시에이션 생성하기1
resource "aws_route_table_association" "demo-rt-association_a" {
subnet_id = aws_subnet.demoSubnet_a.id
route_table_id = aws_route_table.demo-rt.id
}
# 라우팅 테이블 어소시에이션 생성하기2
resource "aws_route_table_association" "demo-rt-association_b" {
subnet_id = aws_subnet.demoSubnet_b.id
route_table_id = aws_route_table.demo-rt.id
}
VPC는 인터넷망과 차단되어 있는 네트워크이다. 인터넷의 트래픽이 VPC 내부로 들어오려면 인터넷 게이트웨이를 생성해야 한다. 인터넷 게이트웨이는 인터넷 트래픽이 VPC 내부로 들어올 수 있는 입구 역할을 한다.
라우팅 테이블은 인터넷 트래픽이 어디로 흘러가야 하는지 경로를 정보를 담고 있다. 라우팅테이블 어소시에이션을 생성하여 서브넷 정보가 라우팅 테이블에 저장된다. 그러면 외부에서 들어온 인터넷 트래픽이 라우팅 테이블 정보를 토대로 서브넷을 찾아갈 수 있다. 그렇다면 VPC 내부에서 외부로 트래픽을 보내려면 어떻게 할까? 예를들어 VPC 내부 호스트가 13.24.52.0/24에 트래픽을 전송한다고 가정해보자. 그런데 13.24.52.0/24과 관련된 네트워크 정보는 라우팅테이블에 저장되어 있지 않다. 그러면 디폴트로 저장된 0.0.0.0/0, 즉 인터넷 게이트웨이로 트래픽을 라우팅하여 VPC 외부로 트래픽을 전달한다.
6. VPC 내부 보안그룹 생성하기
resource "aws_security_group" "demoVPC-sg" {
name = "demoVPC-sg"
vpc_id = aws_vpc.demoVPC.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
tags = {
Name = "allow_tls"
}
}
ingress는 인바운드 규칙이고 egress는 아웃바운드 규칙이다. 해당 보안그룹을 LoadBalancer와 EC2 인스턴스가 같이 사용할 것이다. LoadBalancer는 80포트를 열어 트래픽을 받는다. EC2 인스턴스는 LoadBalancer와 포트포워딩할 80포트와 SSH 통신을 위한 22포트를 오픈한다.
7. LoadBalancer 생성하기
# 로드밸런서 생성하기
resource "aws_lb" "demo-alb" {
name = "demo-alb"
internal = false # 외부 트래픽 접근 가능
load_balancer_type = "application"
security_groups = [aws_security_group.demoVPC-sg.id]
subnets = [aws_subnet.demoSubnet_a.id, aws_subnet.demoSubnet_b.id]
}
# 로드밸런서 Listener 생성하기
resource "aws_lb_listener" "demo-lb-listener" {
load_balancer_arn = aws_lb.demo-alb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "forward" # forward or redirect or fixed-response
target_group_arn = aws_lb_target_group.my_tg.arn
}
}
# 대상그룹 생성하기
resource "aws_lb_target_group" "my_tg" {
name = "my-tg"
target_type = "instance"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.demoVPC.id
}
'로드밸런서'의 타입은 Application 계층 수준의 데이터를 주고 받을 것이므로 Application으로 하고 VPC 외부의 트래픽도 로드밸런서로 들어올 수 있도록 internal 설정을 false로 한다. 그리고 보안그룹을 설정하고 로드밸런싱 할 서브넷도 정보도 설정한다.
'로드밸런서 리스너'는 로드밸런싱을 어떤 식으로 할지 룰을 정한다. HTTP 프로토콜로 80포트로 들어온 트래픽은 대상그룹으로 forward한고도 명세되어 있다.
'대상그룹'은 AWS가 관리하는 인스턴스 정보 목록이다. AWS는 VPC와 인스턴스를 연결하여 대상그룹으로 관리하는데, 로드밸런서 리스너는 대상그룹에서 인스턴스 정보를 얻어 트래픽을 라우팅한다. 대상그룹 설정에 따르면, demoVPC 안의 인스턴스가 HTTP 프로토콜로 80포트를 열어놓는다고 명세되어 있다.
정리하면, 로드밸런서 리스너는 로드밸런서 80포트로 유입된 외부트래픽을 demoVPC 안에있는 인스턴스의 80포트로 forwarding한다는 의미다.
8. 인스턴스 생성 및 AutoScaling 설정하기
main.tf
# 인스턴스 생성하기
resource "aws_launch_template" "my_launch_template" {
name = "my_launch_template"
image_id = "ami-086cae3329a3f7d75" # 인스턴스 이미지
instance_type = "t2.micro" # 인스턴스 타입
key_name = aws_key_pair.key_pair.key_name #SSH Key 정보
# 인스턴시 Lanuch시 실행될 명령어 모음
user_data = filebase64("${path.module}/server.sh")
network_interfaces {
associate_public_ip_address = true # Public IP 생성
security_groups = [ aws_security_group.demoVPC-sg.id ] #보안그룹 설정
}
}
# AutoScaling Group 생성하기
resource "aws_autoscaling_group" "my-asg" {
name = "my-asg"
max_size = 2
min_size = 2
desired_capacity = 2
target_group_arns = [aws_lb_target_group.my_tg.arn]
vpc_zone_identifier = [ aws_subnet.demoSubnet_a.id, aws_subnet.demoSubnet_b.id ]
launch_template {
id = aws_launch_template.my_launch_template.id
version = "$Latest"
}
드디어 마지막 단계이다!
aws_launch_template 리소스는 EC2 인스턴스를 생성하는 리소스이다. aws_instance 리소스로도 EC2 인스턴스를 생성할 수 있지만 Auto Scaling이 가능하려면 aws_launch_template 리소스로 EC2 인스턴스를 생성해야 한다. Auto Scaling은 최대 EC2 인스턴스 수와 최소 EC2 인스턴스 수를 설정하고 그 사이에서 EC2 인스턴스를 유지하는 것을 의미한다.
- EC2 생성하기
인스턴스 이미지 ID는 Instance Type은 AWS 콘솔로 들어가 인스턴스 생성을 눌러 확인하면 된다.
key_name은 SSH 원격접속을 위한 데이터로 위에서 만든 key-pair를 설정한다.
user_data는 인스턴스 launch시, 동작할 명령어 모음을 가리킨다. main.tf 와 동일한 경로에 server.sh 파일을 생성한다.
server.sh
#!/bin/bash
apt update
apt upgrade -y
apt install apache2 -y
echo "<h1>Hello world from highly available group of ec2 instances</h1>" > /var/www/html/index.html
systemctl start apache2
systemctl enable apache2
아파치 웹서버를 EC2 인스턴스에 띄운다. 이는 실제로 인스턴스가 제대로 올라왔는지 확인용으로 올리는 것이다.
네트워크 인터페이스는 보안그룹을 설정하고 Public IP를 할당받도록 한다. 로드밸런서로 인스턴스에 접근할 것이기에, 인스턴스가 Public IP를 가질 필요는 없지만 테스트용으로 Public IP를 할당받았다.
- Auto Scaling 설정하기
EC2 설정이 끝나면 Auto Scaling 설정을 해야 한다. Auto Scaling 할 max,min, desired 수를 정하고 대상그룹과 인스턴스를 생성할 VPC 내 서브넷 정보를 설정한다. 그리고 마지막으로 EC2를 생성한 aws_launch_template 리소스를 설정하면 된다.
8. AWS 인프라 리소스 생성하기
terraform init
main.tf 설정파일 작성이 완료되면 이를 토대로 워킹디렉토리를 초기화해야 한다.
terraform plan
terraform이 AWS 인프라 리소스를 생성하기 전에 이전과 변경된 점을 비교한다.
terraform apply --auto-approve
terraform에게 AWS 인프라 리소스를 생성을 명령한다. --auto-approve는 yes 답변을 자동으로 해준다.
이와 같이, Apply 성공문구가 뜨면 AWS 콘솔로 이동하여 리소스가 제대로 생성되었는지 확인한다.
VPC 정보를 확인해보자.
VPC는 두 개의 가용영역 안에 두 개의 Subnet을 만들었고 이는 우리가 만든 라우팅 테이블과 연결되어 있다. 그리고 라우팅 테이블은 main이라는 Internet Gateway와도 연결되어 있다.
그럼 로드밸런서로 이동해보자.
로드밸런서가 생성되었고 DNS도 갖게 되었다. 그럼 DNS 이름으로 요청을 보내어 실제 아파치가 떠있는 EC2로 트래픽이 전달되는지 확인해보자.
Hello World가 잘 떴다.
terraform destroy --auto-approve
만약 생성한 모든 AWS 리소스를 삭제하고 싶다면 destroy 명령어를 사용하면 된다.
이처럼 Terraform은 개발자가 AWS 콘솔에 접근하여 하나하나 리소스를 생성하는 것이 아닌, 코드로 한번에 만들고 삭제할 수 있도록 도와준다. AWS 뿐만 아니라 다른 플랫폼 환경의 리소스도 쉽게 생성할 수 있다. Terraform을 활용하여 손쉬운 AWS 리소스 생성을 경험해보기를 추천한다.
참고자료