본문 바로가기

AWS

[CICD] Terraform을 통한 AWS 3 Tier 구성 및 CI/CD 파이프라인 배포 #3 - 3 Tier 인프라 구축

반응형

* 본 글에서는 Terraform 코드에 대한 자세한 설명은 진행하지 않습니다. *

 

- Module은 EC2, VPC, RDS - MySQL 3개만 구현하였고, 나머지는 Resource를 사용하여 인프라를 구현하였다.

 

- 동적으로 Infra의 설정 값을 변경할 수 있도록 variables.tf를 적절히 활용하였고, 각 Test, Dev, Stg, Prd 등 각 환경마다 변수 값을 다르게 설정할 수 있도록 env/~.tfvars 처럼 tfvars 파일을 생성하였다.

 

- 아직 Module화, count, for_each 등의 Terraform 함수들을 활용하여 좀 더 간결하여 동적인 Code로 리팩터링하는 작업이 필요할 것으로 보인다.

 

- Tomcat AMI는 Packer로 생성하였는데, 아래의 링크에서 확인할 수 있다.

https://nyyang.tistory.com/85

 

[Packer] Packer를 이용한 EC2 AMI 생성

Packer의 정의는 쉽게 VM의 Image를 생성해주는 Image Builder라고 생각하면 된다. Packer 설치 Packer는 yum, apt-get, choco 등 여러가지 방식으로 설치할 수 있지만 아래의 설치 방법은 wget을 이용하여 zip 파..

nyyang.tistory.com

 

아키텍처

이 중에 Code Deploy는 Terraform으로 구성 X

Root Domain은 가비아 도메인인 pingping2.shop을 사용하였으며 Backend로는 S3와 Terraform Lock을 위한 DynamoDB를 사용하였다.

 

- Public Subnet, Private Subnet, Database Subnet으로 구성하였으며, 만약 Database Subnet의 CIDR을 Variables 선언 부분에 기입하지 않을 경우 동적으로 Terraform이 이를 파악하여 생성하지 않도록 하였음 (count 함수)

 

- AWS Load Balancer를 사용하면 ACM과 같이 SSL Termination을 LB단에서 적용할 수 있으므로 내부에서는 http 프로토콜을 사용할 수 있으며 좀 더 빠르고 해당 LB를 거친 VM들에 대해서는 https 인증서를 적용하지 않아도 된다.

 

- AutoScaling Group에서는 Launch Template, Launch Configuration 2가지 중 하나를 사용할 수 있으며 이번에는 Launch Template을 사용하였다. (버전 관리가 가능하기 때문)

 

- Tomcat과 RDS MySQL은 RDS의 Endpoint로 설정하여 평소에 Primary DB로 쿼리를 하지만 만약 장애가 발생할 경우 페일 오버가 진행되며, Standby DB가 Primary DB로 승격하게 된다.

=> Multi AZ 기능

 

Terraform으로 구성한 Resource 내역

- VPC

- Subnet

- Route Table

- Internet Gateway

- NAT Gateway

 

- EC2

  - Bastion, Jenkins

 

- S3 (war 파일 저장을 위한 스토리지)

 

- Route 53, ACM

 

- AutoScaling Group, SNS, ASG Policy

 

- ALB, NLB

 

- RD

디렉터리 구조

크게 Modules 디렉터리와 Project 디렉터리로 구성되어 있다.

 

- modules : 여러 프로젝트에서 재사용할 수 있도록 리소스를 모듈화함

 

- project/common : 특정 프로젝트와 상관없이 공통으로 사용하는 리소스들을 별도 관리하기 위해 구성 (IAM, Route53, tfstate를 위한 S3, DynamoDB 리소스, ..)

=> 3 Tier 프로젝트를 destory해도 Global 서비스들을 유지시키기 위함

 

- project/3tier : 3 티어 프로젝트와 연관이 있는 리소스들을 한 디렉터리에 모아 관리하기 위함. 각 서비스별로 tf 파일을 구성하였으며 AWS 계정 내에서 동적으로 값을 가져오기 위한 data.tf 및 variables.tf 파일 또한 생성하였다.

 

- project/3tier/env : 각 환경별로 tfvars 파일을 둠으로써 환경 별 리소스를 세팅 및 같은 인프라이지만 변수 값만 다르게 구현할 수 있도록 하기 위함

 

- project/3tier/scripts : 쉘 스크립트 및 Terraform 템플릿 파일을 모아놓은 폴더

 

test.tfvars 파일 예시이며, 아래처럼 본인이 원하는 인프라 값을 기입해주면 인프라가 형성된다.

