强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Varnish Cache 运维教程 / 第12章:Docker 容器化部署

第12章:Docker 容器化部署

12.1 Docker 部署概述

Docker 容器化部署 Varnish 可以实现环境一致性、快速部署和易于扩展。

12.1.1 容器化优势

优势说明
环境一致性开发、测试、生产环境完全一致
快速部署秒级启动,无需手动配置
易于扩展容器编排实现水平扩展
版本管理镜像标签实现版本管理
资源隔离CPU/内存资源限制

12.2 基本 Dockerfile

12.2.1 标准 Dockerfile

# Dockerfile
FROM ubuntu:22.04

# 安装 Varnish
RUN apt-get update && \
    apt-get install -y \
        curl \
        gnupg \
        apt-transport-https && \
    curl -fsSL https://packagecloud.io/varnishcache/varnish70/gpgkey | \
        gpg --dearmor -o /usr/share/keyrings/varnish-archive-keyring.gpg && \
    echo "deb [signed-by=/usr/share/keyrings/varnish-archive-keyring.gpg] \
        https://packagecloud.io/varnishcache/varnish70/ubuntu/ jammy main" | \
        tee /etc/apt/sources.list.d/varnish.list && \
    apt-get update && \
    apt-get install -y varnish && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*

# 复制配置文件
COPY default.vcl /etc/varnish/default.vcl
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

# 默认参数
ENV VARNISH_SIZE=256m
ENV VARNISH_PORT=6081
ENV VARNISH_ADMIN_PORT=6082
ENV BACKEND_HOST=backend
ENV BACKEND_PORT=80

# 暴露端口
EXPOSE ${VARNISH_PORT}
EXPOSE ${VARNISH_ADMIN_PORT}

# 健康检查
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:${VARNISH_PORT}/ || exit 1

# 启动命令
ENTRYPOINT ["/entrypoint.sh"]

12.2.2 启动脚本

#!/bin/bash
# entrypoint.sh

# 替换环境变量
envsubst < /etc/varnish/default.vcl > /tmp/default.vcl
mv /tmp/default.vcl /etc/varnish/default.vcl

# 验证 VCL 配置
varnishd -C -f /etc/varnish/default.vcl || {
    echo "VCL configuration error"
    exit 1
}

# 启动 Varnish
exec varnishd \
    -F \
    -a :${VARNISH_PORT} \
    -T localhost:${VARNISH_ADMIN_PORT} \
    -f /etc/varnish/default.vcl \
    -s malloc,${VARNISH_SIZE} \
    -p thread_pool_min=5 \
    -p thread_pool_max=500 \
    -p thread_pool_timeout=120

12.2.3 VCL 配置文件

# default.vcl
vcl 4.1;

backend default {
    .host = "${BACKEND_HOST}";
    .port = "${BACKEND_PORT}";
    .connect_timeout = 5s;
    .first_byte_timeout = 30s;
    .between_bytes_timeout = 10s;
    .probe = {
        .url = "/health";
        .timeout = 3s;
        .interval = 5s;
        .window = 5;
        .threshold = 3;
    }
}

sub vcl_recv {
    if (req.method != "GET" && req.method != "HEAD") {
        return (pass);
    }

    if (req.url ~ "^/admin") {
        return (pass);
    }

    return (hash);
}

sub vcl_backend_response {
    if (beresp.http.Cache-Control ~ "no-cache|no-store|private") {
        set beresp.uncacheable = true;
        set beresp.ttl = 0s;
        return (deliver);
    }

    if (bereq.url ~ "\.(css|js|jpg|png|gif|webp)$") {
        set beresp.ttl = 1h;
    } else {
        set beresp.ttl = 5m;
    }

    set beresp.grace = 1h;
}

sub vcl_deliver {
    if (obj.hits > 0) {
        set resp.http.X-Cache = "HIT (" + obj.hits + ")";
    } else {
        set resp.http.X-Cache = "MISS";
    }
}

12.3 Docker Compose 部署

12.3.1 基本 Compose 配置

# docker-compose.yml
version: '3.8'

services:
  varnish:
    build: .
    image: varnish:7.5
    container_name: varnish
    ports:
      - "6081:6081"
      - "6082:6082"
    environment:
      - VARNISH_SIZE=256m
      - BACKEND_HOST=backend
      - BACKEND_PORT=80
    volumes:
      - ./default.vcl:/etc/varnish/default.vcl:ro
    depends_on:
      backend:
        condition: service_healthy
    networks:
      - web
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M

  backend:
    image: nginx:alpine
    container_name: backend
    volumes:
      - ./html:/usr/share/nginx/html:ro
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    networks:
      - web
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 10s
      timeout: 3s
      retries: 3

networks:
  web:
    driver: bridge

12.3.2 生产环境 Compose

# docker-compose.prod.yml
version: '3.8'

services:
  varnish:
    build:
      context: .
      dockerfile: Dockerfile
    image: myapp-varnish:latest
    ports:
      - "80:6081"
    environment:
      - VARNISH_SIZE=2G
      - BACKEND_HOST=backend
      - BACKEND_PORT=80
    volumes:
      - ./config/default.vcl:/etc/varnish/default.vcl:ro
      - ./config/secret:/etc/varnish/secret:ro
    networks:
      - web
      - backend-net
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      mode: replicated
      replicas: 2
      resources:
        limits:
          cpus: '4'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 512M
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
      restart_policy:
        condition: any
        delay: 5s
        max_attempts: 3
        window: 120s

  hitch:
    image: hitch:latest
    ports:
      - "443:443"
    volumes:
      - ./certs:/etc/hitch/certs:ro
      - ./config/hitch.conf:/etc/hitch/hitch.conf:ro
    networks:
      - web
    depends_on:
      - varnish

  backend:
    image: myapp-backend:latest
    networks:
      - backend-net
    deploy:
      mode: replicated
      replicas: 3
      resources:
        limits:
          cpus: '2'
          memory: 2G

networks:
  web:
    driver: overlay
  backend-net:
    driver: overlay
    internal: true

12.4 配置管理

12.4.1 使用配置文件挂载

# docker-compose.yml
services:
  varnish:
    volumes:
      # 挂载 VCL 配置
      - ./config/default.vcl:/etc/varnish/default.vcl:ro
      # 挂载 secret 文件
      - ./config/secret:/etc/varnish/secret:ro
      # 挂载自定义 VMOD
      - ./vmods:/usr/lib/varnish/vmods:ro

12.4.2 使用环境变量

# 使用环境变量的 VCL
vcl 4.1;

backend default {
    .host = "${BACKEND_HOST}";
    .port = "${BACKEND_PORT}";
}

sub vcl_recv {
    # 使用环境变量控制行为
    if (req.http.Host ~ "${ALLOWED_DOMAINS}") {
        return (hash);
    }
    return (synth(403, "Forbidden"));
}
# 启动时传入环境变量
docker run -e BACKEND_HOST=10.0.0.1 \
           -e BACKEND_PORT=8080 \
           -e ALLOWED_DOMAINS="example\.com" \
           varnish:latest

12.4.3 使用 Docker Secrets

# docker-compose.yml
version: '3.8'

services:
  varnish:
    image: varnish:latest
    secrets:
      - varnish_secret
      - backend_password
    environment:
      - VARNISH_SECRET_FILE=/run/secrets/varnish_secret

secrets:
  varnish_secret:
    file: ./secrets/varnish_secret
  backend_password:
    file: ./secrets/backend_password

12.5 健康检查

12.5.1 Dockerfile 健康检查

# 简单健康检查
HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD curl -f http://localhost:6081/ || exit 1

# 详细健康检查
HEALTHCHECK --interval=10s --timeout=5s --retries=3 --start-period=30s \
    CMD varnishadm status || exit 1

12.5.2 Compose 健康检查

services:
  varnish:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:6081/health"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 30s

12.5.3 自定义健康检查端点

# VCL 中添加健康检查端点
sub vcl_recv {
    if (req.url == "/health") {
        # 检查后端健康状态
        if (std.healthy(req.backend_hint)) {
            return (synth(200, "OK"));
        } else {
            return (synth(503, "Backend Unhealthy"));
        }
    }

    if (req.url == "/health/detailed") {
        # 返回详细状态
        return (synth(200, "Detailed OK"));
    }
}

sub vcl_synth {
    if (resp.status == 200 && resp.reason == "OK") {
        set resp.http.Content-Type = "application/json";
        synthetic({"{"status":"healthy","backend":""} + req.backend_hint + {"","cache_hit":""} + obj.hits + {"","}"});
        return (deliver);
    }
}

12.5.4 Docker 健康检查脚本

#!/bin/bash
# healthcheck.sh

# 检查 Varnish 进程
if ! pgrep -x "varnishd" > /dev/null; then
    echo "Varnish process not running"
    exit 1
fi

# 检查端口响应
if ! curl -sf http://localhost:6081/ > /dev/null 2>&1; then
    echo "HTTP check failed"
    exit 1
fi

# 检查管理端口
if ! varnishadm status > /dev/null 2>&1; then
    echo "Admin check failed"
    exit 1
fi

# 检查缓存命中率
HIT=$(varnishstat -1 -f MAIN.cache_hit | awk '{print $2}')
MISS=$(varnishstat -1 -f MAIN.cache_miss | awk '{print $2}')
TOTAL=$((HIT + MISS))

if [ $TOTAL -gt 100 ]; then
    HITRATE=$((HIT * 100 / TOTAL))
    if [ $HITRATE -lt 50 ]; then
        echo "Low cache hit rate: ${HITRATE}%"
        exit 1
    fi
fi

echo "All checks passed"
exit 0

12.6 Nginx 集成

12.6.1 Nginx 作为 TLS 终结代理

# nginx.conf
upstream varnish {
    server varnish:6081;
    keepalive 32;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/certs/fullchain.pem;
    ssl_certificate_key /etc/nginx/certs/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://varnish;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # 启用 keepalive
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        # 超时设置
        proxy_connect_timeout 5s;
        proxy_send_timeout 30s;
        proxy_read_timeout 30s;
    }
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

12.6.2 Docker Compose 完整配置

# docker-compose.nginx.yml
version: '3.8'

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    depends_on:
      - varnish
    networks:
      - web

  varnish:
    build: .
    volumes:
      - ./default.vcl:/etc/varnish/default.vcl:ro
    environment:
      - VARNISH_SIZE=256m
      - BACKEND_HOST=backend
      - BACKEND_PORT=80
    depends_on:
      - backend
    networks:
      - web
      - backend-net

  backend:
    image: myapp-backend:latest
    networks:
      - backend-net

networks:
  web:
    driver: bridge
  backend-net:
    driver: bridge
    internal: true

12.6.3 VCL 处理代理头部

# 处理来自 Nginx 的代理头
sub vcl_recv {
    # 获取真实客户端 IP
    if (req.http.X-Forwarded-For) {
        set req.http.X-Forwarded-For = req.http.X-Forwarded-For + ", " + client.ip;
    } else {
        set req.http.X-Forwarded-For = client.ip;
    }

    # 获取原始协议
    if (req.http.X-Forwarded-Proto) {
        set req.http.X-Proto = req.http.X-Forwarded-Proto;
    } else {
        set req.http.X-Proto = "http";
    }

    # 重定向 HTTP 到 HTTPS
    if (req.http.X-Proto != "https") {
        return (synth(750, "https://" + req.http.Host + req.url));
    }
}

12.7 容器编排

12.7.1 Kubernetes 部署

# k8s-varnish.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: varnish
spec:
  replicas: 3
  selector:
    matchLabels:
      app: varnish
  template:
    metadata:
      labels:
        app: varnish
    spec:
      containers:
      - name: varnish
        image: varnish:7.5
        ports:
        - containerPort: 6081
          name: http
        - containerPort: 6082
          name: admin
        env:
        - name: VARNISH_SIZE
          value: "512m"
        - name: BACKEND_HOST
          valueFrom:
            configMapKeyRef:
              name: varnish-config
              key: backend-host
        volumeMounts:
        - name: vcl-config
          mountPath: /etc/varnish/default.vcl
          subPath: default.vcl
        resources:
          requests:
            cpu: "500m"
            memory: "1Gi"
          limits:
            cpu: "2"
            memory: "2Gi"
        livenessProbe:
          httpGet:
            path: /health
            port: 6081
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health
            port: 6081
          initialDelaySeconds: 10
          periodSeconds: 5
      volumes:
      - name: vcl-config
        configMap:
          name: varnish-vcl
---
apiVersion: v1
kind: Service
metadata:
  name: varnish
spec:
  selector:
    app: varnish
  ports:
  - name: http
    port: 80
    targetPort: 6081
  type: ClusterIP
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: varnish-vcl
data:
  default.vcl: |
    vcl 4.1;
    backend default {
        .host = "backend";
        .port = "80";
    }
    sub vcl_recv {
        return (hash);
    }

12.7.2 ConfigMap 管理

# varnish-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: varnish-config
data:
  backend-host: "backend-service"
  backend-port: "80"
  varnish-size: "512m"
  default.vcl: |
    vcl 4.1;
    backend default {
        .host = "BACKEND_HOST_PLACEHOLDER";
        .port = "BACKEND_PORT_PLACEHOLDER";
    }

12.8 监控与日志

12.8.1 Docker 日志配置

services:
  varnish:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        tag: "{{.Name}}/{{.ID}}"

12.8.2 指标导出

# Prometheus 指标导出
services:
  varnish-exporter:
    image: prometheus/varnish-exporter:latest
    command:
      - "--varnish.addr=localhost:6082"
      - "--web.listen-address=:9131"
    ports:
      - "9131:9131"
    network_mode: "service:varnish"

12.9 注意事项

重要

  1. Docker 容器中 Varnish 的内存限制要与 -s malloc 大小匹配
  2. 使用 init 进程或 dumb-init 处理信号,确保 Varnish 正确关闭
  3. 生产环境使用多副本部署,配合负载均衡器
  4. 配置文件使用只读挂载(:ro),避免意外修改
  5. 日志输出到 stdout/stderr,便于 Docker 日志收集
  6. 定期更新基础镜像,修复安全漏洞
  7. 健康检查要覆盖关键功能,不仅仅是进程存在

12.10 扩展阅读