Компиляция вашего приложения Go для контейнеров
Отрывок из книги «Мощные приложения командной строки в Go» Рикардо Херарди
📘 Если вам понравился этот отрывок, подумайте о том, чтобы купить книгу или присоединиться к беседе на DevTalk (или и к тому, и к другому).
Еще один альтернативный способ распространения вашего приложения, который становится все более популярным в последние годы, — разрешить вашим пользователям запускать приложение в контейнерах Linux. Контейнеры упаковывают ваше приложение и все необходимые зависимости, используя стандартный формат образа, и запускают приложение изолированно от других процессов, работающих в той же системе. Контейнеры используют ресурсы ядра Linux, такие как пространства имен и групповые группы, для обеспечения изоляции и управления ресурсами.
Доступны различные среды выполнения контейнеров, такие как Podman и Docker. Если вы запускаете эти примеры в системе Linux, вы можете использовать любой из них взаимозаменяемо. Если вы работаете в Windows или macOS, Docker предоставляет настольную версию, которая упрощает запуск. Вы также можете использовать Podman в этих операционных системах, но для этого вам необходимо установить виртуальную машину. Мы не будем рассматривать здесь процесс установки среды выполнения контейнера. Для получения более подробной информации ознакомьтесь с документацией соответствующего проекта.
Чтобы распространять ваше приложение в виде контейнера, вам необходимо создать образ контейнера. Вы можете сделать это несколькими способами, но наиболее распространенным способом является использование файла Dockerfile, который содержит рецепт создания образа. Затем вы передаете этот файл в качестве входных данных командам docker или podman для создания образа. Для получения более подробной информации о том, как создать Dockerfile, обратитесь к его документации.
В центре внимания этого раздела — предоставление некоторых параметров сборки для оптимизации вашего приложения для работы в контейнерах. Go — отличный выбор для создания приложений, работающих в контейнерах, поскольку он создает один двоичный файл, который можно добавить в образ контейнера без дополнительных сред выполнения или зависимостей.
Чтобы сделать двоичный файл еще более подходящим для запуска в контейнере, вы можете передать дополнительные параметры сборки. Например, вы активируете статически связанный двоичный файл, установив CGO_ENABLED=0
, и вы можете передать дополнительные параметры компоновщика, используя флаг -ldflags
. Чтобы уменьшить размер двоичного файла, используйте параметры -ldflags=”-s -w”
для удаления символов отладки из двоичного файла. Прежде чем приступить к работе, внимательно изучите некоторые параметры сборки, которые вы будете использовать:
CGO_ENABLED=0
: включает статически связанные двоичные файлы, чтобы сделать приложение более переносимым. Это позволяет вам использовать двоичный файл с исходными изображениями, которые не поддерживают общие библиотеки, при создании образа контейнера.GOOS=linux
: Поскольку контейнеры работают под управлением Linux, установите этот параметр, чтобы включить повторяющиеся сборки даже при сборке приложения на другой платформе.-ldflags=”-s -w”
: Параметр-ldflags
позволяет указать дополнительные параметры компоновщика, которые go build использует на этапе компоновки процесса сборки. В этом случае опция-s -w
удаляет из двоичного файла символы отладки, уменьшая его размер. Без этих символов сложнее отлаживать приложение, но обычно это не является серьезной проблемой при работе в контейнере. Чтобы просмотреть все параметры компоновщика, которые вы можете использовать, запустите go tool link.-tags=containers
: Это относится к вашему приложению Pomodoro. Создайте приложение, используя файлы, указанные с помощью тега container, чтобы удалить зависимость от SQLite и уведомлений, как вы делали в разделе Условная сборка приложения.
Теперь создайте свой двоичный файл, используя эти параметры:
$ CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -tags=containers
Проверьте этот файл, чтобы проверить его свойства и размер:
$ ls -lh pomo -rwxr-xr-x 1 ricardo users 7.2M Feb 28 12:06 pomo $ file pomo pomo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, ... ... , stripped
Обратите внимание, что размер файла составляет около 7 МБ, и файл статически связан и разделен.
Сравните это со сборкой приложения без этих опций:
$ go build $ ls -lh pomo -rwxr-xr-x 1 ricardo users 13M Feb 28 12:09 pomo $ file pomo pomo: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, ..., for GNU/Linux 4.4.0, not stripped
Бинарный файл, оптимизированный для контейнеров, почти на 50 % меньше исходного. Он также статически связан и лишен символов отладки.
Получив двоичный файл, вы создадите образ контейнера с помощью Dockerfile. Вернитесь в корневой каталог главы и создайте новый подкаталог containers:
$ cd $HOME/pragprog.com/rggo/distributing $ mkdir containers
Создайте и отредактируйте файл Dockerfile в этом подкаталоге. Добавьте следующее содержимое, чтобы создать новый образ из базового образа alpine:latest, создайте pomo обычного пользователя для запуска приложения и скопируйте созданный ранее двоичный файл pomo/pomo в образ в каталоге /app:
FROM alpine:latest RUN mkdir /app && adduser -h /app -D pomo WORKDIR /app COPY --chown=pomo /pomo/pomo . CMD ["/app/pomo"]
Создайте свой образ с помощью команды сборки docker, предоставив этот Dockerfile в качестве входных данных:
$ docker build -t pomo/pomo:latest -f containers/Dockerfile . STEP 1: FROM alpine:latest STEP 2: RUN mkdir /app && adduser -h /app -D pomo --> 500286ad2c9 STEP 3: WORKDIR /app --> 175d6b43663 STEP 4: COPY --chown=pomo /pomo/pomo . --> 2b05fa6dbba STEP 5: CMD ["/app/pomo"] STEP 6: COMMIT pomo/pomo:latest --> 998e1c2cc75 998e1c2cc75dc865f57890cb6294c2f25725da97ce8535909216ea27a4a56a38
Эта команда создает образ с тегом pomo/pomo:latest
. Перечислите его, используя образы докеров:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/pomo/pomo latest 998e1c2cc75d 47 minutes ago 13.4 MB docker.io/library/alpine latest e50c909a8df2 4 weeks ago 5.88 MB
Запустите приложение с помощью Docker, указав флаги -it
для включения эмулятора терминала, необходимого для запуска интерактивного интерфейса командной строки Pomodoro:
$ docker run --rm -it localhost/pomo/pomo
Вы также можете использовать Docker для сборки приложения с официальным образом Go и многоэтапным Dockerfile. Многоэтапный Dockerfile создает экземпляр контейнера для компиляции приложения, а затем копирует полученный файл во второй образ, аналогичный предыдущему созданному вами Dockerfile. Создайте новый файл с именем Dockerfile.builder в подкаталоге контейнеров. Определите многоэтапную сборку, используя следующий код:
раздача/контейнеры/Dockerfile.builder
FROM golang:1.15 AS builder RUN mkdir /distributing WORKDIR /distributing COPY notify/ notify/ COPY pomo/ pomo/ WORKDIR /distributing/pomo RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -tags=containers FROM alpine:latest RUN mkdir /app && adduser -h /app -D pomo WORKDIR /app COPY --chown=pomo --from=builder /distributing/pomo/pomo . CMD ["/app/pomo"]
Теперь используйте этот образ для создания двоичного файла и образа контейнера для вашего приложения:
$ docker build -t pomo/pomo:latest -f containers/Dockerfile.builder . STEP 1: FROM golang:1.15 AS builder STEP 2: RUN mkdir /distributing --> e8e2ea98b04 STEP 3: WORKDIR /distributing --> 81cee711389 STEP 4: COPY notify/ notify/ --> ac86b302a7a STEP 5: COPY pomo/ pomo/ --> 5353bc4d73e STEP 6: WORKDIR /distributing/pomo --> bfddd5217bf STEP 7: RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -tags=containers go: downloading github.com/spf13/viper v1.7.1 go: downloading github.com/spf13/cobra v1.1.1 go: downloading github.com/mitchellh/go-homedir v1.1.0 go: downloading github.com/mum4k/termdash v0.13.0 go: downloading github.com/spf13/afero v1.1.2 go: downloading github.com/spf13/cast v1.3.0 go: downloading github.com/pelletier/go-toml v1.2.0 go: downloading gopkg.in/yaml.v2 v2.2.8 go: downloading github.com/mitchellh/mapstructure v1.1.2 go: downloading github.com/spf13/pflag v1.0.5 go: downloading golang.org/x/text v0.3.4 go: downloading github.com/subosito/gotenv v1.2.0 go: downloading github.com/magiconair/properties v1.8.1 go: downloading github.com/fsnotify/fsnotify v1.4.7 go: downloading github.com/mattn/go-runewidth v0.0.9 go: downloading github.com/spf13/jwalterweatherman v1.0.0 go: downloading github.com/hashicorp/hcl v1.0.0 go: downloading github.com/gdamore/tcell/v2 v2.0.0 go: downloading gopkg.in/ini.v1 v1.51.0 go: downloading golang.org/x/sys v0.0.0-20201113233024-12cec1faf1ba go: downloading github.com/gdamore/encoding v1.0.0 go: downloading github.com/lucasb-eyer/go-colorful v1.0.3 --> de7b70a3753 STEP 8: FROM alpine:latest STEP 9: RUN mkdir /app && adduser -h /app -D pomo --> Using cache 500286ad2c9f1242184343eedb016d53e36e1401675eb6769fb9c64146... --> 500286ad2c9 STEP 10: WORKDIR /app --> Using cache 175d6b43663f6db66fd8e61d80a82e5976b27078b79d59feebcc517d44... --> 175d6b43663 STEP 11: COPY --chown=pomo --from=builder /distributing/pomo/pomo . --> 0292f63c58f STEP 12: CMD ["/app/pomo"] STEP 13: COMMIT pomo/pomo:latest --> 3c3ec9fafb8 3c3ec9fafb8f463aa2776f1e45c216dc60f7490df1875c133bb962ffcceab050
В результате получается тот же образ, что и раньше, но с этим новым Dockerfile вам не нужно вручную компилировать приложение перед созданием образа. Многоступенчатая сборка делает все за вас повторяемым и последовательным образом.
Go собирает приложения в отдельные двоичные файлы; вы можете создать их статически связанными, а также можете создавать образы, у которых нет других файлов или зависимостей. Эти крошечные образы оптимизированы для передачи данных и более безопасны, поскольку содержат только двоичный файл вашего приложения.
Чтобы создать такой образ, вы будете использовать многоэтапный Dockerfile. Поэтому скопируйте файл containers/Dockerfile.builder
в новый файл containers/Dockerfile.scratch
и отредактируйте этот новый файл, заменив изображение второго этапа в команде FROM нулями. В этом образе нет каталогов или пользователей, поэтому замените оставшиеся команды на команду для копирования двоичного файла в корневой каталог. Когда вы закончите, ваш Dockerfile будет выглядеть так:
раздача/контейнеры/Dockerfile.scratch
FROM golang:1.15 AS builder RUN mkdir /distributing WORKDIR /distributing COPY notify/ notify/ COPY pomo/ pomo/ WORKDIR /distributing/pomo RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -tags=containers FROM scratch WORKDIR / COPY --from=builder /distributing/pomo/pomo . CMD ["/pomo"]
Создайте свой образ, используя этот Dockerfile, как вы делали это раньше:
$ docker build -t pomo/pomo:latest -f containers/Dockerfile.scratch . STEP 1: FROM golang:1.15 AS builder STEP 2: RUN mkdir /distributing --> 9021735fd16 ... TRUNCATED OUTPUT ... STEP 8: FROM scratch STEP 9: WORKDIR / --> 00b6e665a3f STEP 10: COPY --from=builder /distributing/pomo/pomo . --> c6bbaccb87b STEP 11: CMD ["/pomo"] STEP 12: COMMIT pomo/pomo:latest --> 4068859c281
Проверьте свое новое изображение и обратите внимание, что его размер близок к размеру двоичного файла, потому что это единственный файл в образе:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE localhost/pomo/pomo latest 4068859c281e 5 seconds ago 8.34 MB
Не все приложения являются хорошими кандидатами для запуска в контейнере, но для тех, которые есть, это еще один вариант распространения вашего приложения для ваших пользователей.
Далее, давайте рассмотрим, как распространять ваше приложение с исходным кодом.
Мы надеемся, что вам понравился этот отрывок из книги Мощные приложения командной строки в Go Рикардо Херарди. Вы можете продолжить чтение на Medium или приобрести электронную книгу напрямую у Pragmatic Programmers.