Pular para o conteúdo principal

Multi-tenancy e governança

A maioria das organizações deveria executar menos clusters, porém maiores, em vez de um cluster por equipe. Multi-tenancy faz isso funcionar sem que as equipes interfiram umas nas outras. Um cluster compartilhado bem governado é mais barato, mais fácil de aplicar patches e mais simples de monitorar — mas sem proteções ele se torna um caos em semanas.

Modelos de tenancy

Escolha um modelo e mantenha-o. Misturar modelos no mesmo cluster gera confusão.

ModeloQuando usarNível de isolamento
Namespace por equipeEscolha padrão. Equipes compartilham um cluster, cada uma recebe um namespace com quotas e RBAC.Lógico
Namespace por ambienteOrganizações pequenas onde uma equipe gerencia namespaces de dev/staging/prod no mesmo cluster.Lógico
Cluster por tenantRequisitos regulatórios (PCI, HIPAA), zero-trust entre tenants ou cargas de trabalho GPU que precisam de controle total dos nós.Físico

Use namespace por equipe a menos que tenha uma razão documentada para não usar. Cluster por tenant dobra sua carga operacional.

dica

Comece com namespace por equipe. Você pode promover um tenant para seu próprio cluster depois. Ir na direção contrária é doloroso.

Checklist de isolamento de namespace

Todo novo namespace de tenant precisa de todos os cinco itens. Pule um e você terá uma lacuna.

#RecursoPropósito
1ResourceQuotaLimita CPU total, memória, armazenamento e contagem de objetos
2LimitRangeDefine requests/limits padrão para que nenhum pod execute sem limites
3NetworkPolicyNega todo tráfego por padrão, depois permite fluxos específicos
4RoleBindingRBAC com escopo — membros da equipe recebem apenas acesso a nível de namespace
5Mapeamento de grupo Entra IDVincule roles a grupos Azure AD, não a usuários individuais

Resource quotas e LimitRange

Sem quotas, uma equipe pode consumir o cluster inteiro. Defina quotas no primeiro dia, não após um incidente.

apiVersion: v1
kind: ResourceQuota
metadata:
name: team-alpha-quota
namespace: team-alpha
spec:
hard:
requests.cpu: "8"
requests.memory: 16Gi
limits.cpu: "16"
limits.memory: 32Gi
persistentvolumeclaims: "10"
pods: "50"
services.loadbalancers: "2"
---
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-alpha
spec:
limits:
- default: { cpu: 500m, memory: 512Mi }
defaultRequest: { cpu: 100m, memory: 128Mi }
max: { cpu: "2", memory: 4Gi }
min: { cpu: 50m, memory: 64Mi }
type: Container
aviso

Se você definir um ResourceQuota para CPU ou memória, cada pod naquele namespace deve especificar requests e limits. Pods sem eles serão rejeitados. Sempre combine quotas com um LimitRange para definir valores padrão.

Network policies

Use Azure CNI com Cilium ou Calico — não kubenet. Kubenet não aplica network policies. Aplique uma política deny-all primeiro, depois abra exceções para DNS e seu ingress controller:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: deny-all, namespace: team-alpha }
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns, namespace: team-alpha }
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to: [{ namespaceSelector: {} }]
ports: [{ protocol: UDP, port: 53 }, { protocol: TCP, port: 53 }]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-ingress-controller, namespace: team-alpha }
spec:
podSelector: {}
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels: { kubernetes.io/metadata.name: ingress-system }
informação

Teste network policies em staging primeiro. Uma política de egress mal configurada que bloqueia DNS derrubará todas as cargas de trabalho no namespace instantaneamente.

Padrões RBAC

Use ClusterRoles integrados para permissões, RoleBindings com escopo de namespace para acesso. Nunca conceda cluster-admin para equipes de aplicação.

FunçãoClusterRole integradoPode fazerNão pode fazer
Admin da equipeadminDeployments, services, configmaps, secrets, HPAModificar quotas, network policies ou RBAC
DesenvolvedoreditImplantar cargas de trabalho, ver logs, exec em podsExcluir PVs, modificar services LoadBalancer
VisualizadorviewLer todos os recursos, ver logsCriar, atualizar ou excluir qualquer coisa
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: team-alpha-admin, namespace: team-alpha }
subjects:
- kind: Group
name: "<entra-group-object-id>"
apiGroup: rbac.authorization.k8s.io
roleRef: { kind: ClusterRole, name: admin, apiGroup: rbac.authorization.k8s.io }
# Troque 'admin' por 'edit' (desenvolvedor) ou 'view' (somente leitura)
aviso

Vincule roles a grupos Entra ID, não a usuários individuais. Quando alguém sai ou muda de equipe, você atualiza a associação ao grupo em um único lugar em vez de procurar em RoleBindings.

Azure Policy para AKS

Use iniciativas de política integradas — não escreva políticas customizadas até esgotar a biblioteca integrada.

PolíticaEfeitoPor quê
Iniciativa Pod security baselineDenyBloqueia containers privilegiados, host networking, host PID/IPC
Imagens de container apenas de registros permitidosDenyImpede pull do Docker Hub ou registros desconhecidos
Containers devem ter limites de recursosDenyReforço adicional sobre o LimitRange
Containers não devem executar como rootAudit, depois DenyComece com audit para encontrar violações, mude para deny quando estiver limpo
Pods devem usar labels aprovadosAuditNecessário para alocação de custos
az policy assignment create \
--name "aks-pod-security-baseline" \
--policy-set-definition "a8640138-9b0a-4a28-b8cb-1666c838647d" \
--scope "/subscriptions/<sub-id>/resourceGroups/<rg>/providers/Microsoft.ContainerService/managedClusters/<cluster>" \
--params '{"effect": {"value": "Deny"}}'

Alocação de custos

Você não pode dividir custos se não pode atribuir recursos às equipes. Habilite o add-on de análise de custos antes de integrar o segundo tenant:

az aks update --resource-group <rg> --name <cluster> --enable-cost-analysis

Aplique estes labels em cada namespace via Azure Policy:

LabelExemploPropósito
cost-centercc-12345Mapeia para o centro de custo financeiro
teamplatform-engineeringPropriedade
environmentproductionDistingue gasto de produção do de dev
dica

Use Azure Policy para negar namespaces sem os labels cost-center e team. Sem aplicação, a disciplina de labels se deteriora em semanas.

Isolamento de node pool

Use node pools dedicados quando isolamento lógico não é suficiente.

CenárioRecomendação
Cargas de trabalho GPUPool dedicado com VMs GPU e taints NoSchedule
Tenants sensíveis a compliancePool dedicado, sem agendamento compartilhado
Vizinhos ruidososAdicione taint a um pool, tolere apenas a carga de trabalho ruidosa
Dev/test com burstPool B-series, mínimo do autoscaler em zero
az aks nodepool add \
--resource-group <rg> --cluster-name <cluster> --name gpupool \
--node-count 2 --node-vm-size Standard_NC6s_v3 \
--node-taints "sku=gpu:NoSchedule" --labels team=ml-team

Pods direcionados a este pool precisam de uma toleration e node selector:

tolerations:
- { key: "sku", operator: "Equal", value: "gpu", effect: "NoSchedule" }
nodeSelector: { team: ml-team }

Erros comuns

ErroConsequênciaCorreção
Sem resource quotasVazamento de uma equipe causa OOM-kill em todo o clusterResourceQuota em cada namespace
RBAC excessivamente permissivoDevs excluem recursos de outras equipesApenas RoleBinding com escopo de namespace
Sem network policiesQualquer pod alcança qualquer podDeny-all padrão por namespace
Service accounts compartilhadosNão é possível auditar quem fez o quêUm SA por carga de trabalho
RBAC vinculado a usuáriosContas obsoletas, proliferaçãoExclusivamente grupos Entra ID
Sem LimitRange com quotaPods rejeitados no deploySempre combine ambos

Template de onboarding de nova equipe

Execute isso uma vez por equipe. Ele cria o namespace com todos os cinco primitivos de isolamento.

#!/bin/bash
set -euo pipefail
NS="${1:?Usage: $0 <namespace> <admin-group-id> <viewer-group-id>}"
ADMIN="${2:?Provide Entra admin group object ID}"
VIEWER="${3:?Provide Entra viewer group object ID}"

kubectl create namespace "$NS" --dry-run=client -o yaml | \
kubectl label --local -f - cost-center="CHANGE-ME" team="$NS" environment="production" \
--dry-run=client -o yaml | kubectl apply -f -

kubectl apply -n "$NS" -f - <<EOF
apiVersion: v1
kind: ResourceQuota
metadata: { name: quota }
spec:
hard: { requests.cpu: "8", requests.memory: 16Gi, limits.cpu: "16", limits.memory: 32Gi, pods: "50", services.loadbalancers: "2" }
---
apiVersion: v1
kind: LimitRange
metadata: { name: default-limits }
spec:
limits:
- default: { cpu: 500m, memory: 512Mi }
defaultRequest: { cpu: 100m, memory: 128Mi }
max: { cpu: "2", memory: 4Gi }
min: { cpu: 50m, memory: 64Mi }
type: Container
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: deny-all }
spec: { podSelector: {}, policyTypes: [Ingress, Egress] }
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata: { name: allow-dns }
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to: [{ namespaceSelector: {} }]
ports: [{ protocol: UDP, port: 53 }, { protocol: TCP, port: 53 }]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: admin }
subjects: [{ kind: Group, name: "$ADMIN", apiGroup: rbac.authorization.k8s.io }]
roleRef: { kind: ClusterRole, name: admin, apiGroup: rbac.authorization.k8s.io }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: viewers }
subjects: [{ kind: Group, name: "$VIEWER", apiGroup: rbac.authorization.k8s.io }]
roleRef: { kind: ClusterRole, name: view, apiGroup: rbac.authorization.k8s.io }
EOF

echo "Done. Update cost-center: kubectl label ns $NS cost-center=<value> --overwrite"

Recursos