Debian+nginx+gunicorn+django+redis+postgresql网站部署

本文详述在Ubuntu服务器上部署复杂网站的全过程,包括PostgreSQL数据库、Redis缓存、Gunicorn应用服务器、Supervisor进程监控及Nginx反向代理的配置,解决跨服务依赖、权限设置与静态资源管理等问题。

为了避免被设为VIP导致用户无法浏览,该文章迁移到

https://gitee.com/silent_missile/technique_note

在Debian服务器上部署一个自己开发的网站,网站比较复杂,用到了很多组件,这些组件如标题所示,需要一步步进行部署。这里把我摸索的步骤记录下来,以供未来参考

数据库服务

首先是数据库服务,因为数据库服务不依赖其他服务。

我的站点因功能原因,写和读的量差不多,而且访问量也不大,所以选择了postgresql

首先是安装,Debian自带了postgresql的安装包,虽然不是最新版本,但是绝大多数情况足够用了,直接apt安装就可以。

Debian安装的postgresql默认是没有密码的,但是没有密码却无法登录,所以直接用数据库管理员权限强行登陆后再修改设定密码。在此之前,必须要让postgresql初始化生成基本的数据目录和配置文件

sudo service postgresql start

新系统首次运行之后,基本的数据目录和配置文件就生成了。

postgresql安装好之后默认的密码验证机制是peer验证,除非有另外的id服务器,否则我们一般都是本地验证的。所以打开

/etc/postgresql/pg_hba.conf

拉到最下面,把几处peer和scram-sha-256更改为trust,就可以无密码登录

sudo service postgresql restart

在Debian中,postgresql服务是由postgres用户发起的daemon,而postgresql数据库初始化后默认的管理员账户就是postgres

sudo -u postgres psql

然后用命令

\password

修改postgresql服务器中存储的管理员账户postgres的密码。修改好以后

\q

退出。

然后再次打开

/etc/postgresql/pg_hba.conf

拉到最下面,把几处trust更改为scram-sha-256,然后重新启动postgresql服务器,把认证替换为本地sha-256认证

sudo service postgresql restart

重启postgresql服务,再在bash中

psql -U postgres

按照提示输入刚刚设定的密码,以数据库管理员身份登录数据库服务器。

在Linux中,postgres这个账户是操作系统的账户,一般被设定为不可登陆,所以仅能通过

su -u postgres somecommand

用root密码来通过root获得postgres用户的权限。

以postgres用户登陆到数据库服务中,然后以这个状态直接来进行后续操作,比如说创建新的数据库用户。当然,可以把新创建的这个用户赋予数据库管理员权限。甚至还可以在操作系统里创建用户,并用操作系统中的这个用户来创建数据库系统的用户,再把这个用户赋予数据库管理员的权限,以此屏蔽默认的postgres用户,一定程度上可以避免黑客猜出来数据库管理员的用户名。

然后创建普通用户和对应的数据库,这里为了凸显通用性,用户名和数据库名是不同的,不过实践中,用户名和数据库名一般是相同的。

postgres=# create user username with password '****';
CREATE ROLE
postgres=# create database dbtest owner username; -- 创建数据库指定所属者
CREATE DATABASE
postgres=# grant all on database dbtest to username; -- 将dbtest所有权限赋值给username
GRANT
postgres=#

需要注意:

1. 要以英文分号结尾

2.密码需要引号包裹

然后\q退出,再以刚刚创建的用户登录上来

psql -U username

看看用户是否已经生效。

redis缓存服务器

和数据库一样redis缓存服务器也不依赖其他的服务。Debian也有redis的包,直接apt安装即可

默认情况下,redis服务是开机启动的,而且没有密码,所以如果没有特别的安全性要求,可以直接用,不过既然我把所有的服务都放到一台服务器上,用户既然能够访问网关,自然也就能够访问redis服务。所以肯定不能这样暴露,密码一定要设置。打开编辑redis的配置文件

sudo vi /etc/redis/redis.conf

找到requirepass的那一行,默认是注释掉的,把注释取消掉,然后修改为

requirepass redispassword

