DevOps 实验项目笔记三 —— Kubernetes 集群部署阶段

阶段目标

完成 Kubernetes 基础集群部署。

目标架构

                devops
           Ansible Control Node
                  │
        ┌─────────┼─────────┐
        │         │         │
   k8s-master  k8s-node1  k8s-node2
  192.168.205  192.168.205 192.168.205
       141        142        143

   Runtime 架构:
   Docker Engine
        │
   cri-dockerd
        │
   kubelet
        │
   Kubernetes

最终状态

项目
Kubernetes 版本1.36.2
Control Plane1
Worker Nodes2
CNIFlannel
Container RuntimeDocker + cri-dockerd

部署前环境确认

节点信息

节点IP角色
k8s-master192.168.205.141Control Plane
k8s-node1192.168.205.142Worker
k8s-node2192.168.205.143Worker

Runtime 架构确认

Kubernetes 不直接调用 Docker:

kubelet
    ↓
cri-dockerd
    ↓
Docker Engine

二、Kubernetes 安装准备

1. 确认 swap 已关闭

三个 Kubernetes 节点执行:

free -h

确认结果:Swap: 0B

永久关闭:

sudo swapoff -a

检查 /etc/fstab,注释 swap 行。

2. 内核模块

加载:

sudo modprobe overlay
sudo modprobe br_netfilter

确认:

lsmod | grep overlay
lsmod | grep br_netfilter

3. 内核参数

配置:

sudo tee /etc/sysctl.d/k8s.conf <<EOF
net.ipv4.ip_forward = 1
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
EOF

生效:

sudo sysctl --system

检查:

sysctl net.ipv4.ip_forward

结果:net.ipv4.ip_forward = 1


三、Docker + cri-dockerd 验证

Docker

三个节点检查:

docker --version
# 示例输出:Docker version xx.xx

systemctl status docker
# 要求:active (running)

Docker cgroup driver

检查:

docker info | grep "Cgroup Driver"
# 要求:Cgroup Driver: systemd

配置文件 /etc/docker/daemon.json

{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "storage-driver": "overlay2",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "3"
  }
}

重启:

sudo systemctl restart docker

cri-dockerd

检查版本:

cri-dockerd --version

检查 socket:

sudo systemctl status cri-docker.socket
# 要求:Active: active (running)

检查 socket 文件:

ls -l /run/cri-dockerd.sock
# 结果:srw-rw---- root docker /run/cri-dockerd.sock

四、第一次 kubeadm init 失败经验总结

第一次执行:

sudo kubeadm init \
  --kubernetes-version=v1.36.2 \
  --cri-socket unix:///run/cri-dockerd.sock \
  --pod-network-cidr=10.244.0.0/16

遇到:wait-control-plane failed

原因分析

控制面组件已经启动:

组件状态
etcdOK
kube-apiserverOK
schedulerOK
controllerOK

但是: kubeadm init 后续 phase 未完成

导致:

  • kubeadm-config 缺失
  • bootstrap token 缺失
  • worker join 失败

最终选择

不是继续手工修 RBAC,而是:

kubeadm reset
重新 init

五、清理失败 Kubernetes 状态

三个 Kubernetes 节点执行:

sudo kubeadm reset -f \
  --cri-socket unix:///run/cri-dockerd.sock

# 清理 CNI
sudo rm -rf /etc/cni/net.d/*

# 清理 Kubernetes
sudo rm -rf /etc/kubernetes

# 清理 kubelet
sudo rm -rf /var/lib/kubelet/*

Master 额外执行:

sudo rm -rf /var/lib/etcd

六、重新初始化 Kubernetes Master

在 master 执行:

sudo kubeadm init \
  --kubernetes-version=v1.36.2 \
  --image-repository=registry.aliyuncs.com/google_containers \
  --cri-socket unix:///run/cri-dockerd.sock \
  --pod-network-cidr=10.244.0.0/16

成功标志: 必须看到

Your Kubernetes control-plane has initialized successfully!

七、配置 kubectl

在 master 执行:

# 创建目录
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
# 初始:k8s-master NotReady
# 原因:没有安装 CNI

八、安装 Flannel 网络插件

因为 init 使用 --pod-network-cidr=10.244.0.0/16,匹配 Flannel。

安装:

kubectl apply -f \
  https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

检查:

kubectl get pods -n kube-flannel
# 结果:kube-flannel-ds-xxxxx 1/1 Running

检查节点:

kubectl get nodes
# 结果:k8s-master Ready

九、Worker 节点加入集群

生成 join 命令

在 master 执行:

kubeadm token create --print-join-command
# 输出类似:
# kubeadm join 192.168.205.141:6443 \
#   --token xxxx.xxxxxxxxxxx \
#   --discovery-token-ca-cert-hash sha256:xxxxxxxx

node1 加入

sudo kubeadm join 192.168.205.141:6443 \
  --token xxxx.xxxxxxxxxxx \
  --discovery-token-ca-cert-hash sha256:xxxxxxxx \
  --cri-socket unix:///run/cri-dockerd.sock

成功标志:

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

node2 同样执行

sudo kubeadm join 192.168.205.141:6443 \
  --token xxxx.xxxxxxxxxxx \
  --discovery-token-ca-cert-hash sha256:xxxxxxxx \
  --cri-socket unix:///run/cri-dockerd.sock

十、最终验收

查看节点

在 master 执行:

kubectl get nodes

最终结果:

NAMESTATUSROLES
k8s-masterReadycontrol-plane
k8s-node1Ready<none>
k8s-node2Ready<none>

查看系统 Pod

kubectl get pods -A

关键组件:

NamespacePodStatus
kube-systemcorednsRunning
kube-systemetcd-k8s-masterRunning
kube-systemkube-apiserverRunning
kube-systemkube-controller-managerRunning
kube-systemkube-schedulerRunning
kube-systemkube-proxyRunning
kube-flannelkube-flannel-ds-xxxRunning

十一、本阶段故障记录

问题 1:cgroup v1

错误信息:

cgroups v1 support is deprecated

原因: Kubernetes 1.36 对 cgroup v1 不再默认支持。

处理: Ubuntu 20.04 当前环境使用 cgroup v1,修改/etc/default/grub,systemd.unified_cgroup_hierarchy=1,开启 systemd cgroup v2,执行 update-grub 并重启节点。重启后 stat -fc %T /sys/fs/cgroup 确认 /sys/fs/cgroup 使用 cgroup2,然后重新执行 kubeadm 初始化。 kubeadm 重新初始化后正常运行。

问题 2:cri endpoint 冲突

错误信息:

found multiple CRI endpoints
containerd.sock
cri-dockerd.sock

原因: 系统同时存在 containerd 和 cri-dockerd。

解决: 所有 kubeadm 操作明确指定:

--cri-socket unix:///run/cri-dockerd.sock

问题 3:worker join 失败

第一次现象:

cluster-info forbidden

原因: kubeadm init 未完整结束。

最终解决: 不是补 RBAC,而是:

kubeadm reset
重新 kubeadm init

十二、本阶段最终成果

完成进度

阶段内容状态
阶段 1基础环境
阶段 2Docker + cri-dockerd
阶段 3Kubernetes Cluster

当前集群

节点状态
k8s-masterReady
k8s-node1Ready
k8s-node2Ready

下一阶段规划

Helm
  ↓
Ingress
  ↓
Harbor 私有镜像仓库
  ↓
Java Spring Boot Demo
  ↓
Jenkins CI/CD
  ↓
ELK 日志平台

后续 Ansible 角色拆分建议

roles/
├── container-runtime
├── kubernetes-install
├── kubeadm-init
└── cni

拓展讲解

一、CNI 网络插件详解

什么是 CNI?

CNI(Container Network Interface)是 Kubernetes 的网络接口标准,定义了容器网络应该如何配置。可以把它理解为 Kubernetes 的"网络施工队"。

为什么 Kubernetes 需要 CNI?

kubeadm init 之后:
  kubectl get nodes
  k8s-master   NotReady    ← 因为没有网络插件

安装 Flannel 之后:
  kubectl get nodes
  k8s-master   Ready       ← 网络通了

Kubernetes 本身不实现网络功能,它只定义标准(CNI),具体的网络方案由第三方插件实现。

常见的 CNI 插件对比

CNI 插件实现方式性能网络策略适用场景
FlannelVXLAN 隧道⭐⭐⭐❌ 不支持小规模、学习环境
CalicoBGP 路由⭐⭐⭐⭐⭐✅ 支持生产环境、大规模
Weave混合模式⭐⭐⭐⭐✅ 支持多云环境
CiliumeBPF⭐⭐⭐⭐⭐✅ 强大云原生、高性能
** Canal **Flannel + Calico⭐⭐⭐⭐✅ 支持兼顾性能和策略

为什么本项目选用 Flannel?

原因如下:

  1. 简单易用:Flannel 是所有 CNI 中最简单的,一条命令搞定
  2. 匹配 CIDR:Flannel 默认使用 10.244.0.0/16,正好匹配 init 参数
  3. 学习友好:作为实验环境,Flannel 的概念最直观——它就是给每个节点分配一个子网段,然后用 VXLAN 隧道打通
  4. 资源占用低:Flannel 的 DaemonSet 只消耗很少的内存和 CPU

Flannel 的工作原理:

Node A (10.244.1.0/24)              Node B (10.244.2.0/24)
    │                                     │
    │ Pod: 10.244.1.2                     │ Pod: 10.244.2.3
    │                                     │
    └─── flannel.1 (VXLAN隧道) ──────────►│
         │                                │
         │ 物理网络 eth0                   │ 物理网络 eth0
         │ 192.168.205.141                │ 192.168.205.142

Flannel 在每个节点上创建一个虚拟网卡 flannel.1,Pod 的数据包通过 VXLAN 隧道封装,在物理网络中传输到目标节点。

为什么不选 Calico?

如果项目后续需要网络策略(NetworkPolicy)来控制 Pod 之间的访问权限,可以考虑迁移到 Calico。但对于当前的学习环境,Flannel 足够。


二、RBAC 深度讲解

先说结论

RBAC(Role-Based Access Control)是 Kubernetes 的权限控制机制,用来决定"谁(Subject)可以对什么资源(Resource)执行什么操作(Verb)"。

项目里 RBAC 有两个层面:

  1. Kubernetes 默认组件使用 RBAC——kubeadm 初始化时自动创建了大量 RBAC 规则
  2. 故障排查中接触了 kubeadm bootstrap 相关 RBAC——worker 节点加入时的权限问题

1. RBAC 基本模型

RBAC 三个核心概念:

Subject(谁)
    │
    │ 通过 RoleBinding / ClusterRoleBinding 绑定
    ▼
Role / ClusterRole(能做什么)
    │
    │ 包含权限规则
    ▼
Resources(对什么资源)

对应关系:

概念说明类比
Subject谁要操作员工
Role / ClusterRole权限规则集合岗位说明书
RoleBinding / ClusterRoleBinding把权限赋给某人任命书
Resources操作对象公司资产
Verbs允许的操作能做什么

2. Kubernetes RBAC 四个核心对象

Role(角色——命名空间级)

只能在某个命名空间内生效。

kind: Role
metadata:
  namespace: default        # 只在 default 命名空间有效
  name: pod-reader
rules:
- apiGroups: [""]           # 核心 API 组
  resources: ["pods"]       # 资源:Pod
  verbs: ["get", "list"]    # 允许的操作:查看、列出

意思:default 命名空间中,允许查看和列出 Pod。

ClusterRole(集群角色——集群级)

在整个集群范围内生效,可以访问集群级别的资源(如 Node)。

kind: ClusterRole
name: node-viewer
rules:
- apiGroups: [""]
  resources: ["nodes"]      # Node 是集群资源
  verbs: ["get", "list"]
RoleBinding(角色绑定——命名空间级)

把 Role 绑定到一个用户或 ServiceAccount。

kind: RoleBinding
metadata:
  namespace: default
  name: dev-pod-reader
subjects:
- kind: User
  name: dev-user           # 给 dev-user 用户
roleRef:
  kind: Role
  name: pod-reader         # 绑定 pod-reader 角色
ClusterRoleBinding(集群角色绑定——集群级)
kind: ClusterRoleBinding
name: admin-binding
subjects:
- kind: User
  name: admin-user
roleRef:
  kind: ClusterRole
  name: cluster-admin

3. 项目里 RBAC 实际在哪里用?

场景一:kubeadm 初始化自动创建

执行 kubeadm init 时,kubeadm 自动创建了大量 RBAC 规则:

# 查看所有 ClusterRoleBinding
kubectl get clusterrolebinding

会看到类似:

名称用途
cluster-admin集群管理员(就是你)
kubeadm:kubelet-bootstrap允许 kubelet 引导
kubeadm:node-autoapprove-bootstrap自动批准引导阶段的 CSR
system:node所有节点的默认权限

4. Worker join 为什么需要 RBAC?

这个就是真实遇到的问题。执行 kubeadm join 时,背后发生了这些事情:

node1 执行 kubeadm join --token xxx
        │
        │ 第一步:使用 bootstrap token 作为临时身份
        ▼
API Server 收到请求
        │
        │ 第二步:检查 RBAC——这个 token 有没有权限?
        ▼
RBAC 检查:system:bootstrap:<token-id> 是否允许?
        │
        │ 第三步:允许后,node1 申请证书(CSR)
        ▼
node1 提交 CertificateSigningRequest
        │
        │ 第四步:kube-controller-manager 自动批准
        ▼
node1 获得正式 kubelet 证书
        │
        │ 第五步:从此使用 system:node:k8s-node1 身份
        ▼
node1 正式成为集群一员

类比成公司入职:

你(master)开了家公司(kubeadm init)
  ├── 制定了员工手册(RBAC 规则)
  ├── 准备了入职邀请码(bootstrap token)
  └── 等着新人来报到

node1(新员工)拿着邀请码来面试
  ├── 前台(API Server)查邀请码是否有效(RBAC 检查)
  ├── 通过后,填入职申请表(CSR)
  ├── HR(controller-manager)审批通过
  └── 拿到工牌(kubelet 证书),正式上班

5. 之前失败为什么是 RBAC 问题?

第一次失败
错误:User "system:anonymous" cannot get configmaps

原因: API Server 认为 node1 是匿名用户system:anonymous),根本没有使用 token。

根本原因: kubeadm init 没有完整执行,导致 bootstrap token 和相关 RBAC 规则都没有创建。

第二次失败
错误:User "system:bootstrap:xxxx" cannot get configmaps kube-system/kubeadm-config

原因: 虽然 token 通过了,但 bootstrap 用户的权限不完整——因为 kubeadm init 没有完成所有 phase。

最终发现: 不是单独的 RBAC 问题,而是 kubeadm init 没完成,导致 bootstrap 相关的 RBAC 规则没有创建完整。

三、Worker Join 完整流程详解

把 node1 加入集群的整个过程画出来:

                    master (192.168.205.141)
                         │
                    kubeadm init
                         │
                    ┌────┴────┐
                    │         │
              创建 CA     创建 RBAC
              签发证书     bootstrap 权限
                    │         │
                    └────┬────┘
                         │
                    生成 token
                    (邀请码)
                         │
                         ▼
                    API Server 监听 :6443
                         ▲
                         │
node1 (192.168.205.142)  │
    │                    │
    │ kubeadm join       │
    │ --token xxx        │
    │                    │
    ├── 1. 发现 master ──┤
    │   通过 token 发现   │  验证 token 是否有效
    │                    │
    ├── 2. 获取集群信息 ──┤
    │   读取 cluster-info │  RBAC 检查通过
    │                    │
    ├── 3. 提交 CSR ─────┤
    │   申请 kubelet 证书  │  自动批准 CSR
    │                    │
    ├── 4. 获取证书 ─────┤
    │   下载已签发的证书   │
    │                    │
    └── 5. 启动 kubelet ──┘
        使用正式身份工作

一句话总结:

Worker 节点加入 Kubernetes 时,先使用 bootstrap token 获取临时身份,通过 RBAC 完成证书申请,master 签发 kubelet 证书后,节点转换成 system:node 身份,之后使用 Node RBAC 工作。


四、Flannel 工作原理详解

为什么需要 Flannel?

Kubernetes 要求所有 Pod 之间可以直接通信,不论它们在哪个节点上。但物理网络的限制是:

Node A 上的 Pod:10.244.1.2
Node B 上的 Pod:10.244.2.3

这两个 IP 在物理网络上是不通的,因为物理网络不认识 10.244.x.x

Flannel 解决了这个问题。

Flannel 的核心思想

给每个节点分配一个独立的子网段,然后用隧道技术打通。

Flannel 的分配:
  k8s-master:10.244.0.0/24  (可用 254 个 IP)
  k8s-node1: 10.244.1.0/24  (可用 254 个 IP)
  k8s-node2: 10.244.2.0/24  (可用 254 个 IP)

每个节点上的 Pod 只能使用自己节点的网段:
  k8s-master 上的 Pod:10.244.0.x
  k8s-node1 上的 Pod: 10.244.1.x
  k8s-node2 上的 Pod: 10.244.2.x

VXLAN 隧道原理

当一个 Pod 要访问另一个节点上的 Pod 时:

Pod A (10.244.1.2) → Pod B (10.244.2.3)
         │
         │ 目标 IP 是 10.244.2.3,不在本节点
         ▼
    Flannel 发现目标在 node2
         │
         │ 把原始数据包封装到 VXLAN 隧道中
         ▼
    封装后的数据包:
    ┌─────────────────────────────────────┐
    │ 外层:源 192.168.205.142 (node1)    │
    │       目标 192.168.205.143 (node2)  │
    │                                     │
    │ 内层:源 10.244.1.2 (Pod A)         │
    │       目标 10.244.2.3 (Pod B)       │
    └─────────────────────────────────────┘
         │
         │ 通过物理网络发送
         ▼
    node2 收到后解封装
         │
         │ 把内层数据包交给 Pod B
         ▼
    Pod B 收到来自 Pod A 的数据

类比理解

Flannel 就像一个快递公司:

每个节点 = 一个城市的分拨中心
每个 Pod = 一个具体的收货地址
VXLAN 隧道 = 城际运输专线

当深圳的包裹要送到北京:
  深圳分拨中心(node1)收到包裹
  检查地址:10.244.2.3 → 属于北京片区
  打包进集装箱(VXLAN 封装)
  通过京深专线(物理网络)运到北京
  北京分拨中心(node2)拆箱
  送到具体地址(Pod B)

总结

本阶段关键技术点

技术作用项目中的应用
CNI容器网络标准选用 Flannel 实现 Pod 互通
Flannel覆盖网络方案给每个节点分配子网,VXLAN 隧道通信
RBAC权限控制kubeadm 自动创建,控制组件和用户权限
bootstrap token临时身份worker 加入时使用,完成后换成正式证书
CSR证书申请worker 通过 CSR 获取 kubelet 证书

更多推荐