前言
之前提到的 Pod & Service 都會各自擁有一個 IP address 供存取,但這些 IP 僅有在 K8s cluster 內部才有辦法存取的到,但若要在 K8s cluster 上提供對外服務呢?
而目前可讓外部存取 K8s 上服務的方式主要有三種:
Service NodePort: 這方法會在每個 master & worker node 上都開啟指定的 port number (這樣其實造成不少資源浪費)
Service LoadBalancer: 只有在 GCP or AWS 這類的 public cloud 平台才有用的功能
Ingress 就是被設計來處理這類的問題:即為此篇文章要談的主角
首先,在沒有 Ingress 之前,從外面到 K8s cluster 的流量會如下圖:
1 2 3 4
| internet | ------------ [ Services ]
|
但在原有的模式下,如果是在 GCP or AWS 使用 K8s 的話,還可以搭配 LoadBalancer 的 service type 動態取得 LB 對外提供服務;但如果是自己架設 K8s,那就只能透過 Service NodePort 的方式讓使用者從外部存取運行在 K8s 上的服務。
所以 Ingress 在 K8s v1.1 之後就應運而生了,而 Ingress 其實就是一堆 rule 的集合,讓外面進來的網路流量可以正確的被導到後方的 Service,架構變成如下圖:
1 2 3 4 5
| internet | [ Ingress ] --|-----|-- [ Services ]
|
在官網文件中提到 Ingress 可以負責以下工作:
看的出來可以做到的功能實在很多,非常值得好好研究一下。
在使用 Ingress 之前
看起來 Ingress 就是處理 inbound traffic 的銀彈,但直接在 YAML 中宣告 Ingress resource 並建立並不會有任何效果,必須搭配 Ingress controller 才有辦法讓設定真正的生效,而不同於其他的 controller,Ingress controller 是屬於 kube-controller-manager
的一部份,而且會自動的跟著 cluster 一起啟動。
目前 K8s 官方 GitHub repository 中提供的則是以 nginx 來作為 Ingress controller。
但我找到另外一個也開發的相當不錯的 HTTP reverse proxy 作為 Ingress Controller,名稱為 Træfik,它可以與相當多的平台或軟體進行整合,例如:Docker, Docker Swarm, Marathon, Consul, etcd, Rancher, Amazon …. 等等,當然 Kubernetes 也是其中之一。
而為什麼要選擇 Træfik 呢? 來看看[官網][traefik]提供的 feature 列表:
Continuously updates its configuration (No restarts!)
Supports multiple load balancing algorithms
Provides HTTPS to your microservices by leveraging Let’s Encrypt (wildcard certificates support)
Circuit breakers, retry
High Availability with cluster mode (beta)
See the magic through its clean web UI
Websocket, HTTP/2, GRPC ready
Provides metrics (Rest, Prometheus, Datadog, Statsd, InfluxDB)
Keeps access logs (JSON, CLF)
Fast
Exposes a Rest API
Packaged as a single binary file (made with :heart: with go) and available as a tiny official docker image
Træfik 功能真的是相當強大阿! 當然要給它試試看….
因此接下來將會以 Træfik 作為 Kubernetes Ingress Controller 作示範。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| $ kubectl get nodes NAME STATUS ROLES AGE VERSION kube-ingress0 Ready ingress,node 6d v1.10.4 kube-ingress1 Ready ingress,node 6d v1.10.4 kube-master0 Ready master 6d v1.10.4 kube-master1 Ready master 6d v1.10.4 kube-master2 Ready master 6d v1.10.4 kube-worker0 Ready node 6d v1.10.4 kube-worker1 Ready node 6d v1.10.4 kube-worker2 Ready node 6d v1.10.4
$ kubectl label node kube-ingress0 k8s-app=traefik-ingress-lb $ kubectl label node kube-ingress1 k8s-app=traefik-ingress-lb
|
若要檢視目前每個 node 所帶的 label 可使用以下指令:
kubectl get nodes –show-labels
佈署 Træfik Ingress Controller
設定 RBAC
首先要讓 Træfik 有相對應的權限,將會進行以下設定:
為 Træfik 建立一個 cluster role(traefik-ingress-controller
),並給定足夠的權限
為 Træfik 建立一個 service account(traefik-ingress-controller
),並繫結(透過 cluster role binding traefik-ingress-controller
)到上述的 cluster role 以取得權限
準備檔案 traefik-rbac.yaml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| --- 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: kube-system
|
kubectl apply -f traefik-rbac.yaml
或是也可以直接套用 traefik 最新的程式碼:
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-rbac.yaml
設定以 DaemonSet 的方式執行
traefik 可以用 Deployment or DaemonSet 的方式進行佈署,兩者的差異比較可以參考官網說明,以下是使用 DaemonSet 安裝所需要的 YAML 設定(traefik-ds.yaml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| --- apiVersion: v1 kind: ServiceAccount metadata: name: traefik-ingress-controller namespace: kube-system --- kind: DaemonSet apiVersion: extensions/v1beta1 metadata: name: traefik-ingress-controller namespace: kube-system labels: k8s-app: traefik-ingress-lb spec: template: metadata: labels: k8s-app: traefik-ingress-lb name: traefik-ingress-lb spec: serviceAccountName: traefik-ingress-controller terminationGracePeriodSeconds: 60 containers: - image: traefik name: traefik-ingress-lb ports: - name: http containerPort: 80 hostPort: 80 - name: admin containerPort: 8080 securityContext: capabilities: drop: - ALL add: - NET_BIND_SERVICE args: - --api - --kubernetes - --logLevel=INFO --- kind: Service apiVersion: v1 metadata: name: traefik-ingress-service namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb ports: - protocol: TCP port: 80 name: web - protocol: TCP port: 8080 name: admin
|
套用上述設定:
kubectl apply -f traefik-ds.yaml
或是也可以直接套用 traefik 最新的程式碼:
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/traefik-ds.yaml
到此階段,traefik ingress controller 已經佈署完成,接著就是定義 ingress 並與其繫結。
佈署 Traefik Dashboard
traefik 有個不錯的優點就是提供了一個還蠻不錯看的 Dashboard,當然也是需要透過設定 ingress 才有辦法連到,因此準備以下檔案(traefik-ui.yaml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| --- apiVersion: v1 kind: Service metadata: name: traefik-web-ui namespace: kube-system spec: selector: k8s-app: traefik-ingress-lb ports: - port: 80 targetPort: 8080
--- apiVersion: extensions/v1beta1 kind: Ingress metadata: name: traefik-web-ui namespace: kube-system annotations: kubernetes.io/ingress.class: traefik spec: rules: - host: traefik-ui.k8s.example.com http: paths: - backend: serviceName: traefik-web-ui servicePort: 80
|
kubectl apply -f traefik-ui.yaml
除此之外,還有 DNS 的部份需要設定;在上面的 ingress host 設定是 traefik-ui.k8s.example.com
,由於這個 domain name 是虛擬的,因此我們必須修改 client 端的 DNS 設定(/etc/hosts
),將 worker node 的 IP 指到這個 domain name。
在我的實驗環境中,worker node 一共有三台,分別是 **10.103.10.[61-63]**,因此在 client 的 /etc/hosts
中加入以下設定
1 2 3
| 10.103.10.61 traefik-ui.k8s.example.com 10.103.10.62 traefik-ui.k8s.example.com 10.103.10.63 traefik-ui.k8s.example.com
|
完成之後就可以透過 http://traefik-ui.k8s.example.com 連到 Traefik Dashboard 了!
佈署網站 & 設定 Ingress
在這邊會另外建立一個 ingress-test
作為測試用的 namespace,並設定為 kubectl 的 default namespace:
1 2 3
| $ kubectl create ns ingress-test $ kubectl config set-context $(kubectl config current-context) --namespace=ingress-test
|
設定 DNS
接下來的範例會需要有相對應的 DNS 設定,首先必須確認兩件事情:
traefik DaemonSet 所存在的機器 IP (一般就是 worker node)
可控制的 domain name
在本環境中,worker node 一共有三台,分別是 **10.103.10.[61-63]**,將會用到的 domain name 為:
由於上述的並非真的由我控制的 domain,因此我們必須修改 client 端的 DNS 設定(/etc/hosts
) 如下:
1 2 3
| 10.103.10.61 stilton.k8s.example.com cheddar.k8s.example.com wensleydale.k8s.example.com 10.103.10.62 stilton.k8s.example.com cheddar.k8s.example.com wensleydale.k8s.example.com 10.103.10.63 stilton.k8s.example.com cheddar.k8s.example.com wensleydale.k8s.example.com
|
接著以下會分為 Name-based Routing & Path-bsaed Routing 進行示範:
Name-based Routing
Deployment
準備 Deployment 檔案(cheese-deployments.yaml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78
| --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: stilton labels: app: cheese cheese: stilton spec: replicas: 2 selector: matchLabels: app: cheese task: stilton template: metadata: labels: app: cheese task: stilton version: v0.0.1 spec: containers: - name: cheese image: errm/cheese:stilton ports: - containerPort: 80 --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: cheddar labels: app: cheese cheese: cheddar spec: replicas: 2 selector: matchLabels: app: cheese task: cheddar template: metadata: labels: app: cheese task: cheddar version: v0.0.1 spec: containers: - name: cheese image: errm/cheese:cheddar ports: - containerPort: 80 --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: wensleydale labels: app: cheese cheese: wensleydale spec: replicas: 2 selector: matchLabels: app: cheese task: wensleydale template: metadata: labels: app: cheese task: wensleydale version: v0.0.1 spec: containers: - name: cheese image: errm/cheese:wensleydale ports: - containerPort: 80
|
kubectl apply -f cheese-deployments.yaml
或是可以直接套用官網提供的 Deployment 範例:
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-deployments.yaml
Service
接著準備 Service 檔案(cheese-services.yaml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| --- apiVersion: v1 kind: Service metadata: name: stilton spec: ports: - name: http targetPort: 80 port: 80 selector: app: cheese task: stilton --- apiVersion: v1 kind: Service metadata: name: cheddar spec: ports: - name: http targetPort: 80 port: 80 selector: app: cheese task: cheddar --- apiVersion: v1 kind: Service metadata: name: wensleydale annotations: traefik.backend.circuitbreaker: "NetworkErrorRatio() > 0.5" spec: ports: - name: http targetPort: 80 port: 80 selector: app: cheese task: wensleydale
|
kubectl apply -f cheese-services.yaml
或是可以直接套用官網提供的 Deployment 範例:
kubectl apply -f https://raw.githubusercontent.com/containous/traefik/master/examples/k8s/cheese-services.yaml
Ingress
最後則是 Ingress 的設定(name-based-routing_cheese-ingress.yaml
):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| apiVersion: extensions/v1beta1 kind: Ingress metadata: name: cheese annotations: kubernetes.io/ingress.class: traefik spec: rules: - host: stilton.k8s.example.com http: paths: - path: / backend: serviceName: stilton servicePort: http - host: cheddar.k8s.example.com http: paths: - path: / backend: serviceName: cheddar servicePort: http - host: wensleydale.k8s.example.com http: paths: - path: / backend: serviceName: wensleydale servicePort: http
|
kubectl apply -f name-based-routing_cheese-ingress.yaml
最後就可以透過以下3個連結連到不同的 Ingress -> Service -> Deployment:
Path-bsaed Routing
這個在 DNS 設定部份就簡單多了,在以下範例中,只要設定一個 DNS record(cheese.k8s.example.com
) 即可,因此 DNS 設定(/etc/hosts
)必須加入以下資訊:
1 2 3
| 10.103.10.61 cheese.k8s.example.com 10.103.10.62 cheese.k8s.example.com 10.103.10.63 cheese.k8s.example.com
|
而在 Deployment & Service 的部份跟 Name-based Routing 是相同的,而這裡的 ingress 設定(path-based-routing_cheese-ingress.yaml
)則是跟上面不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| apiVersion: extensions/v1beta1 kind: Ingress metadata: name: cheeses annotations: kubernetes.io/ingress.class: traefik traefik.frontend.rule.type: PathPrefixStrip spec: rules: - host: cheeses.k8s.example.com http: paths: - path: /stilton backend: serviceName: stilton servicePort: http - path: /cheddar backend: serviceName: cheddar servicePort: http - path: /wensleydale backend: serviceName: wensleydale servicePort: http
|
kubectl apply -f path-based-routing_cheese-ingress.yaml
最後就可以透過以下3個連結連到不同的 Ingress -> Service -> Deployment:
References