Kubernetes 網路介紹: Part2 – Calico

原始文章:https://leebriggs.co.uk/blog/2017/02/18/kubernetes-networking-calico.html

在前一個貼文,我講解了一些基本的 Kubernetes 網路運作觀念。網路的需求很簡單:每個 Pod 都可以連接到其他 Pod。在眾多選項裡的唯一差別就是 Pod 之間如何連接。

在這篇貼文,我會講解 Calico 如何運作的基本觀念。如同我在前一篇所提到的,我不喜歡 Kubernetes 的 Deployments,簡單的使用 yaml 檔案設置並且部署就好了吧,有時候其實直接看了就會了解他是如何運作的,不過我仍然希望這篇貼文可以幫助大家更加瞭解內部運作。

跟之前提到的一樣,我並不是網路方面的專家,如果你發現任何錯誤,請送一個 Pull Request 給我!

什麼是 Calico ?

Calico 是 MetaSwitch 提出的容器網路的解決方案。
相對於 Flannel 運作在網路第二層,Calico 是運用網路第三層來路由封包到 Pods 內,網路第三層也是大家比較能理解的部分。另外,Calico 也提供了網路政策給 Kubernetes 使用。不過我們會暫時忽略網路政策的部分,專注在 Calico 如何提供容器使用網路。

元件

簡單來說,Calico 是由以下四個元件所組合而成的。包含了:

Etcd

Etcd 是用來儲存所有 Calico 所需要的資料,如果你已經將 Kubernetes 部署完畢,其實你就已經將 Etcd 部署好了,不過通常會建議另外部署一個 Etcd 系統來給線上環境使用,或是將 Etcd 部署在 Kubernetes 之外,不過這樣部屬的案例比較少。

你可以利用指令 etcdctl 來檢驗 Calico 所提供的資訊,預設的存放地點位於 /calico 鍵值底下

$ etcdctl ls /calico
/calico/ipam
/calico/v1
/calico/bgp

BIRD

下一個關鍵元件是 BIRD,BIRD 是個執行在每台主機上的 BGP 路由進程,Calico 使用 BGP 在各個主機間作路由以遞送封包。BGP (如果你沒注意到的話) 是個廣泛使用在網際網路上的路由協定,建議你多閱讀一些關於 BGP 的概念會使你更熟悉 Calico。

BIRD 會執行在 Kubernetes 中的每個節點,通常會使用 DaemonSet 來運作,它包含在 calico/node 容器內。

Confd

Confd 是個簡單的組態管理工具,它會從 Etcd 讀取設定,再將它們寫入在儲存在磁碟中的檔案。如果你仔細看 calico/node 容器 (正常狀況下他會持續運行) 內部的運作,你會大概知道它在做什麼:

# ps
PID USER TIME COMMAND
1 root 0:00 /sbin/runsvdir -P /etc/service/enabled
105 root 0:00 runsv felix
106 root 0:00 runsv bird
107 root 0:00 runsv bird6
108 root 0:00 runsv confd
109 root 0:28 bird6 -R -s /var/run/calico/bird6.ctl -d -c /etc/calico/confd/config/bird6.cfg
110 root 0:00 confd -confdir=/etc/calico/confd -interval=5 -watch --log-level=debug -node=http://etcd1:4001
112 root 0:40 bird -R -s /var/run/calico/bird.ctl -d -c /etc/calico/confd/config/bird.cfg
230 root 31:48 calico-felix
256 root 0:00 calico-iptables-plugin
257 root 2:17 calico-iptables-plugin
11710 root 0:00 /bin/sh
11786 root 0:00 ps

如同你看到的,confd 連接了 etcd 節點,並從 etcd 讀取資料,並將資料轉為設定儲存至 confd 的目錄中。
confd 資料夾裡面的內容可以在 calicoctl github 中看到。

如果你仔細看裡面的內容,你會發現到有三個目錄。

首先,有一個 confd 目錄,這裡面包含了一堆 toml 設定檔,讓我們來仔細看一下:

[template]
src = "bird_ipam.cfg.template"
dest = "/etc/calico/confd/config/bird_ipam.cfg"
prefix = "/calico/v1/ipam/v4"
keys = [
"/pool",
]
reload_cmd = "pkill -HUP bird || true"

