Triển khai Docker với kiến trúc Traefik và CrowdSec (Phần 2)

Bây giờ chúng ta tiếp tục bài viết mới trong loạt bài viết hướng dẫn triển khai Docker với kiến trúc Traefik và CrowdSec, bằng cách sử dụng Traefik làm Reverse Proxy và CrowdSec làm hệ thống phòng thủ chủ động.

Kiến trúc tổng thể

Mô hình nên đi theo luồng: Internet Traefik (80/443)web apps nội bộ trong Docker network; CrowdSec đọc log Traefik và có thể bật AppSec lắng nghe trên cổng 7422 trong mạng container để Traefik gửi yêu cầu kiểm tra trước khi vào app. Tài liệu CrowdSec cho Traefik nêu rõ nên dùng Traefik plugin hiện tại, không dùng bouncer cũ đã deprecated, và AppSec trong container phải bind 0.0.0.0:7422 để nhận kết nối từ container khác.

I. Gia cố hệ thống Ubuntu (Host)

II. Thiết lập môi trường Docker

Docker đơn giản hóa việc quản lý các tiến trình ứng dụng trong container — môi trường được cách ly tài nguyên, có tính di động và hiệu quả tài nguyên cao hơn so với máy ảo.

Hướng dẫn này bao gồm cài đặt Docker Community Edition (CE) trên Ubuntu, làm việc với container và image, đẩy image lên Docker Hub và quản lý vòng đời container. Nó cũng bao gồm cài đặt hỗ trợ GPU, khắc phục các lỗi thường gặp và các biện pháp bảo mật tốt nhất.

Bước 1 — Cài Docker Engine chuẩn chính thức

Docker khuyến nghị gỡ các gói xung đột như docker.io, docker-compose, podman-docker, containerd, runc trước khi cài từ repo chính thức. Sau đó thêm GPG key, thêm Docker apt source, rồi cài docker-ce, docker-ce-cli, containerd.io, docker-buildx-plugin, và docker-compose-plugin.

Chạy lần lượt:

sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc || true
sudo apt update
sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Bash

Kiểm tra dịch vụ:

sudo systemctl status docker
sudo docker run hello-world
docker compose version
Bash

Docker cho biết service thường tự khởi động sau cài đặt, và lệnh hello-world là bước xác minh chính thức.

Bước 2 — Thực thi lệnh Docker mà không cần Sudo

Theo mặc định,  docker lệnh này chỉ có thể được chạy bởi người dùng root hoặc bởi người dùng thuộc nhóm docker , nhóm này được tự động tạo trong quá trình cài đặt Docker. Nếu bạn cố gắng chạy lệnh docker mà không thêm tiền tố sudo hoặc không thuộc nhóm docker , bạn sẽ nhận được kết quả như sau:

docker: Cannot connect to the Docker daemon. Is the docker daemon running on this host?.
See 'docker run --help'.

Nếu bạn muốn tránh phải gõ lại sudo mỗi khi chạy docker lệnh, hãy thêm tên người dùng của bạn vào docker nhóm:

sudo usermod -aG docker $USER
newgrp docker
docker ps
Bash

Nếu SSH session cũ chưa nhận group mới, logout/login lại.

Bước 3 — Chuẩn hóa thư mục triển khai

Tôi khuyên dùng layout này để dễ backup và vận hành:

sudo mkdir -p /opt/docker/{traefik,crowdsec,apps,shared}
sudo mkdir -p /opt/docker/traefik/{dynamic,letsencrypt,logs}
sudo mkdir -p /opt/docker/crowdsec/{config,data}
sudo chmod 600 /opt/docker/traefik/letsencrypt || true
touch /opt/docker/traefik/letsencrypt/acme.json
sudo chmod 600 /opt/docker/traefik/letsencrypt/acme.json
Bash

