테라폼 전체 코드
https://github.com/su-mmer/AWSCodeDeployTest
spring 코드
https://github.com/su-mmer/cloud-skills-sample-spring-boot-app
목적
2023.12.27 - [클라우드 공부/AWS] - Git Actions + AWS CodeDeploy로 SpringBoot 배포
해당 글에서 콘솔로 생성한 인프라를 모두 테라폼으로 만들어보고자 합니다.
- Terraform을 사용해 인프라를 구축합니다.
- 테라폼 클라우드를 사용합니다.
- Terraform v1.5.6
- AWS profile을 사용했습니다.
main.tf
provider 정의
# ------ provider 정의 ------ #
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
cloud {
organization = "hee"
workspaces {
name = "AWSCodeDeployTest"
}
}
}
provider "aws" {
profile = "my-terraform" // 로컬 aws profile에 등록한 이름
region = "${var.region}"
default_tags {
tags = {
Name = "${var.name}"
}
}
}
required_providers에 사용할 provider에 대해 정의합니다.
cloud에 테라폼 클라우드를 정의합니다.
정의 후에 terraform init을 통한 초기 세팅이 필요합니다.
Network
# ------ AWS VPC ------ #
resource "aws_vpc" "tfcd-vpc" {
cidr_block = var.cidr_block
tags = {
Name = "${var.name}-vpc"
}
}
// subnet
resource "aws_subnet" "public-2a" {
vpc_id = aws_vpc.tfcd-vpc.id
cidr_block = "10.0.180.0/28"
availability_zone = "${var.region}a"
tags = {
Name = "${var.name}-subnet"
}
}
# igw
resource "aws_internet_gateway" "tfcd_igw" {
vpc_id = aws_vpc.tfcd-vpc.id
tags = {
Name = "${var.name}-igw"
}
}
# routing table 생성 - public subnet, igw 연결
resource "aws_route_table" "tfcd_route" {
vpc_id = aws_vpc.tfcd-vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.tfcd_igw.id
}
tags = {
Name = "${var.name}-public_rt"
}
}
# routing table에 public subnet 추가
resource "aws_route_table_association" "routing_a" {
subnet_id = aws_subnet.public-2a.id
route_table_id = aws_route_table.tfcd_route.id
}
rtb-0b0d는 자동으로 생성되는 기본 라우팅 테이블입니다. 프라이빗입니다.
security group
EC2에 연결할 보안그룹입니다.
테라폼으로 생성한 보안그룹은 아웃바운드 전체 포트가 자동으로 생성되지 않습니다.
# ------ Security Group ------ #
resource "aws_security_group" "ec2-default" {
vpc_id = aws_vpc.tfcd-vpc.id
name = "ec2-default"
description = "ec2 security group"
tags = {
Name = "${var.name}-ec2"
}
}
// 인바운드
resource "aws_security_group_rule" "sg_ec2_http" {
type = "ingress"
from_port = 80
to_port = 80
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ec2-default.id
}
resource "aws_security_group_rule" "sg_ec2_https" {
type = "ingress"
from_port = 443
to_port = 443
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ec2-default.id
}
resource "aws_security_group_rule" "sg_ec2_ssh" {
type = "ingress"
from_port = 22
to_port = 22
protocol = "TCP"
cidr_blocks = [ "${var.cidr_block_myIP}" ]
security_group_id = aws_security_group.ec2-default.id
}
resource "aws_security_group_rule" "sg_ec2_tomcat" {
type = "ingress"
from_port = 8080
to_port = 8080
protocol = "TCP"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ec2-default.id
}
// 아웃바운드
resource "aws_security_group_rule" "sg_ec2_egress" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1" // 모든 프로토콜
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.ec2-default.id
}
IAM
IAM policy는 data를 이용해 불러왔습니다. 불러오는 필터로는 name을 사용했습니다.
# ------ AWS IAM ------ #
# EC2 S3 접근 권한을 위한 IAM 생성
// s3 접근 정책 불러오기
data "aws_iam_policy" "IAM_S3FullAccess" {
name = "AmazonS3FullAccess"
}
// IAM 역할 생성
resource "aws_iam_role" "ec2_iam_role" {
name = "${var.name}-ec2-role"
assume_role_policy = data.aws_iam_policy_document.assume_role.json
}
// IAM role 과 policy 연결
resource "aws_iam_role_policy_attachment" "ec2_iam_role-attach" {
role = aws_iam_role.ec2_iam_role.name
policy_arn = data.aws_iam_policy.IAM_S3FullAccess.arn
}
// IAM instance profile 생성, IAM role과 연결
resource "aws_iam_instance_profile" "ec2_instance_profile" {
name = "${var.name}-instance-profile"
role = aws_iam_role.ec2_iam_role.name
}
// IAM instance profile에 연결할 policy 불러오기
data "aws_iam_policy_document" "assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
# CodeDeploy를 위한 IAM 생성
// CodeDeploy를 위한 정책 불러오기
data "aws_iam_policy" "IAM_CodeDeploy" {
name = "AWSCodeDeployRole"
}
data "aws_iam_policy_document" "cd_assume_role" {
statement {
effect = "Allow"
principals {
type = "Service"
identifiers = ["codedeploy.amazonaws.com"]
}
actions = ["sts:AssumeRole"]
}
}
// IAM 역할 생성
resource "aws_iam_role" "codedeploy_iam_role" {
name = "${var.name}-cd-role"
assume_role_policy = data.aws_iam_policy_document.cd_assume_role.json
}
// IAM role 과 policy 연결
resource "aws_iam_role_policy_attachment" "cd_iam_role_attach" {
role = aws_iam_role.codedeploy_iam_role.name
policy_arn = data.aws_iam_policy.IAM_CodeDeploy.arn
}
# GitAction을 위한 IAM 사용자 생성
// GitAction을 위한 정책 불러오기
data "aws_iam_policy" "policy_s3_Git" {
name = "AmazonS3FullAccess"
}
data "aws_iam_policy" "policy_cd_Git" {
name = "AWSCodeDeployFullAccess"
}
// IAM 사용자 생성
resource "aws_iam_user" "gitaction_user" {
name = "${var.name}-git-user"
tags = {
Name = "${var.name}-git-user"
}
}
// 사용자 key 생성
resource "aws_iam_access_key" "gitaction_user_key" {
user = aws_iam_user.gitaction_user.name
}
// IAM role 과 policy 연결
resource "aws_iam_user_policy_attachment" "git-s3-attach" {
user = aws_iam_user.gitaction_user.name
policy_arn = data.aws_iam_policy.policy_s3_Git.arn
}
resource "aws_iam_user_policy_attachment" "git-cd-attach" {
user = aws_iam_user.gitaction_user.name
policy_arn = data.aws_iam_policy.policy_cd_Git.arn
}
EC2에 Instance profile로 사용하기 위한 IAM에는 Instance profile(resource.aws_iam_instance_profile)을 직접 생성하여 IAM에 연결해야 합니다.
EC2
# ------ EC2 생성 ------ #
// EC2 접근을 위한 pem key
data "aws_key_pair" "mykey" {
key_name = "hhkey"
include_public_key = true
}
data "aws_ami" "ubuntu" {
most_recent = true
filter {
name = "image-id"
values = ["ami-086cae3329a3f7d75"] # ubuntu 22.04
}
filter {
name = "virtualization-type"
values = ["hvm"]
}
}
// EC2에 붙일 EIP 생성
resource "aws_eip" "instance_eip" {
instance = aws_instance.myinstance.id
tags = {
Name = "${var.name}-ec2"
}
}
// EC2 생성
resource "aws_instance" "myinstance" {
ami = data.aws_ami.ubuntu.id
instance_type = "t3.micro"
subnet_id = aws_subnet.public-2a.id
vpc_security_group_ids = [ aws_security_group.ec2-default.id ]
iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.id
key_name = data.aws_key_pair.mykey.key_name
user_data = <<-EOF
#!/bin/bash
sudo apt update
sudo apt-get install -y openjdk-11-jdk
sudo apt install ruby-full -y
sudo apt install wget
cd /home/ubuntu
wget https://aws-codedeploy-ap-northeast-2.s3.ap-northeast-2.amazonaws.com/latest/install
sudo chmod +x ./install
sudo ./install auto
EOF
tags = {
Name = "${var.name}-instance"
"${var.codedeploy_tag_key}" = "${var.codedeploy_tag_value}"
}
}
EC2에 접근하기 위한 키페어는 콘솔에서 미리 생성하고 data로 불러왔습니다.
물론 sshkey-gen으로 키를 직접 만들어 aws에 리소스를 생성해도 되지만.. 그렇게까지 하고 싶진 않았습니다....
SSM 기능을 사용하면 키페어를 사용하지 않아도 접근할 수 있습니다. 애용합시다.
S3
# ------ S3 ------ #
// private bucket
resource "aws_s3_bucket" "cicd_bucket" {
bucket = "${var.name}-cicd-bucket-hh"
force_destroy = true
tags = {
Name = "${var.name}-s3"
}
}
force_destroy=true 옵션으로 terraform destroy 명령에서 S3에 객체가 있더라도 테라폼이 강제로 S3를 삭제할 수 있습니다.
AWS CodeDeploy
# ------ Code Deploy 생성 ------ #
resource "aws_codedeploy_app" "tf_codedeploy" {
compute_platform = "Server"
name = "${var.name}-app"
}
resource "aws_codedeploy_deployment_group" "tf_cd_group" {
app_name = aws_codedeploy_app.tf_codedeploy.name
deployment_group_name = "${var.name}-group"
deployment_config_name = "CodeDeployDefault.OneAtATime"
service_role_arn = aws_iam_role.codedeploy_iam_role.arn
ec2_tag_filter {
key = "${var.codedeploy_tag_key}"
type = "KEY_AND_VALUE"
value = "${var.codedeploy_tag_value}"
}
auto_rollback_configuration { // 배포 실패 시 롤백
enabled = true
events = ["DEPLOYMENT_FAILURE"]
}
}
variables.tf
# resource 태그에 사용할 변수
variable "name" {
type = string
default = "terraform-example"
description = "All resources name incloud this value"
}
# vpc cidr
variable "cidr_block" {
type = string
description = "for vpc cidr"
}
# resource 생성될 기본 region
variable "region" {
type = string
description = "resources create this region"
}
# EC2 보안그룹 ssh 소스 지정을 위한 변수
variable "cidr_block_myIP" {
type = string
description = "sg를 위한 내 IP"
}
# codedeploy 변수
variable "codedeploy_tag_key" {
type = string
description = "codedeploy에서 태그 매칭에 사용할 key"
}
variable "codedeploy_tag_value" {
type = string
description = "codedeploy에서 태그 매칭에 사용할 value"
}
변동이 있을 수 있는 값들은 변수처리 했습니다.
plan이나 apply시에 직접 입력할 수 있고 tfvars 파일을 만들어 사용할 수도 있습니다.
terraform.tfvars
cidr_block_myIP = "<내 IP>"
region = "ap-northeast-2"
codedeploy_tag_key = "codedeploy"
codedeploy_tag_value = "Example Instance"
cidr_block = "10.0.180.0/24"
변수를 매번 입력하는 것 보다 tfvars 파일을 만들어 사용하는 것이 효율적인 것 같아 사용하게 되었습니다.
Github에서 레포지토리를 생성할 때 gitignore로 terraform을 선택하면 자동으로 terraform.tfvars 파일이 공유 대상 파일에서 제외됩니다.
레포지토리 생성 시에 gitignore를 등록하지 않았다면 구글링하면 됩니다.
outputs.tf
output "ec2_EIP" {
value = aws_eip.instance_eip.public_ip // EC2에 접속할 Public IP 출력
}
output "iam_access_key_id" {
description = "The access key ID"
value = aws_iam_access_key.gitaction_user_key.id // Git action에 추가할 AC
}
output "secret" {
description = "The access key ID"
value = aws_iam_access_key.gitaction_user_key.secret // Git action에 추가할 SK
sensitive = true
}
output "s3" {
description = "s3 bucket name"
value = aws_s3_bucket.cicd_bucket.id
}
output "aws_codedeploy_app" {
description = "codedeploy app name"
value = aws_codedeploy_app.tf_codedeploy.name
}
output "aws_codedeploy_group" {
description = "codedeploy group name"
value = aws_codedeploy_deployment_group.tf_cd_group.deployment_group_name
}
spring 레포지토리의 Github secret에 등록할 값들을 출력하도록 했습니다.
output은 콘솔과 state 파일 둘 다에 기록됩니다.
output.secret은 sensitive = true 설정을 하여 콘솔에는 출력되지 않고 state 파일에만 출력되도록 했습니다.
terraform apply
Apply complete! Resources: 25 added, 0 changed, 0 destroyed.
Outputs:
aws_codedeploy_app = "terraform-example-app"
aws_codedeploy_group = "terraform-example-group"
ec2_EIP = "13.209.191.178"
iam_access_key_id = "AKIAYCUELDXSBIEFKTH6"
s3 = "terraform-example-cicd-bucket-hh"
secret = <sensitive>
리소스가 모두 생성되었습니다.
IAM의 secret key를 제외하고는 모두 output이 콘솔에 출력되었습니다.
Terraform Cloud를 확인합니다.
생성된 Resource와 output을 모두 확인할 수 있습니다.
여기서도 secret은 값이 가려져있습니다.
그렇다면 이 값은 sensitive 값은 어떻게 확인할까요.
{
"version": 4,
"terraform_version": "1.5.6",
"serial": 11,
"lineage": "***-**-**-**-***",
"outputs": {
"aws_codedeploy_app": {
"value": "terraform-example-app",
"type": "string"
},
"aws_codedeploy_group": {
"value": "terraform-example-group",
"type": "string"
},
"ec2_EIP": {
"value": "13.209.191.178",
"type": "string"
},
"iam_access_key_id": {
"value": "AKIAYCUELDXSBIEFKTH6",
"type": "string"
},
"s3": {
"value": "terraform-example-cicd-bucket-hh",
"type": "string"
},
"secret": {
"value": "bf*******",
"type": "string",
"sensitive": true
}
},
state file에 기록됩니다. 여기서 value를 확인할 수 있습니다.
지난 번 사용한 Spring 레포지토리에서 git action을 동작시켜보겠습니다.
우선 git action이 사용할 IAM을 새로 생성했으므로 secret 키들을 변경해주어야 합니다.
output으로 출력된 access key와 secret key를 입력해줍니다.
git action이 성공적으로 수행되었습니다.
S3에 빌드 파일이 적재되었습니다.
CodeDeploy가 동작하여 배포가 성공했습니다.
어플리케이션이 제대로 동작합니다.
이렇게 테라폼으로 구성을 해보았는데, 코드가 길어지면 모듈화와 반복문을 사용해야겠지요,,!!