Skip to main content

Harbor na Prática | Parte 8 Integração com Kubernetes

Esta é a última parte da série. Vamos configurar o Kubernetes para fazer pull de imagens privadas do Harbor, usando imagePullSecrets, ServiceAccounts e boas práticas de segurança para ambientes de produção.


O problema​

Por padrão, o Kubernetes não sabe como autenticar em um registry privado. Quando um pod tenta puxar uma imagem do Harbor, ele recebe um erro:

Failed to pull image "harbor.empresa.com/backend/api:v1.0.0":
rpc error: code = Unknown desc = failed to pull and unpack image:
failed to resolve reference "harbor.empresa.com/backend/api:v1.0.0":
unexpected status code 401 Unauthorized

A solução é criar um Secret com as credenciais do Harbor e vinculá-lo ao pod.


Pré-requisitos​

  • Cluster Kubernetes com acesso ao Harbor
  • Robot Account com permissão apenas de pull (veja a Parte 6)
  • Se o Harbor usa HTTP: containerd configurado com insecure-registries

Passo 1: Configurar o containerd para registries HTTP​

Se o Harbor não usa HTTPS, configure o containerd em cada node do cluster.

# Em cada node do cluster
sudo mkdir -p /etc/containerd/certs.d/harbor.empresa.com

cat <<EOF | sudo tee /etc/containerd/certs.d/harbor.empresa.com/hosts.toml
server = "http://harbor.empresa.com"

[host."http://harbor.empresa.com"]
capabilities = ["pull", "resolve"]
skip_verify = true
EOF

sudo systemctl restart containerd

Para HTTPS com certificado autoassinado, substitua o certificado:

sudo mkdir -p /etc/containerd/certs.d/harbor.empresa.com
sudo cp ca.crt /etc/containerd/certs.d/harbor.empresa.com/ca.crt

cat <<EOF | sudo tee /etc/containerd/certs.d/harbor.empresa.com/hosts.toml
server = "https://harbor.empresa.com"

[host."https://harbor.empresa.com"]
capabilities = ["pull", "resolve"]
ca = "/etc/containerd/certs.d/harbor.empresa.com/ca.crt"
EOF

sudo systemctl restart containerd

Passo 2: Criar o Secret com as credenciais do Harbor​

O imagePullSecret é um Secret do tipo kubernetes.io/dockerconfigjson que contém as credenciais para autenticar no registry.

Criando com kubectl​

kubectl create secret docker-registry harbor-pull-secret \
--docker-server=harbor.empresa.com \
--docker-username='robot$backend+kubernetes-pull' \
--docker-password='TOKEN_DO_ROBOT' \
--namespace=minha-app

⚠️ O nome do Robot Account contém $, entre aspas simples no shell para evitar que o bash interprete como variável.

Criando via manifesto YAML​

Mais adequado para GitOps, o secret fica versionado (sem o valor real, que deve vir de um Secret Manager ou Sealed Secrets).

# Gerar o base64 do config.json
AUTH=$(echo -n 'robot$backend+kubernetes-pull:TOKEN_DO_ROBOT' | base64)

cat <<EOF
{
"auths": {
"harbor.empresa.com": {
"auth": "$AUTH"
}
}
}
EOF | base64

Use o resultado no manifesto:

# harbor-pull-secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: harbor-pull-secret
namespace: minha-app
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: <BASE64_DO_JSON_ACIMA>
kubectl apply -f harbor-pull-secret.yaml

Criando com Sealed Secrets (recomendado para GitOps)​

Se você usa Sealed Secrets no cluster:

kubectl create secret docker-registry harbor-pull-secret \
--docker-server=harbor.empresa.com \
--docker-username='robot$backend+kubernetes-pull' \
--docker-password='TOKEN_DO_ROBOT' \
--namespace=minha-app \
--dry-run=client -o yaml | \
kubeseal --format yaml > harbor-pull-secret-sealed.yaml

O arquivo harbor-pull-secret-sealed.yaml pode ser commitado no repositório Git com segurança.


Passo 3: Usar o Secret em um Deployment​

Opção A: Referenciar no Deployment diretamente​

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minha-api
namespace: minha-app
spec:
replicas: 2
selector:
matchLabels:
app: minha-api
template:
metadata:
labels:
app: minha-api
spec:
imagePullSecrets:
- name: harbor-pull-secret # referência ao Secret criado
containers:
- name: api
image: harbor.empresa.com/backend/api:v1.0.0
ports:
- containerPort: 8080

