Se você usa linguagens compiladas (Golang, Java) e está buscando uma forma eficiente de reduzir o tamanho das suas imagens docker, está no lugar certo.
Mas antes de decidir utilizar imagens pequenas, precisamos entender o real impacto do uso das imagens grandes.
Esse impacto pode ser maior que o imaginado em ambientes de alta volumetria.
Cenário:
- Uma aplicação utiliza uma imagem docker de 1GB.
- Essa aplicação está em um cluster de 100 nós.
No cenário descrito acima, imagine que a aplicação recebe uma volumetria inesperada e vai escalar automaticamente de 1 para 100 containers.
Isso significa que o cluster todo vai trafegar até 99GB de informação, fazendo pull (download) da imagem docker em cada nó que ainda não a tem disponível.
Pra piorar a situação, resolvemos fazer deploy de uma nova versão com a aplicação escalada, ou seja, todas as imagens serão substituídas nesse processo (~100GB).
Resultado:
- Altas taxas de transferência
- Concorrência de banda entre pull de imagens e usuários
- Escalabilidade afetada
Para tentar resolver essa questão, vamos falar de Docker multistage, que apesar de ser uma funcionalidade bastante conhecida na comunidade, ainda gera dúvidas em alguns dos nossos clientes.
Antes da prática, vamos entender a teoria do uso de Multi Stage para o nosso caso:
imagine que o processo de build de uma aplicação precise atender uma série de dependências. Essas dependências criam camadas que fazem a imagem docker ficar cada vez maior.
Com o uso de Multi Stage, temos a possibilidade de utilizar imagens diferentes entre o processo de build e o processo de execução do binário. O resultado disso é uma imagem significativamente menor, já que as camadas anteriores são ‘descartadas’, restando somente o binário para execução em uma imagem enxuta.
Dockerfile:
FROM golang:1.7.3 AS builder
WORKDIR /go/src/github.com/alexellis/href-counter/
RUN go get -d -v golang.org/x/net/html
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY -–from=builder /go/src/github.com/alexellis/href-counter/app .
CMD ["./app"]
O exemplo acima, ilustra o uso da imagem golang:1.7.3 com a tag builder, para o processo de build
da aplicação.
Em seguida, a imagem alpine:latest, é utilizada como imagem de execução, onde copiamos somente o
binário da imagem anterior: COPY –from=builder /go/src/github.com/alexellis/href-counter/app .
Isso traz resultados relevantes, já que a imagem final precisa somente do binário compilado.
Mão na Massa
Para o nosso laboratório prático, vamos utilizar um projeto opensource mantido pelo Luizalabs
chamado Teresa, onde tive a chance de colaborar para uma redução significativa da imagem docker.
A ideia do passo a passo é fazer o build da imagem antes do commit com Multi Stage, e depois do
commit pra fazermos a comparação.
Antes do Multi Stage
1 – Vamos começar clonando o projeto
$git clone https://github.com/luizalabs/teresa.git
2 – Em seguda acesse a pasta teresa
$cd teresa
3 – Voltando para o commit antes da alteração
$git checkout 70da95bbe2197ce1db3b46e8945ab990b28ebd8a
4 – Visualizando dockerfile
$cat Dockerfile
FROM golang:1.8
RUN mkdir -p /go/src/github.com/luizalabs/teresa
WORKDIR /go/src/github.com/luizalabs/teresa
COPY . /go/src/github.com/luizalabs/teresa
RUN make build-server
ENTRYPOINT ["./teresa-server"]
CMD ["run"]
EXPOSE 50051
5 – Fazendo build da imagem antiga
$docker build -t teresa-old .
6 – Listando imagens existentes
$docker images
Observe que a imagem tinha o tamanho de 823MB antes da alteração.
Depois do Multi Stage
1 – Indo para o commit depois da alteração
$git checkout 15de6e124ac1d081479320259c106f34628b989f
2 – Visualizando dockerfile novo
$cat Dockerfile
FROM golang:1.8 AS builder
WORKDIR /go/src/github.com/luizalabs/teresa
COPY . /go/src/github.com/luizalabs/teresa
RUN make build-serverFROM debian:9-slim
RUN apt-get update && \
apt-get install ca-certificates -y &&\
rm -rf /var/lib/apt/lists/* &&\
rm -rf /var/cache/apt/archives/*
WORKDIR /app
COPY –from=builder /go/src/github.com/luizalabs/teresa .
ENTRYPOINT [“./teresa-server”]
CMD [“run”]
EXPOSE 50051
3 – Fazendo build da imagem nova
$docker build -t teresa-new .
4 – Listando imagens existentes
$docker images
Pós alteração, a imagem chegou a 172MB, com possibilidade de melhoras.
Se você ainda não conhecia Docker Multistage, agora já conhece e pode utilizar na busca por imagens menores.
Se você já utiliza, compartilhe conosco o seu case também.