ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Ubuntu 18.04 部署 Kubernetes 1.18 全流程实战与避坑指南

Ubuntu 18.04 部署 Kubernetes 1.18 全流程实战与避坑指南

1. 为什么这个组合值得单独拿出来讲

Ubuntu 18.04 加 Kubernetes 1.18,这个组合放在今天来看确实有点“考古”的味道,但我可以很负责任地说,现在仍然有不少团队在内部测试环境、离线机房、教学实验室里跑着类似的技术栈。原因很实在:老服务器的内核版本卡在那里,业务依赖的容器运行时还没做新版本适配,或者干脆就是一套跑了很久的私有集群,没人敢轻易动它。所以把这两个版本凑在一起部署,不是猎奇,而是真实存在的需求。

这个内容适合谁看?一类是手里有 Ubuntu 18.04 机器、想从零搭一套 Kubernetes 练手或做内部验证的运维和开发人员;另一类是在离线或半离线环境里需要快速复现一套可用集群的工程师。它解决的核心问题就一个:在系统版本和容器编排版本都偏旧的前提下,怎么把集群稳稳当当地拉起来,并且知道每一步为什么这么做。

我自己前前后后在不同环境里装过不下十次,踩过的坑包括镜像拉不下来、kubelet 反复重启、节点 NotReady、网络插件和内核模块打架等等。下面我把整套流程拆开,从设计思路到实操细节,再到问题排查,尽量讲透。你照着做,大概率能少走很多弯路。

2. 整体部署思路与方案选型拆解

2.1 为什么选 kubeadm 而不是二进制手工部署

Kubernetes 的部署方式大致分三类:二进制手工部署、kubeadm 工具部署、以及各种发行版封装好的方案。在这个场景下,我强烈建议用 kubeadm,理由有三条。

第一,Ubuntu 18.04 的系统环境相对干净,kubeadm 对它的支持在 1.18 这个版本上已经相当成熟,官方文档里明确把它列为验证过的平台。第二,kubeadm 把证书生成、组件配置、启动参数这些容易出错的环节都做了标准化,你不需要去背每个组件的 flag 含义。第三,出问题的时候,kubeadm 的报错信息相对明确,排查路径清晰,不像手工部署那样一个参数写错就找半天。

当然,kubeadm 也不是万能的。它默认不帮你装网络插件,也不管你用什么容器运行时,这些需要你自己补上。但这恰恰是好事,因为你可以根据实际环境选择最合适的方案,而不是被某个发行版绑死。

2.2 容器运行时为什么选 Docker 而不是 containerd

Kubernetes 1.18 这个时间点,Docker 还是最主流的选择,containerd 虽然已经在后面追,但生态和文档丰富度还差一截。更重要的是,很多老环境的镜像仓库里只有 Docker 格式的镜像,用 containerd 还得额外处理命名空间和配置,徒增复杂度。

这里有个关键点要注意:Kubernetes 1.18 对 Docker 的版本有要求,太新的 Docker 反而可能不兼容。我实测下来,Docker 19.03 这个系列和 Kubernetes 1.18 配合最稳。如果你用的是 Ubuntu 18.04 自带的 Docker 版本,大概率太旧,需要手动升级或指定版本安装。

2.3 网络插件选型:Calico 还是 Flannel

网络插件这块,Flannel 和 Calico 是两个最常见的选项。Flannel 配置简单,适合快速验证;Calico 功能更强,支持网络策略,适合对隔离有要求的场景。

在 Ubuntu 18.04 加 Kubernetes 1.18 这个组合里,我倾向于先用 Flannel 把集群跑通,确认基础功能没问题之后,如果确实需要网络策略,再换成 Calico。原因很简单:Flannel 的故障点少,出问题容易定位;Calico 依赖 BGP 和 iptables 的配合,在老内核上偶尔会有兼容性小毛病,排查起来更费时间。

提示:不管你选哪个插件,都要确保所有节点的内核模块加载正确,尤其是 br_netfilter 和 overlay,这两个是容器网络的基础。

2.4 版本锁定与离线镜像准备

这一步很多人会忽略,但恰恰是最影响成功率的。Kubernetes 1.18 对应的组件版本必须锁死,不能让它自动升级。具体来说,kubeadm、kubelet、kubectl 三个包的版本要完全一致,都是 1.18.x。如果你用 apt 安装,一定要用 apt-mark hold 把它们锁住,否则某次系统更新可能就把 kubelet 升到不兼容的版本,集群直接挂掉。

离线环境的话,需要提前把所需镜像全部拉下来,打包成 tar 文件,再传到目标机器上加载。镜像列表可以用 kubeadm config images list 命令生成,这个后面实操部分会详细讲。

3. 系统环境准备与核心参数配置

