【转载】Kubernetes IPAM分配IP原理 host-local

转载

Kubernetes IPAM分配IP原理

IPAM是k8s cni插件中负责分配ip的一类插件,其实现有dhcp,host-local等

在这里插入图片描述

IPAM host-local分配IP原理

Kube-controller-manager为每个节点分配一个podCIDR。从podCIDR中的子网值中为节点上的Pod分配IP地址。由于所有节点上的podCIDR是不相交的子网,因此它允许为每个pod分配唯一的IP地址。

先看pod创建过程:

在这里插入图片描述

1、kubelet收到创建Pod的事件,调用CRI接口,Docker或者其他的容器运行时发起对Pod的创建;

2、后端Docker创建网络空间network namespace;

3、CRI调用CNI插件,会传入刚刚创建好的网络空间network namespace;

4、CNI插件会读取Node节点/etc/cni/net.d路径下的配置文件,配置Pod网络,
     实现Pod网络到Node节点网络的打通,同时拿到Pod的IP;

5、Docker创建Pause容器,每一个Pod在启动时都会有一个基础的Pause容器,
   把Pause容器加入到网络空间中,后续其他的容器都会公用Pause容器的网络空间。

host-local为啥叫host-local

host-local插件从address ranges 中分配IP,将分配的结果存在本地机器,所以这也是为什么叫做host-local

毕竟如果没有地方进行纪录来进行比较,就没有办法每次都回传一个没有被用过的IP地址。

host-local example

dataDir的变数会指定要用哪个资料夹作为host-local记录用过的资讯,预设值是/var/lib/cni/networks/ .

{
    "ipam": {
        "type": "host-local",
        "ranges": [
            [
                {
                    "subnet": "10.10.0.0/16",
                    "rangeStart": "10.10.1.20",
                    "rangeEnd": "10.10.3.50",
                    "gateway": "10.10.0.254"
                },
                {
                    "subnet": "172.16.5.0/24"
                }
            ],
            [
                {
                    "subnet": "3ffe:ffff:0:01ff::/64",
                    "rangeStart": "3ffe:ffff:0:01ff::0010",
                    "rangeEnd": "3ffe:ffff:0:01ff::0020"
                }
            ]
        ],
        "routes": [
            { "dst": "0.0.0.0/0" },
            { "dst": "192.168.0.0/16", "gw": "10.10.5.1" },
            { "dst": "3ffe:ffff:0:01ff::1/64" }
        ],
        "dataDir": "/run/my-ipam-path"
    }
}

// 没有特别指定dataDir,所有的档案都会存放在/var/lib/cni/networks/里面
$ sudo find /var/lib/cni/networks/ -type f
/var/lib/cni/networks/last_reserved_ip.0
/var/lib/cni/networks/10.10.1.1
/var/lib/cni/networks/10.10.1.2
/var/lib/cni/networks/10.10.1.3

我们可以观察到,每个被用过的IP都会产生一个以该IP为名的文件,该档案中的内容非常简单,
就是使用的container ID

还可以观察到一个名为last_reserved_ip的文件,用来记住每个range目前分配的最后一个IP是哪个

在k8s node上进行验证

$ kubectl describe nodes | grep PodCIDR
PodCIDR:                     10.244.0.0/24
PodCIDR:                     10.244.1.0/24
PodCIDR:                     10.244.2.0/24

$ sudo cat /run/flannel/subnet.env
FLANNEL_NETWORK=10.244.0.0/16
FLANNEL_SUBNET=10.244.0.1/24
FLANNEL_MTU=1450
FLANNEL_IPMASQ=true

$ sudo ls /var/lib/cni/networks/cbr0
10.244.0.2  10.244.0.3  10.244.0.8  last_reserved_ip.0  lock

$ sudo cat /var/lib/cni/networks/cbr0/10.244.0.8
2d39d5afb81e56314a7fd6bdd57c9ccf6d02c32b556273cfb6b9bb8a248c851b

$ sudo docker ps  --no-trunc | grep $(sudo cat /var/lib/cni/networks/cbr0/10.244.0.8)
2d39d5afb81e56314a7fd6bdd57c9ccf6d02c32b556273cfb6b9bb8a248c851b   k8s.gcr.io/pause:3.1 "/pause" Up 4 hours  k8s_POD_k8s-udpserver-6576555bcb-7h8jh_default_87196597-ccda-4643-ac5d-85343a3b6c90_0

解析:
* 每个节点上都有一个PodCIDR的栏位,代表的是该节点可以使用的网段
* 节点是对应到的PodCIDR是10.244.0.0/24,与/run/flannel/subnet.env里面的数值一致。
* 查看下ipam分配ip后为每个ip生成的记录文件,内容是一个id
* docker查看,可以发现是pod里对应的container的id

host-local如何知道上一次分配到ip是哪个

host-local会在宿主机生成一个记录文件:/var/lib/cni/networks/last_reserved_ip.0

cni官方文档对host-local的简述

host-local IPAM plugin allocates ip addresses out of a set of address ranges. It stores the state locally on the host filesystem, therefore ensuring uniqueness of IP addresses on a single host. The allocator can allocate multiple ranges, and supports sets of multiple (disjoint) subnets. The allocation strategy is loosely round-robin within each range set.

下文对官方文档中的 round-robin 算法原理进行剖析

IP分配算法:round-robin

轮询调度(Round Robin Scheduling)算法就是以轮询的方式依次将请求调度不同的服务器,即每次调度执行i = (i + 1) mod n,并选出第i台服务器。 算法的优点是其简洁性,它无需记录当前所有连接的状态,所以它是一种无状态调度。

host-local会根据参数给予的IP范围,依序回传一个没有被使用过的IP, 这个运作原理非常的符合我们真正的需求,每次有POD产生的时候都可以得到一个没有被使用过的IP地址,避免重复同时又能够使用。

IPAM host-local round-robin分配

源码剖析:

// 分配一个ip
func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
	a.store.Lock()
	defer a.store.Unlock()

	var reservedIP *net.IPNet
	var gw net.IP

    // 如果请求ip不为空,则查看请求的ip是否满足分配的条件
	if requestedIP != nil {
		if err := canonicalizeIP(&requestedIP); err != nil {
			return nil, err
		}

		r, err := a.rangeset.RangeFor(requestedIP)
		if err != nil {
			return nil, err
		}

		if requestedIP.Equal(r.Gateway) {
			return nil, fmt.Errorf("requested ip %s is subnet's gateway", requestedIP.String())
		}

		reserved, err := a.store.Reserve(id, ifname, requestedIP, a.rangeID)
		if err != nil {
			return nil, err
		}
		if !reserved {
			return nil, fmt.Errorf("requested IP address %s is not available in range set %s", requestedIP, a.rangeset.String())
		}
		reservedIP = &net.IPNet{IP: requestedIP, Mask: r.Subnet.Mask}
		gw = r.Gateway
    // 否则分配一个新的未使用的ip回去
	} else {
        ...
        ...
        // 获取迭代器,迭代器指向上一个分配的ip
		iter, err := a.GetIter()
		if err != nil {
			return nil, err
		}
		for {
		    // 迭代器的下一个ip就是要分配出去的ip
			reservedIP, gw = iter.Next()
			if reservedIP == nil {
				break
			}

			reserved, err := a.store.Reserve(id, ifname, reservedIP.IP, a.rangeID)
			if err != nil {
				return nil, err
			}

			if reserved {
				break
			}
		}
	}

	if reservedIP == nil {
		return nil, fmt.Errorf("no IP addresses available in range set: %s", a.rangeset.String())
	}

	return &current.IPConfig{
		Address: *reservedIP,
		Gateway: gw,
	}, nil
}

// 获取迭代器
// 迭代器指向的ip就是上一个分配的ip
func (a *IPAllocator) GetIter() (*RangeIter, error) {
	iter := RangeIter{
		rangeset: a.rangeset,
	}

	// Round-robin by trying to allocate from the last reserved IP + 1
	startFromLastReservedIP := false

    // 迭代器指向的ip就是上一个分配的ip
    // 原理是读取本地的/var/lib/cni/networks/last_reserved_ip.0
	lastReservedIP, err := a.store.LastReservedIP(a.rangeID)

	// Find the range in the set with this IP
	// 如果存在上一个分配的ip,则迭代器指向上一个分配的ip,否则指向第一个ip
	if startFromLastReservedIP {
		for i, r := range *a.rangeset {
			if r.Contains(lastReservedIP) {
				iter.rangeIdx = i
				// We advance the cursor on every Next(), so the first call
				// to next() will return lastReservedIP + 1
				iter.cur = lastReservedIP
				break
			}
		}
	} else {
		iter.rangeIdx = 0
		iter.startIP = (*a.rangeset)[0].RangeStart
	}
	return &iter, nil
}

// 从迭代器获取其下一个ip作为分配的ip
// 迭代器指向的ip是上一次分配的ip,其下一个ip就是round-robin算法的下一个ip
func (i *RangeIter) Next() (*net.IPNet, net.IP) {
	r := (*i.rangeset)[i.rangeIdx]

    // 如果是第一次分配,则取第一个ip
	if i.cur == nil {
		i.cur = r.RangeStart
		i.startIP = i.cur
		if i.cur.Equal(r.Gateway) {
			return i.Next()
		}
		return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
	}

    // 如果到了末端,则重头开始
	if i.cur.Equal(r.RangeEnd) {
	    // 这里是round-robin算法的实现
		i.rangeIdx += 1
		i.rangeIdx %= len(*i.rangeset)
		r = (*i.rangeset)[i.rangeIdx]

		i.cur = r.RangeStart
    // 如果没到末端,则取下一个ip
	} else {
		i.cur = ip.NextIP(i.cur)
	}

	if i.startIP == nil {
		i.startIP = i.cur
	} else if i.cur.Equal(i.startIP) {
		// IF we've looped back to where we started, give up
		return nil, nil
	}

	if i.cur.Equal(r.Gateway) {
		return i.Next()
	}

	return &net.IPNet{IP: i.cur, Mask: r.Subnet.Mask}, r.Gateway
}

总结:每次从本地读取/var/lib/cni/networks/last_reserved_ip.0文件,获取上一次分配的ip,然后其下一个ip就是要分配出去的,如果没有上一次分配的ip,则获取第一个ip分配出去

k8s如何保证pod ip不重复

1、kubernetes会针对每个node去标示一个名为PodCIDR的值,代表该Node可以使用的网段是什么,且分配的时候保证为每个pod分配不同的段

2、flannel的Pod 会去读取node中的PodCIDR,并且将该资讯写道/run/flannel/subnet.env中,
flannel CNI收到任何创建Pod的请求时,会去读取/run/flannel/subnet.env,flannel CNI会调用bridge CNI,bridge CNI会调用host-local CNI,host-local CNI使用round-robin保证同一个node上的pod的ip不重复

为什么使用round-robin,而不是每次取最小的未分配的ip

因为CNI回收ip和docker回收容器两个操作不是一个原子性的操作!

假设有一个pod用了10.244.1.1,然后这个pod被删除了,然后CNI插件释放这个ip,docker释放这个容器,但是有可能docker还没完全释放这个容器的时候,CNI先完成了ip的释放并且这时候又有新的pod创建出来,那么新的pod可能用了10.244.1.1这个ip,但是旧的正在被释放容器也在用这个ip,就可能出现冲突和数据混乱

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 使用docker-compose可以通过自定义网络来分配IP地址。在docker-compose文件中定义一个网络,然后为该网络设置一个IP范围,通过这个范围来分配IP地址给容器。 首先,在docker-compose.yml文件中定义一个网络,在`networks`部分添加一个新的网络,并为该网络指定一个自定义的名称,例如"my_network"。 ``` version: '2' services: my_service: image: my_image networks: - my_network networks: my_network: ipam: config: - subnet: 192.168.0.0/24 ``` 上面的例子中,我们定义了一个名为"my_network"的网络,并为该网络指定了一个IP范围192.168.0.0/24。 然后,将容器添加到该网络中。在服务的定义中,使用`networks`属性将服务连接到上述定义的网络。 这样,当使用`docker-compose up`启动容器时,容器将被分配一个在IP范围内的IP地址。 需要注意的是,如果要为多个容器分配独立的IP地址,需要确保IP范围的大小足够容纳所有容器所需的地址。 另外,使用自定义网络可以实现多个容器之间的通信,它们可以通过容器名称来相互访问,而无需使用IP地址。 综上所述,通过在docker-compose文件中定义自定义网络并为其设置IP范围,可以实现为容器分配IP地址。 ### 回答2: Docker Compose 允许我们在容器部署时使用自定义网络来分配 IP 地址。下面是关于 Docker Compose 自定义网络分配 IP 地址的一些重要信息: 1. 自定义网络的配置:在 Docker Compose 文件中,我们可以使用 `networks` 关键字定义自定义网络,并为其指定一个名称。例如: ```yaml version: '3' services: app: # 定义自定义网络 networks: - my-network networks: my-network: # 可选配置网络驱动 driver: bridge ``` 2. IP 地址分配方式:Docker Compose 支持三种不同的 IP 地址分配方式,分别是 `default`、`bridge` 和 `host`。默认情况下,Docker Compose 使用 `default` 分配方式。通过在网络配置中指定 `ipam` 选项,我们可以使用其他分配方式。 3. 使用 `default` 分配方式:如果使用默认的 `default` 分配方式,那么 Docker Compose 将自动为容器分配 IP 地址,并且会保持容器名称与 IP 地址之间的关联。例如,我们可以通过在服务定义中使用 `networks` 字段指定网络,并通过使用该网络的容器名称来访问容器。 ```yaml services: app1: networks: - my-network app2: networks: - my-network networks: my-network: ``` 在这种情况下,Docker Compose 将为 `app1` 容器分配一个 IP 地址,并将其映射到容器名称 `my-app1`。 4. 使用其他分配方式:如果我们希望自定义 IP 地址分配方式,可以在网络配置中使用 `ipam` 选项。例如,我们可以指定一个静态 IP 地址供容器使用。 ```yaml services: app: networks: my-network: ipv4_address: 192.168.0.2 networks: my-network: ipam: config: - subnet: 192.168.0.0/16 ``` 在这种情况下,Docker Compose 将为 `app` 容器分配指定的 IP 地址 192.168.0.2。 通过以上的配置,我们可以根据需要在 Docker Compose 中自定义网络并分配 IP 地址。这样可以更好地控制容器之间的通信,并实现特定的网络需求。 ### 回答3: 在Docker Compose中,我们可以使用自定义网络来对容器进行分组和分配IP地址。通过自定义网络,我们可以更好地管理容器之间的通信和连接。 首先,在docker-compose.yml文件中定义一个自定义网络。我们可以使用以下语法来创建一个自定义网络: ```yml networks: <network_name>: driver: <driver_name> ``` 在上面的代码片段中,`<network_name>`是我们给网络取的名称,`<driver_name>`是网络使用的驱动程序。 例如,我们可以创建一个名为`my_network`的自定义网络,并使用默认的`bridge`驱动程序,只需在docker-compose.yml文件中添加以下代码: ```yml networks: my_network: driver: bridge ``` 一旦我们定义了一个自定义网络,我们可以将容器连接到这个网络,并为它们分配IP地址。 要将容器连接到自定义网络,我们可以使用以下代码: ```yml services: <service_name>: networks: - <network_name> ``` 在上面的代码片段中,`<service_name>`是我们要将容器连接到的服务名称,`<network_name>`是我们之前定义的自定义网络名称。 例如,如果我们要将一个名为`web`的服务连接到`my_network`网络,我们可以在docker-compose.yml文件中添加以下代码: ```yml services: web: networks: - my_network ``` 最后,我们可以为连接到自定义网络的容器分配IP地址。Docker将自动为连接到网络的容器分配IP地址。我们可以使用以下代码来查看容器的IP地址: ```bash docker network inspect <network_name> ``` 在上面的命令中,`<network_name>`是我们之前定义的自定义网络名称。 总之,使用Docker Compose的自定义网络,我们可以轻松地进行容器的分组和分配IP地址。通过定义自定义网络并连接容器,我们可以实现灵活的容器通信和连接管理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值