Docker を使ってパッケージング

これは NSEG Advent Calendar 2015 -Adventar 11 日目の記事です。

NSEG とは?

一言で言うと「長野の IT 勉強会」ということになりますが、勉強会と言ってもお堅いものではなく、長野県やコンピュータにかかわることを幅広く取り上げて、楽しんだり技術の向上を目指す、といった感じで活動しております。詳しくは nseg.jp もご覧ください。

これは何?

先日の「理論から学ぶデータベース実践入門」読書会スペシャルの懇親会で、「 Docker 周辺が盛り上がっているけど、実際業務に使ってたりする?」ということを聞かれたので、自分は「独自で RPM なんかのパッケージを作る時に便利に活用してますよ」ということを話したのですが、じゃあ具体的にどんなことをしているのか、というのを公開している h2o-rpm を元に書いてみようかな、という趣旨です。

Docker 自体はウェブアプリケーションなどの展開・運用にそのまま便利に使え、そちらが本筋とも思いますが、こんな使い方もできる、という一例にしてもらえれば幸いです。なお RPM の SPEC の書き方については、今回は省略します。

パッケージング作業の概要

そもそも RPM などのパッケージを作る時は、パッケージ対象のソースコードと関連ファイル、 SPEC ファイルのようなパッケージング手順が書かれたファイルなどが必要なわけですが、もう一つ、パッケージを作成する対象のディストリビューションのクリーンな環境が必要です。しかしながらこれを毎回物理サーバやいわゆる仮想マシン上でやっていると、メンテナンスが大変、起動に時間がかかる、など作業上のストレスがあっという間にたまることになるので、気軽に作成・削除のできる Docker でその部分をやろう、ということになります。かつての boot2docker や、最近では Docker Machine のおかげで、自分のマシン上でもお手軽に作業ができるのも利点です。

その場合のおおまかなパッケージ作成の流れは、

  1. パッケージ作成対象のディストリビューションの環境を整える
  2. パッケージ作成に必要なソースファイルなどを Docker コンテナに展開
  3. ビルド、パッケージングを行う
  4. できたパッケージを Docker コンテナから取り出す

となりますが、 h2o-rpm ではこれを DockerfileMakefile で記述して、

  1. Dockerfile を使ったイメージビルドを利用して、環境準備、ソース展開、ビルド、パッケージング
  2. できあがったイメージからデータ取り出しのためだけのダミーコンテナを作ってパッケージを取り出し

という形に落とし込んで、 make 一つで自動でパッケージングできるようにしています。パッケージングまでの部分は docker run でやるのも一つなのですが、イメージビルドを使うことにしたのは、

  • docker run だと、ローカルファイルシステム・コンテナ間でデータの受け渡し方法を用意するのが面倒
  • 結局自動処理のためにシェルスクリプトを書くことになるのなら、 Dockerfile の方がシンプルで見通しがよい
  • Dockerfile の各ステップの中間イメージが残っていくため、失敗した時の作業再開や調査がやりやすい

という理由によるものです。

以下、リポジトリの Dockerfile のうち Dockerfile.centos7Makefile を元に、この流れを見ていきます。

ビルド環境の準備

まず、ビルド環境の準備は Dockerfile に記述してある

FROM centos:7
ENV HOME /
RUN yum update -y
RUN yum install -y rpm-build redhat-rpm-config rpmdevtools cmake gcc-c++ tar make openssl-devel ruby bison
RUN rpmdev-setuptree
RUN echo '%dist   .el7' >> /.rpmmacros

で行っています。 centos7 の公式イメージを元に、環境内のパッケージの全アップデート、ビルド作業に必要なコンパイラなどのパッケージやビルド対象と依存関係にあるパッケージのインストール、ビルド用ディレクトリツリーの作成などを順に実施しています。

ENV HOME / については、 RPM のパッケージング処理がホームディレクトリ配下の rpmbuild ディレクトリを使う、という方式なのと、一般的なコンテナ内ではトップディレクトリが起点になっているというのを合わせる意味で指定しています。イメージ環境自体は使い捨てなので、通常やらないだろう設定をやってもいっこうに構いません。

