Primeiramente o Kubernetes possui uma gama enorme de recursos e ferramentas: Se eu parar para ler o medium por meia hora vou encontrar um monte de estruturas e ferramentas novas que são inventadas todo o momento. Esse artigo se atem às principais funcionalidades mas que não são utilizadas necessariamente no dia-a-dia. Desse modo vamos falar sobre recursos avançados no kubernetes, tais como: Resources, HPA, ResourceQuota, NetworkPolicy, Taint e Tolerant, Affinity e Anti-affinity, topology spread constraint e pod disruption budget.
Sumário
Resources
Os pods podem crescer e consumir todos os recursos do node e gerar problemas críticos no ambiente kubernetes. Para evitar esse problema é comum limitar os recursos que ele pode consumir, seja em memória RAM ou seja em CPU. Mas também é possível indicar qual é o mínimo de recursos. Assim veja a seguir um exemplo de um deployment simples com a definição dos resouces.
apiVersion: apps/v1
kind: Deployment
"metadata":
name: wordpress
namespace: app-wordpress
spec:
selector:
matchLabels:
app.kubernetes.io/instance: wordpress-mainapp
replicas: 1
template:
"metadata":
labels:
app.kubernetes.io/instance: wordpress-mainapp
spec:
containers:
- name: wordpress
image: wordpress:php8.1-apache
resources:
limits:
memory: "256Mi"
cpu: "300m"
requests:
memory: "128Mi"
cpu: "100m"
Horizontal Pod Autoscaler – HPA
O HPA é uma estrutura do kubernetes para aumentar o diminuir dinamicamente a quantidade de pods dependendo de indicadores como CPU, memória ou outros customizados. A utilização de HPAs é algo realmente recomendado, mas há no mercado soluções alternativas como o Keda.
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
"metadata":
name: nginx-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-nginx
minReplicas: 1
maxReplicas: 10
behavior:
scaleDown:
policies:
- type: Pods
value: 1
periodSeconds: 30
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 3
ResourceQuota
Assim como é possível definir num deployment recursos de hardware e de software com o ResourceQuota é possível dizer para todo um namespace o que pode ser consumido. Ele possui um carater ligeiramente diferente mas é uma solução bastante interessante. Com ela é possível, por exemplo, indicar a quantidade máxima de pods do namespace. Veja o exemplo:
apiVersion: v1
kind: ResourceQuota
"metadata":
name: disable-cross-namespace-affinity
namespace: foo-ns
spec:
hard:
pods: "0"
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
requests.nvidia.com/gpu: 4
configmaps: "10"
persistentvolumeclaims: "4"
replicationcontrollers: "20"
secrets: "10"
services: "10"
services.loadbalancers: "2"
Network Policy
O Network Policy é uma estrutura de controle de comunicação de rede entre as camadas 3 (Network layer) e 4 (Transport Layer) para o Kubernetes. Portanto ela define regras de comunicação entre outros pods, namespaces e blocos de IP. Vale destacar que na instalação de um cluster k8s é necessário instalar um provedor de rede para que a infraestrutura funcione, tal como Weave net, calico, cilium, etc. Veja um exemplo do uso de NetworkPolicy:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
"metadata":
name: test-network-policy
namespace: default
spec:
podSelector:
matchLabels:
role: db
policyTypes:
- Ingress
- Egress
ingress:
- from:
- ipBlock:
cidr: 172.17.0.0/16
except:
- 172.17.1.0/24
- namespaceSelector:
matchLabels:
project: myproject
- podSelector:
matchLabels:
role: frontend
ports:
- protocol: TCP
port: 6379
egress:
- to:
- ipBlock:
cidr: 10.0.0.0/24
ports:
- protocol: TCP
port: 5978
Taint e Tolerant
Essa é uma técnica em que o kube-scheduler aloca determinados para determinados pods apenas para determinados nodes. Esse é um dos recursos mais avançados do kubernetes. Um node possui um Taint que indica quem ele é. Já o Pod possui um Tolerant que define regras que dá a ele a possibilidade de ir o não para aquele node.
Inicialmente é necessário criar um tainer com o comando que segue. Após isso é possível utilizar um ou mais tolerants na definição de um pod.
kubectl taint nodes node1 key1=value1:NoSchedule # adiciona o taint
kubectl taint nodes node1 key1=value1:NoSchedule- # remove o taint
# Essa é a definição de um pod com um array de tolerations
tolerations:
- key: "key1"
operator: "Equal"
value: "value1"
effect: "NoSchedule"
Affinity e anti-affinity
É possível indicar que um pod tenha afinidade com um node específico, ou, pelo contrário, ele pode ter uma anti-afinidade. No primeiro caso ele tende a ser alocado no node, e no segudo ele tende a não ser alocado. A definição do critério de afinidade dá a opção da alocação ser obrigatória (ou seja, se não for possível, ele vai ficar esperando) ou opcional (ou seja, se não for possível, ele será alocado em outro node).
# Isso se baseia em labels associados aos nodes
kubectl get nodes --show-labels
# # Essa regra de afinidade indica que o pod só será distribuido em nós com o label 'ssd-enabled=true'
# # A distribuição será obrigatório no agendamento do pod
# # A regra não se aplicará ao pod após estar em execução
# # Se não for possível distribuir o pod para o node em questão, o pod não será distribuído.
affinity:
nodeAffinity:
# Obrigatório durante o agendamento
# Ignorado durante a execução
requiredDuringSchedullingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: ssd-enabled
operator: In
values:
- "true"
# # Essa regra de afinidade indica que o pod será distribuido, preferencialmente, para nós com o label 'ssd-enabled=true'
# # A distribuição seguirá esse critério apenas no agendamento do pod
# # A regra não se aplicará ao pod após estar em execução
# # Se não for possível distribuir o pod para o node em questão, o kubernetes escolherá algum outro node por algum critério arbitrário
affinity:
nodeAffinity:
# Obrigatório durante o agendamento
# Ignorado durante a execução
preferredDuringSchedullingIgnoredDuringExecution:
preference:
matchExpressions:
- key: ssd-enabled
operator: In
values:
- "true"
weight: 1
Há também a afinidade de Pods. Imagine que a aplicação e o banco devam estar no mesmo node para reduzir a latência. Nesse caso é necessário garantir uma afinidade entre esses nós. O modo de estruturar o yaml é bem semelhante a do nodeAffinity mas utilizando podAffinity.
apiVersion: v1
kind: Pod
"metadata":
name: with-pod-affinity
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S1
topologyKey: topology.kubernetes.io/zone
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: security
operator: In
values:
- S2
topologyKey: topology.kubernetes.io/zone
containers:
- name: with-pod-affinity
image: registry.k8s.io/pause:2.0
Pod Topology Spread Constraints
Imagine o seguinte problema: você tem um cluster com 20 nodes. Ao criar um deployment com 10 replicas é possível que todos os pods sejam agendados para o mesmo node. Com a definição da topologia é possível dizer que a diferença de pods entre os nodes máxima é de n. Imagine n=2: o node 1 tem 2 (total: 2), node 2 tem 2 (total: 4), node 3 tem 2 (total: 6), node 4 tem 2 (total: 8), node 5 tem 2 (total: 10), node 6 tem 0 (10) … Para mais detalhes consulte Pod Topology Spread Constraints.
# Exemplo da definição de um pod
topologySpreadConstraints:
# diferença máxima
- maxSkew: 1
# label do node utilizada para distribuir
topologyKey: kubernetes.io/hostname
# o que fazer quando não for possível distribuir
whenUnsatisfiable: DoNotSchedule
# label dos nodes que participam dessa topologia
matchLabelKeys:
- app
- pod-template-hash
Pod Disruption Budget – PDB
Esse é um cenário importante para quando se trabalha com vários nodes mas é necessário remover um nodo para manutenção. Trata-se de um dos recursos mais avançados do Kubernetes. Quando isso ocorre os workloads de um node devem ser drenados para outro node. Ocorre que durante esse processo os workloads podem se comportar incorretamente por não ter necessariamente o mesmo ambiente de recursos. Para tal o PDB existe indicando condições mínimas para o funcionamento das aplicações, mesmo durante a janela de manutenção.
Boas práticas a serem executadas para reduzir o impacto de manutenções e o PDB funcionar do melhor modo:
1 – Garantir que os Resources Limits estejam disponiveis
2 – Colocar a aplicação com replicas para HA
3 – Utilizar anti-afinidade para HA
kubectl get nodes
kubectl drain <node name>
apiVersion: policy/v1
kind: PodDisruptionBudget
"metadata":
name: zk-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: zookeeper
Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.