内容来源:2017年9月2日,小红书运维团队负责人孙国清在“七牛云&美丽联合集团架构师实践日:CI/CD落地最佳实践”进行《小红书在容器环境的 CD 实践》演讲分享。本文转载自Go中国,IT 大咖说(id:itdakashuo)做为独家视频合做方,经主办方和讲者审阅受权发布。前端
阅读字数:2380 | 4分钟阅读数据库
嘉宾演讲视频回顾及PPT:suo.im/2THF9s后端
容器推出以来,给软件开发带来了极具传染性的振奋和创新,并得到了来自各个行业、各个领域的巨大的支持——从大企业到初创公司,从研发到各种 IT 人员等等。跨境知名电商小红书随着业务的铺开,线上部署单元的数量急剧增长,以 Jenkins 调用脚本进行文件推送的部署模式已经不能适应需求。本文做者介绍小红书如何以最小的投入,最低的开发量快速的实现容器化镜像部署,以及由此带来的收益。服务器
小红书是一个从社区作起来的跨境电商。用户喜欢在咱们的平台上发关于生活、健身、购物体验、旅游等相关帖子。目前咱们已经有有 5 千万的用户,1 千万的图文,每日有 1 亿次笔记曝光,涉及彩妆、护肤、健身、旅游等等各类领域。网络
小红书是国内最先践行社区电商这个商业模式并得到市场承认的一家电商,咱们从社区把流量引入电商,如今在电商平台的 SKU 已经上到了十万级。咱们从社区里的用户建立的笔记生成相关的标签,关联相关商品,同时在商品页面也展现社区内的和这商品有关的用户笔记。架构
如图 2 是以前的应用上线的过程,开发向运维提需求,须要多少台服务器,运维依据需求去作初始化并交付给开发。咱们如今有一个运维平台,全部服务器的部署都是由这个平台来完成的,平台调用腾讯云 API 生成服务器,作环境初始化,配置监控和报警,交付给开发的是一个标准化好的服务器。
负载均衡
开发者拿到服务器准备线上发布时用 Jenkins 触发脚本的方式:用 Jenkins 的脚本作测试,执行代码推送。当须要新加一台服务器或者下线一台服务器,要去修改这个发布脚本。 发布流程大概是这样的:Jenkins 脚本先往 beta 环境发,开发者在 beta 环境里作自测,自测环境没有问题就全量发。框架
咱们遇到很多的状况都是在开发者自测的时候没有问题,而后在线上发,线上都是全量发,结果就挂了。而后回退的时候,怎么作呢?咱们只能整个流程跑一遍,开发者回退老代码,再跑一次 Jenkins 脚本,整个过程最长须要10来分钟,这段过程线上故障一直存在,因此这个效率挺低。运维
以上的作法实际上是大多数公司的现状,可是对于咱们已经不太能适应了,目前咱们整个技术在作更迭,环境的复杂度愈来愈高,若是仍是维持现有的代码上线模式,显然会有失控的风险,并且基于这样的基础架构要作例如自动容量管理等都是很难作到的。微服务
首先,咱们整个技术团队人数在增长,再加上技术栈在变。之前都是纯 Python 的技术环境,如今不一样的团队在尝试 Java、Go、Node。还有就是咱们在作微服务的改造,之前的单体应用正在加速拆分红各个微服务,因此应用的数量也增长不少。拆分微服务后,团队也变得更细分了;同时咱们还在作先后端的拆分,原来不少 APP 的页面是后端渲染的,如今在作先后端的拆分,后端程序是 API,前端是展现页面,各类应用的依赖关系也变得愈来愈多。再加上电商每一年大促销,扩容在现有模式也很耗时耗力。因此如今的模式基本上已经不太可行了,很难持续下去。
咱们团队在两三个月之前就思考怎么解决这些问题,怎么把线上环境和代码发布作得更加好一点。基本上咱们须要作这几点:
重构“从代码到上线”的流程;
支持 Canary 发布的策略,实现流量的细颗粒度管理;
能快速回退;
实践自动化测试,要有一个环境让自动化测试能够跑;
要求服务器等资源管理透明化,不要让开发者关心应用跑在哪一个服务器上,这对开发者没有意义,他只要关心开发就能够了。
要可以方便的扩容、缩容。
咱们一开始就考虑到容器化,一开始就是用 Kubernetes 的框架作容器化的管理。为何是容器化?由于容器和微服务是一对“好朋友”,从开发环境到线上环境能够作到基本一致;为何用 Kubernetes?这和运行环境和部署环境有关系,咱们是腾讯云的重度用户,腾讯云有对 Kubernetes提供了很是到位的原生支持,所谓原生支持是指它有几个方面的实现:第一个是网络层面,咱们知道 Kubernetes 在裸金属的环境下,要实现 Overlay 网络,或者有 SDN 网络的环境,而在腾讯云的环境里,它自己就是软件定义网络,因此它在网络上的实现能够作到在容器环境里和原生的网络同样的快,没有任何的性能牺牲。第二在腾讯云的环境里,负载均衡器和 Kubernetes 里的 service 能够捆绑,能够经过建立 Kubernetes 的 service 去维护云服务的 L4 负载均衡器。第三就是腾讯云的网盘能够被 Kubernetes 管理,实现 PVC 等,固然 Kubernetes 自己提供的特性是足够知足咱们的需求的。
刚刚说了咱们做为创业公司都是是以开源为主,在新的环境里应用了这样的一些开源技术(图 4),Jenkins、GitLab、Prometheus 和 Spinnaker。Jenkins 和 GitLab 应该都据说,你们都在用,Prometheus、Docker 也都是很主流的。
Netflix 开源项目
开放性和集成能力
较强的 Pipeline 表达能力
强大的表达式
界面友好
支持多种云平台
刚才介绍了 Spinnaker,它是一个开源项目,是 Netflix 的开源项目。Netflix 的开源项目在社区一直有着不错的口碑。它有开放式的集成能力,它原生就能够支持 Jenkins、GitLab 的整合,它还支持 Webhook,就是说在某一个环境里,若是后面的某个资源的控制组件,自己是个 API,那它就很容易整合到 Spinnaker 里。
再者它有比较强的 Pipeline 的能力,它的 Pipeline 能够复杂很是复杂,Pipeline 之间还能够关联,它还有很强的表达式功能,能够在任何的环节里用表达式来替代静态参数和值,在 Pipeline 开始的时候,生成的过程变量均可以被 Pipeline 每一个 stage 调用。好比说这个 Pipeline 是何时开始的,触发时的参数是什么,某一个步骤是成功仍是失败了,这次要部署的镜像是什么,线上目前是什么版本,这些均可以经过变量访问到。它还有一个比较友好的操做界面,重点的是支持多种云平台。目前支持 Kubernetes、OpenStack、亚马逊的容器云平台。
图 5 是 Spinnaker 的架构,是一个微服务的架构。这是一个微服务架构,里面包含用户界面 Deck,API 网关 Gate 等,API 网关是能够对外开放的,咱们能够利用它和其它工具作一些深度整合,Rosco 是它作镜像构建的组件,咱们也能够不用 Rosco 来作镜像构建,Orca 是它的核心,就是流程引擎。Echo 是通知系统,Igor 是用来集成 Jenkins 等 CI 系统的一个组件。Front52 是存储管理,Cloud driver 是它用来适配不一样的云平台的,好比 Kubernetes 就有专门的 Cloud driver,也有亚马逊容器云的 Cloud driver。Fiat 是它一个鉴权的组件。
图 6 是它的界面。界面一眼看上去挺乱,实际上它仍是有很好的逻辑性。这里每个块都有三种颜色来表示 Kubernetes 的环境里的某个实例的当前状态。绿色是表明是活着的,右边是实例的信息。实例的 YML 配置,实例所在的集群,实例的状态和相关 event。
图 7 是 Pipeline 的界面。首先,我以为这个界面很好看很清晰。二是 Pipeline 能够作得很是灵活,能够说执行了前几个步骤以后,等全部的步骤执行完了再执行某个步骤。这个步骤是某个用户作某个审批,再分别执行三个步骤其中的一个步骤,而后再执行某个环节。也能够说要发布仍是回退,发布是走发布的流程,回退就是回退的流程。总之在这里,你所期待的 Pipeline 的功能均可以提供,若是实在不行,还有 Webhook 的模式让你方便的和外部系统作整合。
图 8 是 Pipeline 步骤的类型。左上 Check Precondltions 前置条件知足的时候才执行某个步骤。例如当前面的第一次发布里全部的实例都存活的时候,才执行某个步骤。或者当前面的步骤达到了某个状态,再执行下一个步骤。deploy 是在 Kubernetes 环境里生成 Replication Set,能够在 deploy 里更新一个服务器组、禁用一个集群、把集群的容量往降低、往上升等等。也能够跑某一个脚本,这个脚本是在某一个容器里,有时候可能有这样的需求,好比说 Java 来讲这个 Java 跑起来以后并非立刻可以接入流量,可能要到 Java 里跑一个 job,从数据库加载数据并作些初始化工做后,才能够开始承接流量。
Pipeline 表达式很厉害,它的表达式是用 Grovvy 来作,你们知道 Grovvy 是一个动态语言。凡是 Grovvy 能用的语法,在字符串的地方均可以用。因此,这些步骤中,能够说这个步骤参数是来自表达式。也能够说有条件的执行,生成环境的时候才作这样的东西。也能够有前置条件,当知足这个条件的时候,这个流程和 stage 能够继续走下去。
如图 10 是各类类型的表达式,从如今看起来,基本上咱们各类需求都能知足了。Pipeline 能够自动触发(图 11),能够天天、每周、每个月、每一年,某一天的时候被自动触发,作一个自动发布等等,也能够在镜像有新 tag 推送到镜像仓库时,Pipeline 去作发布。
Spinnaker 和 Kubernetes 有什么关系?它有不少概念是一对一的,Spinnaker 有一个叫 Account的,Account 对应到 Kubernetes 是 Kubernetes Cluster,咱们的环境里如今有三个 Kubernetes 的 Cluster,分别对应到开发、测试和生产,它也是对应到 Spinnaker 的 三个 Account;Instance 对应到 Kubernetes 里是 Pod,一个 Pod 就是一个运行的单元;还有有 Server Group,这个 Server Group 对应的是 Replica Set 或者是 Deepionment。而后是 Load Balance,在 Spinnaker 里称之为 Load Balance 的东西在 Kubernetes 里就是 Service。
Traefik 亮点:
配置热加载,无需重启
自带熔断功能
-traefik.backend.circuitbreaker:NetworkErrorRatio() > 0.5
动态权重的轮询策略
-traefik.backend.loadbalancer.method:drr
为何咱们用 Traefik 而不用 Nginx 作反向代理呢?首先 Traefik 是一个配置热加载,用 Nginx 时更新路由规则则是作后端服务器的上线、下线都须要重载,但 Traefik 不须要。Traefik 自带熔断功能,能够定义后端某个实例错误率超过好比 50% 的时候,主动熔断它,请求不再发给它了。还有动态的负载均衡策略,它会记录 5 秒钟以内全部后端实例对请求的响应时间或链接数,若是某个后端实例响应特别慢,那接下来的 5 秒钟就会将这个后端的权重下降直到它恢复到正常性能,这个过程是在不断的调整中,这是咱们须要的功能。由于上了容器以后,咱们很难保证一个应用的全部实例都部署在相同处理能力的节点上,云服务商采购服务器也是按批量来的,每一批不可能彻底一致,很难去保证全部的节点性能都是一致的。
图 14 是 Traefik 自带的界面。咱们定义的规则,后端实例的状况均可以实时的展示。
Kubernetes 集群中的 Ingress Controller
动态加载 Ingress 更新路由规则
根据 Service 的定义动态更新后端 Pod
根据 Pod 的 Liveness 检查结果动态调整可用 Pod
请求直接发送到 Pod
Traefik 和 Kubernetes 有什么关系呢?为何在 Kubernetes 环境里选择了 Traefik?Traefik 在 Kubernetes 是以 Ingress Controller 存在,你们知道 Kubernetes 到 1.4 以后就引进了 Ingress 的概念。Kubernetes 原来只有一个 Service 来实现服务发现和负载均衡,service 是四层的负载均衡,它作不到基于规则的转发。在 Kubernetes 里 Ingress 是属于七层 HTTP 的实现,固然 Kubernetes 自己不去作七层的负载均衡,它是经过 Ingress Controller 实现的,Traefik 在 Kubernetes 里就是一种 Ingress Controller。它能够动态加载 Kubernetes 里的 Ingress 所定义的路由规则,Ingress 里也定义了一个路由规则所对应的 Service,而 Service 又和具体的 Pod 相关,Traefik 据此能够将请求直接发送给目标 Pod,而无需经过 Service 所维护的 iptables 来作转发。Pod列表是根据 Pod 的 Liveness 和 Readiness 状态作动态的调整。
图 15 是新发布的一个流程或者是开发的流程。咱们有三个环节:一个是开发阶段,一个是集成测试,一个是上线。
开发阶段,开发者在迭代开始时生成一个 Feature 分支,之后的每次更新都将这个 Feature 分支推送到 GitLab 。GitLab 里配置的 Webhook 触发一个 Jenkins job,这个 job 作单元测试和镜像构建,构建成一个 Feature 分支的镜像,给这个镜像一个特定的 tag。生成新的镜像以后,触发 Spinnaker 的部署,这个部署只在开发环境里。
开发者怎么访问刚刚部署的开发环境呢?若是这是个 HTTP 应用,假设应用叫作 APP1,而分支名称叫 A,那开发者就经过 APP1-A.dev.xiaohongshu.com 就能够访问到 Feature A 的代码。在整个周期里能够不断的迭代,最后开发者以为完成了这个 Feature 了,就能够推送到 release。一旦把代码推往 release 就触发另外一个构建,基本上和前面的过程差很少。最后会有一个自动化的测试,基本上是由测试团队提供的自动化测试的工具,用 Spinnaker 调用它,看结果是什么样。
若是今天颇有信心了,决定往生产发了,能够在 Git 上生成一个 tag,好比这个 tag 是 0.1.1,今天要发 0.1.1 版了,一样也会触发一个镜像的构建。这三个不一样的阶段构建的镜像 tag 不同,每生成一个新 tag, Spinnaker 会根据 tag 的命名规则触发不一样的 Pipeline,作不一样环境的部署。
最重要的是咱们有一个 Canary 的发布过程,咱们在 Spinnaker 的基础上,开发了一套 Canary 的机制。Canary 和 Beta 差很少,但 Canary 是真实引入流量,它把线上用户分为两组:一是稳定版的流量用户;二是 Canary 版的用户,他们会率先使用新版本,咱们的具体策略是先给公司、先给咱们本身办公室的人来用,这个灰度若是没问题了,用户反馈 OK,看看监控数据也以为没有问题,再按照 1%-10%-20%-50%-100% 的阶段随机挑选线上用户继续灰度,在这整个过程都有监控数据能够看, 任什么时候候若是有异常均可以经过 Spinnaker 进行回退。
这个是 Canary 的示意图,线上用户被分红两组,大部分用户访问老版本,特定用户经过负载均衡转发到特定的版本里,后台有监控数据方便去比较两个版本之间的差别。
这是咱们在容器环境里实现的 Canary 的架构(图 17),用户请求从前面进来,首先打到 Traefik,若是没有作 Canary 的过程,Traefik 是直接把请求打到组实例。若是要发布一个新的版本,有一个 HTTP 的 API 控制 project service,决定把什么样的流量能够打到这个里面版本。咱们的策略多是把办公室用户,能够经过 IP 看到 IP,或者把线上的安卓用户,或者线上 1% 的安卓用户打给它,这些都是能够定义的。
如图 18 所示是线上真实的部署流程。首先是要设置一个 Canary 策略,这个策略能够指定彻底随机仍是根据用户的特定来源。好比说是一个办公室用户,或者全部上海的用户等等,而后去调整参数,是 1% 的上海用户,仍是全部的上海用户。而后开始部署服务。接下来把这个 Canary 实例作扩展,在流量进来以前,实例的容量必定要先准备好。进来以后把流量作从新定向,把流量从原来直接打给后端的 Pod 改为打到 Canary 代理服务上,由 Canary 代理服务根据策略和用户来源作进一步的流量分发。整个过程不断的迭代,有 1% 的线上用户开始慢慢到到 100%。在达到 100% 后,就采用红黑的策略替换掉全部旧版本:先把全部的新版本实例生成出来,等全部的新版本经过健康检查,都在线了,旧的版本再批量下线,这样完成一个灰度。若是中途发现问题不能继续,立刻就能够回退,所谓的回退就是把 把流量打回到线上版本去。
图上(图 19)是咱们的 Canary 策略。这是咱们本身实现的一套东西。图中的例子是把来自指定网段一半的 iPhone 用户进行 Canary。用户分组的维度还能够有其它规则,如今咱们支持的是彻底随机/特定 IP/特定设备类型,这些规则能够组合起来。
咱们的用户分组是有一致性保证的,一旦为某个用户分组了,那在当前灰度期间,这个用户的分组不会变,不然会影响用户体验。
ACA——自动灰度分析
自动容量管理
下一步咱们打算作两件事情:第一,咱们想作自动灰度分析,叫 ACA,如今 AIOps 概念很热门,我我的认为自动灰度分析能够说是一个具体的 AIOps 落地。在灰度的过程当中,人肉判断新版本是否正常,其实若是日志采集够完整的话,这个判断能够由机器来作,机器根据全部数据来为新版本作评分,而后发布系统根据评分结果自动继续发布或者终止发布并回退。第二,再往下能够作自动的容量管理,固然是基于 Kubernetes 的基础上,作自动容量管理,以便更好的善用计算资源。
最后总结一下:一个好的 CD 系统应该可以控制发布带来的风险;咱们在人力资源有限的状况下倾向于采用开源的方法解决问题,若是开源不知足的话,咱们再开发一些适配的功能。