This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Executar Aplicações

Execute e gerencie aplicações com estado e sem estado.

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
  1. Crie um Deployment com base no arquivo YAML:

    kubectl apply -f https://k8s.io/examples/application/deployment.yaml
    
  2. 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.
    
  3. 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
    
  4. 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
  1. Aplique o novo arquivo YAML:

    kubectl apply -f https://k8s.io/examples/application/deployment-update.yaml
    
  2. 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
  1. Aplique o novo arquivo YAML:

    kubectl apply -f https://k8s.io/examples/application/deployment-scale.yaml
    
  2. 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

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.

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.

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.

# 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

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

  1. 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
    
  2. Exclua o StatefulSet. Isso também inicia a finalização dos Pods.

    kubectl delete statefulset mysql
    
  3. 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.
    
  4. Exclua o ConfigMap, os Services e os PersistentVolumeClaims.

    kubectl delete configmap,service,pvc -l app=mysql
    
  5. 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

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:

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.

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"
    }
  ]
}