Gợi ý cấu trúc:

  • /opt/docker/traefik: reverse proxy, cert, cấu hình động
  • /opt/docker/crowdsec: cấu hình CrowdSec, DB
  • /opt/docker/apps: từng app một thư mục riêng
  • /opt/docker/shared: file dùng chung nếu cần

Đây là sơ đồ bố trí file và thư mục của bộ triển khai production mà tôi đã chuẩn bị. Các thành phần được tách theo từng dịch vụ riêng: Traefik, CrowdSec, OpenLiteSpeed, MySQL, MongoDB, PHP app, script vận hành và tài liệu kiểm tra/khởi động.

/opt/docker/
├── traefik/
   ├── docker-compose.yml
   ├── dynamic/
      ├── dashboard.yml
      ├── crowdsec.yml
      └── middlewares.yml
   ├── letsencrypt/
      └── acme.json
   └── logs/
       └── access.log

├── crowdsec/
   ├── docker-compose.yml
   ├── appsec.yaml
   ├── config/
   └── data/

├── apps/
   ├── openlitespeed/
      ├── docker-compose.yml
      ├── conf/
      ├── logs/
      └── sites/
   
   ├── php-app/
      ├── docker-compose.yml
      └── www/
          └── index.php
   
   ├── mysql/
      ├── docker-compose.yml
      ├── conf.d/
         └── custom.cnf
      └── data/
   
   ├── mongodb/
      ├── docker-compose.yml
      └── data/
   
   └── uptime-kuma/
       ├── docker-compose.yml
       └── data/

└── shared/
    ├── .env
    ├── scripts/
       ├── up.sh
       ├── down.sh
       └── restore.sh
    ├── backups/
    └── docs/
        ├── CHECKLIST.md
        └── README-ORDER.md
Bash

Cách bố trí này giữ apps/ làm nơi tập trung toàn bộ application stack, trong khi traefik/ và crowdsec/ đứng riêng vì chúng là lớp hạ tầng dùng chung cho nhiều app. CrowdSec cũng khuyến nghị persist cấu hình và database ra volume hoặc bind mount riêng để tránh mất credentials/dữ liệu phân tích, nên việc tách hẳn thư mục như trên là hợp lý.

  • traefik/ là lớp edge, không nên trộn với app. Traefik file provider cũng hoạt động tốt khi bind mount cả một thư mục dynamic thay vì từng file lẻ.
  • crowdsec/ là lớp security dùng chung, nên để độc lập khỏi từng app để tránh coupling.
  • apps/ gom mọi web app và database app-level.
  • shared/ là nơi hợp lý để chứa .env, script, backup, tài liệu, thay vì để lẫn trong app cụ thể.

Thứ tự khởi động sẽ là:

  1. traefik
  2. crowdsec
  3. apps/mysql
  4. apps/mongodb
  5. apps/openlitespeed
  6. apps/php-app
  7. apps/uptime-kuma

Cách này đảm bảo edge proxy và security layer sẵn sàng trước, sau đó mới đến database và application layer.

Bước 4 — Tạo Docker networks

Tách network giúp giảm lộ bề mặt tấn công:

docker network create proxy
docker network create backend
Bash
  • proxy: Traefik + các app cần public
  • backend: app + database + dịch vụ nội bộ
  • Nhiều app chỉ cần vào backend, không cần vào proxy

III. Triển khai các dịch vụ (Tách biệt bằng Docker Compose)

Bước 1 — Dựng Traefik:

Traefik thường bật Docker provider, access log, entrypoints web/websecure, ACME storage, và plugin crowdsec-bouncer.

Tạo file /opt/docker/traefik/docker-compose.yml:

services:
  traefik:
    image: traefik:v3.6.13
    container_name: traefik
    restart: unless-stopped
    command:
      - --global.sendanonymoususage=false
      - --api.dashboard=true
      - --api.insecure=false

      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true

      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      - --entrypoints.web.http.redirections.entryPoint.to=websecure
      - --entrypoints.web.http.redirections.entryPoint.scheme=https

      - --certificatesresolvers.le.acme.email=admin@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

      - --log.level=INFO
      - --accesslog=true
      - --accesslog.filepath=/var/log/traefik/access.log

      - --experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin
      - --experimental.plugins.bouncer.version=v1.3.5

    ports:
      - "80:80"
      - "443:443"

    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./dynamic:/etc/traefik/dynamic:ro
      - ./letsencrypt:/letsencrypt
      - ./logs:/var/log/traefik

    networks:
      - proxy
      - backend

networks:
  proxy:
    external: true
  backend:
    external: true
YAML

Lưu ý:

  • exposedbydefault=false là rất quan trọng, chỉ container nào gắn label mới được public.
  • Trên miền dùng email cần phải đổi từ admin@example.com sang tên miền khác example.com để không bị báo lỗi khi khởi chạy letsencrypt.
  • Access log cần bật để CrowdSec phân tích log Traefik.
  • Plugin bouncer là plugin Traefik được CrowdSec quickstart yêu cầu dùng thay cho bouncer cũ.

Bước 2: Cấu hình dashboard Traefik an toàn

Không nên public dashboard thô. Tạo password hash:

sudo apt install -y apache2-utils
htpasswd -nb admin 'MatKhauRatManh'
Bash

Tạo file /opt/docker/traefik/dynamic/dashboard.yml:

http:
  routers:
    traefik-dashboard:
      rule: Host(`traefik.example.com`)
      entryPoints:
        - websecure
      service: api@internal
      tls:
        certResolver: le
      middlewares:
        - dashboard-auth

  middlewares:
    dashboard-auth:
      basicAuth:
        users:
          - "admin:$apr1$xxxxxxxx$yyyyyyyyyyyyyyyyyyyyy"
YAML

Khởi động:

cd /opt/docker/traefik
docker compose up -d
docker logs -f traefik
Bash

Kiểm tra kết quả:

Bước 3: dựng CrowdSec để đọc log Traefik

CrowdSec blog có ví dụ chạy container CrowdSec với collections cho Traefik và HTTP CVE, mount log Traefik, mount DB, và expose API nội bộ.

Tạo /opt/docker/crowdsec/appsec.yaml:

appsec_configs:
  - crowdsecurity/appsec-default
labels:
  type: appsec
listen_addr: 0.0.0.0:7422
source: appsec
YAML

Thiết lập này đúng với quickstart CrowdSec cho môi trường Docker Compose; tài liệu nhấn mạnh phải dùng 0.0.0.0:7422 chứ không phải 127.0.0.1.

Tạo /opt/docker/crowdsec/docker-compose.yml:

services:
  crowdsec:
    image: crowdsecurity/crowdsec:latest
    container_name: crowdsec
    restart: unless-stopped
    environment:
      COLLECTIONS: "crowdsecurity/traefik crowdsecurity/http-cve crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules"
      GID: "${GID-1000}"
    volumes:
      - ./config:/etc/crowdsec
      - ./data:/var/lib/crowdsec/data
      - /opt/docker/traefik/logs:/var/log/traefik:ro
      - ./appsec.yaml:/etc/crowdsec/acquis.d/appsec.yaml:ro
      - /var/log/auth.log:/var/log/auth.log:ro
    expose:
      - "8080"
      - "7422"
    networks:
      - backend

networks:
  backend:
    external: true
YAML

Khởi động:

cd /opt/docker/crowdsec
docker compose up -d
docker logs -f crowdsec
Bash

Bước 4: Tạo API key để Traefik hỏi CrowdSec

Bạn cần một bouncer/API key để Traefik plugin giao tiếp với LAPI của CrowdSec. Quickstart CrowdSec dùng crowdsecLapiKey trong middleware/plugin config.

Tạo key:

docker exec -it crowdsec cscli bouncers add traefik-bouncer
Bash

Lệnh này sẽ trả về API key, ví dụ:

Api key for 'traefik-bouncer':
xxxxxxxxxxxxxxxxxxxxxxxx
TeX

Lưu key này cẩn thận.

Kiểm tra collections:

docker exec -it crowdsec cscli collections list
Bash

Kết quả:

Kiểm tra metrics AppSec sau này:

docker exec -it crowdsec cscli metrics show appsec
Bash

Quickstart CrowdSec xác nhận cscli metrics show appsec là lệnh kiểm tra AppSec hoạt động và số request bị block.

Kết quả:

Bước 5: Gắn middleware CrowdSec vào Traefik

Bạn có hai kiểu:

  1. Gắn middleware riêng cho từng app.
  2. Tạo middleware dùng chung trong file dynamic.

Tôi khuyên dùng file dynamic dùng chung. Tạo /opt/docker/traefik/dynamic/crowdsec.yml:

http:
  middlewares:
    crowdsec:
      plugin:
        bouncer:
          enabled: true
          crowdsecLapiKey: "THAY_API_KEY_O_DAY"
          crowdsecLapiHost: "crowdsec:8080"
          crowdsecLapiScheme: "http"
          crowdsecMode: "live"

          crowdsecAppsecEnabled: true
          crowdsecAppsecHost: "crowdsec:7422"
          crowdsecAppsecFailureBlock: true
          crowdsecAppsecUnreachableBlock: true
YAML

Các directive crowdsecAppsecEnabledcrowdsecAppsecHostcrowdsecAppsecFailureBlock, và crowdsecAppsecUnreachableBlock đều được CrowdSec quickstart mô tả rõ cho Traefik plugin.

Khởi động lại Traefik:

cd /opt/docker/traefik
docker compose restart traefik
Bash

Bước 6: Triển khai ứng dụng web thực tế bên trong Docker

Mỗi ứng dụng web nên có:

  • Một thư mục riêng trong thư mục apps.
  • Một compose riêng.
  • Nếu cần DB thì DB chỉ nằm ở backend.
  • Chỉ app public mới vào proxy.
  • Không publish cổng DB/MySQL/Postgres/Redis ra host.

Ví dụ app Node.js:

Tạo /opt/docker/apps/nodejs/docker-compose.yml

services:
  app:
    image: ghcr.io/your-org/your-app:latest
    restart: unless-stopped
    environment:
      NODE_ENV: production
      APP_PORT: 3000
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.app.rule=Host(`app.example.com`)
      - traefik.http.routers.app.entrypoints=websecure
      - traefik.http.routers.app.tls=true
      - traefik.http.routers.app.tls.certresolver=le
      - traefik.http.routers.app.middlewares=crowdsec@file
      - traefik.http.services.app.loadbalancer.server.port=3000
    networks:
      - proxy
      - backend

  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_DB: app
      POSTGRES_USER: app
      POSTGRES_PASSWORD: doi-mat-khau-manh
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - backend

volumes:
  postgres_data:

networks:
  proxy:
    external: true
  backend:
    external: true
YAML

Khởi động:

cd /opt/docker/apps/nodejs
docker compose up -d
YAML

Ví dụ WordPress sau Traefik:

CrowdSec blog có ví dụ WordPress với Traefik labels, TLS certresolver, và middleware CrowdSec riêng cho router.

Mẫu rút gọn:

Tạo /opt/docker/apps/wordpress/docker-compose.yml

services:
  wordpress:
    image: wordpress:latest
    restart: unless-stopped
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: doi-mat-khau-manh
    volumes:
      - wordpress_data:/var/www/html
    labels:
      - traefik.enable=true
      - traefik.docker.network=proxy
      - traefik.http.routers.wordpress.rule=Host(`blog.example.com`)
      - traefik.http.routers.wordpress.entrypoints=websecure
      - traefik.http.routers.wordpress.tls=true
      - traefik.http.routers.wordpress.tls.certresolver=le
      - traefik.http.routers.wordpress.middlewares=crowdsec@file
      - traefik.http.services.wordpress.loadbalancer.server.port=80
    networks:
      - proxy
      - backend

  db:
    image: mariadb:11
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: doi-mat-khau-manh
      MYSQL_ROOT_PASSWORD: doi-root-password-rat-manh
    volumes:
      - mariadb_data:/var/lib/mysql
    networks:
      - backend

volumes:
  wordpress_data:
  mariadb_data:

networks:
  proxy:
    external: true
  backend:
    external: true
YAML

Khởi động:

cd /opt/docker/apps/wordpress
docker compose up -d
YAML

IV. Kiểm tra và Vận hành

Bước 1 – Tăng cường bảo mật host

Docker Docs cảnh báo rằng khi publish cổng container, các cổng đó có thể bypass một phần cấu hình firewall như UFW, vì vậy bạn nên hạn chế publish port, chỉ cho Traefik dùng 80/443, và áp quy tắc lọc bổ sung.

Tối thiểu nên làm:

  • Tắt đăng nhập root qua SSH.
  • Dùng SSH key thay mật khẩu.
  • Đổi port SSH nếu muốn giảm bot scan.
  • Cài fail2ban cho SSH nếu chưa dùng CrowdSec cho auth log.
  • Chỉ publish 80/443; tránh 8080:80803306:33065432:5432 ra ngoài.

Bước 2 – Kiểm tra CrowdSec có chặn thật không

CrowdSec quickstart đề xuất test bằng request tới /.env, vì rule AppSec mặc định có thể chặn truy cập kiểu này và trả về block page/403.

Test:

curl -I https://whoami.example.com/.env
Bash

Nếu cấu hình đúng, bạn có thể thấy HTTP 403 hoặc nội dung block từ middleware/AppSec.

Sau đó kiểm tra:

docker exec -it crowdsec cscli metrics show appsec
docker exec -it crowdsec cscli alerts list
docker exec -it crowdsec cscli decisions list
Bash

Metrics AppSec sẽ hiển thị số request processed, blocked và rule đã trigger.

Bước 3: Vận hành hằng ngày

Bạn nên áp dụng quy trình vận hành sau:

  • Cập nhật image định kỳ: docker compose pull && docker compose up -d
  • Backup các volume quan trọng: DB, WordPress data, acme.json/opt/docker/crowdsec/data
  • Theo dõi log:
docker logs traefik
docker logs crowdsec
docker logs <app>
Bash
  • Kiểm tra cert Let’s Encrypt còn gia hạn bình thường
  • Kiểm tra CrowdSec collections và decisions định kỳ

Ví dụ cập nhật một stack:

cd /opt/docker/apps/nodejs
docker compose pull
docker compose up -d
docker image prune -f
Bash

V. Những lỗi hay gặp

  • Không trỏ DNS đúng về server nên Let’s Encrypt fail. Traefik ACME HTTP challenge cần domain resolve về máy chủ và cổng 80 truy cập được từ ngoài.
  • App không vào network proxy, nên Traefik không route được.
  • Quên traefik.docker.network=proxy, khiến Traefik chọn nhầm network.
  • CrowdSec không thấy log vì chưa mount thư mục access log Traefik. CrowdSec blog mount ./logsTraefik:/var/log/traefik chính là vì lý do này.
  • AppSec không hoạt động vì listen_addr để 127.0.0.1 thay vì 0.0.0.0:7422. CrowdSec quickstart nói rõ phải dùng 0.0.0.0 trong container.
  • Dùng bouncer cũ traefik-crowdsec-bouncer thay vì plugin Traefik mới.

Bằng cách sử dụng Traefik, bạn có một hệ thống tự động hóa cực cao. Kết hợp với CrowdSec, máy chủ của bạn không chỉ đứng vững trước các cuộc tấn công Brute-force mà còn miễn nhiễm với các bot quét lỗi tự động trên internet.

X