K8S总结

一、从 Docker Compose 到 Kubernetes:为什么需要编排

在接触 K8s 之前,很多人已经用过 Docker Compose 来管理多容器应用。Compose 用一个 docker-compose.yml 就能定义服务、网络、数据卷,一条 up -d 拉起整套环境------这在单机开发场景下非常方便。

复制代码
services:
  web1:
    image: nginx:1.23
    networks:
      - mynet1
    volumes:
      - "webdata1:/usr/share/nginx/html"
  web2:
    image: nginx:1.23
    networks:
      - mynet1
    volumes:
      - "webdata2:/usr/share/nginx/html"
  haproxy:
    image: haproxy:2.3
    ports:
      - "80:80"
    volumes:
      - "/conf/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg"

但 Compose 有明显的天花板:

维度 Docker Compose Kubernetes
规模 单机多容器 跨节点集群
自愈能力 无自动重启策略 ReplicaSet/Deployment 自动补副本
负载均衡 手动配 haproxy Service 原生支持
滚动更新 声明式,零停机
资源调度 Scheduler 按资源/亲和性调度

我的理解 :Compose 是"写死在哪台机器上跑什么",而 K8s 是"声明我要什么状态,系统自己决定怎么跑"。这就是命令式 vs 声明式的本质区别。

二、Kubernetes 集群架构与核心组件原理

2.1 整体架构一览

图为k8s集群集体架构

一个完整的 K8s 集群由两大部分组成:

2.2 控制平面(Control Plane)

控制平面是集群的"大脑",负责全局决策和事件响应。

组件 职责 我的理解
kube-apiserver 暴露 HTTP API,是唯一入口 所有组件都通过它通信;做鉴权、准入校验
etcd 高可用键值数据库 存储集群全部状态(Pod、Service、ConfigMap...);是唯一的"真相源"
kube-scheduler 为未调度的 Pod 选择运行节点 综合考虑 CPU/内存/亲和性/污点等
kube-controller-manager 运行各种控制器循环 维持期望态:副本数不够就创建、Node 宕机就迁移

关键点 :控制器的工作模式是控制循环(Control Loop)------不断监听 apiserver 的实际态,与期望态比较,有偏差就修正。这是 K8s 自愈能力的根源。

2.3 节点组件(Node Components)

每个工作节点运行的组件:

组件 职责
kubelet 在本节点启动 Pod/容器,向 apiserver 报告状态
kube-proxy 维护 iptables/ipvs 规则,实现 Service 的负载均衡
容器运行时 真正运行容器的引擎(Docker/containerd/CRI-O)

2.4 为什么需要 cri-dockerd?

这是一个非常重要的历史转折点

K8s v1.24 之前,kubelet 内置了 dockershim 直接调用 Docker Engine。但从 1.24 起,K8s 要求所有容器运行时必须实现 CRI(Container Runtime Interface) 标准。Docker Engine 本身不实现 CRI,所以需要中间层适配器 ------ cri-dockerd

复制代码
kubelet → (CRI 协议) → cri-dockerd.sock → cri-dockerd → Docker Engine

这就是我们在部署时必须安装 cri-dockerd 的原因。它本质上是把被移除的 dockershim 以独立进程形式复活了。

三、主流集群部署方式对比

图 2:kubeadm 五步部署流程及每步的核心原理

方式 适用场景 复杂度 原理说明
kubeadm 生产环境首选 ⭐⭐⭐ 官方推荐工具,用静态 Pod 启动控制面,标准化程度最高
二进制手动部署 深度学习原理 ⭐⭐⭐⭐⭐ 手动生成证书、配置 systemd unit,理解最深但最繁琐
kubespray(Ansible) 批量生产部署 ⭐⭐⭐⭐ 基于 Ansible playbook,适合一次部署几十个节点
云托管(EKS/GKE/ACK) 企业上云 托管控制面,只管 Worker 节点
minikube / kind 本地学习测试 单机模拟集群,快速验证

我的选择 :学习和中小型生产环境,kubeadm 是最佳平衡点------既不是黑盒(能看到每一步),又不会像二进制部署那样让人崩溃。下面我们以 kubeadm 为主线展开实战。


四、实战:基于 kubeadm 的集群部署全流程

4.1 环境规划

本次实验使用以下拓扑(来自实际操作记录):

主机名 IP 地址 配置 角色
harbor.timinglee.org 172.25.254.254 1C/1G Harbor 私有镜像仓库
master 172.25.254.100 4C/>4G 控制平面
node1 172.25.254.10 2C/2G 工作节点
node2 172.25.254.20 2C/2G 工作节点

