아래 글을 읽은 후에 본 글을 읽기를 추천합니다.


이전 글에서도 말했지만, 기존의 블로그를 블로거(Blogger)로 이전하려고 했던 두 번째 이유가 번거로운 글쓰기였다. 지킬(Jekyll)의 경우에 블로그를 빌드하기 위해서는 루비(Ruby)와 지킬이 설치된 윈도(Windows)나 리눅스(Linux), 맥(Mac)이 필요하다. 상상해보자. 아름답고 한적한 카페에서 커피를 홀짝이는 나의 모습을. 블로그에 글을 쓰려고 노트북을 꺼냈지만, 루비와 지킬이 깔려있지 않아 당황하는 모습을. 설상가상으로 인터넷이 되지 않아 갖고 있던 핸드폰을 꺼내 테더링(Tethering) 하는 모습을.

휴고(Hugo)도 마찬가지다. 사전에 휴고가 설치되어 있지 않으면 블로그를 빌드할 수 없다. 물론, 카페에서는 글만 쓰고 블로그는 다음에 업데이트하면 된다. 그러나 글쓰기와 블로그 출판이 분리된 환경이 글만 쓰는 데 집중할 수 있는 환경이라고 말하기는 어려울 것 같다. 워드프레스(Wordpress)미디엄(Medium)은 웹 기반이므로 언제 어디서나 글을 쓰고 출판할 수 있다. 즉, 지킬과 휴고가 워드프레스나 미디엄과 다른 점은 CMS(Content Management System)의 부재이다. 본 포스트는 휴고 기반의 블로그 운영에 있어서 CMS와 유사한 시스템을 구성하는 방법을 담았다.

깃허브 페이지(GitHub Pages) & 넷리파이(Netlify)

최근 블로그를 만들면서 이전에 몰랐던 유용한 서비스를 알게 되었다. 바로 넷리파이. 깃허브 페이지와 매우 유사한 서비스다. 지킬이나 휴고로 만든 정적 웹 사이트(Static Web Site)를 빌드하고 출판한다는 점에선 같다. 하지만, 넷리파이는 미리보기나 별도의 URL 연결 등 더 풍부한 기능을 제공한다. 넷리파이는 유료지만 웹 사이트 빌드 및 배포와 미리보기 정도는 무료로 이용이 가능한 것으로 보인다. 다만, 배포된 웹 사이트의 주소는 “[name].netlify.app"의 형태를 띠는 것에 주의하자.

깃허브 페이지와 넷리파이는 기존에 내가 느꼈던 불편함을 일부 해소해주는 좋은 솔루션이다. 깃허브 저장소에 저장된 블로그 소스코드에 새 글이나 수정된 테마를 푸시(Push)하면 자동으로 빌드되고 배포되기 때문이다. 다만, 이 방법에는 두 가지 단점이 있다.

  1. 소스코드 쪼개기 불가

    • 글과 블로그 소스코드가 함께 저장된 단일 저장소에만 적용 가능하다. 따라서 이전 글에서 제안했던 것처럼 깃허브 서브모듈(GitHub Submodule)을 이용하여 글과 웹 사이트 소스코드를 별도의 저장소에 분리할 수 없다. 또한, 깃허브 페이지와 넷리파이가 저장소에 접근할 수 있어야 하므로 해당 저장소는 공개된 상태(Public)이어야만 한다. 깃허브에서는 저장소 단위로만 비공개(Private) 설정이 가능하므로 적어도 마크다운으로 저장된 글은 비공개 하고 싶은 필자의 바람은 이뤄질 수 없겠다.
  2. 배포 컨트롤

    • 미완성인 글을 저장소에 푸시하더라도 무조건 배포된다. 물론, 미완성인 글에 드래프트(draft) 태그를 달아 블로그에 포함되지 않도록 할 수 있다. 하지만 블로그의 각종 설정이나 테마에 불완전한 수정이 가해졌을 때에 자동으로 배포된다면 난감할 것이다. 별도의 브랜치(branch)에서 작업한 뒤에 main 브랜치로 머지(merge)하면 되겠지만 꽤 번거로울 것이다.

넷리파이 CMS(Netlify CMS)

넷리파이 CMS라는 것이 있다. 이것은 서두에서도 지적했던 지킬 및 휴고의 단점(CMS의 부재)을 완벽히 해결해주는 솔루션으로 보인다. 넷리파이 CMS 자체는 무료이다. 다만, 넷리파이에 깃허브 저장소를 반드시 연결해야 쓸 수 있는 것인지는 확인하지 못했다. 굳이 글을 공개해도 상관이 없다면 이 서비스를 이용해 보는 것을 추천한다.(훨씬 더 나을 것 같다)

