I wanted to develop an application with Djagno + React configuration and deploy it to Google Kubernetes Engine, but I didn't think there was a cohesive tutorial, so I wrote it.
However, ** I think there are some points that are not perfect yet **, but I think that if you have some experience, you can use it immediately.
This is a record of an inexperienced hobby engineer struggling to deploy to create a portfolio. If there are any deficiencies, please point them out. ..
$ node --version
v12.14.1
$ npm --version
6.13.7
$ python --version
Python 3.7.4
$ docker --version
Docker version 19.03.8
OS windows10 pro
#Creating a project folder
$ mkdir gke-django-tutorial
$ cd gke-django-tutorial
#Create a directory
$\gke-django-tutorial\mkdir backend
$\gke-django-tutorial\mkdir frontend
The backend pod delivers the Rest API with Django-rest-framework. Let's organize the inside of the backend pod.
Role | Container image |
---|---|
Proxy server | Nginx: 1.17.4-alpine |
Application | Python3.7 --Django rest framework |
cloud_sql_proxy | gcr.io/cloudsql-docker/gce-proxy |
Create a directory in the backend.
#Move to backend directory
$\gke-django-tutorial\cd backend
#Create django directory
$\gke-django-tutorial\backend\mkdir web-back
#Create Nginx directory
$\gke-django-tutorial\backend\mkdir nginx
We will create a virtual environment for Python and develop an API server with the Django rest framework.
Create this in the backend \ web-back \
directory.
# web-back directory
$\gke-django-tutorial\backend\cd web-back
#Creating a virtual environment for Python
$\gke-django-tutorial\backend\web-back\python -m venv venv
#Enable virtual environment
$\gke-django-tutorial\backend\web-back\venv\Scripts\activate
#Python package installation
(venv)$\gke-django-tutorial\backend\web-back\python -m install --upgrade pip setuptools
(venv)$\gke-django-tutorial\backend\web-back\python -m install django djangorestframework python-dotenv
#Start a Django project
(venv)$\gke-django-tutorial\backend\web-back\django-admin startproject config .
By doing django-admin startprject config .
under the web-back directory
A Django project folder called config
has been created.
Let's check if the local server starts.
(venv)$\gke-django-tutorial\backend\web-back\python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
Run 'python manage.py migrate' to apply them.
April 27, 2020 - 11:22:06
Django version 3.0.5, using settings 'config.settings'
Starting development server at http://127.0.0.1:8000/
Quit the server with CTRL-BREAK.
Since the development server has started, you can see the screen of The install worked successfully!
By accessing http: // localhost: 8000 /
.
settings.py
Edit config / settings.py
to include the basic settings.
Confidential information in settings.py
should be written in the .env
file so that it is not disclosed.
Use the python-dotenv package to change it to use the information contained in .env
.
# .Creating an env file
(venv)$\gke-django-tutorial\backend\web-back\type nul > .env
# config/settings.py
import os
from dotenv import load_dotenv #add to
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROJECT_DIR = os.path.basename(BASE_DIR) #add to
# .Load env
load_dotenv(os.path.join(BASE_DIR, '.env')) #add to
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv('DEBUG')
ALLOWED_HOSTS = ["*"] #Change
# 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 = 'config.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], #Change
'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 = 'config.wsgi.application'
# Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
# Password validation
# https://docs.djangoproject.com/en/3.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/3.0/topics/i18n/
LANGUAGE_CODE = 'ja' #Change
TIME_ZONE = 'Asia/Tokyo'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/
STATIC_URL = '/static/'
#Where to refer to static files in the development environment
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] #add to
#Where to reference static files in production
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') #add to
#Media file path
MEDIA_URL = '/media/' #add to
.env
# .env
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
DEBUG = False
Let's create a todo application.
(venv)$\gke-django-tutorial\backend\web-back\python manage.py startapp todo
Add todo
and rest_framework
to ʻINSTALLED_APPS in
config / settings.py`.
# config/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd party
'rest_framework',
# Local
'todo.apps.TodoConfig',
]
#add to
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
rest_framework.permissions.AllowAny
is for removing the default setting'DEFAULT_PERMISSION_CLASSES'
implicitly set by django-rest-framework.
todo/models.py
Create a todo
application model.
# todo/models.py
from django.db import models
class Todo(models.Model):
title = models.CharField(max_length=200)
body = models.TextField()
def __str__(self):
return self.title
Add the created model to todo / admin.py
.
# todo/admin.py
from django.contrib import admin
from .models import Todo
admin.site.register(Todo)
Migrate.
(venv)$\gke-django-tutorial\backend\web-back\python manage.py makemigrations
Migrations for 'todo':
todo\migrations\0001_initial.py
- Create model Todo
(venv)$\gke-django-tutorial\backend\web-back\python manage.py migrate
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions, todo
Running migrations:
Applying contenttypes.0001_initial... OK
Applying auth.0001_initial... OK
Applying admin.0001_initial... OK
Applying admin.0002_logentry_remove_auto_add... OK
Applying admin.0003_logentry_add_action_flag_choices... OK
Applying contenttypes.0002_remove_content_type_name... OK
Applying auth.0002_alter_permission_name_max_length... OK
Applying auth.0003_alter_user_email_max_length... OK
Applying auth.0004_alter_user_username_opts... OK
Applying auth.0005_alter_user_last_login_null... OK
Applying auth.0006_require_contenttypes_0002... OK
Applying auth.0007_alter_validators_add_error_messages... OK
Applying auth.0008_alter_user_username_max_length... OK
Applying auth.0009_alter_user_last_name_max_length... OK
Applying auth.0010_alter_group_name_max_length... OK
Applying auth.0011_update_proxy_permissions... OK
Applying sessions.0001_initial... OK
Applying todo.0001_initial... OK
createsuperuser
Create an admin user.
(venv)$\gke-django-tutorial\backend\web-back\python manage.py createsuperuser
username(leave blank to use '[YOUR_NAME]'): [USER_NAME]
mail address: [email protected]
Password:
Password (again):
Superuser created successfully.
Start your development server and go to http: // localhost: 8000 / admin /
to see the Django admin site login screen. Enter the set user name and password to log in.
If you can log in, you can check the table of the created application Todo
.
Let's add a few items.
URLs
Add a route to the todo appison in config / urls.py
.
# config/urls.py
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('todo.urls')) #add to
]
todo/urls.py
Create todo / urls.py
.
(venv)$\gke-django-tutorial\backend\web-back\type nul > todo\urls.py
# todo/urls.py
from django.urls import path, include
from .views import ListTodo, DetailTodo
urlpatterns = [
path('<int:pk>/', DetailTodo.as_view()),
path('', ListTodo.as_view())
]
todo/selializers.py
Create a serializer to easily convert the model instance to json format.
(venv)$\gke-django-tutorial\backend\type nul > todo\serializers.py
# todo/serializers.py
from rest_framework import serializers
from .models import Todo
class TodoSerializer(serizers.ModelSerializer):
class Meta:
model = Todo
fields = ('id', 'title', 'body')
ʻId at
fields = ('id','title','text') is automatically added by Django if you don't specify a
PrimaryKey` in model.
todo/views.py
When creating views.py
with Django rest framework, it inherits ~~ APIView
from rest_framework.generics
.
# todo/views.py
from django.shortcuts import render
from rest_framework import generics
from .models import Todo
from .serializers import TodoSerializer
class ListTodo(generics.ListAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
class DetailTodo(generics.RetrieveAPIView):
queryset = Todo.objects.all()
serializer_class = TodoSerializer
I haven't set up the router etc., but for the time being, I'm ready to deliver the items of the Todo application as Rest API.
You can check the API view by accessing http: // localhost: 8000 / api /
on the development server.
So far, we've been developing in a local environment, which is common in Django.
CORS
How Django (localhost: 8000
) interacts with React (localhost: 3000
) for json
It is necessary to set CORS (Cross-Origin Resource Sharing).
Let's install django-cors-headers
.
(venv)$\gke-django-tutorial\backend\web-back\python -m pip install django-cors-headers
Update config / settings.py
.
# config/settings.py
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 3rd party
'rest_framework',
'corsheaders',
# Local
'todos.apps.TodosConfig',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMidddleware', #add to
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
##################
# rest_framework #
##################
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
]
}
CORS_ORIGIN_WHITELIST = (
'http://localhost:3000',
)
local_settings.py
Assuming that config / settings.py
will be used in a production environment, create config / local_settings.py
and keep it for local development. By separating settings.py so that CloudSQL is used when deploying GKE and sqlite3 is used locally, it is not necessary to rewrite the setting values.
#File creation
(venv)$\gke-django-tutorial\backend\web-back\type nul > config/local_settings.py
# config/local_settings.py
from .settings import *
DEBUG = True
ALLOWED_HOSTS = ['*']
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
}
}
Start the development server using config / local_settings.py
.
(venv)$\gke-django-tutorial\backend\web-back\python manage.py runserver --settings config.local_settings
Tests
Write a test.
# todos/test.py
from django.test import TestCase
from .models import Todo
class TodoModelTest(TestCase):
@classmethod
def setUpTestData(cls):
Todo.objects.create(title="first todo", body="a body here")
def test_title_content(self):
todo = Todo.objects.get(id=1)
excepted_object_name = f'{todo.title}'
self.assertEqual(excepted_object_name, 'first todo')
def test_body_content(self):
todo = Todo.objects.get(id=1)
excepted_object_name = f'{todo.body}'
self.assertEqual(excepted_object_name, 'a body here')
(venv)$\gke-django-tutorial\backend\web-back\python manage.py test
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
..
----------------------------------------------------------------------
Ran 2 tests in 0.007s
OK
Destroying test database for alias 'default'...
It seems to have worked.
Aggregate static files so that the css of the administrator function is reflected after deployment.
The directory for aggregating static files for distribution is staticfiles /
, and the static file directory added for development is static /
.
#Directory for static file delivery
(venv)$\gke-django-tutorial\backend\web-back\mkdir staticfiles
#Static file development directory
(venv)$\gke-django-tutorial\backend\web-back\mkdir static
#Static file aggregation
(venv)$\gke-django-tutorial\backend\web-back\python manage.py collectstatic
You can see that admin CSS etc. are also added under the staticfiles /
directory.
Use Cloud SQL Postgres when deploying to GKE. You need psycopig2 to use Postgres from Django. Also, use gunicorn to launch the application.
Install the required packages additionally, and put together the Python packages installed in the virtual environment in requirements.txt.
#Package installation
(venv)$\gke-django-tutorial\backend\web-back\python -m pip install wheel gunicorn psycopg2-binary
# requirements.Update txt
(venv)$\gke-django-tutorial\backend\web-back\python -m pip freeze > requirements.txt
When executed, requirements.txt will be created under backend /.
asgiref==3.2.7
Django==3.0.5
django-cors-headers==3.2.1
djangorestframework==3.11.0
gunicorn==20.0.4
psycopg2-binary==2.8.5
python-dotenv==0.13.0
pytz==2019.3
sqlparse==0.3.1
Create a Dockerfile to create a container image on the Django side.
#Creating a Dockerfile
(venv)$\gke-django-tutorial\backend\web-back\type nul > Dockerfile
# .Creating docker ignore
(venv)$\gke-django-tutorial\backend\web-back\type nul > .dockerignore
# backend/web-back/Dockerfile
# set base image
FROM python:3.7
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# set work directory
WORKDIR /code
# install dependencies
COPY requirements.txt ./
RUN python3 -m pip install --upgrade pip setuptools
RUN pip install -r requirements.txt
# Copy project
COPY . ./
# Expose application port
EXPOSE 8000
Also create a .dockerignore to separate the files you don't want to put in the container.
.dockerignore
venv/
.env
Dockerfile
config/local_settings.py
Now you're ready to create a Docker image for Django.
Place the Nginx container as a reverse proxy server in the backend-Pod.
Nginx uses the configuration file in /etc/nginx/conf.d/
to define the reverse proxy function.
Also, at the end of the backend development, I would like to start it with docker-compose, so create a file for docker-compose as well.
#File creation for Nginx
$\gke-django-tutorial\backened\nginx\type nul > Dockerfile
$\gke-django-tutorial\backened\nginx\type nul > Dockerfile.dev
$\gke-django-tutorial\backened\nginx\type nul > default.conf
$\gke-django-tutorial\backened\nginx\type nul > default.dev.conf
I set the reverse proxy so that default.conf
is Nginx container: 80
⇒ Django: 8000
.
The location = / healthz
directive is the health check path you will need after deploying to GKE.
The location / static /
directive is the path for delivering static files. Without this, the CSS of the administrator screen will not be applied. When deploying to GKE, static files will be delivered from Cloud Storage, so delete them.
The server
directive is localhost: 8000
when deploying to GKE, and web-back: 8000
when starting with docker-compose.
This is because when starting with docker-compose, it is necessary to resolve the name by service name. When deploying to GKE, it is in the same pod, so name resolution is possible with localhost: 8000
.
; default.dev.conf
upstream django {
server web-back:8000;
}
; default.For conf
; upstream django {
; server localhost:8000;
; }
server {
listen 80;
location = /healthz {
return 200;
}
location / {
proxy_pass http://django;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_redirect off;
}
;Deleted when deploying GKE
location /static/ {
alias /code/staticfiles/;
}
}
Dockerfile reflects the settings by copying the Nginx configuration file to the Nginx container.
# backend\nginx\Dockerfile.dev
FROM nginx:1.17.4-alpine
RUN rm /etc/nginx/conf.d/default.conf
COPY default.dev.conf /etc/nginx/conf.d
# backend\nginx\Dockerfile.For dev
# COPY default.conf /etc/nginx/conf.d
I would like to use docker-compose to start a container with Nginx + Django configuration.
# docker-compose.Create yml
$\gke-django-tutorial\backend\type nul > docker-compose.yml
version: "3.7"
services:
web-back:
container_name: python-backend
env_file: ./web-back/.env
build: ./web-back/.
volumes:
- ./web-back:/code/
- static_volume:/code/staticfiles # <-- bind the static volume
stdin_open: true
tty: true
command: gunicorn --bind :8000 config.wsgi:application
networks:
- backend_network
environment:
- CHOKIDAR_USEPOLLING=true
- DJANGO_SETTINGS_MODULE=config.local_settings
server:
container_name: nginx
build:
context: ./nginx/.
dockerfile: Dockerfile.dev
volumes:
- static_volume:/code/staticfiles # <-- bind the static volume
ports:
- "8080:80"
depends_on:
- web-back
networks:
- backend_network
networks:
backend_network:
driver: bridge
volumes:
static_volume:
# docker-compose.Start with yml
$\gke-django-tutorial\backend\docker-compose up --build
http: // localhost: 8080
⇒ Nginx container: 80
⇒ Django: 8000
It is port forwarding.
Go to http: // localhost: 8080 / admin /
and see if the CSS is reflected.
The environment where you can start backend development by starting with docker-compose in the local environment is ready.
⇒ Go to (2) frontend development: Nginx + React
Recommended Posts