Вы написали программу (микросервис) на Go, и хотите развернуть ее в контейнере Docker?
В этой небольшой заметке я постараюсь максимально компактно и практично описать этот процесс для новичков.

Про Docker в двух словах скажу так — он позволяет запустить контейнер с виртуальной операционной системой и вашим приложением, размещенным в ней. А так же, когда надоест, удалить напрочь этот контейнер, не заботясь о том, что Ваше приложение где-то тайком намусорило в системе.
Вот, собственно, и все преимущества от использования контейнеров docker для новичков :)

В данной заметке я сначала рассматриваю способ компиляции Вашей программы внутри образа, на этапе его сборки. Это самый очевидный способ для начинающих. Затем рассматривается создание образа и запуск контейнера из заранее скомпилированного файла программы.

Еще пару слов о том, с чем docker работает. Он хранит у себя образы (image) контейнеров, на основе которых Вы можете запустить сколько угодно экземпляров самих этих контейнеров и настроить им маршрутизацию по сети (мэппинг входящих портов и т.п). Образы собираются на основе других образов (можно сказать, есть наследование образов), которые есть в публичном доступе в репозитории docker. Для программы на Go лучшим готовым выбором будут образы golang:latest (на базе debian) и golang:alpine (на базе alpine linux). Другие названия образов можно посмотреть тут: hub.docker.com/_/golang

Сборка Вашей программы из исходников внутри образа Docker


Этот вариант используем, например, тогда, когда ваша основная ось — windows или macos, а вы собираете контейнер для linux. Чтобы не пересобирать компилятор go для других платформ на своей машине, просто компилируем исходники в целевом Docker-образе.
Чтобы собрать образ (image), нужно в папке исходников своей программы сделать текстовый файл Dockerfile, без расширения, с таким содержимым:

#имя базового образа
FROM golang:latest

#создаем папку, где будет наша программа
RUN mkdir -p /go/src/app

#идем в папку
WORKDIR /go/src/app

#копируем все файлы из текущего пути к файлу Docker на вашей системе в нашу новую папку образа
COPY . /go/src/app

#скачиваем зависимые пакеты через скрипт, любезно разработанный командой docker
RUN go-wrapper download

#инсталлируем все пакеты и вашу программу
RUN go-wrapper install

#запускаем вашу программу через тот же скрипт, чтобы не зависеть от ее скомпилированного имени
#-web - это параметр, передаваемый вашей программе при запуске, таких параметров может быть сколько угодно
#go-wrapper запускает set -x для того, чтобы отправить в stderr имя бинарника Вашей программы в момент ее запуска 
CMD ["go-wrapper", "run", "-web"]

#пробрасываем порт вашей программы наружу образа
EXPOSE 8000

Особенностью этого файла для Go является наличие в базовом образе golang специального скрипта go-wrapper, который облегчает работу с компилированием, позволяя не думать о путях и именах файлов.
В папке с этим файлом и вашими исходниками запускаем команду:
docker build -t my-golang-app .

Точка в конце — это текущая директория с файлом Docker, не забываем ее указывать.
my-golang-app — это будет название образа, в котором скомпилируется ваша программа.
После выполнения этой команды можно посмотреть наличие успешно собранного образа командой
docker images
docker ps -a

Создать и запустить контейнер на базе собранного образа можно так:
docker run -d --rm --name my-running-app my-golang-app

my-running-app — это будет имя создаваемого контейнера на основе образа my-golang-app.
Еще тут можно использовать ключи:
-d | --detach - запускает контейнер в фоновом режиме и выводит только id свежесозданного контейнера. (по умолчанию == false)
-i | --interactive - запускает контейнер в интерактивном режиме (оставляет STDIN открытым, даже если контейнер запущен в неприкрепленном режиме)
-t | --tty - запускает псевдотерминал, часто используется с -i
-p | --publish=[] - пробрасывает порты контейнера в хост. Формат: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
-e | --env=[] - пробрасывает переменные окружения внутрь контейнера.
-v | --volume=[] - пробрасывает директорию файловой системы внутрь контейнера

Подробнее по ключам запуска docker и составу файла Dockerfile смотрите тут и тут.

Запуск скомпилированного бинарного файла программы в контейнере Docker


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

Создаем и запускаем контейнер с бинарником:
#!/bin/bash
#для сборки CGO зависимых приложений под alpine
docker run --rm -v "$PWD":/go/src/myapp -w /go/src/myapp golang:alpine /bin/sh -c "apk add --update gcc musl-dev; go build -ldflags \"-s -w\" -a -o myapp_bin_name"

Альтернативный вариант — пакуем его в контейнер:
# -*- Dockerfile -*-
FROM alpine
COPY myapp_bin_name /app/

RUN apk add --update tzdata openssl musl-dev ca-certificates && rm -rf /var/cache/apk/* && cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime && echo "Europe/Moscow" > /etc/timezone && chmod +x /app/myapp_bin_name && mkdir -p /data

WORKDIR /app
VOLUME /data
CMD /app/myapp_bin_name
EXPOSE 8085

Создаем образ:

docker build -t mymegapp .

Запускаем:
docker run -d --rm --name my-running-app my-golang-app
Поделиться с друзьями
-->

Комментарии (16)


  1. Shtucer
    29.06.2017 13:38

    Настройка файла настройки?
    По приведенной в статье ссылке на Докер хаб гораздо больше интересного, чем просто названия образов. Да чего уж, и вся статья там в один из тамошних параграфов уместилась....


    1. pfihr
      29.06.2017 15:26

      там в общем про любые контейнеры, но не конкретно про go.
      для go есть особенность, например, скрипт в сборке, который сам подбирает и компилирует все зависимости.


      1. Shtucer
        29.06.2017 16:47

        Эммм, вы это серьёзно? Сверим ссылки, в статье вот такая: https://hub.docker.com/_/golang/


        1. pfihr
          29.06.2017 18:57
          +1

          да, там все что надо, но не по русски ;)


  1. alexesDev
    29.06.2017 15:25
    +2

    Вот только нужно создавать два контейнера. Один собирает приложение, другой чисто запускает. Скомпилированное приложение не нуждается в той куче мусора, что ставится для компиляции.


    1. Alexeyco
      29.06.2017 15:59

      А можно делать как делаю я с imagick. Качается весь паровозик для сборки, потом сборка приложения, потом грохаем из образа весь мусор, включая git. По максимуму. Заодно хотя бы узнаю, насколько неправильно я делаю.


      1. Caravus
        29.06.2017 16:36
        +1

        Теперь есть multi-stage builds


        1. grossws
          29.06.2017 18:27

          А docker hub его уже умеет? Когда его анонсировали на dockercon17, то я не нашел никакой информации об этом.


          upd: имелось ввиду умеет ли docker hub в automated builds использовать multi-stage


          1. Caravus
            29.06.2017 18:29

            Не в курсе, не пользуюсь. Релизнули уже давно (несколько недель), так что должно всё быть ок, я думаю.


            1. grossws
              29.06.2017 18:34

              Ну, анонсировали вообще в апреле (18-20 числа, когда шёл DockerCon), как и moby project. Они его в CE stable занесли или только в 17.05 и 17.06 CE edge?


              1. Caravus
                29.06.2017 18:43

                Я не в курсе :)


              1. Alexeyco
                29.06.2017 21:25

                И как тут за всем успеть


          1. shcoderAlex
            30.06.2017 04:53

            До сих пор не умеет.

            Error parsing reference: «node:alpine as builder» is not a valid repository/tag: invalid reference format


  1. subvillion
    30.06.2017 05:52

    Чета хрень в посте

    1й командой создаем бинарник:

    #!/bin/bash
    
    #для сборки CGO зависимых приложений под alpine
    docker run --rm -v "$PWD":/go/src/myapp -w /go/src/myapp golang:alpine /bin/sh -c "apk add --update gcc musl-dev; go build -ldflags \"-s -w\" -a -o myapp_bin_name"
    


    2й пакуем его в почти любой контейнер:

    # -*- Dockerfile -*-

    FROM alpine
    COPY myapp_bin_name /app/

    RUN apk add --update tzdata openssl musl-dev ca-certificates && \
    rm -rf /var/cache/apk/* && \
    cp /usr/share/zoneinfo/Europe/Moscow /etc/localtime && \
    echo "Europe/Moscow" > /etc/timezone && \
    chmod +x /app/myapp_bin_name && \
    mkdir -p /data

    WORKDIR /app
    VOLUME /data
    EXPOSE 8085

    CMD /app/myapp_bin_name


    docker build -t mymegapp .
    


    Если CGO нет — еще половину можно выкинуть. Ну и вендоринг тут нужен само собой, но это уже не про контейнеры.


    1. Caravus
      30.06.2017 08:14
      -1

      Не хорошо под рутом запускать софт, даже в контейнере. Also, use UTC, Luke.


    1. pfihr
      30.06.2017 08:45
      +1

      Про таймзону — спасибо, хороший пример.
      Статья не про сборку бинарника в пакет, а про сборку из исходников. Добавлю Ваш пример во вторую часть, про сборку из бинарника.