Linode Kubernetes Engine Clusters With Terraform

Linode is an affordable alternative to the major cloud providers. What is interesting about Linode is that they offer a managed Kubernetes control plane with no additional cost. I'm going to set up a cluster using Terraform to test out the Infrastructure as Code (IaC) capability of Linode as well.

Linode Kubernetes Engine Clusters With Terraform

Linode is an affordable alternative to the major cloud providers. What is interesting about Linode is that they offer a managed Kubernetes control plane with no additional cost. All you pay for is the worker nodes.

I'm going to set up a cluster using Terraform to test out the Infrastructure as Code (IaC) capability of Linode as well, since that is an important requirement for me when selecting any cloud provider.

Prerequisites

To follow this guide you will first need a Linode account and an API token with permissions to read/write Kubernetes.

Setup Terraform and Linode Provider

In your main file, add the Linode provider.

main.tf

terraform {
  required_providers {
    linode = {
      source = "linode/linode"
    }
    local = {
      source = "hashicorp/local"
    }
  }
  required_version = ">= 0.13"
}

After adding the provider, run terraform init to download the provider details. I've also added the local provider which will help us grab the kubeconfig from the cluster and write it to a file.

Define Some Variables

To prevent hardcoding of values, we define input variables needed to setup the cluster.

vars.tf

provider "linode" {
  token = var.linode_token
}

variable "linode_token" {
  type = string
  description = "The API token to utilize for linode."
}

variable "region" {
  type = string
  description = "The name of the linode region to deploy to."
  default = "us-central"
}

variable "cluster_name" {
  type = string
  description = "The name of the Kubernetes cluster to create"
}

variable "cluster_kubernetes_version" {
  type = string
  description = "The version of Kubernetes for the cluster"
}

variable "cluster_node_type" {
  type = string
  description = "The type of linodes to create"
  default = "g6-standard-2"
}

variable "cluster_node_count" {
  type = number
  description = "The number of linodes to create in the cluster"
  default = 3
}

variable "cluster_tags" {
  type = list(string)
  description = "Tags to include on the cluster"
  default = []
}

Create Linode LKE Cluster

Creating a cluster is easy by just wiring up the config variables. After the cluster is created, we will want to log into it, so we extract the kubeconfig and save to a file using the local_file resource.

cluster.tf

resource "linode_lke_cluster" "linode-cluster" {
    region      = var.region

    label       = var.cluster_name
    k8s_version = var.cluster_kubernetes_version
    tags        = var.cluster_tags

    pool {
        type  = var.cluster_node_type
        count = var.cluster_node_count
    }
}

resource "local_file" "linode-cluster-kubeconfig" {
    filename = "linode-cluster-kubeconfig.yaml"
    content = base64decode(linode_lke_cluster.linode-cluster.kubeconfig)
}

Define The Configuration

The rest of the work is creating some tfvars files with the specific configuration you'd like to deploy. I created two files, cluster-config.auto.tfvars and cluster-secrets.auto.tfvars. The secrets file will obviously not be checked in, and I added it to my .gitignore to be safe.

cluster-config.auto.tfvars

region = "us-central"

cluster_name = "dallas-1"
cluster_kubernetes_version = "1.18"
cluster_node_type = "g6-standard-2"
cluster_node_count = 3
cluster_tags = ["dev"]

To get the available api values for region and node type, the easiest way is to use the Linode API directly.

Region IDs

$ curl -s https://api.linode.com/v4/regions | jq '.data[].id'
"ap-west"
"ca-central"
"ap-southeast"
"us-central"
"us-west"
"us-east"
"eu-west"
"ap-south"
"eu-central"
"ap-northeast"

I'm going with us-central which maps to their Dallas datacenter.

Linode Types

curl -s https://api.linode.com/v4/linode/types | jq '.data[] | {id: .id, label: .label, price: .price.monthly}'
{
  "id": "g6-nanode-1",
  "label": "Nanode 1GB",
  "price": 5
}
{
  "id": "g6-standard-1",
  "label": "Linode 2GB",
  "price": 10
}
{
  "id": "g6-standard-2",
  "label": "Linode 4GB",
  "price": 20
}
{
  "id": "g6-standard-4",
  "label": "Linode 8GB",
  "price": 40
}
...

