GitHub Actionsでpandocのコンテナを使ってmarkdownをepubやPDFにする
久しぶりにこのブログでDevOps系の話題を書こうと思います。
今までは、技術書執筆は Google DocsかRe:Viewを使っていました。
■ReViewで本や卒論を書いてGitHub ActionsでPDFやePubを生成するテンプレート「kaitas/ReBook」
Re:Viewは Markdown風でもありTeX風でもあるので、卒論や技術書典のような書き物をPDF化するのは便利です。ePubもKindle Desktop Publishingに対応したePub3が出力できます。技術同人誌だけでなく商業出版も含めGitHub Actionsでビルドできる環境を作ってしまったので、ここ数年はこれで進めてきたのです。
ところがLLM時代になって、MarkdownやVisual Studio Code+Copilotによる執筆がより一般的になったのと、TeXタイプセットといっても数式はほとんどない「普通に美しいPDFが出せればいい」という需要があり、さらに商業出版ではepubでサポートしてほしい。コンテンツ面では特にHTMLでよし。共通に使うのはハイパーリンクや画像タグ、あとは強調構文ぐらいしか使わないので平打ちテキストやmarkdown原稿をわざわざRe:Viewで書き直す積極的な理由がほとんどなくなってしまった。ソースコードブロックも、スタイルで定義できれば、そのためにRe:Viewを使う理由にはならない。
というわけで、あとはGitHub ActionsでのビルドだけがPros/Consで残りました。これができれば何も悩むことはない。Re:View化の手間が無くなる(LLMで変換すればいいという説もあるが!)。ともかくここは意を決してpandocによるMarkdown中心とした技術書執筆ワークフローの構築をやってみたいと思います。
AIサービスを使った DevOpsへの活用
Claude 3.5 Sonnet Artifactsによる GitHub Actionsの設定
Claude 3.5 Sonnet Artifactsはとても便利です。コーディングの勉強にもなるし読みやすいので、ChatGPTや Gemini + AI Studio とはまた異なる魅力があります。コーディングだけでなく、やらせてみると GitHub Actionsの設定のようなノウハウを含めた手順書構築にも便利であることがわかりましたので、今回は「GitHub Actionsの知識があやふやな私」+「Claude」という構成でどこまでできるか試してみます。
GitHub Actions化する前に:Pandocでの環境構築
この作業に入る前に、いったんWindowsのローカル環境で以下を探求しています。
要求仕様としては以下の通りです
pandocコマンドで複数のmarkdownファイルを纏めてPDFやePubにしたい
いざというときは(GitHub Actionsに頼らず)ローカルビルドしたい
今後一切、Re:Viewフレームワークにに頼らない
TeXを使ってPDFを生成するのは許可。LiveTex2024をフルインストールするのもやっておく。
コンテンツ一式もmarkdownで管理する
画像についてはローカルリポジトリに置くが、リモート可なら大変ありがたい
TexLive2024のインストール、久しぶりにやりましたが数時間かかるのね。
8.54GB。これは下手な画像生成AIモデルより大きい。
大事なのは日本語環境と日本語フォント。IPAexにIPAexフォントがインストールされている。
C:\texlive\2024\texmf-dist\fonts\truetype\public\ipaex
ありがたいです。
その他、ローカルで pandocを使って思いのままにビルドできるようになるまで、LLMに壁打ちしてもらう。これはChatGPTでもできる。
完成したpandoc起動コマンド。
epub3をビルドする例。
同様に、複数のmarkdownファイルを入力に lualatex経由でPDF化する例。
・toc:目次
・pdf-engine:これはLiveTexをインストールすれば色々片付く
・-V documentclass=ltjsarticle:jsarticleでもいけるけど、最近のトレンドはもっと新しい「LuaLaTeX」を使った環境。
・include-in-header=header.tex:ePubにおけるCSSを担当するのが「header.tex」で、これをどう書くかで問題発生したり解決できるので、ここはLLMに助けてもらうといい。
ローカルでのビルドで一通りの疑問がなくなったので、GitHub化を進めます。
ここから先は、出来上がりベースで紹介していきます。
ふりかえり:GitHub ActionsをClaudeとやってみてわかったこと
初見で出してきたyamlは動くが、いろんな不具合がある
ビルドやインストールをゼロからやる愚鈍なActionが初版
そのままだとビルドに15分以上かかる
DockerとBuildXを使ってキャッシュ化、ghcr.ioの活用
GitHub Actionsの設定
多くはGitHub Actionsのエラーログを食わせれば解決する
Actionsを運用してないとわからない勘所はけっこうある
特にセキュリティアップデートがあるので最新のActionを作っていく、追従させるところが大事。初見はcheckout@v2を提案してきた
ディスクの節約は実用性を考えて調整必要
キャッシュとDocker Imageのサイズバランス、ビルド速度の調整はめちゃ大変で、だいたいイテレーションとしては18回転ぐらいかかった。
とはいえGitHub ActionsをClaudeが書けるということを知っているか知っていないかでAI活用の開発体験としては大変な違い。
ePubとPDFを同時に4分でビルドできるのはなかなかではないでしょうか!
Claudeと実装編。
上の流れを、pandocの起動コマンドを完成させたあとに探求していきます。まずは最適化されたワークフローを作り、とりあえずのビルドができるようになります。ただこれでは15分以上かかるので実用的ではありませんし、Actionsの処理時間は課金対象です。
とはいえ月間で、3000分は使えるので1日100分を超えなければ…というところです。GitHubの設定で見れます。
https://github.com/settings/billing/summary
編集業務で複数のワーカーが使うとなるとピーク時は1日あたり数十回ビルドすることもあるので、実用性やUXも含めて1回のプッシュでの処理時間は10分を切りたいところです。
ちなみにMacOSやWindows版のコアもあります。
GitHub Actions の課金について
Docker Buildxを使った高速化
Docker Buildxを設定し、ビルドキャッシュを使用する方針にします。
GitHub Container Registryにログインし、ビルドしたイメージをプッシュします。初回以降のステップでは、ビルドしたイメージをプルして使用します。これにより2回目以降の実行では、Dockerイメージのビルド時間が大幅に短縮されます。
ビルド済みのイメージを再利用するため、全体の実行時間が短くなります。
注意点:GitHub Container Registryを使用するには、リポジトリの設定で適切な権限を設定する必要があります。
Claudeが生成した初期の build.yaml
name: Build PDF and EPUB
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Cache Docker layers
uses: actions/cache@v3
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v4
with:
context: .
push: true
tags: ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new
- name: Move cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Prepare content files list
run: echo $(cat contents.md | grep '^-' | sed 's/^- //') > content_files.txt
- name: Build PDF
run: |
docker run --rm -v ${{ github.workspace }}:/workspace ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest \
pandoc $(cat content_files.txt) -o output.pdf \
--toc \
--toc-depth=2 \
--pdf-engine=lualatex \
-V documentclass=ltjsarticle \
-V classoption="12pt,a4paper" \
-V geometry:margin=1in \
--epub-cover-image=cover.png \
--include-in-header=header.tex \
--metadata-file=title.txt
- name: Build EPUB
run: |
docker run --rm -v ${{ github.workspace }}:/workspace ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest \
pandoc -f markdown -t epub3 $(cat content_files.txt) title.txt -o output.epub \
--css stylesheet.css \
--toc \
--toc-depth=2 \
--epub-cover-image=cover.png
- name: Upload PDF artifact
uses: actions/upload-artifact@v3
with:
name: output-pdf
path: output.pdf
- name: Upload EPUB artifact
uses: actions/upload-artifact@v3
with:
name: output-epub
path: output.epub
permissions セクションを追加して、パッケージへの書き込み権限を明示的に付与しています。GitHub Container Registry へのログインで github.actor を使用しています。
github.actor の設定
リポジトリの設定で以下の点を確認してください。
・"Settings" -> "Actions" -> "General" で、"Workflow permissions" を "Read and write permissions" に設定します。
・"Settings" -> "Actions" -> "General" で、"Allow GitHub Actions to create and approve pull requests" にチェックを入れます。
初回ビルドに成功
やったぜ
この更新されたワークフローでの主な変更点は以下の通りです:
actions/checkout@v4 に更新
docker/setup-buildx-action@v3 に更新
actions/cache@v4 に更新
docker/login-action@v3 に更新
docker/build-push-action@v5 に更新
actions/upload-artifact@v4 に更新(両方のアップロードステップ)
とはいえビルド時間は…15分以上かかる
しかもディスクが足りない
Annotations
1 warning
build
You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: 83 MB
この警告は非常に重要です。GitHub Actionsのランナーのディスク容量が不足しており、わずか83MBしか残っていないことを示しています。これは、大きなDockerイメージをビルドしているために発生している可能性が高いです。
この問題に対処するために、以下の方法を試すことができます:
ディスク使用量の削減:
不要なファイルやキャッシュを削除する
Dockerイメージのサイズを縮小する
ワークフローの最適化:
必要最小限のパッケージのみをインストールする
マルチステージビルドを使用してイメージサイズを縮小する
ランナーのクリーンアップ:
ワークフロー実行中に定期的にディスクをクリーンアップする
最終的に完成した Dockerfileと build.yml
以下に、これらの対策を適用した更新版のワークフローとDockerfileの例を示します。
最終的に完成したDockerfile
FROM ubuntu:22.04
ENV DEBIAN_FRONTEND=noninteractive
ENV TZ=Asia/Tokyo
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
perl \
texlive-full \
pandoc \
fonts-ipafont \
fonts-ipaexfont \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /workspace
# パスが正しく設定されているか確認
RUN which pandoc && pandoc --version && tex --version
# luatexbase.styが存在することを確認
RUN find /usr/share/texlive -name luatexbase.sty
# TeX Liveのバージョンを確認(tlmgrの代わりに)
RUN tex --version
# インストールされているTeXパッケージを確認(tlmgrの代わりに)
RUN apt list --installed | grep texlive
完成した build.yml
完成したGitHub Actionsワークフロー(.github/workflows/build.yml)
name: Build PDF and EPUB
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Check initial disk space
run: df -h
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Check disk space
run: |
echo "Disk space before build:"
df -h
echo "Docker image and container sizes before build:"
docker system df -v
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Check disk space after Docker build
run: |
echo "Disk space after build:"
df -h
echo "Docker image and container sizes after build:"
docker system df -v
echo "Size of the built image:"
docker image ls ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest --format "{{.Size}}"
- name: Check Docker image
run: |
docker run --rm ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest which pandoc
docker run --rm ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest pandoc --version
docker run --rm ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest tex --version
docker run --rm ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest find /usr/share/texlive -name luatexbase.sty
docker run --rm ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest apt list --installed | grep texlive
- name: Prepare content files list
run: |
echo $(cat contents.md | grep '^-' | sed 's/^- //') > content_files.txt
echo "Content of content_files.txt:"
cat content_files.txt
- name: Build PDF
run: |
docker run --rm -v ${{ github.workspace }}:/workspace ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest \
bash -c "cd /workspace && \
pandoc \$(cat content_files.txt) -o output.pdf \
--toc --toc-depth=2 --pdf-engine=lualatex \
-V documentclass=ltjsarticle -V classoption='12pt,a4paper' \
-V geometry:margin=1in --epub-cover-image=cover.png \
--include-in-header=header.tex --metadata-file=title.txt"
- name: Build EPUB
run: |
docker run --rm -v ${{ github.workspace }}:/workspace ghcr.io/${{ github.repository_owner }}/texlive-pandoc:latest \
bash -c "cd /workspace && \
pandoc -f markdown -t epub3 \$(cat content_files.txt) \
-o output.epub \
--css stylesheet.css --toc --toc-depth=2 --epub-cover-image=cover.png \
--metadata-file=title.txt"
- name: Upload PDF artifact
uses: actions/upload-artifact@v4
with:
name: output-pdf
path: output.pdf
- name: Upload EPUB artifact
uses: actions/upload-artifact@v4
with:
name: output-epub
path: output.epub
- name: Check final disk space
run: df -h
- name: Clean up
if: always()
run: |
docker system prune -af
docker volume prune -f
主な変更点:
Dockerfileを最適化し、マルチステージビルドを使用してイメージサイズを縮小しました。
ワークフローの開始時に不要なファイルを削除して、ディスク空間を確保します。
Docker BuildxのキャッシュにGitHub Actionsのキャッシュを使用します。
ワークフロー終了時にDockerのクリーンアップを実行します。
・LiveTexのインストールは色々試してみたけど、結局フルインストールしたほうがシンプルに解決する。
・apt-getでインストールする
もっと削れるかもしれないけど。
そもそも9.3GBぐらいしかあいてない。
仕上がりはこんな感じ
ePubです。Kindle Desktop Publishing対応。
PDFです。
気になる本誌「AICUマガジン Vol.3」はこちら!
(これはRe:Viewでビルドしてました)