本人从事容器研究和容器化多年,从最初对容器的了解到积累了大量的容器迁移经验和给客户讲解容器技术之后,发现大家对容器的理解存在很多误区,容器并不是虚拟机的替代品,而是有非常具体的应用场景。***部分:容器的误区误区一:容器启动快,秒启动。容器启动快的原因是没有内核,镜像比较小。但是容器有一个主进程,就是Entrypoint。只有主进程完全启动,容器才能真正启动。一个比喻就是容器更像是人的衣服。人站起来,衣服也跟着竖起来,人躺下,衣服也跟着躺下。衣服有一定的隔离性,但隔离性没那么好。衣无根(芯),衣却可以随人去处。那么根据一个nginx来判断一个容器的启动速度有意义吗?对于Java应用,里面安装了tomcat,tomcat启动,加载war,真正的应用启动。如果你盯着tomcat的日志看,还是需要一些时间,根本不是秒级别的。如果应用启动需要一两分钟,那么只谈容器的秒级启动是没有意义的。现在OpenStack中VM的启动速度也优化得越来越快了。启动VM时,本来需要从Glance下载虚拟机??镜像。后来有一个技术,就是当Glance和系统盘共享Ceph存储的时候,不需要下载虚拟机??镜像,启动速度快很多。而容器启动快的原因是经常推荐使用非常小的镜像,比如alpine,里面裁掉了很多东西,所以启动速度更快。OpenStack的虚拟机镜像也可以裁剪很多,实现快速启动。我们可以精细测量虚拟机启动的每一步,将相应的模块和启动流程切出来,大大减少虚拟机的启动时间。比如在一个UnitedStack的博客https://www.ustack.com/blog/build-block-storage-service中,我们可以看到这样的实现和描述:“使用原生OpenStack创建虚拟机需要1~3分钟,使用修改后的OpenStack只需不到10秒的时间。这是因为nova-compute不再需要通过HTTP下载整个镜像,直接读取Ceph中的镜像数据即可启动虚拟机。”所以对于虚拟机整体的启动时间,如果优化的好,一般可以在十几秒到半分钟以内。和Tomcat的启动时间相比,这个时间其实不算是负担,而且是和容器的启动速度相比,相比之下,没有质的区别,可能有人会说启动速度更快,尤其是线上环境的挂机自修复,分秒必争不是吗?自我修复的问题,下面会讲到。不过,虚拟机的一个好处就是隔离性好。如果说容器是衣服,那么虚拟机就是房子。房子立在那里。不管人是不是里面是站着的还是躺着的,房子永远是站着的,房子不会跟着人。使用虚拟机就像人住公寓,一人一间,互补干扰,使用容器就像每个人都穿衣服挤在公交车里,看似孤立无援,谁把公交车撞坏了,谁都出不来。综上所述,容器的启动速度不足以构成相对于OpenStack虚拟机的明显优势,但虚拟机的隔离性秒杀了容器。误区二:容器是轻量级的,每台主机都会运行成百上千个容器。很多人会做实验,甚至跟客户说容器平台有多牛。你看,我们可以在一台机器上运行几十万个容器,这是虚拟机做不到的。但是一台机器跑几十万个容器,有这么真实的应用场景吗?对于容器来说,最重要的是里面的应用。应用的核心在于稳定性和高并发支持,而不在于密度。我遇到过很多知名的双十一和618打交道的讲师普遍反映现在的Java应用基本都是4核8G为标准配置。如果产能不够,小部分纵向扩张,大部分横向扩张。如果4核8G是标准配置。不到20个服务可以填满一个物理服务器。在一台机器上运行数百个nginx很有趣吗?这不是一个严肃的使用场景。当然,有一种非常流行的Serverless无服务器架构,在这种架构中,所有自定义代码都作为隔离的、自包含的、通常是细粒度的函数来编写和执行,这些函数在AWSLambda等无状态计算服务中运行。这些计算服务可以是虚拟机,也可以是容器。对于一个无状态的函数,需要快速的创建和删除,很可能执行一个函数的时间本身就很短。在这种情况下,容器还是比虚拟机好。有一定的优势。目前Serverless架构更适合运行一些基于任务的批处理操作,利用进程层面的横向弹性来抵消进程创建和销毁的大量成本。在spark和mesos的整合中,有一个Fine-Grained模式。不同于通常的大数据执行,任务的执行过程已经申请了资源,不同于在那里分配资源。这种模式在任务分配给时的好处是可以弹性申请和释放资源。缺点是进程的创建和销毁还是太细了,所以spark运行在这种模式下的性能会比较差。spark的思想类似于serverless架构。你会发现,我们在学习操作系统的时候,说进程粒度太大,每次都创建和销毁进程会太慢。为了实现高并发,我们后面有了线程。创建和销毁都轻量了很多,当然还是感觉慢,所以有一个线程池,是提前创建在那里的,用的时候不用现在创建,递过去就好了当你不使用它时回来。后来还是觉得慢,因为线程的创建也需要在内核中完成,所以后来有了协程,所有的线程切换都是在用户态进行的。例如,AKKA和Go都使用协程。你会发现现在是高并发的趋势,而且粒度越来越细。现在很多时候都需要流程层面,有一种风水轮流转的感觉。误区三:容器有镜像,可以维护版本号,可以升级回滚。容器有两个特点,一个是封装,一个是标准。有了容器镜像,就可以把应用的各种配置、文件路径、权限封装起来,然后像孙悟空一样说“确定”,封装完成的那一刻就定好了。镜像是标准的,无论在哪个容器运行环境,运行同一个镜像都可以还原当时的那一刻。容器的镜像也有一个版本号。我们可以根据容器的版本号进行升级。一旦升级出错,可以根据版本号回滚。回滚完成后,可以保证容器内部还是原来的状态。但是OpenStack虚拟机也是有镜像的,虚拟机镜像也可以做快照。快照时,会保存那一刻的所有状态,快照也可以有版本号,也可以升级回滚。看来OpenStack虚拟机具备了容器的这些特性。两者有什么区别?虚拟机镜像大,而容器镜像小。虚拟机镜像动辄几十G甚至上百G,而容器镜像动辄几百M,虚拟机镜像不适合跨环境迁移。比如开发环境在本地,测试环境在一个OpenStack上,开发环境在另一个OpenStack上。虚拟机镜像的迁移非常困难,需要复制非常大的文件。容器要好很多,因为镜像很小,可以快速从不同的环境迁移过来。虚拟机镜像不适合跨云迁移。目前还没有公有云平台支持虚拟机镜像的下载和上传(出于安全原因,盗版原因),所以不同云之间的镜像,或者直接在同一云的不同区域的镜像是无法迁移的,但是只能搬迁。做一个镜像,这样就无法保证环境的一致性。容器的镜像中心是独立于云端的。只要能连上镜像中心,在任何云端都可以下载。因为镜像小,下载速度快,而且镜像是分层的。每次只需要下载不同的部分。部分。OpenStack对镜像的优化基本上是在一个云中工作。一旦跨越多个环境,镜像就方便多了。误区四:容器可以利用容器平台管理自动重启实现自愈容器的自愈能力经常被吹捧。因为容器是一件衣服,当人躺下时,衣服也随之躺下。集装箱平台可以第一时间发现人是躺着的,所以可以快速叫醒人工作。而虚拟机就是房子,人躺着,房子还站着,所以虚拟机管理平台不知道里面的人能不能工作,所以容器挂了会自动重启,里面的应用虚拟机挂了,只要虚拟机不挂掉,很可能没人会知道。这些说法都是对的,但是人们逐渐发现了另一种场景,那就是容器中的应用程序并没有被挂起,所以容器看起来还在运行,但是应用程序在不工作的时候没有响应。当容器启动的时候,虽然容器的状态是up,但是里面的应用需要一定的时间才能提供服务。所以,对于这种场景,容器平台会为容器中的应用提供健康检查,不仅要检查容器是否存在,还要检查里面的应用是否可以使用。如果没有,它可以自动重启。一旦引入了健康检查,就和虚拟机没有太大区别了,因为有了健康检查,虚拟机也可以检查里面的应用程序是否正常工作,如果不正常就重启应用程序。还有就是容器的启动速度快,秒级启动。如果能自动重启修复,秒级修复,应用更高可用。这种看法当然是不正确的,应用的高可用与重启的速度没有直接关系。高可用性必须通过多副本来实现。其中任何一个挂掉后,都无法通过快速重启这个应用来解决。相反,其他副本应该在挂断期间立即接管任务来解决它。虚拟机和容器都可以有多个副本。在多副本的情况下,重启是1秒还是20秒就没那么重要了。重要的是挂机期间程序做了什么。如果程序是在做一些无关紧要的操作,那么挂掉20秒也无所谓。如果程序在做交易和支付,挂一秒也没关系,一定能恢复。因此,应用的高可用取决于应用层的重试和幂等性,而不是基础设施层重启的速度。对于无状态服务,如果有重试机制,自动重启修复是没有问题的,因为无状态服务不会保存很重要的操作。对于有状态服务,容器重启不仅不被推荐,而且可能是灾难的开始。服务具有状态,例如数据库。在高并发场景下,一旦挂掉,哪怕只是一秒,我们也要搞清楚这一秒到底发生了什么,哪些数据被保存了,哪些数据丢失了,而不是一味的重启,否则可能导致数据不一致,后期无法修复。比如高频交易下的数据库宕机了,说是DBA应该严格审查丢失了什么数据,而不是在DBA不知情的情况下盲目重启。DBA还是觉得什么都没发生,找了很久才发现问题。所以容器更适合部署无状态服务,可以随意重启。在容器中部署有状态容器也不是不可以,但是要非常小心,甚至不推荐。虽然很多容器平台都支持有状态容器,但是平台往往不能解决数据问题,除非你非常非常非常熟悉容器中的应用。当容器挂掉的时候,你可以准确的知道什么东西丢失了,什么东西重要,什么东西不重要,你需要编写代码来处理这些情况,然后才能支持重启。在网易数据库主备同步的情况下,修改了MySQL的源码,保证主备之间的数据完全同步,使得备在master宕机的时候可以自动切换master。推动有状态容器的自动重启,对于服务客户来说是非常不经济的,因为客户往往对应用的逻辑没有清晰的认识,甚至应用是买来的。当发现数据丢失时,仍然会责怪你。所以自动重启有状态服务也不是不可以,需要足够专业。误区五:容器可以使用容器平台进行服务发现。容器平台swarm、kubernetes、mesos都支持服务发现。当一个服务访问另一个服务时,会将服务名转换成VIP,然后访问具体的容器。但是,人们会发现,对于基于Java的应用,服务之间的调用大部分不会被容器平台的服务发现,而是被Dubbo或者springcloud的服务发现。因为容器平台层的服务发现还比较基础,基本上就是一个域名映射的过程,对于熔断、限流、降级等都没有很好的支持。不过既然用到了服务发现,还是希望服务发现中间件能够做到。此时容器平台很少用于服务间的服务发现,越是需要高并发的应用越多。容器平台的服务发现就没用了吗?不是的,慢慢你会发现内部服务发现是一方面,这些dubbo和springcloud都可以搞定,但是外部服务发现就不一样了,比如访问数据库,缓存等,是不是应该配置一个数据库服务名还是IP地址?如果使用IP地址的话,配置会很复杂,因为很多应用配置很复杂,是因为依赖太多的外部应用,也是最难管理的。方面。如果有外部服务发现,配置会简单很多,只需要配置外部服务的名称即可。如果外部服务的地址发生变化,可以灵活更改外部服务发现。误区六:容器可以根据镜像弹性伸缩在容器平台上,如果容器有多个副本,只要将副本数从5改为10,容器就会根据镜像进行弹性伸缩。其实虚拟机也可以做到这一点。AWS的自动缩放基于虚拟机映像。如果它们在同一个云中,则没有区别。当然,如果跨云无状态容器可以弹性伸缩,容器就方便多了,可以实现混合云模型。在高并发场景下,无状态容器不可能扩展到公有云,这对于虚拟机来说是不可能的。对容器的误解总结如图。左边就是常说的所谓容器的优势,但是虚拟机可以一个一个往回走。如果部署的是传统应用,应用启动慢,进程数少,基本不更新,那么虚拟机完全可以满足要求。应用启动慢:应用启动需要15分钟,容器本身需要几秒。很多平台的虚拟机都可以优化到十几秒。基本无更新:每六个月更新一次,虚拟机镜像还是可以升级回滚的无法恢复,也可能是数据丢失,不修复,一味重启会造成数据混乱。进程数少:两个或三个进程相互配置,不需要服务发现,配置不麻烦。如果是传统应用,没必要在容器化上花费精力,因为白费力气,享受不到好处。第2部分:容器化、微服务和DevOps三位一体。什么情况下我们应该考虑做一些改变?传统业务突然受到互联网业务的冲击,应用总是在变化,需要三天更新一次,流量增加以前支付系统是提款刷卡,现在需要互联网支付,并且流量增加了N倍。没办法,一句话:拆机,各个子模块独立改动,相互影响较小。拆开之后,以前是一个进程承载流量,现在是多个进程一起承载。这就是为什么它被称为微服务。微服务场景,进程多,更新快,所以每天有100个进程,一张镜像。容器开心,每个容器镜像很小,没有问题,虚拟机哭了,因为虚拟机的每个镜像都太大了。所以在微服务场景下,可以开始考虑使用容器。虚拟机生气了,我也不要容器了。微服务拆分后,用Ansible自动部署也是一样的。所以从技术角度来说是没有问题的。然而,问题是从组织的角度出现的。一般公司,开发多于运维。开发和编写代码后,您无需担心。环境的部署完全由运维负责。为了自动化运维,写Ansible脚本解决问题。但是拆合并的进程那么多,更新又那么快,配置一直在变,而且Ansible脚本要经常改,天天上线,所以运维肯定不行筋疲力尽的。所以在这么大的工作量下,运维很容易出错,即使是通过自动化脚本。这时候,容器就可以作为一个非常好的工具。除了容器的技术角度,大部分内部配置都可以放在镜像中,更重要的是,从流程的角度,把环境配置的事情往前推,推到开发上。开发完成后,需要,需要考虑环境部署的问题,不能做掌柜。这样做的好处是虽然流程多,配置变化多,更新频繁,但是对于某个模块的开发团队来说,这个量是很小的,因为5-10个人专门负责维护这个模块的配置和更新模块。容易出错。如果把这些工作量全部交给少数运维团队,不仅信息传递会造成环境配置不一致,部署量也会非常大。容器是一个非常好的工具,它让每个开发只多做5%的工作,可以节省200%的运维工作,而且更不容易出错。但是,原来运维该做的事情,都发展出来了。开发老大愿意吗?开发老大有没有向运维老大抱怨?这不是技术问题。其实这就是DevOps。DevOps不区分开发和运维,但是公司可以打通从组织到流程,看怎么合作,怎么划分边界,这样更有利于系统的稳定。所以微服务、DevOps和容器是相辅相成、密不可分的。不是微服务,根本不需要容器,用虚拟机就可以,不需要DevOps,一年部署一次,再慢的开发和运维沟通也可以做完了。所以,容器的本质是基于镜像的跨环境迁移。镜像是容器的根本发明,也是打包运行的标准。其他名称空间和cgroup早已存在。这是技术层面。从流程上来说,镜像是DevOps的好工具。容器是为了跨环境迁移。第一个迁移场景是开发、测试和生产环境之间的迁移。如果不需要迁移,或者迁移不频繁,虚拟机镜像还好,但总要迁移,几百G的虚拟机镜像太大了。第二种迁移场景是跨云迁移。跨公有云,跨Region,或者跨两个OpenStack虚拟机迁移是非常麻烦的,甚至是不可能的,因为公有云不提供下载和上传虚拟机镜像的功能。而且虚拟机镜像太大,传了一天。因此,在跨云场景和混合云场景中,容器也是很好的使用场景。这也解决了私有云资源不足,无法同时处理流量的问题。第三部分:容器的正确使用场景基于以上分析,我们发现容器推荐在以下场景使用。部署无状态服务并使用它们与虚拟机互补以实现隔离。如果你想部署有状态的服务,你需要对里面的应用程序有很好的了解。作为持续集成的重要工具,您可以在开发、测试和生产之间平滑迁移。适用于跨云部署、跨地域、跨数据中心,混合云场景下的应用部署和弹性伸缩,以容器作为应用交付物,保持环境一致性,建立不可更改的基础设施理念。运行进程和基本任务类型的程序用于管理变更,频繁变更的应用使用容器镜像和版本号,轻巧便捷的多用途容器必须管理好应用,并进行健康检查和容错设计