我的建议:Master 至少 4 核 4G 内存,否则 etcd + apiserver 会 OOM。生产环境建议 Master 8C/16G+ 且高可用(3 个 master)。

4.2 第一步:所有主机前置准备

关闭 Swap(必须!)
复制代码
systemctl disable --now swap.target
systemctl mask swap.target
sed '/swap/s/^/#/g' -i /etc/fstab

原理 :Kubelet 默认检测到 swap 就拒绝启动。因为 K8s 的 QoS 和资源保证机制依赖 cgroup 对内存的精确控制,swap 会破坏这种保证。官方文档明确要求禁用或配置 failSwapOn: false

安装 Docker 并配置私有仓库访问
复制代码
# 安装 docker-ce(所有节点)
dnf install docker-ce -y

# 内核参数(桥接流量走 iptables)
cat > /etc/sysctl.d/docker.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system

# 分发 Harbor 证书到所有节点
mkdir -p /etc/docker/certs.d/reg.timinglee.org/
scp harbor:/data/certs/timinglee.org.crt \
    /etc/docker/certs.d/reg.timinglee.org/ca.crt

# 配置镜像加速器指向 Harbor
cat > /etc/docker/daemon.json <<EOF
{
  "registry-mirrors":["https://reg.timinglee.org"]
}
EOF
systemctl restart docker && systemctl enable docker

为什么要私有仓库? 国内拉取 registry.k8s.iogcr.io 经常超时。Harbor 作为本地缓存代理,不仅加速 K8s 组件镜像下载,也是企业存放业务镜像的标准方案。

配置主机名解析 & K8s yum 源
复制代码
# 所有节点的 /etc/hosts
172.25.254.100     master
172.25.254.10      node1
172.25.254.20      node2
172.25.254.200     reg.timinglee.org

# K8s 安装源(阿里云镜像)
cat > /etc/yum.repos.d/kubernetes.repo <<EOF
[kubernetes]
name = kubernetes
baseurl = https://mirrors.aliyun.com/kubernetes-new/core/stable/v1.35/rpm/
gpgcheck = 0
EOF

4.3 第二步:安装 cri-dockerd(所有节点)

这是 K8s 1.24+ 使用 Docker 运行时的关键步骤:

复制代码
# 方式 A:RPM 包安装(版本较旧)
rpm -ivh cri-dockerd-0.3.14-3.el8.x86_64.rpm libcgroup-0.41-19.el8.x86_64.rpm

# 方式 B:二进制包安装(推荐,版本更新)
wget https://github.com/Mirantis/cri-dockerd/releases/download/v0.4.4/cri-dockerd-0.4.4.amd64.tgz
tar zxf cri-dockerd-0.4.4.amd64.tgz
install -o root -g root -m 0755 cri-dockerd/cri-dockerd /usr/local/bin/cri-dockerd
cp cri-dockerd/cri-docker.service /lib/systemd/system/
cp cri-dockerd/cri-docker.socket /lib/systemd/system/
chmod +x /lib/systemd/system/cri-docker.s*

修改 service 文件指定 pause 镜像和 CRI socket

复制代码
[Service]
Type=notify
ExecStart=/usr/local/bin/cri-dockerd \
  --network-plugin=cni \
  --pod-infra-container-image=reg.timinglee.org/k8s/pause:3.10.1 \
  --container-runtime-endpoint fd://
Restart=always

systemctl daemon-reload
systemctl enable --now cri-docker.service
ll /var/run/cri-dockerd.sock   # 确认 socket 存在

pause 镜像是做什么的? 它是每个 Pod 的"基础设施容器"(也叫 sandbox container),负责持有 Pod 的网络命名空间。所有同一 Pod 的容器共享它的网络栈。这个镜像必须提前准备好,否则 Pod 创建会卡在 ContainerCreating。

4.4 第三步:安装 kubelet / kubeadm / kubectl

复制代码
# Master 节点(需要 kubectl 管理)
dnf install kubelet kubeadm kubectl -y
systemctl enable --now kubelet.service

# Node 节点(不需要 kubectl)
dnf install kubelet kubeadm -y
systemctl enable --now kubelet.service

# Master 补齐 bash 自动补全
echo "source <(kubectl completion bash)" >> ~/.bashrc
echo "source <(kubeadm completion bash)" >> ~/.bashrc
source ~/.bashrc

注意kubeadm 不帮你管理 kubeletkubectl,你需要自己确保三者版本匹配。官方建议 kubelet 版本不超过 apiserver 一个小版本。

4.5 第四步:拉取并推送镜像到 Harbor

