LoadBalancer as a service gives you the ability to expose your Kubernetes services as a type: LoadBalancer. All your clusters on Civo come pre-installed with the Civo cloud controller manager (CCM) responsible for creating the Loadbalancer resources and giving the Public IP to your service when you create it with type: LoadBalancer.

We cannot directly create Load Balancer objects via Terraform, but you can get the object details via the provider.

This guide will explore how to create a volume to store our database backups and attach it to our compute instance running in Civo.

Requirements

This guide assumes that you:

  • Have a Civo account
  • Have a Terraform project initialized (refer to this guide for more details)
  • Are using Civo Terraform provider version 1.0.16 or later
  • Have gone through the Launch a Civo compute instance using Terraform guide
  • Have a basic understanding of Linux commands and how to use them in a terminal

Step #1 - Add region to provider

Since the region field is optional in most of the Civo Terraform provider's resources and data sources (if no region is provided in the configuration or provider.tf file, the system will choose one for you), it's a good idea to declare it once at the provider level.

The benefits of declaring region at the provider level are:

  • We don't have to repeat it in our configuration file(s) every time.
  • Terraform will ensure all API calls for data sources and resources communicate with a consistent region.

Update your provider.tf to include a region field. Example:

"`terraform // code omitted for brevity

provider "civo" { token = "" region = "LON1" } ```

Step #2 - Prepare configuration file

Let's first create a file named main.tf:

# Change directory to Terraform project
$ cd ~/civo

# Create the file
$ touch main.tf

Using your favourite editor (e.g. nano or VS Code), add the following code inside that file and save it:

# Query medium instance size
data "civo_size" "medium" {
    filter {
        key = "name"
        values = ["medium"]
        match_by = "re"
    }

    filter {
        key = "type"
        values = ["kubernetes"]
    }

    sort {
        key = "ram"
        direction = "asc"
    }
}

resource "civo_kubernetes_cluster" "my-cluster" {
    name = "demo"
    firewall_id = civo_firewall.my-firewall.id
    region = "LON1"
    applications = "Traefik-v2-loadbalancer"
    pools {
        size = element(data.civo_size.medium.sizes, 0).name
        node_count = 3
    }
}
resource "civo_firewall" "my-firewall" {
    name = "my-firewall"
}

resource "time_sleep" "wait_150_seconds" {
  depends_on = [civo_kubernetes_cluster.my-cluster]

  create_duration = "150s"
}

data civo_loadbalancer "my-lb" {
  depends_on = [time_sleep.wait_150_seconds]
  name = "demo-kube-system-traefik"
}

output "civo_loadbalancer_output" {
  value = data.civo_loadbalancer.my-lb.public_ip
}

So what happens when we apply the configuration above?

  • The following blocks were taken from the Launch a Civo Kubernetes cluster using Terraform guide. Be sure to check out that guide for more in-depth explanations of each.
    • Create a firewall
    • Create a cluster
  • In the Query medium instance size block:
    • We are using civo_instances_size data source to find a list of compute `size's that match with these filters:
      • name contains medium word
      • type is kubernetes
    • We sort the result (instance sizes) by ram in ascending order (smallest first)
    • We then can refer to this data source as data.civo_instances_size.medium
    • To access the sizes later, we can use data.civo_instances_size.medium.sizes syntax where sizes is the data source key that contains all the sizes (list)
  • In the Query timesleep block:
    • We are using asleep for 150 seconds so that we wait for the Loadbalancer Traefik is creating to get the public IP which we will be later getting from the data source.
  • civoloadbalanceroutput and civoloadbalanceroutput will get the output of the load balancer IP address

Step #3 - Plan

Now, you can run the terraform plan command to see what will be created.

