本稿に関して動作がおかしいところや不明な点,ツッコミなどがあれば適宜コメントいただけるとありがたいです.
はじめに
大学の研究会で講義を1コマ持つことになったため,弊研究会では必須なLaTeX環境をDockerで作る方法を解説することにしました.
読者としてはDockerの知識が一切ない人を想定しています.
なお,本稿ではLaTeX自体の使い方に関してはあまり触れません.
LaTeXとは?
LateXとは,Donald E. Knuth氏が開発したレイアウトシステムのTexをLeslie Lamport氏が文書作成のために改良したものだそうです.主に学術論文を書く際に使用されており,数式を綺麗にレイアウトしてくれます.
例えば,世界一美しいとされるオイラーの等式でさえ,テキストで表現すると美しくないですが,
e^{iπ} + 1 = 0
TeXを用いてコンパイルすれば,以下のように美しくレイアウトされます.
$e^{iπ} + 1 = 0$
Dockerとは?
Dockerとはコンテナ仮想化技術のプラットフォームです.
従来の仮想化技術では,ホスト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環境を構築します.
とりあえずDockerでLaTex環境を建ててみる
DockerとSkimのインストールが完了したら,筆者が事前に用意したTeXLiveのDockerイメージ(nontan18/texlive)を用いて以下のコマンドでLaTeX環境を実際に構築してみましょう.
// 作業用のworkディレクトリを作成し移動
$ mkdir work && cd work
work
ディレクトリに入ったら下記の内容のファイルを作成します.
\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 mylatexlive 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
を見てみましょう.
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 prune
とdocker 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
オプションを必要とするものには注意しましょう.