<h2>Entendendo Armazenamento Persistente em Kubernetes</h2>
<p>Quando você trabalha com Kubernetes em produção, rapidamente percebe que os dados precisam sobreviver ao ciclo de vida dos pods. Diferentemente de um container tradicional, um pod no Kubernetes é efêmero — ele nasce, executa e morre. Se você armazenar dados dentro do pod, tudo será perdido. É aqui que entram os Persistent Volumes (PVs), Persistent Volume Claims (PVCs) e Storage Classes.</p>
<p>O modelo de armazenamento persistente em Kubernetes funciona como um mecanismo de desacoplamento entre a infraestrutura e a aplicação. Você não precisa saber onde os dados serão armazenados (local, cloud, SAN) — basta declarar que precisa de armazenamento com certas características, e o Kubernetes cuida do resto. Esse padrão segue o princípio de abstração que torna Kubernetes portável entre diferentes ambientes.</p>
<h2>Persistent Volumes (PV) — O Recurso de Infraestrutura</h2>
<p>Um Persistent Volume é um recurso a nível de cluster que representa uma unidade de armazenamento física ou virtual. Ele existe independentemente de qualquer pod. Um administrador é responsável por criar e gerenciar os PVs, definindo qual tecnologia de storage será usada (NFS, iSCSI, cloud storage, etc).</p>
<p>Pense no PV como um "pedaço de disco" que você disponibiliza para o cluster. Ele possui capacidade, modo de acesso e outras propriedades. Quando um pod termina, o PV continua lá, pronto para ser usado por outro pod.</p>
<p>Veja um exemplo prático de um PV usando NFS:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-nfs-001
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
nfs:
server: 192.168.1.100
path: "/exports/kubernetes"
persistentVolumeReclaimPolicy: Retain</code></pre>
<p>Aqui, criamos um PV de 10GB usando NFS. O <code>accessModes</code> define como o volume pode ser montado. As opções disponíveis são:</p>
<ul>
<li><strong>ReadWriteOnce (RWO)</strong>: pode ser montado como leitura-escrita por um único nó</li>
<li><strong>ReadOnlyMany (ROX)</strong>: pode ser montado como apenas leitura por múltiplos nós</li>
<li><strong>ReadWriteMany (RWX)</strong>: pode ser montado como leitura-escrita por múltiplos nós</li>
</ul>
<p>O <code>persistentVolumeReclaimPolicy</code> define o que acontece quando o PVC é deletado. Com <code>Retain</code>, o volume é mantido (você precisa deletá-lo manualmente). Outras opções são <code>Delete</code> (deleta automaticamente) e <code>Recycle</code> (limpa o conteúdo — obsoleto).</p>
<h3>Ciclo de Vida do PV</h3>
<p>Um PV passa por diferentes fases: <strong>Available</strong> (disponível para ser reclamado), <strong>Bound</strong> (vinculado a um PVC), <strong>Released</strong> (o PVC foi deletado, mas o PV ainda existe) e <strong>Failed</strong> (ocorreu um erro).</p>
<h2>Persistent Volume Claims (PVC) — A Solicitação da Aplicação</h2>
<p>Enquanto o PV é um recurso de infraestrutura, o PVC é uma solicitação de armazenamento feita pela aplicação ou desenvolvedor. Um PVC "reclama" um PV existente ou dispara a criação automática de um (quando usar Storage Classes, que veremos adiante).</p>
<p>O PVC é namespaced, ou seja, existe dentro de um namespace específico. A aplicação (pod) se vincula ao PVC, não diretamente ao PV. Essa separação permite que desenvolvedores solicitem armazenamento sem conhecer detalhes de infraestrutura.</p>
<p>Veja um exemplo de PVC que reclama o PV que criamos anteriormente:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: meu-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
volumeName: pv-nfs-001</code></pre>
<p>Neste exemplo, solicitamos 5GB de armazenamento em modo ReadWriteOnce e especificamos explicitamente que queremos usar o PV chamado <code>pv-nfs-001</code>. Após criar este PVC, o status muda para "Bound" e ele fica pronto para ser usado.</p>
<p>Agora, veja como um pod usa o PVC:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: Pod
metadata:
name: app-com-storage
spec:
containers:
- name: minha-aplicacao
image: nginx:latest
volumeMounts:
- name: dados
mountPath: /data
volumes:
- name: dados
persistentVolumeClaim:
claimName: meu-pvc</code></pre>
<p>O pod monta o PVC no caminho <code>/data</code>. Qualquer arquivo salvo nesse diretório será persistido no NFS. Se o pod morrer e um novo pod montar o mesmo PVC, os dados estarão lá.</p>
<h2>Storage Classes — Provisionamento Dinâmico</h2>
<p>Até agora, criamos PVs manualmente. Em ambientes reais, especialmente em cloud, você não quer fazer isso. Storage Classes permite provisionamento dinâmico: quando um PVC é criado, a Storage Class automaticamente cria um PV correspondente.</p>
<p>Uma Storage Class define o tipo de storage disponível e como os PVs devem ser criados. Cada cloud provider (AWS, Azure, GCP) possui suas próprias Storage Classes, assim como tecnologias on-premises como Ceph ou Longhorn.</p>
<p>Veja um exemplo de Storage Class usando o provisionador padrão do Kubernetes em um cluster local (usando hostPath — apenas para desenvolvimento):</p>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
allowVolumeExpansion: true</code></pre>
<p>Este é um Storage Class que usa o provisionador <code>no-provisioner</code>, adequado para volumes estáticos. Aqui está um exemplo mais realista com um provisionador dinâmico (como em um cluster AWS):</p>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ebs-gp3
provisioner: ebs.csi.aws.com
parameters:
type: gp3
iops: "3000"
throughput: "125"
encrypted: "true"
allowVolumeExpansion: true
reclaimPolicy: Delete</code></pre>
<p>Este Storage Class usa o provisionador EBS da AWS, cria volumes GP3 com 3000 IOPS, throughput de 125 MB/s e criptografia habilitada.</p>
<h3>Usando Storage Class com PVC</h3>
<p>Quando você cria um PVC referenciando uma Storage Class, o PV é criado automaticamente:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dados-app
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-gp3
resources:
requests:
storage: 20Gi</code></pre>
<p>Observe que agora usamos <code>storageClassName</code> em vez de <code>volumeName</code>. O Kubernetes procura um Storage Class chamado <code>ebs-gp3</code>, usa seu provisionador para criar um novo PV e vincula automaticamente o PVC a ele.</p>
<h3>Expansão Dinâmica de Volumes</h3>
<p>Uma característica poderosa é a <code>allowVolumeExpansion: true</code> na Storage Class. Isso permite aumentar o tamanho de um volume existente editando o PVC:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: dados-app
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-gp3
resources:
requests:
storage: 50Gi # aumentado de 20Gi</code></pre>
<p>Dependendo do tipo de storage e do sistema de arquivos, essa expansão pode acontecer sem downtime. Isso é especialmente útil em produção quando um volume fica cheio e você precisa aumentar sua capacidade rapidamente.</p>
<h2>Um Exemplo Prático Completo</h2>
<p>Para consolidar o aprendizado, vamos criar um cenário real: um banco de dados PostgreSQL persistente em Kubernetes.</p>
<p>Primeiro, a Storage Class:</p>
<pre><code class="language-yaml">apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: postgres-storage
provisioner: kubernetes.io/no-provisioner
allowVolumeExpansion: true</code></pre>
<p>O PVC para o PostgreSQL:</p>
<pre><code class="language-yaml">apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: postgres-storage
resources:
requests:
storage: 50Gi</code></pre>
<p>E um StatefulSet que usa este PVC:</p>
<pre><code class="language-yaml">apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: default
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15-alpine
env:
- name: POSTGRES_PASSWORD
value: "senha-forte"
- name: POSTGRES_USER
value: "admin"
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
ports:
- containerPort: 5432
name: postgres
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc</code></pre>
<p>Neste exemplo, usamos um StatefulSet porque o PostgreSQL precisa de identidade estável. O container monta o PVC em <code>/var/lib/postgresql/data</code>, garantindo que os dados persistam mesmo se o pod for reiniciado.</p>
<p>Para verificar o status, use:</p>
<pre><code class="language-bash">kubectl get pv
kubectl get pvc
kubectl get storageclass
kubectl describe pvc postgres-pvc</code></pre>
<h2>Conclusão</h2>
<p>Você aprendeu que <strong>armazenamento persistente em Kubernetes funciona em três camadas</strong>: infraestrutura (PV), solicitação (PVC) e provisionamento automático (Storage Class). O PV é estático e gerenciado pelo administrador, o PVC é dinâmico e gerenciado pela aplicação, e a Storage Class automatiza tudo. Na prática, em produção, você raramente criará PVs manualmente — trabalhará com Storage Classes e deixará o provisionador fazer seu trabalho.</p>
<p>Outro ponto crucial é entender os <code>accessModes</code> e escolher o tipo correto de storage para seu caso de uso. Um banco de dados precisa de ReadWriteOnce; uma aplicação que serve conteúdo estático para múltiplos nós precisa de ReadWriteMany ou ReadOnlyMany. E finalmente, lembre-se de definir uma política de reciclagem apropriada — em produção, você quase sempre usará <code>Delete</code> com Storage Classes, evitando acúmulo de volumes órfãos.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" target="_blank" rel="noopener noreferrer">Documentação Oficial: Persistent Volumes</a></li>
<li><a href="https://kubernetes.io/docs/concepts/storage/storage-classes/" target="_blank" rel="noopener noreferrer">Documentação Oficial: Storage Classes</a></li>
<li><a href="https://cloud.google.com/kubernetes-engine/docs/concepts/persistent-volumes" target="_blank" rel="noopener noreferrer">Kubernetes Best Practices: Storage</a></li>
<li><a href="https://www.thekubernetesbook.com/" target="_blank" rel="noopener noreferrer">The Kubernetes Book - Nigel Poulton</a></li>
</ul>
<p><!-- FIM --></p>