はじめに
この記事ではマルチステージビルドを使って、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ファイルの以降の処理が行われなくなることに注意しました。
#!/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は以下の通りです。
#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 "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の実行 |
(変数の定義 省略)
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にまとめるだけでなく、ツールの良さを活かせると、今までできなかったことができるようになると思いました。
これからも勉強します。