使用现有的Docker镜像进行多阶段构建,以减小大小
首先
本篇文章介绍如何使用多阶段构建创建Docker镜像,并使用Terraform图像。开始时本该查询使用Kubernetes使用Terraform的方法,但由于在镜像创建阶段遇到了困难,所以决定总结一下。
这篇文章的要点是什么?
由于Terraform存在现有镜像,因此可以使用它来节省时间。
在这篇文章中,我们考虑的是在已经有现有资源(Kubernetes资源、AWS)的情况下,开始使用Terraform的情况。如果有一种功能可以将现有资源导入Terraform的话,那就太好了。因此,这次我们决定尝试一下Terraformer。
如果您要在Docker上使用Terraformer,您需要自行准备图像。除了其他文章中提到的方法外,我们还可以考虑使用多阶段构建来缩小图像大小。
以下是文章的流程:
-
- DockerにてTerraformを使う
-
- Terraformerを使えるイメージを作成する
ベースイメージの選択
マルチステージビルドの実践
Terraformerを使ってみる
AWS
Kubernetes(失敗)
使用Docker来使用Terraformer
首先,在EC2上安装Docker。由于之前的文章和方法相同,所以省略了详细步骤。
唯一改变的是,我使用了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 的映像,但我們需要考慮將其指定為基礎映像的選擇。我們需要有 Terraform 和 Terraformer 的執行檔,但其他部分可以根據需要進行更改。本文將著重於映像大小,試圖排除不必要的檔案,讓映像盡可能小。我們考慮使用以下映像。
https://hub.docker.com/r/hashicorp/terraform/Alpine5.54MB軽量なイメージとしてよく見る印象があります。Distroless(static)2.36MBAlpineよりも軽いイメージです。
https://console.cloud.google.com/gcr/images/distroless/GLOBAL/staticCentOS7204MB参考までに色々入っているであろうCentOSのサイズを調べました。
https://hub.docker.com/_/centos
看到尺寸时,Terraform的镜像大小超过100MB。即使尺寸缩小,可能仍会超过100MB。虽然Alpine和Distroless之间的尺寸差异约为两倍,但从100MB的尺寸感来看,可能只是微不足道的差异。因此,还考虑到所包含源文件的差异可能是明智的。
多层次建筑
基于上述提到的想法,我们将创建一个Terraformer镜像。此外,我们将使用多阶段构建的方法来创建只包含所需文件的镜像。
在多阶段构建中,除了最终生成的镜像之外,还可以使用中间镜像来进行源代码的构建。这样一来,从生成的镜像中可以更轻易地排除安装时仅使用的文件和未使用的软件包。
需要注意的是,由于会生成多个中间镜像,因此可能需要更多的磁盘容量。
基于Terraform(通过CentOS7和aws-cli)
那么,我们将在Terraform镜像上安装Terraformer,并创建镜像。要使用的镜像的作用如下所示。
在尝试和错误的过程中,最终的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
阿尔卑斯基础(通过CentOS7、aws-cli、Terraform)
接下来,我们将以Alpine为基础创建镜像。
基本上与Terraform基础相同,但最终执行Terraform和Terraformer的将是Alpine镜像。我们使用Terraform镜像来获取Terraform执行文件。
(変数の定義 省略)
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
由于安装了Terraformer,Terraform基础设施已经增大了712MB。与其他基础设施相比,它增加了约20MB。这个结果是否有益取决于具体用途,但我认为它可以作为一个标准。
使用已创建的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)已经是大约两年前的版本了。我觉得很难进行适应。也许使用类似下面这样的其他工具可能更好。
最后
Docker的多阶段构建让我觉得不容易受到镜像差异的影响,也更方便。除了将以前手动执行的内容整合到Dockerfile中,我认为还可以利用工具的优势,实现以前无法做到的事情。我将继续学习下去。