image

Привет, Хабр! Сегодня поговорим об одном интересном микро-фреймворке для Python — Flask. Мы создадим свое собственное веб-приложение и изучим расширения flask, а после задеплоим его на сервер, чтобы иметь доступ из внешнего мира.

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

Мы затронем все моменты, я объясняю каждую строчку кода. Мы будем создавать не просто какой то статичный сайт — а открытую публичную стену, с регистрацией и авторизацией. Каждый может туда зайти, авторизоваться и оставлять посты на общедоступной стене.

А самое главное — безболезненный, быстрый и легкий деплой будущего приложения.

Наш проект я назвал Open Wall. Оригинально, но вы можете назвать как хотите, главное название поменяйте.

Чтобы понимать, о чем мы будем разговаривать, вам требуется знать сам язык python, язык разметки HTML и CSS. Без этих базовых знаний вы мало что поймете.

По моему концепту, Open Wall будет состоять из следующего функционала:

  • Регистрация пользователя
  • Авторизация пользователя
  • Создание постов на общей стене
  • Профили пользователей

Open Wall будет иметь базовый функционал. Если проект станет популярным — может быть, я улучшу его, и сделаю новый туториал на тему улучшения. Например, добавление flask-admin.

Если вы хотите сразу опробовать его — перейдите на сам сайт или в его репозиторий.

За дизайн сайта не ругайте, вы вольны его изменить как хотите, я быстро сделал легкий, адаптивный сайт.

Итак, допустим вы хотите сделать проект с самого начала. Тогда следуйте дальнейшим шагам для создания базового виртуального окружения. Виртуальное окружение позволит изолировать ваш проект от остальных. Все команды будут указаны для Linux.

python3 -m venv venv && source venv/bin/activate && pip3 install flask gunicorn flask_login flask_sqlalchemy && pip3 freeze > requirements.txt

Этой командой мы: а) создали виртуальное окружение и активировали его; б) установили зависимости — сам flask и gunicorn для запуска его на сервере; в) «заморозили» зависимости в файле, чтобы после можно было их установить на сервер.

После создаем базовую архитектуру:

  • Создайте директорию templates, там будут храниться шаблоны.
  • Создайте директорию static/css. static — это специальная директория для хранения статичных файлов, например style.css в папке css.
  • Создайте главный файл приложения — app.py, где мы будем писать код.

Итак, давайте напишем само приложение:

from flask import Flask, render_template

app = Flask(__name__)


@app.route('/')
def index():
	return render_template('index.html')


if __name__ == '__main__':
	app.run(debug=True)

Давайте разберем каждую строчку:

  • from flask import Flask, render_template. Этой командой мы импортировали приложение Flask и функцию для рендера Jinja2-шаблона. О шаблонах поговорим позже.
  • app = Flask(__name__). Создаем приложение.
  • app.route('/'). Это декоратор для рендера пути сайта — в данном случае это корень, /. Дальше идет функция, где мы возвращаем шаблон.
  • if __name__ == '__main__'. Это специальная строка, которая позволяет выполнять код, если скрипт исполняется напрямую (не импортируется).
  • app.run(debug=True). Запускаем приложение с включенным дебагом. Также можно задать host, port.

Шаблоны


В Flask используется шаблонизатор Jinja. Это быстрая программа, которая позволяет создавать HTML-шаблоны. Вот как выглядит базовый шаблон base.html:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
</head>
<body>
	{% block content %}
	{% endblock %}
</body>
</html>

Здесь вы можете увидеть фигурные скобки с процентами — они используются если нужны какие-то функции, например цикл for:

{% for item in items %}
	{{ item }}
{% endfor %}

Также есть просто двойные фигурные скобки — они нужны, например, для переменных.

База данных и авторизация


Flask — это микрофреймворк, поэтому база данных может быть любая, даже просто встроенный модуль sqlite3. Но мы будем использовать специальное расширение — Flask SQLAlchemy. Это ORM модуль, с возможностью подключения различных БД — от sqlite до MySQL. Мы будем использовать sqlite.

Вот пример модели пользователя для БД:

from datetime import datetime
from flask import Flask, render_template, url_for, request, redirect, flash, get_flashed_messages
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, current_user, login_user, logout_user, login_required
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy import desc

app = Flask(__name__)
login = LoginManager(app)
login.login_view = 'login'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///wall.db'
app.config['SECRET_KEY'] = 'секретный ключ'
db = SQLAlchemy(app)


@login.user_loader
def load_user(id):
	return db.session.get(User, int(id))


@app.route('/logout')
def logout():
    logout_user()
    return redirect(url_for('index'))


class Post(db.Model):
	__tablename__ = 'posts'
	id = db.Column(db.Integer(), primary_key=True)
	title = db.Column(db.String(255), nullable=False)
	content = db.Column(db.Text(), nullable=False)
	created_on = db.Column(db.DateTime(), default=datetime.utcnow)
	author = db.Column(db.Integer(), nullable=False)

	def __repr__(self):
		return "<{}:{}>".format(self.id,  self.title[:10])


class User(UserMixin, db.Model):
	id = db.Column(db.Integer, primary_key=True)
	username = db.Column(db.String(63), index=True, unique=True)
	password_hash = db.Column(db.String(127))

	def set_password(self, password):
		self.password_hash = generate_password_hash(password)

	def check_password(self, password):
		return check_password_hash(self.password_hash, password)

	def __repr__(self):
		return '<{}:{}>'.format(self.id, self.username[:10])

Давайте разберем код подробнее:

  • Мы импортируем модуль datetime — для взаимодействия с датой и временем в БД.
  • Мы импортируем из модуля Flask некоторые классы и функции — Flask, render_template, url_for (создание URL для сайта), request — получаем информацию о запросе, нужно при создании форм, redirect — редирект на URL на сайте, flash — создание быстрых flash-сообщений (например о том, что пароль при входе в аккаунт неверный), get_flashed_messages — получаем flash-сообщения, чаще всего используется в шаблоне.
  • Импортируем из модуля flask_login классы и функции для входа и регистрации. Flask Login — это расширение, которое упрощает разработчику создание методов входа и регистрации. Мы импортируем LoginManager — менеджер входами, UserMixin — специальный класс, который мы наследуем для модели пользователя, чтобы можно было работать с текущим пользователем, current_user — при помощи его мы сможем использовать данные текущего пользователя, например его ID, или просто проверить, вошел ли юзер в свой аккаунт. login_user позволяет авторизовать пользователя, logout_user — функция выхода из аккаунта и login_required — декоратор для создания страниц, для которых требуется логин.
  • Следующий шаг — импорт модуля Werkzeug, а точнее двух функций — generate_password_hash и check_password_hash. В любых БД пароли хранят в хешированном виде, а иногда даже с солью — случайным набором букв, ибо если взломают БД, то злоумышленники могут увидеть пароли. А хешированные пароли тяжело взломать — особенно если используются новые безопасные алгоритмы. Можно конечно найти коллизию — это когда хеш одной фразы и хеш другой совпадают, но фразы разные. Но найти коллизии очень трудно, практически невыполнимо.
  • И последний импорт — это desc из sqlalchemy. Простая функция, которая нам нужна, когда мы будем сортировать посты по времени создания.

После идет создание экземпляров классов и настройка:

  • app = Flask(__name__). Здесь мы создаем веб-приложение
  • login = LoginManager(app). LoginManager позволяет нам управлять логинами, осуществлять авторизацию и работать с текущим пользователем
  • Дальше мы задаем для менеджера авторизации, куда пересылать пользователя когда он будет заходить на страницу, которая доступна только авторизованным пользователем. О пути login мы поговорим ниже
  • Следующая строка задает параметр SQLALCHEMY_DATABASE_URI — это путь до базы данных
  • После мы также конфигурируем приложение, и задаем секретный ключ. Без этого ключа приложение не будет работать.
  • И в конце мы вызываем экземпляр класса SQLAlchemy.

