Навроцкий Артем
При повторной сборке образов, Docker считает, что если не менялись входные данные, то можно использовать результат предыдущей сборки. Это заметно ускоряет повторную сборку контейнера.
Но директива RUN
не является "чистой функцией" и зависит от внешних факторов.
В результате после изменения Dockerfile может возникнуть ситуация, что RUN
команды будут выполняться
в сильно разное время.
RUN apt -y update
RUN apt -y install curl nginx
RUN apt -y update
RUN apt -y install curl nginx git
# docker используент кэш недельной давности
RUN apt -y update
# docker пытается выполнить команду, но кэш apt не актуален
RUN apt -y install curl nginx git
RUN apt -y update
RUN apt -y install curl nginx
RUN apt -y update && \
apt -y install curl nginx && \
rm -rf /var/lib/apt/lists/*
При сборке Docker-образов создаётся много слоёв, которые содержат различные временные данные и лишние зависимости.
Для уменьшения размера образов имеет смысл разбить Dockerfile на несколько образов:
FROM golang:1.7.3 AS build
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 go build -o /go/bin/app .
FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=build /go/bin/app .
CMD ["./app"]
https://docs.docker.com/develop/develop-images/multistage-build/
$ docker pull docker.joom.it/helm:latest
$ docker build --cache-from=docker.joom.it/helm:latest .
--cache-from
, то данный образ будет переиспользоваться при сборке. Локальный кэш
сборки при этом игнорируется.
--cache-from
, то приоритет образов не определён.--cache-from
для каждого промежуточного слоя.
FROM nginx:1.17.7
RUN apt-get update && apt-get install -y curl
HEALTHCHECK \
--interval=5s \
--timeout=1s \
--retries=3 CMD curl --head http://localhost/
$ docker build -q -t test . && docker run --rm -it test
sha256:d2064caeda2e5c32d0d39a17c9b2d71e27dbdd6d93b2d6f2361802ea60235d34
127.0.0.1 - - [10/Jul/2020:10:54:45 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"
127.0.0.1 - - [10/Jul/2020:10:54:50 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"
127.0.0.1 - - [10/Jul/2020:10:54:55 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"
127.0.0.1 - - [10/Jul/2020:10:55:00 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"
127.0.0.1 - - [10/Jul/2020:10:55:05 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/7.64.0" "-"
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
027b1a3d2cd0 test "nginx -g 'daemon of…" 1 second ago Up Less than a second (health: starting)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS
027b1a3d2cd0 test "nginx -g 'daemon of…" 9 seconds ago Up 8 seconds (healthy)
$ docker inspect 027b1a3d2cd0 | jq ".[0].State.Health.Status"
"healthy"
FROM ubuntu
CMD /bin/sleep 600
FROM ubuntu
CMD ["/bin/sleep", "600"]
$ docker build -t test .
$ docker run --rm --name test test
У контейнеров будет разная реакция на:
$ sudo killall -SIGTERM sleep
$ docker exec test ps
PID TTY TIME CMD
1 ? 00:00:00 sh
7 ? 00:00:00 sleep
8 ? 00:00:00 ps
$ docker exec test ps
PID TTY TIME CMD
1 ? 00:00:00 sleep
6 ? 00:00:00 ps
PID = 1 имеет достаточно специфическое поведение с точки зрения обработки сигналов.
$ docker exec test ps
PID TTY TIME CMD
1 ? 00:00:00 docker-init
6 ? 00:00:00 sleep
7 ? 00:00:00 ps
Флаг --init
отсутсвует при запуске контейнеров в Kubernetes.
FROM ubuntu
ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]
CMD ["/bin/sleep", "600"]
$ docker exec test ps
PID TTY TIME CMD
1 ? 00:00:00 tini
6 ? 00:00:00 sleep
7 ? 00:00:00 ps
FROM ubuntu
CMD /bin/sleep 600
Shell в Ubuntu прокидывает SIGTERM
до дочернего процесса.
FROM alpine
CMD /bin/sleep 600
Shell в Alpine не прокидывает SIGTERM
до дочернего процесса.
При сборке образа указывается корневой каталог контекста сборки.
Контекст сборки копируется с локальной машины на Docker Daemon.
$ docker build .
Sending build context to Docker daemon 6.51 MB
...
Для исключения файлов из контекста сборки используется файл .dockerignore
.
$ cat .dockerignore
# comment
*/temp*
*/*/temp*
temp?
Интересный факт:
Файл Dockerfile
может находиться вне контекста и его смело можно добавлять в
.dockerignore
.
$ DOCKER_BUILDKIT=1 docker build
--secret
https://docs.docker.com/develop/develop-images/build_enhancements/
$ echo 'WARMACHINEROX' > mysecret.txt
$ DOCKER_BUILDKIT=1 docker build --progress=plain \
--no-cache --secret id=mysecret,src=mysecret.txt .
# syntax = docker/dockerfile:1.0-experimental
FROM alpine
# shows secret from default secret location
RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
# shows secret from custom secret location
RUN --mount=type=secret,id=mysecret,dst=/foobar cat /foobar
Статистика сборки (начало 2020):
Execution statistics:
docker push 20m7.591993776s
docker build 10m52.346187972s
shell command 4m51.205196455s
go build 3m36.109218439s
docker pull 19.589956997s
docker image rm 3.795116679s
file rm 1.217284781s
docker file 7.277674ms
Build completed: 7m43.878900367s
Отображается wall clock time при сборке в несколько потоков. Но всё равно сильно выделяется время на сборку контейнеров.
Сборка каждого Dockerfile отправляет контекст сборки (в нашем случае туда входит только исполняемый файл) в Docker Host.
Каждая комадна в Dockerfile провоцирет создание временного Docker-контейнера.
Есть ощущение, что параллельное выполнение docker build
частично сериализуется где-то внутри Docker
Host.
Во время docker push
львиную долю времени занимает сжатие образов GZip-ом.
RUN
by designНельзя использовать тэг latest
при деплое в production.
Особенно если деплой в Kubernetes.
pull
, то может быть запущен образ,
собранный несколько pull
, то при переезде pod-а может быть
запущен образ, собранный через несколько дней после деплоя.
Никогда не заливайте контейнеры, собранные локально в Docker Registry.
Все контейнеры для Docker Registry должны собраться через CI:
ppa
с ffmpeg
)
У нас есть инфраструктура для сборки простых образов в репозитории: github.com/joomcode/docker-images
При сборке docker-образов крайне желательно заполнять label-ы, чтобы не было вопросов из серии "откуда вообще этот образ взялся":
Пример:
com.joom.retention.maxCount: 5
com.joom.retention.maxCountGroup: master
org.opencontainers.image.created: 2020-06-25T16:20:56Z
org.opencontainers.image.ref.ci: https://jenkins.joom.it/job/docker-images/job/...
org.opencontainers.image.ref.dockerfile: backend-api-test-base/Dockerfile
org.opencontainers.image.revision: 41a1d8a485ae38311b141e7c355acf081dcc7f11
org.opencontainers.image.source: https://github.com/joomcode/docker-images.git
Аннотации взяты отсюда: https://github.com/opencontainers/image-spec/blob/master/annotations.md
Чтобы не захламлять Dockerfile, можно воспользоваться конструкцией вида:
$ docker build \
--label org.opencontainers.image.created=$buildDate \
--label org.opencontainers.image.ref.ci=$BUILD_URL \
--label org.opencontainers.image.ref.dockerfile=$dockerFile \
--label org.opencontainers.image.source=$GIT_URL \
--label org.opencontainers.image.revision=$GIT_COMMIT \
...
У нас в docker registry реализован механизм чистки более ненужных образов.
Ответственность за чистку образов возлагается на команды, которые эти образы используют.
Docker рассчитан на запуск одного процесса в контейнере.
Если приложение состоит из нескольких процессов, то есть следующие варианты:
version: "3.8"
services:
server:
build: server/
ports:
- 8080:80
client:
build: client/
command: python ./client.py
environment:
- SERVER=server:80
depends_on:
- server
$ docker-compose build && docker-compose up
http://server:80
)
ports
прокидывает порты на хост-машинуdocker-compose.yml
в строках вида ${FOO}
и $FOO
подставляются переменные окружения
.env
Для запуска нескольких процессов в контейнере можно воспользоваться supervisord.
FROM ubuntu
RUN apt-get update \
&& apt-get install -y supervisor nginx \
&& rm -rf /var/lib/apt/lists/*
COPY nginx.conf /etc/supervisor/conf.d/
CMD ["/usr/bin/supervisord", "--nodaemon"]
[program:nginx]
command=/usr/sbin/nginx -g 'daemon off;'
stdout_logfile=/var/log/nginx-stdout.log
stderr_logfile=/var/log/nginx-stderr.log
autostart=true
autorestart=true
numprocs=1
$ supervisorctl status
nginx RUNNING pid 7, uptime 0:00:56
$ supervisorctl stop nginx
nginx: stopped
$ supervisorctl status
nginx STOPPED Jul 10 02:10 PM
$ supervisorctl start nginx
nginx: started
$ supervisorctl status
nginx RUNNING pid 64, uptime 0:00:05
Docker поддерживает запуск сборки "внутри" Docker-контейнера из коробки:
Docker compose при этом не поддерживается.
pipeline {
agent {
docker {
image "golang:1.13.9-alpine3.11"
args "--hostname ${env.JOB_NAME.replace('/', '-')}-${env.BUILD_NUMBER}"
customWorkspace "workspace/example"
label "onspot"
}
}
...
}
pipeline {
agent {
docker {
dir "jenkins"
filename "Dockerfile"
additionalBuildArgs "--pull"
args "--hostname ${env.JOB_NAME.replace('/', '-')}-${env.BUILD_NUMBER}"
customWorkspace "workspace/example"
label "onspot"
}
}
...
}
hostname
.