AWS ECS를 이용하여 WebService를 구축하고 CI/CD,Motioring이 가능하도록 하자.
- 기본 네트워크 구축
- ECS 클러스터를 이용한 컨테이너 방식 사용
- Code Pipeline을 통한 CI/CD 구축
- Cloud Watch를 이용한 모니터링
- SNS를 이용한 경보 전달
- 테라폼 모듈화를 통해 좀 더 간편하고 쉽게 사용할 수 있도록 하자.
- 목적: 개발자가 컨테이너 기반의 애플리케이션을 쉽게 배포하고 운영 및 확장할 수 있도록 하
고 그에 따른 실시간 모니터링 방식으로 완벽한 인프라 구축에 목적을 두고 있음
12.26 ~ 12.27
- 기본 네트워크 구축
12.20 ~ 12.22
- Code Pipeline을 통한 CI/CD구축
- ECS 클러스터를 이용한 컨테이너 방식 구축
12.26 ~ 1.01
- 모듈화를 통한 간편화
- Cloud Watch를 이용한 모니터링
- SNS을 이용한 경보 전달
서비스에 대한 IAM정책을 하나하나 찾느라 시간이 많이 걸렸다.

> Network
Region : us-east-2
VPC :10.16.0.0/16
Subnet
Public_Subnet : 10.16.1.0/24, 10.16.2.0/24
Private_Subnet : 10.16.3.0/24, 10.16.4.0/24
Internet Gateway
NAT Gateway,EIP
EC2 - Instance
> Cluster
ECS(Cluseter,Service,Task Defintion
ALB
ECR
> CI/CD
Code-Build
Code-Commit
Code-pipeline
S3
> Monitoring
CloudWatch
SNS
Dockerfile(web-service용도)
Dockerfile(stress용도)
buildspec.yml
index.html
관리형 컨테이너 오케스트레이션 서비스
1. Aws 통합 및 간편함
-> 다른 서비스와 높은 통합성을 제공하여 다양한 aws서비스와 연동가능
2. 보안성
-> 애플리케이션을 실행하는 컨테이너는 격리되어 독립적이며 보안이 강화됩니다.
3. 유연성
-> 다양한 애플리케이션 타입과 언어를 지원하여 개발자들에게 유연한 환경을 제공합니다.

1. CLUSTER
-> Task가 배포되는 환경들이 논리적으로 그룹화되는 단위를 의미한다.
2. Service
-> 클러스터에 Task를 몇 개 배포할 것인지 결정하고 ELB 또는 AutoScailng을 설정한다.
3. Task
-> TASK Definition에 의해 배포된 컨테이너 Set입니다.
4. Task Definition
-> 태스크 정의는 컨테이너에서 실행할 작업의 정보를 정의합니다.

#### VPC 생성 ####
resource "aws_vpc" "my_vpc" {
cidr_block = var.vpc_cidr
instance_tenancy = var.instance_tenancy
tags = var.vpc_tag
}
### Internet gateway 생성 ###
resource "aws_internet_gateway" "my_igw" {
vpc_id = var.vpc-id
tags = var.igw-tags
}
### Elastic Ip 생성 ###
resource "aws_eip" "NAT-eip" {
domain = "vpc"
}
### NAT gateway 생성 ###
resource "aws_nat_gateway" "myNAT" {
allocation_id = var.myeip-id
subnet_id = var.pub-sub2-id
tags = var.nat-tags
}
### Subnet 생성 ###
### Public Subnet 생성 ###
resource "aws_subnet" "pub_sub1" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = var.pub-sub1-cidr
map_public_ip_on_launch = true
availability_zone = var.zone_1
tags = var.pub-sub1-tags
}
resource "aws_subnet" "pub_sub2" {
vpc_id = aws_vpc.my_vpc.id
cidr_block = var.pub-sub2-cidr
map_public_ip_on_launch = true
availability_zone = var.zone_2
tags = var.pub-sub2-tags
}
### Public-Route 생성-연결 ###
resource "aws_route_table" "pub_rt" {
vpc_id = var.vpc-id
tags = var.pub-rt-tags
}
resource "aws_route" "pub_route" {
route_table_id = var.pub-rt-id
destination_cidr_block = var.destination_cidr_block
gateway_id = var.myigw-id
}
resource "aws_route_table_association" "pub_assoc1" {
subnet_id = var.pub-sub1-id
route_table_id = var.pub-rt-id
}
resource "aws_route_table_association" "pub_assoc2" {
subnet_id = var.pub-sub2-id
route_table_id = var.pub-rt-id
}
### Private-Route 생성 1,2 ###
### Private-Subnet 생성 ###
resource "aws_subnet" "pri_sub1" {
vpc_id = var.vpc-id
cidr_block = var.pri-sub1-cidr
availability_zone = var.zone_1
tags = var.pri-sub1-tags
}
resource "aws_subnet" "pri_sub2" {
vpc_id = var.vpc-id
cidr_block = var.pri-sub2-cidr
availability_zone = var.zone_2
tags = var.pri-sub2-tags
}
### Private-Route 생성-연결 ###
resource "aws_route_table" "pri_rt" {
vpc_id = var.vpc-id
tags = var.pri-rt-tags
}
resource "aws_route" "pri_route" {
route_table_id = var.pri-rt-id
destination_cidr_block = var.destination_cidr_block
nat_gateway_id = var.mynat-id
}
resource "aws_route_table_association" "private_assoc1" {
subnet_id = var.pri-sub1-id
route_table_id = var.pri-rt-id
}
resource "aws_route_table_association" "private_assoc2" {
subnet_id = var.pri-sub2-id
route_table_id = var.pri-rt-id
}
### keypair ###
resource "aws_key_pair" "testkey" {
key_name = "testkey"
public_key = file("~/.ssh/testkey.pub")
}
### 보안그룹 - instance ###
resource "aws_security_group" "SG_instance" {
name = "SG_instance"
description = "Allow HTTP(80/tcp, 8080/tcp), ssh(22/tcp)"
vpc_id = var.vpc-id
ingress {
description = "Allow HTTP(80)"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow HTTPs(8080)"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow ssh(22)"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "SG_instance"
}
}
### EC2 역할 & 정책 ###
### EC2 정책 ###
data "aws_iam_policy_document" "ec2_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ec2.amazonaws.com"]
}
}
}
### EC2 역할 ###
resource "aws_iam_role" "ec2-role" {
name = "ecr-role"
assume_role_policy = data.aws_iam_policy_document.ec2_role.json
### Policy
}
resource "aws_iam_role_policy_attachment" "AdministratorAccess" {
role = aws_iam_role.ec2-role.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
resource "aws_iam_instance_profile" "test_profile" {
name = "test_profile"
role = aws_iam_role.ec2-role.name
}
### bastion-Instance 생성 ###
resource "aws_instance" "bastion-host" {
ami = "ami-011ab7c70f5b5170a"
instance_type = "t2.micro"
iam_instance_profile = aws_iam_instance_profile.test_profile.name
vpc_security_group_ids = [aws_security_group.SG_instance.id]
subnet_id = var.pub-sub1-id
user_data = <<-EOF
#!/bin/bash
sudo -i sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
sed -i 's/^#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
systemctl restart sshd
echo 'qwe123' | passwd --stdin root
yum install -y docker
EOF
root_block_device {
volume_size = 10
}
tags = var.ec2-tags
}
##### VPC 생성 #####
variable "vpc_cidr" {
description = "VPC"
type = string
default = "10.16.0.0/16"
}
variable "instance_tenancy" {
description = "Instance Tenancy"
type = string
default = "default"
}
variable "vpc_tag" {
description = "VPC tags"
type = map(string)
default = {
Name = "my_vpc"
}
}
variable "vpc-id" {
description = "VPC ID"
type = string
}
### Subnet ###
### public subnet ###
variable "pub-sub1-cidr" {
description = "Pub_Sub1 CIDR Block"
type = string
default = "10.16.1.0/24"
}
variable "pub-sub2-cidr" {
description = "Pub_Sub2 CIDR Block"
type = string
default = "10.16.2.0/24"
}
variable "pub-sub1-tags" {
description = "Pub_Sub1 tags"
type = map(string)
default = { Name = "pub_sub1" }
}
variable "pub-sub2-tags" {
description = "Pub_Sub2 tags"
type = map(string)
default = { Name = "pub_sub2" }
}
### zone ###
variable "zone_1" {
description = "value"
type = string
default = "us-east-2a"
}
variable "zone_2" {
description = "value"
type = string
default = "us-east-2b"
}
### private-subnet ###
variable "pri-sub1-cidr" {
description = "Pri_Sub1 CIDR Block"
type = string
default = "10.16.3.0/24"
}
variable "pri-sub2-cidr" {
description = "Pri_Sub2 CIDR Block"
type = string
default = "10.16.4.0/24"
}
variable "pri-sub1-tags" {
description = "Pri_Sub1 tags"
type = map(string)
default = { Name = "pri_sub1" }
}
variable "pri-sub2-tags" {
description = "Pri_Sub2 tags"
type = map(string)
default = { Name = "pri_sub2" }
}
### route_table ###
variable "pub-rt-id" {
description = "Pub_RT_ID"
type = string
}
variable "pub-rt-tags" {
description = "Pub_RT tags"
type = map(string)
default = { Name = "pub_rt_table" }
}
variable "myigw-id" {
description = "Internet Gateway ID"
type = string
}
variable "pub-sub1-id" {
description = "Pub_Sub1 ID"
type = string
}
variable "pub-sub2-id" {
description = "Pub_Sub_2 ID"
type = string
}
variable "pri-rt-id" {
description = "Pri_RT ID"
type = string
}
variable "pri-rt-tags" {
description = "Pri_RT tags"
type = map(string)
default = { Name = "pri_rt_table" }
}
variable "pri-sub1-id" {
description = "Pri_Sub1 ID"
type = string
}
variable "pri-sub2-id" {
description = "Pri_Sub2 ID"
type = string
}
variable "destination_cidr_block" {
description = "Destination_cidr_block"
type = string
default = "0.0.0.0/0"
}
### internet gateway ###
variable "igw-tags" {
description = "Internet Gateway tags"
type = map(string)
default = { Name = "my_igw" }
}
### NAT gateway ###
variable "myeip-id" {
description = "Eip ID"
type = string
}
variable "mynat-id" {
description = "NAT Gateway ID"
type = string
}
variable "nat-tags" {
description = "Internet Gateway tags"
type = map(string)
default = { Name = "my_nat" }
}
### bation host ###
variable "ec2-tags" {
description = "Internet Gateway tags"
type = map(string)
default = { Name = "bastion-host" }
}
output "vpc_id" {
description = "VPC ID"
value = aws_vpc.my_vpc.id
}
output "pub_sub1_id" {
description = "PUBLIC SUBNET1"
value = aws_subnet.pub_sub1.id
}
output "pub_sub2_id" {
description = "PUBLIC SUBNET2"
value = aws_subnet.pub_sub2.id
}
output "pri_sub1_id" {
description = "PRIVATE SUBNET1"
value = aws_subnet.pri_sub1.id
}
output "pri_sub2_id" {
description = "PRIVATE SUBNET2"
value = aws_subnet.pri_sub2.id
}
output "pub_rt_id" {
description = "ROUTING TABLE ID"
value = aws_route_table.pub_rt.id
}
output "pri_rt_id" {
description = "ROUTING TABLE ID"
value = aws_route_table.pri_rt.id
}
output "eip_id" {
description = "VALUE"
value = aws_eip.NAT-eip.id
}
output "igw_id" {
description = "IGW ID"
value = aws_internet_gateway.my_igw.id
}
output "nat_id" {
description = "VALUE"
value = aws_nat_gateway.myNAT.id
}
### ECS 역할 정책 ###
### 정책-task ###
data "aws_iam_policy_document" "ecs_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["ecs-tasks.amazonaws.com"]
}
}
}
### 역할-task ###
resource "aws_iam_role" "ecs_role" {
name = "ecs-role"
assume_role_policy = data.aws_iam_policy_document.ecs_role.json
}
### Policy-ECS ###
resource "aws_iam_role_policy_attachment" "AmazonECS_FullAccess" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
### Policy-ECS-Task ###
resource "aws_iam_role_policy_attachment" "AmazonECSTaskExecutionRolePolicy" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
### Policy-S3 ###
resource "aws_iam_role_policy_attachment" "AmazonS3FullAccess" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
### Policy - cloudwatch_logs ###
resource "aws_iam_role_policy_attachment" "CloudWatchLogsFullAccess" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess"
}
resource "aws_iam_role_policy_attachment" "CloudWatchReadOnlyAccess" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess"
}
resource "aws_iam_role_policy_attachment" "AmazonAPIGatewayPushToCloudWatchLogs" {
role = aws_iam_role.ecs_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs"
}
### ECS 생성 ###
### ECS_Task_Definition ###
resource "aws_ecs_task_definition" "service" {
family = "service"
network_mode = "awsvpc"
execution_role_arn = aws_iam_role.ecs_role.arn
cpu = 256
memory = 512
requires_compatibilities = ["FARGATE"]
task_role_arn = aws_iam_role.ecs_role.arn
container_definitions = jsonencode([
{
"name": "service",
"image": "${var.ecr-url}",
"cpu": 256,
"memory": 512,
"essential": true,
"portMappings": [
{
"name": "serivce-80-tcp",
"containerPort": 80,
"hostPort": 80,
"appProtocol": "http"
}
],
"logconfiguration" : {
"logdriver" : "awslogs",
"options" : {
"awslogs-group" : "${var.log-group}",
"awslogs-region" : "us-east-2",
"awslogs-stream-prefix" : "${var.log-stream}",
}
}
}
])
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
}
### ECS Cluster ###
resource "aws_ecs_cluster" "ECS_Cluster" {
name = "my_cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
### ECS service ###
resource "aws_ecs_service" "ECS-Service" {
name = "service"
cluster = aws_ecs_cluster.ECS_Cluster.id
task_definition = aws_ecs_task_definition.service.arn
launch_type = "FARGATE"
desired_count = 1
network_configuration {
security_groups = [aws_security_group.SG_alb.id]
subnets = [
var.pri-sub1-id,
var.pri-sub2-id
]
assign_public_ip = true
}
load_balancer {
target_group_arn = aws_lb_target_group.ALB-TG.arn
container_name = aws_ecs_task_definition.service.family
container_port = 80
}
}
### LB 구성 ####
### 보안그룹 - ALB ###
resource "aws_security_group" "SG_alb" {
name = "WEBSG"
description = "Allow HTTP(80/tcp, 8080/tcp)"
vpc_id = var.vpc-id
ingress {
description = "Allow HTTP(80)"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
description = "Allow HTTPs(8080)"
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "SG_alb"
}
}
### ALB 생성 ###
resource "aws_lb" "ALB" {
name = "myALB"
load_balancer_type = "application"
subnets = [
var.pub-sub1-id,
var.pub-sub2-id
]
security_groups = [aws_security_group.SG_alb.id]
}
### ALB Listner 생성 ###
resource "aws_lb_listener" "ALB-Listener" {
load_balancer_arn = aws_lb.ALB.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.ALB-TG.arn
}
}
### Tagret Group 생성 ###
resource "aws_lb_target_group" "ALB-TG" {
name = "myALB-TG"
port = 80
protocol = "HTTP"
target_type = "ip"
vpc_id = var.vpc-id
}
###ECR 생성 ###
resource "aws_ecr_repository" "my_ecr" {
name = "my_ecr"
image_tag_mutability = "MUTABLE"
image_scanning_configuration {
scan_on_push = true
}
tags = var.ecr-tags
}
resource "aws_ecr_lifecycle_policy" "ecr_policy" {
repository = aws_ecr_repository.my_ecr.name
policy = <<EOF
{
"rules": [
{
"rulePriority": 1,
"description": "Keep last 30 images",
"selection": {
"tagStatus": "tagged",
"tagPrefixList": ["v"],
"countType": "imageCountMoreThan",
"countNumber": 30
},
"action": {
"type": "expire"
}
}
]
}
EOF
}
variable "ecr-url" {
description = "ECR-URL"
type = string
}
variable "pri-sub1-id" {
description = "Pri-Sub1 ID"
type = string
}
variable "pri-sub2-id" {
description = "Pri-Sub2 ID"
type = string
}
variable "pub-sub1-id" {
description = "Pub-Sub1 ID"
type = string
}
variable "pub-sub2-id" {
description = "Pri-Sub2 ID"
type = string
}
variable "vpc-id" {
description = "VPC ID"
type = string
}
variable "ecr-tags" {
description = "ECR tags"
type = map(string)
default = { Name = "ecr" }
}
variable "log-group" {
description = "cloudwatch log group ID"
type = string
}
variable "log-stream" {
description = "cloudwatch log stream ID"
type = string
}
output "dns_name" {
description = "ALB DNS Name"
value = aws_lb.ALB.dns_name
}
output "ecr_name" {
description = "ECR NAME"
value = aws_ecr_repository.my_ecr.name
}
output "ecr_url" {
description = "ECR NAME"
value = aws_ecr_repository.my_ecr.repository_url
}
output "ecs-cluster-name" {
description = "ECS CLUSTER NAME"
value = aws_ecs_cluster.ECS_Cluster.name
}
output "ecs-service-name" {
description = "ECS SERVICE NAME"
value = aws_ecs_service.ECS-Service.name
}
data "aws_security_group" "default" {
name = "default"
vpc_id = var.vpc-id
}
### Codebuild ###
### 역할- Codebuild ###
resource "aws_iam_role" "codebuild_role" {
name = "build-role"
assume_role_policy = data.aws_iam_policy_document.codebuild_role.json
}
### 정책-codebuild ###
data "aws_iam_policy_document" "codebuild_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["codebuild.amazonaws.com"]
}
}
}
# policy-build
resource "aws_iam_role_policy_attachment" "role_policy_attachment" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess"
}
resource "aws_iam_role_policy_attachment" "AdministratorAccess" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
# policy-ecr
resource "aws_iam_role_policy_attachment" "role_policy_attachment2" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess"
}
# policy-s3
resource "aws_iam_role_policy_attachment" "role_policy_attachment3" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
# codecommnit
resource "aws_iam_role_policy_attachment" "CodeCommitFullAccess" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess"
}
# codepipeline
resource "aws_iam_role_policy_attachment" "CodePipeline_FullAccess" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess"
}
# policy-ecs
resource "aws_iam_role_policy_attachment" "AmazonECS_FullAccess" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
# policy-ecs-task
resource "aws_iam_role_policy_attachment" "AmazonECSTaskExecutionRolePolicy" {
role = aws_iam_role.codebuild_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
### codebuildproject ###
resource "aws_codebuild_project" "MyBuildProject" {
name = "MyBuildProject"
description = "code-build-project"
service_role = aws_iam_role.codebuild_role.arn
artifacts {
type = "S3"
name = var.s3-id
location = var.s3-bucket
path = "/"
packaging = "ZIP"
}
cache {
type = "LOCAL"
modes = ["LOCAL_DOCKER_LAYER_CACHE", "LOCAL_SOURCE_CACHE"]
}
environment {
compute_type = "BUILD_GENERAL1_SMALL"
image = "aws/codebuild/amazonlinux2-x86_64-standard:4.0"
type = "LINUX_CONTAINER"
image_pull_credentials_type = "CODEBUILD"
privileged_mode = true
}
vpc_config {
vpc_id = var.vpc-id
subnets = [
var.pri-sub1-id,
var.pri-sub2-id
]
security_group_ids = [
data.aws_security_group.default.id
]
}
source {
type = "CODECOMMIT"
location = var.code-repo-url
buildspec = "buildspec.yml"
}
logs_config {
cloudwatch_logs {
group_name = "Build-log-group"
status = "ENABLED"
}
}
}
### Codecommit ###
### Codecommit-repository ###
resource "aws_codecommit_repository" "MyCommitRepository" {
repository_name = "MyCommitRepository"
description = "Repository for CodeCommit"
}
# 역할-pipe
resource "aws_iam_role" "pipe_role" {
name = "pipe-role"
assume_role_policy = data.aws_iam_policy_document.pipe_role.json
}
# 정책 - pipe
data "aws_iam_policy_document" "pipe_role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["codepipeline.amazonaws.com"]
}
}
}
# policy-pipeline
resource "aws_iam_role_policy_attachment" "AWSCodePipeline_FullAccess" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodePipeline_FullAccess"
}
# policy-s3
resource "aws_iam_role_policy_attachment" "AmazonS3FullAccess" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
# policy-commit
resource "aws_iam_role_policy_attachment" "AWSCodeCommitFullAccess" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess"
}
resource "aws_iam_role_policy_attachment" "AWSCodeCommitReadOnly" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeCommitReadOnly"
}
# policy-build
resource "aws_iam_role_policy_attachment" "AWSCodeBuildAdminAccess" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess"
}
# policy - CodeBuildReadOnlyAcces
resource "aws_iam_role_policy_attachment" "AWSCodeBuildReadOnlyAccess" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeBuildReadOnlyAccess"
}
# policy-ecs-task
resource "aws_iam_role_policy_attachment" "AmazonECSTaskExecutionRolePolicy1" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}
# policy-ecs-ecs
resource "aws_iam_role_policy_attachment" "AmazonECS_FullAccess1" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}
# policy-ecs-deploy
resource "aws_iam_role_policy_attachment" "AWSCodeDeployRoleForECS" {
role = aws_iam_role.pipe_role.name
policy_arn = "arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS"
}
### pipeline ###
resource "aws_codepipeline" "codepipeline" {
name = "test-pipeline"
role_arn = aws_iam_role.pipe_role.arn
artifact_store {
location = var.s3-bucket
type = "S3"
}
stage {
name = "Source"
action {
name = "Source"
category = "Source"
owner = "AWS"
provider = "CodeCommit"
version = "1"
output_artifacts = ["source_output"]
configuration = {
RepositoryName = "MyCommitRepository"
BranchName = "master"
}
}
}
stage {
name = "Build"
action {
name = "Build"
category = "Build"
owner = "AWS"
provider = "CodeBuild"
input_artifacts = ["source_output"]
output_artifacts = ["build_output"]
version = "1"
configuration = {
ProjectName = "MyBuildProject"
}
}
}
stage {
name = "Deploy"
action {
name = "Deploy"
category = "Deploy"
owner = "AWS"
provider = "ECS"
input_artifacts = ["build_output"]
version = "1"
configuration = {
ClusterName = "my_cluster"
ServiceName = "service"
FileName = "imagedefinitions.json"
}
}
}
}
### S3 역할 & 정책###
### S3 역할 ###
resource "aws_iam_role" "s3-role" {
name = "s3-role"
assume_role_policy = data.aws_iam_policy_document.s3-role.json
}
### S3 정책 ###
data "aws_iam_policy_document" "s3-role" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["s3.amazonaws.com"]
}
}
}
### Policy-S3 ###
resource "aws_iam_role_policy_attachment" "AmazonS3FullAccess2" {
role = aws_iam_role.s3-role.name
policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
### S3 생성 ###
resource "aws_s3_bucket" "Mys3" {
bucket = "mys3-8596"
}
variable "vpc-id" {
description = "VPC ID"
type = string
}
variable "s3-id" {
description = "S3 NAME"
type = string
}
variable "s3-bucket" {
description = "S3 BUCKET"
type = string
}
variable "pri-sub1-id" {
description = "Pri-Sub1 ID"
type = string
}
variable "pri-sub2-id" {
description = "Pri-Sub2 ID"
type = string
}
variable "code-repo-url" {
description = "Code Commit Repository URL"
type = string
}
/*
variable "code-build-id" {
description = "code-build-project ID"
type = string
}
/*
variable "commit-repository-id" {
description = "Repository for CodeCommit"
type = string
}
*/
#####
output "s3_url" {
description = "s3-url"
value = aws_s3_bucket.Mys3.arn
}
output "s3_id" {
description = "s3-name"
value = aws_s3_bucket.Mys3.id
}
output "s3_bucket" {
description = "s3 bucket"
value = aws_s3_bucket.Mys3.bucket
}
output "code_repo_url" {
description = "Code Commit Repository URL"
value = aws_codecommit_repository.MyCommitRepository.clone_url_http
}
output "code_build_id" {
description = "Code Build Project Name"
value = aws_codebuild_project.MyBuildProject
}
/*
output "log_group_id" {
description = "cloudwatch_log_group Name"
value = aws_cloudwatch_log_group.log-group.name
}
*/
### CloudWatch Dashboard ###
resource "aws_cloudwatch_dashboard" "ecs_dashboard" {
dashboard_name = "ecs-dashboard"
dashboard_body = jsonencode({
widgets = [
{
type = "metric",
x = 0,
y = 0,
width = 12,
height = 6,
properties = {
metrics = [
["AWS/ECS", "CPUUtilization", "ServiceName", var.ecs-service-name, "ClusterName", var.ecs-cluster-name, {stat = "Average"}],
[".", "MemoryUtilization", ".", ".", ".", ".", {stat = "Average"}]
],
region = "us-east-2"
annotations = {
horizontal = [
{
color = "#ff9896",
label = "100% CPU",
value = 100
},
{
color = "#9edae5",
label = "100% Memory",
value = 100,
yAxis = "right"
},
]
}
yAxis = {
left = {
min = 0
}
right = {
min = 0
}
}
period = 300,
title = "ECS Service Metrics",
},
},
],
})
}
### CloudWatch Log-group & log-stream
resource "aws_cloudwatch_log_group" "log-group" {
name = "task-log-group"
retention_in_days = "14"
}
resource "aws_cloudwatch_log_stream" "log-stream" {
name = "log-stream"
log_group_name = aws_cloudwatch_log_group.log-group.name
}
### ECS CloudAlarm metric - CPU ###
resource "aws_cloudwatch_metric_alarm" "ecs_service_cpu_alarm" {
alarm_name = "ecs-service-cpu-alarm"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 2
metric_name = "CPUUtilization"
namespace = "AWS/ECS"
period = 300
statistic = "Average"
threshold = 40
actions_enabled = true
alarm_description = "This will alarm if ECS service CPU utilization is greater than or equal to 80%"
dimensions = {
ServiceName = var.ecs-service-name
ClusterName = var.ecs-cluster-name
}
alarm_actions = [var.sns-topic-arn]
}
### ECS CloudAlarm metric - Memory ###
resource "aws_cloudwatch_metric_alarm" "ecs_service_memory_alarm" {
alarm_name = "ecs-service-memory-alarm"
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 2
metric_name = "MemoryUtilization"
namespace = "AWS/ECS"
period = 300
statistic = "Average"
threshold = 40
actions_enabled = true
alarm_description = "This will alarm if ECS service memory utilization is greater than or equal to 80%"
dimensions = {
ServiceName = var.ecs-service-name
ClusterName = var.ecs-cluster-name
}
alarm_actions = [var.sns-topic-arn]
}
### SNS Topic 생성 ###
resource "aws_sns_topic" "sns_topic" {
name = "sns-topic"
display_name = "SNS Topic"
}
### SNS 구독 생성 ###
resource "aws_sns_topic_subscription" "example_subscription" {
topic_arn = var.sns-topic-arn
protocol = "email"
endpoint = "dongju08@naver.com" # 이메일 주소로 변경
}
variable "ecs-service-name" {
description = "ecs-service-name"
type = string
}
variable "ecs-cluster-name" {
description = "ecs-cluster-name"
type = string
}
variable "sns-topic-arn" {
description = "sns-topic-arn"
type = string
}
output "cloudwatch-log-name" {
description = "cloudwatch-log-name"
value = aws_cloudwatch_log_group.log-group.name
}
output "cloudwatch-log-stream-name" {
description = "cloudwatch-log-steram"
value = aws_cloudwatch_log_stream.log-stream.name
}
output "sns-topic-arn" {
description = "cloudwatch-log-steram"
value = aws_sns_topic.sns_topic.arn
}











현재까지 구축환경의 그림대로 잘 생성되었다. 이제 본격적으로 테스트를 진행해보겠다. <먼저 미리 만들어놨던 Bationhost와 linux 로컬 두 곳 에서 테스트를 다 진행해보았다.>

FROM tgagor/centos-stream
MAINTAINER dongju
RUN yum -y install httpd
COPY index.html /var/www/html/
CMD ["/usr/sbin/httpd", "-D", "FOREGROUND"]
EXPOSE 80
- Docker 컨테이너 이미지를 빌드하기 위한 지시사항을 포함하고 있는 파일이다. ECS는
Docker 기반의 컨테이너 오케스트레이션 서비스 이므로 Dockerfile을 필수이다.
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t service:1 .
- docker tag service:1 880076045111.dkr.ecr.us-east-2.amazonaws.com/my_ecr:latest
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the Docker image...
- docker push 880076045111.dkr.ecr.us-east-2.amazonaws.com/my_ecr:latest
- printf '[{"name":"service","imageUri":"%s"}]' 880076045111.dkr.ecr.us-east-2.amazonaws.com/my_ecr:latest > imagedefinitions.json
artifacts:
files: imagedefinitions.json
codebuild.yml은 AWS CodeBuild에서 빌드 프로젝트를 정의하는 설정 파일이다.
이 파일은AWS CodeBuild가 빌드 프로젝트를 실행할 때 참조되어 빌드 환경과 빌드 단계를 지정한다.
- pre_build 단계: ‘aws ecr get-login’ 명렁어를 통해 Amazon ECR에 로그인한다. 이 명령어는 Docker 클라이언트가 ECR에 푸시 및 풀할 수 있도록 인증 토큰을 생성한다.
- build단계: 도커 이미지를 빌드하고 lates 및 Git 커밋 ID로 이미지에 태그를 지정한다. - post_build 단계: ECR 리포지토리에 두 태그와 함께 이미지를 푸시한다. Amazon ECS 서
비스의 컨테이너 이름과 이미지 및 태그를 포함하는 ‘imagedefinitions.json 파일을 빌드
루트로 사용한다. 이 파일은 ECS Task Definition에 사용할 이미지 정보를 담고 있다. 생
성된 JSON 형식은 [{"name":"service","imageUri":"<ECR 리포지토리 URI>:latest"}]로 구성된다.- artifacts 단계: ‘imagedefinitions.json’ 파일을 CodeBuild 빌드 프로젝트의 Artifacts에저장한다.
이 스크립트는 주로 CI/CD 파이프라인에서 사용되며, Docker 이미지를 빌드하고 ECR에 푸
시한 뒤, ECS Task Definition을 업데이트하는데 필요한 정보‘imagedefintions.json’ 파일에
저장한다. 그 후 이 파일을 배포 파이프라인 단계에서 사용한다.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Dongju Last Project!</title>
<style>
/* 스타일 시트 */
html {
height: 100%;
}
body {
background: #2196F3;
overflow: hidden;
height: 100%;
color: #FBFBFF;
}
.centered {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
div#shadow {
/* 그림자 효과를 주기 위한 스타일 */
-moz-transform: translateZ(0);
-ms-transform: translateZ(0);
-webkit-transform: translateZ(0);
transform: translateZ(0);
text-shadow: 3px 3px 0px rgba(11, 79, 108, 0.1);
-moz-transition: text-shadow 20ms;
-o-transition: text-shadow 20ms;
-webkit-transition: text-shadow 20ms;
transition: text-shadow 20ms;
-webkit-text-stroke: 1px rgba(13, 71, 161, 0.5);
}
div#shadow .shadow {
font-size: 5rem;
font-weight: bolder;
}
a {
color: #FBFBFF;
}
a:hover {
text-decoration: none;
}
</style>
</head>
<body>
<!-- 페이지 내용 -->
<div class="centered">
<div id='shadow'>
<div class='shadow'>
Dongju Last Project!
-> repository
</div>
</div>
</div>
<!-- JavaScript 및 jQuery 코드 -->
<script src="https://code.jquery.com/jquery-3.3.1.slim.js"></script>
<script>
// JavaScript 코드
// ...
</script>
</body>
</html>
- index.html을 파일은 github에서 가져온 후 수정하였다.

