K8s面试实战:从Pod创建到Service暴露的完整流程解析(附常见坑点)

如果你正准备一场Kubernetes相关的技术面试,或者正在从理论转向实际运维,那么这篇文章就是为你准备的。面试官常常会问:“请描述一下一个Pod从创建到对外提供服务的完整流程。” 这看似是一个基础问题,却串联了K8s的核心组件、网络模型和资源调度,是检验你是否真正理解其工作原理的绝佳试金石。今天,我们不只讲流程,更会结合真实运维中遇到的“坑”,带你走一遍从kubectl apply到服务可访问的完整闭环,让你在面试和实战中都游刃有余。

1. 流程总览:一张图看懂Pod的“诞生”与“亮相”

在深入细节之前,我们先从宏观上把握整个过程。一个Pod从无到有,再到通过Service被外界访问,其生命周期大致可以分为四个阶段:提交与调度节点执行网络配置服务暴露。这背后是K8s各个组件精密协作的结果。

为了更直观地理解,我们可以将核心流程梳理如下:

sequenceDiagram
    participant U as 用户 (kubectl)
    participant A as API Server
    participant E as etcd
    participant S as Scheduler
    participant K as kubelet (Node)
    participant C as Container Runtime
    participant P as kube-proxy
    participant CNI as CNI Plugin

    U->>A: 1. 提交 Pod YAML
    A->>E: 2. 存储 Pod 对象
    A->>S: 3. 通知新 Pod (Watch)
    S->>A: 4. 绑定 Pod 到 Node
    A->>E: 5. 更新 Pod 绑定信息
    A->>K: 6. 通知目标 Node kubelet (Watch)
    K->>C: 7. 拉取镜像,创建容器
    C->>K: 8. 容器启动成功
    K->>A: 9. 上报 Pod 状态 (Running)
    K->>CNI: 10. 调用 CNI 配置 Pod 网络
    CNI->>K: 11. 分配 IP,设置网络
    Note over P,CNI: 网络就绪
    P->>A: 12. Watch Service/Endpoint 变化
    P->>P: 13. 配置 iptables/ipvs 规则
    Note over U,P: 14. 外部请求可通过 Service 访问 Pod

上图清晰地展示了从用户提交到服务可用的关键步骤与组件交互。接下来,我们将拆解每个阶段,并附上你可能遇到的典型问题。

2. 第一阶段:提交与调度——控制平面的“大脑”决策

当你执行 kubectl apply -f pod.yaml 时,故事就开始了。这个命令会将你的YAML定义发送给Kubernetes集群的“大门”——API Server。

2.1 API Server:统一的入口与守门人

API Server是所有请求的唯一起点。它首先会对你提交的YAML进行一系列校验,包括语法、资源配额、安全策略等。这里常遇到的第一个“坑”是认证与授权。如果你的kubeconfig配置错误或RBAC权限不足,请求在第一步就会被拒绝。

注意:在调试时,如果kubectl命令失败,首先检查 kubectl config view 确认当前上下文和用户,并使用 kubectl auth can-i create pods 来验证权限。

通过校验后,API Server会将这个Pod对象的期望状态(Desired State)写入分布式键值存储etcd。etcd是K8s集群的“记忆中枢”,所有集群状态数据都存储于此。

2.2 Scheduler:为Pod寻找“家”

Pod对象存入etcd后,其nodeName字段为空,处于Pending状态。此时,Scheduler这个“调度器”开始工作。它通过Watch机制监听到新的、未绑定的Pod,并启动调度决策流程。这个过程分为两步:

  1. 过滤(Filtering/Predicates):根据Pod的资源请求(requests.cpu/memory)、节点选择器(nodeSelector)、亲和性/反亲和性(affinity/anti-affinity)、污点与容忍(taints and tolerations)等规则,筛选出所有符合条件的候选节点。
  2. 打分(Scoring/Priorities):对候选节点进行评分,例如选择资源最空闲的节点(LeastRequestedPriority)、将Pod分散到不同域(SelectorSpreadPriority)等,得分最高的节点胜出。

常见坑点:Pod一直处于Pending状态

这是面试和运维中最常见的问题之一。排查思路如下:

