Invalidando o cache do Docker

O Docker é uma tecnologia fantástica. Através de recursos do kernel do Linux, a ferramenta permite executar o código da sua aplicação em “máquinas virtuais” (na verdade, containers) muito leves.

Uma coisa chata é que o Docker funciona nativamente apenas em distribuições Linux. Pra usar no Mac OS X ou no Windows, você precisa de uma máquina virtual VirtualBox.

Receita

Com o Docker, você pode definir uma “receita” em um arquivo chamado Dockerfile. Através de comandos pré-definidos, você fornece um passo-a-passo que é usado para criar templates de máquinas, conhecidos como imagens. As imagens são usadas para criar os containers que realmente executam aplicações. Um exemplo de Dockerfile seria:

FROM ubuntu:trusty

RUN apt-get install -y --no-install-recommends \
	curl \
	git

#instalando nodejs 0.12.4 (com npm 2.10.1 incluso)
RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" \
	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
	&& rm "node-v0.12.4-linux-x64.tar.gz" 

RUN npm install -g alexandreaquiles/marechal-random

CMD marechal-random

O comando FROM permite o uso de uma imagem pré-definida. Já o RUN permite que algo seja executado na nova imagem que está sendo criada. O CMD define o comando que deve ser executado por padrão nos containers criados a partir dessa imagem.

No Dockerfile acima:

  • criamos uma imagem baseada no Ubuntu 14.04 (trusty);
  • instalamos as ferramentas curl e git;
  • instalamos o Node JS;
  • através do npm, instalamos o módulo Node JS alexandreaquiles/marechal-random globalmente, permitindo que seja executado em linha de comando. O código é obtido a partir do repositório github.com/alexandreaquiles/marechal-random (e não do npm registry);
  • configuramos a execução do comando marechal-random por padrão, que invoca nosso módulo Node JS

Executando

O módulo Node JS marechal-random é uma ferramenta bem simples de linha de comando que, quando executada, mostra um número randômico maior que 0 e menor que 1. O código do arquivo index.js é o seguinte:

#!/usr/bin/env node
console.log(Math.random());

Por meio do comando docker build, podemos criar uma imagem chamada maquina a partir do Dockerfile:

docker build -t maquina pasta-do-Dockerfile/

Será mostrado algo como:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Running in 512f2fb0af33
Reading package lists...
Building dependency tree...
Reading state information...
The following extra packages will be installed:
  git-man libasn1-8-heimdal libcurl3 libcurl3-gnutls liberror-perl ...
Recommended packages:
  patch rsync ssh-client ca-certificates krb5-locales libsasl2-modules
The following NEW packages will be installed:
  curl git git-man libasn1-8-heimdal libcurl3 libcurl3-gnutls liberror-perl ...
0 upgraded, 25 newly installed, 0 to remove and 0 not upgraded.
Need to get 5367 kB of archives.
After this operation, 29.3 MB of additional disk space will be used.
...
 ---> 78b2ceb410cc
Removing intermediate container 512f2fb0af33
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Running in 8dc493ab0aeb
 ---> 41f993547202
Removing intermediate container 8dc493ab0aeb
Step 4 : RUN npm install -g alexandreaquiles/marechal-random
 ---> Running in e366e4914731
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> 748021adbd43
Removing intermediate container e366e4914731
Step 5 : CMD marechal-random
 ---> Running in 99d8f0df0e79
 ---> 9974d8f6a06b
Removing intermediate container 99d8f0df0e79
Successfully built 9974d8f6a06b

Então, através do docker run, podemos executar o comando padrão:

docker run --rm  maquina

É criado um container a partir da imagem maquina. A opção --rm faz com que esse container seja removido logo após o término da execução, economizando espaço em disco.

O resultado será algum número randômico:

0.6202559822704643

Se executarmos novamente, teremos outros resultado:

0.13229440338909626

Alterações

Vamos alterar esse módulo, para que seja impressa a mensagem “Random” antes do número:

#!/usr/bin/env node
console.log('Random: '+Math.random());

Depois de feito commit e push para o repositório do GitHub, vamos regerar a imagem para que o módulo marechal-random seja atualizado:

docker build -t maquina pasta-do-Dockerfile/

O resultado seria:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> 78b2ceb410cc
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> 41f993547202
Step 4 : RUN npm install -g alexandreaquiles/marechal-random
 ---> Using cache
 ---> 748021adbd43
Step 5 : CMD marechal-random
 ---> Using cache
 ---> 9974d8f6a06b
Successfully built 9974d8f6a06b

Perceba que todos os comandos tem a mensagem Using cache, incluindo a instalação do módulo marechal-random (Step 4).

Vamos, então, executar novamente nosso comando:

docker run --rm  maquina

Teremos:

0.2565556331537664

A mensagem “Random” não apareceu na frente do número!

A raiz do problema está na criação da imagem maquina a partir do Dockerfile: se exatamente o mesmo comando for executado, é utilizado o cache pré-existente. E o que é comparado é o texto do comando! Como RUN npm install -g alexandreaquiles/marechal-random já tinha sido executado, foi utilizado o cache 748021adbd43.

Invalidação de cache

O que fazer então para que, toda vez que mudarmos o código de marechal-random no GitHub, seja criada uma nova imagem?

Precisamos fazer com que o cache seja invalidado.

O comando COPY pode ser usado no Dockerfile para copiar um arquivo para dentro da imagem que está sendo criada. Se o conteúdo do arquivo ou algum metadado for modificado, o cache é invalidado no COPY e nos comandos posteriores.

Vamos passar a instalação do módulo marechal-random para um arquivo chamado instala.sh:

#!/bin/bash
npm install -g alexandreaquiles/marechal-random

É importante que o arquivo instala.sh tenha permissão de execução.

Depois disso, vamos modificar o Dockerfile para que o arquivo instala.sh seja copiado para a imagem:

FROM ubuntu:trusty

RUN apt-get install -y --no-install-recommends \
	curl \
	git

#instalando nodejs 0.12.4 (com npm 2.10.1 incluso)
RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" \
	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 \
	&& rm "node-v0.12.4-linux-x64.tar.gz" 

COPY instala.sh /modulos/
RUN /modulos/instala.sh

CMD marechal-random

Devemos regerar a imagem maquina:

docker build -t maquina pasta-do-Dockerfile/

Teremos a seguinte saída:

Sending build context to Docker daemon 2.048 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> 78b2ceb410cc
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> 41f993547202
Step 4 : COPY instala.sh /modulos/
 ---> d3bbc70bc9ac
Removing intermediate container b19aa7ff986b
Step 5 : RUN /modulos/instala.sh
 ---> Running in 37fc84fbcb65
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> 686a674f0e1f
Removing intermediate container 37fc84fbcb65
Step 6 : CMD marechal-random
 ---> Running in 11a58e68a5d6
 ---> cbface0310ed
Removing intermediate container 11a58e68a5d6
Successfully built cbface0310ed

Perceba que, para os três primeiros comandos, foram utilizados os caches. A partir do COPY (Step 4), não foi usado o cache. No RUN (Step 5), foi feita a instalação do módulo marechal-random.

Mas não foi usado o cache desses comandos porque modificamos o conteúdo do Dockerfile. Para testar se a invalidação está funcionando, precisamos regerar a imagem:

docker build -t maquina pasta-do-Dockerfile/

Teremos:

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> ce97fca9b976
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> bfef268a14e4
Step 4 : COPY instala.sh /modulos/
 ---> Using cache
 ---> d3bbc70bc9ac
Step 5 : RUN /modulos/instala.sh
 ---> Using cache
 ---> 686a674f0e1f
Step 6 : CMD marechal-random
 ---> Using cache
 ---> cbface0310ed
Successfully built cbface0310ed

Ih! Não funcionou! Foi usado o cache para todos os comandos!

Acontece que, para invalidar o cache do COPY, precisamos mudar o conteúdo ou metadados do arquivo instala.sh.

Até a versão 1.8 do Docker, poderíamos modificar o último acesso do arquivo com um touch. Porém, da versão 1.8 em diante, o último acesso e última modificação de arquivo são desconsiderados.

Para mudar o conteúdo do instala.sh sem mudar seu comportamento, podemos adicionar um comentário com a data atual na última linha do arquivo:

echo "#$(date)" >> instala.sh

Então, vamos construir novamente a imagem maquina:

docker build -t maquina pasta-do-Dockerfile/

Veremos como resposta:

Sending build context to Docker daemon 3.072 kB
Step 1 : FROM ubuntu:trusty
 ---> 6d4946999d4f
Step 2 : RUN apt-get install -y --no-install-recommends 	curl 	git
 ---> Using cache
 ---> ce97fca9b976
Step 3 : RUN curl -sSLO "http://nodejs.org/dist/v0.12.4/node-v0.12.4-linux-x64.tar.gz" 	&& tar -xzf "node-v0.12.4-linux-x64.tar.gz" -C /usr/local --strip-components=1 	&& rm "node-v0.12.4-linux-x64.tar.gz"
 ---> Using cache
 ---> bfef268a14e4
Step 4 : COPY instala.sh /modulos/
 ---> fdcbc296ca90
Removing intermediate container c8ddba926b63
Step 5 : RUN /modulos/instala.sh
 ---> Running in 84bae6632f68
/usr/local/bin/marechal-random -> /usr/local/lib/node_modules/marechal-random/index.js
marechal-random@1.0.0 /usr/local/lib/node_modules/marechal-random
 ---> ec2b0f560f1c
Removing intermediate container 84bae6632f68
Step 6 : CMD marechal-random
 ---> Running in 54d91a990478
 ---> 9c4aefc0ab6b
Removing intermediate container 54d91a990478
Successfully built 9c4aefc0ab6b

Uma boa prática é colocar os comandos que invalidarão o cache por último no seu Dockerfile.

Anúncios