DevOps
Github Actions으로 배포 자동화하기
2021년 7월 2일
원문에서 보기 ↗Github Actions으로 배포 자동화하기

Github Actions이란?
Github Actions이란 Github에서 제공하는 워크플로우(workflow)를 자동화하도록 도와주는 도구이다. 테스트, 빌드, 배포 등의 다양한 작업들을 자동화하여 처리한다.
요금과 제한
public 저장소의 경우 무료로 사용 가능하며 private 저장소는 월마다 제공되는 무료 사용량 초과 시에 요금이 부과된다. 무료 계정을 기준으로 500MB의 스토리지와 월마다 2,000분의 실행시간이 제공된다.
Github Actions 시작하기
Github Actions은 Github 저장소에서 등록할 수도 있고 .github/workflows 폴더 내에 .yml 파일을 추가하여 등록할 수도 있다.
전자의 경우 Github이 해당 저장소에서 사용하는 기술에 맞는 workflow 템플릿을 추천해준다. 
tui-image-editor 저장소의 추천 템플릿
Github Actions의 구성
워크 플로우(workflows)
저장소에 추가하는 자동화된 프로세스이다. 하나 이상의 job으로 이루어져 있으며 이벤트에 의해 실행된다.
이벤트(Events)
워크 플로우를 실행하는 특정 활동이나 규칙이다. 커밋의 push, pull request가 생성되었을 때뿐만 아니라 저장소 dispatch event를 통해 Github 외부에서 발생하는 활동으로도 이벤트를 발생시킬 수도 있다.
# push나 pull request가 발생할 때 워크 플로우 실행
on: [push, pull_request]
또한 schedule에 POSIX cron 문법으로 스케쥴 이벤트를 발생시킬 수도 있다.
on:
schedule:
- cron: '*/15 * * * *'
다양한 이벤트에 대한 정보는 여기에서 확인 가능하다.
러너(runners)
Github 액션 러너 애플리케이션이 설치된 서버이다. Github에서 호스팅 하는 러너를 사용할 수도 있고 직접 호스팅 할 수도 있다. Github에서 호스팅 하는 러너는 Ubuntu Linux, Windows, macOS 환경을 기반으로 하며 워크 플로우의 각 작업은 새로운 가상 환경에서 실행된다.
작업(jobs)
워크플로우의 기본 단위라고 보면 되며 다시 더 작은 단위인 스텝(step)으로 이루어져 있다. 기본적으로 워크 플로우는 여러 작업을 병렬적으로 실행하며 순차적으로 실행하도록 설정할 수도 있다. 예를 들어 빌드와 테스트 코드의 수행인 두 작업을 순차적으로 실행할 수도 있으며 이 경우에는 빌드 작업이 실패하면 테스트 작업은 실행되지 않는다.
스텝(steps)
작업에서 커맨드를 실행하는 독립적인 단위이다. 한 작업(job)의 각 스텝들은 동일한 러너에서 실행되므로 해당 작업의 액션들은 서로 데이터를 공유한다.
액션(actions)
워크 플로우의 가장 작은 요소로 직접 만들어 사용할 수도 있고 마켓에 등록된 이미 만들어진 것을 가져와 사용할 수도 있다.
워크 플로우 관리
민감한 정보 저장
워크 플로우가 비밀번호나 인증서 같은 민감한 정보를 사용한다면 Github에 secret으로 저장하여 환경 변수로 사용 가능하다.
jobs:
example-job:
runs-on: unbuntu-latest
steps:
- name: Retrieve secret
# 환경변수로 저장하고
env:
super_secret: ${{ secrets.SUPERSECRET }}
# 저장한 환경변수를 활용한다.
run: |
example-command "$super_secret"
의존적인 작업 구성
기본적으로 작업은 병렬적으로 수행된다. 다른 작업이 완전히 끝난 후에 작업을 실행시키고 싶다면 needs 키워드를 통해 작업이 의존성을 갖도록 지정하면 된다.
jobs:
setup:
runs-on: ubuntu-latest
steps:
- run: ./setup_server.sh
build:
needs: setup
runs-on: ubuntu-latest
steps:
- run: ./build_server.sh
test:
needs: build
runs-on: ubuntu-latest
steps:
- run: ./test_server.sh
needs 키워드를 통해 build, test 작업이 각각 이전 작업인 setup, build 작업이 끝난 후에 실행되도록 설정했다.
빌드 매트릭스 활용하기
워크 플로우가 다양한 OS, 플랫폼, 언어의 여러 조합에서 테스트를 실행하려는 경우 빌드 매트릭스를 활용하면 된다. 빌드 옵션을 배열로 받는 strategy 키워드를 사용하면 된다.
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
# 다양한 버전의 Node.js를 이용하여 작업을 여러번 실행
node: [6, 8, 10]
steps:
- uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node }}
종속성 캐싱
GIthub의 러너는 각 작업에서 새로운 환경으로 실행되므로 작업들이 종속성을 재사용하는 경우 파일들을 캐싱하여 성능을 높이면 된다. 캐시를 생성하면 해당 저장소의 모든 워크 플로우에서 사용 가능하다.
jobs:
example-job:
steps:
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# `~/.npm` 디렉토리를 캐시해 성능을 높인다
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
TOAST UI 사이트에 적용할 액션 만들기
TOAST UI 사이트는 현재 위클리를 업로드할 때마다 master 브랜치에 머지 후 개발자의 로컬 환경에서 직접 배포했었다. TOAST UI 사이트의 배포는 빌드 및 gh-pages 브랜치로 push 하는 과정으로 이루어진다. 이 과정을 자동화하는 워크플로우를 만들어보자. 실제 테스트는 임시 저장소에서 진행했다.
1) 워크플로우 이름 지정
name: brandsite deploy
가장 먼저 워크플로우의 이름을 설정했다. 이렇게 설정된 이름은 액션에서 각 워크플로우의 이름으로 노출된다. 
2) 조건 설정
on:
push:
branches: [ main ]
워크플로우가 실행되는 조건을 설정했다. main 브랜치에 push 되었을 때 이벤트를 통해 이 워크플로우가 실행된다.
이런 식으로 push 되었을 때 해당 커밋의 이름이 노출되며 결과와 걸린 시간을 보여준다.
push가 아닌 pull_requests로 PR을 만들 때 이벤트가 발생하도록 지정할 수도 있으며 cron을 통해 스케줄을 정의할 수도 있다
3) 환경 설정
jobs:
build:
runs-on: macos-latest
strategy:
matrix:
node-version: [ 14.x ]
워크플로우의 작업(job)을 설정하는 부분이다. build는 이 작업의 이름이 되어 노출된다. 또한 runs-on과 strategy를 통해 워크플로우의 환경을 설정했다. macOS 환경이며 Node.js 14 버전인 환경에서 워크플로우가 실행된다.
4) 브랜치 체크아웃
steps:
- name: Checkout main branch
uses: actions/checkout@v2
이제 각 스텝 별로 액션을 수행하게 되며 마찬가지로 name 항목은 액션에서 각 스텝 항목의 이름으로 표시되는 부분이다. uses로 사용할 액션(미리 만들어진 패키지)을 지정하여 main 브랜치를 체크아웃했다.
5) Node.js 환경설정
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
미리 지정한 Node.js의 환경으로 node 버전을 지정했다. 마찬가지로 이미 만들어져 있는 액션을 사용했다.
6) NPM 캐싱
- name: npm cache
uses: actions/cache@v2
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ hashFiles('**/package-lock.json') }}
npm node_modules 캐싱을 활용하는 부분이다.
7) 패키지 설치
- name: npm install
run: |
npm install
매번 전체 node_modules를 설치하는 속도의 부담을 덜기 위해 이전 과정에서 캐싱을 이용했다.
8) 빌드 및 배포
- name: Build
run: |
rm -rf ./public
mkdir public
cp index.html ./public
- name: Checkout gh-pages branch
uses: actions/checkout@v2
with:
ref: "gh-pages"
clean: false
- name: Copy Directories
run: |
rm -rf ./build
cp -r ./public ./build
- name: Add & Commit
uses: EndBug/add-and-commit@v4.4.0
with:
add: 'build'
ref: "gh-pages"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Push commit
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
branch: "gh-pages"
force: true
실질적인 배포를 수행하는 부분으로 프로젝트의 빌드(테스트에선 단순히 파일을 옮기기만 했다)와 Github Pages를 위한 브랜치 체크아웃과 파일 이동 및 커밋과 푸시 작업을 수행한다. 배포를 위해 Github Actions을 만들었기 때문에 이런 과정이 들어갔을 뿐 테스트 혹은 그 밖의 작업 또한 실행 가능하다.
실제 테스트를 위해 임시 저장소에서 Hello World!를 표시했던 문구를 Action test로 변경한 후 master 브랜치에 push했다.
액션이 실행되고 성공적으로 끝난 뒤 Hello World!가 Action test로 바뀐 것을 확인할 수 있었다.
맺음말
TOAST UI 어플리케이션뿐만 아니라 대다수의 오픈소스에서 Github Actions을 사용하고 있다. 핵심적인 요소들만 이해하면 쉽고 간단하게 Github Actions을 사용할 수 있다. 이 글의 예제에서는 단순히 빌드와 배포의 자동화만을 보여주었지만 PR이 올라왔을 때의 테스트 또는 타입 검사 등의 자동화를 넘어서 ChatOps 구현 같이 다양하게 활용할 수도 있다. 당신의 Github 저장소에도 Github Actions을 도입하여 매번 반복되는 작업을 자동화하는데 도움이 되었으면 좋겠다!