Airgapped Kubernetes Cluster with containerd
- 2 minutes read - 349 wordsAfter evaluating several local Kubernetes solutions I encountered repeated manual steps (downloading bootstrap images, pulling images from registry mirrors and retagging them, and loading images into clusters) that were time-consuming and error-prone. I decided to set up a properly air-gapped Kubernetes cluster using kubeadm and containerd, leveraging containerd’s registry mirror support. This post documents the steps I followed.
Prerequisites
This guide assumes a Debian/Ubuntu host. Installing the latest Docker Engine will also provide containerd as a dependency.
# Remove any conflicting packages (if present)
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1) || true
# Add Docker's official GPG key and repository
sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Setting up containerd with registry mirrors
-
Generate the default containerd config:
sudo containerd config default > /etc/containerd/config.toml
-
Enable the certs.d config path for containerd’s CRI image registry plugin by adding the registry config path to the containerd config:
[plugins."io.containerd.cri.v1.images".registry]
config_path = "/etc/containerd/certs.d"
-
Create registry hosts configuration for the mirror. Example structure and content:
sudo mkdir -p /etc/containerd/certs.d/docker.io
sudo tee /etc/containerd/certs.d/docker.io/hosts.toml > /dev/null <<'EOF'
server = "https://docker.io"
[host."https://docker-remote.example.corp"]
capabilities = ["pull", "resolve"]
EOF
# Restart containerd to apply changes
sudo systemctl restart containerd
Notes: - Adjust the mirror URL (https://docker-remote.example.corp) to point to your internal registry mirror. - The registry configuration tells containerd to resolve and pull images through the specified mirror.
Initialize the Kubernetes cluster with kubeadm
Generate a kubeadm configuration using the host IP and initialize the cluster:
IPADDR=$(ip -4 addr show eth0 | grep -oP 'inet \K[\d.]+' | head -1)
cat <<EOF > kubeadm-config.yaml
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
kubernetesVersion: 1.34.1
advertiseAddress: "${IPADDR}"
---
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
failSwapOn: false
EOF
sudo kubeadm init --config kubeadm-config.yaml
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config