Code Monkey home page Code Monkey logo

web-full-stack-practice's Introduction

Web Full Stack Practice:Docker + uWSGI + Celery + Django + Supervisor + React + Nginx + Https + Postgres + Redis

本项目主要介绍基于 Docker 的 Web 开发和部署(开发要求在改动代码时服务或页面能够实时发生变化)全流程,来源于日常项目,后端以 Django 为例,前端以 React 为例,使用到的其他模块也可以换成同类产品,比如 uWSGI 可以换成 Gunicorn,数据库可以换成 Mysql 等。我们将通过一个案例前后端分离介绍,这样容易理解。

目标

  • docker-compose 启动前后端同时开发
  • 本地开发 + 正式部署 Https
  • Supervisor + uWSGI + Nginx 部署

特别说明:在前后端联合调试时比较方便,如果单个开发后端或前端,直接本地很多时候会更方便。

关于本地 Https,很多框架本来就是支持的,就不要像本文这么麻烦了。

环境

  • MacOS Mojave 10.14
  • Docker Desktop Community Version 2.0.0.2
    • Engine:18.09.1
    • Compose:1.23.2

产品

作为一名 NLP 算法工程师,我们决定做一个简单的 Language Model 的 Demo,前端用户输入一个词,返回一段自动生成的文本。

模型参考:递归神经网络 | TensorFlow,使用张爱玲作品集的句子作为训练集,800 个句子,52750 字,跑了 150 个 epoch。

后端

后端部分主要包括:uWSGI、Celery、Django、Supervisor、Postgres 和 Redis,首先分别简单介绍一下这些模块的功能:

Step1: Postgres

首先把 db 设置好,如果这里需要用到 redis 也需要一并设置。

我们可以使用 docker-compose up db(在 docker-compose.yml 所在目录执行)只启动 db,然后在本地登陆 db 去创建用户,当然本地也可以直接使用 postgres 作为用户。需要注意的是:host 地址是本机的 IP 地址,Mac 可以使用 ifconfig 查看。

psql -h 192.168.0.103 -U postgres,密码就是启动时创建的超级用户的密码,登陆后最好是更改一下 postgres 的密码,因为环境变量的那个只是启动时用一下。

# create user
create user demo with password "demopassword";
# create db
create database demo;
# grant privileges
grant all privileges ON database demo to demo;

当然,你也可以直接使用 docker run -it --rm --name mypsql -e POSTGRES_PASSWORD=password4superuser -p 5432:5432 -v ~/docker_volume/pg9.5:/var/lib/postgresql/data postgres:9.5 启动 db,启动后可以更改 postgres 用户的密码。

这一步的主要目的就是创建用户和 db ,然后把 data 都映射出来,这样我们后端启动时,就可以通过配置文件连接到 db 了。

Step2: Django

这块主要针对 Django 的流程和注意事项,熟悉或者不需要的可以跳过。

# 初始化项目
mkdir demo && cd "$_"
django-admin startproject demo_backend
cd demo_backend
# 创建一个 app
python manage.py startapp text_generator

到这一步基本的框架就有了,之后就是项目配置和具体代码的编写。一些 Keypoints:

  • Python 环境及开发包管理:pypa/pipenv: Python Development Workflow for Humans.

    • 创建虚拟环境:pipenv --python 3.6.5 # 我的本地环境是 python 3.6.5
    • pipenv shell 可以进入虚拟环境,或使用 pipenv run python xxx.py 等于直接在虚拟环境中运行 python xxx.pypipenv install xx 可以安装需要的依赖,或安装指定版本,比如本例:pipenv install tensorflow==1.12.0
    • 使用之前,将 Pipfile 中的 url 改成 https://pypi.douban.com/simple 或其他速度快的源,Pipfile 能看到所有已安装的包
  • 关于 settings:

    • 将 setting 分为 prod 和 dev 两个文件,分别设置开发和正式环境的参数,需要修改 wsgi.py, manage.py 以便 Django 能够找到配置文件
    • 一般每个 APP 下面都可能有配置文件,编写代码时最好改成可以统一在项目的 settings 这里覆盖。比如训练好的模型文件(详见配置文件 settings/base.py)可以映射出来,这样不但可以方便我们随时更新模型,而且也能减小 image 的大小。
  • 关于 database:

    • 使用配置文件,这样本地开发完部署时只要在服务器用正式的配置文件即可
    • 如果使用 docker-compose,数据库的 host 是:192.168.65.2,而不是 127.0.0.1 或本机地址

