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

CDN 与 WAF 精讲教程 / 第14章 Docker 部署

第14章 Docker 部署

本章讲解如何使用 Docker 容器化部署 WAF 和反向代理,涵盖容器化 WAF 架构、Docker Compose 编排、以及容器环境的安全加固。


14.1 容器化 WAF 架构

14.1.1 容器部署 vs 传统部署

维度传统部署容器化部署
部署方式编译安装Docker 镜像
环境一致性依赖系统环境容器内自包含
扩缩容手动Docker/K8s 自动
更新方式手动编译拉取新镜像
隔离性进程级容器级(namespace/cgroup)
资源占用系统级可精确限制 CPU/内存

14.1.2 容器化 WAF 部署架构

Docker 容器化 WAF 架构:

┌──────────────────────────────────────────────────────────────────┐
│  Docker Host / Docker Swarm / Kubernetes                        │
│                                                                  │
│  ┌───────────────────────┐    ┌───────────────────────┐         │
│  │  Nginx + ModSecurity   │    │  HAProxy              │         │
│  │  容器                  │    │  容器                  │         │
│  │  端口: 443             │    │  端口: 443             │         │
│  └───────────┬───────────┘    └───────────┬───────────┘         │
│              │                            │                     │
│              └──────────┬─────────────────┘                     │
│                         │                                       │
│              ┌──────────▼──────────┐                            │
│              │   Docker Network     │                            │
│              │   (内部网络)          │                            │
│              └──────────┬──────────┘                            │
│                         │                                       │
│  ┌──────────────────────▼──────────────────────────────────────┐│
│  │  后端应用容器                                                ││
│  │  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐   ││
│  │  │ App-1    │  │ App-2    │  │ App-3    │  │ Redis    │   ││
│  │  │ :8080    │  │ :8080    │  │ :8080    │  │ :6379    │   ││
│  │  └──────────┘  └──────────┘  └──────────┘  └──────────┘   ││
│  └─────────────────────────────────────────────────────────────┘│
└──────────────────────────────────────────────────────────────────┘

14.2 Nginx + ModSecurity Docker 部署

14.2.1 Dockerfile

# Dockerfile - Nginx + ModSecurity WAF

FROM ubuntu:22.04 AS builder

ENV DEBIAN_FRONTEND=noninteractive