Теперь давайте создадим некоторые нужные нам функции — это загрузка пользователя и выход из аккаунта:

  • Первая функция — load_user, с декоратором login.user_loader. Это функция загружает пользователя из БД по его ID
  • Вторая функция — logout. Это функция, которая вызывает функцию logout_user (выход из аккаунта) из flask_login, и после создает редирект на главную страницу.

Настала очередь создать две модели для базы данных — это модель поста:

  • Мы создаем класс Post, наследуя его от базового класса db.Model.
  • Мы задаем название таблицы, в нашем случае — posts.
  • Мы записываем id — это будет целочисленное значение, которое будет автоматически задаваться, то есть будет проходить инкрементирование значения.
  • Далее — title, заголовок. Это будет строковое значение с максимальной длиной 255 символов. Он должен быть обязательно (nullable).
  • После мы задаем параметр content — контента статьи. Он также обязательно должен быть (параметр nullable).
  • Предпоследнее значение — дата создания поста, дефолтное значение — это дата и время по UTC, когда был создан пост.
  • И последнее значение — это author, куда мы указываем ID автора (то есть ID текущего пользователя, current_user.id).
  • Осталась только магическая функция __repr__. Она позволяет преобразовывать произвольные объекты в строковое значение.

И модель пользователя:

  • Кроме наследования db.Model, мы наследуем специальный класс UserMixin, чтобы работать с Flask Login.
  • Также, как и в модели поста мы создаем id, который автоматически увеличивается
  • Дальше мы задаем имя пользователя, максимальная длина — 63.
  • И в конце создание колонки хеша пароля — с максимальной длиной, нет, не 127, а 128 символов.
  • Теперь нам требуется создать две вспомогательные функции — set_password и check_password. Первая преобразует пароль в хеш, а вторая проверяет пароль, который подан на аргументы и пароль из бд. Здесь мы как раз и задействуем модуль werkzeug.
  • И мы также создаем магическую функцию __repr__, как и в модели пользователя.

Вот и первая часть кода позади. Мы создали уже основу, скелет приложения, но не хватает главного — самого функционала сайта, взаимодействия с БД.

Создание путей отображения


Итак, начнем c функции index. Главная функция, отображает корневой каталог сайта.

@app.route('/')
@app.route('/index')
def index():
	posts = Post.query.order_by(desc(Post.created_on)).all()

	return render_template('index.html', posts=posts)

В функции index мы задаем два пути отображения — / и /index. После мы создаем список постов, обращаемся к модели Post, и требуем выдать все посты, отсортированные по дате создания. После мы делаем рендер шаблона, и передаем список постов аргументов, чтобы потом использовать его.

Давайте рассмотрим сам шаблон index.html и base.html. Сами шаблоны хранятся в директории templates. base.html — это базовый шаблон, от него наследуются уже все остальные. В нем мы задаем форму и создаем блоки. В общем, вот как выглядит он у меня:

<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Open Wall</title>
	<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
</head>
<body>
	
	<div class="wrapper">
		<div class="header">
			<h1><a href="/" style='color: white; text-decoration: none;'>Open Wall - открытая публичная стена</a></h1>
			<a href="https://github.com/AlexeevDeveloper/openwall">Репозиторий</a>
			<a href="https://habr.com/ru/companies/timeweb/articles/812413/">Статья</a>
			{% if not current_user.is_authenticated %}
				<a href="/login">Войти</a>
				<a href="/reg">Регистрация</a>
			{% else %}
				<a href="/new">Создать пост</a>
			{% endif %}
		</div>

		<div class="content">
			{% block content %}
			{% endblock %}
		</div>

		<div class="footer">
			Все что останется здесь - останется навсегда. Добро пожаловать на Open Wall!
		</div>
	</div>

</body>
</html>

Думаю, вы знаете HTML, но некоторые моменты надо прояснить. Как мы уже говорили, фигурные скобки с процентами — для функций шаблонизатора Jinja, а просто двойные фигурные скобки — для переменных.

Итак, в head-блоке, в элементе link, где мы задаем CSS стили, можно увидеть строку {{ url_for('static', filename='css/style.css') }}. Чтобы нам вручную не вводить путь, мы задаем путь через url_for (функция из Flask). Здесь мы задаем путь до стилей, лежат они в директории static. Вторым аргументом мы задаем путь до файла — css/style.css.

