├── .gitignore ├── screenshots ├── flare.png ├── traefik.png └── flare-ready.png ├── scripts ├── prepare-network.sh └── generate-certs.sh ├── docker-compose.make-cert.yml ├── config ├── default.toml └── tls.toml ├── LICENSE ├── docker-compose.flare.yml ├── docker-compose.yml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | .DS_Store 3 | ssl -------------------------------------------------------------------------------- /screenshots/flare.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulteary/traefik-example/HEAD/screenshots/flare.png -------------------------------------------------------------------------------- /screenshots/traefik.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulteary/traefik-example/HEAD/screenshots/traefik.png -------------------------------------------------------------------------------- /screenshots/flare-ready.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/soulteary/traefik-example/HEAD/screenshots/flare-ready.png -------------------------------------------------------------------------------- /scripts/prepare-network.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | NETWORK_NAME=traefik 4 | 5 | if [ -z $(docker network ls --filter name=^${NETWORK_NAME}$ --format="{{ .Name }}") ] ; then 6 | docker network create ${NETWORK_NAME} ; 7 | fi -------------------------------------------------------------------------------- /docker-compose.make-cert.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | 3 | services: 4 | 5 | certs-maker: 6 | image: soulteary/certs-maker 7 | environment: 8 | - CERT_DNS=example.com;*.example.com; 9 | volumes: 10 | - ./ssl:/ssl -------------------------------------------------------------------------------- /config/default.toml: -------------------------------------------------------------------------------- 1 | [http.middlewares.gzip.compress] 2 | [http.middlewares.content-compress.compress] 3 | 4 | # tricks 5 | # https://github.com/containous/traefik/issues/4863#issuecomment-491093096 6 | [http.services] 7 | [http.services.noop.LoadBalancer] 8 | [[http.services.noop.LoadBalancer.servers]] 9 | url = "" # or url = "localhost" 10 | 11 | [http.routers] 12 | [http.routers.https-redirect] 13 | entryPoints = ["http"] 14 | rule = "HostRegexp(`{any:.*}`)" 15 | middlewares = ["https-redirect"] 16 | service = "noop" 17 | 18 | [http.middlewares.https-redirect.redirectScheme] 19 | scheme = "https" 20 | -------------------------------------------------------------------------------- /config/tls.toml: -------------------------------------------------------------------------------- 1 | [tls] 2 | 3 | [tls.options] 4 | [tls.options.default] 5 | minVersion = "VersionTLS12" 6 | maxVersion = "VersionTLS12" 7 | [tls.options.test-tls13] 8 | minVersion = "VersionTLS13" 9 | cipherSuites = [ 10 | "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 11 | "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", 12 | "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", 13 | "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", 14 | "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", 15 | "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", 16 | ] 17 | 18 | [tls.stores] 19 | [tls.stores.default] 20 | [tls.stores.default.defaultCertificate] 21 | certFile = "/data/ssl/example.com.crt" 22 | keyFile = "/data/ssl/example.com.key" 23 | 24 | [[tls.certificates]] 25 | certFile = "/data/ssl/example.com.crt" 26 | keyFile = "/data/ssl/example.com.key" 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Su Yang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /scripts/generate-certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mkdir -p ssl 4 | 5 | OUTPUT_FILENAME="example.com" 6 | 7 | printf "[req] 8 | prompt = no 9 | default_bits = 4096 10 | default_md = sha256 11 | encrypt_key = no 12 | string_mask = utf8only 13 | 14 | distinguished_name = cert_distinguished_name 15 | req_extensions = req_x509v3_extensions 16 | x509_extensions = req_x509v3_extensions 17 | 18 | [ cert_distinguished_name ] 19 | C = CN 20 | ST = BJ 21 | L = BJ 22 | O = example.com 23 | OU = example.com 24 | CN = example.com 25 | 26 | [req_x509v3_extensions] 27 | basicConstraints = critical,CA:true 28 | subjectKeyIdentifier = hash 29 | keyUsage = critical,digitalSignature,keyCertSign,cRLSign #,keyEncipherment 30 | extendedKeyUsage = critical,serverAuth #, clientAuth 31 | subjectAltName = @alt_names 32 | 33 | [alt_names] 34 | DNS.1 = example.com 35 | DNS.2 = *.example.com 36 | 37 | ">ssl/${OUTPUT_FILENAME}.conf 38 | 39 | openssl req -x509 -newkey rsa:2048 -keyout ssl/$OUTPUT_FILENAME.key -out ssl/$OUTPUT_FILENAME.crt -days 3600 -nodes -config ssl/${OUTPUT_FILENAME}.conf 40 | -------------------------------------------------------------------------------- /docker-compose.flare.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | 3 | services: 4 | flare: 5 | image: soulteary/flare:0.2.5 6 | restart: always 7 | # 默认无需添加任何参数,如有特殊需求 8 | # 可阅读文档 https://github.com/soulteary/docker-flare/blob/main/docs/advanced-startup.md 9 | command: flare 10 | labels: 11 | - "traefik.enable=true" 12 | - "traefik.docker.network=traefik" 13 | 14 | - "traefik.http.routers.traefik-flare-http.middlewares=https-redirect@file" 15 | - "traefik.http.routers.traefik-flare-http.entrypoints=http" 16 | - "traefik.http.routers.traefik-flare-http.rule=Host(`flare.example.com`)" 17 | - "traefik.http.routers.traefik-flare-http.service=dashboard@internal" 18 | 19 | - "traefik.http.routers.traefik-flare-https.entrypoints=https" 20 | - "traefik.http.routers.traefik-flare-https.rule=Host(`flare.example.com`) && PathPrefix(`/`)" 21 | - "traefik.http.routers.traefik-flare-https.tls=true" 22 | 23 | - "traefik.http.services.traefik-flare-backend.loadbalancer.server.scheme=http" 24 | - "traefik.http.services.traefik-flare-backend.loadbalancer.server.port=5005" 25 | networks: 26 | - traefik 27 | expose: 28 | - 5005 29 | # 仅作为示例,无需映射作数据持久化 :) 30 | # volumes: 31 | # - ./app:/app 32 | 33 | networks: 34 | traefik: 35 | external: true -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | 5 | traefik: 6 | container_name: traefik 7 | image: traefik:v2.6.1 8 | restart: always 9 | ports: 10 | - 127.0.0.1:80:80 11 | - 127.0.0.1:443:443 12 | networks: 13 | - traefik 14 | command: 15 | - "--global.sendanonymoususage=false" 16 | - "--global.checknewversion=false" 17 | - "--entrypoints.http.address=:80" 18 | - "--entrypoints.https.address=:443" 19 | - "--api=true" 20 | - "--api.insecure=true" 21 | - "--api.dashboard=true" 22 | - "--api.debug=false" 23 | - "--ping=true" 24 | - "--log.level=DEBUG" 25 | - "--log.format=common" 26 | - "--accesslog=false" 27 | - "--providers.docker=true" 28 | - "--providers.docker.watch=true" 29 | - "--providers.docker.exposedbydefault=false" 30 | - "--providers.docker.endpoint=unix:///var/run/docker.sock" 31 | - "--providers.docker.swarmMode=false" 32 | - "--providers.docker.useBindPortIP=false" 33 | - "--providers.docker.network=traefik" 34 | - "--providers.file=true" 35 | - "--providers.file.watch=true" 36 | - "--providers.file.directory=/etc/traefik/config" 37 | - "--providers.file.debugloggeneratedtemplate=true" 38 | volumes: 39 | - /var/run/docker.sock:/var/run/docker.sock:ro 40 | - ./ssl/:/data/ssl/:ro 41 | - ./logs:/data/logs:rw 42 | - ./config/:/etc/traefik/config/:ro 43 | labels: 44 | - "traefik.enable=true" 45 | - "traefik.docker.network=traefik" 46 | # default request forwarding https port 47 | - "traefik.http.routers.traefik-dash-default.middlewares=https-redirect@file" 48 | - "traefik.http.routers.traefik-dash-default.entrypoints=http" 49 | - "traefik.http.routers.traefik-dash-default.rule=Host(`dashboard.example.com`)" 50 | - "traefik.http.routers.traefik-dash-default.service=dashboard@internal" 51 | # handle dashboard 52 | - "traefik.http.routers.traefik-dash-web.entrypoints=https" 53 | - "traefik.http.routers.traefik-dash-web.rule=Host(`dashboard.example.com`) && PathPrefix(`/`)" 54 | - "traefik.http.routers.traefik-dash-web.tls=true" 55 | - "traefik.http.routers.traefik-dash-web.service=dashboard@internal" 56 | # handle api 57 | - "traefik.http.routers.traefik-dash-api.entrypoints=https" 58 | - "traefik.http.routers.traefik-dash-api.rule=Host(`dashboard.example.com`) && (PathPrefix(`/api`) || PathPrefix(`/dashboard`))" 59 | - "traefik.http.routers.traefik-dash-api.tls=true" 60 | - "traefik.http.routers.traefik-dash-api.service=api@internal" 61 | extra_hosts: 62 | # https://github.com/containous/traefik/blob/master/pkg/version/version.go#L61 63 | - "update.traefik.io:127.0.0.1" 64 | # https://github.com/containous/traefik/blob/master/pkg/collector/collector.go#L20 65 | - "collect.traefik.io:127.0.0.1" 66 | - "stats.g.doubleclick.net:127.0.0.1" 67 | healthcheck: 68 | test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"] 69 | interval: 3s 70 | retries: 12 71 | logging: 72 | driver: "json-file" 73 | options: 74 | max-size: "1m" 75 | 76 | networks: 77 | traefik: 78 | external: true 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Traefik 快速上手示例 2 | 3 | **如果你在寻找 3.x 版本示例,可以访问 [soulteary/traefik-v3-example](https://github.com/soulteary/traefik-v3-example)** 4 | 5 | 本项目旨在演示如何快速上手 Traefik:包含服务动态接入、配置证书。 6 | 7 | 如果你觉得这个例子有帮到你,欢迎点赞✨(star),如果你希望收到这个项目的更新推送,可以点击关注 👀(watch)并选择适合自己的关注模式。 8 | 9 | ## 如何配置和启动 Traefik 10 | 11 | 这个小节将展示如何启动一个最基础的 Traefik 示例,配置具体的内容你并不需要过多了解,建议先跑起来再说 :) 12 | ### 第一步:创建容器虚拟网卡 13 | 14 | 首先使用创建一个 Traefik 和它服务的相关应用使用的网卡,我们约定在这个网卡上的应用,将能够被进行服务发现和自动注册到 traefik 上。 15 | 16 | ```bash 17 | bash scripts/prepare-network.sh 18 | ``` 19 | 20 | ### 第二步:生成必要的证书 21 | 22 | 接着生成我们所需要的证书,这里有两个选择,第一种使用容器来进行生成: 23 | 24 | ```bash 25 | docker-compose -f docker-compose.make-cert.yml up 26 | ``` 27 | 28 | 第二种则是直接使用命令(需要本地安装 openssl): 29 | 30 | ```bash 31 | bash scripts/generate-certs.sh 32 | ``` 33 | 34 | 如果你希望生成多个不同的泛解析域名,或者想获得一些复杂的域名 DNS 组合,可以使用这个工具 [certs-maker](https://github.com/soulteary/certs-maker),它的命令行使用示例: 35 | 36 | ```bash 37 | docker run --rm -it -e CERT_DNS=a.com\;\*.domain.com\;a.c.com -v `pwd`/certs:/ssl soulteary/certs-maker 38 | ``` 39 | 40 | ### 第三步:绑定域名到合适的机器上 41 | 42 | 如果你是在本地启动 Traefik,可以使用下面的方式,将某个域名绑定到本地。 43 | 44 | ```bash 45 | echo "127.0.0.1 dashboard.example.com">>/etc/hosts 46 | ``` 47 | 48 | 或者使用 DNS 服务器(Homelab 或者公网都可以)指定到某台远程服务器上。 49 | 50 | ### 第四步:启动 Traefik 应用 51 | 52 | 接着使用 `docker-compose up -d` 启动服务,稍等片刻,在浏览器中打开刚刚绑定到域名,就能开始你的 Traefik 之旅啦。 53 | 54 | ![traefik dashboard](./screenshots/traefik.png) 55 | 56 | 当然,因为我们使用的是自签名证书,所以在浏览器中打开网站的时候,会提示安全风险。这里推荐进行自签名证书信任,当然,你也可以选择购买网络服务商的证书,或者使用 Let's Encrypt 签发的证书。(另外一个示例,可以参考这里 https://suyang.wiki/runbook/traefik/issue-certs.html ) 57 | 58 | ## 如何进行服务动态接入 59 | 60 | 本小节将介绍如何使用 traefik 动态接入服务。 61 | 62 | ### 第一步:观察和分析需要接入的应用 63 | 64 | 这里以另外一个项目 https://github.com/soulteary/docker-flare 为例。 65 | 66 | ```yml 67 | version: '3.6' 68 | 69 | services: 70 | flare: 71 | image: soulteary/flare:0.2.5 72 | restart: always 73 | # 默认无需添加任何参数,如有特殊需求 74 | # 可阅读文档 https://github.com/soulteary/docker-flare/blob/main/docs/advanced-startup.md 75 | command: flare 76 | ports: 77 | - 5005:5005 78 | volumes: 79 | - ./app:/app 80 | ``` 81 | 如上面的配置展示的一样,flare 默认将使用本地的 5005 端口和容器内的应用 5005 端口打通,提供 HTTP 服务。 82 | 83 | 我们如果想要看的 flare 的界面,则需要在启动应用后,访问 `localhost:5005`。 84 | 85 | ![flare homepage](./screenshots/traefik.png) 86 | 87 | ### 第二步:改写接入 Traefik 的配置文件 88 | 89 | 如果想要一个应用接入 Traefik,最简单的方式便是在它原本的配置中作一些调整: 90 | 91 | ```yml 92 | version: '3.6' 93 | 94 | services: 95 | flare: 96 | image: soulteary/flare:0.2.5 97 | restart: always 98 | # 默认无需添加任何参数,如有特殊需求 99 | # 可阅读文档 https://github.com/soulteary/docker-flare/blob/main/docs/advanced-startup.md 100 | command: flare 101 | labels: 102 | - "traefik.enable=true" 103 | - "traefik.docker.network=traefik" 104 | 105 | - "traefik.http.routers.traefik-flare-http.middlewares=https-redirect@file" 106 | - "traefik.http.routers.traefik-flare-http.entrypoints=http" 107 | - "traefik.http.routers.traefik-flare-http.rule=Host(`flare.example.com`)" 108 | - "traefik.http.routers.traefik-flare-http.service=dashboard@internal" 109 | 110 | - "traefik.http.routers.traefik-flare-https.entrypoints=https" 111 | - "traefik.http.routers.traefik-flare-https.rule=Host(`flare.example.com`) && PathPrefix(`/`)" 112 | - "traefik.http.routers.traefik-flare-https.tls=true" 113 | 114 | # 这里是一般性习惯,为了便于阅读,如果我们不声明服务端口,则默认会取 expose 的第一个端口,以及使用第一张网卡 115 | - "traefik.http.services.traefik-flare-backend.loadbalancer.server.scheme=http" 116 | - "traefik.http.services.traefik-flare-backend.loadbalancer.server.port=5005" 117 | networks: 118 | - traefik 119 | expose: 120 | - 5005 121 | # 仅作为示例,无需映射作数据持久化 :) 122 | # volumes: 123 | # - ./app:/app 124 | 125 | networks: 126 | traefik: 127 | external: true 128 | ``` 129 | 130 | 对比第一步中的配置,我们可以看到,在 `labels` 字段中,我们添加了大量描述内容,用于告诉 traefik: 131 | 132 | - 这个应用要接入 traefik 的服务网络 133 | - 接入使用的网卡是 traefik 这块网卡 134 | - 这个应用分别使用 HTTP、HTTPS 提供服务,并分别使用 traefik 的 `http`/`https` entrypoints(端口不同,80和443),两种协议下提供服务的域名都是 `flare.example.com`。 135 | - 其中因为 HTTP 配置了自动跳转 HTTPS 服务,所以这个 entrypoint 下对应的转发服务无所谓是什么,这里我象征性的配置了 traefik 的内部服务 `dashboard@internal`。 136 | - 而 HTTPS entrypoint 我们需要声明它使用 `tls`。 137 | - 最后告诉 traefik,我们的应用和 traefik 交互协议是 http,服务端口是 `5005`。 138 | 139 | 当然,除了做出上面的细粒度声明之外,我们还需要将他们放到一个网络里: 140 | 141 | ```yaml 142 | networks: 143 | - traefik 144 | ``` 145 | ### 第三步:绑定服务域名和启动服务 146 | 147 | 和上面使用 traefik 一样,我们将 `flare.example.com` 的 DNS 指向绑定到合适的机器上。 148 | 149 | 然后使用 `docker-compose -f docker-compose.flare.yml up -d` 启动服务。 150 | 151 | ### 第四步:访问服务 152 | 153 | 接着我们在浏览器中访问:`http://flare.example.com` ,会发现浏览器自动跳转到了 `https://flare.example.com`(当然,别忘记使用更合适的证书,或者信任自签名证书,避开烦人的安全提示)。 154 | 155 | 到此为止,我们就已经成功的在不了原理的情况下,掌握了 Traefik 最基础的使用啦。相比较 Nginx,是不是更灵活和简单呢? 156 | 157 | 或许你会好奇,在 flare 运行起来之后,traefik 会有什么变化吗? 158 | 159 | ![flare is ready](./screenshots/flare-ready.png) 160 | 161 | 重新访问我们的 traefik dashboard (https://dashboard.exmaple.com) ,你会发现在路由和服务等页面,多出现了几条刚刚我们在上面“第一步操作”时编写的规则。这些规则其实非常灵活、以及和软件的生命周期息息相关,如果你足够熟悉的话,你可以使用 traefik 快速搭建一套完整的 CI/CD 流水线,或者使用 traefik 做一套简单的 WAF、以及一套简单的 Serverless 平台工具。 162 | 163 | ## 如何调整服务域名 164 | 165 | 相信你一定希望能够使用自己的域名来运行服务。 166 | 167 | 你可以在下面的文件中,将 `example.com` 替换为你想要的域名,然后重启服务就可以了。 168 | 169 | - docker-compose.yml 170 | - config/tls.toml(注意保持路径和文件名正确) 171 | - scripts/generate-certs.sh (如果没有使用脚本生成证书,则可以忽略) 172 | --------------------------------------------------------------------------------