ECS, ECR, Github Actions를 사용하여 Nest.js CICD하기

LeeJaeJun·2023년 5월 27일
5

시작하며

본 글은 Nest.js 프레임워크로 개발된 코드를 Github Actions을 활용하여 자동 배포하는 과정입니다. Gihtub Actions로 Docker로 Build 하고 Amazon Elastic Container Registry에 Push한 후 Amazon Elastic Container Service에서 서비스를 실행시키는 과정입니다.

명명

  • Amazon Elastic Container Service - Amazon ECS
  • Amazon Elastic Container Registry - Amazon ECR

서비스 흐름

  1. Nest.js 프로젝트를 Github Repository에 Push 합니다.
  2. Github Actions에서 트리거가 발생하여 사전에 지정한 Step을 진행
    • 프로젝트를 Docker로 Build
    • Build된 Docker Image를 ECR로 Push
    • ECR에 등록한 Image를 ECS에서 실행

구축 과정

  1. IAM Role 설정
  2. IAM User 설정
  3. Github Actions의 Secret Key 설정
  4. AWS VPC 설정
  5. AWS 보안그룹 설정
  6. AWS ECS Cluster 설정
  7. AWS ECS Task Definition 설정

작성일

  • 2022년 08월 26일

IAM Role 설정

사용 사례 설정시 Elastic Container Service Task를 선택하지 않으면 ECS

IAM User 설정

Github Actions Secret Key 설정

> CICD를 설정한 Repository > Settings > Secrets > Actions AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY 설정 위에서 발급받은 액세스 키 ID와 비밀 액세스 키

VPC 설정

> 가용 영역수를 1개로 설정할경우 ecs 클러스터를 생성할 수 없음

보안 그룹

>VPC를 제대로 설정해야함

ECR 설정

ECS Cluster 설정

>Fargate 와 EC2 중 선택해서 고르기. Fargate 사용시 LB는 Instance가 아닌 IP로 설정해야함

>우리는 Fargate로 진행하였음.

ECS Task definition 설정

> Amazon ECS > 작업정의 > 새 작업정의 생성

> 클러스터에서 설정한 내용을 바탕으로 진행. Fargate로 진행

> 테스크 역할, 과 작업 실행 역할은 사전에 지정한 role-nestjs로 설정. 운영체제 패밀리는 Linux로 설정 > 작업 메모리 및 작업 CPU는 필요한 용량에 맞게 지정

> 컨테이너 추가시 컨테이너 이름은 무관하지만 이미지 주소를 ECR의 주소로 설정해야한다. 단, "레포지토리 주소로 설정하지말고 레포지토리에서 이미지 태그까지 포함된 주소를 복사해야한다. 예시는 아래와 같다.

331485284130.dkr.ecr.ap-northeast-2.amazonaws.com/repository-nestjs:799d02e96cab5b1143e09c6fd3025a30ca2d5c6a

> 생성한 작업정의 > JSON에 들어가면 Task-definition.json에 들어갈 코드를 복사할 수 있다.

Task-definition.json