유사 CMS 사용 시나리오

위와 같은 이유로 CMS라 부르기엔 많이 부족하지만 나름 쓸만한 방법(이후 유사 CMS라고 부르겠다)을 찾았고 그 내용을 공유한다. 본 글은 이전 글에서 제안한 소스코드 쪼개기를 전제한다. 즉, 블로그 소스코드에서 글, 테마, 웹 사이트를 별도의 저장소로 분리한 뒤, 글 저장소는 비공개 상태로 전환한다. 최종적으로 사용자가 글을 쓰고 출판하는 과정을 묘사하면 다음과 같다.

  1. 글쓰기 깃허브 자체에서 지원하는 기본 에디터로 글을 쓸 수 있다. 불편하다면 prose.io라는 대체 제품을 사용해 볼 수 있겠다. 하지만 깃허브는 기본 에디터 말고도 VSCode라는 아주 유명한 에디터를 웹상에서 지원하고 있다! 데스크톱 버전과 비교하여 전혀 부족하지 않은 기능을 제공한다.
Opening VSCode on the WebWriting with VSCode on the Web
  1. 깃허브 토론(Discussion) 탭에 짧은 글 남김 글 저장소에 글을 하나 푸시했지만 깃허브 페이지는 이 사실을 모른다. 왜냐면 글과 소스코드, 테마, 웹 사이트 모두 별도의 저장소에 있기 때문이다. 우리의 유사 CMS 시스템은 깃허브 액션(Github Actions)을 이용하여 깃허브 토론 탭에 글이 등록된 순간에 모든 저장소를 아울러 블로그를 빌드할 것이다. 그리고 깃허브 페이지로 하여금 새로운 블로그를 출판하게 할 것이다.
Opening GitHub DiscussionsLeave a short note on GitHub DiscussionsCheck out newly published blog

위 시나리오에서 사용자는 웹 에디터로 글을 쓰고 토론 탭에 짤막한 글을 남길 뿐이다. 짤막한 글의 내용은 아무래도 상관없다. 단지, 글을 남겼다는 행위 자체가 트리거로서 유사 CMS를 동작시킨다. 이 과정에서 사용자는 깃(Git) 문법을 몰라도 되며, 심지어 글 저장소가 비공개 상태여도 블로그는 빌드되고 출판된다. 어떤가? 워드프레스같은 CMS까지는 아니지만, 꽤 괜찮은 수준의 편리함을 느낄 수 있을 것 같지 않은가? 동의한다면 본 글을 끝까지 읽어보길 추천한다.

깃허브 액션을 이용한 본격 유사 CMS 구축 방법

앞서 소개한 시나리오의 2번 과정에 대하여 설명할 것이다. 이전 글에서 소스코드 쪼개기를 위하여 깃허브 서브모듈을 주로 활용했다면 이번 글에서는 깃허브 액션을 주로 이용할 것이다. 깃허브 액션은 데브옵스(DevOps)를 실현하기 위한 수많은 CI/CD(Continuous Integration)/(Continuous Deployment) 도구 중 하나이다. 데브옵스란 개발(Development)과 운영(Operations)의 합성어로 개발과 운영이 함께 어우러지는 환경 또는 문화를 의미하는 포괄적인 개념이다. 데브옵스의 실현에는 필연적으로 자동화가 필요한데, 그러한 측면에서 CI/CD 도구들은 특히 소프트웨어 제품들에 적용해 볼 수 있다. 깃허브 액션은 자동화된 워크플로우(Workflow)를 구성하도록 도와준다. 시나리오상에서 토론 탭에 짧은 글을 남기는 행위를 트리거(Trigger)로 정의하고, 이 트리거에 의해 자동으로 블로그를 빌드하고 배포하도록 워크플로우를 구성할 예정인데 이 모든 것이 깃허브 액션으로 가능하다.

액션 스크립트 작성하기

깃허브 액션으로 우리가 원하는 워크플로우를 구성하려면 구성도 같은 것을 작성해야 한다. 그것이 바로 액션 스크립트(Action Script)이다. 즉, 액션 스크립트를 잘 짜는 것이 핵심이다. 액션 스크립트는 yml(Yaml) 포맷으로 작성된다. 휴고를 이용해 봤다면 yml이나 toml의 문법에는 어느 정도 익숙할 것이다. 액션 스크립트는 소스코드 저장소에 위치하여야 하며 그 경로는 “.github/workflows/“로 고정되어 있다. 파일 이름은 확장자가 yml이기만 하면 되는데 본 글에서는 “cms.yml"로 정했다. 이제 “cms.yml"의 각 섹션 및 항목별로 설명하겠다.

on 섹션

액션 스크립트에는 on 섹션, jobs 섹션이 필수적이다. on 섹션은 본 액션 스크립트의 트리거를 지정한다. 아래 코드를 보면 토론 탭에 새 글이 올라왔거나, 기존 글이 수정되었거나, 댓글 달렸을 때를 트리거로 정의함을 직관적으로 알 수 있을 것이다.

on:
  discussion:
    types: [created, edited, answered]

jobs 섹션

트리거가 발생한 이후에 수행할 워크플로우를 단계별로 나열한 것이다. “Submodule Update”, “Setup Hugo”, “Build”, “Publish"의 세부 단계들로 구성되는데 우선은 “uses: actions/checkout@v2"라고 되어 있는 부분을 보자.

      - uses: actions/checkout@v2
        with:
          token: ${{ secrets.SECRETTOKEN }}
          submodules: true  
          fetch-depth: 0

깃허브 액션에서는 다른이가 작성한 액션 스크립트도 가져다가 쓸 수 있다. uses 예약어로 “actions/checkout"이라는 액션 스크립트를 가져다 쓸 것을 명시했다. 이 액션 스크립트는 본 워크플로우를 실행할 때 다른 저장소의 내용을 가져다 쓸 수 있도록 도와주는 스크립트이다. 우리는 깃허브 서브모듈을 이용하여 별도의 저장소에 있는 글과 테마를 가져올 필요가 있으므로 with 예약어로 submodule 변수의 값을 “true"로 설정했다.

한편 token 변수에 “secrets.SECRETTOKEN"이라는 값을 할당하고 있다. 우리가 글 저장소를 비공개 설정했기 때문에 깃허브 액션이라 할지라도 접근할 수 없다. 접근하려면 접근 권한이 부여된 토큰이 필요한데, 이것은 깃허브 홈페이지에서 발급받을 수 있다. 발급받은 토큰을 그대로 액션 스크립트에 사용할 순 있겠으나…. 그러면 글 저장소를 비공개로 설정한 의미가 없어진다. 따라서, 해당 토큰에 식별자를 부여하고 그 식별자를 액션 스크립트에 사용한다. 여기서는 “SECRETTOKEN"이 식별자이다. 키 발급 및 식별자 부여 방법에 대해서는 본 글의 젤 마지막에 설명할 테니 우선은 넘어가자.

“Submodule Update” 단계

본 워크플로우의 첫 번째 단계이다. 각 단계에서는 run 예약어에 나열된 쉘 스크립트(Shell script)를 실행한다. 본 단계는 서브모듈들을 가장 최신의 것으로 업데이트하는 쉘 스크립트를 실행한다. 앞서 “actions/checkout” 액션 스크립트로 다른 저장소에 분산된 서브모듈들을 가져왔으나 이들은 각 저장소의 특정 커밋(Commit)을 가리키고 있을 뿐이다. 이 단계가 없다면 새로이 작성한 글이 블로그에 추가되지 않을 것이다.

      - name: Git Submodule Update
        run: |
          git pull --recurse-submodules
          git submodule update --remote --recursive          

“Setup Hugo” 단계

휴고를 빌드하기 위해 휴고를 설치하는 단계다. 이번 단계에서는 run 예약어가 아닌 uses 예약어로 “peaceiris/actions-hugo"라는 액션 스크립트를 실행한다. 휴고 공식 사이트에서도 소개 중인, 이 액션 스크립트는 최신 버전의 휴고를 설치한다. 마찬가지로 지킬이나 여타 도구들도 비슷한 액션 스크립트가 있으므로 꼭 휴고가 아니더라도 유사 CMS의 적용이 가능하다.

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

“Build” 단계

소스코드와 함께 글과 테마도 가져왔고 휴고까지 설치했으니 드디어 블로그를 빌드할 수 있다. 우리가 늘 하던 것처럼 휴고를 실행하여 빌드한다.

      - name: Build
        run: |
                    hugo --minify

“Publish” 단계

이제 빌드된 블로그를 출판하는 일만 남았다. 블로그는 public 디렉토리에 위치하며 이 디렉토리가 별도의 저장소이므로 새로이 빌드된 블로그를 해당 저장소에 통째로 푸시해야 한다. 이때 “publish_date"라는 변수에 현재 날짜를 담고 이를 커밋 메시지에 사용하도록 했다. 사실 커밋 메시지는 어떤 부분이 변경되었는지 파악이 쉽도록 작성하는 것이다. 그러나, 현 워크플로우가 자동으로 동작해야 하므로 임의의 커밋 메시지를 작성할 수 없다. 따라서 빌드 날짜를 커밋 메시지에 담아 버저닝(Versioning)이 가능하게 하였다. 최소한 이전까지 작성된 글과 수정된 소스코드가 반영되었음을 알 수 있다.

      - name: Publish
        run: |
          cd ./public
          git config --global user.email "baek9@blahblah.com"
          git config --global user.name "baek9"
          git add .
          echo "publish_date=$(date --rfc-3339=date)" >> ${GITHUB_ENV}
          git commit -am "${{ env.publish_date }}" || echo "No changes to commit"
          git push origin HEAD:main --force          

토큰 등록

거의 다 왔다. 앞서 깃허브 액션이 비공개 저장소인 글 저장소에 접근하기 위해서는 권한이 필요하다고 했다. 바로 그 토큰 발급 부분에 관해서 설명하겠다. 토큰이란 지정된 권한을 행할 수 있도록 하는 키와 같다. 즉, 비공개 상태의 글 저장소를 열람할 수 있는 권한을 가진 키를 깃허브 액션에 부여하여야 한다. 키는 깃허브에 접속 후 [Settings - Developer settings - Personal access tokens]에서 발급이 가능하다. Generate new token 버튼을 누르면 키를 발급하는 절차를 시작할 수 있다.

키 생성 시에 Note 항목은 키의 식별자를 쓰는 곳으로 우선은 “blogtoken"이라고 써보자. 우리가 만들 키는 비공개 저장소에 접근할 권한을 부여를 가지므로 유출에 주의해야 한다. 따라서 Expiration 항목에서 키의 유효기간을 설정하기를 권장한다. 그 아래에는 키에 다양한 권한들을 부여할 수 있는 체크박스들이 있는데 repo를 선택하면 비공개 저장소에 접근 가능한 권한이 부여된다. 마지막으로 Generate token 버튼을 누르면 키 생성이 완료된다. 이때, 화면상에 보이는 키값을 반드시 복사해야 한다. 키를 확인할 수 있는 유일한 시점이므로 이후에는 해당 키의 확인이 불가능하기 때문이다.

발급받은 키를 작성한 액션 스크립트에서 사용할 수 있도록 하는 게 요지이다. 그러자면 키를 액션 스크립트가 위치한 소스코드 저장소에 등록해야 한다. 해당 저장소의 [Settings - Secrets - Actions] 메뉴에서 키를 포함해 워크플로우 상에서 필요한 모든 비밀정보 이를테면, 비밀번호 같은 것들을 등록할 수 있다. 자, New repository secret 버튼을 누르고 Value 항목에 아까 복사해 놓은 키를 그대로 붙여넣자. 그리고 Name 항목에는 액션 스크립트에서 해당 키를 참조하기 위한 식별자를 적는다. 우리는 앞서 “SECRETTOKEN"이라고 미리 식별자를 정해 놓았다. 아, 번거롭다는 이유로 본 절차를 생략하고서 hugo.yml에 직접 키를 사용하지 말자. 정성스레 작성한 글들을 날리고 싶지 않다면 말이다.

깃허브 토론 생성하기

깃허브 토론 역시 데브옵스에 일부 도움이 되는 기능으로서 저장소를 중심으로 주로 관계자들과 고객들 간의 활발한 의사소통과 협업을 가능하게 한다. 일종의 블로그와 비슷한 것으로 깃허브 측에서는 제품에 관련한 Q&A나 설문조사 등에 주로 이용하도록 고안한 것 같다. 필자는 이 깃허브 토론을 유사 CMS를 동작시키기 위한 트리거로 이용했다. 사전에 정의된 트리거의 종류가 많은데 그중에서도 깃허브 토론은 내가 원할 때 트리거할 수 있다. 예를 들어 푸시 트리거는 푸시될 때마다 워크플로우가 동작한다. 만약, 푸시된 사항이 진행 중이라면 오류가 포함된 블로그 또는 제품이 빌드되어 배포될 것이다. 깃허브 토론을 트리거로 설정하는 경우에는 글을 쓰거나 댓글을 다는 명시적인 행위로 워크플로우를 동작시킬 수 있다. 다만, 깃허브 토론은 공개된 공간이기에 누구나 워크플로우를 트리거할 수 있다. 필자는 소스코드 저장소 역시 비공개함으로써 이 문제를 해결했다. 다행히 테마 자체가 MIT 라이선스였기에 가능한 조치였다. 물론 공개 의무가 있는 라이선스라 하더라도 최신의 소스코드를 별도의 공개된 저장소에 푸시하는 또 하나의 워크플로우를 만듦으로써 우회할 수 있을 것이다.

마무리

여기까지 유사 CMS를 구축하는 모든 과정이 끝났다. 길고 복잡하지만 위 과정을 완벽히 수행했다면 핸드폰과 패드로도 편하게 글을 쓰고 출판할 수 있을 것이다.