目錄
前言
最近在學習運用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,來簡化服務器環境的設置。