聊聊如何讓辦公網路直連Kubernetes叢集PodIP/ClusterIP/Service DNS等

2022-11-23 06:01:02

想象一下,如果您日常使用的研發測試Kubernetes叢集,能夠有以下效果:

  • 在辦公網路下直接存取Pod IP
  • 在辦公網路下直接存取Service Cluster IP
  • 在辦公網路下直接存取叢集內部域名,類似 service.namespace.svc.cluster.local

會不會很方便,很優雅?

筆者近期就給內部的一個新叢集做過類似的調整,特此分享一些心得。

PS: 這裡的 直接存取/直連 指的是不借助Ingress/hostnetwork:true/NodePort等常規方式,直接存取k8s內部IP or DNS,起到 網路拉平 的效果。

先決條件 - 三層路由方案

辦公網段跟Kubernetes叢集大概率是不同的網段,所以要想打通最自然的想法是依賴路由。相應的,Kubernetes 跨主機網路方案,我們最好也選擇三層路由方案或者Host-GW,而非Overlay,不然封包在封包解包過程中可能會失去路由方向。

我們的叢集選用的是Calico,且關閉了 IPIP 模式。具體的IPPool設定如下:

-> calicoctl get IPPool -o yaml
apiVersion: projectcalico.org/v3
items:
- apiVersion: projectcalico.org/v3
  kind: IPPool
  metadata:
    name: default-pool
  spec:
    blockSize: 24
    cidr: 10.233.64.0/18
    # 關閉IPIP模式
    ipipMode: Never
    natOutgoing: true
    nodeSelector: all()
    vxlanMode: Never
kind: IPPoolList

Calico RR(Route Reflectors)or Full-Mesh 模式?

網上的很多類似教學,上來都會引導大家先把叢集改為RR模式,其實這不是必須的。大家可以思考下,RR模式解決的問題是什麼?是為了防止所有節點間都做BGP連線交換,浪費資源。但如果你的叢集很小, 且已經是按Full Mesh模式部署了,到也沒必要非得改為RR模式。Full Mesh下所有的節點都是類似RR節點的效果,所以如果我們想選擇作為 BGPPeer交換的節點,選擇任意節點就行。 比如,筆者的叢集就選擇了Ingress所在的節點,作為BGPPeer。

~ calicoctl get BGPPeer -o yaml
apiVersion: projectcalico.org/v3
items:
- apiVersion: projectcalico.org/v3
  kind: BGPPeer
  metadata:
    name: peer-switch
  spec:
  	# 交換機設定
    asNumber: 65200
    peerIP: 10.200.20.254
    # 這個label是Ingress節點特有的
    nodeSelector: node-role.kubernetes.io/ingress == 'ingress'
kind: BGPPeerList

從叢集外部存取Pod IP vs 從叢集內部存取?

這個問題很關鍵,如果我們想從外部直接存取到叢集內部的Pod IP,那麼首先需要搞清楚叢集內的節點是如何暢通存取的。

以下面的節點為例,我們來看它的路由資訊:

~ ip r
# 預設路由
default via 10.200.20.21 dev bond0 onlink
# 宿主機封包路由
10.200.20.0/24 dev bond0 proto kernel scope link src 10.200.20.105
# 黑洞,防止成環
blackhole 10.233.98.0/24 proto bird
# 目的地址是10.233.98.3的封包,走cali9832424c93e網路卡
10.233.98.3 dev cali9832424c93e scope link
# 目的地址是10.233.98.4的封包,走cali4f5c6d27f17網路卡
10.233.98.4 dev cali4f5c6d27f17 scope link
# 目的地址是10.233.98.8的封包,走cali8f10abc672f網路卡
10.233.98.8 dev cali8f10abc672f scope link
# 目的地址是10.233.110.0/24網段的封包,從bond0網路卡出到下一跳10.200.20.107上
10.233.110.0/24 via 10.200.20.107 dev bond0 proto bird
# 目的地址是10.233.112.0/24網段的封包,從bond0網路卡出到下一跳10.200.20.106上
10.233.112.0/24 via 10.200.20.106 dev bond0 proto bird
# 目的地址是10.233.115.0/24網段的封包,從bond0網路卡出到下一跳10.200.20.108上
10.233.115.0/24 via 10.200.20.108 dev bond0 proto bird

相信看了筆者的註釋,大家應該很容易瞭解到以下資訊:

  • 這臺宿主機IP是10.200.20.105,叢集內其他的宿主機還有10.200.20.106, 10.200.20.107, 10.200.20.108等
  • 主機10.200.20.105上的Pod IP段是10.233.98.0/24, 10.200.20.106上是10.233.112.0/24,10.200.20.107上是10.233.110.0/24
  • 目的地址是10.233.98.3的封包走cali9832424c93e網路卡,目的地址10.233.98.4的封包走cali4f5c6d27f17網路卡等

而這些資訊實際解答了,容器封包的 出和入 這個關鍵問題:

  • 比如想存取Pod IP為10.233.110.7的容器,宿主機自然知道下一跳是10.200.20.107上
  • 比如接收到了目的地址是10.233.98.8的封包,宿主機自然也知道要把這個包交給cali8f10abc672f網路卡。而這個網路卡是veth pair裝置的一端,另一端必然在目標Pod裡

那這些路由資訊是哪裡來的呢?自然是Calico藉助BGP的能力實現的。我們進一步想,如果外部節點也有這些資訊,是不是也就自然知道了Pod IP在哪裡了? 答案確實如此,其實總結基於Calico的網路打平方案,核心原理就是 通過BGP能力,將叢集路由資訊廣播給外部。

而在具體的設定上,就比較簡單了,只需要在兩端設定好BGP Peer即可。

  • 先是叢集這一側,前面筆者已給出:

    ~ calicoctl get BGPPeer -o yaml
    apiVersion: projectcalico.org/v3
    items:
    - apiVersion: projectcalico.org/v3
      kind: BGPPeer
      metadata:
        name: peer-switch
      spec:
      	# 交換機設定
        asNumber: 65200
        peerIP: 10.200.20.254
        # 這個label就是Ingress節點特有的
        nodeSelector: node-role.kubernetes.io/ingress == 'ingress'
    kind: BGPPeerList
    
  • 再就是外部,一般是交換機,使用類似下面的命令:

    [SwitchC] bgp 64513       # 這是k8s叢集的ASN
    [SwitchC-bgp] peer 10.200.20.107 as-number 64513
    [SwitchC-bgp] peer 10.200.20.108 as-number 64513
    

    PS: 具體的交換機操作方式可以參考各品牌交換機官方檔案

到這裡,基本上我們已經打通了外部直接存取Pod IP的能力。當然,如果您的辦公網路到交換機這一側還有多個閘道器,您還需要在這些閘道器上設定合適的路由才行。

為什麼 Service Cluster IP 還不能存取?

也許這時候您會發現,可以直連Pod IP,但 Cluster IP不可以,這是為什麼呢?原來,預設情況Calico並沒有廣播Service IP,您可以在交換機這一側通過檢視交換過來的IP段來確認這一點。

PS: 您是否注意到,k8s主機節點上也沒有service的ip路由,但為啥在叢集內部存取service沒有問題呢?

解決方案也簡單,只要開啟相關的設定即可, 類似如下:


~ calicoctl get bgpconfig default -o yaml
apiVersion: projectcalico.org/v3
kind: BGPConfiguration
metadata:
  name: default
spec:
  asNumber: 64513
  listenPort: 179
  logSeverityScreen: Info
  nodeToNodeMeshEnabled: true
  # 這就是需要廣播的service cluster IP 段
  serviceClusterIPs:
  - cidr: 10.233.0.0/18

打通內網DNS,直接存取Service域名

直連IP雖然方便,但有時若想記住某服務的具體IP卻不是那麼容易。所以,我們將K8s內部的DNS域名也暴漏出來了,類似下面:

<service>.<namespaces>.svc.cluster.local

而這塊的設定也相對簡單,一般企業都有內網DNS,只需要新增相應解析到K8s內部DNS Server即可。

總結

其實若想打造一個好用的研發測試叢集,有很多的細節需要處理,筆者後續也會繼續分享類似的經驗,希望對大家有用。

參考連結