$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions
are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.civo_loadbalancer.my-lb will be read during apply
  # (config refers to values not yet known)
 <= data "civo_loadbalancer" "my-lb"  {
      + algorithm                       = (known after apply)
      + backends                        = (known after apply)
      + cluster_id                      = (known after apply)
      + enable_proxy_protocol           = (known after apply)
      + external_traffic_policy         = (known after apply)
      + firewall_id                     = (known after apply)
      + name                            = "demo-kube-system-traefik"
      + private_ip                      = (known after apply)
      + public_ip                       = (known after apply)
      + session_affinity                = (known after apply)
      + session_affinity_config_timeout = (known after apply)
      + state                           = (known after apply)
    }

  # civo_firewall.my-firewall will be created
  + resource "civo_firewall" "my-firewall" {
      + create_default_rules = true
      + id                   = (known after apply)
      + name                 = "my-firewall"
      + network_id           = (known after apply)
    }

  # civo_kubernetes_cluster.my-cluster will be created
  + resource "civo_kubernetes_cluster" "my-cluster" {
      + api_endpoint           = (known after apply)
      + applications           = "Traefik-v2-loadbalancer"
      + cni                    = (known after apply)
      + created_at             = (known after apply)
      + dns_entry              = (known after apply)
      + firewall_id            = (known after apply)
      + id                     = (known after apply)
      + installed_applications = (known after apply)
      + kubeconfig             = (sensitive value)
      + kubernetes_version     = (known after apply)
      + master_ip              = (known after apply)
      + name                   = "demo"
      + network_id             = (known after apply)
      + num_target_nodes       = (known after apply)
      + ready                  = (known after apply)
      + region                 = "LON1"
      + status                 = (known after apply)
      + target_nodes_size      = (known after apply)

      + pools {
          + id             = (known after apply)
          + instance_names = (known after apply)
          + node_count     = 3
          + size           = "g4s.kube.medium"
        }
    }

  # time_sleep.wait_150_seconds will be created
  + resource "time_sleep" "wait_150_seconds" {
      + create_duration = "150s"
      + id              = (known after apply)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + civo_loadbalancer_output = (known after apply)

───────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take
exactly these actions if you run "terraform apply" now.

As you can see from the plan output above, Terraform will create the following resources in LON1 region for us:

  • A Kubernetes cluster (demo) with g4s.kube.medium size
  • A Firewall (my-firewall) in the default network
  • Asleep just for all things to get ready before we get the data for the load balancer

Step #4 - Apply

It's now time to create the actual compute instance, volume and volume attachment. First, let's run terraform apply command. Typeyes` and hit Enter key when it asks for confirmation.

$ terraform apply
Terraform used the selected providers to generate the following execution plan. Resource actions
are indicated with the following symbols:
  + create
 <= read (data resources)

Terraform will perform the following actions:

  # data.civo_loadbalancer.my-lb will be read during apply
  # (config refers to values not yet known)
 <= data "civo_loadbalancer" "my-lb"  {
      + algorithm                       = (known after apply)
      + backends                        = (known after apply)
      + cluster_id                      = (known after apply)
      + enable_proxy_protocol           = (known after apply)
      + external_traffic_policy         = (known after apply)
      + firewall_id                     = (known after apply)
      + name                            = "demo-kube-system-traefik"
      + private_ip                      = (known after apply)
      + public_ip                       = (known after apply)
      + session_affinity                = (known after apply)
      + session_affinity_config_timeout = (known after apply)
      + state                           = (known after apply)
    }

  # civo_firewall.my-firewall will be created
  + resource "civo_firewall" "my-firewall" {
      + create_default_rules = true
      + id                   = (known after apply)
      + name                 = "my-firewall"
      + network_id           = (known after apply)
    }

  # civo_kubernetes_cluster.my-cluster will be created
  + resource "civo_kubernetes_cluster" "my-cluster" {
      + api_endpoint           = (known after apply)
      + applications           = "Traefik-v2-loadbalancer"
      + cni                    = (known after apply)
      + created_at             = (known after apply)
      + dns_entry              = (known after apply)
      + firewall_id            = (known after apply)
      + id                     = (known after apply)
      + installed_applications = (known after apply)
      + kubeconfig             = (sensitive value)
      + kubernetes_version     = (known after apply)
      + master_ip              = (known after apply)
      + name                   = "demo"
      + network_id             = (known after apply)
      + num_target_nodes       = (known after apply)
      + ready                  = (known after apply)
      + region                 = "LON1"
      + status                 = (known after apply)
      + target_nodes_size      = (known after apply)

      + pools {
          + id             = (known after apply)
          + instance_names = (known after apply)
          + node_count     = 3
          + size           = "g4s.kube.medium"
        }
    }

  # time_sleep.wait_150_seconds will be created
  + resource "time_sleep" "wait_150_seconds" {
      + create_duration = "150s"
      + id              = (known after apply)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + civo_loadbalancer_output = (known after 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

civo_firewall.my-firewall: Creating...
civo_firewall.my-firewall: Creation complete after 2s [id=92db8ed8-3320-41a9-9abd-d25cfbd28507]
civo_kubernetes_cluster.my-cluster: Creating...
civo_kubernetes_cluster.my-cluster: Still creating... [10s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [20s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [30s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [40s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [50s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [1m0s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [1m10s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [1m20s elapsed]
civo_kubernetes_cluster.my-cluster: Still creating... [1m30s elapsed]
civo_kubernetes_cluster.my-cluster: Creation complete after 1m34s [id=5b6c0471-36bf-4807-877e-0559921d1a42]
time_sleep.wait_150_seconds: Creating...
time_sleep.wait_150_seconds: Still creating... [10s elapsed]
time_sleep.wait_150_seconds: Still creating... [20s elapsed]
time_sleep.wait_150_seconds: Still creating... [30s elapsed]
time_sleep.wait_150_seconds: Still creating... [40s elapsed]
time_sleep.wait_150_seconds: Still creating... [50s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m0s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m10s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m20s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m30s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m40s elapsed]
time_sleep.wait_150_seconds: Still creating... [1m50s elapsed]
time_sleep.wait_150_seconds: Still creating... [2m0s elapsed]
time_sleep.wait_150_seconds: Still creating... [2m10s elapsed]
time_sleep.wait_150_seconds: Still creating... [2m20s elapsed]
time_sleep.wait_150_seconds: Still creating... [2m30s elapsed]
time_sleep.wait_150_seconds: Creation complete after 2m30s [id=2022-04-11T11:34:26Z]
data.civo_loadbalancer.my-lb: Reading...
data.civo_loadbalancer.my-lb: Read complete after 0s [id=2e3b45f8-1852-4a27-9aca-b2cc75956397]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

civo_loadbalancer_output = "74.220.19.252"

Now, if you refresh the Civo web UI, you will see there's a new Kubernetes cluster created for you:

Created Kubernetes cluster as a result of the Terraform command with the name "demo"

This is what the Loadbalancer looks like from the UI:

Demo cluster's load balancer information sheet

And you have got the IP address for the service Traefik under the output section of the terraform apply command above.

If you notice, there will be a new file named terraform.tfstate created for you in your local project directory. And, if you print its contents, it will look like:

"console $ cat terraform.tfstate { "version": 4, "terraform_version": "1.1.7", "serial": 15, "lineage": "2de9ba1e-0a35-3922-0119-c075b59177df", "outputs": { "civo_loadbalancer_output": { "value": "74.220.19.252", "type": "string" } }, "resources": [ { "mode": "data", "type": "civo_loadbalancer", "name": "my-lb", "provider": "provider[\"registry.terraform.io/civo/civo\"]", "instances": [ { "schema_version": 0, "attributes": { "algorithm": "round_robin", "backends": [ { "health_check_port": 0, "ip": "192.168.1.7", "protocol": "TCP", "source_port": 80, "target_port": 31218 }, { "health_check_port": 0, "ip": "192.168.1.6", "protocol": "TCP", "source_port": 80, "target_port": 31218 }, { "health_check_port": 0, "ip": "192.168.1.9", "protocol": "TCP", "source_port": 80, "target_port": 31218 }, { "health_check_port": 0, "ip": "192.168.1.7", "protocol": "TCP", "source_port": 443, "target_port": 32671 }, { "health_check_port": 0, "ip": "192.168.1.6", "protocol": "TCP", "source_port": 443, "target_port": 32671 }, { "health_check_port": 0, "ip": "192.168.1.9", "protocol": "TCP", "source_port": 443, "target_port": 32671 } ], "cluster_id": "5b6c0471-36bf-4807-877e-0559921d1a42", "enable_proxy_protocol": "", "external_traffic_policy": "Cluster", "firewall_id": "c9e14ae8-b8eb-4bae-a687-9da4637233da", "id": "2e3b45f8-1852-4a27-9aca-b2cc75956397", "name": "demo-kube-system-traefik", "private_ip": "192.168.1.10", "public_ip": "74.220.19.252", "region": null, "session_affinity": "", "session_affinity_config_timeout": 0, "state": "available" }, "sensitive_attributes": [] } ] }, { "mode": "data", "type": "civo_size", "name": "medium", "provider": "provider[\"registry.terraform.io/civo/civo\"]", "instances": [ { "schema_version": 0, "attributes": { "filter": [ { "all": false, "key": "name", "match_by": "re", "values": [ "medium" ] }, { "all": false, "key": "type", "match_by": "exact", "values": [ "kubernetes" ] } ], "id": "terraform-20220411113016316300000001", "sizes": [ { "cpu": 2, "description": "Medium - Standard", "disk": 50, "name": "g4s.kube.medium", "ram": 4096, "selectable": true, "type": "kubernetes" }, { "cpu": 4, "description": "Medium - RAM optimized", "disk": 80, "name": "g4m.kube.medium", "ram": 32768, "selectable": true, "type": "kubernetes" }, { "cpu": 16, "description": "Medium - CPU optimized", "disk": 80, "name": "g4c.kube.medium", "ram": 32768, "selectable": true, "type": "kubernetes" }, { "cpu": 8, "description": "Medium - Performance", "disk": 80, "name": "g4p.kube.medium", "ram": 32768, "selectable": true, "type": "kubernetes" } ], "sort": [ { "direction": "asc", "key": "ram" } ] }, "sensitive_attributes": [] } ] }, { "mode": "managed", "type": "civo_firewall", "name": "my-firewall", "provider": "provider[\"registry.terraform.io/civo/civo\"]", "instances": [ { "schema_version": 0, "attributes": { "create_default_rules": true, "id": "92db8ed8-3320-41a9-9abd-d25cfbd28507", "name": "my-firewall", "network_id": "28244c7d-b1b9-48cf-9727-aebb3493aaac", "region": null }, "sensitive_attributes": [], "private": "bnVsbA==" } ] }, { "mode": "managed", "type": "civo_kubernetes_cluster", "name": "my-cluster", "provider": "provider[\"registry.terraform.io/civo/civo\"]", "instances": [ { "schema_version": 0, "attributes": { "api_endpoint": "https://74.220.20.157:6443", "applications": "Traefik-v2-loadbalancer", "cni": "flannel", "created_at": "2022-04-11 11:30:25 +0000 UTC", "dns_entry": "5b6c0471-36bf-4807-877e-0559921d1a42.k8s.civo.com", "firewall_id": "92db8ed8-3320-41a9-9abd-d25cfbd28507", "id": "5b6c0471-36bf-4807-877e-0559921d1a42", "installed_applications": [ { "application": "Traefik-v2-loadbalancer", "category": "architecture", "installed": false, "version": "2.5" } ], "kubeconfig": "apiVersion: v1\nclusters:\n- cluster:\n certificate-authority-data: LS0tL...Qo=\n", "kubernetes_version": "1.22.2-k3s1", "master_ip": "74.220.20.157", "name": "demo", "network_id": "28244c7d-b1b9-48cf-9727-aebb3493aaac", "num_target_nodes": 3, "pools": [ { "id": "c51b08ae-630f-47b3-ac4a-a4aa83421058", "instance_names": [ "k3s-demo-89e4-ef7fed-node-pool-618a", "k3s-demo-89e4-ef7fed-node-pool-94cd", "k3s-demo-89e4-ef7fed-node-pool-c871" ], "node_count": 3, "size": "g4s.kube.medium" } ], "ready": true, "region": "LON1", "status": "ACTIVE", "tags": "", "target_nodes_size": "g4s.kube.medium" }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "civo_firewall.my-firewall", "data.civo_size.medium" ] } ] }, { "mode": "managed", "type": "time_sleep", "name": "wait_30_seconds", "provider": "provider[\"registry.terraform.io/hashicorp/time\"]", "instances": [ { "schema_version": 0, "attributes": { "create_duration": "150s", "destroy_duration": null, "id": "2022-04-11T11:34:26Z", "triggers": null }, "sensitive_attributes": [], "private": "bnVsbA==", "dependencies": [ "civo_firewall.my-firewall", "civo_kubernetes_cluster.my-cluster", "data.civo_size.medium" ] } ] } ] } ``

That's the Terraform state file that was created after you create the above cluster and get the load balancer IP of the service Traefik.

When you update your main.tf file and run terraform apply again, Terraform will refresh the state file, understand what you want to update and update your compute instance/volume/volume attachment accordingly.

If there's no change in your main.tf file and you rerun terraform apply, it will output No changes. Your infrastructure matches the configuration. message back to you.

***

So now you know how to get LoadBalancer details by using Terraform. Good job!