Компилиране на вашето Go приложение за контейнери
Откъс от Мощни приложения за команден ред в Go от Рикардо Герарди
📘 Ако харесвате този откъс, моля, обмислете „купуването на книгата“ или присъединяването към „разговора в DevTalk“ (или и двете).
Друг алтернативен начин за разпространение на вашето приложение, който става все по-популярен през последните години, е да позволите на вашите потребители да стартират приложението в Linux контейнери. Контейнерите пакетират вашето приложение и всички необходими зависимости, като използват стандартен формат на изображението и изпълняват приложението изолирано от други процеси, изпълнявани на същата система. Контейнерите използват ресурси на ядрото на Linux като пространства от имена и Cgroups, за да осигурят изолация и управление на ресурсите.
Има различни налични времена за изпълнение на контейнери, като 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. Създайте приложението, като използвате файловете, посочени с маркера на контейнера, за да премахнете зависимостта от 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
Забележете, че размерът на файла е около 7MB и файлът е статично свързан и премахнат.
Сравнете това със създаването на приложението без тези опции:
$ 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. Превключете обратно към основната директория на главата и създайте нови контейнери на поддиректория:
$ cd $HOME/pragprog.com/rggo/distributing $ mkdir containers
Създайте и редактирайте файл, наречен Dockerfile в тази поддиректория. Добавете следното съдържание, за да създадете ново изображение от базовото изображение alpine:latest, създайте обикновен потребител pomo, за да стартирате приложението, и копирайте двоичния файл pomo/pomo, който сте създали преди, в изображението в директория /app:
разпространение/контейнери/Dockerfile
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
, за да активирате терминален емулатор, който е необходим за стартиране на интерактивния CLI на 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 с scratch. Това изображение няма директории или потребители, така че заменете останалите команди с команда за копиране на двоичния файл в основната директория. Когато сте готови, вашият 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.