Airgap installatie: alle Kubernetes tools zonder internet
Een airgap-omgeving is een netwerk zonder verbinding met het internet — gebruikelijk in overheids-, financiële en industriële omgevingen met strenge beveiligingseisen. Het installeren van Kubernetes-tools in een airgap-omgeving vereist een andere aanpak: alle images, binaries, Helm charts en packages moeten vooraf worden gedownload en via een interne registry of bestandsserver worden aangeboden. Dit artikel behandelt de airgap-procedure voor alle tools die in onze kennisbank worden beschreven.
Algemene aanpak: de drie stappen
- Downloaden — haal alles op via een machine met internetverbinding (de “bastion” of “jump host”)
- Overdragen — kopieer via een goedgekeurd medium (USB, gecontroleerde bestandsoverdracht, geïsoleerde DMZ-server)
- Installeren — installeer vanuit de interne locatie in de airgap-omgeving
Vereiste interne infrastructuur
Voordat je begint heb je in de airgap-omgeving nodig:
- Container registry — bijv. Harbor, Quay of een eenvoudige Docker Registry v2 voor het hosten van container images
- Helm chart repository — een eenvoudige HTTP-server (nginx, Apache) met chart-bestanden
- Bestandsserver — voor het hosten van binaries (terraform, kustomize, kubectl, helm)
- Package mirror (optioneel) — bijv. een lokale apt/yum-mirror voor systeem packages
1. Kubernetes (kubeadm) — airgap installatie
Op de machine MET internet (voorbereiding)
# Stap 1: Vereiste images ophalen
kubeadm config images list # Toon welke images nodig zijn
# Images downloaden
kubeadm config images pull
# Images exporteren naar tar
docker save \
registry.k8s.io/kube-apiserver:v1.30.0 \
registry.k8s.io/kube-controller-manager:v1.30.0 \
registry.k8s.io/kube-scheduler:v1.30.0 \
registry.k8s.io/kube-proxy:v1.30.0 \
registry.k8s.io/pause:3.9 \
registry.k8s.io/etcd:3.5.12-0 \
registry.k8s.io/coredns/coredns:v1.11.1 \
-o kubernetes-images.tar
# Kubeadm, kubelet en kubectl downloaden
KUBE_VERSION=1.30.0
curl -LO https://dl.k8s.io/release/v${KUBE_VERSION}/bin/linux/amd64/kubeadm
curl -LO https://dl.k8s.io/release/v${KUBE_VERSION}/bin/linux/amd64/kubelet
curl -LO https://dl.k8s.io/release/v${KUBE_VERSION}/bin/linux/amd64/kubectl
In de airgap-omgeving (installatie)
# Stap 2: Images laden in de interne registry
docker load -i kubernetes-images.tar
# Images retaggeren naar interne registry
for image in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep registry.k8s.io); do
nieuwe_tag="interne-registry.voorbeeld.nl/${image#registry.k8s.io/}"
docker tag "$image" "$nieuwe_tag"
docker push "$nieuwe_tag"
done
# Stap 3: kubeadm.yaml aanpassen voor airgap
cat > kubeadm-config.yaml << 'EOF'
apiVersion: kubeadm.k8s.io/v1beta3
kind: ClusterConfiguration
imageRepository: interne-registry.voorbeeld.nl
kubernetesVersion: v1.30.0
---
apiVersion: kubeadm.k8s.io/v1beta3
kind: InitConfiguration
EOF
# Cluster initialiseren
kubeadm init --config kubeadm-config.yaml
2. OpenShift (EX280/EX380) — airgap installatie
Op de machine MET internet
# oc mirror gebruiken voor airgap image mirroring
# Installeer oc-mirror plugin
curl -LO https://mirror.openshift.com/pub/openshift-v4/clients/ocp/latest/oc-mirror.tar.gz
tar -xvf oc-mirror.tar.gz
chmod +x oc-mirror
sudo mv oc-mirror /usr/local/bin/
# ImageSetConfiguration aanmaken
cat > imageset-config.yaml << 'EOF'
apiVersion: mirror.openshift.io/v1alpha2
kind: ImageSetConfiguration
storageConfig:
local:
path: ./oc-mirror-workspace
mirror:
platform:
channels:
- name: stable-4.18
type: ocp
operators:
- catalog: registry.redhat.io/redhat/redhat-operator-index:v4.18
packages:
- name: openshift-gitops-operator
- name: openshift-logging
- name: loki-operator
- name: oadp-operator
additionalImages:
- name: registry.redhat.io/ubi9/ubi:latest
EOF
# Images downloaden naar disk
oc mirror --config=imageset-config.yaml file:///pad/naar/mirror
# Of direct naar een registry
oc mirror --config=imageset-config.yaml docker://interne-registry.voorbeeld.nl
In de airgap-omgeving
# Images van disk naar interne registry pushen
oc mirror --from=/pad/naar/mirror docker://interne-registry.voorbeeld.nl
# ImageContentSourcePolicy toepassen (vertelt OpenShift welke registry te gebruiken)
oc apply -f oc-mirror-workspace/results-*/imageContentSourcePolicy.yaml
# CatalogSource toepassen (operator-catalogus uit interne registry)
oc apply -f oc-mirror-workspace/results-*/catalogSource.yaml
3. Helm — airgap installatie
Op de machine MET internet
# Helm binary downloaden
HELM_VERSION=3.15.0
curl -LO https://get.helm.sh/helm-v${HELM_VERSION}-linux-amd64.tar.gz
tar -xvf helm-v${HELM_VERSION}-linux-amd64.tar.gz
# Kopieer linux-amd64/helm naar de airgap-omgeving
# Charts lokaal opslaan
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add cert-manager https://charts.jetstack.io
helm repo add harbor https://helm.goharbor.io
helm repo update
# Charts downloaden als .tgz bestanden
helm pull ingress-nginx/ingress-nginx --version 4.9.1
helm pull cert-manager/cert-manager --version 1.14.0
helm pull harbor/harbor --version 1.14.0
# Container images uit charts exporteren
helm template ingress-nginx ingress-nginx/ingress-nginx | \
grep "image:" | awk '{print $2}' | sort -u > ingress-nginx-images.txt
# Images downloaden en exporteren
while read image; do
docker pull "$image"
done < ingress-nginx-images.txt
docker save $(cat ingress-nginx-images.txt | tr '\n' ' ') \
-o ingress-nginx-images.tar
Interne Helm chart repository opzetten
# Eenvoudige Helm repo via nginx op Kubernetes
apiVersion: apps/v1
kind: Deployment
metadata:
name: helm-repo
namespace: tooling
spec:
template:
spec:
containers:
- name: nginx
image: interne-registry.voorbeeld.nl/nginx:1.25
volumeMounts:
- name: charts
mountPath: /usr/share/nginx/html/charts
volumes:
- name: charts
persistentVolumeClaim:
claimName: helm-charts-pvc
# Charts uploaden naar de interne repo
helm repo index /pad/naar/charts --url http://helm-repo.tooling.svc/charts
# Kopieer de .tgz bestanden en index.yaml naar de PVC of nginx-map
In de airgap-omgeving
# Helm binary installeren
chmod +x helm
sudo mv helm /usr/local/bin/
# Interne registry toevoegen
helm repo add intern http://helm-repo.intern.voorbeeld.nl/charts
helm repo update
# Chart installeren vanuit interne repo
helm install ingress-nginx intern/ingress-nginx \
--set controller.image.registry=interne-registry.voorbeeld.nl \
--set controller.image.image=ingress-nginx/controller \
--set controller.image.tag=v1.9.4
4. Harbor — airgap installatie
Op de machine MET internet
# Harbor offline installer downloaden (bevat alle images)
HARBOR_VERSION=2.11.0
curl -LO https://github.com/goharbor/harbor/releases/download/v${HARBOR_VERSION}/harbor-offline-installer-v${HARBOR_VERSION}.tgz
# De offline installer bevat:
# - harbor.yml.tmpl (configuratie template)
# - install.sh (installatiescript)
# - docker-compose.yml
# - Alle Harbor images als tar-bestanden
In de airgap-omgeving
# Offline installer uitpakken tar -xvf harbor-offline-installer-v2.11.0.tgz cd harbor # Configuratie aanpassen cp harbor.yml.tmpl harbor.yml # Bewerk harbor.yml: # - hostname: harbor.intern.voorbeeld.nl # - certificate: /pad/naar/tls.crt # - private_key: /pad/naar/tls.key # - external_url: https://harbor.intern.voorbeeld.nl # Installeren (laadt images automatisch uit het tar-bestand) sudo ./install.sh --with-trivy # Harbor is nu bereikbaar op https://harbor.intern.voorbeeld.nl # Standaard login: admin / Harbor12345 (wijzig direct!)
Harbor op Kubernetes via Helm (airgap)
# Harbor images retaggeren naar tijdelijke lokale registry
docker load -i harbor-images.tar
for image in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep goharbor); do
nieuwe_tag="interne-registry.voorbeeld.nl/${image#goharbor/}"
docker tag "$image" "$nieuwe_tag"
docker push "$nieuwe_tag"
done
# Harbor installeren via Helm met interne images
helm install harbor intern/harbor \
--namespace harbor \
--create-namespace \
--set global.imageRegistry=interne-registry.voorbeeld.nl \
--set global.imagePullSecrets[0]=interne-registry-secret
5. OpenBao — airgap installatie
Op de machine MET internet
# OpenBao binary downloaden
OPENBAO_VERSION=2.0.0
curl -LO https://github.com/openbao/openbao/releases/download/v${OPENBAO_VERSION}/bao_${OPENBAO_VERSION}_linux_amd64.zip
unzip bao_${OPENBAO_VERSION}_linux_amd64.zip
# OpenBao container image exporteren
docker pull quay.io/openbao/openbao:${OPENBAO_VERSION}
docker save quay.io/openbao/openbao:${OPENBAO_VERSION} -o openbao-image.tar
# Helm chart downloaden
helm repo add openbao https://openbao.github.io/openbao-helm
helm pull openbao/openbao --version 0.7.0
In de airgap-omgeving
# Binary installeren chmod +x bao sudo mv bao /usr/local/bin/ # Image laden en retaggeren docker load -i openbao-image.tar docker tag quay.io/openbao/openbao:2.0.0 \ interne-registry.voorbeeld.nl/openbao/openbao:2.0.0 docker push interne-registry.voorbeeld.nl/openbao/openbao:2.0.0 # Installeren via Helm met interne image helm install openbao ./openbao-0.7.0.tgz \ --namespace openbao \ --create-namespace \ --set server.image.repository=interne-registry.voorbeeld.nl/openbao/openbao \ --set server.image.tag=2.0.0 \ --set injector.image.repository=interne-registry.voorbeeld.nl/openbao/openbao-k8s
6. Terraform — airgap installatie
Op de machine MET internet
# Terraform binary downloaden
TF_VERSION=1.9.0
curl -LO https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip
# Providers vooraf downloaden
# Maak een tijdelijk project aan
mkdir /tmp/tf-providers && cd /tmp/tf-providers
cat > main.tf << 'EOF'
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
kubernetes = {
source = "hashicorp/kubernetes"
version = "~> 2.0"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.0"
}
}
}
EOF
# Providers downloaden
terraform init
# Providers zitten in ~/.terraform.d/plugins/
# Of maak een lokale provider mirror
terraform providers mirror /pad/naar/provider-mirror
Interne Terraform provider registry opzetten
# Simpele provider mirror via nginx
# Mappenstructuur: registry.voorbeeld.nl/hashicorp/aws/5.0.0/linux_amd64/
# Nginx-configuratie:
server {
listen 443 ssl;
server_name registry.voorbeeld.nl;
root /var/www/terraform-registry;
autoindex on;
location /.well-known/terraform.json {
return 200 '{"providers.v1": "/v1/providers/"}';
}
}
In de airgap-omgeving
# Binary installeren
unzip terraform_1.9.0_linux_amd64.zip
sudo mv terraform /usr/local/bin/
# Terraform configureren voor interne registry
cat > ~/.terraformrc << 'EOF'
provider_installation {
filesystem_mirror {
path = "/opt/terraform-providers"
include = ["registry.terraform.io/*/*"]
}
direct {
exclude = ["registry.terraform.io/*/*"]
}
}
EOF
# Providers kopiëren naar de mirror-map
cp -r /pad/naar/provider-mirror /opt/terraform-providers
# Project initialiseren vanuit lokale providers
terraform init
7. Kustomize — airgap installatie
Op de machine MET internet
# Kustomize binary downloaden
KUSTOMIZE_VERSION=5.4.0
curl -LO https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv${KUSTOMIZE_VERSION}/kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz
tar -xvf kustomize_v${KUSTOMIZE_VERSION}_linux_amd64.tar.gz
# Resultaat: ./kustomize binary
In de airgap-omgeving
# Binary installeren
chmod +x kustomize
sudo mv kustomize /usr/local/bin/
# Verifiëren
kustomize version
# Kustomize werkt volledig offline — het heeft geen
# internetverbinding nodig na installatie
# Alle images in manifesten worden opgehaald uit de interne registry
# Pas image-referenties aan via kustomization.yaml
images:
- name: nginx # Originele naam in manifesten
newName: interne-registry.voorbeeld.nl/nginx # Interne locatie
newTag: "1.25.3"
8. Renovate — airgap installatie
Op de machine MET internet
# Renovate container image downloaden docker pull renovate/renovate:latest docker save renovate/renovate:latest -o renovate-image.tar # Of als Node.js package voor self-hosting npm pack renovate # Downloadt het npm-pakket als .tgz
In de airgap-omgeving (Kubernetes CronJob)
# Image laden en pushen naar interne registry
docker load -i renovate-image.tar
docker tag renovate/renovate:latest \
interne-registry.voorbeeld.nl/renovate/renovate:latest
docker push interne-registry.voorbeeld.nl/renovate/renovate:latest
# CronJob met interne image
apiVersion: batch/v1
kind: CronJob
metadata:
name: renovate
namespace: tooling
spec:
schedule: "0 2 * * *"
jobTemplate:
spec:
template:
spec:
containers:
- name: renovate
image: interne-registry.voorbeeld.nl/renovate/renovate:latest
env:
- name: RENOVATE_TOKEN
valueFrom:
secretKeyRef:
name: renovate-secret
key: token
# Interne Git-server configureren
- name: RENOVATE_ENDPOINT
value: "https://gitlab.intern.voorbeeld.nl"
- name: RENOVATE_PLATFORM
value: "gitlab"
# Renovate configureren voor interne package registries (renovate.json)
{
"extends": ["config:recommended"],
"registryAliases": {
"registry.npmjs.org": "npm.intern.voorbeeld.nl",
"index.docker.io": "interne-registry.voorbeeld.nl",
"quay.io": "interne-registry.voorbeeld.nl/quay-mirror"
},
"hostRules": [
{
"matchHost": "interne-registry.voorbeeld.nl",
"username": "renovate-robot",
"password": "{{ secrets.REGISTRY_TOKEN }}"
}
]
}
9. Argo CD (CGOA/CAPA) — airgap installatie
Op de machine MET internet
# Alle Argo CD images ophalen
ARGOCD_VERSION=2.11.0
curl -LO https://raw.githubusercontent.com/argoproj/argo-cd/v${ARGOCD_VERSION}/manifests/install.yaml
# Images extraheren uit het manifest
grep "image:" install.yaml | awk '{print $2}' | sort -u > argocd-images.txt
# Images downloaden en exporteren
while read image; do docker pull "$image"; done < argocd-images.txt
docker save $(cat argocd-images.txt | tr '\n' ' ') -o argocd-images.tar
# argocd CLI binary
curl -LO https://github.com/argoproj/argo-cd/releases/download/v${ARGOCD_VERSION}/argocd-linux-amd64
In de airgap-omgeving
# Images laden en retaggeren
docker load -i argocd-images.tar
for image in $(docker images --format "{{.Repository}}:{{.Tag}}" | grep quay.io/argoproj); do
nieuwe_tag="interne-registry.voorbeeld.nl/${image#quay.io/}"
docker tag "$image" "$nieuwe_tag"
docker push "$nieuwe_tag"
done
# install.yaml aanpassen voor interne images
sed -i 's|quay.io/argoproj/|interne-registry.voorbeeld.nl/argoproj/|g' install.yaml
kubectl apply -n argocd -f install.yaml
# Argo CD instellen voor interne Git-server
argocd login argocd.intern.voorbeeld.nl
argocd repo add https://gitlab.intern.voorbeeld.nl/mijnorg/mijn-repo \
--username git \
--password $GITLAB_TOKEN \
--insecure-skip-server-verification
Volledig airgap-script (automatisering)
# download-alle-tools.sh — uitvoeren op machine MET internet
#!/bin/bash
set -euo pipefail
MIRROR_DIR=/mnt/usb/airgap-mirror
mkdir -p $MIRROR_DIR/{binaries,images,charts}
echo "=== Binaries downloaden ==="
curl -Lo $MIRROR_DIR/binaries/kubectl \
https://dl.k8s.io/release/v1.30.0/bin/linux/amd64/kubectl
curl -Lo $MIRROR_DIR/binaries/helm.tar.gz \
https://get.helm.sh/helm-v3.15.0-linux-amd64.tar.gz
curl -Lo $MIRROR_DIR/binaries/kustomize.tar.gz \
https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2Fv5.4.0/kustomize_v5.4.0_linux_amd64.tar.gz
curl -Lo $MIRROR_DIR/binaries/terraform.zip \
https://releases.hashicorp.com/terraform/1.9.0/terraform_1.9.0_linux_amd64.zip
curl -Lo $MIRROR_DIR/binaries/bao.zip \
https://github.com/openbao/openbao/releases/download/v2.0.0/bao_2.0.0_linux_amd64.zip
echo "=== Helm charts downloaden ==="
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo add harbor https://helm.goharbor.io
helm repo add openbao https://openbao.github.io/openbao-helm
helm repo update
helm pull ingress-nginx/ingress-nginx --destination $MIRROR_DIR/charts
helm pull harbor/harbor --destination $MIRROR_DIR/charts
helm pull openbao/openbao --destination $MIRROR_DIR/charts
echo "=== Container images downloaden ==="
IMAGES=(
"registry.k8s.io/ingress-nginx/controller:v1.9.4"
"quay.io/openbao/openbao:2.0.0"
"quay.io/argoproj/argocd:v2.11.0"
"renovate/renovate:latest"
"goharbor/harbor-core:v2.11.0"
"goharbor/trivy-adapter-photon:v2.11.0"
"goharbor/harbor-db:v2.11.0"
"goharbor/redis-photon:v2.11.0"
"goharbor/nginx-photon:v2.11.0"
)
for image in "${IMAGES[@]}"; do
docker pull "$image"
done
docker save "${IMAGES[@]}" -o $MIRROR_DIR/images/alle-tools.tar
echo "=== Klaar! Kopieer $MIRROR_DIR naar de airgap-omgeving ==="
du -sh $MIRROR_DIR
Checklist vóór airgap-implementatie
- ☐ Interne container registry beschikbaar en bereikbaar (Harbor, Quay, of Docker Registry)
- ☐ TLS-certificaten voor interne registry geldig en vertrouwd door alle nodes
- ☐ DNS-entries voor interne registry aangemaakt
- ☐ Alle container images aanwezig in interne registry
- ☐ Helm charts geïndexeerd in interne chart-repository
- ☐ Binaries beschikbaar op interne bestandsserver of direct geïnstalleerd
- ☐ ImagePullSecret aangemaakt voor de interne registry in alle relevante namespaces
- ☐ Terraform providers beschikbaar in lokale mirror
- ☐ Renovate geconfigureerd voor interne Git-server en package registries
- ☐ Argo CD repositories geconfigureerd voor interne Git-server
Veelgestelde vragen
Hoe houd ik airgap-images up-to-date?
Stel een periodiek updateproces in op een machine in de DMZ (een semi-geïsoleerde zone) die wél toegang heeft tot het internet maar niet tot het productienetwerk. Nieuwe versies worden gedownload, gevalideerd (vulnerability scan via Trivy) en via een goedkeuringsproces overgezet naar de airgap-omgeving. Renovate kan worden ingezet op de DMZ-machine om updates te detecteren.
Wat is het verschil tussen een airgap en een disconnected omgeving?
Airgap (ook wel “air-gapped”) betekent volledig geen netwerkaansluiting naar buiten. Disconnected betekent dat er geen directe internetverbinding is maar er wel beperkt verkeer mogelijk is via proxies of DMZ-zones. OpenShift gebruikt de term “disconnected” voor installaties waarbij de oc mirror-aanpak wordt gebruikt met een tussenliggende registry.
Hoe werkt certificaatvertrouwen in een airgap-omgeving?
Alle interne services gebruiken TLS-certificaten uitgegeven door een interne Certificate Authority (CA). Deze CA moet worden vertrouwd door alle nodes, containers en tools. Voeg het CA-certificaat toe aan het systeem-truststore van de nodes (/etc/pki/ca-trust/ op RHEL, /usr/local/share/ca-certificates/ op Debian), en configureer tools zoals Docker, Helm en kubectl om de interne CA te vertrouwen.