Skip to content

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

  1. Install Terraform (at least v1.8.0, since we use functions)
  2. Create an API Key for your meltcloud organization
  3. Create an Equinix Metal Account
  4. Create an API Key for the Equinix Metal project:
    In the Equinix Metal Console, go to Project SettingsAPI KeysAdd 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
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:

terraform
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:

shell
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:

terraform
# 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:

shell
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
terraform
# 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:

shell
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:

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:

shell
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):

terraform
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