
Traefik 是一個開源的可以使服務發布變得輕松有趣的邊緣路由器。它負責接收你系統的請求,然后使用合適的組件來對這些請求進行處理。

除了眾多的功能之外,Traefik 的與眾不同之處還在於它會自動發現適合你服務的配置。當 Traefik 在檢查你的服務時,會找到服務的相關信息並找到合適的服務來滿足對應的請求。
Traefik 兼容所有主流的集群技術,比如 Kubernetes,Docker,Docker Swarm,AWS,Mesos,Marathon,等等;並且可以同時處理多種方式。(甚至可以用於在裸機上運行的比較舊的軟件。)
使用 Traefik,不需要維護或者同步一個獨立的配置文件:因為一切都會自動配置,實時操作的(無需重新啟動,不會中斷連接)。使用 Traefik,你可以花更多的時間在系統的開發和新功能上面,而不是在配置和維護工作狀態上面花費大量時間。
核心概念
Traefik 是一個邊緣路由器,是你整個平台的大門,攔截並路由每個傳入的請求:它知道所有的邏輯和規則,這些規則確定哪些服務處理哪些請求;傳統的反向代理需要一個配置文件,其中包含路由到你服務的所有可能路由,而 Traefik 會實時檢測服務並自動更新路由規則,可以自動服務發現。

