年终盘点:解读2016之容器篇——“已死”和“永生”

也说不上什么时候起,“XXX Is Dead. Long Live XXX”的句式突然成为了技术会议上演讲题目的一个标准套路。然而不管已经被引用的多么烂俗,用这套悖论来总结2016年容器技术圈子发生的凡事种种,却实在有种说不出来的恰到好处。

无需多言,稍微回顾一下2016年容器技术圈子的时间线,我们很容易就能回想起容器技术如何在这一年迅速登上云计算舞台的中心。这股热潮,从年初Docker公司闪电收购Unikernel Systems提前扼杀各种“被颠覆”的苗头,蔓延到Kubernetes,Mesos,Docker三家项目在年中掀起的“编排”之争,再到年末阿里云一举震撼国内创业市场。“编排”,“fork docker”,“OCI runtime”, “镜像标准”,一个又一个令人目不暇接的关键词带着背后的技术爆点填满了2016一整年的时间线。只不过,惊喜不断的同时,嘈杂的容器圈子也难免给我们带来了些许无所适从的挫败感。回顾这一年的容器技术发展历程,相信大家都有这样的疑惑:当这个圈子平复下来之后,我们该如何去理性地思考和解读呢?

Docker

在2016年,当我们再次说出这个关键词的时候,已经很难用一句话解释清楚我们到底在说的是什么。是Docker公司?是Docker容器?是Docker镜像?还是Docker集群?

在创业初期通过一连串极其成功的开源战略迅速蹿红之后,Docker项目几经重构,最终选择了“大一统”的战略模式,一系列普通认知中应该是独立项目的功能模块都被编译进了Docker项目的二进制文件中,这其中最引人注目的,当属SwarmKit项目。

2016年6月,Docker公司宣布将在接下来版本的Docker项目中将会提供内置的“编排”功能,这个功能的实现将主要由一个名叫“SwarmKit”的依赖库来负责。此新闻一出立刻成为容器圈子一时的舆论热点,尤其在国内。其中,看涨者不少,有言“生态闭环”、“Docker正统”,唱衰者也不缺,直呼“公然越界”、“野心昭然”。时至今日,该项目本身也日趋稳定,我们不妨再回头来重新解读一下曾经在风口浪尖上的SwarmKit。

SwarmKit的核心功能乃是“编排”,不过它对这个编排的定义还是比较模糊的,在初期主要指的是“多容器副本”和“副本负载均衡”两个核心能力,后面逐渐加入的是更多应用管理功能。说起这个项目发布的初衷,当时国内的诸多讨论之中,曾有一种误区是认为SwarmKit是Docker Swarm项目的继承、是Swarm项目的“内置版”(当然,这也要部分归功于Docker公司老辣的命名技巧)。但现在回头来看,这些“编排”能力在Docker Swarm项目中,一直都是不存在的,继承自然无从谈起。SwarmKit唯一跟Docker Swarm项目重叠的功能乃是“调度”,但实际上SwarmKit的调度也是从头做起,它维护了一个NodeHeap(堆),然后通过堆算法配合过滤条件来筛选最符合要求的节点来运行任务。这套调度机制在Swarm中也是不存在的。而在API层面,Docker Swarm项目提供的是一套简洁的单容器的API来让用户操作容器集群(这个能力非常受欢迎),而SwarmKit却从一开始就引入了Service,Task等一系列面向容器集群的、平台级别的概念,并且从底层实现上就不兼容Swarm风格的单容器API。两者的关系正如同“Java”和“JavaScript”一样风马牛不相及。

那么Docker公司内置“编排”能力并且起一个这样的有混淆意味的名字,到底意义何在呢?

答案很简单:“平台(Platform)”。或者说成是“应用/容器集群管理”,或者说成是“PaaS”甚至“CaaS”,都可以,一个意思。

这个改变的关键就在于,从今以后Docker项目就变成了一个“平台”项目而不再是一个单纯的“容器”项目了。它要站在Kubernetes,DC/OS,Cloud Foundry一样的位置上直面云的终端用户,而不是继续做这些平台背后的容器技术(甚至只是容器技术中的一种)。

