Развертывание веб-приложения Go на Docker и Nginx в Ubuntu 18.04

Docker – самое распространенное программное обеспечение для контейнеризации на сегодняшний день. Эта платформа позволяет разработчикам быстро пакетировать приложения вместе с необходимой средой, благодаря чему ускоряется итерация и повышается продуктивность ресурса. При этом каждый запуск выполняется в требуемой среде. Docker Compose – это инструмент для управления контейнерами, который отвечает современным требованиям приложений. Он позволяет запускать несколько взаимосвязанных контейнеров одновременно. В отличие от ручного запуска, инструменты оркестровки дают разработчикам возможность одновременно контролировать, масштабировать и расширять контейнер.

Преимуществами использования Nginx в качестве фронтэнд веб-сервера являются его производительность, простота настройки и TLS-терминация. nginx-proxy – это автоматизированная система для контейнеров Docker, которая значительно упрощает процесс настройки Nginx в качестве обратного прокси-сервера. Аддон Let’s Encrypt можно использовать вместе с прокси-сервером nginx для автоматизации создания и обновления сертификатов для проксированных контейнеров.

В этом мануале вы научитесь развертывать простое приложение Go. В качестве маршрутизатора запросов используется gorilla/mux, а в качестве веб-сервера – Nginx. Все это будет помещено в контейнеры Docker, организованные с помощью Docker Compose. В качестве обратного прокси используется nginx-proxy

с аддоном Let’s Encrypt. В конце этого мануала вы с помощью Docker развернете веб-приложение Go, доступное по нескольким маршрутам и защищенное сертификатами Let’s Encrypt.

Требования

1: Создание простого веб-приложения Go

На данном этапе нужно подготовить рабочее пространство и создать простое приложение Go, которое позже вы сможете контейнеризовать. Это приложение будет использовать гибкий и быстрый маршрутизатор запросов gorilla/mux.

Войдите как 8host:

ssh 8host@your_server_ip

В этом мануале данные будут храниться в каталоге ~/go-docker. Создайте его:

mkdir ~/go-docker

Перейдите в этот каталог:

cd ~/go-docker

Код приложения Go будет храниться в файле по имени main.go. Создайте его:

nano main.go

Добавьте в него эти строки:

package main
import (
"fmt"
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>This is the homepage. Try /hello and /hello/8host\n</h1>")
})
r.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "<h1>Hello from Docker!\n</h1>")
})
r.HandleFunc("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
title := vars["name"]
fmt.Fprintf(w, "<h1>Hello, %s!\n</h1>", title)
})
http.ListenAndServe(":80", r)
}

Сначала нужно импортировать пакеты net/http и gorilla/mux, которые обеспечивают работу HTTP-сервера и маршрутизацию.

Пакет gorilla/mux реализует более простой и мощный маршрутизатор и диспетчер запросов, и в то же время поддерживает совместимость интерфейса со стандартным маршрутизатором. Здесь создается новый маршрутизатор mux и сохраняется в переменной r. Затем код определяет три маршрута: /, /hello, и /hello/{name}. Первый слеш – адрес домашней страницы. Второй адрес (/hello) выдает посетителю приветствие. А третий маршрут (/hello/{name}) принимает имя в качестве параметра и показывает приветственное сообщение с этим именем.

В конце файла находится код для запуска HTTP-сервера с помощью http.ListenAndServe, который будет прослушивать порт 80, используя настроенный маршрутизатор.

Сохраните и закройте файл.

Перед запуском приложения Go сначала необходимо скомпилировать и упаковать его для работы внутри контейнера Docker. Go – это компилируемый язык, поэтому перед запуском программы компилятор переводит программный код в исполняемый машинный код.

Итак, вы настроили свое рабочее пространство и создали простое веб-приложение Go. Теперь нужно развернуть nginx-proxy с автоматизированным получением сертификата Let’s Encrypt.

2: Развертывание nginx-proxy с модулем Let’s Encrypt

Приложение важно заранее защитить с помощью HTTPS. Для этого нужно развернуть nginx-proxy через Docker Compose вместе с модулем от Let’s Encrypt. Эта связка обеспечивает безопасность проксироованных контейнеров Docker и обеспечивает защиту приложения по HTTPS, автоматически обрабатывая создание и обновление TLS-сертификатов.

Хранить конфигурацию Docker Compose для nginx-proxy нужно в файле nginx-proxy-compose.yaml. Создайте его:

nano nginx-proxy-compose.yaml

Поместите в файл следующее:

version: '2'
services:
nginx-proxy:
restart: always
image: jwilder/nginx-proxy
ports:
- "80:80"
- "443:443"
volumes:
- "/etc/nginx/vhost.d"
- "/usr/share/nginx/html"
- "/var/run/docker.sock:/tmp/docker.sock:ro"
- "/etc/nginx/certs"
letsencrypt-nginx-proxy-companion:
restart: always
image: jrcs/letsencrypt-nginx-proxy-companion
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
volumes_from:
- "nginx-proxy"

Этот код определяет два контейнера: один для nginx-proxy и второй для модуля Let’s Encrypt (letsencrypt-nginx-proxy-companion). Для прокси мы указываем образ jwilder/nginx-proxy, открываем и связываем порты HTTP и HTTPS и, наконец, указываем тома, которые будут доступны контейнеру для сохранения данных, связанных с Nginx.

Во втором блоке мы определяем образ для конфигурации модуля Let’s Encrypt. Затем настраивается доступ к сокету Docker и определяется том, а также существующие тома из контейнера прокси, которые можно наследовать. Для обоих контейнеров свойство restart имеет значение always, благодаря чему Docker будет всегда поддерживать и перезапускать их (в случае сбоя или перезагрузки системы).

Сохраните и закройте файл.

Разверните nginx-proxy:

docker-compose -f nginx-proxy-compose.yaml up -d

Docker Compose принимает пользовательский именованный файл через флаг -f. Команда up запускает контейнеры, а флаг -d переводит их в фоновый режим.

Окончательный код будет выглядеть так:

Creating network "go-docker_default" with the default driver
Pulling nginx-proxy (jwilder/nginx-proxy:)...
latest: Pulling from jwilder/nginx-proxy
a5a6f2f73cd8: Pull complete
2343eb083a4e: Pull complete
...
Digest: sha256:619f390f49c62ece1f21dfa162fa5748e6ada15742e034fb86127e6f443b40bd
Status: Downloaded newer image for jwilder/nginx-proxy:latest
Pulling letsencrypt-nginx-proxy-companion (jrcs/letsencrypt-nginx-proxy-companion:)...
latest: Pulling from jrcs/letsencrypt-nginx-proxy-companion
...
Creating go-docker_nginx-proxy_1 ... done
Creating go-docker_letsencrypt-nginx-proxy-companion_1 ... done

3: Докеризация веб-приложения Go

На этом этапе вы создадите Dockerfile, содержащий инструкции о том, как Docker будет создавать постоянный образ для вашего веб-приложения Go. Docker создает постоянный образ приложения, похожий на снимок контейнера, используя инструкции из файла Dockerfile. Постоянство, неизменяемость образа гарантируют, что при каждом запуске контейнера на основе конкретного образа получается одно и то же окружение.

Создайте Dockerfile:

nano Dockerfile

Поместите в него такой код:

FROM golang:alpine AS build
RUN apk --no-cache add gcc g++ make git
WORKDIR /go/src/app
COPY . .
RUN go get ./...
RUN GOOS=linux go build -ldflags="-s -w" -o ./bin/web-app ./main.go
FROM alpine:3.9
RUN apk --no-cache add ca-certificates
WORKDIR /usr/bin
COPY --from=build /go/src/app/bin /go/bin
EXPOSE 80
ENTRYPOINT /go/bin/web-app --port 80

Этот Dockerfile состоит из двух частей. На первом этапе используется база golang:alpine, которая содержит предустановленную версию Go на Alpine Linux.

