本节做一个实战的练习,使用buildkit完成容器镜像的远程构建,即实现本地的buildctl客户端远程请求服务器上的buildkitd完成一个容器镜像的构建并推送到镜像仓库中。

部署buildkitd服务端, 暴露为TCP服务

下面将在服务器上以二进制的形式部署服务端buildkitd,并将其gRPC API以TCP服务的形式暴露出来。 此处在部署buildkitd的时候是以具备生产环境可用为目的,为了安全性会以rootless的形式启动buildkitd,同时为gRPC Server和Client生成TLS证书,开启TLS认证以保障服务的安全。

服务端的环境信息如下:

  • CentOS 7.9 (linux 3.10.0-1160)
  • 本地局域网IP地址: 192.168.100.61

为了以rootless模式运行buildkitd,需要先下载并安装rootlesskit。 然后创建非特权用户如buildkit,之后使用cfssl或其他ssl工具生成服务端和客户端证书:

1ca.crt
2client.crt
3client.key
4server.crt
5server.key

证书生成成功后,就可以下载和安装buildkit相关的二进制文件,并创建buildkitd的配置文件buildkitd.toml。 这里把buildkit安装到/urs/local/buildkit下,前面的各个步骤执行完成后,目录结构如下:

 1/usr/local/buildkit/
 2├── bin
 3│   ├── buildctl
 4│   ├── buildkitd
 5│   ├── buildkit-qemu-aarch64
 6│   ├── buildkit-qemu-arm
 7│   ├── buildkit-qemu-i386
 8│   ├── buildkit-qemu-ppc64le
 9│   ├── buildkit-qemu-riscv64
10│   ├── buildkit-qemu-s390x
11│   ├── buildkit-runc
12│   └── rootlesskit
13├── config
14│   └── buildkitd.toml
15└── ssl
16    ├── ca.crt
17    ├── client.crt
18    ├── client.key
19    ├── server.crt
20    └── server.key

配置文件buildkitd.toml的内容如下:

 1debug = false
 2root = "/var/lib/buildkit"
 3
 4[grpc]
 5  address = [ "unix:///run/user/997/buildkit/buildkitd.sock", "tcp://192.168.100.61:9090" ]
 6  [grpc.tls]
 7    cert = "/usr/local/buildkit/ssl/server.crt"
 8    key = "/usr/local/buildkit/ssl/server.key"
 9    ca = "/usr/local/buildkit/ssl/ca.crt"
10
11[worker.oci]
12  enabled = true
13  snapshotter = "auto"
14  rootless = true # see docs/rootless.md for the details on rootless mode.
15  # Whether run subprocesses in main pid namespace or not, this is useful for
16  # running rootless buildkit inside a container.
17  noProcessSandbox = false
18  binary = "/usr/local/buildkit/bin/buildkit-runc"
19  gc = true
20  gckeepstorage = 9000
21
22[worker.containerd]
23  address = "/run/containerd/containerd.sock"
24  enabled = false
25  namespace = "buildkit"
26  gc = true
27  gckeepstorage = 9000

这个配置文件还有以下内容需要我们关注一下:

  • 可以看出buildkitd的gRPC API在unix:///run/user/997/buildkit/buildkitd.socktcp://192.168.100.61:9090上可用。 其中/run/user/997/buildkit/buildkitd.sock997是前面创建的buildkit用户的uid。
  • root = "/var/lib/buildkit"指定是buildkitd的数据目录
  • buildkitd的worker可以是containerd,也可以是oci,因为这里需要以rootless模式启动,rootless模式目前还不支持使用containerd作为worker,所以这里将worker containerd停用
  • snapshotter = "auto"将snapshotter指定为auto,这是因为在rootless模式下,buildkit还不支持overlayfs,这里设置为auto,snapshotter会自动切换到native

为了以非特权用户启动buldkitd服务,还需要一些配置,具体可参考Rootless mode ,这里以Cent OS 7.9为例。 需要添加user.max_user_namespaces=28633/etc/sysctl.conf(或/etc/sysctl.d下的buildkit.conf),然后运行sysctl -p。 接下来为buildkit用户映射下级用户和组:

1usermod --add-subuids 100000-165535 --add-subgids 100000-165535

上面的命令会往/etc/subuid/etc/subgid中添加buildkit:100000:65536,为buildkit用户分配了从属uid的范围从100000到165535。 uid 100000将会以uid 0(root)映射到名称空间内(例如在容器中时),uid 100001将会以uid 1映射到名称空间,以此类推。也就是说如果某个进程尝试提权的话,如到uid 0(root),实际上只会作为主机上非特权的大编号uid,如此处为100000,大编号的uid甚至在主机上映射不到真实的用户,也就是说该进程不会在主机上取得特权,这样就起到了安全的作用。

接下来就编写如下的systemd unit文件buildkit.service:

 1[Unit]
 2Description=BuildKit
 3After=network.target
 4
 5[Service]
 6User=buildkit
 7ExecStart=/usr/local/buildkit/bin/rootlesskit \
 8  /usr/local/buildkit/bin/buildkitd \
 9  --config=/usr/local/buildkit/config/buildkitd.toml
10
11[Install]
12WantedBy=multi-user.target

部署完成后systemctl start buildkit启动buildkitd,systemctl status buildkit会提示running server on /run/user/997/buildkit/buildkitd.sockrunning server on [::]:9090,前者本机buildctl客户端使用没有启用TLS,后者暴露给其他机器上buildctl客户端使用启用了TLS。

下面本机验证一下部署是否成功:

1buildctl --addr=unix:///run/user/997/buildkit/buildkitd.sock debug workers
2ID				PLATFORMS
3mcr24g17vg9of9zhv6ob0jw8m	linux/amd64,linux/386

如果正常打印出workers且没有报错,则buildkitd部署成功。 ps -ef | grep buildkitd查看一下,buildkitd确实由非特权用户buildkit启动。

buildctl远程调用buildkitd构建镜像

下面使用buildctl远程调用服务器上的buildkitd构建镜像。这里在我个人的macbook上下载mac os版本的buildctl,并把服务器端生成的证书ca.crt, client.crt, client.key拷贝到本地:

1├── buildctl
2├── ca.crt
3├── client.crt
4└── client.key

编写一个最简单的Dockerfile:

1mkdir /tmp/myproject
2echo "FROM alpine" > /tmp/myproject/Dockerfile

使用下面的命令构建镜像:

1./buildctl --addr=tcp://192.168.100.61:9090 \
2  --tlscacert=./ca.crt \
3	--tlscert=./client.crt \
4	--tlskey=./client.key \
5  build   \
6  --frontend dockerfile.v0  \
7  --local context=/tmp/myproject   \
8  --local dockerfile=/tmp/myproject \
9  --output type=image,name=harbor.myorg.com/myproject/myimg:1.0,push=true

上面的命令执行后,当镜像构建成功后将会自动推送镜像到镜像仓库中。 需要注意如果镜像仓库是私有仓库且开启了认证的话,需要在buildctl的执行用户的~/.docker目录下创建config.json:

1{
2	"auths": {
3		"harbor.myorg.com": {
4			"auth": "base64(username:password)"
5		}
6	}
7}

因为目前buildctl默认读取这个config.json里的认证信息登录到私有镜像仓库。 这个在https://github.com/moby/buildkit/issues/565这个issue中有讨论。

总结

本节我们以二进制的形式rootless的模式部署了buildkitd,并使用buildctl远程调用buildkitd完成了一个容器镜像的构建,在下一节中我们将介绍在k8s集群中以容器的形式部署buildkitd。

参考