首先,當啟動 Traefik 時,需要定義 entrypoints(入口點),然后,根據連接到這些 entrypoints 的路由來分析傳入的請求,來查看他們是否與一組規則相匹配,如果匹配,則路由可能會將請求通過一系列中間件轉換過后再轉發到你的服務上去。在了解 Traefik 之前有幾個核心概念我們必須要了解:
- Providers 用來自動發現平台上的服務,可以是編排工具、容器引擎或者 key-value 存儲等,比如 Docker、Kubernetes、File
- Entrypoints 監聽傳入的流量(端口等…),是網絡入口點,它們定義了接收請求的端口(HTTP 或者 TCP)。
- Routers 分析請求(host, path, headers, SSL, …),負責將傳入請求連接到可以處理這些請求的服務上去。
- Services 將請求轉發給你的應用(load balancing, …),負責配置如何獲取最終將處理傳入請求的實際服務。
- Middlewares 中間件,用來修改請求或者根據請求來做出一些判斷(authentication, rate limiting, headers, ...),中間件被附件到路由上,是一種在請求發送到你的服務之前(或者在服務的響應發送到客戶端之前)調整請求的一種方法。
安裝
由於 Traefik 2.X 版本和之前的 1.X 版本不兼容,我們這里選擇功能更加強大的 2.X 版本來和大家進行講解,我們這里使用的是最新的鏡像 traefik:2.3.6。
在 Traefik 中的配置可以使用兩種不同的方式:
- 動態配置:完全動態的路由配置
- 靜態配置:啟動配置
靜態配置中的元素(這些元素不會經常更改)連接到 providers 並定義 Treafik 將要監聽的 entrypoints。
在 Traefik 中有三種方式定義靜態配置:在配置文件中、在命令行參數中、通過環境變量傳遞
動態配置包含定義系統如何處理請求的所有配置內容,這些配置是可以改變的,而且是無縫熱更新的,沒有任何請求中斷或連接損耗。
這里我們還是使用 Helm 來快速安裝 traefik,首先獲取 Helm Chart 包:
➜ git clone https://github.com/traefik/traefik-helm-chart
創建一個定制的 values 配置文件:
# values-prod.yaml
# Create an IngressRoute for the dashboard
ingressRoute:
dashboard:
enabled: false # 禁用helm中渲染的dashboard,我們自己手動創建
# Configure ports
ports:
web:
port: 8000
hostPort: 80 # 使用 hostport 模式
# Use nodeport if set. This is useful if you have configured Traefik in a
# LoadBalancer
# nodePort: 32080
# Port Redirections
# Added in 2.2, you can make permanent redirects via entrypoints.
# https://docs.traefik.io/routing/entrypoints/#redirection
# redirectTo: websecure
websecure:
port: 8443
hostPort: 443 # 使用 hostport 模式
# Options for the main traefik service, where the entrypoints traffic comes
# from.
service: # 使用 hostport 模式就不需要Service了
enabled: false
# Logs
# https://docs.traefik.io/observability/logs/
logs:
general:
level: DEBUG
tolerations: # kubeadm 安裝的集群默認情況下master是有污點,需要容忍這個污點才可以部署
- key: "node-role.kubernetes.io/master"
operator: "Equal"
effect: "NoSchedule"
nodeSelector: # 固定到master1節點(該節點才可以訪問外網)
kubernetes.io/hostname: "master1"
這里我們使用 hostport 模式將 Traefik 固定到 master1 節點上,因為只有這個節點有外網 IP,所以我們這里 master1 是作為流量的入口點。直接使用上面的 values 文件安裝 traefik:
➜ helm install --namespace kube-system traefik ./traefik -f ./values-prod.yaml
NAME: traefik
LAST DEPLOYED: Thu Dec 24 11:23:51 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
➜ kubectl get pods -n kube-system -l app.kubernetes.io/name=traefik
NAME READY STATUS RESTARTS AGE
traefik-78ff486794-64jbd 1/1 Running 0 3m15s
安裝完成后我們可以通過查看 Pod 的資源清單來了解 Traefik 的運行方式:
➜ kubectl get pods traefik-78ff486794-64jbd -n kube-system -o yaml
apiVersion: v1
kind: Pod
metadata:
......
spec:
containers:
- args:
- --global.checknewversion
- --global.sendanonymoususage
- --entryPoints.traefik.address=:9000/tcp
- --entryPoints.web.address=:8000/tcp
- --entryPoints.websecure.address=:8443/tcp
- --api.dashboard=true
- --ping=true
- --providers.kubernetescrd
- --providers.kubernetesingress
- --accesslog=true
- --accesslog.fields.defaultmode=keep
- --accesslog.fields.headers.defaultmode=drop
...
其中 entryPoints 屬性定義了 web 和 websecure 這兩個入口點的,並開啟 kubernetesingress 和 kubernetescrd 這兩個 provider,也就是我們可以使用 Kubernetes 原本的 Ingress 資源對象,也可以使用 Traefik 自己擴展的 IngressRoute 這樣的 CRD 資源對象。
我們可以首先創建一個用於 Dashboard 訪問的 IngressRoute 資源清單:
➜ cat <<EOF | kubectl apply -f -
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: traefik-dashboard
namespace: kube-system
spec:
entryPoints:
- web
routes:
- match: Host(`traefik.qikqiak.com`) # 指定域名
kind: Rule
services:
- name: api@internal
kind: TraefikService # 引用另外的 Traefik Service
EOF
➜ kubectl get ingressroute -n kube-system
NAME AGE
traefik-dashboard 19m
其中的 TraefikService 是 Traefik Service 的一個 CRD 實現,這里我們使用的 api@internal 這個 TraefikService,表示我們訪問的是 Traefik 內置的應用服務。
部署完成后我們可以通過在本地 /etc/hosts 中添加上域名 traefik.qikqiak.com 的映射即可訪問 Traefik 的 Dashboard 頁面了:

