DevOps全链路实战 | 第 2 天:K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件

第 2/18 天

引言

在第 1 天,我们把「从代码提交到灰度发布」的完整 DevOps 管道蓝图摊在了桌面上:Git 仓库、CI 流水线、镜像仓库、GitOps 控制器、可观测性组件、渐进式交付控制器——这些子系统的边界和责任划分决定了后面每一天的落地路径。但蓝图落纸的第一步,永远是从一块”能跑的 Kubernetes 底座”开始。

第 2 天,我们聚焦 K8s 集群本身的搭建。为了让这套环境在真实生产环境里也能被复制,我们选择 kubeadm 离线部署 + Cilium 网络插件 的组合。离线部署解决了内网/等保环境的镜像拉取难题;Cilium 用 eBPF 直接替代了传统的 iptables + CNI 组合,既能让 Pod 之间零额外跳数通信,又能为后续的 NetworkPolicy、L7 观测和 Gateway API 打下地基。

K8s

为什么选 kubeadm + Cilium

kubeadm:生产级、幂等、可版本化

kubeadm 是 Kubernetes 官方推荐的集群初始化工具,它把「装控制面组件 → 生成证书 → 写 Kubeconfig → 打标签 → 装 CoreDNS/Calico 或自定义 CNI」这一长串步骤封装为几个命令。相比 minikube、k3s 这类”一键玩具”,kubeadm 的每一次 kubeadm init/join 都可以被复现到生产环境;相比手工装 kubelite/kubeadm-static,它又提供了配置即代码的能力。

它有一个”离线模式”——kubeadm init --config 加一份 imageRepository: "" 的 YAML,就能跳过默认从 quay.io 拉镜像的动作,完全依赖预下载的本地镜像。

Cilium:eBPF 原生网络

Cilium 是一个 CNI 插件,它不依赖 CNI 提供的 iptables 规则链来做 Pod 之间流量转发,而是把流量处理下沉到内核 eBPF 程序。这带来三个直接收益:

  • 性能:Pod 到 Pod 的南北向流量少跳一层用户态代理,同节点 Pod 通信甚至能走 eBPF map 直连。
  • NetworkPolicy 零开销:策略编译进 eBPF map,Pod 启动即生效,不依赖 iptables 重建。
  • 可观测性入口:Cilium Hubble 后续可直接提供 L3-L7 追踪,与今天的 Prometheus/Grafana 天然衔接。

环境准备

硬件与系统

我们准备三台机器,均为 Ubuntu 22.04 LTS:

角色 主机名 IP
控制面 k8s-master 192.168.10.10
工作节点 k8s-node-01 192.168.10.11
工作节点 k8s-node-02 192.168.10.12

系统前置操作(每台都执行):

💻 代码示例

# 关闭 swap 与防火墙,加载 overlayfs / br_netfilter 内核模块

sudo swapoff -a && sed -i '/ swap / s/^/#/' /etc/fstab

sudo systemctl stop firewalld ufw && sudo systemctl disable firewalld ufw

 

sudo modprobe overlay && sudo modprobe br_netfilter

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf

overlay

br_netfilter

EOF

 

# 关闭透明大页与 SELinux(如启用)

echo never | sudo tee /sys/kernel/mm/transparent_hugepage/enabled

echo never | sudo tee /sys/kernel/mm/transparent_hugepage/defrag

sudo setenforce 0 2>/dev/null || true

 

# kubelet 内核参数

cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf

net.bridge.bridge-nf-call-iptables = 1

net.bridge.bridge-nf-call-ip6tables = 1

net.ipv4.ip_forward = 1

net.core.somaxconn = 32768

net.ipv4.tcp_tw_reuse = 1

fs.inotify.max_user_watches = 524288

fs.nr_open = 2097152

vm.swappiness = 0

EOF

sudo sysctl –system

安装 containerd 与 kubectl / kubeadm

选择 containerd 而不是 Docker,是因为它和 K8s 的版本节奏一致(CRI 原生,无 dockershim 中转):

💻 代码示例

# 安装 containerd

sudo apt-get install -y containerd

 

# 生成默认配置并启用 SystemdCgroup

sudo containerd config default | sudo tee /etc/containerd/config.toml > /dev/null

sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

sudo sed -i 's/sandbox_image = "registry.k8s.io/pause:sandbox-image"/sandbox_image = "registry.aliyuncs.com/google_containers/pause:3.9"/' /etc/containerd/config.toml

sudo systemctl restart containerd

 

# 安装 kubeadm 1.28.x / kubectl / kubelet

apt-get install -y apt-transport-https ca-certificates curl gnupg

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key

| sudo gpg –dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /'

| sudo tee /etc/apt/sources.list.d/kubernetes.list

sudo apt-get update

sudo apt-get install -y kubelet-1.28.* kubeadm-1.28.* kubectl-1.28.*

sudo systemctl enable –now kubelet

离线镜像准备

内网环境的最大坑是”镜像源不可达”。我们把控制面与工作节点所需的镜像全部预下载到 master,再推到内网 Harbor 或 air-gap 目录:

💻 代码示例

# 在 master 上导出所有 kubeadm 组件镜像

kubeadm config images list v1.28.4 | while read img; do

ctr -n k8s.io images pull "$img" || ctr -n k8s.io images pull "registry.aliyuncs.com/google_containers/$(basename $img):$(echo $img | awk -F: '{print $NF}')"

done

 

# 额外准备 Cilium 与 kube-proxy 镜像(版本需与后面 chart 对齐)

MCR=mcr.microsoft.com; QIO=quay.io

for img in

"$QIO/cilium/cilium:v1.14.7"

"$QIO/cilium/cilium-operator-generic:v1.14.7"

"$MCR/mcr.microsoft.com/cse/kubernetes/kube-proxy:v1.28.4"; do

ctr -n k8s.io images pull "$img"

done

 

# 打包成离线 tar(可复制到 air-gapped 节点)

mkdir -p /opt/k8s-images

ctr -n k8s.io images export /opt/k8s-images/k8s-base-1.28.4.tar

k8s.gcr.io/kube-apiserver:v1.28.4

k8s.gcr.io/kube-controller-manager:v1.28.4

k8s.gcr.io/kube-scheduler:v1.28.4

k8s.gcr.io/kube-proxy:v1.28.4

k8s.gcr.io/pause:3.9

k8s.gcr.io/coredns:v1.10.1

k8s.gcr.io/etcd:3.5.9-0

quay.io/cilium/cilium:v1.14.7

quay.io/cilium/cilium-operator-generic:v1.14.7

 

# 分发到其它节点

rsync -avz –progress /opt/k8s-images/ root@k8s-node-01:/opt/k8s-images/

rsync -avz –progress /opt/k8s-images/ root@k8s-node-02:/opt/k8s-images/

ssh k8s-node-0{1,2} 'ctr -n k8s.io images import /opt/k8s-images/k8s-base-1.28.4.tar'

kubeadm 初始化

准备 kubeadm-config

我们不使用 kubeadm init 的默认配置,而是显式给一份 YAML,避免踩默认 CIDR 冲突的坑:

💻 代码示例

# /root/kubeadm-config.yaml

apiVersion: kubeadm.k8s.io/v1beta4

kind: InitConfiguration

nodeRegistration:

name: k8s-master

criSocket: unix:///var/run/containerd/containerd.sock

localAPIEndpoint:

advertiseAddress: 192.168.10.10

bindPort: 6443

controlPlaneEndpoint: "192.168.10.10:6443"

timeout: 30m

apiServerExtraArgs:

enable-admission-plugins: NodeRestriction,ServiceAccount

apiVersion: kubeadm.k8s.io/v1beta4

kind: ClusterConfiguration

kubernetesVersion: v1.28.4

imageRepository: "" # 关键:禁用默认 imageRepository,走本地已导入镜像

networking:

podSubnet: 10.244.0.0/16 # Cilium 分配给 Pod 的默认地址段

serviceSubnet: 10.96.0.0/12

apiVersion: kubelet.config.k8s.io/v1beta1

kind: KubeletConfiguration

cgroupDriver: system

containerLogMaxSize: 20Mi

containerLogMaxFiles: 3

evictionHard:

nodefs.available: "10%"

nodefs.inodesFree: "5%"

memory.available: "100Mi"

执行初始化

💻 代码示例

sudo kubeadm init config.yaml –upload-certs –ignore-preflight-errors=NumCPU

 

# 保存 kubeconfig

mkdir -p $HOME/.kube

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

sudo chown $(id -u):$(id -g) $HOME/.kube/config

 

# 验证组件

kubectl get nodes

kubectl get pods -A

# 此时 CoreDNS 可能 CrashLoop,因为还没装 CNI —— 正常现象

加入工作节点

kubeadm 会输出一段 kubeadm join 命令,我们显式写出来便于复用:

💻 代码示例

# 在 node-01 / node-02 上执行

sudo kubeadm join 192.168.10.10:6443

–token abcdef.0123456789abcdef

–discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

–node-name k8s-node-01

–cri-socket unix:///var/run/containerd/containerd.sock

执行后回到 master:

💻 代码示例

kubectl get nodes

# NAME STATUS ROLES AGE VERSION

# k8s-master NotReady master,worker 2m v1.28.4

# k8s-node-01 NotReady <none> 20s v1.28.4

# k8s-node-02 NotReady <none> 20s v1.28.4

三个节点 NotReady 是因为 CNI 未安装,Pod 网络不通。下一步装 Cilium 就会自动恢复。

Cilium 网络插件安装

添加 Cilium Helm 仓库

💻 代码示例

helm repo add cilium https://helm.cilium.io/

helm repo update

helm search repo cilium –version 1.14.7

定制 values.yaml

生产落地 Cilium 时,我们至少要做四件事:关闭 host routing 走 eBPF 隧道、指定 registry、开启 Hubble、禁用 kube-proxy 让其由 Cilium 接管。

💻 代码示例

# /root/cilium-values.yaml

cluster:

name: "stellardata"

 

# 内网镜像仓库

global:

# 若已把 Cilium 镜像推到 Harbor:换成你的域名

image:

repository: quay.io/cilium/cilium

k8sServiceHost: "" # 让 Cilium 从 in-cluster API 自动发现

k8sServicePort: 6443

 

# eBPF 网络模式

ipam:

mode: kubernetes # 用 kube-apiserver 做 IPAM,兼容 kubeadm 默认 CIDR

bgp:

enabled: false

 

kubeProxyReplacement:

strict: false

mode: "native-routing" # eBPF 模式,取代 kube-proxy 的 iptables 规则

 

egress:

enabled: false # 生产需要时再打开

policy: default-deny

 

hubble:

enabled: true # L3-L7 可观测性

relay:

enabled: true

ui:

enabled: true

 

# 关闭默认 iptables 模式,走 eBPF

ipv4:

masquerade: true # 出公网时 SNAT

 

# 资源请求,避免 Cilium agent 被 OOMKill

agent:

resources:

requests:

cpu: 300m

memory: 256Mi

limits:

memory: 512Mi

安装 Cilium

💻 代码示例

helm upgrade –install cilium cilium/cilium

–namespace kube-system

–version 1.14.7

–values /root/cilium-values.yaml

–set global.kubeProxyReplacement=true

–set hubble.relay.enabled=true

–atomic –wait –timeout 10m

安装过程会打印大量 eBPF 编译日志。当看到 cilium-agent-* 三副本全部 Runningcilium-operator-* 一副本 Running,即代表成功。

卸载 kube-proxy(可选但推荐)

因为 Cilium 已接管 kube-proxy 的工作,我们可以清理 kube-proxy 让控制面更纯净:

💻 代码示例

# 检查 kube-proxy 是否由 kubeadm 静态托管

ls /etc/kubernetes/manifests/kube-proxy.yaml &&

sudo kubeadm delete kube-proxy –kubeconfig /etc/kubernetes/admin.conf

 

kubectl delete ds -n kube-system kube-proxy 2>/dev/null || true

kubectl get ds -n kube-system

验证 Pod 网络

💻 代码示例

kubectl wait –for=condition=Ready pod -A –timeout=300s

kubectl get nodes -o wide

kubectl get pods -n kube-system

 

# Cilium 状态检查

kubectl exec -n kube-system deploy/cilium-operator —

cilium-dbg status | head -40

一条简单的连通性验证:

💻 代码示例

kubectl run -it –rm busybox –image=mcr.microsoft.com/busybox –restart=Never — nslookup kubernetes.default

kubectl run -it –rm –image=mcr.microsoft.com/hello-world test –restart=Never –command — sleep 30

常见问题

Q1:kubeadm initcontainer runtime network plugin not initialized

这是最常见的报错,通常是因为 CNI 没装或 CIDR 冲突。检查 kubeadm-config.yaml 里的 networking.podSubnet,确保它和后续 Cilium / Calico 的 IPAM 段一致;然后 kubeadm reset 重来。

Q2:Cilium agent 一直 CrashLoopBackOff,日志出现 BPF programs are not loaded

多半是内核或主机名问题。确认:

💻 代码示例

uname -r # 建议 >= 5.10

sysctl net.core.bpf_jit_enable net.core.bpf_jit_harden

grep cilium /var/log/messages | tail

若节点被 SELinux 拦截 eBPF 加载,暂时 setenforce 0 验证。

Q3:Node 状态卡在 NotReady,原因是 NetworkUnavailable=True

90% 是 CNI 还没起来。用 kubectl describe node <name> 看 Events,找到 Cilium agent 的调度节点,kubectl logs -n kube-system <cilium-agent-pod> 排查。

Q4:Pod 到 Pod 通,但访问 Service ClusterIP 不通?

检查 Cilium 是否替代了 kube-proxy:kubectl get pods -n kube-system | grep cilium,确认 cilium-agentRunning 状态;再用 cilium statusProxy 段,BPF 段是否有 IPCache 条目。

Q5:离线环境 ctr images importkubeadm init 仍报镜像拉取失败?

多半是 image name 与 containerd 里保留的 tag 不一致(比如 tag 被截断)。用 ctr -n k8s.io images ls 核对实际导入的完整镜像名,并在 imageRepository 里显式覆盖对应 tag。

总结

第 2 天,我们完成了三件事:

  1. kubeadm 离线部署:以一份 kubeadm-config.yaml 作为代码化的集群拓扑描述,ctr + tar 完成所有控制面镜像的离线导入,三节点集群从空机到 Ready 全程不依赖公网镜像仓库。
  2. Cilium 网络插件:以 eBPF 为内核底座替代 kube-proxy 与传统 iptables,为后续 NetworkPolicy、Hubble 观测、Gateway API 铺路。
  3. 调试套路:把 kubeadm 与 Cilium 常见的 5 类问题沉淀为可执行的诊断脚本。

这套底座是后面 16 天所有操作的地基:Harbor 会跑在 k8s 上,GitLab CI 会 build 镜像后推 Harbor,Argo CD 会 apply 到本集群,Prometheus 会 scrape 本集群指标,Argo Rollouts 会在本集群上做金丝雀。

下期预告

第 3 天:Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送

我们将从今天搭好的 k8s 集群出发,用 Helm 一键部署 Harbor 全套(registry、core、jobservice、portal、redis、postgresql、harbor-db),配置 500MB 单镜像上限与保留策略,然后用 crane + docker pushhello-world 与一个自定义 nginx 镜像推到私有仓库,为后面 GitLab CI 的镜像构建环节做好准备。

系列目录

  • 第 1 天:全链路架构总览:从代码提交到灰度发布的完整管道设计
  • 第 2 天:K8s 集群与网络基础:kubeadm 离线部署与 Cilium 网络插件 ← 本篇
  • 第 3 天:Harbor 私有镜像仓库部署与验证:Helm 安装与镜像推送 🔜
  • 第 4 天:GitLab CE 自托管部署:代码仓库与项目管理平台 🔜
  • 第 5 天:GitLab Runner 配置:K8s Executor 与 RBAC 权限设置 🔜
  • 第 6 天:GitLab CI 流水线搭建:.gitlab-ci.yml 与 Kaniko 无守护进程构建 🔜
  • 第 7 天:Harbor 镜像版本管理与 Tag 策略配置 🔜
  • 第 8 天:Argo CD 部署与 GitOps 配置仓库创建 🔜
  • 第 9 天:CI 与 CD 联动:从代码提交到部署的全自动闭环 🔜
  • 第 10 天:Prometheus 部署:kube-prometheus-stack 与 ServiceMonitor 指标采集 🔜
  • 第 11 天:Grafana 看板搭建:K8s 标准面板与自定义业务看板 🔜
  • 第 12 天:告警规则与 Alertmanager:钉钉/邮件通知渠道配置 🔜
  • 第 13 天:Argo Rollouts 安装与基础概念:CRD 与 Rollout 资源定义 🔜
  • 第 14 天:金丝雀发布实战:setWeight 流量分割与 pause 步骤 🔜
  • 第 15 天:AnalysisTemplate 指标分析与自动回滚机制 🔜
  • 第 16 天:全链路端到端演示:代码提交→构建→灰度→监控→告警完整流程 🔜
  • 第 17 天:生产化加固要点:Harbor/GitLab/Argo CD 高可用方案 🔜
  • 第 18 天:供应链安全闭环:Trivy 扫描 + cosign 签名 + Kyverno 验签 🔜
微信二维码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片快捷回复

    暂无评论内容