Tutorial: Persistir dados em um aplicativo de contêiner usando volumes no VS Code

Neste tutorial, você aprenderá a persistir dados em um aplicativo de contêiner. Quando você executá-lo ou atualizá-lo, os dados ainda estarão disponíveis. Há dois tipos principais de volumes usados para persistir dados. Este tutorial se concentra em volumes nomeados.

Você também aprenderá sobre montagens de associação, que controlam o ponto de montagem exato no host. Você pode usar montagens de associação para persistir dados, mas também pode adicionar mais dados a contêineres. Ao trabalhar em um aplicativo, você pode usar uma montagem de associação para montar o código-fonte no contêiner para permitir que ele veja as alterações de código, responda e deixe que você veja as alterações imediatamente.

Este tutorial também apresenta camadas de imagem, cache de camadas e builds de vários estágios.

Neste tutorial, você aprenderá como:

  • Entenda os dados entre contêineres.
  • Persista os dados usando volumes nomeados.
  • Use montagens de associação.
  • Exibir camada de imagem.
  • Dependências de cache.
  • Entenda os builds de vários estágios.

Pré-requisitos

Este tutorial continua o tutorial anterior, Criar e compartilhar um aplicativo do Docker com Visual Studio Code. Comece com essa, que inclui pré-requisitos.

Entender os dados entre contêineres

Nesta seção, você iniciará dois contêineres e criará um arquivo em cada um. Os arquivos criados em um contêiner não estão disponíveis em outro.

  1. Inicie um ubuntu contêiner usando este comando:

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    

    Esse comando começa a invocar dois comandos usando &&. A primeira parte escolhe um único número aleatório e grava-o /data.txtem . O segundo comando está observando um arquivo para manter o contêiner em execução.

  2. No VS Code, na área do Docker , clique com o botão direito do mouse no contêiner do ubuntu e selecione Anexar Shell.

    A captura de tela mostra a extensão do Docker com um contêiner selecionado e um menu de contexto com o Shell de Anexação selecionado.

    Um terminal é aberto que está executando um shell no contêiner do Ubuntu.

  3. Execute o comando a seguir para ver o conteúdo do /data.txt arquivo.

    cat /data.txt
    

    O terminal mostra um número entre 1 e 10000.

    Para usar a linha de comando para ver esse resultado, obtenha a ID do contêiner usando o docker ps comando e execute o comando a seguir.

    docker exec <container-id> cat /data.txt
    
  4. Inicie outro ubuntu contêiner.

    docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
    
  5. Use este comando para examinar o conteúdo da pasta.

    docker run -it ubuntu ls /
    

    Não deve haver nenhum data.txt arquivo lá porque ele foi gravado no espaço zero apenas para o primeiro contêiner.

  6. Selecione esses dois contêineres Ubuntu. Clique com o botão direito do mouse e selecione Remover. Na linha de comando, você pode removê-los usando o docker rm -f comando.

Persista seus dados de tarefas usando volumes nomeados

Por padrão, o aplicativo todo armazena seus dados em um Banco de Dados SQLite em /etc/todos/todo.db. O Banco de Dados SQLite é um banco de dados relacional que armazena dados de um único arquivo. Essa abordagem funciona para projetos pequenos.

Você pode persistir o arquivo único no host. Quando você disponibilizá-lo para o próximo contêiner, o aplicativo pode continuar de onde parou. Criando um volume e anexando ou montando-o à pasta na qual os dados estão armazenados, você pode persistir os dados. O contêiner grava no arquivo todo.db e esses dados persistem no host no volume.

