# SkillHub Helm Chart 企业级 AI 技能中心私有化部署方案,基于 Kubernetes 和 Helm。 ## 特性 - **微服务架构**:Server(Spring Boot)、Web(Nginx)、Scanner 分离部署 - **高可用**:支持 HPA 自动扩缩容、PDB Pod 中断预算 - **数据层**:使用 Bitnami PostgreSQL/Redis,支持主从复制、哨兵模式 - **安全**:TLS 证书管理、Secret 密码保护;Bitnami 数据组件默认提供 NetworkPolicy - **可观测性**:内置 Prometheus metrics exporter ## 快速开始 ### 前置要求 - Kubernetes 1.24+ - Helm 3.8+ - kubectl configured ### 安装 先创建受保护的 `values-production.yaml`。以下值必须替换为实际随机强密码: ```yaml secrets: allowAutoGenerated: false bootstrapAdminPassword: "<固定管理员密码>" downloadAnonCookieSecret: "<至少32字符的固定随机值>" postgresql: auth: postgresPassword: "<固定PostgreSQL管理员密码>" password: "<固定skillhub用户密码>" redis: auth: password: "<固定Redis密码>" ``` ```bash helm dependency build ./charts/skillhub kubectl create namespace skillhub helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set publicBaseUrl=https://skills.example.com ``` 未显式设置 `deviceAuthVerificationUri` 时,Chart 使用 `/cli/auth`。所有 values 会先经过 `values.schema.json` 和跨字段校验, 无效的组件、Ingress、HPA 与存储组合会在安装前失败。 > **Ingress values 迁移:** 当前版本只支持结构化的 `ingress.hosts[]` 和 > `ingress.tls[]`。旧的 `ingress.host`、`ingress.tls.enabled` 与 > `ingress.tls.secretName` 不再接受,升级前必须改成本文 Ingress 示例中的数组结构。 合并或发布前,可在一个空的测试 Kubernetes 集群中运行可重复的安装/升级 smoke: ```bash for scenario in default sentinel s3 ingress-tls; do HELM_SMOKE_SCENARIO="$scenario" \ bash charts/skillhub/tests/install-upgrade-smoke.sh done ``` 脚本验证 `install -> Ready -> HTTP health -> upgrade -> Ready`,并确认 Secret 数据、 PVC UID 与绑定 PV 在升级前后保持不变。四个场景分别覆盖默认依赖、Redis Sentinel、实际 MinIO S3 连接,以及由 Kubernetes API 接受的 TLS Ingress 路由。 默认清理自己创建的 namespace;设置 `KEEP_HELM_SMOKE=true` 可保留现场用于排查。 ### 高可用模式 ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set postgresql.architecture=replication \ --set postgresql.auth.replicationPassword=your-replication-password \ --set redis.architecture=replication ``` ### 外部数据库模式 ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set postgresql.enabled=false \ --set redis.enabled=false \ --set externalDatabase.host=postgres.example.com \ --set externalDatabase.port=5432 \ --set externalDatabase.database=skillhub \ --set externalDatabase.username=skillhub \ --set externalDatabase.password=your-db-password \ --set externalRedis.host=redis.example.com \ --set externalRedis.port=6379 \ --set externalRedis.password=your-redis-password ``` ### 使用 existingSecret 通过 `existingSecret` 引用已存在的 Secret 对象,避免在 values 中明文写入密码。 内置 PostgreSQL/Redis 使用各自的 Bitnami Secret,不需要复制到该 Secret。 | Key | 必填 | 说明 | |-----|------|------| | `spring-datasource-password` | 使用外部 PostgreSQL 时 | 数据库密码 | | `redis-password` | 使用外部 Redis 时 | Redis 密码 | | `redis-sentinel-password` | 使用外部 Sentinel 时 | Redis Sentinel 密码 | | `bootstrap-admin-password` | 是 | 初始管理员密码 | | `skillhub-download-anon-cookie-secret` | 是 | 至少 32 字符的匿名下载 Cookie 签名密钥 | | `oauth2-github-client-id` | 否 | GitHub OAuth2 Client ID | | `oauth2-github-client-secret` | 否 | GitHub OAuth2 Client Secret | | `skill-scanner-llm-api-key` | 否 | Scanner LLM API Key | | `skill-scanner-llm-base-url` | 否 | Scanner 自定义 LLM API 地址 | | `skill-scanner-llm-model` | 否 | Scanner LLM 模型名称 | | `skillhub-storage-s3-access-key` | 否 | S3 Access Key | | `skillhub-storage-s3-secret-key` | 否 | S3 Secret Key | ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set existingSecret=my-custom-secret ``` ### GitOps 稳定 Secret Argo CD 等 GitOps 工具使用离线 `helm template`,无法通过 Helm `lookup` 读取集群 中已有的 Secret。Bitnami 子 Chart 和父 Chart 的空密码会在每次渲染时重新随机 生成。Chart 默认禁止自动生成并要求提供固定值: ```yaml secrets: allowAutoGenerated: false bootstrapAdminPassword: "<固定管理员密码>" downloadAnonCookieSecret: "<至少32字符的固定随机值>" postgresql: auth: postgresPassword: "<固定PostgreSQL管理员密码>" password: "<固定skillhub用户密码>" # replication 架构还必须配置 replicationPassword redis: auth: password: "<固定Redis密码>" ``` 也可以为三个组件分别配置 `existingSecret`。`allowAutoGenerated=false` 不会生成 可预测密码,而是在任何随机密码缺失时终止渲染并指出具体配置项。敏感值应放在 受保护的 values、External Secrets、Sealed Secrets 或密钥注入插件中。 内置 PostgreSQL、Redis、Sentinel 及 metrics exporter 镜像默认使用不可变的 多架构 manifest digest,避免 Bitnami 子 Chart 的 `latest` 默认值造成不可复现的 安装和回滚,同时保留 amd64/arm64 支持。覆盖私有镜像仓库或 tag 时,必须同时把 对应的 `image.digest` 设为空,或改成私有仓库中该镜像的真实 digest;digest 非空时会优先于 tag。 ## 配置参考 ### 副本数配置 | 参数 | 描述 | 默认值 | |------|------|--------| | `server.replicaCount` | Server 副本数 | `1` | | `web.replicaCount` | Web 副本数 | `1` | | `scanner.replicaCount` | Scanner 副本数 | `1` | ```bash # 差异化副本配置 helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set server.replicaCount=3 \ --set server.storage.accessMode=ReadWriteMany \ --set web.replicaCount=2 \ --set scanner.replicaCount=1 ``` 本地存储运行多个 Server 副本时,必须显式设置 `ReadWriteMany`,并使用支持 RWX 的 StorageClass。无法提供 RWX 时应改用 S3。 ### 服务配置 | 参数 | 描述 | 默认值 | |------|------|--------| | `server.service.type` | Server Service 类型 | `ClusterIP` | | `server.service.port` | Server 端口 | `8080` | | `web.service.type` | Web Service 类型 | `ClusterIP` | | `web.service.port` | Web 端口 | `80` | | `scanner.service.port` | Scanner 端口 | `8000` | ### 私有镜像仓库 使用私有仓库时,需要分别覆盖 SkillHub 镜像、依赖等待镜像和 Bitnami 子 Chart 镜像。以下示例中的数据库镜像标签均为明确版本,不使用 `latest`: ```yaml global: imagePullSecrets: - private-registry security: allowInsecureImages: true images: registry: registry.example.com/library tag: v0.2.14 pullPolicy: IfNotPresent server: dependencyWait: image: registry: registry.example.com repository: library/busybox tag: "1.37" pullPolicy: IfNotPresent imagePullSecrets: - name: private-registry web: imagePullSecrets: - name: private-registry scanner: imagePullSecrets: - name: private-registry postgresql: image: registry: registry.example.com repository: library/postgresql tag: 18.4.0 digest: "" metrics: image: registry: registry.example.com repository: library/postgres-exporter tag: 0.20.1 digest: "" redis: image: registry: registry.example.com repository: library/redis tag: 8.8.0 digest: "" sentinel: image: registry: registry.example.com repository: library/redis-sentinel tag: 8.8.0 digest: "" metrics: image: registry: registry.example.com repository: library/redis-exporter tag: 1.86.0 digest: "" ``` `global.security.allowInsecureImages` 是 Bitnami 对自定义镜像仓库和镜像名称的校验 开关,并不表示使用不安全的 HTTP 仓库。先在目标 namespace 创建拉取凭据: ```bash kubectl create secret docker-registry private-registry \ -n skillhub \ --docker-server=registry.example.com \ --docker-username='<用户名>' \ --docker-password='<密码>' ``` ### 数据库配置 | 参数 | 描述 | 默认值 | |------|------|--------| | `postgresql.enabled` | 启用内置 PostgreSQL | `true` | | `postgresql.architecture` | 架构模式 | `standalone` | | `redis.enabled` | 启用内置 Redis | `true` | | `redis.architecture` | 架构模式 | `standalone` | #### 数据库架构支持边界 以下内置数据库目标架构已完成独立 namespace 的全新安装和运行时验证: | 数据组件 | 已验证架构 | 运行时验证 | |----------|------------|------------| | PostgreSQL | standalone | Server 连接、Flyway 和应用启动 | | PostgreSQL | replication | 1 Primary + 2 Read Replicas,两个副本均处于 recovery,流复制状态为 `streaming` | | Redis | standalone | Server 读写和应用启动 | | Redis | replication | 1 Master + 2 Replicas,角色和数据复制正常 | | Redis | replication + Sentinel | 3 个 Sentinel 节点 master 视图一致,Server 可通过 Sentinel 读写 | 上述支持表示 Chart 能够全新部署目标架构,并为 SkillHub 配置正确的写节点或 Sentinel 地址。Chart **不负责数据库架构切换时的数据迁移**,也不承诺仅修改 `architecture` 或 `sentinel.enabled` 就能保留已有数据。已有数据的 PostgreSQL standalone → replication、Redis standalone/replication → Sentinel 等切换,必须由 运维人员在 Chart 之外完成备份、恢复、PVC 复用或其他迁移方案。 外部 Redis Cluster 由云服务或运维系统提供,Chart 只负责注入连接配置,不创建 Cluster,也不将其计入上述内置架构运行时验证范围。应用侧应另行验证 Spring Data、Spring Session 与 Redisson Stream 链路。 ### Redis Sentinel 内置 Sentinel 使用 Bitnami Redis 的同一份密码同时保护 Redis 数据节点和 Sentinel。节点地址由副本数自动生成,不需要手动配置。由于 Bitnami Sentinel 上报的 Pod 地址可能与客户端连接的 Headless Service FQDN 不同,Chart 仅在该 内置模式下关闭 Redisson 的 Sentinel 地址一致性检查: ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set redis.architecture=replication \ --set redis.sentinel.enabled=true ``` 外部 Sentinel 必须提供至少一个 `host:port` 节点。Redis 数据密码和 Sentinel 密码可以不同;使用 `existingSecret` 时分别对应 `redis-password` 和 `redis-sentinel-password`。外部 Sentinel 默认保留 Redisson 地址一致性检查; 只有已确认服务发现会改写节点地址时,才通过 `server.extraEnv` 显式设置 `SKILLHUB_REDIS_SENTINEL_CHECK_SENTINELS_LIST=false`: ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set redis.enabled=false \ --set externalRedis.password=redis-password \ --set externalRedis.sentinel.enabled=true \ --set externalRedis.sentinel.password=sentinel-password \ --set-json 'externalRedis.sentinel.nodes=["sentinel-0.example.com:26379","sentinel-1.example.com:26379"]' ``` 确需关闭检查时,在 values 文件中显式记录该兼容例外: ```yaml server: extraEnv: - name: SKILLHUB_REDIS_SENTINEL_CHECK_SENTINELS_LIST value: "false" ``` ### 外部 Redis Cluster Chart 不创建内置 Redis Cluster。生产环境的 Cluster 由云 Redis 或独立运维系统 提供,SkillHub 通过标准 Spring Boot 配置连接。至少配置一个 seed 节点,且 Cluster 通告的所有节点地址都必须能从 Server Pod 访问: ```yaml redis: enabled: false externalRedis: username: skillhub tls: enabled: true connectTimeout: 5s timeout: 3s clientName: skillhub-server cluster: enabled: true nodes: - redis-0.example.com:6379 - redis-1.example.com:6379 - redis-2.example.com:6379 maxRedirects: 5 ``` Redis Cluster 只支持数据库 `0`。`maxRedirects` 交给 Spring Data/Lettuce 处理; Redisson 使用同一节点、ACL、TLS 与超时配置并自行处理 Cluster 路由。密码建议 通过 `existingSecret` 的 `redis-password` 提供: ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set redis.enabled=false \ --set existingSecret=skillhub-production-secret \ --set externalRedis.cluster.enabled=true \ --set-json 'externalRedis.cluster.nodes=["redis-0.example.com:6379","redis-1.example.com:6379","redis-2.example.com:6379"]' ``` `externalRedis.sentinel.enabled` 与 `externalRedis.cluster.enabled` 互斥;内置 `redis.enabled=true` 时也不能启用外部 Cluster。 ### 存储配置 | 参数 | 描述 | 默认值 | |------|------|--------| | `server.storage.accessMode` | 留空时单副本使用 ReadWriteOnce;多副本必须显式使用 ReadWriteMany | `""` | | `server.storage.size` | PVC 大小 | `10Gi` | | `server.storage.storageClassName` | StorageClass | `""` | | `server.podSecurityContext.fsGroup` | Server 本地存储的可写组 ID,应与镜像内 app 用户组一致 | `101` | | `server.podSecurityContext.fsGroupChangePolicy` | kubelet 调整 PVC 组权限的策略 | `OnRootMismatch` | 本地 PVC 会覆盖镜像内预先设置的目录所有者。Chart 默认通过 Pod `fsGroup=101` 使 Server 的非 root `app` 用户可以创建和更新技能文件。使用自定义 Server 镜像且其 运行组 ID 不同时,必须同步覆盖 `server.podSecurityContext.fsGroup`。 使用本地 `ReadWriteOnce` PVC 时,Server Deployment 自动采用 `Recreate`,避免 滚动升级期间新旧 Pod 同时挂载非共享卷而触发 Multi-Attach。单副本升级会有短暂 停机;使用支持 RWX 的 `ReadWriteMany` 存储或启用 S3 时,Chart 保留 `RollingUpdate`。 ```bash # 默认使用本地 PVC helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml ``` ### S3 对象存储 `s3.enabled=true` 时,不创建 PVC,应用使用 S3 作为存储后端。 | 参数 | 描述 | 默认值 | |------|------|--------| | `s3.enabled` | 启用 S3 | `false` | | `s3.bucket` | Bucket 名称 | `skillhub-storage` | | `s3.endpoint` | S3 端点,非空时必须是绝对 HTTP(S) URL | `""` | | `s3.publicEndpoint` | S3 公网访问端点,非空时必须是绝对 HTTP(S) URL | `""` | | `s3.region` | 区域 | `us-east-1` | | `s3.forcePathStyle` | 强制 path-style 访问 | `true` | | `s3.disableChunkedEncoding` | 禁用 aws-chunked 编码 | `false` | | `s3.autoCreateBucket` | 自动创建 Bucket | `false` | | `s3.accessKey` | Access Key | `""` | | `s3.secretKey` | Secret Key | `""` | ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set s3.enabled=true \ --set s3.bucket=your-bucket \ --set s3.endpoint=https://s3.amazonaws.com \ --set s3.region=us-east-1 \ --set s3.accessKey=your-access-key \ --set s3.secretKey=your-secret-key ``` ### Ingress + TLS ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set ingress.enabled=true \ --set-json 'ingress.hosts=[{"host":"skills.example.com","paths":[{"path":"/","pathType":"Prefix"}]}]' \ --set-json 'ingress.tls=[{"hosts":["skills.example.com"],"secretName":"skills-tls"}]' \ --set publicBaseUrl=https://skills.example.com \ --set ingress.certManager.enabled=true ``` 配置非空 `ingress.tls` 或启用 `ingress.certManager` 时,Chart 会自动将 Session Cookie 标记为 Secure。Ingress 要求 Server 和 Web Service 均保持启用。 `ingress.className` 和旧式 `kubernetes.io/ingress.class` annotation 均受支持, 可以任选其一,也可以同时输出。仅使用旧式 annotation 时将 `className` 留空: ```yaml ingress: enabled: true className: "" annotations: kubernetes.io/ingress.class: alb alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":6443}]' ``` `hosts` 是至少包含一个条目的对象数组。Chart 自动将 `/api`、`/oauth2`、 `/login/oauth2` 和 `/.well-known` 直接转发给 Server,确保 TLS 终止后的 OAuth 回调协议保持正确;`hosts[].paths` 中的其他路径转发给 Web,因此上述四个前缀 均为保留路径。`tls` 同样是数组,可为不同证书分别配置域名;TLS 域名会写入 cert-manager Certificate SAN: ```yaml ingress: hosts: - host: skills.example.com paths: - path: / pathType: Prefix - host: skills.internal.example.com paths: - path: / pathType: Prefix tls: - hosts: - skills.example.com - skills.internal.example.com secretName: skills-tls ``` ### 自动扩缩容 ```bash helm -n skillhub upgrade -i skillhub ./charts/skillhub \ -f values-production.yaml \ --set server.autoscaling.enabled=true \ --set server.autoscaling.minReplicas=2 \ --set server.autoscaling.maxReplicas=10 \ --set server.storage.accessMode=ReadWriteMany ``` 每个 HPA 至少需要一个非零 CPU 或内存利用率目标。本地存储的 Server HPA 同样 要求 RWX;也可以启用 S3 来避免共享 PVC。 ## 发布 `.github/workflows/publish-chart.yml` 在 GitHub Release 发布后或手动 `workflow_dispatch` 时运行。Release tag 必须使用 `vX.Y.Z`、`chart-vX.Y.Z` 或 `helm-vX.Y.Z`;手动运行时显式输入 `X.Y.Z`。工作流按该版本打包 Chart,并推送到 `oci://ghcr.io/iflytek/charts`,同时保留构建 artifact。 ## 卸载 ```bash helm -n skillhub uninstall skillhub ``` Server 数据 PVC 带有 `helm.sh/resource-policy: keep`,卸载 release 后仍会保留, 需要确认数据不再使用后手动删除。 ## 依赖 | 依赖 | 版本 | |------|------| | postgresql | 18.6.10 | | redis | 25.5.3 |