# Environment
env = "test"

# Profile, Region Name
profile_name = "default"
cred_file    = "~/.aws/credentials"
region       = "ap-northeast-2"

# VPC Configurations
cidr_block          = "10.0.0.0/16" # VPC CIDR Block
public_subnet_cidr  = ["10.0.0.0/24", "10.0.1.0/24"]
private_subnet_cidr = ["10.0.10.0/24", "10.0.11.0/24"]
db_subnet_cidr      = ["10.0.20.0/24", "10.0.21.0/24"]
available_azs       = ["ap-northeast-2a", "ap-northeast-2c"]

# EC2 Configurations
default_instance_type = "t3.small"
key_pair      = "your-key"

jenkins_instance_type = "t3.small"
jenkins_instance_profile = "test_profile"

# Security Groups
# tuple : list of {description, source_cidr, from, to, protocol}
source_bastion_cidrs = [
  {
    desc     = "Bastion SSH Inbound",
    cidrs    = ["0.0.0.0/0"],
    from     = 22,
    to       = 22,
    protocol = "tcp",
  },
]

source_web_cidrs = [
  {
    desc     = "WEB HTTP Inbound",
    cidrs    = ["0.0.0.0/0"],
    from     = 80,
    to       = 80,
    protocol = "tcp",
  },
]

source_was_cidrs = [
  {
    desc     = "WAS HTTP Inbound",
    cidrs    = ["0.0.0.0/0"],
    from     = 8080,
    to       = 8080,
    protocol = "tcp",
  },
]

// Launch Template
web_instance_type = "t2.small"
was_instance_type = "t2.small"

web_ami_version = "v0.1"
was_ami_version = "v0.2"

web_lt_default_version = 1
was_lt_default_version = 1

// AutoScaling

web_asg_version = "$Default"
was_asg_version = "$Default"

web_asg_capacity = {
  desired = 1
  min = 1,
  max = 2,
}

was_asg_capacity = {
  desired = 1
  min = 1,
  max = 2,
}

// ASG Policy
// 1. WEB
web_adjustment_type = "ChangeInCapacity"
web_cpu_scaleup_threshold = 60
web_cpu_scaledown_threshold = 20

// 2. WAS
was_adjustment_type = "ChangeInCapacity"
was_cpu_scaleup_threshold = 60
was_cpu_scaledown_threshold = 20
was_instance_profile = "CodeDeployRoleProfile"

// SNS
asg_noti_endpoint = "example@gmail.com"

// Route 53, ALB, ACM
domain = "example.com"


// Database

// Global
db_name = "global"

// Param Group
db_family = "mysql5.7"

// DB Instance
db_identifier = "test-mysql-57"
db_engine = "mysql"
db_engine_version = "5.7"
db_allocated_storage = "30"
db_instance_class = "db.t3.micro"
db_multi_az = false

db_username = "admin"
db_password = "passw0rd1!"

// Option Group
db_engine_name = "mysql"
db_major_engine_version = "5.7"

 

- 아래는 Directory 구조이다.

❯ tree | grep -v tfstate
.
├── README.md
├── images
│   └── archutecture.png
├── modules
│   ├── ec2
│   │   ├── README.md
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   ├── variables.tf
│   │   └── version.tf
│   ├── lb
│   │   ├── main.tf
│   │   ├── output.tf
│   │   ├── provider.tf
│   │   └── variables.tf
│   ├── rds
│   │   ├── mysql
│   │   │   ├── README.md
│   │   │   ├── data.tf
│   │   │   ├── main.tf
│   │   │   ├── output.tf
│   │   │   ├── provider.tf
│   │   │   ├── test_vars
│   │   │   │   └── test.tfvars
│   │   │   └── variables.tf
│   │   └── postgresql
│   └── vpc
│       ├── README.md
│       ├── main.tf
│       ├── output.tf
│       ├── provider.tf
│       └── variables.tf
└── project
    ├── 3Tier
    │   ├── asg.tf
    │   ├── asg_policy.tf
    │   ├── data.tf
    │   ├── ec2.tf
    │   ├── ec2_security_group.tf
    │   ├── env
    │   │   └── test.tfvars
    │   ├── outputs.tf
    │   ├── provider.tf
    │   ├── rds.tf
    │   ├── scripts
    │   │   ├── common
    │   │   │   ├── amz2_init.sh
    │   │   │   └── jenkins_install.sh
    │   │   ├── was
    │   │   │   ├── setenv.sh
    │   │   │   ├── shutdown.sh
    │   │   │   ├── startup.sh
    │   │   │   ├── tomcat.service
    │   │   │   ├── tomcat_install.sh
    │   │   │   └── was_userdata.sh
    │   │   └── web
    │   │       └── nginx_tomcat.tftpl
    │   ├── variables.tf
    │   ├── vpc.tf
    │   ├── was-nlb.tf
    │   └── web-alb.tf
    ├── common
    │   ├── acm
    │   │   ├── data.tf
    │   │   ├── main.tf
    │   │   ├── output.tf
    │   │   ├── provider.tf
    │   │   └── variables.tf
    │   ├── etc
    │   │   ├── backend.tf
    │   │   ├── provider.tf
    │   │   ├── s3.tf
    │   │   └── variables.tf
    │   ├── iam
    │   │   ├── data.tf
    │   │   ├── main.tf
    │   │   ├── provider.tf
    │   │   └── variables.tf
    │   └── route53
    │       ├── main.tf
    │       ├── provider.tf
    │       └── variables.tf
    ├── packer
    │   └── was
    │       ├── packer.pkrvars.hcl
    │       ├── tomcat_ami.pkr.hcl
    │       └── tomcat_install.sh
    └── test
        ├── data.tf
        ├── provider.tf
        └── variables.tf

 

 

 

3 Tier Project 내부 파일 설명

    ├── 3Tier
    │   ├── asg.tf
    │   ├── config
    │   │   ├── amz2_init.sh
    │   │   ├── nginx_tomcat.tftpl
    │   │   ├── setenv.sh
    │   │   ├── shutdown.sh
    │   │   ├── startup.sh
    │   │   └── tomcat.service
    │   ├── data.tf
    │   ├── ec2.tf
    │   ├── ec2_security_group.tf
    │   ├── env
    │   │   └── test.tfvars
    │   ├── outputs.tf
    │   ├── provider.tf
    │   ├── rds.tf
    │   ├── terraform.tfstate
    │   ├── terraform.tfstate.backup
    │   ├── variables.tf
    │   ├── vpc.tf
    │   ├── was-nlb.tf
    │   └── web-alb.tf

vpc.tf (Module 사용)
: VPC, Subnet Group, Route Table, Route Table Attachment, .. 등의 리소스를 생성

ec2.tf (Module 사용)
: Bastion, 그 외 ec2들을 생성

 

ec2_security_group.tf
: EC2에서 사용하는 Security Group, Security Group Rule

web-alb.tf , was-nlb.tf
: WEB, WAS 로드 밸런서 생성, Target Group 생성

rds.tf (Module 사용)
: Option Group, Param Group, Subnet Group, DB Instance 리소스 생성 (MySQL)

asg.tf
: Autoscaling 및 Launch Template, ASG Attachment 리소스 생성

config 디렉터리
: templatefile 함수를 사용하기 위한 tftpl 파일, 초기화 shell script 등을 모아놓은 디렉터리

env 디렉터리
: 환경에 맞는 변수를 설정하기 위한 파일들을 생성해놓는 곳

그 외
data.tf : AWS로부터 데이터를 가져오기 위한 data 블럭들을 모아놓음


variables.tf : 변수 생성

 

asg.tf 파일

모든 파일을 설명하면 글이 길어지기 때문에 그 중 하나인 asg.tf에 대해서만 간략히 설명

 

1. Launch Template 생성

    : 기 생성해 놓은 WEB AMI, WAS AMI를 통해 Launch Template 생성

 

2. 생성해둔 Launch Template를 통해 ASG 생성

 

3. 기 생성해둔 LB의 Target Group의 VM들을 ASG와 연결시키기 위한 Attachment 작업

 

// Launch Template
resource "aws_launch_template" "web" {
  name                    = "${var.env}-web-launch_template"
  disable_api_termination = var.disable_api_termination
  image_id                = data.aws_ami.web.image_id
  instance_type           = var.web_instance_type
  key_name                = var.key_pair
  vpc_security_group_ids  = [aws_security_group.web.id, aws_security_group.bastion_common.id]
  tags = {
    Name = "${var.env}-web-launch_template"
  }
  default_version = var.web_lt_default_version
  user_data = base64encode(templatefile(
    "${path.module}/scripts/web/nginx_tomcat.tftpl",
    {
      BACKEND_LB   = aws_lb.was.dns_name,
      BACKEND_PORT = var.was_port
      ROOT_DOMAIN  = var.domain
    }
  ))
}
resource "aws_launch_template" "was" {
  name                    = "${var.env}-was-launch_template"
  disable_api_termination = var.disable_api_termination
  image_id                = data.aws_ami.was.image_id
  instance_type           = var.was_instance_type
  key_name                = var.key_pair
  vpc_security_group_ids  = [aws_security_group.was.id, aws_security_group.bastion_common.id]
  tags = {
    Name = "${var.env}-was-launch_template"
  }
  default_version = var.was_lt_default_version
  user_data       = base64encode(file("./scripts/was/was_userdata.sh"))
  iam_instance_profile {
    name = var.was_instance_profile
  }
}
// AutoScaling Group
// WEB
resource "aws_autoscaling_group" "web" {
  name = "${var.env}-web-asg"
  // VPC, Network
  vpc_zone_identifier = module.main_vpc.private_subnets_ids
  // Capacity
  min_size         = var.web_asg_capacity.min
  max_size         = var.web_asg_capacity.max
  desired_capacity = var.web_asg_capacity.desired
  // Launch Template Setting
  launch_template {
    id      = aws_launch_template.web.id
    version = var.web_asg_version
  }
  force_delete = true
  timeouts {
    delete = "15m"
  }
  lifecycle {
    ignore_changes = [load_balancers, target_group_arns]
  }
  tag {
    key                 = "Name"
    value               = "${var.env}-web"
    propagate_at_launch = true
  }
  tag {
    key                 = "ASG"
    value               = true
    propagate_at_launch = true
  }
}
// WAS
resource "aws_autoscaling_group" "was" {
  name = "${var.env}-was-asg"
  // VPC, Network
  vpc_zone_identifier = module.main_vpc.private_subnets_ids
  // Capacity
  min_size         = var.was_asg_capacity.min
  max_size         = var.was_asg_capacity.max
  desired_capacity = var.was_asg_capacity.desired
  // Launch Template Setting
  launch_template {
    id      = aws_launch_template.was.id
    version = var.was_asg_version
  }
  force_delete = true
  timeouts {
    delete = "15m"
  }
  lifecycle {
    ignore_changes = [load_balancers, target_group_arns]
  }
  tag {
    key                 = "Name"
    value               = "${var.env}-was"
    propagate_at_launch = true
  }
  tag {
    key                 = "ASG"
    value               = true
    propagate_at_launch = true
  }
}
// AutoScaling Group Attachment
resource "aws_autoscaling_attachment" "web" {
  autoscaling_group_name = aws_autoscaling_group.web.id
  alb_target_group_arn   = aws_lb_target_group.web_http.id
}
resource "aws_autoscaling_attachment" "was" {
  autoscaling_group_name = aws_autoscaling_group.was.id
  alb_target_group_arn   = aws_lb_target_group.was_http.id
}

 

Template 파일에 동적으로 변수를 넣어주기 위한 templatefile 함수 사용

 

참고 (아래 URL을 참고하여 작성하였음)

user_data 부분에는 Launch Template를 통해 생성될 EC2의 초기 스크립트를 기입할 수 있으며 이를 templatefile 이란 Terraform에서 제공하는 함수를 통해 동적으로 변수를 기입할 수 있다.

 

BACKEND_LB와 BACKEND_PORT, ROOT_DOMAIN 3개의 변수를 설정하였다.

 

추가로 base64encode 함수를 통해 인코딩해주어야 함

 

  user_data = base64encode(templatefile(
    "./config/nginx_tomcat.tftpl",
    {
      BACKEND_LB   = aws_lb.was.dns_name
      BACKEND_PORT = var.was_port
      ROOT_DOMAIN  = "pingping2.shop"
    }
  ))

 

아래는 nginx_tomcat.tftpl 파일이다.

 

변수를 사용할 때는 ${variable} 형식으로 사용하면 된다.

 

 

결과

- 정적 분석

terraform fmt && terraform validate

 

 

- Plan

terraform plan -var-file=./env/test.tfvars

 

 

- Apply

terraform apply -var-file=./env/test.tfvars -auto-approve

 

 

- ASG

- SNS

 

- ASG Event 발생 시 SNS

 

- RDS

 

- Static Content

 

- Dynamic Content

  : Tomcat 서버에서 처리. 아래는 단순 index.jsp를 hello world로 변경해서 고양이가 안뜨는 것일 뿐

 

 

- Destroy

terraform destroy -var-file=./env/test.tfvars

이로써 destroy 이후 apply 하여도 동일한 구성의 인프라가 계속 생성되도록 하였다.

 

 

이후에 WAR Demo 파일을 Tomcat에 배포하고 ASG를 활용한 Blue Green 배포를 진행하겠다.

 

 

 

 

 

반응형