文档
- 1: Kubernetes 文档
- 1.1: Kubernetes 文档支持的版本
- 2: 入门
- 2.1: 学习环境
- 2.2: 生产环境
- 2.2.1: 容器运行时
- 2.2.2: 使用部署工具安装 Kubernetes
- 2.2.2.1: 使用 kubeadm 引导集群
- 2.2.2.1.1: 安装 kubeadm
- 2.2.2.1.2: 对 kubeadm 进行故障排查
- 2.2.2.1.3: 使用 kubeadm 创建集群
- 2.2.2.1.4: 使用 kubeadm API 定制组件
- 2.2.2.1.5: 高可用拓扑选项
- 2.2.2.1.6: 利用 kubeadm 创建高可用集群
- 2.2.2.1.7: 使用 kubeadm 创建一个高可用 etcd 集群
- 2.2.2.1.8: 使用 kubeadm 配置集群中的每个 kubelet
- 2.2.2.1.9: 使用 kubeadm 支持双协议栈
- 2.2.3: Turnkey 云解决方案
- 2.3: 最佳实践
- 2.3.1: 大规模集群的注意事项
- 2.3.2: 运行于多可用区环境
- 2.3.3: 校验节点设置
- 2.3.4: 强制实施 Pod 安全性标准
- 2.3.5: PKI 证书和要求
- 3: 概念
- 3.1: 概述
- 3.1.1: Kubernetes 组件
- 3.1.2: Kubernetes 对象
- 3.1.2.1: Kubernetes 对象管理
- 3.1.2.2: 对象名称和 ID
- 3.1.2.3: 标签和选择算符
- 3.1.2.4: 名字空间
- 3.1.2.5: 注解
- 3.1.2.6: 字段选择器
- 3.1.2.7: Finalizers
- 3.1.2.8: 属主与附属
- 3.1.2.9: 推荐使用的标签
- 3.1.3: Kubernetes API
- 3.2: Kubernetes 架构
- 3.2.1: 节点
- 3.2.2: 节点与控制面之间的通信
- 3.2.3: 控制器
- 3.2.4: 租约(Lease)
- 3.2.5: 云控制器管理器
- 3.2.6: 关于 cgroup v2
- 3.2.7: 容器运行时接口(CRI)
- 3.2.8: 垃圾收集
- 3.2.9: 混合版本代理
- 3.3: 容器
- 3.3.1: 镜像
- 3.3.2: 容器环境
- 3.3.3: 容器运行时类(Runtime Class)
- 3.3.4: 容器生命周期回调
- 3.4: 工作负载
- 3.4.1: Pod
- 3.4.1.1: Pod 的生命周期
- 3.4.1.2: Init 容器
- 3.4.1.3: 边车容器
- 3.4.1.4: 临时容器
- 3.4.1.5: 干扰(Disruptions)
- 3.4.1.6: Pod QoS 类
- 3.4.1.7: 用户命名空间
- 3.4.1.8: Downward API
- 3.4.2: 工作负载管理
- 3.4.2.1: Deployments
- 3.4.2.2: ReplicaSet
- 3.4.2.3: StatefulSet
- 3.4.2.4: DaemonSet
- 3.4.2.5: Job
- 3.4.2.6: 已完成 Job 的自动清理
- 3.4.2.7: CronJob
- 3.4.2.8: ReplicationController
- 3.4.3: 管理工作负载
- 3.4.4: 自动扩缩工作负载
- 3.5: 服务、负载均衡和联网
- 3.5.1: 服务(Service)
- 3.5.2: Ingress
- 3.5.3: Ingress 控制器
- 3.5.4: Gateway API
- 3.5.5: EndpointSlice
- 3.5.6: 网络策略
- 3.5.7: Service 与 Pod 的 DNS
- 3.5.8: IPv4/IPv6 双协议栈
- 3.5.9: 拓扑感知路由
- 3.5.10: Windows 网络
- 3.5.11: Service ClusterIP 分配
- 3.5.12: 服务内部流量策略
- 3.6: 存储
- 3.6.1: 卷
- 3.6.2: 持久卷
- 3.6.3: 投射卷
- 3.6.4: 临时卷
- 3.6.5: 存储类
- 3.6.6: 卷属性类
- 3.6.7: 动态卷制备
- 3.6.8: 卷快照
- 3.6.9: 卷快照类
- 3.6.10: CSI 卷克隆
- 3.6.11: 存储容量
- 3.6.12: 特定于节点的卷数限制
- 3.6.13: 卷健康监测
- 3.6.14: Windows 存储
- 3.7: 配置
- 3.7.1: 配置最佳实践
- 3.7.2: ConfigMap
- 3.7.3: Secret
- 3.7.4: 存活、就绪和启动探针
- 3.7.5: 为 Pod 和容器管理资源
- 3.7.6: 使用 kubeconfig 文件组织集群访问
- 3.7.7: Windows 节点的资源管理
- 3.8: 安全
- 3.8.1: 云原生安全和 Kubernetes
- 3.8.2: Pod 安全性标准
- 3.8.3: Pod 安全性准入
- 3.8.4: 服务账号
- 3.8.5: Pod 安全策略
- 3.8.6: Windows 节点的安全性
- 3.8.7: Kubernetes API 访问控制
- 3.8.8: 基于角色的访问控制良好实践
- 3.8.9: Kubernetes Secret 良好实践
- 3.8.10: 多租户
- 3.8.11: Kubernetes API 服务器旁路风险
- 3.8.12: 加固指南 - 身份认证机制
- 3.8.13: 安全检查清单
- 3.8.14: 针对 Pod 和容器的 Linux 内核安全约束
- 3.8.15: 应用安全检查清单
- 3.9: 策略
- 3.9.1: 限制范围
- 3.9.2: 资源配额
- 3.9.3: 进程 ID 约束与预留
- 3.9.4: 节点资源管理器
- 3.10: 调度、抢占和驱逐
- 3.10.1: Kubernetes 调度器
- 3.10.2: 将 Pod 指派给节点
- 3.10.3: Pod 开销
- 3.10.4: Pod 调度就绪态
- 3.10.5: Pod 拓扑分布约束
- 3.10.6: 污点和容忍度
- 3.10.7: 调度框架
- 3.10.8: 动态资源分配
- 3.10.9: 调度器性能调优
- 3.10.10: 资源装箱
- 3.10.11: Pod 优先级和抢占
- 3.10.12: 节点压力驱逐
- 3.10.13: API 发起的驱逐
- 3.11: 集群管理
- 3.11.1: 节点关闭
- 3.11.2: 证书
- 3.11.3: 集群网络系统
- 3.11.4: 日志架构
- 3.11.5: Kubernetes 系统组件指标
- 3.11.6: Kubernetes 对象状态的指标
- 3.11.7: 系统日志
- 3.11.8: 追踪 Kubernetes 系统组件
- 3.11.9: Kubernetes 中的代理
- 3.11.10: API 优先级和公平性
- 3.11.11: 安装扩展(Addon)
- 3.11.12: 集群自动扩缩容
- 3.11.13: 协调领导者选举
- 3.12: Kubernetes 中的 Windows
- 3.12.1: Kubernetes 中的 Windows 容器
- 3.12.2: Kubernetes 中的 Windows 容器调度指南
- 3.13: 扩展 Kubernetes
- 3.13.1: Operator 模式
- 3.13.2: 计算、存储和网络扩展
- 3.13.3: 扩展 Kubernetes API
- 3.13.3.1: 定制资源
- 3.13.3.2: Kubernetes API 聚合层
- 4: 任务
- 4.1: 安装工具
- 4.1.1: 在 Linux 系统中安装并设置 kubectl
- 4.1.2: 在 macOS 系统上安装和设置 kubectl
- 4.1.3: 在 Windows 上安装 kubectl
- 4.2: 管理集群
- 4.2.1: 为集群超配节点容量
- 4.2.2: 用 kubeadm 进行管理
- 4.2.2.1: 添加 Linux 工作节点
- 4.2.2.2: 添加 Windows 工作节点
- 4.2.2.3: 升级 kubeadm 集群
- 4.2.2.4: 升级 Linux 节点
- 4.2.2.5: 升级 Windows 节点
- 4.2.2.6: 配置 cgroup 驱动
- 4.2.2.7: 使用 kubeadm 进行证书管理
- 4.2.2.8: 重新配置 kubeadm 集群
- 4.2.2.9: 更改 Kubernetes 软件包仓库
- 4.2.3: 从 dockershim 迁移
- 4.2.3.1: 将节点上的容器运行时从 Docker Engine 改为 containerd
- 4.2.3.2: 将 Docker Engine 节点从 dockershim 迁移到 cri-dockerd
- 4.2.3.3: 查明节点上所使用的容器运行时
- 4.2.3.4: 排查 CNI 插件相关的错误
- 4.2.3.5: 检查移除 Dockershim 是否对你有影响
- 4.2.3.6: 从 dockershim 迁移遥测和安全代理
- 4.2.4: 手动生成证书
- 4.2.5: 管理内存、CPU 和 API 资源
- 4.2.5.1: 为命名空间配置默认的内存请求和限制
- 4.2.5.2: 为命名空间配置默认的 CPU 请求和限制
- 4.2.5.3: 配置命名空间的最小和最大内存约束
- 4.2.5.4: 为命名空间配置 CPU 最小和最大约束
- 4.2.5.5: 为命名空间配置内存和 CPU 配额
- 4.2.5.6: 配置命名空间下 Pod 配额
- 4.2.6: 安装网络策略驱动
- 4.2.6.1: 使用 Antrea 提供 NetworkPolicy
- 4.2.6.2: 使用 Calico 提供 NetworkPolicy
- 4.2.6.3: 使用 Cilium 提供 NetworkPolicy
- 4.2.6.4: 使用 kube-router 提供 NetworkPolicy
- 4.2.6.5: 使用 Romana 提供 NetworkPolicy
- 4.2.6.6: 使用 Weave Net 提供 NetworkPolicy
- 4.2.7: 使用 Kubernetes API 访问集群
- 4.2.8: 为节点发布扩展资源
- 4.2.9: 自动扩缩集群 DNS 服务
- 4.2.10: 从轮询切换为基于 CRI 事件的更新来获取容器状态
- 4.2.11: 改变默认 StorageClass
- 4.2.12: 将 PersistentVolume 的访问模式更改为 ReadWriteOncePod
- 4.2.13: 更改 PersistentVolume 的回收策略
- 4.2.14: Kubernetes 云管理控制器
- 4.2.15: 配置 kubelet 镜像凭据提供程序
- 4.2.16: 配置 API 对象配额
- 4.2.17: 控制节点上的 CPU 管理策略
- 4.2.18: 控制节点上的拓扑管理策略
- 4.2.19: 自定义 DNS 服务
- 4.2.20: 调试 DNS 问题
- 4.2.21: 声明网络策略
- 4.2.22: 开发云控制器管理器
- 4.2.23: 启用/禁用 Kubernetes API
- 4.2.24: 静态加密机密数据
- 4.2.25: 解密已静态加密的机密数据
- 4.2.26: 关键插件 Pod 的调度保证
- 4.2.27: IP Masquerade Agent 用户指南
- 4.2.28: 限制存储使用量
- 4.2.29: 迁移多副本的控制面以使用云控制器管理器
- 4.2.30: 名字空间演练
- 4.2.31: 操作 Kubernetes 中的 etcd 集群
- 4.2.32: 为系统守护进程预留计算资源
- 4.2.33: 以非 root 用户身份运行 Kubernetes 节点组件
- 4.2.34: 安全地清空一个节点
- 4.2.35: 保护集群
- 4.2.36: 通过配置文件设置 kubelet 参数
- 4.2.37: 通过名字空间共享集群
- 4.2.38: 升级集群
- 4.2.39: 在集群中使用级联删除
- 4.2.40: 使用 KMS 驱动进行数据加密
- 4.2.41: 使用 CoreDNS 进行服务发现
- 4.2.42: 在 Kubernetes 集群中使用 NodeLocal DNSCache
- 4.2.43: 在 Kubernetes 集群中使用 sysctl
- 4.2.44: 使用 NUMA 感知的内存管理器
- 4.2.45: 验证已签名容器镜像
- 4.3: 配置 Pods 和容器
- 4.3.1: 为容器和 Pod 分配内存资源
- 4.3.2: 为容器和 Pods 分配 CPU 资源
- 4.3.3: 调整分配给容器的 CPU 和内存资源
- 4.3.4: 为 Windows Pod 和容器配置 GMSA
- 4.3.5: 为 Windows 的 Pod 和容器配置 RunAsUserName
- 4.3.6: 创建 Windows HostProcess Pod
- 4.3.7: 配置 Pod 的服务质量
- 4.3.8: 为容器分派扩展资源
- 4.3.9: 配置 Pod 以使用卷进行存储
- 4.3.10: 配置 Pod 以使用 PersistentVolume 作为存储
- 4.3.11: 配置 Pod 使用投射卷作存储
- 4.3.12: 为 Pod 或容器配置安全上下文
- 4.3.13: 为 Pod 配置服务账号
- 4.3.14: 从私有仓库拉取镜像
- 4.3.15: 配置存活、就绪和启动探针
- 4.3.16: 将 Pod 分配给节点
- 4.3.17: 用节点亲和性把 Pod 分配到节点
- 4.3.18: 配置 Pod 初始化
- 4.3.19: 为容器的生命周期事件设置处理函数
- 4.3.20: 配置 Pod 使用 ConfigMap
- 4.3.21: 在 Pod 中的容器之间共享进程命名空间
- 4.3.22: Pod 使用镜像卷
- 4.3.23: 为 Pod 配置 user 名字空间
- 4.3.24: 创建静态 Pod
- 4.3.25: 将 Docker Compose 文件转换为 Kubernetes 资源
- 4.3.26: 通过配置内置准入控制器实施 Pod 安全标准
- 4.3.27: 使用名字空间标签来实施 Pod 安全性标准
- 4.3.28: 从 PodSecurityPolicy 迁移到内置的 PodSecurity 准入控制器
- 4.4: 监控、日志和调试
- 4.4.1: 集群故障排查
- 4.4.1.1: kubectl 故障排查
- 4.4.1.2: 资源监控工具
- 4.4.1.3: 资源指标管道
- 4.4.1.4: 节点健康监测
- 4.4.1.5: 使用 crictl 对 Kubernetes 节点进行调试
- 4.4.1.6: Windows 调试技巧
- 4.4.1.7: 审计
- 4.4.1.8: 使用 telepresence 在本地开发和调试服务
- 4.4.1.9: 用 Kubectl 调试 Kubernetes 节点
- 4.4.2: 应用故障排除
- 4.4.2.1: 调试 Pod
- 4.4.2.2: 调试 Service
- 4.4.2.3: 调试 StatefulSet
- 4.4.2.4: 确定 Pod 失败的原因
- 4.4.2.5: 调试 Init 容器
- 4.4.2.6: 调试运行中的 Pod
- 4.4.2.7: 获取正在运行容器的 Shell
- 4.5: 管理 Kubernetes 对象
- 4.5.1: 使用配置文件对 Kubernetes 对象进行声明式管理
- 4.5.2: 使用 Kustomize 对 Kubernetes 对象进行声明式管理
- 4.5.3: 使用指令式命令管理 Kubernetes 对象
- 4.5.4: 使用配置文件对 Kubernetes 对象进行命令式管理
- 4.5.5: 使用 kubectl patch 更新 API 对象
- 4.5.6: 使用存储版本迁移功能来迁移 Kubernetes 对象
- 4.6: 管理 Secrets
- 4.6.1: 使用 kubectl 管理 Secret
- 4.6.2: 使用配置文件管理 Secret
- 4.6.3: 使用 Kustomize 管理 Secret
- 4.7: 给应用注入数据
- 4.7.1: 为容器设置启动时要执行的命令和参数
- 4.7.2: 定义相互依赖的环境变量
- 4.7.3: 为容器设置环境变量
- 4.7.4: 通过环境变量将 Pod 信息呈现给容器
- 4.7.5: 通过文件将 Pod 信息呈现给容器
- 4.7.6: 使用 Secret 安全地分发凭据
- 4.8: 运行应用
- 4.8.1: 使用 Deployment 运行一个无状态应用
- 4.8.2: 运行一个单实例有状态应用
- 4.8.3: 运行一个有状态的应用程序
- 4.8.4: 扩缩 StatefulSet
- 4.8.5: 删除 StatefulSet
- 4.8.6: 强制删除 StatefulSet 中的 Pod
- 4.8.7: Pod 水平自动扩缩
- 4.8.8: HorizontalPodAutoscaler 演练
- 4.8.9: 为应用程序设置干扰预算(Disruption Budget)
- 4.8.10: 从 Pod 中访问 Kubernetes API
- 4.9: 运行 Jobs
- 4.9.1: 使用 CronJob 运行自动化任务
- 4.9.2: 使用工作队列进行粗粒度并行处理
- 4.9.3: 带 Pod 间通信的 Job
- 4.9.4: 使用工作队列进行精细的并行处理
- 4.9.5: 使用索引作业完成静态工作分配下的并行处理
- 4.9.6: 使用展开的方式进行并行处理
- 4.9.7: 使用 Pod 失效策略处理可重试和不可重试的 Pod 失效
- 4.10: 访问集群中的应用程序
- 4.10.1: 部署和访问 Kubernetes 仪表板(Dashboard)
- 4.10.2: 访问集群
- 4.10.3: 配置对多集群的访问
- 4.10.4: 使用端口转发来访问集群中的应用
- 4.10.5: 使用服务来访问集群中的应用
- 4.10.6: 使用 Service 把前端连接到后端
- 4.10.7: 创建外部负载均衡器
- 4.10.8: 列出集群中所有运行容器的镜像
- 4.10.9: 在 Minikube 环境中使用 NGINX Ingress 控制器配置 Ingress
- 4.10.10: 同 Pod 内的容器使用共享卷通信
- 4.10.11: 为集群配置 DNS
- 4.10.12: 访问集群上运行的服务
- 4.11: 扩展 Kubernetes
- 4.11.1: 配置聚合层
- 4.11.2: 使用自定义资源
- 4.11.2.1: 使用 CustomResourceDefinition 扩展 Kubernetes API
- 4.11.2.2: CustomResourceDefinition 的版本
- 4.11.3: 安装一个扩展的 API server
- 4.11.4: 配置多个调度器
- 4.11.5: 使用 HTTP 代理访问 Kubernetes API
- 4.11.6: 使用 SOCKS5 代理访问 Kubernetes API
- 4.11.7: 设置 Konnectivity 服务
- 4.12: TLS
- 4.12.1: 管理集群中的 TLS 认证
- 4.12.2: 手动轮换 CA 证书
- 4.12.3: 为 kubelet 配置证书轮换
- 4.13: 管理集群守护进程
- 4.13.1: 对 DaemonSet 执行滚动更新
- 4.13.2: 对 DaemonSet 执行回滚
- 4.13.3: 仅在某些节点上运行 Pod
- 4.14: 网络
- 4.14.1: 使用 HostAliases 向 Pod /etc/hosts 文件添加条目
- 4.14.2: 扩展 Service IP 范围
- 4.14.3: 验证 IPv4/IPv6 双协议栈
- 4.15: 调度 GPU
- 4.16: 管理巨页(HugePage)
- 4.17: 用插件扩展 kubectl
- 5: 教程
- 5.1: 你好,Minikube
- 5.2: 学习 Kubernetes 基础知识
- 5.2.1: 创建集群
- 5.2.1.1: 使用 Minikube 创建集群
- 5.2.2: 部署应用
- 5.2.2.1: 使用 kubectl 创建 Deployment
- 5.2.3: 了解你的应用
- 5.2.3.1: 查看 Pod 和节点
- 5.2.4: 公开地暴露你的应用
- 5.2.4.1: 使用 Service 暴露你的应用
- 5.2.5: 扩缩你的应用
- 5.2.5.1: 运行多实例的应用
- 5.2.6: 更新你的应用
- 5.2.6.1: 执行滚动更新
- 5.3: 配置
- 5.3.1: 通过 ConfigMap 更新配置
- 5.3.2: 使用 ConfigMap 来配置 Redis
- 5.3.3: 使用边车(Sidecar)容器
- 5.4: 安全
- 5.4.1: 在集群级别应用 Pod 安全标准
- 5.4.2: 在名字空间级别应用 Pod 安全标准
- 5.4.3: 使用 AppArmor 限制容器对资源的访问
- 5.4.4: 使用 seccomp 限制容器的系统调用
- 5.5: 无状态的应用
- 5.5.1: 公开外部 IP 地址以访问集群中的应用
- 5.5.2: 示例:使用 Redis 部署 PHP 留言板应用
- 5.6: 有状态的应用
- 5.6.1: StatefulSet 基础
- 5.6.2: 示例:使用持久卷部署 WordPress 和 MySQL
- 5.6.3: 示例:使用 StatefulSet 部署 Cassandra
- 5.6.4: 运行 ZooKeeper,一个分布式协调系统
- 5.7: 集群管理
- 5.7.1: 以独立模式运行 kubelet
- 5.8: Service
- 5.8.1: 使用 Service 连接到应用
- 5.8.2: 使用源 IP
- 5.8.3: 探索 Pod 及其端点的终止行为
- 6: 参考
- 6.1: 词汇表
- 6.2: API 概述
- 6.2.1: Kubernetes API 概念
- 6.2.2: 服务器端应用(Server-Side Apply)
- 6.2.3: 客户端库
- 6.2.4: Kubernetes 中的通用表达式语言
- 6.2.5: Kubernetes 弃用策略
- 6.2.6: 已弃用 API 的迁移指南
- 6.2.7: Kubernetes API 健康端点
- 6.3: API 访问控制
- 6.3.1: 用户认证
- 6.3.2: 使用启动引导令牌(Bootstrap Tokens)认证
- 6.3.3: 鉴权
- 6.3.4: 使用 RBAC 鉴权
- 6.3.5: 使用 Node 鉴权
- 6.3.6: Webhook 模式
- 6.3.7: 使用 ABAC 鉴权
- 6.3.8: 准入控制器参考
- 6.3.9: 动态准入控制
- 6.3.10: 管理服务账号
- 6.3.11: 证书和证书签名请求
- 6.3.12: 从 PodSecurityPolicy 映射到 Pod 安全性标准
- 6.3.13: Kubelet 认证/鉴权
- 6.3.14: TLS 启动引导
- 6.3.15: 验证准入策略(ValidatingAdmissionPolicy)
- 6.4: 众所周知的标签、注解和污点
- 6.4.1: 审计注解
- 6.5: Kubernetes API
- 6.5.1: 工作负载资源
- 6.5.1.1: Pod
- 6.5.1.2: Binding
- 6.5.1.3: PodTemplate
- 6.5.1.4: ReplicaSet
- 6.5.1.5: ReplicationController
- 6.5.1.6: Deployment
- 6.5.1.7: StatefulSet
- 6.5.1.8: ControllerRevision
- 6.5.1.9: DaemonSet
- 6.5.1.10: Job
- 6.5.1.11: CronJob
- 6.5.1.12: HorizontalPodAutoscaler
- 6.5.1.13: HorizontalPodAutoscaler
- 6.5.1.14: PriorityClass
- 6.5.1.15: PodSchedulingContext v1alpha3
- 6.5.1.16: ResourceClaim v1alpha3
- 6.5.1.17: ResourceClaimTemplate v1alpha3
- 6.5.2: Service 资源
- 6.5.2.1: Service
- 6.5.2.2: Endpoints
- 6.5.2.3: EndpointSlice
- 6.5.2.4: Ingress
- 6.5.2.5: IngressClass
- 6.5.3: 配置和存储资源
- 6.5.3.1: ConfigMap
- 6.5.3.2: Secret
- 6.5.3.3: CSIDriver
- 6.5.3.4: CSINode
- 6.5.3.5: CSIStorageCapacity
- 6.5.3.6: PersistentVolumeClaim
- 6.5.3.7: PersistentVolume
- 6.5.3.8: StorageClass
- 6.5.3.9: StorageVersionMigration v1alpha1
- 6.5.3.10: Volume
- 6.5.3.11: VolumeAttachment
- 6.5.3.12: VolumeAttributesClass v1beta1
- 6.5.4: 身份认证资源
- 6.5.4.1: ServiceAccount
- 6.5.4.2: TokenRequest
- 6.5.4.3: TokenReview
- 6.5.4.4: CertificateSigningRequest
- 6.5.4.5: ClusterTrustBundle v1alpha1
- 6.5.4.6: SelfSubjectReview
- 6.5.5: 鉴权资源
- 6.5.5.1: LocalSubjectAccessReview
- 6.5.5.2: SelfSubjectAccessReview
- 6.5.5.3: SelfSubjectRulesReview
- 6.5.5.4: SubjectAccessReview
- 6.5.5.5: ClusterRole
- 6.5.5.6: ClusterRoleBinding
- 6.5.5.7: Role
- 6.5.5.8: RoleBinding
- 6.5.6: 策略资源
- 6.5.6.1: FlowSchema
- 6.5.6.2: LimitRange
- 6.5.6.3: ResourceQuota
- 6.5.6.4: NetworkPolicy
- 6.5.6.5: PodDisruptionBudget
- 6.5.6.6: PriorityLevelConfiguration v1
- 6.5.7: 扩展资源
- 6.5.7.1: CustomResourceDefinition
- 6.5.7.2: DeviceClass v1alpha3
- 6.5.7.3: MutatingWebhookConfiguration
- 6.5.7.4: ValidatingWebhookConfiguration
- 6.5.8: 集群资源
- 6.5.8.1: APIService
- 6.5.8.2: ComponentStatus
- 6.5.8.3: Event
- 6.5.8.4: IPAddress v1beta1
- 6.5.8.5: Lease
- 6.5.8.6: LeaseCandidate v1alpha1
- 6.5.8.7: Namespace
- 6.5.8.8: Node
- 6.5.8.9: RuntimeClass
- 6.5.8.10: ServiceCIDR v1beta1
- 6.5.9: 公共定义
- 6.5.9.1: DeleteOptions
- 6.5.9.2: LabelSelector
- 6.5.9.3: ListMeta
- 6.5.9.4: LocalObjectReference
- 6.5.9.5: NodeSelectorRequirement
- 6.5.9.6: ObjectFieldSelector
- 6.5.9.7: ObjectMeta
- 6.5.9.8: ObjectReference
- 6.5.9.9: Patch
- 6.5.9.10: Quantity
- 6.5.9.11: ResourceFieldSelector
- 6.5.9.12: Status
- 6.5.9.13: TypedLocalObjectReference
- 6.5.10: 常用参数
- 6.6: 插桩
- 6.6.1: Kubernetes 组件 SLI 指标
- 6.6.2: CRI Pod 和容器指标
- 6.6.3: 节点指标数据
- 6.7: Kubernetes 问题和安全
- 6.7.1: Kubernetes 问题追踪
- 6.7.2: Kubernetes 安全和信息披露
- 6.7.3: 官方 CVE 订阅源
- 6.8: 节点参考信息
- 6.8.1: Kubelet Checkpoint API
- 6.8.2: Linux 内核版本要求
- 6.8.3: 关于 dockershim 移除和使用兼容 CRI 运行时的文章
- 6.8.4: 由 kubelet 填充的节点标签
- 6.8.5: kubelet 所使用的本地文件和路径
- 6.8.6: kubelet 配置目录合并
- 6.8.7: Kubelet 设备管理器 API 版本
- 6.8.8: Seccomp 和 Kubernetes
- 6.8.9: 节点状态
- 6.9: 网络参考
- 6.9.1: Service 所用的协议
- 6.9.2: 端口和协议
- 6.9.3: 虚拟 IP 和服务代理
- 6.10: 安装工具
- 6.10.1: Kubeadm
- 6.10.1.1: 创建 Kubeadm
- 6.10.1.1.1:
- 6.10.1.1.2:
- 6.10.1.1.2.1:
- 6.10.1.1.2.2:
- 6.10.1.1.2.3:
- 6.10.1.1.2.4:
- 6.10.1.1.2.5:
- 6.10.1.1.2.6:
- 6.10.1.1.2.7:
- 6.10.1.1.2.8:
- 6.10.1.1.2.9:
- 6.10.1.1.2.10:
- 6.10.1.1.2.11:
- 6.10.1.1.2.12:
- 6.10.1.1.2.13:
- 6.10.1.1.2.14:
- 6.10.1.1.2.15:
- 6.10.1.1.2.16:
- 6.10.1.1.3:
- 6.10.1.1.4:
- 6.10.1.1.4.1:
- 6.10.1.1.4.2:
- 6.10.1.1.4.3:
- 6.10.1.1.4.4:
- 6.10.1.1.4.5:
- 6.10.1.1.4.6:
- 6.10.1.1.4.7:
- 6.10.1.1.4.8:
- 6.10.1.1.4.9:
- 6.10.1.1.4.10:
- 6.10.1.1.5:
- 6.10.1.1.5.1:
- 6.10.1.1.5.2:
- 6.10.1.1.5.3:
- 6.10.1.1.5.4:
- 6.10.1.1.5.5:
- 6.10.1.1.5.6:
- 6.10.1.1.5.7:
- 6.10.1.1.5.8:
- 6.10.1.1.5.9:
- 6.10.1.1.5.10:
- 6.10.1.1.5.11:
- 6.10.1.1.5.12:
- 6.10.1.1.5.13:
- 6.10.1.1.5.14:
- 6.10.1.1.5.15:
- 6.10.1.1.5.16:
- 6.10.1.1.5.17:
- 6.10.1.1.5.18:
- 6.10.1.1.5.19:
- 6.10.1.1.5.20:
- 6.10.1.1.5.21:
- 6.10.1.1.5.22:
- 6.10.1.1.5.23:
- 6.10.1.1.5.24:
- 6.10.1.1.5.25:
- 6.10.1.1.5.26:
- 6.10.1.1.5.27:
- 6.10.1.1.5.28:
- 6.10.1.1.5.29:
- 6.10.1.1.5.30:
- 6.10.1.1.5.31:
- 6.10.1.1.5.32:
- 6.10.1.1.5.33:
- 6.10.1.1.5.34:
- 6.10.1.1.5.35:
- 6.10.1.1.5.36:
- 6.10.1.1.5.37:
- 6.10.1.1.5.38:
- 6.10.1.1.5.39:
- 6.10.1.1.5.40:
- 6.10.1.1.5.41:
- 6.10.1.1.5.42:
- 6.10.1.1.5.43:
- 6.10.1.1.5.44:
- 6.10.1.1.5.45:
- 6.10.1.1.5.46:
- 6.10.1.1.6:
- 6.10.1.1.6.1:
- 6.10.1.1.6.2:
- 6.10.1.1.6.3:
- 6.10.1.1.6.4:
- 6.10.1.1.6.5:
- 6.10.1.1.6.6:
- 6.10.1.1.6.7:
- 6.10.1.1.6.8:
- 6.10.1.1.6.9:
- 6.10.1.1.6.10:
- 6.10.1.1.6.11:
- 6.10.1.1.6.12:
- 6.10.1.1.6.13:
- 6.10.1.1.6.14:
- 6.10.1.1.7:
- 6.10.1.1.8:
- 6.10.1.1.9:
- 6.10.1.1.10:
- 6.10.1.1.10.1:
- 6.10.1.1.10.2:
- 6.10.1.1.10.3:
- 6.10.1.1.10.4:
- 6.10.1.1.10.5:
- 6.10.1.1.10.6:
- 6.10.1.1.10.7:
- 6.10.1.1.10.8:
- 6.10.1.1.11:
- 6.10.1.1.12:
- 6.10.1.2: kubeadm init
- 6.10.1.3: kubeadm join
- 6.10.1.4: kubeadm upgrade
- 6.10.1.5: kubeadm config
- 6.10.1.6: kubeadm reset
- 6.10.1.7: kubeadm token
- 6.10.1.8: kubeadm version
- 6.10.1.9: kubeadm alpha
- 6.10.1.10: kubeadm certs
- 6.10.1.11: kubeadm init phase
- 6.10.1.12: kubeadm join phase
- 6.10.1.13: kubeadm kubeconfig
- 6.10.1.14: kubeadm reset phase
- 6.10.1.15: kubeadm upgrade phase
- 6.10.1.16: 实现细节
- 6.11: 命令行工具 (kubectl)
- 6.11.1: kubectl 介绍
- 6.11.2: kubectl 参考
- 6.11.2.1: kubectl
- 6.11.2.2: kubectl annotate
- 6.11.2.3: kubectl api-resources
- 6.11.2.4: kubectl api-versions
- 6.11.2.5: kubectl apply
- 6.11.2.5.1: kubectl apply edit-last-applied
- 6.11.2.5.2: kubectl apply set-last-applied
- 6.11.2.5.3: kubectl apply view-last-applied
- 6.11.2.6: kubectl attach
- 6.11.2.7: kubectl auth
- 6.11.2.7.1: kubectl auth can-i
- 6.11.2.7.2: kubectl auth reconcile
- 6.11.2.7.3: kubectl auth whoami
- 6.11.2.8: kubectl autoscale
- 6.11.2.9: kubectl certificate
- 6.11.2.9.1: kubectl certificate approve
- 6.11.2.9.2: kubectl certificate deny
- 6.11.2.10: kubectl cluster-info
- 6.11.2.10.1: kubectl cluster-info dump
- 6.11.2.11: kubectl completion
- 6.11.2.12: kubectl config
- 6.11.2.12.1: kubectl config current-context
- 6.11.2.12.2: kubectl config delete-cluster
- 6.11.2.12.3: kubectl config delete-context
- 6.11.2.12.4: kubectl config delete-user
- 6.11.2.12.5: kubectl config get-clusters
- 6.11.2.12.6: kubectl config get-contexts
- 6.11.2.12.7: kubectl config get-users
- 6.11.2.12.8: kubectl config rename-context
- 6.11.2.12.9: kubectl config set
- 6.11.2.12.10: kubectl config set-cluster
- 6.11.2.12.11: kubectl config set-context
- 6.11.2.12.12: kubectl config set-credentials
- 6.11.2.12.13: kubectl config unset
- 6.11.2.12.14: kubectl config use-context
- 6.11.2.12.15: kubectl config view
- 6.11.2.13: kubectl cordon
- 6.11.2.14: kubectl cp
- 6.11.2.15: kubectl create
- 6.11.2.15.1: kubectl create clusterrole
- 6.11.2.15.2: kubectl create clusterrolebinding
- 6.11.2.15.3: kubectl create configmap
- 6.11.2.15.4: kubectl create cronjob
- 6.11.2.15.5: kubectl create deployment
- 6.11.2.15.6: kubectl create ingress
- 6.11.2.15.7: kubectl create job
- 6.11.2.15.8: kubectl create namespace
- 6.11.2.15.9: kubectl create poddisruptionbudget
- 6.11.2.15.10: kubectl create priorityclass
- 6.11.2.15.11: kubectl create quota
- 6.11.2.15.12: kubectl create role
- 6.11.2.15.13: kubectl create rolebinding
- 6.11.2.15.14: kubectl create secret
- 6.11.2.15.15: kubectl create secret docker-registry
- 6.11.2.15.16: kubectl create secret generic
- 6.11.2.15.17: kubectl create secret tls
- 6.11.2.15.18: kubectl create service
- 6.11.2.15.19: kubectl create service clusterip
- 6.11.2.15.20: kubectl create service externalname
- 6.11.2.15.21: kubectl create service loadbalancer
- 6.11.2.15.22: kubectl create service nodeport
- 6.11.2.15.23: kubectl create serviceaccount
- 6.11.2.15.24: kubectl create token
- 6.11.2.16: kubectl debug
- 6.11.2.17: kubectl delete
- 6.11.2.18: kubectl describe
- 6.11.2.19: kubectl diff
- 6.11.2.20: kubectl drain
- 6.11.2.21: kubectl edit
- 6.11.2.22: kubectl events
- 6.11.2.23: kubectl exec
- 6.11.2.24: kubectl explain
- 6.11.2.25: kubectl expose
- 6.11.2.26: kubectl get
- 6.11.2.27: kubectl kustomize
- 6.11.2.28: kubectl label
- 6.11.2.29: kubectl logs
- 6.11.2.30: kubectl options
- 6.11.2.31: kubectl patch
- 6.11.2.32: kubectl plugin
- 6.11.2.32.1: kubectl plugin list
- 6.11.2.33: kubectl port-forward
- 6.11.2.34: kubectl proxy
- 6.11.2.35: kubectl replace
- 6.11.2.36: kubectl rollout
- 6.11.2.36.1: kubectl rollout history
- 6.11.2.36.2: kubectl rollout pause
- 6.11.2.36.3: kubectl rollout restart
- 6.11.2.36.4: kubectl rollout resume
- 6.11.2.36.5: kubectl rollout status
- 6.11.2.36.6: kubectl rollout undo
- 6.11.2.37: kubectl run
- 6.11.2.38: kubectl scale
- 6.11.2.39: kubectl set
- 6.11.2.39.1: kubectl set env
- 6.11.2.39.2: kubectl set image
- 6.11.2.39.3: kubectl set resources
- 6.11.2.39.4: kubectl set selector
- 6.11.2.39.5: kubectl set serviceaccount
- 6.11.2.39.6: kubectl set subject
- 6.11.2.40: kubectl taint
- 6.11.2.41: kubectl top
- 6.11.2.41.1: kubectl top node
- 6.11.2.41.2: kubectl top pod
- 6.11.2.42: kubectl uncordon
- 6.11.2.43: kubectl version
- 6.11.2.44: kubectl wait
- 6.11.3: kubectl 快速参考
- 6.11.4: kubectl 命令
- 6.11.5: kubectl
- 6.11.6: JSONPath 支持
- 6.11.7: 适用于 Docker 用户的 kubectl
- 6.11.8: kubectl 的用法约定
- 6.12: 调试集群
- 6.12.1: 流控
- 6.13: 组件工具
- 6.13.1: 特性门控
- 6.13.2: 特性门控(已移除)
- 6.13.3: kubelet
- 6.13.4: kube-apiserver
- 6.13.5: kube-controller-manager
- 6.13.6: kube-proxy
- 6.13.7: kube-scheduler
- 6.14: 配置 API
- 6.14.1: Event Rate Limit Configuration (v1alpha1)
- 6.14.2: Image Policy API (v1alpha1)
- 6.14.3: kube 配置 (v1)
- 6.14.4: kube-apiserver Admission (v1)
- 6.14.5: kube-apiserver Audit 配置(v1)
- 6.14.6: kube-apiserver 配置 (v1)
- 6.14.7: kube-apiserver 配置 (v1alpha1)
- 6.14.8: kube-apiserver 配置 (v1beta1)
- 6.14.9: kube-controller-manager Configuration (v1alpha1)
- 6.14.10: kube-proxy 配置 (v1alpha1)
- 6.14.11: kube-scheduler 配置 (v1)
- 6.14.12: kubeadm 配置 (v1beta4)
- 6.14.13: kubeadm 配置(v1beta3)
- 6.14.14: Kubelet CredentialProvider (v1)
- 6.14.15: Kubelet 配置 (v1)
- 6.14.16: Kubelet 配置 (v1alpha1)
- 6.14.17: Kubelet 配置 (v1beta1)
- 6.14.18: WebhookAdmission 配置 (v1)
- 6.14.19: 客户端身份认证(Client Authentication) (v1)
- 6.14.20: 客户端身份认证(Client Authentication)(v1beta1)
- 6.15: 外部 API
- 6.15.1: Kubernetes 外部指标 (v1beta1)
- 6.15.2: Kubernetes 指标 (v1beta1)
- 6.15.3: Kubernetes 自定义指标 (v1beta2)
- 6.16: 调度
- 6.17: 其他工具
- 7: 为 Kubernetes 出一份力
- 7.1: 为 Kubernetes 文档出一份力
- 7.2: 提出内容改进建议
- 7.3: 贡献新内容
- 7.3.1: 发起拉取请求(PR)
- 7.3.2: 为发行版本撰写功能特性文档
- 7.3.3: 提交博客和案例分析
- 7.4: 评阅变更
- 7.5: 本地化 Kubernetes 文档
- 7.6: 参与 SIG Docs
- 7.7: 更新参考文档
- 7.7.1: 参考文档快速入门
- 7.7.2: 为上游 Kubernetes 代码库做出贡献
- 7.7.3: 为 Kubernetes API 生成参考文档
- 7.7.4: 为 kubectl 命令集生成参考文档
- 7.7.5: 为指标生成参考文档
- 7.7.6: 为 Kubernetes 组件和工具生成参考文档
- 7.7.7:
- 7.8: 文档样式概述
- 7.9: 进阶贡献
- 7.10: 查看站点分析
- 7.11: 中文本地化样式指南
- 8: 测试页面(中文版)
1 - Kubernetes 文档
1.1 - Kubernetes 文档支持的版本
本网站包含当前版本和之前四个版本的 Kubernetes 文档。
Kubernetes 版本的文档可用性与当前是否支持该版本是分开的。 阅读支持期限,了解官方支持 Kubernetes 的哪些版本,以及支持多长时间。
2 - 入门
本节列出了设置和运行 Kubernetes 的不同方法。 安装 Kubernetes 时,请根据以下条件选择安装类型:易于维护、安全性、可控制性、可用资源以及操作和管理 Kubernetes 集群所需的专业知识。
你可以下载 Kubernetes,在本地机器、云或你自己的数据中心上部署 Kubernetes 集群。
诸如 kube-apiserver 或 kube-proxy 等某些 Kubernetes 组件可以在集群中以容器镜像部署。
建议尽可能将 Kubernetes 组件作为容器镜像运行,并且让 Kubernetes 管理这些组件。 但是运行容器的相关组件 —— 尤其是 kubelet,不在此列。
如果你不想自己管理 Kubernetes 集群,则可以选择托管服务,包括经过认证的平台。 在各种云和裸机环境中,还有其他标准化和定制的解决方案。
学习环境
如果正打算学习 Kubernetes,请使用 Kubernetes 社区支持 或生态系统中的工具在本地计算机上设置 Kubernetes 集群。 请参阅安装工具。
生产环境
在评估生产环境的解决方案时, 请考虑要自己管理 Kubernetes 集群(或相关抽象)的哪些方面,将哪些托付给提供商。
对于你自己管理的集群,官方支持的用于部署 Kubernetes 的工具是 kubeadm。
接下来
- 下载 Kubernetes
- 下载并安装包括 kubectl 在内的工具
- 为新集群选择容器运行时
- 了解集群设置的最佳实践
Kubernetes 的设计是让其控制平面在 Linux 上运行的。 在集群中,你可以在 Linux 或其他操作系统(包括 Windows)上运行应用程序。
2.1 - 学习环境
2.2 - 生产环境
生产质量的 Kubernetes 集群需要规划和准备。 如果你的 Kubernetes 集群是用来运行关键负载的,该集群必须被配置为弹性的(Resilient)。 本页面阐述你在安装生产就绪的集群或将现有集群升级为生产用途时可以遵循的步骤。 如果你已经熟悉生产环境安装,因此只关注一些链接,则可以跳到接下来节。
生产环境考量
通常,一个生产用 Kubernetes 集群环境与个人学习、开发或测试环境所使用的 Kubernetes 相比有更多的需求。 生产环境可能需要被很多用户安全地访问,需要提供一致的可用性,以及能够与需求变化相适配的资源。
在你决定在何处运行你的生产用 Kubernetes 环境(在本地或者在云端), 以及你希望承担或交由他人承担的管理工作量时, 需要考察以下因素如何影响你对 Kubernetes 集群的需求:
- 可用性:一个单机的 Kubernetes 学习环境
具有单点失效特点。创建高可用的集群则意味着需要考虑:
- 将控制面与工作节点分开
- 在多个节点上提供控制面组件的副本
- 为针对集群的 API 服务器 的流量提供负载均衡
- 随着负载的合理需要,提供足够的可用的(或者能够迅速变为可用的)工作节点
- 规模:如果你预期你的生产用 Kubernetes 环境要承受固定量的请求, 你可能可以针对所需要的容量来一次性完成安装。 不过,如果你预期服务请求会随着时间增长,或者因为类似季节或者特殊事件的原因而发生剧烈变化, 你就需要规划如何处理请求上升时对控制面和工作节点的压力,或者如何缩减集群规模以减少未使用资源的消耗。
- 安全性与访问管理:在你自己的学习环境 Kubernetes 集群上,你拥有完全的管理员特权。 但是针对运行着重要工作负载的共享集群,用户账户不止一两个时, 就需要更细粒度的方案来确定谁或者哪些主体可以访问集群资源。 你可以使用基于角色的访问控制(RBAC) 和其他安全机制来确保用户和负载能够访问到所需要的资源, 同时确保工作负载及集群自身仍然是安全的。 你可以通过管理策略和 容器资源 来针对用户和工作负载所可访问的资源设置约束。
在自行构建 Kubernetes 生产环境之前, 请考虑将这一任务的部分或者全部交给云方案承包服务提供商或者其他 Kubernetes 合作伙伴。选项有:
- 无服务:仅是在第三方设备上运行负载,完全不必管理集群本身。 你需要为 CPU 用量、内存和磁盘请求等付费。
- 托管控制面:让供应商决定集群控制面的规模和可用性,并负责打补丁和升级等操作。
- 托管工作节点:配置一个节点池来满足你的需要,由供应商来确保节点始终可用,并在需要的时候完成升级。
- 集成:有一些供应商能够将 Kubernetes 与一些你可能需要的其他服务集成, 这类服务包括存储、容器镜像仓库、身份认证方法以及开发工具等。
无论你是自行构造一个生产用 Kubernetes 集群还是与合作伙伴一起协作, 请审阅下面章节以评估你的需求,因为这关系到你的集群的控制面、工作节点、用户访问以及负载资源。
生产用集群安装
在生产质量的 Kubernetes 集群中,控制面用不同的方式来管理集群和可以分布到多个计算机上的服务。 每个工作节点则代表的是一个可配置来运行 Kubernetes Pod 的实体。
生产用控制面
最简单的 Kubernetes 集群中,整个控制面和工作节点服务都运行在同一台机器上。 你可以通过添加工作节点来提升环境运算能力,正如 Kubernetes 组件示意图所示。 如果只需要集群在很短的一段时间内可用,或者可以在某些事物出现严重问题时直接丢弃, 这种配置可能符合你的需要。
如果你需要一个更为持久的、高可用的集群,那么就需要考虑扩展控制面的方式。 根据设计,运行在一台机器上的单机控制面服务不是高可用的。 如果你认为保持集群的正常运行并需要确保它在出错时可以被修复是很重要的, 可以考虑以下步骤:
- 选择部署工具:你可以使用类似 kubeadm、kops 和 kubespray 这类工具来部署控制面。 参阅使用部署工具安装 Kubernetes 以了解使用这类部署方法来完成生产就绪部署的技巧。 存在不同的容器运行时 可供你的部署采用。
- 管理证书:控制面服务之间的安全通信是通过证书来完成的。 证书是在部署期间自动生成的,或者你也可以使用自己的证书机构来生成它们。 参阅 PKI 证书和需求了解细节。
- 为 API 服务器配置负载均衡:配置负载均衡器来将外部的 API 请求散布给运行在不同节点上的 API 服务实例。 参阅创建外部负载均衡器了解细节。
- 分离并备份 etcd 服务:etcd 服务可以运行于其他控制面服务所在的机器上, 也可以运行在不同的机器上以获得更好的安全性和可用性。 因为 etcd 存储着集群的配置数据,应该经常性地对 etcd 数据库进行备份, 以确保在需要的时候你可以修复该数据库。与配置和使用 etcd 相关的细节可参阅 etcd FAQ。 更多的细节可参阅为 Kubernetes 运维 etcd 集群 和使用 kubeadm 配置高可用的 etcd 集群。
- 创建多控制面系统:为了实现高可用性,控制面不应被限制在一台机器上。 如果控制面服务是使用某 init 服务(例如 systemd)来运行的,每个服务应该至少运行在三台机器上。 不过,将控制面作为服务运行在 Kubernetes Pod 中可以确保你所请求的个数的服务始终保持可用。 调度器应该是可容错的,但不是高可用的。 某些部署工具会安装 Raft 票选算法来对 Kubernetes 服务执行领导者选举。 如果主节点消失,另一个服务会被选中并接手相应服务。
- 跨多个可用区:如果保持你的集群一直可用这点非常重要,可以考虑创建一个跨多个数据中心的集群; 在云环境中,这些数据中心被视为可用区。若干个可用区在一起可构成地理区域。 通过将集群分散到同一区域中的多个可用区内,即使某个可用区不可用,整个集群能够继续工作的机会也大大增加。 更多的细节可参阅跨多个可用区运行。
- 管理演进中的特性:如果你计划长时间保留你的集群,就需要执行一些维护其健康和安全的任务。 例如,如果你采用 kubeadm 安装的集群, 则有一些可以帮助你完成 证书管理 和升级 kubeadm 集群 的指令。 参见管理集群了解一个 Kubernetes 管理任务的较长列表。
如要了解运行控制面服务时可使用的选项,可参阅 kube-apiserver、 kube-controller-manager 和 kube-scheduler 组件参考页面。 如要了解高可用控制面的例子,可参阅高可用拓扑结构选项、 使用 kubeadm 创建高可用集群 以及为 Kubernetes 运维 etcd 集群。 关于制定 etcd 备份计划,可参阅对 etcd 集群执行备份。
生产用工作节点
生产质量的工作负载需要是弹性的;它们所依赖的其他组件(例如 CoreDNS)也需要是弹性的。 无论你是自行管理控制面还是让云供应商来管理,你都需要考虑如何管理工作节点 (有时也简称为节点)。
- 配置节点:节点可以是物理机或者虚拟机。如果你希望自行创建和管理节点,
你可以安装一个受支持的操作系统,之后添加并运行合适的节点服务。考虑:
- 在安装节点时要通过配置适当的内存、CPU 和磁盘读写速率、存储容量来满足你的负载的需求。
- 是否通用的计算机系统即足够,还是你有负载需要使用 GPU 处理器、Windows 节点或者 VM 隔离。
- 验证节点:参阅验证节点配置以了解如何确保节点满足加入到 Kubernetes 集群的需求。
- 添加节点到集群中:如果你自行管理你的集群,你可以通过安装配置你的机器, 之后或者手动加入集群,或者让它们自动注册到集群的 API 服务器。 参阅节点节,了解如何配置 Kubernetes 以便以这些方式来添加节点。
- 扩缩节点:制定一个扩充集群容量的规划,你的集群最终会需要这一能力。 参阅大规模集群考察事项 以确定你所需要的节点数; 这一规模是基于你要运行的 Pod 和容器个数来确定的。 如果你自行管理集群节点,这可能意味着要购买和安装你自己的物理设备。
- 节点自动扩缩容:查阅集群自动扩缩容, 了解可以自动管理节点的工具及其提供的能力。
- 安装节点健康检查:对于重要的工作负载,你会希望确保节点以及在节点上运行的 Pod 处于健康状态。 通过使用 Node Problem Detector, 你可以确保你的节点是健康的。
生产级用户环境
在生产环境中,情况可能不再是你或者一小组人在访问集群,而是几十上百人需要访问集群。 在学习环境或者平台原型环境中,你可能具有一个可以执行任何操作的管理账号。 在生产环境中,你会需要对不同名字空间具有不同访问权限级别的很多账号。
建立一个生产级别的集群意味着你需要决定如何有选择地允许其他用户访问集群。 具体而言,你需要选择验证尝试访问集群的人的身份标识(身份认证), 并确定他们是否被许可执行他们所请求的操作(鉴权):
- 认证(Authentication):API 服务器可以使用客户端证书、持有者令牌、 身份认证代理或者 HTTP 基本认证机制来完成身份认证操作。 你可以选择你要使用的认证方法。通过使用插件, API 服务器可以充分利用你所在组织的现有身份认证方法, 例如 LDAP 或者 Kerberos。 关于认证 Kubernetes 用户身份的不同方法的描述, 可参阅身份认证。
- 鉴权(Authorization):当你准备为一般用户执行权限判定时,
你可能会需要在 RBAC 和 ABAC 鉴权机制之间做出选择。
参阅鉴权概述,
了解对用户账户(以及访问你的集群的服务账户)执行鉴权的不同模式。
- 基于角色的访问控制(RBAC): 让你通过为通过身份认证的用户授权特定的许可集合来控制集群访问。 访问许可可以针对某特定名字空间(Role)或者针对整个集群(ClusterRole)。 通过使用 RoleBinding 和 ClusterRoleBinding 对象,这些访问许可可以被关联到特定的用户身上。
作为在你的生产用 Kubernetes 集群中安装身份认证和鉴权机制的负责人,要考虑的事情如下:
- 设置鉴权模式:当 Kubernetes API 服务器(kube-apiserver)启动时,
所支持的鉴权模式必须使用
--authorization-mode
标志配置。 例如,kube-apiserver.yaml
(位于/etc/kubernetes/manifests
下)中对应的标志可以设置为Node,RBAC
。 这样就会针对已完成身份认证的请求执行 Node 和 RBAC 鉴权。
- 创建用户证书和角色绑定(RBAC):如果你在使用 RBAC 鉴权,用户可以创建由集群 CA 签名的 CertificateSigningRequest(CSR)。接下来你就可以将 Role 和 ClusterRole 绑定到每个用户身上。 参阅证书签名请求了解细节。
- 创建组合属性的策略(ABAC):如果你在使用 ABAC 鉴权, 你可以设置属性组合以构造策略对所选用户或用户组执行鉴权, 判定他们是否可访问特定的资源(例如 Pod)、名字空间或者 apiGroup。 进一步的详细信息可参阅示例。
- 考虑准入控制器:针对指向 API 服务器的请求的其他鉴权形式还包括 Webhook 令牌认证。 Webhook 和其他特殊的鉴权类型需要通过向 API 服务器添加准入控制器来启用。
为负载资源设置约束
生产环境负载的需求可能对 Kubernetes 的控制面内外造成压力。 在针对你的集群的负载执行配置时,要考虑以下条目:
- 设置名字空间限制:为每个名字空间的内存和 CPU 设置配额。 参阅管理内存、CPU 和 API 资源以了解细节。 你也可以设置层次化名字空间来继承这类约束。
- 为 DNS 请求做准备:如果你希望工作负载能够完成大规模扩展,你的 DNS 服务也必须能够扩大规模。 参阅自动扩缩集群中 DNS 服务。
- 创建额外的服务账户:用户账户决定用户可以在集群上执行的操作,服务账号则定义的是在特定名字空间中
Pod 的访问权限。默认情况下,Pod 使用所在名字空间中的 default 服务账号。
参阅管理服务账号以了解如何创建新的服务账号。
例如,你可能需要:
- 为 Pod 添加 Secret,以便 Pod 能够从某特定的容器镜像仓库拉取镜像。 参阅为 Pod 配置服务账号以获得示例。
- 为服务账号设置 RBAC 访问许可。参阅服务账号访问许可了解细节。
接下来
- 决定你是想自行构造自己的生产用 Kubernetes, 还是从某可用的云服务外包厂商或 Kubernetes 合作伙伴获得集群。
- 如果你决定自行构造集群,则需要规划如何处理证书并为类似 etcd 和 API 服务器这些功能组件配置高可用能力。
2.2.1 - 容器运行时
你需要在集群内每个节点上安装一个 容器运行时 以使 Pod 可以运行在上面。本文概述了所涉及的内容并描述了与节点设置相关的任务。
Kubernetes 1.31 要求你使用符合容器运行时接口(CRI)的运行时。
有关详细信息,请参阅 CRI 版本支持。 本页简要介绍在 Kubernetes 中几个常见的容器运行时的用法。
说明:
v1.24 之前的 Kubernetes 版本直接集成了 Docker Engine 的一个组件,名为 dockershim。 这种特殊的直接整合不再是 Kubernetes 的一部分 (这次删除被作为 v1.20 发行版本的一部分宣布)。
你可以阅读检查 Dockershim 移除是否会影响你以了解此删除可能会如何影响你。 要了解如何使用 dockershim 进行迁移, 请参阅从 dockershim 迁移。
如果你正在运行 v1.31 以外的 Kubernetes 版本,查看对应版本的文档。
安装和配置先决条件
默认情况下,Linux 内核不允许 IPv4 数据包在接口之间路由。 大多数 Kubernetes 集群网络实现都会更改此设置(如果需要),但有些人可能希望管理员为他们执行此操作。 (有些人可能还期望设置其他 sysctl 参数、加载内核模块等;请参阅你的特定网络实施的文档。)
启用 IPv4 数据包转发
手动启用 IPv4 数据包转发:
# 设置所需的 sysctl 参数,参数在重新启动后保持不变
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.ipv4.ip_forward = 1
EOF
# 应用 sysctl 参数而不重新启动
sudo sysctl --system
使用以下命令验证 net.ipv4.ip_forward
是否设置为 1:
sysctl net.ipv4.ip_forward
cgroup 驱动
在 Linux 上,控制组(CGroup)用于限制分配给进程的资源。
kubelet 和底层容器运行时都需要对接控制组来强制执行 为 Pod 和容器管理资源 并为诸如 CPU、内存这类资源设置请求和限制。若要对接控制组,kubelet 和容器运行时需要使用一个 cgroup 驱动。 关键的一点是 kubelet 和容器运行时需使用相同的 cgroup 驱动并且采用相同的配置。
可用的 cgroup 驱动有两个:
cgroupfs 驱动
cgroupfs
驱动是 kubelet 中默认的 cgroup 驱动。
当使用 cgroupfs
驱动时, kubelet 和容器运行时将直接对接 cgroup 文件系统来配置 cgroup。
当 systemd 是初始化系统时,
不 推荐使用 cgroupfs
驱动,因为 systemd 期望系统上只有一个 cgroup 管理器。
此外,如果你使用 cgroup v2,
则应用 systemd
cgroup 驱动取代 cgroupfs
。
systemd cgroup 驱动
当某个 Linux 系统发行版使用 systemd
作为其初始化系统时,初始化进程会生成并使用一个 root 控制组(cgroup
),并充当 cgroup 管理器。
systemd 与 cgroup 集成紧密,并将为每个 systemd 单元分配一个 cgroup。
因此,如果你 systemd
用作初始化系统,同时使用 cgroupfs
驱动,则系统中会存在两个不同的 cgroup 管理器。
同时存在两个 cgroup 管理器将造成系统中针对可用的资源和使用中的资源出现两个视图。某些情况下,
将 kubelet 和容器运行时配置为使用 cgroupfs
、但为剩余的进程使用 systemd
的那些节点将在资源压力增大时变得不稳定。
当 systemd 是选定的初始化系统时,缓解这个不稳定问题的方法是针对 kubelet 和容器运行时将
systemd
用作 cgroup 驱动。
要将 systemd
设置为 cgroup 驱动,需编辑 KubeletConfiguration
的 cgroupDriver
选项,并将其设置为 systemd
。例如:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
...
cgroupDriver: systemd
说明:
从 v1.22 开始,在使用 kubeadm 创建集群时,如果用户没有在
KubeletConfiguration
下设置 cgroupDriver
字段,kubeadm 默认使用 systemd
。
如果你将 systemd
配置为 kubelet 的 cgroup 驱动,你也必须将 systemd
配置为容器运行时的 cgroup 驱动。参阅容器运行时文档,了解指示说明。例如:
在 Kubernetes 1.31 中,启用 KubeletCgroupDriverFromCRI
特性门控结合支持
RuntimeConfig
CRI RPC 的容器运行时,kubelet 会自动从运行时检测适当的 Cgroup
驱动程序,并忽略 kubelet 配置中的 cgroupDriver
设置。
注意:
注意:更改已加入集群的节点的 cgroup 驱动是一项敏感的操作。 如果 kubelet 已经使用某 cgroup 驱动的语义创建了 Pod,更改运行时以使用别的 cgroup 驱动,当为现有 Pod 重新创建 PodSandbox 时会产生错误。 重启 kubelet 也可能无法解决此类问题。
如果你有切实可行的自动化方案,使用其他已更新配置的节点来替换该节点, 或者使用自动化方案来重新安装。
将 kubeadm 管理的集群迁移到 systemd
驱动
如果你希望将现有的由 kubeadm 管理的集群迁移到 systemd
cgroup 驱动,
请按照配置 cgroup 驱动操作。
CRI 版本支持
你的容器运行时必须至少支持 v1alpha2 版本的容器运行时接口。
Kubernetes 从 1.26 版本开始仅适用于 v1 版本的容器运行时(CRI)API。早期版本默认为 v1 版本, 但是如果容器运行时不支持 v1 版本的 API, 则 kubelet 会回退到使用(已弃用的)v1alpha2 版本的 API。
容器运行时
containerd
本节概述了使用 containerd 作为 CRI 运行时的必要步骤。
要在系统上安装 containerd,请按照开始使用 containerd
的说明进行操作。创建有效的 config.toml
配置文件后返回此步骤。
你可以在路径 /etc/containerd/config.toml
下找到此文件。
你可以在路径 C:\Program Files\containerd\config.toml
下找到此文件。
在 Linux 上,containerd 的默认 CRI 套接字是 /run/containerd/containerd.sock
。
在 Windows 上,默认 CRI 端点是 npipe://./pipe/containerd-containerd
。
配置 systemd
cgroup 驱动
结合 runc
使用 systemd
cgroup 驱动,在 /etc/containerd/config.toml
中设置:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
...
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]
SystemdCgroup = true
如果你使用 cgroup v2,则推荐 systemd
cgroup 驱动。
说明:
如果你从软件包(例如,RPM 或者 .deb
)中安装 containerd,你可能会发现其中默认禁止了
CRI 集成插件。
你需要启用 CRI 支持才能在 Kubernetes 集群中使用 containerd。
要确保 cri
没有出现在 /etc/containerd/config.toml
文件中 disabled_plugins
列表内。如果你更改了这个文件,也请记得要重启 containerd
。
如果你在初次安装集群后或安装 CNI 后遇到容器崩溃循环,则随软件包提供的 containerd
配置可能包含不兼容的配置参数。考虑按照
getting-started.md
中指定的 containerd config default > /etc/containerd/config.toml
重置 containerd
配置,然后相应地设置上述配置参数。
如果你应用此更改,请确保重新启动 containerd:
sudo systemctl restart containerd
当使用 kubeadm 时,请手动配置 kubelet 的 cgroup 驱动。
在 Kubernetes v1.28 中,你可以启用 Cgroup 驱动程序的自动检测的 Alpha 级别特性。 详情参阅 systemd cgroup 驱动。
重载沙箱(pause)镜像
在你的 containerd 配置中, 你可以通过设置以下选项重载沙箱镜像:
[plugins."io.containerd.grpc.v1.cri"]
sandbox_image = "registry.k8s.io/pause:3.2"
一旦你更新了这个配置文件,可能就同样需要重启 containerd
:systemctl restart containerd
。
CRI-O
本节包含安装 CRI-O 作为容器运行时的必要步骤。
要安装 CRI-O,请按照 CRI-O 安装说明执行操作。
cgroup 驱动
CRI-O 默认使用 systemd cgroup 驱动,这对你来说可能工作得很好。
要切换到 cgroupfs
cgroup 驱动,请编辑 /etc/crio/crio.conf
或在
/etc/crio/crio.conf.d/02-cgroup-manager.conf
中放置一个插入式配置,例如:
[crio.runtime]
conmon_cgroup = "pod"
cgroup_manager = "cgroupfs"
你还应该注意当使用 CRI-O 时,并且 CRI-O 的 cgroup 设置为 cgroupfs
时,必须将 conmon_cgroup
设置为值 pod
。
通常需要保持 kubelet 的 cgroup 驱动配置(通常通过 kubeadm 完成)和 CRI-O 同步。
在 Kubernetes v1.28 中,你可以启用 Cgroup 驱动程序的自动检测的 Alpha 级别特性。 详情参阅 systemd cgroup 驱动。
对于 CRI-O,CRI 套接字默认为 /var/run/crio/crio.sock
。
重载沙箱(pause)镜像
在你的 CRI-O 配置中, 你可以设置以下配置值:
[crio.image]
pause_image="registry.k8s.io/pause:3.6"
这一设置选项支持动态配置重加载来应用所做变更:systemctl reload crio
。
也可以通过向 crio
进程发送 SIGHUP
信号来实现。
Docker Engine
说明:
以下操作假设你使用 cri-dockerd
适配器来将
Docker Engine 与 Kubernetes 集成。
- 在你的每个节点上,遵循安装 Docker Engine 指南为你的 Linux 发行版安装 Docker。
- 请按照文档中的安装部分指示来安装
cri-dockerd
。
对于 cri-dockerd
,默认情况下,CRI 套接字是 /run/cri-dockerd.sock
。
Mirantis 容器运行时
Mirantis Container Runtime (MCR)
是一种商用容器运行时,以前称为 Docker 企业版。
你可以使用 MCR 中包含的开源 cri-dockerd
组件将 Mirantis Container Runtime 与 Kubernetes 一起使用。
要了解有关如何安装 Mirantis Container Runtime 的更多信息, 请访问 MCR 部署指南。
检查名为 cri-docker.socket
的 systemd 单元以找出 CRI 套接字的路径。
重载沙箱(pause)镜像
cri-dockerd
适配器能够接受指定用作 Pod 的基础容器的容器镜像(“pause 镜像”)作为命令行参数。
要使用的命令行参数是 --pod-infra-container-image
。
接下来
除了容器运行时,你的集群还需要有效的网络插件。
2.2.2 - 使用部署工具安装 Kubernetes
搭建你自己的 Kubernetes 生产集群有许多方法和工具。例如:
- Cluster API: 一个 Kubernetes 子项目,专注于提供声明式 API 和工具,以简化多个 Kubernetes 集群的配置、升级和操作。
- kubespray: 提供了 Ansible Playbook、 清单(inventory)、 制备工具和通用 OS/Kubernetes 集群配置管理任务领域的知识。 你可以通过 Slack 频道 #kubespray 联系此社区。
2.2.2.1 - 使用 kubeadm 引导集群
2.2.2.1.1 - 安装 kubeadm
本页面显示如何安装 kubeadm
工具箱。
有关在执行此安装过程后如何使用 kubeadm 创建集群的信息,
请参见使用 kubeadm 创建集群。
此 installation guide 适用于Kubernetes v1.31。如果你想使用其他 Kubernetes 版本,请参考以下页面:
准备开始
- 一台兼容的 Linux 主机。Kubernetes 项目为基于 Debian 和 Red Hat 的 Linux 发行版以及一些不提供包管理器的发行版提供通用的指令。
- 每台机器 2 GB 或更多的 RAM(如果少于这个数字将会影响你应用的运行内存)。
- 控制平面机器需要 CPU 2 核心或更多。
- 集群中的所有机器的网络彼此均能相互连接(公网和内网都可以)。
- 节点之中不可以有重复的主机名、MAC 地址或 product_uuid。请参见这里了解更多详细信息。
- 开启机器上的某些端口。请参见这里了解更多详细信息。
- 交换分区的配置。kubelet 的默认行为是在节点上检测到交换内存时无法启动。
更多细节参阅交换内存管理。
- 如果 kubelet 未被正确配置使用交换分区,则你必须禁用交换分区。
例如,
sudo swapoff -a
将暂时禁用交换分区。要使此更改在重启后保持不变,请确保在如/etc/fstab
、systemd.swap
等配置文件中禁用交换分区,具体取决于你的系统如何配置。
- 如果 kubelet 未被正确配置使用交换分区,则你必须禁用交换分区。
例如,
说明:
kubeadm
的安装是通过使用动态链接的二进制文件完成的,安装时假设你的目标系统提供 glibc
。
这个假设在许多 Linux 发行版(包括 Debian、Ubuntu、Fedora、CentOS 等)上是合理的,
但对于不包含默认 glibc
的自定义和轻量级发行版(如 Alpine Linux),情况并非总是如此。
预期的情况是,发行版要么包含 glibc
,
要么提供了一个兼容层以提供所需的符号。
确保每个节点上 MAC 地址和 product_uuid 的唯一性
- 你可以使用命令
ip link
或ifconfig -a
来获取网络接口的 MAC 地址 - 可以使用
sudo cat /sys/class/dmi/id/product_uuid
命令对 product_uuid 校验
一般来讲,硬件设备会拥有唯一的地址,但是有些虚拟机的地址可能会重复。 Kubernetes 使用这些值来唯一确定集群中的节点。 如果这些值在每个节点上不唯一,可能会导致安装失败。
检查网络适配器
如果你有一个以上的网络适配器,同时你的 Kubernetes 组件通过默认路由不可达,我们建议你预先添加 IP 路由规则, 这样 Kubernetes 集群就可以通过对应的适配器完成连接。
检查所需端口
启用这些必要的端口后才能使 Kubernetes 的各组件相互通信。 可以使用 netcat 之类的工具来检查端口是否开放,例如:
nc 127.0.0.1 6443 -v
你使用的 Pod 网络插件 (详见后续章节) 也可能需要开启某些特定端口。 由于各个 Pod 网络插件的功能都有所不同,请参阅他们各自文档中对端口的要求。
交换分区的配置
kubelet 的默认行为是在节点上检测到交换内存时无法启动。 这意味着要么禁用交换(swap)功能,要么让 kubelet 容忍交换。
- 若需允许交换分区(swap),请在 kubelet 配置文件中添加
failSwapOn: false
,或通过命令行参数指定。 注意:即使设置了failSwapOn: false
,工作负载默认情况下仍无法访问交换空间。 可以通过在 kubelet 配置文件中设置swapBehavior
来修改此设置。若要使用交换空间, 请设置swapBehavior
的值,这个值不能是默认的NoSwap
。 更多细节参阅交换内存管理。 - 要禁用交换分区(swap),可以使用命令
sudo swapoff -a
暂时关闭交换分区功能。 要使此更改在重启后仍然生效,请确保在系统的配置文件(如/etc/fstab
或systemd.swap
)中禁用交换功能, 具体取决于你的系统配置方式。
安装容器运行时
为了在 Pod 中运行容器,Kubernetes 使用 容器运行时(Container Runtime)。
默认情况下,Kubernetes 使用 容器运行时接口(Container Runtime Interface,CRI) 来与你所选择的容器运行时交互。
如果你不指定运行时,kubeadm 会自动尝试通过扫描已知的端点列表来检测已安装的容器运行时。
如果检测到有多个或者没有容器运行时,kubeadm 将抛出一个错误并要求你指定一个想要使用的运行时。
参阅容器运行时 以了解更多信息。
说明:
Docker Engine 没有实现 CRI, 而这是容器运行时在 Kubernetes 中工作所需要的。 为此,必须安装一个额外的服务 cri-dockerd。 cri-dockerd 是一个基于传统的内置 Docker 引擎支持的项目, 它在 1.24 版本从 kubelet 中移除。
下面的表格包括被支持的操作系统的已知端点。
运行时 | Unix 域套接字 |
---|---|
containerd | unix:///var/run/containerd/containerd.sock |
CRI-O | unix:///var/run/crio/crio.sock |
Docker Engine(使用 cri-dockerd) | unix:///var/run/cri-dockerd.sock |
运行时 | Windows 命名管道路径 |
---|---|
containerd | npipe:////./pipe/containerd-containerd |
Docker Engine(使用 cri-dockerd) | npipe:////./pipe/cri-dockerd |
安装 kubeadm、kubelet 和 kubectl
你需要在每台机器上安装以下的软件包:
kubeadm
:用来初始化集群的指令。kubelet
:在集群中的每个节点上用来启动 Pod 和容器等。kubectl
:用来与集群通信的命令行工具。
kubeadm 不能帮你安装或者管理 kubelet
或 kubectl
,
所以你需要确保它们与通过 kubeadm 安装的控制平面的版本相匹配。
如果不这样做,则存在发生版本偏差的风险,可能会导致一些预料之外的错误和问题。
然而,控制平面与 kubelet 之间可以存在一个次要版本的偏差,但 kubelet
的版本不可以超过 API 服务器的版本。
例如,1.7.0 版本的 kubelet 可以完全兼容 1.8.0 版本的 API 服务器,反之则不可以。
有关安装 kubectl
的信息,请参阅安装和设置 kubectl 文档。
警告:
这些指南不包括系统升级时使用的所有 Kubernetes 程序包。这是因为 kubeadm 和 Kubernetes 有特殊的升级注意事项。
关于版本偏差的更多信息,请参阅以下文档:
- Kubernetes 版本与版本间的偏差策略
- kubeadm 特定的版本偏差策略
apt.kubernetes.io
和yum.kubernetes.io
)。
强烈建议使用托管在 pkgs.k8s.io
上的新软件包仓库来安装 2023 年 9 月 13 日之后发布的 Kubernetes 版本。
旧版软件包仓库已被弃用,其内容可能在未来的任何时间被删除,恕不另行通知。新的软件包仓库提供了从 Kubernetes v1.24.0 版本开始的下载。说明:
每个 Kubernetes 小版本都有一个专用的软件包仓库。 如果你想安装 1.31 以外的次要版本,请参阅所需次要版本的安装指南。
以下指令适用于 Kubernetes 1.31.
更新
apt
包索引并安装使用 Kubernetesapt
仓库所需要的包:sudo apt-get update # apt-transport-https 可能是一个虚拟包(dummy package);如果是的话,你可以跳过安装这个包 sudo apt-get install -y apt-transport-https ca-certificates curl gpg
下载用于 Kubernetes 软件包仓库的公共签名密钥。所有仓库都使用相同的签名密钥,因此你可以忽略URL中的版本:
# 如果 `/etc/apt/keyrings` 目录不存在,则应在 curl 命令之前创建它,请阅读下面的注释。 # sudo mkdir -p -m 755 /etc/apt/keyrings curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.31/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
说明:
在低于 Debian 12 和 Ubuntu 22.04 的发行版本中,/etc/apt/keyrings
默认不存在。
应在 curl 命令之前创建它。
添加 Kubernetes
apt
仓库。 请注意,此仓库仅包含适用于 Kubernetes 1.31 的软件包; 对于其他 Kubernetes 次要版本,则需要更改 URL 中的 Kubernetes 次要版本以匹配你所需的次要版本 (你还应该检查正在阅读的安装文档是否为你计划安装的 Kubernetes 版本的文档)。# 此操作会覆盖 /etc/apt/sources.list.d/kubernetes.list 中现存的所有配置。 echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.31/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
更新
apt
包索引,安装 kubelet、kubeadm 和 kubectl,并锁定其版本:sudo apt-get update sudo apt-get install -y kubelet kubeadm kubectl sudo apt-mark hold kubelet kubeadm kubectl
将 SELinux 设置为
permissive
模式:以下指令适用于 Kubernetes 1.31。
# 将 SELinux 设置为 permissive 模式(相当于将其禁用) sudo setenforce 0 sudo sed -i 's/^SELINUX=enforcing$/SELINUX=permissive/' /etc/selinux/config
注意:
- 通过运行命令
setenforce 0
和sed ...
将 SELinux 设置为 permissive 模式相当于将其禁用。 这是允许容器访问主机文件系统所必需的,例如,某些容器网络插件需要这一能力。 你必须这么做,直到 kubelet 改进其对 SELinux 的支持。 - 如果你知道如何配置 SELinux 则可以将其保持启用状态,但可能需要设定部分 kubeadm 不支持的配置。
添加 Kubernetes 的
yum
仓库。在仓库定义中的exclude
参数确保了与 Kubernetes 相关的软件包在运行yum update
时不会升级,因为升级 Kubernetes 需要遵循特定的过程。请注意,此仓库仅包含适用于 Kubernetes 1.31 的软件包; 对于其他 Kubernetes 次要版本,则需要更改 URL 中的 Kubernetes 次要版本以匹配你所需的次要版本 (你还应该检查正在阅读的安装文档是否为你计划安装的 Kubernetes 版本的文档)。# 此操作会覆盖 /etc/yum.repos.d/kubernetes.repo 中现存的所有配置 cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo [kubernetes] name=Kubernetes baseurl=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/ enabled=1 gpgcheck=1 gpgkey=https://pkgs.k8s.io/core:/stable:/v1.31/rpm/repodata/repomd.xml.key exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni EOF
安装 kubelet、kubeadm 和 kubectl,并启用 kubelet 以确保它在启动时自动启动:
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes sudo systemctl enable --now kubelet
安装 CNI 插件(大多数 Pod 网络都需要):
CNI_PLUGINS_VERSION="v1.3.0"
ARCH="amd64"
DEST="/opt/cni/bin"
sudo mkdir -p "$DEST"
curl -L "https://github.com/containernetworking/plugins/releases/download/${CNI_PLUGINS_VERSION}/cni-plugins-linux-${ARCH}-${CNI_PLUGINS_VERSION}.tgz" | sudo tar -C "$DEST" -xz
定义要下载命令文件的目录:
说明:
DOWNLOAD_DIR
变量必须被设置为一个可写入的目录。
如果你在运行 Flatcar Container Linux,可设置 DOWNLOAD_DIR="/opt/bin"
。
DOWNLOAD_DIR="/usr/local/bin"
sudo mkdir -p "$DOWNLOAD_DIR"
可以选择安装 crictl(与容器运行时接口 (CRI) 交互时必需,但对 kubeadm 来说是可选的):
CRICTL_VERSION="v1.31.0"
ARCH="amd64"
curl -L "https://github.com/kubernetes-sigs/cri-tools/releases/download/${CRICTL_VERSION}/crictl-${CRICTL_VERSION}-linux-${ARCH}.tar.gz" | sudo tar -C $DOWNLOAD_DIR -xz
安装 kubeadm
、kubelet
、kubectl
并添加 kubelet
系统服务:
RELEASE="$(curl -sSL https://dl.k8s.io/release/stable.txt)"
ARCH="amd64"
cd $DOWNLOAD_DIR
sudo curl -L --remote-name-all https://dl.k8s.io/release/${RELEASE}/bin/linux/${ARCH}/{kubeadm,kubelet}
sudo chmod +x {kubeadm,kubelet}
RELEASE_VERSION="v0.16.2"
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/krel/templates/latest/kubelet/kubelet.service" | sed "s:/usr/bin:${DOWNLOAD_DIR}:g" | sudo tee /usr/lib/systemd/system/kubelet.service
sudo mkdir -p /usr/lib/systemd/system/kubelet.service.d
curl -sSL "https://raw.githubusercontent.com/kubernetes/release/${RELEASE_VERSION}/cmd/krel/templates/latest/kubeadm/10-kubeadm.conf" | sed "s:/usr/bin:${DOWNLOAD_DIR}:g" | sudo tee /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
说明:
对于默认不包括 glibc
的 Linux 发行版,请参阅开始之前一节的注释。
请参照安装工具页面的说明安装 kubelet
。
激活并启动 kubelet
:
systemctl enable --now kubelet
说明:
Flatcar Container Linux 发行版会将 /usr/
目录挂载为一个只读文件系统。
在启动引导你的集群之前,你需要执行一些额外的操作来配置一个可写入的目录。
参见 kubeadm 故障排查指南
以了解如何配置一个可写入的目录。
kubelet 现在每隔几秒就会重启,因为它陷入了一个等待 kubeadm 指令的死循环。
配置 cgroup 驱动程序
容器运行时和 kubelet 都具有名字为 "cgroup driver" 的属性,该属性对于在 Linux 机器上管理 CGroups 而言非常重要。
故障排查
如果你在使用 kubeadm 时遇到困难, 请参阅我们的故障排查文档。
接下来
2.2.2.1.2 - 对 kubeadm 进行故障排查
与任何程序一样,你可能会在安装或者运行 kubeadm 时遇到错误。 本文列举了一些常见的故障场景,并提供可帮助你理解和解决这些问题的步骤。
如果你的问题未在下面列出,请执行以下步骤:
如果你认为问题是 kubeadm 的错误:
- 转到 github.com/kubernetes/kubeadm 并搜索存在的问题。
- 如果没有问题,请 打开 并遵循问题模板。
如果你对 kubeadm 的工作方式有疑问,可以在 Slack 上的
#kubeadm
频道提问, 或者在 StackOverflow 上提问。 请加入相关标签,例如#kubernetes
和#kubeadm
,这样其他人可以帮助你。
由于缺少 RBAC,无法将 v1.18 Node 加入 v1.17 集群
自从 v1.18 后,如果集群中已存在同名 Node,kubeadm 将禁止 Node 加入集群。 这需要为 bootstrap-token 用户添加 RBAC 才能 GET Node 对象。
但这会导致一个问题,v1.18 的 kubeadm join
无法加入由 kubeadm v1.17 创建的集群。
要解决此问题,你有两种选择:
使用 kubeadm v1.18 在控制平面节点上执行 kubeadm init phase bootstrap-token
。
请注意,这也会启用 bootstrap-token 的其余权限。
或者,也可以使用 kubectl apply -f ...
手动应用以下 RBAC:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: kubeadm:get-nodes
rules:
- apiGroups:
- ""
resources:
- nodes
verbs:
- get
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: kubeadm:get-nodes
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: kubeadm:get-nodes
subjects:
- apiGroup: rbac.authorization.k8s.io
kind: Group
name: system:bootstrappers:kubeadm:default-node-token
在安装过程中没有找到 ebtables
或者其他类似的可执行文件
如果在运行 kubeadm init
命令时,遇到以下的警告
[preflight] WARNING: ebtables not found in system path
[preflight] WARNING: ethtool not found in system path
那么或许在你的节点上缺失 ebtables
、ethtool
或者类似的可执行文件。
你可以使用以下命令安装它们:
- 对于 Ubuntu/Debian 用户,运行
apt install ebtables ethtool
命令。 - 对于 CentOS/Fedora 用户,运行
yum install ebtables ethtool
命令。
在安装过程中,kubeadm 一直等待控制平面就绪
如果你注意到 kubeadm init
在打印以下行后挂起:
[apiclient] Created API client, waiting for the control plane to become ready
这可能是由许多问题引起的。最常见的是:
- 网络连接问题。在继续之前,请检查你的计算机是否具有全部联通的网络连接。
- 容器运行时的 cgroup 驱动不同于 kubelet 使用的 cgroup 驱动。要了解如何正确配置 cgroup 驱动, 请参阅配置 cgroup 驱动。
- 控制平面上的 Docker 容器持续进入崩溃状态或(因其他原因)挂起。你可以运行
docker ps
命令来检查以及docker logs
命令来检视每个容器的运行日志。 对于其他容器运行时,请参阅使用 crictl 对 Kubernetes 节点进行调试。
当删除托管容器时 kubeadm 阻塞
如果容器运行时停止并且未删除 Kubernetes 所管理的容器,可能发生以下情况:
sudo kubeadm reset
[preflight] Running pre-flight checks
[reset] Stopping the kubelet service
[reset] Unmounting mounted directories in "/var/lib/kubelet"
[reset] Removing kubernetes-managed containers
(block)
一个可行的解决方案是重新启动 Docker 服务,然后重新运行 kubeadm reset
:
你也可以使用 crictl
来调试容器运行时的状态。
参见使用 CRICTL 调试 Kubernetes 节点。
Pod 处于 RunContainerError
、CrashLoopBackOff
或者 Error
状态
在 kubeadm init
命令运行后,系统中不应该有 Pod 处于这类状态。
在
kubeadm init
命令执行完后,如果有 Pod 处于这些状态之一,请在 kubeadm 仓库提起一个 issue。coredns
(或者kube-dns
) 应该处于Pending
状态, 直到你部署了网络插件为止。如果在部署完网络插件之后,有 Pod 处于
RunContainerError
、CrashLoopBackOff
或Error
状态之一,并且coredns
(或者kube-dns
)仍处于Pending
状态, 那很可能是你安装的网络插件由于某种原因无法工作。你或许需要授予它更多的 RBAC 特权或使用较新的版本。请在 Pod Network 提供商的问题跟踪器中提交问题, 然后在此处分类问题。
coredns
停滞在 Pending
状态
这一行为是预期之中的,因为系统就是这么设计的。kubeadm 的网络供应商是中立的,
因此管理员应该选择安装 Pod 的网络插件。
你必须完成 Pod 的网络配置,然后才能完全部署 CoreDNS。
在网络被配置好之前,DNS 组件会一直处于 Pending
状态。
HostPort
服务无法工作
此 HostPort
和 HostIP
功能是否可用取决于你的 Pod 网络配置。请联系 Pod 网络插件的作者,
以确认 HostPort
和 HostIP
功能是否可用。
已验证 Calico、Canal 和 Flannel CNI 驱动程序支持 HostPort。
有关更多信息,请参考 CNI portmap 文档.
如果你的网络提供商不支持 portmap CNI 插件,你或许需要使用
NodePort 服务的功能或者使用
HostNetwork=true
。
无法通过其服务 IP 访问 Pod
许多网络附加组件尚未启用 hairpin 模式 该模式允许 Pod 通过其服务 IP 进行访问。这是与 CNI 有关的问题。 请与网络附加组件提供商联系,以获取他们所提供的 hairpin 模式的最新状态。
如果你正在使用 VirtualBox (直接使用或者通过 Vagrant 使用),你需要 确保
hostname -i
返回一个可路由的 IP 地址。默认情况下,第一个接口连接不能路由的仅主机网络。 解决方法是修改/etc/hosts
,请参考示例 Vagrantfile。
TLS 证书错误
以下错误说明证书可能不匹配。
# kubectl get pods
Unable to connect to the server: x509: certificate signed by unknown authority (possibly because of "crypto/rsa: verification error" while trying to verify candidate authority certificate "kubernetes")
验证
$HOME/.kube/config
文件是否包含有效证书, 并在必要时重新生成证书。在 kubeconfig 文件中的证书是 base64 编码的。 该base64 --decode
命令可以用来解码证书,openssl x509 -text -noout
命令可以用于查看证书信息。使用如下方法取消设置
KUBECONFIG
环境变量的值:unset KUBECONFIG
或者将其设置为默认的
KUBECONFIG
位置:export KUBECONFIG=/etc/kubernetes/admin.conf
另一个方法是覆盖
kubeconfig
的现有用户 "管理员":mv $HOME/.kube $HOME/.kube.bak mkdir $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config
Kubelet 客户端证书轮换失败
默认情况下,kubeadm 使用 /etc/kubernetes/kubelet.conf
中指定的 /var/lib/kubelet/pki/kubelet-client-current.pem
符号链接来配置 kubelet 自动轮换客户端证书。如果此轮换过程失败,你可能会在 kube-apiserver 日志中看到诸如
x509: certificate has expired or is not yet valid
之类的错误。要解决此问题,你必须执行以下步骤:
- 从故障节点备份和删除
/etc/kubernetes/kubelet.conf
和/var/lib/kubelet/pki/kubelet-client*
。 - 在集群中具有
/etc/kubernetes/pki/ca.key
的、正常工作的控制平面节点上 执行kubeadm kubeconfig user --org system:nodes --client-name system:node:$NODE > kubelet.conf
。$NODE
必须设置为集群中现有故障节点的名称。 手动修改生成的kubelet.conf
以调整集群名称和服务器端点, 或传递kubeconfig user --config
(请参阅为其他用户生成 kubeconfig 文件)。 如果你的集群没有ca.key
,你必须在外部对kubelet.conf
中的嵌入式证书进行签名。
- 将得到的
kubelet.conf
文件复制到故障节点上,作为/etc/kubernetes/kubelet.conf
。 - 在故障节点上重启 kubelet(
systemctl restart kubelet
),等待/var/lib/kubelet/pki/kubelet-client-current.pem
重新创建。
手动编辑
kubelet.conf
指向轮换的 kubelet 客户端证书,方法是将client-certificate-data
和client-key-data
替换为:client-certificate: /var/lib/kubelet/pki/kubelet-client-current.pem client-key: /var/lib/kubelet/pki/kubelet-client-current.pem
- 重新启动 kubelet。
- 确保节点状况变为
Ready
。
在 Vagrant 中使用 flannel 作为 Pod 网络时的默认 NIC
以下错误可能表明 Pod 网络中出现问题:
Error from server (NotFound): the server could not find the requested resource
如果你正在 Vagrant 中使用 flannel 作为 Pod 网络,则必须指定 flannel 的默认接口名称。
Vagrant 通常为所有 VM 分配两个接口。第一个为所有主机分配了 IP 地址
10.0.2.15
,用于获得 NATed 的外部流量。这可能会导致 flannel 出现问题,它默认为主机上的第一个接口。这导致所有主机认为它们具有相同的公共 IP 地址。为防止这种情况,传递
--iface eth1
标志给 flannel 以便选择第二个接口。
容器使用的非公共 IP
在某些情况下 kubectl logs
和 kubectl run
命令或许会返回以下错误,即便除此之外集群一切功能正常:
Error from server: Get https://10.19.0.41:10250/containerLogs/default/mysql-ddc65b868-glc5m/mysql: dial tcp 10.19.0.41:10250: getsockopt: no route to host
这或许是由于 Kubernetes 使用的 IP 无法与看似相同的子网上的其他 IP 进行通信的缘故, 可能是由机器提供商的政策所导致的。
DigitalOcean 既分配一个共有 IP 给
eth0
,也分配一个私有 IP 在内部用作其浮动 IP 功能的锚点, 然而kubelet
将选择后者作为节点的InternalIP
而不是公共 IP。使用
ip addr show
命令代替ifconfig
命令去检查这种情况,因为ifconfig
命令 不会显示有问题的别名 IP 地址。或者指定的 DigitalOcean 的 API 端口允许从 droplet 中 查询 anchor IP:curl http://169.254.169.254/metadata/v1/interfaces/public/0/anchor_ipv4/address
解决方法是通知
kubelet
使用哪个--node-ip
。当使用 DigitalOcean 时,可以是(分配给eth0
的)公网 IP, 或者是(分配给eth1
的)私网 IP。私网 IP 是可选的。 kubadmNodeRegistrationOptions
结构 的KubeletExtraArgs
部分被用来处理这种情况。然后重启
kubelet
:systemctl daemon-reload systemctl restart kubelet
coredns
Pod 有 CrashLoopBackOff
或者 Error
状态
如果有些节点运行的是旧版本的 Docker,同时启用了 SELinux,你或许会遇到 coredns
Pod 无法启动的情况。
要解决此问题,你可以尝试以下选项之一:
升级到 Docker 的较新版本。
修改
coredns
部署以设置allowPrivilegeEscalation
为true
:
kubectl -n kube-system get deployment coredns -o yaml | \
sed 's/allowPrivilegeEscalation: false/allowPrivilegeEscalation: true/g' | \
kubectl apply -f -
CoreDNS 处于 CrashLoopBackOff
时的另一个原因是当 Kubernetes 中部署的 CoreDNS Pod 检测到环路时。
有许多解决方法
可以避免在每次 CoreDNS 监测到循环并退出时,Kubernetes 尝试重启 CoreDNS Pod 的情况。
警告:
禁用 SELinux 或设置 allowPrivilegeEscalation
为 true
可能会损害集群的安全性。
etcd Pod 持续重启
如果你遇到以下错误:
rpc error: code = 2 desc = oci runtime error: exec failed: container_linux.go:247: starting container process caused "process_linux.go:110: decoding init error from pipe caused \"read parent: connection reset by peer\""
如果你使用 Docker 1.13.1.84 运行 CentOS 7 就会出现这种问题。 此版本的 Docker 会阻止 kubelet 在 etcd 容器中执行。
为解决此问题,请选择以下选项之一:
回滚到早期版本的 Docker,例如 1.13.1-75
yum downgrade docker-1.13.1-75.git8633870.el7.centos.x86_64 docker-client-1.13.1-75.git8633870.el7.centos.x86_64 docker-common-1.13.1-75.git8633870.el7.centos.x86_64
安装较新的推荐版本之一,例如 18.06:
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install docker-ce-18.06.1.ce-3.el7.x86_64
无法将以逗号分隔的值列表传递给 --component-extra-args
标志内的参数
kubeadm init
标志例如 --component-extra-args
允许你将自定义参数传递给像
kube-apiserver 这样的控制平面组件。然而,由于解析 (mapStringString
) 的基础类型值,此机制将受到限制。
如果你决定传递一个支持多个逗号分隔值(例如
--apiserver-extra-args "enable-admission-plugins=LimitRanger,NamespaceExists"
)参数,
将出现 flag: malformed pair, expect string=string
错误。
发生这种问题是因为参数列表 --apiserver-extra-args
预期的是 key=value
形式,
而这里的 NamespacesExists
被误认为是缺少取值的键名。
一种解决方法是尝试分离 key=value
对,像这样:
--apiserver-extra-args "enable-admission-plugins=LimitRanger,enable-admission-plugins=NamespaceExists"
但这将导致键 enable-admission-plugins
仅有值 NamespaceExists
。
已知的解决方法是使用 kubeadm 配置文件。
在节点被云控制管理器初始化之前,kube-proxy 就被调度了
在云环境场景中,可能出现在云控制管理器完成节点地址初始化之前,kube-proxy 就被调度到新节点了。 这会导致 kube-proxy 无法正确获取节点的 IP 地址,并对管理负载平衡器的代理功能产生连锁反应。
在 kube-proxy Pod 中可以看到以下错误:
server.go:610] Failed to retrieve node IP: host IP unknown; known addresses: []
proxier.go:340] invalid nodeIP, initializing kube-proxy with 127.0.0.1 as nodeIP
一种已知的解决方案是修补 kube-proxy DaemonSet,以允许在控制平面节点上调度它, 而不管它们的条件如何,将其与其他节点保持隔离,直到它们的初始保护条件消除:
kubectl -n kube-system patch ds kube-proxy -p='{
"spec": {
"template": {
"spec": {
"tolerations": [
{
"key": "CriticalAddonsOnly",
"operator": "Exists"
},
{
"effect": "NoSchedule",
"key": "node-role.kubernetes.io/control-plane"
}
]
}
}
}
}'
此问题的跟踪在这里。
节点上的 /usr
被以只读方式挂载
在类似 Fedora CoreOS 或者 Flatcar Container Linux 这类 Linux 发行版本中,
目录 /usr
是以只读文件系统的形式挂载的。
在支持 FlexVolume 时,
类似 kubelet 和 kube-controller-manager 这类 Kubernetes 组件使用默认路径
/usr/libexec/kubernetes/kubelet-plugins/volume/exec/
,
而 FlexVolume 的目录必须是可写入的,该功能特性才能正常工作。
说明:
FlexVolume 在 Kubernetes v1.23 版本中已被弃用。
为了解决这个问题,你可以使用 kubeadm 的配置文件来配置 FlexVolume 的目录。
在(使用 kubeadm init
创建的)主控制节点上,使用 --config
参数传入如下文件:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
- name: "volume-plugin-dir"
value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
controllerManager:
extraArgs:
- name: "flex-volume-plugin-dir"
value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
在加入到集群中的节点上,使用下面的文件:
apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
nodeRegistration:
kubeletExtraArgs:
- name: "volume-plugin-dir"
value: "/opt/libexec/kubernetes/kubelet-plugins/volume/exec/"
或者,你可以更改 /etc/fstab
使得 /usr
目录能够以可写入的方式挂载,
不过请注意这样做本质上是在更改 Linux 发行版的某种设计原则。
kubeadm upgrade plan
输出错误信息 context deadline exceeded
在使用 kubeadm
来升级某运行外部 etcd 的 Kubernetes 集群时可能显示这一错误信息。
这并不是一个非常严重的一个缺陷,之所以出现此错误信息,原因是老的 kubeadm
版本会对外部 etcd 集群执行版本检查。你可以继续执行 kubeadm upgrade apply ...
。
这一问题已经在 1.19 版本中得到修复。
kubeadm reset
会卸载 /var/lib/kubelet
如果已经挂载了 /var/lib/kubelet
目录,执行 kubeadm reset
操作的时候会将其卸载。
要解决这一问题,可以在执行了 kubeadm reset
操作之后重新挂载
/var/lib/kubelet
目录。
这是一个在 1.15 中引入的故障,已经在 1.20 版本中修复。
无法在 kubeadm 集群中安全地使用 metrics-server
在 kubeadm 集群中可以通过为 metrics-server
设置 --kubelet-insecure-tls
来以不安全的形式使用该服务。
建议不要在生产环境集群中这样使用。
如果你需要在 metrics-server 和 kubelet 之间使用 TLS,会有一个问题, kubeadm 为 kubelet 部署的是自签名的服务证书。这可能会导致 metrics-server 端报告下面的错误信息:
x509: certificate signed by unknown authority
x509: certificate is valid for IP-foo not IP-bar
参见为 kubelet 启用签名的服务证书 以进一步了解如何在 kubeadm 集群中配置 kubelet 使用正确签名了的服务证书。
另请参阅 How to run the metrics-server securely。
因 etcd 哈希值无变化而升级失败
仅适用于通过 kubeadm 二进制文件 v1.28.3 或更高版本升级控制平面节点的情况, 其中此节点当前由 kubeadm v1.28.0、v1.28.1 或 v1.28.2 管理。
以下是你可能遇到的错误消息:
[upgrade/etcd] Failed to upgrade etcd: couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced: static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
[upgrade/etcd] Waiting for previous etcd to become available
I0907 10:10:09.109104 3704 etcd.go:588] [etcd] attempting to see if all cluster endpoints ([https://172.17.0.6:2379/ https://172.17.0.4:2379/ https://172.17.0.3:2379/]) are available 1/10
[upgrade/etcd] Etcd was rolled back and is now available
static Pod hash for component etcd on Node kinder-upgrade-control-plane-1 did not change after 5m0s: timed out waiting for the condition
couldn't upgrade control plane. kubeadm has tried to recover everything into the earlier state. Errors faced
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.rollbackOldManifests
cmd/kubeadm/app/phases/upgrade/staticpods.go:525
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.upgradeComponent
cmd/kubeadm/app/phases/upgrade/staticpods.go:254
k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade.performEtcdStaticPodUpgrade
cmd/kubeadm/app/phases/upgrade/staticpods.go:338
...
本次失败的原因是受影响的版本在 PodSpec 中生成的 etcd 清单文件带有不需要的默认值。 这将导致与清单比较的差异,并且 kubeadm 预期 Pod 哈希值将发生变化,但 kubelet 永远不会更新哈希值。
如果你在集群中遇到此问题,有两种解决方法:
可以运行以下命令跳过 etcd 的版本升级,即受影响版本和 v1.28.3(或更高版本)之间的版本升级:
kubeadm upgrade {apply|node} [version] --etcd-upgrade=false
但不推荐这种方法,因为后续的 v1.28 补丁版本可能引入新的 etcd 版本。
在升级之前,对 etcd 静态 Pod 的清单进行修补,以删除有问题的默认属性:
diff --git a/etc/kubernetes/manifests/etcd_defaults.yaml b/etc/kubernetes/manifests/etcd_origin.yaml index d807ccbe0aa..46b35f00e15 100644 --- a/etc/kubernetes/manifests/etcd_defaults.yaml +++ b/etc/kubernetes/manifests/etcd_origin.yaml @@ -43,7 +43,6 @@ spec: scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 - successThreshold: 1 timeoutSeconds: 15 name: etcd resources: @@ -59,26 +58,18 @@ spec: scheme: HTTP initialDelaySeconds: 10 periodSeconds: 10 - successThreshold: 1 timeoutSeconds: 15 - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File volumeMounts: - mountPath: /var/lib/etcd name: etcd-data - mountPath: /etc/kubernetes/pki/etcd name: etcd-certs - dnsPolicy: ClusterFirst - enableServiceLinks: true hostNetwork: true priority: 2000001000 priorityClassName: system-node-critical - restartPolicy: Always - schedulerName: default-scheduler securityContext: seccompProfile: type: RuntimeDefault - terminationGracePeriodSeconds: 30 volumes: - hostPath: path: /etc/kubernetes/pki/etcd
有关此错误的更多信息,请查阅此问题的跟踪页面。
2.2.2.1.3 - 使用 kubeadm 创建集群
使用 kubeadm
,你能创建一个符合最佳实践的最小化 Kubernetes 集群。
事实上,你可以使用 kubeadm
配置一个通过
Kubernetes 一致性测试的集群。
kubeadm
还支持其他集群生命周期功能,
例如启动引导令牌和集群升级。
kubeadm
工具很棒,如果你需要:
- 一个尝试 Kubernetes 的简单方法。
- 一个现有用户可以自动设置集群并测试其应用程序的途径。
- 其他具有更大范围的生态系统和/或安装工具中的构建模块。
你可以在各种机器上安装和使用 kubeadm
:笔记本电脑、一组云服务器、Raspberry Pi 等。
无论是部署到云还是本地,你都可以将 kubeadm
集成到 Ansible 或 Terraform 这类预配置系统中。
准备开始
要遵循本指南,你需要:
- 一台或多台运行兼容 deb/rpm 的 Linux 操作系统的计算机;例如:Ubuntu 或 CentOS。
- 每台机器 2 GB 以上的内存,内存不足时应用会受限制。
- 用作控制平面节点的计算机上至少有 2 个 CPU。
- 集群中所有计算机之间具有完全的网络连接。你可以使用公共网络或专用网络。
你还需要使用可以在新集群中部署特定 Kubernetes 版本对应的 kubeadm
。
Kubernetes 版本及版本偏差策略适用于
kubeadm
以及整个 Kubernetes。
查阅该策略以了解支持哪些版本的 Kubernetes 和 kubeadm
。
该页面是为 Kubernetes v1.31 编写的。
kubeadm
工具的整体特性状态为正式发布(GA)。一些子特性仍在积极开发中。
随着工具的发展,创建集群的实现可能会略有变化,但总体实现应相当稳定。
说明:
根据定义,在 kubeadm alpha
下的所有命令均在 Alpha 级别上受支持。
目标
- 安装单个控制平面的 Kubernetes 集群
- 在集群上安装 Pod 网络,以便你的 Pod 可以相互连通
操作指南
主机准备
安装组件
在所有主机上安装容器运行时和 kubeadm。 详细说明和其他前提条件,请参见安装 kubeadm。
说明:
如果你已经安装了 kubeadm, 请查看升级 Linux 节点文档的前两步, 了解如何升级 kubeadm 的说明。
升级时,kubelet 每隔几秒钟重新启动一次, 在 crashloop 状态中等待 kubeadm 发布指令。crashloop 状态是正常现象。 初始化控制平面后,kubelet 将正常运行。
网络设置
kubeadm 与其他 Kubernetes 组件类似,会尝试在与主机默认网关关联的网络接口上找到可用的 IP 地址。 这个 IP 地址随后用于由某组件执行的公告和/或监听。
要在 Linux 主机上获得此 IP 地址,你可以使用以下命令:
ip route show # 查找以 "default via" 开头的行
说明:
如果主机上存在两个或多个默认网关,Kubernetes 组件将尝试使用遇到的第一个具有合适全局单播 IP 地址的网关。 在做出这个选择时,网关的确切顺序可能因不同的操作系统和内核版本而有所差异。
Kubernetes 组件不接受自定义网络接口作为选项,因此必须将自定义 IP 地址作为标志传递给所有需要此自定义配置的组件实例。
说明:
如果主机没有默认网关,并且没有将自定义 IP 地址传递给 Kubernetes 组件,此组件可能会因错误而退出。
要为使用 init
或 join
创建的控制平面节点配置 API 服务器的公告地址,
你可以使用 --apiserver-advertise-address
标志。
最好在 kubeadm API中使用
InitConfiguration.localAPIEndpoint
和 JoinConfiguration.controlPlane.localAPIEndpoint
来设置此选项。
对于所有节点上的 kubelet,--node-ip
选项可以在 kubeadm 配置文件
(InitConfiguration
或 JoinConfiguration
)的 .nodeRegistration.kubeletExtraArgs
中设置。
有关双协议栈细节参见使用 kubeadm 支持双协议栈。
你分配给控制平面组件的 IP 地址将成为其 X.509 证书的使用者备用名称字段的一部分。 更改这些 IP 地址将需要签署新的证书并重启受影响的组件,以便反映证书文件中的变化。 有关此主题的更多细节参见手动续期证书。
警告:
Kubernetes 项目不推荐此方法(使用自定义 IP 地址配置所有组件实例)。
Kubernetes 维护者建议设置主机网络,使默认网关 IP 成为 Kubernetes 组件自动检测和使用的 IP。
对于 Linux 节点,你可以使用诸如 ip route
的命令来配置网络;
你的操作系统可能还提供更高级的网络管理工具。
如果节点的默认网关是公共 IP 地址,你应配置数据包过滤或其他保护节点和集群的安全措施。
准备所需的容器镜像
这个步骤是可选的,只适用于你希望 kubeadm init
和 kubeadm join
不去下载存放在
registry.k8s.io
上的默认容器镜像的情况。
当你在离线的节点上创建一个集群的时候,kubeadm 有一些命令可以帮助你预拉取所需的镜像。 阅读离线运行 kubeadm 获取更多的详情。
kubeadm 允许你给所需要的镜像指定一个自定义的镜像仓库。 阅读使用自定义镜像获取更多的详情。
初始化控制平面节点
控制平面节点是运行控制平面组件的机器, 包括 etcd(集群数据库) 和 API 服务器 (命令行工具 kubectl 与之通信)。
- (推荐)如果计划将单个控制平面 kubeadm 集群升级成高可用,
你应该指定
--control-plane-endpoint
为所有控制平面节点设置共享端点。 端点可以是负载均衡器的 DNS 名称或 IP 地址。 - 选择一个 Pod 网络插件,并验证是否需要为
kubeadm init
传递参数。 根据你选择的第三方网络插件,你可能需要设置--pod-network-cidr
的值。 请参阅安装 Pod 网络附加组件。
- (可选)
kubeadm
试图通过使用已知的端点列表来检测容器运行时。 使用不同的容器运行时或在预配置的节点上安装了多个容器运行时,请为kubeadm init
指定--cri-socket
参数。 请参阅安装运行时。
要初始化控制平面节点,请运行:
kubeadm init <args>
关于 apiserver-advertise-address 和 ControlPlaneEndpoint 的注意事项
--apiserver-advertise-address
可用于为控制平面节点的 API 服务器设置广播地址,
--control-plane-endpoint
可用于为所有控制平面节点设置共享端点。
--control-plane-endpoint
允许 IP 地址和可以映射到 IP 地址的 DNS 名称。
请与你的网络管理员联系,以评估有关此类映射的可能解决方案。
这是一个示例映射:
192.168.0.102 cluster-endpoint
其中 192.168.0.102
是此节点的 IP 地址,cluster-endpoint
是映射到该 IP 的自定义 DNS 名称。
这将允许你将 --control-plane-endpoint=cluster-endpoint
传递给 kubeadm init
,
并将相同的 DNS 名称传递给 kubeadm join
。稍后你可以修改 cluster-endpoint
以指向高可用性方案中的负载均衡器的地址。
kubeadm 不支持将没有 --control-plane-endpoint
参数的单个控制平面集群转换为高可用性集群。
更多信息
有关 kubeadm init
参数的更多信息,请参见 kubeadm 参考指南。
要使用配置文件配置 kubeadm init
命令,
请参见带配置文件使用 kubeadm init。
要自定义控制平面组件,包括可选的对控制平面组件和 etcd 服务器的活动探针提供 IPv6 支持, 请参阅自定义参数。
要重新配置一个已经创建的集群, 请参见重新配置一个 kubeadm 集群。
要再次运行 kubeadm init
,你必须首先卸载集群。
如果将具有不同架构的节点加入集群, 请确保已部署的 DaemonSet 对这种体系结构具有容器镜像支持。
kubeadm init
首先运行一系列预检查以确保机器为运行 Kubernetes 准备就绪。
这些预检查会显示警告并在错误时退出。然后 kubeadm init
下载并安装集群控制平面组件。这可能会需要几分钟。完成之后你应该看到:
Your Kubernetes control-plane has initialized successfully!
To start using your cluster, you need to run the following as a regular user:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
You should now deploy a Pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
/docs/concepts/cluster-administration/addons/
You can now join any number of machines by running the following on each node
as root:
kubeadm join <control-plane-host>:<control-plane-port> --token <token> --discovery-token-ca-cert-hash sha256:<hash>
要使非 root 用户可以运行 kubectl,请运行以下命令,
它们也是 kubeadm init
输出的一部分:
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
或者,如果你是 root
用户,则可以运行:
export KUBECONFIG=/etc/kubernetes/admin.conf
警告:
kubeadm init
生成的 kubeconfig 文件 admin.conf
包含一个带有 Subject: O = kubeadm:cluster-admins, CN = kubernetes-admin
的证书。
kubeadm:cluster-admins
组被绑定到内置的 cluster-admin
ClusterRole 上。
不要与任何人共享 admin.conf
文件。
kubeadm init
生成另一个 kubeconfig 文件 super-admin.conf
,
其中包含带有 Subject: O = system:masters, CN = kubernetes-super-admin
的证书。
system:masters
是一个紧急访问、超级用户组,可以绕过授权层(例如 RBAC)。
不要与任何人共享 super-admin.conf
文件,建议将其移动到安全位置。
有关如何使用 kubeadm kubeconfig user
为其他用户生成 kubeconfig
文件,请参阅为其他用户生成 kubeconfig 文件。
记录 kubeadm init
输出的 kubeadm join
命令。
你需要此命令将节点加入集群。
令牌用于控制平面节点和加入节点之间的相互身份验证。
这里包含的令牌是密钥。确保它的安全,
因为拥有此令牌的任何人都可以将经过身份验证的节点添加到你的集群中。
可以使用 kubeadm token
命令列出,创建和删除这些令牌。
请参阅 kubeadm 参考指南。
安装 Pod 网络附加组件
注意:
本节包含有关网络设置和部署顺序的重要信息。在继续之前,请仔细阅读所有建议。
你必须部署一个基于 Pod 网络插件的容器网络接口(CNI), 以便你的 Pod 可以相互通信。在安装网络之前,集群 DNS (CoreDNS) 将不会启动。
- 注意你的 Pod 网络不得与任何主机网络重叠:如果有重叠,你很可能会遇到问题。
(如果你发现网络插件的首选 Pod 网络与某些主机网络之间存在冲突,
则应考虑使用一个合适的 CIDR 块来代替,
然后在执行
kubeadm init
时使用--pod-network-cidr
参数并在你的网络插件的 YAML 中替换它)。
- 默认情况下,
kubeadm
将集群设置为使用和强制使用 RBAC(基于角色的访问控制)。 确保你的 Pod 网络插件支持 RBAC,以及用于部署它的清单也是如此。
- 如果要为集群使用 IPv6(双协议栈或仅单协议栈 IPv6 网络), 请确保你的 Pod 网络插件支持 IPv6。 IPv6 支持已在 CNI v0.6.0 版本中添加。
说明:
kubeadm 应该是与 CNI 无关的,对 CNI 驱动进行验证目前不在我们的端到端测试范畴之内。 如果你发现与 CNI 插件相关的问题,应在其各自的问题跟踪器中记录而不是在 kubeadm 或 kubernetes 问题跟踪器中记录。
一些外部项目为 Kubernetes 提供使用 CNI 的 Pod 网络, 其中一些还支持网络策略。
请参阅实现 Kubernetes 网络模型的附加组件列表。
请参阅安装插件页面, 了解 Kubernetes 支持的网络插件的非详尽列表。 你可以使用以下命令在控制平面节点或具有 kubeconfig 凭据的节点上安装 Pod 网络附加组件:
kubectl apply -f <add-on.yaml>
说明:
只有少数 CNI 插件支持 Windows, 更多详细信息和设置说明请参阅添加 Windows 工作节点。
每个集群只能安装一个 Pod 网络。
安装 Pod 网络后,你可以通过在 kubectl get pods --all-namespaces
输出中检查
CoreDNS Pod 是否 Running
来确认其是否正常运行。
一旦 CoreDNS Pod 启用并运行,你就可以继续加入节点。
如果你的网络无法正常工作或 CoreDNS 不在 Running
状态,请查看 kubeadm
的故障排除指南。
托管节点标签
默认情况下,kubeadm 启用
NodeRestriction
准入控制器来限制 kubelet 在节点注册时可以应用哪些标签。准入控制器文档描述 kubelet --node-labels
选项允许使用哪些标签。
其中 node-role.kubernetes.io/control-plane
标签就是这样一个受限制的标签,
kubeadm 在节点创建后使用特权客户端手动应用此标签。
你可以使用一个有特权的 kubeconfig,比如由 kubeadm 管理的 /etc/kubernetes/admin.conf
,
通过执行 kubectl label
来手动完成操作。
控制平面节点隔离
默认情况下,出于安全原因,你的集群不会在控制平面节点上调度 Pod。 如果你希望能够在单机 Kubernetes 集群等控制平面节点上调度 Pod,请运行:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
输出看起来像:
node "test-01" untainted
...
这将从任何拥有 node-role.kubernetes.io/control-plane:NoSchedule
污点的节点(包括控制平面节点)上移除该污点。
这意味着调度程序将能够在任何地方调度 Pod。
此外,你可以执行以下命令从控制平面节点中删除
node.kubernetes.io/exclude-from-external-load-balancers
标签,这会将其从后端服务器列表中排除:
kubectl label nodes --all node.kubernetes.io/exclude-from-external-load-balancers-
添加更多控制平面节点
请参阅使用 kubeadm 创建高可用性集群, 了解通过添加更多控制平面节点创建高可用性 kubeadm 集群的步骤。
添加工作节点
工作节点是工作负载运行的地方。
以下页面展示如何使用 kubeadm join
命令将 Linux 和 Windows 工作节点添加到集群:
(可选)从控制平面节点以外的计算机控制集群
为了使 kubectl 在其他计算机(例如笔记本电脑)上与你的集群通信, 你需要将管理员 kubeconfig 文件从控制平面节点复制到工作站,如下所示:
scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf get nodes
说明:
上面的示例假定为 root 用户启用了 SSH 访问。如果不是这种情况,
你可以使用 scp
将 admin.conf
文件复制给其他允许访问的用户。
admin.conf 文件为用户提供了对集群的超级用户特权。
该文件应谨慎使用。对于普通用户,建议生成一个你为其授予特权的唯一证书。
你可以使用 kubeadm kubeconfig user --client-name <CN>
命令执行此操作。
该命令会将 KubeConfig 文件打印到 STDOUT,你应该将其保存到文件并分发给用户。
之后,使用 kubectl create (cluster)rolebinding
授予特权。
(可选)将 API 服务器代理到本地主机
如果你要从集群外部连接到 API 服务器,则可以使用 kubectl proxy
:
scp root@<control-plane-host>:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf proxy
你现在可以在 http://localhost:8001/api/v1
从本地访问 API 服务器。
清理
如果你在集群中使用了一次性服务器进行测试,则可以关闭这些服务器,而无需进一步清理。
你可以使用 kubectl config delete-cluster
删除对集群的本地引用。
但是,如果要更干净地取消配置集群, 则应首先清空节点并确保该节点为空, 然后取消配置该节点。
移除节点
使用适当的凭据与控制平面节点通信,运行:
kubectl drain <节点名称> --delete-emptydir-data --force --ignore-daemonsets
在移除节点之前,请重置 kubeadm
安装的状态:
kubeadm reset
重置过程不会重置或清除 iptables 规则或 IPVS 表。如果你希望重置 iptables,则必须手动进行:
iptables -F && iptables -t nat -F && iptables -t mangle -F && iptables -X
如果要重置 IPVS 表,则必须运行以下命令:
ipvsadm -C
现在移除节点:
kubectl delete node <节点名称>
如果你想重新开始,只需运行 kubeadm init
或 kubeadm join
并加上适当的参数。
清理控制平面
你可以在控制平面主机上使用 kubeadm reset
来触发尽力而为的清理。
有关此子命令及其选项的更多信息,请参见
kubeadm reset
参考文档。
版本偏差策略
虽然 kubeadm 允许所管理的组件有一定程度的版本偏差, 但是建议你将 kubeadm 的版本与控制平面组件、kube-proxy 和 kubelet 的版本相匹配。
kubeadm 中的 Kubernetes 版本偏差
kubeadm 可以与 Kubernetes 组件一起使用,这些组件的版本与 kubeadm 相同,或者比它大一个版本。
Kubernetes 版本可以通过使用 --kubeadm init
的 --kubernetes-version
标志或使用 --config
时的
ClusterConfiguration.kubernetesVersion
字段指定给 kubeadm。
这个选项将控制 kube-apiserver、kube-controller-manager、kube-scheduler 和 kube-proxy 的版本。
例子:
- kubeadm 的版本为 1.31。
kubernetesVersion
必须为 1.31 或者 1.30。
kubeadm 中 kubelet 的版本偏差
与 Kubernetes 版本类似,kubeadm 可以使用与 kubeadm 相同版本的 kubelet, 或者比 kubeadm 老三个版本的 kubelet。
例子:
- kubeadm 的版本为 1.31。
- 主机上的 kubelet 必须为 1.31、1.30、 1.29 或 1.28。
kubeadm 支持的 kubeadm 的版本偏差
kubeadm 命令在现有节点或由 kubeadm 管理的整个集群上的操作有一定限制。
如果新的节点加入到集群中,用于 kubeadm join
的 kubeadm 二进制文件必须与用 kubeadm init
创建集群或用 kubeadm upgrade
升级同一节点时所用的 kubeadm 版本一致。
类似的规则适用于除了 kubeadm upgrade
以外的其他 kubeadm 命令。
kubeadm join
的例子:
- 使用
kubeadm init
创建集群时使用版本为 1.31 的 kubeadm。 - 添加节点所用的 kubeadm 可执行文件为版本 。
对于正在升级的节点,所使用的的 kubeadm 必须与管理该节点的 kubeadm 具有相同的 MINOR 版本或比后者新一个 MINOR 版本。
kubeadm upgrade
的例子:
- 用于创建或升级节点的 kubeadm 版本为 1.30。
- 用于升级节点的 kubeadm 版本必须为 1.30 或 1.31。
要了解更多关于不同 Kubernetes 组件之间的版本偏差, 请参见版本偏差策略。
局限性
集群弹性
此处创建的集群具有单个控制平面节点,运行单个 etcd 数据库。 这意味着如果控制平面节点发生故障,你的集群可能会丢失数据并且可能需要从头开始重新创建。
解决方法:
- 定期备份 etcd。
kubeadm 配置的 etcd 数据目录位于控制平面节点上的
/var/lib/etcd
中。
平台兼容性
kubeadm deb/rpm 软件包和二进制文件是为 amd64、arm (32-bit)、arm64、ppc64le 和 s390x 构建的遵循多平台提案。
从 v1.12 开始还支持用于控制平面和附加组件的多平台容器镜像。
只有一些网络提供商为所有平台提供解决方案。 请查阅上方的网络提供商清单或每个提供商的文档以确定提供商是否支持你选择的平台。
故障排除
如果你在使用 kubeadm 时遇到困难, 请查阅我们的故障排除文档。
接下来
- 使用 Sonobuoy 验证集群是否正常运行。
- 有关使用 kubeadm 升级集群的详细信息, 请参阅升级 kubeadm 集群。
- 在 kubeadm 参考文档中了解有关
kubeadm
进阶用法的信息。 - 了解有关 Kubernetes 概念和
kubectl
的更多信息。 - 有关 Pod 网络附加组件的更多列表,请参见集群网络页面。
- 请参阅附加组件列表以探索其他附加组件, 包括用于 Kubernetes 集群的日志记录、监视、网络策略、可视化和控制的工具。
- 配置集群如何处理集群事件的日志以及在 Pod 中运行的应用程序。 有关所涉及内容的概述,请参见日志架构。
反馈
- 有关漏洞,访问 kubeadm GitHub issue tracker
- 有关支持,访问 #kubeadm Slack 频道
- 常规的 SIG Cluster Lifecycle 开发 Slack 频道: #sig-cluster-lifecycle
- SIG Cluster Lifecycle 的 SIG 资料
- SIG Cluster Lifecycle 邮件列表: kubernetes-sig-cluster-lifecycle
2.2.2.1.4 - 使用 kubeadm API 定制组件
本页面介绍了如何自定义 kubeadm 部署的组件。
你可以使用 ClusterConfiguration
结构中定义的参数,或者在每个节点上应用补丁来定制控制平面组件。
你可以使用 KubeletConfiguration
和 KubeProxyConfiguration
结构分别定制 kubelet 和 kube-proxy 组件。
所有这些选项都可以通过 kubeadm 配置 API 实现。 有关配置中的每个字段的详细信息,你可以导航到我们的 API 参考页面 。
说明:
kubeadm 目前不支持对 CoreDNS 部署进行定制。
你必须手动更新 kube-system/coredns
ConfigMap
并在更新后重新创建 CoreDNS Pod。
或者,你可以跳过默认的 CoreDNS 部署并部署你自己的 CoreDNS 变种。
有关更多详细信息,请参阅在 kubeadm 中使用 init phase.
说明:
要重新配置已创建的集群,请参阅重新配置 kubeadm 集群。
使用 ClusterConfiguration
中的标志自定义控制平面
kubeadm ClusterConfiguration
对象为用户提供了一种方法,
用以覆盖传递给控制平面组件(如 APIServer、ControllerManager、Scheduler 和 Etcd)的默认参数。
各组件配置使用如下字段定义:
apiServer
controllerManager
scheduler
etcd
这些结构包含一个通用的 extraArgs
字段,该字段由 name
/ value
组成。
要覆盖控制平面组件的参数:
- 将适当的字段
extraArgs
添加到配置中。 - 向字段
extraArgs
添加要覆盖的参数值。 - 用
--config <YOUR CONFIG YAML>
运行kubeadm init
。
说明:
你可以通过运行 kubeadm config print init-defaults
并将输出保存到你所选的文件中,
以默认值形式生成 ClusterConfiguration
对象。
说明:
ClusterConfiguration
对象目前在 kubeadm 集群中是全局的。
这意味着你添加的任何标志都将应用于同一组件在不同节点上的所有实例。
要在不同节点上为每个组件应用单独的配置,你可以使用补丁。
说明:
当前不支持重复的参数(keys)或多次传递相同的参数 --foo
。
要解决此问题,你必须使用补丁。
APIServer 参数
有关详细信息,请参阅 kube-apiserver 参考文档。
使用示例:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
apiServer:
extraArgs:
- name: "enable-admission-plugins"
value: "AlwaysPullImages,DefaultStorageClass"
- name: "audit-log-path"
value: "/home/johndoe/audit.log"
ControllerManager 参数
有关详细信息,请参阅 kube-controller-manager 参考文档。
使用示例:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
controllerManager:
extraArgs:
- name: "cluster-signing-key-file"
value: "/home/johndoe/keys/ca.key"
- name: "deployment-controller-sync-period"
value: "50"
Scheduler 参数
有关详细信息,请参阅 kube-scheduler 参考文档。
使用示例:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.16.0
scheduler:
extraArgs:
- name: "config"
value: "/etc/kubernetes/scheduler-config.yaml"
extraVolumes:
- name: schedulerconfig
hostPath: /home/johndoe/schedconfig.yaml
mountPath: /etc/kubernetes/scheduler-config.yaml
readOnly: true
pathType: "File"
Etcd 参数
有关详细信息,请参阅 etcd 服务文档.
使用示例:
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
etcd:
local:
extraArgs:
- name: "election-timeout"
value: 1000
使用补丁定制
Kubernetes v1.22 [beta]
Kubeadm 允许将包含补丁文件的目录传递给各个节点上的 InitConfiguration
和 JoinConfiguration
。
这些补丁可被用作组件配置写入磁盘之前的最后一个自定义步骤。
可以使用 --config <你的 YAML 格式控制文件>
将配置文件传递给 kubeadm init
:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
patches:
directory: /home/user/somedir
说明:
对于 kubeadm init
,你可以传递一个包含 ClusterConfiguration
和 InitConfiguration
的文件,以 ---
分隔。
你可以使用 --config <你的 YAML 格式配置文件>
将配置文件传递给 kubeadm join
:
apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
patches:
directory: /home/user/somedir
补丁目录必须包含名为 target[suffix][+patchtype].extension
的文件。
例如,kube-apiserver0+merge.yaml
或只是 etcd.json
。
target
可以是kube-apiserver
、kube-controller-manager
、kube-scheduler
、etcd
和kubeletconfiguration
之一。suffix
是一个可选字符串,可用于确定首先按字母数字应用哪些补丁。patchtype
可以是strategy
、merge
或json
之一,并且这些必须匹配 kubectl 支持 的补丁格式。 默认补丁类型是strategic
的。extension
必须是json
或yaml
。
说明:
如果你使用 kubeadm upgrade
升级 kubeadm 节点,你必须再次提供相同的补丁,以便在升级后保留自定义配置。
为此,你可以使用 --patches
参数,该参数必须指向同一目录。 kubeadm upgrade
目前不支持用于相同目的的 API 结构配置。
自定义 kubelet
要自定义 kubelet,你可以在同一配置文件中的 ClusterConfiguration
或 InitConfiguration
之外添加一个 KubeletConfiguration
,用 ---
分隔。
然后可以将此文件传递给 kubeadm init
,kubeadm 会将相同的
KubeletConfiguration
配置应用于集群中的所有节点。
要在基础 KubeletConfiguration
上应用特定节点的配置,你可以使用
kubeletconfiguration
补丁定制。
或者你可以使用 kubelet
参数进行覆盖,方法是将它们传递到 InitConfiguration
和 JoinConfiguration
支持的 nodeRegistration.kubeletExtraArgs
字段中。一些 kubelet 参数已被弃用,
因此在使用这些参数之前,请在 kubelet 参考文档 中检查它们的状态。
更多详情,请参阅使用 kubeadm 配置集群中的每个 kubelet
自定义 kube-proxy
要自定义 kube-proxy,你可以在 ClusterConfiguration
或 InitConfiguration
之外添加一个由 ---
分隔的 KubeProxyConfiguration
, 传递给 kubeadm init
。
可以导航到 API 参考页面查看更多详情,
说明:
kubeadm 将 kube-proxy 部署为 DaemonSet,
这意味着 KubeProxyConfiguration
将应用于集群中的所有 kube-proxy 实例。
2.2.2.1.5 - 高可用拓扑选项
本页面介绍了配置高可用(HA)Kubernetes 集群拓扑的两个选项。
你可以设置 HA 集群:
- 使用堆叠(stacked)控制平面节点,其中 etcd 节点与控制平面节点共存
- 使用外部 etcd 节点,其中 etcd 在与控制平面不同的节点上运行
在设置 HA 集群之前,你应该仔细考虑每种拓扑的优缺点。
说明:
kubeadm 静态引导 etcd 集群。 阅读 etcd 集群指南以获得更多详细信息。
堆叠(Stacked)etcd 拓扑
堆叠(Stacked)HA 集群是一种这样的拓扑, 其中 etcd 分布式数据存储集群堆叠在 kubeadm 管理的控制平面节点上,作为控制平面的一个组件运行。
每个控制平面节点运行 kube-apiserver
、kube-scheduler
和 kube-controller-manager
实例。
kube-apiserver
使用负载均衡器暴露给工作节点。
每个控制平面节点创建一个本地 etcd 成员(member),这个 etcd 成员只与该节点的 kube-apiserver
通信。
这同样适用于本地 kube-controller-manager
和 kube-scheduler
实例。
这种拓扑将控制平面和 etcd 成员耦合在同一节点上。相对使用外部 etcd 集群, 设置起来更简单,而且更易于副本管理。
然而,堆叠集群存在耦合失败的风险。如果一个节点发生故障,则 etcd 成员和控制平面实例都将丢失, 并且冗余会受到影响。你可以通过添加更多控制平面节点来降低此风险。
因此,你应该为 HA 集群运行至少三个堆叠的控制平面节点。
这是 kubeadm 中的默认拓扑。当使用 kubeadm init
和 kubeadm join --control-plane
时,
在控制平面节点上会自动创建本地 etcd 成员。
外部 etcd 拓扑
具有外部 etcd 的 HA 集群是一种这样的拓扑, 其中 etcd 分布式数据存储集群在独立于控制平面节点的其他节点上运行。
就像堆叠的 etcd 拓扑一样,外部 etcd 拓扑中的每个控制平面节点都会运行
kube-apiserver
、kube-scheduler
和 kube-controller-manager
实例。
同样,kube-apiserver
使用负载均衡器暴露给工作节点。但是 etcd 成员在不同的主机上运行,
每个 etcd 主机与每个控制平面节点的 kube-apiserver
通信。
这种拓扑结构解耦了控制平面和 etcd 成员。因此它提供了一种 HA 设置, 其中失去控制平面实例或者 etcd 成员的影响较小,并且不会像堆叠的 HA 拓扑那样影响集群冗余。
但此拓扑需要两倍于堆叠 HA 拓扑的主机数量。 具有此拓扑的 HA 集群至少需要三个用于控制平面节点的主机和三个用于 etcd 节点的主机。
接下来
2.2.2.1.6 - 利用 kubeadm 创建高可用集群
本文讲述了使用 kubeadm 设置一个高可用的 Kubernetes 集群的两种不同方式:
- 使用具有堆叠的控制平面节点。这种方法所需基础设施较少。etcd 成员和控制平面节点位于同一位置。
- 使用外部 etcd 集群。这种方法所需基础设施较多。控制平面的节点和 etcd 成员是分开的。
在下一步之前,你应该仔细考虑哪种方法更好地满足你的应用程序和环境的需求。 高可用拓扑选项 讲述了每种方法的优缺点。
如果你在安装 HA 集群时遇到问题,请在 kubeadm 问题跟踪里向我们提供反馈。
你也可以阅读升级文档。
注意:
这篇文档没有讲述在云提供商上运行集群的问题。在云环境中, 此处记录的方法不适用于类型为 LoadBalancer 的服务对象,也不适用于具有动态 PersistentVolume 的对象。
准备开始
根据集群控制平面所选择的拓扑结构不同,准备工作也有所差异:
需要准备:
- 配置满足 kubeadm 的最低要求
的三台机器作为控制面节点。控制平面节点为奇数有利于机器故障或者分区故障时重新选举。
- 机器已经安装好容器运行时,并正常运行
- 配置满足 kubeadm 的最低要求
的三台机器作为工作节点
- 机器已经安装好容器运行时,并正常运行
- 在集群中,确保所有计算机之间存在全网络连接(公网或私网)
- 在所有机器上具有 sudo 权限
- 可以使用其他工具;本教程以
sudo
举例
- 可以使用其他工具;本教程以
- 从某台设备通过 SSH 访问系统中所有节点的能力
- 所有机器上已经安装
kubeadm
和kubelet
拓扑详情请参考堆叠(Stacked)etcd 拓扑。
需要准备:
- 配置满足 kubeadm 的最低要求
的三台机器作为控制面节点。控制平面节点为奇数有利于机器故障或者分区故障时重新选举。
- 机器已经安装好容器运行时,并正常运行
- 配置满足 kubeadm 的最低要求
的三台机器作为工作节点
- 机器已经安装好容器运行时,并正常运行
- 在集群中,确保所有计算机之间存在全网络连接(公网或私网)
- 在所有机器上具有 sudo 权限
- 可以使用其他工具;本教程以
sudo
举例
- 可以使用其他工具;本教程以
- 从某台设备通过 SSH 访问系统中所有节点的能力
- 所有机器上已经安装
kubeadm
和kubelet
还需要准备:
- 给 etcd 集群使用的另外至少三台机器。为了分布式一致性算法达到更好的投票效果,集群必须由奇数个节点组成。
- 机器上已经安装
kubeadm
和kubelet
。 - 机器上同样需要安装好容器运行时,并能正常运行。
- 机器上已经安装
拓扑详情请参考外部 etcd 拓扑。
容器镜像
每台主机需要能够从 Kubernetes 容器镜像仓库(registry.k8s.io
)读取和拉取镜像。
想要在无法拉取 Kubernetes 仓库镜像的机器上部署高可用集群也是可行的。通过其他的手段保证主机上已经有对应的容器镜像即可。
命令行
一旦集群创建成功,需要在 PC 上安装 kubectl 用于管理 Kubernetes。
为了方便故障排查,也可以在每个控制平面节点上安装 kubectl
。
这两种方法的第一步
为 kube-apiserver 创建负载均衡器
说明:
负载均衡器有很多种可能的配置方式。以下仅提供一个配置的例子。你的集群可能需要进行不同的配置。
创建一个名为 kube-apiserver 的负载均衡器解析 DNS。
在云环境中,应该将控制平面节点放置在 TCP 转发负载平衡后面。 该负载均衡器将流量分配给目标列表中所有运行状况良好的控制平面节点。 API 服务器的健康检查是在 kube-apiserver 的监听端口(默认值
:6443
) 上进行的一个 TCP 检查。不建议在云环境中直接使用 IP 地址。
负载均衡器必须能够在 API 服务器端口上与所有控制平面节点通信。 它还必须允许其监听端口的入站流量。
确保负载均衡器的地址始终匹配 kubeadm 的
ControlPlaneEndpoint
地址。阅读软件负载平衡选项指南 以获取更多详细信息。
添加第一个控制平面节点到负载均衡器并测试连接:
nc -v LOAD_BALANCER_IP PORT
由于 API 服务器尚未运行,预期会出现一个连接拒绝错误。 然而超时意味着负载均衡器不能和控制平面节点通信。 如果发生超时,请重新配置负载均衡器与控制平面节点进行通信。
将其余控制平面节点添加到负载均衡器目标组。
使用堆控制平面和 etcd 节点
控制平面节点的第一步
初始化控制平面:
sudo kubeadm init --control-plane-endpoint "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" --upload-certs
- 你可以使用
--kubernetes-version
标志来设置要使用的 Kubernetes 版本。 建议将 kubeadm、kebelet、kubectl 和 Kubernetes 的版本匹配。 - 这个
--control-plane-endpoint
标志应该被设置成负载均衡器的地址或 DNS 和端口。 - 这个
--upload-certs
标志用来将在所有控制平面实例之间的共享证书上传到集群。 如果正好相反,你更喜欢手动地通过控制平面节点或者使用自动化工具复制证书, 请删除此标志并参考如下部分证书分配手册。
说明:
在使用
kubeadm init
时,标记--config
和--certificate-key
不能混用, 因此如果你要使用 kubeadm 配置,你必须在相应的配置结构 (位于InitConfiguration
和JoinConfiguration: controlPlane
)添加certificateKey
字段。说明:
一些 CNI 网络插件需要更多配置,例如指定 Pod IP CIDR,而其他插件则不需要。参考 CNI 网络文档。 通过传递
--pod-network-cidr
标志添加 Pod CIDR,或者你可以使用 kubeadm 配置文件,在ClusterConfiguration
的networking
对象下设置podSubnet
字段。输出类似于:
... You can now join any number of control-plane node by running the following command on each as a root: kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07 Please note that the certificate-key gives access to cluster sensitive data, keep it secret! As a safeguard, uploaded-certs will be deleted in two hours; If necessary, you can use kubeadm init phase upload-certs to reload certs afterward. Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
将此输出复制到文本文件。 稍后你将需要它来将控制平面节点和工作节点加入集群。
当使用
--upload-certs
调用kubeadm init
时,主控制平面的证书被加密并上传到kubeadm-certs
Secret 中。要重新上传证书并生成新的解密密钥,请在已加入集群节点的控制平面上使用以下命令:
sudo kubeadm init phase upload-certs --upload-certs
- 你还可以在
init
期间指定自定义的--certificate-key
,以后可以由join
使用。 要生成这样的密钥,可以使用以下命令:
kubeadm certs certificate-key
证书密钥是一个十六进制编码的字符串,它是一个 32 字节大小的 AES 密钥。
说明:
kubeadm-certs
Secret 和解密密钥会在两个小时后失效。注意:
正如命令输出中所述,证书密钥可访问集群敏感数据。请妥善保管!
- 你可以使用
应用你所选择的 CNI 插件: 请遵循以下指示 安装 CNI 驱动。如果适用,请确保配置与 kubeadm 配置文件中指定的 Pod CIDR 相对应。
说明:
在进行下一步之前,必须选择并部署合适的网络插件。 否则集群不会正常运行。
输入以下内容,并查看控制平面组件的 Pod 启动:
kubectl get pod -n kube-system -w
其余控制平面节点的步骤
对于每个其他控制平面节点,你应该:
执行先前由第一个节点上的
kubeadm init
输出提供给你的 join 命令。 它看起来应该像这样:sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866 --control-plane --certificate-key f8902e114ef118304e561c3ecd4d0b543adc226b7a07f675f56564185ffe0c07
- 这个
--control-plane
标志通知kubeadm join
创建一个新的控制平面。 --certificate-key ...
将导致从集群中的kubeadm-certs
Secret 下载控制平面证书并使用给定的密钥进行解密。
- 这个
你可以并行地加入多个控制面节点。
外部 etcd 节点
使用外部 etcd 节点设置集群类似于用于堆叠 etcd 的过程, 不同之处在于你应该首先设置 etcd,并在 kubeadm 配置文件中传递 etcd 信息。
设置 etcd 集群
按照这里的指示去设置。
根据这里 的描述配置 SSH。
将以下文件从集群中的任一 etcd 节点复制到第一个控制平面节点:
export CONTROL_PLANE="ubuntu@10.0.0.7" scp /etc/kubernetes/pki/etcd/ca.crt "${CONTROL_PLANE}": scp /etc/kubernetes/pki/apiserver-etcd-client.crt "${CONTROL_PLANE}": scp /etc/kubernetes/pki/apiserver-etcd-client.key "${CONTROL_PLANE}":
- 用第一台控制平面节点的
user@host
替换CONTROL_PLANE
的值。
- 用第一台控制平面节点的
设置第一个控制平面节点
用以下内容创建一个名为
kubeadm-config.yaml
的文件:--- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: stable controlPlaneEndpoint: "LOAD_BALANCER_DNS:LOAD_BALANCER_PORT" # change this (see below) etcd: external: endpoints: - https://ETCD_0_IP:2379 # 适当地更改 ETCD_0_IP - https://ETCD_1_IP:2379 # 适当地更改 ETCD_1_IP - https://ETCD_2_IP:2379 # 适当地更改 ETCD_2_IP caFile: /etc/kubernetes/pki/etcd/ca.crt certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
说明:
这里的堆叠(stacked)etcd 和外部 etcd 之前的区别在于设置外部 etcd 需要一个
etcd
的external
对象下带有 etcd 端点的配置文件。 如果是内部 etcd,是自动管理的。在你的集群中,将配置模板中的以下变量替换为适当值:
LOAD_BALANCER_DNS
LOAD_BALANCER_PORT
ETCD_0_IP
ETCD_1_IP
ETCD_2_IP
以下的步骤与设置内置 etcd 的集群是相似的:
在节点上运行
sudo kubeadm init --config kubeadm-config.yaml --upload-certs
命令。记下输出的 join 命令,这些命令将在以后使用。
应用你选择的 CNI 插件。
说明:
在进行下一步之前,必须选择并部署合适的网络插件。 否则集群不会正常运行。
其他控制平面节点的步骤
步骤与设置内置 etcd 相同:
- 确保第一个控制平面节点已完全初始化。
- 使用保存到文本文件的 join 命令将每个控制平面节点连接在一起。 建议一次加入一个控制平面节点。
- 不要忘记默认情况下,
--certificate-key
中的解密秘钥会在两个小时后过期。
列举控制平面之后的常见任务
安装工作节点
你可以使用之前存储的 kubeadm init
命令的输出将工作节点加入集群中:
sudo kubeadm join 192.168.0.200:6443 --token 9vr73a.a8uxyaju799qwdjv --discovery-token-ca-cert-hash sha256:7c2e69131a36ae2a042a339b33381c6d0d43887e2de83720eff5359e26aec866
手动证书分发
如果你选择不将 kubeadm init
与 --upload-certs
命令一起使用,
则意味着你将必须手动将证书从主控制平面节点复制到将要加入的控制平面节点上。
有许多方法可以实现这种操作。下面的例子使用了 ssh
和 scp
:
如果要在单独的一台计算机控制所有节点,则需要 SSH。
在你的主设备上启用 ssh-agent,要求该设备能访问系统中的所有其他节点:
eval $(ssh-agent)
将 SSH 身份添加到会话中:
ssh-add ~/.ssh/path_to_private_key
检查节点间的 SSH 以确保连接是正常运行的
- SSH 到任何节点时,请确保添加
-A
标志。 此标志允许你通过 SSH 登录到节点后从该节点上访问你自己 PC 上的 SSH 代理。 如果你不完全信任该节点上的用户会话安全,可以考虑使用其他替代方法。
ssh -A 10.0.0.7
- 当在任何节点上使用 sudo 时,请确保保持环境变量设置,以便 SSH 转发能够正常工作:
sudo -E -s
- SSH 到任何节点时,请确保添加
在所有节点上配置 SSH 之后,你应该在运行过
kubeadm init
命令的第一个控制平面节点上运行以下脚本。 该脚本会将证书从第一个控制平面节点复制到另一个控制平面节点:在以下示例中,用其他控制平面节点的 IP 地址替换
CONTROL_PLANE_IPS
。USER=ubuntu # 可定制 CONTROL_PLANE_IPS="10.0.0.7 10.0.0.8" for host in ${CONTROL_PLANE_IPS}; do scp /etc/kubernetes/pki/ca.crt "${USER}"@$host: scp /etc/kubernetes/pki/ca.key "${USER}"@$host: scp /etc/kubernetes/pki/sa.key "${USER}"@$host: scp /etc/kubernetes/pki/sa.pub "${USER}"@$host: scp /etc/kubernetes/pki/front-proxy-ca.crt "${USER}"@$host: scp /etc/kubernetes/pki/front-proxy-ca.key "${USER}"@$host: scp /etc/kubernetes/pki/etcd/ca.crt "${USER}"@$host:etcd-ca.crt # 如果你正使用外部 etcd,忽略下一行 scp /etc/kubernetes/pki/etcd/ca.key "${USER}"@$host:etcd-ca.key done
注意:
只需要复制上面列表中的证书。kubeadm 将负责生成其余证书以及加入控制平面实例所需的 SAN。 如果你错误地复制了所有证书,由于缺少所需的 SAN,创建其他节点可能会失败。
然后,在每个即将加入集群的控制平面节点上,你必须先运行以下脚本,然后再运行
kubeadm join
。 该脚本会将先前复制的证书从主目录移动到/etc/kubernetes/pki
:USER=ubuntu # 可定制 mkdir -p /etc/kubernetes/pki/etcd mv /home/${USER}/ca.crt /etc/kubernetes/pki/ mv /home/${USER}/ca.key /etc/kubernetes/pki/ mv /home/${USER}/sa.pub /etc/kubernetes/pki/ mv /home/${USER}/sa.key /etc/kubernetes/pki/ mv /home/${USER}/front-proxy-ca.crt /etc/kubernetes/pki/ mv /home/${USER}/front-proxy-ca.key /etc/kubernetes/pki/ mv /home/${USER}/etcd-ca.crt /etc/kubernetes/pki/etcd/ca.crt # 如果你正使用外部 etcd,忽略下一行 mv /home/${USER}/etcd-ca.key /etc/kubernetes/pki/etcd/ca.key
2.2.2.1.7 - 使用 kubeadm 创建一个高可用 etcd 集群
默认情况下,kubeadm 在每个控制平面节点上运行一个本地 etcd 实例。也可以使用外部的 etcd 集群,并在不同的主机上提供 etcd 实例。 这两种方法的区别在高可用拓扑的选项页面中阐述。
这个任务将指导你创建一个由三个成员组成的高可用外部 etcd 集群,该集群在创建过程中可被 kubeadm 使用。
准备开始
- 三个可以通过 2379 和 2380 端口相互通信的主机。本文档使用这些作为默认端口。 不过,它们可以通过 kubeadm 的配置文件进行自定义。
- 每个主机必须安装 systemd 和 bash 兼容的 shell。
- 每台主机必须安装有容器运行时、kubelet 和 kubeadm。
- 每个主机都应该能够访问 Kubernetes 容器镜像仓库 (registry.k8s.io),
或者使用
kubeadm config images list/pull
列出/拉取所需的 etcd 镜像。 本指南将把 etcd 实例设置为由 kubelet 管理的静态 Pod。
- 一些可以用来在主机间复制文件的基础设施。例如
ssh
和scp
就可以满足此需求。
建立集群
一般来说,是在一个节点上生成所有证书并且只分发这些必要的文件到其它节点上。
说明:
kubeadm 包含生成下述证书所需的所有必要的密码学工具;在这个例子中,不需要其他加密工具。
说明:
下面的例子使用 IPv4 地址,但是你也可以使用 IPv6 地址配置 kubeadm、kubelet 和 etcd。 一些 Kubernetes 选项支持双协议栈,但是 etcd 不支持。关于 Kubernetes 双协议栈支持的更多细节, 请参见 kubeadm 的双栈支持。
将 kubelet 配置为 etcd 的服务管理器。
说明:
你必须在要运行 etcd 的所有主机上执行此操作。
由于 etcd 是首先创建的,因此你必须通过创建具有更高优先级的新文件来覆盖 kubeadm 提供的 kubelet 单元文件。
cat << EOF > /etc/systemd/system/kubelet.service.d/kubelet.conf # 将下面的 "systemd" 替换为你的容器运行时所使用的 cgroup 驱动。 # kubelet 的默认值为 "cgroupfs"。 # 如果需要的话,将 "containerRuntimeEndpoint" 的值替换为一个不同的容器运行时。 # apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration authentication: anonymous: enabled: false webhook: enabled: false authorization: mode: AlwaysAllow cgroupDriver: systemd address: 127.0.0.1 containerRuntimeEndpoint: unix:///var/run/containerd/containerd.sock staticPodPath: /etc/kubernetes/manifests EOF cat << EOF > /etc/systemd/system/kubelet.service.d/20-etcd-service-manager.conf [Service] ExecStart= ExecStart=/usr/bin/kubelet --config=/etc/systemd/system/kubelet.service.d/kubelet.conf Restart=always EOF systemctl daemon-reload systemctl restart kubelet
检查 kubelet 的状态以确保其处于运行状态:
systemctl status kubelet
为 kubeadm 创建配置文件。
使用以下脚本为每个将要运行 etcd 成员的主机生成一个 kubeadm 配置文件。
# 使用你的主机 IP 更新 HOST0、HOST1 和 HOST2 的 IP 地址 export HOST0=10.0.0.6 export HOST1=10.0.0.7 export HOST2=10.0.0.8 # 使用你的主机名更新 NAME0、NAME1 和 NAME2 export NAME0="infra0" export NAME1="infra1" export NAME2="infra2" # 创建临时目录来存储将被分发到其它主机上的文件 mkdir -p /tmp/${HOST0}/ /tmp/${HOST1}/ /tmp/${HOST2}/ HOSTS=(${HOST0} ${HOST1} ${HOST2}) NAMES=(${NAME0} ${NAME1} ${NAME2}) for i in "${!HOSTS[@]}"; do HOST=${HOSTS[$i]} NAME=${NAMES[$i]} cat << EOF > /tmp/${HOST}/kubeadmcfg.yaml --- apiVersion: "kubeadm.k8s.io/v1beta4" kind: InitConfiguration nodeRegistration: name: ${NAME} localAPIEndpoint: advertiseAddress: ${HOST} --- apiVersion: "kubeadm.k8s.io/v1beta4" kind: ClusterConfiguration etcd: local: serverCertSANs: - "${HOST}" peerCertSANs: - "${HOST}" extraArgs: - name: initial-cluster value: ${NAMES[0]}=https://${HOSTS[0]}:2380,${NAMES[1]}=https://${HOSTS[1]}:2380,${NAMES[2]}=https://${HOSTS[2]}:2380 - name: initial-cluster-state value: new - name: name value: ${NAME} - name: listen-peer-urls value: https://${HOST}:2380 - name: listen-client-urls value: https://${HOST}:2379 - name: advertise-client-urls value: https://${HOST}:2379 - name: initial-advertise-peer-urls value: https://${HOST}:2380 EOF done
生成证书颁发机构。
如果你已经拥有 CA,那么唯一的操作是复制 CA 的
crt
和key
文件到etc/kubernetes/pki/etcd/ca.crt
和/etc/kubernetes/pki/etcd/ca.key
。 复制完这些文件后继续下一步,“为每个成员创建证书”。如果你还没有 CA,则在
$HOST0
(你为 kubeadm 生成配置文件的位置)上运行此命令。kubeadm init phase certs etcd-ca
这一操作创建如下两个文件:
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
为每个成员创建证书。
kubeadm init phase certs etcd-server --config=/tmp/${HOST2}/kubeadmcfg.yaml kubeadm init phase certs etcd-peer --config=/tmp/${HOST2}/kubeadmcfg.yaml kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST2}/kubeadmcfg.yaml kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST2}/kubeadmcfg.yaml cp -R /etc/kubernetes/pki /tmp/${HOST2}/ # 清理不可重复使用的证书 find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete kubeadm init phase certs etcd-server --config=/tmp/${HOST1}/kubeadmcfg.yaml kubeadm init phase certs etcd-peer --config=/tmp/${HOST1}/kubeadmcfg.yaml kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST1}/kubeadmcfg.yaml kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST1}/kubeadmcfg.yaml cp -R /etc/kubernetes/pki /tmp/${HOST1}/ find /etc/kubernetes/pki -not -name ca.crt -not -name ca.key -type f -delete kubeadm init phase certs etcd-server --config=/tmp/${HOST0}/kubeadmcfg.yaml kubeadm init phase certs etcd-peer --config=/tmp/${HOST0}/kubeadmcfg.yaml kubeadm init phase certs etcd-healthcheck-client --config=/tmp/${HOST0}/kubeadmcfg.yaml kubeadm init phase certs apiserver-etcd-client --config=/tmp/${HOST0}/kubeadmcfg.yaml # 不需要移动 certs 因为它们是给 HOST0 使用的 # 清理不应从此主机复制的证书 find /tmp/${HOST2} -name ca.key -type f -delete find /tmp/${HOST1} -name ca.key -type f -delete
复制证书和 kubeadm 配置。
证书已生成,现在必须将它们移动到对应的主机。
USER=ubuntu HOST=${HOST1} scp -r /tmp/${HOST}/* ${USER}@${HOST}: ssh ${USER}@${HOST} USER@HOST $ sudo -Es root@HOST $ chown -R root:root pki root@HOST $ mv pki /etc/kubernetes/
确保已经所有预期的文件都存在
$HOST0
所需文件的完整列表如下:/tmp/${HOST0} └── kubeadmcfg.yaml --- /etc/kubernetes/pki ├── apiserver-etcd-client.crt ├── apiserver-etcd-client.key └── etcd ├── ca.crt ├── ca.key ├── healthcheck-client.crt ├── healthcheck-client.key ├── peer.crt ├── peer.key ├── server.crt └── server.key
在
$HOST1
上:$HOME └── kubeadmcfg.yaml --- /etc/kubernetes/pki ├── apiserver-etcd-client.crt ├── apiserver-etcd-client.key └── etcd ├── ca.crt ├── healthcheck-client.crt ├── healthcheck-client.key ├── peer.crt ├── peer.key ├── server.crt └── server.key
在
$HOST2
上:$HOME └── kubeadmcfg.yaml --- /etc/kubernetes/pki ├── apiserver-etcd-client.crt ├── apiserver-etcd-client.key └── etcd ├── ca.crt ├── healthcheck-client.crt ├── healthcheck-client.key ├── peer.crt ├── peer.key ├── server.crt └── server.key
创建静态 Pod 清单。
既然证书和配置已经就绪,是时候去创建清单了。 在每台主机上运行
kubeadm
命令来生成 etcd 使用的静态清单。root@HOST0 $ kubeadm init phase etcd local --config=/tmp/${HOST0}/kubeadmcfg.yaml root@HOST1 $ kubeadm init phase etcd local --config=$HOME/kubeadmcfg.yaml root@HOST2 $ kubeadm init phase etcd local --config=$HOME/kubeadmcfg.yaml
可选:检查集群运行状况。
如果
etcdctl
不可用,你可以在容器镜像内运行此工具。 你可以使用crictl run
这类工具直接在容器运行时执行此操作,而不是通过 Kubernetes。ETCDCTL_API=3 etcdctl \ --cert /etc/kubernetes/pki/etcd/peer.crt \ --key /etc/kubernetes/pki/etcd/peer.key \ --cacert /etc/kubernetes/pki/etcd/ca.crt \ --endpoints https://${HOST0}:2379 endpoint health ... https://[HOST0 IP]:2379 is healthy: successfully committed proposal: took = 16.283339ms https://[HOST1 IP]:2379 is healthy: successfully committed proposal: took = 19.44402ms https://[HOST2 IP]:2379 is healthy: successfully committed proposal: took = 35.926451ms
- 将
${HOST0}
设置为要测试的主机的 IP 地址。
- 将
接下来
一旦拥有了一个正常工作的 3 成员的 etcd 集群, 你就可以基于使用 kubeadm 外部 etcd 的方法, 继续部署一个高可用的控制平面。
2.2.2.1.8 - 使用 kubeadm 配置集群中的每个 kubelet
Kubernetes v1.11 [stable]
kubeadm CLI 工具的生命周期与 kubelet 解耦;kubelet 是一个守护程序,在 Kubernetes 集群中的每个节点上运行。 当 Kubernetes 初始化或升级时,kubeadm CLI 工具由用户执行,而 kubelet 始终在后台运行。
由于kubelet是守护程序,因此需要通过某种初始化系统或服务管理器进行维护。 当使用 DEB 或 RPM 安装 kubelet 时,配置系统去管理 kubelet。 你可以改用其他服务管理器,但需要手动地配置。
集群中涉及的所有 kubelet 的一些配置细节都必须相同,
而其他配置方面则需要基于每个 kubelet 进行设置,以适应给定机器的不同特性(例如操作系统、存储和网络)。
你可以手动地管理 kubelet 的配置,但是 kubeadm 现在提供一种 KubeletConfiguration
API
类型用于集中管理 kubelet 的配置。
Kubelet 配置模式
以下各节讲述了通过使用 kubeadm 简化 kubelet 配置模式,而不是在每个节点上手动地管理 kubelet 配置。
将集群级配置传播到每个 kubelet 中
你可以通过 kubeadm init
和 kubeadm join
命令为 kubelet 提供默认值。
有趣的示例包括使用其他容器运行时或通过服务器设置不同的默认子网。
如果你想使用子网 10.96.0.0/12
作为服务的默认网段,你可以给 kubeadm 传递 --service-cidr
参数:
kubeadm init --service-cidr 10.96.0.0/12
现在,可以从该子网分配服务的虚拟 IP。
你还需要通过 kubelet 使用 --cluster-dns
标志设置 DNS 地址。
在集群中的每个管理器和节点上的 kubelet 的设置需要相同。
kubelet 提供了一个版本化的结构化 API 对象,该对象可以配置 kubelet
中的大多数参数,并将此配置推送到集群中正在运行的每个 kubelet 上。
此对象被称为 KubeletConfiguration
。
KubeletConfiguration
允许用户指定标志,例如用骆峰值代表集群的 DNS IP 地址,如下所示:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
clusterDNS:
- 10.96.0.10
有关 KubeletConfiguration
的更多详细信息,请参阅本节。
提供特定于某实例的配置细节
由于硬件、操作系统、网络或者其他主机特定参数的差异。某些主机需要特定的 kubelet 配置。 以下列表提供了一些示例。
由 kubelet 配置标志
--resolv-conf
指定的 DNS 解析文件的路径在操作系统之间可能有所不同, 它取决于你是否使用systemd-resolved
。 如果此路径错误,则在其 kubelet 配置错误的节点上 DNS 解析也将失败。除非你使用云驱动,否则默认情况下 Node API 对象的
.metadata.name
会被设置为计算机的主机名。 如果你需要指定一个与机器的主机名不同的节点名称,你可以使用--hostname-override
标志覆盖默认值。当前,kubelet 无法自动检测容器运行时使用的 cgroup 驱动程序, 但是值
--cgroup-driver
必须与容器运行时使用的 cgroup 驱动程序匹配,以确保 kubelet 的健康运行状况。要指定容器运行时,你必须用
--container-runtime-endpoint=<path>
标志来指定端点。
应用此类特定于实例的配置的推荐方法是使用
KubeletConfiguration
补丁。
使用 kubeadm 配置 kubelet
如果自定义的 KubeletConfiguration
API 对象使用像 kubeadm ... --config some-config-file.yaml
这样的配置文件进行传递,则可以配置 kubeadm 启动的 kubelet。
通过调用 kubeadm config print init-defaults --component-configs KubeletConfiguration
,
你可以看到此结构中的所有默认值。
也可以在基础 KubeletConfiguration
上应用实例特定的补丁。
阅读自定义 kubelet
来获取有关各个字段的更多信息。
使用 kubeadm init
时的工作流程
当调用 kubeadm init
时,kubelet 的配置会被写入磁盘 /var/lib/kubelet/config.yaml
,
并上传到集群 kube-system
命名空间的 kubelet-config
ConfigMap。
kubelet 配置信息也被写入 /etc/kubernetes/kubelet.conf
,其中包含集群内所有 kubelet 的基线配置。
此配置文件指向允许 kubelet 与 API 服务器通信的客户端证书。
这解决了将集群级配置传播到每个 kubelet 的需求。
针对为特定实例提供配置细节的第二种模式,
kubeadm 的解决方法是将环境文件写入 /var/lib/kubelet/kubeadm-flags.env
,其中包含了一个标志列表,
当 kubelet 启动时,该标志列表会传递给 kubelet 标志在文件中的显示方式如下:
KUBELET_KUBEADM_ARGS="--flag1=value1 --flag2=value2 ..."
除了启动 kubelet 时所使用的标志外,该文件还包含动态参数,例如 cgroup
驱动程序以及是否使用其他容器运行时套接字(--cri-socket
)。
将这两个文件编组到磁盘后,如果使用 systemd,则 kubeadm 尝试运行以下两个命令:
systemctl daemon-reload && systemctl restart kubelet
如果重新加载和重新启动成功,则正常的 kubeadm init
工作流程将继续。
使用 kubeadm join
时的工作流程
当运行 kubeadm join
时,kubeadm 使用 Bootstrap Token 证书执行 TLS 引导,该引导会获取一份证书,
该证书需要下载 kubelet-config
ConfigMap 并把它写入 /var/lib/kubelet/config.yaml
中。
动态环境文件的生成方式恰好与 kubeadm init
完全相同。
接下来,kubeadm
运行以下两个命令将新配置加载到 kubelet 中:
systemctl daemon-reload && systemctl restart kubelet
在 kubelet 加载新配置后,kubeadm 将写入 /etc/kubernetes/bootstrap-kubelet.conf
KubeConfig 文件中,
该文件包含 CA 证书和引导程序令牌。
kubelet 使用这些证书执行 TLS 引导程序并获取唯一的凭据,该凭据被存储在 /etc/kubernetes/kubelet.conf
中。
当 /etc/kubernetes/kubelet.conf
文件被写入后,kubelet 就完成了 TLS 引导过程。
kubeadm 在完成 TLS 引导过程后将删除 /etc/kubernetes/bootstrap-kubelet.conf
文件。
kubelet 的 systemd drop-in 文件
kubeadm
中附带了有关系统如何运行 kubelet 的 systemd 配置文件。
请注意 kubeadm CLI 命令不会修改此文件。
通过 kubeadm
包
安装的配置文件被写入 /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf
并由 systemd 使用。它对原来的
kubelet.service
作了增强。
如果你想进一步覆盖它,可以创建一个目录/etc/systemd/system/kubelet.service.d/
(而不是/usr/lib/systemd/kubelet.service.d/
),并将你自己的自定义配置写入到该目录下的一个文件中。
例如,您可以添加一个新的本地文件/etc/systemd/system/kubelet.service.d/local-overrides.conf
以覆盖“kubeadm”配置的单元设置。
以下是你可能在“/usr/lib/systemd/kubelet.service.d/10 kubeadm.conf”中找到的内容:
说明:
下面的内容只是一个例子。如果你不想使用包管理器, 请遵循(没有包管理器) 章节的指南。
[Service]
Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"
Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"
# 这是 "kubeadm init" 和 "kubeadm join" 运行时生成的文件,
# 动态地填充 KUBELET_KUBEADM_ARGS 变量
EnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env
# 这是一个文件,用户在不得已下可以将其用作替代 kubelet args。
# 用户最好使用 .NodeRegistration.KubeletExtraArgs 对象在配置文件中替代。
# KUBELET_EXTRA_ARGS 应该从此文件中获取。
EnvironmentFile=-/etc/default/kubelet
ExecStart=
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS
此文件指定由 kubeadm 为 kubelet 管理的所有文件的默认位置。
- 用于 TLS 引导程序的 KubeConfig 文件为
/etc/kubernetes/bootstrap-kubelet.conf
, 但仅当/etc/kubernetes/kubelet.conf
不存在时才能使用。 - 具有唯一 kubelet 标识的 KubeConfig 文件为
/etc/kubernetes/kubelet.conf
。 - 包含 kubelet 的组件配置的文件为
/var/lib/kubelet/config.yaml
。 - 包含的动态环境的文件
KUBELET_KUBEADM_ARGS
是来源于/var/lib/kubelet/kubeadm-flags.env
。 - 包含用户指定标志替代的文件
KUBELET_EXTRA_ARGS
是来源于/etc/default/kubelet
(对于 DEB),或者/etc/sysconfig/kubelet
(对于 RPM)。KUBELET_EXTRA_ARGS
在标志链中排在最后,并且在设置冲突时具有最高优先级。
Kubernetes 可执行文件和软件包内容
Kubernetes 版本对应的 DEB 和 RPM 软件包是:
软件包名称 | 描述 |
---|---|
kubeadm | 给 kubelet 安装 /usr/bin/kubeadm CLI 工具和 kubelet 的 systemd drop-in 文件。 |
kubelet | 安装 /usr/bin/kubelet 可执行文件。 |
kubectl | 安装 /usr/bin/kubectl 可执行文件。 |
cri-tools | 从 cri-tools git 仓库中安装 /usr/bin/crictl 可执行文件。 |
kubernetes-cni | 从 plugins git 仓库中安装 /opt/cni/bin 可执行文件。 |
2.2.2.1.9 - 使用 kubeadm 支持双协议栈
Kubernetes v1.23 [stable]
你的集群包含双协议栈组网支持, 这意味着集群网络允许你在两种地址族间任选其一。在集群中,控制面可以为同一个 Pod 或者 Service 同时赋予 IPv4 和 IPv6 地址。
准备开始
你需要已经遵从安装 kubeadm 中所给的步骤安装了 kubeadm 工具。
针对你要作为节点使用的每台服务器, 确保其允许 IPv6 转发。
启用 IPv6 数据包转发
检查 IPv6 数据包转发是否已被启用:
sysctl net.ipv6.conf.all.forwarding
如果输出为 net.ipv6.conf.all.forwarding = 1
,IPv6 数据包转发已被启用。
否则,IPv6 数据包转发尚未被启用。
手动启用 IPv6 数据包转发:
# 设置所需的 sysctl 参数,这些参数在重启后仍然有效
cat <<EOF | sudo tee -a /etc/sysctl.d/k8s.conf
net.ipv6.conf.all.forwarding = 1
EOF
# 应用 sysctl 参数而无需重启
sudo sysctl --system
你需要一个可以使用的 IPv4 和 IPv6 地址范围。集群操作人员通常对 IPv4 使用
私有的地址范围。对于 IPv6,集群操作人员通常会基于分配给他自己的地址范围,
从 2000::/3
中选择一个全局的单播地址块。你不需要将集群的 IP 地址范围路由到公众互联网。
所分配的 IP 地址数量应该与你计划运行的 Pod 和 Service 的数量相适应。
说明:
如果你在使用 kubeadm upgrade
命令升级现有的集群,kubeadm
不允许更改 Pod
的 IP 地址范围(“集群 CIDR”),也不允许更改集群的 Service 地址范围(“Service CIDR”)。
创建双协议栈集群
要使用 kubeadm init
创建一个双协议栈集群,你可以传递与下面的例子类似的命令行参数:
# 这里的地址范围仅作示例使用
kubeadm init --pod-network-cidr=10.244.0.0/16,2001:db8:42:0::/56 --service-cidr=10.96.0.0/16,2001:db8:42:1::/112
为了更便于理解,参看下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件,
该文件用于双协议栈控制面的主控制节点。
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
networking:
podSubnet: 10.244.0.0/16,2001:db8:42:0::/56
serviceSubnet: 10.96.0.0/16,2001:db8:42:1::/112
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: "10.100.0.1"
bindPort: 6443
nodeRegistration:
kubeletExtraArgs:
- name: "node-ip"
value: "10.100.0.2,fd00:1:2:3::2"
InitConfiguration 中的 advertiseAddress
给出 API 服务器将公告自身要监听的
IP 地址。advertiseAddress
的取值与 kubeadm init
的标志
--apiserver-advertise-address
的取值相同。
运行 kubeadm 来实例化双协议栈控制面节点:
kubeadm init --config=kubeadm-config.yaml
kube-controller-manager 标志 --node-cidr-mask-size-ipv4|--node-cidr-mask-size-ipv6
是使用默认值来设置的。参见配置 IPv4/IPv6 双协议栈。
说明:
标志 --apiserver-advertise-address
不支持双协议栈。
向双协议栈集群添加节点
在添加节点之前,请确保该节点具有 IPv6 可路由的网络接口并且启用了 IPv6 转发。
下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件示例用于向集群中添加工作节点。
apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
discovery:
bootstrapToken:
apiServerEndpoint: 10.100.0.1:6443
token: "clvldh.vjjwg16ucnhp94qr"
caCertHashes:
- "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
# 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration:
kubeletExtraArgs:
- name: "node-ip"
value: "10.100.0.2,fd00:1:2:3::3"
下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件示例用于向集群中添加另一个控制面节点。
apiVersion: kubeadm.k8s.io/v1beta4
kind: JoinConfiguration
controlPlane:
localAPIEndpoint:
advertiseAddress: "10.100.0.2"
bindPort: 6443
discovery:
bootstrapToken:
apiServerEndpoint: 10.100.0.1:6443
token: "clvldh.vjjwg16ucnhp94qr"
caCertHashes:
- "sha256:a4863cde706cfc580a439f842cc65d5ef112b7b2be31628513a9881cf0d9fe0e"
# 请更改上面的认证信息,使之与你的集群中实际使用的令牌和 CA 证书匹配
nodeRegistration:
kubeletExtraArgs:
- name: "node-ip"
value: "10.100.0.2,fd00:1:2:3::4"
JoinConfiguration.controlPlane 中的 advertiseAddress
设定 API 服务器将公告自身要监听的
IP 地址。advertiseAddress
的取值与 kubeadm join
的标志
--apiserver-advertise-address
的取值相同。
kubeadm join --config=kubeadm-config.yaml
创建单协议栈集群
说明:
双协议栈支持并不意味着你需要使用双协议栈来寻址。 你可以部署一个启用了双协议栈联网特性的单协议栈集群。
为了更便于理解,参看下面的名为 kubeadm-config.yaml
的 kubeadm
配置文件示例,
该文件用于单协议栈控制面节点。
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
networking:
podSubnet: 10.244.0.0/16
serviceSubnet: 10.96.0.0/16
接下来
- 验证 IPv4/IPv6 双协议栈联网
- 阅读双协议栈集群网络
- 进一步了解 kubeadm 配置格式
2.2.3 - Turnkey 云解决方案
本页列示 Kubernetes 认证解决方案供应商。 在每一个供应商分页,你可以学习如何安装和设置生产就绪的集群。
2.3 - 最佳实践
2.3.1 - 大规模集群的注意事项
集群包含多个运行着 Kubernetes 代理程序、 由控制平面管理的一组节点(物理机或虚拟机)。 Kubernetes v1.31 单个集群支持的最大节点数为 5,000。 更具体地说,Kubernetes 设计为满足以下所有标准的配置:
- 每个节点的 Pod 数量不超过 110
- 节点数不超过 5,000
- Pod 总数不超过 150,000
- 容器总数不超过 300,000
你可以通过添加或删除节点来对集群扩缩容。集群扩缩容的方式取决于集群的部署方式。
云供应商资源配额
为避免遇到云供应商配额问题,在创建具有大规模节点的集群时,请考虑以下事项:
- 请求增加云资源的配额,例如:
- 计算实例
- CPU
- 存储卷
- 使用中的 IP 地址
- 数据包过滤规则集
- 负载均衡数量
- 网络子网
- 日志流
- 由于某些云供应商限制了创建新实例的速度,因此通过分批启动新节点来控制集群扩展操作,并在各批之间有一个暂停。
控制面组件
对于大型集群,你需要一个具有足够计算能力和其他资源的控制平面。
通常,你将在每个故障区域运行一个或两个控制平面实例, 先垂直缩放这些实例,然后在到达下降点(垂直)后再水平缩放。
你应该在每个故障区域至少应运行一个实例,以提供容错能力。 Kubernetes 节点不会自动将流量引向相同故障区域中的控制平面端点。 但是,你的云供应商可能有自己的机制来执行此操作。
例如,使用托管的负载均衡器时,你可以配置负载均衡器发送源自故障区域 A 中的 kubelet 和 Pod 的流量, 并将该流量仅定向到也位于区域 A 中的控制平面主机。 如果单个控制平面主机或端点故障区域 A 脱机,则意味着区域 A 中的节点的所有控制平面流量现在都在区域之间发送。 在每个区域中运行多个控制平面主机能降低出现这种结果的可能性。
etcd 存储
为了提高大规模集群的性能,你可以将事件对象存储在单独的专用 etcd 实例中。
在创建集群时,你可以(使用自定义工具):
- 启动并配置额外的 etcd 实例
- 配置 API 服务器,将它用于存储事件
有关为大型集群配置和管理 etcd 的详细信息, 请参阅为 Kubernetes 运行 etcd 集群和使用 kubeadm 创建一个高可用 etcd 集群。
插件资源
Kubernetes 资源限制有助于最大程度地减少内存泄漏的影响以及 Pod 和容器可能对其他组件的其他方式的影响。 这些资源限制适用于插件资源, 就像它们适用于应用程序工作负载一样。
例如,你可以对日志组件设置 CPU 和内存限制:
...
containers:
- name: fluentd-cloud-logging
image: fluent/fluentd-kubernetes-daemonset:v1
resources:
limits:
cpu: 100m
memory: 200Mi
插件的默认限制通常基于从中小规模 Kubernetes 集群上运行每个插件的经验收集的数据。 插件在大规模集群上运行时,某些资源消耗常常比其默认限制更多。 如果在不调整这些值的情况下部署了大规模集群,则插件可能会不断被杀死,因为它们不断达到内存限制。 或者,插件可能会运行,但由于 CPU 时间片的限制而导致性能不佳。
为避免遇到集群插件资源问题,在创建大规模集群时,请考虑以下事项:
- 部分垂直扩展插件 —— 总有一个插件副本服务于整个集群或服务于整个故障区域。 对于这些附加组件,请在扩大集群时加大资源请求和资源限制。
- 许多水平扩展插件 —— 你可以通过运行更多的 Pod 来增加容量——但是在大规模集群下, 可能还需要稍微提高 CPU 或内存限制。 Vertical Pod Autoscaler 可以在 recommender 模式下运行,以提供有关请求和限制的建议数字。
- 一些插件在每个节点上运行一个副本,并由 DaemonSet 控制: 例如,节点级日志聚合器。与水平扩展插件的情况类似, 你可能还需要稍微提高 CPU 或内存限制。
接下来
VerticalPodAutoscaler
是一种自定义资源,你可以将其部署到集群中,帮助你管理 Pod 的资源请求和资源限制。 了解有关 Vertical Pod Autoscaler 的更多信息,了解如何用它扩展集群组件(包括对集群至关重要的插件)的信息。阅读关于集群自动扩缩容的信息。
- addon resizer 可帮助你在集群规模变化时自动调整插件的大小。
2.3.2 - 运行于多可用区环境
本页描述如何跨多个区(Zone)运行集群。
背景
Kubernetes 从设计上允许同一个 Kubernetes 集群跨多个失效区来运行, 通常这些区位于某个称作 区域(Region) 逻辑分组中。 主要的云提供商都将区域定义为一组失效区的集合(也称作 可用区(Availability Zones)), 能够提供一组一致的功能特性:每个区域内,各个可用区提供相同的 API 和服务。
典型的云体系结构都会尝试降低某个区中的失效影响到其他区中服务的概率。
控制面行为
所有的控制面组件 都支持以一组可相互替换的资源池的形式来运行,每个组件都有多个副本。
当你部署集群控制面时,应将控制面组件的副本跨多个失效区来部署。 如果可用性是一个很重要的指标,应该选择至少三个失效区, 并将每个控制面组件(API 服务器、调度器、etcd、控制器管理器)复制多个副本, 跨至少三个失效区来部署。如果你在运行云控制器管理器, 则也应该将该组件跨所选的三个失效区来部署。
说明:
Kubernetes 并不会为 API 服务器端点提供跨失效区的弹性。 你可以为集群 API 服务器使用多种技术来提升其可用性,包括使用 DNS 轮转、SRV 记录或者带健康检查的第三方负载均衡解决方案等等。
节点行为
Kubernetes 自动为负载资源(如 Deployment 或 StatefulSet) 跨集群中不同节点来部署其 Pod。 这种分布逻辑有助于降低失效带来的影响。
节点启动时,每个节点上的 kubelet 会向 Kubernetes API 中代表该 kubelet 的 Node 对象添加标签。 这些标签可能包含区信息。
如果你的集群跨了多个可用区或者地理区域,你可以使用节点标签,结合 Pod 拓扑分布约束 来控制如何在你的集群中多个失效域之间分布 Pod。这里的失效域可以是地理区域、可用区甚至是特定节点。 这些提示信息使得调度器能够更好地调度 Pod,以实现更好的可用性,降低因为某种失效给整个工作负载带来的风险。
例如,你可以设置一种约束,确保某个 StatefulSet 中的 3 个副本都运行在不同的可用区中, 只要其他条件允许。你可以通过声明的方式来定义这种约束, 而不需要显式指定每个工作负载使用哪些可用区。
跨多个区分布节点
Kubernetes 的核心逻辑并不会帮你创建节点,你需要自行完成此操作,或者使用类似 Cluster API 这类工具来替你管理节点。
使用类似 Cluster API 这类工具,你可以跨多个失效域来定义一组用做你的集群工作节点的机器, 以及当整个区的服务出现中断时如何自动治愈集群的策略。
为 Pod 手动指定区
你可以应用节点选择算符约束到你所创建的 Pod 上,或者为 Deployment、StatefulSet 或 Job 这类工作负载资源中的 Pod 模板设置此类约束。
跨区的存储访问
当创建持久卷时,Kubernetes 会自动向那些链接到特定区的 PersistentVolume 添加区标签。
调度器通过其
NoVolumeZoneConflict
断言确保申领给定 PersistentVolume 的 Pod
只会被调度到该卷所在的可用区。
请注意,添加区标签的方法可能取决于你的云提供商和存储制备器。 请参阅具体的环境文档,确保配置正确。
请注意,添加区标签的方法可能会根据你所使用的云提供商和存储制备器而有所不同。 为确保配置正确,请始终参阅你的环境的特定文档。
你可以为 PersistentVolumeClaim 指定 StorageClass 以设置该类中的存储可以使用的失效域(区)。 要了解如何配置能够感知失效域或区的 StorageClass, 请参阅可用的拓扑逻辑。
网络
Kubernetes 自身不提供与可用区相关的联网配置。
你可以使用网络插件
来配置集群的联网,该网络解决方案可能拥有一些与可用区相关的元素。
例如,如果你的云提供商支持 type=LoadBalancer
的 Service,
则负载均衡器可能仅会将请求流量发送到运行在负责处理给定连接的负载均衡器组件所在的区。
请查阅云提供商的文档了解详细信息。
对于自定义的或本地集群部署,也可以考虑这些因素。 Service 和 Ingress 的行为, 包括处理不同失效区的方法,在很大程度上取决于你的集群是如何搭建的。
失效恢复
在搭建集群时,你可能需要考虑当某区域中的所有失效区都同时掉线时,是否以及如何恢复服务。 例如,你是否要求在某个区中至少有一个节点能够运行 Pod? 请确保任何对集群很关键的修复工作都不要指望集群中至少有一个健康节点。 例如:当所有节点都不健康时,你可能需要运行某个修复性的 Job, 该 Job 要设置特定的容忍度, 以便修复操作能够至少将一个节点恢复为可用状态。
Kubernetes 对这类问题没有现成的解决方案;不过这也是要考虑的因素之一。
接下来
要了解调度器如何在集群中放置 Pod 并遵从所配置的约束, 可参阅调度与驱逐。
2.3.3 - 校验节点设置
节点一致性测试
节点一致性测试是一个容器化的测试框架,提供了针对节点的系统验证和功能测试。 测试验证节点是否满足 Kubernetes 的最低要求;通过测试的节点有资格加入 Kubernetes 集群。
该测试主要检测节点是否满足 Kubernetes 的最低要求,通过检测的节点有资格加入 Kubernetes 集群。
节点的前提条件
要运行节点一致性测试,节点必须满足与标准 Kubernetes 节点相同的前提条件。 节点至少应安装以下守护程序:
- 与 CRI 兼容的容器运行时,例如 Docker、Containerd 和 CRI-O
- kubelet
运行节点一致性测试
要运行节点一致性测试,请执行以下步骤:
- 得出 kubelet 的
--kubeconfig
的值;例如:--kubeconfig=/var/lib/kubelet/config.yaml
。 由于测试框架启动了本地控制平面来测试 kubelet,因此使用http://localhost:8080
作为API 服务器的 URL。 一些其他的 kubelet 命令行参数可能会被用到:--cloud-provider
:如果使用--cloud-provider=gce
,需要移除这个参数来运行测试。
使用以下命令运行节点一致性测试:
# $CONFIG_DIR 是你 kubelet 的 Pod manifest 路径。 # $LOG_DIR 是测试的输出路径。 sudo docker run -it --rm --privileged --net=host \ -v /:/rootfs -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \ registry.k8s.io/node-test:0.2
针对其他硬件体系结构运行节点一致性测试
Kubernetes 也为其他硬件体系结构的系统提供了节点一致性测试的 Docker 镜像:
架构 | 镜像 |
---|---|
amd64 | node-test-amd64 |
arm | node-test-arm |
arm64 | node-test-arm64 |
运行特定的测试
要运行特定测试,请使用你希望运行的测试的特定表达式覆盖环境变量 FOCUS
。
sudo docker run -it --rm --privileged --net=host \
-v /:/rootfs:ro -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \
-e FOCUS=MirrorPod \ # Only run MirrorPod test
registry.k8s.io/node-test:0.2
要跳过特定的测试,请使用你希望跳过的测试的常规表达式覆盖环境变量 SKIP
。
sudo docker run -it --rm --privileged --net=host \
-v /:/rootfs:ro -v $CONFIG_DIR:$CONFIG_DIR -v $LOG_DIR:/var/result \
-e SKIP=MirrorPod \ # 运行除 MirrorPod 测试外的所有一致性测试内容
registry.k8s.io/node-test:0.2
节点一致性测试是 节点端到端测试的容器化版本。 默认情况下,它会运行所有一致性测试。
理论上,只要合理地配置容器和挂载所需的卷,就可以运行任何的节点端到端测试用例。 但是这里强烈建议只运行一致性测试,因为运行非一致性测试需要很多复杂的配置。
注意事项
- 测试会在节点上遗留一些 Docker 镜像,包括节点一致性测试本身的镜像和功能测试相关的镜像。
- 测试会在节点上遗留一些死的容器。这些容器是在功能测试的过程中创建的。
2.3.4 - 强制实施 Pod 安全性标准
本页提供实施 Pod 安全标准(Pod Security Standards) 时的一些最佳实践。
使用内置的 Pod 安全性准入控制器
Kubernetes v1.25 [stable]
Pod 安全性准入控制器 尝试替换已被废弃的 PodSecurityPolicies。
配置所有集群名字空间
完全未经配置的名字空间应该被视为集群安全模型中的重大缺陷。 我们建议花一些时间来分析在每个名字空间中执行的负载的类型, 并通过引用 Pod 安全性标准来确定每个负载的合适级别。 未设置标签的名字空间应该视为尚未被评估。
针对所有名字空间中的所有负载都具有相同的安全性需求的场景, 我们提供了一个示例 用来展示如何批量应用 Pod 安全性标签。
拥抱最小特权原则
在一个理想环境中,每个名字空间中的每个 Pod 都会满足 restricted
策略的需求。
不过,这既不可能也不现实,某些负载会因为合理的原因而需要特权上的提升。
- 允许
privileged
负载的名字空间需要建立并实施适当的访问控制机制。 - 对于运行在特权宽松的名字空间中的负载,需要维护其独特安全性需求的文档。 如果可能的话,要考虑如何进一步约束这些需求。
采用多种模式的策略
Pod 安全性标准准入控制器的 audit
和 warn
模式(mode)
能够在不影响现有负载的前提下,让该控制器更方便地收集关于 Pod 的重要的安全信息。
针对所有名字空间启用这些模式是一种好的实践,将它们设置为你最终打算 enforce
的
期望的 级别和版本。这一阶段中所生成的警告和审计注解信息可以帮助你到达这一状态。
如果你期望负载的作者能够作出变更以便适应期望的级别,可以启用 warn
模式。
如果你希望使用审计日志了监控和驱动变更,以便负载能够适应期望的级别,可以启用 audit
模式。
当你将 enforce
模式设置为期望的取值时,这些模式在不同的场合下仍然是有用的:
- 通过将
warn
设置为enforce
相同的级别,客户可以在尝试创建无法通过合法检查的 Pod (或者包含 Pod 模板的资源)时收到警告信息。这些信息会帮助于更新资源使其合规。 - 在将
enforce
锁定到特定的非最新版本的名字空间中,将audit
和warn
模式设置为enforce
一样的级别而非latest
版本, 这样可以方便看到之前版本所允许但当前最佳实践中被禁止的设置。
第三方替代方案
Kubernetes 生态系统中也有一些其他强制实施安全设置的替代方案处于开发状态中:
采用 内置的 方案(例如 PodSecurity 准入控制器)还是第三方工具, 这一决策完全取决于你自己的情况。在评估任何解决方案时,对供应链的信任都是至关重要的。 最终,使用前述方案中的 任何 一种都好过放任自流。
2.3.5 - PKI 证书和要求
Kubernetes 需要 PKI 证书才能进行基于 TLS 的身份验证。如果你是使用 kubeadm 安装的 Kubernetes, 则会自动生成集群所需的证书。 你也可以自己生成证书 --- 例如,不将私钥存储在 API 服务器上, 可以让私钥更加安全。此页面说明了集群必需的证书。
集群是如何使用证书的
Kubernetes 需要 PKI 才能执行以下操作:
服务器证书
客户端证书
- 针对每个 kubelet 的客户端证书,用于 API 服务器作为 Kubernetes API 的客户端进行身份验证
- 每个 API 服务器的客户端证书,用于向 etcd 进行身份验证
- 控制器管理器与 API 服务器进行安全通信的客户端证书
- 调度程序与 API 服务器进行安全通信的客户端证书
- 客户端证书(每个节点一个),用于 kube-proxy 向 API 服务器进行身份验证
- 集群管理员向 API 服务器进行身份验证的可选客户端证书
- 前端代理的可选客户端证书
kubelet 的服务器和客户端证书
为了建立安全连接并向 kubelet 进行身份验证,API 服务器需要客户端证书和密钥对。
在此场景中,证书的使用有两种方法:
共享证书:kube-apiserver 可以使用与验证其客户端相同的证书和密钥对。 这意味着现有证书(例如
apiserver.crt
和apiserver.key
)可用于与 kubelet 服务器进行通信。单独的证书:或者,kube-apiserver 可以生成新的客户端证书和密钥对,以验证其与 kubelet 服务器的通信。 在这种情况下,将创建一个名为
kubelet-client.crt
的不同证书及其对应的私钥kubelet-client.key
。
说明:
只有当你运行 kube-proxy
并要支持扩展 API 服务器时,
才需要 front-proxy
证书。
etcd 还实现了双向 TLS 来对客户端和对其他对等节点进行身份验证。
证书存储位置
假如你通过 kubeadm 安装 Kubernetes,大多数证书会被存储在 /etc/kubernetes/pki
中。
本文档中的所有路径都是相对于该目录的,但用户账号证书除外,kubeadm 将其放在 /etc/kubernetes
中。
手动配置证书
如果你不想通过 kubeadm 生成所需证书,你可以使用一个单根 CA 来创建这些证书,或者直接提供所有证书。 参见证书以进一步了解如何创建自己的证书授权机构。 更多关于管理证书的信息,请参阅使用 kubeadm 进行证书管理。
单根 CA
你可以创建由管理员控制的单根 CA。这个根 CA 可以创建多个中间 CA, 并将所有进一步的创建委托给 Kubernetes 本身。
需要这些 CA:
路径 | 默认 CN | 描述 |
---|---|---|
ca.crt、key | kubernetes-ca | Kubernetes 通用 CA |
etcd/ca.crt、key | etcd-ca | 与 etcd 相关的所有功能 |
front-proxy-ca.crt、key | kubernetes-front-proxy-ca | 用于前端代理 |
上面的 CA 之外,还需要获取用于服务账号管理的密钥对,也就是 sa.key
和 sa.pub
。
下面的例子说明了上表中所示的 CA 密钥和证书文件。
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-ca.key
所有的证书
如果你不想将 CA 的私钥拷贝至你的集群中,你也可以自己生成全部的证书。
需要这些证书:
默认 CN | 父级 CA | O(位于 Subject 中) | kind | 主机(SAN) |
---|---|---|---|---|
kube-etcd | etcd-ca | server、client | <hostname> 、<Host_IP> 、localhost 、127.0.0.1 | |
kube-etcd-peer | etcd-ca | server、client | <hostname> 、<Host_IP> 、localhost 、127.0.0.1 | |
kube-etcd-healthcheck-client | etcd-ca | client | ||
kube-apiserver-etcd-client | etcd-ca | client | ||
kube-apiserver | kubernetes-ca | server | <hostname> 、<Host_IP> 、<advertise_IP> 1 | |
kube-apiserver-kubelet-client | kubernetes-ca | system:masters | client | |
front-proxy-client | kubernetes-front-proxy-ca | client |
说明:
不使用超级用户组 system:masters
来控制 kube-apiserver-kubelet-client
,
可以使用一个权限较低的组。kubeadm 使用 kubeadm:cluster-admins
组来达到这个目的。
其中 kind
对应一种或多种类型的 x509 密钥用途,也可记录在
CertificateSigningRequest
类型的 .spec.usages
中:
kind | 密钥用途 |
---|---|
server | 数字签名、密钥加密、服务端认证 |
client | 数字签名、密钥加密、客户端认证 |
说明:
上面列出的 Host/SAN 是获取工作集群的推荐配置方式; 如果需要特殊安装,则可以在所有服务器证书上添加其他 SAN。
说明:
对于 kubeadm 用户:
- 不使用私钥并将证书复制到集群 CA 的方案,在 kubeadm 文档中将这种方案称为外部 CA。
- 如果将上表与 kubeadm 生成的 PKI 进行比较,你会注意到,如果使用外部 etcd,则不会生成
kube-etcd
、kube-etcd-peer
和kube-etcd-healthcheck-client
证书。
证书路径
证书应放置在建议的路径中(以便 kubeadm 使用)。无论使用什么位置,都应使用给定的参数指定路径。
默认 CN | 建议的密钥路径 | 建议的证书路径 | 命令 | 密钥参数 | 证书参数 |
---|---|---|---|---|---|
etcd-ca | etcd/ca.key | etcd/ca.crt | kube-apiserver | --etcd-cafile | |
kube-apiserver-etcd-client | apiserver-etcd-client.key | apiserver-etcd-client.crt | kube-apiserver | --etcd-keyfile | --etcd-certfile |
kubernetes-ca | ca.key | ca.crt | kube-apiserver | --client-ca-file | |
kubernetes-ca | ca.key | ca.crt | kube-controller-manager | --cluster-signing-key-file | --client-ca-file, --root-ca-file, --cluster-signing-cert-file |
kube-apiserver | apiserver.key | apiserver.crt | kube-apiserver | --tls-private-key-file | --tls-cert-file |
kube-apiserver-kubelet-client | apiserver-kubelet-client.key | apiserver-kubelet-client.crt | kube-apiserver | --kubelet-client-key | --kubelet-client-certificate |
front-proxy-ca | front-proxy-ca.key | front-proxy-ca.crt | kube-apiserver | --requestheader-client-ca-file | |
front-proxy-ca | front-proxy-ca.key | front-proxy-ca.crt | kube-controller-manager | --requestheader-client-ca-file | |
front-proxy-client | front-proxy-client.key | front-proxy-client.crt | kube-apiserver | --proxy-client-key-file | --proxy-client-cert-file |
etcd-ca | etcd/ca.key | etcd/ca.crt | etcd | --trusted-ca-file, --peer-trusted-ca-file | |
kube-etcd | etcd/server.key | etcd/server.crt | etcd | --key-file | --cert-file |
kube-etcd-peer | etcd/peer.key | etcd/peer.crt | etcd | --peer-key-file | --peer-cert-file |
etcd-ca | etcd/ca.crt | etcdctl | --cacert | ||
kube-etcd-healthcheck-client | etcd/healthcheck-client.key | etcd/healthcheck-client.crt | etcdctl | --key | --cert |
注意事项同样适用于服务账号密钥对:
私钥路径 | 公钥路径 | 命令 | 参数 |
---|---|---|---|
sa.key | kube-controller-manager | --service-account-private-key-file | |
sa.pub | kube-apiserver | --service-account-key-file |
下面的例子展示了自行生成所有密钥和证书时所需要提供的文件路径。 这些路径基于前面的表格。
/etc/kubernetes/pki/etcd/ca.key
/etc/kubernetes/pki/etcd/ca.crt
/etc/kubernetes/pki/apiserver-etcd-client.key
/etc/kubernetes/pki/apiserver-etcd-client.crt
/etc/kubernetes/pki/ca.key
/etc/kubernetes/pki/ca.crt
/etc/kubernetes/pki/apiserver.key
/etc/kubernetes/pki/apiserver.crt
/etc/kubernetes/pki/apiserver-kubelet-client.key
/etc/kubernetes/pki/apiserver-kubelet-client.crt
/etc/kubernetes/pki/front-proxy-ca.key
/etc/kubernetes/pki/front-proxy-ca.crt
/etc/kubernetes/pki/front-proxy-client.key
/etc/kubernetes/pki/front-proxy-client.crt
/etc/kubernetes/pki/etcd/server.key
/etc/kubernetes/pki/etcd/server.crt
/etc/kubernetes/pki/etcd/peer.key
/etc/kubernetes/pki/etcd/peer.crt
/etc/kubernetes/pki/etcd/healthcheck-client.key
/etc/kubernetes/pki/etcd/healthcheck-client.crt
/etc/kubernetes/pki/sa.key
/etc/kubernetes/pki/sa.pub
为用户账号配置证书
你必须手动配置以下管理员账号和服务账号:
文件名 | 凭据名称 | 默认 CN | O (位于 Subject 中) |
---|---|---|---|
admin.conf | default-admin | kubernetes-admin | <admin-group> |
super-admin.conf | default-super-admin | kubernetes-super-admin | system:masters |
kubelet.conf | default-auth | system:node:<nodeName> (参阅注释) | system:nodes |
controller-manager.conf | default-controller-manager | system:kube-controller-manager | |
scheduler.conf | default-scheduler | system:kube-scheduler |
说明:
kubelet.conf
中 <nodeName>
的值必须与 kubelet 向 apiserver 注册时提供的节点名称的值完全匹配。
有关更多详细信息,请阅读节点授权。
说明:
在上面的例子中,<admin-group>
是实现特定的。
一些工具在默认的 admin.conf
中签署证书,以成为 system:masters
组的一部分。
system:masters
是一个紧急情况下的超级用户组,可以绕过 Kubernetes 的授权层,如 RBAC。
另外,某些工具不会生成单独的 super-admin.conf
将证书绑定到这个超级用户组。
kubeadm 在 kubeconfig 文件中生成两个单独的管理员证书。
一个是在 admin.conf
中,带有 Subject: O = kubeadm:cluster-admins, CN = kubernetes-admin
。
kubeadm:cluster-admins
是绑定到 cluster-admin
ClusterRole 的自定义组。
这个文件在所有由 kubeadm 管理的控制平面机器上生成。
另一个是在 super-admin.conf
中,具有 Subject: O = system:masters, CN = kubernetes-super-admin
。
这个文件只在调用了 kubeadm init
的节点上生成。
对于每个配置,请都使用给定的通用名称(CN)和组织(O)生成 x509 证书/密钥对。
为每个配置运行下面的
kubectl
命令:KUBECONFIG=<文件名> kubectl config set-cluster default-cluster --server=https://<主机ip>:6443 --certificate-authority <kubernetes-ca路径> --embed-certs KUBECONFIG=<文件名> kubectl config set-credentials <凭据名称> --client-key <密钥路径>.pem --client-certificate <证书路径>.pem --embed-certs KUBECONFIG=<文件名> kubectl config set-context default-system --cluster default-cluster --user <凭据名称> KUBECONFIG=<文件名> kubectl config use-context default-system
这些文件用途如下:
文件名 | 命令 | 说明 |
---|---|---|
admin.conf | kubectl | 配置集群的管理员 |
super-admin.conf | kubectl | 为集群配置超级管理员用户 |
kubelet.conf | kubelet | 集群中的每个节点都需要一份 |
controller-manager.conf | kube-controller-manager | 必须添加到 manifests/kube-controller-manager.yaml 清单中 |
scheduler.conf | kube-scheduler | 必须添加到 manifests/kube-scheduler.yaml 清单中 |
下面是前表中所列文件的完整路径。
/etc/kubernetes/admin.conf
/etc/kubernetes/super-admin.conf
/etc/kubernetes/kubelet.conf
/etc/kubernetes/controller-manager.conf
/etc/kubernetes/scheduler.conf
3 - 概念
概念部分帮助你了解 Kubernetes 系统的各个部分以及 Kubernetes 用来表示集群的抽象概念, 并帮助你更深入地理解 Kubernetes 是如何工作的。
3.1 - 概述
此页面是 Kubernetes 的概述。
Kubernetes 这个名字源于希腊语,意为“舵手”或“飞行员”。K8s 这个缩写是因为 K 和 s 之间有 8 个字符的关系。 Google 在 2014 年开源了 Kubernetes 项目。 Kubernetes 建立在 Google 大规模运行生产工作负载十几年经验的基础上, 结合了社区中最优秀的想法和实践。
为什么需要 Kubernetes,它能做什么?
容器是打包和运行应用程序的好方式。在生产环境中, 你需要管理运行着应用程序的容器,并确保服务不会下线。 例如,如果一个容器发生故障,则你需要启动另一个容器。 如果此行为交由给系统处理,是不是会更容易一些?
这就是 Kubernetes 要来做的事情! Kubernetes 为你提供了一个可弹性运行分布式系统的框架。 Kubernetes 会满足你的扩展要求、故障转移你的应用、提供部署模式等。 例如,Kubernetes 可以轻松管理系统的 Canary (金丝雀) 部署。
Kubernetes 为你提供:
服务发现和负载均衡
Kubernetes 可以使用 DNS 名称或自己的 IP 地址来暴露容器。 如果进入容器的流量很大, Kubernetes 可以负载均衡并分配网络流量,从而使部署稳定。
存储编排
Kubernetes 允许你自动挂载你选择的存储系统,例如本地存储、公共云提供商等。
自动部署和回滚
你可以使用 Kubernetes 描述已部署容器的所需状态, 它可以以受控的速率将实际状态更改为期望状态。 例如,你可以自动化 Kubernetes 来为你的部署创建新容器, 删除现有容器并将它们的所有资源用于新容器。
自动完成装箱计算
你为 Kubernetes 提供许多节点组成的集群,在这个集群上运行容器化的任务。 你告诉 Kubernetes 每个容器需要多少 CPU 和内存 (RAM)。 Kubernetes 可以将这些容器按实际情况调度到你的节点上,以最佳方式利用你的资源。
自我修复
Kubernetes 将重新启动失败的容器、替换容器、杀死不响应用户定义的运行状况检查的容器, 并且在准备好服务之前不将其通告给客户端。
密钥与配置管理
Kubernetes 允许你存储和管理敏感信息,例如密码、OAuth 令牌和 SSH 密钥。 你可以在不重建容器镜像的情况下部署和更新密钥和应用程序配置,也无需在堆栈配置中暴露密钥。
- 批处理执行 除了服务外,Kubernetes 还可以管理你的批处理和 CI(持续集成)工作负载,如有需要,可以替换失败的容器。
- 水平扩缩 使用简单的命令、用户界面或根据 CPU 使用率自动对你的应用进行扩缩。
- IPv4/IPv6 双栈 为 Pod(容器组)和 Service(服务)分配 IPv4 和 IPv6 地址。
- 为可扩展性设计 在不改变上游源代码的情况下为你的 Kubernetes 集群添加功能。
Kubernetes 不是什么
Kubernetes 不是传统的、包罗万象的 PaaS(平台即服务)系统。 由于 Kubernetes 是在容器级别运行,而非在硬件级别,它提供了 PaaS 产品共有的一些普遍适用的功能, 例如部署、扩展、负载均衡,允许用户集成他们的日志记录、监控和警报方案。 但是,Kubernetes 不是单体式(monolithic)系统,那些默认解决方案都是可选、可插拔的。 Kubernetes 为构建开发人员平台提供了基础,但是在重要的地方保留了用户选择权,能有更高的灵活性。
Kubernetes:
- 不限制支持的应用程序类型。 Kubernetes 旨在支持极其多种多样的工作负载,包括无状态、有状态和数据处理工作负载。 如果应用程序可以在容器中运行,那么它应该可以在 Kubernetes 上很好地运行。
- 不部署源代码,也不构建你的应用程序。 持续集成(CI)、交付和部署(CI/CD)工作流取决于组织的文化和偏好以及技术要求。
- 不提供应用程序级别的服务作为内置服务,例如中间件(例如消息中间件)、 数据处理框架(例如 Spark)、数据库(例如 MySQL)、缓存、集群存储系统 (例如 Ceph)。这样的组件可以在 Kubernetes 上运行,并且/或者可以由运行在 Kubernetes 上的应用程序通过可移植机制(例如开放服务代理)来访问。
- 不是日志记录、监视或警报的解决方案。 它集成了一些功能作为概念证明,并提供了收集和导出指标的机制。
- 不提供也不要求配置用的语言、系统(例如 jsonnet),它提供了声明性 API, 该声明性 API 可以由任意形式的声明性规范所构成。
- 不提供也不采用任何全面的机器配置、维护、管理或自我修复系统。
- 此外,Kubernetes 不仅仅是一个编排系统,实际上它消除了编排的需要。 编排的技术定义是执行已定义的工作流程:首先执行 A,然后执行 B,再执行 C。 而 Kubernetes 包含了一组独立可组合的控制过程,可以持续地将当前状态驱动到所提供的预期状态。 你不需要在乎如何从 A 移动到 C,也不需要集中控制,这使得系统更易于使用且功能更强大、 系统更健壮,更为弹性和可扩展。
Kubernetes 的历史背景
让我们回顾一下为何 Kubernetes 能够裨益四方。
传统部署时代:
早期,各个组织是在物理服务器上运行应用程序。 由于无法限制在物理服务器中运行的应用程序资源使用,因此会导致资源分配问题。 例如,如果在同一台物理服务器上运行多个应用程序, 则可能会出现一个应用程序占用大部分资源的情况,而导致其他应用程序的性能下降。 一种解决方案是将每个应用程序都运行在不同的物理服务器上, 但是当某个应用程序资源利用率不高时,剩余资源无法被分配给其他应用程序, 而且维护许多物理服务器的成本很高。
虚拟化部署时代:
因此,虚拟化技术被引入了。虚拟化技术允许你在单个物理服务器的 CPU 上运行多台虚拟机(VM)。 虚拟化能使应用程序在不同 VM 之间被彼此隔离,且能提供一定程度的安全性, 因为一个应用程序的信息不能被另一应用程序随意访问。
虚拟化技术能够更好地利用物理服务器的资源,并且因为可轻松地添加或更新应用程序, 而因此可以具有更高的可扩缩性,以及降低硬件成本等等的好处。 通过虚拟化,你可以将一组物理资源呈现为可丢弃的虚拟机集群。
每个 VM 是一台完整的计算机,在虚拟化硬件之上运行所有组件,包括其自己的操作系统。
容器部署时代:
容器类似于 VM,但是更宽松的隔离特性,使容器之间可以共享操作系统(OS)。 因此,容器比起 VM 被认为是更轻量级的。且与 VM 类似,每个容器都具有自己的文件系统、CPU、内存、进程空间等。 由于它们与基础架构分离,因此可以跨云和 OS 发行版本进行移植。
容器因具有许多优势而变得流行起来,例如:
- 敏捷应用程序的创建和部署:与使用 VM 镜像相比,提高了容器镜像创建的简便性和效率。
- 持续开发、集成和部署:通过快速简单的回滚(由于镜像不可变性), 提供可靠且频繁的容器镜像构建和部署。
- 关注开发与运维的分离:在构建、发布时创建应用程序容器镜像,而不是在部署时, 从而将应用程序与基础架构分离。
- 可观察性:不仅可以显示 OS 级别的信息和指标,还可以显示应用程序的运行状况和其他指标信号。
- 跨开发、测试和生产的环境一致性:在笔记本计算机上也可以和在云中运行一样的应用程序。
- 跨云和操作系统发行版本的可移植性:可在 Ubuntu、RHEL、CoreOS、本地、 Google Kubernetes Engine 和其他任何地方运行。
- 以应用程序为中心的管理:提高抽象级别,从在虚拟硬件上运行 OS 到使用逻辑资源在 OS 上运行应用程序。
- 松散耦合、分布式、弹性、解放的微服务:应用程序被分解成较小的独立部分, 并且可以动态部署和管理 - 而不是在一台大型单机上整体运行。
- 资源隔离:可预测的应用程序性能。
- 资源利用:高效率和高密度。
接下来
- 查阅 Kubernetes 组件
- 查阅 Kubernetes API
- 查阅 Cluster 架构
- 开始 Kubernetes 的建置吧!
3.1.1 - Kubernetes 组件
本文档概述了一个正常运行的 Kubernetes 集群所需的各种组件。
核心组件
Kubernetes 集群由控制平面和一个或多个工作节点组成。以下是主要组件的简要概述:
控制平面组件(Control Plane Components)
管理集群的整体状态:
- kube-apiserver
- 公开 Kubernetes HTTP API 的核心组件服务器
- etcd
- 具备一致性和高可用性的键值存储,用于所有 API 服务器的数据存储
- kube-scheduler
- 查找尚未绑定到节点的 Pod,并将每个 Pod 分配给合适的节点。
- kube-controller-manager
- 运行控制器来实现 Kubernetes API 行为。
- cloud-controller-manager (optional)
- 与底层云驱动集成
Node 组件
在每个节点上运行,维护运行的 Pod 并提供 Kubernetes 运行时环境:
- kubelet
- 确保 Pod 及其容器正常运行。
- kube-proxy(可选)
- 维护节点上的网络规则以实现 Service 的功能。
- 容器运行时(Container runtime)
- 负责运行容器的软件,阅读容器运行时以了解更多信息。
你的集群可能需要每个节点上运行额外的软件;例如,你可能还需要在 Linux 节点上运行 systemd 来监督本地组件。
插件(Addons)
插件扩展了 Kubernetes 的功能。一些重要的例子包括:
架构灵活性
Kubernetes 允许灵活地部署和管理这些组件。此架构可以适应各种需求,从小型开发环境到大规模生产部署。
有关每个组件的详细信息以及配置集群架构的各种方法, 请参阅集群架构页面。
3.1.2 - Kubernetes 对象
本页说明了在 Kubernetes API 中是如何表示 Kubernetes 对象的,
以及如何使用 .yaml
格式的文件表示 Kubernetes 对象。
理解 Kubernetes 对象
在 Kubernetes 系统中,Kubernetes 对象是持久化的实体。 Kubernetes 使用这些实体去表示整个集群的状态。 具体而言,它们描述了如下信息:
- 哪些容器化应用正在运行(以及在哪些节点上运行)
- 可以被应用使用的资源
- 关于应用运行时行为的策略,比如重启策略、升级策略以及容错策略
Kubernetes 对象是一种“意向表达(Record of Intent)”。一旦创建该对象, Kubernetes 系统将不断工作以确保该对象存在。通过创建对象,你本质上是在告知 Kubernetes 系统,你想要的集群工作负载状态看起来应是什么样子的, 这就是 Kubernetes 集群所谓的期望状态(Desired State)。
操作 Kubernetes 对象 —— 无论是创建、修改或者删除 —— 需要使用
Kubernetes API。
比如,当使用 kubectl
命令行接口(CLI)时,CLI 会调用必要的 Kubernetes API;
也可以在程序中使用客户端库,
来直接调用 Kubernetes API。
对象规约(Spec)与状态(Status)
几乎每个 Kubernetes 对象包含两个嵌套的对象字段,它们负责管理对象的配置:
对象 spec
(规约) 和对象 status
(状态)。
对于具有 spec
的对象,你必须在创建对象时设置其内容,描述你希望对象所具有的特征:
期望状态(Desired State)。
status
描述了对象的当前状态(Current State),它是由 Kubernetes
系统和组件设置并更新的。在任何时刻,Kubernetes
控制平面
都一直在积极地管理着对象的实际状态,以使之达成期望状态。
例如,Kubernetes 中的 Deployment 对象能够表示运行在集群中的应用。
当创建 Deployment 时,你可能会设置 Deployment 的 spec
,指定该应用要有 3 个副本运行。
Kubernetes 系统读取 Deployment 的 spec
,
并启动我们所期望的应用的 3 个实例 —— 更新状态以与规约相匹配。
如果这些实例中有的失败了(一种状态变更),Kubernetes 系统会通过执行修正操作来响应
spec
和 status
间的不一致 —— 意味着它会启动一个新的实例来替换。
关于对象 spec、status 和 metadata 的更多信息,可参阅 Kubernetes API 约定。
描述 Kubernetes 对象
创建 Kubernetes 对象时,必须提供对象的 spec
,用来描述该对象的期望状态,
以及关于对象的一些基本信息(例如名称)。
当使用 Kubernetes API 创建对象时(直接创建或经由 kubectl
创建),
API 请求必须在请求主体中包含 JSON 格式的信息。
大多数情况下,你会通过 清单(Manifest) 文件为 kubectl
提供这些信息。
按照惯例,清单是 YAML 格式的(你也可以使用 JSON 格式)。
像 kubectl
这样的工具在通过 HTTP 进行 API 请求时,
会将清单中的信息转换为 JSON 或其他受支持的序列化格式。
这里有一个清单示例文件,展示了 Kubernetes Deployment 的必需字段和对象 spec
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # 告知 Deployment 运行 2 个与该模板匹配的 Pod
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
与上面使用清单文件来创建 Deployment 类似,另一种方式是使用 kubectl
命令行接口(CLI)的
kubectl apply
命令,
将 .yaml
文件作为参数。下面是一个示例:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
输出类似下面这样:
deployment.apps/nginx-deployment created
必需字段
在想要创建的 Kubernetes 对象所对应的清单(YAML 或 JSON 文件)中,需要配置的字段如下:
apiVersion
- 创建该对象所使用的 Kubernetes API 的版本kind
- 想要创建的对象的类别metadata
- 帮助唯一标识对象的一些数据,包括一个name
字符串、UID
和可选的namespace
spec
- 你所期望的该对象的状态
对每个 Kubernetes 对象而言,其 spec
之精确格式都是不同的,包含了特定于该对象的嵌套字段。
Kubernetes API 参考可以帮助你找到想要使用
Kubernetes 创建的所有对象的规约格式。
例如,参阅 Pod API 参考文档中
spec
字段。
对于每个 Pod,其 .spec
字段设置了 Pod 及其期望状态(例如 Pod 中每个容器的容器镜像名称)。
另一个对象规约的例子是 StatefulSet API 中的
spec
字段。
对于 StatefulSet 而言,其 .spec
字段设置了 StatefulSet 及其期望状态。
在 StatefulSet 的 .spec
内,有一个为 Pod 对象提供的模板。
该模板描述了 StatefulSet 控制器为了满足 StatefulSet 规约而要创建的 Pod。
不同类型的对象可以有不同的 .status
信息。API 参考页面给出了 .status
字段的详细结构,
以及针对不同类型 API 对象的具体内容。
说明:
请查看配置最佳实践来获取有关编写 YAML 配置文件的更多信息。
服务器端字段验证
从 Kubernetes v1.25 开始,API
服务器提供了服务器端字段验证,
可以检测对象中未被识别或重复的字段。它在服务器端提供了 kubectl --validate
的所有功能。
kubectl
工具使用 --validate
标志来设置字段验证级别。它接受值
ignore
、warn
和 strict
,同时还接受值 true
(等同于 strict
)和
false
(等同于 ignore
)。kubectl
的默认验证设置为 --validate=true
。
Strict
- 严格的字段验证,验证失败时会报错
Warn
- 执行字段验证,但错误会以警告形式提供而不是拒绝请求
Ignore
- 不执行服务器端字段验证
当 kubectl
无法连接到支持字段验证的 API 服务器时,它将回退为使用客户端验证。
Kubernetes 1.27 及更高版本始终提供字段验证;较早的 Kubernetes 版本可能没有此功能。
如果你的集群版本低于 v1.27,可以查阅适用于你的 Kubernetes 版本的文档。
接下来
如果你刚开始学习 Kubernetes,可以进一步阅读以下信息:
- 最重要的 Kubernetes 基本对象 Pod。
- Deployment 对象。
- Kubernetes 中的控制器。
- kubectl 和 kubectl 命令。
Kubernetes 对象管理
介绍了如何使用 kubectl
来管理对象。
如果你还没有安装 kubectl
,你可能需要安装 kubectl。
从总体上了解 Kubernetes API,可以查阅:
若要更深入地了解 Kubernetes 对象,可以阅读本节的其他页面:
3.1.2.1 - Kubernetes 对象管理
kubectl
命令行工具支持多种不同的方式来创建和管理 Kubernetes
对象。
本文档概述了不同的方法。
阅读 Kubectl book 来了解 kubectl
管理对象的详细信息。
管理技巧
警告:
应该只使用一种技术来管理 Kubernetes 对象。混合和匹配技术作用在同一对象上将导致未定义行为。
管理技术 | 作用于 | 建议的环境 | 支持的写者 | 学习难度 |
---|---|---|---|---|
指令式命令 | 活跃对象 | 开发项目 | 1+ | 最低 |
指令式对象配置 | 单个文件 | 生产项目 | 1 | 中等 |
声明式对象配置 | 文件目录 | 生产项目 | 1+ | 最高 |
指令式命令
使用指令式命令时,用户可以在集群中的活动对象上进行操作。用户将操作传给
kubectl
命令作为参数或标志。
这是开始或者在集群中运行一次性任务的推荐方法。因为这个技术直接在活跃对象 上操作,所以它不提供以前配置的历史记录。
例子
通过创建 Deployment 对象来运行 nginx 容器的实例:
kubectl create deployment nginx --image nginx
权衡
与对象配置相比的优点:
- 命令用单个动词表示。
- 命令仅需一步即可对集群进行更改。
与对象配置相比的缺点:
- 命令不与变更审查流程集成。
- 命令不提供与更改关联的审核跟踪。
- 除了实时内容外,命令不提供记录源。
- 命令不提供用于创建新对象的模板。
指令式对象配置
在指令式对象配置中,kubectl 命令指定操作(创建,替换等),可选标志和 至少一个文件名。指定的文件必须包含 YAML 或 JSON 格式的对象的完整定义。
有关对象定义的详细信息,请查看 API 参考。
警告:
replace
指令式命令将现有规范替换为新提供的规范,并放弃对配置文件中
缺少的对象的所有更改。此方法不应与对象规约被独立于配置文件进行更新的
资源类型一起使用。比如类型为 LoadBalancer
的服务,它的 externalIPs
字段就是独立于集群配置进行更新。
例子
创建配置文件中定义的对象:
kubectl create -f nginx.yaml
删除两个配置文件中定义的对象:
kubectl delete -f nginx.yaml -f redis.yaml
通过覆盖活动配置来更新配置文件中定义的对象:
kubectl replace -f nginx.yaml
权衡
与指令式命令相比的优点:
- 对象配置可以存储在源控制系统中,比如 Git。
- 对象配置可以与流程集成,例如在推送和审计之前检查更新。
- 对象配置提供了用于创建新对象的模板。
与指令式命令相比的缺点:
- 对象配置需要对对象架构有基本的了解。
- 对象配置需要额外的步骤来编写 YAML 文件。
与声明式对象配置相比的优点:
- 指令式对象配置行为更加简单易懂。
- 从 Kubernetes 1.5 版本开始,指令对象配置更加成熟。
与声明式对象配置相比的缺点:
- 指令式对象配置更适合文件,而非目录。
- 对活动对象的更新必须反映在配置文件中,否则会在下一次替换时丢失。
声明式对象配置
使用声明式对象配置时,用户对本地存储的对象配置文件进行操作,但是用户
未定义要对该文件执行的操作。
kubectl
会自动检测每个文件的创建、更新和删除操作。
这使得配置可以在目录上工作,根据目录中配置文件对不同的对象执行不同的操作。
说明:
声明式对象配置保留其他编写者所做的修改,即使这些更改并未合并到对象配置文件中。
可以通过使用 patch
API 操作仅写入观察到的差异,而不是使用 replace
API
操作来替换整个对象配置来实现。
例子
处理 configs
目录中的所有对象配置文件,创建并更新活跃对象。
可以首先使用 diff
子命令查看将要进行的更改,然后在进行应用:
kubectl diff -f configs/
kubectl apply -f configs/
递归处理目录:
kubectl diff -R -f configs/
kubectl apply -R -f configs/
权衡
与指令式对象配置相比的优点:
- 对活动对象所做的更改即使未合并到配置文件中,也会被保留下来。
- 声明性对象配置更好地支持对目录进行操作并自动检测每个文件的操作类型(创建,修补,删除)。
与指令式对象配置相比的缺点:
- 声明式对象配置难于调试并且出现异常时结果难以理解。
- 使用 diff 产生的部分更新会创建复杂的合并和补丁操作。
接下来
3.1.2.2 - 对象名称和 ID
每个 Kubernetes 对象也有一个 UID 来标识在整个集群中的唯一性。
比如,在同一个名字空间
中只能有一个名为 myapp-1234
的 Pod,但是可以命名一个 Pod 和一个 Deployment 同为 myapp-1234
。
对于用户提供的非唯一性的属性,Kubernetes 提供了标签(Label)和 注解(Annotation)机制。
名称
客户端提供的字符串,引用资源 URL 中的对象,如/api/v1/pods/some name
。
某一时刻,只能有一个给定类型的对象具有给定的名称。但是,如果删除该对象,则可以创建同名的新对象。
名称在同一资源的所有 API 版本中必须是唯一的。 这些 API 资源通过各自的 API 组、资源类型、名字空间(对于划分名字空间的资源)和名称来区分。 换言之,API 版本在此上下文中是不相关的。
说明:
当对象所代表的是一个物理实体(例如代表一台物理主机的 Node)时, 如果在 Node 对象未被删除并重建的条件下,重新创建了同名的物理主机, 则 Kubernetes 会将新的主机看作是老的主机,这可能会带来某种不一致性。
以下是比较常见的四种资源命名约束。
DNS 子域名
很多资源类型需要可以用作 DNS 子域名的名称。 DNS 子域名的定义可参见 RFC 1123。 这一要求意味着名称必须满足如下规则:
- 不能超过 253 个字符
- 只能包含小写字母、数字,以及 '-' 和 '.'
- 必须以字母数字开头
- 必须以字母数字结尾
RFC 1123 标签名
某些资源类型需要其名称遵循 RFC 1123 所定义的 DNS 标签标准。也就是命名必须满足如下规则:
- 最多 63 个字符
- 只能包含小写字母、数字,以及 '-'
- 必须以字母数字开头
- 必须以字母数字结尾
RFC 1035 标签名
某些资源类型需要其名称遵循 RFC 1035 所定义的 DNS 标签标准。也就是命名必须满足如下规则:
- 最多 63 个字符
- 只能包含小写字母、数字,以及 '-'
- 必须以字母开头
- 必须以字母数字结尾
说明:
RFC 1035 和 RFC 1123 标签标准之间的唯一区别是 RFC 1123 标签允许以数字开头,而 RFC 1035 标签只能以小写字母字符开头。
路径分段名称
某些资源类型要求名称能被安全地用作路径中的片段。
换句话说,其名称不能是 .
、..
,也不可以包含 /
或 %
这些字符。
下面是一个名为 nginx-demo
的 Pod 的配置清单:
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
说明:
某些资源类型可能具有额外的命名约束。
UID
Kubernetes 系统生成的字符串,唯一标识对象。
在 Kubernetes 集群的整个生命周期中创建的每个对象都有一个不同的 UID,它旨在区分类似实体的历史事件。
Kubernetes UID 是全局唯一标识符(也叫 UUID)。 UUID 是标准化的,见 ISO/IEC 9834-8 和 ITU-T X.667。
接下来
- 进一步了解 Kubernetes 标签和注解。
- 参阅 Kubernetes 标识符和名称的设计文档。
3.1.2.3 - 标签和选择算符
标签(Labels) 是附加到 Kubernetes 对象(比如 Pod)上的键值对。 标签旨在用于指定对用户有意义且相关的对象的标识属性,但不直接对核心系统有语义含义。 标签可以用于组织和选择对象的子集。标签可以在创建时附加到对象,随后可以随时添加和修改。 每个对象都可以定义一组键/值标签。每个键对于给定对象必须是唯一的。
"metadata": {
"labels": {
"key1" : "value1",
"key2" : "value2"
}
}
标签能够支持高效的查询和监听操作,对于用户界面和命令行是很理想的。 应使用注解记录非识别信息。
动机
标签使用户能够以松散耦合的方式将他们自己的组织结构映射到系统对象,而无需客户端存储这些映射。
服务部署和批处理流水线通常是多维实体(例如,多个分区或部署、多个发行序列、多个层,每层多个微服务)。 管理通常需要交叉操作,这打破了严格的层次表示的封装,特别是由基础设施而不是用户确定的严格的层次结构。
示例标签:
"release" : "stable"
,"release" : "canary"
"environment" : "dev"
,"environment" : "qa"
,"environment" : "production"
"tier" : "frontend"
,"tier" : "backend"
,"tier" : "cache"
"partition" : "customerA"
,"partition" : "customerB"
"track" : "daily"
,"track" : "weekly"
有一些常用标签的例子;你可以任意制定自己的约定。 请记住,标签的 Key 对于给定对象必须是唯一的。
语法和字符集
标签是键值对。有效的标签键有两个段:可选的前缀和名称,用斜杠(/
)分隔。
名称段是必需的,必须小于等于 63 个字符,以字母数字字符([a-z0-9A-Z]
)开头和结尾,
带有破折号(-
),下划线(_
),点( .
)和之间的字母数字。
前缀是可选的。如果指定,前缀必须是 DNS 子域:由点(.
)分隔的一系列 DNS 标签,总共不超过 253 个字符,
后跟斜杠(/
)。
如果省略前缀,则假定标签键对用户是私有的。
向最终用户对象添加标签的自动系统组件(例如 kube-scheduler
、kube-controller-manager
、
kube-apiserver
、kubectl
或其他第三方自动化工具)必须指定前缀。
kubernetes.io/
和 k8s.io/
前缀是为 Kubernetes 核心组件保留的。
有效标签值:
- 必须为 63 个字符或更少(可以为空)
- 除非标签值为空,必须以字母数字字符(
[a-z0-9A-Z]
)开头和结尾 - 包含破折号(
-
)、下划线(_
)、点(.
)和字母或数字
例如,以下是一个清单 (manifest),适用于具有 environment: production
和 app: nginx
这两个标签的 Pod:
apiVersion: v1
kind: Pod
metadata:
name: label-demo
labels:
environment: production
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
标签选择算符
与名称和 UID 不同, 标签不支持唯一性。通常,我们希望许多对象携带相同的标签。
通过标签选择算符,客户端/用户可以识别一组对象。标签选择算符是 Kubernetes 中的核心分组原语。
API 目前支持两种类型的选择算符:基于等值的和基于集合的。
标签选择算符可以由逗号分隔的多个需求组成。
在多个需求的情况下,必须满足所有要求,因此逗号分隔符充当逻辑与(&&
)运算符。
空标签选择算符或者未指定的选择算符的语义取决于上下文, 支持使用选择算符的 API 类别应该将算符的合法性和含义用文档记录下来。
说明:
对于某些 API 类别(例如 ReplicaSet)而言,两个实例的标签选择算符不得在命名空间内重叠, 否则它们的控制器将互相冲突,无法确定应该存在的副本个数。
注意:
对于基于等值的和基于集合的条件而言,不存在逻辑或(||
)操作符。
你要确保你的过滤语句按合适的方式组织。
基于等值的需求
基于等值或基于不等值的需求允许按标签键和值进行过滤。
匹配对象必须满足所有指定的标签约束,尽管它们也可能具有其他标签。
可接受的运算符有 =
、==
和 !=
三种。
前两个表示相等(并且是同义词),而后者表示不相等。例如:
environment = production
tier != frontend
前者选择所有资源,其键名等于 environment
,值等于 production
。
后者选择所有资源,其键名等于 tier
,值不同于 frontend
,所有资源都没有带有 tier
键的标签。
可以使用逗号运算符来过滤 production
环境中的非 frontend
层资源:environment=production,tier!=frontend
。
基于等值的标签要求的一种使用场景是 Pod 要指定节点选择标准。
例如,下面的示例 Pod 选择存在 accelerator
标签且值为 nvidia-tesla-p100
的节点。
apiVersion: v1
kind: Pod
metadata:
name: cuda-test
spec:
containers:
- name: cuda-test
image: "registry.k8s.io/cuda-vector-add:v0.1"
resources:
limits:
nvidia.com/gpu: 1
nodeSelector:
accelerator: nvidia-tesla-p100
基于集合的需求
基于集合的标签需求允许你通过一组值来过滤键。
支持三种操作符:in
、notin
和 exists
(只可以用在键标识符上)。例如:
environment in (production, qa)
tier notin (frontend, backend)
partition
!partition
- 第一个示例选择了所有键等于
environment
并且值等于production
或者qa
的资源。 - 第二个示例选择了所有键等于
tier
并且值不等于frontend
或者backend
的资源,以及所有没有tier
键标签的资源。 - 第三个示例选择了所有包含了有
partition
标签的资源;没有校验它的值。 - 第四个示例选择了所有没有
partition
标签的资源;没有校验它的值。
类似地,逗号分隔符充当与运算符。因此,使用 partition
键(无论为何值)和
environment
不同于 qa
来过滤资源可以使用 partition, environment notin (qa)
来实现。
基于集合的标签选择算符是相等标签选择算符的一般形式,因为 environment=production
等同于 environment in (production)
;!=
和 notin
也是类似的。
基于集合的要求可以与基于相等的要求混合使用。例如:partition in (customerA, customerB),environment!=qa
。
API
LIST 和 WATCH 过滤
对于 list 和 watch 操作,你可以指定标签选择算符过滤返回的对象集;你可以使用查询参数来指定过滤条件。 (了解 Kubernetes 中的 watch 操作细节,请参阅 高效检测变更)。 两种需求都是允许的。(这里显示的是它们出现在 URL 查询字符串中)
- 基于等值的需求:
?labelSelector=environment%3Dproduction,tier%3Dfrontend
- 基于集合的需求:
?labelSelector=environment+in+%28production%2Cqa%29%2Ctier+in+%28frontend%29
两种标签选择算符都可以通过 REST 客户端用于 list 或者 watch 资源。
例如,使用 kubectl
定位 apiserver
,可以使用基于等值的标签选择算符可以这么写:
kubectl get pods -l environment=production,tier=frontend
或者使用基于集合的需求:
kubectl get pods -l 'environment in (production),tier in (frontend)'
正如刚才提到的,基于集合的需求更具有表达力。例如,它们可以实现值的或操作:
kubectl get pods -l 'environment in (production, qa)'
或者通过notin运算符限制不匹配:
kubectl get pods -l 'environment,environment notin (frontend)'
在 API 对象中设置引用
一些 Kubernetes 对象,例如 services
和 replicationcontrollers
,
也使用了标签选择算符去指定了其他资源的集合,例如
pods。
Service 和 ReplicationController
一个 Service
指向的一组 Pod 是由标签选择算符定义的。同样,一个 ReplicationController
应该管理的 Pod 的数量也是由标签选择算符定义的。
两个对象的标签选择算符都是在 json
或者 yaml
文件中使用映射定义的,并且只支持
基于等值需求的选择算符:
"selector": {
"component" : "redis",
}
或者
selector:
component: redis
这个选择算符(分别在 json
或者 yaml
格式中)等价于 component=redis
或 component in (redis)
。
支持基于集合需求的资源
比较新的资源,例如 Job
、
Deployment
、
ReplicaSet
和
DaemonSet
,
也支持基于集合的需求。
selector:
matchLabels:
component: redis
matchExpressions:
- { key: tier, operator: In, values: [cache] }
- { key: environment, operator: NotIn, values: [dev] }
matchLabels
是由 {key,value}
对组成的映射。
matchLabels
映射中的单个 {key,value}
等同于 matchExpressions
的元素,
其 key
字段为 "key",operator
为 "In",而 values
数组仅包含 "value"。
matchExpressions
是 Pod 选择算符需求的列表。
有效的运算符包括 In
、NotIn
、Exists
和 DoesNotExist
。
在 In
和 NotIn
的情况下,设置的值必须是非空的。
来自 matchLabels
和 matchExpressions
的所有要求都按逻辑与的关系组合到一起
-- 它们必须都满足才能匹配。
选择节点集
通过标签进行选择的一个用例是确定节点集,方便 Pod 调度。 有关更多信息,请参阅选择节点文档。
有效地使用标签
到目前为止我们使用的示例中的资源最多使用了一个标签。 在许多情况下,应使用多个标签来区分不同集合。
例如,不同的应用可能会为 app
标签设置不同的值。
但是,类似 guestbook 示例
这样的多层应用,还需要区分每一层。前端可能会带有以下标签:
labels:
app: guestbook
tier: frontend
Redis 的主从节点会有不同的 tier
标签,甚至还有一个额外的 role
标签:
labels:
app: guestbook
tier: backend
role: master
以及
labels:
app: guestbook
tier: backend
role: replica
标签使得我们能够按照所指定的任何维度对我们的资源进行切片和切块:
kubectl apply -f examples/guestbook/all-in-one/guestbook-all-in-one.yaml
kubectl get pods -Lapp -Ltier -Lrole
NAME READY STATUS RESTARTS AGE APP TIER ROLE
guestbook-fe-4nlpb 1/1 Running 0 1m guestbook frontend <none>
guestbook-fe-ght6d 1/1 Running 0 1m guestbook frontend <none>
guestbook-fe-jpy62 1/1 Running 0 1m guestbook frontend <none>
guestbook-redis-master-5pg3b 1/1 Running 0 1m guestbook backend master
guestbook-redis-replica-2q2yf 1/1 Running 0 1m guestbook backend replica
guestbook-redis-replica-qgazl 1/1 Running 0 1m guestbook backend replica
my-nginx-divi2 1/1 Running 0 29m nginx <none> <none>
my-nginx-o0ef1 1/1 Running 0 29m nginx <none> <none>
kubectl get pods -lapp=guestbook,role=replica
NAME READY STATUS RESTARTS AGE
guestbook-redis-replica-2q2yf 1/1 Running 0 3m
guestbook-redis-replica-qgazl 1/1 Running 0 3m
更新标签
有时需要要在创建新资源之前对现有的 Pod 和其它资源重新打标签。
这可以用 kubectl label
完成。
例如,如果想要将所有 NGINX Pod 标记为前端层,运行:
kubectl label pods -l app=nginx tier=fe
pod/my-nginx-2035384211-j5fhi labeled
pod/my-nginx-2035384211-u2c7e labeled
pod/my-nginx-2035384211-u3t6x labeled
首先用标签 "app=nginx" 过滤所有的 Pod,然后用 "tier=fe" 标记它们。 想要查看你刚设置了标签的 Pod,请运行:
kubectl get pods -l app=nginx -L tier
NAME READY STATUS RESTARTS AGE TIER
my-nginx-2035384211-j5fhi 1/1 Running 0 23m fe
my-nginx-2035384211-u2c7e 1/1 Running 0 23m fe
my-nginx-2035384211-u3t6x 1/1 Running 0 23m fe
此命令将输出所有 "app=nginx" 的 Pod,并有一个额外的描述 Pod 所在分层的标签列
(用参数 -L
或者 --label-columns
标明)。
想要了解更多信息,请参考标签和
kubectl label
命令文档。
接下来
3.1.2.4 - 名字空间
在 Kubernetes 中,名字空间(Namespace) 提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一名字空间内的资源名称要唯一,但跨名字空间时没有这个要求。 名字空间作用域仅针对带有名字空间的对象, (例如 Deployment、Service 等),这种作用域对集群范围的对象 (例如 StorageClass、Node、PersistentVolume 等)不适用。
何时使用多个名字空间
名字空间适用于存在很多跨多个团队或项目的用户的场景。对于只有几到几十个用户的集群,根本不需要创建或考虑名字空间。当需要名字空间提供的功能时,请开始使用它们。
名字空间为名称提供了一个范围。资源的名称需要在名字空间内是唯一的,但不能跨名字空间。 名字空间不能相互嵌套,每个 Kubernetes 资源只能在一个名字空间中。
名字空间是在多个用户之间划分集群资源的一种方法(通过资源配额)。
不必使用多个名字空间来分隔仅仅轻微不同的资源,例如同一软件的不同版本: 应该使用标签来区分同一名字空间中的不同资源。
说明:
对于生产集群,请考虑不要使用 default
名字空间,而是创建其他名字空间来使用。
初始名字空间
Kubernetes 启动时会创建四个初始名字空间:
default
- Kubernetes 包含这个名字空间,以便于你无需创建新的名字空间即可开始使用新集群。
kube-node-lease
- 该名字空间包含用于与各个节点关联的 Lease(租约)对象。 节点租约允许 kubelet 发送心跳, 由此控制面能够检测到节点故障。
kube-public
- 所有的客户端(包括未经身份验证的客户端)都可以读取该名字空间。 该名字空间主要预留为集群使用,以便某些资源需要在整个集群中可见可读。 该名字空间的公共属性只是一种约定而非要求。
kube-system
- 该名字空间用于 Kubernetes 系统创建的对象。
使用名字空间
名字空间的创建和删除在名字空间的管理指南文档描述。
说明:
避免使用前缀 kube-
创建名字空间,因为它是为 Kubernetes 系统名字空间保留的。
查看名字空间
你可以使用以下命令列出集群中现存的名字空间:
kubectl get namespace
NAME STATUS AGE
default Active 1d
kube-node-lease Active 1d
kube-public Active 1d
kube-system Active 1d
为请求设置名字空间
要为当前请求设置名字空间,请使用 --namespace
参数。
例如:
kubectl run nginx --image=nginx --namespace=<名字空间名称>
kubectl get pods --namespace=<名字空间名称>
设置名字空间偏好
你可以永久保存名字空间,以用于对应上下文中所有后续 kubectl 命令。
kubectl config set-context --current --namespace=<名字空间名称>
# 验证
kubectl config view --minify | grep namespace:
名字空间和 DNS
当你创建一个服务时, Kubernetes 会创建一个相应的 DNS 条目。
该条目的形式是 <服务名称>.<名字空间名称>.svc.cluster.local
,这意味着如果容器只使用
<服务名称>
,它将被解析到本地名字空间的服务。这对于跨多个名字空间(如开发、测试和生产)
使用相同的配置非常有用。如果你希望跨名字空间访问,则需要使用完全限定域名(FQDN)。
因此,所有的名字空间名称都必须是合法的 RFC 1123 DNS 标签。
警告:
通过创建与公共顶级域名同名的名字空间, 这些名字空间中的服务可以拥有与公共 DNS 记录重叠的、较短的 DNS 名称。 所有名字空间中的负载在执行 DNS 查找时, 如果查找的名称没有尾部句点, 就会被重定向到这些服务上,因此呈现出比公共 DNS 更高的优先序。
为了缓解这类问题,需要将创建名字空间的权限授予可信的用户。 如果需要,你可以额外部署第三方的安全控制机制, 例如以准入 Webhook 的形式,阻止用户创建与公共 TLD 同名的名字空间。
并非所有对象都在名字空间中
大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某些名字空间中。 但是名字空间资源本身并不在名字空间中。而且底层资源, 例如节点和持久化卷不属于任何名字空间。
查看哪些 Kubernetes 资源在名字空间中,哪些不在名字空间中:
# 位于名字空间中的资源
kubectl api-resources --namespaced=true
# 不在名字空间中的资源
kubectl api-resources --namespaced=false
自动打标签
Kubernetes 1.22 [stable]
Kubernetes 控制面会为所有名字空间设置一个不可变更的标签
kubernetes.io/metadata.name
。
标签的值是名字空间的名称。
接下来
3.1.2.5 - 注解
你可以使用 Kubernetes 注解为对象附加任意的非标识的元数据。 客户端程序(例如工具和库)能够获取这些元数据信息。
为对象附加元数据
你可以使用标签或注解将元数据附加到 Kubernetes 对象。 标签可以用来选择对象和查找满足某些条件的对象集合。 相反,注解不用于标识和选择对象。 注解中的元数据,可以很小,也可以很大,可以是结构化的,也可以是非结构化的,能够包含标签不允许的字符。 可以在同一对象的元数据中同时使用标签和注解。
注解和标签一样,是键/值对:
"metadata": {
"annotations": {
"key1" : "value1",
"key2" : "value2"
}
}
说明:
Map 中的键和值必须是字符串。 换句话说,你不能使用数字、布尔值、列表或其他类型的键或值。
以下是一些例子,用来说明哪些信息可以使用注解来记录:
- 由声明性配置所管理的字段。 将这些字段附加为注解,能够将它们与客户端或服务端设置的默认值、 自动生成的字段以及通过自动调整大小或自动伸缩系统设置的字段区分开来。
- 构建、发布或镜像信息(如时间戳、发布 ID、Git 分支、PR 数量、镜像哈希、仓库地址)。
- 指向日志记录、监控、分析或审计仓库的指针。
可用于调试目的的客户端库或工具信息:例如,名称、版本和构建信息。
用户或者工具/系统的来源信息,例如来自其他生态系统组件的相关对象的 URL。
轻量级上线工具的元数据信息:例如,配置或检查点。
负责人员的电话或呼机号码,或指定在何处可以找到该信息的目录条目,如团队网站。
从用户到最终运行的指令,以修改行为或使用非标准功能。
你可以将这类信息存储在外部数据库或目录中而不使用注解, 但这样做就使得开发人员很难生成用于部署、管理、自检的客户端共享库和工具。
语法和字符集
注解(Annotations) 存储的形式是键/值对。有效的注解键分为两部分:
可选的前缀和名称,以斜杠(/
)分隔。
名称段是必需项,并且必须在 63 个字符以内,以字母数字字符([a-z0-9A-Z]
)开头和结尾,
并允许使用破折号(-
),下划线(_
),点(.
)和字母数字。
前缀是可选的。如果指定,则前缀必须是 DNS 子域:一系列由点(.
)分隔的 DNS 标签,
总计不超过 253 个字符,后跟斜杠(/
)。
如果省略前缀,则假定注解键对用户是私有的。 由系统组件添加的注解
(例如,kube-scheduler
,kube-controller-manager
,kube-apiserver
,kubectl
或其他第三方组件),必须为终端用户添加注解前缀。
kubernetes.io/
和 k8s.io/
前缀是为 Kubernetes 核心组件保留的。
例如,下面是一个 Pod 的清单,其注解中包含 imageregistry: https://hub.docker.com/
:
apiVersion: v1
kind: Pod
metadata:
name: annotations-demo
annotations:
imageregistry: "https://hub.docker.com/"
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
接下来
- 进一步了解标签和选择算符。
- 查找众所周知的标签、注解和污点。
3.1.2.6 - 字段选择器
“字段选择器(Field selectors)”允许你根据一个或多个资源字段的值筛选 Kubernetes 对象。 下面是一些使用字段选择器查询的例子:
metadata.name=my-service
metadata.namespace!=default
status.phase=Pending
下面这个 kubectl
命令将筛选出
status.phase
字段值为 Running
的所有 Pod:
kubectl get pods --field-selector status.phase=Running
说明:
字段选择器本质上是资源“过滤器(Filters)”。默认情况下,字段选择器/过滤器是未被应用的,
这意味着指定类型的所有资源都会被筛选出来。
这使得 kubectl get pods
和 kubectl get pods --field-selector ""
这两个 kubectl
查询是等价的。
支持的字段
不同的 Kubernetes 资源类型支持不同的字段选择器。
所有资源类型都支持 metadata.name
和 metadata.namespace
字段。
使用不被支持的字段选择器会产生错误。例如:
kubectl get ingress --field-selector foo.bar=baz
Error from server (BadRequest): Unable to find "ingresses" that match label selector "", field selector "foo.bar=baz": "foo.bar" is not a known field selector: only "metadata.name", "metadata.namespace"
支持字段列表
类别 | 字段 |
---|---|
Pod | spec.nodeName spec.restartPolicy spec.schedulerName spec.serviceAccountName spec.hostNetwork status.phase status.podIP status.nominatedNodeName |
Event | involvedObject.kind involvedObject.namespace involvedObject.name involvedObject.uid involvedObject.apiVersion involvedObject.resourceVersion involvedObject.fieldPath reason reportingComponent source type |
Secret | type |
Namespace | status.phase |
ReplicaSet | status.replicas |
ReplicationController | status.replicas |
Job | status.successful |
Node | spec.unschedulable |
CertificateSigningRequest | spec.signerName |
支持的操作符
你可在字段选择器中使用 =
、==
和 !=
(=
和 ==
的意义是相同的)操作符。
例如,下面这个 kubectl
命令将筛选所有不属于 default
命名空间的 Kubernetes 服务:
kubectl get services --all-namespaces --field-selector metadata.namespace!=default
说明:
基于集合的操作符
(in
、notin
、exists
)不支持字段选择算符。
链式选择器
同标签和其他选择器一样,
字段选择器可以通过使用逗号分隔的列表组成一个选择链。
下面这个 kubectl
命令将筛选 status.phase
字段不等于 Running
同时
spec.restartPolicy
字段等于 Always
的所有 Pod:
kubectl get pods --field-selector=status.phase!=Running,spec.restartPolicy=Always
多种资源类型
你能够跨多种资源类型来使用字段选择器。
下面这个 kubectl
命令将筛选出所有不在 default
命名空间中的 StatefulSet 和 Service:
kubectl get statefulsets,services --all-namespaces --field-selector metadata.namespace!=default
3.1.2.7 - Finalizers
Finalizer 是带有命名空间的键,告诉 Kubernetes 等到特定的条件被满足后, 再完全删除被标记为删除的资源。 Finalizer 提醒控制器清理被删除的对象拥有的资源。
当你告诉 Kubernetes 删除一个指定了 Finalizer 的对象时,
Kubernetes API 通过填充 .metadata.deletionTimestamp
来标记要删除的对象,
并返回 202
状态码(HTTP "已接受") 使其进入只读状态。
此时控制平面或其他组件会采取 Finalizer 所定义的行动,
而目标对象仍然处于终止中(Terminating)的状态。
这些行动完成后,控制器会删除目标对象相关的 Finalizer。
当 metadata.finalizers
字段为空时,Kubernetes 认为删除已完成并删除对象。
你可以使用 Finalizer 控制资源的垃圾收集。 例如,你可以定义一个 Finalizer,在删除目标资源前清理相关资源或基础设施。
你可以使用 Finalizers 来控制对象的垃圾回收, 方法是在删除目标资源之前提醒控制器执行特定的清理任务。
Finalizers 通常不指定要执行的代码。 相反,它们通常是特定资源上的键的列表,类似于注解。 Kubernetes 自动指定了一些 Finalizers,但你也可以指定你自己的。
Finalizers 如何工作
当你使用清单文件创建资源时,你可以在 metadata.finalizers
字段指定 Finalizers。
当你试图删除该资源时,处理删除请求的 API 服务器会注意到 finalizers
字段中的值,
并进行以下操作:
- 修改对象,将你开始执行删除的时间添加到
metadata.deletionTimestamp
字段。 - 禁止对象被删除,直到其
metadata.finalizers
字段内的所有项被删除。 - 返回
202
状态码(HTTP "Accepted")。
管理 finalizer 的控制器注意到对象上发生的更新操作,对象的 metadata.deletionTimestamp
被设置,意味着已经请求删除该对象。然后,控制器会试图满足资源的 Finalizers 的条件。
每当一个 Finalizer 的条件被满足时,控制器就会从资源的 finalizers
字段中删除该键。
当 finalizers
字段为空时,deletionTimestamp
字段被设置的对象会被自动删除。
你也可以使用 Finalizers 来阻止删除未被管理的资源。
一个常见的 Finalizer 的例子是 kubernetes.io/pv-protection
,
它用来防止意外删除 PersistentVolume
对象。
当一个 PersistentVolume
对象被 Pod 使用时,
Kubernetes 会添加 pv-protection
Finalizer。
如果你试图删除 PersistentVolume
,它将进入 Terminating
状态,
但是控制器因为该 Finalizer 存在而无法删除该资源。
当 Pod 停止使用 PersistentVolume
时,
Kubernetes 清除 pv-protection
Finalizer,控制器就会删除该卷。
说明:
- 当你
DELETE
一个对象时,Kubernetes 为该对象增加删除时间戳,然后立即开始限制 对这个正处于待删除状态的对象的.metadata.finalizers
字段进行修改。 你可以删除现有的 finalizers (从finalizers
列表删除条目),但你不能添加新的 finalizer。 对象的deletionTimestamp
被设置后也不能修改。 - 删除请求已被发出之后,你无法复活该对象。唯一的方法是删除它并创建一个新的相似对象。
属主引用、标签和 Finalizers
与标签类似, 属主引用 描述了 Kubernetes 中对象之间的关系,但它们作用不同。 当一个控制器 管理类似于 Pod 的对象时,它使用标签来跟踪相关对象组的变化。 例如,当 Job 创建一个或多个 Pod 时, Job 控制器会给这些 Pod 应用上标签,并跟踪集群中的具有相同标签的 Pod 的变化。
Job 控制器还为这些 Pod 添加了“属主引用”,指向创建 Pod 的 Job。 如果你在这些 Pod 运行的时候删除了 Job, Kubernetes 会使用属主引用(而不是标签)来确定集群中哪些 Pod 需要清理。
当 Kubernetes 识别到要删除的资源上的属主引用时,它也会处理 Finalizers。
在某些情况下,Finalizers 会阻止依赖对象的删除, 这可能导致目标属主对象被保留的时间比预期的长,而没有被完全删除。 在这些情况下,你应该检查目标属主和附属对象上的 Finalizers 和属主引用,来排查原因。
说明:
在对象卡在删除状态的情况下,要避免手动移除 Finalizers,以允许继续删除操作。 Finalizers 通常因为特殊原因被添加到资源上,所以强行删除它们会导致集群出现问题。 只有了解 finalizer 的用途时才能这样做,并且应该通过一些其他方式来完成 (例如,手动清除其余的依赖对象)。
接下来
- 在 Kubernetes 博客上阅读使用 Finalizers 控制删除。
3.1.2.8 - 属主与附属
在 Kubernetes 中,一些对象是其他对象的“属主(Owner)”。 例如,ReplicaSet 是一组 Pod 的属主。 具有属主的对象是属主的“附属(Dependent)”。
属主关系不同于一些资源使用的标签和选择算符机制。
例如,有一个创建 EndpointSlice
对象的 Service,
该 Service 使用标签来让控制平面确定哪些
EndpointSlice
对象属于该 Service。除开标签,每个代表 Service 所管理的
EndpointSlice
都有一个属主引用。属主引用避免 Kubernetes
的不同部分干扰到不受它们控制的对象。
对象规约中的属主引用
附属对象有一个 metadata.ownerReferences
字段,用于引用其属主对象。一个有效的属主引用,
包含与附属对象同在一个命名空间下的对象名称和一个
UID。
Kubernetes 自动为一些对象的附属资源设置属主引用的值,
这些对象包含 ReplicaSet、DaemonSet、Deployment、Job、CronJob、ReplicationController 等。
你也可以通过改变这个字段的值,来手动配置这些关系。
然而,通常不需要这么做,你可以让 Kubernetes 自动管理附属关系。
附属对象还有一个 ownerReferences.blockOwnerDeletion
字段,该字段使用布尔值,
用于控制特定的附属对象是否可以阻止垃圾收集删除其属主对象。
如果控制器(例如 Deployment 控制器)
设置了 metadata.ownerReferences
字段的值,Kubernetes 会自动设置
blockOwnerDeletion
的值为 true
。
你也可以手动设置 blockOwnerDeletion
字段的值,以控制哪些附属对象会阻止垃圾收集。
Kubernetes 准入控制器根据属主的删除权限控制用户访问,以便为附属资源更改此字段。 这种控制机制可防止未经授权的用户延迟属主对象的删除。
说明:
根据设计,kubernetes 不允许跨名字空间指定属主。 名字空间范围的附属可以指定集群范围的或者名字空间范围的属主。 名字空间范围的属主必须和该附属处于相同的名字空间。 如果名字空间范围的属主和附属不在相同的名字空间,那么该属主引用就会被认为是缺失的, 并且当附属的所有属主引用都被确认不再存在之后,该附属就会被删除。
集群范围的附属只能指定集群范围的属主。 在 v1.20+ 版本,如果一个集群范围的附属指定了一个名字空间范围类型的属主, 那么该附属就会被认为是拥有一个不可解析的属主引用,并且它不能够被垃圾回收。
在 v1.20+ 版本,如果垃圾收集器检测到无效的跨名字空间的属主引用,
或者一个集群范围的附属指定了一个名字空间范围类型的属主,
那么它就会报告一个警告事件。该事件的原因是 OwnerRefInvalidNamespace
,
involvedObject
属性中包含无效的附属。
你可以运行 kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
来获取该类型的事件。
属主关系与 Finalizer
当你告诉 Kubernetes 删除一个资源,API 服务器允许管理控制器处理该资源的任何
Finalizer 规则。
Finalizer
防止意外删除你的集群所依赖的、用于正常运作的资源。
例如,如果你试图删除一个仍被 Pod 使用的 PersistentVolume
,该资源不会被立即删除,
因为 PersistentVolume 有
kubernetes.io/pv-protection
Finalizer。
相反,数据卷将进入 Terminating
状态,
直到 Kubernetes 清除这个 Finalizer,而这种情况只会发生在 PersistentVolume
不再被挂载到 Pod 上时。
当你使用前台或孤立级联删除时,
Kubernetes 也会向属主资源添加 Finalizer。
在前台删除中,会添加 foreground
Finalizer,这样控制器必须在删除了拥有
ownerReferences.blockOwnerDeletion=true
的附属资源后,才能删除属主对象。
如果你指定了孤立删除策略,Kubernetes 会添加 orphan
Finalizer,
这样控制器在删除属主对象后,会忽略附属资源。
接下来
- 了解更多关于 Kubernetes Finalizer。
- 了解关于垃圾收集。
- 阅读对象元数据的 API 参考文档。
3.1.2.9 - 推荐使用的标签
除了 kubectl 和 dashboard 之外,你还可以使用其他工具来可视化和管理 Kubernetes 对象。 一组通用的标签可以让多个工具之间相互操作,用所有工具都能理解的通用方式描述对象。
除了支持工具外,推荐的标签还以一种可以查询的方式描述了应用程序。
元数据围绕 应用(application) 的概念进行组织。Kubernetes 不是平台即服务(PaaS),没有或强制执行正式的应用程序概念。 相反,应用程序是非正式的,并使用元数据进行描述。应用程序包含的定义是松散的。
说明:
这些是推荐的标签。它们使管理应用程序变得更容易但不是任何核心工具所必需的。
共享标签和注解都使用同一个前缀:app.kubernetes.io
。没有前缀的标签是用户私有的。
共享前缀可以确保共享标签不会干扰用户自定义的标签。
标签
为了充分利用这些标签,应该在每个资源对象上都使用它们。
键 | 描述 | 示例 | 类型 |
---|---|---|---|
app.kubernetes.io/name | 应用程序的名称 | mysql | 字符串 |
app.kubernetes.io/instance | 用于唯一确定应用实例的名称 | mysql-abcxyz | 字符串 |
app.kubernetes.io/version | 应用程序的当前版本(例如语义版本 1.0、修订版哈希等) | 5.7.21 | 字符串 |
app.kubernetes.io/component | 架构中的组件 | database | 字符串 |
app.kubernetes.io/part-of | 此级别的更高级别应用程序的名称 | wordpress | 字符串 |
app.kubernetes.io/managed-by | 用于管理应用程序的工具 | Helm | 字符串 |
为说明这些标签的实际使用情况,请看下面的 StatefulSet 对象:
# 这是一段节选
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/instance: mysql-abcxyz
app.kubernetes.io/version: "5.7.21"
app.kubernetes.io/component: database
app.kubernetes.io/part-of: wordpress
app.kubernetes.io/managed-by: Helm
应用和应用实例
应用可以在 Kubernetes 集群中安装一次或多次。在某些情况下,可以安装在同一命名空间中。 例如,可以不止一次地为不同的站点安装不同的 WordPress。
应用的名称和实例的名称是分别记录的。例如,WordPress 应用的
app.kubernetes.io/name
为 wordpress
,而其实例名称
app.kubernetes.io/instance
为 wordpress-abcxyz
。
这使得应用和应用的实例均可被识别,应用的每个实例都必须具有唯一的名称。
示例
为了说明使用这些标签的不同方式,以下示例具有不同的复杂性。
一个简单的无状态服务
考虑使用 Deployment
和 Service
对象部署的简单无状态服务的情况。
以下两个代码段表示如何以最简单的形式使用标签。
下面的 Deployment
用于监督运行应用本身的那些 Pod。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: myservice
app.kubernetes.io/instance: myservice-abcxyz
...
下面的 Service
用于暴露应用。
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: myservice
app.kubernetes.io/instance: myservice-abcxyz
...
带有一个数据库的 Web 应用程序
考虑一个稍微复杂的应用:一个使用 Helm 安装的 Web 应用(WordPress), 其中使用了数据库(MySQL)。以下代码片段说明用于部署此应用程序的对象的开始。
以下 Deployment
的开头用于 WordPress:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: wordpress
app.kubernetes.io/instance: wordpress-abcxyz
app.kubernetes.io/version: "4.9.4"
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: server
app.kubernetes.io/part-of: wordpress
...
这个 Service
用于暴露 WordPress:
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: wordpress
app.kubernetes.io/instance: wordpress-abcxyz
app.kubernetes.io/version: "4.9.4"
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: server
app.kubernetes.io/part-of: wordpress
...
MySQL 作为一个 StatefulSet
暴露,包含它和它所属的较大应用程序的元数据:
apiVersion: apps/v1
kind: StatefulSet
metadata:
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/instance: mysql-abcxyz
app.kubernetes.io/version: "5.7.21"
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: database
app.kubernetes.io/part-of: wordpress
...
Service
用于将 MySQL 作为 WordPress 的一部分暴露:
apiVersion: v1
kind: Service
metadata:
labels:
app.kubernetes.io/name: mysql
app.kubernetes.io/instance: mysql-abcxyz
app.kubernetes.io/version: "5.7.21"
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/component: database
app.kubernetes.io/part-of: wordpress
...
使用 MySQL StatefulSet
和 Service
,你会注意到有关 MySQL 和 WordPress 的信息,包括更广泛的应用程序。
3.1.3 - Kubernetes API
Kubernetes 控制面的核心是 API 服务器。 API 服务器负责提供 HTTP API,以供用户、集群中的不同部分和集群外部组件相互通信。
Kubernetes API 使你可以在 Kubernetes 中查询和操纵 API 对象 (例如 Pod、Namespace、ConfigMap 和 Event)的状态。
大部分操作都可以通过 kubectl 命令行接口或类似 kubeadm 这类命令行工具来执行, 这些工具在背后也是调用 API。不过,你也可以使用 REST 调用来访问这些 API。 Kubernetes 为那些希望使用 Kubernetes API 编写应用的开发者提供一组客户端库。
每个 Kubernetes 集群都会发布集群所使用的 API 规范。
Kubernetes 使用两种机制来发布这些 API 规范;这两种机制都有助于实现自动互操作。
例如,kubectl
工具获取并缓存 API 规范,以实现命令行补全和其他特性。所支持的两种机制如下:
- 发现 API 提供有关 Kubernetes API 的信息:API 名称、资源、版本和支持的操作。 此 API 是特定于 Kubernetes 的一个术语,因为它是一个独立于 Kubernetes OpenAPI 的 API。 其目的是为可用的资源提供简要总结,不详细说明资源的具体模式。有关资源模式的参考,请参阅 OpenAPI 文档。
- Kubernetes OpenAPI 文档为所有 Kubernetes API 端点提供(完整的) OpenAPI v2.0 和 v3.0 模式。OpenAPI v3 是访问 OpenAPI 的首选方法, 因为它提供了更全面和准确的 API 视图。其中包括所有可用的 API 路径,以及每个端点上每个操作所接收和生成的所有资源。 它还包括集群支持的所有可扩展组件。这些数据是完整的规范,比 Discovery API 提供的规范要大得多。
Discovery API
Kubernetes 通过 Discovery API 发布集群所支持的所有组版本和资源列表。对于每个资源,包括以下内容:
- 名称
- 集群作用域还是名字空间作用域
- 端点 URL 和所支持的动词
- 别名
- 组、版本、类别
API 以聚合和非聚合形式提供。聚合的发现提供两个端点,而非聚合的发现为每个组版本提供单独的端点。
聚合的发现
Kubernetes v1.30 [stable]
(enabled by default: true)Kubernetes 为聚合的发现提供了 Beta 支持,通过两个端点(/api
和 /apis
)发布集群所支持的所有资源。
请求这个端点会大大减少从集群获取发现数据时发送的请求数量。你可以通过带有
Accept
头(Accept: application/json;v=v2beta1;g=apidiscovery.k8s.io;as=APIGroupDiscoveryList
)
的请求发送到不同端点,来指明聚合发现的资源。
如果没有使用 Accept
头指示资源类型,对于 /api
和 /apis
端点的默认响应将是一个非聚合的发现文档。
内置资源的发现文档可以在 Kubernetes GitHub 代码仓库中找到。如果手头没有 Kubernetes 集群可供查询, 此 Github 文档可用作可用资源的基础集合的参考。端点还支持 ETag 和 protobuf 编码。
非聚合的发现
在不使用聚合发现的情况下,发现 API 以不同级别发布,同时根端点为下游文档发布发现信息。
集群支持的所有组版本列表发布在 /api
和 /apis
端点。例如:
{
"kind": "APIGroupList",
"apiVersion": "v1",
"groups": [
{
"name": "apiregistration.k8s.io",
"versions": [
{
"groupVersion": "apiregistration.k8s.io/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "apiregistration.k8s.io/v1",
"version": "v1"
}
},
{
"name": "apps",
"versions": [
{
"groupVersion": "apps/v1",
"version": "v1"
}
],
"preferredVersion": {
"groupVersion": "apps/v1",
"version": "v1"
}
},
...
}
用户需要发出额外的请求才能在 /apis/<group>/<version>
(例如 /apis/rbac.authorization.k8s.io/v1alpha1
)
获取每个组版本的发现文档。这些发现文档会公布在特定组版本下所提供的资源列表。
kubectl 使用这些端点来获取某集群所支持的资源列表。
OpenAPI 接口定义
有关 OpenAPI 规范的细节,参阅 OpenAPI 文档。
Kubernetes 同时提供 OpenAPI v2.0 和 OpenAPI v3.0。OpenAPI v3 是访问 OpenAPI 的首选方法,
因为它提供了对 Kubernetes 资源更全面(无损)的表示。由于 OpenAPI v2 的限制,
所公布的 OpenAPI 中会丢弃掉一些字段,包括但不限于 default
、nullable
、oneOf
。
OpenAPI v2
Kubernetes API 服务器通过 /openapi/v2
端点提供聚合的 OpenAPI v2 规范。
你可以按照下表所给的请求头部,指定响应的格式:
头部 | 可选值 | 说明 |
---|---|---|
Accept-Encoding | gzip | 不指定此头部也是可以的 |
Accept | application/com.github.proto-openapi.spec.v2@v1.0+protobuf | 主要用于集群内部 |
application/json | 默认值 | |
* | 提供application/json |
OpenAPI v3
Kubernetes v1.27 [stable]
(enabled by default: true)Kubernetes 支持将其 API 的描述以 OpenAPI v3 形式发布。
发现端点 /openapi/v3
被提供用来查看可用的所有组、版本列表。
此列表仅返回 JSON。这些组、版本以下面的格式提供:
{
"paths": {
...,
"api/v1": {
"serverRelativeURL": "/openapi/v3/api/v1?hash=CC0E9BFD992D8C59AEC98A1E2336F899E8318D3CF4C68944C3DEC640AF5AB52D864AC50DAA8D145B3494F75FA3CFF939FCBDDA431DAD3CA79738B297795818CF"
},
"apis/admissionregistration.k8s.io/v1": {
"serverRelativeURL": "/openapi/v3/apis/admissionregistration.k8s.io/v1?hash=E19CC93A116982CE5422FC42B590A8AFAD92CDE9AE4D59B5CAAD568F083AD07946E6CB5817531680BCE6E215C16973CD39003B0425F3477CFD854E89A9DB6597"
},
....
}
}
为了改进客户端缓存,相对的 URL 会指向不可变的 OpenAPI 描述。
为了此目的,API 服务器也会设置正确的 HTTP 缓存标头
(Expires
为未来 1 年,和 Cache-Control
为 immutable
)。
当一个过时的 URL 被使用时,API 服务器会返回一个指向最新 URL 的重定向。
Kubernetes API 服务器会在端点 /openapi/v3/apis/<group>/<version>?hash=<hash>
发布一个 Kubernetes 组版本的 OpenAPI v3 规范。
请参阅下表了解可接受的请求头部。
头部 | 可选值 | 说明 |
---|---|---|
Accept-Encoding | gzip | 不提供此头部也是可接受的 |
Accept | application/com.github.proto-openapi.spec.v3@v1.0+protobuf | 主要用于集群内部使用 |
application/json | 默认 | |
* | 以 application/json 形式返回 |
k8s.io/client-go/openapi3
包中提供了获取 OpenAPI v3 的 Golang 实现。
Kubernetes 1.31 发布了 OpenAPI v2.0 和 v3.0; 近期没有支持 v3.1 的计划。
Protobuf 序列化
Kubernetes 为 API 实现了一种基于 Protobuf 的序列化格式,主要用于集群内部通信。 关于此格式的详细信息,可参考 Kubernetes Protobuf 序列化设计提案。 每种模式对应的接口描述语言(IDL)位于定义 API 对象的 Go 包中。
持久化
Kubernetes 通过将序列化状态的对象写入到 etcd 中完成存储操作。
API 组和版本控制
为了更容易消除字段或重组资源的呈现方式,Kubernetes 支持多个 API 版本,每个版本位于不同的 API 路径,
例如 /api/v1
或 /apis/rbac.authorization.k8s.io/v1alpha1
。
版本控制是在 API 级别而不是在资源或字段级别完成的,以确保 API 呈现出清晰、一致的系统资源和行为视图, 并能够控制对生命结束和/或实验性 API 的访问。
为了更容易演进和扩展其 API,Kubernetes 实现了 API 组, 这些 API 组可以被启用或禁用。
API 资源通过其 API 组、资源类型、名字空间(用于名字空间作用域的资源)和名称来区分。 API 服务器透明地处理 API 版本之间的转换:所有不同的版本实际上都是相同持久化数据的呈现。 API 服务器可以通过多个 API 版本提供相同的底层数据。
例如,假设针对相同的资源有两个 API 版本:v1
和 v1beta1
。
如果你最初使用其 API 的 v1beta1
版本创建了一个对象,
你稍后可以使用 v1beta1
或 v1
API 版本来读取、更新或删除该对象,
直到 v1beta1
版本被废弃和移除为止。此后,你可以使用 v1
API 继续访问和修改该对象。
API 变更
任何成功的系统都要随着新的使用案例的出现和现有案例的变化来成长和变化。 为此,Kubernetes 已设计了 Kubernetes API 来持续变更和成长。 Kubernetes 项目的目标是不要给现有客户端带来兼容性问题,并在一定的时期内维持这种兼容性, 以便其他项目有机会作出适应性变更。
一般而言,新的 API 资源和新的资源字段可以被频繁地添加进来。 删除资源或者字段则要遵从 API 废弃策略。
Kubernetes 对维护达到正式发布(GA)阶段的官方 API 的兼容性有着很强的承诺,通常这一 API 版本为 v1
。
此外,Kubernetes 保持与 Kubernetes 官方 API 的 Beta API 版本持久化数据的兼容性,
并确保在该功能特性已进入稳定期时数据可以通过 GA API 版本进行转换和访问。
如果你采用一个 Beta API 版本,一旦该 API 进阶,你将需要转换到后续的 Beta 或稳定的 API 版本。 执行此操作的最佳时间是 Beta API 处于弃用期,因为此时可以通过两个 API 版本同时访问那些对象。 一旦 Beta API 结束其弃用期并且不再提供服务,则必须使用替换的 API 版本。
说明:
尽管 Kubernetes 也努力为 Alpha API 版本维护兼容性,在有些场合兼容性是无法做到的。 如果你使用了任何 Alpha API 版本,需要在升级集群时查看 Kubernetes 发布说明, 如果 API 确实以不兼容的方式发生变更,则需要在升级之前删除所有现有的 Alpha 对象。
关于 API 版本分级的定义细节,请参阅 API 版本参考页面。
API 扩展
有两种途径来扩展 Kubernetes API:
接下来
- 了解如何通过添加你自己的 CustomResourceDefinition 来扩展 Kubernetes API。
- 控制 Kubernetes API 访问页面描述了集群如何针对 API 访问管理身份认证和鉴权。
- 通过阅读 API 参考了解 API 端点、资源类型以及示例。
- 阅读 API 变更(英文) 以了解什么是兼容性的变更以及如何变更 API。
3.2 - Kubernetes 架构
Kubernetes 集群由一个控制平面和一组用于运行容器化应用的工作机器组成, 这些工作机器称作节点(Node)。每个集群至少需要一个工作节点来运行 Pod。
工作节点托管着组成应用负载的 Pod。控制平面管理集群中的工作节点和 Pod。 在生产环境中,控制平面通常跨多台计算机运行,而一个集群通常运行多个节点,以提供容错和高可用。
本文概述了构建一个完整且可运行的 Kubernetes 集群所需的各种组件。
关于此架构
图 1 中的图表展示了 Kubernetes 集群的示例参考架构, 组件的实际分布可能根据特定的集群设置和要求而有所不同。
图中每个节点都运行 kube-proxy
组件。
你需要在每个节点上安装一个网络代理组件,以确保 Service
API 和相关行为在你的集群网络上可用。
但是,一些网络插件为流量代理提供了自己的第三方实现。
当你使用那种网络插件时,节点便不需要运行 kube-proxy
。
控制平面组件
控制平面组件会为集群做出全局决策,比如资源的调度。
以及检测和响应集群事件,例如当不满足 Deployment 的
replicas
字段时,要启动新的 Pod)。
控制平面组件可以在集群中的任何节点上运行。 然而,为了简单起见,安装脚本通常会在同一个计算机上启动所有控制平面组件, 并且不会在此计算机上运行用户容器。 请参阅使用 kubeadm 构建高可用性集群中关于跨多机器安装控制平面的示例。
kube-apiserver
API 服务器是 Kubernetes 控制平面的组件, 该组件负责公开了 Kubernetes API,负责处理接受请求的工作。 API 服务器是 Kubernetes 控制平面的前端。
Kubernetes API 服务器的主要实现是 kube-apiserver。
kube-apiserver
设计上考虑了水平扩缩,也就是说,它可通过部署多个实例来进行扩缩。
你可以运行 kube-apiserver
的多个实例,并在这些实例之间平衡流量。
etcd
一致且高可用的键值存储,用作 Kubernetes 所有集群数据的后台数据库。
如果你的 Kubernetes 集群使用 etcd 作为其后台数据库, 请确保你针对这些数据有一份 备份计划。
你可以在官方文档中找到有关 etcd 的深入知识。
kube-scheduler
kube-scheduler
是控制平面的组件,
负责监视新创建的、未指定运行节点(node)的 Pods,
并选择节点来让 Pod 在上面运行。
调度决策考虑的因素包括单个 Pod 及 Pods 集合的资源需求、软硬件及策略约束、 亲和性及反亲和性规范、数据位置、工作负载间的干扰及最后时限。
kube-controller-manager
kube-controller-manager 是控制平面的组件, 负责运行控制器进程。
从逻辑上讲, 每个控制器都是一个单独的进程, 但是为了降低复杂性,它们都被编译到同一个可执行文件,并在同一个进程中运行。
控制器有许多不同类型。以下是一些例子:
- Node 控制器:负责在节点出现故障时进行通知和响应
- Job 控制器:监测代表一次性任务的 Job 对象,然后创建 Pod 来运行这些任务直至完成
- EndpointSlice 控制器:填充 EndpointSlice 对象(以提供 Service 和 Pod 之间的链接)。
- ServiceAccount 控制器:为新的命名空间创建默认的 ServiceAccount。
以上并不是一个详尽的列表。
cloud-controller-manager
一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager)允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。cloud-controller-manager
仅运行特定于云平台的控制器。
因此如果你在自己的环境中运行 Kubernetes,或者在本地计算机中运行学习环境,
所部署的集群不包含云控制器管理器。
与 kube-controller-manager
类似,cloud-controller-manager
将若干逻辑上独立的控制回路组合到同一个可执行文件中,以同一进程的方式供你运行。
你可以对其执行水平扩容(运行不止一个副本)以提升性能或者增强容错能力。
下面的控制器都包含对云平台驱动的依赖:
- Node 控制器:用于在节点终止响应后检查云平台以确定节点是否已被删除
- Route 控制器:用于在底层云基础架构中设置路由
- Service 控制器:用于创建、更新和删除云平台上的负载均衡器
节点组件
节点组件会在每个节点上运行,负责维护运行的 Pod 并提供 Kubernetes 运行时环境。
kubelet
kubelet
会在集群中每个节点(node)上运行。
它保证容器(containers)都运行在
Pod 中。
kubelet 接收一组通过各类机制提供给它的 PodSpec,确保这些 PodSpec 中描述的容器处于运行状态且健康。 kubelet 不会管理不是由 Kubernetes 创建的容器。
kube-proxy(可选)
kube-proxy 是集群中每个节点(node)上所运行的网络代理, 实现 Kubernetes 服务(Service) 概念的一部分。
kube-proxy 维护节点上的一些网络规则, 这些网络规则会允许从集群内部或外部的网络会话与 Pod 进行网络通信。
如果操作系统提供了可用的数据包过滤层,则 kube-proxy 会通过它来实现网络规则。 否则,kube-proxy 仅做流量转发。
如果你使用网络插件为 Service 实现本身的数据包转发, 并提供与 kube-proxy 等效的行为,那么你不需要在集群中的节点上运行 kube-proxy。容器运行时
这个基础组件使 Kubernetes 能够有效运行容器。 它负责管理 Kubernetes 环境中容器的执行和生命周期。
Kubernetes 支持许多容器运行环境,例如 containerd、 CRI-O 以及 Kubernetes CRI (容器运行环境接口) 的其他任何实现。
插件(Addons)
插件使用 Kubernetes 资源(DaemonSet、
Deployment 等)实现集群功能。
因为这些插件提供集群级别的功能,插件中命名空间域的资源属于 kube-system
命名空间。
下面描述众多插件中的几种。有关可用插件的完整列表, 请参见插件(Addons)。
DNS
尽管其他插件都并非严格意义上的必需组件,但几乎所有 Kubernetes 集群都应该有集群 DNS, 因为很多示例都需要 DNS 服务。
集群 DNS 是一个 DNS 服务器,和环境中的其他 DNS 服务器一起工作,它为 Kubernetes 服务提供 DNS 记录。
Kubernetes 启动的容器自动将此 DNS 服务器包含在其 DNS 搜索列表中。
Web 界面(仪表盘)
Dashboard 是 Kubernetes 集群的通用的、基于 Web 的用户界面。 它使用户可以管理集群中运行的应用程序以及集群本身,并进行故障排除。
容器资源监控
容器资源监控 将关于容器的一些常见的时序度量值保存到一个集中的数据库中,并提供浏览这些数据的界面。
集群层面日志
集群层面日志机制负责将容器的日志数据保存到一个集中的日志存储中, 这种集中日志存储提供搜索和浏览接口。
网络插件
网络插件 是实现容器网络接口(CNI)规范的软件组件。它们负责为 Pod 分配 IP 地址,并使这些 Pod 能在集群内部相互通信。
架构变种
虽然 Kubernetes 的核心组件保持一致,但它们的部署和管理方式可能有所不同。 了解这些变化对于设计和维护满足特定运营需求的 Kubernetes 集群至关重要。
控制平面部署选项
控制平面组件可以通过以下几种方式部署:
- 传统部署
- 控制平面组件直接在专用机器或虚拟机上运行,通常作为 systemd 服务进行管理。
- 静态 Pod
- 控制平面组件作为静态 Pod 部署,由特定节点上的 kubelet 管理。 这是像 kubeadm 这样的工具常用的方法。
- 自托管
- 控制平面在 Kubernetes 集群本身内部作为 Pod 运行, 由 Deployments、StatefulSets 或其他 Kubernetes 原语管理。
- 托管 Kubernetes 服务
- 云平台通常将控制平面抽象出来,将其组件作为其服务的一部分进行管理。
工作负载调度说明
含控制平面组件在内的工作负载的调度可能因集群大小、性能要求和操作策略而有所不同:
- 在较小或开发集群中,控制平面组件和用户工作负载可能在同一节点上运行。
- 较大的生产集群通常将特定节点专用于控制平面组件,将其与用户工作负载隔离。
- 一些组织在控制平面节点上运行关键组件或监控工具。
集群管理工具
像 kubeadm、kops 和 Kubespray 这样的工具提供了不同的集群部署和管理方法, 每种方法都有自己的组件布局和管理方式。
Kubernetes 架构的灵活性使各组织能够根据特定需求调整其集群,平衡操作复杂性、性能和管理开销等因素。
定制和可扩展性
Kubernetes 架构允许大幅度的定制:
- 你可以部署自定义的调度器与默认的 Kubernetes 调度器协同工作,也可以完全替换掉默认的调度器。
- API 服务器可以通过 CustomResourceDefinition 和 API 聚合进行扩展。
- 云平台可以使用 cloud-controller-manager 与 Kubernetes 深度集成。
Kubernetes 架构的灵活性使各组织能够根据特定需求调整其集群,平衡操作复杂性、性能和管理开销等因素。
接下来
了解更多内容:
- 节点及其与控制平面的通信。
- Kubernetes 控制器。
- Kubernetes 的默认调度器 kube-scheduler。
- Etcd 的官方文档。
- Kubernetes 中的几个容器运行时。
- 使用 cloud-controller-manager 与云平台集成。
- kubectl 命令。
3.2.1 - 节点
Kubernetes 通过将容器放入在节点(Node)上运行的 Pod 中来执行你的工作负载。 节点可以是一个虚拟机或者物理机器,取决于所在的集群配置。 每个节点包含运行 Pod 所需的服务; 这些节点由控制面负责管理。
通常集群中会有若干个节点;而在一个学习所用或者资源受限的环境中,你的集群中也可能只有一个节点。
节点上的组件包括 kubelet、 容器运行时以及 kube-proxy。
管理
向 API 服务器添加节点的方式主要有两种:
- 节点上的
kubelet
向控制面执行自注册; - 你(或者别的什么人)手动添加一个 Node 对象。
在你创建了 Node 对象或者节点上的
kubelet
执行了自注册操作之后,控制面会检查新的 Node 对象是否合法。
例如,如果你尝试使用下面的 JSON 对象来创建 Node 对象:
{
"kind": "Node",
"apiVersion": "v1",
"metadata": {
"name": "10.240.79.157",
"labels": {
"name": "my-first-k8s-node"
}
}
}
Kubernetes 会在内部创建一个 Node 对象作为节点的表示。Kubernetes 检查 kubelet
向 API 服务器注册节点时使用的 metadata.name
字段是否匹配。
如果节点是健康的(即所有必要的服务都在运行中),则该节点可以用来运行 Pod。
否则,直到该节点变为健康之前,所有的集群活动都会忽略该节点。
Node 对象的名称必须是合法的 DNS 子域名。
节点名称唯一性
节点的名称用来标识 Node 对象。 没有两个 Node 可以同时使用相同的名称。 Kubernetes 还假定名字相同的资源是同一个对象。 就 Node 而言,隐式假定使用相同名称的实例会具有相同的状态(例如网络配置、根磁盘内容) 和类似节点标签这类属性。这可能在节点被更改但其名称未变时导致系统状态不一致。 如果某个 Node 需要被替换或者大量变更,需要从 API 服务器移除现有的 Node 对象, 之后再在更新之后重新将其加入。
节点自注册
当 kubelet 标志 --register-node
为 true(默认)时,它会尝试向 API 服务注册自己。
这是首选模式,被绝大多数发行版选用。
对于自注册模式,kubelet 使用下列参数启动:
--kubeconfig
- 用于向 API 服务器执行身份认证所用的凭据的路径。--cloud-provider
- 与某云驱动 进行通信以读取与自身相关的元数据的方式。--register-node
- 自动向 API 服务器注册。--register-with-taints
- 使用所给的污点列表 (逗号分隔的<key>=<value>:<effect>
)注册节点。当register-node
为 false 时无效。
--node-ip
- 可选的以英文逗号隔开的节点 IP 地址列表。你只能为每个地址簇指定一个地址。 例如在单协议栈 IPv4 集群中,需要将此值设置为 kubelet 应使用的节点 IPv4 地址。 参阅配置 IPv4/IPv6 双协议栈了解运行双协议栈集群的详情。如果你未提供这个参数,kubelet 将使用节点默认的 IPv4 地址(如果有); 如果节点没有 IPv4 地址,则 kubelet 使用节点的默认 IPv6 地址。
--node-labels
- 在集群中注册节点时要添加的标签。 (参见 NodeRestriction 准入控制插件所实施的标签限制)。--node-status-update-frequency
- 指定 kubelet 向 API 服务器发送其节点状态的频率。
当 Node 鉴权模式和 NodeRestriction 准入插件被启用后, 仅授权 kubelet 创建/修改自己的 Node 资源。
说明:
正如节点名称唯一性一节所述,当 Node 的配置需要被更新时,
一种好的做法是重新向 API 服务器注册该节点。例如,如果 kubelet 重启时其 --node-labels
是新的值集,但同一个 Node 名称已经被使用,则所作变更不会起作用,
因为节点标签是在 Node 注册到 API 服务器时完成(或修改)的。
如果在 kubelet 重启期间 Node 配置发生了变化,已经被调度到某 Node 上的 Pod 可能会出现行为不正常或者出现其他问题,例如,已经运行的 Pod 可能通过污点机制设置了与 Node 上新设置的标签相排斥的规则,也有一些其他 Pod, 本来与此 Pod 之间存在不兼容的问题,也会因为新的标签设置而被调到同一节点。 节点重新注册操作可以确保节点上所有 Pod 都被排空并被正确地重新调度。
手动节点管理
你可以使用 kubectl 来创建和修改 Node 对象。
如果你希望手动创建节点对象时,请设置 kubelet 标志 --register-node=false
。
你可以修改 Node 对象(忽略 --register-node
设置)。
例如,你可以修改节点上的标签或并标记其为不可调度。
你可以结合使用 Node 上的标签和 Pod 上的选择算符来控制调度。 例如,你可以限制某 Pod 只能在符合要求的节点子集上运行。
如果标记节点为不可调度(unschedulable),将阻止新 Pod 调度到该 Node 之上, 但不会影响任何已经在其上的 Pod。 这是重启节点或者执行其他维护操作之前的一个有用的准备步骤。
要标记一个 Node 为不可调度,执行以下命令:
kubectl cordon $NODENAME
更多细节参考安全地腾空节点。
说明:
被 DaemonSet 控制器创建的 Pod 能够容忍节点的不可调度属性。 DaemonSet 通常提供节点本地的服务,即使节点上的负载应用已经被腾空, 这些服务也仍需运行在节点之上。
节点状态
一个节点的状态包含以下信息:
你可以使用 kubectl
来查看节点状态和其他细节信息:
kubectl describe node <节点名称>
更多细节参见 Node Status。
节点心跳
Kubernetes 节点发送的心跳帮助你的集群确定每个节点的可用性,并在检测到故障时采取行动。
对于节点,有两种形式的心跳:
节点控制器
节点控制器是 Kubernetes 控制面组件, 管理节点的方方面面。
节点控制器在节点的生命周期中扮演多个角色。 第一个是当节点注册时为它分配一个 CIDR 区段(如果启用了 CIDR 分配)。
第二个是保持节点控制器内的节点列表与云服务商所提供的可用机器列表同步。 如果在云环境下运行,只要某节点不健康,节点控制器就会询问云服务是否节点的虚拟机仍可用。 如果不可用,节点控制器会将该节点从它的节点列表删除。
第三个是监控节点的健康状况。节点控制器负责:
- 在节点不可达的情况下,在 Node 的
.status
中更新Ready
状况。 在这种情况下,节点控制器将 NodeReady 状况更新为Unknown
。 - 如果节点仍然无法访问:对于不可达节点上的所有 Pod 触发
API 发起的逐出操作。
默认情况下,节点控制器在将节点标记为
Unknown
后等待 5 分钟提交第一个驱逐请求。
默认情况下,节点控制器每 5 秒检查一次节点状态,可以使用 kube-controller-manager
组件上的 --node-monitor-period
参数来配置周期。
逐出速率限制
大部分情况下,节点控制器把逐出速率限制在每秒 --node-eviction-rate
个(默认为 0.1)。
这表示它每 10 秒钟内至多从一个节点驱逐 Pod。
当一个可用区域(Availability Zone)中的节点变为不健康时,节点的驱逐行为将发生改变。
节点控制器会同时检查可用区域中不健康(Ready
状况为 Unknown
或 False
)
的节点的百分比:
- 如果不健康节点的比例超过
--unhealthy-zone-threshold
(默认为 0.55), 驱逐速率将会降低。 - 如果集群较小(意即小于等于
--large-cluster-size-threshold
个节点 - 默认为 50), 驱逐操作将会停止。 - 否则驱逐速率将降为每秒
--secondary-node-eviction-rate
个(默认为 0.01)。
在逐个可用区域中实施这些策略的原因是, 当一个可用区域可能从控制面脱离时其它可用区域可能仍然保持连接。 如果你的集群没有跨越云服务商的多个可用区域,那(整个集群)就只有一个可用区域。
跨多个可用区域部署你的节点的一个关键原因是当某个可用区域整体出现故障时,
工作负载可以转移到健康的可用区域。
因此,如果一个可用区域中的所有节点都不健康时,节点控制器会以正常的速率
--node-eviction-rate
进行驱逐操作。
在所有的可用区域都不健康(也即集群中没有健康节点)的极端情况下,
节点控制器将假设控制面与节点间的连接出了某些问题,它将停止所有驱逐动作
(如果故障后部分节点重新连接,节点控制器会从剩下不健康或者不可达节点中驱逐 Pod)。
节点控制器还负责驱逐运行在拥有 NoExecute
污点的节点上的 Pod,
除非这些 Pod 能够容忍此污点。
节点控制器还负责根据节点故障(例如节点不可访问或没有就绪)
为其添加污点。
这意味着调度器不会将 Pod 调度到不健康的节点上。
资源容量跟踪
Node 对象会跟踪节点上资源的容量(例如可用内存和 CPU 数量)。 通过自注册机制生成的 Node 对象会在注册期间报告自身容量。 如果你手动添加了 Node, 你就需要在添加节点时手动设置节点容量。
Kubernetes 调度器
保证节点上有足够的资源供其上的所有 Pod 使用。
它会检查节点上所有容器的请求的总和不会超过节点的容量。
总的请求包括由 kubelet 启动的所有容器,但不包括由容器运行时直接启动的容器,
也不包括不受 kubelet
控制的其他进程。
说明:
如果要为非 Pod 进程显式保留资源。 请参考为系统守护进程预留资源。
节点拓扑
Kubernetes v1.27 [stable]
(enabled by default: true)如果启用了 TopologyManager
特性门控,
kubelet
可以在作出资源分配决策时使用拓扑提示。
参考控制节点上拓扑管理策略了解详细信息。
交换内存(swap)管理
Kubernetes v1.30 [beta]
(enabled by default: true)要在节点上启用交换内存,必须启用 kubelet 的 NodeSwap
特性门控(默认启用),
同时使用 --fail-swap-on
命令行参数或者将 failSwapOn
配置设置为 false。
为了允许 Pod 使用交换内存,在 kubelet 配置中不应将 swapBehavior
设置为 NoSwap
(默认行为)。
警告:
当内存交换功能被启用后,Kubernetes 数据(如写入 tmpfs 的 Secret 对象的内容)可以被交换到磁盘。
用户还可以选择配置 memorySwap.swapBehavior
以指定节点使用交换内存的方式。例如:
memorySwap:
swapBehavior: LimitedSwap
NoSwap
(默认):Kubernetes 工作负载不会使用交换内存。LimitedSwap
:Kubernetes 工作负载对交换内存的使用受到限制。 只有具有 Burstable QoS 的 Pod 可以使用交换内存。
如果启用了特性门控但是未指定 memorySwap
的配置,默认情况下 kubelet 将使用与
NoSwap
设置相同的行为。
采用 LimitedSwap
时,不属于 Burstable QoS 分类的 Pod
(即 BestEffort
/Guaranteed
QoS Pod)
被禁止使用交换内存。为了保持上述的安全性和节点健康性保证,
在 LimitedSwap
生效时,不允许这些 Pod 使用交换内存。
在详细介绍交换限制的计算之前,有必要定义以下术语:
nodeTotalMemory
:节点上可用的物理内存总量。totalPodsSwapAvailable
:节点上可供 Pod 使用的交换内存总量 (一些交换内存可能被保留由系统使用)。containerMemoryRequest
:容器的内存请求。
交换内存限制被配置为 (containerMemoryRequest / nodeTotalMemory) * totalPodsSwapAvailable
的值。
需要注意的是,位于 Burstable QoS Pod 中的容器可以通过将内存请求设置为与内存限制相同来选择不使用交换内存。 以这种方式配置的容器将无法访问交换内存。
只有 Cgroup v2 支持交换内存,Cgroup v1 不支持。
如需了解更多信息、协助测试和提交反馈,请参阅关于 Kubernetes 1.28:NodeSwap 进阶至 Beta1 的博客文章、 KEP-2400 及其设计提案。
接下来
进一步了解以下资料:
- 构成节点的组件 。
- Node 的 API 定义。
- 架构设计文档中有关 Node 的章节。
- 集群自动扩缩 以管理集群中节点的数量和规模。
- 污点和容忍度。
- 节点资源管理器。
- Windows 节点的资源管理。
3.2.2 - 节点与控制面之间的通信
本文列举控制面节点(确切地说是 API 服务器)和 Kubernetes 集群之间的通信路径。 目的是为了让用户能够自定义他们的安装,以实现对网络配置的加固, 使得集群能够在不可信的网络上(或者在一个云服务商完全公开的 IP 上)运行。
节点到控制面
Kubernetes 采用的是中心辐射型(Hub-and-Spoke)API 模式。 所有从节点(或运行于其上的 Pod)发出的 API 调用都终止于 API 服务器。 其它控制面组件都没有被设计为可暴露远程服务。 API 服务器被配置为在一个安全的 HTTPS 端口(通常为 443)上监听远程连接请求, 并启用一种或多种形式的客户端身份认证机制。 一种或多种客户端鉴权机制应该被启用, 特别是在允许使用匿名请求 或服务账户令牌的时候。
应该使用集群的公共根证书开通节点, 这样它们就能够基于有效的客户端凭据安全地连接 API 服务器。 一种好的方法是以客户端证书的形式将客户端凭据提供给 kubelet。 请查看 kubelet TLS 启动引导 以了解如何自动提供 kubelet 客户端证书。
想要连接到 API 服务器的 Pod
可以使用服务账号安全地进行连接。
当 Pod 被实例化时,Kubernetes 自动把公共根证书和一个有效的持有者令牌注入到 Pod 里。
kubernetes
服务(位于 default
名字空间中)配置了一个虚拟 IP 地址,
用于(通过 kube-proxy
)转发请求到
API 服务器的 HTTPS 末端。
控制面组件也通过安全端口与集群的 API 服务器通信。
这样,从集群节点和节点上运行的 Pod 到控制面的连接的缺省操作模式即是安全的, 能够在不可信的网络或公网上运行。
控制面到节点
从控制面(API 服务器)到节点有两种主要的通信路径。 第一种是从 API 服务器到集群中每个节点上运行的 kubelet 进程。 第二种是从 API 服务器通过它的代理功能连接到任何节点、Pod 或者服务。
API 服务器到 kubelet
从 API 服务器到 kubelet 的连接用于:
- 获取 Pod 日志。
- 挂接(通过 kubectl)到运行中的 Pod。
- 提供 kubelet 的端口转发功能。
这些连接终止于 kubelet 的 HTTPS 末端。 默认情况下,API 服务器不检查 kubelet 的服务证书。这使得此类连接容易受到中间人攻击, 在非受信网络或公开网络上运行也是 不安全的。
为了对这个连接进行认证,使用 --kubelet-certificate-authority
标志给
API 服务器提供一个根证书包,用于 kubelet 的服务证书。
如果无法实现这点,又要求避免在非受信网络或公共网络上进行连接,可在 API 服务器和 kubelet 之间使用 SSH 隧道。
最后,应该启用 Kubelet 认证/鉴权 来保护 kubelet API。
API 服务器到节点、Pod 和服务
从 API 服务器到节点、Pod 或服务的连接默认为纯 HTTP 方式,因此既没有认证,也没有加密。
这些连接可通过给 API URL 中的节点、Pod 或服务名称添加前缀 https:
来运行在安全的 HTTPS 连接上。
不过这些连接既不会验证 HTTPS 末端提供的证书,也不会提供客户端证书。
因此,虽然连接是加密的,仍无法提供任何完整性保证。
这些连接 目前还不能安全地 在非受信网络或公共网络上运行。
SSH 隧道
Kubernetes 支持使用 SSH 隧道来保护从控制面到节点的通信路径。 在这种配置下,API 服务器建立一个到集群中各节点的 SSH 隧道(连接到在 22 端口监听的 SSH 服务器) 并通过这个隧道传输所有到 kubelet、节点、Pod 或服务的请求。 这一隧道保证通信不会被暴露到集群节点所运行的网络之外。
说明:
SSH 隧道目前已被废弃。除非你了解个中细节,否则不应使用。 Konnectivity 服务是 SSH 隧道的替代方案。
Konnectivity 服务
Kubernetes v1.18 [beta]
作为 SSH 隧道的替代方案,Konnectivity 服务提供 TCP 层的代理,以便支持从控制面到集群的通信。 Konnectivity 服务包含两个部分:Konnectivity 服务器和 Konnectivity 代理, 分别运行在控制面网络和节点网络中。 Konnectivity 代理建立并维持到 Konnectivity 服务器的网络连接。 启用 Konnectivity 服务之后,所有控制面到节点的通信都通过这些连接传输。
请浏览 Konnectivity 服务任务 在你的集群中配置 Konnectivity 服务。
接下来
- 阅读 Kubernetes 控制面组件
- 进一步了解 Hubs and Spoke model
- 进一步了解如何保护集群
- 进一步了解 Kubernetes API
- 设置 Konnectivity 服务
- 使用端口转发来访问集群中的应用
- 学习如何检查 Pod 的日志 以及如何使用 kubectl 端口转发
3.2.3 - 控制器
在机器人技术和自动化领域,控制回路(Control Loop)是一个非终止回路,用于调节系统状态。
这是一个控制环的例子:房间里的温度自动调节器。
当你设置了温度,告诉了温度自动调节器你的期望状态(Desired State)。 房间的实际温度是当前状态(Current State)。 通过对设备的开关控制,温度自动调节器让其当前状态接近期望状态。
在 Kubernetes 中,控制器通过监控集群 的公共状态,并致力于将当前状态转变为期望的状态。控制器模式
一个控制器至少追踪一种类型的 Kubernetes 资源。这些
对象
有一个代表期望状态的 spec
字段。
该资源的控制器负责确保其当前状态接近期望状态。
控制器可能会自行执行操作;在 Kubernetes 中更常见的是一个控制器会发送信息给 API 服务器,这会有副作用。 具体可参看后文的例子。
通过 API 服务器来控制
Job 控制器是一个 Kubernetes 内置控制器的例子。 内置控制器通过和集群 API 服务器交互来管理状态。
Job 是一种 Kubernetes 资源,它运行一个或者多个 Pod,
来执行一个任务然后停止。
(一旦被调度了,对 kubelet
来说 Pod
对象就会变成期望状态的一部分)。
在集群中,当 Job 控制器拿到新任务时,它会保证一组 Node 节点上的 kubelet
可以运行正确数量的 Pod 来完成工作。
Job 控制器不会自己运行任何的 Pod 或者容器。Job 控制器是通知 API 服务器来创建或者移除 Pod。
控制面中的其它组件
根据新的消息作出反应(调度并运行新 Pod)并且最终完成工作。
创建新 Job 后,所期望的状态就是完成这个 Job。Job 控制器会让 Job 的当前状态不断接近期望状态:创建为 Job 要完成工作所需要的 Pod,使 Job 的状态接近完成。
控制器也会更新配置对象。例如:一旦 Job 的工作完成了,Job 控制器会更新 Job 对象的状态为 Finished
。
(这有点像温度自动调节器关闭了一个灯,以此来告诉你房间的温度现在到你设定的值了)。
直接控制
相比 Job 控制器,有些控制器需要对集群外的一些东西进行修改。
例如,如果你使用一个控制回路来保证集群中有足够的 节点,那么控制器就需要当前集群外的 一些服务在需要时创建新节点。
和外部状态交互的控制器从 API 服务器获取到它想要的状态,然后直接和外部系统进行通信 并使当前状态更接近期望状态。
(实际上有一个控制器 可以水平地扩展集群中的节点。)
这里的重点是,控制器做出了一些变更以使得事物更接近你的期望状态, 之后将当前状态报告给集群的 API 服务器。 其他控制回路可以观测到所汇报的数据的这种变化并采取其各自的行动。
在温度计的例子中,如果房间很冷,那么某个控制器可能还会启动一个防冻加热器。 就 Kubernetes 集群而言,控制面间接地与 IP 地址管理工具、存储服务、云驱动 APIs 以及其他服务协作,通过扩展 Kubernetes 来实现这点。
期望状态与当前状态
Kubernetes 采用了系统的云原生视图,并且可以处理持续的变化。
在任务执行时,集群随时都可能被修改,并且控制回路会自动修复故障。 这意味着很可能集群永远不会达到稳定状态。
只要集群中的控制器在运行并且进行有效的修改,整体状态的稳定与否是无关紧要的。
设计
作为设计原则之一,Kubernetes 使用了很多控制器,每个控制器管理集群状态的一个特定方面。 最常见的一个特定的控制器使用一种类型的资源作为它的期望状态, 控制器管理控制另外一种类型的资源向它的期望状态演化。 例如,Job 的控制器跟踪 Job 对象(以发现新的任务)和 Pod 对象(以运行 Job,然后查看任务何时完成)。 在这种情况下,新任务会创建 Job,而 Job 控制器会创建 Pod。
使用简单的控制器而不是一组相互连接的单体控制回路是很有用的。 控制器会失败,所以 Kubernetes 的设计正是考虑到了这一点。
说明:
可以有多个控制器来创建或者更新相同类型的对象。 在后台,Kubernetes 控制器确保它们只关心与其控制资源相关联的资源。
例如,你可以创建 Deployment 和 Job;它们都可以创建 Pod。 Job 控制器不会删除 Deployment 所创建的 Pod,因为有信息 (标签)让控制器可以区分这些 Pod。
运行控制器的方式
Kubernetes 内置一组控制器,运行在 kube-controller-manager 内。 这些内置的控制器提供了重要的核心功能。
Deployment 控制器和 Job 控制器是 Kubernetes 内置控制器的典型例子。 Kubernetes 允许你运行一个稳定的控制平面,这样即使某些内置控制器失败了, 控制平面的其他部分会接替它们的工作。
你会遇到某些控制器运行在控制面之外,用以扩展 Kubernetes。 或者,如果你愿意,你也可以自己编写新控制器。 你可以以一组 Pod 来运行你的控制器,或者运行在 Kubernetes 之外。 最合适的方案取决于控制器所要执行的功能是什么。
接下来
- 阅读 Kubernetes 控制平面组件
- 了解 Kubernetes 对象 的一些基本知识
- 进一步学习 Kubernetes API
- 如果你想编写自己的控制器,请查看 Kubernetes 扩展模式 以及控制器样例。
3.2.4 - 租约(Lease)
分布式系统通常需要租约(Lease);租约提供了一种机制来锁定共享资源并协调集合成员之间的活动。
在 Kubernetes 中,租约概念表示为 coordination.k8s.io
API 组中的
Lease 对象,
常用于类似节点心跳和组件级领导者选举等系统核心能力。
节点心跳
Kubernetes 使用 Lease API 将 kubelet 节点心跳传递到 Kubernetes API 服务器。
对于每个 Node
,在 kube-node-lease
名字空间中都有一个具有匹配名称的 Lease
对象。
在此基础上,每个 kubelet 心跳都是对该 Lease
对象的 update 请求,更新该 Lease 的 spec.renewTime
字段。
Kubernetes 控制平面使用此字段的时间戳来确定此 Node
的可用性。
更多细节请参阅 Node Lease 对象。
领导者选举
Kubernetes 也使用 Lease 确保在任何给定时间某个组件只有一个实例在运行。
这在高可用配置中由 kube-controller-manager
和 kube-scheduler
等控制平面组件进行使用,
这些组件只应有一个实例激活运行,而其他实例待机。
参阅协调领导者选举以了解 Kubernetes 如何基于 Lease API 来选择哪个组件实例充当领导者。
API 服务器身份
Kubernetes v1.26 [beta]
(enabled by default: true)从 Kubernetes v1.26 开始,每个 kube-apiserver
都使用 Lease API 将其身份发布到系统中的其他位置。
虽然它本身并不是特别有用,但为客户端提供了一种机制来发现有多少个 kube-apiserver
实例正在操作
Kubernetes 控制平面。kube-apiserver 租约的存在使得未来可以在各个 kube-apiserver 之间协调新的能力。
你可以检查 kube-system
名字空间中名为 kube-apiserver-<sha256-hash>
的 Lease 对象来查看每个
kube-apiserver 拥有的租约。你还可以使用标签选择算符 apiserver.kubernetes.io/identity=kube-apiserver
:
kubectl -n kube-system get lease -l apiserver.kubernetes.io/identity=kube-apiserver
NAME HOLDER AGE
apiserver-07a5ea9b9b072c4a5f3d1c3702 apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05 5m33s
apiserver-7be9e061c59d368b3ddaf1376e apiserver-7be9e061c59d368b3ddaf1376e_84f2a85d-37c1-4b14-b6b9-603e62e4896f 4m23s
apiserver-1dfef752bcb36637d2763d1868 apiserver-1dfef752bcb36637d2763d1868_c5ffa286-8a9a-45d4-91e7-61118ed58d2e 4m43s
租约名称中使用的 SHA256 哈希基于 API 服务器所看到的操作系统主机名生成。
每个 kube-apiserver 都应该被配置为使用集群中唯一的主机名。
使用相同主机名的 kube-apiserver 新实例将使用新的持有者身份接管现有 Lease,而不是实例化新的 Lease 对象。
你可以通过检查 kubernetes.io/hostname
标签的值来查看 kube-apisever 所使用的主机名:
kubectl -n kube-system get lease apiserver-07a5ea9b9b072c4a5f3d1c3702 -o yaml
apiVersion: coordination.k8s.io/v1
kind: Lease
metadata:
creationTimestamp: "2023-07-02T13:16:48Z"
labels:
apiserver.kubernetes.io/identity: kube-apiserver
kubernetes.io/hostname: master-1
name: apiserver-07a5ea9b9b072c4a5f3d1c3702
namespace: kube-system
resourceVersion: "334899"
uid: 90870ab5-1ba9-4523-b215-e4d4e662acb1
spec:
holderIdentity: apiserver-07a5ea9b9b072c4a5f3d1c3702_0c8914f7-0f35-440e-8676-7844977d3a05
leaseDurationSeconds: 3600
renewTime: "2023-07-04T21:58:48.065888Z"
kube-apiserver 中不再存续的已到期租约将在到期 1 小时后被新的 kube-apiserver 作为垃圾收集。
你可以通过禁用 APIServerIdentity
特性门控来禁用 API 服务器身份租约。
工作负载
你自己的工作负载可以定义自己使用的 Lease。例如,
你可以运行自定义的控制器,
让主要成员或领导者成员在其中执行其对等方未执行的操作。
你定义一个 Lease,以便控制器副本可以使用 Kubernetes API 进行协调以选择或选举一个领导者。
如果你使用 Lease,良好的做法是为明显关联到产品或组件的 Lease 定义一个名称。
例如,如果你有一个名为 Example Foo 的组件,可以使用名为 example-foo
的 Lease。
如果集群操作员或其他终端用户可以部署一个组件的多个实例, 则选择名称前缀并挑选一种机制(例如 Deployment 名称的哈希)以避免 Lease 的名称冲突。
你可以使用另一种方式来达到相同的效果:不同的软件产品不相互冲突。
3.2.5 - 云控制器管理器
Kubernetes v1.11 [beta]
使用云基础设施技术,你可以在公有云、私有云或者混合云环境中运行 Kubernetes。 Kubernetes 的信条是基于自动化的、API 驱动的基础设施,同时避免组件间紧密耦合。
组件 cloud-controller-manager 是指云控制器管理器, 一个 Kubernetes 控制平面组件, 嵌入了特定于云平台的控制逻辑。 云控制器管理器(Cloud Controller Manager)允许将你的集群连接到云提供商的 API 之上, 并将与该云平台交互的组件同与你的集群交互的组件分离开来。
通过分离 Kubernetes 和底层云基础设置之间的互操作性逻辑,
cloud-controller-manager
组件使云提供商能够以不同于 Kubernetes 主项目的步调发布新特征。
cloud-controller-manager
组件是基于一种插件机制来构造的,
这种机制使得不同的云厂商都能将其平台与 Kubernetes 集成。
设计
云控制器管理器以一组多副本的进程集合的形式运行在控制面中,通常表现为 Pod
中的容器。每个 cloud-controller-manager
在同一进程中实现多个控制器。
说明:
你也可以用 Kubernetes 插件 的形式而不是控制面中的一部分来运行云控制器管理器。
云控制器管理器的功能
云控制器管理器中的控制器包括:
节点控制器
节点控制器负责在云基础设施中创建了新服务器时为之更新节点(Node)对象。 节点控制器从云提供商获取当前租户中主机的信息。节点控制器执行以下功能:
- 使用从云平台 API 获取的对应服务器的唯一标识符更新 Node 对象;
- 利用特定云平台的信息为 Node 对象添加注解和标签,例如节点所在的区域 (Region)和所具有的资源(CPU、内存等等);
- 获取节点的网络地址和主机名;
- 检查节点的健康状况。如果节点无响应,控制器通过云平台 API 查看该节点是否已从云中禁用、删除或终止。如果节点已从云中删除, 则控制器从 Kubernetes 集群中删除 Node 对象。
某些云驱动实现中,这些任务被划分到一个节点控制器和一个节点生命周期控制器中。
路由控制器
Route 控制器负责适当地配置云平台中的路由,以便 Kubernetes 集群中不同节点上的容器之间可以相互通信。
取决于云驱动本身,路由控制器可能也会为 Pod 网络分配 IP 地址块。
服务控制器
服务(Service)与受控的负载均衡器、 IP 地址、网络包过滤、目标健康检查等云基础设施组件集成。 服务控制器与云驱动的 API 交互,以配置负载均衡器和其他基础设施组件。 你所创建的 Service 资源会需要这些组件服务。
鉴权
本节分别讲述云控制器管理器为了完成自身工作而产生的对各类 API 对象的访问需求。
节点控制器
节点控制器只操作 Node 对象。它需要读取和修改 Node 对象的完全访问权限。
v1/Node
:
- get
- list
- create
- update
- patch
- watch
- delete
路由控制器
路由控制器会监听 Node 对象的创建事件,并据此配置路由设施。 它需要读取 Node 对象的 Get 权限。
v1/Node
:
- get
服务控制器
服务控制器监测 Service 对象的 create、update 和 delete 事件, 并配置对应服务的 Endpoints 对象 (对于 EndpointSlices,kube-controller-manager 按需对其进行管理)。
为了访问 Service 对象,它需要 list 和 watch 访问权限。 为了更新 Service 对象,它需要 patch 和 update 访问权限。
为了能够配置 Service 对应的 Endpoints 资源, 它需要 create、list、get、watch 和 update 等访问权限。
v1/Service
:
- list
- get
- watch
- patch
- update
其他
在云控制器管理器的实现中,其核心部分需要创建 Event 对象的访问权限, 并创建 ServiceAccount 资源以保证操作安全性的权限。
v1/Event
:
- create
- patch
- update
v1/ServiceAccount
:
- create
用于云控制器管理器 RBAC 的 ClusterRole 如下例所示:
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: cloud-controller-manager
rules:
- apiGroups:
- ""
resources:
- events
verbs:
- create
- patch
- update
- apiGroups:
- ""
resources:
- nodes
verbs:
- '*'
- apiGroups:
- ""
resources:
- nodes/status
verbs:
- patch
- apiGroups:
- ""
resources:
- services
verbs:
- list
- patch
- update
- watch
- apiGroups:
- ""
resources:
- serviceaccounts
verbs:
- create
- apiGroups:
- ""
resources:
- persistentvolumes
verbs:
- get
- list
- update
- watch
- apiGroups:
- ""
resources:
- endpoints
verbs:
- create
- get
- list
- watch
- update
接下来
云控制器管理器的管理 给出了运行和管理云控制器管理器的指南。
要升级 HA 控制平面以使用云控制器管理器, 请参见将复制的控制平面迁移以使用云控制器管理器。
想要了解如何实现自己的云控制器管理器,或者对现有项目进行扩展么?
- 云控制器管理器使用 Go 语言的接口(具体指在
kubernetes/cloud-provider
项目中
cloud.go
文件中所定义的CloudProvider
接口),从而使得针对各种云平台的具体实现都可以接入。
- 本文中列举的共享控制器(节点控制器、路由控制器和服务控制器等)的实现以及其他一些生成具有
CloudProvider 接口的框架的代码,都是 Kubernetes 的核心代码。
特定于云驱动的实现虽不是 Kubernetes 核心成分,仍要实现
CloudProvider
接口。
- 关于如何开发插件的详细信息, 可参考开发云控制器管理器文档。
- 云控制器管理器使用 Go 语言的接口(具体指在
kubernetes/cloud-provider
项目中
3.2.6 - 关于 cgroup v2
在 Linux 上,控制组约束分配给进程的资源。
kubelet 和底层容器运行时都需要对接 cgroup 来强制执行为 Pod 和容器管理资源, 这包括为容器化工作负载配置 CPU/内存请求和限制。
Linux 中有两个 cgroup 版本:cgroup v1 和 cgroup v2。cgroup v2 是新一代的 cgroup
API。
什么是 cgroup v2?
Kubernetes v1.25 [stable]
cgroup v2 是 Linux cgroup
API 的下一个版本。cgroup v2 提供了一个具有增强资源管理能力的统一控制系统。
cgroup v2 对 cgroup v1 进行了多项改进,例如:
- API 中单个统一的层次结构设计
- 更安全的子树委派给容器
- 更新的功能特性, 例如压力阻塞信息(Pressure Stall Information,PSI)
- 跨多个资源的增强资源分配管理和隔离
- 统一核算不同类型的内存分配(网络内存、内核内存等)
- 考虑非即时资源变化,例如页面缓存回写
一些 Kubernetes 特性专门使用 cgroup v2 来增强资源管理和隔离。 例如,MemoryQoS 特性改进了内存 QoS 并依赖于 cgroup v2 原语。
使用 cgroup v2
使用 cgroup v2 的推荐方法是使用一个默认启用 cgroup v2 的 Linux 发行版。
要检查你的发行版是否使用 cgroup v2,请参阅识别 Linux 节点上的 cgroup 版本。
要求
cgroup v2 具有以下要求:
- 操作系统发行版启用 cgroup v2
- Linux 内核为 5.8 或更高版本
- 容器运行时支持 cgroup v2。例如:
- containerd v1.4 和更高版本
- cri-o v1.20 和更高版本
- kubelet 和容器运行时被配置为使用 systemd cgroup 驱动
Linux 发行版 cgroup v2 支持
有关使用 cgroup v2 的 Linux 发行版的列表, 请参阅 cgroup v2 文档。
- Container-Optimized OS(从 M97 开始)
- Ubuntu(从 21.10 开始,推荐 22.04+)
- Debian GNU/Linux(从 Debian 11 Bullseye 开始)
- Fedora(从 31 开始)
- Arch Linux(从 2021 年 4 月开始)
- RHEL 和类似 RHEL 的发行版(从 9 开始)
要检查你的发行版是否使用 cgroup v2, 请参阅你的发行版文档或遵循识别 Linux 节点上的 cgroup 版本中的指示说明。
你还可以通过修改内核 cmdline 引导参数在你的 Linux 发行版上手动启用 cgroup v2。
如果你的发行版使用 GRUB,则应在 /etc/default/grub
下的 GRUB_CMDLINE_LINUX
中添加 systemd.unified_cgroup_hierarchy=1
,
然后执行 sudo update-grub
。不过,推荐的方法仍是使用一个默认已启用 cgroup v2 的发行版。
迁移到 cgroup v2
要迁移到 cgroup v2,需确保满足要求,然后升级到一个默认启用 cgroup v2 的内核版本。
kubelet 能够自动检测操作系统是否运行在 cgroup v2 上并相应调整其操作,无需额外配置。
切换到 cgroup v2 时,用户体验应没有任何明显差异,除非用户直接在节点上或从容器内访问 cgroup 文件系统。
cgroup v2 使用一个与 cgroup v1 不同的 API,因此如果有任何应用直接访问 cgroup 文件系统, 则需要将这些应用更新为支持 cgroup v2 的版本。例如:
- 一些第三方监控和安全代理可能依赖于 cgroup 文件系统。你要将这些代理更新到支持 cgroup v2 的版本。
- 如果以独立的 DaemonSet 的形式运行 cAdvisor 以监控 Pod 和容器, 需将其更新到 v0.43.0 或更高版本。
- 如果你部署 Java 应用程序,最好使用完全支持 cgroup v2 的版本:
- OpenJDK / HotSpot: jdk8u372、11.0.16、15 及更高的版本
- IBM Semeru Runtimes: 8.0.382.0、11.0.20.0、17.0.8.0 及更高的版本
- IBM Java: 8.0.8.6 及更高的版本
- 如果你正在使用 uber-go/automaxprocs 包, 确保你使用的版本是 v1.5.1 或者更高。
识别 Linux 节点上的 cgroup 版本
cgroup 版本取决于正在使用的 Linux 发行版和操作系统上配置的默认 cgroup 版本。
要检查你的发行版使用的是哪个 cgroup 版本,请在该节点上运行 stat -fc %T /sys/fs/cgroup/
命令:
stat -fc %T /sys/fs/cgroup/
对于 cgroup v2,输出为 cgroup2fs
。
对于 cgroup v1,输出为 tmpfs
。
接下来
3.2.7 - 容器运行时接口(CRI)
CRI 是一个插件接口,它使 kubelet 能够使用各种容器运行时,无需重新编译集群组件。
你需要在集群中的每个节点上都有一个可以正常工作的容器运行时, 这样 kubelet 能启动 Pod 及其容器。
容器运行时接口(CRI)是 kubelet 和容器运行时之间通信的主要协议。
Kubernetes 容器运行时接口(Container Runtime Interface;CRI)定义了主要 gRPC 协议, 用于节点组件 kubelet 和容器运行时之间的通信。
API
Kubernetes v1.23 [stable]
当通过 gRPC 连接到容器运行时,kubelet 将充当客户端。运行时和镜像服务端点必须在容器运行时中可用,
可以使用 --image-service-endpoint
命令行标志在 kubelet 中单独配置。
对 Kubernetes v1.31,kubelet 偏向于使用 CRI v1
版本。
如果容器运行时不支持 CRI 的 v1
版本,那么 kubelet 会尝试协商较老的、仍被支持的所有版本。
v1.31 版本的 kubelet 也可协商 CRI v1alpha2
版本,但该版本被视为已弃用。
如果 kubelet 无法协商出可支持的 CRI 版本,则 kubelet 放弃并且不会注册为节点。
升级
升级 Kubernetes 时,kubelet 会尝试在组件重启时自动选择最新的 CRI 版本。 如果失败,则将如上所述进行回退。如果由于容器运行时已升级而需要 gRPC 重拨, 则容器运行时还必须支持最初选择的版本,否则重拨预计会失败。 这需要重新启动 kubelet。
接下来
- 了解更多有关 CRI 协议定义
3.2.8 - 垃圾收集
垃圾收集(Garbage Collection)是 Kubernetes 用于清理集群资源的各种机制的统称。 垃圾收集允许系统清理如下资源:
- 终止的 Pod
- 已完成的 Job
- 不再存在属主引用的对象
- 未使用的容器和容器镜像
- 动态制备的、StorageClass 回收策略为 Delete 的 PV 卷
- 阻滞或者过期的 CertificateSigningRequest (CSR)
- 在以下情形中删除了的节点对象:
- 当集群使用云控制器管理器运行于云端时;
- 当集群使用类似于云控制器管理器的插件运行在本地环境中时。
- 节点租约对象
属主与依赖
Kubernetes 中很多对象通过属主引用 链接到彼此。属主引用(Owner Reference)可以告诉控制面哪些对象依赖于其他对象。 Kubernetes 使用属主引用来为控制面以及其他 API 客户端在删除某对象时提供一个清理关联资源的机会。 在大多数场合,Kubernetes 都是自动管理属主引用的。
属主关系与某些资源所使用的标签和选择算符不同。
例如,考虑一个创建 EndpointSlice
对象的 Service。
Service 使用标签来允许控制面确定哪些 EndpointSlice
对象被该 Service 使用。
除了标签,每个被 Service 托管的 EndpointSlice
对象还有一个属主引用属性。
属主引用可以帮助 Kubernetes 中的不同组件避免干预并非由它们控制的对象。
说明:
根据设计,系统不允许出现跨名字空间的属主引用。名字空间作用域的依赖对象可以指定集群作用域或者名字空间作用域的属主。 名字空间作用域的属主必须存在于依赖对象所在的同一名字空间。 如果属主位于不同名字空间,则属主引用被视为不存在,而当检查发现所有属主都已不存在时,依赖对象会被删除。
集群作用域的依赖对象只能指定集群作用域的属主。 在 1.20 及更高版本中,如果一个集群作用域的依赖对象指定了某个名字空间作用域的类别作为其属主, 则该对象被视为拥有一个无法解析的属主引用,因而无法被垃圾收集处理。
在 1.20 及更高版本中,如果垃圾收集器检测到非法的跨名字空间 ownerReference
,
或者某集群作用域的依赖对象的 ownerReference
引用某名字空间作用域的类别,
系统会生成一个警告事件,其原因为 OwnerRefInvalidNamespace
和 involvedObject
设置为非法的依赖对象。你可以通过运行
kubectl get events -A --field-selector=reason=OwnerRefInvalidNamespace
来检查是否存在这类事件。
级联删除
Kubernetes 会检查并删除那些不再拥有属主引用的对象,例如在你删除了 ReplicaSet 之后留下来的 Pod。当你删除某个对象时,你可以控制 Kubernetes 是否去自动删除该对象的依赖对象, 这个过程称为级联删除(Cascading Deletion)。 级联删除有两种类型,分别如下:
- 前台级联删除
- 后台级联删除
你也可以使用 Kubernetes Finalizers 来控制垃圾收集机制如何以及何时删除包含属主引用的资源。
前台级联删除
在前台级联删除中,正在被你删除的属主对象首先进入 deletion in progress 状态。 在这种状态下,针对属主对象会发生以下事情:
- Kubernetes API 服务器将某对象的
metadata.deletionTimestamp
字段设置为该对象被标记为要删除的时间点。 - Kubernetes API 服务器也会将
metadata.finalizers
字段设置为foregroundDeletion
。 - 在删除过程完成之前,通过 Kubernetes API 仍然可以看到该对象。
当属主对象进入删除进行中状态后,控制器会删除其已知的依赖对象。 在删除所有已知的依赖对象后,控制器会删除属主对象。 这时,通过 Kubernetes API 就无法再看到该对象。
在前台级联删除过程中,唯一会阻止属主对象被删除的是那些带有
ownerReference.blockOwnerDeletion=true
字段并且存在于垃圾收集控制器缓存中的依赖对象。
垃圾收集控制器缓存中可能不包含那些无法成功被列举/监视的资源类型的对象,
或在属主对象删除的同时创建的对象。
参阅使用前台级联删除
以了解进一步的细节。
后台级联删除
在后台级联删除过程中,Kubernetes 服务器立即删除属主对象, 而垃圾收集控制器(无论是自定义的还是默认的)在后台清理所有依赖对象。 如果存在 Finalizers,它会确保所有必要的清理任务完成后对象才被删除。 默认情况下,Kubernetes 使用后台级联删除方案,除非你手动设置了要使用前台删除, 或者选择遗弃依赖对象。
参阅使用后台级联删除以了解进一步的细节。
被遗弃的依赖对象
当 Kubernetes 删除某个属主对象时,被留下来的依赖对象被称作被遗弃的(Orphaned)对象。 默认情况下,Kubernetes 会删除依赖对象。要了解如何重载这种默认行为, 可参阅删除属主对象和遗弃依赖对象。
未使用容器和镜像的垃圾收集
kubelet 会每五分钟对未使用的镜像执行一次垃圾收集, 每分钟对未使用的容器执行一次垃圾收集。 你应该避免使用外部的垃圾收集工具,因为外部工具可能会破坏 kubelet 的行为,移除应该保留的容器。
要配置对未使用容器和镜像的垃圾收集选项,
可以使用一个配置文件,基于
KubeletConfiguration
资源类型来调整与垃圾收集相关的 kubelet 行为。
容器镜像生命周期
Kubernetes 通过其镜像管理器(Image Manager) 来管理所有镜像的生命周期, 该管理器是 kubelet 的一部分,工作时与 cadvisor 协同。 kubelet 在作出垃圾收集决定时会考虑如下磁盘用量约束:
HighThresholdPercent
LowThresholdPercent
磁盘用量超出所配置的 HighThresholdPercent
值时会触发垃圾收集,
垃圾收集器会基于镜像上次被使用的时间来按顺序删除它们,首先删除的是最近未使用的镜像。
kubelet 会持续删除镜像,直到磁盘用量到达 LowThresholdPercent
值为止。
未使用容器镜像的垃圾收集
Kubernetes v1.30 [beta]
(enabled by default: true)这是一个 Beta 特性,不论磁盘使用情况如何,你都可以指定本地镜像未被使用的最长时间。 这是一个可以为每个节点配置的 kubelet 设置。
要配置该设置,你需要在 kubelet 配置文件中为 imageMaximumGCAge
字段设置一个值。
该值应遵循 Kubernetes 持续时间(Duration)格式; 有关更多详细信息,请参阅词汇表中的持续时间(Duration)。
例如,你可以将配置字段设置为 12h45m
,代表 12 小时 45 分钟。
说明:
这个特性不会跟踪 kubelet 重新启动后的镜像使用情况。
如果 kubelet 被重新启动,所跟踪的镜像年龄会被重置,
导致 kubelet 在根据镜像年龄进行垃圾收集时需要等待完整的
imageMaximumGCAge
时长。
容器垃圾收集
kubelet 会基于如下变量对所有未使用的容器执行垃圾收集操作,这些变量都是你可以定义的:
MinAge
:kubelet 可以垃圾回收某个容器时该容器的最小年龄。设置为0
表示禁止使用此规则。MaxPerPodContainer
:每个 Pod 可以包含的已死亡的容器个数上限。设置为小于0
的值表示禁止使用此规则。MaxContainers
:集群中可以存在的已死亡的容器个数上限。设置为小于0
的值意味着禁止应用此规则。
除以上变量之外,kubelet 还会垃圾收集除无标识的以及已删除的容器,通常从最近未使用的容器开始。
当保持每个 Pod 的最大数量的容器(MaxPerPodContainer
)会使得全局的已死亡容器个数超出上限
(MaxContainers
)时,MaxPerPodContainer
和 MaxContainers
之间可能会出现冲突。
在这种情况下,kubelet 会调整 MaxPerPodContainer
来解决这一冲突。
最坏的情形是将 MaxPerPodContainer
降格为 1
,并驱逐最近未使用的容器。
此外,当隶属于某已被删除的 Pod 的容器的年龄超过 MinAge
时,它们也会被删除。
说明:
kubelet 仅会回收由它所管理的容器。
配置垃圾收集
你可以通过配置特定于管理资源的控制器来调整资源的垃圾收集行为。 下面的页面为你展示如何配置垃圾收集:
接下来
- 进一步了解 Kubernetes 对象的属主关系。
- 进一步了解 Kubernetes finalizers。
- 进一步了解 TTL 控制器, 该控制器负责清理已完成的 Job。
3.2.9 - 混合版本代理
Kubernetes v1.28 [alpha]
(enabled by default: false)Kubernetes 1.31 包含了一个 Alpha 特性,可以让 API 服务器代理指向其他对等 API 服务器的资源请求。当一个集群中运行着多个 API 服务器,且各服务器的 Kubernetes 版本不同时 (例如在上线 Kubernetes 新版本的时间跨度较长时),这一特性非常有用。
此特性通过将(升级过程中所发起的)资源请求引导到正确的 kube-apiserver 使得集群管理员能够配置高可用的、升级动作更安全的集群。 该代理机制可以防止用户在升级过程中看到意外的 404 Not Found 错误。
这个机制称为 Mixed Version Proxy(混合版本代理)。
启用混合版本代理
当你启动 API 服务器时,
确保启用了 UnknownVersionInteroperabilityProxy
特性门控:
kube-apiserver \
--feature-gates=UnknownVersionInteroperabilityProxy=true \
# 需要为此特性添加的命令行参数
--peer-ca-file=<指向 kube-apiserver CA 证书的路径>
--proxy-client-cert-file=<指向聚合器代理证书的路径>,
--proxy-client-key-file=<指向聚合器代理密钥的路径>,
--requestheader-client-ca-file=<指向聚合器 CA 证书的路径>,
# requestheader-allowed-names 可设置为空以允许所有 Common Name
--requestheader-allowed-names=<验证代理客户端证书的合法 Common Name>,
# 此特性的可选标志
--peer-advertise-ip=`应由对等方用于代理请求的 kube-apiserver IP`
--peer-advertise-port=`应由对等方用于代理请求的 kube-apiserver 端口`
# ... 和其他常规标志
API 服务器之间的代理传输和身份验证
源 kube-apiserver 重用现有的 API 服务器客户端身份验证标志
--proxy-client-cert-file
和--proxy-client-key-file
来表明其身份,供对等(目标 kube-apiserver)验证。 目标 API 服务器根据你使用--requestheader-client-ca-file
命令行参数指定的配置来验证对等连接。要对目标服务器所用的证书进行身份验证,必须通过指定
--peer-ca-file
命令行参数来为源 API 服务器配置一个证书机构包。
对等 API 服务器连接的配置
要设置 kube-apiserver 的网络位置以供对等方来代理请求,
使用为 kube-apiserver 设置的 --peer-advertise-ip
和 --peer-advertise-port
命令行参数,
或在 API 服务器配置文件中指定这些字段。如果未指定这些参数,对等方将使用 --advertise-address
或 --bind-address
命令行参数的值。如果这些也未设置,则使用主机的默认接口。
混合版本代理
启用混合版本代理时, 聚合层会加载一个特殊的过滤器, 完成以下操作:
- 当资源请求到达无法提供该 API 的 API 服务器时 (可能的原因是服务器早于该 API 的正式引入日期或该 API 在 API 服务器上被关闭), API 服务器会尝试将请求发送到能够提供所请求 API 的对等 API 服务器。 API 服务器通过发现本地服务器无法识别的 API 组/版本/资源来实现这一点, 并尝试将这些请求代理到能够处理这些请求的对等 API 服务器。
- 如果对等 API 服务器无法响应,则源 API 服务器将以 503("Service Unavailable")错误进行响应。
内部工作原理
当 API 服务器收到一个资源请求时,它首先检查哪些 API 服务器可以提供所请求的资源。
这个检查是使用内部的
StorageVersion
API
进行的。
- 如果资源被收到请求(例如
GET /api/v1/pods/some-pod
)的 API 服务器所了解,则请求会在本地处理。
- 如果没有找到适合所请求资源(例如
GET /my-api/v1/my-resource
)的内部StorageVersion
对象, 并且所配置的 APIService 设置了指向扩展 API 服务器的代理,那么代理操作将按照扩展 API 的常规流程进行。
如果找到了对应所请求资源(例如
GET /batch/v1/jobs
)的合法的内部StorageVersion
对象, 并且正在处理请求的 API 服务器(处理中的 API 服务器)禁用了batch
API, 则正处理的 API 服务器使用已获取的StorageVersion
对象中的信息, 获取提供相关 API 组/版本/资源(在此情况下为api/v1/batch
)的对等 API 服务器。 处理中的 API 服务器随后将请求代理到能够理解所请求资源且匹配的对等 kube-apiserver 之一。如果没有对等方了解所给的 API 组/版本/资源,则处理请求的 API 服务器将请求传递给自己的处理程序链, 最终应返回 404("Not Found")响应。
如果处理请求的 API 服务器已经识别并选择了一个对等 API 服务器,但该对等方无法响应 (原因可能是网络连接问题或正接收的请求与向控制平面注册对等信息的控制器之间存在数据竞争等), 则处理请求的 API 服务器会以 503("Service Unavailable")错误进行响应。
3.3 - 容器
本页将讨论容器和容器镜像,以及它们在运维和解决方案开发中的应用。
容器是一个多义词。每当你使用这个词时,请确认你的受众是否使用相同的定义。
每个运行的容器都是可重复的; 包含依赖环境在内的标准,意味着无论你在哪里运行它都会得到相同的行为。
容器将应用程序从底层的主机设施中解耦。 这使得在不同的云或 OS 环境中部署更加容易。
Kubernetes 集群中的每个节点都会运行容器, 这些容器构成分配给该节点的 Pod。 单个 Pod 中的容器会在共同调度下,于同一位置运行在相同的节点上。
容器镜像
容器镜像是一个随时可以运行的软件包, 包含运行应用程序所需的一切:代码和它需要的所有运行时、应用程序和系统库,以及一些基本设置的默认值。
容器旨在设计成无状态且不可变的: 你不应更改已经运行的容器的代码。如果有一个容器化的应用程序需要修改, 正确的流程是:先构建包含更改的新镜像,再基于新构建的镜像重新运行容器。
容器运行时
这个基础组件使 Kubernetes 能够有效运行容器。 它负责管理 Kubernetes 环境中容器的执行和生命周期。
Kubernetes 支持许多容器运行环境,例如 containerd、 CRI-O 以及 Kubernetes CRI (容器运行环境接口) 的其他任何实现。
通常,你可以允许集群为一个 Pod 选择其默认的容器运行时。如果你需要在集群中使用多个容器运行时, 你可以为一个 Pod 指定 RuntimeClass, 以确保 Kubernetes 会使用特定的容器运行时来运行这些容器。
你还可以通过 RuntimeClass,使用相同的容器运行时,但使用不同设定的配置来运行不同的 Pod。
3.3.1 - 镜像
容器镜像(Image)所承载的是封装了应用程序及其所有软件依赖的二进制数据。 容器镜像是可执行的软件包,可以单独运行;该软件包对所处的运行时环境具有良定(Well Defined)的假定。
你通常会创建应用的容器镜像并将其推送到某仓库(Registry),然后在 Pod 中引用它。
本页概要介绍容器镜像的概念。
说明:
如果你正在寻找 Kubernetes 某个发行版本(如最新次要版本 v1.31) 的容器镜像,请访问下载 Kubernetes。
镜像名称
容器镜像通常会被赋予 pause
、example/mycontainer
或者 kube-apiserver
这类的名称。
镜像名称也可以包含所在仓库的主机名。例如:fictional.registry.example/imagename
。
还可以包含仓库的端口号,例如:fictional.registry.example:10443/imagename
。
如果你不指定仓库的主机名,Kubernetes 认为你在使用 Docker 公共仓库。
你可以通过在容器运行时 配置中设置默认镜像仓库来更改此行为。
在镜像名称之后,你可以添加一个标签(Tag) 或 摘要(digest)
(与使用 docker
或 podman
等命令时的方式相同)。
使用标签能让你辨识同一镜像序列中的不同版本。
摘要是特定版本镜像的唯一标识符,是镜像内容的哈希值,不可变。
镜像标签可以包含小写字母、大写字母、数字、下划线(_
)、句点(.
)和连字符(-
)。
它的长度最多为 128 个字符,并且必须遵循正则表达式模式:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}
。
你可以在 OCI 分发规范
中阅读有关并找到验证正则表达式的更多信息。
如果你不指定标签,Kubernetes 认为你想使用标签 latest
。
图像摘要由哈希算法(例如 sha256
)和哈希值组成,例如:
sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
。
你可以在 OCI 镜像规范
中找到有关摘要格式的更多信息。
Kubernetes 可以使用的一些镜像名称示例包括:
busybox
- 仅包含镜像名称,没有标签或摘要,Kubernetes 将使用 Docker 公共镜像仓库和latest
标签。 (例如docker.io/library/busybox:latest
)busybox:1.32.0
- 带标签的镜像名称,Kubernetes 将使用 Docker 公共镜像仓库。 (例如docker.io/library/busybox:1.32.0
)registry.k8s.io/pause:latest
- 带有自定义镜像仓库和latest
标签的镜像名称。registry.k8s.io/pause:3.5
- 带有自定义镜像仓库和非latest
标签的镜像名称。registry.k8s.io/pause@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
- 带摘要的镜像名称。registry.k8s.io/pause:3.5@sha256:1ff6c18fbef2045af6b9c16bf034cc421a29027b800e4f9b68ae9b1cb3e9ae07
- 带有标签和摘要的镜像名称,镜像拉取仅参考摘要。
更新镜像
当你最初创建一个 Deployment、
StatefulSet、Pod
或者其他包含 Pod 模板的对象时,如果没有显式设定的话,
Pod 中所有容器的默认镜像拉取策略是 IfNotPresent
。这一策略会使得
kubelet
在镜像已经存在的情况下直接略过拉取镜像的操作。
镜像拉取策略
容器的 imagePullPolicy
和镜像的标签会影响
kubelet 尝试拉取(下载)指定的镜像。
以下列表包含了 imagePullPolicy
可以设置的值,以及这些值的效果:
IfNotPresent
- 只有当镜像在本地不存在时才会拉取。
Always
- 每当 kubelet 启动一个容器时,kubelet 会查询容器的镜像仓库, 将名称解析为一个镜像摘要。 如果 kubelet 有一个容器镜像,并且对应的摘要已在本地缓存,kubelet 就会使用其缓存的镜像; 否则,kubelet 就会使用解析后的摘要拉取镜像,并使用该镜像来启动容器。
Never
- kubelet 不会尝试获取镜像。如果镜像已经以某种方式存在本地, kubelet 会尝试启动容器;否则,会启动失败。 更多细节见提前拉取镜像。
只要能够可靠地访问镜像仓库,底层镜像提供者的缓存语义甚至可以使 imagePullPolicy: Always
高效。
你的容器运行时可以注意到节点上已经存在的镜像层,这样就不需要再次下载。
说明:
在生产环境中部署容器时,你应该避免使用 :latest
标签,因为这使得正在运行的镜像的版本难以追踪,并且难以正确地回滚。
相反,应指定一个有意义的标签,如 v1.42.0
,和/或者一个摘要。
为了确保 Pod 总是使用相同版本的容器镜像,你可以指定镜像的摘要;
将 <image-name>:<tag>
替换为 <image-name>@<digest>
,例如
image@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2
。
当使用镜像标签时,如果镜像仓库修改了代码所对应的镜像标签,可能会出现新旧代码混杂在 Pod 中运行的情况。 镜像摘要唯一标识了镜像的特定版本,因此 Kubernetes 每次启动具有指定镜像名称和摘要的容器时,都会运行相同的代码。 通过摘要指定镜像可固定你运行的代码,这样镜像仓库的变化就不会导致版本的混杂。
有一些第三方的准入控制器 在创建 Pod(和 Pod 模板)时产生变更,这样运行的工作负载就是根据镜像摘要,而不是标签来定义的。 无论镜像仓库上的标签发生什么变化,你都想确保你所有的工作负载都运行相同的代码,那么指定镜像摘要会很有用。
默认镜像拉取策略
当你(或控制器)向 API 服务器提交一个新的 Pod 时,你的集群会在满足特定条件时设置 imagePullPolicy
字段:
- 如果你省略了
imagePullPolicy
字段,并且你为容器镜像指定了摘要, 那么imagePullPolicy
会自动设置为IfNotPresent
。
- 如果你省略了
imagePullPolicy
字段,并且容器镜像的标签是:latest
,imagePullPolicy
会自动设置为Always
。 - 如果你省略了
imagePullPolicy
字段,并且没有指定容器镜像的标签,imagePullPolicy
会自动设置为Always
。 - 如果你省略了
imagePullPolicy
字段,并且为容器镜像指定了非:latest
的标签,imagePullPolicy
就会自动设置为IfNotPresent
。
说明:
容器的 imagePullPolicy
的值总是在对象初次创建时设置的,
如果后来镜像的标签或摘要发生变化,则不会更新。
例如,如果你用一个非 :latest
的镜像标签创建一个 Deployment,
并在随后更新该 Deployment 的镜像标签为 :latest
,则 imagePullPolicy
字段不会变成 Always
。
你必须手动更改已经创建的资源的拉取策略。
必要的镜像拉取
如果你想总是强制执行拉取,你可以使用下述的一种方式:
- 设置容器的
imagePullPolicy
为Always
。 - 省略
imagePullPolicy
,并使用:latest
作为镜像标签; 当你提交 Pod 时,Kubernetes 会将策略设置为Always
。 - 省略
imagePullPolicy
和镜像的标签; 当你提交 Pod 时,Kubernetes 会将策略设置为Always
。 - 启用准入控制器 AlwaysPullImages。
ImagePullBackOff
当 kubelet 使用容器运行时创建 Pod 时,容器可能因为 ImagePullBackOff
导致状态为
Waiting。
ImagePullBackOff
状态意味着容器无法启动,
因为 Kubernetes 无法拉取容器镜像(原因包括无效的镜像名称,或从私有仓库拉取而没有 imagePullSecret
)。
BackOff
部分表示 Kubernetes 将继续尝试拉取镜像,并增加回退延迟。
Kubernetes 会增加每次尝试之间的延迟,直到达到编译限制,即 300 秒(5 分钟)。
基于运行时类的镜像拉取
Kubernetes v1.29 [alpha]
(enabled by default: false)Kubernetes 包含了根据 Pod 的 RuntimeClass 来执行镜像拉取的 Alpha 支持。
如果你启用了 RuntimeClassInImageCriApi
特性门控,
kubelet 会通过一个元组(镜像名称,运行时处理程序)而不仅仅是镜像名称或镜像摘要来引用容器镜像。
你的容器运行时
可能会根据选定的运行时处理程序调整其行为。
基于运行时类来拉取镜像对于基于 VM 的容器(如 Windows Hyper-V 容器)会有帮助。
串行和并行镜像拉取
默认情况下,kubelet 以串行方式拉取镜像。 也就是说,kubelet 一次只向镜像服务发送一个镜像拉取请求。 其他镜像拉取请求必须等待,直到正在处理的那个请求完成。
节点独立地做出镜像拉取的决策。即使你使用串行的镜像拉取,两个不同的节点也可以并行拉取相同的镜像。
如果你想启用并行镜像拉取,可以在 kubelet 配置
中将字段 serializeImagePulls
设置为 false。
当serializeImagePulls
设置为 false 时,kubelet 会立即向镜像服务发送镜像拉取请求,多个镜像将同时被拉动。
启用并行镜像拉取时,请确保你的容器运行时的镜像服务可以处理并行镜像拉取。
kubelet 从不代表一个 Pod 并行地拉取多个镜像。
例如,如果你有一个 Pod,它有一个初始容器和一个应用容器,那么这两个容器的镜像拉取将不会并行。 但是,如果你有两个使用不同镜像的 Pod,当启用并行镜像拉取时,kubelet 会代表两个不同的 Pod 并行拉取镜像。
最大并行镜像拉取数量
Kubernetes v1.27 [alpha]
当 serializeImagePulls
被设置为 false 时,kubelet 默认对同时拉取的最大镜像数量没有限制。
如果你想限制并行镜像拉取的数量,可以在 kubelet 配置中设置字段 maxParallelImagePulls
。
当 maxParallelImagePulls
设置为 n 时,只能同时拉取 n 个镜像,
超过 n 的任何镜像都必须等到至少一个正在进行拉取的镜像拉取完成后,才能拉取。
当启用并行镜像拉取时,限制并行镜像拉取的数量可以防止镜像拉取消耗过多的网络带宽或磁盘 I/O。
你可以将 maxParallelImagePulls
设置为大于或等于 1 的正数。
如果将 maxParallelImagePulls
设置为大于等于 2,则必须将 serializeImagePulls
设置为 false。
kubelet 在无效的 maxParallelImagePulls
设置下会启动失败。
带镜像索引的多架构镜像
除了提供二进制的镜像之外,
容器仓库也可以提供容器镜像索引。
镜像索引可以指向镜像的多个镜像清单,
提供特定于体系结构版本的容器。
这背后的理念是让你可以为镜像命名(例如:pause
、example/mycontainer
、kube-apiserver
)
的同时,允许不同的系统基于它们所使用的机器体系结构取回正确的二进制镜像。
Kubernetes 自身通常在命名容器镜像时添加后缀 -$(ARCH)
。
为了向前兼容,请在生成较老的镜像时也提供后缀。
这里的理念是为某镜像(如 pause
)生成针对所有平台都适用的清单时,
生成 pause-amd64
这类镜像,以便较老的配置文件或者将镜像后缀硬编码到其中的
YAML 文件也能兼容。
使用私有仓库
从私有仓库读取镜像时可能需要密钥。 凭据可以用以下方式提供:
- 配置节点向私有仓库进行身份验证
- 所有 Pod 均可读取任何已配置的私有仓库
- 需要集群管理员配置节点
- kubelet 凭据提供程序,动态获取私有仓库的凭据
- kubelet 可以被配置为使用凭据提供程序 exec 插件来访问对应的私有镜像库
- 预拉镜像
- 所有 Pod 都可以使用节点上缓存的所有镜像
- 需要所有节点的 root 访问权限才能进行设置
- 在 Pod 中设置 ImagePullSecrets
- 只有提供自己密钥的 Pod 才能访问私有仓库
- 特定于厂商的扩展或者本地扩展
- 如果你在使用定制的节点配置,你(或者云平台提供商)可以实现让节点向容器仓库认证的机制
下面将详细描述每一项。
配置 Node 对私有仓库认证
设置凭据的具体说明取决于你选择使用的容器运行时和仓库。 你应该参考解决方案的文档来获取最准确的信息。
有关配置私有容器镜像仓库的示例, 请参阅任务从私有镜像库中拉取镜像。 该示例使用 Docker Hub 中的私有镜像仓库。
用于认证镜像拉取的 kubelet 凭据提供程序
说明:
此方法尤其适合 kubelet 需要动态获取仓库凭据时。 最常用于由云提供商提供的仓库,其中身份认证令牌的生命期是短暂的。
你可以配置 kubelet,以调用插件可执行文件的方式来动态获取容器镜像的仓库凭据。 这是为私有仓库获取凭据最稳健和最通用的方法,但也需要 kubelet 级别的配置才能启用。
有关更多细节请参见配置 kubelet 镜像凭据提供程序。
config.json 说明
对于 config.json
的解释在原始 Docker 实现和 Kubernetes 的解释之间有所不同。
在 Docker 中,auths
键只能指定根 URL,而 Kubernetes 允许 glob URL 以及前缀匹配的路径。
唯一的限制是 glob 模式(*
)必须为每个子域名包括点(.
)。
匹配的子域名数量必须等于 glob 模式(*.
)的数量,例如:
*.kubernetes.io
不会匹配kubernetes.io
,但会匹配abc.kubernetes.io
*.*.kubernetes.io
不会匹配abc.kubernetes.io
,但会匹配abc.def.kubernetes.io
prefix.*.io
将匹配prefix.kubernetes.io
*-good.kubernetes.io
将匹配prefix-good.kubernetes.io
这意味着,像这样的 config.json
是有效的:
{
"auths": {
"my-registry.io/images": { "auth": "…" },
"*.my-registry.io/images": { "auth": "…" }
}
}
现在镜像拉取操作会将每种有效模式的凭据都传递给 CRI 容器运行时。例如下面的容器镜像名称会匹配成功:
my-registry.io/images
my-registry.io/images/my-image
my-registry.io/images/another-image
sub.my-registry.io/images/my-image
但这些不会匹配成功:
a.sub.my-registry.io/images/my-image
a.b.sub.my-registry.io/images/my-image
kubelet 为每个找到的凭据的镜像按顺序拉取。这意味着对于不同的路径在 config.json
中也可能有多项:
{
"auths": {
"my-registry.io/images": {
"auth": "…"
},
"my-registry.io/images/subpath": {
"auth": "…"
}
}
}
如果一个容器指定了要拉取的镜像 my-registry.io/images/subpath/my-image
,
并且其中一个失败,kubelet 将尝试从另一个身份验证源下载镜像。
提前拉取镜像
说明:
该方法适用于你能够控制节点配置的场合。 如果你的云供应商负责管理节点并自动置换节点,这一方案无法可靠地工作。
默认情况下,kubelet
会尝试从指定的仓库拉取每个镜像。
但是,如果容器属性 imagePullPolicy
设置为 IfNotPresent
或者 Never
,
则会优先使用(对应 IfNotPresent
)或者一定使用(对应 Never
)本地镜像。
如果你希望使用提前拉取镜像的方法代替仓库认证,就必须保证集群中所有节点提前拉取的镜像是相同的。
这一方案可以用来提前载入指定的镜像以提高速度,或者作为向私有仓库执行身份认证的一种替代方案。
所有的 Pod 都可以使用节点上提前拉取的镜像。
在 Pod 上指定 ImagePullSecrets
说明:
运行使用私有仓库中镜像的容器时,建议使用这种方法。
Kubernetes 支持在 Pod 中设置容器镜像仓库的密钥。
imagePullSecrets
必须全部与 Pod 位于同一个名字空间中。
引用的 Secret 必须是 kubernetes.io/dockercfg
或 kubernetes.io/dockerconfigjson
类型。
使用 Docker Config 创建 Secret
你需要知道用于向仓库进行身份验证的用户名、密码和客户端电子邮件地址,以及它的主机名。 运行以下命令,注意替换适当的大写值:
kubectl create secret docker-registry <name> \
--docker-server=DOCKER_REGISTRY_SERVER \
--docker-username=DOCKER_USER \
--docker-password=DOCKER_PASSWORD \
--docker-email=DOCKER_EMAIL
如果你已经有 Docker 凭据文件,则可以将凭据文件导入为 Kubernetes Secret, 而不是执行上面的命令。 基于已有的 Docker 凭据创建 Secret 解释了如何完成这一操作。
如果你在使用多个私有容器仓库,这种技术将特别有用。
原因是 kubectl create secret docker-registry
创建的是仅适用于某个私有仓库的 Secret。
说明:
Pod 只能引用位于自身所在名字空间中的 Secret,因此需要针对每个名字空间重复执行上述过程。
在 Pod 中引用 ImagePullSecrets
现在,在创建 Pod 时,可以在 Pod 定义中增加 imagePullSecrets
部分来引用该 Secret。
imagePullSecrets
数组中的每一项只能引用同一名字空间中的 Secret。
例如:
cat <<EOF > pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: foo
namespace: awesomeapps
spec:
containers:
- name: foo
image: janedoe/awesomeapp:v1
imagePullSecrets:
- name: myregistrykey
EOF
cat <<EOF >> ./kustomization.yaml
resources:
- pod.yaml
EOF
你需要对使用私有仓库的每个 Pod 执行以上操作。不过,
设置该字段的过程也可以通过为服务账号资源设置
imagePullSecrets
来自动完成。有关详细指令,
可参见将 ImagePullSecrets 添加到服务账号。
你也可以将此方法与节点级别的 .docker/config.json
配置结合使用。
来自不同来源的凭据会被合并。
使用案例
配置私有仓库有多种方案,以下是一些常用场景和建议的解决方案。
- 集群运行非专有镜像(例如,开源镜像)。镜像不需要隐藏。
- 使用来自公共仓库的公共镜像
- 无需配置
- 某些云厂商会自动为公开镜像提供高速缓存,以便提升可用性并缩短拉取镜像所需时间
- 使用来自公共仓库的公共镜像
- 集群运行一些专有镜像,这些镜像需要对公司外部隐藏,对所有集群用户可见
- 使用托管的私有仓库
- 在需要访问私有仓库的节点上可能需要手动配置
- 或者,在防火墙内运行一个组织内部的私有仓库,并开放读取权限
- 不需要配置 Kubernetes
- 使用控制镜像访问的托管容器镜像仓库服务
- 与手动配置节点相比,这种方案能更好地处理集群自动扩缩容
- 或者,在不方便更改节点配置的集群中,使用
imagePullSecrets
- 使用托管的私有仓库
- 集群使用专有镜像,且有些镜像需要更严格的访问控制
- 确保 AlwaysPullImages 准入控制器被启用。 否则,所有 Pod 都可以使用所有镜像。
- 确保将敏感数据存储在 Secret 资源中,而不是将其打包在镜像里。
- 集群是多租户的并且每个租户需要自己的私有仓库
- 确保 AlwaysPullImages 准入控制器。 否则,所有租户的所有的 Pod 都可以使用所有镜像。
- 为私有仓库启用鉴权。
- 为每个租户生成访问仓库的凭据,放置在 Secret 中,并将 Secret 发布到各租户的名字空间下。
- 租户将 Secret 添加到每个名字空间中的 imagePullSecrets。
如果你需要访问多个仓库,可以为每个仓库创建一个 Secret。
旧版的内置 kubelet 凭据提供程序
在旧版本的 Kubernetes 中,kubelet 与云提供商凭据直接集成。 这使它能够动态获取镜像仓库的凭据。
kubelet 凭据提供程序集成存在三个内置实现: ACR(Azure 容器仓库)、ECR(Elastic 容器仓库)和 GCR(Google 容器仓库)。
有关该旧版机制的更多信息,请阅读你正在使用的 Kubernetes 版本的文档。 从 Kubernetes v1.26 到 v1.31 不再包含该旧版机制,因此你需要:
- 在每个节点上配置一个 kubelet 镜像凭据提供程序
- 使用
imagePullSecrets
和至少一个 Secret 指定镜像拉取凭据
接下来
- 阅读 OCI Image Manifest 规范。
- 了解容器镜像垃圾收集。
- 了解从私有仓库拉取镜像。
3.3.2 - 容器环境
本页描述了在容器环境里容器可用的资源。
容器环境
Kubernetes 的容器环境给容器提供了几个重要的资源:
容器信息
一个容器的 hostname 是该容器运行所在的 Pod 的名称。通过 hostname
命令或者调用 libc 中的
gethostname
函数可以获取该名称。
Pod 名称和命名空间可以通过 下行 API 转换为环境变量。
Pod 定义中的用户所定义的环境变量也可在容器中使用,就像在 container 镜像中静态指定的任何环境变量一样。
集群信息
创建容器时正在运行的所有服务都可用作该容器的环境变量。 这里的服务仅限于新容器的 Pod 所在的名字空间中的服务,以及 Kubernetes 控制面的服务。
对于名为 foo 的服务,当映射到名为 bar 的容器时,定义了以下变量:
FOO_SERVICE_HOST=<其上服务正运行的主机>
FOO_SERVICE_PORT=<其上服务正运行的端口>
服务具有专用的 IP 地址。如果启用了 DNS 插件, 可以在容器中通过 DNS 来访问服务。
接下来
- 学习更多有关容器生命周期回调的知识。
- 动手为容器的生命周期事件设置处理函数。
3.3.3 - 容器运行时类(Runtime Class)
Kubernetes v1.20 [stable]
本页面描述了 RuntimeClass 资源和运行时的选择机制。
RuntimeClass 是一个用于选择容器运行时配置的特性,容器运行时配置用于运行 Pod 中的容器。
动机
你可以在不同的 Pod 设置不同的 RuntimeClass,以提供性能与安全性之间的平衡。 例如,如果你的部分工作负载需要高级别的信息安全保证,你可以决定在调度这些 Pod 时尽量使它们在使用硬件虚拟化的容器运行时中运行。 这样,你将从这些不同运行时所提供的额外隔离中获益,代价是一些额外的开销。
你还可以使用 RuntimeClass 运行具有相同容器运行时但具有不同设置的 Pod。
设置
- 在节点上配置 CRI 的实现(取决于所选用的运行时)
- 创建相应的 RuntimeClass 资源
1. 在节点上配置 CRI 实现
RuntimeClass 的配置依赖于运行时接口(CRI)的实现。 根据你使用的 CRI 实现,查阅相关的文档(下方)来了解如何配置。
说明:
RuntimeClass 假设集群中的节点配置是同构的(换言之,所有的节点在容器运行时方面的配置是相同的)。 如果需要支持异构节点,配置方法请参阅下面的调度。
所有这些配置都具有相应的 handler
名,并被 RuntimeClass 引用。
handler 必须是有效的 DNS 标签名。
2. 创建相应的 RuntimeClass 资源
在上面步骤 1 中,每个配置都需要有一个用于标识配置的 handler
。
针对每个 handler 需要创建一个 RuntimeClass 对象。
RuntimeClass 资源当前只有两个重要的字段:RuntimeClass 名 (metadata.name
) 和 handler (handler
)。
对象定义如下所示:
# RuntimeClass 定义于 node.k8s.io API 组
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
# 用来引用 RuntimeClass 的名字
# RuntimeClass 是一个集群层面的资源
name: myclass
# 对应的 CRI 配置的名称
handler: myconfiguration
RuntimeClass 对象的名称必须是有效的 DNS 子域名。
说明:
建议将 RuntimeClass 写操作(create、update、patch 和 delete)限定于集群管理员使用。 通常这是默认配置。参阅授权概述了解更多信息。
使用说明
一旦完成集群中 RuntimeClasses 的配置,
你可以在 Pod spec 中指定 runtimeClassName
来使用它。例如:
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
runtimeClassName: myclass
# ...
这一设置会告诉 kubelet 使用所指的 RuntimeClass 来运行该 Pod。
如果所指的 RuntimeClass 不存在或者 CRI 无法运行相应的 handler,
那么 Pod 将会进入 Failed
终止阶段。
你可以查看相应的事件,
获取执行过程中的错误信息。
如果未指定 runtimeClassName
,则将使用默认的 RuntimeHandler,相当于禁用 RuntimeClass 功能特性。
CRI 配置
关于如何安装 CRI 运行时,请查阅 CRI 安装。
containerd
通过 containerd 的 /etc/containerd/config.toml
配置文件来配置运行时 handler。
handler 需要配置在 runtimes 块中:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.${HANDLER_NAME}]
更详细信息,请查阅 containerd 的配置指南
CRI-O
通过 CRI-O 的 /etc/crio/crio.conf
配置文件来配置运行时 handler。
handler 需要配置在
crio.runtime 表之下:
[crio.runtime.runtimes.${HANDLER_NAME}]
runtime_path = "${PATH_TO_BINARY}"
更详细信息,请查阅 CRI-O 配置文档。
调度
Kubernetes v1.16 [beta]
通过为 RuntimeClass 指定 scheduling
字段,
你可以通过设置约束,确保运行该 RuntimeClass 的 Pod 被调度到支持该 RuntimeClass 的节点上。
如果未设置 scheduling
,则假定所有节点均支持此 RuntimeClass。
为了确保 pod 会被调度到支持指定运行时的 node 上,每个 node 需要设置一个通用的 label 用于被
runtimeclass.scheduling.nodeSelector
挑选。在 admission 阶段,RuntimeClass 的 nodeSelector 将会与
Pod 的 nodeSelector 合并,取二者的交集。如果有冲突,Pod 将会被拒绝。
如果 node 需要阻止某些需要特定 RuntimeClass 的 Pod,可以在 tolerations
中指定。
与 nodeSelector
一样,tolerations 也在 admission 阶段与 Pod 的 tolerations 合并,取二者的并集。
更多有关 node selector 和 tolerations 的配置信息,请查阅 将 Pod 分派到节点。
Pod 开销
Kubernetes v1.24 [stable]
你可以指定与运行 Pod 相关的开销资源。声明开销即允许集群(包括调度器)在决策 Pod 和资源时将其考虑在内。
Pod 开销通过 RuntimeClass 的 overhead
字段定义。
通过使用这个字段,你可以指定使用该 RuntimeClass 运行 Pod 时的开销并确保 Kubernetes 将这些开销计算在内。
接下来
3.3.4 - 容器生命周期回调
这个页面描述了 kubelet 管理的容器如何使用容器生命周期回调框架, 藉由其管理生命周期中的事件触发,运行指定代码。
概述
类似于许多具有生命周期回调组件的编程语言框架,例如 Angular、Kubernetes 为容器提供了生命周期回调。 回调使容器能够了解其管理生命周期中的事件,并在执行相应的生命周期回调时运行在处理程序中实现的代码。
容器回调
有两个回调暴露给容器:
PostStart
这个回调在容器被创建之后立即被执行。 但是,不能保证回调会在容器入口点(ENTRYPOINT)之前执行。 没有参数传递给处理程序。
PreStop
在容器因 API 请求或者管理事件(诸如存活态探针、启动探针失败、资源抢占、资源竞争等)
而被终止之前,此回调会被调用。
如果容器已经处于已终止或者已完成状态,则对 preStop 回调的调用将失败。
在用来停止容器的 TERM 信号被发出之前,回调必须执行结束。
Pod 的终止宽限周期在 PreStop
回调被执行之前即开始计数,
所以无论回调函数的执行结果如何,容器最终都会在 Pod 的终止宽限期内被终止。
没有参数会被传递给处理程序。
有关终止行为的更详细描述, 请参见终止 Pod。
回调处理程序的实现
容器可以通过实现和注册该回调的处理程序来访问该回调。 针对容器,有三种类型的回调处理程序可供实现:
- Exec - 在容器的 cgroups 和名字空间中执行特定的命令(例如
pre-stop.sh
)。 命令所消耗的资源计入容器的资源消耗。 - HTTP - 对容器上的特定端点执行 HTTP 请求。
- Sleep - 将容器暂停一段指定的时间。
这是由
PodLifecycleSleepAction
特性门控默认启用的 Beta 级特性。
回调处理程序执行
当调用容器生命周期管理回调时,Kubernetes 管理系统根据回调动作执行其处理程序,
httpGet
、tcpSocket
和 sleep
由 kubelet 进程执行,而 exec
在容器中执行。
当容器创建时,会调用 PostStart
回调程序,
这意味着容器的 ENTRYPOINT 和 PostStart
回调会同时触发。然而,
如果 PostStart
回调程序执行时间过长或挂起,它可能会阻止容器进入 running
状态。
PreStop
回调并不会与停止容器的信号处理程序异步执行;回调必须在可以发送信号之前完成执行。
如果 PreStop
回调在执行期间停滞不前,Pod 的阶段会变成 Terminating
并且一直处于该状态,
直到其 terminationGracePeriodSeconds
耗尽为止,这时 Pod 会被杀死。
这一宽限期是针对 PreStop
回调的执行时间及容器正常停止时间的总和而言的。
例如,如果 terminationGracePeriodSeconds
是 60,回调函数花了 55 秒钟完成执行,
而容器在收到信号之后花了 10 秒钟来正常结束,那么容器会在其能够正常结束之前即被杀死,
因为 terminationGracePeriodSeconds
的值小于后面两件事情所花费的总时间(55+10)。
如果 PostStart
或 PreStop
回调失败,它会杀死容器。
用户应该使他们的回调处理程序尽可能的轻量级。 但也需要考虑长时间运行的命令也很有用的情况,比如在停止容器之前保存状态。
回调递送保证
回调的递送应该是至少一次,这意味着对于任何给定的事件,
例如 PostStart
或 PreStop
,回调可以被调用多次。
如何正确处理被多次调用的情况,是回调实现所要考虑的问题。
通常情况下,只会进行单次递送。 例如,如果 HTTP 回调接收器宕机,无法接收流量,则不会尝试重新发送。 然而,偶尔也会发生重复递送的可能。 例如,如果 kubelet 在发送回调的过程中重新启动,回调可能会在 kubelet 恢复后重新发送。
调试回调处理程序
回调处理程序的日志不会在 Pod 事件中公开。
如果处理程序由于某种原因失败,它将播放一个事件。
对于 PostStart
,这是 FailedPostStartHook
事件,对于 PreStop
,这是 FailedPreStopHook
事件。
要自己生成失败的 FailedPostStartHook
事件,请修改
lifecycle-events.yaml
文件将 postStart 命令更改为 “badcommand” 并应用它。
以下是通过运行 kubectl describe pod lifecycle-demo
后你看到的一些结果事件的示例输出:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 7s default-scheduler Successfully assigned default/lifecycle-demo to ip-XXX-XXX-XX-XX.us-east-2...
Normal Pulled 6s kubelet Successfully pulled image "nginx" in 229.604315ms
Normal Pulling 4s (x2 over 6s) kubelet Pulling image "nginx"
Normal Created 4s (x2 over 5s) kubelet Created container lifecycle-demo-container
Normal Started 4s (x2 over 5s) kubelet Started container lifecycle-demo-container
Warning FailedPostStartHook 4s (x2 over 5s) kubelet Exec lifecycle hook ([badcommand]) for Container "lifecycle-demo-container" in Pod "lifecycle-demo_default(30229739-9651-4e5a-9a32-a8f1688862db)" failed - error: command 'badcommand' exited with 126: , message: "OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: \"badcommand\": executable file not found in $PATH: unknown\r\n"
Normal Killing 4s (x2 over 5s) kubelet FailedPostStartHook
Normal Pulled 4s kubelet Successfully pulled image "nginx" in 215.66395ms
Warning BackOff 2s (x2 over 3s) kubelet Back-off restarting failed container
接下来
- 进一步了解容器环境。
- 动手为容器的生命周期事件设置处理函数。
3.4 - 工作负载
在 Kubernetes 中,无论你的负载是由单个组件还是由多个一同工作的组件构成, 你都可以在一组 Pod 中运行它。 在 Kubernetes 中,Pod 代表的是集群上处于运行状态的一组 容器的集合。
Kubernetes Pod 遵循预定义的生命周期。 例如,当在你的集群中运行了某个 Pod,但是 Pod 所在的 节点 出现致命错误时, 所有该节点上的 Pod 的状态都会变成失败。Kubernetes 将这类失败视为最终状态: 即使该节点后来恢复正常运行,你也需要创建新的 Pod 以恢复应用。
不过,为了减轻用户的使用负担,通常不需要用户直接管理每个 Pod
。
而是使用负载资源来替用户管理一组 Pod。
这些负载资源通过配置 控制器
来确保正确类型的、处于运行状态的 Pod 个数是正确的,与用户所指定的状态相一致。
Kubernetes 提供若干种内置的工作负载资源:
- Deployment 和 ReplicaSet (替换原来的资源 ReplicationController)。 Deployment 很适合用来管理你的集群上的无状态应用,Deployment 中的所有 Pod 都是相互等价的,并且在需要的时候被替换。
- StatefulSet 让你能够运行一个或者多个以某种方式跟踪应用状态的 Pod。 例如,如果你的负载会将数据作持久存储,你可以运行一个 StatefulSet,将每个 Pod 与某个 PersistentVolume 对应起来。你在 StatefulSet 中各个 Pod 内运行的代码可以将数据复制到同一 StatefulSet 中的其它 Pod 中以提高整体的服务可靠性。
- DaemonSet
定义提供节点本地支撑设施的 Pod。这些 Pod 可能对于你的集群的运维是
非常重要的,例如作为网络链接的辅助工具或者作为网络
插件
的一部分等等。每次你向集群中添加一个新节点时,如果该节点与某
DaemonSet
的规约匹配,则控制平面会为该 DaemonSet 调度一个 Pod 到该新节点上运行。 - Job 和 CronJob。 定义一些一直运行到结束并停止的任务。 你可以使用 Job 来定义只需要执行一次并且执行后即视为完成的任务。你可以使用 CronJob 来根据某个排期表来多次运行同一个 Job。
在庞大的 Kubernetes 生态系统中,你还可以找到一些提供额外操作的第三方工作负载相关的资源。 通过使用定制资源定义(CRD), 你可以添加第三方工作负载资源,以完成原本不是 Kubernetes 核心功能的工作。 例如,如果你希望运行一组 Pod,但要求所有 Pod 都可用时才执行操作 (比如针对某种高吞吐量的分布式任务),你可以基于定制资源实现一个能够满足这一需求的扩展, 并将其安装到集群中运行。
接下来
除了阅读了解每类资源外,你还可以了解与这些资源相关的任务:
- 使用 Deployment 运行一个无状态的应用
- 以单实例或者多副本集合 的形式运行有状态的应用;
- 使用 CronJob 运行自动化的任务
要了解 Kubernetes 将代码与配置分离的实现机制,可参阅配置节。
关于 Kubernetes 如何为应用管理 Pod,还有两个支撑概念能够提供相关背景信息:
- 垃圾收集机制负责在 对象的属主资源被删除时在集群中清理这些对象。
- Time-to-Live 控制器会在 Job 结束之后的指定时间间隔之后删除它们。
一旦你的应用处于运行状态,你就可能想要以 Service 的形式使之可在互联网上访问;或者对于 Web 应用而言,使用 Ingress 资源将其暴露到互联网上。
3.4.1 - Pod
Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。
Pod(就像在鲸鱼荚或者豌豆荚中)是一组(一个或多个) 容器; 这些容器共享存储、网络、以及怎样运行这些容器的规约。 Pod 中的内容总是并置(colocated)的并且一同调度,在共享的上下文中运行。 Pod 所建模的是特定于应用的 “逻辑主机”,其中包含一个或多个应用容器, 这些容器相对紧密地耦合在一起。 在非云环境中,在相同的物理机或虚拟机上运行的应用类似于在同一逻辑主机上运行的云应用。
除了应用容器,Pod 还可以包含在 Pod 启动期间运行的 Init 容器。 你也可以注入临时性容器来调试正在运行的 Pod。
什么是 Pod?
说明:
为了运行 Pod,你需要提前在每个节点安装好容器运行时。
Pod 的共享上下文包括一组 Linux 名字空间、控制组(cgroup)和可能一些其他的隔离方面, 即用来隔离容器的技术。 在 Pod 的上下文中,每个独立的应用可能会进一步实施隔离。
Pod 类似于共享名字空间并共享文件系统卷的一组容器。
Kubernetes 集群中的 Pod 主要有两种用法:
运行单个容器的 Pod。"每个 Pod 一个容器"模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器,并且 Kubernetes 直接管理 Pod,而不是容器。
运行多个协同工作的容器的 Pod。 Pod 可以封装由紧密耦合且需要共享资源的多个并置容器组成的应用。 这些位于同一位置的容器构成一个内聚单元。
将多个并置、同管的容器组织到一个 Pod 中是一种相对高级的使用场景。 只有在一些场景中,容器之间紧密关联时你才应该使用这种模式。
你不需要运行多个容器来扩展副本(为了弹性或容量); 如果你需要多个副本,请参阅工作负载管理。
使用 Pod
下面是一个 Pod 示例,它由一个运行镜像 nginx:1.14.2
的容器组成。
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
要创建上面显示的 Pod,请运行以下命令:
kubectl apply -f https://k8s.io/examples/pods/simple-pod.yaml
Pod 通常不是直接创建的,而是使用工作负载资源创建的。 有关如何将 Pod 用于工作负载资源的更多信息,请参阅使用 Pod。
用于管理 Pod 的工作负载资源
通常你不需要直接创建 Pod,甚至单实例 Pod。相反,你会使用诸如 Deployment 或 Job 这类工作负载资源来创建 Pod。 如果 Pod 需要跟踪状态,可以考虑 StatefulSet 资源。
每个 Pod 都旨在运行给定应用程序的单个实例。如果希望横向扩展应用程序 (例如,运行多个实例以提供更多的资源),则应该使用多个 Pod,每个实例使用一个 Pod。 在 Kubernetes 中,这通常被称为副本(Replication)。 通常使用一种工作负载资源及其控制器来创建和管理一组 Pod 副本。
参见 Pod 和控制器以了解 Kubernetes 如何使用工作负载资源及其控制器以实现应用的扩缩和自动修复。
使用 Pod
你很少在 Kubernetes 中直接创建一个个的 Pod,甚至是单实例(Singleton)的 Pod。 这是因为 Pod 被设计成了相对临时性的、用后即抛的一次性实体。 当 Pod 由你或者间接地由控制器 创建时,它被调度在集群中的节点上运行。 Pod 会保持在该节点上运行,直到 Pod 结束执行、Pod 对象被删除、Pod 因资源不足而被驱逐或者节点失效为止。
说明:
重启 Pod 中的容器不应与重启 Pod 混淆。 Pod 不是进程,而是容器运行的环境。 在被删除之前,Pod 会一直存在。
Pod 的名称必须是一个合法的 DNS 子域值, 但这可能对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的 DNS 标签规则。
Pod 操作系统
Kubernetes v1.25 [stable]
你应该将 .spec.os.name
字段设置为 windows
或 linux
以表示你希望 Pod 运行在哪个操作系统之上。
这两个是 Kubernetes 目前支持的操作系统。将来,这个列表可能会被扩充。
在 Kubernetes v1.31 中,.spec.os.name
的值对
kube-scheduler
如何选择要运行 Pod 的节点没有影响。在任何有多种操作系统运行节点的集群中,你应该在每个节点上正确设置
kubernetes.io/os
标签,并根据操作系统标签为 Pod 设置 nodeSelector
字段。
kube-scheduler 将根据其他标准将你的 Pod 分配到节点,
并且可能会也可能不会成功选择合适的节点位置,其中节点操作系统适合该 Pod 中的容器。
Pod 安全标准也使用这个字段来避免强制执行与该操作系统无关的策略。
Pod 和控制器
你可以使用工作负载资源来创建和管理多个 Pod。 资源的控制器能够处理副本的管理、上线,并在 Pod 失效时提供自愈能力。 例如,如果一个节点失败,控制器注意到该节点上的 Pod 已经停止工作, 就可以创建替换性的 Pod。调度器会将替身 Pod 调度到一个健康的节点执行。
下面是一些管理一个或者多个 Pod 的工作负载资源的示例:
Pod 模板
工作负载资源的控制器通常使用 Pod 模板(Pod Template) 来替你创建 Pod 并管理它们。
Pod 模板是包含在工作负载对象中的规范,用来创建 Pod。这类负载资源包括 Deployment、 Job 和 DaemonSet 等。
工作负载的控制器会使用负载对象中的 PodTemplate
来生成实际的 Pod。
PodTemplate
是你用来运行应用时指定的负载资源的目标状态的一部分。
创建 Pod 时,你可以在 Pod 模板中包含 Pod 中运行的容器的环境变量。
下面的示例是一个简单的 Job 的清单,其中的 template
指示启动一个容器。
该 Pod 中的容器会打印一条消息之后暂停。
apiVersion: batch/v1
kind: Job
metadata:
name: hello
spec:
template:
# 这里是 Pod 模板
spec:
containers:
- name: hello
image: busybox:1.28
command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
restartPolicy: OnFailure
# 以上为 Pod 模板
修改 Pod 模板或者切换到新的 Pod 模板都不会对已经存在的 Pod 直接起作用。 如果改变工作负载资源的 Pod 模板,工作负载资源需要使用更新后的模板来创建 Pod, 并使用新创建的 Pod 替换旧的 Pod。
例如,StatefulSet 控制器针对每个 StatefulSet 对象确保运行中的 Pod 与当前的 Pod 模板匹配。如果编辑 StatefulSet 以更改其 Pod 模板, StatefulSet 将开始基于更新后的模板创建新的 Pod。
每个工作负载资源都实现了自己的规则,用来处理对 Pod 模板的更新。 如果你想了解更多关于 StatefulSet 的具体信息, 请阅读 StatefulSet 基础教程中的更新策略。
在节点上,kubelet 并不直接监测或管理与 Pod 模板相关的细节或模板的更新,这些细节都被抽象出来。 这种抽象和关注点分离简化了整个系统的语义, 并且使得用户可以在不改变现有代码的前提下就能扩展集群的行为。
Pod 更新与替换
正如前面章节所述,当某工作负载的 Pod 模板被改变时, 控制器会基于更新的模板创建新的 Pod 对象而不是对现有 Pod 执行更新或者修补操作。
Kubernetes 并不禁止你直接管理 Pod。对运行中的 Pod 的某些字段执行就地更新操作还是可能的。不过,类似
patch
和
replace
这类更新操作有一些限制:
Pod 的绝大多数元数据都是不可变的。例如,你不可以改变其
namespace
、name
、uid
或者creationTimestamp
字段;generation
字段是比较特别的, 如果更新该字段,只能增加字段取值而不能减少。如果
metadata.deletionTimestamp
已经被设置,则不可以向metadata.finalizers
列表中添加新的条目。Pod 更新不可以改变除
spec.containers[*].image
、spec.initContainers[*].image
、spec.activeDeadlineSeconds
或spec.tolerations
之外的字段。 对于spec.tolerations
,你只被允许添加新的条目到其中。在更新
spec.activeDeadlineSeconds
字段时,以下两种更新操作是被允许的:- 如果该字段尚未设置,可以将其设置为一个正数;
- 如果该字段已经设置为一个正数,可以将其设置为一个更小的、非负的整数。
资源共享和通信
Pod 使它的成员容器间能够进行数据共享和通信。
Pod 中的存储
一个 Pod 可以设置一组共享的存储卷。 Pod 中的所有容器都可以访问该共享卷,从而允许这些容器共享数据。 卷还允许 Pod 中的持久数据保留下来,即使其中的容器需要重新启动。 有关 Kubernetes 如何在 Pod 中实现共享存储并将其提供给 Pod 的更多信息, 请参考存储。
Pod 联网
每个 Pod 都在每个地址族中获得一个唯一的 IP 地址。
Pod 中的每个容器共享网络名字空间,包括 IP 地址和网络端口。
Pod 内的容器可以使用 localhost
互相通信。
当 Pod 中的容器与 Pod 之外的实体通信时,它们必须协调如何使用共享的网络资源(例如端口)。
在同一个 Pod 内,所有容器共享一个 IP 地址和端口空间,并且可以通过 localhost
发现对方。
他们也能通过如 SystemV 信号量或 POSIX 共享内存这类标准的进程间通信方式互相通信。
不同 Pod 中的容器的 IP 地址互不相同,如果没有特殊配置,就无法通过 OS 级 IPC 进行通信。
如果某容器希望与运行于其他 Pod 中的容器通信,可以通过 IP 联网的方式实现。
Pod 中的容器所看到的系统主机名与为 Pod 配置的 name
属性值相同。
网络部分提供了更多有关此内容的信息。
Pod 安全设置
要对 Pod 和容器设置安全约束,请使用 Pod 规约中的 securityContext
字段。
该字段使你可以精细控制 Pod 或单个容器可以执行的操作。例如:
- 放弃特定的 Linux 权能(Capability)以避免受到某 CVE 的影响。
- 强制 Pod 中的所有进程以非 root 用户或特定用户或组 ID 的身份运行。
- 设置特定的 seccomp 配置文件。
- 设置 Windows 安全选项,例如容器是否作为 HostProcess 运行。
注意:
你还可以使用 Pod securityContext 在 Linux 容器中启用特权模式。
特权模式会覆盖 securityContext 中的许多其他安全设置。
请避免使用此设置,除非你无法通过使用 securityContext 中的其他字段授予等效权限。
在 Kubernetes 1.26 及更高版本中,你可以通过在 Pod 规约的安全上下文中设置
windowsOptions.hostProcess
标志,以类似的特权模式运行 Windows 容器。
有关详细信息和说明,请参阅创建 Windows HostProcess Pod。
- 要了解可以使用的内核级安全约束,请参阅 Pod 和容器的 Linux 内核安全约束。
- 要了解有关 Pod 安全上下文的更多信息,请参阅为 Pod 或容器配置安全上下文。
静态 Pod
静态 Pod(Static Pod) 直接由特定节点上的 kubelet
守护进程管理,
不需要 API 服务器看到它们。
尽管大多数 Pod 都是通过控制面(例如,Deployment)
来管理的,对于静态 Pod 而言,kubelet
直接监控每个 Pod,并在其失效时重启之。
静态 Pod 通常绑定到某个节点上的 kubelet。
其主要用途是运行自托管的控制面。
在自托管场景中,使用 kubelet
来管理各个独立的控制面组件。
kubelet
自动尝试为每个静态 Pod 在 Kubernetes API
服务器上创建一个镜像 Pod。
这意味着在节点上运行的 Pod 在 API 服务器上是可见的,但不可以通过 API 服务器来控制。
有关更多信息,请参阅创建静态 Pod 的指南。
说明:
静态 Pod 的 spec
不能引用其他的 API 对象(例如:
ServiceAccount、
ConfigMap、
Secret 等)。
Pod 管理多个容器
Pod 被设计成支持构造内聚的服务单元的多个协作进程(形式为容器)。 Pod 中的容器被自动并置到集群中的同一物理机或虚拟机上,并可以一起进行调度。 容器之间可以共享资源和依赖、彼此通信、协调何时以及何种方式终止自身。
Kubernetes 集群中的 Pod 主要有两种用法:
- 运行单个容器的 Pod。"每个 Pod 一个容器" 模型是最常见的 Kubernetes 用例; 在这种情况下,可以将 Pod 看作单个容器的包装器。Kubernetes 直接管理 Pod,而不是容器。
- 运行多个需要协同工作的容器的 Pod。 Pod 可以封装由多个紧密耦合且需要共享资源的并置容器组成的应用。 这些位于同一位置的容器可能形成单个内聚的服务单元 —— 一个容器将文件从共享卷提供给公众, 而另一个单独的边车容器则刷新或更新这些文件。 Pod 将这些容器和存储资源打包为一个可管理的实体。
例如,你可能有一个容器,为共享卷中的文件提供 Web 服务器支持,以及一个单独的 边车(Sidercar) 容器负责从远端更新这些文件,如下图所示:
有些 Pod 具有 Init 容器和 应用容器。 Init 容器默认会在启动应用容器之前运行并完成。
你还可以拥有为主应用 Pod 提供辅助服务的 边车容器(例如:服务网格)。
Kubernetes v1.29 [beta]
启用 SidecarContainers
特性门控(默认启用)允许你为
Init 容器指定 restartPolicy: Always
。设置重启策略为 Always
会确保设置的 Init 容器被视为边车,
并在 Pod 的整个生命周期内保持运行。
更多细节参阅边车容器和重启策略
容器探针
Probe 是由 kubelet 对容器执行的定期诊断。要执行诊断,kubelet 可以执行三种动作:
ExecAction
(借助容器运行时执行)TCPSocketAction
(由 kubelet 直接检测)HTTPGetAction
(由 kubelet 直接检测)
你可以参阅 Pod 的生命周期文档中的探针部分。
接下来
- 了解 Pod 生命周期。
- 了解 RuntimeClass, 以及如何使用它来配置不同的 Pod 使用不同的容器运行时配置。
- 了解 PodDisruptionBudget, 以及你可以如何利用它在出现干扰因素时管理应用的可用性。
- Pod 在 Kubernetes REST API 中是一个顶层资源。 Pod 对象的定义中包含了更多的细节信息。
- 博客分布式系统工具箱:复合容器模式中解释了在同一 Pod 中包含多个容器时的几种常见布局。
- 了解 Pod 拓扑分布约束。
要了解为什么 Kubernetes 会在其他资源 (如 StatefulSet 或 Deployment) 封装通用的 Pod API,相关的背景信息可以在前人的研究中找到。具体包括:
3.4.1.1 - Pod 的生命周期
本页面讲述 Pod 的生命周期。
Pod 遵循预定义的生命周期,起始于 Pending
阶段,
如果至少其中有一个主要容器正常启动,则进入 Running
,之后取决于 Pod
中是否有容器以失败状态结束而进入 Succeeded
或者 Failed
阶段。
和一个个独立的应用容器一样,Pod 也被认为是相对临时性(而不是长期存在)的实体。 Pod 会被创建、赋予一个唯一的 ID(UID), 并被调度到节点,并在终止(根据重启策略)或删除之前一直运行在该节点。 如果一个节点死掉了,调度到该节点的 Pod 也被计划在给定超时期限结束后删除。
Pod 生命期
在 Pod 运行期间,kubelet
能够重启容器以处理一些失效场景。
在 Pod 内部,Kubernetes 跟踪不同容器的状态并确定使
Pod 重新变得健康所需要采取的动作。
在 Kubernetes API 中,Pod 包含规约部分和实际状态部分。 Pod 对象的状态包含了一组 Pod 状况(Conditions)。 如果应用需要的话,你也可以向其中注入自定义的就绪态信息。
Pod 在其生命周期中只会被调度一次。 将 Pod 分配到特定节点的过程称为绑定,而选择使用哪个节点的过程称为调度。 一旦 Pod 被调度并绑定到某个节点,Kubernetes 会尝试在该节点上运行 Pod。 Pod 会在该节点上运行,直到 Pod 停止或者被终止; 如果 Kubernetes 无法在选定的节点上启动 Pod(例如,如果节点在 Pod 启动前崩溃), 那么特定的 Pod 将永远不会启动。
你可以使用 Pod 调度就绪态来延迟 Pod 的调度,直到所有的调度门控都被移除。 例如,你可能想要定义一组 Pod,但只有在所有 Pod 都被创建完成后才会触发调度。
Pod 和故障恢复
如果 Pod 中的某个容器失败,Kubernetes 可能会尝试重启特定的容器。 有关细节参阅 Pod 如何处理容器问题。
然而,Pod 也可能以集群无法恢复的方式失败,在这种情况下,Kubernetes 不会进一步尝试修复 Pod; 相反,Kubernetes 会删除 Pod 并依赖其他组件提供自动修复。
如果 Pod 被调度到某个节点而该节点之后失效, Pod 会被视为不健康,最终 Kubernetes 会删除 Pod。 Pod 无法在因节点资源耗尽或者节点维护而被驱逐期间继续存活。
Kubernetes 使用一种高级抽象来管理这些相对而言可随时丢弃的 Pod 实例, 称作控制器。
任何给定的 Pod (由 UID 定义)从不会被“重新调度(rescheduled)”到不同的节点;
相反,这一 Pod 可以被一个新的、几乎完全相同的 Pod 替换掉。
如果你创建一个替换 Pod,它甚至可以拥有与旧 Pod 相同的名称(如 .metadata.name
),
但替换 Pod 将具有与旧 Pod 不同的 .metadata.uid
。
Kubernetes 不保证现有 Pod 的替换 Pod 会被调度到与被替换的旧 Pod 相同的节点。
关联的生命期
如果某物声称其生命期与某 Pod 相同,例如存储卷, 这就意味着该对象在此 Pod (UID 亦相同)存在期间也一直存在。 如果 Pod 因为任何原因被删除,甚至某完全相同的替代 Pod 被创建时, 这个相关的对象(例如这里的卷)也会被删除并重建。
Pod 阶段
Pod 的 status
字段是一个
PodStatus
对象,其中包含一个 phase
字段。
Pod 的阶段(Phase)是 Pod 在其生命周期中所处位置的简单宏观概述。 该阶段并不是对容器或 Pod 状态的综合汇总,也不是为了成为完整的状态机。
Pod 阶段的数量和含义是严格定义的。
除了本文档中列举的内容外,不应该再假定 Pod 有其他的 phase
值。
下面是 phase
可能的值:
取值 | 描述 |
---|---|
Pending (悬决) | Pod 已被 Kubernetes 系统接受,但有一个或者多个容器尚未创建亦未运行。此阶段包括等待 Pod 被调度的时间和通过网络下载镜像的时间。 |
Running (运行中) | Pod 已经绑定到了某个节点,Pod 中所有的容器都已被创建。至少有一个容器仍在运行,或者正处于启动或重启状态。 |
Succeeded (成功) | Pod 中的所有容器都已成功终止,并且不会再重启。 |
Failed (失败) | Pod 中的所有容器都已终止,并且至少有一个容器是因为失败终止。也就是说,容器以非 0 状态退出或者被系统终止,且未被设置为自动重启。 |
Unknown (未知) | 因为某些原因无法取得 Pod 的状态。这种情况通常是因为与 Pod 所在主机通信失败。 |
说明:
当 Pod 反复启动失败时,某些 kubectl 命令的 Status
字段中可能会出现 CrashLoopBackOff
。
同样,当 Pod 被删除时,某些 kubectl 命令的 Status
字段中可能会出现 Terminating
。
确保不要将 Status(kubectl 用于用户直觉的显示字段)与 Pod 的 phase
混淆。
Pod 阶段(phase)是 Kubernetes 数据模型和
Pod API
的一个明确的部分。
NAMESPACE NAME READY STATUS RESTARTS AGE
alessandras-namespace alessandras-pod 0/1 CrashLoopBackOff 200 2d9h
Pod 被赋予一个可以体面终止的期限,默认为 30 秒。
你可以使用 --force
参数来强制终止 Pod。
从 Kubernetes 1.27 开始,除了静态 Pod
和没有 Finalizer 的强制终止 Pod
之外,kubelet
会将已删除的 Pod 转换到终止阶段
(Failed
或 Succeeded
具体取决于 Pod 容器的退出状态),然后再从 API 服务器中删除。
如果某节点死掉或者与集群中其他节点失联,Kubernetes
会实施一种策略,将失去的节点上运行的所有 Pod 的 phase
设置为 Failed
。
容器状态
Kubernetes 会跟踪 Pod 中每个容器的状态,就像它跟踪 Pod 总体上的阶段一样。 你可以使用容器生命周期回调 来在容器生命周期中的特定时间点触发事件。
一旦调度器将 Pod
分派给某个节点,kubelet
就通过容器运行时开始为
Pod 创建容器。容器的状态有三种:Waiting
(等待)、Running
(运行中)和
Terminated
(已终止)。
要检查 Pod 中容器的状态,你可以使用 kubectl describe pod <pod 名称>
。
其输出中包含 Pod 中每个容器的状态。
每种状态都有特定的含义:
Waiting
(等待)
如果容器并不处在 Running
或 Terminated
状态之一,它就处在 Waiting
状态。
处于 Waiting
状态的容器仍在运行它完成启动所需要的操作:例如,
从某个容器镜像仓库拉取容器镜像,或者向容器应用 Secret
数据等等。
当你使用 kubectl
来查询包含 Waiting
状态的容器的 Pod 时,你也会看到一个
Reason 字段,其中给出了容器处于等待状态的原因。
Running
(运行中)
Running
状态表明容器正在执行状态并且没有问题发生。
如果配置了 postStart
回调,那么该回调已经执行且已完成。
如果你使用 kubectl
来查询包含 Running
状态的容器的 Pod 时,
你也会看到关于容器进入 Running
状态的信息。
Terminated
(已终止)
处于 Terminated
状态的容器开始执行后,或者运行至正常结束或者因为某些原因失败。
如果你使用 kubectl
来查询包含 Terminated
状态的容器的 Pod 时,
你会看到容器进入此状态的原因、退出代码以及容器执行期间的起止时间。
如果容器配置了 preStop
回调,则该回调会在容器进入 Terminated
状态之前执行。
Pod 如何处理容器问题
Kubernetes 通过在 Pod spec
中定义的 restartPolicy
管理 Pod 内容器出现的失效。
该策略决定了 Kubernetes 如何对由于错误或其他原因而退出的容器做出反应,其顺序如下:
- 最初的崩溃:Kubernetes 尝试根据 Pod 的
restartPolicy
立即重新启动。 - 反复的崩溃:在最初的崩溃之后,Kubernetes 对于后续重新启动的容器采用指数级回退延迟机制,
如
restartPolicy
中所述。 这一机制可以防止快速、重复的重新启动尝试导致系统过载。 - CrashLoopBackOff 状态:这一状态表明,对于一个给定的、处于崩溃循环、反复失效并重启的容器, 回退延迟机制目前正在生效。
- 回退重置:如果容器成功运行了一定时间(如 10 分钟), Kubernetes 会重置回退延迟机制,将新的崩溃视为第一次崩溃。
在实际部署中,CrashLoopBackOff
是在描述或列出 Pod 时从 kubectl
命令输出的一种状况或事件。
当 Pod 中的容器无法正常启动,并反复进入尝试与失败的循环时就会出现。
换句话说,当容器进入崩溃循环时,Kubernetes 会应用容器重启策略 中提到的指数级回退延迟机制。这种机制可以防止有问题的容器因不断进行启动失败尝试而导致系统不堪重负。
下列问题可以导致 CrashLoopBackOff
:
- 应用程序错误导致的容器退出。
- 配置错误,如环境变量不正确或配置文件丢失。
- 资源限制,容器可能没有足够的内存或 CPU 正常启动。
- 如果应用程序没有在预期时间内启动服务,健康检查就会失败。
- 容器的存活探针或者启动探针返回
失败
结果,如探针部分所述。
要调查 CrashLoopBackOff
问题的根本原因,用户可以:
- 检查日志:使用
kubectl logs <pod名称>
检查容器的日志。 这通常是诊断导致崩溃的问题的最直接方法。 - 检查事件:使用
kubectl describe pod <pod名称>
查看 Pod 的事件, 这可以提供有关配置或资源问题的提示。 - 审查配置:确保 Pod 配置正确无误,包括环境变量和挂载卷,并且所有必需的外部资源都可用。
- 检查资源限制: 确保容器被分配了足够的 CPU 和内存。有时,增加 Pod 定义中的资源可以解决问题。
- 调试应用程序:应用程序代码中可能存在错误或配置不当。 在本地或开发环境中运行此容器镜像有助于诊断应用程序的特定问题。
容器重启策略
Pod 的 spec
中包含一个 restartPolicy
字段,其可能取值包括
Always、OnFailure 和 Never。默认值是 Always。
restartPolicy
应用于 Pod
中的应用容器和常规的
Init 容器。
Sidecar 容器忽略
Pod 级别的 restartPolicy
字段:在 Kubernetes 中,Sidecar 被定义为
initContainers
内的一个条目,其容器级别的 restartPolicy
被设置为 Always
。
对于因错误而退出的 Init 容器,如果 Pod 级别 restartPolicy
为 OnFailure
或 Always
,
则 kubelet 会重新启动 Init 容器。
Always
:只要容器终止就自动重启容器。OnFailure
:只有在容器错误退出(退出状态非零)时才重新启动容器。Never
:不会自动重启已终止的容器。
当 kubelet 根据配置的重启策略处理容器重启时,仅适用于同一 Pod
内替换容器并在同一节点上运行的重启。当 Pod 中的容器退出时,kubelet
会以指数级回退延迟机制(10 秒、20 秒、40 秒......)重启容器,
上限为 300 秒(5 分钟)。一旦容器顺利执行了 10 分钟,
kubelet 就会重置该容器的重启延迟计时器。
Sidecar 容器和 Pod 生命周期中解释了
init containers
在指定 restartpolicy
字段时的行为。
Pod 状况
Pod 有一个 PodStatus 对象,其中包含一个 PodConditions 数组。Pod 可能通过也可能未通过其中的一些状况测试。 Kubelet 管理以下 PodCondition:
PodScheduled
:Pod 已经被调度到某节点;PodReadyToStartContainers
:Pod 沙箱被成功创建并且配置了网络(Beta 特性,默认启用);ContainersReady
:Pod 中所有容器都已就绪;Initialized
:所有的 Init 容器都已成功完成;Ready
:Pod 可以为请求提供服务,并且应该被添加到对应服务的负载均衡池中。
字段名称 | 描述 |
---|---|
type | Pod 状况的名称 |
status | 表明该状况是否适用,可能的取值有 "True "、"False " 或 "Unknown " |
lastProbeTime | 上次探测 Pod 状况时的时间戳 |
lastTransitionTime | Pod 上次从一种状态转换到另一种状态时的时间戳 |
reason | 机器可读的、驼峰编码(UpperCamelCase)的文字,表述上次状况变化的原因 |
message | 人类可读的消息,给出上次状态转换的详细信息 |
Pod 就绪态
Kubernetes v1.29 [beta]
你的应用可以向 PodStatus 中注入额外的反馈或者信号:Pod Readiness(Pod 就绪态)。
要使用这一特性,可以设置 Pod 规约中的 readinessGates
列表,为 kubelet
提供一组额外的状况供其评估 Pod 就绪态时使用。
就绪态门控基于 Pod 的 status.conditions
字段的当前值来做决定。
如果 Kubernetes 无法在 status.conditions
字段中找到某状况,
则该状况的状态值默认为 "False
"。
这里是一个例子:
kind: Pod
...
spec:
readinessGates:
- conditionType: "www.example.com/feature-1"
status:
conditions:
- type: Ready # 内置的 Pod 状况
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
- type: "www.example.com/feature-1" # 额外的 Pod 状况
status: "False"
lastProbeTime: null
lastTransitionTime: 2018-01-01T00:00:00Z
containerStatuses:
- containerID: docker://abcd...
ready: true
...
你所添加的 Pod 状况名称必须满足 Kubernetes 标签键名格式。
Pod 就绪态的状态
命令 kubectl patch
不支持修改对象的状态。
如果需要设置 Pod 的 status.conditions
,应用或者
Operators
需要使用 PATCH
操作。你可以使用
Kubernetes 客户端库之一来编写代码,
针对 Pod 就绪态设置定制的 Pod 状况。
对于使用定制状况的 Pod 而言,只有当下面的陈述都适用时,该 Pod 才会被评估为就绪:
- Pod 中所有容器都已就绪;
readinessGates
中的所有状况都为True
值。
当 Pod 的容器都已就绪,但至少一个定制状况没有取值或者取值为 False
,
kubelet
将 Pod 的状况设置为 ContainersReady
。
Pod 网络就绪
Kubernetes v1.25 [alpha]
说明:
在其早期开发过程中,这种状况被命名为 PodHasNetwork
。
在 Pod 被调度到某节点后,它需要被 kubelet 接受并且挂载所需的存储卷。
一旦这些阶段完成,Kubelet 将与容器运行时(使用容器运行时接口(Container Runtime Interface;CRI))
一起为 Pod 生成运行时沙箱并配置网络。如果启用了 PodReadyToStartContainersCondition
特性门控
(Kubernetes 1.31 版本中默认启用),
PodReadyToStartContainers
状况会被添加到 Pod 的 status.conditions
字段中。
当 kubelet 检测到 Pod 不具备配置了网络的运行时沙箱时,PodReadyToStartContainers
状况将被设置为 False
。以下场景中将会发生这种状况:
- 在 Pod 生命周期的早期阶段,kubelet 还没有开始使用容器运行时为 Pod 设置沙箱时。
- 在 Pod 生命周期的末期阶段,Pod 的沙箱由于以下原因被销毁时:
- 节点重启时 Pod 没有被驱逐
- 对于使用虚拟机进行隔离的容器运行时,Pod 沙箱虚拟机重启时,需要创建一个新的沙箱和全新的容器网络配置。
在运行时插件成功完成 Pod 的沙箱创建和网络配置后,
kubelet 会将 PodReadyToStartContainers
状况设置为 True
。
当 PodReadyToStartContainers
状况设置为 True
后,
Kubelet 可以开始拉取容器镜像和创建容器。
对于带有 Init 容器的 Pod,kubelet 会在 Init 容器成功完成后将 Initialized
状况设置为 True
(这发生在运行时成功创建沙箱和配置网络之后),
对于没有 Init 容器的 Pod,kubelet 会在创建沙箱和网络配置开始之前将
Initialized
状况设置为 True
。
容器探针
probe 是由 kubelet 对容器执行的定期诊断。 要执行诊断,kubelet 既可以在容器内执行代码,也可以发出一个网络请求。
检查机制
使用探针来检查容器有四种不同的方法。 每个探针都必须准确定义为这四种机制中的一种:
exec
- 在容器内执行指定命令。如果命令退出时返回码为 0 则认为诊断成功。
grpc
- 使用 gRPC 执行一个远程过程调用。 目标应该实现 gRPC 健康检查。 如果响应的状态是 "SERVING",则认为诊断成功。
httpGet
- 对容器的 IP 地址上指定端口和路径执行 HTTP
GET
请求。如果响应的状态码大于等于 200 且小于 400,则诊断被认为是成功的。 tcpSocket
- 对容器的 IP 地址上的指定端口执行 TCP 检查。如果端口打开,则诊断被认为是成功的。 如果远程系统(容器)在打开连接后立即将其关闭,这算作是健康的。
注意:
和其他机制不同,exec
探针的实现涉及每次执行时创建/复制多个进程。
因此,在集群中具有较高 pod 密度、较低的 initialDelaySeconds
和 periodSeconds
时长的时候,
配置任何使用 exec 机制的探针可能会增加节点的 CPU 负载。
这种场景下,请考虑使用其他探针机制以避免额外的开销。探测结果
每次探测都将获得以下三种结果之一:
Success
(成功)- 容器通过了诊断。
Failure
(失败)- 容器未通过诊断。
Unknown
(未知)- 诊断失败,因此不会采取任何行动。
探测类型
针对运行中的容器,kubelet
可以选择是否执行以下三种探针,以及如何针对探测结果作出反应:
livenessProbe
- 指示容器是否正在运行。如果存活态探测失败,则 kubelet 会杀死容器,
并且容器将根据其重启策略决定未来。如果容器不提供存活探针,
则默认状态为
Success
。 readinessProbe
- 指示容器是否准备好为请求提供服务。如果就绪态探测失败,
端点控制器将从与 Pod 匹配的所有服务的端点列表中删除该 Pod 的 IP 地址。
初始延迟之前的就绪态的状态值默认为
Failure
。 如果容器不提供就绪态探针,则默认状态为Success
。 startupProbe
- 指示容器中的应用是否已经启动。如果提供了启动探针,则所有其他探针都会被
禁用,直到此探针成功为止。如果启动探测失败,
kubelet
将杀死容器, 而容器依其重启策略进行重启。 如果容器没有提供启动探测,则默认状态为Success
。
如欲了解如何设置存活态、就绪态和启动探针的进一步细节, 可以参阅配置存活态、就绪态和启动探针。
何时该使用存活态探针?
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活态探针;
kubelet
将根据 Pod 的 restartPolicy
自动执行修复操作。
如果你希望容器在探测失败时被杀死并重新启动,那么请指定一个存活态探针,
并指定 restartPolicy
为 "Always
" 或 "OnFailure
"。
何时该使用就绪态探针?
如果要仅在探测成功时才开始向 Pod 发送请求流量,请指定就绪态探针。 在这种情况下,就绪态探针可能与存活态探针相同,但是规约中的就绪态探针的存在意味着 Pod 将在启动阶段不接收任何数据,并且只有在探针探测成功后才开始接收数据。
如果你希望容器能够自行进入维护状态,也可以指定一个就绪态探针, 检查某个特定于就绪态的因此不同于存活态探测的端点。
如果你的应用程序对后端服务有严格的依赖性,你可以同时实现存活态和就绪态探针。 当应用程序本身是健康的,存活态探针检测通过后,就绪态探针会额外检查每个所需的后端服务是否可用。 这可以帮助你避免将流量导向只能返回错误信息的 Pod。
如果你的容器需要在启动期间加载大型数据、配置文件或执行迁移, 你可以使用启动探针。 然而,如果你想区分已经失败的应用和仍在处理其启动数据的应用,你可能更倾向于使用就绪探针。
说明:
请注意,如果你只是想在 Pod 被删除时能够排空请求,则不一定需要使用就绪态探针; 在删除 Pod 时,Pod 会自动将自身置于未就绪状态,无论就绪态探针是否存在。 等待 Pod 中的容器停止期间,Pod 会一直处于未就绪状态。
何时该使用启动探针?
对于所包含的容器需要较长时间才能启动就绪的 Pod 而言,启动探针是有用的。 你不再需要配置一个较长的存活态探测时间间隔,只需要设置另一个独立的配置选定, 对启动期间的容器执行探测,从而允许使用远远超出存活态时间间隔所允许的时长。
如果你的容器启动时间通常超出 initialDelaySeconds + failureThreshold × periodSeconds
总值,你应该设置一个启动探测,对存活态探针所使用的同一端点执行检查。
periodSeconds
的默认值是 10 秒。你应该将其 failureThreshold
设置得足够高,
以便容器有充足的时间完成启动,并且避免更改存活态探针所使用的默认值。
这一设置有助于减少死锁状况的发生。
Pod 的终止
由于 Pod 所代表的是在集群中节点上运行的进程,当不再需要这些进程时允许其体面地终止是很重要的。
一般不应武断地使用 KILL
信号终止它们,导致这些进程没有机会完成清理操作。
设计的目标是令你能够请求删除进程,并且知道进程何时被终止,同时也能够确保删除操作终将完成。 当你请求删除某个 Pod 时,集群会记录并跟踪 Pod 的体面终止周期, 而不是直接强制地杀死 Pod。在存在强制关闭设施的前提下, kubelet 会尝试体面地终止 Pod。
通常 Pod 体面终止的过程为:kubelet 先发送一个带有体面超时限期的 TERM(又名 SIGTERM)
信号到每个容器中的主进程,将请求发送到容器运行时来尝试停止 Pod 中的容器。
停止容器的这些请求由容器运行时以异步方式处理。
这些请求的处理顺序无法被保证。许多容器运行时遵循容器镜像内定义的 STOPSIGNAL
值,
如果不同,则发送容器镜像中配置的 STOPSIGNAL,而不是 TERM 信号。
一旦超出了体面终止限期,容器运行时会向所有剩余进程发送 KILL 信号,之后
Pod 就会被从 API 服务器上移除。
如果 kubelet
或者容器运行时的管理服务在等待进程终止期间被重启,
集群会从头开始重试,赋予 Pod 完整的体面终止限期。
Pod 终止流程,如下例所示:
你使用
kubectl
工具手动删除某个特定的 Pod,而该 Pod 的体面终止限期是默认值(30 秒)。API 服务器中的 Pod 对象被更新,记录涵盖体面终止限期在内 Pod 的最终死期,超出所计算时间点则认为 Pod 已死(dead)。 如果你使用
kubectl describe
来查验你正在删除的 Pod,该 Pod 会显示为 "Terminating" (正在终止)。 在 Pod 运行所在的节点上:kubelet
一旦看到 Pod 被标记为正在终止(已经设置了体面终止限期),kubelet
即开始本地的 Pod 关闭过程。如果 Pod 中的容器之一定义了
preStop
回调,kubelet
开始在容器内运行该回调逻辑。如果超出体面终止限期时,preStop
回调逻辑仍在运行,kubelet
会请求给予该 Pod 的宽限期一次性增加 2 秒钟。如果
preStop
回调在体面期结束后仍在运行,kubelet 将请求短暂的、一次性的体面期延长 2 秒。
说明:
如果preStop
回调所需要的时间长于默认的体面终止限期,你必须修改terminationGracePeriodSeconds
属性值来使其正常工作。kubelet
接下来触发容器运行时发送 TERM 信号给每个容器中的进程 1。如果 Pod 中定义了Sidecar 容器, 则存在特殊排序。否则,Pod 中的容器会在不同的时间和任意的顺序接收 TERM 信号。如果关闭顺序很重要,考虑使用
preStop
钩子进行同步(或者切换为使用 Sidecar 容器)。
在
kubelet
启动 Pod 的体面关闭逻辑的同时,控制平面会评估是否将关闭的 Pod 从对应的 EndpointSlice(和端点)对象中移除,过滤条件是 Pod 被对应的服务以某 选择算符选定。 ReplicaSet 和其他工作负载资源不再将关闭进程中的 Pod 视为合法的、能够提供服务的副本。关闭动作很慢的 Pod 不应继续处理常规服务请求,而应开始终止并完成对打开的连接的处理。 一些应用程序不仅需要完成对打开的连接的处理,还需要更进一步的体面终止逻辑 - 比如:排空和完成会话。
任何正在终止的 Pod 所对应的端点都不会立即从 EndpointSlice 中被删除,EndpointSlice API(以及传统的 Endpoints API)会公开一个状态来指示其处于 终止状态。 正在终止的端点始终将其
ready
状态设置为false
(为了向后兼容 1.26 之前的版本), 因此负载均衡器不会将其用于常规流量。如果需要排空正被终止的 Pod 上的流量,可以将
serving
状况作为实际的就绪状态。你可以在教程 探索 Pod 及其端点的终止行为 中找到有关如何实现连接排空的更多详细信息。
kubelet 确保 Pod 被关闭和终止
超出终止宽限期限时,如果 Pod 中仍有容器在运行,kubelet 会触发强制关闭过程。 容器运行时会向 Pod 中所有容器内仍在运行的进程发送
SIGKILL
信号。kubelet
也会清理隐藏的pause
容器,如果容器运行时使用了这种容器的话。kubelet
将 Pod 转换到终止阶段(Failed
或Succeeded
,具体取决于其容器的结束状态)。kubelet 通过将宽限期设置为 0(立即删除),触发从 API 服务器强制移除 Pod 对象的操作。
API 服务器删除 Pod 的 API 对象,从任何客户端都无法再看到该对象。
强制终止 Pod
注意:
对于某些工作负载及其 Pod 而言,强制删除很可能会带来某种破坏。
默认情况下,所有的删除操作都会附有 30 秒钟的宽限期限。
kubectl delete
命令支持 --grace-period=<seconds>
选项,允许你重载默认值,
设定自己希望的期限值。
将宽限期限强制设置为 0
意味着立即从 API 服务器删除 Pod。
如果 Pod 仍然运行于某节点上,强制删除操作会触发 kubelet
立即执行清理操作。
使用 kubectl 时,你必须在设置 --grace-period=0
的同时额外设置 --force
参数才能发起强制删除请求。
执行强制删除操作时,API 服务器不再等待来自 kubelet
的、关于 Pod
已经在原来运行的节点上终止执行的确认消息。
API 服务器直接删除 Pod 对象,这样新的与之同名的 Pod 即可以被创建。
在节点侧,被设置为立即终止的 Pod 仍然会在被强行杀死之前获得一点点的宽限时间。
注意:
马上删除时不等待确认正在运行的资源已被终止。这些资源可能会无限期地继续在集群上运行。
如果你需要强制删除 StatefulSet 的 Pod, 请参阅从 StatefulSet 中删除 Pod 的任务文档。
Pod 关闭和 Sidecar 容器
如果你的 Pod 包含一个或多个 Sidecar 容器 (重启策略为 Always 的 Init 容器),kubelet 将延迟向这些 Sidecar 容器发送 TERM 信号, 直到最后一个主容器已完全终止。Sidecar 容器将按照它们在 Pod 规约中被定义的相反顺序被终止。 这样确保了 Sidecar 容器继续为 Pod 中的其他容器提供服务,直到完全不再需要为止。
这意味着主容器的慢终止也会延迟 Sidecar 容器的终止。 如果在终止过程完成之前宽限期已到,Pod 可能会进入强制终止阶段。 在这种情况下,Pod 中所有剩余的容器将在某个短宽限期内被同时终止。
同样地,如果 Pod 有一个 preStop
钩子超过了终止宽限期,可能会发生紧急终止。
总体而言,如果你以前使用 preStop
钩子来控制没有 Sidecar 的 Pod 中容器的终止顺序,
你现在可以移除这些钩子,允许 kubelet 自动管理 Sidecar 的终止。
Pod 的垃圾收集
对于已失败的 Pod 而言,对应的 API 对象仍然会保留在集群的 API 服务器上, 直到用户或者控制器进程显式地将其删除。
Pod 的垃圾收集器(PodGC)是控制平面的控制器,它会在 Pod 个数超出所配置的阈值
(根据 kube-controller-manager
的 terminated-pod-gc-threshold
设置)时删除已终止的
Pod(阶段值为 Succeeded
或 Failed
)。
这一行为会避免随着时间演进不断创建和终止 Pod 而引起的资源泄露问题。
此外,PodGC 会清理满足以下任一条件的所有 Pod:
- 孤儿 Pod - 绑定到不再存在的节点,
- 计划外终止的 Pod
- 终止过程中的 Pod,当启用
NodeOutOfServiceVolumeDetach
特性门控时, 绑定到有node.kubernetes.io/out-of-service
污点的未就绪节点。
在清理 Pod 的同时,如果它们处于非终止状态阶段,PodGC 也会将它们标记为失败。 此外,PodGC 在清理孤儿 Pod 时会添加 Pod 干扰状况。参阅 Pod 干扰状况 了解更多详情。
接下来
- 动手实践为容器生命周期时间关联处理程序。
- 动手实践配置存活态、就绪态和启动探针。
- 进一步了解容器生命周期回调。
- 进一步了解 Sidecar 容器。
- 关于 API 中定义的有关 Pod 和容器状态的详细规范信息,
可参阅 API 参考文档中 Pod 的
status
字段。
3.4.1.2 - Init 容器
本页提供了 Init 容器的概览。Init 容器是一种特殊容器,在 Pod 内的应用容器启动之前运行。Init 容器可以包括一些应用镜像中不存在的实用工具和安装脚本。
你可以在 Pod 的规约中与用来描述应用容器的 containers
数组平行的位置指定
Init 容器。
在 Kubernetes 中,边车容器 是在主应用容器之前启动并持续运行的容器。本文介绍 Init 容器:在 Pod 初始化期间完成运行的容器。
理解 Init 容器
每个 Pod 中可以包含多个容器, 应用运行在这些容器里面,同时 Pod 也可以有一个或多个先于应用容器启动的 Init 容器。
Init 容器与普通的容器非常像,除了如下两点:
- 它们总是运行到完成。
- 每个都必须在下一个启动之前成功完成。
如果 Pod 的 Init 容器失败,kubelet 会不断地重启该 Init 容器直到该容器成功为止。
然而,如果 Pod 对应的 restartPolicy
值为 "Never",并且 Pod 的 Init 容器失败,
则 Kubernetes 会将整个 Pod 状态设置为失败。
为 Pod 设置 Init 容器需要在
Pod 规约中添加 initContainers
字段,
该字段以 Container
类型对象数组的形式组织,和应用的 containers
数组同级相邻。
参阅 API 参考的容器章节了解详情。
Init 容器的状态在 status.initContainerStatuses
字段中以容器状态数组的格式返回
(类似 status.containerStatuses
字段)。
与普通容器的不同之处
Init 容器支持应用容器的全部字段和特性,包括资源限制、 数据卷和安全设置。 然而,Init 容器对资源请求和限制的处理稍有不同, 在下面容器内的资源共享节有说明。
常规的 Init 容器(即不包括边车容器)不支持 lifecycle
、livenessProbe
、readinessProbe
或
startupProbe
字段。Init 容器必须在 Pod 准备就绪之前完成运行;而边车容器在 Pod 的生命周期内继续运行,
它支持一些探针。有关边车容器的细节请参阅边车容器。
如果为一个 Pod 指定了多个 Init 容器,这些容器会按顺序逐个运行。 每个 Init 容器必须运行成功,下一个才能够运行。当所有的 Init 容器运行完成时, Kubernetes 才会为 Pod 初始化应用容器并像平常一样运行。
与边车容器的不同之处
Init 容器在主应用容器启动之前运行并完成其任务。 与边车容器不同, Init 容器不会持续与主容器一起运行。
Init 容器按顺序完成运行,等到所有 Init 容器成功完成之后,主容器才会启动。
Init 容器不支持 lifecycle
、livenessProbe
、readinessProbe
或 startupProbe
,
而边车容器支持所有这些探针以控制其生命周期。
Init 容器与主应用容器共享资源(CPU、内存、网络),但不直接与主应用容器进行交互。 不过这些容器可以使用共享卷进行数据交换。
使用 Init 容器
因为 Init 容器具有与应用容器分离的单独镜像,其启动相关代码具有如下优势:
Init 容器可以包含一些安装过程中应用容器中不存在的实用工具或个性化代码。 例如,没有必要仅为了在安装过程中使用类似
sed
、awk
、python
或dig
这样的工具而去FROM
一个镜像来生成一个新的镜像。应用镜像的创建者和部署者可以各自独立工作,而没有必要联合构建一个单独的应用镜像。
与同一 Pod 中的多个应用容器相比,Init 容器能以不同的文件系统视图运行。因此,Init 容器可以被赋予访问应用容器不能访问的 Secret 的权限。
由于 Init 容器必须在应用容器启动之前运行完成,因此 Init 容器提供了一种机制来阻塞或延迟应用容器的启动,直到满足了一组先决条件。 一旦前置条件满足,Pod 内的所有的应用容器会并行启动。
Init 容器可以安全地运行实用程序或自定义代码,而在其他方式下运行这些实用程序或自定义代码可能会降低应用容器镜像的安全性。 通过将不必要的工具分开,你可以限制应用容器镜像的被攻击范围。
示例
下面是一些如何使用 Init 容器的想法:
等待一个 Service 完成创建,通过类似如下 Shell 命令:
for i in {1..100}; do sleep 1; if nslookup myservice; then exit 0; fi; done; exit 1
注册这个 Pod 到远程服务器,通过在命令中调用 API,类似如下:
curl -X POST http://$MANAGEMENT_SERVICE_HOST:$MANAGEMENT_SERVICE_PORT/register -d 'instance=$(<POD_NAME>)&ip=$(<POD_IP>)'
在启动应用容器之前等一段时间,使用类似命令:
sleep 60
克隆 Git 仓库到卷中。
将配置值放到配置文件中,运行模板工具为主应用容器动态地生成配置文件。 例如,在配置文件中存放
POD_IP
值,并使用 Jinja 生成主应用配置文件。
使用 Init 容器的情况
下面的例子定义了一个具有 2 个 Init 容器的简单 Pod。 第一个等待 myservice
启动,
第二个等待 mydb
启动。 一旦这两个 Init 容器都启动完成,Pod 将启动 spec
节中的应用容器。
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app.kubernetes.io/name: MyApp
spec:
containers:
- name: myapp-container
image: busybox:1.28
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox:1.28
command: ['sh', '-c', "until nslookup myservice.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
- name: init-mydb
image: busybox:1.28
command: ['sh', '-c', "until nslookup mydb.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for mydb; sleep 2; done"]
你通过运行下面的命令启动 Pod:
kubectl apply -f myapp.yaml
输出类似于:
pod/myapp-pod created
使用下面的命令检查其状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 0/1 Init:0/2 0 6m
或者查看更多详细信息:
kubectl describe -f myapp.yaml
输出类似于:
Name: myapp-pod
Namespace: default
[...]
Labels: app.kubernetes.io/name=MyApp
Status: Pending
[...]
Init Containers:
init-myservice:
[...]
State: Running
[...]
init-mydb:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Containers:
myapp-container:
[...]
State: Waiting
Reason: PodInitializing
Ready: False
[...]
Events:
FirstSeen LastSeen Count From SubObjectPath Type Reason Message
--------- -------- ----- ---- ------------- -------- ------ -------
16s 16s 1 {default-scheduler } Normal Scheduled Successfully assigned myapp-pod to 172.17.4.201
16s 16s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulling pulling image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Pulled Successfully pulled image "busybox"
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Created Created container init-myservice
13s 13s 1 {kubelet 172.17.4.201} spec.initContainers{init-myservice} Normal Started Started container init-myservice
如需查看 Pod 内 Init 容器的日志,请执行:
kubectl logs myapp-pod -c init-myservice # 查看第一个 Init 容器
kubectl logs myapp-pod -c init-mydb # 查看第二个 Init 容器
在这一刻,Init 容器将会等待至发现名称为 mydb
和 myservice
的服务。
如下为创建这些 Service 的配置文件:
---
apiVersion: v1
kind: Service
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
apiVersion: v1
kind: Service
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
创建 mydb
和 myservice
服务的命令:
kubectl apply -f services.yaml
输出类似于:
service/myservice created
service/mydb created
这样你将能看到这些 Init 容器执行完毕,随后 my-app
的 Pod 进入 Running
状态:
kubectl get -f myapp.yaml
输出类似于:
NAME READY STATUS RESTARTS AGE
myapp-pod 1/1 Running 0 9m
这个简单例子应该能为你创建自己的 Init 容器提供一些启发。 接下来节提供了更详细例子的链接。
具体行为
在 Pod 启动过程中,每个 Init 容器会在网络和数据卷初始化之后按顺序启动。 kubelet 运行依据 Init 容器在 Pod 规约中的出现顺序依次运行之。
每个 Init 容器成功退出后才会启动下一个 Init 容器。
如果某容器因为容器运行时的原因无法启动,或以错误状态退出,kubelet 会根据
Pod 的 restartPolicy
策略进行重试。
然而,如果 Pod 的 restartPolicy
设置为 "Always",Init 容器失败时会使用
restartPolicy
的 "OnFailure" 策略。
在所有的 Init 容器没有成功之前,Pod 将不会变成 Ready
状态。
Init 容器的端口将不会在 Service 中进行聚集。正在初始化中的 Pod 处于 Pending
状态,
但会将状况 Initializing
设置为 false。
如果 Pod 重启,所有 Init 容器必须重新执行。
对 Init 容器规约的修改仅限于容器的 image
字段。
直接更改 Init 容器的 image
字段不会重启该 Pod 或触发其重新创建。如果该 Pod 尚未启动,则该更改可能会影响 Pod 的启动方式。
对于 Pod 模板,你通常可以更改 Init 容器的任何字段;更改的影响取决于 Pod 模板的使用位置。
因为 Init 容器可能会被重启、重试或者重新执行,所以 Init 容器的代码应该是幂等的。
特别地,向任何 emptyDir
卷写入数据的代码应该对输出文件可能已经存在做好准备。
Init 容器具有应用容器的所有字段。然而 Kubernetes 禁止使用 readinessProbe
,
因为 Init 容器不能定义不同于完成态(Completion)的就绪态(Readiness)。
Kubernetes 会在校验时强制执行此检查。
在 Pod 上使用 activeDeadlineSeconds
和在容器上使用 livenessProbe
可以避免
Init 容器一直重复失败。
activeDeadlineSeconds
时间包含了 Init 容器启动的时间。
但建议仅在团队将其应用程序部署为 Job 时才使用 activeDeadlineSeconds
,
因为 activeDeadlineSeconds
在 Init 容器结束后仍有效果。
如果你设置了 activeDeadlineSeconds
,已经在正常运行的 Pod 会被杀死。
在 Pod 中的每个应用容器和 Init 容器的名称必须唯一; 与任何其它容器共享同一个名称,会在校验时抛出错误。
容器内的资源共享
在给定的 Init、边车和应用容器执行顺序下,资源使用适用于如下规则:
- 所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为 Pod 有效初始 request/limit。 如果任何资源没有指定资源限制,这被视为最高限制。
- Pod 对资源的 有效 limit/request 是如下两者中的较大者:
- 所有应用容器对某个资源的 limit/request 之和
- 对某个资源的有效初始 limit/request
- 基于有效 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源, 这些资源在 Pod 生命周期过程中并没有被使用。
- Pod 的 有效 QoS 层,与 Init 容器和应用容器的一样。
配额和限制适用于有效 Pod 的请求和限制值。
Init 容器和 Linux cgroup
在 Linux 上,Pod 级别的 CGroup 资源分配基于 Pod 的有效请求和限制值,与调度程序相同。
Pod 重启的原因
Pod 重启会导致 Init 容器重新执行,主要有如下几个原因:
Pod 的基础设施容器 (译者注:如
pause
容器) 被重启。这种情况不多见, 必须由具备 root 权限访问节点的人员来完成。当
restartPolicy
设置为Always
,Pod 中所有容器会终止而强制重启。 由于垃圾回收机制的原因, Init 容器的完成记录将会丢失。
当 Init 容器的镜像发生改变或者 Init 容器的完成记录因为垃圾收集等原因被丢失时, Pod 不会被重启。这一行为适用于 Kubernetes v1.20 及更新版本。 如果你在使用较早版本的 Kubernetes,可查阅你所使用的版本对应的文档。
接下来
进一步了解以下内容:
- 创建包含 Init 容器的 Pod
- 调试 Init 容器
- kubelet 和 kubectl 的概述。
- 探针类型: 存活态探针、就绪态探针、启动探针。
- 边车容器。
3.4.1.3 - 边车容器
Kubernetes v1.29 [beta]
边车容器是与主应用容器在同一个 Pod 中运行的辅助容器。 这些容器通过提供额外的服务或功能(如日志记录、监控、安全性或数据同步)来增强或扩展主应用容器的功能, 而无需直接修改主应用代码。
通常,一个 Pod 中只有一个应用容器。 例如,如果你有一个需要本地 Web 服务器的 Web 应用, 则本地 Web 服务器以边车容器形式运行,而 Web 应用本身以应用容器形式运行。
Kubernetes 中的边车容器
Kubernetes 将边车容器作为 Init 容器的一个特例来实现, Pod 启动后,边车容器仍保持运行状态。 本文档使用术语"常规 Init 容器"来明确指代仅在 Pod 启动期间运行的容器。
如果你的集群启用了 SidecarContainers
特性门控
(该特性自 Kubernetes v1.29 起默认启用),你可以为 Pod 的 initContainers
字段中列出的容器指定 restartPolicy
。
这些可重新启动的边车(Sidecar) 容器独立于其他 Init 容器以及同一 Pod 内的主应用容器,
这些容器可以启动、停止和重新启动,而不会影响主应用容器和其他 Init 容器。
你还可以运行包含多个未标记为 Init 或边车容器的 Pod。
如果作为一个整体而言,某个 Pod 中的所有容器都要运行,但你不需要控制哪些容器先启动或停止,那么这种设置是合适的。
如果你使用的是不支持容器级 restartPolicy
字段的旧版本 Kubernetes,你也可以这样做。
应用示例
下面是一个包含两个容器的 Deployment 示例,其中一个容器是边车形式:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app: myapp
spec:
replicas: 1
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: alpine:latest
command: ['sh', '-c', 'while true; do echo "logging" >> /opt/logs.txt; sleep 1; done']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail -F /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
volumes:
- name: data
emptyDir: {}
边车容器和 Pod 生命周期
如果创建 Init 容器时将 restartPolicy
设置为 Always
,
则它将在整个 Pod 的生命周期内启动并持续运行。这对于运行与主应用容器分离的支持服务非常有帮助。
如果为此 Init 容器指定了 readinessProbe
,其结果将用于确定 Pod 的 ready
状态。
由于这些容器被定义为 Init 容器,所以它们享有与其他 Init 容器相同的顺序和按序执行保证, 从而允许将边车容器与常规 Init 容器混合使用,支持复杂的 Pod 初始化流程。
与常规 Init 容器相比,在 initContainers
中定义的边车容器在启动后继续运行。
当 Pod 的 .spec.initContainers
中有多个条目时,这一点非常重要。
在边车风格的 Init 容器运行后(kubelet 将该 Init 容器的 started
状态设置为 true),
kubelet 启动 .spec.initContainers
这一有序列表中的下一个 Init 容器。
该状态要么因为容器中有一个正在运行的进程且没有定义启动探针而变为 true,
要么是其 startupProbe
成功而返回的结果。
在 Pod 终止时, kubelet 会推迟终止边车容器,直到主应用容器已完全停止。边车容器随后将按照它们在 Pod 规约中出现的相反顺序被关闭。 这种方法确保了在不再需要边车服务之前这些边车继续发挥作用,以支持 Pod 内的其他容器。
带边车容器的 Job
如果你定义 Job 时使用基于 Kubernetes 风格 Init 容器的边车容器, 各个 Pod 中的边车容器不会阻止 Job 在主容器结束后进入完成状态。
以下是一个具有两个容器的 Job 示例,其中一个是边车:
apiVersion: batch/v1
kind: Job
metadata:
name: myjob
spec:
template:
spec:
containers:
- name: myjob
image: alpine:latest
command: ['sh', '-c', 'echo "logging" > /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
initContainers:
- name: logshipper
image: alpine:latest
restartPolicy: Always
command: ['sh', '-c', 'tail -F /opt/logs.txt']
volumeMounts:
- name: data
mountPath: /opt
restartPolicy: Never
volumes:
- name: data
emptyDir: {}
与应用容器的区别
边车容器与同一 Pod 中的应用容器并行运行。不过边车容器不执行主应用逻辑,而是为主应用提供支持功能。
边车容器具有独立的生命周期。它们可以独立于应用容器启动、停止和重启。 这意味着你可以更新、扩展或维护边车容器,而不影响主应用。
边车容器与主容器共享相同的网络和存储命名空间。这种共存使它们能够紧密交互并共享资源。
从 Kubernetes 的角度来看,边车容器的体面终止(Graceful Termination)相对不那么重要。
当其他容器耗尽了分配的体面终止时间后,边车容器将会比预期更快地接收到 SIGTERM
,随后是 SIGKILL
。
因此,在 Pod 终止时,边车容器退出码不为 0
(0
表示成功退出)是正常的,
通常应该被外部工具忽略。
与 Init 容器的区别
边车容器与主容器并行工作,扩展其功能并提供附加服务。
边车容器与主应用容器同时运行。它们在整个 Pod 的生命周期中都处于活动状态,并且可以独立于主容器启动和停止。 与 Init 容器不同, 边车容器支持探针来控制其生命周期。
边车容器可以直接与主应用容器交互,因为与 Init 容器一样, 它们总是与应用容器共享相同的网络,并且还可以选择共享卷(文件系统)。
Init 容器在主容器启动之前停止,因此 Init 容器无法与 Pod 中的应用容器交换消息。
所有数据传递都是单向的(例如,Init 容器可以将信息放入 emptyDir
卷中)。
容器内的资源共享
假如执行顺序为 Init 容器、边车容器和应用容器,则关于资源用量适用以下规则:
- 所有 Init 容器上定义的任何特定资源的 limit 或 request 的最大值,作为 Pod 有效初始 request/limit。 如果任何资源没有指定资源限制,则被视为最高限制。
- Pod 对资源的 有效 limit/request 是如下两者中的较大者:
- 所有应用容器对某个资源的 limit/request 之和
- Init 容器中对某个资源的有效 limit/request
- 系统基于有效的 limit/request 完成调度,这意味着 Init 容器能够为初始化过程预留资源, 而这些资源在 Pod 的生命周期中不再被使用。
- Pod 的 有效 QoS 级别,对于 Init 容器和应用容器而言是相同的。
配额和限制适用于 Pod 的有效请求和限制值。
边车容器和 Linux Cgroup
在 Linux 上,Pod Cgroup 的资源分配基于 Pod 级别的有效资源请求和限制,这一点与调度器相同。
接下来
- 了解如何采用边车容器。
- 阅读关于原生边车容器的博文。
- 阅读如何创建具有 Init 容器的 Pod。
- 了解探针类型: 存活态探针、就绪态探针、启动探针。
- 了解 Pod 开销。
3.4.1.4 - 临时容器
Kubernetes v1.25 [stable]
本页面概述了临时容器:一种特殊的容器,该容器在现有 Pod 中临时运行,以便完成用户发起的操作,例如故障排查。 你会使用临时容器来检查服务,而不是用它来构建应用程序。
了解临时容器
Pod 是 Kubernetes 应用程序的基本构建块。 由于 Pod 是一次性且可替换的,因此一旦 Pod 创建,就无法将容器加入到 Pod 中。 取而代之的是,通常使用 Deployment 以受控的方式来删除并替换 Pod。
有时有必要检查现有 Pod 的状态。例如,对于难以复现的故障进行排查。 在这些场景中,可以在现有 Pod 中运行临时容器来检查其状态并运行任意命令。
什么是临时容器?
临时容器与其他容器的不同之处在于,它们缺少对资源或执行的保证,并且永远不会自动重启,
因此不适用于构建应用程序。
临时容器使用与常规容器相同的 ContainerSpec
节来描述,但许多字段是不兼容和不允许的。
- 临时容器没有端口配置,因此像
ports
、livenessProbe
、readinessProbe
这样的字段是不允许的。 - Pod 资源分配是不可变的,因此
resources
配置是不允许的。 - 有关允许字段的完整列表,请参见 EphemeralContainer 参考文档。
临时容器是使用 API 中的一种特殊的 ephemeralcontainers
处理器进行创建的,
而不是直接添加到 pod.spec
段,因此无法使用 kubectl edit
来添加一个临时容器。
与常规容器一样,将临时容器添加到 Pod 后,将不能更改或删除临时容器。
说明:
临时容器不被静态 Pod 支持。
临时容器的用途
当由于容器崩溃或容器镜像不包含调试工具而导致 kubectl exec
无用时,
临时容器对于交互式故障排查很有用。
尤其是,Distroless 镜像
允许用户部署最小的容器镜像,从而减少攻击面并减少故障和漏洞的暴露。
由于 distroless 镜像不包含 Shell 或任何的调试工具,因此很难单独使用
kubectl exec
命令进行故障排查。
使用临时容器时, 启用进程名字空间共享很有帮助, 可以查看其他容器中的进程。
接下来
- 了解如何使用临时调试容器来进行调试
3.4.1.5 - 干扰(Disruptions)
本指南针对的是希望构建高可用性应用的应用所有者,他们有必要了解可能发生在 Pod 上的干扰类型。
文档同样适用于想要执行自动化集群操作(例如升级和自动扩展集群)的集群管理员。
自愿干扰和非自愿干扰
Pod 不会消失,除非有人(用户或控制器)将其销毁,或者出现了不可避免的硬件或软件系统错误。
我们把这些不可避免的情况称为应用的非自愿干扰(Involuntary Disruptions)。例如:
- 节点下层物理机的硬件故障
- 集群管理员错误地删除虚拟机(实例)
- 云提供商或虚拟机管理程序中的故障导致的虚拟机消失
- 内核错误
- 节点由于集群网络隔离从集群中消失
- 由于节点资源不足导致 pod 被驱逐。
除了资源不足的情况,大多数用户应该都熟悉这些情况;它们不是特定于 Kubernetes 的。
我们称其他情况为自愿干扰(Voluntary Disruptions)。 包括由应用所有者发起的操作和由集群管理员发起的操作。 典型的应用所有者的操作包括:
- 删除 Deployment 或其他管理 Pod 的控制器
- 更新了 Deployment 的 Pod 模板导致 Pod 重启
- 直接删除 Pod(例如,因为误操作)
集群管理员操作包括:
- 排空(drain)节点进行修复或升级。
- 从集群中排空节点以缩小集群(了解集群自动扩缩)。
- 从节点中移除一个 Pod,以允许其他 Pod 使用该节点。
这些操作可能由集群管理员直接执行,也可能由集群管理员所使用的自动化工具执行,或者由集群托管提供商自动执行。
咨询集群管理员或联系云提供商,或者查询发布文档,以确定是否为集群启用了任何资源干扰源。 如果没有启用,可以不用创建 Pod Disruption Budgets(Pod 干扰预算)
注意:
并非所有的自愿干扰都会受到 Pod 干扰预算的限制。 例如,删除 Deployment 或 Pod 的删除操作就会跳过 Pod 干扰预算检查。
处理干扰
以下是减轻非自愿干扰的一些方法:
- 确保 Pod 在请求中给出所需资源。
- 如果需要更高的可用性,请复制应用。 (了解有关运行多副本的无状态 和有状态应用的信息。)
- 为了在运行复制应用时获得更高的可用性,请跨机架(使用 反亲和性) 或跨区域(如果使用多区域集群)扩展应用。
自愿干扰的频率各不相同。在一个基本的 Kubernetes 集群中,没有自愿干扰(只有用户触发的干扰)。 然而,集群管理员或托管提供商可能运行一些可能导致自愿干扰的额外服务。例如,节点软 更新可能导致自愿干扰。另外,集群(节点)自动缩放的某些 实现可能导致碎片整理和紧缩节点的自愿干扰。集群 管理员或托管提供商应该已经记录了各级别的自愿干扰(如果有的话)。 有些配置选项,例如在 pod spec 中 使用 PriorityClasses 也会产生自愿(和非自愿)的干扰。
干扰预算
Kubernetes v1.21 [stable]
即使你会经常引入自愿性干扰,Kubernetes 提供的功能也能够支持你运行高度可用的应用。
作为一个应用的所有者,你可以为每个应用创建一个 PodDisruptionBudget
(PDB)。
PDB 将限制在同一时间因自愿干扰导致的多副本应用中发生宕机的 Pod 数量。
例如,基于票选机制的应用希望确保运行中的副本数永远不会低于票选所需的数量。
Web 前端可能希望确保提供负载的副本数量永远不会低于总数的某个百分比。
集群管理员和托管提供商应该使用遵循 PodDisruptionBudgets 的接口 (通过调用Eviction API), 而不是直接删除 Pod 或 Deployment。
例如,kubectl drain
命令可以用来标记某个节点即将停止服务。
运行 kubectl drain
命令时,工具会尝试驱逐你所停服的节点上的所有 Pod。
kubectl
代表你所提交的驱逐请求可能会暂时被拒绝,
所以该工具会周期性地重试所有失败的请求,
直到目标节点上的所有的 Pod 都被终止,或者达到配置的超时时间。
PDB 指定应用可以容忍的副本数量(相当于应该有多少副本)。
例如,具有 .spec.replicas: 5
的 Deployment 在任何时间都应该有 5 个 Pod。
如果 PDB 允许其在某一时刻有 4 个副本,那么驱逐 API 将允许同一时刻仅有一个(而不是两个)Pod 自愿干扰。
使用标签选择器来指定构成应用的一组 Pod,这与应用的控制器(Deployment、StatefulSet 等) 选择 Pod 的逻辑一样。
Pod 的“预期”数量由管理这些 Pod 的工作负载资源的 .spec.replicas
参数计算出来的。
控制平面通过检查 Pod 的
.metadata.ownerReferences
来发现关联的工作负载资源。
PDB 无法防止非自愿干扰; 但它们确实计入预算。
由于应用的滚动升级而被删除或不可用的 Pod 确实会计入干扰预算,
但是工作负载资源(如 Deployment 和 StatefulSet)
在进行滚动升级时不受 PDB 的限制。
应用更新期间的故障处理方式是在对应的工作负载资源的 spec
中配置的。
建议在你的 PodDisruptionBudget 中将
不健康 Pod 驱逐策略
设置为 AlwaysAllow
以支持在节点腾空期间驱逐行为不当的应用程序。
默认行为是等待应用程序 Pod 变得
健康,然后才能继续执行腾空。
当使用驱逐 API 驱逐 Pod 时,Pod 会被体面地
终止,期间会
参考 PodSpec
中的 terminationGracePeriodSeconds
配置值。
PodDisruptionBudget 例子
假设集群有 3 个节点,node-1
到 node-3
。集群上运行了一些应用。
其中一个应用有 3 个副本,分别是 pod-a
,pod-b
和 pod-c
。
另外,还有一个不带 PDB 的无关 pod pod-x
也同样显示出来。
最初,所有的 Pod 分布如下:
node-1 | node-2 | node-3 |
---|---|---|
pod-a available | pod-b available | pod-c available |
pod-x available |
3 个 Pod 都是 deployment 的一部分,并且共同拥有同一个 PDB,要求 3 个 Pod 中至少有 2 个 Pod 始终处于可用状态。
例如,假设集群管理员想要重启系统,升级内核版本来修复内核中的缺陷。
集群管理员首先使用 kubectl drain
命令尝试腾空 node-1
节点。
命令尝试驱逐 pod-a
和 pod-x
。操作立即就成功了。
两个 Pod 同时进入 terminating
状态。这时的集群处于下面的状态:
node-1 draining | node-2 | node-3 |
---|---|---|
pod-a terminating | pod-b available | pod-c available |
pod-x terminating |
Deployment 控制器观察到其中一个 Pod 正在终止,因此它创建了一个替代 Pod pod-d
。
由于 node-1
被封锁(cordon),pod-d
落在另一个节点上。
同样其他控制器也创建了 pod-y
作为 pod-x
的替代品。
(注意:对于 StatefulSet 来说,pod-a
(也称为 pod-0
)需要在替换 Pod 创建之前完全终止,
替代它的也称为 pod-0
,但是具有不同的 UID。除此之外,此示例也适用于 StatefulSet。)
当前集群的状态如下:
node-1 draining | node-2 | node-3 |
---|---|---|
pod-a terminating | pod-b available | pod-c available |
pod-x terminating | pod-d starting | pod-y |
在某一时刻,Pod 被终止,集群如下所示:
node-1 drained | node-2 | node-3 |
---|---|---|
pod-b available | pod-c available | |
pod-d starting | pod-y |
此时,如果一个急躁的集群管理员试图排空(drain)node-2
或 node-3
,drain 命令将被阻塞,
因为对于 Deployment 来说只有 2 个可用的 Pod,并且它的 PDB 至少需要 2 个。
经过一段时间,pod-d
变得可用。
集群状态如下所示:
node-1 drained | node-2 | node-3 |
---|---|---|
pod-b available | pod-c available | |
pod-d available | pod-y |
现在,集群管理员试图排空(drain)node-2
。
drain 命令将尝试按照某种顺序驱逐两个 Pod,假设先是 pod-b
,然后是 pod-d
。
命令成功驱逐 pod-b
,但是当它尝试驱逐 pod-d
时将被拒绝,因为对于
Deployment 来说只剩一个可用的 Pod 了。
Deployment 创建 pod-b
的替代 Pod pod-e
。
因为集群中没有足够的资源来调度 pod-e
,drain 命令再次阻塞。集群最终将是下面这种状态:
node-1 drained | node-2 | node-3 | no node |
---|---|---|---|
pod-b terminating | pod-c available | pod-e pending | |
pod-d available | pod-y |
此时,集群管理员需要增加一个节点到集群中以继续升级操作。
可以看到 Kubernetes 如何改变干扰发生的速率,根据:
- 应用需要多少个副本
- 优雅关闭应用实例需要多长时间
- 启动应用新实例需要多长时间
- 控制器的类型
- 集群的资源能力
Pod 干扰状况
Kubernetes v1.31 [stable]
(enabled by default: true)Pod 会被添加一个 DisruptionTarget
状况,
用来表明该 Pod 因为发生干扰而被删除。
状况中的 reason
字段进一步给出 Pod 终止的原因,如下:
PreemptionByScheduler
- Pod 将被调度器抢占, 目的是接受优先级更高的新 Pod。 要了解更多的相关信息,请参阅 Pod 优先级和抢占。
DeletionByTaintManager
- 由于 Pod 不能容忍
NoExecute
污点,Pod 将被 Taint Manager(kube-controller-manager
中节点生命周期控制器的一部分)删除; 请参阅基于污点的驱逐。
EvictionByEvictionAPI
- Pod 已被标记为通过 Kubernetes API 驱逐。
DeletionByPodGC
- 绑定到一个不再存在的 Node 上的 Pod 将被 Pod 垃圾收集删除。
在所有其他中断场景中,例如由于超出
[Pod 容器限制]而被驱逐,DisruptionTarget
状况不会被添加到 Pod 上,
因为中断可能是由 Pod 引起的,并且会在重试时再次发生。
说明:
Pod 的干扰可能会被中断。控制平面可能会重新尝试继续干扰同一个 Pod,但这没办法保证。
因此,DisruptionTarget
状况可能会被添加到 Pod 上,
但该 Pod 实际上可能不会被删除。
在这种情况下,一段时间后,Pod 干扰状况将被清除。
在清理 Pod 的同时,如果这些 Pod 处于非终止阶段, 则 Pod 垃圾回收器 (PodGC) 也会将这些 Pod 标记为失效 (另见 Pod 垃圾回收)。
使用 Job(或 CronJob)时,你可能希望将这些 Pod 干扰状况作为 Job Pod 失效策略的一部分。
分离集群所有者和应用所有者角色
通常,将集群管理者和应用所有者视为彼此了解有限的独立角色是很有用的。这种责任分离在下面这些场景下是有意义的:
- 当有许多应用团队共用一个 Kubernetes 集群,并且有自然的专业角色
- 当第三方工具或服务用于集群自动化管理
Pod 干扰预算通过在角色之间提供接口来支持这种分离。
如果你的组织中没有这样的责任分离,则可能不需要使用 Pod 干扰预算。
如何在集群上执行干扰性操作
如果你是集群管理员,并且需要对集群中的所有节点执行干扰操作,例如节点或系统软件升级,则可以使用以下选项
- 接受升级期间的停机时间。
- 故障转移到另一个完整的副本集群。
- 没有停机时间,但是对于重复的节点和人工协调成本可能是昂贵的。
- 编写可容忍干扰的应用和使用 PDB。
- 不停机。
- 最小的资源重复。
- 允许更多的集群管理自动化。
- 编写可容忍干扰的应用是棘手的,但对于支持容忍自愿干扰所做的工作,和支持自动扩缩和容忍非 自愿干扰所做工作相比,有大量的重叠
接下来
参考配置 Pod 干扰预算中的方法来保护你的应用。
进一步了解排空节点的信息。
了解更新 Deployment 的过程,包括如何在其进程中维持应用的可用性
3.4.1.6 - Pod QoS 类
本页介绍 Kubernetes 中的 服务质量(Quality of Service,QoS) 类, 阐述 Kubernetes 如何根据为 Pod 中的容器指定的资源约束为每个 Pod 设置 QoS 类。 Kubernetes 依赖这种分类来决定当 Node 上没有足够可用资源时要驱逐哪些 Pod。
QoS 类
Kubernetes 对你运行的 Pod 进行分类,并将每个 Pod 分配到特定的 QoS 类中。
Kubernetes 使用这种分类来影响不同 Pod 被处理的方式。Kubernetes 基于 Pod
中容器的资源请求进行分类,
同时确定这些请求如何与资源限制相关。
这称为服务质量 (QoS) 类。
Kubernetes 基于每个 Pod 中容器的资源请求和限制为 Pod 设置 QoS 类。Kubernetes 使用 QoS
类来决定从遇到节点压力的
Node 中驱逐哪些 Pod。可选的 QoS 类有 Guaranteed
、Burstable
和 BestEffort
。
当一个 Node 耗尽资源时,Kubernetes 将首先驱逐在该 Node 上运行的 BestEffort
Pod,
然后是 Burstable
Pod,最后是 Guaranteed
Pod。当这种驱逐是由于资源压力时,
只有超出资源请求的 Pod 才是被驱逐的候选对象。
Guaranteed
Guaranteed
Pod 具有最严格的资源限制,并且最不可能面临驱逐。
在这些 Pod 超过其自身的限制或者没有可以从 Node 抢占的低优先级 Pod 之前,
这些 Pod 保证不会被杀死。这些 Pod 不可以获得超出其指定 limit 的资源。这些 Pod 也可以使用
static
CPU 管理策略来使用独占的 CPU。
判据
Pod 被赋予 Guaranteed
QoS 类的几个判据:
- Pod 中的每个容器必须有内存 limit 和内存 request。
- 对于 Pod 中的每个容器,内存 limit 必须等于内存 request。
- Pod 中的每个容器必须有 CPU limit 和 CPU request。
- 对于 Pod 中的每个容器,CPU limit 必须等于 CPU request。
Burstable
Burstable
Pod 有一些基于 request 的资源下限保证,但不需要特定的 limit。
如果未指定 limit,则默认为其 limit 等于 Node 容量,这允许 Pod 在资源可用时灵活地增加其资源。
在由于 Node 资源压力导致 Pod 被驱逐的情况下,只有在所有 BestEffort
Pod 被驱逐后
这些 Pod 才会被驱逐。因为 Burstable
Pod 可以包括没有资源 limit 或资源 request 的容器,
所以 Burstable
Pod 可以尝试使用任意数量的节点资源。
判据
Pod 被赋予 Burstable
QoS 类的几个判据:
- Pod 不满足针对 QoS 类
Guaranteed
的判据。 - Pod 中至少一个容器有内存或 CPU 的 request 或 limit。
BestEffort
BestEffort
QoS 类中的 Pod 可以使用未专门分配给其他 QoS 类中的 Pod 的节点资源。
例如若你有一个节点有 16 核 CPU 可供 kubelet 使用,并且你将 4 核 CPU 分配给一个 Guaranteed
Pod,
那么 BestEffort
QoS 类中的 Pod 可以尝试任意使用剩余的 12 核 CPU。
如果节点遇到资源压力,kubelet 将优先驱逐 BestEffort
Pod。
判据
如果 Pod 不满足 Guaranteed
或 Burstable
的判据,则它的 QoS 类为 BestEffort
。
换言之,只有当 Pod 中的所有容器没有内存 limit 或内存 request,也没有 CPU limit 或
CPU request 时,Pod 才是 BestEffort
。Pod 中的容器可以请求(除 CPU 或内存之外的)
其他资源并且仍然被归类为 BestEffort
。
使用 cgroup v2 的内存 QoS
Kubernetes v1.22 [alpha]
(enabled by default: false)内存 QoS 使用 cgroup v2 的内存控制器来保证 Kubernetes 中的内存资源。
Pod 中容器的内存请求和限制用于设置由内存控制器所提供的特定接口 memory.min
和 memory.high
。
当 memory.min
被设置为内存请求时,内存资源被保留并且永远不会被内核回收;
这就是内存 QoS 确保 Kubernetes Pod 的内存可用性的方式。而如果容器中设置了内存限制,
这意味着系统需要限制容器内存的使用;内存 QoS 使用 memory.high
来限制接近其内存限制的工作负载,
确保系统不会因瞬时内存分配而不堪重负。
内存 QoS 依赖于 QoS 类来确定应用哪些设置;它们的机制不同,但都提供对服务质量的控制。
某些行为独立于 QoS 类
某些行为独立于 Kubernetes 分配的 QoS 类。例如:
- 所有超过资源 limit 的容器都将被 kubelet 杀死并重启,而不会影响该 Pod 中的其他容器。
- 如果一个容器超出了自身的资源 request,且该容器运行的节点面临资源压力,则该容器所在的 Pod 就会成为被驱逐的候选对象。 如果出现这种情况,Pod 中的所有容器都将被终止。Kubernetes 通常会在不同的节点上创建一个替代的 Pod。
- Pod 的资源 request 等于其成员容器的资源 request 之和,Pod 的资源 limit 等于其成员容器的资源 limit 之和。
- kube-scheduler 在选择要抢占的 Pod 时不考虑 QoS 类。当集群没有足够的资源来运行你所定义的所有 Pod 时,就会发生抢占。
接下来
- 进一步了解为 Pod 和容器管理资源。
- 进一步了解节点压力驱逐。
- 进一步了解 Pod 优先级和抢占。
- 进一步了解 Pod 干扰。
- 进一步了解如何为容器和 Pod 分配内存资源。
- 进一步了解如何为容器和 Pod 分配 CPU 资源。
- 进一步了解如何配置 Pod 的服务质量。
3.4.1.7 - 用户命名空间
Kubernetes v1.30 [beta]
本页解释了在 Kubernetes Pod 中如何使用用户命名空间。 用户命名空间将容器内运行的用户与主机中的用户隔离开来。
在容器中以 root 身份运行的进程可以在主机中以不同的(非 root)用户身份运行; 换句话说,该进程在用户命名空间内的操作具有完全的权限, 但在命名空间外的操作是无特权的。
你可以使用这个功能来减少被破坏的容器对主机或同一节点中的其他 Pod 的破坏。 有几个安全漏洞被评为 高 或 重要, 当用户命名空间处于激活状态时,这些漏洞是无法被利用的。 预计用户命名空间也会减轻一些未来的漏洞。
准备开始
这是一个只对 Linux 有效的功能特性,且需要 Linux 支持在所用文件系统上挂载 idmap。 这意味着:
- 在节点上,你用于
/var/lib/kubelet/pods/
的文件系统,或你为此配置的自定义目录, 需要支持 idmap 挂载。 - Pod 卷中使用的所有文件系统都必须支持 idmap 挂载。
在实践中,这意味着你最低需要 Linux 6.3,因为 tmpfs 在该版本中开始支持 idmap 挂载。 这通常是需要的,因为有几个 Kubernetes 功能特性使用 tmpfs (默认情况下挂载的服务账号令牌使用 tmpfs、Secret 使用 tmpfs 等等)。
Linux 6.3 中支持 idmap 挂载的一些比较流行的文件系统是:btrfs、ext4、xfs、fat、 tmpfs、overlayfs。
此外,容器运行时及其底层 OCI 运行时必须支持用户命名空间。以下 OCI 运行时提供支持:
说明:
一些 OCI 运行时不包含在 Linux Pod 中使用用户命名空间所需的支持。 如果你使用托管 Kubernetes,或者使用软件包下载并安装 Kubernetes 集群, 则集群中的节点可能使用不包含支持此特性的运行时。
此外,需要在容器运行时提供支持, 才能在 Kubernetes Pod 中使用这一功能:
- containerd:2.0(及更高版本)支持容器使用用户命名空间。
- CRI-O:1.25(及更高)版本支持配置容器的用户命名空间。
你可以在 GitHub 上的 [issue][CRI-dockerd-issue] 中查看 cri-dockerd 中用户命名空间支持的状态。
介绍
用户命名空间是一个 Linux 功能,允许将容器中的用户映射到主机中的不同用户。 此外,在某用户命名空间中授予 Pod 的权能只在该命名空间中有效,在该命名空间之外无效。
一个 Pod 可以通过将 pod.spec.hostUsers
字段设置为 false
来选择使用用户命名空间。
kubelet 将挑选 Pod 所映射的主机 UID/GID, 并以此保证同一节点上没有两个 Pod 使用相同的方式进行映射。
pod.spec
中的 runAsUser
、runAsGroup
、fsGroup
等字段总是指的是容器内的用户。
启用该功能时,有效的 UID/GID 在 0-65535 范围内。这以限制适用于文件和进程(runAsUser
、runAsGroup
等)。
使用这个范围之外的 UID/GID 的文件将被视为属于溢出 ID,
通常是 65534(配置在 /proc/sys/kernel/overflowuid和/proc/sys/kernel/overflowgid
)。
然而,即使以 65534 用户/组的身份运行,也不可能修改这些文件。
大多数需要以 root 身份运行但不访问其他主机命名空间或资源的应用程序, 在用户命名空间被启用时,应该可以继续正常运行,不需要做任何改变。
了解 Pod 的用户命名空间
一些容器运行时的默认配置(如 Docker Engine、containerd、CRI-O)使用 Linux 命名空间进行隔离。 其他技术也存在,也可以与这些运行时(例如,Kata Containers 使用虚拟机而不是 Linux 命名空间)结合使用。 本页适用于使用 Linux 命名空间进行隔离的容器运行时。
在创建 Pod 时,默认情况下会使用几个新的命名空间进行隔离: 一个网络命名空间来隔离容器网络,一个 PID 命名空间来隔离进程视图等等。 如果使用了一个用户命名空间,这将把容器中的用户与节点中的用户隔离开来。
这意味着容器可以以 root 身份运行,并将该身份映射到主机上的一个非 root 用户。
在容器内,进程会认为它是以 root 身份运行的(因此像 apt
、yum
等工具可以正常工作),
而实际上该进程在主机上没有权限。
你可以验证这一点,例如,如果你从主机上执行 ps aux
来检查容器进程是以哪个用户运行的。
ps
显示的用户与你在容器内执行 id
命令时看到的用户是不一样的。
这种抽象限制了可能发生的情况,例如,容器设法逃逸到主机上时的后果。 鉴于容器是作为主机上的一个非特权用户运行的,它能对主机做的事情是有限的。
此外,由于每个 Pod 上的用户将被映射到主机中不同的非重叠用户, 他们对其他 Pod 可以执行的操作也是有限的。
授予一个 Pod 的权能也被限制在 Pod 的用户命名空间内, 并且在这一命名空间之外大多无效,有些甚至完全无效。这里有两个例子:
CAP_SYS_MODULE
若被授予一个使用用户命名空间的 Pod 则没有任何效果,这个 Pod 不能加载内核模块。CAP_SYS_ADMIN
只限于 Pod 所在的用户命名空间,在该命名空间之外无效。
在不使用用户命名空间的情况下,以 root 账号运行的容器,在容器逃逸时,在节点上有 root 权限。 而且如果某些权能被授予了某容器,这些权能在宿主机上也是有效的。 当我们使用用户命名空间时,这些都不再成立。
如果你想知道关于使用用户命名空间时的更多变化细节,请参见 man 7 user_namespaces
。
设置一个节点以支持用户命名空间
默认情况下,kubelet 会分配 0-65535 范围以上的 Pod UID/GID, 这是基于主机的文件和进程使用此范围内的 UID/GID 的假设,也是大多数 Linux 发行版的标准。 此方法可防止主机的 UID/GID 与 Pod 的 UID/GID 之间出现重叠。
避免重叠对于减轻 CVE-2021-25741 等漏洞的影响非常重要, 其中 Pod 可能会读取主机中的任意文件。 如果 Pod 和主机的 UID/GID 不重叠,则 Pod 的功能将受到限制: Pod UID/GID 将与主机的文件所有者/组不匹配。
kubelet 可以对 Pod 的用户 ID 和组 ID 使用自定义范围。要配置自定义范围,节点需要具有:
- 系统中的用户
kubelet
(此处不能使用任何其他用户名)。 - 已安装二进制文件
getsubids
(shadow-utils 的一部分)并位于 kubelet 二进制文件的PATH
中。 kubelet
用户的从属 UID/GID 配置 (请参阅man 5 subuid
和man 5 subgid
)
此设置仅收集 UID/GID 范围配置,不会更改执行 kubelet
的用户。
对于分配给 kubelet
用户的从属 ID 范围, 你必须遵循一些限制:
- 启动 Pod 的 UID 范围的从属用户 ID 必须是 65536 的倍数,并且还必须大于或等于 65536。 换句话说,Pod 不能使用 0-65535 范围内的任何 ID;kubelet 施加此限制是为了使创建意外不安全的配置变得困难。
从属 ID 计数必须是 65536 的倍数
从属 ID 计数必须至少为
65536 x <maxPods>
,其中<maxPods>
是节点上可以运行的最大 Pod 数量。你必须为用户 ID 和组 ID 分配相同的范围。如果其他用户的用户 ID 范围与组 ID 范围不一致也没关系。
所分配的范围不得与任何其他分配重叠。
从属配置必须只有一行。换句话说,你不能有多个范围。
例如,你可以定义 /etc/subuid
和 /etc/subgid
来为 kubelet
用户定义以下条目:
# 格式为:
# name:firstID:count of IDs
# 在哪里:
# - firstID 是 65536 (可能的最小值)
# - IDs 的数量是 110(默认数量限制)* 65536
kubelet:65536:7208960
与 Pod 安全准入检查的集成
Kubernetes v1.29 [alpha]
对于启用了用户命名空间的 Linux Pod,Kubernetes 会以受控方式放宽
Pod 安全性标准的应用。
这种行为可以通过特性门控
UserNamespacesPodSecurityStandards
进行控制,可以让最终用户提前尝试此特性。
如果管理员启用此特性门控,必须确保群集中的所有节点都启用了用户命名空间。
如果你启用相关特性门控并创建了使用用户命名空间的 Pod,以下的字段不会被限制,
即使在执行了 Baseline 或 Restricted Pod 安全性标准的上下文中。这种行为不会带来安全问题,
因为带有用户命名空间的 Pod 内的 root
实际上指的是容器内的用户,绝不会映射到主机上的特权用户。
以下是在这种情况下不进行检查的 Pod 字段列表:
spec.securityContext.runAsNonRoot
spec.containers[*].securityContext.runAsNonRoot
spec.initContainers[*].securityContext.runAsNonRoot
spec.ephemeralContainers[*].securityContext.runAsNonRoot
spec.securityContext.runAsUser
spec.containers[*].securityContext.runAsUser
spec.initContainers[*].securityContext.runAsUser
限制
当 Pod 使用用户命名空间时,不允许 Pod 使用其他主机命名空间。
特别是,如果你设置了 hostUsers: false
,那么你就不可以设置如下属性:
hostNetwork: true
hostIPC: true
hostPID: true
接下来
3.4.1.8 - Downward API
对于容器来说,在不与 Kubernetes 过度耦合的情况下,拥有关于自身的信息有时是很有用的。 Downward API 允许容器在不使用 Kubernetes 客户端或 API 服务器的情况下获得自己或集群的信息。
例如,现有应用程序假设某特定的周知的环境变量是存在的,其中包含唯一标识符。 一种方法是对应用程序进行封装,但这很繁琐且容易出错,并且违背了低耦合的目标。 更好的选择是使用 Pod 名称作为标识符,并将 Pod 名称注入到周知的环境变量中。
在 Kubernetes 中,有两种方法可以将 Pod 和容器字段暴露给运行中的容器:
这两种暴露 Pod 和容器字段的方式统称为 Downward API。
可用字段
只有部分 Kubernetes API 字段可以通过 Downward API 使用。本节列出了你可以使用的字段。
你可以使用 fieldRef
传递来自可用的 Pod 级字段的信息。在 API 层面,一个 Pod 的
spec
总是定义了至少一个 Container。
你可以使用 resourceFieldRef
传递来自可用的 Container 级字段的信息。
可通过 fieldRef
获得的信息
对于某些 Pod 级别的字段,你可以将它们作为环境变量或使用 downwardAPI
卷提供给容器。
通过这两种机制可用的字段有:
metadata.name
- Pod 的名称
metadata.namespace
- Pod 的命名空间
metadata.uid
- Pod 的唯一 ID
metadata.annotations['<KEY>']
- Pod 的注解
<KEY>
的值(例如:metadata.annotations['myannotation']
)
metadata.labels['<KEY>']
- Pod 的标签
<KEY>
的值(例如:metadata.labels['mylabel']
)
以下信息可以通过环境变量获得,但不能作为 downwardAPI
卷 fieldRef
获得:
spec.serviceAccountName
- Pod 的服务账号名称
spec.nodeName
- Pod 运行时所处的节点名称
status.hostIP
- Pod 所在节点的主 IP 地址
status.hostIPs
- 这组 IP 地址是
status.hostIP
的双协议栈版本,第一个 IP 始终与status.hostIP
相同。
status.podIP
- Pod 的主 IP 地址(通常是其 IPv4 地址)
status.podIPs
- 这组 IP 地址是
status.podIP
的双协议栈版本, 第一个 IP 始终与status.podIP
相同。
以下信息可以通过 downwardAPI
卷 fieldRef
获得,但不能作为环境变量获得:
metadata.labels
- Pod 的所有标签,格式为
标签键名="转义后的标签值"
,每行一个标签
metadata.annotations
- Pod 的全部注解,格式为
注解键名="转义后的注解值"
,每行一个注解
可通过 resourceFieldRef
获得的信息
resource: limits.cpu
- 容器的 CPU 限制值
resource: requests.cpu
- 容器的 CPU 请求值
resource: limits.memory
- 容器的内存限制值
resource: requests.memory
- 容器的内存请求值
resource: limits.hugepages-*
- 容器的巨页限制值
resource: requests.hugepages-*
- 容器的巨页请求值
resource: limits.ephemeral-storage
- 容器的临时存储的限制值
resource: requests.ephemeral-storage
- 容器的临时存储的请求值
资源限制的后备信息
如果没有为容器指定 CPU 和内存限制时尝试使用 Downward API 暴露该信息,那么 kubelet 默认会根据 节点可分配资源 计算并暴露 CPU 和内存的最大可分配值。
接下来
你可以阅读有关 downwardAPI
卷的内容。
你可以尝试使用 Downward API 暴露容器或 Pod 级别的信息:
3.4.2 - 工作负载管理
Kubernetes 提供了几个内置的 API 来声明式管理工作负载及其组件。
最终,你的应用以容器的形式在 Pods 中运行; 但是,直接管理单个 Pod 的工作量将会非常繁琐。例如,如果一个 Pod 失败了,你可能希望运行一个新的 Pod 来替换它。Kubernetes 可以为你完成这些操作。
你可以使用 Kubernetes API 创建工作负载对象, 这些对象所表达的是比 Pod 更高级别的抽象概念,Kubernetes 控制平面根据你定义的工作负载对象规约自动管理 Pod 对象。
用于管理工作负载的内置 API 包括:
Deployment (也间接包括 ReplicaSet) 是在集群上运行应用的最常见方式。Deployment 适合在集群上管理无状态应用工作负载, 其中 Deployment 中的任何 Pod 都是可互换的,可以在需要时进行替换。 (Deployment 替代原来的 ReplicationController API)。
StatefulSet 允许你管理一个或多个运行相同应用代码、但具有不同身份标识的 Pod。 StatefulSet 与 Deployment 不同。Deployment 中的 Pod 预期是可互换的。 StatefulSet 最常见的用途是能够建立其 Pod 与其持久化存储之间的关联。 例如,你可以运行一个将每个 Pod 关联到 PersistentVolume 的 StatefulSet。如果该 StatefulSet 中的一个 Pod 失败了,Kubernetes 将创建一个新的 Pod, 并连接到相同的 PersistentVolume。
DaemonSet 定义了在特定节点上提供本地设施的 Pod, 例如允许该节点上的容器访问存储系统的驱动。当必须在合适的节点上运行某种驱动或其他节点级别的服务时, 你可以使用 DaemonSet。DaemonSet 中的每个 Pod 执行类似于经典 Unix / POSIX 服务器上的系统守护进程的角色。DaemonSet 可能对集群的操作至关重要, 例如作为插件让该节点访问集群网络, 也可能帮助你管理节点,或者提供增强正在运行的容器平台所需的、不太重要的设施。 你可以在集群的每个节点上运行 DaemonSets(及其 Pod),或者仅在某个子集上运行 (例如,只在安装了 GPU 的节点上安装 GPU 加速驱动)。
你可以使用 Job 和/或 CronJob 定义一次性任务和定时任务。 Job 表示一次性任务,而每个 CronJob 可以根据排期表重复执行。
本节中的其他主题:
3.4.2.1 - Deployments
一个 Deployment 为 Pod 和 ReplicaSet 提供声明式的更新能力。
你负责描述 Deployment 中的目标状态,而 Deployment 控制器(Controller) 以受控速率更改实际状态, 使其变为期望状态。你可以定义 Deployment 以创建新的 ReplicaSet,或删除现有 Deployment, 并通过新的 Deployment 收养其资源。
说明:
不要管理 Deployment 所拥有的 ReplicaSet。 如果存在下面未覆盖的使用场景,请考虑在 Kubernetes 仓库中提出 Issue。
用例
以下是 Deployments 的典型用例:
- 创建 Deployment 以将 ReplicaSet 上线。ReplicaSet 在后台创建 Pod。 检查 ReplicaSet 的上线状态,查看其是否成功。
- 通过更新 Deployment 的 PodTemplateSpec,声明 Pod 的新状态 。 新的 ReplicaSet 会被创建,Deployment 以受控速率将 Pod 从旧 ReplicaSet 迁移到新 ReplicaSet。 每个新的 ReplicaSet 都会更新 Deployment 的修订版本。
- 如果 Deployment 的当前状态不稳定,回滚到较早的 Deployment 版本。 每次回滚都会更新 Deployment 的修订版本。
- 扩大 Deployment 规模以承担更多负载。
- 暂停 Deployment 的上线 以应用对 PodTemplateSpec 所作的多项修改, 然后恢复其执行以启动新的上线版本。
- 使用 Deployment 状态来判定上线过程是否出现停滞。
- 清理较旧的不再需要的 ReplicaSet 。
下面是一个 Deployment 示例。其中创建了一个 ReplicaSet,负责启动三个 nginx
Pod:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
在该例中:
创建名为
nginx-deployment
(由.metadata.name
字段标明)的 Deployment。 该名称将成为后续创建 ReplicaSet 和 Pod 的命名基础。 参阅编写 Deployment 规约获取更多详细信息。该 Deployment 创建一个 ReplicaSet,它创建三个(由
.spec.replicas
字段标明)Pod 副本。.spec.selector
字段定义所创建的 ReplicaSet 如何查找要管理的 Pod。 在这里,你选择在 Pod 模板中定义的标签(app: nginx
)。 不过,更复杂的选择规则是也可能的,只要 Pod 模板本身满足所给规则即可。说明:
.spec.selector.matchLabels
字段是{key,value}
键值对映射。 在matchLabels
映射中的每个{key,value}
映射等效于matchExpressions
中的一个元素, 即其key
字段是 “key”,operator
为 “In”,values
数组仅包含 “value”。 在matchLabels
和matchExpressions
中给出的所有条件都必须满足才能匹配。
template
字段包含以下子字段:- Pod 被使用
.metadata.labels
字段打上app: nginx
标签。 - Pod 模板规约(即
.template.spec
字段)指示 Pod 运行一个nginx
容器, 该容器运行版本为 1.14.2 的nginx
Docker Hub 镜像。 - 创建一个容器并使用
.spec.template.spec.containers[0].name
字段将其命名为nginx
。
- Pod 被使用
开始之前,请确保的 Kubernetes 集群已启动并运行。 按照以下步骤创建上述 Deployment :
通过运行以下命令创建 Deployment :
kubectl apply -f https://k8s.io/examples/controllers/nginx-deployment.yaml
运行
kubectl get deployments
检查 Deployment 是否已创建。 如果仍在创建 Deployment,则输出类似于:NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 0/3 0 0 1s
在检查集群中的 Deployment 时,所显示的字段有:
NAME
列出了名字空间中 Deployment 的名称。READY
显示应用程序的可用的“副本”数。显示的模式是“就绪个数/期望个数”。UP-TO-DATE
显示为了达到期望状态已经更新的副本数。AVAILABLE
显示应用可供用户使用的副本数。AGE
显示应用程序运行的时间。
请注意期望副本数是根据
.spec.replicas
字段设置 3。
要查看 Deployment 上线状态,运行
kubectl rollout status deployment/nginx-deployment
。输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated... deployment "nginx-deployment" successfully rolled out
几秒钟后再次运行
kubectl get deployments
。输出类似于:NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 18s
注意 Deployment 已创建全部三个副本,并且所有副本都是最新的(它们包含最新的 Pod 模板) 并且可用。
要查看 Deployment 创建的 ReplicaSet(
rs
),运行kubectl get rs
。 输出类似于:NAME DESIRED CURRENT READY AGE nginx-deployment-75675f5897 3 3 3 18s
ReplicaSet 输出中包含以下字段:
NAME
列出名字空间中 ReplicaSet 的名称;DESIRED
显示应用的期望副本个数,即在创建 Deployment 时所定义的值。 此为期望状态;CURRENT
显示当前运行状态中的副本个数;READY
显示应用中有多少副本可以为用户提供服务;AGE
显示应用已经运行的时间长度。
注意 ReplicaSet 的名称格式始终为
[Deployment 名称]-[哈希]
。 该名称将成为所创建的 Pod 的命名基础。 其中的哈希
字符串与 ReplicaSet 上的pod-template-hash
标签一致。
要查看每个 Pod 自动生成的标签,运行
kubectl get pods --show-labels
。 输出类似于:NAME READY STATUS RESTARTS AGE LABELS nginx-deployment-75675f5897-7ci7o 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897 nginx-deployment-75675f5897-kzszj 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897 nginx-deployment-75675f5897-qqcnn 1/1 Running 0 18s app=nginx,pod-template-hash=75675f5897
所创建的 ReplicaSet 确保总是存在三个
nginx
Pod。
说明:
你必须在 Deployment 中指定适当的选择算符和 Pod 模板标签(在本例中为 app: nginx
)。
标签或者选择算符不要与其他控制器(包括其他 Deployment 和 StatefulSet)重叠。
Kubernetes 不会阻止你这样做,但是如果多个控制器具有重叠的选择算符,
它们可能会发生冲突执行难以预料的操作。
Pod-template-hash 标签
注意:
不要更改此标签。
Deployment 控制器将 pod-template-hash
标签添加到 Deployment
所创建或收留的每个 ReplicaSet 。
此标签可确保 Deployment 的子 ReplicaSet 不重叠。
标签是通过对 ReplicaSet 的 PodTemplate
进行哈希处理。
所生成的哈希值被添加到 ReplicaSet 选择算符、Pod 模板标签,并存在于在 ReplicaSet
可能拥有的任何现有 Pod 中。
更新 Deployment
说明:
仅当 Deployment Pod 模板(即 .spec.template
)发生改变时,例如模板的标签或容器镜像被更新,
才会触发 Deployment 上线。其他更新(如对 Deployment 执行扩缩容的操作)不会触发上线动作。
按照以下步骤更新 Deployment:
先来更新 nginx Pod 以使用
nginx:1.16.1
镜像,而不是nginx:1.14.2
镜像。kubectl set image deployment.v1.apps/nginx-deployment nginx=nginx:1.16.1
或者使用下面的命令:
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
在这里,
deployment/nginx-deployment
表明 Deployment 的名称,nginx
表明需要进行更新的容器, 而nginx:1.16.1
则表示镜像的新版本以及它的标签。输出类似于:
deployment.apps/nginx-deployment image updated
或者,可以对 Deployment 执行
edit
操作并将.spec.template.spec.containers[0].image
从nginx:1.14.2
更改至nginx:1.16.1
。kubectl edit deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment edited
要查看上线状态,运行:
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
或者
deployment "nginx-deployment" successfully rolled out
获取关于已更新的 Deployment 的更多信息:
在上线成功后,可以通过运行
kubectl get deployments
来查看 Deployment: 输出类似于:NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 36s
运行
kubectl get rs
以查看 Deployment 通过创建新的 ReplicaSet 并将其扩容到 3 个副本并将旧 ReplicaSet 缩容到 0 个副本完成了 Pod 的更新操作:kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-deployment-1564180365 3 3 3 6s nginx-deployment-2035384211 0 0 0 36s
现在运行
get pods
应仅显示新的 Pod:kubectl get pods
输出类似于:
NAME READY STATUS RESTARTS AGE nginx-deployment-1564180365-khku8 1/1 Running 0 14s nginx-deployment-1564180365-nacti 1/1 Running 0 14s nginx-deployment-1564180365-z9gth 1/1 Running 0 14s
下次要更新这些 Pod 时,只需再次更新 Deployment Pod 模板即可。
Deployment 可确保在更新时仅关闭一定数量的 Pod。默认情况下,它确保至少所需 Pod 的 75% 处于运行状态(最大不可用比例为 25%)。
Deployment 还确保仅所创建 Pod 数量只可能比期望 Pod 数高一点点。 默认情况下,它可确保启动的 Pod 个数比期望个数最多多出 125%(最大峰值 25%)。
例如,如果仔细查看上述 Deployment ,将看到它首先创建了一个新的 Pod,然后删除旧的 Pod, 并创建了新的 Pod。它不会杀死旧 Pod,直到有足够数量的新 Pod 已经出现。 在足够数量的旧 Pod 被杀死前并没有创建新 Pod。它确保至少 3 个 Pod 可用, 同时最多总共 4 个 Pod 可用。 当 Deployment 设置为 4 个副本时,Pod 的个数会介于 3 和 5 之间。
获取 Deployment 的更多信息
kubectl describe deployments
输出类似于:
Name: nginx-deployment Namespace: default CreationTimestamp: Thu, 30 Nov 2017 10:56:25 +0000 Labels: app=nginx Annotations: deployment.kubernetes.io/revision=2 Selector: app=nginx Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.16.1 Port: 80/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: <none> NewReplicaSet: nginx-deployment-1564180365 (3/3 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 2m deployment-controller Scaled up replica set nginx-deployment-2035384211 to 3 Normal ScalingReplicaSet 24s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 1 Normal ScalingReplicaSet 22s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 2 Normal ScalingReplicaSet 22s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 2 Normal ScalingReplicaSet 19s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 1 Normal ScalingReplicaSet 19s deployment-controller Scaled up replica set nginx-deployment-1564180365 to 3 Normal ScalingReplicaSet 14s deployment-controller Scaled down replica set nginx-deployment-2035384211 to 0
可以看到,当第一次创建 Deployment 时,它创建了一个 ReplicaSet(
nginx-deployment-2035384211
) 并将其直接扩容至 3 个副本。更新 Deployment 时,它创建了一个新的 ReplicaSet (nginx-deployment-1564180365),并将其扩容为 1,等待其就绪;然后将旧 ReplicaSet 缩容到 2, 将新的 ReplicaSet 扩容到 2 以便至少有 3 个 Pod 可用且最多创建 4 个 Pod。 然后,它使用相同的滚动更新策略继续对新的 ReplicaSet 扩容并对旧的 ReplicaSet 缩容。 最后,你将有 3 个可用的副本在新的 ReplicaSet 中,旧 ReplicaSet 将缩容到 0。
说明:
Kubernetes 在计算 availableReplicas
数值时不考虑终止过程中的 Pod,
availableReplicas
的值一定介于 replicas - maxUnavailable
和 replicas + maxSurge
之间。
因此,你可能在上线期间看到 Pod 个数比预期的多,Deployment 所消耗的总的资源也大于
replicas + maxSurge
个 Pod 所用的资源,直到被终止的 Pod 所设置的
terminationGracePeriodSeconds
到期为止。
翻转(多 Deployment 动态更新)
Deployment 控制器每次注意到新的 Deployment 时,都会创建一个 ReplicaSet 以启动所需的 Pod。
如果更新了 Deployment,则控制标签匹配 .spec.selector
但模板不匹配 .spec.template
的 Pod 的现有 ReplicaSet 被缩容。
最终,新的 ReplicaSet 缩放为 .spec.replicas
个副本,
所有旧 ReplicaSet 缩放为 0 个副本。
当 Deployment 正在上线时被更新,Deployment 会针对更新创建一个新的 ReplicaSet 并开始对其扩容,之前正在被扩容的 ReplicaSet 会被翻转,添加到旧 ReplicaSet 列表 并开始缩容。
例如,假定你在创建一个 Deployment 以生成 nginx:1.14.2
的 5 个副本,但接下来
更新 Deployment 以创建 5 个 nginx:1.16.1
的副本,而此时只有 3 个 nginx:1.14.2
副本已创建。在这种情况下,Deployment 会立即开始杀死 3 个 nginx:1.14.2
Pod,
并开始创建 nginx:1.16.1
Pod。它不会等待 nginx:1.14.2
的 5
个副本都创建完成后才开始执行变更动作。
更改标签选择算符
通常不鼓励更新标签选择算符。建议你提前规划选择算符。 在任何情况下,如果需要更新标签选择算符,请格外小心, 并确保自己了解这背后可能发生的所有事情。
说明:
在 API 版本 apps/v1
中,Deployment 标签选择算符在创建后是不可变的。
- 添加选择算符时要求使用新标签更新 Deployment 规约中的 Pod 模板标签,否则将返回验证错误。 此更改是非重叠的,也就是说新的选择算符不会选择使用旧选择算符所创建的 ReplicaSet 和 Pod, 这会导致创建新的 ReplicaSet 时所有旧 ReplicaSet 都会被孤立。
- 选择算符的更新如果更改了某个算符的键名,这会导致与添加算符时相同的行为。
- 删除选择算符的操作会删除从 Deployment 选择算符中删除现有算符。 此操作不需要更改 Pod 模板标签。现有 ReplicaSet 不会被孤立,也不会因此创建新的 ReplicaSet, 但请注意已删除的标签仍然存在于现有的 Pod 和 ReplicaSet 中。
回滚 Deployment
有时,你可能想要回滚 Deployment;例如,当 Deployment 不稳定时(例如进入反复崩溃状态)。 默认情况下,Deployment 的所有上线记录都保留在系统中,以便可以随时回滚 (你可以通过修改修订历史记录限制来更改这一约束)。
说明:
Deployment 被触发上线时,系统就会创建 Deployment 的新的修订版本。
这意味着仅当 Deployment 的 Pod 模板(.spec.template
)发生更改时,才会创建新修订版本
-- 例如,模板的标签或容器镜像发生变化。
其他更新,如 Deployment 的扩缩容操作不会创建 Deployment 修订版本。
这是为了方便同时执行手动缩放或自动缩放。
换言之,当你回滚到较早的修订版本时,只有 Deployment 的 Pod 模板部分会被回滚。
假设你在更新 Deployment 时犯了一个拼写错误,将镜像名称命名设置为
nginx:1.161
而不是nginx:1.16.1
:kubectl set image deployment/nginx-deployment nginx=nginx:1.161
输出类似于:
deployment.apps/nginx-deployment image updated
此上线进程会出现停滞。你可以通过检查上线状态来验证:
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 1 out of 3 new replicas have been updated...
- 按 Ctrl-C 停止上述上线状态观测。有关上线停滞的详细信息,参考这里。
你可以看到旧的副本(算上来自
nginx-deployment-1564180365
和nginx-deployment-2035384211
的副本)有 3 个, 新的副本(来自nginx-deployment-3066724191
)有 1 个:kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-deployment-1564180365 3 3 3 25s nginx-deployment-2035384211 0 0 0 36s nginx-deployment-3066724191 1 1 0 6s
查看所创建的 Pod,你会注意到新 ReplicaSet 所创建的 1 个 Pod 卡顿在镜像拉取循环中。
kubectl get pods
输出类似于:
NAME READY STATUS RESTARTS AGE nginx-deployment-1564180365-70iae 1/1 Running 0 25s nginx-deployment-1564180365-jbqqo 1/1 Running 0 25s nginx-deployment-1564180365-hysrc 1/1 Running 0 25s nginx-deployment-3066724191-08mng 0/1 ImagePullBackOff 0 6s
说明:
Deployment 控制器自动停止有问题的上线过程,并停止对新的 ReplicaSet 扩容。 这行为取决于所指定的 rollingUpdate 参数(具体为
maxUnavailable
)。 默认情况下,Kubernetes 将此值设置为 25%。
获取 Deployment 描述信息:
kubectl describe deployment
输出类似于:
Name: nginx-deployment Namespace: default CreationTimestamp: Tue, 15 Mar 2016 14:48:04 -0700 Labels: app=nginx Selector: app=nginx Replicas: 3 desired | 1 updated | 4 total | 3 available | 1 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.161 Port: 80/TCP Host Port: 0/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True ReplicaSetUpdated OldReplicaSets: nginx-deployment-1564180365 (3/3 replicas created) NewReplicaSet: nginx-deployment-3066724191 (1/1 replicas created) Events: FirstSeen LastSeen Count From SubObjectPath Type Reason Message --------- -------- ----- ---- ------------- -------- ------ ------- 1m 1m 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-2035384211 to 3 22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 1 22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 2 22s 22s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 2 21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 1 21s 21s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-1564180365 to 3 13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled down replica set nginx-deployment-2035384211 to 0 13s 13s 1 {deployment-controller } Normal ScalingReplicaSet Scaled up replica set nginx-deployment-3066724191 to 1
要解决此问题,需要回滚到以前稳定的 Deployment 版本。
检查 Deployment 上线历史
按照如下步骤检查回滚历史:
首先,检查 Deployment 修订历史:
kubectl rollout history deployment/nginx-deployment
输出类似于:
deployments "nginx-deployment" REVISION CHANGE-CAUSE 1 kubectl apply --filename=https://k8s.io/examples/controllers/nginx-deployment.yaml 2 kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 3 kubectl set image deployment/nginx-deployment nginx=nginx:1.161
CHANGE-CAUSE
的内容是从 Deployment 的kubernetes.io/change-cause
注解复制过来的。 复制动作发生在修订版本创建时。你可以通过以下方式设置CHANGE-CAUSE
消息:- 使用
kubectl annotate deployment/nginx-deployment kubernetes.io/change-cause="image updated to 1.16.1"
为 Deployment 添加注解。 - 手动编辑资源的清单。
- 使用
要查看修订历史的详细信息,运行:
kubectl rollout history deployment/nginx-deployment --revision=2
输出类似于:
deployments "nginx-deployment" revision 2 Labels: app=nginx pod-template-hash=1159050644 Annotations: kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 Containers: nginx: Image: nginx:1.16.1 Port: 80/TCP QoS Tier: cpu: BestEffort memory: BestEffort Environment Variables: <none> No volumes.
回滚到之前的修订版本
按照下面给出的步骤将 Deployment 从当前版本回滚到以前的版本(即版本 2)。
假定现在你已决定撤消当前上线并回滚到以前的修订版本:
kubectl rollout undo deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment rolled back
或者,你也可以通过使用
--to-revision
来回滚到特定修订版本:kubectl rollout undo deployment/nginx-deployment --to-revision=2
输出类似于:
deployment.apps/nginx-deployment rolled back
与回滚相关的指令的更详细信息,请参考
kubectl rollout
。现在,Deployment 正在回滚到以前的稳定版本。正如你所看到的,Deployment 控制器生成了回滚到修订版本 2 的
DeploymentRollback
事件。
检查回滚是否成功以及 Deployment 是否正在运行,运行:
kubectl get deployment nginx-deployment
输出类似于:
NAME READY UP-TO-DATE AVAILABLE AGE nginx-deployment 3/3 3 3 30m
获取 Deployment 描述信息:
kubectl describe deployment nginx-deployment
输出类似于:
Name: nginx-deployment Namespace: default CreationTimestamp: Sun, 02 Sep 2018 18:17:55 -0500 Labels: app=nginx Annotations: deployment.kubernetes.io/revision=4 kubernetes.io/change-cause=kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1 Selector: app=nginx Replicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 25% max unavailable, 25% max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.16.1 Port: 80/TCP Host Port: 0/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: <none> NewReplicaSet: nginx-deployment-c4747d96c (3/3 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 12m deployment-controller Scaled up replica set nginx-deployment-75675f5897 to 3 Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 1 Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 2 Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 2 Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 1 Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-c4747d96c to 3 Normal ScalingReplicaSet 11m deployment-controller Scaled down replica set nginx-deployment-75675f5897 to 0 Normal ScalingReplicaSet 11m deployment-controller Scaled up replica set nginx-deployment-595696685f to 1 Normal DeploymentRollback 15s deployment-controller Rolled back deployment "nginx-deployment" to revision 2 Normal ScalingReplicaSet 15s deployment-controller Scaled down replica set nginx-deployment-595696685f to 0
缩放 Deployment
你可以使用如下指令缩放 Deployment:
kubectl scale deployment/nginx-deployment --replicas=10
输出类似于:
deployment.apps/nginx-deployment scaled
假设集群启用了Pod 的水平自动缩放, 你可以为 Deployment 设置自动缩放器,并基于现有 Pod 的 CPU 利用率选择要运行的 Pod 个数下限和上限。
kubectl autoscale deployment/nginx-deployment --min=10 --max=15 --cpu-percent=80
输出类似于:
deployment.apps/nginx-deployment scaled
比例缩放
RollingUpdate 的 Deployment 支持同时运行应用程序的多个版本。 当自动缩放器缩放处于上线进程(仍在进行中或暂停)中的 RollingUpdate Deployment 时, Deployment 控制器会平衡现有的活跃状态的 ReplicaSet(含 Pod 的 ReplicaSet)中的额外副本, 以降低风险。这称为 比例缩放(Proportional Scaling)。
例如,你正在运行一个 10 个副本的 Deployment,其 maxSurge=3,maxUnavailable=2。
确保 Deployment 的这 10 个副本都在运行。
kubectl get deploy
输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx-deployment 10 10 10 10 50s
更新 Deployment 使用新镜像,碰巧该镜像无法从集群内部解析。
kubectl set image deployment/nginx-deployment nginx=nginx:sometag
输出类似于:
deployment.apps/nginx-deployment image updated
镜像更新使用 ReplicaSet
nginx-deployment-1989198191
启动新的上线过程, 但由于上面提到的maxUnavailable
要求,该进程被阻塞了。检查上线状态:kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-deployment-1989198191 5 5 0 9s nginx-deployment-618515232 8 8 8 1m
- 然后,出现了新的 Deployment 扩缩请求。自动缩放器将 Deployment 副本增加到 15。 Deployment 控制器需要决定在何处添加 5 个新副本。如果未使用比例缩放,所有 5 个副本 都将添加到新的 ReplicaSet 中。使用比例缩放时,可以将额外的副本分布到所有 ReplicaSet。 较大比例的副本会被添加到拥有最多副本的 ReplicaSet,而较低比例的副本会进入到 副本较少的 ReplicaSet。所有剩下的副本都会添加到副本最多的 ReplicaSet。 具有零副本的 ReplicaSet 不会被扩容。
在上面的示例中,3 个副本被添加到旧 ReplicaSet 中,2 个副本被添加到新 ReplicaSet。 假定新的副本都很健康,上线过程最终应将所有副本迁移到新的 ReplicaSet 中。 要确认这一点,请运行:
kubectl get deploy
输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx-deployment 15 18 7 8 7m
上线状态确认了副本是如何被添加到每个 ReplicaSet 的。
kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE
nginx-deployment-1989198191 7 7 0 7m
nginx-deployment-618515232 11 11 11 7m
暂停、恢复 Deployment 的上线过程
在你更新一个 Deployment 的时候,或者计划更新它的时候, 你可以在触发一个或多个更新之前暂停 Deployment 的上线过程。 当你准备应用这些变更时,你可以重新恢复 Deployment 上线过程。 这样做使得你能够在暂停和恢复执行之间应用多个修补程序,而不会触发不必要的上线操作。
例如,对于一个刚刚创建的 Deployment:
获取该 Deployment 信息:
kubectl get deploy
输出类似于:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE nginx 3 3 3 3 1m
获取上线状态:
kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-2142116321 3 3 3 1m
使用如下指令暂停上线:
kubectl rollout pause deployment/nginx-deployment
输出类似于:
deployment.apps/nginx-deployment paused
接下来更新 Deployment 镜像:
kubectl set image deployment/nginx-deployment nginx=nginx:1.16.1
输出类似于:
deployment.apps/nginx-deployment image updated
注意没有新的上线被触发:
kubectl rollout history deployment/nginx-deployment
输出类似于:
deployments "nginx" REVISION CHANGE-CAUSE 1 <none>
获取上线状态验证现有的 ReplicaSet 没有被更改:
kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-2142116321 3 3 3 2m
你可以根据需要执行很多更新操作,例如,可以要使用的资源:
kubectl set resources deployment/nginx-deployment -c=nginx --limits=cpu=200m,memory=512Mi
输出类似于:
deployment.apps/nginx-deployment resource requirements updated
暂停 Deployment 上线之前的初始状态将继续发挥作用,但新的更新在 Deployment 上线被暂停期间不会产生任何效果。
最终,恢复 Deployment 上线并观察新的 ReplicaSet 的创建过程,其中包含了所应用的所有更新:
kubectl rollout resume deployment/nginx-deployment
输出类似于这样:
deployment.apps/nginx-deployment resumed
监视上线的状态,直到完成。
kubectl get rs --watch
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-2142116321 2 2 2 2m nginx-3926361531 2 2 0 6s nginx-3926361531 2 2 1 18s nginx-2142116321 1 2 2 2m nginx-2142116321 1 2 2 2m nginx-3926361531 3 2 1 18s nginx-3926361531 3 2 1 18s nginx-2142116321 1 1 1 2m nginx-3926361531 3 3 1 18s nginx-3926361531 3 3 2 19s nginx-2142116321 0 1 1 2m nginx-2142116321 0 1 1 2m nginx-2142116321 0 0 0 2m nginx-3926361531 3 3 3 20s
获取最近上线的状态:
kubectl get rs
输出类似于:
NAME DESIRED CURRENT READY AGE nginx-2142116321 0 0 0 2m nginx-3926361531 3 3 3 28s
说明:
你不可以回滚处于暂停状态的 Deployment,除非先恢复其执行状态。
Deployment 状态
Deployment 的生命周期中会有许多状态。上线新的 ReplicaSet 期间可能处于 Progressing(进行中),可能是 Complete(已完成),也可能是 Failed(失败)以至于无法继续进行。
进行中的 Deployment
执行下面的任务期间,Kubernetes 标记 Deployment 为进行中(Progressing)_:
- Deployment 创建新的 ReplicaSet
- Deployment 正在为其最新的 ReplicaSet 扩容
- Deployment 正在为其旧有的 ReplicaSet(s) 缩容
- 新的 Pod 已经就绪或者可用(就绪至少持续了 MinReadySeconds 秒)。
当上线过程进入“Progressing”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetCreated
|reason: FoundNewReplicaSet
|reason: ReplicaSetUpdated
你可以使用 kubectl rollout status
监视 Deployment 的进度。
完成的 Deployment
当 Deployment 具有以下特征时,Kubernetes 将其标记为完成(Complete);
- 与 Deployment 关联的所有副本都已更新到指定的最新版本,这意味着之前请求的所有更新都已完成。
- 与 Deployment 关联的所有副本都可用。
- 未运行 Deployment 的旧副本。
当上线过程进入“Complete”状态时,Deployment 控制器会向 Deployment 的
.status.conditions
中添加包含下面属性的状况条目:
type: Progressing
status: "True"
reason: NewReplicaSetAvailable
这一 Progressing
状况的状态值会持续为 "True"
,直至新的上线动作被触发。
即使副本的可用状态发生变化(进而影响 Available
状况),Progressing
状况的值也不会变化。
你可以使用 kubectl rollout status
检查 Deployment 是否已完成。
如果上线成功完成,kubectl rollout status
返回退出代码 0。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 of 3 updated replicas are available...
deployment "nginx-deployment" successfully rolled out
从 kubectl rollout
命令获得的返回状态为 0(成功):
echo $?
0
失败的 Deployment
你的 Deployment 可能会在尝试部署其最新的 ReplicaSet 受挫,一直处于未完成状态。 造成此情况一些可能因素如下:
- 配额(Quota)不足
- 就绪探测(Readiness Probe)失败
- 镜像拉取错误
- 权限不足
- 限制范围(Limit Ranges)问题
- 应用程序运行时的配置错误
检测此状况的一种方法是在 Deployment 规约中指定截止时间参数:
(.spec.progressDeadlineSeconds
)。
.spec.progressDeadlineSeconds
给出的是一个秒数值,Deployment 控制器在(通过 Deployment 状态)
标示 Deployment 进展停滞之前,需要等待所给的时长。
以下 kubectl
命令设置规约中的 progressDeadlineSeconds
,从而告知控制器
在 10 分钟后报告 Deployment 的上线没有进展:
kubectl patch deployment/nginx-deployment -p '{"spec":{"progressDeadlineSeconds":600}}'
输出类似于:
deployment.apps/nginx-deployment patched
超过截止时间后,Deployment 控制器将添加具有以下属性的 Deployment 状况到
Deployment 的 .status.conditions
中:
type: Progressing
status: "False"
reason: ProgressDeadlineExceeded
这一状况也可能会比较早地失败,因而其状态值被设置为 "False"
,
其原因为 ReplicaSetCreateError
。
一旦 Deployment 上线完成,就不再考虑其期限。
参考 Kubernetes API Conventions 获取更多状态状况相关的信息。
说明:
除了报告 Reason=ProgressDeadlineExceeded
状态之外,Kubernetes 对已停止的
Deployment 不执行任何操作。更高级别的编排器可以利用这一设计并相应地采取行动。
例如,将 Deployment 回滚到其以前的版本。
说明:
如果你暂停了某个 Deployment 上线,Kubernetes 不再根据指定的截止时间检查 Deployment 上线的进展。 你可以在上线过程中间安全地暂停 Deployment 再恢复其执行,这样做不会导致超出最后时限的问题。
Deployment 可能会出现瞬时性的错误,可能因为设置的超时时间过短, 也可能因为其他可认为是临时性的问题。例如,假定所遇到的问题是配额不足。 如果描述 Deployment,你将会注意到以下部分:
kubectl describe deployment nginx-deployment
输出类似于:
<...>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True ReplicaSetUpdated
ReplicaFailure True FailedCreate
<...>
如果运行 kubectl get deployment nginx-deployment -o yaml
,Deployment 状态输出
将类似于这样:
status:
availableReplicas: 2
conditions:
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: Replica set "nginx-deployment-4262182780" is progressing.
reason: ReplicaSetUpdated
status: "True"
type: Progressing
- lastTransitionTime: 2016-10-04T12:25:42Z
lastUpdateTime: 2016-10-04T12:25:42Z
message: Deployment has minimum availability.
reason: MinimumReplicasAvailable
status: "True"
type: Available
- lastTransitionTime: 2016-10-04T12:25:39Z
lastUpdateTime: 2016-10-04T12:25:39Z
message: 'Error creating: pods "nginx-deployment-4262182780-" is forbidden: exceeded quota:
object-counts, requested: pods=1, used: pods=3, limited: pods=2'
reason: FailedCreate
status: "True"
type: ReplicaFailure
observedGeneration: 3
replicas: 2
unavailableReplicas: 2
最终,一旦超过 Deployment 进度限期,Kubernetes 将更新状态和进度状况的原因:
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing False ProgressDeadlineExceeded
ReplicaFailure True FailedCreate
可以通过缩容 Deployment 或者缩容其他运行状态的控制器,或者直接在命名空间中增加配额
来解决配额不足的问题。如果配额条件满足,Deployment 控制器完成了 Deployment 上线操作,
Deployment 状态会更新为成功状况(Status=True
和 Reason=NewReplicaSetAvailable
)。
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
type: Available
加上 status: True
意味着 Deployment 具有最低可用性。
最低可用性由 Deployment 策略中的参数指定。
type: Progressing
加上 status: True
表示 Deployment 处于上线过程中,并且正在运行,
或者已成功完成进度,最小所需新副本处于可用。
请参阅对应状况的 Reason 了解相关细节。
在我们的案例中 reason: NewReplicaSetAvailable
表示 Deployment 已完成。
你可以使用 kubectl rollout status
检查 Deployment 是否未能取得进展。
如果 Deployment 已超过进度限期,kubectl rollout status
返回非零退出代码。
kubectl rollout status deployment/nginx-deployment
输出类似于:
Waiting for rollout to finish: 2 out of 3 new replicas have been updated...
error: deployment "nginx" exceeded its progress deadline
kubectl rollout
命令的退出状态为 1(表明发生了错误):
echo $?
1
对失败 Deployment 的操作
可应用于已完成的 Deployment 的所有操作也适用于失败的 Deployment。 你可以对其执行扩缩容、回滚到以前的修订版本等操作,或者在需要对 Deployment 的 Pod 模板应用多项调整时,将 Deployment 暂停。
清理策略
你可以在 Deployment 中设置 .spec.revisionHistoryLimit
字段以指定保留此
Deployment 的多少个旧有 ReplicaSet。其余的 ReplicaSet 将在后台被垃圾回收。
默认情况下,此值为 10。
说明:
显式将此字段设置为 0 将导致 Deployment 的所有历史记录被清空,因此 Deployment 将无法回滚。
金丝雀部署
如果要使用 Deployment 向用户子集或服务器子集上线版本, 则可以遵循资源管理所描述的金丝雀模式, 创建多个 Deployment,每个版本一个。
编写 Deployment 规约
同其他 Kubernetes 配置一样, Deployment 需要 .apiVersion
,.kind
和 .metadata
字段。
有关配置文件的其他信息,请参考部署 Deployment、
配置容器和使用 kubectl 管理资源等相关文档。
当控制面为 Deployment 创建新的 Pod 时,Deployment 的 .metadata.name
是命名这些 Pod 的部分基础。
Deployment 的名称必须是一个合法的
DNS 子域值,
但这会对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签规则。
Deployment 还需要
.spec
部分。
Pod 模板
.spec
中只有 .spec.template
和 .spec.selector
是必需的字段。
.spec.template
是一个 Pod 模板。
它和 Pod 的语法规则完全相同。
只是这里它是嵌套的,因此不需要 apiVersion
或 kind
。
除了 Pod 的必填字段外,Deployment 中的 Pod 模板必须指定适当的标签和适当的重新启动策略。 对于标签,请确保不要与其他控制器重叠。请参考选择算符。
只有 .spec.template.spec.restartPolicy
等于 Always
才是被允许的,这也是在没有指定时的默认设置。
副本
.spec.replicas
是指定所需 Pod 的可选字段。它的默认值是1。
如果你对某个 Deployment 执行了手动扩缩操作(例如,通过
kubectl scale deployment deployment --replicas=X
),
之后基于清单对 Deployment 执行了更新操作(例如通过运行
kubectl apply -f deployment.yaml
),那么通过应用清单而完成的更新会覆盖之前手动扩缩所作的变更。
如果一个 HorizontalPodAutoscaler
(或者其他执行水平扩缩操作的类似 API)在管理 Deployment 的扩缩,
则不要设置 .spec.replicas
。
恰恰相反,应该允许 Kubernetes
控制面来自动管理
.spec.replicas
字段。
选择算符
.spec.selector
是指定本 Deployment 的 Pod
标签选择算符的必需字段。
.spec.selector
必须匹配 .spec.template.metadata.labels
,否则请求会被 API 拒绝。
在 API apps/v1
版本中,.spec.selector
和 .metadata.labels
如果没有设置的话,
不会被默认设置为 .spec.template.metadata.labels
,所以需要明确进行设置。
同时在 apps/v1
版本中,Deployment 创建后 .spec.selector
是不可变的。
当 Pod 的标签和选择算符匹配,但其模板和 .spec.template
不同时,或者此类 Pod
的总数超过 .spec.replicas
的设置时,Deployment 会终结之。
如果 Pod 总数未达到期望值,Deployment 会基于 .spec.template
创建新的 Pod。
说明:
你不应直接创建与此选择算符匹配的 Pod,也不应通过创建另一个 Deployment 或者类似于 ReplicaSet 或 ReplicationController 这类控制器来创建标签与此选择算符匹配的 Pod。 如果这样做,第一个 Deployment 会认为它创建了这些 Pod。 Kubernetes 不会阻止你这么做。
如果有多个控制器的选择算符发生重叠,则控制器之间会因冲突而无法正常工作。
策略
.spec.strategy
策略指定用于用新 Pod 替换旧 Pod 的策略。
.spec.strategy.type
可以是 “Recreate” 或 “RollingUpdate”。“RollingUpdate” 是默认值。
重新创建 Deployment
如果 .spec.strategy.type==Recreate
,在创建新 Pod 之前,所有现有的 Pod 会被杀死。
说明:
这只会确保为了升级而创建新 Pod 之前其他 Pod 都已终止。如果你升级一个 Deployment, 所有旧版本的 Pod 都会立即被终止。控制器等待这些 Pod 被成功移除之后, 才会创建新版本的 Pod。如果你手动删除一个 Pod,其生命周期是由 ReplicaSet 来控制的, 后者会立即创建一个替换 Pod(即使旧的 Pod 仍然处于 Terminating 状态)。 如果你需要一种“最多 n 个”的 Pod 个数保证,你需要考虑使用 StatefulSet。
滚动更新 Deployment
Deployment 会在 .spec.strategy.type==RollingUpdate
时,采取
滚动更新的方式更新 Pod。你可以指定 maxUnavailable
和 maxSurge
来控制滚动更新过程。
最大不可用
.spec.strategy.rollingUpdate.maxUnavailable
是一个可选字段,
用来指定更新过程中不可用的 Pod 的个数上限。该值可以是绝对数字(例如,5),也可以是所需
Pod 的百分比(例如,10%)。百分比值会转换成绝对数并去除小数部分。
如果 .spec.strategy.rollingUpdate.maxSurge
为 0,则此值不能为 0。
默认值为 25%。
例如,当此值设置为 30% 时,滚动更新开始时会立即将旧 ReplicaSet 缩容到期望 Pod 个数的70%。 新 Pod 准备就绪后,可以继续缩容旧有的 ReplicaSet,然后对新的 ReplicaSet 扩容, 确保在更新期间可用的 Pod 总数在任何时候都至少为所需的 Pod 个数的 70%。
最大峰值
.spec.strategy.rollingUpdate.maxSurge
是一个可选字段,用来指定可以创建的超出期望
Pod 个数的 Pod 数量。此值可以是绝对数(例如,5)或所需 Pod 的百分比(例如,10%)。
如果 MaxUnavailable
为 0,则此值不能为 0。百分比值会通过向上取整转换为绝对数。
此字段的默认值为 25%。
例如,当此值为 30% 时,启动滚动更新后,会立即对新的 ReplicaSet 扩容,同时保证新旧 Pod 的总数不超过所需 Pod 总数的 130%。一旦旧 Pod 被杀死,新的 ReplicaSet 可以进一步扩容, 同时确保更新期间的任何时候运行中的 Pod 总数最多为所需 Pod 总数的 130%。
以下是一些使用 maxUnavailable
和 maxSurge
的滚动更新 Deployment 的示例:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 1
进度期限秒数
.spec.progressDeadlineSeconds
是一个可选字段,用于指定系统在报告 Deployment
进展失败之前等待 Deployment 取得进展的秒数。
这类报告会在资源状态中体现为 type: Progressing
、status: False
、
reason: ProgressDeadlineExceeded
。Deployment 控制器将在默认 600 毫秒内持续重试 Deployment。
将来,一旦实现了自动回滚,Deployment 控制器将在探测到这样的条件时立即回滚 Deployment。
如果指定,则此字段值需要大于 .spec.minReadySeconds
取值。
最短就绪时间
.spec.minReadySeconds
是一个可选字段,用于指定新创建的 Pod
在没有任意容器崩溃情况下的最小就绪时间,
只有超出这个时间 Pod 才被视为可用。默认值为 0(Pod 在准备就绪后立即将被视为可用)。
要了解何时 Pod 被视为就绪,
可参考容器探针。
修订历史限制
Deployment 的修订历史记录存储在它所控制的 ReplicaSet 中。
.spec.revisionHistoryLimit
是一个可选字段,用来设定出于回滚目的所要保留的旧 ReplicaSet 数量。
这些旧 ReplicaSet 会消耗 etcd 中的资源,并占用 kubectl get rs
的输出。
每个 Deployment 修订版本的配置都存储在其 ReplicaSet 中;因此,一旦删除了旧的 ReplicaSet,
将失去回滚到 Deployment 的对应修订版本的能力。
默认情况下,系统保留 10 个旧 ReplicaSet,但其理想值取决于新 Deployment 的频率和稳定性。
更具体地说,将此字段设置为 0 意味着将清理所有具有 0 个副本的旧 ReplicaSet。 在这种情况下,无法撤消新的 Deployment 上线,因为它的修订历史被清除了。
paused(暂停的)
.spec.paused
是用于暂停和恢复 Deployment 的可选布尔字段。
暂停的 Deployment 和未暂停的 Deployment 的唯一区别是,Deployment 处于暂停状态时,
PodTemplateSpec 的任何修改都不会触发新的上线。
Deployment 在创建时是默认不会处于暂停状态。
接下来
- 进一步了解 Pod。
- 使用 Deployment 运行一个无状态应用。
- 阅读 Deployment, 以了解 Deployment API 的细节。
- 阅读 PodDisruptionBudget 了解如何使用它来在可能出现干扰的情况下管理应用的可用性。
- 使用 kubectl 来创建一个 Deployment。
3.4.2.2 - ReplicaSet
ReplicaSet 的目的是维护一组在任何时候都处于运行状态的 Pod 副本的稳定集合。 因此,它通常用来保证给定数量的、完全相同的 Pod 的可用性。
ReplicaSet 的工作原理
ReplicaSet 是通过一组字段来定义的,包括一个用来识别可获得的 Pod 的集合的选择算符、一个用来标明应该维护的副本个数的数值、一个用来指定应该创建新 Pod 以满足副本个数条件时要使用的 Pod 模板等等。 每个 ReplicaSet 都通过根据需要创建和删除 Pod 以使得副本个数达到期望值, 进而实现其存在价值。当 ReplicaSet 需要创建新的 Pod 时,会使用所提供的 Pod 模板。
ReplicaSet 通过 Pod 上的 metadata.ownerReferences 字段连接到附属 Pod,该字段给出当前对象的属主资源。 ReplicaSet 所获得的 Pod 都在其 ownerReferences 字段中包含了属主 ReplicaSet 的标识信息。正是通过这一连接,ReplicaSet 知道它所维护的 Pod 集合的状态, 并据此计划其操作行为。
ReplicaSet 使用其选择算符来辨识要获得的 Pod 集合。如果某个 Pod 没有 OwnerReference 或者其 OwnerReference 不是一个控制器, 且其匹配到某 ReplicaSet 的选择算符,则该 Pod 立即被此 ReplicaSet 获得。
何时使用 ReplicaSet
ReplicaSet 确保任何时间都有指定数量的 Pod 副本在运行。 然而,Deployment 是一个更高级的概念,它管理 ReplicaSet,并向 Pod 提供声明式的更新以及许多其他有用的功能。 因此,我们建议使用 Deployment 而不是直接使用 ReplicaSet, 除非你需要自定义更新业务流程或根本不需要更新。
这实际上意味着,你可能永远不需要操作 ReplicaSet 对象:而是使用 Deployment,并在 spec 部分定义你的应用。
示例
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: frontend
labels:
app: guestbook
tier: frontend
spec:
# 按你的实际情况修改副本数
replicas: 3
selector:
matchLabels:
tier: frontend
template:
metadata:
labels:
tier: frontend
spec:
containers:
- name: php-redis
image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5
将此清单保存到 frontend.yaml
中,并将其提交到 Kubernetes 集群,
就能创建 yaml 文件所定义的 ReplicaSet 及其管理的 Pod。
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你可以看到当前被部署的 ReplicaSet:
kubectl get rs
并看到你所创建的前端:
NAME DESIRED CURRENT READY AGE
frontend 3 3 3 6s
你也可以查看 ReplicaSet 的状态:
kubectl describe rs/frontend
你会看到类似如下的输出:
Name: frontend
Namespace: default
Selector: tier=frontend
Labels: app=guestbook
tier=frontend
Annotations: <none>
Replicas: 3 current / 3 desired
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: tier=frontend
Containers:
php-redis:
Image: us-docker.pkg.dev/google-samples/containers/gke/gb-frontend:v5
Port: <none>
Host Port: <none>
Environment: <none>
Mounts: <none>
Volumes: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 13s replicaset-controller Created pod: frontend-gbgfx
Normal SuccessfulCreate 13s replicaset-controller Created pod: frontend-rwz57
Normal SuccessfulCreate 13s replicaset-controller Created pod: frontend-wkl7w
最后可以查看启动了的 Pod 集合:
kubectl get pods
你会看到类似如下的 Pod 信息:
NAME READY STATUS RESTARTS AGE
frontend-gbgfx 1/1 Running 0 10m
frontend-rwz57 1/1 Running 0 10m
frontend-wkl7w 1/1 Running 0 10m
你也可以查看 Pod 的属主引用被设置为前端的 ReplicaSet。 要实现这点,可取回运行中的某个 Pod 的 YAML:
kubectl get pods frontend-gbgfx -o yaml
输出将类似这样,frontend ReplicaSet 的信息被设置在 metadata 的
ownerReferences
字段中:
apiVersion: v1
kind: Pod
metadata:
creationTimestamp: "2024-02-28T22:30:44Z"
generateName: frontend-
labels:
tier: frontend
name: frontend-gbgfx
namespace: default
ownerReferences:
- apiVersion: apps/v1
blockOwnerDeletion: true
controller: true
kind: ReplicaSet
name: frontend
uid: e129deca-f864-481b-bb16-b27abfd92292
...
非模板 Pod 的获得
尽管你完全可以直接创建裸的 Pod,强烈建议你确保这些裸的 Pod 并不包含可能与你的某个 ReplicaSet 的选择算符相匹配的标签。原因在于 ReplicaSet 并不仅限于拥有在其模板中设置的 Pod,它还可以像前面小节中所描述的那样获得其他 Pod。
以前面的 frontend ReplicaSet 为例,并在以下清单中指定这些 Pod:
apiVersion: v1
kind: Pod
metadata:
name: pod1
labels:
tier: frontend
spec:
containers:
- name: hello1
image: gcr.io/google-samples/hello-app:2.0
---
apiVersion: v1
kind: Pod
metadata:
name: pod2
labels:
tier: frontend
spec:
containers:
- name: hello2
image: gcr.io/google-samples/hello-app:1.0
由于这些 Pod 没有控制器(Controller,或其他对象)作为其属主引用, 并且其标签与 frontend ReplicaSet 的选择算符匹配,它们会立即被该 ReplicaSet 获取。
假定你在 frontend ReplicaSet 已经被部署之后创建 Pod,并且你已经在 ReplicaSet 中设置了其初始的 Pod 副本数以满足其副本计数需要:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
新的 Pod 会被该 ReplicaSet 获取,并立即被 ReplicaSet 终止, 因为它们的存在会使得 ReplicaSet 中 Pod 个数超出其期望值。
取回 Pod:
kubectl get pods
输出显示新的 Pod 或者已经被终止,或者处于终止过程中:
NAME READY STATUS RESTARTS AGE
frontend-b2zdv 1/1 Running 0 10m
frontend-vcmts 1/1 Running 0 10m
frontend-wtsmm 1/1 Running 0 10m
pod1 0/1 Terminating 0 1s
pod2 0/1 Terminating 0 1s
如果你先行创建 Pod:
kubectl apply -f https://kubernetes.io/examples/pods/pod-rs.yaml
之后再创建 ReplicaSet:
kubectl apply -f https://kubernetes.io/examples/controllers/frontend.yaml
你会看到 ReplicaSet 已经获得了该 Pod,并仅根据其规约创建新的 Pod, 直到新的 Pod 和原来的 Pod 的总数达到其预期个数。 这时取回 Pod 列表:
kubectl get pods
将会生成下面的输出:
NAME READY STATUS RESTARTS AGE
frontend-hmmj2 1/1 Running 0 9s
pod1 1/1 Running 0 36s
pod2 1/1 Running 0 36s
采用这种方式,一个 ReplicaSet 中可以包含异质的 Pod 集合。
编写 ReplicaSet 的清单
与所有其他 Kubernetes API 对象一样,ReplicaSet 也需要 apiVersion
、kind
、和 metadata
字段。
对于 ReplicaSet 而言,其 kind
始终是 ReplicaSet。
当控制平面为 ReplicaSet 创建新的 Pod 时,ReplicaSet
的 .metadata.name
是命名这些 Pod 的部分基础。ReplicaSet 的名称必须是一个合法的
DNS 子域值,
但这可能对 Pod 的主机名产生意外的结果。为获得最佳兼容性,名称应遵循更严格的
DNS 标签规则。
ReplicaSet 也需要
.spec
部分。
Pod 模板
.spec.template
是一个 Pod 模板,
要求设置标签。在 frontend.yaml
示例中,我们指定了标签 tier: frontend
。
注意不要将标签与其他控制器的选择算符重叠,否则那些控制器会尝试收养此 Pod。
对于模板的重启策略
字段,.spec.template.spec.restartPolicy
,唯一允许的取值是 Always
,这也是默认值.
Pod 选择算符
.spec.selector
字段是一个标签选择算符。
如前文中所讨论的,这些是用来标识要被获取的 Pod
的标签。在签名的 frontend.yaml
示例中,选择算符为:
matchLabels:
tier: frontend
在 ReplicaSet 中,.spec.template.metadata.labels
的值必须与 spec.selector
值相匹配,否则该配置会被 API 拒绝。
说明:
对于设置了相同的 .spec.selector
,但
.spec.template.metadata.labels
和 .spec.template.spec
字段不同的两个
ReplicaSet 而言,每个 ReplicaSet 都会忽略被另一个 ReplicaSet 所创建的 Pod。
Replicas
你可以通过设置 .spec.replicas
来指定要同时运行的 Pod 个数。
ReplicaSet 创建、删除 Pod 以与此值匹配。
如果你没有指定 .spec.replicas
,那么默认值为 1。