看起來很簡單對吧,裡面指定了來源樣板,以及組合完後的設定檔該儲存到哪個位置,還有來源樣板的設定值該從 etcd 的哪個鍵值取得,基本上 confd 就是用來設定 BIRD 以供 Calico 使用。如果你想檢驗鍵值,你可以看下面的範例:

$ etcdctl ls /calico/v1/ipam/v4/pool//calico/v1/ipam/v4/pool/192.168.0.0-16

在這個例子裡,可以得到設定給 pod 的 cidr 網路位址,我們會在後面解釋其餘細節。

為了瞭解這個鍵值的意義,你有必要看一下 confd 所使用的 來源樣板

現在讓我們稍微看一下這個似乎複雜的東西,confd 使用類似 Go 模板語言寫了類似的模板,並從 etcd 取得值。看一下 這個 範例:

讓我們看一下他大致上是怎麼運作的:

  • /v1/ipam/v4/pool 鍵值裡面取得所有在底下的的鍵值,以我們的例子只有一個:192.168.0.0-16

  • 以取得的鍵值,取出內部的資料放入變數 $data 裡面

  • $data 裡面的 JSON 取得我們需要的內容,在這個例子我們需要的是鍵值 cidr:
$ etcdctl get /calico/v1/ipam/v4/pool/192.168.0.0-16
{"cidr":"192.168.0.0/16","ipip":"tunl0","masquerade":true,"ipam":true,"disabled":false}

之後我們就可以將 cidr 的內容寫入檔案,最後提供 calico/node 容器使用的設定檔看起來像:

if ( net ~ 192.168.0.0/16 ) then {
accept;
}

很間單對吧!

calico-felix

最後一個元件是 calico-felix 進程,他是個使 calico 能順利運行的重要關鍵,他做了以下的事情:

  • 將路由表寫入作業系統裡面,你會在 Calico的運作 部分看到
  • 操縱主機上的 IPtable 表,你一樣會在 Calico的運作 部分看到

他使用了從 etcd 讀取出來的資料做了上述的動作,他一樣是跑在 calico/node 容器內,與 confd 以及 BIRD 共同運作。

Calico 的運作

我們建議你先在一開始遵照 這裡 的建議部署 Calico,請確認以下事項:

  • 你在每個需要執行 Kubernetes 的主機都運行 calico/node 容器
  • 使用指令 kubectl get logs 檢查 calico/node 容器,確保沒有出現任何錯誤訊息,以確保 Calico 運行正常

在這個階段,你會想要部署一些東西,讓 Calico 得以施展它的魔法,我會建議你部署 guestbook 來看看 Calico 是怎麼運作的。

路由表 (Routing Table)

當你部署完 Calico 以及 guestbook 範例之後,使用 kubectl 取得 pod IP

$ kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE
frontend-88237173-f3sz4 1/1 Running 0 2m 192.168.15.195 node1
frontend-88237173-j407q 1/1 Running 0 2m 192.168.228.195 node2
frontend-88237173-pwqfx 1/1 Running 0 2m 192.168.175.195 node3
redis-master-343230949-zr5xg 1/1 Running 0 2m 192.168.0.130 node4
redis-slave-132015689-475lt 1/1 Running 0 2m 192.168.71.1 node5
redis-slave-132015689-dzpks 1/1 Running 0 2m 192.168.105.65 node6

運作順利的話,你應該可以從 Kubernetes 裡的任何一台主機 ping 到每個 pod,讓我們來試試看:

$ ping -c 1 192.168.15.195
PING 192.168.15.195 (192.168.15.195) 56(84) bytes of data.
64 bytes from 192.168.15.195: icmp_seq=1 ttl=63 time=0.318 ms

--- 192.168.15.195 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.318/0.318/0.318/0.000 ms

如果你有安裝 fping 套件,你可以用一行指令來驗證:

$ kubectl get po -o json | jq .items[].status.podIP -r | fping
192.168.15.195 is alive
192.168.228.195 is alive
192.168.175.195 is alive
192.168.0.130 is alive
192.168.71.1 is alive
192.168.105.65 is alive

真正的問題在於,到底這一切是怎麼運作的?我們是怎麼 ping 到這些 Pods 的?其實只要將你的路由表列出來就可以得到答案了:

$ ip route
default via 172.29.132.1 dev eth0
169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.29.132.0/24 dev eth0 proto kernel scope link src 172.29.132.127
172.29.132.1 dev eth0 scope link
192.168.0.128/26 via 172.29.141.98 dev tunl0 proto bird onlink
192.168.15.192/26 via 172.29.141.95 dev tunl0 proto bird onlink
blackhole 192.168.33.0/26 proto bird
192.168.71.0/26 via 172.29.141.105 dev tunl0 proto bird onlink
192.168.105.64/26 via 172.29.141.97 dev tunl0 proto bird onlink
192.168.175.192/26 via 172.29.141.102 dev tunl0 proto bird onlink
192.168.228.192/26 via 172.29.141.96 dev tunl0 proto bird onlink

大部分動作都在這裡可以看到,讓我們來拆解一下。

子網路 (Subnet)

每台執行了 calico/node 容器的主機都會有自己的一個 CIDR /26 的子網路,你可以在 etcd 裡面看到:

$ etcdctl ls /calico/ipam/v2/host/node1/ipv4/block/
/calico/ipam/v2/host/node1/ipv4/block/192.168.228.192-26

在這個範例裡面,主機 node1 佔用了子網路 192.168.228.192-26,在其他的主機啟動 kubernetes 以及 calico/node 容器的時候,會取得這個子網路指向 node1,這個動作是 Kubetnetes 網路的標準。

不同的是 Calico 如何處理它,讓我們回去看我們的路由表,仔細看有關 192.168.228.192/26 子網路的部分:

192.168.228.192/26 via 172.29.141.96 dev tunl0 proto bird onlink

這邊顯示的路由,是由 calico-felix 從 etcd 所讀取出來的值,並且將這個子網路指向 node1,也就是 IP 位址 172.29.141.96。 Calico 現在知道了主機的 IP,也知道了 pod 子網路的範圍,有了這些資訊,就可以在其他主機控制路由,指示其他主機「如果有資料要傳送到這個子網路的,將資料透過 tunl0 這個介面傳送到 IP 172.29.141.96

這個 tunl0 介面或許不會顯示在你的主機上,在這邊會顯示是因為我們在測試中啟動了 Calico 的 IPIP 封裝功能。

目的地主機

現在資料知道他們的目的地了,主機擁有路由資訊,而且也知道該藉由主機上的哪個介面來傳送資料。可是,資料抵達之後呢?

這個問題的答案還是在路由表上,當你有了另一個 pod 被指定到主機上的時候,再顯示一次路由表:

$ ip route
default via 172.29.132.1 dev eth0
169.254.0.0/16 dev eth0 scope link metric 1002
172.17.0.0/16 dev docker0 proto kernel scope link src 172.17.0.1
172.29.132.0/24 dev eth0 proto kernel scope link src 172.29.132.127
172.29.132.1 dev eth0 scope link
192.168.0.128/26 via 172.29.141.98 dev tunl0 proto bird onlink
192.168.15.192/26 via 172.29.141.95 dev tunl0 proto bird onlink
blackhole 192.168.33.0/26 proto bird
192.168.71.0/26 via 172.29.141.105 dev tunl0 proto bird onlink
192.168.105.64/26 via 172.29.141.97 dev tunl0 proto bird onlink
192.168.175.192/26 via 172.29.141.102 dev tunl0 proto bird onlink
192.168.228.192/26 via 172.29.141.96 dev tunl0 proto bird onlink
192.168.228.195 dev cali7b262072819 scope link

居然多了一條路由!你可以看到有一個 pod IP 指向目的地,並且經由一個指定的介面 cali7b262072819

讓我們把介面列出來看一下:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
3: eth1: <BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP> mtu 1500 qdisc mq master bond0 state UP mode DEFAULT qlen 1000
link/ether 00:25:90:62:ed:c6 brd ff:ff:ff:ff:ff:ff
4: bond0: <BROADCAST,MULTICAST,MASTER,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 00:25:90:62:ed:c6 brd ff:ff:ff:ff:ff:ff
5: cali7b262072819@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT
link/ether 32:e9:d2:f3:17:0f brd ff:ff:ff:ff:ff:ff link-netnsid 4

