发布于

更好的反向代理工具 Traefik 配置入门——Docker 篇

作者
  • 姓名
    Twitter

前言

本人 Ghost 博客以及自建私有云 Nextcloud 均已经稳定运行了一年有余,期间经历过两次服务器迁移和多次版本升级,均未发生过数据丢失和其他问题,在迁移过程中也曾分享过如何将 Docker Volumes 安全的跨服务器迁移。目前博客和网盘服务分别搭建于阿里云国际新加坡和阿里云香港轻量,并统一使用另一台服务器作反代。

在反代软件上,一直使用的都是 Nginx,最近在研究 K8s,也对比较流行的云原生反向代理工具 Traefik 产生了兴趣,于是便花了一点时间将反代服务器上的 Nginx 换成了 Traefik。一定要吐槽的一点是,Traefik 的官方文档写得实在太糟糕了,初次接触的用户,想通过只看官方文档把事情搞定,那是相当困难,所以我觉得非常有必要分享一下,让其他初次使用的用户避免踩坑。

目前 Traefik 已经更新到了 2.1 版本,其 2.x 版本和 1.x 版本差异较大,本文自然将基于最新版本,新版本增加的自动配置 HTTPS 功能和 TCP 代理功能都十分有价值。

主要目标

反向代理服务器的工作,自然就是截获服务器流量,并将流量转发至目标服务或地址,在这一基础上,如果转发的目标服务或地址是多个,就是负载均衡。Traefik 官方文档中的图片都是萌萌的,下面这张图就比较清晰的说明了 Traefik 或者说一般反代软件的功能:

Traefik 功能

本人目前的需求比较简单,就是将来自于pbeta.mewww.pbeta.me的访问流量转发至 博客IP:博客端口,将来自于cloud.pbeta.me的访问流量转发至 网盘IP:端口,另外,考虑到演示需要,本文将完成以下目标:

  • 域名访问 Traefik Dashboard
  • 将域名访问转发至 外部IP:端口
  • 将域名访问转发至 Docker 服务
  • 配置 HTTPS 访问(使用 LetsEncrypt)
  • 使用 Middlewares 实现 HTTP 访问自动跳转 HTTPS
  • 使用 Middlewares 为 Traefik Dashboard 增加密码验证

Traefik 配置说明

在安装之前,我觉得有必要讲一下 Traefik 这款工具的配置方式,搞清楚了基本的配置方式,在下面安装和配置的过程中才能有清晰的思路,以下内容主要参考了官方文档中的Configuration Introduction 页面

配置类型

配置类型

简单的说,Traefik 的配置包括静态配置和动态配置两种,前者是 Traefik 自身的配置,需要重启才能生效,后者则可以理解为被代理服务的配置,可以通过配置实现为即时生效。

如果配置内容不存在服务差异性,那就可以统一在静态配置中,否则就需要在动态配置中为每一个服务中单独添加,动态配置中的多个服务也可以共用同一配置内容,比如可以配置一个basicAuthMiddlewares,由多个服务共用。

配置内容位置

无论静态配置还是动态配置,都有两种配置方式:CLI 形式或者独立文件形式。

对于静态配置,一个是 Traefik 镜像启动时的启动参数,另一个是单独的配置文件(如 traefik.yml),官方文档提供示例时,也同时包括这两种形式,分别对应的是 CLIFile(YAML),不过在查看官方的配置示例时,你会发现还包括 File(TOML),TOML 文件是对 YAML 的一种改进,本文暂时都使用 YAML,读者可以自行转换。另外,官方文档中也说明了静态配置可以通过环境变量实现,本文暂不使用此方式。

配置项的优先级为配置文件 > 命令行参数 > 环境变量,请注意配置文件与命令行参数是互斥的,如果你选择使用配置文件,就不能再使用命令行参数,最终的配置并非二者的叠加

而对于动态配置,可以选择直接在服务的 docker-compose 文件最下方通过 labels 实现,比如这样:

# yaml
  whoami:
    # A container that exposes an API to show its IP address
    image: containous/whoami
    labels:
      - "traefik.http.routers.whoami.rule=Host(`whoami.docker.localhost`)"

也可以写在单独的配置文件中,这一文件在 Traefik 中一般命名为 dynamic.yml,你可以将所有路由配置写在此文件中,为了清晰起见,本文统一使用后一种方式。

配置加载方式

通过上面的说明,我们可以知道,在 Docker 中使用 Traefik 大致可能会涉及到三个文件:

  • docker-compose.yml
  • traefik.yml(可选)
  • dynamic.yml

再次重复一次,如果使用独立配置文件来存放静态配置,那么 docker-compose.yml 文件中的 command 部分将不会生效。

有了以上的文件,如何加载呢?docker-compose.yml 中的配置自然不必考虑这个问题,对于其他两个文件的加载则必须要绑定至容器。

traefik.yml 文件的加载

当 Traefik 启动时,将会在以下位置搜索配置文件,名称可以是 traefik.toml、traefik.yml 或者 traefik.yaml:

  • /etc/traefik/
  • $XDG_CONFIG_HOME/
  • $HOME/.config/
  • . (the working directory).

另外,可以通过添加形式如 --configFile=foo/bar/myconfigfile.toml 的启动参数覆盖默认行为。

根据官方文档的以上描述,考虑到我们是在 Docker 环境中运行 Traefik,所以建议将放置配置文件的目录绑定到容器的 /etc/traefik 目录即可。

dynamic.yml 文件的加载

动态配置文件的加载,则需要使用 Traefik 的 providers 启动参数,形式如下:

  • CLI 形式
--providers.file.directory=/etc/traefik
--providers.file.filename=dynamic.yml
--providers.file.watch=true
  • 配置文件形式
providers:
  file:
    directory: "/etc/traefik"
    filename: "dynamic.yml"
    watch: true

这三行启动参数的含义非常容易理解,在官方文档中我没有注意到有默认目录,所以我们一定需要指定目录,指定配置文件名,最后一行的watch=true将开启动态配置项的即时生效,不再需要重启容器。

traefik.yml 文件的加载类似,在 Docker 环境运行 Traefik 的情况下,我们需要将存放 dynamic.yml 的目录绑定至容器的 /etc/traefik 目录,这里我们使用了同一目录,这样就不必分别绑定两个目录了。

通过以上的描述,读者应该基本明白了 Traefik 整个配置的运作方式,更加高级的用法可以自行研究官方文档。

Traefik 的安装

我们使用 Docker 安装 Traefik,下文将以官方文档中的相关示例为模板,修正其错误,完成初步配置及安装。

我们使用的配置文件来自于官方文档中Docker-compose with let's encrypt: TLS Challenge,内容如下:

version: "3.3"

services:

  traefik:
    image: "traefik:v2.0.0-rc3"
    container_name: "traefik"
    command:
      #- "--log.level=DEBUG"
      - "--api.insecure=true"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
      #- "--certificatesresolvers.mytlschallenge.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory"
      - "--certificatesresolvers.mytlschallenge.acme.email=postmaster@mydomain.com"
      - "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"
    ports:
      - "443:443"
      - "8080:8080"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  whoami:
    image: "containous/whoami"
    container_name: "simple-service"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.whoami.rule=Host(`whoami.mydomain.com`)"
      - "traefik.http.routers.whoami.entrypoints=websecure"
      - "traefik.http.routers.whoami.tls.certresolver=mytlschallenge"

官方文档中的这一示例,使用了 CLI 配置静态配置的方式,但其提供的这一示例主要存在以下几个问题:

  • 缺少 80 端口绑定,只能使用 HTTPS
  • 没有开启 Dashboard
  • 没有绑定动态配置文件目录

下面我们对其针对性的修改,另外为了清晰,我们将示例中提供的 whoami 应用在后面作为单独应用运行。

