背景:一次“看似简单”的容器化迁移
事情是这样的:我们有一个运行了五年的Java应用集群,原本搭建在物理机上,依赖LXC容器做资源分离。随着业务增长,需要迁移到Kubernetes集群。团队里有人提议直接上Docker,有人坚持用Podman(因为无守护进程),还有人觉得LXC够用。于是我们决定在测试环境分别搭建三套方案,结果第一天就出问题了。
第一坑:Docker的“守护进程依赖”与文件权限
先说说Docker。我们按照标准流程安装:
yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install docker-ce docker-ce-cli containerd.io
systemctl start docker
然后拉取镜像启动容器:
docker run -d --name app1 -v /data/logs:/var/log/app myapp:latest
一切正常,直到我们尝试在宿主机上查看日志。发现/data/logs目录下的文件属主全是root:root,而应用进程是以appuser运行的。排查发现,Docker默认以root权限挂载卷,导致容器内用户无法写入宿主机目录。解决方案是加上--user参数:
docker run -d --user 1000:1000 -v /data/logs:/var/log/app myapp:latest
但更坑的是,Docker守护进程一旦崩溃,所有容器都会停止。有一次我们升级内核后忘记重启docker服务,导致整个集群离线了15分钟。这就是Docker的“单点故障”问题——所有容器都依赖dockerd进程。
第二坑:Podman的“无根模式”与网络兼容性
因为Docker的守护进程问题,我们转向Podman。Podman最大的卖点是无守护进程,每个容器直接由fork/exec启动,理论上更防护。安装很简单:
yum install -y podman
使用上几乎和Docker一样:
podman run -d --name app2 -v /data/logs:/var/log/app myapp:latest
但当我们尝试在Podman容器内访问宿主机网络时,遇到了麻烦。Podman默认使用slirp4netns进行网络分离,这导致容器内无法直接绑定宿主机端口。我们不得不手动设置CNI网络:
podman run -d --network host --name app2 myapp:latest
更麻烦的是,Podman的无根模式(rootless)虽然防护,但会控制容器对系统资源的访问。比如我们有个应用需要调整内核参数net.core.somaxconn,在无根模式下根本改不了。最终我们只能切换到有根模式,但这又失去了无根的防护优势。
第三坑:LXC的“系统容器”与资源管理
最后说说LXC。作为老牌容器方案,LXC更接近轻量级虚拟机,每个容器都有完整的init进程。我们之前用LXC跑Java应用时,设置了cgroup控制:
lxc-create -n app3 -t download -- -d ubuntu -r focal -a amd64
lxc-cgroup -n app3 memory.limit_in_bytes 4G
lxc-cgroup -n app3 cpuset.cpus 0-3
但LXC的问题是镜像管理太原始。我们需要手动构建rootfs,不像Docker有分层镜像和Dockerfile。而且LXC的容器迁移非常麻烦,需要导出整个文件系统,不像Docker可以轻松push/pull镜像。
最致命的是,LXC在Kubernetes生态中几乎无法使用。我们尝试用LXC作为K8s的容器运行时,发现CRI接口支持非常有限,最终只能放弃。
实战总结:如何选型?
经过这次排坑,我们总结出以下经验:
Docker:适合需要完整生态的场景。如果你在Kubernetes上运行微服务,Docker依然是最成熟的选择。但要注意守护进程的可用性,建议用systemd管理并设置自动重启。另外,文件权限问题可以通过--user和--group-add解决。
Podman:适合对防护要求高、需要无守护进程的场景。比如在CI/CD流水线中,Podman的无根模式可以避免权限提升风险。但要注意网络设置和资源控制问题,建议使用podman-compose管理多容器应用。
LXC:适合需要完整系统环境的场景,比如运行systemd服务或需要内核模块的旧应用。但LXC不适合现代微服务架构,建议逐步迁移到Docker或Podman。
最后的建议
如果你正在做容器运行时选型,我的建议是:不要盲目追新。我们最终选择了Podman作为主力运行时,因为它的无守护进程特性减少了运维复杂度,而且兼容Docker命令,迁移成本低。但如果你需要Kubernetes原生支持,Docker依然是稳妥之选。
记住,没有银弹。每个方案都有坑,关键是理解业务需求,做好充分测试。希望这篇排坑记能帮大家少走弯路。
👨💻 运维经验:根据实际生产环境,以上步骤建议先在测试环境验证,并做好备份。参数值需根据服务器设置调整,不要盲目照搬。