DockOne微信分享(一六七):AI公司商汤科技内部服务容器化历程


【编者的话】商汤科技是专注于计算机视觉领域的AI公司。商汤的运维团队为了云服务、内部效率系统的部署、更新、维护,搭建了一个容器管理平台,该平台粘合了许多容器世界的开源工具。此次分享将结合容器平台团队帮助业务/内部服务容器化的历程,分享我们使用的工具、最佳实践及吸取的经验教训。

背景

商汤科技是一家计算机视觉领域的AI创业公司,公司内会有一些业务需要云端API支持,一些客户也会通过公网调用这些所谓SaaS服务。总体来讲,云API的架构比较简单,另外由于公司成立不久,历史包袱要轻许多,很多业务在设计之初就有类似微服务的架构,比较适合通过容器化来适配其部署较繁复的问题。

公司各个业务线相对独立,在组织上,体现在人员,绩效及汇报关系的差异;在技术上体现在编程语言,框架及技术架构的独自演进,而服务的部署上线和后续维护的工作,则划归于运维部门。这种独立性、差异性所加大的运维复杂度需要得到收敛。

我们遇到的问题不是新问题,业界也是有不少应对的工具和方法论,但在早期,我们对运维工具的复杂性增长还是保持了一定的克制:SSH + Bash Scripts扛过了早期的一段时光,Ansible也得到过数月的应用,但现实所迫,我们最终还是投向了Docker的怀抱。

Docker是革命性的,干净利落的UX俘获了技术人员的芳心,我们当时所处的时期,容器编排的大战则正处于Docker Swarm mode发布的阶段,而我们需要寻找那种工具,要既能应对日益增长的运维复杂度,也能把运维工程师从单调、重复、压力大的发布中解放出来。

Rancher是我们在HackerNews上的评论上看到的,其简单易用性让我们看到了生产环境部署容器化应用的曙光,但是要真正能放心地在生产环境使用容器,不“翻车”,还是有不少工作要做。由于篇幅的原因,事无巨细的描述是不现实的。我接下来首先介绍我们当时的需求分析和技术选型,再谈谈几个重要的组成部分如容器镜像、监控报警和可靠性保障

需求分析与技术选型

暂时抛开容器/容器编排/微服务这些时髦的词在一边,对于我们当时的情况,这套新的运维工具需要三个特性才能算成功:开发友好、操作可控及易运维

开发友好

能把应用打包的工作推给开发来做,来消灭自己打包/编译如Java/Ruby/Python代码的工作,但又要保证开发打出的包在生产环境至少要能运行,所以怎么能让开发人员方便正确地打出发布包,后者又能自动流转到生产环境是关键。长话短说,我们采取的是Docker + Harbor的方式,由开发人员构建容器镜像,通过LDAP认证推送到公司内部基于Harbor的容器镜像站,再通过Harbor的Replication机制,自动将内部镜像同步到生产环境的镜像站,具体实现可参考接下来的容器镜像一节。

操作可控

能让开发人员参与到服务发布的工作中来,由于业务线迥异的业务场景/技术栈/架构,使得只靠运维人员来解决发布时出现的代码相关问题是勉为其难的,所以需要能够让开发人员在受控的情境下,参与到服务日常的发布工作中来,而这就需要像其提供一些受限可审计且易用的接口,WebUI+Webhook就是比较灵活的方案。这方面,Rancher提供的功能符合需求。

易运维

运维复杂度实话说是我们关注的核心,毕竟容器化是运维部门为适应复杂度与日俱增而发起的,屁股决定脑袋。考虑到本身容器的黑盒性和稳定性欠佳的问题,再加上真正把容器技术搞明白的人寥寥无几,能平稳落地的容器化运维在我们这里体现为三个需求:多租户支持,稳定且出了事能知道,故障切换成本低。多租户是支持多个并行业务线的必要项;容器出问题的情况太多,线上环境以操作系统镜像的方式限定每台机器Docker和内核版本;由于传统监控报警工具在容器化环境捉襟见肘,需要一整套新的监控报警解决方案;没人有把握能现场调试所有容器问题(如跨主机容器网络不通/挂载点泄漏/dockerd卡死/基础组件容器起不来),需要蓝绿部署的出故障后能立刻切换,维护可靠与可控感对于一个新系统至关重要。

技术架构图

总结一下,Rancher、Harbor、Prometheus/Alertmanager为主的开源系统组合可以基本满足容器管理的大部分需求,总体架构如下图:
640.webp_.jpg

容器镜像

容器镜像服务是公司级别的IT基础设施,在各个办公区互联带宽有限的物理限制下,需要给分散在多个地理位置的用户以一致、方便、快速的使用体验。我们主要使用了VMware开源的Harbor工具来搭建容器镜像服务,虽然Harbor解决了如认证、同步等问题,但Harbor不是这个问题的银色子弹,还是需要做一些工作来使镜像服务有比较好的用户体验。这种体验我们以Google Container Registry为例来展现。

作为Google的开放容器镜像服务,全球各地的用户都会以同一个域名gcr.io推拉镜像docker push gcr.io/my_repo/my_image:my_tag,但其实用户推拉镜像的请求,由于来源地理位置不同,可能会被GeoDNS分发在不同的Google数据中心上,这些数据中心之间有高速网络连接,各种应用包括GCR会通过网络同步数据。这样的方法既给用户一致的使用体验,即所有人都是通过gcr.io的域名推拉镜像,又因为每个人都是同自己地理位置近的数据中心交互而不会太“卡”,并且由于Google Container Registry底层存储的跨数据中心在不断高速同步镜像(得益于Google优异的IT基础设施),异国他乡的别人也能感觉很快地拉取我们推送的镜像(镜像“推”和“拉”的异步性是前提条件)。

花篇幅介绍Google Container Registry的目的是,用户体验对用户接受度至关重要,而后者往往是一个新服务存活的关键,即在公司内部提供类似GCR一般的体验,是我们容器镜像服务为了成功落地而想接近的产品观感。为了达到这种观感,需要介绍两个核心的功能,开发/生产镜像自动同步,镜像跨办公区同步。另外,虽然有点超出镜像服务本身,但由于特殊的国情和使用关联性,国外镜像(DockerHub、GCR、Quay)拉取慢也是影响容器镜像服务使用体验的关键一环,镜像加速服务也是需要的。

开发/生产镜像自动同步

由于开发环境(公司私网),生产环境(公网)的安全性和使用场景的差异,我们部署了两套镜像服务,内网的为了方便开发人员使用是基于LDAP认证,而公网的则做了多种安全措施来限制访问。但这带来的问题是如何方便地向生产环境传递镜像,即开发人员在内网打出的镜像需要能自动地同步到生产环境。

我们利用了Harbor的replication功能,只对生产环境需要的项目才手动启用了replication,通过这种方式只需初次上线时候的配置,后续开发的镜像推送就会有内网Harbor自动同步到公网的Harbor上,不需要人工操作。

镜像跨办公区同步

由于公司在多地有办公区,同一个team的成员也会有地理位置的分布。为了使他们能方便地协作开发,镜像需要跨地同步,这我们就依靠了公司已有的Swift存储,这一块儿没有太多可说的,带宽越大,同步的速度就越快。值得一提的是,由于Harbor的UI需要从MySQL提取数据,所以如果需要各地看到一样的界面,是需要同步Harbor MySQL数据的。

镜像加速

很多开源镜像都托管在DockerHub、Google Container Registry和Quay上,由于受制于GFW及公司网络带宽,直接pull这些镜像,速度如龟爬,极大影响工作心情和效率。

一种可行方案是将这些镜像通过代理下载下来,docker tag后上传到公司镜像站,再更改相应manifest yaml,但这种方案的用户体验就是像最终幻想里的踩雷式遇敌,普通用户不知道为什么应用起不了,即使知道了是因为镜像拉取慢,镜像有时能拉有时又不能拉,他的机器能拉,我的机器不能拉,得搞明白哪里去配默认镜像地址,而且还得想办法把镜像从国外拉回来,上传到公司,整个过程繁琐耗时低智,把时间浪费在这种事情上,实在是浪费生命。

