add nginx-wsgi-django-mysql example
Signed-off-by: IML <shino1025@naver.com>
This commit is contained in:
parent
6531426a96
commit
dced7d7016
121
nginx-wsgi-django-mysql/README.md
Normal file
121
nginx-wsgi-django-mysql/README.md
Normal file
@ -0,0 +1,121 @@
|
||||
# Compose Sample Application
|
||||
|
||||
## NGINX Reverse Proxy -> WSGI -> Python/Django Backend + MySQL
|
||||
|
||||
Project structure:
|
||||
|
||||
```text
|
||||
.
|
||||
├── README.md
|
||||
├── django
|
||||
│ ├── Dockerfile
|
||||
│ ├── requirements.txt
|
||||
│ └── sample/
|
||||
│ ├── manage.py
|
||||
│ ├── requirements.txt
|
||||
│ └── sample/
|
||||
├── docker-compose.yaml
|
||||
└── nginx
|
||||
├── Dockerfile
|
||||
├── default.conf
|
||||
├── nginx.conf
|
||||
└── start.sh
|
||||
```
|
||||
|
||||
[_docker-compose.yaml_](docker-compose.yaml)
|
||||
|
||||
```yml
|
||||
version: "3.9"
|
||||
services:
|
||||
|
||||
nginx-proxy:
|
||||
build: nginx
|
||||
ports:
|
||||
- 80:80
|
||||
...
|
||||
|
||||
django-app:
|
||||
build: django
|
||||
container_name: django-app
|
||||
ports:
|
||||
- 8000:8000
|
||||
...
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.28
|
||||
container_name: mysql
|
||||
ports:
|
||||
- 3306:3306
|
||||
...
|
||||
|
||||
|
||||
```
|
||||
|
||||
The compose file defines an application with three services `nginx-proxy`, `django-app` and `mysql`.
|
||||
When deploying the application, docker-compose maps port 80 of the web service container to port 80 of the host as specified in the file.
|
||||
|
||||
Make sure port 80 on the host is not being used by another container, otherwise the port should be changed.
|
||||
|
||||
## Deploy with docker-compose
|
||||
|
||||
```bash
|
||||
$ docker-compose up -d
|
||||
[+] Running 4/4
|
||||
⠿ Network nginx-wsgi-django-mysql_default Created
|
||||
⠿ Container mysql Started
|
||||
⠿ Container django-app Started
|
||||
⠿ Container nginx-proxy Started
|
||||
```
|
||||
|
||||
## Expected result
|
||||
|
||||
Listing containers must show three containers running and the port mapping as below:
|
||||
|
||||
```bash
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
d80066a23885 some-nginx "/docker-entrypoint.…" About an hour ago Up About an hour 0.0.0.0:80->80/tcp nginx-proxy
|
||||
a30bb5db0798 mysql:8.0.28 "docker-entrypoint.s…" About an hour ago Up About an hour 0.0.0.0:3306->3306/tcp, 33060/tcp mysql
|
||||
7031de9c4531 some-django "gunicorn -w 2 -b 0.…" About an hour ago Up About an hour 0.0.0.0:8000->8000/tcp django-app
|
||||
```
|
||||
|
||||
After the application starts, navigate to `http://localhost:80` in your web browser or run:
|
||||
|
||||
```bash
|
||||
$ curl localhost:80
|
||||
|
||||
<!doctype html>
|
||||
|
||||
<html lang="en-us" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
...
|
||||
```
|
||||
|
||||
Stop and remove the containers
|
||||
|
||||
```bash
|
||||
$ docker-compose down
|
||||
[+] Running 4/4
|
||||
⠿ Container nginx-proxy Removed
|
||||
⠿ Container mysql Removed
|
||||
⠿ Container django-app Removed
|
||||
⠿ Network nginx-wsgi-django-mysql_default Removed
|
||||
```
|
||||
|
||||
## About
|
||||
|
||||
By following the steps above, you will have an NGINX Reverse Proxy and a Django backend. The general traffic flow will look like the following:
|
||||
|
||||
`Client -> NGINX -> WSGI -> Django + MySQL`
|
||||
|
||||
### NGINX
|
||||
|
||||
With this deployment model, we use NGINX to proxy and handle all requests to our Django backend. This is a powerful deployment model as we can use NGINX to cache responses or even act as an application load balancer between multiple Django backends. You could also integrate a Web Application Firewall into NGINX to protect your Django backend from attacks.
|
||||
|
||||
### WSGI
|
||||
|
||||
WSGI (Web Server Gateway Interface) is the interface that sits in between our NGINX proxy and Django backend. It is used to handle requests and interface with our backend. WSGI allows you to handle thousands of requests at a time and is highly scalable. In this `docker-compose` sample, we use Gunicorn for our WSGI.
|
||||
|
||||
### Django
|
||||
|
||||
Django is a web development framework written in Python. It is the "backend" which processes requests.
|
16
nginx-wsgi-django-mysql/django/Dockerfile
Normal file
16
nginx-wsgi-django-mysql/django/Dockerfile
Normal file
@ -0,0 +1,16 @@
|
||||
FROM python:3.9-alpine
|
||||
|
||||
COPY /sample/ /home/sample/
|
||||
WORKDIR /home/sample/
|
||||
|
||||
RUN apk update && \
|
||||
apk add --no-cache bash gcc musl-dev mariadb-connector-c-dev
|
||||
|
||||
RUN pip install --upgrade pip && \
|
||||
pip install -r requirements.txt
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
CMD ["gunicorn","-w","2", \
|
||||
"--bind","0.0.0.0:8000", \
|
||||
"sample.wsgi:application"]
|
5
nginx-wsgi-django-mysql/django/requirements.txt
Normal file
5
nginx-wsgi-django-mysql/django/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
asgiref==3.5.0
|
||||
Django==4.0.2
|
||||
gunicorn==20.1.0
|
||||
mysqlclient==2.1.0
|
||||
sqlparse==0.4.2
|
22
nginx-wsgi-django-mysql/django/sample/manage.py
Executable file
22
nginx-wsgi-django-mysql/django/sample/manage.py
Executable file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python
|
||||
"""Django's command-line utility for administrative tasks."""
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def main():
|
||||
"""Run administrative tasks."""
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample.settings')
|
||||
try:
|
||||
from django.core.management import execute_from_command_line
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"Couldn't import Django. Are you sure it's installed and "
|
||||
"available on your PYTHONPATH environment variable? Did you "
|
||||
"forget to activate a virtual environment?"
|
||||
) from exc
|
||||
execute_from_command_line(sys.argv)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
5
nginx-wsgi-django-mysql/django/sample/requirements.txt
Normal file
5
nginx-wsgi-django-mysql/django/sample/requirements.txt
Normal file
@ -0,0 +1,5 @@
|
||||
asgiref==3.5.0
|
||||
Django==4.0.2
|
||||
gunicorn==20.1.0
|
||||
mysqlclient==2.1.0
|
||||
sqlparse==0.4.2
|
16
nginx-wsgi-django-mysql/django/sample/sample/asgi.py
Normal file
16
nginx-wsgi-django-mysql/django/sample/sample/asgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
ASGI config for sample project.
|
||||
|
||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/howto/deployment/asgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.asgi import get_asgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample.settings')
|
||||
|
||||
application = get_asgi_application()
|
127
nginx-wsgi-django-mysql/django/sample/sample/settings.py
Normal file
127
nginx-wsgi-django-mysql/django/sample/sample/settings.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""
|
||||
Django settings for sample project.
|
||||
|
||||
Generated by 'django-admin startproject' using Django 4.0.2.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/topics/settings/
|
||||
|
||||
For the full list of settings and their values, see
|
||||
https://docs.djangoproject.com/en/4.0/ref/settings/
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
# Quick-start development settings - unsuitable for production
|
||||
# See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
|
||||
|
||||
# SECURITY WARNING: keep the secret key used in production secret!
|
||||
SECRET_KEY = 'django-insecure-nz0h+$=@na+sazn6^wdr8fes1l5gj_2uajw-)i(3i%6cgy2zca'
|
||||
|
||||
# SECURITY WARNING: don't run with debug turned on in production!
|
||||
DEBUG = True
|
||||
|
||||
ALLOWED_HOSTS = ["127.0.0.1", "localhost", "0.0.0.0"]
|
||||
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'sample.urls'
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
WSGI_APPLICATION = 'sample.wsgi.application'
|
||||
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'sample',
|
||||
'USER': 'root',
|
||||
'PASSWORD': 'password',
|
||||
'HOST': 'mysql',
|
||||
'PORT': '3306',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#auth-password-validators
|
||||
|
||||
AUTH_PASSWORD_VALIDATORS = [
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||
},
|
||||
{
|
||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/4.0/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
|
||||
TIME_ZONE = 'UTC'
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
# https://docs.djangoproject.com/en/4.0/howto/static-files/
|
||||
|
||||
STATIC_URL = 'static/'
|
||||
|
||||
# Default primary key field type
|
||||
# https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
21
nginx-wsgi-django-mysql/django/sample/sample/urls.py
Normal file
21
nginx-wsgi-django-mysql/django/sample/sample/urls.py
Normal file
@ -0,0 +1,21 @@
|
||||
"""sample URL Configuration
|
||||
|
||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
||||
https://docs.djangoproject.com/en/4.0/topics/http/urls/
|
||||
Examples:
|
||||
Function views
|
||||
1. Add an import: from my_app import views
|
||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
||||
Class-based views
|
||||
1. Add an import: from other_app.views import Home
|
||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
||||
Including another URLconf
|
||||
1. Import the include() function: from django.urls import include, path
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.urls import path
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', admin.site.urls),
|
||||
]
|
16
nginx-wsgi-django-mysql/django/sample/sample/wsgi.py
Normal file
16
nginx-wsgi-django-mysql/django/sample/sample/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
||||
"""
|
||||
WSGI config for sample project.
|
||||
|
||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||
|
||||
For more information on this file, see
|
||||
https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.core.wsgi import get_wsgi_application
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sample.settings')
|
||||
|
||||
application = get_wsgi_application()
|
34
nginx-wsgi-django-mysql/docker-compose.yaml
Normal file
34
nginx-wsgi-django-mysql/docker-compose.yaml
Normal file
@ -0,0 +1,34 @@
|
||||
version: "3.9"
|
||||
services:
|
||||
|
||||
nginx-proxy:
|
||||
build: nginx
|
||||
container_name: nginx-proxy
|
||||
volumes:
|
||||
- ./nginx/default.conf:/tmp/default.conf
|
||||
environment:
|
||||
- SERVER_ADDR=django-app:8000
|
||||
ports:
|
||||
- 80:80
|
||||
depends_on:
|
||||
- django-app
|
||||
command: /app/start.sh
|
||||
|
||||
django-app:
|
||||
build: django
|
||||
container_name: django-app
|
||||
ports:
|
||||
- 8000:8000
|
||||
command: gunicorn -w 1 -b 0.0.0.0:8000 sample.wsgi:application
|
||||
|
||||
mysql:
|
||||
image: mysql:8.0.28
|
||||
container_name: mysql
|
||||
volumes:
|
||||
- /Users/iml/Desktop/mysql:/var/lib/mysql
|
||||
ports:
|
||||
- 3306:3306
|
||||
environment:
|
||||
- MYSQL_ROOT_PASSWORD=password
|
||||
|
||||
|
24
nginx-wsgi-django-mysql/nginx/Dockerfile
Normal file
24
nginx-wsgi-django-mysql/nginx/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
FROM nginx:1.21.4-alpine
|
||||
|
||||
# Add nginx.conf to container
|
||||
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --chown=nginx:nginx default.conf /tmp/default.conf
|
||||
COPY --chown=nginx:nginx start.sh /app/start.sh
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Add bash for boot cmd &
|
||||
# permissions and nginx user for tightened security
|
||||
RUN apk add bash && \
|
||||
chown -R nginx:nginx /app && \
|
||||
chmod -R 755 /app && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
chmod -R 755 /var/log/nginx && \
|
||||
chown -R nginx:nginx /etc/nginx/conf.d && \
|
||||
touch /var/run/nginx.pid && \
|
||||
chown -R nginx:nginx /var/run/nginx.pid
|
||||
|
||||
USER nginx
|
||||
|
||||
CMD ["/app/start.sh"]
|
15
nginx-wsgi-django-mysql/nginx/default.conf
Normal file
15
nginx-wsgi-django-mysql/nginx/default.conf
Normal file
@ -0,0 +1,15 @@
|
||||
server {
|
||||
listen 80;
|
||||
server_name 0.0.0.0;
|
||||
|
||||
location / {
|
||||
proxy_pass http://$SERVER_ADDR;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_connect_timeout 300;
|
||||
proxy_send_timeout 300;
|
||||
proxy_read_timeout 300;
|
||||
send_timeout 300;
|
||||
}
|
||||
}
|
63
nginx-wsgi-django-mysql/nginx/nginx.conf
Normal file
63
nginx-wsgi-django-mysql/nginx/nginx.conf
Normal file
@ -0,0 +1,63 @@
|
||||
worker_processes auto;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Define the format of log messages.
|
||||
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||
'"$host" sn="$server_name" '
|
||||
'rt=$request_time '
|
||||
'ua="$upstream_addr" us="$upstream_status" '
|
||||
'ut="$upstream_response_time" ul="$upstream_response_length" '
|
||||
'cs=$upstream_cache_status' ;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
types_hash_max_size 1024;
|
||||
# Disable Display of NGINX Version
|
||||
server_tokens off;
|
||||
|
||||
##
|
||||
# Logging Settings
|
||||
##
|
||||
access_log /var/log/nginx/access.log main_ext;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
##
|
||||
# Gzip Settings
|
||||
##
|
||||
gzip on;
|
||||
# gzip_vary on;
|
||||
# gzip_proxied any;
|
||||
# gzip_comp_level 6;
|
||||
# gzip_buffers 16 8k;
|
||||
# gzip_http_version 1.1;
|
||||
# gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
# Size Limits
|
||||
client_body_buffer_size 10K;
|
||||
client_header_buffer_size 1k;
|
||||
client_max_body_size 10m;
|
||||
large_client_header_buffers 4 8k;
|
||||
|
||||
##
|
||||
# SSL Settings
|
||||
##
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_session_timeout 15m;
|
||||
# ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
# ssl_session_tickets off;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
}
|
3
nginx-wsgi-django-mysql/nginx/start.sh
Normal file
3
nginx-wsgi-django-mysql/nginx/start.sh
Normal file
@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
envsubst '$SERVER_ADDR' < /tmp/default.conf > /etc/nginx/conf.d/default.conf \
|
||||
&& nginx -g 'daemon off;'
|
Loading…
Reference in New Issue
Block a user