Compare commits

..

11 Commits
master ... test

Author SHA1 Message Date
eb6612a7ea feat: track oss
All checks were successful
Sync Consul Config / sync (push) Successful in 4s
2026-09-23 14:46:07 +08:00
bedd3a862e 忽略40x
All checks were successful
Sync Consul Config / sync (push) Successful in 4s
2026-09-22 18:41:58 +08:00
eb4d9bb64b oss
All checks were successful
Sync Consul Config / sync (push) Successful in 4s
2026-09-22 16:57:33 +08:00
7df974a59a cors
All checks were successful
Sync Consul Config / sync (push) Successful in 4s
2026-09-22 16:36:07 +08:00
f156204e1e 111
All checks were successful
Sync Consul Config / sync (push) Successful in 3s
2026-09-21 17:52:21 +08:00
e7227e0df0 111
Some checks failed
Sync Consul Config / sync (push) Has been cancelled
2026-09-21 17:39:53 +08:00
a67ef63eeb 1
All checks were successful
Sync Consul Config / sync (push) Successful in 9m3s
2026-09-21 17:17:56 +08:00
f9b6b93064 2
Some checks failed
Sync Consul Config / sync (push) Failing after 2m12s
2026-09-21 16:57:32 +08:00
aae640d675 1
Some checks failed
Sync Consul Config / sync (push) Failing after 0s
2026-09-21 16:36:47 +08:00
e19ebab1e6 test
Some checks failed
Sync Consul Config / sync (push) Failing after 1s
2026-09-21 16:18:53 +08:00
3cf8dd9ced init
Some checks failed
Sync Consul Config / sync (push) Failing after 0s
2026-09-21 16:17:39 +08:00
8 changed files with 225 additions and 139 deletions

View File

@ -1,15 +1,10 @@
# push 到 main/master → 渲染密钥 → 写入 Consul → 重启相关 API # 复制到配置仓 .gitea/workflows/sync-consul.yml
# 触发方式与 api/front 一致(不做 paths 过滤,避免「推了却不跑」) # push master → prod Consul;push test → test Consul
name: Sync Consul Config name: Sync Consul Config
on: on:
push: push:
branches: [master, main] branches: [master, test]
paths:
- "idwms/**"
- "scripts/**"
- ".gitea/workflows/sync-consul.yml"
workflow_dispatch:
jobs: jobs:
sync: sync:
@ -18,20 +13,46 @@ jobs:
- name: Checkout and sync - name: Checkout and sync
run: | run: |
set -euxo pipefail set -euxo pipefail
REF="${{ gitea.ref_name }}"
case "$REF" in
test) TARGET=test ;;
master) TARGET=prod ;;
*) echo "unsupported branch: $REF"; exit 1 ;;
esac
case "$TARGET" in
test)
export KUBECONFIG=/etc/rancher/k3s/k3s-test.yaml
export NAMESPACE=test
# 内网 NodePort(consul-ci);安全组仅 42→13:30850
export CONSUL_HTTP_ADDR="${TEST_CONSUL_HTTP_ADDR:-http://172.16.16.13:30850}"
;;
*)
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export NAMESPACE=prod export NAMESPACE=prod
unset CONSUL_HTTP_ADDR
;;
esac
if [[ ! -f "$KUBECONFIG" ]]; then
echo "FAIL: missing KUBECONFIG=$KUBECONFIG host=$(hostname) uid=$(id -u)"
ls -la /etc/rancher/k3s/ /root/.kube/ 2>/dev/null || true
exit 1
fi
# 暂时关闭改配置后自动重启 API
# export RESTART_APIS=1
export RESTART_APIS=0
REPO="${{ gitea.repository }}" REPO="${{ gitea.repository }}"
REPO="${REPO:-${{ github.repository }}}"
SHA="${{ gitea.sha }}" SHA="${{ gitea.sha }}"
SHA="${SHA:-${{ github.sha }}}"
test -n "$REPO" && [[ "$REPO" == */* ]] test -n "$REPO" && [[ "$REPO" == */* ]]
test -n "$SHA" test -n "$SHA"
echo "TARGET=$TARGET REF=$REF NAMESPACE=$NAMESPACE"
GITEA_HTTP="https://git.ideship.com" GITEA_HTTP="https://git.ideship.com"
WORK="/data/ci-build/config" WORK="/data/ci-build/config-${TARGET}"
GITEA_USER="$(kubectl -n prod get secret kaniko-git -o jsonpath='{.data.GITEA_USER}' | base64 -d)" GITEA_USER="$(kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml -n prod get secret kaniko-git -o jsonpath='{.data.GITEA_USER}' | base64 -d)"
GIT_TOKEN="$(kubectl -n prod get secret kaniko-git -o jsonpath='{.data.GIT_TOKEN}' | base64 -d)" GIT_TOKEN="$(kubectl --kubeconfig=/etc/rancher/k3s/k3s.yaml -n prod get secret kaniko-git -o jsonpath='{.data.GIT_TOKEN}' | base64 -d)"
GITEA_USER="${GITEA_USER:-gitea}" GITEA_USER="${GITEA_USER:-gitea}"
rm -rf "$WORK" rm -rf "$WORK"