另外阿里云的Debian在申请时可以设定是否ipv6,如果仅仅是ipv4,没有v6,那么需要在配置文件中找到bind行修改

# bind 127.0.0.1 ::1
bind 127.0.0.1

然后重启redis服务器

sudo service redis-service restart

如果还有问题,那就需要把bind这一行注释掉,启动redis服务之后可以再改回来127.0.0.1,然后再重启redis

注意,redis服务的名字不是redis,而是redis-server

想查看系统中到底安装了哪些服务,或者说想知道那个服务到底叫什么名字

sudo ls /etc/init.d/

virtualenv设置

因为Debian比较保守,自带的软件包都比较古老,所以如果需要较好的开发、运行环境,还是要自己手动安装一些新的软件,最好就是创建virtualenv,然后在这个虚拟幻境里,通过pip安装各种新版本的软件

通过命令

mkdir your_virtualenv_dir # 创建目录,一般我会把所有的虚拟环境放在这个目录里
cd your_virtualenv_dir
virtualenv your_env_name -p /usr/bin/python3 # 新的virtualenv会存放在your_virtualenv_dir/your_env_name,也可以指定其他的python

就可以创建一个虚拟环境

LD_LIBRARY_PATH设置

因为Debian自带的包比较老,所以我会自己编译一些新的包,这些包因为不是用deb安装的,所以一般我会把它们安装在your_virtualenv_dir/your_env_name。这会导致一些新的问题,虽然PATH在activate中设置了,但是LD_LIBRARY_PATH并没有在activate中设置。

我会自己编译安装opencascade和pythonocc-core,而且都是安装在your_virtualenv_dir/your_env_name目录,如果不设置LD_LIBRARY_PATH,pythonocc-core就无法找到opencascade的so

所以需要调整LD_LIBRARY_PATH设置

根据

python - virtualenv that can find relocated libraires (like mysqlclient lib for MySQLdb) - Stack Overflow

打开

nano your_virtualenv_dir/your_env_name/bin/activate

然后在deactivate ()函数里面,加入如下代码

    if ! [ -z ${_OLD_LD_LIBRARY_PATH+_} ] ; then
        LD_LIBRARY_PATH="$_OLD_LD_LIBRARY_PATH"
        export LD_LIBRARY_PATH
        unset _OLD_LD_LIBRARY_PATH
    fi

在deactivate ()函数结束以后,加入如下代码

_OLD_LD_LIBRARY_PATH="$LD_LIBRARY_PATH"
LD_LIBRARY_PATH="$VIRTUAL_ENV/lib:$LD_LIBRARY_PATH"
export LD_LIBRARY_PATH

这样,每次进入虚拟环境的时候

source your_virtualenv_dir/your_env_name/bin/activate

就会自动设置LD_LIBRARY_PATH,这样通过编译安装的软件也可以被识别。

pip源设置

官方pip源在国外,访问比较慢,所以采用国内的镜像,比如说南京大学的镜像

source your_virtualenv_dir/your_env_name/bin/activate
pip config set global.index-url https://mirror.nju.edu.cn/pypi/web/simple
pip install --upgrade pip

安装一些python包

科学计算包

对于numpy、scipy这种科学计算的python包,使用更高效的blas库可以有效提高运行速度,所以我一般会从源代码安装,在编译的时候可以让它去找高速blas,从而加快运行速度

首先安装高速blas,可以用命令行

sudo apt-get install libopenblas-dev

也可以用图形化的synaptic,如果synaptic运行出了问题

sudo rm /root/.synaptic/ -rf

然后重新启动synaptic就可以了

安装好高速blas以后把这个高速blas设置为默认的,替代系统原本的默认的比较慢的

sudo update-alternatives --all

有很多可以选的,遇到了blas就选openblas就切换过来了

然后从源代码编译安装科学计算包

pip install numpy --no-binary numpy
pip install scipy --no-binary scipy

过程中会自动编译、自动安装

如果提示

Did not find CMake 'cmake'
      Found CMake: NO
      Run-time dependency xsimd found: NO (tried pkgconfig and cmake)
      Run-time dependency threads found: YES
      Library npymath found: YES
      pybind11-config found: YES (/tmp/pip-build-env-r96k30hw/overlay/bin/pybind11-config) 2.12.1
      Run-time dependency pybind11 found: YES 2.12.1
      Program f2py found: YES (/tmp/pip-build-env-r96k30hw/overlay/bin/f2py)
      Run-time dependency scipy-openblas found: NO (tried pkgconfig)
      Run-time dependency openblas found: NO (tried pkgconfig and cmake)
      Run-time dependency openblas found: NO

      ../scipy/meson.build:216:9: ERROR: Dependency lookup for OpenBLAS with method 'pkgconfig' failed: Pkg-config for machine host machine not found. Giving up.

安装cmake就可以

安装psycopg

Installation - psycopg 3.2.3.dev1 documentation

psycopg前两年升级到了3.x,从3.x开始可以从源代码安装

pip install psycopg[C]

如果非要从二进制安装也可以

pip install psycopg[binary]

其实编译安装也花不了多少时间,编译时间也就是几分钟而已。如果编译过程提示找不到postgresql的头文件,就把postgresql-server-dev包用apt装上

其他包

除了上面介绍的包需要从源代码安装,要额外注意,其他的包基本上直接安装就可以,对于Django开发运行而言,大体上常用的包如下

pip install django redis pylint pylint-django autopep8 gunicorn gevent greenlet supervisor

django设置

redis设置

在Django-4.x之后,redis访问已经被集成到Django里面了,不需要import

不过需要用户自己安装一个包

pip install redis

在用户开发的Django站点程序中,把redis的密码加入settings.py

Django 缓存框架 | Django 文档 | Django

CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": ["redis://:你的密码@服务器地址:6379/0"]
    }
}

“你的密码”自然就是前面设置的redis密码,“服务器地址”这个选项,则是因为redis可以通过网络远程访问,如果所有的服务都设置在一台服务器上,那么直接写localhost即可

静态资源设置

根据django的官方文档

管理静态文件(比如图片、JavaScript、CSS) | Django 文档 | Django

尽管django自带的开发服务器程序支持静态文件,但是官方不鼓励在生产环境通过django来访问静态文件

通过STATIC_URL这个变量可以用于在程序中为涉及到静态文件的地方做相关配置,这是用于生成访问静态文件的url的,浏览器得到这个url后,会再访问nginx来请求静态文件,此时就不需要django来处理了

通过django.contrib.staticfiles这个类提供了一些处理静态文件的工具,这个工具通过命令

python manage.py collectstatic

可以把项目相关的静态文件全部收集到STATIC_ROOT这个变量指定的目录里,以供生产环境部署,也就是复制到nginx配置文件中指定的那个"/static"所alias的目录下,其中"/static"对应STATIC_URL,alias目录对应STATIC_ROOT,这样所有的对静态文件的访问就都通过nginx来处理,不需要django来运算

根据官方文档

The staticfiles app | Django 文档 | Django

在urls.py中增加

from django.contrib.staticfiles.urls import staticfiles_urlpatterns

# ... the rest of your URLconf here ...

urlpatterns += staticfiles_urlpatterns()

可以让django提供访问静态文件的能力,不过官方还是不推荐这么做。经过实践检验,gunicorn搭配这种配置不能提供静态文件的访问功能,至少不能提供django-admin模块所需的静态文件如css之类的访问功能。

所以没必要调整django来支持静态文件的访问,而是在nginx中配置,"/static"对应STATIC_URL,alias目录对应STATIC_ROOT就好。

不过对于前后端分离的项目,前后端通过json交换信息,前端通过js进行呈现,一般不需要访问Django的静态资源,静态资源都是前端开发时就处理掉了。

gunicorn服务

gunicorn使用非常简单,cd进入django程序目录后

gunicorn -w 进程数量 -b ip:端口 -k 运行模式 项目名.wsgi:application

# 样例
gunicorn -w 2 -b localhost:8080 -k gevent teacher.wsgi:application

gevent模式响应比较快,而gevent又依赖greenlet

