一、引言
随着携程国际化战略的实施和业务海外的部署,混合云已经达到了一定的规模。为了支持容器,我们在 AWS 上部署了 kubernetes。主要是弹性发布的考量。
应用交付效率上,初始化一台虚拟机在分钟级别,涉及操作庞杂,除了启动虚拟机,还需要进行应用环境的安装,而拉起一台容器的时间可以控制在秒级。
运维管理效率上,虚拟机的维护比容器要复杂得多,特别是在目前有着多个公有云合作厂商的情况下,工作量和复杂程度爆增。以镜像为例,自定义的镜像无法在不同公有云厂商之间进行统一,比如 AWS 的 C5 等系列要求我们安装 ENA(Elastic Network Adapter)增强联网的驱动。目前我们在几家公有云上的自定义镜像是也是保持着异构的状态。
所以,引入了混合云的容器发布。借助着业务在 AWS 的应用部署,有了 Kubernetes@AWS 的主题。
二、为什么在 AWS 上自己搭建 kubernetes
首先是网络上的重要考量,目前适配携程的技术体系需要满足两个网络适配的要求。
IP 直连:存在应用互访是通过 IP 进行访问的情况,而且携程的应用并非全是跑在 kubernetes 集群中,所以为了保证全局应用的可用性,就需要容器的 IP 是 kubernetes 集群外可见的 IP。
IP 固定:假设应用在重新发布时 IP 发生变化,那必须有良好的机制通知各个相关的系统。但是目前存在有运维等系统的架构是基于 IP 固定这一前提条件。要完善这种架构不是一时之力,所以我们在这期项目的折中方案是适应当前的技术架构,保证容器应用在发布新版本、宿主机故障重新拉起容器时,IP 保持不变。
这两个需求都不是 kubernetes 可以原生支持的,kubernetes 提供了 Service、Ingress 等服务暴露的方法,但是不能完全满足我们上述的网络需求,社区现成的 flannel、calico 等容器网络方案不能采用,需要进行定制化。
同时,我们也针对 kubernetes 做了调度等层面的自定义开发。虽然 AWS 也发布了 kubernetes 产品——EKS,是一套打通公有云的方案,但也意味着失去了自定义开发及控制平面运维管控的能力。所以,我们也没有采用公有云的原生 kubernetes 产品。
出于对定制化能力和控制平面管控的需求,也为了与私有云 IDC 的技术栈保持一致,尽量减少运维成本,进行过一系列的调研之后,我们最终选择自己在云上搭建 kubernetes 集群。
三、如何在 AWS 上自己搭建 kubernetes
主要从网络、镜像、日志监控这几个组件讲讲我们的方案选型及设计。
3.1 网络
容器网络的方案我们是基于 VPC(Virtual Private Cloud)和弹性网络接口(Elastic Network Interface,ENI)进行实现的。
VPC 是一个用户在公有云上自建的隔离的虚拟网络,可以自定义地址空间段。我们以一个地域(Region)为例,一个 VPC 在其中一个可用区内的架构大致如下:
每个可用区内有一个公有子网和一个私有子网。
公有子网:想对 internet 直接暴露的服务一般部署在公有子网,给实例绑定一个弹性 IP(公有 IP),Internet 即可通过这个公网 IP 进行访问。公有子网里的缺省路由是 Internet 网关,由 Internet 网关实现 VPC 与 Internet 之间的通信。其中公有子网里有个 NAT 网关,有对应的私有 IP 和公有 IP。NAT 网关的作用是允许私有子网中的实例连接到 Internet 或其他 AWS 服务的能力,但能避免 Internet 直接连接这些实例。
私有子网:一般用于供内部调用的服务,如数据库、应用后端等。私有子网里的实例如果要访问 Internet 上的服务,与私有 IDC 里通过代理实现的方式不一样,会通过描述私有子网的缺省路由为公有子网里的 NAT 网关,实现与 Internet 的通信。
图上子网里实例的私有 IP 地址,其实是和弹性网络接口(Elastic Network Interface,ENI)相关的。它是 VPC 中的一个逻辑网络组件,特性如下:
1 个弹性网络接口具有:
1 个 Mac 地址
1 个主要私有 IP 地址
多个辅助私有 IP 地址
1 个或多个安全组,需要注意的是:ENI 上的 IP 地址的安全组是跟随着对应网卡的安全组而决定出入控制的。
其中每个私有 IP 地址都可以绑定一个弹性 IP。
一个实例可以有多个弹性网络接口:
1 个主要弹性网络接口,不可插拔,如 ifconfig 中 eth0
多个辅助网络接口,可动态插拔,如 ifconfig 中 eth1~n
实例可以容纳的最大弹性网络接口数和每个接口的最大 IP 地址个数是有限制的,一般来说,与实例类型相关,总体来说与配置成正比。
利用弹性网络接口,我们衍生出了两种容器网络的子方案:
单网卡多 IP
单网卡单 IP
单网卡多 IP 容器网络方案
该方案的网络要点在于:一个弹性网络接口绑定多个 IP,其中第一个 IP(主要私有 IP)作为宿主机对外通信的 IP,后面的 IP(辅助私有 IP)是容器的 IP。容器和宿主机之间通过 veth pair 进行连接,同时通过路由表进行容器出入流量的控制。
单网卡单 IP 容器网络方案
虽然一个弹性网络接口可以绑定多个 IP,但是也提高了复杂性。该单网卡单 IP 子方案中,把一个弹性网络接口作为一个调度的单元,每个弹性网络接口只使用一个 IP,其中第一个弹性网络接口(eth0)留给宿主机继续使用,在实例上添加新的辅助弹性网络接口(eth1-N),并把弹性网络接口移入容器的网络命名空间,直接给容器使用。对比如下:
我们最终选择了单网卡单 IP 的子方案,原因在于,比较简单,同时,承载的主要是 Java 应用,对应的配置一般不会太低,不会有很明显的资源浪费。
可以看到,如上的网络方案,由于容器的 IP 其实是 VPC 范畴地址空间段的 IP 地址,所以可以达到 kubernetes 集群外 IP 可见的需求,还有 IP 固定的需求,是这么实现的。
有一个全局的 IP 地址管理模块,简称 GIPAM,它存储了 pod name(我们使用的是 Statefulset)与 IP 的一对一关系,并负责与公有云进行 API 的交互。Node 上的 cni 插件,在设置网络前,先去 GIPAM 上查询一下 IP 的信息。应用重新发布,故障迁移,重新拉起一个容器时,不管是不是在同个 node,由于 cni 插件会以 GIPAM 作为 IP 的数据源,这样就保证了 IP 的固定和漂移。
举个创建容器网络的图例:
3.2 镜像
我们在私有云的每个 IDC 都部署了一套 Harbor,组成了 Harbor 集群联邦,并且跨越测试环境和生产环境。并使用了 DNS 劫持的技术,推送 docker 镜像时,是往各自 IDC 的 Harbor 进行推送,后台自动异步地同步到其他 IDC。保证应用在部署时优先从同 IDC 的 Harbor 进行镜像的拉取,如果失败,再跨 IDC 拉取镜像,减少应用拉起的时间。
在 AWS 上我们选择了自己搭建 Harbor,后端的存储采用 S3。由于目前我们在公有云 IDC 上的应用相对较少,暂时未加入联邦,未开启自动同步,在发布流程上保证在应用在部署时预先推送镜像到 AWS IDC 的 Harbor。同样也使用了 DNS 劫持技术,保证使用同样的镜像域名。
3.3 日志监控
网络解决了,镜像可以拉取了,可以正式拉起一台容器了。那监控和日志是如何解决的呢?
在公有云我们部署了一套与私有云一致的日志监控系统,hickwall、grafana 等,采用了 Prometheus+Telegraf+InfluxDB+Grafana 等与私有云一致的技术栈。在云上采集,云上处理,只送回必要的告警数据,并对接了 NOC 体系,进行 7*24 的监控。
四、同步私有云数据到公有云及疑难杂症
4.1 同步私有云数据到公有云
涉及到 IDC 之间大量数据的同步,我们是采用公网传输的方式,毕竟专线带宽有限而且昂贵。有关 AWS 数据的流量费用,可以总结为:
如果服务部署在私有子网,与 Internet 的通信会经过 NAT 网关。经过 NAT 网关的流量不管出入,一律收取数据处理费用。
如果服务部署在公有子网,通过公网 IP 与 Internet 通信。而公网 IP 的收费模式是只收出流量,进流量不收费。
所以我们通用的把数据从私有 IDC 同步到公有云的部署架构是:把数据收集服务部署在公有子网,实例带有公有 IP,私有云 IDC 的推送服务通过 Internet 进行数据的推送。
同样,如果公有云上的服务需要从外部供应商拉取的数据量比较大,我们也是采用服务部署在公有子网的模式。如此节省了经过 NAT 网关的数据处理费用。
4.2 AWS ELB 负载均衡不支持回环路由
我们通过 AWS ELB(Elastic Load Balancing)把 kubernetes api server 组成一个高可用的集群,但是上线后,发现在 master 节点上使用 kubectl 命令经常会超时,集群中也有比较多的访问 api-server 的 i/o timeout 日志。
这个问题在于通过实例 id 进行注册的 ELB 不支持回环路由。即通过实例 id 注册到 ELB 的目标时,客户端的源 IP 地址会保留,ELB 在路由后发现源地址和目的地址相同,会丢弃这些包,连接仅在请求路由到不同的实例时才会成功。Kubernetes 在部署架构中,通常采用混合部署的模式,在同一台主机上同时启用 kube-scheduler、kube-api 等组件。
该问题可以通过改用通过 IP 地址注册目标到 ELB 负载均衡进行解决。为了支持这种应用混布场景,AWS 推出了通过 IP 地址进行注册的 ELB 负载均衡,即可避免不支持回环路由而导致连接超时的问题。
4.3 kubelet 进程 max-pods 设置
由于我们的网络方案受限于实例上弹性网络接口的最大个数,要注意在 kubernetes node 上对 kubelet 进程的 max-pods 参数进行设置以限制 Pod 的最大数量。
五、总结
本文首先介绍了在携程混合云平台上支持容器发布的背景,并引出了自搭 kubernetes 的初衷,要适配的网络需求,再详细从网络、镜像、日志监控等组件介绍了携程在 AWS 自己搭建 kubernetes 的方案选型及设计,接着给出了从私有云 IDC 同步数据到公有云的通用部署架构,并从几个示例介绍了探索过程中碰到过的注意事项。
我们在混合云上的广大探索还在持续中
作者介绍:
陈丹双,携程高级工程师,毕业于复旦大学,积累多家公有云对接经验,持续探索混合云,关注 kubernetes、docker 等容器技术。
本文转载自公众号携程技术(ID:ctriptech)。
原文链接:
https://mp.weixin.qq.com/s/r7HeP6FCr6ZbHIRsOt0kcg
评论