容器化与 DevOps

深入理解 Docker 容器化、Kubernetes 基础、前端应用的容器化部署,以及 GitOps 工作流。


Docker 基础

核心概念

┌──────────────────────────────────────────────────────────────┐
│                      Docker 核心概念                           │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   Image (镜像) - 应用的只读模板                               │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  FROM node:20-alpine                                │   │
│   │  WORKDIR /app                                        │   │
│   │  COPY package*.json ./                               │   │
│   │  RUN npm ci                                          │   │
│   │  COPY . .                                            │   │
│   │  CMD ["npm", "start"]                                │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
│   Container (容器) - 镜像的运行实例                           │
│   ┌────────────┐                                            │
│   │  Container  │ ← Image 的运行实例                        │
│   │             │ ← 可读写层                                 │
│   │  应用进程   │ ← 隔离的文件系统                           │
│   │             │ ← 独立的网络                                │
│   └────────────┘                                            │
│                                                              │
│   Registry (镜像仓库) - 存储和分发镜像                         │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  Docker Hub / Harbor / ECR / GCR                    │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Dockerfile 最佳实践

# 多阶段构建
# 阶段 1:构建
FROM node:20-alpine AS builder
WORKDIR /app

# 复制依赖文件(利用 Docker 缓存)
COPY package*.json ./
RUN npm ci --only=production && \
    npm cache clean --force

# 复制源码
COPY . .

# 构建
RUN npm run build

# 阶段 2:运行
FROM node:20-alpine AS production

# 创建非 root 用户
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

WORKDIR /app

# 只复制构建产物
COPY --from=builder --chown=nextjs:nodejs /app/dist ./dist
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./package.json

# 切换用户
USER nextjs

# 健康检查
HEALTHCHECK --interval=30s --timeout=3s \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/health || exit 1

EXPOSE 3000
ENV NODE_ENV=production

CMD ["node", "dist/server.js"]

Docker Compose

# docker-compose.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    image: myapp:latest
    container_name: myapp
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgres://user:pass@db:5432/app
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 10s
    networks:
      - app-network

  db:
    image: postgres:15-alpine
    container_name: postgres
    environment:
      POSTGRES_USER: user
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: app
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U user -d app"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - app-network

  cache:
    image: redis:7-alpine
    container_name: redis
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    restart: unless-stopped
    networks:
      - app-network

  nginx:
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    restart: unless-stopped
    networks:
      - app-network

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

Kubernetes 基础

核心概念

┌──────────────────────────────────────────────────────────────┐
│                    Kubernetes 核心概念                         │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   Cluster (集群)                                             │
│   ┌─────────────────────────────────────────────────────┐   │
│   │  ┌─────────┐  ┌─────────┐  ┌─────────┐              │   │
│   │  │ Control │  │ Worker  │  │ Worker  │              │   │
│   │  │ Plane   │  │ Node 1  │  │ Node 2  │              │   │
│   │  └─────────┘  └────┬────┘  └────┬────┘              │   │
│   │                   │            │                     │   │
│   │                   └─────┬──────┘                     │   │
│   │                         ▼                             │   │
│   │              ┌─────────────────┐                      │   │
│   │              │     Pods        │                      │   │
│   │              │  ┌───┐ ┌───┐   │                      │   │
│   │              │  │App│ │ DB │   │                      │   │
│   │              │  └───┘ └───┘   │                      │   │
│   │              └─────────────────┘                      │   │
│   └─────────────────────────────────────────────────────┘   │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Pod、Deployment、Service

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  labels:
    app: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
        - name: myapp
          image: myapp:latest
          ports:
            - containerPort: 3000
          env:
            - name: NODE_ENV
              value: production
            - name: DATABASE_URL
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: database-url
          resources:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "512Mi"
              cpu: "500m"
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /ready
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
spec:
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: ClusterIP
---
apiVersion: v1
kind: Service
metadata:
  name: myapp-lb
spec:
  selector:
    app: myapp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 3000
  type: LoadBalancer

Ingress

# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
    nginx.ingress.kubernetes.io/rate-limit: "100"
spec:
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-service
                port:
                  number: 80

Helm Charts

Helm 模板

# values.yaml
replicaCount: 3

image:
  repository: myapp
  tag: latest
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix

resources:
  limits:
    cpu: 500m
    memory: 512Mi
  requests:
    cpu: 250m
    memory: 256Mi

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70

config:
  database_url: ""
  redis_url: ""
# 部署
helm install myapp ./charts/myapp \
  --namespace production \
  --values ./charts/myapp/values.prod.yaml

# 升级
helm upgrade myapp ./charts/myapp \
  --namespace production \
  --values ./charts/myapp/values.prod.yaml

# 回滚
helm rollback myapp 1 --namespace production

GitOps 工作流

ArgoCD 配置

# argocd-application.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
  name: myapp
  namespace: argocd
spec:
  project: default
  source:
    repoURL: https://github.com/myorg/myapp
    targetRevision: main
    path: k8s/overlays/production
    kustomize:
      images:
        - myapp=myorg/myapp:v1.2.3
  destination:
    server: https://kubernetes.default.svc
    namespace: production
  syncPolicy:
    automated:
      prune: true
      selfHeal: true
    syncOptions:
      - CreateNamespace=true
    retry:
      limit: 5
      backoff:
        duration: 5s
        factor: 2
        maxDuration: 3m

GitOps 流程

┌──────────────────────────────────────────────────────────────┐
│                      GitOps 流程                             │
├──────────────────────────────────────────────────────────────┤
│                                                              │
│   开发者 ──→ Git ──→ CI/CD ──→ Kubernetes                   │
│      │                    │                    │            │
│      │                    ▼                    ▼            │
│      │              ┌───────────┐         ┌───────────┐    │
│      │              │  镜像构建  │         │  自动部署  │    │
│      │              └───────────┘         └───────────┘    │
│      │                                         │            │
│      └─────────────────────────────────────────┘            │
│                    Git 为唯一真相来源                          │
│                                                              │
└──────────────────────────────────────────────────────────────┘

Kustomize

# k8s/base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  template:
    spec:
      containers:
        - name: myapp
          image: myapp:latest
          ports:
            - containerPort: 3000
# k8s/overlays/staging/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - ../../base
namespace: staging
replicas:
  - name: myapp
    count: 2
commonLabels:
  variant: staging
patches:
  - path: replicas.yaml
  - path: env.yaml
# k8s/overlays/production/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases:
  - ../../base
namespace: production
replicas:
  - name: myapp
    count: 5
commonLabels:
  variant: production
patches:
  - path: replicas.yaml
  - path: env.yaml

环境管理

多环境配置

# config.yaml
environments:
  development:
    apiUrl: http://localhost:8080
    logLevel: debug
    features:
      enableDebugTools: true

  staging:
    apiUrl: https://staging-api.example.com
    logLevel: info
    features:
      enableDebugTools: false

  production:
    apiUrl: https://api.example.com
    logLevel: warn
    features:
      enableDebugTools: false

ConfigMap 和 Secret

# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
data:
  app.yaml: |
    server:
      port: 3000
    logging:
      level: info
    features:
      enableAnalytics: true
---
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
type: Opaque
stringData:
  database-url: "postgres://user:pass@db:5432/app"
  api-key: "secret-api-key"

监控与日志

Prometheus + Grafana

# prometheus-monitoring.yaml
apiVersion: v1
kind: Service
metadata:
  name: prometheus
spec:
  ports:
    - port: 9090
      targetPort: 9090
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: myapp
spec:
  selector:
    matchLabels:
      app: myapp
  endpoints:
    - port: metrics
      path: /metrics
      interval: 15s

日志收集

# fluentd-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: fluentd-config
data:
  fluent.conf: |
    <source>
      @type tail
      @id input-tail
      path /var/log/containers/*.log
      pos_file /var/log/fluentd-containers.log.pos
      tag kubernetes.*
      <parse>
        @type json
        time_format %Y-%m-%dT%H:%M:%S.%NZ
      </parse>
    </source>

    <filter kubernetes.**>
      @type kubernetes_metadata
      @id filter-kube_metadata
    </filter>

    <match kubernetes.**>
      @type elasticsearch
      @id output-elasticsearch
      host elasticsearch.logging
      port 9200
      logstash_format true
      logstash_prefix kubernetes
    </match>

这一章想说的

容器化和 DevOps 让前端应用可以可靠地部署和运维:

  1. Docker:容器化应用,实现一致的运行环境
  2. Docker Compose:本地多容器编排
  3. Kubernetes:生产级容器编排平台
  4. Helm:Kubernetes 包管理
  5. GitOps:以 Git 为核心的声明式部署
  6. 监控与日志:完整的可观测性

掌握这些技术,前端工程师可以更好地参与运维工作。


延展阅读