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"
- "[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 扩展阅读
- Docker Security Best Practices — Docker 安全指南
- OWASP Docker Security Cheat Sheet — Docker 安全清单
- Trivy Documentation — 容器安全扫描工具
- CrowdSec Documentation — CrowdSec 安全引擎
- Kubernetes Network Policies — K8s 网络策略
本章小结
| 主题 | 核心要点 |
|---|---|
| 容器化 WAF | 多阶段构建、最小镜像、非 root 用户 |
| Compose 编排 | WAF + App + Redis + Filebeat 完整栈 |
| CrowdSec | 开源安全引擎,社区驱动威胁情报 |
| 容器安全 | 镜像扫描 + 运行时限制 + 网络隔离 |
| K8s 部署 | Deployment + Service + ConfigMap + Secrets |
下一章:第15章 最佳实践 →