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

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

首先是数据库服务,因为数据库和其他的软件没有依赖关系。

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

首先是安装,Ubuntu自带了postgresql的安装包,直接apt安装就可以。

Ubuntu安装的postgresql默认是没有密码的,但是没有密码却无法登录,所以直接用数据库管理员权限强行登陆后再修改设定密码。

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

/etc/postgresql/pg_hba.conf

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

sudo systemctl restart postgresql.service

在Ubuntu中,postgresql服务是由postgres用户发起的daemon,而在登录postgresql服务的时候,如果当前登录用户名和postgresql中的用户名一致,则可以免数据库密码登录,所以可以以postgres用户登录来操作postgresql服务,不过postgres这个用户是不允许登录的,可以用root来降级到postgres用户

sudo -u postgres psql

然后用命令

\password

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

\q

退出。

然后打开

/etc/postgresql/pg_hba.conf

拉到最下面,把几处peer更改为md5,然后重新启动postgresql服务器,把远程peer认证替换为本地md5认证

sudo systemctl restart postgresql.service

重启postgresql服务,再在bash中

psql -U postgres

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

这里要特别说明一下,虽然Ubuntu是基于Debian的,不过上面的操作仅适用于Ubuntu,不适用Debian。在Linux中,postgres这个账户是操作系统的账户,一般被设定为不可登陆,所以仅能通过

su
su postgres

先切换成root,然后再从root切换到postgres用户,才能使用。或者

su -u postgres somecommand

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

在Debian系统中,甚至不能通过

psql -U postgres

来使用。那么就只能

su -u postgres psql

以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缓存服务器也不依赖其他的服务。Ubuntu也有redis的包,直接apt安装即可

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

sudo vi /etc/redis/redis.conf

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

requirepass redispassword

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

# bind 127.0.0.1 ::1
bind 127.0.0.1

然后重启redis服务器

sudo systemctl restart redis.service

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

与之对应的,django网站的代码也需要作出修改

传统上,一般使用django-redis插件来实现,不过这个插件已经很久不更新了。所以这里推荐另外一个插件django-redis-cache

GitHub - sebleier/django-redis-cache: A Redis cache backend for django

它在api上和django自带的cache系统完全一致,可以说直接载入插件,剩下的就不用动了,使用起来也非常简单。在anaconda上的下载量远超古老的django-redis

把redis的密码加入settings.py

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

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

gunicorn服务

经过实践检测,Ubuntu一直到19.04都没有带上django-2.x,一直停留在1.11,而且有些我需要用到的软件包Ubuntu没有自带deb包,而我并不希望使用pip来安装,因为pip上很多包都过时了。

所以我只能放弃使用ubuntu自带的python环境,而是另外选择了anaconda的linux版。经过年初4月份anaconda因为版权原因禁止中国区镜像服务器事件之后,在6月份清华终于谈下来了,可以在国内设置一个anaconda的镜像服务器

Index of /anaconda/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

从archive子目录下找到最新的python3-2019.07版,下载之后这个文件是没有可执行权限的,所以

sh Anaconda3-2019.07-Linux-x86_64.sh

默认的安装路径是在安装者的home目录,这个可以不动,在安装到最后时会问是否把anaconda环境加入bashrc,使得用户每次启动bash都会自带anaconda环境,也就是用anaconda的python替换掉系统自带的python,我选择了是。

然后重启bash,或者

source ~/.bashrc

载入刚刚创建的anaconda环境。然后检查一下当前的python是不是anaconda的

which python

检查无误就可以添加中国镜像。如果有额外需要也可以到清华镜像站上去看看其他频道。

conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/
conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/menpo/

其实就是调整了以下~/.condarc文件的内容,其实这个文件也可以手工编辑

然后在anaconda环境中安装软件就快了。

conda update conda
conda install django djangorestframework django-redis-cache pylint-django gunicorn gevent greenlet psycopg2 supervisor
conda install freecad

先更新conda包管理器自身和新的镜像源的清单,然后安装django和gunicorn,gevent和greenlet对gunicorn很重要。我个人还需要一些额外的包,比如freecad

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

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

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

gevent模式响应比较快

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

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

gunicorn mysite:wsgi -c gunicorn_config.py

supervisor监控程序

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

阿里云的Ubuntu版本不是最新版的,出于稳定性考虑,会采用久经考验的旧版本,旧版本默认的supervisor是基于python2的,而我希望采用python3的,所以利用anaconda发行版的supervisor

conda install supervisor

参考

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返回浏览器。

django网站程序静态资源配置

根据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就好。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值