侧边栏壁纸
  • 累计撰写 22 篇文章
  • 累计创建 5 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录
k8s

K8s-部署指南-001

Kubernetes (K8s) 部署指南

版本: 1.0
更新时间: 2025-06-07
维护者: guobin


前言

什么是 Kubernetes?

想象一下:你经营一支庞大的物流车队(服务器集群),有 100 辆卡车(节点),每辆卡车上要装不同的货物(应用容器)。你需要决定哪辆卡车装什么货物,哪辆车坏了要把货转到其他车上,哪个路线最优……一个人根本管不过来。

Kubernetes(K8s)就像车队的"总调度室",它自动帮你:

  • 分配货物到合适的卡车(Pod 调度)
  • 卡车坏了自动换车(故障自愈)
  • 订单多了自动加车(弹性伸缩)
  • 保证货物安全送达(服务发现和负载均衡)

Kubernetes 的名字来源于希腊语,意思是"舵手"或"领航员"。它的缩写 K8s 是因为 K 和 s 之间有 8 个字母。

本文档会告诉你:

  • Kubernetes 的核心概念和架构
  • 环境准备和 K8s 集群安装步骤
  • 如何部署应用到 K8s
  • 服务管理、扩缩容、日志监控
  • 常见问题排查和解决方案

目录

  1. 核心概念
  2. 环境准备要求
  3. 安装 Kubernetes 集群
  4. 部署应用到 K8s
  5. 服务管理
  6. 扩缩容与更新
  7. 日志与监控
  8. 数据持久化
  9. 常见问题排查
  10. 总结

一、核心概念

1.1 K8s 架构

                  ┌─────────────────┐
                  │   Master 节点    │  ← 大脑(调度中心)
                  │  (控制平面)      │
                  └────────┬────────┘
                           │
            ┌──────────────┼──────────────┐
            │              │              │
     ┌──────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
     │ Worker 节点1 │ │ Worker   │ │ Worker 节点3 │  ← 身体(干活的)
     │            │ │  节点2   │ │              │
     │ [Pod][Pod] │ │ [Pod]    │ │ [Pod][Pod]   │
     └────────────┘ └──────────┘ └──────────────┘

1.2 必须知道的概念

概念 通俗解释 生活比喻
Cluster K8s 管理的整个服务器集群 整个物流公司
Node 集群中的一台服务器 一辆卡车
Master 控制节点,负责调度和管理 调度中心
Worker 工作节点,运行实际的应用 干活的卡车
Pod K8s 最小的部署单元,包含一个或多个容器 一个货物包裹
Deployment 管理 Pod 的控制器,保证指定数量的 Pod 运行 包裹管理器(丢了会自动补发)
Service 给 Pod 提供一个稳定的访问地址 快递柜(不管包裹怎么变,快递柜地址不变)
Namespace 命名空间,用来隔离不同项目/环境 不同的仓库区域
ConfigMap 存储配置文件 说明书(告诉应用怎么运行)
Secret 存储敏感信息(密码、证书) 保险箱(存放重要信息)
PersistentVolume 持久化存储 仓库(数据永久存放的地方)
Ingress 外部访问入口,反向代理 公司大门(统一入口,分配到不同部门)

1.3 Pod 详解

┌─────────────────────────────────┐
│            Pod                   │
│  ┌───────────┐  ┌───────────┐   │
│  │ Container1│  │ Container2│   │  ← 容器(你的应用)
│  │  (主应用)  │  │ (Sidecar) │   │
│  └───────────┘  └───────────┘   │
│         ┌─────────────┐         │
│         │  Shared      │         │  ← 共享网络(Pod 内的容器共享 IP 和端口)
│         │  Network     │         │
│         └─────────────┘         │
└─────────────────────────────────┘

通俗解释:Pod 就像一个"快递盒子",里面可以装一个或多个容器。同一盒子里的容器共享网络(同一个 IP 地址),就像住在同一套房子里的人共享一个门牌号。


二、环境准备要求

2.1 硬件要求

资源 最低要求(学习) 推荐(生产) 说明
CPU 2 核 4 核+ Master 节点至少 2 核
内存 2 GB 8 GB+ Master 节点至少 2GB
磁盘 20 GB 50 GB+ SSD 更佳
网络 互通即可 千兆网络 节点间需要通信

2.2 软件要求

软件 版本要求 说明
操作系统 Ubuntu 20.04+ / CentOS 7+ 推荐 Ubuntu
Docker/Containerd 最新稳定版 容器运行时
kubeadm 1.28+ 集群初始化工具
kubectl 1.28+ K8s 命令行工具
kubelet 1.28+ 节点代理服务

2.3 环境检查

# 👀 检查操作系统版本
cat /etc/os-release

# 输出示例:
# NAME="Ubuntu"
# VERSION="20.04.6 LTS (Focal Fossa)"
# ...
# 解读:确认是 Ubuntu 20.04 或更高版本
# 👀 检查 CPU 核心数
nproc

# 输出:4
# 解读:4 核 CPU,满足最低要求(至少 2 核)
# 👀 检查内存大小
free -h

# 输出示例:
#               total        used        free      shared  buff/cache   available
# Mem:          7.8Gi       1.2Gi       5.1Gi       256Mi       1.5Gi       6.2Gi
# 解读:7.8GB 内存,满足要求
# 👀 检查磁盘空间
df -h /

# 输出示例:
# Filesystem  Size  Used  Avail  Use%  Mounted on
# /dev/sda1    50G   12G     38G   24%  /
# 解读:38GB 可用空间,满足要求
# 👀 检查端口占用(K8s 需要的端口)
# Master 节点需要:6443, 2379-2380, 10250-10252
# Worker 节点需要:10250, 30000-32767

sudo ss -tuln | grep -E '6443|2379|2380|10250|30000'

# 如果无输出,说明端口空闲,可以安装 ✅

三、安装 Kubernetes 集群

3.0 系统前置配置(必须执行)

⚠️ 重要:在 Ubuntu 20.04+ 上安装 K8s 前,必须完成以下 3 个步骤。
跳过这些会导致 kubeadm init 预检失败。

# 🔧 1. 关闭 Swap(kubelet 默认拒绝在开启 Swap 的节点上启动)
sudo swapoff -a

# 永久关闭:注释掉 /etc/fstab 中的 swap 行
sudo sed -i '/swap/d' /etc/fstab

# 验证
free -h | grep Swap
# 输出:Swap: 0B   ← 确认 Swap 已关闭 ✅
# 🔧 2. 加载必要的内核模块
sudo modprobe overlay
sudo modprobe br_netfilter

# 设置开机自动加载
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
# 🔧 3. 设置 sysctl 参数(K8s 网络转发必需)
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF

# 使配置生效
sudo sysctl --system

# 验证
sysctl net.bridge.bridge-nf-call-iptables
sysctl net.ipv4.ip_forward
# 输出都应该是 = 1 ✅

3.1 安装容器运行时(Containerd)

什么是 Containerd?

如果说 Docker 是一个完整的集装箱管理系统,Containerd 就是其中最核心的"集装箱引擎"。
K8s 从 1.24 版本开始不再直接支持 Docker,改用更轻量的 Containerd 作为容器运行时。

# 🚀 安装 Containerd
sudo apt update

sudo apt install -y containerd

# 参数解释:
# apt update:更新软件包列表
# install -y:安装软件包(-y 自动确认,不需要输入 y)

# 生成默认配置
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml

# 修改配置,使用 systemd 作为 cgroup 驱动
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

# ⚠️ 注意:在较新的 containerd v2.x 中,SystemdCgroup 默认已经是 true
# 如果 sed 替换时提示找不到匹配,说明已经是 true,无需修改

# 国内环境额外配置:修改 pause 镜像地址(registry.k8s.io 被墙)
sudo sed -i "s|sandbox = 'registry.k8s.io/pause:.*'|sandbox = 'registry.aliyuncs.com/google_containers/pause:3.10'|" /etc/containerd/config.toml

# 参数解释:
# sandbox 是 K8s 的 pause 容器镜像,默认从 registry.k8s.io 拉取
# 国内无法访问 registry.k8s.io,需要改成阿里云镜像源

# 启动并设置开机自启
sudo systemctl restart containerd
sudo systemctl enable containerd

# 验证
containerd --version

# 输出示例:
# containerd containerd.io 1.7.11
# 解读:看到版本号 = 安装成功 ✅

3.2 安装 K8s 工具

# 🚀 添加 K8s 软件源
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg

echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list

# 参数解释:
# curl -fsSL:下载密钥
# gpg --dearmor:将密钥转换为 apt 可识别的格式
# echo ... | tee:写入软件源配置

# 🚀 安装 kubeadm、kubelet、kubectl
sudo apt update
sudo apt install -y kubelet kubeadm kubectl

# 参数解释:
# kubeadm:集群初始化工具(就像装修队长,负责搭建)
# kubelet:节点代理服务(每个节点上都有,负责执行 Master 的指令)
# kubectl:命令行工具(你用这个命令和 K8s 对话)

# 锁定版本(防止意外更新)
sudo apt-mark hold kubelet kubeadm kubectl

# 启动 kubelet
sudo systemctl enable --now kubelet

# 验证安装
kubeadm version
kubectl version --client

# 输出示例:
# kubeadm version: &version.Info{Major:"1", Minor:"28", GitVersion:"v1.28.4"}
# Client Version: v1.28.4
# 解读:看到版本号 = 安装成功 ✅

3.3 初始化 Master 节点

# 🚀 初始化 K8s Master 节点
sudo kubeadm init \
  --pod-network-cidr=10.244.0.0/16 \
  --image-repository registry.aliyuncs.com/google_containers

# 参数解释:
# --pod-network-cidr=10.244.0.0/16:Pod 网络的 IP 范围(10.244.x.x)
#   就像给每个包裹分配一个地址段
#   10.244.0.0/16 是 Flannel 网络的默认范围
# --image-repository registry.aliyuncs.com/google_containers:国内镜像源
#   默认镜像源 registry.k8s.io 在国内无法访问,会卡住
#   使用阿里云镜像源替代
#
# ⚠️ 关于 --apiserver-advertise-address:
#   文档旧版本使用 --apiserver-advertise-address=0.0.0.0
#   不推荐!kubeadm 会自动检测本机 IP,不需要手动指定
#   如果多网卡需要指定,请填写本机内网 IP(如 192.168.1.100),不要填 0.0.0.0

# 输出示例(成功时):
# Your Kubernetes control-plane has initialized successfully!
#
# To start using your cluster, you need to run the following:
#   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...
#
# Then you can join any number of worker nodes by running the following:
# kubeadm join 192.168.1.100:6443 --token abcdef.123456 \
#   --discovery-token-ca-cert-hash sha256:xxxxx

# ⚠️ 重要:保存最后的 kubeadm join 命令!
# 这是 Worker 节点加入集群的钥匙,丢了要重新生成

3.4 配置 kubectl 访问权限

# ⚙️ 配置 kubectl(让当前用户可以使用 kubectl 命令)
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 参数解释:
# mkdir -p:创建 .kube 目录(存放配置文件)
# cp -i:复制配置文件(-i 如果已存在会提示覆盖)
# chown:修改文件所有者为当前用户

# 验证配置
kubectl cluster-info

# 输出示例:
# Kubernetes control plane is running at https://192.168.1.100:6443
# CoreDNS is running at https://192.168.1.100:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
# 解读:看到集群信息 = 配置成功 ✅

3.5 安装网络插件(Flannel)

什么是 Flannel?

想象一下:每辆卡车(Node)上的货物(Pod)需要互相通信,但每辆卡车的内部地址只有车上的人知道。
Flannel 就像一个"内部电话系统",让不同卡车上的货物能互相找到并通信。

# 🚀 安装 Flannel 网络插件
# 方式一:官方源(国外服务器可用)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

# 方式二:国内服务器(GitHub 被墙时使用)
curl -fsSL https://gitee.com/mirrors/flannel/raw/master/Documentation/kube-flannel.yml | kubectl apply -f -

# 参数解释:
# apply -f:应用配置文件
# URL:Flannel 的最新配置(自动下载并应用)

# ⚠️ 注意:Flannel 镜像(ghcr.io/flannel-io/flannel)拉取可能较慢
# 如果 Pod 长时间处于 Init 或 ContainerCreating 状态,请耐心等待
# 可用 kubectl describe pod -n kube-flannel 查看拉取进度

# 验证安装
kubectl get pods -n kube-system | grep flannel

# 输出示例:
# NAME                          READY   STATUS    RESTARTS   AGE
# kube-flannel-ds-abc12         1/1     Running   0          30s

# 解读:
# - READY 1/1:Pod 就绪 ✅
# - STATUS Running:正在运行 ✅

3.6 验证集群状态

# 👀 检查节点状态
kubectl get nodes

# 输出示例:
# NAME     STATUS   ROLES           AGE   VERSION
# master   Ready    control-plane   5m    v1.28.4

# 解读:
# - STATUS Ready:节点就绪,可以接受工作负载 ✅
# - STATUS NotReady:节点未就绪(可能网络插件还没装好)
# - ROLES control-plane:这是 Master 节点
# 👀 检查系统 Pod 状态
kubectl get pods -n kube-system

# 输出示例:
# NAME                                  READY   STATUS    RESTARTS   AGE
# coredns-5d78c9869d-abc12              1/1     Running   0          5m
# coredns-5d78c9869d-def34              1/1     Running   0          5m
# etcd-master                           1/1     Running   0          5m
# kube-apiserver-master                 1/1     Running   0          5m
# kube-controller-manager-master        1/1     Running   0          5m
# kube-flannel-ds-abc12                 1/1     Running   0          1m
# kube-proxy-xyz56                      1/1     Running   0          5m
# kube-scheduler-master                 1/1     Running   0          5m

# 解读:
# 所有 Pod 都是 Running 状态 = 集群健康 ✅
# 如果有 Pending 或 CrashLoopBackOff,需要排查

⚠️ 单节点学习环境必做:Master 节点默认带 node-role.kubernetes.io/control-plane:NoSchedule 污点,
业务 Pod 会被卡在 Pending生产多节点集群不要做这一步

# 🚀 移除 control-plane 节点的 NoSchedule 污点
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

# 验证:再次部署业务 Pod 应能成功 Running(参考第四章)

多节点集群的正确做法:把业务 Pod 加 tolerations 容忍这个污点,或加入 worker 节点。

3.7 安装 Metrics Server(HPA / kubectl top 依赖)

⚠️ 必须安装kubectl top 和 HPA(自动扩缩容)都依赖 Metrics Server。
kubeadm 不会自动安装它,必须手动部署。不安装的话:

  • kubectl top nodes 会报错 Metrics API not available
  • HPA 的 TARGETS 列会显示 <unknown>,无法自动伸缩
# 🚀 安装 Metrics Server
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml

# ⚠️ 国内环境:GitHub 可能被墙,可以先下载再 apply
# curl -fsSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -o metrics-server.yaml
# kubectl apply -f metrics-server.yaml

⚠️ 两个常见问题

问题 1:Metrics Server 镜像 registry.k8s.io/metrics-server/metrics-server:v0.8.1 被墙

解决:修改 YAML 中的镜像地址为阿里云源

# 下载 YAML
curl -fsSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -o metrics-server.yaml

# 替换镜像地址
sed -i 's|registry.k8s.io/metrics-server/metrics-server:|registry.aliyuncs.com/google_containers/metrics-server:|g' metrics-server.yaml

# 应用
kubectl apply -f metrics-server.yaml

问题 2:Pod 启动后 Readiness 失败,报 x509: cannot validate certificate

原因:kubeadm 环境的 kubelet 证书不包含 IP SAN,Metrics Server 默认不信任

解决:添加 --kubelet-insecure-tls 参数

kubectl patch deployment metrics-server -n kube-system --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

问题 3:添加 --kubelet-insecure-tls 后 Pod 仍反复重启,日志持续 Failed to scrape node ... 403 Forbidden

原因:kubelet 默认 authorization.mode: Webhook,会对 metrics-server 的请求做 RBAC 校验;并且 components.yaml 自带的 system:aggregated-metrics-reader 只授予"读 metrics.k8s.io API"的权限,不包含直接 list nodes/stats 的权限

解决(学习/测试环境两步):

# 步骤 1:把 kubelet 授权模式改为 AlwaysAllow(仅学习环境用)
sudo sed -i 's/  mode: Webhook/  mode: AlwaysAllow/' /var/lib/kubelet/config.yaml
sudo systemctl restart kubelet

