Kubernetes代码走读之Minion Node 组件 kube-proxy

Kube-proxy是kubernetes 里运行在minion节点上的一个组件, 它起的作用是一个服务代理的角色. 本文的内容将分为以下两部分, 源代码来自kubernetes release-0.8.1, 代码有删节,省略的代码或log输出用...表示:

1 Kube-proxy 简介

2 Kube-proxy代码解读

1 Kube-proxy简介

Kube-proxy网络代理运行在每个minion节点上。网上很多人所说这个proxy是kubernetes里的SDN组件,我本人并不这么认为, 我认为可以把他看成是一个高级的反向代理.它的功能反映了定义在每个节点上Kubernetes API中的Services信息,并且可以做简单的TCP流转发或在一组服务后端做round robin的流转发.服务端点目前通过与docker link 兼容的环境变量指定的端口被发现, 这些端口由服务代理打开.目前,用户必须在代理上选择一个端口以暴露服务.

2 Kube-proxy代码解读

Kube-proxy代码入口定义在cmd/kube-proxy/proxy.go中, 源代码较长,分段解读如下:

首先

1. 使用flag pkg初始化命令行参数到相应的变量, 如etcd_servers选项

2. 初始化log

3. 应用oomScoreAdj参数到/proc/self/oom_score_adj文件.
oom_score_adj 是-1000到1000的数值, 用来表征进程当发生OOM(out of memory)时系统对该进程的行为,值越低越不容易被杀死.默认值是-899.

4. 使用下列两个函数新建两个重要的数据结构 ServiceConfigEndpointsConfig

由于proxy和kubernetes Service概念关系很大, 强烈建议读者访问kubernetes Service官方文档了解其基本概念. 先介绍ServiceConfig结构体和相关操作NewServiceConfig(), 源代码如下, 定义在pkg/proxy/config/config.go中

ServiceConfig结构体跟踪记录Service配置信息的变化,他接受通过channel传递的Sevice上”set”, ”add”, ”remove”的操作, 并使用相应的handler函数处理这些变化. ServiceConfig通过组合的方式将config.Mux, config.Broadcaster和serviceStore类集成起来. 并启动一个goroutine watchForUpdates(bcaster, store, updates), watchForUpdates()函数源代码如下:

他的作用是当updates channel有可用的信息时, 调用bcaster.Notify()函数以广播的方式将MergedState()处理后的serivces信息通知到各个Listener. 其中MergeState实际调用的是(s *serviceStore) MergedState(); bcaster.Notify()函数源码如下, 它定义在pkg/util/config/config.go中

listener 相关的结构体源代码如下, 可以看出golang interface的飘逸灵活用法:

NewServiceConfig函数最后返回这个新建的ServiceConfig结构体.

EndpointsConfig 结构体类似于ServiceConfig结构体, 相关的操作也与Service极其相似,也是采用组合的方式将config.Mux, config.Broadcaster, endpointsStore组合起来, 并马上启动一个goroutine watchForUpdates(),调用的和之前的watchForUpdates相同. 5. 新建loadBalancerRR和代理proxier

5.1 先介绍loadBalancer,源代码定义在pkg/proxy/roundrobin.go中,如下:

5.2 proxier源代码定义在pkg/proxy/proxier中, 如下

如上面的代码注释所说NewProxier函数初始化主机上的iptables信息. proxier组成的元素主要包含刚刚创建的loadBalancer对象和一个iptables的执行器,他负责根据service的变化信息更新iptables设置.

6. 将刚刚新建的proxier和loadBalancer分别向servicesConfig和endpointsConfig绑定

他的作用是当service或endpoint的配置信息发生变化时,就调用proxier或loadbalancer的相关函数.

下面先介绍serviceConfig.RegisterHandler(proxier),源代码如下:

这个函数的功能是将proxier的OnUpdate函数注册添加到上文说的serviceConfig的bcaster的Listener[] slice里,当从channel收到新的可用信息, 实际调用的是下面的OnUpdate()函数即Proxier对象的OnUpdate(). 这个函数很重要,由于代码段比较长,下面分段解释,他定义在pkg/proxy/proxier.go 中.

首先看一下传进参数services的值

从后端的一台minion机器上的kube-proxy.log查看一下传进来的services信息如下:

如上展示的是kubernetes官方guest-example的redis-master的service, 他的重要参数是Spec这个字段,他包含service portal信息. 再继续往下进行,下面的代码段将刚收到的services信息和本地已有的通过info, exists := proxier.getServiceInfo(service.Name) 函数获得的info作比较. 如果service有更改, 则在后台重启或使用addServiceOnPort()函数新增代理:

其中核心的addServiceOnPort()函数源代码如下, 他的最主要的作用是为每个service开启一个代理socket, 其中分配一个随机的port号为portNum, 并启动一个goroutine sock.ProxyLoop() 进行代理循环,这个函数定义在pkg/proxy/proxier.go中,它实现了一个round robin代理的逻辑,供读者自行阅读,这里由于篇幅所限不赘述:

观察后台kube-proxy log 如下, 49263 就是个随机分配的port:

回到OnUpdate函数里,之后将最新的service信息保存到info数据,  由于proxier.getServiceInfo(service.Name) 返回的是指针, 所以最后实际上保存到了proxier.serviceMap这个数据结构里.

最后调用openPortal函数打开servicePortal

他实际调用的是如下的openOnePortal函数,他的源码如下,定义在pkg/proxy/proxier.go中

他的作用是保证根据上文的service信息添加了正确的iptables rule, 并将相应的规则添加到iptables NAT表里, 最后调用的是pkg/util/iptables/iptables.go中的run函数,源代码如下:

从后台的kube-proxy log中可以看到下面的iptables操作

使用命令$ iptables -t nat -L 可以看到结果如下:

可以看到minion主机上的iptable新增了KUBE-PROXY CHAIN , 并且采用的是REDIRECT target. 这个CHAIN有两个引用, 分别是PREROUTING 和 OUTPUT CHAIN. 最后的结果是容器里的应用如果想通过11.1.1.176:6379 访问redisslave服务时,流量被REDIRECT到了本机的37334端口, 通过这个本地37334端口,再使用刚刚说到的每个service提供的代理socket导向到真正后端. 如下面的终端输出所示.这里的 11.1.1.176相当于一个虚拟的ip. 这个ip范围可在启动 kube-apiserver时指定portal_net参数指定.

再使用proxier.loadBalancer.NewService函数

源代码定义在pkg/proxy/roundrobin.go中,源代码如下:

可以看到一个很重要的操作是维护serviceDtlMap这样一个golang map数据结构, key是相应service name. 这里sessionAffinityType有基于clientIPAddress的方式,也有最普通的None模式,也就是常用的round robin方式无sessionAffinity, 如下面的log就是创建了一个名为redis-master的sessionAffinityType为 None的 service.

OnUpdate函数最后的活动是对未活动的service, 即要被删除掉的service 关闭portal, 删除指定的iptables rule, 关闭代理套接字. 代码如下:

7.回到main函数,下一步指定变化的源 ,一般采用etcd_server ,即调用下面代码的else部分

流程为 7.1 先使用给定的命令行参数etcd_servers 创建一个新的etcdClient对象, 使用的pkg是coreos的github.com/coreos/go-etcd/etcd pkg. 查看后端kube-proxy的log如下

7.2 之后使用config.NewConfigSourceEtcd(etcdClient, serviceConfig.Channel(“etcd”), endpointsConfig.Channel(“etcd”))函数. 注意到这个函数的后两个参数是serviceConfig.Channel(“etcd”)和endpointsConfig.Channel(“etcd”)的返回值. 这两个函数除了make创建出相应的channel之外还做了一些额外的操作值得注意! 下面介绍其中的一个serviceConfig.Channel(“etcd”), endpointsConfig.Channel(“etcd”)雷同.

他调用Mux里的Channel函数创建一个新的channel ch, 并且创建一个传送ServiceUpdate 的channel serviceCh, 一旦serviceCh有可用的信息如update就将他写入ch. 之后再回来看一下ConfigSourceEtcd结构体和相关的NewConfigSourceEtcd函数,它的源代码定义在pkg/proxy/config/etcd.go, 代码如下:

NewConfigSourceEtcd作用是创建一个新的ConfigSourceEtcd结构体, 这个结构体里封装了etcdClient和两个传输信息的Channel: 1 serviceChannel 2 endpointsChannel.并启动一个goroutine config.Run()函数.源代码如下:

8.启动health监控

目前什么http的HandleFunc都没有,访问healthz_port会遇到404 not found error.

9.最后调用SyncLoop()

其源代码定义在pkg/proxy/proxier.go中,如下:

总结:

1 每个Kubernetes节点都运行一个kube-proxy这么一个服务代理,它 watch kubernetes 集群里 service 和 endpoints(label是某一特定条件的pods)这两个对象的增加或删除, 并且维护一个service 到 endpoints的映射. 他使用iptables REDIRECT target将对服务的请求流量转接到本地的一个port上, 然后再将流量发到后端, 这样的转发还支持一些策略, 如round robin等,所以我们可以把他看成是一个具有高级功能的反向代理。

2 下面为Kube-proxy内部goroutine及channel 使用图,以service信息传递为例

serviceChannel

译者介绍:杜军,浙江大学SEL实验室硕士研究生,目前在云平台团队从事科研和开发工作。浙大团队对PaaS,Docker,大数据和主流开源云计算技术有深入的研究和二次开发经验,团队现将部分技术文章贡献出来,希望能对读者有所帮助。

浙江大学SEL实验室是本网站上所有页面设计、页面内容的著作权人,对该网站所载的作品,包括但不限于网站所载的文字、数据、图形、照片、有声文件、动画文件、音视频资料等拥有完整的版权,受著作权法保护。严禁任何媒体、网站、个人或组织以任何形式或出于任何目的在未经本实验室书面授权的情況下抄袭、转载、摘编、修改本网站內容,或链接、转帖或以其他方式复制用于商业目的或发行,或稍作修改后在其它网站上使用,前述行为均将构成对本网站版权之侵犯,本网站將依法追究其法律责任。
本网站与他人另有协议授权下载的或法律另有规定的,在下载使用时必须注明“稿件来源:浙江大学SEL实验室”。

One thought on “Kubernetes代码走读之Minion Node 组件 kube-proxy”

Leave a Reply

Your email address will not be published. Required fields are marked *