9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Docker】既存イメージを使って、サイズを小さくするマルチステージビルド

Posted at

はじめに

この記事ではマルチステージビルドを使って、Dockerイメージを作成する方法を考えます。Terraformについてのイメージを使います。
本当はKubernetesでTerraformを使う方法を調べたかったのですが、イメージ作成のところで詰まったのでまとめることにしました。

記事の概要

Terraformは既存イメージがあるので、手間を省くために使います。
https://hub.docker.com/r/hashicorp/terraform/

この記事では既存リソース(Kubernetesリソース、AWS)がある状態で、新しくTerraformを使い始める場合を考えます。この場合、既存リソースをTerraformに取り込む機能があったらいいなと思います。そこで、今回はTerraformerに触ってみることにしました。

DockerからTerraformerを使う場合、自分でイメージを使う必要があります。他記事でも紹介されていますが、加えてマルチステージビルドでイメージのサイズを小さくする方法を考えます。

記事の流れとしては以下の通りです。

  • DockerにてTerraformを使う
  • Terraformerを使えるイメージを作成する
    • ベースイメージの選択
    • マルチステージビルドの実践
  • Terraformerを使ってみる
    • AWS
    • Kubernetes(失敗)

DockerにてTerraformerを使う

まず、DockerをEC2にインストールします。今までの記事とやり方は同じなので、詳細は割愛します。
1点変えたのは、newgrp dockerで再ログインせずに一時的にグループを変更しました。ただし新しいshellになるので、shファイルの以降の処理が行われなくなることに注意しました。

setup-docker.sh
#!/bin/bash -ex

cd $HOME

echo "install docker"
DOCKER_VERSION=20.10.13-2.amzn2 #yum list | grep docker
sudo yum install -y docker-$DOCKER_VERSION
sudo systemctl start docker
sudo systemctl enable docker
sudo gpasswd -a ec2-user docker
newgrp docker

Terraformerを使えるイメージを作成する

ベースとなるイメージの選択

Terraformerを使えるイメージを作成しますが、FROMで指定するベースのイメージを何にすればよいか考えます。Terraform、Terraformerを実行するためのソースファイルは必要ですが、それ以外は用途に応じて変更するとします。この記事ではイメージサイズに注目して、不要なファイルは除きできるだけ小さくできないかを考えます。以下のイメージを使うことを考えます。

イメージ名 サイズ 備考
Terraform 110MB Terraformが入ったイメージ
https://hub.docker.com/r/hashicorp/terraform/
Alpine 5.54MB 軽量なイメージとしてよく見る印象があります。
Distroless(static) 2.36MB Alpineよりも軽いイメージです。
https://console.cloud.google.com/gcr/images/distroless/GLOBAL/static
CentOS7 204MB 参考までに色々入っているであろうCentOSのサイズを調べました。
https://hub.docker.com/_/centos

サイズを見ると、Terraformのイメージは100MB以上あります。サイズを小さくするとしても100MBは超えるかもしれません。AlpineとDistrolessは2倍ほどサイズは違いますが、100MBのサイズ感で見れば、些細な差かもしれません。そのため、入っているソースファイルの差も考慮するとよいかもしれません。

マルチステージビルド

上記で挙げたイメージをベースとして、Terraformerイメージを作成します。また、必要なファイルだけ入ったイメージを作成する方法としてマルチステージビルドを使います。
マルチステージビルドでは、最終的に出来上がるイメージとは別に、ソースファイルのビルドを行うために中間的なイメージを使うことができます。これにより、出来上がるイメージから、インストール時にのみ使うファイルや使わないパッケージを除きやすくなります。
注意点としては、中間的なイメージがいくつができるので、ディスク容量が必要になることがあります。

Terraformベース(CentOS7、aws-cli経由)

それでは、TerraformイメージにTerraformerをインストールして、イメージを作成します。使うイメージの役割は以下の通りです。

イメージ名 役割 備考
hashicorp/terraform:0.13.7 Terraform、Terraformerの実行 出来上がったイメージからTerraform、Terraformerを実行します。
centos:centos7 Terraformerのインストール Terraformイメージでは、ごみが残ったり、インストール用のツールがないかもしれないので、CentOSイメージを使います。
amazon/aws-cli:2.7.21 AWSのconfig,credentialの設定 TerraformerからAWSにアクセスするために使います。AWSのアクセス用のファイルを作るだけですが、細かいミスをしないようAWS CLIを使ってみました。

Try&Errorしつつ、最終的なDockerfileは以下の通りです。

Dockerfile
#AWSの認証情報。ビルド時に指定して、Dockerfileには書かないようにしました。
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_DEFAULT_REGION
ARG AWS_DEFAULT_OUTPUT=json
#Terraform、Terraformerについて
ARG TERRAFORMER_VERSION=0.8.21
ARG PROVIDER=all
ARG TMPDIR=/tmp/terraformer
ARG HOME_DIR=/root
ARG PROVIDER_FILE=provider.tf

FROM centos:centos7 as builder
#https://github.com/GoogleCloudPlatform/terraformer#installation
ARG TERRAFORMER_VERSION
ARG PROVIDER
ARG TMPDIR
WORKDIR ${TMPDIR}
RUN curl -LO https://github.com/GoogleCloudPlatform/terraformer/releases/download/${TERRAFORMER_VERSION}/terraformer-${PROVIDER}-linux-amd64
RUN chmod +x terraformer-${PROVIDER}-linux-amd64

FROM amazon/aws-cli:2.7.21 as aws-config
#https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-configure-quickstart.html
ARG AWS_ACCESS_KEY_ID
ARG AWS_SECRET_ACCESS_KEY
ARG AWS_DEFAULT_REGION
ARG AWS_DEFAULT_OUTPUT
RUN aws configure set aws_access_key_id $AWS_ACCESS_KEY_ID
RUN aws configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY
RUN aws configure set region $AWS_DEFAULT_REGION
RUN aws configure set output $AWS_DEFAULT_OUTPUT

FROM hashicorp/terraform:0.13.7
#Terraformerがサポートしているバージョンを指定します。(https://github.com/GoogleCloudPlatform/terraformer#capabilities)
ARG PROVIDER
ARG TMPDIR
ARG HOME_DIR
ARG PROVIDER_FILE
WORKDIR $TMPDIR
#Terraformのプラグインをインストールするために/tmp/terraformerディレクトリを作成します。こうしないとエラーになりました。
WORKDIR $HOME_DIR
COPY --from=builder ${TMPDIR}/terraformer-${PROVIDER}-linux-amd64 /usr/local/bin/terraformer
#必要な実行ファイルのみコピーして持ってきています。
COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/libpthread.so.0 /lib64/libc.so.6 /lib64/
#Terraformerの実行ファイルの依存ライブラリも持ってくる必要があります。
COPY --from=aws-config $HOME_DIR/.aws $HOME_DIR/.aws
#AWSの認証情報が入ったファイルをコピーして持ってきます。
COPY $PROVIDER_FILE $HOME_DIR/
RUN ["terraform","init"]
ENTRYPOINT [""]
#もともとterraformが実行されるようになっているので、上書きしました。
provider.tf
provider "aws" { version = "4.25.0" }
provider "kubernetes" { version = "2.12.1" }

Dockerfileで行っていることは上記に記載しました。他注意点としては以下の通りです。
・Terraformイメージでどこまで処理できるか不明なため、なるべくRUNは使わず、エラーとなることを防ぎました。
・Terraformerの依存ライブラリは以下のコマンドで確認できます。エラー文だけだとわかりづらいので注意が必要でした。

[root@4dcc09333e27 terraformer]# ldd terraformer-all-linux-amd64
        linux-vdso.so.1 =>  (0x00007ffee23fc000)
        libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fe0df421000)
        libc.so.6 => /lib64/libc.so.6 (0x00007fe0df053000)
        /lib64/ld-linux-x86-64.so.2 (0x00007fe0df63d000)

それでは、ビルドしてみます。

[ec2-user@ip-10-0-0-203 ~]$ docker build \
-f $HOME/Terraform/Dockerfile . \
-t my_tfr:Terraform \
--build-arg AWS_ACCESS_KEY_ID="..." \
--build-arg AWS_SECRET_ACCESS_KEY="..." \
--build-arg AWS_DEFAULT_REGION=...
Sending build context to Docker daemon  32.77kB
Step 1/38 : ARG AWS_ACCESS_KEY_ID
・・・
Successfully built aaa1f443c6c4
Successfully tagged my_tfr:Terraform

Terraform、Terraformerが実行できそうか確認します。

[ec2-user@ip-10-0-0-203 ~]$ docker run my_tfr:Terraform terraform -version
Terraform v0.13.7
+ provider registry.terraform.io/hashicorp/aws v4.25.0
+ provider registry.terraform.io/hashicorp/kubernetes v2.12.1

Your version of Terraform is out of date! The latest version
is 1.2.7. You can update by downloading from https://www.terraform.io/downloads.html
[ec2-user@ip-10-0-0-203 ~]$ docker run my_tfr:Terraform terraformer -v
version v0.8.21

Alpineベース(CentOS7、aws-cli、Terraform経由)

次にAlpineをベースとしてイメージを作成してみます。
基本はTerraformベースと同じですが、最終的にTerraformとTerraformerを実行するのはAlpineイメージとなります。TerraformイメージはTerraformの実行ファイルを持ってくるために使います。

イメージ名 役割 備考
hashicorp/terraform:0.13.7 Terraformのインストールを省くため。 中間イメージとして、Terraformを使い、最終的なイメージにTerraformだけ持ってきます。
docker.io/library/alpine:3.16.2 Terraform、Terraformerの実行
Dockerfile
(変数の定義 省略)

FROM centos:centos7 as builder
(Terraformerのインストール 省略)

FROM amazon/aws-cli:2.7.21 as aws-config
(AWS認証情報の設定 省略)

FROM hashicorp/terraform:0.13.7 as terraform
#中間イメージとして、Terraformを使い、インストールを省く。

FROM docker.io/library/alpine:3.16.2
ARG PROVIDER
ARG TMPDIR
ARG HOME_DIR
ARG PROVIDER_FILE
WORKDIR $TMPDIR
WORKDIR $HOME_DIR
COPY --from=builder ${TMPDIR}/terraformer-${PROVIDER}-linux-amd64 /usr/local/bin/terraformer
COPY --from=builder /lib64/ld-linux-x86-64.so.2 /lib64/libpthread.so.0 /lib64/libc.so.6 /lib64/
COPY --from=aws-config $HOME_DIR/.aws $HOME_DIR/.aws
COPY --from=terraform /bin/terraform /bin/terraform
#↑ここだけ違う。Terraformの実行ファイルを持ってくる。
COPY $PROVIDER_FILE $HOME_DIR/
RUN ["terraform","init"]
ENTRYPOINT [""]

Terraformベースと同様にビルドすることができます。

Distrolessベース(CentOS7、aws-cli、Terraform経由)

最後にDistrolessイメージを使います。Alpineベースと比べて、ベースとするイメージのみ違います。他コマンドは全く同じで大丈夫です。
具体的には、

FROM docker.io/library/alpine:3.16.2

から、

FROM gcr.io/distroless/static:latest

に書き換えるだけです。最低限のコマンドしか実行していないため、ベースとなるイメージを変えてもエラーとなりづらくなるのではないかと思います。
ただ、Distrolessでは実行できないコマンドがあります。具体例として、shがあります。

[ec2-user@ip-10-0-0-203 ~]$ docker run my_tfr:Alpine sh -c id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
[ec2-user@ip-10-0-0-203 ~]$ docker run my_tfr:Distroless sh -c id
docker: Error response from daemon: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "sh": executable file not found in $PATH: unknown.
ERRO[0000] error waiting for container: context canceled

Distrolessイメージでshを使う方法として、debugタグのイメージを使う方法があります。しかし、/bin/shではなく、/busybox/shが実行されることに注意が必要です。CentOSなどで動いていたshellが動かない可能性はあります。

[ec2-user@ip-10-0-0-203 ~]$ docker run -it gcr.io/distroless/static:debug
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 /busybox/sh
    6 root      0:00 ps

出来上がったイメージのサイズを比較する

それでは色々な方法で作成したイメージのサイズを比較します。

[ec2-user@ip-10-0-0-203 ~]$ docker images | head -n 1; docker images | grep my_tfr
REPOSITORY                 TAG          IMAGE ID       CREATED          SIZE
my_tfr                     Distroless   0495e5802299   9 minutes ago    799MB
my_tfr                     Alpine       ca2316e1de8f   17 minutes ago   802MB
my_tfr                     Terraform    aaa1f443c6c4   33 minutes ago   822MB

