Hoje é dia de falar sobre Taints, Tolerations, Affinity e Antiaffinity. Vamos entender como eles podem nos ajudar no dia-a-dia na administração de um cluster Kubernetes.
Com eles podemos isolar workloads, garantir que Pods sejam agendados em Nodes específicos e até mesmo evitar que Pods sejam agendados em determinados Nodes do cluster.
Bora lá, pois o dia será intenso de coisas novas e com certeza você vai aprender muito! Bora?
Para que possamos ter alguns exemplos mais divertido, vamos imaginar que temos um empresa chamada Strigus, e que essa empresa tem o seu cluster Kubernetes de produção composto por 08 nodes, sendo 4 control planes e 4 workers. Ele está dividido em duas regiões, São Paulo e Salvador, chamadas de strigus-br-sp e strigus-br-ssa respectivamente. E cada região tem dois datacenters, strigus-sp-1 e strigus-sp-2 em São Paulo e strigus-br-ssa-1 e strigus-br-ssa-2 em Salvador.
kubectl get nodes
NAME STATUS ROLES AGE VERSION
strigus-control-plane1 Ready control-plane 65d v1.27.3
strigus-control-plane2 Ready control-plane 65d v1.27.3
strigus-control-plane3 Ready control-plane 65d v1.27.3
strigus-control-plane4 Ready control-plane 65d v1.27.3
strigus-worker1 Ready <none> 65d v1.27.3
strigus-worker2 Ready <none> 65d v1.27.3
strigus-worker3 Ready <none> 65d v1.27.3
strigus-worker4 Ready <none> 65d v1.27.3
A nossa missão é criar as Labels e Taints necessárias para que nosso cluster fique organizado, seguro e com alta disponibilidade. E com isso, com alguns ajustes em nossos deployments, garantir que nossos Pods sejam agendados nos Nodes corretos e distribuídos entre os datacenters corretamente.
A distribuição dos nossos nodes nas regiões e datacenters é a seguinte:
-
strigus-br-sp
- strigus-br-sp-1
- strigus-control-plane1
- strigus-worker3
- strigus-br-sp-2
- strigus-control-plane4
- strigus-worker1
- strigus-br-sp-1
-
strigus-br-ssa
- strigus-br-ssa-1
- strigus-control-plane2
- strigus-worker2
- strigus-br-ssa-2
- strigus-control-plane3
- strigus-worker4
- strigus-br-ssa-1
A primeira coisa que precisamos fazer é criar as Labels em nossos Nodes. Para isso, vamos utilizar o comando kubectl label nodes
e vamos adicionar as labels region
e datacenter
em cada um dos nossos Nodes.
kubectl label nodes strigus-control-plane1 region=strigus-br-sp datacenter=strigus-br-sp-1
kubectl label nodes strigus-control-plane2 region=strigus-br-ssa datacenter=strigus-br-ssa-1
kubectl label nodes strigus-control-plane3 region=strigus-br-ssa datacenter=strigus-br-ssa-2
kubectl label nodes strigus-control-plane4 region=strigus-br-sp datacenter=strigus-br-sp-2
kubectl label nodes strigus-worker1 region=strigus-br-sp datacenter=strigus-br-sp-2
kubectl label nodes strigus-worker2 region=strigus-br-ssa datacenter=strigus-br-ssa-1
kubectl label nodes strigus-worker3 region=strigus-br-sp datacenter=strigus-br-sp-1
kubectl label nodes strigus-worker4 region=strigus-br-ssa datacenter=strigus-br-ssa-2
Com o comando acima estamos utilizando o kubectl label nodes
para adicionar as labels region
e datacenter
em cada um dos nossos Nodes. Agora, se executarmos o comando kubectl get nodes strigus-control-plane1 --show-labels
, veremos algo como:
NAME STATUS ROLES AGE VERSION LABELS
strigus-control-plane1 Ready control-plane 65d v1.27.3 beta.kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,datacenter=strigus-br-sp-1,kubernetes.io/arch=amd64,kubernetes.io/hostname=strigus-control-plane1,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,region=strigus-br-sp
Pronto, as nossas labels estão criadas, com isso já conseguimos ter um pouco mais de organização em nosso cluster.
Mas ainda não acabou o nosso trabalho, cada uma das regiões possui um node com um hardware especial, que é uma GPU. Vamos criar uma label para identificar esses nodes, mas por enquanto é somente para identificar, não vamos fazer nada com eles ainda.
kubectl label nodes strigus-worker1 gpu=true
kubectl label nodes strigus-worker4 gpu=true
Pronto, por enquanto isso resolve, mas com certeza precisaremos adicionar mais labels em nossos nodes no futuro, mas por enquanto isso já resolve o nosso problema.
Caso eu queira remover alguma Label, basta utilizar o comando kubectl label nodes strigus-control-plane1 region-
e a label será removida.
Agora vamos entender como funcionam as Taints.
Taints são "manchas" ou "marcações" aplicadas aos Nodes que os marcam para evitar que certos Pods sejam agendados neles. Essa é uma forma bastante comum de isolar workloads em um cluster Kubernetes, por exemplo em momentos de manutenção ou quando você tem Nodes com recursos especiais, como GPUs.
Os Taints são aplicados nos Nodes e podem ter um efeito de NoSchedule
, PreferNoSchedule
ou NoExecute
. O efeito NoSchedule
faz com que o Kubernetes não agende Pods nesse Node a menos que eles tenham uma Toleration correspondente. O efeito PreferNoSchedule
faz com que o Kubernetes tente não agendar, mas não é uma garantia. E o efeito NoExecute
faz com que os Pods existentes sejam removidos se não tiverem uma Toleration correspondente.
Agora vamos entender isso na prática!
Em nosso primeiro exemplo vamos conhecer a Taint NoSchedule
. Para que você possa configurar um Taint em um Node, você utiliza o comando kubectl taint
. Por exemplo:
kubectl taint nodes strigus-worker1 key=value:NoSchedule
No comando acima, estamos aplicando um Taint no Node strigus-worker
com a chave key
e o valor value
e com o efeito NoSchedule
. Isso significa que o Kubernetes não irá agendar nenhum Pod nesse Node a menos que ele tenha uma Toleration correspondente, que veremos mais adiante.
Nesse exemplo estamos utilizando a chave key
e o valor value
, mas você pode utilizar qualquer chave e valor que desejar. Por exemplo, você pode utilizar environment=production
para marcar um Node como sendo de produção, ou gpu=true
para marcar um Node com uma GPU.
Você irá entender melhor o porquê de utilizar Taints e o porquê de utilizar chaves e valores específicos quando falarmos sobre Tolerations.
Para visualizar os Taints aplicados em um Node, você pode utilizar o comando kubectl describe node strigus-worker1
. Você verá algo como:
Taints: key=value:NoSchedule
Está lá conforme esperado! Agora o que precisamos fazer é testar! O nosso cluster ainda não está com nenhuma aplicação em execução, somente os Pods do próprio Kubernetes, então vamos criar alguns para testar. :)
Para esse teste, vamos criar um Deployment com 10 réplicas do Nginx, e vamos ver o que acontece.
kubectl create deployment nginx --image=nginx --replicas=10
Agora vamos verificar se os Pods foram criados e se estão em execução.
kubectl get pods -o wide
A saída será algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-77b4fdf86c-4rwb9 1/1 Running 0 12s 10.244.4.3 strigus-worker3 <none> <none>
nginx-77b4fdf86c-chpgb 1/1 Running 0 12s 10.244.6.2 strigus-worker2 <none> <none>
nginx-77b4fdf86c-dplq7 1/1 Running 0 12s 10.244.5.3 strigus-worker4 <none> <none>
nginx-77b4fdf86c-l5vwq 1/1 Running 0 12s 10.244.6.4 strigus-worker2 <none> <none>
nginx-77b4fdf86c-nwwvn 1/1 Running 0 12s 10.244.5.2 strigus-worker4 <none> <none>
nginx-77b4fdf86c-qz9t4 1/1 Running 0 12s 10.244.5.4 strigus-worker4 <none> <none>
nginx-77b4fdf86c-r4lt6 1/1 Running 0 12s 10.244.6.5 strigus-worker2 <none> <none>
nginx-77b4fdf86c-rmqnm 1/1 Running 0 12s 10.244.4.4 strigus-worker3 <none> <none>
nginx-77b4fdf86c-rsgbg 1/1 Running 0 12s 10.244.4.2 strigus-worker3 <none> <none>
nginx-77b4fdf86c-wnxg7 1/1 Running 0 12s 10.244.6.3 strigus-worker2 <none> <none>
Perceba que não temos nenhum Pod em execução no Node strigus-worker1
, que é o Node que aplicamos o Taint. Isso acontece porque o Kubernetes não irá agendar nenhum Pod nesse Node a menos que ele tenha uma Toleration correspondente, que veremos mais adiante.
Agora vamos remover o Taint do Node strigus-worker1
e ver o que acontece.
kubectl taint nodes strigus-worker1 key=value:NoSchedule-
Os Pods não serão movidos automaticamente para o Node strigus-worker1
, mas podemos usar o comando kubectl rollout restart deployment nginx
para reiniciar o Deployment e o Kubernetes redistribuirá os Pods entre os Nodes disponíveis.
kubectl rollout restart deployment nginx
Agora vamos verificar se os Pods foram criados e se estão em execução.
kubectl get pods -o wide
A saída será algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7c58f9889c-6qcpl 1/1 Running 0 28s 10.244.4.10 strigus-worker3 <none> <none>
nginx-7c58f9889c-lj7p5 1/1 Running 0 29s 10.244.3.3 strigus-worker1 <none> <none>
nginx-7c58f9889c-mdrj7 1/1 Running 0 29s 10.244.4.9 strigus-worker3 <none> <none>
nginx-7c58f9889c-nr7h9 1/1 Running 0 29s 10.244.6.9 strigus-worker2 <none> <none>
nginx-7c58f9889c-pqrb9 1/1 Running 0 26s 10.244.3.4 strigus-worker1 <none> <none>
nginx-7c58f9889c-pzx7n 1/1 Running 0 29s 10.244.5.8 strigus-worker4 <none> <none>
nginx-7c58f9889c-qn9hh 1/1 Running 0 28s 10.244.5.9 strigus-worker4 <none> <none>
nginx-7c58f9889c-wmm2n 1/1 Running 0 26s 10.244.6.11 strigus-worker2 <none> <none>
nginx-7c58f9889c-znrjt 1/1 Running 0 29s 10.244.3.2 strigus-worker1 <none> <none>
nginx-7c58f9889c-zp9g9 1/1 Running 0 28s 10.244.6.10 strigus-worker2 <none> <none>
Pronto, ele redistribuiu os Pods entre os Nodes disponíveis, e voltou a executar Pods no Node strigus-worker1
, afinal, nós removemos o Taint dele.
Agora vamos testar o efeito NoExecute
. Para isso, vamos aplicar um Taint no Node strigus-worker1
com o efeito NoExecute
.
kubectl taint nodes strigus-worker1 key=value:NoExecute
Diferente do efeito NoSchedule
, o efeito NoExecute
faz com que os Pods existentes sejam removidos se não tiverem uma Toleration correspondente.
Vamos ver o que aconteceu com os nossos Pods.
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7c58f9889c-6qcpl 1/1 Running 0 2m29s 10.244.4.10 strigus-worker3 <none> <none>
nginx-7c58f9889c-gm6tb 1/1 Running 0 5s 10.244.4.11 strigus-worker3 <none> <none>
nginx-7c58f9889c-lnhmf 1/1 Running 0 5s 10.244.5.10 strigus-worker4 <none> <none>
nginx-7c58f9889c-mdrj7 1/1 Running 0 2m30s 10.244.4.9 strigus-worker3 <none> <none>
nginx-7c58f9889c-mz78w 1/1 Running 0 5s 10.244.6.12 strigus-worker2 <none> <none>
nginx-7c58f9889c-nr7h9 1/1 Running 0 2m30s 10.244.6.9 strigus-worker2 <none> <none>
nginx-7c58f9889c-pzx7n 1/1 Running 0 2m30s 10.244.5.8 strigus-worker4 <none> <none>
nginx-7c58f9889c-qn9hh 1/1 Running 0 2m29s 10.244.5.9 strigus-worker4 <none> <none>
nginx-7c58f9889c-wmm2n 1/1 Running 0 2m27s 10.244.6.11 strigus-worker2 <none> <none>
nginx-7c58f9889c-zp9g9 1/1 Running 0 2m29s 10.244.6.10 strigus-worker2 <none> <none>
Funcionou! Os Pods que estavam sendo executados no Node strigus-worker1
foram removidos e agendados em outros Nodes.
Nesse caso, o Kubernetes não irá agendar nenhum Pod nesse Node a menos que ele tenha uma Toleration para o Taint que aplicamos.
Isso é interessante em momentos que você precisa realizar manutenção em um Node, garantindo que não teremos nenhum Pod executando nele e que nenhum Pod será agendado nesse Node.
Agora vamos remover o Taint do Node strigus-worker1
e ver o que acontece.
kubectl taint nodes strigus-worker1 key=value:NoExecute-
Mais uma vez vale lembrar, o Kubernetes não irá mover os Pods automaticamente para o Node strigus-worker1
, mas podemos usar o comando kubectl rollout restart deployment nginx
para reiniciar o Deployment e o Kubernetes redistribuirá os Pods entre os Nodes disponíveis.
kubectl rollout restart deployment nginx
Simples como voar!
Agora vamos entender o efeito PreferNoSchedule
. Para isso, vamos aplicar um Taint no Node strigus-worker1
com o efeito PreferNoSchedule
.
kubectl taint nodes strigus-worker1 key=value:PreferNoSchedule
Diferente do efeito NoSchedule
, o efeito PreferNoSchedule
faz com que o Kubernetes tente não agendar, mas não é uma garantia.
Ele tentará agendar os Pods em outros Nodes, mas se não for possível, ele irá agendar no Node que tem o Taint.
Os nossos Pods estão distribuidos da seguinte forma:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-67668985bc-6h2ml 1/1 Running 0 9s 10.244.3.5 strigus-worker1 <none> <none>
nginx-67668985bc-dbnxr 1/1 Running 0 9s 10.244.5.11 strigus-worker4 <none> <none>
nginx-67668985bc-hxkt8 1/1 Running 0 9s 10.244.3.6 strigus-worker1 <none> <none>
nginx-67668985bc-ldwck 1/1 Running 0 9s 10.244.6.13 strigus-worker2 <none> <none>
nginx-67668985bc-nvcd8 1/1 Running 0 7s 10.244.3.7 strigus-worker1 <none> <none>
nginx-67668985bc-pwz4d 1/1 Running 0 9s 10.244.4.12 strigus-worker3 <none> <none>
nginx-67668985bc-v2s2b 1/1 Running 0 7s 10.244.4.13 strigus-worker3 <none> <none>
nginx-67668985bc-xdqjw 1/1 Running 0 7s 10.244.5.13 strigus-worker4 <none> <none>
nginx-67668985bc-xkdt8 1/1 Running 0 7s 10.244.5.12 strigus-worker4 <none> <none>
nginx-67668985bc-zdtsq 1/1 Running 0 7s 10.244.6.14 strigus-worker2 <none> <none>
Temos Pods em execução no Node strigus-worker1
, que é o Node que aplicamos o Taint, pois esses Pods já estavam em execução antes de aplicarmos o Taint.
Agora vamos aumentar o número de réplicas do nosso Deployment para 20 e ver o que acontece.
kubectl scale deployment nginx --replicas=20
Agora vamos verificar se os Pods foram criados e se estão em execução.
kubectl get pods -o wide
A saída será algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-67668985bc-6h2ml 1/1 Running 0 2m24s 10.244.3.5 strigus-worker1 <none> <none>
nginx-67668985bc-9298b 1/1 Running 0 22s 10.244.5.17 strigus-worker4 <none> <none>
nginx-67668985bc-c8nck 1/1 Running 0 22s 10.244.4.17 strigus-worker3 <none> <none>
nginx-67668985bc-dbnxr 1/1 Running 0 2m24s 10.244.5.11 strigus-worker4 <none> <none>
nginx-67668985bc-fds62 1/1 Running 0 22s 10.244.6.18 strigus-worker2 <none> <none>
nginx-67668985bc-gtmq8 1/1 Running 0 22s 10.244.4.19 strigus-worker3 <none> <none>
nginx-67668985bc-hxkt8 1/1 Running 0 2m24s 10.244.3.6 strigus-worker1 <none> <none>
nginx-67668985bc-kbtqc 1/1 Running 0 22s 10.244.4.20 strigus-worker3 <none> <none>
nginx-67668985bc-ldwck 1/1 Running 0 2m24s 10.244.6.13 strigus-worker2 <none> <none>
nginx-67668985bc-mtsxv 1/1 Running 0 22s 10.244.6.19 strigus-worker2 <none> <none>
nginx-67668985bc-nvcd8 1/1 Running 0 2m22s 10.244.3.7 strigus-worker1 <none> <none>
nginx-67668985bc-pwz4d 1/1 Running 0 2m24s 10.244.4.12 strigus-worker3 <none> <none>
nginx-67668985bc-snvnt 1/1 Running 0 22s 10.244.5.16 strigus-worker4 <none> <none>
nginx-67668985bc-txgd4 1/1 Running 0 22s 10.244.4.18 strigus-worker3 <none> <none>
nginx-67668985bc-v2s2b 1/1 Running 0 2m22s 10.244.4.13 strigus-worker3 <none> <none>
nginx-67668985bc-w9hmj 1/1 Running 0 22s 10.244.6.20 strigus-worker2 <none> <none>
nginx-67668985bc-xdqjw 1/1 Running 0 2m22s 10.244.5.13 strigus-worker4 <none> <none>
nginx-67668985bc-xkdt8 1/1 Running 0 2m22s 10.244.5.12 strigus-worker4 <none> <none>
nginx-67668985bc-zdtsq 1/1 Running 0 2m22s 10.244.6.14 strigus-worker2 <none> <none>
nginx-67668985bc-zfglb 1/1 Running 0 22s 10.244.6.21 strigus-worker2 <none> <none>
O que vemos na saída do comando é que o Kube-Scheduler agendou os Pods em outros Nodes, mantendo somente os Pods que já estavam em execução no Node strigus-worker1
.
O Kube-scheduler somente irá agendar novos pods no Node strigus-worker1
se não houver nenhum outro Node disponível. Simples demais, não?!?
Agora que entendemos como os Taints funcionam e como eles influenciam o agendamento de Pods nos Nodes, vamos mergulhar no mundo das Tolerations. As Tolerations são como o "antídoto" para os Taints. Elas permitem que um Pod seja agendado em um Node que possui um Taint específico. Em outras palavras, elas "toleram" as Taints.
Vamos voltar ao nosso cluster da Strigus para entender melhor como isso funciona.
Imagine que temos um workload crítico que precisa ser executado em um Node com GPU. Já marcamos nossos Nodes com GPU com a label gpu=true
, e agora vamos usar Tolerations para garantir que nosso Pod possa ser agendado nesses Nodes. Isso não faz com que o Pod seja agendado nesses Nodes, mas permite que ele seja agendado nesses Nodes. Entendeu a diferença?
Primeiro, vamos criar um Taint no Node strigus-worker1
, que possui uma GPU.
kubectl taint nodes strigus-worker1 gpu=true:NoSchedule
Com esse Taint, estamos dizendo que nenhum Pod será agendado nesse Node, a menos que ele tenha uma Toleration específica para a Taint gpu=true
.
Vamos criar um Deployment com 5 réplicas do Nginx e ver o que acontece.
kubectl create deployment nginx --image=nginx --replicas=5
Agora vamos verificar se os Pods foram criados e se estão em execução.
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-77b4fdf86c-274jk 1/1 Running 0 5s 10.244.6.31 strigus-worker2 <none> <none>
nginx-77b4fdf86c-97r8d 1/1 Running 0 5s 10.244.5.25 strigus-worker4 <none> <none>
nginx-77b4fdf86c-cm96h 1/1 Running 0 5s 10.244.6.30 strigus-worker2 <none> <none>
nginx-77b4fdf86c-rhdmh 1/1 Running 0 5s 10.244.4.29 strigus-worker3 <none> <none>
nginx-77b4fdf86c-ttqzg 1/1 Running 0 5s 10.244.4.30 strigus-worker3 <none> <none>
Como esperado, nenhum Pod foi agendado no Node strigus-worker1
, que é o Node que aplicamos o Taint.
Agora, vamos modificar o nosso Deployment do Nginx para que ele tenha uma Toleration para a Taint gpu=true
.
Para ficar mais fácil, vamos criar um manifesto para o nosso Deployment utilizando o comando kubectl create deployment nginx --image=nginx --replicas=5 --dry-run=client -o yaml > gpu-deployment.yaml
.
Agora vamos editar o arquivo gpu-deployment.yaml
e adicionar a Toleration para a Taint gpu=true
.
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
tolerations:
- key: gpu
operator: Equal
value: "true"
effect: NoSchedule
Vamos entender o que adicionamos no arquivo.
tolerations:
- key: gpu
operator: Equal
value: "true"
effect: NoSchedule
Onde:
key
é a chave do Taint que queremos tolerar.operator
é o operador que queremos utilizar. Nesse caso, estamos utilizando o operadorEqual
, que significa que o valor do Taint precisa ser igual ao valor da Toleration.value
é o valor do Taint que queremos tolerar.effect
é o efeito do Taint que queremos tolerar. Nesse caso, estamos utilizando o efeitoNoSchedule
, que significa que o Kubernetes não irá agendar nenhum Pod nesse Node a menos que ele tenha uma Toleration correspondente.
Vamos aplicar esse Deployment e ver o que acontece.
kubectl apply -f gpu-deployment.yaml
Se verificarmos os Pods agora, veremos que o nosso Pod gpu-pod
está rodando no Node strigus-worker1
.
kubectl get pods -o wide
A saída mostrará algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7b68fffb4-czrpt 1/1 Running 0 11s 10.244.5.24 strigus-worker4 <none> <none>
nginx-7b68fffb4-d577x 1/1 Running 0 11s 10.244.4.28 strigus-worker3 <none> <none>
nginx-7b68fffb4-g2kxr 1/1 Running 0 11s 10.244.3.10 strigus-worker1 <none> <none>
nginx-7b68fffb4-m5kln 1/1 Running 0 11s 10.244.6.29 strigus-worker2 <none> <none>
nginx-7b68fffb4-n6kck 1/1 Running 0 11s 10.244.3.11 strigus-worker1 <none> <none>
Isso demonstra o poder das Tolerations em combinação com os Taints. Podemos controlar com precisão onde nossos Pods são agendados, garantindo que workloads críticos tenham os recursos que necessitam.
Para remover o Taint do Node strigus-worker1
, basta usar o comando kubectl taint nodes strigus-worker1 gpu=true:NoSchedule-
.
Mas lembrando mais uma vez, as Tolerations não fazem com que o Pod seja agendado nesses Nodes, mas permite que ele seja agendado nesses Nodes.
Então, caso você queira garantir que determinado Pod seja executado em determinado Node, você precisa utilizar o conceito de Affinity, que veremos agora.
Affinity e Antiaffinity são conceitos que permitem que você defina regras para o agendamento de Pods em determinados Nodes. Com eles você pode definir regras para que Pods sejam agendados em Nodes específicos, ou até mesmo para que Pods não sejam agendados em Nodes específicos.
Vamos entender como isso funciona na prática.
Você se lembra que adicionamos a label gpu=true
nos Nodes que possuem GPU? Então, vamos utilizar essa label para garantir que o nosso Pod seja agendado somente neles. Para isso, vamos utilizar o conceito de Affinity.
Vamos criar um Deployment com 5 réplicas do Nginx com a seguinte configuração:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
Vamos entender o que temos de novo:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: gpu
operator: In
values:
- "true"
Onde:
affinity
é o início da configuração de Affinity.nodeAffinity
é o conceito de Affinity para Nodes.requiredDuringSchedulingIgnoredDuringExecution
é usado para indicar que o Pod só pode ser agendado em um Node que atenda aos requisitos de Affinity, está falando que essa regra é obrigatória no momento do agendamento do Pod, mas que pode ser ignorada durante a execução do Pod.nodeSelectorTerms
é usado para indicar os termos de seleção de Nodes, que será usado para selecionar os Nodes que atendem aos requisitos de Affinity.matchExpressions
é usado para indicar as expressões de seleção de Nodes, ou seja, o nome da label, o operador e o valor da label.key
é o nome da label que queremos utilizar para selecionar os Nodes.operator
é o operador que queremos utilizar. Nesse caso, estamos utilizando o operadorIn
, que significa que o valor da label precisa estar dentro dos valores que estamos informando.values
é o valor da label que queremos utilizar para selecionar os Nodes.
Sendo assim, estamos falando para o Kubernetes que o nosso Pod só pode ser agendado em um Node que tenha a label gpu=true
. Simples assim!
Vamos aplicar esse Deployment e ver o que acontece.
kubectl apply -f gpu-deployment.yaml
Se verificarmos os Pods agora, veremos que os nossos Pods estão rodando somente nos Nodes que possuem a label gpu=true
.
kubectl get pods -o wide
A saída mostrará algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-5dd89c4b9b-hwzcx 1/1 Running 0 4s 10.244.3.13 strigus-worker1 <none> <none>
nginx-5dd89c4b9b-m4fj2 1/1 Running 0 4s 10.244.5.37 strigus-worker4 <none> <none>
nginx-5dd89c4b9b-msnv8 1/1 Running 0 4s 10.244.3.14 strigus-worker1 <none> <none>
nginx-5dd89c4b9b-nlcgs 1/1 Running 0 4s 10.244.5.36 strigus-worker4 <none> <none>
nginx-5dd89c4b9b-trgw7 1/1 Running 0 4s 10.244.3.12 strigus-worker1 <none> <none>
Isso demonstra o poder do Affinity. Podemos controlar com precisão onde nossos Pods serão agendados, garantindo que workloads críticos tenham os recursos que necessitam. Sensacional demais!
Agora vamos entender o conceito de Antiaffinity.
O Antiaffinity é o conceito contrário ao Affinity. Com ele você pode definir regras para que Pods não sejam agendados em Nodes específicos.
Vamos conhecer ele na prática!
Vamos relembrar como os nossos Nodes estão distribuidos.
-
strigus-br-sp
- strigus-br-sp-1
- strigus-control-plane1
- strigus-worker3
- strigus-br-sp-2
- strigus-control-plane4
- strigus-worker1
- strigus-br-sp-1
-
strigus-br-ssa
- strigus-br-ssa-1
- strigus-control-plane2
- strigus-worker2
- strigus-br-ssa-2
- strigus-control-plane3
- strigus-worker4
- strigus-br-ssa-1
Ou seja, duas regiões e duas zonas de disponibilidade em cada região, certo?
Cada Node está com as labels region
e datacenter
devidamente configuradas, representando a região e a zona de disponibilidade que ele está.
Agora vamos imaginar que precisamos garantir que a nossa estará sendo distribuida entre as regiões e zonas de disponibilidade, ou seja, precisamos garantir que os nossos Pods não sejam agendados em Nodes que estejam na mesma região e na mesma zona de disponibilidade.
Para isso, vamos utilizar o conceito de Antiaffinity. \o/
Vamos criar um Deployment com 5 réplicas do Nginx com a seguinte configuração:
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx
name: nginx
spec:
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- image: nginx
name: nginx
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "datacenter"
Vamos entender o que estamos fazendo:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: "datacenter"
Onde:
affinity
é o início da configuração de Affinity.podAntiAffinity
é o conceito de Antiaffinity para Pods.requiredDuringSchedulingIgnoredDuringExecution
é usado para indicar que o Pod só pode ser agendado em um Node que atenda aos requisitos de Antiaffinity, está falando que essa regra é obrigatória no momento do agendamento do Pod, mas que pode ser ignorada durante a execução do Pod.labelSelector
é usado para indicar os termos de seleção de Pods, que será usado para selecionar os Pods que atendem aos requisitos de Antiaffinity.matchExpressions
é usado para indicar as expressões de seleção de Pods, ou seja, o nome da label, o operador e o valor da label.key
é o nome da label que queremos utilizar para selecionar os Pods.operator
é o operador que queremos utilizar. Nesse caso, estamos utilizando o operadorIn
, que significa que o valor da label precisa estar dentro dos valores que estamos informando.values
é o valor da label que queremos utilizar para selecionar os Pods.topologyKey
é usado para indicar a chave de topologia que será usada para selecionar os Pods. Nesse caso, estamos utilizando a chavedatacenter
, que é a chave que representa a zona de disponibilidade.
Sendo assim, estamos falando para o Kubernetes que o nosso Pod só pode ser agendado em um Node que não tenha nenhum Pod com a label app=nginx
na mesma zona de disponibilidade.
Você já deve estar imaginando que isso irá dar errado, certo? Pois somente temos 4 datacenters, e estamos tentando agendar 5 Pods, ou seja, um Pod não será agendado.
Vamos aplicar esse Deployment e ver o que acontece.
kubectl apply -f nginx-deployment.yaml
Se verificarmos os Pods agora, veremos que somente 4 Pods estão rodando, e um Pod não foi agendado.
kubectl get pods -o wide
A saída mostrará algo como:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-58b9c8f764-7rqr7 1/1 Running 0 8s 10.244.4.32 strigus-worker3 <none> <none>
nginx-58b9c8f764-mfpp7 1/1 Running 0 8s 10.244.6.33 strigus-worker2 <none> <none>
nginx-58b9c8f764-n45p6 1/1 Running 0 8s 10.244.3.15 strigus-worker <none> <none>
nginx-58b9c8f764-qwjdk 0/1 Pending 0 8s <none> <none> <none> <none>
nginx-58b9c8f764-qzffs 1/1 Running 0 8s 10.244.5.38 strigus-worker4 <none> <none>
Perceba que o Pod nginx-58b9c8f764-qwjdk
está com o status Pending
, pois não foi possível agendar ele em nenhum Node, pois em nossa regra nós falamos que ele não pode ser agendado em Nodes que tenham Pods com a label app=nginx
na mesma zona de disponibilidade, ou seja, não teremos nenhum Node que atenda a essa regra.
Sensacional demais, eu sei! \o/
No conteúdo de hoje, focamos em conceitos avançados de Kubernetes, explorando Taints, Tolerations, Affinity, e Antiaffinity. Esses são elementos cruciais para o gerenciamento eficiente de um cluster Kubernetes, permitindo controle refinado sobre onde e como os Pods são agendados.
Taints e Tolerations:
- Taints: Utilizados para "manchar" Nodes, prevenindo que certos Pods sejam agendados neles, exceto se tiverem Tolerations correspondentes.
- Tolerations: Permitem que Pods sejam agendados em Nodes com Taints específicos.
Affinity e Antiaffinity:
- Affinity: Permite especificar que Pods sejam agendados em Nodes específicos, baseados em labels.
- Antiaffinity: Utilizado para evitar que Pods sejam agendados em Nodes específicos, ajudando na distribuição equilibrada de Pods em diferentes Nodes.
Vimos como organizar e gerenciar um cluster Kubernetes considerando diferentes cenários, como manutenção de Nodes, uso de recursos especiais (GPUs), e a distribuição de Pods em diferentes regiões e datacenters para alta disponibilidade.
Abordamos também como os Taints e Tolerations podem ser usados na prática, com comandos específicos para adicionar e remover Taints, e como aplicar Tolerations em Pods. Isso incluiu a configuração de Taints com diferentes efeitos como NoSchedule
, PreferNoSchedule
, e NoExecute
.
No contexto de Affinity e Antiaffinity, detalhamos como esses conceitos são aplicados para assegurar que Pods sejam alocados em Nodes específicos ou distribuídos entre Nodes para evitar pontos únicos de falha, demonstrando a importância desses conceitos para a confiabilidade e eficiência de um cluster Kubernetes.