完整安装总 Runbook:5 台 Ubuntu 到可用平台
这篇回答“我拿到 5 台空 Ubuntu 机器,怎么一步步装到当前这套平台”。如果机器上已经有集群,先不要重跑安装命令,先用 Day 0 新手接管 Runbook 看状态。
0. 安装目标
装完后具备:
- 3 control plane + 2 worker 的 K8s v1.30 集群。
- 本地 HAProxy 做 apiserver 高可用入口:
k8s-api:16443。 - containerd 作为容器运行时。
- Cilium 做 CNI 和 Hubble 流量观测。
- Longhorn 做默认 StorageClass。
- kube-prometheus-stack + Loki 做监控和日志。
- Harbor + Gitea + Jenkins + ArgoCD 做镜像、源码、CI、CD。
组件关系:
kubectl -> k8s-api:16443 -> HAProxy -> 3 个 apiserver
-> etcd 存集群状态
Pod -> Cilium -> 跨节点网络 / Service / NetworkPolicy
PVC -> Longhorn CSI -> 多副本块存储
Prometheus -> ServiceMonitor -> 采集指标 -> Grafana 展示
Promtail -> Loki -> Grafana 查日志
Gitea -> Jenkins/Kaniko -> Harbor -> ArgoCD -> K8s
1. 机器规划
| 节点 | 公网 IP | 内网 IP | hostname | 角色 |
|---|---|---|---|---|
| 1 | 154.201.73.31 | 10.0.24.31 | k8s-cp-1 | init control plane |
| 2 | 154.201.73.81 | 10.0.24.29 | k8s-cp-2 | control plane |
| 3 | 45.205.31.214 | 10.0.24.32 | k8s-cp-3 | control plane |
| 4 | 45.205.31.180 | 10.0.24.28 | k8s-w-1 | worker |
| 5 | 45.205.31.10 | 10.0.24.30 | k8s-w-2 | worker |
为什么 3 个 control plane:etcd 使用 Raft,多数派为 2,3 节点能容忍 1 台坏掉。2 个 control plane 坏 1 台就没有多数派,不是 HA。
为什么 worker 只有 2 台:学习集群资源有限,业务负载和系统组件可以先混跑;生产应把 control plane 和业务节点隔离。
2. 从本机重建 SSH 通道 + 检查系统
这一节在你自己的电脑(Mac / Linux)上执行,不是在服务器上。
2.0 重装系统后必做:重建 SSH(否则后面每条命令都连不上)
云控制台「重装系统 / 重置」会把机器恢复成全新 OS,带来两个后果,新手最容易卡在这里:
- 新系统 = 新 SSH 指纹。你本地
~/.ssh/known_hosts里记的还是旧机器指纹,再连会被当成「中间人攻击」直接拒连,报REMOTE HOST IDENTIFICATION HAS CHANGED。 - 新系统 =
authorized_keys被清空。原来的免密公钥没了,只能先用控制台给的密码把公钥重新装回去。
第一步,清掉本地记的旧指纹(IP 和别名都要清):
for k in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10 \
m1 m2 m3 m4 m5 k8s-cp-1 k8s-cp-2 k8s-cp-3 k8s-w-1 k8s-w-2; do
ssh-keygen -R "$k"
done
第二步,用密码把公钥装回 5 台。ssh-copy-id 会把本地 ~/.ssh/id_rsa.pub 追加到对端 ~/.ssh/authorized_keys:
ssh-copy-id -o StrictHostKeyChecking=accept-new root@154.201.73.31 # 输入控制台给的密码
# 其余 4 台同理
StrictHostKeyChecking=accept-new:自动接受新机器的指纹并写进 known_hosts;但若是已知机器指纹突然变了仍会拦——比直接关掉校验安全。
第三步——最坑的一步。很多云镜像(含本次用的)默认在 /etc/ssh/sshd_config 里写了 PubkeyAuthentication no,于是公钥装上了也用不了,登录只会回退到密码。现象:
ssh -v root@154.201.73.31 true 2>&1 | grep "can continue"
# debug1: Authentications that can continue: password
# ^^^^^^^^ 只有 password、没有 publickey = 公钥认证被关
服务器上确认并打开:
ssh root@<ip> "sshd -T | grep -i pubkeyauth" # pubkeyauthentication no ← 就是它
ssh root@<ip> "sed -i 's/^PubkeyAuthentication no/PubkeyAuthentication yes/' /etc/ssh/sshd_config \
&& systemctl reload ssh"
用
reload不用restart:reload 不断开当前连接;restart 会把你正用着的这条 SSH 也踢掉(命令可能在回显前就断,看到Connection closed)。只能 restart 时用nohup systemctl restart ssh &后台重启,再新开连接验证。
验证免密通了:
for h in m1 m2 m3 m4 m5; do echo "$h: $(ssh -o BatchMode=yes $h hostname)"; done
BatchMode=yes 关掉一切交互(不会弹密码框)——能打印出 hostname 就说明纯靠密钥登进去了。
2.1 检查系统 + 核实内网 IP
for h in m1 m2 m3 m4 m5; do
echo "== $h =="
ssh -o BatchMode=yes $h '
hostname
ip -4 -o addr show | awk "{print \$2, \$4}" | grep "10\\."
. /etc/os-release && echo "$PRETTY_NAME $(uname -r)"
echo "mem=$(free -h | awk "/Mem/{print \$2}") cpu=$(nproc)"
'
done
真实输出(本集群,5 台同构,只贴 m1):
== m1 ==
ser681858145161
ens17 10.0.24.31/24
Ubuntu 24.04.1 LTS 6.8.0-48-generic
mem=7.8Gi cpu=8
逐项看什么:
| 字段 | 看什么 | 本集群 |
|---|---|---|
hostname | 重装后是云厂默认名(如 ser6818...),Step 3 才改成 k8s-cp-1 | 待改 |
10.0.24.x/24 | 最关键:内网 IP 必须和「机器规划」表一致。HAProxy 后端、kubeadm advertiseAddress、etcd peer 全写死这些 IP,变了整套配置都要改 | 31/29/32/28/30 ✓ |
ens17 | 内网网卡名,后面 VXLAN / 路由走它 | 一致 |
| OS / kernel | kernel ≥ 5.4 才能跑 Cilium eBPF,本集群 6.8 没问题 | 6.8 ✓ |
| mem / cpu | 控制面建议 ≥ 2C2G,本集群 8C8G 宽裕 | ✓ |
为什么先做这一步:后面所有安装都是远程批量执行。SSH、hostname、内网 IP 任意一个错,kubeadm join 会把错误节点注册进集群,排查代价很高。
3. 写 hostname 和 hosts
只在空机器安装时执行:
ssh root@154.201.73.31 'hostnamectl set-hostname k8s-cp-1'
ssh root@154.201.73.81 'hostnamectl set-hostname k8s-cp-2'
ssh root@45.205.31.214 'hostnamectl set-hostname k8s-cp-3'
ssh root@45.205.31.180 'hostnamectl set-hostname k8s-w-1'
ssh root@45.205.31.10 'hostnamectl set-hostname k8s-w-2'
然后 5 台统一写 /etc/hosts:
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
cp -a /etc/hosts /etc/hosts.bak.$(date +%Y%m%d-%H%M%S)
cp -a /etc/cloud/cloud.cfg /etc/cloud/cloud.cfg.bak.$(date +%Y%m%d-%H%M%S) 2>/dev/null || true
if grep -q "^manage_etc_hosts:" /etc/cloud/cloud.cfg 2>/dev/null; then
sed -i "s/^manage_etc_hosts:.*/manage_etc_hosts: false/" /etc/cloud/cloud.cfg
else
printf "\nmanage_etc_hosts: false\n" >> /etc/cloud/cloud.cfg
fi
cat > /etc/hosts <<HOSTS
127.0.0.1 localhost k8s-api
127.0.1.1 $(hostname)
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.0.24.31 k8s-cp-1 m1
10.0.24.29 k8s-cp-2 m2
10.0.24.32 k8s-cp-3 m3
10.0.24.28 k8s-w-1 m4
10.0.24.30 k8s-w-2 m5
HOSTS
getent hosts k8s-api
'
done
为什么 k8s-api 指向 127.0.0.1:每台机器本地跑 HAProxy,监听 127.0.0.1:16443,再转发到 3 个 apiserver。这样 kubelet 和 kubectl 都只连本机入口,不依赖外部 LB。
4. 节点基础配置
5 台都执行:
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
swapoff -a
sed -i.bak "/ swap / s/^/#/" /etc/fstab
cat > /etc/modules-load.d/k8s.conf <<EOF
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat > /etc/sysctl.d/99-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
apt-get update
apt-get install -y ca-certificates curl gnupg lsb-release chrony jq ipset ipvsadm conntrack socat ebtables ethtool
systemctl enable --now chrony
'
done
为什么:
swapoff:kubelet 默认不允许节点开启 swap,避免内存压力时调度判断失真。br_netfilter:让桥接网络经过 iptables/nftables,CNI 和 Service 转发需要。ip_forward:Pod 跨节点、Service 转发都需要 Linux 转发包。chrony:etcd 对时间敏感,时间漂移会导致证书和 Raft 异常。
5. 安装 containerd
5 台都执行:
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
| gpg --dearmor -o /etc/apt/keyrings/docker.gpg
chmod a+r /etc/apt/keyrings/docker.gpg
. /etc/os-release
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu ${VERSION_CODENAME} stable" \
> /etc/apt/sources.list.d/docker.list
apt-get update
apt-get install -y containerd.io
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
sed -i "s/SystemdCgroup = false/SystemdCgroup = true/" /etc/containerd/config.toml
sed -i "s|config_path = \"\"|config_path = \"/etc/containerd/certs.d\"|" /etc/containerd/config.toml
mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.io"
[host."https://docker.m.daocloud.io"]
capabilities = ["pull", "resolve"]
EOF
systemctl enable --now containerd
systemctl restart containerd
systemctl is-active containerd
'
done
验证(5 台都要 active):
ssh root@<ip> 'systemctl is-active containerd; containerd --version'
# active
# containerd containerd.io 2.2.4 ...
为什么不用 Docker:K8s 1.24 起移除了 dockershim,直接用 containerd 更短、更接近生产。
为什么 SystemdCgroup=true:Ubuntu 24.04 默认用 systemd 管 cgroup,kubelet 和 containerd 的 cgroup driver 必须一致,否则资源限制和 Pod 退出时容易异常。
⚠️ containerd v2 配置格式变了(实测踩坑)
Docker 源现在装的是 containerd v2.2.4,配置文件是 TOML
version = 3新格式,和老教程(v1.x)有三处不同:
项 v1.x(老教程) v2.x(现在) 空值写法 config_path = ""(双引号)config_path = ''(单引号)CRI 插件路径 io.containerd.grpc.v1.criio.containerd.cri.v1.imagesSystemdCgroup 默认 false(必须手动改)默认已是 true后果:上面
sed "s/SystemdCgroup = false/.../"在 v2 上找不到匹配(默认已是 true,不影响);但sed 's/config_path = ""/.../'也匹配不到单引号的 v2 写法,镜像加速 mirror 静默不生效。本 runbook 已改成兼容写法:# 只替换第一处(registry 那处)的空 config_path,v1 双引号 / v2 单引号都覆盖 sed -i "0,/config_path = ''/s||config_path = \"/etc/containerd/certs.d\"|" /etc/containerd/config.toml sed -i '0,/config_path = ""/s||config_path = "/etc/containerd/certs.d"|' /etc/containerd/config.toml验证 mirror 真的写进去了:
grep 'config_path = .*certs.d' /etc/containerd/config.toml应回显config_path = "/etc/containerd/certs.d"。另:用
sed改 TOML 时别在单引号 SSH 参数里再嵌单引号('\''极易多写一个引号),结果是config_path = '/etc/...''',containerd 启动直接报Failure unmarshaling TOML ... expected newline but got U+0027。要么用 heredoc 把脚本喂给bash -s,要么像上面用 TOML 合法的双引号写路径值。本环境实测:5 台能直连
registry.k8s.io(200)和docker.io(302),所以 daocloud mirror 其实非必需;但配上能在仓库变慢时兜底。国内机器若直连不通,这个 mirror 就是刚需。
6. 安装 kubeadm / kubelet / kubectl
5 台都执行:
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.30/deb/Release.key \
| gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.30/deb/ /" \
> /etc/apt/sources.list.d/kubernetes.list
apt-get update
apt-get install -y kubelet kubeadm kubectl
apt-mark hold kubelet kubeadm kubectl
cat > /etc/crictl.yaml <<EOF
runtime-endpoint: unix:///run/containerd/containerd.sock
image-endpoint: unix:///run/containerd/containerd.sock
timeout: 10
debug: false
EOF
'
done
为什么 apt-mark hold:避免系统自动升级 kubelet/kubeadm/kubectl,K8s 版本升级必须按顺序做,不能让 apt 自动滚。
7. 安装 HAProxy 本地 apiserver 入口
5 台都执行:
for ip in 154.201.73.31 154.201.73.81 45.205.31.214 45.205.31.180 45.205.31.10; do
ssh root@$ip 'set -eu
apt-get install -y haproxy
cat > /etc/haproxy/haproxy.cfg <<EOF
global
log /dev/log local0
log /dev/log local1 notice
daemon
defaults
log global
mode tcp
option tcplog
option dontlognull
timeout connect 5s
timeout client 1m
timeout server 1m
frontend k8s_api
bind 127.0.0.1:16443
default_backend k8s_api_backends
backend k8s_api_backends
option tcp-check
server cp1 10.0.24.31:6443 check
server cp2 10.0.24.29:6443 check
server cp3 10.0.24.32:6443 check
EOF
systemctl enable haproxy
systemctl restart haproxy
ss -tlnp | grep 16443 || true
'
done
为什么不用公网 IP:apiserver 和 etcd 走内网,减少延迟和暴露面。
8. kubeadm init 第一个控制面
在 k8s-cp-1 执行:
ssh root@154.201.73.31
写 kubeadm 配置:
cat > /root/kubeadm-init.yaml <<'EOF'
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: v1.30.14
controlPlaneEndpoint: k8s-api:16443
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/12
apiServer:
certSANs:
- k8s-api
- 127.0.0.1
- 10.0.24.31
- 10.0.24.29
- 10.0.24.32
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 10.0.24.31
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
cgroupDriver: systemd
EOF
kubeadm init --config /root/kubeadm-init.yaml --upload-certs
mkdir -p ~/.kube
cp -f /etc/kubernetes/admin.conf ~/.kube/config
chmod 600 ~/.kube/config
kubectl get nodes
此时节点大概率是 NotReady,因为 CNI 还没装。这是正常状态。
保存 kubeadm 输出里的两条 join 命令:
- control plane join:给 cp-2/cp-3 用,带
--control-plane --certificate-key。 - worker join:给 w-1/w-2 用。
8.1 init 输出逐段读懂
kubeadm init 会打出一大段 [phase] 日志,新手容易被吓到。其实它就是按顺序做完这些事,每个方括号是一个阶段:
[preflight] 检查端口/swap/cgroup/镜像,拉控制面镜像
[certs] 生成整套 TLS 证书(ca / apiserver / etcd / front-proxy / sa)
apiserver serving cert is signed for ... IPs [10.96.0.1 10.0.24.31 127.0.0.1 10.0.24.29 10.0.24.32]
↑ 这行很重要:apiserver 证书签了哪些名字/IP。少了某个 IP,用那个 IP 连 apiserver 会报证书错
[control-plane] 把 apiserver/controller-manager/scheduler 写成静态 Pod manifest 放到 /etc/kubernetes/manifests
[etcd] 本机起一个 stacked etcd(控制面和 etcd 同机)
[kubelet-start] 启动 kubelet,由它拉起上面那些静态 Pod
[wait-control-plane] / [api-check] 等 apiserver 健康(The API server is healthy after 7.5s)
[upload-certs] 把证书塞进 Secret kubeadm-certs,并打印 certificate key(给控制面 join 用,2 小时过期)
[bootstrap-token] 生成 join 用的 token + 配好 RBAC
[addons] 装上 CoreDNS 和 kube-proxy
中间这条 WARNING 可以无视:
W... [endpoint] WARNING: port specified in controlPlaneEndpoint overrides bindPort in the controlplane address
意思是「你在 controlPlaneEndpoint: k8s-api:16443 指定了端口,它会覆盖 bindPort: 6443」——这正是我们想要的(对外走 HAProxy 的 16443,本机 apiserver 仍听 6443),不是错误。
两条 join 命令分别长这样(token / hash / certificate-key 每次 init 都不同):
# 控制面 join(给 cp-2 / cp-3)——多了 --control-plane 和 --certificate-key
kubeadm join k8s-api:16443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane --certificate-key <cert-key>
# worker join(给 w-1 / w-2)——只有 token 和 hash
kubeadm join k8s-api:16443 --token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
配好 kubeconfig 后看一眼:
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
k8s-cp-1 NotReady control-plane 20s v1.30.14
$ kubectl get pods -n kube-system
coredns-... 0/1 Pending ← 没 CNI、拿不到 Pod IP,正常
coredns-... 0/1 Pending
etcd-k8s-cp-1 1/1 Running
kube-apiserver-k8s-cp-1 1/1 Running
kube-controller-manager-k8s-cp-1 1/1 Running
kube-proxy-... 1/1 Running
kube-scheduler-k8s-cp-1 1/1 Running
✅ 此刻的正确状态:节点 NotReady、coredns Pending,其余控制面组件 Running。这不是故障——CNI(Step 10 的 Cilium)还没装,节点没有 Pod 网络。装完 Cilium 就会转 Ready、coredns 才拿得到 IP。新手最常见的误判就是在这里以为「装坏了」。
9. 加入其余节点
在 cp-2/cp-3 执行 control-plane join。示例:
kubeadm join k8s-api:16443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <cert-key> \
--cri-socket unix:///run/containerd/containerd.sock
控制面要逐台串行 join,别并发。 每加一个控制面,etcd 就多一个成员,需要重新达成多数派。并发加会让 etcd 仲裁不稳。
join 过程中会刷出这种 WARNING,是正常的、不用管:
{"level":"warn",...,"error":"etcdserver: can only promote a learner member which is in sync with leader"}新 etcd 成员先以 learner(学习者) 身份加入、只同步数据不投票;等它追上 leader,kubeadm 再把它提升为正式成员。上面的 warning 就是「还在追、稍等」的重试日志。最后看到
A new etcd member was added to the local/stacked etcd cluster就成了。
在 w-1/w-2 执行 worker join:
kubeadm join k8s-api:16443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--cri-socket unix:///run/containerd/containerd.sock
如果 token 过期,在 cp-1 重新生成:
kubeadm token create --print-join-command
kubeadm init phase upload-certs --upload-certs
验收:
kubectl get nodes -o wide
真实输出(5 台都 join 完、Cilium 还没装):
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-cp-1 NotReady control-plane 4m2s v1.30.14 10.0.24.31 <none> Ubuntu 24.04.1 LTS 6.8.0-48-generic containerd://2.2.4
k8s-cp-2 NotReady control-plane 2m46s v1.30.14 10.0.24.29 <none> Ubuntu 24.04.1 LTS 6.8.0-48-generic containerd://2.2.4
k8s-cp-3 NotReady control-plane 46s v1.30.14 10.0.24.32 <none> ...
k8s-w-1 NotReady <none> 19s v1.30.14 10.0.24.28 <none> ...
k8s-w-2 NotReady <none> 13s v1.30.14 10.0.24.30 <none> ...
kubectl get nodes -o wide 逐列说明(新手最该背的一张表):
| 列 | 含义 | 怎么判断 |
|---|---|---|
NAME | 节点名 = Step 3 设的 hostname | 应是 k8s-cp-1…k8s-w-2,不是云厂默认名 |
STATUS | Ready / NotReady | 没装 CNI 前全 NotReady 正常;装完 Cilium 应全 Ready |
ROLES | control-plane 或 <none> | worker 显示 <none> 是正常的(K8s 没有内置 worker role 标签) |
AGE | 节点 join 进来多久 | cp-1 最老(先 init),worker 最新 |
VERSION | kubelet 版本 | 必须一致,本集群全 v1.30.14 |
INTERNAL-IP | 节点内网 IP | 必须和机器规划表一致;apiserver / etcd / VXLAN 都用它 |
EXTERNAL-IP | 外网 IP(kubelet 探测到的) | <none> 正常,集群内部通信不用它 |
CONTAINER-RUNTIME | 运行时及版本 | containerd://2.2.4,确认不是 docker |
✅ 验证点:5 台全部出现(少一台说明那台 join 失败)+ VERSION 一致 + INTERNAL-IP 对得上规划表。NotReady 此刻是对的,等 Cilium 装完恢复(见 Step 10 / Cilium Runbook)。
⚠️ 如果 token 过期(默认 24h)或想再加节点,在 cp-1 重新生成:
kubeadm token create --print-join-command # 打印 worker join(含新 token+hash)
kubeadm init phase upload-certs --upload-certs # 重新上传证书,打印新 certificate-key(控制面 join 要)
10. 安装平台组件
按顺序安装,具体细节看组件 Runbook:
后续按组件 Runbook 执行:
- CNI 网络:Cilium 网络 Runbook
- 存储:Longhorn 存储 Runbook
- 监控日志:监控日志 Runbook
- 镜像仓库、Git、CI、CD:CI / GitOps Runbook
- 安全和准入策略:安全准入 Runbook
顺序不能乱:
- Cilium 必须最先装,否则节点一直 NotReady,Pod 没网络。
- Longhorn 依赖 K8s 网络正常。
- 监控日志依赖 StorageClass 提供 PVC。
- Harbor/Gitea/Jenkins/ArgoCD 也依赖 PVC 和网络。
11. 最终验收
kubectl get nodes -o wide
kubectl get pods -A \
--field-selector=status.phase!=Running,status.phase!=Succeeded \
-o wide
helm list -A
cilium status --wait=false
kubectl get sc
kubectl get pvc -A
kubectl get svc -A | grep NodePort
期望:
- 5 个节点都是
Ready。 - 除演练 Pod 外,没有业务组件 CrashLoop。
longhorn是默认 StorageClass。- Cilium
Desired: 5, Ready: 5/5。 - Grafana、Harbor、Jenkins、ArgoCD、Longhorn 都有 NodePort。