LaTeX のコンパイル環境を docker に閉じ込めて使う

VM を含めて普段から4箇所ほど環境を使いまわしていると LaTeX の環境をそれぞれで整えるのがかなり億劫である. 昨年の卒業論文で書き始める前に構築した環境が割とポータブルだったのでそれをまとめて書く.

求めていた環境は以下のような形で,割とシンプルに満たせたと思う.

  • ローカルに LaTeX およびそれに関係するパッケージは一切入れない
  • docker とプロジェクトがあればどこでもビルドできる
  • ローカルに LaTeX 環境を整えた場合とコンパイル等の速度に差は無い
  • (編集はターミナルで完結させたい = GUI使いたくない)

github.com

README にも書いてある通り,プロジェクトごと一度引っ張ってきて解凍するだけで良い. docker を使っているので(docker 以外に)インストールが必要なものはないし,パッケージで必要なのがあっても Dockerfile に書いてしまえば問題無い.

Dockerfile のベースイメージは alpine をベースに作ってあるもの*1を使っているのでディスクもそんなに食べない.

Dockerfile

FROM paperist/alpine-texlive-ja

RUN apk add --update ghostscript git

RUN tlmgr update --self

# You can install package using tlmgr
# e.g.) RUN tlmgr install ulem

日本語入力系の設定が記載している .latexmkrc を参考に*2してコマンド類をまとめることで,コンパイル時のスクリプトも単純にしている.

build.sh

docker build -t latex-template .
docker run --rm -it -v $PWD:/workdir latex-template:latest latexmk main.tex

.latexmkrc

#!/usr/bin/env perl
$latex            = 'platex -synctex=1 -halt-on-error';
$latex_silent     = 'platex -synctex=1 -halt-on-error -interaction=batchmode';
$bibtex           = 'pbibtex';
$dvipdf           = 'dvipdfmx %O -o %D %S';
$makeindex        = 'mendex %O -o %D %S';
$max_repeat       = 5;
$pdf_mode         = 3;

ipsj のテンプレートくらいの内容なら特に問題無く扱える.コンテナ起動速度もコンパイル時間に比べれば一瞬なので気にならないはず. ブログの内容は古い可能性があるので,適宜リポジトリを参照してほしい.

signal を http で受ける process wrapper 書いた

結局使わなかったので供養のためにブログ化しておく.

github.com

docker を始めとするコンテナ環境では,設定を適用するため signal を送ることで graceful reload をする仕組みは相性が悪いことがある.ホスト環境から動作中のコンテナに対してなんらかの働きかけが必要だからだ.なので,例えば systemd 環境下で動作しているアプリケーションをそのままコンテナ化することが難しかったりする. 特に,複数のアプリケーションが強調して動作する仕組みが必要である場合,side car コンテナなどでアプリケーションを分割すると途端に困難さが増す*1. アーキテクチャの刷新をするのが最も健全な手段であるが,そうもいかない場合に対処するため,signal のインターフェースを http に露出させるプロセスの wrapper を書いた.

mvp

signal を露出させたいプロセスの例として signal_echo.rb を用意

# signal_echo.rb

running = true

Signal.trap('INT') do |signo|
  puts 'Traped SIGINT'
  running = false
end

loop do
  break unless running
end

signal_echo.rb を http-signal-proxy から立ち上げる Dockerfile を用意

# Dockerfile

FROM ruby:2.5

RUN wget -q https://github.com/s4ichi/http-signal-proxy/releases/download/v0.1.0/http-signal-proxy_v0.1.0_linux_amd64.tar.gz && \
  tar -zxvf http-signal-proxy_v0.1.0_linux_amd64.tar.gz && \
  mv http-signal-proxy_v0.1.0_linux_amd64/http-signal-proxy /usr/local/bin/http-signal-proxy && \
  chmod +x /usr/local/bin/http-signal-proxy

COPY ./signal_echo.rb /signal_echo.rb
CMD ["http-signal-proxy", "--port=8080", "--command=ruby signal_echo.rb"]

実行

$ docker run -d --rm -p 8080:8080 -t http-signal-proxy-test:latest 
81f3d8507baa9c2a945c20d920824b6ca18f3be4ef04c7b853ac9b33c0480356

$ docker ps
CONTAINER ID        IMAGE                           COMMAND                  CREATED             STATUS              PORTS                    NAMES
81f3d8507baa        http-signal-proxy-test:latest   "http-signal-proxy -…"   4 seconds ago       Up 3 seconds        0.0.0.0:8080->8080/tcp   jolly_spence

$ curl http://localhost:8080/http-signal/sigint
Successed to proxy sigint to destination command%             

$ docker ps                                    
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

実装は entrykit とか supervisord を参考にした.entrykit はなかなか厳しそうなコードがそこそこあることがわかった.

push or pull

コンテナ外部から reload したいとき(push 型)はこういう手段がいいのかなぁと思って書いた.結局使わなかったというのは,コンテナ内部で reload が必要かどうかを polling すれば(pull 型)十分であることがわかったからだ.push 型はいつか必要になるのかな….

アイデンティティを発破解体した

学生気分が薄くなっていくにつれて,id と自分自身になんとなくギャップが生まれているなと感じることが多くなり, 将来的に 30 代になってもこの id を名乗り続けるのは厳しいという気持ちが芽生えたので,アイデンティティを発破解体して無難なものへと変更した.

変更すると決断するまでは,どうせ中途半端になるか何か問題が発生するだろうという諦め(怠惰の言い訳?)のもと,1ヶ月くらい悩んでいた. 色んなサービスの id 変更について調べたり,自分の気持ちにどうなんだと問いかけたり,今までになく足が重かったと思う.

自分でも覚えていないくらい様々なサービスに登録していたり,そもそも id を変更しきれない部分*1もあったり,変更すると混乱を招きそうな部分はそのままにするつもりなので,しばらくごちゃまぜになります⚠

*1:ちなみにはてなIDは変わらないそうなので独自ドメインにした