Затем устанавливается gcc, g++, make и git – необходимые инструменты для компиляции приложения Go. Рабочий каталог  находится в /go/src/app, он указан в GOPATH по умолчанию. Также файл копирует содержимое текущего каталога в контейнер. Первый этап файла завершается рекурсивной выборкой пакетов из кода и компиляцией файла main.go (для релиза без символов и отладочной информации). Это делается путем передачи -ldflags=»-s -w».при компиляции программы Go сохраняется отдельная часть двоичного файла, которая будет использоваться для отладки, однако эта дополнительная информация занимает память и ее не нужно сохранять при развертывании в производственной среде.

Второй этап основан на alpine:3.9 (Alpine Linux 3.9). Он устанавливает сертификаты доверенного ЦС, копирует скомпилированные двоичные файлы приложения с первого этапа в текущий образ, открывает порт 80 и устанавливает двоичный файл приложения в качестве точки входа в образ.

Сохраните и закройте файл.

Вы создали Dockerfile для своего приложения Go, который будет извлекать пакеты, компилировать их для релиза и запускать приложение при создании контейнера. Теперь нужно будет создать yaml-файл Docker Compose и протестировать приложение, запустив его в Docker.

4: Создание и запуск файла Docker Compose

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

Хранить конфигурацию Docker Compose для веб-приложения Go нужно в файле go-app-compose.yaml. Создайте его:

nano go-app-compose.yaml

Добавьте следующие строки в этот файл:

version: '2'
services:
go-web-app:
restart: always
build:
dockerfile: Dockerfile
context: .
environment:
- VIRTUAL_HOST=example.com
- LETSENCRYPT_HOST=example.com

Не забудьте заменить example.com своим доменом. Сохраните и закройте файл.

Эта конфигурация Docker Compose содержит один контейнер (go-web-app), который будет вашим веб-приложением Go. Файл собирает приложение, используя Dockerfile, который вы создали на предыдущем этапе, и принимает в качестве контекста для сборки текущий каталог, содержащий исходный код. Кроме того, он устанавливает две переменные среды: VIRTUAL_HOST и LETSENCRYPT_HOST. nginx-proxy использует VIRTUAL_HOST, чтобы узнать, с какого домена принимать запросы. Переменная LETSENCRYPT_HOST указывает имя домена для генерации сертификатов TLS (оно должно совпадать с VIRTUAL_HOST, если домен указан без подстановочных знаков).

Теперь запустите веб-приложение Go в фоновом режиме с помощью Docker Compose:

docker-compose -f go-app-compose.yaml up -d

Окончательный результат будет выглядеть следующим образом:

Creating network "go-docker_default" with the default driver
Building go-web-app
Step 1/12 : FROM golang:alpine AS build
---> b97a72b8e97d
...
Successfully tagged go-docker_go-web-app:latest
WARNING: Image for service go-web-app was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.
Creating go-docker_go-web-app_1 ... done

Если вы просмотрите вывод, представленный после выполнения команды, вы увидите, что Docker регистрирует каждый шаг сборки образа приложения в соответствии с конфигурацией в Dockerfile.

Теперь вы можете перейти на https://example.com/, чтобы увидеть свою домашнюю страницу. По домашнему адресу вашего веб-приложения (маршрут /) появится страница:

This is the homepage. Try /hello and /hello/8host

Теперь откройте адрес https://example.com/hello. Вы должны увидеть:

Hello from Docker!

Затем попробуйте https://example.com/hello/8host. На экране появится:

Hello, 8host!

Примечание: В случае ошибки о недействительных сертификатах TLS подождите несколько минут, пока модуль Let’s Encrypt создаст сертификаты. Если через некоторое время вы все еще получаете ошибку, убедитесь, что вы ввели правильные команды и конфигурации.

Итак, вы создали файл Docker Compose и написали конфигурацию для запуска приложения Go внутри контейнера.

Заключение

В этом мануале вы развернули веб-приложение Go с помощью Docker и Nginx на сервере Ubuntu 18.04. Обслуживание приложений с помощью Docker становится проще, потому что среда, в которой работает приложение, гарантированно будет одинаковой при каждом запуске. Пакет gorilla/mux имеет отличную документацию и предлагает более сложные функции (именование маршрутов и обслуживание статических файлов). Чтобы узнать больше о модуле HTTP-сервера, посетите официальную документацию.

Tags: , , , , , ,