CI/CD w/ CircleCI + Docker Hub + AWS EC2

barely-works·2020년 5월 8일
0

telegram-bot

목록 보기
9/9

현재의 개발 후 배포 프로세스에 대해서는 지난 포스팅에 정리를 했다. 어차피 다 스크립트를 손으로 실행시키는 방식인데, 이것들 조금만 엮으면 자동 배포가 가능하겠다 싶었다. 그리고 며칠 뒤 삽질을 거쳐서

Github Merge -> CircleCI -> Build and Push Docker Image -> SSH & Trigger Deploy Script on AWS, via CircleCI

로 이어지는 자동배포를 완성했다. 사실 Git Merge 이전에 PR 이 생성되어야 하고 PR 생성시 Circle 이 Test 들을 돌려주는 과정이 있어야 하는데 아직 테스트가 없어서 (...) master branch 에 merge 되는 경우 그 이후 과정만 자동화를 했다.

일단 본인은 Jenkins 보다는 CircleCI 를 선호하는데, 내가 실행하는 Script 들의 형상관리가 가능하다는 장점과 함께 workflow - jobs - steps 로 이어지는 각 단계별 관리가 수월하기 때문이다. 그리고 이정도의 프로젝트는 무료로 계속 돌릴 수 있으며 형상 관리가 되는 덕택에 다양한 셋업들이 공유되고 찾을 수 있다. 아예 오피셜 셋업인 Orbs 도 존재한다.

먼저 CircleCI 에 가입하고 Github 를 연동한 뒤, Add Project 로 들어가면 연동하려는 Repository 를 고를 수 있다.

Set Up Project 를 하고나면 Circle CI 를 어떻게 설정 할 수 있을지 나오는데 기본적으로 Repository 의 root 에 .circleci 라는 폴더, 그리고 그 안에 config.yml 파일을 넣으면 된다. 그 이후 git 에서 commit & push 를 하면 circleci jobs 이 자동으로 돌아간다.

CircleCI 에서 사용 할 docker image 혹은 machine 을 설정 할 수 있고, 해당 환경에서 Github 의 code 를 checkout 한 뒤 테스트를 할 수 있다. native run 으로 test 를 돌려볼 수도 있고, docker 로 container 를 띄워서 테스트를 할 수도 있다. 현재 테스트 코드가 없는 관계로 현재의 설정은 docker image 를 build 하고 push 한 뒤, ECS 에 SSH 로 접속하여 deploy script 를 실행시킨다.

version: 2.1

jobs:
  build_and_push_docker_image:
    machine: true
    steps:
      - checkout
      - run:
          name: build and push docker image
          command: |
            set -x
            REPO="https://github.com/Junyong-Suh/bitfront-telegram-bot.git"
            touch confidentials.py && echo "FOREIGN_WORKER_BOT_ID = \"${FOREIGN_WORKER_BOT_ID}\"" >> confidentials.py
            LAST_GIT_TAG=$(git ls-remote --tags $REPO | awk '{print $2}' | grep -v '{}' | awk -F"/" '{print $3}' | sort -n -t. -k1,1 -k2,2 -k3,3 | tail -n 1)
            NEW_IMAGE="zechery/bitfront-price-alert:${LAST_GIT_TAG}"
            docker login -u="$DOCKER_ID" -p="$DOCKER_PWD"
            docker build -t ${NEW_IMAGE} .
            docker push ${NEW_IMAGE}
  deploy:
    machine: true
    steps:
      - checkout
      - run:
          name: deploy new image
          command: |
            ssh deployer@ec2-ip.region.compute.amazonaws.com ./circle.sh

workflows:
  version: 2.1
  build_and_deploy:
    jobs:
      - build_and_push_docker_image:
          filters:
            branches:
              only: master
      - deploy:
          requires:
            - build_and_push_docker_image
          filters:
            branches:
              only: master

여기서 confidential 에 해당하는 부분은 Git 에 올라가지 않게 하고 있기에 CircleCI Project 의 environment varible 에 setup 을 해두고 쓰고 있다.

docker hub 에 접근하기 위한 credential, telegret bot 의 secret key 등은 이쪽에 설정을 해두어 외부 및 그 어떤 log 에도 남지 않게 할 수 있다.

touch confidentials.py && echo "FOREIGN_WORKER_BOT_ID = \"${FOREIGN_WORKER_BOT_ID}\"" >> confidentials.py

현 프로젝트에서는 아예 credential 을 파일로 관리하여 git 에 commit 되지 않게 하고 있는데 위의 명령을 통해 해당 파일을 생성하고 하나뿐인 해당 상수를 inject ㅎ해서 사용하고 있다. Git 의 경우는 연동 과정에서 Deploy 키가 자동으로 생성되어 셋업되어 있는 것을 볼 수 있다.

