[[ECR ECS CodePipeline]]
---
AWSTemplateFormatVersion: "2010-09-09"
Description: "Amazon VPC - Private and Public subnets"
Parameters:
VpcBlock:
Type: String
Default: 10.1.0.0/16
Description: The CIDR range for the VPC. This should be a valid prviateate (RFC 1918) CIDR range.
PublicSubnet01Block:
Type: String
Default: 10.1.0.0/24
Description: CidrBlock for publiclic subnet 01 within the VPC
PublicSubnet02Block:
Type: String
Default: 10.1.1.0/24
Description: CidrBlock for publiclic subnet 02 within the VPC
PrivateSubnet01Block:
Type: String
Default: 10.1.2.0/24
Description: CidrBlock for prviateate subnet 01 within the VPC
PrivateSubnet02Block:
Type: String
Default: 10.1.3.0/24
Description: CidrBlock for prviateate subnet 02 within the VPC
Subnet01AZ:
Type: String
Default: "ap-northeast-2a"
Subnet02AZ:
Type: String
Default: "ap-northeast-2b"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Worker Network Configuration"
Parameters:
- VpcBlock
- PublicSubnet01Block
- PublicSubnet02Block
- PrivateSubnet01Block
- PrivateSubnet02Block
Resources:
VPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-vpc"
InternetGateway:
Type: "AWS::EC2::InternetGateway"
VPCGatewayAttachment:
Type: "AWS::EC2::VPCGatewayAttachment"
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref VPC
PublicRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Public Subnets
- Key: Network
Value: Public
PrivateRouteTable01:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Private Subnet AZ1
- Key: Network
Value: Private01
PrivateRouteTable02:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref VPC
Tags:
- Key: Name
Value: Private Subnet AZ2
- Key: Network
Value: Private02
PublicRoute:
DependsOn: VPCGatewayAttachment
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PrivateRoute01:
DependsOn:
- VPCGatewayAttachment
- NatGateway01
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable01
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway01
PrivateRoute02:
DependsOn:
- VPCGatewayAttachment
- NatGateway02
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PrivateRouteTable02
DestinationCidrBlock: 0.0.0.0/0
NatGatewayId: !Ref NatGateway02
NatGateway01:
DependsOn:
- NatGatewayEIP1
- PublicSubnet01
- VPCGatewayAttachment
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt "NatGatewayEIP1.AllocationId"
SubnetId: !Ref PublicSubnet01
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-NatGatewayAZ1"
NatGateway02:
DependsOn:
- NatGatewayEIP2
- PublicSubnet02
- VPCGatewayAttachment
Type: AWS::EC2::NatGateway
Properties:
AllocationId: !GetAtt "NatGatewayEIP2.AllocationId"
SubnetId: !Ref PublicSubnet02
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-NatGatewayAZ2"
NatGatewayEIP1:
DependsOn:
- VPCGatewayAttachment
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
NatGatewayEIP2:
DependsOn:
- VPCGatewayAttachment
Type: "AWS::EC2::EIP"
Properties:
Domain: vpc
PublicSubnet01:
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 01
Properties:
MapPublicIpOnLaunch: true
AvailabilityZone: !Ref Subnet01AZ
CidrBlock:
Ref: PublicSubnet01Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-a"
PublicSubnet02:
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 02
Properties:
MapPublicIpOnLaunch: true
AvailabilityZone: !Ref Subnet02AZ
CidrBlock:
Ref: PublicSubnet02Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-public-b"
PrivateSubnet01:
Type: AWS::EC2::Subnet
Metadata:
Comment: Subnet 03
Properties:
AvailabilityZone: !Ref Subnet01AZ
CidrBlock:
Ref: PrivateSubnet01Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-prviate-a"
PrivateSubnet02:
Type: AWS::EC2::Subnet
Metadata:
Comment: Private Subnet 02
Properties:
AvailabilityZone: !Ref Subnet02AZ
CidrBlock:
Ref: PrivateSubnet02Block
VpcId:
Ref: VPC
Tags:
- Key: Name
Value: !Sub "${AWS::StackName}-prviate-b"
PublicSubnet01RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet01
RouteTableId: !Ref PublicRouteTable
PublicSubnet02RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet02
RouteTableId: !Ref PublicRouteTable
PrivateSubnet01RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet01
RouteTableId: !Ref PrivateRouteTable01
PrivateSubnet02RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet02
RouteTableId: !Ref PrivateRouteTable02
Outputs:
SubnetIds:
Description: Subnets IDs in the VPC
Value:
!Join [
",",
[
!Ref PublicSubnet01,
!Ref PublicSubnet02,
!Ref PrivateSubnet01,
!Ref PrivateSubnet02,
],
]
VpcId:
Description: The VPC Id
Value: !Ref VPC
위 CloudFormation 스택으로 VPC, Network(subnet, route table, igw, nat gateway) 생성
도커 기본 이미지 빌드 go module init을 위한 Bastion EC2 생성
PowerUserAccess 연결하고 ecs role을 위한 IAM Access도 필요함
jq curl docker golang 까지 설치
aws configure로 region도 미리 설정해 놓자!
ECS 서비스 및 컨테이너 생성을 위해서 ECR 생성
현재 상세 과제 내용이 없는 관계로 go언어를 이용한 application 만들고 Dockerfile도 제작함
main.go
package main
import (
"github.com/gin-gonic/gin"
)
func main() {
gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.Run(":80") // port
}
위 파일을 기반으로 의존성 설치를 위해 아래 두개의 명령어 실행
go mod init wsi-api, go mod tidy
Dockerfile
FROM golang:alpine AS builder
ENV GO111MODULE=on \
CGO_ENABLED=0 \
GOOS=linux \
GOARCH=amd64
WORKDIR /build
COPY go.mod go.sum main.go ./
RUN go mod download
RUN go build -o main .
WORKDIR /dist
RUN cp /build/main .
FROM scratch
COPY --from=builder /dist/main .
ENTRYPOINT ["/main"]
해당 도커파일 기반으로 실행시켜보고 ECR에 배포
aws ecr get-login-password | docker login --username AWS --password-stdin $REPOSITORY_URI
docker push $REPOSITORY_URI:latest
Info: 옛날 구버전 ECS 환경에서 작업할 것
새로운 ECS 환경에서는 Route53, CodeDeploy BlueGreen Deployment 등의
기능들을 지원하지 않는 게 많음
기본 설정대로 생성
ECS Task Definition을 생성하기 위해 ECS 컨테이너를 실행할 IAM Role 을 생성
콘솔에서 작업하는 것이 간단하여 콘솔에서 작업하는 걸 추천
To create a task execution IAM role (AWS Management Console)
Open the IAM console at https://console.aws.amazon.com/iam/.
In the navigation pane, choose Roles, Create role.
In the Trusted entity type section, choose AWS service, Elastic Container Service.
For Use case, choose Elastic Container Service Task, then choose Next.
In the Attach permissions policy section, do the following:
Under Role details, do the following:
ecsTaskExecutionRole.Choose Create role.
Task Definition은 AWS CLI로 생성
아래 파일을 Bastion EC2에 생성 후
명령어를 실행하여 생성한다. family가 task definition 이름, 컨테이너 정의 안에 컨테이너 이름이 있다.
taskdef.json
{
"containerDefinitions": [
{
"name": "wsi-api",
"image": "492622758225.dkr.ecr.ap-northeast-2.amazonaws.com/wsi-api-ecr:latest",
"essential": true,
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
]
}
],
"executionRoleArn": "arn:aws:iam::492622758225:role/ecsTaskExecutionRole",
"taskRoleArn": "arn:aws:iam::492622758225:role/ecsTaskExecutionRole",
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"family": "wsi-api"
}
ECS CodeDeploy Blue Green Deployment를 같이 생성 할 경우 ECS CodeDeploy IAM Role이 필요하므로 생성해준다.
참고로 Blue Green Deployment의 경우 IAM Pass 권한도 필요하다.
아래의 문서를 참고하여 ECSCodeDeployRole을 생성
https://docs.aws.amazon.com/AmazonECS/latest/developerguide/codedeploy_IAM_role.html
ECS 배포를 위해서 ALB, Target Group 2개 생성
ECS 서비스를 만들어준다.
ECS 서비스를 생성하면서 CodeDeploy (ECS Blue/Green Deployments) 와 Route53 또한 같이 생성할 수 있으니 같이 생성한다.
빌드, 배포를 위해 codecommit 레포지토리를 만들고 아래의 모든 파일들을 업로드, 혹은 직접 편집한다
go.mod go.sum 파일은 Bastion EC2에서 생성한 파일을 이용
appspec.yaml
version: 0.0
Resources:
- TargetService:
Type: AWS::ECS::Service
Properties:
TaskDefinition: <TASK_DEFINITION>
LoadBalancerInfo:
ContainerName: "wsi-api"
ContainerPort: 80
buildspec.yml
version: 0.2
phases:
pre_build:
commands:
- echo Logging in to Amazon ECR...
- aws --version
- aws ecr get-login-password | docker login --username AWS --password-stdin $REPOSITORY_URI
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
build:
commands:
- echo Docker build and tagging started on `date`
- docker build -t $REPOSITORY_URI:latest -t $REPOSITORY_URI:$IMAGE_TAG .
- echo Docker build and tagging completed on `date`
post_build:
commands:
- echo Build completed on `date`
- echo Pushing the docker images...
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- echo Update the REPOSITORY_URI:IMAGE_TAG in task definition...
- echo Container image to be used $REPOSITORY_URI:$IMAGE_TAG
- sed -i 's@REPOSITORY_URI@'$REPOSITORY_URI'@g' taskdef.json
- sed -i 's@IMAGE_TAG@'$IMAGE_TAG'@g' taskdef.json
- echo update the REGION in task definition...
- sed -i 's@AWS_REGION@'$AWS_REGION'@g' taskdef.json
- echo update the roles in task definition...
- sed -i 's@TASK_EXECUTION_ARN@'$TASK_EXECUTION_ARN'@g' taskdef.json
artifacts:
files:
- "appspec.yaml"
- "taskdef.json"
배포를 위해서 REPOSITORY_URI와 IMAGE TAG를 sed로 치환하기 위해 값을 placeholder 로 채워놓은 것을 유의!
taskdef.json
{
"containerDefinitions": [
{
"name": "wsi-api",
"image": "REPOSITORY_URI:IMAGE_TAG",
"essential": true,
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
]
}
],
"executionRoleArn": "TASK_EXECUTION_ARN",
"taskRoleArn": "TASK_EXECUTION_ARN",
"requiresCompatibilities": [
"FARGATE"
],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"family": "wsi-api"
}
Codebuild 빌드 프로젝트를 생성
상기한 buildspec.yml을 이용.
이 buildspec.yml을 위해 필요한 환경변수는 다음과 같음
ECR을 사용한 배포이므로 CodeBuild IAM Role에 ECR Builder Policy를 함께 제공할것
위의 3가지 서비스를 조합하여 CodePipeline 생성