Github Action으로 CI/CD 구성하기

Ssol·2022년 12월 8일
3
post-thumbnail

개발자는 반복적인 작업을 싫어하는 사람들이라고 한다.
뉴비 개발자인 나도 귀찮은 걸 싫어한다. 똑같은 행위를 계속 반복해야 한다면 이것을 시스템으로 자동화 해버리면 되지 않을까?
그래서 나온 것이 CI/CD이다.

CI/CD

개발 작업하면서 개발서버, 운영서버에 소스코드를 통합하고, 빌드하고, 배포하는 시간만 따져도 꽤 나올 것이다. 이 시간이 일주일, 1달, 1년동안 모이고 모이면 엄청나게 아깝잖아?
그래서 요즘에는 다들 CI/CD로 지속적인 통합, 지속적인 배포를 통해
각자 개발 후 통합 ➝ 테스트 ➝ 배포까지의 애플리케이션 라이프 사이클을 자동화하고 모니터링 하고 있다.
즉, CI/CD는 반복되는 위 과정을 자동화해서 테스트까지 마치고 문제가 없을 때 배포까지 되도록 하는 것이다.
그래서 귀찮을걸 싫어하는 나도 회사 프로젝트에 CI/CD를 적용하기로 했다.

프로젝트에 CI/CD를 적용하려면 우선 프로젝트가 어떻게 빌드되고 배포되는지 과정부터 알아야 겠지?
현재 프로젝트는 하나의 인스턴스에 FE와 BE, DB가 컨테이너로 올라가 있는 구조라서

  1. ssh로 서버에 접속해서
  2. 소스코드를 pull 받아
  3. 프로젝트 build
  4. Docker build 한 뒤,
  5. docker-compose로 실행

방식을 사용하고 있다.

원래는 이 과정을 모두 서버에 직접 접속해 수동으로 해야만 했는데
CI/CD를 구축하면 문제가 없는 코드는 알아서 통합되고 빌드되어 배포까지 된다는 말씀.

그럼 바로 CI/CD를 위한 툴을 찾아보자.

CI/CD 툴

Jenkins

CI/CD를 위한 툴에는 여러 종류가 있는데 가장 유명한 툴로는 Jenkins가 있다.

무료이면서 일상적인 개발 작업을 자동화할 뿐 아니라 파이프라인(Pipeline)을 사용해 거의 모든 언어의 조합과 소스코드 리포지토리에 대한 지속적인 통합과 지속적인 전달 환경을 구축하기 위한 간단한 방법을 제공하고 있다.

프로젝트의 표준 컴파일 환경에서 컴파일 오류 검출해주고, 자동화 테스트도 수행해주며, 프로파일링을 통해 소스 변경에 따른 성능의 변화도 감시할 수 있다. 그리고 개발 업무를 도와주는 많은 플러그인을 가지고 있다.(소나큐브 등)
그리고 많은 사람들이 이용하기 때문에 인터넷에 각종 문서나 참고 자료도 많다는 것도 장점이다.

하지만 이렇게 마냥 장점만 있지는 않은데, 서버에 따로 설치가 필요하며 호스팅을 하나부터 열까지 관리해야해서 비용이 좀 드는 편이다.

나도 예전에 토이 프로젝트를 하면서 젠킨스 서버를 만들어서 CI/CD를 구축한 적이 있어서 이번에도 젠킨스를 사용하려 했는데...

Github Actions

요즘은 Github에 Github Actions라고 자동화 도구가 나왔더라고?
공부하려고 좀 찾아봤더니 카카오 웹툰에서도 사용 중인 툴이면서 젠킨스와는 다르게 설치나 세팅이 필요없이 깃허브를 통해 바로 쓸수 있는게 장점이었다.

레포지토리마다 yaml을 사용해 자동화 작업을 수행할 흐름인 workflow를 설정할 수 있는데 레포지토리마다 최대 20개까지 설정이 가능하며, 이 workflow는 깃허브에서 발생하는 push, merge 같은 이벤트를 기반으로 작동하게 된다.
Workflow는 Runners라고 불리는 Github에서 호스팅 하는 Linux, macOS, Windows 환경에서 실행된다.

게다가 Github의 Market Place에는 여러 사람들이 공유한 workflow가 올라와 있으며, 자신이 직접 만든 workflow도 공유할 수 있다.
템플릿화 되어있는 workflow 스크립트라니 완전 개꿀이지 않은가?

그외 툴로 Travis CI나 Circle CI 처럼 좋은 유료 서비스도 있긴 하지만...

Github Actions으로 정한 이유

