Веб разработка

Как развернуть Go-приложение на сервере


6 апреля 2019

В этом посте я поделюсь с вами, как можно легко и быстро развернуть Go-приложение на сервере.

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

Также вы (как и я; читай предыдущий пост О хостинге) решили арендовать собственный сервер на базе Linux и разворачивать свой продукт именно там. Если вы пользуетесь облачными сервисами, то этот пост будет вам неактуален. У каждого облачного провайдера есть детальные инструкции, как "повесить" своё приложение в облако.

Интересный факт: времени на разворачивание сайта на Google App Engine я потратила куда больше, чем на своём сервере. И это никак не связано с приобретённым опытом! На Google Cloud я столкнулась с большим количеством багов (со стороны GAE). Я много "гуглила", что же означают постоянно выскакивающие ошибки, подгоняла всю структуру под требования Google, а один раз пришлось даже удалить папку с зависимыми библиотеками, так как в команде Google баг ещё не исправили, а на эту папку ругалась среда разработки. Спасибо за опыт, так сказать. Однако, на своём же сервере ты властелин и имеешь бОльшую свободу!

Поиски в интернете детальной инструкции, которая бы описывала весь процесс от и до (с пояснением всех (!) технических особенностей для новичков), не увенчались успехом. Пришлось собирать по крупицам. Так и появилась мысль опубликовать пост, чтобы облегчить жизнь начинающим программистам. Выполняйте последовательно следующие пункты, и совсем скоро вы увидите ваше творение во всемирной паутине.

1. Создайте бинарный файл под операционную систему вашего сервера

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

Например, дома я работаю на компьютере с операционной системой MacOS X, что является моей средой разработки. Если я просто создам бинарный файл моего приложения по умолчанию на домашнем компьютере, отправлю его на свой Linux сервер (у меня стоит дистрибутив Ubuntu) и попробую его запустить, то ничего не выйдет: сразу выскачет ошибка, что не удалось запустить исполняемый файл. Дело в том, что операционные системы очень разные по всем параметрам: от структуры файловой системы до программного обеспечения. Исполняемый файл - это набор инструкций для конкретной операционной системы (сходи туда, сделай то). Если вам дать карту Парижа, когда вы находитесь в Москве, то боюсь, что добраться до места назначения вам не удаться. Так же и с бинарниками.

Подробно о кросс-компиляции Go с практическими примерами можно почитать здесь.

Следовательно, для сборки приложения вам нужно запустить следующую команду с соответствующими значениями переменных GOOS и GOARCH (пример для моего приложения):

$ env GOOS=linux GOARCH=amd64 go build -o blog_new

После запуска данной команды в корне вашей рабочей директории появляется бинарный файл для операционной системы Linux. Пропробуйте его запустить:

$ ./blog_new

Если ваш компьютер не на Linux, вы увидите:

$ -bash: ./blog_new: cannot execute binary file

Также можете ознакомиться с полезной статьей на данную тему Готовим сборку Go-приложения в продакш.

2. Отправляйте бинарный файл и все сопутствующие файлы на сервер при помощи sftp (Ssh File Transfer Protocol)

Под сопутствующими файлами я подразумеваю html темплейты, css и javascript файлы, фотографии, картинки и svg-изображения. Иными словами, всё, что у вас вероятно лежит в папках /templates и /public.

Для начала создайте на сервере рабочую директорию для вашего приложения. Обычно на Linux принято создавать директории для коммерческих приложений в каталоге /opt (optional).

$ mkdir /opt/blog_project

Дальше со своего компьютера необходимо подключиться к серверу, используя протокол sftp (подключаетесь как обычно через ssh, только вместо команды shh, используете sftp). Рекомендую пользоваться именно этим протоколом, так как все данные шифруются при отправке, в отличии, например, от протокола ftp. При отправке файлов через ftp любые данные становятся уязвимыми для перехвата и чтения.

Подключились? Теперь можете начинать копировать файлы. Команды, которые вам пригодятся:

  1. pwd - адрес директории, где вы сейчас находитесь удалённо. Present working directory.
  2. lpwd - адрес директории, где вы сейчас находитесь локально
  3. lcd <имя директории> - меняет директорию на локальном компьютере
  4. cd <имя директории> - меняет директорию на удалённом сервере
  5. mput -r <имя директории> - копирует все файлы с локального компьютера на сервер, включая содержимое всех входящих директорий
  6. put <имя файла> - копирует один файл с локального компьютера на сервер
  7. get <имя файла> - копирует один файл с сервера на локальный компьютер

