1. 为什么需要BuildKit?从Docker到Containerd的构建之路
如果你和我一样,从Docker转向Containerd,遇到的第一个头疼问题就是:我的镜像怎么构建?在Docker时代,一句 docker build -t myapp . 就能搞定一切,但切换到Containerd后,你会发现熟悉的 ctr 命令根本没有 build 这个子命令。我第一次尝试时,系统直接给我泼了盆冷水,提示 buildctl 找不到。这其实是因为Containerd的设计哲学是“做一件事,并做好它”——它专注于容器运行时和镜像管理,而把镜像构建这个功能剥离了出去,交给了更专业的工具。
这就是BuildKit登场的时候了。你可以把它理解为Docker引擎里那个负责构建镜像的“大脑”被独立拆分出来了。它由Docker公司(现在是Mirantis)开源并维护,是目前Containerd生态下构建镜像的事实标准。我实测下来,它的构建速度比传统Docker构建要快不少,尤其是在处理复杂的多阶段构建和大量依赖层时,缓存机制非常高效。
那么,这套组合拳是怎么打的呢?简单来说,我们通常会用 nerdctl 这个命令行工具(它长得和 docker 命令几乎一模一样)来发起构建请求,nerdctl 背后会去调用 buildctl 客户端,buildctl 再与后台守护进程 buildkitd 通信,而 buildkitd 最终会利用Containerd作为“工人”(worker)来实际执行构建任务,并把构建好的镜像直接存到Containerd的镜像仓库里。整个过程清晰、高效,并且完全兼容现有的Dockerfile,你之前写的那些构建脚本几乎不用改就能跑起来。
2. 从零开始:BuildKit的安装与系统服务配置
好了,理论说再多不如动手做一遍。下面我就带你一步步把BuildKit装起来,并配置成系统服务,让它开机自启,稳定运行。
2.1 下载与安装二进制文件
首先,我们需要去BuildKit的GitHub发布页下载最新的稳定版。我习惯用 wget 直接拉取,这里以 v0.12.0 版本为例,你可以随时替换成更新的版本。
# 下载BuildKit压缩包
wget https://github.com/moby/buildkit/releases/download/v0.12.0/buildkit-v0.12.0.linux-amd64.tar.gz
# 解压到 /usr/local 目录,这是存放本地编译软件的标准位置
sudo tar -xzf buildkit-v0.12.0.linux-amd64.tar.gz -C /usr/local/
解压后,你会发现在 /usr/local/bin/ 目录下多了好几个可执行文件,其中最关键的两个是:
buildkitd:这是构建服务的守护进程,以后台方式运行,负责接收和处理构建任务。buildctl:这是命令行客户端,我们(或nerdctl)通过它来向buildkitd发送构建指令。
为了让系统在任何位置都能找到这些命令,通常它们已经在 /usr/local/bin 下了,而这个目录默认就在系统的 PATH 环境变量里。你可以用 which buildctl 检查一下,如果能看到路径,就说明安装成功了。
2.2 配置Systemd服务(让BuildKit在后台稳定运行)
让buildkitd在终端里直接跑不是长久之计,终端一关服务就停了。生产环境我们肯定要用systemd把它管起来。这样能实现开机自启、自动重启、集中日志管理等一系列好处。
创建一个service文件:
sudo vim /etc/systemd/system/buildkit.service
把下面的配置内容贴进去。这里有个关键点:我们通过 --oci-worker=false --containerd-worker=true 参数,明确告诉BuildKit使用Containerd作为后端工作器,而不是默认的runc。这样做的好处是构建出来的镜像会直接存入Containerd,省去了导入导出的步骤,效率更高。
[Unit]
Description=BuildKit
Documentation=https://github.com/moby/buildkit
After=network.target containerd.service
Wants=containerd.service
[Service]
Type=notify
ExecStart=/usr/local/bin/buildkitd --oci-worker=false --containerd-worker=true
Restart=always
RestartSec=5
# 安全相关设置,根据你的环境调整
User=root
Group=root
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_FOWNER CAP_FSETID CAP_KILL CAP_SETGID CAP_SETUID CAP_SYS_CHROOT
NoNewPrivileges=true
LimitNPROC=infinity
LimitCORE=infinity
LimitNOFILE=1048576
TasksMax=infinity
[Install]
WantedBy=multi-user.target
配置写好之后,就是标准的systemd操作流程了:
# 重新加载systemd配置,让它识别新的service文件
sudo systemctl daemon-reload
# 设置开机自启
sudo systemctl enable buildkit
# 立即启动服务
sudo systemctl start buildkit
# 查看服务状态,确认是否正常运行
sudo systemctl status buildkit
如果一切顺利,你应该能看到绿色的“active (running)”状态。这时候,一个专为Containerd服务的构建引擎就已经在后台默默待命了。
3. 构建利器nerdctl的安装与初体验
有了强大的BuildKit引擎,我们还需要一个顺手的“方向盘”来驾驶它。虽然可以直接用 buildctl 命令,但它的参数比较复杂,对习惯了Docker的用户不够友好。nerdctl 就是为了解决这个问题而生的,它是Containerd生态下的“Docker CLI”,命令语法和 docker 高度兼容,用起来几乎没有学习成本。
3.1 安装nerdctl
同样,我们去它的GitHub发布页下载。它提供两种包:“精简版”只包含 nerdctl 命令本身;“完整版”则包含了 buildctl、containerd、runc 等一堆依赖。既然我们已经单独装了BuildKit,这里下载精简版就足够了。
# 下载精简版nerdctl
wget https://github.com/containerd/nerdctl/releases/download/v1.5.0/nerdctl-1.5.0-linux-amd64.tar.gz
# 解压出可执行文件
sudo tar -xzf nerdctl-1.5.0-linux-amd64.tar.gz -C /usr/local/bin nerdctl
解压后,nerdctl 命令就已经在 /usr/local/bin 下了。你可以通过 nerdctl --version 来验证安装。
3.2 配置命名空间与命令补全
这里有个Containerd和Docker的重要区别:命名空间(Namespace)。Containerd用命名空间来隔离不同的镜像和容器集合。Kubernetes默认使用 k8s.io 这个命名空间。为了让 nerdctl 默认操作Kubernetes能看到的镜像,我们最好配置一下。
创建一个配置文件:
sudo mkdir -p /etc/nerdctl
sudo vim /etc/nerdctl/nerdctl.toml
加入以下内容,指定默认命名空间:
namespace = "k8s.io"
接下来,给 nerdctl 加上命令补全功能,这样敲命令时按Tab键就能提示,效率倍增。对于bash用户,可以这样设置:
# 生成bash补全脚本并应用到当前shell
sudo nerdctl completion bash > /etc/bash_completion.d/nerdctl
# 或者,你也可以把它加到你的个人bash配置里
echo 'source <(nerdctl completion bash)' >> ~/.bashrc
source ~/.bashrc
现在,你可以试试 nerdctl images 或者 nerdctl ps,看看是不是和 docker 命令的输出格式一模一样?它会列出 k8s.io 命名空间下的镜像和容器。如果之前用 crictl 或 crictl 拉取过镜像,这里就能看到了。
4. 实战:构建你的第一个Containerd镜像
环境都准备好了,手开始痒了吧?让我们实际构建一个镜像。为了演示从下载到构建、打标、运行的完整流程,我们以一个简单的Nginx镜像为例。
4.1 准备构建上下文与Dockerfile
首先,创建一个项目目录并编写Dockerfile:
mkdir ~/nginx-build && cd ~/nginx-build
vim Dockerfile
Dockerfile内容如下,它做了几件事:基于Alpine这个超小的Linux镜像,安装Nginx,复制一个自定义的首页,并暴露端口。
# 使用轻量级的Alpine Linux作为基础镜像
FROM alpine:3.18
# 安装nginx
RUN apk add --no-cache nginx
# 创建一个简单的自定义首页
RUN echo "<html><body><h1>Hello from Containerd + BuildKit!</h1></body></html>" > /var/www/localhost/htdocs/index.html
# 暴露80端口
EXPOSE 80
# 以前台方式启动nginx
CMD ["nginx", "-g", "daemon off;"]
4.2 使用nerdctl进行构建
构建命令和 docker build 几乎一样,-t 参数用于给镜像打标签:
nerdctl build -t my-nginx:alpine .
当你按下回车,魔法就开始了。nerdctl 会调用背后的BuildKit,你会看到非常熟悉的、分层的构建输出:
[+] Building 12.5s (7/7) FINISHED
=> [internal] load build definition from Dockerfile
=> => transferring dockerfile: 37B
=> [internal] load .dockerignore
=> => transferring context: 2B
=> [internal] load metadata for docker.io/library/alpine:3.18
=> [1/3] FROM docker.io/library/alpine:3.18@sha256:...
=> [2/3] RUN apk add --no-cache nginx
=> [3/3] RUN echo "<html><body><h1>Hello from Containerd + BuildKit!</h1></body></html>" > /var/www/localhost/htdocs/index.html
=> exporting to oci image format
=> => exporting layers
=> => exporting manifest sha256:...
=> => exporting config sha256:...
这个过程是不是和Docker构建一模一样?但底层已经是BuildKit在驱动了。构建完成后,用 nerdctl images 看看,是不是多了一个 my-nginx:alpine 的镜像?
4.3 运行并测试容器
镜像有了,跑起来看看:
# -d 后台运行,-p 将容器的80端口映射到主机的8080端口
nerdctl run -d --name my-nginx-test -p 8080:80 my-nginx:alpine
现在,打开你的浏览器,访问 http://你的服务器IP:8080,应该就能看到那个“Hello from Containerd + BuildKit!”的页面了。这说明镜像构建和运行都成功了!
最后,清理一下测试容器:
nerdctl rm -f my-nginx-test
5. 进阶技巧:构建优化与私有仓库集成
基本的构建跑通了,但在实际生产环境中,我们还得解决两个核心问题:如何构建得更快?如何推送到自己的私有镜像仓库?
5.1 利用BuildKit缓存与并行构建加速
BuildKit的强大之处在于其智能的缓存机制。你可能会注意到,第二次构建同样的Dockerfile时速度飞快,这就是缓存的作用。为了最大化利用缓存,在编写Dockerfile时,可以遵循一些最佳实践:
- 顺序很重要:把变化最少的层(比如安装基础软件包)放在Dockerfile前面,变化频繁的层(比如复制应用代码)放在后面。这样,前面几层可以被缓存复用。
- 合并RUN指令:将多个
RUN指令用&&连接起来,并在最后清理apt或apk缓存,可以减少镜像层数,缩小镜像体积。例如:RUN apk add --no-cache nginx && \ rm -rf /var/cache/apk/* - 使用
.dockerignore文件:在构建上下文目录下创建.dockerignore文件,排除不需要打包进镜像的文件(如.git、node_modules、日志文件等),能显著减少构建上下文大小,提升上传速度。
BuildKit默认就支持这些优化。你还可以通过 nerdctl build 的 --progress=plain 参数查看详细的、非缓存的构建输出,方便调试。
5.2 推送到私有Harbor仓库
企业内部通常使用Harbor、Quay等私有仓库。这里以Harbor为例,演示如何推送镜像。
第一步:登录仓库
和Docker一样,需要先登录。如果你的Harbor用的是自签名证书,需要加上 --insecure-registry 参数跳过证书验证(生产环境建议配置可信证书)。
nerdctl login --insecure-registry harbor.your-company.com
输入用户名和密码后,凭证会保存在 ~/.docker/config.json,下次就不需要再输了。
第二步:给镜像打上私有仓库的标签 镜像名称必须包含完整的仓库地址。
nerdctl tag my-nginx:alpine harbor.your-company.com/my-project/my-nginx:alpine
第三步:推送镜像
使用 push 命令:
nerdctl push --insecure-registry harbor.your-company.com/my-project/my-nginx:alpine
如果一切配置正确,你会看到镜像层被一层层推送到仓库的进度。之后,在其他安装了 nerdctl 或 crictl 的机器上,就可以用 nerdctl pull 拉取这个镜像了。
5.3 配置BuildKit使用私有仓库(可选高级配置)
对于需要频繁访问的私有仓库,或者需要配置镜像加速器(如阿里云、腾讯云镜像加速地址),可以修改BuildKit的配置文件,避免每次命令都带参数。
编辑BuildKit的配置文件(如果不存在则创建):
sudo mkdir -p /etc/buildkit
sudo vim /etc/buildkit/buildkitd.toml
添加以下内容,例如配置一个使用HTTP的私有仓库和一个镜像加速器:
# 配置私有仓库,允许使用HTTP和不安全的连接(仅限内网可信环境)
[registry."harbor.your-company.com"]
http = true
insecure = true
# 配置镜像加速器(以阿里云为例)
[registry."docker.io"]
mirrors = ["registry.cn-hangzhou.aliyuncs.com"]
修改配置后,需要重启BuildKit服务使配置生效:
sudo systemctl restart buildkit
这样配置后,当你构建或拉取 docker.io 上的镜像时,BuildKit会自动从阿里云的镜像加速地址拉取,速度会快很多。而访问 harbor.your-company.com 时,也会自动使用HTTP协议并忽略证书错误。
6. 排错指南:常见问题与解决方案
在实际操作中,你可能会遇到一些坑。这里我总结几个自己踩过的,以及解决办法。
问题一:执行 nerdctl build 时报错 buildctl needs to be installed
- 原因:
nerdctl找不到buildctl命令。 - 解决:确保
/usr/local/bin/buildctl存在,并且该目录在系统的PATH环境变量中。可以用echo $PATH检查,或者直接用绝对路径/usr/local/bin/nerdctl试试。
问题二:BuildKit服务启动失败,日志显示权限错误或找不到containerd socket
- 原因:
buildkitd服务没有权限访问/run/containerd/containerd.sock,或者Containerd服务没启动。 - 解决:
- 确认Containerd服务正在运行:
systemctl status containerd。 - 确认socket文件存在:
ls -la /run/containerd/containerd.sock。 - 检查
buildkit.service文件中配置的用户组。如果Containerd的socket文件所属组是containerd,你可以将BuildKit服务的运行用户改为containerd,或者将buildkitd用户加入到containerd组。
- 确认Containerd服务正在运行:
问题三:构建时无法拉取基础镜像,报网络错误或证书错误
- 原因:网络不通,或者对于使用自签名证书的私有仓库,没有正确配置。
- 解决:
- 对于公有镜像,检查网络连接和DNS。
- 对于私有Harbor(自签名证书),确保按照前面章节,在
nerdctl login和nerdctl push/pull时使用了--insecure-registry参数。 - 更彻底的解决方案是将私有仓库的CA证书放到Containerd的信任目录下:
/etc/containerd/certs.d/<仓库域名>/ca.crt,然后重启Containerd。这样就不需要--insecure-registry了。
问题四:构建成功,但 nerdctl images 看不到镜像,或者Kubernetes拉取不到
- 原因:命名空间不对。BuildKit默认可能把镜像存到了
buildkit命名空间,而nerdctl默认查看的是default或k8s.io命名空间。 - 解决:
- 用
nerdctl -n buildkit images查看buildkit命名空间的镜像。 - 构建时指定命名空间:
nerdctl -n k8s.io build -t ...,这样镜像会直接存到Kubernetes使用的命名空间。 - 或者,修改BuildKit配置,让其默认使用
k8s.io作为worker的命名空间(在buildkitd.toml中配置namespace = "k8s.io")。
- 用
踩过这些坑之后,你会发现Containerd加BuildKit这套组合拳其实非常稳定和高效。它摆脱了对Docker守护进程的依赖,架构更清晰,特别适合云原生和Kubernetes环境。刚开始切换可能会有点不习惯,但一旦流程跑顺,你会爱上这种更模块化、更云原生友好的工作方式。

472

被折叠的 条评论
为什么被折叠?



