- read

Golang — Lightweight Docker + Kafka Image

Guilherme Mariano 1

Algumas dicas e boas práticas para ajudar a diminuir o tamanho de imagens para Docker

Recentemente passei a estudar mais sobre EDD (event-driven development) e resolvi aplicar um pouco desse conceito em uma aplicação Go com Kafka, uma ideia simples, um endpoint para publicar mensagens em um tópico e um consumer que vai receber mensagens desse mesmo tópico no Kafka.

Primeira etapa — Criação do Dockerfile

Meu primeiro passo foi criar um ambiente de desenvolvimento dockerizado utilizando imagem oficial do Go e o Dockerfile ficou desse jeito:

Até ai tudo tranquilo, a imagem fez o build e rodou como esperado. Mas analisando a imagem, tinha quase 1GB!

Diminuindo o tamanho com imagem Alpine

Apesar de rodar como esperado, o tamanho da imagem montada ficou grande. Para tentar diminuir esse tamanho, alterei a imagem para usar a versão alpine, uma versão mais leve. Com a alteração, o Dockerfile ficou assim:

Vou explicar os detalhes dessas alterações:

imagem: alteramos para golang:alpine, dessa forma mudamos a imagem base do nosso container.

novo comando apk: adicionamos um novo comando apk (Alpine Package Keeper) para adicionar 2 dependências que vamos precisar para utilizar o Kafka e que, por padrão, não estão presentes na imagem golang:alpine.

build tag: para buildar nosso binário foi adicionada uma nova tag -tags musl, que será responsável por adicionar o código da dependência que adicionamos com o apk anteriormente.

Depois dessa alteração, reduzimos o tamanho da imagem para quase a metade do tamanho original:

Multi-stage build

Mesmo trocando para uma imagem mais leve ainda ficou um tamanho considerável. Como em Go nós precisamos apenas do binário para rodar nossas aplicações, é uma boa prática não utilizarmos o runtime do Go em ambientes produtivos. Para diminuir ainda mais e aplicar as boas práticas recomendadas, vamos aplicar o multi-stage em nosso Dockerfile:

Vamos as alterações:

as builder: demos um nome ao nosso primeiro stage de build para usarmos no stage seguinte

segundo FROM: no nosso segundo stage não precisamos da imagem do golang, então podemos utilizar a imagem alpine que é bem mais leve e baseada na lib musl-dev que estamos utilizando

copy: nessa parte que usamos o nome do primeiro stage para copiar o binário gerado para nosso segundo stage e na sequência executamos

Com essas alterações, conseguimos chegar em pouco mais de 21MB!

Imagens Scratch

Apesar de ser uma redução de mais de 97% do tamanho inicial, podemos reduzir ainda mais passando alguns parâmetros adicionais no go build e se trocarmos a nossa imagem alpine para usar a imagem scratch no segundo stage, uma imagem sem nada onde vamos deixar apenas nosso binário. Nosso Dockerfile alterado ficou assim:

Explicando os parâmetros do build:

GOOS: com isso nós especificamos que nosso build sera para sistemas baseados em Linux.

GOARCH: esse parâmetro define que nosso binário sera utilizado em sistemas com arquitetura x64 (64 bits).

ldflags: com isso podemos passar algumas flags para o build, no nosso caso passamos uma extldflags “-static”, dizendo para que nosso binário tenha links estáticos com nossas dependências para utilização do Kafka. Caso isso não seja feito, o stage scratch não vai conseguir usar nosso binário.

Ao final de todo esse processo, chegamos a incríveis 14.1MB!

Distroless — Um pouco mais de segurança

Por não ter nada a imagem scratch é muito leve, mas por não ter nada ela também abre espaço para muitas vulnerabilidades como ausência de ca-certificates, nonroot user etc…
Para contornar esse problema, podemos usar as Distroless.
Distroless são imagens tão leves quanto a scratch, mas possui mais segurança para ambientes produtivos.
Com essa alteração, o Dockerfile ficou assim:

E, por fim, nossa imagem final ficou com apenas 16.6MB!

Considerações finais

Espero ter ajudado com essas dicas e que todo o processo tenha ficado claro. De qualquer forma, vou deixar algumas referências interessantes para aprofundar mais em redução de imagens com Docker e cobre as ldflags e extldflags do Go.