一文搞懂 Traefik2.1 的使用

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

除了眾多的功能之外,Traefik 的與眾不同之處還在於它會自動發現適合你服務的配置。當 Traefik 在檢查你的服務時,會找到服務的相關信息並找到合適的服務來滿足對應的請求。

Traefik 兼容所有主流的集群技術,比如 Kubernetes,Docker,Docker Swarm,AWS,Mesos,Marathon,等等;並且可以同時處理多種方式。(甚至可以用於在裸機上運行的比較舊的軟件。)

一文搞懂 Traefik2.1 的使用

traefik architecture

使用 Traefik,不需要維護或者同步一個獨立的配置文件:因為一切都會自動配置,實時操作的(無需重新啟動,不會中斷連接)。使用 Traefik,你可以花更多的時間在系統的開發和新功能上面,而不是在配置和維護工作狀態上面花費大量時間。

核心概念

Traefik 是一個邊緣路由器,是你整個平臺的大門,攔截並路由每個傳入的請求:它知道所有的邏輯和規則,這些規則確定哪些服務處理哪些請求;傳統的反向代理需要一個配置文件,其中包含路由到你服務的所有可能路由,而 Traefik 會實時檢測服務並自動更新路由規則,可以自動服務發現。

一文搞懂 Traefik2.1 的使用

traefik architecture overview


首先,當啟動 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.1.1。

在 Traefik 中的配置可以使用兩種不同的方式:

  • 動態配置:完全動態的路由配置
  • 靜態配置:啟動配置

靜態配置中的元素(這些元素不會經常更改)連接到 providers 並定義 Treafik 將要監聽的 entrypoints。

在 Traefik 中有三種方式定義靜態配置:在配置文件中、在命令行參數中、通過環境變量傳遞

動態配置包含定義系統如何處理請求的所有配置內容,這些配置是可以改變的,而且是無縫熱更新的,沒有任何請求中斷或連接損耗。

安裝 Traefik 到 Kubernetes 集群中的資源清單文件我這裡提前準備好了,直接執行下面的安裝命令即可:

<code>$ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/crd.yaml
$ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/rbac.yaml
$ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/deployment.yaml
$ kubectl apply -f https://www.qikqiak.com/k8strain/network/manifests/traefik/dashboard.yaml/<code>

其中 deployment.yaml 我這裡是固定到 master 節點上的,如果你需要修改可以下載下來做相應的修改即可。我們這裡是通過命令行參數來做的靜態配置:

<code>args:
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --api=true
- --api.dashboard=true
- --ping=true
- --providers.kubernetesingress
- --providers.kubernetescrd
- --log.level=INFO
- --accesslog/<code>

其中前兩項配置是來定義 web 和 websecure 這兩個入口點的,--api=true 開啟,=,就會創建一個名為 api@internal 的特殊 service,在 dashboard 中可以直接使用這個 service 來訪問,然後其他比較重要的就是開啟 kubernetesingress 和 kubernetescrd 這兩個 provider。

dashboard.yaml 中定義的是訪問 dashboard 的資源清單文件,可以根據自己的需求修改。

<code>$ kubectl get pods -n kube-system                       
NAME READY STATUS RESTARTS AGE
traefik-867bd6b9c-lbrlx 1/1 Running 0 6m17s
......
$ kubectl get ingressroute
NAME AGE
traefik-dashboard 30m/<code>

部署完成後我們可以通過在本地 /etc/hosts 中添加上域名 traefik.domain.com 的映射即可訪問 Traefik 的 Dashboard 頁面了:

一文搞懂 Traefik2.1 的使用

traefik dashboard demo


ACME

Traefik 通過擴展 CRD 的方式來擴展 Ingress 的功能,除了默認的用 Secret 的方式可以支持應用的 HTTPS 之外,還支持自動生成 HTTPS 證書。

比如現在我們有一個如下所示的 whoami 應用:

<code>apiVersion: v1
kind: Service
metadata:
name: whoami
spec:
ports:
- protocol: TCP
name: web
port: 80
selector:
app: 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/<code>

然後定義一個 IngressRoute 對象:

<code>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/<code>

通過 entryPoints 指定了我們這個應用的入口點是 web,也就是通過 80 端口訪問,然後訪問的規則就是要匹配 who.qikqiak.com 這個域名,並且具有 /notls 的路徑前綴的請求才會被 whoami 這個 Service 所匹配。我們可以直接創建上面的幾個資源對象,然後對域名做對應的解析後,就可以訪問應用了:

一文搞懂 Traefik2.1 的使用

traefik whoami http demo


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

一文搞懂 Traefik2.1 的使用

traefik route matcher


如果我們需要用 HTTPS 來訪問我們這個應用的話,就需要監聽 websecure 這個入口點,也就是通過 443 端口來訪問,同樣用 HTTPS 訪問應用必然就需要證書,這裡我們用 openssl 來創建一個自簽名的證書:

<code>$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls.key -out tls.crt -subj "/CN=who.qikqiak.com"/<code>

然後通過 Secret 對象來引用證書文件:

<code># 要注意證書文件名稱必須是 tls.crt 和 tls.key 

$ kubectl create secret tls who-tls --cert=tls.crt --key=tls.key
secret/who-tls created/<code>

這個時候我們就可以創建一個 HTTPS 訪問應用的 IngressRoute 對象了:

<code>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/<code>

創建完成後就可以通過 HTTPS 來訪問應用了,由於我們是自簽名的證書,所以證書是不受信任的:

一文搞懂 Traefik2.1 的使用

traefik whoami https demo


除了手動提供證書的方式之外 Traefik 還支持使用 Let’s Encrypt 自動生成證書,要使用 Let’s Encrypt 來進行自動化 HTTPS,就需要首先開啟 ACME,開啟 ACME 需要通過靜態配置的方式,也就是說可以通過環境變量、啟動參數等方式來提供,我們這裡還是直接使用啟動參數的形式來開啟,在 Traefik 的部署文件中添加如下命令行參數:

<code>args:
......
# 使用 dns 驗證方式
- --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
# 郵箱配置
- [email protected]
# 保存 ACME 證書的位置
- --certificatesResolvers.ali.acme.storage=/etc/acme/acme.json/<code>

ACME 有多種校驗方式 tlsChallenge、httpChallenge 和 dnsChallenge 三種驗證方式,之前更常用的是 http 這種驗證方式,關於這幾種驗證方式的使用可以查看文檔:https://www.qikqiak.com/traefik-book/https/acme/ 瞭解他們之間的區別。要使用 tls 校驗方式的話需要保證 Traefik 的 443 端口是可達的,dns 校驗方式可以生成通配符的證書,只需要配置上 DNS 解析服務商的 API 訪問密鑰即可校驗。我們這裡用 DNS 校驗的方式來為大家說明如何配置 ACME。上面我們通過設置 --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns 參數來指定指定阿里雲的 DNS 校驗,要使用阿里雲的 DNS 校驗我們還需要配置3個環境變量:ALICLOUD_ACCESS_KEY、ALICLOUD_SECRET_KEY、ALICLOUD_REGION_ID,分別對應我們平時開發阿里雲應用的時候的密鑰,可以登錄阿里雲後臺獲取,由於這是比較私密的信息,所以我們用 Secret 對象來創建:

<code>$ kubectl create secret generic traefik-alidns-secret --from-literal=ALICLOUD_ACCESS_KEY=<aliyun> --from-literal=ALICLOUD_SECRET_KEY=<aliyun>--from-literal=ALICLOUD_REGION_ID=cn-beijing -n kube-system/<aliyun>/<aliyun>/<code>

創建完成後將這個 Secret 通過環境變量配置到 Traefik 的應用中。還有一個值得注意的是驗證通過的證書我們這裡存到 /etc/acme/acme.json 文件中,我們一定要將這個文件持久化,否則每次 Traefik 重建後就需要重新認證,而 Let’s Encrypt 本身校驗次數是有限制的。最後我們這裡完整的 Traefik 的配置資源清單如下所示:

<code>apiVersion: apps/v1
kind: Deployment
metadata:
name: traefik
namespace: kube-system
labels:
app: traefik
spec:
selector:
matchLabels:
app: traefik
template:
metadata:
labels:
app: traefik
spec:
serviceAccountName: traefik
terminationGracePeriodSeconds: 60
tolerations:
- operator: "Exists"
nodeSelector:
kubernetes.io/hostname: ydzs-master
volumes:
- name: acme
hostPath:
path: /data/k8s/traefik/acme
containers:
- image: traefik:2.1.1
name: traefik
ports:
- name: web
containerPort: 80
hostPort: 80
- name: websecure

containerPort: 443
hostPort: 443
args:
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --api=true
- --api.dashboard=true
- --ping=true
- --providers.kubernetesingress
- --providers.kubernetescrd
- --log.level=INFO
- --accesslog
# 使用 dns 驗證方式
- --certificatesResolvers.ali.acme.dnsChallenge.provider=alidns
# 郵箱配置
- [email protected]
# 保存 ACME 證書的位置
- --certificatesResolvers.ali.acme.storage=/etc/acme/acme.json
# 下面是用於測試的ca服務,如果https證書生成成功了,則移除下面參數
# - --certificatesresolvers.ali.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
envFrom:
- secretRef:
name: traefik-alidns-secret
# ALICLOUD_ACCESS_KEY
# ALICLOUD_SECRET_KEY
# ALICLOUD_REGION_ID
volumeMounts:
- name: acme
mountPath: /etc/acme
resources:
requests:
cpu: "50m"
memory: "50Mi"
limits:
cpu: "200m"
memory: "100Mi"
securityContext:
allowPrivilegeEscalation: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
readinessProbe:
httpGet:
path: /ping

port: 8080
failureThreshold: 1
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2
livenessProbe:
httpGet:
path: /ping
port: 8080
failureThreshold: 3
initialDelaySeconds: 10
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 2/<code>

直接更新 Traefik 應用即可。更新完成後現在我們來修改上面我們的 whoami 應用:

<code>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"/<code>

其他的都不變,只需要將 tls 部分改成我們定義的 ali 這個證書解析器,如果我們想要生成一個通配符的域名證書的話可以定義 domains 參數來指定,然後更新 IngressRoute 對象,這個時候我們再去用 HTTPS 訪問我們的應用(當然需要將域名在阿里雲 DNS 上做解析):

一文搞懂 Traefik2.1 的使用

traefik wildcard domain


我們可以看到訪問應用已經是受瀏覽器信任的證書了,查看證書我們還可以發現該證書是一個通配符的證書。

中間件

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

一文搞懂 Traefik2.1 的使用

traefik middleware overview


同樣比如上面我們定義的 whoami 這個應用,我們可以通過 https://who.qikqiak.com/tls 來訪問到應用,但是如果我們用 http 來訪問的話呢就不行了,就會404了,因為我們根本就沒有簡單80端口這個入口點,所以要想通過 http 來訪問應用的話自然我們需要監聽下 web 這個入口點:

<code>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/<code>

注意這裡我們創建的 IngressRoute 的 entryPoints 是 web,然後創建這個對象,這個時候我們就可以通過 http 訪問到這個應用了。

但是我們如果只希望用戶通過 https 來訪問應用的話呢?按照以前的知識,我們是不是可以讓 http 強制跳轉到 https 服務去,對的,在 Traefik 中也是可以配置強制跳轉的,只是這個功能現在是通過中間件來提供的了。如下所示,我們使用 redirectScheme 中間件來創建提供強制跳轉服務:

<code>apiVersion: traefik.containo.us/v1alpha1
kind: Middleware
metadata:
name: redirect-https
spec:
redirectScheme:
scheme: https/<code>

然後將這個中間件附加到 http 的服務上面去,因為 https 的不需要跳轉:

<code>---
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/<code>

這個時候我們再去訪問 http 服務可以發現就會自動跳轉到 https 去了。關於更多中間件的用法可以查看文檔 Traefik Docs。

灰度發佈