这种平台级别的能力对于Docker公司来说是至关重要。容器的热度终究会冷却,用户很快就不会关心底层的容器技术为何物,他们只会记得Kubernetes API,Service, Replication Controller,DC/OS,顶多在编写Dockerfile的时候,才回忆起Docker公司的存在。很多人批评Docker公司野心太大,其实对于一家拒绝了微软40亿美金收购的后端技术创业公司来说,有怎样的进取心都不为过。Docker公司的目标是下一个VMware,下一个Intel,一个实实在在能盈利能上市的商业公司,在这个巨头如林的云计算行业里,这是令人钦佩的。

Docker公司在2016年在集群领域所做的努力离不开“平台”二字,不过国内曾经一度涌现出来的过分解读着实让这个项目背负了太多的压力。其实打开SwarmKit项目的代码看看,从编排到调度,模块设计,代码实现,跟其他项目并无二异,ipvs也是普通的NAT模式,Master节点一样维护着Apiserver, Scheduler, Orchestrator,同宿主机上的Agent通过gRPC交互,就连Agent也跟其他“第三方”项目一样也要通过client去调用Docker Engine的API。所谓的各种“颠覆性”、“大道至简”、“容器OS化”、“从下往上改变容器云形态”等观点,实在无从谈起。

还有一种可能性是Docker公司依靠Docker Swarm这样一个独立的、以单容器操作API为核心的项目来完成PaaS的使命(业界基于Swarm构建PaaS的用户不在少数)。但现实是,几乎同时发布的Google Kubernetes项目却出人意料地狙击了Swarm的发展势头。下图是自发布起,Swarm项目和Kubernetes项目GitHub Star数目的变化统计

年终盘点:解读2016之容器篇——“已死”和“永生”_第1张图片

事实上,Docker Swarm项目“使用单容器API来操作集群”的理念是相当有吸引力的。但是这个能力在接下来Docker公司想要重点发展的企业私有云市场却陷入了窘境:单容器API纵然简单友好,但企业级用户却没办法直接用它来实现哪怕一个最简单的“容器集群负载均衡”的需求。所以很多企业私有云用户更倾向于把Docker Swarm项目作为容器云的一个环节,然后自己来实现各种平台级别功能(往往还要参考Kubernetes的各种理念和设计)。照这个趋势发展,如果不推翻重来提供类似的面向集群的API,Docker公司的平台之梦恐怕就很难实现了。这也正是为什么我们前面简单一对比就不难发现,SwarmKit相比Swarm项目其实是翻天覆地的自我革命,而非继承或者内置,所谓向后兼容的问题自然也无从谈起。

