在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
按照提示输入刚刚设定的密码,以数据库管理员身份登录数据库服务器。然后创建普通用户和对应的数据库,这里为了凸显通用性,用户名和数据库名是不同的,不过实践中,用户名和数据库名一般是相同的。
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缓存服务器也不依赖其他的服务。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
https://github.com/sebleier/django-redis-cache/
它在api上和django自带的cache系统完全一致,可以说直接载入插件,剩下的就不用动了,使用起来也非常简单。在anaconda上的下载量远超古老的django-redis
把redis的密码加入settings.py
CACHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": ["redis://:你的密码@服务器地址:6379/0"]
}
}
“你的密码”自然就是刚刚设置的redis密码,“服务器地址”这个选项,则是因为redis可以通过网络远程访问,如果所有的服务都设置在一台服务器上,那么直接写localhost即可
经过实践检测,Ubuntu一直到19.04都没有带上django-2.x,一直停留在1.11,而且有些我需要用到的软件包Ubuntu没有自带deb包,而我并不希望使用pip来安装,因为pip上很多包都过时了。
所以我只能放弃使用ubuntu自带的python环境,而是另外选择了anaconda的linux版。经过年初4月份anaconda因为版权原因禁止中国区镜像服务器事件之后,在6月份清华终于谈下来了,可以在国内设置一个anaconda的镜像服务器
https://mirrors.tuna.tsinghua.edu.cn/anaconda/
从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
如果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服务进行调用
外网用户访问时正常访问到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监听的端口
关于转发规则的匹配,有些地方需要注意
https://blog.csdn.net/u011789653/article/details/78822419
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配置参数来控制,可以通过字符串匹配来处理,也可以使用正则表达式来匹配。具体可以参考
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_redirect
实际上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的官方文档
https://docs.djangoproject.com/zh-hans/2.2/howto/static-files/
尽管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来运算
根据官方文档
https://docs.djangoproject.com/zh-hans/2.2/ref/contrib/staticfiles/
在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就好。