Traefik2.0 的一個更強大的功能就是灰度發佈,灰度發佈我們有時候也會稱為金絲雀發佈(Canary),主要就是讓一部分測試的服務也參與到線上去,經過測試觀察看是否符號上線要求。

一文搞懂 Traefik2.1 的使用

canary deployment


比如現在我們有兩個名為 appv1 和 appv2 的服務,我們希望通過 Traefik 來控制我們的流量,將 3⁄4 的流量路由到 appv1,1/4 的流量路由到 appv2 去,這個時候就可以利用 Traefik2.0 中提供的帶權重的輪詢(WRR)來實現該功能,首先在 Kubernetes 集群中部署上面的兩個服務。為了對比結果我們這裡提供的兩個服務一個是 whoami,一個是 nginx,方便測試。

appv1 服務的資源清單如下所示:(appv1.yaml)

<code>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/<code>

appv2 服務的資源清單如下所示:(appv2.yaml)

<code>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/<code>

直接創建上面兩個服務:

<code>$ 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/<code>

在 Traefik2.1 中新增了一個 TraefikService 的 CRD 資源,我們可以直接利用這個對象來配置 WRR,之前的版本需要通過 File Provider,比較麻煩,新建一個描述 WRR 的資源清單:(wrr.yaml)

<code>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/<code>

然後為我們的灰度發佈的服務創建一個 IngressRoute 資源對象:(ingressroute.yaml)

<code>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/<code>

不過需要注意的是現在我們配置的 Service 不再是直接的 Kubernetes 對象了,而是上面我們定義的 TraefikService 對象,直接創建上面的兩個資源對象,這個時候我們對域名 wrr.qikqiak.com 做上解析,去瀏覽器中連續訪問 4 次,我們可以觀察到 appv1 這應用會收到 3 次請求,而 appv2 這個應用只收到 1 次請求,符合上面我們的 3:1 的權重配置。

一文搞懂 Traefik2.1 的使用

traefik wrr demo


流量複製

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

一文搞懂 Traefik2.1 的使用

traefik mirror


同樣的在 2.0 中只能通過 FileProvider 進行配置,在 2.1 版本中我們已經可以通過 TraefikService 資源對象來進行配置了,現在我們部署兩個 whoami 的服務,資源清單文件如下所示:

<code>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/<code>

直接創建上面的資源對象:

<code>$ 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/<none>/<none>/<code>

現在我們創建一個 IngressRoute 對象,將服務 v1 的流量複製 50% 到服務 v2,如下資源對象所示:(mirror-ingress-route.yaml)

<code>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/<code>

然後直接創建這個資源對象即可:

<code>$ kubectl apply -f mirror-ingress-route.yaml 
ingressroute.traefik.containo.us/mirror-ingress-route created
traefikservice.traefik.containo.us/mirroring-example created/<code>

這個時候我們在瀏覽器中去連續訪問4次 mirror.qikqiak.com 可以發現有一半的請求也出現在了 v2 這個服務中:

一文搞懂 Traefik2.1 的使用

TCP

另外 Traefik2.0 已經支持了 TCP 服務的,下面我們以 mongo 為例來了解下 Traefik 是如何支持 TCP 服務得。

簡單 TCP 服務

首先部署一個普通的 mongo 服務,資源清單文件如下所示:(mongo.yaml)

<code>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/<code>

直接創建 mongo 應用:

<code>$ kubectl apply -f mongo.yaml 

deployment.apps/mongo-traefik created
service/mongo-traefik created/<code>

創建成功後就可以來為 mongo 服務配置一個路由了。由於 Traefik 中使用 TCP 路由配置需要 SNI,而 SNI 又是依賴 TLS 的,所以我們需要配置證書才行,如果沒有證書的話,我們可以使用通配符 * 進行配置,我們這裡創建一個 IngressRouteTCP 類型的 CRD 對象(前面我們就已經安裝了對應的 CRD 資源):(mongo-ingressroute-tcp.yaml)

<code>apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`*`)
services:
- name: mongo-traefik
port: 27017/<code>