I selected the g6-standard-2 for nodes. This would bring the monthly cost to $60/month if I kept it running like this.

cluster-secrets.auto.tfvars

# This file should not be checked in. It contains secrets. It should be in .gitignore.
# These secrets are loaded automatically when calling terraform because it is named with the pattern *.auto.tfvars

linode_token = "insert_token_here"

The only secret needed is a Linode token.

Terraform Plan and Apply

With everything defined, we can now run terraform plan and terraform apply. If you're not familiar with terraform, it's always a good idea to run plan first to check what would be done before running apply.

If everything worked, in a couple minutes you'll have a running cluster and a cluster config file sitting in your working directory.

Connect to the Cluster

With the kubeconfig file in hand, there are many ways to interact with the new cluster. A simple test can be accomplished with kubectl.

$ export KUBECONFIG=linode-cluster-kubeconfig.yaml
$ kubectl get po --all-namespaces
NAMESPACE     NAME                                       READY   STATUS    RESTARTS   AGE
kube-system   calico-kube-controllers-5d4fb868fc-7qd5r   1/1     Running   0          5m
kube-system   calico-node-d2gx4                          1/1     Running   0          5m
kube-system   calico-node-fvw6l                          1/1     Running   0          5m
kube-system   calico-node-p57kg                          1/1     Running   0          5m
kube-system   coredns-6f5c7bbdfb-tslpq                   1/1     Running   0          5m
kube-system   coredns-6f5c7bbdfb-xxdjz                   1/1     Running   0          5m
kube-system   csi-linode-controller-0                    4/4     Running   0          5m
kube-system   csi-linode-node-2xf2h                      2/2     Running   0          5m
kube-system   csi-linode-node-4nfpn                      2/2     Running   0          12h
kube-system   csi-linode-node-z9ggg                      2/2     Running   0          5m
kube-system   kube-proxy-gr4xn                           1/1     Running   0          5m
kube-system   kube-proxy-kg8zj                           1/1     Running   0          5m
kube-system   kube-proxy-z47k4                           1/1     Running   0          5m

That Was Easy

I really like the simplicity of the Linode cluster provisioning experience. It rivals DigitalOcean in terms of going from zero to cluster in no time at all.

What Linode really got right with LKE was that the only thing you need to do is use their API to provision a full working cluster. No need for tools like kops, eksctl, or any other tooling.

An Aside: Why Bother With Terraform?

Linode has a great UI for doing all these things, why bother with Terraform?

Infrastructure as Code(IaC) is how to manage the complexity of software deployments in the cloud. At some point, sooner than you realize, your cloud footprint will be complex enough that you will forget how it is setup. Worse yet, if you have a team, nobody will know how things are setup unless there is meticulous documentation.

By forcing your configuration into code, you gain several benefits, including the ability to build reproducible environments, version control your configuration using tools like Git, and an ability to collaborate on changes safely with others.

A big part of my exploration is to see whether IaC can be accomplished successfully on Linode.

Stop ClickOps

When working in the cloud, all providers provide beautiful consoles and UIs to manage the cloud stack. They can be very enticing and many people fall into the trap of going click-happy to setup servers in their environment. Don't fall for it. Your future self and team will thank you later.

Utilize Terraform or some other code-driven mechanism to express your cloud configuration as code. This way you can safely make changes and ensure your cloud is in the state you want it to be. It reduces errors, miscommunications and incidents.

Use the Console for Visibility

Here's where you can use the console. Use it as a read-only view into the cloud. Use it to explore options you will exploit via API. Do not use it to provision or manage long-living resources.

Next Steps: Cluster Deep Dive

Now that we have a Linode cluster, I will be tearing it apart to see what they have done here, and whether this is a setup I can confidently use.

Subscribe to kekoav

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe