前言

很多K8s新手(包括当初的我)都是从kubectl create开始的——照着教程敲一行命令,Pod就跑起来了,觉得K8s不过如此。但说实话,一旦出了问题,比如Pod一直Pending、或者ContainerCreating卡住不动,就完全懵了,因为根本不知道背后发生了什么。

我个人的建议是:学K8s,先搞清楚一个Pod从提交到运行,中间经过了哪些组件、做了什么事情。 这是理解K8s架构的起点,也是排查问题的基础。

今天我们就从部署一个最简单的nginx Pod开始,把整个流程拆解清楚。

确立目标

在动手之前,先明确我们要搞清楚的三件事:

  1. 从创建Pod的全流程入手,了解各组件的工作内容,核心组件包括:

    • kubectl:客户端命令行工具
    • kube-apiserver:集群的"大脑",所有操作的入口
    • etcd:分布式KV存储,保存集群所有状态数据
    • kube-controller-manager:控制器的大管家
    • kube-scheduler:负责Pod调度到哪个Node
    • kubelet:每个Node上的代理人,负责实际运行容器
  2. 对核心模块与引用的库有基本的认识,为后续深入源码做好铺垫

  3. 结合源码,掌握Kubernetes的核心概念,而不是停留在"会用命令"的层面

从创建Pod开始:手把手部署

编写nginx Pod的YAML

创建一个文件nginx_pod.yaml

apiVersion: v1          # API版本,Pod属于核心API组v1
kind: Pod               # 资源类型,这里是最基础的Pod
metadata:
  name: nginx-pod       # Pod的名称,集群内唯一标识
spec:
  containers:           # Pod中运行的容器列表
  - name: nginx         # 容器名称
    image: nginx:1.8    # 使用的镜像及版本
    # 注意:nginx:1.8是比较老的版本(2016年发布)
    # 实际项目建议使用 nginx:stable 或 nginx:1.25 等较新版本

小贴士:很多教程用的都是nginx:1.8nginx:latest。实际项目中,千万别用latest标签——你无法确定每次拉到的镜像版本一致,出了问题很难复现。建议始终指定明确的版本号。

使用kubectl部署Pod

# 根据YAML文件创建Pod
kubectl create -f nginx_pod.yaml
# 输出:pod/nginx-pod created

看到pod/nginx-pod created,说明Pod已经提交给API Server了。但注意,"created"只是表示请求被接受并写入etcd,并不意味着容器已经启动运行。 从created到Running,中间还有调度、拉取镜像、启动容器等好几个步骤。

观察Pod状态

# 查看Pod状态
kubectl get pod

# 输出示例:
# NAME           READY   STATUS    RESTARTS   AGE
# nginx-pod      1/1     Running   0          92s

各字段含义:

字段名含义说明
NAMEPod名称对应YAML中metadata.name的值
READY就绪状态1/1表示1个容器中1个已就绪
STATUS当前状态Running表示运行中,常见状态见下表
RESTARTS重启次数0表示容器未重启过,频繁重启需排查
AGE运行时长Pod创建后经过的时间

Pod常见状态速查:

状态含义可能原因
Pending等待调度资源不足、调度器未选中合适Node
ContainerCreating容器创建中正在拉取镜像或挂载存储
Running运行中容器已启动且正常运行
CrashLoopBackOff崩溃重启容器启动后立即退出,反复重启
ImagePullBackOff镜像拉取失败镜像地址错误或无权限
Completed已完成任务型Pod执行完毕退出

查看Pod详情

# 查看Pod的详细信息,包括事件、IP、所在Node等
kubectl describe pod nginx-pod

# 查看Pod的实时日志
kubectl logs nginx-pod

# 持续跟踪日志输出(类似tail -f)
kubectl logs -f nginx-pod

kubectl describe是排查Pod问题最常用的命令,底部的Events区域会记录Pod从创建到运行过程中的所有事件,包括调度决策、镜像拉取、容器启动等。如果Pod状态异常,第一时间看这里。

kubectl按下回车后发生了什么?

这是理解K8s架构的关键问题。很多人以为kubectl create就是直接在Node上创建容器,实际上中间经过了多个组件的协作:

kubectl                    kube-apiserver              etcd
   |                           |                         |
   |--- POST /api/v1/pods --->|                         |
   |                           |--- 写入Pod数据 -------->|
   |                           |<--- 写入成功 -----------|
   |<-- 201 Created -----------|                         |
   |                           |                         |
                               |--- watch通知 ---------> kube-scheduler
                               |                         (选择目标Node)
                               |<-- 绑定Node -----------|
                               |--- 更新etcd ---------->|
                               |                         |
                               |--- watch通知 ---------> kubelet(目标Node)
                               |                         (创建并启动容器)

完整流程拆解:

  1. kubectl 读取YAML文件,将内容序列化为JSON,向kube-apiserver发起HTTP POST请求
  2. kube-apiserver 收到请求后进行认证、鉴权、准入控制(Admission Control),校验通过后将Pod数据写入etcd
  3. kube-scheduler 通过watch机制发现新创建但未调度的Pod,根据资源需求、亲和性等规则选择一个合适的Node,将绑定信息写回etcd
  4. kubelet 通过watch机制发现被调度到本Node的Pod,调用容器运行时(containerd/Docker)拉取镜像并启动容器
  5. kubelet 持续上报Pod状态给apiserver,apiserver更新etcd中的状态数据

关键认知:K8s的组件之间不是直接调用的,而是通过apiserver+etcd作为中间层,采用watch/list的声明式机制。这意味着任何组件挂掉重启后,都能从etcd恢复状态,这是K8s高可用的基础。

踩坑实录

坑1:镜像版本踩坑——nginx:1.8的坑

现象:按照教程使用nginx:1.8,在某些新版本的containerd环境下容器启动失败。

根因nginx:1.8发布于2016年,基于非常老的Debian基础镜像,可能存在glibc兼容性问题,且包含大量已知安全漏洞。

解决方案:使用较新版本的nginx镜像:

spec:
  containers:
  - name: nginx
    image: nginx:1.25       # 推荐使用明确的稳定版本
    # image: nginx:stable   # 或者使用stable标签

预防措施:项目中建立镜像版本白名单机制,禁止使用超过2年未更新的镜像版本。

坑2:Pod状态卡在Pending

现象kubectl get pod显示STATUS一直是Pending,等了好几分钟都没变化。

根因:最常见的原因是集群Node资源不足。比如Node的CPU或内存已经分配完了,scheduler找不到满足Pod资源需求的Node。

排查步骤

# 第一步:查看Pod事件
kubectl describe pod nginx-pod | grep -A 20 Events

# 第二步:查看Node资源使用情况
kubectl describe node <node-name> | grep -A 5 Allocatable

# 第三步:查看所有Pod的资源请求
kubectl get pods -A -o wide

解决方案

  • 释放不用的Pod资源
  • 在YAML中设置合理的resources.requestsresources.limits
  • 扩容集群Node

坑3:kubectl命令执行报错"connection refused"

现象:执行任何kubectl命令都报The connection to the server x.x.x.x:6443 was refused

根因:kubeconfig配置错误或API Server未运行。新手最常犯的错误是没有正确配置kubeconfig,特别是使用云厂商托管集群时,需要先下载并配置kubeconfig文件。

解决方案

# 检查kubeconfig路径
echo $KUBECONFIG

# 查看当前context
kubectl config current-context

# 如果是云厂商集群,按文档下载kubeconfig并设置环境变量
export KUBECONFIG=/path/to/kubeconfig

生产环境Pod部署检查清单

在把Pod部署到生产环境之前,建议逐项检查:

  • YAML中指定了明确的镜像版本号(禁止使用latest
  • 设置了resources.requestsresources.limits
  • 配置了livenessProbereadinessProbe健康检查
  • 设置了合理的imagePullPolicy(建议IfNotPresent
  • 配置了restartPolicy(默认Always,确认符合预期)
  • 如需持久化,已配置volumeMountsvolumes
  • 镜像来源可信,已通过安全扫描

注意:上面的检查清单中,生产环境最不能省的是resources限制健康检查。我见过太多因为没设resources导致一个Pod吃光Node内存、拖垮整个节点的生产事故。

总结

从一个最简单的kubectl create命令出发,我们拆解了Pod从提交到运行的完整链路:

  • kubectl负责将YAML提交给apiserver
  • apiserver是所有操作的入口,数据最终持久化到etcd
  • scheduler负责为Pod选择合适的Node
  • kubelet在目标Node上实际创建和运行容器
  • 各组件通过watch机制协作,而非直接调用

理解这个流程,是排查K8s问题的基础。下次Pod状态异常时,你就知道该从哪个环节入手了。

你踩过这些坑吗?

  1. 你的第一个K8s Pod部署遇到了什么问题?是怎么解决的?
  2. 在生产环境中,你用什么策略来管理Pod的镜像版本?
  3. 除了上面提到的三个坑,你在K8s部署中还踩过哪些坑?

延伸思考

  • 如果Pod需要访问外部服务,网络策略该怎么配置?
  • 在多Node集群中,scheduler如何决定Pod调度到哪个Node?亲和性和反亲和性怎么用?
  • 当Pod的容器数量>1(Sidecar模式),READY字段显示2/2意味着什么?两个容器的生命周期如何关联?

求助与交流

如果你在部署K8s Pod的过程中遇到了问题,或者有更好的实践方案,欢迎在评论区交流。也欢迎分享你踩过的坑,我会整理补充到文章中。

更多推荐