一波七折之寻找遗失的容器ip

news/2024/11/7 20:47:21/

最近业务有个需求,需要在宿主机上获取容器ip。获取ip这就不是个事,平凡而普通。

常用手段获取ip

常用的命令ifconfig, ip等等首先被pass,因为宿主机环境未知,这些命令可能没有。

那么只能从系统api着手。getifaddrs可以获取网卡名字和ip, 一轮测试下来,发现只能拿到容器虚拟网卡名字。显然不符合业务需求,首战就这样折戟沉沙。

利用容器命令获取ip

可以用的容器命令是什么呢?docekr? crictl? … 基于开源魔改的容器管理服务*ocekr? 甚至于完全自研的XXX?一切皆有可能,这种方案细想一下也直接被pass了。

原本以为挺简单的需求此时陷入僵局,常见的方法都不好使了。

查询路由表获取ip

在k8s节点上寻寻觅觅,发现路由表有针对容器虚拟网的详细路由。

此时的虚拟网卡为主流开源方案calico的虚拟网卡,本着Destination和Iface的对应关系,解决了容器ip的问题。

虽然可以使用,但是心里却是没底的。因为容器方案层出不穷。果不其然,可用了没多长时间就遇到了担心的事情,另一个场景是基于Iaas自研的容器管理方案。此时只见容器,不见calico,路由表上空空如也。又一次陷入僵局。

进入 net ns 获取ip

容器实现的核心之一就是linux的namespace,如果我能进入容器的net-ns,就可以获取到容器的ip。

因为namespace的资料基本都是针对进程的,所以不确定一个进程的多个线程能不能分属于不同的net-ns。因为是多线程程序,且对外有网络连接,所以不可能整个进程直接进入容器的net-ns,至少发起网络连接的线程需要在默认net-ns下。写了测试程序发现一个进程的多个线程可以分属于不同的net-ns。

于是遍历/proc/目录,先获取进程1的net-ns。

/proc/1/ns/net --> symlink

再逐个获取宿主机上所有进程的net-ns,只要和进程1的symlink不相同的,就表示是一个新的net-ns,我就需要进入其中获取这个net-ns下的ip。

进入容器的net-ns后,利用getifaddrs获取网卡的ip和ifindex(用于标识系统中的每个网络接口唯一ID)。

宿主机上同样利用getifaddrs获取网卡名称,同时获取网卡XXX的iflink(主要被隧道设备使用,用于标识隧道另一头的设备ID)

/sys/class/net/XXX/iflink

如果容器内网卡A的ifindex和宿主机容器网卡A1的iflink能匹配上,意味着A和A1属于一对veth pair,那么容器网卡A1的ip就是容器内网卡A的ip。

到此完美解决,理论上适用于任意形态容器的ip获取。同时以read-only进入容器的net-ns,也不会破坏容器的net-ns。

遗失的iflink

经过测试,可以获取docker系容器网卡ip。

但是测试常用组合containerd + calico时,却发现行不通了。最终发现是因为所有容器内网卡的ifindex都是同一个值,因为属于不同的net-ns,ifindex可以是同一个值。自然宿主机容器网卡的iflink的值也都是同一个值。

解决起来,也比较简单。 宿主机容器网卡都属于默认net-ns,所以他们的ifindex必定不一样,用容器内网卡的iflink去匹配宿主机容器网卡的ifindex即可。

尝试从/sys/class/net/XXX/iflink读取容器网卡的iflink。嗯?读取失败?文件不存在?原来只进入了net-ns,没有进入mnt-ns,所以无法读取该文件。

于是尝试利用setns让 ‘获取容器网卡ip的线程’ 再进入一个mnt-ns,嗯?调用失败?invalid arguments?近在眼前的胜利怎么能让它阻挡,各种查资料,最后得出结论,mnt-ns只能在主线程中setns,并且调用setns成功后,才能创建其他线程。 最后不信邪地写代码验证,发现确实如此。Go语言因为天然多线程,最后只能利用 cgo constructor trick 在go的runtime还没启动时提前setns。

我的程序不可能在主线程设置mnt-ns,这与程序记录日志格格不如。又一次陷入僵局,这次似乎无解了。。。

柳暗花明的iproute2

nsenter进入容器的net-ns, 利用ip命令居然可以读出来iflink,类似这样的网卡eth0@if6,if6表示iflink的值是6。在只进入net-ns前提下,它为什么可以读出来iflink?

于是下载ip命令的源码iproute2,通过添加printf分析源码,发现它是利用netlink从内核得到的iflink,事情似乎又有转机了。

netlink寻找iflink

利用AF_NETLINK的socket,向内核发起RTM_GETLINK请求,从响应ifinfomsg中解析出iflink。因为在容器net-ns下创建的socket,所以socket的属性与这个容器网络相关联,可以查出容器网卡的信息。
示例源码如下:

std::unordered_map<std::string, uint32_t> iflinks;
struct nl_req {struct nlmsghdr hdr;struct rtgenmsg gen;
};struct sockaddr_nl kernel {};
kernel.nl_family = AF_NETLINK;struct sockaddr_nl local {};
local.nl_family = AF_NETLINK;
local.nl_pid = getpid();
local.nl_groups = 0;nl_req req{};
req.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(struct rtgenmsg));
req.hdr.nlmsg_type = RTM_GETLINK;
req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP;
req.hdr.nlmsg_pid = getpid();
req.hdr.nlmsg_seq = 1;
req.gen.rtgen_family = AF_PACKET;struct iovec iov {};
iov.iov_base = &req;
iov.iov_len = req.hdr.nlmsg_len;struct msghdr msg {};
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_name = &kernel;
msg.msg_namelen = sizeof(kernel);auto fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {return iflinks;
}
auto closer = std::shared_ptr<char>(new char,[fd](char* p) {delete p; close(fd); });auto ok = bind(fd, (struct sockaddr*)&local, sizeof(local));
if (ok < 0) {return iflinks;
}
sendmsg(fd, (struct msghdr*)&msg, 0);memset(&iov, 0, sizeof(iov));
constexpr int buf_size = 1024 * 32;
char buf[buf_size]{};
iov.iov_base = buf;
iov.iov_len = buf_size;
int64_t msg_len = 0;while (true) {msg_len = recvmsg(fd, &msg, 0);if (msg_len < 0) {break;}auto nlmsg_ptr = (struct nlmsghdr*)buf;if (nlmsg_ptr->nlmsg_type == NLMSG_DONE ||nlmsg_ptr->nlmsg_type == NLMSG_ERROR) {break;}while (NLMSG_OK(nlmsg_ptr, msg_len)) {if (nlmsg_ptr->nlmsg_type != RTM_NEWLINK) {nlmsg_ptr = NLMSG_NEXT(nlmsg_ptr, msg_len);continue;}auto ifi_ptr = (struct ifinfomsg*)NLMSG_DATA(nlmsg_ptr);auto attr_ptr = IFLA_RTA(ifi_ptr);auto attr_len = nlmsg_ptr->nlmsg_len - NLMSG_LENGTH(sizeof(*ifi_ptr));std::string ifname;uint32_t iflink = 0;while (RTA_OK(attr_ptr, attr_len)) {if (attr_ptr->rta_type == IFLA_IFNAME) {ifname = (char*)RTA_DATA(attr_ptr);}if (attr_ptr->rta_type == IFLA_LINK) {iflink = *((uint32_t*)RTA_DATA(attr_ptr));}attr_ptr = RTA_NEXT(attr_ptr, attr_len);}if (iflink != 0) {iflinks.emplace(std::move(ifname), iflink);}nlmsg_ptr = NLMSG_NEXT(nlmsg_ptr, msg_len);}
}

完美终章

遍历宿主机所有进程的net-ns,选择出和进程1不一样的新net-ns,逐个进入新的net-ns,利用getifaddrs获取容器网卡ip,利用netlink获取容器网卡iflink。

与宿主机上的网卡ifindex做关联比对,确定宿主机容器网卡ip。到此在宿主机上获取容器网卡ip完成。

如果是K8s daemonset模式,spec增加hostNetwork: true和hostPID: true 即可


http://www.ppmy.cn/news/1027866.html

相关文章

掌握Python的X篇_34_Python朗读文字

各种广告中说python是人工智能的主宰&#xff0c;其实这更多是噱头的成分&#xff0c;但是python确实可以做很多的事情&#xff0c;本篇将会介绍利用pythonAI平台来合成声音。今天将会用到的是百度。 文章目录 1. baiToVoice2. 注册appid3. 合成代码 1. baiToVoice 使用百度A…

C++初阶——运算符重载

前言&#xff1a;前面介绍过了函数重载&#xff0c;C为了增强代码的可读性引入了运算符重载的概念&#xff0c;运算符重载是具有特殊函数名的函数&#xff0c;也具有其返回值类型。 下文博主将通过自定义类型日期类的比较引出运算符重载&#xff0c;以此凸显运算符重载提高代码…

【第二阶段】kotlin语言的内联-inline关键字

1.函数如果没有使用lambda作为参数&#xff0c;就不需要声明成内联 2.函数如果使用lambda作为参数&#xff0c;就需要声明成内联&#xff0c;如果不使用内联&#xff0c;在调用端会生成多个对象来完成lambda的调用&#xff0c;会造成性能的损耗 3.函数如果使用lambda作为参数&a…

项目实战 — 消息队列(8){网络通信设计①}

目录 一、自定义应用层协议 &#x1f345; 1、格式定义 &#x1f345; 2、准备工作 &#x1f384;定义请求和响应 &#x1f384; 定义BasicArguments &#x1f384; 定义BasicReturns &#x1f345; 2、创建参数类 &#x1f384; 交换机 &#x1f384; 队列 &#x1f38…

2023河南萌新联赛第(五)场:郑州轻工业大学C-数位dp

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 给定一个正整数 n&#xff0c;你可以对 n 进行任意次&#xff08;包括零次&#xff09;如下操作&#xff1a; 选择 n 上的某一数位&#xff0c;将其删去&#xff0c;剩下的左右部分合并。例如 123&#xff0c;你可以选择…

MATLAB 2023a的机器学习、深度学习

MATLAB 2023版的深度学习工具箱&#xff0c;提供了完整的工具链&#xff0c;使您能够在一个集成的环境中进行深度学习的建模、训练和部署。与Python相比&#xff0c;MATLAB的语法简洁、易于上手&#xff0c;无需繁琐的配置和安装&#xff0c;让您能够更快地实现深度学习的任务。…

【linux】2 软件管理器yum和编辑器vim

目录 1. linux软件包管理器yum 1.1 什么是软件包 1.2 关于rzsz 1.3 注意事项 1.4 查看软件包 1.5 如何安装、卸载软件 1.6 centos 7设置成国内yum源 2. linux开发工具-Linux编辑器-vim使用 2.1 vim的基本概念 2.2 vim的基本操作 2.3 vim正常模式命令集 2.4 vim末行…

FileZilla Server安装配置使用说明

作者&#xff1a;John 链接&#xff1a;https://www.zhihu.com/question/20577011/answer/2360828234 来源&#xff1a;知乎 第一步&#xff1a;右键点击”立即下载“ 第二步&#xff1a;服务器端点击&#xff0c;“windows平台”版本 第三步&#xff1a;这个安装最新的“Fi…