# 1. 查看Pod的详细事件,这是最直接的线索
kubectl describe pod <pod-name>

# 2. 查看事件中是否有明确的调度失败原因,例如:
#   - `0/3 nodes are available: 3 Insufficient cpu.` (资源不足)
#   - `0/3 nodes are available: 3 node(s) didn't match Pod's node affinity/selector.` (节点选择器不匹配)
#   - `0/3 nodes are available: 3 node(s) had taint {key:value}, that the pod didn't tolerate.` (污点未容忍)

# 3. 检查节点资源情况
kubectl top nodes
kubectl describe node <node-name> | grep -A 10 -i allocatable

# 4. 检查Pod的资源请求是否合理
kubectl get pod <pod-name> -o yaml | grep -A 5 resources

Scheduler做出决策后,会通过API Server将绑定信息(spec.nodeName)写回etcd。至此,Pod在控制平面的“落户”完成。

3. 第二阶段:节点执行——kubelet与容器运行时的协作

当Pod被绑定到某个Node(假设是node-01)后,该节点上的kubelet就开始行动了。kubelet是每个工作节点上的“节点代理”,负责Pod的生命周期管理。

3.1 kubelet:Pod的“保姆”

kubelet同样通过Watch API Server,发现了有一个Pod被调度到了自己管理的节点上。它的工作流程是:

  1. 获取Pod清单:从API Server获取Pod的配置详情。
  2. 拉取镜像:通过容器运行时(如containerd)从镜像仓库拉取指定的容器镜像。这里藏着第二个大坑:镜像拉取失败
  3. 创建容器:调用容器运行时的接口(CRI)创建和启动容器。
  4. 执行探针:如果配置了livenessProbereadinessProbe等,kubelet会定期执行健康检查。
  5. 上报状态:持续向API Server报告Pod和容器的状态。

常见坑点:ImagePullBackOff 或 ErrImagePull

# 查看Pod事件和容器日志
kubectl describe pod <pod-name>
kubectl logs <pod-name> --previous # 查看之前失败容器的日志

# 常见原因及解决:
# 1. 镜像名称拼写错误或标签不存在:检查 `kubectl get pod <pod-name> -o yaml | grep image`
# 2. 私有仓库认证失败:需要创建 `docker-registry` 类型的Secret,并在Pod中引用
#    kubectl create secret docker-registry regcred \
#      --docker-server=<your-registry> \
#      --docker-username=<username> \
#      --docker-password=<password>
# 然后在Pod spec中配置:`spec.imagePullSecrets: - name: regcred`
# 3. 节点网络问题:登录节点,尝试手动 `docker pull` 或 `crictl pull` 镜像。

3.2 容器网络接口(CNI):赋予Pod身份

在容器启动前后,kubelet会调用配置好的CNI插件(如Calico、Flannel)为Pod配置网络。CNI插件会完成几件关键事:

  • 为Pod分配一个集群内唯一的IP地址(通常是Pod CIDR网段内的一个IP)。
  • 创建虚拟网络设备(如veth pair),一端在Pod网络命名空间内(通常命名为eth0),另一端连接到节点的网络桥接或 overlay 网络。
  • 设置路由规则,确保Pod能与同节点其他Pod、跨节点Pod以及外部网络通信。

此时,Pod获得了IP,进入了Running状态,并具备了基本的网络通信能力。

4. 第三阶段:服务暴露——Service与kube-proxy的魔法

一个孤立的Pod IP是临时的,且对集群外不可见。为了让服务稳定地被访问,我们需要Service

4.1 Service:稳定的服务抽象

Service通过标签选择器(Label Selector)动态地关联一组Pod。它提供了一个稳定的ClusterIP(集群内部IP)和DNS名称(<service-name>.<namespace>.svc.cluster.local),后端Pod的变化(扩缩容、重启)对客户端透明。

创建Service时,你需要关注几个核心字段:

apiVersion: v1
kind: Service
metadata:
  name: my-app-service
spec:
  selector:
    app: my-app # 关键:通过此标签选择Pod
  ports:
    - protocol: TCP
      port: 80       # Service对外暴露的端口
      targetPort: 8080 # Pod容器内监听的端口
  type: ClusterIP # 服务类型