Если вы хотите увидеть стили (я не стал сюда их вставлять, ибо это заняло бы слишком много места) — то перейдите по ссылке.

Ниже мы видим конструкцию типа if-else, то есть условный оператор. Первой строкой мы узнаем, текущий пользователь авторизован или нет. Если нет, то мы выводим ссылки на вход и регистрацию, а иначе — одну ссылку на создание поста. В последней строке мы даем шаблонизатору понять, что конструкция закончилась.

И последнее — это блок контента. Чтобы мы могли наследовать и задавать контент, мы создадим блок, и в других шаблонах мы можем туда вписывать что нам нужно.

Теперь рассмотрим файл index.html:

{% extends 'base.html' %}

{% block content %}
	{% for post in posts %}
			<div class="posts">
				<div class="post">
					<div class="header">
						<h2>{{ post.title }}</h2>
					</div>
						{{ post.content }}
					<hr>
					<a href="/profile/{{ post.author }}">Автор: {{ post.author }}
					Дата: {{ post.created_on }}
				</div>
			</div>
	{% endfor %}
{% endblock %}

  • Командой extends 'base.html' мы говорим приложению наследовать наш базовый шаблон.
  • После мы редактируем блок content — добавляем в него список постов.
  • Дальше, в самом блоке контента, мы создаем цикл, и проходимся по всем элементам в списке posts (который мы передали в функции) и выводим: заголовок, контент, ссылку на профиль автора (рассмотрим позже) и дату создания поста.
  • В конце мы обозначаем, что завершили цикл и блок.



Теперь займемся функцией регистрации:

@app.route('/reg', methods=['GET', 'POST'])
def reg():
	if current_user.is_authenticated:
		return redirect(url_for('index'))

	if request.method == 'POST':
		username = request.form['username']
		password = request.form['password']
		password2 = request.form['password2']

		if password != password2:
			flash('Пароли не совпадают')
			return render_template('reg.html')

		if len(username) > 63:
			flash('Длина имени пользователя не должна быть больше 63 символов')
			return render_template('reg.html')

		try:
			user = User(username=username)
			user.set_password(password)
			db.session.add(user)
			db.session.commit()
		except Exception as e:
			flash(f'Ошибка при создании пользователя: {e}')
			return render_template('reg.html')
		else:
			login_user(user)
			return redirect('index')

	return render_template('reg.html')

Мы задаем путь отображения — это /reg. Также мы добавляем в декораторе параметр METHODS, который определяет принимаемые запросы — GET и POST.

После мы проверяем, авторизован ли пользователь. Если да, то отправляем его на главную страницу.

Следующий проверяет метод запроса — если это POST, то есть отправка данных, то мы начинаем регистрацию. Мы получаем username, пароль и повторение пароля из формы, проверяем пароли, проверяем длину имени пользователя и после мы создаем нового пользователя, автоматически его авторизуя в системе. При успешной регистрации перенаправляем на главную страницу, а при провале или при GET-запросе — рендерим шаблон reg.html:

{% extends 'base.html' %}

{% block content %}
	<h2>Регистрация</h2>

	<form method="POST">

		{% for msg in get_flashed_messages() %}
			<div class="error">
				{{ msg }}
			</div>
		{% endfor %}

		Введите имя пользователя:
		<input type="text" name='username' placeholder="Имя пользователя"><br>
		Введите пароль:
		<input type="password" name='password' placeholder="Пароль"><br>
		Повторите пароль:
		<input type="password" name='password2' placeholder="Повторите пароль"><br>
		<br>
		<input type="submit" class='btn'>
		<br>
	</form>
{% endblock %}

В это шаблоне мы также используем функцию get_flashed_messages для отображения flash-сообщений.





Следующим шагом мы создадим функцию логина пользователя:

@app.route('/login', methods=['GET', 'POST'])
def login():
	if current_user.is_authenticated:
		return redirect(url_for('index'))
	
	if request.method == 'POST':
		user = db.session.query(User).filter(User.username == request.form['username']).first()

		if user is not None:
			if not user.check_password(request.form['password']):
				flash('Пароль неверный')
				return render_template('login.html')

			login_user(user)
			return redirect(url_for('index'))
		else:
			flash('Пользователя не существует. Проверьте имя пользователя или пароль.')

	return render_template('login.html')

Также мы задаем типы методов, и также проверяем авторизован ли уже пользователь.

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

Шаблон страницы логина:

{% extends 'base.html' %}

{% block content %}
	<h2>Вход в аккаунт</h2>

	<form method="POST">

		{% for msg in get_flashed_messages() %}
			<div class="error">
				{{ msg }}
			</div>
		{% endfor %}

		Введите имя пользователя:
		<input type="text" name='username' placeholder="Имя пользователя"><br>
		Введите пароль:
		<input type="password" name='password' placeholder="Пароль"><br>
		<br>
		<input type="submit" class='btn'>
		<br>
	</form>
{% endblock %}

Все практически также, как и при регистрации. Разве что без повторного ввода пароля.



Теперь давайте немного отдохнем, напишем несложную функцию отображения профиля:

@app.route('/profile/<username>')
def profile(username: str):
	user = db.session.query(User).filter(User.username == username.first()

	if user is None:
		return redirect('index')

	posts = db.session.query(Post).filter(Post.author == username).order_by(desc(Post.created_on)).all()

	return render_template('profile.html', username=username, posts=posts)

В пути вы могли увидеть после второго слэша слово username. Это специальный аргумент, который будет означать произвольное слово после /profile/. В данном случае — пользователя. Этот аргумент также есть в функции.

Также, как и при логине мы проверяем, существует ли пользователь. Если нет, то делаем редирект на главную страницу. Если пользователь существует, мы собираем все посты данного пользователя и сортируем по дате, и после передаем username и posts в функцию render_template. А вот сам шаблон профиля:

{% extends 'base.html' %}

{% block content %}
	<h2>Пользователь: {{ username }}</h2>
	{% if current_user.username == username %}
		<a href="/logout">Выйти из аккаунта</a>
	{% endif %}
	<h3 style='text-align: center;'>Посты</h3>
	{% for post in posts %}
		{% if post.author == username %}
			<div class="posts">
				<div class="post">
					<div class="header">
						<h2>{{ post.title }}</h2>
					</div>
						{{ post.content }}
					<hr>
					Дата: {{ post.created_on }}
				</div>
			</div>
		{% endif %}
	{% endfor %}
{% endblock %}

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





И наконец-то последняя функция — функция создания нового поста.

@login_required
@app.route('/new', methods=['GET', 'POST'])
def new_post():
	if not current_user.is_authenticated:
		return redirect('login')

	if request.method == 'POST':
		title = request.form['title']
		content =  request.form['content']

		if len(title) > 0 and len(title) < 256 and len(content) > 0:
			post = Post(title=title, content=content, author=current_user.username)
			
			try:
				db.session.add(post)
				db.session.commit()
			except Exception as e:
				flash(f'Возникла ошибка при записи в базу данных: {e}')
			else:
				return redirect('index')
		else:
			flash('Ошибка, длина заголовка поста не соответствует стандартам. Максимальное количество символов заголовка - 255.')
			return render_template('newpost.html')

	return render_template('newpost.html')

Здесь как раз мы и используем декоратор login_required. И также мы, на случай обхода этого декоратора, создаем условие на проверку, авторизован ли пользователь. Если нет — то посылаем его на страницу логина.

Потом мы также, как и при регистрации, если метод POST получаем заголовок и контент поста, проверяем его на размер, создаем новую модель и отправляем его в БД.

Вот шаблон newpost.html:

{% extends 'base.html' %}

{% block content %}
	<h2>Создание статьи</h2>

	<form method="POST">
		{% for msg in get_flashed_messages() %}
			<div class="error">
				{{ msg }}
			</div>
		{% endfor %}

		Введите заголовок статьи
		<input type="text" name='title' placeholder="Заголовок"><br>
		Введите текст статьи
		<textarea name="content" id=""></textarea>
		<br>
		<input type="submit" class='btn'>
		<br>
	</form>
{% endblock %}



Финальные приготовления


В файл app.py (главный файл приложения), в самый конец, прописываем специальный условный оператор:

if __name__ == '__main__':
	app.run(debug=True, port=5000)

Если программа запускается напрямую (не импортируется), то мы запускаем приложение с включенным дебагом и портом на 5000.

После мы создаем файл create_db.py, который будет создавать файл базы данных:

from app import app, db

with app.app_context():
	db.create_all()

Мы импортируем app и db из файла приложения (app) и при помощи контекстного менеджера with вызываем метод create_all из db.

И давайте создадим последний файл — main.py, он и будет запускать наш сервер, именно через него gunicorn (специальный модуль для запуска фласк-приложения на сервере) будет запускать нашу стену:

from app import app


if __name__ == '__main__':
	app.run(debug=False)

И наконец то создадим небольшой bash-скрипт deploy.sh, для деплоя на сервер без лишних телодвижений:

#!/bin/bash

pip3 install -r requirements.txt

python3 create_db.py

echo "END"

Здесь мы устанавливаем зависимости и создаем БД.

Если вы хотите запустить сайт — можете ввести две команды (на выбор):

  • Тестовый сервер — python3 app.py.
  • Продакшен сервер — gunicorn main:app --timeout 60.

И вот, все готово. Мы огромные молодцы. Можно скинуть свой сайт другу… Стоп, так мы же на локалхосте?

Если вы хотите опубликовать наше приложение на сервер, то есть два варианта:

  • 1 — покупать сервер, самому все настроить и обслуживать.
  • 2 — создать сервер на платформе netlify или похожей

Оба варианта нам не подойдут. Первый слишком муторный, а 2, к сожалению, из РФ недоступен.

Но есть еще один вариант — Cloud Apps от Timeweb Cloud. Это сервис для быстрого деплоя приложения, чтобы можно было его быстро опубликовать и забыть.

Думаю у многих возникала усталость от бесконечного конфигурирования серверов или постоянной монотонной рутины — подключился на сервер, клонировал репозиторий, установил, отключился. И так по кругу.

Или просто появлялась банальная лень — хотелось бы просто указать репозиторий, указать команды для сборки, и чтобы все сделали за тебя… Желательно чтобы и недорого, и с логами, и с поддержкой нескольких языков программирования и фреймворков. И Docker-контейнеры, и бекенд, и фронтенд.

Есть Netlify, Vercel — но их использование, из-за геополитического конфликта, в России ограничено.

Давайте опубликуем наше веб-приложение!

Приложение делится на три типа — frontend, backend и docker. Cloud Apps поддерживает большинство популярных языков программирования и фреймворков — «большая тройка» JS-библиотек (vue, react, angular) и другие популярные библиотеки или даже просто ванильный nodeJS, бекенд — от PHP и NodeJS до Java, Python, Go, Elixir и .NET, а также просто через Docker-контейнер. Мы будем использовать backend, python (Flask).

Плюсы Cloud Apps:

  • Доступ к логам.
  • Автоматическая установка зависимостей.
  • Возможность выбора версий фреймворков и библиотек.
  • Поддержка различных приложений — от простых докер-контейнеров до развертывания бекенда.
  • Поддержка различных языков программирования и их фреймворков.
  • Возможность использовать приватные репозитории.
  • Автодеплой.
  • Быстрота и легкость использования.
  • Экономия затрат.
  • Масштабируемость.

Минусы, это и так понятно, что для больших высоконагруженных проектов, где надо постоянно заходить на сервер, это будет не удобно.

Как работает Cloud Apps?


Как я уже говорил, Apps — это облачный сервис для автоматической выгрузки кода и автодеплоя ваших приложений на серверах Timeweb.

Работает он так:

  • Шаг 1. Вы заказываете сервис — подключаете репозиторий на GitHub, GitLab или Bitbucket и выбираете нужный фреймворк и сервер с подходящими параметрами.
  • Шаг 2. Все остальное делает сервис: запускает сервер с необходимым ПО, «подтягивает» ваш код из репозитория, ставит зависимости, проверяет код и запускает его.

После запуска сервиса вы можете работать с кодом, как обычно: вносить правки и дополнения и делать коммиты в репозиторий. Сервис Apps автоматически отследит наличие изменений и, если у вас включен автодеплой, выкатит обновления в продакшен-среду.

Если что-то пошло не так и нужно откатиться на прошлую версию — запустите новый деплой с коммитом, по которому был последний успешный деплой.

К приложению будет привязан бесплатный технический домен с SSL Let's Encrypt, который можно использовать для тестирования и запросов к вашему приложению.

Основная функция сервиса приложений — автоматический деплой. Apps автоматически выгружает на сервер код вашего сайта, API-сервиса, приложения и т.п.

Для бекенда также автоматически запускается сервер на nginx, а также приложение хранится в Docker-контейнере.

Работа сервиса с frontend-приложениями имеет одно важное отличие от backend-приложений — после сборки не создается Docker-контейнер, приложение хранится в директории на сервере. Такое приложение — это статические файлы, которые отдаются клиентам с сервера.

Однако, в отличие от обычного размещения приложения на сервере, где вам нужно самостоятельно настраивать окружение, сервис Apps, как и в случае с бэкенд-приложениями, сделает всё за вас:

  • «Подтянет» код из репозитория;
  • Установит зависимости и ПО;
  • Настроит Nginx;
  • Выпустит SSL-сертификат;
  • Выполнит сборку вашего приложения.

А в дальнейшем будет автоматически деплоить изменения — если вы оставите включенной опцию автодеплоя.

Запуск приложения на Cloud Apps


Время попробовать задеплоить нашу открытую стену. Но перед созданием Cloud App нам требуется сам репозиторий — и этот репозиторий можеть быть на вашем GitHub (но также поддерживается GitLab и BitBucket) аккаунте, либо можно даже просто склонировать по URL. Мы советуем первый способ, т.к. так можно сделать всю настройку, и запушить все в приватный репозиторий, ведь при первом способе можно импортировать даже их, в отличии от второго.

Если у вас остались вопросы, то советуем перейти на документацию по подключению репозиториев. Там все рассказано подробно, если у вас возникнут ошибки или проблемы.

Для начала вам потребуется зайти на Timeweb Cloud и зарегистрироваться или войти в аккаунт.



После этого вы попадете в ваш личный кабинет:



После перейдите на вкладку «Apps»:



Займемся переносом приложения на Cloud Apps. Укажите URL вашего GIT-репозитория, в этом примере — наш сайт:



После этого в команде сборки указываем bash deploy.sh, а в команде запуска указываем gunicorn main:app --timeout 60:



Готово! Можете перейти на сайт и протестировать нашу небольшую публичную стену!



Заключение


Приложения могут быть невероятно полезны. Быстро опубликовать сайт-визитку, протестировать что-то или сделать временный сайт — без проблем. Но когда дело касается серьезных проектов — лучше использовать обычные сервера и потратить время на ручную настройку.

Ссылки



Я надеюсь, вам понравилась эта статья. Мы смогли написать довольно хорошее приложение на Flask и быстро опубликовать его. Буквально за день вы получаете +1 проект в ваше портфолио.

Если у вас есть мнение по коду — то прошу их оставить в комментарии, я обязательно отвечу.

Дуров, верни стену!



Возможно, захочется почитать и это:


Новости, обзоры продуктов и конкурсы от команды Timeweb.Cloud — в нашем Telegram-канале

Комментарии (3)


  1. Octabun
    20.05.2024 10:20
    +2

    Буквально за день вы получаете +1 проект в ваше портфолио.

    Тогда могу рекомендовать https://anvil.works


  1. iredun
    20.05.2024 10:20
    +4

    Если у вас есть мнение по коду — то прошу их оставить в комментарии, я обязательно отвечу.


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


    1. DrArgentum Автор
      20.05.2024 10:20
      +1

      Охх, что то совсем забылся когда деплоил. Спасибо!