强曰为道

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

第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"
      - "[email protected]"
      - "--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章 最佳实践 →