首頁 > 軟體

KubeSphere中部署Wiki系統wiki.js並啟用中文全文檢索

2022-06-25 18:00:54

背景

wiki.js 是優秀的開源 Wiki 系統,相較於 xwiki ,功能目前性上比 xwiki 不夠完善,但也在不斷進步。 Wiki 寫作、分享、許可權管理功能還是有的,勝在 UI 設計很漂亮,能滿足小團隊的基本知識管理需求。

以下工作是在 KubeSphere 3.2.1 + Helm 3 已經部署好的情況下進行的。

部署 KuberSphere 的方法官網有很詳細的檔案介紹,這裡不再贅敘。 kubesphere.com.cn/docs/instal…

準備 storageclass

我們使用 OpenEBS 作為儲存,OpenEBS 預設安裝的 Local StorageSlass 在 Pod 銷燬後自動刪除,不適合用於我的 MySQL 儲存,我們在 Local StorageClass 基礎上稍作修改,建立新的 StorageClass,允許 Pod 銷燬後,PV 內容繼續保留,手動決定怎麼處理。

apiVersion: v1
items:
- apiVersion: storage.k8s.io/v1
  kind: StorageClass
  metadata:
    annotations:
      cas.openebs.io/config: |
        - name: StorageType
          value: "hostpath"
        - name: BasePath
          value: "/var/openebs/localretain/"
      openebs.io/cas-type: local
      storageclass.beta.kubernetes.io/is-default-class: "false"
      storageclass.kubesphere.io/supported-access-modes: '["ReadWriteOnce"]'
    name: localretain
  provisioner: openebs.io/local
  reclaimPolicy: Retain
  volumeBindingMode: WaitForFirstConsumer
kind: List
metadata:
  resourceVersion: ""
  selfLink: ""

部署 PostgreSQL 資料庫

我們團隊其他專案中也需要使用 PostgreSQL, 為了提高 PostgreSQL 資料庫的利用率和統一管理,我們獨立部署 PostgreSQL,並在安裝 wiki.js 時,設定為使用外部資料庫。

準備使用者名稱密碼設定

我們使用 Secret 儲存 PostgreSQL 使用者密碼等敏感資訊。

kind: Secret
apiVersion: v1
metadata:
  name: postgres-prod
data:
  POSTGRES_PASSWORD: xxxx
type: Opaque

以上 POSTGRES_PASSWORD 自行準備,為 base64 編碼的資料。

準備資料庫初始化指令碼

使用 ConfigMap 儲存資料庫初始化指令碼,在 資料庫建立時,將 ConfigMap 中的資料庫初始化指令碼掛載到 /docker-entrypoint-initdb.d, 容器初始化時會自動執行該指令碼。

apiVersion: v1
kind: ConfigMap
metadata:
  name: wikijs-postgres-init
data:
  init.sql: |-
    CREATE DATABASE wikijs;
    CREATE USER wikijs with password 'xxxx';
    GRANT CONNECT ON DATABASE wikijs to wikijs;
    GRANT USAGE ON SCHEMA public TO wikijs;
    GRANT SELECT,update,INSERT,delete ON ALL TABLES IN SCHEMA public TO wikijs;
    ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO wikijs;

以上 wikijs 使用者的密碼自行準備,明文儲存。

準備儲存

我們使用 KubeSphere 預設安裝的 OpenEBS 來提供儲存服務。可以通過建立 PVC 來提供持久化儲存。

這裡宣告一個 10G 的 PVC。

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: postgres-prod-data
  finalizers:
    - kubernetes.io/pvc-protection
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: localretain
  volumeMode: Filesystem

部署 PostgreSQL 資料庫

在前面的步驟準備好各種設定資訊和儲存後,就可以開始部署 PostgreSQL 服務了。

我們的 Kubernetes 沒有設定儲存陣列,使用的是 OpenEBS 作為儲存,採用 Deployment 方式部署 PostgreSQL。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: postgres-prod
  name: postgres-prod
spec:
  replicas: 1
  selector:
    matchLabels:
      app: postgres-prod
  template:
    metadata:
      labels:
        app: postgres-prod
    spec:
      containers:
        - name: db
          imagePullPolicy: IfNotPresent
          image: 'abcfy2/zhparser:12-alpine'
          ports:
            - name: tcp-5432
              protocol: TCP
              containerPort: 5432
          envFrom:
          - secretRef:
              name: postgres-prod
          volumeMounts:
            - name: postgres-prod-data
              readOnly: false
              mountPath: /var/lib/postgresql/data
            - name: wikijs-postgres-init
              readOnly: true
              mountPath: /docker-entrypoint-initdb.d
      volumes:
        - name: postgres-prod-data
          persistentVolumeClaim:
            claimName: postgres-prod-data
        - name: wikijs-postgres-init
          configMap:
            name: wikijs-postgres-init

建立供其他 Pod 存取的 Service

apiVersion: v1
kind: Service
metadata:
  name: postgres-prod
spec:
  selector:
    app: postgres-prod
  ports:
    - protocol: TCP
      port: 5432
      targetPort: tcp-5432

完成 PostgreSQL 部署

測試略

部署 wiki.js

準備使用者名稱密碼設定

我們使用 Secret 儲存 wiki.js 用於連線資料庫的使用者名稱密碼等敏感資訊。

apiVersion: v1
kind: Secret
metadata:
  name: wikijs
data:
  DB_USER: d2lraWpz
  DB_PASS: xxxx
type: Opaque

以上 DB_PASS 自行準備,為 base64 編碼的資料。

準備資料庫連線設定

我們使用 ConfigMap 儲存 wiki.js 的資料庫連線資訊。

apiVersion: v1
kind: ConfigMap
metadata:
  name: wikijs
data:
  DB_TYPE: postgres
  DB_HOST: postgres-prod.infra
  DB_PORT: "5432"
  DB_NAME: wikijs
  HA_ACTIVE: "true"

建立資料庫使用者和資料庫

如果 PostgreSQL 資料庫裡沒有建立 wikijs 使用者和資料 ,需要手工完成一下工作:

通過『資料庫工具』連線 PostgreSQL 資料庫,執行一下 SQL 語句,完成資料庫和使用者的建立、授權。

CREATE DATABASE wikijs;
CREATE USER wikijs with password 'xxxx';
GRANT CONNECT ON DATABASE wikijs to wikijs;
GRANT USAGE ON SCHEMA public TO wikijs;
GRANT SELECT,update,INSERT,delete ON ALL TABLES IN SCHEMA public TO wikijs;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO wikijs;

以上 wikijs 的密碼自行修改。

準備 wiki.js 的 yaml 部署檔案

採用 Deployment 方式 部署 wiki.js 的 yaml 檔案如下:

# wikijs-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: wikijs
  name: wikijs
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wikijs
  template:
    metadata:
      labels:
        app: wikijs
    spec:
      containers:
        - name: wikijs
          image: 'requarks/wiki:2'
          ports:
            - name: http-3000
              protocol: TCP
              containerPort: 3000
          envFrom:
          - secretRef:
              name: wikijs
          - configMapRef:
              name: wikijs

建立叢集記憶體取 wiki.js 的 Service

# wikijs-svc.yaml
apiVersion: v1
kind: Service
metadata:
  name: wikijs
spec:
  selector:
    app: wikijs
  ports:
    - protocol: TCP
      port: 3000
      targetPort: http-3000

建立叢集外存取的 Ingress

# wikijs-ing.yaml
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: wikijs
spec:
  ingressClassName: nginx
  rules:
    - host: wiki.xxxx.cn
      http:
        paths:
          - path: /
            pathType: ImplementationSpecific
            backend:
              service:
                name: wikijs
                port:
                  number: 3000

以上 host 域名需要自行設定。

執行部署

$ kubectl apply -f wikijs-deploy.yaml
$ kubectl apply -f wikijs-svc.yaml
$ kubectl apply -f wikijs-ing.yaml

設定 wiki.js 支援中文全文檢索

wiki.js 的全文檢索支援基於 PostgreSQL 的檢索,也支援 Elasticsearch 等,相對來說, PostgreSQL 比較輕量級,本專案中,我們使用 PostgreSQL 的全文檢索。