TerraformベースがTerraformerをインストールしたことで、712MB大きくなっています。また、他ベースと比較すると、20MBほど大きくなっています。この結果が有益かは用途次第ですが、1つの基準になればと思います。

作成したTerraformerイメージを使ってみる

それでは作成したイメージを使って、AWSのtfファイルを作成してみます。

[ec2-user@ip-10-0-0-75 ~]$ docker run -v $HOME/tf-src:/root/generated/ my_tfr:Distroless terraformer import aws -r=ec2_instance,vpc
2022/08/13 04:45:13 aws importing default region
2022/08/13 04:45:13 aws importing... ec2_instance
2022/08/13 04:45:15 aws done importing ec2_instance
2022/08/13 04:45:15 aws importing... vpc
2022/08/13 04:45:15 aws done importing vpc
・・・

できたファイルを見てみます。

[ec2-user@ip-10-0-0-75 ~]$ cd tf-src/aws/ec2_instance/
[ec2-user@ip-10-0-0-75 ec2_instance]$ ls
instance.tf  outputs.tf  provider.tf  terraform.tfstate
[ec2-user@ip-10-0-0-75 ec2_instance]$ view instance.tf
・・・
resource "aws_instance" "<instance名>" {
  ami                         = "ami-02892a4ea9bfa2192"
  associate_public_ip_address = "true"
  availability_zone           = "ap-northeast-1a"

  capacity_reservation_specification {
    capacity_reservation_preference = "open"
  }

  cpu_core_count       = "2"
  cpu_threads_per_core = "1"

  credit_specification {
    cpu_credits = "standard"
  }

  disable_api_stop        = "false"
  disable_api_termination = "false"
  ebs_optimized           = "false"

  enclave_options {
    enabled = "false"
  }

  get_password_data                    = "false"
  hibernation                          = "false"
  instance_initiated_shutdown_behavior = "stop"
  instance_type                        = "t2.medium"
  ipv6_address_count                   = "0"
  key_name                             = "..."

  maintenance_options {
    auto_recovery = "default"
  }

  metadata_options {
    http_endpoint               = "enabled"
    http_put_response_hop_limit = "1"
    http_tokens                 = "optional"
    instance_metadata_tags      = "disabled"
  }

  monitoring = "false"

  private_dns_name_options {
    enable_resource_name_dns_a_record    = "false"
    enable_resource_name_dns_aaaa_record = "false"
    hostname_type                        = "ip-name"
  }

  private_ip = "10.0.0.75"

  root_block_device {
    delete_on_termination = "true"
    encrypted             = "false"
    volume_size           = "20"
    volume_type           = "gp2"
  }

  source_dest_check = "true"
  subnet_id         = "subnet-00b0aafb0a5599895"

  tags = {
    Author = "..."
    Name   = "..."
  }

  tags_all = {
    Author = "..."
    Name   = "..."
  }

  tenancy                = "default"
  vpc_security_group_ids = ["sg-0b7a74b0cbb945d6c"]
}
・・・

EC2の設定が一通り載っていました。このファイルを基にEC2を複製できそうかなと思います。
次にKubernetesで使ってみたかったのですが、うまくいきませんでした。

[ec2-user@ip-10-0-0-75 ~]$ docker run -v $HOME/tf-src:/root/generated/ my_tfr:Distroless terraformer import kubernetes list
[ec2-user@ip-10-0-0-75 ~]$ docker run -v $HOME/tf-src:/root/generated/ my_tfr:Distroless terraformer import kubernetes -r deployment
2022/08/13 05:03:42 kubernetes importing... deployment
2022/08/13 05:03:42 kubernetes error importing deployment, err: kubernetes: deployment not supported resource
2022/08/13 05:03:42 kubernetes Connecting....

原因を調べましたが、バージョン違いの問題かもしれません。ツールのそれぞれの対応バージョンは以下のページにあります。

Terraformerが対応しているTerraformバージョン(0.13)が2年近く前のものになります。それに合わせるのは難しいと感じました。以下のような別のツールを使う方がよいかもしれません。

おわりに

Dockerのマルチステージビルドはイメージの違いに左右されにくく楽だなと感じました。
今まで手動実行していたものをDockerfileにまとめるだけでなく、ツールの良さを活かせると、今までできなかったことができるようになると思いました。
これからも勉強します。

9
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?