刚开始时候博客部署在Github Pages上面,当时感觉用了github的托管就没啥归属感。再接着使用rsync将github action生成的public文件夹同步到国内云服务器的caddy服务器里面,使用自己的服务器域名,但是rsync这个方案不太优雅每次部署时阿里云都会有访问告警。现在自己的k3s集群起来了一切就好说了。

主要的想法是CI部分依旧白嫖github,将编译好的网站文件夹连同caddy服务器打包成docker镜像,依靠Helm3实现CD。

这里先放出完整的github ci文件

The deploy_hexo updated at: 2022.08.01

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
name: deploy_hexo

on:
push:
branches:
- hexo # default branch

jobs:
deploy_hexo:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v3
- name: Use Node.js 18
uses: actions/setup-node@v3
with:
node-version: "18"
cache: "yarn"
- name: Cache dependencies
uses: actions/cache@v3
with:
path: node_modules
key: ${{ runner.OS }}-hexo-dependencies-cache
restore-keys: |
${{ runner.OS }}-hexo-dependencies-cache
- name: Install Dependencies
run: yarn install --immutable
- name: Build
run: yarn run clean && yarn run build && yarn run algolia && yarn run gulp && yarn run deploy
env:
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
ALGOLIA_ADMIN_API_KEY: ${{ secrets.ALGOLIA_ADMIN_API_KEY }}
ALGOLIA_INDEX_NAME: ${{ secrets.ALGOLIA_INDEX_NAME }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
id: docker_build
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/blog:latest
- name: Deploy to k3s
uses: WyriHaximus/github-action-helm3@v2
with:
exec: |
helm repo add clemon https://helm-charts.clemon.icu &&
helm repo update &&
helm upgrade blog clemon/hexo -n web -f blog-deploy.yaml --set image.dig=${{ steps.docker_build.outputs.digest }} --install
kubeconfig: '${{ secrets.KUBECONFIG_FILE }}'

网站镜像build与push

第一大部分还是编译Hexo博客,和之前的没有任何区别,编译生成的文件都在public文件夹里。编译完成后就是打包镜像并push到dockerhub等镜像仓库里面去。

Dockerfile

dockerfile很简单,这里直接把Caddyfile放在了dockerfile里头,并且添加GET http://*:8080/healthz作为k3s健康检测的endpoint,且关闭自动https。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
FROM caddy:2

RUN echo $'{ \n\
auto_https off \n\
} \n\
\n\
:80 { \n\
encode zstd gzip \n\
\n\
root * /var/www/html/blog \n\
file_server \n\
} \n\
\n\
:8080 { \n\
@healthz { \n\
method GET \n\
path /healthz \n\
} \n\
\n\
respond @healthz 200 \n\
}' > /etc/caddy/Caddyfile

COPY public /var/www/html/blog

构建与推送镜像

此步骤在workflows有三部分:

  1. docker/setup-buildx-action@v2来设置构建环境。
  2. docker/login-action@v2登陆到docker hub。
  3. docker/build-push-action@v3构建并推送镜像。

这里需要用到docker hub的token,登陆后在Account Settings->Security里面创建一个token,记下token下面要用,不得不说docker hub有点小气就给一个免费的token:

image-20211114171230220

在博客的仓库添加两个secret,分别是docker hub的用户名DOCKERHUB_USERNAME和tokenDOCKERHUB_TOKEN,该部分workflow如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
id: docker_build
with:
context: .
push: true
tags: ${{ secrets.DOCKERHUB_USERNAME }}/blog:latest

dockerfile等都放在项目跟目录,所以这了context就是.tag就是推送上去镜像名字。注意这里的id: docker_build一定要加,接下来的部署过程会用到。

Helm模板

This section updated at: 2022.08.01

删除helm chart内容,直接展示chart repo

helm使用v3版本,把chart做到了私人的repo里去

https://github.com/c1emon/helm-charts

1
2
3
helm repo add clemon https://helm-charts.clemon.icu
helm repo update
helm pull clemon/hexo

简单的values.yaml文件,开启了tls

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
image:
registry: "registry"
repository: "blog"
pullPolicy: "IfNotPresent"

ingress:
enabled: true
className: "nginx"
tls: true
host: "blog.clemon.icu"

common:
annotations:
ingress:
"cert-manager.io/cluster-issuer": "letsencrypt-prod"

自动部署

为Github CI创建k3s凭证

这部分是我花时间最多的,首先说下我并没有系统性的学习过k3s,甚至本身不是计算机科班出身也不干这行。所以对k3s权限以及API等部分理解的却是不好。刚开始一个劲想要为github ci分配一个serviceAccount,但是不停尝试都不能生成个能用的kubeconfig,依稀记得之前玩k8s的时候这样只能拿到一个token(可以登陆dashboard)。接着好好想了想,github ci不就是相当于一个运维人员吗,所以给个user类型的账号做好权限控制就行了~

这篇Kubernetes RBAC 详解写的很好,可以参考~

  1. 本地创建密钥文件gh-ci.key

    1
    openssl genrsa -out gh-ci.key 4096
  2. 为key生成签名请求文件gh-ci.csr

    1
    openssl req -new -key gh-ci.key -out gh-ci.csr -subj "/CN=github-ci/O=clemon"

    这里的CN=github-ci表示用户名就是github-ciO=clemon表示该用户属于组织clemon

  3. 进行签名,获得签名完成的文件gh-ci.crt

    • k3s的证书与密钥在master节点的/var/lib/rancher/k3s/server/tls目录下,分别是client-ca.crtclient-ca.key
    1
    openssl x509 -req -in gh-ci.csr -CA /var/lib/rancher/k3s/server/tls/client-ca.crt -CAkey /var/lib/rancher/k3s/server/tls/client-ca.key -CAcreateserial -out gh-ci.crt -days 3650

    This section updated at: 2022.08.01

    这种签名方法麻烦而且需要额外保存签名文件,推荐使用CertificateSigningRequest进行签名

    https://kubernetes.io/zh-cn/docs/reference/access-authn-authz/certificate-signing-requests/#normal-user

  4. 生成kubeconfig文
    <ca-tata>就是/etc/rancher/k3s/k3s.yamlcertificate-authority-data内容,看了很多文章都会用/var/lib/rancher/k3s/server/tls/client-ca.crt,如果用这个的话kubectl提示证书无法验证,可以通过--insecure-skip-tls-verify跳过,但是这种情况导致了helm部署失败,因为helm没有这个跳过验证证书的flag。

    1
    2
    3
    4
    kubectl --kubeconfig=gh-ci.kubeconfig config set-cluster default --server=https://apiserver.domain.or.ip:6443 --certificate-authority=<ca-tata> --embed-certs=true
    kubectl --kubeconfig=gh-ci.kubeconfig config set-credentials github-ci --client-certificate=`pwd`/gh-ci.crt --client-key=`pwd`/gh-ci.key --embed-certs=true
    kubectl --kubeconfig=gh-ci.kubeconfig config set-context gh-ci --cluster default --user=github-ci
    kubectl --kubeconfig=gh-ci.kubeconfig config set current-context gh-ci

    到这里一个用户就创建好了,但是现在这个用户没有权限,所以不能执行任何操作。

  5. 授权

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    kind: Role
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
    name: blog-maintainer
    namespace: blog
    rules:
    - apiGroups: ["", "apps", "networking.k8s.io"] # 空字符串""表明使用core API group
    resources: ["services", "deployments", "ingresses", "secrets"]
    verbs: ["*"]

    ---
    kind: RoleBinding
    apiVersion: rbac.authorization.k8s.io/v1
    metadata:
    name: blog-maintainer-authorization
    namespace: blog
    subjects:
    - kind: User
    name: github-ci
    apiGroup: ""
    roleRef:
    kind: Role
    name: blog-maintainer
    apiGroup: ""

    此处给出的权限可能过大,还需要慢慢调整。这里subjects.name要和你的用户名对应!

自动部署

最后就是部署部分了:

1
2
3
4
5
6
7
8
- name: Deploy to k3s
uses: WyriHaximus/github-action-helm3@v2
with:
exec: |
helm repo add clemon https://helm-charts.clemon.icu &&
helm repo update &&
helm upgrade blog clemon/hexo -n web -f values.yaml --set image.dig=${{ steps.docker_build.outputs.digest }} --install
kubeconfig: '${{ secrets.KUBECONFIG_FILE }}'

首先在本地手动部署一个到k3s集群中,接下来直接自动化~万岁~完结撒花~