使用Nginx + Django在GKE上从零开始部署Django+React(1)后端开发

使用Nginx + Django进行开发,在GKE上从零部署Django+React的后端。(1)

想要做的事情

由于在Django+React构架下开发应用并将其部署到Google Kubernetes Engine的过程中,并无完整的教程可供参考,因此我撰写了这篇文章。

我认为虽然还有一些地方不完善,但是如果有一点经验的人,应该可以立即使用。

请留意

這是一位未曾有經驗的興趣工程師為了建立個人作品集而努力解決部署問題的記錄。如果有任何疏漏之處,敬請指正。

瞄准的姿态 de zī

环境

$ 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

首先在本地开始

创建一个目录

# プロジェクトフォルダの作成
$ mkdir gke-django-tutorial
$ cd gke-django-tutorial

# ディレクトリを作成する
$\gke-django-tutorial\mkdir backend
$\gke-django-tutorial\mkdir frontend

后端开发(Django版)

后端的Pod会使用Django-rest-framework来提供RestAPI。
关于后端的Pod,我会进行整理。

役割コンテナイメージプロキシサーバーNginx:1.17.4-alpineアプリケーションPython3.7 – Django rest frameworkcloud_sql_proxygcr.io/cloudsql-docker/gce-proxy

在后端(backend)创建目录。

# backendディレクトリに移動
$\gke-django-tutorial\cd backend

# djangoディレクトリの作成
$\gke-django-tutorial\backend\mkdir web-back

# Nginxディレクトリの作成
$\gke-django-tutorial\backend\mkdir nginx

用Django开始项目。

在中文中创建Python虚拟环境,并使用Django REST framework开发API服务器。这将在backend\web-back\目录下创建。

# web-backディレクトリ
$\gke-django-tutorial\backend\cd web-back

# Pythonの仮想環境作成
$\gke-django-tutorial\backend\web-back\python -m venv venv

# 仮想環境の有効化
$\gke-django-tutorial\backend\web-back\venv\Scripts\activate

# Pythonパッケージのインストール
(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

# Djangoのプロジェクトを始める
(venv)$\gke-django-tutorial\backend\web-back\django-admin startproject config .

在web-back目录下,通过运行django-admin startproject config .命令,成功创建了一个名为config的Django项目文件夹。

让我们检查一下本地服务器是否已经启动。

(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.

当开发服务器启动后,您可以访问http://localhost:8000/,以确认安装成功的页面为”The install worked successfully!”。

设置.py

请编辑config/settings.py文件以包含基本配置。
将应该保密的settings.py信息记录在.env文件中,不要公开。
使用python-dotenv包来使用.env文件中记录的信息。

# .envファイルの作成
(venv)$\gke-django-tutorial\backend\web-back\type nul > .env
# config/settings.py

import os
from dotenv import load_dotenv  # 追加

# 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)  # 追加

# .envの読み込み
load_dotenv(os.path.join(BASE_DIR, '.env'))  # 追加

# 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 = ["*"]  # 変更


# 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')],  # 変更
        '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'  # 変更

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/'

# 開発環境下で静的ファイルを参照する先
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] # 追加

# 本番環境で静的ファイルを参照する先
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') # 追加

# メディアファイルpath
MEDIA_URL = '/media/' # 追加

# .env
SECRET_KEY = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
DEBUG = False

添加应用程序

让我们一起开发一个待办事项应用程序吧。

(venv)$\gke-django-tutorial\backend\web-back\python manage.py startapp todo

在config/settings.py的INSTALLED_APPS中添加todo和rest_framework。

# 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',
]

# 追加
REST_FRAMEWORK = {
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ]
}

rest_framework.permissions.AllowAny是用来取消django-rest-framework默认设置’DEFAULT_PERMISSION_CLASSES’的隐式设置的。

todo/models.py
待办/模型.py

我会创建一个任务应用程序模型。

# 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

将在 todo/admin.py 中添加已创建的模型。

# todo/admin.py
from django.contrib import admin
from .models import Todo


admin.site.register(Todo)

我要进行迁移。

(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

创建超级用户

创建管理员用户。

(venv)$\gke-django-tutorial\backend\web-back\python manage.py createsuperuser
ユーザー名 (leave blank to use '[YOUR_NAME]'): [USER_NAME]
メールアドレス: YOUR_MAIL_ADDRESS@MAIL.COM
Password:
Password (again):
Superuser created successfully.

当您启动开发服务器并访问http://localhost:8000/admin/时,将显示Django管理网站的登录界面。请输入您设置的用户名和密码以进行登录。

当您成功登录后,您可以查看您创建的Todo应用程序的表格。
让我们先添加2到3个项目。

网址

在config/urls.py中添加至todo应用的路由。

# 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'))  # 追加
]

待办事项/urls.py

我将创建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())
]

待办事项/序列化器.py

我将创建一个序列化器,以便将模型实例简单地转换为JSON格式。

(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')

如果在字段(fields)中,没有在模型(model)中指定Primary Key(主键),Django会自动添加一个id。

的任务/视图.py

在使用Django rest framework创建views.py时,需要继承rest_framework.generics下的~~APIView类。

# 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

尽管路由器等设备尚未配置好,但我们已经准备好将Todo应用程序的项目作为Rest API进行发布。
您可以通过在开发服务器上访问http://localhost:8000/api/来确认API视图。

到目前为止,这是常见的在Django中进行本地环境开发的方式。

CORS 的中文释义是跨源资源共享。

要使Django(localhost:8000)与React(localhost:3000)进行JSON交互,需要进行CORS(跨源资源共享)的设置。

让我们安装django-cors-headers库。

(venv)$\gke-django-tutorial\backend\web-back\python -m pip install django-cors-headers

将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',  # 追加
    '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',
)

本地设置.py

为了在生产环境中使用,我们假定config/settings.py,并创建config/local_settings.py,以便在本地开发中进行区分。使用CloudSQL进行GKE部署,本地使用sqlite3,通过将settings.py分开,我们可以避免修改配置值。

# ファイルの作成
(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'),
    }
}

请使用 config/local_settings.py 启动开发服务器。

(venv)$\gke-django-tutorial\backend\web-back\python manage.py runserver --settings config.local_settings

考试

我要写考试。

# 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'...

看起来进展顺利。

靜態文件

在部署之后,我们会将静态文件进行汇总,以便管理者功能的CSS能够得到反映。
用于发布的静态文件会被汇总到staticfiles/目录中,而用于开发的静态文件目录则是static/。

# 静的ファイル配信用ディレクトリ
(venv)$\gke-django-tutorial\backend\web-back\mkdir staticfiles

# 静的ファイル開発用ディレクトリ
(venv)$\gke-django-tutorial\backend\web-back\mkdir static

# 静的ファイルの集約
(venv)$\gke-django-tutorial\backend\web-back\python manage.py collectstatic

可以确认在staticfiles/目录下也添加了admin的CSS等文件。

添加Python软件包

在部署到GKE时,使用Cloud SQL的Postgres。
要在Django中使用Postgres,需要psycopg2库。
另外,使用gunicorn来启动应用程序。

我們將安裝必要的套件並將安裝在虛擬環境中的Python套件整理至requirements.txt檔案中。

# パッケージのインストール
(venv)$\gke-django-tutorial\backend\web-back\python -m pip install wheel gunicorn psycopg2-binary

# requirements.txtの更新
(venv)$\gke-django-tutorial\backend\web-back\python -m pip freeze > requirements.txt

运行后,将在backend/目录下生成requirements.txt文件。

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

创建Dockerfile

创建一个Dockerfile以制作Django的容器映像。

# Dockerfileの作成
(venv)$\gke-django-tutorial\backend\web-back\type nul > Dockerfile

# .dockerignoreの作成
(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

您可以创建 `.dockerignore` 文件来指定不希望放入容器中的文件。

venv/
.env
Dockerfile
config/local_settings.py

只需一个选项:

现在我们已经准备好创建一个关于Django的Docker镜像。

后端开发(Nginx部分)

将Nginx容器作为后端Pod中的反向代理服务器来配置。 Nginx将使用/etc/nginx/conf.d/目录中的配置文件来定义反向代理功能。

此外,在开发后端的最后阶段我还想尝试使用docker-compose进行启动,所以我会创建一个docker-compose用的文件。

# 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

我已经设置了反向代理,将 default.conf 配置为 Nginx 容器的 80 端口并指向 Django 的 8000 端口。

location = /healthz指令是GKE部署后所需的用于健康检查的路径。
location /static/指令是用于提供静态文件的路径。如果没有这个路径,管理界面的CSS将无法正常应用。在GKE部署时,静态文件将从Cloud Storage进行提供,因此将其删除。

如果在GKE上部署,服务器指令将设为localhost:8000,如果在docker-compose中启动,则设为web-back:8000。这是因为在docker-compose中启动时需要通过服务名称进行名称解析。在GKE上部署时,由于位于同一Pod内,可以通过localhost:8000进行名称解析。

; default.dev.conf
upstream django {
    server web-back:8000;
}

; default.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;
    }

; GKEデプロイ時には削除
    location /static/ {
        alias /code/staticfiles/;
    }
}

通过将Nginx配置文件复制到Nginx容器中的Dockerfile,使配置生效。

# 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.devの場合
# COPY default.conf /etc/nginx/conf.d

开发后端(使用docker-compose进行)

我想使用docker-compose在Nginx+Django配置中启动容器。

# docker-compose.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.ymlで起動する
$\gke-django-tutorial\backend\docker-compose up --build

http://localhost:8080被转发到Nginx容器的80端口,再转发到Django的8000端口。

让我们访问 http://localhost:8080/admin/,并确认CSS是否已经生效。

在本地环境下,使用docker-compose启动,已经准备好了用于后端开发的环境。

前端开发:使用Nginx + React实现

bannerAds