背景与整体构思
最近在进行软工开发时,针对如何让所有的人能够随时访问到最新的后端接口的问题产生了一些思考。最终,通过将近一整天的构思和操练,实现了这一套从本地到Github再到云服务器上部署与运行的自动化流程。
这套流程中有三个主要实体:本地、Github仓库、云服务器。至于为什么一定要用Github,这只是因为我们的项目本来就全部托管在Github上面,为了让大家能够无感接入这套系统,我就将Github作为一个中转点了。
在这套系统中,三个实体负责的任务分别如下:
- 本地:开发,推送到Github
- Github:整理所有人的更改,在发生push操作时自动将新的代码push到云服务器对应的仓库中
- 服务器:Python环境配置,建立对应的裸仓库和工作仓库,后台运行Django项目,在代码发生改变时自动重载服务
可以看到,每个实体的操作都还算清晰明了,并且看着也并不难,但是经过我这一天实践下来,发现里面是坑点满满,一路走来可谓如履薄冰,中间甚至差点要重装服务器的系统。。。
下面,我就介绍一下这套系统的具体实现和中间踩到的坑。
本地
本地的任务是最简单的,只需要能正常将仓库推送到Github即可,我相信能看到这篇文章的小伙伴都没问题。
Github
Github在该套系统中最重要的操作就是检测到push操作时自动将仓库推送到服务器对应的裸仓库中,这个部分我用Github Actions来实现。
新建一个Github Actions,命名为auto_push.yml,填入以下内容:
name: AutoPush
on: push
jobs:
deployment:
runs-on: ubuntu-latest
name: Deployment
steps:
- name: Check Out
uses: actions/checkout@v3
- name: Setup Git
run: |
git config --global user.name "your_name"
git config --global user.email "123456@qq.com"
- name: Setup SSH Key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_KEY }}" > ~/.ssh/id_rsa
chmod 600 ~/.ssh/id_rsa
- name: Deploy
run: |
ssh-keyscan -H your_vps_ip >> ~/.ssh/known_hosts
git fetch --unshallow origin
git remote add huawei git@your_vps_ip:/home/git/your_path
git push -u huawei master
将其中的your_name
、123456@qq.com
、your_vps_ip
和your_path
换成你自己的即可。(其中your_path
为你服务器上裸仓库的路径,如果还没有建立,请先自定义一个,后面你的裸仓库需要和它保持一致)
坑点:
ssh-keyscan -H your_vps_ip >> ~/.ssh/known_hosts
这一步必须执行,否则git会提示该路径未保存,是否保存的交互式窗口,导致actions执行失败。git fetch --unshallow origin
这一行是保证将Github所操作仓库的所有内容都fetch进来,否则可能会出现远程服务器拒绝接收的情况(记录不完整)。git remote add huawei git@your_vps_ip:/home/git/EPP/Epp_BackEnd
这里设置远程服务器名称的时候一定不能用origin
,因为前面checkout拉取本地库时自动定义的仓库名就是origin。git push -u huawei master
这里必须有-u
或--set-upstream
保证第一次能够将本地仓库的master分支和远程的master分支相关联。
我们注意到,actions文件里面用到了secrets.SSH_KEY
这个环境密钥,这个密钥我们需要在设置->secrets and variables->actions->Repository secrets里面添加一条SSH_KEY
记录,记录的内容为我们的私钥。(这个私钥可以在任意一台机器用ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
生成,同时生成的.pub公钥请保存起来,后面需要在服务器上设置)
至此,Github上的操作就基本完成了。
云服务器
1.建立裸仓库和工作仓库
git的原理其实就是通过鉴权后将一个仓库和服务器上一个路径(裸仓库)关联起来,了解这个原理后我们完成这一步骤就会更容易一些。
首先root用户下新建git用户:
sudo adduser git
根据要求设置密码等信息。
切换到git用户:
su git
输入刚才设置的密码。
切换到家目录下,新建一个.ssh
目录,设置权限为700,在.ssh
目录中新建authorized_keys
文件,设置权限为600,并存入刚才Github部分生成的公钥。
cd ~
mkdir .ssh
chmod 700 .ssh
cd .ssh
cat "your_public_key" > authorized_keys
chmod 600 authorized_keys
坑点:.ssh和authorized_keys的访问权限和所有者务必设置正确,否则后续可能无法建立连接。
在家目录下新建一个裸仓库,名称和刚才Github上设置的保持一致,后面我都以test
为例。
git init --bare test
注意,这里如果提示找不到git,请先安装git:
sudo apt-get update
sudo apt-get install git
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
新建后,到~/test/hooks/目录下新建一个钩子,命名为post-receive
,给它可执行权限,然后填入以下内容:
#!/bin/bash
git --work-tree=/home/root/test --git-dir=/home/git/test checkout -f
其中--work-tree
请设置为自己的工作目录(裸仓库不能做工作目录,需要将该仓库中的内容自动复制到工作目录,这个钩子就起该作用),后面我都以/home/root/test
为例。
注意:工作目录需要让git用户有读写权限,可以直接设置为777。
至此,裸仓库和工作仓库已经建立完成。顺利的话,你的Github仓库在收到推送时已经可以自动推送到服务器上的工作仓库里面了。
2.安装conda,新建虚拟环境,安装依赖,使用uwsgi实现后台运行和更新自动重载
后面就是针对Django项目祖传的步骤了,也是最恶心的步骤,尤其是Python版本和依赖的配置,真叫人头大!!!
首先,安装conda:
在Anaconda官网(https://www.anaconda.com/)下载最新版。(用wget)
运行 .sh 文件
bash Anaconda3-2021.11-Linux-x86_64.sh
进入注册信息页面,输入yes
阅读注册信息,然后输入yes;查看文件即将安装的位置,按enter,即可安装
安装完成后,收到加入环境变量的提示信息,输入yes
执行
~/.bashrc
刷新下环境变量输入
conda -v
检验是否安装成功
下来,创建虚拟环境。
conda create -n test python=3.9
conda activate test
名字和Python版本按你的项目来。
安装项目依赖:
pip install -r requirements.txt
如果有报错,就尝试手动安装,少什么缺什么,注意版本对应关系!!!
安装uwsgi,如果你在conda里面直接用pip装大概率报错,请用conda去安装。
conda install -c conda-forge uwsgi
接下来试试uwsgi -v,如果报错,恭喜你用到正品conda了(
正常情况下,你会收到缺少.so库的报错,缺少的库因人而异,这时候如果你放弃了,直接去用系统Python,那么我只能建议你无论怎么搞系统Python,都不要动系统自带的Python配置,要不你可能会把apt都弄崩掉(别问我怎么知道的。。。)
如果你还不想放弃,想接着在conda里面搞,那我们继续。
这时我们首先需要知道缺少哪些库,用
ldd $(which uwsgi)
看一下,找到里面缺少的库的名称。
下面,先在系统中找找有没有其他地方存在这个库:
find / -name libcrypt.so.2
如果有,你很幸运,用下面命令把它软链接到conda能读取的库文件夹(自己在前面ldd搜出来的路径中选一个)中:
ln -s /lib/x86_64-linux-gnu/xxx /lib/x86_64-linux-gnu/libcrypt.so.2
没有的话,你跟我一样,我们继续。
去网上搜一搜,如果这个库能够找到,你只需要安装它即可,但是如果找不到,那就神仙难救了,比如我缺的这个libcrypt.so.2
,搜遍了也没找到到底是个啥,后来在一篇帖子中才发现这玩意居然是libcrypt.so.1
改了个名字,把后者复制一份改个名字就行了,我。。。附上今日最无语命令。
ln -s /lib/x86_64-linux-gnu/libcrypt.so.1 /lib/x86_64-linux-gnu/libcrypt.so.2
这样把所有依赖都解决之后,uwsgi就应该能跑起来了,下面我们来踩下一块冰,把项目用uwsgi跑起来。
建议用uwsgi之前你先直接起一下,绑定到0.0.0.0:8000
端口,起了后外网访问一下,如果不通你可能需要去检查以下内容:
安全组是否开放8000端口
防火墙有没有放行8000:
ufw allow 8000
Django跨域配了没有,setting.py中加以下内容:
ALLOWED_HOSTS = ['*'] INSTALLED_APPS = [ ..... 'corsheaders', ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "corsheaders.middleware.CorsMiddleware", #加这个 "django.middleware.common.CommonMiddleware", # "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] CORS_ALLOW_ALL_ORIGINS = True CORS_ALLOW_METHODS = ( "DELETE", "GET", "OPTIONS", "PATCH", "POST", "PUT", ) CORS_ALLOW_HEADERS = ( 'XMLHttpRequest', 'X_FILENAME', 'accept-encoding', 'authorization', 'content-type', 'dnt', 'origin', 'user-agent', 'x-csrftoken', 'x-requested-with', 'Pragma', )
如果这样访问没问题了,那么就去配uwsgi把,在项目根目录下面建一个配置文件,名称自定,我这里为Epp_BackEnd.ini
,写入以下内容:
[uwsgi]
http = 0.0.0.0:8000
buffer-size=65535
chdir = /home/root/EPP/Epp_BackEnd/backend
module=backend.wsgi:application
master=True
pidfile = /home/root/EPP/Epp_BackEnd/backend/uwsgi_blog.pid
touch-reload = /home/root/EPP/Epp_BackEnd/backend/uwsgi_blog.pid
processes = 1
workers= 2
threads = 2
daemonize = /home/root/EPP/Epp_BackEnd/backend/log/uwsgi_blog.log
py-autoreload = 1 # 代码修改后自动重启
注意!注意!:
- py-autoreload需要设置为1,这样才能在代码改变后自动重载
- 如果要直接用http访问,请设置为
http=0.0.0.0:8000
,如果要用nginx反代,请设置为socket=127.0.0.1:8000
,这两者不一样!!! - chdir为项目的根目录,也就是manage.py所在目录
- module为
wsgi.py
所在软件包,例如在./backend/wsgi.py,就写成我那种形式 - master表示是否创建一个管理线程,用于管理工作线程
- pidfile保存启动后uwsgi程序的进程号,在停止服务和重载服务时要用到
- touch-reload同上
- daemonize表示后台启动,后面需要加日志路径
全部配好后,在项目根目录下用uwsgi --ini xxx.ini
启动uwsgi,访问你的ip:8000/api/xxx测试,如果不通,不要着急,去看看你的log,并且多在网上搜搜,多检查你的ini配置文件。
好了,到这里基本你就已经完成了一整套Django项目的自动部署。这个系统目前唯一的缺陷在于后端Python依赖发生变化后,很可能出现错误。关于这个缺陷,我目前还没有一个很好的解决方案,后面想到的话再更新吧。
如果要使用nginx反代uwsgi,可能还会出现一些问题,主要表现在uwsgi和nginx的配合上,这里贴一个原来项目的相关配置:
nginx的server配置:
server {
listen 443;
server_name 111@yoursite;
charset utf-8;
ssl_certificate your_cert_path;
ssl_certificate_key your_key_path;
location / {
include uwsgi_params;
uwsgi_pass 127.0.0.1:8000; #端口要和uwsgi里配置的一样
uwsgi_param UWSGI_SCRIPT backend.wsgi; #wsgi.py所在的目录名+.wsgi
uwsgi_param UWSGI_CHDIR /path/to/your/project; #项目路径
}
location /static {
alias /path/to/your/project/static; #静态资源路径
}
location /templates {
alias /path/to/your/project/weather;
}
}
nginx根目录下uwsgi_params
的内容:
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;
希望大家都能顺利搭建,O(∩_∩)O哈哈~