语雀写作,Kubernetes部署——Hexo相关的持续集成

背景

之前也尝试过Github Action相关的工具进行语雀和博客的持续集成,但是它会依赖语雀Webhook、函数计算服务、Github Action等,其中一个出问题,那么整个流程就不可用。因此在“尽量减少外部依赖”的思路下,除了必须的Hexo仓库和语雀,重新在Kubernetes集群上构建了基于Elog+Hexo博客的持续集成流程。
你需要做的前期准备工作是:

  1. 一个原始hexo git仓库
  2. 一个安装了Ingress、Cert Manager的K8s集群和K8s操作经验;没有的话可以参考前面的文章搭建一套云上云下的轻量K3s集群
  3. 喝一杯咖啡的时间

流程介绍

yuque-hexo (1).png
整个博客自动化的流程如上图所示:

  1. Kubernetes集群中的elog-yuque-syner通过Elog定时获取是否有内容更新
  2. 当在语雀等平台完成内容写作,内容发生变动后,syner中的elog感知到之后发起blog Pod资源的删除
  3. Kubernetes集群中的Deployment控制器就会拉起一个新的Pod
  4. 新的Pod首先会从Git拉取基础的Hexo站点配置
  5. Pod中的Elog工具会拉取语雀中更新过后的博客内容
  6. Hexo生成网站内容,Caddy启动,博客重新部署完成

操作方法

密钥配置

从本地SSH私钥文件创建Secrets, 需要确认这个私钥对应的公钥已经在git仓库中配置过

1
2
kubectl crete ns blog
kubectl create - n blog secret generic private-key --from-file=id_rsa=<SSH私钥文件路径>

部署博客

