学习目标
学完本章你应该能够:
- 说清容器不是虚拟机,而是用 Linux Namespace / Cgroups / UnionFS 实现的进程级隔离,并讲出 Docker / containerd / runc 的层级关系。
- 写一份生产级多阶段 Dockerfile(Go → distroless),解释层缓存顺序为什么「最不变放最前」,以及
.dockerignore漏了.env会出什么事故。 - 用 kind 在本地起一个多节点 + Ingress + 私有仓库的 K8s 集群,并踩平 M1/M2 架构与 registry 残留两个坑。
- 区分工作负载 / 网络 / 配置 / 存储四类核心资源,照着一份 YAML 把 Deployment + Service + ConfigMap + PVC 跑起来。
- 在 YAML / Helm / Kustomize 之间按场景选型,讲清各自「复制粘贴漏改 / upgrade 不删资源 / list patch 反直觉」的坑。
前置知识:
- 会基本使用命令行(bash)、了解 Go 或任意一门编译型语言
- 知道什么是镜像、容器的基本概念(哪怕只跑过
docker run) - 建议先回看本文「容器底层技术栈」一节再动手搭集群
本章你会动手做的事:
- 用文末的多阶段 Dockerfile 把你的一个 Go 服务构建成 <30MB 的 distroless 镜像。
kind create cluster起一个 3 节点集群,装好 ingress-nginx,推一个本地镜像进去。- 把「一套能跑的应用清单」
kubectl apply -f起来,故意把livenessProbe的 path 改错,观察 CrashLoopBackOff。
容器底层技术栈
容器不是虚拟机,而是基于 Linux Namespace、Cgroups、UnionFS 等技术实现的进程级隔离。云原生场景下常见的容器运行时包括:
| 组件 | 定位 | 说明 |
|---|---|---|
| Docker | 容器平台 | 提供 CLI、镜像构建、容器生命周期管理等完整体验 |
| containerd | 高级容器运行时 | 从 Docker 中剥离,专注容器管理,K8s 默认使用 |
| CRI-O | 轻量容器运行时 | 专为 Kubernetes CRI 设计,兼容 OCI 标准 |
| runc | 低级容器运行时 | OCI 参考实现,负责真正创建和运行容器进程 |
(先用一张图建立层级直觉:你敲的命令在最上面,真正 fork 出容器进程的是最底层 runc,中间 containerd 负责镜像与卷。)
flowchart TD
CLI[你敲 docker run / k8s 调用] --> CRI[CRI 接口
gRPC]
CRI --> HL[高级运行时
containerd / CRI-O]
HL --> LL[低级运行时 runc
OCI 参考实现]
LL --> NS[Namespace 隔离 + Cgroup 限资源
fork 出容器进程]Kubernetes 1.24 之后移除了 dockershim,直接使用 containerd 等符合 CRI(Container Runtime Interface)的运行时。理解这一分层,有助于排查镜像拉取、容器启动、资源限制等实际问题。
一张图理清 Docker / containerd / runc / OCI
说实话我刚开始学这块的时候也懵——一堆名词,到底谁包谁?后来自己画了张图就懂了:
你写的命令 / K8s 调用
│
▼
┌─────────────────────────────────┐
│ 高层接口 │
│ docker CLI / crictl / k8s │
└────────────────┬────────────────┘
│ CRI(Container Runtime Interface,gRPC 协议)
▼
┌─────────────────────────────────┐
│ 高级运行时(High-level Runtime)│
│ containerd | CRI-O │
│ 负责:镜像管理、卷、网络 │
└────────────────┬────────────────┘
│ 调用 OCI runtime
▼
┌─────────────────────────────────┐
│ 低级运行时(Low-level Runtime) │
│ runc(OCI 参考实现) │
│ 负责:真正 fork 出容器进程 │
│ 用 namespace 做隔离,cgroup 限资源 │
└─────────────────────────────────┘
OCI 规范包含两部分:
- Image Spec:镜像长啥样(manifest、layer、config)
- Runtime Spec:容器运行时长啥样(rootfs、process、mounts)
简单说:你敲 docker run,docker CLI 把请求转给 containerd,containerd 拉镜像、准备 rootfs,最后调用 runc 创建容器进程。K8s 1.24+ 不再用 docker,直接调 containerd 的 CRI 接口,链路更短、更省内存。
这里有个排障小技巧:节点上 Pod 起不来,先 crictl ps -a 看容器状态,再 crictl logs <id>。以前用 docker 的时候是 docker ps,现在忘了 docker 命令反而更顺。
Dockerfile 生产最佳实践
Dockerfile 是定义容器镜像构建过程的脚本。生产环境中应遵循以下最佳实践:
- 选择合适的基础镜像:优先使用官方精简镜像,如
golang:1.21-alpine、distroless等。 - 多阶段构建:将编译阶段与运行阶段分离,减少最终镜像体积。
- 合理分层与缓存:将不常变化的依赖安装放在前面,提高构建缓存命中率。
- 非 root 运行:通过
USER指令降低容器内权限。 - 只复制必要文件:使用
.dockerignore避免将敏感文件打入镜像。
完整多阶段构建:Go 应用瘦身到 distroless
下面这份是我线上 API 服务的 Dockerfile,最终镜像只有 20MB 左右。关键是 CGO_ENABLED=0,否则 distroless 没有 glibc 会跑不起来。
# Dockerfile —— 多阶段构建,最终镜像 ~20MB
# syntax=docker/dockerfile:1.6
ARG GO_VERSION=1.22
# ===== 阶段 1:构建 =====
FROM golang:${GO_VERSION}-alpine AS builder
# 装个 git,go mod 私有仓库需要
RUN apk add --no-cache git ca-certificates
WORKDIR /src
# 关键:先 COPY go.mod / go.sum,再 go mod download
# 这样代码改动不会让依赖下载缓存失效
COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
# 再 COPY 源码,只有源码改了才会重新 build
COPY . .
# 关键:CGO_ENABLED=0,纯静态二进制,distroless 才能跑
# -ldflags="-s -w" 去掉调试信息,二进制能小 30%
RUN --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /out/server ./cmd/server
# ===== 阶段 2:运行 =====
FROM gcr.io/distroless/static-debian12:nonroot
# 从 builder 拷贝编译产物
COPY --from=builder /out/server /server
# ca-certificates:HTTPS 请求需要,不然调外部 API 全报 x509 错
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt
# distroless nonroot 镜像自带 nonroot 用户(uid=65532),不用手动建
USER nonroot:nonroot
EXPOSE 8080
ENTRYPOINT ["/server"]
构建命令:
# 用 BuildKit 启用 --mount=cache,加速重复构建
DOCKER_BUILDKIT=1 docker buildx build -t myapp:latest .
# 看镜像大小
docker image ls myapp:latest
踩坑提示:distroless 没有 shell,你想 docker exec -it myapp sh 进去看现场?没门。排查问题的时候要么换 alpine 镜像,要么把 distroless image 换成 debug tag(gcr.io/distroless/static-debian12:debug-nonroot),里面带 busybox shell。生产用 debug tag,调试完换回去。
层缓存优化:顺序很重要
Dockerfile 每条 RUN/COPY/ADD 都会生成一层。缓存命中的逻辑是:某一层一旦失效,它后面所有层全部失效。所以顺序必须从"最不容易变"到"最容易变"。
反例(我见过最离谱的写法):
# 反例:把 COPY . . 放前面,任何代码改动都让依赖重新装
FROM golang:1.22
WORKDIR /app
COPY . . # ← 改一行代码,下面全失效
RUN go mod download # ← 重新下载所有依赖
RUN go build -o server . # ← 重新编译
正例就是上面那份,go.mod → go mod download → COPY 源码 → go build。改业务代码只会触发最后的 build 层。
.dockerignore 示例
.dockerignore 经常被忽略,但它真的重要。我有一次把 .git 目录打进了镜像,500MB 的镜像里有 200MB 是 git history,蠢哭。
# .dockerignore
# VCS
.git
.gitignore
.gitattributes
# 本地开发文件
.idea
.vscode
*.swp
.DS_Store
# 测试与文档
testdata
*.md
docs/
# 环境变量与密钥(绝对不能进镜像!)
.env
.env.*
*.pem
*.key
config/local.yaml
# 构建产物
bin/
dist/
*.log
# Docker 自身文件
Dockerfile
docker-compose*.yml
.dockerignore
踩坑提示:.env 文件如果被 COPY . . 打进镜像,密钥就泄露了。我有同事干过这事,公司立刻轮换了所有数据库密码。除了 .dockerignore,CI 阶段还可以跑 trivy fs --secret-config trivy-secret.yaml . 扫一遍,双重保险。
本地 Kubernetes 集群搭建
本地学习 K8s 的常用方案有:
- minikube:跨平台单节点 K8s,适合初学者。
- kind(Kubernetes IN Docker):用 Docker 容器模拟多节点集群,CI 与本地测试常用。
- k3s/k3d:轻量级发行版,资源占用低,适合边缘与本地开发。
- Docker Desktop:自带单节点 K8s,开箱即用。
kind 完整搭建:多节点 + Ingress + 私有镜像仓库
(直觉:kind 把每个 K8s 节点装进一个 Docker 容器——control-plane 是「市政府」,worker 是「街区」,私有 registry 是「本地货运站」。下面这张图把节点与 registry 的关系画出来。)
flowchart LR
CP[control-plane
ingress-ready] --> W1[worker]
CP --> W2[worker]
CP --> W3[worker + disktype=ssd]
REG[本地 registry:5001] -.->|docker network connect| CP
ING[ingress-nginx] --> CP我现在本地开发就用 kind,启动快、多节点能模拟真实调度。下面这份是我常用的 kind.yaml:
# kind-config.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
# 关键:把 nginx ingress controller 的 80/443 端口映射到宿主机
nodes:
- role: control-plane
kubeadmConfigPatches:
- |-
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
# 允许 master 调度 Pod(本地开发用,生产别这么干)
node-labels: "ingress-ready=true"
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
- role: worker
- role: worker
# 加一个 registry 节点,本地推镜像不用走公网
- role: worker
labels:
disktype: ssd
containerdConfigPatches:
- |-
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."localhost:5001"]
endpoint = ["http://kind-registry:5000"]
完整搭建脚本:
# 1. 装 kind
brew install kind
# 2. 起集群
kind create cluster --config kind-config.yaml --name dev
# 3. 起本地 registry(CI 友好,省去 docker push 公网镜像库)
docker run -d --restart=always \
-p 5001:5000 --name kind-registry registry:2
# 4. 把 registry 连到 kind 网络里,让节点能拉到镜像
docker network connect kind kind-registry
# 5. 验证
kubectl get nodes
# NAME STATUS ROLES AGE VERSION
# dev-control-plane Ready control-plane 1m v1.30.0
# dev-worker Ready <none> 1m v1.30.0
# dev-worker2 Ready <none> 1m v1.30.0
# 6. 装 ingress controller(kind 官方脚本)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
# 7. 把本地镜像推到 registry 测试
docker tag myapp:latest localhost:5001/myapp:latest
docker push localhost:5001/myapp:latest
# 8. 在集群里用这个镜像
kubectl run myapp --image=kind-registry:5000/myapp:latest
踩坑提示:kind delete cluster --name dev 之后 registry 容器还在,得手动 docker rm kind-registry。我重装集群无数次才发现这事儿,硬盘空间悄悄少了几个 G。
另一个坑:M1/M2 Mac 上 kind 跑 amd64 镜像会很慢(QEMU 模拟)。一定要 docker buildx build --platform linux/arm64,或者直接 --load 用本地架构。
K8s 核心资源
Kubernetes 通过声明式 API 管理应用生命周期。核心资源可分为四类:
(用一张图记住这四类:工作负载管「跑什么」,网络管「怎么通」,配置管「用什么参数」,存储管「数据放哪」。)
flowchart TD
A[核心资源] --> B[工作负载
Pod/Deployment/StatefulSet/DaemonSet/Job]
A --> C[网络
Service/Ingress/NetworkPolicy]
A --> D[配置
ConfigMap/Secret/downwardAPI]
A --> E[存储
PV/PVC/StorageClass]工作负载
- Pod:最小调度单元,包含一个或多个容器。
- Deployment:管理无状态应用,支持滚动更新与副本控制。
- StatefulSet:管理有状态应用,提供稳定网络标识与持久存储。
- DaemonSet:每个节点运行一个 Pod,适合日志、监控代理。
- Job / CronJob:一次性或定时任务。
网络
- Service:ClusterIP、NodePort、LoadBalancer、ExternalName 四种类型。
- Ingress / IngressClass:七层流量入口,通常配合 NGINX、Traefik 使用。
- NetworkPolicy:定义 Pod 间网络访问策略。
配置
- ConfigMap:存储非敏感配置。
- Secret:存储敏感信息,如密码、Token。
- ** downwardAPI**:将 Pod/Node 元数据注入容器。
存储
- PersistentVolume(PV):集群层面的存储资源。
- PersistentVolumeClaim(PVC):用户申请存储的接口。
- StorageClass:动态供应存储模板。
完整 YAML:一套能跑的应用清单
下面这份是我贴在新人 wiki 上的"教科书式"应用清单,Deployment + Service + ConfigMap + PVC 全在一个文件里,kubectl apply -f 一把跑起来。
# app.yaml —— 完整应用清单
apiVersion: v1
kind: Namespace
metadata:
name: demo
---
# ConfigMap:非敏感配置
apiVersion: v1
kind: ConfigMap
metadata:
name: api-config
namespace: demo
data:
app.yaml: |
server:
port: 8080
timeout: 30s
log:
level: info
format: json
# 注意:data 里的值都是字符串,数字也要引号
feature_flags: "true"
---
# PVC:申请 10Gi 存储,用 StorageClass 动态供应
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: api-data
namespace: demo
spec:
accessModes:
- ReadWriteOnce # 单 Pod 读写,多 Pod 共享用 ReadWriteMany
storageClassName: standard # 跟集群里的 StorageClass 名对齐
resources:
requests:
storage: 10Gi
---
# Deployment:管理 Pod 副本
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
namespace: demo
labels:
app: api-server
spec:
replicas: 3
selector:
matchLabels:
app: api-server
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # 滚动时最多多出 1 个 Pod
maxUnavailable: 0 # 不允许减少可用副本,0 停机发布
template:
metadata:
labels:
app: api-server
spec:
containers:
- name: api
image: localhost:5001/api-server:v1.0.0
ports:
- containerPort: 8080
name: http
# 关键:readinessProbe 没过不接流量,livenessProbe 挂了重启
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 15 # 给应用启动留时间
periodSeconds: 10
# 资源限制必加,否则 OOM 时调度器抓瞎
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# 挂载 ConfigMap 当配置文件
volumeMounts:
- name: config
mountPath: /etc/app
readOnly: true
- name: data
mountPath: /var/lib/app/data
env:
- name: LOG_LEVEL
valueFrom:
configMapKeyRef:
name: api-config
key: feature_flags
volumes:
- name: config
configMap:
name: api-config
- name: data
persistentVolumeClaim:
claimName: api-data
# 优雅终止:给应用 30s 清理时间
terminationGracePeriodSeconds: 30
---
# Service:暴露服务,ClusterIP 给集群内访问
apiVersion: v1
kind: Service
metadata:
name: api-server
namespace: demo
spec:
type: ClusterIP
selector:
app: api-server
ports:
- port: 80
targetPort: 8080
protocol: TCP
name: http
踩坑提示:
readinessProbe和livenessProbe不要用同一个 path。readiness 失败只是切流量,liveness 失败会重启容器,逻辑混在一起很容易死循环重启。livenessProbe.initialDelaySeconds一定比应用冷启动时间长,否则 Pod 还没起好就被 kill 重启,陷入 CrashLoopBackOff。- PVC 的
accessModes要看 StorageClass 支持啥,NFS 才支持 ReadWriteMany,EBS 只能 ReadWriteOnce。
应用交付:YAML、Manifest、Helm、Kustomize
(直觉:裸 YAML 像「手写便签」——直白但多环境要抄多份;Helm 像「模板印刷机」——一套模板印多版;Kustomize 像「透明叠图」——同一底图上面盖不同环境的差异层。下面把三者并列。)
flowchart LR
Y[裸 YAML
直白易改 多环境易漏改] --> H[Helm
模板强 包分发]
Y --> K[Kustomize
overlay 无模板]
H --> SEL[选型: 中间件 Chart]
K --> SEL2[选型: 业务多环境]YAML / Manifest
K8s 资源以 YAML 形式描述,称为 Manifest。典型结构包含 apiVersion、kind、metadata、spec 四个部分。
裸 YAML 的好处是直白——所见即所得,kubectl apply -f 就能跑。但环境一多就崩了,dev/staging/prod 三个环境复制三份,改一个字段改三遍,必然漏改。
Helm
Helm 是 K8s 的包管理工具,通过 Chart 将一组资源模板化。核心概念:
- Chart:应用包目录。
- Template:使用 Go template 渲染 YAML。
- Values:参数化配置。
- Release:Chart 部署到集群后的实例。
Helm Chart 目录结构
下面是 helm create myapp 生成的标准目录结构,我自己加了几条注释:
myapp/
├── Chart.yaml # Chart 元信息:name/version/appVersion
├── values.yaml # 默认参数,被 values-prod.yaml 覆盖
├── values-prod.yaml # 生产环境覆盖值
├── templates/
│ ├── deployment.yaml # 应用 Deployment
│ ├── service.yaml # Service
│ ├── ingress.yaml # Ingress
│ ├── configmap.yaml # ConfigMap
│ ├── _helpers.tpl # 公共模板片段(名字、label selector)
│ ├── NOTES.txt # 安装后给用户看的提示信息
│ └── tests/
│ └── test-connection.yaml # helm test 跑的冒烟测试
├── charts/ # 依赖的子 Chart
└── .helmignore # 类似 .gitignore
Chart.yaml 长这样:
apiVersion: v2
name: myapp
description: A simple API service
type: application
version: 0.1.0 # Chart 版本
appVersion: "1.0.0" # 应用版本,跟镜像 tag 对应
values.yaml:
# values.yaml —— 默认值
replicaCount: 2
image:
repository: localhost:5001/myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# 生产覆盖值在 values-prod.yaml
ingress:
enabled: false
hosts: []
values-prod.yaml:
# values-prod.yaml —— 生产环境覆盖
replicaCount: 5
image:
tag: "1.2.3" # 锁定生产版本
resources:
limits:
cpu: 1000m
memory: 1Gi
ingress:
enabled: true
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
部署命令:
# 用 prod values 部署
helm install myapp ./myapp -f values-prod.yaml -n prod
# 升级版本
helm upgrade myapp ./myapp -f values-prod.yaml -n prod
# 看渲染结果,不真正部署(调试神器)
helm template myapp ./myapp -f values-prod.yaml
# 回滚到上一版本
helm rollback myapp 1 -n prod
踩坑提示:helm install 之后改 values.yaml 是没用的,必须 helm upgrade。我有同事改完 values 直接以为生效了,结果线上跑的还是老配置。还有个坑——helm upgrade 不会删资源,如果你在 templates 里删了一个 Deployment,老的那个会一直留着,得 helm upgrade --atomic 或者手动清理。
Kustomize
Kustomize 通过 overlay 方式管理不同环境的配置差异,无需模板语言。典型目录结构:
base/
deployment.yaml
service.yaml
kustomization.yaml
overlays/
dev/
kustomization.yaml
replica-patch.yaml
prod/
kustomization.yaml
replica-patch.yaml
resource-patch.yaml
base/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels:
app: myapp
resources:
- deployment.yaml
- service.yaml
overlays/prod/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: prod
resources:
- ../../base
patches:
- replica-patch.yaml # 改副本数
- resource-patch.yaml # 改资源配置
images:
- name: myapp # 把 base 里的镜像 tag 替换掉
newName: registry.example.com/myapp
newTag: "1.2.3"
overlays/prod/replica-patch.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 5
部署:
# 渲染 prod 配置
kustomize build overlays/prod
# 直接 apply
kustomize build overlays/prod | kubectl apply -f -
# kubectl 1.14+ 原生支持
kubectl apply -k overlays/prod
踩坑提示:Kustomize 的 patch 是按字段合并,不是替换。如果你想替换整个 list(比如所有 containers),得用 strategic merge patch 的特殊语法或者 JSON Patch。我踩过这个坑,改了半天发现 list 只是被追加不是被替换。
YAML vs Helm vs Kustomize 三者对比
| 维度 | 裸 YAML | Helm | Kustomize |
|---|---|---|---|
| 学习成本 | 最低 | 中(Go template 语法) | 低(无模板语言) |
| 适合场景 | 单环境、小项目、demo | 多环境、需要版本化分发的应用包 | 同一应用多环境差异化 |
| 模板能力 | 无 | 强(循环、条件、函数) | 弱(只能 patch 和 overlay) |
| 包分发 | 不支持 | 支持(helm repo) | 不支持 |
| GitOps 友好 | 高(纯文件) | 中(需要 helm template 渲染) | 高(原生 YAML) |
| 回滚能力 | 靠 git revert | helm rollback 内建 | 靠 git revert |
| 坑 | 复制粘贴漏改 | 模板复杂后可读性差、upgrade 不删资源 | list patch 行为反直觉 |
我个人现在的选型:小项目和个人工具直接裸 YAML;公司内部分发的中间件(比如 MySQL、Redis 集群)用 Helm Chart;业务应用多环境差异化用 Kustomize + ArgoCD。三者不是互斥,经常一起用——Helm Chart 作为基础包,Kustomize 在上面再 patch 几个环境差异。
总结
容器与 Kubernetes 是云原生架构的基石。掌握容器运行时原理、Dockerfile 构建技巧、K8s 核心资源模型,以及 Helm/Kustomize 等交付工具,是进入 AIOps、Operator 开发和可观测性领域的必备基础。
说真的这块内容看着多,但每天都会用到。我现在写 Operator、调 ArgoCD、查 Pod 故障,每个动作背后都是这些基础概念。刚入门的同学别贪多,先把 kind 起一个集群,跟着这份笔记把 Deployment/Service/Helm 跑一遍,比看十篇文章都管用。
自测题与动手练习
自测题(合上书能答出来,才算懂):
- 容器和虚拟机最本质的区别是什么?Namespace、Cgroups、UnionFS 分别解决了「隔离」里的哪一部分?
- K8s 1.24 之后为什么不再直接调 Docker?
dockershim移除后,你排查 Pod 起不来该用docker ps还是crictl ps? - 多阶段 Dockerfile 里「
COPY go.mod→go mod download→COPY 源码→build」这个顺序为什么能最大化缓存命中?反过来放会怎样? livenessProbe与readinessProbe失败分别触发什么动作?为什么二者不能共用同一个 path?PVC 的ReadWriteMany受什么约束?- Helm 改了
values.yaml之后直接生效吗?helm upgrade会删除 templates 里被移除的 Deployment 吗?Kustomize 的 patch 是替换还是合并?
动手练习(建议真做一遍):
- 用文末 distroless Dockerfile 构建一个 Go 服务,
docker image ls看体积,再用:debugtag 进去sh排查一次。 kind create cluster起 3 节点集群,装 ingress-nginx,把一个本地镜像推到localhost:5001后kubectl run起来。- 把「一套能跑的应用清单」apply 后,故意把
replicas改成 5 并kubectl scale,再用kubectl rollout做一次滚动回滚。
本章小结
- 容器是进程级隔离(Namespace+Cgroups+UnionFS),运行时分「高级(containerd/CRI-O)+ 低级(runc)」两层,K8s 1.24+ 直接走 CRI。
- 生产 Dockerfile 用多阶段构建 + distroless,缓存顺序「最不变在前」,
.dockerignore必须挡住.env/密钥。 - 本地用 kind 起多节点集群 + ingress + 私有仓库;注意 M1/M2 架构与 registry 容器残留两个坑。
- K8s 核心资源分工作负载/网络/配置/存储四类;交付方式按场景在裸 YAML / Helm / Kustomize 间选型,三者常组合使用。
- 下一篇可进入「K8s 可观测性与 Operator 开发」,把这里的基础用到 AIOps 实战里。