居然有一個介面是給 pod 使用的!當容器被執行起來的時候,Calico (經由 CNI) 會建立一個介面並且指定給該 pod,這到底是怎麼做到的?

CNI

這個問題的答案就在安裝 Calico 的步驟中,如果你仔細看你在安裝 Calico 所使用的 yaml 檔案,你會看到一個步驟被執行在每個 calico/node 容器中,它使用了 configmap,看起來像是下面這樣:

# This ConfigMap is used to configure a self-hosted Calico installation.
kind: ConfigMap
apiVersion: v1
metadata:
name: calico-config
namespace: kube-system
data:
# The location of your etcd cluster. This uses the Service clusterIP
# defined below.
etcd_endpoints: "http://10.96.232.136:6666"

# True enables BGP networking, false tells Calico to enforce
# policy only, using native networking.
enable_bgp: "true"

# The CNI network configuration to install on each node.
cni_network_config: |-
{
"name": "k8s-pod-network",
"type": "calico",
"etcd_endpoints": "__ETCD_ENDPOINTS__",
"log_level": "info",
"ipam": {
"type": "calico-ipam"
},
"policy": {
"type": "k8s",
"k8s_api_root": "https://__KUBERNETES_SERVICE_HOST__:__KUBERNETES_SERVICE_PORT__",
"k8s_auth_token": "__SERVICEACCOUNT_TOKEN__"
},
"kubernetes": {
"kubeconfig": "/etc/cni/net.d/__KUBECONFIG_FILENAME__"
}
}

# The default IP Pool to be created for the cluster.
# Pod IP addresses will be assigned from this pool.
ippool.yaml: |
apiVersion: v1
kind: ipPool
metadata:
cidr: 192.168.0.0/16
spec:
ipip:
enabled: true
nat-outgoing: true

這個 自我設定檔 (manifests) 放在每一台主機的 /etc/cni/net.d 目錄裡面:

$ ls /etc/cni/net.d/
10-calico.conf calico-kubeconfig calico-tls

所以當一個 pod 啟動的時候,Calico 會做以下的事情:

  • 查詢 Kubernetes API 來決定這個 pod 被指定到哪一台主機
  • 利用 IPAM 指定 pod IP
  • 在該主機上建立介面,使容器可以獲取到他的 IP 位址
  • 通知 Kubernetes API 這個容器的 IP

神奇吧!

IPTables

最後的一塊拼圖是在 IPTables 上,如同我們之前提到的,Calico 支援網路政策,即使你沒有使用網路政策這個部分,他還是存在你的系統中,而且不管如何,你還是需要一個預設的網路政策來使連線可以正常運作。如果你列出 iptables -L 所顯示的東西,可以會看到類似於底下的輸出:

**Chain felix-to-7b262072819 (1 references)
target prot opt source destination
MARK all -- anywhere anywhere MARK and 0xfeffffff
MARK all -- anywhere anywhere /* Start of tier default */ MARK and 0xfdffffff
felix-p-_722590149132d26-i all -- anywhere anywhere mark match 0x0/0x2000000
RETURN all -- anywhere anywhere mark match 0x1000000/0x1000000 /* Return if policy accepted */
DROP all -- anywhere anywhere mark match 0x0/0x2000000 /* Drop if no policy in tier passed */
felix-p-k8s_ns.default-i all -- anywhere anywhere
RETURN all -- anywhere anywhere mark match 0x1000000/0x1000000 /* Profile accepted packet */
DROP all -- anywhere anywhere /* Packet did not match any profile (endpoint eth0) */

IPTables 鏈的名字,也會出現在 calico 介面裡面,這個 iptables 規則對於 calico 傳遞資料進入容器是很重要的,它會決定封包是否該進入這個容器,或是將其丟棄。

如果你沒有看到這個鏈值,他會被預設政策所丟棄,這些規則是由 calico-felix 設定的。

總結

希望透過這邊文章,你對於 Calico 的運作方式又多瞭解了一些,Calico 核心部分的運作真的十分簡單,只要在所有主機上利用指令 ip route 看過就大概會知道了。它做了最困難的部分,也就是管理路由,剩下的你只需要簡單的使用 Kubernetes,容器就可以順利的運作。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s