# 步骤 2:补 ClusterRole + Binding 给 metrics-server 读取 nodes/stats
cat <<'EOF' | kubectl apply -f -
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: metrics-server
rules:
- apiGroups: [""]
  resources: ["pods", "nodes", "nodes/stats", "namespaces", "services"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: metrics-server
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: metrics-server
subjects:
- kind: ServiceAccount
  name: metrics-server
  namespace: kube-system
EOF

# 步骤 3:重启 metrics-server 让配置生效
kubectl rollout restart deployment metrics-server -n kube-system

生产环境不要用 mode: AlwaysAllow;正确做法是给 metrics-server SA 签发 client cert,然后用 --kubelet-client-certificate / --kubelet-client-key 走 mTLS。

# 验证安装
kubectl get pods -n kube-system | grep metrics-server

# 输出示例:
# NAME                              READY   STATUS    RESTARTS   AGE
# metrics-server-5f8c78b5d6-abc12   1/1     Running   0          30s

# ⚠️ 等 1-2 分钟后 Metrics Server 才开始收集到数据
kubectl top nodes
kubectl top pods

# 输出示例:
# NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# master   250m         12%    1500Mi          38%

四、部署应用到 K8s

4.1 创建一个 Deployment

什么是 Deployment?

想象一下:你需要雇 3 个客服(Pod 副本),如果有人请假(Pod 故障),Deployment 会自动招一个新人顶上。
你只需要告诉 Deployment"我要 3 个客服",它就会自动帮你管理。

创建 nginx-deployment.yaml

apiVersion: apps/v1
# 是什么:API 版本(apps/v1 是 Deployment 的当前版本)
# 就像软件的版本号,K8s 根据这个知道如何处理这个资源

kind: Deployment
# 是什么:资源类型(Deployment)
# 告诉 K8s 我们要创建一个部署控制器

metadata:
  # 是什么:元数据(资源的基本信息)
  name: nginx-deployment
  # 是什么:Deployment 的名称
  # 怎么用:改成你想要的名字
  labels:
    app: nginx
    # 是什么:标签(key-value 形式)
    # 为什么:用来标识和选择资源,后续 Service 会用到
    # 怎么用:可以自由定义,如 env: prod, team: backend

spec:
  # 是什么:期望的状态(告诉 K8s 你想要的样子)
  replicas: 3
  # 是什么:副本数量(要运行几个 Pod)
  # 为什么:3 个副本保证高可用,一个挂了还有其他
  # 怎么用:根据你的需求调整

  selector:
    matchLabels:
      app: nginx
      # 是什么:选择器(告诉 Deployment 管理哪些 Pod)
      # 为什么:Deployment 通过这个标签找到它管理的 Pod
      # 注意:必须和 template.metadata.labels 一致

  template:
    # 是什么:Pod 模板(新 Pod 的蓝图)
    # 为什么:Deployment 按照这个模板创建 Pod
    metadata:
      labels:
        app: nginx
        # 是什么:Pod 的标签
        # 注意:必须和 selector.matchLabels 匹配
    spec:
      containers:
        - name: nginx
          # 是什么:容器名称
          image: nginx:1.25
          # 是什么:使用的 Docker 镜像
          # 为什么:nginx:1.25 是当前稳定版
          # 怎么用:可以改成其他版本,如 nginx:latest
          
          ports:
            - containerPort: 80
              # 是什么:容器暴露的端口
              # 为什么:Nginx 默认监听 80 端口
              # 怎么用:根据应用实际端口修改

          resources:
            # 是什么:资源限制
            # 为什么:防止单个 Pod 占用过多资源
            requests:
              # 是什么:资源请求(保证分配的最小资源)
              memory: "64Mi"
              cpu: "100m"
              # 100m = 0.1 个 CPU 核心(m = milli)
            limits:
              # 是什么:资源上限(不能超过这个限制)
              memory: "128Mi"
              cpu: "250m"

          # 是什么:健康检查(就绪探针)
          # 为什么:K8s 通过这个检查知道 Pod 什么时候才算准备好接收流量
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            # Pod 启动后等多久开始检查(Nginx 启动需要时间)
            periodSeconds: 10
            # 每隔多久检查一次
            failureThreshold: 3
            # 连续失败几次才认为不健康

          # 是什么:健康检查(存活探针)
          # 为什么:K8s 通过这个检查知道 Pod 是不是还活着,不活了就重启
          livenessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 20
            failureThreshold: 3

4.2 部署应用

# 🚀 部署 Nginx
kubectl apply -f nginx-deployment.yaml

# 参数解释:
# apply -f:应用配置文件(创建或更新资源)
# nginx-deployment.yaml:配置文件路径

# 输出示例:
# deployment.apps/nginx-deployment created
# 解读:Deployment 创建成功 ✅

4.3 检查部署状态

# 👀 查看 Deployment 状态
kubectl get deployment nginx-deployment

# 输出示例:
# NAME               READY   UP-TO-DATE   AVAILABLE   AGE
# nginx-deployment   3/3     3            3           2m

# 解读:
# READY 3/3:期望 3 个副本,3 个都就绪 ✅
# UP-TO-DATE 3:3 个副本已经是最新版本
# AVAILABLE 3:3 个副本可用,可以接收流量
# 如果 READY 不是 3/3,说明 Pod 还没准备好
# 👀 查看 Pod 状态
kubectl get pods -l app=nginx

# 参数解释:
# -l app=nginx:通过标签筛选(只显示 app=nginx 的 Pod)

# 输出示例:
# NAME                                READY   STATUS    RESTARTS   AGE
# nginx-deployment-6b4c4b5f7a-abc12   1/1     Running   0          2m
# nginx-deployment-6b4c4b5f7a-def34   1/1     Running   0          2m
# nginx-deployment-6b4c4b5f7a-ghi56   1/1     Running   0          2m

# 解读:
# - READY 1/1:每个 Pod 的 1 个容器都就绪 ✅
# - STATUS Running:正在运行
# - RESTARTS 0:没有重启过(说明很健康)
# - AGE 2m:运行了 2 分钟
# 👀 查看 Pod 详细信息(如果状态异常)
kubectl describe pod nginx-deployment-6b4c4b5f7a-abc12

# 参数解释:
# describe:查看资源的详细信息(包括事件、状态、配置)
# 后面跟 Pod 名称(从 kubectl get pods 获取)

# 输出中的关键字段:
# Conditions:Pod 的各种状态
# Events:最近发生的事件(排查问题必看)
# 示例事件:
#   Normal   Scheduled    Successfully assigned default/nginx-deployment-xxx to node1
#   Normal   Pulling      Pulling image "nginx:1.25"
#   Normal   Pulled       Successfully pulled image "nginx:1.25"
#   Normal   Created      Created container nginx
#   Normal   Started      Started container nginx

五、服务管理

5.1 创建 Service(暴露服务)

什么是 Service?

Pod 就像临时工,随时可能换人(重建、迁移)。如果直接访问 Pod 的 IP,下次 Pod 重建 IP 就变了。
Service 就像一个"固定前台",不管后面的人员怎么换,前台的地址永远不变。
你把请求发给前台,前台会自动转给后面的工作人员。

创建 nginx-service.yaml

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  # Service 的名称

spec:
  type: NodePort
  # 是什么:Service 类型
  # 为什么:NodePort 让外部可以通过节点 IP + 端口访问服务
  # 其他选项:
  #   ClusterIP:仅集群内访问(默认)
  #   NodePort:节点端口访问(外部可访问)
  #   LoadBalancer:云负载均衡器(云环境使用)
  
  selector:
    app: nginx
    # 是什么:选择器
    # 为什么:告诉 Service 把流量转发给哪些 Pod
    # 注意:必须和 Pod 的标签匹配

  ports:
    - protocol: TCP
      port: 80
      # 是什么:Service 端口(集群内访问的端口)
      targetPort: 80
      # 是什么:目标端口(容器的端口)
      nodePort: 30080
      # 是什么:节点端口(外部访问的端口,范围 30000-32767)
      # 为什么:通过这个端口,外部可以访问到 Service
# 🚀 创建 Service
kubectl apply -f nginx-service.yaml

# 输出:
# service/nginx-service created
# 👀 查看 Service
kubectl get svc nginx-service

# 输出示例:
# NAME            TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
# nginx-service   NodePort   10.96.100.50   <none>        80:30080/TCP   1m

# 解读:
# - TYPE NodePort:外部可访问
# - CLUSTER-IP 10.96.100.50:集群内访问地址
# - PORT(S) 80:30080/TCP:
#   - 80:Service 端口(集群内用)
#   - 30080:NodePort(外部访问用)
#   - 外部访问地址:节点IP:30080

5.2 访问服务

# 🔌 集群内访问(ClusterIP)
curl http://10.96.100.50

# 输出:
# <!DOCTYPE html>
# <html>
# <head><title>Welcome to nginx!</title></head>
# <body><h1>Welcome to nginx!</h1></body>
# </html>
# 解读:看到 Nginx 欢迎页面 = 服务正常 ✅
# 🔌 外部访问(NodePort)
curl http://<节点IP>:30080

# 示例:
curl http://192.168.1.100:30080

5.3 查看所有 Service

# 👀 查看集群中的所有 Service
kubectl get svc

# 输出示例:
# NAME            TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)                  AGE
# kubernetes      ClusterIP   10.96.0.1      <none>        443/TCP                  1h
# nginx-service   NodePort    10.96.100.50   <none>        80:30080/TCP             1m

# 解读:
# kubernetes:API Server 的 Service(内部使用)
# nginx-service:我们创建的 Service

六、扩缩容与更新

6.1 手动扩缩容

# ⚡ 扩容到 5 个副本
kubectl scale deployment nginx-deployment --replicas=5

# 参数解释:
# scale:调整副本数
# --replicas=5:目标副本数

# 验证
kubectl get deployment nginx-deployment

# 输出示例:
# NAME               READY   UP-TO-DATE   AVAILABLE   AGE
# nginx-deployment   5/5     5            5           30m
# 解读:5/5 = 5 个副本都就绪 ✅
# ⏹️ 缩容到 2 个副本
kubectl scale deployment nginx-deployment --replicas=2

# 验证
kubectl get pods -l app=nginx

# 输出示例:
# NAME                                READY   STATUS        RESTARTS   AGE
# nginx-deployment-xxx-abc12          1/1     Running       0          30m
# nginx-deployment-xxx-def34          1/1     Running       0          30m
# nginx-deployment-xxx-ghi56          1/1     Terminating   0          30m  ← 正在关闭
# 解读:Terminating 表示 Pod 正在被优雅地关闭

6.2 自动扩缩容(HPA)

什么是 HPA?

Horizontal Pod Autoscaler(水平 Pod 自动伸缩器)就像餐厅的智能排班系统:

  • 客人多了(CPU/内存使用率高),自动加人
  • 客人少了,自动减人

⚠️ 前置条件:必须先安装 Metrics Server(见 3.7 节)
没有 Metrics Server 的话,HPA 的 TARGETS 会显示 <unknown>,无法工作。

⚠️ 另一个必要条件:Deployment 必须设置 resources.requests.cpu
HPA 需要通过 CPU 使用率来判断是否扩缩容。如果 Deployment 没有设置
resources.requests.cpu,HPA 会报错:
failed to get cpu utilization: missing request for cpu
本文档 4.1 节的 nginx-deployment.yaml 已包含资源请求配置。

# ⚡ 创建 HPA(根据 CPU 自动扩缩容)
kubectl autoscale deployment nginx-deployment \
  --min=2 \
  --max=10 \
  --cpu-percent=70

# 参数解释:
# autoscale:创建自动伸缩规则
# --min=2:最少 2 个副本
# --max=10:最多 10 个副本
# --cpu-percent=70:当 CPU 使用率超过 70% 时扩容

# 查看 HPA 状态
kubectl get hpa

# 输出示例:
# NAME               REFERENCE                     TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
# nginx-deployment   Deployment/nginx-deployment   15%/70%   2         10        5          10m

# 解读:
# TARGETS 15%/70%:当前 CPU 15%,阈值 70%
# 15% < 70%:不需要扩容
# 如果变成 80%/70%,就会自动扩容

6.3 滚动更新

什么是滚动更新?

想象一下:你要给 10 辆卡车换轮胎(升级版本)。
滚动更新就是一次只换 1-2 辆车的轮胎,换好一辆再换下一辆。
这样在更新过程中,始终有车可以拉货(服务不中断)。

# 🚀 更新镜像版本
kubectl set image deployment/nginx-deployment nginx=nginx:1.26

# 参数解释:
# set image:更新容器镜像
# nginx=nginx:1.26:把 nginx 容器的镜像改成 1.26 版本
#
# ⚠️ 注意:旧版文档中使用的 --record 参数已弃用
# kubectl set image deployment/nginx-deployment nginx=nginx:1.26 --record
# 会提示:Flag --record has been deprecated

# 查看更新进度
kubectl rollout status deployment/nginx-deployment

# 输出示例:
# Waiting for deployment "nginx-deployment" rollout to finish:
# 2 out of 3 new replicas have been updated...
# deployment "nginx-deployment" successfully rolled out
# 解读:更新完成 ✅
# 👀 查看更新历史
kubectl rollout history deployment/nginx-deployment

# 输出示例:
# REVISION  CHANGE-CAUSE
# 1         <none>
# 2         <none>

# 解读:
# 1:初始版本
# 2:更新后的版本
# ⏹️ 回滚到上一个版本
kubectl rollout undo deployment/nginx-deployment

# 回滚到指定版本
kubectl rollout undo deployment/nginx-deployment --to-revision=1

# 验证回滚
kubectl get pods -l app=nginx

七、日志与监控

7.1 查看 Pod 日志

# 📋 查看 Pod 日志
kubectl logs nginx-deployment-6b4c4b5f7a-abc12

# 输出示例(Nginx 访问日志):
# 10.244.0.1 - - [06/Jun/2025:10:00:00 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.68.0"
# 📋 实时查看日志(类似 tail -f)
kubectl logs -f nginx-deployment-6b4c4b5f7a-abc12

# 参数解释:
# -f:follow 模式,实时滚动显示新日志
# 按 Ctrl+C 退出
# 📋 查看最近 100 行日志
kubectl logs --tail=100 nginx-deployment-6b4c4b5f7a-abc12

# 📋 查看所有副本的日志
kubectl logs -l app=nginx --tail=50

# 参数解释:
# -l app=nginx:通过标签选择所有匹配的 Pod
# 会输出所有 nginx Pod 的日志

7.2 进入 Pod 内部

# 🔌 进入 Pod 的容器内部
# 注意:kubectl exec 不支持 -l 标签选择器,需要指定具体 Pod 名称
# 可以先用 kubectl get pods -l app=nginx 获取 Pod 名称
kubectl exec -it nginx-deployment-6b4c4b5f7a-abc12 -- bash

# 小技巧:用变量自动获取第一个 Pod 名称
POD=$(kubectl get pod -l app=nginx -o jsonpath='{.items[0].metadata.name}')
kubectl exec -it $POD -- bash

# 参数解释:
# exec:在容器内执行命令
# -it:交互式终端
# -- bash:执行 bash shell(也可以用 sh)

# 进入后可以看到容器的文件系统:
# / # ls
# bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 退出
exit
# 🔌 执行单条命令(不需要进入容器)
kubectl exec nginx-deployment-6b4c4b5f7a-abc12 -- cat /etc/nginx/nginx.conf

# 参数解释:
# -- cat ...:在容器内执行 cat 命令
# 可以看到 Nginx 的配置文件内容

7.3 监控集群状态

# 👀 查看所有资源状态
kubectl get all

# 输出示例:
# NAME                                    READY   STATUS    RESTARTS   AGE
# pod/nginx-deployment-6b4c4b5f7a-abc12   1/1     Running   0          1h
# pod/nginx-deployment-6b4c4b5f7a-def34   1/1     Running   0          1h
# pod/nginx-deployment-6b4c4b5f7a-ghi56   1/1     Running   0          1h
#
# NAME                      TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
# service/kubernetes        ClusterIP   10.96.0.1      <none>        443/TCP        2h
# service/nginx-service     NodePort    10.96.100.50   <none>        80:30080/TCP   1h
#
# NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
# deployment.apps/nginx-deployment   3/3     3            3           1h

# ⚠️ 注意:kubectl get all 不会显示所有资源类型
# 它会遗漏:Endpoints, NetworkPolicy, PVC, ConfigMap, Secret 等
# 建议用 kubectl get pods,svc,deployments 分别查看,而不是依赖 get all
# 👀 查看节点资源使用情况
kubectl top nodes

# 输出示例:
# NAME     CPU(cores)   CPU%   MEMORY(bytes)   MEMORY%
# master   250m         12%    1500Mi          38%
# 解读:
# - CPU 使用 250m(0.25 核),占总量的 12%
# - 内存使用 1500MB,占总量的 38%
# 如果 CPU% 或 MEMORY% 超过 80%,需要关注
# 👀 查看 Pod 资源使用情况
kubectl top pods

# 输出示例:
# NAME                                CPU(cores)   MEMORY(bytes)
# nginx-deployment-6b4c4b5f7a-abc12   2m           10Mi
# 解读:
# - CPU 2m(0.002 核),非常空闲
# - 内存 10MB,远低于限制的 128Mi

八、数据持久化

8.1 创建 PersistentVolumeClaim (PVC)

什么是 PVC?

Pod 就像临时宿舍,里面的东西随时会丢。
PVC 就像一个"仓库",不管 Pod 怎么变动,仓库里的东西一直都在。

创建 mysql-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-data
  # PVC 的名称

spec:
  accessModes:
    - ReadWriteOnce
    # 是什么:访问模式
    # ReadWriteOnce:只能被一个节点读写
    # ReadOnlyMany:可以被多个节点只读
    # ReadWriteMany:可以被多个节点读写
  resources:
    requests:
      storage: 10Gi
      # 是什么:申请 10GB 存储空间

  # ⚠️ 关于 storageClassName:
  # kubeadm 安装的裸机集群没有默认 StorageClass
  # 如果不指定 storageClassName,PVC 会处于 Pending 状态
  # 以下是几种解决方案:
  #
  # 方案 1:安装 Local Path Provisioner(学习/测试推荐)
  # kubectl apply -f https://raw.githubusercontent.com/rancher/local-path-provisioner/master/deploy/local-path-storage.yaml
  # 安装后会自动创建 storageClass "local-path"
  #
  # 方案 2:使用 hostPath 直接挂载(仅限单节点学习)
  # volumes:
  #   - name: mysql-storage
  #     hostPath:
  #       path: /data/mysql
  #       type: DirectoryOrCreate
  #
  # 方案 3:使用 NFS + nfs-subdir-external-provisioner(单节点/小规模推荐)
  # 需要:一台 NFS 服务器 + K8s 中的 provisioner
  # 具体配置见下方「NFS StorageClass 完整配置」

8.1.1 NFS StorageClass 完整配置

适用场景:单节点学习、内部小规模集群

原理:K8s 创建 PVC → provisioner 在 NFS 服务器上自动创建子目录 → 自动绑定 PV → PVC 变为 Bound

第一步:配置 NFS 服务端

在 NFS 服务器(可以是 K8s 节点本身)上执行:

# 1. 安装 NFS 服务端
sudo apt-get install -y nfs-kernel-server

# 2. 创建共享目录
sudo mkdir -p /nfs-data
sudo chmod 777 /nfs-data

# 3. 配置 exports(允许所有客户端读写)
echo "/nfs-data *(rw,sync,no_root_squash,no_subtree_check)" | sudo tee /etc/exports

# 4. 启动 NFS 服务
sudo exportfs -rav
sudo systemctl enable --now nfs-server

# 5. 验证
showmount -e localhost
# 输出:
# Export list for localhost:
# /nfs-data *

第二步:部署 nfs-subdir-external-provisioner

# nfs-storageclass.yaml
# RBAC + Deployment + StorageClass 完整配置

apiVersion: v1
kind: Namespace
metadata:
  name: nfs-provisioner
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  namespace: nfs-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: nfs-client-provisioner-runner
rules:
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["get", "list", "watch", "create", "delete"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["get", "list", "watch", "update"]
  - apiGroups: ["storage.k8s.io"]
    resources: ["storageclasses"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: run-nfs-client-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-provisioner
roleRef:
  kind: ClusterRole
  name: nfs-client-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: leader-locking-nfs-client-provisioner
  namespace: nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    namespace: nfs-provisioner
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  namespace: nfs-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: nfs-client-provisioner
  template:
    metadata:
      labels:
        app: nfs-client-provisioner
    spec:
      serviceAccountName: nfs-client-provisioner
      containers:
        - name: nfs-client-provisioner
          image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: k8s-sigs.io/nfs-subdir-external-provisioner
            - name: NFS_SERVER
              value: 192.168.1.100
              # ⚠️ 改成你的 NFS 服务器 IP
            - name: NFS_PATH
              value: /nfs-data
              # ⚠️ 改成你的 NFS 共享目录
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.1.100
            # ⚠️ 改成你的 NFS 服务器 IP
            path: /nfs-data
            # ⚠️ 改成你的 NFS 共享目录
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-client
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
    # 设为默认 StorageClass,创建 PVC 时不指定 storageClassName 也会自动使用
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
  archiveOnDelete: "false"
  # 删除 PVC 时是否保留 NFS 上的数据目录
  # true = 保留(重命名为 archived-xxx)
  # false = 彻底删除
reclaimPolicy: Delete
  # PV 回收策略:Delete = 删除 PVC 时 PV 和 NFS 数据一起删除
volumeBindingMode: Immediate
  # 立即绑定(创建 PVC 时立即创建 PV)
# 🚀 部署
kubectl apply -f nfs-storageclass.yaml

# 验证
kubectl get pods -n nfs-provisioner
# 输出:
# NAME                                      READY   STATUS    RESTARTS   AGE
# nfs-client-provisioner-5757f487c6-7cn4m   1/1     Running   0          10s

kubectl get sc
# 输出:
# NAME                   PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
# nfs-client (default)   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  10s

⚠️ 国内环境镜像问题
registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 可能被墙

解决:从华为云镜像源拉取后 tag

docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2

第三步:测试 PVC 自动分配

# 创建 PVC(不指定 storageClassName,会使用默认的 nfs-client)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: test-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
EOF

# 验证
kubectl get pvc test-pvc
# 输出:
# NAME       STATUS   VOLUME                                     CAPACITY   ACCESS MODES   AGE
# test-pvc   Bound    pvc-e48277ae-ab07-4f80-9cbc-ab642adc5c87   10Gi       RWO            5s

kubectl get pv
# 输出:
# NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              AGE
# pvc-e48277ae-ab07-4f80-9cbc-ab642adc5c87   10Gi       RWO            Delete           Bound    default/test-pvc   5s

# 检查 NFS 服务器上的目录
ls /nfs-data/
# 输出:
# default-test-pvc-pvc-e48277ae-ab07-4f80-9cbc-ab642adc5c87

⚠️ NFS 服务端挂掉后的表现

场景 表现
已挂载的 Pod 继续 Running,但读写 NFS 挂载点会卡住(I/O hang),kubectl exec 访问挂载目录也会超时
PVC/PV 状态 保持 Bound,K8s 不会自动解绑(NFS 挂载是内核级行为,kubelet 不感知 NFS 服务状态)
新建 Pod 无法启动,卡在 ContainerCreating(挂载 NFS 卷时超时)
删除 Pod 可能卡住(卸载 NFS 挂载点超时),需要等 NFS 恢复或 force delete
节点状态 不会变成 NotReady(kubelet 的 health check 不检查 NFS 挂载状态)

恢复方法

# 1. 恢复 NFS 服务端
sudo systemctl start nfs-server

# 2. 如果 Pod 已经 I/O hang,可能需要重启 Pod
kubectl delete pod <pod-name>  # 让 K8s 重新调度

# 3. 如果删除 Pod 也卡住,强制删除
kubectl delete pod <pod-name> --grace-period=0 --force

生产建议

  • 使用 NFS HA 方案(如 DRBD + keepalived、分布式存储 Ceph/GlusterFS)
  • 监控 NFS 服务端健康状态
  • 关键数据使用云存储或分布式存储,而非单点 NFS

8.2 使用 PVC

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:8.0
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "MyRootPassword@123"
          ports:
            - containerPort: 3306
          volumeMounts:
            - name: mysql-storage
              mountPath: /var/lib/mysql
              # 是什么:挂载到 MySQL 数据目录
      volumes:
        - name: mysql-storage
          persistentVolumeClaim:
            claimName: mysql-data
            # 是什么:使用刚才创建的 PVC
            # 为什么:MySQL 的数据就会持久化到 PVC 中
            # 即使 Pod 重建,数据也不会丢失

九、常见问题排查

问题 1:Pod 一直处于 Pending 状态

现象

kubectl get pods
# 输出:STATUS = Pending

通俗解释:就像一个员工(Pod)入职了,但 HR 还没给他分配工位,只能在前台等着。

排查步骤

# 📋 第一步:查看 Pod 详情
kubectl describe pod <pod-name>

# 看 Events 部分的最后一行,常见原因:

# 原因 1:资源不足
# Events:
#   Warning  FailedScheduling  ...  0/1 nodes are available: insufficient cpu
# 解决:检查节点资源,释放资源或增加节点
kubectl top nodes

# 原因 2:没有匹配的节点
# Events:
#   Warning  FailedScheduling  ...  0/1 nodes are available: 1 node(s) had taint
#
# ⚠️ 常见情况:单节点 kubeadm 集群的 control-plane 节点默认有 taint
# 阻止业务 Pod 调度到 Master 节点上(安全策略)
#
# 如果只想在单节点上学习/测试,可以移除 taint:
kubectl taint nodes --all node-role.kubernetes.io/control-plane-
#
# 注意:生产环境不要移除这个 taint!
# 解决:移除 taint 或添加 tolerations

# 原因 3:PVC 未绑定
# Events:
#   Warning  FailedScheduling  ...  pod has unbound immediate PersistentVolumeClaims
# 解决:检查 PVC 状态
kubectl get pvc

问题 2:Pod 一直处于 CrashLoopBackOff 状态

现象

kubectl get pods
# 输出:STATUS = CrashLoopBackOff

通俗解释:就像一个员工(容器)反复尝试上岗,但每次都做不好被辞退,然后又重新招聘,陷入恶性循环。

排查步骤

# 📋 第一步:查看 Pod 日志(最重要)
kubectl logs <pod-name>

# 常见原因及解决方案:

# 原因 1:应用启动失败
# 日志示例:Error: Cannot bind to port 80: Address already in use
# 解决:检查端口冲突或修改配置

# 原因 2:配置错误
# 日志示例:FATAL: configuration file not found
# 解决:检查 ConfigMap/配置文件是否正确挂载

# 原因 3:镜像拉取失败
# 日志示例:Failed to pull image "xxx": ImagePullBackOff
# 解决:
kubectl describe pod <pod-name> | grep -A5 "Image"
# 检查镜像名称和 tag 是否正确

# 原因 4:缺少环境变量
# 日志示例:Error: DATABASE_URL environment variable is required
# 解决:检查环境变量配置

问题 3:Service 无法访问

现象

curl http://<节点IP>:30080
# 输出:Connection refused

通俗解释:就像你给前台打电话(访问 Service),但电话打不通,可能是前台没上班(Pod 没就绪)或电话线断了(网络问题)。

排查步骤

# 1. 检查 Service 是否存在
kubectl get svc nginx-service

# 2. 检查 Service 的 Endpoints(后端 Pod)
kubectl get endpoints nginx-service

# 输出示例:
# NAME            ENDPOINTS                         AGE
# nginx-service   10.244.0.5:80,10.244.0.6:80      1h

# 解读:
# ENDPOINTS 有 IP 地址 = 有后端 Pod ✅
# ENDPOINTS 为空 = 没有匹配的 Pod(检查 selector 和 Pod 标签)

# 3. 检查 Pod 是否就绪
kubectl get pods -l app=nginx

# READY 必须是 1/1(就绪才能被 Service 转发流量)

# 4. 检查节点端口是否开放
sudo ss -tuln | grep 30080

# 如果无输出,说明端口没监听(kube-proxy 可能有问题)

# 5. 检查防火墙
sudo ufw status
# 如果启用了防火墙,需要开放端口
sudo ufw allow 30080/tcp

问题 4:镜像拉取失败

现象

kubectl describe pod <pod-name>
# Events:
#   Warning  Failed  Failed to pull image "xxx": rpc error: code = Unknown

排查步骤

# 1. 测试镜像是否可以拉取
crictl pull nginx:1.25

# 如果拉取失败:
# - 检查网络连接(能否访问 Docker Hub)
# - 检查镜像名称和 tag 是否正确
# - 如果是私有仓库,检查认证信息

# ⚠️ 国内环境:registry.k8s.io / Docker Hub 可能被墙
# 可以使用阿里云镜像源替代:
crictl pull registry.aliyuncs.com/google_containers/coredns:v1.10.1
# 或先用 Docker 拉取后导入 containerd:
# docker pull xxx | ctr -n k8s.io images import -

# 2. 配置私有仓库认证
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=admin \
  --docker-password=password \
  --docker-email=admin@example.com

# 参数解释:
# create secret docker-registry:创建 Docker 仓库认证
# regcred:Secret 的名称

# 在 Pod 中使用:
# spec:
#   imagePullSecrets:
#     - name: regcred

问题 5:节点 NotReady

现象

kubectl get nodes
# 输出:STATUS = NotReady

通俗解释:就像一个员工(节点)生病了,无法正常工作,调度中心标记他为"不可用"。

排查步骤

# 1. 查看节点详情
kubectl describe node <node-name>

# 看 Conditions 部分:
#   MemoryPressure    False   # 内存压力
#   DiskPressure      False   # 磁盘压力
#   PIDPressure       False   # 进程数压力
#   Ready             False   # 节点就绪状态 ← 这个是 False

# 2. 检查 kubelet 状态
sudo systemctl status kubelet

# 如果 kubelet 未运行:
sudo systemctl restart kubelet
sudo journalctl -u kubelet --no-pager -n 50

# 3. 检查 Containerd 状态
sudo systemctl status containerd

# 4. 检查磁盘空间
df -h

# 如果磁盘满了,kubelet 会自动标记节点 NotReady
# 解决:清理磁盘空间

问题 6:NFS 存储后端挂掉后的表现

现象:使用 NFS StorageClass 的 PVC,NFS 服务端停止后,各种命令表现异常。

通俗解释:NFS 挂掉就像仓库的门锁死了。仓库管理员(kubelet)还在岗位上,但仓库里的东西拿不出来也放不进去。

实际测试结论

操作 表现 原因
kubectl get nodes/pods/svc/pvc 完全正常 走 API Server + etcd 读取数据,和 NFS 无关
kubectl describe pod 正常 同样走 API Server,不接触 NFS 挂载点
kubectl exec 到挂载 NFS 的 Pod 超时/卡住 容器内 shell 初始化时需要访问挂载点,被 NFS I/O hang 阻塞
已挂载 NFS 的 Pod Running,但读写挂载点卡住 内核级 NFS 挂载阻塞,进程进入 D 状态(不可中断睡眠)
新建挂载 NFS 的 Pod 卡在 ContainerCreating 挂载 NFS 卷时连接超时
删除挂载 NFS 的 Pod 可能卡住 卸载 NFS 挂载点超时
节点状态 不会变成 NotReady kubelet 不检测 NFS 挂载的健康状态

排查步骤

# 1. 确认 kubectl 是否正常(应该正常)
kubectl get nodes
kubectl get pods

# 2. 检查 NFS 服务端状态
# 在 NFS 服务器上执行:
sudo systemctl status nfs-server
showmount -e localhost

# 3. 确认 Pod 是否挂载了 NFS 卷
kubectl describe pod <pod-name> | grep -A3 "Volumes:"
# 输出示例:
# Volumes:
#   data:
#     Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim)
#     ClaimName:  mysql-pvc

# 4. 确认 PVC 使用的 StorageClass
kubectl get pvc <pvc-name> -o jsonpath='{.spec.storageClassName}'
# 如果是 nfs-client,说明使用的是 NFS 存储

恢复方法

# 1. 恢复 NFS 服务端
sudo systemctl start rpcbind
sudo systemctl start nfs-server

# 2. 验证 NFS 恢复
showmount -e localhost

# 3. 如果 Pod 已经 I/O hang,kubectl exec 会卡住
#    等待 NFS 恢复后,容器内的进程会自动恢复(不需要重启 Pod)

# 4. 如果删除 Pod 也卡住(卸载 NFS 挂载点超时),强制删除
kubectl delete pod <pod-name> --grace-period=0 --force

生产建议

  • 使用 NFS HA 方案(如 DRBD + keepalived、分布式存储 Ceph/GlusterFS)
  • 监控 NFS 服务端健康状态
  • 关键数据使用云存储或分布式存储,而非单点 NFS
  • 定期备份 NFS 上的数据

核心操作速查表

操作 命令 说明
系统前置 swapoff -a && modprobe overlay br_netfilter 关闭 Swap、加载内核模块
初始化集群 kubeadm init --pod-network-cidr=... 初始化 Master 节点
查看节点 kubectl get nodes 检查节点状态
查看 Pod kubectl get pods 查看 Pod 列表
部署应用 kubectl apply -f xxx.yaml 应用配置文件
查看日志 kubectl logs <pod> 查看容器日志
进入容器 kubectl exec -it <pod> -- bash 进入 Pod 终端(不支持 -l)
扩缩容 kubectl scale --replicas=N 调整副本数
更新镜像 kubectl set image 滚动更新
回滚 kubectl rollout undo 回滚到上一版本
查看资源 kubectl top nodes/pods 需要 Metrics Server
删除资源 kubectl delete -f xxx.yaml 删除资源

黄金法则

  1. 先看日志,再动手 - 90% 的问题日志里都有答案
  2. describe 是你的好朋友 - kubectl describe 显示详细状态和事件
  3. 标签要一致 - selector、labels 必须匹配,否则 Service 找不到 Pod
  4. 资源限制要设置 - requests 和 limits 防止资源滥用
  5. 数据要持久化 - 不用 PVC,Pod 重建数据就没了
  6. 更新要渐进 - 滚动更新保证服务不中断

文件结构

k8s-deploy/
├── nginx-deployment.yaml    # Nginx 部署配置
├── nginx-service.yaml       # Nginx Service 配置
├── mysql-pvc.yaml           # MySQL 持久化存储
└── mysql-deployment.yaml    # MySQL 部署配置

完整部署流程

# 1. 系统前置配置(必须执行)
sudo swapoff -a && sudo sed -i '/swap/d' /etc/fstab
sudo modprobe overlay && sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system

# 2. 安装 K8s 集群(国内使用 --image-repository)
sudo kubeadm init --pod-network-cidr=10.244.0.0/16 \
  --image-repository registry.aliyuncs.com/google_containers

# 3. 配置 kubectl
mkdir -p $HOME/.kube && sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config

# 4. 安装网络插件(国内用 Gitee 镜像)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
# 国内备选:curl -fsSL https://gitee.com/mirrors/flannel/raw/master/Documentation/kube-flannel.yml | kubectl apply -f -

# 5. 安装 Metrics Server(kubectl top / HPA 依赖)
# 注意:国内需要替换镜像地址 + 添加 --kubelet-insecure-tls
curl -fsSL https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml -o metrics-server.yaml
sed -i 's|registry.k8s.io/metrics-server/metrics-server:|registry.aliyuncs.com/google_containers/metrics-server:|g' metrics-server.yaml
kubectl apply -f metrics-server.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' \
  -p='[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'

# 6. 移除 control-plane taint(单节点学习用,生产环境不要执行)
kubectl taint nodes --all node-role.kubernetes.io/control-plane-

# 7. 部署应用
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-service.yaml

# 8. 验证
kubectl get pods
kubectl get svc

# 9. 访问服务
curl http://<节点IP>:30080

文档版本:1.0
最后更新:2025-06-07
维护者:guobin

0
  1. 支付宝打赏

    qrcode alipay
  2. 微信打赏

    qrcode weixin

评论区