Para esta seção, use um volume nomeado. O Docker mantém a localização física do volume no disco. Consulte o nome do volume e o Docker fornece os dados corretos.

  1. Crie um volume usando o docker volume create comando.

    docker volume create todo-db
    
  2. Em CONTÊINERES, selecione iniciar e clique com o botão direito do mouse. Selecione Parar para interromper o contêiner do aplicativo.

    Para interromper o contêiner da linha de comando, use o docker stop comando.

  3. Inicie o contêiner de introdução usando o comando a seguir.

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    O parâmetro de volume especifica o volume a ser montado e o local. /etc/todos

  4. Atualize seu navegador para recarregar o aplicativo. Se você fechou a janela do navegador, vá para http://localhost:3000/. Adicione alguns itens à sua lista de tarefas pendentes.

    Captura de tela que mostra o aplicativo de exemplo com vários itens adicionados à lista.

  5. Remova o contêiner de introdução para o aplicativo todo. Clique com o botão direito do mouse no contêiner na área do Docker e selecione Remover ou usar os docker stop comandos e docker rm .

  6. Inicie um novo contêiner usando o mesmo comando:

    docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
    

    Esse comando monta a mesma unidade de antes. Atualize seu navegador. Os itens adicionados ainda estão em sua lista.

  7. Remova o contêiner de introdução novamente.

Volumes nomeados e montagens de associação, discutidos abaixo, são os principais tipos de volumes compatíveis com uma instalação padrão do mecanismo do Docker.

Propriedade Volumes nomeados Montagens por associação
Local do host O Docker escolhe Você controla
Exemplo de montagem (usando -v) my-volume:/usr/local/data /path/to/data:/usr/local/data
Popula o novo volume com conteúdo de contêiner Sim Não
Dá suporte a drivers de volume Sim Não

Há muitos plug-ins de driver de volume disponíveis para dar suporte a NFS, SFTP, NetApp e muito mais. Esses plug-ins são especialmente importantes para executar contêineres em vários hosts em um ambiente clusterizado, como Swarm ou Kubernetes.

Se você se perguntar onde o Docker realmente armazena seus dados, execute o comando a seguir.

docker volume inspect todo-db

Examine a saída, semelhante a este resultado.

[
    {
        "CreatedAt": "2019-09-26T02:18:36Z",
        "Driver": "local",
        "Labels": {},
        "Mountpoint": "/var/lib/docker/volumes/todo-db/_data",
        "Name": "todo-db",
        "Options": {},
        "Scope": "local"
    }
]

O Mountpoint local real em que os dados são armazenados. Na maioria dos computadores, você precisa de acesso raiz para acessar esse diretório do host.

Usar montagens de associação

Com montagens de associação, você controla o ponto de montagem exato no host. Essa abordagem persiste os dados, mas geralmente é usada para fornecer mais dados em contêineres. Você pode usar uma montagem de associação para montar o código-fonte no contêiner para permitir que ele veja as alterações de código, responda e deixe que você veja as alterações imediatamente.

Para executar o contêiner para dar suporte a um fluxo de trabalho de desenvolvimento, você executará as seguintes etapas:

  1. Remova todos os getting-started contêineres.

  2. Na pasta, execute o comando a app seguir.

    docker run -dp 3000:3000 -w /app -v ${PWD}:/app node:12-alpine sh -c "yarn install && yarn run dev"
    

    Esse comando contém os parâmetros a seguir.

    • -dp 3000:3000 O mesmo que antes. Execute no modo desanexado e crie um mapeamento de porta.
    • -w /app Diretório de trabalho dentro do contêiner.
    • -v ${PWD}:/app" Associe a montagem do diretório atual do host no contêiner ao /app diretório.
    • node:12-alpine A imagem a ser usada. Essa imagem é a imagem base do aplicativo do Dockerfile.
    • sh -c "yarn install && yarn run dev" Um comando. Ele inicia um shell usando sh e é executado yarn install para instalar todas as dependências. Em seguida, ele é executado yarn run dev. Se você olhar no package.json, o dev script está começando nodemon.
  3. Você pode observar os logs usando docker logs.

    docker logs -f <container-id>
    
    $ nodemon src/index.js
    [nodemon] 1.19.2
    [nodemon] to restart at any time, enter `rs`
    [nodemon] watching dir(s): *.*
    [nodemon] starting `node src/index.js`
    Using sqlite database at /etc/todos/todo.db
    Listening on port 3000
    

    Quando você vê a entrada final nesta lista, o aplicativo está em execução.

    Quando terminar de observar os logs, selecione qualquer chave na janela do terminal ou selecione Ctrl+C em uma janela externa.

  4. No VS Code, abra src/static/js/app.js. Altere o texto do botão Adicionar Item na linha 109.

    - {submitting ? 'Adding...' : 'Add Item'}
    + {submitting ? 'Adding...' : 'Add'}
    

    Salve sua alteração.

  5. Atualize seu navegador. Você deve ver a alteração.

    Captura de tela que mostra o aplicativo de exemplo com o novo texto no botão.

