fix(chart): 修复 PR review 反馈项

- 删除 subchart image block,使用 Bitnami 默认版本
- 移除 Ingress cert-manager annotation,消除双重签发
- 清空默认明文密码,改为空字符串
- 排除 tgz 进 git,CI 添加 helm dependency build
- checksum 改为模板级渲染,修复文件末尾换行
- sentinel default 3,列表生成改用 append+join
- 添加 externalRedis.sentinel.password 字段
- README 补充 existingSecret key 清单
- CI 添加 kubeconform -strict 校验
- RedissonConfig 添加注释,补充空密码测试用例

Signed-off-by: jangrui <admin@jangrui.com>
This commit is contained in:
jangrui 2026-06-04 06:32:30 +08:00
parent 07c97cf7cd
commit 0605357316
12 changed files with 68 additions and 37 deletions

View file

@ -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"

3
.gitignore vendored
View file

@ -84,5 +84,8 @@ docs/superpowers/
# Local workspace metadata
CLAUDE.md
# Helm chart dependencies
charts/skillhub/charts/*.tgz
# Local config file
.mcp.json

View file

@ -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
```
## 配置参考
### 副本数配置

View file

@ -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 */}}

View file

@ -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 }}

View file

@ -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 → 随机生成

View file

@ -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 }}
{{- end }}

View file

@ -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 配置

View file

@ -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<String> nodes = redisProperties.getSentinel().getNodes();
nodes.stream()

View file

@ -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);