Github actions 톺아보기

Created
2023/05/12 01:04
안녕하세요. 볼드나인 프론트엔드 개발자 에이든입니다.
볼드나인 개발팀은 코드 버전 관리를 위해 Github를 사용하고 있는데요. Github는 계속해서 유용한 기능들을 개발하며 발전하고 있습니다. 오늘은 그중 Github actions 에 대해 알아보려고 해요.
볼드나인팀은 협업툴을 최소화시키기 위해 CI/CD 역시 Github의 actions를 사용하고 있습니다.
출시한지 시간이 좀 지났지만 기초 개념부터 정리해 보겠습니다.

Github actions 란?

Gihub actions는 Github에서 제공하는 빌드, 테스트, 배포 과정을 자동화 파이프라인을 만드는 CI/CD 플랫폼입니다. Git 호스팅 서비스 중 가장 높은 점유율을 가지고 있는 Github에서 출시한 만큼 이벤트 주도적으로 파이프라인을 작성할 수 있습니다. 이벤트 주도적이란 말은 Github의 이벤트(Pull request, Issue create)들, 각 이벤트에 트리거되어 다양한 이벤트별로 파이프라인을 작성할 수 있다는 말인데요. 사용 방법도 비교적 쉬워 TravisCI, CircleCI, 와 같은 다른 CI/CD 플랫폼들을 긴장시키고 있습니다.

Actions의 주요 컴포넌트들

Actions는 여러 요소들로 구성되어 있습니다. Actions을 이루는 요소들을 소개하고 요소들의 역할을 간략하게 설명하겠습니다.

 Workflows

Workflow는 action의 root입니다. 하나 이상의 작업을 특정 이벤트에 자동으로 트리거되어 실행되는 가장 큰 프로세스입니다. workflow는 YAML파일로 작성되며, 레포지토리별로 작성할 수 있습니다.

 Events

event는 레포지토리에서 일어나는 활동들을 의미합니다. 예를 들면, 이슈가 생성된다거나 Push가 되거나 Pull request가 등록된다거나 이러한 활동들을 의미합니다. 우리는 이런 이벤트가 일어날 때 슬랙에 알림 메시지를 보내는 Workflow를 작성할 수 있습니다.

 Jobs

Job은 step은 집합입니다. 다시 말해, 여러 task의 묶음이라고 할 수 있습니다. job의 가장 주요한 특징은 하나의 job은 하나의 runner(가상머신)에서 실행된다는 점입니다. 따라서 Workflow를 작성할 때 순서가 필요 없는 작업들은 Job으로 나누어 workflow 실행 시간을 단축시킬 수 있습니다.
Job은 기본적으로 병력적으로 실행되지만 필요에 따라 실행 순서를 제어할 수 있습니다.

 Steps

Step은 Job을 이루는 구성 요소로 실질적으로 workflow에서 하고자 하는 작업을 정의합니다. runner에서 커멘드나 스크립트를 실행하거나 또 다른 action을 실행시킬 때도 steps에 작성합니다.

 Runners

Runner는 하나의 job이 실행되는 가상 인스턴스입니다. Github에서 제공하는 runner를 사용할 수도 있고 직접 호스팅하는 runner를 사용할 수도 있습니다. Github에서 제공하는 runner는 Ubuntu Linux, Microsoft Windows, macOS 3가지입니다. 자세한 사양은 링크를 참고해 주세요.

 Actions

action은 복잡하고 재사용될 가능성이 높은 작업들을 미리 작성하여 재사용 가능하도록 만들어 놓은 어플리케이션입니다. action을 이용하면 중복되는 코드의 양을 줄일 수 있습니다. 일반적인 프로그래밍에서의 함수와 비슷하다고 생각하시면 됩니다.

직접 사용해보기

 Workflow 생성하기

각 Workflow는 YAML로 작성되며, 작성된 YAML파일은 레포지토리 안에 .github/workflows 폴더에 저장되어야 한다는 규칙을 가지고 있습니다.
name: learn-github-actions #1 on: [push] #2 # on: # push: jobs: #3 check-bats-version: #4 runs-on: ubuntu-latest #5 steps: #6 - uses: actions/checkout@v2 #7 - uses: actions/setup-node@v2 #8 with: #9 node-version: '14' #10 - run: npm install -g bats #11 - run: bats -v #12
YAML
복사
레포지토리 root에 .github/workflows 폴더를 만들어 주세요. 그리고 위 코드와 같이 작성 후 commit 후 push 해주세요. 그리고 github repository에서 Actions 탭에 들어가 보면 learn-github-actions workflow가 생성된 것을 보실 수 있을 겁니다.
그럼 이제 코드를 한 줄씩 살펴보죠. 이 workflow는 push가 될 때마다 ubuntu OS runner에서 node를 14버전으로 세팅하고 bats 모듈을 설치 후 bats의 버전을 확인하는 workflow입니다.
1.
name: learn-github-actions - workflow의 이름을 작성합니다. 여기에 작성된 이름은 레포지토리 Actions 탭에 리스트 중 항목으로 표시되게 됩니다.
2.
on: [push]: - 트리거 되어야 할 이벤트를 지정합니다. on 키워드에는 여러 이벤트를 지정할 수 있습니다.
3.
jobs: - job 정의를 시작합니다.
4.
check-bats-version: - 첫 번째 job의 이름입니다. 특별한 키워드 없이 바로 이름을 작성하면 됩니다.
5.
runs-on: - 첫 번째 job에서 사용할 runner의 환경을 지정합니다. 여기서는 ubuntu-lastest 버전을 선택했네요. label은 아래 이미지와 같은 원하는 환경에 맞게 작성하면 됩니다.
6 - 12. steps: - 본격적으로 job의 task를 정의합니다.
7.
uses: actions/checkout - 현재 repository의 default branch로 이동합니다. uses 키워드는 커스텀 액션을 사용할 때 사용되는 키워드입니다.
8.
use: actions/setup-node - nodeJS 버전을 세팅합니다.
9 -10. with: with는 action에 전달할 input이 있을 때 사용하는 키워드입니다. 다음 라인부터 action마다 필요한 input name대로 작성하게 됩니다.
 실행 결과
이하 중복되는 설명은 생략하겠습니다.

 수동 트리거 만들기

Github actions의 workflow는 대부분 특정 이벤트에 자동으로 트리거되지만 수동으로 workflow으로 실행할 수 있는 이벤트도 제공합니다.
name: Manually triggered workflow on: workflow_dispatch: #1 inputs: #2 name: #3 description: "Person to greet" #4 required: true #5 default: "Mona the Octocat" #6 home: description: "location" required: false default: "The Octoverse" jobs: say_hello: name: say_hello_name runs-on: ubuntu-latest steps: - run: | #7 echo "Hello ${{ github.event.inputs.name }}!" echo "- in ${{ github.event.inputs.home }}!"
YAML
복사
1.
workflow_dispatch 는 manually하게 workflow 트리거하는 이벤트입니다.
2.
workflow_dispatch는 workflow를 실행할 때 필요한 인자를 전달할 수 있도록 기능을 제공합니다. inputs: workflow에서 필요한 input들을 정의합니다.
3.
name은 input의 이름입니다.(키워드로 혼동하지 말아주세요) 두 번째 정의한 home 도 input의 이름입니다.
4.
description 키워드는 input의 설명을 작성할 때 사용합니다. 작성된 description은 workflow를 실행할 때 사용자에게 표시됩니다.
5.
required 키워드는 input의 필수 여부를 설정합니다.
6.
default 키워드는 기본적으로 초기화될 값을 설정합니다.
 실행 결과

 Scheduled workflow 만들기

