草庐IT

部署前后端为独立的 Docker 节点

Zewan Blog 2023-03-28 原文

『服务器部署 Vue 和 Django 项目的全记录』一文中,介绍了在服务器中使用 Nginx 部署前后端项目的过程。然而,当 Web 应用流量增多时,需要考虑负载均衡、流量分发、容灾等情况,原生的部署方式通常难以满足需求。此时,引入 Docker 部署多节点,能够在单台高性能服务器或服务器集群中搭建更完善的部署架构。

本文主要以 Vue 和 Django 项目为例介绍 Docker 部署的流程,稍带 Docker 简介和基础的 Nginx 负载均衡配置。

Docker 简介与安装

简单介绍 Docker 相关概念,具体需要读者另外学一学,推荐『Docker-从入门到实践』

Docker 是什么

Docker 是一个开源的应用容器引擎,可以让开发者打包应用和依赖到一个轻量级、可移植的容器中,并发布到任何流行的 Linux 机器上,也可以实现虚拟化。容器使用沙箱机制,相互之间不存在接口,其与宿主机通过端口转发进行通信,性能开销低。

Docker 部署 Web 应用有以下优点:

  • 容器适合持续集成和持续交付(CI/CD)流程
  • 响应式部署和扩展,其可移植性和轻量级的特性支持实时扩展或拆除服务
  • Docker 轻巧快速,支持开发者在同一机器上运行更多工作负载

Docker 工作流程

Docker 包括三个概念:

  • 镜像(Image):相当于一个 root 文件系统。
  • 容器(Container):镜像和容器的关系类似于对象程序设计中的类和实例,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
  • 仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。

Docker 的工作流程通常为:

  1. 从仓库中拉取(pull)官方或基准镜像
  2. 在 Dockerfile 中描述应用和安装依赖的指令,构建镜像
  3. 由镜像创建和运行容器

Docker 安装

『Ubuntu 安装 Docker 环境』.

部署架构

在不考虑多节点负载均衡时,本文的部署架构如下:

前后端项目分离部署,分别部署在两个 Nginx 节点,对应两个域名或两个端口。

Nginx + Docker 部署前端

首先,Vue 项目打包为 dist 文件夹,同目录下新建 Dockerfilevhosts.conf 文件和 logs 文件夹,作用见下列代码块中的注释。

.
├── dist            # Vue 项目打包用以部署的文件夹
├── Dockerfile      # 用于建立 Docker 镜像
├── vhosts.conf     # 容器中启动 Nginx 服务的配置文件
└── logs            # 映射容器中的 Nginx 日志目录,以便在宿主机查看日志

Dockerfile 文件内容:

# 设置基础镜像
FROM nginx:latest
#设置CTS时区
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
# 将dist文件中的内容复制到 /usr/share/nginx/html/ 这个目录下面
COPY ./dist /usr/share/nginx/html/
#用本地的 vhosts.conf 配置来替换 nginx 镜像里的默认配置
COPY vhosts.conf /etc/nginx/conf.d/vhosts.conf

vhosts.conf 文件内容:

server {
    listen       80;
    server_name  localhost;
    access_log  /var/log/nginx/host.access.log  main;
    error_log  /var/log/nginx/error.log  error;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
        try_files $uri $uri/ /index.html;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}

构建 Docker 镜像

在终端中输入以下命令,根据 Dockerfile 构建镜像。

docker build -f Dockerfile -t nginx/hsheng-mall:v1.0.0 .
# 镜像名称:nginx/hsheng-mall
# 版本号:v1.0.0

运行 Docker 容器

依据创建的镜像 nginx/hsheng-mall:v1.0.0 运行容器。

docker run -d -p 8081:80 --name=hsheng-mall -v /home/hsheng/www/hsheng-mall/logs:/var/log/nginx nginx/hsheng-mall:v1.0.0
# 宿主机 8081 端口映射容器 80 端口
# 容器名称:hsheng-mall
# 宿主机 /home/hsheng/www/hsheng-mall/logs 目录映射容器 /var/log/nginx 目录
# 镜像名称:nginx/hsheng-mall:v1.0.0

宿主机 Nginx 转发

与原生 Nginx 部署类似,在 /etc/nginx/conf.d 目录下创建配置文件 hsheng-mall.conf,内容如下:

server {
    listen 443 ssl; # 端口,若部署 https 域名则为 443
    server_name aaa.abc.com; # 域名或 IP

    location / {
        proxy_pass http://127.0.0.1:8081;   # 转发本机(宿主机) 8081 端口,已与 Docker 端口建立映射
        proxy_redirect default;
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall/ssl_certs/aaa.abc.com_bundle.crt;    # ssl证书绝对路径
    ssl_certificate_key  /home/hsheng/www/hsheng-mall/ssl_certs/aaa.abc.com.key;    # ssl证书私钥绝对路径
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name aaa.abc.com;
    # 把http的域名请求转成https
    return 301 https://$host$request_uri;
}

而后重启 Nginx 服务,即可通过 server_name 访问 Docker 的应用服务。

sudo nginx -s reload

Nginx + Docker 部署 uWSGI 后端

项目部署目录结构如下:

.
├── src                     # Django 项目源码
│   ├── manage.py
│   ├── requirements.txt    # Python 项目依赖包
│   ├── uwsgi.ini           # uWSGI 配置文件
│   ├── start.sh            # Django 服务启动脚本
|   └── ...
├── Dockerfile              # 用于建立 Docker 镜像
└── logs                    # 映射容器中的 uWSGI 日志目录,以便在宿主机查看日志

Dockerfile 文件内容:

FROM python:3.8
RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' > /etc/timezone
RUN mkdir -p /var/www/html/backend
COPY ./src /var/www/html/backend/
WORKDIR /var/www/html/backend

RUN pip install -i https://pypi.doubanio.com/simple uwsgi
RUN pip install -i https://pypi.doubanio.com/simple/ -r requirements.txt

# Windows环境下编写的start.sh每行命令结尾有多余的\r字符,需移除
RUN sed -i 's/\r//' ./start.sh
RUN chmod +x ./start.sh

uwsgi.ini 文件内容:

[uwsgi]
socket = 0.0.0.0:8000   # 容器内 uWSGI 服务须为 0.0.0.0,以便与宿主机建立正常连接
project = backend
base = /var/www/html
base-app = hshengmall
chdir = %(base)/%(project)
wsgi-file = %(base)/%(project)/%(base-app)/wsgi.py
master = true
processes = 8
threads = 4
enable-threads = true
buffer-size = 65536
post-buffering = 32768
vacuum = true
pidfile = %(base)/uwsgi/%(project)-master.pid
daemonize = %(base)/uwsgi/uwsgi.log
chmod-socket = 664
# 设置一个请求的超时时间(秒),如果一个请求超过了这个时间,则请求被丢弃
harakiri = 300
# 当一个请求被harakiri杀掉会,会输出一条日志
harakiri-verbose = true

start.sh 文件内容:

python manage.py makemigrations&&
python manage.py migrate&&
uwsgi --ini /var/www/html/backend/uwsgi.ini

构建 Docker 镜像

在终端中输入以下命令,根据 Dockerfile 构建镜像。

docker build -f Dockerfile -t python/hsheng-mall-backend:v1.0.0 .

运行 Docker 容器

依据创建的镜像 python/hsheng-mall-backend:v1.0.0 运行容器。

docker run -it -p 8001:8000 --name=hsheng-mall-backend -v /home/hsheng/www/hsheng-mall-backend/logs:/var/www/html/uwsgi -d python/hsheng-mall-backend:v1.0.0

启动服务

进入容器:

docker exec -it <container_id> /bin/bash

运行启动脚本:

./start.sh

这样即成功启动了一个后端服务容器,若想做多节点负载均衡,可以修改端口映射关系,按上述步骤多创建和启动几个容器。

宿主机 Nginx 转发

/etc/nginx/conf.d 目录下创建配置文件 hsheng-mall-backend.conf,内容如下:

server {
    listen 443 ssl;
    server_name api-aaa.abc.com;

    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass 127.0.0.1:8001;  # 转发本机(宿主机) 8001 端口,已与 Docker 端口建立映射
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com_bundle.crt;
    ssl_certificate_key  /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name api-aaa.abc.com;
    return 301 https://$host$request_uri;
}

而后重启 Nginx 服务,即可通过 server_name 请求后端服务。

sudo nginx -s reload

*Nginx 负载均衡

简略描述一个负载均衡的结构:Nginx + Docker 多节点部署架构。

前端节点为静态节点,通常只需要单个节点即可,可使用 CDN 加速优化访问。因此,当请求流量大时,主要通过增多后端 Docker+uWSGI 节点进行负载均衡。

为实现上图架构,首先根据本文第四章节「Docker 部署 uWSGI 后端节点」创建和启动 3 个 Docker 后端服务节点,分别映射至宿主机 8001 ~ 8003 端口。

而后,修改宿主机用于部署后端的 Nginx 配置文件,例如本文的 hsheng-mall-backend.conf,添加 upstream。修改后文件内容应为:

upstream uwsgicluster {
    server 127.0.0.1:8001;
    server 127.0.0.1:8002;
    server 127.0.0.1:8003;
}
server {
    listen 443 ssl;
    server_name api-aaa.abc.com;

    location / {
        include /etc/nginx/uwsgi_params;
        uwsgi_pass uwsgicluster  # 转发上游集群
    }

    ssl_certificate   /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com_bundle.crt;
    ssl_certificate_key  /home/hsheng/www/hsheng-mall-backend/ssl_certs/api-aaa.abc.com.key;
    ssl_session_timeout 5m;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE;
    ssl_prefer_server_ciphers on;
}

server {
    listen 80;
    server_name api-aaa.abc.com;
    return 301 https://$host$request_uri;
}

可以看到,与单节点不同之处在于,新增了 upstream 定义,并将 uwsgi_pass 修改为定义的 upstream 名称。

在配置文件中,还能设置各个节点的权重分配等,此处不展开介绍,默认为轮询方式,请求随机派发到各节点。

有关部署前后端为独立的 Docker 节点的更多相关文章

  1. ruby-on-rails - 独立 ruby​​ 脚本的配置文件 - 2

    我有一个在Linux服务器上运行的ruby​​脚本。它不使用rails或任何东西。它基本上是一个命令行ruby​​脚本,可以像这样传递参数:./ruby_script.rbarg1arg2如何将参数抽象到配置文件(例如yaml文件或其他文件)中?您能否举例说明如何做到这一点?提前谢谢你。 最佳答案 首先,您可以运行一个写入YAML配置文件的独立脚本:require"yaml"File.write("path_to_yaml_file",[arg1,arg2].to_yaml)然后,在您的应用中阅读它:require"yaml"arg

  2. ruby-on-rails - 每次我尝试部署时,我都会得到 - (gcloud.preview.app.deploy) 错误响应 : [4] DEADLINE_EXCEEDED - 2

    我是Google云的新手,我正在尝试对其进行首次部署。我的第一个部署是RubyonRails项目。我基本上是在关注thisguideinthegoogleclouddocumentation.唯一的区别是我使用的是我自己的项目,而不是他们提供的“helloworld”项目。这是我的app.yaml文件runtime:customvm:trueentrypoint:bundleexecrackup-p8080-Eproductionconfig.ruresources:cpu:0.5memory_gb:1.3disk_size_gb:10当我转到我的项目目录并运行gcloudprevie

  3. ruby-on-rails - Ruby on Rails 可以部署在 Azure 网站上吗? - 2

    我可以在Azure网站上部署RubyonRails吗? 最佳答案 还没有。目前仅支持.NET和PHP。 关于ruby-on-rails-RubyonRails可以部署在Azure网站上吗?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/12964010/

  4. Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting - 2

    1.错误信息:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:requestcanceledwhilewaitingforconnection(Client.Timeoutexceededwhileawaitingheaders)或者:Errorresponsefromdaemon:Gethttps://registry-1.docker.io/v2/:net/http:TLShandshaketimeout2.报错原因:docker使用的镜像网址默认为国外,下载容易超时,需要修改成国内镜像地址(首先阿里

  5. jenkins部署1--jenkins+gitee持续集成 - 2

    前置步骤我们都操作完了,这篇开始介绍jenkins的集成。话不多说,看操作1、登录进入jenkins后会让你选择安装插件,选择第一个默认的就行。安装完成后设置账号密码,重新登录。2、配置JDK和Git都需要执行路径,所以需要先把执行路径找到,先进入服务器的docker容器,2.1JDK的路径root@69eef9ee86cf:/usr/bin#echo$JAVA_HOME/usr/local/openjdk-82.2Git的路径root@69eef9ee86cf:/#whichgit/usr/bin/git3、先配置JDK和Git。点击:ManageJenkins>>GlobalToolCon

  6. C51单片机——实现用独立按键控制LED亮灭(调用函数篇) - 2

    说在前面这部分我本来是合为一篇来写的,因为目的是一样的,都是通过独立按键来控制LED闪灭本质上是起到开关的作用,即调用函数和中断函数。但是写一篇太累了,我还是决定分为两篇写,这篇是调用函数篇。在本篇中你主要看到这些东西!!!1.调用函数的方法(主要讲语法和格式)2.独立按键如何控制LED亮灭3.程序中的一些细节(软件消抖等)1.调用函数的方法思路还是比较清晰地,就是通过按下按键来控制LED闪灭,即每按下一次,LED取反一次。重要的是,把按键与LED联系在一起。我打算用K1来作为开关,看了一下开发板原理图,K1连接的是单片机的P31口,当按下K1时,P31是与GND相连的,也就是说,当我按下去时

  7. 深度学习部署:Windows安装pycocotools报错解决方法 - 2

    深度学习部署:Windows安装pycocotools报错解决方法1.pycocotools库的简介2.pycocotools安装的坑3.解决办法更多Ai资讯:公主号AiCharm本系列是作者在跑一些深度学习实例时,遇到的各种各样的问题及解决办法,希望能够帮助到大家。ERROR:Commanderroredoutwithexitstatus1:'D:\Anaconda3\python.exe'-u-c'importsys,setuptools,tokenize;sys.argv[0]='"'"'C:\\Users\\46653\\AppData\\Local\\Temp\\pip-instal

  8. Ruby,使用包含 TK GUI 的 ocra 部署一个 exe - 2

    Ocra无法处理需要“tk”的应用程序require'tk'puts'nope'用奥克拉http://github.com/larsch/ocra不起作用(如链接中的一个问题所述)问题:https://github.com/larsch/ocra/issues/29(Ocra是1.9的"new"rubyscript2exe,本质上它用于将rb脚本部署为可执行文件)唯一的问题似乎是缺少tcl的DLL文件我不认为这是一个问题据我所知,问题是缺少tk的DLL文件如果它们是已知的,则可以在执行ocra时将它们包括在内有没有办法知道tk工作所需的DLL依赖项? 最佳答

  9. ruby-on-rails - NameError(未初始化常量 Unzipper::Zip)但仅在 Heroku 部署(Rails)上 - 2

    我有一个类unzipper.rb,它使用Rubyzip解压文件。在我的本地环境中,我可以成功解压缩文件,而无需使用require'zip'明确包含依赖项但是在Heroku上,我得到一个NameError(uninitializedconstantUnzipper::Zip)我只能通过使用明确的require来解决问题:为什么这在H​​eroku环境中是必需的,但在本地主机上却不是?我的印象是Rails自动需要所有gem。app/services/unzipper.rbrequire'zip'#OnlyrequiredforHeroku.Workslocallywithout!class

  10. ruby - Sinatra + Heroku + Datamapper 使用 dm-sqlite-adapter 部署问题 - 2

    出于某种原因,heroku尝试要求dm-sqlite-adapter,即使它应该在这里使用Postgres。请注意,这发生在我打开任何URL时-而不是在gitpush本身期间。我构建了一个默认的Facebook应用程序。gem文件:source:gemcuttergem"foreman"gem"sinatra"gem"mogli"gem"json"gem"httparty"gem"thin"gem"data_mapper"gem"heroku"group:productiondogem"pg"gem"dm-postgres-adapter"endgroup:development,:t

随机推荐