Edited at

【Docker超入門】DockerでつくるLaTeX環境

本稿に関して動作がおかしいところや不明な点,ツッコミなどがあれば適宜コメントいただけるとありがたいです.


はじめに

大学の研究会で講義を1コマ持つことになったため,弊研究会では必須なLaTeX環境をDockerで作る方法を解説することにしました.

読者としてはDockerの知識が一切ない人を想定しています.

なお,本稿ではLaTeX自体の使い方に関してはあまり触れません.


LaTeXとは?

LateXとは,Donald E. Knuth氏が開発したレイアウトシステムのTexをLeslie Lamport氏が文書作成のために改良したものだそうです.主に学術論文を書く際に使用されており,数式を綺麗にレイアウトしてくれます.

例えば,世界一美しいとされるオイラーの等式でさえ,テキストで表現すると美しくないですが,

e^{iπ} + 1 = 0

TeXを用いてコンパイルすれば,以下のように美しくレイアウトされます.

$e^{iπ} + 1 = 0$


Dockerとは?

Dockerとはコンテナ仮想化技術のプラットフォームです.

image.png

従来の仮想化技術では,ホストOS上の仮想ハードウェアや,Hypervisorを利用してゲストOSを動かすことで環境を隔離していたのに対し,DockerではDocker EngineがホストOSのカーネルの利用を上手く管理することでプロセスとユーザを隔離しているため,VMに比べて軽量な仮想化環境を実現しています.


事前準備

本稿では,このDockerを用いてLaTeXのディストリビューションであるTeXLiveの環境を行うため,まずはDockerと生成されたPDFの閲覧用にSkimをインストールします,


Dockerのインストール

下記ページからDockerHubのアカウントを作成しdmgをダウンロードしてインストールします.

MAC=> https://docs.docker.com/docker-for-mac/install/

Windows=> https://docs.docker.com/docker-for-mac/install/


Skim(軽量PDFビューワー)のインストール

下記ページよりダウンロードしてインストール

https://skim-app.sourceforge.io/


全体像を把握する

今回は下記のような構成でDockerを用いてLaTeX環境を構築します.

image.png


とりあえずDockerでLaTex環境を建ててみる

DockerとSkimのインストールが完了したら,筆者が事前に用意したTeXLiveのDockerイメージ(nontan18/texlive)を用いて以下のコマンドでLaTeX環境を実際に構築してみましょう.

// 作業用のworkディレクトリを作成し移動

$ mkdir work && cd work

workディレクトリに入ったら下記の内容のファイルを作成します.


sample.tex

\documentclass[twocolumn, a4j]{article}

\usepackage{multirow}
\usepackage{amsmath,amssymb}
\usepackage[T1]{fontenc}

\title{サンプル用のTexファイル}
\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\author{Nozomu Miyamoto\footnotemark[2] nontan@sfc.wide.ad.jp}
\renewcommand{\thefootnote}{\arabic{footnote}}
\date{\today}

\begin{document}

\twocolumn[
\begin{@twocolumnfalse}
\maketitle
\vspace{-6mm}
\begin{abstract}
ここに概要を書きましょう.
\end{abstract}
\vspace{2mm}
\end{@twocolumnfalse}
]

\renewcommand{\thefootnote}{\fnsymbol{footnote}}
\footnotetext[2]{慶應義塾大学 村井研}
\renewcommand{\thefootnote}{\arabic{footnote}}

\section{はじめに}

 hoge hoge hoge hoge

\renewcommand{\refname}{参考文献}
\begin{thebibliography}{数字}
\bibitem[opt]{key} 文献情報
\end{thebibliography}

\end{document}


// 生成したdocument.texをdocument.pdfにコンパイル

$ docker run -v $(pwd):/root/work -it nontan18/texlive:stable latexmk --pvc ./sample.tex

これでworkディレクトリ内にsample.pdfが出力されますので,SkimなどのPDFビューワーを使って表示してみましょう.

表示できたら,お好きなエディタを用いて,sample.texを編集してみます.

$ vim sample.tex

編集が完了すると自動でコンパルが始まり,document.pdfが更新されるかと思います.


Dockerの基本的な使い方

このセクションでは上記のDockerコマンドで一体何が起きているのかを説明します.


docker run

docker runはDockerイメージを元にDockerコンテナを起動するコマンドです.

runコマンドの第1引数で指定したコマンドが起動したコンテナ内で実行されます.

上記の

$ docker run -v $(pwd):/root/work -it nontan18/texlive:1.0.0 latexmk --pvc ./sample.tex

においてはlatexmk --pvc ./sample.texコマンドが実行されています.

(latexmk --pvc ./sample.texコマンドは任意のTexファイルを監視して更新されるたびに自動コンパイルするコマンドです.)