Github actions는 Linux에서 사용되는 crontab처럼 스케줄링된 이벤트도 제공합니다. schedule 이벤트를 사용할 때 몇 가지 규칙이 있습니다.
1.
시간은 UTC 기준으로 합니다.
2.
스케줄 시간을 설정할 때 문법은 POSTIX cron 문법을 사용합니다.
3.
스케줄링의 주기는 최소 5분이 되어야 합니다.
name: Schedule CI on: schedule: - cron: "*/5 * * * *" #1 jobs: echo: runs-on: ubuntu-latest steps: # Runs a single command using the runners shell - name: Run a one-line script run: echo Hello, world!
YAML
복사
1.
cron 키워드를 시간을 설정합니다. 매 5분마다 한 번씩 실행되도록 설정하였습니다.
그럼 아래와 같이 실행되는 것을 볼 수 있습니다.
 실행 결과
그런데 실행 시간이 뭔가 좀 이상합니다. 분명 매 5분마다 실행되도록 설정했는데 시간이 불규칙하고 시간 간격이 5분이 넘습니다!
schedule 이벤트를 사용할 때 주의할 점이 있는데 schedule 이벤트가 안전하지 않다는 겁니다. 워크플로우 실행이 많은 시간에는 workflow가 지연되거나 대기열에서 삭제될 수 있다고 합니다. (아직은 사용하기에 위험하겠네요ㅠㅠ) schedule에 대한 자세한 내용이 보고 싶으시다면 링크를 참고해 주세요.

 조건별로 작업 실행하기

특정 Job을 Github contextexpression 을 사용하여 조건에 충족할 경우만 실행되도록 할 수 있습니다.
name: Workflow with if keyword on: workflow_dispatch: jobs: job1: runs-on: ubuntu-latest if: ${{ true }} #1 steps: - run: | echo running job1 job2: runs-on: ubuntu-latest if: ${{ false }} #2 steps: - run: | echo running job2
YAML
복사
1.
표현식을 사용하는데 true 값을 넣었습니다. 그럼 해당 조건은 참으로 평가될 것입니다. 따라서 job1은 실행됩니다.
2.
반대로 false는 거짓으로 평가되겠죠. 따라서 job2는 실행되지 않습니다.
name: Workflow with if keyword on: workflow_dispatch: jobs: job1: runs-on: ubuntu-latest steps: - name: My first step uses: octo-org/action-name@main - name: My backup step if: ${{ failure() }} 1# uses: actions/heroku@1.0.0
YAML
복사
예제 하나를 더 보겠습니다. if 키워드는 steps에서도 사용이 가능합니다. 그리고 failure 함수와 같은 이전 step의 실행 상태를 체크할 수 있는 함수를 제공합니다.
1.
My first step 실행이 실패하면 failure() 함수가 false를 반환하여 My backup step 실행될 것이고 그렇지 않다면 My backup step은 실행되지 않을 겁니다.
Github actions에서는 이 밖에도 fomatter, filter 등등 다양한 기능의 함수를 제공하며, 표현식에서 사용할 수 있습니다. 또한 표현식에서는 조건 연산자, 산술 연산자, 리터럴 등 다양한 방법으로 표현식을 작성할 수 있는데요. 좀 더 자세히 알아보고 싶으시다면 링크를 참고해 주세요.

환경 변수와 시크릿 사용하기

Workflow, job, step 각 context에서 사용할 환경 변수를 선언하고 사용할 수 있습니다.
name: Greeting on variable day on: workflow_dispatch env: DAY_OF_WEEK: Monday #1 jobs: greeting_job: runs-on: ubuntu-latest env: Greeting: Hello #2 steps: - name: "Say Hello Mona it's Monday" run: echo "$Greeting $First_Name. Today is $DAY_OF_WEEK!" #4 env: First_Name: Mona #3
YAML
복사
1.
workflow 레벨에 DAY_OF_WEEK 라는 환경 변수를 선언합니다. 값은 Monday로 할당합니다.
2.
job 레벨에 Greeting이라는 환경 변수를 선언하고 Hello를 값으로 할당합니다.
3.
steps 레벨에 First_Name이라는 환경 변수를 선언하고 Mona를 값으로 할당합니다.
4.
환경 변수를 가져다가 인사하는 문장을 출력합니다.
 실행 결과