复制代码
# 从阿里云镜像站拉取 K8s 组件镜像
kubeadm config images pull \
  --image-repository registry.aliyuncs.com/google_containers \
  --kubernetes-version v1.35.3 \
  --cri-socket=unix:///var/run/cri-dockerd.sock

# 输出:
# Pulled registry.aliyuncs.com/google_containers/kube-apiserver:v1.35.3
# Pulled registry.aliyuncs.com/google_containers/kube-controller-manager:v1.35.3
# Pulled registry.aliyuncs.com/google_containers/kube-scheduler:v1.35.3
# Pulled registry.aliyuncs.com/google_containers/kube-proxy:v1.35.3
# Pulled registry.aliyuncs.com/google_containers/coredns:v1.13.1
# Pulled registry.aliyuncs.com/google_containers/pause:3.10.1
# Pulled registry.aliyuncs.com/google_containers/etcd:3.6.6-0

推送到本地 Harbor(这样其他节点也能快速拉取):

复制代码
docker login reg.timinglee.org -u admin
# 批量 tag 并 push
docker images --format "{{.Repository}}:{{.Tag}}" \
  | awk -F "/" '/google/{system("docker tag "$0" reg.timinglee.org/k8s/"$3)}'
docker images --format "{{.Repository}}:{{.Tag}}" \
  | awk -F "/" '/timinglee/{system("docker push "$0)}'

这 7 个镜像就是 K8s 控制平面和工作节点的全部依赖。

4.6 第五步:kubeadm init 初始化控制平面

复制代码
kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --image-repository reg.timinglee.org/k8s \
  --kubernetes-version v1.35.3 \
  --cri-socket=unix:///var/run/cri-dockerd.sock

参数解读(我的说明)

参数 含义 为什么重要
--pod-network-cidr Pod 网络地址段 必须与后续 CNI 插件配置一致,Flannel 默认就是 10.244.0.0/16
--image-repository 镜像仓库地址 默认去 registry.k8s.io 拉,国内基本连不上,改为我们的 Harbor
--cri-socket CRI socket 路径 告诉 kubeadm 用哪个容器运行时

init 成功后输出关键信息

复制代码
Your Kubernetes control-plane has initialized successfully!

# 加入集群的凭证(保存好!)
kubeadm join 172.25.254.100:6443 \
  --token tdwjoc.8d1yw3wl4r4tm6c4 \
  --discovery-token-ca-cert-hash sha256:6b59...

配置 kubectl 访问集群

复制代码
export KUBECONFIG=/etc/kubernetes/admin.conf
echo "export KUBECONFIG=/etc/kubernetes/admin.conf" >> ~/.bash_profile
source ~/.bash_profile

kubectl get nodes
# NAME     STATUS     ROLES           AGE   VERSION
# master   NotReady   control-plane   102s  v1.35.3

注意 :此时节点状态是 NotReady,这是正常的!因为我们还没装网络插件(CNI),Pod 网络还没打通。

4.7 第六步:Worker 节点加入集群

在每个 worker 节点上执行 init 时输出的 join 命令:

复制代码
# node1 和 node2 分别执行
kubeadm join 172.25.254.100:6443 \
  --token jl4ztx.cax3iysvu7onsh5s \
  --discovery-token-ca-cert-hash sha256:6b59... \
  --cri-socket=unix:///var/run/cri-dockerd.sock

Token 原理 :Token 是一张短期有效的引导凭据(默认 24 小时),包含 join 所需的基本信息。ca-cert-hash 是控制平面 CA 证书的 SHA256 摘要,用于防止你手滑 join 到别人的集群。

如果忘了 token:

复制代码
kubeadm token create --print-join-command
# 会重新生成并打印完整 join 命令

如果初始化出了严重问题想重来:

复制代码
kubeadm reset --cri-socket=unix:///var/run/cri-dockerd.sock
# 这会清理本机的 K8s 配置,但不影响其他节点

加入后在 master 上查看:

复制代码
kubectl get nodes
# NAME     STATUS     ROLES           AGE     VERSION
# master   NotReady   control-plane   5m16s   v1.35.3
# node1    NotReady   <none>          66s     v1.35.3
# node2    NotReady   <none>          58s     v1.35.3

三个节点都在了,但都是 NotReady ------ 等待网络插件。

4.8 第七步:部署 Flannel 网络插件

Flannel 是最常用的 CNI(Container Network Interface)插件之一,为每个 Pod 分配独立的 IP 地址,并通过 VXLAN/UDP 封装实现跨节点互通。

复制代码
# 加载 flannel 镜像到本地
docker load -i flannel-0.28.1.tar

# tag 并推送到 Harbor(确保所有节点能拉取)
docker tag ghcr.io/flannel-io/flannel-cni-plugin:v1.9.0-flannel1 \
  reg.timinglee.org/flannel-io/flannel-cni-plugin:v1.9.0-flannel1
docker push reg.timinglee.org/flannel-io/flannel-cni-plugin:v1.9.0-flannel1
docker tag ghcr.io/flannel-io/flannel:v0.28.1 \
  reg.timinglee.org/flannel-io/flannel:v0.28.1
docker push reg.timinglee.org/flannel-io/flannel:v0.28.1

修改 kube-flannel.yml 中的镜像地址为我们的 Harbor,然后 apply:

复制代码
kubectl apply -f kube-flannel.yml
# namespace/kube-flannel created
# serviceaccount/flannel created
# clusterrole.rbac.authorization.k8s.io/flannel created
# clusterrolebinding.rbac.authorization.k8s.io/flannel created
# configmap/kube-flannel-cfg created
# daemonset.apps/kube-flannel-ds created

验证集群 Ready

复制代码
kubectl get nodes
# NAME     STATUS   ROLES           AGE     VERSION
# master   Ready    control-plane   13m     v1.35.3
# node1    Ready    <none>          9m44s   v1.35.3
# node2    Ready    <none>          9m36s   v1.35.3

🎉 三个节点全部 Ready!集群部署完成。

五、部署后的 Pod 管理与应用发布

5.1 基础对象管理

复制代码
# 查看命名空间
kubectl get namespaces
# default / kube-system / kube-flannel / kube-public / kube-node-lease

# 创建 Pod(命令式)
kubectl run lee --image nginx:latest
kubectl get pods -o wide
# NAME   READY   STATUS    RESTARTS   AGE   IP            NODE
# lee    1/1     Running   0          25s   10.244.1.10   k8s-node1

# 排查问题 Pod
kubectl describe pods error-pod   # 查看 Events 定位原因
kubectl logs pods/my-pod         # 查看容器日志
kubectl exec -it pods/testpod -- /bin/bash  # 进入容器
kubectl cp testpod:/path/file ./local       # 从 Pod 拷贝文件

5.2 Deployment 控制器:声明式管理的核心

Deployment 是生产中最常用的控制器,提供副本管理、滚动更新、回滚等能力。

复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: webcluster
  labels:
    app: webcluster
spec:
  replicas: 2                    # 期望副本数
  selector:
    matchLabels:
      app: webcluster
  template:
    metadata:
      labels:
        app: webcluster
    spec:
      containers:
      - image: myapp:v1
        name: myapp
        ports:
        - containerPort: 80

kubectl apply -f webcluster.yml
kubectl expose deployment webcluster --port 80 --target-port 80 --type NodePort
kubectl get svc
# NAME         TYPE        CLUSTER-IP     PORT(S)        AGE
# webcluster   NodePort    10.99.3.32    80:30713/TCP   2m54s

此时可以通过 http://<任意节点IP>:30713 访问应用。

5.3 滚动更新与回滚

图 3:Deployment 滚动更新与回滚原理 --- 新旧 ReplicaSet 同时保留

复制代码
# 更新镜像版本(触发滚动更新)
kubectl set image deployments/webcluster myapp=myapp:v2
kubectl annotate deployment/webcluster kubernetes.io/change-cause="升级到v2"

# 查看更新进度
kubectl rollout status deployment/webcluster
# deployment "webcluster" successfully rolled out

# 查看历史版本
kubectl rollout history deployment/webcluster
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         升级到v2

# 回滚到上一版
kubectl rollout undo deployment/webcluster

# 回滚到指定版本
kubectl rollout undo deployment/webcluster --to-revision=1

原理说明 :Deployment 底层维护多个 ReplicaSet(RS),每个 RS 对应一个版本。滚动更新时,新 RS 逐步扩容、旧 RS 逐步缩容,始终保持可用副本数不低于阈值。回滚时只需切换活跃 RS 即可,秒级完成。

5.4 其他常用 kubectl 操作速查

操作 命令 说明
弹性伸缩 kubectl scale deploy webcluster --replicas=4 动态调整副本数
编辑资源 kubectl edit deploy webcluster 打开编辑器直接改 YAML
打补丁 kubectl patch deploy webcluster -p '{"spec":{"replicas":1}}' 局部更新
重启 kubectl rollout restart deployment webcluster 滚动重启所有 Pod
标签管理 kubectl label pods xxx app=newlabel 添加/修改标签
端口暴露 kubectl expose deploy webcluster --port 80 创建 Service

六、用 Ansible 实现部署自动化

图 4:Ansible 无代理批量自动化 --- 控制节点通过 SSH 编排所有受控主机

