赞
踩
Heat的出现,从个人的观点来看,具有非常重大的意义,那就是,将云应用“编程”的门槛,降低到一个新的“低度”,同时提供了许多非常实用的功能,而且,每半年发布的新版本,总是在朝着实用一小步,一小步地迈进…许多的功能在新的版本加进来,愈来愈趋于完善。基于此,编写大型而复杂的各种云平台应用系统,将不再是个日久天长的事情。它的明天的辉煌,让我们拭目以待。
那么,heat具有什么重大意义呢?
许多人在前后的IT码农经历中,都多少接触过脚本语言,如PHP, Perl, Python,免去了高级编程语言的编译、调试,我们都记得C/C++的指针和Java的内存泄漏给我们带来了多少不眠的夜晚。而脚本语言如果出错了,修改下代码,在测试环境甚至是生产环境中再跑一遍(当然不是推荐这种做法)。OpenStack Heat一如此,或者更贴近javascript, 或者早期的java tag library, 许多功能都可以经由这样一些简单的“类型”+“属性”来实现,避免了许多代码的开发和调试。
让我们看下为什么这样说:
"WebServer": {
"Type":"AWS::EC2::Instance",
"DependsOn":"DatabaseServer",
"Metadata": {
"AWS::CloudFormation::Init" : {
"config" : {
"packages" : {
"yum" : {
"httpd" : [],
"wordpress" : []
}
},
"services" : {
"systemd" : {
"httpd" : { "enabled" : "true","ensureRunning" : "true" }
}
}
}
}
},
"Properties": {
"ImageId": { "Fn::FindInMap" : [ "DistroArch2AMI", { "Ref": "LinuxDistribution" },
{"Fn::FindInMap" : [ "AWSInstanceType2Arch", {"Ref" :
"InstanceType" },"Arch" ] } ] },
"InstanceType" : { "Ref" :"InstanceType" },
"KeyName" : { "Ref" :"KeyName" },
"UserData" : { "Fn::Base64" : {"Fn::Join" : ["", [
"#!/bin/bash -v\n",
"/opt/aws/bin/cfn-init\n",
"sed -i \"/Deny from All/d\"/etc/httpd/conf.d/wordpress.conf\n",
"sed -i \"s/Require local/Requireall granted/\" /etc/httpd/conf.d/wordpress.conf\n",
"sed --in-place --es/database_name_here/", { "Ref" : "DBName" }, "/--e
s/username_here/", {"Ref" : "DBUsername" }, "/ --e s/password_here/",{ "Ref" :
"DBPassword" }, "/--e s/localhost/", { "Fn::GetAtt" : ["DatabaseServer",
"PublicIp" ]}, "//usr/share/wordpress/wp-config.php\n",
"systemctl restarthttpd.service\n"
]]}}
}
}
这段代码讲了什么呢?让我们简要地分析下代码:这段代码将创建一个新的虚拟机,安装软件,并在虚拟机内部运行脚本,做一些配置。其中的properties一段,首先指出了这个虚拟机创建需要的镜像。镜像是创建虚拟机大部分时候需要的(当然还可以通过clone, volume等方式创建一个新的虚拟机),然后是创建虚拟机需要的主机类型,即所需要的资源的规格参数。另外,创建的虚拟机需要能访问,这里提供了一个公私钥的名字,用户可以通过公私钥对,来访问创建的虚拟机。当然,现在所支持的Heat语言不是万能的,在办不到的时候呢,还可以执行任何真正的脚本,这里用一段shell脚本,来初始化wordpress的一些配置。其中,当需要引用当时环境里的值时,还可以用特定的语法,如”Ref”, “Fn::GetAtt”等来取得。这是不是几乎可以直接对应到Dashboard创建虚拟机那个界面需要填写的参数呢?当然有人会说,Dashboard里,还可以选择网络,没错,这里也可以配置网络,只是这里没写而已,自有其如上述类似的语法可以支持。
那么前半部分呢?这里Type指出,我们需要创建一个Instance, Instance在不同语境中有不同含义,这里是指一个虚拟机,另外我们如果看到Server,那也是指虚拟机。这段代码意思是在虚拟机里装两个软件,另外,起几个服务。就这么简单。我并不精通Python,也不熟悉OpenStack API。但我也可以“编程”调用OpenStack了。
为什么说Heat是一个类似于脚本编程的语言呢?首先并不是要将它映射到具体某个编程的脚本语言而去,而是通用的,我们所使用的脚本语言,并没有如import, for, while, if等语句,没有看到许多各种类型和变量定义。我们在一个更高的高度,用一种特殊的语法,努力编写我们的应用。
让我们看下古老的java tag library的一个例子:
<c:set var="bookId"value="${param.Remove}"/>
<jsp:useBean id="bookId"type="java.lang.String" />
<% cart.remove(bookId); %>
<sql:query var="books"
dataSource="${applicationScope.bookDS}">
select * from PUBLIC.books where id = ?
<sql:param value="${bookId}" />
</sql:query>
上面这段代码是在页面里,通过调用sql语句来抽取数据,做页面显示。想想看,如果没有这种语法,那如何实现类似功能?如果自己从头到尾自己写,该写多少代码?测试多长时间呢?
<table>
<tr>
<th>Firstname</th>
<th>Lastname</th>
</tr>
<tr>
<td>Peter</td>
<td>Griffin</td>
</tr>
<tr>
<td>Lois</td>
<td>Griffin</td>
</tr>
</table>
上面例子是用HTML在页面画一个table。语法大家都看得懂。
那么脚本语言是什么?不是具体某种具体的语法,而是更高层的封装、更简洁的语法、更高效的开发、更多人的上手、更关注应用本身。
相信这点,没有多少人可以否定。同样的事情,可以直接调用OpenStack API来实现,但也可以选择heat语言。而如果进入Heat代码的目录,去读一下代码,不就是把各种功能的API,用逻辑封装并串起来。一个Heat的类型,可能涉及几十个API,而且是Nova,Neutron,Cinder, Swift的API调用。这里有两个层次,即开发者看到的底层的API,而应用的使用者看到的偏向于应用的功能抽象。Heat是这两者的一个有效的桥梁,一个过渡,但已经显著地降低了OpenStack编程的门槛。想想看,如果一个应用系统,用C/C++编写,那得招聘多少年经验的资深人士,而如果招聘一些熟悉PHP, JavaScript的人,那么培训的周期将大大缩短,再也不用为内存分配而苦恼,不会为了进程互斥而胆战心惊,同样的功能,只需要随着手指在键盘的跳舞而源源不断地输出。同时,也大可不必对软件的可靠性,那些bug而烦心,因为这些Heat,以及类似的“高级语言”,已经封装过了,增加了许多输入/输出的安全检查,每个高级功能,又是经过OpenStack社区千锤百炼,经过无数全球第一流编程高手的设计、编写和审查。又有什么可以不放心呢?
如果嫌OpenStack Heat提供的功能不够,怎么办?OpenStack Heat是用Python编写的,而且是开源的。OpenStack中充满了框架,想想我们在软件开发过程中使用的各种设计模式,原理是同样的,抛开OpenStack宏大的功能,在一个局部范围,这个框架的使用和编写,并不比我们以前遇到的设计模式编写理解困难多少,那么,拿出一张纸,将相关代码的逻辑、处理、相关的类,用自己熟悉的文字语言或者UML梳理下,看看已经集成进去的插件是如何设计,实现了那些功能;然后搭建一套基本的OpenStack环境,将脑海里的功能开始用Python代码来实现。可能有人说我对Python代码不熟,这也不是大问题,对于我们遇到的每个python编程问题,在互联网上,都可以找到参考的代码,只需要修改而已。另外,其实我们手边已经有许多python的参考代码,那就是OpenStack代码本身。另外,OpenStack社区特别是每届的峰会,都有大量的技术研究和分享文档,我们并不一定要从头开始什么,我们只需要站在巨人的肩膀上,一样可以成为一只在OpenStack这座高峰上自由飞翔的小鸟。
当然,要将Heat稳定而熟练地运行起来,其实是个系统功能,包括下面几个方面的艰巨工作:
在一个大型复杂的应用系统中,我们必然会遇到需要同步互斥的需求。互斥是需要对共享的资源进行保护,避免多个程序同时访问而造成数据状态的不一致或者引起资源分配的混乱。而同步,则需要仔细协调各个程序的执行步伐,让他们在某个时间点协同去完成一件复杂的工作。
在OpenStack中,数据的一致性和资源的保护都是由底层的服务来保证的,即我们所熟悉的各种nova, neutron, glance, cinder等服务来保证。他们许多服务都是分布式的,通过消息中间件和/或数据库耦合在一起。他们很多又是无状态。即使这样,也有效地保证了数据的一致性和资源分配的一致性。
而同步,则更多地落在应用本身上,底层只提供了资源访问的接口,而这些接口如何使用,则是属于上层的任务。幸运的是,OpenStack以一种令人惊讶的方式实现了这点。我们看下面一些代码片段:
"MySqlWaitHandle" : {
"Type" :"AWS::CloudFormation::WaitConditionHandle"
},
"MySqlWaitCondition" : {
"Type" :"AWS::CloudFormation::WaitCondition",
"DependsOn" :"MySqlDatabaseServer",
"Properties" : {
"Handle" : {"Ref" :"MySqlWaitHandle"},
"Timeout" : "900"
}
}
这不由自主地令我想起来java中的synchronized语句和notify, notifyAll等方法,以及C/C++中的信号与信号量,还有mutex等。让我们把相关逻辑放到一个平面来分析下:
下图中,浅蓝色是语法格式和参数,黄色的部分是使用的具体例子,而绿色是说明。WaitCondition和WaitConditionHandle就是OpenStack Heat中定义的类似于同步的语言和语法。通常WaitCondition和WaitConditionHandle都是结队使用的。WaitConditionHandle顾名思义,类似于我们通常使用的一个句柄,不同的是,在cloud环境中,不再是一个内存中64位的一个二进制值,而是一个URL,因为云中资源的引用,通常都是URL或者URI的形式-云已经跨越一个线程、进程,甚至一台物理机的边界,云中存在的都是xxx-Service,即xxx服务,而服务通常都是通过URL来访问的。我们可以针对此URL发送各种信息,其中包括信号(cfn-signal),可以发送许多信息,如成功/失败、一段任意数据、一个原因,甚至退出代码。这些信息已经足够让接收者获得足够信息去决定下一步该怎么办了。
而WaitCondition可以配置收到的信号计数,超时时间和关联的Handle,来暂停和启动模板中其他资源的执行。到这里,我们可以惊讶于语法的简洁和设计的巧妙。但是似乎还少了一件事,在传统的同步互斥中,还少了一个载体,那就是这一切在哪里执行呢?JVM?内核?而云中这都没有。答案是heat-engine,一个运行于控制节点,可以运行多个服务实例的进程。于是,heat-engine开始扮演我们熟悉的JVM或者Linux内核的角色,在heat-engine中,创建了这类资源,然后将heat模板中不同资源关联到一起…
当然,要这一切无缝地工作,还需要一些劳动,比如安装服务、配置…配置是需要小心谨慎的,在单控制节点和高可用集群中都需要注意使用的服务IP,另外,用户名/密码/租户等信息和具体环境息息相关。
看到这些,我相信许多编程高手已经开始挽起袖子,准备跃跃欲试了…
当我们还在使用PC机、服务器的时候,我们更关注一个应用中的进程和线程,许多事情分解为不同的进程和线程,他们在一个主机内工作。当需要调整时,我们可以运行多个进程或者线程。进程和线程通常运行在一起,他们的数量取决于主机本身的资源。不同类型的主机,里面运行的进程和线程数量是不定的,并没有一个具体的规格参数可以按图索骥,只有一些最佳实践可以参考。比如,在最典型的EJB系统中,前端的线程数量总是大于应用服务器数量,而应用服务器中线程数量总是大于数据库服务的线程数。许多类似的主机共同托管起一个分布式,多节点的应用。资源的添加还是非常耗时耗力的。这是应用管理的第一个演化阶段。
当我们将应用搬到虚拟机里时,只是用虚拟机替代了物理机,内部的运行结构和关系并没有发生多少轻微的改变(通常是应用的owner也不愿意改变)。不同的是,原来在一个个单独的物理主机里运行,现在是在物理主机里运行的一个个虚拟机里运行。原来的运行宿主是刚性的,而现在的运行宿主是弹性的,同一主机的多个虚拟机可能存在资源的竞争,无论是CPU,内存还是磁盘IO或者网络IO。而一个应用本身可能分布在多个物理主机上,具有一些约束。但是通过使用镜像来创建虚拟机,和虚拟化调度,让原来的应用管理变得敏捷和弹性,这正是所谓的软件定义的最大价值所在。此时,虚拟机的管理还是手工的,只是管理是通过一个精心设计的界面,而不是在机房里通过USB盘或者console。这是应用管理的第二个演化阶段。
接着OpenStack Heat来了。然而,仔细查看Heat的资源类型,可以看出,heat最擅长的是以虚拟机为单位,这也是和我们平时对OpenStack的使用相一致的,而并不善于处理进程和线程,甚至需要配置管理工具的帮忙。此时,由于引入资源充裕的物理主机(CPU、内存的配置都非常高),虚拟机所分配的资源可以任意调整,理论上可以给予传统的应用任何所需要的资源,但Heat的短板依然在于进程/线程级别的孱弱,而力不从心,比如,各种不同的应用,如何通用的处理进程和线程的数量与资源关系?并没有看到许多线索。所以此时,已经移植到虚拟机的应用需要重构,将不同业务的进程和线程分离,安置于不同的虚拟机里,每个虚拟机的进程/线程数目和资源也要相对确定,让heat集中精力在处理虚拟机,以及虚拟机之间的逻辑关系。这里拿一个图像缩略图处理应用为例,图像缩略图处理由抽取和转码两部分处理组成,平时的资源压力并不一致。再比如,一个EJB的应用,里面由不同种类的业务类型组成,其运行实例和资源压力也不同。比较理想的方案是将它们分离成不同的虚拟机单元,独立调度,关注相互之间的联系,而此类应用这种重构也比较容易。这种处理,对于auto-scaling场景来说,尤其关键,我们无法auto-scaling那些不同的进程/线程或者EJB应用,而擅长的是auto-scaling虚拟机。这种处理带来的一个重大利好是,可以支持跨可用域或者region的auto-scaling和应用的HA。尽管由于虚拟机的增多,带来一定的资源开销,然而利远大于弊。这是应用管理的第三个演化阶段。
这一点已经是不争的事实。现在Trove(Database-as-a-Service), Sahara(BigData-as-a-Service)等,都提供了基于Heat的方式。可以预见,其他OpenStackIaaS之上的应用,将越来越以这种方式为主。事实上,用Heat去写此类应用,远比原来一个个API的一针一线方式有效和高效的多。这也给了广大OpenStack研发人员手中一个利器,即任何复杂的IaaS之上应用,都可以考虑用此类方式去实现和解决之。另外一个好消息是,如果将Docker等同于KVM,则会惊喜地发现,原来绝大部分Heat对于KVM的支持,都适用于Docker,大概就是Instance或者Server这两个资源类型不可用吧。
Heat仍然需要很多实践,才能克服其中的短板,变成一种流行的语言。但是在支持云应用时,或许出于有意,或者出于无奈,那就是对于云平台管理网的依赖。请看下图,我们看到,虚拟机需要访问出于管理网的Heat-engine。没错,确实需要,否则waitCondition如何实现?但是,我们不能忘记另外一点,那就是将管理网暴露在虚拟机用户的眼前。尽管我们可以做许多的防护,许多的设置,然而,只要有漏洞,总是吸引着攻击,而且没有绝对安全。即使实现了绝对安全,那也是无数复杂的流程和配置来保证的,而这些潜在又会带来新的危险,夜不能寐的时刻也许会来临…
另一方面是针对“维护”场景还缺乏最佳实践。这也是一种实际需要,比如应用的版本升级,密码更换,系统软件的升级等。升级只需要更新相应的软件包即可,大可不必销毁而重建一个虚拟机。尽管heat已经能满足一部分需求,然而,代码的可读性和复杂度都大打折扣。也许是呼唤一个基于heat的“设计模式”的时候了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。