它解决什么问题

Kubernetes,简称 K8s,是用来管理一组机器上容器的系统:程序跑在哪里、跑几份、挂了怎么恢复、更新时怎么切换,都交给它协调。

容器把程序和运行环境打包起来,K8s 负责把这些容器组织成一个持续运行的服务。

核心思想是声明式管理:告诉系统「我想要什么状态」,系统持续把实际状态往这个目标上调整。

比如声明「这个服务运行 3 个副本」,少了就补,多了就减。这种不断比较期望与现实、再做修正的过程,叫调谐(Reconciliation)。

集群由什么组成

集群里的机器叫 Node,可以是物理机,也可以是虚拟机。组件分成控制面和节点侧两部分。

控制面负责管理集群:

  1. kube-apiserver:统一入口,kubectl 和各组件通过 API 读取、修改资源
  2. etcd:保存集群状态的键值数据库,存的是资源配置和状态,不是应用的业务数据库
  3. kube-scheduler:给还没安排节点的 Pod 选择合适的 Node
  4. kube-controller-manager:运行各种控制器,持续检查并修正资源状态

节点侧负责执行:

  1. kubelet:根据分配到本节点的 Pod 配置,协调容器运行并上报状态
  2. 容器运行时:比如 containerd,通过 CRI 接口与 kubelet 配合,真正创建和运行容器
  3. 网络组件:通常由 CNI 插件实现 Pod 联网,Service 转发通常由 kube-proxy 或网络插件提供的替代实现负责

记住分工:API 接收请求,控制器补齐状态,调度器选机器,kubelet 让容器在机器上跑起来。

Pod:最小调度单位

Pod 是 K8s 创建和调度的最小单位,一个 Pod 可以包含一个或多个紧密协作的容器。

  1. 同一个 Pod 里的容器总在同一个 Node 上
  2. 它们共享网络空间,可以通过 localhost 通信,也要避免监听端口冲突
  3. 它们可以通过挂载同一个 Volume 共享文件,但并不自动共享各自的根文件系统
  4. Pod 是可替换的:被删除后,控制器可以创建新的 Pod,新 Pod 的名字和 IP 都可能变化

容器崩溃时,kubelet 可以根据重启策略在原 Pod 内重启容器;Pod 被删除或节点故障时,通常由控制器创建替代 Pod。不是把原来的 Pod 搬到另一台机器上。

工作负载:谁来管理 Pod

一般不会直接创建一个裸 Pod,而是声明更高层的工作负载:

  1. Deployment:管理通常可互相替代的无状态副本,支持滚动更新和回滚
  2. StatefulSet:给 Pod 提供稳定的身份和持久卷关联,常用于有状态服务;数据库复制和容灾仍需要应用自己实现
  3. DaemonSet:让每个符合条件的节点运行一个 Pod,适合日志采集、监控和网络代理
  4. Job:执行一次任务,追踪任务是否成功完成
  5. CronJob:按时间计划创建 Job

Deployment 不直接管理 Pod,中间还有 ReplicaSet:

Deployment → ReplicaSet → Pod

Deployment 管版本和发布过程,ReplicaSet 维持指定数量的 Pod。更新 Pod 模板时,Deployment 创建新的 ReplicaSet,再逐步扩新缩旧。

一次部署是怎么发生的

执行 kubectl apply -f app.yaml 之后:

  1. kubectl 把资源声明提交给 API Server,经过认证、授权和准入等处理后保存到 etcd
  2. Deployment 控制器发现目标配置,创建或调整 ReplicaSet
  3. ReplicaSet 控制器创建缺少的 Pod
  4. Scheduler 根据资源需求和调度约束,为 Pod 选择 Node
  5. 对应节点的 kubelet 协调运行时、网络和存储组件,准备环境并启动容器
  6. Pod 就绪后,通常会被纳入对应 Service 的可用后端,接收流量

这些组件通过 API 观察变化、分别调谐,不是一次同步调用就把所有事情做完。所以 apply 成功只说明声明被接受,不代表应用已经可用。

网络:怎么找到服务

Pod IP 会变化,所以客户端通常不直接依赖 Pod IP,而是访问 Service。

Service 给一组后端提供稳定的访问入口,常见类型有:

  1. ClusterIP:默认类型,提供集群内部可访问的虚拟 IP
  2. NodePort:在节点上开放端口,把请求转发给后端
  3. LoadBalancer:对接云平台或其他负载均衡实现,提供外部入口;需要对应基础设施支持

Label 是资源上的键值标签,Selector 是按标签筛选资源的条件。Service 通常通过 Selector 找 Pod,由 EndpointSlice 记录后端地址和就绪情况。

同一 Namespace 内通常可以直接用 Service 名访问,比如 http://web;跨 Namespace 可以用 web.demo,分别表示服务名和命名空间名。名字解析通常由集群中的 CoreDNS 提供。

HTTP/HTTPS 的域名、路径路由通常通过 Ingress 或 Gateway API 描述,需要安装相应的控制器或实现,光创建规则不会自动产生入口。

几个容易混淆的端口:

  1. containerPort:声明容器预期监听的端口,不会让应用自动监听,也不会自动对外开放
  2. Service 的 port:客户端访问 Service 时使用的端口
  3. Service 的 targetPort:流量最终发往 Pod 的端口
  4. nodePort:使用 NodePort 时,节点上对外提供的端口

配置、存储与权限

  1. Namespace:给资源分组,适合区分团队和环境;它本身不是完整的网络或安全隔离边界
  2. ConfigMap:保存普通配置,可以作为环境变量或文件提供给容器
  3. Secret:保存密码、令牌等敏感配置;清单里的 base64 是编码,不是加密
  4. Volume:给容器挂载存储;比如 emptyDir 可以跨容器重启保留,但随 Pod 删除而消失
  5. PV / PVC:PV 表示存储资源,PVC 表示应用对存储的申请,可以通过 StorageClass 动态供应
  6. ServiceAccount / RBAC:前者表示工作负载使用的身份,后者规定这个身份能对哪些资源执行哪些操作

持久卷可以让数据独立于 Pod 生命周期存在,但实际能挂载到哪些节点、删除后是否保留,取决于存储类型、访问模式和回收策略。

ConfigMap 或 Secret 作为环境变量注入时,更新资源不会直接改变已有进程的环境变量,通常需要重建 Pod。

资源与调度

容器的资源配置里,最重要的是 requests 和 limits:

  1. requests:声明资源需求,调度器主要按它判断节点的可分配资源是否足够
  2. limits:限制容器的资源使用;CPU 达到限制通常会被节流,内存超限可能触发 OOM Kill

CPU 的 1000m 等于 1 个 CPU 单位,100m 就是 0.1 个;内存常用 Mi、Gi。

requests 不是使用上限,limits 也不会凭空变出资源。节点当前 CPU 很空闲,不代表还能调度一个 requests 很大的 Pod,因为已分配的资源需求可能已经占满。

其他常见调度条件:

  1. nodeSelector / 节点亲和性:希望或要求 Pod 放到某类节点
  2. Pod 亲和性 / 反亲和性:希望或要求某些 Pod 靠近或分开
  3. 污点与容忍:节点用污点排斥某些 Pod,容忍表示允许接受对应污点,不保证一定调度到那里

GPU 通常需要驱动和设备插件,以 nvidia.com/gpu 等扩展资源暴露给 K8s。默认申请的是设备数量,并不是「申请多少 GB 显存」;切分和共享需要额外机制。

健康检查与扩缩容

三个探针分别回答三个问题:

  1. startupProbe:应用启动完成了吗?配置后,在它成功前不会执行存活和就绪探针;连续失败达到阈值会触发容器重启处理
  2. readinessProbe:现在能接流量吗?失败后通常会从 Service 的就绪后端中移除,但不会因此重启容器
  3. livenessProbe:应用是否失去正常运行能力,需要重启?连续失败达到阈值后,kubelet 会终止容器,并按重启策略处理

进程在运行,不等于服务已就绪;服务暂时不能接流量,也不一定应该重启。

扩缩容也分两层:

  1. HPA:根据 CPU、内存或自定义指标调整工作负载副本数,需要相应的指标服务;按 CPU 利用率计算时,还需要对应容器设置 CPU requests
  2. 节点自动扩缩容:增加或减少集群里的机器,需要云平台或对应基础设施支持

Pod 变多不代表机器也会变多。资源不够时,新 Pod 会 Pending,等待有节点满足条件。

一个最小部署示例

下面声明 2 个 Nginx 副本,并用一个集群内部的 Service 访问它们。保存为 app.yaml,在已配置好 kubectl 的集群上执行。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:stable
          ports:
            - containerPort: 80
          resources:
            requests:
              cpu: 100m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 2
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

Deployment 的 Selector、Pod 模板上的 Label、Service 的 Selector,要能对应起来。这里连接它们的就是 app: web。

# 创建独立的示例命名空间并部署
kubectl create namespace k8s-demo
kubectl apply -n k8s-demo -f app.yaml

# 等待部署就绪,查看副本和服务
kubectl rollout status deployment/web -n k8s-demo
kubectl get deployments,pods,services -n k8s-demo

# 将本机端口转发到服务,保持这个命令运行
kubectl port-forward -n k8s-demo service/web 8080:80

然后在运行 kubectl 的机器上访问 http://localhost:8080。这个端口转发适合临时调试,不是正式的对外入口。实际发布时通常固定镜像版本或 digest,避免可变标签带来版本漂移。

常用排障思路

先看状态,再看事件,再看日志,最后检查应用内部:

# Pod 在哪个节点、是什么状态
kubectl get pods -n k8s-demo -o wide

# 把 <pod-name> 替换为实际 Pod 名称
kubectl describe pod <pod-name> -n k8s-demo
kubectl logs <pod-name> -n k8s-demo -c nginx

# 查看同一 Pod 中上一次退出的容器日志
kubectl logs <pod-name> -n k8s-demo -c nginx --previous

# 查看命名空间中的近期事件
kubectl get events -n k8s-demo --sort-by=.metadata.creationTimestamp

# 检查服务后端是否生成,以及后端的就绪状态
kubectl get endpointslices -n k8s-demo -l kubernetes.io/service-name=web -o yaml

# 进入容器检查,需要镜像中存在对应 shell
kubectl exec -it <pod-name> -n k8s-demo -c nginx -- /bin/sh

几种常见现象:

  1. Pending:可能没有节点满足资源或调度条件,也可能在等待存储等启动准备,先看事件
  2. ImagePullBackOff:镜像拉取失败,检查镜像地址、仓库凭据和网络
  3. CrashLoopBackOff:容器反复退出,系统正在退避重试,重点看退出原因和 --previous 日志
  4. OOMKilled:容器因内存问题被终止,检查内存限制、应用峰值和节点内存情况
  5. Running 但不 Ready:进程已经启动,但就绪条件未满足,检查探针和应用依赖
  6. Service 访问不通:按 Selector、EndpointSlice、targetPort、应用监听地址、网络策略的顺序检查

把 K8s 串起来看:工作负载声明要跑什么,调度器决定跑在哪里,节点负责运行,Service 负责找到它,控制器持续让现实接近期望。