本站 - 服务器部署原创
笔记
如果你不打算部署在 Github Pages 或者 Gitee Pages 上,而是部署在自己的服务器上,又或者三个都想自动同步部署,那么本内容就是带你如何在服务器部署项目,同时也有 Docker 的一次实战。
2021-11-03 @Young Kbt
# 原理介绍
实现服务器自动化部署,我们使用的是 WebHook 技术,这个技术 Github 和 Gitee 都有,不难理解。
首先要知道为什么使用 WebHook?如果我每次克隆项目,需要手动实现 git clone
命令,但是每次别人 push 新代码,我必须手动克隆或者拉取最新的代码,那么有没有一种技术,别人 push 新代码,我发个呆,然后 push 的新代码自动被我的服务器克隆下来呢。这个技术就是 WebHook。
WebHook 可以理解为一个仓库的触发器。
什么时候这个触发器被触发呢:由你决定,可以在 push 新代码之后,可以在分支合并之后
触发的内容是什么:WebHook 会主动发送一个 POST 请求到你配置的地址,这个地址是我们服务器的脚本。一旦请求我们写的脚本,就会触发脚本,脚本里的内容就是执行
git clone
等命令拉取仓库代码
所以总结就是:当我们 push 项目到仓库,仓库触发 WebHook,发送 POST 请求到我们的服务器,这个请求触发我们写的脚本,脚本执行 git clone
等命令拉取项目代码。
所以确保提供给 GitHub 或 Gitee 的请求能访问并触发编写的脚本。而如何访问并触发脚本,就是本内容要介绍的。
# 环境准备
CentOS 系统(其他系统也可以)
一个普通用户(强烈建议)
Docker 容器(可选)
OpenResty,可以理解为 Nginx 和 Lua 的集成,使用 Nginx 也可以
PHP 环境(PHP 脚本必须)
注意:这里是使用 Docker 容器进行管理,如果没用过或者不打算用 Docker,也可以参考:Docker 是一个容器,容器里的每一个环境可以理解为一个 CentOS 系统,比如 Docker 里的 Nginx 就是一个安装了 Nginx 的 CentOS 系统,Docker 里的 PHP 就是安装了 PHP 的 CentOS 系统。(实际上不完全是 CentOS 系统,取决于供应商的设计,这里为了解释说明)
有人疑惑了,可不可以在 Docker 里的 Nginx 环境安装 PHP,毕竟 Nginx 所在的也是一个 CentOS 系统,额(⊙﹏⊙),不建议,还是老老实实安装 PHP 吧。Docker 的出现就是为了隔离每一个环境,但是又不影响彼此的联系。
如果你不使用 Docker,请下意识把 Docker 这一层因素去掉即可。关于 Nginx 和 PHP 的步骤是完全一样,不会因为 Docker 而被影响。
我是先进入服务器,然后从服务器进入 Docker,接着从 Docker 进入 Nginx 和 PHP。而你不用 Docker,只需要进入服务器即可,因为不用 Dcoerk,Nginx 和 PHP 就处在服务器里。
# 环境安装
关于 Docker 的安装,我已经写在 Docker 的知识体系中,点击传送。
关于 OpenResty(Nginx)在 Docker 安装,我已经写在 Nginx 的知识体系中,点击传送。
如果你已经安装过 Docker,并且 Docker 里安装了 OpenResty 或者 Nginx,则不需要重新安装。
笔记
这里再次说明,没接触过 OpenResty 的也不慌,这里的它的作用就是 Nginx 的作用,所以本文提到的 OpenResty,请在脑海里替换为 Nginx 即可。
2021-11-03 @Young Kbt
安装完 Docker 和 OpenResty(Nginx)后,我们需要下载 PHP 镜像,服务器部署的脚本是使用 PHP 编写,所以需要 PHP 的运行环境。
查看 PHP 版本 传送门 (opens new window)
建议下载带有 fpm
的版本,我下载的是 7.3-fpm 版本。
docker pull php:7.3.33-fpm
# 环境用户
强烈建议 Nginx、PHP 或者其他的容器不要用 root 用户,这里创建一个普通用户来进行操作,这也是实际生产上需要做的。
创建 kbt 普通用户(用户名自己决定)
useradd kbt
passwd kbt
# 然后这里填 kbt 用户的密码
2
3
将 kbt 用户放入 docker 组,这样 kbt 用户才能使用 docker 的相关命令(如果不用 docker,请忽略)
usermod -a -G docker kbt # 将 kbt 用户加入到 docker 用户组中
systemctl restart docker # 重启 docker
2
3
-a 是追加,普通用户加入 docker 组后,不会退出原来所在的组。
-G 是指定组名。
# 环境启动
安装完 Docker、OpenResty 或 Nginx、PHP 后,接下来就启动它们。
Docker 的启动很简单,但是也很麻烦,简单在于命令是固定的,麻烦在于数据卷的考虑,到底放在宿主机的哪个路径下。
如果不用 docker,请忽略。
# Nginx启动
OpenResty 或 Nginx 的启动时,需要将配置文件目录、静态文件目录、日志文件目录给挂载出来,方便备份和修改。
这里只是说明我的路径,你的路径请根据你的需求修改,只需要修改路径即可。
Nginx 的配置文件挂载在
/docker/openresty/conf
目录下Nginx 的静态文件目录挂载在
/docker/openresty/html
目录下Nginx 的日志文件目录挂载在
/docker/openresty/logs
目录下
因为我打算把所有的挂载目录都放在 /docker
目录下,如 node 镜像挂载目录是 /docker/node
,tomcat 镜像挂载目录在 /docker/tomcat
...... 类推
首先创建根路径:
mkdir /docker/openresty
这里说明一下,我们需要把 Nginx 容器里的这些目录拷贝出来,为什么呢?因为一旦挂载成功,宿主机的目录会覆盖容器的目录,想想此时刚刚创建的宿主机的目录都为空,一旦挂载后,容器里的目录被覆盖,也为空。所以我们事先从容器拿出这些目录,放到根路径下,再挂载,这样覆盖的目录就是拷贝出来的目录。
先简单启动一个 OpenResty 容器。容器名叫 nginx,启动的镜像名是 OpenResty,版本为 latest。
docker run -d --name nginx OpenResty:latest
将容器需要的挂载目录拷贝出来
docker cp nginx:/etc/nginx/conf.d /docker/openresty/conf
docker cp nginx:/usr/local/openresty/nginx/html /docker/openresty/html
docker cp nginx:/usr/local/openresty/nginx/logs /docker/openresty/logs
2
3
我拷贝的 conf.d 目录是 简易版 配置文件所在的目录,并将 conf.d 改为 conf,你可以不改,我只是不喜欢 .d。而静态文件目录和日志文件目录名不变。
完整版 的配置文件在容器的 /usr/local/openresty/nginx/conf
路径下,文件名叫 nginx.conf
。
为什么不使用完整版?
「简易版」只能写 server 块,「完整版」不仅可以写 server 块,还能写 http 块。因为「完整版」的 http 块利用 include
指令引入「简易版」配置文件所在的目录,所以我们只需要在「简易版」目录下添加任意 .conf 文件,则自动会被引入到「完整版」,避免直接修改「完整版」引起安全问题。
拿出来三个目录后就可以删除这个容器,为完整版的容器做准备。
docker rm -f nginx
启动最终版的 OpenResty 容器前,我们需要创建一个网桥,避免过多的容器积压在默认网桥上,也为了 Nginx 能和 PHP 等容器进行通信。
网桥就是网络,我喜欢称呼网桥
默认网桥无法直接通信,必须使用 IP 或者 --link
进行通信,所以很麻烦,我们就创建一个网桥,解决所有问题。如果还不懂,请看原理:点击跳转
2021-11-09 @Young Kbt
创建一个叫做 web 的网桥
docker network create web
最后启动 OpenResty 容器,实现挂载
docker run -d --name nginx -p 80:80 --restart always \
-v /docker/openresty/conf:/etc/nginx/conf.d \
-v /docker/openresty/html:/usr/local/openresty/nginx/html \
-v /docker/openresty/logs:/usr/local/openresty/nginx/logs \
-v /home/kbt:/home/kbt \
--network web --network-alias nginx \
OpenResty:latest
2
3
4
5
6
7
/home/kbt
是后面 PHP 环境需要,因为 PHP 不允许 root 用户操作,所以我们使用前面创建的 kbt 用作为操作 PHP 的权限用户。
--network-alias
是给所在的网桥起个别名,这个别名是自己所在网桥的一种标识,方便相同网桥的其他容器如 Nginx 通过别名找到你。尽量和容器名一致。
# PHP启动
PHP 容器启动很简单,我们需要注意的是 PHP 脚本的「住处」,我把脚本放在 /home/kbt
处,因为 PHP 环境使用 kbt 用户,所以脚本也要放在 kbt 用户的目录下,防止外界恶意访问执行脚本,引起严重后果。
如果你是 xxx 用户,则请将脚本放在 /home/xxx
目录下。
docker run -d --name php -v /home/kbt:/home/kbt --network web --network-alias php php:7.3-fpm
Ngxin 启动的时候也挂载了这个 /home/kbt
目录。
此时 /home/kbt
目录相当于一个「中转站」,连接着 Nginx 和 PHP,这样 Nginx 就能通过这个「中转站」访问 PHP 的脚本了。当然这样直接访问还远远不够,我们还需要在 Nginx 的配置文件进行配置,指定 PHP 的脚本在 home/kbt
下。
# 环境配置
我们需要修改 Nginx 的用户权限为 kbt,以及 PHP 的用户权限也为 kbt,这样为了防止外界恶意访问 Nginx,植入恶意脚本,因为 root 的权限太大,拥有了 root 权限,容易被植入恶意脚本。所以不要一味的追求 root。
不要忽略这一步,哪怕你不用 docker,你只需要把 docker 相关的命令无视掉。
# Nginx配置
我们需要配置 Nginx 的用户权限、PHP 脚本的访问路径、以及静态资源的缓存等。
配置 PHP 脚本的访问路径
首先打开 /docker/openresty/conf
的「简易版」配置文件,名字叫 default.conf
vim /docker/openresty/conf/default.conf
添加如下内容:
server {
listen 80;
server_name 你的服务器域名;
# ...... 其他配置
location ~ \.php$ { # 访问 .php 后缀的请求
root /home/kbt; # 脚本的根目录
fastcgi_pass php:9000; # 通过 php 网桥别名的 9000 端口连接上 PHP容器
fastcgi_index index.php; # 默认首页
fastcgi_param SCRIPT_FILENAME /home/kbt$fastcgi_script_name; # 脚本的根目录
include fastcgi_params;
}
# ...... 其他配置
}
2
3
4
5
6
7
8
9
10
11
12
13
14
第 7 行改为 /home/kbt
目录,这是我们放置脚本的根目录。
第 8 行的 php
就是我们启动 PHP 容器指定的网桥别名,Nginx 和 PHP 都处于 web 网桥,那么可以通过网桥别名找到彼此。9000 是 PHP 容器的默认端口。
第 9 行的 /home/kbt$fastcgi_script_name
指的是,启动 PHP 环境来运行 /home/kbt
目录下的脚本,否则脚本无法启动。如果你不是 /home/kbt
,则自行修改。
注意
不能在 $fastcgi
前加 /
。
2021-11-09 @Young Kbt
如果不使用 docker,则内容为
server {
listen 80;
server_name 你的服务器域名;
# ...... 其他配置
location ~ \.php$ {
proxy_pass http://127.0.0.1;
}
location ~ \.php$ {
root /home/kbt;
fastcgi_pass localhost:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /home/kbt$fastcgi_script_name; # 脚本的根目录
include fastcgi_params;
}
# ...... 其他配置
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
先转发到本地(127.0.0.1),然后触发 12 行的 localhost,找到 9000 端口的 PHP 环境。
安装 vim
因为 Nginx 容器并不带 vim 和 vi 命令,所以我们需要安装 vim,安装前需要更新 apt-get
(容器没有 yum)
# 进入 Nginx 容器
docker exec -it nginx bash
2
下载前需要修改下载源为国内源,默认的国外源太慢。
# 进入 apt-get 配置目录
cd /etc/apt
# 执行备份命令,避免修改失败无法使用
cp sources.list sources.list.bak
# 同时执行 echo下的 4 行命令,修改成国内镜像源
echo "">sources.list \
echo "deb http://ftp2.cn.debian.org/debian/ buster main">>sources.list \
echo "deb http://ftp2.cn.debian.org/debian/debian-security buster/updates main">>sources.list \
echo "deb http://ftp2.cn.debian.org/debian/debian buster-updates main">>sources.list
# 执行更新命令 apt-get update
apt-get update
# 下载 vim
apt-get install -y vim
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
你可以将安装完 vim 的新容器,变成新的镜像,不然每次启动初始的镜像,都要更新 apt-get
和安装 vim
docker commit nginx nginx:1.0
第一个 nginx 是打包的容器名,而 nginx:1.0
则是打包成新镜像的名字和版本。构建新镜像后,可以把初始的镜像删除掉,保留也可以。
配置 Nginx 用户权限和缓存等。
进入 Nginx 容器
docker exec -it nginx bash
创建 kbt 用户,设置密码,要和宿主机创建的用户保持一致
groupadd kbt;
useradd -g kbt kbt # 创建用户,并加入组,都是 kbt
passwd kbt
# 然后这里填 kbt 用户的密码
2
3
4
创建完用户后,接着修改「完整版」配置文件
vim /usr/local/openresty/nginx/conf/nginx.conf
将 user 改为 kbt,
user kbt;
如图:
顺便配置一下优化代码,提高 Nginx 的访问效率。
在 events 块添加:(已经存在的 events 块)
events{
worker_connections 1024;
accept_mutex on; # 开启 Nginx 网络连接序列化
multi_accept on; # 开启同时接收多个网络连接
use epoll; # 使用 epoll 函数来优化 Nginx
}
2
3
4
5
6
在 http 块添加:(已经存在的 http 块)
http {
# ...... 其他配置
sendfile on; # 开启高效的文件传输模式
tcp_nopush on; # 提升网络包的传输「效率」
tcp_nodelay on; # 高网络包传输的「实时性」
keepalive_timeout 65; # 连接超时时间
gzip on; # 开启 Gzip 功能
gzip_types *; # 压缩源文件类型,根据具体的访问资源类型设定
gzip_comp_level 6; # Gzip 压缩级别
gzip_min_length 1k; # 进行压缩响应页面的最小长度,content-length
gzip_buffers 4 16K; # 缓存空间大小
gzip_http_version 1.1; # 指定压缩响应所需要的最低 HTTP 请求版本
gzip_vary on; # 往头信息中添加压缩标识
gzip_disable "MSIE [1-6]\."; # 对 IE6 以下的版本都不进行压缩
gzip_proxied off; # Nginx 作为反向代理压缩服务端返回数据的条件
# ...... 其他配置
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# PHP配置
创建普通用户
首先进入 PHP 容器
docker exec -it php bash
创建 kbt 用户,设置密码,要和宿主机创建的用户保持一致
groupadd kbt;
useradd -g kbt kbt # 创建用户,并加入组,都是 kbt
passwd kbt
# 然后这里填 kbt 用户的密码
2
3
4
安装 vim
我已经在 Ngixn 配置写了如何安装 vim,请按照 Nginx 配置的安装 vim,进行安装,就在上面不远处。
配置 PHP 环境的用户权限
修改配置文件的用户
vim /usr/local/etc/php-fpm.d/www.conf
大概在 23 - 24 行,改为:
user = kbt
group = kbt
2
# 环境重启
笔记
重启按顺序:PHP、Nginx,因为 Nginx 配置文件依赖 PHP 脚本,所以先启动 PHP,再启动 Nginx。
2021-11-09 @Younng Kbt
docker restart php
docker restart nginx
2
# 环境测试
测试 Nginx 是否能访问 PHP 脚本,我们在宿主机的 /home/kbt
,创建 index.php 文件
vim /home/kbt/index.php
添加内容:
<?
echo phpinfo();
?>
2
3
然后访问你的网站:ip:port/index.php
,如图:
此时 /home/kbt
就是你 PHP 脚本的根路径,如果你想对 PHP 脚本分类,可以在根路径下创建一个文件夹,如我创建 test 文件夹,将 index.php 放入该文件夹里,接着访问的时候,加上文件夹名即可。如下:
# Git环境
环境准备就绪,现在进入正题。我们需要安装 Git,来实现自动部署。
分别在宿主机和 PHP 容器安装 Git
宿主机安装 Git 是为了配置 SSH 公钥
不理解公钥?我们克隆的项目地址有 HTTPS 方式,也有 SSH 方式。
而 SSH 方式需要配置公钥,我们在自己的服务器生成 SSH 公钥,然后交给 Github 或者 Gitee,这样它们才能通过公钥「连接」我们的服务器。才能拉取项目。
PHP 容器安装 Git 是因为我们执行 PHP 脚本,然后 PHP 容器会根据脚本使用 Git 拉取仓库的项目。
为什么不在 PHP 容器配置公钥?
一个服务器可以生成一个公钥,尽量不要在容器内生成,因为服务器的公钥是公用的,任何容器都可以使用。但是只在一个容器内生产,其他容器可能出现无法使用的问题。
# 宿主机配置Git
宿主机安装 Git,可以直接使用 yum 来下载
yun -y install git
查看 Git 版本
git --version
配置全局用户信息
git config --global user.name "你的用户名" # 定义全局的用户名
git config --global user.email "你的邮箱" # 定义全局的邮件地址
git config --list # 查看配置信息是否成功
2
3
切换 kbt 用户
su kbt
警告
必须切换 kbt 用户,因为我们 PHP 环境使用的是 kbt 用户,拉取代码时,不仅验证 SSH 公钥,也会验证生成 SSH 公钥的用户名。
如果是 root 用户配置公钥,则 PHP 容器没有权限拉取 Git 项目。
2021-11-09 @Young Kbt
生成公钥(确保已经切换了和 PHP 环境一致的用户)
ssh-keygen -t rsa -C "xxx@xx.com" # 填写正确的你的邮箱
按三次回车即可生成 SSH 公钥。
生成 SSH 公钥后,会告诉你生成的目录:Your public key has been saved in /home/lkbt/.ssh/id_rsa.pub.
cat /home/kbt/.ssh/id_rsa.pub
查看文件后获取(复制)SSH 公钥,添加到 Github 或者 Gitee 中。如果不知道如何添加,请看 配置 SSH Key,该内容有三步,配置 SSH 公钥在第二步。
验证是否配置生效
ssh -T git@github.com # GitHub
ssh -T git@gitee.com # Gitee
2
建议 Github 和 Gitee 都配置 SSH 公钥。
# PHP容器配置Git
首先进入 PHP 容器
docker exec -it php bash
确保你的 apt-get
已经是最新的,如果不是,请更新。因为 apt-get
初始版本库里的 Git 版本很低。
apt-get update
安装 Git
apt-get -y install git
查看 Git 版本
git --version
配置全局用户信息
git config --global user.name "你的用户名" # 定义全局的用户名
git config --global user.email "你的邮箱" # 定义全局的邮件地址
git config --list # 查看配置信息是否成功
2
3
初次使用,先克隆一次项目,放在 /home/kbt
目录下
cd /home/kbt
git clone -b ga-pages <仓库 SSH 地址>
2
-b
指定克隆的分支。不指定默认是默认分支 master。
警告
建议在 PHP 容器克隆项目,或者在宿主机切换到 kbt 用户再克隆项目,如果在宿主机以 root 用户克隆项目,那么 PHP 容器将没有权限操作这个项目。
2021-11-11 @Young Kbt
# 项目访问
此时你访问你的服务器,会发现访问不了你克隆的项目,因为项目克隆在 /home/kbt
目录下,而这个目录目前仅仅是 PHP 脚本的根路径,不是非 PHP 脚本的路径,我们还需要修改 Nginx 的配置文件,让它跳转到这个目录。
vim /docker/openresty/conf/default.conf
假设我的项目名是 notes,则配置一个 localtion。
server {
listen 80;
server_name 你的服务器域名;
location /notes {
root /home/kbt; # /notes 会拼接到这后面
index index.html; # 默认访问 idnex.html 页面
}
}
2
3
4
5
6
7
8
9
此时访问你的服务器 ip:port/notes
,Nginx 就会去 /home/kbt
下找到 notes 目录,然后在这个目录下获取 idnex.html 页面返回,你就会成功看到你的项目页面。
# 自动化部署
我们使用的是 webhook 技术。
这里使用 PHP 脚本进行自动化部署,用到 PHP 的 shell_exec 函数,而开放 shell_exec 这个 PHP 函数是非常危险的,因此切记不要在生产环境开放这个函数,更加不能用 root 权限去执行 PHP。当然我们只是简单的个人博客,不是什么大公司,所以危害很低。
我们首先在 /home/kbt
目录下创建一个文件夹,专门存储 webhook 的 PHP 脚本。比如叫 deploy。
在 deploy 文件夹下创建 PHP 脚本
# Gitee脚本
<?php
$git = "git"; // 默认是用 Git 全局变量,有的环境可能要指明具体安装路径
$source = "Gitee"; // 项目仓库源,如 GitHub 或 Gitee
$branch = "gh-pages"; // 指定仓库分支,为空就是 master 分支
$logName = "logs/deploy"; // 本地日志名称,与当前 php 文件在同一目录,不需要加 .log
$savePath = "/home/kbt/deploy"; // 网站根目录,初次克隆确保目录为空
$gitSSHPath = "git@gitee.com:kele-bingtang/notes-blog.git";// 代码仓库 SSH 地址
$password = "你在 WebHook 设置的密码"; // WebHook 设置的密码
$name = "你的用户名";// 仓库用户名
$email = "你的邮箱";// 用户仓库邮箱
$isCloned = true;// 设置是否已经 clone 到本地。true:已经 clone,直接 pull,false:先 clone
$is_test = false;// 测试模式,无需密码。true 打开,平时 false 关闭
// 如果已经 clone 过,则直接拉去代码
if ($isCloned) {
$requestBody = file_get_contents("php://input"); // 获取请求体的数据
if (empty($requestBody) && empty($is_test)) { // empty(var) 如果 var 为空或 false,则返回 true
die('发送到仓库的请求失败'); // die 代表输出一条消息,并退出当前脚本
}
// 解析发过来的 JSON 信息
$content = json_decode($requestBody, true);
// 若是指定的分支且提交数大于 0,且密码正确
if($content['password'] == $password || !empty($is_test)){
if($content['total_commits_count'] > 0 || !empty($is_test)) {
if ($content['ref'] == "refs/heads/$branch" || !$branch || !empty($is_test)) {
$res_log = "------------------------- [PULL START] -------------------------" . PHP_EOL; // PHP_EOL 是换行符
$cmd = "rm -rf $savePath && $git clone -b $branch $gitSSHPath $savePath && echo '拉取项目并部署成功'";
$result = shell_exec($cmd); // shell_exec 关键命令,执行拉取代码
if(!empty($is_test)){
$res_log .= date('Y-m-d H:i:s') . '执行测试!'. PHP_EOL; // .= 代表追加到后面,不会覆盖前面
}else{
$res_log .= "[" . date('Y-m-d H:i:s', time() + 8 * 60 * 60) . '] - 拉取 ' . $content['repository']['name'] . ' 仓库的 ' . $content['ref'] . ' 分支代码进行部署' . PHP_EOL;
}
if(!$result){
$result = "拉取项目失败" . PHP_EOL;
}
$res_log .= "[项目源] - $source" . PHP_EOL;
$res_log .= "[执行命令] - " . $cmd . PHP_EOL;
$res_log .= "[结果] - " . $result;
$res_log .= "-------------------------- [PULL END] --------------------------" . PHP_EOL;
$res_log .= PHP_EOL . PHP_EOL;
file_put_contents($logName.".log", $res_log, FILE_APPEND); // 写入日志
echo $result;
}
}
} else {
file_put_contents($logName.".log", '[Gitee] 密码错误!' . PHP_EOL, FILE_APPEND);
echo '密码错误!';
}
}else { // 如果没有 clone,则先克隆代码
$res = "[ CLONE START ----------------- ]".PHP_EOL;
// 如果配置全局信息过,则会覆盖
$res .= shell_exec("$git config --global user.email $email").PHP_EOL;
$res .= shell_exec("$git config --global user.name $name").PHP_EOL;
$res .= shell_exec("$git clone $gitSSHPath $savePath").PHP_EOL;
$res .= "[ CLONE END ----------------- ]".PHP_EOL;
file_put_contents($logName.".log", $res, FILE_APPEND); // 写入日志
}
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
具体的内容我已经都注释好了,同时也会生成日志文件,第 39 - 44 都是输入日志的数据,你可以适当根据喜好修改。
相信我,你只需要修改第 3 - 11 行的数据为你的数据即可,我把需要的变量都拿出来了,其他不会出错。
29 行代码是核心命令,该脚本执行这个命令,去克隆仓库代码。所以这也是为什么 PHP 环境要安装 Git,不然无法克隆。
说明一下 29 行代码为什么要这样写,因为我们的博客是打包后生成的一大堆 html、css、js 文件,源码修改一点内容,打包后都会引起这些文件的位置、内容变化,所以我们需要把原来克隆的项目删除掉,再重新克隆。
有人想,能不能执行 git pull
命令,只拉取最新的代码,这里告诉你,我测试过了,哪怕我提交到仓库的打包项目没有任何修改, pull 下来的代码全是和原来的代码冲突,需要手动处理合并。想想就麻烦,所以只能先删除再克隆新的。
如果你 git pull
成功,请留言告诉我,可能我当初的方法不对。
# Github脚本
<?php
$git = "git"; // 默认是用 Git 全局变量,有的环境可能要指明具体安装路径
$source = "GitHub"; // 项目仓库源,如 GitHub 或 Gitee
$branch = "gh-pages"; // 指定仓库分支,为空就是 master 分支
$logName = "logs/deploy"; // 本地日志名称,与当前 php 文件在同一目录,不需要加 .log
$savePath = "/home/kbt/deploy"; // 网站根目录,初次克隆确保目录为空
$gitSSHPath = "git@github.com:Kele-Bingtang/notes-blog.git";// 代码仓库 SSH 地址
$password = "你在 WebHook 设置的密码"; // WebHook 设置的密码
$name = "你的用户名";// 仓库用户名
$email = "你的邮箱";// 用户仓库邮箱
$isCloned = true;// 设置是否已经 clone 到本地。true:已经 clone,直接 pull,false:先 clone
$is_test = false;// 测试模式,无需密码。true 打开,平时 false 关闭
// 如果已经 clone 过,则直接拉去代码
if ($isCloned) {
$requestBody = file_get_contents("php://input"); // 获取请求体的数据
if (empty($requestBody) && empty($is_test)) { // empty(var) 如果 var 为空或 false,则返回 true
die('发送到仓库的请求失败'); // die 代表输出一条消息,并退出当前脚本
}
// 获取 GitHub 的加密令牌 Secret
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE_256'];
// GitHub 的令牌被 sha256 加密了,所以对 $secret 相同方式加密
$encyptSecret = "sha256=" . hash_hmac('sha256', $requestBody, $secret);
// 先对 URL 解码,再解析为 JSON 数组
$content = json_decode(strstr(urldecode($requestBody),"{"),true);
// 若是指定的分支且提交数大于 0,且密码正确
if(strcmp($signature, $encyptSecret) == 0 || !empty($is_test)){ // strcmp 函数比较两个字符串
if ($content['ref'] == "refs/heads/$branch" || !$branch || !empty($is_test)) {
$res_log = "------------------------- [PULL START] -------------------------" . PHP_EOL; // PHP_EOL 是换行符
$cmd = "rm -rf $savePath && $git clone -b $branch $gitSSHPath $savePath && echo '拉取项目并部署成功'";
$result = shell_exec($cmd); // shell_exec 关键命令,执行拉取代码
if(!empty($is_test)){
$res_log .= "[" . date('Y-m-d H:i:s') . '] 执行测试!'. PHP_EOL; // .= 代表追加到后面,不会覆盖前面
}else{
$res_log .= "[" . date('Y-m-d H:i:s', time() + 8 * 60 * 60) . '] - 拉取 ' . $content['repository']['name'] . ' 仓库的 ' . $content['ref'] . ' 分支代码进行部署' . PHP_EOL;
}
if(!$result){
$result = "拉取项目失败" . PHP_EOL;
}
$res_log .= "[项目源] - $source" . PHP_EOL;
$res_log .= "[执行命令] - " . $cmd . PHP_EOL;
$res_log .= "[结果] - " . $result;
$res_log .= "-------------------------- [PULL END] --------------------------" . PHP_EOL;
$res_log .= PHP_EOL . PHP_EOL;
file_put_contents($logName.".log", $res_log, FILE_APPEND); // 写入日志
echo $result;
}
} else {
file_put_contents($logName.".log", '[GitHub] 密码错误!' . PHP_EOL, FILE_APPEND);
echo '密码错误!';
}
}else { // 如果没有 clone,则先克隆代码
$res = "[ CLONE START ----------------- ]".PHP_EOL;
// 如果配置全局信息过,则会覆盖
$res .= shell_exec("$git config --global user.email $email").PHP_EOL;
$res .= shell_exec("$git config --global user.name $name").PHP_EOL;
$res .= shell_exec("$git clone $gitSSHPath $savePath").PHP_EOL;
$res .= "[ CLONE END ----------------- ]".PHP_EOL;
file_put_contents($logName.".log", $res, FILE_APPEND); // 写入日志
}
?>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
GitHub 的脚本和 Gitee 脚本的内容不是一样的,首先 Github 返回的令牌是 sha256 加密的,而 Gitee 返回的是不加密的,所以我们需要对本地的令牌进行 sha256 加密,然后再进行两者的判定,是否相等。
其次 GitHub 返回的请求是经过编码过的,如这种 %7B%22ref%22%3A%22refs%2Fheads%2Fgh-pages%
,所以我们获取后需要解码,请看 29 代码。
日志文件如下:
# WebHook配置
讲了那么多,都不懂 WebHook 如何配置,现在开始配置。
Github配置
进入仓库里,然后 Github 点击 setting,找到 Webhooks,然后点击 Add webhook 进行配置。
然后填写 PHP 脚本地址以及令牌。
令牌要对应上我提供脚本的 $secre
变量值。
脚本访问地址:比如脚本放在 /home/kbt/
下的 deploy 目录下,脚本名叫做 deploy.php,那么地址是 ip:port/deploy/deploy.php
。
为什么地址没有 /home/kbt
,因为上面我已经配置过了,这个目录是脚本的根路径,所以 ip:port/xxx.php
就是访问根目录下的 xxx.php,有目录则加上目录即可。
Gitee
其实就是中文版的 Github,所以位置完全一样
密码要对应上脚本的密码。
添加 WebHook 后,可以不断发送请求进行测试。
笔记
只需选择 push 事件触发即可,push 事件是指仓库的 任意分支 被 push 了都会触发,并非只有 master 分支触发。
2021-11-09 @Young Kbt
# 问答
Q1 - 能否总结本内容步骤?
- 安装 Docker,然后利用 Docker 安装 Nginx 和 PHP 镜像
- 创建 kbt 普通用户,加入 docker 组
- 启动 Nginx 容器,将配置文件目录、静态文件目录、日志文件目录拷贝出来,然后关闭 Nginx 容器
- Docker 创建网桥,给两个容器通信
- 启动 Nginx 容器,将拷贝的出来的三个目录和容器的三个目录进行挂载,以及与 kbt 用户的目录挂载
- 启动 PHP 容器,与 kbt 用户的目录挂载,PHP 容器的挂载目录和 kbt 用户的目录路径一致
- 在 Nginx 容器创建 kbt 普通用户和组,然后修改 nginx.conf 配置文件的 user 为 kbt,即 Nginx 的访问权限仅限于 kbt 的权限
- 修改挂载出来的配置文件目录里的 default.conf 文件,指定 PHP 脚本所在的根路径为 kbt 的用户目录
- 在 PHP 容器创建 kbt 普通用户和组,然后修改 www.conf 配置文件的 user 和 group 为 kbt,即 PHP 脚本运行权限仅限于 kbt 的权限
- 两个容器配置完后,按顺序重启 PHP、Nginx 容器
- 宿主机安装 Git,并配置 SSH 公钥给 Github 或者 Gitee。PHP 安装 Git,在 kbt 用户目录克隆项目
- 修改挂载出来的配置文件目录里的 default.conf 文件,添加 location,能访问克隆项目的目录
- 编写 PHP 脚本,然后在 Github 或者 Gitee 上开启 WebHook,配置脚本的 URL 访问地址和令牌、密码
Q2 - 为什么 Nginx 的静态文件目录、日志文件目录没有用到?
我要求启动 Nginx 容器的时候,将这些目录挂载出来,是因为这是实际生产开发需要的,不一定是本内容需要的,这里只是打好预防针,留个印象。
我当初部署的时候确实是把 PHP 脚本和克隆的项目放在静态文件目录下,但是不安全,这些目录是 root 用户管理的,所以我就创建了普通用户 kbt,将 PHP 脚本和克隆的项目放到该普通用户的目录下,即 /home/kbt
。而放到该目录后,就要在 Nginx 和 PHP 创建 kbt 用户,并且挂载到 kbt 用户目录下,实现三者的同一个目录互通。如下图:
Q3 - 一定要在 Nginx 和 PHP 容器创建普通用户 kbt 吗?
如果不创建普通用户 kbt,那么就需要一个目录实现上方图的三者互通,那么这个目录就属于 root 用户。
Q4 - 两个容器创建的 kbt 用户和宿主机的 kbt 用户一样吗?
不一样,但是我现在部署的时候就是这样创建的,我只把我成功的案例说出来。你也可以试试只在宿主机创建 kbt 用户,两个容器使用默认的用户(不创建 kbt 用户),我认为这样也可以。
是可以的,据我了解,在宿主机以普通用户 kbt 启动 Docker 容器,那么容器里虽然显示 root 用户,但是这个 root 用户的权限其实就是宿主机启动的用户权限,所以需要切换到普通用户,并给普通用户使用 Docker 的权限,然后启动。
Q5 - 不使用 Docker 如何操作?
其实熟悉 Docker 了,发现本内容 Docker 操作两个容器,其实就相当于在两台电脑分别安装 Nginx 和 PHP,然后进行操作,Docker 只是实现了两者在同一台电脑的互通(配置同一个网桥)。
不使用 Docker,会发现服务器部署非常方便,步骤大抵如下:
在服务器安装 Nginx 和 PHP 的环境
在服务器创建普通用户 kbt,生成
/home/kbt
路径修改 Nginx 的配置文件和 PHP 的配置文件,将用户权限改为 kbt
修改 Nginx 的配置文件,实现于 PHP 的互通,以及设置 PHP 脚本的根路径
安装 Git,配置信息,生成 SSH 公钥给 Github 或 Gitee
编写 PHP 脚本,然后在 Github 或 Gitee 开启 WebHook,填写脚本的 URL 访问地址
此时会发现少了 Docker 的网桥配置,因为处于同一个服务器,本身就能通信。少了 Docker,也不需要在两个容器创建普通用户 kbt,只需要在宿主机创建即可。
Q6 - PHP 容器不安装 Git 行吗,只在宿主机安装?
不行的,因为 PHP 脚本执行后,使用的 git
命令是脚本所在的环境存在的,不安装即无法克隆项目代码,说其实的,如果曾经已经配置过宿主机的 SSH 公钥,则不需要在宿主机安装 Git。宿主机安装 Git 的目的仅仅是生成 SSH 公钥,而 PHP 容器才是使用 Git 相关的命令。
Q7 - PHP 容器不挂载出来可以吗?
可以的,但是不建议。Docker 挂载的目的就是实现宿主机和容器的数据同步,也就是说两个挂载目录,只要有一方发生修改,则另一方同步修改。而实际上,我们应该禁止容器的挂载目录能被手动修改数据,这样,只修改宿主机的挂载目录数据,安全大大提高的同时,容器的数据也会同步改变。
而如果不挂载出来,就必须进入到容器内部,创建脚本,修改脚本,违背了容器能修改数据的原则,再想想容器万一不小心删除了,则数据全没了。而服务器只有过期、被入侵。两者相比优势清晰明了。