PUSH в GIT как средство доставки в прод, или как сэкономить время на развертывании и обновлении сервисов

Программисты любят программировать. Но если вы — программист, и результат вашего творения делается не «в стол», рано или поздно наступит момент, когда нужно показать его миру: заказчику, пользователям, инвесторам, etc. Хорошо, когда вы работаете в компании, где есть целый отдел (или хотя бы отдельный специально обученный человек), который может развернуть ваше приложение, где вы скажете и как вы скажете. Однако не все компании могут себе такое позволить. А уж если вы фрилансер или это ваш пет-проект, развертывание приложения точно ляжет на вас.

О чем вам нужно позаботиться перед развертыванием? Арендовать сервер, настроить его, зарегистрировать доменное имя, получить SSL-сертификат, подумать о доставке обновлений.

Чтобы предметно рассмотреть процесс развертывания, напишем небольшой API-сервис TODO-заметок на языке программирования Python с использованием микрофреймворка Flask и развернем его разными способами.

Планирование

Каждая заметка будет определяться следующим образом:

{
  "text": "Купить молоко",
  "done": true
}

Все заметки будут храниться массивом в файле формата JSON.

Определим API следующим образом.

GET /todo получает список всех TODO.
GET /todo/ получает TODO с заданным id (индексом в массиве).
POST /todo добавляет новую TODO в конец списка.
PUT /todo/ заменяет 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/")
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/", 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», если до этого вы вообще ни разу ничего не развертывали) и скопируете конфигурационные файлы из первой статьи, которую найдете. Так как об этом написано множество статей (например, эта), пробежимся по этому процессу в общих чертах:

  1. Арендовать виртуальный сервер. Столкнуться с тем, что там Linux. Почитать статьи, посмотреть видео на ютубе, запомнить основные команды.

  2. Установить nginx.

  3. Установить pip и сопутствующие пакеты.

  4. Установить git.

  5. Склонировать репозиторий с кодом приложения на сервере.

  6. Создать виртуальное окружение python (опционально).

  7. Установить зависимости из requirements.txt

  8. Добавить виртуальный хост в nginx:

    1. Зарегистрировать доменное имя.

    2. Написать конфиг в /etc/nginx/sites-available (возможно, узнать как вообще пишутся конфиги виртуальных хостов для nginx и что такое upstream?).

    3. Сделать симлинк на написанный конфиг в /etc/nginx/sites-enabled.

    4. Перезапустить nginx.

  9. Столкнуться с тем, что браузер считает наш сервис небезопасным, установить certbot и сгенерировать SSL-сертификат.

  10. Почувствовать радость от победы, но в будущем обнаружить, что при перезагрузке сервера приложение не перезапускается.

  11. Научиться писать 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 и нажмите кнопку «Создать». Укажите параметры как на изображении:

71cf39fa61b90e2740b75276cfa624be.png

Перейдем на страницу проекта: https://cloud.amvera.ru/projects/todo-app. Там нас интересуют команды для привязки проекта к существующему репозиторию:

git remote add amvera https://git.amvera.ru/<имя-пользователя>/todo-app
git push amvera master

После этого начнется сборка и развертывание проекта. Дождитесь появления статуса «Успешно развернуто»:

8ce750c73db17cc28b6e9c67222bfdee.png

Проверка работоспособности

Снова воспользуемся Postman для отправки запросов.

2a68769f8dc4e55477e20185a9224aaa.png9cc21144b55cfa97569d0f96f40dca14.png

Для проверки сохранности данных после перезагрузки, перезапустим сервис:

ac293b0c592165b3fb240a3a1f130407.png

После завершения перезапуска отправим запрос Постманом для проверки наличия TODO заметок. Если они есть, значит мы все сделали правильно.

Также нам не пришлось задумываться об SSL-сертификатах. Они были автоматически выписаны для нашего приложения при первом развертывании.

Поздравляем, вы успешно создали свое первое приложение на Amvera!

В чем преимущества такого подхода

  1. Экономия времени на первом развертывании. При ручном развертывании вам нужно было разобраться в нескольких технологиях (Linux, nginx, systemd). В случае с Amvera, это всего один Dockerfile.

  2. При последующем обновлении все еще проще. Потребуется только запушить обновления в Git через командную строку или любимое GUI приложение, и все!

  3. Вы автоматизируете процесс доставки и развертывания кода ваших сервисов. Это совсем другой пользовательский опыт, где вам не нужно отвлекаться на посторонние от разработки вещи.

  4. Легче масштабироваться. У вас увеличилась нагрузка? Просто создайте копию контейнера, а балансировщик сам распределит нагрузку.

P/S/ Будем рады всем желающим поучаствовать в бета-тесте нашей облачной платформы Amvera, который мы проводим с октября по декабрь 2022 г. В сервисе вы сможете опробовать описанный в статье подход. Это бесплатно для участников. Для участия нужно просто зарегистрироваться, после чего на счет поступит 1 т.р. Если их не хватит — напишите нам, и мы руками добавим столько вычислительного ресурса, сколько требуется для вашего проекта. Будем рады, если потом дадите обратную связь и поможете нам сделать действительно полезный и удобный сервис!

© Habrahabr.ru