第 12 章:从零搭建生产级 K8s 集群 + Prometheus 监控栈实战

4 阅读7分钟

本文记录一次真实的三节点 K8s 集群搭建全过程:3 台阿里云 ECS(4C8G)、Alibaba Cloud Linux 4、kubeadm v1.31.14、containerd 运行时、Calico 网络插件,最后部署 Prometheus + Grafana 监控栈。所有命令和输出均为实战实录,踩坑过程一并保留,可直接复现。

部署全流程

1. 环境规划

1.1 节点信息

主机名内网 IP公网 IP角色配置操作系统
k8s-master192.168.0.201131.42.218.249控制平面4核8GAlibaba Cloud Linux 4 LTS
k8s-node1192.168.0.20247.96.118.121工作节点4核8GAlibaba Cloud Linux 4 LTS
k8s-node2192.168.0.203118.158.121.148工作节点4核8GAlibaba Cloud Linux 4 LTS

架构方案:

  • Kubernetes v1.31.14(阿里云新源最高支持版本)
  • 容器运行时:containerd 1.7.29(K8s 1.24+ 官方标准,不使用 Docker)
  • 网络插件:Calico v3.29.1
  • 监控:Prometheus + Grafana(kube-prometheus-stack)

集群架构图

1.2 阿里云安全组配置【前置必做】

在阿里云控制台 → ECS 安全组,为这三台机器所在安全组添加入方向规则(源地址 192.168.0.0/16):

端口范围协议作用
6443TCPK8s apiserver
2379-2380TCPetcd 集群通信
10250TCPkubelet 通信
10259TCPkube-scheduler
10257TCPkube-controller-manager
179TCPCalico BGP 路由
30000-32767TCP/UDPNodePort 服务端口

⚠️ 漏配安全组是最常见的坑:集群内网不通、Calico Pod 起不来,90% 都是这里的问题。


2. 三台节点统一初始化

以下操作三台机器全部执行,逐一验证。

2.1 设置主机名

# Master(131.42.218.249)
hostnamectl set-hostname k8s-master

# Node1(47.96.118.121)
hostnamectl set-hostname k8s-node1

# Node2(118.158.121.148)
hostnamectl set-hostname k8s-node2

执行结果(Master):

$ hostnamectl set-hostname k8s-master && hostname
k8s-master

三台分别确认:

# node1
k8s-node1
# node2
k8s-node2

2.2 配置 hosts 解析(三台执行)

cat >> /etc/hosts <<EOF
192.168.0.201 k8s-master
192.168.0.202 k8s-node1
192.168.0.203 k8s-node2
EOF

验证:

$ tail -3 /etc/hosts
192.168.0.201 k8s-master
192.168.0.202 k8s-node1
192.168.0.203 k8s-node2

2.3 关闭 SELinux 和防火墙

setenforce 0
sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config

systemctl stop firewalld
systemctl disable firewalld

执行结果:

$ getenforce
Disabled

2.4 永久关闭 Swap

swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
grep swap /etc/fstab || echo "(no swap entries found - OK)"

执行结果(三台一致):

Swap closed, checking fstab:
(no swap entries found - OK)

2.5 加载内核模块 + 开启网络转发

modprobe overlay
modprobe br_netfilter

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

验证(三台均输出):

net.ipv4.ip_forward = 1

2.6 添加阿里云 K8s YUM 源【踩坑记录】

坑 1:文档里的 el7 旧源不支持 Alibaba Cloud Linux 4

最初按老教程写的源:

# ❌ 失败:el7 源最高只有 1.28,没有 1.31
baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el7-x86_64/

yum makecache 直接报 GPG 签名错误:

Error: Failed to download metadata for repo 'kubernetes': 
repomd.xml GPG signature verification error: Bad GPG signature

即使关闭 GPG 检查,也只能装到 1.28:

$ yum list --showduplicates kubeadm
kubeadm.x86_64  1.25.5-0   kubernetes
...
kubeadm.x86_64  1.28.2-0   kubernetes   # 最高只到 1.28

正确做法:使用阿里云 kubernetes-new 源

cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name=Kubernetes
baseurl=https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.31/rpm/
enabled=1
gpgcheck=0
repo_gpgcheck=0
EOF

yum clean all && yum makecache

验证:

$ yum list kubeadm --showduplicates
kubeadm.x86_64  1.31.11-150500.1.1  kubernetes
kubeadm.x86_64  1.31.12-150500.1.1  kubernetes
kubeadm.x86_64  1.31.13-150500.1.1  kubernetes
kubeadm.x86_64  1.31.14-150500.1.1  kubernetes  # 选最新

3. 安装 containerd 运行时(三台执行)

3.1 安装并生成默认配置

yum install -y containerd
containerd config default > /etc/containerd/config.toml

3.2 修改关键配置

# 开启 systemd cgroup 驱动(必须和 kubelet 保持一致)
sed -i 's#SystemdCgroup = false#SystemdCgroup = true#g' /etc/containerd/config.toml

# 替换 pause 镜像为阿里云镜像(防止拉取超时)
sed -i 's#registry.k8s.io/pause#registry.aliyuncs.com/google_containers/pause#g' /etc/containerd/config.toml
sed -i 's#registry.k8s.io#registry.aliyuncs.com/google_containers#g' /etc/containerd/config.toml

3.3 启动并设置开机自启

systemctl daemon-reload
systemctl start containerd
systemctl enable containerd
systemctl status containerd

执行结果:

 containerd.service - containerd container runtime
     Loaded: loaded (/usr/lib/systemd/system/containerd.service; enabled)
     Active: active (running) since Mon 2026-08-03 11:41:51 CST
   Main PID: 4386 (containerd)
...
"containerd successfully booted in 0.048508s"

三台均安装成功:containerd 1.7.29 + runc 1.2.8。


4. 安装 K8s 核心组件(三台执行)

yum install -y kubeadm-1.31.14 kubelet-1.31.14 kubectl-1.31.14
systemctl enable kubelet

验证版本(三台一致):

$ kubeadm version
kubeadm version: &version.Info{Major:"1", Minor:"31", GitVersion:"v1.31.14", ...}

$ kubectl version --client
Client Version: v1.31.14
Kustomize Version: v5.4.2

$ kubelet --version
Kubernetes v1.31.14

5. Master 节点初始化(仅在 192.168.0.201 执行)

5.1 初始化控制平面

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

关键输出(完整流程):

[init] Using Kubernetes version: v1.31.14
[preflight] Running pre-flight checks
[preflight] Pulling images required for setting up a Kubernetes cluster
[certs] Generating "ca" certificate and key
[certs] Generating "apiserver" certificate and key
[certs] apiserver serving cert is signed for DNS names [k8s-master kubernetes ...] 
        and IPs [10.96.0.1 192.168.0.201]
...
[wait-control-plane] Waiting for the kubelet to boot up the control plane
[kubelet-check] The kubelet is healthy after 1.000841782s
[api-check] The API server is healthy after 5.501143988s
[addons] Applied essential addon: CoreDNS
[addons] Applied essential addon: kube-proxy

Your Kubernetes control-plane has initialized successfully!

重点:保存好 join 命令! 输出末尾的这段,Node 节点加入要用:

kubeadm join 192.168.0.201:6443 --token 5lxqj1.v7rnhfd00wbwmlw6 \
    --discovery-token-ca-cert-hash sha256:312cf079265eb18b8578abf6a1ce1e087899460b332a945b9e1837f6a1fb46dc

Token 有效期 24 小时,过期后可用 kubeadm token create --print-join-command 重新生成。

5.2 配置 kubectl

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

此时查看节点状态(NotReady 是正常现象,因为还没装 CNI 插件):

$ kubectl get nodes
NAME         STATUS     ROLES           AGE   VERSION
k8s-master   NotReady   control-plane   34s   v1.31.14

5.3 部署 Calico 网络插件【踩坑记录】

坑 2:官方文档路径 404

# ❌ 失败
kubectl apply -f https://docs.projectcalico.org/v3.28/manifests/calico.yaml
# error: server reported 404 Not Found

正确做法(使用 GitHub raw 地址):

kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml

输出:

poddisruptionbudget.policy/calico-kube-controllers created
serviceaccount/calico-kube-controllers created
serviceaccount/calico-node created
configmap/calico-config created
customresourcedefinition.apiextensions.k8s.io/bgpconfigurations.crd.projectcalico.org created
...(20+ 个 CRD)
daemonset.apps/calico-node created
deployment.apps/calico-kube-controllers created

6. Node 节点加入集群

k8s-node1k8s-node2 分别执行:

kubeadm join 192.168.0.201:6443 --token 5lxqj1.v7rnhfd00wbwmlw6 \
  --discovery-token-ca-cert-hash sha256:312cf079265eb18b8578abf6a1ce1e087899460b332a945b9e1837f6a1fb46dc

