As soon as you run more than one Kubernetes cluster (production and staging, or one cluster per region), you need a reliable way to reach each of them and to know which one a command will hit. In this tutorial you will merge several kubeconfig files into one, switch clusters and namespaces with kubectl contexts and kubectx/kubens, build a separate read-only kubeconfig for automation, and run a command against every cluster at once. Everything runs on an Ubuntu 24.04 workstation.

Prerequisites

To follow this guide you need:

  • An Ubuntu 24.04 workstation (or a CubePath VPS used as a jump host) with kubectl installed. Check with kubectl version --client.
  • Admin kubeconfig files for two or more clusters. On kubeadm clusters it is /etc/kubernetes/admin.conf on a control plane node; on k3s it is /etc/rancher/k3s/k3s.yaml.
  • Network access from the workstation to each cluster's API server, usually TCP port 6443.

This guide uses two example clusters called prod and staging. Replace the names, hosts and paths with your own.

Step 1 - Understanding the kubeconfig structure

A kubeconfig file has three lists and one pointer:

SectionWhat it holds
clustersAPI server URL and the CA certificate used to trust it
usersCredentials: client certificate, token or an exec plugin
contextsA named combination of one cluster, one user and an optional default namespace
current-contextThe context kubectl uses when you do not pass --context

Switching clusters means switching contexts. Check what your current file contains:

kubectl config get-contexts
CURRENT   NAME                          CLUSTER      AUTHINFO           NAMESPACE
*         kubernetes-admin@kubernetes   kubernetes   kubernetes-admin

Most installers name everything kubernetes or default. If you merge two files with identical names, entries from the first file win and the second cluster silently disappears. That is why every file gets unique names before merging.

Step 2 - Collecting and renaming the kubeconfig files

Create a directory for the individual files with restrictive permissions, since they contain admin credentials:

mkdir -p ~/.kube/clusters
chmod 700 ~/.kube ~/.kube/clusters

Copy the admin kubeconfig of each cluster. With kubeadm the file is only readable by root, so read it with sudo cat over SSH (this needs passwordless sudo on the server; otherwise copy the file to your home directory on the server first and fetch it with scp):

ssh your_user@prod_control_plane_ip 'sudo cat /etc/kubernetes/admin.conf' > ~/.kube/clusters/prod.yaml
ssh your_user@staging_control_plane_ip 'sudo cat /etc/kubernetes/admin.conf' > ~/.kube/clusters/staging.yaml
chmod 600 ~/.kube/clusters/*.yaml

For k3s, use /etc/rancher/k3s/k3s.yaml and then replace https://127.0.0.1:6443 in the copied file with the server's real address.

Now give each file unique names. Renaming the context is one command:

kubectl --kubeconfig ~/.kube/clusters/prod.yaml config rename-context kubernetes-admin@kubernetes prod
kubectl --kubeconfig ~/.kube/clusters/staging.yaml config rename-context kubernetes-admin@kubernetes staging

The cluster and user entries have no rename command, so edit them with sed, which also updates the references inside the context. For the production file:

sed -i -e 's/^  name: kubernetes$/  name: prod/' \
       -e 's/cluster: kubernetes$/cluster: prod/' \
       -e 's/^- name: kubernetes-admin$/- name: prod-admin/' \
       -e 's/user: kubernetes-admin$/user: prod-admin/' \
       ~/.kube/clusters/prod.yaml

Run the same command on staging.yaml, replacing prod with staging. Then verify each file still works on its own:

kubectl --kubeconfig ~/.kube/clusters/prod.yaml get nodes
kubectl --kubeconfig ~/.kube/clusters/staging.yaml get nodes

Both commands should list the nodes of the right cluster. If one fails with context was not found or user not found, open the file with nano and check that the names in contexts match those in clusters and users.

Step 3 - Merging the files into one kubeconfig

kubectl can read several files at once through the KUBECONFIG variable, a colon-separated list. Combined with kubectl config view --flatten, which embeds all certificates, this produces a single self-contained file.

Back up your current config first, if you have one:

cp ~/.kube/config ~/.kube/config.bak 2>/dev/null || true

Merge the files into a temporary file and move it into place. Never redirect the output directly to ~/.kube/config while kubectl is reading it, or you end up with an empty file:

KUBECONFIG=~/.kube/clusters/prod.yaml:~/.kube/clusters/staging.yaml kubectl config view --flatten > /tmp/kubeconfig.merged
install -m 600 /tmp/kubeconfig.merged ~/.kube/config
rm /tmp/kubeconfig.merged

List the contexts again:

kubectl config get-contexts
CURRENT   NAME      CLUSTER   AUTHINFO        NAMESPACE
*         prod      prod      prod-admin
          staging   staging   staging-admin

When you add a third cluster later, prepare its file as in Step 2 and repeat the merge with ~/.kube/config as the first entry of KUBECONFIG.

Step 4 - Switching clusters and namespaces

Select the cluster you want to work with:

kubectl config use-context staging
kubectl config current-context
Switched to context "staging".
staging

The selected context is saved in the kubeconfig file, so it also applies to every other terminal. For one-off commands it is safer to pass the context explicitly, which does not change the global state:

kubectl --context prod get nodes

Each context can also carry a default namespace, which saves typing -n all the time:

kubectl config set-context staging --namespace=my-app

The NAMESPACE column of kubectl config get-contexts now shows my-app for staging.

Step 5 - Installing kubectx and kubens

kubectx and kubens are two small tools that shorten context and namespace switching. Ubuntu 24.04 packages them in the universe repository:

sudo apt update
sudo apt install kubectx

The package installs both commands. Switch contexts and namespaces:

kubectx prod
kubens kube-system
Switched to context "prod".
Context "prod" modified.
Active namespace is "kube-system".

Other useful forms:

kubectx
kubectx -
kubectx -c
kubens -

kubectx alone lists the contexts and highlights the current one, kubectx - returns to the previous context, kubectx -c prints the current one, and kubens - returns to the previous namespace. If fzf is installed (sudo apt install fzf), running kubectx or kubens without arguments opens an interactive picker instead.

You can also shorten long context names: kubectx p=prod renames the prod context to p.

Step 6 - Creating a read-only kubeconfig for automation

Do not give CI pipelines, dashboards or colleagues a copy of the admin kubeconfig. Create a dedicated ServiceAccount with only the permissions it needs and a kubeconfig that uses its token. This example gives read-only access with the built-in view ClusterRole.

Run these commands against the target cluster:

kubectl --context prod create namespace automation
kubectl --context prod -n automation create serviceaccount ci-reader
kubectl --context prod create clusterrolebinding ci-reader-view --clusterrole=view --serviceaccount=automation:ci-reader

Since Kubernetes 1.24, ServiceAccounts no longer get a token Secret automatically. Create a long-lived token Secret explicitly:

nano ci-reader-token.yaml
apiVersion: v1
kind: Secret
metadata:
  name: ci-reader-token
  namespace: automation
  annotations:
    kubernetes.io/service-account.name: ci-reader
type: kubernetes.io/service-account-token
kubectl --context prod apply -f ci-reader-token.yaml

Read the token, the CA certificate and the API server URL into shell variables:

TOKEN=$(kubectl --context prod -n automation get secret ci-reader-token -o jsonpath='{.data.token}' | base64 -d)
CA_DATA=$(kubectl --context prod -n automation get secret ci-reader-token -o jsonpath='{.data.ca\.crt}')
SERVER=$(kubectl config view --minify --context prod -o jsonpath='{.clusters[0].cluster.server}')

Build a new kubeconfig file with those values. The --kubeconfig flag makes sure your own config is not modified:

KCFG=~/ci-reader-prod.kubeconfig
kubectl --kubeconfig "$KCFG" config set-cluster prod --server="$SERVER"
kubectl --kubeconfig "$KCFG" config set clusters.prod.certificate-authority-data "$CA_DATA"
kubectl --kubeconfig "$KCFG" config set-credentials ci-reader --token="$TOKEN"
kubectl --kubeconfig "$KCFG" config set-context ci-reader@prod --cluster=prod --user=ci-reader
kubectl --kubeconfig "$KCFG" config use-context ci-reader@prod
chmod 600 "$KCFG"

Check what the new identity can and cannot do:

kubectl --kubeconfig ~/ci-reader-prod.kubeconfig get pods -A | head -n 3
kubectl --kubeconfig ~/ci-reader-prod.kubeconfig auth can-i delete pods -A
NAMESPACE     NAME                               READY   STATUS    RESTARTS   AGE
kube-system   coredns-7c65d6cfc9-5hnbw           1/1     Running   0          12d
kube-system   coredns-7c65d6cfc9-kqz8x           1/1     Running   0          12d
no

Listing pods works and deleting is denied. Store this file as a secret in your CI system. To revoke access, delete the Secret; the token stops working immediately.

Step 7 - Running a command against every cluster

For quick checks across your fleet, loop over the contexts. kubectl config get-contexts -o name prints only the names, one per line:

for ctx in $(kubectl config get-contexts -o name); do
  echo "== ${ctx}"
  kubectl --context "$ctx" get nodes --no-headers
done
== prod
prod-cp-1       Ready   control-plane   40d   v1.33.4
prod-worker-1   Ready   <none>          40d   v1.33.4
== staging
staging-cp-1    Ready   control-plane   12d   v1.33.4

The loop uses --context on every call, so it never changes your current context. Use it for read-only checks such as versions, node status or certificate expiry; for changes, apply manifests to each cluster explicitly or use a GitOps tool.

Choosing tools for larger fleets

Contexts and kubectx are enough for a handful of clusters managed by a small team. As the number of clusters grows, these tools cover the next problems:

NeedTool
Same applications deployed to many clusters from GitArgo CD (ApplicationSets) or Flux
Web UI, central RBAC and cluster importRancher
Creating and upgrading the clusters themselves declarativelyCluster API

Each of them builds on the kubeconfig and RBAC concepts from this guide: they still need a credential per cluster, ideally a dedicated ServiceAccount rather than an admin certificate.

Troubleshooting

Unable to connect to the server: x509: certificate is valid for ..., not .... You are reaching the API server through an address that is not in its certificate, typically a public IP when the certificate lists only private ones. Use an address included in the certificate, or add it to the API server's certificate SANs (on kubeadm, apiServer.certSANs).

A context disappeared after merging. Two files used the same cluster, user or context name. Rename the entries as in Step 2 and merge again from your backup files.

error: You must be logged in to the server (Unauthorized). The credential expired or was revoked. kubeadm admin client certificates are valid for one year; renew them with sudo kubeadm certs renew admin.conf on the control plane and copy the file again.

Conclusion

You now have a single kubeconfig with clearly named contexts, fast switching with kubectx and kubens, a least-privilege kubeconfig for automation, and a safe way to query all clusters at once. Next, consider adding the current context to your shell prompt so you always see which cluster you are on, moving deployments to a GitOps tool, and replacing long-lived tokens with short-lived ones where your tooling supports it.