- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 1. 容器到底是什么
- 2. Docker 基础
- 3. Dockerfile 最佳实践
- 4. Docker Compose
- 5. Kubernetes 架构
- 6. Kubernetes 核心对象
- 7. Kubernetes 配置管理
- 8. Helm - Kubernetes 包管理器
- 9. 实战部署示例 - Web 应用 + DB + Redis
- 10. 故障排查
- 结语
1. 容器到底是什么
虚拟机与容器的区别
传统的虚拟机 (VM) 在 hypervisor 之上运行一整套 Guest OS。每台 VM 都包含内核、库和二进制文件,因此需要数 GB 磁盘和数十秒的启动时间。
容器则共享宿主机 OS 的内核,只在进程级别做隔离。镜像大小只有几十 MB,启动时间是毫秒级。
| 项目 | 虚拟机 | 容器 |
|---|---|---|
| 隔离级别 | 整个 OS | 进程 |
| 镜像大小 | 数 GB | 几十~几百 MB |
| 启动时间 | 数十秒 | 毫秒 |
| 资源开销 | 高 | 低 |
| 可移植性 | 依赖 hypervisor | 内核一致就能到处跑 |
Linux 命名空间与 cgroups
容器的隔离由两项 Linux 内核功能实现。
命名空间(Namespace)限制进程所能看到的系统资源范围。
- PID 命名空间:在容器内部看到的是从 PID 1 开始的独立进程树
- NET 命名空间:拥有独立的网络接口、IP 地址与路由表
- MNT 命名空间:拥有独立的文件系统挂载点
- UTS 命名空间:拥有独立的主机名
- IPC 命名空间:拥有独立的 IPC 资源
- USER 命名空间:拥有独立的 UID/GID 映射
cgroups(Control Groups) 限制 CPU、内存、磁盘 I/O 等硬件资源的使用量。
# 用 cgroup 确认内存限制
cat /sys/fs/cgroup/memory/docker/CONTAINER_ID/memory.limit_in_bytes
正是靠这两项功能,容器才能一边表现得像一台独立机器,一边共享内核保持轻量。
2. Docker 基础
三个核心概念
镜像(Image)是只读的模板。应用代码、运行时、系统库、配置文件等以分层结构保存在其中。
容器(Container)是镜像的运行实例。它在镜像之上追加了一个可写层。
镜像仓库(Registry)是存储与分发镜像的服务。Docker Hub 最具代表性,此外还有 AWS ECR、GitHub Container Registry 等私有仓库。
必备命令
# pull 镜像
docker pull nginx:1.25
# 运行容器
docker run -d --name my-nginx -p 8080:80 nginx:1.25
# 查看运行中的容器
docker ps
# 查看容器日志
docker logs my-nginx
# 进入容器内部
docker exec -it my-nginx /bin/bash
# 停止并删除容器
docker stop my-nginx
docker rm my-nginx
# 构建镜像
docker build -t my-app:1.0 .
# 把镜像推送到镜像仓库
docker tag my-app:1.0 registry.example.com/my-app:1.0
docker push registry.example.com/my-app:1.0
理解镜像的分层结构
Docker 镜像由多个只读层组成。Dockerfile 中的每条指令都会生成一层。
# 查看镜像分层
docker history my-app:1.0
层是会被缓存的。没有变化的层不会重新构建,因此在 Dockerfile 中把变更频率低的指令放在上面,是构建速度优化的关键。
3. Dockerfile 最佳实践
基本的 Dockerfile 结构
# 指定基础镜像
FROM node:20-alpine
# 设置工作目录
WORKDIR /app
# 先复制依赖文件(缓存优化)
COPY package.json package-lock.json ./
RUN npm ci --only=production
# 复制源代码
COPY . .
# 暴露端口
EXPOSE 3000
# 启动命令
CMD ["node", "server.js"]
多阶段构建
把构建工具与运行时环境分开,可以大幅缩小最终镜像的体积。
# 第 1 阶段: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# 第 2 阶段: 生产
FROM node:20-alpine AS production
WORKDIR /app
COPY /app/dist ./dist
COPY /app/node_modules ./node_modules
COPY /app/package.json ./
EXPOSE 3000
USER node
CMD ["node", "dist/server.js"]
只在构建阶段需要的 devDependencies、源代码和构建工具都不会进入最终镜像。
分层缓存优化
# 坏例子: 源码一有变更就会重新执行 npm install
COPY . .
RUN npm ci
# 好例子: 只要 package.json 没变就能命中缓存
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
.dockerignore 文件
用它避免把不必要的文件带进构建上下文。
node_modules
.git
.env
*.md
dist
.DS_Store
coverage
安全最佳实践
# 1. 用非 root 用户运行
FROM node:20-alpine
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
# 2. 使用固定版本的基础镜像(禁止 latest 标签)
FROM node:20.11.1-alpine3.19
# 3. 使用 COPY,避开 ADD
COPY ./config /app/config
# 4. 不要把敏感信息打进镜像
# 构建时可以用 ARG,但要注意别让它残留在最终镜像里
4. Docker Compose
多容器编排
Docker Compose 用一个 YAML 文件定义并管理多个容器。
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/mydb
- REDIS_URL=redis://cache:6379
depends_on:
db:
condition: service_healthy
cache:
condition: service_started
volumes:
- ./src:/app/src
networks:
- backend
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
POSTGRES_DB: mydb
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user"]
interval: 10s
timeout: 5s
retries: 5
networks:
- backend
cache:
image: redis:7-alpine
ports:
- "6379:6379"
networks:
- backend
volumes:
postgres_data:
networks:
backend:
driver: bridge
Compose 核心命令
# 启动全部服务
docker compose up -d
# 查看日志
docker compose logs -f app
# 重新构建指定服务后启动
docker compose up -d --build app
# 查看服务状态
docker compose ps
# 停止全部服务并清理资源
docker compose down -v
网络与卷
网络:同一个 Compose 文件中的服务之间可以用服务名互相通信。上面的示例里,app 服务通过 db:5432 访问数据库。
卷:即使容器被删除,数据也会保留。名为 postgres_data 的 named volume 保管着数据库文件。
开发环境 vs 生产环境
# docker-compose.override.yml(开发环境自动生效)
services:
app:
build:
target: development
volumes:
- ./src:/app/src
environment:
- NODE_ENV=development
command: npm run dev
# 生产部署时排除 override
docker compose -f docker-compose.yml up -d
5. Kubernetes 架构
为什么需要 Kubernetes
Docker Compose 适合在单台主机上管理多个容器,但真实的生产环境还需要这些能力。
- 跨多台服务器部署容器
- 自动伸缩
- 服务发现与负载均衡
- 滚动更新与回滚
- 自愈 (self-healing)
Kubernetes(K8s) 就是提供上述全部能力的容器编排平台。
Control Plane 组件
API Server(kube-apiserver):处理集群全部请求的中央关口。kubectl 命令、内部组件、外部客户端都要经过 API Server。
etcd:保存集群全部状态的分布式键值存储。哪个 Pod 跑在哪里、存在哪些 Service,这类信息都在这里。
Scheduler(kube-scheduler):决定新创建的 Pod 该放到哪个节点上。它会综合考虑资源需求、节点状态、亲和性规则等。
Controller Manager(kube-controller-manager):负责把集群当前状态调整到期望状态 (desired state)。其中包含 ReplicaSet Controller、Node Controller、Job Controller 等。
Worker Node 组件
kubelet:运行在每个节点上,按照 API Server 的指示启动容器并汇报状态。
kube-proxy:在每个节点上管理网络规则,处理 Service 的负载均衡。
Container Runtime:实际运行容器的软件。containerd 使用得最为广泛。
Control Plane
+------------------+
| API Server |<--- kubectl, 客户端
| etcd |
| Scheduler |
| Controller Mgr |
+------------------+
|
Worker Node 1 Worker Node 2
+-----------------+ +-----------------+
| kubelet | | kubelet |
| kube-proxy | | kube-proxy |
| containerd | | containerd |
| [Pod] [Pod] | | [Pod] [Pod] |
+-----------------+ +-----------------+
6. Kubernetes 核心对象
Pod
Pod 是 K8s 中可部署的最小单位。它包含一个或多个容器,这些容器共享同一套网络与存储。
apiVersion: v1
kind: Pod
metadata:
name: my-app
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 10
periodSeconds: 5
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 3
ReplicaSet
保证指定数量的 Pod 副本始终处于运行状态。通常不直接使用,而是通过 Deployment 来管理。
Deployment
以声明式方式管理 Pod 与 ReplicaSet。支持滚动更新、回滚与伸缩。
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: app
image: my-app:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
# 查看部署状态
kubectl rollout status deployment/my-app
# 更新镜像(触发滚动更新)
kubectl set image deployment/my-app app=my-app:2.0
# 回滚
kubectl rollout undo deployment/my-app
# 伸缩
kubectl scale deployment/my-app --replicas=5
Service
为 Pod 提供稳定的网络端点。Pod 是临时的,而 Service 的 IP 与 DNS 是固定的。
ClusterIP(默认值):只能从集群内部访问。
apiVersion: v1
kind: Service
metadata:
name: my-app-svc
spec:
type: ClusterIP
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
NodePort:通过每个节点上的特定端口从外部访问。
apiVersion: v1
kind: Service
metadata:
name: my-app-nodeport
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
nodePort: 30080
LoadBalancer:自动预置云厂商的负载均衡器。
apiVersion: v1
kind: Service
metadata:
name: my-app-lb
spec:
type: LoadBalancer
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
Ingress
把 HTTP/HTTPS 流量路由到集群内部的 Service。可以定义基于主机名与路径的路由规则。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app-svc
port:
number: 80
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
tls:
- hosts:
- app.example.com
secretName: tls-secret
7. Kubernetes 配置管理
ConfigMap
把环境配置与容器镜像分离开来管理。
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
DATABASE_HOST: "db-service"
DATABASE_PORT: "5432"
LOG_LEVEL: "info"
app.properties: |
server.port=3000
cache.ttl=300
# 在 Pod 中使用 ConfigMap
spec:
containers:
- name: app
image: my-app:1.0
envFrom:
- configMapRef:
name: app-config
volumeMounts:
- name: config-volume
mountPath: /app/config
volumes:
- name: config-volume
configMap:
name: app-config
items:
- key: app.properties
path: app.properties
Secret
管理密码、API 密钥等敏感数据。它们以 base64 编码后保存。
# 创建 Secret
kubectl create secret generic db-secret \
--from-literal=username=admin \
--from-literal=password=s3cret
apiVersion: v1
kind: Secret
metadata:
name: db-secret
type: Opaque
data:
username: YWRtaW4=
password: czNjcmV0
# 在 Pod 中使用 Secret
spec:
containers:
- name: app
env:
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
PersistentVolume (PV) 与 PersistentVolumeClaim (PVC)
让数据独立于 Pod 生命周期而保留下来。
# PersistentVolumeClaim
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
# 在 Deployment 中使用 PVC
spec:
containers:
- name: postgres
image: postgres:16-alpine
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
8. Helm - Kubernetes 包管理器
什么是 Helm
Helm 是把 K8s manifest 打包成 chart 来管理的工具。它可以把多个 YAML 文件当作一个整体来安装、升级和回滚。
chart 结构
my-app-chart/
Chart.yaml # chart 元数据
values.yaml # 默认配置值
templates/ # K8s manifest 模板
deployment.yaml
service.yaml
ingress.yaml
configmap.yaml
_helpers.tpl # 模板辅助函数
charts/ # 依赖 chart
values.yaml
# values.yaml
replicaCount: 3
image:
repository: my-app
tag: "1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
hostname: app.example.com
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilization: 70
Helm 核心命令
# 安装 chart
helm install my-release ./my-app-chart
# 用自定义 values 安装
helm install my-release ./my-app-chart -f production-values.yaml
# 升级 release
helm upgrade my-release ./my-app-chart --set image.tag=2.0
# 查看 release 列表
helm list
# 查看 release 状态
helm status my-release
# release 历史
helm history my-release
# 回滚
helm rollback my-release 1
# 删除 release
helm uninstall my-release
按环境管理 values 文件
# 按环境划分的文件结构
values.yaml # 通用默认值
values-dev.yaml # 开发环境
values-staging.yaml # 预发环境
values-prod.yaml # 生产环境
# 部署到预发环境
helm upgrade --install my-app ./my-app-chart \
-f values.yaml \
-f values-staging.yaml \
--namespace staging
# 部署到生产环境
helm upgrade --install my-app ./my-app-chart \
-f values.yaml \
-f values-prod.yaml \
--namespace production
9. 实战部署示例 - Web 应用 + DB + Redis
整体构成
这是把 Web 应用、PostgreSQL 数据库和 Redis 缓存部署到 Kubernetes 的完整示例。
创建命名空间
kubectl create namespace my-app
部署 PostgreSQL
# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:16-alpine
ports:
- containerPort: 5432
env:
- name: POSTGRES_DB
value: mydb
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: my-app
spec:
selector:
app: postgres
ports:
- port: 5432
targetPort: 5432
部署 Redis
# redis-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
namespace: my-app
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "250m"
---
apiVersion: v1
kind: Service
metadata:
name: redis
namespace: my-app
spec:
selector:
app: redis
ports:
- port: 6379
targetPort: 6379
部署 Web 应用
# app-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: my-app
spec:
replicas: 3
selector:
matchLabels:
app: web-app
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web-app
spec:
containers:
- name: web-app
image: my-web-app:1.0
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
value: "postgres://$(DB_USER):$(DB_PASS)@postgres:5432/mydb"
- name: REDIS_URL
value: "redis://redis:6379"
- name: DB_USER
valueFrom:
secretKeyRef:
name: db-credentials
key: username
- name: DB_PASS
valueFrom:
secretKeyRef:
name: db-credentials
key: password
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: web-app
namespace: my-app
spec:
type: ClusterIP
selector:
app: web-app
ports:
- port: 80
targetPort: 3000
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-app-ingress
namespace: my-app
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-app
port:
number: 80
部署顺序
# 1. 创建 Secret
kubectl create secret generic db-credentials \
--from-literal=username=admin \
--from-literal=password=secure-password-here \
-n my-app
# 2. 创建 PVC
kubectl apply -f postgres-pvc.yaml
# 3. 部署数据库
kubectl apply -f postgres-deployment.yaml
# 4. 部署 Redis
kubectl apply -f redis-deployment.yaml
# 5. 部署 Web 应用
kubectl apply -f app-deployment.yaml
# 6. 确认状态
kubectl get all -n my-app
10. 故障排查
CrashLoopBackOff
Pod 反复启动又崩溃的状态。
# 确认原因
kubectl describe pod POD_NAME -n my-app
kubectl logs POD_NAME -n my-app --previous
# 常见原因:
# - 应用启动时发生错误
# - 环境变量配置错误
# - 无法连接到依赖的服务
# - livenessProbe 失败
解决方法:查看日志定位错误。调大 livenessProbe 的 initialDelaySeconds,或确认环境变量是否正确。
ImagePullBackOff
无法拉取容器镜像的状态。
# 确认原因
kubectl describe pod POD_NAME -n my-app
# 常见原因:
# - 镜像名或标签拼写错误
# - 私有镜像仓库未配置认证
# - 镜像不存在
# 配置私有镜像仓库认证
kubectl create secret docker-registry regcred \
--docker-server=registry.example.com \
--docker-username=user \
--docker-password=pass \
-n my-app
OOMKilled
容器超出内存限制被强制终止的状态。
# 确认原因
kubectl describe pod POD_NAME -n my-app
# 查看实时资源用量
kubectl top pod -n my-app
# 解决: 调大内存 limits 值
resources:
limits:
memory: "512Mi" # 从原来的 256Mi 提高
常用的调试命令
# 查看 Pod 列表与状态
kubectl get pods -n my-app -o wide
# Pod 详细信息(含事件)
kubectl describe pod POD_NAME -n my-app
# 实时查看日志
kubectl logs -f POD_NAME -n my-app
# 进入 Pod 内部
kubectl exec -it POD_NAME -n my-app -- /bin/sh
# 确认 Service 端点
kubectl get endpoints -n my-app
# 在集群内部确认 DNS
kubectl run debug --rm -it --image=busybox -- nslookup web-app.my-app.svc.cluster.local
# 节点资源现状
kubectl top nodes
结语
把本文讲过的内容整理如下。
- 容器基础:用命名空间与 cgroups 实现轻量隔离
- Docker:镜像构建、多阶段构建、安全实践
- Docker Compose:开发环境中的多容器管理
- K8s 架构:Control Plane 与 Worker Node 的职责
- K8s 对象:Pod、Deployment、Service、Ingress 的用法
- 配置管理:ConfigMap、Secret、PV/PVC
- Helm:用 chart 做发布管理
- 实战部署:Web 应用 + DB + Redis 的三层架构
- 故障排查:应对 CrashLoopBackOff、ImagePullBackOff、OOMKilled
Docker 与 Kubernetes 是现代基础设施的地基。希望你能在本地环境(minikube 或 kind)亲手把本文的示例跑一遍。真正去部署 Pod、配置 Service、动手排障,才是最快的学习方式。