DevOps与容器化实战指南:从Docker到Kubernetes
前言:DevOps 是文化,不是工具
DevOps 的核心是打破开发和运维的壁垒,通过自动化实现快速、可靠的交付。
DevOps 的核心实践:
- 容器化(Docker)
- 容器编排(Kubernetes)
- 持续集成/持续部署(CI/CD)
- 基础设施即代码(IaC)
- 监控与告警
一、Docker 基础
1.1 第一个容器
# 运行容器
docker run hello-world
docker run -it ubuntu bash # 交互式
docker run -d nginx # 后台
# 容器管理
docker ps # 查看运行中的容器
docker ps -a # 查看所有容器
docker stop <id>
docker rm <id>
# 镜像管理
docker images
docker rmi <image>
1.2 Dockerfile 最佳实践
# 多阶段构建(减小镜像体积)
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:18-alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
EXPOSE 3000
CMD ["node", "dist/index.js"]
Dockerfile 最佳实践:
- 用小基础镜像:
alpine或slim - 多阶段构建:减小最终镜像体积
- 合并 RUN 命令:减少层数
- 合理利用缓存:先 COPY 依赖文件,再 COPY 源码
- 不要放敏感信息
1.3 Docker Compose
version: '3.8'
services:
app:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgres://postgres:password@db:5432/myapp
depends_on:
- db
- redis
restart: unless-stopped
db:
image: postgres:13
environment:
- POSTGRES_PASSWORD=password
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:alpine
volumes:
- redis-data:/data
volumes:
db-data:
redis-data:
docker-compose up -d # 后台启动
docker-compose logs -f app # 查看日志
docker-compose down # 停止并删除
docker-compose exec app sh # 进入容器
1.4 Docker 网络
# 查看网络
docker network ls
# 创建网络
docker network create mynet
# 容器加入网络
docker run --network mynet --name app myimage
# Compose 默认创建网络
# 同一 compose 文件的服务可以通过服务名互相访问
1.5 Docker 的坑
坑一:镜像太大
# 错误:用完整镜像
FROM node:18 # ~900MB
# 正确:用 alpine
FROM node:18-alpine # ~150MB
坑二:忘了 .dockerignore
# .dockerignore
node_modules
npm-debug.log
.git
.env
dist
坑三:以 root 用户运行
FROM node:18-alpine
RUN addgroup -S app && adduser -S app -G app
USER app
坑四:数据存在容器里
容器删除后数据丢失。用 Volume 或 Bind Mount。
二、Kubernetes 基础
2.1 K8s 设计哲学
K8s 的核心是声明式 + 控制器模式:
- 声明式:你声明期望状态(YAML),K8s 负责达到这个状态
- 控制器:后台循环监控实际状态,与期望状态对齐
- 最终一致:apply 成功不代表立刻可用
与 Swarm 的区别:
- Swarm:发指令(命令式)
- K8s:交期望状态(声明式)
2.2 核心 Resource 模型
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec: # 用户写的期望状态
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.25
resources:
requests: { memory: "128Mi", cpu: "250m" }
limits: { memory: "256Mi", cpu: "500m" }
status: # 控制器写回的实际状态(用户不写)
replicas: 3
readyReplicas: 3
2.3 核心概念
| 概念 | 说明 |
|---|---|
| Pod | 最小调度单位,1+ 容器 |
| Deployment | 管理 Pod 副本,支持滚动更新 |
| Service | 网络抽象,负载均衡到 Pod |
| Ingress | HTTP 路由入口 |
| ConfigMap | 配置 |
| Secret | 敏感数据 |
| Volume | 存储 |
2.4 Deployment 示例
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 3
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v1
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
2.5 Service
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
selector:
app: myapp
ports:
- port: 80
targetPort: 3000
type: ClusterIP # 内部访问
# type: LoadBalancer # 云厂商负载均衡
# type: NodePort # 节点端口
2.6 Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts: [api.example.com]
secretName: myapp-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapp
port:
number: 80
2.7 常用 kubectl 命令
# 查看
kubectl get pods,svc,deploy
kubectl get pods -o wide
kubectl describe pod <name>
# 日志
kubectl logs <pod>
kubectl logs -f <pod> # 跟踪
kubectl logs <pod> -c <container> # 多容器
# 进入容器
kubectl exec -it <pod> -- sh
# 应用/删除
kubectl apply -f deploy.yaml
kubectl delete -f deploy.yaml
# 扩缩容
kubectl scale deployment myapp --replicas=5
# 滚动更新
kubectl set image deployment/myapp myapp=myapp:v2
kubectl rollout status deployment/myapp
kubectl rollout undo deployment/myapp # 回滚
# 标签和选择器
kubectl get pods -l app=myapp
kubectl label pod <name> env=prod
2.8 K8s 的坑
坑一:手工改集群被覆盖
kubectl scale deployment web --replicas=5
# 10 分钟后又变回 3,因为 Git 里 YAML 还写着 replicas: 3
解决: 生产变更只走 PR 改 YAML,禁止裸 kubectl scale。
坑二:label selector 对不上
改了 Pod template 的 label 忘了改 Deployment selector,新 ReplicaSet 创建 0 个 Pod。
坑三:把 status 当 spec 改
status 是控制器写回的,用户只管 spec。export 现网 YAML 时要剔除 status。
坑四:apply 成功 ≠ 服务可用
kubectl rollout status deployment/web --timeout=120s
apply 只说明 API Server 接受了 spec,Pod 调度、拉镜像、探针通过还要时间。
坑五:livenessProbe 太激进
initialDelaySeconds 设太小,应用没启动完就被判定不健康,导致 CrashLoopBackOff。
三、Helm:K8s 包管理
3.1 Helm Chart 结构
mychart/
├── Chart.yaml # 元数据
├── values.yaml # 默认配置
├── templates/
│ ├── deployment.yaml
│ ├── service.yaml
│ └── _helpers.tpl
└── charts/ # 依赖
3.2 Chart.yaml
apiVersion: v2
name: myapp
description: My application
type: application
version: 1.0.0
appVersion: "1.0"
3.3 values.yaml
replicaCount: 3
image:
repository: myapp
tag: "1.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
resources:
requests:
cpu: 250m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
ingress:
enabled: true
host: api.example.com
3.4 deployment.yaml 模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ include "myapp.name" . }}
template:
metadata:
labels:
app: {{ include "myapp.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
resources:
{{- toYaml .Values.resources | nindent 12 }}
3.5 Helm 命令
helm install myapp ./mychart # 安装
helm upgrade myapp ./mychart # 升级
helm uninstall myapp # 卸载
helm list # 查看已安装
helm template myapp ./mychart > out.yaml # 渲染模板
四、CI/CD
4.1 CI/CD 的演进
4.2 GitHub Actions
# .github/workflows/ci.yml
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
build-and-push:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
deploy:
needs: build-and-push
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: azure/setup-kubectl@v3
- run: |
echo "${{ secrets.KUBE_CONFIG }}" > kubeconfig
kubectl --kubeconfig kubeconfig rollout restart deployment/myapp
4.3 GitLab CI/CD
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
cache:
paths:
- node_modules/
test:
stage: test
image: node:20
script:
- npm ci
- npm run lint
- npm test
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build:
stage: build
image: docker:24
services: [docker:24-dind]
script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- docker build -t $DOCKER_IMAGE .
- docker push $DOCKER_IMAGE
only:
- main
deploy:staging:
stage: deploy
environment:
name: staging
url: https://staging.example.com
script:
- kubectl rollout restart deployment/myapp -n staging
only:
- main
deploy:production:
stage: deploy
environment:
name: production
url: https://example.com
script:
- kubectl rollout restart deployment/myapp -n production
when: manual # 手动确认
only:
- main
4.4 GitOps(Argo CD)
GitOps 的核心理念:Git 是唯一真理来源。
# Argo CD Application
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
spec:
source:
repoURL: https://github.com/myorg/k8s-manifests
targetRevision: HEAD
path: production
destination:
server: https://kubernetes.default.svc
namespace: production
syncPolicy:
automated:
prune: true
selfHeal: true
GitOps 优势:
- Git 历史就是部署历史
- 回滚就是 git revert
- 审计友好
- 禁止裸 kubectl 操作
五、基础设施即代码(IaC)
5.1 Terraform
# main.tf
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "WebServer"
}
}
resource "aws_db_instance" "database" {
allocated_storage = 20
engine = "postgres"
engine_version = "13"
instance_class = "db.t3.micro"
username = "postgres"
password = var.db_password
}
terraform init # 初始化
terraform plan # 查看变更
terraform apply # 应用
terraform destroy # 销毁
5.2 Ansible
# deploy.yml
- hosts: webservers
become: yes
tasks:
- name: Install nginx
apt:
name: nginx
state: present
update_cache: yes
- name: Start nginx
service:
name: nginx
state: started
enabled: yes
- name: Copy config
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
handlers:
- name: restart nginx
service:
name: nginx
state: restarted
ansible-playbook -i hosts deploy.yml
六、监控与告警
6.1 Prometheus + Grafana
# Prometheus 监控 Node.js 应用
const promClient = require('prom-client');
const collectDefaultMetrics = promClient.collectDefaultMetrics;
collectDefaultMetrics({ register: promClient.register });
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests',
labelNames: ['method', 'route', 'code'],
buckets: [0.1, 0.3, 0.5, 0.7, 1, 3, 5, 7, 10]
});
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.route?.path || req.path, res.statusCode)
.observe(duration);
});
next();
});
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
6.2 告警规则
# prometheus/rules/alerts.yml
groups:
- name: example
rules:
- alert: HighRequestLatency
expr: rate(http_request_duration_seconds_sum[5m]) / rate(http_request_duration_seconds_count[5m]) > 1
for: 10m
labels:
severity: warning
annotations:
summary: "High request latency"
description: "Average request latency is {{ $value }}s"
- alert: ServiceDown
expr: up == 0
for: 5m
labels:
severity: critical
annotations:
summary: "{{ $labels.instance }} is down"
6.3 日志聚合(ELK / Loki)
# Filebeat 采集日志
filebeat.inputs:
- type: log
paths:
- /var/log/myapp/*.log
fields:
app: myapp
env: production
output.elasticsearch:
hosts: ["elasticsearch:9200"]
# 或输出到 Loki
# output.loki:
# hosts: ["loki:3100"]
七、容器化的踩坑总结
坑一:Docker 镜像太大
解决: 多阶段构建 + alpine 基础镜像。
坑二:容器时区不对
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
坑三:K8s 中 Pod 频繁重启
检查 livenessProbe 是否太激进,initialDelaySeconds 设够大。
坑四:CI/CD 流水线太慢
- 用缓存(node_modules、Docker layer)
- 并行执行测试
- 只对 main 分支构建镜像
坑五:Secret 管理不当
# 错误:直接写 Secret
apiVersion: v1
kind: Secret
metadata:
name: db-secret
stringData:
password: mypassword123 # 泄露!
# 正确:用 Sealed Secrets 或 External Secrets
坑六:rolling update 卡住
健康检查没过,新 Pod 起不来,旧 Pod 已经被替换。
解决: 配 readinessProbe,确认就绪后再切流量。
八、DevOps 实践清单
容器化
- 应用无状态
- 配置外置(环境变量)
- 日志输出到 stdout
- 优雅关闭(SIGTERM 处理)
- 健康检查端点(/health, /ready)
K8s
- 资源 requests/limits
- livenessProbe + readinessProbe
- ConfigMap/Secret 管理配置
- PodDisruptionBudget
- HorizontalPodAutoscaler
CI/CD
- 代码提交触发流水线
- 自动化测试
- 镜像自动构建
- 多环境部署(dev/staging/prod)
- 回滚机制
监控
- 基础指标(CPU、内存、磁盘)
- 应用指标(QPS、延迟、错误率)
- 业务指标
- 告警规则
- 日志聚合
安全
- 镜像漏洞扫描
- 最小权限原则
- Secret 加密管理
- 网络策略
- RBAC
九、写在最后
DevOps 不是工具堆砌,是一种让交付快速、可靠、可重复的文化和方法论。
几条核心原则:
- 声明式 > 命令式:YAML 比 shell 脚本可审计
- Git 是唯一真理:所有变更走 PR
- 自动化一切:能脚本化的不要手工
- 不可变基础设施:容器、镜像、配置都版本化
- 监控先行:没有监控的系统是黑盒
- 失败要快:故障快速发现、快速恢复
技术会变(Docker → containerd,Jenkins → GitHub Actions),但核心思想稳定:让软件交付更高效、更可靠。
本文整合了 10 篇 DevOps 相关文章,涵盖 Docker 容器化、Kubernetes 编排、Helm、CI/CD(Jenkins/GitLab/GitHub Actions)、GitOps、IaC(Terraform/Ansible)、监控告警等核心技术。
版权声明: 本文首发于 指尖魔法屋-DevOps与容器化实战指南:从Docker到Kubernetes(https://blog.thinkmoon.cn/post/devops-containerization-comprehensive-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!
评论
使用 GitHub 账号登录后即可留言,支持 Markdown。