小白服务器编程指北(1)——从零搭建Django服务器(Centos7+Nginx+uWSGI)

前言

最近在学习运用python Django框架开发服务器的相关知识。为了能够让服务器在生产环境中运行,也顺便学习了Centos操作系统。总的感觉是,从无到有是最困难的,一个新的环境,新的框架,入了门,接下来就简单了。当然,从入门到深入,又是另一回事,暂且按下不表。现在就记录下如何在Centos下安装Django服务器。

用到的开发配置如下:
虚拟机:

  • VMWare 11.5.1

操作系统:

  • CentOS 7.7.1908

Django开发环境:

  • Python 3.6.8
  • psycopg2 2.8.4
  • Django 3.0.1

数据库:

  • Postgresql-12
  • Redis 5

服务器环境:

  • uWSGI 2.0.18
  • Nginx 1.16.1

三方服务

  • 阿里云python core SDK

CentOS配置

我们可以去CentOS官网来下载对应的ISO镜像,来安装CentOS系统。

网络连接

当我们在虚拟机中安装完Centos系统后,会发现无法连接网络。我们需要修改其网络配置。

将虚拟机网络连接修改为NAT模式
在这里插入图片描述

通过Mac终端查看VMware Fusion的vmnet8目录下nat.conf文件内容

cat /Library/Preferences/VMware\ Fusion/vmnet8/nat.conf

记录下nat的网关地址和子网掩码:
在这里插入图片描述

查看dhcpd.conf中的内容:

cat /Library/Preferences/VMware\ Fusion/vmnet8/dhcpd.conf

在这里插入图片描述
注意range 这个是虚拟机允许选择的静态ip地址范围,自定义的静态ip地址必须要在这个范围内(本文打算使用172.16.9.200为例介绍)

获取DNS(在mac系统偏好设置—>网络—>)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

登录CentOS系统,进入虚拟机的network-scripts目录,配置网络:

cd /etc/sysconfig/network-scripts

在这里插入图片描述

查找CentOS虚拟机对应的网卡配置文件,找到ifcfg-en开头的文件,上图中我的是ifcfg-ens33

想用vim编辑该文件,却发现vim在CentOS下面默认没有安装(WTF???),只能用vi来编辑:
原始内容:
在这里插入图片描述

修改后的内容:
在这里插入图片描述

重新启动network服务:

systemctl restart network

现在来试一下ping 百度吧,可以看到,网络已经通了:
在这里插入图片描述

配置YUM 源

在CentOS系统中,我们可以通过YUM(Yellow dog Update Modifie)来管理安装软件,但是系统默认的YUM源上的软件包有限,我们需要安装扩展的epel源。

默认的yum源:
在这里插入图片描述

安装eple源:
输入命令

yum -y install epel-release

在这里插入图片描述

再次查看已经安装的YUM源:
在这里插入图片描述

ifconfig: command not found

针对CentOS,其最小化安装已经不会包含ifconfig和netstat相关的网络工具了。

使用yum进行安装:

yum install net-tools

安装vim

yum install vim

安装gcc/g++

开发时,我们需要gcc/g++编译器来编译文件,在CentOS默认最小安装下是没有的,我们可以用yum来安装:

yum install gcc
yum install gcc-c++

Python配置

Python3 update

CentOS 7 中的python默认版本是2.7.5,我们需要将其升级至python3。

yum -y install python36

yum会安装python3到本机,同时会安装python的依赖库pip3 。

这时,在命令行中输入python3,就会使用python 3。

在这里插入图片描述
如果要默认的python命令替换为python 3执行的话,需要断开原有的python命令连接,使其指向python 3:

$ sudo rm /bin/python 
$ sudo rm /bin/python3 
$ cd /bin
$ ln -s python3.6 python # Choose the Python 3.x binary here

成功:
在这里插入图片描述

NOTE:在安装python3的时候,会默认安装依赖库pip3。但有时候pip3 不能够正常运行,这时候只要用yum将python3删除重装就好。

安装Python3 开发套件

sudo yum install python-devel   # for python2.x installs
sudo yum install python3-devel  # for python3.x installs

这时候会出现如下错误:
在这里插入图片描述

这时因为,yum使用python2来写的,因此我们将默认的python指向python3,会出现语法错误。

这时候可以根据yum的错误提示,修改对应的错误文件的头不引用:

!/usr/bin/python 
改成了

!/usr/bin/python2.7 

参考这里

再次运行yum命令,正常安装:
在这里插入图片描述

当输入‘y’后,仍会报错,这时候要依次修改python引用。

PostgreSQL数据库

PostgreSQL数据库是一款最高效的面向关系型数据库。我们可以到他的官网来下载安装对应的版本。

我们这里使用12这个版本。

首先安装postgresql YUM源:

yum install https://download.postgresql.org/pub/repos/yum/reporpms/EL-7-x86_64/pgdg-redhat-repo-latest.noarch.rpm

安装postgresql及server

yum install postgresql12
yum install postgresql12-server

初始化数据库,并设置默认开机启动postgresql服务

/usr/pgsql-12/bin/postgresql-12-setup initdb
systemctl enable postgresql-12
systemctl start postgresql-12

安装postgresql开发包

yum install postgresql-devel

安装psycopg2

psycopg2是python的postgresql DB适配器,为了能够在python中开发postgresql数据库,需要安装该python库。

注意,这时候我们需要使用pip3来安装到python3,默认的pip会安装到python2中。

pip3 install psycopg2-binary==2.8.4

这里我们选择用安装包安装,而不是本地编译源代码安装。因为源代码安装会发生编译错误,这时因为psycopg2使用python2编写的,在python3环境下编译会出现错误。

Redis数据库

Redis数据库是一款优秀的非关系型数据库,利用键值对的关系,可以达到内存中的高效存取。我们可以用它来做Server中一些缓存的操作。

yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm
yum --enablerepo=remi install redis

启用Redis服务

systemctl enable --now redis

修改Redis配置

vim /etc/redis.conf
Bind 0.0.0.0 (71 line)

重启Redis

systemctl restart redis

查看Redis状态

systemctl status redis

在这里插入图片描述

测试Redis Server是否可用

[root@linuxhelp ~]# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> exit

安装Django 及其他Python库

安装Django

pip3 install Django==3.0.1

安装Django restframework

pip3 install djangorestframework==3.11.0

安装Python 图像处理库Pillow

pip3 install pillow==6.2.1

安装Django JWT认证库

pip3 install django-rest-auth==0.9.5

pip3 install djangorestframework-jwt==1.11.0

安装Django的Redis库

pip3 install django-redis==4.11.0

注意到在安装django-redis时的时候,会默认安装依赖的redis 3.3.11版本。但是我们之前安装启动了redis 5这个版本,不知道会如何处理。

因为项目中使用了阿里云的短信服务,所以需要安装阿里云Python SDK:

pip3 install aliyun_python_sdk_core

配置Postgresql

默认情况下,Postgresql数据库是采用host认证的方式,就是其DB用户是和Linux用户绑定的,当用户登录了系统后,便可直接访问Postgresql数据库。为了支持密码认证,我们可以通过修改他的host-based authentication (HBA)配置:

vim /var/lib/pgsql/12/data/pg_hba.conf

在文件最后的位置,找到:

host    all             all             127.0.0.1/32            ident
host    all             all             ::1/128                 ident

修改为:

host    all             all             127.0.0.1/32            md5
host    all             all             ::1/128                 md5

启动Postgresql服务

systemctl start postgresql-12
systemctl enable postgresql-12

使用Postgresql role

在Postgresql中,使用‘role’的概念来控制数据库的访问权限。所谓的role,可以理解为角色,他是和CentOS系统中的account是对应的。当我们创建一个role时,默认会与CentOS中同名的account进行关联(即该account能够按照role中的规定,访问Postgresql数据库)。当我们在CentOS中安装Postgresql时,安装包会默认创建一个postgres的用户,该用户被分配到Postgresql default role。

postgres用户类似于CentOS中的root用户,拥有顶级的数据库权限。因此,我们可以用postgres用户创建其他用户及数据库。

登录到postgres用户

sudo -i -u postgres

进入psql shell

-bash-4.2$ psql
psql (12.1)
Type "help" for help.

postgres=# 

退出psql

postgres=# \q

Create new Postgresql role

利用postgres用户,我们可以创建新的Postgresql role:

-bash-4.2$ createuser -dP John.tyler
Enter password for new role: 
Enter it again: 

-d : role用户拥有创建数据库权限
-P : role用户需要密码认证。(我们在前面已经启用了密码认证)

Create new CentOS user

上一步我们创建了一个 John.tyler的role,它会默认匹配到到当前CentOS同名用户中。

如果没有同名用户,我们需要创建一个新的John.tyler用户

adduser John.tyler

为John.tyler设置CentOS登录密码

[root@localhost ~]# passwd John.tyler

让John.tyler拥有sudo权限:

# 首先切换为root用户
su root

# 修改sudoers文件
d

# 找到权限设置,如下
root    ALL=(ALL)       ALL

# 若要给John.tyler用户增加sudo权限,需要增加如下一行
root    ALL=(ALL)       ALL
John.tyler    ALL=(ALL)       ALL

