在Docker上部署Hexo

遵循小题大做原则,除了将 Hexo 属于在 Docker 中,还将实现,Hexo 托管于代码管理平台,当我们使用 git 发起 push 操作之后,容器可自动部署生成新的 Hexo 博客页面。此番折腾下来,将涉及到 Docker、Nginx、Hexo、pm2、Linux脚本编写等方面知识。

先将域名解析指向 VPS,然后在进行下面的操作

安装Docker

Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的镜像中,然后发布到任何流行的 Linux 或 Windows 机器上,也可以实现虚拟化。容器是完全使用沙箱机制,相互之间不会有任何接口。

以系统为 CentOS Linux release 8.2.2004 (Core) 的VPS为例,安装 Docker

官网教程:https://docs.docker.com/engine/install/

卸载旧版本

不管有没有,先运行一遍就完事了

1
2
3
4
5
6
7
8
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-engine

安装新版本

接着先安装 yum-utils 软件包,之后设置稳定版仓库

1
2
3
4
5
$ sudo yum install -y yum-utils

$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo

安装 Docker

1
$ sudo yum install docker-ce docker-ce-cli containerd.io

测试Docker

这时 Docker 已经安装成功了,为了验证是否正确的安装了 Docker 可以通过下面命令测试一下

1
$ sudo docker run hello-world

如果可以输出参考信息,不报错的话,就表示安装成功了

Docker 信息

1
$ sudo docker version
1
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
Client: Docker Engine - Community
Version: 19.03.14
API version: 1.40
Go version: go1.13.15
Git commit: 5eb3275d40
Built: Tue Dec 1 19:19:47 2020
OS/Arch: linux/amd64
Experimental: false

Server: Docker Engine - Community
Engine:
Version: 19.03.14
API version: 1.40 (minimum version 1.12)
Go version: go1.13.15
Git commit: 5eb3275d40
Built: Tue Dec 1 19:18:24 2020
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.3.9
GitCommit: ea765aba0d05254012b0b9e595e995c09186427f
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683

安装Nginx

Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在BSD-like 协议下发行。其特点是占有内存少,并发能力强,事实上 Nginx 的并发能力在同类型的网页服务器中表现较好。

Nginx 在这里主要作用就反向代理,已经Https证书配置

Docker_Hub:https://hub.docker.com/_/nginx

Docker Hub 中包含了 Nginx 镜像 ,直接拉取下来,安装即可。但由于需要更改 Nginx 的配置文件,所以还需要创建一个容器里的 nginx.conf 和 VPS 的 nginx.conf 文件映射,如果要使用 Https 协议,也最好简历一个映射文件夹,方便管理证书

文件夹映射

在 /home 目录创建文件

1
$ mkdir /home/nginx /home/nginx/config /home/nginx/logs/ /home/nginx/ssl

安装Nginx

1
$ docker run  -p 443:443 -p 80:80 --name nginx -v /home/nginx/config/nginx.conf:/etc/nginx/nginx.conf -v /home/nginx/logs:/var/log/nginx -v /homenginx/ssl:/etc/nginx/ssl -v /home/nginx/dhparam.pem:/etc/nginx/dhparam.pem -d nginx

安装NODE

接下来,这个是重点了,这里将实现自动部署构建,之所以使用 node ,而不是直接使用 Hexo 镜像,是因为需要通过 npm 安装 pm2,管理hexo,已经自动化部署脚本,所以直接使用 node 镜像了

同样,我们需要有一个映射目录,这样方便管理

1
$ mkdir /home/blog

安装容器

6666 端口,用来监听 WebHook,4000端口,用来 Hexo 运行,并给此容器起名为 blog

1
$ docker run -itd --name blog -p 4000:4000 -p6666:6666 -v /home/blog:/etc/blog node

安装 hexo 和 pm2

首先进入运行的容器:

1
$ docerk exec -it blog /bin/bash

然后直接采用全局安装的方式,安装 hexo 和 pm2

1
$ npm install -g hexo-cli pm2

Gitee 准备

我建议,新建一个私人仓库,用来存放 Hexo,我使用的是 Gitee 平台,创建过程就省略了