执行结果(Node1):

[preflight] Running pre-flight checks
[preflight] Reading configuration from the cluster...
[kubelet-start] Writing kubelet configuration to file "/var/lib/kubelet/config.yaml"
[kubelet-start] Starting the kubelet
[kubelet-check] The kubelet is healthy after 1.001627868s

This node has joined the cluster:
* Certificate signing request was sent to apiserver and a response was received.
* The Kubelet was informed of the new secure connection details.

Node2 同样加入成功。

踩坑 3:Calico 镜像拉取失败(国内网络问题)

等待后查看,所有节点 NotReady,Calico Pod 全是 ImagePullBackOff

$ kubectl get pods -n kube-system | grep calico
calico-node-sdbkn   0/1   Init:ErrImagePull      0   2m23s
calico-node-cl845   0/1   Init:ImagePullBackOff  0   81s
calico-node-zdg9w   0/1   Init:ImagePullBackOff  0   83s

查看原因:

Failed to pull image "docker.io/calico/cni:v3.29.1": 
dial tcp 104.244.43.6:443: i/o timeout   # Docker Hub 国内无法直连

解决:配置 containerd 镜像加速(三台都执行)

mkdir -p /etc/containerd/certs.d/docker.io
cat > /etc/containerd/certs.d/docker.io/hosts.toml <<EOF
server = "https://docker.1ms.run"

[host."https://docker.1ms.run"]
  capabilities = ["pull", "resolve"]
  skip_verify = true
EOF

# 启用 config_path
sed -i 's#config_path = ""#config_path = "/etc/containerd/certs.d"#g' /etc/containerd/config.toml
systemctl restart containerd

⚠️ 注意:目录名必须是 docker.io,不是 _default。这是 containerd hosts.toml 的匹配规则——按 registry 域名匹配目录。

删除失败的 Pod 让其重建:

kubectl delete pods -n kube-system -l k8s-app=calico-node --force --grace-period=0
kubectl delete pods -n kube-system -l k8s-app=kube-dns --force --grace-period=0

7. 集群验证

7.1 节点全部 Ready

终端实录:节点与监控 Pod 状态

$ kubectl get nodes -o wide
NAME         STATUS   ROLES           AGE   VERSION    INTERNAL-IP     CONTAINER-RUNTIME
k8s-master   Ready    control-plane   16m   v1.31.14   192.168.0.201   containerd://1.7.29
k8s-node1    Ready    <none>          14m   v1.31.14   192.168.0.202   containerd://1.7.29
k8s-node2    Ready    <none>          14m   v1.31.14   192.168.0.203   containerd://1.7.29

7.2 系统组件全部 Running

$ kubectl get pods -n kube-system
NAME                                       READY   STATUS    RESTARTS   AGE
calico-kube-controllers-5d7d9cdfd8-fnlrc   1/1     Running   0          33m
calico-node-9gjrw                          1/1     Running   0          16m
calico-node-jcvzf                          1/1     Running   0          33m
calico-node-tj7w7                          1/1     Running   0          33m
coredns-855c4dd65d-59whc                   1/1     Running   0          33m
coredns-855c4dd65d-drqlv                   1/1     Running   0          16m
etcd-k8s-master                            1/1     Running   0          44m
kube-apiserver-k8s-master                  1/1     Running   0          44m
kube-controller-manager-k8s-master         1/1     Running   0          44m
kube-proxy-nf7s6                           1/1     Running   0          42m
kube-proxy-wcrps                           1/1     Running   0          44m
kube-proxy-xwxsv                           1/1     Running   0          42m
kube-scheduler-k8s-master                  1/1     Running   0          44m

7.3 部署测试应用

kubectl create deployment nginx-demo --image=nginx:alpine
kubectl expose deployment nginx-demo --type NodePort --port 80
$ kubectl get pods -l app=nginx-demo -o wide
NAME                          READY   STATUS    NODE         IP
nginx-demo-db78b9d74-sw48p    1/1     Running   k8s-node2    10.244.169.130

$ kubectl get svc nginx-demo
NAME         TYPE       CLUSTER-IP     PORT(S)        AGE
nginx-demo   NodePort   10.97.237.99   80:31347/TCP   10s

内网访问验证:

$ curl -s http://192.168.0.201:31347 | head -5
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>

集群搭建完成 🎉


8. 部署 Prometheus + Grafana 监控栈