View File

@ -4,6 +4,15 @@ Gitea 仓保存 `idwms/*.json` → CI 用 K8s Secret 渲染密码 → `PUT` 到
应用仍只读 Consul(`AddConsul("idwms/...")`),不读本仓库。 应用仍只读 Consul(`AddConsul("idwms/...")`),不读本仓库。
## 分支 → 环境
| 分支 | 集群 | Namespace | 前端 CORS |
|------|------|-----------|-----------|
| `master` | prod(134) | `prod` | `https://ideship.com` |
| `test` | test(13) | `test` | `https://test.ideship.com` |
`test` 分支里中间件地址应为 `*.test.svc.cluster.local`;`master` 为 `*.prod.svc.cluster.local`。
## 目录 ## 目录
| 文件 | Consul KV | | 文件 | Consul KV |
@ -13,26 +22,27 @@ Gitea 仓保存 `idwms/*.json` → CI 用 K8s Secret 渲染密码 → `PUT` 到
| `idwms/itfxapi.json` | `idwms/itfxapi` | | `idwms/itfxapi.json` | `idwms/itfxapi` |
| `idwms/trackapi.json` | `idwms/trackapi` | | `idwms/trackapi.json` | `idwms/trackapi` |
JSON 里敏感字段用占位符,例如 `${MYSQL_ROOT_PASSWORD}`。同步时从 `prod-credentials` / `jwt-secret` 注入,**明文密码不进 git**。 JSON 里敏感字段用占位符,例如 `${MYSQL_ROOT_PASSWORD}`。同步时从对应 namespace 的 `prod-credentials` / `jwt-secret` 注入,**明文密码不进 git**。
## 一次性准备 ## 一次性准备
### 1. JWT(业务 API 用,中间件 Secret 里没有) ### 1. JWT(业务 API 用)
在 k3s 节点:
```bash ```bash
# prod
kubectl -n prod create secret generic jwt-secret \ kubectl -n prod create secret generic jwt-secret \
--from-literal=JWT_SECRET_KEY='请换成足够长的随机串' \ --from-literal=JWT_SECRET_KEY='请换成足够长的随机串' \
--dry-run=client -o yaml | kubectl apply -f - --dry-run=client -o yaml | kubectl apply -f -
# test(也可用 secrets.yaml 里 prod-credentials 的 JWT_SECRET_KEY)
kubectl --kubeconfig=/etc/rancher/k3s/k3s-test.yaml -n test \
create secret generic jwt-secret \
--from-literal=JWT_SECRET_KEY='ideship-jwt-secret-test-change-me' \
--dry-run=client -o yaml | kubectl apply -f -
``` ```
也可把 `JWT_SECRET_KEY` 加进现有 `manifests/secrets.yaml` 的 `prod-credentials`。
### 2. (可选)Consul ACL Token ### 2. (可选)Consul ACL Token
若已开 ACL:
```bash ```bash
kubectl -n prod create secret generic consul-acl \ kubectl -n prod create secret generic consul-acl \
--from-literal=CONSUL_HTTP_TOKEN='...' \ --from-literal=CONSUL_HTTP_TOKEN='...' \
@ -43,50 +53,59 @@ kubectl -n prod create secret generic consul-acl \
```bash ```bash
cd d:/my/itfx/config cd d:/my/itfx/config
git remote add origin https://git.ideship.com/<org>/config.git git push -u origin master # → prod Consul
git add -A && git commit -m "chore: initial consul config repo" git push -u origin test # → test Consul
git push -u origin master
``` ```
在 Gitea 启用 Actions,确保 runner 标签含 `k3s-host`(与 API 部署相同)。 Runner 标签需含 `k3s-host`;134 上需有 `/etc/rancher/k3s/k3s-test.yaml`。
### 4. 与线上对账(推荐先做) ### 4. 与线上对账(推荐先做)
在能 `kubectl` 的机器上:
```bash ```bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml
export NAMESPACE=prod
./scripts/export-from-consul.sh ./scripts/export-from-consul.sh
# 把导出文件里的密码改回 ${MYSQL_ROOT_PASSWORD} 等占位符后再提交 # 把导出文件里的密码改回 ${MYSQL_ROOT_PASSWORD} 等占位符后再提交到 master
``` ```
避免模板库名/字段和现网不一致,第一次同步把线上冲掉。
## 日常 ## 日常
改 JSON → push `main`/`master` → workflow `Sync Consul Config` 自动同步并 `rollout restart` 对应 API。 改 JSON → push `master` / `test` → workflow 按分支写入对应 Consul 并 `rollout restart`。
手动: 手动(在 runner 机上):
```bash ```bash
export KUBECONFIG=/etc/rancher/k3s/k3s.yaml # prod(ClusterIP,本机可达)
./scripts/sync-to-consul.sh # 全部 export KUBECONFIG=/etc/rancher/k3s/k3s.yaml NAMESPACE=prod
RESTART_APIS=1 ./scripts/sync-to-consul.sh admin ./scripts/sync-to-consul.sh
# test(内网 NodePort consul-ci;安全组仅 runner→13:30850)
export KUBECONFIG=/etc/rancher/k3s/k3s-test.yaml NAMESPACE=test
export CONSUL_HTTP_ADDR=http://172.16.16.13:30850
./scripts/sync-to-consul.sh
``` ```
Consul **不要**走公网 Ingress;集群内业务仍用 `consul.test.svc:8500`。
## 密钥来源 ## 密钥来源
| 占位符 | Secret | | 占位符 | Secret(在目标 NAMESPACE 内 `prod-credentials`) |
|--------|--------| |--------|--------------------------------------------------|
| `${MYSQL_ROOT_PASSWORD}` | `prod-credentials` | | `${MYSQL_ROOT_PASSWORD}` | MYSQL_ROOT_PASSWORD |
| `${REDIS_PASSWORD}` | `prod-credentials` | | `${REDIS_PASSWORD}` | REDIS_PASSWORD |
| `${RABBITMQ_DEFAULT_PASS}` | `prod-credentials` | | `${RABBITMQ_DEFAULT_PASS}` | RABBITMQ_DEFAULT_PASS |
| `${ELASTIC_PASSWORD}` | `prod-credentials` | | `${ELASTIC_PASSWORD}` | ELASTIC_PASSWORD |
| `${JWT_SECRET_KEY}` | `jwt-secret` 或 `prod-credentials` | | `${JWT_SECRET_KEY}` | JWT_SECRET_KEY(或单独 `jwt-secret`) |
| `${COS_SECRET_ID}` / `${COS_SECRET_KEY}` | COS_* |
| `${FEISHU_WEBHOOK}` | FEISHU_WEBHOOK |
| `${WECHAT_MP_*}` | WECHAT_MP_APP_ID / SECRET / TOKEN |
| `${OMS_TRACK_*}` | OMS_TRACK_APP_KEY / SECRET |
| `${LAZADA_*}` `${SHOPEE_*}` `${TIKTOK_*}` `${TEMU_*}` | 对应平台密钥 |
与 `k3s/manifests/secrets.yaml` 一致:中间件用 Secret;本仓只存带占位符的业务配置模板。 同步后 **默认不再** `rollout restart`(已暂时关闭);需要时手动 restart 或恢复脚本里注释块。
## 注意 ## 注意
- `App:CorsOrigins` 在启动时打进 CORS 策略,改完必须重启 API(CI 默认会 restart)。 - `App:CorsOrigins` 改完必须重启 API(CI 默认会 restart)。
- 首次用仓库模板前务必 `export-from-consul` 对过现网,或确认 MySQL 库名(`oms` / `itfx` / `track`)正确。 - COS 等共享公网地址可两端相同;库名/中间件 DNS 必须按环境分开。
- Consul 保持 ClusterIP,CI 经节点访问 Service ClusterIP,不暴露公网。 - Consul 保持 ClusterIP,CI 经节点访问 Service ClusterIP。

View File

@ -2,19 +2,16 @@
"App": { "App": {
"Urls": [ "Urls": [
"http://*:5281" "http://*:5281"
],
"CorsOrigins": [
"https://ideship.com"
] ]
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"Default": "server=mysql.prod.svc.cluster.local;database=fake_simple_admin;uid=dev;pwd=itd!@#123;charset=utf8mb4;", "Default": "server=mysql.test.svc.cluster.local;database=fake_simple_admin;uid=dev;pwd=${MYSQL_ROOT_PASSWORD};charset=utf8mb4;",
"Rbac": "server=mysql.prod.svc.cluster.local;database=fake_simple_admin;uid=dev;pwd=itd!@#123;charset=utf8mb4;" "Rbac": "server=mysql.test.svc.cluster.local;database=fake_simple_admin;uid=dev;pwd=${MYSQL_ROOT_PASSWORD};charset=utf8mb4;"
}, },
"Redis": { "Redis": {
"Host": "redis.prod.svc.cluster.local:6379", "Host": "redis.test.svc.cluster.local:6379",
"Password": "itd!@#123", "Password": "${REDIS_PASSWORD}",
"MaxPoolSize": 1000, "MaxPoolSize": 1000,
"Database": 0, "Database": 0,
"Prefix": "" "Prefix": ""
@ -22,10 +19,10 @@
"RabbitMQ": { "RabbitMQ": {
"Connections": { "Connections": {
"Default": { "Default": {
"HostName": "rabbitmq.prod.svc.cluster.local", "HostName": "rabbitmq.test.svc.cluster.local",
"Port": 5672, "Port": 5672,
"UserName": "rabbit", "UserName": "rabbit",
"Password": "itd!@#123", "Password": "${RABBITMQ_DEFAULT_PASS}",
"DispatchConsumersAsync": true "DispatchConsumersAsync": true
} }
}, },
@ -42,53 +39,51 @@
}, },
"WeChat": { "WeChat": {
"Mp": { "Mp": {
"AppId": "wxd5141ba46c856e1f", "AppId": "${WECHAT_MP_APP_ID}",
"Secret": "379f51a89419dc36fbb9167feef6f89a", "Secret": "${WECHAT_MP_SECRET}",
"Token": "c6ca74013df823c0e1da0feaaaf8bf66", "Token": "${WECHAT_MP_TOKEN}",
"EncodingAesKey": "", "EncodingAesKey": "",
"OAuthRedirectBase": "https://ideship.com" "OAuthRedirectBase": "https://test.ideship.com"
} }
}, },
"FileStorage": { "ObjectStorage": {
"Cos": {
"KeyPrefix": "admin/" "KeyPrefix": "admin/"
}
}, },
"Oms": { "Oms": {
"TrackService": { "TrackService": {
"Enabled": true, "Enabled": true,
"BaseUrl": "http://api.ideship.com/track", "BaseUrl": "https://api.test.ideship.com/track",
"AppKey": "W2606001", "AppKey": "${OMS_TRACK_APP_KEY}",
"AppSecret": "ziK8UzlQ39m3kdL346XEBg8nljY8os52", "AppSecret": "${OMS_TRACK_APP_SECRET}",
"TimeoutSeconds": 30 "TimeoutSeconds": 30
}, },
"Itfx": { "Itfx": {
"BaseUrl": "http://api.ideship.com/itfx", "BaseUrl": "https://api.test.ideship.com/itfx",
"TimeoutSeconds": 30 "TimeoutSeconds": 30
}, },
"PlatformShop": { "PlatformShop": {
"FrontendBaseUrl": "http://localhost:5173", "FrontendBaseUrl": "https://test.ideship.com",
"Lazada": { "Lazada": {
"AppKey": "123631", "AppKey": "${LAZADA_APP_KEY}",
"AppSecret": "7vBz2EaD6rIupeGEfwrgkabsTNsXt2ix", "AppSecret": "${LAZADA_APP_SECRET}",
"AuthorizeUrl": "https://auth.lazada.com/oauth/authorize", "AuthorizeUrl": "https://auth.lazada.com/oauth/authorize",
"AccessTokenUrl": "https://auth.lazada.com/rest" "AccessTokenUrl": "https://auth.lazada.com/rest"
}, },
"Shopee": { "Shopee": {
"PartnerId": 2004112, "PartnerId": ${SHOPEE_PARTNER_ID},
"PartnerKey": "2f385d7ea38b087d5d71f7292e06a09c9f19022ca6c3f60ec0110a8214ef7d65", "PartnerKey": "${SHOPEE_PARTNER_KEY}",
"HostUrl": "https://openplatform.shopee.cn", "HostUrl": "https://openplatform.shopee.cn",
"HostAuthUrl": "https://partner.shopeemobile.com" "HostAuthUrl": "https://partner.shopeemobile.com"
}, },
"Tiktok": { "Tiktok": {
"AppKey": "6hc64lc6vl312", "AppKey": "${TIKTOK_APP_KEY}",
"AppSecret": "5a691a96ee6d844534769bab5c21de1f848fb678", "AppSecret": "${TIKTOK_APP_SECRET}",
"HostAuth": "https://auth.tiktok-shops.com", "HostAuth": "https://auth.tiktok-shops.com",
"HostApi": "https://open-api.tiktokglobalshop.com" "HostApi": "https://open-api.tiktokglobalshop.com"
}, },
"Temu": { "Temu": {
"AppKey": "4ebbc9190ae410443d65b4c2faca981f", "AppKey": "${TEMU_APP_KEY}",
"AppSecret": "4782d2d827276688bf4758bed55dbdd4bbe79a79", "AppSecret": "${TEMU_APP_SECRET}",
"HostAuthUrl": "https://openapi-b-us.temu.com/openapi/router" "HostAuthUrl": "https://openapi-b-us.temu.com/openapi/router"
} }
} }

View File

@ -1,7 +1,7 @@
{ {
"App": { "App": {
"CorsOrigins": [ "CorsOrigins": [
"https://ideship.com" "https://test.ideship.com"
] ]
}, },
"Serilog": { "Serilog": {
@ -42,47 +42,36 @@
] ]
}, },
"FeiShuNotice": { "FeiShuNotice": {
"Webhook": "https://open.feishu.cn/open-apis/bot/v2/hook/94e24bce-619d-4b3b-936c-e206353ce802", "Webhook": "${FEISHU_WEBHOOK}",
"BatchSize": 10, "BatchSize": 10,
"BatchIntervalSeconds": 10, "BatchIntervalSeconds": 10,
"EnableFeiShuExceptionSubscribe": true, "EnableFeiShuExceptionSubscribe": true,
"Skip40X": false, "Skip40X": true,
"WriteBody": true, "WriteBody": true,
"WriteStack": true, "WriteStack": true,
"MaxLength": 3000 "MaxLength": 3000
}, },
"JwtSettings": { "JwtSettings": {
"SecretKey": "YourSuperSecretKeyThatIsAtLeast32CharactersLong!", "SecretKey": "${JWT_SECRET_KEY}",
"Issuer": "SimpleAdmin", "Issuer": "SimpleAdmin",
"Audience": "SimpleAdminClient", "Audience": "SimpleAdminClient",
"ExpiryInMinutes": 600, "ExpiryInMinutes": 600,
"RefreshExpiryInDays": 7 "RefreshExpiryInDays": 7
}, },
"FileStorage": {
"Provider": "Cos",
"Cos": {
"SecretId": "AKIDdOxIOGuKVSAetteBTbZLtCG6i5D6k9JE",
"SecretKey": "kEWbWDPPAhz2qQq4FF5kBOXTAcpnkY8U",
"Region": "ap-guangzhou",
"Bucket": "ideship-1308574410",
"BaseUrl": "https://cos.ideship.com",
"DurationSecond": 3600,
"UploadDurationSecond": 600,
"SignUrls": true
}
},
"ObjectStorage": { "ObjectStorage": {
"Provider": "TencentCos", "Provider": "TencentCos",
"SignUrlsByDefault": true, "SignUrlsByDefault": true,
"DefaultSignedUrlExpiresSeconds": 900, "DefaultSignedUrlExpiresSeconds": 3600,
"DefaultUploadExpiresSeconds": 600,
"TencentCos": { "TencentCos": {
"SecretId": "AKIDdOxIOGuKVSAetteBTbZLtCG6i5D6k9JE", "SecretId": "${COS_SECRET_ID}",
"SecretKey": "kEWbWDPPAhz2qQq4FF5kBOXTAcpnkY8U", "SecretKey": "${COS_SECRET_KEY}",
"Region": "ap-guangzhou", "Region": "ap-guangzhou",
"Bucket": "ideship-1308574410", "Bucket": "test-1308574410",
"BaseUrl": "https://cos.ideship.com", "BaseUrl": "https://cos.ideship.com",
"UseHttps": true, "UseHttps": true,
"SignUrls": true "SignUrls": true,
"DurationSecond": 600
} }
} }
} }

View File

@ -19,11 +19,11 @@
"KeyPrefix": "itfx" "KeyPrefix": "itfx"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"Default": "server=mysql.prod.svc.cluster.local;database=interface_forward;uid=dev;pwd=itd!@#123;charset=utf8mb4;" "Default": "server=mysql.test.svc.cluster.local;database=interface_forward;uid=dev;pwd=${MYSQL_ROOT_PASSWORD};charset=utf8mb4;"
}, },
"Redis": { "Redis": {
"Host": "redis.prod.svc.cluster.local:6379", "Host": "redis.test.svc.cluster.local:6379",
"Password": "itd!@#123", "Password": "${REDIS_PASSWORD}",
"MaxPoolSize": 1000, "MaxPoolSize": 1000,
"Database": 0, "Database": 0,
"Prefix": "" "Prefix": ""
@ -31,18 +31,18 @@
"RabbitMQ": { "RabbitMQ": {
"Connections": { "Connections": {
"Default": { "Default": {
"HostName": "rabbitmq.prod.svc.cluster.local", "HostName": "rabbitmq.test.svc.cluster.local",
"Port": 5672, "Port": 5672,
"UserName": "rabbit", "UserName": "rabbit",
"Password": "itd!@#123", "Password": "${RABBITMQ_DEFAULT_PASS}",
"DispatchConsumersAsync": true "DispatchConsumersAsync": true
} }
} }
}, },
"ElasticSearch": { "ElasticSearch": {
"NodeUrls": [ "http://es.prod.svc.cluster.local:9200" ], "NodeUrls": [ "http://es.test.svc.cluster.local:9200" ],
"LoginName": "elastic", "LoginName": "elastic",
"Password": "itd!@#123", "Password": "${ELASTIC_PASSWORD}",
"ShowLogInfo": false, "ShowLogInfo": false,
"IndexPrefix": "" "IndexPrefix": ""
} }

View File

@ -10,16 +10,19 @@
"OrderPullStatusScanInterval": 60, "OrderPullStatusScanInterval": 60,
"TrackButtomUpScanInterval":600, "TrackButtomUpScanInterval":600,
"WarningNotifyUrl": "http://xxxx", "WarningNotifyUrl": "http://xxxx",
"ItfxBaseAddress": "http://interfaceforward-api.prod.svc.cluster.local:8888/", "ItfxBaseAddress": "http://interfaceforward-api.test.svc.cluster.local:8888/",
"TrackPullTaskLevel": 0, "TrackPullTaskLevel": 0,
"TrackPullTaskMinuteInterval": 5 "TrackPullTaskMinuteInterval": 5
}, },
"ObjectStorage": {
"KeyPrefix": "track/"
},
"ConnectionStrings": { "ConnectionStrings": {
"Default": "server=mysql.prod.svc.cluster.local;database=track_center;uid=dev;pwd=itd!@#123;charset=utf8mb4;" "Default": "server=mysql.test.svc.cluster.local;database=track_center;uid=dev;pwd=${MYSQL_ROOT_PASSWORD};charset=utf8mb4;"
}, },
"Redis": { "Redis": {
"Host": "redis.prod.svc.cluster.local:6379", "Host": "redis.test.svc.cluster.local:6379",
"Password": "itd!@#123", "Password": "${REDIS_PASSWORD}",
"MaxPoolSize": 1000, "MaxPoolSize": 1000,
"Database": 1, "Database": 1,
"Prefix": "" "Prefix": ""
@ -27,23 +30,23 @@
"RabbitMQ": { "RabbitMQ": {
"Connections": { "Connections": {
"Default": { "Default": {
"HostName": "rabbitmq.prod.svc.cluster.local", "HostName": "rabbitmq.test.svc.cluster.local",
"Port": 5672, "Port": 5672,
"UserName": "rabbit", "UserName": "rabbit",
"Password": "itd!@#123", "Password": "${RABBITMQ_DEFAULT_PASS}",
"DispatchConsumersAsync": true "DispatchConsumersAsync": true
} }
} }
}, },
"ElasticSearch": { "ElasticSearch": {
"NodeUrls": [ "http://es.prod.svc.cluster.local:9200" ], "NodeUrls": [ "http://es.test.svc.cluster.local:9200" ],
"LoginName": "elastic", "LoginName": "elastic",
"Password": "itd!@#123", "Password": "${ELASTIC_PASSWORD}",
"ShowLogInfo": false, "ShowLogInfo": false,
"IndexPrefix": "" "IndexPrefix": ""
}, },
"Kafka": { "Kafka": {
"BootstrapServers": "kafka.prod.svc.cluster.local:9092", "BootstrapServers": "kafka.test.svc.cluster.local:9092",
"Consumer": { "Consumer": {
// 会话超时时间ms,超时时间未收到客户端心跳,则会移除该客户端同时触发自平衡 // 会话超时时间ms,超时时间未收到客户端心跳,则会移除该客户端同时触发自平衡
"SessionTimeoutMs": 45000, "SessionTimeoutMs": 45000,

View File

@ -36,9 +36,14 @@ json_ok_str() {
fi fi
} }
if [[ -z "${CONSUL_HTTP_ADDR:-}" ]]; then if [[ -z "${CONSUL_HTTP_ADDR:-}" ]]; then
if [[ "$NS" == "test" ]]; then
CONSUL_HTTP_ADDR="${TEST_CONSUL_HTTP_ADDR:-http://172.16.16.13:30850}"
else
CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')" CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')"
CONSUL_HTTP_ADDR="http://${CIP}:8500" CONSUL_HTTP_ADDR="http://${CIP}:8500"
fi
fi fi
echo "==> Consul ${CONSUL_HTTP_ADDR}"
if [[ -z "${CONSUL_HTTP_TOKEN:-}" ]]; then if [[ -z "${CONSUL_HTTP_TOKEN:-}" ]]; then
raw="$(kubectl -n "$NS" get secret consul-acl -o jsonpath='{.data.CONSUL_HTTP_TOKEN}' 2>/dev/null || true)" raw="$(kubectl -n "$NS" get secret consul-acl -o jsonpath='{.data.CONSUL_HTTP_TOKEN}' 2>/dev/null || true)"

View File

@ -125,24 +125,68 @@ load_secret() {
fi fi
} }
echo "==> 加载密钥(K8s Secret / env)" echo "==> 加载密钥(K8s Secret / env) ns=${NS} kubeconfig=${KUBECONFIG}"
if ! kubectl -n "$NS" get secret prod-credentials >/dev/null 2>&1; then
echo "FAIL: 找不到 secret/${NS}/prod-credentials"
echo " 当前: KUBECONFIG=${KUBECONFIG}"
echo " test 集群请先: kubectl --kubeconfig=/etc/rancher/k3s/k3s-test.yaml apply -f /data/k3s-test/manifests/secrets.yaml"
echo " 或: kubectl -n ${NS} get secret"
exit 1
fi
load_secret MYSQL_ROOT_PASSWORD prod-credentials MYSQL_ROOT_PASSWORD load_secret MYSQL_ROOT_PASSWORD prod-credentials MYSQL_ROOT_PASSWORD
load_secret REDIS_PASSWORD prod-credentials REDIS_PASSWORD load_secret REDIS_PASSWORD prod-credentials REDIS_PASSWORD
load_secret RABBITMQ_DEFAULT_PASS prod-credentials RABBITMQ_DEFAULT_PASS load_secret RABBITMQ_DEFAULT_PASS prod-credentials RABBITMQ_DEFAULT_PASS
load_secret ELASTIC_PASSWORD prod-credentials ELASTIC_PASSWORD load_secret ELASTIC_PASSWORD prod-credentials ELASTIC_PASSWORD
load_secret JWT_SECRET_KEY jwt-secret JWT_SECRET_KEY load_secret JWT_SECRET_KEY jwt-secret JWT_SECRET_KEY
# 兼容:JWT 也放在 prod-credentials
load_secret JWT_SECRET_KEY prod-credentials JWT_SECRET_KEY load_secret JWT_SECRET_KEY prod-credentials JWT_SECRET_KEY
load_secret COS_SECRET_ID prod-credentials COS_SECRET_ID
load_secret COS_SECRET_KEY prod-credentials COS_SECRET_KEY
load_secret FEISHU_WEBHOOK prod-credentials FEISHU_WEBHOOK
load_secret WECHAT_MP_APP_ID prod-credentials WECHAT_MP_APP_ID
load_secret WECHAT_MP_SECRET prod-credentials WECHAT_MP_SECRET
load_secret WECHAT_MP_TOKEN prod-credentials WECHAT_MP_TOKEN
load_secret OMS_TRACK_APP_KEY prod-credentials OMS_TRACK_APP_KEY
load_secret OMS_TRACK_APP_SECRET prod-credentials OMS_TRACK_APP_SECRET
load_secret LAZADA_APP_KEY prod-credentials LAZADA_APP_KEY
load_secret LAZADA_APP_SECRET prod-credentials LAZADA_APP_SECRET
load_secret SHOPEE_PARTNER_ID prod-credentials SHOPEE_PARTNER_ID
load_secret SHOPEE_PARTNER_KEY prod-credentials SHOPEE_PARTNER_KEY
load_secret TIKTOK_APP_KEY prod-credentials TIKTOK_APP_KEY
load_secret TIKTOK_APP_SECRET prod-credentials TIKTOK_APP_SECRET
load_secret TEMU_APP_KEY prod-credentials TEMU_APP_KEY
load_secret TEMU_APP_SECRET prod-credentials TEMU_APP_SECRET
: "${MYSQL_ROOT_PASSWORD:?缺少 MYSQL_ROOT_PASSWORD(env 或 secret/prod-credentials)}" : "${MYSQL_ROOT_PASSWORD:?缺少 MYSQL_ROOT_PASSWORD(env 或 secret/${NS}/prod-credentials)}"
: "${REDIS_PASSWORD:?缺少 REDIS_PASSWORD}" : "${REDIS_PASSWORD:?缺少 REDIS_PASSWORD}"
: "${RABBITMQ_DEFAULT_PASS:?缺少 RABBITMQ_DEFAULT_PASS}" : "${RABBITMQ_DEFAULT_PASS:?缺少 RABBITMQ_DEFAULT_PASS}"
: "${ELASTIC_PASSWORD:?缺少 ELASTIC_PASSWORD}" : "${ELASTIC_PASSWORD:?缺少 ELASTIC_PASSWORD}"
: "${JWT_SECRET_KEY:?缺少 JWT_SECRET_KEY(请创建 secret/jwt-secret 或写入 prod-credentials)}" : "${JWT_SECRET_KEY:?缺少 JWT_SECRET_KEY(请创建 secret/jwt-secret 或写入 prod-credentials)}"
: "${COS_SECRET_ID:?缺少 COS_SECRET_ID}"
: "${COS_SECRET_KEY:?缺少 COS_SECRET_KEY}"
: "${FEISHU_WEBHOOK:?缺少 FEISHU_WEBHOOK}"
: "${WECHAT_MP_APP_ID:?缺少 WECHAT_MP_APP_ID}"
: "${WECHAT_MP_SECRET:?缺少 WECHAT_MP_SECRET}"
: "${WECHAT_MP_TOKEN:?缺少 WECHAT_MP_TOKEN}"
: "${OMS_TRACK_APP_KEY:?缺少 OMS_TRACK_APP_KEY}"
: "${OMS_TRACK_APP_SECRET:?缺少 OMS_TRACK_APP_SECRET}"
: "${LAZADA_APP_KEY:?缺少 LAZADA_APP_KEY}"
: "${LAZADA_APP_SECRET:?缺少 LAZADA_APP_SECRET}"
: "${SHOPEE_PARTNER_ID:?缺少 SHOPEE_PARTNER_ID}"
: "${SHOPEE_PARTNER_KEY:?缺少 SHOPEE_PARTNER_KEY}"
: "${TIKTOK_APP_KEY:?缺少 TIKTOK_APP_KEY}"
: "${TIKTOK_APP_SECRET:?缺少 TIKTOK_APP_SECRET}"
: "${TEMU_APP_KEY:?缺少 TEMU_APP_KEY}"
: "${TEMU_APP_SECRET:?缺少 TEMU_APP_SECRET}"
# Consul:prod 用 ClusterIP(runner 在簇内);test 用内网 NodePort(见 consul-ci / TEST_CONSUL_HTTP_ADDR)
if [[ -z "$CONSUL_HTTP_ADDR" ]]; then if [[ -z "$CONSUL_HTTP_ADDR" ]]; then
if [[ "$NS" == "test" ]]; then
# 安全组:仅放行 CI 机内网 → 测试机 30850;勿对公网开放
CONSUL_HTTP_ADDR="${TEST_CONSUL_HTTP_ADDR:-http://172.16.16.13:30850}"
else
CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')" CIP="$(kubectl -n "$NS" get svc consul -o jsonpath='{.spec.clusterIP}')"
CONSUL_HTTP_ADDR="http://${CIP}:8500" CONSUL_HTTP_ADDR="http://${CIP}:8500"
fi
fi fi
echo "==> Consul ${CONSUL_HTTP_ADDR}" echo "==> Consul ${CONSUL_HTTP_ADDR}"
@ -195,8 +239,15 @@ for name in "${TARGETS[@]}"; do
[[ -f "$src" ]] || { echo "FAIL: 无 ${src}"; exit 1; } [[ -f "$src" ]] || { echo "FAIL: 无 ${src}"; exit 1; }
# 仅替换已知占位符,避免 JSON 里其它 $ 被误伤 # 仅替换已知占位符,避免 JSON 里其它 $ 被误伤
export MYSQL_ROOT_PASSWORD REDIS_PASSWORD RABBITMQ_DEFAULT_PASS ELASTIC_PASSWORD JWT_SECRET_KEY export MYSQL_ROOT_PASSWORD REDIS_PASSWORD RABBITMQ_DEFAULT_PASS ELASTIC_PASSWORD JWT_SECRET_KEY \
envsubst '${MYSQL_ROOT_PASSWORD} ${REDIS_PASSWORD} ${RABBITMQ_DEFAULT_PASS} ${ELASTIC_PASSWORD} ${JWT_SECRET_KEY}' \ COS_SECRET_ID COS_SECRET_KEY FEISHU_WEBHOOK \
WECHAT_MP_APP_ID WECHAT_MP_SECRET WECHAT_MP_TOKEN \
OMS_TRACK_APP_KEY OMS_TRACK_APP_SECRET \
LAZADA_APP_KEY LAZADA_APP_SECRET \
SHOPEE_PARTNER_ID SHOPEE_PARTNER_KEY \
TIKTOK_APP_KEY TIKTOK_APP_SECRET \
TEMU_APP_KEY TEMU_APP_SECRET
envsubst '${MYSQL_ROOT_PASSWORD} ${REDIS_PASSWORD} ${RABBITMQ_DEFAULT_PASS} ${ELASTIC_PASSWORD} ${JWT_SECRET_KEY} ${COS_SECRET_ID} ${COS_SECRET_KEY} ${FEISHU_WEBHOOK} ${WECHAT_MP_APP_ID} ${WECHAT_MP_SECRET} ${WECHAT_MP_TOKEN} ${OMS_TRACK_APP_KEY} ${OMS_TRACK_APP_SECRET} ${LAZADA_APP_KEY} ${LAZADA_APP_SECRET} ${SHOPEE_PARTNER_ID} ${SHOPEE_PARTNER_KEY} ${TIKTOK_APP_KEY} ${TIKTOK_APP_SECRET} ${TEMU_APP_KEY} ${TEMU_APP_SECRET}' \
<"$src" >"${TMP}/${name}.json" <"$src" >"${TMP}/${name}.json"
# 禁止未替换的占位符漏网(注释里的 ${...} 也会拦) # 禁止未替换的占位符漏网(注释里的 ${...} 也会拦)
@ -223,19 +274,22 @@ for name in "${TARGETS[@]}"; do
esac esac
done done
# 暂时关闭:改配置后自动 rollout restart API(需要时把下面取消注释,或 CI 设 RESTART_APIS=1)
# if [[ "$RESTART_APIS" == "1" ]]; then
# readarray -t RESTART_SET < <(printf '%s\n' "${RESTART_SET[@]}" | sort -u)
# echo "==> rollout restart: ${RESTART_SET[*]}"
# for d in "${RESTART_SET[@]}"; do
# [[ -n "$d" ]] || continue
# if kubectl -n "$NS" get "deploy/${d}" >/dev/null 2>&1; then
# kubectl -n "$NS" rollout restart "deploy/${d}"
# kubectl -n "$NS" rollout status "deploy/${d}" --timeout=180s || true
# else
# echo "SKIP deploy/${d}(不存在)"
# fi
# done
# fi
if [[ "$RESTART_APIS" == "1" ]]; then if [[ "$RESTART_APIS" == "1" ]]; then
# 去重 echo "==> SKIP rollout restart(已暂时关闭;RESTART_APIS=1 被忽略)"
readarray -t RESTART_SET < <(printf '%s\n' "${RESTART_SET[@]}" | sort -u)
echo "==> rollout restart: ${RESTART_SET[*]}"
for d in "${RESTART_SET[@]}"; do
[[ -n "$d" ]] || continue
if kubectl -n "$NS" get "deploy/${d}" >/dev/null 2>&1; then
kubectl -n "$NS" rollout restart "deploy/${d}"
kubectl -n "$NS" rollout status "deploy/${d}" --timeout=180s || true
else
echo "SKIP deploy/${d}(不存在)"
fi
done
fi fi
echo "DONE" echo "DONE"