
Создание модуля Go
В Go 1.13 были введены модули. Это означает, что больше не нужно размещать все проекты в одно рабочее пространство Go.
Для начала создаем новый каталог go-docker, в котором будут храниться все файлы.
Затем инициализируем репозиторий Git и создаем модуль Go.
git init git remote add origin [email protected]:Dirk94/go-docker.git go mod init github.com/dirk94/go-docker
В директории находится файл go.mod, который содержит все зависимости этого модуля, аналогично файлу package.json в разработке Node.
Создание API
Модуль установлен, пришло время для создания API.
Мы будем использовать пакет маршрутизации thegorilla/mux для API.
go get -u github.com/gorilla/mux
После выполнения команда добавится в качестве зависимости в файл go.mod.
Затем создаем основной файл Go commands/runserver.go.
package main import ( "fmt" "github.com/gorilla/mux" "net/http" ) func main() { r := mux.NewRouter() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to this life-changing API.") }) fmt.Println("Server listening!") http.ListenAndServe(":80", r) }
Все, что делает API — это возвращает сообщение “Welcome to this life-changing API”.
Проверим, работает ли программа перед тем, как перейти к контейнерам Docker. Для запуска сервера используем команду go run.
go run commands/runserver.go Server listening!

API работает! 🎉
Установка Docker
Начнем с создания образа Docker для этого проекта, который содержит набор инструкций, сообщающих Docker, какую среду нужно создать.
FROM golang:latest WORKDIR /app COPY ./ /app RUN go mod download ENTRYPOINT go run commands/runserver.go
Используем образ golang:latest в качестве основы для этого нового пользовательского образа.
Копируем весь проект в директорию образа /app, а затем загружаем зависимости с помощью go mod download.
И, наконец, сообщаем Docker о запуске команды go run commands/runserver.go.
Для создания этого образа запускаем следующую команду:
docker build -t go-docker-image .
Теперь у нас есть инструкции по созданию образа Docker, которые необходимо запустить.
docker run go-docker-image Server listening!
Сервер выполняет прослушивание в контейнере Docker, однако при переходе на localhost в браузере появляется сообщение об ошибке “Refused to connect”.

Происходит следующее: контейнер Docker прослушивает порт 80 на предмет входящих запросов, а операционная система хоста — нет. Таким образом, при отправке запроса GET на localhost он не находит работающий сервер.
Ниже представлена диаграмма с описанием проблемы:

Чтобы это исправить, нужно сопоставить порт 80 контейнеров с портом 80 хоста.
docker run -p 80:80 go-docker-image
Диаграмма теперь будет выглядеть следующим образом:

Теперь при переходе на localhost появляется сообщение “Welcome to this life-changing API”!
Изменение исходного кода
Нам необходимо внести некоторые изменения в API.
package main import ( "fmt" "github.com/gorilla/mux" "net/http" ) func main() { r := mux.NewRouter() r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Welcome to this life-changing API.nIts the best API, its true, all other API's are fake.") }) fmt.Println("Server listening!") http.ListenAndServe(":80", r) }
Добавляем новую строку в API. Попробуем запустить новый контейнер Docker.
docker run -p 80:80 go-docker-image
Но при переходе на localhost мы видим то же самое сообщение.

Причина заключается в том, что образ Docker не изменился. Чтобы изменения вступили в силу, нам нужно перестроить образ.
docker build -t go-docker-image . docker run -p 80:80 go-docker-image
Теперь появляется обновленное сообщение:

Установка горячей перезагрузки
Перестройка образа Docker после каждого изменения исходного кода отнимает слишком много времени, поэтому мы создадим более удобную систему.
Мы будем использовать пакет Compile Daemon, который автоматически перестроит и перезапустит приложение Go при изменении любого из исходных файлов Go.
FROM golang:latest WORKDIR /app COPY ./ /app RUN go mod download RUN go get github.com/githubnemo/CompileDaemon ENTRYPOINT CompileDaemon --build="go build commands/runserver.go" --command=./runserver
Обновляем Dockerfile, чтобы загрузить пакет CompileDaemon.
Затем изменяем точку входа, чтобы запустить программу CompileDaemon. Указываем команду сборки и запуска программы, которые выполняются при каждом изменении файла Go.
Образ перестраивается при запуске:
docker build -t go-docker-image .
При запуске Docker добавляем флаг -v ~/projects/go-docker:/app. Он устанавливает директорию хоста go-docker в директорию /app контейнера Docker.
При каждом изменении в директории go-docker файлы в директории контейнера /app также изменяются.
Последняя команда представлена ниже. Обратите внимание, что путь -v не может быть относительным.
docker run -v ~/projects/go-docker:/app -p 80:80 go-docker-image
Во время запуска контейнера внесите изменения в исходный код, и вы увидите, что он автоматически обновляется. 🔥🚀
Использование Docker compose
Сейчас нам приходится писать очень длинную команду docker run -v ~/projects/go-docker:/app -p 80:80 go-docker-image для запуска контейнера.
Эта задача выполнима в подобном проекте, поскольку он содержит только один контейнер. Но предположим, что в проекте есть несколько контейнеров, которые нужно запустить. Выполнение всех команд docker run отнимет очень много сил.
Решение — Docker Compose. С помощью этого инструмента можно указать, какие контейнеры нужно запускать при запуске команды docker-compose.
Для установки создаем файл docker-compose.yml.
version: "3" services: go-docker-image: build: ./ ports: - '80:80' volumes: - ./:/app
Здесь указываем, что нужно создать образ go-docker-image. Образ должен быть собран с использованием Dockerfile в директории ./.
Сопоставляем порты и устанавливаем громкость — на этот раз можно использовать относительный путь.
Для запуска контейнеров, указанных в файле docker-compose.yml, запускаем docker-compose up.
Вот и все! Мы создали рабочий API в Docker, который автоматически перезагружается при изменении файлов! 🙌
Исходный код можно просмотреть здесь — https://github.com/dirk94/go-docker
Специально для сайта ITWORLD.UZ. Новость взята с сайта NOP::Nuances of programming