DockerでCLIツールのイメージを作ってみる
はじめに
dockerはあまりつかったことなかったんですがECSを使ってみたいなと考えてたのと ビルドするのに時間がかかるツールなどをdockerイメージにしておくと手軽に色々なところから使えていいかなと思いやってみました。
Docker覚え書き
インストール
https://docs.docker.com/install/linux/docker-ce/ubuntu/
docker-ceというのを入れるようです。
コマンド
build
Dockerfileからイメージを作成するrun
イメージからコンテナを立ち上げるimages
イメージの情報を表示するsystem df
ディスクの使用状況system prune
未使用データの削除rm
コンテナを削除するrmi
イメージを削除するtag
イメージに別の名前をつけるpull
イメージをdockerhubなどから取得するpush
イメージをdockerhubなどにアップロードする
Dockerfile
FROM
ベースのイメージ。ASでステージに名前をつけて後のステージから参照できる。COPY
ファイルやディレクトリをホストまたは別のステージからコピーする。 コピーされるファイルの変更検知するようにして以降のビルドをやり直すなどキャッシュの制御にも使えるようです。ADD
コピーと似ていてファイルやディレクトリを追加する。URLを指定することもできる。RUN
色々なコマンドを実行する。ENV
環境変数を変更する。パスの追加など。ENTRYPOINT
runしたときに実行されるコマンドのようです。上書きするにはオプションで--entrypoint command
のように指定する必要があります。CMD
runしたときに実行されるコマンドのようです。ENTRYPOINTと似ていますが少し違いがあるようです。 run実行時にコマンドにオプションや引数を渡すにはコマンドを省略せずに書く必要があります。
haskellプログラムのdockerビルド
イメージを作りたいプログラムというのはhaskellで書いたものでした。
色々調べていてhaskellプログラムのdockerビルドについていくつか知見が得られたため書いておきます。
ベースイメージ
https://hub.docker.com/_/haskell/
haskellのイメージがありました。ただ少し古いようでした。
https://hub.docker.com/r/fpco/stack-build/
もう一つstack-buildというイメージもあったんですがこちらは3GBぐらいあってちょっと大きすぎる感じがしました。
https://github.com/freebroccolo/docker-haskell
docker-haskellのgithubを見ると最新にするプルリクが出ていてこれを参考にDockerfileを書けばイメージを作れそうでした。 下のDockerfileでイメージがつくれました。
## Dockerfile for a haskell environment FROM debian:stretch ## ensure locale is set during build ENV LANG C.UTF-8 RUN apt-get update && apt-get install -y --no-install-recommends gnupg dirmngr && \ echo 'deb http://ppa.launchpad.net/hvr/ghc/ubuntu xenial main' > /etc/apt/sources.list.d/ghc.list && \ apt-key adv --keyserver keyserver.ubuntu.com --recv-keys F6F88286 && \ apt-get update && \ apt-get install -y --no-install-recommends cabal-install-2.0 ghc-8.2.2 happy-1.19.5 alex-3.1.7 \ zlib1g-dev libtinfo-dev libsqlite3-0 libsqlite3-dev ca-certificates g++ git curl && \ curl -fSL https://github.com/commercialhaskell/stack/releases/download/v1.6.1/stack-1.6.1-linux-x86_64-static.tar.gz -o stack.tar.gz && \ curl -fSL https://github.com/commercialhaskell/stack/releases/download/v1.6.1/stack-1.6.1-linux-x86_64-static.tar.gz.asc -o stack.tar.gz.asc && \ apt-get purge -y --auto-remove curl && \ export GNUPGHOME="$(mktemp -d)" && \ gpg --keyserver hkps://hkps.pool.sks-keyservers.net --recv-keys C5705533DA4F78D8664B5DC0575159689BEFB442 && \ gpg --batch --verify stack.tar.gz.asc stack.tar.gz && \ tar -xf stack.tar.gz -C /usr/local/bin --strip-components=1 && \ /usr/local/bin/stack config set system-ghc --global true && \ rm -rf "$GNUPGHOME" /var/lib/apt/lists/* /stack.tar.gz.asc /stack.tar.gz ENV PATH /root/.cabal/bin:/root/.local/bin:/opt/cabal/2.0/bin:/opt/ghc/8.2.2/bin:/opt/happy/1.19.5/bin:/opt/alex/3.1.7/bin:$PATH ## run ghci by default unless a command is specified CMD ["ghci"]
https://hub.docker.com/r/tkaaad97/haskell/
dockerhubにもpushしてみました。
indexファイルのダウンロードが遅い
stackが依存ライブラリの解決するときにindexファイルというのをダウンロードするようなんですが、
https://s3.amazonaws.com/hackage.fpcomplete.com/
から取得していて非常に時間がかかることがありました。
https://github.com/commercialhaskell/stack/issues/2240 https://github.com/commercialhaskell/stack/issues/3088
いくつかissueにもなっていました。s3からのダウンロードではなくCloudFrontなどCDNを使ったらどうかという話が上がっていました。
stackの設定でstackageやhackageのサイトから取得するようにしたら速くなったというコメントも書かれていたので真似してみました。
Dockerfileに下のように書いて設定を変更してみました。
RUN printf "\npackage-indices:\n- name: Stackage\n download-prefix: https://www.stackage.org/lts-10.4/package/\n http: https://www.stackage.org/lts-10.4/00-index.tar.gz\n- name: HackageOrig\n download-prefix: https://hackage.haskell.org/package/\n http: https://hackage.haskell.org/00-index.tar.gz" >> ~/.stack/config.yaml
下の内容がconfig.yamlに追加されてここからindex取得するようになります。
package-indices: - name: Stackage download-prefix: https://www.stackage.org/lts-10.4/package/ http: https://www.stackage.org/lts-10.4/00-index.tar.gz - name: HackageOrig download-prefix: https://hackage.haskell.org/package/ http: https://hackage.haskell.org/00-index.tar.gz
依存ライブラリのビルドをキャッシュしたい
プログラム開発途中などビルドが失敗することはよくあります。 しかしビルド時に依存ライブラリのビルドも全て毎回実行されると非常に時間がかかってしまいます。 依存ライブラリのビルドは対象プログラムのビルドとは分けてキャッシュして必要があれば更新したいです。
https://github.com/freebroccolo/docker-haskell/issues/54
こちらによさそうなやり方が書かれていました。
まず依存ライブラリのビルドを分けて行うためcabalファイルとstack.yamlのみをCOPYします。
これはソース全体をコピーしてしまうとソースコードに少しでも変更があった場合にキャッシュが使われなくなってしまうためです。
それからinstall --only-dependencies
で依存ライブラリのみビルドします。
次にソースコード全体をCOPYしてビルドを行います。
これで依存ライブラリビルドにキャッシュが使われるようになりました。
staticビルド
実行用イメージのサイズを小さくするには必要なライブラリのみstaticリンクして実行ファイルに含めてしまうのがいいと思います。 ただstaticリンクにするというのは思ったより難しくて色々と試行錯誤されているようです。
https://www.fpcomplete.com/blog/2016/10/static-compilation-with-stack https://vadosware.io/post/static-binaries-for-haskell-a-convoluted-approach/
あまりよくわかってませんがcabalファイルにld-options: static
を追加するのがいいようです。
pandocビルド
haskellのCLIツールとしてはpandocが有名だと思います。 pandocのdockerビルドをしている方が他にもいたため自分でもやってみました。
pandocのcabalファイルを見るとstaticフラグとembed_data_filesフラグを有効にすると実行ファイルだけで動かせそうでした。
FROM haskell:8.2.2 RUN stack --system-ghc --resolver lts-10.4 --local-bin-path /sbin install pandoc pandoc-citeproc --ghc-options '-fPIC' --flag pandoc:static --flag pandoc:embed_data_files FROM alpine:latest COPY --from=0 /sbin/pandoc /sbin/ COPY --from=0 /sbin/pandoc-citeproc /sbin/ ENTRYPOINT ["pandoc"]
https://hub.docker.com/r/tkaaad97/pandoc/
ビルドしたイメージです。 簡単なコマンド実行してみたところは動作しているようでした。
作成したイメージ
https://github.com/bigsleep/ImagePacker
今回イメージ作成したプログラムはこちらです。
これは画像ファイルを読み込んでパッキングしてまとめた画像を出力するというプログラムです。
マルチディスタンスフィールドでのフォント描画の記事でもフォント画像を一ファイルにまとめるのに使いました。
以下のDockerfileでビルドすることができました。
FROM haskell:8.2.2 AS fetch-source ADD https://api.github.com/repos/bigsleep/ImagePacker/branches/master /repository-hash RUN git clone --depth 1 -b master https://github.com/bigsleep/ImagePacker /source FROM haskell:8.2.2 AS build RUN printf "\npackage-indices:\n- name: Stackage\n download-prefix: https://www.stackage.org/lts-10.4/package/\n http: https://www.stackage.org/lts-10.4/00-index.tar.gz\n- name: HackageOrig\n download-prefix: https://hackage.haskell.org/package/\n http: https://hackage.haskell.org/00-index.tar.gz" >> ~/.stack/config.yaml RUN stack update RUN mkdir /work COPY --from=fetch-source /source/ImagePacker.cabal /work/ COPY --from=fetch-source /source/stack.yaml /work/ RUN cd /work && stack --system-ghc --resolver lts-10.4 --local-bin-path /sbin install --only-dependencies COPY --from=fetch-source /source /work RUN cd /work && stack --system-ghc --resolver lts-10.4 --local-bin-path /sbin install --flag ImagePacker:static FROM alpine:latest COPY --from=build /sbin/ImagePacker /sbin/ COPY --from=build /sbin/ImageGen /sbin/ ENTRYPOINT ["ImagePacker"]
https://hub.docker.com/r/tkaaad97/imagepacker/
まとめ
dockerコマンドとDockerfileの書き方を学びました。 haskellプログラムのdockerビルドについて学びました。 自作プログラムのdockerイメージを作ってdockerhubに上げることができました。