이로 인해

steps:
      - checkout

라는 과정에 Git repository 를 추가 셋업 없이 check out 할 수 있으면,

LAST_GIT_TAG=$(git ls-remote --tags $REPO | awk '{print $2}' | grep -v '{}' | awk -F"/" '{print $3}' | sort -n -t. -k1,1 -k2,2 -k3,3 | tail -n 1)

같은 코드로 숫자로 되어있는 최신 버젼 태그를 가져올 수도 있다. Docker 의 경우는 별도로 로그인을 해줘야 하는데 이 역시 environment variable 에 설정해 둔 것으로 다음과 같이 가져다 쓸 수 있다.

docker login -u="$DOCKER_ID" -p="$DOCKER_PWD"
docker build -t ${NEW_IMAGE} .
docker push ${NEW_IMAGE}

이렇게 Git repository 를 checkout 해서 최신 태그로 Docker image 를 만들어 올리는 것이 build_and_push_docker_image 라는 이름으로 정의되어 사용중이다.

다음 단계는 이렇게 Docker Hub 에 올라가있는 image 를 EC2 에 배포하는 과정이다. AWS 에서 제공하는 ECS 를 사용중이면 Docker Hub 와 연동해서 직접 가져갈 수 있다고 알고 있으나 EC2 에서는 일단 instance 에 script 를 설치해두고 CircleCI 에서 실행시키고 있다.

CircleCI project setting 에 SSH key 를 등록하고,

해당 키를 EC2 instance 에 ~/.ssh/authorized_keys 등록해둔 뒤, AWS EC2 의 SSH inbound IP 를 열어주면 Circle CI 에서 SSH 로 EC2 instance 에 접근해서 특정 스크립트를 실행시킬 수 있다.

혹여 CircleCI 에 SSH key 등록에 실패한다면 이 포스팅을 참조. (https://support.circleci.com/hc/en-us/articles/360015467253-Adding-SSH-Permissions-fails)

ssh deployer@ec2-ip.region.compute.amazonaws.com ./circle.sh

해당 스크립트는 Git 의 최신 tag 와 docker hub image 의 최신 tag 를 비교하고 같다고 image 를 download 한 뒤, 기존 container 를 종료 및 제거 후 최신 버젼을 실행한다.

#!/bin/sh

CYAN='\033[0;36m'
GREEN='\033[0;32m'
NC='\033[0m' # No Color

# validate if both git and docker have the same tag
LAST_TAG=$(./get_last_tag.sh) # execute and capture stdout
STATUS=$? # find out exit status
if [ $STATUS -eq 0 ]; then
  printf "${CYAN}Current Git and Docker tag: ${LAST_TAG}${NC}\n"
else
  printf "${LAST_TAG}"
  exit 1
fi

# pull the new docker image
DOCKER_HUB="zechery/bitfront-price-alert"
NEW_IMAGE="$DOCKER_HUB:$LAST_TAG"
printf "${CYAN}Pulling ${NEW_IMAGE}:${NC}\n"
docker pull ${NEW_IMAGE}

# stop and remove current containers
printf "${CYAN}Stop all running containers:${NC}\n"
docker stop "$(docker ps -aq)"
printf "${CYAN}Remove all containers:${NC}\n"
docker rm "$(docker ps -aq)"

# run the new image
printf "${CYAN}Run ${NEW_IMAGE}:${NC}\n"
docker run -d "$NEW_IMAGE"

# display running containers
printf "${CYAN}Docker containers:${NC}\n"
docker ps
printf "${GREEN}Deployed ${NEW_IMAGE}${NC}\n"

이제 셋업이 완료되었다. 현재 Circle CI 의 master branch 에 merge 되는 경우만 실행이 되도록 해놓았기 때문에 master brnach 에 merge 발생 시 Circle CI 가 알아서 docker tag 를 추출해 docker image 를 build 하여 docker hub 에 push 하고, EC2 instance 에 배포한다.

추후 테스트를 추가하고 filter 를 제거하면 PR 이 생길 때마다 unit test 및 integration test 를 수행하고, docker image 를 build 후 test 환경에서 실행시켜서 e2e 혹은 integration test 를 한번 더 수행 시킬 수도 있다.

후에 scale 이 되는 서비스를 만들려면 ECS 를 쓰거나 현재의 방식을 활용하여 deployer ssh key 를 circle ci 에 설정 -> 배포 할 instance list 확보 -> instance 별로 deploy script 설치 및 authorized key 설정을 하면 쓸 수 있지 않을까 싶다.

profile
another backend engineer

0개의 댓글