ASG(Auto Scaling groups) 배포
💡 아래 설정(옵션들)과 동작 이해를 위해서 ‘AWS 관리 콘솔’에서 수동으로 직접 ELB, EC2 ASG(오토 스케일링) 등을 배포 후에 아래 테라폼 실습을 따라 할 것!
목표
: 오토스케일링 그룹으로 EC2 배포
graph LR; A(((User))); A --> B(EC2 Instance); A --> C(EC2 Instance); A --> D(EC2 Instance); A --> E(EC2 more...); subgraph AWS; B & C & D & E; end;
수명 주기(lifecycle) 설정 : 리소스를 교체하는 순서를 반대로 하여 교체 리소스를 먼저 생성하고 기존 리소스 삭제 동작 설정 가능
- 오토스케일링 시작 구성(launch configuration) : aws_instance 리소스와 거의 동일한 매개 변수를 사용
- 일부 파라미터는 지원하지 않을 수 있으며, 몇몇 파라미터는 이름이 다르기도 하다
- ami → image_id
- vpc_security_group_ids → security_groups
- 아래 ASG 예시 : EC2 2대~10대 사이 중 초기 시작은 기본값 2대로 실행되고, 태그 지정됨
# 샘플 코드 : 실행을 하지는 않음
resource "aws_autoscaling_group" "example" {
launch_configuration = aws_launch_configuration.example.name
min_size = 2
max_size = 10
tag {
key = "Name"
value = "terraform-asg-example"
propagate_at_launch = true
}
}
- ASG는 시작 구성 정보를 참고하여 인스턴스를 생성하는데 한 가지 문제가 발생 → 시작 구성은 변경할 수 없으므로 시작 구성 매개 변수를 변경 시 테라폼이 이를 대체하려고 합니다.
- 일반적으로 리소스를 교체할 때 테라폼은 이전 리소스를 먼저 삭제한 다음 대체 리소스를 생성합니다. 그러나 ASG에 이전 리소스에 대한 참조가 있기 때문에 테라폼은 해당 리소스를 삭제 할 수 없습니다.
- 이 문제 해결을 위해 수명 주기 설정(lifecycle)를 할 수 있습니다.
- 모든 테라폼 리소스는 리소스 생성, 업데이트 및 삭제 방법을 구성하는 몇 가지 수명 주기(lifecycle) 설정을 지원합니다.
- 특히 create_before_destroy 를 true 로 설정하면 테라폼은 리소스를 교체하는 순서를 반대로 하여 교체 리소스를 먼저 생성하고(이전 리소스가 가리키고 있던 참조를 업데이트하여 교체한 리소스를 가리킴) 기존 리소스를 삭제합니다.
- 아래와 같인 lifecycle 블록을 aws_launch_configuration 에 추가합니다
# 샘플 코드 : 실행을 하지는 않음
resource "aws_launch_configuration" "example" {
image_id = "ami-0e9bfdb247cc8de84"
instance_type = "t2.micro"
security_groups = [aws_security_group.instance.id]
user_data = <<-EOF
#!/bin/bash
echo "Hello, World" > index.html
nohup busybox httpd -f -p ${var.server_port} &
EOF
# Required when using a launch configuration with an auto scaling group.
**lifecycle** {
**create_before_destroy = true**
}
}
ASG 배포 : asg.tf
- 코드 파일 작성 : CMD창에서 바로 복사&붙여넣기로 main.tf 파일 생성 시 ** 주의 ⇒ 그냥 직접 **IDE에 붙여넣을때는 **** 제거하세요
cat <<EOT > asg.tf
data "aws_ami" "my_amazonlinux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_launch_configuration" "mylauchconfig" {
name_prefix = "t101-lauchconfig-"
image_id = data.aws_ami.my_amazonlinux2.id
instance_type = "t2.micro"
security_groups = [aws_security_group.mysg.id]
associate_public_ip_address = true
user_data = <<-EOF
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
RZAZ=\$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
IID=\$(curl 169.254.169.254/latest/meta-data/instance-id)
LIP=\$(curl 169.254.169.254/latest/meta-data/local-ipv4)
echo "<h1>RegionAz(\$RZAZ) : Instance ID(\$IID) : Private IP(\$LIP) : Web Server</h1>" > index.html
nohup ./busybox httpd -f -p 80 &
EOF
# Required when using a launch configuration with an auto scaling group.
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "myasg" {
name = "myasg"
launch_configuration = aws_launch_configuration.mylauchconfig.name
vpc_zone_identifier = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
min_size = 2
max_size = 10
tag {
key = "Name"
value = "terraform-asg"
propagate_at_launch = true
}
}
EOT
배포
# [터미널1] EC2 생성 모니터링
# macOS
export AWS_PAGER=""
while true; do aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output text ; echo "------------------------------" ; sleep 1; done
# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_autoscaling_group.myasg
aws_launch_configuration.mylauchconfig
...
# curl 접속 확인
IP1=<EC2 1번 Public IP>
IP2=<EC2 2번 Public IP>
echo "$IP1 - WebSrv" ; curl -s $IP1 ; echo ; echo "$IP2 - WebSrv" ; curl -s $IP2 ; echo
AWS 관리 콘솔 : ASG 시작 구성, AS 그룹 확인
# ASG 시작 구성, AS 그룹 정보 확인
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names | jq
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names --output table
aws autoscaling describe-auto-scaling-groups --auto-scaling-group-names myasg| jq
aws autoscaling describe-scaling-activities --auto-scaling-group-name myasg | jq
로드 밸런서 배포
목표
: ASG 에 ALB 연동 배포
graph LR; A(((User))); F{Elastic Load Balancer}; A --> F; subgraph AWS; F --> B(EC2 Instance); F --> C(EC2 Instance); F --> D(EC2 Instance); F --> E(EC2 more...); end;
ALB 구성 : Listener, Listener rule, Target groups
graph LR; A(((Request))); A --> F; subgraph Listeners; F(Port 80, HTTP); G(Port 443, HTTPS); end; subgraph Listeners Rules; F --> B(foo.example.com); F --> C(bar.example.com); G --> D(/foo/*); G --> E(/bar/*); end; subgraph Target Group1; H(foo); I(foo); end; B & D ---> H & I; subgraph Target Group2; J(bar); K(bar); end; C & E ---> J & K;
배포 : alb.tf
- ALB 생성 ```bash cat «EOT > alb.tf resource “aws_lb” “myalb” { name = “t101-alb” load_balancer_type = “application” subnets = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id] security_groups = [aws_security_group.mysg.id]
tags = { Name = “t101-alb” } }
output “myalb_dns” { value = aws_lb.myalb.dns_name description = “The DNS Address of the ALB” }
EOT
```bash
# 배포 : 2분 10초 정도 소요
terraform plan && terraform apply -auto-approve
Outputs:
myalb_dns = "t101-alb-1753585046.ap-northeast-2.elb.amazonaws.com"
terraform state list
aws_lb.myalb
...
# ALB 정보 확인
aws elbv2 describe-load-balancers --output table
aws elbv2 describe-load-balancers | jq
# [터미널2] 출력된 ALB DNS주소로 cul 접속 확인
cd my-vpc-ec2-asg
terraform output -raw myalb_dns
t101-alb-1753585046.ap-northeast-2.elb.amazonaws.com
ALBDNS=$(terraform output -raw myalb_dns)
while true; do curl --connect-timeout 1 http://$ALBDNS/ ; echo; echo "------------------------------"; date; sleep 1; done
- ALB의 리스너 생성 : http 80 인입, 리스너 규칙과 일치하지 않을 경우 404 페이지 리턴
cat <<EOT > alb.tf
resource "aws_lb" "myalb" {
name = "t101-alb"
load_balancer_type = "application"
subnets = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
security_groups = [aws_security_group.mysg.id]
tags = {
Name = "t101-alb"
}
}
resource "aws_lb_listener" "myhttp" {
load_balancer_arn = aws_lb.myalb.arn
port = 80
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found - T101 Study"
status_code = 404
}
}
}
output "myalb_dns" {
value = aws_lb.myalb.dns_name
description = "The DNS Address of the ALB"
}
EOT
- 배포 후 접속
# 배포
terraform plan && terraform apply -auto-approve
# ALB DNS 주소 curl 접속 확인
- 다음으로 aws_lb_target_group 리소스를 사용하여 ASG의 대상 그룹을 생성
cat <<EOT > alb.tf
resource "aws_lb" "myalb" {
name = "t101-alb"
load_balancer_type = "application"
subnets = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
security_groups = [aws_security_group.mysg.id]
tags = {
Name = "t101-alb"
}
}
resource "aws_lb_listener" "myhttp" {
load_balancer_arn = aws_lb.myalb.arn
port = 80
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found - T101 Study"
status_code = 404
}
}
}
resource "aws_lb_target_group" "myalbtg" {
name = "t101-alb-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.myvpc.id
health_check {
path = "/"
protocol = "HTTP"
matcher = "200-299"
interval = 5
timeout = 3
healthy_threshold = 2
unhealthy_threshold = 2
}
}
output "myalb_dns" {
value = aws_lb.myalb.dns_name
description = "The DNS Address of the ALB"
}
EOT
# 배포
terraform plan && terraform apply -auto-approve
# ALB 대상 그룹 확인 : 아직은 타켓 대상(EC2) 없음
ALB + ASG 연동 : asg.tf
- 대상그룹은 어느 EC2에 요청을 보내는지 아나요? → ASG + ALB 통합 이점 활용(aws_autoscaling_group 리소스에 target_group_arns 인수를 설정)하여 새 대상 그룹을 지정
- 이때 헬스체크도 EC2가 아닌 ELB로 변경 → 이 경우 EC2 불량 시 자동으로 인스턴스가 교체되고 대상그룹에 등록됨
- 코드 파일 작성 : CMD창에서 바로 복사&붙여넣기로 main.tf 파일 생성 시 ** 주의 ⇒ 그냥 직접 **IDE에 붙여넣을때는 **** 제거하세요
cat <<EOT > asg.tf
data "aws_ami" "my_amazonlinux2" {
most_recent = true
filter {
name = "owner-alias"
values = ["amazon"]
}
filter {
name = "name"
values = ["amzn2-ami-hvm-*-x86_64-ebs"]
}
owners = ["amazon"]
}
resource "aws_launch_configuration" "mylauchconfig" {
name_prefix = "t101-lauchconfig-"
image_id = data.aws_ami.my_amazonlinux2.id
instance_type = "t2.micro"
security_groups = [aws_security_group.mysg.id]
associate_public_ip_address = true
user_data = <<-EOF
#!/bin/bash
wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
mv busybox-x86_64 busybox
chmod +x busybox
RZAZ=\$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
IID=\$(curl 169.254.169.254/latest/meta-data/instance-id)
LIP=\$(curl 169.254.169.254/latest/meta-data/local-ipv4)
echo "<h1>RegionAz(\$RZAZ) : Instance ID(\$IID) : Private IP(\$LIP) : Web Server</h1>" > index.html
nohup ./busybox httpd -f -p 80 &
EOF
# Required when using a launch configuration with an auto scaling group.
lifecycle {
create_before_destroy = true
}
}
resource "aws_autoscaling_group" "myasg" {
name = "myasg"
launch_configuration = aws_launch_configuration.mylauchconfig.name
vpc_zone_identifier = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
min_size = 2
max_size = 10
health_check_type = "ELB"
target_group_arns = [aws_lb_target_group.myalbtg.arn]
tag {
key = "Name"
value = "terraform-asg"
propagate_at_launch = true
}
}
EOT
- 마지막으로 aws_lb_listener_rule 리소스를 사용해 리스너 규칙을 생성하여 연결
cat <<EOT > alb.tf
resource "aws_lb" "myalb" {
name = "t101-alb"
load_balancer_type = "application"
subnets = [aws_subnet.mysubnet1.id, aws_subnet.mysubnet2.id]
security_groups = [aws_security_group.mysg.id]
tags = {
Name = "t101-alb"
}
}
resource "aws_lb_listener" "myhttp" {
load_balancer_arn = aws_lb.myalb.arn
port = 80
protocol = "HTTP"
# By default, return a simple 404 page
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "404: page not found - T101 Study"
status_code = 404
}
}
}
resource "aws_lb_target_group" "myalbtg" {
name = "t101-alb-tg"
port = 80
protocol = "HTTP"
vpc_id = aws_vpc.myvpc.id
health_check {
path = "/"
protocol = "HTTP"
matcher = "200-299"
interval = 5
timeout = 3
healthy_threshold = 2
unhealthy_threshold = 2
}
}
resource "aws_lb_listener_rule" "myalbrule" {
listener_arn = aws_lb_listener.myhttp.arn
priority = 100
condition {
path_pattern {
values = ["*"]
}
}
action {
type = "forward"
target_group_arn = aws_lb_target_group.myalbtg.arn
}
}
output "myalb_dns" {
value = aws_lb.myalb.dns_name
description = "The DNS Address of the ALB"
}
EOT
배포 완료 후 curl 접속 확인
# 배포
terraform plan && terraform apply -auto-approve
# ALB DNS주소로 curl 접속 확인
ALBDNS=$(terraform output -raw myalb_dns)
for i in {1..100}; do curl -s http://$ALBDNS/ ; done | sort | uniq -c | sort -nr
# EC2 최소 2대 => 3대로 증가 수정
sed -i -e 's/min_size = 2/min_size = 3/g' asg.tf
# 배포
terraform plan && terraform apply -auto-approve
# ALB DNS주소로 curl 접속 확인
for i in {1..100}; do curl -s http://$ALBDNS/ ; done | sort | uniq -c | sort -nr
# 강제로 EC2 1대 삭제 후 curl 접속 확인 : 종료 확인 후 치료를 위한 인지까지 3분 정도 시간 소요 - (자가치료) 알아서 1대 추가
삭제: **terraform destroy -auto-approve**
- destroy 는 ‘실행 취소(undo)’를 할 수 없으니, 실제 운영 환경에서는 주의해서 실행해야 함