{
  "ipcMode": null,
  "executionRoleArn": "arn:aws:iam::331485284130:role/role-nestjs",
  "containerDefinitions": [
    {
      "dnsSearchDomains": null,
      "environmentFiles": null,
      "logConfiguration": {
        "logDriver": "awslogs",
        "secretOptions": null,
        "options": {
          "awslogs-group": "/ecs/role-nestjs",
          "awslogs-region": "ap-northeast-2",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "entryPoint": null,
      "portMappings": [
        {
          "hostPort": 3000,
          "protocol": "tcp",
          "containerPort": 3000
        }
      ],
      "command": null,
      "linuxParameters": null,
      "cpu": 0,
      "environment": [],
      "resourceRequirements": null,
      "ulimits": null,
      "dnsServers": null,
      "mountPoints": [],
      "workingDirectory": null,
      "secrets": null,
      "dockerSecurityOptions": null,
      "memory": null,
      "memoryReservation": null,
      "volumesFrom": [],
      "stopTimeout": null,
      "image": "331485284130.dkr.ecr.ap-northeast-2.amazonaws.com/repository-nestjs:799d02e96cab5b1143e09c6fd3025a30ca2d5c6a",
      "startTimeout": null,
      "firelensConfiguration": null,
      "dependsOn": null,
      "disableNetworking": null,
      "interactive": null,
      "healthCheck": null,
      "essential": true,
      "links": null,
      "hostname": null,
      "extraHosts": null,
      "pseudoTerminal": null,
      "user": null,
      "readonlyRootFilesystem": null,
      "dockerLabels": null,
      "systemControls": null,
      "privileged": null,
      "name": "container-nestjs"
    }
  ],
  "placementConstraints": [],
  "memory": "1024",
  "taskRoleArn": "arn:aws:iam::331485284130:role/role-nestjs",
  "compatibilities": ["EC2", "FARGATE"],
  "taskDefinitionArn": "arn:aws:ecs:ap-northeast-2:331485284130:task-definition/role-nestjs:3",
  "family": "role-nestjs",
  "requiresAttributes": [
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.logging-driver.awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-awslogs"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.ecr-auth"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.19"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.task-iam-role"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.execution-role-ecr-pull"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "com.amazonaws.ecs.capability.docker-remote-api.1.18"
    },
    {
      "targetId": null,
      "targetType": null,
      "value": null,
      "name": "ecs.capability.task-eni"
    }
  ],
  "pidMode": null,
  "requiresCompatibilities": ["FARGATE"],
  "networkMode": "awsvpc",
  "runtimePlatform": {
    "operatingSystemFamily": "LINUX",
    "cpuArchitecture": null
  },
  "cpu": "256",
  "revision": 3,
  "status": "ACTIVE",
  "inferenceAccelerators": null,
  "proxyConfiguration": null,
  "volumes": []
}

hostPort를 수정하면 외부 포트를 변경할 수 있음

ECS Service 생성

> 기존 VPC 설정 및 기존 보안그룹 설정

> 기존에 설정한 보안그룹 등록

Elastic Load Balancer 설정

Elastic Load Balancer에서 등록한 포트로 ECS의 서비스가 연결된다. 따라서 ECS 컨테이너가 3000번으로 Open 되었어도 Load Balancer의 Listener가 80포트로 연결되었다면 80포트를 3000포트로 연결해준다.

> Application LB 선택

> LB 이름 설정

> 새로 생성한 VPC 설정 및 기존 보안그룹 설정

targetGroup 설정

> Create TargetGroup

Fargate의 경우 IP addresses로 설정해야 가능함. > [대상 유형(Target type" width="800"/>

]에서 [인스턴스(Instance" width="800"/>

] 또는 [IP]를 선택합니다. 중요: 서비스의 태스크 정의에서 awsvpc 네트워크 모드(AWS Fargate 시작 유형에 필수" width="800"/>

를 사용하는 경우 대상 유형으로 [IP]를 선택해야 합니다. awspc 네트워크 모드를 사용하는 태스크의 경우 탄력적 네트워크 인터페이스에 연결되기 때문입니다. 이러한 태스크는 Amazon Elastic Compute Cloud(Amazon EC2" width="800"/>

에 연결되지 않습니다.

> VPC와 Health check를 받을 수 있는 Path 확인후 Next

> VPC 확인후 다음

> 돌아와서 TargetGroup 설정 완료

Create LB

ECS Service로 돌아와서 마저 진행

> 로드밸런서 이름설정

> 대상 그룹 이름을 기존 TargetGroup으로 설정 후 다음단계

> 다음 그리고 서비스 생성

Github Actions 설정

name: Deploy to Amazon ECR

on:
  push:
    branches:
      - main
env:
  AWS_REGION: ap-northeast-2
  ECR_REGISTRY: 331485284130.dkr.ecr.ap-northeast-2.amazonaws.com/repository-nestjs
  ECR_REPOSITORY: repository-nestjs

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest
    environment: production

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@v1

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: setting-task-definition
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: container-nestjs
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.setting-task-definition.outputs.task-definition }}
          service: service-nestjs
          cluster: cluster-nestjs
          wait-for-service-stability: false

  • container-name, service, cluster는 env로 이름을 명명할 수 없고 직접 넣어줘야함
  • setting-task-definition은 작업정의를 생성된 image에 맞게 새로운 버전을 계속 생성함

완성

수정사항

Warning: Ignoring property 'compatibilities' in the task definition file. This property is returned by the Amazon ECS DescribeTaskDefinition API and may be shown in the ECS console, but it is not a valid field when registering a new task definition. This field can be safely removed from your task definition file.
Warning: Ignoring property 'taskDefinitionArn' in the task definition file. This property is returned by the Amazon ECS DescribeTaskDefinition API and may be shown in the ECS console, but it is not a valid field when registering a new task definition. This field can be safely removed from your task definition file.
Warning: Ignoring property 'requiresAttributes' in the task definition file. This property is returned by the Amazon ECS DescribeTaskDefinition API and may be shown in the ECS console, but it is not a valid field when registering a new task definition. This field can be safely removed from your task definition file.
Warning: Ignoring property 'revision' in the task definition file. This property is returned by the Amazon ECS DescribeTaskDefinition API and may be shown in the ECS console, but it is not a valid field when registering a new task definition. This field can be safely removed from your task definition file.
Warning: Ignoring property 'status' in the task definition file. This property is returned by the Amazon ECS DescribeTaskDefinition API and may be shown in the ECS console, but it is not a valid field when registering a new task definition. This field can be safely removed from your task definition file.

0개의 댓글