Service的类型决定了其暴露方式:

类型作用访问方式适用场景
ClusterIP默认类型,在集群内部提供访问<cluster-ip>:<port> 或 Service DNS集群内微服务间通信
NodePort在每个节点上开放一个静态端口<node-ip>:<node-port>从集群外访问服务的简易方式(测试、临时)
LoadBalancer云厂商提供的外部负载均衡器负载均衡器的IP/域名生产环境对外暴露服务(云环境)
ExternalName将Service映射到外部DNS名Service名称解析为外部CNAME集成集群外服务

4.2 kube-proxy:流量转发的实现者

Service的魔法背后是每个节点上运行的kube-proxy。它监听API Server中Service和Endpoint(后端Pod IP:Port列表)的变化,并配置本地的网络规则,将发往Service IP的流量转发到实际的后端Pod。

kube-proxy的三种工作模式对比:

模式原理优点缺点生产建议
userspace (已弃用)在用户空间做代理和转发兼容性好性能差,流量需要进出内核多次不使用
iptables (默认)使用Linux内核的iptables规则链进行DNAT转发性能较好,成熟稳定规则线性匹配,Service数量多时性能下降,不支持复杂LB算法中小规模集群
ipvs (推荐)使用内核的IPVS模块(L4负载均衡器)高性能,支持丰富的LB算法(rr, wrr, lc等),可哈希表存储规则需要内核模块支持生产环境推荐

启用ipvs模式通常需要在kube-proxy的配置中设置:

apiVersion: kubeproxy.config.k8s.io/v1alpha1
kind: KubeProxyConfiguration
mode: "ipvs"

4.3 Endpoints与EndpointSlices:服务的“后端列表”

Service并不直接管理Pod,而是通过Endpoints对象(或更新的EndpointSlices)来维护后端Pod的地址列表。当你创建Service时,K8s会自动创建一个同名的Endpoints对象,并持续更新,使其中的地址列表与标签选择器选中的Pod IP:Port保持一致。

常见坑点:Service无法访问,Endpoints为空

如果通过Service无法访问Pod,一个首要的检查点就是Endpoints。

# 查看Service的详细信息,特别是Selector和Endpoints
kubectl describe svc <service-name>

# 查看Endpoints列表
kubectl get endpoints <service-name>

# 如果Endpoints为空,检查:
# 1. Service的selector是否与Pod的labels完全匹配?(区分大小写)
kubectl get pods --show-labels
# 2. Pod的端口定义是否正确?Service的targetPort是否与Pod的containerPort对应?
# 3. Pod是否处于Ready状态?如果readinessProbe失败,Pod不会被加入Endpoints。
kubectl get pods -l app=my-app

5. 第四阶段:外部访问——Ingress与网络策略

对于需要从集群外部(如互联网)访问的服务,仅有Service(NodePort/LoadBalancer)可能还不够优雅或功能不足。这时就需要Ingress

5.1 Ingress:HTTP(S)流量的智能路由器

Ingress不是一种服务类型,而是一个API对象,它定义了从外部到集群内服务的HTTP和HTTPS路由规则(基于主机名和路径)。需要一个Ingress Controller(如Nginx Ingress Controller、Traefik)来具体实现这些规则。

一个典型的Ingress配置如下:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: / # Nginx Ingress特定注解
spec:
  ingressClassName: nginx # 指定Ingress Controller
  rules:
  - host: app.mycompany.com # 域名
    http:
      paths:
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: api-service
            port:
              number: 80
      - path: /
        pathType: Prefix
        backend:
          service:
            name: web-service
            port:
              number: 80

Ingress Controller会监听Ingress对象的变化,并动态更新其负载均衡器(如Nginx)的配置,将app.mycompany.com/api的流量导向api-service,将根路径的流量导向web-service

5.2 NetworkPolicy:Pod间的网络防火墙

默认情况下,K8s集群内的Pod网络是扁平的,所有Pod可以互相通信。在生产环境中,为了安全,我们可能需要实现微服务间的网络隔离。NetworkPolicy就是用来定义Pod间网络通信规则的防火墙。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-default
spec:
  podSelector: {} # 选择所有Pod
  policyTypes:
  - Ingress
  - Egress

