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?
原因如下:
- 简单易用:Flannel 是所有 CNI 中最简单的,一条命令搞定
- 匹配 CIDR :Flannel 默认使用
10.244.0.0/16,正好匹配 init 参数 - 学习友好:作为实验环境,Flannel 的概念最直观------它就是给每个节点分配一个子网段,然后用 VXLAN 隧道打通
- 资源占用低: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 有两个层面:
- Kubernetes 默认组件使用 RBAC------kubeadm 初始化时自动创建了大量 RBAC 规则
- 故障排查中接触了 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 证书 |