위 이미지와 같이 잘 출력된 것을 볼 수 있습니다.
위 예제처럼 workflow 내에서 환경 변수를 선언할 수도 있지만 repository, oganiztion별로 환경 변수를 선언할 수 있습니다.
Repository > Settings > Secrets and variables 에서 repository 별 환경 변수를 만들어 놓을 수 있습니다. workflow에서 사용 방법은 똑같습니다.
그리고 사이드 메뉴바를 자세히 보면 Environments 라는 메뉴가 있습니다. 이 메뉴는 사용 목적에 따른 환경을 나누어 환경 변수와 시크릿을 관리할 수 있는 기능을 제공합니다. 예를 들면, dev, staging, production 운영 환경별로 환경 변수와 시크릿을 관리하고 싶다 하면 해당 기능을 사용하시면 됩니다.
위 이미지를 보면 Protection rules 을 설정할 수 있습니다. 특정 브랜치만 접근할 수 있도록 하거나 환경에 엑세스하는 workflow를 실행할 경우, 승인을 받게 한다거나 rule을 만들어 환경을 관리할 수 있도록 기능을 제공합니다.
시크릿을 만드는 방법은 환경 변수와 크게 다르지 않습니다. Secrets and variables 메뉴에서 만들어주시면 되는데요. 여기서 주의할 점은 수정할 때는 이전에 입력한 값을 확인할 수 없으며, 새로 값을 입력해야 한다는 점입니다. 값을 다시 확인할 수 없기 때문에 입력할 때 정확한 값을 입력하는 것이 중요합니다.
그럼 workflow에서 한 번 사용해 보겠습니다.
name: Secrets Test on: workflow_dispatch: jobs: action: runs-on: ubuntu-latest steps: - name: "Print TOKEN" run: echo ${{ secrets.TOKEN }}
YAML
복사
secrets 컨텍스트에서 secret 이름을 통해 접근할 수 있습니다. 간단하고 secret을 출력하는 코드인데요. 결과가 어떻게 나오지는 보겠습니다.
 실행 결과
출력하면 secret은 마스킹 처리되어 볼 수 없는 것을 알 수 있습니다. 인자로 전달하거나 할 때는 정확한 값이 전달되지만 이런 식으로 노출될 경우에는 마스킹 처리되어 보안에 좋습니다.

Custom Action 만들기

