博客大事记-Github Pages、阿里云双线部署,实现国内国外分流

生命不息,折腾不止!

Posted on September 1, 2019

从建站伊始,博客一直托管在 Github Pages 提供的静态页面托管服务上,由于众所周知的原因,博客在国内的访问速度一直不是十分理想。故而为了改善博客在国内的访问速度,用上吃灰的阿里云学生机,于是将博客的国内访客迁移到位于上海的阿里云轻量应用服务器上。

其实购买这台阿里云服务器的只要原因是为了降低我部署的内网穿透服务的延时。之前将内网穿透的服务器部署在了海外,导致使用诸如远程桌面、SSH等服务时的延时完全不可接受,同时阿里云的学生机114一年,配置还不低,于是就入手了一台。当然,服务器只跑一个内网穿透也太浪费了,所以这就琢磨着将博客也部署到服务器上,实现国内国外分流,提高国内的访问速度。

这次折腾主要分为几个部分:

  • 域名备案
  • 搭建 Jekyll 环境
  • 实现 https 访问
  • 设置 webhook,实现自动部署

域名备案

众所周知,服务器在国内的话,需要备案才能使用80及443端口。所以想要在服务器上部署网页服务的第一步就是进行备案。

AliyunBeian

由于域名是在阿里云购买的,所以需要在阿里云进行 ICP 备案。从2019年7月14日提交备案请求到2019年8月1日获得备案号,整个过程历经半个月时间,过程整体较为便捷。但是7月29号之后备案系统升级,不再需要幕布拍照和打印审核单。而我这仿如49年入国军,赶在升级前提交了备案申请。

搭建 Jekyll 环境

博客是基于 Jekyll 环境的,所以我们的服务器上需要搭建 Jekyll 环境,但是作为一个有洁癖强迫症的人完全不能忍受服务器环境被污染,所以这里选择了使用 Docker 部署 Jekyll 环境。

使用 Docker 的好处是显而易见的:

  • 隔离了宿主机与容器,保证了宿主服务器的纯净。

    对于我的需求来说,仅仅使用 Jekyll 进行博客静态页面的生成,使用 Docker 还可以在生成博客时启动容器,生成完毕后结束甚至删除容器,从强迫症的角度来看,无疑是极佳的。

  • 能够很方便的升级和部署。

    传统方式部署 Jekyll 需要先安装 Ruby 等一系列依赖,过程繁复且容易产生错误。而通过 Docker 方式部署则极大的避免了部署时的错误。同时需要升级 Jekyll 版本时只需要将原来的容器删除,重新拉取最新镜像部署即可完成,极大简化了我们的后期维护。

为了方便管理所运行的容器,我们同时使用了 Docker-Compose 管理我们的 Docker 容器。

为了定制需要的 Jekyll 环境,我自己编写了 Dockerfile 文件及 Docker-compose.yaml 文件。

Dockerfile 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM ruby:2.6.3-alpine3.10
MAINTAINER WuYehow "wyhpersonal@163.com"

RUN apk update --no-cache \
    && apk add --no-cache --virtual .build-deps \
        build-base \
    && gem install \
        jekyll \
        jekyll-paginate \
    && apk del -f .build-deps

VOLUME /data
WORKDIR /data

ENTRYPOINT [ "jekyll" ]

我们拉取了官方的 ruby 镜像,在其基础上安装了 Jekyll,并将 /data 目录作为工作目录。后期如果我们需要添加插件或升级版本,只需更改这个文件。然后重新生成镜像即可。

与之相对应的 Docker-compose.yaml 文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
version: "3"

services:
 jekyll:
    build:
      context: .
      dockerfile: Dockerfile
    image: jekyll
    container_name: jekyll
    command: build
    volumes:
      - /var/www/blog:/data
    environment:
      - TZ=Asia/Shanghai

在这里,我们将 /var/www/blog 目录挂载到了容器中的 /data 目录。

这样,我们将代码放入 /var/www/blog 目录,在 Docker-compose.yaml 所在目录执行

1
docker-compose up

便可以在 /var/www/blog/_site 生成对应的静态文件。

如此,我们将 Nginx 的根目录指向 /var/www/blog/_site 便可以访问我们的博客。

实现 https 访问

之前在使用 Github Pages 时,其提供了对自定义域名的 HTTPS 支持。而使用自己的服务器则需要我们自己获取 SSL 证书。好在 Let’s encrypt 提供免费的 SSL/TLS 证书,并且通过国人编写的 acme.sh 便可以很方便的获得我们所需要的 HTTPS 证书。

由于我们在之前安装了 Docker 并且域名托管在阿里云。所以我们使用了 Docker 版本的 acme.sh,并且通过 DNS 模式更新证书。

所使用的 Docker-compose.yaml 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 version: "3"

services:
  acme.sh:
    image: neilpang/acme.sh
    container_name: acme.sh
#    command: --renew -d *.wuyehow.com
    command: --issue --dns dns_ali -d *.wuyehow.com -k ec-256
    volumes:
      - ./acme.sh:/acme.sh
    environment:
      - Ali_Key=***
      - Ali_Secret=***
      - TZ=Asia/Shanghai

注意使用之前在阿里云申请对应的 API KEY,并将其填入相应的位置,其中 --issue --dns dns_ali -d *.wuyehow.com -k ec-256 行中,--dns 参数表示使用阿里云DNS模式,-d 参数表示签发的域名,-k 参数表示签发证书使用的算法。

在宿主机中安装的 acme.sh 有自动更新功能,但是在 Docker 中自然无法使用了,故而我自己编写了一个脚本:

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
#!/bin/bash
container_name="acme.sh"
acme_dir="/etc/docker/acmesh/acme.sh"
acme_domin="*.wuyehow.com"
cert_dir="/etc/nginx/cert"
cert_ecc="true"

docker start $container_name > /dev/null 2>&1

sleep 20

result=$(docker inspect --format='' $container_name)

if [ $result -eq 0 ]; then
        echo "Certificates need to be updated"
        if [ $cert_ecc == "true" ]; then
                if [ ! -d ${cert_dir}_ecc ]; then
                        mkdir ${cert_dir}_ecc
                fi

                if [ ! -f $acme_dir/${acme_domin}_ecc/${acme_domin}.key -o ! -f $acme_dir/${acme_domin}_ecc/fullchain.cer ]; then
                        echo "Error! Not found certificates"
                else
                        cp $acme_dir/${acme_domin}_ecc/${acme_domin}.key ${cert_dir}_ecc/${acme_domin}.key
                        cp $acme_dir/${acme_domin}_ecc/fullchain.cer ${cert_dir}_ecc/fullchain.cer
                        systemctl force-reload nginx
                fi
        else
                if [ ! -d ${cert_dir}_ecc ]; then
                        mkdir ${cert_dir}_ecc
                fi

                if [ ! -f $acme_dir/$acme_domin/${acme_domin}.key -o ! -f $acme_dir/$acme_domin/fullchain.cer ]; then
                        echo "Error! Not found certificates"
                else
                        cp $acme_dir/$acme_domin/${acme_domin}.key $cert_dir/${acme_domin}.key
                        cp $acme_dir/$acme_domin/fullchain.cer $cert_dir/fullchain.cer
                        systemctl force-reload nginx
                fi

        fi
fi

if [ $result -eq 1 ]; then
        echo "Error! Certificates updates error"
fi

if [ $result -eq 2 ]; then
        echo "Certificates do not need to be updated"
fi

通过在计划任务中定期运行这个脚本,便可以对证书进行自动续期,并将证书复制到 /etc/nginx/cert/etc/nginx/cert_ecc 目录中。

同时在 Nginx 的配置文件中增加 SSL 部分配置:

1
2
3
4
5
6
7
ssl on;
ssl_protocols TLSv1.2;
ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
ssl_certificate /etc/nginx/cert_ecc/fullchain.cer;
ssl_certificate_key /etc/nginx/cert_ecc/*.wuyehow.com.key;
ssl_session_timeout 5m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

便可以使用 HTTPS 访问我们的网站了。

为了提高安全性,我们需要对非 HTTPS 的访问强制跳转到 HTTPS。

则需要对 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
server {
        listen 80;
        server_name www.wuyehow.com;

        return 301 https://www.$server_name$request_uri;
}

server {
        listen 80;
        server_name wuyehow.com;

        return 301 https://www.$server_name$request_uri;
}

server {
        listen 443 ssl http2;
#       listen [::]:443;
        server_name wuyehow.com;

        # SSL configuration
        ssl on;
        ssl_protocols TLSv1.2;
        ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
        ssl_certificate /etc/nginx/cert_ecc/fullchain.cer;
        ssl_certificate_key /etc/nginx/cert_ecc/*.wuyehow.com.key;
        ssl_session_timeout 5m;
        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

        return 301 https://www.$server_name$request_uri;
}

则将 http://wuyehow.comhttp://www.wuyehow.comhttps://wuyehow.com 均通过301跳转到 https://www.wuyehow.com 实现强制 HTTPS 访问。

webhook 自动部署

由于采用了双线部署,那么两个服务端的同步成为了无法忽视的问题,好在 Github 提供了 webhook 以实现代码的自动化管理。

我们在 VPS 上使用了基于 GO 语言的开源实现 webhook 来接收和处理 GitHub 发出的 POST 请求。

由于 webhook 仅有一个二进制文件和一个配置文件,所以安装过程极其简单,只需要将相应的文件放入指定的目录,然后使用命令:

1
/path/to/webhook -hooks hooks.json -verbose

即可运行程序,并监听 9000 端口上的通讯。

值得注意的是, webhook 同时支持 json 和 yaml 格式的配置文件。yaml 格式通过缩进来表示块级,相较于 json 格式通过括号表示,简洁很多,更容易识别。所以我们在这里选择了 yaml 格式。

1
2
3
4
5
6
7
8
9
10
11
12
- id: blog-update
  execute-command: /usr/local/etc/webhooks/sh/blog-update.sh
  command-working-directory: /var/www/blog
  response-message: The blog will be updated
  trigger-rule:
    and:
    - match:
        type: payload-hash-sha1
        secret: ******
        parameter:
          source: header
          name: X-Hub-Signature

其中的 id 是识别 webhook 的依据,同时也是在 Github 中需要填写的 URL 的一部分、execute-command 是当捕获 webhook 请求时所运行的 Shell 脚本、command-working-directory 时工作目录,可以填写博客源文件所在目录、response-message 是捕获 webhook 请求后,返回给客户端的信息。

trigger-rule 下级的 secret 为我们需要在 Github 所设置的密码。

为了自动更新我们的博客,我们也编写了相应的 shell 脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/bin/bash
blog_dir="/var/www/blog"
jekyll_docker_name="jekyll"
jekyll_compose_dir="/etc/docker/jekyll"

cd /$blog_dir

for i in {1..3};do
        git_result=$(git pull)

        if [ "$git_result" = "Already up-to-date." ];then
                cd /$jekyll_compose_dir
                docker-compose up -d
                break
        else
                echo "Error, will retry"
        fi
        sleep 10
done

同时编写了 Systemctl 文件,用以自启动 webhook 服务:

1
2
3
4
5
6
7
8
9
10
11
12
[Unit]
Description=Webhook Server Service
After=network.target

[Service]
Type=simple
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/bin/webhook -hooks /usr/local/etc/webhooks/hooks.yaml -ip 127.0.0.1 -verbose

[Install]
WantedBy=multi-user.target

由于之前已经安装了 Nginx 服务,那么我们也可以将 webhook 置于 Nginx 之后,以使用80或443端口同时启用 SSL:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
server {
        listen 80;
        listen 443 ssl;
#       listen [::]:443;
        server_name api.wuyehow.com;

        ssl_protocols TLSv1.2;
        ssl_ciphers EECDH+AESGCM:EDH+AESGCM;
        ssl_certificate /etc/nginx/cert_ecc/fullchain.cer;
        ssl_certificate_key /etc/nginx/cert_ecc/*.wuyehow.com.key;
        ssl_session_timeout 5m;

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                proxy_pass http://127.0.0.1:9000;
        }
}

这里我们使用了 api.wuyehow.com 作为我们的服务域名。

在 GitHub 的 repositories 界面上方的 settings 选项卡中可以找到 Webhooks 项目。点击 Add webhook 便可以添加一个 Webhook 项目。

WebHookofGithub

在其中,我们可以设置 Payload URL 即发生变动时,Github 所请求的URL和 Secert 以判断服务器所获取的 POST 请求是否合法。

值得注意的是 Content type 一项我们需要选择 application/json

同时当我们在 Payload URL 中输入 HTTPS URL 时,下方将出现 SSL 相关的选项。我们可以根据需要启用或关闭 SSL。

同时将 Which events would you like to trigger this webhook? 设置为 Just the push event.

根据我们的 Nginx 配置及 webhook 设置,将 Github 设置为:

WebhookSet

至此,我们当我们写好博文并 push 到 Github 后,便可以在 Github 中上面设置界面的下方看到如下的信息。

WebhookResult

展开后可以在 Response 栏看到服务器的回复。

WebhookResultDetail

DNS 国内外分流

由于域名托管在阿里云,而阿里云域名解析也提供了解析线路的选择。

对此,我们只需要新建一个 @ 解析和一个 www 解析以默认线路解析到我们 VPS 的 IP 地址。

AliyunDnsVps

同时将以前存在的解析到 Github Pages IP 的解析的解析线路更改为境外即可。

AliyunDnsGithub

总结

相较之前部署在 Github 的方案,部署在国内服务器无疑极大的了网站的访问速度,并且由于 Github 对百度爬虫的限制,搭建在 Github 上的网站无法被百度搜索引擎收录,而通过这种方法也可以实现百度对我们网站的收录。

而要说到缺点,大概就是需要投入一笔不小的费用吧。



TOP