接着在 blog 容器里,运行下面命令,并敲三次回车,生成 SSH 公钥

xxxxx@xxxxx.com 改为你自己的邮箱

1
$ ssh-keygen -t rsa -C "xxxxx@xxxxx.com"  

查看公钥

1
$ cat ~/.ssh/id_rsa.pub

将生成的公钥复制粘贴到 Gitee 平台的个人账户里

打开这个链接 https://gitee.com/profile/sshkeys 将公钥复制进去,保存即可

WebHook

URL地址,自己填一个地址,然后密码也自己输入一个,最后点击添加

WebHook

再次进入容器,将项目 GiteeWebHook 复制到 blog 容器里,并安装服务

1
2
3
4
$ cd /etc/blog
$ git clone https://gitee.com/geshuyong/GiteeWebHook.git
$ cd GiteeWebHook
$ npm install

修改 GiteeWebHook 项目配置文件

这里因为容器里没有装 vi 或 vim,可以装一个,也可以新建一个远程终端,在 VPS 里操作,我就新建一个终端,在 VPS 里操作了

以下命令在 VPS 中操作

1
2
$ cd /home/blog/GiteeWebHook
$ vim config.js

改为下面配置并保存

1
2
3
4
5
6
module.exports = {
os: "linux", //系统类型:windows\linux
path: "/auto_build", //WebHook POST路径
secret: "123456", //请求密码
port: 6666 //WEB Hook服务端口号
};

修改自动部署脚本

1
2
$ cd /home/blog/GiteeWebHook/cmd
$ vim WEB-Server.sh

改为下面配置并保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#! /bin/bash

SITE_PATH='/etc/blog/hexo'
WEB_USER='root'
WEB_USERGROUP='root'

echo "开始部署"
echo "停止hexo"
pm2 stop blog
pm2 delete blog
echo "拉取最新代码"
cd /etc/blog/hexo
echo "当前工作目录"
pwd
git reset --hard
git clean -df
git pull origin master
echo "构建项目"
npm install
hexo g
echo "运行hexo"
pm2 start --name blog hexo -- s
echo "部署结束"

修改 index.js

1
$ vim /home/blog/GiteeWebHook/index.js

找到里面的 handler.on 方法改为下面代码

1
2
3
4
5
6
7
8
handler.on('Push Hook', function (event) {
logger.info('Received a push event for %s to %s',
event.payload.repository.name,
event.payload.ref
);
logger.info('执行脚本')
linux_run_cmd('sh', ['./cmd/WEB-Server.sh']);//需要执行的脚本位置
});

到此 WebHook 的准备工作完成了

安装Hexo博客

电脑需安装 Node.js,安装完成之后,打开CMD进入到D盘,运行下面命令

1
2
$ npm install hexo-cli -g
$ hexo init blog

进入到blog文件夹,删除里面的.git文件夹,这样才能关联上面我们的私人的仓库

1
2
3
4
5
$ git init
$ git add .
$ git commit -m "first commit"
$ git remote add origin 仓库地址
$ git push origin master

PM2

PM2是具有内置负载平衡器的Node.js应用程序的生产过程管理器。它使您可以使应用程序永远保持活动状态,无需停机即可重新加载它们,并简化常见的系统管理任务

我们使用 PM2 来管理自动化部署脚本,以及监听Gitee的发送过来的Push 通知

启动监听脚本

以下操作需要进入到 blog 容器里,

1
$ docker exec -it blog /binb/bash

进入之后,启动监听脚本

1
2
$ cd /etc/blog/GiteeWebHook
$ pm2 start index.js

克隆Gitee仓库

这步很重要,不能跳过

克隆前面的私人仓库

1
2
3
$ mkdir /etc/blog/hexo
$ cd /etc/blog/hexo
$ git clone 仓库地址

到这里PM2的步骤完成了,在脚本里会自动创建 hexo 运行服务,不用手动启动

修改反向代理

此步骤在 VPS 中操作

1
2
$ cd /home/nginx/config
$ vim nginx.conf

