OpenStack 编排管理:Heat 把一台虚拟机写进模板里

发布时间:2026/11/5 1:57:47
OpenStack 编排管理:Heat 把一台虚拟机写进模板里

OpenStack 编排管理:Heat 把一台虚拟机写进模板里

Heat 是 OpenStack 里的模板服务:把虚拟机、网络、卷这些资源写成一份声明式模板,一次描述、反复发放,改一行参数就能重发一整套环境。这篇从组件分工讲到模板的四个必写段落,再走 Web 与 CLI 两套部署实验,最后讲嵌套模板怎么拆。

一、Heat 是什么

Heat 是一种通过 OpenStack 原生 REST API、使用声明性模板格式编排复合云应用的服务,与其他 OpenStack 核心项目紧密集成。

三个关键性质:

  1. 模板是文本文件:描述云应用的基础架构,用户可读写,支持用版本控制工具(如 git)管理;
  2. 模板指定资源之间的关系:Heat 能调用 OpenStack API 以正确的顺序创建所需的基础设施——先有网络再起虚拟机、先有密钥再注入实例,顺序它来保证;
  3. 位置在 Nova、Neutron 等服务之上:用户不需要直接接触其他服务,把资源需求写在 Heat 模板里,Heat 自动调相关服务的接口配置资源。它充当了 OpenStack 对外接口的角色。

模板里的资源集合很广:虚拟机、网络、存储、告警、浮动 IP、安全组、伸缩组、嵌套 stack 等——通过定义模板可以多次创建需要的资源。

二、三个组件:两个接单,一个干活

组件职责
heat-api提供 REST API,是其他组件与 Heat 交互的入口,接收请求并传送给 heat-engine
heat-api-cfn提供兼容AWS CloudFormation的 API,接收请求并转发给 heat-engine
heat-engineHeat 的核心:实现任务调度、资源生命周期管理。注意它自身并不提供资源创建功能,只负责编排资源,然后交由其他组件(各服务的 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),三条语法规则:

  1. 使用缩进(一个或多个空格)排版——缩进就是层级;
  2. 序列(列表)项用短划线-表示;
  3. 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:

  1. 创建外网(或确认已有 External),记录 public_net ID;
  2. 创建镜像,记录 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 list

5.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 基础上加了三样东西:

  1. constraints 校验:image/flavor/key_name 用custom_constraint(glance.image、nova.flavor、nova.keypair)自动校验存在性;admin_pass 要求长度 6~8、纯字母数字、大写开头;db_port 限定 40000~60000;
  2. hidden: true:admin_pass 是密码,创建时不回显;
  3. 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_01
openstack 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 进程

附:关键英文全称

缩写全称含义
HOTHeat Orchestration TemplateHeat 原生模板格式
YAMLYAML Ain’t Markup Language递归缩写,模板语言
Stack—堆栈,按模板创建的资源集合
CFNCloudFormationAWS 的编排服务,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