在做算力容器平台的过程中，遇到哪些需要做页面功能、哪些不需要的困惑，经过调研和学习，目前整理出关于工作负载：Pod、Deployment、StatefulSet、DaemonSet、Job、CronJob 的逻辑关系如下，在此进行梳理，以便自己理解进行定义设计： # 工作负载整体操作逻辑 ## 核心逻辑 ![核心逻辑](https://cdn-imagehost.xyzttt.xyz/image/2025/11/21/raoziv.webp) ## 树形图 ![树形图](https://cdn-imagehost.xyzttt.xyz/image/2025/11/24/qymh3v.webp) ## 流程图 ![流程图](https://cdn-imagehost.xyzttt.xyz/image/2025/11/24/o4830h.webp) # References - claude sonnet 4.5
# 安装须知 详见连接：https://docs.openfuyao.cn/docs/Cluster_API_Installation/Before_You_Start # 安装包完整性校验 https://docs.openfuyao.cn/docs/Cluster_API_Installation/Installation_Package_Integrity_Verification # 环境准备 本次实践使用虚拟机作为节点进行部署配置 ## 引导节点 创建一个 4c8g 512GiB 的虚拟机 vm-1318 deployment 并安装系统 DHCPv4 10.17.36.49/22 ## 业务集群节点 创建一个 8c16g 512GiB 的虚拟机 vm-1319 master 并安装系统 DCHPv4 10.17.36.50/22 # 安装准备 ## 在引导节点上，下载并自动化安装 BKE 工具 ```bash curl -sfL https://openfuyao.obs.cn-north-4.myhuaweicloud.com/openFuyao/bkeadm/releases/download/v25.09/download.sh | bash ``` 可能会没有操作权限，使用 `sudo` 命令可以解决 ```bash curl -sfL https://openfuyao.obs.cn-north-4.myhuaweicloud.com/openFuyao/bkeadm/releases/download/v25.09/download.sh | sudo bash ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qhfm9i.webp) ## 初始化引导集群 该步骤会在引导节点上部署一个轻量级的 K3s 集群，集群会部署 cluster-api、provider-bke 以及 openFuyao 安装部署相关的 Pod ```bash bke init --otherRepo cr.openfuyao.cn/openfuyao/bke-online-installed:v25.09 ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qigwav.webp) 老问题，还是要 `sudo` ```bash sudo bke init --otherRepo cr.openfuyao.cn/openfuyao/bke-online-installed:v25.09 ``` ## 查看引导集群上部署的 Pod ```bash sudo kubectl get pod -A ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qkwi40.webp) 所有 Pod 状态正常后，则引导集群安装完毕，可以继续登录openFuyao引导节点的管理面进行[业务集群的安装](https://docs.openfuyao.cn/docs/Cluster_API_Installation/Service_Cluster_Installation_on_the_Management_Plane)。 # 登录引导节点的 openFuyao 管理面 ## 引导节点登录 https:// 引导节点 IP 地址: 30010 **说明：** > - 首次登录时，用户名为 admin，密码为 test@1234。首次登录后需要修改用户名和密码。 > - 引导节点的 openFuyao 管理面使用 https 协议，web 服务端口默认为 30010 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qn994b.webp) ## 新增集群 ### 集群基本信息配置 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qs35n3.webp) ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qs65k7.webp) - 单击右下角的【一键校验】 - 校验成功后，单击下方的【创建】，单击【创建】即可下发业务集群的创建任务。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qsqi9a.webp) 日志滚动完成，集群部署失败 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/qukh7u.webp) ### 无法删除集群 准备删除该集群，重新进行部署： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/r1najr.webp) 但是这里犯了一个错误，在我更仔细研读使用手册时发现： > 目前仅支持已创建成功集群的删除，创建失败的集群需要排查后，到登录集群所在的各个节点上进行手动删除 按照手册指引继续进行以下操作： #### 在集群的每个节点上下载并自动安装 bkeadm 因为我在业务节点已经切换成 `root` 用户了，所以直接执行以下命令即可 ```bash curl -sfL https://openfuyao.obs.cn-north-4.myhuaweicloud.com/openFuyao/bkeadm/releases/download/v25.09/download.sh | bash ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/r69ucg.webp) #### 执行配置初始化 ```bash bke reset --all --mount ``` #### 删除 BKE 文件 ```bash rm -rf /bke which bke | xargs rm -rf ``` 目前 `bke reset` 操作可能会有容器挂载路径残留，可以执行 `df -h` 命令查看是否存在 overlay 项。若存在，可以尝试再次执行 `bke reset` 操作，或者执行 `reboot` 命令重启机器，删除 overlay项。 执行后发现没有 overlay 项： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/18/r7iapb.webp) # 第一次部署总结 后来发现，如果没有部署成功，那么上面的操作其实都没有办法正确恢复。 接着又踩了一个坑，我重启了引导节点，结果整体服务起不来，所以决定推掉之前的部署，重新开始。 操作方法：在引导节点执行以下命令： ```bash bke reset --all --mount ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/19/dues9x.webp) # 重新开始部署 重复上面的安装步骤发现： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/19/f965sj.webp) 依然没起来。发现还是 `sudo` 的问题 ## 进入 root 用户环境再次进行以上操作 ```bash sudo su - ``` ## 引导节点安装成功 ## 尝试在引导节点连接业务节点，成功 ## 打开管理面继续配置集群 # 新增集群 重复上面新增集群的操作，等待 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/19/idls49.webp) ## 新增集群成功 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/19/ipemtl.webp) 可以登录业务集群的openFuyao管理面进行集群管理相关的操作。 浏览器中输入“https://业务集群IP地址:31616”。 **说明：** > - 首次登录时，用户名为 admin，密码为 test@1234。首次登录后需要修改用户名和密码。 > - 业务集群的 openFuyao 管理面使用 https 协议，web 服务端口默认为 31616。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/11/19/iq4r43.webp) # References - claude sonnet 4.5 - [openFuyao Cluster-API 安装指导：在线安装引导集群](https://docs.openfuyao.cn/docs/Cluster_API_Installation/Online_Bootstrap_Cluster_Installation) - [openFuyao Cluster-API 安装指导：管理面安装业务集群](https://docs.openfuyao.cn/docs/Cluster_API_Installation/Service_Cluster_Installation_on_the_Management_Plane)
最近准备调研一个 Kubernetes 管理平台，所以找 claude 帮我整理了一下架构。以下为 Claude 返回的内容，因为不想一直停留在 Cluade 的页面，所以以下内容仅供我本人参考。 --- 根据你提供的侧边栏结构，我给你一个系统化的调研方案： ## 一、建立知识体系框架（先理解 K8s 本身） 在深入平台前，需要理解 Kubernetes 的核心概念： ``` 资源层次关系： 集群 → 命名空间 → 工作负载/服务/配置 → Pod/容器 ``` ## 二、模块分层梳理法 ### 1️⃣ **第一层：资源管理（核心）** 这是平台的主体功能，按照以下顺序理解： ``` 工作负载（计算资源） ├─ Pod（最小单元） ├─ Deployment（无状态应用）⭐ ├─ StatefulSet（有状态应用）⭐ ├─ DaemonSet（节点守护进程） ├─ Job/CronJob（任务调度） 网络（服务访问） ├─ Service（服务发现）⭐ └─ Ingress（外部访问入口）⭐ 存储（数据持久化） ├─ PV（持久卷） ├─ PVC（持久卷声明）⭐ └─ StorageClass（存储类） 配置与密钥（配置管理） ├─ ConfigMap（配置文件）⭐ └─ Secret（敏感信息）⭐ 命名空间（资源隔离）⭐ ├─ Namespace ├─ LimitRange（资源限制） └─ ResourceQuota（配额管理） ``` ### 2️⃣ **第二层：应用市场/管理** - **应用市场**：应用模板/Helm Chart 管理 - **应用管理**：应用生命周期管理 - **扩展组件管理**：插件/附加组件 ### 3️⃣ **第三层：运维功能** - **监控中心**：指标监控、自定义面板 - **告警**：告警规则配置 - **事件**：集群事件日志 - **算力优化中心**（NUMA亲和调度等） ## 三、核心关联关系图 ![mermaid-diagram-2025-11-13-102843](https://cdn-imagehost.xyzttt.xyz/image/2025/11/13/h0ywwj.webp) ## 四、快速上手实操路径 ### 🎯 第一周：核心功能实操 1. **创建命名空间** → 理解资源隔离 2. **部署 Deployment** → 理解无状态应用 3. **创建 Service** → 理解服务暴露 4. **配置 Ingress** → 理解外部访问 5. **使用 ConfigMap** → 理解配置分离 ### 🎯 第二周：进阶功能 6. **部署 StatefulSet + PVC** → 理解有状态应用 7. **配置资源配额** → 理解资源管理 8. **查看监控和日志** → 理解可观测性 9. **尝试应用市场** → 理解快速部署 ### 🎯 第三周：联动关系 10. 追踪一个完整应用的部署链路 11. 修改配置观察联动效果 12. 模拟故障观察自愈机制 ## 五、整理平台架构的具体方法 ### 📋 方法1：功能清单法 创建表格记录： | 模块 | 子功能 | 对应K8s资源 | 关联模块 | 核心程度 | |------|--------|-------------|----------|----------| | 工作负载 | Deployment | Deployment | Service, ConfigMap, PVC | ⭐⭐⭐ | | 网络 | Service | Service | Pod, Ingress | ⭐⭐⭐ | ### 📋 方法2：流程图法 针对核心场景绘制流程： - **应用部署流程** - **服务访问流程** - **存储挂载流程** - **配置更新流程** ### 📋 方法3：思维导图法 以"资源类型"为中心节点，扩展： - 创建方式 - 配置项 - 关联资源 - 常见问题 ### 📋 方法4：对比学习法 | 功能 | Kuboard实现 | 原生kubectl命令 | Dashboard实现 | |------|------------|----------------|--------------| | 创建Deployment | 表单化 | `kubectl create` | 可视化 | ## 六、核心联动关系重点 ### 🔗 强关联（必须配合使用） 1. **Deployment → Pod** （控制关系） 2. **Service → Pod** （通过 Label Selector） 3. **Ingress → Service** （路由规则） 4. **Pod → ConfigMap/Secret** （配置注入） 5. **StatefulSet → PVC → PV** （存储绑定） ### 🔗 弱关联（可选配合） 1. **Namespace → ResourceQuota** （配额限制） 2. **Pod → Node** （通过调度策略） 3. **Deployment → HPA** （自动扩缩容） ## 七、建议的文档整理结构 ``` 项目文档/ ├── 01-平台概览.md ├── 02-核心概念.md ├── 03-资源管理/ │ ├── 工作负载.md │ ├── 网络.md │ ├── 存储.md │ └── 配置管理.md ├── 04-关联关系图.drawio ├── 05-实操记录/ │ ├── 部署应用.md │ └── 问题记录.md └── 06-最佳实践.md ``` ## 八、快速验证理解的方法 ✅ **自测题**： 1. 删除 Deployment 会发生什么？Pod 会被删除吗？ 2. Service 如何找到对应的 Pod？ 3. ConfigMap 更新后，Pod 会自动重启吗？ 4. StatefulSet 和 Deployment 的本质区别是什么？ --- **推荐起步顺序**： 1. 先用 3 天熟悉 **Deployment + Service + ConfigMap** 这条主线 2. 再花 2 天理解 **Namespace + ResourceQuota** 的隔离机制 3. 最后探索监控、存储等周边功能 # References - claude sonnet 4.5
同一个 Pod 中的多个容器能够共享 Pod 级别的存储卷 Volume。Volume 可以被定义为各种类型，多个容器各自进行挂载操作，将一个 Volume 挂载为容器内部需要的目录 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/14/nqed5a.webp) # 多容器协作实践解析 Pod 内包含两个容器：tomcat 和 busybox，在 Pod 级别设置 Volume"apps-logs"，用于 tomcat 向其中写日志文件，busybox 读日志文件 ```yaml apiVersion: v1 kind: Pod metadata: name: volume-pod spec: containers: - name: tomcat # 第一个容器（主容器）tomcat image: tomcat # 使用 tomcat 镜像 ports: - containerPort: 8080 # 容器内部端口 volumeMounts: - name: app-logs # 挂载的卷名称 mountPath: /usr/local/tomcat/logs # 挂载到容器内的路径（tomecat 的日志目录 - name: busybox # 第二个容器（sidecar 容器）busybox image: busybox # 使用 busybox 镜像 command: ["sh", "-c", "tail -f /logs/catalina*.log"] # 持续监控日志文件 volumeMounts: - name: app-logs # 挂载同名卷 mountPath: /logs # 挂载到容器内的路径 volumes: - name: app-logs # 卷名称 emptyDir: {} # 使用 emptyDir 类型的卷 ``` ## 配置的主要功能和特点 ### 多容器协作 - 一个 Pod 中运行两个容器：tomcat 和 busybox - 两个容器通过共享卷进行通信 ### 日志收集方案 - tomcat 容器产生日志 - busybox 容器实时读取这些日志 ### 数据共享 - 使用 `emptyDir` 卷类型 - 这是一个临时存储，如果 Pod 删除 数据会丢失 - 两个容器可以同时访问这个共享存储 ### 实际应用场景 - 典型的 日志收集模式 - 用于监控、调试或日志转发 ## 角色定位（读写关系如何确定的） 发出一个疑问，为什么是 tomcat 写入，busybox 读取，为什么不能是 busybox 写入，tomcat 读取。 是因为 tomcat 是一个 Web 应用服务器，在运行的过程中自然会产生日志文件（如 catalina.ouot、localhost_access_log 等），这是 tomcat 的固有功能 busybox 在这个例子中，使用 `tail -f` 命令，这是一个读取命令，用于实时查看文件内容。 实际上从技术上讲确实可以反过来，但这不符合容器的设计目的： - tomcat 容器的主要职责是运行 Web 应用并自动生成日志 - busybox 容器在这里扮演一个工具容器的角色，用于监控日志 这种设计遵循 “单一职责原则”： - tomcat 容器专注于运行 web 应用 - busybox 容器专注于日志监控 - 每个容器都在做最适合他的事情
# Pod 定义 YAML 格式的 Pod 定义文件完整内容如下： ```yaml apiVersion: v1 # Pod API 版本，v1 是稳定版，String，必选 kind: Pod # 资源类型为 Pod，String，必选 metadata: # 元数据定义，Object，必选 name: string # Pod 的名称，必选，命名规范需要符合 RFC 1035 规范，metadata.name namespace: string # Pod 所属的命名空间，默认值为 default，必选，metadata.namespace labels: # 标签定义 key: value # 标签是 key-value 格式 annotations: # 注解定义 key: value # 注解是 key-value 格式 spec: # Pod 规格定义，object，必选 containers: # 容器定义，是一个数组，必选，spec.containers[] - name: string # 容器名称，必选，spec.containers[].name image: string # 容器镜像，必选，spec.containers[].image imagePullPolicy: Always # 镜像拉取策略：Always、Never、IfNotPresent # Always：每次都尝试重新拉取镜像 # IfNotPresent：如果本地有该镜像，则使用本地的镜像，本地不存在时拉取镜像 # Never：仅使用本地镜像 # 如果包含以下设置，系统将默认设置 imagePullPolicy=Always： # 不设置 imagePullPolicy，也未指定镜像的 tag # 不设置 imagePullPolicy，镜像 tag 为 latest # 启用名为 AlwaysPullImages 的准入控制器（Admission Controller） command: ["string"] # 容器启动命令，如果不指定则使用镜像打包时使用的启动命令 args: ["string"] # 容器的启动命令参数列表 workdingDir: string # 容器的工作目录 volumeMounts: # 卷挂载定义，挂载到容器内部的存储卷配置 - name: string # 挂载卷名称，引用 Pod 定义的共享存储卷的名称，需要使用 volumes[]部分定义的共享存储卷名称 mountPath: string # 挂载路径，存储卷在容器内 Mount 的绝对路径，应少于 512 个字符 readOnly: false # 是否为只读模式，默认为读写 ports: # 端口定义，容器需要暴露的端口号列表 - name: string # 端口名称 containerPort: 80 # 容器需要监听的端口号 hostPort: 80 # 主机端口，容器所在主机需要监听的端口号，默认与 containerPort 想通，设置 hostPath 时，同一台宿主机将无法启动该容器的第 2 份副本 protocol: TCP # 端口协议，TCP/UDP/SCTP，默认值为 TCP env: # 环境变量定义 - name: string # 环境变量名 value: string # 环境变量值 resources: # 资源限制和资源请求的设置 limits: # 资源限制的设置（资源上限） cpu: "1" # CPU 限制，单位为 core 数，将用于 docker run --cpu-shares 参数 memory: "256Mi" # 内存限制，单位可以为 MiB、GiB 等，将用于 docker run -- memory 参数 requeests: # 资源请求 cpu: "500m" # CPU 请求，单位为 core 数，容器启动的初始可用数量 memory: "128Mi" # 内存请求，单位可以为 MiB、GiB 等，容器启动的初始可用数量 livenessProbe: # 存活探针 exec: # 执行命令检查，对 Pod 内各容器健康检查的设置，exec 方式 command: ["string"] # exec 方式需要指定的命令或者脚本 httpGet: # HTTP GET 检查，object， path: string port: 80 host: string scheme: HTTP # HTTP/HTTPS httpHeaders: # HTTP 同 - name: string value: string topSocket: # TCP 端口检查，对 Pod 内各容器健康检查的设置，tcpSocket 方式 port: 80 initialDelaySecond: 0 # 初始延迟，容器启动完成后首次探测的时间，单位 s timeoutSeconds: 1 # 超时时间，对容器健康检查的探测等待响应的超时时间设置，单位 s，默认值为 1。若超过该超时时间设置，则将认为该容器不健康，会重启该容器 periodSeconds: 10 # 检查间隔，对容器健康检查的定期探测时间设置，单位 s，默认 10s 探测一次 successThreshold: 1 # 成功阈值 failureThreshold: 3 # 失败阈值 securityContext: # 安全上下文 privileged: false # 是否特权容器 restartPolicy: Always # 重启策略，Always、OnFailure、Never，默认值为 Always # Always：Pod 一旦终止运行，则无论容器是如何终止的，kubelet 都将重启它 # OnFilure：只有 Pod 以非零退户码终止时，kubelet 才会重启该容器。如果容器正常结束（退出码为 0），则 kubelet 将不会重启它 # Never：Pod 终止后，kubelet 将退出码报告给 Master，不会再重启该 Pod nodeSelector: # 节点选择器，设置 Node 的 Label，以 key:value 的格式，Pod 将被调度到具有这些 Label 的 Node 上 key: value imagePullSecrets: # 镜像拉取密钥 - name: string # 密钥名称 hostNetwork: false # 是否使用主机网络模式，默认值为 false。 # 设置为 true 表示容器使用宿主机网络，不再使用 Docker 网桥，该 Pod 将无法再同一台宿主机上启动第 2 个副本 volumes: # 卷定义，该 Pod 上定义的共享存储卷列表 - name: string # 共享存储卷名称，容器定义部分的 containers[].volumeMounts[].name 将引用该共享存储卷的名称 emptyDir: {} # 临时目录卷，与 Pod 同生命周期的一个临时目录，值为一个空对象 hostPath: # 主机路径卷，挂载 Pod 所在宿主机的目录 path: string # 主机路径，将被用于容器中 mount 的目录 secret: # Secret 存储卷，挂载集群预定义的 secret 对象到容器内部 secretName: string # Secret 名称 items: # Secret 条目 - key: string # 键名 path: string # 路径 configMap: # ConfigMap 的存储卷，挂载集群预定义的 configMap 对象到容器内部 name: string # ConfigMap 名 items: # ConfigMap 条目 - key: string # 键名 path: string # 路径 ``` # 基本用法 Supervisor 辅助工具进行前台运行的功能，提供一种可以同时启动多个后台应用，并保持Supervisor 自身在前台执行的机制 Pod 可以由 1 个或多个容器组合而成，看之前的例子 Kubernetes 术语 - Label、Replication Controller、Deployment 中可以知道 这个 frontend Pod 在成功启动后，将启动 1 个 Docker 容器 另一个场景：当 frontend 和 redis 两个容器应用为紧耦合的关系，并组合成一个整体对外提供服务时，应将这两个容器打包为一个 Pod： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/12/r6iahb.webp) 配置文件 `frontend-localredis-pod.yaml` 内容： ```yaml apiVersion: v1 kind: Pod metadata: name: redis-php labels: name: redis-php spec: containers: - name: frontend image: kubeguide/guestbook-php-frontend:localredis # 加前缀 dockerio.vkus1.skybyte.me/kubeguide/ ports: - containerPort: 80 - name: redis image: kubeguide/redis-master # 在实际的实践过程中，kubeguide/redis-master 镜像获取失败，换成了 library/redis:latest ports: - containerPort: 6379 ``` 属于同一个 Pod 的多个容器应用之间相互访问时，仅需要通过 localhost 就可以通信，这样这一组容器被 “绑定” 在了一个环境中 在 Docker 容器 `kubeguide/guestbook-php-frontend:localredis` 的 PHP 网页中，直接通过 URL 地址 “localhost:6379" 对同属于一个 Pod 的redis-master 进行访问 ```bash kubectl describe pod redis-php ``` # References - claude 3.5 sonnet - DeepSeek-V3 - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）
静态 Pod 由 kubelet 管理的仅存在于特定 Node 上的 Pod。他们不能通过 API Server 进行管理，无法与 RC、Deployment 或者 DaemonSet 进行关联，并且 kubelet 无法对他们进行健康检查。 静态 Pod 是 由 kubectl 创建，并总在 kubelet 所在的 Node 上运行。 # 创建静态 Pod - 配置文件方式 设置 kubelet 的启动参数 `--config`，指定 kubelet 需要监控的配置文件所在的目录，kubelet 会定期扫描该目录，并根据该目录下的 `.yaml` 或 `.json` 文件进行创建操作。 假设配置目录为 `/etc/kubelet.d`，配置启动参数为 `--config=/etc/kubelet.d/` ，然后重启 kubelet 服务。 `--config=/etc/kubelet.d/` 适用于指定 kubelet 配置文件的参数，不是静态 Pod 的配置文件内容。kubelet 会从该目录中读取配置文件来启动和管理 Pod，这个参数通常用于配置 kubelet 的行为，而不是直接管理静态 Pod ## 静态 Pod 实践 ### 检查 kubelet 启动参数 确保 kubelet 启动时指定了 `--pod-manifest-path` 参数，该参数指向静态 Pod 的配置文件目录（/etc/kubelet.d/） 查看 kubelet 启动参数： ```bash ps aux | grep kubelet ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/13/nxr5y3.webp) 从上面的参数中可以看到，kubelet 使用了 `--config=/etc/kubernetes/kubelet-confg.yaml` 来指定配置文件，而不是直接通过命令行参数配置静态 Pod 的路径。因为需要在 `kubelet-config.yaml` 文件中配置静态 Pod 的路径。 但是目前不想要更改目录，所以不更改 kubelet-config.yaml 的配置内容。 ### 创建静态 Pod 的配置文件 在 `/etc/kubernetes/manifests/` 目录中创建静态 Pod 的 YAML 文件。 ```bash sudo vim /etc/kubernetes/manifests/static-web.yaml ``` 配置文件内容： ```yaml apiVersion: v1 kind: Pod metadata: name: static-web labels: name: static-web spec: containers: - name: static-web image: dockerio.vkus1.skybyte.me/library/nginx ports: - name: web containerPort: 80 ``` 执行 `kubectl get pods` 可以看到 刚刚创建的 Pod static-web。 ### 静态 Pod 命名规则 `-` 静态 Pod 无法通过 API Server 直接管理，所以在 Master 上尝试删除这个 Pod 时，会使其变成 `Pending` 且不会被删除。 删除该 Pod 的操作只能到期所在的 Node 上 将其定义文件 static-web.yaml 从 目录中删除 # 创建静态 Pod - HTTP 方式 通过设置 kubelet 的启动参数 `--manifest-url`，kubelet 将会定期从该 URL 地址下载 Pod 的定义文件，并以 `.yaml` 或 `.json` 文件的格式进行解析，然后创建 Pod。 其实现方式与配置文件方式是一致的 # References - claude 3.5 sonnet - DeepSeek-V3 - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）
# kubectl 用法概述 kubectl 命令行的语法如下： ```bash kubectl [command] [TYPE] [NAME] [flags] ``` - `command`：子命令，用于操作 Kubernetes 集群资源对象的命令，如 create、delete、describe、get、apply 等 - `TYPE`：资源对象的类型，区分大小写，能以单数。复数或简写形式表示，如，以下三种 TYPE 是等价的： ```bash kubectl get pod pod1 kubectl get pods pod1 kubectl get po pod1 ``` - `NAME`：资源对象的名称，区分大小写，如果不指定名称，系统则将返回属于 TYPE 的全部对象的列表 - `flags`：kubectl 子命令的可选参数，如使用 `-s` 指定 API Server 的 URL 地址而不用默认值 # kubectl 可操作的资源对象类型 及其缩写 | 资源对象类型 | 缩写 | 资源对象类型 | 缩写 | | ------------------------ | ------ | ---------------------- | ----- | | cluster | | statefulsets | | | componentstatuses | cs | persistentvolumeclaims | pvc | | configmaps | cm | persistentvolumes | pv | | daemonsets | ds | pods | po | | deployments | deploy | podsecuritypolicies | psp | | endpoints | ep | podtemplates | | | events | eb | replicasets | rs | | horizontalpodautoscalers | hpa | replicationcontrollers | rc | | ingresses | ing | resourcequotas | quota | | Jobs | | cronjob | | | limitranges | limits | secrets | | | nodes | no | serviceaccounts | | | namespaces | ns | services | svc | | networkpolicies | | storageclasses | sc | | nodes | no | thirdpartyresources | | 在一个命令行中也可以同时对多个资源对象进行操作，以多个 TYPE 和 NAME 的组合表示，eg. 获取多个 Pod 的信息： ```bash kubectl get pods pod1 pod2 ``` 获取多种对象的信息： ```bash kubectl get pod/pod1 rc/rc1 ``` 同时应用多个 YAML 文件，以多个 `-f file` 参数表示： ```bash kubectl get pod -f pod1.yaml -f pod2.yaml kubectl create -f pod1.yaml -f rcl.yaml -f servicel.yaml ``` # kubectl 子命令详解 常用的 kubectl 子命令： | 子命令 | 语法 | 说明 | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | | apply | kubectl apply -f FILENAME [flags] | 从配置文件或 stdin 中对资源对象进行配置更新 | | get | kubectl get [资源类型] [NAME] [flags] | 显示一个或多个资源的信息 | | describe | kubectl describe [资源类型] [NAME] [flags] | 显示特定资源或资源组的详细信息 | | delete | kubectl delete [资源类型] [NAME] [flags] | 从文件、stdin 或指定标签选择器、名称删除资源 | | create | kubectl create -f FILENAME [flags] | 从文件或 stdin 创建一个或多个资源 | | edit | kubectl edit [资源类型] [NAME] [flags] | 编辑服务器上的资源 | | exec | kubectl exec POD [-c CONTAINER] [-i] [-t] [flags] -- COMMAND [args...] | 在容器中执行命令 | | logs | kubectl logs POD [-c CONTAINER] [flags] | 打印 Pod 中容器的日志 | | scale | kubectl scale [--resource-version=版本] [--current-replicas=数量] --replicas=数量 [资源类型] [NAME] [flags] | 扩缩容 Pod、ReplicaSet、Deployment、ReplicationController 或 Job | | port-forward | kubectl port-forward POD [LOCAL_PORT:]REMOTE_PORT [...[LOCAL_PORT_N:]REMOTE_PORT_N] [flags] | 将一个或多个本地端口转发到 Pod | | rollout | kubectl rollout SUBCOMMAND [flags] | 管理资源的发布（如 deployment 的升级回滚） | | label | kubectl label [--overwrite] (-f FILENAME \| 资源类型 NAME) KEY_1=VAL_1 ... KEY_N=VAL_N [flags] | 更新资源上的标签 | | config | kubectl config SUBCOMMAND [flags] | 修改 kubeconfig 文件（用于访问 API，配置认证信息等） | | expose | kubectl expose (-f FILENAME \| 资源类型 NAME) [--port=端口] [--protocol=TCP\|UDP] [--target-port=数字\|名称] [--name=名称] [--external-ip=外部IP地址] [flags] | 将资源暴露为新的 Kubernetes Service | | set | kubectl set SUBCOMMAND [flags] | 在资源对象上设置特定功能（如镜像、环境变量等） | | taint | kubectl taint NODE NAME KEY_1=VAL_1:TAINT_EFFECT_1 ... KEY_N=VAL_N:TAINT_EFFECT_N [flags] | 更新一个或多个节点上的污点 | | top | kubectl top [flags] [OPTIONS] | 显示资源（CPU/内存/存储）使用情况 | | cordon | kubectl cordon NODE [flags] | 标记节点为不可调度 | | uncordon | kubectl uncordon NODE [flags] | 标记节点为可调度 | | drain | kubectl drain NODE [flags] | 排空节点以准备维护 | | auth | kubectl auth [flags] [OPTIONS] | 检查授权 | | cluster-info | kubectl cluster-info [flags] | 显示集群信息 | | api-resources | kubectl api-resources [flags] | 打印服务器上支持的 API 资源 | | api-versions | kubectl api-versions [flags] | 打印服务器上支持的 API 版本 | | version | kubectl version [flags] | 显示客户端和服务器的版本信息 | 常用的 flags（参数）： |参数|说明| |---|---| |-n, --namespace|指定命名空间| |-o, --output|输出格式（json\|yaml\|wide\|name）| |--all-namespaces|列出所有命名空间的资源| |-l, --selector|标签选择器| |-f, --filename|指定包含配置的文件名、目录或 URL| |--force|强制执行操作| |-h, --help|获取帮助信息| |-v, --v|日志级别| |--dry-run|仅打印将要执行的操作，而不实际执行| |-w, --watch|监视资源变化| 这些命令和参数涵盖了 kubectl 的主要功能，可以满足日常 Kubernetes 集群管理和操作的需求。每个命令都支持 `--help` 参数来获取更详细的使用说明。 # kubectl 参数列表 kubectl 命令行的公共启动参数如下： | 参数名 | 取值示例 | 说明 | |--------|----------|------| | --kubeconfig | ~/.kube/config | 指定 kubeconfig 配置文件的路径 | | --context | my-context | 指定要使用的 kubeconfig 上下文名称 | | --cluster | production-cluster | 指定要使用的 kubeconfig 集群名称 | | --namespace, -n | default | 指定命名空间，默认为 "default" | | --user | admin-user | 指定要使用的 kubeconfig 用户名称 | | --server, -s | https://10.0.0.1:6443 | 指定 Kubernetes API 服务器的地址和端口 | | --certificate-authority | /path/to/ca.crt | 指定用于验证 API 服务器的 CA 证书路径 | | --client-certificate | /path/to/client.crt | 指定用于 TLS 的客户端证书文件路径 | | --client-key | /path/to/client.key | 指定用于 TLS 的客户端密钥文件路径 | | --token | eyJhbGc...token | 指定用于认证的 Bearer 令牌 | | --username | admin | 基本认证的用户名 | | --password | secret | 基本认证的密码 | | --insecure-skip-tls-verify | true | 是否跳过 TLS 证书验证（不建议在生产环境使用） | | --request-timeout | 30s | API 服务器请求超时时间 | | --v, -v | 0-9 | 日志级别，数字越大输出信息越详细（0-9） | | --alsologtostderr | true | 同时将日志输出到标准错误和文件 | | --log-dir | /path/to/logs | 指定日志文件目录 | | --log-file | kubectl.log | 指定日志文件名称 | | --log-file-max-size | 1800 | 日志文件的最大大小（MB） | | --logtostderr | true | 仅将日志输出到标准错误 | | --match-server-version | true | 要求服务器版本与客户端版本匹配 | | --profile | profile-name | 要使用的性能分析名称 | | --profile-output | profile.pprof | 性能分析输出文件路径 | | --cache-dir | ~/.kube/cache | HTTP 缓存目录 | | --api-version | v1 | 要使用的 API 版本 | | --as | other-user | 以其他用户身份执行操作 | | --as-group | system:masters | 以其他用户组身份执行操作 | | --tls-server-name | kubernetes | 用于验证服务器证书的主机名 | | --proxy-url | http://proxy.example.com:8080 | 要使用的代理服务器的 URL | 一些使用示例： ```bash # 使用特定的 kubeconfig 文件 kubectl --kubeconfig=/path/to/config get pods # 使用特定的上下文 kubectl --context=prod-cluster get nodes # 指定命名空间和日志级别 kubectl -n kube-system --v=6 get pods # 使用特定的认证信息 kubectl --server=https://k8s.example.com:6443 \ --client-certificate=/path/to/cert.crt \ --client-key=/path/to/key.key \ get pods # 以其他用户身份执行操作 kubectl --as=system:serviceaccount:default:myapp get pods ``` 注意事项： 1. 这些参数可以在命令行中指定，也可以在 kubeconfig 文件中配置 2. 某些参数（如认证相关的）通常建议配置在 kubeconfig 文件中，而不是在命令行中暴露 3. 生产环境中要谨慎使用 --insecure-skip-tls-verify 4. 日志级别（-v）通常用于调试，生产环境建议使用默认值 # kubectl 输出格式 kubectl 命令可以用多种格式对结果进行显示，输出的格式通过 `-o` 参数指定： ```bash kubectl [command] [TYPE] [NAME] -o= ``` 根据不同子命令的输出结果，可选的输出格式如下： | 输出格式 | 语法示例 | 说明 | | ------------------- | ---------------------------------------------------------- | ------------------------ | | json | -o json | 以 JSON 格式输出 | | yaml | -o yaml | 以 YAML 格式输出 | | wide | -o wide | 以纯文本格式输出附加信息 | | name | -o name | 仅输出资源名称 | | custom-columns | -o custom-columns=NAME:.metadata.name,STATUS:.status.phase | 自定义列输出，可以使用 JSONPath 表达式 | | custom-columns-file | -o custom-columns-file=columns.txt | 从文件中读取自定义列格式 | | jsonpath | -o jsonpath='{.items[*].metadata.name}' | 使用 JSONPath 表达式提取特定字段 | | jsonpath-file | -o jsonpath-file=template.json | 从文件中读取 JSONPath 模板 | | template | -o template="{{range .items}}{{.metadata.name}}{{end}}" | 使用 Go 模板进行自定义输出 | | template-file | -o template-file=template.txt | 从文件中读取 Go 模板 | **custom-columns 常用示例** ```bash # Pod 名称和状态 kubectl get pods -o custom-columns=POD:metadata.name,STATUS:status.phase # 显示节点名称和 CPU 容量 kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.capacity.cpu # 显示 deployment 名称、副本数和可用副本数 kubectl get deployments -o custom-columns=NAME:.metadata.name,REPLICAS:.spec.replicas,AVAILABLE:.status.availableReplicas ``` **jsonpath 常用示例** ```bash # 获取所有 Pod 的名称 kubectl get pods -o jsonpath='{.items[*].metadata.name}' # 获取所有节点的 IP kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' # 获取所有容器的镜像 kubectl get pods -o jsonpath='{.items[*].spec.containers[*].image}' ``` **template 常用示例** ```bash # 自定义格式输出 Pod 信息 kubectl get pods -o template --template='{{range .items}}{{.metadata.name}} {{.status.phase}}{{"\n"}}{{end}}' # 输出带表头的列表 kubectl get pods -o template --template='NAME STATUS\n{{range .items}}{{.metadata.name}} {{.status.phase}}{{"\n"}}{{end}}' ``` 常用的输出格式实例： 显示 Pod 的更多信息： ```bash kubectl get pod -o wide ``` 以 YAML 格式显示 Pod 的详细信息： ```bash kubectl get pod -o yaml ``` 以自定义列明显示 Pod 的信息： ```bash kubectl get pod -o=custom-columns=NAME:.metadata.name,RSRC:.metadata.resourceVersion ``` 基于文件的自定义列名输出： ```bash kubectl get pods -o=custom-columns-file=template.txt ``` 将输出结果按某个字段排序，通过 `--sory-by` 参数以 jsonpath 表达式进行指定： ```bash kubectl [command] [TYPE] [NAME] --sort-by= ``` # kubectl 操作实例 ## 执行容器的命令 执行 Pod 的 date 命令，默认使用 Pod 中的 第一个容器 执行： ```bash kubectl exec date ``` 指定 Pod 中的某个容器执行 date 命令： ```bash kubectl exec -c date ``` 通过 bash 获得 Pod 中 某个容器的 TTY，相当于登录容器： ```bash kubectl exec -ti -c /bin/bash ``` ## 查看容器的日志 查看容器输出到 sudout 的日志： ```bash kubectl logs ``` 跟踪查看容器的日志，相当于 `tail -f` 命令的结果： ```bash kubectl logs -f -c ``` ## 在线编辑运行中的资源对象 可以使用 kubectl edit 命令编辑运行中的资源对象，例如使用下面的命令编辑运行中的一个 Deployment： ```bash kubectl edit deploy nginx ``` 在命令执行之后，会通过 YAML 格式展示该对象的定义和状态，用户可以对代码进行编辑和保存，从而完成对在线资源的直接修改。 ## 将 Pod 的开放端口映射到本地 将集群上 Pod 的 80 端口映射到本地的 8888 端口，在浏览器 http://127.0.0.1:8888 中就能访问到容器提供的服务 ```bash kubectl port-forward --address 0.0.0.0 | pod/podname 8888:80 ``` ## 在 Pod 和本地之间复制文件 把 Pod 上的 `/etc/fstab` 复制到本地的 `/tmp` 目录： ```bash kubectl cp podname:/etc/fstab /tmp ``` ## 资源对象的标签设置 为 default namespace 设置 testing=true 标签： ```bash kubectl label namespaces default testing=true ``` ## 检查可用的 API 资源类型列表 该命令经常用于检查特定类型的资源是否已经定义，列出所有资源对象类型 ```bash kubectl api-resources ``` # References - claude 3.5 sonnet - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）
# Job 批处理任务通常并行（或者串行）启动多个计算机进程去处理一批工作项（work item），在处理完成后，整个批处理任务结束。 Kubernetes 支持批处理类型的应用，可以通过 Kubernetes Job 这种新的资源对象定义并启动一个 批处理任务 Job。 与 RC、Deployment、RS、DaemonSet 类似，Job 也控制一组 Pod 容器。Job 也是一种特殊的 Pod 副本自动控制器，同时 Job 控制 Pod 副本与 RC 等控制器工作机制有以下差别 ## Job 控制的 Pod 副本是短暂运行的 可以将 Job 视为一组 Docker 容器，其中的每个 Docker 容器都仅仅运行一次，当 Job 控制的所有 Pod 副本都运行结束时，对应的Job 也就结束了。 Job 生成的 Pod 副本是不能自动重启的，对应 Pod 副本的 RestartPoliy 都被设置为 `Never` 当对应 Pod 副本都执行完时，相应的 Job 也就完成了控制使命，即 Job 生成的 Pod 在 Kubernetes 中是短暂存在的。 ### 定时任务 CronJob 在 1.5 版本之后，提供了类似 crontab 的定时任务，CronJob，解决了某些批处理任务需要定时反复执行的问题。 ## Job 控制的 Pod 副本的工作模式能够多实例并行计算 如 TensorFlow 框架，可以将一个机器学习的计算任务分布到 10 台机器上，在每台机器上都运行一个 worker 执行计算任务。 ## Job 的常见使用场景 - 数据处理 / 迁移 - 批量计算 - CI/CD 流水线任务 - 一次性初始化工作 - 数据库架构更新 # Volume 存储卷 Volume 是 Pod 中能够被多个容器访问的共享目录。 Kubernetes 中的 Volume 被定义在 Pod 上，然后被一个 Pod 里的多个容器挂载到具体的文件目录下； Kubernetes 中的 Volume 与 Pod 的生命周期相同，但与容器的生命周期不相关，当容器终止或者重启时，Volume 中的数据也不会丢失； Kubernetes 支持多种类型的 Volume，如 GlusterFS、Ceph 等分布式文件系统。 ## Volume 的使用 现在 Pod 上生命一个 Volume，然后再容器里引用该 Volume 并挂载（Mount）到容器里的某个目录上。 ## Volume 实践 给之前的 Tomcat Pod 增加一个名为 datavol 的Volume，并且挂载到容器的 `/mydata-data/` 目录上。 可以追溯到之前创建 tomcat 的 deployment 配置文件是 tomcat-deployment.yaml ，使用 vim 进入配置文件编辑如下内容： ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: frontend spec: replicas: 1 selector: matchLabels: tier: frontend template: metadata: labels: app: app-demo tier: frontend spec: containers: - name: tomcat-demo image: kubeguide/tomcat-app:v1 imagePullPolicy: IfNotPresent ports: - containerPort: 8080 resources: requests: cpu: "200m" memory: "256Mi" limits: cpu: "500m" memory: "512Mi" volumeMounts: # volumeMounts 在 container 层级下 - name: datavol mountPath: /mydata-data volumes: # volumes 在 spec 层级下，跟 containers 同级 - name: datavol emptyDir: {} ``` 更新deployment ```bash kubectl apply -f tomcat-deployment.yaml ``` 配置文件解析： - 在容器配置中添加了 `volumeMounts` 部分，指定挂载点 - 在 Pod 规格中添加了 `volumes` 部分，定义了一个 `emptyDir` 类型的卷。 - 这是一个临时存储，Pod 删除后数据会丢失，如果需要持久化存储，可以考虑使用 PersistentVolumeClaim（PVC） 和 PersistentVolume（PV） ## Volume 类型 ### emptyDir 一个 `emptyDir Volume` 是在 Pod 分配到 Node 时创建的，初始内容为空，并且无需指定宿主机上对应的目录文件，因为这个是 Kubernetes 自动分配的一个目录，当 Pod 从 Node 上移除时，emptyDir 中的数据也会被永久删除。用途如下： - 临时空间，用于某些应用程序运行时所需要的临时目录，且无须永久保留 - 长时间任务的中间过程 CheckPoint 的临时保存目录 - 一个容器需要从另一个容器中获取数据的目录（多容器共享目录） 用户无法控制 emptyDir 使用的介质种类。如果 kubelet 的配置是使用硬盘，那么所有 emptyDir 都会被创建在该硬盘上。 ### hostPath `hostPath` 为在 Pod 上`挂载宿主机`上的文件或目录，通常用于以下方面： - 容器应用程序生成的日志文件需要永久保存时，可以用宿主机的高速文件系统进行存储 - 需要访问宿主机上 Docker 引擎内部数据结构的容器应用时，可以通过定义 hostPath 为宿主机 `/var/lib/docker` 目录，使容器内部应用可以直接访问 Docker 的文件系统 需要注意的是： - 在不同的 Node 上具有相同配置的 Pod，可能会因为宿主机上的目录和文件不同而导致对 Volume 上目录和文件的访问结果不一致 - 如果使用了资源配额管理，则 Kubernetes 无法将 hostPath 在宿主机上使用的资源纳入管理 可以通过如下方式定义：(使用宿主机的 `/data` 目录定义了一个 hostPath 类型的 Volume ```yaml volumes: - name: "persistent-storage" hostPath: path: "/data" ``` ### gcePersistentDisk 使用 gcePersistentDisk 表示使用谷歌公有云提供的永久磁盘（`Persisten Disk，PD`）存放 Volume 的数据。 PD 上的内容会被永久保存，当 Pod 被删除时，PD 只是被卸载（Unomunt），但不会被删除。 ！！需要先创建一个 PD，才能使用 gcePersistentDisk 限制条件： - Node （运行 kubelet 的节点）需要是 GCE 虚拟机 - 这些虚拟机需要与 PD 存在于相同的 GCE 项目和 Zone 中。 ### awsElasticBlockStore GCE 类似，该类型的 Volume 使用亚马逊公有云提供的 EBS Volume 存储数据，需要先创建一个EBS Volume 才能使用 awsElasticBlockStore。 ### NFS 使用 NFS 网络文件系统提供的共享目录存储数据时，我们需要在系统中部署一个 NFS Server。 ```yaml volumes: - name: nfs nfs: # 改为你的NFS服务器地址 server: nfs-server.localhost path: "/" ``` ### 其他类型 - iSCSI：使用 iSCSI 存储设备上的目录挂载到 Pod 中 - Flocker：使用 Flocker 管理存储卷 - rbd：使用 Ceph 块设备共享存储（Rados Block Device）挂载到 Pod 中 - gitRepo：通过挂载一个空目录，并从 Git 库 clone 一个 git repository 以供 Pod 使用 - secret：一个 Secret Volume 用于为 Pod 提供加密的信息，你可以将定义在 Kubernetes 中的 Secret 直接挂载为文件让 Pod 访问。Secret Volume 是通过 TMFS（内存文件系统）实现的，这种类型的 Volume 总是不会被持久化的 # Persistent Volume Volume 是被定义在 Pod 上的，属于计算资源的一部分。网络存储是相对独立于计算资源而存在的一种实体资源。 在使用虚拟机的情况下，通常会先定义一个网络存储，然后从中划出一个 “网盘” 并挂接到虚拟机上。 Persistent Volume（PV）和与之相关联的 Persistent Volume Claim（PVC） 也起到了类似的作用。 PV 可以被理解成 Kubernetes 集群中的某个网络存储对应的一块存储，与 Volume 类似，但有以下区别： - PV 只能是网络存储，不属于任何 Node。但可以在每个 Node 上访问 - PV 并不是被定义在 Pod 上的，而是独立于 Pod 之外定义的 - PV 目前支持的类型包括：gcePersistentDisk、AWSElasticBlockStore、AzureFile、AzureDisk、FC（Fibre Channel）​、Flocker、NFS、iSCSI、RBD（Rados Block Device）​、CephFS、Cinder、GlusterFS、VsphereVolume、Quobyte Volumes、VMware Photon、Portworx Volumes、ScaleIO Volumes和HostPath（仅供单机测试）​ ## PV 的声明方式 NFS 类型的 PV 的一个 YAML 定义文件，声明了需要 5Gi 的存储空间： ```yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv0003 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce nfs: path: /somepath server: 172.17.0.2 ``` ## PV 的 accessModes 属性 - `ReadWriteOnce`：读写权限，并且只能被单个 Node 挂载 - `ReadOnlyMany`：只读权限，允许被多个 Node 挂载 - `ReadWriteMany`：读写权限，允许被多个 Node 挂载 如果某个 Pod 想申请某种类型的 PV，则首先需要定义一个 PersistentVolumeClaim 对象： ```yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: myclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi ``` 然后在 Pod 的 Volume 定义中引用上述 PVC 即可： ```yaml volumes: - name: mypod persistentVolumeClaim: claimName: myclaim ``` ## PV 的状态 PV 是有状态的对象，状态有以下几种： - Available：空闲状态 - Bound：已经绑定到某个 PVC 上 - Released：对应的 PVC 已经被删除，但资源还没有被集群收回 - Failed：PV 自动回收失败 # Namespace Namespace 在很多情况下用于实现 `多租户` 的资源隔离。 Namespace 通过将集群内部的资源对象 “分配” 到不同的 Namespace 中，形成逻辑上分组的不同项目、小组或用户组，便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。 Kubernetes 集群在启动后会创建一个名为 default 的 Namespace，查看方式： ```bash kubectl get namespaces ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/10/t15kot.webp) 如果不特别指明 Namespace，则用户创建的 Pod、RC、Service 都将被系统创建到这个默认的 名为 `default` 的 Namespace 中。 ## Namespace 定义方式 ```yaml apiVersion: v1 kind: Namespace metadata: name: development ``` 一旦创建了 Namespace，在创建资源对象时就可以指定这个资源对象属于哪个 Namespace。 ## Namespace 实践 创建 namespace ```bash touch busybox-namespace.yaml ``` busybox-namespace.yaml 文件内容： ```yaml apiVersion: v1 kind: Namespace metadata: name: development ``` 创建 busybox Pod 在这个 Namespace 中 ```bash touch busybox-pod.yaml ``` 配置文件内容： ```yaml apiVersion: v1 kind: Pod metadata: name: busybox namespace: development # 指定 namespace spec: containers: - name: busybox image: dockerio.vkus1.skybyte.me/library/busybox command: - sleep - "3600" # 让容器运行 1 小时 ``` 执行创建命令： ```bash kubectl apply -f busybox-development.yaml kubectl apply -f busybox-pod.yaml ``` 如果此时只简单地执行 `kubectl get pods` 是看不到刚刚创建的 pod 的，因为这个命令默认只会显示 `default` namespace 的 pods。这个命令等同于 `kubectl get pods -n default` 如果想要显示指定 namespace 的 pods，则可以执行以下命令： ```bash kubectl get pods --namespace=development # 或 kubectl get pods -n development ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/11/p263rg.webp) 如果执行以下命令，可以查看所有 namespace： ```bash kubectl get pods --all-namespaces # 或 kubectl get pods -A ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/11/p5pr7a.webp) ### 所有查看方式汇总 | 命令 | 显示范围 | 输出示例 | | ----------------------------------- | --------------------- | -------------------------- | | `kubectl get pods` | default namespace | frontend-pod, mysql-pod... | | `kubectl get pods -n development` | development namespace | busybox | | `kubectl get pods --all-namespaces` | 所有 namespaces | 所有 pods | 当给每个租户创建一个 Namespace 来实现多租户的资源隔离时，还能组合 Kubernetes 的资源配额管理，限定不同租户能占用的资源，例如 CPU 使用量、内存使用量等。 # Annotation Annotation：注解，与 Label 类似，也是用 key/value 键值对 的形式定义。 Annotation 是用户任意定义的附加信息，以便于外部工具查找。 通常来说会通过 Annotation 来记录以下信息： - build 信息、release 信息、Docker 镜像信息等，eg. 时间戳、release id 号、PR 号、镜像 Hash 值、Docker Registry 地址等 - 日志库、监控库、分析库等资源库的地址信息 - 程序调试工具信息，eg. 工具名称、版本号等 - 团队的联系信息，eg, 电话号码、负责人名称、网址等 # ConfigMap Docker 通过将 程序、依赖库、数据及配置文件 “打包固化” 到一个不变的镜像文件中的做法，解决了应用的部署的难题，但这同时带来了：配置文件中的参数在运行期如何修改 的问题。 我们不可以在启动 Docker 容器后 再修改容器里的配置文件，然后用新的配置文件重启容器里的用户主进程，所以 Docker 提供了两种方式： - 在运行时通过容器的环境变量来传递参数 - 通过 Docker Volume 将容器外的配置文件映射到容器内 后一种方式更适合，因为大多数应用通常从一个或多个配置文件中读取参数。但使用这种方式的话，我们必须在目标主机上先创建好对应的配置文件，然后才能映射到容器里。 ## Kubernetes 的解决方案 把所有的配置项都当作 `key-value` 字符串，当然 value 可以来自摸个文本文件，比如配置项 `password=123456`、`user=root`、`host=192.168.8.4` 等用于表示连接 FTP 服务器的配置参数。 这些配置项可以作为 Map 表中的一个项，整个 Map 的数据可以被持久化存储在 Kubernetes 的 Etcd 数据库中，然后提供 API 以方便 Kubernetes 相关组件或客户应用 CRUD 操作这些数据，这些专门用来保存配置参数的 Map 就是 Kubernetes ConfigMap 资源对象。 Kubernetes 提供一种内建机制，将存储在 etcd 中的 ConfigMap 通过 Volume 映射的方式变成目标 Pod 内的配置文件，不管目标 Pod 被调度到哪台服务器上，都会完成自动映射。 如果 ConfigMap 中的 key-value 数据被修改，则映射到 Pod 中的 “配置文件” 也会随之自动更新。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/11/po58eu.webp) ConfigMap 特点： - 可动态更新 - 明文存储 - namespace 隔离 - 容量限制 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/11/pqi9e2.webp) 最佳实践： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/11/pro8af.webp) # References - claude 3.5 sonnet - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）
# 概述 Service 服务是 Kubernetes 里的核心资源对象之一，Kubernetes 里的每个 Service 其实就是我们经常提到的 **微服务架构** 中的一个 **微服务**。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/iqwl3j.webp) 如上图所示： - Kubernetes 的 Service 定义了一个服务的访问入口地址 - 前端的应用（Pod）通过这个入口地址访问其背后的一组由 Pod 副本组成的集群实例 - Service 与其后端 Pod 副本集群之间是通过 Label Selector 来实现无缝对接的 - RC 的作用是保证 Service 的服务能力 和 服务质量始终符合预期标准 # 简单来说 Service 是一个联通，一个 Pod 通过 Service 来找到应该对应的另一个 Pod # 网络通信 通过分析、识别并建模系统中的所有服务为微服务 -- Kubernetes Service，系统最终由多个提供不同业务能力而又彼此独立的微服务单元组成，服务之间通过 TCP/IP 进行通信，从而形成了强大而又灵活的弹性网格，拥有强大的分布式能力、弹性伸缩能力、容错能力，程序架构也变得简单和直观 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/j0b5e0.webp) 既然每个 Pod 都会被分一个单独的 IP 地址，而且每个 Pod 都提供了一个独立的 Endpoint（Pod IP + ContainerPort）以被客户端访问，多个 Pod 副本组成了一个集群来提供服务。 ↓客户端访问方式↓ 部署一个负载均衡器（软件或硬件），为这组 Pod 开启一个对外的服务端口如 8000 端口，将这些 Pod 的 Endpoint 列表加入 8000 端口的转发列表，客户端就可以通过负载均衡器的对外 IP 地址 + 服务端口 来访问此服务 客户端的请求最后会被转发到哪个 Pod，由负载均衡器的算法所决定。 运行在每个 Node 上的 `kube-proxy` 进程其实就是一个智能的软件负载均衡器，负责对把 Service 的请求转发到后端的某个 Pod 实例上，并在内部实现服务的负载均衡与会话保持机制。 Service 没有共用一个负载均衡器的 IP 地址，每个 Service 都被分配了一个全局唯一的 `虚拟 IP 地址`，这个虚拟 IP 地址被称为 `Cluster IP`。 每个服务就变成了具备唯一 IP 地址的通信节点，服务调用就变成了最基础的 TCP 网络通信问题。 # 服务发现问题 Pod 的 Endpoint 地址会随着 Pod 的销毁和重新创建而发生改变，因为新的 Pod 的 IP 地址与之前旧的 Pod 的不同。 而 Service 一旦被创建，Kubernetes 就会自动为它分配一个可用的 Cluster IP，而在 Service 的整个生命周期内，他的 Cluster IP 不会改变。 只要用 Service 的 Name 与 Service 的 Cluster IP 地址做一个 DNS 域名映射，即可完美解决问题。 # Service 实践 ## 创建 Service 定义文件 ```bash touch tomcat-service.yaml ``` 配置文件内容如下： ```yaml apiVersion: v1 kind: Service metadata: name: tomcat-service spec: ports: - port: 8080 # 服务端口为 8080 selector: tier: frontend # 拥有 “tier=frontend” 这个 Label 的 所有 Pod 实例都属于他 ``` ## 创建 Service ```bash kubectl create -f tomcat-server.yaml ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/qn5gny.webp) 在之前的实践中的 `tomcat-deployment.yaml` 里定义的 Tomcat 的 Pod 刚好拥有这个标签，所以刚才创建的 tomcat-service 已经对应一个 Pod 实例了。 ## 查看 tomcat-service 的 Endpoint 列表 ```bash kubectl get endpoints ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/qoci27.webp) 其中 10.234.168.24 是与 `tomcat-service` 关联的 Pod 的 IP 地址，端口 8080 是 Container 暴露的端口。 执行上述命令时，显示的是 Service 对应的后端 Pod 的实际 IP 地址和端口。 工作流如下： ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/qqcezr.webp) 需要了解的是： - 这个 IP 不是 Service 的 Cluster IP； - Service 通过 Endpoints 对象跟踪所有匹配的 Pod IP - kube-proxy 使用这些 Endpoints 信息来设置网络规则 - 当有请求访问 Service 时，会被转发到这些 Endpoints 中的某个 Pod IP ## 查看 Service 的 Cluster IP 执行以下命令（输出详细的 YAML 格式）： ```bash kubectl get svc tomcat-service -o yaml ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/qs5rk4.webp) 执行以下命令（简洁）： ```bash kubectl get service tomcat-service ``` ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/07/qsttn3.webp) 在 `spec.ports` 的定义中， - `targetPort` 属性用来确定提供该服务的容器所暴露（EXPOSE）的端口号，即 具体业务进程在容器内的 targetPort 上提供 TCP/IP 接入 - `port` 属性定义了 Service 的虚端口。 # Service 的多端口问题 很多服务都存在多个端口的问题，通常一个端口提供业务服务，另外一个端口提供管理服务，比如 Mycat、Codis 等常见中间件。 Kubernetes Service 支持多个 Endpoint，在存在多个 Endpoint 的情况下，要求每个 Endpoint 都定义一个名称来区分 多端口的 yaml 文件内容： ```yaml apiVersion: v1 kind: Service metadata: name: tomcat-service spec: ports: - port: 8080 name: service-port - port: 8005 name: shutdown-port selector: tier: frontend ``` # Kubernetes 的服务发现机制 任何分布式系统都会涉及 `服务发现` 这个基础问题，大部分分布式系统都通过提供特定的 API 接口来实现服务发现功能，但这样做会导致平台的侵入性比较强，也增加了开发、测试的难度。Kubernetes 采用比较直观朴素的思路来解决这个问题： 每个 Kubernetes 中的 Service 都有`唯一的` `Cluster IP` 及 `唯一的` `名称`，名称是有开发者自己定义的，部署时也不需要改变，所以完全可以被固定在配置中。 ## 采用 Linux 环境变量解决该问题 每个 Service 都生成一些对应的 Linux 环境（ENV），并在每个 Pod 的容器启动时自动注入这些环境变量. ### 环境变量 #### 环境变量基本概念 - 环境变量就是系统级的 “全局变量” - 他们是以 “键值对” 的形式存在，如 `NAME=VALUE` - 所有运行的程序都可以访问这些变量 #### 在 Kubernetes 中的作用 - 配置应用程序 - 传递服务发现信息 - 存储敏感信息（通过 Secrets） - 传递运行时参数 #### 环境变量的优势 - 配置与代码分离 - 便于管理不同环境（开发、测试、生产） - 提高安全性（敏感信息可以通过环境变量注入） - 便于服务发现和配置 ### 查看 tomcat-service 产生的环境变量条 ```bash kubectl get pods NAME READY STATUS RESTARTS AGE frontend-6f78b7f7b8-f7xjz 1/1 Running 0 26h mysql-4jzdh 1/1 Running 0 2d2h mysql-8n5ss 1/1 Running 0 7d23h myweb-4t4rj 1/1 Running 0 7d myweb-mnlxr 1/1 Running 0 7d netchecker-agent-5wkcf 1/1 Running 0 15d netchecker-agent-hostnet-w2bdc 1/1 Running 0 15d netchecker-server-67c989ccbb-2hfdd 2/2 Running 1 (15d ago) 15d ``` 但是此时，如果我们直接执行查看环境变量的命令是看不到 tomcat service 的环境变量的，因为如果我们顺着实践顺序，当前的 pod 都是在 tomcat service 创建之前创建的 pod。所以我们如果想要查看 tomcat service 的环境变量 需要先创建一个新的 pod 创建 deployment 配置文件 ```bash touch test-deployment.yaml ``` ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: test-deploy spec: replicas: 1 selector: matchLabels: app: test-deploy template: metadata: labels: app: test-deploy spec: containers: - name: busybox image: busybox command: ["sleep", "3600"] ``` 然后执行命令创建 pod ```bash kubectl apply -f test-deployment.yaml ``` 完成创建之后，get pods 找到刚刚创建的 pod 名称：test-deploy-5f8f6c99b6-hrhfs 执行以下命令： ```bash kubectl exec test-deploy-5f8f6c99b6-hrhfs -- env | grep TOMCAT_SERVICE ``` 看到环境变量如下： ```js TOMCAT_SERVICE_PORT_8005_TCP_PROTO=tcp TOMCAT_SERVICE_PORT_8005_TCP_PORT=8005 TOMCAT_SERVICE_SERVICE_PORT_SERVICE_PORT=8080 TOMCAT_SERVICE_PORT_8080_TCP_PORT=8080 TOMCAT_SERVICE_PORT_8005_TCP=tcp://10.233.161.183:8005 TOMCAT_SERVICE_SERVICE_PORT=8080 TOMCAT_SERVICE_PORT_8005_TCP_ADDR=10.233.161.183 TOMCAT_SERVICE_PORT_8080_TCP_PROTO=tcp TOMCAT_SERVICE_PORT_8080_TCP_ADDR=10.233.161.183 TOMCAT_SERVICE_SERVICE_PORT_SHUTDOWN_PORT=8005 TOMCAT_SERVICE_SERVICE_HOST=10.233.161.183 TOMCAT_SERVICE_PORT=tcp://10.233.161.183:8080 TOMCAT_SERVICE_PORT_8080_TCP=tcp://10.233.161.183:8080 ``` #### 最重要的三个环境变量 - `TOMCAT_SERVICE_SERVICE_HOST=10.233.161.183` - Service 的 ClusterIP - 这是服务的主机地址 - `TOMCAT_SERVICE_SERVICE_PORT=8080` - Service 的主端口 - 这是服务的默认端口 - `TOMCAT_SERVICE_PORT=tcp://10.233.161.183:8080` - 完整的服务访问地址 - 组合了协议、IP和端口 这三个被认为最重要是因为： - 它们提供了最基本的连接信息（IP + 端口） - 格式简单直接，易于应用程序使用 - 包含了服务的核心访问信息 ### 服务发现的工作原理 #### 自动注入机制 - 当 Pod 创建时，Kubernetes 会自动注入所有已存在 Service 的环境变量 - 变量命名遵循固定格式：`_SERVICE_HOST` 和 `_SERVICE_PORT` 但通过环境变量来获取 Service 地址的方式仍然不太方便、不够直观，原因如下： - 环境变量是静态的，创建之后不会更新 - 只能发现 Pod 创建时已存在的服务 后来通过 Add-On 增值包引入了 `DNS 系统`（更加动态），把服务名作为 DNS 域名，这样程序就可以直接使用服务名来建立通信连接了。 # 外部系统访问 Service 的问题 ## Node IP `Node IP` 是 Kubernetes 集群中每个节点的物理网卡的 IP地址，是一个 `真实存在` 的物理网络，所有属于这个网络的服务器都能通过这个网络直接通信，不管其中是否有部分节点不属于这个 Kubernetes 集群。 也就是说，在 Kubernetes 集群之外的节点访问 Kubernetes 集群之间的某个节点或者 TCP/IP 服务时，都必须通过 Node IP 通信。 ## Pod IP `Pod IP` 是每个 Pod 的 IP 地址，他是 Docker Engine 根据 docker0 网桥的 IP 地址段进行分配的，通常是一个虚拟的二层网络。 Kubernetes 要求位于不同 Node 上的 Pod 都能够彼此直接通信，所以 Kubernetes 里一个 Pod 里面的容器访问另一个 Pod 里的容器时，就是通过 `Pod IP` 所在的虚拟二层网络进行通信的，而真实的 TCP/IP 流量是通过 Node IP 所在的物理网卡流出的。 ## Cluster IP 是 Service 的 `Cluster IP` 也是一种虚拟的 IP，但更像一个 “伪造” 的 IP 网络 - Cluster IP 仅仅作用于 Kubernetes Service 这个对象，并由 Kubernetes 管理和分配 IP 地址（来源于 Cluster IP 地址池） - Cluster IP 无法被 Ping，因为没有 “实体网络对象” 来响应 - Cluster IP 只能结合 Service Port 组成一个具体的通信端口，单独的 Cluster IP 不具备 TCP/IP 通信的基础，并且他们属于 Kubernetes 集群的封闭空间，集群外的节点如果要访问这个通信端口，则需要做一些额外的工作 - 在 Kubernetes 集群内，Node IP 网、Pod IP 网与 Cluster IP 网之间的通信，采用的是 Kubernetes 自己设计的一种编程方式的特殊路由规则，与我们熟知的 IP 路由有很大的不同 Service 的 `Cluster IP` 属于 Kubernetes 集群内部的地址，无法在集群外部直接使用这个地址。 我们在开发的业务系统中有一部分服务是要提供给 Kubernetes 集群外部的应用或者用户使用的，比如 Web 端的服务模块（eg. 上面的 tomcat-service），如何实现？ ↓↓↓ ## Node Port 以之前的 tomcat-service 为例，在 Service 的定义里做如下的扩展： ```yaml apiVersion: v1 kind: Service metadata: name: tomcat-service spec: type: NodePort ports: - port: 8080 nodePort: 31002 name: service-port selector: tier: frontend ``` 解析： - 这是一个 Kubernetes Service 的定义，使用 NodePort 类型来暴露服务： - `type: NodePort` 表明这个 Service 将在集群的每个节点上开放一个端口，使得服务可以从集群外部访问 - `ports`： - `port: 8080`：Service 在集群内部的端口 - `nodePort: 31002` 在每个节点上开放的外部访问端口（范围必须在 `30000~32767` 之间），也就是 手动指定 tomcat-service 的 NodePort 为 31002，否则 Kubernetes 会自动分配一个可用的端口 - `selector: tier: frontend`：定义了 Service 将会路由流量到哪些 Pod，在这个实践中会选择带有标签 `tier: frontend` 的 Pod `kubectl apply -f `之后，出现以下返回显示 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/10/mhfd7k.webp) 可以通过 `:` 访问服务了，在本实践中 访问 `http://192.168.25.72:31002` 就可以看到 Tomcat 的欢迎界面了。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/10/nbq91r.webp) NodePort 的实现方式是在 Kubernetes 集群里的每个 Node 上都为需要外部访问的 Service 开启一个对应的 TCP 监听端口，外部系统只要用任意一个 Node 的 IP 地址 + 具体的 NodePort 端口号即可访问此服务。也就是说 `NodePort Service` 会在所有集群上开放指定端口，所以可以通过任何节点的 IP 加上 NodePort 端口方位服务。 在任意 Node 上运行 `netstat` 命令，就可以看到有 NodePort 端口被监听。需要注意的是，`netstat` 命令需要安装 `net-tools` 包 ```bash sudo apt update sudo apt install net-tools netstat -tlp | grep 31002 ``` 但是经过一系列的排查后，发现仍然无法出现实践实例中的内容，经排查返回以下原因： 1. `curl http://localhost:31002` 失败 - 这是正常的，因为 localhost 可能没有被正确绑定 2. `curl http://node1:31002` 成功 - 返回了完整的 Tomcat 欢迎页面 - 这证明你的 NodePort 服务工作正常 - Tomcat 8.0.35 已经成功部署并可以访问 3. `netstat` 和 `iptables` 没有显示 31002 端口 - 这是因为 Kubernetes 使用了 IPVS 或者其他代理模式 - 这是正常现象，不影响服务的实际使用 ## 解决外部访问 Service 的所有问题 ### 负载均衡问题 假设集群中有 10 个 Node，则此时最好有一个负载均衡器，外部的请求只需要访问此负载均衡器的 IP 地址，由 负载均衡器负责转发流量到后面某个 Node 的 NodePort 上。 ![image](https://cdn-imagehost.xyzttt.xyz/image/2025/03/10/ns63al.webp) 从上图可知，Load balancer 组件独立于 Kubernetes 集群之外，通常是一个硬件的 负载均衡器，或者以软件的方式实现，例如 HAProxy 或者 Nginx。 对于每个 Service 通常需要配置一个对应的 Load balancer 实例来转发流量到后端的 Node 上，但这样会增加工作量及出错的概率。 所以 Kubernetes 提供了自动化的解决方案，如果集群运行在谷歌的 GCE 上，只要讲 Service 的 `type=NodePort` 改为 `type=LoadBalancer`，Kubernetes 就会自动创建一个对应的 Load balancer 实例并返回它的 IP 地址供外部客户端使用。 其他公有云提供商只要实现了支持此特性的驱动，也可以达到上述目的。 裸机上的类似机制（Bare Metal Service Load Balancers）也在被开发。 # 服务暴露、负载均衡、运维管理、高可用、安全、云原生等问题的解决机制 ## 主流云服务商支持 - AWS: 自动创建 ELB/NLB/ALB - Azure: 自动创建 Azure Load Balancer - GCP: 自动创建 Google Cloud Load Balancer - 阿里云: 自动创建 SLB - 腾讯云: 自动创建 CLB ## 新的解决方案 a) **Cloud Controller Manager**： ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: type: LoadBalancer # 可以指定注解来配置云服务商特定的功能 annotations: service.beta.kubernetes.io/aws-load-balancer-type: nlb ``` b) **MetalLB**：为裸机集群提供 LoadBalancer 实现 ```yaml apiVersion: v1 kind: Service metadata: name: my-service spec: type: LoadBalancer # MetalLB 会自动分配 IP ``` ## 新特性 - **服务拓扑** (Service Topology) - **外部流量策略** (External Traffic Policy) - **内部负载均衡器** (Internal Load Balancer) ```yaml metadata: annotations: cloud.google.com/load-balancer-type: "Internal" ``` ## 替代方案 - **Ingress Controller**：更适合 HTTP/HTTPS 流量 - **Service Mesh**：如 Istio，提供更复杂的流量管理 - **Gateway API**：新一代的流量管理 API（目前处于 beta 阶段） 所以基本机制没变，但生态系统更丰富了，选择更多了。根据具体需求，可以： 1. 使用云服务商的 LoadBalancer 2. 在本地环境使用 MetalLB 3. 使用 Ingress 控制器 4. 采用服务网格解决方案 # References - claude 3.5 sonnet - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）
在 Kubernetes 系统中， Pod 的管理对象 RC、Deployment、DaemonSet 和 Job 都面向 **无状态的服务**。 但现实中很多服务是有状态的，特别是一些复杂的中间件集群，例如 MySQL 集群、MongoDB 集群、Akka 集群、ZooKeeper 集群等，这些 **应用集群** 有 4 个共同点： - 每个节点都有固定的身份 ID，通过这个 ID，集群中的成员可以相互发现并通信； - 集群的规模是比较固定的，集群规模不能随意变动 - 集群中的每个节点都是有状态的，通常会持久化数据到永久存储中 - 如果磁盘损坏，则集群里的某个节点无法正常运行，集群功能受损。 # Pod 命名规则 注意，通过 RC 或者 Deployment 创建的 Pod 不是完全固定的： ## 通过 RC 创建的 Pod 命名为：`-xxxx` - RC 名称是固定部分 - xxxx 是随机生成的字符串 - 当 Pod 被删除重建时，随机字符串会改变 ## 通过 Deployment 创建的 Pod 命名为：`--xxxx` - deployment-name shi guding d bufen - replicaset-hash 是基于 Pod template 生成的哈希值 - xxxx 是随机生成的字符串 也可以在 YAML 中定义 Pod 的名称： ```yaml apiVersion: v1 kind: Pod metadata: name: my-pod-name # 直接定义 Pod 名称 ``` 所以，如果通过 RC 或 Deployment 控制 Pod 副本数量来实现上述有状态的集群，就会发现第一点无法满足，因为 Pod 的名称是随机产生的， Pod 的 IP 地址也是在运行期才确定且有可能改变的，所以无法事先为每个 Pod 都确定唯一不变的 ID。 为了能够在其他节点上恢复某个失败的节点，这种集群中的 Pod 需要挂载某种共享存储，所以从 Kubernetes 1.4 版本开始引入了 PetSet 这个新的资源对象，并在 1.5 版本时更名为 StatefulSet。 所以 StatefulSet 从本质上来说，可以看做 Deployment / RC 的一个特殊变种。 # StatefulSet 特性 - StatefulSet 里的每个 Pod 都有`稳定`、`唯一`的`网络标识`，可以用来`发现集群内的其他成员`。eg. StatefulSet 的名称为 kakfa，那么第一个 Pod 叫 `kafka-0`、第二个叫 `kafka-1` - StatefulSet 控制的 Pod 副本的`启停顺序是受控`的，操作第 n 个 Pod 时，前 n-1 个 Pod 已经是运行且准备好的状态 - StatefulSet 里的 Pod 采用`稳定的` `持久化存储卷`，通过 PV 或者 PVC 来实现，删除 Pod 时默认不会删除与 StatefulSet 相关的存储卷 # Headless Service StatefulSet 除了要与 PV 卷捆绑使用以存储 Pod 的状态数据，还要与 Headless Service 配合使用，即 在每个 StatefulSet 定义中都要生命它属于哪个 Headless Service。 Headless Service 与 普通 Service 的关键区别在于，它没有 Cluster IP，如果解析 Headless Service 的 DNS 域名，则返回的是该 Service 对应的全部 Pod 的 Endpoint 列表。 StatefulSet 在 Headless Service 的基础上又为 StatefulSet 控制的每个 Pod 实例都创建了一个 DNS 域名，这个域名的格式为 `$(podname).$(headless service name)` 比如一个 3 节点的 Kafka 的 StatefulSet 集群对应的 Headless Service 的名称为 kafka，StatefulSet 的名称为 kafka，则 StatefulSet 里的 3 个 Pod 的 DNS 名称分别为 kafka-0.kafka、kafka-1.kafka、kafka-3.kafka，这些 DNS 名称可以直接在集群的配置文件中固定下来。 ## Headless Service 定义文件 ```yaml apiVersion: v1 kind: Service metadata: name: mysql spec: clusterIP: None # 关键点：设置为 None 使其成为 Headless selector: app: mysql ports: - port: 3306 ``` - 没有固定的 Cluster IP - 直接返回所有 Pod 的 IP - 可以访问特定的 Pod ## DNS 域名解析 假设有一个 MySQL 集群： ```yaml # Headless Service apiVersion: v1 kind: Service metadata: name: mysql spec: clusterIP: None selector: app: mysql ports: - port: 3306 --- # StatefulSet apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql spec: serviceName: mysql # 关联到上面的 Headless Service replicas: 3 selector: matchLabels: app: mysql template: metadata: labels: app: mysql ``` 会创建如下的 DNS 记录： ### 单个 Pod 域名 mysql-0.mysql.default.svc.cluster.local mysql-1.mysql.default.svc.cluster.local mysql-2.mysql.default.svc.cluster.local ### Service 域名 mysql.default.svc.cluster.local ## 实际应用场景 以 MySQL 主从复制为例： ### 主库（master） ```bash # 总是访问 mysql-0 mysql -h mysql-0.mysql ``` ### 从库（slave） ```bash # 可以访问 mysql-1 或 mysql-2 mysql -h mysql-1.mysql mysql -h mysql-2.mysql ``` 为什么这么设计： - 固定身份 - 每个 Pod 有唯一的网络标识 - 重启后 Pod 名称和网络标识不变 - 便于其他服务定位特定 Pod - 有序部署 - Pod 按顺序创建：mysql-0, mysql-1, mysql-2 - 按相反顺序删除：mysql-2, mysql-1, mysql-0 - 确保数据一致性 - 主从架构 - 容易识别主节点（通常是 -0） - 从节点可以找到主节点 - 便于配置复制关系 # 有状态 和 无状态 ## 无状态（Stateless） 假设一个自助餐厅的服务员： - 每个服务员都可以为任何客人服务 - 不需要记住客人之前点过什么 - 服务员可以随时更换，不影响服务质量 - 客人的每次请求都是独立的 比如： ### Web 服务器 - 处理 HTTP 请求 - 不需要记住用户上一次的请求 - 每个请求都是独立的 - 任何服务器实例都能处理请求 ### 无状态 API `GET /api/products/1` - 不知道之前的请求 - 响应仅依赖于当前请求参数 ## 有状态（Stateful） 一个特定的理发店的理发师： - 需要记住客人的发型偏好 - 要知道上次理发的长度 - 不能随意更换理发师 - 服务是连续的，有上下文关联 比如： ### 数据库 - 需要持久化存储数据 - 主从节点有固定的角色 - 需要维护数据一致性 - 每个实例都是独特的 ### 购物车会话 ```bash Session ID: abc123 Cart Items: [item1, item2] ``` - 需要记住用户的购物记录 - 需要追踪会话状态 ## 为什么要区分有状态无状态 ### 部署策略不同 - 无状态：可以随意部署、销毁 - 例如 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: webserver ``` - 有状态：需要特殊处理 ```yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: mysql ``` ### 数据处理方式 - 无状态：不需要考虑数据持久化 - 有状态：需要持久化存储（PV / PVC） ```yaml volumeClaimTemplates: - metadata: name: data spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 10Gi ``` ### 扩展性考虑 - 无状态：可以随意扩展 ```bash kubectl scale deployment webserver --replicas=5 ``` - 有状态：需要考虑数据同步 ```bash kubectl scale statefulset mysql --replicas=3 # 需要考虑数据复制 ``` ### 故障恢复 - 无状态：直接重启新实例 - 有状态：需要恢复数据和状态 ## 实际应用建议 ### 优先考虑无状态设计 - 更容易扩展 - 更容易维护 - 更适合云原生架构 ### 状态外部化 - 将状态存储在专门的存储服务中 - 使用缓存服务（如 Redis） - 使用数据库服务 ### 合理使用 有状态服务 - 必要的数据库服务 - 需要持久化的消息队列 - 分布式存储系统 # References - claude 3.5 sonnet - Kubernetes 权威指南：从 Docker 到 Kubernetes 实践全接触（第 4 版）