我们采取的方案是,用mirror.example.com的域名来mirror DockerHub,同时公司nameserver劫持Quay,GCR,这样用户只需要配置一次docker daemon就可以无痛拉取所有常用镜像,也不用担心是否哪里需要override拉取镜像的位置,而且每个办公区都做类似的部署,这样用户都是在办公区本地拉取镜像,速度快并且节约宝贵的办公区间带宽。
值得一提的是,由于对gcr.io等域名在办公区内网做了劫持,但我们手里肯定没有这些域名的key,所以必须用http来拉取镜像,于是需要配置docker daemon的--insecure-registry这个项。

用户体验

配置docker daemon(以Ubuntu 16.04为例)
sudo -s
cat << EOF > /etc/docker/daemon.json
{
"insecure-registries": ["quay.io", "gcr.io","k8s.gcr.io],
"registry-mirrors": ["https://mirror.example.com"]
}
EOF
systemctl restart docker.service

测试
# 测试解析,应解析到一个内网IP地址(private IP address)

拉取dockerhub镜像

docker pull ubuntu:xenial

拉取google镜像

docker pull gcr.io/google_containers/kube-apiserver:v1.10.0

拉取quay镜像

docker pull quay.io/coreos/etcd:v3.2

minikube

minikube start --insecure-registry gcr.io,quay.io,k8s.gcr.io --registry-mirror https://mirror.example.com

技术架构图

640.webp_(1)_.jpg

监控报警

由于Zabbix等传统监控报警工具容器化环境中捉襟见肘,我们需要重新建立一套监控报警系统,幸亏Prometheus/Alertmanager使用还算比较方便,并且已有的Zabbix由于使用不善,导致已有监控系统的用户体验很差(误报/漏报/报警风暴/命名不规范/操作复杂等等),不然在有限的时间和人员条件下,只是为了kick start而什么都得另起炉灶,还是很麻烦的。

其实分布式系统的监控报警系统,不论在是否用容器,都需要解决这些问题:能感知机器/容器(进程)/应用/三个层面的指标,分散在各个机器的日志要能尽快收集起来供查询检索及报警低信噪比、不误报不漏报、能“望文生义”等。

而这些问题就像之前提到的,Prometheus/Alertmanager已经解决得比较好了:通过exporter pattern,插件化的解决灵活适配不同监控目标(node-exporter、cAdvisor、mysql-exporter、elasticsearch-exporter等等);利用Prometheus和Rancher DNS服务配合,可以动态发现新加入的exporter/agent;Alertmanager则是一款很优秀的报警工具,能实现alerts的路由/聚合/正则匹配,配合已有的邮件和我们自己添加的微信(现已官方支持)/电话(集成阿里云语音服务),每天报警数量和频次达到了oncall人员能接受的状态。

至于日志收集,我们还是遵从了社区的推荐,使用了Elasticsearch + Fluentd + Kibana的组合,Fluentd作为Rancher的Global Serivce(对应于Kubernetes的daemon set),收集每台机器的系统日志,dockerd日志,通过docker_metadata这个插件来收集容器标准输出(log_driver: json_file)的日志,Rancher基础服务日志,既本地文件系统压缩存档也及时地发往相应的Elasticsearch服务(并未用容器方式启动),通过Kibana可视化供产品售后使用。基于的日志报警使用的是Yelp开源的Elastalert工具。

为每个环境手动创建监控报警stack还是蛮繁琐的,于是我们也自定义了一个Rancher Catalog来方便部署。

监控报警系统涉及的方面太多,而至于什么是一个“好”的监控报警系统,不是我在这里能阐述的话题,Google的Site Reliability Engineering的这本书有我认为比较好的诠释,但一个抛砖引玉的观点可以分享,即把监控报警系统也当成一个严肃的产品来设计和改进,需要有一个人(最好是核心oncall人员)承担产品经理般的角色,来从人性地角度来衡量这个产品是否真的好用,是否有观感上的问题,特别是要避免破窗效应,这样对于建立oncall人员对监控报警系统的信赖和认可至关重要。

技术架构图

3.webp_.jpg

可靠性保障

分布式系统在提升了并发性能的同时,也增大了局部故障的概率。健壮的程序设计和部署方案能够提高系统的容错性,提高系统的可用性。可靠性保障是运维部门发起的一系列目的在于保障业务稳定/可靠/鲁棒的措施和方法,具体包括:
  • 生产就绪性检查
  • 备份管理体系
  • 故障分析与总结
  • Chaos Monkey


主要谈谈Chaos Monkey,总体思路就是流水不腐,户枢不蠹。通过模拟各种可能存在的故障,发现系统存在的可用性问题,提醒开发/运维人员进行各种层面的改进。

预期

  • 大多数故障无需人立刻干预
  • 业务异常(如HTTP 502/503)窗口在两分钟以内

  • 报警系统应该保证
    • 不漏报
    • 没有报警风暴
    • 报警分级别(邮件/微信/电话)发到该接收报警的人


测试样例

我们需要进行测试的case有:
  • service升级
  • 业务容器随机销毁
  • 主机遣散
  • 网络抖动模拟
  • Rancher基础服务升级
  • 主机级别网络故障
  • 单主机机器宕机
  • 若干个主机机器宕机
  • 可用区宕机


部署示例(单个租户&单个地域)

22.webp_.jpg

总结

  1. 体量较小公司也可以搭建相对可用的容器平台。
  2. 公司发展早期投入一些精力在基础设施的建设上,从长远来看还是有价值的,这种价值体现在很早就可以积累一批有能力有经验有干劲儿的团队,来不断对抗规模扩大后的复杂性猛增的问题。一个“让人直观感觉”,“看起来”混乱的基础技术架构,会很大程度上影响开发人员编码效率。甚至可以根据破窗原理揣测,开发人员可能会觉得将会运行在“脏”,“乱”,”差”平台的项目没必要把质量看得太重。对于一个大的组织来讲,秩序是一种可贵的资产,是有无法估量的价值的。
  3. 镜像拉取慢问题也可以比较优雅地缓解。
  4. 国内访问国外网络资源总体来讲还是不方便的,即使没有GFW,带宽也是很大的问题。而我们的解决方案也很朴素,就是缓存加本地访问,这样用比较优雅高效地方法解决一个“苍蝇”问题,改善了很多人的工作体验,作为工程人员,心里是很满足的。
  5. 容器化也可以看作是一种对传统运维体系的重构。
  6. 容器化本质上是当容器成为技术架构的所谓building blocks之后,对已有开发运维解决方案重新审视,设计与重构。微服务、云原生催生了容器技术产生,而后者,特别是Docker工具本身美妙的UX,极大地鼓舞了技术人员与企业奔向运维“应许之地”的热情。虽然大家都心知肚明银色子弹并不存在,但Kubernetes ecosystem越来越看起来前途不可限量,给人以无限希望。而贩卖希望本身被历史不断证明,倒真是稳赚不亏的商业模式。


Q&A

Q:你们线上是直接用的测试环境同步的镜像吗?
A:是的,这也是社区推荐的实践,同一个镜像流转开发/测试/预发布/生产环境,可以预先提供一些安全/小型的base image给开发人员,做好配置文件与代码的隔离就好。

Q:如何解决不同环境的配置问题?
A:为了保证同一个镜像流转开发/测试/预发布/生产环境,所以需要首先把配置与代码分离,配置可以通过环境变量,side-kick container或者rancher-secret的方式传入代码所在镜像。

Q:作为一家AI公司,是否有使用容器来给开发者构建一个机器学习的平台?
A:商汤在比较早期就开始跑分布式的深度训练集群,当时容器/编排还不是一个feasiable的solution,对于异构的架构支持也不太好,比如NVIDIA GPU的device plugin也是最近才发布,所以容器还不是我们生产训练集群的基础技术,但是我们迭代的方向。

Q:你好,关于拉取墙外的镜像这个地方我有点不太清楚,最终肯定还是需要一个可以翻出去的节点去拉取镜像吧?
A:对,有多种方法做到这一点。最简单的方法就是利用http代理的方式,网络层的VPN也可以。

Q:数据库你们也放Docker里么?现在我看到有些人也把MySQL放Docker里,这种方案你们研究过么?可行性如何?
A:有状态的应用跑在容器里本身就是一个复杂的问题,Kubernetes也是引入了Operator pattern才在几个有状态的应用(etcd/Prometheus)上有比较好的效果,Operator的代码量也是相对庞大的,Rancher/Cattle作为轻量级的解决方案,还是适合Web类型的应用跑在容器里。

Q:业务整体融合到Rancher中遇到过什么问题吗?
A:这个问题就太宽泛了,遇到的问题有很多,技术非技术都会有,我可以讲一个例子,比如Java应用跑在容器里,我们就会遇到类似https://developers.redhat.com/ ... cker/这样的问题,可以问的更具体一些。

Q:普罗米修斯的catalog能否共享一下?
A:这个catalog我们就是按Rancher官方catalog里的Prometheus改的,增加了Fluentd/额外的exporter,定制了镜像之类的,没有什么magic。

Q:Prometheus和Altermanger有没有相关文档?
A:Prometheus/Alertmanager说实话,是poorly documented,Alertmanager我们是代码好好看过的,Prometheus的查询语句也是不太好写,这一点没啥好办法,多看多尝试吧。

Q:Harbor里面的镜像,贵公司是怎么批量删除的?
A:目前还没有这个刚需,但确实是需要考虑的,我这里还没啥想法。

Q:接口监控是怎么做的?网络抖动用什么模拟的?
A:接口监控我们做的比较粗糙,用的blackbox-exporter,需要手动添加,目前监控报警系统我们在深度定制中,目标是做成向OpsGenie这样的体验;网络抖动是用https://github.com/alexei-led/pumba 这个工具做的。

Q:你们的服务可用性达到了一个什么样的级别呢?有没有出现过什么比较大的事故?
A:目前各个服务上线都不久,谈可用性就比较虚了;比较大的事故的话,我们曾经遇到Rancher 的一个bug(https://github.com/rancher/rancher/issues/9118),还有应用没有好好配健康检查,服务进程PID不为1,大量503这样的,我们每次大的事故都会做Postmortem,早期还不少的,主要是经验和测试不够的问题。

Q:请问Prometheus用的是什么存储,有没有考虑数据高可用这块?
A:Prometheus我们就是用的普通的local storage,升级就会丢失,考虑过数据高可用,后续考虑remote storage。

Q:您在分享中提到了一个Alertmanage,这个产品必须配合Prometheus使用吗?
A:这不一定的,我们还用Alertmanager直接接受Zabbix发出的报警,Alertmanager提供HTTP的接口的https://prometheus.io/docs/alerting/clients/

Q:请问多租户是如何实现的?
A:我们是利用Rancher的Environments做多租户的,每个环境一个租户(其实为了可灵活切换/基础组件升级,每个租户会有两个几乎一样的环境)。

Q:生产环境上Kubernetes的话,采用哪种部署方式比较好?
A:我觉得Rancher 2.0就是一个很好的方案,很适合企业需求,部署的话RKE真的蛮好使的(之前我都不信),比Kubespray好使多了。

Q:普罗米修斯里面的NodeExporter和cAdvicor都是Overlay网络的地址吧。如何和宿主机对应上呢?每次找起来挺费劲的。
A:这个是好问题,这两个直接用host network,然后勾选cattle的Enable Rancher DNS service discovery这个选项,来让Rancher DNS服务应用到不使用Managed Network的服务就好。

Q:Elastic kibana的安全你们是怎么做的?ELK的企业版么?
A:一般来讲这个可以先在Nginx里disable delete方法,再配合basic auth来做,有的team使用了SearchGuard这个插件。

Q:请问你们的服务暴露用Service做NodePort还是Ingress?
A:我们生产还没有使用Kubernetes,Rancher的话可以考虑使用Kong或者Rancher LoadBalancer直接绑主机端口。

Q:EFK的日志数据用的什么存储?贵司维护Rancher的团队有多少人?
A:Fluentd会在本地文件系统压一份,再往Elasticsearch打一份(配置文件里用copy这个Directive),我司维护Rancher的团队为4人,但这个团队不仅仅维护Rancher,还有不少内部系统开发类、研发类的工作。

以上内容根据2018年4月26日晚微信群分享内容整理。 分享人阿尔曼,商汤科技运维工程师。北大本科毕业,在商汤科技运维团队参与公司云服务容器相关工作。 DockOne每周都会组织定向的技术分享,欢迎感兴趣的同学加微信:liyingjiesa,进群参与,您有想听的话题或者想分享的话题都可以给我们留言。

0 个评论

要回复文章请先登录注册