监控栈架构图

8.1 安装 Helm 3(Master 执行)

GitHub 直连慢,改用华为云镜像:

curl -fsSL -o helm-v3.16.4-linux-amd64.tar.gz \
  https://mirrors.huaweicloud.com/helm/v3.16.4/helm-v3.16.4-linux-amd64.tar.gz
tar -zxvf helm-v3.16.4-linux-amd64.tar.gz
mv linux-amd64/helm /usr/local/bin/helm
helm version
version.BuildInfo{Version:"v3.16.4", GitCommit:"7877b45b..."}

8.2 获取 kube-prometheus-stack chart【踩坑记录】

坑 4:Helm 仓库全部不可达

# ❌ 官方仓库:connection reset
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

# ❌ 华为云镜像:404
https://mirrors.huaweicloud.com/repository/helm/prometheus-community/

解决:本地下载 chart 包后上传

# 本地(能翻墙的机器)下载
curl -fsSL -o kube-prometheus-stack-66.3.0.tgz \
  "https://github.com/prometheus-community/helm-charts/releases/download/kube-prometheus-stack-66.3.0/kube-prometheus-stack-66.3.0.tgz"

# 上传到 Master
scp kube-prometheus-stack-66.3.0.tgz root@131.42.218.249:/root/

8.3 安装监控栈

kubectl create namespace monitoring

helm install prometheus /root/kube-prometheus-stack-66.3.0.tgz \
  --namespace monitoring \
  --set grafana.adminPassword='YourPassword' \
  --set grafana.persistence.enabled=true \
  --set grafana.persistence.size=5Gi \
  --set grafana.service.type=NodePort \
  --set prometheus.service.type=NodePort \
  --set prometheus.prometheusSpec.resources.limits.cpu='1' \
  --set prometheus.prometheusSpec.resources.limits.memory='2Gi' \
  --set alertmanager.enabled=true

8.4 连环踩坑解决

坑 5:admission webhook 镜像不存在

Failed to pull image "registry.k8s.io/ingress-nginx/kube-webhook-certgen:...": not found

阿里云镜像仓库里没有这个特定版本。解决:禁用 admission webhook(可选组件):

helm uninstall prometheus -n monitoring
kubectl delete namespace monitoring
kubectl create namespace monitoring

# 重装时加上禁用参数
helm install prometheus /root/kube-prometheus-stack-66.3.0.tgz \
  --namespace monitoring \
  ... \
  --set prometheusOperator.admissionWebhooks.enabled=false \
  --set prometheusOperator.admissionWebhooks.patch.enabled=false

坑 6:quay.io 镜像拉取超时

operator 等组件镜像在 quay.io,同样需要加速(三台执行):

mkdir -p /etc/containerd/certs.d/quay.io
cat > /etc/containerd/certs.d/quay.io/hosts.toml <<EOF
server = "https://quay.io"

[host."https://quay.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
  skip_verify = true
EOF
systemctl restart containerd

坑 7:Grafana PVC Pending(无 StorageClass)

Warning  FailedScheduling  pod has unbound immediate PersistentVolumeClaims
$ kubectl get sc
No resources found   # 集群没有默认存储类

解决:安装 local-path-provisioner 本地存储:

kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/v0.0.30/deploy/local-path-storage.yaml

# 设为默认 StorageClass
kubectl patch storageclass local-path -p \
  '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

PVC 成功绑定:

$ kubectl get pvc -n monitoring
NAME                 STATUS   VOLUME                                     CAPACITY   STORAGECLASS
prometheus-grafana   Bound    pvc-663ee392-2d30-44de-b039-6a8593308cbf   5Gi        local-path

坑 8:operator CrashLoopBackOff(TLS 证书缺失)

failed to create web server err="failed to load serving certificate and key: 
open /cert/cert: no such file or directory"

因为禁用了 admission webhook 但 operator 仍要求 TLS 证书。解决:

# 创建空 secret + helm upgrade 禁用 TLS
kubectl create secret generic prometheus-kube-prometheus-admission -n monitoring

helm upgrade prometheus /root/kube-prometheus-stack-66.3.0.tgz \
  --namespace monitoring \
  ... \
  --set prometheusOperator.tls.enabled=false

坑 9:registry.k8s.io 镜像超时

kube-state-metrics 在 registry.k8s.io,配置 daocloud 加速:

mkdir -p /etc/containerd/certs.d/registry.k8s.io
cat > /etc/containerd/certs.d/registry.k8s.io/hosts.toml <<EOF
server = "https://registry.k8s.io"

[host."https://k8s.m.daocloud.io"]
  capabilities = ["pull", "resolve"]
  skip_verify = true
EOF
systemctl restart containerd

8.5 最终状态:全部 Running

$ kubectl get pods -n monitoring -o wide
NAME                                                     READY   STATUS    NODE
alertmanager-prometheus-kube-prometheus-alertmanager-0   2/2     Running   k8s-node1
prometheus-grafana-7445c99b47-5gxvt                      3/3     Running   k8s-node2
prometheus-kube-prometheus-operator-85c55f769c-bgctl     1/1     Running   k8s-node1
prometheus-kube-state-metrics-6489887dc-n97z7            1/1     Running   k8s-node2
prometheus-prometheus-kube-prometheus-prometheus-0       2/2     Running   k8s-node2
prometheus-prometheus-node-exporter-dwtl4                1/1     Running   k8s-node2
prometheus-prometheus-node-exporter-n8mqk                1/1     Running   k8s-master
prometheus-prometheus-node-exporter-twxb5                1/1     Running   k8s-node1

8.6 访问入口

$ kubectl get svc -n monitoring | grep NodePort
prometheus-grafana                      NodePort   80:31964/TCP
prometheus-kube-prometheus-prometheus   NodePort   9090:30090/TCP
组件访问地址凭证
Grafanahttp://131.42.218.249:31964admin / YourPassword
Prometheushttp://131.42.218.249:30090无需登录

安全组需放行 31964 和 30090 端口(30000-32767 已在第一步放行 ✅)。

Grafana 登录页:

Grafana 登录页

登录后首页(预置 20+ K8s 监控仪表盘):

Grafana 首页

内置 Kubernetes 仪表盘列表:

仪表盘列表

Kubernetes / Compute Resources / Cluster 集群资源总览:

集群资源仪表盘

Prometheus 查询界面:

Prometheus


9. 踩坑总结

#问题根因解决方案
1yum 源 GPG 签名错误el7 旧源不兼容 Alinux4kubernetes-new/core/stable/v1.31/rpm/
2kubeadm-1.31.4 找不到源里最高 1.31.14用 1.31.14,同 minor 版本完全兼容
3Calico 404官方 docs 路径变更用 GitHub raw:raw.githubusercontent.com/projectcalico/calico/v3.29.1/...
4docker.io 镜像超时国内无法直连 Docker Hubcontainerd certs.d 配置镜像加速(目录名必须叫 docker.io
5Helm 仓库不可达GitHub Pages 被墙本地下载 chart tgz 后 scp 上传,用 helm install <tgz>
6webhook-certgen not found阿里云仓库无此镜像禁用 admissionWebhooks
7PVC Pending集群无 StorageClass装 local-path-provisioner 并设为默认
8operator 要 /cert/cert禁用 webhook 但 TLS 仍开启--set prometheusOperator.tls.enabled=false
9registry.k8s.io 超时Google 镜像仓库被墙k8s.m.daocloud.io 加速

10. 常用运维命令速查

# 节点与 Pod
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl describe pod <pod> -n <ns>     # 排错第一步:看 Events

# 日志
kubectl logs <pod> -n <ns> --tail=50
journalctl -u kubelet -f               # kubelet 日志

# 集群事件(排错神器)
kubectl get events -A --sort-by=.lastTimestamp

# 重新生成 join 命令(token 过期后)
kubeadm token create --print-join-command

# 集群重置(推倒重来)
kubeadm reset -f

# containerd 手动拉镜像(排查镜像问题)
ctr -n k8s.io images pull --hosts-dir /etc/containerd/certs.d <image>

附录:最终集群全貌

集群:

  • Kubernetes v1.31.14 / containerd 1.7.29 / Calico v3.29.1
  • 1 Master + 2 Worker,全部 Ready

监控栈(kube-prometheus-stack 66.3.0):

  • Prometheus v2.55.1(NodePort 30090)
  • Grafana 11.3.1(NodePort 31964,持久化 5Gi)
  • Alertmanager + node-exporter(3 节点全覆盖)+ kube-state-metrics

镜像加速配置(三台统一):

/etc/containerd/certs.d/
├── docker.io/hosts.toml        → docker.1ms.run
├── quay.io/hosts.toml          → quay.m.daocloud.io
└── registry.k8s.io/hosts.toml  → k8s.m.daocloud.io