按如下blog.yaml文件修改对应配置后,部署Hexo博客相关的资源至集群中

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
apiVersion: v1
kind: ConfigMap
metadata:
name: init-script
namespace: blog
data:
# elog相关配置来源请参考地址 https://elog.1874.cool/notion/write-platform
elog.env: |
YUQUE_USERNAME=<语雀用户名>
YUQUE_PASSWORD=<语雀密码>
YUQUE_LOGIN=<账号登陆名>
YUQUE_REPO=<仓库ID>
elog.config.js: |
module.exports = {
write: {
platform: 'yuque-pwd',
'yuque-pwd': {
username: process.env.YUQUE_USERNAME,
password: process.env.YUQUE_PASSWORD,
login: process.env.YUQUE_LOGIN,
repo: process.env.YUQUE_REPO,
onlyPublic: false,
onlyPublished: true,
linebreak: false,
},
},
deploy: {
platform: 'local',
local: {
outputDir: './source/_posts',
filename: 'title',
format: 'matter-markdown',
catalog: false,
formatExt: '',
},
},
image: {
enable: false
},
}
init.sh: |
#!/bin/sh
mkdir -p /root/.ssh/
cp /etc/ssh-key/id_rsa /root/.ssh/
chmod 600 /root/.ssh/id_rsa
cd /tmp/
ssh-keyscan -p 22 github.com >> ~/.ssh/known_hosts
# 这里需要修改为你自己的git仓库地址和文件夹名
git clone git@github.com:demo/site.git
cd ./site
npm --registry https://registry.npmmirror.com install
rm -rf source/_posts/*
cp /tmp/script/elog.config.js ./
elog sync -e /tmp/script/elog.env
hexo generate
cp -R ./public/* /tmp/public/
yuque-sync.sh: |
#!/bin/sh
cd /cache
cp /tmp/script/elog.config.js ./
while true; do
result=$(elog sync -e /tmp/script/elog.env)
echo $result
if echo "$result" | grep -q "没有需要同步的文档"; then
sleep 15
else
kubectl --token=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) delete pods -l app=hexo-caddy
sleep 5m
fi
done
---
apiVersion: v1
kind: ConfigMap
metadata:
name: caddy-config
namespace: blog
data:
Caddyfile: |
:80 {
root * /var/www/html
file_server
}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: hexo-caddy
namespace: blog
spec:
selector:
matchLabels:
app: hexo-caddy
replicas: 1
template:
metadata:
labels:
app: hexo-caddy
spec:
initContainers:
- name: init
image: registry.cn-hangzhou.aliyuncs.com/custom-toolkit/toolkit:hexo-git-openssh-elog
imagePullPolicy: Always
command: ["sh"]
args: ["/tmp/script/init.sh"]
volumeMounts:
- name: private-key
mountPath: /etc/ssh-key
readOnly: true
- name: script-vol
mountPath: /tmp/script
- name: public-vol
mountPath: /tmp/public
containers:
- name: caddy
image: caddy:2.4.0-alpine
command: ["caddy"]
args: ["run", "--config", "/etc/caddy/Caddyfile"]
volumeMounts:
- name: public-vol
mountPath: /var/www/html
- name: caddy-vol
mountPath: /etc/caddy/
ports:
- containerPort: 80
volumes:
- name: private-key
secret:
secretName: private-key
- name: public-vol
emptyDir: {}
- name: script-vol
configMap:
name: init-script
- name: caddy-vol
configMap:
name: caddy-config
---
apiVersion: v1
kind: Service
metadata:
labels:
app: hexo-caddy
name: hexo-caddy-service
namespace: blog
spec:
ports:
- protocol: TCP
port: 80
targetPort: 80
selector:
app: hexo-caddy
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: hexo-caddy
namespace: blog
annotations:
# Traefik Ingress中间件配置,将HTTP重定向到为HTTPS
traefik.ingress.kubernetes.io/router.middlewares: default-redirectscheme-https@kubernetescrd
# Cert Manager中的issuer按需修改
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- secretName: hexo-blog-cert
hosts:
- <你的博客域名>
rules:
- host: <你的博客域名>
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: hexo-caddy-service
port:
number: 80

总结下上面配置中的注意点,必须修改的包括:

  • 语雀账号和仓库配置
  • Hexo博客仓库地址和文件夹名
  • 博客域名

可能需要修改的包括:

  • 集群内的Ingress配置,用于重定向
  • 集群内的Cert Manager Issuer,用于为你的博客域名自动签发证书

部署定时同步任务

等待第二步中的博客部署成功后,按如下elog-yuque-syncer.yaml文件部署定时同步专用的Pod。Pod启动时会默认执行一次博客重建,可以用来验证任务是否能够执行。

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
apiVersion: v1
kind: ServiceAccount
metadata:
name: pod-operator-sa
namespace: blog
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-operator-role
namespace: blog
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["*"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: authorization-role-binding
namespace: blog
subjects:
- kind: ServiceAccount
name: pod-operator-sa
roleRef:
kind: Role
name: pod-operator-role
apiGroup: rbac.authorization.k8s.io
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: elog-yuque-syncer
namespace: blog
spec:
selector:
matchLabels:
app: elog-yuque-syncer
replicas: 1
template:
metadata:
labels:
app: elog-yuque-syncer
spec:
serviceAccountName: pod-operator-sa
containers:
- name: syncer
image: registry.cn-hangzhou.aliyuncs.com/custom-toolkit/toolkit:hexo-git-openssh-elog
imagePullPolicy: Always
command: ["sh"]
args: ["/tmp/script/yuque-sync.sh"]
volumeMounts:
- name: script-vol
mountPath: /tmp/script
- name: cache-vol
mountPath: /cache
volumes:
- name: cache-vol
emptyDir: {}
- name: script-vol
configMap:
name: init-script

部署完毕后,执行以下命令观察博客Pod是否正常删除

1
2
3
hexo kubectl -n blog logs -l app=elog-yuque-syncer

# 正常返回如: pod "hexo-caddy-66956dcbfd-ppdpc" deleted

附录

工具镜像Dockerfile如下

1
2
3
4
5
6
7
8
9
FROM --platform=linux/amd64 alpine@sha256:48d9183eb12a05c99bcc0bf44a003607b8e941e1d4f41f9ad12bdcc4b5672f86

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories && \
apk add --no-cache curl git openssh nodejs npm && \
node -v && npm -v
RUN npm install -g hexo-cli @elog/cli@0.12.2 && \
rm -rf /var/cache/apk/*
RUN wget https://dl.k8s.io/release/v1.28.4/bin/linux/amd64/kubectl -O /usr/local/bin/kubectl && \
chmod a+x /usr/local/bin/kubectl