概述

前段时间使用 kubesphere 采用 UI 界面的操作方式成功将自己的博客部署在了 kubernetes 平台上(参考链接https://rainwu.cn/archives/use-kubesphere-deploy-halo),上个周末折腾了一下将之前部署的内容整理成了 yaml 文件,方便其他小伙伴在原生的 kubernetes 平台或其他 k8s 发行版上进行部署。

部署架构

架构跟之前部署的一样,数据库采用 mysql,缓存采用 redis ,如下:

image-1655704145831
本次部署组合采用 Halo + MySQL + RedisHalo 作为前端服务,MySQL 作为后端数据库,Redis 作缓存。在 kubernetes 平台上创建一个项目 halo-project ,该项目下创建三个服务,分别是 halo-front-sevicehalo-mysql-servicehalo-redis-service,具体服务包含内容如下:

image-1655704300821

部署过程

创建 halo-app.yaml 文件,内容如下:

## 用以保存 mysql 数据库密码的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: halo-mysql-secret
data:
  MYSQL_ROOT_PASSWORD: MTIzNDU2             # 必须是 base64 编码,在 Linux 可以通过 “echo -n "123456" | base64”命令获取
type: Opaque

---
## 用以保存 halo 连接 mysql 数据库密码和 redis 缓存密码的 Secret
apiVersion: v1
kind: Secret
metadata:
  name: halo-secret
data:
  SPRING_DATASOURCE_PASSWORD: MTIzNDU2      # 必须是 base64 编码,在 Linux 可以通过 “echo -n "123456" | base64”命令获取
  SPRING_REDIS_PASSWORD: MTIzNDU2
type: Opaque

---
## 用以保存 mysql 配置文件的 ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
  name: halo-mysql-config
data:
  my.cnf: |-
    [client]
    default-character-set=utf8mb4
    [mysql]
    default-character-set=utf8mb4
    [mysqld]
    default_authentication_plugin=mysql_native_password
    character-set-server=utf8mb4
    collation-server=utf8mb4_general_ci
    explicit_defaults_for_timestamp=true

---
## 用以保存 redis 配置文件的 ConfigMap,requirepass 的值为 redis 的连接密码
apiVersion: v1
kind: ConfigMap
metadata:
  name: halo-redis-config
data:
  redis.conf: |-
    port 6379
    bind 0.0.0.0
    appendonly yes
    protected-mode no
    requirepass 123456

---
## halo 需要的存储空间,用以持久化 /root/.halo 里的数据
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: halo-front-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: nfs-client               # storageClass 可根据您本地集群拥有的类型进行选择,这里采用的是 nfs-client

---
## 将 mysql 数据库以 StatefulSet 工作负载形式创建出来
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: halo-mysql
  labels:
    app: halo-mysql
spec:
  replicas: 1
  selector:
    matchLabels:
      app: halo-mysql
  template:
    metadata:
      labels:
        app: halo-mysql
    spec:
      volumes:                                # 将前面创建的 ConfigMap 定义出来,方便后面调用
        - name: halo-mysql-config
          configMap:
            name: halo-mysql-config
            defaultMode: 420
      containers:
        - name: mysql
          image: 'mysql:8.0.27'               # mysql 数据库的镜像版本,这里选择 mysql 8.0
          ports:
            - name: tcp-3306
              containerPort: 3306             # 将容器的 3306 端口暴露出来
              protocol: TCP
          env:                                # 配置 mysql 数据库环境变量
            - name: MYSQL_ROOT_PASSWORD       # 数据库 root 账户的密码,这里采用 Secret 方式初始化连接密码,下面填写最开始创建的名称为“halo-mysql-secret”的 Secret
              valueFrom:
                secretKeyRef:
                  name: halo-mysql-secret
                  key: MYSQL_ROOT_PASSWORD
            - name: MYSQL_DATABASE            # 启动后创建一个名为 halodb 的库
              value: halodb
          volumeMounts:
            - name: halo-mysql-pvc            # 将 mysql 数据库的 “/var/lib/mysql” 进行持久化,数据库落盘到名称为 “halo-mysql-pvc” 的持久化数据卷
              mountPath: /var/lib/mysql
            - name: halo-mysql-config         # 将 my.cnf 配置文件以 ConfigMap 的形式进行外部挂载
              readOnly: true
              mountPath: /etc/mysql/conf.d/my.cnf
              subPath: my.cnf
  serviceName: halo-mysql-service
  volumeClaimTemplates:                       # 创建 mysql 数据库需要的持久化卷
    - kind: PersistentVolumeClaim
      apiVersion: v1
      metadata:
        name: halo-mysql-pvc
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 5Gi
        storageClassName: nfs-client          # storageClass 可根据您本地集群拥有的类型进行选择,这里采用的是 nfs-client

---
## 为 mysql 工作负载创建 service
apiVersion: v1
kind: Service
metadata:
  name: halo-mysql-service
spec:
  ports:
  - name: tcp-3306
    protocol: TCP
    port: 3306
    targetPort: 3306
  selector:
    app: halo-mysql
  clusterIP: None

---
## 将 redis 缓存以 StatefulSet 工作负载形式创建出来
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: halo-redis
  labels:
    app: halo-redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: halo-redis
  template:
    metadata:
      labels:
        app: halo-redis
    spec:
      containers:
        - name: halo-redis
          image: 'redis:5.0.8'
          ports:
            - name: tcp-6379                       # 将容器的 6379 端口暴露出来
              protocol: TCP
              containerPort: 6379
          livenessProbe:                           # 健康检查配置,添加探针以定时检查容器健康状态
            initialDelaySeconds: 300
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
            tcpSocket:
              port: 6379
          readinessProbe:
            initialDelaySeconds: 5
            timeoutSeconds: 1
            periodSeconds: 10
            successThreshold: 1
            failureThreshold: 3
            tcpSocket:
              port: 6379
          command:
            - sh
            - '-c'
            - redis-server /usr/local/etc/redis/redis.conf
          volumeMounts:
            - readOnly: false
              mountPath: /data                    # 将 redis 缓存的 “/data” 进行持久化,数据库落盘到名称为 “halo-redis-pvc” 的持久化数据卷
              name: redis-pvc
            - name: halo-redis-config             # 将 redis.conf 配置文件以 ConfigMap 的形式进行外部挂载
              readOnly: true
              mountPath: /usr/local/etc/redis/redis.conf
              subPath: redis.conf
      initContainers:                             # 初始化容器定义,使用启动脚本通过初始化容器对 redis 进行初始化操作
        - name: system-init
          image: 'busybox:1.32'
          command:
            - sh
            - '-c'
            - >-
              echo 2048 > /proc/sys/net/core/somaxconn && echo never >
              /sys/kernel/mm/transparent_hugepage/enabled
          securityContext:                        # 启用容器安全上下文权限为特权模式,以主机上的 root 用户运行容器进程
            privileged: true
      volumes:                                    # 将前面创建的 ConfigMap 定义出来,方便后面调用
        - name: halo-redis-config
          configMap:
            name: halo-redis-config
  serviceName: halo-redis-service
  volumeClaimTemplates:                           # 创建 redis 缓存需要的持久化卷
    - spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 10Gi
        storageClassName: nfs-client              # storageClass 可根据您本地集群拥有的类型进行选择,这里采用的是 nfs-client
      metadata:
        name: redis-pvc

---
## 为 redis 工作负载创建 service
apiVersion: v1
kind: Service
metadata:
  name: halo-redis-service
spec:
  selector:
    app: halo-redis
  ports:
    - name: tcp-6379
      protocol: TCP
      port: 6379
      targetPort: 6379
  clusterIP: None

---
## 将 halo 前端服务以 Deployment 工作负载的方式创建出来
apiVersion: apps/v1
kind: Deployment
metadata:
  name: halo-front
  labels:
    app: halo-front
spec:
  replicas: 1
  selector:
    matchLabels:
      app: halo-front
  template:
    metadata:
      labels:
        app: halo-front
    spec:
      volumes:                                # 将前面创建的 ConfigMap 定义出来,方便后面调用
        - name: halo-front-pvc
          persistentVolumeClaim:
            claimName: halo-front-pvc
      containers:
        - name: halo
          image: 'halohub/halo:1.5.2'         # halo 的镜像版本,后续修改这里的版本号可以实现 halo 的滚动升级
          ports:
            - name: http-8090
              containerPort: 8090
              protocol: TCP
          env:                                # halo 的环境变量配置,可参考https://docs.halo.run/getting-started/install/other/docker-compose
            - name: SERVER_PORT
              value: '8090'
            - name: SPRING_DATASOURCE_DRIVER_CLASS_NAME
              value: com.mysql.cj.jdbc.Driver
            - name: SPRING_DATASOURCE_URL     # 注意下面 3306 前面的 halo-mysql-service 为 mysql 数据库的 service 名称,如果前面 mysql 数据库的 service 做了修改,这里也要修改
              value: jdbc:mysql://halo-mysql-service:3306/halodb?characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
            - name: SPRING_DATASOURCE_USERNAME
              value: root
            - name: SPRING_DATASOURCE_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: halo-secret           # halo 连接数据库的密码,这里采用 Secret 方式初始化连接密码,下面填写最开始创建的名称为“halo-secret”的 Secret
                  key: SPRING_DATASOURCE_PASSWORD
            - name: HALO_ADMIN_PATH
              value: admin
            - name: HALO_CACHE
              value: redis
            - name: SPRING_REDIS_PORT
              value: '6379'
            - name: SPRING_REDIS_DATABASE
              value: '0'
            - name: SPRING_REDIS_HOST
              value: halo-redis-service
            - name: SPRING_REDIS_PASSWORD     # halo 连接缓存的密码,这里采用 Secret 方式初始化连接密码,下面填写最开始创建的名称为“halo-secret”的 Secret
              valueFrom:
                secretKeyRef:
                  name: halo-secret
                  key: SPRING_REDIS_PASSWORD
          volumeMounts:                       # 将 halo 的 “/root/.halo” 进行持久化,数据库落盘到名称为 “halo-front-pvc” 的持久化数据卷
            - name: halo-front-pvc
              mountPath: /root/.halo

---
## 为 halo 工作负载创建 service,并通过 nodePort 的方式暴露出来
apiVersion: v1
kind: Service
metadata:
  name: halo-front-service
spec:
  type: NodePort
  ports:
  - name: tcp-8090
    protocol: TCP
    port: 8090
    targetPort: 8090
    nodePort: 30890
  selector:
    app: halo-front

执行下面命令进行部署:

## 为 halo 项目创建一个命名空间里
$ kubectl create namespace halo-project
namespace/halo-project created

## 将 halo 部署到 halo-project 命名空间里
$ kubectl -n halo-project apply -f halo-app.yaml
secret/halo-mysql-secret created
secret/halo-secret created
configmap/halo-mysql-config created
configmap/halo-redis-config created
persistentvolumeclaim/halo-front-pvc created
statefulset.apps/halo-mysql created
service/halo-mysql-service created
statefulset.apps/halo-redis created
service/halo-redis-service created
deployment.apps/halo-front created
service/halo-front-service created

稍等片刻,查看 pod 启动情况:

$ kubectl get pod -n halo-project
NAME                         READY   STATUS    RESTARTS      AGE
halo-front-7d6dc4c66-pqx9d   1/1     Running   2 (80s ago)   103s
halo-mysql-0                 1/1     Running   0             103s
halo-redis-0                 1/1     Running   0             103s

查看 halo 容器的日志输出:

$ kubectl logs -f -n halo-project halo-front-7d6dc4c66-pqx9d

    __  __      __
   / / / /___ _/ /___
  / /_/ / __ `/ / __ \
 / __  / /_/ / / /_/ /
/_/ /_/\__,_/_/\____/

Version: 1.5.2
2022-06-20 12:59:53.833  INFO 6 --- [           main] run.halo.app.Application                 : Starting Application v1.5.2 using Java 11.0.11 on halo-front-7d6dc4c66-pqx9d with PID 6 (/application/BOOT-INF/classes started by root in /application)
2022-06-20 12:59:53.837  INFO 6 --- [           main] run.halo.app.Application                 : No active profile set, falling back to 1 default profile: "default"
2022-06-20 12:59:55.616  INFO 6 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2022-06-20 12:59:55.622  INFO 6 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-06-20 12:59:56.061  INFO 6 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 423 ms. Found 24 JPA repository interfaces.
2022-06-20 12:59:57.615  INFO 6 --- [           main] org.eclipse.jetty.util.log               : Logging initialized @5567ms to org.eclipse.jetty.util.log.Slf4jLog
2022-06-20 12:59:57.904  INFO 6 --- [           main] o.s.b.w.e.j.JettyServletWebServerFactory : Server initialized with port: 8090
2022-06-20 12:59:57.912  INFO 6 --- [           main] org.eclipse.jetty.server.Server          : jetty-9.4.45.v20220203; built: 2022-02-03T09:14:34.105Z; git: 4a0c91c0be53805e3fcffdcdcc9587d5301863db; jvm 11.0.11+9
2022-06-20 12:59:57.970  INFO 6 --- [           main] o.e.j.s.h.ContextHandler.application     : Initializing Spring embedded WebApplicationContext
2022-06-20 12:59:57.970  INFO 6 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 4052 ms
2022-06-20 12:59:58.754  INFO 6 --- [           main] run.halo.app.config.HaloConfiguration    : Halo cache store load impl : [class run.halo.app.cache.RedisCacheStore]
2022-06-20 12:59:59.912  INFO 6 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-06-20 12:59:59.991  INFO 6 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate ORM core version 5.4.33
2022-06-20 13:00:00.069  INFO 6 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-06-20 13:00:00.272  INFO 6 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...
2022-06-20 13:00:00.665  INFO 6 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.
2022-06-20 13:00:00.715  INFO 6 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQL8Dialect
2022-06-20 13:00:04.196  INFO 6 --- [           main] o.h.e.t.j.p.i.JtaPlatformInitiator       : HHH000490: Using JtaPlatform implementation: [org.hibernate.engine.transaction.jta.platform.internal.NoJtaPlatform]
2022-06-20 13:00:04.212  INFO 6 --- [           main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2022-06-20 13:00:05.574  INFO 6 --- [           main] org.eclipse.jetty.server.session         : DefaultSessionIdManager workerName=node0
2022-06-20 13:00:05.578  INFO 6 --- [           main] org.eclipse.jetty.server.session         : No SessionScavenger set, using defaults
2022-06-20 13:00:05.580  INFO 6 --- [           main] org.eclipse.jetty.server.session         : node0 Scavenging every 600000ms
2022-06-20 13:00:05.593  INFO 6 --- [           main] o.e.jetty.server.handler.ContextHandler  : Started o.s.b.w.e.j.JettyEmbeddedWebAppContext@2416378c{application,/,[file:///tmp/jetty-docbase.8090.3312258845098811428/, jar:file:/application/BOOT-INF/lib/springfox-swagger-ui-3.0.0.jar!/META-INF/resources],AVAILABLE}
2022-06-20 13:00:05.594  INFO 6 --- [           main] org.eclipse.jetty.server.Server          : Started @13548ms
2022-06-20 13:00:06.595  INFO 6 --- [           main] run.halo.app.handler.file.FileHandlers   : Registered 9 file handler(s)
2022-06-20 13:00:10.495  INFO 6 --- [           main] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 4 endpoint(s) beneath base path '/api/admin/actuator'
2022-06-20 13:00:10.565  INFO 6 --- [           main] o.e.j.s.h.ContextHandler.application     : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-06-20 13:00:10.566  INFO 6 --- [           main] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-06-20 13:00:10.571  INFO 6 --- [           main] o.s.web.servlet.DispatcherServlet        : Completed initialization in 2 ms
2022-06-20 13:00:10.584  INFO 6 --- [           main] o.e.jetty.server.AbstractConnector       : Started ServerConnector@5fd8dd66{HTTP/1.1, (http/1.1)}{0.0.0.0:8090}
2022-06-20 13:00:10.585  INFO 6 --- [           main] o.s.b.web.embedded.jetty.JettyWebServer  : Jetty started on port(s) 8090 (http/1.1) with context path '/'
2022-06-20 13:00:10.613  INFO 6 --- [           main] run.halo.app.Application                 : Started Application in 17.761 seconds (JVM running for 18.567)
2022-06-20 13:00:10.617  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Starting migrate database...
2022-06-20 13:00:10.740  INFO 6 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 7.15.0 by Redgate
2022-06-20 13:00:10.741  INFO 6 --- [           main] o.f.c.i.database.base.BaseDatabaseType   : Database: jdbc:mysql://halo-mysql-service:3306/halodb (MySQL 8.0)
2022-06-20 13:00:10.784  INFO 6 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Repair of failed migration in Schema History table `halodb`.`flyway_schema_history` not necessary as table doesn't exist.
2022-06-20 13:00:10.802  INFO 6 --- [           main] o.f.core.internal.command.DbRepair       : Successfully repaired schema history table `halodb`.`flyway_schema_history` (execution time 00:00.023s).
2022-06-20 13:00:10.816  INFO 6 --- [           main] o.f.c.internal.license.VersionPrinter    : Flyway Community Edition 7.15.0 by Redgate
2022-06-20 13:00:10.836  INFO 6 --- [           main] o.f.core.internal.command.DbValidate     : Successfully validated 6 migrations (execution time 00:00.006s)
2022-06-20 13:00:10.872  INFO 6 --- [           main] o.f.c.i.s.JdbcTableSchemaHistory         : Creating Schema History table `halodb`.`flyway_schema_history` with baseline ...
2022-06-20 13:00:11.018  INFO 6 --- [           main] o.f.core.internal.command.DbBaseline     : Successfully baselined schema with version: 1
2022-06-20 13:00:11.031  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Current version of schema `halodb`: 1
2022-06-20 13:00:11.037  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `halodb` to version "2 - migrate 1.2.0-beta.1 to 1.2.0-beta.2"
2022-06-20 13:00:11.177  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `halodb` to version "3 - migrate 1.3.0-beta.1 to 1.3.0-beta.2"
2022-06-20 13:00:11.698  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `halodb` to version "4 - migrate 1.3.0-beta.2 to 1.3.0-beta.3"
2022-06-20 13:00:11.779  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `halodb` to version "5 - migrate remove notnull for email in comments table"
2022-06-20 13:00:11.834  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Migrating schema `halodb` to version "6 - migrate create contents table"
2022-06-20 13:00:11.904  INFO 6 --- [           main] o.f.core.internal.command.DbMigrate      : Successfully applied 5 migrations to schema `halodb`, now at version v6 (execution time 00:00.882s)
2022-06-20 13:00:11.913  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Migrate database succeed.
2022-06-20 13:00:11.914  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Created backup directory: [/tmp/halo-backup]
2022-06-20 13:00:11.914  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Created data export directory: [/tmp/halo-data-export]
2022-06-20 13:00:13.554  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Copied theme folder from [/application/BOOT-INF/classes/templates/themes] to [/root/.halo/templates/themes/caicai_anatole]
2022-06-20 13:00:13.598  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Halo started at         http://127.0.0.1:8090
2022-06-20 13:00:13.598  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Halo admin started at   http://127.0.0.1:8090/admin
2022-06-20 13:00:13.598  INFO 6 --- [           main] run.halo.app.listener.StartedListener    : Halo has started successfully!

通过输出日志,可以看到 halo 已启动成功。
访问 halo ,在浏览器输入 http://nodeIP:30890,进入 halo 安装向导页面:
image-1655704539304