Привет! Меня зовут Артём Шумейко, я Python-разработчик и создатель одноименного канала на YouTube. Представьте: у вас есть проект с фронтенд- и бэкенд-частью. Первый работает на одном порту и отображает данные, после — передает бэкенду. Второй работает на другом порту, принимает и обрабатывает эти данные, после чего возвращает ответ. Обычно сайты находятся на едином домене с фронтендом и бэкендом, а здесь — на двух отдельных.
Будучи новичком я не понимал, как объединить фронтенд и бэкенд. Думал, нужно подключать два домена и неведомым образом их «подружить». Но все оказалось намного проще. В тексте поделюсь подробной инструкцией и покажу, как задеплоить проект на облачный сервер.
О проекте
В инструкции не будем писать веб-приложение с нуля — возьмем готовый проект, который состоит из 30-40 строк кода. Фронтенд будет отображать данные с бэкенда: картинки и текст.
from fastapi import FastAPI
import random
@app.get("/items")
def get_items():
items = [
{
"id": 1,
"name": "Docker",
"img": "https://static-00.iconduck.com/assets.00/docker-icon-2048x2048-5mc7mvtn.png",
},
{
"id": 2,
"name": "Nginx",
"img": "https://www.svgrepo.com/show/373924/nginx.svg",
},
{
"id": 3,
"name": "GitHub",
"img": "https://cdn-icons-png.flaticon.com/512/25/25231.png",
},
]
random.shuffle(items)
return items
Весь проект локально запущен в двух терминалах. В первом — бэкенд с портом 8000, во втором — фронтенд с портом 5173. По сути, это два разных сервера, которые нужно соединить.
Чтобы объединить фронтенд и бэкенд, есть две стратегии. Первый способ — это реверс-прокси(обратный прокси), при котором мы не создаем домен. В нем разворачиваем фронтенд на основной сайт, а бэкенд — на адрес/API. Второй — это использование обратного прокси через создание поддомена. В нем создаем еще один домен к основному, например api.mysite.ru, чтобы разделить сайты. В тексте будем использовать обратный прокси без поддомена.
Настройка Docker-файла
Бэкенд
Нужно написать два простых Docker-файла, чтобы перенести данные в контейнеры. В образ Python добавляем slim
, чтобы не занимать много места:
FROM python:3.11.9-slim
Перемещаем все файлы в локальную директорию и добавляем зависимости в requirements.txt. После копируем файлы в Docker-образ:
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
Сначала устанавливаем файлы с зависимостями, которые редко меняются, и только потом остальные. Запускаем код:
CMD [ "python", "main.py" ]
Фронтенд
Копируем весь Docker-файл и добавляем его в папку frontend. В нем будем использовать не Python, а JavaScript и Node.js.
Указываем последнюю версию ноды типа alpine
и называем ее build
:
FROM node:alpine as build
Здесь зависимости находятся не в папке requirements.txt, а в package.json. Копируем их и добавляем в файл:
COPY package.json package.json
RUN npm install
После — копируем оставшиеся файлы:
COPY . .
RUN npm run build
Фронтенд поддерживает только статические файлы: JavaScript, HTML и CSS. Чтобы забрать их из контейнера, запускаем nginx прямо в коде:
FROM nginx: stable-alpine
После команды npm build
внутри Docker-образа появилась папка /dist. В ней хранятся созданные билды, а именно — статические файлы. Отправляем их в nginx:
COPY --from=build /dist /usr/share/nginx/html
Далее копируем nginx.conf в классическую директору nginx:
COPY --from=build nginx.conf /etc/nginx/conf.d/default.conf
Указываем порт, на котором будет работать nginx, через EXPOSE 3000, но можно выбрать другой:
EXPOSE 3000
CMD [ "nginx", "-g", "daemon off;" ]
Чтобы отдавать файлы index html, создаем nginx.conf:
server {
listen 3000;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html =404;
}
include /etc/nginx/extra-conf.d/*.conf;
}
Общая сборка приложения
Создаем файл docker-compose, чтобы управлять сразу несколькими контейнерами. Здесь используем контейнер бэкенда и фронтенда в YAML-формате.
services:
backend:
build:
context: ./backend
frontend:
build:
context: ./frontend
Перед контейнерами запускаем nginx из готового образа DockerHub:
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
backend:
build:
context: ./backend
frontend:
build:
context: ./frontend
Указываем стандартный порт HTTP — 80.
Далее нужно выполнить две задачи:
настроить приложение так, чтобы nginx запускался только после бэкенда и фронтенда;
добавить volumes, чтобы nginx видел файлы с локального сервера.
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
volumes:
- './nginx.conf:/etc/nginx/nginx.conf'
depends_on:
- backend
- frontend
Соединяем все контейнеры в единую сеть и указываем зависимости:
networks:
dev:
services:
nginx:
image: nginx:stable-alpine
ports:
- "80:80"
- "443:443"
volumes:
- './nginx.conf:/etc/nginx/nginx.conf'
depends_on:
- backend
- frontend
networks:
- dev
backend:
build:
context: ./backend
networks:
- dev
frontend:
build:
context: ./frontend
networks:
- dev
Настройка конфигурации nginx
Используем готовый файл nginx.conf. Называем пользователя root, добавляем необходимое количество worker-нод и порт 80.
user root;
worker_processes 1;
events {
}
http {
server {
listen 80;
}
}
Указываем Location, чтобы при запуске выводить фронтенд на главную страницу. После — добавляем proxy_pass, чтобы проксировать запросы через nginx, и порт 3000, как на нашем сервере.
location / {
proxy_pass http://frontend:3000/;
}
Далее подключаем бэкенд и добавляем в Location /api/. Порт — 8000, поскольку контейнер с бэкендом использует именно его.
location /api/ {
proxy_pass http://backend:8000/;
}
Конфигурация nginx готова:
user root;
worker_processes 1;
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://frontend:3000/;
}
location /api/ {
proxy_pass http://backend:8000/;
}
}
}
Создание облачного сервера
Переходим в панель управления Selectel и создаем новый сервер. Указываем имя, регион и операционную систему — в нашем случае Ubuntu 24.04 LTS 64-bit. Для работы сайта подойдет минимальная конфигурация:
1 CPU,
1 ГБ RAM,
10 ГБ SSD,
публичный IP-адрес.
Далее добавляем SSH-ключ. Указываем имя и копируем команду для терминала. После получаем публичный ключ и добавляем его в окно создания.
Теперь можно автоматически подключаться к серверу без использования пароля, с помощью команды ssh root@<IP-адрес сервера>. Нажимаем Создать сервер.
Создание репозитория
Создаем новый репозиторий для хранения кодовой базы. Если проект не конфиденциален и вы хотите клонировать его на сервер без дополнительной авторизации, сделайте репозиторий публичным. Далее — переходим в терминал (предварительно не забудьте подключиться к серверу) и клонируем репозиторий с помощью команды git clone <адрес репозитория>.
Поскольку у нас около 20 файлов, создаем файл .gitignore. Туда добавляем файлы с зависимостями — venv и node-modules. Пишем initial commit, чтобы отправить их в Git.
venv/
node_modules/
Переходим в терПереходим в терминал и обновляем пакеты, чтобы установить Git. Создаем клон и скачиваем код по HTTPS с GitHub. На выходе получаем папку test-deploy-site, в которой находятся все файлы.
Деплой веб-приложения на сервер
Теперь нужно установить Docker. Используем готовый код — копируем и добавляем его в терминал.
Собираем все образы и добавляем их в контейнеры Docker:
docker compose up --build
Адрес бэкенда и фронтенда скрыты от конечного пользователя и мошенников. Напрямую с ними работать не получится, только через nginx.
Заключение
Нам удалось соединить фронтенд с бэкендом в одном веб-приложении и развернуть его на облачном сервере. Оставляю код на GitHub и видеоверсию инструкции, чтобы вы легко могли повторить все шаги.
Комментарии (7)
gorod0k
27.08.2024 10:00+1Ох. Начиналось все с того, что "а давайте сделаем серверный js, чтобы разработчик мог, зная только js разрабатывать и фронт и бэк". Но, надо заметить, получилось в основном довольно удобно в плане реализации, хоть и не без костылей.
А теперь у них и фронт на отдельном сервере запускается. Так сказать, обмажутся своим node js и ... друг друга в ...
olku
Nginx тут лишний.
JohnDorian
Why?
olku
TLS не терминируется решением, но 443 выставлен наружу. Идея терминировать сервисом хостера, если такой есть, хорошая. Имена хостов захардкожены, это плохая практика - в Swarm будет один, в Kubernetes другой. Бекенд проксировать не обязательно, если нет задачи делать его открытым всему миру. Спеки OpenAPI фронтендер обычно смотрит лишь во время разработки. Трафик с фронтенда на бекенд гонять через nginx не нужно, как API Gateway он так себе. Фронтенд тоже непонятно зачем проксировать, он самодостаточен.
Показалось что автор не вполне понимает оркестрацию. Так себе реклама, но хороший способ научиться самому - научить других.
ig_rudenko
Уточню, что можно обойтись лишь одним nginx, вместо двух, если в frontend конфигурации прописать для location /api/ перенаправление на backend
trigremm
Пояснение.
Nginx на фронте тут лишний. Так как перед ним уже стоит nginx.