Программисты любят программировать. Но если вы – программист, и результат вашего творения делается не “в стол”, рано или поздно наступит момент, когда нужно показать его миру: заказчику, пользователям, инвесторам, etc. Хорошо, когда вы работаете в компании, где есть целый отдел (или хотя бы отдельный специально обученный человек), который может развернуть ваше приложение, где вы скажете и как вы скажете. Однако не все компании могут себе такое позволить. А уж если вы фрилансер или это ваш пет-проект, развертывание приложения точно ляжет на вас.
О чем вам нужно позаботиться перед развертыванием? Арендовать сервер, настроить его, зарегистрировать доменное имя, получить SSL-сертификат, подумать о доставке обновлений.
Чтобы предметно рассмотреть процесс развертывания, напишем небольшой API-сервис TODO-заметок на языке программирования Python с использованием микрофреймворка Flask и развернем его разными способами.
Планирование
Каждая заметка будет определяться следующим образом:
{
"text": "Купить молоко",
"done": true
}
Все заметки будут храниться массивом в файле формата JSON.
Определим API следующим образом.
GET /todo получает список всех TODO.
GET /todo/<id> получает TODO с заданным id (индексом в массиве).
POST /todo добавляет новую TODO в конец списка.
PUT /todo/<id> заменяет TODO с заданным id.
Реализация
Для начала напишем само приложение.
Так как наше приложение использует Flask, создадим файл requirements.txt:
Flask==2.2.2
Flask-CORS==3.0.10
Напишем код приложения, работающий локально, в файле app.py
:
import json
from flask import Flask, request, abort
from flask_cors import CORS
FILENAME = "todo.json"
def get_data():
try:
with open(FILENAME, "r", encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
def save_data(data):
with open(FILENAME, "w", encoding="utf-8") as f:
json.dump(data, f)
app = Flask(__name__)
cors = CORS(app)
@app.route("/")
def index():
return "TODO App"
@app.route("/todo")
def get_all_todo():
return get_data()
@app.route("/todo/<int:id>")
def get_single_todo(id):
data = get_data()
if id < 0 or id >= len(data):
abort(404)
return data[id]
@app.route("/todo", methods=["POST"])
def add_new_todo():
new_todo = request.json
if new_todo is None:
abort(400)
data = get_data()
data.append(new_todo)
save_data(data)
return "OK", 201
@app.route("/todo/<int:id>", methods=["PUT"])
def update_todo(id):
data = get_data()
if id < 0 or id >= len(data):
abort(404)
updated_todo = request.json
if updated_todo is None:
abort(400)
data[id] = updated_todo
save_data(data)
return "OK"
if __name__ == "__main__":
app.run(port=8080)
Установим зависимости:
pip install -r requirements.txt
Запустим приложение:
python app.py
Убедимся в его работоспособности при помощи Postman
Визуализация Postman
Развертывание вручную
Если развертывание приложений не является для вас чем-то регулярным, вы вряд ли на память помните все шаги, да и шаблонов нужных конфигурационных файлов у вас (еще) нет. В этом случае вы скорее всего загуглите что-то вроде “nginx gunicorn flask” (или даже “flask production”, если до этого вы вообще ни разу ничего не развертывали) и скопируете конфигурационные файлы из первой статьи, которую найдете. Так как об этом написано множество статей (например, эта), пробежимся по этому процессу в общих чертах:
Арендовать виртуальный сервер. Столкнуться с тем, что там Linux. Почитать статьи, посмотреть видео на ютубе, запомнить основные команды.
Установить nginx.
Установить pip и сопутствующие пакеты.
Установить git.
Склонировать репозиторий с кодом приложения на сервере.
Создать виртуальное окружение python (опционально).
Установить зависимости из requirements.txt
Добавить виртуальный хост в nginx:
Зарегистрировать доменное имя.
Написать конфиг в /etc/nginx/sites-available (возможно, узнать как вообще пишутся конфиги виртуальных хостов для nginx и что такое upstream?).
Сделать симлинк на написанный конфиг в /etc/nginx/sites-enabled.
Перезапустить nginx.
Столкнуться с тем, что браузер считает наш сервис небезопасным, установить certbot и сгенерировать SSL-сертификат.
Почувствовать радость от победы, но в будущем обнаружить, что при перезагрузке сервера приложение не перезапускается.
Научиться писать unit-файлы для systemd (+ узнать о существовании systemd).
У автора на этот процесс уходит примерно час-полтора, если не учитывать борьбу с 502 Bad Gateway в nginx из-за опечатки в конфиге.
Что можно улучшить?
Если развертывать нужно часто и однотипные приложения, то в какой-то момент вы начнете задумываться об автоматизации этого процесса: разберетесь с Ansible, начнете использовать CI/CD решения. Это безусловно будет для вас полезно, но, может быть, можно проще?
В таких сервисах, как Amvera или Heroku, вам уже предоставляется репозиторий с кодом, куда достаточно сделать git push для развертывания. Рассмотрим развертывание приложения в Amvera.
Так как в Amvera приложения исполняются в контейнерах, напишем Dockerfile для нашего приложения.
FROM python:3.10-slim-buster
WORKDIR /app
ENV PORT=80
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENTRYPOINT gunicorn --bind 0.0.0.0:$PORT app:app
Так как для развертывания нашего приложения мы используем gunicorn, добавим его в requirements.txt:
Flask==2.2.2
Flask-CORS==3.0.10
gunicorn==20.1.0
В отличие от виртуальных машин для сохранности данных их нужно помещать в папку /data.
Добавим import os в начало файла app.py, а также изменим объявление переменной FILENAME:
FILENAME = "/data/todo.json" if "AMVERA" in os.environ else "todo.json"
Теперь начало файла app.py выглядит так:
import os
import json
from flask import Flask, request, abort
from flask_cors import CORS
FILENAME = "/data/todo.json" if "AMVERA" in os.environ else "todo.json"
def get_data():
# ... остаток файла
Инициализируем репозиторий git:
git init
Добавим созданные нами файлы в индекс:
git add app.py requirements.txt amvera.yml
Зафиксируем изменения:
git commit -m "TODO App"
Создание проекта в Amvera
Теперь нужно создать проект в Amvera. Откроем страницу https://cloud.amvera.ru/projects и нажмите кнопку "Создать". Укажите параметры как на изображении:
Перейдем на страницу проекта: https://cloud.amvera.ru/projects/todo-app. Там нас интересуют команды для привязки проекта к существующему репозиторию:
git remote add amvera https://git.amvera.ru/<имя-пользователя>/todo-app
git push amvera master
После этого начнется сборка и развертывание проекта. Дождитесь появления статуса "Успешно развернуто":
Проверка работоспособности
Снова воспользуемся Postman для отправки запросов.
Для проверки сохранности данных после перезагрузки, перезапустим сервис:
После завершения перезапуска отправим запрос Постманом для проверки наличия TODO заметок. Если они есть, значит мы все сделали правильно.
Также нам не пришлось задумываться об SSL-сертификатах. Они были автоматически выписаны для нашего приложения при первом развертывании.
Поздравляем, вы успешно создали свое первое приложение на Amvera!
В чем преимущества такого подхода
Экономия времени на первом развертывании. При ручном развертывании вам нужно было разобраться в нескольких технологиях (Linux, nginx, systemd). В случае с Amvera, это всего один Dockerfile.
При последующем обновлении все еще проще. Потребуется только запушить обновления в Git через командную строку или любимое GUI приложение, и все!
Вы автоматизируете процесс доставки и развертывания кода ваших сервисов. Это совсем другой пользовательский опыт, где вам не нужно отвлекаться на посторонние от разработки вещи.
Легче масштабироваться. У вас увеличилась нагрузка? Просто создайте копию контейнера, а балансировщик сам распределит нагрузку.
P/S/ Будем рады всем желающим поучаствовать в бета-тесте нашей облачной платформы Amvera, который мы проводим с октября по декабрь 2022 г. В сервисе вы сможете опробовать описанный в статье подход. Это бесплатно для участников. Для участия нужно просто зарегистрироваться, после чего на счет поступит 1 т.р. Если их не хватит – напишите нам, и мы руками добавим столько вычислительного ресурса, сколько требуется для вашего проекта. Будем рады, если потом дадите обратную связь и поможете нам сделать действительно полезный и удобный сервис!