老版的gunicorn要求用户在最后给的运行程序一定要加上application类名,不过新版已经不需要了,只要写到wsgi就可以了

当然也可以创建配置文件,然后用-c参数读入配置文件来启动

gunicorn mysite:wsgi -c gunicorn_config.py

supervisor监控程序

如果gunicorn进程意外中断,会导致网站访问受阻。为了避免这个问题,可以使用supervisor监控,一旦gunicorn进程终端,supervisor会自动再次启动gunicorn

https://www.jianshu.com/p/bbd0b4cfcac9

针对自己的网站程序编辑一个配置文件

[program:web]
command=gunicorn -w 4 -b 127.0.0.1:8000 -k gevent webProgram.wsgi:application
directory=/home/user/webProgram
autostart=true

再把这个配置文件放置到

/home/username/anaconda3/envs/yourenv/etc/supervisord/conf.d/

目录下,这样在运行anaconda安装的supervisor时自然就会载入了

我试过编辑一个supervisor的配置文件,然后通过

supervisord -c custome.conf

来借助自定义的配置文件启动,但是实践表明,这个anaconda自带的supervisor程序还是会读取pyenv环境下的配置文件,而不是读取自定义的配置文件。所以只能通过这种方式来增加配置。另外即便采用

[include]
file=/home/username/anaconda3/envs/yourenv/etc/supervisord/supervisord.conf

那么在采用supervisor -c命令来载入,会提示没有supervisord段,启动出错

经过实践检验,使用anaconda自带的supervisor,把网站个性配置文件放到conf.d目录下是唯一成功的启动方法

进入anaconda环境后,直接使用命令

supervisord

即可启动,还可以使用

supervisorctl restart webProgram

或者其他supervisorctl命令来对supervisor服务进行调用

nginx转发配置和django路由配置的协调

外网用户访问时正常访问到80端口,nginx会监听这个端口,然后根据用户请求分别转发给gunincorn的django程序或者直接从硬盘上读取静态文件,也可以转发给其他服务程序比如php-fpm。作为网关nginx性能很好,为后面的其他服务程序提供了不错的屏蔽,提高了服务器的安全性。

/etc/nginx/nginx.conf文件是nginx的主配置文件,其中通过include引用了/etc/nginx/esites-enabled这个目录内的文件,而在/etc/nginx/sites-available目录,则存放着各个不同站点的配置文件。通过在/etc/nginx/sites-enabled创建指向/etc/nginx/sites-available目录内的文件,来调整nginx实际运行中的站点。

所以在/etc/nginx/sites-available目录创建配置文件,一般为了方便管理都是把文件名设定成域名

server {
    listen 80;
    server_name domainname.com;
    root /home/developer/mysite/public;
    index index.html;

    location /api {
        proxy_pass http://localhost:8080;
        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_set_header Referer $http_referer;
        proxy_set_header X-Forwarded-Proto $scheme;
        #proxy_redirect localhost/ /api/;
        #proxy_redirect ~^/(.+)$ /api/$1;
    }
    location /static {
        alias  /home/developer/mysite/static;
    }
}

这个配置就是说遇到“/static/...”访问路径时会直接从硬盘上读取/home/developer/mysite/static目录下的文件;而如果遇到了"/api/..."访问路径则会转发给http://localhost:8080也就是gunicorn监听的端口

关于转发规则的匹配,有些地方需要注意

nginx的proxy_pass路径转发规则浅析(末尾/问题)_东372550723的博客-CSDN博客_nginx配置转发路径

location后面的路径是否以“/”结尾,以及proxy_pass是否以“/”结尾都对匹配模式有影响

另外后台的django+gunicorn返回的内容中也要注意,如果仅仅是返回json,那不需要担心,浏览器直接处理即可

如果返回的是html页面内容,其中还包含了链接,尤其是涉及到css、js、图片等自动加载的链接,浏览器还会再次访问这个链接,如果这个链接拼接的有问题,就会在载入的时候出错。

在django中,通过配置STATIC_URL可以控制其生成的html页面中包含的静态资源的路径,如果这个路径和nginx的配置一致,不论是访问到nginx server root配置的目录下的静态文件,还是访问到location /static目录下的文件,都可以正常使用。但如果这个链接是访问到其他目录就会出问题。

如果django返回的是HTTP 302 redirect重定向,nginx对此提供了proxy_redirect配置参数来控制,可以通过字符串匹配来处理,也可以使用正则表达式来匹配。具体可以参考

Module ngx_http_proxy_module

实际上nginx仅仅会修改响应的Location字段,不会修改response body的内容。所以如果重定向以后返回的内容又包含了html页面内容,那还是要注意内部包含的链接的问题。

我遇到的问题是django-admin页面不能正常工作,因为里面的链接都需要额外处理,而这个处理只能在django完成,nginx是不能处理的。而django-admin又十分封闭

所以最后为了保证访问index时,不会和django的访问api/url冲突,我还是修改了django的url路由配置,尤其是django-admin的部分,给它们统统加上了"/api"前缀。

对于django而言,其url为

http://domainname/url/param/

红色的部分。这部分却不会直接和urls.py中的urlpatterns匹配,而是要去掉最前面的'/',用剩下的url/param/来匹配urlpatterns

所以可以在django中添加一个BASE_URL为'api/',这个变量用于匹配urlpatterns,所以没有起始的'/',然后在根目录的urls.py中的urlpatterns里都加上BASE_URL前缀,其他的url通过include作为根目录下url的子集,就可以确保从nginx传递过来的url和django返回的带有链接的html内容都没有问题。如果仅考虑静态文件,通过django的STATIC_URL来设置也可以,不过对于django-admin却会出问题,为了保证django-admin不出问题,还是用这个方法比较稳妥。

另外在其他代码——最常见的是权限控制——中涉及到url访问权限的判别,一定要用红色的带有起始'/'的url——也就是/url/param/——来判别,因为这是常规字符串处理,不是django自己的路由匹配会自动删除起始的'/'

如果需要使用vue中的history模式,那么nginx还要再调整配置,不过我不希望服务器的负载增大,所以关闭了vue的history模式。虽然这样会在浏览器地址栏留下一个难看的#符,不过毕竟减轻了服务器的负担。

如果nginx配置中把django的转发url设定了前缀,那么vue里也要加上这个前缀。vue中涉及到django后台交互的url必须要保留起始的'/',所以前缀是'/api',和django的BASE_URL不同。

把nginx的站点配置文件编辑好后,把/etc/nginx/enabled-site目录下默认的default软连接删除,再创建一个指向刚刚这个配置文件的软连接,否则default可能会拦截请求导致设定的网站访问不到,具体原则则是按照字母顺序,如果新设定的站点字母顺序比default靠前就会被正常访问,否则就会被default拦截。

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/example /etc/nginx/sites-enabled/example

为了让nginx能够读取静态文件,需要对文件系统权限做些调整。nginx默认的运行用户是www-data,可以修改/etc/nginx/nginx.conf文件,修改user行,修改为静态文件目录的所有人。应该说把静态文件存放目录的访问权限调整为www-data也可访问也是可以的,不过未来如果再有新的文件或者目录,调整起来会非常困难,所以简便的方法还是替换nginx的启动用户。ubuntu没有selinux,如果是红帽系的带了selinux,那修改目录的用户权限会更难,还是替换nginx的启动用户简便一些。不过安全性上稍微差一些,主要差异就在于目录所有人是可以登录的,但是www-data用户是不可以登录的,所以一旦用户登录信息被窃取,损失比www-data用户要大。

然后测试一下nginx的配置文件

nginx -T  # 检查nginx语法问题

如果有问题会提示具体在哪一行,没问题就通过。如果提示的问题是访问权限,可以用sudo临时用户测试一下

sudo -u correctuser nginx -T

因为nginx测试的时候是不会用配置文件里的user行的内容来测试的,而是用当前用户来测试的。

通过以后重新启动nginx,使新配置生效

sudo systemctl restart nginx.service

此时通过浏览器访问

http://localhost/api

就能看到用户请求通过nginx转发给gunicorn运行的django网站程序,然后程序返回的数据通过gunicorn和nginx返回浏览器。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值