Armazenamento no Kubernetes
Esta aula explora o armazenamento no Kubernetes, desde volumes efêmeros até PersistentVolumes e Claims, com uma visão geral de StorageClasses e boas práticas para gerenciar estado em clusters. Você aprenderá a configurar persistência de dados para aplicações containerizadas.
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
emptyDirouhostPathem 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
- Crie um pod que use um volume
emptyDirpara compartilhar dados entre dois contêineres. Descreva o que acontece com os dados se o pod for deletado. - Explique a diferença entre PersistentVolume (PV) e PersistentVolumeClaim (PVC) e como eles se relacionam.
- 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. - Descreva como você faria para persistir dados de um banco de dados MySQL em um cluster Kubernetes usando StatefulSet e volumes persistentes.
- Qual é o impacto da política de reclaim de um PersistentVolume (Retain, Delete, Recycle) e como escolher a adequada?
- 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 -rfno volume) e fica disponível novamente. É obsoleto e não recomendado.
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.
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.
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.
Retain para evitar perda acidental; para volumes temporários ou não críticos, Delete é conveniente.