标签

Kubernetes

Kubernetes(通常缩写为 K8s)是一个开源的容器编排平台,用于自动化容器应用的部署、扩展和管理。它最初是由 Google 设计并开发,并于 2014 年开源,现在由 Cloud Native Computing Foundation(CNCF)管理。

Kubernetes
服务端5月31日 16:34
Kubernetes Pod 是什么?生命周期和重启策略怎么理解?Pod 是 Kubernetes 里最小的可调度单元,不是容器本身。一个 Pod 可以包含一个或多个容器,这些容器共享网络命名空间、存储卷和生命周期,被调度到同一个节点上运行。大多数业务场景是一个 Pod 一个主容器;只有当多个容器必须紧密协作、共享 localhost 或共享文件时,才适合放进同一个 Pod。理解 Pod 的重点是:它是一次运行实例,天然会被替换,不应该把它当成固定机器来使用。 ## Pod 和容器是什么关系 容器负责运行进程,Pod 负责把一组容器包装成 Kubernetes 能调度和管理的对象。同一个 Pod 内的容器共享同一个 IP,可以通过 `localhost` 通信,也可以挂载同一个 volume 交换文件。取舍是协作方便,但扩缩容粒度也被绑在一起:一个 Sidecar 占用资源过高,会影响主容器;主容器需要扩容时,Sidecar 也会跟着复制。不要把数据库、后端、前端这类生命周期不同的组件塞进一个 Pod。 ```yaml apiVersion: v1 kind: Pod metadata: name: web labels: app: web spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80 resources: requests: cpu: 100m memory: 128Mi limits: memory: 256Mi ``` ## Pod 生命周期有哪些阶段 Pod 常见 phase 有 Pending、Running、Succeeded、Failed 和 Unknown。Pending 表示对象已经创建,但还没成功运行,原因可能是调度失败、镜像拉取慢、PVC 没绑定或 init container 还没完成。Running 只表示 Pod 已经绑定节点并且至少一个容器在运行,不等于业务已可接流量。Succeeded 和 Failed 常见于 Job,Unknown 多和节点失联或 kubelet 状态无法上报有关。 ```bash kubectl get pod web -o wide kubectl describe pod web kubectl get events --sort-by=.metadata.creationTimestamp ``` ## 重启策略怎么选 Pod 的 `restartPolicy` 有 Always、OnFailure、Never。Deployment、DaemonSet、StatefulSet 这类长期服务通常只能用 Always,因为控制器期望服务持续运行。Job 常用 OnFailure,失败时让容器重跑,成功退出就不再重启。Never 适合一次性调试或希望失败状态被保留下来的任务。边界是 restartPolicy 只管 Pod 内容器重启,不等于控制器是否会重新创建 Pod;Deployment 即使容器一直崩,ReplicaSet 仍会努力维持副本数。 ```yaml apiVersion: batch/v1 kind: Job metadata: name: data-migrate spec: template: spec: restartPolicy: OnFailure containers: - name: migrate image: busybox command: ["sh", "-c", "./migrate.sh"] backoffLimit: 3 ``` ## 探针和生命周期钩子别混淆 livenessProbe 用来判断容器是否需要重启,readinessProbe 用来判断 Pod 是否可以加入 Service 后端,startupProbe 用来保护启动慢的应用。常见踩坑是把 readiness 写得太宽,应用还没连上数据库就接流量;或者把 liveness 写得太激进,启动高峰时被反复杀掉。生命周期钩子如 preStop 更适合优雅下线,让应用先停止接新请求,再等待连接处理完。 ```yaml readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 30 periodSeconds: 10 ``` ## 为什么不建议直接管理裸 Pod 裸 Pod 删除后不会自动恢复,节点故障时也缺少更高层控制器帮你维持副本。生产服务通常应该用 Deployment、StatefulSet、DaemonSet 或 Job 创建 Pod。Deployment 适合无状态服务,StatefulSet 适合有稳定身份和存储的服务,DaemonSet 适合每个节点一个代理,Job 适合一次性任务。直接写 Pod YAML 可以用于学习和临时排查,但不适合作为长期交付方式。 ## 追问 ### Pod 为什么不是直接等于容器? Kubernetes 需要一个比容器更高的抽象来表达共享网络、共享卷、调度和生命周期。Pod 内多个容器可以通过 localhost 通信,这对 Sidecar、日志代理、服务网格代理很有用。取舍是这些容器会被一起调度、一起扩缩容,不能独立水平扩展。只有强协作关系才放同一个 Pod,否则应该拆成不同工作负载。 ### Pending 一定是镜像拉不下来吗? 不一定。Pending 可能是调度阶段就失败了,比如资源 request 太高、节点污点没有容忍、亲和性规则过硬、PVC 未绑定,也可能是镜像拉取或 init container 阶段卡住。最可靠的方式是看 `kubectl describe pod` 里的 Events。踩坑点是只看 Pod phase 会误导判断,真正原因通常写在 Warning 事件里。 ### Running 是否代表服务已经可用? 不代表。Running 只说明容器层面已启动,业务可能还在加载配置、连接数据库或预热缓存。Service 是否转发流量主要看 readinessProbe 和 EndpointSlice,而不是只看 Pod phase。边界是没配置 readinessProbe 时,Kubernetes 可能过早把 Pod 加入后端。滚动发布中这会导致短暂 502 或请求超时。 ### Always、OnFailure、Never 应该怎么选? 长期服务用 Always,失败了就重启,配合 Deployment 等控制器维持副本。批处理任务通常用 OnFailure,让非零退出码触发重试,成功退出后保持完成状态。Never 适合调试或希望失败现场保留下来的任务。不要在 Deployment 里幻想用 OnFailure 管服务生命周期,这不符合控制器约束,也会让行为变得难以预测。 ### Pod 被删除后数据会不会还在? 要看数据放在哪里。容器文件系统和 emptyDir 通常会随 Pod 消失,PVC 挂载的持久卷则可以跨 Pod 保留。取舍是持久化能保护数据,但也引入存储绑定、访问模式、扩容和回收策略问题。生产里不要把重要数据写在容器本地路径,除非明确接受 Pod 重建后数据丢失。
服务端5月31日 16:34
Kubernetes Service 有什么作用?ClusterIP、NodePort 和 LoadBalancer 怎么选?Kubernetes Service 的作用,是给一组会变化的 Pod 提供一个稳定访问入口。Pod IP 会随着重建、扩缩容、滚动发布而变化,客户端如果直接访问 Pod,很快就会遇到地址失效和负载不均的问题。Service 用 selector 找到后端 Pod,通过 EndpointSlice 记录真实后端,再由 kube-proxy 或 CNI 数据面把流量转发过去。理解 Service 的关键不是背类型,而是知道不同类型解决的是“集群内访问、节点端口暴露、云负载均衡、外部域名映射”这几类问题。 ## Service 如何找到后端 Pod 最常见的 Service 通过 selector 匹配 Pod 标签。只要 Pod 上有 `app: web`,它就会被加入 Service 对应的 EndpointSlice。Deployment 滚动更新时,新旧 Pod 会动态进出后端列表,Service 的 ClusterIP 和 DNS 名称保持不变。踩坑点是 selector 写错时 Service 仍然存在,但没有后端,访问表现通常是连接失败或超时。 ```yaml apiVersion: v1 kind: Service metadata: name: web spec: type: ClusterIP selector: app: web ports: - name: http port: 80 targetPort: 8080 --- apiVersion: apps/v1 kind: Deployment metadata: name: web spec: replicas: 3 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - name: web image: nginx ports: - containerPort: 8080 ``` ```bash kubectl get svc web kubectl get endpointslice -l kubernetes.io/service-name=web kubectl describe svc web ``` ## 四种 Service 类型怎么选 ClusterIP 是默认类型,只能在集群内部访问,适合微服务之间互调、内部数据库代理、队列服务等。它安全、简单,也最常用。边界是集群外不能直接访问,除非配合 Ingress、Gateway、端口转发或 VPN。 NodePort 会在每个节点上打开一个端口,外部可以通过 `NodeIP:NodePort` 访问。它适合临时测试、裸金属环境接入外部负载均衡器,默认端口范围通常是 30000-32767。取舍是暴露面更大,而且节点 IP 变化、端口冲突、安全组放行都要自己管,生产 HTTP 服务一般不直接把 NodePort 当最终入口。 LoadBalancer 会请求云厂商创建外部负载均衡器,再把流量转到 Service 后端。它适合云上对外暴露 TCP/UDP 服务,使用体验最好。边界是依赖 cloud-controller-manager 和云平台权限,裸金属集群默认不会自动得到外部 LB,通常要配 MetalLB 或云厂商插件。 ExternalName 不代理流量,只返回一个 CNAME,把集群内服务名映射到外部 DNS 名称。它适合把外部数据库、第三方 API 用统一的集群内域名表达出来。踩坑点是它没有 ClusterIP,也没有端口转发和健康检查能力,排障时不要去找 Endpoint。 ```yaml apiVersion: v1 kind: Service metadata: name: external-db spec: type: ExternalName externalName: db.example.com ``` ## kube-proxy 在中间做什么 kube-proxy 监听 Service 和 EndpointSlice 变化,在节点上维护转发规则。iptables 模式简单稳定,但规则很多时更新成本会上升;IPVS 模式适合更大规模,支持更丰富的负载均衡算法。现在不少 CNI 也会用 eBPF 接管 Service 数据面,这时 kube-proxy 可能被替代。边界要看集群实现,不能只凭“Service 不通”就断定 kube-proxy 有问题。 ## Headless Service 适合什么场景 Headless Service 设置 `clusterIP: None`,不会分配虚拟 IP,而是让 DNS 直接返回后端 Pod 地址。它常用于 StatefulSet,比如数据库、消息队列这类需要稳定 Pod 身份的应用。取舍是客户端要能处理多个后端地址和连接策略,不能再完全依赖 Service 做统一负载均衡。 ```yaml apiVersion: v1 kind: Service metadata: name: mysql spec: clusterIP: None selector: app: mysql ports: - port: 3306 ``` ## 追问 ### ClusterIP、NodePort、LoadBalancer 的关系是什么? LoadBalancer 通常会包含 NodePort,NodePort 又建立在 ClusterIP 之上,所以它们不是完全割裂的三套机制。ClusterIP 解决集群内稳定访问,NodePort 把入口扩到节点端口,LoadBalancer 再借助云厂商或外部 LB 提供公网或内网入口。取舍是越往外暴露,运维边界越大,安全组、证书、源地址保留和费用都要考虑。内部服务不要为了“方便”直接用 LoadBalancer。 ### Service selector 写错会发生什么? Service 对象会创建成功,DNS 也可能正常解析,但 EndpointSlice 为空。客户端访问时通常表现为连接被拒绝、超时或没有可用后端。排查时先执行 `kubectl get endpointslice -l kubernetes.io/service-name=<svc>`,再核对 Pod 标签和 Service selector。这个坑很常见,因为 YAML 校验不会知道你的业务标签是否写对。 ### 什么时候用 Ingress,而不是直接用 LoadBalancer? 如果是 HTTP/HTTPS,多服务共享域名、路径路由、TLS 证书和灰度规则,用 Ingress 或 Gateway API 通常更合适。每个 Service 都建一个 LoadBalancer 简单直接,但成本高、入口分散,也不好统一做证书和访问控制。边界是非 HTTP 协议不一定适合 Ingress,需要看控制器是否支持 TCP/UDP 转发。生产里常见做法是一个 Ingress Controller 前面挂一个 LoadBalancer。 ### sessionAffinity 能解决所有会话保持问题吗? 不能。`sessionAffinity: ClientIP` 只能按客户端 IP 做相对简单的粘性会话,NAT、代理和移动网络会让多个用户看起来来自同一个 IP。它也不理解应用层登录态,Pod 重启后会话仍可能丢失。更稳的做法是把会话放到 Redis、数据库或外部状态存储里。Service 层会话保持可以作为补充,不应该成为唯一依赖。 ### Service 不通时优先排查哪几步? 先查 Service 是否有 ClusterIP 和端口,再查 EndpointSlice 是否有后端地址。然后进同命名空间 Pod 里用 `curl` 或 `nc` 测 Service DNS、ClusterIP 和 Pod IP,区分是 DNS、Service 转发还是应用端口问题。NodePort 或 LoadBalancer 不通时,还要检查节点安全组、云负载均衡器健康检查和 externalTrafficPolicy。不要只看 Service YAML,真正的线索通常在 EndpointSlice、事件和后端 Pod readiness。
服务端5月31日 16:34
Kubernetes 控制平面由哪些组件组成?它们如何协同工作?Kubernetes 控制平面是集群的决策层,负责接收请求、保存状态、做调度决策,并不断把实际状态拉回到期望状态。它通常由 kube-apiserver、etcd、kube-scheduler、kube-controller-manager 和 cloud-controller-manager 组成。简单说,API Server 是入口,etcd 是账本,Scheduler 负责把 Pod 放到合适节点,Controller Manager 负责持续纠偏,Cloud Controller Manager 负责和云厂商资源打交道。真正排障时,不要把它们看成一组静态组件,而要看一次资源变更如何流过这些组件。 ## 控制平面的核心组件 ### kube-apiserver kube-apiserver 是所有请求的统一入口,kubectl、控制器、调度器、Webhook 和外部系统都通过它读写集群资源。它负责认证、授权、准入控制、对象校验和 API 聚合,最后再把合法状态写入 etcd。API Server 本身是无状态组件,所以可以部署多个副本放在负载均衡器后面。边界是:它不直接创建容器,也不负责调度,只负责让状态变更有统一入口。 ```bash kubectl get --raw='/readyz?verbose' kubectl get pods -n kube-system -l component=kube-apiserver ``` ### etcd etcd 保存 Kubernetes 的全部关键状态,包括 Pod、Deployment、Service、Secret、ConfigMap、Node 状态和 Lease。它基于 Raft 保证一致性,常见生产部署是 3 或 5 个成员。取舍很明确:etcd 强一致带来可靠状态,但对磁盘延迟、网络抖动非常敏感。踩坑最多的是只备份了业务数据,忘了备份 etcd;一旦控制平面故障,集群对象就很难恢复。 ```bash ETCDCTL_API=3 etcdctl snapshot save /backup/etcd.db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key ``` ### kube-scheduler kube-scheduler 监听还没有绑定节点的 Pod,先过滤掉不满足资源、亲和性、污点容忍、端口、卷拓扑等条件的节点,再给剩余节点打分,最后把绑定结果写回 API Server。它不会启动 Pod,真正启动容器的是工作节点上的 kubelet。一个常见误区是以为 Pod Pending 一定是调度器坏了,实际更多是 CPU/内存 request 太高、PVC 未绑定、节点污点没容忍或亲和性规则写得过硬。 ### kube-controller-manager kube-controller-manager 运行一组控制器,例如 Deployment、ReplicaSet、Node、Job、CronJob、EndpointSlice、ServiceAccount 等控制器。控制器的模式是 watch 资源变化,比较期望状态和实际状态,然后发起下一步变更。比如 Deployment 期望 3 个副本,实际只有 2 个,ReplicaSet 控制器会补一个 Pod;Node 长时间失联,Node Controller 会更新状态并触发后续驱逐。它的价值在“持续调和”,不是执行一次就结束。 ### cloud-controller-manager cloud-controller-manager 把云厂商相关逻辑从 Kubernetes 核心里拆出来,负责云节点生命周期、LoadBalancer、路由和云盘等资源。自建裸金属集群可能没有它,云上集群通常离不开它。边界要分清:Service type=LoadBalancer 创建不出外部负载均衡时,问题经常不在 kube-proxy,而在云控制器权限、配额、子网标签或安全组配置。 ```yaml apiVersion: v1 kind: Service metadata: name: web spec: type: LoadBalancer selector: app: web ports: - port: 80 targetPort: 8080 ``` ## 一次创建 Pod 会发生什么 用户执行 `kubectl apply` 后,请求先到 API Server,经过认证、授权和准入控制,再写入 etcd。Scheduler 看到这个 Pod 没有 `spec.nodeName`,开始选择节点并写入绑定结果。目标节点上的 kubelet watch 到属于自己的 Pod 后,通过容器运行时拉镜像、创建容器、挂载卷并上报状态。控制器同时观察这些状态,如果副本数不足、节点异常或对象被删除,就继续发起新的调和动作。 ```bash kubectl apply -f pod.yaml kubectl get pod demo -o wide kubectl describe pod demo kubectl get events --sort-by=.metadata.creationTimestamp ``` ## 追问 ### API Server 为什么要设计成无状态? 无状态的好处是可以水平扩展,多个 API Server 实例后面挂一个负载均衡器即可提升可用性。状态统一放在 etcd,避免每个 API Server 各自保存一份数据导致一致性问题。取舍是 API Server 对 etcd 的依赖非常强,etcd 慢了,整个集群的读写都会跟着慢。生产环境里经常把 API Server 扩了很多副本,却忽略了 etcd 磁盘延迟,这类扩容收益很有限。 ### etcd 为什么通常部署奇数个节点? etcd 使用 Raft,多数派可用才能提交写入,所以 3 节点能容忍 1 个故障,5 节点能容忍 2 个故障。偶数节点并不会提高多数派容错能力,比如 4 节点仍只能容忍 1 个故障,反而增加同步成本。边界是成员越多写入延迟越高,不是越多越安全。小中型集群用 3 个 etcd 成员通常比盲目堆到 7 个更稳。 ### Scheduler 和 Controller Manager 都在“控制状态”,区别是什么? Scheduler 只解决“这个未调度 Pod 放到哪个 Node”这个决策问题,核心输出是绑定关系。Controller Manager 负责更广的持续调和,比如副本数、节点状态、EndpointSlice、Job 完成情况等。踩坑点是排查 Pending Pod 时先看调度事件,排查副本补不齐时再看控制器和 ReplicaSet 事件。把两者混在一起,容易误判问题方向。 ### 控制平面高可用是不是只要多部署几个 master? 不是。高可用至少要考虑 API Server 多副本、etcd 多成员、Controller Manager 和 Scheduler 的 leader election、前置负载均衡器以及证书和备份。只加 master 节点但 etcd 仍是单点,故障时集群状态照样不可写。另一个边界是多副本不等于无限可用,错误的准入 Webhook、过期证书或慢 etcd 仍可能让整个控制面卡住。 ### 控制平面异常时应该先看什么? 先看 API Server 的 readyz、etcd 健康和 kube-system 里控制平面 Pod 日志,再看事件和证书有效期。不要一上来重启所有组件,尤其是 etcd 抖动时,频繁重启会放大选主和恢复时间。命令层面可以从 `kubectl get --raw=/readyz?verbose`、`kubectl get componentstatuses`(旧集群可用)和 `journalctl -u kubelet` 开始。能访问节点但访问不了 API Server 时,还要检查负载均衡器、6443 端口和控制平面证书。
服务端5月31日 16:34
Kubernetes 工作节点包含哪些组件?各自负责什么?Kubernetes 工作节点是实际运行 Pod 的机器,核心组件通常包括 kubelet、容器运行时、kube-proxy,再加上 CNI、CSI、日志和监控代理等配套组件。控制平面负责“决定应该是什么状态”,工作节点负责“把这个状态跑出来并持续汇报”。如果一个节点 NotReady,排查也通常围绕这几件事展开:kubelet 有没有连上 API Server,容器运行时能不能创建容器,网络插件是否正常,磁盘、内存、PID 是否触发压力状态。 ## kubelet 负责什么? kubelet 是节点上的主代理,它从 API Server 获取分配到本节点的 Pod 规范,然后通过 CRI 调用 containerd 或 CRI-O 创建容器。它还负责挂载卷、执行探针、上报 Node 和 Pod 状态,并在容器异常退出时根据 restartPolicy 做处理。可以用下面的命令看 kubelet 和节点状态: ```bash kubectl describe node <node-name> journalctl -u kubelet -n 100 --no-pager crictl ps -a crictl logs <container-id> ``` kubelet 的边界也要说清楚:它不负责为 Pod 选择节点,那是 scheduler 的工作;它也不直接实现 Service 负载均衡,那主要由 kube-proxy 或 eBPF 数据面完成。很多人看到 Pod 起不来就重启 kubelet,但如果根因是镜像拉取失败、PVC 挂载失败或 CNI 没准备好,重启只会掩盖现场。 ## 容器运行时和 kube-proxy 各做什么? 容器运行时负责真正创建容器、拉镜像、管理容器生命周期。现在主流选择是 containerd 或 CRI-O,Docker 的 dockershim 在 Kubernetes 1.24 之后已经移除;如果还用 Docker,也通常是通过额外适配层接入。kube-proxy 负责根据 Service 和 EndpointSlice 维护节点上的转发规则,常见模式有 iptables 和 IPVS,有些集群会用 Cilium 等 eBPF 方案替代它的部分职责。 ```yaml apiVersion: kubeproxy.config.k8s.io/v1alpha1 kind: KubeProxyConfiguration mode: "ipvs" ipvs: scheduler: "rr" ``` iptables 模式简单、兼容性好;IPVS 在大规模 Service 场景下性能和算法选择更好,但需要内核模块支持。取舍不是“哪个更高级”,而是集群规模、内核能力、运维熟悉度和网络插件支持是否匹配。 ## 追问 ### Node Ready 由什么决定? Node Ready 主要由 kubelet 上报,背后包含 kubelet 自身健康、容器运行时可用性、网络是否就绪以及节点压力状态等信息。你可以用 `kubectl describe node` 看 Conditions,包括 Ready、MemoryPressure、DiskPressure、PIDPressure、NetworkUnavailable。边界是 Ready=True 只表示节点可参与调度,不代表节点上的每个 Pod 都健康;Pod 是否对外服务还要看 readinessProbe 和 Service 后端。生产排障时要把 Node 条件、Pod 事件和容器日志放在一起看。 ### cordon 和 drain 有什么区别? `cordon` 只是把节点标记为不可调度,新 Pod 不会再放到这个节点,已有 Pod 不受影响。`drain` 会驱逐普通 Pod,常用于节点维护或下线: ```bash kubectl cordon <node-name> kubectl drain <node-name> --ignore-daemonsets --delete-emptydir-data kubectl uncordon <node-name> ``` 取舍点在于你是否要立刻迁走工作负载;只做内核参数检查可能 cordon 就够了,重启机器或换盘通常需要 drain。踩坑是 DaemonSet 不会被 drain 删除,带本地 emptyDir 的 Pod 可能丢临时数据,所以命令参数要看清楚。 ### kube-proxy 出问题会表现成什么? 典型表现是 Pod 本身正常,但通过 Service 访问失败,或者只有部分节点访问 Service 异常。可以检查 kube-proxy 日志、Service 后端和节点转发规则: ```bash kubectl get svc,endpointslices -A kubectl logs -n kube-system -l k8s-app=kube-proxy --tail=100 iptables-save | grep KUBE-SVC | head ``` 如果集群使用 IPVS,还要看 `ipvsadm -Ln`。边界是 DNS 解析失败不一定是 kube-proxy,CoreDNS、NetworkPolicy、CNI 路由同样可能导致服务不可达。不要一上来就删除 kube-proxy Pod,先确认是所有节点异常还是单节点异常。 ### 节点压力状态会怎样影响 Pod? MemoryPressure、DiskPressure、PIDPressure 触发后,节点可能拒绝新 Pod 调度,严重时 kubelet 会按 QoS 和驱逐阈值清理 Pod。BestEffort Pod 最容易被驱逐,Guaranteed Pod 相对更稳,但也不是绝对免死。实际取舍是给关键服务设置合理 requests/limits,同时给系统和 kubelet 预留资源。一个常见坑是日志撑爆磁盘导致 DiskPressure,应用没改一行代码,却出现镜像拉取失败、容器创建失败和 Pod 被驱逐。 ### 工作节点需要哪些日常巡检? 至少要巡检 kubelet、containerd、CNI、磁盘、内存、节点证书和系统时间。常用命令包括: ```bash kubectl get nodes -o wide kubectl top nodes systemctl status kubelet containerd journalctl -u containerd -n 50 --no-pager ``` 巡检的边界是不要只看 Kubernetes 对象,底层内核、磁盘 inode、conntrack 表、时间同步都会影响节点稳定性。对于生产集群,建议把节点 NotReady、DiskPressure、kubelet 证书过期、容器运行时重启次数纳入告警。节点问题越早在系统层发现,越不容易演变成业务层故障。
服务端5月31日 16:34
Kubernetes PV 和 PVC 有什么区别?如何管理持久化存储?PV 和 PVC 的区别可以用一句话概括:PV 是集群里真实或即将创建的存储资源,PVC 是业务在命名空间里提出的存储申请。Pod 不直接挑磁盘,而是引用 PVC;控制器再根据容量、访问模式、StorageClass、selector 等条件,把 PVC 绑定到合适的 PV。这样做的好处是把“应用要多大空间”和“底层用 EBS、NFS 还是本地盘”解耦,但代价是排障时要同时看 PVC、PV、StorageClass、Pod 事件,不能只盯一个对象。 ## PV、PVC 和 StorageClass 怎么配合? 动态供给是最常见的方式:开发者创建 PVC,指定 StorageClass,存储插件自动创建 PV。下面是一个比较典型的写法: ```yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: fast-ssd provisioner: ebs.csi.aws.com reclaimPolicy: Delete volumeBindingMode: WaitForFirstConsumer allowVolumeExpansion: true --- apiVersion: v1 kind: PersistentVolumeClaim metadata: name: data-pvc spec: accessModes: - ReadWriteOnce storageClassName: fast-ssd resources: requests: storage: 20Gi ``` `volumeBindingMode: WaitForFirstConsumer` 很关键,它会等 Pod 出现后再结合调度结果创建或绑定卷,避免卷先在 A 可用区创建,Pod 却被调度到 B 可用区。`reclaimPolicy: Delete` 适合临时环境,PVC 删除后底层卷也会被删;生产数据库通常更倾向 Retain,防止误删 PVC 直接丢数据。这个取舍没有绝对答案,关键看数据是否可再生,以及团队有没有成熟的备份和恢复流程。 ## Pod 怎么挂载 PVC? Pod 里只引用 PVC 名称,不关心底层 PV 是谁: ```yaml apiVersion: v1 kind: Pod metadata: name: app spec: containers: - name: app image: nginx:1.25 volumeMounts: - name: data mountPath: /usr/share/nginx/html volumes: - name: data persistentVolumeClaim: claimName: data-pvc ``` 如果是 Deployment 多副本,要特别注意访问模式。`ReadWriteOnce` 通常只能被一个节点读写挂载,多个副本被调度到不同节点时可能挂载失败;需要多节点同时读写时,应选择支持 `ReadWriteMany` 的后端,比如 NFS、CephFS 或云厂商文件存储。别把 PVC 当成“共享目录万能解法”,有状态服务还要考虑锁、并发写入、数据一致性和备份窗口。 ## 追问 ### 静态 PV 和动态 PV 怎么选? 动态 PV 适合大多数云上和标准化环境,开发者只需要申请容量,底层卷由 CSI 插件创建,运维成本低。静态 PV 适合已有存储、特殊挂载参数、本地盘或者迁移场景,管理员先创建 PV,再让 PVC 通过 storageClassName、容量和 selector 去绑定。取舍点在于灵活性和自动化:动态供给快,但受 StorageClass 能力限制;静态供给可控,但人工维护更容易出错。生产里常见做法是通用应用走动态,数据库迁移或本地高性能盘走静态。 ### RWO、RWX、RWOP 有什么区别? RWO 是单节点读写,不等于单 Pod;同一节点上的多个 Pod 在某些场景下可能同时使用它。RWX 是多节点读写,适合共享文件,但底层必须真的支持多写,不是 YAML 写了就生效。RWOP 是单 Pod 读写,边界更严格,适合希望从 Kubernetes 层面避免两个 Pod 同时挂同一卷的场景。踩坑点是访问模式表达的是挂载能力,不保证你的应用层并发写入一定安全。 ### 为什么 PVC 一直 Pending? 先看 PVC 事件和 StorageClass: ```bash kubectl describe pvc data-pvc kubectl get storageclass kubectl describe storageclass fast-ssd kubectl get pv ``` 常见原因包括 storageClassName 写错、没有默认 StorageClass、CSI 插件异常、容量或访问模式没有匹配 PV。使用 `WaitForFirstConsumer` 时,PVC 在 Pod 创建前 Pending 是正常的,因为它要等调度器确定节点和可用区。真正的坑是 Pod 也因为其他原因 Pending,导致你误以为是存储问题;这时要同时 `kubectl describe pod` 看调度事件。 ### reclaimPolicy 选 Delete 还是 Retain? Delete 的优点是干净,PVC 删除后底层存储自动释放,测试环境和可再生数据很适合。Retain 的优点是安全,误删 PVC 时底层数据还在,但需要人工清理 PV 的 claimRef、回收磁盘或重新导入。生产数据库、用户上传文件、审计数据更适合 Retain,再配合备份策略;缓存、临时索引和 CI 产物通常可以 Delete。边界是 Retain 不等于备份,它只是“不自动删”,磁盘损坏、误写入和勒索加密仍然需要快照或异地备份解决。 ### 扩容 PVC 有哪些限制? 首先 StorageClass 必须设置 `allowVolumeExpansion: true`,底层 CSI 也要支持扩容。扩容命令很简单: ```bash kubectl patch pvc data-pvc -p '{"spec":{"resources":{"requests":{"storage":"50Gi"}}}}' kubectl describe pvc data-pvc ``` 但缩容通常不支持,文件系统在线扩容也可能依赖节点插件和文件系统类型。踩坑最多的是只看到 PVC 容量变大,却没确认容器内文件系统是否完成扩展;可以进入容器用 `df -h` 验证。对数据库类应用,扩容前仍建议做快照,因为存储层操作成功不代表应用层一定能平滑消化。
服务端5月31日 16:34
Kubernetes 亲和性和反亲和性如何控制 Pod 调度?Kubernetes 亲和性和反亲和性,本质上是在回答“Pod 应该靠近谁、远离谁、只能去哪里”。nodeAffinity 关心节点标签,比如把需要 SSD 的服务放到 `disk=ssd` 的节点;podAffinity 关心已有 Pod,比如把网关和同可用区缓存放近一点;podAntiAffinity 则常用于把同一应用副本打散,避免一个节点故障带走所有实例。真正落地时不要把所有规则都写成 required,硬约束越多,调度失败的概率越高;通常是“必须满足资源和隔离,性能偏好用 preferred”。 ## 怎么用节点亲和性限制 Pod 去指定节点? 节点亲和性比 `nodeSelector` 更灵活,支持 In、NotIn、Exists、Gt、Lt 等操作符。比如只允许 Pod 调度到带有 `nodepool=ssd` 的节点,并优先选择同区域节点,可以这样写: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: api spec: replicas: 2 selector: matchLabels: app: api template: metadata: labels: app: api spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: nodepool operator: In values: ["ssd"] preferredDuringSchedulingIgnoredDuringExecution: - weight: 80 preference: matchExpressions: - key: topology.kubernetes.io/zone operator: In values: ["cn-east-1a"] containers: - name: api image: nginx:1.25 ``` 这里的 `requiredDuringSchedulingIgnoredDuringExecution` 是硬门槛,不满足就 Pending;`preferredDuringSchedulingIgnoredDuringExecution` 是加分项,不满足仍可调度。后半段的 `IgnoredDuringExecution` 很容易被误解:它表示 Pod 调度成功后,如果节点标签后来被改掉,Kubernetes 不会主动驱逐这个 Pod。这个边界很重要,亲和性解决的是“调度时放哪里”,不是“运行中持续纠偏”。 如果要给节点补标签,先用命令确认标签是否稳定存在: ```bash kubectl label node node-1 nodepool=ssd kubectl get nodes -L nodepool,topology.kubernetes.io/zone ``` 不要把临时标签当成长期调度依据。比如临时给节点打 `debug=true` 后忘记清理,后续 Pod 可能被错误吸到这批节点上。更稳的做法是把节点池、可用区、实例规格这类由平台维护的标签作为调度依据,业务自己维护的标签则要有变更流程。 ## 追问 ### required 和 preferred 应该怎么取舍? required 适合合规、安全、硬件能力这类不能妥协的条件,比如 GPU 任务必须上 GPU 节点,数据库实例必须上本地盘节点。preferred 适合性能优化和成本优化,比如优先同可用区、优先 SSD、优先空闲节点,但没抢到也允许服务先跑起来。踩坑最多的是把高可用诉求也全写成 required,节点数量一少、标签不齐或滚动升级时,Pod 会长时间 Pending。实际项目里建议先用 preferred 观察调度结果,再把确实不能退让的条件收紧成 required。 ### Pod 亲和性和反亲和性常用在哪些场景? Pod 亲和性适合把强依赖、低延迟通信的组件放近,比如计算服务和本地缓存希望在同一可用区,减少跨区访问延迟。Pod 反亲和性更常见,用来把同一 Deployment 的副本分散到不同节点或不同可用区,降低单点故障影响。边界是它依赖目标 Pod 的 label,如果 label 写错或 selector 太宽,会出现“亲和到不该亲和的 Pod”的情况。生产环境里通常给业务 label 保持稳定,避免把版本号、临时灰度标签拿来做调度依据。 ### topologyKey 选 hostname 还是 zone? `kubernetes.io/hostname` 表示按节点维度分散,适合防止同一个应用的多个副本落在一台机器上。`topology.kubernetes.io/zone` 表示按可用区维度分散,适合云上多可用区容灾,但跨区会带来网络延迟和流量成本。取舍点在于你要防的是“单机故障”还是“可用区故障”。如果副本数小于可用区数量,强制 zone 反亲和可能导致某些副本调度不上,这时 preferred 往往比 required 更稳。 ### 如何排查亲和性导致的 Pending? 先看事件,调度器通常会直接告诉你哪些节点不满足规则: ```bash kubectl describe pod <pod-name> kubectl get nodes --show-labels kubectl describe node <node-name> | grep -A5 Taints ``` 如果事件里出现 `didn't match Pod's node affinity`,说明节点标签和 nodeAffinity 对不上;如果出现 `didn't match pod affinity/anti-affinity rules`,就要检查目标 Pod 的 label 和 topologyKey。另一个常见坑是污点和亲和性叠加:亲和性允许去某节点,但没有 toleration,调度器仍然不会放过去。排查时不要只盯 affinity 字段,资源不足、污点、PVC 绑定模式也可能同时参与过滤。 ### 亲和性需要和污点容忍一起用吗? 需要,但两者解决的问题不同。亲和性是在“吸引”或“偏好”某些节点,污点是节点主动“拒绝”不合适的 Pod,容忍度则是 Pod 表示自己可以接受这个拒绝条件。比如专用 GPU 节点通常会打 taint,只有声明 toleration 且有 GPU nodeAffinity 的任务才能上去。边界是 toleration 不是调度目标,它只代表“允许去”,真正想让 Pod 去哪里仍要靠 nodeSelector、nodeAffinity 或资源请求。
服务端5月27日 19:41
Kubernetes Ingress 是什么?它如何实现外部访问集群内服务?## Kubernetes Ingress 是什么 当你把一个 Web 应用部署到 Kubernetes 集群后,集群外部的用户怎么访问它?最直接的方式是用 Service 的 NodePort 或 LoadBalancer 类型暴露端口,但前者端口范围有限且不安全,后者每个 Service 都要占用一个云厂商的负载均衡器,成本很高。 Ingress 就是解决这个问题的方案。它是一种 API 对象,在集群入口处统一管理 HTTP 和 HTTPS 路由规则,根据域名和路径把流量分发到不同的 Service。你可以把它理解成集群的"前台接待"——所有外部请求先到 Ingress,再由它根据规则转给对应的后端服务。 Ingress 能做的事情包括: - **基于域名的路由**:`api.example.com` 走 A 服务,`web.example.com` 走 B 服务,共享同一个入口 IP - **基于路径的路由**:`example.com/api` 走后端 API 服务,`example.com/app` 走前端服务 - **TLS 终止**:在 Ingress 层处理 HTTPS 握手,后端 Service 只需跑 HTTP,证书管理集中化 - **路径重写**:把 `/v2/api` 重写为 `/api` 再转发给后端 - **负载均衡**:在多个 Pod 副本之间分发请求 ## 一个请求从到达到转发的完整链路 理解 Ingress 工作方式的关键是搞清楚请求的完整路径: 1. 外部客户端发送请求到 `https://api.example.com/users` 2. DNS 将域名解析到 Ingress Controller 暴露的 IP(通常是一个 LoadBalancer Service) 3. 请求到达 Ingress Controller,Controller 检查 TLS 证书完成 HTTPS 握手 4. Controller 根据 Host 头和路径匹配 Ingress 规则,找到对应的 Service 5. Controller 将请求转发给 Service 关联的某个 Pod(通过 Endpoints 列表) 6. Pod 处理请求并返回响应 注意:Ingress 资源本身只是规则定义,真正干活的是 Ingress Controller。没有 Controller,Ingress 规则就是一纸空文。 ## Ingress Controller 选型 Ingress Controller 是 Ingress 功能的实际执行者,它监听集群中 Ingress 资源的变化,动态更新自己的配置(比如 NGINX 的 upstream 配置),然后按照规则转发流量。 ### NGINX Ingress Controller 社区使用最广泛的方案,基于 NGINX/OpenResty 实现。功能成熟、社区活跃、文档齐全,支持限流、认证、CORS、自定义错误页、灰度发布等高级特性。如果你没有特殊需求,选它基本不会出错。 ### Traefik 云原生设计,原生支持自动服务发现和 Let's Encrypt 自动证书申请,配置方式比 NGINX 更直观(支持文件和 CRD 两种 provider)。适合追求配置简洁和自动化的团队。 ### HAProxy Ingress 基于 HAProxy 实现,强项是高性能和丰富的负载均衡算法(加权轮询、最少连接、一致性哈希等)。对性能有极致要求时可以考虑。 ### Istio Gateway 服务网格方案中的入口网关,除了基本路由还支持 mTLS、流量镜像、故障注入、熔断等服务网格能力。如果集群已经用了 Istio,直接用它就够了,不需要再额外部署 Ingress Controller。 ### AWS ALB Ingress Controller 专为 AWS 设计,直接创建 ALB 资源作为入口,和 AWS 的 WAF、ACM 证书、CloudWatch 等服务深度集成。纯 AWS 环境下的首选。 ## Ingress 资源配置实战 ### 最基本的路由规则 下面这个示例把 `example.com/app1` 和 `example.com/app2` 分别路由到两个不同的 Service: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: simple-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: rules: - host: example.com http: paths: - path: /app1 pathType: Prefix backend: service: name: app1-service port: number: 80 - path: /app2 pathType: Prefix backend: service: name: app2-service port: number: 80 ``` `pathType` 是 v1 版本必须指定的字段,有三个可选值: - **Exact**:精确匹配,只有路径完全一致才命中。`/app` 只匹配 `/app`,不匹配 `/app/` 或 `/app1` - **Prefix**:前缀匹配,按 `/` 分段进行前缀判断。`/app` 能匹配 `/app`、`/app/`、`/app/user`,但不匹配 `/app1` - **ImplementationSpecific**:由 Controller 自行决定匹配逻辑 ### 配置 HTTPS(TLS 终止) 要启用 HTTPS,需要先在集群中创建 TLS 证书的 Secret,然后在 Ingress 中引用: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: tls-ingress spec: tls: - hosts: - secure.example.com secretName: tls-secret rules: - host: secure.example.com http: paths: - path: / pathType: Prefix backend: service: name: secure-service port: number: 80 ``` Ingress Controller 会在 443 端口处理 TLS 握手,解密后以 HTTP 转发给后端 Service,后端无需关心证书。 ### 默认后端(Default Backend) 当请求没有匹配到任何规则时,Ingress Controller 会把流量发给默认后端。通常是一个返回 404 的简单服务: ```yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: default-backend-ingress spec: defaultBackend: service: name: default-service port: number: 80 rules: - host: example.com http: paths: - path: /api pathType: Prefix backend: service: name: api-service port: number: 80 ``` ## IngressClass:多 Controller 共存的关键 一个集群中可能部署了多个 Ingress Controller(比如 NGINX 处理外部流量,Traefik 处理内部流量)。IngressClass 用来指定一个 Ingress 资源由哪个 Controller 处理: ```yaml apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: name: nginx-external annotations: ingressclass.kubernetes.io/is-default-class: "true" spec: controller: k8s.io/ingress-nginx --- apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress spec: ingressClassName: nginx-external rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: my-service port: number: 80 ``` 标注 `is-default-class: "true"` 的 IngressClass 会被自动分配给没有指定 `ingressClassName` 的 Ingress 资源。 ## 常用 Annotations 配置 Annotations 是 Ingress 的核心扩展机制,不同 Controller 支持不同的注解。以下是 NGINX Ingress Controller 最常用的几个: **路径重写**:把匹配到的路径部分替换后再转发 ```yaml nginx.ingress.kubernetes.io/rewrite-target: /$2 ``` **强制 HTTPS 重定向**:HTTP 请求自动跳转到 HTTPS ```yaml nginx.ingress.kubernetes.io/ssl-redirect: "true" ``` **限流配置**:限制每个客户端的请求频率和并发连接数 ```yaml nginx.ingress.kubernetes.io/limit-rps: "10" nginx.ingress.kubernetes.io/limit-connections: "5" ``` **CORS 跨域**:前后端分离场景经常需要 ```yaml nginx.ingress.kubernetes.io/enable-cors: "true" nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com" ``` **Basic 认证**:简单的访问控制 ```yaml nginx.ingress.kubernetes.io/auth-type: basic nginx.ingress.kubernetes.io/auth-secret: basic-auth ``` **自定义错误页面**:统一展示 404、503 等错误页 ```yaml nginx.ingress.kubernetes.io/custom-http-errors: "404,503" ``` ## Ingress 和 Service、LoadBalancer 的区别 很多初学者容易混淆这三者的关系,这里做个明确对比。 **Ingress vs Service**:Service 是四层(L4)负载均衡,基于端口和 IP 转发 TCP/UDP 流量,不关心请求内容。Ingress 是七层(L7)负载均衡,能识别 HTTP 的 Host 头和 URL 路径,做更精细的路由。Service 是必须的(Ingress 最终还是把流量转给 Service),Ingress 是可选的增强。 **Ingress vs LoadBalancer**:LoadBalancer 类型的 Service 每个都占用一个云厂商负载均衡器,成本高且没有域名路由能力。Ingress 只需要一个 LoadBalancer(给 Ingress Controller 用),然后通过规则复用这个入口给多个 Service 使用。 | 维度 | Ingress | Service (ClusterIP/NodePort) | Service (LoadBalancer) | |------|---------|------|------| | 层级 | L7 | L4 | L4 | | 路由能力 | 域名 + 路径 | 端口 | 端口 | | TLS 终止 | 支持 | 不支持 | 部分支持 | | 成本 | 低(共享入口) | 低 | 高(每 Service 一个 LB) | | 协议 | HTTP/HTTPS | TCP/UDP | TCP/UDP | ## 部署 NGINX Ingress Controller 用 Helm 安装是最快的方式: ```bash # 添加 Helm 仓库 helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update # 安装到独立命名空间 helm install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx \ --create-namespace ``` 安装完成后验证: ```bash kubectl get pods -n ingress-nginx kubectl get svc -n ingress-nginx ``` 如果看到 Service 有一个 EXTERNAL-IP(云环境)或 NodePort(本地环境),说明 Controller 已经就绪。 ## 生产环境的最佳实践 **命名空间隔离**:把 Ingress Controller 部署在独立命名空间(如 `ingress-nginx`),和业务负载分开管理。 **资源限制**:Ingress Controller 是集群流量的咽喉,必须设置合理的 CPU 和内存 requests/limits,避免被其他 Pod 抢占资源。 **监控告警**:重点监控连接数、请求延迟、4xx/5xx 错误率、证书过期时间。Prometheus + Grafana 是主流方案。 **证书管理**:生产环境推荐用 cert-manager 自动签发和续期 Let's Encrypt 证书,避免手动管理证书过期。 **健康检查**:确保后端 Service 配置了正确的 readinessProbe,Ingress Controller 只会把流量发给就绪的 Pod。 **配置备份与版本管理**:Ingress 规则属于基础设施即代码的一部分,应该用 Git 管理 YAML 文件,而不是直接 `kubectl edit`。 **灰度发布**:利用 `nginx.ingress.kubernetes.io/canary` 系列注解实现金丝雀发布,按权重或 Header 把部分流量导向新版本。 ## 常见故障排查 排查 Ingress 问题有一个清晰的思路:从外到内,逐层验证。 **第一层:Ingress 规则是否正确** ```bash kubectl get ingress kubectl describe ingress <ingress-name> ``` 检查 Rules 中的 Host、Path、Backend 是否符合预期,特别注意 `pathType` 是否匹配。 **第二层:Controller 是否正常工作** ```bash kubectl logs -n ingress-nginx <pod-name> --tail=100 ``` 看日志中是否有配置加载错误或后端连接失败的记录。 **第三层:DNS 是否解析正确** ```bash nslookup example.com dig example.com ``` 确认域名指向 Ingress Controller 的外部 IP。 **第四层:后端 Service 和 Pod 是否健康** ```bash kubectl get svc kubectl get endpoints <service-name> kubectl get pods -l app=<your-app> ``` Endpoints 列表为空说明没有 Pod 通过了 readinessProbe,流量无处可去。 **第五层:TLS 证书是否有效** ```bash kubectl get secret tls-secret -o yaml openssl x509 -in <cert-file> -text -noout ``` 检查证书是否过期、域名是否匹配、Secret 是否在正确的命名空间。 按照这个顺序逐层排查,绝大多数 Ingress 问题都能定位到原因。
服务端5月27日 19:40
Kubernetes Deployment 的作用是什么?它如何实现滚动更新和回滚?Kubernetes Deployment 是用来管理无状态应用生命周期的核心控制器。它围绕 ReplicaSet 实现了声明式部署、滚动更新和版本回滚,是日常使用频率最高的 K8s 工作负载类型。 ## Deployment 到底管什么 Deployment 并不直接管理 Pod。它管理的是 ReplicaSet,再由 ReplicaSet 来确保 Pod 的副本数。这种两层结构是理解 Deployment 更新和回滚机制的关键——每次更新 Pod 模板,Deployment 都会创建一个新的 ReplicaSet,逐步把流量从旧 ReplicaSet 迁移到新 ReplicaSet。 一个最小化的 Deployment 定义: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: replicas: 3 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 ports: - containerPort: 80 ``` 注意 `selector.matchLabels` 必须和 `template.metadata.labels` 匹配,否则 Deployment 无法关联到自己的 Pod,这是一个常见的新手错误。 ## 滚动更新是怎么发生的 当你修改了 Deployment 的 Pod 模板(比如换了镜像版本),Kubernetes 不会一次性替换所有 Pod,而是按策略逐步替换。默认使用 RollingUpdate 策略,整个过程依赖两个参数: - **maxUnavailable**:更新过程中最多允许多少个 Pod 处于不可用状态。默认值是 25%,即 3 副本的 Deployment 最多允许 1 个 Pod 不可用。 - **maxSurge**:更新过程中最多允许超出期望副本数多少个 Pod。默认值也是 25%。 以 3 副本为例,默认配置下的滚动更新过程大致如下:Kubernetes 先创建 1 个新 Pod(因为 maxSurge=25%,3 的 25% 向上取整为 1),等新 Pod 就绪后,再终止 1 个旧 Pod,如此循环直到全部替换完成。 这里有一个容易忽略的细节:所谓"就绪",依赖的是 readinessProbe。如果你没有配置 readinessProbe,Kubernetes 只要看到容器启动就认为 Pod ready,这可能导致流量打到还没准备好的新 Pod 上。生产环境中务必配置 readinessProbe。 查看更新状态: ```bash kubectl rollout status deployment/nginx-deployment ``` ## Recreate 策略什么时候用 除了 RollingUpdate,还有一种 Recreate 策略:先杀掉所有旧 Pod,再创建新 Pod。这会带来停机时间,看起来不如 RollingUpdate,但有些场景必须用它: - 应用不支持多版本同时运行(比如数据库 schema 变更后旧代码会报错) - 新旧版本共享的资源无法兼容(比如同一个 ConfigMap 被新旧版本以不同方式解析) 设置方式: ```yaml spec: strategy: type: Recreate ``` 选择策略时问自己一个问题:新旧 Pod 能不能同时对外服务?能就用 RollingUpdate,不能就用 Recreate。 ## 回滚机制的底层逻辑 每次更新 Pod 模板,Deployment 都会创建一个新的 ReplicaSet,旧的 ReplicaSet 不会被删除,而是保留作为回滚的锚点。Kubernetes 用 `revisionHistoryLimit` 控制保留多少个旧 ReplicaSet,默认值是 10。 查看更新历史: ```bash kubectl rollout history deployment/nginx-deployment ``` 回滚到上一版本: ```bash kubectl rollout undo deployment/nginx-deployment ``` 回滚到指定版本: ```bash kubectl rollout undo deployment/nginx-deployment --to-revision=2 ``` 回滚的本质是什么?是把当前 Deployment 的 Pod 模板替换成目标 revision 对应的 ReplicaSet 的 Pod 模板,然后走一遍正常的滚动更新流程。所以回滚不是"魔法还原",它和正向更新走的是同一条路径,同样受 maxUnavailable 和 maxSurge 约束。 一个常见问题:如果你发现更新出错了,想暂停更新怎么办? ```bash kubectl rollout pause deployment/nginx-deployment ``` 暂停后可以做多次修改,确认没问题后再恢复: ```bash kubectl rollout resume deployment/nginx-deployment ``` ## 扩缩容:手动和自动 手动扩缩容: ```bash kubectl scale deployment/nginx-deployment --replicas=5 ``` 自动扩缩容需要 HPA(HorizontalPodAutoscaler)。HPA 根据 CPU、内存或自定义指标自动调整 Deployment 的副本数: ```yaml apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: nginx-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: nginx-deployment minReplicas: 2 maxReplicas: 10 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 50 ``` 需要注意的是,HPA 扩容是立刻生效的,但缩容有一个默认 5 分钟的稳定窗口(`behavior.scaleDown.stabilizationWindowSeconds`),防止指标波动导致副本数来回抖动。 ## Deployment 和其他控制器的选择 面试中常问的一个问题是:什么时候用 Deployment,什么时候用 StatefulSet 或 DaemonSet? **Deployment** 适用于无状态应用——Pod 之间没有差异,任何一个 Pod 都能处理任何请求。Web 服务、API 网关、微服务实例都属于这一类。 **StatefulSet** 适用于有状态应用——每个 Pod 有稳定的网络标识和持久化存储。数据库主从集群、ZooKeeper、Kafka 集群需要用 StatefulSet。 **DaemonSet** 确保每个节点上运行一个 Pod 副本,常用于日志采集、监控 Agent、网络插件等节点级服务。 选错控制器的后果很直接:用 Deployment 跑数据库,Pod 重建后数据丢失;用 StatefulSet 跑无状态 Web 服务,滚动更新变慢且没有收益。 ## 生产环境中的几个注意点 **资源限制必须设置**。没有 requests 和 limits 的 Pod 可能抢占节点资源,导致其他 Pod 被驱逐。至少设置 requests,让调度器能正确决策。 **健康检查不能省**。livenessProbe 检测进程死锁,readinessProbe 控制流量接入。只配 livenessProbe 不配 readinessProbe,是导致滚动更新期间 502 的常见原因。 **不要用 latest 标签**。`image: nginx:latest` 意味着每次拉取可能拿到不同版本,这会让 Deployment 的声明式管理失去意义。用明确的版本号,变更时改 YAML 走正常的更新流程。 **revisionHistoryLimit 不要设成 0**。有些团队为了"清理资源"把它设成 0,结果是无法回滚。旧 ReplicaSet 里的 Pod 都是 0 副本,占用的资源微乎其微,保留回滚能力的收益远大于节省的那点开销。 掌握了 Deployment 的滚动更新机制、回滚原理和与其他控制器的选型逻辑,面试中关于工作负载的大部分问题都能从容应对。
服务端5月27日 18:31
什么是 Kubernetes?它的核心概念和工作原理是什么?## Kubernetes 是什么? Kubernetes(常缩写为 K8s)是一个开源的容器编排平台,用于自动化容器化应用的部署、扩展和运维管理。它最初由 Google 基于内部运行大规模容器的经验(Borg/Omega 系统)设计并开发,于 2014 年开源,随后成为 Cloud Native Computing Foundation(CNCF)的旗舰项目。 简单来说,当你从"在一台机器上跑几个容器"发展到"在几百台机器上跑几千个容器,还要保证服务不中断、能自动扩缩容、出了故障能自愈"时,Kubernetes 就是解决这个问题的工具。 ## 核心概念详解 ### Pod — 最小调度单元 Pod 是 Kubernetes 中最小的可部署单元。一个 Pod 包含一个或多个紧密耦合的容器,它们共享网络命名空间(同一个 IP 地址和端口空间)和存储卷。大多数情况下,一个 Pod 只运行一个容器;多容器 Pod 的典型场景是 Sidecar 模式,比如主容器运行业务逻辑,Sidecar 容器负责日志收集或代理网络请求。 ```yaml apiVersion: v1 kind: Pod metadata: name: my-app spec: containers: - name: app image: my-app:1.0 ports: - containerPort: 8080 ``` ### Node — 工作节点 Node 是集群中实际运行工作负载的机器,可以是物理机或虚拟机。每个 Node 上运行着三个关键组件: - **kubelet**:负责管理本 Node 上 Pod 的生命周期,向控制平面汇报状态 - **kube-proxy**:维护节点上的网络规则,实现 Service 的负载均衡 - **容器运行时**:实际运行容器的软件,如 containerd、CRI-O ### Cluster — 集群 Cluster 由一组 Node 组成,是 Kubernetes 管理的计算资源池。一个集群通常包含多个 Worker Node 和至少一个 Master Node(控制平面)。集群是 Kubernetes 运维的基本单元——所有的应用部署、资源分配、网络策略都在集群范围内定义和管理。 ### Service — 服务发现与负载均衡 Pod 的 IP 地址是临时的——每次 Pod 重建后 IP 都会变化。Service 通过标签选择器(Label Selector)匹配一组 Pod,并为它们提供一个稳定的虚拟 IP(ClusterIP)和 DNS 名称,解决了"如何找到一组随时可能变化的 Pod"这个问题。 常见的 Service 类型: - **ClusterIP**:默认类型,仅在集群内部可访问 - **NodePort**:通过每个 Node 的指定端口暴露服务 - **LoadBalancer**:向云厂商请求外部负载均衡器 ### Deployment — 声明式应用管理 Deployment 是最常用的工作负载控制器,它管理 ReplicaSet,而 ReplicaSet 管理 Pod 副本数量。你只需要声明"我需要 3 个副本运行 my-app:2.0 镜像",Kubernetes 就会自动完成从旧版本到新版本的滚动更新,并且在更新出问题时支持一键回滚。 ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-app spec: replicas: 3 selector: matchLabels: app: my-app template: metadata: labels: app: my-app spec: containers: - name: app image: my-app:2.0 ``` ### ConfigMap 与 Secret — 配置管理 ConfigMap 用于存储非敏感的配置数据(如应用配置文件、环境变量),Secret 用于存储敏感信息(如数据库密码、TLS 证书)。将配置与镜像解耦后,同一份镜像可以在开发、测试、生产环境中复用,只需替换不同的 ConfigMap 和 Secret 即可。 ### Namespace — 资源隔离 Namespace 在同一个物理集群中划分出多个逻辑上的"虚拟集群"。不同 Namespace 下的资源名称可以重复,资源配额(ResourceQuota)和访问控制(RBAC)也可以按 Namespace 粒度设置。常见做法是为每个团队或每个环境(dev/staging/prod)创建独立的 Namespace。 ## Kubernetes 的工作原理 Kubernetes 采用经典的主从架构,分为控制平面(Control Plane)和数据平面(Worker Node)两部分。 ### 控制平面(Control Plane) 控制平面是集群的"大脑",负责全局决策和响应集群事件: - **kube-apiserver**:集群的统一入口,所有组件之间的通信都通过 REST API 经由 apiserver 完成。它是唯一直接与 etcd 交互的组件。 - **etcd**:分布式键值存储,保存集群的全部状态数据。etcd 的数据就是集群的"唯一真相来源"(Single Source of Truth)。 - **kube-scheduler**:监听新创建且尚未被调度的 Pod,根据资源需求、亲和性规则、污点容忍等策略为 Pod 选择合适的 Node。 - **kube-controller-manager**:运行各种控制器(Deployment Controller、ReplicaSet Controller、Node Controller 等),通过控制循环不断将集群的当前状态向期望状态收敛。 ### 工作节点(Worker Node) 工作节点是集群的"手脚",负责实际运行业务容器: - **kubelet**:在每个 Node 上运行的代理,接收 PodSpec 并确保容器按照规范运行,同时向 apiserver 汇报 Node 和 Pod 的状态。 - **kube-proxy**:在每个 Node 上维护网络规则(默认使用 iptables 或 IPVS 模式),实现 Service 到 Pod 的请求转发和负载均衡。 - **容器运行时**:负责拉取镜像、启动和停止容器。Kubernetes 通过 CRI(Container Runtime Interface)与运行时交互,不再直接依赖 Docker。 ### 一个请求的完整流程 当你执行 `kubectl apply -f deployment.yaml` 时,发生了什么: 1. kubectl 将 YAML 发送到 kube-apiserver 2. apiserver 对请求进行认证、鉴权和准入控制后,将数据写入 etcd 3. kube-scheduler 监听到未调度的 Pod,为其选择 Node 并将结果写回 etcd 4. 目标 Node 上的 kubelet 监听到有 Pod 分配给自己,调用容器运行时启动容器 5. kube-controller-manager 不断监控实际副本数与期望副本数是否一致,如有偏差则创建或删除 Pod 这个"声明式 + 控制循环"的设计思想是 Kubernetes 最核心的理念——你只需要告诉它"我想要什么",而不是"怎么做"。 ## Kubernetes 的主要特性 - **自动化部署和回滚**:通过声明式配置实现滚动更新,支持按比例控制升级速度,出错时可一键回滚到上一版本 - **服务发现和负载均衡**:自动为 Service 分配 ClusterIP 和 DNS 记录,内置轮询式负载均衡 - **自动扩缩容**:Horizontal Pod Autoscaler(HPA)根据 CPU/内存使用率或自定义指标自动增减 Pod 副本数 - **自愈能力**:自动重启失败容器、替换无响应节点上的 Pod、杀死未通过健康检查的容器 - **存储编排**:通过 PV/PVC 机制自动挂载各种存储后端(本地磁盘、NFS、云盘等),无需关心底层实现 - **配置和密钥管理**:ConfigMap 和 Secret 将配置与镜像解耦,支持热更新而不需要重新构建镜像 ## 典型应用场景 - **微服务架构**:每个微服务独立部署为一个 Deployment,通过 Service 互相调用,配合 Istio 等 Service Mesh 实现流量治理 - **CI/CD 流水线**:利用 Kubernetes 的声明式特性,将构建、测试、部署全流程容器化,实现 GitOps 工作流 - **批处理和定时任务**:Job 和 CronJob 控制器支持一次性任务和定时调度任务 - **机器学习训练**:利用 GPU 调度、分布式训练框架(如 Kubeflow)在 Kubernetes 上运行大规模模型训练 --- 掌握 Kubernetes 的核心概念和工作原理,是理解整个云原生技术栈的基础。从 Pod 到 Deployment,从 Service 到 Namespace,每一个概念都对应着生产环境中真实存在的问题和解决方案。建议在学习理论的同时动手搭建一个多节点集群,用 `kubectl` 完成一次完整的部署、扩容和滚动更新,这样对这些概念的理解才会从"知道"变成"会用"。
服务端5月27日 18:31
Kubernetes 污点(Taints)和容忍度(Tolerations)是什么?如何使用它们控制 Pod 调度?Kubernetes 集群中并非所有节点都一样——有的挂了 GPU,有的专门跑监控组件,有的正在维护。如何让 Pod "知道"哪些节点该避开、哪些节点可以进入?答案就是污点(Taints)和容忍度(Tolerations)。这对机制从节点侧和 Pod 侧分别控制调度行为,是 Kubernetes 调度体系中不可绕过的一环。 ## 污点是什么——节点说"别来" 污点是打在节点上的标记,告诉调度器:"除非 Pod 明确声明能容忍我,否则别往这儿调度。" 一个完整的污点由三部分组成: - **Key**:污点的键,必填。比如 `dedicated`、`node-role.kubernetes.io/master` - **Value**:污点的值,选填。比如 `gpu`、`control-plane` - **Effect**:污点生效的方式,必填。决定"不匹配时怎么办" ### 三种 Effect 的区别 这是面试中最常被追问的细节,三种 Effect 行为差异很大: **NoSchedule**——硬性拒绝新 Pod 调度到该节点,但已经在跑的 Pod 不受影响。这是最常见的用法,典型场景是为专用节点(GPU、Ingress)加锁:没写容忍度的 Pod 根本进不来。 **PreferNoSchedule**——软性偏好,调度器会尽量避开,但如果集群资源紧张,还是可能把 Pod 放过来。适合那种"最好别来,但来了也行"的场景,比如想让某节点尽量只跑日志采集组件,但不强制。 **NoExecute**——最严厉的一种。不仅阻止新 Pod 调度进来,还会把已经在跑但没有匹配容忍度的 Pod 驱逐走。这是唯一会"赶人"的 Effect,通常用于节点故障或维护场景。Kubernetes 控制面默认用这种 Effect 处理 NotReady 和 Unreachable 节点。 ### 污点的增删查 添加污点用 `kubectl taint`: ```bash # 给 node1 添加 NoSchedule 污点 kubectl taint nodes node1 dedicated=gpu:NoSchedule # 没有值的污点也可以 kubectl taint nodes node1 special:NoSchedule ``` 查看节点的污点: ```bash # 查看单个节点 kubectl describe node node1 | grep Taints # 列出所有节点的污点 kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints ``` 删除污点时在键后面加减号: ```bash # 删除指定污点 kubectl taint nodes node1 dedicated=gpu:NoSchedule- # 删除该键下所有 Effect kubectl taint nodes node1 dedicated- ``` ## 容忍度是什么——Pod 说"我能进" 容忍度写在 Pod 的 `spec.tolerations` 里,告诉调度器:"这个污点我能接受,可以调度到对应节点。" ### 容忍度的字段 一个容忍度包含以下字段: | 字段 | 说明 | 是否必填 | |------|------|----------| | key | 要容忍的污点键 | 否,为空时匹配所有键 | | operator | Equal 或 Exists | 否,默认 Equal | | value | 污点的值 | Equal 时必填 | | effect | 污点的 Effect | 否,为空时匹配所有 Effect | | tolerationSeconds | 容忍多久后被驱逐 | 仅 NoExecute 有效 | ### 两种 Operator 的匹配逻辑 **Equal**——精确匹配,key、value、effect 三者都要对上才算匹配成功: ```yaml tolerations: - key: "dedicated" operator: "Equal" value: "gpu" effect: "NoSchedule" ``` 这条容忍度只能匹配 `dedicated=gpu:NoSchedule` 这一个污点,少一个字段都不行。 **Exists**——只检查 key 是否存在,不关心 value 是什么: ```yaml tolerations: - key: "dedicated" operator: "Exists" effect: "NoSchedule" ``` 这条能匹配 `dedicated=gpu:NoSchedule`、`dedicated=cpu:NoSchedule` 等所有 key 为 `dedicated` 且 effect 为 `NoSchedule` 的污点。 两个极端写法也值得记住:`operator: "Exists"` 且不写 key,匹配一切污点;只写 `operator: "Exists"` 连 effect 也不写,匹配所有污点的所有 Effect。 ### tolerationSeconds 的作用 这个字段只对 NoExecute 生效。假设节点出了问题被自动打上污点,Pod 不会立刻被驱逐,而是等 tolerationSeconds 秒后再驱逐。这给应用留了缓冲时间做优雅退出: ```yaml tolerations: - key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" tolerationSeconds: 300 # 节点 NotReady 后等 5 分钟再驱逐 ``` 如果不设置 tolerationSeconds,Pod 会一直容忍该污点,不会被驱逐。 ## 匹配规则:调度器怎么判断 调度器的匹配逻辑可以简化为三步: 1. 取出节点上所有污点 2. 逐个检查 Pod 的容忍度能否匹配每个污点(忽略能匹配的) 3. 剩下未匹配的污点中,如果有 NoSchedule 或 PreferNoSchedule,影响调度决策;如果有 NoExecute,直接驱逐 几个容易混淆的边界情况: - 容忍度的 key 为空且 operator 为 Exists,匹配所有污点(包括后面新增的) - 容忍度的 effect 为空,匹配该 key 下的所有 Effect - 多个污点之间是"与"的关系:Pod 必须容忍节点的所有污点才能被调度,容忍一个不够 ## 控制面自动添加的污点 Kubernetes 控制面会在特定条件下自动给节点打污点,这些污点是内置的,了解它们对排查调度问题至关重要: | 污点键 | Effect | 触发条件 | |--------|--------|----------| | `node.kubernetes.io/not-ready` | NoExecute | 节点 NotReady | | `node.kubernetes.io/unreachable` | NoExecute | 节点不可达 | | `node.kubernetes.io/memory-pressure` | NoSchedule | 节点内存压力 | | `node.kubernetes.io/disk-pressure` | NoSchedule | 节点磁盘压力 | | `node.kubernetes.io/pid-pressure` | NoSchedule | PID 资源不足 | | `node.kubernetes.io/network-unavailable` | NoSchedule | 节点网络不可用 | | `node.kubernetes.io/unschedulable` | NoSchedule | 节点被 cordon | Kubernetes 还默认为 Pod 添加了对 `not-ready` 和 `unreachable` 的容忍度,tolerationSeconds 为 300 秒。这就是为什么节点故障后 Pod 不会立刻被驱逐,而是等 5 分钟。这个默认行为可以通过在 Pod 中显式声明容忍度来覆盖。 ## 实战场景 ### 专用节点隔离 集群里有几台 GPU 机器,只想让需要 GPU 的 Pod 调度上去,普通 Pod 不要占位置。做法是给 GPU 节点打污点,给 GPU Pod 加容忍度: ```bash # 节点侧 kubectl taint nodes gpu-node dedicated=gpu:NoSchedule ``` ```yaml # Pod 侧 spec: tolerations: - key: "dedicated" operator: "Equal" value: "gpu" effect: "NoSchedule" containers: - name: gpu-app image: nvidia/cuda:11.0.3-base-ubuntu20.04 ``` 但注意,只加容忍度只能让 Pod "可以进",不能保证 Pod "一定进"。如果要让 GPU Pod 只调度到 GPU 节点,还需要配合 nodeAffinity 或 nodeSelector 一起使用。污点是"拒绝"机制,不是"吸引"机制。 ### 节点维护与驱逐 需要对节点做内核升级,先标记为不可调度并驱逐工作负载: ```bash # cordon 阻止新 Pod 调度 kubectl cordon node1 # drain 驱逐现有 Pod(忽略 DaemonSet) kubectl drain node1 --ignore-daemonsets --delete-emptydir-data ``` `kubectl drain` 的本质就是给节点加 `node.kubernetes.io/unschedulable:NoSchedule` 污点,然后驱逐所有不匹配的 Pod。DaemonSet 的 Pod 默认带有对这些污点的容忍度,所以 drain 不会驱逐它们。 ### DaemonSet 为什么不怕污点 DaemonSet 控制器会自动为管理的 Pod 添加以下容忍度: ```yaml tolerations: - key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" - key: "node.kubernetes.io/unreachable" operator: "Exists" effect: "NoExecute" - key: "node.kubernetes.io/disk-pressure" operator: "Exists" effect: "NoSchedule" - key: "node.kubernetes.io/memory-pressure" operator: "Exists" effect: "NoSchedule" # ... 还有更多 ``` 这就是为什么日志采集、监控 Agent 这类 DaemonSet 的 Pod 在节点出问题时仍然留在节点上——它们天生容忍这些污点。 ## 污点容忍度与节点亲和性的配合 污点和亲和性解决的是不同方向的问题: - **污点**:从节点出发,"我不想要谁" - **亲和性**:从 Pod 出出发,"我想去哪里" 两者配合才能实现完整的调度控制。一个常见模式是:污点把不该来的 Pod 挡在外面,亲和性把该来的 Pod 拉到正确位置。比如 GPU 场景——污点阻止普通 Pod,亲和性确保 GPU Pod 优先去 GPU 节点。 单独使用污点有一个隐患:Pod 加了容忍度后能进节点,但不一定只进这个节点。它可能被调度到任何有匹配容忍度的节点。所以污点适合"排他",亲和性适合"定向",两者结合才是完整方案。 ## TaintBasedEviction 机制 当节点进入 NotReady 或 Unreachable 状态时,Kubernetes 不是立刻驱逐 Pod,而是基于 TaintBasedEviction 机制工作: 1. 节点控制器检测到节点异常,给节点打上对应污点 2. Pod 上的容忍度开始倒计时(tolerationSeconds) 3. 倒计时结束,Pod 被标记为驱逐 4. 驱逐由 kubelet 执行优雅终止 这个机制比旧版的基于 NodeCondition 的驱逐更灵活,因为你可以为不同的 Pod 设置不同的容忍时间。关键服务给较长缓冲,非关键服务快速驱逐。 ## 排查调度问题的思路 Pod 调度失败时,按这个顺序排查: ```bash # 1. 查看 Pod 的调度失败事件 kubectl describe pod <pod-name> | grep -A 20 Events # 2. 检查目标节点的污点 kubectl describe node <node-name> | grep Taints # 3. 对比 Pod 的容忍度是否匹配 kubectl get pod <pod-name> -o jsonpath='{.spec.tolerations}' # 4. 检查调度器日志 kubectl logs -n kube-system -l component=kube-scheduler ``` 常见的调度失败提示比如 `node(s) had taints that the pod didn't tolerate`,说明 Pod 缺少对应污点的容忍度,要么给 Pod 加容忍度,要么去掉节点上的污点。 --- 污点和容忍度的核心思路就是"节点标记排斥,Pod 声明接受"。三种 Effect 的行为差异、控制面内置污点的触发条件、与亲和性的配合关系、以及 tolerationSeconds 带来的驱逐缓冲——这些是面试和实战中真正需要掌握的要点。理解了这套机制,就能在专用节点隔离、节点维护、故障处理这些场景下做出合理的调度决策。
服务端5月27日 18:30
Kubernetes ConfigMap 和 Secret 有什么区别?Kubernetes 中 ConfigMap 和 Secret 都用于将配置与容器镜像解耦,但它们在数据性质、存储方式和安全机制上有本质区别。理解两者的差异并正确使用,是管理 K8s 应用配置的基本功,也是面试高频考点。 ## ConfigMap 和 Secret 的核心区别是什么 ConfigMap 存储非敏感的配置数据,比如应用端口号、日志级别、功能开关;Secret 专门存储密码、证书、Token 等敏感信息。这是最根本的划分原则——如果你犹豫某个值该放哪里,问自己一个问题:泄露后会不会出安全事故?会就放 Secret。 两者在 API 层面的差异: - **编码方式**:ConfigMap 的 data 字段存明文,Secret 的 data 字段存 Base64 编码。注意 Base64 只是编码不是加密,etcd 中 Secret 默认仍是明文存储,必须启用 EncryptionConfiguration 才能真正加密。 - **访问控制**:Secret 有更严格的 RBAC 建议策略,K8s 审计日志可以单独追踪 Secret 的访问记录。 - **etcd 存储**:开启加密后,Secret 在 etcd 中以密文保存;ConfigMap 始终明文。 - **大小限制**:两者单条都限制 1 MiB,超限需要拆分或引入外部配置中心。 一个容易忽略的点:Secret 挂载到 Pod 后,kubelet 会将其写入 tmpfs(内存文件系统),Pod 删除后数据随之消失;而 ConfigMap 挂载的文件默认写入磁盘。 ## 怎样创建 ConfigMap 实际项目中很少用命令行一条条创建,更多是通过 YAML 声明式管理,配合 GitOps 流程。以下覆盖常见的创建方式。 从字面值创建,适合少量键值对: ```bash kubectl create configmap app-config \ --from-literal=LOG_LEVEL=info \ --from-literal=MAX_RETRIES=3 ``` 从文件创建,适合将整个配置文件注入: ```bash kubectl create configmap nginx-config \ --from-file=nginx.conf=./nginx.conf ``` 从 YAML 声明创建,这是推荐的做法,便于版本管理和审计: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config data: LOG_LEVEL: "info" MAX_RETRIES: "3" application.yml: | server: port: 8080 spring: datasource: url: jdbc:mysql://mysql-svc:3306/mydb ``` 注意 ConfigMap 的 data 下所有值都是字符串类型。如果你写了 `PORT: 8080`,实际存储的是 `"8080"`,这在某些语言框架中可能引发类型解析问题。 ## 怎样在 Pod 中使用 ConfigMap ConfigMap 有三种使用方式,选择哪种取决于配置的变更频率和消费方式。 **作为环境变量**——适合少量、启动时确定的配置: ```yaml spec: containers: - name: app image: my-app:latest env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL ``` 也可以一次性注入所有键值对: ```yaml envFrom: - configMapRef: name: app-config ``` 注意 `envFrom` 会把 ConfigMap 的所有键注入环境变量,如果键名冲突会被后面的覆盖,要确认命名规范一致。 **挂载为卷**——适合配置文件场景,如 Nginx 配置、Spring 的 application.yml: ```yaml spec: containers: - name: app volumeMounts: - name: config-volume mountPath: /etc/app/config readOnly: true volumes: - name: config-volume configMap: name: app-config ``` 挂载为卷有一个重要特性:ConfigMap 更新后,挂载的文件会在几分钟内自动刷新(kubelet 的同步周期默认 60 秒 + 随机延迟)。但应用本身需要有能力感知文件变化并热加载,否则还是要滚动重启 Pod。 **作为命令行参数**——在容器启动命令中引用环境变量: ```yaml spec: containers: - name: app image: my-app:latest command: ["./app"] args: ["--log-level=$(LOG_LEVEL)"] env: - name: LOG_LEVEL valueFrom: configMapKeyRef: name: app-config key: LOG_LEVEL ``` 这种方式本质上还是环境变量,只是被 command/args 引用了。 ## 怎样创建 Secret Secret 的创建方式与 ConfigMap 类似,但有一个关键区别:`data` 字段的值必须 Base64 编码。 ```yaml apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque data: username: YWRtaW4= # echo -n 'admin' | base64 password: c2VjcmV0MTIz # echo -n 'secret123' | base64 ``` 如果你不想手动编码,可以用 `stringData` 字段,K8s 会自动帮你转 Base64: ```yaml apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque stringData: username: admin password: secret123 ``` `stringData` 在写入 etcd 后会被转为 `data` 字段的 Base64 编码形式,所以通过 `kubectl get -o yaml` 看到的仍然是编码后的值。 创建 TLS 类型的 Secret,用于 Ingress 或 Pod 的 HTTPS 配置: ```bash kubectl create secret tls tls-cert \ --cert=./tls.crt \ --key=./tls.key ``` 创建镜像拉取凭据: ```bash kubectl create secret docker-registry regcred \ --docker-server=registry.example.com \ --docker-username=user \ --docker-password=pass ``` ## 怎样在 Pod 中使用 Secret **作为环境变量**——最简单但要注意安全隐患: ```yaml spec: containers: - name: app env: - name: DB_USERNAME valueFrom: secretKeyRef: name: db-credentials key: username ``` 环境变量方式的缺点:应用日志或调试输出可能意外打印敏感值;子进程会继承所有环境变量。如果对安全性要求高,优先用卷挂载。 **挂载为卷**——推荐方式,Secret 以文件形式存在 tmpfs 中: ```yaml spec: containers: - name: app volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true volumes: - name: secret-volume secret: secretName: db-credentials ``` 挂载方式的好处是应用可以按需读取,不会意外泄露到环境变量或日志中。 **imagePullSecrets**——用于拉取私有镜像仓库: ```yaml spec: imagePullSecrets: - name: regcred containers: - name: app image: registry.example.com/my-app:latest ``` ## ConfigMap 和 Secret 的更新机制有什么坑 这是一个面试常考、实战也常踩的要点。 ConfigMap/Secret 更新后,**Pod 不会自动重启**。对于 Deployment 管理的 Pod,你需要在模板中引用 ConfigMap/Secret 的某个标注(比如通过 subPath 或 env 注入资源版本号),触发滚动更新。否则新配置只会通过卷挂载的方式在 Pod 内部刷新文件内容。 环境变量方式更加局限——容器的环境变量在启动时就固定了,ConfigMap 更新后,已经运行的 Pod 的环境变量不会变,必须重建 Pod 才能生效。 还有一个容易忽略的坑:如果你用了 `subPath` 挂载 ConfigMap 或 Secret 的某个键,该文件不会随着 ConfigMap/Secret 更新而自动刷新,因为它脱离了符号链接的更新机制。 ## immutable 不可变配置有什么用 Kubernetes 1.21 起,ConfigMap 和 Secret 支持 `immutable: true` 字段: ```yaml apiVersion: v1 kind: ConfigMap metadata: name: app-config immutable: true data: LOG_LEVEL: "info" ``` 标记为不可变后,无法修改 data 字段,只能删除重建。好处有两点: - **性能提升**:kubelet 不需要 watch 不可变 ConfigMap/Secret,减轻了 API Server 和 kubelet 的负担。集群中 ConfigMap/Secret 数量多时效果明显。 - **安全性**:防止配置被意外或恶意篡改。 生产环境中,如果配置在发布后确实不会变更,建议加上 `immutable: true`。 ## Secret 的安全加固方案有哪些 Base64 编码不等于加密,这是一个必须强调的事实。以下是生产环境中需要落实的安全措施。 **启用 etcd 加密**——配置 EncryptionConfiguration,让 Secret 在 etcd 中以密文存储: ```yaml apiVersion: apiserver.config.k8s.io/v1 kind: EncryptionConfiguration resources: - resources: - secrets providers: - aescbc: keys: - name: key1 secret: <base64-encoded-32-byte-key> - identity: {} ``` **配置 RBAC 最小权限**——只授权必要的 Secret 访问: ```yaml apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: name: secret-reader rules: - apiGroups: [""] resources: ["secrets"] resourceNames: ["db-credentials"] verbs: ["get"] ``` **使用外部密钥管理系统**——对于安全等级高的场景,引入 HashiCorp Vault、AWS Secrets Manager 或 Sealed Secrets 等: - Vault Agent Injector:通过 Mutating Admission Webhook 自动将 Vault 中的密钥注入 Pod - External Secrets Operator:将外部密钥同步为 K8s Secret - Sealed Secrets:加密后可以安全地存储在 Git 中 **启用审计日志**——追踪谁在什么时候访问了哪些 Secret: ```yaml apiVersion: audit.k8s.io/v1 kind: Policy rules: - level: RequestResponse resources: - group: "" resources: ["secrets"] ``` **定期轮换密钥**——建立轮换机制,避免长期使用同一密钥。可以结合 CI/CD 流程在部署时自动更新 Secret。 ## 面试中如何回答 ConfigMap 和 Secret 的相关问题 面试官问这个问题,通常不只是要你背概念,而是在考察你对 K8s 配置管理的整体理解。一个合格的回答应该涵盖以下层次: 1. 先说清楚本质区别:非敏感 vs 敏感,明文 vs Base64 编码,不同的安全机制 2. 再说实际使用:创建方式、三种消费方式(环境变量/卷挂载/命令行参数)及各自的适用场景 3. 然后说踩坑经验:更新机制的限制、环境变量不热更新、subPath 不自动刷新 4. 最后说安全加固:etcd 加密、RBAC 最小权限、外部密钥管理、审计日志 如果你只答了第一层,面试官会认为你对 K8s 的理解停留在表面。能把第四层讲清楚,才能体现出生产环境的实战经验。
前端2024年7月20日 02:38
如何设置Kubernetes集群?要设置Kubernetes集群,主要有几个步骤,我会逐一解释每个步骤和相关的操作。 ### 1. 确定基础设施 首先,需确定部署Kubernetes集群的环境。可以在本地机器、私有云、公有云或混合云中部署。例如,如果选择在AWS上部署,可以利用其EKS(Elastic Kubernetes Service)服务,这样可以减少很多手动配置的工作。 ### 2. 配置主节点和工作节点 Kubernetes集群通常包括至少一个主节点和多个工作节点。主节点负责管理集群的状态,调度应用程序,维护其所需的配置等。工作节点则是实际运行应用程序的服务器。 - **主节点设置**:安装Kubernetes的控制平面组件,例如API服务器、集群存储(etcd)、调度器等。 - **工作节点设置**:安装Kubelet、Kube-Proxy等,它们用于管理容器和与主节点通信。 ### 3. 安装Kubernetes 可以通过多种方法安装Kubernetes,例如使用kubeadm、kops(针对AWS)、Minikube(适用于学习和开发环境)等工具。以**kubeadm**为例,步骤如下: 1. **安装kubeadm, kubelet 和 kubectl**: ```bash apt-get update && apt-get install -y apt-transport-https curl curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - echo "deb https://apt.kubernetes.io/ kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list apt-get update apt-get install -y kubelet kubeadm kubectl apt-mark hold kubelet kubeadm kubectl ``` 2. **初始化主节点**: ```bash kubeadm init --pod-network-cidr=10.244.0.0/16 ``` 这里的CIDR块是为了配置网络插件使用的,确保不与现有网络冲突。 3. **配置kubectl**: ```bash mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config ``` 4. **安装网络插件**: 可以选择Calico, Flannel等。例如,安装Calico: ```bash kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml ``` 5. **将工作节点加入集群**: 每个工作节点运行: ```bash kubeadm join <MASTER_IP>:<MASTER_PORT> --token <TOKEN> --discovery-token-ca-cert-hash sha256:<HASH> ``` 这里需要替换 `<MASTER_IP>`, `<MASTER_PORT>`, `<TOKEN>`, 和 `<HASH>`为实际值。 ### 4. 验证集群状态 完成以上步骤后,应使用以下命令检查集群的状态: ```bash kubectl get nodes ``` 如果一切正常,将看到所有节点的状态为 `Ready`。 ### 5. 部署应用 现在,集群已准备好部署应用。可以使用Deployment资源来部署,例如: ```bash kubectl create deployment nginx --image=nginx ``` 然后,可以通过创建Service来暴露Deployment,如: ```bash kubectl expose deployment nginx --port=80 --type=NodePort ``` ### 总结 这是一个基本的Kubernetes集群设置流程,涵盖了从选择基础设施到部署应用的完整步骤。当然,实际操作中会根据具体需求调整,例如在生产环境中可能需要配置高可用性等。