一文搞懂 Traefik2.1 的使用

原文鏈接:一文搞懂 Traefik2.1 的使用

一文搞懂 Traefik2.1 的使用


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

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

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

traefik architecture

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

核心概念

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

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 集羣中的資源清單文件我這裏提前準備好了,直接執行下面的安裝命令即可:

$ 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

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

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

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

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

$ 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

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

traefik dashboard demo

ACME

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

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

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

然後定義一個 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 所匹配。我們可以直接創建上面的幾個資源對象,然後對域名做對應的解析後,就可以訪問應用了:

traefik whoami http demo

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

traefik route matcher

如果我們需要用 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 whoami https demo

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

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

ACME 有多種校驗方式 tlsChallengehttpChallengednsChallenge 三種驗證方式,之前更常用的是 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_KEYALICLOUD_SECRET_KEYALICLOUD_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 的應用中。還有一個值得注意的是驗證通過的證書我們這裏存到 /etc/acme/acme.json 文件中,我們一定要將這個文件持久化,否則每次 Traefik 重建後就需要重新認證,而 Let’s Encrypt 本身校驗次數是有限制的。最後我們這裏完整的 Traefik 的配置資源清單如下所示:

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

直接更新 Traefik 應用即可。更新完成後現在我們來修改上面我們的 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 上做解析):

traefik wildcard domain

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

中間件

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

traefik middleware overview

同樣比如上面我們定義的 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

灰度發佈

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

canary deployment

比如現在我們有兩個名爲 appv1appv2 的服務,我們希望通過 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 wrr demo

流量複製

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

traefik mirror

同樣的在 2.0 中只能通過 FileProvider 進行配置,在 2.1 版本中我們已經可以通過 TraefikService 資源對象來進行配置了,現在我們部署兩個 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 這個服務中:
traefik mirror demo

TCP

另外 Traefik2.0 已經支持了 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: 27017

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

......
- 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
  ......

這裏給入口點添加 hostPort 是爲了能夠通過節點的端口訪問到服務,關於 entryPoints 入口點的更多信息,可以查看文檔 entrypoints 瞭解更多信息。

然後更新 Traefik 後我們就可以直接創建上面的資源對象:

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

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

traefik-tcp-mongo-1

然後我們配置一個域名 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

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

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"

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

$ mongo --host mongo.qikqiak.com --port 27017 --ssl

掃描下面的二維碼(或微信搜索k8s技術圈)關注我們的微信公衆帳號,在微信公衆帳號中回覆 加羣 即可加入到我們的 kubernetes 討論羣裏面共同學習。
k8s技術圈二維碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章