Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

GitHub Actions에서 원하는 워크플로우 만들기

이 글 이후 GitHub Actions가 많이 바뀌었으므로 새로 업데이트된 내용인 GitHub Actions 워크플로우 사용하기를 참고하세요.

지난달에 GitHub Actions을 살펴봤는데 이때는 GitHub에 내장된 Action으로 어떻게 사용하는지만 테스트해봤다. 실제로 사용할 때는 내장된 기능 외에도 필요한 기능을 만들어서 써야 하므로 이번에는 어떻게 직접 사용할 수 있는지 확인해 보기로 했다. Tools for building GitHub Actions 저장소가 있어서 봤는데 문서가 좀 빈약해서 정확히 어떤 도구인지 이해를 할 수 없어서 그냥 워크플로우를 만들어 보기로 했다.

.github/main.workflow

GitHub에서 제공하는 GUI에서는 커스텀 액션을 추가하는 기능이 없으므로 직접 파일을 수정해야 한다. 익숙해 지면 로컬에서 작업해서 커밋을 푸시해도 되지만 처음에는 GitHub에서 .github/main.workflow을 바로 수정하고 실행해 보는 것이 쉬울 것 같다. 이 파일을 작성하는 방법을 파악하기 위해서 Workflow configuration options를 참고했다.

Action 워크플로우는 HCL을 사용하는데 여러 workflow, actions 블록을 조합해서 구성하는 방식이다.

워크플로우에는 현재 다음과 같은 제약사항이 있다고 한다.

  • 각 워크플로우는 큐 대기시간과 실행 시간을 포함해서 58분 동안 실행될 수 있다.
  • 각 워크플로우는 빌드와 다른 메타 과정을 포함해서 100개의 액션을 실행 후 있다.
  • 저장소당 2개의 워크플로우가 동시에 실행될 수 있다.
  • action 내에서 실행된 태스크는 추가 action을 실행할 수 없다. 예를 들면 push, deployment나 GITHUB_TOKEN이 제공되어서 생성된 태스크는 다른 액션 트리거로 실행되는 워크플로우를 실행할 수 없다.
  • Docker 컨테이너 로그는 최대 64KB로 제한된다.

workflow 블록

workflow 블록은 다음과 같은 형식이다.

workflow "IDENTIFIER" {
  on = "EVENT"
  resolves = ["ACTION1", "ACTION2"]
}
  • on: 워크플로우를 실행할 GitHub의 이벤트를 나타낸다. 이벤트 타입에 따라 사용되는 워크플로우 파일의 버전이 다른데 예를 들어 push 이벤트에서는 푸시된 최신 커밋의 워크플로우 파일이 사용되지만 issue_comment 이벤트 같은 경우는 기본 브랜치의 워크플로우 파일을 사용하게 된다.
  • resolves: 실행할 action을 지정한다. 여러 액션을 지정하면 병렬로 실행된다.

생각보다 간단해서 약간 사용하면 흐름을 이해하기는 어렵지 않을 것 같다.

action 블록

action "IDENTIFIER" {
  needs = "ACTION1"
  uses = "docker://image2"
}
  • needs: 이 액션이 실행되기 전에 반드시 성공적으로 완료되어야 하는 액션을 지정한다.
  • uses: 액션을 실행할 Docker 이미지다. Docker 이미지는 Docker 레지스트리에 배포된 이미지 외에도 GitHub 저장소에 있는 것도(아마도 Docker 빌드 전?) 사용 가능하다. {user}/{repo}@{ref}, {user}/{repo}/{path}@{ref}같은 형식(예: actions/aws/ec2@v2.0.1)으로 저장소에서 가져올 수도 있는데 {ref}는 브랜치, SHA, 태그 등이다. 현재 워크플로우가 같은 저장소에 있는 액션은 ./path/to/dir로 지정할 수 있고 배포된 Docker 이미지는 docker://{image}:{tag}docker://{host}/{image}:{tag}로 지정할 수 있다.
  • runs: Docker 이미지에서 실행할 명령어다. 생략하면 DockerfileENTRYPOINT가 실행된다.
  • args: 액션에 전달할 인자로 args = "container:release --app web"args = ["container:release", "--app", "web"]처럼 지정할 수 있다.
  • env: 환경변수를 지정한다.
  • secrets: 시크릿 변수의 이름을 지정해서 액션이 환경변수로 접근할 수 있게 한다. 시크릿의 값은 저장소의 설정이나 비주얼 워크플로우 에디터에서 지정해야 한다. GITHUB_TOKEN은 기본으로 제공되고 이 토큰의 권한은 문서를 참고하면 된다.