# 保存退出后John.tyler用户则拥有了sudo权限

创建数据库

首先切换到John.tyler

su - John.tyler

创建数据库

createdb -E utf8 -U John.tyler testDB

测试,运行psql命令,会发现报错,提示John.tyler数据库不存在。

[John.tyler@localhost ~]$ psql
psql: error: could not connect to server: FATAL:  database "John.tyler" does not exist

这是因为psql默认会去尝试连接与当前role同名的数据库,我们可以用-d选项,指定连接数据库名称为testDB:

[John.tyler@localhost ~]$ psql -d testDB
psql (12.1)
Type "help" for help.

testDB=> 

输入\conninfo查看当前的DB连接信息

testDB=> \conninfo
You are connected to database "testDB" as user "John.tyler" via socket in "/var/run/postgresql" at port "5432".

OK,至此,我们创建了一个叫做testDB的数据库,用户名称为John.tyler,testDB数据库登录密码是我们在createuser中指定的,注意,不是我们CentOS中John.tyler的account 密码!

为Django设置不同的环境配置

在实际的项目中,往往需要处理多种环境,其中至少涉及本地开发和产品环境,某些设置可以适用于全部环境,而其他一些则需要针对性的设置。下面就说明如何设置多种环境配置。

在项目的settings.py文件旁创建settings目录,将settings.py重命名为base.py,并将其移动到settings目录下。同时,在settings目录中生成如下附加文件:

settings/
	__init__.py
	base.py
	local.py
	pro.py

local.py用于本地环境配置,pro.py用于产品环境配置。他们都继承自base.py:

from .base import *

编辑settings/base.py,找到如下代码行:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

修改为:

BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(os.path.join(__file__, os.pardir))))

因为我们已经将配置文件移动到了低一级的目录,因此需要将BASE_DIR指向其父目录方可有效,可以使用os.pardir

编辑settings/local.py :

from .base import *

DEBUT = True

DATABASE = {
	'default': {
		'ENGINE': 'django.db.backends.sqlite3',
		'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
	}
}

编辑settings/pro.py:

from .base import *

DEBUG = False

ADMINS = (
    ('John.tyler', '[email protected]'),
)

ALLOWED_HOSTS = ['*']

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': 'testDB',
        'USER': 'John.tyler',
        'PASSWORD': '******',
    }
}

ADMINS : 因为此时DEBUG = False,因此对于视图的异常情况,会以邮件的形式发送到ADMINS。

ALLOWED_HOSTS:Django仅会在指定的服务器上运行,这里未来可以设置线上服务器的域名或IP地址。*表示可以再任意主机上运行。

DATABASES:产品环境下,我们将数据库替换为更强大的Postgresql,注意这里的参与要与我们创建的数据库相匹配。

我们已经为本地和产品环境设置了两套配置文件,那么Django如何知道使用哪个配置文件呢?

我们需要设置一个DJANGO_SETTINGS_MODULE环境变量来指定我们使用的setting 文件:

打开Shell并运行以下命令:

export DJANGO_SETTINGS_MODULE=yourprojectname.settings.pro

或者在每次运行管理命令时,添加–settings参数:

python manage.py migrate --settings=yourprojectname.settings.pro

如果不想每次都添加settings变量,我们可以配置Shell的全局环境变量(以bash shell为例):

/bin/bash
The bash executable
/etc/profile
The systemwide initialization file, executed for login shells
~/.bash_profile
The personal initialization file, executed for login shells
~/.bashrc
The individual per-interactive-shell startup file
~/.bash_logout
The individual login shell cleanup file, executed when a login shell exits
~/.inputrc
Individual readline initialization file|
vim ~/.bash_profile
export DJANGO_SETTINGS_MODULE=yourprojectname.settings.pro

重新加载Shell变量

$ source ~/.bashrc

uWSGI

在本地开发中,我们用Django自带的服务器做开发测试。但是,在真正的产品环境中,需要使用真实的Web服务器。

对于Python来说,使用uWSGI。

安装uWSGI

pip3 install uwsgi==2.0.18 

安装成功后,就可以用uwsgi来运行我们的server了。

为了让uWSGI能够正确的运行我们的server,我们需要为uWSGI编写启动配置文件。

在项目目录下生成如下文件结构:

config/
	uwsgi.ini

uwsgi.ini内容如下:

[uwsgi]
# variables
projectname = yourprojectname
base = absoult path of your project folder
# configuration
pythonpath = %(base)
chdir = %(base)
env = DJANGO_SETTINGS_MODULE=%(projectname).settings.pro
module = %(projectname).wsgi:application
socket = /tmp/%(projectname).sock
# maximum number of worker processes
processes      = 10
# ... with appropriate permissions - may be needed
chmod-socket = 666
# clear environment on exit
vacuum = true
enable-threads = true

