ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu上为K8s配置Docker:从cgroup驱动到网络参数全对齐

Ubuntu上为K8s配置Docker:从cgroup驱动到网络参数全对齐 我见过太多人在Ubuntu上装Docker就是一行apt install docker.io装完docker run hello-world能跑就觉得自己搞定了。结果到了准备k8s集群的时候问题一个接一个冒出来kubelet报cgroup驱动不一致、节点状态一直是NotReady、Pod网络时通时断。作为一个从k8s 1.16一路折腾到1.28的运维我想说句实话在Ubuntu上为k8s基础环境装Docker和单纯装个Docker跑项目完全不是一个量级的工程。这篇东西就是帮你理清这个差异并给出一套可以直接照抄的完整流程。本文不打算讲“打开Docker Desktop点点点”那种玩法而是面向真实集群场景用apt源装Docker Engine、配置daemon.json、对齐k8s要求的cgroup和网络参数、排查启动失败和网络不通。无论你是刚接触Docker的运维新人还是准备用kubeadm搭集群但卡在第一步的人这套流程都能帮你避掉大部分坑。1. 普通装法和k8s基础环境装法差异到底在哪1.1 能跑容器和能当集群节点是两码事开发机上装Docker需求很朴素能把项目跑起来能映射端口能把数据挂到宿主机。这时候哪怕cgroup驱动是cgroupfs、swap没关、ufw开着一般都不耽误干活。但k8s节点不一样。kubelet接管节点后要实时感知节点上所有Pod的状态要和容器运行时通过CRI通信要配合网络插件Calico、Flannel、Cilium配置转发规则。任何一个底层参数不对kubelet要么直接拒绝启动要么启动后把节点标记成NotReady然后你就要开始漫长的翻日志之旅。举几个我实际踩过的例子Docker默认的cgroup驱动是cgroupfs而k8s从1.24之后强烈建议kubelet和容器运行时都用systemd驱动。两边不一致kubelet直接报错退出根本到不了集群初始化的步骤。节点上有swap没关kubeadm在preflight检查阶段就会红字警告即使你强行忽略后面Pod调度和内存回收也会出现诡异问题。内核的br_netfilter模块没加载net.bridge.bridge-nf-call-iptables不是1集群内Pod跨节点通信时DNS解析和Service转发会随机失败——这类问题最难排查因为它不是“必现”而是“偶发”。所以说“装好Docker”只是开始“让Docker符合k8s的运行要求”才是真正的目标。普通装法让你能跑容器k8s装法让你能把这个节点交给集群调度系统去管理。1.2 三条硬性要求对照表把普通Docker使用和k8s基础环境做个对比下面这几点是最核心的差异检查项普通Docker使用k8s基础环境说明cgroup驱动cgroupfs也能跑必须与kubelet一致推荐systemd不一致时kubelet直接拒绝启动CRI支持不需要必须有/var/run/containerd/containerd.sockDocker内置的containerd不对外暴露CRI接口swap分区无所谓必须关闭kubeadm preflight会检查生产环境也建议关br_netfilter模块可选必选桥接流量经过iptables是Pod网络的基础ip_forward通常自动开启但没人在意必须为1容器和Pod的跨节点转发依赖它这张表不是理论是kubeadm、kubelet、网络插件三者协同工作时实打实的依赖。建议你在动手前先把这张表存下来后面每一步都对照着看。1.3 别把Docker Desktop当k8s节点的容器运行时另外一个常见的认知偏差是想在Ubuntu上用Docker Desktop。Docker Desktop在Linux上确实存在但它本质是靠KVM虚拟化跑一个轻量级虚拟机依赖/dev/kvm设备。在本地体验没问题放到生产节点或云服务器上就非常别扭——你等于在每个节点上叠了一层虚拟化资源开销和故障点都变多了。为k8s基础环境准备节点正解是装Docker Engine并让它配套的containerd来承担集群运行时职责。如果你只需要在节点上调试镜像甚至可以只装docker-ce-cli把完整的Docker Engine留给专门的构建机。这个习惯我在后文会详细展开。2. Ubuntu上Docker的两种安装路径与完整命令2.1 安装前必须核对的环境参数动手之前先把环境确认一遍。很多安装失败不是命令背错了而是系统状态本来就不干净。系统版本建议用20.04、22.04或24.04 LTS内核保持在5.x以上。新装的Ubuntu通常没问题但如果你是从旧版本一路升级上来的内核和设备驱动可能藏着历史债。内核和架构检查uname -r uname -m架构正常是x86_64如果你在树莓派或ARM服务器上装下面的$(dpkg --print-architecture)会自动识别命令不需要改。然后是卸载旧版本。这一步很多人跳过结果后面撞上莫名其妙的依赖冲突sudo apt-get remove -y docker docker-engine docker.io containerd runc docker-compose docker-doc podman-docker sudo apt-get autoremove -y旧版Docker和新版并存时容易出现的现象是docker命令能查到但dockerd起来后立刻退出journal日志里报“无法锁定/var/lib/docker”或者“overlay2驱动初始化失败”。所以干脆先把老的全部清掉再装新。还需要检查一下有没有残留配置目录ls -la /etc/docker /var/lib/docker /etc/containerd如果你之前手动改过/etc/docker/daemon.json建议先备份再清空避免新版本加载旧配置导致起不来。2.2 官方apt源安装步骤推荐这里只推荐用Docker官方apt源安装。原因很简单官方源里的docker-ce、containerd.io、docker-buildx-plugin、docker-compose-plugin版本同步更新依赖关系也完整。你很难从Ubuntu自带源里同时拿到这几个组件更别说版本那么新。一步步来sudo apt-get update sudo apt-get install -y ca-certificates curl gnupg添加GPG key并写入keyrings目录这是Ubuntu 22.04的标准做法旧教程里让写/etc/apt/keyrings/docker.gpg新版本路径和权限都要注意sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg添加apt源这里用变量自动获取当前Ubuntu版本代号echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release echo $VERSION_CODENAME) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null更新索引并安装sudo apt-get update sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin这一步会安装四个核心组件除了Docker Engine本身特别重要的是containerd.io。很多人的误区是装了Docker就以为containerd只是附属品实际上在k8s基础环境里这个独立containerd才是真正要交给CRI的角色。2.3 为什么不推荐直接用Ubuntu自带的docker.io包Ubuntu仓库里确实有docker.io包用起来就一行sudo apt install docker.io网速快的时候几分钟就好。但我很不推荐在k8s基础环境里走这条路。第一版本落后。Ubuntu 22.04自带的docker.io大概率还是老版本新一些的镜像、Docker Compose v2特性、Buildx增强都体验不到。第二拆装麻烦。docker.io包把一堆组件打在一起你想单独升级containerd或者换buildx插件反而绑手绑脚。第三和kubelet的配合验证不足。kubeadm和网络插件在社区里跑得最熟的是官方源那套版本组合你用仓库里的旧包等于把自己放在了一个别人很少测试的路径上。如果你只是想在Ubuntu开发机上快速跑一个容器那docker.io不是不能用。但只要涉及k8s请老老实实走官方源。2.4 装完后的第一轮启动与权限处理安装完成后先把服务设置成开机自启并启动sudo systemctl enable --now docker sudo systemctl enable --now containerd验证两个服务都处于active状态systemctl is-active docker systemctl is-active containerd然后把当前用户加入docker组方便后续不用每次sudosudo usermod -aG docker $USER newgrp docker最后跑一个官方测试镜像验证基本可用性docker run --rm hello-world如果你看到“Hello from Docker!”并输出了系统信息说明Docker引擎本身没问题。注意这只代表“能跑容器”和k8s基础环境还有距离后面的配置才是重点。3. 为k8s定制Dockerdaemon.json与cgroup driver对齐3.1 daemon.json怎么写每个参数凭什么Docker的很多关键行为都集中在/etc/docker/daemon.json这个文件里。默认情况下这个文件不存在需要你手动创建。以下是我在k8s节点上的标准配置{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, storage-driver: overlay2, registry-mirrors: [ https://docker.m.daocloud.io, https://hub-mirror.c.163.com ] }逐项说明exec-opts把原生cgroup驱动改为systemd这是k8s节点必须做的一件事。原因下一小节细讲。log-driver和log-opts容器日志用json-file格式单文件上限100MB保留3个轮转文件。这个参数在节点上尤其重要不然一个容器日志能写满整块磁盘节点直接进入磁盘压力状态。storage-driver用overlay2而不是vfs或aufs。现代Ubuntu内核都支持overlay2性能好镜像层复用也高效。registry-mirrors国内网络环境下拉公共镜像确实慢配一个稳定的公共镜像服务会舒服很多。生产环境建议换你们内网里的Harbor或云厂商镜像仓库。改完配置后必须重启Docker并且确认服务正常sudo systemctl restart docker sudo docker info执行docker info时重点看两行Storage Driver: overlay2和Cgroup Driver: systemd。只要这两个对了Docker侧的k8s基础要求就满足了一半。3.2 cgroup driversystemd的前因后果cgroup是Linux内核做资源限制的机制CPU、内存、IO的限制全靠它。一个节点上systemd需要管理用户会话和服务Docker/containerd要为容器分配资源kubelet要为Pod和kubelet本身设置资源预留。三套东西都在cgroup里做事如果驱动不一致很容易出现资源统计错乱和互相覆盖的问题。k8s官方远在v1.22就把systemd驱动标为推荐到了v1.24前后kubelet默认也在此方向收敛。实际操作中如果Docker用的还是cgroupfs而kubelet用的systemd你会看到这样一条很典型的报错failed to run Kubelet: misconfiguration: kubelet cgroup driver: systemd is different from docker cgroup driver: cgroupfs这句话基本就是从“装完Docker直接搭集群”的人那里来的。解决办法就是在daemon.json里写exec-opts: [native.cgroupdriversystemd]重启Docker。注意一点如果你是在容器里嵌套跑Docker比如Docker in Docker这个参数会不生效因为嵌套环境里没有真正的systemd。但正常的Ubuntu节点不会遇到这个问题。改完验证docker info --format {{.CgroupDriver}}输出应该是systemd。3.3 如果运行时选了containerdSystemdCgroup怎么配到了k8s 1.24之后Docker官方通过dockershim那条路已经断了。所以现在有两种主流组合节点上装Docker Engine但kubelet不直接走Docker而是走独立containerd的CRI socket。Docker留着给运维人员调试镜像用。节点上只装containerd不装Docker Engine用crictl和ctr管理镜像和容器所有调试都走containerd CLI。无论哪种k8s真正用的运行时其实是containerd而不是dockerd。这里必须把一件事说明白Docker从23.0开始确实内置了containerd但那套containerd是Docker进程自己管理的socket位于/var/run/docker/containerd/containerd.sock不对外开放CRI接口。k8s用的是另一个独立服务containerd.servicesocket在/var/run/containerd/containerd.sock。两台“containerd”各管各的目录互不干扰。如果你选择让k8s走独立containerd还要确认containerd的cgroup配置也指向systemd。编辑/etc/containerd/config.toml确保有这一段[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc] runtime_type io.containerd.runc.v2 [plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true如果config.toml不存在可以先用默认配置生成sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml然后修改上述字段最后sudo systemctl restart containerd这一步很多教程不提但恰恰是k8s部署时kubelet报“CRI v1 runtime API is not implemented”或cgroup错乱的高发原因。4. Ubuntu网络暗坑ufw、iptables与ip_forward的协同和冲突4.1 Docker、ufw、iptables三方的规则博弈Ubuntu自带ufw前端底层依然是iptables。Docker启动时会直接往iptables里塞规则一个NAT表的POSTROUTING链做容器出网一个FILTER表的DOCKER链做端口隔离一个FORWARD链控制跨容器转发。k8s网络插件Calico、Flannel、Cilium上场后还会再加一套自己的规则。这三套规则共同工作本来没问题但ufw一旦开启或者执行ufw reload它可能把Docker生成的规则打断。最常见的现象是容器里能访问外网宿主机外部却访问不了docker run -p 8080:80发布的容器端口。执行ufw reload后已启动容器的网络瞬间断开。k8s NodePort访问不通但节点上curl127.0.0.1:30080又是通的。这事的根因是Docker和ufw都在改iptables顺序错乱时DOCKER链上那条允许转发到容器IP的规则会被ufw的策略顶掉。传统解法是修改/etc/default/ufw里的DEFAULT_FORWARD_POLICYACCEPT再写/etc/ufw/before.rules放行docker网段。这套逻辑在旧版本Docker上还行但新版本Docker对iptables的操作更激进越改越容易出问题。我的实际建议是面向k8s集群的Ubuntu节点不要开着ufw去和Docker纠缠。要么彻底关闭ufw把网络安全交给云安全组、VPC网络ACL或机房交换机来管要么你把ufw当成一个“只放行固定端口”的工具但你要清楚它和Docker的规则博弈随时可能翻车。4.2 ip_forward和bridge-nf-call-iptables的强制开启无论你用不用ufw内核转发参数必须开对。这是容器网络和Pod网络的双重基础。sudo sysctl net.ipv4.ip_forward正常应该输出1。如果是0容器即使有网络也出不了外网Pod跨节点通信也会断。这通常发生在你拿某个极简云镜像或容器默认快照装了Ubuntu的场景。写进sysctl配置保证重启不丢echo net.ipv4.ip_forward1 | sudo tee /etc/sysctl.d/99-docker-k8s.conf sudo sysctl -p /etc/sysctl.d/99-docker-k8s.conf接着是bridge-nf桥接流量经过iptables相关。老版本Ubuntu默认加载了br_netfilter模块但未必置为1。检查一下lsmod | grep br_netfilter cat /proc/sys/net/bridge/bridge-nf-call-iptables如果模块没加载sudo modprobe br_netfilter echo br_netfilter | sudo tee /etc/modules-load.d/br_netfilter.confmodprobe只对当前会话生效所以要写入/etc/modules-load.d/否则重启后又是老样子。都把模块加载了再把对应参数补进刚才的测试脚本里继续查echo net.bridge.bridge-nf-call-iptables1 | sudo tee -a /etc/sysctl.d/99-docker-k8s.conf echo net.bridge.bridge-nf-call-ip6tables1 | sudo tee -a /etc/sysctl.d/99-docker-k8s.conf sudo sysctl -p /etc/sysctl.d/99-docker-k8s.conf不开这个的后果很折磨人集群里的Pod能互Ping但Service的ClusterIP不通DNS解析一会儿好一会儿坏kubectl get nodes永远显示有节点NotReady。网络插件大多依赖桥接流量走iptables做标记和转发没有它整个CNI链路都在裸奔。4.3 面向k8s的最小防火墙放行策略如果你出于合规要求必须保留防火墙那就别用ufw这种和Docker抢方向盘的工具直接在自己熟悉的iptables体系里放行。k8s集群需要打通的服务端口如下服务端口方向kube-apiserver6443Master节点间、Node到Masteretcd2379-2380Master节点间kubelet10250所有节点互通NodePort服务30000-32767外部入口到节点Flannel VXLAN8472/UDP各节点间Calico BGP179/TCP各节点间生产环境里这些流量通常发生在内网网段放行时尽量限定来源网段不要对全0.0.0.0/0开放。比如只放行集群VPC网段10.0.0.0/8和Pod网段10.244.0.0/16访问10250和8472。最省心的姿势还是开头那句内网安全交给你上层的网络策略Ubuntu节点本地防火墙能关就关。等你在生产环境处理过一次“端口全通但Pod网络插件初始化失败”的问题就会明白多一层本地过滤就多一份故障排查负担。5. 现场排障启动失败、Desktop虚拟化报错与镜像拉取慢5.1 启动失败的排查链路Docker装完执行systemctl start docker结果半天没起来。这时候别急着反复systemctl restart先看日志journalctl -u docker --no-pager -n 100日志会直接告诉你失败在哪。下面是我遇到过的高频故障和对应处理日志关键信息根因处理方式failed to start daemon: error initializing graphdriver存储驱动初始化失败常见于内核太老或/var/lib/docker在特殊文件系统上切换overlay2确认内核支持必要时清空/var/lib/docker后重启iptables failed: Operation not permitted进程权限不足或容器环境无CAP_NET_ADMIN确认以root/systemd启动不要用用户进程手工拉起dockerdfailed to load listeners: no iptables support内核缺少iptables模块或模块未加载检查iptables、ip_tables、nf_tables模块“Device or resource busy”老进程占用docker.sock或/var/lib/docker杀掉残留dockerd进程清理pid文件再启动overlay mount failed/var/lib/docker所在的底层文件系统不支持overlay2的d_type特性换成支持d_type的文件系统或改用fuse-overlayfs兜底其中“overlay mount failed”这个坑在云平台上特别常见因为有些云盘的默认格式不是ext4而是xfs且mkfs时没开-n ftype1overlay2无法在上面正常工作。如果你必须用xfs重新格式化时记得加-n ftype1。还有一个容易忽略点/etc/docker/daemon.json本身。如果里面有语法错误或未知字段restart时Docker会悄无声息地回退到默认配置然后你的cgroup driver、镜像源全部失效。所以改完一定用docker info确认不要只看systemctl is-active。5.2 Docker Desktop的“virtualization support not detected”到底怎么回事如果你在Ubuntu上用的是Docker Desktop启动时遇到一段类似“virtualization support not detected, failed to start because virtualization support is not detected”的提示先别觉得是Docker坏了。这跟Docker本身没关系是Docker Desktop for Linux需要KVM虚拟化而你的环境没把KVM暴露出来。Docker Desktop在Linux上的底层逻辑是在轻量级虚拟机里跑一个完整的Docker环境这个虚拟机需要/dev/kvm设备才能硬件加速。在VMware或VirtualBox里装Ubuntu如果没开启“虚拟化引擎/嵌套虚拟化”选项/dev/kvm就不存在Docker Desktop自然起不来。自查命令ls -l /dev/kvm egrep -c (vmx|svm) /proc/cpuinfo/dev/kvm不存在或egrep输出为0就说明CPU虚拟化没透传或没在BIOS里开启。物理机上需要进BIOS打开Intel VT-x或AMD-V虚拟机上需要把嵌套虚拟化选项打开。如果确实需要KVMUbuntu下的标准做法sudo apt install -y qemu-kvm libvirt-daemon-system virtinst bridge-utils sudo adduser $USER libvirt装完重新登录一般/dev/kvm就能用了。但回到k8s基础环境这个主题我给的建议很直接每个k8s节点都装Docker Desktop是完全没必要的。Docker Desktop适合本地图形化开发不适合当集群节点运行时。你的目标既然是k8s那就放弃这条路用Docker Engine就好。别在一个工具的形态问题上浪费时间。5.3 镜像拉取慢与容器网络不通的定位方法“docker pull centos:7”跑到一半卡住网上大多是统一答复“换加速器”。但你配了加速器还是慢就得往深一层查。先看拉镜时Docker daemon在干嘛journalctl -u docker -f另开一个终端执行docker pull如果日志里反复出现“Waiting”或连接超时大概率是当前网络环境到目标镜像服务器链路有问题。这时候确认daemon.json里的registry-mirrors是否真的被加载docker info | grep -A 5 Registry Mirrors如果输出为空说明配置没生效回去查文件路径和权限重启Docker。容器网络不通是另一个高频问题。现象可以细分成几类容器内ping 8.8.8.8不通但ping 网关/宿主机IP通。最常见就是/etc/resolv.conf没配好或者Docker NAT规则丢失。容器内完全没网络先看宿主机能上网吗再看ip_forward是不是0最后看NAT表sudo iptables -t nat -L POSTROUTING -v正常会有一条类似MASQUERADE all -- 172.17.0.0/16 anywhere的规则。如果这条规则没了重建Docker默认网络sudo systemctl restart docker或者手动补sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE宿主机能访问容器发布的端口但外部机器访问不了。这种基本回到第4节的ufw/iptables博弈问题先把ufw status关掉测试再决定要不要保留它。6. 配置完成后的自检清单从单机Docker到k8s基础环境就绪6.1 一键体检脚本与输出解读配置完成后别急着kubeadm init先用下面这段脚本做一次全身体检echo Docker 服务 systemctl is-enabled docker systemctl is-active docker echo containerd 服务 systemctl is-enabled containerd systemctl is-active containerd echo cgroup 驱动 docker info --format {{.CgroupDriver}} echo 存储驱动 docker info --format {{.Driver}} echo ip_forward cat /proc/sys/net/ipv4/ip_forward echo br_netfilter 模块 lsmod | grep br_netfilter || echo 模块未加载 echo 桥接流量配置 cat /proc/sys/net/bridge/bridge-nf-call-iptables echo swap swapon --show || echo swap 已关闭 echo CRI socket ls -l /var/run/containerd/containerd.sock逐项看Docker和containerd都应该是enabled且active缺一不可。如果containerd没起来kubelet后面找不到运行时。cgroup driver输出必须是systemd输出cgroupfs就是配置没生效。存储驱动输出overlay2vfs就要回炉重造。ip_forward必须为1br_netfilter必须能查到模块bridge-nf-call-iptables必须为1。swap输出为空代表已关闭。如果还有swap执行sudo swapoff -a然后注释掉/etc/fstab里对应行否则重启后swap又回来了。CRI socket文件存在说明独立containerd正常对外提供了CRI接口。6.2 面向k8s的专项验证与生产习惯体检脚本全过之后再用crictl验证一次containerd的CRI链路sudo apt install -y cri-tools crictl version crictl pull busybox:1.28 crictl imagescrictl version能看到客户端和服务端版本crictl pull能拉镜像说明CRI socket工作正常。这一步验证的是“kubelet将来能不能通过CRI管理镜像和容器”比docker ps可靠得多。最后说两个生产习惯第一如果节点只是当k8s节点用不负责镜像构建建议只装docker-ce-cli不装docker-ce本体。这样你依然能用docker pull、docker inspect这些命令调试但系统里少了一个dockerd常驻进程也少了一套iptables搅局的可能。需要构建镜像时单独搞一台构建机装完整Docker。第二daemon.json里的cgroup参数和containerd的SystemdCgroup配置每次升级Docker或containerd版本后都要重新确认一遍。升级工具偶尔会覆盖配置或引入新默认值k8s层面的兼容性全靠这几行配置兜底。按这套流程把Ubuntu准备妥当之后kubeadm init那一步基本不会再因为容器运行的底层问题翻车。我自己这些年养成的习惯是拿到一台新Ubuntu服务器第一件事就是写sysctl配置、加载br_netfilter、关swap然后装Docker和containerd、对齐cgroup最后才谈建集群。顺序反了或者在配置不全的情况下硬上集群后面花在排障上的时间绝对比你现在多写几行配置要多得多。
返回列表