Hakyll 과 Github Action

Posted on June 17, 2020

Hakyll 로 블로그를 돌리게 되면 컨텐츠를 등록할 때마다 다음 절차를 실행해야 합니다.

  1. 컨텐츠를 수정
  2. Hakyll 로 정적 사이트를 만들고,
  3. 사이트 소스 커밋,
  4. 생성된 사이트 커밋

매 번 이렇게 하기에는 성가신 작업입니다.

  1. 컨텐츠 수정
  2. 사이트 소스 커밋

이렇게 하면 나머지는 자동으로 했으면 좋겠습니다. 이럴 때 Github Action 을 쓰면 됩니다. 이 전에는 Travis Ci, Circle Ci 등을 이용했다고 하는데, 2019년 말에 Github 자체에도 push, pull 등의 이벤트가 발생하면 자동으로 실행되는 기능이 생겼습니다.

이렇게 실행되는 작업을 workflow 라 부릅니다. workflow 는 여러 개 액션들의 조합으로 이루어집니다.

workflow 를 실행하는 머신을 runner 라고 부르는데, 클라우드 호스트라고 생각하면 됩니다. workflow 실행은 호스트를 준비하는 것으로부터 시작합니다. 매 번 이렇게 호스트를 올리는게 비효율적인 것 같긴 한데, Github 의 runner 관리 입장에서 보면 효율적인 설계일 수도 있겠습니다.

lionhairdino 에 걸어 둔 workflow 동작 순서

  1. Ubuntu, Windows, MacOS 중 선택
  2. 현재 워크플로우가 들어있는 저장소의 마지막 커밋 하나를 runner 로 가져온다.
  3. 하스켈 설치 GHC 와 Cabal
    runner 이미지가 이미 갖고 있는 버전을 지정하면 시간을 단축 할 수 있다.
  4. 매 번 호스트를 생성하고 하스켈을 설치하면, 하스켈 패키지를 받는데 30여분의 시간이 걸린다. 매 번 다운로드하지 않게 캐싱해 놓을 수 있다. 이 전에 해 놓은 캐싱이 있으면, 현재 환경에서 쓸 수 있는 캐싱인가 확인해서 새로 다운로드하지 않는다. 만약 환경이 달라졌거나 캐싱해 놓은게 없거나 하면 workflow 말미에 캐싱하도록 예약 한다.
  5. Cabal Update
  6. hakyll 빌드
  7. hakyll 로 정적 사이트 빌드
  8. _site 폴더에 만들어진 정적 사이트를 github page 와 물려있는 저장소에 커밋
  9. 예약된 post 캐싱 작업
  10. 예약된 post checkout 작업
  11. 프로세스 끝내기 (실행하면서 생긴 프로세스들을 정리)

처음 workflow 를 실행할 때는 40분 가까이 걸렸고, 캐싱을 사용하면서부터 3분정도의 시간이 걸립니다. workflow 로그를 보면, 아직도 하스켈 설치에 1분, 캐싱에서 파일을 풀어서 설치하는데 1분정도가 걸립니다. todo: 하스켈 설치 폴더도 캐싱 작업을 걸어두면 더 빨라질 것 같다.

lionhairdino 에 걸어 둔 workflow 소스

(workflow 문법: Workflow syntax for GitHub Actions)

name: Haskell CI

on:
  push:
    branches: [ master ]

jobs:
  build-deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-haskell@v1
      with:
        ghc-version: '8.8.3'
        cabal-version: '3.2.0'

    - name: Cache
      uses: actions/cache@v1
      env:
        cache-name: cache-cabal
      with:
        path: ~/.cabal
        key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/*.cabal') }}-${{ hashFiles('**/cabal.project') }}
        restore-keys: |
          ${{ runner.os }}-build-${{ env.cache-name }}-
          ${{ runner.os }}-build-
          ${{ runner.os }}-
    - name: Install dependencies
      run: |
        cabal update
        cabal install --only-dependencies
    - name: Build
      run: cabal build
    - name: Build Site
      run: cabal exec lionhairdino build
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        publish_dir: _site
        publish_branch: master  
        External_repository: lionhairdino/lionhairdino.github.io

Hakyll 을 쓰는 블로그를 위한 Github 설정

사이트 소스와 공개된 사이트를 위해 두 개의 저장소를 만듭니다.

sitSource 저장소에 workflow 를 걸어 두는데, 작업 말미에 결과를 lionhairdino.github.io 저장소에 커밋하려면 보안을 위해 ssh 키 쌍을 등록해 둬야 합니다. (peaceiris/actions-gh-pages 액션을 보면 workflow 를 걸어 둔 저장소와 같은 저장소로 커밋할 때는 github_token 을 쓰고, 다른 저장소에 커밋할 때는 deploy_key 를 씁니다. peaceiris / actions-gh-pages)

siteSource 에 public 키를, lionhairdino.github.io 에 private 키를 등록합니다.

commit 후 push 도 번거롭다

VS Code를 이용한다면 Settings -> Extensions -> Git -> Post Commit Command를 push로 선택하면 됩니다. 아마도 다른 툴들도 비슷한 post 액션 설정이 있을겁니다.

2021.04.29 추가

캐시

Caching dependencies to speed up workflows

cache 액션은 현재 실행되는 workflow를 가진 branch에서 key 또는 restore-keys가 일치하는 캐시를 찾습니다. 현재 branch에서 일치하는 캐시가 없을 때는 부모 branchupstream branch에서 찾습니다.
key로 못찾으면 사용할 restore key 목록을 지정할 수 있습니다. 자세한 이름에서 덜 자세한 이름 순서로 지정해서 매칭을 시도합니다.

복원키 지정

restore-keys: |
  npm-foobar-${{ hashFiles('package-lock.json') }}
  npm-foobar-
  npm-

npm-foobar-XXX 복원키는 npm-foobar-XXX로 시작하는 모든 키와 매치하고,
npm-foobar- 복원키는 npm -foor-로 시작하는 모든 키,
npm- 복원키는 npm-로 시작하는 모든 키와 매치됩니다. 여려개가 매치되면 가장 최근에 생성한 캐시를 선택합니다.

캐시는 7일동안 접근이 없으면 지워집니다. 캐시 개수의 제한은 없고, 전체 캐시를 위한 용량은 5GB로 제한됩니다. 용량이 꽉차면 오래된 것부터 자동으로 지웁니다evicting.

출력 매개 변수 cache-hitkey와 정확히 일치한 캐시가 있는지 여부를 알 수 있습니다. key와 정확히 일치하는 캐시를 찾으면 true, 아니면 false

캐시 액션의 위치는 체크 아웃 바로 다음이 적당한 듯 합니다.

    - uses: actions/checkout@v2
    - name: Cache
      uses: actions/cache@v2

하킬 바이너리로 처리

캐싱을 제대로 써먹지 못할 때가 많아, install dependency 에서 30분 이상 소요되어 아예 GHC를 설치할 필요없이 Hakyll 바이너리를 올려서 사이트를 생성하게 바꾸었습니다.

name: Haskell CI

on:
  push:
    branches: [ master ]

jobs:
  build-deploy:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: decompress lionhardino
      run: gzip -d ~/work/*****/*****/hakyllBinary.gz
    - name: Build Site
      run:  ~/work/*****/*****/hakyllBinary build
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v3
      with:
        deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        publish_dir: _site
        publish_branch: master  
        External_repository: lionhairdino/lionhairdino.github.io

hakyll 바이너리를 만들면 120MB쯤 되는데, github가 100MB가 넘어가는 파일은 받지 않기 때문에 압축을 해서 올리고 실행할 때 풉니다. 이렇게 하니, GHC를 설치할 필요도, 의존 파일들을 설치할 필요도, 캐싱할 필요도 없어 액션 실행 시간이 13초로 줄었습니다. 음, 처음부터 왜 이렇게 셋팅하지 않았나 싶은데, 아마도 막연히 바이너리를 돌리는 게 까다로울 거란 생각을 했었나 봅니다.

2021.05.24 추가
UPX를 이용해 하킬 바이너리를 압축하니 용량이 120MB가 넘던 파일이 24MB로 줄어들었습니다. 굳이 압축해서 올렸다 압축을 푸는 번거로움을 없앨 수 있게 되었습니다. 위 코드에서

    - name: decompress lionhardino
      run: gzip -d ~/work/*****/*****/hakyllBinary.gz

이제 이 부분을 삭제해도 됩니다.

https://dixonary.co.uk/blog/haskell/small 하스켈로 만든 바이너리가 너무 클 때 참고할만한 블로그입니다.

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com