$ git init
$ git add.
$ git commit –m test
$ git push origin master
-> repository


- 소스 단계 -> codecommit
Codecommitrepository에서 코드를 가져와지고 구성 변경이 감지됩니다.- 빌드 단계 -> codebuild
Codebuild를 이용하여 도커이미지를 ECR 저장소에 Push합니다.- 배포 단계 -> ECS
빌드된 애플리케이션이 ECS에 테스트 및 배포됩니다.

index.html 파일을 고쳐 ci/cd가 잘 적용되는지 확인 테스트를 해보겠다.


테라폼코드로 ECS의 CPU, Memory를 Cloud Watch dashboard를 이용하여 지표화, cloudwatch metric alarm 설정을 통해 어느 적정선이 넘으면 경보 알람을 울리도록 설정했다. 그리고 알람이 SNS를 통해 Email로 전송된다. -> 새로운 dockerfile을 작성한후 배포 시킨다.
FROM ubuntu:latest
RUN apt-get update && apt-get install –y stress-ng
CMD ["stress-ng", "--cpu", "2"]
Cloudwatch Dahboard

cloudwatch dashboard를 확인해보니 cpu가 급격하게 올라간 것을 확인할 수 있다.
Cloudwatch metric alarm

metric alarm을 통해 또한 경보 발생도 확인하였다. cpu에만 부하를 주었기 떄문에
memory는 알람이 울리지 않았다.

SNS를 통한 Email 발송 성공

- Task definition에서 로그를 구성하는 설정을 지정하는 부분으로 테라폼에서 jasoncode를 지정하며, 생성한 로그를 어디에 저장할지, 어떤 형식으로 저장할지를 정의한다.
- 미리 생성해둔 cloudwatch 로그 그룹과 스트립에 연동시켰다.
- 해당 컨테이너에서 실행중인 어플리케이션이나 서비스가 생성하는 메시지를 출력한다.

- ECS Cluster 또는 Kubernetes 클러스터에서 사용되며 클러스터 및 컨테이너 수준에서
성능을 모니터링하고 디버깅한다. 주로 cpu사용률, 메모리 사용률,네트워크 I/O 등 컨테이너의 성능 지표를 수집한다.- 테라폼 코드를 통해 ECS Cluster를 만드는 부분에서 Cloudwatch와 연동시켰다.

- CodeBuild에서 Build하는 과정을 CloudWatch와 연동시켜 Build내용을 모니터링할 수 있다.
- 테라폼에서 CodeBuild를 구성할 때 설정해준다.