涉及到代码相关或相应文件的,项目中均有注释,可直接查看相应文件。

Step3: Celery+Redis

首先代码需要做相应的修改,可以直接查看代码文件。

本地开发环境配置方面需要注意的是:

  • wsgi.py 同目录(settings文件夹同目录)新建 celery.py 并配置,然后修改 __init__.py,再修改 settings/base.py 中的配置
  • 启动 redis,运行:celery -A demo_backend worker -l info 即可

正式环境需要将其作为 daemon 启动,需要配置 conf:

  • demo_backend/celery 目录下,conf 最重要的两个配置是:CELERY_APP="demo_backend"CELERYD_CHDIR="/demo-backend",后者一般是 Dockerfile 后端 server 的根目录,也就是整个后端项目的根目录(settings 文件夹和 celery.py 的上级目录);sh 主要就是把DEFAULT_USER 改成和 conf 一致。

    需要说明的是:这两个文件可以放在任何地方,因为它们最终都是要放到 /etc/ 下面的。

  • user 和 group 一般就选择 root,当然也可以自己创建 user 和 group,官方建议非 root,不过因为我们是在 docker 里面,所以 root 也没有太多问题。

    这里的原因是,root 用户出错时可能会对系统造成一些意想不到的错误,如果系统有其他服务可能会出问题,但我们的 docker 服务是隔离和相对独立的,所以个人觉得使用 root 问题不大;同样的问题在 uWSGI 那里也有。

这里需要说明的是:本例直接等后端返回结果再传给前端,没有使用异步,因为速度不是特别慢。如果需要异步,可以在 task.get() 之前马上将 task id 返给前端,然后由前端根据 task id 获取最终的结果,这样就变成了异步操作。

Step4: uWSGI

按照配置文件配置,需要注意的是,这里不用配置 daemon。可以设置 server 为:socket=app.sock 或直接使用 http(docker 中不能使用 app.sock,因为文件不在一个容器内)。

需要注意的是,这个是在正式环境下使用的,如果在本地开发环境,直接用 python manage.py runserver 0.0.0.0:8000 启动服务即可。

相关参数详细说明可以参考:

Step5: Supervisor

主要目的是把多个服务放在一个容器内启动,官方文档 Run multiple services in a container | Docker Documentation 也有相应的介绍。关于 stopsignal 参数的说明,可以参考:How to use supervisor fo start/stop uWSGI application? - Stack Overflow

Step6: Dockerfile

接下来就是编写 Dockerfile 了,我们可以用一个 Dockerfile 同时满足本地开发和正式部署,主要通过 supervisor 不同的配置文件来实现,本地开发时还需要把整个目录映射出去,这样当文件内容发生变化时,服务会自动刷新。

