Offline/air-gapped deployments: clusters sem internet
1. Introdução aos Ambientes Air-gapped
Ambientes air-gapped, ou isolados, são clusters Kubernetes completamente desconectados da internet pública. Eles são comuns em indústrias como defesa, governo, finanças, energia e edge computing, onde dados sensíveis ou operações críticas exigem isolamento total.
Os desafios principais incluem: ausência de acesso a repositórios públicos (Docker Hub, GitHub, registry oficial do Kubernetes), necessidade de atualizações manuais e sincronização cuidadosa de imagens e pacotes. Diferente de clusters conectados, onde kubectl apply ou helm install puxam recursos automaticamente, em ambientes offline cada componente precisa ser pré-carregado.
A complexidade operacional é maior, mas os ganhos em segurança — eliminação de ataques de supply chain, vazamento de dados e dependência de terceiros — justificam o esforço.
2. Planejamento da Infraestrutura Offline
Antes de qualquer deploy, é essencial inventariar todas as dependências:
- Imagens Docker de todos os componentes do cluster (etcd, kube-apiserver, kube-controller-manager, kube-scheduler, coredns, kube-proxy)
- Imagens das aplicações que rodarão no cluster
- Charts Helm e suas dependências
- Pacotes de sistema (containerd, runc, kubernetes-cni, kubectl)
- Binários do Kubernetes (kubeadm, kubelet)
Estratégias de mirroring incluem:
- Registry privado: Harbor, Nexus, Docker Registry com autenticação
- Proxies de pacotes: Artifactory, Pulp para APT/YUM
- Versionamento rigoroso com GitOps (ArgoCD em modo offline) para evitar divergências
3. Preparação do Registry Privado e Sincronização de Imagens
Vamos configurar um registry local simples com Docker Registry e autenticação básica.
# No ambiente conectado, crie o arquivo de configuração
mkdir -p /data/registry
cat > /data/registry/config.yml <<EOF
version: 0.1
log:
level: info
storage:
filesystem:
rootdirectory: /var/lib/registry
http:
addr: :5000
headers:
X-Content-Type-Options: [nosniff]
auth:
htpasswd:
realm: basic-realm
path: /etc/docker/registry/htpasswd
EOF
# Gere senha (usuário: admin, senha: admin123)
docker run --entrypoint htpasswd httpd:2 -Bbn admin admin123 > /data/registry/htpasswd
# Inicie o registry
docker run -d -p 5000:5000 --name registry \
-v /data/registry:/etc/docker/registry \
registry:2
Para sincronizar imagens, use skopeo:
# Baixar imagem do Docker Hub
skopeo copy docker://docker.io/library/nginx:1.25 \
docker://localhost:5000/library/nginx:1.25
# Sincronizar múltiplas imagens com script
for img in kube-apiserver:v1.28.0 kube-controller-manager:v1.28.0 \
kube-scheduler:v1.28.0 kube-proxy:v1.28.0 coredns:v1.10.0 \
etcd:3.5.9 pause:3.9; do
skopeo copy docker://registry.k8s.io/$img \
docker://localhost:5000/k8s/$img
done
Para sincronização incremental, use crane:
crane copy library/nginx:1.25 localhost:5000/library/nginx:1.25
4. Deploy do Cluster Kubernetes sem Internet
No ambiente air-gapped, instale o Kubernetes usando kubeadm com imagens pré-carregadas.
# No nó mestre, carregue as imagens localmente
for img in kube-apiserver:v1.28.0 kube-controller-manager:v1.28.0 \
kube-scheduler:v1.28.0 kube-proxy:v1.28.0 coredns:v1.10.0 \
etcd:3.5.9 pause:3.9; do
ctr image pull localhost:5000/k8s/$img
ctr image tag localhost:5000/k8s/$img registry.k8s.io/$img
done
# Configure containerd para usar mirror do registry local
cat > /etc/containerd/config.toml <<EOF
version = 2
[plugins."io.containerd.grpc.v1.cri".registry]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["http://localhost:5000"]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."registry.k8s.io"]
endpoint = ["http://localhost:5000"]
EOF
systemctl restart containerd
# Inicialize o cluster
kubeadm init --pod-network-cidr=10.244.0.0/16 \
--image-repository=localhost:5000/k8s
Para instalação sem pacotes APT/YUM, use binários estáticos:
# Baixe os binários em máquina conectada
wget https://dl.k8s.io/v1.28.0/kubernetes-node-linux-amd64.tar.gz
# Transfira para o ambiente offline e extraia
tar -xzf kubernetes-node-linux-amd64.tar.gz
cp kubernetes/node/bin/kubelet /usr/local/bin/
cp kubernetes/node/bin/kubectl /usr/local/bin/
5. Gerenciamento de Aplicações e Helm Charts em Modo Offline
Crie um repositório Helm local com ChartMuseum:
# No ambiente conectado, instale ChartMuseum
docker run -d -p 8080:8080 --name chartmuseum \
-e STORAGE=local \
-e STORAGE_LOCAL_ROOTDIR=/charts \
-v /data/charts:/charts \
chartmuseum/chartmuseum:latest
# Adicione charts ao repositório
helm repo add stable https://charts.helm.sh/stable
helm fetch stable/nginx-ingress --version 1.41.3
curl --data-binary "@nginx-ingress-1.41.3.tgz" \
http://localhost:8080/api/charts
# No ambiente offline, use o repositório local
helm repo add local http://registry-interno:8080
helm install my-nginx local/nginx-ingress --version 1.41.3
Para versionamento de dependências:
# Em máquina conectada
helm dependency build ./my-chart
helm package ./my-chart
# Transfira o .tgz para o ambiente offline
6. Segurança e Compliance em Ambientes Isolados
Configure OPA/Gatekeeper para garantir que apenas imagens do registry local sejam usadas:
# ConstraintTemplate
apiVersion: templates.gatekeeper.sh/v1beta1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not startswith(container.image, "registry-interno:5000/")
msg := sprintf("Imagem %v não permitida. Use apenas registry-interno:5000", [container.image])
}
# Constraint
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: repo-is-required
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
Assine imagens com Cosign sem acesso externo:
# Gere chave localmente
cosign generate-key-pair
# Assine imagem
cosign sign --key cosign.key registry-interno:5000/minha-app:v1.0
# Verifique
cosign verify --key cosign.pub registry-interno:5000/minha-app:v1.0
7. Manutenção e Atualizações Contínuas
Para atualizar o cluster offline:
# Backup do etcd
ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d).db
# Baixe novas imagens em máquina conectada
skopeo copy docker://registry.k8s.io/kube-apiserver:v1.29.0 \
docker://localhost:5000/k8s/kube-apiserver:v1.29.0
# Transfira para ambiente offline
# No cluster, atualize
kubeadm upgrade plan
kubeadm upgrade apply v1.29.0
Para sincronização de vulnerabilidades, use Trivy offline:
# Baixe banco de CVEs em máquina conectada
trivy image --download-db-only --cache-dir /data/trivy-db
# Transfira para ambiente offline
# Escaneie imagens
trivy image --cache-dir /data/trivy-db \
registry-interno:5000/minha-app:v1.0
8. Exemplo Prático: Pipeline Completo de Deploy Offline
# === FASE 1: PREPARAÇÃO (Máquina conectada) ===
# 1. Baixar imagens
./sync-images.sh
# 2. Baixar charts Helm
helm repo add bitnami https://charts.bitnami.com/bitnami
helm fetch bitnami/nginx --version 15.0.0
helm fetch bitnami/postgresql --version 12.5.0
# 3. Baixar binários
wget https://dl.k8s.io/v1.28.0/kubernetes-node-linux-amd64.tar.gz
# === FASE 2: TRANSFERÊNCIA ===
# Copiar para USB/mídia física:
# - /data/registry (imagens)
# - /charts/*.tgz
# - kubernetes-node-linux-amd64.tar.gz
# === FASE 3: DEPLOY (Ambiente offline) ===
# 1. Iniciar registry
docker load < registry.tar
docker run -d -p 5000:5000 --name registry registry:2
# 2. Carregar imagens
for img in $(cat images.txt); do
docker load < $img.tar
docker tag $img localhost:5000/$img
docker push localhost:5000/$img
done
# 3. Instalar Kubernetes
tar -xzf kubernetes-node-linux-amd64.tar.gz
cp kubernetes/node/bin/kubelet /usr/local/bin/
kubeadm init --image-repository=localhost:5000/k8s
# 4. Deploy aplicação com Helm
helm repo add local http://localhost:8080
helm install my-app local/nginx --version 15.0.0
# 5. Validação
kubectl get pods -A
kubectl logs -n default my-app-nginx-xxxx
kubectl describe pod my-app-nginx-xxxx
Referências
- Documentação oficial do Kubernetes: Instalação offline com kubeadm — Guia oficial para instalação do Kubernetes em ambientes sem internet
- Harbor: Registry privado para Kubernetes — Documentação do Harbor, ferramenta de registry com suporte a replicação offline
- Skopeo: Trabalhando com imagens de container em ambientes air-gapped — Repositório oficial da ferramenta para copiar, inspecionar e sincronizar imagens sem Docker
- Cosign: Assinatura de imagens em ambientes offline — Guia oficial da Sigstore para assinar e verificar imagens sem acesso a chaves públicas externas
- Trivy: Scanner de vulnerabilidades offline — Documentação oficial sobre como usar Trivy em modo desconectado com banco de CVEs local
- ChartMuseum: Repositório Helm offline — Documentação oficial do ChartMuseum para hospedar charts Helm em ambientes isolados
- OPA Gatekeeper: Políticas de segurança para clusters offline — Guia oficial para configurar políticas de imagem e recursos em Kubernetes sem internet