Exibir camadas de imagem

Você pode examinar as camadas que compõem uma imagem. Execute o docker image history comando para ver o comando que foi usado para criar cada camada dentro de uma imagem.

  1. Use docker image history para ver as camadas na imagem de introdução que você criou anteriormente no tutorial.

    docker image history getting-started
    

    Seu resultado deve ser semelhante a essa saída.

    IMAGE               CREATED             CREATED BY                                      SIZE                COMMENT
    a78a40cbf866        18 seconds ago      /bin/sh -c #(nop)  CMD ["node" "/app/src/ind…   0B                  
    f1d1808565d6        19 seconds ago      /bin/sh -c yarn install --production            85.4MB              
    a2c054d14948        36 seconds ago      /bin/sh -c #(nop) COPY dir:5dc710ad87c789593…   198kB               
    9577ae713121        37 seconds ago      /bin/sh -c #(nop) WORKDIR /app                  0B                  
    b95baba1cfdb        13 days ago         /bin/sh -c #(nop)  CMD ["node"]                 0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) COPY file:238737301d473041…   116B                
    <missing>           13 days ago         /bin/sh -c apk add --no-cache --virtual .bui…   5.35MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV YARN_VERSION=1.21.1      0B                  
    <missing>           13 days ago         /bin/sh -c addgroup -g 1000 node     && addu…   74.3MB              
    <missing>           13 days ago         /bin/sh -c #(nop)  ENV NODE_VERSION=12.14.1     0B                  
    <missing>           13 days ago         /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B                  
    <missing>           13 days ago         /bin/sh -c #(nop) ADD file:e69d441d729412d24…   5.59MB   
    

    Cada uma das linhas representa uma camada na imagem. A saída mostra a base na parte inferior com a camada mais recente na parte superior. Usando essas informações, você pode ver o tamanho de cada camada, ajudando a diagnosticar imagens grandes.

  2. Várias das linhas são truncadas. Se você adicionar o --no-trunc parâmetro, obterá a saída completa.

    docker image history --no-trunc getting-started
    

Dependências de cache

Depois que uma camada for alterada, todas as camadas downstream também precisarão ser recriadas. Aqui está o Dockerfile novamente:

FROM node:12-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "/app/src/index.js"]

Cada comando no Dockerfile se torna uma nova camada na imagem. Para minimizar o número de camadas, você pode reestruturar seu Dockerfile para dar suporte ao cache de dependências. Para aplicativos baseados em nó, essas dependências são definidas no package.json arquivo.

