說說 Kubernetes 是怎么實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)的
我們來說說 Kubernetes 的服務(wù)發(fā)現(xiàn)。那么首先這個(gè)大前提是同主機(jī)通信以及跨主機(jī)通信都是 ok 的,即同一 Kubernetes 集群中各個(gè) Pod 都是互通的。這點(diǎn)是由更底層的方案實(shí)現(xiàn),包括 docker0/CNI 網(wǎng)橋、Flannel vxlan/host-gw 模式等,在此篇就不展開講了。
在各 Pod 都互通的前提下,我們可以通過訪問 podIP 來調(diào)用 Pod 上的資源,那么離服務(wù)發(fā)現(xiàn)還有多少距離呢?首先 Pod 的 IP 不是固定的,另一方面我們?cè)L問一組 Pod 實(shí)例的時(shí)候往往會(huì)有負(fù)載均衡的需求,那么 Service 對(duì)象就是用來解決此類問題的。
集群內(nèi)通信
Endpoints
Service 首先解決的是集群內(nèi)通信的需求,首先我們編寫一個(gè)普通的 deployment:
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: hostnames
- spec:
- selector:
- matchLabels:
- app: hostnames
- replicas: 3
- template:
- metadata:
- labels:
- app: hostnames
- spec:
- containers:
- - name: hostnames
- image: mirrorgooglecontainers/serve_hostname
- ports:
- - containerPort: 9376
- protocol: TCP
這個(gè)應(yīng)用干的事兒就是訪問它是返回自己的 hostname,并且每個(gè) Pod 都帶上了 APP 為 hostnames 的標(biāo)簽。
那么我們?yōu)檫@些 pod 編寫一個(gè)普通的 Service:
- apiVersion: v1
- kind: Service
- metadata:
- name: hostnames
- spec:
- selector:
- app: hostnames
- ports:
- - name: default
- protocol: TCP
- port: 80
- targetPort: 9376
可以看到 Service 通過 selector 選擇 了帶相應(yīng)的標(biāo)簽 Pod,而這些被選中的 Pod,成為 Endpoints,我們可以試一下:
- ~/cloud/k8s kubectl get ep hostnames
- NAME ENDPOINTS
- hostnames 172.28.21.66:9376,172.28.29.52:9376,172.28.70.13:9376
當(dāng)某一個(gè) Pod 出現(xiàn)問題,不處于 running 狀態(tài)或者 readinessProbe 未通過時(shí),Endpoints 列表會(huì)將其摘除。
ClusterIP
以上我們有了 Service 和 Endpoints,而默認(rèn)創(chuàng)建 Service 的類型是 ClusterIP 類型,我們查看一下之前創(chuàng)建的 Service:
- ~ kubectl get svc hostnames
- NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
- hostnames ClusterIP 10.212.8.127 <none> 80/TCP 8m2s
我們看到 ClusterIP 是 10.212.8.127,那么我們此時(shí)可以在 Kubernetes 集群內(nèi)通過這個(gè)地址訪問到 Endpoints 列表里的任意 Pod:
- sh-4.2# curl 10.212.8.127
- hostnames-8548b869d7-9qk6b
- sh-4.2# curl 10.212.8.127
- hostnames-8548b869d7-wzksp
- sh-4.2# curl 10.212.8.127
- hostnames-8548b869d7-bvlw8
訪問了三次 ClusterIP 地址,返回了三個(gè)不同的 hostname,我們意識(shí)到 ClusterIP 模式的 Service 自動(dòng)對(duì)請(qǐng)求做了 round robin 形式的負(fù)載均衡。
對(duì)于此時(shí) ClusterIP 模式 Serivice 來說,它有一個(gè) A 記錄是 service-name.namespace-name.svc.cluster.local,指向 ClusterIP 地址:
- sh-4.2# nslookup hostnames.coops-dev.svc.cluster.local
- Server: 10.212.0.2
- Address: 10.212.0.2#53
- Name: hostnames.coops-dev.svc.cluster.local
- Address: 10.212.8.127
理所當(dāng)然我們通過此 A 記錄去訪問得到的效果一樣:
- sh-4.2# curl hostnames.coops-dev.svc.cluster.local
- hostnames-8548b869d7-wzksp
那對(duì) Pod 來說它的 A 記錄是啥呢,我們可以看一下:
- sh-4.2# nslookup 172.28.21.66
- 66.21.28.172.in-addr.arpa name = 172-28-21-66.hostnames.coops-dev.svc.cluster.local.
Headless service
Service 的 CluserIP 默認(rèn)是 Kubernetes 自動(dòng)分配的,當(dāng)然也可以自己設(shè)置,當(dāng)我們將 CluserIP 設(shè)置成 None 的時(shí)候,它就變成了 Headless service。
Headless service 一般配合 StatefulSet 使用。StatefulSet 是一種有狀態(tài)應(yīng)用的容器編排方式,其核心思想是給予 Pod 指定的編號(hào)名稱,從而讓 Pod 有一個(gè)不變的唯一網(wǎng)絡(luò)標(biāo)識(shí)碼。那這么說來,使用 CluserIP 負(fù)載均衡訪問 Pod 的方式顯然是行不通了,因?yàn)槲覀兛释ㄟ^某個(gè)標(biāo)識(shí)直接訪問到 Pod 本身,而不是一個(gè)虛擬 vip。
這個(gè)時(shí)候我們其實(shí)可以借助 DNS,每個(gè) Pod 都會(huì)有一條 A 記錄 pod-name.service-name.namespace-name.svc.cluster.local 指向 podIP,我們可以通過這條 A 記錄直接訪問到 Pod。
我們編寫相應(yīng)的 StatefulSet 和 Service 來看一下:
- ---
- apiVersion: apps/v1
- kind: StatefulSet
- metadata:
- name: hostnames
- spec:
- serviceName: "hostnames"
- selector:
- matchLabels:
- app: hostnames
- replicas: 3
- template:
- metadata:
- labels:
- app: hostnames
- spec:
- containers:
- - name: hostnames
- image: mirrorgooglecontainers/serve_hostname
- ports:
- - containerPort: 9376
- protocol: TCP
- ---
- apiVersion: v1
- kind: Service
- metadata:
- name: hostnames
- spec:
- selector:
- app: hostnames
- clusterIP: None
- ports:
- - name: default
- protocol: TCP
- port: 80
- targetPort: 9376
如上,StatefulSet 和 deployment 并沒有什么不同,多了一個(gè)字段 spec.serviceName,這個(gè)字段的作用就是告訴 StatefulSet controller,在邏輯處理時(shí)使用 hostnames 這個(gè) Service 來保證 Pod 的唯一可解析性。
當(dāng)你執(zhí)行 apply 之后,一會(huì)你就可以看到生成了對(duì)應(yīng)的 Pod:
- ~ kubectl get pods -w -l app=hostnames
- NAME READY STATUS RESTARTS AGE
- hostnames-0 1/1 Running 0 9m54s
- hostnames-1 1/1 Running 0 9m28s
- hostnames-2 1/1 Running 0 9m24s
如意料之中,這里對(duì) Pod 名稱進(jìn)行了遞增編號(hào),并不重復(fù),同時(shí)這些 Pod 的創(chuàng)建過程也是按照編號(hào)依次串行進(jìn)行的。我們知道,使用 deployment 部署的 Pod 名稱會(huì)加上 replicaSet 名稱和隨機(jī)數(shù),重啟后是不斷變化的。而這邊使用 StatefulSet 部署的 Pod,雖然 podIP 仍然會(huì)變化,但名稱是一直不會(huì)變的,基于此我們得以通過固定的 DNS A 記錄來訪問到每個(gè) Pod。
那么此時(shí),我們來看一下 Pod 的 A 記錄:
- sh-4.2# nslookup hostnames-0.hostnames
- Server: 10.212.0.2
- Address: 10.212.0.2#53
- Name: hostnames-0.hostnames.coops-dev.svc.cluster.local
- Address: 172.28.3.57
- sh-4.2# nslookup hostnames-1.hostnames
- Server: 10.212.0.2
- Address: 10.212.0.2#53
- Name: hostnames-1.hostnames.coops-dev.svc.cluster.local
- Address: 172.28.29.31
- sh-4.2# nslookup hostnames-2.hostnames
- Server: 10.212.0.2
- Address: 10.212.0.2#53
- Name: hostnames-2.hostnames.coops-dev.svc.cluster.local
- Address: 172.28.23.31
和之前的推論一致,我們可以通過 pod-name.service-name.namespace-name.svc.cluster.local 這條 A 記錄訪問到 podIP,在同一個(gè) namespace 中,我們可以簡(jiǎn)化為 pod-name.service-name。
而這個(gè)時(shí)候,Service 的 A 記錄是什么呢:
- sh-4.2# nslookup hostnames
- Server: 10.212.0.2
- Address: 10.212.0.2#53
- Name: hostnames.coops-dev.svc.cluster.local
- Address: 172.28.29.31
- Name: hostnames.coops-dev.svc.cluster.local
- Address: 172.28.3.57
- Name: hostnames.coops-dev.svc.cluster.local
- Address: 172.28.23.31
原來是 Endpoints 列表里的一組 podIP,也就是說此時(shí)你依然可以通過service-name.namespace-name.svc.cluster.local這條 A 記錄來負(fù)載均衡地訪問到后端 Pod。
iptables
或多或少我們知道 Kubernetes 里面的 Service 是基于 kube-proxy 和 iptables 工作的。Service 創(chuàng)建之后可以被 kube-proxy 感知到,那么它會(huì)為此在宿主機(jī)上創(chuàng)建對(duì)應(yīng)的 iptables 規(guī)則。
以 CluserIP 模式的 Service 為例,首先它會(huì)創(chuàng)建一條 KUBE-SERVICES 規(guī)則作為入口:
- -A KUBE-SERVICES -d 10.212.8.127/32 -p tcp -m comment --comment "default/hostnames: cluster IP" -m tcp --dport 80 -j KUBE-SVC-NWV5X2332I4OT4T3
這條記錄的意思是:所有目的地址是 10.212.8.127 這條 CluserIP 的,都將跳轉(zhuǎn)到 KUBE-SVC iptables 鏈處理。
那么我們來看 KUBE-SVC 鏈都是什么:
- -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-WNBA2IHDGP2BOBGZ
- -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-X3P2623AGDH6CDF3
- -A KUBE-SVC-NWV5X2332I4OT4T3 -m comment --comment "default/hostnames:" -j KUBE-SEP-57KPRZ3JQVENLNBR
這組規(guī)則其實(shí)是用于負(fù)載均衡的,我們看到了--probability 依次是 1/3、1/2、1,由于 iptables 規(guī)則是自上而下匹配的,所以設(shè)置這些值能保證每條鏈匹配到的幾率一樣。處理完負(fù)載均衡的邏輯后,又分別將請(qǐng)求轉(zhuǎn)發(fā)到了另外三條規(guī)則,我們來看一下:
- -A KUBE-SEP-57KPRZ3JQVENLNBR -s 172.28.21.66/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
- -A KUBE-SEP-57KPRZ3JQVENLNBR -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.21.66:9376
- -A KUBE-SEP-WNBA2IHDGP2BOBGZ -s 172.28.29.52/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
- -A KUBE-SEP-WNBA2IHDGP2BOBGZ -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.29.52:9376
- -A KUBE-SEP-X3P2623AGDH6CDF3 -s 172.28.70.13/32 -m comment --comment "default/hostnames:" -j MARK --set-xmark 0x00004000/0x00004000
- -A KUBE-SEP-X3P2623AGDH6CDF3 -p tcp -m comment --comment "default/hostnames:" -m tcp -j DNAT --to-destination 172.28.70.13:9376
可以看到 KUBE-SEP 鏈就是三條 DNAT 規(guī)則,并在 DNAT 之前設(shè)置了一個(gè) 0x00004000 的標(biāo)志。DNAT 規(guī)則就是在 PREROUTING,即路由作用之前,將請(qǐng)求的目的地址和端口改為 --to-destination 指定的 podIP 和端口。這樣一來,我們起先訪問 10.212.8.127 這個(gè) CluserIP 的請(qǐng)求,就會(huì)被負(fù)載均衡到各個(gè) Pod 上。
那么 Pod 重啟了,podIP 變了怎么辦?自然是 kube-proxy 負(fù)責(zé)監(jiān)聽 Pod 變化以及更新維護(hù) iptables 規(guī)則了。
而對(duì)于 Headless service 來說,我們直接通過固定的 A 記錄訪問到了 Pod,自然不需要這些 iptables 規(guī)則了。
iptables 理解起來比較簡(jiǎn)單,但實(shí)際上性能并不好??梢韵胂螅?dāng)我們的 Pod 非常多時(shí),成千上萬的 iptables 規(guī)則將被創(chuàng)建出來,并不斷刷新,會(huì)占用宿主機(jī)大量的 CPU 資源。一個(gè)行之有效的方案是基于 IPVS 模式的 Service,IPVS 不需要為每個(gè) Pod 都設(shè)置 iptables 規(guī)則,而是將這些規(guī)則都放到了內(nèi)核態(tài),極大降低了維護(hù)這些規(guī)則的成本。
集群間通信
外界訪問 Service
以上我們講了請(qǐng)求怎么在 Kubernetes 集群內(nèi)互通,主要基于 kube-dns 生成的 DNS 記錄以及 kube-proxy 維護(hù)的 iptables 規(guī)則。而這些信息都是作用在集群內(nèi)的,那么自然我們從集群外訪問不到一個(gè)具體的 Service 或者 Pod 了。
Service 除了默認(rèn)的 CluserIP 模式外,還提供了很多其他的模式,比如 nodePort 模式,就是用于解決該問題的。
- apiVersion: v1
- kind: Service
- metadata:
- name: hostnames
- spec:
- selector:
- app: hostnames
- type: NodePort
- ports:
- - nodePort: 8477
- protocol: TCP
- port: 80
- targetPort: 9376
我們編寫了一個(gè) NodePort 模式的 Service,并且設(shè)置 NodePort 為 8477,那么意味著我們可以通過任意一臺(tái)宿主機(jī)的 8477 端口訪問到 hostnames 這個(gè) Service。
- sh-4.2# curl 10.1.6.25:8477
- hostnames-8548b869d7-j5lj9
- sh-4.2# curl 10.1.6.25:8477
- hostnames-8548b869d7-66vnv
- sh-4.2# curl 10.1.6.25:8477
- hostnames-8548b869d7-szz4f
我們隨便找了一臺(tái) Node 地址去訪問,得到了相同的返回配方。
那么這個(gè)時(shí)候它的 iptables 規(guī)則是怎么作用的呢:
- -A KUBE-NODEPORTS -p tcp -m comment --comment "default/hostnames: nodePort" -m tcp --dport 8477 -j KUBE-SVC-67RL4FN6JRUPOJYM
kube-proxy 在每臺(tái)宿主機(jī)上都生成了如上的 iptables 規(guī)則,通過 --dport 指定了端口,訪問該端口的請(qǐng)求都會(huì)跳轉(zhuǎn)到 KUBE-SVC 鏈上,KUBE-SVC 鏈和之前 CluserIP Service 的配方一樣,接下來就和訪問 CluserIP Service 沒什么區(qū)別了。
不過還需要注意的是,在請(qǐng)求離開當(dāng)前宿主機(jī)發(fā)往其他 Node 時(shí)會(huì)對(duì)其做一次 SNAT 操作:
- -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
可以看到這條 postrouting 規(guī)則給即將離開主機(jī)的請(qǐng)求進(jìn)行了一次 SNAT,判斷條件為帶有 0x4000 標(biāo)志,這就是之前 DNAT 帶的標(biāo)志,從而判斷請(qǐng)求是從 Service 轉(zhuǎn)發(fā)出來的,而不是普通請(qǐng)求。
需要做 SNAT 的原因很簡(jiǎn)單,首先這是一個(gè)外部的未經(jīng) Kubernetes 處理的請(qǐng)求,如果它訪問 node1,node1 的負(fù)載均衡將其轉(zhuǎn)發(fā)給 node2 上的某個(gè) Pod,這沒什么問題,而這個(gè) Pod 處理完后直接返回給外部 client,那么外部 client 就很疑惑,明明自己訪問的是 node1,給自己返回的確是 node2,這時(shí)往往會(huì)報(bào)錯(cuò)。
SNAT 的作用與 DNAT 相反,就是在請(qǐng)求從 node1 離開發(fā)往 node2 時(shí),將源地址改為 node1 的地址,那么當(dāng) node2 上的 Pod 返回時(shí),會(huì)返回給 node1,然后再讓 node1 返回給 client。
- client
- | ^
- | |
- v |
- node 2 <--- node 1
- | ^ SNAT
- | | --->
- v |
- endpoints
Service 還有另外 2 種通過外界訪問的方式。適用于公有云的 LoadBalancer 模式的 service,公有云 Kubernetes 會(huì)調(diào)用 CloudProvider 在公有云上為你創(chuàng)建一個(gè)負(fù)載均衡服務(wù),并且把被代理的 Pod 的 IP 地址配置給負(fù)載均衡服務(wù)做后端。另外一種是 ExternalName 模式,可以通過在 spec.externalName 來指定你想要的外部訪問域名,例如 hostnames.example.com,那么你訪問該域名和訪問 service-name.namespace-name.svc.cluser.local 效果是一樣的,這時(shí)候你應(yīng)該知道,其實(shí) kube-dns 為你添加了一條 CNAME 記錄。
Ingress
Service 有一種類型叫作 LoadBalancer,不過如果每個(gè) Service 對(duì)外都配置一個(gè)負(fù)載均衡服務(wù),成本很高而且浪費(fèi)。一般來說我們希望有一個(gè)全局的負(fù)載均衡器,通過訪問不同 url,轉(zhuǎn)發(fā)到不同 Service 上,而這就是 Ingress 的功能,Ingress 可以看做是 Service 的 Service。
Ingress 其實(shí)是對(duì)反向代理的一種抽象,相信大家已經(jīng)感覺到,這玩意兒和 Nginx 十分相似,實(shí)際上 Ingress 是抽象層,而其實(shí)現(xiàn)層其中之一就支持 Nginx。
我們可以部署一個(gè) nginx ingress controller:
- $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/mandatory.yaml
mandatory.yaml是官方維護(hù)的 ingress controller,我們看一下:
- kind: ConfigMap
- apiVersion: v1
- metadata:
- name: nginx-configuration
- namespace: ingress-nginx
- labels:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
- ---
- apiVersion: extensions/v1beta1
- kind: Deployment
- metadata:
- name: nginx-ingress-controller
- namespace: ingress-nginx
- labels:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
- spec:
- replicas: 1
- selector:
- matchLabels:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
- template:
- metadata:
- labels:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
- annotations:
- ...
- spec:
- serviceAccountName: nginx-ingress-serviceaccount
- containers:
- - name: nginx-ingress-controller
- image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.20.0
- args:
- - /nginx-ingress-controller
- - --configmap=$(POD_NAMESPACE)/nginx-configuration
- - --publish-service=$(POD_NAMESPACE)/ingress-nginx
- - --annotations-prefix=nginx.ingress.kubernetes.io
- securityContext:
- capabilities:
- drop:
- - ALL
- add:
- - NET_BIND_SERVICE
- # www-data -> 33
- runAsUser: 33
- env:
- - name: POD_NAME
- valueFrom:
- fieldRef:
- fieldPath: metadata.name
- - name: POD_NAMESPACE
- - name: http
- valueFrom:
- fieldRef:
- fieldPath: metadata.namespace
- ports:
- - name: http
- containerPort: 80
- - name: https
- containerPort: 443
總的來說,我們定義了一個(gè)基于 nginx-ingress-controller 鏡像的 Pod,而這個(gè) Pod 自身,是一個(gè)監(jiān)聽 Ingress 對(duì)象及其代理后端 Service 變化的控制器。
當(dāng)一個(gè) Ingress 對(duì)象被創(chuàng)建時(shí),nginx-ingress-controller 就會(huì)根據(jù) Ingress 對(duì)象里的內(nèi)容,生成一份 Nginx 配置文件(nginx.conf),并依此啟動(dòng)一個(gè) Nginx 服務(wù)。
當(dāng) Ingress 對(duì)象被更新時(shí),nginx-ingress-controller 就會(huì)更新這個(gè)配置文件。nginx-ingress-controller 還通過 Nginx Lua 方案實(shí)現(xiàn)了 nginx upstream 的動(dòng)態(tài)配置。
為了讓外界可以訪問到這個(gè) Nginx,我們還得給它創(chuàng)建一個(gè) Service 來把 Nginx 暴露出去:
- $ kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
這里面的內(nèi)容描述了一個(gè) NodePort 類型的 Service:
- apiVersion: v1
- kind: Service
- metadata:
- name: ingress-nginx
- namespace: ingress-nginx
- labels:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
- spec:
- type: NodePort
- ports:
- - name: http
- port: 80
- targetPort: 80
- protocol: TCP
- - name: https
- port: 443
- targetPort: 443
- protocol: TCP
- selector:
- app.kubernetes.io/name: ingress-nginx
- app.kubernetes.io/part-of: ingress-nginx
可以看到這個(gè) Service 僅僅是把 Nginx Pod 的 80/443 端口暴露出去,完了你就可以通過宿主機(jī) IP 和 NodePort 端口訪問到 Nginx 了。
接下來我們來看 Ingress 對(duì)象一般是如何編寫的,我們可以參考一個(gè)例子。
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: cafe-ingress
- spec:
- tls:
- - hosts:
- - cafe.example.com
- secretName: cafe-secret
- rules:
- - host: cafe.example.com
- http:
- paths:
- - path: /tea
- backend:
- serviceName: tea-svc
- servicePort: 80
- - path: /coffee
- backend:
- serviceName: coffee-svc
- servicePort: 80
這個(gè) Ingress 表明我們整體的域名是 cafe.example.com,希望通過 cafe.example.com/tea 訪問 tea-svc 這個(gè) Service,通過 cafe.example.com/coffee 訪問 coffee-svc 這個(gè) Service。這里我們通過關(guān)鍵字段 spec.rules 來編寫轉(zhuǎn)發(fā)規(guī)則。
我們可以查看到 Ingress 對(duì)象的詳細(xì)信息:
- $ kubectl get ingress
- NAME HOSTS ADDRESS PORTS AGE
- cafe-ingress cafe.example.com 80, 443 2h
- $ kubectl describe ingress cafe-ingress
- Name: cafe-ingress
- Namespace: default
- Address:
- Default backend: default-http-backend:80 (<none>)
- TLS:
- cafe-secret terminates cafe.example.com
- Rules:
- Host Path Backends
- ---- ---- --------
- cafe.example.com
- /tea tea-svc:80 (<none>)
- /coffee coffee-svc:80 (<none>)
- Annotations:
- Events:
- Type Reason Age From Message
- ---- ------ ---- ---- -------
- Normal CREATE 4m nginx-ingress-controller Ingress default/cafe-ingress
我們之前講了我們通過 NodePort 的方式將 nginx-ingress 暴露出去了,而這時(shí)候我們 Ingress 配置又希望通過 cafe.example.com 來訪問到后端 Pod,那么首先 cafe.example.com 這個(gè)域名得指到任意一臺(tái)宿主機(jī) Ip:nodePort上,請(qǐng)求到達(dá) nginx-ingress 之后再轉(zhuǎn)發(fā)到各個(gè)后端 Service 上。當(dāng)然,暴露 nginx-ingress 的方式有很多種,除了 NodePort 外還包括 LoadBalancer、hostNetwork 方式等等。
我們最后來試一下請(qǐng)求:
- $ curl cafe.example.com/coffee
- Server name: coffee-7dbb5795f6-vglbv
- $ curl cafe.example.com/tea
- Server name: tea-7d57856c44-lwbnp
可以看到 Nginx Ingress controller 已經(jīng)為我們成功將請(qǐng)求轉(zhuǎn)發(fā)到了對(duì)應(yīng)的后端 Service。而當(dāng)請(qǐng)求沒有匹配到任何一條 ingress rule 的時(shí)候,理所當(dāng)然我們會(huì)得到一個(gè) 404。
至此,Kubernetes 的容器網(wǎng)絡(luò)是怎么實(shí)現(xiàn)服務(wù)發(fā)現(xiàn)的已經(jīng)講完了,而服務(wù)發(fā)現(xiàn)正是微服務(wù)架構(gòu)中最核心的問題,解決了這個(gè)問題,那么使用 Kubernetes 來實(shí)現(xiàn)微服務(wù)架構(gòu)也就實(shí)現(xiàn)了一大半。