> ## Documentation Index
> Fetch the complete documentation index at: https://docs.siderolabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Migrating from Kubeadm

> Transition an existing kubeadm cluster to Talos with a controlled, node-by-node migration process.

export const VersionWarningBanner = () => {
  const latestVersion = "v1.13";
  const [latestUrl, setLatestUrl] = useState(null);
  const [currentVersion, setCurrentVersion] = useState(null);
  const [isBeta, setIsBeta] = useState(false);
  const parseVersion = v => v.replace("v", "").split(".").map(Number);
  const isGreaterVersion = (a, b) => {
    const [aMajor, aMinor] = parseVersion(a);
    const [bMajor, bMinor] = parseVersion(b);
    if (aMajor > bMajor) return true;
    if (aMajor === bMajor && aMinor > bMinor) return true;
    return false;
  };
  useEffect(() => {
    if (typeof window === "undefined") return;
    const {pathname, hash, search} = window.location;
    const match = pathname.match(/\/talos\/(v\d+\.\d+)\//);
    if (!match) return;
    const detectedVersion = match[1];
    if (detectedVersion === latestVersion) return;
    setCurrentVersion(detectedVersion);
    if (isGreaterVersion(detectedVersion, latestVersion)) {
      setIsBeta(true);
    }
    const newPath = pathname.replace(`/talos/${detectedVersion}/`, `/talos/${latestVersion}/`);
    setLatestUrl(`${newPath}${search}${hash}`);
  }, []);
  if (!latestUrl || !currentVersion) return null;
  return <div className="not-prose sticky top-6 z-50 my-6">
      <div className="border border-yellow-500/30 bg-yellow-500/10 px-4 py-3 rounded-xl">
        <div className="text-sm">
          {isBeta ? <>
              ⚠️ You are viewing a <strong>beta version</strong> of Talos ({currentVersion}).
              This version may be unstable.
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View latest stable version {latestVersion} →
              </a>
            </> : <>
              ⚠️ You are viewing an older version of Talos ({currentVersion}).
              <a href={latestUrl} className="ml-2 underline text-yellow-400 hover:text-yellow-300 font-medium">
                View the latest version {latestVersion} →
              </a>
            </>}
        </div>
      </div>
    </div>;
};

<VersionWarningBanner />

It is possible to migrate Talos from a cluster that is created using
[kubeadm](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/create-cluster-kubeadm/) to Talos.

High-level steps are the following:

1. Collect CA certificates and a bootstrap token from a control plane node.
2. Create a Talos machine config with the CA certificates with the ones you collected.
3. Update control plane endpoint in the machine config to point to the existing control plane (i.e. your load balancer address).
4. Boot a new Talos machine and apply the machine config.
5. Verify that the new control plane node is ready.
6. Remove one of the old control plane nodes.
7. Repeat the same steps for all control plane nodes.
8. Verify that all control plane nodes are ready.
9. Repeat the same steps for all worker nodes, using the machine config generated for the workers.

## Remarks on kube-apiserver load balancer

While migrating to Talos, you need to make sure that your kube-apiserver load balancer is in place
and keeps pointing to the correct set of control plane nodes.

This process depends on your load balancer setup.

If you are using an LB that is external to the control plane nodes (e.g. cloud provider LB, F5 BIG-IP, etc.),
you need to make sure that you update the backend IPs of the load balancer to point to the control plane nodes as
you add Talos nodes and remove kubeadm-based ones.

If your load balancing is done on the control plane nodes (e.g. keepalived + haproxy on the control plane nodes),
you can do the following:

1. Add Talos nodes and remove kubeadm-based ones while updating the haproxy backends
   to point to the newly added nodes except the last kubeadm-based control plane node.
2. Turn off keepalived to drop the virtual IP used by the kubeadm-based nodes (introduces kube-apiserver downtime).
3. Set up a virtual-IP based new load balancer on the new set of Talos control plane nodes.
   Use the previous LB IP as the LB virtual IP.
4. Verify apiserver connectivity over the Talos-managed virtual IP.
5. Migrate the last control-plane node.

## Prerequisites

* Admin access to the kubeadm-based cluster
* Access to the `/etc/kubernetes/pki` directory (e.g. SSH & root permissions)
  on the control plane nodes of the kubeadm-based cluster
* Access to kube-apiserver load-balancer configuration

## Step-by-step guide

1. Download `/etc/kubernetes/pki` directory from a control plane node of the kubeadm-based cluster.

2. Create a new join token for the new control plane nodes:

   ```bash theme={null}
   # inside a control plane node
   kubeadm token create --ttl 0
   ```

3. Create Talos secrets from the PKI directory you downloaded on step 1 and the token you generated on step 2:

   ```bash theme={null}
   talosctl gen secrets --kubernetes-bootstrap-token <TOKEN> --from-kubernetes-pki <PKI_DIR>
   ```

4. Create a new Talos config from the secrets:

   ```bash theme={null}
   talosctl gen config --with-secrets secrets.yaml <CLUSTER_NAME> https://<EXISTING_CLUSTER_LB_IP>
   ```

5. Collect the information about the kubeadm-based cluster from the kubeadm configmap:

   ```bash theme={null}
   kubectl get configmap -n kube-system kubeadm-config -oyaml
   ```

   Take note of the following information in the `ClusterConfiguration`:

   * `.controlPlaneEndpoint`
   * `.networking.dnsDomain`
   * `.networking.podSubnet`
   * `.networking.serviceSubnet`

6. Replace the following information in the generated `controlplane.yaml`:
   * `.cluster.network.cni.name` with `none`
   * `.cluster.network.podSubnets[0]` with the value of the `networking.podSubnet` from the previous step
   * `.cluster.network.serviceSubnets[0]` with the value of the `networking.serviceSubnet` from the previous step
   * `.cluster.network.dnsDomain` with the value of the `networking.dnsDomain` from the previous step

7. Go through the rest of `controlplane.yaml` and `worker.yaml` to customize them according to your needs, especially :
   * `.cluster.secretboxEncryptionSecret` should be either removed if you don't currently use `EncryptionConfig` on your `kube-apiserver` or set to the correct value

8. Make sure that, on your current Kubeadm cluster, the first `--service-account-issuer=` parameter in `/etc/kubernetes/manifests/kube-apiserver.yaml` is equal to the value of `.cluster.controlPlane.endpoint` in `controlplane.yaml`.
   If it's not, add a new `--service-account-issuer=` parameter with the correct value before your current one in `/etc/kubernetes/manifests/kube-apiserver.yaml` on all of your control planes nodes, and restart the kube-apiserver containers.

9. Bring up a Talos node to be the initial Talos control plane node.

10. Apply the generated `controlplane.yaml` to the Talos control plane node:

    ```bash theme={null}
    talosctl --nodes <TALOS_NODE_IP> apply-config --insecure --file controlplane.yaml
    ```

11. Wait until the new control plane node joins the cluster and is ready.

    ```bash theme={null}
    kubectl get node -owide --watch
    ```

12. Update your load balancer to point to the new control plane node.

13. Drain the old control plane node you are replacing:

    ```bash theme={null}
    kubectl drain <OLD_NODE> --delete-emptydir-data --force --ignore-daemonsets --timeout=10m
    ```

14. Remove the old control plane node from the cluster:

    ```bash theme={null}
    kubectl delete node <OLD_NODE>
    ```

15. Destroy the old node:

    ```bash theme={null}
    # inside the node
    sudo kubeadm reset --force
    ```

16. Repeat the same steps, starting from step 7, for all control plane nodes.

17. Repeat the same steps, starting from step 7, for all worker nodes while applying the `worker.yaml` instead and skipping the LB step:

    ```bash theme={null}
    talosctl --nodes <TALOS_NODE_IP> apply-config --insecure --file worker.yaml
    ```

18. Your kubeadm `kube-proxy` configuration may not be compatible with the one generated by Talos, which will make the Talos Kubernetes upgrades impossible (labels may not be the same, and `selector.matchLabels` is an immutable field).
    To be sure, export your current kube-proxy daemonset manifest, check the labels, they have to be:

    ```yaml theme={null}
    tier: node
    k8s-app: kube-proxy
    ```

    If they are not, modify all the labels fields, save the file, delete your current kube-proxy daemonset, and apply the one you modified.

## Limitations on custom PKI

Talos always uses a per-cluster PKI model.
During bootstrap, Talos expects a single root CA to issue all other certificates, including those for etcd, the Kubernetes API server, and the front-proxy.

Talos does not support kubeadm PKIs that rely on intermediate CAs (for example, a root CA with separate intermediates for different components).
By design, both `--cluster-signing-cert-file` and `--root-ca-file` point to the same CA certificate, and these values cannot be overridden.

If your kubeadm cluster uses an intermediate CA hierarchy, you cannot directly reuse that PKI with Talos.
Instead, you must regenerate certificates using the Talos per-cluster CA model.
