diff --git a/.github/workflows/pr-helm-chart.yml b/.github/workflows/pr-helm-chart.yml index 52fa7366..48afc044 100644 --- a/.github/workflows/pr-helm-chart.yml +++ b/.github/workflows/pr-helm-chart.yml @@ -36,6 +36,9 @@ jobs: with: version: latest + - name: Build dependencies + run: helm dependency build . + - name: Lint chart run: helm lint . @@ -124,24 +127,26 @@ jobs: with: version: latest + - name: Build dependencies + run: helm dependency build . + - name: Render template - ${{ matrix.scenario.name }} run: | echo "## ${{ matrix.scenario.description }}" - helm template test-release . ${{ matrix.scenario.args }} > /tmp/rendered.yaml + helm template test-release . ${{ matrix.scenario.args }} > rendered.yaml echo "✅ Template rendered successfully" - name: Validate resources run: | - RESOURCES=$(grep -c '^kind:' /tmp/rendered.yaml || true) + RESOURCES=$(grep -c '^kind:' rendered.yaml || true) echo "Rendered $RESOURCES resources for ${{ matrix.scenario.name }}" if [ "$RESOURCES" -eq 0 ]; then echo "ERROR: No resources rendered for ${{ matrix.scenario.name }}" exit 1 fi - grep -E '^ name:' /tmp/rendered.yaml | while read -r line; do - if echo "$line" | grep -qP '\{\{'; then - echo "ERROR: Unrendered template in name: $line" - exit 1 - fi - done - echo "✅ All resource names properly rendered" + + - name: Schema validation (kubeconform) + uses: docker://ghcr.io/yannh/kubeconform:latest + with: + entrypoint: '/kubeconform' + args: "-strict -summary -output text charts/skillhub/rendered.yaml" diff --git a/.gitignore b/.gitignore index 3a6c5f5b..76a972db 100644 --- a/.gitignore +++ b/.gitignore @@ -84,5 +84,8 @@ docs/superpowers/ # Local workspace metadata CLAUDE.md +# Helm chart dependencies +charts/skillhub/charts/*.tgz + # Local config file .mcp.json diff --git a/charts/skillhub/README.md b/charts/skillhub/README.md index b74d6985..19fcefc3 100644 --- a/charts/skillhub/README.md +++ b/charts/skillhub/README.md @@ -53,6 +53,30 @@ helm -n skillhub upgrade -i skillhub ./charts/skillhub \ --set externalRedis.password=your-redis-password ``` +### 使用 existingSecret + +通过 `existingSecret` 引用已存在的 Secret 对象,避免在 values 中明文写入密码。该 Secret 必须包含以下 key: + +| Key | 必填 | 说明 | +|-----|------|------| +| `spring-datasource-url` | 是 | JDBC 连接 URL | +| `spring-datasource-username` | 是 | 数据库用户名 | +| `spring-datasource-password` | 是 | 数据库密码 | +| `redis-password` | 是 | Redis 密码 | +| `redis-sentinel-password` | 否 | Redis Sentinel 密码(sentinel 模式) | +| `bootstrap-admin-password` | 是 | 初始管理员密码 | +| `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-model` | 否 | Scanner LLM 模型名称 | +| `s3-access-key` | 否 | S3 Access Key | +| `s3-secret-key` | 否 | S3 Secret Key | + +```bash +helm -n skillhub upgrade -i skillhub ./charts/skillhub \ + --set existingSecret=my-custom-secret +``` + ## 配置参考 ### 副本数配置 diff --git a/charts/skillhub/charts/postgresql-18.6.10.tgz b/charts/skillhub/charts/postgresql-18.6.10.tgz deleted file mode 100644 index cb6f57c3..00000000 Binary files a/charts/skillhub/charts/postgresql-18.6.10.tgz and /dev/null differ diff --git a/charts/skillhub/charts/redis-25.5.3.tgz b/charts/skillhub/charts/redis-25.5.3.tgz deleted file mode 100644 index ec0c013f..00000000 Binary files a/charts/skillhub/charts/redis-25.5.3.tgz and /dev/null differ diff --git a/charts/skillhub/templates/_helpers.tpl b/charts/skillhub/templates/_helpers.tpl index 824363d4..7315f3b4 100644 --- a/charts/skillhub/templates/_helpers.tpl +++ b/charts/skillhub/templates/_helpers.tpl @@ -138,8 +138,8 @@ app.kubernetes.io/component: scanner {{- $prefix := printf "%s-redis-node" (include "skillhub.fullname" .) -}} {{- $headless := printf "%s-redis-headless" (include "skillhub.fullname" .) -}} {{- $port := include "skillhub.redis.port" . -}} -{{- $replicas := .Values.redis.replica.replicaCount | default 2 | int -}} -{{- $first := true -}}{{- range $i := until $replicas -}}{{- if not $first -}},{{- end -}}{{ $prefix }}-{{ $i }}.{{ $headless }}.{{ $.Release.Namespace }}.svc.cluster.local:{{ $port }}{{- $first = false -}}{{- end -}} +{{- $replicas := .Values.redis.replica.replicaCount | default 3 | int -}} +{{- $nodes := list -}}{{- range $i := until $replicas -}}{{- $nodes = append $nodes (printf "%s-%d.%s.%s.svc.cluster.local:%s" $prefix $i $headless $.Release.Namespace $port) -}}{{- end -}}{{- join "," $nodes -}} {{- end }} {{- /* Redis Host */}} diff --git a/charts/skillhub/templates/ingress.yaml b/charts/skillhub/templates/ingress.yaml index 00a8b602..8c95928c 100644 --- a/charts/skillhub/templates/ingress.yaml +++ b/charts/skillhub/templates/ingress.yaml @@ -10,14 +10,6 @@ metadata: {{- if .Values.ingress.annotations }} {{- toYaml .Values.ingress.annotations | nindent 4 }} {{- end }} - {{- if .Values.ingress.certManager.enabled }} - {{- if eq .Values.ingress.certManager.issuerKind "ClusterIssuer" }} - cert-manager.io/cluster-issuer: {{ .Values.ingress.certManager.issuerName }} - {{- else }} - cert-manager.io/issuer: {{ .Values.ingress.certManager.issuerName }} - {{- end }} - cert-manager.io/issuer-kind: {{ .Values.ingress.certManager.issuerKind }} - {{- end }} spec: ingressClassName: {{ .Values.ingress.className }} {{- if or .Values.ingress.tls.enabled .Values.ingress.certManager.enabled }} diff --git a/charts/skillhub/templates/secret.yaml b/charts/skillhub/templates/secret.yaml index eb568b82..648fd9e7 100644 --- a/charts/skillhub/templates/secret.yaml +++ b/charts/skillhub/templates/secret.yaml @@ -53,7 +53,7 @@ stringData: redis-sentinel-password: {{ .Values.redis.auth.sentinelPassword | default .Values.redis.auth.password | quote }} {{- end }} {{- else if and (not .Values.redis.enabled) .Values.externalRedis.sentinel.enabled }} - redis-sentinel-password: {{ .Values.externalRedis.password | default "" | quote }} + redis-sentinel-password: {{ .Values.externalRedis.sentinel.password | default .Values.externalRedis.password | default "" | quote }} {{- end }} # Bootstrap 管理员密码 # 优先级: secrets.bootstrapAdminPassword → bootstrapAdmin.password → 集群已有 Secret → 随机生成 diff --git a/charts/skillhub/templates/server-deployment.yaml b/charts/skillhub/templates/server-deployment.yaml index ac854665..2363efe2 100644 --- a/charts/skillhub/templates/server-deployment.yaml +++ b/charts/skillhub/templates/server-deployment.yaml @@ -17,8 +17,8 @@ spec: labels: {{- include "skillhub.server.selectorLabels" . | nindent 8 }} annotations: - checksum/config: {{ toYaml (dict "redisHost" (include "skillhub.redis.host" .) "redisPort" (include "skillhub.redis.port" .) "storage" .Values.server.storage "s3" .Values.s3 "session" .Values.session "springProfilesActive" .Values.springProfilesActive "bootstrapAdmin" .Values.bootstrapAdmin "scannerEnabled" .Values.scanner.enabled "scannerPort" .Values.scanner.service.port) | sha256sum }} - checksum/secret: {{ toYaml (dict "secrets" .Values.secrets "bootstrapAdmin" .Values.bootstrapAdmin) | sha256sum }} + checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} + checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }} {{- range $key, $val := .Values.server.podAnnotations }} {{ $key }}: {{ $val }} {{- end }} @@ -285,4 +285,4 @@ spec: {{- toYaml . | nindent 8 }} {{- end }} -{{- end }} \ No newline at end of file +{{- end }} diff --git a/charts/skillhub/values.yaml b/charts/skillhub/values.yaml index 9cedffdc..3fb16248 100644 --- a/charts/skillhub/values.yaml +++ b/charts/skillhub/values.yaml @@ -82,19 +82,13 @@ secrets: postgresql: enabled: true - image: - registry: docker.io - repository: bitnami/postgresql - tag: latest - digest: "" - architecture: standalone auth: postgresPassword: "" database: skillhub username: skillhub - password: "skillhub_demo" + password: "" primary: persistence: @@ -163,17 +157,11 @@ externalDatabase: redis: enabled: true - image: - registry: docker.io - repository: bitnami/redis - tag: latest - digest: "" - architecture: standalone auth: enabled: true - password: "skillhub_redis" + password: "" sentinelPassword: "" master: @@ -235,6 +223,7 @@ externalRedis: enabled: false masterSet: mymaster nodes: [] + password: "" # ============================================================================ # Server 配置 diff --git a/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/RedissonConfig.java b/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/RedissonConfig.java index df8f528e..09123c37 100644 --- a/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/RedissonConfig.java +++ b/server/skillhub-app/src/main/java/com/iflytek/skillhub/config/RedissonConfig.java @@ -42,6 +42,8 @@ public class RedissonConfig { SentinelServersConfig sentinelServersConfig = config.useSentinelServers() .setMasterName(redisProperties.getSentinel().getMaster()) .setDatabase(redisProperties.getDatabase()) + // K8s headless DNS 场景下,客户端通过 pod FQDN 连接 sentinel, + // 与 sentinel 自身上报的地址格式不同,跳过地址一致性检查避免误报连接失败 .setCheckSentinelsList(false); List nodes = redisProperties.getSentinel().getNodes(); nodes.stream() diff --git a/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/RedissonConfigTest.java b/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/RedissonConfigTest.java index bd18e6a5..1095f16e 100644 --- a/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/RedissonConfigTest.java +++ b/server/skillhub-app/src/test/java/com/iflytek/skillhub/config/RedissonConfigTest.java @@ -136,6 +136,22 @@ class RedissonConfigTest { assertThat(sentinelConfig.isCheckSentinelsList()).isFalse(); } + @Test + void createConfig_doesNotSetSentinelPasswordWhenEmpty() throws Exception { + RedisProperties properties = new RedisProperties(); + RedisProperties.Sentinel sentinel = new RedisProperties.Sentinel(); + sentinel.setMaster("mymaster"); + sentinel.setNodes(List.of("redis-sentinel-1:26379")); + properties.setSentinel(sentinel); + properties.setPassword("master-secret"); + + Config config = RedissonConfig.createConfig(properties); + SentinelServersConfig sentinelConfig = sentinelConfig(config); + + assertThat(sentinelConfig.getPassword()).isEqualTo("master-secret"); + assertThat(sentinelConfig.getSentinelPassword()).isNull(); + } + private SentinelServersConfig sentinelConfig(Config config) throws Exception { Method method = Config.class.getDeclaredMethod("getSentinelServersConfig"); method.setAccessible(true);