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 最佳实践:

  1. 用小基础镜像alpineslim
  2. 多阶段构建:减小最终镜像体积
  3. 合并 RUN 命令:减少层数
  4. 合理利用缓存:先 COPY 依赖文件,再 COPY 源码
  5. 不要放敏感信息

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
IngressHTTP 路由入口
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 的演进

graph LR A[手工部署] --> B[Jenkins] B --> C[GitLab CI] C --> D[GitHub Actions] D --> E[GitOps]

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 不是工具堆砌,是一种让交付快速、可靠、可重复的文化和方法论。

几条核心原则:

  1. 声明式 > 命令式:YAML 比 shell 脚本可审计
  2. Git 是唯一真理:所有变更走 PR
  3. 自动化一切:能脚本化的不要手工
  4. 不可变基础设施:容器、镜像、配置都版本化
  5. 监控先行:没有监控的系统是黑盒
  6. 失败要快:故障快速发现、快速恢复

技术会变(Docker → containerd,Jenkins → GitHub Actions),但核心思想稳定:让软件交付更高效、更可靠


本文整合了 10 篇 DevOps 相关文章,涵盖 Docker 容器化、Kubernetes 编排、Helm、CI/CD(Jenkins/GitLab/GitHub Actions)、GitOps、IaC(Terraform/Ansible)、监控告警等核心技术。

版权声明: 本文首发于 指尖魔法屋-DevOps与容器化实战指南:从Docker到Kuberneteshttps://blog.thinkmoon.cn/post/devops-containerization-comprehensive-guide/) 转载或引用必须申明原指尖魔法屋来源及源地址!