배포 흐름
- 이해를 돕기위한 미리캔버스 슬라이드 쇼
Terraform 코드
코드의 가독성과 독자의 이해력을 높이기 위해 필요하지 않은 기타 인프라 리소스는 제외되어 있는 코드임.
- ecs.tf
### ECS Service
resource "aws_ecs_service" "api" {
name = "${local.name_prefix}-api"
cluster = aws_ecs_cluster.designhub.name
task_definition = aws_ecs_task_definition.api.id
enable_execute_command = true
launch_type = "FARGATE"
desired_count = 1
network_configuration {
subnets = local.private_subnets
security_groups = [local.sg-allow-int-all]
}
load_balancer {
target_group_arn = module.alb_api.target_group_arns[0]
container_name = aws_ecs_task_definition.api.family
container_port = 443
}
deployment_controller {
type = "CODE_DEPLOY"
}
lifecycle {
ignore_changes = [
task_definition,
desired_count,
load_balancer
]
}
}
- alb.tf
### ALB
module "alb_api" {
source = "../../../modules/alb"
name = "${local.name_prefix}-api"
load_balancer_type = "application"
vpc_id = local.vpc_id
subnets = local.public_subnets
security_groups = [
local.sg-allow-ext-http,
local.sg-allow-idc-all,
local.sg-allow-office-all
]
http_tcp_listeners = [
{
port = 80
protocol = "HTTP"
action_type = "redirect"
redirect = {
port = "443"
protocol = "HTTPS"
status_code = "HTTP_301"
}
}
]
### Disabled Using Code Deploy
# https_listeners = [
# {
# port = 443
# protocol = "HTTPS"
# certificate_arn = local.certificate_arn
# }
# ]
target_groups = [
{
name = "${local.name_prefix}-api-a"
backend_protocol = "HTTPS"
backend_port = 443
target_type = "ip"
deregistration_delay = 30
health_check = {
enabled = true
interval = 30
path = "/"
port = "80"
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 6
protocol = "HTTP"
matcher = "200"
}
},
{
name = "${local.name_prefix}-api-b"
backend_protocol = "HTTPS"
backend_port = 443
target_type = "ip"
deregistration_delay = 30
health_check = {
enabled = true
interval = 30
path = "/"
port = "80"
healthy_threshold = 3
unhealthy_threshold = 3
timeout = 6
protocol = "HTTP"
matcher = "200"
}
}
]
tags = local.tags
}
resource "aws_lb_listener" "api" {
load_balancer_arn = module.alb_api.lb_arn
port = "443"
protocol = "HTTPS"
ssl_policy = "ELBSecurityPolicy-2016-08"
certificate_arn = local.certificate_arn
default_action {
type = "forward"
target_group_arn = module.alb_api.target_group_arns[0]
}
lifecycle {
ignore_changes = [
default_action[0].target_group_arn
]
}
}
- codedeploy.tf
### Codedeploy
resource "aws_codedeploy_app" "api" {
compute_platform = "ECS"
name = var.project
}
resource "aws_codedeploy_deployment_group" "main" {
app_name = var.project
deployment_group_name = var.environment
deployment_config_name = "CodeDeployDefault.ECSAllAtOnce"
service_role_arn = local.iam_role_codedeploy
blue_green_deployment_config {
deployment_ready_option {
action_on_timeout = "CONTINUE_DEPLOYMENT"
}
terminate_blue_instances_on_deployment_success {
action = "TERMINATE"
termination_wait_time_in_minutes = 1
}
}
ecs_service {
cluster_name = aws_ecs_cluster.designhub.name
service_name = aws_ecs_service.api.name
}
deployment_style {
deployment_option = "WITH_TRAFFIC_CONTROL"
deployment_type = "BLUE_GREEN"
}
auto_rollback_configuration {
enabled = true
events = ["DEPLOYMENT_FAILURE"]
}
load_balancer_info {
target_group_pair_info {
prod_traffic_route {
listener_arns = [
aws_lb_listener.api.arn
]
}
target_group {
name = module.alb_api.target_group_names[0]
}
target_group {
name = module.alb_api.target_group_names[1]
}
}
}
}
배포 파이프라인
이 글에서는 빌드 파이프라인 부분은 생략했고 배포 파이프라인 부분만 다룬다.
Bamboo 에이전트에서 aws cli로 codedeploy를 트리거링 하여 배포를 하는 방식으로 배포한다.
(Jenkins도 UI나 용어만 다를 뿐 메커니즘은 동일하게 가져가면 된다.)
Codedeploy의 배포는 어떤 Task Definition을 사용하여 어떤 로드밸런서를 사용할건지 등이 정의되어 있는 YAML 형식의 파일을 만들고 Input 해줘야 한다.
- codedeploy에 필요한 yaml 파일을 빌드 에이전트의 로컬에 생성한다.
- aws deploy create-deployment 명령어의 --cli-input-yaml 옵션을 이용하여 로컬에 생성된 yaml 파일을 지정하고 트리거링 한다.
# define codedeploy.yml
cat <<EOF > codedeploy.yml
applicationName: '${bamboo.PROJECT}'
deploymentGroupName: '${bamboo.ENVIRONMENT}'
deploymentConfigName: CodeDeployDefault.ECSAllAtOnce
revision:
revisionType: AppSpecContent
appSpecContent:
content: |
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: "arn:aws:ecs:ap-northeast-2:${bamboo.AWS_ACCOUNT_ID_SECRET}:task-definition/${bamboo.PROJECT}-${bamboo.ENVIRONMENT}-api"
LoadBalancerInfo:
ContainerName: "${bamboo.PROJECT}-${bamboo.ENVIRONMENT}-api"
ContainerPort: 443
EOF
aws deploy create-deployment \
--cli-input-yaml file://codedeploy.yml
여기서 중요한 포인트
위 구성에서는 CodeDeploy의 Deploy를 aws cli로 실행하는데 배포가 완료되면 ALB 리스너에서는 전에 사용하던 Target Group의 연결을 끊고 새로 배포된 컨테이너가 존재하는 Target Group으로 연결이 된다.
그런데 라폼이에는 위의 상태가 저장이 안된다. 그러므로 위의 과정 이후에 Terraform Apply를 치면 전에 사용하던 Target Group을 다시 스위칭하는 현상이 발생한다. (Replcae 발생)
따라서 특정 Attribute에 변경사항이 일어나도 무시하는 lifecycle을 추가해줘야 하고 아래와 같이 aws_lb_listener 리소스에 ignore_chages를 설정해줘야 한다.
resource "aws_lb_listener" "api" {
...
lifecycle {
ignore_changes = [
default_action[0].target_group_arn
]
}
}
aws_lb_listener
리소스 뿐만 아니라 aws_ecs_service
에도 아래와 같이 ignore_changes
를 설정해줘야한다.
resource "aws_ecs_service" "api" {
...
lifecycle {
ignore_changes = [
task_definition,
desired_count,
load_balancer
]
}
}
이렇게 설정하면 Terraform가 관리하는 스콮 이외의 상태변화에도 Replace가 발생하지 않는다.
Reference
'Devops > Terraform' 카테고리의 다른 글
[Terraform] Configuration Syntax (0) | 2022.12.30 |
---|---|
[Terraform] Types (0) | 2022.12.26 |
[Terraform] count와 for_each (Meta-Arguments) (0) | 2022.12.25 |
[Terraform] Terragrunt와 Terraform Cloud(TFC) 연동해보기 (1) | 2022.12.16 |
[Terraform] README.md 작성 자동화 프로그램 ‘Terraform Docs’ 간단소개 (0) | 2022.09.11 |