cni設(shè)計(jì)
參考: https://github.com/containernetworking/cni/blob/main/SPEC.md
1. 插件式使用的cni
術(shù)語理解:
container: 在cni這里對應(yīng)linux network ns
network: 指一組endpoint,可以用于獲取到唯一的地址
runtime: 一個(gè)負(fù)責(zé)執(zhí)行CNI plugin的程序
plugin: 負(fù)責(zé)配置網(wǎng)絡(luò)
這個(gè)文檔會僅對runtime 和 plugin進(jìn)行說明
第一部分: 網(wǎng)絡(luò)配置格式
cniVersion: 版本,版本號越大支持的特性越豐富
name: 網(wǎng)絡(luò)名
disableCheck: 對應(yīng)cni的check (Add|Del|check)
plugins: cni list
plugin 配置對象
type: 對應(yīng)cni二進(jìn)制的名字,比如macvlan,ipvlan
capabilities: 稍后補(bǔ)充
ipMasq: 通過host上的一個(gè)ip來偽裝整個(gè)網(wǎng)絡(luò),所有pod用的該網(wǎng)段的pod ip都會使用該host ip進(jìn)行地址偽裝(nat)
ipam: 地址管理字典,其內(nèi)的type字段即是ipam plugin
dns: 一個(gè)字典缨历,包括,nameservers糙麦,domain戈二,search,options
{
"cniVersion": "1.0.0",
"name": "dbnet",
"plugins": [
{
"type": "bridge",
// plugin specific parameters
"bridge": "cni0",
"keyA": ["some more", "plugin specific", "configuration"],
"ipam": {
"type": "host-local",
// ipam specific
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1",
"routes": [
{"dst": "0.0.0.0/0"}
]
},
"dns": {
"nameservers": [ "10.1.0.1" ]
}
},
{
"type": "tuning",
"capabilities": {
"mac": true
},
"sysctl": {
"net.core.somaxconn": "500"
}
},
{
"type": "portmap",
"capabilities": {"portMappings": true}
}
]
}
第二部分喳资, 執(zhí)行協(xié)議
cni protocol協(xié)議基于容器運(yùn)行時(shí)調(diào)用cni(二進(jìn)制)觉吭,cni定義了cni二進(jìn)制腳本和容器運(yùn)行時(shí)交互的協(xié)議。
plugin分兩種仆邓。
"Interface" plugins: 常見的cni macvlan ipvlan等
"Chained" plugins: 基于cni配置好的接口鲜滩,做qos,安全組等配置节值,比如terway 的ipvlan和之后的cilium鏈?zhǔn)秸{(diào)用
容器運(yùn)行時(shí)徙硅,通過將配置和環(huán)境變量以stdin的形式傳遞給cni插件。cni插件配置完容器接口以及網(wǎng)絡(luò)之后搞疗,基于stdout 反饋成功嗓蘑。 如果有錯誤,基于strerr反饋匿乃。
stdin的配置和stdout的結(jié)果都是JSON格式桩皿。
成功是狀態(tài)碼 必須是0
失敗時(shí) 非0
容器運(yùn)行時(shí)是在root network ns中執(zhí)行cni plugin的。
傳遞給cni的json包括:
CNI_COMMAND: ADD, DEL, CHECK, or VERSION
CNI_CONTAINERID: 容器id
CNI_NETNS: 容器所在網(wǎng)絡(luò)命名空間 /run/netns/[nsname]
CNI_IFNAME: 容器內(nèi)部的網(wǎng)卡
CNI_ARGS: 額外的參數(shù)幢炸,格式為 "FOO=BAR;ABC=123"
CNI_PATH: CNI插件所在路徑
cni 的操作
ADD 將容器連接到網(wǎng)絡(luò)泄隔,或者執(zhí)行變更
- 在CNI_NETNS內(nèi)部, 基于CNI_IFNAME 創(chuàng)建網(wǎng)卡
- 或者在CNI_NETNS內(nèi)部, 基于CNI_IFNAME 調(diào)整接口的配置
如果ADD成功,那么返回的json 也就是 prevResult 對象會包括這些內(nèi)容: https://github.com/containernetworking/cni/blob/main/SPEC.md#Success
cniVersion:
容器網(wǎng)卡: name, mac, samdbox(CNI_NETNS)
ips: ipcidr 192.168.1.3/24, gw, 網(wǎng)卡index
路由: dst cidr, gw
dns: nameservers domain search options
如果容器內(nèi)有同樣的接口宛徊,肯定會報(bào)錯佛嬉,而且,容器運(yùn)行時(shí)不會連續(xù)執(zhí)行ADD兩次(中間沒有刪除動作)闸天,僅執(zhí)行一次暖呕。
輸入:
容器運(yùn)行時(shí)會提供一個(gè)json 對象作為cni的標(biāo)輸入:
必須包含的字段:
- CNI_COMMAND
- CNI_CONTAINERID
- CNI_NETNS
- CNI_IFNAME
可選字段:
CNI_ARGS
CNI_PATH
DEL是add的反操作,暫不寫
CHECK 用于檢查和確認(rèn)苞氮,暫不寫
ipam 代理插件
Delegated plugins (IPAM)
ipam 插件必須反饋一個(gè)縮略的的成功對象的結(jié)構(gòu)體湾揽。 比如沒有接口數(shù)組以及接口ip等。
ipam插件式cni設(shè)計(jì)的一部分,ipam和cni二級制協(xié)調(diào)工作钝腺,才能完成網(wǎng)絡(luò)配置
第三部分抛姑, 網(wǎng)絡(luò)配置的執(zhí)行
容器運(yùn)行時(shí)會在容器內(nèi)部執(zhí)行 add, del, check 等網(wǎng)絡(luò)操作赞厕。
- 網(wǎng)絡(luò)配置如何轉(zhuǎn)化
- 轉(zhuǎn)化后的網(wǎng)絡(luò)配置如何提供給插件
網(wǎng)絡(luò)配置的操作統(tǒng)一叫做 attachment艳狐, 容器id 和 接口id, 是attachment的唯一標(biāo)識
(操作)周期 和 順序
- 在調(diào)用任何插件(cni ipam)之前皿桑,容器運(yùn)行時(shí)必須已經(jīng)創(chuàng)建了一個(gè)新的網(wǎng)絡(luò)命名空間毫目。
- 對于同一個(gè)容器,容器運(yùn)行時(shí)不能調(diào)用多個(gè)并行的操作诲侮。 多個(gè)容器的操作可以并行镀虐。
- 不同的容器,插件可以并行執(zhí)行沟绪,必要的話刮便,需要實(shí)現(xiàn)一定的鎖機(jī)制,必須ipam 的地址分配绽慈。
- 容器運(yùn)行時(shí)必須保證恨旱,add 和 delete的先后順序, add 結(jié)束坝疼,delete才能執(zhí)行搜贤,即使add失敗,del必須成功執(zhí)行钝凶。
- delete保證冪等仪芒,無論delete多少次,結(jié)果一致耕陷。
- add 和 delete 之間的時(shí)間段掂名, 網(wǎng)絡(luò)配置不能發(fā)生變化。
- 在不同的attachments之間哟沫,網(wǎng)絡(luò)配置不能發(fā)生變化铆隘。
- 容器運(yùn)行時(shí)負(fù)責(zé)ns的清理。
Attachment的參數(shù):
- 容器id
- ns
- 容器網(wǎng)卡名
- 原生參數(shù)南用, 額外參數(shù)膀钠,鍵值對格式,在CNI_ARGS 里面
- Capability Arguments裹虫, 鍵值對肿嘲,json 格式: https://github.com/containernetworking/cni/blob/main/CONVENTIONS.md
"capabilities": {"portMappings": true}
"runtimeConfig": {
"portMappings": [
{"hostPort": 8080, "containerPort": 80, "protocol": "tcp"}
]
}
添加一個(gè)attachment
# cat /etc/cni/net.d/00-multus.conf | jq
{
"cniVersion": "0.4.0",
"name": "multus-cni-network",
"type": "multus",
"capabilities": {
"portMappings": true
},
"kubeconfig": "/etc/cni/net.d/multus.d/multus.kubeconfig",
"delegates": [
{
"name": "kube-ovn",
"cniVersion": "0.3.1",
"plugins": [
{
"type": "kube-ovn",
"server_socket": "/run/openvswitch/kube-ovn-daemon.sock"
},
{
"type": "portmap",
"capabilities": {
"portMappings": true
}
}
]
}
]
}
# 比如當(dāng)前的k8s集群環(huán)境
# plugins包含兩部分 cni是multus,ipam是kube-ovn-daemon
- 查看type 字段筑公,不存在雳窟,則失敗,報(bào)錯
- 基于plugin 配置,去請求配置封救,包括如下參數(shù)
如果這個(gè)plugin在list中的第一位拇涤, 不需要提供之前的結(jié)果,應(yīng)該指的是默認(rèn)cni誉结。
其他的plugins鹅士, 之前的結(jié)果就是上一個(gè)plugin處理過的輸出。
這里說明惩坑,主plugin和其他plugin是有一定的順序鏈?zhǔn)秸{(diào)用關(guān)系的掉盅。
執(zhí)行plugin 二進(jìn)制(比如macvlan cni), 基于CNI_COMMAND=ADD 參數(shù)以舒,二進(jìn)制可以通過環(huán)境變量讀入?yún)?shù)趾痘,stdin也有包含一些配置項(xiàng)(ip mac dns 網(wǎng)關(guān) svc localdns等)。
一旦plugin 執(zhí)行出錯蔓钟,會立刻停止并失敗永票,返回錯誤。
容器運(yùn)行時(shí)滥沫,必須存儲最后一個(gè)plugin返回的結(jié)果侣集,并持久化存儲(pod的狀態(tài)),用于做pod狀態(tài)檢查以及是否刪除佣谐。
Deleting an attachment: 是add的反向操作肚吏,暫不寫
從 plugin 配置中,獲取(待)執(zhí)行配置項(xiàng)
網(wǎng)絡(luò)配置的格式: 就是一個(gè)list狭魂,包含所有需要執(zhí)行的plugin配置內(nèi)容罚攀。
必須被轉(zhuǎn)化為單個(gè)plugin能夠理解的格式,因?yàn)榧词故嵌嗑W(wǎng)卡場景雌澄,也要一個(gè)一個(gè)的執(zhí)行斋泄。
單個(gè)plugin調(diào)用時(shí)需要的配置也是一個(gè)json。
容器運(yùn)行時(shí)必須將以下配置項(xiàng)傳遞給cni二進(jìn)制
- cniVersion: cni 版本
- name: 取自網(wǎng)絡(luò)配置中的name字段
- runtimeConfig: json 對象镐牺, plugin中的capabilities 字段
- prevResult: 一個(gè)json對象炫掐,上一個(gè)運(yùn)行過的plugin(add/delete/check)返回的結(jié)果
plugin 中定義的 capabilities會被容器運(yùn)行時(shí)移除。 其他field都毫無更改的直接傳遞睬涧。
runtimeConfig
然而 當(dāng)CNI_ARGS 被傳遞給所有plugins后募胃,沒有任何指示表示這些信息會被消費(fèi)。
所以畦浓,Capability需要在plugins字段中明確定義痹束。 那么 容器運(yùn)行時(shí),就可以基于此來確定是否一個(gè)給定的網(wǎng)絡(luò)配置支持某些特定的capability(能力)讶请。
{
"type": "myPlugin",
"capabilities": {
"portMappings": true // 表示支持端口映射的能力
}
}
// 將 capabilities 格式化為 runtimeConfig
{
"type": "myPlugin",
"runtimeConfig": {
"portMappings": [ { "hostPort": 8080, "containerPort": 80, "protocol": "tcp" } ]
}
...
}
runtimeConfig 的內(nèi)容來自于 網(wǎng)絡(luò)配置中的capabilities域
第四部分 插件代理(Plugin Delegation)
有一些操作祷嘶,無論出于何種原因,都不能合理地實(shí)現(xiàn)為一個(gè)離散的鏈?zhǔn)讲寮?br> 更確切的說,一個(gè)CNI plugin 希望能代理別的插件執(zhí)行一些功能论巍。 一個(gè)最常用的例子就是代理執(zhí)行ip 地址分配(ipam)
作為網(wǎng)絡(luò)配置的一部分烛谊,CNI 插件需要能夠分配并維護(hù)容器內(nèi)網(wǎng)卡的ip地址,也包括配置必要的路由嘉汰。
這讓CNI插件具有極大的靈活性丹禀,但也變得更重(ipam 路由 dns 這些東西的維護(hù))。
很多CNI插件需要的ipam的代碼幾乎都是類似的郑现,比如dhcp host-local湃崩。
所以荧降, 一個(gè)CNI 插件需要設(shè)計(jì)為可以用來作為其他plugin的ipam代理接箫。
為了讓CNI更輕便,而且讓IPAM 和 cni (二進(jìn)制)保持正交關(guān)系(低耦合)朵诫。
正交設(shè)計(jì)原則
消除重復(fù)
分離關(guān)注點(diǎn)
縮小依賴范圍
向穩(wěn)定的方向依賴
ipam 被設(shè)計(jì)為一個(gè)第三方的插件辛友,為了保證ipam被代理調(diào)用,同時(shí)設(shè)計(jì)了一個(gè)協(xié)議剪返,用于指導(dǎo)代理(ipam)功能的實(shí)現(xiàn)废累。
然而這不是容器運(yùn)行時(shí)的職責(zé),而是CNI plugin的職責(zé): 在執(zhí)行的過程中脱盲,適當(dāng)?shù)臅r(shí)間點(diǎn)邑滨,調(diào)用IPAM插件。
ipam 插件必須定義了ip钱反,子網(wǎng)掖看,網(wǎng)關(guān),路由面哥。 并且返回這些信息給“主” 插件去應(yīng)用(把這些參數(shù)在調(diào)用cni二進(jìn)制的時(shí)候給過去)哎壳。
ipam插件也有可能通過協(xié)議(dhcp)來獲取這些信息。 或者數(shù)據(jù)存儲在本地文件系統(tǒng)中尚卫。 或者網(wǎng)絡(luò)配置的ipam部分归榕。
被代理的插件協(xié)議(Delegated Plugin protocol)
和cni 插件一樣,被代理的(ipam)插件也是通過調(diào)用一個(gè)可執(zhí)行的(二進(jìn)制)吱涉。 這些插件也在預(yù)先指定好的目錄下刹泄,
# ls -l /opt/cni/bin/
total 177440
-rwxr-xr-x. 1 root root 4151672 Feb 5 2021 bandwidth # 帶寬限制
-rwxr-xr-x. 1 root root 4536104 Feb 5 2021 bridge
-rwxr-xr-x. 1 root root 10270090 Feb 5 2021 dhcp # ipam
-rwxr-xr-x. 1 root root 4767801 Feb 5 2021 firewall # 防火墻限制
-rwxr-xr-x. 1 root root 3357992 Feb 5 2021 flannel
-rwxr-xr-x. 1 root root 4144106 Feb 5 2021 host-device
-rwxr-xr-x. 1 root root 3565330 Feb 5 2021 host-local # ipam
-rwxr-xr-x. 1 root root 4288339 Feb 5 2021 ipvlan
-rwxr-xr-x. 1 root root 65658880 Apr 10 03:51 kube-ovn
-rwxr-xr-x. 1 root root 3566204 Apr 10 03:51 loopback
-rwxr-xr-x. 1 root root 4497003 Apr 10 03:51 macvlan
-rwxr-xr-x. 1 root root 41849770 Apr 10 03:48 multus
-rwxr-xr-x. 1 root root 3979034 Apr 10 03:51 portmap # 端口映射
-rwxr-xr-x. 1 root root 4467317 Feb 5 2021 ptp
-rwxr-xr-x. 1 root root 3701138 Feb 5 2021 sbr
-rwxr-xr-x. 1 root root 3153330 Feb 5 2021 static
-rwxr-xr-x. 1 root root 3668289 Feb 5 2021 tuning
-rwxr-xr-x. 1 root root 4287972 Feb 5 2021 vlan
-rwxr-xr-x. 1 root root 3759977 Feb 5 2021 vrf
CNI_PATH: 用于指定CNI的路徑
所有被代理的cni插件收到的環(huán)境變量都是一樣的,就像cni插件一樣怎爵,通過stdin收到網(wǎng)絡(luò)配置特石,基于stdout傳出配置。
被代理的插件(下層的)提供完整的網(wǎng)絡(luò)配置給到(上層的)插件疙咸,(插件間按照主次順序調(diào)用)
被代理的插件執(zhí)行流程
當(dāng)一個(gè)插件執(zhí)行一個(gè)被代理的插件時(shí)县匠,它應(yīng)該:
在CNI_PATH(環(huán)境變量)對應(yīng)目錄下查找二進(jìn)制對象
根據(jù)插件收到的配置和環(huán)境變量執(zhí)行找到的二進(jìn)制對象(執(zhí)行時(shí)傳給配置和環(huán)境變量)
確保 被代理的插件的stderr被傳遞給調(diào)用者的stderr對象
ADD 場景中,如果被代理的插件執(zhí)行失敗了,那么在返回錯誤前需要執(zhí)行該插件的del操作乞旦。
同樣的贼穆,DEL | check 上層插件檢測失敗,要將stderr返回給下層兰粉。
第五部分故痊,返回值類型
只有3種類型
Success (or Abbreviated Success)
Error
_Version
5.1 Success
所有插件的請求配置內(nèi)容中都會有一個(gè) prevResult, prevResult 作為插件自身的輸出玖姑。
任何插件都可能會對 prevResult 進(jìn)行修改愕秫。 如果不修改,直接反射(傳遞)即可焰络。
ADD 操作成功后戴甩,所有插件(鏈?zhǔn)綀?zhí)行后)必須返回一個(gè)json對象:
cniVersion:
interfaces: 網(wǎng)卡名, mac闪彼, ns
ips: attachment 配置好的ipcidr甜孤, 網(wǎng)關(guān), 網(wǎng)卡index
routes: 路由 dst cidr畏腕,gw
dns: nameservers domain search options
ipam (被代理的插件)
被代理的插件可以忽略 相關(guān)的配置項(xiàng)缴川。
ipam 必須返回一個(gè)縮略的 Success 成功對象,可能不會包含 interfaces 數(shù)組描馅,以及ips 對象內(nèi)部的interface把夸。
錯誤對象暫時(shí)不了解
版本
插件必須返回cniVerison, 以及 supportedVersions
{
"cniVersion": "1.0.0",
"supportedVersions": [ "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", "1.0.0" ]
}
示例
- CNI_COMMAND=ADD, 把如下參數(shù)傳遞給 bridge cni二級制 插件
{
"cniVersion": "1.0.0",
"name": "dbnet", // 網(wǎng)絡(luò)名
"type": "bridge", // cni 二進(jìn)制的名字
"bridge": "cni0", // 參數(shù) 網(wǎng)橋名
"keyA": ["some more", "plugin specific", "configuration"], // 額外參數(shù)
"ipam": { // 被代理調(diào)用的ipam 插件
"type": "host-local",
"subnet": "10.1.0.0/16",
"gateway": "10.1.0.1"
},
"dns": { // 被代理調(diào)用的 dns 插件 ?
"nameservers": [ "10.1.0.1" ]
}
}
接下來 主 cni (比如multus)铭污,會調(diào)用ipam(基于ipam內(nèi)的參數(shù)恋日,執(zhí)行調(diào)用host-local二進(jìn)制,將subnet gw屬性基于stdin傳遞給該二進(jìn)制)
host-local 會返回如下結(jié)果
{
"ips": [
{
"address": "10.1.0.5/16", // 分配出ip
"gateway": "10.1.0.1" // 返回網(wǎng)關(guān)
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
然后 bridge 二進(jìn)制cni文件况凉,基于ipam的返回結(jié)果谚鄙,配置容器的網(wǎng)絡(luò),然后返回如下結(jié)果
{
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
], // 以上是ipam的返回內(nèi)容刁绒,保持不變
"interfaces": [ //以下是cni添加的內(nèi)容
{
"name": "cni0", // 網(wǎng)橋名
"mac": "00:11:22:33:44:55" // 網(wǎng)橋的mac
},
{
"name": "veth3243", // veth-pair 接在網(wǎng)橋的一段
"mac": "55:44:33:22:11:11" // 其mac
},
{
"name": "eth0", // 容器ns 內(nèi)部的eth0 網(wǎng)卡闷营,veth-pair的另一端
"mac": "99:88:77:66:55:44", // mac
"sandbox": "/var/run/netns/blue" // ns 路徑
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
這個(gè)結(jié)果就是 bridge cni操作后的 prevResult。
第一個(gè)插件知市,一般是cni 二進(jìn)制傻盟,它的prevResult是空的,但是它的(下一個(gè)|上層)插件會基于第一個(gè)插件的result作為prevResult嫂丙。
- 基于 tuning (二進(jìn)制)插件娘赴, 基于CNI_COMMAND=ADD, 調(diào)用 tuning的 add函數(shù)跟啤,基于 prevResult诽表, 結(jié)合mac的capability (特性)唉锌,stdin 收到的配置請求體如下
{
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "tuning", // 二進(jìn)制插件
"sysctl": {
"net.core.somaxconn": "500"
},
"runtimeConfig": { // 特性, 將eth0的mac調(diào)整為該值
"mac": "00:11:22:33:44:66"
},
"prevResult": { // 下層的返回
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "99:88:77:66:55:44",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
tuning 成功執(zhí)行后的返回結(jié)果竿奏,可以看到eth0 mac已發(fā)生變化
{
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66", // 已變化
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
tuning返回的prevResult
- 最后 調(diào)用 portmap 插件袄简,CNI_COMMAND=ADD 對應(yīng) portmap add函數(shù),使用的 prevResult如上泛啸,
而 tuning 執(zhí)行完畢后绿语,如下
{
"cniVersion": "1.0.0",
"name": "dbnet",
"type": "portmap", // 增加了該部分
"runtimeConfig": {
"portMappings" : [
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp" }
]
},
"prevResult": {// 下層的返回
"ips": [
{
"address": "10.1.0.5/16",
"gateway": "10.1.0.1",
"interface": 2
}
],
"routes": [
{
"dst": "0.0.0.0/0"
}
],
"interfaces": [
{
"name": "cni0",
"mac": "00:11:22:33:44:55"
},
{
"name": "veth3243",
"mac": "55:44:33:22:11:11"
},
{
"name": "eth0",
"mac": "00:11:22:33:44:66",
"sandbox": "/var/run/netns/blue"
}
],
"dns": {
"nameservers": [ "10.1.0.1" ]
}
}
}
DELETE正好是add 的逆操作,所以最上層的插件先執(zhí)行候址,執(zhí)行的參數(shù)就是上面ADD最后返回的結(jié)果吕粹。