本节做一个实战的练习,使用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工具生成服务端和客户端证书:

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

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

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

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

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
debug = false
root = "/var/lib/buildkit"

[grpc]
  address = [ "unix:///run/user/997/buildkit/buildkitd.sock", "tcp://192.168.100.61:9090" ]
  [grpc.tls]
    cert = "/usr/local/buildkit/ssl/server.crt"
    key = "/usr/local/buildkit/ssl/server.key"
    ca = "/usr/local/buildkit/ssl/ca.crt"

[worker.oci]
  enabled = true
  snapshotter = "auto"
  rootless = true # see docs/rootless.md for the details on rootless mode.
  # Whether run subprocesses in main pid namespace or not, this is useful for
  # running rootless buildkit inside a container.
  noProcessSandbox = false
  binary = "/usr/local/buildkit/bin/buildkit-runc"
  gc = true
  gckeepstorage = 9000

[worker.containerd]
  address = "/run/containerd/containerd.sock"
  enabled = false
  namespace = "buildkit"
  gc = true
  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用户映射下级用户和组:

1
usermod --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
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
[Unit]
Description=BuildKit
After=network.target

[Service]
User=buildkit
ExecStart=/usr/local/buildkit/bin/rootlesskit \
  /usr/local/buildkit/bin/buildkitd \
  --config=/usr/local/buildkit/config/buildkitd.toml

[Install]
WantedBy=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。

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

1
2
3
buildctl --addr=unix:///run/user/997/buildkit/buildkitd.sock debug workers
ID				PLATFORMS
mcr24g17vg9of9zhv6ob0jw8m	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
2
3
4
├── buildctl
├── ca.crt
├── client.crt
└── client.key

编写一个最简单的Dockerfile:

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

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

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

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

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

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

总结

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

参考