ACME
Traefik 通過擴展 CRD 的方式來擴展 Ingress 的功能,除了默認的用 Secret 的方式可以支持應用的 HTTPS 之外,還支持自動生成 HTTPS 證書。
比如現在我們有一個如下所示的 whoami 應用:
kind: Deployment
apiVersion: apps/v1
metadata:
name: whoami
labels:
app: whoami
spec:
replicas: 2
selector:
matchLabels:
app: whoami
template:
metadata:
labels:
app: whoami
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- name: web
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: whoami
然后定義一個 IngressRoute 對象:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: simpleingressroute
spec:
entryPoints:
- web
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/notls`)
kind: Rule
services:
- name: whoami
port: 80
通過 entryPoints 指定了我們這個應用的入口點是 web,也就是通過 80 端口訪問,然后訪問的規則就是要匹配 who.qikqiak.com 這個域名,並且具有 /notls 的路徑前綴的請求才會被 whoami 這個 Service 所匹配。我們可以直接創建上面的幾個資源對象,然后對域名做對應的解析后,就可以訪問應用了:

在 IngressRoute 對象中我們定義了一些匹配規則,這些規則在 Traefik 中有如下定義方式:

如果我們需要用 HTTPS 來訪問我們這個應用的話,就需要監聽 websecure 這個入口點,也就是通過 443 端口來訪問,同樣用 HTTPS 訪問應用必然就需要證書,這里我們用 openssl 來創建一個自簽名的證書:
➜ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=who.qikqiak.com"
然后通過 Secret 對象來引用證書文件:
# 要注意證書文件名稱必須是 tls.crt 和 tls.key
➜ kubectl create secret tls who-tls --cert=tls.crt --key=tls.key
secret/who-tls created
這個時候我們就可以創建一個 HTTPS 訪問應用的 IngressRoute 對象了:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls
spec:
entryPoints:
- websecure
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
kind: Rule
services:
- name: whoami
port: 80
tls:
secretName: who-tls
創建完成后就可以通過 HTTPS 來訪問應用了,由於我們是自簽名的證書,所以證書是不受信任的:

除了手動提供證書的方式之外 Traefik 同樣也支持使用 Let’s Encrypt 自動生成證書,要使用 Let’s Encrypt 來進行自動化 HTTPS,就需要首先開啟 ACME,開啟 ACME 需要通過靜態配置的方式,也就是說可以通過環境變量、啟動參數等方式來提供。
ACME 有多種校驗方式 tlsChallenge、httpChallenge 和 dnsChallenge 三種驗證方式,之前更常用的是 http 這種驗證方式,關於這幾種驗證方式的使用可以查看文檔:https://www.qikqiak.com/traefik-book/https/acme/ 了解他們之間的區別。要使用 tls 校驗方式的話需要保證 Traefik 的 443 端口是可達的,dns 校驗方式可以生成通配符的證書,只需要配置上 DNS 解析服務商的 API 訪問密鑰即可校驗。我們這里用 DNS 校驗的方式來為大家說明如何配置 ACME。
我們可以重新修改 Helm 安裝的 values 配置文件,添加如下所示的定制參數:
# values-prod.yaml
additionalArguments:
# 使用 dns 驗證方式
- --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
# 先使用staging環境進行驗證,驗證成功后再使用移除下面一行的配置
# - --certificatesResolvers.ali.acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
# 郵箱配置
- --certificatesResolvers.ali.acme.email=ych_1024@163.com
# 保存 ACME 證書的位置
- --certificatesResolvers.ali.acme.storage=/data/acme.json
envFrom:
- secretRef:
name: traefik-alidns-secret
# ALICLOUD_ACCESS_KEY
# ALICLOUD_SECRET_KEY
# ALICLOUD_REGION_ID
persistence:
enabled: true # 開啟持久化
accessMode: ReadWriteOnce
size: 128Mi
path: /data
# 由於上面持久化了ACME的數據,需要重新配置下面的安全上下文
securityContext:
readOnlyRootFilesystem: false
runAsGroup: 0
runAsUser: 0
runAsNonRoot: false
這樣我們可以通過設置 --certificatesresolvers.ali.acme.dnschallenge.provider=alidns 參數來指定指定阿里雲的 DNS 校驗,要使用阿里雲的 DNS 校驗我們還需要配置3個環境變量:ALICLOUD_ACCESS_KEY、ALICLOUD_SECRET_KEY、ALICLOUD_REGION_ID,分別對應我們平時開發阿里雲應用的時候的密鑰,可以登錄阿里雲后台獲取,由於這是比較私密的信息,所以我們用 Secret 對象來創建:
➜ kubectl create secret generic traefik-alidns-secret --from-literal=ALICLOUD_ACCESS_KEY=<aliyun ak> --from-literal=ALICLOUD_SECRET_KEY=<aliyun sk> --from-literal=ALICLOUD_REGION_ID=cn-beijing -n kube-system
創建完成后將這個 Secret 通過環境變量配置到 Traefik 的應用中,還有一個值得注意的是驗證通過的證書我們這里存到 /data/acme.json 文件中,我們一定要將這個文件持久化,否則每次 Traefik 重建后就需要重新認證,而 Let’s Encrypt 本身校驗次數是有限制的。所以我們在 values 中重新開啟了數據持久化,不過開啟過后需要我們提供一個可用的 PV 存儲,由於我們將 Traefik 固定到 master1 節點上的,所以我們可以創建一個 hostpath 類型的 PV(后面會詳細講解):
➜ cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: traefik
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 128Mi
hostPath:
path: /data/k8s/traefik
EOF
然后使用如下所示的命令更新 Traefik:
➜ helm upgrade --install traefik --namespace=kube-system ./traefik -f ./values-prod.yaml
Release "traefik" has been upgraded. Happy Helming!
NAME: traefik
LAST DEPLOYED: Thu Dec 24 14:32:04 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 2
TEST SUITE: None
更新完成后現在我們來修改上面我們的 whoami 應用:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls
spec:
entryPoints:
- websecure
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
kind: Rule
services:
- name: whoami
port: 80
tls:
certResolver: ali
domains:
- main: "*.qikqiak.com"
其他的都不變,只需要將 tls 部分改成我們定義的 ali 這個證書解析器,如果我們想要生成一個通配符的域名證書的話可以定義 domains 參數來指定,然后更新 IngressRoute 對象,這個時候我們再去用 HTTPS 訪問我們的應用(當然需要將域名在阿里雲 DNS 上做解析):

我們可以看到訪問應用已經是受瀏覽器信任的證書了,查看證書我們還可以發現該證書是一個通配符的證書。
中間件
中間件是 Traefik2.x 中一個非常有特色的功能,我們可以根據自己的各種需求去選擇不同的中間件來滿足服務,Traefik 官方已經內置了許多不同功能的中間件,其中一些可以修改請求頭信息,一些負責重定向,一些添加身份驗證等等,而且中間件還可以通過鏈式組合的方式來適用各種情況。

同樣比如上面我們定義的 whoami 這個應用,我們可以通過 https://who.qikqiak.com/tls 來訪問到應用,但是如果我們用 http 來訪問的話呢就不行了,就會404了,因為我們根本就沒有監聽80端口這個入口點,所以要想通過 http 來訪問應用的話自然我們需要監聽下 web 這個入口點:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls-http
spec:
entryPoints:
- web
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
kind: Rule
services:
- name: whoami
port: 80
注意這里我們創建的 IngressRoute 的 entryPoints 是 web,然后創建這個對象,這個時候我們就可以通過 http 訪問到這個應用了。
但是我們如果只希望用戶通過 https 來訪問應用的話呢?按照以前的知識,我們是不是可以讓 http 強制跳轉到 https 服務去,對的,在 Traefik 中也是可以配置強制跳轉的,只是這個功能現在是通過中間件來提供的了。如下所示,我們使用 redirectScheme 中間件來創建提供強制跳轉服務:
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: redirect-https
spec:
redirectScheme:
scheme: https
然后將這個中間件附加到 http 的服務上面去,因為 https 的不需要跳轉:
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: ingressroutetls-http
spec:
entryPoints:
- web
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/tls`)
kind: Rule
services:
- name: whoami
port: 80
middlewares:
- name: redirect-https
這個時候我們再去訪問 http 服務可以發現就會自動跳轉到 https 去了。關於更多中間件的用法可以查看文檔 Traefik Docs。
Traefik Pilot
雖然 Traefik 已經默認實現了很多中間件,可以滿足大部分我們日常的需求,但是在實際工作中,用戶仍然還是有自定義中間件的需求,這就 Traefik Pilot 的功能了。