需要注意的是,在反向代理的配置 proxy_pass ,可以通过下面命令,查看Docker 容器的IP地址,将这个地址,替换 proxy_pass 后面的地址

1
$ docker inspect blog

容器地址

nginx.conf的配置,需要在 vps 的 /home/nginx/ssl 目录存放 SSL 证书要然会报错,下面是我的 Nginx 配置

1
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
user www-data;
pid /run/nginx.pid;
worker_processes auto;
worker_rlimit_nofile 65535;

events {
multi_accept on;
worker_connections 65535;
}

http {
charset utf-8;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
log_not_found off;
types_hash_max_size 2048;
client_max_body_size 16M;

# MIME
include mime.types;
default_type application/octet-stream;

# Logging
access_log /var/log/nginx/jenkins.liaocp.cn_access.log;
error_log /var/log/nginx/jenkins.liaocp.cn_error.log warn;

# SSL
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

# Diffie-Hellman parameter for DHE ciphersuites
ssl_dhparam /etc/nginx/dhparam.pem;

# Mozilla Intermediate configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 1.0.0.1 8.8.8.8 8.8.4.4 208.67.222.222 208.67.220.220 valid=60s;
resolver_timeout 2s;

# Load configs
include /etc/nginx/conf.d/*.conf;
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name jenkins.liaocp.cn;

# SSL
ssl_certificate /etc/nginx/ssl/jenkins.liaocp.cn.pem;
ssl_certificate_key /etc/nginx/ssl/jenkins.liaocp.cn.key;

# security
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# . files
location ~ /\.(?!well-known) {
deny all;
}

# reverse proxy
location / {
proxy_pass http://172.17.0.2:4000;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;

# Proxy headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# Proxy timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

# additional config
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}

# robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
}

# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}

# subdomains redirect
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.jenkins.liaocp.cn;

# SSL
ssl_certificate /etc/nginx/ssl/jenkins.liaocp.cn.pem;
ssl_certificate_key /etc/nginx/ssl/jenkins.liaocp.cn.key;
return 301 https://jenkins.liaocp.cn$request_uri;
}

# HTTP redirect
server {
listen 80;
listen [::]:80;
server_name .jenkins.liaocp.cn;
return 301 https://jenkins.liaocp.cn$request_uri;
}

server {
listen 80;
listen [::]:80;
server_name jenkins.liaocp.cn;
return 301 https://jenkins.liaocp.cn$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.liaocp.cn;

# SSL
ssl_certificate /etc/nginx/ssl/www.liaocp.cn.pem;
ssl_certificate_key /etc/nginx/ssl/www.liaocp.cn.key;

# security
# security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

# . files
location ~ /\.(?!well-known) {
deny all;
}

# reverse proxy
location / {
proxy_pass http://172.17.0.2:4000;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;

# Proxy headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# Proxy timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

# reverse proxy
location /auto_build {
proxy_pass http://172.17.0.2:6666;
proxy_http_version 1.1;
proxy_cache_bypass $http_upgrade;

# Proxy headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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 X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;

# Proxy timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}

# additional config
# favicon.ico
location = /favicon.ico {
log_not_found off;
access_log off;
}

# robots.txt
location = /robots.txt {
log_not_found off;
access_log off;
}

# gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;
}

# subdomains redirect
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name *.www.liaocp.cn;

# SSL
ssl_certificate /etc/nginx/ssl/www.liaocp.cn.pem;
ssl_certificate_key /etc/nginx/ssl/www.liaocp.cn.key;
return 301 https://www.liaocp.cn$request_uri;
}

# HTTP redirect
server {
listen 80;
listen [::]:80;
server_name .www.liaocp.cn;
return 301 https://www.liaocp.cn$request_uri;
}

server {
listen 80;
listen [::]:80;
server_name www.liaocp.cn;
return 301 https://www.liaocp.cn$request_uri;
}

}

最后

重启下 Nginx 容器,就可以了,当给私人仓库 push 操作时候,VPS 就会自动执行部署脚本,完成 Heox 页面生成并启动

1
$ docker restart nginx