Skip to main content

Kubernetes Cluster API

Learn how to provision a Kubernetes cluster with Cluster API

Kubernetes Cluster API

Cluster API is a Kubernetes sub-project focused on providing declarative APIs and tooling to simplify provisioning, upgrading, and operating multiple Kubernetes clusters.

Started by the Kubernetes Special Interest Group (SIG) Cluster Lifecycle, the Cluster API project uses Kubernetes-style APIs and patterns to automate cluster lifecycle management for platform operators. The supporting infrastructure, like virtual machines, networks, load balancers, and VPCs, as well as the Kubernetes cluster configuration are all defined in the same way that application developers operate deploying and managing their workloads. This enables consistent and repeatable cluster deployments across a wide variety of infrastructure environments.

This guide will show you how to deploy a Kubernetes cluster using the Equinix Metal Cluster API provider (CAPEM).

This guide assumes that you have an existing Kubernetes cluster available to run as your management cluster. For testing, you can use Kind, minikube, or Docker for Mac. For production, we recommend you take a look at our guide for building a resilient k3s management plane on Equinix Metal.

NOTE: when testing on macOS, we recommend using minikube with podman to create your management cluster to avoid a Docker Desktop networking issue that blocks creation of workload cluster nodes.


These terms are defined in the Cluster API documentation, but are replicated here to save you a few clicks.

Management Cluster

A Kubernetes cluster that manages the lifecycle of Workload Clusters. A Management Cluster is also where one or more Infrastructure Providers run, and where resources such as Machines are stored.

Workload Cluster

A Kubernetes cluster whose lifecycle is managed by a Management Cluster.


The provider is still working out how to safely migrate it's name and conventions to Equinix Metal. As such, you'll see packet used in parts of this guide. We'll keep it updated as the migration evolves.

Cluster API CLI

We'll be using the Cluster API CLI to provision Cluster API in our management cluster and to generate the manifests of our workload cluster.

curl -L$(uname | tr '[:upper:]' '[:lower:]')-$(uname -m) -o clusterctl
chmod +x ./clusterctl
sudo mv ./clusterctl /usr/local/bin/clusterctl

Deploying Cluster API to the Management Cluster

Using clusterctl

We need to export our API key so that the provider can use it during bootstrap. You can use a project level API key, or a user API key.


Next, we can use the clusterctl command to deploy the CAPI controllers to our management cluster, using the --infrastructure flag to request that the Equinix Metal provider also be deployed.

clusterctl init --infrastructure packet


If you'd prefer not to use clusterctl, you can deploy the manifests yourself.

Cluster API

We first need to specify which version of Cluster API to install. We can fetch this via curl.

export VERSION=$(curl | jq -r ".name")

curl -o capi.yaml -fsSL${VERSION}/cluster-api-components.yaml

We now have capi.yaml, which is the description of the Cluster API workloads we need to deploy to the management cluster. We cannot apply this directly to the cluster because there are some values that need to be provided first. You'll need to search for EXP_ in the YAML and handle accordingly.

When you've handled these feature flags, you can apply the manifests.

kubectl apply -f capi.yaml

Equinix Metal Provider

Very similiar to what we did for the Cluster API component, we can do the same for the Equinix Metal provider.

export VERSION=$(curl | jq -r ".name")

curl -o capem.yaml -fsSL${VERSION}/infrastructure-components.yaml

kubectl apply -f capem.yaml

Provisioning a Workload Cluster

Using clusterctl

The Cluster API CLI provides a config cluster helper that will generate the required manifests based on some environment variables. The environment variables you require are:

# The Project to deploy the new devices to

# The facility where you want your cluster to be provisioned
export FACILITY="fr2"

# The operating system to use
export NODE_OS="ubuntu_20_04"

# The pod and service CIDRs for the new cluster
export POD_CIDR=""
export SERVICE_CIDR=""

# Device type to use for control plane and worker nodes
export CONTROLPLANE_NODE_TYPE="c3.medium.x86"
export WORKER_NODE_TYPE="c3.medium.x86"

# SSH key to use for access to nodes

With our configuration set, we can now ask clusterctl to generate the manifests. Feel free to modify the Kubernetes version, control plane node count (You can use 1 or 3), and worker node count (You can use any number).

clusterctl generate cluster my-cluster-name \
  --kubernetes-version v1.21.2 \
  --control-plane-machine-count=3 \
  --worker-machine-count=3 \
  > my-cluster-name.yaml

All that's left now is to apply the manifest and wait for the workload cluster to be created.

kubectl apply -f my-cluster-name.yaml


We strongly encourage you to use the clusterctl config cluster approach above to generate the base configuration for your workload cluster. However, you may wish to add further node pools with different device configurations.

To do so, you can copy and modify some of the generated manifests from the steps above.

Machine Templates

You can define additional machine templates that can be used. This allows you to provide additional device types and operating systems to be used in your node pools.

kind: PacketMachineTemplate
  name: "my-cluster-name-storage-workers"
      OS: "ubuntu_20_04"
      billingCycle: hourly
      machineType: "s3.xlarge.x86"
      tags: []

Kubeadm Config Templates

In order to add new node pools to our workload cluster, we need to define a KubeadmConfigTemplate that tells Cluster API and kubeadm how to bootstrap the node. These should be defined for each device type, as you may need to tweak the kernel modules or disks for each; however, we've had success using a generic template across multiple device types on Equinix Metal. Your milage may vary.

kind: KubeadmConfigTemplate
  name: "my-cluster-name-worker-node"
        - sed -ri '/\sswap\s/s/^#?/#/' /etc/fstab
        - swapoff -a
        - mount -a
        - |
          cat << EOF > /etc/modules-load.d/containerd.conf
        - modprobe overlay
        - modprobe br_netfilter
        - |
          cat << EOF > /etc/sysctl.d/99-kubernetes-cri.conf
          net.bridge.bridge-nf-call-iptables  = 1
          net.ipv4.ip_forward                 = 1
          net.bridge.bridge-nf-call-ip6tables = 1
        - sysctl --system
        - apt-get -y update
        - DEBIAN_FRONTEND=noninteractive apt-get install -y apt-transport-https curl
        - curl -s | apt-key add -
        - echo "deb kubernetes-xenial main" > /etc/apt/sources.list.d/kubernetes.list
        - apt-get update -y
        - TRIMMED_KUBERNETES_VERSION=$(echo {{ .kubernetesVersion }} | sed 's/\./\\./g' | sed 's/^v//')
        - RESOLVED_KUBERNETES_VERSION=$(apt-cache policy kubelet | awk -v VERSION=$${TRIMMED_KUBERNETES_VERSION} '$1~ VERSION { print $1 }' | head -n1)
        - apt-get install -y ca-certificates socat jq ebtables apt-transport-https cloud-utils prips containerd kubelet=$${RESOLVED_KUBERNETES_VERSION} kubeadm=$${RESOLVED_KUBERNETES_VERSION} kubectl=$${RESOLVED_KUBERNETES_VERSION}
        - systemctl daemon-reload
        - systemctl enable containerd
        - systemctl start containerd
            cloud-provider: external

Machine Deployments

The MachineDeployment custom resource is the glue that joins together our KubeadmConfigTemplate and MachineTemplate to provide a node pool for your workload cluster.

kind: MachineDeployment
  name: my-cluster-name-worker-pool-1
  labels: my-cluster-name
    pool: worker-pool-1
  replicas: 3
  clusterName: my-cluster-name
    matchLabels: my-cluster-name
      pool: worker-pool-1
      labels: my-cluster-name
        pool: worker-pool-1
      version: v1.21.2
      clusterName: my-cluster-name
          # This name is the name of your `KubeadmConfigTemplate`
          name: my-cluster-name-worker-node
          kind: KubeadmConfigTemplate
        # This name is the name of your `PacketMachineTemplate`
        name: my-cluster-name-storage-workers
        kind: PacketMachineTemplate

Getting Your Cluster Ready

Once you've applied your desired cluster resources to your management cluster, you should see devices spinning up and being provisioned. However, your cluster won't be "Ready" until you've deployed a Container Networking Interface (CNI) implementation.

In order to do so, you need to get the kubeconfig for your workload cluster.

clusterctl get kubeconfig my-cluster-name > my-cluster-name.kubeconfig

If you wish to automate this, you can use ClusterResourceSets. Please note that they're currently behind a feature flag and experimental.

Now, using the kubeconfig, you can apply your CNI of choice. You can deploy any CNI implementation, but for this guide we'll use Calico.

kubectl --kubeconfig=./my-cluster-name.kubeconfig \
  apply -f

Last updated

06 August 2021


Subscribe to our newsletter

A monthly digest of the latest news, articles, and resources.