Conceitos de Armazenamento
Acredito que o estudo e o entendimento perfeito de como funciona os volumes em Kubernetes deve ser feito com carinho pois o que vale no final são as informações que produzimos e não queremos perdê-las!
Quando falamos de storage no Kubernetes primeiro precisamos entender como storages funciona com containers.
Primeiro vamos entender armazenamento com o Docker que nos trará noções básicas para que fique mais fácil entender no Kubernetes.
No Docker temos dois conceitos:
- Storage Drivers
- Volume Drivers
Quando instalamos o docker em uma máquina ele cria a pasta /var/lib/docker com várias subpasta que onde ficam guardados os dados. Os dados são relacionados a imagens, containers em execução, etc.
sudo tree -L 1 /var/lib/docker
/var/lib/docker
├── buildkit
├── containers
├── engine-id
├── image
├── network
├── overlay2
├── plugins
├── runtimes
├── swarm
├── tmp
└── volumes
Quando buildamos um Dockerfile, cada instrução cria uma nova camada (layer) que vai adicionando suas diferenças. Uma vez que buildamos uma imagem estamos adicionando mais camadas a partir da imagem que vem do FROM. Cada camada adicionada irá aumentando o tamanho da nossa imagem.
Porém se buildarmos uma imagem duas vezes conseguimos aproveitar as camadas anteriores por serem iguais, somente geramos as camadas diferentes.

Uma imagem é toda ready only e quando rodamos um container baseado nesta imagem, uma nova camada é criada para termos a permissão de leitura e escrita. Somente os arquivos gerados pelo container são de fato armazenados.

Geralmente são arquivos de log, arquivos temporários gerados pelo container ou os arquivos modificados pelo usuário. Essa camada só existe enquanto o container estiver sendo executado ou parado. Quando destruímos o container esta camada também será destruída junto perdendo todos os dados.
Uma imagem pode ser usada por vários containers então não faz sentido que um container modifique os arquivos de uma imagem. Para ela ser imutável ela é somente leitura.
Se quisermos modificar um arquivo que veio da própria imagem como isso é tratado?
É possível modificar sim um arquivo, mas o docker cria uma cópia deste arquivo para a camada read write e então modifica. Esse mecanismo é chamado copy on write (COW). Toda vez que um arquivo é solicitado, primeiro ele irá procurar na camada COW criada pelo container, caso não encontre irá procurar dentro dos arquivos da imagem. Quando é necessário abrir um arquivo da imagem para modificação, é criada uma cópia na sua camada COW.

Sabendo que os dados do container morrerão junto com ele como persistir os dados?
No docker podemos montar um diretório do host dentro do container e gravar dados nesse diretório. O diretório é montado em um path dentro do container.
docker run -v /caminho/do/host:/caminho/no/container
Os volumes Docker são gerenciados pelo próprio Docker e não estão diretamente ligados a um diretório específico do host. Eles são armazenados em um local específico do sistema de arquivos do Docker como mostrado anteriormente.
Os volumes Docker são mais portáteis e independentes do host. Eles podem ser facilmente movidos entre contêineres e até mesmo entre hosts. Além disso, os volumes Docker podem ter configurações específicas, como serem apenas leitura ou terem backups automatizados.
Já entendemos que é melhor montar um docker volume do que mapear pasta do host para dentro do container.
Então criando um volume docker...
docker volume create data_volume
docker volume ls
DRIVER VOLUME NAME
local 489998434eb6ccc46100905700247d441f9abb565a005592a73617f8f5090cea
local b8af063f368da3ab7deaa0b5e44d645e91d2ffaaf1fa0f3e947f7c62ff6e11c4
local cacf4290059f1ba89f765469941d6712d0e78f94d53557dda530702ec2b2904d
local data_volume
local eb98d6fd1af16b7d964a0a0251e6e2a3485878f7a8a66d2cc866464da9bec471
Antes de usar o volume vamos confirmar como funciona o COW na prática.
# Não passei o volume
docker run -it --name ubuntu ubuntu bash
echo "teste" > /root/david
# Onde como vejo isso na camada COW?
docker inspect ubuntu
[
{
# REMOVIDO PARA FACILITAR A LEITURA
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2-init/diff:/var/lib/docker/overlay2/fcf52ba43f52d039928bd1ba9aba777fbf6148cba54e6693a94e4a98f6ce7726/diff",
"MergedDir": "/var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/merged",
"UpperDir": "/var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/diff",
"WorkDir": "/var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/work"
},
"Name": "overlay2"
},
# REMOVIDO PARA FACILITAR A LEITURA
}
]
A pasta merged é onde acontece o COW.
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/merged
total 80K
drwxr-xr-x 1 root root 4,0K fev 14 22:13 .
drwx--x--- 5 root root 4,0K fev 14 22:11 ..
lrwxrwxrwx 1 root root 7 jan 25 11:03 bin -> usr/bin
drwxr-xr-x 2 root root 4,0K abr 18 2022 boot
drwxr-xr-x 1 root root 4,0K fev 14 22:06 dev
-rwxr-xr-x 1 root root 0 fev 14 22:06 .dockerenv
drwxr-xr-x 1 root root 4,0K fev 14 22:15 etc
drwxr-xr-x 2 root root 4,0K abr 18 2022 home
lrwxrwxrwx 1 root root 7 jan 25 11:03 lib -> usr/lib
lrwxrwxrwx 1 root root 9 jan 25 11:03 lib32 -> usr/lib32
lrwxrwxrwx 1 root root 9 jan 25 11:03 lib64 -> usr/lib64
lrwxrwxrwx 1 root root 10 jan 25 11:03 libx32 -> usr/libx32
drwxr-xr-x 2 root root 4,0K jan 25 11:03 media
drwxr-xr-x 2 root root 4,0K jan 25 11:03 mnt
drwxr-xr-x 2 root root 4,0K jan 25 11:03 opt
drwxr-xr-x 2 root root 4,0K abr 18 2022 proc
drwx------ 1 root root 4,0K fev 14 22:15 root
drwxr-xr-x 5 root root 4,0K jan 25 11:06 run
lrwxrwxrwx 1 root root 8 jan 25 11:03 sbin -> usr/sbin
drwxr-xr-x 2 root root 4,0K jan 25 11:03 srv
drwxr-xr-x 2 root root 4,0K abr 18 2022 sys
drwxrwxrwt 1 root root 4,0K fev 14 22:15 tmp
drwxr-xr-x 1 root root 4,0K jan 25 11:03 usr
drwxr-xr-x 1 root root 4,0K jan 25 11:06 var
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/merged/root
total 28K
drwx------ 1 root root 4,0K fev 14 22:15 .
drwxr-xr-x 1 root root 4,0K fev 14 22:13 ..
-rw------- 1 root root 17 fev 14 22:08 .bash_history
-rw-r--r-- 1 root root 3,1K out 15 2021 .bashrc
-rw-r--r-- 1 root root 6 fev 14 22:15 david # aqui esta
-rw-r--r-- 1 root root 161 jul 9 2019 .profile
-rw------- 1 root root 712 fev 14 22:15 .viminfo
Se pararmos esse container ou o container terminar, o que teremos no diretório 3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2 ? Lembrando que esse diretório só existe enquanto o container existir
# Ubuntu não esta em execução, mas não foi destruido
docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c9cd02186069 ubuntu "bash" 3 hours ago Exited (0) 2 minutes ago ubuntu
7ac5423aea7b kindest/node:v1.29.1 "/usr/local/bin/entr…" 6 days ago Up 3 days 127.0.0.1:33959->6443/tcp kind-cluster-control-plane
d719119fd49b kindest/node:v1.29.1 "/usr/local/bin/entr…" 6 days ago Up 3 days kind-cluster-worker
effc6ac77623 kindest/node:v1.29.1 "/usr/local/bin/entr…" 6 days ago Up 3 days kind-cluster-worker3
cac77bb51e52 kindest/node:v1.29.1 "/usr/local/bin/entr…" 6 days ago Up 3 days kind-cluster-worker2
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2
total 92K
drwx--x--- 4 root root 4,0K fev 15 00:37 .
drwx--x--- 580 root root 68K fev 14 22:06 ..
drwxr-xr-x 7 root root 4,0K fev 14 22:13 diff
-rw-r--r-- 1 root root 26 fev 14 22:06 link
-rw-rw-rw- 1 root root 57 fev 14 22:06 lower
drwx------ 3 root root 4,0K fev 14 22:11 work
A pasta merged não existe mais e para onde foi o arquivo /root/david?
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/diff
total 40K
drwxr-xr-x 7 root root 4,0K fev 14 22:13 .
drwx--x--- 4 root root 4,0K fev 15 00:37 ..
drwxr-xr-x 5 root root 4,0K fev 14 22:15 etc
drwx------ 2 root root 4,0K fev 14 22:15 root
drwxrwxrwt 2 root root 4,0K fev 14 22:15 tmp
drwxr-xr-x 5 root root 4,0K jan 25 11:03 usr
drwxr-xr-x 5 root root 4,0K jan 25 11:06 var
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/diff/root/
total 20K
drwx------ 2 root root 4,0K fev 14 22:15 .
drwxr-xr-x 7 root root 4,0K fev 14 22:13 ..
-rw------- 1 root root 121 fev 15 00:37 .bash_history
-rw-r--r-- 1 root root 6 fev 14 22:15 david
-rw------- 1 root root 712 fev 14 22:15 .viminfo
sudo cat /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2/diff/root/david
teste
Agora vamos matar o container.
docker container rm ubuntu
# A pasta esta lá?
sudo ls -lha /var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2
ls: cannot access '/var/lib/docker/overlay2/3e94271808092cd2f2d0cfacbbf16e13eba84f9dc5529cb85687bb0cc9f3c8c2': No such file or directory
# Não
Vimos que de fato precisamos persistir os dados quando necessário e para isso podemos usar um volume.
Se fossemos subir esse mesmo container mas agora usando o volume data_volume.
# Estamos dizendo que o volume data_volume deve ser o diretório /volume dentro do container
docker run -it --name ubuntu -v data_volume:/volume ubuntu bash
# Existe o volume?
root@e8b35562c932:/# ls -lha /volume
total 8.0K
drwxr-xr-x 2 root root 4.0K Feb 15 00:34 .
drwxr-xr-x 1 root root 4.0K Feb 15 03:50 ..
# Vamos criar o arquivo david com o conteúdo teste e sair do container
root@e8b35562c932:/# cd volume/
root@e8b35562c932:/volume# echo "teste" > david
root@e8b35562c932:/volume# ls
david
root@e8b35562c932:/volume# exit
# Matamos o container
docker container rm ubuntu
# Conferindo os dados
sudo ls -lha /var/lib/docker/volumes/data_volume/_data/
total 12K
drwxr-xr-x 2 root root 4,0K fev 15 00:51 .
drwx-----x 3 root root 4,0K fev 14 21:34 ..
-rw-r--r-- 1 root root 6 fev 15 00:51 david
sudo cat /var/lib/docker/volumes/data_volume/_data/david
teste
Uma curiosidade, se você não criar o volume ele automaticamente criará.
docker run -it --name ubuntu -v data_volume2:/volume ubuntu bash
root@178a9408f35f:/# exit
exit
docker volume ls
DRIVER VOLUME NAME
local 489998434eb6ccc46100905700247d441f9abb565a005592a73617f8f5090cea
local b8af063f368da3ab7deaa0b5e44d645e91d2ffaaf1fa0f3e947f7c62ff6e11c4
local cacf4290059f1ba89f765469941d6712d0e78f94d53557dda530702ec2b2904d
local data_volume
local data_volume2
local eb98d6fd1af16b7d964a0a0251e6e2a3485878f7a8a66d2cc866464da9bec471
# Removendo os volumes se quiser
Agora vamos fazer um outro teste 2 container acessando o mesmo volume.
# No container 1
docker run -it --name ubuntu1 -v shared_volume:/volume ubuntu bash
root@7f3599bc79de:/#
# No container 2
docker run -it --name ubuntu2 -v shared_volume:/volume ubuntu bash
root@4ad12aeb8fe5:/# cd /volume/
root@4ad12aeb8fe5:/volume# touch david
# No container 1
root@7f3599bc79de:/# cd volume/
root@7f3599bc79de:/volume# ls
david
E eles compartilham o mesmo diretório facilmente. Volume mount é quando montamos um volume docker em um diretório no container e Volume bind é quando montamos um diretório do host no container.
Storage Drivers
Quem é responsável por executar todas essas operações? Quais operações? Manter essa arquitetura de camada criando uma camada que é possível de escrever, mover os arquivo entre as camadas, etc. São os Storage drivers.
Os storage drivers mantêm toda essa arquitetura de camada e arquivos entre as camadas.
Alguns storage drivers conhecidos:
- AUFS
- ZFS
- BTRFS
- Device Mapper
- Overlay
- Overlay2
A seleção do storage driver costuma ser feita automaticamente dependendo do sistema operacional do host. Esses drivers possuem desempenho diferentes.
Overlay2: agora é o padrão em todas as distribuições Linux com suporte ativo. Requer um sistema de arquivos ext4 ou xfs. Oferece um bom equilíbrio entre desempenho e eficiência para operações de cópia na gravação. Quando é necessária uma cópia na gravação, o driver pesquisa nas camadas da imagem para encontrar o arquivo correto, começando pela camada superior. Os resultados são armazenados em cache para acelerar o processo na próxima vez. Overlay2 no nível do arquivo em oposição ao nível do bloco. Isso melhora o desempenho, maximizando a eficiência do uso da memória, mas pode resultar em camadas graváveis maiores quando muitas alterações são feitas.
AUFS e Overlay: são mais antigos. Nenhum deles é recomendado para uso em distribuições Linux modernas onde overlay2 é suportado.
BTRFS e ZFS:Esses dois drivers funcionam no nível do bloco e são ideais para operações com uso intensivo de gravação. Cada um deles requer seu respectivo sistema de arquivos de apoio. O uso desses drivers faz com que seu /var/lib/docker diretório seja armazenado em um volume btrfs ou zfs. Cada camada de imagem obtém seu próprio diretório na subvolumes pasta. O espaço é alocado para diretórios sob demanda conforme necessário, mantendo baixa a utilização do disco até que ocorram operações de cópia na gravação.
As camadas base da imagem são armazenadas como subvolumes no sistema de arquivos. Outras camadas tornam-se instantâneos, contendo apenas as diferenças que introduzem. As modificações da camada gravável são tratadas no nível do bloco, adicionando outro instantâneo com uso eficiente de espaço.
Você pode criar instantâneos de subvolumes e outros instantâneos a qualquer momento. Esses snapshots continuam compartilhando dados inalterados, minimizando o consumo geral de armazenamento.
Usar um desses drivers pode proporcionar uma experiência melhor para contêineres com uso intenso de gravação. Se você estiver gravando muitos arquivos temporários ou armazenando em cache muitas operações no disco, btrfs ou zfs pode superar o desempenho do overlay2. O que você deve usar depende do seu sistema de arquivos de apoio - geralmente zfs é preferido como uma alternativa mais moderna ao btrfs.
Device Mapper :Este já foi o driver recomendado para CentOS e RHEL, mas perdeu seu lugar nas overlay2 versões mais recentes do kernel. Este driver exigia um direct-lvm sistema de arquivos de apoio. Não deve mais ser usado - está obsoleto e será totalmente removido no futuro.
Os drivers de armazenamento do Docker são usados para gerenciar camadas de imagem e a parte gravável do sistema de arquivos de um contêiner. Embora as alterações nos sistemas de arquivos do contêiner sejam perdidas quando o contêiner para, elas ainda precisam ser persistidas enquanto o contêiner estiver em execução. É o driver de armazenamento que fornece esse mecanismo.
Cada driver possui um conjunto diferente de otimizações que o torna mais ou menos adequado para diferentes cenários. Hoje em dia overlay2 é o driver padrão e a opção recomendada para a maioria das cargas de trabalho, embora opções alternativas como btrfs, zfs e fuse-overlayfs tenham alguns recursos mais avançados e possam ser necessárias em certos casos
Ainda existem mais, mas no final ou é Overlay2 e ZFS sendo que o último somente deve ser aplicado em caso específico.
Volume Driver
Os Volumes no docker não são manipulados pelo Storage Driver mas pelo Volume Driver Plugins. Storage driver somente são responsáveis pelo controle entre as camadas.
Volumes são dedicados e apartado da arquitetura de camadas. Estão mais para um HD externo!
O volume driver plugin padrão é o Local que ajuda a criar um volume no host (/var/lib/docker/volumes) para armazenar os dados como já vimos.
Existem muito outros plugins que permitem criar um volume em soluções externas como:
- Azure File Storage
- Convoy
- Digital Ocean Block Storage
- Flocker
- gce-docker
- GlusterFS
- NetApp
- RexRay: Pode ser usado para provisionar armazenamento em AWS EBS, S3, Openstack Cinder
- Portworx
- VMware Vsphere Storage
E muitos outros...
# Exemplo de uso com RexRay EBS
docker run -it --name ubuntu --volume-driver rexray/ebs --mount src=ebs-vol,target=/volume ubuntu bash