但是,因為 PostgreSQL 不支援中文分詞,需要額外安裝外掛並設定啟用中文分詞,下面描述了為 wiki.js 啟動基於 PostgreSQL 資料庫中文分詞的全文檢索。

授予 wikijs 使用者臨時超管許可權

通過資料庫管理工具登入有超管許可權的 PostgreSQL 使用者,臨時授予 wiki.js 使用者臨時超管許可權,便於啟動中文分詞功能。

ALTER USER wikijs WITH SUPERUSER;

啟用資料庫的中文分詞能力

使用資料庫管理工具登入 PostgreSQL 資料庫的 wikijs 使用者,執行以下命令,啟動資料庫的中文分詞功能。

CREATE EXTENSION pg_trgm;
CREATE EXTENSION zhparser;
CREATE TEXT SEARCH CONFIGURATION pg_catalog.chinese_zh (PARSER = zhparser);
ALTER TEXT SEARCH CONFIGURATION chinese_zh ADD MAPPING FOR n,v,a,i,e,l WITH simple;
-- 忽略標點影響
ALTER ROLE wikijs SET zhparser.punctuation_ignore = ON;
-- 短詞複合
ALTER ROLE wikijs SET zhparser.multi_short = ON;
-- 測試一下
select ts_debug('chinese_zh', '青春是最美好的年歲,青春是最燦爛的日子。每一個人的青春都無比寶貴,寶貴的青春只有與奮鬥為伴才最閃光、最出彩。');

取消 wikijs 使用者的臨時超管許可權

登入 PostgreSQL 資料庫 wikijs 使用者,取消 wikijs 使用者的超管許可權。

ALTER USER wikijs WITH NOSUPERUSER;

建立支援中文分詞的設定 ConfigMap

# zh-parse.yaml
kind: ConfigMap
apiVersion: v1
metadata:
  name: wikijs-zhparser
data:
  definition.yml: |-
    key: postgres
    title: Database - PostgreSQL
    description: Advanced PostgreSQL-based search engine.
    author: requarks.io
    logo: https://static.requarks.io/logo/postgresql.svg
    website: https://www.requarks.io/
    isAvailable: true
    props:
      dictLanguage:
        type: String
        title: Dictionary Language
        hint: Language to use when creating and querying text search vectors.
        default: english
        enum:
          - simple
          - danish
          - dutch
          - english
          - finnish
          - french
          - german
          - hungarian
          - italian
          - norwegian
          - portuguese
          - romanian
          - russian
          - spanish
          - swedish
          - turkish
          - chinese_zh
        order: 1
  engine.js: |-
    const tsquery = require('pg-tsquery')()
    const stream = require('stream')
    const Promise = require('bluebird')
    const pipeline = Promise.promisify(stream.pipeline)
    /* global WIKI */
    module.exports = {
      async activate() {
        if (WIKI.config.db.type !== 'postgres') {
          throw new WIKI.Error.SearchActivationFailed('Must use PostgreSQL database to activate this engine!')
        }
      },
      async deactivate() {
        WIKI.logger.info(`(SEARCH/POSTGRES) Dropping index tables...`)
        await WIKI.models.knex.schema.dropTable('pagesWords')
        await WIKI.models.knex.schema.dropTable('pagesVector')
        WIKI.logger.info(`(SEARCH/POSTGRES) Index tables have been dropped.`)
      },
      /**
       * INIT
       */
      async init() {
        WIKI.logger.info(`(SEARCH/POSTGRES) Initializing...`)
        // -> Create Search Index
        const indexExists = await WIKI.models.knex.schema.hasTable('pagesVector')
        if (!indexExists) {
          WIKI.logger.info(`(SEARCH/POSTGRES) Creating Pages Vector table...`)
          await WIKI.models.knex.schema.createTable('pagesVector', table => {
            table.increments()
            table.string('path')
            table.string('locale')
            table.string('title')
            table.string('description')
            table.specificType('tokens', 'TSVECTOR')
            table.text('content')
          })
        }
        // -> Create Words Index
        const wordsExists = await WIKI.models.knex.schema.hasTable('pagesWords')
        if (!wordsExists) {
          WIKI.logger.info(`(SEARCH/POSTGRES) Creating Words Suggestion Index...`)
          await WIKI.models.knex.raw(`
            CREATE TABLE "pagesWords" AS SELECT word FROM ts_stat(
              'SELECT to_tsvector(''simple'', "title") || to_tsvector(''simple'', "description") || to_tsvector(''simple'', "content") FROM "pagesVector"'
            )`)
          await WIKI.models.knex.raw('CREATE EXTENSION IF NOT EXISTS pg_trgm')
          await WIKI.models.knex.raw(`CREATE INDEX "pageWords_idx" ON "pagesWords" USING GIN (word gin_trgm_ops)`)
        }
        WIKI.logger.info(`(SEARCH/POSTGRES) Initialization completed.`)
      },
      /**
       * QUERY
       *
       * @param {String} q Query
       * @param {Object} opts Additional options
       */
      async query(q, opts) {
        try {
          let suggestions = []
          let qry = `
            SELECT id, path, locale, title, description
            FROM "pagesVector", to_tsquery(?,?) query
            WHERE (query @@ "tokens" OR path ILIKE ?)
          `
          let qryEnd = `ORDER BY ts_rank(tokens, query) DESC`
          let qryParams = [this.config.dictLanguage, tsquery(q), `%${q.toLowerCase()}%`]
          if (opts.locale) {
            qry = `${qry} AND locale = ?`
            qryParams.push(opts.locale)
          }
          if (opts.path) {
            qry = `${qry} AND path ILIKE ?`
            qryParams.push(`%${opts.path}`)
          }
          const results = await WIKI.models.knex.raw(`
            ${qry}
            ${qryEnd}
          `, qryParams)
          if (results.rows.length < 5) {
            const suggestResults = await WIKI.models.knex.raw(`SELECT word, word <-> ? AS rank FROM "pagesWords" WHERE similarity(word, ?) > 0.2 ORDER BY rank LIMIT 5;`, [q, q])
            suggestions = suggestResults.rows.map(r => r.word)
          }
          return {
            results: results.rows,
            suggestions,
            totalHits: results.rows.length
          }
        } catch (err) {
          WIKI.logger.warn('Search Engine Error:')
          WIKI.logger.warn(err)
        }
      },
      /**
       * CREATE
       *
       * @param {Object} page Page to create
       */
      async created(page) {
        await WIKI.models.knex.raw(`
          INSERT INTO "pagesVector" (path, locale, title, description, "tokens") VALUES (
            ?, ?, ?, ?, (setweight(to_tsvector('${this.config.dictLanguage}', ?), 'A') || setweight(to_tsvector('${this.config.dictLanguage}', ?), 'B') || setweight(to_tsvector('${this.config.dictLanguage}', ?), 'C'))
          )
        `, [page.path, page.localeCode, page.title, page.description, page.title, page.description, page.safeContent])
      },
      /**
       * UPDATE
       *
       * @param {Object} page Page to update
       */
      async updated(page) {
        await WIKI.models.knex.raw(`
          UPDATE "pagesVector" SET
            title = ?,
            description = ?,
            tokens = (setweight(to_tsvector('${this.config.dictLanguage}', ?), 'A') ||
            setweight(to_tsvector('${this.config.dictLanguage}', ?), 'B') ||
            setweight(to_tsvector('${this.config.dictLanguage}', ?), 'C'))
          WHERE path = ? AND locale = ?
        `, [page.title, page.description, page.title, page.description, page.safeContent, page.path, page.localeCode])
      },
      /**
       * DELETE
       *
       * @param {Object} page Page to delete
       */
      async deleted(page) {
        await WIKI.models.knex('pagesVector').where({
          locale: page.localeCode,
          path: page.path
        }).del().limit(1)
      },
      /**
       * RENAME
       *
       * @param {Object} page Page to rename
       */
      async renamed(page) {
        await WIKI.models.knex('pagesVector').where({
          locale: page.localeCode,
          path: page.path
        }).update({
          locale: page.destinationLocaleCode,
          path: page.destinationPath
        })
      },
      /**
       * REBUILD INDEX
       */
      async rebuild() {
        WIKI.logger.info(`(SEARCH/POSTGRES) Rebuilding Index...`)
        await WIKI.models.knex('pagesVector').truncate()
        await WIKI.models.knex('pagesWords').truncate()
        await pipeline(
          WIKI.models.knex.column('path', 'localeCode', 'title', 'description', 'render').select().from('pages').where({
            isPublished: true,
            isPrivate: false
          }).stream(),
          new stream.Transform({
            objectMode: true,
            transform: async (page, enc, cb) => {
              const content = WIKI.models.pages.cleanHTML(page.render)
              await WIKI.models.knex.raw(`
                INSERT INTO "pagesVector" (path, locale, title, description, "tokens", content) VALUES (
                  ?, ?, ?, ?, (setweight(to_tsvector('${this.config.dictLanguage}', ?), 'A') || setweight(to_tsvector('${this.config.dictLanguage}', ?), 'B') || setweight(to_tsvector('${this.config.dictLanguage}', ?), 'C')), ?
                )
              `, [page.path, page.localeCode, page.title, page.description, page.title, page.description, content,content])
              cb()
            }
          })
        )
        await WIKI.models.knex.raw(`
          INSERT INTO "pagesWords" (word)
            SELECT word FROM ts_stat(
              'SELECT to_tsvector(''simple'', "title") || to_tsvector(''simple'', "description") || to_tsvector(''simple'', "content") FROM "pagesVector"'
            )
          `)
        WIKI.logger.info(`(SEARCH/POSTGRES) Index rebuilt successfully.`)
      }
    }

更新 wikijs 的 Deployment

wiki.js 的基於 PostgreSQL 的全文檢索引擎設定位於 /wiki/server/modules/search/postgres ,我們將前面設定的 ConfigMap 載入到這個目錄。

# wikijs-zh.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
  name: wikijs
  labels:
    app: wikijs
spec:
  replicas: 1
  selector:
    matchLabels:
      app: wikijs
  template:
    metadata:
      labels:
        app: wikijs
    spec:
      volumes:
        - name: volume-dysh4f
          configMap:
            name: wikijs-zhparser
            defaultMode: 420
      containers:
        - name: wikijs
          image: 'requarks/wiki:2'
          ports:
            - name: http-3000
              containerPort: 3000
              protocol: TCP
          envFrom:
            - secretRef:
                name: wikijs
            - configMapRef:
                name: wikijs
          volumeMounts:
            - name: volume-dysh4f
              readOnly: true
              mountPath: /wiki/server/modules/search/postgres

設定 wiki.js ,啟用基於 PostgreSQL 的全文檢索

  • 重新 apply 新的 Delployment 檔案後
$ kubectl apply -f zh-parse.yaml
$ kubectl apply -f wikijs-zh.yaml
  • 開啟 wiki.js 管理
  • 點選搜尋引擎
  • 選擇 Database - PostgreSQL
  • 在 Dictionary Language 的下拉式選單裡選擇 chinese_zh。
  • 點選應用,並重建索引。
  • 完成設定。

總結

本文介紹的 wiki.js 部署方式支援中文全文檢索的支援,整合了 PostgreSQL 和 zhparser 中文分詞外掛。

相對於標準的 wiki.js 安裝部署過程,主要做了以下設定:

  • PostgreSQL 映象採用了 abcfy2/zhparser:12-alpine ,這個映象自帶 zhparser 中文分詞外掛。
  • wiki.js 映象外掛了 ConfigMap ,用於修改原 Docker 映象裡關於 PostgreSQL 搜尋引擎設定的資訊,以支援 chinese_zh 選項。

以上就是KubeSphere中部署Wiki系統wiki.js並啟用中文全文檢索的詳細內容,更多關於KubeSphere部署wiki.js並啟用的資料請關注it145.com其它相關文章!


IT145.com E-mail:sddin#qq.com