Quickstart with Terraform (and Equinix Metal)
All actions within meltcloud can be terraformed – in fact, we recommend using Infrastructure as Code right from the start.
This tutorial shows how the meltcloud Terraform Provider works.
In this specific example, we will use Equinix Metal to provision a bare metal physical server, since they offer a terraform provider and support booting from a remote iPXE script, which allows us to do the full end-to-end setup in terraform.
In practice, you probably want to orchestrate your bare metal servers with something like the Redfish Terraform Provider – but the meltcloud terraform part should remain similar.
Prerequisites
- Install Terraform (at least v1.8.0, since we use
functions
) - Create an API Key for your meltcloud organization
- Create an Equinix Metal Account
- Create an API Key for the Equinix Metal project:
In the Equinix Metal Console, go to Project Settings → API Keys → Add New Key
Everything ready? Let's get started!
Deploy
WARNING
Be aware that deploying a Bare Metal Server on Equinix Metal incurs costs. The complete example shouldn't take longer than an hour and the server costs < 1$/h, but you will still be charged.
First, let's specify the Terraform providers & versions that we will use:
terraform {
required_providers {
equinix = {
source = "equinix/equinix"
version = "2.4.1"
}
meltcloud = {
source = "meltcloud/meltcloud"
version = "~> 1.0"
}
time = {
source = "hashicorp/time"
version = "0.11.2"
}
random = {
source = "hashicorp/random"
version = "3.6.3"
}
local = {
source = "hashicorp/local"
version = "2.5.2"
}
helm = {
source = "hashicorp/helm"
version = "2.15.0"
}
}
}
Next, let's configure the providers.
For this, copy the following snippet and adapt the organization UUID that you grab from the meltcloud Console URL:
provider "meltcloud" {
# adapt to your organization from the url (i.e. https://app.meltcloud.io/ui/orgs/f505052b-19cf-4761-b9ac-482fb3481297/overview)
organization = "f505052b-19cf-4761-b9ac-482fb3481297"
}
provider "equinix" {
}
Let's export the API tokens and initialize terraform:
export MELTCLOUD_API_KEY=ejy... # meltcloud API Key that you created as a prerequisite
export METAL_AUTH_TOKEN=zEq... # Equinix Metal API Key that you created as a prerequisite
terraform init
...
Terraform has been successfully initialized!
Now, let's create a meltcloud cluster and a machine pool:
# create a cluster on meltcloud
resource "meltcloud_cluster" "equinix" {
name = "melt-equinix"
version = "1.30"
pod_cidr = "10.36.0.0/16"
service_cidr = "10.96.0.0/16"
dns_service_ip = "10.96.0.10"
}
# save the kubeconfig for use with kubectl
resource "local_sensitive_file" "kubeconfig" {
filename = "${path.module}/melt-equinix.kubeconfig"
content = meltcloud_cluster.equinix.kubeconfig_raw
file_permission = "0600"
}
# create a machine pool with the necessary settings for equinix
resource "meltcloud_machine_pool" "equinix" {
cluster_id = meltcloud_cluster.equinix.id
name = "equinix-pool"
version = "1.30"
# equinix has the ephemeral disk on /dev/sda
primary_disk_device = "/dev/sda"
}
Run terraform apply
and wait some minutes until your cluster becomes ready:
terraform apply
...
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
meltcloud_cluster.equinix: Creating...
meltcloud_cluster.equinix: Still creating... [10s elapsed]
meltcloud_cluster.equinix: Still creating... [20s elapsed]
meltcloud_cluster.equinix: Still creating... [30s elapsed]
meltcloud_cluster.equinix: Still creating... [40s elapsed]
meltcloud_cluster.equinix: Still creating... [50s elapsed]
meltcloud_cluster.equinix: Still creating... [1m0s elapsed]
meltcloud_cluster.equinix: Still creating... [1m10s elapsed]
meltcloud_cluster.equinix: Still creating... [1m20s elapsed]
meltcloud_cluster.equinix: Still creating... [1m30s elapsed]
meltcloud_cluster.equinix: Still creating... [1m40s elapsed]
meltcloud_cluster.equinix: Still creating... [1m50s elapsed]
meltcloud_cluster.equinix: Creation complete after 1m56s [name=melt-equinix]
meltcloud_machine_pool.equinix: Creating...
meltcloud_machine_pool.equinix: Creation complete after 0s [name=equinix-pool
If you're curious what's happening behind the scenes, you can follow the Operations in the meltcloud Console.
We now have a working Kubernetes cluster!
The terraform snippet above downloaded the kubeconfig
for us, so let's have a look:
export KUBECONFIG=melt-equinix.kubeconfig
kubectl cluster-info
Kubernetes control plane is running at https://34.65.48.39:2002
...
kubectl get nodes
No nodes yet! Let's add a bare metal server to the cluster.
For this, we will:
- Create a UUID that we can use for the Machine. With this UUID, we can pre-register the Machine in meltcloud and assign it to a Pool, so when the Machine boots, it will be automatically become part of the pool/cluster.
- Create an iPXE Chain URL that can be used by Equinix Metal to boot into meltcloud
- Start the Equinix Bare Metal Machine, converting it directly into a fully-managed Kubernetes worker
# create a random uuid
resource "random_uuid" "machine" {
}
# pre-register the machine on meltcloud and assign it to the pool
resource "meltcloud_machine" "equinix01" {
machine_pool_id = meltcloud_machine_pool.equinix.id
uuid = random_uuid.machine.result
name = "melt-equinix-01"
}
# create ipxe chain url for boot
resource "time_offset" "in_a_year" {
offset_days = 365
}
resource "meltcloud_ipxe_chain_url" "equinix" {
name = "equinix"
expires_at = time_offset.in_a_year.rfc3339
}
# create a bare metal machine!
resource "equinix_metal_device" "equinix01" {
hostname = "melt-equinix-01"
plan = "c3.small.x86"
metro = "fr"
operating_system = "custom_ipxe"
billing_cycle = "hourly"
# TODO: adapt this to your equinix project ID (find it in your Equinix Metal Console URL)
project_id = "f46295d8-833a-4b96-a5e5-8e85ce2d471d"
always_pxe = "true"
# boot from the iPXE Chain URL, using the UUID we created before.
user_data = provider::meltcloud::customize_uuid_in_ipxe_script(meltcloud_ipxe_chain_url.equinix.script, meltcloud_machine.equinix01.uuid)
}
Run terraform apply
to see the bare metal cluster being provisioned:
terraform apply
...
equinix_metal_device.equinix01: Still creating... [1m40s elapsed]
equinix_metal_device.equinix01: Still creating... [1m50s elapsed]
equinix_metal_device.equinix01: Still creating... [2m0s elapsed]
equinix_metal_device.equinix01: Still creating... [2m10s elapsed]
equinix_metal_device.equinix01: Still creating... [2m20s elapsed]
equinix_metal_device.equinix01: Still creating... [2m30s elapsed]
equinix_metal_device.equinix01: Still creating... [2m40s elapsed]
equinix_metal_device.equinix01: Still creating... [2m50s elapsed]
equinix_metal_device.equinix01: Still creating... [3m0s elapsed]
equinix_metal_device.equinix01: Creation complete after 3m4s [id=c7e9aeb4-ed9a-4991-b5dd-0a117732dbb0]
After roughly 3 minutes, the server will be provisioned and have booted successfully into a Kubernetes worker.
If we check with kubectl
now, we will see that the node joined our cluster:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
melt-equinix-01 NotReady <none> 1m v1.30.1
The node is NotReady
, as a CNI is missing. So, as a final step, let's add Cilium as a CNI with terraform:
provider "helm" {
kubernetes {
host = meltcloud_cluster.equinix.kubeconfig.host
username = meltcloud_cluster.equinix.kubeconfig.username
password = meltcloud_cluster.equinix.kubeconfig.password
client_certificate = base64decode(meltcloud_cluster.equinix.kubeconfig.client_certificate)
client_key = base64decode(meltcloud_cluster.equinix.kubeconfig.client_key)
cluster_ca_certificate = base64decode(meltcloud_cluster.equinix.kubeconfig.cluster_ca_certificate)
}
}
resource "helm_release" "cilium" {
name = "cilium"
repository = "https://helm.cilium.io"
chart = "cilium"
namespace = "kube-system"
version = "1.16.1"
set {
name = "ipam.mode"
value = "kubernetes"
}
}
After a last terraform apply
, your fully-managed Kubernetes cluster with a fully-managed bare metal Kubernetes Worker is ready to use:
kubectl get nodes
NAME STATUS ROLES AGE VERSION
melt-equinix-01 Ready <none> 10m v1.30.1
You can find the full example also on Github: https://github.com/meltcloud/terraform-provider-meltcloud/blob/main/examples/equinix-metal/main.tf
Cleanup
In order to delete the resources again, you have to first unassign the Machine from the Machine Pool (this is a safety measure to not disrupt the workloads):
resource "meltcloud_machine" "equinix01" {
# unassign from machine pool
#machine_pool_id = meltcloud_machine_pool.equinix.id
uuid = random_uuid.machine.result
name = "melt-equinix-01"
}
Let's run a terraform apply
to unassign the Machine. This will take up to 10 minutes until the Equinix Machine has rebooted.
After that, you can delete the resources as usual with a terraform destroy