DevGang
Авторизоваться

Создайте минимальный образ Docker

Если вы знакомы с docker , вы, вероятно, знаете, что в хранилище образов docker используется технология многоуровневого хранения Union FS . Когда вы создаете образ Docker, он строится по одному слою за раз, при этом предыдущий слой служит основой для следующего слоя, и каждый слой не изменяется после его создания. Из-за этого при сборке docker-образа мы должны быть особенно внимательны, чтобы включать в каждый слой только то, что необходимо, и удалять как можно больше лишнего в конце сборки. Например, если вы создаете простое приложение, написанное на Go, в принципе вам нужен только бинарный файл, скомпилированный с помощью Go, и нет необходимости сохранять инструменты и среду для сборки.

docker официально предоставляет специальный пустой образ, использование этого образа означает, что нам не нужен какой-либо существующий образ в качестве основы, и мы напрямую используем наши пользовательские инструкции в качестве первого слоя образа.

FROM scratch
...

На самом деле, мы можем создать собственный рабочий образ.


$ tar cv --files-from /dev/null | docker import - scratch

Возникает вопрос, какие изображения мы можем сделать, используя скретч-образы в качестве основы? Ответ заключается в том, что все исполняемые файлы, не нуждающиеся в каких-либо зависимостях, могут быть созданы с использованием нуля в качестве базового образа. Конкретно для статически скомпилированных программ под linux нет необходимости в поддержке runtime со стороны операционной системы, все уже включено в исполняемый файл, например, многие приложения, разработанные на языке Go, используют прямой FROM scratch метод для создания образов, поэтому конечный образ размер очень маленький.

Вот код простого веб-приложения, разработанного на языке Go.

package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Hello, you've requested: %s\n", r.URL.Path)
    })

    http.ListenAndServe(":80", nil)
}

Мы можем использовать go build для компиляции этой программы и создания образа докера на основе нуля со следующим файлом докера.

FROM scratch
ADD helloworld /
CMD ["/helloworld"]

Затем начните компилировать и создавать образ докера.

mc@mcmbp:~/gocode/src/hello# go build -o helloworld
mc@mcmbp:~/gocode/src/hello# docker build -t helloworld .
Sending build context to Docker daemon  7.376MB
Step 1/3 : FROM scratch
 --->
Step 2/3 : ADD helloworld /
 ---> 000f150706c7
Step 3/3 : CMD ["/helloworld"]
 ---> Running in f9c2c6932a34
Removing intermediate container f9c2c6932a34
 ---> 496f865c05e4
Successfully built 496f865c05e4
Successfully tagged helloworld:latest

Итак, образ построен успешно, давайте посмотрим на его размер.

mc@mcmbp:~/gocode/src/hello# docker image ls
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              496f865c05e4        8 seconds ago       7.37MB

Но запустив этот образ, вы обнаружите, что контейнер не может быть создан.


mc@mcmbp:~/gocode/src/hello# docker run -ti -p 80:80 helloworld
standard_init_linux.go:207: exec user process caused "no such file or

Причина в том, что наш исполняемый файл helloworld работает с некоторыми библиотеками, такими как libc, которые все еще динамически связаны, а рабочий образ полностью пуст, поэтому при сборке исполняемого файла helloworld укажите флаг статической ссылки -static и другие параметры, чтобы сгенерированный двоичный файл hellowrld статически связывал все библиотеки.

$ CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o helloworld .

Затем воссоздайте образ докера.

mc@mcmbp:~/gocode/src/hello# CGO_ENABLED=0 go build -a -ldflags '-extldflags "-static"' -o helloworld .
mc@mcmbp:~/gocode/src/hello# docker build -t helloworld .
Sending build context to Docker daemon  7.316MB
Step 1/3 : FROM scratch
 --->
Step 2/3 : ADD helloworld /
 ---> 3fec774cb2a4
Step 3/3 : CMD ["/helloworld"]
 ---> Running in cbe7dc97d6ad
Removing intermediate container cbe7dc97d6ad
 ---> d15a1e6d759a
Successfully built d15a1e6d759a
Successfully tagged helloworld:latest
mc@mcmbp:~/gocode/src/hello# docker image ls -a
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
helloworld          latest              d15a1e6d759a        3 seconds ago       7.31MB
<none>              <none>              3fec774cb2a4        3 seconds ago       7.31MB

Запустите образ докера.

mc@mcmbp:~/gocode/src/hello# docker run -ti -d -p 80:80 helloworld
3c77ae750352245369c4d142e4e57fd3c0f1e11d67ef857235417ec475ef6286
mc@mcmbp:~/gocode/src/hello# curl -v localhost
* Rebuilt URL to: localhost/
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 19 Mar 2019 12:54:28 GMT
< Content-Length: 27
< Content-Type: text/plain; charset=utf-8
<
Hello, you've requested: /
* Connection #0 to host localhost left intact

Но возникает вопрос, если мы скомпилируем бинарники helloowrld и сделаем образы на MacOS, сможем ли мы запустить контейнеры docker? Ответ - нет!

Нам нужно указать GOOS=linux, что означает, что полная команда компиляции должна выглядеть так.


$ CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"'

Для некоторых OCD-программистов, которые хотят пойти дальше, почему бы не скомпилировать исполняемый файл внутри контейнера, а затем не собрать образ? Преимущество этого состоит в том, что он контролирует среду компиляции языка Go, обеспечивает повторяемость компиляции и удобен для некоторых проектов, которые интегрируются с инструментами непрерывной интеграции.

Docker-CE 17.5 представляет новую функцию для создания образов с нуля, которая называется «Многоэтапные сборки». С помощью этой новой функции мы можем написать наш dockerfile следующим образом.

FROM golang as compiler
RUN CGO_ENABLED=0 go get -a -ldflags '-s' \
github.com/morvencao/helloworld
FROM scratch
COPY --from=compiler /go/bin/helloworld .
EXPOSE 80
CMD ["./helloworld"]

Да, вы правильно прочитали, это действительно dockerfile, содержащий две директивы FROM со следующими оговорками.

  1. FROM golang as compiler заключается в том, чтобы дать первому этапу сборки имя, называемое compiler
  2. COPY --from=compiler /go/bin/helloworld . является ссылкой на вывод сборки первого этапа для сборки второго этапа

Если у вас нет имени для первого этапа, вы можете использовать номер этапа сборки (начиная с 0), чтобы указать его, например: --from=0 , но из соображений удобочитаемости лучше дать ему имя.

После завершения сборки давайте посмотрим на размер образа.

mc@mcmbp:~/gocode/src/hello# docker image ls
REPOSITORY        TAG      IMAGE ID       CREATED          SIZE
helloworld        latest   071ca07e23f5   1 minutes ago    7.31MB
<none>            <none>   2471fd63f0e7   1 minutes ago    720MB
golang            latest   6d0bfafa0452   2 weeks ago      703MB

Запустите образ докера.

mc@mcmbp:~/gocode/src/hello# docker run -ti -d -p 80:80 helloworld
3c77ae750352245369c4d142e4e57fd3c0f1e11d67ef857235417ec475ef6286
mc@mcmbp:~/gocode/src/hello# curl -v localhost
* Rebuilt URL to: localhost/
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 80 (#0)
> GET / HTTP/1.1
> Host: localhost
> User-Agent: curl/7.47.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Date: Tue, 19 Mar 2019 12:54:28 GMT
< Content-Length: 27
< Content-Type: text/plain; charset=utf-8
<
Hello, you've requested: /
* Connection #0 to host localhost left intact
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

В подарок 100$ на счет при регистрации

Получить