Traefik Pilot 是一個 SaaS 平台,和 Traefik 進行鏈接來擴展其功能,它提供了很多功能,通過一個全局控制面板和 Dashboard 來增強對 Traefik 的觀測和控制:
- Traefik 代理和代理組的網絡活動的指標
- 服務健康問題和安全漏洞警報
- 擴展 Traefik 功能的插件
在 Traefik 可以使用 Traefik Pilot 的功能之前,必須先連接它們,我們只需要對 Traefik 的靜態配置進行少量更改即可。
Traefik 代理必須要能訪問互聯網才能連接到 Traefik Pilot,通過 HTTPS 在 443 端口上建立連接。
首先我們需要在 Traefik Pilot 主頁上(https://pilot.traefik.io/)創建一個帳戶,注冊新的 Traefik 實例並開始使用 Traefik Pilot。登錄后,可以通過選擇 Register New Traefik Instance來創建新實例。

另外,當我們的 Traefik 尚未連接到 Traefik Pilot 時,Traefik Web UI 中將出現一個響鈴圖標,我們可以選擇 Connect with Traefik Pilot 導航到 Traefik Pilot UI 進行操作。

登錄完成后,Traefik Pilot 會生成一個新實例的令牌,我們需要將這個 Token 令牌添加到 Traefik 靜態配置中。

我們這里就是在 values-prod.yaml 文件中啟用 Pilot 的配置:
# values-prod.yaml
# Activate Pilot integration
pilot:
enabled: true
token: "e079ea6e-536a-48c6-b3e3-f7cfaf94f477"
然后重新更新 Traefik:
➜ helm upgrade --install traefik --namespace=kube-system ./traefik -f ./values-prod.yaml
更新完成后,我們在 Traefik 的 Web UI 中就可以看到 Traefik Pilot UI 相關的信息了。

接下來我們就可以在 Traefik Pilot 的插件頁面選擇我們想要使用的插件,比如我們這里使用 Demo Plugin 這個插件。

點擊右上角的 Install Plugin 按鈕安裝插件會彈出一個對話框提示我們如何安裝。

首先我們需要將當前 Traefik 注冊到 Traefik Pilot(已完成),然后需要以靜態配置的方式添加這個插件到 Traefik 中,這里我們同樣更新 values-prod.yaml 文件中的 Values 值即可:
# values-prod.yaml
# Activate Pilot integration
pilot:
enabled: true
token: "e079ea6e-536a-48c6-b3e3-f7cfaf94f477"
additionalArguments:
# 添加 demo plugin 的支持
- --experimental.plugins.plugindemo.modulename=github.com/traefik/plugindemo
- --experimental.plugins.plugindemo.version=v0.2.1
# 其他配置
同樣重新更新 Traefik:
➜ helm upgrade --install traefik --namespace=kube-system ./traefik -f ./values-prod.yaml
更新完成后創建一個如下所示的 Middleware 對象:
➜ cat <<EOF | kubectl apply -f -
apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: myplugin
spec:
plugin:
plugindemo: # 插件名
Headers:
X-Demo: test
Foo: bar
EOF
然后添加到上面的 whoami 應用的 IngressRoute 對象中去:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: simpleingressroute
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`who.qikqiak.com`) && PathPrefix(`/notls`)
kind: Rule
services:
- name: whoami # K8s Service
port: 80
middlewares:
- name: myplugin # 使用上面新建的 middleware
更新完成后,當我們去訪問 http://who.qikqiak.com/notls 的時候就可以看到新增了兩個上面插件中定義的兩個 Header。

