附PDF下载:《迁移到原生云应用架构》第二部分
迁移到原生云应用架构
马特.斯泰恩 著
KVM云技术社区/翻译组出品
现在是云计算时代,云计算不仅是一种IT资源的使用方式,并且已经演变成一种生态,它给软件架构设计也带来深刻影响,微服务的设计就是为了服务云时代。如何基于云做软件架构设计,或者如何让自己原来的软件架构能迁移到云上,本书给出了原理和初步的方法,相信能给读者带来一些启示。
本书是KVM云技术社区翻译小组两个多月的结晶,特别感谢以下几位KVM云技术社区金牌翻译者:
雷通 QQ:824517369,完成了第一章部分内容的翻译
武楠 华讯网络顾问工程师 QQ:963209770,完成了第一章的翻译
何涛涛 平安科技开发工程师 QQ:1849649074,完成了第二章的翻译
贾文杰 甲骨文中国 QQ:1141913371,完成了第三章前半部分的翻译
韩卫 海康威视虚拟化工程师 QQ:499349274,完成了第三章后半部分的翻译
另外,肖力完成了本书的校对和排版工作。
欢迎大家关注KVM云计算社区订阅号“KVM虚拟化实践”,分享在云计算/虚拟化项目实施中的资讯、经验、技术,坚持干货、原创,请大家扫描二维码关注:
翻译的过程是译者和作者思想沟通的过程,也是一个学习的过程,中间充满艰辛,也充满快乐,欢迎大家加入KVM云技术社区翻译小组,一起交流、学习、提高,加入请联系群主(群主微信:xiaoli173702,群穿越使者北极熊微信:hadxiaer)。
关于本书错误和相关讨论,也请联系群主或者北极熊,加相关的微信群一起讨论。
KVM云技术社区翻译小组
2016年5月23日
目录
译者序
第一章原生云的崛起
为什么是原生态云应用架构?
定义原生云架构
总结
第二章变革是当务之急
文化变革
从信息孤岛(Silos)到DevOps
从间断平衡到持续交付
从集中管理到分散自治
结构变革
业务能力团队
平台运维团队
技术转变
分解巨石
分解数据
容器化
从管弦乐编曲到舞蹈编排
总结
第三章迁移手册
分解方法
新功能即微服务
隔离层
扼杀巨石(整体架构)
潜在的结束状态
分布式系统方法
版本和分布式配置
服务注册/发现
路由与负载均衡
容错
API网关/边缘服务
总结
迁移手册
现在,我们已经定义了云原生应用架构和给予了变革一个简短的高度概述,当企业必须采用它的时候,就需要来研究技术细节了。每个主题都应有其自己的章节,而这超出了本书的范围。不过,本章提供了一组简短的操作手册,随着深入阅读,帮助理解采取云原生应用的体系结构的具体任务和模式。
在讨论数据,服务和客户团队的分解后,经常有人问我,“太好了!我们如何从这里到达那里?“问得好。我们如何撕裂现有整体架构,并将其移动到云?
事实证明,我已经看到了我推荐给我所有的客户使用的一种相当于重复增量迁移的模式并取得了成功。公开这种模式的可参考的例子可以在的SoundCloud和Karma中找到。在本节中,我们将逐步讲解一系列提供分解整体服务并将它们转移到云的过程的方法。
即微服务
令人惊奇的是,第一个步骤不是凿碎整体架构本身。我们依然假设你有使用整体架构的惯性。事实上,如果你没有任何高级的新的功能来构建,你甚至不应该考虑这种分解,这是值得商榷的。(鉴于我们的主要动机是速度,你怎么快速分开那些不变的东东?)
...团队决定,最好的办法来处理架构的变化不应立即分裂整体,而是给不给它添加任
何新的东西。我们所有的新功能被建成微服务...
—Phil Calcado, SoundCloud
因此现在是时候停止向整体架构添加新的代码了。所有新的特性将被建成为微服务。我们必须获得这个良好的开始,因为从Scratch(是一款由麻省理工学院(MIT) 设计开发的一款面向少年的简易编程工具)构建新的服务比外科手术式的从中提取它们要容易得多。
然而,不可避免的,这些新的微服务的完成就需要与整体架构进行沟通。我们如何解决这个问题呢?
由于这么多我们的逻辑仍然是在Railsmonolith,在某种程度上几乎所有的微服务不得不谈它。
—Phi Calcado, SoundCloud
由Eric Evans(Addison-Wesley)发明的领域驱动设计(DDD),讨论了隔离层的思想。其目的是为了让两个系统整合,不允许一个系统的域模型来破坏对方的领域模型。当你向微服务建立新的功能时,你不需要这些新的服务对整体架构作深入了解,从而变成紧耦合。隔离层是一种创建API约定,使整体架构看起来像其他微服务的方式。
埃文斯把隔离层的实现分为三个子模块,前两个代表经典的设计模式(引用自Gamma 等人的著作《设计模式:可复用面向对象软件的基础》[Addison Wesley出版社出版]):
表现层
这里的表现层模块的目的是简化与整体架构的界面集成的过程。很可能是整体架构并未设计这种类型的集成,所以表现层的目的就是为了解决这个问题。重要的是,它并没有改变整体架构的模型,小心不要耦合翻译和集成的关系。
应用层
应用层是我们定义的“服务”并由其提供我们新特性需要的东西。它知道如何从我们系统中用它理解的协议获得一个请求,并使该请求到整体架构的表现层。
转换层
转换层的责任是对整体架构的域模型和新的微服务的域模型之间的请求和响应进行转换。
这三个松散耦合的组件解决三个问题:
1.系统集成
2.协议转换
3.模型转换
剩下的是在通信链路的定位。在DDD,埃文斯讨论了两个备选方案。第一,表现层到系统,它的主要用途是当您无法访问或更改原有系统的时候。在这里我们的重点是我们控制的整体架构,所以我们倾向于埃文斯的第二个建议,应用层到表现层。使用这种替代方案,我们所建立的表现层到整体,允许应用层和表现层之间发生通信,因为据推测这是更容易地创建用于此目的两件事情之间的联系。
最后,需要注意的是隔离层可以促进双向沟通,这是非常重要的。正如我们新的微服务可能需要与整体架构沟通来完成工作,反之也是如此,特别是当我们进入到我们的下一个阶段的时候。
(整体架构)
在对该架构进行了更改后,我们的团队可以自由打造他们的新功能和增强功能的一个更灵活的环境,一个重要的问题依然存在,我们如何从整体的应用中提取单一的特性?
—PhilCalcado, SoundCloud
我从Martin Fowler的文章--题目是“更健壮的应用”的理念中借用了“扼杀整体架构”这个想法。在这篇文章中,福勒解释了逐渐形成“在旧系统的边缘创建一个新的系统,让它慢慢成长好几年,直到旧的系统被扼杀“的理念。我们将要在这里做同样的事情。通过提取微服务和附加的隔离层的组合,我们将围绕现有的整体架构边缘建立一个新的云原生系统。
两个标准帮助我们选择提取组件:
1.SoundCloud揭露了第一个标准:识别整体架构内的边界上下文。如果你还记得之前我们关于边界上下文的讨论,就会知道他们需要一个内部一致性的域模型。但我们整体架构的域模型不是内部一致的情况是非常可能发生的。现在是时候开始确定子模型了。这些都是我们提取的候选人。
2.我们的第二个标准是处理优先级:在我们的候选功能中,我们是否先提取?我们可以通过查看我们的移动云原生架构第一个理由来回答这个问题:创新的速度。什么候选微服务将受益于创新的速度?很明显,我们要选择的是那些正在改变最有助于我们当前的业务需求。看巨石(整体架构)的backlog。识别那些为了交付改变需求而需要改变的巨石(整体架构)代码的区域,然后在做出希望的改变之前提取相应的有界上下文。
我们怎么知道我们什么时候完成?基本上有两种结束状态:
1.整体架构已经完全被扼杀。所有边界上下文已经被提取到微服务。最后一步是识别机会,以消除那些不再需要的隔离层。
2.整体架构已经被扼杀到附加服务提取的代价超过必要的发展努力的回报点。在整体架构的某些部分可能是相当稳定的,这些部分已经运行多年。迁移这些部分没有多大的价值,保持必要的隔离层与之集成的成本可能足够低,以至于我们可以把它当做长期运行来考量。
当我们开始构建以微服务组成的分布式系统作为我们将要开发的整体架构时,我们通常不会遇到非功能性需求。有时采取物理定律的方式就可以解决这些问题,如一致性,延迟和网络分区的方式。然而,脆弱性和可管理性的问题通常可以通过合适的相当通用的样板模式的应用程序得以说明,在本节中,我们将通过研究方法来帮助我们理解这些关注。
这些方法是从Spring Cloud项目和Netflix OSS家族的项目组合绘制而来。
我们在第9页的“十二因素应用”中讨论了指定通过操作系统级的环境变量配置对应注入配置的正确配置管理的重要性,这种方法非常适合于简单的系统,但是,当扩展到更大的系统时,有时我们需要附加的配置功能:
为了诊断生产事件而改变应用程序的日志记录级别
从一个消息代理改变接收消息的线程数
报告生产系统所做的所有配置更改,以支持审计监管
正在运行的应用程序的切换功能的开/关
保护嵌入在应用程序中的配置机密(如密码)
为了支持这些功能,我们需要具有以下特性的配置管理方法:
Versioning(版本控制)
Auditability(可审核性)
Encryption(加密)
Refreshwithout restart(在线刷新)
Spring Cloud项目中包含的一个可提供这些功能的配置服务器。此配置服务器通过Git本地仓库(Repository)支持的REST API呈现了应用程序及应用程序配置文件(例如:可被开/关切换的一组配置作为一组,如“开发”或“分段”配置)(图3 -1)
图3-1 Spring Cloud配置服务器
下面例子是一个样本配置服务器(例3-1)的默认应用程序配置文件:
例3-1. 样本配置服务器的默认应用程序配置文件
{
"label":"",
"name":"default", "propertySources": [
{
"name":"https://github.com/mstine/config-repo.git/application.yml",
"source": { "greeting":"ohai"
}
}
]
}
这一配置在指定的后备Git仓库文件application.yml支持。
greeting 当前被设定为:ohai.
例3-1的配置是自动生成,并没有手动编码。我们可以看到,greeting的值正在通过检查它/ env的终端被分发到Spring应用程序(例3-2)。
例3-2 环境配置服务器的客户端
"configService:https://github.com/mstine/config-repo.git/application.yml":{
"greeting":"ohai"
},
这个应用程序正在接收来自配置服务器的greeting的值:ohai.
所有这一切仍然是我们能够在不重新启动客户端应用程序的情况下更新greeting的值。这种功能是另一个被称为Spring Cloud BUS 的Spring cloud项目模板提供,这个项目使用轻量级的消息代理连接一个分布式文件系统的接点,然后可以用来广播正如我们期望的配置改变的状态变化。
简单地对参与此总线的任何应用程序终端执行一个HTTP POST(对/bus/refresh)(这当然应该有适当的安全守卫)指令,我们就可以从配置服务器指示总线上的所有应用程序刷新其最新的可用配置。
图3-2 Spring Cloud总线
随着分布式系统的创建,代码的调用不再是依赖一个方法了。相反,为了消耗它们,我们必须进行网络调用。如何执行必要的布线,以允许组成的系统内的所有的微服务的相互沟通?
在云中(图3-3)的通用架构模式是具有前端(应用程序)和后端(业务)服务。后端服务往往不能直接从互联网访问,而是通过前端服务访问。服务注册提供的所有服务的列表,使他们可以通过一个客户端库到达前端服务(第35页的“路由和负载均衡”),客户端库执行负载均衡和路由到后端服务。
图3-3服务注册与发现
我们使用了服务定位器和依赖注入模式的不同化身之前已经解决了这个问题,面向服务的架构早已采用了各种形式的服务注册。我们将通过利用Eureka,这是一个Netflix的OSS项目,采用此类似的解决方案可用于实现负载均衡和中间层服务故障的目的定位服务。Eureka的消费是通过Spring Cloud Netflix的云计算项目,它提供了一个主要基于注解的配置模型消费Netflix的OSS服务的进一步简化。
一个应用程序充分利用Spring Boot可简单地通过增加@EnableDiscovery客户端注释来参与服务注册和发现(例3-3)。
例3-3服务注册SpringBoot应用程序/启用发现
@SpringBootApplication
@EnableDiscoveryClient
public classApplication{
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
@EnableDiscoveryClient开启了应用程序的服务注册/发现功能
该应用程序就能够通过利用DiscoveryClient与它的依赖组建通信。实例3-4是应用程序查找名为PRODUCER的注册服务的一个实例,获得其URL,然后利用Spring’s RestTemplate与之通信。
例3-4使用DiscoveryClient找到一个生产服务
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping("/")
public String consume() {
InstanceInfo instance= discoveryClient.getNextServerFromEureka("PRODUCER", false);
RestTemplaterestTemplate =newRestTemplate();
ProducerResponseresponse =restTemplate.getForOb ject(instance.getHomePageUrl(),ProducerResponse.class);
return "{\"value\":\"" + response.getValue() + "\"}";
}
开启的DiscoveryClient被Spring注入
使用getNextServerFromEureka方法提供了使用循环算法的服务实例的位置
基本的循环负载均衡对很多场景都是有效的,但是在云环境中的分布式系统往往要求更先进的一套路由和负载均衡的方法。这些通常是由各种外部、集中负载均衡的解决方案提供的。然而,我们经常遇到的是,这样的解决方案对于给定应用不具有足够的信息或上下文做出最佳的选择,因为它试图用它的依赖性进行通信。此外,假如这样的外部解决方案失败,这些失败可以导致级联整个架构出现失败。
云本地解决方案往往转移用于对客户端使用路由和负载均衡的解决方案负责。一个这样的客户端解决方案是Ribbon Netflix OSS项目(图3-4)。
图3-4 Ribbon 客户端负载均衡
Ribbon提供了一套丰富的功能,包括
•多个内置的负载平衡规则:
- Round-robin 简单轮询负载均衡
- Average response-time weighted平均加权响应时间负载均衡
- Random 随机负载均衡
- Availability filtered可用性过滤负载均衡(避免跳闸线路或高并发连接数)
•自定义负载均衡规则的插件系统
•与服务发现解决方案可插拔集成(包括Eureka)
•智能云应用,比如域亲和力(zone affinity)和不健康域躲避(unhealthy zone avoidance)
•内置故障恢复能力
至于Eureka,SpringCloud Netflix项目大大简化了Ribbon的Spring应用开发。开发人员可以注入负载均衡器客户端的一个实例,然后用它来解决一个应用程序依赖(例3-5)的实例,而不是注入一个DiscoveryClient实例(直接使用Eureka)。
例3-5 使用LoadBalancerClient启动一个productor服务
@Autowired
LoadBalancerClient loadBalancer;
@RequestMapping("/")public String consume() {
ServiceInstanceinstance =loadBalancer.choose("producer");
URI producerUri = URI.create("http://${instance.host}:$
{instance.port}");
RestTemplaterestTemplate =newRestTemplate();
ProducerResponseresponse =restTemplate.getForObject(producer Uri, ProducerResponse.class);
return "{\"value\":\"" + response.getValue() + "\"}"; }
开启的LoadBalancerClient被Spring注入
选择的方法提供了一个服务实例,该实例使用了当前的负载均衡算法。
Spring Cloud Netflix通过创建一个可以注入到beans的RestTemplate bean,大大减少了Ribbon的功耗。这个RestTemplate实例可以配置,用来自动转换本地逻辑服务名称实例,转换为使用Ribbon(例3-6)的实例URI。
例3-6 使用Ribbon-enabledRestTemplate
@Autowired
RestTemplate restTemplate;
@RequestMapping("/")public String consume() {
ProducerResponseresponse =restTemplate.getForObject("http:// producer", ProducerResponse.class);
return "{\"value\":\"" + response.getValue() + "\"}"; }
RestTemplate被注入,而不是一个 LoadBalancerClient
注入的RestTemplate自动生成http://producer为一个实际的服务实例URI
分布式系统比集中式有更多的潜在失效模式。因为每个到来的请求经过数十甚至上百个不同的微服务,一些故障需要一个或多个依赖被实质地保证。
如果不采取措施确保容错,30个依赖,每个99.99%的正常运行时间将导致每个月2个小时以上的停机时间(99.99%^30^=99.7%的正常运行时间=每个月2个小时以上停机时间)。
—BenChristensen, Netflix 工程师
如何防止此类故障的产生级联故障,给我们带来不可用数据?Mike Nygard在他的书(Pragmatic Programmers)中记载了有助于一些能够解决的模式,其中包括:
熔断器
当一个服务被诊断为不健康,熔断器通过阻止远程调用来隔离服务,就好比电路中的熔断器在过度使用电力时会跳闸。熔断器作为状态机工作(图3-5)。当处于闭合状态,调用会简单地通过并发送到依赖组件。如果有部分调用失败,这些会被计数。当计数达到一个指定的阈值时,熔断器跳闸到打开状态。处于打开状态,调用总是直接失败。在预定周期之后,电路转换到半开状态。此种状态下,调用再次尝试远程依赖组件。成功调用则将熔断器转到闭合状态,而失败调用将熔断器带回打开状态。
图3-5一种熔断器状态机
舱壁
舱壁划分了一个服务用于限制错误,以及避免整个服务因区域故障陷入而瘫痪。它们被命名为分区(partition),它们可以被密封,把船分割成多个防水舱。这样能够保护整艘船避免下沉的危险(例如,由鱼雷命中引起)。软件系统可以有多种方法使用舱壁模式。简单地划分微服务是我们的第一道防线。应用程序进程运行在Linux容器(第26页的“容器化”),这样一个进程无法接管整个机器。另一个例子是工作在不同的线程池的并行化分工。
Netflix 为容错机制提供了一个强大的库,在运用了这些模式的Hystrix里。为了包含熔断器的代码,Hystrix允许代码被包含到HystrixCommand对象中。
例3-7 使用HystrixCommand对象
public classCommandHelloWorldextendsHystrixCommand<String> {
privatefinalString name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name; }
@Override protected Stringrun() { return "Hello" + name+ "!";
}
}
运行中的代码包含了熔断器。
Spring Cloud Netflix添加了一个@EnableCircuitBreaker注释,在spring boot应用中启用Hystrix runtime 组件。然后他利用了一套开源注释,用spring和Hystrix编程,像我们描述的早期集成一样容易(例 3-8)。
例 3-8 使用@HystrixCommand
@Autowired
RestTemplate restTemplate;
@HystrixCommand(fallbackMethod = "getProducerFallback")
public ProducerResponse getProducerResponse() {
returnrestTemplate.getForObject("http://producer", ProducerRes ponse.class); }
public ProducerResponse getProducerFallback() {
return new ProducerResponse(42); }
这个使用@HystrixCommand注释的方法包含了一个熔断器。
当电路处于打开或半开的状态,方法getProducerFallback被注释引用,执行了一个优美的回调动作。
Hystrix区别于许多其他熔断器的实现,因为它采用舱壁模式,通过使用自己的线程池操作每个熔断器。它也收集许多有用的关于熔断器状态的指标,包括:
流量
请求率
错误的百分比
主机报告
延迟百分点
成功,失败和拒绝
这些指标会作为事件流被发送,然后被另一个叫做Turbine的Netflix OSS项目汇总。单个或汇总的指标流,可以使用强大的Hystrix dashboard(图3-6)查看,它在分布式系统的整体运行状况上提供了优良的可视性界面。
图3-6Hystrix dashboard 显示三套熔断器指标
网关/边缘服务
在第8页的“移动应用程序和客户端的多样化”我们讨论了服务器端聚集和微服务组成的生态系统改造的想法。为什么有这个必要?
延迟
移动设备通常运行在比我们家庭设备更低速的网络上。即使在我们的家庭或企业网络上,为了满足单个应用屏幕的需求,这都需要连接数十(或者上百?)个微服务,减少延迟到不可接受的水平。并发访问这些服务的需求很快变得清晰起来。在服务端一次性捕获和实行这些并发模式,会比在每一个设备平台上做相同的事情,来得更廉价、更不容易出错。
延迟的另一个来源是响应大小。Web服务的发展,近年来一直呈向“回归一切你有可能需要”的做法,导致更大的响应有效载荷,对满足单一的移动设备屏幕的需求是有必要的。移动设备开发者更倾向于通过仅检索必要的信息而忽略其他不重要的信息,来减少等待时间。
往返通信(Round trips)
即使网络速度不是问题,大量微服务的通信仍然会造成对移动开发者造成困扰。网络的使用是这些设备电池寿命的主要消费者之一。移动开发者尽可能使用最少的服务端调用来减少网络使用,达到所需的用户体验。
设备多样性
移动设备生态系统内的多样性是巨大的。企业必须与在其客户群的差异日渐增多,包括不同的处理:
•制造商
设备类型
形式因素
设备尺寸编程语言
操作系统
运行环境并发模型
支持的网络协议
这种多样性扩大甚至超出了移动设备生态系统,因为开发者目前在关注家庭消费电子设备不断增长的生态系统,包括智能电视和机顶盒。
API网关模式(图3-7)是旨在把这些需求的负担从设备开发者转移到服务器端。 API网关仅仅是一类特殊的满足单个客户端应用程序的微服务(如特定的iPhone app),并为其提供一个单一入口点到后端。他们携带每个请求同时访问数十(或数百)个微服务,汇总响应并转化,以满足客户应用的需求。在必要时,他们还进行协议转换(例如,HTTP到AMQP)。
图3-7 API网管模型
API网关可以使用任何支持web编程和并发模式的语言、runtime、或框架,和与目标微服务进行通信的协议来实现。热门的选择包括Node.js的(由于其反应式编程模型)和GO编程语言(由于其简单的并发模型)。
在这次讨论中,我们将坚持使用Java,并给出一个RxJava例子,一个Netflix开发的Reactive Extensions的JVM实现例子。仅使用Java语言所提供的原函数,组成多个工作或数据流是一个挑战,而RxJava是一种致力于缓解这种复杂性的技术(还包括Reactor)技术之一。
在这个例子中,我们正在创建一个类似Netflix的站点,展现视频目录给用户,并为这些视频的评分和评价。此外,当查看特定标题的时候,如果他们喜欢的标题当前被查看,网站会提供建议给这些视频的观看者。为了提供这些功能,三个微服务需要被开发出来:
一个服务目录
一个评论服务
一个建议服务
这个服务的移动应用的响应应该像例3-9一样。
例3-9 视频详情响应
{
"mlId": "1",
"recommendations": [
{
"mlId": "2",
"title": "GoldenEye(1995)"
}
],
"reviews":[
{
"mlId": "1",
"rating": 5,
"review": "Greatmovie!",
"title": "Toy Story(1995)",
"userName":"mstine"
}
],
"title":"Toy Story (1995)"
}
例3-10中找到的代码利用RxJava的Observable.zip方法同时访问的每一项服务。接收到三个响应后,代码将它们传递到Java8LAMBDA,使用它们来创建一个MovieDetails实例。这个实例可以被序列化来生成例3-9中的响应。
例 3-10 同时访问三个服务,汇总回复
Observable<MovieDetails> details = Observable.zip(
catalogIntegrationService.getMovie(mlId), reviewsIntegrationService.reviewsFor(mlId), recommendationsIntegrationService.getRecommendations(mlId),
(movie, reviews, recommendations)-> {
MovieDetailsmovieDetails =newMovieDetails(); movieDetails.setMlId(movie.getMlId()); movieDetails.setTitle(movie.getTitle()); movieDetails.setReviews(reviews); movieDetails.setRecommendations(recommendations);
return movieDetails;
}
);
本实施例几乎没有涉及RxJava的可用功能,读者可以在RxJava的维基上进一步研究该库。
在本章中,我们通过两种方法,来帮助走近cloud-native云应用架构。
分解方法
我们通过以下方法分解单一应用:
构建所有新特性作为微服务
集成新的微服务
通过区分边界上下文和提取服务分解
分布式系统
通过配置服务器和管理总线,进行版本控制,分发,以及更新配置。
自动发现远程依赖
分散负载均衡策略
通过熔断器和舱壁模式防止连锁故障
通过API网关集成到特定客户端的实例上
许多额外的帮助性的模式存在,包括那些用于自动化测试和持续交付的实施。欲了解更多信息,请读者阅读Toby Clemson的《Testing Strategies in aMicroservice Architecture》(微服务架构中的测试策略),以及Jez Humbl和David Farley(AddisonWesley)的《ContinuousDelivery: Reliable Software Releases through Build, Test, and DeploymentAutomation》(持续交付:通过构建,测试和部署自动化发布可靠软件) 。
本文PDF版下载链接:
http://pan.baidu.com/s/1jH7TKUE
KVM社区QQ群,99.99%纯技术交流气氛
QQ 2群:131961942,加入密码大写KVM
千人VMWare技术交流群:494084329,加入密码小写vm
OpenNebula QQ群:495571573 加入密码Nebula
OpenStack开发纯技术群: 334605713 加入密码nova
Cloudstack纯技术交流群:515249455密码cs
桌面云行业讨论: 484979056 加入密码大写VDI