-tオプションでDockerイメージを参照することができます.

上記の例ではDockerHubに登録されているnontan18/texliveというイメージ(より詳細には,その中でも1.0.0タグがついているイメージ)を参照して,それを元にDockerコンテナを作成しています.

(ちなみに,このDockerHubのようなDockerImageが登録されている場所をDockerRegistryと呼びます.)

-vオプションはボリュームのマウントオプションで,DockerホストのボリュームをDokcerコンテナ内の任意の場所にマウントすることができます.上記の例では,texの作業用に作った

(-v オプションでは相対リンクでボリュームを指定できないので,pwdコマンドで現在のディレクトリを取得してそれを引数に取っています.)


docker ps

docker runコマンドで動いているDockerコンテナはdocker psコマンドで確認することができます.

$ docker ps

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
1349e83ba710 nontan18/texlive:1.0.0 "latexmk --pvc ./sam…" 5 seconds ago Up 3 seconds gallant_galileo


docker exec

また,docker exec -it [CONTAINER ID] [COMMAND]コマンドを使うと,起動中のコンテナ内でコマンドを実行することができます.

// $CONTAINER_IDはdocker psで取得したコンテナのIDに変更(example:1349e83ba710)

$ docker exec -it $CONTEAINER_ID sh
// ここからコンテナ内
~/work# ls
sample.aux sample.dvi sample.fdb_latexmk sample.fls sample.log sample.pdf sample.synctex.gz sample.tex
// TeXLiveの絵文字パッケージが入っているか調べる
~/work# tlmgr search fontawesome
// コンテナ内にTeXLiveの絵文字パッケージをインストール
~/work# tlmgr install fontawesome
//...省略
tlmgr: package repository http://mirror.utexas.edu/ctan/systems/texlive/tlnet (verified)
[1/1, ??:??/??:??] install: fontawesome [498k]
running mktexlsr ...
done running mktexlsr.
running updmap-sys ...
done running updmap-sys.
tlmgr: package log updated: /usr/local/texlive/2019/texmf-var/web2c/tlmgr.log
// コンテナから出る
~/work# exit
exit

// ホストのシェルに戻った
$

上記の例では先程建てたコンテナ内でshコマンドを実行して,ターミナルからコンテナ内に入り,TeXLiveの絵文字パッケージ(fontawsome)が入っていないことを確認して,TeXLiveパッケージマネージャー(tlmgr)を用いて絵文字パッケージをインストールしました.


docker commit(※あんま使わない)

ただ,これだけではDockerコンテナ内での変更でしか無いため,一度Dockerコンテナを削除し,docker runコマンドで再度コンテナを建てると,絵文字パッケージはインストールされていない状態に戻ってしまいます.

このDockerコンテナの変更をDockerイメージに反映させるためにはdocker commit [CONTAINER ID] [IMAGE NAME]コマンドを用います.

$ docker commit 1349 nontan18/texlive:stable

sha256:a64dcff68e35d56082c52817f9432b9a2c1142d32a631f461474188912207341

これでローカルのイメージに変更が反映され,新しいイメージのSHA256ハッシュ値が出力されます.


docker images

ローカルのDockerイメージはdocker imagesコマンドで確認することができ,以下の用に,変更したDockerイメージ(nontan18/texlive)のIMAGE IDが先程のハッシュ値になっていることが確認できます.

$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE
nontan18/texlive stable a64dcff68e35 3 minutes ago 1.48GB

実際に,docker run -it [IMAGE_NAME] [COMMAND]コマンドを用いて,更新したDockerイメージからDockerコンテナを作成すると,今度は絵文字パッケージ(fontawesome)がはじめからインストールされていることがわかります.

// コンテナを立てるとともにshを起動してコンテナ内に入る

$ docker run -it nontan18/texlive:stable sh
// fontawesomeがインストールされているか調べる
~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
~/work# exit
exit

※ただ,上記の方法でイメージを更新すると,イメージの中身が不透明になりメンテナンスが難しくなるのでdocker commitコマンドの仕様はあまり推奨しません.

そこで次にDockerイメージの元となるDockerfileを変更する方法を解説します.


Dockerfile

DockerfileはDockerイメージの作り方が書かれたファイルです.

今回使っているnontan18/texliveのDockerfileはgithubに公開されているので確認してみましょう.

// githubからローカルにレポジトリをクローン

$ git clone https://github.com/nontan18/texlive.git
// クローンしてきたディレクトリに移動
$ cd texlive
// ディレクトリ内のファイル一覧を出力
$ ls
Dockerfile README.md app docker-compose.yml sample
// Dockerfileの中身を出力
$ cat Dockerfile

今回使ったDockerイメージ(nontan18/texlive)の元となった以下のようなDockerfileが出力されるかと思います.

# Alpine Linux v3.9.4をベースのイメージとして指定

FROM alpine:3.9.4

# MAINTAINER nontan <nontan@sfc.wide.ad.jp>
LABEL maintainer="nontan@sfc.wide.ad.jp"

WORKDIR /root/work

# パッケージリストの更新
RUN apk update
# TeXLiveのインストールに必要なパッケージを取得
RUN apk add perl wget fontconfig-dev
# TeXLiveのインストーラーの解凍に必要なパッケージのインストール
RUN apk add xz tar

# 圧縮されたTeXLiveのインストーラーをダウンロードし,tmpディレクトリに保存
ADD http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz /tmp/install-tl-unx.tar.gz
# インストーラーを解凍した際に配置するディレクトリを作成
RUN mkdir /tmp/install-tl-unx
# ダウンロードしたinstall-tl-unx.tar.gzを解凍
RUN tar -xvf /tmp/install-tl-unx.tar.gz -C /tmp/install-tl-unx --strip-components=1
# TeXLiveのインストール用の設定ファイルを作成
RUN echo "selected_scheme scheme-basic" >> /tmp/install-tl-unx/texlive.profile
# TeXLiveのインストール
RUN /tmp/install-tl-unx/install-tl -profile /tmp/install-tl-unx/texlive.profile
# TeXLiveのバージョンを取得しインストールディレクトリを特定し,latestの名称でシンボリックリンクを作成
RUN TEX_LIVE_VERSION=$(/tmp/install-tl-unx/install-tl --version | tail -n +2 | awk '{print $5}'); \
ln -s "/usr/local/texlive/${TEX_LIVE_VERSION}" /usr/local/texlive/latest
# インストールしたTeXLiveへパスを通す
ENV PATH="/usr/local/texlive/latest/bin/x86_64-linuxmusl:${PATH}"

# TeXLive Package Managerを使用して必要なパッケージをインストール
# texファイルの自動コンパイルパッケージをインストール
RUN tlmgr install latexmk
# latexmkの設定ファイルをホストからイメージにコピー
COPY ./app/config/.latexmkrc /root/.latexmkrc
# 2カラムの設定に必要なパッケージのインストール
RUN tlmgr install multirow
# 日本語対応パッケージのインストール
RUN tlmgr install collection-langjapanese
# フォントパッケージのインストール
RUN tlmgr install collection-fontsrecommended
RUN tlmgr install collection-fontutils