socket :这里指定与uWSGI通信是使用unxi socket文件(比直接指定ip:端口形式通信效率高),socket文件的路径为/tmp/%(projectname).sock。
当uwsgi运行时,会自动在tmp目录下创建sock文件,无需人工参与。当使用NGINX时,NGINX会通过该sock文件与uWSGI通信。

chmod-socket:sock文件的访问权限,这里用666表示所有用户皆可访问。这时为了预防NGINX因为sock文件权限,而无法与uWSGI通信的问题。

Nginx

我们访问网络,获取的资源,可以整体上分为静态资源和动态资源。而在当前流行的服务器架构中,静态资源和动态资源的获取可以分为两种不同的方式,从而减轻后台服务器的压力。

通用的做法是,在真正的服务器之前,设置一个反向代理,通常使用Nginx。Nginx的功能是过滤客户端的资源请求类型,对于静态资源,会有Nginx直接返回,并作缓存以备下次使用。而对于真正需要动态返回的动态资源,Nginx才会去请求后台服务器(在我们这里也就是uWGSI),由uWSGI将动态结果返回给Nginx,Nginx再返回给客户端。对于动态资源,Nginx起到一个代理请求的作用。

这也就是反向代理名称的由来,正向代理是由客户端使用的,常见的如VPN。正向代理代理的是客户端。而反向代理,并不是在客户端使用,而是在服务器内部作代理,这种服务器自身使用的代理对客户端来说是透明的,比如我们访问www.baidu.com,我们并不知道我们请求下来的资源究竟是来自Nginx还是其后台真正的服务器。

使用了Nginx后,产品环境的架构如下:
在这里插入图片描述

在我们的应用中,Nginx和uWSGI还是在同一个服务器上,在更复杂的应用中,我们可以将Nginx和uWSGI部署到不同的服务器中,来减轻同一个服务器的压力。

安装Nginx

根据CentOS 7 下 yum 安装和配置 Nginx这篇文章中的内容,我们可以如下安装Nginx:
添加Nginx for CentOS7 的YUM源:

$ sudo rpm -ivh http://nginx.org/packages/centos/7/noarch/RPMS/nginx-release-centos-7-0.el7.ngx.noarch.rpm

YUM安装Nginx:

$ sudo yum install nginx

配置Nginx

为了Nginx能够和我们的uWSGI服务器能够协同工作,我们需要编写Nginx的配置文件。同样在config目录,添加nginx.conf文件:

config/
	nginx.conf
	uwsgi.ini

内容如下:

# the upstream componet nginx needs to connect ot
upstream ai_mei_jia {
    server unix:///tmp/ai_mei_jia.sock;
}

server {
    listen 80;
    # the domain name it will serve for
    server_name 172.16.9.130; # substitute your machine's IP address or FQDN(服务器域名)
    charset     utf-8;

     # Django media
    location /media  {
        alias /home/ai_mei_jia_db_mgr/ai_mei_jia/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /home/ai_mei_jia_db_mgr/ai_mei_jia/static; # your Django project's static files - amend as required
    }

    # Finally, send all non-media requests to the Django server.
    location / {
        uwsgi_pass  ai_mei_jia;
        include     /etc/nginx/uwsgi_params; # the uwsgi_params file you installed
    }

}

uwsgi_pass : 用来指定Nginx和uWSGI通信的socket地址。通常情况下,可以写为:

uwsgi_pass unix:///tmp/ai_mei_jia.sock;

这里我们使用了upstream ai_mei_jia。这样写可以支持多服务器负载均衡,因为在upstream里面,我可以指定多个server,Nginx会自动在这些server间做负载均衡,如:

upstream uwsgicluster {
  server unix:///tmp/uwsgi.sock;
  server 192.168.1.235:3031;
  server 10.0.0.17:3017;
}
uwsgi_pass uwsgicluster;

Nginx配置有很多,具体可以参考Nginx支持。我们不做深入研究。

写好配置文件后,我们就可以将我们整个的Django工程目录,scp到服务器的对应目录下了。

最后的配置

当我们用scp命令将Django文件拷贝到CentOS中后,还需要做一些最后的配置。

初始化Django工程

进入到项目根目录,运行

$ python manage.py migrate

如果一切正常,恭喜,已经成功了一大半了。

然后,我们来创建第一个super user:

$ python manage.py createsuperuser

uWSGI启动工程

接下来,我们通过uWSG来启动工程,而不是本地开发的runserver命令。

设置Nginx启动配置

在前面的步骤中,为了能够使Nginx和uWSGI联合工作,我们需要将之前我们写的nginx.conf拷贝到Nginx的配置文件目录中:

cp /home/John.tyler/ai_mei_jia/config/nginx.conf /etc/nginx/conf.d/ai_mei_jia.conf

启动Nginx

systemctl start nginx

并设置nginx服务开机自启动

 systemctl enable nginx

尝试连接服务器:

http://172.16.9.150/admin

可能会出现服务器无法连接错误:
在这里插入图片描述

这可能是因为CentOS的防火墙的原因,我们让防火墙开放http的80端口:

firewall-cmd --zone=public --permanent --add-service=http
sudo firewall-cmd --reload

重新尝试,会发现服务器已经联通,但是会有502服务器错误:
在这里插入图片描述

这可能是因为CentOS中SELinux导致Nginx无法访问uWSGI的sock文件导致的。

SELinux(Security-Enhanced Linux)
是美国国家安全局(NSA)对于强制访问控制的实现,是Linux历史上最杰出的新安全子系统。
NSA是在Linux社区的帮助下开发了一种访问控制体系,在这种访问控制体系的限制下,进程只能访问那些在他的任务中所需要文件。

这有点像沙盒机制的概念。但是SELinux也是的Nginx无法正常的使用。正统的方式应该是在SELinux中配置相应策略,但是配置起来比较麻烦。我们就根据selinux 不限制nginx

永久关闭selinux,永久关闭需要重启才能生效
编辑vim /etc/selinux/config

这个文件的注释写的非常清楚,直接将selinux设置为disable后,重启,即可。

再次尝试访问Django服务器,这时候应该可以正常访问。如果仍然提示502错误,可以查看nginx的error log,

cat /var/log/nginx/error.log 

如果error log如下所示:

2019/12/30 18:33:50 [crit] 1454#1454: *1 connect() to unix:///tmp/ai_mei_jia.sock failed (13: Permission denied) while connecting to upstream, client: 172.16.9.1, server: 172.16.9.150, request: "GET /admin HTTP/1.1", upstream: "uwsgi://unix:///tmp/ai_mei_jia.sock:", host: "172.16.9.150"

说明nginx没有权限访问ai_mei_jia.sock,这时候要再次检查一下uwsgi.ini的配置,

chmod-socket = 666

来确保nginx能够有权限访问sock文件。

用Nginx项静态和媒体数据集提供服务

至此,我们的Nginx服务并没有初始化需要其提供的静态和媒体数据。

我们到Django工程中,编辑settings/base.py文件:

STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

然后运行

python manage.py collectstatic

对应输出结果类似下面:

160 static files copied to '/ai_mei_jia/static'.

注意上面的路径要和nginx.conf中的配置一致:

  # Django media
    location /media  {
        alias /home/ai_mei_jia_db_mgr/ai_mei_jia/media;  # your Django project's media files - amend as required
    }

    location /static {
        alias /home/ai_mei_jia_db_mgr/ai_mei_jia/static; # your Django project's static files - amend as required
    }

重新加载nginx服务

systemctl reload nginx

这时候再加载页面,会发现nginx仍然没有加载静态文件(这里是格式文件css):

查看nginx的error log,会发现是nginx没有权限访问css文件:

2019/12/31 11:17:16 [error] 2747#2747: *4 open() "/home/John.tyler/ai_mei_jia/static/admin/css/responsive.css" failed (13: Permission denied), client: 172.16.9.1, server: 172.16.9.150, request: "GET /static/admin/css/responsive.css HTTP/1.1", host: "172.16.9.150", referrer: "http://172.16.9.150/admin/login/?next=/admin/"

再用ll命令查看static文件夹的权限,发现其他用户明明是有rx权限的,很是奇怪。

尝试让nginx以root用户运行,修改nginx.conf文件:


user  nginx; # 修改nginx为root
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;

    include /etc/nginx/conf.d/*.conf;
}

将第一行的nginx user修改为root,重启nginx服务,再次加载页面:
在这里插入图片描述

这次css文件终于加载成功了。

总结

查了无数资料,终于将本地的server代码整到CentOS服务器上运行了。回头看一下,服务器的环境配置确实很很繁琐,但是又重复性的劳动。尤其是Nginx和uWSGI之间的通信,涉及到的用户权限问题,自己也是模模糊糊的用最高权限搞定了,但细想一下,这里肯定会有很大的安全隐患在里面。

下一步会继续研究Docker,来简化服务器环境的设置。

发布了89 篇原创文章 · 获赞 47 · 访问量 12万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章