서버 인프라/DevOps

자동 배포 환경 Github Action으로 이전하기

트리맨스 2022. 10. 7. 00:59
반응형

 

현재 서버 배포 환경은 Jenkins를 사용해서 하고 있다. 처음에는 내가 직접 인스턴스를 만들어 상세히 커스텀을 할 수 있다는 점이 좋았는데, 이게 시간이 지날수록 서버를 직접 관리해야 한다는 점이 단점으로 크게 다가왔다. 그래서 자동 배포 관련한 기능을 찾아보니, 나의 목적에 잘 맞는 Github Action이라는 서비스가 있어서, Jenkins에 있는 배포 환경을 Github Action으로 이전하기로 했다.

 

Github Action이란


간단히 말하면 테스트, 빌드, 배포 등의 활동들을 Github 내에서 자동으로 설정할 수 있는 도구이다. 그냥 CI/CD 도구 중 하나인데, Github와의 연동성이 매우 좋은 도구라 보면 된다. 거기에 다양한 플러그인 및 확장 프로그램까지 지원하여 꽤 괜찮은 도구로서의 가능성을 보여준다. 

 

기존 배포 방식


EC2 인스턴스에 직접 젠킨스를 설치하여 동작했다. 자세한 배포 과정은 아래 링크에 잘 나와 있다.

https://tre2man.tistory.com/285?category=951293 

 

Jenkins를 이용하여 Docker 애플리케이션 배포하기 [1]

현재 우리 회사의 배포환경은 절반은 자동, 절반은 수동으로 배포를 하는 중이다. 대략적인 플로우는 github에 push를 하게 되면 dockerhub에서 자동으로 빌드가 되고, 이 이미지를 수동으로 elastic bean

tre2man.tistory.com

 

해당 플로우의 최대 단점은 Jenkins 인스턴스의 관리이다. 우리는 여러개의 노드를 사용할 필요가 없기에 단일 노드만 사용중인데, 혹시나 해당 Jenkins 인스턴스가 중지될까봐 주기적으로 AMI를 백업하고 있다. 게다가 아주 가끔 배포 작업이 5개이상 밀릴 때가 있는데, Queue에 담아서 하나씩 빌드하므로 속도가 종종 느릴때가 있다. 심지어 도커 이미지 파일이 캐시로 남아 있어서 주기적으로 지워야 하기도 한다. 이러한 귀찮은 때문에 관리를 더욱~안해도 되는 Github Action으로 넘어갔다.

 

배포 과정


사실 뭐 배포 과정이라고 하기에도 좀 그렇다. 그냥 일련의 과정들을 명령어의 모음으로 묶은 것이 전부라서 그런 것 같다.

1. push commit 이벤트 확인

2. 환경변수 (dotenv) 파일 제작

3. 도커 이미지를 도커파일을 이용하여 제작

4. 도커 이미지를 ECR에 업로드

5. Elastic Beanstalk 환경 업데이트 (ECR에 있는 최신의 도커 이미지 적용)

6. 슬랙 알람 보내기

 

위에서 해당하는 모든 일련의 과정들을 하나의 yml파일로 묶어 보았다. 보안상 문제가 될 만한 값들은 모두 삭제하거나 변경하였다.

 

name: Deploy

on:
  push:
    branches: [ "dev" ]

