Kubernetes(k8s)底層網路原理刨析
目錄
- 1 典型的資料傳輸流程圖
- 2 3種ip說明
- 3 Docker0網橋和flannel網路方案
- 4 Service和DNS
- 4.1 service
- 4.2 DNS
- 5 外部訪問叢集
- 5.1 外部訪問service
- 5.2 ingress
- 附 擴充套件實戰
- 附A 用service實現DB的管理
- 附B 用NetworkPolicy實現訪問許可權隔離
- 附C 用secret物件管理賬戶密碼
1 典型的資料傳輸流程圖
• 一個外部的business-manager請求,首先進入叢集的入口(ingress),ingress反向代理後負載到business-manager的service。Service層再負載到某個node下的具體的business-manager pod
• Business-manager pod再將請求發往data-product的service,同理,service層繼續隨機選擇一個data-product的Pod來接收請求
• 上面這個請求,涉及到容器的網路-docker0、跨主機通訊-flannel網路方案、ingress和service元件,以及DNS等,下面我會挨個介紹它們的基本原理。
2 3種ip說明
寫這個文件的同時,我在虛擬機器上搭建了一個K8S環境,叢集內包含2臺主機,ip分別為192.168.0.21和192.168.0.22,主要元件為ingress->nginx、service->kube-proxy、網路->flannel,我們以這個叢集為例進行分析。
在深入之前,我們先科普一下K8S叢集內常見IP的含義:
# kubectl get po -o wide NAME READY STATUS RESTARTS AGE IP NODE business-manager-666f454f7f-bg2bt 1/1 Running 0 153m 172.30.76.4 192.168.0.21 business-manager-666f454f7f-kvn5z 1/1 Running 0 153m 172.30.76.5 192.168.0.21 business-manager-666f454f7f-ncjp7 1/1 Running 0 153m 172.30.9.4 192.168.0.22 data-product-6664c6dcb9-7sxnz 1/1 Running 0 160m 172.30.76.2 192.168.0.21 data-product-6664c6dcb9-j2f48 1/1 Running 0 160m 172.30.76.3 192.168.0.21 data-product-6664c6dcb9-p5xkw 1/1 Running 0 160m 172.30.9.3 192.168.0.22
Node ip:宿主機的ip,由路由器分配。上圖最右邊的NODE列代表的就是容器所在的宿主機的物理ip,可以看到現在叢集內2臺主機都有分配容器。
Pod ip:被docker0網橋隔離的pod子網的ip。K8s在每個Node裡虛擬出的區域網。上圖的IP列,就是每個pod ip,可以看到同一宿主機下Pod在同網段(後面我會介紹不同的node下的Pod,是如何藉助flannel來實現跨主機通訊的)
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
business-manager ClusterIP 10.254.80.22 <none> 80/TCP 156m
data-product ClusterIP 10.254.116.224 <none> 50051/TCP 159m
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 5h8m
Cluster ip:k8s分配給每個service的全域性唯一的虛擬ip,也可以叫VIP。VIP沒有掛接到網路裝置,不能直接訪問。(後面會介紹這個ip的用處)
除了上面的3個主要ip,叢集裡還有其他的一些特定的ip和網段:
• DNS伺服器:這裡配置的是10.254.0.2:53
• 10.254.0.0/16網段,是可配置的當前叢集的網段,DNS和service的虛擬Ip正是處在這個網段裡。
3 Docker0網橋和flannel網路方案
在介紹Ingress和service這兩個元件之前,我們先簡單瞭解一下k8s節點之間的底層網路原理及典型的flannel-VXLAN方案。後面的章節,預設在節點之間的傳輸,都會有docker0網橋和flannel外掛的功勞。(有資料提到K8S採用cni0網橋替代了docker0網橋,兩者的原理是一樣的,我搭建的環境裡只有docker0網橋,所以我們按docker0來分析)
# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
business-manager-666f454f7f-7l86b 1/1 Running 1 11m 172.30.76.7 192.168.0.21
business-manager-666f454f7f-h5tvw 1/1 Running 1 11m 172.30.76.6 192.168.0.21
business-manager-666f454f7f-zxmsx 1/1 Running 0 8s 172.30.9.3 192.168.0.22
data-product-6664c6dcb9-4zk27 1/1 Running 1 11m 172.30.76.5 192.168.0.21
data-product-6664c6dcb9-7bn7p 1/1 Running 1 11m 172.30.76.3 192.168.0.21
data-product-6664c6dcb9-tkmms 1/1 Running 0 5m39s 172.30.9.2 192.168.0.22
大家注意到沒有,每個pod具備不同的Ip(這裡指k8s叢集內可訪問的虛擬ip),不同node下的pod甚至在不同的網段。那麼問題來了,叢集內不同IP、不同網段的節點是怎麼實現通訊的呢?這樣歸功於docker0和flannel.1這兩個虛擬網路裝置,我們先ifconfig檢視一下:
# ifconfig
docker0: 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 0
eth0: 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 0
flannel.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後,會在宿主機上建立上述兩個網路裝置。接下來我們通過一個示意圖來了解這兩個裝置的工作:
• K8s在每個宿主機(node)上建立了cni0網橋(這篇文件對應的叢集環境採用的是docker0網橋,原理一樣):容器的閘道器,實際指向的是這個網橋。
• Flannel則在每個宿主機上建立了一個VTEP(虛擬隧道端點)裝置flannel.1。
現在我們來分析下docker0和flannel.1是怎麼實現跨主機通訊的(由node1的business-manager:172.30.76.7發往node2的data-product:172.30.9.2):
# route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default gateway 0.0.0.0 UG 100 0 0 eth0
172.30.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
172.30.76.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
192.168.0.0 0.0.0.0 255.255.255.0 U 100 0 0 eth0
- 上圖是node1的路由表:第2行表示凡是發往172.30.0.0/16網段的包均交給node1-flannel.1裝置處理;第3行表示凡是發往172.30.76.0/8網段的包均交給node1-docker0網橋處理。
- 於是business- manager的請求,首先到達node1-docker0網橋,目的地址是172.30.9.2,只能匹配第2條規則,請求被交給node1-flannel.1裝置。
- node1-flannel.1又如何處理呢?請看下圖,展示的是flannel.1的ARP表:
# ip neigh show dev flannel.1
172.30.9.2 lladdr 96:8f:2d:49:c5:31 REACHABLE
172.30.9.1 lladdr 96:8f:2d:49:c5:31 REACHABLE
172.30.9.0 lladdr 96:8f:2d:49:c5:31 STALE
- node1-flannel.1的ARP表記錄的是ip和對應節點上的flannel.1裝置mac的對映。於是發往172.30.9.2匹配到了上述第1條規則,需要發往mac地址為96:8f:2d:49:c5:31的裝置。
# bridge fdb show flannel.1 |grep 96:8f:2d:49:c5:31
96:8f:2d:49:c5:31 dev flannel.1 dst 192.168.0.22 self permanent
- 這時候node1-flannel.1裝置又扮演一個網橋的角色,上圖為node1上查詢出的橋接規則,96:8f:2d:49:c5:31的目的ip對應於192.168.0.22,這正是我們這個例子裡node2的宿主機Ip。於是這個請求被轉發給了node2。
- 不難理解,node2也有一個像第1步那樣的路由表,於是來自node1-business-manager:172.30.76.7的請求最終經node2-docker0送達node2-data-product:172.30.9.2。
• 隨著node和pod加入和退出叢集,flannel程序會從ETCD感知相應的變化,並及時更新上面的規則。
• 現在我們已實現通過ip訪問pod,但pod的ip隨著k8s排程會變化,不可能隔三差五的去人工更新每個ip配置吧,這就需要service這個元件了,請看下一章。
4 Service和DNS
4.1 service
pod的ip不是固定的,而且同一服務的多個pod需要有負載均衡,這正是建立service的目的。
Service是由kube-proxy元件和iptables來共同實現的。
分析service原理前,大家可以先帶上這個問題:service的ip為什麼ping不通?
OK,我們現在直接上圖,隨便一個node的iptables(內容比較豐富,我隨便截了幾段,下文會挑幾個重要的規則展開分析):
# 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
...
現在可以回答service ip ping不通的問題了,因為service不是真實存在的(沒有掛接具體的網路裝置),而是由上圖這些iptables規則組成的一個虛擬的服務。
• Iptables是linux核心提供給使用者的可配置的網路層防火牆規則,核心在解析網路層ip資料包時,會加入相應的檢查點,匹配iptables定義的規則。
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
business-manager ClusterIP 10.254.80.22 <none> 80/TCP 3h28m
data-product ClusterIP 10.254.116.224 <none> 50051/TCP 3h32m
kubernetes ClusterIP 10.254.0.1 <none> 443/TCP 6h
• 我們還是看第3章的例子,business-manager要訪問data-product,於是往service-data-product的ip和port(10.254.116.224:50051)傳送請求。每個service物件被建立時,k8s均會分配一個叢集內唯一的ip給它,並且該ip伴隨service的生命週期不會變化,這就解決了本節開篇的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表裡存在上面這條規則,表示發往10.254.116.224:50051的資料包,跳轉到KUBE-SVC-45TXGSBX3LGQQRTB規則。
-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:這條規則,實際上是一條規則鏈,data-product我建了3個pod,所以這條規則鏈對應的正是這3個pod。這裡是service負載均衡的關鍵實現,第1條規則表示採用隨機模式,有1/3(33%)的概率跳轉到KUBE-SEP-DGXT5Z3WOYVLBGRM;第2條規則的概率是1/2(50%);第3條則直接跳轉。這裡有個需要注意的地方,iptables是順序往下匹配的,所以多節點隨機演算法,概率是遞增的,以data-product為例,我配置了3個Pod,就有3條規則,第1條被選中的概率為1/3,第2條則為1/2,最後1條沒得挑了,概率配置為1或直接跳轉。
-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:假設隨機到第2條KUBE-SEP-P6GCAAVN4MLBXK7I,這裡又是兩條規則。第1條是給轉發的資料包加標籤Mark,目的是在叢集多入口的場景下,保證資料包從哪進來的就從哪個node返回給客戶端,詳細原理就不展開說了。同時這裡還涉及到一個技術點,經過service轉發的資料包,pod只能追查到轉發的service所在的Node,如果有場景需要Pod明確知道外部client的源Ip,可以借用service的spec.externalTrafficPolicy=local欄位實現。
• KUBE-SEP-xxx:第2條規則就很簡單了,資料包轉發給172.30.76.5:50051,這裡已經拿到pod的ip和port,可以通過第3章的docker0和flannel.1網路進行通訊了。
上面是基於iptables的service方案,存在一個風險,當pod數量很大,幾百、幾千時,遍歷iptables將會是效能瓶頸。IPVS虛擬網絡卡技術在大量級的pod場景下表現比iptables優秀(運維的同事反饋11版本的k8s,官方已預設採用IPVS)。這不屬於本文件的目的,不展開說。
本節開頭我們提到service是由kube-proxy和iptables共同實現的,所以Kube-proxy所扮演的角色就不難想象了,kube-proxy負責感知叢集的變化,及時更新service的規則。
最後,我們還面臨著一個小問題,上面的過程是基於服務的VIP的訪問服務的,通過服務名的方式訪問又是怎麼實現的呢,請看下一節:DNS
4.2 DNS
本來寫這個文件沒想到要有DNS這一章節的,但叢集搭建好之後發現通過服務名無法訪問服務,通過VIP卻可以,才想起來叢集還需要額外搭個DNS元件。
# kubectl get po -n kube-system
NAME READY STATUS RESTARTS AGE
kube-dns-7cd94476cd-kr76t 4/4 Running 0 25s
DNS元件是跑在kube-system名稱空間下的一個pod,監聽著叢集ip:10.254.0.2:53。通過這個Ip:port(建立kubelet時指定DNS的ip)即可獲取到叢集內部的DNS解析服務。
現在我們隨便進入一個pod裡,可以看到dns的資訊已被k8s寫入。同時我們ping一個service:
# cat /etc/resolv.conf
nameserver 10.254.0.2
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
# ping data-product.default.svc.cluster.local
PING 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
當然是ping不通的,但vip已經被解析出來了。
Kubenetes可以為pod提供穩定的DNS名字,且這個名字可通過pod名和service名拼接出來,以上面的data-product為例,該服務的完整域名是[服務名].[名稱空間].svc.[叢集名稱]。相應的,每個pod也有類似規則的域名。
5 外部訪問叢集
5.1 外部訪問service
Service代理的是叢集內部的ip和埠,出了叢集這個ip:port就沒什麼意義了。所以如何在叢集外部訪問到service呢?
方式一:配置service的type=NodePort,此方式下k8s會給service做埠對映。這種方式是最常用的,我們DEV環境下很多service做了埠對映,可以通過宿主機Ip加映射出去的埠號直接訪問服務。這種方式的原理簡單,kube-proxy只需要在iptables裡增加一條規則,將外部埠的包導向第4章的service規則去處理即可。(下一節要講的ingress,正是這種方式的一種更細緻的實現)
方式二:type=LoadBalancer,適用於公有云提供的K8s環境,此時K8s使用一個叫作CloudProvider的轉接層與公有云的API互動,並由公有云API來實現負載均衡。
方式三:type=ExternalName,這個方式的用法我還沒搞清楚。
按前面章節的套路,這裡我們依然會面臨一個小問題,把外部需要訪問的服務大量的通過埠對映方式暴露出去,勢必給埠的管理帶來麻煩。所以,接下來我們看看ingress是怎麼作為叢集的入口,幫我們管理後端服務的。
5.2 ingress
4.2章節,在叢集內部我們實現了通過域名(服務名)獲取具體的服務vip,從而免去了管理Vip煩惱。那麼從外部訪問叢集的服務,又如何實現通過域名的方式呢?後端的服務有很多,我們也需要一個全域性的負載均衡器來管理後面服務。這就是ingress。
# kubectl get po -n ingress-nginx
NAME READY STATUS RESTARTS AGE
nginx-ingress-controller-546bfbff9-hpwsz 1/1 Running 0 84s
• 使用ingress,我們除了要建立ingress物件以外,還需要安裝一個ingress-controller,這裡我們選擇最常用的nginx-ingress-controller。如上所示,安裝之後,會增加一個ingress-nginx名稱空間,執行著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 "/";
...
• 當Ingress物件被建立時,nginx-ingress-controller會在這個nginx容器內部生成一個配置檔案/etc/nginx/nginx.conf(內容比較豐富,上圖我截了一小段,可以看到data-product.default的主要配置),並用這個檔案啟動nginx服務。當ingress物件被更新時,nginx-ingress-controller會實現nginx服務的動態更新。
# cat ing.yaml
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
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服務的功能:隨便找一個ingress檔案檢視,rules欄位包含一組域名、路徑、後端服務名、服務埠的對映,這就是個反向代理的配置檔案。當前我們用nginx做反向代理,以及將請求負載給後端的service。加上證書,nginx還可以解析https,給後端依然是http明文通訊
現在又面臨一個小問題了,這個nginx服務居然執行在容器裡,參考5.1章節,這個服務外部還是訪問不了啊?所以安裝nginx-ingress-controller時還需要建立一個服務,將這個pod裡的nginx服務監聽的80和443埠暴露出去。
# kubectl describe svc ingress-nginx -n ingress-nginx
Name: ingress-nginx
Namespace: ingress-nginx
Labels: app.kubernetes.io/name=ingress-nginx
app.kubernetes.io/part-of=ingress-nginx
Annotations: <none>
Selector: app.kubernetes.io/name=ingress-nginx,app.kubernetes.io/part-of=ingress-nginx
Type: NodePort
IP: 10.254.189.164
Port: http 80/TCP
TargetPort: 80/TCP
NodePort: http 30799/TCP
Endpoints: 172.30.76.2:80
Port: https 443/TCP
TargetPort: 443/TCP
NodePort: https 31522/TCP
Endpoints: 172.30.76.2:443
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
上面這個服務,正是ingress-nginx的SVC,它向外暴露的埠(NodePort)是30799和31522,對應的endpoints正是nginx容器裡的nginx服務監聽的兩個埠80和433。這個ingress-service加上ingress-nginx容器,共同組成了ingress。所以廣義上,ingress提供的是叢集入口服務,是一個虛擬的概念。不考慮具體的功能的話,business層以NodePort方式運作時,就可以看作business層就是data層的ingress。
現在我們可以用business-manager.default:30799/api/v1/product/list來發起請求。
附 擴充套件實戰
原理分析的再多再深入,最終還是希望能夠為我們的工作提供一些幫助。所以下面的篇幅我記錄了在分析過程中看到或是想到的可能有助於我們實際工作的思路,限於精力有限,這些思路我暫時還沒有完整驗證過,同學們有興趣的話可以參與進來。
附A 用service實現DB的管理
當前DB的ip和埠是配置在每個應用的configmap裡的,如果出現DB切換、遷移等因素導致IP或埠變更,我們需要挨個去修改每個應用的config。
K8s支援指定service的endpoints為一個特定的點,比如可以指定為DB的IP和埠。這樣我們可以建立兩個service:service-DB-read,和service-DB-write。由service來管理DB的IP和PORT,變更只需要修改這兩個service的config即可。由4.1章節的分析我們知道,應用訪問上述兩個service,資料包會被轉發給endpoints也就是真正的db。
請見下圖,Endpoints指向叢集外部資料庫的service-mysql:
# kubectl describe svc mysql
Name: mysql
Namespace: default
Labels: <none>
Annotations: <none>
Selector: <none>
Type: ClusterIP
IP: 10.254.84.209
Port: <unset> 3306/TCP
TargetPort: 3306/TCP
Endpoints: 192.168.0.103:3306
Session Affinity: None
Events: <none>
應用層通過訪問service-mysql,流量最終會到達endpoints也就是叢集外部的真實資料庫的ip:port。細心的同學應該能想到,這玩意可以用於簡單的資料庫負載均衡,比如有多個讀庫的情況下,我們只需要讓service-mysql的endpoints指向這幾個讀庫,流量即能被負載均衡到各個庫。
附B 用NetworkPolicy實現訪問許可權隔離
以DB為例,當前叢集的DB對所有pod開放,那有沒有辦法限制訪問許可權呢,比如只允許data層訪問。回想第4.1章節service的本質是iptables規則,那麼就有可能通過iptables實現更細緻的規則,比如DB的訪問許可權管理。這就是k8s的NetworkPolicy,支援以pod的標籤的形式制定相應的iptables規則。目前flannel網路外掛不支援NetworkPolicy,flannel + Calico外掛可以實現。