K8S
本文范围
这篇讲 Kubernetes 自身的最小生产链路:节点准备、containerd、kubeadm、kubelet、kubectl、CNI、Pod、Deployment、Service、Ingress、ConfigMap、Secret、Volume、Namespace、资源限制、健康检查、滚动发布、回滚和 kubectl 排障。
不展开 Dockerfile、Harbor 完整搭建、Helm Chart 模板、Jenkins 流水线、Nginx 入口优化和云厂商托管集群产品能力。它们和 K8S 有连接点,但不是本文主线。
前置条件
建议用 3 台 Linux 节点练习:1 台控制面、2 台工作节点。官方 kubeadm 文档对控制面节点的最低要求是 2 CPU、2GB 内存起步;真实生产不要按最低值规划,要把业务 Pod、系统组件、日志、监控和镜像缓存算进去。
落地前先确认:
| 项目 | 要求 |
|---|---|
| 系统 | Ubuntu、Debian、RHEL 系或同类发行版,所有节点时间同步 |
| 运行时 | containerd 或 CRI-O;不要再把 Docker 当作新集群默认 CRI |
| 网络 | 节点互通,Pod 网段和 Service 网段不和现有网络冲突 |
| 镜像 | 能访问 registry.k8s.io 或已准备内网镜像仓库和离线镜像 |
| 权限 | 能关闭 swap、配置内核参数、安装软件包、修改 containerd 配置 |
| 版本 | 以安装当天 Kubernetes releases、version skew policy 和 kubeadm 文档为准;本文示例按 v1.36 这一代文档口径组织 |
先把这些条件对齐,再执行 kubeadm init。K8S 安装失败,大半不是 Kubernetes 本身难,而是系统、网络、镜像、证书和运行时没准备好。
先把边界说清楚
学 K8S 最容易走偏的地方,是一上来就背 Pod、Service、Ingress、ConfigMap,一堆名词背完了,真到服务器上还是不知道先敲哪条命令。
这里我们换一种方式:先把一个服务跑起来,再一步步拆它背后的对象。你看到每条命令,都要知道三件事:这条命令解决什么问题,执行后应该看到什么,失败了先查哪里。
这篇只讲 K8S 本身。Dockerfile 怎么写、镜像怎么构建、Harbor 怎么完整搭建、Nginx 怎么做生产入口、Jenkins 怎么发版,都不在这里展开。K8S 这里最多讲到:镜像要从哪里拉,Pod 怎么运行,Service 怎么暴露,Ingress 怎么进来,发布失败怎么回滚。
这里要注意,现在的 K8S 主线不是“每个节点装 Docker 然后 K8S 调 Docker”。Kubernetes 1.24 以后已经移除了内置 dockershim,节点上一般走 containerd 或 CRI-O 这类 CRI 运行时。Docker 仍然可以用来构建镜像,但不要再把它当成 K8S 节点运行时的默认答案。
截至 2026-07-10,当前 Kubernetes 主线最新稳定版本是 1.36,最新补丁是 1.36.2。你真正落地时,要去官方 release 页面重新看一眼当前补丁版本,不要拿旧教程里的固定版本硬套。
先理解 K8S 到底在帮你管什么
如果你只在一台机器上跑一个容器,Docker 就够了。问题是生产环境不会这么简单:服务要多副本,要挂了自动拉起,要滚动升级,要统一入口,要看日志,要限制 CPU 和内存,要能回滚。
K8S 解决的就是这类问题:你告诉它“我希望这个服务一直有 3 个副本,用这个镜像,暴露这个端口,配置这些资源限制”,它会持续检查真实状态。如果少了一个 Pod,它补一个;如果你换了镜像,它滚动替换;如果新版本不行,它还能回滚。
先记住这条主线:
Deployment -> ReplicaSet -> Pod -> Container
Service -> EndpointSlice -> Pod IP
Ingress -> Service -> Pod
ConfigMap / Secret / Volume -> Pod这几个对象先不用背定义,先看它们分别干什么:
| 对象 | 你可以先这么理解 | 日常最常用命令 |
|---|---|---|
| Node | 集群里的机器 | kubectl get nodes -o wide |
| Pod | K8S 调度的最小运行单元 | kubectl get pods -o wide |
| Deployment | 管无状态服务副本和发布 | kubectl get deploy |
| ReplicaSet | Deployment 背后用来维持副本数 | kubectl get rs |
| Service | 给一组 Pod 一个稳定入口 | kubectl get svc |
| Ingress | HTTP/HTTPS 域名入口规则 | kubectl get ingress |
| ConfigMap | 放非敏感配置 | kubectl get cm |
| Secret | 放密码、令牌、镜像仓库凭证 | kubectl get secret |
| Volume | 给 Pod 挂载文件或持久化目录 | kubectl get pvc |
| Namespace | 隔离环境、团队、权限、配额 | kubectl get ns |
一般业务服务不要直接创建 Pod,而是创建 Deployment。因为单独 Pod 挂了就挂了,Deployment 会帮你补齐副本,还能滚动发布和回滚。
Kubernetes 官方组件图
Kubernetes 官方文档用下面这张图说明控制面、节点和集群插件的基础关系。这里保留官方原图,是为了先对齐术语边界:控制面负责 API、调度、控制循环和集群状态,节点面负责 kubelet、容器运行时、网络代理和实际工作负载。它说明的是“组件组成”,不能直接等同于你的生产部署拓扑。
来源:Kubernetes 官方文档 “Components of Kubernetes”,页面地址:https://kubernetes.io/docs/concepts/overview/components/。Kubernetes 文档按 CC BY 4.0 分发;本站保留原图用于术语对照。
紧接着下面这张本站重绘 Mermaid,把官方组件口径压到设计评审和上线检查清单里。评审时先确认对象边界是否完整,排障时按 kubectl get/describe/events、kubectl logs/exec、kubectl get endpointslice 的顺序,从控制面期望状态一路追到节点、网络和入口流量,不要只盯某一个异常 Pod。
这张图后面要反复使用:Deployment 相关问题先看控制面是否生成了正确的 ReplicaSet;启动问题看 kubelet、containerd、CNI 和 Secret;流量问题看 Service、EndpointSlice、CoreDNS、Ingress 或 Gateway。只要能把故障落到其中一层,排查就不会在“服务挂了”这种大词里打转。
先处理国内、内网和离线环境
很多 K8S 安装失败,不是命令写错,而是依赖没准备好。
常见现场是这样的:教程里一条 kubeadm init,结果机器上拉不到 registry.k8s.io;CNI 插件 YAML 里有 GitHub raw 地址,内网访问不了;业务镜像在 Docker Hub,节点没有外网;私有仓库用了自签证书,containerd 不认。
所以动手前先准备三类东西。
第一类是软件包:
kubeadm version
kubelet --version
kubectl version --client这三条命令用来确认你当前装的版本。如果还没安装,内网环境要提前准备对应系统的 deb 或 rpm 包。现在官方仓库入口是 pkgs.k8s.io,旧的 apt.kubernetes.io、yum.kubernetes.io 已经废弃冻结,不要继续照老文章配置。
第二类是 K8S 控制面镜像:
kubeadm config images list --kubernetes-version v1.36.2这条命令会列出当前版本需要哪些镜像,一般会看到 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、coredns、pause 这些。
如果能联网,可以先拉下来:
kubeadm config images pull --kubernetes-version v1.36.2
crictl images执行后用 crictl images 看本机 containerd 里有没有这些镜像。如果拉取失败,先不要急着 kubeadm init,因为初始化时一样会失败。
第三类是业务镜像和组件镜像。生产环境建议统一推到私有仓库,例如:
crictl pull registry.example.com/app/order-service:2026.07.09-001
crictl images | grep order-service这里要注意,registry.example.com 只是示例,你要换成自己的私有仓库。真正生产里不要依赖临时镜像加速地址,更不要把不稳定第三方镜像源写死成唯一方案。比较稳的做法是:公网中转机拉镜像并校验,再推送到企业私有仓库,K8S 节点只访问企业仓库。
如果私有仓库用了自签证书,containerd 要配置证书信任。常见配置目录是:
sudo mkdir -p /etc/containerd/certs.d/registry.example.com然后放 hosts.toml:
server = "https://registry.example.com"
[host."https://registry.example.com"]
capabilities = ["pull", "resolve", "push"]改完重启 containerd:
sudo systemctl restart containerd
crictl pull registry.example.com/app/order-service:2026.07.09-001如果这里还拉不下来,先查仓库地址、用户名密码、证书、DNS、代理,不要直接怪 K8S。
准备节点
我们用 kubeadm + containerd + CNI 作为自建集群主线。托管集群可以跳过安装,但后面的对象、发布和排障一样适用。
先在每台节点上配置主机名。比如控制面节点:
hostnamectl set-hostname k8s-master-01工作节点:
hostnamectl set-hostname k8s-worker-01执行后重新登录,或者用下面命令确认:
hostname然后打开内核模块和转发参数:
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter这一步解决什么问题?K8S Pod 网络、iptables 转发、容器文件系统都依赖这些能力。执行后这样验证:
lsmod | grep -E 'overlay|br_netfilter'能看到 overlay 和 br_netfilter,说明模块已经加载。
继续写内核参数:
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.ipv4.ip_forward应该看到:
net.ipv4.ip_forward = 1这里要注意,如果 ip_forward 没开,后面 Pod 跨节点通信、Service 转发都可能出问题。
再关闭 swap:
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
free -h执行后看 free -h,Swap 应该是 0B。如果 kubeadm 初始化时报 swap 相关错误,先回来看这里。
生产上还要确认时间同步:
timedatectl如果时间不同步,证书、日志、etcd、监控都会跟着出问题。一般需要配置 chrony 或 NTP。
安装 containerd
containerd 是 kubelet 调用的容器运行时。先生成默认配置:
sudo mkdir -p /etc/containerd
containerd config default | sudo tee /etc/containerd/config.toml然后改 cgroup 驱动。找到这段:
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options]确保下面有:
SystemdCgroup = true这里为什么要改?因为 kubelet 和容器运行时的 cgroup 驱动最好保持一致。生产环境里一般使用 systemd cgroup,避免 kubelet 和 containerd 管资源时口径不一致。
启动并设置开机自启:
sudo systemctl enable --now containerd
sudo systemctl status containerd验证 containerd 能被 CRI 工具访问:
crictl info如果这里报连接不到 runtime,先查:
sudo systemctl status containerd
journalctl -u containerd -n 100一般是 containerd 没启动、配置文件格式写错,或者 CRI socket 路径不对。
安装 kubeadm、kubelet、kubectl
Ubuntu / Debian 可以这样装:
sudo apt-get update
sudo apt-get install -y apt-transport-https ca-certificates curl gpg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/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.36/deb/ /' \
| sudo tee /etc/apt/sources.list.d/kubernetes.list
sudo apt-get update
sudo apt-get install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectlRHEL / Rocky / CentOS Stream 可以这样装:
cat <<EOF | sudo tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.36/rpm/repodata/repomd.xml.key
exclude=kubelet kubeadm kubectl cri-tools kubernetes-cni
EOF
sudo yum install -y kubelet kubeadm kubectl --disableexcludes=kubernetes
sudo systemctl enable --now kubelet装完验证:
kubeadm version
kubelet --version
kubectl version --client这里要注意,kubelet 安装后可能是启动失败状态,这不一定是问题。因为集群还没有初始化,kubelet 缺少配置,等 kubeadm init 或 kubeadm join 后再看。
初始化控制面节点
先生成一份默认配置:
kubeadm config print init-defaults > kubeadm-config.yaml然后编辑它。一个常见骨架长这样:
apiVersion: kubeadm.k8s.io/v1beta4
kind: InitConfiguration
localAPIEndpoint:
advertiseAddress: 192.0.2.10
bindPort: 6443
nodeRegistration:
criSocket: unix:///run/containerd/containerd.sock
---
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.36.2
controlPlaneEndpoint: "192.0.2.10:6443"
imageRepository: registry.k8s.io
networking:
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12这几个字段要看清楚:
advertiseAddress是当前控制面节点对外通告的地址。controlPlaneEndpoint是集群控制面入口。生产高可用一般写负载均衡地址。criSocket指向 containerd。imageRepository默认是registry.k8s.io,内网环境要改成私有仓库。podSubnet要和你选的 CNI 插件匹配。
先拉镜像:
sudo kubeadm config images pull --config kubeadm-config.yaml
sudo crictl images如果这里镜像拉不下来,初始化也会失败。先把镜像仓库问题解决掉。
开始初始化:
sudo kubeadm init --config kubeadm-config.yaml --upload-certs成功后会输出两类重要命令:
- 配置当前用户使用
kubectl的命令。 - 工作节点加入集群的
kubeadm join命令。
先配置 kubectl:
mkdir -p "$HOME/.kube"
sudo cp -i /etc/kubernetes/admin.conf "$HOME/.kube/config"
sudo chown "$(id -u):$(id -g)" "$HOME/.kube/config"验证:
kubectl get nodes
kubectl get pods -n kube-system这时节点可能还不是 Ready,一般是因为 CNI 网络插件还没装。不要急着部署业务。
安装 CNI 网络插件
K8S 定义网络模型,但不替你选择网络插件。常见 CNI 有 Calico、Cilium、Flannel。生产要根据网络策略、性能、团队熟悉度和维护能力选。
安装 CNI 时要注意三件事:
- 插件版本要支持当前 K8S 版本。
- 插件镜像要能从节点拉到。
- 插件的 Pod 网段要和
kubeadm-config.yaml里的podSubnet一致。
安装后这样验证:
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get pods -n kube-system正常情况下,节点应该变成 Ready,CoreDNS 也应该 Running。
如果 CoreDNS 一直 Pending,先看:
kubectl describe pod -n kube-system -l k8s-app=kube-dns
kubectl get nodes如果 CNI Pod 拉镜像失败,还是回到镜像仓库、DNS、证书、代理这条线排查。
加入工作节点
控制面初始化成功后,会给出一条类似这样的命令:
sudo kubeadm join 192.0.2.10:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>在工作节点执行它。执行后回到控制面看:
kubectl get nodes -o wide正常应该看到工作节点加入,并且状态逐步变成 Ready。
如果 join 命令过期了,在控制面重新生成:
kubeadm token create --print-join-command如果 join 失败,先查四个点:
- 工作节点能不能访问控制面的
6443端口。 - 时间是否同步。
- containerd 是否正常。
- token 和 CA hash 是否正确。
跑第一个服务
现在我们跑一个最小 Nginx 服务。先创建命名空间:
kubectl create namespace demo
kubectl config set-context --current --namespace=demo执行后看一下:
kubectl get ns应该能看到 demo。
创建 Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-demo
labels:
app: nginx-demo
spec:
replicas: 2
revisionHistoryLimit: 5
selector:
matchLabels:
app: nginx-demo
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: nginx-demo
spec:
containers:
- name: nginx
image: nginx:1.27
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 20保存为 nginx-demo-deployment.yaml,然后执行:
kubectl apply -f nginx-demo-deployment.yaml看结果:
kubectl get deploy,rs,pod -o wide你应该看到一个 Deployment,一个 ReplicaSet,两个 Pod。这里就能看出关系:你创建的是 Deployment,K8S 帮你创建 ReplicaSet,ReplicaSet 再创建 Pod。
如果 Pod 没起来,先不要猜,直接看详情:
kubectl describe pod <pod-name>describe 的 Events 区域最有价值。镜像拉不到、资源不够、探针失败、卷挂载失败,基本都会在这里留下线索。
用 Service 暴露服务
Pod IP 会变,所以不能让别人直接访问 Pod IP。这里需要 Service。
创建 Service:
apiVersion: v1
kind: Service
metadata:
name: nginx-demo
spec:
type: ClusterIP
selector:
app: nginx-demo
ports:
- name: http
port: 80
targetPort: 80执行:
kubectl apply -f nginx-demo-service.yaml
kubectl get svc nginx-demo再看它有没有找到后端 Pod:
kubectl get endpoints nginx-demo
kubectl get endpointslice -l kubernetes.io/service-name=nginx-demo如果 Endpoints 是空的,一般不是网络问题,先查 selector:
kubectl describe svc nginx-demo
kubectl get pod --show-labelsService 的 selector 必须能匹配 Pod 的 label。这里最常见的坑就是 label 写错,比如 Service 选的是 app: nginx,Pod 上却是 app: nginx-demo。
在集群里临时访问一下:
kubectl run curl --image=curlimages/curl:8.10.1 -it --rm --restart=Never -- \
curl -I http://nginx-demo.demo.svc.cluster.local正常应该看到 HTTP 响应头。如果这个调试镜像在内网拉不到,就先把它同步到私有仓库,再把命令里的镜像地址换掉。
Service 的几种类型
Service 常见有四种:
ClusterIP:默认类型,只能集群内访问。NodePort:每个节点开一个端口,外部通过NodeIP:NodePort访问。LoadBalancer:需要云厂商或裸金属负载均衡器支持。ExternalName:把 Service 映射到外部域名。
测试环境常见写法是 NodePort:
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
nodePort: 30080访问:
curl -I http://<node-ip>:30080这里要注意,NodePort 只是简单暴露,生产入口一般不会直接靠它对外提供正式域名访问。生产通常会走负载均衡器、Ingress 或 Gateway,再转到 Service。
用 Ingress 做 HTTP 入口
Service 解决的是集群内部稳定访问。外部域名怎么进来?常见是 Ingress。
先说清楚,Ingress 只是规则,必须有 Ingress Controller 才能生效。没有 Controller,你创建 Ingress 也不会真正接流量。
一个最小 Ingress 长这样:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: nginx-demo
spec:
ingressClassName: nginx
rules:
- host: demo.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx-demo
port:
number: 80应用:
kubectl apply -f nginx-demo-ingress.yaml
kubectl get ingress
kubectl describe ingress nginx-demo还要确认集群里有没有对应的 IngressClass 和 Controller:
kubectl get ingressclass
kubectl get pods -A | grep -i ingress如果访问 404,多半是域名或路径没命中。如果访问 502 或 503,先查 Service 后面有没有 Endpoints:
kubectl get svc nginx-demo
kubectl get endpoints nginx-demo这里要注意一个现状:Ingress API 已经冻结,新能力推荐看 Gateway API;同时 ingress-nginx 项目已经进入退休状态。已有集群可以继续评估迁移节奏,新集群选入口控制器时不要无脑把旧教程里的 ingress-nginx 当唯一方案。
ConfigMap:把配置从镜像里拿出来
很多人刚开始容器化时,会把配置文件直接打进镜像。这样有个问题:只是改个配置,也要重新构建镜像。K8S 里一般用 ConfigMap 放非敏感配置。
创建一个 ConfigMap:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
APP_ENV: "prod"
LOG_LEVEL: "info"
application.yaml: |
server:
port: 8080执行:
kubectl apply -f app-config.yaml
kubectl get configmap
kubectl get configmap app-config -o yaml作为环境变量引用:
envFrom:
- configMapRef:
name: app-config进入 Pod 看:
kubectl exec -it <pod-name> -- env | grep APP_ENV作为文件挂载:
volumes:
- name: app-config
configMap:
name: app-config
containers:
- name: app
volumeMounts:
- name: app-config
mountPath: /app/config
readOnly: true验证:
kubectl exec -it <pod-name> -- ls /app/config
kubectl exec -it <pod-name> -- cat /app/config/application.yaml这里要注意,ConfigMap 改了以后,应用不一定自动生效。环境变量方式一般要重启 Pod;文件挂载方式也要看应用是否监听文件变化。生产发布时要写清楚:这次改配置需不需要重启。
Secret:敏感信息不要乱放
密码、令牌、镜像仓库凭证不要放 ConfigMap。K8S 里可以用 Secret。
创建普通 Secret:
kubectl create secret generic db-secret \
--from-literal=username=app \
--from-literal=password='<password>'查看:
kubectl get secret
kubectl describe secret db-secret如果是私有镜像仓库,创建镜像拉取凭证:
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username='<username>' \
--docker-password='<password>' \
--docker-email='ops@example.com'Pod 里引用:
imagePullSecrets:
- name: regcred如果 Pod 还是拉不到镜像,看:
kubectl describe pod <pod-name>Events 里如果出现 unauthorized,多半是账号密码或权限问题;如果是 x509,多半是证书信任问题;如果是 not found,多半是镜像名或 tag 写错。
这里要注意,Secret 默认不是“绝对安全保险箱”。生产要配 RBAC 最小权限、etcd 加密、审计,不要把 Secret 明文提交到 Git。
Volume:别把数据写进容器可写层
容器里的可写层是临时的。Pod 被删除重建后,里面新写的文件可能就没了。K8S 用 Volume 解决文件挂载和持久化问题。
常见 Volume:
| 类型 | 适合场景 | 注意点 |
|---|---|---|
emptyDir | 临时缓存、临时文件 | Pod 删除后数据也没了 |
hostPath | 特殊系统场景、节点本地目录 | 绑定节点,生产业务慎用 |
configMap | 配置文件挂载 | 不能放敏感信息 |
secret | 敏感文件挂载 | 注意权限和加密 |
persistentVolumeClaim | 生产持久化主线 | 依赖 StorageClass / PV |
PVC 示例:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
storageClassName: standardPod 挂载:
volumes:
- name: app-data
persistentVolumeClaim:
claimName: app-data
containers:
- name: app
volumeMounts:
- name: app-data
mountPath: /data验证:
kubectl get pvc
kubectl describe pvc app-data
kubectl get pv
kubectl get storageclass如果 PVC 一直 Pending,先查有没有默认 StorageClass、存储插件是否正常、访问模式是否匹配、容量是否足够。
这里的原理跟实战是挂钩的:不要把业务数据写到容器可写层,因为容器层适合运行进程,不适合承载持久数据。日志、上传文件、数据库数据,都要明确落到外部存储、对象存储或 PVC。
Namespace:别把所有东西都放 default
刚学习时放 default 没问题。生产上一般要按环境、团队或业务域拆 Namespace。
kubectl create namespace prod
kubectl config set-context --current --namespace=prod
kubectl get ns为什么要拆?因为权限、配额、对象命名都要有边界。比如生产 Namespace 可以配 ResourceQuota:
apiVersion: v1
kind: ResourceQuota
metadata:
name: prod-quota
spec:
hard:
requests.cpu: "20"
requests.memory: 40Gi
limits.cpu: "40"
limits.memory: 80Gi
pods: "100"应用:
kubectl apply -f prod-quota.yaml -n prod
kubectl describe quota -n prod再配默认限制:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
spec:
limits:
- type: Container
defaultRequest:
cpu: 100m
memory: 128Mi
default:
cpu: 500m
memory: 512Mi这样即使有人忘了写资源,也不会完全裸奔。当然,关键服务还是应该在自己的 YAML 里显式写清楚。
requests 和 limits:资源不是随便填的
requests 是“我希望调度时至少给我这些资源”,limits 是“最多不许超过这个上限”。
示例:
resources:
requests:
cpu: 500m
memory: 512Mi
limits:
cpu: "1"
memory: 1Gi这里要注意:
- CPU request 影响调度。
- CPU limit 太低会导致 CPU throttling,服务响应变慢。
- Memory limit 是硬上限,超了可能 OOMKilled。
- 不写 requests,Pod 可能被调度到资源紧张节点。
- 不写 limits,一个服务可能把节点资源吃光。
查看资源:
kubectl top nodes
kubectl top pods如果命令不可用,说明 Metrics Server 可能没装或没正常工作。
看节点压力:
kubectl describe node <node-name>Java 服务尤其要注意,不能只配容器内存 limit,还要看 JVM 参数、线程数、直接内存、Metaspace。比如你给容器 1Gi,但 JVM、线程栈、直接内存加起来超过了,照样可能 OOM。
生产经验是:先压测和观察,再定 requests;先保守限额,再根据监控收敛。不要所有服务都复制同一份资源模板。
健康检查:不要让探针把服务杀死
K8S 有三类探针:
startupProbe:服务启动完成了吗?readinessProbe:服务能接流量了吗?livenessProbe:服务是不是已经死掉,需要重启?
一个常见配置:
startupProbe:
httpGet:
path: /actuator/health
port: 8080
failureThreshold: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 8080
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 2
failureThreshold: 3
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 8080
initialDelaySeconds: 30
periodSeconds: 20
timeoutSeconds: 2
failureThreshold: 3验证:
kubectl describe pod <pod-name>
kubectl get pod -w
kubectl get endpoints <service-name>这里要特别注意几个坑:
- 启动慢的服务不配
startupProbe,很容易被 liveness 提前杀掉。 - 把数据库连通性放进 liveness,数据库一抖,应用全被重启,故障会扩大。
- readiness 失败时,Pod 可以 Running,但不会进入 Service Endpoints,这是正常设计。
- 探针路径不要太重,不要每几秒跑一次复杂业务逻辑。
一般建议:liveness 只判断进程是否活着,readiness 判断能否接流量,startup 给慢启动应用兜底。
滚动发布和回滚
K8S 里发布最常用的是改 Deployment 镜像。
kubectl set image deployment/order-service \
order-service=registry.example.com/app/order-service:2026.07.09-001这条命令解决什么问题?它把 Deployment 里的容器镜像改成新版本,Deployment 会创建新的 ReplicaSet,再逐步替换旧 Pod。
看发布过程:
kubectl rollout status deployment/order-service --timeout=180s
kubectl get deploy,rs,pod -l app=order-service -o wide正常应该看到新 Pod 慢慢起来,旧 Pod 慢慢退出。
发布策略一般这样写:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0maxSurge: 1 表示升级过程中可以额外多起 1 个 Pod;maxUnavailable: 0 表示升级过程中不允许可用副本减少。这样更稳,但要求集群有额外资源。
下面这张链路图用来把滚动发布拆成可观测的检查点。上线前用它检查 maxSurge、maxUnavailable、readinessProbe、镜像凭证和节点容量;发布中用它决定下一条命令是 kubectl rollout status、kubectl describe pod、kubectl get events、kubectl logs --previous,还是直接暂停和回滚。
这张图的关键是:Service 不会因为新 Pod 创建就立刻把流量打过去,只有 readinessProbe 通过、EndpointSlice 更新后才会摘挂后端。反过来,回滚也不是单条命令结束,必须确认旧 ReplicaSet 扩容完成、EndpointSlice 已恢复、入口健康检查和日志都回到预期状态。
记录变更原因:
kubectl annotate deployment/order-service \
kubernetes.io/change-cause="update order-service image to 2026.07.09-001" \
--overwrite查看历史:
kubectl rollout history deployment/order-service
kubectl rollout history deployment/order-service --revision=3如果新版本有问题,回滚:
kubectl rollout undo deployment/order-service
kubectl rollout status deployment/order-service --timeout=180s回滚到指定版本:
kubectl rollout undo deployment/order-service --to-revision=2这里要注意,回滚不是敲完命令就算结束。还要验证:
kubectl get pod -l app=order-service -o wide
kubectl get endpoints order-service
kubectl logs deployment/order-service --tail=100
curl -I https://api.example.com/order/health应该看到 Pod Ready,Endpoints 正常,日志没有连续异常,入口健康检查返回正常。
kubectl 常用命令
先看资源:
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get deploy,rs,svc,ingress
kubectl get pod <pod-name> -o yaml看详情:
kubectl describe pod <pod-name>
kubectl describe node <node-name>
kubectl describe svc <service-name>看事件:
kubectl events
kubectl events --for pod/<pod-name>
kubectl get events --sort-by=.lastTimestamp看日志:
kubectl logs <pod-name>
kubectl logs <pod-name> -c <container-name>
kubectl logs <pod-name> --previous
kubectl logs deployment/order-service --tail=200
kubectl logs -f deployment/order-service进入容器:
kubectl exec -it <pod-name> -- sh
kubectl exec -it <pod-name> -c <container-name> -- sh解释字段:
kubectl explain deployment.spec.strategy
kubectl explain pod.spec.containers.resources
kubectl explain ingress.spec.rules这里要注意,kubectl logs --previous 很有用。Pod 如果一直重启,当前容器日志可能已经不是上一次崩溃现场,--previous 才能看到上一个容器实例退出前的日志。
按现象排障
Pod 一直 Pending
先看:
kubectl describe pod <pod-name>重点看 Events。常见原因:
- CPU 或内存 requests 太大,节点放不下。
- 节点有 taint,Pod 没有 toleration。
- nodeSelector 或 affinity 不匹配。
- PVC 没绑定。
- CNI 异常。
继续查:
kubectl get nodes
kubectl describe node <node-name>
kubectl get pvc修复后看:
kubectl get pod -w状态应该从 Pending 进入 ContainerCreating,再进入 Running。
ImagePullBackOff
先看详情:
kubectl describe pod <pod-name>如果 Events 里有 pull access denied,一般是权限问题。如果是 manifest unknown,一般是 tag 不存在。如果是 x509,一般是证书不被信任。如果是 timeout,先查网络和代理。
在节点上直接试拉:
crictl pull registry.example.com/app/order-service:2026.07.09-001再看 containerd 日志:
journalctl -u containerd -n 200这里要注意,imagePullSecrets 必须和 Pod 在同一个 Namespace。Secret 在 default,Pod 在 prod,是用不到的。
CrashLoopBackOff
先看上一次崩溃日志:
kubectl logs <pod-name> --previous再看事件:
kubectl describe pod <pod-name>常见原因:
- 启动命令错了。
- 配置文件没挂进去。
- Secret key 写错。
- 数据库、Redis、MQ 等依赖连不上。
- JVM 参数超过容器内存。
- liveness 探针太激进,把还在启动的服务杀掉。
修完以后重启 Deployment:
kubectl rollout restart deployment/order-service
kubectl rollout status deployment/order-serviceService 访问不通
先看 Service:
kubectl get svc order-service -o wide
kubectl describe svc order-service再看 Endpoints:
kubectl get endpoints order-service
kubectl get endpointslice -l kubernetes.io/service-name=order-service如果 Endpoints 是空的,先查 label 和 readiness:
kubectl get pod -l app=order-service -o wide
kubectl get pod --show-labels如果 Endpoints 正常,再查端口、DNS、网络策略、入口层。
Ingress 404 / 502 / 503
先看入口规则:
kubectl get ingress -A
kubectl describe ingress <ingress-name>
kubectl get ingressclass再看后端:
kubectl get svc <service-name>
kubectl get endpoints <service-name>最后看 Controller 日志:
kubectl logs -n <ingress-namespace> deployment/<ingress-controller> --tail=200一般判断:
- 404:域名或 path 没命中。
- 502/503:后端 Service 或 Pod 不可用。
- TLS 错误:证书 Secret、域名、证书链或入口配置有问题。
OOMKilled
先看 Pod:
kubectl describe pod <pod-name> | grep -A5 -i OOM
kubectl logs <pod-name> --previous再看资源:
kubectl top pod <pod-name>
kubectl describe node <node-name>修复思路:
- 调整应用内存参数。
- 调整 memory limit。
- 增加副本分摊压力。
- 查内存泄漏、大对象、大查询、大文件处理。
- Java 服务要同步检查 JVM 参数。
Evicted
先看原因:
kubectl describe pod <pod-name>
kubectl describe node <node-name>常见是节点内存压力、磁盘压力、inode 耗尽、容器日志过大、临时存储使用过高。
清理 Evicted Pod 前先列出来:
kubectl get pods -A | grep Evicted确认后再删:
kubectl get pods -A | awk '$4=="Evicted" {print $1, $2}' \
| xargs -r -n2 sh -c 'kubectl delete pod "$1" -n "$0"'生产上不要只清理现象,要回头查节点磁盘、日志轮转、ephemeral storage 和资源水位。
落地工程深水区
K8S 深水区不是“对象很多”,而是所有对象最终都会落到真实资源、网络和团队职责上。
容量和调度:requests 决定能不能调度,limits 决定运行上限。不要全服务复制同一份资源模板。上线前先看:
kubectl top node
kubectl top pod -A
kubectl describe node <node-name>
kubectl get resourcequota,limitrange -A如果节点长期内存水位高,Pod 会出现 OOMKilled、Evicted 或 Pending;这不是改重启次数能解决的。
权限和命名空间:生产集群不要所有服务都放 default。Namespace、ServiceAccount、RoleBinding、ResourceQuota 要一起设计。发布账号只允许操作目标 namespace,先用 kubectl auth can-i 验证。
网络和入口:Pod Running 不等于服务可访问。排查要按 Pod IP -> Service endpoints -> Ingress -> 外部负载均衡 往外看:
kubectl get endpoints <service-name> -n <namespace>
kubectl describe ingress <ingress-name> -n <namespace>
kubectl exec -it <pod-name> -n <namespace> -- curl -fsS http://127.0.0.1:<port>/health存储和数据:无状态服务优先用 Deployment;有状态组件要明确 StorageClass、PVC 回收策略、备份和恢复演练。不要把业务数据写进容器可写层,也不要把 PVC 删除策略交给默认值碰运气。
升级和回滚:K8S 本身升级要看 version skew policy,业务发布要保留 revisionHistoryLimit、镜像 digest、配置变更和回滚命令。数据库变更、消息兼容和配置兼容不归 K8S 自动兜底。
监控和告警:最少要覆盖节点 NotReady、Pod CrashLoopBackOff、ImagePullBackOff、OOMKilled、PVC 未绑定、Service endpoints 为空、Ingress 5xx、证书过期、核心组件异常。没有告警,K8S 只是把失败状态写出来,并不会自动通知你。
生产上线检查清单
上线前不要只问“Pod 起了吗”。至少按这张清单走一遍:
[ ] K8S 版本仍在维护窗口内
[ ] kubelet / kubectl / kube-apiserver 版本偏移符合规则
[ ] 节点 Ready,CNI、CoreDNS 正常
[ ] 镜像已推送到目标仓库,节点可拉取
[ ] Deployment replicas、selector、labels 正确
[ ] Service selector 能匹配 Pod
[ ] Endpoints 有后端 Pod
[ ] readiness/liveness/startup probe 已按启动特征配置
[ ] requests/limits 已配置
[ ] ConfigMap / Secret 已存在且 key 正确
[ ] PVC 已 Bound
[ ] Ingress 或入口层规则已验证
[ ] 发布命令和回滚命令已准备
[ ] 核心接口、日志、指标、告警验证项已准备发布后再看:
kubectl rollout status deployment/<name>
kubectl get pod -l app=<app-name> -o wide
kubectl get endpoints <service-name>
kubectl logs deployment/<name> --tail=100
kubectl top pod -l app=<app-name>访问入口后应该看到健康检查正常、核心接口正常、日志没有连续异常、CPU 和内存没有明显冲高。
一套最小生产建议
镜像方面:
- 不用
latest。 - 业务镜像固定 tag,关键服务记录 digest。
- 所有节点只拉私有仓库。
- 镜像拉取凭证按 Namespace 配好。
资源方面:
- 所有生产 Pod 都写 requests 和 limits。
- Namespace 配 ResourceQuota 和 LimitRange。
- 网关、批处理、Java 服务分开估算资源。
- 用压测和监控修正配置,不要全服务复制一个模板。
发布方面:
- Deployment 保留
revisionHistoryLimit。 - 设置合理
maxSurge和maxUnavailable。 - 每次发布记录镜像版本和变更原因。
- 回滚命令提前演练。
配置方面:
- 普通配置用 ConfigMap。
- 敏感信息用 Secret 或外部密钥系统。
- 配置变更要说明是否重启。
- 不把环境配置写死进镜像。
排障方面:
- 先
get看状态。 - 再
describe看事件。 - 再
logs --previous看崩溃现场。 - 再
exec进容器确认文件、端口、环境变量。 - 最后结合节点资源、网络、存储和应用日志判断根因。
故障命令速查
真正线上出事,先别慌,先把这组命令打出来:
kubectl get nodes -o wide
kubectl get pods -A -o wide
kubectl get deploy,rs,svc,ingress -A
kubectl describe pod <pod-name> -n <namespace>
kubectl events -n <namespace> --for pod/<pod-name>
kubectl logs <pod-name> -n <namespace> --previous
kubectl get endpoints <service-name> -n <namespace>
kubectl describe node <node-name>这组命令的目的不是立刻修好,而是先判断故障属于哪一层:调度、镜像、启动、配置、存储、服务发现、入口、资源,还是应用自身。
排障最怕一上来就猜。K8S 已经把很多状态写在对象和事件里了,先把这些信息读出来,基本就能少走一半弯路。
