本篇文章聊聊如何通過(guò) Docker 容器使用 Traefik,進(jìn)行穩(wěn)定的 Traefik 服務(wù)的部署。
寫在前面
距離 Traefik v2.0.0 的發(fā)布,不知不覺快四年了,在過(guò)去的四年里,我寫過(guò)非常多和 Traefik 相關(guān)的實(shí)踐內(nèi)容,感興趣的同學(xué)可以翻閱這里。
上個(gè)月官方 Traefik 3.0.0 第三個(gè) beta 版本的發(fā)布,3.0 新版本的代碼被第二次正式合并進(jìn)主干分支,距離我們能夠正式使用到 3.0 版本,也越來(lái)越近了。
相較一個(gè)季度前的版本,目前 Traefik 版本變化應(yīng)該已經(jīng)接近穩(wěn)定,為了后面更簡(jiǎn)單的切換到新版本,或許是時(shí)候開始嘗試服務(wù)遷移了。
正好,嘗試詳細(xì)的寫一篇使用 Docker 來(lái)使用 Traefik 的內(nèi)容,幫助還沒有入門的同學(xué),或者使用但是還不熟悉的同學(xué)查缺補(bǔ)漏。
Traefik 的 Docker 基礎(chǔ)容器配置
在展開詳細(xì)的 Traefik 容器配置和優(yōu)化調(diào)整之前,我們需要先來(lái)看看最簡(jiǎn)的容器配置是什么樣的。
Traefik 的 Docker 最簡(jiǎn)容器配置
最基礎(chǔ)的配置不到十行,我們只需要聲明 Traefik 服務(wù)使用的容器鏡像、使用和對(duì)外暴露的端口號(hào)、以及基礎(chǔ)的命令行參數(shù)即可。
version: "3"
services:
traefik:
image: traefik:v3.0.0-beta3
ports:
- 8080:8080
command: "--api=true --api.dashboard=true --api.insecure=true"
將上面的內(nèi)容保存為 docker-compose.yml
后,我們使用 docker compose up
啟動(dòng) Traefik 容器服務(wù),打開瀏覽器 localhost:8080/dashboard
就能夠看到 Traefik 的 Dashboard 啦。
但是,這個(gè)容器只能夠提供我們查看 Traefik Dashboard 和默認(rèn)的內(nèi)部“服務(wù)”,不能夠提供神奇的“服務(wù)發(fā)現(xiàn)”和各種“高級(jí)自定義”或“服務(wù)可觀測(cè)性”等功能。
所以,我們還需要繼續(xù)進(jìn)行配置擴(kuò)展和調(diào)整。
使用 Traefik 進(jìn)行服務(wù)域名綁定
Traefik 最擅長(zhǎng)的能力是提供服務(wù)發(fā)現(xiàn),即讓能夠提供對(duì)外網(wǎng)絡(luò)訪問(wèn)能力(HTTP/TCP)的服務(wù)使用域名和具體的 URL 地址允許用戶訪問(wèn)。
假設(shè)我們上文中使用 localhost:8080/dashboard
訪問(wèn)的 Dashboard 是一個(gè)正式的服務(wù),有正式的域名,沒有“非正式”的端口號(hào),在使用 Traefik 能力的情況下該如何做呢?
比如,我們要使用 traefik.console.lab.io
這個(gè)域名來(lái)訪問(wèn)上面的服務(wù),那么首先要確保這個(gè)域名的指向是 Traefik 服務(wù)的網(wǎng)絡(luò) IP 地址。你可以通過(guò) DNS 管理工具(包括各種云服務(wù)商的網(wǎng)頁(yè)控制面板)來(lái)調(diào)整,或者如果你的 Traefik 服務(wù)運(yùn)行在本地,可以修改 /etc/hosts
來(lái)完成第一步前置條件。
# 修改 /etc/hosts
127.0.0.1 traefik.console.lab.io
接著,我們對(duì)上面的配置進(jìn)行調(diào)整:
version: "3"
services:
traefik:
image: traefik:v3.0.0-beta3
ports:
- 8080:8080
- 80:80
command: "--api=true --api.dashboard=true --api.insecure=true --entrypoints.http.address=:80 --providers.docker=true --providers.docker.endpoint=unix:///var/run/docker.sock"
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=http"
- "traefik.http.routers.traefik-dashboard-api.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api.service=api@internal"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
使用上面的內(nèi)容,更新之前保存的 docker-compose.yml
文件,再次使用 docker compose up
啟動(dòng) Traefik 容器服務(wù),我們除了還能夠使用瀏覽器訪問(wèn) localhost:8080/dashboard
來(lái)訪問(wèn) Dashboard 之外,與此同時(shí),也能夠使用 traefik.console.lab.io
這個(gè)域名來(lái)訪問(wèn)服務(wù)啦。
在上面的配置中,我們首先增加了容器暴露的端口 80:80
,并在 Traefik 啟動(dòng)參數(shù)中添加了 --entrypoints.http.address=:80
參數(shù),創(chuàng)建了一個(gè)名為 http
的網(wǎng)絡(luò)入口。
接著,我們?cè)?Docker Volumes 中將本地的 docker.sock 和容器中的 sock 文件進(jìn)行了映射 /var/run/docker.sock:/var/run/docker.sock:ro
,允許 Traefik 訂閱 Docker 服務(wù)事件,來(lái)動(dòng)態(tài)的添加或刪除要對(duì)用戶暴露的網(wǎng)絡(luò)服務(wù),在啟動(dòng)參數(shù)中,也添加了對(duì)應(yīng)的內(nèi)容 --providers.docker=true --providers.docker.endpoint=unix:///var/run/docker.sock
。
最后,通過(guò)在 Docker Labels 中添加了聲明式的路由,分別將 Dashboard 的網(wǎng)頁(yè)(路由名稱 traefik-dashboard
)和 API (路由名稱 traefik-dashboard-api
)注冊(cè)在了我們創(chuàng)建的 http
網(wǎng)絡(luò)入口上,用戶就可以通過(guò)我們?cè)O(shè)置的域名來(lái)訪問(wèn)服務(wù)了。
這里的 service=dashboard@internal
和 service=api@internal
是 Traefik 的內(nèi)部服務(wù)別名,在日常使用過(guò)程中,我們可以使用 Docker Compose 中的 Service Name
、Container Name
、具體的 IP:端口號(hào)
來(lái)進(jìn)行替換。
通過(guò)類似上面的方式,我們能夠?qū)崿F(xiàn)通過(guò)不同的域名,而非端口號(hào)來(lái)訪問(wèn)我們的網(wǎng)絡(luò)服務(wù),只需要根據(jù)實(shí)際需求,創(chuàng)建不同的路由名稱和地址規(guī)則即可。
減少對(duì)外暴露的端口
在上面的配置中,我們能夠通過(guò)兩種方式來(lái)訪問(wèn)相同的服務(wù)。
實(shí)際使用過(guò)程中,除非我們需要進(jìn)行調(diào)試,否則只通過(guò) Traefik 提供服務(wù)注冊(cè)的域名來(lái)進(jìn)行服務(wù)訪問(wèn),顯然是更好的模式。并且,在這個(gè)過(guò)程中,我們能夠在我們聲明的路由中添加各種各樣的額外操作:添加認(rèn)證、修改請(qǐng)求頭、修改響應(yīng)內(nèi)容、進(jìn)行重定向、進(jìn)行限流、進(jìn)行訪問(wèn)限制等等。
所以,我們可以將上面暴露的端口從下面的內(nèi)容:
ports:
- 8080:8080
- 80:80
調(diào)整為:
ports:
- 80:80
優(yōu)化 Traefik 命令行配置寫法
在上面的配置中,我們操作 Traefik 的命令行的書寫方式是這樣的:
command: "--api=true --api.dashboard=true --api.insecure=true --entrypoints.http.address=:80 --providers.docker=true --providers.docker.endpoint=unix:///var/run/docker.sock"
實(shí)際使用過(guò)程中,我們會(huì)使用二十余個(gè)參數(shù),如果使用上面的寫法進(jìn)行書寫,那么將是一大坨文本混在一起,非常不利于后續(xù)調(diào)試和調(diào)整,不過(guò)我們可以使用一個(gè) 小技巧,來(lái)進(jìn)行改善:
command:
- "--api=true"
- "--api.dashboard=true"
- "--api.insecure=true"
- "--entrypoints.http.address=:80"
- "--providers.docker=true"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
添加 Traefik 服務(wù)的健康檢查
和其他所有的想要穩(wěn)定運(yùn)行的網(wǎng)絡(luò)服務(wù)一樣,為了服務(wù)運(yùn)行穩(wěn)定省心,我們需要進(jìn)行定期的 Health Check,并在服務(wù)不健康的時(shí)候,重新啟動(dòng)服務(wù),來(lái)保證服務(wù)能力。
Traefik 本身內(nèi)置了服務(wù)檢查的接口,我們可以通過(guò)在 command
參數(shù)中添加 --ping=true
來(lái)啟用 /ping
路由接口。
接著在 docker-compose.yml
中添加下面的內(nèi)容,讓 Traefik 能夠每 3 秒進(jìn)行一次服務(wù)自檢,當(dāng)連續(xù)十次檢查失敗之后(30秒),告訴 Docker 服務(wù)狀態(tài)是異常的:
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"]
interval: 3s
retries: 10
為了讓服務(wù)能夠遇到問(wèn)題自動(dòng)重啟,我們可以添加下面的內(nèi)容到配置中:
restart: always
完整的配置如下:
version: "3"
services:
traefik:
image: traefik:v3.0.0-beta3
restart: always
ports:
- 80:80
command:
- "--api=true"
- "--api.dashboard=true"
- "--api.insecure=true"
- "--ping=true"
- "--entrypoints.http.address=:80"
- "--providers.docker=true"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=http"
- "traefik.http.routers.traefik-dashboard-api.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api.service=api@internal"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"]
interval: 3s
retries: 10
再次使用 docker compose up
啟動(dòng)服務(wù)之后,我們新開一個(gè)命令行窗口,執(zhí)行 docker compose ps
就能夠看到類似下面的日志輸出了:
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTS
traefikconsolelabio-traefik-1 traefik:v3.0.0-beta3 "/entrypoint.sh --ap…" traefik 18 seconds ago Up 17 seconds (healthy) 0.0.0.0:80->80/tcp
日志中的 (healthy)
說(shuō)明服務(wù)是運(yùn)行在健康狀態(tài)的。
當(dāng)然,在添加 healthcheck
聲明之后,如果服務(wù)狀態(tài)還不為健康狀態(tài)時(shí),Traefik 是不會(huì)將服務(wù)進(jìn)行對(duì)外注冊(cè)和暴露的(不可訪問(wèn))。
所以,如果我們注冊(cè)了一個(gè)服務(wù)到 Traefik,并要求使用具體域名和路徑提供對(duì)外服務(wù),但是始終訪問(wèn)不到服務(wù),除了 Typo 別字問(wèn)題外,最大的可能就是服務(wù)健康狀態(tài)不是 “healthy” 的。
使用 Traefik 內(nèi)置中間件:壓縮網(wǎng)頁(yè)內(nèi)容
前面提到了,我們?cè)谧?cè)服務(wù)路由上“疊buff”,下面我們來(lái)使用 Traefik 內(nèi)置中間件能力來(lái)對(duì)網(wǎng)頁(yè)內(nèi)容進(jìn)行壓縮,只需要在配置中先添加一行,定義一個(gè)名為 gzip
的中間件:
labels:
- "traefik.http.middlewares.gzip.compress=true"
定義完中間件之后,我們就可以在之前定義好的路由中添加這個(gè)服務(wù)了:
labels:
- "traefik.http.routers.traefik-dashboard.middlewares=gzip@docker"
- "traefik.http.routers.traefik-dashboard-api.middlewares=gzip@docker"
因?yàn)槲覀兊?gzip
服務(wù)是寫在 Docker 的配置文件中的,為了使用的嚴(yán)謹(jǐn),這里在調(diào)用中間件的時(shí)候,推薦加上 @docker
后綴,要求服務(wù)從 “Docker 中”定義的中間件里查找能夠使用的中間件。(因?yàn)槲覀円部梢栽谖募卸x中間件)
再次訪問(wèn)服務(wù),能夠發(fā)現(xiàn)我們的頁(yè)面已經(jīng)被壓縮過(guò)了,網(wǎng)頁(yè)訪問(wèn)速度也得到了提升。
搭配外部軟件提供 HTTPS 服務(wù)
上面的配置和實(shí)驗(yàn)中,我們能夠使用傳統(tǒng)的 HTTP 的方式來(lái)訪問(wèn)不同的網(wǎng)絡(luò)服務(wù),但是,在很多程序的場(chǎng)景需要中,不論是因?yàn)閿?shù)據(jù)安全需要,還是因?yàn)榘?Server Push 等功能,我們是需要啟用 HTTPS 的。
想要讓 Traefik 提供 HTTPS 服務(wù),本質(zhì)上我們需要讓 Traefik 或者 Traefik “身前”的其他服務(wù),正確掛載 HTTPS 證書。
在詳細(xì)展開 HTTPS 配置之前,我們先聊兩種簡(jiǎn)單,用于生產(chǎn)環(huán)境有極高性能的玩法。
搭配云服務(wù)商的負(fù)載均衡軟件使用
先從最簡(jiǎn)單的方式聊起。
包括阿里云、騰訊云等服務(wù)商的負(fù)載均衡服務(wù),都支持掛載 HTTPS 證書,我們只需要在掛載好 HTTPS 證書的云服務(wù)后,設(shè)置上游端口為我們的 Traefik 服務(wù)器地址的 80 端口即可。
這里我們不需要使用自己的服務(wù)器來(lái)處理 HTTPS 握手、證書解析等等計(jì)算,所有的計(jì)算機(jī)算力資源都能夠用在服務(wù)上,所以效率最高。
搭配 Nginx 和現(xiàn)成的證書提供 HTTPS 訪問(wèn)
有一些同學(xué)之前會(huì)注冊(cè)或購(gòu)買 HTTPS 證書,然后搭配 Nginx 進(jìn)行使用。和上面使用云服務(wù)商類似,我們的 Nginx 充當(dāng)了 “負(fù)載均衡網(wǎng)關(guān)”。我們只需要“配置 Nginx HTTPS 證書” 和 “Nginx 反向代理地址為 Traefik 服務(wù)地址:80 端口”即可。
由于 Nginx 默認(rèn)不像 Traefik 一樣支持動(dòng)態(tài)注冊(cè)服務(wù),并且是使用 C 針對(duì) WEB 服務(wù)場(chǎng)景進(jìn)行了大量特別優(yōu)化的軟件,所以性能相比單純使用 Traefik 會(huì)高非常多,如果我們的訪問(wèn)服務(wù)域名固定,這個(gè)方法不失為一個(gè)非常好的性能優(yōu)化技巧。
使用 Traefik 提供 HTTPS 訪問(wèn)服務(wù)
接下來(lái),我們來(lái)聊聊如何讓 Traefik 直接提供 HTTPS 服務(wù),不需要借助外部軟件,雖然性能會(huì)下降一些,但是勝在維護(hù)成本低、代碼配置更內(nèi)聚,方便遷移和管理。
為 Traefik 創(chuàng)建 HTTPS 服務(wù)接口
想要提供 HTTPS 服務(wù),創(chuàng)建 Traefik HTTPS 接口和準(zhǔn)備證書缺一不可。先來(lái)看看如何在 Traefik 中創(chuàng)建 HTTPS 服務(wù)接口。
因?yàn)槟J(rèn)的 HTTPS 服務(wù)端口為 443,所以我們可以在配置的端口中增加提供外部訪問(wèn)的容器中端口:
ports:
- 443:443
在上面的內(nèi)容中,我們定義了 80
端口,舉一反三,我們可以定義一個(gè)名為 https
的 443
端口:
command:
- "--entrypoints.https.address=:443"
然后,我們可以增加或修改原來(lái)的服務(wù)路由使用的 entrypoints
接口:
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=http"
從 http
調(diào)整為 https
:
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=https"
當(dāng)然,除了簡(jiǎn)單的改名字之外,我們還需要額外增加一個(gè)配置聲明 tls=true
:
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
- "traefik.http.routers.traefik-dashboard.tls=true"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api.tls=true"
如果我們不設(shè)置 tls=true
,那么 Traefik 其實(shí)是不會(huì)在我們的端口上啟用 TLS 來(lái)對(duì)內(nèi)容進(jìn)行響應(yīng)的,換言之,沒有這個(gè)標(biāo)記的網(wǎng)絡(luò)接口,就是普通的 HTTP 響應(yīng)。(例如我們可以在 443 端口提供的 HTTP 服務(wù))。
好了,了解了該怎么調(diào)整配置之后,我們來(lái)解決證書,讓 HTTPS 服務(wù)更完善。
使用 Traefik 和 DNS 服務(wù)自動(dòng)完成 HTTPS 證書申請(qǐng)和服務(wù)
和絕大多數(shù)的現(xiàn)代的 HTTP 服務(wù)器一樣,Traefik 也支持我們直接注冊(cè)和使用 let’s encrypt 免費(fèi)的證書,來(lái)提供 HTTPS 服務(wù)。
關(guān)于這部分,本篇文章就只展開如何使用能夠通過(guò) Cloudflare 修改域名記錄的服務(wù),更多的域名服務(wù)商的相關(guān)內(nèi)容,有必要單獨(dú)寫一篇文章來(lái)講。
我們首先在配置中添加下面三個(gè)環(huán)境變量:
environment:
- CF_API_EMAIL=${CF_DNS_EMAIL}
- CLOUDFLARE_DNS_API_TOKEN=${CF_API_TOKEN}
- CLOUDFLARE_ZONE_API_TOKEN=${CF_API_TOKEN}
CF_API_EMAIL
是我們的 Cloudflare 賬號(hào)郵箱,剩下的兩個(gè) *_API_TOKEN
則可以從 Cloudflare 控制面板中創(chuàng)建。
接著,我們需要在 Traefik 命令行中添加命令,讓 Traefik 能夠按照我們的要求,向指定的域名 DNS 服務(wù)商 Cloudflare 申請(qǐng)證書,并將證書保存在我們想要的目錄中,這里同樣別忘記修改 email
字段。
command:
- "--certificatesresolvers.le.acme.email=${CF_DNS_EMAIL}"
- "--certificatesresolvers.le.acme.storage=/certs/acme.json"
- "--certificatesresolvers.le.acme.dnsChallenge.resolvers=1.1.1.1:53,8.8.8.8:53"
- "--certificatesresolvers.le.acme.dnsChallenge.provider=cloudflare"
- "--certificatesresolvers.le.acme.dnsChallenge.delayBeforeCheck=30"
最后,在我們要提供 HTTPS 的服務(wù)的網(wǎng)絡(luò)路由中進(jìn)行下面的配置:
labels:
- "traefik.http.routers.traefik-dashboard- secure.tls.certresolver=le"
- "traefik.http.routers.traefik-dashboard- secure.tls.domains[0].main=${CF_DNS_MAIN}"
- "traefik.http.routers.traefik-dashboard- secure.tls.domains[0].sans=${CF_DNS_LIST}"
上面變量中的 CF_DNS_MAIN
需要替換為你想申請(qǐng)(你擁有的域名)的根域名(example.com
)或二級(jí)域名(console.example.com
),CF_DNS_LIST
則可以寫一串域名,比如 *.console.example.com,*.data.example.com,*.exmaple.com
,在完成配置更新之后,我們使用 docker compose up
啟動(dòng)服務(wù),在稍等片刻之后,Traefik 會(huì)自動(dòng)注冊(cè)好我們所需要的域名的證書,讓我們能夠通過(guò)上面域名列表內(nèi)的域名訪問(wèn)我們的服務(wù)。
為了讓 Traefik 能夠記住我們花費(fèi)時(shí)間申請(qǐng)好的證書,我們需要將容器的文件做持久化存儲(chǔ):
volumes:
- ./certs/:/certs/:ro
在上面的一通操作之后,我們的 Traefik 就能夠自動(dòng)的申請(qǐng)和使用 HTTPS 證書了,并且會(huì)在證書快過(guò)期之前,自動(dòng)更新證書。
完整的配置文件,你可以參考 GitHub 中早些時(shí)候提交的配置例子。
使用 Traefik 和現(xiàn)成證書提供服務(wù)
通過(guò)云服務(wù)商購(gòu)買或免費(fèi)申請(qǐng) HTTPS 證書也好、通過(guò)類似上面的 let’s encrypt 注冊(cè)證書工具進(jìn)行證書注冊(cè)和保存,或者進(jìn)行自簽名證書生成也罷,我們都能夠得到提高服務(wù)所需要的證書文件。
這里,我們使用最簡(jiǎn)單和自由度最高的方案來(lái)進(jìn)行接下來(lái)的配置的講解:自簽名證書。
幾年前,我寫過(guò)一個(gè)簡(jiǎn)單的,只有 4MB 大小的容器工具:soulteary/certs-maker,使用它可以快速的生成任意域名的 HTTPS 證書,搭配一些 DNS 設(shè)置,比如公司內(nèi)的 DNSMASQ 等私有 DNS 服務(wù)器設(shè)置或修改 /etc/hosts
,我們可以讓 Traefik 支持任意服務(wù)的任意域名的 HTTPS 訪問(wèn),比如你可以提供一個(gè)頁(yè)面上有一個(gè)蘋果的服務(wù),通過(guò) https://www.apple.com
來(lái)訪問(wèn)它。
雖然使用 Docker 命令行可以看起來(lái)更短小精悍的生成配置,但考慮到清晰可讀,我們還是創(chuàng)建一個(gè) docker-compose.certs.yml
的文件,來(lái)幫助我們生成 HTTPS 證書吧。
version: "2"
services:
certs-maker:
image: soulteary/certs-maker:v3.2.0
environment:
- CERT_DNS=lab.io,*.lab.io,*.console.lab.io
volumes:
- ./ssl:/ssl
使用 docker compose -f docker-compose.certs.yml up
,在執(zhí)行命令的目錄中,就能夠看到新鮮的被生成出來(lái)的證書文件和配置了。
├── lab.io.conf
├── lab.io.crt
└── lab.io.key
想要讓我們的 Traefik 正確的使用證書,提供瀏覽器支持的 TLS 傳輸能力,我們需要先創(chuàng)建一個(gè) tls.toml 配置文件:
[tls]
[tls.options.default]
minVersion = "VersionTLS12"
sniStrict = true
cipherSuites = [
# TLS 1.3
"TLS_AES_128_GCM_SHA256",
"TLS_AES_256_GCM_SHA384",
"TLS_CHACHA20_POLY1305_SHA256",
# TLS 1.2
"TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256",
"TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
"TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
]
[tls.stores.default.defaultCertificate]
certFile = "/certs/lab.io.crt"
keyFile = "/certs/lab.io.key"
[[tls.certificates]]
certFile = "/certs/lab.com.crt"
keyFile = "/certs/lab.com.key"
[[tls.certificates]]
certFile = "/certs/lab.io.crt"
keyFile = "/certs/lab.io.key"
在上面的配置中,我們定義了 TLS 服務(wù)最低版本,以及 1.2 和 1.3 版本 TLS 使用的相對(duì)安全的算法,以及設(shè)置了 Traefik 的證書路徑。(你可以參考這個(gè)例子增加更多的不同域名的證書)
接著,我們來(lái)調(diào)整文件目錄,將 tls.toml
配置文件,放在 config/tls.toml
,將剛剛生成在 ssl
目錄中的證書們,移動(dòng)到 certs
目錄中。如果你希望你的設(shè)備能夠像使用付費(fèi)證書、或者申請(qǐng)的公網(wǎng)證書一樣,直接訪問(wèn)服務(wù),而不是在瀏覽器中允許訪問(wèn)不受信任的證書,那么需要在系統(tǒng)或?yàn)g覽器中信任自簽名證書(非常簡(jiǎn)單,可以搜索一下 ?? )。
然后,我們將目錄映射到容器環(huán)境中:
volumes:
- ./certs/:/certs/:ro
- ./config/:/etc/traefik/config/:ro
并在 command
命令中添加能夠讀取目錄中 tls.toml
等配置的參數(shù):
command:
- "--providers.file.directory=/etc/traefik/config"
和上文中一樣在 labels
中調(diào)整之前的路由的 entrypoints
和增加 tls=true
配置:
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
- "traefik.http.routers.traefik-dashboard.tls=true"
我們將上文中提供 HTTP 服務(wù)的配置進(jìn)行調(diào)整,完成配置如下:
version: "3"
services:
traefik:
image: traefik:v3.0.0-beta3
restart: always
ports:
- 443:443
command:
- "--api=true"
- "--api.dashboard=true"
- "--api.insecure=true"
- "--ping=true"
- "--entrypoints.https.address=:443"
- "--providers.docker=true"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
- "--providers.file.directory=/etc/traefik/config"
labels:
- "traefik.http.middlewares.gzip.compress=true"
- "traefik.http.routers.traefik-dashboard.middlewares=gzip@docker"
- "traefik.http.routers.traefik-dashboard-api.middlewares=gzip@docker"
- "traefik.http.routers.traefik-dashboard.entrypoints=https"
- "traefik.http.routers.traefik-dashboard.tls=true"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api.tls=true"
- "traefik.http.routers.traefik-dashboard-api.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api.service=api@internal"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./ssl/:/certs/:ro
- ./config/:/etc/traefik/config/:ro
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"]
interval: 3s
retries: 10
使用 docker compose up
啟動(dòng)服務(wù)之后,我們就能夠通過(guò) https://traefik.console.lab.io
來(lái)訪問(wèn)服務(wù)了。
在瀏覽器的證書信息選項(xiàng)卡里,我們能夠看到這張自簽名證書的詳細(xì)信息,如果你想進(jìn)行信息的定制化,可以參考 certs-maker 項(xiàng)目文檔調(diào)整生成證書使用的參數(shù)。
同時(shí)提供 HTTP 和 HTTPS 服務(wù)
在上面的文章中,我們?yōu)榱斯?jié)約代碼篇幅,和減少不必要的理解成本,分別實(shí)現(xiàn)了幾種 HTTP 和 HTTPS 服務(wù)的實(shí)現(xiàn)方式,下面我們來(lái)將配置組合在一起,完成一個(gè)完整的服務(wù)配置。
首先是定義 command
和 ports
,讓 Traefik 能夠同時(shí)提供 80 端口的 HTTP 服務(wù),和 443 端口的 HTTPS 服務(wù)。
ports:
- 80:80
- 443:443
command:
- "--api=true"
- "--api.dashboard=true"
- "--api.insecure=true"
- "--ping=true"
- "--entrypoints.http.address=:80"
- "--entrypoints.https.address=:443"
- "--providers.docker=true"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
- "--providers.file.directory=/etc/traefik/config"
上面的配置讓 Traefik 擁有了提供服務(wù)的基礎(chǔ)能力,但是沒有服務(wù)內(nèi)容,所以接下來(lái)我們創(chuàng)建能夠提供服務(wù)內(nèi)容的路由配置:
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api.entrypoints=http"
- "traefik.http.routers.traefik-dashboard-api.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api.service=api@internal"
- "traefik.http.routers.traefik-dashboard-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-secure.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard-secure.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-api-secure.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api-secure.service=api@internal"
因?yàn)槲覀円瑫r(shí)滿足網(wǎng)頁(yè)服務(wù)和接口服務(wù)都能夠支持 HTTP 和 HTTPS,所以這里配的內(nèi)容看起來(lái)重復(fù)率比較高,但其實(shí)細(xì)節(jié)上還是有差異的,首先是每個(gè)路由的名稱是不同的,其次是前文中提到的 tls=true
和 entrypoints
的設(shè)置。
重新啟動(dòng)服務(wù),我們就能夠使用 http://traefik.console.lab.io
或 https://traefik.console.lab.io
訪問(wèn)服務(wù)了。
使用中間件來(lái)改進(jìn)服務(wù)訪問(wèn)體驗(yàn)
在上面的配置中,我們的冗余內(nèi)容其實(shí)挺多的,雖然 Traefik 1.x 之后沒轍,就得這樣。不過(guò),如果我們服務(wù)只需要 HTTPS 訪問(wèn),當(dāng)用戶訪問(wèn) HTTP 協(xié)議的時(shí)候(直接在某些瀏覽器中敲域名,回車后),HTTP 協(xié)議自動(dòng)跳轉(zhuǎn) HTTPS ,上面的配置就能夠精簡(jiǎn)很多了。
我們先定義一個(gè)能夠?qū)⒎?wù)協(xié)議從 HTTP 自動(dòng)切換為 HTTPS 的 Traefik 中間件規(guī)則:
- "traefik.http.middlewares.redir-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redir-https.redirectscheme.permanent=false"
然后,在 HTTP 網(wǎng)頁(yè)服務(wù)的路由上添加這個(gè)中間件規(guī)則:
- "traefik.http.routers.traefik-dashboard.middlewares=redir-https@docker"
重啟服務(wù),當(dāng)我們?cè)L問(wèn) http://traefik.console.lab.io
時(shí),就會(huì)自動(dòng)的跳轉(zhuǎn)到 https://traefik.console.lab.io
啦。
使用 HTTP 協(xié)議自動(dòng)跳轉(zhuǎn)的小技巧
在本文的例子中,我們的服務(wù)同時(shí)提供 HTTP 和 HTTPS 訪問(wèn),分別有兩個(gè)路由:網(wǎng)頁(yè)和 API。
如果用戶直接訪問(wèn) HTTP,瀏覽器會(huì)收到 HTTP 302 響應(yīng),不會(huì)提供服務(wù)內(nèi)容,不會(huì)訪問(wèn)到 HTTP 協(xié)議的 API 路由,所以,我們可以將需要跳轉(zhuǎn) HTTPS 的網(wǎng)頁(yè)服務(wù)會(huì)調(diào)用的 HTTP API 的路由刪除掉:
labels:
- "traefik.http.middlewares.redir-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redir-https.redirectscheme.permanent=false"
- "traefik.http.routers.traefik-dashboard.middlewares=redir-https@docker"
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-secure.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard-secure.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-api-secure.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api-secure.service=api@internal"
因?yàn)槲覀兊木W(wǎng)頁(yè)服務(wù)其實(shí)也并不會(huì)調(diào)用到背后真實(shí)的程序進(jìn)行計(jì)算,所以這里定義真實(shí)的服務(wù)多多少少會(huì)設(shè)計(jì)到 Traefik 尋找和匹配真實(shí)服務(wù)網(wǎng)絡(luò)地址的計(jì)算,我們可以使用 Traefik 內(nèi)部的一個(gè)“魔術(shù)變量”來(lái)進(jìn)行服務(wù)替換,將真實(shí)服務(wù)替換為一個(gè)空的服務(wù)。
labels:
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=noop@internal"
進(jìn)階:完善 Traefik 細(xì)節(jié)配置
搞定 HTTP 和 HTTPS 服務(wù)后,我們來(lái)了解進(jìn)階的配置優(yōu)化方法。
顯式聲明所有靜態(tài)配置參數(shù)
有很多文章會(huì)使用 Traefik 配置文件來(lái)管理服務(wù)行為和能力,就我個(gè)人的使用經(jīng)驗(yàn)和觀點(diǎn)來(lái)看,Traefik 支持的動(dòng)態(tài)配置,我們可以通過(guò)文件來(lái)管理,而靜態(tài)配置,使用本文中提到的參數(shù)化的方式來(lái)管理,更為合理:
- 動(dòng)態(tài)化參數(shù)是可選的,不影響服務(wù)核心能力。
- 動(dòng)態(tài)化參數(shù)可以通過(guò)文件下發(fā),來(lái)完成服務(wù)行為更新。
- 靜態(tài)化參數(shù)和服務(wù)配置在一起,可以避免把靜態(tài)化參數(shù)寫在配置中,服務(wù)啟動(dòng)后,靜態(tài)配置調(diào)整更新,服務(wù)重啟前,配置和服務(wù)行為不一致的問(wèn)題。
3.0 版本的 Traefik 支持的所有的靜態(tài)配置文件,可以參考這個(gè)在線文檔來(lái)使用和調(diào)整。
雖然有很多參數(shù)默認(rèn)是 false
、“空”等我們不設(shè)置也沒問(wèn)題的數(shù)值,但是為了避免 Traefik 程序版本升級(jí),調(diào)整默認(rèn)行為,對(duì)我們?cè)斐煞?wù)行為預(yù)期不符的問(wèn)題,建議將所有的使用到的相關(guān)配置都進(jìn)行顯式的聲明:
command:
- "--global.sendanonymoususage=false"
- "--global.checknewversion=false"
- "--entrypoints.http.address=:80"
- "--entrypoints.https.address=:443"
- "--api=true"
- "--api.insecure=true"
...
創(chuàng)建一個(gè)專用于 Traefik 網(wǎng)絡(luò)服務(wù)發(fā)現(xiàn)的虛擬網(wǎng)絡(luò)
Traefik 默認(rèn)會(huì)使用當(dāng)前 Traefik 應(yīng)用服務(wù)的網(wǎng)絡(luò),來(lái)進(jìn)行服務(wù)發(fā)現(xiàn),簡(jiǎn)單來(lái)說(shuō),我們得將各種要提供公開服務(wù)的軟件都寫在和 Traefik 服務(wù)所在的 docker-compose.yml
中,然后通過(guò) docker compose up
命令,來(lái)管理服務(wù)。
這樣的模式“不科學(xué)”,一來(lái)是可能影響到整體服務(wù),比如錯(cuò)誤調(diào)整和修改了不需要變更的配置,比如 Traefik 的內(nèi)容;二來(lái),服務(wù)想生效,總歸要重啟服務(wù),可能會(huì)造成服務(wù)的短暫中斷;三來(lái),多種不同的服務(wù)配置代碼都寫一塊。代碼配置長(zhǎng)不說(shuō),也不便于管理以及 CI/CD 集成使用。
為了解決這個(gè)問(wèn)題,我們可以使用 Traefik 虛擬網(wǎng)絡(luò)來(lái)解決問(wèn)題,首先是通過(guò)命令行創(chuàng)建一個(gè) Traefik 用來(lái)服務(wù)發(fā)現(xiàn)使用的traefik
虛擬網(wǎng)絡(luò):
docker network create traefik
接著,在需要 Traefik 提供服務(wù)發(fā)現(xiàn)的應(yīng)用中添加下面的字段,讓應(yīng)用在網(wǎng)絡(luò)中:
networks:
- traefik
然后,在 docker-compose.yml
配置的末尾,聲明這個(gè) traefik
網(wǎng)絡(luò)是容器外的獨(dú)立的網(wǎng)絡(luò),而不是根據(jù)當(dāng)前的服務(wù)配置文件,創(chuàng)建一個(gè)只在這個(gè)配置文件作用范圍的虛擬網(wǎng)絡(luò)。
networks:
traefik:
external: true
因?yàn)?Docker 容器中時(shí)常會(huì)有多個(gè)虛擬網(wǎng)絡(luò),所以我們需要在 command
中指定要使用的網(wǎng)絡(luò)名稱:
command:
- "--providers.docker.network=traefik"
為了避免 Traefik 智能的自動(dòng)解析和將所有在 Traefik 網(wǎng)絡(luò)的服務(wù)都嘗試進(jìn)行公開服務(wù),我們可以在命令中添加下面的命令,讓 Traefik 只對(duì)我們?cè)?labels
中聲明了要進(jìn)行服務(wù)注冊(cè)的應(yīng)用提供服務(wù):
"--providers.docker.exposedbydefault=false"
在 labels
中,我們的定義寫法是這樣的:
labels:
- "traefik.enable=true"
這里還有一些小技巧,比如對(duì)計(jì)劃提供發(fā)現(xiàn)服務(wù)的進(jìn)行進(jìn)一步的篩選,或者針對(duì)每一個(gè)服務(wù),調(diào)整要使用 Traefik 進(jìn)行服務(wù)的虛擬網(wǎng)絡(luò),我們未來(lái)的文章里再聊。
調(diào)整容器服務(wù)端口
在上面的文章中,我們?yōu)榱诵形暮?jiǎn)單,使用了端口暴露的簡(jiǎn)寫模式,為了能夠讓 Traefik 在容器中也能夠取到正確的訪問(wèn)客戶端的 IP 地址,我們需要將 ports
調(diào)整為下面的寫法:
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
避免 Traefik 進(jìn)行數(shù)據(jù)上報(bào)
想要避免 Traefik 進(jìn)行數(shù)據(jù)上報(bào),我們可以通過(guò)設(shè)置下面兩個(gè) command
參數(shù)實(shí)現(xiàn):
command:
- "--global.sendanonymoususage=false"
- "--global.checknewversion=false"
如果你還不放心,可以繼續(xù)設(shè)置下面的配置,讓容器訪問(wèn)不到下面的 API 地址:
extra_hosts:
# https://github.com/traefik/traefik/blob/master/pkg/version/version.go#L64
- "update.traefik.io:127.0.0.1"
# https://github.com/containous/traefik/blob/master/pkg/collector/collector.go#L20
- "collect.traefik.io:127.0.0.1"
- "stats.g.doubleclick.net:127.0.0.1"
避免容器服務(wù)日志體積過(guò)大
類似 Traefik 這類 HTTP 服務(wù),在長(zhǎng)時(shí)間運(yùn)行后,都會(huì)積累比較多的日志內(nèi)容,哪怕我們關(guān)閉了日志文件保存功能,只讓服務(wù)在 stdout
中進(jìn)行日志打印,Docker 會(huì)將輸出內(nèi)容都進(jìn)行完整保存。
為了緩解這個(gè)問(wèn)題,我們可以通過(guò)在將日志單純對(duì)外保存之后,使用 Docker 的 Log 配置參數(shù),來(lái)自動(dòng)丟棄過(guò)大的日志輸出:
logging:
driver: "json-file"
options:
max-size: "1m"
比如上面的配置中,將自動(dòng)丟棄超過(guò) 1MB 的日志輸出。
最終的容器配置文件
好了,我們將上面的所有內(nèi)容進(jìn)行合理組合,不難得到最終的配置(相關(guān)代碼已上傳至 soulteary/Home-Network-Note/tree/master/example/traefik-v3.0.0):
version: "3"
services:
traefik:
image: traefik:v3.0.0-beta3
restart: always
ports:
- target: 80
published: 80
protocol: tcp
mode: host
- target: 443
published: 443
protocol: tcp
mode: host
command:
- "--global.sendanonymoususage=false"
- "--global.checknewversion=false"
- "--api=true"
- "--api.dashboard=true"
- "--api.insecure=true"
- "--api.debug=false"
- "--ping=true"
- "--log.level=INFO"
- "--log.format=common"
- "--accesslog=false"
- "--entrypoints.http.address=:80"
- "--entrypoints.https.address=:443"
- "--providers.docker=true"
- "--providers.docker.watch=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.docker.endpoint=unix:///var/run/docker.sock"
- "--providers.docker.useBindPortIP=false"
- "--providers.docker.network=traefik"
- "--providers.file=true"
- "--providers.file.watch=true"
- "--providers.file.directory=/etc/traefik/config"
- "--providers.file.debugloggeneratedtemplate=true"
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
- "traefik.http.middlewares.gzip.compress=true"
- "traefik.http.middlewares.redir-https.redirectscheme.scheme=https"
- "traefik.http.middlewares.redir-https.redirectscheme.permanent=false"
- "traefik.http.routers.traefik-dashboard.middlewares=redir-https@docker"
- "traefik.http.routers.traefik-dashboard-secure.middlewares=gzip@docker"
- "traefik.http.routers.traefik-dashboard-api-secure.middlewares=gzip@docker"
- "traefik.http.routers.traefik-dashboard.entrypoints=http"
- "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard.service=noop@internal"
- "traefik.http.routers.traefik-dashboard-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-secure.rule=Host(`traefik.console.lab.io`)"
- "traefik.http.routers.traefik-dashboard-secure.service=dashboard@internal"
- "traefik.http.routers.traefik-dashboard-api-secure.entrypoints=https"
- "traefik.http.routers.traefik-dashboard-api-secure.tls=true"
- "traefik.http.routers.traefik-dashboard-api-secure.rule=Host(`traefik.console.lab.io`) && PathPrefix(`/api`)"
- "traefik.http.routers.traefik-dashboard-api-secure.service=api@internal"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./certs/:/certs/:ro
- ./config/:/etc/traefik/config/:ro
healthcheck:
test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:8080/ping || exit 1"]
interval: 3s
retries: 10
logging:
driver: "json-file"
options:
max-size: "1m"
networks:
traefik:
external: true
最后
本篇文章就先寫到這里啦,不出意外,應(yīng)該是你能夠在網(wǎng)上找到的最簡(jiǎn)單和最詳盡的關(guān)于 Traefik 的教程啦。
接下來(lái)的這個(gè)系列,我想逐步分享過(guò)去幾年中使用 Traefik 的一些小經(jīng)驗(yàn),包括服務(wù)鑒權(quán)、用戶登陸、自動(dòng)擴(kuò)縮容、服務(wù)監(jiān)控等。
–EOF
本文使用「署名 4.0 國(guó)際 (CC BY 4.0)」許可協(xié)議,歡迎轉(zhuǎn)載、或重新修改使用,但需要注明來(lái)源。 署名 4.0 國(guó)際 (CC BY 4.0)
本文作者: 蘇洋文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-594405.html
創(chuàng)建時(shí)間: 2023年07月18日
統(tǒng)計(jì)字?jǐn)?shù): 22551字
閱讀時(shí)間: 46分鐘閱讀
本文鏈接: https://soulteary.com/2023/07/18/traefik-v3-docker-comprehensive-user-guide-basics.html文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-594405.html
到了這里,關(guān)于Traefik v3.0 Docker 全面使用指南:基礎(chǔ)篇的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!