基于Kubernetes(k8s)网络方案演进

VIP PaaS在接近两年时间里,基于kubernetes主要经历四次网络方案的变迁:node

1. kubernetes + flannelgit

2. 基于Docker libnetwork的网络定制github

3. kubernetes + contiv + kube-haproxydocker

4. 应用容器IP固定api

先简单说一下背景,PaaS平台的应用管理包括应用配置管理,应用的运行态管理。一个应用的运行态对应kubernetes的一个Replication Controller(后面使用RC简称)和一个Service,应用实例对应kubernetes中的Pod, 咱们基于这样的管理方式,须要提供应用之间的相互调用,同时对部分应用要提供基于http/tcp的直接访问。网络

首先说一下kubernetes + flannel。架构

flannel主要提供了跨主机间的容器通讯;dom

在kubernetes的Pod、Service模型里,kube-proxy又借助iptables实现了Pod和Service间通讯。tcp

基于这种网络访问功能,咱们平台提供了如下功能:测试

基于gorouter提供的平台域名的访问 – watch k8s endpoints event管理router信息;

基于skydns并定制化kube2sky组件和kubelet,提供同一命名空间下应用(Pod)之间基于业务域名的访问 – kube2sky基于k8s Service annotation解析并注册域名信息、kubelet设置容器启动时的domain search及外部dns;

实现容器tty访问控制台 – 每台k8s node部署平台组件 tty agent(根据Pod所属node信息, 创建对应k8s结点的tty链接);

网络访问关系图以下:

 

tu1
在k8s + flannel的模型下,容器网络是封闭子网,能够提供平台内部应用之间基于4层和7层的调用,同时对外部提供应用基于域名(工做在七层)的直接访问,但没法知足用户在平台外部须要直接使用IP访问的需求。

在flannel网络稳定使用后,开始研究network plugin以使应用服务实例以public IP 方式供用户直接使用。

当时docker的版本为1.8, 自己还不支持网络插件.同时 kubernetes自己提供一套基于CNI的网络插件, 但自己有bug[CNI delete invoked twice with non-infra container id #20379]。

因而咱们尝试从docker network plugin的角度入手,结合libnetwork从docker源码的角度进行定制。

整个架构分为三层:

  1. Client Layer – Docker CLI和kubernetes(Docker client);
  2. Docker Layer – Docker daemon 并在代码层面集成libnetwork(内置OVS driver);
  3. Controller Layer – ovsdb-server及network controller(自开发IPAM);

总体访问结构图:

 

tu2

 

整个方案包括如下三个流程:

1. 启动Docker Daemon:

初始化network controller -> 加载OVS Driver -> OVS Driver调用libovsdb建立docker0-ovs Bridge -> OVS Driver将主机上的一物理网卡attach到docker0-ovs上;

2. 启动容器:

OVS Driver 建立veth pair 用于链接network namespaces -> OVS Driver调用network controller获取容器IP和VLAN Tag -> OVS Driver将veth pair的一端添加到docker0-ovs上,并设置VLAN Tag -> OVS Driver设置容器内interface的IP,Mac Address以及路由 -> 设置各network interface为up;

3. 中止容器:

OVS Driver调用network controller释放容器IP -> 删除network link -> OVS Driver调用libovsdb删除port;

libnetwork工做完成了测试阶段但没有经历上线,随着Docker版本的推动,Docker1.9开始支持 contiv netplugin,咱们开始研究contiv应用,在期间咱们也完成了使用haproxy替换kube-proxy的开发[https://github.com/AdoHe/kube2haproxy],并最后采用docker1.10+contiv上线。

这里根据咱们实际网络访问关系再描述下PaaS在contiv总体部署结构:

 

tu3

 

Kube-haproxy替代了kube-proxy,主要是提供服务ip的公共调用,同时避免了容器数量增长后带来的iptables规则的大量增加,方便调试。

contiv带来的方即是用户能够根据实例IP直接进行访问;咱们在使用过程当中总体比较稳定,中间出现过一次问题: 机房停电致使了部分IP的分配状态不正确,并且contiv当时尚未提供查看已分配IP的接口。

Docker 1.10版本支持指定IP启动容器,而且因为部分应用对实例IP固定有需求,咱们开始着手容器IP固定方案的设计与开发。

前面提到应用运行时,对应k8s内一个ReplicationController以及一个Service。 应用的从新部署目前采用的策略主要是重建策略。 重建的流程包括删除RC及RC下全部Pod,更新并建立新的RC(kubernetes会根据RC配置产生新的POD)。

在默认的k8s+contiv的网络环境下,容器(Pod)的IP网络链接是由contiv network plugin来完成的, contiv master只实现了简单的IP地址分配和回收,每次部署应用时,并不能保证Pod IP不变。因此咱们引入了新的Pod层面的IPAM,以保证同一个应用屡次发生部署时,Pod IP始终是不变的。

做为Pod层面的IPAM,咱们把这一功能直接集成在了kubernetes。Pod做为k8s的最小调度单元,原有的k8s Pod Registry(主要负责处理全部与Pod以及Pod subresource相关的请求:Pod的增删改查,Pod的绑定及状态更新,exec/attach/log等操做) 并不支持在建立Pod时为Pod分配IP,Pod IP是经过获取Pod Infra Container的IP来获取的,而Pod Infra Container的IP即为contiv动态分配得来的。

Pod Registry 访问设计图:

 

tu4

 

在原有kubernetes代码基础上,咱们修改了Pod结构(在PodSpec中加入PodIP)并重写了Pod Registry 同时引入了两个新的资源对象:

1. Pod IP Allocator: Pod IP Allocator是一个基于etcd的IP地址分配器,主要实现Pod IP的分配与回收。
Pod IP Allocator经过位图记录IP地址的分配状况,而且将该位图持久化到Etcd;

2. Pod IP Recycler: Pod IP Recycler是一个基于etcd的IP地址回收站,也是实现PodConsistent IP的核心。Pod IP Recycler基于RC全名(namespace + RC name)记录每个应用曾经使用过的IP地址,而且在下一次部署的时候预先使用处于回收状态的IP。

Pod IP Recycler只会回收经过RC建立的Pod的IP,经过其余controller或者直接建立的Pod的IP并不会记录,因此经过这种方式建立的Pod的IP并不会保持不变; 同时Pod IP Recycle检测每一个已回收IP对象的TTL,目前设置的保留时间为一天。

这里对kubelet也进行了改造,主要包括根据Pod Spec中指定IP进行相关的容器建立(docker run加入IP指定)以及Pod删除时释放IP操做。

建立和删除Pod的UML时序图以下:

 

tu5

 

Pod的建立在PaaS里主要有两种情形:

  1. 应用的第一次部署及扩容,这种状况主要是从IP pool中随机分配;
  2. 应用的从新部署:在从新部署时,已经释放的IP已根据RC全名存放于IP Recycle列表中,这里优先从回收列表中获取IP,从而达到IP固定的效果。

 

tu6

 

总体删除过程为:由PaaSNg或kube-controller-manager调用apiserver Pod Delete并设置DeletionTimestamp, kubelet监听到删除时间并获取GracefulDeletiontime,删除应用容器, 通知apiserver释放IP(释放IP时获取Pod所属RC,根据是否有对应RC 名称决定是否存放在IP Recycle列表),删除Pause Pod,通知apiserver 删除Pod对象。

另外为了防止IP固定方案中可能出现的问题,咱们在kubernetes中加入了额外的REST api: 包括对已分配IP的查询,手动分配/释放IP..。

对目前方案的总结:

容器IP固定方案已上线,运行基本没问题,但稳定性有待提高。主要表现为偶然性不能在预期时间内中止旧Pod,从而没法释放IP形成没法复用(初步缘由是因为Docker偶尔的卡顿形成没法在规定时间内中止容器)。咱们短时间的work around是使用额外添加的REST apiss手动修复,后期IP固定方案会继续增强稳定性并根据需求进行优化。