Part 2 - Deploy Kubernetes with Terraform and KubeOne

Introduction

This is the second part of my Blog Series "Deploy Kubernetes with Terraform and KubeOne". If you missed the first part of the series, follow this link to read it. In the first part, we talked about how we provide the basic infrastructure with Terraform.

What we got so far

At the moment we built the basic infrastructure on Hetzner Cloud. We got

  • 3 x Control Plane Server

  • 1 x Hetzner Firewall

  • 1 x Hetzner Network

  • 1 x Hetzner Load Balancer

The 3 Control Plane Servers are just basic Ubuntu 22.04 Servers with no additional software installed. Now it is time for KubeOne to actually do its job.

Default configuration

By default, KubeOne installs the following components:

  • Container Runtime: containerd for Kubernetes 1.22+ clusters, otherwise Docker

  • CNI: Canal (based on Calico and Flannel)

    • Cilium, WeaveNet, and user-provided CNI are supported as an alternative
  • metrics-server for collecting and exposing metrics from Kubelets

  • NodeLocal DNSCache for caching DNS queries to improve the cluster performance

  • Kubermatic machine controller, a Cluster-API-based implementation for managing worker nodes

It’s possible to configure which components are installed and how they are configured by adjusting the KubeOne configuration manifest. To see possible configuration options, refer to the configuration manifest reference which can be obtained by running kubeone config print --full.

Provision the Cluster

Now that the infrastructure is up and running, we can provision our Kubernetes Cluster with KubeOne. The first step is to create a KubeOne configuration manifest that describes our Cluster configuration, like the Kubernetes Version, Addons to be deployed and many more options. The manifest should be saved in a file called kubeone.yaml. Because we are using Hetzner Cloud, you need to configure KubeOne at least like this:

apiVersion: kubeone.k8c.io/v1beta2
kind: KubeOneCluster
versions:
  kubernetes: '1.25.6'
cloudProvider:
  hetzner: {}
  external: true

I customized the default configuration a little bit and added some plugins provided by Kubermatic. You can get a full list of addons on the KubeOne GitHub repository: https://github.com/kubermatic/kubeone/tree/main/addons

Of course, you can write your own add-ons, but this would be a separate topic.

In the first article, I mentioned KubeOne utilizes Terraform to configure the configuration. This information is used for accessing the instances, provisioning the cluster, and later creating the worker nodes. The format is already defined in a file called output.tf and everything you need to do is to run the output command:

terraform output -json > tf.json

This command creates a file called tf.json in the current directory. Its content is a JSON-formatted representation of the Terraform state that can be parsed by KubeOne. With our kubeone.yaml and the tf.json file, we are ready to provision our cluster. This is done by running the kubeone apply command and providing it the configuration manifest and the Terraform state file that we created in the previous step.

kubeone apply -m kubeone.yaml -t tf.json                                                                                                                                                              

INFO[16:42:43 CET] Determine hostname...
INFO[16:42:46 CET] Determine operating system...
INFO[16:42:46 CET] Running host probes...
INFO[16:42:49 CET] Electing cluster leader...
INFO[16:42:49 CET] Elected leader "hcloud-k8s-control-plane-1"...
INFO[16:42:52 CET] Building Kubernetes clientset...
INFO[16:42:53 CET] Running cluster probes...
The following actions will be taken:
Run with --verbose flag for more information.

    ~ ensure credential
    ~ ensure embedded addons
    ~ ensure custom addons
    ~ ensure external CCM

Do you want to proceed (yes/no): yes

INFO[22:43:14 CET] Determine hostname...
INFO[22:43:14 CET] Determine operating system...
INFO[22:43:14 CET] Installing prerequisites...
INFO[22:43:14 CET] Creating environment file...                  node=116.203.140.93 os=ubuntu
INFO[22:43:14 CET] Creating environment file...                  node=116.201.139.92 os=ubuntu
INFO[22:43:14 CET] Creating environment file...                  node=116.203.200.170 os=ubuntu
INFO[22:43:14 CET] Configuring proxy...                          node=116.201.139.92 os=ubuntu
INFO[22:43:14 CET] Installing kubeadm...                         node=116.201.139.92 os=ubuntu
INFO[22:43:14 CET] Configuring proxy...                          node=116.203.200.170 os=ubuntu
INFO[22:43:14 CET] Installing kubeadm...                         node=116.203.200.170 os=ubuntu
INFO[22:43:14 CET] Configuring proxy...                          node=116.203.140.93 os=ubuntu
INFO[22:43:14 CET] Installing kubeadm...                         node=116.203.140.93 os=ubuntu
...
...
INFO[22:49:54 CET] Installing machine-controller...
INFO[22:49:57 CET] Installing machine-controller webhooks...
INFO[22:49:58 CET] Waiting for machine-controller to come up...
INFO[22:50:34 CET] Creating worker machines...

At this point, your cluster is provisioned, but it may take several more minutes for worker nodes to get created by the Kubermatic machine controller and join the cluster. Meanwhile, you can configure access to the cluster.

Getting access to the cluster

KubeOne automatically downloads the kubeconfig file for the cluster. You can use it with kubectl such as:

kubectl --kubeconfig=<cluster_name>-kubeconfig

or export the KUBECONFIG environment variable:

export KUBECONFIG=$PWD/<cluster_name>-kubeconfig

Final Words

Awesome! We created our first Kubernetes Cluster with Kubermatic KubeOne! We are now able to deploy workloads on our Cluster.

Maybe there will be another article in the future for this series.

If there are any questions or problems, feel free to ask me in the comments or connect with me on my social media:

10
Subscribe to my newsletter

Read articles from Dominic Cardellino directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Dominic Cardellino
Dominic Cardellino