【Docker使えるようになりたい】【#12 マルチステージビルド】Dockerを使った細分化したビルドと実行の方法

マルチステージビルドの必要性

Dockerで環境を細かく分離してビルド、実行することを、マルチステージビルドと呼んでいます。

プログラムは通常、特定のOS向けにビルドされた実行ファイルが必要です。
その実行ファイルを作成するためには、対象のOS上でビルドを行う必要があります。

従来は、「開発環境」と「実行環境」を同じOS上に共存させていましたが、Dockerを使用することで、これらの環境を分離し、それぞれの環境を分離してテスト、リリースすることができます。

環境を分離することによるメリットは以下の通りです。

  • 問題発生時のトラブルシューティングが環境なのかコードなのか切り分けやすい
  • 両環境にとってそれぞれ互いに無駄なファイルを削減できるのでシンプルな環境を保てる

1つのDockerfile内で複数のイメージを使用する

以下の例のように、マルチステージビルドを行いたい場合、それぞれのイメージの定義連携までを1つのDockerfile内で行うことができます。

例 : gccのビルドと実行をマルチステージビルドで試す

今まで通りのコンテナを作成して実行

まずは、いままで通りのbuild方法を使って最終的に出力にHello Single Stageという出力をするコンテナを作成してみます。

フォルダ構造としては以下の構造を準備して行います。

DockerTest/
    - Dockerfile
    - main.c

まずは、Hello Single Stageを出力するシンプルなC言語のソースファイルmain.cを作成します。

main.c
#include <stdio.h>

int main(){
  printf("Hello Signle Stage");

  return 0;
}

Dockerfileは、以下のようにホストOS上にあるmain.cをコピーして、イメージ生成時にコンパイルをするように指定します。

そして、実行時の命令としてコンパイル後に出力されるa.outバイナリを実行することでHello Single Stageを出力するイメージを生成します。

Dockerfile
# イメージはgccを使用
FROM gcc

# ホストOS上のmain.cをイメージ内のカレントディレクトリに同名でコピー
COPY ./main.c .

# gccを使ってmain.cをコンパイル
RUN gcc ./main.c

# コンテナ実行時にコンパイルしたバイナリを実行
CMD ["./a.out"]

では、必要なファイルがそろったのでイメージのビルドを行います。

# コマンド : イメージ生成
docker image build -t single-stage-build .

# 出力
single-stage-build

正常にコマンドが終了したら、runを実行してコンテナを生成後実行します。
問題なくHello Single Stageが出力されるはずです

# 実行するコマンド
docker container run --rm single-stage-build   

# 出力
Hello Signle Stage

では、これをコンパイル用イメージと、実行イメージに分けた、マルチステージビルドに修正してみます。

マルチステージビルドを使ったバージョンに書き換え

マルチステージビルドではDockerfile内で「複数のイメージを使ってビルド」しますが、最終的に出力されるイメージは明示的に指定しない限りは「最後に使用したイメージ」が出力されます。

FROM命令から次のFROM命令までが1つのイメージとして扱われます。

Dockerfile
# コンパイル専用のImage
FROM gcc:12.2.0
# ホストOS上のmain.cをイメージ内のカレントディレクトリに同名でコピー
COPY ./main.c .
# gccを使ってmain.cをコンパイル
RUN gcc main.c

# 実行専用のImage
FROM ubuntu:20.04
# 最初のイメージで作った
COPY --from=0 /a.out .
# 実行
CMD ["./a.out"]

マルチステージビルドでは、前のビルドステージ(イメージ)で行った作業の結果を次のイメージに引き継ぐ必要があるので、それぞれのイメージ間のデータのやり取りを行うために以下のような構文を使用します。

COPY --from={index} {indexで指定したイメージ内のファイル} {現在のイメージ内のファイル}

indexの番号は0から始まります。
今回はgccイメージから引き継ぐので--from=0となっています。

また、indexだと可読性が低くなりがちなので、イメージの後に、AS 名前で名前を付けることもできます。

Dockerfile
# コンパイル専用のImage
FROM gcc:12.2.0 AS compiler
# ホストOS上のmain.cをイメージ内のカレントディレクトリに同名でコピー
COPY ./main.c .
# gccを使ってmain.cをコンパイル
RUN gcc main.c

# 実行専用のImage
FROM ubuntu:20.04
# 最初のイメージで作った
COPY --from=compiler /a.out .
# 実行
CMD ["./a.out"]

出力する文字をHello Multi Stageに変更するためにmain.cのprintf関数内の文字も変えます

main.c
#include <stdio.h>

int main(){
  printf("Hello Multi Stage");

  return 0;
}

buildと実行はいつもと同じように行うだけになります。

# イメージを生成
docker image build -t multi-stage-build .

# 実行するコマンド
docker container run --rm multi-stage-build   

# 出力
Hello Multi Stage

マルチステージビルドの利点

一番の利点は無駄な開発環境が入らないので、イメージのサイズが小さく保てることです。

REPOSITORY           TAG       IMAGE ID       CREATED             SIZE
multi-stage-build    latest    8ef8c3556e99   47 minutes ago      109MB
single-stage-build   latest    dd82766f40e7   About an hour ago   2GB

イメージを複数使うのでそれぞれのバージョンのかみ合わせが悪いとそれはそれで不具合が出る可能性があります。
開発段階では1つのイメージでまとめて開発して、本番環境や配布時になってからマルチステージビルドを使うようにしてもいいかと思います。

外部引数で生成するイメージを切り替える

Dockerfileで生成できるイメージは1つだけかつ最後に使用したイメージだけですが、生成するイメージを指定することができます。

生成するイメージを指定するオプションは、docker image buildに対して--target イメージ名で行います。

docker image build --target {イメージ名} .

以下のようにASを使って外部から指定して生成したいイメージに名前を付けておかないと--targetのイメージとして指定できないので注意してください

Dockerfile
# コンパイル
FROM gcc:12.2.0 AS compiler
COPY main.c main.c
RUN gcc main.c

# ベースイメージ
FROM ubuntu:20.04 AS base
COPY --from=compiler /a.out /a.out

# 開発引数を入れたイメージ
FROM base AS development
CMD ["./a.out","開発環境"]

# 公開時引数を入れたイメージ
FROM base AS producation
CMD ["./a.out","公開環境"]

また、上記ベースイメージのように共通の処理のをまとめて、そのベースイメージを元に派生イメージを生成させることもできます。

この場合のmain.cファイルは、以下のように引数を受け入れるようにしています。

main.c
#include <stdio.h>

int main(int argc, char *argv[]){
  printf("Hello: %s\n",argv[1]);

  return 0;
}

実行したコマンドと出力結果が以下になります。

# 開発イメージのビルド
docker image build --target development -t dev-image .
# 実行
docker container run --rm dev-image
# 出力
環境 : 開発環境

# 公開イメージのビルド
docker image build --target development -t pro-image .
# 実行
docker container run --rm pro-image
# 出力
環境 : 公開環境

さいごに

Dockerfileを複数用意すれば同じようなことができることですが、メンテナンスなどを考えると一つにまとめる努力をするのはいいことだと思いました。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA