今天我将带来一些关于滴滴弹性云的分享,内容是从物理机到 Kubernetes 的那些坑与心得。
先简单自我介绍下,我叫谭霖,之前在 Intel 从事 OpenStack 相关的工作,也花了很大精力在开源社区这方面。
后来我来到滴滴的弹性云团队,主要从事基于 Kubernetes 的基础平台建设,可以说是从一个做菜的“厨师”变成了一个“食客”。
今天的分享主要分为四个部分:
- 整体架构
- 产品功能
- 方案细节
- 心得展望
在我们准备做弹性云时,我们问了自己三个问题:
- 为什么要做私有云
- 为什么选容器
- 为什么选 Kubernetes
为什么要做私有云?因为滴滴的集群规模太大,有数万台物理机(这样的规模量要求我们做一个私有云平台),结果因为之前没有一个比较好的方案,造成了资源使用率特别低、资源浪费大的问题。
另一个主要原因是滴滴的发展太快,总有新的业务,隔一段时间就听说成立了一个新的部门,而且随着新需求的出现,更新迭代异常频繁,这些都对平台的稳定性提出了特别高的要求。
这个问题靠堆人是解决不了的,所以需要有一个好的平台和机制来解决它。
为什么选容器?KVM / Xen 这样的传统虚拟化已经出现多年了,很稳定,但大家也知道其弊端,就是损耗比较大。
另外,传统混部也就是滴滴现在在用的方案(即继续保持原有的传统混部),虽然能够部分解决资源使用率低的问题,但因为完全没有隔离,导致程序之间容易互相影响。
而容器是一个相对折中的方案,具有一定的隔离性、损耗更少,这是我们选择它的最主要原因。
同时,更重要的是容器的镜像所带来的新玩法,因为镜像的环境是一致的,对运维人员特别友好。
因此他们不用再关心那些纷繁复杂的配置文件,也不用频繁地走初始化流程,更不用每次上线更新都提心吊胆,担心会对新的服务造成影响。
为什么要选择 Kubernetes?大家都知道现在比较著名的容器平台有 Mesos、Kubernetes 和 Swarm。
一年前我们在 Mesos 和 Kubernetes 之间犹豫过很久,后来主要觉得我们要做的是一个基于容器的平台,相比较 Mesos,Kubernetes 更专注容器。
而 Swarm 当时还很年轻,很多功能不够完善,相较之下 Kubernetes 更成熟。当然更关键的原因是 Kubernetes 的社区更有活力,设计架构也更清晰,可拓展性也高。
现在回过头来看一年前的选择,非常庆幸我们做了正确的选择,因为之前相较于两者,Kubernetes 还只是旗鼓相当,而现在则有一统江山的趋势。
我们目前还是基于 Kubernetes 1.6 版本做的改造,接下来 1.8 版本 release 之后,我们会尽量跟上,保持和社区同步。
整体架构
项目介绍
滴滴弹性云的目标就是基于容器技术,为滴滴提供稳定高效、可伸缩的服务管理平台。
先简单看看弹性云的项目历程:
- 2016 年 7 月,弹性云项目正式启动,然后 10 月份出了***版。
- 2017 年 4 月,发布了第二版。在几个月的使用体验中,我们根据用户的反馈,不管是产品形式还是底层网络方案都经过大规模优化。
- 目前第三版也正在开发中,这一版会更侧重在用户交互和可视化上。
截止到目前为止,我们总共有 300 多台宿主机,2000+ 的容器实例,这不算很多,但目前有多条滴滴核心业务线跑在我们的平台上,预见 2018 年规模会有指数型的上升。
整体目标
为了解决先前提出的三个问题,我们的整体目标有四点:
- 实现资源按需分配,提高资源利用率
- 实现资源的弹性伸缩,可以快速响应负载变化
- 基础环境保持一致,实现服务的快速交付
- 保证服务的容错性,实现基础设施免运维
解决方案
针对不同的产品线和需求,我们具体落地的产品解决方案主要分为两大块:
- 基于容器构建的轻量级虚拟机,叫做静态容器组。
- 更纯粹的微服务类容器,我们称之为弹性伸缩组。同时我们还整合了滴滴现有的部署监控和日志收集系统。
如上图,是整体架构,中间***的一块就是我们的 K8S 集群,它主要分成控制节点和工作节点两部分。
上面一块是我们的控制台和管理员入口,左边是滴滴的一些现有的权限认证系统和服务树,右边是我们的网络方案。
所以按照流程我们主要做的操作是用户先通过权限系统认证后,创建服务(也就是 K8S 的实例),实现容器的扩容缩容和部署更新。
之后我们平台就会进行实时监控和日志收集,完成监控数据的上报和镜像拉取。
刚才说的主要是管理员相关的工作流程,现在看看用户流量。首先我们这边实现了每个容器通过 IPAM 组件,都能获取一个独立的 IP,这个 IP 可以和物理机双向互通,所以允许用户的流量可以直接通过容器 IP 访问实例。
同时我们也提供了一个 4 层的 LB,允许用户可以通过只访问一个 VIP,自动把流量打到后面的容器里。
产品功能
接下来详细讲讲滴滴弹性云具体的产品功能。
功能类型
正如之前所说,我们主要提供静态容器和动态伸缩两大产品,同时伸缩组方面又细分为有状态和无状态伸缩组。
其实在刚开始时,我们只想提供 K8S 支持得***的 Deployment,也就是无状态伸缩,这也是***容器使用习惯的产品形式。但在实际落地的过程中,我们发现完全不是那么一回事。
首先,不是所有程序都能比较轻易地改造成 Cloud-Native 的服务,所以针对这样的传统服务,我们还是需要提供一个比较贴近物理机的用户体验的产品,它的问题就是不能弹性伸缩,但好处是可以挂载本地存储,IP 是恒定的。
同时为了支持有状态的服务,我们也提高了有状态伸缩组。既支持弹性伸缩又支持挂载网络存储。
不过比较出人意料的是,用户选择有状态伸缩组的很大一部分原因并不是因为挂载 Ceph,而是因为有状态伸缩组的容器 IP/HostName 是不变的,这样不管是从监控和日志上,都是更友好的。
而无状态伸缩我们这个当初最想推动的服务,也就只有在一些对弹性伸缩速度有特别高的要求的场景下才会用到。
功能特性
具体到产品功能的特性上,我们主要有三大块:
- 复杂配置简单化
- 上线流程标准化
- 服务管理
复杂配置简单化
了解过 K8S 的朋友们都清楚,它上手很难,有特别多的配置,有时候反而给用户带来了困扰。
所以我们做了减法,简化了很多配置,只留给用户一些接口,避免不必要的困扰。
比如我们支持多种接入方式,既可以一键完成从 Git 代码仓库到上线镜像的转换过程,也支持直接通过自定义镜像。
同时我们也对镜像做了分层(基础镜像→环境镜像→服务镜像),实现 RD 和 SRE 每个角色负责不同的镜像制作,这样分工更合理、层次更清晰,做到比较好的权责分明、高效有序。
监控日志自动配置关联也是做了减法,希望用户较少关注这一点,从而得到更好的体验。
上线流程标准化
相对于减法来说,我们也做了很多的加法,因为我们是一个严肃的运维平台,之所以强调严肃,是因为只要有人参与的地方就容易犯错。
一方面我们要尽量减少人工参与的地方;另一方面又要尽量通过各种规范来减少人犯错的机会。
比如变更审批系统,每一次上线变更都需要开发 leader 的审核;同时也对它进行了改造,使其每次分组小流量灰度。
每次发布更新的过程中都有强制的观察时间,如果在观察过程中,比如说你发布了第二组后,发现你的更新过程中代码出了 Bug,我们还支持了一键回滚。
服务管理
在服务管理方面,我们实现了服务不可用自动重启、一键扩容和负载均衡等,最为重要的就是对用户强制异地多活,必须在我们的两个机房都有实例。
方案细节
现在介绍我们的方案细节,主要包括:
- 网络监控
- 日志
- 镜像市场
网络介绍
SDN
我们的网络方案主要是基于 Onos+OVS 的 SDN 网络方案,每个容器都有一个区别于机房实体机的 Overlay IP,容器与容器之间实现“大二层”互通,与机房网络之间通过交换机打通。
物理机可以直接通过容器 Overlay IP 访问容器,反之亦然。
IP
在 IP 上,静态容器和伸缩容器都可以进行 Hostname 绑定,通过容器不变的 Hostname 保证容器在漂移到其他宿主后 IP 依旧保持不变,当容器发布更新时,它永远保持稳定。
对于 IP 随机分配的情况,我们还提供了 IP 池,保证 IP 只会从 IP 池里出现。
负载均衡
我们使用弹性云独立的 4 层、7 层负载均衡器,实现动态的负载均衡及故障自动剔除。
网络打通
架构
当前弹性云容器网络大体上分为三种类型:同宿主间的容器通信、跨主机间的容器通信,以及容器与物理机间的通信。
每一个部署容器的宿主机,都会部署一套 Ovs 网络组建,其中关键的就是两个 Ovs Bridge,一个负责建立隧道与外界通信,叫 Ovs-tunnel 桥。
一个负责整合本机上的容器通信,叫 Ovs-int 桥。所有的 Ovs-tunnel 都会与 Onos 的 Controller 建立连接,负责查询流表并建立流表和其他宿主机间的隧道。
因此三种通信类型就可以解释为:
- 同宿主间的容器通信:直接通过 Ovs-int 桥通信,因此也不依赖 Ovs-tunnel 与外界的隧道。
- 跨主机间的容器通信:***次通信时,Ovs-tunnel 也会查询 Onos,Onos 返回目标容器的主机信息,Ovs-tunnel 通过主机间的隧道,将容器数据发送到目的容器的宿主机,目的容器宿主机上的 Ovs-tunnel 则会将数据继续向上一层传递,经 Ovs-int 传递给容器。
- 容器与物理机间的通信:容器发送物理机的数据包,会被 Ovs-tunnel 经隧道发送给 Overlay 网关,Overlay 网关进行 Vxlan 解包的操作,将数据传递到物理网络。
相反地,物理机发送给容器的数据包,经 Overlay 网关把数据封包成 Vxlan 数据包,再通过隧道发往容器宿主,再由宿主传递给上一层的容器。
网络 IP 分配
具体到产品上,每个 IP 分配方案如下:
- 静态容器组:IP 和 Hostname 恒定。
- 有状态伸缩组:IP 和 Hostname 恒定,支持 IP 池。
- 无状态伸缩组:支持 IP 池,IP 随机分配,灵活度更高。
创建过程
这个是容器网络创建的过程。一旦用户有创建容器需求时,控制节点会对整个集群做一个调度,选择一个合适的工作节点,而作为节点最重要核心的 Kubelet 就负责创建容器,控制节点会把需求发给它。
具体创建步骤如下:
- 当 Kubelet 收到调度到本 Node 的 Pod 信息后,会先创建一个无网络配置的 Infrastructure 的基础容器,此容器用于承载 Pod 中所有容器的网络 Namespace。
- Kubelet Exec 启动 CNIPlugin,并将***步中创建的网络 Namespace 及 Pod 名称、容器 ID 等作为标准输入,传给新启动的 Plugin 进程。
- CNIPlugin 根据收到的参数,去 IP Controller 中申请容器的网络 IP。
- IP Controller 会判断该容器属于哪一个子网,子网中是否有可用的 IP,及是否有绑定的 IP 地址等,在根据计算出的 IP 地址及子网信息等,去 SDN IPAM 端申请虚拟 Port(Overlay IP)。
- IPAM 会将创建好的虚拟 Port 返回 API 调用者 IP Controller,并同时将信息同步给 SDN Onos。
- IP Controller 会将返回的 Port 保存在自己的存储库中。如果是绑定 IP 类型的 Pod,它还会将该 Pod 与该虚拟 Port(Overlay IP)进行绑定,则下一次该 Pod 再一次进行申请时,仍会申请到该 Port(Overlay IP)。数据保存后,它会把 Port 信息再返回给 CNIPlugin。
- CNIPlugin 收到 Port 信息后,会根据其中的 Overlay IP、Gateway、Vlan Tag 等信息创建 Veth Pair、配置 Veth 信息、配置路由、添加 Ovs-brdge 等操作。
- 至此,本来没有任何网络的基础容器,就有了网络配置(除了与外部通信的 Ovs 网络外,同时也会添加 Lo 网络)。
- CNIPlugin 在成功配置完网络后,会将结果返回给 Kubelet。
- Kubelet 会使用配置成功的基础容器的网络 Namespace 继续去创建其他的业务容器,如果配置失败了,会继续从***步开始,继续创建基础容器,直到网络配置成功。
监控需求
监控主要有两方面需求:
- 基础监控:基础监控使用了 Google 开源容器监控软件 Cadvisor/Cgroup 作为监控数据来源,数据上报至 Odin,同时可在 Odin 和弹性云页面上展开监控数据。
物理机监控 Agent 无法采集到容器有效监控数据,Cadvisor、Proc、Cgroup 容器基础监控项同物理机有明显差异,但用户的需求没变。
- 业务监控:业务监控同物理机需求完全一致,复用物理机监控方式。
这张图就是弹性云配置监控和查看监控的示意图,我们在每个物理机上有获取容器的基础监控指标,容器里面也会有获取业务指标。
日志需求
日志主要有三个方面考虑:
- 日志时效性:在线日志采集延迟 2min,满足大多数场景,紧急情况下可以登录到容器内查看日志。
- 日志持久性:生成的容器直接拉到远端存储一份,选择分布式存储日志在 Ceph 存储一份。
- 日志展示:事后追溯有多种丰富的日志展示、分析系统。
这是我们在弹性云平台上创建容器的一个操作日志,也是一个简单的示意图。
通常用户都打在容器里,而容器一旦发布更新,漂移之后数据都丢了,所以我们直接把日志打到 Ceph 上,用户不喜欢用这个的话可以直接打到远端,远端采集系统可以使用不同的系统。
比如说 ES 和 HDFS,这样就提供了更加丰富的日志展示和分析系统。
持久化存储
存储主要提供了本地卷和网络卷:
本地卷 Host Path 提供给静态容器组,我们把宿主机上的目录挂载到容器里作为数据存储卷,优势是稳定和读写性高。
网络卷 Ceph 的优势是多备份更可靠,支持容器迁移,缺点是网络抖动不可避免,所以得用 Ceph 的服务做改造,避免网络抖动影响整个服务的进程。
Overlay FS
我们选择 Overlay FS 作为存储,一方面是因为它的性能/稳定性更好,当然它也有不足。
比如 DeviceMapper 可以通过 LVM 来解决磁盘的容量限制,但 Overlay FS 单靠本身是解决不了的,需要靠 XFS 来限制。
但我们发现在某些场景中 XFS 因为没有开启 ftype 功能,导致系统读写缓慢,因此我们建议大家如果也选用了 Overlay FS+XFS 方式,要把 ftype 设置为 1。
镜像市场
如上图所示,镜像市场主要还是分层,目的是让不同的用户可以专注于不同的事情上:
- 基础环境镜像:弹性云管理员制作的镜像:FROM 官方镜像,增加了服务用到的各种 Agent、常用软件、系统配置(添加 User、日志切割等)的镜像。包揽最复杂的周边应用的整合,并兼顾将镜像做得更小。
- 服务环境镜像:SRE 同学制作和维护的镜像:FROM 基础环境镜像,安装了服务运行所需的环境、配置。更加专注服务环境的保障,使镜像满足产品线的使用。
- 服务镜像:使用 RD 代码中 Dockerfile(一般只需要 Copy 代码到线上所需目录即可),FROM 服务环境镜像,通过弹性云系统生成的提供服务的镜像。
心得与展望
心得体会
首先,特别想强调的是“云计算拼的是运维,拼的是可靠性”。产品做得再好、运维人不靠谱,出了纰漏后就没人敢再用你的产品了,说得再好也没用。
其次,“线上无小事,要对线上保持敬畏感”。我之前是做开源社区的,解决方案做得多,但现在做云计算方案时才发现实际与想象的差别特别大,可以说云计算落地更难。
也许大家看了一些网上的分享后都会觉得搭建一套容器环境是比较容易的,可最难的就像我们这样已经有现成的体系。
如果你要把它落地就需要做各种打通、妥协,甚至对一些功能做一些切割,有时这些不是我们想做的,但为了推进落地不得不去做。
此外,技术挑战是一方面,但用户习惯是更加大的挑战,特别是业务方,他们肯定会说为什么我要迁,现在用得好好的,即便你和他们说做这个是对公司省成本的,可以提高运维的便利性,他们依然会觉得我不缺钱,怕麻烦。
再者,他们不是做运维的,明显感觉不到这些带来的好处。另一方面,有些用户觉得我上你的云可以,但你不要改变我的习惯,原来是什么样现在还得保持什么样。
但问题是原来是物理机的用法,可现在已经上容器了 ,还想保持原来的方式,这是不可能的。
运维是一个特别苦的职业,线上无小事,一点点小改动,如果没有考虑清楚、没有充分验证过回滚方案,那分分钟要出事,相信大家也深谙此理。
未来展望
对于弹性云的未来规划,***是更细粒度的隔离。大家对容器比较了解的话,就会清楚现在 Docker 更多的是对 Memory 使用量的隔离,以及对 CPU 使用量的隔离。
但实际过程中我们会发现,比如你给用户一个 8G 内存,在某种极端配置下 8G 内存会不断地刷新读写,那你的 L3 Cache 很快就会被刷爆了,导致大量的 Cache Miss,这样的话还是会对程序性能造成很大影响的。
第二是静态容器的弹性伸缩。因为静态容器确实就像虚拟机一样,运维成本特别高,所以一旦物理机挂了,容器也就挂了,很多东西是找不回来的。
而且你要扩容也只能按照传统的方法,先申请一个容器再去上面做部署、配置等,这些都会给运维同学带来非常大的困扰。
第三是更智能的扩容算法。目前我们是根据实时的使用率来进行扩容,但其实对于我们来说,很多业务高峰期是有规律的,如果能做到提前预测出高峰期,做适应的扩容,那将会极大地提高资源使用率。
最重要的一点是依托社区、回馈社区。我们打算腾出手来,把一些定制化需求不是那么高的解决方案来回馈到社区。
谭霖,滴滴出行弹性云架构师、云计算专家,曾任职于英特尔从事云计算方向研究。OpenStack Ironic 项目核心开发者,《OpenStack设计与实现》作者,多次在OpenStack Summit SRECon 峰会上分享 Topic。目前从事基于 Kubernetes 的私有云平台的研发,专注提高服务的稳定性和研发、运维效率。