# 不要なパッケージなどの削除(イメージの容量削減のため)
RUN apk del xz tar
RUN rm -rf /var/cache/apk/*
RUN rm -rf /tmp/*

# References
# - https://github.com/blang/latex-docker
# - https://github.com/Paperist/docker-alpine-texlive-ja/blob/master/Dockerfile

ここで,先程のようにfontawesomeをインストールしておくには,RUN tlmgr install fontawesomeを追加します.

# フォントパッケージのインストール

RUN tlmgr install collection-fontsrecommended
RUN tlmgr install collection-fontutils
# 下の行を追加
RUN tlmgr install fontawesome


docker build

DockerfileからDockerイメージをビルドするには,docker build -t [IMAGE NAME] .コマンドを用います.

$ docker build -t mylatexlive . 

Dockerイメージのビルドが終わったら,docker imagesコマンドでDockerイメージが追加されているのがわかります.

$ docker images

REPOSITORY TAG IMAGE ID CREATED SIZE
mylatexlive latest 0bbb5bed8ce1 7 minutes ago 1.45GB

当然,このDockerイメージからDockerコンテナを作成すると,fontawesomeのパッケージがインストールされていることがわかります.

// コンテナを立てるとともにshを起動してコンテナ内に入る

$ docker run -it mytexlive sh
// fontawesomeがインストールされているか調べる
~/work# tlmgr search fontawesome
fontawesome - Font containing web-related icons
~/work# exit
exit


docker-composeの基本的な使い方

ここまででDockerの基本的な使い方を学びましたが,実際にDockerを用いて環境構築を行おうと考えると,複数のコンテナで様々なDockerイメージやオプションを追加しなければならなく非効率です.

そこで,複数のコンテナを効率的に管理するためにdokcer-composeを紹介します.


docker-compose.yml

docker-composeはdocker-compose.ymlに記述したDockerコンテナの構成をdocker-compose upコマンドやdocker-compose downコマンドなどで手軽に展開できるツールです.

ここでは,上の過程でgit cloneしたdocker-compose.ymlを見てみましょう.


docker-compose.yml

version: "3.1"

services:
texlive:
build: .
# image: nontan18/texlive
volumes:
- ./sample:/root/work
command: latexmk --pvc /root/work/sample.tex

nginx:
# /root/public以下のファイルをホスティングするnginxベースのイメージ
image: nontan18/stable-file-host
ports:
# "ホストのポート:コンテナのポート"
- "8080:80"
volumes:
# 生成されるsample.pdfを公開ディレクトリにマウント
- ./sample:/root/public


上記のdocker-compose.ymlではtexliveコンテナでコンパイルされたsample.pdfファイルが入っているsampleディレクトリがnginxコンテナの/root/public/にマウントされるようになっています.nginxコンテナのイメージであるnontan18/stable-file-host/root/public/以下のファイルをホスティングしているため,nginxコンテナの80番ポートにアクセスすることができれば,ブラウザ上でsample.pdfを閲覧することができます.

ここではportsでホストの8080番ポートをnginxコンテナ内の80番ポートに転送しているので,このコンテナがたつと,ブラウザからhttp://localhost:8080/sample.pdfにアクセスすることで出力されたPDFを閲覧することが可能になります.


docker-compose up

上記のdocker-compose.ymlのあるディレクトリでdocker-compose upコマンドを実行すると下記のようにdocker-compose.ymlに記述されたすべてのコンテナが起動します.

$ docker-compose up

Starting texlive_nginx_1 ...
Starting texlive_nginx_1 ... done
Attaching to texlive_texlive_1, texlive_nginx_1

上記の通り,http://localhost:8080/sample.pdfにアクセスすると出力されたPDFが確認できるはずです.


docker-compose ps

docker-compose upした状態でdocker-compose psコマンドを実行すると,docker-compose.ymlで定義された各コンテナが確認できます.

$ docker-compose ps

Name Command State Ports
---------------------------------------------------------------------------------
texlive_nginx_1 nginx -g daemon off; Up 0.0.0.0:8080->80/tcp
texlive_texlive_1 latexmk --pvc /root/work/s ... Up


docker-compose down

docker-compose upをした状態でCtrl + Cで各コンテナを停止させることもできますが,別のターミナルのセッションからdocker-compose downを用いることで起動したすべてのDockerコンテナを停止することができます.

$ docker-compose down

docker-compose downには,マウントしたDockerボリューム(本稿では解説していませんが,ホストの特定ディレクトリをマウントする以外のコンテナのデータを永続化させる方法)をすべて削除する-vオプションや使用しているイメージを削除する--rmiオプションなどが存在します.

MySQL+Django+Angularのようなサーバークライアントモデルの開発環境を構築する際には,このdocker-composeは非常に便利です.


おわりに

最後に,今回,無造作に建てたDockerコンテナとイメージを削除してクリーンな状態に戻しましょう.

docker container rm [CONTAINER ID]コマンドやdocker image rm [IMAGE NAME]コマンドでもDockerコンテナやDockerイメージの削除は可能ですが,ここではdocker container prunedocker image pruneを紹介します.


docker container prune

docker container pruneコマンドは停止しているDockerコンテナをすべて削除するコマンドです.

docker psコマンドで起動中のコンテナを確認し,不必要なコンテナをdocker stop [CONTAINER ID]で停止させたのち,docker container pruneコマンドを実行しましょう.

$ docker container prune


docker image prune

docker container pruneと同様にdocker image pruneは起動中のコンテナに使用されていないDockerイメージを全て削除します.(なぜだかは知りませんが削除するイメージの数が多いとだいぶ長く時間がかかることがあります)

$ docker image prune

これで本稿で作成したコンテナもイメージも綺麗さっぱりホストから削除することができました.

従来の仮想化技術より軽量なコンテナ仮想化技術は,複雑な環境ごとパッケージ化したアプリケーションを気軽に展開したり削除したりできて非常に便利です.

本稿で紹介したのはDockerコマンドの一部に過ぎず,DockerはCIツールやクラスターにコンテナによるオーケストレーションを展開するkubernetes,アプリケーションコンテナの依存関係を整理しパッケージ管理するHelm,Docker環境にkubernetesクラスターを瞬時に展開しHelmを用いてアプリケーションを展開するRancherなどDockerの世界は非常に広大です.

本稿が皆様のHello Docker Worldのきっかけになれば幸いです.


注意事項

上記では何の躊躇もせずに筆者がビルドしたDockerイメージを使用していますが,信頼できない他人の作ったDockerイメージを安易に使用するのには注意が必要です.

たとえDockerfileが公開されていたとしても,Dockerレジストリに公開されているDockerイメージが悪意の無いものとは限りません.

Dockerfileを精査したのち,自身でdocker buildを行って使用することを推奨します.

特にホストのボリュームをマウントする場合や,--privilegedオプションを必要とするものには注意しましょう.


参考