Bem-vindo à aula sobre armazenamento no Kubernetes. Nesta aula, vamos mergulhar nos conceitos e práticas de armazenamento dentro do Kubernetes, um dos pilares para executar aplicações com estado de forma confiável. Entender como o Kubernetes lida com volumes é essencial para qualquer profissional de DevOps que deseja gerenciar dados persistentes, seja para bancos de dados, sistemas de arquivos ou qualquer workload que precise manter informações além do ciclo de vida de um pod.

Começaremos com os fundamentos dos volumes, depois avançaremos para PersistentVolumes (PV) e PersistentVolumeClaims (PVC), e finalmente veremos como as StorageClasses permitem provisionamento dinâmico. Ao final, discutiremos a importância do estado e como o Kubernetes lida com ele. Prepare-se para uma aula prática e rica em exemplos.

Volumes

No Kubernetes, um volume é um diretório acessível aos contêineres em um pod. Ele permite que os dados persistam além do ciclo de vida de um contêiner, mas não necessariamente além do ciclo de vida do pod, dependendo do tipo de volume. Os volumes são definidos na especificação do pod e montados nos contêineres em caminhos específicos.

Existem muitos tipos de volumes, desde simples diretórios no host até integrações com sistemas de armazenamento em nuvem. Os mais comuns incluem emptyDir, hostPath, configMap, secret e volumes de rede como NFS. Cada um tem seu propósito e características. Por exemplo, emptyDir é efêmero e é criado quando o pod é agendado, sendo útil para armazenamento temporário ou compartilhamento entre contêineres do mesmo pod. Já hostPath monta um diretório do nó, o que é útil para acesso a arquivos do host, mas não é recomendado para dados persistentes em produção devido à falta de portabilidade.

Vamos ver um exemplo de pod usando um volume emptyDir:

apiVersion: v1
kind: Pod
metadata:
  name: meu-pod
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: cache
      mountPath: /cache
  volumes:
  - name: cache
    emptyDir: {}

Neste exemplo, o contêiner nginx monta o volume cache em /cache. O volume é criado no nó e dura enquanto o pod existir. Se o pod for recriado, o volume é apagado. Isso é suficiente para dados temporários, mas não para dados que precisam sobreviver a falhas do pod.

PersistentVolumes e Claims

Para dados que precisam persistir além do ciclo de vida de um pod, o Kubernetes oferece PersistentVolumes (PV) e PersistentVolumeClaims (PVC). Um PV é um recurso de cluster que representa um pedaço de armazenamento provisionado por um administrador ou dinamicamente. Um PVC é uma solicitação de armazenamento feita por um usuário, especificando tamanho e modo de acesso. O Kubernetes liga um PVC a um PV que atenda aos requisitos.

Os PVs podem ser provisionados de duas formas: estaticamente, quando o administrador cria os PVs antecipadamente, ou dinamicamente, usando StorageClasses (que veremos a seguir). O ciclo de vida de um PV pode ser Available, Bound, Released ou Failed. Quando um PVC é criado, o Kubernetes procura um PV que corresponda ao tamanho e ao modo de acesso solicitado e o vincula. Depois que o PVC é usado e liberado, o PV pode ser reciclado ou retido, dependendo da política de retenção.

Vamos ver um exemplo de criação de um PV estático e um PVC:

# persistent-volume.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-exemplo
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  hostPath:
    path: /mnt/data

# persistent-volume-claim.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-exemplo
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

Para usar o PVC em um pod, basta referenciá-lo no volume:

apiVersion: v1
kind: Pod
metadata:
  name: app-com-pvc
spec:
  containers:
  - name: app
    image: nginx
    volumeMounts:
    - name: dados
      mountPath: /dados
  volumes:
  - name: dados
    persistentVolumeClaim:
      claimName: pvc-exemplo

Neste exemplo, o pod monta o PVC pvc-exemplo em /dados. Se o pod for recriado, o PVC ainda existe e os dados são preservados, desde que o PV não seja liberado. Isso é fundamental para aplicações com estado, como bancos de dados.

StorageClasses (visão geral)

StorageClasses permitem o provisionamento dinâmico de PersistentVolumes. Em vez de o administrador criar PVs manualmente, um StorageClass define um “perfil” de armazenamento que pode ser usado para criar PVs automaticamente quando um PVC é solicitado. Isso é especialmente útil em nuvens públicas, onde o Kubernetes pode criar discos sob demanda.

Uma StorageClass especifica o provisionador (por exemplo, kubernetes.io/aws-ebs, kubernetes.io/gce-pd, ou nfs), parâmetros específicos do provisionador e a política de reclaim (Retain, Delete ou Recycle). Quando um PVC referencia uma StorageClass, o Kubernetes usa o provisionador para criar o armazenamento e o PV correspondente.

Vamos ver um exemplo de StorageClass para AWS EBS:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp3
  fsType: ext4
reclaimPolicy: Delete

Com essa StorageClass, um PVC pode solicitá-la:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-dinamico
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: fast
  resources:
    requests:
      storage: 10Gi

Quando o PVC é criado, o Kubernetes automaticamente provisiona um disco EBS de 10Gi e cria um PV vinculado. Isso simplifica muito o gerenciamento de armazenamento, especialmente em ambientes dinâmicos.

Estado

O estado em Kubernetes refere-se a aplicações que precisam manter dados persistentes, como bancos de dados, sistemas de mensageria e outros serviços que armazenam informações. O gerenciamento de estado é um dos maiores desafios em orquestração de contêineres, pois os pods são efêmeros e podem ser movidos entre nós. O Kubernetes oferece várias ferramentas para lidar com isso, incluindo PersistentVolumes, StatefulSets e Operators.

StatefulSets são um controlador específico para aplicações com estado, que fornece identidades de rede estáveis, armazenamento persistente e ordem de deployment. Cada pod em um StatefulSet tem um nome estável e um volume persistente dedicado, garantindo que, se o pod for recriado, ele se conecte ao mesmo volume. Isso é essencial para bancos de dados como PostgreSQL ou Cassandra.

Além disso, é importante considerar o backup e a recuperação de dados. O Kubernetes não oferece backup nativo, mas você pode usar ferramentas como Velero ou soluções específicas de cada banco de dados. A estratégia de backup deve ser parte do design de qualquer aplicação com estado.

Vamos ver um exemplo de StatefulSet com um volume persistente:

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  serviceName: "nginx"
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "standard"
      resources:
        requests:
          storage: 1Gi

Neste exemplo, cada pod do StatefulSet tem seu próprio PVC criado a partir do template volumeClaimTemplates. Isso garante que cada pod tenha armazenamento persistente e estável.

Boas Práticas e Observações Finais

Ao trabalhar com armazenamento no Kubernetes, algumas boas práticas são essenciais:

  • Use StorageClasses sempre que possível para provisionamento dinâmico, evitando trabalho manual.
  • Prefira volumes persistentes para dados críticos, evitando emptyDir ou hostPath em produção.
  • Monitore o uso de armazenamento e defina alertas para evitar que os volumes fiquem cheios.
  • Implemente backups regulares para dados persistentes, usando ferramentas como Velero.
  • Entenda as políticas de reclaim dos PVs para evitar perda de dados acidental.

O armazenamento é uma parte crítica do Kubernetes, e dominar esses conceitos é essencial para executar aplicações com estado de forma confiável. Pratique com os exercícios abaixo para consolidar o aprendizado.

Exercícios

  1. Crie um pod que use um volume emptyDir para compartilhar dados entre dois contêineres. Descreva o que acontece com os dados se o pod for deletado.
  2. ✓ Resposta: Um pod com dois contêineres compartilhando um volume emptyDir pode ser definido assim:
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod-compartilhado
    spec:
      containers:
      - name: produtor
        image: busybox
        command: ["/bin/sh", "-c", "echo 'dado' > /dados/arquivo.txt; sleep 3600"]
        volumeMounts:
        - name: dados
          mountPath: /dados
      - name: consumidor
        image: busybox
        command: ["/bin/sh", "-c", "cat /dados/arquivo.txt; sleep 3600"]
        volumeMounts:
        - name: dados
          mountPath: /dados
      volumes:
      - name: dados
        emptyDir: {}
    
    Quando o pod é deletado, o volume emptyDir é removido do nó, e todos os dados são perdidos. O emptyDir é efêmero e não sobrevive ao ciclo de vida do pod.
  3. Explique a diferença entre PersistentVolume (PV) e PersistentVolumeClaim (PVC) e como eles se relacionam.
  4. ✓ Resposta: Um PersistentVolume (PV) é um recurso de cluster que representa um pedaço de armazenamento provisionado, enquanto um PersistentVolumeClaim (PVC) é uma solicitação de armazenamento feita por um usuário. O PVC especifica tamanho e modo de acesso, e o Kubernetes vincula o PVC a um PV que atenda aos requisitos. O PV é o recurso físico ou lógico, e o PVC é a referência que os pods usam para acessar o armazenamento.
  5. Crie uma StorageClass para um ambiente local usando o provisionador kubernetes.io/no-provisioner (para hostPath) e um PVC que a utilize. Explique o que acontece.
  6. ✓ Resposta: Exemplo de StorageClass local:
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: local
    provisioner: kubernetes.io/no-provisioner
    volumeBindingMode: WaitForFirstConsumer
    
    PVC:
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pvc-local
    spec:
      accessModes:
      - ReadWriteOnce
      storageClassName: local
      resources:
        requests:
          storage: 1Gi
    
    Como o provisionador é no-provisioner, o PVC não será provisionado automaticamente. Ele ficará pendente até que um PV seja criado manualmente com a mesma storageClassName ou até que um pod que o utilize seja agendado (com WaitForFirstConsumer). Em ambientes locais, você precisa criar um PV manualmente.
  7. Descreva como você faria para persistir dados de um banco de dados MySQL em um cluster Kubernetes usando StatefulSet e volumes persistentes.
  8. ✓ Resposta: Para persistir dados de um MySQL, você pode criar um StatefulSet com um volumeClaimTemplate que provisiona um PVC para cada réplica. Um exemplo simplificado:
    apiVersion: apps/v1
    kind: StatefulSet
    metadata:
      name: mysql
    spec:
      serviceName: mysql
      replicas: 1
      selector:
        matchLabels:
          app: mysql
      template:
        metadata:
          labels:
            app: mysql
        spec:
          containers:
          - name: mysql
            image: mysql:8.0
            env:
            - name: MYSQL_ROOT_PASSWORD
              value: "senha"
            volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
      volumeClaimTemplates:
      - metadata:
          name: data
        spec:
          accessModes: [ "ReadWriteOnce" ]
          storageClassName: "standard"
          resources:
            requests:
              storage: 10Gi
    
    Isso garante que o MySQL tenha um volume persistente que sobrevive a reinícios de pod. Lembre-se de configurar um Service para acesso estável.
  9. Qual é o impacto da política de reclaim de um PersistentVolume (Retain, Delete, Recycle) e como escolher a adequada?
  10. ✓ Resposta: A política de reclaim define o que acontece com o PV depois que o PVC é liberado.
    • Retain: O PV é retido, mas não está mais disponível para novos PVCs. Os dados permanecem, e o administrador precisa limpar manualmente.
    • Delete: O PV e o armazenamento subjacente são excluídos automaticamente. Os dados são perdidos.
    • Recycle: O PV é limpo (por exemplo, executando rm -rf no volume) e fica disponível novamente. É obsoleto e não recomendado.
    A escolha depende do caso: para dados críticos, use Retain para evitar perda acidental; para volumes temporários ou não críticos, Delete é conveniente.

Referências