Веб разработка
Как развернуть 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 любые данные становятся уязвимыми для перехвата и чтения.
Подключились? Теперь можете начинать копировать файлы. Команды, которые вам пригодятся:
pwd
- адрес директории, где вы сейчас находитесь удалённо. Present working directory.lpwd
- адрес директории, где вы сейчас находитесь локальноlcd <имя директории>
- меняет директорию на локальном компьютереcd <имя директории>
- меняет директорию на удалённом сервереmput -r <имя директории>
- копирует все файлы с локального компьютера на сервер, включая содержимое всех входящих директорийput <имя файла>
- копирует один файл с локального компьютера на сервер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
Отдельная благодарность Алексею Мичурину за консультирование по особо сложным вопросам!
Ссылка на картинку с гофером.
Мария
Очень подробно и хорошо раскрыта тема! Умница,горжусь! (Правда я не поняла ничего в силу неосведомленности, но это круто!)))
Мария Ефименко
marialife.comМашенька, спасибо большое!
Максим
Мария, а зачем нужен был вообще прокси-сервер? Кластеризация?
Мария Ефименко
marialife.comМаксим, если бы кластеризация :) Я таких слов тогда, к сожалению, не знала. Всё очень просто: мне нужно было сделать так, чтобы запрос на marialife.com автоматически уходил на порт 8080. Пока я на сделала эту настройку, сайт открывался лишь только на marialife.com:8080. Надо будет разобраться, как можно сделать по-другому. Если есть советы, буду очень признательна :)
Марат
Привет! На Google Cloud можно развернуть сайт не используя Google App Engine, а прямо там же с systemd сервисом и базой данных. Можно узнать твое мнение по поводу Buffalo фреймворка, там есть комманда buffalo build. Она пакует все assets (js, css, img файлы, далее - "статика") в один бинарник с Go программой. В высоконагруженных сервисах рекоммендуется для раздачи статики использовать отдельный сервер, но Buffalo формирует кэш для статики. А еще nginx используют для раздачи статики. Какой подход тебе больше нравится?
Мария Ефименко
https://marialife.comПривет, Марат! Думаю, что так или иначе Google Cloud будет дороже. По крайней мере когда у меня всё-таки получилось задеплоить через App Engine, съедало кредита ооочень много. Buffalo фреймворк выглядит интересным! Конечно, прежде чем сформировать мнение, стоит попробовать для начала :) В учебном проекте была цель не использовать никаких фрейморков, чтобы разобраться во всех тонкостях, как всё работает. По поводу статики. В идеале конечно (не для высоконагруженных приложений) делать фронт отдельным сервисом, который бы ходил через API на бекенд. Такой фронт билдится и кладётся в один контейнер с сервисом с API. API раздает по корневому урлу всю статику. У нас на работе такой подход к админкам. Бек и фронт живут в одном репозитории, но это всё-таки два разных сервиса. Мне нравится больше именно такой подход. Чтобы nginx раздавал статику не слышала, но мне кажется, что так не стоит делать, даже если можно :) Сейчас у меня в блоге каша, но так как это был учебный проект, то сойдёт. Надеюсь, что когда у меня будет побольше времени, я перепишу полностью код, запакую всё в докер :) Прямо руки чешутся :)