Persistent Volume
Na sessão anterior configuramos o volume dentro do spec do pod. Toda configuração para acesso ao volume está dentro do pod.
Quando temos um ambiente com vários deployments, precisaríamos configurar os parâmetros do volume em todas as definições.
Para evitar isso podemos gerenciar o volume de forma mais centralizada onde o administrador pudesse criar um pool de volumes ao nível do cluster que pudesse ser utilizado pelos usuários para deploy de aplicações no cluster.

Cada aplicação cria um Persistent Volume Claim que é uma requisição de volume. Vamos entender mais pra frente.
Para criar um Persistent Volume.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /tmp/data
EOF
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 10s
Este PV criado não tem um Storage Class por ter sido provisionado manualmente. Fica com isso na cabeça.
AccessModes define como o volume deve ser montado EM TODOS OS HOSTS. No exemplo acima colocamos em /tmp que é uma pasta que tem permissão para isso.
Poderia ser:
- ReadOnlyMany
- ReadWriteOnce
- ReadWriteMany
Novamente vamos testar essa solução do hostPath o que acontece.
Esse seria um caso mais produtivo.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol1
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
awsElasticBlockStore:
volumeID: <volume-id>
fsType: ext4
Vamos acertar os conceitos um pouco mais
PersistentVolume (PV):
- Um PersistentVolume é um recurso de armazenamento provisionado no cluster Kubernetes.
- Existe independentemente dos Pods que o utilizam.
- São provisionados manualmente pelo administrador do cluster ou de forma dinâmica por meio de um provisionador de armazenamento (Storage Class).
- Representam o armazenamento físico real disponível no cluster, que pode ser discos, volumes de rede, ou qualquer outro tipo de armazenamento suportado.
- Os PVs têm uma especificação que define características como capacidade, modo de acesso (ReadWriteOnce, ReadWriteMany, ReadOnlyMany), entre outros.
PersistentVolumeClaim (PVC):
- É uma solicitação feita por um pod para um armazenamento persistente.
- Um PVC solicita um determinado tipo de armazenamento (por exemplo, uma determinada capacidade e modo de acesso) e o Kubernetes encontra um PV adequado que satisfaça essa solicitação.
- São usados pelos desenvolvedores para solicitar armazenamento sem precisar se preocupar com os detalhes específicos de como o armazenamento é provisionado e gerenciado no cluster.
- Quando um PVC é criado e não há PVs correspondentes disponíveis, o Kubernetes pode provisionar um novo PV automaticamente com base nas especificações do PVC. Isso é conhecido como provisionamento dinâmico.
- Quando um PVC é criado e há um PV adequado disponível, o Kubernetes vincula automaticamente o PVC ao PV correspondente.
Um PVC só existe vinculado a um PV e um PV só pode ter um PVC quando vinculado.
O PVC solicita o acesso a um PV, mas qual? Baseando em matchs e parâmetros. Assim como podemos selecionar um node que atenda determinados requisitos para um pod, o PVC solicita um PV que atenda os seus requisitos.
Poderíamos ter requisitos:
- Sufficient Capacity
- Access Modes
- Volume Modes
- Storage Class
- Selector
Nos metadados de um volume podemos ter as labels para usar um matchLabels. Geralmente é bom usar assim pois mais de um PV pode atender os requisitos do PVC.
Todos os critérios precisam ser satisfeitos, mas um PVC que precisa somente de um espaço suficiente de 1Gi pode ser vinculado a um PV de 100Gi se não tiver uma outra opção disponível. Se só tem tu vai tu mesmo.
Se nenhum PV satisfazer os critérios o PVC ficará em Pending até que um PV satisfaça os critérios.
Vamos fazer um PVC para que o PV anterior satisfaça.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
EOF
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending
kubectl describe PVC myclaim
Name: myclaim
Namespace: default
StorageClass: standard # <<< Veja isso
Status: Pending
Volume:
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/PVC-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal WaitForFirstConsumer 12s (x2 over 24s) persistentvolume-controller waiting for first consumer to be created before binding
Nós não definimos um Storage Class mas ele tem um. Quando não é definido o default será usado.
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 7d11h
kubectl describe sc standard
Name: standard
IsDefaultClass: Yes
Annotations: kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"},"name":"standard"},"provisioner":"rancher.io/local-path","reclaimPolicy":"Delete","volumeBindingMode":"WaitForFirstConsumer"}
,storageclass.kubernetes.io/is-default-class=true
Provisioner: rancher.io/local-path
Parameters: <none>
AllowVolumeExpansion: <unset>
MountOptions: <none>
ReclaimPolicy: Delete
VolumeBindingMode: WaitForFirstConsumer
Events: <none>
Se observou bem o PVC não fez o bind com o PV.
Quando um pod usar esse PVC o bind será feito. Isso é uma configuração do Storage Class (o provisionador) do meu cluster. Como não é possível editar um Storage Class por ser imutável eu precisaria criar um Storage Class para isso para ver esse bind acontecer sem precisar de um pod.
Vamos criar um outro Storage Class então.
cat <<EOF | kubectl apply -f -
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: Immediate
EOF
# Observe que esse Storage Class não é o default do sistema, logo para ser usado precisa ser apontado.
kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
fast rancher.io/local-path Delete Immediate false 5s
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 6d18h
# Outro PV apontando o Storage Class
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-vol2
spec:
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /tmp/data
storageClassName: fast
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 3h13m
pv-vol2 1Gi RWO Retain Available fast <unset> 8s
# Outro PVC mas forçando que esteja no Storage Class fast
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast
EOF
# Vendo o bind
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 56m
myclaim2 Bound pv-vol2 1Gi RWO fast <unset> 6s
kubectl get pv |
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 3h19m
pv-vol2 1Gi RWO Retain Bound default/myclaim2 fast <unset> 5m59s
21h
Vamos criar um PVC para que procuraria o Storage Class fast, mas não teria um PV para se associar.
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim3
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast
EOF
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 63m
myclaim2 Bound pv-vol2 1Gi RWO fast <unset> 6m22s
myclaim3 Pending fast <unset> 2m26s
# Vamos deletar o myclaim2 para liberar e ver o que acontece
kubectl delete PVC myclaim2
persistentvolumeclaim "myclaim2" deleted
# Mesmo assim ele ainda não encontrou disponível
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 6h36m
myclaim3 Pending fast <unset> 5h35m
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
pv-vol1 1Gi RWO Retain Available <unset> 8h
pv-vol2 1Gi RWO Retain Released default/myclaim2 fast <unset> 5h43m
O que acontece quando deletamos um PVC. Por padrão, mesmo perdendo o vínculo o PV não é deletado. Acontece que um PV possui uma política de retenção de dados e o padrão é Retain.
persistentVolumeReclaimPolicy: Retain é o padrão
Nós não definimos isso, logo ele manteve o Retain.
- Retain: Os dados permanecem e nenhum outro PVC pode se associar, NEM O MESMO?
- Recycle: o PV volta a ser disponível quando o PVC for destruído e os dados serão apagados quando outro PVC solicitar o acesso.
- Delete: o PV irá ser destruído quando o PVC for destruído.
Se eu criar o mesmo PVC anterior ele irá ser associado ao PV?
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim2
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
storageClassName: fast
EOF
persistentvolumeclaim/myclaim2 created
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Pending standard <unset> 6h45m
myclaim2 Pending fast <unset> 6s
myclaim3 Pending fast <unset> 5h44m
# Por que ele ainda não foi?
kubectl describe PVC myclaim2
Name: myclaim2
Namespace: default
StorageClass: fast
Status: Pending
Volume:
Labels: <none>
Annotations: volume.beta.kubernetes.io/storage-provisioner: rancher.io/local-path
volume.kubernetes.io/storage-provisioner: rancher.io/local-path
Finalizers: [kubernetes.io/PVC-protection]
Capacity:
Access Modes:
VolumeMode: Filesystem
Used By: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Provisioning 29s (x2 over 44s) rancher.io/local-path_local-path-provisioner-7577fdbbfb-5dd24_6c93344f-2afb-45ff-b736-d7a5071e082a External provisioner is provisioning volume for claim "default/myclaim2"
Warning ProvisioningFailed 29s (x2 over 44s) rancher.io/local-path_local-path-provisioner-7577fdbbfb-5dd24_6c93344f-2afb-45ff-b736-d7a5071e082a failed to provision volume with StorageClass "fast": configuration error, no node was specified
Normal ExternalProvisioning 7s (x4 over 44s) persistentvolume-controller Waiting for a volume to be created either by the external provisioner 'rancher.io/local-path' or manually by the system administrator. If volume creation is delayed, please verify that the provisioner is running and correctly registered.
#
Não, nem criando um PVC idêntico conseguiremos esse PV.
TODO: Vamos colocar uma tentativa de recuperação mais pra frente e ver os caminhos possíveis para fazer um pod acessar esses dados novamente.
Vamos deployar agora um POD que usa um PVC e que dinamicamente irá criar o PV. Como vi anteriormente, criamos um PVC e um PV não foi criado e abaixo vamos usar um código muito parecido e vai dar certo. O motivo disso é que quando usamos dentro do pod um PVC é neste momento que o provisionamento dinâmico acontece, no momento que ele diz junta o PVC no pod.
cat << EOF | kubectl apply -f -
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: myclaim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 500Mi
---
apiVersion: v1
kind: Pod
metadata:
name: mypod
spec:
containers:
- name: myfrontend
image: nginx
volumeMounts:
- mountPath: "/var/www/html"
name: mypd
volumes:
- name: mypd
persistentVolumeClaim:
claimName: myclaim
EOF
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Bound PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO standard <unset> 4s
kubectl get pod
NAME READY STATUS RESTARTS AGE
mypod 1/1 Running 0 12s
# Podemos ver que o PV foi criado dinamicamente com a Reclaim Policy Delete, ou seja, também será deletado quando o PVC for deletado.
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO Delete Bound default/myclaim standard <unset> 19s
28h
kubectl delete pod mypod
pod "mypod" deleted
kubectl get PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
myclaim Bound PVC-903f9ac6-ded9-4d1e-b5d3-2c277ce41f87 500Mi RWO standard <unset> 47s
kubectl delete PVC myclaim
persistentvolumeclaim "myclaim" deleted
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
➜ files git:(main) ✗
TODO: Preciso mastigar um pouco mais
Já vimos que é possível reservar um PV para um PVC específico, mas preciso ter certeza entre o READ WRITE ONCE e MANY e se o que eu fiz localmente teria algum problema.