Airgap Installatie Kubernetes Tools

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

  1. Downloaden — haal alles op via een machine met internetverbinding (de “bastion” of “jump host”)
  2. Overdragen — kopieer via een goedgekeurd medium (USB, gecontroleerde bestandsoverdracht, geïsoleerde DMZ-server)
  3. 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.