第一个应用¶
这一章不追求覆盖所有字段,而是让你完整走一遍「部署 → 暴露 → 验证 → 变更 → 回滚」的闭环。走完之后,你会对声明式 API 有一次真实的体感。
你将学到¶
- 用 Namespace 隔离应用
- 用 Deployment 管理 Pod,用 Service 暴露服务
- 用 Ingress 把服务发布到集群外
- 扩缩容、滚动更新、回滚的标准命令
- 用
get / describe / logs / exec建立排错证据链
应用长什么样¶
我们使用仓库中的 ikubernetes/demoapp:v1.0 镜像。它启动一个 HTTP 服务,监听 80 端口,提供:
| 路径 | 用途 |
|---|---|
/ |
首页,显示 Pod 主机名 |
/livez |
存活探针 |
/readyz |
就绪探针 |
/version |
版本信息 |
最终拓扑:
flowchart LR
U[客户端] --> I[Ingress<br/>demo.lab]
I --> S[Service<br/>demoapp]
S --> P1[Pod v1]
S --> P2[Pod v1]
P1 --> D[Deployment]
P2 --> D
图:外部请求经 Ingress → Service → Pod 的路径。
步骤 1 · 创建命名空间¶
kubectl create namespace demo
kubectl config set-context --current --namespace=demo
kubectl get ns demo
后续命令都默认在 demo 命名空间执行。切回默认:
步骤 2 · 创建 Deployment 和 Service¶
apiVersion: apps/v1
kind: Deployment
metadata:
name: demoapp
namespace: demo
labels:
app: demoapp
spec:
replicas: 2
selector:
matchLabels:
app: demoapp
template:
metadata:
labels:
app: demoapp
version: v1.0
spec:
containers:
- name: demoapp
image: ikubernetes/demoapp:v1.0
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /readyz
port: http
initialDelaySeconds: 3
periodSeconds: 5
livenessProbe:
httpGet:
path: /livez
port: http
initialDelaySeconds: 10
periodSeconds: 10
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
---
apiVersion: v1
kind: Service
metadata:
name: demoapp
namespace: demo
spec:
selector:
app: demoapp
ports:
- name: http
port: 80
targetPort: http
预期:
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/demoapp 2/2 2 2 20s
NAME READY STATUS RESTARTS AGE
pod/demoapp-6c9f7d8b5c-2xk9p 1/1 Running 0 20s
pod/demoapp-6c9f7d8b5c-9m4qz 1/1 Running 0 20s
NAME TYPE CLUSTER-IP PORT(S) AGE
service/demoapp ClusterIP 10.96.123.45 80/TCP 20s
selector 必须匹配
Deployment 的 spec.selector.matchLabels 必须与 template.metadata.labels 匹配,否则 API Server 直接拒绝。Service 的 selector 则匹配 Pod 标签,不匹配时 Endpoints 为空,这是「Service 访问不通」的第一大原因。
步骤 3 · 在集群内验证¶
先看 Endpoints 是否就绪:
预期 ENDPOINTS 至少有两个 Pod IP。然后用临时 Pod 访问:
kubectl run curl --image=curlimages/curl:8.10.1 --rm -it --restart=Never -n demo -- \
curl -s http://demoapp/
或者用 port-forward 在本地验证:
预期返回包含 Pod 主机名的 HTML 或 JSON。
步骤 4 · 用 Ingress 暴露¶
前提:已安装 ingress-nginx,并有一个可解析的域名。实验环境可以在本机 /etc/hosts 中把 demo.lab 指向 Ingress 入口 IP。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demoapp
namespace: demo
spec:
ingressClassName: nginx
rules:
- host: demo.lab
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: demoapp
port:
number: 80
获取入口地址:
如果类型是 LoadBalancer,用 EXTERNAL-IP;如果是 NodePort,用 节点IP:NodePort。然后:
步骤 5 · 扩缩容¶
kubectl scale deploy demoapp -n demo --replicas=5
kubectl rollout status deploy/demoapp -n demo
kubectl get pods -n demo -l app=demoapp
缩回 2 个:
观察 Pod 被终止时,Service 的 Endpoints 会先摘除再删除,避免流量打到正在退出的 Pod。
步骤 6 · 滚动更新与回滚¶
更新镜像版本:
kubectl set image deploy/demoapp demoapp=ikubernetes/demoapp:v1.1 -n demo
kubectl rollout status deploy/demoapp -n demo
kubectl rollout history deploy/demoapp -n demo
验证新版本:
kubectl get pods -n demo -l app=demoapp -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.containers[0].image}{"\n"}{end}'
回滚到上一版:
回滚到指定版本:
kubectl rollout history deploy/demoapp -n demo
kubectl rollout undo deploy/demoapp -n demo --to-revision=1
把变更原因写进 annotation
kubectl annotate deploy/demoapp -n demo \
kubernetes.io/change-cause="upgrade demoapp to v1.1" --overwrite
这样 rollout history 会显示可读的原因,而不是一串镜像名。
步骤 7 · 删除¶
删除 namespace 会级联删除其中所有对象。实验环境可以这样做,生产环境要非常谨慎。
排错证据链¶
当应用访问不通时,按下面顺序收集证据,不要跳步:
| 顺序 | 命令 | 你要确认什么 |
|---|---|---|
| 1 | kubectl get pods -n demo |
Pod 是否 Running、Ready |
| 2 | kubectl describe pod <pod> -n demo |
Events 里有没有拉镜像、探针、调度失败 |
| 3 | kubectl logs <pod> -n demo |
应用是否启动、是否报错 |
| 4 | kubectl get endpoints demoapp -n demo |
Service 是否选中了 Pod |
| 5 | kubectl get ingress -n demo |
Ingress 是否拿到地址、后端是否正确 |
| 6 | kubectl exec -n demo <pod> -- wget -qO- localhost:80 |
容器内应用本身是否正常 |
常见现象¶
| 现象 | 原因 | 处理 |
|---|---|---|
Pod ImagePullBackOff |
镜像名错 / 仓库不可达 | describe pod 看 Events,crictl pull 测试 |
Pod CrashLoopBackOff |
应用启动失败 / 配置错 | logs --previous 看上一次崩溃日志 |
Pod Running 但 0/1 |
readiness 探针失败 | describe 看 probe 失败原因,进容器 curl |
| Service 访问不通 | selector 不匹配 / 端口错 | get endpoints,对照 Service 与 Pod 标签 |
| Ingress 404 | host / path 不匹配 | 检查 Host 头、pathType、IngressClass |
| Ingress 502 | 后端未 Ready / 端口错 | get endpoints,检查 targetPort |
| 滚动更新卡住 | 新 Pod 起不来 / 资源不足 | rollout status、describe、get events |
进阶挑战¶
- 给 Deployment 加上
strategy.rollingUpdate.maxSurge/maxUnavailable,观察更新过程中 Pod 数量的变化。 - 故意把 readiness 探针路径改成
/not-exist,观察 Endpoints 如何变化,再修复。 - 用
kubectl diff -f demoapp.yaml预览一次变更,再 apply。 - 把 Service 改成
NodePort,记录端口范围与访问方式。 - 用
kubectl scale缩到 0,再访问 Service,观察返回什么。
小结¶
- 一个最小应用需要:Deployment 管副本,Service 做稳定入口,Ingress 做外部暴露。
kubectl get看状态,describe看事件,logs看应用,三者缺一不可。- 滚动更新与回滚是 Deployment 的核心能力,务必亲手做一次失败回滚。