Heat vs Terraform : syntax comparison
Presentation
Heat and Terraform are IAC projects (Infrastructure as Code).
According to the documentation :
Heat is the main project in the OpenStack Orchestration program. It implements an orchestration engine to launch multiple composite cloud applications based on templates in the form of text files that can be treated like code.
On the other side,
Terraform (Hashicorp product) is agnostic and provides multiple integration (AWS, Google Cloud, Azure and of course OpenStack) :HashiCorp Terraform enables you to safely and predictably create, change, and improve infrastructure. It is an open source tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned. Note : there is multiple way to write your template and maybe you'll not see the same syntax in the documentation (inline, etc).
Set your params
In Heat
parameters:
flavor_name:
type: string
default: m1.small
image_name:
type: string
default: Centos7
key_name:
type: string
default: mykey
public_net:
type: string
default: public
# shortened version
parameters:
flavor_name: {default: m1.small, type: string}
image_name: {default: Centos7, type: string}
key_name: {default: mykey, type: string}
public_net: {default: external, type: string}
In Terraform
variable "flavor_name" {
default = "m1.small"
}
variable "image_name" {
default = "Centos7"
}
variable "key_name" {
default = "mykey"
}
variable "public_net" {
default = "public"
}
Network examples
Create a subnet
In this example we will create two resources, a network called internal_network and its associated subnet called internal_subnet. The subnet will be 172.16.10.0/24
and the DHCP allocation pool 172.16.10.10
to 172.16.10.250
.
In Heat
network-1:
type: OS::Neutron::Net
properties:
admin_state_up: true
name: internal_network
network-1-subnet-1:
type: OS::Neutron::Subnet
properties:
network: { get_resource: network-1 }
name: internal_subnet
ip_version: 4
cidr: 172.16.10.0/24
allocation_pools:
- {end: 172.16.10.10, start: 172.16.10.250}
enable_dhcp: true
dns_nameservers: [{ 1.1.1.1 }, { 8.8.8.8 }]
In Terraform
resource "openstack_networking_network_v2" "network-1" {
name = "internal_network"
admin_state_up = "true"
}
resource "openstack_networking_subnet_v2" "network-1-subnet-1" {
name = "internal_subnet"
network_id = "${openstack_networking_network_v2.network-1.id}"
cidr = "172.16.10.0/24"
ip_version = 4
dns_nameservers = ["1.1.1.1","8.8.8.8"]
allocation_pools {
start = "172.16.10..10"
end = "172.16.10.250"
}
}
Create a Security Group
For this example we will create a security group resource called sec_group that will allow connection on ports 22
(SSH), 80
(http), 443
(https) and ICMP protocol (ping).
In Heat
sec-group-1:
type: OS::Neutron::SecurityGroup
properties:
name: sec_group
rules:
- {port_range_max: 22, port_range_min: 22, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
- {port_range_max: 80, port_range_min: 80, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
- {port_range_max: 443, port_range_min: 443, protocol: tcp, remote_ip_prefix: 0.0.0.0/0}
- {protocol: icmp, remote_ip_prefix: 0.0.0.0/0}
In Terraform
resource "openstack_compute_secgroup_v2" "sec-group-1" {
name = "sec_group"
rule {
from_port = 22
to_port = 22
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
rule {
from_port = 80
to_port = 80
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
rule {
from_port = 443
to_port = 443
ip_protocol = "tcp"
cidr = "0.0.0.0/0"
}
rule {
from_port = -1
to_port = -1
ip_protocol = "icmp"
cidr = "0.0.0.0/0"
}
}
Request a floating IP
In Heat
fip-1:
type: OS::Nova::FloatingIP
properties:
pool: public
fip-1-association:
type: OS::Nova::FloatingIPAssociation
properties:
floating_ip: { get_resource: fip-1 }
server_id: { get_resource: instance-1 }
In Terraform
resource "openstack_networking_floatingip_v2" "fip-1" {
pool = "public"
}
resource "openstack_compute_floatingip_associate_v2" "fip-1-association" {
floating_ip = "${openstack_networking_floatingip_v2.fip-1.address}"
instance_id = "${openstack_compute_instance_v2.instance-1.id}"
}
Create a router
In Heat
router-1:
type: OS::Neutron::Router
properties:
name: router
external_gateway_info:
network: public
router-1-interface-1:
type: OS::Neutron::RouterInterface
properties:
router_id: { get_resource: router-1 }
subnet_id: { get_resource: network-1-subnet-1 }
In Terraform
resource "openstack_networking_router_v2" "router-1" {
name = "router"
admin_state_up = true
external_network_id = "$PUBLIC_ID"
}
resource "openstack_networking_router_interface_v2" "router-1-interface-1" {
router_id = "${openstack_networking_router_v2.router-1.id}"
subnet_id = "${openstack_networking_subnet_v2.network-1-subnet-1.id}"
}
Compute examples
In this example we will create a compute resource that will launch a machine named cirros_instance
, on the previously created internal_network
network and the security group sec_group
, with the image cirros
and the flavor m1.small, with your key yourkey
which in this example is already present on your OpenStack.
We will launch an user_data
script named setup.sh
that will initialize the instance in the
way you want (install and configure a web server and so on) :
#!/bin/bash
# some configuration...
Create an instance
In Heat
instance-1:
type: OS::Nova::Server
properties:
name = "cirros_instance"
key_name: { get_param: key_name }
image: { get_param: image_name }
flavor: { get_param: flavor_name }
networks:
- network : { get_param : network-1 }
security_groups:
- { get_resource: sec-group-1 }
user_data_format: RAW
user_data:
get_file: setup.sh
In Terraform
resource "openstack_compute_instance_v2" "cirros_instance" {
name = "cirros_instance"
image_name = "${var.image_name}"
flavor_name = "${var.flavor_name}"
key_pair = "${var.key_name}"
security_groups = ["${openstack_compute_secgroup_v2.sec_group.name}"]
network {
name = "${openstack_networking_network_v2.internal_network.name}"
}
user_data = "${file("setup.sh")}"
}
Create a volume
Add some parameters
In Heat
parameters:
availability_zone:
type: string
default: nova
volume_size:
type: number
default: 10
In Terraform
variable "availability_zone" {
default = "nova"
}
variable "volume_size" {
default = "10"
}
Create a volume
In Heat
resources:
cinder_volume:
type: OS::Cinder::Volume
properties:
size: { get_param: volume_size }
availability_zone: { get_param: availability_zone }
In Terraform
resource "openstack_blockstorage_volume_v3" "volume-1" {
region = "RegionOne"
name = "volume_1"
size = 10
}
Now I hope you have a better idea of what you can do with Heat and Terraform and how to do it :)
Feel free to correct me if you see any typo or if something seems wrong to you.
You can send me an email or comment below.