Vultr

Cloudfleet supports adding Vultr instances as nodes in your CFKE cluster. The most streamlined and scalable approach is the cloud-init integration of the Cloudfleet Terraform provider. You create Vultr instances with Terraform. The Cloudfleet Terraform provider generates a cloud-init configuration that automatically registers the instances with your CFKE cluster.

Prerequisites

  • You have the Terraform CLI installed on your workstation.
  • You configured the Cloudfleet CLI by following the instructions here. You can alternatively use the token authentication by following the instructions here.
  • You have a Vultr account and a Personal Access Token. You can create the Personal Access Token from the Vultr control panel.
  • You have a CFKE cluster running. The example below assumes a cluster with the ID CLUSTER_ID. You can also create the cluster with Terraform as shown in the Terraform introduction.

Adding Vultr instances to your CFKE cluster

Use the following Terraform configuration to create Vultr instances and integrate them with your CFKE cluster. Replace the cfke_cluster_id and vultr_api_key variables with your values.

terraform
variable "cfke_cluster_id" {
  type    = string
  default = "CFKE Cluster ID"
}

variable "vultr_api_key" {
  type = string
  description = "Vultr Personal Access Token"
}

variable "region" {
  type    = string
  default = "fra"
}

terraform {
  required_providers {
    cloudfleet = {
      source = "terraform.cloudfleet.ai/cloudfleet/cloudfleet"
    }

    kubernetes = {
      source  = "hashicorp/kubernetes"
      version = "~> 2.0"
    }

    vultr = {
      source  = "vultr/vultr"
      version = "~> 2.0"
    }
  }
}

provider "vultr" {
  api_key = var.vultr_api_key
}

data "cloudfleet_cfke_cluster" "cluster" {
  id = var.cfke_cluster_id
}

// Generate the cloud-init configuration to join the Vultr instance to the CFKE cluster
resource "cloudfleet_cfke_node_join_information" "vultr" {
  cluster_id = data.cloudfleet_cfke_cluster.cluster.id
  region     = var.region
  zone       = var.region
  node_labels = {
    "cfke.io/provider" = "vultr"
  }
}

// Vultr resources

// Create a VPC for the CFKE nodes. This is optional since Vultr instances can communicate over the public internet, but when nodes are part of the same VPC, they establish encrypted tunnels over the private network
resource "vultr_vpc" "cfke_vpc" {
  region      = var.region
  description = "CFKE VPC"
}

data "vultr_os" "ubuntu" {
  filter {
    name = "name"
    values = ["Ubuntu 24.04 LTS x64"]
  }
}

resource "vultr_instance" "cfke_node" {
  count       = 1
  plan        = "vc2-2c-4gb"
  region      = var.region
  os_id       = data.vultr_os.ubuntu.id
  enable_ipv6 = true
  label       = "cfke-node-${count.index + 1}"
  user_data   = cloudfleet_cfke_node_join_information.vultr.rendered // Use the generated cloud-init configuration
  hostname    = "cfke-node-${count.index + 1}"
  vpc_ids = [vultr_vpc.cfke_vpc.id]
}

After you create the Terraform configuration, run these commands to provision the Vultr instances and integrate them with your CFKE cluster:

bash
terraform init
terraform apply

After the deployment completes, verify that the nodes joined your cluster:

bash
kubectl get nodes

Adding a load balancer

To expose workloads to the internet, do these steps:

  1. Create a Kubernetes Service of type NodePort.
  2. Configure a Vultr Load Balancer to forward traffic to the nodes on that port.

The example below exposes a basic Nginx Deployment on port 31000, with a matching Vultr Load Balancer configuration.

terraform
locals {
  node_port = 31000
}

resource "vultr_load_balancer" "load_balancer" {
  region              = var.region
  label               = "cfke-load-balancer"
  balancing_algorithm = "roundrobin"

  forwarding_rules {
    frontend_protocol = "tcp"
    frontend_port     = 80
    backend_protocol  = "tcp"
    backend_port      = local.node_port
  }

  health_check {
    path                = "/"
    port                = local.node_port
    protocol            = "http"
    response_timeout    = 1
    unhealthy_threshold = 2
    check_interval      = 3
    healthy_threshold   = 4
  }
  attached_instances = sort(vultr_instance.cfke_node[*].id)
}


// An example Kubernetes deployment using a Load Balancer
data "cloudfleet_client_config" "me" {}

provider "kubernetes" {
  host                   = data.cloudfleet_cfke_cluster.cluster.endpoint
  cluster_ca_certificate = data.cloudfleet_cfke_cluster.cluster.certificate_authority
  token                  = data.cloudfleet_client_config.me.access_token
}

resource "kubernetes_namespace" "test" {
  depends_on = [
    data.cloudfleet_cfke_cluster.cluster
  ]
  metadata {
    name = "nginx"
  }
}

resource "kubernetes_deployment" "app" {
  wait_for_rollout = false
  metadata {
    name      = "nginx"
    namespace = kubernetes_namespace.test.id
    labels = {
      app = "nginx"
    }
  }

  spec {
    replicas = 2
    selector {
      match_labels = {
        app = "nginx"
      }
    }

    template {
      metadata {
        labels = {
          app = "nginx"
        }
      }

      spec {
        affinity {
          node_affinity {
            required_during_scheduling_ignored_during_execution {
              node_selector_term {
                match_expressions {
                  key      = "cfke.io/provider"
                  operator = "In"
                  values = [
                    "vultr"
                  ]
                }
              }
            }
          }
        }

        container {
          image = "nginx:latest"
          name  = "nginx"

          port {
            name           = "http"
            container_port = 80
          }

        }
      }
    }
  }
}


resource "kubernetes_service" "app" {
  depends_on = [
    data.cloudfleet_cfke_cluster.cluster
  ]
  metadata {
    name      = "nginx"
    namespace = kubernetes_namespace.test.id
    labels = {
      app = "nginx"
    }
  }
  spec {
    selector = {
      app = "nginx"
    }
    external_traffic_policy = "Local"
    type                    = "NodePort"
    port {
      name        = "http"
      port        = 80
      target_port = 80
      node_port   = local.node_port
    }
  }
}


output "load_balancer_ip" {
  value = vultr_load_balancer.load_balancer.ipv4
}

output "load_balancer_ip_v6" {
  value = vultr_load_balancer.load_balancer.ipv6
}

For production deployments, consider installing an Ingress controller instead of this basic NodePort example.

Firewall configuration

Configure a Vultr firewall group with this rule to secure your cluster:

  • Port 31000 (TCP): Allow NodePort Service access only from the Vultr load balancer IP address. Do not open this port to all sources (0.0.0.0/0), because that creates unnecessary security exposure.

This configuration follows the principle of least privilege: only the load balancer can access the NodePort. All external traffic flows through the load balancer, which gives centralized access control and public exposure management.

On this page