뭐 일단 당연히 무료이니까!
그리고 Jenkins보다 구축하기 편해보이니까!!!

예전에 Jenkins 서버를 구축하는데에도 고생을 했었고 AWS 파이프라인을 짜는 데에도 애를 먹었었거든.
일단 빠르게 구축하고 개발 우선 해야 하는 상황이니 빠르게 구축할 수 있고, 만드는 수고도 덜 들어가는 툴로 정하게 되었다.

내 프로젝트의 CI/CD 프로세스

이건 어디까지나 내 프로젝트에 적용하기 위해 사용한 방법으로 ssh를 사용하는 프로젝트에는 참고가 되겠지만 나머지 경우에는 다른 블로그를 찾아보는게 빠를 것이다.

위에 설명한 프로젝트 배포 방식에서 최대한 프로세스 변경 없이 CI/CD를 적용하자면

  1. 소스코드가 github에 push될 때를 트리거로
    (또는 특정 브랜치에 push, merge 될때만)
  2. 배포할 서버에 ssh 접속
  3. 프로젝트 경로로 이동 후 push 된 소스코드 pull
  4. 프로젝트 build와 docker build가 묶여있는 쉘스크립트 파일을 실행
  5. 프로젝트 build, 도커 build 모두 성공시에 docker-compose up -d --build 실행
    (하나라도 실패시 그대로 종료)
  6. 위 과정이 종료되면 workflow 결과를 Slack 알림 채널에 메시지로 전송

이렇게 하면 될 것이다.

사전 작업

우선 CI/CD를 구축하기 전에 사전 준비를 해보자.
Github Action으로 서버에 ssh 접속 하는 yaml 스크립트를 짜야하는데, 위에 Github Action을 소개할 때 다른 사람들이 만들어 놓은 workflow를 Market Place에서 사용할 수 있다고 한거 기억나지?
Market Place에서 ssh 접속할 때 가장 많이 사용되는 appleboy의 ssh remote command를 사용해보자.
https://github.com/marketplace/actions/ssh-remote-commands

ssh remote command 액션의 정보를 찾아보니 password를 이용한 접속보다는 key를 이용하는 것이 문제가 생길 확률이 더 적다고 한다. 그래서 기존 password로 ssh 접속 방식에서 key 방식으로 변경을 하기로 하였다.

ssh key 방식으로 접속할 수 있게 세팅해보자.

ssh-keygen으로 서버 ssh 접속용 키 발급받기

ssh remote command 액션의 문서에 있는 ssh 접속용 key 발급 명령어를 사용해 public키와 private키를 발급받는다.

ssh-keygen -t rsa -b 4096 -C "example@gmail.com"

터미널에 위 명령어를 사용하면 추가로 3번 묻는다.
1번째는 키를 저장할 경로( 기본값 : $HOME/.ssh/id_rsa)
2번째는 passphrase (추가로 사용할 암호, 기본값 없음)
3번째는 passphrase 확인

그냥 엔터 3번 눌러서 passphrase 없이 기본 경로에 저장하도록 하고 생성된 공개 키와 비밀키 중 공개 키를 서버에 넣어줘야 한다.
서버에 접속해서 홈(/) 경로에 있는 .ssh 폴더안에 authorized_keys 파일을 생성하고 이 안에 공개 키 값을 넣어주면 된다. 만약 홈 경로에 .ssh 폴더를 생성하고 넣어주면 된다.
(공개 키 넣을 때 주의 할 점!!!!!!! 절대 마지막에 엔터가 들어가면 안된다. 스페이스나 엔터 등 공개 키 값 외에는 다른 거 넣지 않기)

비밀 키는 ssh 접속할 때 사용하면 되는데 이것을 스크립트에 그대로 노출시키면 내 서버는 그날로 공공재가 되는 날이다.

그렇다면 비밀 값들은 어떻게 처리해야할까?

Github Action의 secrets

이런 상황을 해결하기 위해 당연히 Github Action에도 암호화 처리가 다 되어있지!

깃허브의 레포지토리 세팅에서 secrets에 있는 Actions로 가보면 암호화 될 값을 넣어주는 곳이 있다.

여기에 비밀키나 서버 ip, 계정 명 등을 넣어서 보호할 수 있다.
SSH 접속에 필요한 값은 모두 이곳에 넣어서 숨기자.

  • 서버 ip
  • 계정 명
  • 비밀키
    (비밀키를 넣을 때에는 앞뒤에 있는 -----BEGIN OPENSSH PRIVATE KEY-----와 -----END OPENSSH PRIVATE KEY-----부분까지 모두 넣어줘야 한다.)
  • 포트 번호

값은 키와 벨류 형태로 들어가게 되며, 이렇게 등록한 secrets는 ${{ secrets.XXX }} 이렇게 키값으로 사용할 수 있다.

이제 사전 준비는 끝났으니 자동화 스크립트를 작성해보자.

Workflow yaml 작성하기

Github Actions는 프로젝트 최상위에 .github/workflows 폴더 안에 있는 yml을 읽어서 작동하도록 되어있다.
이 yml 파일을 생성하는 것은 IDE에서 바로 작성해도 되고, 깃허브 레포지토리에 있는 Actions 탭을 사용하여 주어진 템플릿으로 생성할 수 도 있다.

통합과 빌드 그리고 배포

name: ssh for dev deploy # workflow의 이름을 지정해준다.

on: # 작동 트리거로 설정할 부분을 작성하는 곳이다.
  push: # 이 스크립트는 개발서버용 이므로 dev 브렌치에 push될 때 작동하도록 해두었다.
    branches: [ dev ]

# jobs에선 action의 단계(step)를 설정할 수 있다. 
# 여러 개의 job을 사용할 수 있고, job끼리 서로 정보를 교환할 수도 있다.
jobs: 
  build:
    name: Build # job의 이름을 지정해 준다.
    runs-on: ubuntu-latest # job을 실행할 환경을 정해준다.

    steps:
      # Github Actions는 해당 프로젝트를 리눅스 환경에 checkout하고 나서 실행한다.
      # 꼭 필요한 과정!
      # 누가 만들어 놓은 Action을 사용할 때에는 uses 키워드를 사용한다.
      - uses: actions/checkout@v2 

	  # step의 이름을 지정해준다.
      - name: SSH Remote Commands
      # 위에 말했던 appleboy의 Action을 사용
        uses: appleboy/ssh-action@v0.1.4
        # with라는 키워드로 Action에 값을 전달할 수 있다.
        # 아까 설정했던 secrets를 사용해서 값을 가져오자.
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.KEY }}
          port: ${{ secrets.PORT }}
          # ssh 연결이 아무리 늦어도 20초 정도면 된다. 
          # 이 이상 끌게되면 사실상 접속 실패이므로 40초 타임아웃을 걸어두자
          timeout: 40s 
          
          # ssh 접속이 되면 실행할 스크립트
          script: |
            echo "#START"
            cd 내 프로젝트 경로
            
            echo "############# GIT PULL #############"
            pass=$(sudo git pull origin dev)
            echo $pass
            if [ -n "$pass" ]; then 
              echo "############# SH BACK-DEPLOY.SH #############"
              build='Successfully built'
              pass2=$(sh back-deploy.sh)
              echo $pass2
              if [[ "${pass2}" == *"${build}"* ]]; then 
                echo "############# DOCKER-COMPOSE UP #############"
                sudo docker-compose up -d --build
              else 
                echo "############## Build Fail!! ##############"
                exit 1;
              fi
            else
              echo "############## git pull: Error ##############"
              exit 1;
            fi

ssh 접속 후 실행할 스크립트에는 소스코드를 pull을 시도하고 오류가 발생한다면 exit 1로 오류 상태를 리턴하며 종료시키고, 오류가 발생하지 않는다면 프로젝트 빌드와 도커 빌드 명령어가 있는 sh 파일을 실행시킨다.
(bash에서 0은 성공인 종료 상태를 나타내고 1~255는 오류코드를 나타낸다.)
빌드 과정에서 오류가 발생하면 역시 exit 1로 오류 상태를 리턴하며 종료. 빌드 오류가 발생하지 않으면 docker-compose up을 실행한다.

이렇게 하면 dev 브렌치에 push가 되거나 merge가 될 때마다 코드를 통합하고, 빌드, 배포까지 자동으로 이뤄지게 된다.
이 과정은 깃허브 레포지토리의 Actions 탭에서 실시간 로그도 확인할 수 있다.

트리거를 작동시키면 아주 작동이 잘 되는 것을 확인할 수 있다!
secrets로 넣은 값은 로그에서 ***로 표시되고 만약 실패시 어느 부분에서 실패했는지, 무슨 이유 때문인지도 로그에 모두 기록된다.👍

이렇게 pull, 빌드, 배포 과정까지 모두 자동화가 되어 엄청 편해졌긴 하지만 이 자동화 실행 결과가 성공했는지 실패했는지는 깃허브에 들어가야 알 수 있다.
진정한 자동화 마무리를 하려면 실행이 끝나고 나면 결과 상태까지 알림으로 보내도록 해서 손가락 하나 까딱 안하고 확인까지 할 수 있어야겠지?

