현제의 현재이야기

[Infra] 잊지말자 CodeDeploy 본문

Infra

[Infra] 잊지말자 CodeDeploy

현재의 현제 2023. 10. 13. 23:09

기존 나의 github action workflow에 큰 불만이 있었다.

- name: Connect and Deploy to EC2
        uses: appleboy/ssh-action@v0.1.6
        with:
          host: ${{ secrets.WAS_HOST }}
          username: ec2-user
          key: ${{ secrets.KEY }}
          script: |
            docker stop $(docker ps -a -q) 
            docker rm $(docker ps -a -q)
            cd /home/ec2-user/
            aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin 279381197488.dkr.ecr.ap-northeast-2.amazonaws.com
            docker-compose pull  
            docker-compose up -d

바로 이 appleboy를 사용한 ec2 접속이었다. 아무래도 오픈 소스이고, SSH키를 통해서 직접적으로 EC2에 접근하는 방식은 좋지 않다고 생각했다. 그래서 여러가지 찾아봤는데 AWS에 codedeploy라는 놈이 있었다. AWS 생태계에 더욱 가까워지면 좋기 때문에 무엇인지 오늘 하루 각잡고 나의 노션봇에 적용시켜보았다.

바뀐 나의 Gitgub action workflow

name: Deploy to EC2

on:
  push:
    branches: [ main ]

jobs:
  continuous-integration:
    name: Deploy to EC2
    runs-on: ubuntu-latest

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

      - name: write env file
        run: |
          echo "NOTION_TOKEN=${{ secrets.NOTION_TOKEN }}" > .env
          echo "SLACK_API_TOKEN=${{ secrets.SLACK_API_TOKEN }}" >> .env
          echo "REDIS_HOST=${{ secrets.REDIS_HOST }}" >> .env
          echo "DB_USER=${{ secrets.DB_USER }}" >> .env
          echo "DB_SCHEMA=${{ secrets.DB_SCHEMA }}" >> .env
          echo "DB_PASSWORD=${{ secrets.DB_PASSWORD }}" >> .env
          echo "DB_HOST=${{ secrets.DB_HOST }}" >> .env
          echo "DB_PORT=${{ secrets.DB_PORT }}" >> .env
    
      - name: Set up and build Docker Compose
        run: |
          sudo apt-get update
          sudo apt-get -y install docker-compose
          docker-compose build

      - 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: ${{ secrets.AWS_REGION }}

      - name: compose
        run: zip -r ./dist.zip ./appspec.yml ./scripts/ ./docker-compose.yml

      - name: upload to S3
        run: |
          aws s3 cp \
            --region ap-northeast-2 \
            ./dist.zip s3://notion-slack/dist.zip

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

      - name : Build, tag, and push image to Amazon ECR 
        run : | 
            docker-compose push

      - name: Create CodeDeploy Deployment
        id: deploy
        run: |
            aws deploy create-deployment \
            --application-name notion-slack \
            --deployment-group-name notion-slack-group \
            --deployment-config-name CodeDeployDefault.OneAtATime \
            --s3-location bucket=notion-slack,bundleType=zip,key=dist.zip

여기서 추가한 부분은 compose부분과 upload to S3, Create Codedeploy Deployment이다.

compose에서 배포에 필요한 파일들을 zip으로 만들고, s3에 올린다. 그리고 도커 컴포즈 빌드를 한 뒤에 codedeploy를 통해서 ec2에 배포를 하는 방식이다. 우선 codedeploy를 생성해야한다.

Codedeploy 생성

1. 애플리케이션 생성

2. 배포 그룹 생성

IAM을 만들때 역할에 미리 codedeploy 역할을 만들어둔다.

본인의 ec2를 연결해준다.

S3 버킷 생성

그냥 편하게 생성해주고 버킷 이름만 알아둔다.

그리고 S3 버킷 이름과 codedeploy의 그룹이름을 기억했다가 workflow에 넣는다.

Appspec.yml

그리고 이놈이 좀 중요하다. codedeploy에 꼭 필요한 놈인데, 위치를 프로젝트 루트에 놓고, 압축 파일에서도 최상단에 위치해야한다.

version: 0.0
os: linux
files:
  - source: /
    destination: /home/ec2-user/
    overwrite: yes
permissions:
  - object: /home/ec2-user/
    owner: ec2-user
    group: ec2-user
    mode: 755
hooks:
  ApplicationStop:
    - location: scripts/stop_docker.sh
      runas: ec2-user
  AfterInstall:
    - location: scripts/pull_images_from_ecr.sh
      timeout: 300
      runas: ec2-user
  ApplicationStart:
    - location: scripts/start_server.sh
      timeout: 300
      runas: ec2-user

여기서 해맸던 부분. 여기서 source는 무엇이고, destination은 무엇일까.

s3에서 압축된 파일을 codedeploy에서 받아와서, 이 경로에 생성된다.

/opt/codedeploy-agent/deployment-root/<배포그룹ID>/<배포ID>/deployment-archive

이곳을 보면 S3에서 생성했던 zip 파일이 자동으로 압축이 풀려있는 것을 확인할 수 있다.

이 파일들이 files에 source이고, 이 파일들을 어디에 복사할 것이냐를 destination으로 정한다.

permission은 말 그대로 유저가 어떤 파일에 대한 어떤 권한을 얻냐를 설정할 수 있다.

중요한 점은 복사하는 시점은 바로 Install시점인 것이다. 실제로 내가 pull_images_from_ecr 스크립트를 BeforeInstall 단계에서 실행하는 바람에 ec2-user 파일에 docker-compose 파일이 존재하지 않아서 배포 오류가 발생했다.

자세한 생명 주기에 대한 내용은 aws 공식 문서에서 확인하고 작성했다.

 

AppSpec 'hooks' 섹션 - AWS CodeDeploy

배포의 Start, DownloadBundle, Install, BlockTraffic, AllowTraffic 및 End 이벤트는 스크립팅할 수 없기 때문에 이 다이어그램에서 회색으로 표시됩니다. 그러나 AppSpec 파일의 'files' 섹션을 편집하여 Install 이벤

docs.aws.amazon.com

stop_docker 스크립트에서 진행되고 있는 도커 컨테이너를 중단시키고, pull_images_from_ecr 에서 docker 이미지들을 docker compose에 맞게 ecr에서 pull 받은 후, start_server을 통해서 docker-compose up을 실행한다. 이로써 간단하게 AWS 생태계를 사용한 나만의 CICD를 완성했다. 어디가서든 뚝딱뚝딱 빠르게 서버를 빌드할 수 있을 것 같다.

트러블 슈팅

첫번째

정말 어이없게도 오타 때문에 5시간을 날렸다. 자꾸만 appspec 파일에서 Location을 찾을 수 없다고 나왔다.

The deployment failed because the application specification file specifies a script with no location value. Specify the location in the hooks section of the AppSpec file, and then try again.

정말 눈물이 났다. -location: 밑에 runas에 -를 잘못 넣는 바람에 그 다음 Location을 인식하지 못해서 난 오류였다.

그 다음으로는 도커 컨테이너가 실행중이여야 정지할 수 있는데, 없어서 못한댄다.

그래서 그냥 docker-compose down으로 변경해서 해결했다.

 

전부터 변경하고 싶었던 부분이라 삽질을 했어도, 그 과정에서 codedeploy에 대해서 잘 알게된 것 같아서 뿌듯하다. 

codedeploy를 사용하면 blue green 배포도 alb를 통해서 쉽게 설정할 수 있다고 하는데 추가적인 금액이 들 것 같아서 잘 공부해보고 도전해보아야겠다. 

두번째

InstanceAgent::Plugins::CodeDeployPlugin::CommandPoller: Cannot reach InstanceService: Aws::CodeDeployCommand::Errors::AccessDeniedException - Aws::CodeDeployCommand::Errors::AccessDeniedException

다른 계정에도 적용시키려고 하니깐 계쏙 이런 오류가 나를 괴롭혔다.

뭐지 싶어서 계속 찾아봤는데, 전에 만들어둔 인스턴스에는 ecs를 넣으려고 ec2 역할을 미리 지정해두었었다. 그런데 새로운 인스턴스에는 역할이 지정되지 않아서 오류가 났었다.

저 둘중에 어떤 걸 넣어야 될지 몰라서 일단 두개 다 넣어두었다. 첫번째만 있어도 될 것 같긴 하다. 그리고 ecr에 대한 권한도 필요하더라.

 

 

codedeploy에서도 역할이 필요한데,

일단 이렇게 주었다. 그리고

rm /home/ec2-user/.aws/credentials

이후에

sudo service codedeploy-agent restart

 를 통해서 codedeploy-agent를 재시작 해주었다. 역할을 꼭 넣어주자!

++) 추가로 에러 보는 곳

cat /var/log/aws/codedeploy-agent/codedeploy-agent.log
Comments