當然除了使用 Traefik Pilot 上開發者提供的插件之外,我們也可以根據自己的需求自行開發自己的插件,可以自行參考文檔:https://doc.traefik.io/traefik-pilot/plugins/plugin-dev/。
灰度發布
Traefik2.0 的一個更強大的功能就是灰度發布,灰度發布我們有時候也會稱為金絲雀發布(Canary),主要就是讓一部分測試的服務也參與到線上去,經過測試觀察看是否符號上線要求。

比如現在我們有兩個名為 appv1 和 appv2 的服務,我們希望通過 Traefik 來控制我們的流量,將 3/4 的流量路由到 appv1,1/4 的流量路由到 appv2 去,這個時候就可以利用 Traefik2.0 中提供的帶權重的輪詢(WRR)來實現該功能,首先在 Kubernetes 集群中部署上面的兩個服務。為了對比結果我們這里提供的兩個服務一個是 whoami,一個是 nginx,方便測試。
appv1 服務的資源清單如下所示:(appv1.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: appv1
spec:
selector:
matchLabels:
app: appv1
template:
metadata:
labels:
use: test
app: appv1
spec:
containers:
- name: whoami
image: containous/whoami
ports:
- containerPort: 80
name: portv1
---
apiVersion: v1
kind: Service
metadata:
name: appv1
spec:
selector:
app: appv1
ports:
- name: http
port: 80
targetPort: portv1
appv2 服務的資源清單如下所示:(appv2.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: appv2
spec:
selector:
matchLabels:
app: appv2
template:
metadata:
labels:
use: test
app: appv2
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: portv2
---
apiVersion: v1
kind: Service
metadata:
name: appv2
spec:
selector:
app: appv2
ports:
- name: http
port: 80
targetPort: portv2
直接創建上面兩個服務:
➜ kubectl apply -f appv1.yaml
➜ kubectl apply -f appv2.yaml
# 通過下面的命令可以查看服務是否運行成功
➜ kubectl get pods -l use=test
NAME READY STATUS RESTARTS AGE
appv1-58f856c665-shm9j 1/1 Running 0 12s
appv2-ff5db55cf-qjtrf 1/1 Running 0 12s
在 Traefik2.1 中新增了一個 TraefikService 的 CRD 資源,我們可以直接利用這個對象來配置 WRR,之前的版本需要通過 File Provider,比較麻煩,新建一個描述 WRR 的資源清單:(wrr.yaml)
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: app-wrr
spec:
weighted:
services:
- name: appv1
weight: 3 # 定義權重
port: 80
kind: Service # 可選,默認就是 Service
- name: appv2
weight: 1
port: 80
然后為我們的灰度發布的服務創建一個 IngressRoute 資源對象:(ingressroute.yaml)
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: wrringressroute
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`wrr.qikqiak.com`)
kind: Rule
services:
- name: app-wrr
kind: TraefikService
不過需要注意的是現在我們配置的 Service 不再是直接的 Kubernetes 對象了,而是上面我們定義的 TraefikService 對象,直接創建上面的兩個資源對象,這個時候我們對域名 wrr.qikqiak.com 做上解析,去瀏覽器中連續訪問 4 次,我們可以觀察到 appv1 這應用會收到 3 次請求,而 appv2 這個應用只收到 1 次請求,符合上面我們的 3:1 的權重配置。

流量復制
除了灰度發布之外,Traefik 2.0 還引入了流量鏡像服務,是一種可以將流入流量復制並同時將其發送給其他服務的方法,鏡像服務可以獲得給定百分比的請求同時也會忽略這部分請求的響應。

現在我們部署兩個 whoami 的服務,資源清單文件如下所示:
apiVersion: v1
kind: Service
metadata:
name: v1
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: v1
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: v1
labels:
app: v1
spec:
selector:
matchLabels:
app: v1
template:
metadata:
labels:
app: v1
spec:
containers:
- name: v1
image: nginx
ports:
- name: web
containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: v2
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: v2
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: v2
labels:
app: v2
spec:
selector:
matchLabels:
app: v2
template:
metadata:
labels:
app: v2
spec:
containers:
- name: v2
image: nginx
ports:
- name: web
containerPort: 80
直接創建上面的資源對象:
➜ kubectl get pods
NAME READY STATUS RESTARTS AGE
v1-77cfb86999-wfbl2 1/1 Running 0 94s
v2-6f45d498b7-g6qjt 1/1 Running 0 91s
➜ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
v1 ClusterIP 10.96.218.173 <none> 80/TCP 99s
v2 ClusterIP 10.99.98.48 <none> 80/TCP 96s
現在我們創建一個 IngressRoute 對象,將服務 v1 的流量復制 50% 到服務 v2,如下資源對象所示:(mirror-ingress-route.yaml)
apiVersion: traefik.containo.us/v1alpha1
kind: TraefikService
metadata:
name: app-mirror
spec:
mirroring:
name: v1 # 發送 100% 的請求到 K8S 的 Service "v1"
port: 80
mirrors:
- name: v2 # 然后復制 50% 的請求到 v2
percent: 50
port: 80
---
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
name: mirror-ingress-route
namespace: default
spec:
entryPoints:
- web
routes:
- match: Host(`mirror.qikqiak.com`)
kind: Rule
services:
- name: app-mirror
kind: TraefikService # 使用聲明的 TraefikService 服務,而不是 K8S 的 Service
然后直接創建這個資源對象即可:
➜ kubectl apply -f mirror-ingress-route.yaml
ingressroute.traefik.containo.us/mirror-ingress-route created
traefikservice.traefik.containo.us/mirroring-example created
這個時候我們在瀏覽器中去連續訪問4次 mirror.qikqiak.com 可以發現有一半的請求也出現在了 v2 這個服務中:

TCP
另外 Traefik2.x 已經支持了 TCP 服務的,下面我們以 mongo 為例來了解下 Traefik 是如何支持 TCP 服務的。
簡單 TCP 服務
首先部署一個普通的 mongo 服務,資源清單文件如下所示:(mongo.yaml)
apiVersion: apps/v1
kind: Deployment
metadata:
name: mongo-traefik
labels:
app: mongo-traefik
spec:
selector:
matchLabels:
app: mongo-traefik
template:
metadata:
labels:
app: mongo-traefik
spec:
containers:
- name: mongo
image: mongo:4.0
ports:
- containerPort: 27017
---
apiVersion: v1
kind: Service
metadata:
name: mongo-traefik
spec:
selector:
app: mongo-traefik
ports:
- port: 27017
直接創建 mongo 應用:
➜ kubectl apply -f mongo.yaml
deployment.apps/mongo-traefik created
service/mongo-traefik created
創建成功后就可以來為 mongo 服務配置一個路由了。由於 Traefik 中使用 TCP 路由配置需要 SNI,而 SNI 又是依賴 TLS 的,所以我們需要配置證書才行,如果沒有證書的話,我們可以使用通配符 * 進行配置,我們這里創建一個 IngressRouteTCP 類型的 CRD 對象(前面我們就已經安裝了對應的 CRD 資源):(mongo-ingressroute-tcp.yaml)
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`*`)
services:
- name: mongo-traefik
port: 27107
要注意的是這里的 entryPoints 部分,是根據我們啟動的 Traefik 的靜態配置中的 entryPoints 來決定的,我們當然可以使用前面我們定義得 80 和 443 這兩個入口點,但是也可以可以自己添加一個用於 mongo 服務的專門入口點,更新 values-prod.yaml 文件,新增 mongo 這個入口點:
# values-prod.yaml
# Configure ports
ports:
web:
port: 8000
hostPort: 80
websecure:
port: 8443
hostPort: 443
mongo:
port: 27017
hostPort: 27017
然后更新 Traefik 即可:
➜ helm upgrade --install traefik --namespace=kube-system ./traefik -f ./values-prod.yaml
這里給入口點添加 hostPort 是為了能夠通過節點的端口訪問到服務,關於 entryPoints 入口點的更多信息,可以查看文檔 entrypoints 了解更多信息。
然后更新 Traefik 后我們就可以直接創建上面的資源對象:
➜ mongo-ingressroute-tcp.yaml
ingressroutetcp.traefik.containo.us/mongo-traefik-tcp created
創建完成后,同樣我們可以去 Traefik 的 Dashboard 頁面上查看是否生效:

然后我們配置一個域名 mongo.local 解析到 Traefik 所在的節點,然后通過 27017 端口來連接 mongo 服務:
➜ mongo --host mongo.local --port 27017
mongo(75243,0x1075295c0) malloc: *** malloc_zone_unregister() failed for 0x7fffa56f4000
MongoDB shell version: 2.6.1
connecting to: mongo.local:27017/test
> show dbs
admin 0.000GB
config 0.000GB
local 0.000GB
到這里我們就完成了將 mongo(TCP)服務暴露給外部用戶了。
帶 TLS 證書的 TCP
上面我們部署的 mongo 是一個普通的服務,然后用 Traefik 代理的,但是有時候為了安全 mongo 服務本身還會使用 TLS 證書的形式提供服務,下面是用來生成 mongo tls 證書的腳本文件:(generate-certificates.sh)
#!/bin/bash
#
# From https://medium.com/@rajanmaharjan/secure-your-mongodb-connections-ssl-tls-92e2addb3c89
set -eu -o pipefail
DOMAINS="${1}"
CERTS_DIR="${2}"
[ -d "${CERTS_DIR}" ]
CURRENT_DIR="$(cd "$(dirname "${0}")" && pwd -P)"
GENERATION_DIRNAME="$(echo "${DOMAINS}" | cut -d, -f1)"
rm -rf "${CERTS_DIR}/${GENERATION_DIRNAME:?}" "${CERTS_DIR}/certs"
echo "== Checking Requirements..."
command -v go >/dev/null 2>&1 || echo "Golang is required"
command -v minica >/dev/null 2>&1 || go get github.com/jsha/minica >/dev/null
echo "== Generating Certificates for the following domains: ${DOMAINS}..."
cd "${CERTS_DIR}"
minica --ca-cert "${CURRENT_DIR}/minica.pem" --ca-key="${CURRENT_DIR}/minica-key.pem" --domains="${DOMAINS}"
mv "${GENERATION_DIRNAME}" "certs"
cat certs/key.pem certs/cert.pem > certs/mongo.pem
echo "== Certificates Generated in the directory ${CERTS_DIR}/certs"
將上面證書放置到 certs 目錄下面,然后我們新建一個 02-tls-mongo 的目錄,在該目錄下面執行如下命令來生成證書:
➜ bash ../certs/generate-certificates.sh mongo.local .
== Checking Requirements...
== Generating Certificates for the following domains: mongo.local...
最后的目錄如下所示,在 02-tls-mongo 目錄下面會生成包含證書的 certs 目錄:
➜ tree .
.
├── 01-mongo
│ ├── mongo-ingressroute-tcp.yaml
│ └── mongo.yaml
├── 02-tls-mongo
│ └── certs
│ ├── cert.pem
│ ├── key.pem
│ └── mongo.pem
└── certs
├── generate-certificates.sh
├── minica-key.pem
└── minica.pem
在 02-tls-mongo/certs 目錄下面執行如下命令通過 Secret 來包含證書內容:
➜ kubectl create secret tls traefik-mongo-certs --cert=cert.pem --key=key.pem
secret/traefik-mongo-certs created
然后重新更新 IngressRouteTCP 對象,增加 TLS 配置:
apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`mongo.local`)
services:
- name: mongo-traefik
port: 27017
tls:
secretName: traefik-mongo-certs
同樣更新后,現在我們直接去訪問應用就會被 hang 住,因為我們沒有提供證書:
➜ mongo --host mongo.local --port 27017
MongoDB shell version: 2.6.1
connecting to: mongo1.local:27017/test
這個時候我們可以帶上證書來進行連接:
➜ mongo --host mongo.local --port 27017 --ssl --sslCAFile=../certs/minica.pem --sslPEMKeyFile=./certs/mongo.pem
MongoDB shell version v4.0.3
connecting to: mongodb://mongo.local:27017/
Implicit session: session { "id" : UUID("e7409ef6-8ebe-4c5a-9642-42059bdb477b") }
MongoDB server version: 4.0.14
......
> show dbs;
admin 0.000GB
config 0.000GB
local 0.000GB
可以看到現在就可以連接成功了,這樣就完成了一個使用 TLS 證書代理 TCP 服務的功能,這個時候如果我們使用其他的域名去進行連接就會報錯了,因為現在我們指定的是特定的 HostSNI:
➜ mongo --host mongo.k8s.local --port 27017 --ssl --sslCAFile=../certs/minica.pem --sslPEMKeyFile=./certs/mongo.pem
MongoDB shell version v4.0.3
connecting to: mongodb://mongo.k8s.local:27017/
2019-12-29T15:03:52.424+0800 E NETWORK [js] SSL peer certificate validation failed: Certificate trust failure: CSSMERR_TP_NOT_TRUSTED; connection rejected
2019-12-29T15:03:52.429+0800 E QUERY [js] Error: couldn't connect to server mongo.qikqiak.com:27017, connection attempt failed: SSLHandshakeFailed: SSL peer certificate validation failed: Certificate trust failure: CSSMERR_TP_NOT_TRUSTED; connection rejected :
connect@src/mongo/shell/mongo.js:257:13
@(connect):1:6
exception: connect failed
Traefik 還有很多功能,比如 UDP 也支持了,特別是強大的中間件和自定義插件的功能,為我們提供了不斷擴展其功能的能力,我們完成可以根據自己的需求進行二次開發。
