容器和 k8s 入门

2025-11-02T14:11:02+08:00 | 15分钟阅读 | 更新于 2025-11-02T14:11:02+08:00

@

学习目标

学完本章你应该能够:

  1. 说清容器不是虚拟机,而是用 Linux Namespace / Cgroups / UnionFS 实现的进程级隔离,并讲出 Docker / containerd / runc 的层级关系。
  2. 写一份生产级多阶段 Dockerfile(Go → distroless),解释层缓存顺序为什么「最不变放最前」,以及 .dockerignore 漏了 .env 会出什么事故。
  3. 用 kind 在本地起一个多节点 + Ingress + 私有仓库的 K8s 集群,并踩平 M1/M2 架构与 registry 残留两个坑。
  4. 区分工作负载 / 网络 / 配置 / 存储四类核心资源,照着一份 YAML 把 Deployment + Service + ConfigMap + PVC 跑起来。
  5. 在 YAML / Helm / Kustomize 之间按场景选型,讲清各自「复制粘贴漏改 / upgrade 不删资源 / list patch 反直觉」的坑。

前置知识

  • 会基本使用命令行(bash)、了解 Go 或任意一门编译型语言
  • 知道什么是镜像、容器的基本概念(哪怕只跑过 docker run
  • 建议先回看本文「容器底层技术栈」一节再动手搭集群

本章你会动手做的事

  1. 用文末的多阶段 Dockerfile 把你的一个 Go 服务构建成 <30MB 的 distroless 镜像。
  2. kind create cluster 起一个 3 节点集群,装好 ingress-nginx,推一个本地镜像进去。
  3. 把「一套能跑的应用清单」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 是定义容器镜像构建过程的脚本。生产环境中应遵循以下最佳实践:

  1. 选择合适的基础镜像:优先使用官方精简镜像,如 golang:1.21-alpinedistroless 等。
  2. 多阶段构建:将编译阶段与运行阶段分离,减少最终镜像体积。
  3. 合理分层与缓存:将不常变化的依赖安装放在前面,提高构建缓存命中率。
  4. 非 root 运行:通过 USER 指令降低容器内权限。
  5. 只复制必要文件:使用 .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.modgo mod downloadCOPY 源码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

踩坑提示:

  • readinessProbelivenessProbe 不要用同一个 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。典型结构包含 apiVersionkindmetadataspec 四个部分。

裸 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 三者对比

维度裸 YAMLHelmKustomize
学习成本最低中(Go template 语法)低(无模板语言)
适合场景单环境、小项目、demo多环境、需要版本化分发的应用包同一应用多环境差异化
模板能力强(循环、条件、函数)弱(只能 patch 和 overlay)
包分发不支持支持(helm repo)不支持
GitOps 友好高(纯文件)中(需要 helm template 渲染)高(原生 YAML)
回滚能力靠 git reverthelm 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 跑一遍,比看十篇文章都管用。

自测题与动手练习

自测题(合上书能答出来,才算懂)

  1. 容器和虚拟机最本质的区别是什么?Namespace、Cgroups、UnionFS 分别解决了「隔离」里的哪一部分?
  2. K8s 1.24 之后为什么不再直接调 Docker?dockershim 移除后,你排查 Pod 起不来该用 docker ps 还是 crictl ps
  3. 多阶段 Dockerfile 里「COPY go.modgo mod downloadCOPY 源码build」这个顺序为什么能最大化缓存命中?反过来放会怎样?
  4. livenessProbereadinessProbe 失败分别触发什么动作?为什么二者不能共用同一个 path?PVC 的 ReadWriteMany 受什么约束?
  5. Helm 改了 values.yaml 之后直接生效吗?helm upgrade 会删除 templates 里被移除的 Deployment 吗?Kustomize 的 patch 是替换还是合并?

动手练习(建议真做一遍)

  1. 用文末 distroless Dockerfile 构建一个 Go 服务,docker image ls 看体积,再用 :debug tag 进去 sh 排查一次。
  2. kind create cluster 起 3 节点集群,装 ingress-nginx,把一个本地镜像推到 localhost:5001kubectl run 起来。
  3. 把「一套能跑的应用清单」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 实战里。
About Me

没什么想介绍的,一个很大众的码农…

喜欢代码,车,马,真的是 🐎

讨厌别人让我给自己的代码写注释 最厌烦别人的程序没有写注释

目标

学AI,加油!加油!