这个策略会默认拒绝所有Pod的入站和出站流量。然后你可以创建更精细的策略来放行特定流量。注意:NetworkPolicy需要网络插件支持(如Calico、Cilium)。

6. 实战演练:部署一个Web应用并暴露服务

让我们通过一个完整的例子,将上述流程串联起来。我们将部署一个简单的Nginx应用,并通过Service和Ingress暴露它。

步骤1:创建Deployment和Service

# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx # 这个标签会被Service选中
    spec:
      containers:
      - name: nginx
        image: nginx:1.25-alpine
        ports:
        - containerPort: 80
        resources:
          requests:
            memory: "64Mi"
            cpu: "250m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /
            port: 80
          initialDelaySeconds: 2
          periodSeconds: 5
---
# nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  selector:
    app: nginx # 匹配Deployment中Pod的标签
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80
  type: ClusterIP

应用配置:

kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml

步骤2:验证部署

# 查看Pod状态
kubectl get pods -l app=nginx -o wide
# 查看Service和Endpoints
kubectl get svc nginx-service
kubectl describe endpoints nginx-service
# 在集群内临时启动一个Pod测试Service
kubectl run test-$RANDOM --rm -it --image=busybox -- sh
# 进入容器后执行
wget -O- http://nginx-service.default.svc.cluster.local

步骤3:(可选)通过Ingress暴露到外部

假设你已安装Nginx Ingress Controller。

# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nginx-ingress
spec:
  ingressClassName: nginx
  rules:
  - host: nginx.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: nginx-service
            port:
              number: 80

应用Ingress后,配置你的DNS或本地hosts文件,将nginx.example.com指向Ingress Controller的外部IP,即可通过浏览器访问。

7. 高级话题与故障排查工具箱

掌握了核心流程后,面试官可能会深入一些高级特性或故障场景。

滚动更新与回滚:这是Deployment的核心能力。通过kubectl set image或修改YAML触发更新,K8s会创建新的ReplicaSet,并逐步用新Pod替换旧Pod。如果出现问题,可以快速回滚。

kubectl rollout status deployment/nginx-deployment
kubectl rollout history deployment/nginx-deployment
kubectl rollout undo deployment/nginx-deployment --to-revision=2

就绪探针(Readiness)与存活探针(Liveness)的误用:这是另一个高频故障点。就绪探针失败,Pod会从Service的Endpoints中移除,停止接收流量;存活探针失败,Pod会被重启。切勿将存活探针设置为与就绪探针相同的敏感检查,否则一个临时性的依赖故障(如数据库连接超时)可能导致你的应用被无限重启。对于启动慢的应用,使用startupProbe来保护存活探针。

Headless Service:当你需要直接与每个Pod通信,而不是通过负载均衡时,可以创建clusterIP: None的Headless Service。DNS查询会返回所有后端Pod的IP地址,常用于有状态应用(如StatefulSet)或自定义的服务发现。

Pod间通信与网络策略:理解Pod网络模型(CNI实现)是解决复杂网络问题的基础。当Pod无法跨节点通信时,需要检查CNI插件状态、节点防火墙规则、路由表等。

最后,记住一套高效的故障排查命令组合:

# 1. 看状态
kubectl get pods,svc,ep,ing -o wide
# 2. 看详情和事件(最关键)
kubectl describe pod <pod-name>
# 3. 看日志
kubectl logs <pod-name> [-c <container>] [-f] [--previous]
# 4. 进入Pod调试
kubectl exec -it <pod-name> -- /bin/sh
# 5. 看节点和集群事件
kubectl get events --sort-by='.lastTimestamp'
kubectl describe node <node-name>

理解从Pod创建到Service暴露的完整流程,不仅仅是面试的需要,更是日常高效运维和故障排查的基石。这个过程体现了K8s声明式API和控制器模式的精髓:你描述期望状态,系统负责驱动现实状态向期望状态收敛。当你下次再被问到这个问题时,可以从用户操作出发,串联起API Server、etcd、Scheduler、kubelet、CNI、kube-proxy、Ingress Controller等组件的协作,并穿插对常见问题的分析和解决思路,这样的回答必定能让你脱颖而出。

更多推荐