jobs:
  deploy_dev:
    runs-on: ubuntu-latest
    env: 
      REGISTRY: docker-registry
    steps:
    - uses: actions/checkout@v3
    - name: Set timezone
      uses: szenius/set-timezone@v1.0
      with:
        timezoneLinux: "Asia/Seoul"
    - name: Create Dotenv
      run: |
        touch .dev.env
        echo "DATABASE_URL=${{ secrets.DATABASE_URL_DEV }}" >> .dev.env
        echo "FIREBASE_DATABASE_URL=${{ secrets.FIREBASE_DATABASE_URL_DEV }}" >> .dev.env
        echo "CLIENT_EMAIL=${{ secrets.CLIENT_EMAIL }}" >> .dev.env
        echo "SECRET_KEY=${{ secrets.SECRET_KEY }}" >> .dev.env
        echo "AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }}" >> .dev.env
        echo "AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }}" >> .dev.env
        echo "JWT_SECRET=${{ secrets.JWT_SECRET }}" >> .dev.env
        echo "BCRYPT_SALT_OR_ROUNDS=${{ secrets.BCRYPT_SALT_OR_ROUNDS }}" >> .dev.env
        echo "TZ=${{ secrets.TZ }}" >> .dev.env
        echo "REDIS_HOST_DEV_LOCAL=${{ secrets.REDIS_HOST_DEV_LOCAL }}" >> .dev.env
        echo "REDIS_HOST_DEV_SERVER=${{ secrets.REDIS_HOST_DEV_SERVER }}" >> .dev.env
        echo "REDIS_HOST_PROD_LOCAL=${{ secrets.REDIS_HOST_PROD_LOCAL }}" >> .dev.env
        echo "REDIS_HOST_PROD_SERVER=${{ secrets.REDIS_HOST_PROD_SERVER }}" >> .dev.env
        echo "REDIS_PORT=${{ secrets.REDIS_PORT }}" >> .dev.env
        echo "REDIS_PWD=${{ secrets.REDIS_PWD }}" >> .dev.env
        echo "REDIS_LIFE=${{ secrets.REDIS_LIFE }}" >> .dev.env
        echo DATE=$(date +"%Y-%m-%d %H:%M KST") >> .dev.env
    - name: Build Dockerfile
      run: docker build -f dev.dockerfile --tag $REGISTRY .
    - name: Configure AWS
      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: ap-northeast-2
    - name: DockerImage Upload
      run: |
        aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin ${{secrets.AWS_ID}}.dkr.ecr.ap-northeast-2.amazonaws.com
        docker tag $REGISTRY:latest ${{secrets.AWS_ID}}.dkr.ecr.ap-northeast-2.amazonaws.com/$REGISTRY:latest
        docker push ${{secrets.AWS_ID}}.dkr.ecr.ap-northeast-2.amazonaws.com/$REGISTRY:latest
    - name: Deploy
      run: aws elasticbeanstalk update-environment --region ap-northeast-2 --environment-name v2-$REGISTRY-jenkins --version-label v2-$REGISTRY-jenkins-source
    - uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        author_name: 'Author'
        fields: repo,message,commit,author,eventName,ref,job,took,pullRequest
        custom_payload: |
          {
            attachments: [{
              color: '${{ job.status }}' === 'success' ? 'good' : '${{ job.status }}' === 'failure' ? 'danger' : 'warning',
            }]
          }
      env:
        SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
      if: always()

 

위에서부터 하나씩 까보면서 확인해보자.

name: deploy 환경의 이름이다.

on ~~ : 전체 동작은 dev branch에 push 동작이 있을 때 jobs를 실행한다.

jobs : 일련의 작업들

runs-on : 어떤 환경에서 명령어들을 실행할 것인지 선택할 수 있다.

env : 환경변수를 직접 지정할 수 있다.

steps : 일련의 과정들을 수행할 곳

uses : github action의 어떠한 라이브러리를 사용할 것인지 나타내는 곳. github action은 github에서 제공하거나 사용자가 따로 제작한 github action 명령어의 집합들을 가져와서 사용할 수 있다.

 

 actions/checkout@v3 : github 저장소의 변화를 감지하는 라이브러리인 듯 하다.

szenius/set-timezone@v1.0 : 해당 배포 환경의 timezone을 설정하는 라이브러리이다.

Create Dotenv : dotenv 파일을 만드는 과정이다.

Build Dockerfile : 도커파일을 빌드한다.

Configure AWS : AWS 서비스와 연결할 것이기 때문에, 해당 라이브러리를 사용했다. 엑세스 아이디, 시크릿 엑세스 키, 리전은 꼭 필요한 구성이다.

DockerImage Upload : 도커이미지를 ECR에 업로드하는 과정. 이거는 ECR에서 레포를 만들 때 cli 명령어를 알려 준다. 이를 참고하면 된다.

Deploy : Elastic beanstalk 환경을 업데이트하는 과정이다. 이거는 AWS 공식 문서 보면 된다.

action-slack@v3 : 슬랙을 보낼 수 있는 라이브러리이다. 배포가 성공했냐, 실패했냐, 중간에 취소했냐 등의 다양한 상태를 슬랙으로 받아볼 수 있다.

 

 

결과


배포가 아주 잘 된다. 슬랙 메시지는 대강 아래의 형식처럼 출력이 된다.

 

중간에 이상한 곳에서 삽질을 한번 했는데, secret key 중에 중간에 특수문자가 끼어 있으면은 환경변수 입력이 잘 안된다. 이럴 때는 yml파일에 직접 env를 설정하여 사용하면 된다.

 

예)

아래의 예시는 종종 안먹힐 때가 있다. (SECRET에 $, ", ' 등의 특수문자 있을 경우)

run:
	echo "SECRET=${{ secrets.SECRET }}" >> .dev.env

하지만 아래 예시로는 대부분 먹힌다.

env:
	SECRET: ${{secrets.SECRET}}
run:
	echo "SECRET=$SECRET" >> .dev.env

 

 

반응형