kubernetes 叢集外部如果想要訪問叢集內部,需要通過 NodePort 型別的 service。而 service 的三種排程模型都工作在四層,對於 https 這樣的七層協議無從下手,證書、私鑰啥的你根本無處配置。kubernetes 的解決方案是,加一箇中間層。使用者請求不會直接到達 service,而是會先到達一個 pod,而由於 pod 和 pod 之間處於同一個網路,所以這個 pod 直接對後端 pod 進行負載均衡,而不再經過 service。
但是 pod 如何接入外部流量?所以它的前面還需要 service,且必須是 NodePort 型別。而為了高可用,每個節點上都會啟動這個埠,所以前端還需要一個負載均衡器,用於將請求排程到每個節點。但是這樣排程 n 次效能損耗十分嚴重。前面提到過,pod 可以共享節點的網路名稱空間,因此,pod 可以直接監聽節點的埠,用於接收外部請求,然後負載均衡到後端 pod 之上。為了高可用,這個 pod 可以執行為 DaemonSet,且可以只執行在有限的節點上。
這個 pod 在 kubernetes 被稱為 ingress controller。ingress 作為一個 pod,執行的容器可以是 nginx、traefik、envoy 等。但是 pod ip 和數量是隨時變化的,nginx 這樣的產品如何能夠知道呢?這還得藉助於 service,一個 nginx 中的 upstream 對應一個 service。而這個 service 並不進行負載均衡,還是用來篩選的,篩選出的 pod 會作為 nginx 的配置檔案。但是 service 監控到的 pod 的變化如何反應到 nginx 呢?
這就需要 ingress 資源,kubernetes 存在一種 ingress 的資源,它不同於 ingress controller。ingress 資源可以獲得 service 的結果,並且反應到 nginx 這樣的負載均衡器的配置檔案中,並且還能通知其進行過載配置檔案。這就體現出 nginx 的侷限性了,因為它配置檔案一更改就得過載配置檔案,所以這裡我選擇使用 traefik,它就是為 docker 而生。
我們的做法是,首先部署一個 ingress controller 型別的 pod,然後選擇是通過基於不同的域名或者不同的 URL,關聯到不同的 service。而後通過 ingress 來監控 service 的變化,最終形成相應的配置檔案。
為了簡單,我們會將 traefik 安裝到 default 名稱空間。
通過該系列的前面文章,相信你現在也有了泛域名證書,因此 traefik 會開啟 https 支援。當然,你也可以選擇 http,跟著往下走也不會有問題。
建立配置檔案
如果不需要為 traefik 開啟 https,這一節可以跳過。
由於我們要提供 https,因此 traefik 需要監聽 443 埠,並且需要將我們的證書提供給它。這樣一來,我們需要將 https 證書掛載進 traefik 容器中,並在配置檔案中指定它的位置。
這裡的解決方法是,將證書建立為 secret,掛載進容器中。同時建立 traefik 配置檔案,在其中指定證書的位置、開啟 443,並將對 80 埠的訪問直接轉發到 443,然後將該配置檔案建立為 configMap,將其掛載到容器中。
首先建立 secret:
kubectl create secret tls ntpstat.com --cert=/etc/cert/ntpstat.com.crt --key=/etc/cert/ntpstat.com.key
複製程式碼
接著建立 traefik 配置檔案:
mkdir -p /usr/local/kuberneters/manifests/trafik
cd /usr/local/kuberneters/manifests/trafik
vim traefik.toml
複製程式碼
以下是檔案的內容:
defaultEntryPoints = ["http","https"]
[kubernetes]
[entryPoints]
[entryPoints.http]
address = ":80"
[entryPoints.http.redirect]
entryPoint = "https"
[entryPoints.https]
address = ":443"
[entryPoints.https.tls]
[[entryPoints.https.tls.certificates]]
CertFile = "/ssl/tls.crt"
KeyFile = "/ssl/tls.key"
複製程式碼
將其建立為 configMap:
kubectl create configmap traefik-config --from-file=traefik.toml
複製程式碼
建立 clusterRoleBinding
部署之前先建立 clusterRoleBinding,它的作用是將下面列出的許可權授予給 traefik-ingress-controller 這個 ServiceAccount,然後 pod 就由這個使用者啟動。這樣一來,pod 就擁有這些許可權了。
# cd /usr/local/kuberneters/manifests/trafik
# vim clusterRoleBinding.yml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
rules:
- apiGroups:
- ""
resources:
- services
- endpoints
- secrets
verbs:
- get
- list
- watch
- apiGroups:
- extensions
resources:
- ingresses
verbs:
- get
- list
- watch
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: traefik-ingress-controller
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: traefik-ingress-controller
subjects:
- kind: ServiceAccount
name: traefik-ingress-controller
namespace: default
複製程式碼
你如果覺得 ClusterRoleBinding 範圍太廣,也可以使用 RoleBinding,1.5 版本後,traefik 支援每個名稱空間級 RoleBinding。因為我們都在 default 名稱空間使用,其實可以使用 RoleBinding 的,這裡就不糾結了。
kubectl apply -f clusterRoleBinding.yml
複製程式碼
部署 traefik
traefik 可以部署為 Deployment 和 DaemonSet 兩種模式,如果使用官方提供的 deployment 的安裝方式,traefik pod 的 80/443/8080 埠會通過 NodePort 的方式暴露出來,也就是說你無法通過節點的 ip + 80 埠進行訪問,因此你前面還得給它加上一個負載均衡器,這種做法就有些反人類了,訪問 traefik 這個負載均衡器還得在前面加上一個負載均衡器不是扯淡麼。
而官方的 DaemonSet 就不存在這樣的問題了,它使用的是 NET_BIND_SERVICE
這樣一個 capabilities,意思是可以直接使用宿主機網路名稱空間的埠。使用它之後,你在宿主機上看不到它監聽了 80 埠,但是你卻可以直接訪問,而不是 Deployment 這樣的 NodePort 方式。但是 DaemonSet 的缺點也很明顯,你的 node 節點數量越多,就越消耗資源。
有沒有兩全其美的方法呢?那肯定是有的,無論你使用 Deployment 還是 DaemonSet,只要都使用 NET_BIND_SERVICE
,然後定義好節點的親和性或者汙點就能讓 pod 只執行在特定的節點上,然後域名解析指向這些節點就行。
當然前提是這些節點的 80/443 都沒有被佔用。不過這裡就不指定親和性了,我會使用 Deployment + NET_BIND_SERVICE 進行部署,之所以不使用 DaemonSet,是因為官方的 DaemonSet 已經能夠滿足需求了。
在部署之前,我們先建立一個名為 nexus-pull
的 secret,這個用於 nexus pull 映象時的認證。關於 nexus 的搭建,可以檢視我的上一篇文章:
kubectl create secret docker-registry nexus-pull --docker-username=admin --docker-password="admin123" --docker-server="registry.ntpstat.com:2222"
複製程式碼
建立 deployment.yml:
# vim /usr/local/kuberneters/manifests/traefik/deployment.yml
---
# 建立一個 serviceaccount 用於啟動 pod,並且擁有 clusterrole 的許可權
apiVersion: v1
kind: ServiceAccount
metadata:
name: traefik-ingress-controller
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
name: traefik-ingress-controller
labels:
k8s-app: traefik-ingress-lb
spec:
# 兩副本
replicas: 2
selector:
matchLabels:
k8s-app: traefik-ingress-lb
template:
metadata:
labels:
k8s-app: traefik-ingress-lb
name: traefik-ingress-lb
spec:
serviceAccountName: traefik-ingress-controller
terminationGracePeriodSeconds: 60
# 證書和配置檔案
volumes:
- name: ssl
secret:
secretName: ntpstat.com
- name: conf
configMap:
name: traefik-config
imagePullSecrets:
- name: nexus-pull
containers:
- image: registry.ntpstat.com:2222/traefik
name: traefik-ingress-lb
volumeMounts:
- mountPath: /ssl
name: ssl
- mountPath: /conf
name: conf
ports:
- name: http
containerPort: 80
# 需要指定 hostPort
hostPort: 80
- name: https
containerPort: 443
hostPort: 443
- name: admin
containerPort: 8080
hostPort: 8080
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
args:
- --api
- --kubernetes
- --logLevel=INFO
# 指定配置檔案位置
- --configFile=/conf/traefik.toml
---
kind: Service
apiVersion: v1
metadata:
name: traefik-ingress-service
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- protocol: TCP
port: 80
name: web
- protocol: TCP
port: 8080
name: admin
- protocol: TCP
port: 443
name: https
複製程式碼
部署:
# kubectl apply -f deployment.yml
serviceaccount/traefik-ingress-controller created
deployment.extensions/traefik-ingress-controller created
service/traefik-ingress-service created
複製程式碼
檢視 pod 是否執行:
kubectl get pod -o wide
複製程式碼
執行 ok 之後,你可以直接使用 curl 來訪問節點的 ip:
# curl NODE_IP
Found
複製程式碼
出現 Found 表示部署成功了。
驗證 traefik
如果你覺得使用 curl 命令不夠直觀的話,你還可以通過部署 traefik ui 來驗證 traefik 已經部署成功。traefik ui 是 traefik 內部的功能,我們現在只需要新增規則開啟它而已。
我們準備通過主機名的方式來訪問它,所以我們接下來做的就是定義基於域名的虛擬主機。
# vim ui.yml
---
apiVersion: v1
kind: Service
metadata:
name: traefik-web-ui
spec:
selector:
k8s-app: traefik-ingress-lb
ports:
- name: web
port: 80
targetPort: 8080
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: traefik-web-ui
spec:
rules:
- host: traefik.ntpstat.com
http:
paths:
- path: /
backend:
serviceName: traefik-web-ui
servicePort: web
tls:
- hosts:
- traefik.ntpstat.com
secretName: ntpstat.com
複製程式碼
然後你可以在新增 hosts 之後在瀏覽器上直接訪問 traefik.ntpstat.com 就可以看到它的 web 介面了,並且會自動跳轉到 https。