メイン環境を汚さない Hugo + Asciidoctor 連携:専用コンテナ切り出しと並行ビルド排他制御(flock)の技術
日々の開発・インフラ検証の備忘録として残している個人ノートです。手元環境での動作ログをもとにまとめています。環境差異等もあるため、参考にされる場合はご自身の環境で検証の上ご活用ください。
はじめに
静的サイトジェネレーター Hugo は、標準で Markdown(Goldmark)の高速パースに対応していますが、技術文書や仕様書に強い AsciiDoc(.adoc) のレンダリングにも対応しています。
しかし、AsciiDoc をパースするには Ruby 製の asciidoctor コマンドが必要です。
Go 言語メインで構成されたクリーンな開発環境(DevContainer)に、Ruby や Bundler、Rouge(シンタックスハイライター)などの大量の Gem を直接追加したくありませんでした。メイン環境の依存関係が汚染され、イメージサイズやビルド構成が複雑化するためです。
そこで、「Asciidoctor を専用の軽量 Docker コンテナに切り出し、Hugo からはローカルコマンドのように透過的に呼び出す」 アーキテクチャを設計しました。
しかし、この構成を組んだ直後、Hugo の超高速並行ビルドによって標準入出力パイプが競合・切断され、出力が 0 bytes になる致命的なトラブル に直面しました。
本記事では、コンテナ連携アーキテクチャの基本設計から、並行ビルド時のパイプ切断のメカニズム、そして flock を用いた堅牢な排他制御ラッパースクリプトの完全な実装までを整理します。
コンテナ連携アーキテクチャの設計思想
Hugo から見ると、ローカル環境に存在する通常の asciidoctor コマンドを実行しているように見えます。しかし、実際にはパスの通ったラッパースクリプトが背後で軽量コンテナを呼び出し、標準入出力(stdin / stdout)を中継します。
| |
この構成のメリット
- メイン環境の汚染ゼロ: メインのコンテナやホスト OS には Ruby も Bundler も一切インストールされません。
- 完全なポータビリティ: Asciidoctor のバージョンや依存 Gem は Docker イメージ内に完全に固定されているため、ローカルと CI 間での挙動差が発生しません。
構築手順 ①:専用 Docker イメージの準備
1. Dockerfile と Gemfile の定義
Dockerfile.asciidoctor:
| |
Gemfile:
| |
2. イメージのビルド
| |
直面した課題:並行ビルド時の「0 bytes パイプ切断の罠」
素朴なラッパースクリプトとして、以下のような 1 行スクリプトを最初に作成しました。
| |
なぜこのスクリプトは失敗するのか?
Hugo は数百ページにおよぶ記事を Go の Goroutine を用いてミリ秒単位で並行ビルド します。
複数の AsciiDoc 記事が存在する場合、Hugo は同時に何十個もの asciidoctor プロセスを立ち上げます。
その結果、以下の致命的な問題が発生しました:
- Docker デーモンの同時実行過負荷:
短時間に数十個の
docker runコンテナが同時に立ち上がり、マシンの CPU/メモリを圧迫。 - 標準入出力パイプの切断(壊れたパイプ / Broken Pipe): コンテナの起動オーバーヘッド(数十〜数百ミリ秒)を待つ間に、Hugo 側の標準入力送信タイムアウトが発生。パイプが切断され、Hugo に 空データ(0 bytes) が返却されてビルドが壊れる。
解決策:一時ファイル読み切り + flock 排他制御ラッパー
この問題を完全に解決するため、ラッパースクリプトに以下の 3 重の防御機構 を組み込みました。
| |
完成したラッパースクリプト (~/.local/bin/asciidoctor)
| |
実行権限を付与します:
| |
構築手順 ②:Hugo のセキュリティ許可設定
Hugo は外部プロセスの無制限な実行を制限しているため、hugo.toml のセキュリティ設定で asciidoctor の実行を明示的に許可します。
| |
パフォーマンスと運用の検証
この構成により、以下の効果が得られました:
- ビルドの安定性 100%: 並行ビルド時にどれほど大量の AsciiDoc ファイルが存在しても、パイプ切断や 0 bytes エラーが完全にゼロ化。
- 高速なビルド: 入力の一時ファイル読み切りと直列化により、1 ページあたりわずか数十ミリ秒で変換が完了。
- 完全なクリーン環境: メインコンテナのイメージサイズを最小限に保ったまま、Ruby エコシステムの恩恵を享受可能。
まとめ
- 専用コンテナの切り出し: 外部言語ランタイム(Ruby/Gem 等)をメイン環境に同居させず、Docker コンテナ+CLI ラッパーで透過的に分離。
- 並行パイプ切断の克服: Hugo の並行ビルド下では、ロック取得前の「stdin 即時読み切り」と「
flockによる実行排他制御」が必須。 - ポータブルな資産化: Dockerfile とラッパースクリプトをリポジトリ内に管理することで、誰のマシンでも CI でも同一のビルドを再現。
※ 本記事の構成検討・技術仕様の検証・Hugo による静的ビルド検証・推敲は、AI コーディングエージェントとの自律協働ループによって執筆・検証されています。