运行 docker-compose build app 单独 build app,build 完成后可以通过 docker-compose up db redis app 来启动 db、redis 和后端服务,docker-compose stop 停止服务。需要注意的是:

  • backend.local.env 中的 db host 和 redis host 都需要改为 192.168.65.2,这是 docker 服务的默认地址,否则无法连接到 db 和 redis。db host 也可以直接使用 docker-compose.yml 中 db 的 name(如本例中是:db)。
  • 本地开发时,需要把整个项目目录映射出去。但在测试正式环境时不需要(注释掉 docker-compose.yml line 29),因为映射后容器里面目录的内容会被清空,以映射出来的目录为准了,而我们本地并没有设置 Celery 的 deamon;而且 uWSGI 也可能会报错,因为我们没有设置虚拟环境的目录。
  • 本地开发时,command 要写成本地的 Supervisor 配置文件以替换 Docker 里面的正式配置文件。但在测试正式环境时要记得注释掉(docker-compose.yml line 40)。
  • 容器启动后,需要通过 docker exec -it app bash(app 可以替换为 container id)进入后端容器内部执行系列命令,包括:
    • python manage.py makemigrations 生成 db 相关数据
    • python manage.py migrate 将生成的数据 migrate 到 db
    • python manage.py createsuperuser 可以创建管理后台的管理员
    • python manage.py collectstatic 自动输出静态文件到项目根目录
  • 本地开发时,log 会直接输出到屏幕。但在测试正式环境时,日志会映射出来到映射的目录(如本例的 ~/docker_volume/log/),可以直接通过目录文件查看。

到这一步,后端部分就已经完成了,我们可以通过 http://127.0.0.1:8000/admin/ 登陆管理员,也可以通过 http://127.0.0.1:8000/api/ 查看 Rest Framework,或者通过调用 http://127.0.0.1:8000/api/generate/ 生成。后端代码修改后,服务会自动刷新。

前端

刚刚后端的访问是直接通过 ip 地址 + 端口执行的,正式环境中需要用 Nginx 做转发;开发环境下,我们只需用 localhost 直接访问即可。这里稍微有点麻烦的是本地 Https 的配置。

React

前端我们使用 Facebook 的 Create React App · Set up a modern web app by running one command.

npx create-react-app demo_frontend
cd demo_frontend
npm start

上面的代码即可创建一个 App 并启动前端页面,安装依赖直接用 npm i xxx 即可,如果只是在开发环境下使用,可以 npm i xxx --save-dev,然后完成代码编写。

Https

要想在本地开发时使用 https 有两个重要的步骤:

  • 生成证书相关文件
  • 配置 React

生成证书相关文件需要借助:dakshshah96/local-cert-generator: 🚀 A set of scripts to quickly generate a HTTPS certificate for your local development environment.

  • 前三步是让你的本机成为一个 “证书颁发机构”,将第二步生成的 rootCA.pem 双击添加后,都改为 “信任” 即可。如图所示:

  • 第二步有几个需要注意的地方:

    • Enter pass phrase for rootCA.key: 输入一个自己定义的密码(要输三次),要记住,以后每次为域名生成证书时都需要输入
    • 后面的除了 Common Name 都可以回车跳过,这个是证书颁发机构的名称,输入 Local CertMy PC Cert 之类的都可以
  • 然后运行第四步生成 localhost 的证书,或者使用仓库中的 g_ssl_for_domain.sh./g_ssl_for_domain.sh localhost /path/to/store/ssl/file/,两个参数分别是你网站的域名和生成 ssl 文件的存储目录。我们需要 server.crtserver.key 就可以了,为了方便之后的操作,我把这两个文件放到了 demo_frontend/ssl.localhost 目录下。

然后要配置 React,这里我们需要安装 timarney/react-app-rewired: Override create-react-app webpack configs without ejectingnpm i react-app-rewired --save-dev

  • 首先在项目根目录下创建一个 config-overrides.js 的文件,配置将在里面进行,详见配置文件。这里我们用了一下环境变量,指定 server.crt, server.key 的位置
  • 然后根据官方说明,修改 package.json,将相应的 react-scripts 改为 react-app-rewired

最后就是 Dockerfile 的编写了,由于本地开发,所以这里会比较简单,只要有 node 环境即可。

然后我们就可以使用 docker-compose up redis db app web 来启动所有服务了,然后通过 https://localhost:3000 访问前端,我们可以看下证书,没错,是我们的 My PC Cert 颁发的。

尝试生成一句:

我们可以修改前端代码,由于目录映射页面会重新编译、自动刷新。最后记得 npm run build 生成静态文件。

Nginx

NGINX | High Performance Load Balancer, Web Server, & Reverse Proxy 一般会用在产品部署上,作为代理和静态资源服务器,接下来我们主要介绍如何在本地调试 Nginx。主要有以下几步:

  • 生成域名的证书,我们随便用一个名字,比如 naivegenerator.com
  • 编写 Nginx 配置文件
  • 编写 Dockerfile 并 build image
  • 修改 Host

生成域名证书时,需要将 v3.ext 中的 DNS.1 = naivegenerator.com 修改掉,然后执行 sh createSelfSigned.sh 即可生成新域名的证书,我们将其放在前端根目录的 ssl 目录下。

Nginx 配置文件我们主要编写 nginx.proj.conf 即可,各配置详细说明可以直接看文件,有个地方需要注意下,如果 location 块使用 /xxx/ 时,proxy_pass 后面加斜杠和不加斜杠结果会不一样,举个例子:

server
{
    listen 80;
    server_name: www.naivegenerator.com;
    location /api/ {
        proxy_pass http://127.0.0.1:8000; # 配置1
        proxy_pass http://127.0.0.1:8000/; # 配置2
    }
}

使用配置 1 时,请求 http://www.naivegenerator.com/api/generate/ 时会被成功转到:http://127.0.0.1:8000/api/generate/,而使用配置 2 时,则会被转到:http://127.0.0.1:8000/generate/。请求静态文件也是一样,所以这里需要稍微注意下。

然后是编写 Dockerfile(最好创建并编写一下 .dockerignore 将 node modules 忽略掉),这里面有四个地方要强调一下:

  • 正式部署时,一般需要先在服务器上放一个验证文件,能点击下载后才能获得 server.keyserver.crt,比如 SSL For Free - Free SSL Certificates in Minutes,但我们本地因为是自己电脑给的证书,所以这一步不需要。当然如果你采用其他的证书授予商,也可能有不同的要求。除了上面那个免费的 SSl 外,还有很多,大家可以上网搜一搜,比如:Getting Started - Let's Encrypt - Free SSL/TLS Certificates
  • 关于 ARG 和 ENV 详细情况大家可以看一下官网,build image 时可以使用 arg。本例中,我们使用 ENV BACKEND_HOST=${backend_host} 设置了一个环境变量,变量值从 docker-compose 中 nginx build 的 arg 中取变量名为 {backend_host} 的变量,然后将该环境变量传入配置文件,让其生效。这么做的目的主要是因为本地 docker-compose up 后 Nginx 访问后端服务需要使用 192.168.65.2,而不是 127.0.0.1,正式部署时在 k8s 上可以使用后者。
  • 前端的静态文件在运行 npm run build 后会自动生成 build 文件夹,我们在 nginx.proj.conf 中将其设置为主页的根目录;后端(admin 和 rest)的静态文件则需要后端用 python manage.py collectstatic 收集后在 Dockerfile 中复制到前端某个地址。需要说明的是,因为我们将这个 static 目录共享了,所以虽然我们把本地目录整个映射出去了,但登陆 app 容器运行上面的命令后本地依然看不到 static 下的文件。因为 docker 会自动创建一个 volume(只要运行 docker volume ls 就看到了),文件就在这共享的 volume 里(本例中位 demo_app_static)。那怎么办呢?有三种办法:
    • 注释掉共享的目录,重新启动运行;

    • 本地生成;

    • 找到文件的实际位置然后复制出来,关于 volume 的详细情况可以通过 docker volume inspect demo_app_static 查看,运行 screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty 然后进入 inspect 的目录就可以看到了。注意需要运行一个 docker 才能复制,步骤如下:

      • docker run -it --rm -v volume_name:/home_or_other_dir container /bin/bash
      • docker cp container_name:/home_or_other_dir /your_local_path

      因为这里的 volume 并没有在你 docker-compose up 起来的容器里,所以我们需要先将它映射到一个容器里再复制。

  • 关于 Dockerfile 中 ENV 替换到 nginx 配置文件需要特别注意,可以参考这里:configuration - nginx 'invalid number of arguments in "map" directive' - Stack Overflow