3.1 主机名与 hosts 解析的规范做法

集群里每台机器的主机名必须唯一,而且要和 hosts 文件里的解析一致。我见过太多因为主机名重复或者解析不对导致节点注册失败的案例。

具体操作上,用 hostnamectl set-hostname 设置主机名,然后在 /etc/hosts 里把每个节点的 IP 和主机名对应写进去。注意不要只写 master 的,所有节点都要互相能解析。如果你有内部 DNS,那更好,但 hosts 文件是最稳妥的兜底方案。

# 在每台机器上执行,替换为实际主机名 hostnamectl set-hostname k8s-master # 编辑 /etc/hosts,加入所有节点的映射 cat >> /etc/hosts <<EOF 192.168.1.10 k8s-master 192.168.1.11 k8s-node1 192.168.1.12 k8s-node2 EOF

3.2 关闭 swap 的必要性与操作细节

Kubernetes 要求关闭 swap,这不是随便定的规矩。kubelet 在计算节点资源的时候,如果 swap 开着,内存的可用量计算会不准,可能导致调度决策出错。所以这一步必须做,而且要做彻底。

临时关闭用 swapoff -a,但重启后会失效。永久关闭需要编辑 /etc/fstab,把 swap 那一行注释掉。注意有些云主机的 swap 不是通过 fstab 管理的,那就需要检查 systemd 的 swap 单元,用 systemctl mask 把它屏蔽掉。

# 临时关闭 swapoff -a # 永久关闭,注释掉 fstab 中的 swap 行 sed -i '/swap/s/^/#/' /etc/fstab # 验证 free -h

3.3 内核模块加载与 sysctl 参数调优

容器网络依赖几个内核模块,必须确保它们在开机时自动加载。br_netfilter 负责网桥的流量过滤,overlay 是 OverlayFS 文件系统的基础,这两个缺一不可。

sysctl 参数方面,最核心的是 net.bridge.bridge-nf-call-iptables 和 net.ipv4.ip_forward。前者让网桥流量经过 iptables 处理,后者开启 IP 转发。这两个如果不对,Pod 之间可能通,但 Pod 到外网就不通,或者反过来。

# 加载内核模块 cat > /etc/modules-load.d/k8s.conf <<EOF br_netfilter overlay EOF modprobe br_netfilter modprobe overlay # 设置 sysctl 参数 cat > /etc/sysctl.d/k8s.conf <<EOF net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sysctl --system

注意:有些 Ubuntu 18.04 的镜像里,br_netfilter 模块可能被黑名单了,需要检查 /etc/modprobe.d/ 下有没有相关配置,有的话要移除或覆盖。

3.4 防火墙与 SELinux 的处理策略

Ubuntu 默认用的是 ufw,不是 firewalld。在集群环境里,我建议直接把 ufw 关掉,因为 Kubernetes 自己会管理 iptables 规则,ufw 的规则可能和它冲突。关掉之后,用安全组或者网络 ACL 来做外层防护。

SELinux 在 Ubuntu 上默认是不启用的,所以不用像 CentOS 那样去 setenforce 0。但如果你手动装过 SELinux,那还是要检查一下状态,确保它是 disabled 或 permissive。

# 关闭 ufw systemctl stop ufw systemctl disable ufw # 检查 SELinux 状态 sestatus 2>/dev/null || echo "SELinux not installed"

4. Docker 安装与 Kubernetes 组件部署实操

4.1 Docker 版本选择与安装步骤

前面说了,Docker 19.03 和 Kubernetes 1.18 配合最稳。安装的时候不要直接用 apt install docker.io,那个版本太旧。要用 Docker 官方的仓库来装。

具体步骤是:先装依赖包,然后添加 Docker 的 GPG 密钥和仓库地址,最后指定版本安装。安装完之后要配置 daemon.json,把 cgroup driver 改成 systemd,这一点非常关键,因为 kubelet 默认也用 systemd,两者不一致会导致 kubelet 启动失败。

# 安装依赖 apt-get update apt-get install -y apt-transport-https ca-certificates curl gnupg-agent software-properties-common # 添加 Docker 官方 GPG 密钥 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - # 添加仓库 add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable" # 安装指定版本 apt-get update apt-get install -y docker-ce=5:19.03.15~3-0~ubuntu-bionic docker-ce-cli=5:19.03.15~3-0~ubuntu-bionic containerd.io # 配置 daemon.json cat > /etc/docker/daemon.json <<EOF { "exec-opts": ["native.cgroupdriver=systemd"], "log-driver": "json-file", "log-opts": { "max-size": "100m" }, "storage-driver": "overlay2" } EOF systemctl daemon-reload systemctl restart docker systemctl enable docker

4.2 kubeadm、kubelet、kubectl 的安装与版本锁定