ソース展開

ソースの展開は Makefile

cp Dockerfile.$* Dockerfile
tar -czf - Dockerfile rpmbuild | docker build -t $(IMAGE_NAME) -

Dockerfile

ADD ./rpmbuild/ /rpmbuild/

の共同作業になります。

まず Makefile の方ですが、各ディストリビューション用に Dockerfile.{dist} のような形でを用意しているものを Dockerfile にコピーした後、パッケージ用ファイルを配置してある rpmbuild ディレクトリとともに tar.gz 形式にまとめ、それを docker buildSTDIN 経由で渡しています。このように渡されたファイルは、 tar.gz 内のディレクトリツリー構造そのままに DockerfileADDCOPY で参照できるので、これを使ってコンテナ内の /rpmbuild 以下にファイルを展開しています。

この辺りについては Docker の build コマンドのリファレンスDockerfile のリファレンス に詳しいです。

また、最初にこの方式で書いた時期とは異なり、現在では .dockerignoredocker build-f オプションも使えますので、上記のような形ではなく、これらの機能を使うことでも同様のものを実現可能と思います。

ビルドとパッケージング

ここは Dockerfile 内の記述になりますが、

RUN rpmbuild -ba /rpmbuild/SPECS/h2o.spec
RUN tar -czf /tmp/h2o.tar.gz -C /rpmbuild RPMS SRPMS
CMD ["/bin/true"]

という形で、 RPM のビルド処理を行い、できあがったものを /tmp 以下に tar.gz ファイルとしてまとめています。このアーカイブファイルは次の取り出しで参照します。

最後の CMD 行は、イメージから作成したコンテナを即時終了させるために便宜的に設定したものとなります。

パッケージの取り出し

ここは Makefile 側の処理になりますが、前のステップまででできあがったイメージを参照し、

docker run --name $(IMAGE_NAME)-tmp $(IMAGE_NAME)
mkdir -p tmp
docker wait $(IMAGE_NAME)-tmp
docker cp $(IMAGE_NAME)-tmp:/tmp/$(TARGZ_FILE) tmp
docker rm $(IMAGE_NAME)-tmp

のコマンドを順に処理して、 Dockerfile で生成された tar.gz ファイルを取り出しています。

まず docker run で、 Dockerfile で作成したイメージを元にダミーコンテナを起動しています。 Dockerfile 内で指定してある CMD ["/bin/true"] により、このコンテナの実行は即座に終わります。欲しいのはダミーコンテナ内のファイルなので、コンテナが起動している必要はありません。念のため docker wait を入れて、コピー処理の前にコンテナが停止しているのを確実にしています。

ダミーコンテナが停止した後は docker cp を使い、コンテナ内のファイルをローカルのファイルシステムにコピーし、用が済んだダミーコンテナを docker rm で削除しています。

欲しいファイルを取り出せたので、後は好きにこれを展開すればいいのですが、本リポジトリではこれを {dist}.build のようなディレクトリ配下に展開しています。

まとめ

以上のような形で自動パッケージングを行い、ローカル側にコピーしてきているわけですが、もちろんできたものを直接特定のサーバにアップロードするなども自由自在です。対象もパッケージングに限らず、 Docker 環境中でなにかしらの成果物を作り、取り出す、という方法として一般的に使える、と思います。

また、ここでは単に好みから Makefile を使っていますが、シェルスクリプトやバッチファイルでも構いません。かつては tar.gz などのアーカイブファイルを渡さなければいけない以上、 Windows で実行するのは若干難しかったように思いますが、今では前述の .dockerignoredocker build-f オプションもあるので、敷居は下がったのではないかと思います。

さらに Windows Server 2016 では Docker がサポートされることも発表されていますので、 Windows アプリケーションも同じような手法でパッケージングできたりするのかもしれず、今後が楽しみです