Deploy a self-managed Kubernetes cluster

This guide covers deploying a self-managed Kubernetes cluster on evroc: which components to install, in what order, and the settings that connect them. Each component's own README is the source of truth for its installation steps, configuration, and reference.

For an overview of the components, see Self-managed Kubernetes on evroc.

Prerequisites

Before you begin, you'll need:

  • The evroc CLI configured against your project, and kubectl on your workstation.
  • For the CAPI path: a management cluster with CAPE installed, so you have an evroc credentials secret and clusterctl configured.

1. Provision the cluster

There are two ways to provision a self-managed cluster on evroc:

  • Cluster API (CAPE) - the recommended path for production clusters. CAPE manages the full cluster lifecycle declaratively from a management cluster. Scaling, rolling upgrades, and rollouts are first-class operations: change a field on the Cluster or MachineDeployment resource and CAPE handles the rest: cordon/drain, VM replacement, and health checking. Follow the CAPE Quick Start.
  • Terraform - the evroc Terraform provider can provision VMs and networking. The k3s-cluster example boots a small k3s cluster directly. This is best suited to small, throwaway test clusters where you don't need the lifecycle management CAPE provides. Terraform provisions infrastructure but doesn't manage the in-cluster lifecycle: scaling out means manually adding VM resources and bootstrapping nodes, upgrades mean replacing VMs and re-running kubeadm/k3s yourself, and rollouts are manual.

2. CAPI: set cloud-provider=external

When using CAPI, one setting is required for the evroc CCM to manage nodes: every kubelet must run with --cloud-provider=external, so the CCM (not the distribution's built-in cloud controller) initializes nodes. Add it to kubeletExtraArgs in both the control-plane and worker bootstrap configs:

spec:
  kubeadmConfigSpec:
    initConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
        - name: cloud-provider
          value: external
    joinConfiguration:
      nodeRegistration:
        kubeletExtraArgs:
        - name: cloud-provider
          value: external

Important: Without --cloud-provider=external, the distribution's own cloud controller initializes nodes first and the CCM is never consulted. On k3s and RKE2 (including the Terraform k3s example), set disable-cloud-controller: true and kubelet-arg: ["cloud-provider=external"] instead.

Apply the cluster and wait for the machines to reach Running:

kubectl get machines -w

At this point nodes are NotReady because no CNI is installed. They also carry the node.cloudprovider.kubernetes.io/uninitialized taint, which stays until the CCM initializes them.

3. Install a CNI

Install a CNI, such as Cilium CNI. Most CNIs, including Cilium and Calico tolerate the node.cloudprovider.kubernetes.io/uninitialized taint, so their agent runs on nodes before the CCM has initialized them. This is required, since pods need a CNI to communicate and the CCM itself runs as pods.

For example, with Cilium run

clusterctl get kubeconfig <cluster-name> > <cluster-name>.kubeconfig
cilium install --kubeconfig <cluster-name>.kubeconfig --set ipam.operator.clusterPoolIPv4PodCIDRList=192.168.0.0/16

Note: The CAPE default and prebuilt flavors install Cilium automatically via postKubeadmCommands, so this step is only needed for custom templates without a built-in CNI.

Note: Cilium by default uses a clusterPoolIPv4PodCIDRList value of 10.0.0.0/8 for IPAM within a Kubernetes cluster, which can often overlap with IPv4 address in your evroc cloud, so we recommend using 192.168.0.0/16 or another Private IPv4 range.

Nodes remain NotReady and carry the uninitialized taint until the CCM runs.

4. Install the CCM

Follow the CCM Quickstart to create the kubernetesCCMAgent service account, build the config secret, and install the Helm chart into your Kubernetes cluster.

The CCM must be installed after the CNI, because it runs as pods that need a working network, and before the CSI driver, because the CSI driver requires the topology.kubernetes.io/zone node labels the CCM sets.

Once the CCM is running, it initializes each node: setting the provider ID, adding topology labels, syncing addresses, and removing the node.cloudprovider.kubernetes.io/uninitialized taint. Verify:

kubectl --kubeconfig <cluster-name>.kubeconfig get nodes \
  -o custom-columns='NAME:.metadata.name,READY:.status.conditions[-1].type,REGION:.metadata.labels.topology\.kubernetes\.io/region,ZONE:.metadata.labels.topology\.kubernetes\.io/zone'

When all nodes are Ready with region and zone labels, continue to the CSI driver.

5. Install the CSI driver

Follow the CSI driver README to create the kubernetesCSIAgent service account, build the config secret, and install the Helm chart into your Kubernetes cluster.

The CSI driver requires the topology.kubernetes.io/zone node label for topology-aware volume placement, so it must be installed after the CCM has initialized the nodes. To make evroc-standard the cluster's default StorageClass, pass --set storageClass.isDefault=true when installing the chart.

What your cluster can do now

Your cluster is integrated with the evroc cloud platform. You can deploy workloads and:

  • Create PersistentVolumeClaims - the CSI driver provisions evroc Disks automatically.
  • Create type: LoadBalancer Services - the CCM provisions evroc load balancers and public IPs automatically.

Next steps