Конфигурация Docker для Django+Vuejs+Gunicorn+Nginx приводит к 502 Bad Gateway

Context

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

В конечном итоге он будет развернут на AWS с такими сервисами, как RDS (DB), S3 (хранение) и ECS (контейнерный сервис).

Вы можете увидеть, как я себе это представляю с помощью изображения ниже, если это может помочь ответить мне:

enter image description here

Мое приложение хорошо работает локально без Docker.

Но я хочу использовать докеризованную версию локально, прежде чем пытаться развернуть ее на AWS, потому что я хочу иметь возможность запустить ее локально с помощью Docker, прежде чем пытаться сделать это на AWS (...).

Поскольку все мои клиенты будут иметь свой собственный экземпляр (и клиент1 может иметь другую версию, чем клиент2 в определенное время), я подумал об одном контейнере со всем необходимым, как вы можете видеть на изображении: приложение Django, собранные файлы Vue, gunicorn и nginx.

Вопрос 1 : Хорошая ли идея сделать это таким образом? Или лучше использовать docker-compose с несколькими сервисами (backend (django) & nginx). Приведет ли это к созданию нескольких контейнеров?

Проблема

Для этого я выполнил следующую конфигурацию :

Dockerfile:

FROM node:lts-alpine as build-frontend-stage
WORKDIR /frontend
COPY ./frontend/package*.json /frontend/
RUN npm install
COPY ./frontend .
RUN npm run build

FROM python:3.8.10-slim as build-backend-stage
RUN apt-get update
RUN apt-get install --yes --no-install-recommends \
    g++ \
    libpq-dev \
    nginx
WORKDIR /backend
RUN pip install --upgrade pip
COPY ./backend/requirements.txt /backend
RUN pip install -r requirements.txt
COPY ./backend .
COPY --from=build-frontend-stage /frontend/dist/static /backend/static
COPY --from=build-frontend-stage /frontend/dist/index.html /backend/static
COPY ./docker-entrypoint.sh /backend
RUN ["chmod", "+x", "/backend/docker-entrypoint.sh"]
RUN rm -rf /etc/nginx/sites-available/default
RUN rm -rf /etc/nginx/sites-enabled/default
COPY ./nginx/nginx.config /etc/nginx/sites-available/
RUN ln -s /etc/nginx/sites-available/nginx.config /etc/nginx/sites-enabled

ENTRYPOINT ["bash", "/backend/docker-entrypoint.sh"]

Это приводит к созданию контейнера со следующей структурой :

ls -al

drwxr-xr-x 1 root root 4096 Oct 21 14:21 .
drwxr-xr-x 1 root root 4096 Oct 22 08:56 ..
drwxrwxr-x 1 root root 4096 Oct 22 08:56 backend
-rwxrwxr-x 1 root root  552 Oct 21 14:20 docker-entrypoint.sh
-rwxrwxr-x 1 root root  638 Oct  5 12:37 manage.py
-rw-rw-r-- 1 root root  115 Oct  5 10:05 requirements.txt
drwxr-xr-x 1 root root 4096 Oct 21 14:09 static

где статическая папка имеет :

ls -al static

drwxr-xr-x 1 root root 4096 Oct 21 14:09 .
drwxr-xr-x 1 root root 4096 Oct 21 14:21 ..
drwxr-xr-x 2 root root 4096 Oct 18 14:47 css
drwxr-xr-x 2 root root 4096 Oct 18 14:47 fonts
-rw-r--r-- 1 root root  773 Oct 18 14:47 index.html
drwxr-xr-x 2 root root 4096 Oct 18 14:47 js

У меня такой конфиг nginx :

upstream app_server {
    server unix:gunicorn.sock fail_timeout=0;
}

server {
    listen 80;

    root /backend/static;
    index index.html;
    include /etc/nginx/mime.types;

    location / {
        proxy_pass http://app_server;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        
    }
}

Вопрос 2 : Я использую gunicorn.sock. Когда я этого не делаю и использую что-то вроде 0.0.0.0:8000 или что-то подобное, я обычно даже не могу связаться с nginx, он достигает gunicorn напрямую, не знаю почему...

Мой Docker-compose выглядит следующим образом :

version: "3.8"
services:
  web:
    network_mode: host
    env_file:
      - local.env
    build: .
    ports:
      - "8000:8000"

В моем файле local.env есть переменные env, такие как :

DB_HOST=127.0.0.1
DB_NAME=db_name
DB_PASSWORD=db_password
DB_PORT=5432
DB_USER=db_user
SECRET_KEY=some_django_secret_key

и, наконец, моя точка входа :

#!/bin/bash

DJANGO_SETTINGS_MODULE=backend.settings.production
DJANGO_WSGI_MODULE=backend.wsgi
SOCK_FILE=gunicorn.sock

export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE

echo Migrating...
python manage.py migrate --no-input


service nginx start
service nginx status
echo Sarting gunicorn...
gunicorn ${DJANGO_WSGI_MODULE}:application \
    --workers 3 \
    --bind=unix:$SOCK_FILE \
    --log-file=-

Чтобы начать все это, я делаю :

docker-compose -f ./docker-compose-dev.yml up

, который все правильно строит и запускает gunicorn :

web_1  | [2021-10-22 09:06:50 +0000] [53] [INFO] Starting gunicorn 20.0.4
web_1  | [2021-10-22 09:06:50 +0000] [53] [INFO] Listening at: unix:gunicorn.sock (53)
web_1  | [2021-10-22 09:06:50 +0000] [53] [INFO] Using worker: sync
web_1  | [2021-10-22 09:06:50 +0000] [55] [INFO] Booting worker with pid: 55
web_1  | [2021-10-22 09:06:50 +0000] [56] [INFO] Booting worker with pid: 56
web_1  | [2021-10-22 09:06:50 +0000] [57] [INFO] Booting worker with pid: 57

Но я не могу получить ничего, кроме 502 Bad Gateway от nginx при попытке обратиться к 127.0.0.1 в браузере:

enter image description here

и я не могу понять, почему. Наверное, это какая-то путаница с моей стороны...

Вопрос 3: Можете ли вы помочь мне выяснить, в чем заключается ошибка в моей конфигурации, или дать мне некоторые рекомендации по настройке такого развертывания (для локального здесь, не AWS), или некоторые рекомендации по поиску источника ошибки?

Заранее благодарю за прочтение. Я действительно плох в этих devOps вещах, и любая помощь будет оценена по достоинству.

Вы не хотите запускать все в один контейнер. Контейнер должен выполнять только одну задачу. У вас есть несколько вариантов. Запустить по одному контейнеру для каждого модуля (nginx, app и т.д.) и сгруппировать их в одну задачу ECS. Задача - это единица развертывания и масштабирования в ECS, которой вы можете управлять с помощью службы ECS. Таким образом, вы можете иметь 1 службу ECS для каждого клиента и начать с 1 задачи, поддерживающей ее. Если вам понадобится больше "мощности", ECS масштабирует вашу единственную задачу до 2 задач (удваивая все ваши контейнеры). И так далее.

Второй подход (архитектурно лучший, но, возможно, более дорогой) заключается в том, чтобы иметь задачу на каждый контейнер и, таким образом, в общей сложности 3 независимые задачи, обернутые в 3 независимые службы. Тогда каждая служба может масштабироваться самостоятельно (например, если вам нужно больше мощности для NGINX, только задача NGINX будет масштабироваться в службе NGINX ECS. Если вы примете этот подход, вам потребуется 3 сервиса (плюс db) на одного клиента.

Это демонстрационное приложение, которое я использую для этих экспериментов и которое как бы имитирует ваше приложение. Мой docker-compose файл для этого приложения выглядит следующим образом:

version: "3.0"
services:
  yelb-ui:
    image: mreferre/yelb-ui:0.7
    depends_on:
      - yelb-appserver
    ports:
      - 80:80
    networks:
      - yelb-network

  yelb-appserver:
    image: mreferre/yelb-appserver:0.5
    depends_on:
      - redis-server
      - yelb-db
    networks:
      - yelb-network

  redis-server:
    image: redis:4.0.2
    networks:
      - yelb-network
    # uncomment the following lines if you want to persist redis data across deployments
    #volumes:
    # - redisdata:/data

  yelb-db:
    image: mreferre/yelb-db:0.5
    networks:
      - yelb-network
    # uncomment the following lines if you want to persist postgres data across deployments
    #volumes:
    #  - postgresqldata:/var/lib/postgresql/data

networks:
  yelb-network:
    driver: bridge # a user defined bridge is required; the default bridge network doesn't support name resolution

Таким образом ваше приложение будет работать локально. Если вы хотите запустить его на ECS, вы можете использовать такой инструмент, как Copilot или, если вы хотите придерживаться docker compose, вы можете использовать новую docker compose integration with ECS и docker compose up ваше приложение в ECS напрямую.

Оба эти решения реализуют второй описанный мной шаблон (1 контейнер на задачу). Если вам нужен шаблон "все контейнеры в задаче", вам следует создать собственный шаблон CloudFormation.

Вернуться на верх