其实,很多人可能已经忘记了早在Docker Swarm项目发布之前,Docker公司已经发布过一个集群管理的项目叫“libswarm”(访问该项目地址有彩蛋:https://github.com/docker/libswarm)。这个项目的初衷非常单纯,就如同Docker项目最开始发布时一样“冰雪通透”:

“... … libswarm项目的初衷是提供一套不依赖与现有分布式系统的集群管理API ,… … ,并使得其他项目可以使用它来方便的构建容器集群 … … ”

彼时的Docker公司,还希望Mesos,Fleet们使用libswarm来管理Docker容器云呢。

所谓:“Swarm已死,Swarm永生”,Docker项目又何尝不是呢。

在经历了OCI成立、贡献出了核心组件libcontainer之后,Docker公司坚定地走向了独立发展的道路。在巨头们的围剿之中,这是一家创业公司从默默无闻到炙手可热,再到痛定思痛之后的必然选择,SwarmKit,以及其他所有外界看来“野心太大”的项目和举措,都只是这个信念的间接产物而已。在未来,Docker公司依然会不遗余力的构建自己的平台世界,网络,存储,Infra Layer,CI/CD,一个全功能平台级项目所欠缺的版块都会被一一补全,各种各样内置于Docker Daemon中的Kit库和收购还会层出不穷,Docker公司还会以此为基础重点推广可以盈利的Docker Cloud和Docker Datacenter。这样的选择很正确,并且唯一。

另一方面,在补全平台级别功能的过程中,Docker项目会依然选择将这些管理组件跟原先的Docker Daemon耦合在一起。从技术角度来看,这并不是个明智之选,过高的耦合度所带来的不稳定性、Data Race和维护的问题会愈加凸显。但从推广的角度来看,这个做法非常厉害:Docker项目需要努力争取现有用户和粉丝的青睐,引导他们放弃单容器API,转而接纳新的、平台级别的API。这个转变是站上“Platform”这个舞台在所难免的,也是从Swarm项目上所得来的经验教训。

2016年末,Docker项目将容器运行时相关的最后一个组件containerd也正式剥离,“Docker”这个名字离“容器”渐行渐远,离“PaaS”越来越近。可能有人会疑惑:像Kubernetes,DC/OS,或者未来的Docker项目,它们跟PaaS不是还有所不同吗?实际上,在这股正是由Docker掀起的容器浪潮下,PaaS的定义恐怕早已悄然变化。

正所谓“PaaS已死,PaaS永生”。

在容器集群管理和企业级需求的支持上,Docker公司还是个新生儿,但Docker项目成功的哲学乃是“simple but powerful”,它一直坚持提供尽量简单的命令行界面,并不惜为此选择更复杂的实现方式,这将是它在未来会继续火热的杀手锏之一:对于任何希望快速寻求一个“可用”的容器工具的开发者和运维者来说,这个吸引力是巨大的。

Kubernetes

尽管Docker公司整整一年都在““平台”领域发力,Kubernetes依然是这个领域最瞩目的项目。这并非意外,在开源的世界里,一旦在某个领域树立了标杆,就很容易跟竞争对手拉开质的差距。Kubernetes幸运的成为了“容器集群管理“领域的开创者,其他的后进项目,无论是Marathon还是SwarmKit,都只能主动或者被动地follow开创者的提出的理念。这正如同如果让Google再做一个容器,它也会十有八九follow Docker一样。

“如果大家只是再造一个Kubernetes,那有什么理由会比Kubernetes团队做的更好?”

当然,含着“千呼万唤始出来”的Borg论文出生,又是Google公司在Big Data领域错失机会之后着重推出的平台级开源项目,Kubernetes本身自然有其过人之处。

Kubernetes的发布给整个容器圈子带来了一系列前所未闻的概念:Service,Replication Controller,Pod,Labels & Label Selector,DaemonSet, Cron Job等等等等。当时,不少用户还在评论Kubernetes的理念太超前了。而现在回头来看,这些特性不仅被用户所接受,而且很多还被其他平台项目比如Docker、Marathon、DC/OS所采纳,变成了它们的内置功能:正如同做容器不支持Docker就显得“落伍”一样,搞平台不谈“Service”、”Replica“,你的编排就不够fancy。

Kubernetes这种相对超前的技术视野与它背后原Google Borg和Omega团队成员的经验和努力密关系巨大。Borg/Omega系统在Google基础设施体系中的声誉和地位无需多言,而用户在Kubernetes中接触到的很多概念,其实在Borg/Omega系统中都有等价的特性。从这个角度来说,把Kubernetes项目描述为Borg系统在开源领域的重生并不为过。

在这一年里,Kubernetes不断地强化自己在容器集群管理领域的优势,密集发布了一系列让人称道的设计。不难预料,这些概念很快也会在其他项目中被借鉴和采纳。

就比如Secret,它允许用户将加密过的Credentials信息保存在Etcd中,然后在容器中通过环境变量或者挂载文件的方式访问它们,从而避免明文密码被随意写在环境变量、配置文件或者Dockerfile中的问题。

类似的还有ConfigMap,只不过保存在Etcd的内容,是应用所需的配置信息。

再比如Deployment,它使得用户可以直接编辑容器化任务的属性,然后直接触发Rolling Updae,还允许用户随意回滚任务到以前的版本。

再比如DaementSet,它允许用户一键部署运行在所有节点上的守护进程任务。

而最近推出的StatefulSet,则提供了原生支持有状态的应用的强大能力,并且既包括了拓扑结构状态,也包括了存储状态。

还有备受欢迎的ScheduledJob,它允许用户用标准的Cron Job格式来定义从镜像启动的定时任务,并保证这个任务执行的正确性和唯一性。

如此种种,都是Kubernetes在容器编排和管理领域树立标杆的手段,而这些设计背后的思想又十分朴素:如果在Borg里,或者在没有容器的传统环境下,我们能够通过脚本或者其他手段自动化地做某些事情,那么Kubernetes同样应该帮你做到。这个过程,正是Kubernetes所定义的(也是我们传统运维意义上的)“编排”,也是DevOps理念中所追求的“No OPs”的主要手段。

在2016年,Kubernetes另一个重点发力的领域则是CRI(Container Runtime Interface)。值得一提的是,CRI的提出者和推广者正是2015年InfoQ CNUT容器大会讲师、华人女工程师Dawn Chen,她也是Kubernetes的几位元老级(Elder)成员之一。早在Docker公司在集群领域发力之前,Dawn所领导的Node Team就已经颇有远见地开始制定解耦容器运行时的方案并持续在社区推动。通过制定这样固定的访问接口,Kubernetes不再对任何容器API产生多余的依赖,也避免了锁定在某种容器运行时之上的尴尬。目前,Kubernetes CRI主要由来自Google公司、Hyper国内团队、以及CoreOS的工程师负责维护。

得益于CRI的逐渐成熟,Kubernetes项目在容器运行时领域的支持能力得到了巨大的提升,除了默认的Docker容器之外,基于虚拟化技术的HyperContainer,CoreOS公司的rkt都已经能够通过CRI原生接入了Kubernetes。而runC团队也不甘寂寞,来自SUSE和RedHat的runC Maintainer提交了了一个叫做CRI-O的孵化项目用来直接将runC接到Kubernetes中。此外,来自微软的Windows容器也已经进入Kubernetes体系。尽管还未完全release,Kubernetes CRI的受欢迎程度已经略见一斑。

在接下来的进展中,Kubernetes会继续加强自己的已有优势,更多新的容器编排和管理能力会接踵而至,其中有状态应用的支持、更加完善的网络规则、GPU和NUMA的支持、批处理和Big Data任务支持都是值得关注的特性。值得注意的是,Kubernetes在定义容器编排和容器管理方面有一个先天的优势:由于OpenShift(Redhat基于Kubernetes的PaaS)的存在,Redhat团队一直在非常谨慎地确保Kubernetes不会功能泛滥。这正是Kubernetes保证自己所提供的平台能力恰到好处的一个重要手段。

与此同时,在捐献给Linux Foundation之后,Kubernetes所属的CNCF基金会已经构建出了一个围绕Kubernetes的平台生态系统,CNCF的成员项目不仅包括了Prometheus这样的明星,还包括了gRPC这样的底层依赖。这些项目之间会尽力兼容API和数据格式,减轻用户构建云平台的负担。一个最典型的例子,就是Kubernetes可以直接使用来自Prometheus的监控数据来做容器自动扩展,无需做任何格式转换和数据过滤。而Kubernetes正在全面基于gRPC进行的全通路上的性能优化(包括Etcd v3)也得益于该社区的有力支持。日前,国内的PingCAP也得到了CNCF的大力赞助,有望在存储状态管理和基于本地磁盘的持久化卷方面为社区注入新的血液。

未来的时间里,Kubernetes面对的挑战依然严峻,其中最大的问题在于作为一个试图真正解决问题工业级项目,如何能保证自己功能足够完善的同时,始终给用户提供简单和友好的交互体验。毕竟在未来一段时间内,争取用户依然是所有玩家的重中之重。我们已经看到Kubernetes正在对UI/UX进行优化,最典型的例子是kubeadm工具,一举解决了之前Kubernetes部署不方便的顽疾,用户终于可以用kubeadm init和kubeadm join这样的指令来启动完整的集群了。

在2016年的Kubernetes生态中,还有一个不能被忽视的因素就是自家兄弟Tensorflow的迅速蹿红(甚至短时间内就超越了Docker项目,速度令人咋舌),并且直接推动了人工智能元年的诞生。在社区层面Tensorflow目前仍无实质性的竞争对手,无论是大小公司还是机构,基于该项目的人工智能项目如雨后春笋般涌现,创业公司也层出不穷,而Kubernetes+Tensorflow的搭配则成为了默认的“基础设施+深度学习平台”的组合。这个机遇恐怕没人能事先预料到。

回想当初Kubernetes刚发布时的,不少人还对没有拿到一个真正的开源版的Borg表示失望。两年后的今天,再回顾这个项目的发展历程,我们不得不说Kubernetes已经走出了一条独立的、健康的发展路线。在这条以开源容器技术兴起为源头的道路上,越来越少人会再去谈论Borg,去做无谓的比较。Kubernetes项目正继承着这些优秀的思想,开启了一个关注容器和应用本身、专注编排和集群管理的新领域。

在这个前所未有的、容器为王的世界里:“Borg已死,Borg永生”。

Mesos生态

当今容器圈子,除了以Docker为主角的容器运行时和以Kubernetes为主角的容器集群管理,还有一方“势力”绝不能被忽视,这就是Mesos生态。众所周知,Mesos本身是一个“老”项目,它诞生于伯克利,崛起于Big Data的爆发。在Docker刚开始成名的头两年,Swarm项目羽翼未丰,Mesos独有的支持多种Framework的设计使它得以轻松接入了Docker生态,并在当时成为了管理Docker容器集群的不二之选,占满了DockerCon的重要位置。毕竟天生就具备管理万级别节点规模的水准,上层的Marathon框架又能提供一套完善的PaaS功能,还有喜人的UI,还能提供生产级别Big Data业务的支撑,Mesos没有理由不受欢迎。不过紧接着,Marathon+Mesos的组合开始显现出疲态,在Docker和Kubernetes各自亮出杀手锏不断刷新用户三观的时候,Apache社区固有的响应迟钝拖慢了Mesos进化的速度,而由创业公司维护的Marathon又一直没办法构建出更加繁荣和活跃的社区。

“社区”两个字,竟成了Mesos生态的命门。在Docker公司煞费苦心在社区争取每一个用户和粉丝、Google公司放下身段把Github作为一线阵地,用Kubernetes全力输出技术理念的时候,一旦错失了先机,哪怕有一身本事如Mesos项目,也只能望用户而心叹。这正是目前Mesos生态系统在容器圈子表现的不够强势的重要原因。当然,既然实力强劲,Mesos生态在工业界中的案例还是数不甚数,除了Marathon框架,Mesosphere公司重点维护的DC/OS项目其实能够提供并不逊于Kubernetes的各项容器编排和管理能力,不可谓不强大。但是社区表现不力,使得Mesos生态错失了成为容器圈的“buzz word”(热词)的机会。SwarmKit发布后,Mesos生态同Docker项目的蜜月期也宣告结束,随后的Mesos 1.0版本也正式release了Unified Containerizer并通过默认的MesosContainerizer取代Docker Daemon,且原生支持多种镜像格式,这个改变比Kubernetes CRI要彻底的多。与此同时,CNI网络(Kubernetes社区采用的网络插件标准)也已经在Mesos项目上得以支持。

在未来,Mesos生态会重点完善自己在15-16年反应不及时所欠下的功能缺口,包括网络、存储、多租户、甚至Pod支持等重要的功能模块都已经有方案提交到了Roadmap。别忘了,Mesos生态(包括DC/OS)是目前(包括未来一段时间)容器圈子唯一支持大数据任务的基础设施依赖,是唯一有生产级别超大规模集群管理能力的资源管理框架,也是唯一原生提供界面的应用管理平台。这三个“唯一”在很多用户心目中(尤其是传统企业里),占有十足的分量。

曾经以Marathon+Mesos为代表的Mesos社区已经逐渐淡去,但围绕着DC/OS的新的Mesos生态终将亮剑,正所谓:“Mesos已死,Mesos永生”

国内外创业生态圈

如果说2016年的容器圈子仍然十分热闹,那必然少不了startup的繁荣。围绕容器运行时、编排、网络、存储、镜像管理、CI/CD、PaaS方案等的一系列生态环节所创立的startup,是推动这个大环境蓬勃发展的直接动力。

除了本身就是创业公司的Docker,容器圈的另一个主角当然是CoreOS。虽然创新能力十足,但CoreOS公司的rkt容器仍然没能在Docker的强势下占据容器市场的主流。由于单点突破受阻,2016年的CoreOS公司也做出了跟Docker公司类似的转型,开始向一个涵盖范围更广的“容器云”公司靠拢。除了一系列改名、扩展rkt的职能之外,还有一个重要的手段是:抛弃Fleet,拥抱Kubernetes。

Fleet曾经是CoreOS体系中重要的容器编排和调度项目,也曾同Mesos等项目一样在容器云领域占有一席之地。但现实证明,它并不足以让CoreOS在“云”的市场上争取到竞争优势。凭借Etcd在Kubernetes中的重要作用,CoreOS的工程师从性能调优作切入口进入了Kubernetes生态,并做出了显著的成绩,rkt也在CRI发布之前就成为了Kubernetes的可选容器运行时之一,kubelet则被内置到CoreOS Linux中作为默认的编排和调度框架。2016年,CoreOS又围绕Kubernetes发布了一系列工具如Operator来完善生态中的“有状态应用管理”,“存储管理”等能力,已经可以说是最成功的结合Kubernetes来创业的startup。

与CoreOS不同,国内的创业公司的发力点主要集中于提供容器服务的PaaS产品(也有人称之为CaaS以突出自己的容器特性)。相比于创业初期主要集中于容器管理平台的建设,2016年的国内容器创业公司则主要在围绕自己的平台构建生态类产品,涵盖了监控、存储、镜像监管、客服等一系列工具,其产品能力明显强于同类型的国外startup。另一个显著的变化是,2016年国内创业公司开始更多关注和宣传线下企业私有云市场的生意,创业初期着重推广的公有云服务的更新和维护力度明显降低。毕竟,在国内startup公有云盈利困难是一个不争的事实。

产品能力的异常强劲,侧面反衬出了国内startup在上游社区层面的影响力的弱势,在社区中推动和提出项目特性的能力依然欠缺。当然,创业维艰,尤其是国内大环境下,恐怕也只有华为能够在一边维护和推进runC等OCI项目的同时,一边在Kubernetes上开展完整的“联邦集群”特性(甚至将Google的负责人招致麾下)。还有Hyper,这只团队已经是Kubernetes CRI的重要维护者之一(CRI中很大部分代码正出自他们之手),同时也是runC运行时cri-o项目的维护者,已经在Kubernetes容器管理部分争取到了一席之地。而其本身维护的虚拟化容器HyperContainer项目和以此为基础的hyper.sh容器托管服务,则创下了占据HackerNews首页长达24小时的惊人记录。不过放眼全球市场,这些工作只能说是Hyper的本职,并不能掩盖国内团体在整个容器开源社区里弱势的现状。而这个现状的改变,以目前国内大多数startup的运营方式和核心能力点来看,恐怕还尚需时日。

2016年国内另一件新闻则是阿里云同Docker公司的牵手。这并非临时起意,早在2015年在国内容器创业氛围正值巅峰时,阿里云并没有直接进场,“而是在谨慎考察国内环境对容器的接纳程度”(此信息来自阿里云官方)。时至2016年,容器技术已经在国内红透半边天,作为后来者,体量又如阿里云这般的巨头要想再进场,必须要拿出最强势的姿态来踏出这第一步。同Docker公司展开官方合作,可以说是最佳选择。而对于Docker公司来说,Google和AWS已经成为直接竞争对手,放眼全球,阿里云即是拿得出手的一线厂商,又对Docker公司无毒无害,这次合作可谓一拍即合。只是从此国内其他公司再想拿“Docker官方”、“Docker Native”来做宣传,在这个排他性的合作面前,恐怕就需三思而后行了。

总结

2016年,容器技术圈子依然热闹非凡,容器社区里的弄潮儿们在“is dead”和“long live”的悖论里不断地自我否定和进化已成为这个社区所独有的常态。Docker公司“萌萌哒”的鲸鱼背后,一只野心勃勃的海洋霸主正蓄势待发;而作为容器编排管理领域的领导者,纵使有Google、Redhat等巨头的撑腰,Kubernetes恐怕也不会放慢脚步,通过CRI联合CNCF、OCI两大开源社区举措也在情理之中。容器技术的圈子里容不得懈怠,Mesos生态已经开始励精图治,意在凭借独有的能力拿回昔日的地位。我们不难发现,在Docker公司向平台领域发展的同时,Kubernetes、Mesos也同样渗透进了容器运行时的范畴。实际上,正如同那些支付宝与微信之争,几个大佬原生的核心能力恐怕才是它们取得今天成绩的关键所在。作为终端用户,理清自身真实需求之后,做出适合自己的选择其实并不太难。

容器技术圈子的繁荣,得益于现代开源软件社区的成功。Docker自不必说,Kubernetes、Tensorflow亦如是。连Google、Microsoft这样的巨头都一改对开发者傲慢的态度和轻视开源社区运营的作风而扎堆到GitHub上施展浑身解数,有幸同处一个时代而成为参与者和亲历者的我们又有何理由作壁上观呢。


作者: 张磊     来源:Infoq
原文链接

你可能感兴趣的:(ui,runtime,人工智能)