写点什么

AI 独角兽商汤科技的内部服务容器化历程

  • 2020-04-12
  • 本文字数:8018 字

    阅读完需:约 26 分钟

AI独角兽商汤科技的内部服务容器化历程

本文由阿尔曼,商汤科技运维工程师于 4 月 26 日晚在 Rancher 微信群所做的技术分享整理而成。商汤科技是专注于计算机视觉领域的 AI 公司。本次分享结合了容器平台团队帮助公司业务/内部服务容器化历程,介绍商汤科技在容器化历程中使用的工具、拥有的最佳实践及值得分享的经验教训。

内容目录

  • 背景

  • 需求分析与技术选型

  • 容器镜像

  • 监控报警

  • 可靠性保障

  • 总结

背景

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


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


我们遇到的问题不是新问题,业界也是有不少应对的工具和方法论,但在早期,我们对运维工具的复杂性增长还是保持了一定的克制:ssh + bash script 扛过了早期的一段时光,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 为主的开源系统组合可以基本满足容器管理的大部分需求,总体架构如下图


容器镜像

容器镜像服务是公司级别的 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 -scat << EOF > /etc/docker/daemon.json{  "insecure-registries": ["quay.io", "gcr.io","k8s.gcr.io],  "registry-mirrors": ["https://mirror.example.com"]}EOFsystemctl 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# minikubeminikube start --insecure-registry gcr.io,quay.io,k8s.gcr.io --registry-mirror https://mirror.example.com
复制代码

技术架构图

监控报警

由于 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 人员对监控报警系统的信赖和认可至关重要。

技术架构图

可靠性保障

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


  • 生产就绪性检查

  • 备份管理体系

  • 故障分析与总结

  • chaos monkey


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

预期

  • 大多数故障无需人立刻干预

  • 业务异常(如 HTTP 502/503)窗口在两分钟以内

  • 报警系统应该保证

  • 不漏报

  • 没有报警风暴

  • 报警分级别(邮件/微信/电话)发到该接收报警的人

测试样例

我们需要进行测试的 case 有:


  • service 升级

  • 业务容器随机销毁

  • 主机遣散

  • 网络抖动模拟

  • Rancher 基础服务升级

  • 主机级别网络故障

  • 单主机机器宕机

  • 若干个主机机器宕机

  • 可用区宕机

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

总结

1、体量较小公司也可以搭建相对可用的容器平台。


2、公司发展早期投入一些精力在基础设施的建设上,从长远来看还是有价值的,这种价值体现在很早就可以积累一批有能力有经验有干劲儿的团队,来不断对抗规模扩大后的复杂性猛增的问题。一个“让人直观感觉”,“看起来”混乱的基础技术架构,会很大程度上影响开发人员编码效率。甚至可以根据破窗原理揣测,开发人员可能会觉得将会运行在“脏”,“乱”,”差”平台的项目没必要把质量看得太重。对于一个大的组织来讲,秩序是一种可贵的资产,是有无法估量的价值的。


3、镜像拉取慢问题也可以比较优雅地缓解。


4、国内访问国外网络资源总体来讲还是不方便的,即使没有 GFW,带宽也是很大的问题。而我们的解决方案也很朴素,就是缓存加本地访问,这样用比较优雅高效地方法解决一个“苍蝇”问题,改善了很多人的工作体验,作为工程人员,心里是很满足的。


5、容器化也可以看作是一种对传统运维体系的重构。


6、容器化本质上是当容器成为技术架构的所谓 building blocks 之后,对已有开发运维解决方案重新审视,设计与重构。微服务、云原生催生了容器技术产生,而后者,特别是 Docker 工具本身美妙的 UX,极大地鼓舞了技术人员与企业奔向运维“应许之地”的热情。虽然大家都心知肚明银色子弹并不存在,但 Kubernetes ecosystem 越来越看起来前途不可限量,给人以无限希望。而贩卖希望本身被历史不断证明,倒真是稳赚不亏的商业模式。

致谢

1、感谢 Richard Stallman 为代表的自由软件运动的参与者、贡献者们,让小人物、小公司也能有大作为。


2、感谢 Google Search 让搜索信息变得如此便利。


3、感谢 Docker 公司及 Docker 软件的贡献者们,催生了一个巨大的行业也改善了众多开发/运维人员的生活。


4、感谢 Rancher 这个优秀的开源项目,提供了如 Docker 般的容器运维 UX。


5、感谢 GitHub 让软件协作和代码共享如此便利和普及。


6、感谢 mermaid 插件的作者们,可以方便地用 markdown 定义编辑好看的流程图。

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/blog/2017/03/14/java-inside-docker/这样的问题,可以问的更具体一些。


Q:普罗米修斯的 catalog 能否共享一下?


A:这个 catalog 我们就是按 Rancher 官方 catalog 里的 Prometheus 改的,增加了 fluentd/额外的 exporter,定制了镜像之类的,没有什么 magic。


Q:普罗米修斯和 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 的 enviroments 做多租户的,每个环境一个租户(其实为了可灵活切换/基础组件升级,每个租户会有两个几乎一样的环境)。


Q:生产环境上 k8s 的话,采用哪种部署方式比较好?


A:我觉得 rancher 2.0 就是一个很好的方案,很适合企业需求,部署的话 rke 真的蛮好使的(之前我都不信),比 kubespray 好使多了。


Q:普罗米修斯里面的 nodeexporter 和 cadvicor 都是 overlay 网络的地址吧。如何和宿主机对应上呢?每次找起来挺费劲的。


A:这个是好问题,这两个直接用 host network,然后勾选 cattle 的 Enable Rancher DNS service discovery 这个选项,来让 rancher dns 服务应用到不使用 managed network 的服务就好。


Q:Prometheus remote storage 你们选择的是什么数据库呢?


A:remote storage 我们还没有正式使用。


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,还有不少内部系统开发类、研发类的工作。


2020-04-12 20:43888

评论

发布
暂无评论
发现更多内容

跑步课程导入能力,助力科学训练

HarmonyOS SDK

HMS Core

PCB如何设计防静电?华秋一文告诉你

华秋电子

文盘Rust -- rust连接oss

TiDB 社区干货传送门

开发语言

Confluence(知识库软件)和腾讯文档(多人协作文档软件)的区别

爱吃小舅的鱼

Confluence PingCode 文档管理工具

重磅来袭!微服务的里程碑SpringCloudAlibaba

做梦都在改BUG

Java 架构 微服务 Spring Cloud spring cloud alibaba

膜拜!阿里内部都在强推的K8S(kubernetes)学习指南,不能再详细了

做梦都在改BUG

Java Kubernetes k8s

小心白蛇!PyPI仓库被持续投放White Snake后门组件

墨菲安全

pypi 开源软件供应链安全

并发编程-ReentrantLook底层设计

做梦都在改BUG

Java 并发编程 ReentrantLook

PHP高并发高负载下的3种实战场景解决方法

北桥苏

php 高并发优化 thinkphp

Elasticsearch分布式搜索引擎的基本使用

北桥苏

php elasticsearch

耗时一个月,整理了这份大厂Java面试 / 学习指南,共计1500+ 题全面解析

采菊东篱下

Java 程序员

方案精讲丨TiDB 在社交场景的解决方案实践

TiDB 社区干货传送门

开源轻量级 IM 框架 MobileIMSDK 的Uniapp客户端库已发布

JackJiang

网络编程 即时通讯 即时通讯IM

如何使用appuploader制作apple证书​

雪奈椰子

Avive World算力挖矿系统开发技术

薇電13242772558

算力

phpstorm开发项目中5种常用的使用方法

北桥苏

php PHPStorm

不到1分钟,帮你剪完旅行vlog,火山引擎全新 AI「神器」真的这么绝?

字节跳动技术范儿

字节跳动 算法 计算机视觉 云服务 火山引擎

2023 年最新 Java 面试必背八股文,1338 道最新大厂架构面试题

架构师之道

Java 面试

从 MySQL 到 Oracle 再到全面 TiDB ,云盛海宏的数据库架构实践

TiDB 社区干货传送门

不服不行!Github爆火的「高并发秒杀顶级教程」,先睹为快

做梦都在改BUG

Java 高并发 秒杀系统

PHP快速使用RabbitMQ实现项目中部分业务的解耦

北桥苏

php RabbitMQ thinkphp

【源码分析】【seata】at 模式分布式事务-server端与客户端交互

如果晴天

源码分析 分布式事务 分布式锁 seata Seata框架

OSS云文件列举分页

北桥苏

php OSS 分页 上传 thinkphp

windows下docker的安装与镜像的制作提交

北桥苏

Docker Docker 镜像

快速地找到任何文件或文件夹:Find Any File Mac版

真大的脸盆

Mac Mac 软件 文件搜索 搜索文件 文件查找工具

袋鼠云产品功能更新报告05期|应有尽“优”,数栈一大波功能优化升级!

袋鼠云数栈

大数据 hadoop 数据中台

ZeroErr 零误框架

西风逍遥游

短信验证 创建应用

MobTech袤博科技

简单学习一下 MyBatis 动态SQL使用及原理

做梦都在改BUG

Java mybatis

2023年报业网络安全等级保护定级流程

行云管家

网络安全 等级保护 报业

这套Github上40K+star面试笔记,可以帮你搞定95%以上的Java面试

做梦都在改BUG

Java java面试 Java八股文 Java面试题 Java面试八股文

AI独角兽商汤科技的内部服务容器化历程_文化 & 方法_Rancher_InfoQ精选文章