OpenStack 编排管理:Heat 把一台虚拟机写进模板里
OpenStack 编排管理:Heat 把一台虚拟机写进模板里
Heat 是 OpenStack 里的模板服务:把虚拟机、网络、卷这些资源写成一份声明式模板,一次描述、反复发放,改一行参数就能重发一整套环境。这篇从组件分工讲到模板的四个必写段落,再走 Web 与 CLI 两套部署实验,最后讲嵌套模板怎么拆。
一、Heat 是什么
Heat 是一种通过 OpenStack 原生 REST API、使用声明性模板格式编排复合云应用的服务,与其他 OpenStack 核心项目紧密集成。
三个关键性质:
- 模板是文本文件:描述云应用的基础架构,用户可读写,支持用版本控制工具(如 git)管理;
- 模板指定资源之间的关系:Heat 能调用 OpenStack API 以正确的顺序创建所需的基础设施——先有网络再起虚拟机、先有密钥再注入实例,顺序它来保证;
- 位置在 Nova、Neutron 等服务之上:用户不需要直接接触其他服务,把资源需求写在 Heat 模板里,Heat 自动调相关服务的接口配置资源。它充当了 OpenStack 对外接口的角色。
模板里的资源集合很广:虚拟机、网络、存储、告警、浮动 IP、安全组、伸缩组、嵌套 stack 等——通过定义模板可以多次创建需要的资源。
二、三个组件:两个接单,一个干活
| 组件 | 职责 |
|---|---|
| heat-api | 提供 REST API,是其他组件与 Heat 交互的入口,接收请求并传送给 heat-engine |
| heat-api-cfn | 提供兼容AWS CloudFormation的 API,接收请求并转发给 heat-engine |
| heat-engine | Heat 的核心:实现任务调度、资源生命周期管理。注意它自身并不提供资源创建功能,只负责编排资源,然后交由其他组件(各服务的 API)去处理 |
请求路径:用户在 Horizon 或 CLI 提交「模板 + 参数」→ 转成 REST 调用 → heat-api 或 heat-api-cfn 验证模板正确性 →消息队列→ heat-engine 处理。
验收命令(heat 装在控制节点):
ps-e|grepheat# 三类进程:heat-api / heat-api-cfn / heat-enginesystemctl status openstack-heat-*三、HOT 模板:四段式结构
3.1 模板语言:YAML
Heat 模板默认编写语言是 YAML(YAML Ain’t Markup Language),三条语法规则:
- 使用缩进(一个或多个空格)排版——缩进就是层级;
- 序列(列表)项用短划线
-表示; - MAP 中的 key-value 对用冒号
:表示——每个冒号后都要有空格,即使冒号后没有内容(实验中最容易栽的坑)。
3.2 四段式结构
| 段 | 作用 | 关键点 |
|---|---|---|
heat_template_version | 版本日期 | 如2018-08-31、2015-10-15,决定可用语法 |
parameters | 参数区 | type/label/description/default;constraints做校验(长度 min/max、allowed_pattern 正则、range 范围、custom_constraint 引用校验);hidden: true隐藏敏感回显(如密码) |
resources | 资源区(核心) | type: OS::Nova::Server等资源类型 +properties属性;内置函数取值 |
outputs | 输出区 | stack 创建完成后回显信息,get_attr取资源属性(如实例 IP、私钥) |
三个内置函数高频出现:
get_param:取 parameters 里用户传入的参数;get_resource:引用本模板内定义的另一个资源(比如 port 引用 subnet);get_attr:取某资源创建后的属性(outputs 里用)。
3.3 常用资源类型
OS::Nova::Server 虚拟机实例 OS::Nova::KeyPair 密钥对 OS::Neutron::Net 网络 OS::Neutron::Subnet 子网 OS::Neutron::Router 路由器 OS::Neutron::RouterInterface 路由器接口 OS::Neutron::FloatingIP 浮动 IP OS::Neutron::FloatingIPAssociation 浮动 IP 绑定 OS::Neutron::SecurityGroup 安全组 OS::Cinder::Volume 块存储卷查询环境支持的全部资源类型:openstack orchestration resource type list。
3.4 Heat Stack
Stack(堆栈)= 按模板创建出来的一整套资源实例。模板是「图纸」,stack 是「按图纸盖好的楼」。常用命令:
| 命令 | 说明 |
|---|---|
openstack stack create -t <模板文件> [--parameter k=v ...] <栈名> | 用模板创建堆栈 |
openstack stack event list <栈名> | 查看创建过程事件流 |
openstack stack list/stack show <栈名> | 堆栈列表 / 详情;状态CREATE_COMPLETE表示完成 |
openstack stack output show --all <栈名> | 查看 outputs 输出(实例名、IP 等) |
openstack stack delete <栈名> | 删除堆栈(交互确认);加--yes跳过确认 |
四、Web 实验:一份模板全家桶
先备好两样被模板引用的资源,并记录它们的 ID:
- 创建外网(或确认已有 External),记录 public_net ID;
- 创建镜像,记录 image ID。
把下面模板存为heat.txt——凡是【 】括起来的占位符都要换成自己的值,注释里保留了实验环境的原值供对照:
heat_template_version:2018-08-31# 模板版本:决定这份模板能用哪些语法和函数description:Simple template to deploy a stack with one virtual machine instances# ---------- 参数区:创建堆栈时传入,没传就用 default 兜底 ----------parameters:image_name_1:# 参数1:镜像 ID,实例1要用type:stringlabel:Image IDdescription:Specify an image name for instance1default:【替换成你的镜像ID】# 实验环境值:732c9cee-2652-4a4e-8c09-19745d6e15b5public_net:# 参数2:外网 ID,路由器网关和浮动 IP 都挂它type:stringlabel:Network IDdescription:Network to be used for the compute instancedefault:【替换成你的外网ID】# 实验环境值:a6e27f03-e762-4c46-a997-a7a6848d5849# ---------- 资源区:10 个资源,Heat 按引用关系自动排创建顺序 ----------resources:mykey:# ① 密钥对:save_private_key=true 让 Heat 生成私钥type:OS::Nova::KeyPairproperties:save_private_key:truename:mykeyweb_secgroup:# ② 安全组:只放行 SSH(22) 和 ping(ICMP)type:OS::Neutron::SecurityGroupproperties:rules:-protocol:tcp# 规则1:TCP 22 端口,源 0.0.0.0/0 = 任意来源remote_ip_prefix:0.0.0.0/0port_range_min:22port_range_max:22-protocol:icmp# 规则2:ICMP 放行 pingprivate_net:# ③ 私有网络:虚拟机真正住的网络type:OS::Neutron::Netproperties:name:private_netprivate_subnet:# ④ 子网:network_id 用 get_resource 引用③type:OS::Neutron::Subnetproperties:network_id:{get_resource:private_net}cidr:"192.168.99.0/24"# 私网网段ip_version:4vrouter:# ⑤ 路由器:外网网关指向参数里的外网type:OS::Neutron::Routerproperties:external_gateway_info:network:{get_param:public_net}vrouter_interface:# ⑥ 路由器接口:把⑤和④接通,私网才有出口type:OS::Neutron::RouterInterfaceproperties:router_id:{get_resource:vrouter}subnet_id:{get_resource:private_subnet}instance_port:# ⑦ 实例端口:提前占好 IP、挂好安全组type:OS::Neutron::Portproperties:network:{get_resource:private_net}security_groups:-default# 默认安全组-{get_resource:web_secgroup}# 再挂②,实例才通 SSH/ICMPfixed_ips:-subnet_id:{get_resource:private_subnet}# 从④的网段分 IPfloating_ip:# ⑧ 浮动 IP:从外网池里申请一个type:OS::Neutron::FloatingIPproperties:floating_network_id:{get_param:public_net}association:# ⑨ 绑定关系:把⑧绑到⑦端口上type:OS::Neutron::FloatingIPAssociationproperties:floatingip_id:{get_resource:floating_ip}port_id:{get_resource:instance_port}instance1:# ⑩ 虚拟机:四样要素全部引用/参数拼装type:OS::Nova::Serverproperties:image:{get_param:image_name_1}# 镜像来自参数区key_name:{get_resource:mykey}# 密钥用①flavor:【替换成你的flavor名】# 实验环境值:Flavor_webnetworks:-port:{get_resource:instance_port}# 用⑦的端口(自带 IP 和安全组)# ---------- 输出区:stack 创建完成后回显信息 ----------outputs:private_key:description:Private keyvalue:{get_attr:[mykey,private_key]}# 取①生成的私钥,登录实例要用这份模板一次描述了10 个资源——密钥对 + 安全组(放行 22/ICMP)+ 私网 + 子网 + 路由器 + 路由器接口 + 实例端口 + 浮动 IP + 绑定关系 + 虚拟机,对照网络管理实验,这就是把「建网络、连路由、绑浮动 IP、配安全组、发实例」全流程写成一份文件。逐段拆解:
参数区(两个 ID)
image_name_1:实例用的镜像 ID。写成参数而不是写死在资源里,是为了换环境时只改default一行(或创建时用--parameter覆盖),模板本身不用动;public_net:外网 ID,被⑤路由器网关和⑧浮动 IP 两处get_param引用——所有跟外网沾边的东西全挂它。
资源区(10 个资源,四条线)
- 网络主线(③④⑤⑥):先有私有网络,再有子网(
network_id用get_resource引用③),路由器外网网关指向public_net,最后路由器接口把子网挂上路由器——私网从这里通向外界。这四步就是实操篇「建网络、连路由」的原样复刻; - 实例接入(⑦⑩):端口提前占好网段内的 IP、挂上 default + ②两组安全组;虚拟机的
networks写的是这个 port 而不是网络名——因为固定 IP 和安全组都配在端口上,实例挂端口就把这些配置一起继承; - 外网可达(⑧⑨):从外网池申请一个浮动 IP,再由绑定关系把它绑到实例端口上,外网才能 SSH 进来;
- 安全与密钥(①②):安全组只放行 TCP 22 和 ICMP,别的全拒;密钥对因
save_private_key: true由 Heat 生成私钥——私钥不落盘在服务端,只在 outputs 里回显给你。
输出区:创建完成后取回①的私钥——用openstack stack output show --all <栈名>拿到私钥内容,才能登录实例。没有这一段,私钥就只存在 Heat 那里拿不出来了。
三个内置函数的出场位置对应着看:get_param出现在⑤⑧⑩(从参数区取外网和镜像),get_resource出现在④⑥⑦⑨⑩(模板内资源互相引用),get_attr只在输出区出现(取创建完成后的属性)。
那 Heat 怎么知道「先建网络、最后建虚拟机」?模板里从头到尾没写一行顺序,但每个引用(get_resource/get_param)本身就是依赖声明:虚拟机引用了端口,端口引用了网络和子网……Heat 把这些引用关系梳理成依赖图,按拓扑顺序自动创建——这正是开篇说的「模板指定资源之间的关系,Heat 能以正确的顺序创建」。
创建堆栈:「项目 > 编排 > 堆栈」→「创建堆栈」→ 选择模板文件 heat.txt → 提交。创建完成后实例、网络、浮动 IP 全部就位。
五、CLI 实验:从单机到嵌套
sourcekeystonerc_admin# 清场:删掉现有实例避免资源不足openstack server list openstack server delete INSTANCE_NAME/ID# 备好四样资源openstack image list / flavor list / keypair list / network list5.1 最简单模板:demo-template.yaml
heat_template_version:2015-10-15# 版本决定可用语法parameters:NetID:# 唯一参数:实例要接入的网络 IDtype:stringdescription:Network ID to use for the instance.resources:server:# 只有一个资源:一台虚拟机type:OS::Nova::Serverproperties:image:Img_cli# 【替换成你的镜像名】flavor:Flavor_cli# 【替换成你的flavor名】key_name:KeyPair_cli# 【替换成你的密钥对名】networks:-network:{get_param:NetID}# 网络从参数取,创建时传入outputs:instance_name:description:Name of the instance.value:{get_attr:[server,name]}# 回显实例名instance_ip:description:IP address of the instance.value:{get_attr:[server,first_address]}# 回显实例 IP创建并验收:
exportNET_ID=【替换成shared网络的ID】 openstack stack create-tdemo-template.yaml--parameter"NetID=$NET_ID"Stack_demo openstack stack event list Stack_demo# 看创建过程openstack stack list# CREATE_COMPLETE 即完成openstack stack output show--allStack_demo# 输出实例名和 IPopenstack server list# 实例已就位5.2 带约束的模板:hello_world.yaml
在 demo 基础上加了三样东西:
- constraints 校验:image/flavor/key_name 用
custom_constraint(glance.image、nova.flavor、nova.keypair)自动校验存在性;admin_pass 要求长度 6~8、纯字母数字、大写开头;db_port 限定 40000~60000; - hidden: true:admin_pass 是密码,创建时不回显;
- user_data + str_replace:实例首次启动时执行脚本,
db_port占位符被get_param的值替换——这是 Heat 做软件配置编排的入口。
# 【 】里换成自己环境的资源名,其余照抄openstack stack create-thello_world.yaml\--parameterimage=【你的镜像名】--parameterflavor=【你的flavor名】\--parameterkey_name=【你的密钥对名】--parameterNetID=【你的网络名】\--parameteradmin_pass=【6~8位大写开头字母数字】 Stack_helloworld_cli# 同一份模板换一套参数再发一套——这就是「一次编写、多次发放」openstack stack create-thello_world.yaml\--parameterimage=【你的镜像名】--parameterflavor=【你的flavor名】\--parameterkey_name=【你的密钥对名】--parameterNetID=【你的网络名】\--parameteradmin_pass=【6~8位大写开头字母数字】 Stack_helloworld_web对比两份模板的区别:demo 的参数只有一个 NetID、资源写死;hello_world 参数化程度高、带校验和初始化脚本——模板越通用,参数化越彻底。
5.3 嵌套模板:主模板引用子模板
子模板my_nova.yaml(只定义一台实例,key_name 和 NetID 暴露为参数):
heat_template_version:2015-04-30parameters:key_name:# 密钥名暴露为参数,由主模板传入type:stringNetID:# 网络 ID 同样暴露为参数type:stringresources:server:# 子模板只干一件事:按传入参数起一台实例type:OS::Nova::Serverproperties:image:Img_cliflavor:Flavor_clikey_name:{get_param:key_name}networks:-network:{get_param:NetID}主模板main.yaml(resources.type 直接写子模板文件名,属性照常传参):
heat_template_version:2015-04-30resources:my_server:# type 直接写子模板文件名,当成一种「资源类型」用type:my_nova.yamlproperties:# 属性名对应子模板 parameters 里的参数名key_name:【替换成你的密钥对名】# 实验环境值:KeyPair_cliNetID:【替换成你的网络ID或名】# 实验环境值:Network_cli_01openstack stack create-tmain.yaml Stack_nest openstack stack event list Stack_nest嵌套的意义:把大架构拆成可复用的积木——主模板管编排,子模板管单件。
5.4 删除堆栈:连坐机制
openstack stack delete Stack_demo# 交互式:提示确认,输入 y 或回车openstack stack delete--yesStack_nest# 静默式:不询问直接删openstack stack list# 空了openstack server list# 也空了结论:由堆栈创建的虚拟机实例会随着堆栈的删除而删除——stack 是资源的「全家桶」。实际工作中建议用交互确认式删除,避免误删;--yes适合脚本自动化。
六、Heat 的编排能力全景(概念加分项)
| 场景 | 说明 |
|---|---|
| 基础设施编排 | 计算/网络/存储按模板与依赖关系一键创建(本篇实验) |
| 软件配置与部署 | user_data + cloud-init 在实例首启时装软件、改配置 |
| 自动伸缩 | 配合伸缩组与告警(Ceilometer/Aodh 提供数据),按负载自动加减实例 |
| 负载均衡编排 | 模板里直接创建 LB 成员与监听 |
| 与配置管理工具集成 | Ansible/Puppet 等作为部署后环节接进模板 |
七、命令速查总表
| 命令 | 关键选项/参数 | 说明 |
|---|---|---|
openstack stack create | -t <模板>;--parameter k=v(可多次);--parameter "k=$VAR" | 创建堆栈 |
openstack stack event list | 栈名 | 查看创建/删除过程事件 |
openstack stack list/show | — | 列表(看状态)/ 详情 |
openstack stack output show | --all | 查看模板 outputs |
openstack stack delete | 加--yes跳过确认 | 删除堆栈(连坐删资源) |
openstack orchestration resource type list | — | 查询支持的资源类型 |
ps -e | grep heat | — | 验收三个 heat 进程 |
附:关键英文全称
| 缩写 | 全称 | 含义 |
|---|---|---|
| HOT | Heat Orchestration Template | Heat 原生模板格式 |
| YAML | YAML Ain’t Markup Language | 递归缩写,模板语言 |
| Stack | — | 堆栈,按模板创建的资源集合 |
| CFN | CloudFormation | AWS 的编排服务,heat-api-cfn 兼容其 API |
| get_param / get_resource / get_attr | — | 取参数 / 引用模板内资源 / 取资源属性 |
📚 参考资料(官方文档)
- Heat 官方文档(组件架构、栈生命周期、CLI):https://docs.openstack.org/heat/latest/
- Heat Template Guide(HOT 模板结构:parameters / resources / outputs 与内置函数):https://docs.openstack.org/heat/latest/template_guide/index.html