最后别忘了修改 host:sudo vim /etc/hosts,添加一行:127.0.0.1 naivegenerator.com,这样当我们访问 naivegenerator.com 时等于是访问了 127.0.0.1

最终效果

重新 docker-compose up redis db app nginx 后我们可以打开 https://naivegenerator.com,然后输入 “爱情” 让模型随机生成,如图所示:

admin 网址:https://naivegenerator.com/admin,如图所示:

rest 网址:https://naivegenerator.com/api, 如图所示:

注意事项

  • 如果 docker-compose stop 后还想把生成的 container 也删掉(因为各种报错我们可能会不停地 build,有时候之前的需要彻底删除),可以使用这个命令:docker stop $(docker ps -a -q); docker rm $(docker ps -a -q); docker volume rm $(docker volume ls -qf dangling=true)

  • 由于本例把所有操作整合在一起了,有些童鞋如果想要了解每一步的细节,可以在本地把环境设置好,使用 docker-compose 单独 up 启动 db 和 redis,然后在本地操作后端交互。无论是 up 还是 stop,都须在 docker-compose.yml 所在目录执行。

  • docker-compose 中 build 之外的字段对 build 没有影响,build 主要受 Dockerfile 的影响;Dockerfile 如果名字不是 Dockerfile 需要指定文件名。

  • 本例把 backend.local.envfrontend.local.env 一起上传了,但在正式项目中,大家务必把 *.env 添加到 .gitignore 中,这样信息就不会泄露了。

  • 本例使用了 Celery 但后端代码并没有写成异步;同样使用了 Redis 但并未用在后端服务,而是用作了 Celery 的 Broker。

  • 本例中的各个模块(Docker)除外都可以替换为同类其他产品,前后端自不必说,uWSGI 有 Guniron,Postgres 有 Mysql、MongoDB 等等,但我觉得基本思路是类似的,我们的目的是 Docker 化。

  • 关于 Docker 操作的,**省一个同胞写的 twtrubiks/docker-tutorial: Docker 基本教學 - 從無到有 Docker-Beginners-Guide 教你用 Docker 建立 Django + PostgreSQL 📝 还不错,另外官方文档也非常赞,所以我就没有废话了。我在他另一个项目里也学到了一些(第一个参考文献),推荐新手看看。

  • 本项目可优化之处还有很多,比如 Redis 用于后端服务、比如异步操作、比如 uWSGI socket 方式等等,不一而足,还请大家结合自己的实际情况灵活使用。另外,各个部分深入后都会有另外一些坑,比如 postgres create db 的编码问题,我会在其他文章中分享,欢迎大家关注。

小结

本项目主要介绍基于 Docker 的 Web 全栈开发系列,看似东西很多,但其实每个地方都没有过多深入,项目也非常简单,所以适合 beginners。如果大家觉得看起来好像非常繁琐那也是正常的,因为确实会有一点繁琐,但只要仔细点理清每个地方其实并不难。我们最后把整个项目的目录列出来并整体总结一下:

tree -L 2
.
├── Dockerfile_local				# 前端本地的 Dockerfile
├── Dockerfile_nginx				# 前端 Nginx 的 Dockerfile
├── Dockerfile_server				# 后端 Server 的 Dockerfile
├── backend.local.env				# 后端 Server 在 docker-compose.yml 中的环境变量
├── demo_backend				# 后端项目目录
│   ├── Pipfile					# 项目依赖管理文件
│   ├── Pipfile.lock				# 同上,lock 文件
│   ├── celery					# Celery 的配置文件,可以放在任何地方
│   ├── demo_backend				# 项目配置文件和入口
│   ├── manage.py				# 本地开发入口文件
│   ├── static					# 后端 admin 和 rest 静态文件
│   ├── supervisor-master.zip			# supervisor repo,build 时下载太慢,采用本地安装
│   ├── supervisord.conf			# Supervisor 正式环境配置文件
│   ├── supervisord.local.conf			# Supervisor 开发环境配置文件
│   ├── supervisord.log				# Supervisor 运行时的 log 文件
│   ├── text_generator				# 后端的 App,本例只有一个
│   └── uwsgi.ini				# uWSGI 配置文件,用于正式环境
├── demo_frontend				# 前端项目目录
│   ├── build					# npm run build 后生成的静态文件
│   ├── config-overrides.js			# 覆盖配置 (本地 https) 使用的配置文件
│   ├── node_modules				# node modules
│   ├── package-lock.json			# package lock 文件
│   ├── package.json				# package 依赖
│   ├── public					# public 文件
│   ├── src					# 前端源代码
│   ├── ssl					# 域名 host ssl 证书相关文件
│   └── ssl.localhost				# localhost ssl 证书相关文件
├── docker-compose.yml				# docker-compose 配置文件
├── frontend.local.env				# 前端开发在 docker-compose.yml 中的环境变量
├── nginx.conf					# nginx 默认配置文件
└── nginx.proj.conf				# nginx 项目配置文件

后端的 supervisor 和 celery 文件夹以及前端的 ssl 和 ssl.localhost 理论上是可以放在其他地方的,放在这些位置只是方便开发。

很多文件或文件夹是可以 ignore 的,比如前端的 build,后端的 supervisord.log 以及模型文件等,不过为了更加方便大家查看就都推上去了。

这样我们就把前后端用 docker 完全地整合在一起,在全栈开发时可以前后端同时开发调试,正式上线时也可以快速完成部署。

另外,把映射的日志文件目录也放在这里:

cd ~/docker_volume/log
tree -L 2
.
├── celery
│   ├── err.log
│   ├── out.log
│   ├── worker1-1.log
│   ├── worker1-2.log
│   ├── worker1-3.log
│   ├── worker1-4.log
│   ├── worker1-5.log
│   ├── worker1-6.log
│   ├── worker1-7.log
│   ├── worker1-8.log
│   └── worker1.log
├── nginx
│   ├── access.log
│   ├── demo.access.log
│   ├── demo.error.log
│   └── error.log
└── uwsgi
    ├── demo.uwsgi.log
    ├── err.log
    └── out.log

分别是 celery、uwsgi 和 nginx 的日志文件,nginx 的 demo.* 就是我们针对项目做得配置,celery 的 err.logout.log 是我们在 Supervisor 中做得配置,其余的则是 celery 的 conf 文件做得配置(celeryd.conf line 24:CELERYD_OPTS="--time-limit=300 --concurrency=8")。建议把一个项目的日志放(或映射)在一个地方,无论是本地开发还是正式部署。

有些 log 是没必要的,比如 uwsgi,如果配置本身设置了的话,supervisor 那里可以不用设置。

参考文献和资源

以下主要罗列使用过的参考文献和一些还不错的资源,简单的归了下类,大家按需取用。

后记

这篇文章包括这个项目花了一天时间才完成,这还是在已经对各模块都有一些经验的情况下。很多地方都踩过坑,说这篇文章的内容 “坑坑洼洼” 也不夸张,尤其是我提到要注意或者特别强调的点。在解决这些坑的过程中得到不少同事的帮助,尤其是 scottming (Scott Ming) 在本地 Https 和 Dockerfile 相关的配置中给予了很多启发和提示。最后,希望这个 demo 项目和文章能对大家有所帮助,如果有 Google、Stackoverflow 没有找到答案且与此项目相关的问题或本项目疏漏的地方,欢迎大家 Issue。

CHANGELOG

  • 20190303 创建

web-full-stack-practice's People

Contributors

hscspring avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.