Kubernetes (K8s) 部署指南
版本: 1.0
更新时间: 2025-06-07
维护者: guobin
前言
什么是 Kubernetes?
想象一下:你经营一支庞大的物流车队(服务器集群),有 100 辆卡车(节点),每辆卡车上要装不同的货物(应用容器)。你需要决定哪辆卡车装什么货物,哪辆车坏了要把货转到其他车上,哪个路线最优……一个人根本管不过来。
Kubernetes(K8s)就像车队的"总调度室",它自动帮你:
- 分配货物到合适的卡车(Pod 调度)
- 卡车坏了自动换车(故障自愈)
- 订单多了自动加车(弹性伸缩)
- 保证货物安全送达(服务发现和负载均衡)
Kubernetes 的名字来源于希腊语,意思是"舵手"或"领航员"。它的缩写 K8s 是因为 K 和 s 之间有 8 个字母。
本文档会告诉你:
- Kubernetes 的核心概念和架构
- 环境准备和 K8s 集群安装步骤
- 如何部署应用到 K8s
- 服务管理、扩缩容、日志监控
- 常见问题排查和解决方案
目录
一、核心概念
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"的权限,不包含直接 listnodes/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 |
删除资源 |
黄金法则
- 先看日志,再动手 - 90% 的问题日志里都有答案
- describe 是你的好朋友 -
kubectl describe显示详细状态和事件 - 标签要一致 - selector、labels 必须匹配,否则 Service 找不到 Pod
- 资源限制要设置 - requests 和 limits 防止资源滥用
- 数据要持久化 - 不用 PVC,Pod 重建数据就没了
- 更新要渐进 - 滚动更新保证服务不中断
文件结构
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
评论区