StatefulSets são objetos do Kubernetes usados ​​para implementar consistentemente componentes de aplicativos com estado. Os pods criados como parte de um StatefulSet recebem identificadores persistentes que retêm mesmo quando reprogramados.

Um StatefulSet pode implementar aplicativos que precisam identificar réplicas específicas de forma confiável, implantar atualizações em uma ordem predefinida ou acessar volumes de armazenamento de forma estável. Eles são aplicáveis ​​a muitos casos de uso diferentes, mas são mais comumente usados ​​para bancos de dados e outros tipos de armazenamento de dados persistentes.

Neste artigo, você aprenderá o que são StatefulSets, como eles funcionam e quando você deve usá-los. Também abordaremos suas limitações e situações em que outros objetos do Kubernetes são uma opção melhor.

O que são conjuntos com estado?

Tornar os pods parte de um StatefulSet instrui o Kubernetes a agendá-los e escalá-los com segurança. Cada Pod recebe uma identidade exclusiva que é preservada por qualquer Pod substituto.

O nome do pod é sufixado com um índice ordinal que define sua ordem durante as operações de programação. Um StatefulSet chamado mysql contendo três réplicas criará os seguintes pods nomeados:

Os pods usam seus nomes como seus nomes de host para que outros serviços que precisam acessar de forma confiável a segunda réplica StatefulSet possam se conectar. mysql-2. Mesmo que o Pod específico em execução mysql-2 for reprogramado posteriormente, sua identidade passará para o seu substituto.

StatefulSets também exigem que os pods sejam descartados na ordem inversa de sua criação. Se o StatefulSet for reduzido a uma réplica, mysql-3 é garantido para sair primeiro, seguido por mysql-2. Este comportamento não se aplica quando o StatefulSet inteiro é excluído e pode ser desabilitado definindo um StatefulSet podManagementPolicy campo para Parallel.

Casos de uso do StatefulSet

StatefulSets normalmente são usados ​​para executar aplicativos replicados em que pods individuais têm funções diferentes. Como exemplo, você pode estar implantando um banco de dados MySQL com uma instância primária e duas réplicas somente leitura. Um conjunto de réplicas regular ou implantação não seria apropriado porque não seria capaz de identificar de forma confiável o pod que executa a réplica primária.

StatefulSets resolvem isso garantindo que cada Pod no ReplicaSet mantenha sua identidade. Seus outros serviços podem se conectar de forma confiável a mysql-1 para interagir com a réplica principal. Os ReplicaSets também impõem que os novos pods sejam iniciados apenas quando o antigo estiver em execução. Isso garante que as réplicas somente leitura sejam criadas depois que o primário estiver ativo e pronto para expor seus dados.

O objetivo dos StatefulSets é acomodar réplicas não intercambiáveis ​​no Kubernetes. Embora os pods em um aplicativo sem estado sejam equivalentes entre si, as cargas de trabalho com estado exigem uma abordagem intencional para implantação, dimensionamento e encerramento.

Os StatefulSets integram-se a volumes persistentes locais para dar suporte ao armazenamento persistente anexado a cada réplica. Cada pod obtém acesso ao seu próprio volume que ficará online automaticamente quando a réplica for reprogramada para outro nó.

Criando um StatefulSet

Aqui está um exemplo de manifesto YAML que define um conjunto com estado para executar o MySQL com um nó primário e duas réplicas:

apiVersion: v1
kind: Service
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  ports:
    - name: mysql
      port: 3306
  clusterIP: None
  selector:
    app: mysql
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  template:
    metadata:
      labels:
        app: mysql
    spec:
      initContainers:
      - name: mysql-init
        image: mysql:8.0
        command:
        - bash
        - "-c"
        - |
          set -ex
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf/server-id.cnf
          # MySQL doesn't allow "0" as a `server-id` so we have to add 1 to the Pod's index
          echo server-id=$((1 + $ordinal)) >> /mnt/conf/server-id.cnf
          if [[ $ordinal -eq 0 ]]; then
            printf "[mysqld]nlog-bin" > /mnt/conf/primary.cnf
          else
            printf "[mysqld]nsuper-read-only" /mnt/conf/replica.cnf
          fi          
        volumeMounts:
        - name: config
          mountPath: /mnt/conf
      containers:
      - name: mysql
        image: mysql:8.0
        env:
        - name: MYSQL_ALLOW_EMPTY_PASSWORD
          value: "1"
        ports:
        - name: mysql
          containerPort: 3306
        volumeMounts:
        - name: config
          mountPath: /etc/mysql/conf.d
        - name: data
          mountPath: /var/lib/mysql
          subPath: mysql
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 5
          timeoutSeconds: 5
        readinessProbe:
          exec:
            command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
          initialDelaySeconds: 5
          periodSeconds: 5
          timeoutSeconds: 1
      volumes:
      - name: config
        emptyDir: {}
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

Este é um manifesto bastante longo, então vamos detalhar o que acontece.

  • Um serviço headless é criado definindo seu clusterIP uma None. Isso está vinculado ao StatefulSet e fornece as identidades de rede para seus pods.
  • Um StatefulSet é criado para conter os pods do MySQL. a replicas O campo especifica que três pods serão executados. O serviço headless é referenciado pelo serviceName campo.
  • Dentro do StatefulSet, é criado um contêiner de inicialização que preenche previamente um arquivo dentro de um diretório de configuração montado por um volume persistente. O contêiner executa um script Bash que define o índice ordinal do pod em execução. Quando o índice é 0, o Pod é o primeiro a ser criado dentro do StatefulSet, então ele se torna o nó principal do MySQL. Os outros pods são configurados como réplicas. O arquivo de configuração apropriado é gravado no volume onde será acessível ao contêiner MySQL posteriormente.
  • O contêiner MySQL é criado com o volume de configuração montado no diretório MySQL correto. Isso garante que a instância do MySQL seja configurada como primária ou réplica, dependendo de ser o primeiro pod iniciado no StatefulSet.
  • As sondagens de manutenção e prontidão são usadas para detectar quando a instância do MySQL está pronta. Isso evita que os pods sucessivos sejam iniciados no StatefulSet até que o anterior esteja em execução, garantindo que não existam réplicas do MySQL antes que o nó primário esteja ativo.

Uma implantação normal ou um conjunto de réplicas não pôde implementar esse fluxo de trabalho. Depois que seus Pods forem iniciados, você poderá dimensionar o StatefulSet para cima ou para baixo sem correr o risco de destruir o nó principal do MySQL. O Kubernetes oferece uma garantia de que o pedido do Pod estabelecido será respeitado.

# Create the MySQL StatefulSet
$ kubectl apply -f mysql-statefulset.yaml

# Scale up to 5 Pods - a MySQL primary and 4 MySQL replicas
$ kubectl scale statefulset mysql --replicas=5

atualizações contínuas

StatefulSets implementam atualizações contínuas quando sua especificação muda. O controlador StatefulSet substituirá cada Pod na ordem sequencial reversa, usando os índices ordinais atribuídos persistentemente. mysql-3 será excluído e substituído primeiro, seguido por mysql-2 S mysql-1. mysql-2 não atualizará até novo mysql-3 Transições de pod para Running doença.

O mecanismo de atualização contínua também inclui suporte para implantações em estágios. Configuração do .spec.updateStrategy.rollingUpdate.partition O campo no manifesto do StatefulSet informa ao Kubernetes para atualizar apenas os pods com um índice ordinal maior ou igual à partição especificada.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  serviceName: mysql
  replicas: 3
  updateStrategy:
    rollingUpdate:
      partition: 1
  template:
    ...
  volumeClaimTemplates:
    ...

Neste exemplo, apenas pods indexados 1 ou superior será o alvo das operações de atualização. O primeiro Pod no StatefulSet não receberá uma nova especificação até que a partição seja reduzida ou excluída.

Limitações

Os StatefulSets têm algumas limitações que você deve conhecer antes de adotá-los. Esses problemas comuns podem causar problemas quando você começa a implantar aplicativos com estado.

  • A remoção de um StatefulSet não garante que os Pods serão encerrados na ordem indicada por suas identidades.
  • A remoção de um StatefulSet ou a redução de sua contagem de réplicas não removerá nenhum volume associado. Isso protege contra perda acidental de dados.
  • O uso de atualizações contínuas pode criar uma situação em que ocorre um estado quebrado inválido. Isso acontece quando você fornece uma configuração que nunca muda para o estado Em execução ou Pronto devido a um problema com seu aplicativo. Reverter para uma boa configuração não resolverá o problema porque o Kubernetes espera indefinidamente até que o pod com defeito esteja pronto. Você precisa resolver manualmente a situação excluindo pods pendentes ou com falha.

StatefulSets também omitem um mecanismo para redimensionar os volumes anexados a cada Pod. Você deve editar manualmente cada volume persistente e sua declaração de volume persistente correspondente e, em seguida, excluir o StatefulSet e deixar seus pods órfãos. A criação de um novo StatefulSet com a especificação revisada permitirá que o Kubernetes recupere pods órfãos e redimensione volumes.

Quando não usar um StatefulSet

Você só deve usar um StatefulSet quando as réplicas individuais tiverem seu próprio estado. Um StatefulSet não é necessário quando todas as réplicas compartilham o mesmo estado, mesmo que seja persistente.

Nessas situações, você pode usar um ReplicaSet ou Deployment regular para iniciar seus pods. Todos os volumes montados serão compartilhados em todos os pods, que é o comportamento esperado para sistemas sem estado.

Um StatefulSet não agrega valor, a menos que você precise de armazenamento persistente individual ou IDs de réplica fixos. O uso incorreto de um StatefulSet pode causar confusão ao sugerir que os pods têm estado quando na verdade estão executando uma carga de trabalho sem estado.

Resumo

StatefulSets fornecem identidades persistentes para pods Kubernetes replicados. Cada pod é nomeado com um índice ordinal que é atribuído sequencialmente. Quando o Pod é reprogramado, sua substituição herda sua identidade. O StatefulSet também garante que os pods sejam encerrados na ordem inversa em que foram criados.

Os StatefulSets permitem que o Kubernetes acomode aplicativos que exigem implantações contínuas, identificadores de rede estáveis ​​e acesso confiável a armazenamento persistente. Eles são adequados para qualquer situação em que as réplicas em um conjunto de Pods tenham seu próprio estado que precise ser preservado.

Não há necessidade de usar um StatefulSet se suas réplicas forem sem estado, mesmo que estejam armazenando alguns dados persistentes. Os conjuntos e implantações de réplicas são mais adequados quando as réplicas individuais não precisam ser identificadas ou dimensionadas em uma ordem consistente.