mdBookをGitHub Actionsでビルド・デプロイ(CI/CD)


Markdownからbookを作成するツールにrust-lang/mdBookを利用します。このツールはGitBookに似ていますが、Rustで書かれたものになっています。mdBook向けに書かれたMarkdownファイル群をGitHubにプッシュすることでGitHub ActionsでCI/CDを行い、pyama2000.github.ioのようにGitHub Pagesにデプロイします。

リポジトリ: pyama2000/mdbook2pages

1. プロジェクトの構成図

mdbook2pages
├── book
├── book.toml
├── docker-compose.yml
├── Dockerfile
├── README.md
└── src
   ├── introduction.md
   ├── production
   │  ├── mdbook2pages.md
   │  ├── moses.md
   │  ├── musa.md
   │  ├── spotif_api.md
   │  ├── tex2pdf.md
   │  └── tof.md
   └── SUMMARY.md

2. mdBook環境の構築

mdBookはRust製のツールなのでローカルまたはDockerでRust + mdBookを実行できる環境を構築する必要があります。ここではDockerを使って環境を構築したいと思います。
Dockerfileは以下のようにシンプルなものになっています。
※ コンパイルが必要なため時間がかかります。

2.1 Dockerfile

Dockerfile
FROM rust:latest

RUN cargo install mdbook --vers "^0.3.5"

EXPOSE 3000

WORKDIR /mdbook

cargo installでmdbookをインストールしてますが、--vers "^0.3.5"とすることで
インストールするバージョンを0.3.5から0.3.xまでに指定しています。

2.2 docker-compose.yml

docker-compose.yml
version: "3"
services:
  mdbook:
    build: . 
    volumes:
      - ./:/mdbook
    ports:
      - 3000:3000
    container_name: mdbook2pages_mdbook

docker-compose up --buildで2.1節のDockerfileをビルドします。
以上でmdBookを編集、閲覧できる環境が整いました。

3. mdBookの使い方

この章ではmdBookの使い方について簡単に説明します。
公式のドキュメントサイトmdBook - mdBook Documentationに詳しい情報が乗っていますので参考にしてください。

3.1 プロジェクトの初期化

mdbook initで最小限の構成でmdBookを開始できます。

$ mdbook init

mdbook2pages
├── book
└── src
   ├── chapter_1.md
   └── SUMMARY.md

bookディレクトリはmdBookでビルドしたファイルが格納されるディレクトリで、デプロイする際に利用します。
srcディレクトリはMarkdownファイル群になっており、SUMMARY.mdはサイドメニューです。

SUMMARY.md
# Summary

- [Chapter 1](./chapter_1.md)

3.2 Markdownのビルド、閲覧

mdbook buildで3.1節で作成されたMarkdownファイル群をビルドできます。ビルドされたファイルはbookディレクトリに格納されているので、book/index.htmlをブラウザで開くと閲覧ができます。

mdbook serve --hostname 0.0.0.0srcディレクトリ以下のファイルを監視し、変更があった場合自動で再ビルドします。また、http://localhost:3000にアクセスすることで閲覧することができます。mdBookはデフォルトで3000番ポートを使用していますが、-pオプションでポート番号を変更できます。

3.3 記事の追加

srcディレクトリ以下にMarkdownファイルを追加、またはSUMMARY.mdや他のMarkdownファイルで他の記事をリンクするとビルド時に自動的にファイルが生成されます。また、src/productionのようにディレクトリを作成して、そこにMarkdownファイルを追加することもできます。

$ cat SUMMARY.md
# Summary

- [自己紹介](./introduction.md)
- [制作物](./production/tof.md)
    - [mdbook2pages](./production/mdbook2pages.md)
    - [moses](./production/moses.md)
    - [musa](./production/musa.md)
    - [spotify_api](./production/spotify_api.md)
    - [TeX2PDF](./production/tex2pdf.md)
    - [New file](./production/new_file.md)  # 存在しないファイル

$ mdbook build

mdbook2pages
├── book
└── src
   ├── introduction.md
   ├── production
   │  ├── mdbook2pages.md
   │  ├── moses.md
   │  ├── musa.md
   │  ├── new_file.md  # 自動で生成される
   │  ├── spotif_api.md
   │  ├── tex2pdf.md
   │  └── tof.md
   └── SUMMARY.md

4. GitHub Actionsとの連携

mdBookで作成したbookをGitHub ActionsでGitHub Pagesにデプロイします。

ファイル構成

mdbook2pages
└── .github
   ├── actions
   │  └── build
   │     └── Dockerfile
   └── workflows
      └── main.yml

4.1 GitHub Pages用のリポジトリを作成

GitHub Pagesは、GitHubのリポジトリからHTML、CSS、JavaScriptファイルを取得し、ウェブサイトを公開できる静的なウェブサイトホスティングサービスです。
GitHub Pages用に<USER_NAME>.github.ioというリポジトリを作成してください。
※ この例ではpyama2000.github.io

リポジトリを作成することができたら、適当にindex.htmlをプッシュし、https://<USER_NAME>.github.ioでユーザのウェブサイトにアクセスすることができます。

ユーザ用のサイトは1つしか作成できませんが、プロジェクトごとにもGitHub Pagesを作成することができます。プロジェクトサイトを公開するにはgh-pagesブランチにHTML、CSS、JavaScriptか、masterブランチの/docsディレクトリを配置することで公開できます。プロジェクトサイトのURLはhttps://<USER_NAME>.github.io/<REPOSITORY_NAME>となります。

詳細についてはGitHub Pages について - GitHub ヘルプを参照してください。

4.2 秘密鍵・公開鍵の作成

GitHub Pagesを公開するのに公開アクションであるGitHub Pages action · Actions · GitHub Marketplaceを使用します。そのために秘密鍵・公開鍵を生成し、それぞれのリポジトリに配置する必要があります。
以下のコマンドで秘密鍵と公開鍵のペアを作成します。

$ ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""

-CオプションでGitに登録しているメールアドレスをコメントとして渡し、-Nオプションでパスフレーズを空白として、-fオプションによってpg-pagesというファイル名でキーペアを作成します。作成したキーペアはコマンドを実行したディレクトリの直下に生成されます。

作成したキーペアの公開鍵( gh-pages.pub )の内容を4.1節で作成したGitHub Pages用のリポジトリ(USER_NAME.github.io)のSettings > Deploy keysに任意の名前で登録します。また、Allow write accessにチェックを入れるのを忘れないでください。

秘密鍵は、mdBookでビルドするリポジトリ(ここではmdbook2pages)のSettings > SecretsACTIONS_DEPLOY_KEYという名前で登録します。

4.3 アクションを作成

ワークフローでmdBookをビルドするアクションを定義します。
.github/actions/build/Dockerfileを作成し、以下のように記述してください。

.github/actions/build/Dockerfile
FROM rust:latest

RUN cargo install mdbook --no-default-features --features output --vers "^0.3.5"

CMD ["mdbook", "build"]

2.1節で作成したDockerfileと似ていますが、mdBookのインストールの際に--no-default-featuresオプションと
--featuresオプションでoutputを指定することで、出力するためだけの機能をもったmdBookをインストールすることができます。
CMD命令によりビルドされたコンテナに入ると、自動的にmdbook buildコマンドが実行されます。

4.4 ワークフローを作成

以上によりmdBookのビルド、公開するアクションの準備が整ったのでいよいよワークフローを定義します。
.github/workflows/main.ymlが以下のコードとなっています。

.github/workflows/main.yml
name: CI/CD
on:
  push:
    branches:
    - master
    paths:
    - "src/**.md"
    - "book.toml"

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1
    - name: Build
      uses: ./.github/actions/build
    - name: Deploy
      uses: peaceiris/actions-gh-pages@v2
      env:
        ACTIONS_DEPLOY_KEY: ${{ secrets.ACTIONS_DEPLOY_KEY }}
        EXTERNAL_REPOSITORY: <USER_NAME>/<USER_NAME>.github.io
        PUBLISH_BRANCH: master
        PUBLISH_DIR: ./book

ワークフローの詳しい書き方は割愛しますが、このYAMLファイルにより、masterブランチにsrc/**.mdまたはbook.tomlファイルがプッシュされたら、deployジョブが実行されます。

deployジョブではファイルを取得し、Buildステップでは4.3節で定義したアクションを実行、mdbook buildによってHTML一式をbookディレクトリ以下に生成します。

その後、peaceiris/actions-gh-pages@v2アクションによってデプロイを行っています。環境変数を与えることで外部リポジトリにプッシュしたりプッシュするブランチを指定できます。ACTIONS_DEPLOY_KEYは4.2節で登録したSecretsを参照し、EXTERNAL_REPOSITORYで外部リポジトリを定義しています。EXTERNAL_REPOSITORYUSER_NAME はご自身のユーザ名に置き換えてください。PUBLISH_BRANCHでプッシュするブランチを指定します。最後に、PUBLISH_DIRBuildステップで生成されたbookディレクトリをプッシュします。

その他の環境変数はREADME.mdに記載されているので、こちらを参照してください。

5. 完成したコードをプッシュ

4章までに作成したコードをリポジトリ(ここではmdbook2pages))にプッシュするとGitHub Actionsが自動で実行され、エラーが発生しなかったらUSER_NAME.github.ioにHTML一式がプッシュされているはずです。

おわりに

GitHub Actionsを利用することで、継続的にテストやビルド、デプロイ(CI/CD)ができるようになり、プッシュするだけでウェブサイトが更新されるようになりました。以前では、手動でビルドし、ビルドしたものをサーバにアップして...などの手順がめんどくさかったですが、GitHub ActionsによってMarkdownファイルを書き、プッシュするだけですべての手順を自動でやってくれるため、書くことに集中することができるようになりました。
また、mdBookではCSSやJavaScriptを独自に定義することができるため、今後はこのCSSやJavaScriptをリント、フォーマッティング、テストを行うアクションを定義したいと思います。


参考情報