# 安装编译依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    ca-certificates \
    git \
    libcurl4-openssl-dev \
    libgeoip-dev \
    liblmdb-dev \
    libpcre2-dev \
    libssl-dev \
    libtool \
    libxml2-dev \
    libyajl-dev \
    pkgconf \
    wget \
    zlib1g-dev \
    && rm -rf /var/lib/apt/lists/*

# 编译 ModSecurity v3
RUN git clone --depth 1 -b v3/master --recurse-submodules \
    https://github.com/owasp-modsecurity/ModSecurity.git /opt/ModSecurity && \
    cd /opt/ModSecurity && \
    git submodule init && git submodule update && \
    ./build.sh && \
    ./configure --with-pcre2 && \
    make -j$(nproc) && make install

# 编译 ModSecurity-nginx 连接器
RUN git clone --depth 1 \
    https://github.com/owasp-modsecurity/ModSecurity-nginx.git /opt/ModSecurity-nginx

# 编译 Nginx
ENV NGINX_VERSION=1.26.2
RUN wget https://nginx.org/download/nginx-${NGINX_VERSION}.tar.gz && \
    tar xzf nginx-${NGINX_VERSION}.tar.gz && \
    cd nginx-${NGINX_VERSION} && \
    ./configure \
        --prefix=/etc/nginx \
        --sbin-path=/usr/sbin/nginx \
        --modules-path=/usr/lib64/nginx/modules \
        --conf-path=/etc/nginx/nginx.conf \
        --error-log-path=/var/log/nginx/error.log \
        --http-log-path=/var/log/nginx/access.log \
        --with-http_ssl_module \
        --with-http_v2_module \
        --with-http_realip_module \
        --with-http_gzip_static_module \
        --with-stream \
        --add-dynamic-module=/opt/ModSecurity-nginx && \
    make -j$(nproc) && make install

# ===== 最终镜像 =====
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y \
    ca-certificates \
    libcurl4 \
    libgeoip1 \
    liblmdb0 \
    libxml2 \
    libyajl2 \
    && rm -rf /var/lib/apt/lists/*

# 复制编译产物
COPY --from=builder /usr/sbin/nginx /usr/sbin/nginx
COPY --from=builder /etc/nginx /etc/nginx
COPY --from=builder /usr/lib64/nginx/modules /usr/lib64/nginx/modules
COPY --from=builder /usr/local/modsecurity /usr/local/modsecurity

# 复制配置文件
COPY nginx.conf /etc/nginx/nginx.conf
COPY modsecurity.conf /etc/nginx/modsecurity/modsecurity.conf
COPY crs/ /etc/nginx/modsecurity/crs/

# 创建必要目录
RUN mkdir -p /var/log/nginx /var/log/modsecurity /tmp/modsecurity

EXPOSE 80 443

STOPSIGNAL SIGQUIT

CMD ["nginx", "-g", "daemon off;"]

14.2.2 Docker Compose 编排

# docker-compose.yml

version: '3.8'

services:
  waf:
    build:
      context: ./waf
      dockerfile: Dockerfile
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./waf/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./waf/modsecurity.conf:/etc/nginx/modsecurity/modsecurity.conf:ro
      - ./waf/crs:/etc/nginx/modsecurity/crs:ro
      - ./waf/certs:/etc/ssl/certs:ro
      - ./waf/keys:/etc/ssl/private:ro
      - waf_logs:/var/log/nginx
      - modsec_logs:/var/log/modsecurity
    networks:
      - frontend
      - backend
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
        reservations:
          cpus: '0.5'
          memory: 512M
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 10s
      timeout: 5s
      retries: 3

  app1:
    image: myapp:latest
    environment:
      - NODE_ENV=production
    networks:
      - backend
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 1G

  app2:
    image: myapp:latest
    environment:
      - NODE_ENV=production
    networks:
      - backend
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    networks:
      - backend
    restart: unless-stopped
    volumes:
      - redis_data:/data
    command: redis-server --requirepass ${REDIS_PASSWORD}

  # 日志收集
  filebeat:
    image: docker.elastic.co/beats/filebeat:8.12.0
    user: root
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml:ro
      - waf_logs:/var/log/nginx:ro
      - modsec_logs:/var/log/modsecurity:ro
    networks:
      - frontend
    restart: unless-stopped

volumes:
  waf_logs:
  modsec_logs:
  redis_data:

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # 内部网络,不对外暴露

14.3 Traefik + CrowdSec 容器化方案

14.3.1 CrowdSec 介绍

CrowdSec 架构:

┌──────────────────────────────────────────────────────────┐
│  CrowdSec (开源安全引擎)                                  │
│  ├── 日志解析器 (Parsers)                                │
│  │   ├── Nginx 日志                                      │
│  │   ├── Traefik 日志                                    │
│  │   └── 系统日志                                        │
│  ├── 场景引擎 (Scenarios)                                │
│  │   ├── 暴力破解检测                                    │
│  │   ├── 爬虫检测                                        │
│  │   ├── DDoS 检测                                       │
│  │   └── 漏洞扫描检测                                    │
│  ├── 决策引擎 (Decisions)                                │
│  │   ├── ban (封禁)                                      │
│  │   ├── captcha (验证)                                  │
│  │   └── throttle (限速)                                 │
│  └── 执行器 (Bouncers)                                   │
│      ├── Nginx Bouncer                                   │
│      ├── Cloudflare Bouncer                              │
│      ├── Firewall Bouncer                                │
│      └── Traefik Bouncer (Plugin)                        │
└──────────────────────────────────────────────────────────┘

14.3.2 Docker Compose 部署

# docker-compose.yml - Traefik + CrowdSec

version: '3.8'

services:
  traefik:
    image: traefik:v3.0
    command:
      - "--api.dashboard=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
      - "--experimental.plugins.crowdsec.modulename=github.com/maxlerebourg/crowdsec-traefik-plugin"
      - "--experimental.plugins.crowdsec.version=v1.3.5"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt
    networks:
      - proxy
    restart: unless-stopped

  crowdsec:
    image: crowdsecurity/crowdsec:latest
    environment:
      - COLLECTIONS=crowdsecurity/traefik crowdsecurity/base-http-scenarios
      - GID=1000
    volumes:
      - crowdsec_config:/etc/crowdsec
      - crowdsec_data:/var/lib/crowdsec/data
      - /var/log/traefik:/var/log/traefik:ro
    networks:
      - proxy
    restart: unless-stopped

  app:
    image: myapp:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=letsencrypt"
      - "traefik.http.routers.app.middlewares=crowdsec@docker"
      - "traefik.http.middlewares.crowdsec.plugin.crowdsec.crowdseclapikey=YOUR_API_KEY"
    networks:
      - proxy
    restart: unless-stopped

volumes:
  letsencrypt:
  crowdsec_config:
  crowdsec_data:

networks:
  proxy:
    driver: bridge

14.4 容器安全加固

14.4.1 容器安全清单

容器安全加固清单:

  镜像安全:
  ├── ✅ 使用最小基础镜像 (alpine / distroless)
  ├── ✅ 不在镜像中硬编码密钥/密码
  ├── ✅ 定期扫描镜像漏洞 (Trivy / Snyk)
  ├── ✅ 固定镜像版本标签(不用 latest)
  └── ✅ 多阶段构建减小镜像体积

  运行时安全:
  ├── ✅ 不以 root 用户运行
  ├── ✅ 只读文件系统 (--read-only)
  ├── ✅ 限制 CPU/内存资源
  ├── ✅ 移除不必要的 Linux Capabilities
  ├── ✅ 禁用特权模式 (--privileged)
  ├── ✅ 使用 seccomp 限制系统调用
  └── ✅ 网络隔离(内部网络不暴露)

  网络安全:
  ├── ✅ 内部网络使用 internal: true
  ├── ✅ 仅暴露必要端口
  ├── ✅ 容器间通信使用服务名
  └── ✅ 配置网络策略 (K8s NetworkPolicy)

14.4.2 安全 Dockerfile 最佳实践

# 安全 Dockerfile 最佳实践

FROM ubuntu:22.04 AS builder
# ... 编译阶段 ...

FROM nginx:1.26-alpine AS production

# 1. 安全更新
RUN apk update && apk upgrade --no-cache

# 2. 创建非 root 用户
RUN addgroup -g 1001 -S waf && \
    adduser -u 1001 -S waf -G waf

# 3. 设置文件权限
COPY --chown=waf:waf nginx.conf /etc/nginx/nginx.conf
COPY --chown=waf:waf modsecurity.conf /etc/nginx/modsecurity/
COPY --chown=waf:waf crs/ /etc/nginx/modsecurity/crs/

# 4. 创建必要目录并设置权限
RUN mkdir -p /var/cache/nginx /var/log/nginx /tmp/modsecurity && \
    chown -R waf:waf /var/cache/nginx /var/log/nginx /tmp/modsecurity && \
    chmod -R 755 /var/cache/nginx /var/log/nginx

# 5. 删除默认配置
RUN rm -f /etc/nginx/conf.d/default.conf

# 6. 使用非 root 用户
USER waf

EXPOSE 80 443

# 7. 健康检查
HEALTHCHECK --interval=10s --timeout=5s --retries=3 \
    CMD wget -qO- http://localhost/health || exit 1

CMD ["nginx", "-g", "daemon off;"]

14.4.3 Docker 安全扫描

# 使用 Trivy 扫描镜像漏洞
docker build -t waf:latest ./waf/
trivy image waf:latest --severity HIGH,CRITICAL

# 扫描结果示例
# waf:latest (ubuntu 22.04)
# ========================
# Total: 3 (HIGH: 2, CRITICAL: 1)
#
# ┌──────────────┬───────────────┬──────────┬───────────────────────┐
# │   Library    │ Vulnerability │ Severity │    Installed Version  │
# ├──────────────┼───────────────┼──────────┼───────────────────────┤
# │ libssl3      │ CVE-2024-XXX  │ CRITICAL │ 3.0.2-0ubuntu1.15     │
# │ libcurl4     │ CVE-2024-XXX  │ HIGH     │ 7.81.0-1ubuntu1.16    │
# └──────────────┴───────────────┴──────────┴───────────────────────┘

14.5 Kubernetes 部署

14.5.1 K8s WAF Deployment

# k8s/waf-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: waf
  namespace: security
spec:
  replicas: 3
  selector:
    matchLabels:
      app: waf
  template:
    metadata:
      labels:
        app: waf
    spec:
      # 安全上下文
      securityContext:
        runAsUser: 1001
        runAsGroup: 1001
        fsGroup: 1001

      containers:
        - name: waf
          image: waf:1.0.0
          ports:
            - containerPort: 443
            - containerPort: 80

          # 资源限制
          resources:
            requests:
              cpu: 500m
              memory: 512Mi
            limits:
              cpu: 2000m
              memory: 2Gi

          # 安全上下文
          securityContext:
            allowPrivilegeEscalation: false
            readOnlyRootFilesystem: true
            capabilities:
              drop:
                - ALL

          # 存储卷
          volumeMounts:
            - name: waf-config
              mountPath: /etc/nginx/modsecurity
              readOnly: true
            - name: tls-certs
              mountPath: /etc/ssl/certs/waf
              readOnly: true
            - name: tmp
              mountPath: /tmp/modsecurity
            - name: nginx-cache
              mountPath: /var/cache/nginx

          # 健康检查
          livenessProbe:
            httpGet:
              path: /health
              port: 80
            initialDelaySeconds: 10
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /health
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5

      volumes:
        - name: waf-config
          configMap:
            name: waf-config
        - name: tls-certs
          secret:
            secretName: waf-tls
        - name: tmp
          emptyDir: {}
        - name: nginx-cache
          emptyDir:
            sizeLimit: 1Gi

---
apiVersion: v1
kind: Service
metadata:
  name: waf-service
  namespace: security
spec:
  selector:
    app: waf
  ports:
    - name: https
      port: 443
      targetPort: 443
    - name: http
      port: 80
      targetPort: 80
  type: ClusterIP

14.6 注意事项

⚠️ 资源限制:WAF 容器的 CPU/内存限制需要根据实际流量调整。ModSecurity 规则匹配是 CPU 密集型,需预留充足 CPU。

⚠️ 日志持久化:容器重启会丢失日志。务必使用 volume 挂载日志目录,或使用日志收集 Agent。

⚠️ 配置更新:修改 WAF 规则后需要重载 Nginx(nginx -s reload),在容器中需使用 docker exec 或信号机制。

⚠️ 镜像更新:定期更新基础镜像以修补安全漏洞,建议配置 CI/CD 自动构建和扫描。


14.7 扩展阅读


本章小结

主题核心要点
容器化 WAF多阶段构建、最小镜像、非 root 用户
Compose 编排WAF + App + Redis + Filebeat 完整栈
CrowdSec开源安全引擎,社区驱动威胁情报
容器安全镜像扫描 + 运行时限制 + 网络隔离
K8s 部署Deployment + Service + ConfigMap + Secrets

下一章:第15章 最佳实践 →