V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
AlaudaCloud
V2EX  ›  推广

Kubernetes 的网络接口 CNI 及灵雀云的实践

  •  
  •   AlaudaCloud · 2017-12-05 14:47:58 +08:00 · 3248 次点击
    这是一个创建于 2576 天前的主题,其中的信息可能已经有所发展或是发生改变。

    K8S 的网络模型

    我们从底层网络来看,分为三个层面。首先是 Pod 之间的多个容器的网络互通。我们知道,K8S 的 Pod 可以由多个容器组成,这个层面网络互通是比较简单的,因为所有的容器都是共享一个网卡,可以直接通信。

    第二个,一台虚拟机上多个容器之间的网络是如何通信的。这块儿其实也比较好解决,例如 Docker 会搭一个网桥,让上面所有的东西、网卡接到网桥上,他们之间的网络就可以互通。Docker( http://www.alauda.cn)默认服务会创建一个 Docker0 的网桥,其它主流的像 Calico、Flannel 的模式也是类似的。这种方式的实现也是现在的主流。

    第三种是比较难的,Docker( http://www.alauda.cn)一开始就没有做好,是跨主机的 Pod 之间的网络通信。对于 K8S 来说,这个网卡如何分配其实 K8S 是没有定义的。不同 Pod 跨主机网络之间如何通信、如何打通网络,K8S 不管,是交给第三方实现的。在这块,是有很多工作可以做的。

    容器网络设计给运维人员带来的困惑

    现在我们再想一些问题,容器网络设计会给传统运维工作带来怎样的困惑?

    传统运维工作强调对 IP 要有很强的管控。容器时代,Pod 需不需要有固定的 IP ? Pod 重启之后,IP 是否不变?其实 K8S 没有规定,而且从大部分主流的实现来看,容器 IP 是可以变的。既然容器的 IP 是会变的,就会带来一个很直接的问题,我想访问这个服务怎么访问?之前两位同事讲过很多,我不做很细的介绍。

    这个大致的原理是,尽管底层 Pod IP 不断变化,但是我会给你提供固定的域名或者 DNS 的方式,或者固定的 Cluster IP 的方式等等。不管 Pod 方式如何变,总会给你固定的方式,通过固定的方式可以访问到一直变化的 IP,这些方式都是通过 Kube-proxy、iptables、Kube-dns 实现。

    我们实际测的时候发现,K8S 的服务发现功能比较少,随着访问量越来越大,会发现它的性能有很多问题,还有稳定性也有很严重的问题,而且它的稳定性缺陷是它从设计当初就很难避免。我们正在改进这些设计缺陷问题。

    为此很多人就会问,说你的容器 IP 是一直变的,怎么管?但容器就是这样的,容器生命周期很短,不断创建、不断消失,IP 肯定不断变。容器的好处是,你可以在这台机器上挂,在另外一台机器也可以。但运维人员首先觉得 IP 这样飘不好。对于运维来说,网络方面是很重要的资源,要对 IP 进行强管控,服务来回飘,会让他的安全感下降很多。

    运维服务有很多基于 IP 的东西,有流量和突发的监控,如果你服务的 IP 一直变化,通过这个 IP 它很难用到这个服务,相当于 IP 的监控就没有意义,因为根本不知道 IP 流量上去了是哪个服务的,很难对应到这个事。

    还有是对于 IP 安全策略没有办法做。如果以前 IP 是固定的,iptables 或者网络底层防火墙,这样的东西都是很好做的,可以很好的进行服务之间的安全访问限制。如果 IP 都是变的,这个事情就变得没有办法做了。

    第四是定位分析和数据处理问题。我们见到,有的客户把网络正常在跑的所有流量镜像一份,每天对复制的流量进行分析,看你有没有违规操作或者有什么恶意攻击,或者哪些行为不正常,这样他们通过 IP 再定位服务。如果 IP 是变的,相当于把他们很多能做的事情去掉了。

    还有一些特殊的软件,他们的 license 是根据网卡来的,就是说他的 license 是根据网卡的 IP 加上 Mac 计算出来的。这种情况下相当于 IP 变了,license 失效,软件跑不起来。

    还有你的 IP 不断变化,实际中也有问题,有很多服务和软件就是基于 IP 部署的,基于 IP 相互发现的,最典型的是 Etcd 这样的。如果你是用 K8S 或者其他的容器,IP 是变的,这个东西怎么填都是很难的事情。

    K8S 有它的解决方案,Headless Server 和 Stateful Set 可以做这个事情。但是这个事情在我看来有两个问题,它引入两个不好理解的概念,如果大家对 K8S 比较关心,第一次看,我觉得你看好半天才能明白它干什么,它为什么这么设计。它的核心理想是你的 Pod IP 是变的,然后给 Pod 一个顺序,给每个 Pod 一个标志符,etcd1、2、3,给他们三个人每个人用 k8s Service+域名,你在里面不用填 IP,填对应的三个域名就可以。但域名有 cash 缓存的问题,底层 Pod IP 失效,域名的缓存没有失效就会造成研判时间的不一致,我觉得靠 Service 做这件事情也是有问题的,并不能完美的解决这个问题。

    为什么 Docker、K8S 都没有做固定 IP 的事情?

    刚才说了 IP 不固定的影响,做容器之前大家可能没有想这个事,可能从一开始就认为 IP 是不固定的,我们想一下如果 IP 是固定的有什么影响,其实好像还可以用,并且可能变得更简单。比如 Cluster IP 或者 DNS 它们的映射由于 IP 是固定的可能变得更简单了。如果容器的 IP 固定,运维传统的基于 IP 的监控也可以做了,因为对他们来说没有影响,和原来是一样的,对他们的接受程度也会更高一点。

    我们很多服务发现的方式,可能之前通过 K8S 的 Sevrice 来进行服务发现,既然 IP 固定,我们可以跳过,直接用 IP 服务发现就可以,没有必要让你的运维理解很复杂的概念。大家会问什么是 Cluster IP,什么是 Pod,这些还是有理解成本,如果提供原生的方式也是不错的。

    可能想到一个问题,既然固定 IP 还是有好处的,为什么 Docker( http://www.alauda.cn)还是 K8S 都没有做这个事情呢?我觉得一开始 Docker 没有想好这个事情怎么做。最开始 Docker 就是一个单机的工具,没有想做得很复杂,最早 Docker 的网络和存储都是很弱的,它根本没有往这方面想。

    还有一点,在我理解就是理念之争。到底服务应该做成有状态的还是无状态的?有 Docker( http://www.alauda.cn)之后大家认为容器应该是跑无状态的东西,而不是有状态的东西,但是网络这块是有状态的。我们之前说有状态想到的是存储,认为存储才是有状态的服务,其实网络也是状态的一部分。大家可以想一下,如果 Pod 里面的服务访问外面,给外部留下的信息是某个 IP 访问我,所以对外的状态对你来说,IP 其实也是你的一个状态,所以说网络其实也是状态的一部分,如果要做有状态这个事情就变得很难。

    但是能不能做呢?可以做,我们做了一些尝试,做的方式是我们自定义 CNI 插件。

    如何自定义 CNI 插件?

    CNI,这个概念大家平时听的少一点。它的全称是 Container Network Interface,它注重给你的容器提供一套标准的网络的接口。现在包括 K8S 等都是通过 CNI 这种方式实现的。CNI 是 CNCF 的一个项目,如果你对比其它项目,CNI 应该是里面最简单的。如果你对这个项目有兴趣,从这里开始是比较好的,可能几百行或者上千行的代码就到头了。一个是 CNI 的标准,包括 CNI 接口和库,怎么快速实现 CNI,还有 CNI 提供的网络插件。

    介绍一下 Kubernetes( http://www.alauda.cn)和 CNI 之间是如何交互的,给大家讲完之后,大家可以比较容易上手,自己实现自己的网络。

    这是 Kubernetes 的基本参数,大家可以看到。红色的标起来的是两个选项。CNI 在我看来是有点奇怪的实现。一般的时候一个程序和另一个程序交互,大概是 HTTP REST 或者是 RPC 或者 TCP 的通信,但是 CNI 和 Kubernetes 的交互方式是通过二进制文件的方式,它会调用二进制文件传一个参数来做这样的事情。第二张是 CNI 实现的组件,他们都是以二进制的形式放在目录下面。左下角是 CNI 的配置,是最简单的,参数很少,还有一些其他比较复杂的。其实比较重要的是 type 和 IPAM。

    大概讲一下它交互的流程,是通过创建 Pod 和销毁 Pod 两个事件来触发的。创建 Pod 的时候,它会去调 CNI,它会根据这个文件会先调用 macvlan,创建一个网卡,然后再分配 IP,都是调里面的 DHCP 和 macvlan 二进制文件。创建网卡,构建 IP,把这个网卡放到 Pod network namespace,相当于完成 Pod 网卡的创建。还有,Pod 删除的时候,可能调用二进制文件释放掉,然后删除网卡,完成网卡的销毁。大概的交互比较简单,就这两个,实现的时候也是实现这两个方法就可以。

    最下面是 main 函数,其实有一个库,可以方便实现,照着这个写就可以,需要实现的是下面的函数就可以。函数其它的都不太用着重关注,都是做错误处理。最主要关注上面的,它是在 NetNSpath 里面创建的一块 link,删除的时候再把这块网卡删除,这是最简单的设备创建。如果大家对网络比较熟,或者对自己的网络有特殊的要求或者定制化的需求,其实在里面实现自己的逻辑就可以,整体来说它的逻辑还是比较简单的。而且还有一个功能是多个命令是可以串联的,比如我刚才举的例子,macvlan 和 DHCP 是串联的,macvlan 先创建网卡,然后再调 DHCP,分配 IP,他们之间有一个 result 的返回,通过 result 进行数据结构的传输。我们看一下 result 就可以知道网络还可以做什么别的事情。

    这是官方定义的 API 标准,其实我们也是可以在里面扩充的。上面是 interfaces,Kubernetes( http://www.alauda.cn)这个网络做得实在太简单,包括容器中的很多东西都是太简单了,可能一个 pod 里面有一个网卡设一个 IP 就完了。其实一个 pod 里面有多个网卡的需求,一个数据流,一个控制流,这种是可以做的。在它的 interfaces 里面可以看到有内置的网卡和 mac 地址,因为我们碰到有些客户,你的 mac 是可信域才可以做安全策略,这需要你要自定义的 mac。再往下,网卡是不是有多个 IP,而不是常用的网络,一个容器的一个 IP。再往下可以定义这个容器的路由表,包括路由的顺序等等都是可以定义的。再往下是 DNS server,容器里面的 DNS 都可以自定义,容器通信网络这块有很多可以做的事情,如果大家想做这方面,我觉得机会还是很大的。现在的网络说实在的,尽管实现很多,但是都还是蛮弱的。

    容器网络方面已经有一些第三方实现,比较知名的有 flannel、calico、weave ……。下面两项其实是它官方已经实现的,看起来分两大类:一是网卡生成的方式,比如 bridge、ipvlan、macvlan、loopback、ptp、vlan。第二大块是 IPAM。我不知道大家是否理解 IPAM,IPAM 的意思是 IP 该怎么分配。官方实现的有两种,一种是 DHCP,一种是 host-local。

    灵雀云( http://www.alauda.cn)如何通过 IPAM 做到精确管控 IP ?

    回到之前说的我们要做固定 IP,大家可以很容易联想 IPAM 这块做一些东西,因为这个 IPAM 是针对 IP 的,如果要做的话,先看一下现有的两种有没有问题,或者现有的两种怎么实现。DHCP 大家比较熟悉,就是自动分配 IP,它的问题是没有办法精细管控 IP。因为当需要通过主机发再发 DHCP 的广播再获得 IP,相当于容器 IP 和宿主机的 IP 混在一起,很难精确分配到某个 Pod 使某个 IP,因为你不知道这些信息。这在实际生产环境用得很少。因为 IP 管控要求很严格,不可能自己计算 IP,我们家里的 wifi 或者办公才会用到,企业级业务碰到的机会都很少。

    host-local,每台机器上都有这个文件,这个文件上规定每台机器能生成一些 IP。可以看一下配置文件的格式。其实这两个参数比较重要,一个是 rangeStart 和 rangeEnd,就是每台机器分配的范围是多少。这种分配方式有一个很严重的问题,Calico 和 Flannel 都有,就是每台机器都是固定的,这给每台机器划分了一个网关,相当于你 Pod 是在机器上的,因为 IP 是很不灵活,很难实现 IP 的漂移。而且分配 IP 比较困难,新增机器要重新定义,这台机器用多少 IP,要考虑 IP 段的释放,很不灵活,显然没有办法实现固定 IP 的事情。

    为什么这个事情感觉这么难?我觉得也是 K8S( http://www.alauda.cn)设计理念的问题,我们可以看到 K8S 管了很多东西,管了 Pod、configmap、Service 等,你会发现它没有管你的 IP。理论上来说,IP 对你的服务或者对你的集群来说是很重要的资源,但是 K8S 里面 IP 不是它的一等应用,甚至你根本没有一个跟容器相关的资源在开发里面找到,所以使得你的 IPAM 变得很难。你要么通过 K8S 完全无关的东西来实现,要么通过锁死的方式实现,这其实都是因为你没有把 IP 资源看得特别重,你只是当做可以随机分配的,所以它设置的理念就是你的 IP 我不关心,该是什么就是什么,所以导致这样的结果。

    最后讲一下我们做的,我们把 IP 当做很重要的资源,进行单独的管理。我们会专门实现 IPAM 的组件,实现网段的添加、删除,可以在 K8S 管理网段,给某个业务或者某几个用户分配一个网关,然后对 IP 进行网关设置,路由设置,和 DNS 设置。有了网段之后,有 IP 的添加删除,会有很重要的资源,该用哪些 IP,哪些 IP 是可以用的,都是可以用管理员指定的,而不是像 K8S 随机划一个网段,还有权限、配额,之后就可以顺利创建服务了。

    现在创建 K8S 的 Deploy 的时候需要申明用哪几个 IP。如果 IP 信息传输到 IPAM 的组件,这个 IPAM 就是一个数据库,这个数据库就会显示哪些 IP 可以使用,哪些应该被哪个服务使用,把这个记录下来。在 K8S( http://www.alauda.cn)启动 Pod,CNI 里面再实现一套逻辑,把 IPAM 的逻辑实现了,那套做的东西会进入 IPAM 的数据库里面找,如果用就占用这个,别人不能再用,就可以实现每个 POD 有自己的 IP,这个 IP 是你之前想要的。如果在删除的时候,CNI 会把 IP 释放掉,说明这个 IP 不会再被这个服务使用,别的服务可以使用这个 IP。我们如果想创建 etcd 的服务,就不用再写一个东西,你只要做你想要的 IP 就可以,很大的简化你的工作量,而且实现我之前说的传统的运维,IP 的监控、管理的东西。

    2 条回复    2017-12-05 15:26:51 +08:00
    Livid
        1
    Livid  
    MOD
       2017-12-05 15:03:21 +08:00
    移动到 /go/promotions

    请注意,软文请发布到 /go/promotions 而不是任何其他节点。
    AlaudaCloud
        2
    AlaudaCloud  
    OP
       2017-12-05 15:26:51 +08:00
    好的 谢谢
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1664 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 16:41 · PVG 00:41 · LAX 08:41 · JFK 11:41
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.