This article will teach you how to automate your infrastructure deployments using Terraform and cloud-init in a VMware Cloud Director environment.
In this guide, we will use Terraform to:
172.16.1.1/24
x.x.x.x
to 172.16.1.200
where x.x.x.x
is a public IP on your Edge GW.172.16.1.200
172.16.1.200
We have created the guide specifically for customers deploying virtual machines from GleSYS templates in VMware Cloud Director. To complete this tutorial, you will need the following:
Refer to the official documentation for creating a VMware Cloud Director API token.
This tutorial assumes that your VMware Cloud Director environment is configured with an NSX Edge Gateway with a public IP address.
Ensuring that your NSX Edge Gateway has no prior configuration is essential. Any configuration, such as firewall rules, will be overwritten.
If you want to follow this guide without impacting your production environment, email support@glesys.se, and we will configure a temporary environment for testing.
Set up a DNS record for the FQDN of your WordPress site wp.example.com
to point to the public IP address of your NSX Edge Gateway.
Before delving into the Terraform configuration, let's first create the cloud-init configuration we will use to install and configure WordPress when our VM boots for the first time.
In your working directory, create a file called metadata.yaml
and paste the following configuration into it:
instance-id: 00000000-0000-0000-0000-000000000000 # replace with your own id
local-hostname: wp.example.com # replace with the FQDN of your WordPress site
network:
version: 2
ethernets:
ens192:
addresses:
- 172.16.1.200/24
nameservers:
addresses: [8.8.8.8, 8.8.4.4]
routes:
- to: 0.0.0.0/0
via: 172.16.1.1
Create a file called userdata.yaml
and paste the following configuration into it. Ensure you replace all instances of the following:
glesys
with your preferred usernamewp.example.com
with the FQDN of your WordPress siteuser@example.com
with a valid email address for the Let's Encrypt certificateecdsa-sha2-nistp256 AAAA...
with your public SSH key#cloud-config
users:
- name: glesys
shell: /bin/bash
sudo: ALL=(ALL) NOPASSWD:ALL
lock_passwd: true
ssh_authorized_keys:
- ecdsa-sha2-nistp256 AAAA...
manage_etc_hosts: true
packages:
- apache2
- php8.1-fpm
- curl
- php8.1-curl
- php8.1-mysql
- php8.1-gd
- certbot
- python3-certbot-apache
- mysql-server
- fail2ban
- automysqlbackup
write_files:
-
content: |
<VirtualHost *:80>
ServerName wp.example.com
DocumentRoot /home/glesys/web/public
<Directory /home/glesys/web/public>
Options -Indexes +FollowSymLinks +MultiViews
AllowOverride All
Require all granted
<files xmlrpc.php>
Require all denied
</files>
#PHP-FPM Socket
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/wordpress.sock|fcgi://localhost/"
</FilesMatch>
</Directory>
</VirtualHost>
path: /etc/apache2/sites-available/wordpress.conf
-
content: |
[wordpress]
user = glesys
group = glesys
listen = /var/run/wordpress.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 50
pm.process_idle_timeout = 10s
pm.max_requests = 200
chdir = /
path: /etc/php/8.1/fpm/pool.d/wordpress.conf
runcmd:
- sed -i 's/post_max_size \= .M/post_max_size \= 50M/g' /etc/php/8.1/fpm/php.ini
- sed -i 's/upload_max_filesize \= .M/upload_max_filesize \= 50M/g' /etc/php/8.1/fpm/php.ini
- 'systemctl restart php8.1-fpm'
- 'a2enmod rewrite headers expires proxy_fcgi proxy_http'
- 'a2ensite wordpress.conf'
- 'a2dissite 000-default-conf'
- 'systemctl restart apache2'
- 'certbot --apache -d wp.example.com --agree-tos -m user@example.com --no-eff-email --redirect'
- 'echo "postfix postfix/mailname string $(hostname --fqdn)" | sudo debconf-set-selections'
- 'echo "postfix postfix/main_mailer_type select Internet Site" | sudo debconf-set-selections'
- 'echo "postfix postfix/destinations string localhost" | sudo debconf-set-selections'
- 'echo "postfix postfix/mynetworks string 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128" | sudo debconf-set-selections'
- 'echo "postfix postfix/mailbox_limit string 0" | sudo debconf-set-selections'
- 'echo "postfix postfix/recipient_delim string +" | sudo debconf-set-selections'
- 'echo "postfix postfix/protocols select all" | sudo debconf-set-selections'
- 'apt-get install postfix -y'
- 'curl -o /usr/local/bin/wp https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar'
- 'chmod 755 /usr/local/bin/wp'
- PASSWORD=`openssl rand -base64 32`
- mysql -e "create database wordpress;"
- mysql -e "CREATE USER wordpress@localhost IDENTIFIED BY '$PASSWORD';"
- mysql -e "GRANT ALL PRIVILEGES ON wordpress.* TO 'wordpress'@'localhost';"
- mysql -e "FLUSH PRIVILEGES;"
- chmod +x /home/glesys
- mkdir -p /home/glesys/web/public
- chown -R glesys:glesys -R /home/glesys/web/
- 'sudo -u glesys -i -- wp core download --path=/home/glesys/web/public/ --quiet'
- sudo -u glesys -i -- wp config create --path=/home/glesys/web/public/ --dbprefix=glesys_ --dbname=wordpress --dbuser=wordpress --dbpass="$PASSWORD"
- 'ufw default deny incoming'
- 'ufw allow OpenSSH'
- 'ufw allow http'
- 'ufw allow https'
- 'ufw --force enable'
In your working directory, create a file called main.tf
and paste the following configuration into it:
terraform {
required_providers {
vcd = {
source = "vmware/vcd"
version = "3.13.0"
}
}
}
provider "vcd" {
user = ""
password = ""
auth_type = "api_token"
api_token = var.vcd_api_token
url = "https://${var.vcd_url}/api"
org = var.vcd_org
vdc = var.vcd_vdc
}
Next, define the variables your project will use to make the code easier to reuse across environments.
Create a file called variables.tf
and paste the following configuration:
variable "vcd_url" {
type = string
description = "Cloud Director URL (Example: 'vcd.dc-fbg1.glesys.net')"
}
variable "vcd_org" {
type = string
description = "Tenant Organization (Example: 'vdo-xxxxx')"
}
variable "vcd_api_token" {
type = string
description = "API Token to authenticate to Cloud Director"
}
variable "vcd_vdc" {
type = string
description = "Organization Virtual Datacenter (Example: 'vdc-xxxxx')"
}
variable "vcd_edge" {
type = string
description = "Edge Gateway (Example: 't1-vdc-xxxxx-fbg1-01')"
}
Run terraform init
to initialize the project and install the required providers:
$ terraform init
Initializing the backend...
Initializing provider plugins...
- Finding vmware/vcd versions matching "3.13.0"...
- Installing vmware/vcd v3.13.0...
- Installed vmware/vcd v3.13.0 (signed by a HashiCorp partner, key ID 8BF53DB49CDB70B0)
Terraform has been successfully initialized!
In your working directory, create a file called network.tf
and paste the following configuration:
# since the edge gateway is not managed by tf, define a data resource for the edge gateway
data "vcd_nsxt_edgegateway" "my_edge" {
name = var.vcd_edge
}
# network with subnet 172.16.1.1/24 for the wordpress server
resource "vcd_network_routed_v2" "wp_net" {
name = "wp-net"
edge_gateway_id = data.vcd_nsxt_edgegateway.my_edge.id
gateway = "172.16.1.1"
prefix_length = 24
dns1 = "8.8.8.8"
dns2 = "8.8.4.4"
static_ip_pool {
start_address = "172.16.1.200"
end_address = "172.16.1.250"
}
}
# destination nat rule mapping edge gateway public ip to the internal ip of the wordpress server
resource "vcd_nsxt_nat_rule" "wp_inbound" {
edge_gateway_id = data.vcd_nsxt_edgegateway.my_edge.id
name = "wp_inbound"
rule_type = "DNAT"
external_address = tolist(data.vcd_nsxt_edgegateway.my_edge.subnet)[0].primary_ip
internal_address = "172.16.1.200"
}
# source nat rule mapping the internal ip of the wordpress server to edge gateway public ip
resource "vcd_nsxt_nat_rule" "wp_outbound" {
edge_gateway_id = data.vcd_nsxt_edgegateway.my_edge.id
name = "wp_outbound"
rule_type = "SNAT"
external_address = tolist(data.vcd_nsxt_edgegateway.my_edge.subnet)[0].primary_ip
internal_address = "172.16.1.200"
}
# custom port profile for the wordpress server
resource "vcd_nsxt_app_port_profile" "wp_app_port_profile" {
name = "wp-app-port-profile"
scope = "TENANT"
app_port {
protocol = "ICMPv4"
}
app_port {
protocol = "TCP"
port = ["22", "80", "443"]
}
}
# ip set for the wordpress server
resource "vcd_nsxt_ip_set" "wp_ip_set" {
edge_gateway_id = data.vcd_nsxt_edgegateway.my_edge.id
name = "wp-ip-set"
ip_addresses = ["172.16.1.200"]
}
resource "vcd_nsxt_firewall" "my_edge_firewall" {
edge_gateway_id = data.vcd_nsxt_edgegateway.my_edge.id
# allow icmp, ssh, http, https to the wordpress server
rule {
action = "ALLOW"
name = "Allow ICMPv4, SSH, HTTP, HTTPS with destination to wp-ip-set"
direction = "IN"
ip_protocol = "IPV4"
app_port_profile_ids = [vcd_nsxt_app_port_profile.wp_app_port_profile.id]
destination_ids = [vcd_nsxt_ip_set.wp_ip_set.id]
}
# allow outbound from the wordpress server
rule {
action = "ALLOW"
name = "Allow all IPv4 traffic to any destination from wp-ip-set"
direction = "OUT"
ip_protocol = "IPV4"
source_ids = [vcd_nsxt_ip_set.wp_ip_set.id]
}
}
In your working directory, create a file called server.tf
and paste the following configuration:
# since the catalog is not managed by tf, define a data resource for the glesys templates catalog
data "vcd_catalog" "os_templates" {
org = "GleSYS"
name = "GleSYS Templates"
}
# define a data resource for the ubuntu-2204 template
data "vcd_catalog_vapp_template" "ubuntu_2204" {
catalog_id = data.vcd_catalog.os_templates.id
name = "ubuntu-2204"
}
# clone vm from the ubuntu-2204 template
resource "vcd_vm" "wp" {
name = "wp"
computer_name = "wp"
vapp_template_id = data.vcd_catalog_vapp_template.ubuntu_2204.id
memory = 4096
cpus = 2
cpu_cores = 1
network {
type = "org"
name = vcd_network_routed_v2.wp_net.name
ip_allocation_mode = "MANUAL"
ip = "172.16.1.200"
connected = true
}
override_template_disk {
bus_type = "paravirtual"
size_in_mb = "10240"
bus_number = 0
unit_number = 0
}
set_extra_config {
key = "guestinfo.userdata"
value = base64gzip(file("${path.module}/userdata.yaml"))
}
set_extra_config {
key = "guestinfo.metadata"
value = base64gzip(file("${path.module}/metadata.yaml"))
}
set_extra_config {
key = "guestinfo.userdata.encoding"
value = "gzip+base64"
}
set_extra_config {
key = "guestinfo.metadata.encoding"
value = "gzip+base64"
}
}
Ensure that your working directory resembles this layout:
$ ls -lh
-rw-r--r-- 1 jamesm jamesm 319 Jan 17 09:50 main.tf
-rw-r--r-- 1 jamesm jamesm 284 Jan 17 09:50 metadata.yaml
-rw-r--r-- 1 jamesm jamesm 2.6K Jan 17 09:50 network.tf
-rw-r--r-- 1 jamesm jamesm 2.6K Jan 17 09:50 server.tf
-rw-r--r-- 1 jamesm jamesm 18K Jan 17 00:39 terraform.tfstate
-rw-r--r-- 1 jamesm jamesm 3.5K Jan 17 09:50 userdata.yaml
-rw-r--r-- 1 jamesm jamesm 659 Jan 17 09:50 variables.tf
Run terraform apply
to apply your configuration and provision your infrastructure:
$ terraform apply -var vcd_url=vcd.dc-fbg1.glesys.net \
-var vcd_api_token=ABC12345678 \
-var vcd_org=vdo-##### -var vcd_vdc=vdc-##### -var vcd_edge=t1-vdc-#####-fbg1-01
Plan: 7 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
vcd_nsxt_ip_set.wp_ip_set: Creating...
vcd_nsxt_nat_rule.wp_inbound: Creating...
vcd_network_routed_v2.wp_net: Creating...
vcd_nsxt_nat_rule.wp_outbound: Creating...
vcd_nsxt_app_port_profile.wp_app_port_profile: Creating...
vcd_nsxt_app_port_profile.wp_app_port_profile: Creation complete after 4s vcd_nsxt_ip_set.wp_ip_set: Creation complete after 4s
vcd_nsxt_firewall.my_edge_firewall: Creating...
vcd_nsxt_nat_rule.wp_outbound: Creation complete after 8s
vcd_nsxt_nat_rule.wp_inbound: Creation complete after 11s
vcd_network_routed_v2.wp_net: Creation complete after 21s
vcd_vm.wp: Creating...
vcd_nsxt_firewall.my_edge_firewall: Creation complete after 21s
vcd_vm.wp: Creation complete after 1m37s
Apply complete! Resources: 7 added, 0 changed, 0 destroyed.
Open your web browser and browse to the FQDN of your site to complete the WordPress installation:
Although not commonly used in production environments, Terraform can destroy the infrastructure that it has provisioned. It is particularly useful in lab scenarios such as this when we want to deploy infrastructure for testing or learning purposes and then destroy it as it is no longer needed.
The destroy command may fail when removing the vcd_network_routed_v2
resource, so you may need to run it twice.
$ terraform destroy -var vcd_url=vcd.dc-fbg1.glesys.net \
-var vcd_api_token=ABC12345678 \
-var vcd_org=vdo-##### -var vcd_vdc=vdc-##### -var vcd_edge=t1-vdc-#####-fbg1-01
Plan: 0 to add, 0 to change, 7 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
vcd_nsxt_nat_rule.wp_outbound: Destroying...
vcd_nsxt_nat_rule.wp_inbound: Destroying...
vcd_nsxt_firewall.my_edge_firewall: Destroying...
vcd_vm.wp: Destroying...
vcd_nsxt_firewall.my_edge_firewall: Destruction complete after 3s
vcd_nsxt_ip_set.wp_ip_set: Destroying...
vcd_nsxt_app_port_profile.wp_app_port_profile: Destroying...
vcd_nsxt_app_port_profile.wp_app_port_profile: Destruction complete after 4s
vcd_nsxt_nat_rule.wp_inbound: Destruction complete after 7s
vcd_nsxt_nat_rule.wp_outbound: Destruction complete after 10s
vcd_nsxt_ip_set.wp_ip_set: Destruction complete after 11s
vcd_vm.wp: Destruction complete after 22s
vcd_network_routed_v2.wp_net: Destroying...
vcd_network_routed_v2.wp_net: Destruction complete after 7s
Destroy complete! Resources: 7 destroyed.
In this tutorial, you have used Terraform to build the infrastructure for running a WordPress server in VMware Cloud Director.
Furthermore, you have used cloud-init to initialize your virtual machine and automate the WordPress installation and configuration.
Now that you understand how Terraform and cloud-init work, you can extend this example to meet your production needs.
Here is a list of some suggestions:
The possibilities are endless.
Kontakta oss gärna för mer information. Vi hjälper dig att komma fram till den bästa lösningen för dina behov.