uses 키워드를 이용해서 어려가지 task를 묶어 재사용할 수 있는 action 을 사용할 수 있는데요.
Github에서 official로 제공하는 actions 도 있지만 개발자가 직접 커스텀 해서 action을 만들 수 있습니다. Custom action은 크게 3가지로 나눌 수 있습니다.
1.
Docker container actions - 작성된 코드를 컨테이너 환경에서 띄워 실행하는 action
2.
Javascript actions - Javascript로 작성된 코드를 실행하는 action
3.
Composite Actions - workflow step들을 조합하여 만든 action
우리는 오늘 Javascript action을 만들어 보겠습니다.
custom action을 만들기 위해서는 먼저 action.yml 이 필요해요. action에 대한 정보를 입력하는 파일이라고 보시면 됩니다.
name: 'Hello World' #1 description: 'Greet someone and record the time' #2 inputs: #3 who-to-greet: # id of input description: 'Who to greet' required: true default: 'World' outputs: #4 time: # id of output description: 'The time we greeted you' runs: #5 using: 'node16' main: 'index.js'
YAML
복사
1.
action의 이름을 작성합니다.
2.
action에 대한 간략한 설명을 작성합니다.
3.
action에서 받을 input을 정의합니다. inputs에 대한 문법은 이전에 workflow에서 사용하던 것과 동일합니다.
4.
action 실행 후 넘겨줄 결과 값들을 정의합니다.
5.
runs는 사용할 node 버전과 실행할 파일 이름을 작성합니다.
두 번째로는 github에서 제공하는 action 패키지를 install 하고 실행시킬 코드를 작성합니다.
npm install @actions/core npm install @actions/github
Bash
복사
const core = require('@actions/core'); const github = require('@actions/github'); try { // `who-to-greet` input defined in action metadata file const nameToGreet = core.getInput('who-to-greet'); console.log(`Hello ${nameToGreet}!`); const time = (new Date()).toTimeString(); core.setOutput("time", time); // Get the JSON webhook payload for the event that triggered the workflow const payload = JSON.stringify(github.context.payload, undefined, 2) console.log(`The event payload: ${payload}`); } catch (error) { core.setFailed(error.message); }
JavaScript
복사
who-to-greet input을 받고 인사하는 문장과 payload를 출력합니다. 그리고 output으로 현재 실행되고 있는 시간을 time이라는 이름으로 넘겨줍니다. 그리고 만약 실행 도중 에러가 발생한다면 fail 상태와 함께 error 메시지를 넘겨줍니다.
모든 준비는 끝났습니다. 이제 workflow에서 사용해 볼까요?
name: Custom action on: workflow_dispatch: jobs: hello_world_job: runs-on: ubuntu-latest name: A job to say hello steps: # To use this repository's private action, # you must check out the repository - name: Checkout uses: actions/checkout@v3 - name: Use Node.js #1 uses: actions/setup-node@v3 with: node-version: 16 - name: Cache node modules #2 uses: actions/cache@v3 id: cache with: path: node_modules key: npm-packages-${{ hashFiles('**/package-lock.json') }} #3 - name: Install Dependencies if: steps.cache.outputs.cache-hit != 'true' #4 run: npm ci - name: Hello world action step uses: ./ # Uses an action in the root directory #5 id: hello #6 with: who-to-greet: 'Mona the Octocat' #7 # Use the output from the `hello` step - name: Get the output time run: echo "The time was ${{ steps.hello.outputs.time }}" #8
YAML
복사
1.
action이 node 16버전에서 실행될 수 있도록 set up합니다.
2.
action 실행 파일인 index.js 에서 @actions/core, @actions/github를 사용하는데 설치하는 시간을 줄이고자 캐싱합니다.
3.
hashFiles은 제공되는 함수로 인자로 넘겨받은 패턴의 파일셋을 해싱하여 해시를 반환합니다. 그렇게 만들어진 키를 기준으로 캐싱됩니다.
4.
hit는 workflow가 실행될 때 캐시 키가 이전과 동일하여 캐시에 접근할 수 있는지 여부를 반환합니다. 키가 변경되어 hit되지 않았다면 packge-lock 파일이 변경되었다는 것을 의미하므로 패키지를 새로 설치합니다.
5.
우리는 external(public) action을 사용하는 것이 아니라 내부적으로 만들어진 private action을 사용합니다. 따라서 상대 경로로 action 파일 경로를 작성합니다.
6.
다음 step에서 outputs을 접근하기 위해 현 step에 id를 부여합니다.
7.
우리가 만든 custom action에 전달할 input을 작성합니다.
8.
이전 step의 output을 가져와 출력합니다.
 실행 결과

마치며

Gihub Action은 사용법도 간단하지만 막강한 오픈소스 플랫폼 특성상 사용자 참여에 의한 유용한 action들이 계속 나오고 있고 Pull request, issue, branch 등 Github event, cycle에 맞는 workflow를 작성할 수 있어 지금 Github를 사용하고 계시다면 적극 추천드리고 싶습니다!

Reference