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

最近业务有个需求,需要在宿主机上获取容器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.hqwc.cn/news/69283.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WSL2 Ubuntu子系统安装cuda+cudnn+torch

文章目录 前言一、安装cudncudnn安装pytorch 前言 确保Windows系统版本高于windows10 21H2或Windows11&#xff0c;然后在Windows中将显卡驱动升级到最新即可&#xff0c;WSL2已支持对显卡的直接调用。 一、安装cudncudnn 配置cuda环境&#xff0c;WSL下的Ubuntu子系统的cu…

Redis心跳检测

在命令传播阶段&#xff0c;从服务器默认会以每秒一次的频率&#xff0c;向主服务器发送命令&#xff1a; REPLCON FACK <rep1 ication_ offset>其中replication_offset是从服务器当前的复制偏移量。 发送REPLCONF ACK命令对于主从服务器有三个作用&#xff1a; 检测主…

Linux交叉编译opencv并移植ARM端

Linux交叉编译opencv并移植ARM端 - 知乎 一、安装交叉编译器 目标平台为arm7l&#xff0c;此为32位ARM架构&#xff0c;要安装合适的编译器 sudo apt install arm-linux-gnueabihf-gcc sudo apt install arm-linux-gnueabihf-g注意&#xff1a;64位ARM架构的编译器与32位ARM架…

macOS CLion 使用 bits/stdc++.h

macOS 下 CLion 使用 bits/stdc.h 头文件 terminal运行 brew install gccCLion里配置 -D CMAKE_CXX_COMPILER/usr/local/bin/g-11

MySQL卸载并重装指定版本

MySQL卸载并重装制定版本 学习新的项目&#xff0c;发现之前的Navicat已经失去了与现有MySQL的链接&#xff0c;而且版本也不适合&#xff0c;为了少走弯路&#xff0c;准备直接重装相应版本的MySQL 卸载现有MySQL 停止windows的MySQL服务&#xff0c;【windowsR】打开运行框…

CI/CD流水线实战

不知道为什么&#xff0c;现在什么技术都想学&#xff0c;因为我觉得我遇到了技术的壁垒&#xff0c;大的项目接触不到&#xff0c;做的项目一个字辣*。所以&#xff0c;整个人心浮气躁&#xff0c;我已经得通过每天的骑行和长跑缓解这种浮躁了。一个周末&#xff0c;我再次宅在…

使用phpstorm开发调试thinkphp

1.环境准备 1.开发工具下载&#xff1a;PhpStorm: PHP IDE and Code Editor from JetBrains 2.PHP下载&#xff1a;PHP: Downloads 3. PHP扩展&#xff1a;PECL :: Package search 4.用与调试的xdebug模块&#xff1a; Xdebug: Downloads xdebug模块&#xff0c;如果是php8以…

TypeScript 语法

环境搭建 以javascript为基础构建的语言&#xff0c;一个js的超集&#xff0c;可以在任何支持js的平台中执行&#xff0c;ts扩展了js并且添加了类型&#xff0c;但是ts不能被js解析器直接执行&#xff0c;需要编译器编译为js文件&#xff0c;然后引入到 html 页面使用。 ts增…

用于弥散加权MRI的关节各向异性维纳滤光片研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

面试热题(验证二叉搜索树)

给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。节点的右子树只包含 大于 当前节点的数。所有左子树和右子树自身必须也是二叉树 二叉树满足以上3个条件&#xff0c…

“记账”很麻烦,看这场竞赛中的队伍与合合信息是如何解决问题的

在我们日常生活中或多或少都会有记账的情况&#xff0c;以此来对自己的收支和消费习惯进行分析&#xff0c;来帮助自己减少不必要的开支&#xff0c;优化财务决策、合理分配资金&#xff0c;减少财务压力和不必要的浪费。 但记账这个动作本身就是一件比较麻烦的。虽然现阶段有…

照耀国产的星火,再度上新!

国产之光&#xff0c;星火闪耀 ⭐ 新时代的星火⭐ 多模态能力⭐ 图像生成与虚拟人视频生成⭐ 音频生成与OCR笔记收藏⭐ 助手模式更新⭐ 插件能力⭐ 代码能力⭐ 写在最后 ⭐ 新时代的星火 在这个快速变革的时代&#xff0c;人工智能正迅猛地催生着前所未有的革命。从医疗到金融…