Create a Civo Kubernetes Cluster with Terraform
In this tutorial we will be creating a Kubernetes Cluster on Civo using Terraform and use the local provider to save the kubeconfig to a local file.
Once we have our Infrastructure up and running, we will deploy a Go web application to our cluster.
Pre-Requisites
You will require the following to follow this tutorial:
- a Civo Cloud account
- Terraform installed on your workstation
- Civo's Terraform Provider Documentation
Authentication
We require a Civo API Key to authenticate terraform with Civo, by heading to your security section from your account.
I will be saving the API Key to my environment as:
$ export TF_VAR_civo_api_key="x"
Configuration
First we create our providers.tf
which will include the providers that we will be using to create our Kubernetes cluster, which will be civo and local to download the kubeconfig and write it to a local file:
terraform {
required_providers {
civo = {
source = "civo/civo"
version = "0.10.9"
}
local = {
source = "hashicorp/local"
version = "2.1.0"
}
}
}
provider "civo" {
token = var.civo_api_key
}
provider "local" {}
Next our variables.tf
where we will define all our user-defined values for our cluster:
variable "civo_api_key" {
type = string
description = "civo api key to authenticate"
}
variable "civo_cluster_name" {
type = string
description = "civo kubernetes cluster name"
default = "ruan"
}
variable "civo_region" {
type = string
description = "civo region to use"
default = "LON1"
}
variable "civo_cluster_applications" {
type = string
description = "applications to deploy on kubernetes cluster"
default = "Traefik"
}
variable "civo_cluster_nodes" {
type = number
description = "kubernetes nodes count"
default = 1
}
variable "civo_nodes_size" {
type = string
description = "instance size to use for nodes"
default = "g3.k3s.xsmall"
}
Our main.tf
which will be our code to define the civo kubernetes cluster and the local file that we will write to our current working directory $PWD/kubeconfig.civo
which will include the kubeconfig that we will get from the civo_kubernetes_cluster
resource:
resource "civo_kubernetes_cluster" "ruan" {
name = var.civo_cluster_name
region = var.civo_region
applications = var.civo_cluster_applications
num_target_nodes = var.civo_cluster_nodes
target_nodes_size = var.civo_nodes_size
}
resource "local_file" "kubeconfig" {
content = civo_kubernetes_cluster.ruan.kubeconfig
filename = "${path.module}/kubeconfig.civo"
}
And lastly our outputs.tf
which will output information about our kubernetes cluster:
output "cluster_name" {
value = civo_kubernetes_cluster.ruan.name
}
output "applications" {
value = civo_kubernetes_cluster.ruan.applications
}
output "kubernetes_version" {
value = civo_kubernetes_cluster.ruan.kubernetes_version
}
output "api_endpoint" {
value = civo_kubernetes_cluster.ruan.api_endpoint
}
output "dns_entry" {
value = civo_kubernetes_cluster.ruan.dns_entry
}
Infrastructure Deployment
First we need to initialize terraform by downloading the providers:
$ terraform init
Initializing the backend...
...
Terraform has been successfully initialized!
To see what terraform will be creating, run a plan:
$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# civo_kubernetes_cluster.ruan will be created
+ resource "civo_kubernetes_cluster" "ruan" {
+ api_endpoint = (known after apply)
+ applications = "Traefik"
+ created_at = (known after apply)
+ dns_entry = (known after apply)
+ id = (known after apply)
+ installed_applications = (known after apply)
+ instances = (known after apply)
+ kubeconfig = (known after apply)
+ kubernetes_version = (known after apply)
+ master_ip = (known after apply)
+ name = "ruan"
+ network_id = (known after apply)
+ num_target_nodes = 1
+ pools = (known after apply)
+ ready = (known after apply)
+ region = "LON1"
+ status = (known after apply)
+ target_nodes_size = "g3.k3s.xsmall"
}
# local_file.kubeconfig will be created
+ resource "local_file" "kubeconfig" {
+ content = (known after apply)
+ directory_permission = "0777"
+ file_permission = "0777"
+ filename = "./kubeconfig.civo"
+ id = (known after apply)
}
Plan: 2 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ api_endpoint = (known after apply)
+ applications = "Traefik"
+ cluster_name = "ruan"
+ dns_entry = (known after apply)
+ kubernetes_version = (known after apply)
Once your are happy with what terraform will be creating, run apply:
$ terraform apply -auto-approve
...
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
Outputs:
api_endpoint = "https://x.x.x.x:6443"
applications = "Traefik"
cluster_name = "ruan"
dns_entry = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.k8s.civo.com"
kubernetes_version = "1.20.0-k3s1"
In our current directory, you will notice that our kubeconfig has been saved to kubeconfig.civo
:
$ ls | grep kubeconfig
kubeconfig.civo
Set the file to our KUBECONFIG
environment variable:
$ export KUBECONFIG=$PWD/kubeconfig.civo
Interact with the Kubernetes Cluster
List the nodes:
$ kubectl get nodes --output wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k3s-ruan-471f4933-node-pool-4696 Ready <none> 16m v1.20.2+k3s1 192.168.1.2 xx.xxx.xx.xx Ubuntu 20.04.2 LTS 5.4.0-80-generic containerd://1.4.3-k3s1
Software Deployment
Let's deploy a basic web application to our cluster, which is a Go webapp which returns the hostname from where the pod is running from.
If you follow along step-by-step, just make sure to replace the cluster id:
---
apiVersion: v1
kind: Service
metadata:
name: hostname-service
spec:
selector:
app: hostname
ports:
- protocol: TCP
port: 8000
targetPort: 8000
name: web
---
# for k3s => v1.19
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hostname-ingress
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
spec:
rules:
- host: hostname.your-cluster-id.k8s.civo.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: hostname-service
port:
number: 8000
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: hostname
name: hostname
spec:
replicas: 3
selector:
matchLabels:
app: hostname
template:
metadata:
labels:
app: hostname
spec:
containers:
- name: hostname
image: ruanbekker/hostname:latest
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8000
name: http
protocol: TCP
Deploy the application to the cluster:
$ kubectl apply -f app.yml
service/hostname-service created
ingress.networking.k8s.io/hostname-ingress created
deployment.apps/hostname created
After a couple of seconds, we can get the status of our deployment and getting the output of our ingress:
$ kubectl get ingress,deployment,pods --output wide
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/hostname-ingress <none> hostname.xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.k8s.civo.com 74.220.21.50 80 6m14s
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
deployment.apps/hostname 3/3 3 3 6m14s hostname ruanbekker/hostname:latest app=hostname
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod/hostname-5c5978694-7kpg4 1/1 Running 0 6m14s 10.42.1.3 k3s-ruan-471f4933-node-pool-f6b1 <none> <none>
pod/hostname-5c5978694-t7rql 1/1 Running 0 6m14s 10.42.1.4 k3s-ruan-471f4933-node-pool-f6b1 <none> <none>
pod/hostname-5c5978694-tbvwl 1/1 Running 0 6m14s 10.42.1.5 k3s-ruan-471f4933-node-pool-f6b1 <none> <none>
Testing the Application
As we can see our deployment is in it's desired state, we can test our application by making a http request to it:
$ curl http://hostname.your-cluster-id.k8s.civo.com
Hostname: hostname-5c5978694-7kpg4
Tear Down
To destroy the cluster, use terraform destroy:
$ terraform destroy -auto-approve
And remove the Civo API Key from your environment:
$ unset TF_VAR_civo_api_key
Resources
The container image:
The Civo provider:
Source code for this demonstration:
Video Walkthrough of using the Civo Provider by Saiyam Pathak
Thanks
Thanks for reading, if you like my content, check out my website or follow me at @ruanbekker on Twitter.