Docker & Kubernetes

Dominando Persistent Volumes em Kubernetes: PV, PVC e Storage Classes em Projetos Reais

10 min de leitura

Dominando Persistent Volumes em Kubernetes: PV, PVC e Storage Classes em Projetos Reais

Entendendo Armazenamento Persistente em Kubernetes 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. 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. Persistent Volumes (PV) — O Recurso de Infraestrutura 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

<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 &quot;pedaço de disco&quot; 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: &quot;/exports/kubernetes&quot;

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 &quot;reclama&quot; 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 &quot;Bound&quot; 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: &quot;3000&quot;

throughput: &quot;125&quot;

encrypted: &quot;true&quot;

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: &quot;senha-forte&quot;

  • name: POSTGRES_USER

value: &quot;admin&quot;

  • 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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Docker & Kubernetes

Guia Completo de Probes em Kubernetes: Liveness, Readiness e Startup na Prática
Guia Completo de Probes em Kubernetes: Liveness, Readiness e Startup na Prática

Introdução aos Probes no Kubernetes Quando você deploya uma aplicação no Kube...

Boas Práticas de Imagens Docker: Layers, Union Filesystem e Como o Build Realmente Funciona para Times Ágeis
Boas Práticas de Imagens Docker: Layers, Union Filesystem e Como o Build Realmente Funciona para Times Ágeis

Introdução: O Que São Layers e Por Que Importam Docker revolucionou a forma c...

Boas Práticas de Namespaces em Kubernetes: Isolamento, ResourceQuotas e LimitRanges para Times Ágeis
Boas Práticas de Namespaces em Kubernetes: Isolamento, ResourceQuotas e LimitRanges para Times Ágeis

Namespaces em Kubernetes: Isolamento, ResourceQuotas e LimitRanges Namespaces...