Construcciones de múltiples etapas
Las construcciones de múltiples etapas son útiles para cualquiera que haya luchado por optimizar los Dockerfiles mientras los mantiene fáciles de leer y mantener.
Sin construcciones de múltiples etapas
Antes de Docker 17.05, típicamente había dos formas de construir imágenes Docker:
Poner todo en un Dockerfile
Una forma era incluir todos los pasos de compilación en un solo Dockerfile
, incluyendo la compilación, prueba y empaquetado del proyecto y sus dependencias. Esto podría llevar a algunos problemas:
-
Múltiples capas de imagen, mayor tamaño de imagen y tiempos de despliegue más largos
-
Riesgo de filtración de código fuente
Por ejemplo, escribir un archivo app.go
que imprima ¡Hola Mundo!
package main
import "fmt"
func main(){
fmt.Printf("Hello World!");
}
Escribir un archivo Dockerfile.one
FROM golang:alpine
RUN apk --no-cache add git ca-certificates
WORKDIR /go/src/github.com/go/helloworld/
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app . \
&& cp /go/src/github.com/go/helloworld/app /root
WORKDIR /root/
CMD ["./app"]
Construir la imagen
$ docker build -t go/helloworld:1 -f Dockerfile.one .
Dividir en múltiples Dockerfiles
La otra forma era primero compilar, probar y empaquetar el proyecto y sus dependencias en un Dockerfile
, y luego copiar los artefactos al entorno de ejecución en otro Dockerfile
. Este enfoque requería escribir dos Dockerfiles
y algunos scripts de compilación para automatizar la integración de las dos etapas, lo que hacía que el proceso de despliegue fuera más complejo, aunque evitaba los riesgos del primer enfoque.
Por ejemplo, escribir un archivo Dockerfile.build
FROM golang:alpine
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld
COPY app.go .
RUN go get -d -v github.com/go-sql-driver/mysql \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
Escribir un archivo Dockerfile.copy
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY app .
CMD ["./app"]
Crear un script build.sh
#!/bin/sh
echo Building go/helloworld:build
docker build -t go/helloworld:build . -f Dockerfile.build
docker create --name extract go/helloworld:build
docker cp extract:/go/src/github.com/go/helloworld/app ./app
docker rm -f extract
echo Building go/helloworld:2
docker build --no-cache -t go/helloworld:2 . -f Dockerfile.copy
rm ./app
Ahora ejecutar el script para construir la imagen
$ chmod +x build.sh
$ ./build.sh
Comparar los tamaños de imagen generados por los dos enfoques
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
Usando construcciones de múltiples etapas
Para resolver los problemas anteriores, Docker v17.05 introdujo soporte para construcciones de múltiples etapas. Usando construcciones de múltiples etapas, podemos resolver fácilmente los problemas mencionados anteriormente, y solo necesitamos escribir un solo Dockerfile
:
Por ejemplo, escribir un archivo Dockerfile
FROM golang:alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]
Construir la imagen
$ docker build -t go/helloworld:3 .
Comparar los tamaños de las tres imágenes
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
go/helloworld 3 d6911ed9c846 7 seconds ago 6.47MB
go/helloworld 2 f7cf3465432c 22 seconds ago 6.47MB
go/helloworld 1 f55d3e16affc 2 minutes ago 295MB
Es claro que la imagen construida usando construcciones de múltiples etapas es de menor tamaño, mientras también resuelve perfectamente los problemas mencionados anteriormente.
Usar una etapa previa como una nueva etapa
Cuando se usan construcciones de múltiples etapas, no se limita a copiar de las etapas que creó anteriormente en su Dockerfile. Puede usar la instrucción COPY --from para copiar desde una imagen separada, ya sea usando el nombre de imagen local, una etiqueta disponible localmente o en un registro de Docker, o un ID de etiqueta. El cliente de Docker tira de la imagen si es necesario y copia el artefacto desde allí. La sintaxis es:
FROM golang:alpine as builder
Por ejemplo, si solo queremos construir la imagen para la etapa builder
, agregar el parámetro --target=builder
:
$ docker build --target builder -t username/imagename:tag .
Usar una imagen externa como etapa
Puede retomar donde una etapa previa se quedó refiriéndose a ella cuando use la directiva FROM.
En el ejemplo anterior, usamos COPY --from=0 /go/src/github.com/go/helloworld/app .
para copiar archivos desde la imagen de la etapa previa. También podemos copiar archivos desde cualquier otra imagen.
$ COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf