Skip to content

Ubuntu 22 + Docker Swarm 环境下,用 Traefik 搭建一个反向代理,实现以下功能:

  1. 自动发现 Swarm 集群里的服务(通过 labels)
  2. 对外提供 HTTPS(使用 Cloudflare SSL/TLS)
  3. 支持跨主机 Swarm 服务

1️⃣ 准备工作

  1. Docker & Swarm
bash
sudo apt update
sudo apt install -y docker.io docker-compose
sudo systemctl enable docker --now

# 初始化 Swarm  <MANAGER_IP> 为当前的主Manager节点的IP地址。后续的其它服务器上的docker都会加入其中。
docker swarm init --advertise-addr <MANAGER_IP>

# 上面会返回其它服务器加入当前manager节点的信息。记下它。
# 如果忘记了,可以用下面的命令再次获取:
sudo docker swarm join-token worker
## or
sudo docker swarm join-token manager
  1. Cloudflare 设置

    • 确保你的域名已接入 Cloudflare
    • 获取 API TokenGlobal API Key,并允许管理 DNS
    • 你的域名在 Cloudflare 上 DNS 已指向 Swarm 节点的公网 IP(A/AAAA 记录)
    • 去个人信息 -> 配置文件 -> API 令牌 创建对应域名的 区域:读取, DNS:编辑

2️⃣ 创建 Traefik 配置目录

bash
mkdir -p /opt/traefik
cd /opt/traefik

# 静态配置
mkdir -p traefik
touch traefik/acme.json
touch traefik/traefik-stack-manager.yml

3️⃣ acme.json 静态配置 (acme.json)

yaml
{}

说明:

  • acme.json 用于存储证书,需创建并 chmod 600:
bash
touch traefik/acme.json
chmod 600 traefik/acme.json

4️⃣ Traefik Docker Swarm 服务 (traefik-stack-manager.yml)

注意事项:

  • 有的docker可能版本过低,无法把服务中的environment变量传递到容器中,所以一旦发现变量没有传递,请先查看docker的版本是否为受限功能的版本。(比如群晖的Docker)
yaml
# manager service deploy: sudo docker stack deploy -c traefik-stack-manager.yml proxy  --detach=false
version: "3.8"

# 将在Cloudflare上生成的apitoken存于docker的secrect中。
secrets:
  cf_dns_api_token:
    external: true

services:
  traefik:
    image: traefik:v3.2
    networks:
      - traefik-public
    ports:
      - "80:80"
      - "443:443"
    environment:
      - CF_DNS_API_TOKEN_FILE=/run/secrets/cf_dns_api_token
    secrets: # 引用上面创建的 secret
      - cf_dns_api_token
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      # HTTPS - 使用绝对路径,避免 stack deploy 找不到当前目录
      - /opt/traefik/acme.json:/acme.json
    command:
      # 1. API 与入口点
      - --api.dashboard=true
      - --api.insecure=false

      # 入口点端口保持默认 80/443,这样容器内部不用改配置
      - --entrypoints.web.address=:80
      - --entrypoints.websecure.address=:443

      # 2. Docker  Provider(禁用以防止多次发现)
      - --providers.docker=false

      # 3. Swarm Provider(关键!替代原来的 swarmMode)
      - --providers.swarm=true
      - --providers.swarm.exposedbydefault=false
      - --providers.swarm.network=traefik-public
      - --providers.swarm.endpoint=unix:///var/run/docker.sock

      # 4. 证书(Cloudflare DNS-01 示例)
      - --certificatesresolvers.letsencrypt.acme.dnschallenge=true # 开启 DNS-01
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.provider=cloudflare
      - --certificatesresolvers.letsencrypt.acme.dnschallenge.delayBeforeCheck=30 # 给 Cloudflare 留传播时间
      - --certificatesresolvers.letsencrypt.acme.email=youremail@aicro.net
      - --certificatesresolvers.letsencrypt.acme.storage=/acme.json

      # 5. 日志
      - --log.level=INFO
      - --accesslog=true # 建议开启访问日志以便排错

    deploy:
      replicas: 1
      restart_policy:
        condition: any
        delay: 10s
        max_attempts: 3
      update_config:
        parallelism: 1
        delay: 10s
      rollback_config:
        parallelism: 1
        delay: 10s
      placement:
        constraints:
          - node.role == manager # 主节点
      labels:
        - traefik.enable=true
        - traefik.http.routers.traefik.rule=Host(`dashboard.aicro.net`)
        - traefik.http.routers.traefik.entrypoints=websecure
        - traefik.http.routers.traefik.tls.certresolver=letsencrypt
        - traefik.http.routers.traefik.service=api@internal
        - "traefik.http.services.traefik.loadbalancer.server.port=8080"

        # --- 证书域名 : 为这个路由器申请一个通配符证书,覆盖主域名和所有子域名 ---
        - "traefik.http.routers.traefik.tls.domains[0].main=aicro.net"
        - "traefik.http.routers.traefik.tls.domains[0].sans=*.aicro.net"

        # 可选:HTTP→HTTPS 强制跳转
        - traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)
        - traefik.http.routers.http-catchall.entrypoints=web
        - traefik.http.routers.http-catchall.middlewares=https-redirect
        - traefik.http.middlewares.https-redirect.redirectscheme.scheme=https
        - traefik.http.middlewares.https-redirect.redirectscheme.permanent=true
        - traefik.http.middlewares.https-redirect.redirectscheme.port=443

networks:
  traefik-public:
    external: true
  • traefik-public 是 overlay 网络,用于 Traefik 与服务通信:
bash
# 创建服务网络
sudo docker network create \
  --driver=overlay \
  --attachable \
  traefik-public

5️⃣ 示例 Web 服务 (web-app-stack.yml)

下面是两个跨节点的服务:

yaml
# service deploy: sudo docker stack deploy -c web-app-stack.yml webapp  --detach=false
version: "3.8"

services:
  whoami:
    image: traefik/whoami:latest
    networks:
      - traefik-public
    deploy:
      replicas: 1
      restart_policy:
        condition: any
      labels:
        - "traefik.enable=true"
        # 域名
        - "traefik.http.routers.whoami.rule=Host(`whoami.aicro.net`)"
        - "traefik.http.routers.whoami.entrypoints=websecure"
        - "traefik.http.routers.whoami.tls=true"
        - "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
        - "traefik.http.services.whoami.loadbalancer.server.port=80"

  # 另一个示例:Nginx静态网站
  nginx-demo:
    image: nginx:alpine
    networks:
      - traefik-public
    volumes:
      - ./html:/usr/share/nginx/html:ro #html里为静态网页内容
    deploy:
      replicas: 2
      restart_policy:
        condition: any
      labels:
        - "traefik.enable=true"
        # 域名
        - "traefik.http.routers.nginx-demo.rule=Host(`www.aicro.net`)"
        - "traefik.http.routers.nginx-demo.entrypoints=websecure"
        - "traefik.http.routers.nginx-demo.tls=true"
        - "traefik.http.routers.nginx-demo.tls.certresolver=letsencrypt"
        - "traefik.http.services.nginx-demo.loadbalancer.server.port=80"

networks:
  traefik-public:
    external: true

这样,无论这两个服务运行在哪个 Swarm 节点,只要挂载在 traefik-public 网络并加上 label,Traefik 就会自动发现并生成 HTTPS。


6️⃣ 部署

bash
# 部署 Traefik manager 节点 主节点上执行
sudo docker stack deploy -c traefik-stack-manager.yml proxy  --detach=false

# 部署 两个示例 服务 主节点上执行
sudo docker stack deploy -c web-app-stack.yml webapp  --detach=false

访问 https://www.aicro.net 就会自动通过 Traefik + Cloudflare 证书提供 HTTPS。


✅ Docker 拉取失败时

大陆用户专享

shell
# 修改配置,让其不要从默认的地方拉取。

sudo tee /etc/docker/daemon.json <<EOF
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me",
    "https://docker.m.daocloud.io",
    "https://docker-0.unsee.tech",
    "https://docker.mirrors.ustc.edu.cn",
    "https://docker.1panel.live",
    "https://registry.docker-cn.com",
    "https://hub-mirror.c.163.com"
  ]
}
EOF

# 重启生效
sudo systemctl restart docker

✅ 其它相关命令

shell
#查询服务
sudo docker service ls

#查询服务失败的最后几条信息,用于排错
sudo docker service ps proxy_traefik
# 查看详细报错
sudo docker service ps proxy_traefik --no-trunc

#查询失败的日志
sudo docker service logs proxy_traefik --tail 50 -f

# 拉取镜像
sudo docker pull traefik:v3.2

# 改完重启 Docker:
sudo systemctl restart docker

# 再强制更新部署:
sudo docker service update --force proxy_traefik

# 1 临时停机(缩容 → 0)
sudo docker service scale proxy_traefik=0

# 随时 scale 回去就能瞬间拉起:
sudo docker service scale proxy_traefik=1


# 重新部署(每次更新docker-compose.yml后必须执行)
sudo  docker stack deploy -c docker-compose.yml proxy
## stack:管理 Docker Swarm 栈(stack) 的子命令。
### 一个 stack 可以包含多个 service、network、config、secret 等资源,适合多节点集群部署。
## deploy: stack 子命令中的 “部署” 动作。
### 第一次执行会创建所有声明的资源;后续执行相当于 滚动更新(rolling update)。
## proxy: 栈的名字。 部署后所有资源都会加上该前缀,例如 service 名 → proxy_traefik

# 1. 确认当前节点是 manager 且 Swarm 已启用
docker node ls

# 暂停一台主机
sudo docker node update --availability drain node-03

# 2. 查看是否已存在同名 secret
sudo docker secret ls | grep cf_dns_api_token

# 3. 如果不存在,重新创建(- 表示从标准输入读取)
printf 'YOUR_CF_DNS_API_TOKEN' | docker secret create cf_dns_api_token -

# 找到 Traefik 容器的 ID 或名称:
sudo docker ps | grep traefik

# 查看该容器的环境变量:
sudo docker inspect 811b1097483f | grep CF


# 测试域名访问(确保DNS已指向你的服务器)
curl -H "Host: whoami.aicro.net" http://192.168.1.2
curl -H "Host: whoami.aicro.net" https://192.168.1.2

✅ 关键点总结:

  1. overlay 网络:确保 Traefik 与 Swarm 服务在同一网络
  2. labels:标记需要被 Traefik 暴露的服务
  3. Cloudflare API Token:用于 DNS-01 自动生成证书
  4. acme.json:保存证书,权限 600