根据前文对 Traefik 配置方式的讲解,我们知道我们既可以选择在 docker-compose.yml 文件中配置,也可以使用独立的 traefik.yml 配置,本人建议使用独立的 traefik.yml 配置,下文也只提供这一种方式,熟悉了以后,读者可以自行切换。

静态配置里可以有哪些内容,可以参考官方文档

我们的配置文件内容如下:

  • docker-compose.yml 文件
version: "3.3"
services:
  traefik:
    image: "traefik:latest" # 我们直接部署最新版本,可自行调整
    container_name: "traefik"
    ports:
      - "80:80"
      - "443:443"
      - "8080:8080"
    volumes:
      # 自动申请的证书存放位置,我们需要在当前目录创建 letsencrypt 目录
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      # 绑定我们的配置目录
      - "./config:/etc/traefik"
  • ./config/traefik.yml 文件
providers:
  docker: {}
  file:
    directory: "/etc/traefik"
    filename: "dynamic.yml"
    watch: true

log:
  level: DEBUG

entryPoints:
  web:
    address: ":80"
  websecure:
    address: ":443"

certificatesResolvers:
  mytlschallenge:
    acme:
      email:  "yourname@domain"
      storage:  "/letsencrypt/acme.json"
      tlsChallenge: {}

api:
  dashboard: true
  • ./config/dynamic.yml 文件

由于我们暂时不转发任何服务,此文件暂时为空。

启动我们的 docker-compose.yml 文件:

docker-compose up -d

稍等片刻,就能看到服务创建成功,使用 docker ps 查看,可以看到容器正常运行。 traefik-docker-1.png

配置域名访问 Traefik Dashboard

Traefik 的 Dashboard 实际上是其 API 功能的图形化展示,所以我们需要开启配置中的 API 参数和 Dashboard 参数,参考官方文档:

If you enable the API, a new special service named api@internal is created and can then be referenced in a router. And then define a routing configuration on Traefik itself with the dynamic configuration.

如果开启了 api,就会创建一个叫做 api@internal 的服务,我们上面所使用的 traefik.yml 配置中,已经打开了 api,也打开了 dashboard

官方配置中有一行 - "--api.insecure=true" 被我们无视掉了,有兴趣的朋友阅读这里

然后按官方文档说明,为 Traefik 自身添加这一动态路由,所以我们修改 dynamic.yml 文件:

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.domain.com`)"
      service: api@internal

Rule 的配置除了 Host 还有 PathPathPrefix 等,配置逻辑运算符来实现配置,具体可以查看这里。另外,记得将域名提前解析到服务器 IP,如果是本地虚拟机测试,请自行修改 hosts 文件。

之后重新运行 docker-compose up -d 即可,此时访问绑定的域名就能看到 Traefik 的 Dashboard 了: traefik-docker-2-dashboard.png traefik-docker-3-dashboard2.png

配置域名转发至 Docker 服务

当我们启动一个 Docker 服务后,Traefik 就能发现服务并为其创建默认路由,需要注意的一点是,如果 Traefik 要能够与新的 Docker 服务进行通信,必须将其加入到同一网络,我们使用 docker-compose.yml 启动 Traefik 时,就创建了 traefik_default 网络,新加入的容器需要手动加入该网络。

加入网络的方式很多,可以自行搜索,使用 docker run 时通过 --network traefik_default,或者使用docker-compose.yml 时添加:

networks:
  - traefik_default

下面我们使用 Traefik 提供的 whoami 应用进行测试,使用 docker run 启动应用:

docker run -d -P --name whoami --net traefik_default containous/whoami

之后我们查看 Dashboard,就会发现 Traefik 已经自动发现了服务,服务名为 whoami@docker 。下面我们为该服务配置路由,打开我们的 ./config/dynamic.yml 文件,在下方增加 whoami 路由配置内容:

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
    whoami:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`) && Path(`/whoami`)"
      service: whoami@docker

配置完成并保存后,在 Dashboard 就可以看到新添加的路由了,此时我们访问 http://traefik.pbeta.cn/whoami 就能访问到我们的服务了: traefik-docker-4.png

你会发现我们并没有告诉要转发服务的哪个端口,这是因为 Traefik 智能的进行了处理,如果服务只暴露一个端口,Traefik 就会自动选择该端口,但如果服务暴露多个端口,你必须手动指定,具体请参考官方文档。

配置转发外部服务

我们使用本机来模拟这一需求,在我们的服务器上运行一个 Ghost 服务,但是并不加入 traefik_default 网络,而是在 Traefik 中通过 服务器互联网IP : 服务端口 来转发。

启动一个 Ghost 容器:

docker run -d -p 2368:2368 --name ghost  ghost

我们暴露了服务器的 2368 端口,此时直接在外部访问 服务器IP:端口 就能访问到网站(如果你防火墙开放了 2368 端口的话) traefik-docker-5.png

下面我们就配置 Traefik 将 traefik.pbeta.cn/ghost 转发至该地址,继续修改我们的 dynamic.yml ,分别添加一个服务和一个路由,完整的 dynamic.yml 文件如下:

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
    whoami:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`) && Path(`/whoami`)"
      service: whoami@docker
    ghost:
      entryPoints:
        - "web"
      rule: "Host(`ghost.pbeta.cn`) "
      service: ghost

  services:
    ghost:
      loadBalancer:
        servers:
        - url: "http://47.74.154.202:2368/"

保存之后访问 http://ghost.pbeta.cn 即可打开我们刚启动的 Ghost 网站:

traefik-docker-6.png

配置 HTTPS 访问

前面我们一直使用的是 web 这一入口,通过简单配置即可实现 HTTPS 访问服务。官方文相关页面有较多示例,可供参考。本文将只讲解使用 Let's Encrypt 自动配置 HTTPS。

前面提供的 traefik.yml 中已经包括了相关的配置:

certificatesResolvers:
  mytlschallenge:
    acme:
      email:  "yourname@domain"
      storage:  "/letsencrypt/acme.json"
      tlsChallenge: {}

我们为这一 certificatesRsolver 命名为了 mytlschallenge,之后在路由配置中添加 tls 部分使用该 resolver 即可,所以我们打开 dynamic.yml,进行编辑,为服务额外添加一条路由配置:

api-tls:
  entryPoints:
    - "websecure"
  rule: "Host(`traefik.pbeta.cn`)"
  service: api@internal
  tls:
    certResolver: "mytlschallenge"

保存之后稍等片刻,就已经配置好了 HTTPS: traefik-docker-7.png

可以在多个网站使用同一个 certificatesResolver,但为了避免冲突,支持通过 option 来进行区分,一个完整的配置示例如下:

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
    api-tls:
      entryPoints:
        - "websecure"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
      tls:
        certResolver: "mytlschallenge"
        options: traefik

    whoami:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`) && Path(`/whoami`)"
      service: whoami@docker
    whoami-tls:
      entryPoints:
        - "websecure"
      rule: "Host(`traefik.pbeta.cn`) && Path(`/whoami`)"
      service: whoami@docker
      tls:
        certResolver: "mytlschallenge"
        options: traefik
    ghost:
      entryPoints:
        - "web"
      rule: "Host(`ghost.pbeta.cn`)"
      service: ghost
    ghost-tls:
      entryPoints:
        - "websecure"
      rule: "Host(`ghost.pbeta.cn`)"
      service: ghost
      tls:
        certResolver: "mytlschallenge"
        options: ghost

  services:
    ghost:
      loadBalancer:
        servers:
        - url: "http://47.74.154.202:2368/"
tls:
  options:
    traefik: {}
    ghost: {}

这样我们的三个路由的 HTTPS 访问都就配置好了,你可以注意到我们使用了2个 options,因为 traefik.pbeta.cnghost.pbeta.cn 需要不同的证书,所以必须以此进行区分,options 中也可以自行根据文档说明自行添加配置内容。

使用 Middlewares

Traefik 提供了非常多的中间件,这些中间件能实现你需要的大部分功能,这里分别以简单权限认证和 HTTP 自动跳转 HTTPS 为例进行介绍。

为了避免配置文件过长,下面只以 Dashboard 进行示例。

实现自动跳转 HTTPS

Middlewares 的使用非常简单,在动态配置文件中的 http (或tcp)下添加 middlewares,之后在每一个路由中使用即可,我们添加一个自动跳转 HTTPS 的 Middleware:

  middlewares:
    redirect:
      redirectScheme:
        scheme: https

之后我们在以 web 为入口的服务添加这一 middleware

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
      middlewares:
        - redirect
    api-tls:
      entryPoints:
        - "websecure"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
      tls:
        certResolver: "mytlschallenge"
        options: traefik

  middlewares:
    redirect:
      redirectScheme:
        scheme: https

tls:
  options:
    traefik: {}

保存文件之后,访问 http://traefik.pbeta.cn 将会自动跳转至 https://traefik.pbeta.cn

实现密码认证

这里需要使用的中间件是 BasicAuth,具体的使用方式可以参阅官方文档

在 K8s 中我们使用 Secret 资源来管理密码等,但在 Docker 中无法通过这种方式,需要使用 users 或者 usersFile 参数管理密码,这里我们使用 users

密码的创建需要借助于 htpasswd,安装方式如下:

  • CentOS
yum -y install httpd-tools
  • Ubuntu
apt install apache2-utils

我们使用 htpasswd 命令来生成一用户名为test 密码 为test1234 的密码对:

echo $(htpasswd -nb test test1234)

得到的信息是 test:$apr1$k.xiBHjv$VdavfNvly69vNZvkKpB2j0

需要注意一点,由于我们是在单独配置文件中使用,所以无须进行转义,如果是以CLI 形式使用的话,所有 $ 符号必须写两次,可以使用官方文档中的命令来生成转义后的密码 echo $(htpasswd -nb user password) | sed -e s/\\$/\\$\\$/g

下面我们就使用此密码对来创建我们的 BasicAuth Middleware,打开 dynamic.yml 文件进行修改,在上面创建的 redirect 下添加如下内容:

test-auth:
  basicAuth:
    users:
      - "test:$apr1$k.xiBHjv$VdavfNvly69vNZvkKpB2j0"

之后在前面的路由下的 middlewares 下添加 test-auth,完整的 dynamic.yml 文件如下:

http:
  routers:
    api:
      entryPoints:
        - "web"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
      middlewares:
        - redirect
        - test-auth
    api-tls:
      entryPoints:
        - "websecure"
      rule: "Host(`traefik.pbeta.cn`)"
      service: api@internal
      middlewares:
        - test-auth
      tls:
        certResolver: "mytlschallenge"
        options: traefik

  middlewares:
    redirect:
      redirectScheme:
        scheme: https
    test-auth:
      basicAuth:
        users:
          - "test:$apr1$k.xiBHjv$VdavfNvly69vNZvkKpB2j0"
tls:
  options:
    traefik: {}

保存文件后,再次访问 http://traefik.pbeta.cn,就会发现需要密码登录了: traefik-docker-9.png

输入密码后即可正常访问。

总结

通过以上内容,我们对 Traefik 的配置方式有了比较深入的了解,并实现了我们的目标,Traefik 的官方文档虽然杂乱,但不失详尽,有了本文提供的基础知识,再结合官方文档,使用 Traefik 实现各种高级功能应该并非难事。

在配置过程中遇到任何问题,可以在评论区中提问,本人将尽力协助。

参考文档

  1. Traefik 官方文档
  2. What api.insecure do exactly?
  3. Comparison of Traefik 1.7 and Traefik 2.0 with Docker