A abordagem é copiar somente esse arquivo primeiro, instalar as dependências e copiar todo o resto. O processo só recriará as dependências do yarn se houver uma alteração no package.json.

  1. Atualize o Dockerfile para copiar no package.json primeiro, instalar dependências e copiar todo o resto. Este é o novo arquivo:

    FROM node:12-alpine
    WORKDIR /app
    COPY package.json yarn.lock ./
    RUN yarn install --production
    COPY . .
    CMD ["node", "/app/src/index.js"]
    
  2. Criar uma nova imagem usando docker build.

    docker build -t getting-started .
    

    Você deverá ver a saída como os seguintes resultados:

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Running in d53a06c9e4c2
    yarn install v1.17.3
    [1/4] Resolving packages...
    [2/4] Fetching packages...
    info fsevents@1.2.9: The platform "linux" is incompatible with this module.
    info "fsevents@1.2.9" is an optional dependency and failed compatibility check. Excluding it from installation.
    [3/4] Linking dependencies...
    [4/4] Building fresh packages...
    Done in 10.89s.
    Removing intermediate container d53a06c9e4c2
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> a239a11f68d8
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 49999f68df8f
    Removing intermediate container 49999f68df8f
    ---> e709c03bc597
    Successfully built e709c03bc597
    Successfully tagged getting-started:latest
    

    Todas as camadas foram reconstruídas. Esse resultado é esperado porque você alterou o Dockerfile.

  3. Faça uma alteração no src/static/index.html. Por exemplo, altere o título para dizer "The Awesome Todo App".

  4. Crie a imagem do Docker agora usando docker build novamente. Desta vez, sua saída deve parecer um pouco diferente.

    Sending build context to Docker daemon  219.1kB
    Step 1/6 : FROM node:12-alpine
    ---> b0dc3a5e5e9e
    Step 2/6 : WORKDIR /app
    ---> Using cache
    ---> 9577ae713121
    Step 3/6 : COPY package* yarn.lock ./
    ---> Using cache
    ---> bd5306f49fc8
    Step 4/6 : RUN yarn install --production
    ---> Using cache
    ---> 4e68fbc2d704
    Step 5/6 : COPY . .
    ---> cccde25a3d9a
    Step 6/6 : CMD ["node", "/app/src/index.js"]
    ---> Running in 2be75662c150
    Removing intermediate container 2be75662c150
    ---> 458e5c6f080c
    Successfully built 458e5c6f080c
    Successfully tagged getting-started:latest
    

    Como você está usando o cache de build, ele deve ir muito mais rápido.

Builds de vários estágios

Os builds de vários estágios são uma ferramenta incrivelmente poderosa para ajudar a usar vários estágios para criar uma imagem. Há várias vantagens para eles:

  • Separar dependências de tempo de build de dependências de runtime
  • Reduzir o tamanho geral da imagem enviando apenas o que seu aplicativo precisa executar

Esta seção fornece exemplos breves.

Exemplo de Maven/Tomcat

Quando você cria aplicativos baseados em Java, um JDK é necessário para compilar o código-fonte no código-fonte para o código-base java. Esse JDK não é necessário na produção. Você pode estar usando ferramentas como Maven ou Gradle para ajudar a criar o aplicativo. Essas ferramentas também não são necessárias na imagem final.

FROM maven AS build
WORKDIR /app
COPY . .
RUN mvn package

FROM tomcat
COPY --from=build /app/target/file.war /usr/local/tomcat/webapps 

Este exemplo usa um estágio buildpara executar a compilação Java real usando o Maven. O segundo estágio, começando em "FROM tomcat", copia em arquivos do build estágio. A imagem final é apenas o último estágio que está sendo criado, que pode ser substituído usando o --target parâmetro.

React exemplo

Ao criar aplicativos React, você precisa de um ambiente de Nó para compilar o código JavaScript, folhas de estilo SASS e muito mais em HTML estático, JavaScript e CSS. Se você não estiver fazendo a renderização do lado do servidor, nem precisa de um ambiente de nó para o build de produção.

FROM node:12 AS build
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
RUN yarn run build

FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html

Este exemplo usa uma node:12 imagem para executar o build, que maximiza o cache de camada e copia a saída em um contêiner nginx .

Limpar recursos

Mantenha tudo o que você fez até agora para continuar esta série de tutoriais.

Próximas etapas

Você aprendeu sobre as opções para persistir dados para aplicativos de contêiner.

Em seguida, tente o próximo tutorial nesta série: