WEBKT

如何用eBPF揪出Kubernetes Pod里的“内鬼”?网络连接异常检测实战

38 0 0 0

为什么选择eBPF?

eBPF与cgroup的完美结合

实战:追踪Pod的网络连接

进阶:连接状态追踪

注意事项

总结

作为一名整天和云原生打交道的DevOps,排查Kubernetes集群问题是家常便饭。你有没有遇到过这样的情况:某个Pod突然变得不太对劲,疯狂对外建立连接,但又不知道它到底在干什么?传统的排查方法,比如抓包,效率低而且容易遗漏关键信息。今天,我就来分享一下如何利用eBPF这个“黑科技”,精准追踪Pod的网络连接,揪出那些偷偷摸摸的“内鬼”,并进行异常检测。

为什么选择eBPF?

在深入细节之前,先简单聊聊eBPF。这玩意儿就像Linux内核的“瑞士军刀”,能在内核运行时动态地注入代码,而无需修改内核源码或重启系统。这意味着我们可以在不影响Pod正常运行的情况下,实时监控它的网络行为。相比传统的tcpdump、Wireshark等工具,eBPF的优势在于:

  • 高性能: eBPF程序在内核态运行,直接访问内核数据,避免了用户态和内核态之间频繁切换的开销。
  • 灵活性: 可以根据需求自定义eBPF程序,监控特定的网络事件,例如连接建立、数据收发、连接关闭等。
  • 安全性: eBPF程序需要经过内核验证器的检查,确保不会导致系统崩溃或安全漏洞。

eBPF与cgroup的完美结合

要追踪特定Pod的网络连接,我们需要将eBPF程序与cgroup(Control Group)结合起来。cgroup是Linux内核提供的一种资源隔离机制,可以限制、控制和隔离进程组的资源使用。Kubernetes正是利用cgroup来管理Pod的资源。

我们可以将eBPF程序附加到Pod对应的cgroup上,这样eBPF程序就能监控该Pod中所有进程的网络事件。简单来说,cgroup相当于给Pod划了一个“监控区域”,eBPF程序只在这个区域内“巡逻”。

实战:追踪Pod的网络连接

接下来,我们通过一个具体的例子,演示如何使用eBPF追踪Pod的网络连接。

1. 确定目标Pod的cgroup路径

首先,我们需要找到目标Pod对应的cgroup路径。可以使用kubectl describe pod <pod-name>命令查看Pod的详细信息,找到kubernetes.io/sandbox的container ID。然后,在/proc/<pid>/cgroup文件中查找包含该container ID的行,即可得到cgroup路径。这里的<pid>是Pod中任意一个进程的ID。

例如,假设Pod名为nginx-pod,container ID为abcdefg1234567,找到的cgroup路径可能是/sys/fs/cgroup/kubepods/burstable/podabcdefg1234567/

2. 编写eBPF程序

接下来,我们需要编写一个eBPF程序,用于监控Pod的网络连接。以下是一个简单的示例,使用BPF_PROG_TYPE_SOCKET_FILTER类型的eBPF程序来抓取TCP连接事件:

#include <linux/bpf.h>
#include <linux/socket.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// 定义存储连接信息的结构体
struct conn_info {
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
__u64 timestamp;
};
// 定义BPF映射,用于存储连接信息
BPF_HASH(connections, struct conn_info, __u64, 1024);
// 定义程序入口
int socket_filter(struct sk_buff *skb) {
// 获取以太网头部
struct ethhdr *eth = bpf_hdr_pointer(skb->data);
if (!eth)
return 0;
// 只处理IP协议
if (eth->h_proto != bpf_htons(ETH_P_IP))
return 0;
// 获取IP头部
struct iphdr *ip = bpf_hdr_pointer(skb->data + sizeof(struct ethhdr));
if (!ip)
return 0;
// 只处理TCP协议
if (ip->protocol != IPPROTO_TCP)
return 0;
// 获取TCP头部
struct tcphdr *tcp = bpf_hdr_pointer(skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr));
if (!tcp)
return 0;
// 填充连接信息
struct conn_info conn = {
.saddr = bpf_ntohl(ip->saddr),
.daddr = bpf_ntohl(ip->daddr),
.sport = bpf_ntohs(tcp->source),
.dport = bpf_ntohs(tcp->dest),
.timestamp = bpf_ktime_get_ns()
};
// 将连接信息存入BPF映射
__u64 value = 1;
connections.update(&conn, &value);
return 0;
}
char LICENSE[] SEC("license") = "GPL";

这个eBPF程序会抓取TCP连接的源IP、目的IP、源端口、目的端口和时间戳,并将这些信息存储在一个名为connections的BPF映射中。你可以根据需要修改这个程序,例如添加对UDP协议的支持,或者过滤特定的IP地址和端口。

3. 编译和加载eBPF程序

使用LLVM和Clang编译eBPF程序:

clang -O2 -target bpf -c ebpf_program.c -o ebpf_program.o

然后,使用bpftool工具加载eBPF程序到内核,并将其附加到目标Pod的cgroup上:

bpftool prog load ebpf_program.o /sys/fs/bpf/my_program
bpftool cgroup attach /sys/fs/cgroup/kubepods/burstable/podabcdefg1234567/ sock_ops pinned /sys/fs/bpf/my_program

这里,/sys/fs/bpf/my_program是eBPF程序的挂载点,你可以自定义。sock_ops是cgroup的附加类型,用于监控网络事件。

4. 从BPF映射中读取连接信息

使用bpftool或其他工具,例如Python的bcc库,从connections BPF映射中读取连接信息:

from bcc import BPF
# 加载eBPF程序
b = BPF(src_file="ebpf_program.c")
# 获取connections BPF映射
connections = b["connections"]
# 遍历connections BPF映射
for key, value in connections.items():
print("源IP: %s" % inet_ntop(AF_INET, key.saddr))
print("目的IP: %s" % inet_ntop(AF_INET, key.daddr))
print("源端口: %d" % key.sport)
print("目的端口: %d" % key.dport)
print("时间戳: %d" % key.timestamp)

这段Python代码会遍历connections BPF映射,并打印出每个连接的详细信息。你可以将这些信息存储到数据库或日志文件中,以便后续分析。

5. 异常检测

有了连接信息,就可以进行异常检测了。以下是一些常见的异常检测方法:

  • 连接数异常高: 统计Pod在一段时间内建立的连接数,如果超过预设的阈值,则认为存在异常。
  • 连接到未知IP: 维护一个白名单,包含Pod允许连接的IP地址。如果Pod连接到白名单之外的IP地址,则认为存在异常。
  • 连接到高危端口: 监控Pod连接的端口,如果连接到一些高危端口,例如22、23、3389等,则认为存在异常。
  • 连接频率异常高: 统计Pod与特定IP地址或端口建立连接的频率,如果超过预设的阈值,则认为存在异常。

你可以根据实际情况,组合使用这些方法,提高异常检测的准确性。

进阶:连接状态追踪

上面的例子只是抓取了TCP连接的建立事件,并没有追踪连接的状态变化。要实现连接状态追踪,可以使用BPF_PROG_TYPE_SOCK_OPS类型的eBPF程序。这种类型的程序可以监控连接的各种状态变化,例如SYN_SENT、ESTABLISHED、FIN_WAIT1、CLOSE_WAIT等。

以下是一个简单的示例:

#include <linux/bpf.h>
#include <linux/socket.h>
#include <linux/tcp.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_endian.h>
// 定义存储连接信息的结构体
struct conn_info {
__u32 saddr;
__u32 daddr;
__u16 sport;
__u16 dport;
__u8 state;
__u64 timestamp;
};
// 定义BPF映射,用于存储连接信息
BPF_HASH(connections, struct conn_info, __u64, 1024);
// 定义程序入口
int sock_ops(struct bpf_sock_ops *skops) {
// 只处理TCP协议
if (skops->family != AF_INET)
return 0;
// 获取连接信息
struct conn_info conn = {
.saddr = bpf_ntohl(skops->remote_ip4),
.daddr = bpf_ntohl(skops->local_ip4),
.sport = bpf_ntohs(skops->remote_port),
.dport = bpf_ntohs(skops->local_port),
.state = skops->state,
.timestamp = bpf_ktime_get_ns()
};
// 将连接信息存入BPF映射
__u64 value = 1;
connections.update(&conn, &value);
return 0;
}
char LICENSE[] SEC("license") = "GPL";

这个eBPF程序会抓取连接的源IP、目的IP、源端口、目的端口、状态和时间戳,并将这些信息存储在connections BPF映射中。通过分析连接状态的变化,可以更准确地判断是否存在异常行为。

注意事项

  • 内核版本要求: eBPF需要较新的Linux内核版本支持,建议使用4.14及以上版本。
  • 权限问题: 加载和附加eBPF程序需要root权限。
  • 性能影响: eBPF程序虽然性能很高,但仍然会对系统造成一定的性能影响。建议只监控必要的网络事件,并优化eBPF程序,降低性能开销。
  • 安全问题: 编写eBPF程序需要谨慎,避免引入安全漏洞。

总结

eBPF是一个强大的网络监控工具,可以帮助我们精准追踪Kubernetes Pod的网络连接,并进行异常检测。通过将eBPF程序与cgroup结合,我们可以将监控范围限定在特定的Pod内,避免对整个集群造成影响。希望这篇文章能够帮助你更好地利用eBPF,提升Kubernetes集群的安全性和稳定性。

云原生侦探 eBPFKubernetes网络安全

评论点评

打赏赞助
sponsor

感谢您的支持让我们更好的前行

分享

QRcode

https://www.webkt.com/article/9181