用 shell 来实现 CNI plugin(bridge)
试试用 shell 脚本来实现一个 CNI(bridge).
Why?
- 好玩
- k8s bridge CNI 的设计非常地通用和简单
- 不用编译, 随时 debug!
How?
使用 Linux 下的命令行工具, 来实现官方实现下的 bridge 插件的(大部分)功能.
requirements
common
- bash - 执行
- ip - 大部分对网络的操作都使用了该命令
- jq - 命令行解析 json, 似乎没什么选择…
- ln - 软链接 netns 文件
- arping - 给容器 IP 发送免费的 arp
Bridge
- brctl - 仅仅用到了
brctl hairpin <bridge> <port> {on|off}
.yum(apt) install bridge-utils
. 实现发卡弯模式
Start
脚本实现和 golang 实现差不多, 主要分为 command_add
/ command_del
/ command_version
. 下面主要仅仅介绍 command_add
的实现过程:
1 | command_add() { |
说明:
- 从 env 中获取 kubelet 需要 CNI 的参数, 从 stdin 中读取 CNI 的配置
- 如果 bridge 不存在则创建
- 需要给 netns 创建软连接到 /var/run/netns, 不然就需要 nsenter 来进入容器的 namespace
- 在容器的 netns 下创建 veth, 因为给容器的 peer 都叫 eth0, 所以不能在主机的 netns 下创建, 并将主机侧的 veth 移出, 连接到 bridge
- 从 .ipam.type 执行得到 IP/Route/DNS 等信息, 给容器侧的 eth0 配置
- 可选地配置 bridge 的地址为网关, 以及配置 iptables 的 MASQ
- 输出结果到 stdout
Env and stdin
1 | # CNI.spec 规定的环境变量 |
check_config/检查 stdin 的配置
1 | # 主要是 混杂模式和发卡弯模式不能同时开启 |
setup_bridge/创建并设置网桥
1 | # env: ${BRIDGE_NAME} ${MTU} ${PROMISC_MODE} |
setup_netns/设置netns
1 | # 主要是因为 ip 读取的 netns 是位于 /var/run/netns 下. |
generate_veth_peer_name/随机 veth name
1 | generate_veth_peer_name() { |
setup_veth/创建并设置 veth peer
1 | # env: ${CNI_IFNAME} |
exec_ipam/执行 ipam 的二进制文件, 获取 IP
1 | exec_ipam() { |
config_container_veth/根据 IPAM 的结果配置容器内的 veth
1 | # env: ${CNI_IFNAME} ${IPAM_RESULT} |
config_gateway/配置到 br0 的默认路由
该部分逻辑在 ipam 返回所有路由信息的情况下, 是不需要的…所以留了 TODO.1
2
3config_gateway() {
echo "TODO"
}
config_ip_masq/配置 iptables NAT 表
当配置和主机同网段 IP 时候, 也不需要该部分配置…也留个 TODO 吧.1
2
3config_ip_masq() {
echo "TODO"
}
Talk is cheap, show me the code.
pikeszfish/shell-plugin-for-cni
Reference
containernetworking/plugins/plugins/main/bridge/README.md
Overview
With bridge plugin, all containers (on the same host) are plugged into a bridge (virtual switch) that resides in the host network namespace.
The containers receive one end of the veth pair with the other end connected to the bridge.
An IP address is only assigned to one end of the veth pair – one residing in the container.
The bridge itself can also be assigned an IP address, turning it into a gateway for the containers.
Alternatively, the bridge can function purely in L2 mode and would need to be bridged to the host network interface (if other than container-to-container communication on the same host is desired).
The network configuration specifies the name of the bridge to be used.
If the bridge is missing, the plugin will create one on first use and, if gateway mode is used, assign it an IP that was returned by IPAM plugin via the gateway field.
Example configuration
1 | { |
Network configuration reference
name
(string, required): the name of the network.type
(string, required): “bridge”.bridge
(string, optional): name of the bridge to use/create. Defaults to “cni0”.isGateway
(boolean, optional): assign an IP address to the bridge. Defaults to false.isDefaultGateway
(boolean, optional): Sets isGateway to true and makes the assigned IP the default route. Defaults to false.forceAddress
(boolean, optional): Indicates if a new IP address should be set if the previous value has been changed. Defaults to false.ipMasq
(boolean, optional): set up IP Masquerade on the host for traffic originating from this network and destined outside of it. Defaults to false.mtu
(integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.hairpinMode
(boolean, optional): set hairpin mode for interfaces on the bridge. Defaults to false.ipam
(dictionary, required): IPAM configuration to be used for this network.promiscMode
(boolean, optional): set promiscuous mode on the bridge. Defaults to false.
原文作者: Pike.SZ.fish
原文链接: https://page.pikeszfish.me/2018/01/26/write-cni-plugin-with-shell/
许可协议: 本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可