要注意的是這裡的 entryPoints 部分,是根據我們啟動的 Traefik 的靜態配置中的 entryPoints 來決定的,我們當然可以使用前面我們定義得 80 和 443 這兩個入口點,但是也可以可以自己添加一個用於 mongo 服務的專門入口點:

<code>......
- image: traefik:2.1.1
name: traefik
ports:
- name: web
containerPort: 80
hostPort: 80
- name: websecure
containerPort: 443
hostPort: 443
- name: mongo
hostPort: 27017

containerPort: 27017
args:
- --entryPoints.web.address=:80
- --entryPoints.websecure.address=:443
- --entryPoints.mongo.address=:27017
....../<code>

這裡給入口點添加 hostPort 是為了能夠通過節點的端口訪問到服務,關於 entryPoints 入口點的更多信息,可以查看文檔 entrypoints 瞭解更多信息。然後更新 Traefik 後我們就可以直接創建上面的資源對象:

<code>$ mongo-ingressroute-tcp.yaml
ingressroutetcp.traefik.containo.us/mongo-traefik-tcp created/<code>

創建完成後,同樣我們可以去 Traefik 的 Dashboard 頁面上查看是否生效:

一文搞懂 Traefik2.1 的使用

traefik-tcp-mongo-1


然後我們配置一個域名 mongo.local 解析到 Traefik 所在的節點,然後通過 27017 端口來連接 mongo 服務:

<code>$ 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/<code>

到這裡我們就完成了將 mongo(TCP)服務暴露給外部用戶了。

帶 TLS 證書的 TCP

上面我們部署的 mongo 是一個普通的服務,然後用 Traefik 代理的,但是有時候為了安全 mongo 服務本身還會使用 TLS 證書的形式提供服務,下面是用來生成 mongo tls 證書的腳本文件:(generate-certificates.sh)

<code>#!/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"/<code>

將上面證書放置到 certs 目錄下面,然後我們新建一個 02-tls-mongo 的目錄,在該目錄下面執行如下命令來生成證書:

<code>$ bash ../certs/generate-certificates.sh mongo.local .
== Checking Requirements...
== Generating Certificates for the following domains: mongo.local.../<code>

最後的目錄如下所示,在 02-tls-mongo 目錄下面會生成包含證書的 certs 目錄:

<code>$ 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/<code>

在 02-tls-mongo/certs 目錄下面執行如下命令通過 Secret 來包含證書內容:

<code>$ kubectl create secret tls traefik-mongo-certs --cert=cert.pem --key=key.pem
secret/traefik-mongo-certs created/<code>

然後重新更新 IngressRouteTCP 對象,增加 TLS 配置:

<code>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/<code>

同樣更新後,現在我們直接去訪問應用就會被 hang 住,因為我們沒有提供證書:

<code>$ mongo --host mongo.local --port 27017
MongoDB shell version: 2.6.1
connecting to: mongo1.local:27017/test/<code>

這個時候我們可以帶上證書來進行連接:

<code>$ 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/<code>

可以看到現在就可以連接成功了,這樣就完成了一個使用 TLS 證書代理 TCP 服務的功能,這個時候如果我們使用其他的域名去進行連接就會報錯了,因為現在我們指定的是特定的 HostSNI:

<code>$ 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/<code>

當然我們也可以使用 ACME 來為我們提供一個合法的證書,這樣在連接的使用就不需要指定證書了,如下所示:

<code>apiVersion: traefik.containo.us/v1alpha1
kind: IngressRouteTCP
metadata:
name: mongo-traefik-tcp
spec:
entryPoints:
- mongo
routes:
- match: HostSNI(`mongo.qikqiak.com`)
services:
- name: mongo-traefik
port: 27017
tls:
certResolver: ali
domains:
- main: "*.qikqiak.com"/<code>

這樣當我們連接的時候就只需要如下的命令即可:

<code>$ mongo --host mongo.qikqiak.com --port 27017 --ssl/<code>

來源:https://www.qikqiak.com/post/traefik-2.1-101/


分享到:


相關文章: