Github-action을 통한 CI/CD

0

CI/CD를 githubaction으로 하기로 했으니, 해당 flow를 대충 짜보았다.
먼저 action파일 에 써져있는대로 Docker file을 읽어 Docker image로 만들고, 그 이미지는 Github container registry에 저장된다. 그 다음 그 이미지를 우리 우분투 서버가 다운받아 container화 시키면 된다!

Docker hub? Github Registry?

Docker Hub와 GitHub Container Registry는 모두 Docker 이미지를 저장하고 관리하는데 사용되는 컨테이너 레지스트리이다.

작년 말 Docker Hub가 정책을 변경해서 6개월 동안 활동이 없으면 이미지를 제거 발표한 것도 있고. GitHub Container Registry는 Docker 이미지 레지스트리를 위한 별도의 계정이나 인증 없이도 쉽게 사용할 수 있기 때문에 Github Registry를 선택했다.

언제 github-action을 실행시킬 것인가?

on:
  release:
    types: [published]

우리 프로젝트는 develop이 default branch이고 새로운 변경사항이 생기면 develop에 선 푸쉬 한다음 main에 merge하는 방식이였기 때문에, main에서 release를 딴 시점에 action파일이 구동되도록 작성하였다.

환경 변수는 어떻게 처리할 것인가?

.env파일에 환경변수를 저장했지만, 이는 github에 올라가지 않게 .gitignore파일에 추가해 두었기 때문에 이대로 action파일이 이미지를 생성한다면 환경변수가 없는채로 생성될 것이였다.

따라서 github의 Action secrets에 우리의 환경변수들을 넣어서 관리하였다.


build-args: DATABASE_URL=${{ secrets.DATABASE_URL }}

그 다음 이런식으로 docker의 환경변수로 설정해 주었다.

ARG DATABASE_URL

ENV DATABASE_URL=$DATABASE_URL 

그 다음 Docker file에선 ARG로 변수를 선언해주고 ENV 값에 넣어주었다.

private validateEnv() {
  const missingVariables: string[] = [];

  needEnv.forEach((envVariable) => {
    if (!process.env[envVariable]) {
      missingVariables.push(envVariable);
    }
  });

  if (missingVariables.length > 0) {
    missingVariables.forEach((variable) => {
      this.logger.error(`${variable} is missing`);
    });
    process.exit(1);
  } else {
    this.logger.info('All required environment variables are present.');
  }
}

.env파일에 변수를 넣었는데 github action secret에 넣는 것을 자주 깜빡해서 컨테이너가 자꾸 종료되는 런타임 에러가 발생하여, 처음 프로세스가 실행될때 env변수들이 모두 있는지 확인하는 코드를 삽입해, 하나라도 없다면 즉시 종료하게 만들었다.

어떻게 우리 서버에 접근하지?

그 다음 문제는 어떻게 github가 우리 우분투 서버에 접근하지? 였다.

  code-deploy:
    needs: build-and-push-image
    runs-on: ubuntu-latest
    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Access to server and Run container
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.SERVER_IP }}
          username: ${{ secrets.SERVER_USER }}
          password: ${{ secrets.SSH_PASSWORD }}

찾아보니 appleboy/ssh-action@master action이 있었다. 이를 통해 우리 서버 컴퓨터에 접근하여 image를 다운 받고 실행시킬 수 있었다.

root는 시스템의 모든 부분에 접근할 수 있는 권한을 갖고 있기 때문에, github action에 대한 새로운 유저를 만들어 준다음, docker에 대한 실행권한을 주고 접근할 수 있도록 만들었다.

Docker 이미지를 어떻게 컨테이너화 시키지?

그 다음은 이렇게 서버에 접근하여 이미지를 다운받고, 컨테이너를 실행시키여야 했다.
사실 이미지를 다운받고, 컨테이너를 실행시키는 명령어는 어렵지 않다. 하지만 새로운 버젼의 이미지가 생성되었을때, 기존에 실행되던 컨테이너를 내리고 기존 이미지를 삭제하고, 새로운 이미지를 다운받아 실행시켜야 했다.

if docker ps -a | grep -q ${{ secrets.CONTAINER_NAME }}; then
    docker stop ${{ secrets.CONTAINER_NAME }} 
    docker rm ${{ secrets.CONTAINER_NAME }}
else
   echo "no container"
fi

if docker images | grep -q ${{ secrets.IMAGE_NAME }}; then
   docker rmi ${{ secrets.IMAGE_NAME }} 

이렇게 리눅스의 if - else로 해당 컨테이너의 유무를 판단하여 컨테이너를 내리고 최신 이미지를 다운받는 과정을 script로 작성하였다.
하지만 이걸 모두 action파일에 작성하기엔 너무 길고 관리도 힘들 것 같았다.

Docker-compose를 통한 컨테이너 관리

version: "3.8"

services:
  to1step:
    image: ghcr.io/to1step/backend:v1.2.5
    container_name: to1step
    ports:
      - "80:4000"
    volumes:
      - logs:/tmp/logs

volumes:
  logs:

이렇게 docker-compose 파일에 작성하여 관리하니 github-action file도 깔끔해지고, 변경사항이 있을때도 변경하기 쉬워졌다. 실제로 나중에 로그를 관리해야 해서 volume을 연결할 일이 생겼는데 docker-compose파일만 수정해 주면 되어서, 매우 편리했다.

profile
https://www.youtube.com/watch?v=__9qLP846JE

1개의 댓글

comment-user-thumbnail
2023년 7월 20일

글이 많은 도움이 되었습니다, 감사합니다.

답글 달기