Compare commits
28
Commits
@@ -0,0 +1,50 @@
|
||||
---
|
||||
name: Platform Execution
|
||||
about: Track platform delivery work with explicit planning, evidence, and rollback fields
|
||||
title: "[Platform] "
|
||||
labels: platform
|
||||
assignees: ""
|
||||
---
|
||||
|
||||
## Summary
|
||||
<!-- Describe the scope, expected outcome, and impacted behavior. -->
|
||||
|
||||
## Type
|
||||
- [ ] Epic
|
||||
- [ ] Issue
|
||||
- [ ] Follow-up
|
||||
|
||||
## Priority
|
||||
- [ ] P0
|
||||
- [ ] P1
|
||||
- [ ] P2
|
||||
- [ ] P3
|
||||
|
||||
## Week
|
||||
<!-- Use YYYY-Www, for example 2026-W10. -->
|
||||
|
||||
## Repos touched
|
||||
- `ljwx-deploy`
|
||||
|
||||
## Files / Paths expected to change
|
||||
- `platform/...`
|
||||
|
||||
## Dependencies
|
||||
- None
|
||||
|
||||
## Acceptance Criteria
|
||||
- [ ]
|
||||
|
||||
## Evidence Required
|
||||
- [ ] PR link
|
||||
- [ ] Manifest or config diff
|
||||
- [ ] Runtime verification output
|
||||
- [ ] Docs updated if contract or behavior changed
|
||||
|
||||
## Rollback Plan
|
||||
- Revert target:
|
||||
- Re-apply or resync step:
|
||||
- Verification after rollback:
|
||||
|
||||
## Links
|
||||
<!-- Optional milestone, epic, dashboard, ArgoCD, or runbook links. -->
|
||||
@@ -57,6 +57,28 @@ jobs:
|
||||
- name: Setup uv
|
||||
uses: astral-sh/setup-uv@v4
|
||||
|
||||
- name: Smart CS contract gate
|
||||
run: bash scripts/ci/check-smart-cs-contract.sh
|
||||
|
||||
- name: Smart CS smoke targets gate
|
||||
run: bash scripts/ci/check-smart-cs-smoke-targets.sh
|
||||
|
||||
- name: Plaintext secret scan
|
||||
run: bash scripts/ci/check-no-plain-secrets.sh .changed-files.txt
|
||||
|
||||
- name: Detect pgvector related changes
|
||||
id: pgvector
|
||||
run: |
|
||||
if grep -Eq '^(apps/ljwx-dify/|apps/n8n/|config/dify/|cluster/.*postgres)' .changed-files.txt; then
|
||||
echo "required=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "required=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Check pgvector baseline
|
||||
if: steps.pgvector.outputs.required == 'true'
|
||||
run: bash scripts/ops/check-pgvector.sh
|
||||
|
||||
- name: Validate release queue structure
|
||||
run: uvx --with pyyaml python scripts/promoter/validate_queue.py
|
||||
|
||||
|
||||
@@ -2,6 +2,19 @@
|
||||
|
||||
GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
|
||||
|
||||
## Start Here
|
||||
|
||||
- 新进程/新项目/老项目统一接入入口:`docs/START-HERE-GITOPS-ONBOARDING.md`
|
||||
- 自动扫描当前仓库是否已接入:`bash scripts/ops/scan-gitops-context.sh --repo "$PWD"`
|
||||
|
||||
## Platform Blueprint
|
||||
|
||||
- 平台装配蓝图入口:`docs/platform/README.md`
|
||||
- 平台配置与契约:`platform/`
|
||||
- 集成版本清单:`release/platform-version.yaml`
|
||||
- 平台 runbooks:`runbooks/`
|
||||
- 新集群初始化脚本骨架:`scripts/bootstrap/cluster-init.sh`
|
||||
|
||||
## Bid-MVP Factory Control Plane
|
||||
|
||||
- 运行验证:`bash scripts/verify.sh`
|
||||
@@ -9,18 +22,45 @@ GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
|
||||
- Promoter/Argo/Smoke 运维:`docs/ops-runbook.md`
|
||||
- 回滚演练:`docs/rollback-drill.md`
|
||||
- Grafana 可观测看板 + TLS(GitOps):`apps/ljwx-platform-observability/`
|
||||
- Public ingress + Let's Encrypt(Traefik HTTP01,GitOps):`argocd-apps/03-cert-manager-dev.yaml`、`argocd-apps/04-cert-manager-config-dev.yaml`、`apps/cert-manager-config/`
|
||||
- 双 k3s 同代码部署规范:`docs/dual-k3s-deployment.md`
|
||||
- CronJob secret 示例:`cluster/deploy-promoter-secret.example.yaml`
|
||||
- 模板化批量接入:`docs/onboarding-automation.md`
|
||||
- 服务仓 enqueue 工作流片段:`docs/service-repo-workflow-snippet.md`
|
||||
- Argo dev 自动同步说明:`docs/argocd-dev-autosync.md`
|
||||
|
||||
## Harbor 同步基线(2026-03-05)
|
||||
|
||||
业务镜像统一命名空间:
|
||||
|
||||
- `harbor.eu.lingjingwanxiang.cn/ljwx/<service>`
|
||||
|
||||
当前复制策略(本地 Harbor API: `http://127.0.0.1:8080`):
|
||||
|
||||
1. `sync-ljwx-to-cn-event`(event_based)
|
||||
- 源:本地 Harbor `ljwx/**`
|
||||
- 目标:`https://harbor.omniverseai.net` 命名空间 `ljwx`
|
||||
2. `sync-ljwx-to-cn-nightly`(scheduled)
|
||||
- cron: `0 0 19 * * *`
|
||||
- 源:本地 Harbor `ljwx/**`
|
||||
- 目标:`https://harbor.omniverseai.net` 命名空间 `ljwx`
|
||||
|
||||
快速核验:
|
||||
|
||||
```bash
|
||||
curl -u admin:Harbor12345 \
|
||||
'http://127.0.0.1:8080/api/v2.0/replication/policies?page=1&page_size=100' \
|
||||
| jq 'map({id,name,enabled,dest_namespace,trigger,speed})'
|
||||
```
|
||||
|
||||
## 目录结构
|
||||
|
||||
- `infra/` - 基础设施组件(Gitea Runner 等)
|
||||
- `argocd-apps/` - ArgoCD Application 定义
|
||||
- `apps/` - 应用配置
|
||||
- `cluster/` - 集群级别配置
|
||||
- `platform/` - 平台装配、契约、路由、知识治理配置
|
||||
- `runbooks/` - 平台级操作手册
|
||||
|
||||
## 组件
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: dnspod-letsencrypt-staging
|
||||
annotations:
|
||||
gitops.ljwx.io/issuer-implementation: http01-traefik
|
||||
spec:
|
||||
acme:
|
||||
email: platform@lingjingwanxiang.cn
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
privateKeySecretRef:
|
||||
name: dnspod-letsencrypt-staging-account-key
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
ingressClassName: traefik
|
||||
@@ -0,0 +1,16 @@
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: dnspod-letsencrypt
|
||||
annotations:
|
||||
gitops.ljwx.io/issuer-implementation: http01-traefik
|
||||
spec:
|
||||
acme:
|
||||
email: platform@lingjingwanxiang.cn
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
privateKeySecretRef:
|
||||
name: dnspod-letsencrypt-account-key
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
ingressClassName: traefik
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- clusterissuer-dnspod-letsencrypt.yaml
|
||||
- clusterissuer-dnspod-letsencrypt-staging.yaml
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- ../../base
|
||||
@@ -37,6 +37,10 @@ kubectl get crd servicemonitors.monitoring.coreos.com
|
||||
|
||||
#### 创建 Secret
|
||||
```bash
|
||||
# 推荐:在 ljwx-deploy 仓库根目录执行,自动从 infra-credentials 拉取密码
|
||||
./scripts/create-app-secret.sh ljwx-bookstore /root/codes/.env
|
||||
|
||||
# 手动方式(如需逐项指定):
|
||||
kubectl create secret generic ljwx-bookstore-secret \
|
||||
--namespace=ljwx-bookstore \
|
||||
--from-literal=DB_HOST='mysql-infra.infra.svc.cluster.local' \
|
||||
@@ -55,6 +59,14 @@ kubectl create secret generic ljwx-bookstore-secret \
|
||||
--from-literal=STORAGE_ALIYUN_ACCESS_KEY_SECRET='YOUR_SECRET'
|
||||
```
|
||||
|
||||
如果 `/root/codes/.env` 中存在 Claude 代理配置,也可以单独同步可选的
|
||||
`ljwx-bookstore-llm-secret`:
|
||||
|
||||
```bash
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
kubectl rollout restart deployment/ljwx-bookstore -n ljwx-bookstore
|
||||
```
|
||||
|
||||
#### 部署应用
|
||||
```bash
|
||||
# 方式 1: Kustomize
|
||||
@@ -81,7 +93,7 @@ kubectl exec -n ljwx-bookstore deployment/ljwx-bookstore -- \
|
||||
kubectl get servicemonitor -n ljwx-bookstore
|
||||
|
||||
# 访问应用
|
||||
curl http://book.omniverseai.net/fiction/index
|
||||
curl https://bookstore.lingjingwanxiang.cn/fiction/index
|
||||
```
|
||||
|
||||
### 📊 监控验证
|
||||
|
||||
@@ -77,6 +77,10 @@ kubectl get svc -n tracing otel-collector-opentelemetry-collector
|
||||
创建包含敏感信息的 Secret(**不要提交到 Git**):
|
||||
|
||||
```bash
|
||||
# 推荐:在 ljwx-deploy 仓库根目录执行
|
||||
./scripts/create-app-secret.sh ljwx-bookstore /root/codes/.env
|
||||
|
||||
# 手动方式:
|
||||
kubectl create secret generic ljwx-bookstore-secret \
|
||||
--namespace=ljwx-bookstore \
|
||||
--from-literal=DB_HOST='mysql-infra.infra.svc.cluster.local' \
|
||||
@@ -96,6 +100,14 @@ kubectl create secret generic ljwx-bookstore-secret \
|
||||
--dry-run=client -o yaml | kubectl apply -f -
|
||||
```
|
||||
|
||||
如果 `/root/codes/.env` 中存在 Claude 代理配置,可额外同步可选的
|
||||
`ljwx-bookstore-llm-secret`,Deployment 会自动读取它覆盖默认 LLM 配置:
|
||||
|
||||
```bash
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
kubectl rollout restart deployment/ljwx-bookstore -n ljwx-bookstore
|
||||
```
|
||||
|
||||
**生产环境建议**:使用 External Secrets Operator 或 Sealed Secrets 管理敏感信息。
|
||||
|
||||
### 步骤 2: 部署应用 (GitOps)
|
||||
@@ -182,7 +194,7 @@ kubectl get servicemonitor -n ljwx-bookstore
|
||||
kubectl get ingress -n ljwx-bookstore
|
||||
|
||||
# 访问应用
|
||||
curl http://book.omniverseai.net/fiction/index
|
||||
curl https://bookstore.lingjingwanxiang.cn/fiction/index
|
||||
curl http://bookstore.192.168.1.83.nip.io/fiction/index
|
||||
```
|
||||
|
||||
|
||||
@@ -2,17 +2,15 @@
|
||||
|
||||
## 域名信息
|
||||
|
||||
- **生产域名**: `book.omniverseai.net`
|
||||
- **DNS提供商**: Cloudflare
|
||||
- **SSL证书**: Let's Encrypt (自动通过cert-manager获取)
|
||||
- **生产域名**: `bookstore.lingjingwanxiang.cn`
|
||||
- **入口控制器**: Traefik
|
||||
- **SSL证书**: Let's Encrypt (自动通过 cert-manager 获取)
|
||||
|
||||
## DNS记录配置
|
||||
|
||||
### 步骤1: 登录 Cloudflare
|
||||
### 步骤1: 配置域名解析
|
||||
|
||||
1. 访问 https://dash.cloudflare.com/
|
||||
2. 选择域名 `omniverseai.net`
|
||||
3. 进入 DNS 管理页面
|
||||
将 `bookstore.lingjingwanxiang.cn` 解析到 k3s 对外入口 IP。
|
||||
|
||||
### 步骤2: 添加 A 记录
|
||||
|
||||
@@ -20,7 +18,7 @@
|
||||
|
||||
| 类型 | 名称 | 内容 | 代理状态 | TTL |
|
||||
|------|------|------|----------|-----|
|
||||
| A | book | `<INGRESS_IP>` | 仅DNS(灰色云朵) | Auto |
|
||||
| A | bookstore | `<INGRESS_IP>` | 仅DNS | Auto |
|
||||
|
||||
**获取 Ingress IP**:
|
||||
```bash
|
||||
@@ -34,20 +32,16 @@ kubectl get ingress ljwx-bookstore -n ljwx-bookstore \
|
||||
192.168.194.177 # 示例,请使用实际IP
|
||||
```
|
||||
|
||||
### 步骤3: Cloudflare 配置建议
|
||||
### 步骤3: HTTPS 配置建议
|
||||
|
||||
#### SSL/TLS 设置
|
||||
1. 进入 `SSL/TLS` → `概述`
|
||||
2. 加密模式选择: **完全(严格)** 或 **完全**
|
||||
3. 推荐: **完全(严格)** - 确保端到端加密
|
||||
|
||||
#### 代理状态
|
||||
- **开发/测试**: 仅DNS(灰色云朵)- 直连Kubernetes
|
||||
- **生产环境**: 代理(橙色云朵)- 使用Cloudflare CDN和DDoS防护
|
||||
|
||||
#### 其他推荐配置
|
||||
- 启用 **自动HTTPS重写**
|
||||
- 启用 **始终使用HTTPS**
|
||||
- DNS 记录生效前不要切换业务入口
|
||||
- 保证 `bookstore.lingjingwanxiang.cn` 已解析到 Traefik 对外 IP
|
||||
- 配置 **最小TLS版本**: TLS 1.2
|
||||
|
||||
## 验证步骤
|
||||
@@ -56,10 +50,10 @@ kubectl get ingress ljwx-bookstore -n ljwx-bookstore \
|
||||
|
||||
```bash
|
||||
# 检查DNS记录
|
||||
dig book.omniverseai.net +short
|
||||
dig bookstore.lingjingwanxiang.cn +short
|
||||
|
||||
# 或使用nslookup
|
||||
nslookup book.omniverseai.net
|
||||
nslookup bookstore.lingjingwanxiang.cn
|
||||
```
|
||||
|
||||
**期望输出**: 应该返回Ingress Controller的IP地址
|
||||
@@ -73,10 +67,10 @@ nslookup book.omniverseai.net
|
||||
kubectl get certificate -n ljwx-bookstore
|
||||
|
||||
# 查看证书详情
|
||||
kubectl describe certificate ljwx-bookstore-tls -n ljwx-bookstore
|
||||
kubectl describe certificate bookstore-lingjingwanxiang-cn-tls -n ljwx-bookstore
|
||||
|
||||
# 检查证书Secret
|
||||
kubectl get secret ljwx-bookstore-tls -n ljwx-bookstore
|
||||
kubectl get secret bookstore-lingjingwanxiang-cn-tls -n ljwx-bookstore
|
||||
```
|
||||
|
||||
**期望状态**: `READY=True`
|
||||
@@ -85,26 +79,35 @@ kubectl get secret ljwx-bookstore-tls -n ljwx-bookstore
|
||||
|
||||
```bash
|
||||
# HTTP 自动重定向到 HTTPS
|
||||
curl -I http://book.omniverseai.net
|
||||
curl -I http://bookstore.lingjingwanxiang.cn
|
||||
|
||||
# HTTPS 访问
|
||||
curl -I https://book.omniverseai.net
|
||||
curl -I https://bookstore.lingjingwanxiang.cn
|
||||
|
||||
# 如果当前终端存在 DNS / 公网回环限制,改用 live Ingress 探测
|
||||
python3 scripts/ops/public_ingress_access.py probe \
|
||||
--host bookstore.lingjingwanxiang.cn \
|
||||
--path /fiction/index
|
||||
|
||||
# 需要浏览器本机验收时,先生成推荐 hosts 映射
|
||||
python3 scripts/ops/public_ingress_access.py hosts \
|
||||
--host bookstore.lingjingwanxiang.cn
|
||||
|
||||
# 浏览器访问
|
||||
open https://book.omniverseai.net/fiction/index
|
||||
open https://bookstore.lingjingwanxiang.cn/fiction/index
|
||||
```
|
||||
|
||||
### 4. SSL证书检查
|
||||
|
||||
```bash
|
||||
# 使用 openssl 检查证书
|
||||
echo | openssl s_client -servername book.omniverseai.net \
|
||||
-connect book.omniverseai.net:443 2>/dev/null | \
|
||||
echo | openssl s_client -servername bookstore.lingjingwanxiang.cn \
|
||||
-connect bookstore.lingjingwanxiang.cn:443 2>/dev/null | \
|
||||
openssl x509 -noout -dates -subject
|
||||
|
||||
# 在线检查
|
||||
# 访问 https://www.ssllabs.com/ssltest/
|
||||
# 输入: book.omniverseai.net
|
||||
# 输入: bookstore.lingjingwanxiang.cn
|
||||
```
|
||||
|
||||
## 故障排查
|
||||
@@ -112,7 +115,7 @@ echo | openssl s_client -servername book.omniverseai.net \
|
||||
### DNS未解析
|
||||
|
||||
```bash
|
||||
# 检查Cloudflare DNS设置
|
||||
# 检查 DNS 设置
|
||||
# 1. 确认A记录存在
|
||||
# 2. 确认IP地址正确
|
||||
# 3. 清除本地DNS缓存: sudo dscacheutil -flushcache (macOS)
|
||||
@@ -125,16 +128,16 @@ echo | openssl s_client -servername book.omniverseai.net \
|
||||
kubectl logs -n cert-manager -l app=cert-manager --tail=50
|
||||
|
||||
# 检查 Certificate Events
|
||||
kubectl describe certificate ljwx-bookstore-tls -n ljwx-bookstore
|
||||
kubectl describe certificate bookstore-lingjingwanxiang-cn-tls -n ljwx-bookstore
|
||||
|
||||
# 检查 CertificateRequest
|
||||
kubectl get certificaterequest -n ljwx-bookstore
|
||||
kubectl describe certificaterequest -n ljwx-bookstore
|
||||
|
||||
# 常见问题:
|
||||
# 1. Cloudflare API Token权限不足
|
||||
# 2. DNS记录配置错误
|
||||
# 3. ClusterIssuer配置问题
|
||||
# 1. DNS记录配置错误
|
||||
# 2. ClusterIssuer配置问题
|
||||
# 3. Traefik 未接住 HTTP01 challenge
|
||||
```
|
||||
|
||||
### HTTPS访问失败
|
||||
@@ -144,8 +147,8 @@ kubectl describe certificaterequest -n ljwx-bookstore
|
||||
kubectl get ingress ljwx-bookstore -n ljwx-bookstore
|
||||
kubectl describe ingress ljwx-bookstore -n ljwx-bookstore
|
||||
|
||||
# 检查 Ingress Controller 日志
|
||||
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=50
|
||||
# 检查 Traefik 日志
|
||||
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik --tail=50
|
||||
```
|
||||
|
||||
## 生产环境清单
|
||||
@@ -156,18 +159,16 @@ kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=5
|
||||
- [ ] SSL证书自动获取成功(`kubectl get certificate -n ljwx-bookstore`)
|
||||
- [ ] HTTPS强制重定向已启用
|
||||
- [ ] 健康检查正常工作
|
||||
- [ ] 应用可通过 https://book.omniverseai.net 访问
|
||||
- [ ] 应用可通过 https://bookstore.lingjingwanxiang.cn 访问
|
||||
- [ ] SSL证书A级评分(ssllabs.com测试)
|
||||
- [ ] Cloudflare防火墙规则已配置(可选)
|
||||
- [ ] 监控和告警已设置(可选)
|
||||
|
||||
## 相关资源
|
||||
|
||||
- Ingress配置: `ingress.yaml`
|
||||
- cert-manager文档: https://cert-manager.io/docs/
|
||||
- Cloudflare API: https://developers.cloudflare.com/api/
|
||||
- Let's Encrypt文档: https://letsencrypt.org/docs/
|
||||
|
||||
## 更新历史
|
||||
|
||||
- 2026-01-01: 初始配置,添加 book.omniverseai.net 域名
|
||||
- 2026-03-06: 切换到 bookstore.lingjingwanxiang.cn,并统一到 Traefik + cert-manager 基线
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
./scripts/create-app-secret.sh ljwx-bookstore
|
||||
```
|
||||
|
||||
如果 `/root/codes/.env` 中存在 Claude 代理配置,上面的脚本会额外同步
|
||||
`ljwx-bookstore-llm-secret`,供 Deployment 以可选方式注入。
|
||||
|
||||
### 手动创建
|
||||
|
||||
如果自动化脚本不可用,使用以下命令手动创建:
|
||||
@@ -43,10 +46,16 @@ kubectl create secret generic ljwx-bookstore-secret \
|
||||
--from-literal=STORAGE_ALIYUN_ACCESS_KEY_SECRET="placeholder" \
|
||||
--from-literal=STORAGE_TENCENT_SECRET_ID="placeholder" \
|
||||
--from-literal=STORAGE_TENCENT_SECRET_KEY="placeholder" \
|
||||
--from-literal=AI_CLOUD_API_KEY="" \
|
||||
--namespace=ljwx-bookstore
|
||||
```
|
||||
|
||||
如果需要让 `ljwx-bookstore` 使用 `/root/codes/.env` 中的 Claude 代理配置:
|
||||
|
||||
```bash
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
kubectl rollout restart deployment/ljwx-bookstore -n ljwx-bookstore
|
||||
```
|
||||
|
||||
## Secret 架构
|
||||
|
||||
```
|
||||
@@ -90,7 +99,12 @@ kubectl create secret generic ljwx-bookstore-secret \
|
||||
./scripts/create-app-secret.sh ljwx-bookstore
|
||||
```
|
||||
|
||||
3. **部署应用**(Argo CD自动同步)
|
||||
3. **如需云端 LLM,创建可选 LLM Secret**
|
||||
```bash
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
```
|
||||
|
||||
4. **部署应用**(Argo CD自动同步)
|
||||
```bash
|
||||
argocd app sync ljwx-bookstore
|
||||
```
|
||||
@@ -109,7 +123,10 @@ kubectl patch secret infra-credentials -n infra \
|
||||
kubectl delete secret ljwx-bookstore-secret -n ljwx-bookstore
|
||||
./scripts/create-app-secret.sh ljwx-bookstore
|
||||
|
||||
# 3. 重启应用
|
||||
# 3. 如果修改了 /root/codes/.env,同步 LLM Secret
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
|
||||
# 4. 重启应用
|
||||
kubectl rollout restart deployment ljwx-bookstore -n ljwx-bookstore
|
||||
```
|
||||
|
||||
@@ -167,6 +184,19 @@ kubectl delete secret ljwx-bookstore-secret -n ljwx-bookstore
|
||||
./scripts/create-app-secret.sh ljwx-bookstore
|
||||
```
|
||||
|
||||
### LLM 配置未生效
|
||||
|
||||
```bash
|
||||
# 重新同步可选 LLM Secret
|
||||
./scripts/sync-llm-secret.sh ljwx-bookstore /root/codes/.env
|
||||
|
||||
# 确认 Deployment 已注入 ljwx-bookstore-llm-secret
|
||||
kubectl get deployment ljwx-bookstore -n ljwx-bookstore -o yaml | grep -n "ljwx-bookstore-llm-secret"
|
||||
|
||||
# 重启应用
|
||||
kubectl rollout restart deployment/ljwx-bookstore -n ljwx-bookstore
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- 详细架构设计:`/docs/GITOPS_SECRET_MANAGEMENT.md`
|
||||
|
||||
@@ -87,7 +87,7 @@ data:
|
||||
# 微信支付配置(非敏感)
|
||||
HGBOOK_WX_APP_ID: "wxe92bc58a93b04a87"
|
||||
HGBOOK_WX_MCH_ID: "1623644809"
|
||||
HGBOOK_WX_NOTIFY_URL: "http://bookstore.192.168.1.83.nip.io/wx/order/pay-notify"
|
||||
HGBOOK_WX_NOTIFY_URL: "https://bookstore.lingjingwanxiang.cn/wx/order/pay-notify"
|
||||
|
||||
# 邮件配置(非敏感)
|
||||
HGBOOK_NOTIFY_MAIL_ENABLE: "true"
|
||||
|
||||
@@ -70,6 +70,9 @@ spec:
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-bookstore-config
|
||||
- secretRef:
|
||||
name: ljwx-bookstore-llm-secret
|
||||
optional: true
|
||||
|
||||
# 环境变量 - 从 Secret(敏感信息)
|
||||
env:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# ljwx-bookstore Ingress Configuration
|
||||
# 域名: book.omniverseai.net (HTTP only)
|
||||
# 域名: bookstore.lingjingwanxiang.cn (HTTPS)
|
||||
# =============================================================================
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
@@ -12,28 +12,21 @@ metadata:
|
||||
app: ljwx-bookstore
|
||||
component: backend
|
||||
annotations:
|
||||
# Nginx Ingress 配置
|
||||
nginx.ingress.kubernetes.io/rewrite-target: /
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
|
||||
# 禁用SSL重定向 - HTTP only
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "false"
|
||||
|
||||
# 健康检查
|
||||
nginx.ingress.kubernetes.io/health-check-path: "/fiction/actuator/health"
|
||||
|
||||
# CORS 配置(如果需要)
|
||||
nginx.ingress.kubernetes.io/enable-cors: "true"
|
||||
nginx.ingress.kubernetes.io/cors-allow-origin: "http://book.omniverseai.net"
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.middlewares: ljwx-bookstore-root-redirect@kubernetescrd
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: nginx
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- bookstore.lingjingwanxiang.cn
|
||||
secretName: bookstore-lingjingwanxiang-cn-tls
|
||||
|
||||
# 路由规则
|
||||
rules:
|
||||
# 生产域名
|
||||
- host: book.omniverseai.net
|
||||
# 正式域名
|
||||
- host: bookstore.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
@@ -44,7 +37,7 @@ spec:
|
||||
port:
|
||||
number: 80
|
||||
|
||||
# 本地测试域名
|
||||
# 历史本地调试域名
|
||||
- host: bookstore.192.168.1.83.nip.io
|
||||
http:
|
||||
paths:
|
||||
|
||||
@@ -14,6 +14,7 @@ resources:
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- root-redirect-middleware.yaml
|
||||
- ingress.yaml
|
||||
- servicemonitor.yaml
|
||||
- db-init-job.yaml
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
---
|
||||
# =============================================================================
|
||||
# Root path redirect for ljwx-bookstore public entrypoint
|
||||
# =============================================================================
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: root-redirect
|
||||
namespace: ljwx-bookstore
|
||||
labels:
|
||||
app: ljwx-bookstore
|
||||
component: backend
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: ^https?://([^/]+)/?$
|
||||
replacement: https://${1}/fiction/index
|
||||
permanent: true
|
||||
@@ -0,0 +1,28 @@
|
||||
# ljwx-bookstore capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-bookstore`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `harbor-only`
|
||||
- runtime secret profile: `none`
|
||||
- image pull secrets: `regcred`
|
||||
- runtime contract secrets: `none`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-bookstore --registry-profile harbor-only
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- `cluster_bootstrap` is disabled for this entry; keep the existing cluster namespace/application manifests as the current source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,20 @@
|
||||
service: ljwx-bookstore
|
||||
environment: dev
|
||||
namespace: ljwx-bookstore
|
||||
cluster_bootstrap: false
|
||||
ingress_profile: none
|
||||
public_host: ''
|
||||
generate_ingress: true
|
||||
ingress_class_name: ''
|
||||
cluster_issuer: ''
|
||||
tls_secret_name: ''
|
||||
namespace_profile: dev-default
|
||||
registry_profile: harbor-only
|
||||
runtime_secret_profile: none
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- regcred
|
||||
contract_secret_names: []
|
||||
@@ -0,0 +1,30 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ljwx-chat-config
|
||||
data:
|
||||
NODE_ENV: "production"
|
||||
PORT: "3210"
|
||||
HOSTNAME: "0.0.0.0"
|
||||
|
||||
DATABASE_DRIVER: "node"
|
||||
|
||||
NEXT_PUBLIC_ENABLE_NEXT_AUTH: "0"
|
||||
NEXT_PUBLIC_ENABLE_LOCAL_AUTH: "1"
|
||||
NEXT_AUTH_SSO_SESSION_STRATEGY: "jwt"
|
||||
NEXT_AUTH_SSO_PROVIDERS: ""
|
||||
AUTH_TRUST_HOST: "true"
|
||||
|
||||
EMAIL_ENABLED: "false"
|
||||
|
||||
NEXT_PUBLIC_BASE_URL: "https://chat.lingjingwanxiang.cn"
|
||||
NEXTAUTH_URL: "https://chat.lingjingwanxiang.cn"
|
||||
|
||||
S3_BUCKET: "ljwx-chat"
|
||||
S3_ENABLE_PATH_STYLE: "true"
|
||||
|
||||
MIDDLEWARE_REWRITE_THROUGH_LOCAL: "1"
|
||||
|
||||
ENABLED_OLLAMA: "0"
|
||||
OPENCLAW_GATEWAY_URL: "https://openclaw.lingjingwanxiang.cn/v1"
|
||||
OPENCLAW_GATEWAY_ORIGIN: "https://openclaw.lingjingwanxiang.cn"
|
||||
@@ -0,0 +1,58 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-chat
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-chat
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-chat
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: ljwx-chat
|
||||
image: ghcr.io/brunogao/ljwx-chat:latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- containerPort: 3210
|
||||
name: http
|
||||
protocol: TCP
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-chat-config
|
||||
- secretRef:
|
||||
name: ljwx-chat-secrets
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 1001
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: false
|
||||
@@ -0,0 +1,29 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-chat
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- chat.lingjingwanxiang.cn
|
||||
secretName: chat-lingjingwanxiang-cn-tls
|
||||
rules:
|
||||
- host: chat.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-chat
|
||||
port:
|
||||
number: 80
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- configmap.yaml
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: ljwx-chat
|
||||
app.kubernetes.io/part-of: ljwx-chat-suite
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-chat
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: ljwx-chat
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
sessionAffinity: ClientIP
|
||||
sessionAffinityConfig:
|
||||
clientIP:
|
||||
timeoutSeconds: 10800
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: ljwx-chat-dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-chat
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-chat
|
||||
newTag: openclaw-ws-20260307-0117
|
||||
@@ -0,0 +1,34 @@
|
||||
# ljwx-chat capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-chat-dev`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `app-runtime`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `runtime-app`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-chat-dev --registry-profile ghcr-and-harbor
|
||||
kubectl create secret generic runtime-app -n ljwx-chat-dev --from-literal=EXAMPLE_KEY='replace-me'
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- Namespace baseline is generated into `cluster/namespace-<namespace>.yaml` and should remain the source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
- The current `ljwx-chat` deployment still reads application secrets from the legacy `ljwx-chat-secrets` secret.
|
||||
- When `lingjingwanxiang:32b` is routed through OpenClaw, `ljwx-chat-secrets` must include `OPENCLAW_GATEWAY_TOKEN`.
|
||||
- `OPENAI_API_KEY` is still populated as a compatibility fallback for older builds.
|
||||
- `OPENCLAW_GATEWAY_URL` is provided by the base ConfigMap and defaults to `https://openclaw.lingjingwanxiang.cn/v1`.
|
||||
- `OPENCLAW_GATEWAY_ORIGIN` is provided by the base ConfigMap and defaults to `https://openclaw.lingjingwanxiang.cn`.
|
||||
@@ -0,0 +1,22 @@
|
||||
service: ljwx-chat
|
||||
environment: dev
|
||||
namespace: ljwx-chat-dev
|
||||
cluster_bootstrap: true
|
||||
ingress_profile: none
|
||||
public_host: ''
|
||||
generate_ingress: true
|
||||
ingress_class_name: ''
|
||||
cluster_issuer: ''
|
||||
tls_secret_name: ''
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: app-runtime
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names:
|
||||
- runtime-app
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: runtime-app
|
||||
namespace: ljwx-chat-dev
|
||||
annotations:
|
||||
gitops.ljwx.io/generated-example: 'true'
|
||||
type: Opaque
|
||||
stringData:
|
||||
EXAMPLE_KEY: <replace-with-app-secret>
|
||||
@@ -0,0 +1,68 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: api
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-dify-api
|
||||
ports:
|
||||
- name: http
|
||||
port: 5001
|
||||
targetPort: 5001
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-dify-api
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-dify-api
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-dify-api
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: api
|
||||
image: ghcr.io/brunogao/ljwx-dify-api:latest
|
||||
ports:
|
||||
- containerPort: 5001
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
- secretRef:
|
||||
name: ljwx-dify-secrets
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /app/api/storage
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 5
|
||||
volumes:
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: ljwx-dify-storage
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ljwx-dify-config
|
||||
data:
|
||||
MODE: "api"
|
||||
LOG_LEVEL: "INFO"
|
||||
|
||||
DB_HOST: "postgres-lb.infra.svc.cluster.local"
|
||||
DB_PORT: "5432"
|
||||
DB_DATABASE: "ljwx_dify"
|
||||
|
||||
REDIS_HOST: "redis-lb.infra.svc.cluster.local"
|
||||
REDIS_PORT: "6379"
|
||||
REDIS_DB: "0"
|
||||
|
||||
VECTOR_STORE: "qdrant"
|
||||
QDRANT_URL: "http://qdrant:6333"
|
||||
|
||||
STORAGE_TYPE: "s3"
|
||||
S3_ENDPOINT: "http://minio.infra.svc.cluster.local:9000"
|
||||
S3_USE_AWS_MANAGED_IAM: "false"
|
||||
S3_USE_PATH_STYLE: "true"
|
||||
S3_BUCKET_NAME: "ljwx-dify"
|
||||
S3_REGION: "us-east-1"
|
||||
|
||||
NEXT_PUBLIC_COMPANY_NAME: "灵境万象"
|
||||
NEXT_PUBLIC_SITE_NAME: "灵境万象知识库"
|
||||
NEXT_PUBLIC_DEFAULT_LOCALE: "zh-Hans"
|
||||
|
||||
CONSOLE_API_URL: ""
|
||||
APP_API_URL: ""
|
||||
|
||||
PLUGIN_DAEMON_URL: "http://plugin-daemon:5002"
|
||||
PLUGIN_DAEMON_PORT: "5002"
|
||||
|
||||
DIFY_API_BASE_URL: "http://api:5001"
|
||||
DIFY_CONSOLE_API_BASE: "http://api:5001/console/api"
|
||||
DIFY_INIT_WAIT_SECONDS: "300"
|
||||
DIFY_INIT_REQUEST_TIMEOUT_SECONDS: "15"
|
||||
DIFY_DEFAULT_LLM_PROVIDER: "anthropic"
|
||||
DIFY_DEFAULT_LLM_MODEL: "claude-sonnet-4-6"
|
||||
@@ -0,0 +1,55 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-dify
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/websocket-services: "api"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- dify.lingjingwanxiang.cn
|
||||
secretName: dify-lingjingwanxiang-cn-tls
|
||||
rules:
|
||||
- host: dify.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 5001
|
||||
- path: /console/api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 5001
|
||||
- path: /v1
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 5001
|
||||
- path: /files
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: api
|
||||
port:
|
||||
number: 5001
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: web
|
||||
port:
|
||||
number: 3000
|
||||
@@ -0,0 +1,454 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: ljwx-dify-configure-default-llm
|
||||
labels:
|
||||
app.kubernetes.io/name: ljwx-dify
|
||||
app.kubernetes.io/component: default-llm-config
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PostSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
backoffLimit: 2
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: ljwx-dify
|
||||
app.kubernetes.io/component: default-llm-config
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: configure-default-llm
|
||||
image: ghcr.io/brunogao/ljwx-dify-api:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -lc
|
||||
- |
|
||||
set -eu
|
||||
/app/api/.venv/bin/python - <<'PY'
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from http.cookiejar import CookieJar
|
||||
from typing import Any
|
||||
|
||||
|
||||
API_BASE = os.getenv("DIFY_API_BASE_URL", "http://api:5001").rstrip("/")
|
||||
CONSOLE_API_BASE = os.getenv("DIFY_CONSOLE_API_BASE", f"{API_BASE}/console/api").rstrip("/")
|
||||
WAIT_SECONDS = int(os.getenv("DIFY_INIT_WAIT_SECONDS", "300"))
|
||||
REQUEST_TIMEOUT_SECONDS = int(os.getenv("DIFY_INIT_REQUEST_TIMEOUT_SECONDS", "15"))
|
||||
|
||||
ADMIN_EMAIL = os.getenv("DIFY_ADMIN_EMAIL", "").strip()
|
||||
ADMIN_NAME = os.getenv("DIFY_ADMIN_NAME", "Admin").strip() or "Admin"
|
||||
ADMIN_PASSWORD = os.getenv("DIFY_ADMIN_PASSWORD", "").strip()
|
||||
INIT_PASSWORD = os.getenv("DIFY_INIT_PASSWORD", "").strip()
|
||||
|
||||
PROVIDER_HINT = os.getenv("DIFY_DEFAULT_LLM_PROVIDER", "anthropic").strip() or "anthropic"
|
||||
DEFAULT_MODEL = os.getenv("DIFY_DEFAULT_LLM_MODEL", "claude-sonnet-4-6").strip()
|
||||
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY", "").strip()
|
||||
ANTHROPIC_BASE_URL = os.getenv("ANTHROPIC_BASE_URL", "https://api.anthropic.com").strip()
|
||||
|
||||
|
||||
def fail(message: str) -> None:
|
||||
print(f"[ERROR] {message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if not ADMIN_EMAIL:
|
||||
fail("缺少 DIFY_ADMIN_EMAIL,无法登录 Dify 控制台。")
|
||||
if not ADMIN_PASSWORD:
|
||||
fail("缺少 DIFY_ADMIN_PASSWORD,无法登录 Dify 控制台。")
|
||||
if not DEFAULT_MODEL:
|
||||
fail("缺少 DIFY_DEFAULT_LLM_MODEL,无法设置默认模型。")
|
||||
if not ANTHROPIC_API_KEY:
|
||||
fail("缺少 ANTHROPIC_API_KEY,无法配置 Anthropic Provider。")
|
||||
|
||||
cookie_jar = CookieJar()
|
||||
opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cookie_jar))
|
||||
csrf_token: str | None = None
|
||||
|
||||
|
||||
def _decode_response(raw: bytes) -> tuple[Any | None, str]:
|
||||
text = raw.decode("utf-8", errors="replace")
|
||||
if not text:
|
||||
return None, ""
|
||||
try:
|
||||
return json.loads(text), text
|
||||
except json.JSONDecodeError:
|
||||
return None, text
|
||||
|
||||
|
||||
def _error_text(body: Any | None, text: str) -> str:
|
||||
if isinstance(body, dict):
|
||||
code = body.get("code") or body.get("error_code")
|
||||
message = body.get("message") or body.get("description") or body.get("error")
|
||||
compact = json.dumps(body, ensure_ascii=False)
|
||||
return f"code={code}, message={message}, body={compact}"
|
||||
return text or "<empty>"
|
||||
|
||||
|
||||
def request_json(
|
||||
method: str,
|
||||
path: str,
|
||||
payload: dict[str, Any] | None = None,
|
||||
expected_status: set[int] | None = None,
|
||||
) -> tuple[int, Any | None, str]:
|
||||
url = path if path.startswith("http") else f"{CONSOLE_API_BASE}{path}"
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
}
|
||||
body_bytes = None
|
||||
if payload is not None:
|
||||
headers["Content-Type"] = "application/json"
|
||||
body_bytes = json.dumps(payload).encode("utf-8")
|
||||
|
||||
if csrf_token and method.upper() not in {"GET", "HEAD", "OPTIONS"}:
|
||||
headers["X-CSRF-Token"] = csrf_token
|
||||
|
||||
req = urllib.request.Request(url=url, data=body_bytes, headers=headers, method=method.upper())
|
||||
try:
|
||||
with opener.open(req, timeout=REQUEST_TIMEOUT_SECONDS) as resp:
|
||||
status_code = resp.getcode()
|
||||
body, text = _decode_response(resp.read())
|
||||
except urllib.error.HTTPError as exc:
|
||||
status_code = exc.code
|
||||
body, text = _decode_response(exc.read())
|
||||
if expected_status and status_code in expected_status:
|
||||
return status_code, body, text
|
||||
fail(
|
||||
f"请求失败 {method.upper()} {url},status={status_code},{_error_text(body, text)}"
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001
|
||||
fail(f"请求异常 {method.upper()} {url},error={exc}")
|
||||
|
||||
if expected_status and status_code not in expected_status:
|
||||
fail(
|
||||
f"请求状态异常 {method.upper()} {url},status={status_code},{_error_text(body, text)}"
|
||||
)
|
||||
return status_code, body, text
|
||||
|
||||
|
||||
def wait_for_api_ready() -> None:
|
||||
health_url = f"{API_BASE}/health"
|
||||
deadline = time.time() + WAIT_SECONDS
|
||||
last_error = ""
|
||||
while time.time() < deadline:
|
||||
try:
|
||||
with urllib.request.urlopen(health_url, timeout=REQUEST_TIMEOUT_SECONDS) as resp:
|
||||
if resp.getcode() == 200:
|
||||
print("[INFO] Dify API 健康检查通过。")
|
||||
return
|
||||
last_error = f"status={resp.getcode()}"
|
||||
except Exception as exc: # noqa: BLE001
|
||||
last_error = str(exc)
|
||||
print("[INFO] 等待 Dify API 就绪...")
|
||||
time.sleep(5)
|
||||
fail(f"Dify API 未在 {WAIT_SECONDS}s 内就绪,最后错误: {last_error}")
|
||||
|
||||
|
||||
def login_console() -> None:
|
||||
global csrf_token
|
||||
nonlocal_csrf_token: str | None = None
|
||||
encoded_password = base64.b64encode(ADMIN_PASSWORD.encode("utf-8")).decode("utf-8")
|
||||
request_json(
|
||||
"POST",
|
||||
"/login",
|
||||
payload={
|
||||
"email": ADMIN_EMAIL,
|
||||
"password": encoded_password,
|
||||
"remember_me": False,
|
||||
},
|
||||
expected_status={200},
|
||||
)
|
||||
for cookie in cookie_jar:
|
||||
if cookie.name.endswith("csrf_token"):
|
||||
nonlocal_csrf_token = cookie.value
|
||||
break
|
||||
if not nonlocal_csrf_token:
|
||||
fail("登录成功但未获取 CSRF Token,无法继续调用写接口。")
|
||||
csrf_token = nonlocal_csrf_token
|
||||
print("[INFO] Dify 控制台登录成功。")
|
||||
|
||||
|
||||
def ensure_setup() -> None:
|
||||
status, body, text = request_json("GET", "/setup", expected_status={200})
|
||||
if status != 200 or not isinstance(body, dict):
|
||||
fail(f"读取 setup 状态失败,body={text}")
|
||||
|
||||
step = str(body.get("step", ""))
|
||||
if step == "finished":
|
||||
print("[INFO] Dify 已完成初始化。")
|
||||
return
|
||||
|
||||
print("[INFO] Dify 尚未初始化,开始执行 setup。")
|
||||
if INIT_PASSWORD:
|
||||
init_status, init_body, init_text = request_json(
|
||||
"POST",
|
||||
"/init",
|
||||
payload={"password": INIT_PASSWORD},
|
||||
expected_status={201, 401, 403},
|
||||
)
|
||||
if init_status == 201:
|
||||
print("[INFO] init 校验已完成。")
|
||||
else:
|
||||
code = init_body.get("code") if isinstance(init_body, dict) else None
|
||||
if code == "already_setup":
|
||||
print("[INFO] init 已完成,继续执行。")
|
||||
else:
|
||||
fail(
|
||||
f"init 校验失败,status={init_status},{_error_text(init_body, init_text)}"
|
||||
)
|
||||
|
||||
setup_status, setup_body, setup_text = request_json(
|
||||
"POST",
|
||||
"/setup",
|
||||
payload={
|
||||
"email": ADMIN_EMAIL,
|
||||
"name": ADMIN_NAME,
|
||||
"password": ADMIN_PASSWORD,
|
||||
"language": "zh-Hans",
|
||||
},
|
||||
expected_status={201, 401, 403},
|
||||
)
|
||||
if setup_status == 201:
|
||||
print("[INFO] Dify 初始化完成。")
|
||||
return
|
||||
|
||||
setup_code = setup_body.get("code") if isinstance(setup_body, dict) else None
|
||||
if setup_code == "already_setup":
|
||||
print("[INFO] setup 已完成,继续执行后续配置。")
|
||||
return
|
||||
|
||||
fail(
|
||||
f"Dify setup 失败,status={setup_status},{_error_text(setup_body, setup_text)}"
|
||||
)
|
||||
|
||||
|
||||
def list_llm_providers() -> list[dict[str, Any]]:
|
||||
_, body, text = request_json(
|
||||
"GET",
|
||||
"/workspaces/current/model-providers?model_type=llm",
|
||||
expected_status={200},
|
||||
)
|
||||
if not isinstance(body, dict) or not isinstance(body.get("data"), list):
|
||||
fail(f"读取 LLM provider 列表失败,body={text}")
|
||||
return body["data"]
|
||||
|
||||
|
||||
def pick_provider(
|
||||
providers: list[dict[str, Any]],
|
||||
provider_hint: str,
|
||||
) -> dict[str, Any]:
|
||||
lower_hint = provider_hint.lower()
|
||||
|
||||
for provider in providers:
|
||||
name = str(provider.get("provider", ""))
|
||||
if name.lower() == lower_hint:
|
||||
return provider
|
||||
|
||||
for provider in providers:
|
||||
name = str(provider.get("provider", ""))
|
||||
if lower_hint in name.lower():
|
||||
return provider
|
||||
|
||||
for provider in providers:
|
||||
name = str(provider.get("provider", ""))
|
||||
if name.split("/")[-1].lower() == lower_hint:
|
||||
return provider
|
||||
|
||||
provider_names = [str(item.get("provider", "")) for item in providers]
|
||||
fail(f"未找到 provider={provider_hint},当前可用: {provider_names}")
|
||||
|
||||
|
||||
def build_provider_credentials(provider_item: dict[str, Any]) -> dict[str, str]:
|
||||
schema = provider_item.get("provider_credential_schema") or {}
|
||||
forms = schema.get("credential_form_schemas") if isinstance(schema, dict) else None
|
||||
|
||||
known_value_map = {
|
||||
"anthropic_api_key": ANTHROPIC_API_KEY,
|
||||
"api_key": ANTHROPIC_API_KEY,
|
||||
"anthropic_api_url": ANTHROPIC_BASE_URL,
|
||||
"api_base": ANTHROPIC_BASE_URL,
|
||||
"base_url": ANTHROPIC_BASE_URL,
|
||||
"endpoint": ANTHROPIC_BASE_URL,
|
||||
}
|
||||
|
||||
credentials: dict[str, str] = {}
|
||||
unresolved_required: list[str] = []
|
||||
|
||||
if isinstance(forms, list):
|
||||
for form in forms:
|
||||
if not isinstance(form, dict):
|
||||
continue
|
||||
variable = str(form.get("variable", "")).strip()
|
||||
if not variable:
|
||||
continue
|
||||
lower_var = variable.lower()
|
||||
required = bool(form.get("required"))
|
||||
|
||||
value = known_value_map.get(lower_var, "")
|
||||
if not value:
|
||||
if "key" in lower_var or "token" in lower_var:
|
||||
value = ANTHROPIC_API_KEY
|
||||
elif "url" in lower_var or "base" in lower_var or "endpoint" in lower_var:
|
||||
value = ANTHROPIC_BASE_URL
|
||||
|
||||
if value:
|
||||
credentials[variable] = value
|
||||
elif required:
|
||||
unresolved_required.append(variable)
|
||||
|
||||
if not credentials:
|
||||
credentials["anthropic_api_key"] = ANTHROPIC_API_KEY
|
||||
if ANTHROPIC_BASE_URL:
|
||||
credentials["anthropic_api_url"] = ANTHROPIC_BASE_URL
|
||||
|
||||
if unresolved_required:
|
||||
fail(
|
||||
f"无法自动映射 provider 必填字段: {unresolved_required},请补充 Job 变量映射。"
|
||||
)
|
||||
|
||||
return credentials
|
||||
|
||||
|
||||
def quote_provider_path(provider_name: str) -> str:
|
||||
return urllib.parse.quote(provider_name, safe="/")
|
||||
|
||||
|
||||
def ensure_provider_credentials(provider_name: str, credentials: dict[str, str]) -> None:
|
||||
providers = list_llm_providers()
|
||||
provider_item = pick_provider(providers, provider_name)
|
||||
provider_name = str(provider_item.get("provider", provider_name))
|
||||
quoted_provider = quote_provider_path(provider_name)
|
||||
|
||||
custom_cfg = provider_item.get("custom_configuration") or {}
|
||||
current_credential_id = custom_cfg.get("current_credential_id")
|
||||
available_credentials = custom_cfg.get("available_credentials") or []
|
||||
|
||||
if not current_credential_id and available_credentials:
|
||||
first_credential = available_credentials[0]
|
||||
candidate_id = first_credential.get("credential_id")
|
||||
if candidate_id:
|
||||
request_json(
|
||||
"POST",
|
||||
f"/workspaces/current/model-providers/{quoted_provider}/credentials/switch",
|
||||
payload={"credential_id": candidate_id},
|
||||
expected_status={200},
|
||||
)
|
||||
current_credential_id = candidate_id
|
||||
print(f"[INFO] 已切换 provider 凭据到 {candidate_id}。")
|
||||
|
||||
if current_credential_id:
|
||||
request_json(
|
||||
"PUT",
|
||||
f"/workspaces/current/model-providers/{quoted_provider}/credentials",
|
||||
payload={
|
||||
"credential_id": current_credential_id,
|
||||
"credentials": credentials,
|
||||
},
|
||||
expected_status={200},
|
||||
)
|
||||
print("[INFO] 已更新现有 Anthropic provider 凭据。")
|
||||
else:
|
||||
request_json(
|
||||
"POST",
|
||||
f"/workspaces/current/model-providers/{quoted_provider}/credentials",
|
||||
payload={"credentials": credentials},
|
||||
expected_status={201},
|
||||
)
|
||||
print("[INFO] 已创建 Anthropic provider 凭据。")
|
||||
|
||||
providers_after = list_llm_providers()
|
||||
provider_item_after = pick_provider(providers_after, provider_name)
|
||||
custom_cfg_after = provider_item_after.get("custom_configuration") or {}
|
||||
current_after = custom_cfg_after.get("current_credential_id")
|
||||
available_after = custom_cfg_after.get("available_credentials") or []
|
||||
if not current_after and available_after:
|
||||
candidate_id = available_after[0].get("credential_id")
|
||||
if candidate_id:
|
||||
request_json(
|
||||
"POST",
|
||||
f"/workspaces/current/model-providers/{quoted_provider}/credentials/switch",
|
||||
payload={"credential_id": candidate_id},
|
||||
expected_status={200},
|
||||
)
|
||||
print(f"[INFO] 已激活新创建的 provider 凭据 {candidate_id}。")
|
||||
|
||||
request_json(
|
||||
"POST",
|
||||
f"/workspaces/current/model-providers/{quoted_provider}/preferred-provider-type",
|
||||
payload={"preferred_provider_type": "custom"},
|
||||
expected_status={200},
|
||||
)
|
||||
print("[INFO] 已设置 provider 使用 custom 配置。")
|
||||
|
||||
|
||||
def set_default_model(provider_name: str, model_name: str) -> None:
|
||||
request_json(
|
||||
"POST",
|
||||
"/workspaces/current/default-model",
|
||||
payload={
|
||||
"model_settings": [
|
||||
{
|
||||
"model_type": "llm",
|
||||
"provider": provider_name,
|
||||
"model": model_name,
|
||||
}
|
||||
]
|
||||
},
|
||||
expected_status={200},
|
||||
)
|
||||
_, body, text = request_json(
|
||||
"GET",
|
||||
"/workspaces/current/default-model?model_type=llm",
|
||||
expected_status={200},
|
||||
)
|
||||
if not isinstance(body, dict) or not isinstance(body.get("data"), dict):
|
||||
fail(f"默认模型校验失败,body={text}")
|
||||
current_model = body["data"].get("model")
|
||||
current_provider = body["data"].get("provider", {}).get("provider")
|
||||
if current_model != model_name or current_provider != provider_name:
|
||||
fail(
|
||||
f"默认模型未生效,期望 provider={provider_name}, model={model_name};"
|
||||
f"实际 provider={current_provider}, model={current_model}"
|
||||
)
|
||||
print(
|
||||
f"[INFO] 默认 LLM 已设置为 provider={provider_name}, model={model_name}。"
|
||||
)
|
||||
|
||||
|
||||
wait_for_api_ready()
|
||||
ensure_setup()
|
||||
login_console()
|
||||
|
||||
providers = list_llm_providers()
|
||||
provider_item = pick_provider(providers, PROVIDER_HINT)
|
||||
selected_provider_name = str(provider_item.get("provider", PROVIDER_HINT))
|
||||
provider_credentials = build_provider_credentials(provider_item)
|
||||
|
||||
ensure_provider_credentials(selected_provider_name, provider_credentials)
|
||||
set_default_model(selected_provider_name, DEFAULT_MODEL)
|
||||
print("[INFO] Dify 默认 LLM 自动化配置完成。")
|
||||
PY
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
- secretRef:
|
||||
name: ljwx-dify-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
@@ -0,0 +1,47 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: ljwx-dify-upgrade-db
|
||||
labels:
|
||||
app.kubernetes.io/name: ljwx-dify
|
||||
app.kubernetes.io/component: db-migration
|
||||
annotations:
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
spec:
|
||||
ttlSecondsAfterFinished: 300
|
||||
backoffLimit: 3
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: ljwx-dify
|
||||
app.kubernetes.io/component: db-migration
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: upgrade-db
|
||||
image: ghcr.io/brunogao/ljwx-dify-api:latest
|
||||
command:
|
||||
- /bin/sh
|
||||
- -lc
|
||||
- |
|
||||
set -e
|
||||
echo "Starting Dify database migration..."
|
||||
/app/api/.venv/bin/flask upgrade-db
|
||||
echo "Dify database migration completed."
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
- secretRef:
|
||||
name: ljwx-dify-secrets
|
||||
resources:
|
||||
requests:
|
||||
cpu: "100m"
|
||||
memory: "256Mi"
|
||||
limits:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
@@ -0,0 +1,18 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- configmap.yaml
|
||||
- job-upgrade-db.yaml
|
||||
- job-configure-default-llm.yaml
|
||||
- pvc.yaml
|
||||
- api.yaml
|
||||
- worker.yaml
|
||||
- web.yaml
|
||||
- plugin-daemon.yaml
|
||||
- qdrant.yaml
|
||||
- ingress.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/name: ljwx-dify
|
||||
app.kubernetes.io/part-of: ljwx-chat-suite
|
||||
@@ -0,0 +1,112 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: plugin-daemon
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-dify-plugin-daemon
|
||||
ports:
|
||||
- name: http
|
||||
port: 5002
|
||||
targetPort: 5002
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-dify-plugin-daemon
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-dify-plugin-daemon
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-dify-plugin-daemon
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: plugin-daemon
|
||||
image: langgenius/dify-plugin-daemon:0.5.1-local
|
||||
ports:
|
||||
- containerPort: 5002
|
||||
name: http
|
||||
env:
|
||||
- name: PLUGIN_DAEMON_PORT
|
||||
value: "5002"
|
||||
- name: REDIS_HOST
|
||||
value: "redis-lb.infra.svc.cluster.local"
|
||||
- name: REDIS_PORT
|
||||
value: "6379"
|
||||
- name: REDIS_DB
|
||||
value: "0"
|
||||
- name: REDIS_USERNAME
|
||||
value: ""
|
||||
- name: REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: REDIS_PASSWORD
|
||||
- name: DB_HOST
|
||||
value: "postgres-lb.infra.svc.cluster.local"
|
||||
- name: DB_PORT
|
||||
value: "5432"
|
||||
- name: DB_DATABASE
|
||||
value: "ljwx_dify"
|
||||
- name: DB_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: DB_USERNAME
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: DB_PASSWORD
|
||||
- name: DIFY_INNER_API_URL
|
||||
value: "http://api:5001"
|
||||
- name: SERVER_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: PLUGIN_DAEMON_KEY
|
||||
- name: DIFY_INNER_API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: DIFY_INNER_API_KEY
|
||||
- name: PLUGIN_REMOTE_INSTALLING_HOST
|
||||
value: "https://dify.lingjingwanxiang.cn"
|
||||
- name: PLUGIN_REMOTE_INSTALLING_PORT
|
||||
value: "443"
|
||||
- name: PLUGIN_WORKING_PATH
|
||||
value: "/app/plugins"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
- secretRef:
|
||||
name: ljwx-dify-secrets
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /app/plugins
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: plugins
|
||||
emptyDir: {}
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: ljwx-dify-storage
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
@@ -0,0 +1,75 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: qdrant
|
||||
spec:
|
||||
selector:
|
||||
app: qdrant
|
||||
ports:
|
||||
- name: http
|
||||
port: 6333
|
||||
targetPort: 6333
|
||||
- name: grpc
|
||||
port: 6334
|
||||
targetPort: 6334
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: qdrant
|
||||
spec:
|
||||
serviceName: qdrant
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: qdrant
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: qdrant
|
||||
spec:
|
||||
containers:
|
||||
- name: qdrant
|
||||
image: qdrant/qdrant:v1.12.6
|
||||
ports:
|
||||
- containerPort: 6333
|
||||
name: http
|
||||
- containerPort: 6334
|
||||
name: grpc
|
||||
env:
|
||||
- name: QDRANT__SERVICE__API_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-dify-secrets
|
||||
key: QDRANT_API_KEY
|
||||
volumeMounts:
|
||||
- name: qdrant-storage
|
||||
mountPath: /qdrant/storage
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: qdrant-storage
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
@@ -0,0 +1,57 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: web
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-dify-web
|
||||
ports:
|
||||
- name: http
|
||||
port: 3000
|
||||
targetPort: 3000
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-dify-web
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-dify-web
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-dify-web
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: web
|
||||
image: ghcr.io/brunogao/ljwx-dify-web:latest
|
||||
ports:
|
||||
- containerPort: 3000
|
||||
name: http
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
@@ -0,0 +1,42 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-dify-worker
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-dify-worker
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-dify-worker
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: worker
|
||||
image: ghcr.io/brunogao/ljwx-dify-api:latest
|
||||
env:
|
||||
- name: MODE
|
||||
value: "worker"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: ljwx-dify-config
|
||||
- secretRef:
|
||||
name: ljwx-dify-secrets
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /app/api/storage
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
volumes:
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: ljwx-dify-storage
|
||||
@@ -0,0 +1,17 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: ljwx-dify-dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-dify-api
|
||||
newName: ghcr.io/brunogao/ljwx-dify-api
|
||||
newTag: latest
|
||||
- name: ghcr.io/brunogao/ljwx-dify-web
|
||||
newName: ghcr.io/brunogao/ljwx-dify-web
|
||||
newTag: latest
|
||||
- name: qdrant/qdrant
|
||||
newTag: v1.12.6
|
||||
@@ -0,0 +1,39 @@
|
||||
# ljwx-dify capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-dify-dev`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `app-runtime`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `runtime-app`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
- db_init_job (application): Declare that the workload uses a database initialization or migration hook job.
|
||||
|
||||
## Public endpoint
|
||||
|
||||
- ingress profile: `traefik-letsencrypt-public`
|
||||
- host: `https://dify.lingjingwanxiang.cn`
|
||||
- ingress class: `traefik`
|
||||
- cluster issuer: `dnspod-letsencrypt`
|
||||
- tls secret: `dify-lingjingwanxiang-cn-tls`
|
||||
- ingress artifact generation: `disabled (managed by existing manifests)`
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-dify-dev --registry-profile ghcr-and-harbor
|
||||
kubectl create secret generic runtime-app -n ljwx-dify-dev --from-literal=EXAMPLE_KEY='replace-me'
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- Namespace baseline is generated into `cluster/namespace-<namespace>.yaml` and should remain the source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,23 @@
|
||||
service: ljwx-dify
|
||||
environment: dev
|
||||
namespace: ljwx-dify-dev
|
||||
cluster_bootstrap: true
|
||||
ingress_profile: traefik-letsencrypt-public
|
||||
public_host: dify.lingjingwanxiang.cn
|
||||
generate_ingress: false
|
||||
ingress_class_name: traefik
|
||||
cluster_issuer: dnspod-letsencrypt
|
||||
tls_secret_name: dify-lingjingwanxiang-cn-tls
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: app-runtime
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
- db_init_job
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names:
|
||||
- runtime-app
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: runtime-app
|
||||
namespace: ljwx-dify-dev
|
||||
annotations:
|
||||
gitops.ljwx.io/generated-example: 'true'
|
||||
type: Opaque
|
||||
stringData:
|
||||
EXAMPLE_KEY: <replace-with-app-secret>
|
||||
@@ -32,18 +32,28 @@ spec:
|
||||
- name: http
|
||||
containerPort: 8080
|
||||
protocol: TCP
|
||||
startupProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
failureThreshold: 18
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 2
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: http
|
||||
initialDelaySeconds: 20
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 2
|
||||
volumes:
|
||||
- name: app-logs
|
||||
emptyDir: {}
|
||||
|
||||
@@ -23,6 +23,13 @@ spec:
|
||||
- host: platform-admin.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-platform
|
||||
port:
|
||||
number: 80
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
@@ -33,6 +40,13 @@ spec:
|
||||
- host: platform-screen.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-platform
|
||||
port:
|
||||
number: 80
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# ljwx-platform capability contract (prod)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-platform`
|
||||
- namespace profile: `prod-default`
|
||||
- registry profile: `harbor-only`
|
||||
- runtime secret profile: `app-runtime`
|
||||
- image pull secrets: `regcred`
|
||||
- runtime contract secrets: `runtime-app`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-platform --registry-profile harbor-only
|
||||
kubectl create secret generic runtime-app -n ljwx-platform --from-literal=EXAMPLE_KEY='replace-me'
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- Namespace baseline is generated into `cluster-prod/namespace-<namespace>.yaml` and should remain the source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,21 @@
|
||||
service: ljwx-platform
|
||||
environment: prod
|
||||
namespace: ljwx-platform
|
||||
cluster_bootstrap: true
|
||||
ingress_profile: none
|
||||
public_host: ''
|
||||
generate_ingress: true
|
||||
ingress_class_name: ''
|
||||
cluster_issuer: ''
|
||||
tls_secret_name: ''
|
||||
namespace_profile: prod-default
|
||||
registry_profile: harbor-only
|
||||
runtime_secret_profile: app-runtime
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- regcred
|
||||
contract_secret_names:
|
||||
- runtime-app
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: runtime-app
|
||||
namespace: ljwx-platform
|
||||
annotations:
|
||||
gitops.ljwx.io/generated-example: 'true'
|
||||
type: Opaque
|
||||
stringData:
|
||||
EXAMPLE_KEY: <replace-with-app-secret>
|
||||
@@ -0,0 +1,45 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-shiti
|
||||
labels:
|
||||
app: ljwx-shiti
|
||||
annotations: &id001
|
||||
gitops.ljwx.io/namespace-profile: dev-default
|
||||
gitops.ljwx.io/registry-profile: ghcr-and-harbor
|
||||
gitops.ljwx.io/runtime-secret-profile: app-runtime
|
||||
gitops.ljwx.io/capabilities: ghcr_pull,harbor_pull,runtime_app_secret
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-shiti
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-shiti
|
||||
annotations: *id001
|
||||
spec:
|
||||
containers:
|
||||
- name: ljwx-shiti
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-shiti:latest
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: runtime-app
|
||||
optional: true
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
@@ -0,0 +1,15 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-shiti
|
||||
labels:
|
||||
app: ljwx-shiti
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: ljwx-shiti
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-shiti-ingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: shiti.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-shiti
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- shiti.lingjingwanxiang.cn
|
||||
secretName: shiti-lingjingwanxiang-cn-tls
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ljwx-shiti-dev
|
||||
resources:
|
||||
- ../../base
|
||||
- ingress.yaml
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-shiti
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-shiti
|
||||
newTag: latest
|
||||
@@ -0,0 +1,38 @@
|
||||
# ljwx-shiti capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-shiti-dev`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `app-runtime`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `runtime-app`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Public endpoint
|
||||
|
||||
- ingress profile: `traefik-letsencrypt-public`
|
||||
- host: `https://shiti.lingjingwanxiang.cn`
|
||||
- ingress class: `traefik`
|
||||
- cluster issuer: `dnspod-letsencrypt`
|
||||
- tls secret: `shiti-lingjingwanxiang-cn-tls`
|
||||
- ingress artifact generation: `enabled`
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-shiti-dev --registry-profile ghcr-and-harbor
|
||||
kubectl create secret generic runtime-app -n ljwx-shiti-dev --from-literal=EXAMPLE_KEY='replace-me'
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- Namespace baseline is generated into `cluster/namespace-<namespace>.yaml` and should remain the source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,22 @@
|
||||
service: ljwx-shiti
|
||||
environment: dev
|
||||
namespace: ljwx-shiti-dev
|
||||
cluster_bootstrap: true
|
||||
ingress_profile: traefik-letsencrypt-public
|
||||
public_host: shiti.lingjingwanxiang.cn
|
||||
generate_ingress: true
|
||||
ingress_class_name: traefik
|
||||
cluster_issuer: dnspod-letsencrypt
|
||||
tls_secret_name: shiti-lingjingwanxiang-cn-tls
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: app-runtime
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names:
|
||||
- runtime-app
|
||||
@@ -0,0 +1,10 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: runtime-app
|
||||
namespace: ljwx-shiti-dev
|
||||
annotations:
|
||||
gitops.ljwx.io/generated-example: 'true'
|
||||
type: Opaque
|
||||
stringData:
|
||||
EXAMPLE_KEY: <replace-with-app-secret>
|
||||
@@ -16,9 +16,12 @@ spec:
|
||||
app: ljwx-website
|
||||
component: web
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: ljwx-website
|
||||
image: harbor.omniverseai.net/ljwx/ljwx-website:latest
|
||||
image: ghcr.io/brunogao/ljwx-website:latest
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-website-ingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: website.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-website
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- website.lingjingwanxiang.cn
|
||||
secretName: website-lingjingwanxiang-cn-tls
|
||||
@@ -3,13 +3,14 @@ kind: Kustomization
|
||||
namespace: ljwx-website-dev
|
||||
resources:
|
||||
- ../../base
|
||||
- ingress.yaml
|
||||
patches:
|
||||
- path: service-nodeport.yaml
|
||||
- path: deployment-probe.yaml
|
||||
images:
|
||||
- name: harbor.omniverseai.net/ljwx/ljwx-website
|
||||
newName: harbor.omniverseai.net/app/ljwx-website
|
||||
digest: sha256:b6bee6292395deda36062a25cdbae5a7eb81477823736bd3dacbcdc5e6be1641
|
||||
- name: ghcr.io/brunogao/ljwx-website
|
||||
newName: ghcr.io/brunogao/ljwx-website
|
||||
newTag: latest
|
||||
replicas:
|
||||
- name: ljwx-website
|
||||
count: 1
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
# ljwx-website capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-website-dev`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `none`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `none`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Public endpoint
|
||||
|
||||
- ingress profile: `traefik-letsencrypt-public`
|
||||
- host: `https://website.lingjingwanxiang.cn`
|
||||
- ingress class: `traefik`
|
||||
- cluster issuer: `dnspod-letsencrypt`
|
||||
- tls secret: `website-lingjingwanxiang-cn-tls`
|
||||
- ingress artifact generation: `enabled`
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-website-dev --registry-profile ghcr-and-harbor
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- `cluster_bootstrap` is disabled for this entry; keep the existing cluster namespace/application manifests as the current source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,21 @@
|
||||
service: ljwx-website
|
||||
environment: dev
|
||||
namespace: ljwx-website-dev
|
||||
cluster_bootstrap: false
|
||||
ingress_profile: traefik-letsencrypt-public
|
||||
public_host: website.lingjingwanxiang.cn
|
||||
generate_ingress: true
|
||||
ingress_class_name: traefik
|
||||
cluster_issuer: dnspod-letsencrypt
|
||||
tls_secret_name: website-lingjingwanxiang-cn-tls
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: none
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names: []
|
||||
@@ -0,0 +1,12 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ljwx-website-prod
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-website
|
||||
newName: ghcr.io/brunogao/ljwx-website
|
||||
newTag: latest
|
||||
replicas:
|
||||
- name: ljwx-website
|
||||
count: 2
|
||||
@@ -0,0 +1,28 @@
|
||||
# ljwx-website capability contract (prod)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-website-prod`
|
||||
- namespace profile: `prod-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `none`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `none`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
- runtime_app_secret (application): Standardize service-specific runtime secret as runtime-app.
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-website-prod --registry-profile ghcr-and-harbor
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- `cluster_bootstrap` is disabled for this entry; keep the existing cluster namespace/application manifests as the current source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,21 @@
|
||||
service: ljwx-website
|
||||
environment: prod
|
||||
namespace: ljwx-website-prod
|
||||
cluster_bootstrap: false
|
||||
ingress_profile: none
|
||||
public_host: ''
|
||||
generate_ingress: true
|
||||
ingress_class_name: ''
|
||||
cluster_issuer: ''
|
||||
tls_secret_name: ''
|
||||
namespace_profile: prod-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: none
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
- runtime_app_secret
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names: []
|
||||
@@ -1,6 +1,2 @@
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: nginx
|
||||
newName: harbor.omniverseai.net/dockerhub/library/nginx
|
||||
newTag: "alpine"
|
||||
|
||||
@@ -7,12 +7,27 @@ data:
|
||||
ENVIRONMENT: "development"
|
||||
RATE_LIMIT_RPM: "30"
|
||||
MAX_TOKENS_PER_RUN: "50000"
|
||||
DB_POOL_MIN_SIZE: "2"
|
||||
DB_POOL_MAX_SIZE: "10"
|
||||
DB_POOL_TIMEOUT_SECONDS: "5"
|
||||
DB_COMMAND_TIMEOUT_SECONDS: "5"
|
||||
KLINE_QUERY_TIMEOUT_SECONDS: "5"
|
||||
KLINE_DEFAULT_LIMIT: "60"
|
||||
KLINE_MAX_LIMIT: "500"
|
||||
KLINE_BULK_PER_SYMBOL_LIMIT: "60"
|
||||
KLINE_BULK_MAX_SYMBOLS: "500"
|
||||
KLINE_BULK_MAX_ROWS: "20000"
|
||||
CANDIDATE_POOL_SIZE: "300"
|
||||
DISPLAY_TOP_N: "20"
|
||||
MIN_AMOUNT_AVG: "10000000"
|
||||
LOOKBACK_DAYS_CALENDAR: "150"
|
||||
STRATEGY_WEIGHT_MOMENTUM_RULE_V1: "0.6"
|
||||
STRATEGY_WEIGHT_TECHNICAL_PATTERN_V1: "0.4"
|
||||
STRATEGY_WEIGHT_QLIB_RECO_READER_V1: "0.2"
|
||||
WRITE_RECO: "true"
|
||||
CODE_VERSION: "gitops"
|
||||
LLM_PROVIDER: "claude"
|
||||
ANTHROPIC_MODEL: "claude-sonnet-4-6-20260217"
|
||||
ANTHROPIC_MODEL: "claude-sonnet-4-6"
|
||||
LLM_TIMEOUT_SECONDS: "20"
|
||||
LLM_MAX_OUTPUT_TOKENS: "1200"
|
||||
MAX_USER_QUERY_LEN: "2000"
|
||||
|
||||
@@ -65,6 +65,56 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: MAX_TOKENS_PER_RUN
|
||||
- name: DB_POOL_MIN_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: DB_POOL_MIN_SIZE
|
||||
- name: DB_POOL_MAX_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: DB_POOL_MAX_SIZE
|
||||
- name: DB_POOL_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: DB_POOL_TIMEOUT_SECONDS
|
||||
- name: DB_COMMAND_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: DB_COMMAND_TIMEOUT_SECONDS
|
||||
- name: KLINE_QUERY_TIMEOUT_SECONDS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_QUERY_TIMEOUT_SECONDS
|
||||
- name: KLINE_DEFAULT_LIMIT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_DEFAULT_LIMIT
|
||||
- name: KLINE_MAX_LIMIT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_MAX_LIMIT
|
||||
- name: KLINE_BULK_PER_SYMBOL_LIMIT
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_BULK_PER_SYMBOL_LIMIT
|
||||
- name: KLINE_BULK_MAX_SYMBOLS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_BULK_MAX_SYMBOLS
|
||||
- name: KLINE_BULK_MAX_ROWS
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: KLINE_BULK_MAX_ROWS
|
||||
- name: CANDIDATE_POOL_SIZE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
@@ -75,6 +125,31 @@ spec:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: DISPLAY_TOP_N
|
||||
- name: MIN_AMOUNT_AVG
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: MIN_AMOUNT_AVG
|
||||
- name: LOOKBACK_DAYS_CALENDAR
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: LOOKBACK_DAYS_CALENDAR
|
||||
- name: STRATEGY_WEIGHT_MOMENTUM_RULE_V1
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: STRATEGY_WEIGHT_MOMENTUM_RULE_V1
|
||||
- name: STRATEGY_WEIGHT_TECHNICAL_PATTERN_V1
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: STRATEGY_WEIGHT_TECHNICAL_PATTERN_V1
|
||||
- name: STRATEGY_WEIGHT_QLIB_RECO_READER_V1
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: stock-agent-config
|
||||
key: STRATEGY_WEIGHT_QLIB_RECO_READER_V1
|
||||
- name: WRITE_RECO
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-stock-agent-ingress
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
rules:
|
||||
- host: stock-agent.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: stock-agent
|
||||
port:
|
||||
number: 80
|
||||
tls:
|
||||
- hosts:
|
||||
- stock-agent.lingjingwanxiang.cn
|
||||
secretName: stock-agent-lingjingwanxiang-cn-tls
|
||||
@@ -3,6 +3,7 @@ kind: Kustomization
|
||||
namespace: ljwx-stock
|
||||
resources:
|
||||
- ../../base
|
||||
- ingress.yaml
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-stock/ljwx-stock-agent
|
||||
newName: ghcr.io/brunogao/ljwx-stock/ljwx-stock-agent
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# ljwx-stock-agent capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-stock`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `none`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `none`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
|
||||
## Public endpoint
|
||||
|
||||
- ingress profile: `traefik-letsencrypt-public`
|
||||
- host: `https://stock-agent.lingjingwanxiang.cn`
|
||||
- ingress class: `traefik`
|
||||
- cluster issuer: `dnspod-letsencrypt`
|
||||
- tls secret: `stock-agent-lingjingwanxiang-cn-tls`
|
||||
- ingress artifact generation: `enabled`
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-stock --registry-profile ghcr-and-harbor
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- `cluster_bootstrap` is disabled for this entry; keep the existing cluster namespace/application manifests as the current source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,20 @@
|
||||
service: ljwx-stock-agent
|
||||
environment: dev
|
||||
namespace: ljwx-stock
|
||||
cluster_bootstrap: false
|
||||
ingress_profile: traefik-letsencrypt-public
|
||||
public_host: stock-agent.lingjingwanxiang.cn
|
||||
generate_ingress: true
|
||||
ingress_class_name: traefik
|
||||
cluster_issuer: dnspod-letsencrypt
|
||||
tls_secret_name: stock-agent-lingjingwanxiang-cn-tls
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: none
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names: []
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 1
|
||||
activeDeadlineSeconds: 21600
|
||||
activeDeadlineSeconds: 43200
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
@@ -74,5 +74,5 @@ spec:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
cpu: "4"
|
||||
memory: 8Gi
|
||||
|
||||
@@ -14,7 +14,7 @@ spec:
|
||||
jobTemplate:
|
||||
spec:
|
||||
backoffLimit: 2
|
||||
activeDeadlineSeconds: 7200
|
||||
activeDeadlineSeconds: 21600
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
@@ -74,5 +74,5 @@ spec:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: qlib-bootstrap
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap:bootstrap
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap:20260305-fix1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- python
|
||||
@@ -64,7 +64,7 @@ spec:
|
||||
- name: EXPORT_LOOKBACK_CALENDAR_DAYS
|
||||
value: "4500"
|
||||
- name: CODE_VERSION
|
||||
value: p6a
|
||||
value: p6a-fix20260305
|
||||
volumeMounts:
|
||||
- name: qlib-data
|
||||
mountPath: /data/qlib
|
||||
|
||||
@@ -19,7 +19,7 @@ spec:
|
||||
restartPolicy: OnFailure
|
||||
initContainers:
|
||||
- name: sync-qlib-from-minio
|
||||
image: minio/mc:RELEASE.2024-06-13T22-53-53Z
|
||||
image: minio/mc:RELEASE.2025-08-13T08-35-41Z
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /bin/sh
|
||||
@@ -29,17 +29,22 @@ spec:
|
||||
|
||||
mc alias set qlib "${MINIO_ENDPOINT}" "${MINIO_ACCESS_KEY}" "${MINIO_SECRET_KEY}"
|
||||
|
||||
qdata_latest="$(mc cat "qlib/${MINIO_BUCKET}/${MINIO_PREFIX_QDATA}/LATEST" | tr -d '\r\n')"
|
||||
if ! mc ls "qlib/${MINIO_BUCKET}" >/dev/null 2>&1; then
|
||||
echo "MinIO bucket 不存在,跳过本次 qlib 同步: ${MINIO_BUCKET}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
qdata_latest="$(mc cat "qlib/${MINIO_BUCKET}/${MINIO_PREFIX_QDATA}/LATEST" 2>/dev/null | xargs || true)"
|
||||
if [ -z "${qdata_latest}" ]; then
|
||||
echo "qlib_data LATEST 为空" >&2
|
||||
exit 1
|
||||
echo "qlib_data LATEST 不存在或为空,跳过本次 qlib 同步"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
model_family="qlib_lightgbm_alpha158"
|
||||
model_latest="$(mc cat "qlib/${MINIO_BUCKET}/${MINIO_PREFIX_MODELS}/${model_family}/LATEST" | tr -d '\r\n')"
|
||||
model_latest="$(mc cat "qlib/${MINIO_BUCKET}/${MINIO_PREFIX_MODELS}/${model_family}/LATEST" 2>/dev/null | xargs || true)"
|
||||
if [ -z "${model_latest}" ]; then
|
||||
echo "model LATEST 为空" >&2
|
||||
exit 1
|
||||
echo "model LATEST 不存在或为空,跳过本次 qlib 同步"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mkdir -p /data/qlib/qlib_data/cn
|
||||
@@ -91,12 +96,38 @@ spec:
|
||||
memory: 1Gi
|
||||
containers:
|
||||
- name: qlib-predict
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-predict:bootstrap
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-predict:20260305-fix1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- python
|
||||
- -m
|
||||
- qlib_predict.app.predict_to_pg
|
||||
- /bin/sh
|
||||
- -ec
|
||||
- |
|
||||
set -euo pipefail
|
||||
|
||||
MODEL_LATEST_FILE="/data/qlib/artifacts/models/qlib_lightgbm_alpha158/LATEST"
|
||||
if [ ! -f "${MODEL_LATEST_FILE}" ]; then
|
||||
echo "未检测到模型 LATEST,跳过本次预测"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MODEL_DATE="$(xargs < "${MODEL_LATEST_FILE}")"
|
||||
if [ -z "${MODEL_DATE}" ]; then
|
||||
echo "模型 LATEST 为空,跳过本次预测"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
MODEL_DIR="/data/qlib/artifacts/models/qlib_lightgbm_alpha158/${MODEL_DATE}"
|
||||
if [ ! -f "${MODEL_DIR}/model.pkl" ] || [ ! -f "${MODEL_DIR}/handler_config.yaml" ] || [ ! -f "${MODEL_DIR}/meta.json" ]; then
|
||||
echo "模型产物不完整,跳过本次预测: ${MODEL_DIR}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ ! -f "/data/qlib/qlib_data/cn/calendars/day.txt" ]; then
|
||||
echo "未检测到 qlib provider 数据,跳过本次预测"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
exec python -m qlib_predict.app.predict_to_pg
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
@@ -110,17 +141,17 @@ spec:
|
||||
- name: CANDIDATE_POOL_SIZE
|
||||
value: "300"
|
||||
- name: CODE_VERSION
|
||||
value: p3a
|
||||
value: p3a-fix20260305
|
||||
volumeMounts:
|
||||
- name: qlib-data
|
||||
mountPath: /data/qlib
|
||||
resources:
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: 2Gi
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: 4Gi
|
||||
volumes:
|
||||
- name: qlib-data
|
||||
persistentVolumeClaim:
|
||||
|
||||
@@ -13,7 +13,7 @@ spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: qlib-bootstrap
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap:bootstrap
|
||||
image: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap:20260305-fix1
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- python
|
||||
@@ -58,7 +58,7 @@ spec:
|
||||
- name: EXPORT_LOOKBACK_CALENDAR_DAYS
|
||||
value: "4500"
|
||||
- name: CODE_VERSION
|
||||
value: p6a
|
||||
value: p6a-fix20260305
|
||||
volumeMounts:
|
||||
- name: qlib-data
|
||||
mountPath: /data/qlib
|
||||
|
||||
@@ -6,10 +6,10 @@ resources:
|
||||
images:
|
||||
- name: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-predict
|
||||
newName: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-predict
|
||||
digest: sha256:9b310ac3e5f1079ac322b6ece4f532e43dc26fccfb9073bd8fce2fb697c0b606
|
||||
digest: sha256:af130411bf6cffe0e8a58ad2aab6f6f30d45d8f8521f591e49074ca7f3a14118
|
||||
- name: ghcr.io/brunogao/ljwx-stock/ljwx-stock-kline-etl
|
||||
newName: ghcr.io/brunogao/ljwx-stock/ljwx-stock-kline-etl
|
||||
digest: sha256:db529b61617eefd4b383e8f73668af1a272a883856c3e8cd32f5838ef5f03cc4
|
||||
- name: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap
|
||||
newName: ghcr.io/brunogao/ljwx-stock/ljwx-stock-qlib-bootstrap
|
||||
digest: sha256:6705bf8c0bb41bc36bbcdcfb021fb49c7b6bb3bb109f0ed37d8bf4118d755a51
|
||||
digest: sha256:3179cf393e708ddc5ce3e3f62837e19f7c2c0d267cdb1bc93370edb170f72eed
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# ljwx-stock-qlib-predict capability contract (dev)
|
||||
|
||||
## Namespace baseline
|
||||
|
||||
- namespace: `ljwx-stock`
|
||||
- namespace profile: `dev-default`
|
||||
- registry profile: `ghcr-and-harbor`
|
||||
- runtime secret profile: `none`
|
||||
- image pull secrets: `ghcr-pull, regcred`
|
||||
- runtime contract secrets: `none`
|
||||
|
||||
## Capabilities
|
||||
|
||||
- ghcr_pull (namespace): Bind GHCR pull secret ghcr-pull to the namespace baseline.
|
||||
- harbor_pull (namespace): Bind Harbor pull secret regcred to the namespace baseline.
|
||||
|
||||
## Bootstrap commands
|
||||
|
||||
```bash
|
||||
bash scripts/ops/sync-registry-pull-secrets.sh --target-namespace ljwx-stock --registry-profile ghcr-and-harbor
|
||||
```
|
||||
|
||||
## Notes
|
||||
|
||||
- `runtime-contract/*.secret.example.yaml` are placeholders only and must not contain real credentials.
|
||||
- `cluster_bootstrap` is disabled for this entry; keep the existing cluster namespace/application manifests as the current source of truth.
|
||||
- For legacy workloads, migrate deployment manifests gradually to `runtime-infra`, `runtime-llm`, and `runtime-app` instead of app-specific secret names.
|
||||
@@ -0,0 +1,20 @@
|
||||
service: ljwx-stock-qlib-predict
|
||||
environment: dev
|
||||
namespace: ljwx-stock
|
||||
cluster_bootstrap: false
|
||||
ingress_profile: none
|
||||
public_host: ''
|
||||
generate_ingress: true
|
||||
ingress_class_name: ''
|
||||
cluster_issuer: ''
|
||||
tls_secret_name: ''
|
||||
namespace_profile: dev-default
|
||||
registry_profile: ghcr-and-harbor
|
||||
runtime_secret_profile: none
|
||||
capabilities:
|
||||
- ghcr_pull
|
||||
- harbor_pull
|
||||
image_pull_secrets:
|
||||
- ghcr-pull
|
||||
- regcred
|
||||
contract_secret_names: []
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cluster-prod-bootstrap
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: cluster-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,27 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cert-manager-dev
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "3"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: quay.io/jetstack/charts
|
||||
chart: cert-manager
|
||||
targetRevision: v1.19.2
|
||||
helm:
|
||||
releaseName: cert-manager
|
||||
values: |
|
||||
crds:
|
||||
enabled: true
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cert-manager
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,22 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cert-manager-config-dev
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "4"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/cert-manager-config/overlays/dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: cert-manager
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -17,4 +17,4 @@ spec:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- CreateNamespace=true
|
||||
|
||||
@@ -17,4 +17,4 @@ spec:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- CreateNamespace=true
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-dify-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-dify/overlays/ljwx-dify-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-dify-dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-chat-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-chat/overlays/ljwx-chat-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-chat-dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-shiti-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-shiti/overlays/dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-shiti-dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,8 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: default
|
||||
namespace: dev
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
@@ -0,0 +1,145 @@
|
||||
apiVersion: batch/v1
|
||||
kind: CronJob
|
||||
metadata:
|
||||
name: deploy-promoter-prod
|
||||
namespace: dev
|
||||
labels:
|
||||
app.kubernetes.io/name: deploy-promoter-prod
|
||||
app.kubernetes.io/part-of: bid-mvp-factory
|
||||
spec:
|
||||
schedule: "5-59/10 * * * *"
|
||||
timeZone: Asia/Tokyo
|
||||
concurrencyPolicy: Forbid
|
||||
successfulJobsHistoryLimit: 2
|
||||
failedJobsHistoryLimit: 3
|
||||
jobTemplate:
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: promoter-prod
|
||||
image: ghcr.io/brunogao/ljwx-deploy-promoter:main
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
# Production-only promoter: wait for artifacts in production Harbor
|
||||
# (https://harbor.omniverseai.net/) and update prod overlays for the
|
||||
# production ArgoCD instance to detect and deploy.
|
||||
- name: GIT_BRANCH
|
||||
value: main
|
||||
- name: DEPLOY_REPO_URL
|
||||
value: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
- name: SERVICE_MAP_PATH
|
||||
value: release/services.orbstack-k3s-cn.yaml
|
||||
- name: ENV_ALLOWLIST
|
||||
value: "prod"
|
||||
- name: SKIP_REGISTRY_CHECK
|
||||
value: "0"
|
||||
- name: SKIP_EVIDENCE_COLLECT
|
||||
value: "0"
|
||||
- name: RETRY_MAX
|
||||
value: "10"
|
||||
- name: HARBOR_INSECURE
|
||||
value: "1"
|
||||
- name: DEPLOY_REPO_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: deploy_repo_token
|
||||
optional: true
|
||||
- name: GITHUB_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: github_token
|
||||
optional: true
|
||||
- name: HARBOR_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_prod_url
|
||||
optional: true
|
||||
- name: HARBOR_URL_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_url
|
||||
optional: true
|
||||
- name: HARBOR_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_prod_username
|
||||
optional: true
|
||||
- name: HARBOR_USER_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_username
|
||||
optional: true
|
||||
- name: HARBOR_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_prod_password
|
||||
optional: true
|
||||
- name: HARBOR_PASS_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_password
|
||||
optional: true
|
||||
command:
|
||||
- /bin/bash
|
||||
- -lc
|
||||
- |
|
||||
set -euo pipefail
|
||||
if [ -z "${HARBOR_URL:-}" ] && [ -n "${HARBOR_URL_FALLBACK:-}" ]; then
|
||||
export HARBOR_URL="${HARBOR_URL_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${HARBOR_USER:-}" ] && [ -n "${HARBOR_USER_FALLBACK:-}" ]; then
|
||||
export HARBOR_USER="${HARBOR_USER_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${HARBOR_PASS:-}" ] && [ -n "${HARBOR_PASS_FALLBACK:-}" ]; then
|
||||
export HARBOR_PASS="${HARBOR_PASS_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${DEPLOY_REPO_TOKEN:-}" ] && [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
export DEPLOY_REPO_TOKEN="${GITHUB_TOKEN}"
|
||||
fi
|
||||
if [ -z "${DEPLOY_REPO_TOKEN:-}" ]; then
|
||||
echo "缺少 DEPLOY_REPO_TOKEN(请在 deploy-promoter-secret 中配置 deploy_repo_token)"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf /work/repo
|
||||
git clone --depth=1 "https://x-access-token:${DEPLOY_REPO_TOKEN}@${DEPLOY_REPO_URL#https://}" /work/repo
|
||||
cd /work/repo
|
||||
|
||||
cmd=(
|
||||
python3 scripts/promoter/promote.py
|
||||
--local-repo-dir /work/repo
|
||||
--service-map "${SERVICE_MAP_PATH}"
|
||||
--push-branch "${GIT_BRANCH}"
|
||||
--retry-max "${RETRY_MAX}"
|
||||
)
|
||||
if [ -n "${ENV_ALLOWLIST:-}" ]; then
|
||||
cmd+=(--env-allowlist "${ENV_ALLOWLIST}")
|
||||
fi
|
||||
if [ -n "${HARBOR_URL:-}" ]; then
|
||||
cmd+=(--harbor-url "${HARBOR_URL}")
|
||||
fi
|
||||
if [ -n "${HARBOR_USER:-}" ]; then
|
||||
cmd+=(--harbor-user "${HARBOR_USER}")
|
||||
fi
|
||||
if [ -n "${HARBOR_PASS:-}" ]; then
|
||||
cmd+=(--harbor-pass "${HARBOR_PASS}")
|
||||
fi
|
||||
if [ "${SKIP_REGISTRY_CHECK:-0}" = "1" ] || [ "${SKIP_REGISTRY_CHECK:-}" = "true" ] || [ "${SKIP_REGISTRY_CHECK:-}" = "TRUE" ]; then
|
||||
cmd+=(--skip-registry-check)
|
||||
fi
|
||||
if [ "${HARBOR_INSECURE:-0}" = "1" ] || [ "${HARBOR_INSECURE:-}" = "true" ] || [ "${HARBOR_INSECURE:-}" = "TRUE" ]; then
|
||||
cmd+=(--harbor-insecure)
|
||||
fi
|
||||
if [ "${SKIP_EVIDENCE_COLLECT:-0}" = "1" ] || [ "${SKIP_EVIDENCE_COLLECT:-}" = "true" ] || [ "${SKIP_EVIDENCE_COLLECT:-}" = "TRUE" ]; then
|
||||
cmd+=(--skip-evidence-collect)
|
||||
fi
|
||||
"${cmd[@]}"
|
||||
@@ -0,0 +1,8 @@
|
||||
resources:
|
||||
- namespace-dev.yaml
|
||||
- default-sa.yaml
|
||||
- namespace-ljwx-platform.yaml
|
||||
- ljwx-platform-prod-application.yaml
|
||||
- namespace-ljwx-website-prod.yaml
|
||||
- website-prod-application.yaml
|
||||
- deploy-promoter-prod-cronjob.yaml
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-platform/overlays/prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-platform
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,4 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: dev
|
||||
@@ -0,0 +1,62 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
labels:
|
||||
name: ljwx-platform
|
||||
environment: prod
|
||||
app: ljwx-platform
|
||||
annotations:
|
||||
gitops.ljwx.io/namespace-profile: prod-default
|
||||
gitops.ljwx.io/registry-profile: harbor-only
|
||||
gitops.ljwx.io/runtime-secret-profile: app-runtime
|
||||
gitops.ljwx.io/capabilities: ghcr_pull,harbor_pull,runtime_app_secret
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: default
|
||||
namespace: ljwx-platform
|
||||
annotations:
|
||||
gitops.ljwx.io/namespace-profile: prod-default
|
||||
gitops.ljwx.io/registry-profile: harbor-only
|
||||
gitops.ljwx.io/runtime-secret-profile: app-runtime
|
||||
gitops.ljwx.io/capabilities: ghcr_pull,harbor_pull,runtime_app_secret
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ResourceQuota
|
||||
metadata:
|
||||
name: ljwx-platform-quota
|
||||
namespace: ljwx-platform
|
||||
spec:
|
||||
hard:
|
||||
requests.cpu: '2'
|
||||
requests.memory: 4Gi
|
||||
limits.cpu: '4'
|
||||
limits.memory: 8Gi
|
||||
persistentvolumeclaims: '10'
|
||||
services: '10'
|
||||
pods: '30'
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: LimitRange
|
||||
metadata:
|
||||
name: ljwx-platform-limits
|
||||
namespace: ljwx-platform
|
||||
spec:
|
||||
limits:
|
||||
- max:
|
||||
cpu: '2'
|
||||
memory: 4Gi
|
||||
min:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
default:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
defaultRequest:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
type: Container
|
||||
@@ -0,0 +1,44 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ljwx-website-prod
|
||||
labels:
|
||||
name: ljwx-website-prod
|
||||
environment: prod
|
||||
app: ljwx-website
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ResourceQuota
|
||||
metadata:
|
||||
name: ljwx-website-quota
|
||||
namespace: ljwx-website-prod
|
||||
spec:
|
||||
hard:
|
||||
requests.cpu: "500m"
|
||||
requests.memory: "512Mi"
|
||||
limits.cpu: "1"
|
||||
limits.memory: "1Gi"
|
||||
persistentvolumeclaims: "5"
|
||||
services: "5"
|
||||
pods: "10"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: LimitRange
|
||||
metadata:
|
||||
name: ljwx-website-limits
|
||||
namespace: ljwx-website-prod
|
||||
spec:
|
||||
limits:
|
||||
- max:
|
||||
cpu: "500m"
|
||||
memory: "512Mi"
|
||||
min:
|
||||
cpu: "10m"
|
||||
memory: "32Mi"
|
||||
default:
|
||||
cpu: "100m"
|
||||
memory: "128Mi"
|
||||
defaultRequest:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
type: Container
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-website-prod
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-website/overlays/ljwx-website-prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-website-prod
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -4,4 +4,5 @@ metadata:
|
||||
name: default
|
||||
namespace: dev
|
||||
imagePullSecrets:
|
||||
- name: ghcr-pull
|
||||
- name: regcred
|
||||
|
||||
@@ -19,21 +19,26 @@ spec:
|
||||
restartPolicy: OnFailure
|
||||
containers:
|
||||
- name: promoter
|
||||
image: ghcr.io/brunogaosz/ljwx-deploy-promoter:main
|
||||
image: ghcr.io/brunogao/ljwx-deploy-promoter:main
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
# Dev promoter only: wait for local Harbor candidate images and update dev/demo overlays.
|
||||
- name: GIT_BRANCH
|
||||
value: main
|
||||
- name: DEPLOY_REPO_URL
|
||||
value: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
- name: SERVICE_MAP_PATH
|
||||
value: release/services.local-k3s.yaml
|
||||
- name: ENV_ALLOWLIST
|
||||
value: "dev,demo"
|
||||
- name: SKIP_REGISTRY_CHECK
|
||||
value: "1"
|
||||
value: "0"
|
||||
- name: SKIP_EVIDENCE_COLLECT
|
||||
value: "0"
|
||||
- name: RETRY_MAX
|
||||
value: "10"
|
||||
- name: HARBOR_INSECURE
|
||||
value: "1"
|
||||
- name: DEPLOY_REPO_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
@@ -46,19 +51,38 @@ spec:
|
||||
name: deploy-promoter-secret
|
||||
key: github_token
|
||||
optional: true
|
||||
# Local Harbor is the dev gate; prod Harbor is handled only by cluster-prod/deploy-promoter-prod.
|
||||
- name: HARBOR_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_local_url
|
||||
optional: true
|
||||
- name: HARBOR_URL_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_url
|
||||
optional: true
|
||||
- name: HARBOR_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_local_username
|
||||
optional: true
|
||||
- name: HARBOR_USER_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_username
|
||||
optional: true
|
||||
- name: HARBOR_PASS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
key: harbor_local_password
|
||||
optional: true
|
||||
- name: HARBOR_PASS_FALLBACK
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: deploy-promoter-secret
|
||||
@@ -69,6 +93,15 @@ spec:
|
||||
- -lc
|
||||
- |
|
||||
set -euo pipefail
|
||||
if [ -z "${HARBOR_URL:-}" ] && [ -n "${HARBOR_URL_FALLBACK:-}" ]; then
|
||||
export HARBOR_URL="${HARBOR_URL_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${HARBOR_USER:-}" ] && [ -n "${HARBOR_USER_FALLBACK:-}" ]; then
|
||||
export HARBOR_USER="${HARBOR_USER_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${HARBOR_PASS:-}" ] && [ -n "${HARBOR_PASS_FALLBACK:-}" ]; then
|
||||
export HARBOR_PASS="${HARBOR_PASS_FALLBACK}"
|
||||
fi
|
||||
if [ -z "${DEPLOY_REPO_TOKEN:-}" ] && [ -n "${GITHUB_TOKEN:-}" ]; then
|
||||
export DEPLOY_REPO_TOKEN="${GITHUB_TOKEN}"
|
||||
fi
|
||||
@@ -76,18 +109,35 @@ spec:
|
||||
echo "缺少 DEPLOY_REPO_TOKEN(请在 deploy-promoter-secret 中配置 deploy_repo_token)"
|
||||
exit 1
|
||||
fi
|
||||
rm -rf /work/repo
|
||||
git clone --depth=1 "https://x-access-token:${DEPLOY_REPO_TOKEN}@${DEPLOY_REPO_URL#https://}" /work/repo
|
||||
cd /work/repo
|
||||
|
||||
cmd=(
|
||||
python3 /app/scripts/promoter/promote.py
|
||||
--deploy-repo-url "${DEPLOY_REPO_URL}"
|
||||
--deploy-repo-token "${DEPLOY_REPO_TOKEN}"
|
||||
python3 scripts/promoter/promote.py
|
||||
--local-repo-dir /work/repo
|
||||
--service-map "${SERVICE_MAP_PATH}"
|
||||
--push-branch "${GIT_BRANCH}"
|
||||
--retry-max "${RETRY_MAX}"
|
||||
)
|
||||
if [ -n "${ENV_ALLOWLIST:-}" ]; then
|
||||
cmd+=(--env-allowlist "${ENV_ALLOWLIST}")
|
||||
fi
|
||||
if [ -n "${HARBOR_URL:-}" ]; then
|
||||
cmd+=(--harbor-url "${HARBOR_URL}")
|
||||
fi
|
||||
if [ -n "${HARBOR_USER:-}" ]; then
|
||||
cmd+=(--harbor-user "${HARBOR_USER}")
|
||||
fi
|
||||
if [ -n "${HARBOR_PASS:-}" ]; then
|
||||
cmd+=(--harbor-pass "${HARBOR_PASS}")
|
||||
fi
|
||||
if [ "${SKIP_REGISTRY_CHECK:-0}" = "1" ] || [ "${SKIP_REGISTRY_CHECK:-}" = "true" ] || [ "${SKIP_REGISTRY_CHECK:-}" = "TRUE" ]; then
|
||||
cmd+=(--skip-registry-check)
|
||||
fi
|
||||
if [ "${HARBOR_INSECURE:-0}" = "1" ] || [ "${HARBOR_INSECURE:-}" = "true" ] || [ "${HARBOR_INSECURE:-}" = "TRUE" ]; then
|
||||
cmd+=(--harbor-insecure)
|
||||
fi
|
||||
if [ "${SKIP_EVIDENCE_COLLECT:-0}" = "1" ] || [ "${SKIP_EVIDENCE_COLLECT:-}" = "true" ] || [ "${SKIP_EVIDENCE_COLLECT:-}" = "TRUE" ]; then
|
||||
cmd+=(--skip-evidence-collect)
|
||||
fi
|
||||
|
||||
@@ -10,6 +10,15 @@ stringData:
|
||||
deploy_repo_token: "<github_app_or_fine_grained_token>"
|
||||
# Backward compatible key (optional, can be omitted after migration).
|
||||
github_token: "<legacy_token_optional>"
|
||||
# Local Harbor credentials (dev/demos deploy gate).
|
||||
harbor_local_url: "https://harbor.eu.lingjingwanxiang.cn"
|
||||
harbor_local_username: "admin"
|
||||
harbor_local_password: "<local_harbor_password>"
|
||||
# Production Harbor credentials (prod deploy gate).
|
||||
harbor_prod_url: "https://harbor.omniverseai.net"
|
||||
harbor_prod_username: "admin"
|
||||
harbor_prod_password: "<prod_harbor_password>"
|
||||
# Legacy single Harbor keys (fallback only).
|
||||
harbor_url: "https://harbor.example.com"
|
||||
harbor_username: "<harbor_user>"
|
||||
harbor_password: "<harbor_password>"
|
||||
|
||||
+17
-11
@@ -1,12 +1,18 @@
|
||||
resources:
|
||||
- namespace-dev.yaml
|
||||
- namespace-ljwx-website-dev.yaml
|
||||
- namespace-ljwx-bookstore.yaml
|
||||
- website-dev-application.yaml
|
||||
- ljwx-platform-observability-application.yaml
|
||||
- deploy-promoter-cronjob.yaml
|
||||
- smoke-runner-cronjob.yaml
|
||||
- regcred.yaml
|
||||
- default-sa.yaml
|
||||
- stock-etl-qlib-predict-application.yaml
|
||||
- stock-agent-application.yaml
|
||||
- namespace-dev.yaml
|
||||
- namespace-ljwx-website-dev.yaml
|
||||
- namespace-ljwx-dify-dev.yaml
|
||||
- namespace-ljwx-chat-dev.yaml
|
||||
- namespace-ljwx-bookstore.yaml
|
||||
- website-dev-application.yaml
|
||||
- ljwx-dify-dev-application.yaml
|
||||
- ljwx-chat-dev-application.yaml
|
||||
- ljwx-platform-observability-application.yaml
|
||||
- deploy-promoter-cronjob.yaml
|
||||
- smoke-runner-cronjob.yaml
|
||||
- regcred.yaml
|
||||
- default-sa.yaml
|
||||
- stock-etl-qlib-predict-application.yaml
|
||||
- stock-agent-application.yaml
|
||||
- namespace-ljwx-shiti-dev.yaml
|
||||
- ljwx-shiti-dev-application.yaml
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-chat-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-chat/overlays/ljwx-chat-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-chat-dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -0,0 +1,20 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-dify-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/ljwx-dify/overlays/ljwx-dify-dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-dify-dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user