上一讲你自己造了一个集装箱。今天,把它交给集群,让它自己跑起来、自己活下去、自己换版本 —— 并且挂上属于你自己的域名。
| 单机 Docker 的痛 | 今天的答案 |
|---|---|
| 机器跑满,手工换机器 | Scheduler 自动挑节点调度 |
| 进程挂了,半夜爬起来重启 | Deployment 副本掉了自动补 |
| 升级要停服 | 滚动更新 新起旧摘,全程可用 |
| 端口冲突、对外暴露难 | Service + Ingress 稳定入口 + 域名 |
| 配置/密码写死在镜像里 | ConfigMap / Secret 配置与镜像分离 |
这些答案不是"五个功能",而是同一套思想推出来的结果 —— 先看这个思想。
浏览器打开 <你的用户名>.dev.oaiai.ai,
看到的是 你自己构建的那个镜像。
看集群状态、读清单、滚一次新版本
亲手把一次故障定位出来
为什么公司要求新人"只看不改"
docker run -d -p 8000:8000 hello:v1
docker stop hello
docker rm hello
docker run -d -p 8000:8000 hello:v2
每一步都要你亲自敲。**机器挂了、进程死了,没人管**。
spec:
replicas: 3
image: registry.akria.net/hello:v2
你只写"我要 3 个、用这个镜像"。**剩下的它自己盯着**。
声明式的价值不在"少敲几条命令",而在集群会持续把现实拉回你声明的那份期望。
$ kubectl -n st-<你> get podshello-6d9f-aaa 1/1 Runninghello-6d9f-bbb 1/1 Running$ kubectl -n st-<你> delete pod hello-6d9f-aaa$ kubectl -n st-<你> get podshello-6d9f-bbb 1/1 Runninghello-6d9f-ccc 1/1 Running ← 新的自己补上来了
replicas: 2
running: 1
不一致 → 动手补齐
这就是"自愈",不是魔法
| 集群 | 用途 | 你能做什么 |
|---|---|---|
| prod(生产) | 线上业务 | 只读:get / describe / logs,仅此而已 |
| learn(教学,你今天用的) | 你们的练习田 | 自己 ns 里可以随意增删改 |
最小调度单元,装 1 个(有时多个)容器。
Pod 是"耗材":坏了就删,不要去修它。
声明"要几个副本、用哪个镜像"。
它负责建 Pod、滚动更新、坏了自己补 —— 你平时只跟它打交道。
新人最常见的错误:手动去删 CrashLoopBackOff 的 Pod"想重来"。
删掉它,Deployment 立刻又建一个,问题照旧 —— 要查日志找根因,不是删 Pod(公司安全规则也明令禁止)。
给一组 Pod 一个稳定入口。
Pod 换来换去 IP 一直在变,Service 的名字不变。
域名 → Service 的路由规则,外加 TLS 证书。
公司用 Traefik 当这层门卫。
浏览器 → 域名 → Traefik(Ingress) → Service → Pod → 容器里的进程
排障时按这条链逐段查:域名对不对 → Ingress 规则对不对 → Service 有没有后端 → Pod 活着没。
逻辑隔离的"地盘"。
你的地盘就是 st-<你的用户名>,配额也按它算。
普通配置:配置文件、index.html、环境变量。
敏感配置:密码、密钥、拉私有镜像的凭据。
为什么要把配置拿出来?因为这样同一个镜像可以跑在不同环境(测试/生产),不用重新构建 —— 上一讲"配置写死在镜像里"的痛,到这里解掉了。
image跑哪个镜像
replicas要几个副本
labels谁认谁(Service 靠它找 Pod)
ports容器监听哪个端口
apiVersion: apps/v1 kind: Deployment metadata: name: hello namespace: st-<你的用户名> spec: replicas: 1 # 几个副本 selector: matchLabels: {app: hello} # 认哪些 Pod template: metadata: labels: {app: hello} # 给它贴的标签 spec: containers: - name: web image: registry.akria.net/hello:v1 # 哪个镜像 ports: [{containerPort: 8000}] # 监听端口
kubectl edit 改了点什么,没人知道git revert 就是回滚集群)
你在公司仓库看到的 manifests/ 目录,就是集群期望状态的"原件" ——
它不是备份,它是事实源。
git tag hello-v1.0.1)registry.akria.net既然 Git 是事实源,那手工登集群改东西 = 制造"漂移":集群和 Git 说的不一样了,下次发版就会互相打架、出了事没人说得清。所以公司规定新人只读生产集群 —— 这不是不信任你,是流程本身的要求。
(自动化同步工具如 ArgoCD / Flux 能把这套做得更极致,行业里很常见;公司目前是"清单进 Git + CI 触发更新"这一步,还没上这类工具。)
想亲眼看到过程:另开一个终端 kubectl -n st-<你> get pods -w,然后改镜像版本。
~/.kube/configst-<你的用户名>,并给你管理员权限网址 brac.oaiai.ai SSH ssh -i ~/.ssh/<你的私钥> -p 30022 \ <你的用户名>@brac.oaiai.ai 登录后 = 一个 Linux 终端 (kubectl 已装好)
详细操作步骤在 STARGATE 站内手册(登录后左侧「手册」):3 分钟从邀请码到 kubectl get nodes。课件不重复贴,以手册为准。
| 步骤 | 要点(照站内手册做) |
|---|---|
| ① 拿到邀请码 | 课上发放,一次性使用、有有效期,丢了找讲师 |
| ② 注册 | 用户名小写字母开头(3–32 位),这个用户名会变成你的命名空间和域名前缀 |
| ③ 下载私钥 | 私钥只显示一次;浏览器可能给它加 .txt 后缀,照实际文件名用 |
| ④ 放进 ~/.ssh | chmod 600 收紧权限,权限太松 SSH 会直接拒绝 |
| ⑤ 登录 | ssh -i 后面要写完整路径,只写文件名会 Permission denied |
登录成功后第一条命令:kubectl get nodes —— 看到两个 Ready 就成功了。
st-<你的用户名> 内管理员 —— 可以随意增删改生产集群 只读,写操作一律禁止
只在 st-<你的用户名> 里写,别碰别人的 ns
域名只能是 xxx.dev.oaiai.ai(准入策略强制,绕不过去)
配额:约 20 个 Pod、2C/2Gi 请求(上限 4C/4Gi)、5 个存储卷。实验做完记得删,别人还要用。
$ kubectl -n st-<你的用户名> create secret docker-registry regcred \ --docker-server=registry.dev.oaiai.ai \ --docker-username=<账号> \ --docker-password=<密码> # 账号密码课上发放(共用的教学仓库账号)
教学仓库是私有的,不做这一步,Pod 一定起不来,报 ImagePullBackOff —— 这不是你写错了清单。
"凭据不只在 push 时需要,pull 也需要"—— 就是这里。
apiVersion: apps/v1
kind: Deployment
metadata: {name: hello, namespace: st-<你的用户名>}
spec:
replicas: 1
selector: {matchLabels: {app: hello}}
template:
metadata: {labels: {app: hello}}
spec:
imagePullSecrets: [{name: regcred}]
containers:
- name: web
image: registry.dev.oaiai.ai/<你的用户名>/hello:v1
ports: [{containerPort: 8000}]
namespace 你的地盘replicas 几个副本labels 贴标签(Service 靠它找你)imagePullSecrets 拉取凭据ports 容器监听端口
保底路径:镜像先换成 nginx:alpine、端口换成 80,把流程跑通再说。
apiVersion: v1
kind: Service
metadata: {name: hello, namespace: st-<你的用户名>}
spec:
selector: {app: hello} # 靠标签找到上面的 Pod
ports:
- port: 80 # Service 对外端口
targetPort: 8000 # 容器实际监听端口
Service 的 selector 必须等于 Pod 的 labels。写错一个字母,Service 就找不到后端 → 页面 503。
kubectl -n st-<你> get endpoints hello
有 IP 就是找到了,空的就是没对上。
前者是集群内部访问用,后者是容器真正监听的。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: {name: hello, namespace: st-<你的用户名>}
spec:
ingressClassName: traefik
rules:
- host: <你的用户名>.dev.oaiai.ai
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: hello
port: {number: 80}
$ kubectl -n st-<你> apply -f hello.yaml$ kubectl -n st-<你> get pods,svc,ingresspod/hello-6d9f-aaa 1/1 Running$ curl http://<你>.dev.oaiai.ai/<!doctype html> …
浏览器打开 http://<你的用户名>.dev.oaiai.ai —— 看到你自己的内容,今天的核心目标达成。
用 http,不用 https:证书要单独申请,Let's Encrypt 有每周限额,课上别批量试。
# 终端 A:盯着看(-w = watch)$ kubectl -n st-<你> get pods -w # 终端 B:改副本数 / 换镜像版本$ kubectl -n st-<你> scale deploy/hello --replicas=3$ kubectl -n st-<你> set image deploy/hello web=registry.dev.oaiai.ai/<你>/hello:v2$ kubectl -n st-<你> rollout status deploy/hello
新 Pod ContainerCreating → Running,然后旧 Pod 才 Terminating。
delete pod 一个副本,看 Deployment 秒级补回来。
回滚也很简单:rollout undo deploy/hello
$ kubectl -n st-<你> set image deploy/hello \ web=registry.dev.oaiai.ai/<你>/hello:v999 $ kubectl -n st-<你> get podshello-7f8c-xyz 0/1 ImagePullBackOff$ kubectl -n st-<你> describe pod hello-7f8c-xyz | tail -20Failed to pull image "…/hello:v999"manifest unknown$ kubectl -n st-<你> rollout undo deploy/hello
get pods —— 看状态describe pod —— 看 Events(最有用)logs —— 看应用自己说了什么get endpoints —— 看 Service 有没有后端反复强调:不要靠删 Pod 解决问题。删了它,Deployment 立刻重建一个,错误照旧 —— 先看 Events 与 logs。
| 状态 | 大概率原因 | 先试 |
|---|---|---|
ImagePullBackOff | 镜像名/标签写错,或没配拉取凭据 —— 事件里出现 no basic auth credentials 就是后者 | describe 看 Failed to pull |
CrashLoopBackOff | 容器起来就退出:配置错、依赖连不上、命令不对 | logs 看应用报错 |
Pending | 调度不出去:资源不够、配额用满 | describe 看 Insufficient |
ContainerCreating 卡住 | 正在拉镜像 / 挂载卷 | 等一会儿,一直卡就 describe |
记住一条:Events 里的那句话,通常就是答案,看不懂就把原文丢给 Claude Code 问。
| 现象 | 断在哪一段 |
|---|---|
404 page not found(Traefik 页面) | Ingress 的 host 拼错,或域名不符合 xxx.dev.oaiai.ai 规则 |
503 Service Temporarily Unavailable | Service 的 selector 和 Pod 的 labels 对不上(get endpoints 为空就是它) |
502 Bad Gateway | Pod 没起来,或没监听你声明的那个端口 |
| 域名解析不了 | 域名规则写错(只能 xxx.dev.oaiai.ai) |
三连命令定位:get pods,svc,ingress → get endpoints hello → describe ingress hello
| 现象 | 先试这个 |
|---|---|
Permission denied (publickey) | -i 要写完整路径;私钥权限 chmod 600;文件名带不带 .txt 要照实际 |
| 建 Ingress 被拒(ValidatingAdmissionPolicy) | 域名必须形如 xxx.dev.oaiai.ai,且每条 rule 都要写 host |
| 忘了自己在哪个 ns | 所有命令加 -n st-<你的用户名>,别依赖默认 |
| apply 报配额不足 | 实验做完了没删(配额 20 Pod / 5Gi 存储),先清理旧作业 |
| 改了镜像没生效 | set image 后要等 rollout status;确认改的是 Deployment 不是 Pod |
| 凭据拷错 / 密码带空格 | regcred 删掉重建,密码复制时别带换行 |
kubectl get nodes 截图pods -w 观察到的变化给 Deployment 加一条 readinessProbe,解释它为什么能让滚动更新更安全
用只读命令看一遍 prod 集群:get nodes / get pods -A / 挑一个服务 describe —— 守住"只看不改"
docs/training/学员/<姓名>/D8/,按 D6 的 Git 规范提 PR,中文提交信息
写 Dockerfile → 推镜像 → 写清单 → 部署 → 挂域名 → 滚版本 → 排故障。 这条链路,就是公司每个服务上线的全流程 —— 你只是把规模缩小到了一个人的地盘。
D9 Python 基础 —— 开始写服务端代码
D22 运维与 CI/CD 实操:把你的项目真正发一次版
站内手册「小实验」:给 nginx 配域名、多副本、HTTPS