Django channels unable to connect(find) websocket after docker-compose of project using redis

В настоящее время я реализовал соединения по веб-сокету через каналы django с использованием слоя redis.

Я новичок в docker и не уверен, где я мог допустить ошибку. После docker-compose up -d --build "статические файлы, медиа, база данных и gunicorn wsgi" все функционируют, но redis не подключается. хотя он работает в фоновом режиме.

До попытки контейнеризации приложения с помощью docker, оно хорошо работало с:

python manage.py runserver

со следующей настройкой settings.py для слоя redis:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("0.0.0.0", 6379)],
        },
    },
}

и путем вызова контейнера docker для слоя redis:

docker run -p 6379:6379 -d redis:5

Но после попытки контейнеризации всего приложения оно не смогло найти вебсокет

Новая настройка для docker-compose выглядит следующим образом:

version: '3.10'

services:
  web:
    container_name: web
    build: 
      context: ./app
      dockerfile: Dockerfile
    command: bash -c "gunicorn core.wsgi:application --bind 0.0.0.0:8000"
    volumes:
      - ./app/:/usr/src/app/
      - static_volume:/usr/src/app/staticfiles/
      - media_volume:/usr/src/app/media/
    ports:
      - 8000:8000
    env_file:
      - ./.env.dev
    depends_on:
      - db
    networks:
      - app_network


  redis:
    container_name: redis
    image: redis:5
    ports:
      - 6379:6379
    networks:
      - app_network
    restart: on-failure


  db:
    container_name: db
    image: postgres
    volumes:
      - postgres_data:/var/lib/postgresql/data/
    environment:
      - ./.env.psql
    ports:
      - 5432:5432
    networks:
      - app_network


volumes:
  postgres_data:
  static_volume:
  media_volume:

networks:
  app_network:

с этим settings.py:

CHANNEL_LAYERS = {
    "default": {
        "BACKEND": "channels_redis.core.RedisChannelLayer",
        "CONFIG": {
            "hosts": [("redis", 6379)],
        },
    },
}

После успешной сборки контейнера и запуска docker-compose logs -f:

И docker ps:

CONTAINER ID   IMAGE                     COMMAND                  CREATED          STATUS          PORTS                                       NAMES
cb3e489e0831   dermatology-project_web   "/usr/src/app/entryp…"   35 minutes ago   Up 35 minutes   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   web
aee14c8665d0   postgres                  "docker-entrypoint.s…"   35 minutes ago   Up 35 minutes   0.0.0.0:5432->5432/tcp, :::5432->5432/tcp   db
94c29591b352   redis:5                   "docker-entrypoint.s…"   35 minutes ago   Up 35 minutes   0.0.0.0:6379->6379/tcp, :::6379->6379/tcp   redis

Сборка Dockerfile:

# set work directory

WORKDIR /usr/src/app

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1

# install psycopg2 dependencies
RUN apt-get update
RUN apt-get install -y libpq-dev python3-pip python-dev postgresql postgresql-contrib netcat

# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt .
RUN pip install -r requirements.txt

# copy entrypoint.sh
COPY ./entrypoint.sh .
RUN sed -i 's/\r$//g' /usr/src/app/entrypoint.sh
RUN chmod +x /usr/src/app/entrypoint.sh


# create the appropriate directories for staticfiles

# copy project
COPY . .

# staticfiles
RUN python manage.py collectstatic --no-input --clear


# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]

и точка входа, которая проверяет соединения:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
        sleep 0.1
    done

    echo "PostgreSQL started"
fi

if [ "$CHANNEL" = "redis" ]
then
    echo "Waiting for redis..."

    while ! nc -z $REDIS_HOST $REDIS_PORT; do
        sleep 0.1
    done

    echo "redis started"
fi
#python manage.py flush --no-input
#python manage.py migrate

exec "$@"

Я также пытался запустить контейнер redis отдельно, как раньше, и поддерживать рабочие контейнеры, но это тоже не работает. Я также пробовал запустить daphne на другом порту и передать asgi:application (daphne -p 8001 myproject.asgi:application), и это также не сработало.

Спасибо

В конечном итоге удалось найти решение

Для того чтобы это работало, мне нужно было запустить серверы wsgi и asgi отдельно друг от друга, каждый со своим собственным контейнером. Кроме того, предыдущий сервис "web", который открывал порты для приложений, нужно было запускать дважды для каждого контейнера, с прокси-серверами nginx, которые подключались к каждому соответствующему порту.

Все это произошло благодаря этому гениальному человеку:

https://github.com/pplonski/simple-tasks

Здесь он объясняет то, что мне было нужно и даже больше. Он также использует celery workers для управления асинхронной очередью задач/очередью заданий на основе распределенной передачи сообщений, что было немного излишне для моего проекта, но красиво.

Новый docker-compose:

version: '2'

services:

    nginx:
        container_name: nginx
        restart: always
        build: ./nginx
        ports:
            - 1337:80
        volumes:
            - static_volume:/usr/src/app/staticfiles/
            - media_volume:/usr/src/app/media/
        depends_on:
            - wsgiserver
            - asgiserver

    postgres:
        container_name: postgres
        restart: always
        image: postgres
        volumes:
            - postgres_data:/var/lib/postgresql/data/
        ports:
            - 5433:5432
        expose:
            - 5432
        environment:
            - ./.env.db

    redis:
        container_name: redis
        image: redis:5
        restart: unless-stopped
        ports:
            - 6378:6379


    wsgiserver:
        build:            
            context: ./app
            dockerfile: Dockerfile
        container_name: wsgiserver
        command: gunicorn core.wsgi:application --bind 0.0.0.0:8000 
        env_file:
            - ./.env.dev
        volumes:
            - ./app/:/usr/src/app/
            - static_volume:/usr/src/app/staticfiles/
            - media_volume:/usr/src/app/media/
        links:
            - postgres
            - redis
        expose:
            - 8000


    asgiserver:
        build:            
            context: ./app
            dockerfile: Dockerfile
        container_name: asgiserver
        command: daphne core.asgi:application -b 0.0.0.0 -p 9000
        env_file:
            - ./.env.dev
        volumes:
            - ./app/:/usr/src/app/
        links:
            - postgres
            - redis
        expose:
            - 9000


volumes:
    static_volume: 
    media_volume:
    postgres_data:

New entrypoint.sh:

#!/bin/sh

if [ "$DATABASE" = "postgres" ]
then
    echo "Waiting for postgres..."

    while ! nc -z $SQL_HOST $SQL_PORT; do
        sleep 0.1
    done

    echo "PostgreSQL started"
fi

#python manage.py flush --no-input
#python manage.py migrate

exec "$@"

Новый nginx

nginx.conf:

server {
    listen 80;


    # gunicon wsgi server
    location / {
        try_files $uri @proxy_api;
    }

    location @proxy_api {
        proxy_set_header X-Forwarded-Proto https;
        proxy_set_header X-Url-Scheme $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass   http://wsgiserver:8000;
    }


    # ASGI
    # map websocket connection to daphne
    location /ws {
        try_files $uri @proxy_to_ws;
    }

    location @proxy_to_ws {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_redirect off;

        proxy_pass   http://asgiserver:9000;
    }
    
    # static and media files 
    location /static/ {
        alias /usr/src/app/staticfiles/;
    }
    location /media/ {
        alias /usr/src/app/media/;
    }
}

Dockerfile для nginx:

FROM nginx:1.21

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d

Примечание

Если кто-то использует это в качестве ссылки, это не производственный контейнер, необходимы дальнейшие шаги.

В этой статье объясняется другой шаг: https://testdriven.io/blog/dockerizing-django-with-postgres-gunicorn-and-nginx/#conclusion

, а также о защите приложения на AWS с помощью Docker и Let's Encrypt, в заключении по ссылке.

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