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

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 Plane 1
Worker Nodes 2
CNI Flannel
Container Runtime Docker + cri-dockerd

部署前环境确认

节点信息

节点 IP 角色
k8s-master 192.168.205.141 Control Plane
k8s-node1 192.168.205.142 Worker
k8s-node2 192.168.205.143 Worker

Runtime 架构确认

Kubernetes 不直接调用 Docker:

复制代码
kubelet
    ↓
cri-dockerd
    ↓
Docker Engine

二、Kubernetes 安装准备

1. 确认 swap 已关闭

三个 Kubernetes 节点执行:

bash 复制代码
free -h

确认结果:Swap: 0B

永久关闭:

bash 复制代码
sudo swapoff -a

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

2. 内核模块

加载:

bash 复制代码
sudo modprobe overlay
sudo modprobe br_netfilter

确认:

bash 复制代码
lsmod | grep overlay
lsmod | grep br_netfilter

3. 内核参数

配置:

bash 复制代码
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

生效:

bash 复制代码
sudo sysctl --system

检查:

bash 复制代码
sysctl net.ipv4.ip_forward

结果:net.ipv4.ip_forward = 1


三、Docker + cri-dockerd 验证

Docker

三个节点检查:

bash 复制代码
docker --version
# 示例输出:Docker version xx.xx

systemctl status docker
# 要求:active (running)

Docker cgroup driver

检查:

bash 复制代码
docker info | grep "Cgroup Driver"
# 要求:Cgroup Driver: systemd

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

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

重启:

bash 复制代码
sudo systemctl restart docker

cri-dockerd

检查版本:

bash 复制代码
cri-dockerd --version

检查 socket:

bash 复制代码
sudo systemctl status cri-docker.socket
# 要求:Active: active (running)

检查 socket 文件:

bash 复制代码
ls -l /run/cri-dockerd.sock
# 结果:srw-rw---- root docker /run/cri-dockerd.sock

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

第一次执行:

bash 复制代码
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

原因分析

控制面组件已经启动:

组件 状态
etcd OK
kube-apiserver OK
scheduler OK
controller OK

但是: kubeadm init 后续 phase 未完成

导致:

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

最终选择

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

bash 复制代码
kubeadm reset
重新 init

五、清理失败 Kubernetes 状态

三个 Kubernetes 节点执行:

bash 复制代码
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 额外执行:

bash 复制代码
sudo rm -rf /var/lib/etcd

六、重新初始化 Kubernetes Master

在 master 执行:

bash 复制代码
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 执行:

bash 复制代码
# 创建目录
mkdir -p $HOME/.kube

# 复制配置
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

# 修改权限
sudo chown $(id -u):$(id -g) $HOME/.kube/config

验证:

bash 复制代码
kubectl get nodes
# 初始:k8s-master NotReady
# 原因:没有安装 CNI

八、安装 Flannel 网络插件

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

安装:

bash 复制代码
kubectl apply -f \
  https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

检查:

bash 复制代码
kubectl get pods -n kube-flannel
# 结果:kube-flannel-ds-xxxxx 1/1 Running

检查节点:

bash 复制代码
kubectl get nodes
# 结果:k8s-master Ready

九、Worker 节点加入集群

生成 join 命令

在 master 执行:

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

node1 加入

bash 复制代码
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 同样执行

bash 复制代码
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 执行:

bash 复制代码
kubectl get nodes

最终结果:

NAME STATUS ROLES
k8s-master Ready control-plane
k8s-node1 Ready <none>
k8s-node2 Ready <none>

查看系统 Pod

bash 复制代码
kubectl get pods -A

关键组件:

Namespace Pod Status
kube-system coredns Running
kube-system etcd-k8s-master Running
kube-system kube-apiserver Running
kube-system kube-controller-manager Running
kube-system kube-scheduler Running
kube-system kube-proxy Running
kube-flannel kube-flannel-ds-xxx Running

十一、本阶段故障记录

问题 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 操作明确指定:

bash 复制代码
--cri-socket unix:///run/cri-dockerd.sock

问题 3:worker join 失败

第一次现象:

复制代码
cluster-info forbidden

原因: kubeadm init 未完整结束。

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

bash 复制代码
kubeadm reset
重新 kubeadm init

十二、本阶段最终成果

完成进度

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

当前集群

节点 状态
k8s-master Ready
k8s-node1 Ready
k8s-node2 Ready

下一阶段规划

复制代码
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 插件 实现方式 性能 网络策略 适用场景
Flannel VXLAN 隧道 ⭐⭐⭐ ❌ 不支持 小规模、学习环境
Calico BGP 路由 ⭐⭐⭐⭐⭐ ✅ 支持 生产环境、大规模
Weave 混合模式 ⭐⭐⭐⭐ ✅ 支持 多云环境
Cilium eBPF ⭐⭐⭐⭐⭐ ✅ 强大 云原生、高性能
** 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(角色------命名空间级)

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

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

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

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

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

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

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

yaml 复制代码
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(集群角色绑定------集群级)
yaml 复制代码
kind: ClusterRoleBinding
name: admin-binding
subjects:
- kind: User
  name: admin-user
roleRef:
  kind: ClusterRole
  name: cluster-admin

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

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

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

bash 复制代码
# 查看所有 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 证书
相关推荐
米饭不加菜1 小时前
C51 语言参考手册
经验分享·笔记·单片机·嵌入式硬件
qq_349447951 小时前
K8S中的Calico如何获取对应版本
云原生·容器·kubernetes
sunshine22 girl1 小时前
Angular7,9,学习笔记一 创建项目,基本语法
笔记·学习
摇滚侠2 小时前
《SpringBoot 3:入门与应用实战》第 6 章 Spring Boot 最佳实践 阅读笔记 11
spring boot·笔记·后端
mlidongfeng2 小时前
[AI][昇腾950]TP(Transport Layer,传输层)学习笔记
笔记·学习
九硕智慧建筑一体化厂家2 小时前
楼宇智能优选!KNX总线系统打造稳定高效智能控制体系
运维·人工智能·笔记·智慧城市
Lyy2 小时前
DevOps平台 — 第七篇:我的项目与表格组件抽取
后端·devops
智能运维指南3 小时前
需求黑洞、业务与 IT 脱节、外包失控:企业需求管理平台如何破局?
devops·cteam
苏子寒3 小时前
Nano-VLLM全代码解析笔记(8)-qwen3与qwen3_moe
笔记·python·深度学习·ai·性能优化·vllm