这三个组件的版本必须一致,都是 1.18.x。安装源用 Kubernetes 官方的 apt 仓库。装完之后立刻用 apt-mark hold 锁住,防止意外升级。

# 添加 Kubernetes 仓库 curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - cat > /etc/apt/sources.list.d/kubernetes.list <<EOF deb https://apt.kubernetes.io/ kubernetes-xenial main EOF apt-get update apt-get install -y kubelet=1.18.20-00 kubeadm=1.18.20-00 kubectl=1.18.20-00 # 锁定版本 apt-mark hold kubelet kubeadm kubectl # 验证版本 kubeadm version kubelet --version

提示:如果你的环境访问不了官方源,可以提前把 deb 包下载好,用 dpkg -i 安装,效果一样。

4.3 初始化 master 节点的完整命令与参数解读

初始化 master 用 kubeadm init,但直接跑默认参数可能会因为镜像拉不下来而卡住。所以要先指定镜像仓库,国内环境可以用阿里云的镜像地址。

命令里的参数逐个解释一下:--image-repository 指定镜像仓库;--kubernetes-version 锁定版本;--pod-network-cidr 指定 Pod 网段,这个要和后面网络插件的配置一致;--apiserver-advertise-address 指定 API Server 对外通告的地址,多网卡机器上必须指定,否则可能选错网卡。

kubeadm init \ --image-repository=registry.aliyuncs.com/google_containers \ --kubernetes-version=v1.18.20 \ --pod-network-cidr=10.244.0.0/16 \ --apiserver-advertise-address=192.168.1.10 \ --service-cidr=10.96.0.0/12

初始化成功后,会输出一段 join 命令,这个要保存好,后面加节点要用。同时按照提示配置 kubectl 的 kubeconfig。

mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config

4.4 网络插件部署与节点加入

Flannel 的部署很简单,直接 apply 一个 yaml 就行。但要注意,如果你的 Pod 网段不是默认的 10.244.0.0/16,需要先下载 yaml 修改里面的网段配置,再 apply。

# 下载并修改网段(如果需要) curl -O https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml # 编辑 kube-flannel.yml,把 Network 字段改成你的 pod-network-cidr kubectl apply -f kube-flannel.yml

节点加入用之前保存的 join 命令,在 node 节点上执行。执行前确保 node 节点也完成了前面所有的系统准备工作。

kubeadm join 192.168.1.10:6443 --token xxxxxx.xxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

加入之后,在 master 上执行 kubectl get nodes,看到节点状态变成 Ready 就说明成功了。如果一直是 NotReady,大概率是网络插件还没完全启动,等一两分钟再查。

5. 常见故障排查与避坑经验实录

5.1 镜像拉取失败的排查路径

镜像拉不下来是最常见的问题,表现就是 kubeadm init 卡在某个步骤不动,或者 kubelet 一直报 ImagePullBackOff。排查思路分三步:先确认网络能不能通到镜像仓库,再确认镜像名称和 tag 对不对,最后检查 Docker 的配置有没有问题。

如果是离线环境,需要提前在有网的机器上把镜像拉下来,用 docker save 导出成 tar,传到目标机器后用 docker load 导入。kubeadm config images list 可以列出所有需要的镜像,照着这个列表准备就行。

# 列出所需镜像 kubeadm config images list --kubernetes-version=v1.18.20 # 导出镜像 docker save -o k8s-images.tar k8s.gcr.io/kube-apiserver:v1.18.20 ... # 导入镜像 docker load -i k8s-images.tar

5.2 kubelet 启动失败的典型原因

kubelet 起不来,九成以上是 cgroup driver 不一致导致的。Docker 用 systemd,kubelet 默认也用 systemd,但如果 Docker 的 daemon.json 没配好,它可能用的是 cgroupfs,这时候 kubelet 就会报错。

另一个常见原因是 swap 没关干净。有时候 fstab 注释了,但 systemd 的 swap 单元还在,需要额外 mask 掉。排查的时候用 journalctl -u kubelet -f 看日志,报错信息一般都很明确。

# 查看 kubelet 日志 journalctl -u kubelet -f # 检查 cgroup driver docker info | grep -i cgroup

5.3 节点 NotReady 的排查顺序

节点 NotReady 的原因很多,我一般按这个顺序排查:先看网络插件 Pod 是不是 Running,再看节点上的 kube-proxy 有没有正常启动,然后检查内核模块和 sysctl 参数,最后看证书有没有过期。

网络插件没起来是最常见的,用 kubectl get pods -n kube-system 看一下 flannel 或 calico 的 Pod 状态。如果是 CrashLoopBackOff,看日志找具体原因,多半是网段配置不对或者内核模块没加载。

现象可能原因排查命令
节点 NotReady网络插件未就绪kubectl get pods -n kube-system
kubelet 反复重启cgroup driver 不一致journalctl -u kubelet
Pod 无法跨节点通信内核模块未加载lsmod | grep br_netfilter
镜像拉取失败仓库地址不可达docker pull 测试
证书错误证书过期或时间不同步kubeadm certs check-expiration

5.4 时间同步与证书问题的隐藏坑

这个问题很隐蔽,但一旦出现就很难查。Kubernetes 的证书对时间敏感,如果节点之间时间差太大,证书验证会失败,表现为节点加入不了或者 API 调用报 x509 错误。

Ubuntu 18.04 默认用 systemd-timesyncd 做时间同步,但有些环境里这个服务没启用,或者 NTP 服务器配得不对。部署前一定要确认所有节点时间一致,用 timedatectl 检查。

# 检查时间同步状态 timedatectl status # 如果没同步,手动配置 NTP apt-get install -y chrony systemctl enable chrony systemctl start chrony

注意:证书问题在集群跑了一段时间后才会暴露,所以部署时最好把证书有效期检查加到日常巡检里。kubeadm 提供了 certs check-expiration 命令,定期跑一下没坏处。

6. 集群验证与日常维护要点

6.1 部署一个测试应用验证集群功能

集群搭好之后,别急着上业务,先跑一个简单的测试应用确认基础功能正常。我一般用 nginx 或者 busybox 做验证,创建一个 Deployment 和一个 Service,然后从不同节点访问,看网络是否通。

# 创建测试 Deployment kubectl create deployment nginx-test --image=nginx:1.18 # 暴露 Service kubectl expose deployment nginx-test --port=80 --type=NodePort # 查看状态 kubectl get pods,svc -o wide

如果 Pod 能正常 Running,Service 能访问,跨节点通信也没问题,那这套集群的基础功能就算通了。接下来再根据实际需求去配置存储、Ingress、监控这些。

6.2 版本锁定与升级的注意事项

前面反复强调版本锁定,这里再展开说一下。Kubernetes 的版本升级不是小事,尤其是跨小版本升级,必须按照官方推荐的顺序来。1.18 升 1.19 可以,但 1.18 直接升 1.20 就可能出问题。

升级前一定要备份 etcd 数据,这个是最关键的。etcd 备份用 etcdctl snapshot save 命令,备份文件要存到安全的地方。升级过程用 kubeadm upgrade plan 先看计划,再按步骤执行。

# 备份 etcd ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-snapshot.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key # 查看升级计划 kubeadm upgrade plan

6.3 资源监控与日志收集的简易方案

集群跑起来之后,至少要有个基本的监控。资源占用用 kubectl top 就能看,但需要先装 metrics-server。日志方面,如果不想上 EFK 那套重家伙,可以先用 kubectl logs 和 journalctl 顶着,等业务量上来了再考虑集中式日志方案。

metrics-server 的部署要注意版本兼容性,1.18 对应的 metrics-server 版本不能太新。装好之后等几分钟,kubectl top nodes 和 kubectl top pods 就能用了。

# 部署 metrics-server kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.3.7/components.yaml # 验证 kubectl top nodes

提示:metrics-server 默认需要证书验证,如果集群用的是自签证书,可能需要在启动参数里加 --kubelet-insecure-tls,测试环境可以这么干,生产环境还是要配好证书。

6.4 我踩过的几个印象深刻的坑

第一个坑是主机名大小写问题。有次我把主机名设成 K8s-Master,结果 kubelet 注册的时候变成小写,和 hosts 里的解析对不上,节点一直 NotReady。后来统一用全小写,问题解决。

第二个坑是 Docker 的 storage-driver。Ubuntu 18.04 默认可能用 overlay2,但如果内核版本太低,overlay2 可能不支持,需要降级到 overlay。这个用 docker info 看 Storage Driver 那一行就能确认。

第三个坑是 Flannel 的网段和 Docker 默认网段冲突。Docker 默认用 172.17.0.0/16,如果 Flannel 也配成这个网段,就会打架。所以 Pod 网段最好避开 172.17,用 10.244 这种。

第四个坑是节点加入时 token 过期。kubeadm 生成的 token 默认 24 小时有效,过期了就得重新生成。用 kubeadm token create 可以生成新的,然后配合 --discovery-token-ca-cert-hash 重新加入。

# 重新生成 token kubeadm token create --print-join-command

这套东西说起来不复杂,但每个环节都有细节,一个不注意就可能卡住。我的建议是第一次部署的时候把每一步的输出都记下来,形成自己的检查清单,后面再装就快了。集群跑稳之后,定期做健康检查,证书有效期、etcd 状态、节点资源这些都要关注,别等出问题了才去查。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进