Slack으로 알림 보내기

push 후 결과 상태까지 바로 알 수 있도록 Slack에 웹훅으로 알림을 보내는 액션을 만들어주면 된다.

.github/actions 경로에 slack-notify 폴더를 만들고 그 안에 action.yml을 생성해주자.

# 액션의 이름은 slack-notify
name: 'slack-notify'

# input으로 받을 값들
inputs:
  status:
  	# 필수 값을 정해줄 수 있는데, status는 필수 옵션을 제거하고
  	# 값이 들어오지 않는다면 기본 값으로 failure를 사용하도록 한다.
    required: false
    default: 'failure'
  slack_incoming_url:
    required: true

runs:
  # using: composite 키워드는 액션을 직접 만들어 사용한다는 의미
  using: 'composite'
  steps:
    - name: Send slack
      # shell 스크립트를 사용할 건데 bash 쉘을 사용하도록 설정했다.
      shell: bash
      # run 뒤에 |를 사용해서 여러 줄의 스크립트를 사용할 수 있다.
      run: |
      	# 전달받은 값을 이용해서 성공/실패를 판단하고 그에 따른 이모티콘 설정
        if [ "${{ inputs.status }}" = "success" ]; then
          EMOTICON="✅"
        else
          EMOTICON="⛔️"
        fi
        
        # ${GITHBU_REPOSITORY}, ${GITHUB_WORKFLOW}, ${GITHUB_RUN_ID} ..
        # 이런 값들은 GitHub Actions에서 제공하는 환경변수 값들이다.
        # 저는 환경변수들을 이용해서 슬랙 알림이 왔을 때 어떤 부분에서 실패했는지 
        # GitHub 레포지토리와 workflow 링크를 넣어주도록 하자.
        MSG="{ \"text\":\">${EMOTICON} workflow (<https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}|${GITHUB_WORKFLOW}>) in <https://github.com/${GITHUB_REPOSITORY}|${GITHUB_REPOSITORY}>\n><https://github.com/${GITHUB_REPOSITORY}/commit/${GITHUB_SHA}/checks|${GITHUB_JOB}> job ${{ inputs.status }}, branch=\`${GITHUB_REF#refs/heads/}\`\"}"
        
        # Slack에 보낼 메시지 내용
        curl -X POST -H 'Content-type: application/json' --data "${MSG}" "${{ inputs.slack_incoming_url }}"

이제 만든 slack 메시지 전송 액션을 원래 사용하던 깃헙 액션 yaml의 뒷 부분에 붙여주기만 하면 된다.

...
              echo "############## git pull: Error ##############"
              exit 1;
            fi

	  # 위에서 만든 Slack 알림 액션을 기존 deploy.yml 액션 아래에 이어서 붙여주자.
	  # 실패시 Slack에 알림을 보낼 step 이름
      - name: Send slack when failed
        # 실패 값이 들어오면
        if: ${{ failure() }}
        # 해당 경로에 있는 action.yml 액션을 사용할건데
        uses: ./.github/actions/slack-notify
        with:
          # Slack 웹훅 주소를 액션에 전달해준다.
          slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}
          
	  # 성공시 Slack에 알림을 보낼 step 이름
      - name: Send slack if completed
        # 성공일때에만
        if: ${{ success() }}
        # 해당 경로에 있는 action.yml 액션을 사용할건데
        uses: ./.github/actions/slack-notify
        with:
          # success 값과 Slack 웹훅 주소를 액션에 전달해준다.
          status: success
          slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}

슬랙 채널 웹훅 링크는 접속 키처럼 공개되면 안되기 때문에 secrets으로 넣어주자.

이제 트리거 브렌치에 push를 하면

이제부터는 배포하려고 컴퓨터 앞에 계속 앉아있을 필요 없이 소스코드를 pull이나 merge만 해두고 커피 한잔 내리러 갔다오면 알아서 결과 보고까지 받을 수 있게 되었다.

나중에 ECS를 이용한 AWS 3tier 아키텍쳐로 변경하게 되면 workflow를 손봐야 겠지만 당분간 사용하기에는 문제 없을 것 같다.

GitHub CLI를 이용하면 굳이 push를 하지 않더라도 repository에 있는 브랜치의 GitHub Actions를 실행할 수 있다고 하니 나중에 이것도 실험해봐야지.
https://github.blog/2021-04-15-work-with-github-actions-in-your-terminal-with-github-cli/

profile
Junior Back-end Developer 🫠

0개의 댓글