Настройка Dockerfile для создания и запуска контейнера Docker с вашей программой на Go

    Вы написали программу (микросервис) на 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
    Поделиться публикацией

    Похожие публикации

    Комментарии 18

      0

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

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

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

            +1

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

        +2
        Вот только нужно создавать два контейнера. Один собирает приложение, другой чисто запускает. Скомпилированное приложение не нуждается в той куче мусора, что ставится для компиляции.
          0
          А можно делать как делаю я с imagick. Качается весь паровозик для сборки, потом сборка приложения, потом грохаем из образа весь мусор, включая git. По максимуму. Заодно хотя бы узнаю, насколько неправильно я делаю.
            +1
            Теперь есть multi-stage builds
              0

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


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

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

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

                    0
                    Я не в курсе :)
                      0
                      И как тут за всем успеть
                    0
                    До сих пор не умеет.
                    Error parsing reference: «node:alpine as builder» is not a valid repository/tag: invalid reference format
              0
              Чета хрень в посте

              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
                Не хорошо под рутом запускать софт, даже в контейнере. Also, use UTC, Luke.
                  +1

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

                    0
                    Вторая статья в итоге будет?
                  0
                  Я так и не понял статью — хоть бы какое-то пояснение. У меня например даже вот с такими настройками dockerfile создается образ

                  # -*- Dockerfile -*-
                  FROM golang:latest
                  EXPOSE 8080

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                  Самое читаемое