Прежде чем вы начнёте переносить все нужные файлы, убедитесь, что после подключения через sftp вы находитесь в нужных вам директориях как локально, так и удалённо.

3. Установите на сервере базу данных, если в этом есть необходимость

Мой блог завязан на NoSQL базу данных MongoDB. Все посты и комментарии, которые вы видите на сайте, тянутся именно из базы данных. При подписке на обновления моего блога ваши данные записываются в базу данных подписчиков. Для установки базы на сервер я пользовалась этой инструкцией на официальном сайте. Вопросов по установке не возникло.

Однако, не забудьте включить автоматический запуск MongoDB при старте вашего сервера (например, после перезагрузки):

$ sudo systemctl enable mongod

4. Создайте сервис на сервере

В Linux есть замечательная утилита systemd, которая позволяет создавать, запускать и настраивать сервисы. Наше скомпилированное в бинарный файл Go-приложение мы запустим как systemd сервис на сервере.

Для этого нам необходимо создать файл с расширением *.service с набором инструкций. Каждый сервис в контексте утилиты systemd также называется юнитом. Все юнит-файлы расположены в директории /lib/systemd/system. Следовательно, создаём в этой директории файл с названием вашего сервиса и следующим (минимальным!) набором инструкций:

$ sudo nano /lib/systemd/system/blog.service

Расскажу на примере своего приложения.

ConditionPathExist= проверяет наличие исполняемого файла.

Requires= и After= определяют зависимости. Если подобная зависимость не является обязательной, вместо этих строк укажите Wants= и After= соответственно. Обратите внимание, что Wants= и Requires= не подразумевают After=, что означает, что если After= не определено, то два юнита будут запущены параллельно друг другу. Environment= создает переменные окружения с соответствующими значениями. Mongod.service - юнит моей базы данных, без которого мой сайт не может работать. After=network.target просто проверяет, что система управления сетевыми подключениями активна и работает.

Restart=on-failure, RestartSec=10, StartLimitInterval=60 отвечают за параметры автоматического перезапуска приложения. В моём случае, приложение будет перезапускаться, если упало с ошибкой, через каждые 10 секунд. Если в течение 60 секунд оно упадёт более 5 раз (так как значение DefaultStartLimitBurst=5 в файле /etc/systemd/system.config), то больше приложение запускаться автоматически не будет. StartLimitInterval=60 и StartLimitBurst= идут в паре. Я отдельно последнюю команду не указывала, меня устраивает значение по умолчанию. У меня в плане поиграться этими значениями и посмотреть, как они работают на практике.

WorkingDirectory= содержит адрес вашей рабочей директории (папка, где лежат все файлы вашего приложения). ExecStart= содержит адрес исполняемого файла.

Например, если я удалю строчку с WorkingDirectory=, то бинарный файл у меня запустится, приложение будет "работать", однако, никакого влияния css, javascript файлов и картинок вы не увидите.

WantedBy=multi-user.target устанавливает запуск при обычной загрузке компьютера.

Если хотиться углубиться, отличный туториал по systemd.

Официальная документация systemd.

5. Hастройте HTTP-сервер и прокси

Я выбрала nginx. Честно? Потому что сразу не разобралась как настроить прокси на apache 😄 Ngnix, например, уже длительное время обслуживает серверы многих высоконагруженных российских сайтов, таких как Яндекс, Mail.Ru, ВКонтакте и Рамблер (источник: nginx.com).

Моё Go-приложение слушает порт :8080 (как обычно для web-приложений). Поэтому у меня появилась необходимость, чтобы стандартные запросы c браузера на мой сервер перенаправлялись на порт :8080. Этим и занимается прокси-сервер.

У меня на сервере автоматически работал Apache2. Перед тем, как устанавливать ngix, надо apache "убить".

$ sudo systemctl stop apache2
$ sudo systemctl disable apache2

Теперь можно установить nginx:

$ sudo apt-get update
$ sudo apt-get install nginx

Смотрим статус:

$ systemctl status nginx

Если при установке сервер не запустился, запускаем самостоятельно:

$ sudo systemctl start nginx
$ sudo systemctl enable nginx

Теперь надо все запросы по нашему IP-адресу перенаправить на порт :8080. Для этого в следующем конфигурационном файле (пример с моего сервера):

$ sudo nano /etc/nginx/sites-available/default

добавляем в location переменную proxy_pass:

server {
	listen 80 default_server;
	listen [::]:80 default_server;
	root /var/www/html;
	server_name _;
	location / {
	proxy_pass http://127.0.0.1:8080;\
	}
}

Перезагружаем nginx:

$ sudo systemctl reload nginx

Подробнее: статья на DigitalOcean.

6. Настройте DNS

Если вы ещё не купили доменное имя и не настроили DNS (Domain Name System) для вашего сервера, то сейчас самое время это сделать. У каждого провайдера имеются инструкции, как это быстро сделать.

7. Запускайте сервис

Перед запуском убедитесь, что все ваши зависимые сервисы активны:

$ sudo systemctl status mongod

Итак! Запускаем!

$ sudo systemctl daemon-reload
$ sudo systemctl start blog

Команду sudo systemctl daemon-reload всегда запускайте, когда вносите изменения в юнит файлы, а затем systemctl restart <название юнита>, чтобы перезапустить приложение.

Не забываем включить автоматический запуск сервиса при старте системы:

$ sudo systemctl enable blog

Логи вашего приложения записываются в файл /var/log/<имя сервиса>/<имя сервиса>.log:

$ sudo cat /var/log/blog/blog.log

Переходим в браузере на страницу с вашим доменным именем, и вуаля! Всё должно заработать!

Очень жду ваших комментариев и обратную связь.

Источники: Running a Go binary as a systemd service on Ubuntu 16.04

Отдельная благодарность Алексею Мичурину за консультирование по особо сложным вопросам!

Ссылка на картинку с гофером.

By Maria Efimenko

6 Комментариев

Комментарии
  1. Мария

    Очень подробно и хорошо раскрыта тема! Умница,горжусь! (Правда я не поняла ничего в силу неосведомленности, но это круто!)))

    06.05.2019 12:42:47
  2. Мария Ефименко

    marialife.com

    Машенька, спасибо большое!

    06.05.2019 15:14:06
  3. Максим

    Мария, а зачем нужен был вообще прокси-сервер? Кластеризация?

    09.01.2020 14:08:26
  4. Мария Ефименко

    marialife.com

    Максим, если бы кластеризация :) Я таких слов тогда, к сожалению, не знала. Всё очень просто: мне нужно было сделать так, чтобы запрос на marialife.com автоматически уходил на порт 8080. Пока я на сделала эту настройку, сайт открывался лишь только на marialife.com:8080. Надо будет разобраться, как можно сделать по-другому. Если есть советы, буду очень признательна :)

    09.01.2020 21:45:29
  5. Марат

    Привет! На Google Cloud можно развернуть сайт не используя Google App Engine, а прямо там же с systemd сервисом и базой данных. Можно узнать твое мнение по поводу Buffalo фреймворка, там есть комманда buffalo build. Она пакует все assets (js, css, img файлы, далее - "статика") в один бинарник с Go программой. В высоконагруженных сервисах рекоммендуется для раздачи статики использовать отдельный сервер, но Buffalo формирует кэш для статики. А еще nginx используют для раздачи статики. Какой подход тебе больше нравится?

    2021-02-05T18:21:55+03:00
  6. Мария Ефименко

    https://marialife.com

    Привет, Марат! Думаю, что так или иначе Google Cloud будет дороже. По крайней мере когда у меня всё-таки получилось задеплоить через App Engine, съедало кредита ооочень много. Buffalo фреймворк выглядит интересным! Конечно, прежде чем сформировать мнение, стоит попробовать для начала :) В учебном проекте была цель не использовать никаких фрейморков, чтобы разобраться во всех тонкостях, как всё работает. По поводу статики. В идеале конечно (не для высоконагруженных приложений) делать фронт отдельным сервисом, который бы ходил через API на бекенд. Такой фронт билдится и кладётся в один контейнер с сервисом с API. API раздает по корневому урлу всю статику. У нас на работе такой подход к админкам. Бек и фронт живут в одном репозитории, но это всё-таки два разных сервиса. Мне нравится больше именно такой подход. Чтобы nginx раздавал статику не слышала, но мне кажется, что так не стоит делать, даже если можно :) Сейчас у меня в блоге каша, но так как это был учебный проект, то сойдёт. Надеюсь, что когда у меня будет побольше времени, я перепишу полностью код, запакую всё в докер :) Прямо руки чешутся :)

    2021-02-13T14:49:53+03:00

Оставить комментарий: