Data science/MLOps

[Step by Step] Github action으로 CD/CI 배포하기 (예제코드 포함)

연금(Pension)술사 2023. 7. 31. 00:34

이 포스팅은 GitHub Actions를 사용하여 이미지를 생성하고, Amazon Elastic Container Registry (ECR)에 업로드한 후, EC2 인스턴스에 배포하는 단계를 설명합니다.

이 과정은 크게 아래와 같이 이루어집니다.

  1. 개발자가 trigger: 개발자가 github에 코드를 commit / push 등 엑션을 하하고, 리포지토리에서 action trigger가 실행
  2. Action에서 이미지 빌딩 및 푸시: github action에서 이미지 빌드: Dockerfile을 이용하여 이미지를 만들고, 만든 이미지를 ECR에 PUSH
  3. Actiond에서 SSH 접속 후 Pull 및 Run: Action에서 EC2인스턴스에 SSH로 접속하여, ECR에 올려져있는 이미지를 PULL 및 RUN

GitHub Actions를 사용하여 이미지를 생성하고, Amazon Elastic Container Registry (ECR)에 업로드한 후, EC2 인스턴스에 배포하는 단계

 

이 과정을 위해, 사전 요구사항 및 단계는 다음과 같이 나뉩니다:

사전 요구사항(Preliquesite)

  1. Dockerfile 만들기
  2. IAM 생성 및 권한 설정
  3. Github secrets 설정하기

Github Action 설정

  1. Action yaml파일 설정하기
  2. AWS 자격증명: AWS 액세스 키와 시크릿 키, 그리고 지정한 AWS 리전을 사용하여 AWS 자격증명을 설정합니다.
  3. ECR에 로그인: Docker를 ECR에 푸시하기 전에 ECR에 로그인을 수행
  4. Docker 이미지 빌드:   Docker 이미지를 빌드
  5. Docker 이미지 ECR로 푸시: 이미지를 ECR로 푸시하기 위해 해당 이미지를 태깅하고, ECR에 로그인한 뒤 이미지를 푸시합니다.
  6. EC2 인스턴스로 SSH 접속:
    5.1암호화된 PEM 파일을 사용하여 EC2 인스턴스로 SSH 접속합니다.
    SSH로 EC2 인스턴스에 접속한 후, ECR에서 이미지를 풀고 해당 이미지를 실행합니다.

 

 

사전 요구사항


1. Dockerifle 생성: ECR에 올려서 PUSH/PULL할 것이기 때문에, 간단한 Dockerfile을 하나 생성합니다.

//Dockerfile
# 베이스 이미지로 Python 3.8을 사용합니다.
FROM python:3.8

# 작업 디렉토리를 /app으로 설정합니다.
WORKDIR app

# 현재 디렉토리의 모든 파일을 /app 디렉토리로 복사합니다.
COPY . .

# Python 종속성을 설치합니다.b
RUN pip install --no-cache-dir -r requirements.txt
RUN pip install uvicorn

# 앱을 실행합니다.
CMD ["uvicorn", "app:app", "--reload"]

아래와 같이 app.py (FastAPI의 앱)을 간단히 생성합니다.

//app.py
from fastapi import FastAPI

app = FastAPI()


@app.get("/")
async def root():
    return {"message": "Hello World"}

requirements.txt도 하나 생성합니다.

//requirement.txt
fastapi

 

 

2. IAM 생성, 키 생성 및 권한 부여: IAM 생성및 EC2, ECR 관련 권한부여

AWS Identity and Access Management (IAM)은 Amazon Web Services (AWS)에서 제공하는 서비스로, AWS 리소스에 대한 액세스를 관리하고 보안을 강화하는 데 사용됩니다. IAM을 사용하여 AWS 계정에 사용자, 그룹 및 역할을 생성하고, 이러한 엔터티에 대한 권한을 정의하여 AWS 리소스에 대한 액세스를 제어할 수 있습니다. 본 과정에서는 github action으로 AWS 리소스(ECR, EC2)을 제어 하기 위함입니다.

권한은 아래와 같이, "AdministratorAccess" 와 "AmazonEC2ContainerRegistryFullAccess"을 할당했습니다.

 

3. Github secrets 설정: 리포지토리 내, Settings-Security 내 Secrets and variable - Actions 등록

Action secrets는 GitHub Actions 워크플로우에서 사용되는 민감한 정보를 안전하게 저장하고 관리하기 위한 기능입니다. Secrets는 암호화되어 안전한 저장소에 저장되며, 워크플로우 실행 시에만 필요한 환경 변수로 사용할 수 있습니다. Action에서 사용하게되는 변수들을 repository에서 안전하게 저장하고, action에서 사용하는 yaml파일에서 접근이 가능합니다.

다음과 같이 등록이 필요합니다.

  • AWS_ACCESS_KEY_ID: AWS(Amazon Web Services) 계정에 액세스하는 데 사용되는 액세스 키의 ID 부분입니다. AWS API에 액세스할 때 사용
  • AWS_SECRET_ACCESS_KEY: AWS(Amazon Web Services) 계정에 액세스하는 데 사용되는 액세스 키의 시크릿 키 부분입니다. AWS API에 액세스할 때 사용
  • AWS_REGION: AWS 리소스가 위치한 지역(region)을 지정하는 변수. 참고로 ap-northeast-2=아시아 태평양(서울)입니다.
  • REPO_NAME: ECR 리포지토리에 접근하는 데 사용되는 계정 ID 또는 별칭입니다. ECR에 도커 이미지를 푸시하거나 불러올 때 사용
  • EC2_PRIVATE_KEY: AWS EC2 인스턴스에 SSH로 접속할 때 사용되는 개인 키(private key). EC2의 pem키의 내용을 그대로 copy & paste하시면 됩니다. "--benign", "--end"을 포함하여 plain text로 복붙하시면됩니다!

github actions secret이 등록된 화면

 

4. ECR 생성하기: dockerhub을 이용하셨다면, 같은 기능을 하는 AWS의 dockerhub라고 보셔도 무관합니다.

AWS 검색화면에서 ECR을 검색하여 다음과 같이, 리포지토리(repository)을 생성합니다. 아래와 같이 private으로 설정할 수 있습니다. 그리고, Repository name에보면, "숫자".dkr.ecr.ap-northeast-2.amazonaws.com 으로 나열되는데 "숫자"에 해당하는 부분이 aws_account_id이고, ap-northeast-2가 region입니다[1]. aws_account_id도 secret_key로 본 포스팅에서는 등록하였습니다.

ECR (리포지토리) 생성과정 화면
ECR 생성후 리포지토리 화면

[1] https://docs.aws.amazon.com/AmazonECR/latest/userguide/Registries.html#registry_auth

 

 

Action 설정하기


1. Action yaml파일 생성: github/workflows 디렉토리에 새 워크플로우 파일을 생성합니다. 예를 들어, deploy.yml 파일을 만들 수 있습니다. 아래와 같이 "set up a workflow yourself"을 눌러 workflow(yaml파일)을 처음부터 작성할 수 있습니다.

Actions 클릭 후 화면
"set up a workflow yourself" 을 클릭 후 화면

 

그리고, 아래와 같이 yaml파일의 초반부를 작성합니다.

name: Deploy to EC2 with ECR
on:
  push:
    branches:
      - main   # 배포할 브랜치를 선택합니다. 원하는 브랜치로 변경 가능

env:
  EC2_USER: ubuntu
  EC2_HOST: <EC2의 IPv4 공개IP>
  
jobs:
  deploy:
  runs-on: ubuntu-latest

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

 

AWS 자격증명: AWS 액세스 키와 시크릿 키, 그리고 지정한 AWS 리전을 사용하여 AWS 자격증명을 설정합니다.

    - 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 }}

 

ECR에 로그인: Docker를 ECR에 푸시하기 전에 ECR에 로그인을 수행

    - name: Login to ECR
      id: ecr
      uses: jwalton/gh-ecr-login@v1
      with:
        access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        region: ${{ secrets.AWS_REGION }}

 

Docker 이미지 빌드:   Docker 이미지를 빌드

    - name: Build image
      run: |
        docker build --tag my-image:latest .

 

Docker 이미지 ECR로 푸시: 이미지를 ECR로 푸시하기 위해 해당 이미지를 태깅하고, ECR에 로그인한 뒤 이미지를 푸시합니다. 위의 단계에서 만든 "my-image:latest"의 이미지를 ECR로 푸시하기위해서, ECR에 로그인하고, ECR리포지토리에 맞게 테그명을 변경합니다. 만일 ECR의 URL가 "000000000.dkr.ecr.ap-northeast-2.amazonaws.com/my-image" 였다면, 이미지도 재 태그하여 "000000000.dkr.ecr.ap-northeast-2.amazonaws.com/my-image:<버전테그>" 형식어야합니다. 즉 "/????"의 이름도 같아야합니다.

    - name : Push to ECR
      env:
        AWS_REGION: ${{ secrets.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
        docker tag my-image:$IMAGE_TAG $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG
        docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG

 

EC2 인스턴스로 SSH 접속: EC2 인스턴스에 접속하기위해서 ".pem"파일의 내용을 sercrets 변수로부터 stdout을 "key.pem"을 작성을 합니다. 이를 이용하여 ssh -i <pem파일경로> 로 접속합니다. 그리고, SSH에 접속후에 ECR로부터 이미지를 pull 받고, 이미 띄워져있는 컨테이너가있다면 중단, 삭제하고, 새로운 이미지를 컨테이너로 구동합니다. 그리고 마지막으로 pem키를 삭제합니다.

    - name: SSH into EC2 instance and deploy
      env:
        AWS_REGION: ${{ env.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        EC2_USER:  ${{ env.EC2_USER }}
        EC2_HOST:  ${{ env.EC2_HOST }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Create PEM file from encrypted secret
        echo "${{ secrets.EC2_PRIVATE_KEY }}" > key.pem
        chmod 400 key.pem

        # SSH into the EC2 instance and perform deployment
        ssh -o "StrictHostKeyChecking no" -i key.pem $EC2_USER@$EC2_HOST \
          "sudo docker login -u AWS -p \"$(aws ecr get-login-password --region $AWS_REGION)\" $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com \
          && sudo docker pull $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG \
          && sudo docker stop my-container || true \
          && sudo docker rm my-container || true \
          && sudo docker run -d --name my-container -p 8000:8000 $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG" 

        # Remove pem
        rm key.pem

 

여기까지 작성했으면, 이제 trigger을 이용하여 action을 실행해볼 차례입니다 .임의의 파일을 수정하여 main에 push하여 tigger을 동작시키면 action화면에 아래와 같이 모든 job 의 실행이 보이게 됩니다.

 

전체 코드는 아래와 같습니다.

name: Deploy to EC2 with ECR
on:
  push:
    branches:
      - main 

  EC2_USER: ubuntu
  EC2_HOST: <IP>
  
jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout repository
      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: ${{ secrets.AWS_REGION }}

    - name: Login to ECR
      id: ecr
      uses: jwalton/gh-ecr-login@v1
      with:
        access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        region: ${{ secrets.AWS_REGION }}

    - name: Build image
      run: |
        docker build --tag my-image:$IMAGE_TAG .

    - name : Push to ECR
      env:
        AWS_REGION: ${{ secrets.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        aws ecr get-login-password --region $AWS_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com
        docker tag my-image:$IMAGE_TAG $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG
        docker push $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG

    - name: SSH into EC2 instance and deploy
      env:
        AWS_REGION: ${{ env.AWS_REGION }}
        AWS_ACCOUNT: ${{ secrets.AWS_ECR_ACCOUNT }}
        EC2_USER:  ${{ env.EC2_USER }}
        EC2_HOST:  ${{ env.EC2_HOST }}
        IMAGE_TAG: ${{ github.sha }}
      run: |
        # Create PEM file from encrypted secret
        echo "${{ secrets.EC2_PRIVATE_KEY }}" > key.pem
        chmod 400 key.pem

        # SSH into the EC2 instance and perform deployment
        ssh -o "StrictHostKeyChecking no" -i key.pem $EC2_USER@$EC2_HOST \
          "sudo docker login -u AWS -p \"$(aws ecr get-login-password --region $AWS_REGION)\" $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com \
          && sudo docker pull $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG \
          && sudo docker stop my-container || true \
          && sudo docker rm my-container || true \
          && sudo docker run -d --name my-container -p 8000:8000 $AWS_ACCOUNT.dkr.ecr.$AWS_REGION.amazonaws.com/my-image:$IMAGE_TAG" 

        # Remove pem
        rm key.pem
반응형