Opção B: Vincular ao ServiceAccount (recomendado)​

Se você vincula o Secret ao ServiceAccount, todos os pods que usam aquele ServiceAccount herdam automaticamente o acesso. Não precisa repetir imagePullSecrets em cada Deployment.

# serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-serviceaccount
namespace: minha-app
imagePullSecrets:
- name: harbor-pull-secret
# deployment.yaml
spec:
template:
spec:
serviceAccountName: app-serviceaccount # sem imagePullSecrets aqui
containers:
- name: api
image: harbor.empresa.com/backend/api:v1.0.0

Qualquer novo Deployment no namespace que usar serviceAccountName: app-serviceaccount vai puxar automaticamente do Harbor sem configuração adicional.


Passo 4: Verificar que o pull está funcionando​

# Aplicar o deployment
kubectl apply -f deployment.yaml

# Acompanhar os eventos do pod
kubectl describe pod -n minha-app <nome-do-pod>

# Se o pull foi bem-sucedido, você verá:
# Successfully pulled image "harbor.empresa.com/backend/api:v1.0.0"

Para verificar que o Secret está correto antes de aplicar:

kubectl get secret harbor-pull-secret -n minha-app -o jsonpath='{.data.\.dockerconfigjson}' | \
base64 -d | jq '.'

Configurando para múltiplos namespaces​

O Secret precisa existir em cada namespace onde os pods rodam. Para automatizar isso, você pode usar um script ou um operador como o kubernetes-replicator.

Script simples para propagar o Secret​

#!/bin/bash
NAMESPACES=("minha-app" "frontend" "data-pipeline" "monitoring")

for NS in "${NAMESPACES[@]}"; do
kubectl create secret docker-registry harbor-pull-secret \
--docker-server=harbor.empresa.com \
--docker-username="robot\$backend+kubernetes-pull" \
--docker-password="TOKEN_DO_ROBOT" \
--namespace="$NS" \
--dry-run=client -o yaml | kubectl apply -f -
echo "✅ Secret criado/atualizado no namespace: $NS"
done

Rotação de credenciais​

Robot Accounts expiram. Para rotacionar sem downtime:

# 1. Criar novo Robot Account no Harbor (via UI ou API)
# 2. Atualizar o Secret em todos os namespaces
kubectl create secret docker-registry harbor-pull-secret \
--docker-server=harbor.empresa.com \
--docker-username="robot\$backend+kubernetes-pull-v2" \
--docker-password="NOVO_TOKEN" \
--namespace=minha-app \
--dry-run=client -o yaml | kubectl apply -f -

# 3. Os pods existentes continuam rodando (já baixaram a imagem)
# 4. Novos pods já usam as novas credenciais
# 5. Após confirmar que tudo está ok, remover o Robot Account antigo no Harbor

Quadro completo da série​

Você chegou ao final da série. Aqui está o fluxo completo que construímos ao longo dos 8 artigos:

Instalação (Parte 1)
│
▼
Projetos e Repositórios (Parte 2)
│
▼
Usuários, LDAP e Robot Accounts (Parte 3)
│
▼
Push e Pull de imagens: máquina local (Parte 4)
│
▼
Proxy Cache: Docker Hub, GCR, ECR (Parte 5)
│
▼
Retenção, Quotas e Token de Pull (Parte 6)
│
▼
GitLab CI: build e push automático (Parte 7)
│
▼
Kubernetes: imagePullSecrets e ServiceAccount (Parte 8)

Próximos passos sugeridos​

Agora que o Harbor está integrado ao seu stack, alguns tópicos para explorar:

  • Alta Disponibilidade: PostgreSQL externo em HA, Redis Sentinel, registry com RWX
  • Replicação entre ambientes: promover imagens de staging para produção via replicação
  • Webhooks: notificar o Slack ou disparar pipelines quando uma imagem é publicada
  • Harbor como proxy cache no containerd: redirecionar todos os pulls do cluster automaticamente
  • Monitoramento: expor métricas do Harbor para o Prometheus e criar dashboards no Grafana

Espero que esta série tenha sido útil! Se te ajudou a implementar o Harbor no seu ambiente, compartilhe com a comunidade. Todo feedback é bem-vindo nos comentários.

Até a próxima série!