· 一個(gè)外部的business-manager請求,首先進(jìn)入集群的入口(ingress),ingress反向代理后負(fù)載到business-manager的service。Service層再負(fù)載到某個(gè)node下的具體的business-manager pod
· Business-manager pod再將請求發(fā)往data-product的service,同理,service層繼續(xù)隨機(jī)選擇一個(gè)data-product的Pod來接收請求
· 上面這個(gè)請求,涉及到容器的網(wǎng)絡(luò)-docker0、跨主機(jī)通訊-flannel網(wǎng)絡(luò)方案、ingress和service組件,以及DNS等,下面我會(huì)挨個(gè)介紹它們的基本原理。
寫這個(gè)文檔的同時(shí),我在虛擬機(jī)上搭建了一個(gè)K8S環(huán)境,集群內(nèi)包含2臺主機(jī),ip分別為192.168.0.21和192.168.0.22,主要組件為ingress->nginx、service->kube-proxy、網(wǎng)絡(luò)->flannel,我們以這個(gè)集群為例進(jìn)行分析。
在深入之前,我們先科普一下K8S集群內(nèi)常見IP的含義:
# kubectl get po -o wideNAME READY STATUS RESTARTS AGE IP NODE business-manager-666f454f7f-bg2bt 1/1 Running 0 153m 172.30.76.4 192.168.0.21business-manager-666f454f7f-kvn5z 1/1 Running 0 153m 172.30.76.5 192.168.0.21business-manager-666f454f7f-ncjp7 1/1 Running 0 153m 172.30.9.4 192.168.0.22data-product-6664c6dcb9-7sxnz 1/1 Running 0 160m 172.30.76.2 192.168.0.21data-product-6664c6dcb9-j2f48 1/1 Running 0 160m 172.30.76.3 192.168.0.21data-product-6664c6dcb9-p5xkw 1/1 Running 0 160m 172.30.9.3 192.168.0.22
Node ip:宿主機(jī)的ip,由路由器分配。上圖最右邊的NODE列代表的就是容器所在的宿主機(jī)的物理ip,可以看到現(xiàn)在集群內(nèi)2臺主機(jī)都有分配容器。
Pod ip:被docker0網(wǎng)橋隔離的pod子網(wǎng)的ip。K8s在每個(gè)Node里虛擬出的局域網(wǎng)。上圖的IP列,就是每個(gè)pod ip,可以看到同一宿主機(jī)下Pod在同網(wǎng)段(后面我會(huì)介紹不同的node下的Pod,是如何借助flannel來實(shí)現(xiàn)跨主機(jī)通訊的)
# kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEbusiness-manager ClusterIP 10.254.80.22 <none> 80/TCP 156mdata-product ClusterIP 10.254.116.224 <none> 50051/TCP 159mkubernetes ClusterIP 10.254.0.1 <none> 443/TCP 5h8m
Cluster ip:k8s分配給每個(gè)service的全局唯一的虛擬ip,也可以叫VIP。VIP沒有掛接到網(wǎng)絡(luò)設(shè)備,不能直接訪問。(后面會(huì)介紹這個(gè)ip的用處)
除了上面的3個(gè)主要ip,集群里還有其他的一些特定的ip和網(wǎng)段:
· DNS服務(wù)器:這里配置的是10.254.0.2:53
· 10.254.0.0/16網(wǎng)段,是可配置的當(dāng)前集群的網(wǎng)段,DNS和service的虛擬Ip正是處在這個(gè)網(wǎng)段里。
在介紹Ingress和service這兩個(gè)組件之前,我們先簡單了解一下k8s節(jié)點(diǎn)之間的底層網(wǎng)絡(luò)原理及典型的flannel-VXLAN方案。后面的章節(jié),默認(rèn)在節(jié)點(diǎn)之間的傳輸,都會(huì)有docker0網(wǎng)橋和flannel插件的功勞。(有資料提到K8S采用cni0網(wǎng)橋替代了docker0網(wǎng)橋,兩者的原理是一樣的,我搭建的環(huán)境里只有docker0網(wǎng)橋,所以我們按docker0來分析)
# kubectl get po -o wideNAME READY STATUS RESTARTS AGE IP NODE business-manager-666f454f7f-7l86b 1/1 Running 1 11m 172.30.76.7 192.168.0.21business-manager-666f454f7f-h5tvw 1/1 Running 1 11m 172.30.76.6 192.168.0.21business-manager-666f454f7f-zxmsx 1/1 Running 0 8s 172.30.9.3 192.168.0.22data-product-6664c6dcb9-4zk27 1/1 Running 1 11m 172.30.76.5 192.168.0.21data-product-6664c6dcb9-7bn7p 1/1 Running 1 11m 172.30.76.3 192.168.0.21data-product-6664c6dcb9-tkmms 1/1 Running 0 5m39s 172.30.9.2 192.168.0.22
大家注意到?jīng)]有,每個(gè)pod具備不同的Ip(這里指k8s集群內(nèi)可訪問的虛擬ip),不同node下的pod甚至在不同的網(wǎng)段。那么問題來了,集群內(nèi)不同IP、不同網(wǎng)段的節(jié)點(diǎn)是怎么實(shí)現(xiàn)通訊的呢?這樣歸功于docker0和flannel.1這兩個(gè)虛擬網(wǎng)絡(luò)設(shè)備,我們先ifconfig查看一下:
# ifconfigdocker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 172.30.76.1 netmask 255.255.255.0 broadcast 172.30.76.255 inet6 fe80::42:67ff:fe05:b530 prefixlen 64 scopeid 0x20<link> ether 02:42:67:05:b5:30 txqueuelen 0 (Ethernet) RX packets 31332 bytes 2136665 (2.0 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 21146 bytes 2125957 (2.0 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.0.21 netmask 255.255.255.0 broadcast 192.168.0.255 inet6 fe80::d34:64ee:27c8:3713 prefixlen 64 scopeid 0x20<link> ether 00:15:5d:02:b2:00 txqueuelen 1000 (Ethernet) RX packets 1588685 bytes 265883182 (253.5 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 1604521 bytes 211279156 (201.4 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0flannel.1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 172.30.21.0 netmask 255.255.255.255 broadcast 0.0.0.0 inet6 fe80::8822:81ff:fe5e:d8b7 prefixlen 64 scopeid 0x20<link> ether 8a:22:81:5e:d8:b7 txqueuelen 0 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 0 bytes 0 (0.0 B) TX errors 0 dropped 8 overruns 0 carrier 0 collisions 0...
部署flannel和docker后,會(huì)在宿主機(jī)上創(chuàng)建上述兩個(gè)網(wǎng)絡(luò)設(shè)備。接下來我們通過一個(gè)示意圖來了解這兩個(gè)設(shè)備的工作:
· K8s在每個(gè)宿主機(jī)(node)上創(chuàng)建了cni0網(wǎng)橋(這篇文檔對應(yīng)的集群環(huán)境采用的是docker0網(wǎng)橋,原理一樣):容器的網(wǎng)關(guān),實(shí)際指向的是這個(gè)網(wǎng)橋。
· Flannel則在每個(gè)宿主機(jī)上創(chuàng)建了一個(gè)VTEP(虛擬隧道端點(diǎn))設(shè)備flannel.1。
現(xiàn)在我們來分析下docker0和flannel.1是怎么實(shí)現(xiàn)跨主機(jī)通訊的(由node1的business-manager:172.30.76.7發(fā)往node2的data-product:172.30.9.2):
# routeKernel IP routing tableDestination Gateway Genmask Flags Metric Ref Use Ifacedefault gateway 0.0.0.0 UG 100 0 0 eth0172.30.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1172.30.76.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
上圖是node1的路由表:第2行表示凡是發(fā)往172.30.0.0/16網(wǎng)段的包均交給node1-flannel.1設(shè)備處理;第3行表示凡是發(fā)往172.30.76.0/8網(wǎng)段的包均交給node1-docker0網(wǎng)橋處理。
于是business- manager的請求,首先到達(dá)node1-docker0網(wǎng)橋,目的地址是172.30.9.2,只能匹配第2條規(guī)則,請求被交給node1-flannel.1設(shè)備。
node1-flannel.1又如何處理呢?請看下圖,展示的是flannel.1的ARP表:
# ip neigh show dev flannel.1172.30.9.2 lladdr 96:8f:2d:49:c5:31 REACHABLE172.30.9.1 lladdr 96:8f:2d:49:c5:31 REACHABLE172.30.9.0 lladdr 96:8f:2d:49:c5:31 STALE
node1-flannel.1的ARP表記錄的是ip和對應(yīng)節(jié)點(diǎn)上的flannel.1設(shè)備mac的映射。于是發(fā)往172.30.9.2匹配到了上述第1條規(guī)則,需要發(fā)往mac地址為96:8f:2d:49:c5:31的設(shè)備。
# bridge fdb show flannel.1 |grep 96:8f:2d:49:c5:3196:8f:2d:49:c5:31 dev flannel.1 dst 192.168.0.22 self permanent
這時(shí)候node1-flannel.1設(shè)備又扮演一個(gè)網(wǎng)橋的角色,上圖為node1上查詢出的橋接規(guī)則,96:8f:2d:49:c5:31的目的ip對應(yīng)于192.168.0.22,這正是我們這個(gè)例子里node2的宿主機(jī)Ip。于是這個(gè)請求被轉(zhuǎn)發(fā)給了node2。
不難理解,node2也有一個(gè)像第1步那樣的路由表,于是來自node1-business-manager:172.30.76.7的請求最終經(jīng)node2-docker0送達(dá)node2-data-product:172.30.9.2。
· 隨著node和pod加入和退出集群,flannel進(jìn)程會(huì)從ETCD感知相應(yīng)的變化,并及時(shí)更新上面的規(guī)則。
· 現(xiàn)在我們已實(shí)現(xiàn)通過ip訪問pod,但pod的ip隨著k8s調(diào)度會(huì)變化,不可能隔三差五的去人工更新每個(gè)ip配置吧,這就需要service這個(gè)組件了,請看下一章。
pod的ip不是固定的,而且同一服務(wù)的多個(gè)pod需要有負(fù)載均衡,這正是創(chuàng)建service的目的。
Service是由kube-proxy組件和iptables來共同實(shí)現(xiàn)的。
分析service原理前,大家可以先帶上這個(gè)問題:service的ip為什么ping不通?
OK,我們現(xiàn)在直接上圖,隨便一個(gè)node的iptables(內(nèi)容比較豐富,我隨便截了幾段,下文會(huì)挑幾個(gè)重要的規(guī)則展開分析):
# iptables-save...-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT-A KUBE-FORWARD -s 10.254.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT-A KUBE-FORWARD -d 10.254.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT-A KUBE-SERVICES -d 10.254.0.2/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp has no endpoints" -m tcp --dport 53 -j REJECT --reject-with icmp-port-unreachable-A KUBE-SERVICES -d 10.254.0.2/32 -p udp -m comment --comment "kube-system/kube-dns:dns has no endpoints" -m udp --dport 53 -j REJECT --reject-with icmp-port-unreachable...-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE-A KUBE-SEP-CNIAJ35IU3EJ7UR6 -s 172.30.9.3/32 -j KUBE-MARK-MASQ-A KUBE-SEP-CNIAJ35IU3EJ7UR6 -p tcp -m tcp -j DNAT --to-destination 172.30.9.3:8080-A KUBE-SEP-DGXT5Z3WOYVLBGRM -s 172.30.76.3/32 -j KUBE-MARK-MASQ-A KUBE-SEP-DGXT5Z3WOYVLBGRM -p tcp -m tcp -j DNAT --to-destination 172.30.76.3:50051...-A KUBE-SERVICES -d 10.254.80.22/32 -p tcp -m comment --comment "default/business-manager:business-manager cluster IP" -m tcp --dport 80 -j KUBE-SVC-FZ5DC5B5DCQ4E7RC-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS-A KUBE-SVC-45TXGSBX3LGQQRTB -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-DGXT5Z3WOYVLBGRM-A KUBE-SVC-45TXGSBX3LGQQRTB -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-P6GCAAVN4MLBXK7I-A KUBE-SVC-45TXGSBX3LGQQRTB -j KUBE-SEP-QFJ7ESRM37V67WJQ...
現(xiàn)在可以回答service ip ping不通的問題了,因?yàn)閟ervice不是真實(shí)存在的(沒有掛接具體的網(wǎng)絡(luò)設(shè)備),而是由上圖這些iptables規(guī)則組成的一個(gè)虛擬的服務(wù)。
· Iptables是linux內(nèi)核提供給用戶的可配置的網(wǎng)絡(luò)層防火墻規(guī)則,內(nèi)核在解析網(wǎng)絡(luò)層ip數(shù)據(jù)包時(shí),會(huì)加入相應(yīng)的檢查點(diǎn),匹配iptables定義的規(guī)則。
# kubectl get svcNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEbusiness-manager ClusterIP 10.254.80.22 <none> 80/TCP 3h28mdata-product ClusterIP 10.254.116.224 <none> 50051/TCP 3h32mkubernetes ClusterIP 10.254.0.1 <none> 443/TCP 6h
· 我們還是看第3章的例子,business-manager要訪問data-product,于是往service-data-product的ip和port(10.254.116.224:50051)發(fā)送請求。每個(gè)service對象被創(chuàng)建時(shí),k8s均會(huì)分配一個(gè)集群內(nèi)唯一的ip給它,并且該ip伴隨service的生命周期不會(huì)變化,這就解決了本節(jié)開篇的Pod ip不固定的問題。
-A KUBE-SERVICES -d 10.254.116.224/32 -p tcp -m comment --comment "default/data-product:data-product cluster IP" -m tcp --dport 50051 -j KUBE-SVC-45TXGSBX3LGQQRTB
· KUBE-SERVICES:Iptables表里存在上面這條規(guī)則,表示發(fā)往10.254.116.224:50051的數(shù)據(jù)包,跳轉(zhuǎn)到KUBE-SVC-45TXGSBX3LGQQRTB規(guī)則。
-A KUBE-SVC-45TXGSBX3LGQQRTB -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-DGXT5Z3WOYVLBGRM-A KUBE-SVC-45TXGSBX3LGQQRTB -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-P6GCAAVN4MLBXK7I-A KUBE-SVC-45TXGSBX3LGQQRTB -j KUBE-SEP-QFJ7ESRM37V67WJQ
· KUBE-SVC-xxx:這條規(guī)則,實(shí)際上是一條規(guī)則鏈,data-product我建了3個(gè)pod,所以這條規(guī)則鏈對應(yīng)的正是這3個(gè)pod。這里是service負(fù)載均衡的關(guān)鍵實(shí)現(xiàn),第1條規(guī)則表示采用隨機(jī)模式,有1/3(33%)的概率跳轉(zhuǎn)到KUBE-SEP-DGXT5Z3WOYVLBGRM;第2條規(guī)則的概率是1/2(50%);第3條則直接跳轉(zhuǎn)。這里有個(gè)需要注意的地方,iptables是順序往下匹配的,所以多節(jié)點(diǎn)隨機(jī)算法,概率是遞增的,以data-product為例,我配置了3個(gè)Pod,就有3條規(guī)則,第1條被選中的概率為1/3,第2條則為1/2,最后1條沒得挑了,概率配置為1或直接跳轉(zhuǎn)。
-A KUBE-SEP-P6GCAAVN4MLBXK7I -s 172.30.76.5/32 -j KUBE-MARK-MASQ-A KUBE-SEP-P6GCAAVN4MLBXK7I -p tcp -m tcp -j DNAT --to-destination 172.30.76.5:50051
· KUBE-SEP-xxx:假設(shè)隨機(jī)到第2條KUBE-SEP-P6GCAAVN4MLBXK7I,這里又是兩條規(guī)則。第1條是給轉(zhuǎn)發(fā)的數(shù)據(jù)包加標(biāo)簽Mark,目的是在集群多入口的場景下,保證數(shù)據(jù)包從哪進(jìn)來的就從哪個(gè)node返回給客戶端,詳細(xì)原理就不展開說了。同時(shí)這里還涉及到一個(gè)技術(shù)點(diǎn),經(jīng)過service轉(zhuǎn)發(fā)的數(shù)據(jù)包,pod只能追查到轉(zhuǎn)發(fā)的service所在的Node,如果有場景需要Pod明確知道外部client的源Ip,可以借用service的spec.externalTrafficPolicy=local字段實(shí)現(xiàn)。
· KUBE-SEP-xxx:第2條規(guī)則就很簡單了,數(shù)據(jù)包轉(zhuǎn)發(fā)給172.30.76.5:50051,這里已經(jīng)拿到pod的ip和port,可以通過第3章的docker0和flannel.1網(wǎng)絡(luò)進(jìn)行通信了。
上面是基于iptables的service方案,存在一個(gè)風(fēng)險(xiǎn),當(dāng)pod數(shù)量很大,幾百、幾千時(shí),遍歷iptables將會(huì)是性能瓶頸。IPVS虛擬網(wǎng)卡技術(shù)在大量級的pod場景下表現(xiàn)比iptables優(yōu)秀(運(yùn)維的同事反饋11版本的k8s,官方已默認(rèn)采用IPVS)。這不屬于本文檔的目的,不展開說。
本節(jié)開頭我們提到service是由kube-proxy和iptables共同實(shí)現(xiàn)的,所以Kube-proxy所扮演的角色就不難想象了,kube-proxy負(fù)責(zé)感知集群的變化,及時(shí)更新service的規(guī)則。
最后,我們還面臨著一個(gè)小問題,上面的過程是基于服務(wù)的VIP的訪問服務(wù)的,通過服務(wù)名的方式訪問又是怎么實(shí)現(xiàn)的呢,請看下一節(jié):DNS
本來寫這個(gè)文檔沒想到要有DNS這一章節(jié)的,但集群搭建好之后發(fā)現(xiàn)通過服務(wù)名無法訪問服務(wù),通過VIP卻可以,才想起來集群還需要額外搭個(gè)DNS組件。
# kubectl get po -n kube-systemNAME READY STATUS RESTARTS AGEkube-dns-7cd94476cd-kr76t 4/4 Running 0 25s
DNS組件是跑在kube-system命名空間下的一個(gè)pod,監(jiān)聽著集群ip:10.254.0.2:53。通過這個(gè)Ip:port(創(chuàng)建kubelet時(shí)指定DNS的ip)即可獲取到集群內(nèi)部的DNS解析服務(wù)。
現(xiàn)在我們隨便進(jìn)入一個(gè)pod里,可以看到dns的信息已被k8s寫入。同時(shí)我們ping一個(gè)service:
# cat /etc/resolv.confnameserver 10.254.0.2search default.svc.cluster.local svc.cluster.local cluster.localoptions ndots:5# ping data-product.default.svc.cluster.localPING data-product.default.svc.cluster.local (10.254.116.224) 56(84) bytes of data.^C--- data-product.default.svc.cluster.local ping statistics ---3 packets transmitted, 0 received, 100% packet loss, time 1999ms
當(dāng)然是ping不通的,但vip已經(jīng)被解析出來了。
Kubenetes可以為pod提供穩(wěn)定的DNS名字,且這個(gè)名字可通過pod名和service名拼接出來,以上面的data-product為例,該服務(wù)的完整域名是[服務(wù)名].[命名空間].svc.[集群名稱]。相應(yīng)的,每個(gè)pod也有類似規(guī)則的域名。
Service代理的是集群內(nèi)部的ip和端口,出了集群這個(gè)ip:port就沒什么意義了。所以如何在集群外部訪問到service呢?
方式一:配置service的type=NodePort,此方式下k8s會(huì)給service做端口映射。這種方式是最常用的,我們DEV環(huán)境下很多service做了端口映射,可以通過宿主機(jī)Ip加映射出去的端口號直接訪問服務(wù)。這種方式的原理簡單,kube-proxy只需要在iptables里增加一條規(guī)則,將外部端口的包導(dǎo)向第4章的service規(guī)則去處理即可。(下一節(jié)要講的ingress,正是這種方式的一種更細(xì)致的實(shí)現(xiàn))
方式二:type=LoadBalancer,適用于公有云提供的K8s環(huán)境,此時(shí)K8s使用一個(gè)叫作CloudProvider的轉(zhuǎn)接層與公有云的API交互,并由公有云API來實(shí)現(xiàn)負(fù)載均衡。
方式三:type=ExternalName,這個(gè)方式的用法我還沒搞清楚。
按前面章節(jié)的套路,這里我們依然會(huì)面臨一個(gè)小問題,把外部需要訪問的服務(wù)大量的通過端口映射方式暴露出去,勢必給端口的管理帶來麻煩。所以,接下來我們看看ingress是怎么作為集群的入口,幫我們管理后端服務(wù)的。
4.2章節(jié),在集群內(nèi)部我們實(shí)現(xiàn)了通過域名(服務(wù)名)獲取具體的服務(wù)vip,從而免去了管理Vip煩惱。那么從外部訪問集群的服務(wù),又如何實(shí)現(xiàn)通過域名的方式呢?后端的服務(wù)有很多,我們也需要一個(gè)全局的負(fù)載均衡器來管理后面服務(wù)。這就是ingress。
# kubectl get po -n ingress-nginxNAME READY STATUS RESTARTS AGEnginx-ingress-controller-546bfbff9-hpwsz 1/1 Running 0 84s
· 使用ingress,我們除了要?jiǎng)?chuàng)建ingress對象以外,還需要安裝一個(gè)ingress-controller,這里我們選擇最常用的nginx-ingress-controller。如上所示,安裝之后,會(huì)增加一個(gè)ingress-nginx命名空間,運(yùn)行著nginx-ingress-controller容器。
# kubectl exec -ti nginx-ingress-controller-546bfbff9-hpwsz sh -n ingress-nginx$ more /etc/nginx/nginx.conf...## start server data-product server { server_name data-product ; listen 80; set $proxy_upstream_name "-"; location / { set $namespace "default"; set $ingress_name "data-product"; set $service_name "data-product"; set $service_port "50051"; set $location_path "/";...
· 當(dāng)Ingress對象被創(chuàng)建時(shí),nginx-ingress-controller會(huì)在這個(gè)nginx容器內(nèi)部生成一個(gè)配置文件/etc/nginx/nginx.conf(內(nèi)容比較豐富,上圖我截了一小段,可以看到data-product.default的主要配置),并用這個(gè)文件啟動(dòng)nginx服務(wù)。當(dāng)ingress對象被更新時(shí),nginx-ingress-controller會(huì)實(shí)現(xiàn)nginx服務(wù)的動(dòng)態(tài)更新。
# cat ing.yaml ---apiVersion: extensions/v1beta1kind: Ingressmetadata: name: business-user namespace: ns-jo annotations: kubernetes.io/ingress.class: "nginx"spec: rules: - host: business-user.ns-jo http: paths: - path: / backend: serviceName: business-user servicePort: 80
· Nginx服務(wù)的功能:隨便找一個(gè)ingress文件查看,rules字段包含一組域名、路徑、后端服務(wù)名、服務(wù)端口的映射,這就是個(gè)反向代理的配置文件。當(dāng)前我們用nginx做反向代理,以及將請求負(fù)載給后端的service。加上證書,nginx還可以解析https,給后端依然是http明文通信
現(xiàn)在又面臨一個(gè)小問題了,這個(gè)nginx服務(wù)居然運(yùn)行在容器里,參考5.1章節(jié),這個(gè)服務(wù)外部還是訪問不了???所以安裝nginx-ingress-controller時(shí)還需要?jiǎng)?chuàng)建一個(gè)服務(wù),將這個(gè)pod里的nginx服務(wù)監(jiān)聽的80和443端口暴露出去。
# kubectl describe svc ingress-nginx -n ingress-nginxName: ingress-nginxNamespace: ingress-nginxLabels: app.kubernetes.io/name=ingress-nginx app.kubernetes.io/part-of=ingress-nginxAnnotations: <none>Selector: app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginxType: NodePortIP: 10.254.189.164Port: http 80/TCPTargetPort: 80/TCPNodePort: http 30799/TCPEndpoints: 172.30.76.2:80Port: https 443/TCPTargetPort: 443/TCPNodePort: https 31522/TCPEndpoints: 172.30.76.2:443Session Affinity: NoneExternal Traffic Policy: ClusterEvents: <none>
上面這個(gè)服務(wù),正是ingress-nginx的SVC,它向外暴露的端口(NodePort)是30799和31522,對應(yīng)的endpoints正是nginx容器里的nginx服務(wù)監(jiān)聽的兩個(gè)端口80和433。這個(gè)ingress-service加上ingress-nginx容器,共同組成了ingress。所以廣義上,ingress提供的是集群入口服務(wù),是一個(gè)虛擬的概念。不考慮具體的功能的話,business層以NodePort方式運(yùn)作時(shí),就可以看作business層就是data層的ingress。
現(xiàn)在我們可以用business-manager.default:30799/api/v1/product/list來發(fā)起請求。
原理分析的再多再深入,最終還是希望能夠?yàn)槲覀兊墓ぷ魈峁┮恍椭?。所以下面的篇幅我記錄了在分析過程中看到或是想到的可能有助于我們實(shí)際工作的思路,限于精力有限,這些思路我暫時(shí)還沒有完整驗(yàn)證過,同學(xué)們有興趣的話可以參與進(jìn)來。
當(dāng)前DB的ip和端口是配置在每個(gè)應(yīng)用的configmap里的,如果出現(xiàn)DB切換、遷移等因素導(dǎo)致IP或端口變更,我們需要挨個(gè)去修改每個(gè)應(yīng)用的config。
K8s支持指定service的endpoints為一個(gè)特定的點(diǎn),比如可以指定為DB的IP和端口。這樣我們可以創(chuàng)建兩個(gè)service:service-DB-read,和service-DB-write。由service來管理DB的IP和PORT,變更只需要修改這兩個(gè)service的config即可。由4.1章節(jié)的分析我們知道,應(yīng)用訪問上述兩個(gè)service,數(shù)據(jù)包會(huì)被轉(zhuǎn)發(fā)給endpoints也就是真正的db。
請見下圖,Endpoints指向集群外部數(shù)據(jù)庫的service-mysql:
# kubectl describe svc mysqlName: mysqlNamespace: defaultLabels: <none>Annotations: <none>Selector: <none>Type: ClusterIPIP: 10.254.84.209Port: <unset> 3306/TCPTargetPort: 3306/TCPEndpoints: 192.168.0.103:3306Session Affinity: NoneEvents: <none>
應(yīng)用層通過訪問service-mysql,流量最終會(huì)到達(dá)endpoints也就是集群外部的真實(shí)數(shù)據(jù)庫的ip:port。細(xì)心的同學(xué)應(yīng)該能想到,這玩意可以用于簡單的數(shù)據(jù)庫負(fù)載均衡,比如有多個(gè)讀庫的情況下,我們只需要讓service-mysql的endpoints指向這幾個(gè)讀庫,流量即能被負(fù)載均衡到各個(gè)庫。
以DB為例,當(dāng)前集群的DB對所有pod開放,那有沒有辦法限制訪問權(quán)限呢,比如只允許data層訪問?;叵氲?.1章節(jié)service的本質(zhì)是iptables規(guī)則,那么就有可能通過iptables實(shí)現(xiàn)更細(xì)致的規(guī)則,比如DB的訪問權(quán)限管理。這就是k8s的NetworkPolicy,支持以pod的標(biāo)簽的形式制定相應(yīng)的iptables規(guī)則。目前flannel網(wǎng)絡(luò)插件不支持NetworkPolicy,flannel Calico插件可以實(shí)現(xiàn)。