목록 보기
카카오웹툰은 GitHub Actions를 어떻게 사용하고 있을까?
기타

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

안녕하세요. 카카오웹툰 프론트엔드를 개발하고 있는 레이입니다 :) 카카오웹툰은 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="✅" else EMOTICON="⛔" fi

    # $(GITHBU_REPOSITORY), $(GITHUB_WORKFLOW), $(GITHUB_RUN_ID) ..
    # 이런 값들은 GitHub Actions에서 제공하는 환경변수 값들입니다.
    # 저는 환경변수들을 이용해서 슬랙 알림이 왔을 때 어떤 부분에서 실패했는지 
    # 바로 클릭해서 GitHub 페이지를 띄워보고싶거든요.
    MSG="( \"text\":\">$(EMOTICON) workflow (&lthttps://github.com/$(GITHUB_REPOSITORY)/actions/runs/$(GITHUB_RUN_ID)|$(GITHUB_WORKFLOW)>) in &lthttps://github.com/$(GITHUB_REPOSITORY)|$(GITHUB_REPOSITORY)>\n>&lthttps://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 정리하면 이렇습니다. push 이벤트가 발생한다. Github Actions는 해당 브랜치를 토대로 리눅스 환경에 checkout한다. Node.js 환경을 세팅한다. node_modules가 캐싱이되었는지를 검사한다. 모듈이 변경이 되었다면 npm install을 하고, 변경되지 않았으면 건너뛴다. 우리가 만들어놓은 npm run 명령어를 이용해 테스트를 각각 실행한다. 모든 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#re

댓글 0

댓글을 작성하려면 로그인이 필요합니다.

댓글을 불러오는 중...