액션이 실행될 때는 GitHub 저장소를 클론하고 클론한 Git SHA를 GITHUB_SHA로 레퍼런스를 GITHUB_REF 환경변수로 지정된다. 이 값은 이벤트 종류에 따라 다른데 이 부분도 문서에 잘 정리되어 있다. GITHUB_WORKFLOW, GITHUB_WORKSPACE 등 사용 가능한 환경변수는 문서에 잘 나와 있다.

아직 GitHub Actions가 클로즈 베타 상태이지만 퍼블릭으로 열린 다음 분위기도 궁금했는데 Docker로 원하는 기능을 만들 수 있고 Docker를 배포하지 않고도 저장소에 Dockerfile을 추가해서 Action을 공유할 수 있으므로 필요한 기능은 금세 채워질 것 같다. 만드는 방법이 어렵지 않을 것 같아서 금세 액션을 공유하는 생태계가 만들어질 것 같다.

커스텀 액션 만들기

어느 정도 구조를 만들었으니 필요한 액션을 직접 만들어 보자. 예제로 Single Page Application으로 만든 애플리케이션의 프로덕션 빌드 파일의 사이즈를 검사해 주는 액션을 생각해 봤다. Create React App으로 생성한 애플리케이션을 예제로 만들고 Pull Request가 올라오면 프로덕션 빌드(npm run build)한 뒤 build/static 아래 만들어진 JavaScript 파일의 용량을 Pull Request의 댓글로 남겨주는 액션을 만들어 보자. 제대로 만들려면 이전 용량과 비교해야겠지만 이를 저장할 곳은 애매하고 여기서는 어떻게 액션을 만드는지 테스트하는 목적이므로 이 부분은 무시했다.

GitHub Action 생성

├── .github
└── actions
    └── bundle-size
        ├── Dockerfile
        └── entrypoint.sh

bundle-size라는 폴더를 만들고 문서에서 권장하는 대로 Dockerfileentrypoint.sh를 만들었다.

FROM node:10.14

LABEL "com.github.actions.name"="Bundle Size"
LABEL "com.github.actions.description"="Check production bundle size"
LABEL "com.github.actions.icon"="archive"
LABEL "com.github.actions.color"="orange"

LABEL "repository"="https://github.com/outsideris/actions-test/actions/bundle-size"
LABEL "homepage"="http://github.com/outsideris/actions-test"
LABEL "maintainer"="Outsider <outsider@example.com>"

RUN apt-get update && \
    apt-get install -y jq

ADD entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]

LABEL을 이용해서 액션의 정보를 입력하고 아이콘은 Feather 에서 로그로 색을 지정한다. 여기서는 npm을 사용하려고 Node.js Docker 이미지를 사용했다. WORKDIR은 따로 지정할 필요 없이 /github/workspace로 알아서 세팅해 주고 이곳에 저장소 소스코드가 들어있게 된다. 그리고 나중에 jq가 필요해서 Docker에 jq를 미리 설치했다.

#!/bin/sh

ls -lha build/static/js/*.js  | awk '{print $5,$9}'

entrypoint.sh에서는 build/static/js 아래 있는 파일의 사이즈와 용량을 출력하는 쉘 스크립트를 실행하도록 했다. 문서에서는 sh -c "npm $*"같은 식으로 사용하는 것을 권장하고 있다. 이렇게 했을 경우 액션 블록의 args로 전달한 값이 $* 위치에 들어가게 되므로 액션을 다양하게 사용할 수 있지만 여기서는 필요치 않은 것 같아서 그냥 작성했다. 그리고 entrypoint.sh의 실행 권한을 반드시 주어야 한다.(chmod +x entrypoint.sh) Docker 이미지를 레지스트리에 등록하지 않고 Dockerfile만 만들어서 바로 액션으로 사용할 수 있는 부분을 원하는 기능을 추가할 때 큰 단계 하나가 줄어들어서 꽤 편하다.

워크플로우 생성

.github/main.workflow를 작성해 보자.

workflow "New workflow" {
  on = "pull_request"
  resolves = ["Bundle Size"]
}

action "Install" {
  uses = "actions/npm@master"
  args = "install"
}

action "Build" {
  needs = ["Install"]
  uses = "actions/npm@master"
  args = "run build"
}

action "Bundle Size" {
  needs = ["Build"]
  uses = "./actions/bundle-size"
  secrets = ["GITHUB_TOKEN"]
}

이 워크플로우에는 3개의 액션을 만들었다. 워크플로우는 Bundle Size 액션을 지정했지만 여기서 Build 액션을 선행 액션으로 지정하고 다시 Install 액션을 지정했으므로 순서는 Install -> Build -> Bundle Size 순으로 실행된다. 앞의 두 액션은 빌트인으로도 있는 npm 액션을 사용했는데 따로 하나의 액션에서 npm 의존성 설치와 빌드를 실행하지 않고 여러 액션으로 조합했다.(이렇게 하는 게 액션의 사용법에 더 맞는 것 같아서...) 각각 다른 Container이지만 앞에서 설치한 파일이 이후 액션에도 자동으로 유지된다. 앞에서 얘기한 대로 빌드 결과를 댓글로 올릴 계획이므로 GITHUB_TOKEN를 액션에서 사용할 수 있도록 지정했다.

이렇게 만든 워크플로우를 저장소에 올리면 GitHub에는 아래와 같이 나온다.

생성된 워크플로우

Pull Request를 올리면 일반적으로 CI 등을 연결한 것처럼 Action이 실행되는 것을 볼 수 있다.

Pull Request 실행중인 액션

Checks 탭에서 액션 실행의 상세 내용을 볼 수 있다.

실행중인 GitHub Action의 상세 화면

액션을 클릭하면 Docker 로그를 볼 수 있는데 다음과 같이 빌드된 js 파일의 사이즈가 출력된 것을 볼 수 있다.

액션의 Docker 로그

문서를 보면 액션 실행에 1 vCPU, 최대 3.75GB 메모리, 100 GB 디스크가 할당되므로 리소스는 충분해 보인다.

빌드 결과 댓글 남기기

액션의 동작 여부를 확인했으니 이제 React앱을 빌드한 JavaScript 파일 크기를 Pull Reqeust의 댓글로 남겨보자.(나중에 활용할 때 필요한 기능의 시나리오를 만든 것이지 이 사이즈 기록의 유용성은 큰 의미가 없다.) entrypoint.sh를 다음과 같이 변경했다.

#!/bin/sh

DATA=$(ls -lha build/static/js/*.js  | awk '{print $5,$9}')
echo $DATA

URL=$(cat /github/workflow/event.json | jq -r .pull_request._links.comments.href)

COMMENT="### Bundle Size:
$DATA
"

PAYLOAD=$(echo '{}' | jq --arg body "$COMMENT" '.body = $body')

curl -d "$PAYLOAD" \
  -H "Accept: application/vnd.github.v3+json" \
  -H "Authorization: token $GITHUB_TOKEN" \
  -H "Content-Type: application/json" \
  -X POST "$URL"

각 JS 파일의 용량 출력을 나중에 사용하기 위해서 변수(DATA)에 담았다. 그리고 현재 이 워크플로우는 Pull Reqest 이벤트로 실행되도록 구성되었다. GitHub에서 webhook으로 이벤트를 받으면 이벤트의 내용이 payload로 담겨오게 되는데 액션에서는 이 payload가 /github/workflow/event.json 파일 안에 저장되어 있다. 즉, PullRequestEvent의 payload가 이 안에 있으므로 필요한 정보는 이 파일을 파싱해서 사용하면 된다. 여기서는 댓글을 생성할 API의 URL을 찾기 위해 pull_request._links.comments.href의 값을 가져왔다.

이후에는 댓글에 남길 Markdown 문자열을 만든 후 curl로 POST 요청을 보냈다. 이때 시크릿으로 등록한 GITHUB_TOKEN로 GitHub API 인증을 했다.

풀리퀘스트에 댓글로 남겨진 JS 용량

Pull Request에 빌드한 파일의 용량이 댓글로 올라온 것을 볼 수 있다.


지난 글에서는 GitHub에서 제공하는 액션만 연결해 봤었는데 직접 워크플로우를 만들어보니 어떤 방식으로 동작하고 어떻게 사용해야 할지 감이 왔다. 처음 봤을 때 예상했던 대로 Travis CICircle CI의 영역을 대부분 대체할 것 같았는데 실제로 사용해보니 정말 대체해버릴 것 같았다. CI는 이미 익숙한 도구이기는 하지만 Docker로 커스텀 동작을 만들어서 액션을 추가하는 데 그리 어렵지 않아서 상당 부분은 여기서 해결하게 될 것 같다.

2018/12/20 02:41 2018/12/20 02:41