从Docker到nerdctl:容器CLI工具演进与K8s环境实战指南
1. 从Docker到nerdctl:为什么我们需要一个新的容器CLI工具
如果你和我一样,习惯了用docker ps查看容器,用docker run启动服务,那么第一次看到nerdctl这个命令时,可能会有点懵。这玩意儿是啥?又一个Docker的替代品?命令还长得差不多。其实,nerdctl的出现,背后是容器生态一次静默但重要的演进。简单来说,你可以把它理解为专门为containerd这个容器运行时设计的“Docker CLI”。Docker本身是一个庞大的体系,包含了客户端(Docker CLI)、容器运行时(containerd)、镜像构建工具等一系列组件。而nerdctl的目标,就是提供一个功能丰富、体验接近Docker CLI的工具,但直接与更底层、更标准的containerd运行时对话。
为什么这很重要?在实际的生产和开发环境中,Kubernetes(K8s)早已成为容器编排的事实标准。而K8s在v1.24版本之后,移除了对Docker作为默认运行时的直接支持,转向了更轻量、更专注的containerd和CRI-O。这意味着,如果你在K8s节点上想直接操作容器(比如排查问题、临时执行命令、导入导出镜像),传统的docker命令可能不再可用或不是最佳选择。nerdctl填补了这个空白,它让你在纯containerd的环境中,依然能获得类似Docker的便捷操作体验。我最初接触它就是在一次K8s集群的故障排查中,节点上只有containerd,docker命令报错“找不到守护进程”,而nerdctl成了我的救命稻草。
所以,这份“操作手册”的目的,不是简单罗列命令,而是帮你快速建立对nerdctl的直觉,让你知道在哪些场景下该用它,以及如何用它高效地完成日常工作。它的命令设计很大程度上兼容了Docker,所以学习成本很低,但也有一些独有的特性和细微差别,这正是我们需要重点关注的地方。
2. nerdctl基础:安装、配置与初体验
在深入具体命令之前,我们得先把环境搭起来。nerdctl的安装方式很灵活,你可以通过包管理器、下载二进制文件或者从源码编译。
2.1 安装与快速配置
对于大多数Linux系统,最方便的方法是使用其项目提供的安装脚本,或者从GitHub Releases页面下载对应架构的二进制文件。这里以直接下载二进制文件为例,因为它通用性最强。
# 假设我们是在一个x86_64的Linux服务器上操作 # 前往GitHub Releases页面找到最新版本,例如 v1.5.0 # 下载压缩包 wget https://github.com/containerd/nerdctl/releases/download/v1.5.0/nerdctl-1.5.0-linux-amd64.tar.gz # 解压 tar -xzf nerdctl-1.5.0-linux-amd64.tar.gz # 将二进制文件移动到系统PATH目录,例如 /usr/local/bin/ sudo mv nerdctl /usr/local/bin/ # 验证安装 nerdctl --version安装完成后,一个关键配置是命名空间(namespace)。containerd支持多租户隔离,不同的命名空间就像不同的“抽屉”,镜像和容器默认是分开的。nerdctl默认使用default命名空间。如果你需要操作K8s创建的容器(它们通常在k8s.io命名空间),就需要在命令中指定。
# 查看当前有哪些命名空间(需要containerd的ctr命令) sudo ctr namespace ls # 使用nerdctl操作k8s.io命名空间下的容器 nerdctl --namespace=k8s.io ps -a为了方便,你可以设置环境变量NERDCTL_NAMESPACE来避免每次输入--namespace。
export NERDCTL_NAMESPACE=k8s.io # 或者写入 ~/.bashrc 持久化 echo 'export NERDCTL_NAMESPACE=k8s.io' >> ~/.bashrc source ~/.bashrc注意:操作
k8s.io命名空间下的资源通常需要root权限,因为K8s组件是以高权限运行的。普通用户可能无法看到或操作这些容器。
2.2 第一个命令:验证与帮助系统
安装配置好后,我们先跑几个最简单的命令来感受一下。
# 查看版本,确认安装成功 nerdctl version # 查看系统信息,类似于 docker info nerdctl info # 强大的帮助系统,任何命令记不清了就用 --help nerdctl --help nerdctl run --help # 查看run子命令的详细用法你会发现,nerdctl info输出的信息比docker info更“底层”一些,它会直接显示containerd的运行时和快照器(snapshotter)信息。快照器是一个关键概念,它决定了容器镜像层在磁盘上的存储方式。常见的如overlayfs、stargz等,nerdctl对此有很好的支持,我们后面会提到。
3. 镜像管理:拉取、查看、构建与清理
镜像操作是容器工作的起点。nerdctl的镜像命令与Docker高度相似,但也有一些增强功能。
3.1 拉取与查看镜像
拉取镜像使用nerdctl pull。默认情况下,它会从Docker Hub拉取。
# 拉取一个nginx镜像 nerdctl pull nginx:alpine # 拉取时指定完整仓库地址 nerdctl pull registry.cn-hangzhou.aliyuncs.com/google_containers/pause:3.9 # 查看本地镜像列表 nerdctl images # 输出格式和docker几乎一致:仓库、标签、镜像ID、创建时间、大小 # 使用更丰富的格式输出,例如只显示镜像ID和仓库名 nerdctl images --format "table {{.ID}}\t{{.Repository}}" # 查看镜像的详细信息(Inspect) nerdctl image inspect nginx:alpine | jq . # 配合jq工具美化JSON输出这里有个实操心得:在拉取一些大型镜像时,可能会遇到速度慢的问题。nerdctl本身没有内置的镜像加速器配置,但你可以通过配置containerd的镜像仓库镜像(registry mirror)来实现加速,这需要修改/etc/containerd/config.toml文件。另一种更直接的方式,是先把镜像拉取到有Docker且配置了加速器的环境,然后用docker save导出,再用nerdctl load导入到目标机器。
3.2 构建镜像:兼容Dockerfile与高级特性
nerdctl build命令用于构建镜像,它完全兼容标准的Dockerfile语法。
# 在当前目录(包含Dockerfile)构建镜像 nerdctl build -t my-app:latest . # 指定构建上下文路径和Dockerfile路径 nerdctl build -t my-app:v1 -f ./Dockerfile.prod ./context-dirnerdctl在构建方面的一个亮点是它对BuildKit的深度集成。BuildKit是下一代镜像构建工具,支持更快的构建、更高效的缓存和更多高级功能。你可以通过--builder参数指定使用BuildKit。
# 使用BuildKit后端进行构建(需要containerd的buildkitd服务在运行) nerdctl build --builder buildkit -t my-app:buildkit .更强大的是,nerdctl支持一种名为“Stargz”的可延迟拉取镜像格式。这种镜像格式允许容器在拉取完元数据后立即启动,而镜像层数据则在需要时按需拉取,非常适合边缘计算或网络带宽受限的场景。构建Stargz格式的镜像需要额外的工具链,但nerdctl提供了实验性支持。
3.3 镜像的导入、导出与清理
日常运维中,经常需要在离线环境或不同机器间迁移镜像。
# 将镜像保存为tar归档文件 nerdctl save -o nginx-alpine.tar nginx:alpine # 从tar归档文件加载镜像 nerdctl load -i nginx-alpine.tar # 给镜像打上新的标签 nerdctl tag nginx:alpine my-registry.local:5000/nginx:my-tag # 推送镜像到私有仓库(需要先登录) nerdctl push my-registry.local:5000/nginx:my-tag镜像清理是维护系统磁盘空间的重要环节。nerdctl提供了比原生Docker更灵活的镜像删除命令。
# 删除指定镜像 nerdctl rmi nginx:alpine # 强制删除(即使有容器引用) nerdctl rmi -f nginx:alpine # 删除所有未被任何容器引用的悬空镜像(dangling images) nerdctl image prune # 交互式删除:列出所有镜像,并询问是否删除 nerdctl images | tail -n +2 | awk '{print $1":"$2}' | fzf -m | xargs -r nerdctl rmi # 这个命令组合使用了fzf(一个命令行模糊查找器),可以让你方便地选择多个镜像进行删除,非常高效。注意:
nerdctl rmi在删除被多个标签引用的镜像时,行为与Docker一致:它只是删除该标签的引用,直到最后一个标签被删除时,才会真正删除镜像层数据。
4. 容器生命周期管理:从创建到销毁
这是最核心的部分,我们每天都要和这些命令打交道。nerdctl run命令的参数与docker run兼容度极高,你可以几乎无缝迁移。
4.1 创建与启动容器
# 最基本的运行:启动一个交互式容器并进入shell nerdctl run -it --rm alpine:latest /bin/sh # -it: 分配一个交互式终端并保持打开 # --rm: 容器退出后自动删除 # 退出shell后,容器自动停止并被删除 # 在后台运行一个守护进程容器 nerdctl run -d --name my-nginx -p 8080:80 nginx:alpine # -d: 后台运行 # --name: 指定容器名称 # -p: 端口映射(主机端口:容器端口) # 运行容器并执行一条命令后退出 nerdctl run --rm alpine:latest echo "Hello, nerdctl!" # 挂载主机目录到容器内 nerdctl run -v /host/data:/container/data -it alpine:latest sh # -v: 卷挂载,格式为 `主机路径:容器路径[:选项]` # 设置环境变量 nerdctl run -e MY_ENV=value -e "JAVA_OPTS=-Xmx512m" my-app:latest # 限制容器资源 nerdctl run -d --name limited-app \ --cpus 1.5 \ # 限制使用1.5个CPU核心 --memory 512m \ # 限制内存为512MB --memory-swap 1g \ # 限制内存+交换分区为1GB my-app:latest一个重要的细节是网络。nerdctl默认会为容器创建一个新的网络命名空间,并使用CNI(Container Network Interface)插件来配置网络。这与Docker默认的bridge驱动不同。你可以使用nerdctl network ls查看现有的CNI网络。如果你需要容器使用主机网络(例如运行一个网络监控工具),可以使用--net=host参数。
4.2 查看、进入与操作运行中的容器
容器跑起来之后,我们需要观察和管理它们。
# 列出正在运行的容器 nerdctl ps # 列出所有容器(包括已停止的) nerdctl ps -a # 使用自定义格式输出,例如只显示ID、名称、状态和镜像 nerdctl ps -a --format "table {{.ID}}\t{{.Names}}\t{{.Status}}\t{{.Image}}" # 查看容器日志 nerdctl logs my-nginx # 实时跟踪日志输出 nerdctl logs -f my-nginx # 查看最近10行日志 nerdctl logs --tail 10 my-nginx # 进入一个正在运行的后台容器(类似 docker exec -it) nerdctl exec -it my-nginx /bin/sh # 在容器内执行单条命令 nerdctl exec my-nginx nginx -t # 测试nginx配置 # 查看容器内进程 nerdctl top my-nginx # 查看容器的详细信息(Inspect),获取所有配置和状态 nerdctl inspect my-nginx | jq .[0].NetworkSettings.Networks # 这个命令可以帮你找到容器在CNI网络中的IP地址,对于网络排查非常有用。4.3 停止、重启与删除容器
# 停止一个运行中的容器 nerdctl stop my-nginx # 强制停止(发送SIGKILL) nerdctl kill my-nginx # 启动一个已停止的容器 nerdctl start my-nginx # 重启容器 nerdctl restart my-nginx # 暂停容器(冻结进程) nerdctl pause my-nginx # 恢复被暂停的容器 nerdctl unpause my-nginx # 删除已停止的容器 nerdctl rm my-nginx # 强制删除一个运行中的容器 nerdctl rm -f my-nginx # 删除所有已停止的容器(清理空间) nerdctl container prune # 交互式清理:类似镜像删除,可以用fzf组合命令这里有一个踩坑点:在K8s环境下,使用nerdctl --namespace=k8s.io操作由K8s创建的容器时,极其不推荐直接使用nerdctl rm -f强制删除。因为这可能会破坏K8s对Pod的管理状态,导致不可预知的行为。正确的做法是通过kubectl delete pod来让K8s以受控的方式清理容器。
5. 数据与网络管理:持久化与连通性
容器本身是无状态的,数据和网络是让它变得有用的关键。
5.1 数据卷(Volume)管理
nerdctl也支持类似Docker的数据卷管理,但底层机制略有不同。
# 创建一个命名卷 nerdctl volume create my-data # 列出所有卷 nerdctl volume ls # 使用命名卷运行容器 nerdctl run -v my-data:/app/data -it alpine:latest sh # 查看卷的详细信息 nerdctl volume inspect my-data # 你会看到卷在主机上的实际存储路径,通常位于 /var/lib/containerd/... 下。 # 删除未使用的卷 nerdctl volume prune注意:与Docker不同,
nerdctl创建的卷是containerd管理的,与Docker的卷不互通。如果你需要迁移,可能需要手动拷贝数据。
对于主机目录绑定挂载(-v /host/path:/container/path),行为与Docker一致,是最简单直接的持久化方式。在K8s的k8s.io命名空间下,你可能会看到很多由K8s自动创建的、名称很长的卷,这些通常对应着Pod中定义的hostPath或emptyDir,不要随意删除它们。
5.2 网络管理
如前所述,nerdctl默认使用CNI插件管理网络。这给了它更强大的网络能力,特别是在多主机和K8s集成场景下。
# 列出所有CNI网络 nerdctl network ls # 你可能会看到类似 `bridge`, `natter` 等网络。 # 创建一个新的CNI桥接网络 nerdctl network create my-net --subnet 10.15.0.0/16 # 在自定义网络中运行容器 nerdctl run -d --name app1 --network my-net nginx:alpine nerdctl run -d --name app2 --network my-net nginx:alpine # 此时,app1和app2可以通过容器名直接互相通信,因为CNI插件通常会集成DNS。 # 将容器连接到另一个网络 nerdctl network connect my-net existing-container # 断开容器与网络的连接 nerdctl network disconnect my-net existing-container # 查看网络详情 nerdctl network inspect my-net # 删除网络(必须没有任何容器连接) nerdctl network rm my-net一个高级技巧是使用--ip参数为容器指定静态IP地址(前提是网络支持)。这在一些需要固定IP的测试场景中很有用。
nerdctl run -d --name static-ip-app --network my-net --ip 10.15.0.100 nginx:alpine6. 组合应用与系统管理
对于复杂的多容器应用,我们通常使用Docker Compose。nerdctl通过nerdctl compose子命令提供了对Compose文件的原生支持,这是一个巨大的便利。
6.1 使用Compose部署应用
首先,你需要一个docker-compose.yml文件,语法与Docker Compose完全兼容。
# docker-compose.yml version: '3.8' services: web: image: nginx:alpine ports: - "80:80" volumes: - ./html:/usr/share/nginx/html db: image: postgres:15-alpine environment: POSTGRES_PASSWORD: secretpassword volumes: - postgres_data:/var/lib/postgresql/data volumes: postgres_data:然后,使用nerdctl compose命令来管理这个应用栈。
# 在包含docker-compose.yml的目录下,启动所有服务 nerdctl compose up -d # -d 表示后台运行 # 查看Compose项目下的容器状态 nerdctl compose ps # 查看特定服务的日志 nerdctl compose logs web nerdctl compose logs -f db # 跟踪日志 # 在运行中的服务内执行命令 nerdctl compose exec web nginx -t # 停止并移除所有服务、网络、卷(但会保留命名卷) nerdctl compose down # 停止并移除所有服务、网络、卷,包括命名卷(数据会丢失!) nerdctl compose down -v实操心得:nerdctl compose命令会默认使用当前目录名作为项目名称(project name),用于隔离不同项目的网络和容器。你可以通过-p参数指定项目名。在K8s节点上使用Compose时,务必注意命名空间,避免与K8s管理的资源冲突。
6.2 系统级查看与资源清理
除了管理单个容器和镜像,nerdctl也提供了一些系统视角的命令。
# 查看容器、镜像、卷、网络的数量和磁盘使用情况(类似 docker system df) nerdctl system df # 这个命令能快速帮你定位是镜像还是容器占用了大量磁盘空间。 # 查看containerd的事件流(类似 docker events) nerdctl events # 在一个终端运行这个命令,在另一个终端操作容器,可以实时看到所有事件(pull, create, start, die等),对于调试非常有用。 # 全面的系统信息 nerdctl info定期清理对于维护一个健康的系统至关重要。我通常会写一个简单的清理脚本,结合nerdctl和系统命令。
#!/bin/bash # cleanup.sh echo "Cleaning up unused containers..." nerdctl container prune -f echo "Cleaning up unused images..." nerdctl image prune -f echo "Cleaning up unused volumes..." nerdctl volume prune -f echo "Cleaning up unused networks..." nerdctl network prune -f echo "Cleanup complete." # 可以把这个脚本加入cron定时任务,例如每周日凌晨执行一次。7. 进阶特性与排错指南
掌握了基础命令,我们来看看nerdctl一些独有的进阶功能,以及遇到问题时如何排查。
7.1 容器检查点(Checkpoint)与恢复
这是一个非常强大的功能,允许你将一个运行中容器的状态(包括内存、CPU寄存器等)保存到磁盘,然后在之后恢复运行。这对于调试复杂的内存状态问题或者进行容器迁移很有帮助。这个功能需要安装额外的工具criu并启用containerd的相应配置。
# 为运行中的容器创建一个检查点 nerdctl checkpoint create my-running-container my-checkpoint # 从检查点恢复容器(可以恢复到原容器或新容器) nerdctl start --checkpoint my-checkpoint my-new-container7.2 使用非默认快照器(Snapshotter)
快照器决定了镜像层如何存储和叠加。overlayfs是最常见的。但nerdctl还支持如stargz、nydus等高级快照器,它们针对镜像启动速度或空间效率做了优化。你可以在运行或构建时指定。
# 使用stargz快照器运行容器(需要提前准备stargz格式的镜像) nerdctl run --snapshotter stargz -it your-stargz-image:latest sh7.3 常见问题排查
问题一:执行nerdctl命令报错connect: permission denied或failed to dial。
这通常是因为当前用户没有权限访问 containerd 的套接字文件(默认是/run/containerd/containerd.sock)。
- 解决方案:
- 使用
sudo执行命令:sudo nerdctl ps。 - 将用户加入
docker或containerd组(如果存在),然后重新登录。sudo usermod -aG docker $USER # 或 containerd 组 - 直接修改套接字文件的权限(不推荐用于生产环境):
sudo chmod 666 /run/containerd/containerd.sock
- 使用
问题二:nerdctl compose up失败,提示网络相关错误。
这通常是因为CNI插件配置问题或网络冲突。
- 排查步骤:
- 检查CNI插件是否安装:
ls /opt/cni/bin/。 - 检查CNI配置文件:
ls /etc/cni/net.d/。 - 尝试使用一个更简单的网络驱动,或者检查Compose文件中定义的网络子网是否与现有网络冲突。
- 查看containerd日志获取更详细信息:
sudo journalctl -u containerd -f。
- 检查CNI插件是否安装:
问题三:在k8s.io命名空间下看不到预期的容器。
- 可能原因:
- 容器可能已经退出并被清理。使用
nerdctl --namespace=k8s.io ps -a查看所有容器。 - 你可能没有使用root权限。尝试
sudo nerdctl --namespace=k8s.io ps。 - containerd的
k8s.io命名空间可能被禁用或配置不同。检查/etc/containerd/config.toml中[plugins."io.containerd.grpc.v1.cri".containerd]下的sandbox_image和snapshotter配置。
- 容器可能已经退出并被清理。使用
问题四:磁盘空间不足,但nerdctl system df显示占用不大。
containerd的镜像和容器数据默认存储在/var/lib/containerd。除了nerdctl管理的内容,还可能存在一些旧的、未清理的临时文件或快照。
- 深度清理命令(谨慎使用):
更安全的方法是定期重启节点,并确保K8s的垃圾回收配置合理。# 停止containerd服务 sudo systemctl stop containerd # 使用containerd自带的清理工具(最安全) sudo containerd config dump | grep root # 查看数据根目录 # 通常可以手动清理根目录下的 `io.containerd.snapshotter.v1.*` 子目录中的陈旧内容,但需要专业知识。 # 重启containerd sudo systemctl start containerd
我个人在从Docker转向nerdctl的过程中,最大的体会就是它“既熟悉又陌生”。熟悉的是命令语法,陌生的是它背后更贴近云原生标准(containerd + CNI)的架构。把它当作一个在纯containerd环境下的“瑞士军刀”,特别是在处理K8s节点运维、CI/CD流水线构建等场景时,你会发现它不可或缺。刚开始可能会纠结于一些命令参数的细微差别,但多用几次,肌肉记忆自然就形成了。关键是要理解它和Docker在底层模型上的不同,这样遇到问题时,你的排查思路才会更清晰。