This is the multi-page printable view of this section. Click here to print.
Executar Aplicações
- 1: Executar uma Aplicação Sem Estado com um Deployment
- 2: Execute uma Aplicação Com Estado e Replicada
- 3: Acessando a API do Kubernetes a partir de um Pod
1 - Executar uma Aplicação Sem Estado com um Deployment
Esta página mostra como executar uma aplicação usando um objeto Deployment do Kubernetes.
Objetivos
- Criar uma instalação do nginx com um Deployment.
- Usar o kubectl para listar informações sobre o Deployment.
- Atualizar o Deployment.
Antes de você começar
Você precisa ter um cluster do Kubernetes e a ferramenta de linha de comando kubectl deve estar configurada para se comunicar com seu cluster. É recomendado executar esse tutorial em um cluster com pelo menos dois nós que não estejam atuando como hosts de camada de gerenciamento. Se você ainda não possui um cluster, pode criar um usando o minikube ou pode usar um dos seguintes ambientes:
O seu servidor Kubernetes deve estar numa versão igual ou superior a v1.9.Para verificar a versão, digite kubectl version
.
Criando e explorando uma instalação do nginx com um Deployment
Você pode executar uma aplicação criando um objeto Deployment do Kubernetes, e pode descrever um Deployment em um arquivo YAML. Por exemplo, este arquivo YAML descreve um Deployment que executa a imagem do contêiner nginx:1.14.2:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2 # diz ao deployment para executar 2 pods que correspondam ao modelo
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
-
Crie um Deployment com base no arquivo YAML:
kubectl apply -f https://k8s.io/examples/application/deployment.yaml
-
Exiba informações sobre o Deployment:
kubectl describe deployment nginx-deployment
A saída é semelhante a esta:
Name: nginx-deployment Namespace: default CreationTimestamp: Tue, 30 Aug 2016 18:11:37 -0700 Labels: app=nginx Annotations: deployment.kubernetes.io/revision=1 Selector: app=nginx Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge Pod Template: Labels: app=nginx Containers: nginx: Image: nginx:1.14.2 Port: 80/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable Progressing True NewReplicaSetAvailable OldReplicaSets: <none> NewReplicaSet: nginx-deployment-1771418926 (2/2 replicas created) No events.
-
Liste os Pods criados pelo Deployment:
kubectl get pods -l app=nginx
A saída é semelhante a esta:
NAME READY STATUS RESTARTS AGE nginx-deployment-1771418926-7o5ns 1/1 Running 0 16h nginx-deployment-1771418926-r18az 1/1 Running 0 16h
-
Exiba informações sobre um Pod:
kubectl describe pod <pod-name>
onde
<pod-name>
é o nome de um dos seus Pods.
Atualizando o Deployment
Você pode atualizar o Deployment aplicando um novo arquivo YAML. Este arquivo YAML especifica que o Deployment deve ser atualizado para usar o nginx:1.16.1.
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.1 # Atualiza a versão do nginx de 1.14.2 para 1.16.1
ports:
- containerPort: 80
-
Aplique o novo arquivo YAML:
kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
-
Observe o Deployment criar Pods com novos nomes e excluir os Pods antigos:
kubectl get pods -l app=nginx
Escalonando a aplicação aumentando a contagem de réplicas
Você pode aumentar o número de Pods no seu Deployment aplicando um novo arquivo YAML. Este arquivo YAML define replicas
como 4, o que especifica que o Deployment deve ter quatro Pods:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
spec:
selector:
matchLabels:
app: nginx
replicas: 4 # Atualiza a contagem de réplicas de 2 para 4
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.16.1
ports:
- containerPort: 80
-
Aplique o novo arquivo YAML:
kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
-
Verifique que o Deployment possui quatro Pods:
kubectl get pods -l app=nginx
A saída é semelhante a esta:
NAME READY STATUS RESTARTS AGE nginx-deployment-148880595-4zdqq 1/1 Running 0 25s nginx-deployment-148880595-6zgi1 1/1 Running 0 25s nginx-deployment-148880595-fxcez 1/1 Running 0 2m nginx-deployment-148880595-rwovn 1/1 Running 0 2m
Excluindo um Deployment
Exclua o Deployment pelo nome:
kubectl delete deployment nginx-deployment
Controladores de Replicação -- a Forma Antiga
A forma preferida de criar uma aplicação replicada é usar um Deployment, que por sua vez utiliza um ReplicaSet. Antes do Deployment e do ReplicaSet serem adicionados ao Kubernetes, aplicações replicadas eram configuradas usando um Controlador de Replicação (ReplicationController).
Próximos passos
- Saiba mais sobre objeto Deployment.
2 - Execute uma Aplicação Com Estado e Replicada
Esta página mostra como executar uma aplicação com estado e replicada usando um StatefulSet. Esta aplicação é um banco de dados MySQL replicado. A topologia de exemplo possui um único servidor primário e múltiplas réplicas, utilizando replicação assíncrona baseada em linhas.
Nota:
Esta não é uma configuração para produção. As configurações do MySQL permanecem nos padrões inseguros para manter o foco nos padrões gerais de execução de aplicações com estado no Kubernetes.Antes de você começar
Você precisa ter um cluster do Kubernetes e a ferramenta de linha de comando kubectl deve estar configurada para se comunicar com seu cluster. É recomendado executar esse tutorial em um cluster com pelo menos dois nós que não estejam atuando como hosts de camada de gerenciamento. Se você ainda não possui um cluster, pode criar um usando o minikube ou pode usar um dos seguintes ambientes:
Você precisa ter um provisionador dinâmico de PersistentVolume com uma StorageClass padrão, ou provisionar PersistentVolumes estaticamente por conta própria para atender aos PersistentVolumeClaims utilizados aqui.
- Este tutorial assume que você está familiarizado com PersistentVolumes e StatefulSets, assim como outros conceitos centrais como Pods, Services e ConfigMaps.
- Algum conhecimento prévio de MySQL ajuda, mas este tutorial busca apresentar padrões gerais que devem ser úteis para outros sistemas.
- Você está utilizando o namespace padrão ou outro namespace que não contenha objetos conflitantes.
- Você precisa ter uma CPU compatível com AMD64.
Objetivos
- Implantar uma topologia MySQL replicada com um StatefulSet.
- Enviar tráfego de cliente MySQL.
- Observar a resistência a indisponibilidades.
- Escalonar o StatefulSet para mais ou para menos réplicas.
Implantar o MySQL
A instalação de exemplo do MySQL consiste em um ConfigMap, dois Services e um StatefulSet.
Criar um ConfigMap
Crie o ConfigMap a partir do seguinte arquivo de configuração YAML:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
app.kubernetes.io/name: mysql
data:
primary.cnf: |
# Aplique esta configuração apenas no primário.
[mysqld]
log-bin
replica.cnf: |
# Aplique esta configuração apenas nas réplicas.
[mysqld]
super-read-only
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
Este ConfigMap fornece substituições para o my.cnf
que permitem controlar independentemente
a configuração no servidor MySQL primário e em suas réplicas.
Neste caso, você deseja que o servidor primário possa disponibilizar logs de replicação para as réplicas
e que as réplicas rejeitem qualquer escrita que não venha por meio da replicação.
Não há nada de especial no próprio ConfigMap que faça com que diferentes partes sejam aplicadas a diferentes Pods. Cada Pod decide qual parte utilizar durante sua inicialização, com base nas informações fornecidas pelo controlador StatefulSet.
Criar Services
Crie os Services a partir do seguinte arquivo de configuração YAML:
# Service headless para entradas DNS estáveis dos membros do StatefulSet.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
app.kubernetes.io/name: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service para conectar a qualquer instância MySQL para leituras.
# Para escritas, é necessário conectar-se ao primário: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
app.kubernetes.io/name: mysql
readonly: "true"
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
O Service headless fornece um local para as entradas de DNS que o
controlador do StatefulSet cria para cada
Pod que faz parte do conjunto.
Como o Service headless se chama mysql
, os Pods são acessíveis por meio da resolução de <nome-do-pod>.mysql
a partir de qualquer outro Pod no mesmo cluster e namespace do Kubernetes.
O Service de cliente, chamado mysql-read
, é um Service normal com seu próprio IP de cluster,
que distribui as conexões entre todos os Pods MySQL que estejam prontos (Ready).
O conjunto de endpoints potenciais inclui o servidor MySQL primário e todas as réplicas.
Observe que apenas consultas de leitura podem utilizar o Service de cliente com balanceamento de carga. Como existe apenas um servidor MySQL primário, os clientes devem se conectar diretamente ao Pod MySQL primário (por meio de sua entrada DNS no Service headless) para executar operações de escrita.
Criar o StatefulSet
Por fim, crie o StatefulSet a partir do seguinte arquivo de configuração YAML:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
app.kubernetes.io/name: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
app.kubernetes.io/name: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Gerar o server-id do MySQL a partir do índice ordinal do pod.
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Adicione um deslocamento (offset) para evitar o valor reservado server-id=0.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copie os arquivos conf.d apropriados do config-map para o emptyDir.
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
- name: clone-mysql
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
# Pule a clonagem se os dados já existirem.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Pule a clonagem no primário (índice ordinal 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone os dados do peer anterior.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare o backup.
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Verifique se é possível executar consultas via TCP (skip-networking está desativado).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# Determine a posição do binlog dos dados clonados, se houver.
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# O XtraBackup já gerou uma consulta "CHANGE MASTER TO" parcial
# porque estamos clonando de uma réplica existente. (É necessário remover o ponto e vírgula final!)
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# Ignore o xtrabackup_binlog_info neste caso (não é útil).
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# Estamos clonando diretamente do primário. Interprete a posição do binlog.
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# Verifique se é necessário completar a clonagem iniciando a replicação.
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mysql-0.mysql', \
MASTER_USER='root', \
MASTER_PASSWORD='', \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
# Em caso de reinício do contêiner, tente isso no máximo uma vez.
mv change_master_to.sql.in change_master_to.sql.orig
fi
# Inicie um servidor para enviar backups quando solicitado pelos peers.
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
Você pode acompanhar o progresso da inicialização executando:
kubectl get pods -l app=mysql --watch
Após algum tempo, você deverá ver os 3 Pods com o status Running
:
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 2m
mysql-1 2/2 Running 0 1m
mysql-2 2/2 Running 0 1m
Pressione Ctrl+C para cancelar o watch.
Nota:
Se você não observar nenhum progresso, certifique-se de que há um provisionador dinâmico de PersistentVolume habilitado, conforme mencionado nos pré-requisitos.Este manifesto utiliza diversas técnicas para gerenciar Pods com estado como parte de um StatefulSet. A próxima seção destaca algumas dessas técnicas para explicar o que acontece à medida que o StatefulSet cria os Pods.
Entendendo a inicialização de Pods com estado
O controlador do StatefulSet inicia os Pods um de cada vez, na ordem do seu índice ordinal. Ele aguarda até que cada Pod reporte estar Ready antes de iniciar o próximo.
Além disso, o controlador atribui a cada Pod um nome único e estável no formato
<nome-do-statefulset>-<índice-ordinal>
, o que resulta em Pods chamados mysql-0
, mysql-1
e mysql-2
.
O template de Pod no manifesto do StatefulSet acima aproveita essas propriedades para realizar a inicialização ordenada da replicação do MySQL.
Gerando configuração
Antes de iniciar qualquer um dos contêineres especificados no Pod, o Pod executa primeiro todos os contêineres de inicialização na ordem definida.
O primeiro init container, chamado init-mysql
, gera arquivos de configuração
especiais do MySQL com base no índice ordinal.
O script determina seu próprio índice ordinal extraindo-o do final do nome do Pod, que é retornado pelo comando hostname
.
Em seguida, ele salva o ordinal (com um deslocamento numérico para evitar valores reservados)
em um arquivo chamado server-id.cnf
no diretório conf.d
do MySQL.
Isso traduz a identidade única e estável fornecida pelo StatefulSet
para o domínio dos IDs de servidor do MySQL, que exigem as mesmas propriedades.
O script no contêiner init-mysql
também aplica primary.cnf
ou
replica.cnf
do ConfigMap, copiando o conteúdo para o diretório conf.d
.
Como a topologia de exemplo consiste em um único servidor MySQL primário e qualquer número de réplicas,
o script atribui o ordinal 0
como o servidor primário, e todos os demais como réplicas.
Combinado com a garantia de ordem de implantação
do controlador StatefulSet, isso garante que o servidor MySQL primário esteja Ready antes de criar as réplicas,
para que elas possam começar a replicar.
Clonando dados existentes
De modo geral, quando um novo Pod entra no conjunto como réplica, ele deve assumir que o servidor MySQL primário pode já conter dados. Também deve considerar que os logs de replicação podem não cobrir todo o histórico desde o início. Essas suposições conservadoras são fundamentais para permitir que um StatefulSet em execução possa ser escalonado para mais ou para menos ao longo do tempo, em vez de ficar limitado ao seu tamanho inicial.
O segundo container de inicialização, chamado clone-mysql
, realiza uma operação de clonagem em um Pod réplica
na primeira vez que ele é iniciado em um PersistentVolume vazio.
Isso significa que ele copia todos os dados existentes de outro Pod em execução,
de modo que seu estado local fique consistente o suficiente para começar a replicar a partir do servidor primário.
O próprio MySQL não fornece um mecanismo para isso, então o exemplo utiliza uma ferramenta
open source popular chamada Percona XtraBackup.
Durante a clonagem, o servidor MySQL de origem pode sofrer redução de desempenho.
Para minimizar o impacto no servidor MySQL primário, o script instrui cada Pod a clonar a partir do Pod cujo índice ordinal é um a menos.
Isso funciona porque o controlador do StatefulSet sempre garante que o Pod N
esteja Ready antes de iniciar o Pod N+1
.
Iniciando a replicação
Após a conclusão bem-sucedida dos contêineres de inicialização, os contêineres regulares são executados.
Os Pods MySQL consistem em um contêiner mysql
, que executa o servidor mysqld
,
e um contêiner xtrabackup
, que atua como um sidecar.
O sidecar xtrabackup
analisa os arquivos de dados clonados e determina se
é necessário inicializar a replicação do MySQL na réplica.
Se for o caso, ele aguarda o mysqld
estar pronto e então executa os comandos
CHANGE MASTER TO
e START SLAVE
com os parâmetros de replicação extraídos dos arquivos clonados pelo XtraBackup.
Assim que uma réplica inicia a replicação, ela memoriza seu servidor MySQL primário e
reconecta-se automaticamente caso o servidor reinicie ou a conexão seja perdida.
Além disso, como as réplicas procuram o servidor primário pelo seu nome DNS estável
(mysql-0.mysql
), elas o encontram automaticamente mesmo que ele receba um novo
IP de Pod devido a um reagendamento.
Por fim, após iniciar a replicação, o contêiner xtrabackup
fica ouvindo conexões de outros
Pods que solicitam a clonagem de dados.
Esse servidor permanece ativo indefinidamente caso o StatefulSet seja escalonado para mais réplicas,
ou caso o próximo Pod perca seu PersistentVolumeClaim e precise refazer a clonagem.
Enviando tráfego de cliente
Você pode enviar consultas de teste para o servidor MySQL primário (hostname mysql-0.mysql
)
executando um contêiner temporário com a imagem mysql:5.7
e utilizando o cliente mysql
.
kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF
Use o hostname mysql-read
para enviar consultas de teste para qualquer servidor que esteja com o status Ready:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-read -e "SELECT * FROM test.messages"
Você deverá obter uma saída semelhante a esta:
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
Para demonstrar que o Service mysql-read
distribui as conexões entre os servidores, você pode executar SELECT @@server_id
em um loop:
kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
Você deverá ver o valor de @@server_id
mudar aleatoriamente, pois um endpoint diferente pode ser selecionado a cada tentativa de conexão:
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 101 | 2006-01-02 15:04:07 |
+-------------+---------------------+
Você pode pressionar Ctrl+C quando quiser parar o loop, mas é útil mantê-lo rodando em outra janela para que você possa observar os efeitos dos próximos passos.
Simular falha de Pod e de Nó
Para demonstrar a maior disponibilidade ao ler do pool de réplicas em vez de um único servidor,
mantenha o loop do SELECT @@server_id
rodando enquanto você força um Pod a sair do estado Ready.
Quebrar a verificação de prontidão
A verificação de prontidão
do contêiner mysql
executa o comando mysql -h 127.0.0.1 -e 'SELECT 1'
para garantir que o servidor está ativo e apto a executar consultas.
Uma forma de forçar essa verificação de prontidão a falhar é quebrar esse comando:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
Esse comando acessa o sistema de arquivos do contêiner real do Pod mysql-2
e renomeia o comando
mysql
para que a verificação de prontidão não consiga encontrá-lo.
Após alguns segundos, o Pod deverá indicar que um de seus contêineres não está Ready,
o que você pode verificar executando:
kubectl get pod mysql-2
Procure por 1/2
na coluna READY
:
NAME READY STATUS RESTARTS AGE
mysql-2 1/2 Running 0 3m
Neste momento, você deverá ver o loop do SELECT @@server_id
continuar rodando, embora ele não mostre mais o valor 102
.
Lembre-se de que o script init-mysql
definiu o server-id
como 100 + $ordinal
, então o ID de servidor 102
corresponde ao Pod mysql-2
.
Agora, repare o Pod e ele deverá voltar a aparecer na saída do loop após alguns segundos:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
Excluir Pods
O StatefulSet também recria Pods caso eles sejam excluídos, de forma semelhante ao que um ReplicaSet faz para Pods sem estado.
kubectl delete pod mysql-2
O controlador do StatefulSet percebe que o Pod mysql-2
não existe mais e cria um novo com o mesmo nome,
vinculado ao mesmo PersistentVolumeClaim.
Você deverá ver o ID de servidor 102
desaparecer da saída do loop por um tempo e depois retornar automaticamente.
Drenar um Nó
Se o seu cluster Kubernetes possui múltiplos Nós, você pode simular uma indisponibilidade de Nó (como durante atualizações) utilizando o comando drain.
Primeiro, determine em qual Nó um dos Pods MySQL está localizado:
kubectl get pod mysql-2 -o wide
O nome do Nó deverá aparecer na última coluna:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Running 0 15m 10.244.5.27 kubernetes-node-9l2t
Em seguida, drene o Nó executando o comando abaixo, que irá isolá-lo para que nenhum novo Pod seja alocado nele e,
em seguida, irá remover quaisquer Pods existentes.
Substitua <node-name>
pelo nome do Nó que você encontrou no passo anterior.
Cuidado:
Drenar um Nó pode impactar outras cargas de trabalho e aplicações em execução no mesmo nó. Execute o passo a seguir apenas em um cluster de testes.# Veja o aviso acima sobre o impacto em outras cargas de trabalho
kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets
Agora você pode observar o Pod sendo reagendado em outro Nó:
kubectl get pod mysql-2 -o wide --watch
Deverá se parecer com isto:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Terminating 0 15m 10.244.1.56 kubernetes-node-9l2t
[...]
mysql-2 0/2 Pending 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:0/2 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:1/2 0 20s 10.244.5.32 kubernetes-node-fjlm
mysql-2 0/2 PodInitializing 0 21s 10.244.5.32 kubernetes-node-fjlm
mysql-2 1/2 Running 0 22s 10.244.5.32 kubernetes-node-fjlm
mysql-2 2/2 Running 0 30s 10.244.5.32 kubernetes-node-fjlm
E novamente, você deverá ver o ID de servidor 102
desaparecer da saída do loop do
SELECT @@server_id
por um tempo e depois retornar.
Agora, remova o isolamento do Nó para retorná-lo ao estado normal:
kubectl uncordon <node-name>
Escalonando o número de réplicas
Ao utilizar replicação MySQL, você pode aumentar a capacidade de consultas de leitura adicionando réplicas. Para um StatefulSet, isso pode ser feito com um único comando:
kubectl scale statefulset mysql --replicas=5
Acompanhe a criação dos novos Pods executando:
kubectl get pods -l app=mysql --watch
Assim que estiverem ativos, você deverá ver os IDs de servidor 103
e 104
começarem a
aparecer na saída do loop do SELECT @@server_id
.
Você também pode verificar se esses novos servidores possuem os dados que você adicionou antes de eles existirem:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
Reduzir o número de réplicas também é um processo transparente:
kubectl scale statefulset mysql --replicas=3
Nota:
Embora o escalonamento para cima crie novos PersistentVolumeClaims automaticamente, o escalonamento para baixo não exclui esses PVCs automaticamente.
Isso lhe dá a opção de manter esses PVCs inicializados para tornar o escalonamento para cima mais rápido, ou extrair os dados antes de excluí-los.
Você pode ver isso executando:
kubectl get pvc -l app=mysql
O que mostra que todos os 5 PVCs ainda existem, apesar de o StatefulSet ter sido reduzido para 3 réplicas:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
data-mysql-0 Bound pvc-8acbf5dc-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-1 Bound pvc-8ad39820-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-2 Bound pvc-8ad69a6d-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-3 Bound pvc-50043c45-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
data-mysql-4 Bound pvc-500a9957-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
Se você não pretende reutilizar os PVCs extras, pode excluí-los:
kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4
Limpando
-
Cancele o loop do
SELECT @@server_id
pressionando Ctrl+C no terminal correspondente, ou executando o seguinte comando em outro terminal:kubectl delete pod mysql-client-loop --now
-
Exclua o StatefulSet. Isso também inicia a finalização dos Pods.
kubectl delete statefulset mysql
-
Verifique se os Pods desapareceram. Eles podem levar algum tempo para serem finalizados.
kubectl get pods -l app=mysql
Você saberá que os Pods foram finalizados quando o comando acima retornar:
No resources found.
-
Exclua o ConfigMap, os Services e os PersistentVolumeClaims.
kubectl delete configmap,service,pvc -l app=mysql
-
Se você provisionou PersistentVolumes manualmente, também será necessário excluí-los manualmente, assim como liberar os recursos subjacentes. Se você utilizou um provisionador dinâmico, ele exclui automaticamente os PersistentVolumes ao detectar que você excluiu os PersistentVolumeClaims. Alguns provisionadores dinâmicos (como os de EBS e PD) também liberam os recursos subjacentes ao excluir os PersistentVolumes.
Próximos passos
- Saiba mais sobre escalonar um StatefulSet.
- Saiba mais sobre depurar um StatefulSet.
- Saiba mais sobre excluir um StatefulSet.
- Saiba mais sobre forçar a exclusão de Pods de um StatefulSet.
- Consulte o repositório de Helm Charts para outros exemplos de aplicações com estado.
3 - Acessando a API do Kubernetes a partir de um Pod
Este guia demonstra como acessar a API do Kubernetes de dentro de um Pod.
Antes de você começar
Você precisa ter um cluster do Kubernetes e a ferramenta de linha de comando kubectl deve estar configurada para se comunicar com seu cluster. É recomendado executar esse tutorial em um cluster com pelo menos dois nós que não estejam atuando como hosts de camada de gerenciamento. Se você ainda não possui um cluster, pode criar um usando o minikube ou pode usar um dos seguintes ambientes:
Acessando a API de dentro de um Pod
Ao acessar a API a partir de um Pod, localizar e autenticar-se no servidor de API são processos ligeiramente diferentes do caso de um cliente externo.
A maneira mais fácil de usar a API do Kubernetes a partir de um Pod é utilizar uma das bibliotecas clientes oficiais. Essas bibliotecas conseguem descobrir automaticamente o servidor de API e autenticar-se.
Usando Bibliotecas Clientes Oficiais
De dentro de um Pod, as formas recomendadas de se conectar à API do Kubernetes são:
-
Para clientes em Go, utilize a biblioteca cliente oficial em Go. A função
rest.InClusterConfig()
lida automaticamente com a descoberta do host da API e a autenticação. Veja um exemplo aqui. -
Para clientes em Python, utilize a biblioteca cliente oficial em Python. A função
config.load_incluster_config()
lida automaticamente com a descoberta do host da API e a autenticação. Veja um exemplo aqui. -
Há diversas outras bibliotecas disponíveis. Consulte a página de Bibliotecas Clientes.
Em todos os casos, as credenciais da conta de serviço do Pod são utilizadas para se comunicar com segurança com o servidor de API.
Acessando diretamente a API REST
Enquanto estiver em execução em um Pod, seu contêiner pode criar uma URL HTTPS para o servidor de API do Kubernetes
obtendo as variáveis de ambiente KUBERNETES_SERVICE_HOST
e KUBERNETES_SERVICE_PORT_HTTPS
.
O endereço do servidor de API dentro do cluster também é publicado em um Service chamado kubernetes
no namespace
default
, para que os Pods possam referenciar kubernetes.default.svc
como um nome DNS para o servidor de API local.
Nota:
O Kubernetes não garante que o servidor de API tenha um certificado válido para o nome de hostkubernetes.default.svc
;
no entanto, espera-se que a camada de gerenciamento apresente um certificado válido para o nome de host ou endereço
IP representado por $KUBERNETES_SERVICE_HOST
.A forma recomendada de autenticar-se no servidor de API é com uma credencial
de conta de serviço.
Por padrão, um Pod é associado a uma conta de serviço, e uma credencial (token) para essa conta de serviço é colocada no
sistema de arquivos de cada contêiner nesse Pod, em /var/run/secrets/kubernetes.io/serviceaccount/token
.
Se disponível, um pacote de certificados é colocado no sistema de arquivos de cada contêiner em /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
,
e deve ser utilizado para verificar o certificado de serviço do servidor de API.
Por fim, o namespace padrão a ser usado para operações da API com escopo de namespace é colocado em um arquivo em
/var/run/secrets/kubernetes.io/serviceaccount/namespace
em cada contêiner.
Usando o kubectl proxy
Se você quiser consultar a API sem utilizar uma biblioteca cliente oficial, pode executar o kubectl proxy
como o comando de um novo contêiner sidecar no Pod.
Dessa forma, o kubectl proxy
irá se autenticar na API e expô-la na interface localhost
do Pod,
permitindo que outros contêineres no Pod a utilizem diretamente.
Sem usar um proxy
É possível evitar o uso do kubectl proxy passando o token de autenticação diretamente para o servidor de API. O certificado interno garante a segurança da conexão.
# Aponte para o nome de host interno do servidor de API
APISERVER=https://kubernetes.default.svc
# Caminho para o token da Conta de Serviço
SERVICEACCOUNT=/var/run/secrets/kubernetes.io/serviceaccount
# Ler o namespace deste Pod
NAMESPACE=$(cat ${SERVICEACCOUNT}/namespace)
# Ler o token de portador da Conta de Serviço
TOKEN=$(cat ${SERVICEACCOUNT}/token)
# Referenciar a autoridade certificadora (CA) interna
CACERT=${SERVICEACCOUNT}/ca.crt
# Explorar a API com o TOKEN
curl --cacert ${CACERT} --header "Authorization: Bearer ${TOKEN}" -X GET ${APISERVER}/api
A saída será semelhante a esta:
{
"kind": "APIVersions",
"versions": ["v1"],
"serverAddressByClientCIDRs": [
{
"clientCIDR": "0.0.0.0/0",
"serverAddress": "10.0.1.149:443"
}
]
}