Tech

카카오웹툰은 GitHub Actions를 어떻게 사용하고 있을까?

ENTER TECH 2022. 3. 23. 09:00

 

안녕하세요. 카카오웹툰 프론트엔드를 개발하고 있는 레이입니다 :)

 

카카오웹툰은 GitHub을 이용해 코드를 관리하고 있습니다. 그리고 자연스럽게 GitHub Actions를 사용하고 있는데요, 카카오웹툰은 어떤 식으로 GitHub Actions를 사용하고 있는지 간략하게 소개하겠습니다.

 

직접 따라하는 예제 2개와 눈으로만 보는 예제 2개를 준비했어요. 코드가 어려우실 수도 있지만 천천히 주석을 읽으며 따라오시면 잘 이해하실 거라 믿습니다. 그럼 시작하겠습니다!

 

GitHub Actions란?

GitHub Actions의 도큐먼트를 보면 GitHub Actions는 CI/CD와 같은 workflow를 자동화 할 수 있는 도구라고 설명되어 있는데, 쉽게 말해 GitHub 내 어떤 이벤트(push, pull, merge ...)가 발생하면 해당 이벤트에 대해 정해진 동작을 실행하게 하는 도구라고 이해하시면 될 거 같아요.

 

GitHub Actions에는 event, trigger, job, step, uses, name ... 등 여러가지 키워드들이 많은데요, 코드주석으로 함께 설명하도록 하겠습니다.

 

프로젝트 세팅하기

일단 테스트용 프로젝트를 하나 생성해볼게요. 저는 npx create-react-app test 명령어를 이용해서 리액트 프로젝트를 만들었습니다. 그리고 GitHub에 개인 repository 하나를 만들었고, 해당 repository에 프로젝트를 push했습니다. 사진-1처럼 됐다면 일단 프로젝트는 세팅 완료입니다.

 

사진-1

그리고 Settings > Secrets에 아래 나올 GitHub Actions 예제에 필요한 값들을 세팅해볼게요. 저는 2개의 환경변수를 만들어 주었어요. 첫 번째는 Node 버전이 담긴 NODE_VERSION, 두 번째는 슬랙 알림을 보내기 위해 슬랙 Incoming Webhooks Url이 들어있는 SLACK_INCOMING_URL입니다.

 

슬랙 알림을 위한 incoming url은 여기에서 만들어주시면 됩니다. 참고로 저는 Node 버전을 16.13.1으로 세팅했습니다. 사진-2처럼 나오면 완료입니다. GitHub Actions를 사용할 때 정보에 민감한 값들은 여기서 관리해주시면 좋습니다.

 

사진-2

GitHub Actions 사용해보기

바로 GitHub Actions를 사용해볼게요. 해당 repository에 어떤 브랜치든 push될 때마다 코드를 테스트해주는 worflow를 만들 거예요. workflow란 하나 이상의 작업을 실행시키는 자동화된 프로세스라고 보시면 되는데요, push같은 이벤트가 발생했을 때 실행된다고 생각하시면 됩니다.

 

일단 사진-3처럼 ./github/workflows 폴더를 만들고 test-every-push.yml 파일을 만들어 줍니다. GitHub Actions는 yaml 파일을 사용합니다.

 

사진-3

그리고 test-every-push.yml에 아래 코드를 입력해줍니다.

# test-every-push.yml

# workflow의 이름. 나중에 해당 값을 사용할 수 있기 때문에 저는 유니크하게 사용합니다.
name: 'test-every-push'

# workflow를 동작하게하는 trigger입니다.
# repository에 push 이벤트가 발생할 때마다 실행될 거예요.
# push 말고도 여러 가지 이벤트들이 있겠죠?
on: push

# job은 사용자가 정한 플랫폼을 통해 step이라는 일련의 과정을 실행할 수 있어요.
# 여러 개의 job을 사용할 수 있으며, 여러 개의 job을 사용할 때는 서로 정보도 교환할 수 있어요.
# 그리고 각각 독립적으로도 실행할 수도 있어요.
# 해당 예제는 간단한 workflow이므로 하나의 job만 갖도록 할게요.
jobs:
  test:
    # job의 이름을 정해줍니다.
    name: Test lint, tsc, build
    # 저는 해당 job을 리눅스 환경에서 사용할 거예요. 다른 플랫폼이 올 수도 있겠죠?
    runs-on: ubuntu-latest

    # job 안에는 step이라는 키워드가 옵니다. step은 shell script를 실행할 수도 있고,
    # 누군가 만들어 놓은 Action을 사용할 수도 있어요.
    steps:
      # GitHub Actions는 해당 프로젝트를 리눅스 환경에 checkout하고 나서 실행을 합니다.
      # 마치 우리가 브랜치를 만들 때 checkout하는 것처럼요. 꼭 필요합니다.
      # 참고로 아래 코드는 누군가 만들어놓은 Action을 사용하는 겁니다.
      # 만들어놓은 Action을 사용할 때는 uses라는 키워드를 사용해야 돼요.
      - uses: actions/checkout@v2

      # 해당 환경을 Node.js 위에서 실행하겠다고 명시해줍니다.
      # 저희 프로젝트는 리액트니깐요!
      # 마찬가지로 누군가 만들어 놓은 Action이겠죠?
      - name: Use Node.js
        uses: actions/setup-node@v2
        # with라는 키워드로 Action에 값을 전달할 수 있어요.
        # 이 Action은 node-version이라는 값을 받을 수 있네요?
        # 아까 NODE_VERSION이라는 Secret을 만들었는데요,
        # ${{ secrets.XXX }}라는 값으로 GitHub의 Secrets 값을 가져올 수 있어요.
        # node-version은 16.13.1이 되겠죠?
        with:
          node-version: ${{ secrets.NODE_VERSION }}

      # push할 때마다 npm을 install 해야될까요? (시간이 여간 많이 걸리는 게 아닐 텐데 ..)
      # 아닙니다. 해당 프로젝트의 node_modules가 변했는지 안 변했는지를 이용해서
      # 모듈 변화가 있을 때만 npm install을 해줄 수도 있어요.
      - name: Cache node modules
        # 그걸 제공하는 Action도 있네요? 갖다 쓰겠습니다.
        uses: actions/cache@v2
        # 해당 step을 대표하는 id를 설정할 수도 있어요.
        # 해당 값은 뒤의 step에서 사용해볼게요.
        id: cache
        with:
          # node_modules라는 폴더를 검사하여
          path: node_modules
          # 아래 키값으로 cache가 돼있는지 확인합니다.
          key: npm-packages-${{ hashFiles('**/package-lock.json') }}

      - name: Install Dependencies
        # 위 step에서 node_modules에 대한 cache 검사를 했잖아요?
        # 만약 모듈에 변한 게 있다면 `npm install`을 실행하고 아니면 해당 step을 건너뛰게 됩니다.
        # if 키워드는 해당 스텝을 실행할지 말지를 결정할 수 있는 키워드예요.
        # `steps.cache.outputs.cache-hit`이 값은 무엇일까요?
        # 위 step에서 정했던 cache라는 id를 steps.cache로 가져올 수 있어요.
        # cache라는 id 값을 가진 step에서는 cache-hit라는 output을 내뱉네요? 
        # 그걸로 cache가 hit 됐는지 안 됐는지를 알 수 있나봐요!
        # 그 값이 true가 아닐 때만 npm install을 하겠죠?
        if: steps.cache.outputs.cache-hit != 'true'
        run: npm install

      # 아래 로직은 저희가 만든 프로젝트의 lint, tsc, build를 테스트하는 곳이에요.
      # run 키워드는 `npm start`같이 커맨트 명령어를 입력할 수 있게 해주는 키워드예요.
      # `npm run xxx`를 사용하려면 pacakge.json에 해당 명령어에 대한 정의가 필요하겠죠?
      # 저는 간단하게 아래 `사진-4`처럼 2개만 만들어봤어요.(실제로는 테스트 항목이 더 많고, 테스트를 실행할 수 있는 코드가 들어가야겠죠?)
      - run: npm run lint
        # `if: ${{ always() }}`라는 문법은 무엇일까요?
        # 만약 `npm run lint`라는 곳에서 에러가 났다고 칩시다.
        # 그러면 뒤의 tsc, build를 실행하지 않고 해당 workflow가 끝나버리게 됩니다.
        # 뒤의 tsc, build에서도 에러가 있을지도 모르는데 말이죠.
        # 하지만 ${{ always() }}라는 문법을 사용한다면
        # `npm run lint`라는 곳에서 에러가 나도 뒤의 tsc, build까지 다 실행을 해보고 난 뒤 종료합니다.
        # 그래서 모든 테스트 스크립트에 ${{ always() }}를 붙여줍니다.
        if: ${{ always() }}
      - run: npm run tsc
        if: ${{ always() }}
      - run: npm run build
        if: ${{ always() }}

사진-4

이제 변경 사항을 add하고 commit한 뒤 repository에 push해볼게요. 짠! 첫 번째 GitHub Actions가 완성되었어요. 클릭해볼까요? (사진-5)

 

사진-5

사진-6처럼 잘 통과된 모습이네요. 만약 여기서 두 번째 push를 하게되면 새로운 패키지가 추가되지 않는 한, 저 Install Dependencies 단계는 건너뛰게 될 거예요.

 

사진-6

이렇게 push라는 이벤트 발생할 때마다 테스트를 실행하는 workflow를 만들어봤습니다. 바로 두 번째 예제로 가볼까요?

 

Composite Action 만들어보기

아까 코드에서 actions/...라고 누가 만들어놓은 Action을 사용했죠? 이제는 직.접. 만들어보겠습니다. 카카오웹툰은 테스트에 실패하거나, 배포가 완료되면 슬랙으로 알림을 보내주는 Action을 만들어서 사용하고 있습니다. 슬랙 알림 기능은 사람들이 많이 사용할 거 같지 않나요? 이미 여러 사람들이 여기에 많이 만들어 놓았어요. 하지만 저는 직접 만들어보려구요. 바로 만들어보시죠!

 

사진-7처럼 ./github/actions 폴더를 만들고 slack-notify.yml 파일을 만들어 줍니다.

 

사진-7

그리고 slack-notify.yml에 아래 코드를 입력해줍니다.

# slack-notify.yml

# 마찬가지로 이름을 정해줍니다.
name: 'slack-notify'

# 아까 `actions/setup-node@v2`를 사용할 때 with로 node-version을 넘겼던 거 기억나시나요?
# 그 내부는 이렇게 생겼답니다. 저희도 input을 받아야될 거 같아요.
# job의 실패 or 성공에 대한 값, 슬랙 알림을 보낼 값인
# status와 slack_incoming_url을 받아볼게요.
inputs:
  status:
    # 필수 값을 정해줄 수 있어요.
    # status는 required가 아니므로 status라는 값이 input으로 들어오지 않으면
    # default로 `failure`를 사용합니다.
    required: false
    default: 'failure'
  slack_incoming_url:
    required: true

# using: 'composite' 라는 값을 필수로 지정해줘야해요. 직접 Action을 만든다는 의미거든요.
runs:
  using: 'composite'

  # 마찬가지로 step을 가지게 됩니다.
  steps:
    - name: Send slack
      # 저는 shell script를 사용할 예정이라
      # shell이라는 키워드에 bash라는 값을 입력해줄게요.
      shell: bash
      # run: 뒤에 '|'을 붙여서 스크립트를 여러 줄로 사용할 수 있어요.
      run: |
        # 전달받은 status 값을 이용해서 성공, 실패를 판단하고,
        # 그에 따른 이모티콘을 지정해볼게요.
        if [ "${{ inputs.status }}" = "success" ]; then
          EMOTICON=":white_check_mark:"
        else
          EMOTICON=":no_entry:"
        fi

        # ${GITHBU_REPOSITORY}, ${GITHUB_WORKFLOW}, ${GITHUB_RUN_ID} ..
        # 이런 값들은 GitHub Actions에서 제공하는 환경변수 값들입니다.
        # 저는 환경변수들을 이용해서 슬랙 알림이 왔을 때 어떤 부분에서 실패했는지 
        # 바로 클릭해서 GitHub 페이지를 띄워보고싶거든요.
        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/}\`\"}"

        # input으로 받은 slack_incoming_url와 MSG를 실어서 보내줍니다. 그러면 슬랙으로 알람이 오겠죠?
        curl -X POST -H 'Content-type: application/json' --data "${MSG}" "${{ inputs.slack_incoming_url }}"

그리고 아까 만들어 놓은 test-every-push.yml 끝 부분에 슬랙 알림 관련 Action을 추가합니다!

# test-every-push.yml

# ...

  if: ${{ always() }}
- run: npm run build
  if: ${{ always() }}

- name: Send slack when failed
  # 실패했을 때 실행되겠죠?
  if: ${{ failure() }}
  # 직접 '만든' Action이므로 uses 키워드를 이용해서 아래 경로를 입력해줍니다.
  uses: ./.github/actions/slack-notify
  # 아까 secrets로 접근할 수 있게 만들어놓은 슬랙 url을 여기에 써볼게요.
  # with 키워드와 함께 slack_incoming_url 값으로 넘겨줍니다.
  with:
    slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}

- name: Send slack if completed
  # 성공할 때만 실행되겠죠?
  if: ${{ success() }}
  uses: ./.github/actions/slack-notify
  # status input은 받는 쪽에서 default 값을 정해놨기 때문에 success일 때만 넘겨줄게요.
  with:
    status: success
    slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}

이제 add 한 뒤 push해볼까요? 앗 슬랙에 사진-8처럼 알림이 왔네요. 성공했나봐요. 슬랙으로온 메시지에 test라는 링크를 클릭하면 해당 Actions 탭으로 바로 이동합니다. 만약 해당 job이 실패했다면 실패 메시지가 오겠죠? 그때 test 링크를 클릭하면 어디에서 실패했는지 바로 확인할 수 있습니다. 이렇게 환경변수들을 이용해서 자신이 원하는 기능을 만들 수도 있습니다. 사내에서 다른 메신저를 사용하더라도 GitHub Actions를 이용해 위 방법으로 비슷하게 만들 수도 있을 거라고 생각해요!

 

사진-8

정리하면 이렇습니다.

  1. push 이벤트가 발생한다.
  2. Github Actions는 해당 브랜치를 토대로 리눅스 환경에 checkout한다.
  3. Node.js 환경을 세팅한다.
  4. node_modules가 캐싱이되었는지를 검사한다.
  5. 모듈이 변경이 되었다면 npm install을 하고, 변경되지 않았으면 건너뛴다.
  6. 우리가 만들어놓은 npm run 명령어를 이용해 테스트를 각각 실행한다.
  7. 모든 step이 잘 동작하면 슬랙 알림으로 성공 메시지를 보내고, 잘 동작하지 않으면 실패 메시지를 보낸다.

 

카카오웹툰 배포 맛보기(1)

카카오웹툰은 글로벌 서비스를 하고 있습니다. 그래서 각 지역(region) 별로 빌드와 배포를 다르게 해야됩니다.

 

배포 스크립트를 만들기 전에 팀원들과의 약속이 필요합니다. 바로 브랜치 이름인데요, 카카오웹툰은 배포를 할 때 브랜치를 deploy/[twn or kor or tha]/[qa or sandbox or real]/[배포에 관련된 짧은 네이밍] 이런 식으로 이용합니다.

ex. deploy/kor/qa/popup
즉, "나 배포(deploy)할 건데, 지역은 한국(kor)이고 환경은 (qa)야. 그리고 내용은 팝업(popup) 관련된 거야."

 

이런 식으로 배포할 브랜치의 이름을 팀원들과 약속합니다. 그리고 배포를 할 때는 위 네이밍을 이용해서 브랜치를 만들고 push 하겠죠.

바로 스크립트를 살펴볼 텐데요, 위에 소개했던 스크립트와는 다르게 눈으로만 보시면 좋을 거 같아요. 스크립트를 보면서

"아~ 이렇게 하면 빌드와 배포를 특정 리전, 특정 환경으로 배포할 수가 있겠구나~"

 

라는 느낌만 가져가시면 됩니다. 위의 다른 예제에 이미 설명했던 코드는 따로 주석을 작성하지 않고 새로 보이는 것들만 주석을 달아볼게요.

# deploy.yml

# push 이벤트가 발생했을 때, deploy로 시작하는 이름을 가진 브랜치만 실행된다는 말입니다.
# 아래처럼 사용할 수도 있고, 특정 브랜치를 ignore할 수도 있어요.
# 다른 방법을 보고싶으시다면 참고 링크의 `Events that trigger workflows`를 확인해주세요!
on:
  push:
    branches:
      - 'deploy/*/*/*'

jobs:
  deploy:
    name: 'Build & Deploy'
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Use Node.js
        uses: actions/setup-node@v2
        with:
          node-version: ${{ secrets.NODE_VERSION }}

      - name: Cache node modules
        uses: actions/cache@v2
        id: cache
        with:
          path: node_modules
          key: npm-packages-${{ hashFiles('**/package-lock.json') }}

      - name: Install Dependencies
        if: steps.cache.outputs.cache-hit != 'true'
        run: npm install

      - name: Extract region and env from branch
        run: |
          # 환경변수로 브랜치 이름을 가져올 수 있습니다.
          # push된 브랜치 이름이 `deploy/kor/qa/popup`이라면
          # BRANCH라는 변수는 `deploy/kor/qa/popup`이 될 거예요.
          BRANCH=${GITHUB_REF#refs/heads/}
          # 브랜치 이름을 '/'로 쪼개서 SPLITTED_BRANCH라는 변수에 담아볼게요.
          SPLITTED_BRANCH=($(echo $BRANCH | tr "/" "\n"))

          # SPLITTED_BRANCH[1]은 kor이 되겠죠? 이걸 region이라는 값을 통해서 output으로 내보낼게요.
          echo "::set-output name=region::$(echo ${SPLITTED_BRANCH[1]})"
          # SPLITTED_BRANCH[2]는 qa가 되겠죠? 이걸 env라는 값을 통해서 output으로 내보낼게요.
          echo "::set-output name=env::$(echo ${SPLITTED_BRANCH[2]})"
        # 이제 extract라는 값으로 위에서 output으로 내보낸 값들을 뒤의 step에서 사용할 수 있겠죠?
        id: extract

      - name: Get AWS infos
        # 카카오웹툰은 AWS를 사용하고 있습니다.
        # 배포를 하기 위해 AWS 관련된 값들이 필요한데요,
        # 아래 secrets 값들은 위에서 세팅해봤던 Settings > Secrets에 있다고 보시면 됩니다.
        # 민감한 정보니깐요!
        run: |
          # 위 스텝에서 세팅한 extract값을 여기서 사용합니다!
          if [ "${{ steps.extract.outputs.region }}" = "kor" ]; then
            ACCESS_KEY_ID=${{ secrets.AWS_KOR_ACCESS_KEY_ID }}
            SECRET_ACCESS_KEY_ID=${{ secrets.AWS_KOR_SECRET_ACCESS_KEY }}
            S3_PATH=${{ secrets.AWS_KOR_S3_PATH }}
            AWS_REGION=ap-northeast-2
          elif [ "${{ steps.extract.outputs.region }}" = "twn" ]; then
            ACCESS_KEY_ID=${{ secrets.AWS_TWN_ACCESS_KEY_ID }}
            SECRET_ACCESS_KEY_ID=${{ secrets.AWS_TWN_SECRET_ACCESS_KEY }}
            S3_PATH=${{ secrets.AWS_TWN_S3_PATH }}
            AWS_REGION=ap-northeast-1
          else
            ACCESS_KEY_ID=${{ secrets.AWS_THA_ACCESS_KEY_ID }}
            SECRET_ACCESS_KEY_ID=${{ secrets.AWS_THA_SECRET_ACCESS_KEY }}
            S3_PATH=${{ secrets.AWS_THA_S3_PATH }}
            AWS_REGION=ap-northeast-1
          fi

          echo "::set-output name=accessKeyId::$(echo ${ACCESS_KEY_ID})"
          echo "::set-output name=secretAccessKeyId::$(echo ${SECRET_ACCESS_KEY_ID})"
          echo "::set-output name=s3Path::$(echo ${S3_PATH})"
          echo "::set-output name=awsRegion::$(echo ${AWS_REGION})"
        # 다음 step들에서 aws-infos라는 값으로 위의 AWS 정보를 가져올 수 있겠죠?
        id: aws-infos

      # 카카오웹툰은 build하는 action을 따로 만들어두었습니다.
      # 여기서 소개하지는 않을게요.
      - name: Build
        uses: ./.github/actions/build
        with:
          # with 키워드로 넘겨받은 env, region 값으로 판단하여 그에 맞는 빌드를 하겠죠?
          env: ${{ steps.extract.outputs.env }}
          region: ${{ steps.extract.outputs.region }}

      # 카카오웹툰은 deploy하는 action도 따로 만들어두었습니다.
      # 마찬가지로 여기서 소개하지는 않을게요.
      - name: Deploy
        uses: ./.github/actions/deploy
        with:
          # env, region, accessKeyId, secretAccessKeyId, s3Path, awsRegion 값으로
          # 배포할 곳을 판단하여 그에 맞는 곳에 배포를 하겠죠?
          env: ${{ steps.extract.outputs.env }}
          region: ${{ steps.extract.outputs.region }}
          accessKeyId: ${{ steps.aws-infos.outputs.accessKeyId }}
          secretAccessKeyId: ${{ steps.aws-infos.outputs.secretAccessKeyId }}
          s3Path: ${{ steps.aws-infos.outputs.s3Path }}
          awsRegion: ${{ steps.aws-infos.outputs.awsRegion }}

      - name: Send slack if failed
        if: ${{ failure() }}
        uses: ./.github/actions/slack-notify
        with:
          # region, env 값을 이용해서 어느 region, env에서 에러가 났는지
          # 슬랙으로 알림을 보낼 수 있겠네요.
          # slack-notify action에서 파일에 대한 수정이 조금 필요하겠죠?
          region: ${{ steps.extract.outputs.region }}
          env: ${{ steps.get.outputs.env }}
          slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}

      - name: Send slack if completed
        if: ${{ success() }}
        uses: ./.github/actions/slack-notify
        with:
          region: ${{ steps.extract.outputs.region }}
          env: ${{ steps.get.outputs.env }}
          status: success
          slack_incoming_url: ${{ secrets.SLACK_INCOMING_URL }}

정리하면 이렇습니다.

  1. deploy/로 시작하는 브랜치에서 push 이벤트가 발생한다.
  2. Github Actions는 해당 브랜치를 토대로 리눅스(우분투) 환경에 checkout한다.
  3. Node.js 환경을 세팅한다.
  4. node_modules가 캐싱이되었는지를 검사한다.
  5. 모듈이 변경이 되었다면 npm install을 하고, 변경되지 않았으면 건너뛴다.
  6. 브랜치 이름을 이용하여 region, env 정보를 가져온다.
  7. 가져온 region, env를 이용하여 aws에 필요한 값들을 secrets 키워드를 이용해 꺼내온다.
  8. region, env를 이용해 프로젝트를 지역과 환경에 맞게 빌드한다.
  9. aws 정보들을 이용해 프로젝트를 지역과 환경에 맞는 곳에 배포한다.
  10. 모든 step이 잘 동작하면 슬랙 알림으로 성공 메시지를 보내고, 잘 동작하지 않으면 실패 메시지를 보낸다.

카카오웹툰에서는 이런 식으로 특정 환경, 지역에 빌드와 배포를 하고 있어요. 위 코드를 읽고 어떤 식으로 사용할 수 있는지 감이 오셨을까요?

 

카카오웹툰 배포 맛보기(2)

카카오웹툰은 매일매일 정해진 시간에 특정 브랜치를 기준으로 자동배포하고 있습니다. 이번에는 정해진 시간에 자동으로 workflow를 실행시키는 방법에 대해 살펴볼게요. 이것도 눈으로만 이해해주세요!

# auto-weekday-deploy.yml

name: auto-weekday-deploy

on:
  # schedule이라는 이벤트를 걸어 자동 이벤트를 발생시킬 수 있습니다.
  # 문법은 참고 링크의 `Scheduled events`를 확인해주세요!
  # 참고로 해당 이벤트는 `default` 브랜치 기준으로 배포가 됩니다.
  # `default` 브랜치는 Settings > Branches에서 확인하실 수 있습니다.
  # 자동으로 배포하고싶은 브랜치를 default로 변경해주시면 돼요.
  schedule:
    # UTC+9 기준 월~금 매일 오전 9시
    # 앞으로 사용해보시면 아시겠지만, 정확히 9시에 배포가 되진 않는다는 점 참고해주세요!
    - cron: '0 0 * * 1-5'

jobs:
  auto-weekday-deploy:
    name: 'Auto Build & Deploy'
    runs-on: ubuntu-latest

    steps:
      # 매일매일 배포를 하더라도 default 브랜치가 변경된 경우에만 배포하는 게 좋겠죠?
      # 그런 경우에 사용하는 step을 저는 하나 더 만들어주었어요.
      - name: Create cache file for checking commit
        run: |
          # 환경 변수를 이용하여 해당 이벤트를 trigger 시킨 commit 값을 따로 저장해놓을게요!
          mkdir sha
          echo ${{ github.sha }} > github-sha.txt

      - name: Check SHA
        id: check
        # 앞에서 소개했던 다른 예제들에서 node_modules 캐싱 관련 action 기억나시나요?
        # 똑같이 사용해보도록 할게요.
        uses: actions/cache@v2
        with:
          # `Create cache file for checking commit` step에서 저장해놓은
          # commit 값이 있는지를 확인합니다.
          path: sha
          key: sha-${{ github.sha }}

      - name: Exit when recent commit does not exist
        # 마찬가지로 cache-hit가 나면 변경된 사항이 없다는 거고,
        # cache-hit가 나지 않으면 변경사항이 있다는 거겠죠?
        if: steps.check.outputs.cache-hit == 'true'
        run: |
          echo "::set-output name=cancel::$(echo true)"
        # 그 결과를 get이라는 id 값으로 다음 스텝에서 사용할 수 있겠네요.
        id: get

      # ...

      # 아래에서는 어떤 일이 벌어질까요? 아까 소개했던 deploy.yml 예제에서 보셨듯이
      # Build, Deploy, 알림을 보내는 Action들이 있겠죠? 그곳에 if 키워드를 이용하여
      # 해당 스텝들을 건너뛸 수 있을 거 같지 않나요?
      # if: steps.get.outputs.cancel != 'true' <- 이런 식으로 말이죠.
      # 위에서 소개했던 모든 예제를 잘 이해하셨다면 뒤에서 어떤 일이 벌어지는지는 이해하실 거라고 믿어요!

정리하면 이렇습니다.

  1. schedule 이벤트를 통해 자동으로 default 브랜치의 workflow를 trigger한다.
  2. trigger가 이루어진 commit 값을 따로 저장한다.
  3. actions/cache@v2를 이용해 commit 값을 비교한다.
  4. 변경사항이 있다면 cache-hitfalse, 변경사항이 없다면 true다.
  5. if: steps.get.outputs.cancel != 'true'를 이용해 그 다음 step들을 pass할지 결정한다.

 

마무리

test-every-push.yml, slack-notify.yml 그리고 눈으로만 보신 deploy.yml, auto-weekday-deploy.yml은 실제로 카카오웹툰에서 사용하고 있는 코드들입니다. 위에 소개하진 않았지만 직접 만든 Build Action, Deploy Action을 배포하는 workflow를 실행시킬 때 재사용하고 있구요.

 

현재 카카오웹툰은 브랜치 이름을 통해 배포하던 방식을 GitHub CLI를 사용해서 일부 다른 방법으로 바꾸었습니다. GitHub CLI를 이용하면 굳이 push를 하지 않더라도 repository에 있는 브랜치의 GitHub Actions를 실행할 수 있고, 실행하면서 필요한 정보를 넘겨줄 수도 있어요. regionenv를 넘길 수 있다는 얘기죠. 즉 브랜치를 push하지 않고 배포할 수 있다는 말입니다.

 

걱정하지 마세요! 위 배포 방식과 매우 흡사하고, 브랜치로부터 env, region을 추출하는 방식을 GitHub CLI를 이용해 명령어로 넘겨주는 방법으로만 변경했어요. 어떤 식으로 바꾸었는지는 다음에 기회가 되면 글을 써보도록 할게요.

 

GitHub Actions는 위에 설명된 작업말고도 다양한 상황에 사용될 수도 있어요. 이 글에서 모든 정보와 내용을 다 담을 수 없어 실제로 카카오웹툰에서 어떤 식으로 사용하고 있는지만 간단하게 살펴봤습니다.(물론 저희보다 훨씬 멋지고 깊게 사용하시는 분들도 계실 거라 생각합니다.)

 

GitHub을 사용하고 있다면, 이번 기회에 한 번 사용해보시는 건 어떨까요?

읽어주셔서 감사합니다.

참고 링크

 

더 많은 FE 지식을 나누고 싶다면?! 카카오엔터테인먼트 FE 기술블로그 [바로가기]