前面我们手动在 4 台机器上执行了几十条命令。如果是 50 台节点呢?这就需要 Ansible 了。

6.1 Ansible 是什么?

Ansible 是一个 IT 自动化工具,以**无代理(agentless)**方式管理机器,使用 OpenSSH 进行传输。

执行流程

  1. 加载配置文件和 inventory 清单
  2. 将任务编译成临时 Python 脚本
  3. 通过 SSH 传送到受控节点执行
  4. 收集结果返回给控制节点
  5. 删除远端临时文件

这意味着受控节点只需要 Python 和 SSH,不需要安装任何 Agent。

6.2 Ansible 自动化 K8s 部署的关键结构

复制代码
# inventory 文件 --- 定义受控主机分组
[master]
192.168.168.10

[workers]
192.168.168.21
192.168.168.22

[harbor]
192.168.168.200

[k8s:children]
master
workers
harbor

# playbook 示例 --- k8s_cluster_init.yml
---
- name: 初始化 K8s 集群
  hosts: k8s
  become: yes
  tasks:
    - name: 关闭 swap
      ansible.builtin.command: swapoff -a
      # ... 更多 tasks

- name: 部署 Harbor
  hosts: harbor
  roles:
    - harbor

- name: 初始化 Master
  hosts: master
  roles:
    - k8s-master

- name: 加入 Worker 节点
  hosts: workers
  roles:
    - k8s-worker

我的实践体会:对于 K8s 集群部署这种"多机多步骤"的任务,Ansible 的价值在于:

  • 幂等性:重复执行不会出错(已完成的跳过)
  • 角色化(Roles):将 Harbor 部署、Master 初始化、Worker Join 拆成独立 role,可复用
  • 变量驱动:不同环境的 IP、版本号通过变量文件切换

进阶选择 :如果你不想自己写 playbook,社区有成熟的 kubespray 项目,它本身就是一套完整的 Ansible playbook 集合,支持高可用集群部署。


七、总结与踩坑建议

7.1 核心原理回顾

图 5:控制平面组件协作 --- 用户声明期望态,控制器让实际态逼近期望态

回顾整个部署流程,我认为有几个最关键的原理值得记住:

  1. CRI 是必经之路:K8s 1.24 移除 dockershim 后,Docker 必须通过 cri-dockerd 适配。这不是可选的。
  2. 静态 Pod 是 kubeadm 的魔法:控制平面组件(apiserver/etcd/scheduler/controller-manager)本质上是由 kubelet 以静态 Pod 形式拉起的,所以它们也受 kubelet 管理。
  3. NotReady 是正常的:没装 CNI 前节点一定是 NotReady,这不代表部署失败。
  4. Token 有时效 :join token 默认 24 小时过期,忘记的话用 kubeadm token create 重建。
  5. 声明式 > 命令式 :尽量用 YAML + kubectl apply,而不是 kubectl run/create,前者可追溯、可版本化管理。

7.2 常见坑与解决方案

问题 原因 解决
ImagePullBackOff 镜像不存在或仓库不可达 检查 image 字段和 docker login 状态
Init:CrashLoopBackOff Init 容器反复崩溃 kubectl describe pod 看 Event 日志
节点一直 NotReady CNI 未部署或 CNI 配置错误 检查 kube-system 下 daemonset 是否正常
kubeadm join 报错 x509 ca-cert-hash 不匹配 重新获取 hash:`openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt
kubelet 启动失败 swap 未关闭 swapoff -a 并注释 /etc/fstab 中 swap 行
cri-dockerd 连接不上 socket 路径不对 确认 --cri-socket 与实际 socket 路径一致
相关推荐
Yiiz.2 小时前
Kubernetes部署
云原生·容器·kubernetes
qizhideyu2 小时前
kubernetes
云原生·容器·kubernetes
Csxyzj2 小时前
kubernetes集群部署方法
java·linux·kubernetes
wish3662 小时前
K8S 免镜像部署:通过 API 上传文件动态创建服务(以 Ollama 为例)
人工智能·云原生·容器·kubernetes·local llm
小小克3 小时前
k8s集群部署的方法
云原生·容器·kubernetes
kobeyyl3 小时前
K8s集群部署核心原理与主流部署方法全解析
云原生·容器·kubernetes
IT大白鼠3 小时前
Kubernetes Goat:云原生安全靶场实践指南
安全·云原生·kubernetes
无望__wsk5 小时前
k8s部署
云原生·容器·kubernetes
万里侯17 小时前
GitOps 2026年演进趋势:从配置管理到环境即代码的范式转移及Pull vs Push模型的再思考
微服务·容器·k8s