No artigo anterior, forneci uma introdução detalhada ao conceito de Pods, que são os mais importantes no projeto Kubernetes. No artigo de hoje, compartilharei mais detalhes sobre os objetos Pod.
Pods vs. Máquinas Virtuais
A essa altura, você já deve ter muito claro que é o Pod, e não o contêiner, a menor unidade de orquestração no projeto Kubernetes. Esse design se reflete nos objetos da API, onde o contêiner se torna apenas um campo comum dentro dos atributos do Pod. Naturalmente, surge a pergunta: quais atributos pertencem ao objeto Pod e quais pertencem ao Container?
O Pod desempenha o papel de uma “máquina virtual” em ambientes de implantação tradicionais. Esse design visa tornar a transição de ambientes tradicionais (ambientes de máquina virtual) para Kubernetes (ambientes de contêiner) mais suave.
Se considerarmos o Pod como a “máquina” em um ambiente tradicional e o contêiner como o “programa do usuário” executado nessa “máquina”, muitos aspectos do design do objeto Pod se tornam muito mais fáceis de entender.
Por exemplo, atributos relacionados a agendamento, rede, armazenamento e segurança estão essencialmente no nível do Pod. A característica comum desses atributos é que eles descrevem a “máquina” como um todo, não os “programas” executados dentro dela.
Por exemplo, configurar a placa de rede da “máquina” (ou seja, a definição de rede do Pod), configurar o disco da “máquina” (ou seja, a definição de armazenamento do Pod) e configurar o firewall da “máquina” (ou seja, a definição de segurança do Pod). Sem mencionar em qual servidor essa “máquina” está sendo executada (ou seja, o agendamento do Pod).
Kubernetes YAML
No Kubernetes, definimos recursos Pod por meio de arquivos declarativos.
NodeSelector: Este é um campo que permite aos usuários vincular um Pod a um Node, como mostrado abaixo:
apiVersion: v1
kind: Pod
...
spec:
nodeSelector:
disktype: ssd
Tal configuração significa que este Pod só pode ser executado em um nó com o rótulo “disktype: ssd”; caso contrário, falhará no agendamento.
NodeName: Uma vez que este campo de um Pod é atribuído, o projeto Kubernetes considera que o Pod foi agendado, e o resultado do agendamento é o nome do nó atribuído. Portanto, esse campo geralmente é definido pelo scheduler, mas os usuários também podem defini-lo para “enganar” o scheduler, embora essa prática geralmente seja usada apenas durante testes ou depuração.
HostAliases: Define o conteúdo do arquivo hosts do Pod (por exemplo, /etc/hosts), como mostrado abaixo:
apiVersion: v1
kind: Pod
...
spec:
hostAliases:
- ip: "10.1.2.3"
hostnames:
- "foo.remote"
- "bar.remote"
...
Deve-se notar que, no projeto Kubernetes, se você quiser definir o conteúdo do arquivo hosts, deve fazê-lo por meio deste método. Caso contrário, se você modificar diretamente o arquivo hosts, o kubelet substituirá automaticamente o conteúdo modificado após o Pod ser excluído e recriado.
Além das configurações relacionadas à “máquina” acima, você também pode notar que quaisquer atributos relacionados ao Linux Namespace do contêiner também estão no nível do Pod. Isso também é fácil de entender: o design do Pod é permitir que os contêineres dentro dele compartilhem o máximo possível de Linux Namespaces, mantendo apenas as capacidades de isolamento e restrição necessárias. Dessa forma, o efeito simulado pelo Pod é muito semelhante à relação entre programas em uma máquina virtual.
apiVersion: v1
kind: Pod
metadata:
name: lifecycle-demo
spec:
containers:
- name: lifecycle-demo-container
image: nginx
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Hello from the postStart handler > /usr/share/message"]
preStop:
exec:
command: ["/usr/sbin/nginx","-s","quit"]
Este é um arquivo YAML de Pod da documentação oficial do Kubernetes. Na verdade, é muito simples, apenas definindo um contêiner com a imagem nginx. No entanto, na seção de containers deste arquivo YAML, você verá que o contêiner definiu os parâmetros postStart e preStop.
O que isso significa?
Vamos começar com postStart, que se refere a uma operação executada imediatamente após o contêiner iniciar. Deve estar claro que a operação definida por postStart é executada após o ENTRYPOINT do contêiner Docker, mas não garante ordem estritamente. Ou seja, quando postStart inicia, o ENTRYPOINT pode não ter terminado ainda.
Claro, se a execução do postStart expirar ou encontrar um erro, o Kubernetes reportará uma mensagem de erro indicando que o contêiner falhou ao iniciar nos Eventos desse Pod, fazendo com que o Pod também entre em estado de falha.
Da mesma forma, o momento do preStop ocorre antes do contêiner ser finalizado (por exemplo, quando ele recebe um sinal SIGKILL). É importante esclarecer que a execução da operação preStop é síncrona. Portanto, ela bloqueia o processo atual de finalização do contêiner até que a operação definida seja concluída, o que é diferente do postStart.
Então, neste exemplo, após o contêiner iniciar com sucesso, ele escreve uma “mensagem de saudação” em /usr/share/message (ou seja, a operação definida por postStart). E antes deste contêiner ser excluído, primeiro chamamos o comando de saída do nginx (ou seja, a operação definida por preStop), alcançando assim um “desligamento gracioso” do contêiner.
Depois de se familiarizar com os principais campos do Pod e sua seção Container, compartilharei o ciclo de vida de tal objeto Pod no Kubernetes.
As mudanças no ciclo de vida do Pod são refletidas principalmente na parte Status do objeto API do Pod, que é seu terceiro campo importante além de Metadata e Spec. Entre eles, pod.status.phase representa o estado atual do Pod, com as seguintes condições possíveis:
1. Pending. Este status significa que o arquivo YAML do Pod foi submetido ao Kubernetes, o objeto API foi criado e salvo no Etcd. No entanto, alguns contêineres neste Pod não podem ser criados sem problemas por algum motivo. Por exemplo, falha no agendamento.
2. Running. Neste estado, o Pod foi agendado com sucesso e está vinculado a um nó específico. Todos os contêineres que ele contém foram criados com sucesso e pelo menos um está atualmente em execução.
3. Succeeded. Este estado significa que todos os contêineres no Pod foram executados até a conclusão e saíram. Essa situação é mais comum ao executar tarefas únicas.
4. Failed. Neste estado, pelo menos um contêiner no Pod saiu em um estado anormal (um código de retorno diferente de zero). A aparência deste estado significa que você precisa descobrir como depurar o aplicativo no contêiner, por exemplo, verificando os Eventos e logs do Pod.
5. Unknown. Este é um estado anormal, indicando que o status do Pod não pode ser reportado continuamente pelo kubelet ao kube-apiserver, provavelmente devido a um problema de comunicação entre o master e o Kubelet.
Além disso, o campo Status do objeto Pod pode ser ainda mais detalhado em um conjunto de Conditions. Esses valores de status detalhados incluem: PodScheduled, Ready, Initialized e Unschedulable. Eles são usados principalmente para descrever as razões específicas para o Status atual.
Por exemplo, se o Status atual do Pod for Pending e o Condition correspondente for Unschedulable, significa que há um problema em seu agendamento.
Entre eles, o sub-status Ready é particularmente digno de atenção: significa que o Pod não apenas iniciou normalmente (no estado Running), mas também está pronto para fornecer serviços externamente. Há uma diferença entre esses dois (Running e Ready), então você pode querer pensar sobre isso cuidadosamente.
Essas mensagens de status do Pod são um critério importante para julgarmos a condição de execução do aplicativo, especialmente quando o Pod entra em um estado não “Running”. Você deve ser capaz de reagir rapidamente, começar a rastrear e localizar com base na situação anormal representada, em vez de consultar freneticamente a documentação.
Resumo
No artigo de hoje, expliquei em detalhes o objeto API Pod, apresentei os principais métodos de uso dos Pods e analisei as semelhanças e diferenças entre Pods e Containers em termos de campos. Espero que essas explicações possam ajudá-lo a entender melhor e lembrar os campos principais no YAML do Pod e seus significados precisos.
Na verdade, o objeto API Pod é o conceito mais central em todo o sistema Kubernetes e também é usado nos controladores que explicarei mais adiante.
