Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c03996f0be | ||
|
|
7d8cab609d | ||
|
|
75723148ff | ||
|
|
5b254b424c | ||
|
|
291aeeef2f | ||
|
|
f8ca3f2e4e | ||
|
|
880d47e92c | ||
|
|
7354b26434 | ||
|
|
3da1c2a4de | ||
|
|
18e9396cf5 | ||
|
|
e3bd94330c | ||
|
|
8bbd8bee1d | ||
|
|
79f846bdf7 | ||
|
|
3a63d7422b | ||
|
|
f6bb371d0c | ||
|
|
65c864e97c | ||
|
|
d403be1089 | ||
|
|
b32843470d | ||
|
|
bcedcb2931 | ||
|
|
1ea8e86112 | ||
|
|
15bc920c82 | ||
|
|
5d2f9d92b1 | ||
|
|
9851e02574 | ||
|
|
990a095f7d | ||
|
|
b1b4d041c9 | ||
|
|
5d6acdddb8 | ||
|
|
f2f26ba434 | ||
|
|
d8d404a617 | ||
|
|
b7177392bf | ||
|
|
024ab2ccd4 | ||
|
|
b0b0aa42b0 | ||
|
|
2570e4d18f | ||
|
|
2465fc77e1 | ||
|
|
35827fe379 | ||
|
|
c8a059d671 | ||
|
|
89c06e3f5b | ||
|
|
d134c11d44 |
@@ -1 +0,0 @@
|
||||
client/data/mysql/mysql.sock
|
||||
@@ -1,66 +0,0 @@
|
||||
name: deploy-repo-gate
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches-ignore: [gh-pages]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
|
||||
jobs:
|
||||
gate:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Detect changed files
|
||||
id: changed
|
||||
run: |
|
||||
git fetch --no-tags --prune --depth=1 origin "${{ github.base_ref }}"
|
||||
git diff --name-only "origin/${{ github.base_ref }}...HEAD" > .changed-files.txt
|
||||
cat .changed-files.txt
|
||||
|
||||
- name: Enforce allowed directories (unless platform-admin label)
|
||||
env:
|
||||
GITHUB_EVENT_PATH: ${{ github.event_path }}
|
||||
run: |
|
||||
python3 - <<'PY'
|
||||
import json, pathlib, sys
|
||||
|
||||
allowed = (
|
||||
'envs/', 'release/', 'evidence/', 'docs/', 'scripts/', 'pages/', '.github/', 'k8s/'
|
||||
)
|
||||
changed = [line.strip() for line in pathlib.Path('.changed-files.txt').read_text(encoding='utf-8').splitlines() if line.strip()]
|
||||
event = json.loads(pathlib.Path('${{ github.event_path }}').read_text(encoding='utf-8'))
|
||||
labels = {label.get('name','') for label in event.get('pull_request', {}).get('labels', [])}
|
||||
|
||||
disallowed = [p for p in changed if not p.startswith(allowed)]
|
||||
if disallowed and 'platform-admin' not in labels:
|
||||
print('Disallowed changes detected (missing platform-admin label):')
|
||||
for p in disallowed:
|
||||
print('-', p)
|
||||
sys.exit(1)
|
||||
|
||||
print('Path gate passed.')
|
||||
PY
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install deps
|
||||
run: pip install --disable-pip-version-check pyyaml jsonschema
|
||||
|
||||
- name: Validate release queue structure
|
||||
run: python3 scripts/promoter/validate_queue.py
|
||||
|
||||
- name: Evidence validate + collect (dry run)
|
||||
run: |
|
||||
python3 scripts/evidence/validate.py
|
||||
python3 scripts/evidence/collect.py --out /tmp/evidence-index.json --summary /tmp/evidence-latest.md
|
||||
@@ -1,36 +0,0 @@
|
||||
name: nightly-auto-repair
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [gh-pages]
|
||||
paths-ignore: ['**']
|
||||
schedule:
|
||||
# 01:30 Asia/Tokyo (JST) = 16:30 UTC previous day
|
||||
- cron: "30 16 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
repair:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Run auto-repair loop
|
||||
run: |
|
||||
bash scripts/repair/run.sh \
|
||||
--allow-main \
|
||||
--check-cmd "bash scripts/ci/run_checks.sh" \
|
||||
--max-attempts 3 \
|
||||
--recipes repairs/recipes.yaml \
|
||||
--issue-repo "${{ github.repository }}"
|
||||
@@ -1,82 +0,0 @@
|
||||
name: nightly-evidence
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore: [gh-pages]
|
||||
paths-ignore: ['**']
|
||||
schedule:
|
||||
# 02:00 Asia/Tokyo (JST) = 17:00 UTC previous day
|
||||
- cron: "0 17 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: nightly-evidence-publish
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout main
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: main
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Setup Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.11"
|
||||
|
||||
- name: Install deps
|
||||
run: pip install --disable-pip-version-check pyyaml jsonschema
|
||||
|
||||
- name: Validate and collect evidence
|
||||
run: |
|
||||
python3 scripts/evidence/validate.py
|
||||
python3 scripts/evidence/collect.py --out evidence/index.json --summary evidence/summary/latest.md
|
||||
|
||||
- name: Build publish payload (atomic staging)
|
||||
run: |
|
||||
rm -rf .publish-tmp
|
||||
mkdir -p .publish-tmp/evidence/summary
|
||||
cp pages/index.html pages/app.js .publish-tmp/
|
||||
cp evidence/index.json .publish-tmp/evidence/index.json
|
||||
cp evidence/summary/latest.md .publish-tmp/evidence/summary/latest.md
|
||||
python3 - <<'PY'
|
||||
import json, pathlib
|
||||
feed = pathlib.Path('.publish-tmp/evidence/index.json')
|
||||
data = json.loads(feed.read_text(encoding='utf-8'))
|
||||
if not isinstance(data, list):
|
||||
raise SystemExit('evidence/index.json must be an array')
|
||||
print(f'staged records: {len(data)}')
|
||||
PY
|
||||
|
||||
- name: Publish to gh-pages branch
|
||||
run: |
|
||||
set -euo pipefail
|
||||
rm -rf .gh-pages-wt
|
||||
if git ls-remote --exit-code --heads origin gh-pages >/dev/null 2>&1; then
|
||||
git worktree add --force -B gh-pages .gh-pages-wt origin/gh-pages
|
||||
else
|
||||
git worktree add --force -B gh-pages .gh-pages-wt
|
||||
fi
|
||||
|
||||
rsync -a --delete .publish-tmp/ .gh-pages-wt/
|
||||
|
||||
cd .gh-pages-wt
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git add -A
|
||||
if git diff --cached --quiet; then
|
||||
echo "No changes to publish"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
git commit -m "pages: update evidence feed [skip ci]"
|
||||
git push origin HEAD:gh-pages
|
||||
@@ -1,78 +0,0 @@
|
||||
# End-to-End Dry Run (Bid-MVP Factory)
|
||||
|
||||
This dry run validates docs gates, evidence feed, promoter, smoke runner, and repair loop without secrets.
|
||||
|
||||
## 1. Docs Repo Checks (`ljwx-docs`)
|
||||
|
||||
```bash
|
||||
cd ../ljwx-docs
|
||||
test -f PLAN.md
|
||||
rg --files docs/factory docs/templates .github/PULL_REQUEST_TEMPLATE
|
||||
rg -n "Bid-MVP Factory|/factory/|/templates/" docs/.vitepress/config.ts
|
||||
```
|
||||
|
||||
Note: `npm run docs:build` currently fails on existing repo dead links unrelated to this implementation.
|
||||
|
||||
## 2. Deploy Repo Checks (`ljwx-deploy`)
|
||||
|
||||
```bash
|
||||
cd ../ljwx-deploy
|
||||
test -f PLAN.md
|
||||
python3 scripts/evidence/validate.py
|
||||
python3 scripts/evidence/collect.py --out evidence/index.json
|
||||
python3 -m json.tool evidence/index.json >/dev/null
|
||||
```
|
||||
|
||||
## 3. Promoter Dry Run
|
||||
|
||||
```bash
|
||||
python3 scripts/promoter/validate_queue.py
|
||||
bash scripts/promoter/promote.sh --dry-run
|
||||
```
|
||||
|
||||
Expected: script reports normalization/promotion simulation without commit/push.
|
||||
|
||||
## 4. Smoke Runner Dry Run
|
||||
|
||||
```bash
|
||||
python3 scripts/smoke/run_smoke.py --help
|
||||
python3 scripts/smoke/run_smoke.py --dry-run
|
||||
```
|
||||
|
||||
Expected: skips targets when no promoted records exist yet.
|
||||
|
||||
## 5. Repair Loop Dry Run
|
||||
|
||||
```bash
|
||||
python3 scripts/repair/run_repair.py --help
|
||||
python3 scripts/repair/diagnose.py --help
|
||||
python3 scripts/repair/run_repair.py \
|
||||
--recipes repairs/recipes.yaml \
|
||||
--check-cmd "bash scripts/ci/run_checks.sh" \
|
||||
--max-attempts 1 \
|
||||
--log-dir .factory/repair/dryrun \
|
||||
--dry-run
|
||||
bash scripts/repair/run.sh \
|
||||
--allow-main \
|
||||
--max-attempts 1 \
|
||||
--check-cmd "bash scripts/ci/run_checks.sh" \
|
||||
--recipes repairs/recipes.yaml \
|
||||
--dry-run
|
||||
```
|
||||
|
||||
Expected: check passes or repair actions/logs are produced in `.factory/repair/dryrun`.
|
||||
|
||||
## 6. Pages Publish Workflow (Manual)
|
||||
|
||||
- Trigger workflow: `evidence-pages` (`workflow_dispatch`).
|
||||
- Confirm `gh-pages` branch is updated by bot commit.
|
||||
- Open dashboard URL: `https://brunogaosz.github.io/ljwx-deploy/`.
|
||||
- Verify `Smoke` and `Promotion` columns render from `evidence/index.json`.
|
||||
|
||||
## 7. Full Dev Autopilot (Optional)
|
||||
|
||||
```bash
|
||||
bash scripts/factory/run_all_dev.sh
|
||||
```
|
||||
|
||||
Requires authenticated `gh` CLI and permissions to create/merge PRs and issues.
|
||||
+20
-7
@@ -1,6 +1,8 @@
|
||||
(function () {
|
||||
const FEED_URL = "./evidence/index.json";
|
||||
const METRICS_URL = "./evidence/metrics/queue-health.json";
|
||||
let allRecords = [];
|
||||
let queueMetrics = null;
|
||||
|
||||
const envFilter = document.getElementById("env-filter");
|
||||
const testFilter = document.getElementById("test-filter");
|
||||
@@ -11,11 +13,11 @@
|
||||
return String(v ?? "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
||||
}
|
||||
|
||||
function digestFromHarbor(harbor) {
|
||||
if (!harbor) return "-";
|
||||
const idx = harbor.indexOf("@sha256:");
|
||||
if (idx === -1) return harbor;
|
||||
return "sha256:" + harbor.slice(idx + 8, idx + 8 + 16);
|
||||
function digestFromImage(imageRef) {
|
||||
if (!imageRef) return "-";
|
||||
const idx = imageRef.indexOf("@sha256:");
|
||||
if (idx === -1) return imageRef;
|
||||
return "sha256:" + imageRef.slice(idx + 8, idx + 8 + 16);
|
||||
}
|
||||
|
||||
function smokeStatus(rec) {
|
||||
@@ -57,14 +59,17 @@
|
||||
<td><code>${text(r.evidenceId || "-")}</code></td>
|
||||
<td>${text(r.service || "-")}</td>
|
||||
<td>${text(r.env || "-")}</td>
|
||||
<td><code>${text(digestFromHarbor(r?.image?.harbor || ""))}</code></td>
|
||||
<td><code>${text(digestFromImage(r?.image?.deployed || r?.image?.harbor || ""))}</code></td>
|
||||
<td>${text(r?.deploy?.syncedAt || r?.promotedAt || "-")}</td>
|
||||
<td><span class="pill ${klass}">${text(smoke)}</span></td>
|
||||
<td>${linksHtml(r)}</td>
|
||||
</tr>`;
|
||||
}).join("") || '<tr><td colspan="7">No records</td></tr>';
|
||||
|
||||
metaEl.textContent = `records: ${filtered.length} / ${allRecords.length}`;
|
||||
const queueMeta = queueMetrics?.counts
|
||||
? ` | queue pending=${queueMetrics.counts.pending}, failed=${queueMetrics.counts.failed}, superseded=${queueMetrics.counts.superseded}`
|
||||
: "";
|
||||
metaEl.textContent = `records: ${filtered.length} / ${allRecords.length}${queueMeta}`;
|
||||
}
|
||||
|
||||
function fillEnvOptions() {
|
||||
@@ -85,6 +90,14 @@
|
||||
}
|
||||
allRecords = await res.json();
|
||||
if (!Array.isArray(allRecords)) allRecords = [];
|
||||
|
||||
const metricsRes = await fetch(METRICS_URL, { cache: "no-store" });
|
||||
if (metricsRes.ok) {
|
||||
const payload = await metricsRes.json();
|
||||
if (payload && typeof payload === "object") {
|
||||
queueMetrics = payload;
|
||||
}
|
||||
}
|
||||
fillEnvOptions();
|
||||
render();
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: agent-platform
|
||||
labels:
|
||||
app: agent-platform
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: agent-platform
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: agent-platform
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: agent-platform
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx/agent-platform-api:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
env:
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-platform-db
|
||||
key: DATABASE_URL
|
||||
- name: REDIS_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-platform-app
|
||||
key: REDIS_URL
|
||||
- name: ANTHROPIC_AUTH_TOKEN
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-platform-app
|
||||
key: ANTHROPIC_AUTH_TOKEN
|
||||
- name: JWT_SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: agent-platform-app
|
||||
key: JWT_SECRET_KEY
|
||||
- name: MILVUS_HOST
|
||||
value: ""
|
||||
- name: MILVUS_PORT
|
||||
value: "19530"
|
||||
- name: APP_ENV
|
||||
value: "prod"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: http
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "1Gi"
|
||||
@@ -1,25 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: agent-platform
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- agent.lingjingwanxiang.cn
|
||||
secretName: agent-platform-tls
|
||||
rules:
|
||||
- host: agent.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: agent-platform
|
||||
port:
|
||||
number: 8000
|
||||
@@ -1,9 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- secret.yaml
|
||||
- regcred.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: regcred
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuZXUubGluZ2ppbmd3YW54aWFuZy5jbiI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifX19
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: agent-platform-db
|
||||
type: Opaque
|
||||
stringData:
|
||||
DATABASE_URL: postgresql+asyncpg://agent:ChangeMeInProduction@postgres-lb.infra.svc.cluster.local:5432/agent_platform
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: agent-platform-app
|
||||
type: Opaque
|
||||
stringData:
|
||||
REDIS_URL: redis://redis-lb.infra.svc.cluster.local:6379/0
|
||||
ANTHROPIC_AUTH_TOKEN: CHANGE_ME
|
||||
JWT_SECRET_KEY: CHANGE_ME
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: agent-platform
|
||||
labels:
|
||||
app: agent-platform
|
||||
spec:
|
||||
selector:
|
||||
app: agent-platform
|
||||
ports:
|
||||
- name: http
|
||||
port: 8000
|
||||
targetPort: http
|
||||
type: ClusterIP
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: agent-platform
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx/agent-platform-api
|
||||
newTag: sha-dec280a
|
||||
@@ -1,62 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: backend
|
||||
labels:
|
||||
app: backend
|
||||
app.kubernetes.io/name: backend
|
||||
app.kubernetes.io/part-of: youngth-guard
|
||||
component: api
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: backend
|
||||
app.kubernetes.io/name: backend
|
||||
app.kubernetes.io/part-of: youngth-guard
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: backend
|
||||
app.kubernetes.io/name: backend
|
||||
app.kubernetes.io/part-of: youngth-guard
|
||||
component: api
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: harbor-registry
|
||||
containers:
|
||||
- name: backend
|
||||
image: harbor.omniverseai.net/ljwx/ljwx-shiti:sha-775861d
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: ENVIRONMENT
|
||||
value: "dev"
|
||||
- name: LOG_LEVEL
|
||||
value: "info"
|
||||
- name: DATABASE_URL
|
||||
value: "postgresql+asyncpg://appuser:123456@postgres-infra-postgresql.infra.svc.cluster.local:5432/appdb"
|
||||
- name: REDIS_URL
|
||||
value: "redis://:123456@redis-infra-master.infra.svc.cluster.local:6379/0"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 128Mi
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
@@ -1,6 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: backend
|
||||
labels:
|
||||
app: backend
|
||||
app.kubernetes.io/name: backend
|
||||
app.kubernetes.io/part-of: youngth-guard
|
||||
component: api
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app: backend
|
||||
app.kubernetes.io/name: backend
|
||||
app.kubernetes.io/part-of: youngth-guard
|
||||
ports:
|
||||
- name: http
|
||||
port: 8000
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: dev
|
||||
namePrefix: gh-
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
@@ -1,326 +0,0 @@
|
||||
# ljwx-bookstore GitOps 配置
|
||||
|
||||
智能小说阅读平台的 Kubernetes GitOps 部署配置,使用 Kustomize 管理。
|
||||
|
||||
## 📋 概览
|
||||
|
||||
- **应用名称**: ljwx-bookstore
|
||||
- **代码仓库**: https://github.com/BrunoGaoSZ/ljwx-bookstore
|
||||
- **GitOps 仓库**: https://github.com/BrunoGaoSZ/ljwx-deploy
|
||||
- **命名空间**: bookstore
|
||||
- **配置管理**: Kustomize
|
||||
- **GitOps 控制器**: Argo CD
|
||||
|
||||
## 🚀 快速部署
|
||||
|
||||
### 前置条件
|
||||
|
||||
1. **Kubernetes 集群**: K3s 或其他 Kubernetes 集群
|
||||
2. **Argo CD**: 已安装并配置
|
||||
3. **基础设施**:
|
||||
- MySQL 8.0 (infra namespace)
|
||||
- Redis 7.2.12 (ljwx-health namespace)
|
||||
- Traefik Ingress Controller
|
||||
- cert-manager + Let's Encrypt
|
||||
|
||||
### 创建必需的 Secrets
|
||||
|
||||
```bash
|
||||
# 1. 创建命名空间
|
||||
kubectl create namespace bookstore
|
||||
|
||||
# 2. GHCR 镜像拉取凭证
|
||||
kubectl create secret docker-registry ghcr-secret \
|
||||
--docker-server=ghcr.io \
|
||||
--docker-username=<github-username> \
|
||||
--docker-password=<github-token> \
|
||||
-n bookstore
|
||||
|
||||
# 3. Redis 连接配置
|
||||
kubectl create secret generic redis-secret \
|
||||
--from-literal=host=redis.ljwx-health.svc.cluster.local \
|
||||
--from-literal=port=6379 \
|
||||
--from-literal=password=<redis-password> \
|
||||
-n bookstore
|
||||
```
|
||||
|
||||
### 部署应用
|
||||
|
||||
```bash
|
||||
# 方法 1: 使用 Argo CD Application CRD
|
||||
kubectl apply -f argocd-apps/40-ljwx-bookstore.yaml
|
||||
|
||||
# 方法 2: 使用 Argo CD CLI
|
||||
argocd app create ljwx-bookstore \
|
||||
--repo https://github.com/BrunoGaoSZ/ljwx-deploy.git \
|
||||
--path apps/ljwx-bookstore/overlays/bookstore \
|
||||
--dest-server https://kubernetes.default.svc \
|
||||
--dest-namespace bookstore \
|
||||
--sync-policy automated \
|
||||
--auto-prune \
|
||||
--self-heal
|
||||
|
||||
# 查看部署状态
|
||||
argocd app get ljwx-bookstore
|
||||
argocd app sync ljwx-bookstore
|
||||
```
|
||||
|
||||
## 📁 目录结构
|
||||
|
||||
```
|
||||
apps/ljwx-bookstore/
|
||||
├── base/ # 基础配置
|
||||
│ ├── deployment.yaml # Deployment 定义
|
||||
│ ├── service.yaml # Service 定义
|
||||
│ ├── ingress.yaml # Ingress + Middleware(HTTPS + 根路径重定向)
|
||||
│ ├── ghcr-secret.yaml # GHCR 拉取凭证引用
|
||||
│ ├── redis-secret.yaml # Redis 连接配置引用
|
||||
│ └── kustomization.yaml # Kustomize 基础配置
|
||||
│
|
||||
└── overlays/ # 环境特定配置
|
||||
└── bookstore/ # bookstore 环境
|
||||
└── kustomization.yaml # 镜像版本配置(自动更新)
|
||||
```
|
||||
|
||||
## ⚙️ 配置说明
|
||||
|
||||
### Deployment 配置
|
||||
|
||||
**文件**: `base/deployment.yaml`
|
||||
|
||||
- **副本数**: 2
|
||||
- **镜像**: `ghcr.io/brunogaosz/ljwx-bookstore/bookstore:bookstore-<commit>`
|
||||
- **端口**: 8080
|
||||
- **环境变量**:
|
||||
- `SPRING_DATASOURCE_URL`: MySQL 连接(infra namespace)
|
||||
- `SPRING_DATASOURCE_USERNAME`: bookstore
|
||||
- `SPRING_DATASOURCE_PASSWORD`: 从 mysql-standalone Secret 获取
|
||||
- `SPRING_DATA_REDIS_HOST`: 从 redis-secret 获取
|
||||
- `SPRING_DATA_REDIS_PORT`: 从 redis-secret 获取
|
||||
- `SPRING_DATA_REDIS_PASSWORD`: 从 redis-secret 获取
|
||||
- **健康检查**:
|
||||
- Liveness Probe: `/fiction/actuator/health/liveness`
|
||||
- Readiness Probe: `/fiction/actuator/health/readiness`
|
||||
|
||||
### Service 配置
|
||||
|
||||
**文件**: `base/service.yaml`
|
||||
|
||||
- **类型**: ClusterIP
|
||||
- **端口**: 8080 → 8080
|
||||
|
||||
### Ingress 配置
|
||||
|
||||
**文件**: `base/ingress.yaml`
|
||||
|
||||
- **Ingress Controller**: Traefik
|
||||
- **域名**: bookstore.lingjingwanxiang.cn
|
||||
- **功能**:
|
||||
- ✅ HTTPS 自动重定向
|
||||
- ✅ Let's Encrypt 证书自动签发(cert-manager)
|
||||
- ✅ 根路径(`/`)自动重定向到 `/fiction/index`
|
||||
- **TLS Secret**: bookstore-lingjingwanxiang-tls
|
||||
- **ClusterIssuer**: dnspod-letsencrypt
|
||||
|
||||
**Traefik Middleware**: 实现根路径重定向
|
||||
```yaml
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect-root
|
||||
namespace: bookstore
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https?://[^/]+/$"
|
||||
replacement: "https://bookstore.lingjingwanxiang.cn/fiction/index"
|
||||
permanent: false
|
||||
```
|
||||
|
||||
### Kustomize 配置
|
||||
|
||||
**基础配置** (`base/kustomization.yaml`):
|
||||
```yaml
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- ghcr-secret.yaml
|
||||
- redis-secret.yaml
|
||||
```
|
||||
|
||||
**环境配置** (`overlays/bookstore/kustomization.yaml`):
|
||||
```yaml
|
||||
resources:
|
||||
- ../../base
|
||||
|
||||
namespace: bookstore
|
||||
|
||||
images:
|
||||
- name: ghcr.io/brunogaosz/ljwx-bookstore/bookstore
|
||||
newName: ghcr.io/brunogaosz/ljwx-bookstore/bookstore
|
||||
newTag: "bookstore-<commit-sha>" # 由 CI 自动更新
|
||||
```
|
||||
|
||||
## 🔄 GitOps 工作流程
|
||||
|
||||
### 自动化部署流程
|
||||
|
||||
1. **代码提交** → GitHub (ljwx-bookstore)
|
||||
2. **GitHub Actions CI**:
|
||||
- 构建 Docker 镜像(多架构:linux/amd64, linux/arm64)
|
||||
- 推送到 GHCR: `ghcr.io/brunogaosz/ljwx-bookstore/bookstore:bookstore-<commit>`
|
||||
3. **GitOps 更新**:
|
||||
- CI 自动创建 PR 到 ljwx-deploy 仓库
|
||||
- 更新 `overlays/bookstore/kustomization.yaml` 中的镜像标签
|
||||
4. **PR 合并** → main 分支
|
||||
5. **Argo CD 同步**:
|
||||
- 检测 Git 仓库变更(轮询间隔:3 分钟)
|
||||
- 自动同步到 Kubernetes 集群
|
||||
6. **滚动更新**:
|
||||
- Kubernetes 执行零停机滚动更新
|
||||
- 新 Pod 启动 → 健康检查通过 → 旧 Pod 终止
|
||||
|
||||
### 手动更新配置
|
||||
|
||||
```bash
|
||||
# 1. 克隆 GitOps 仓库
|
||||
git clone https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
cd ljwx-deploy
|
||||
|
||||
# 2. 修改配置
|
||||
vim apps/ljwx-bookstore/base/deployment.yaml
|
||||
|
||||
# 3. 提交变更
|
||||
git add .
|
||||
git commit -m "feat: update bookstore configuration"
|
||||
git push
|
||||
|
||||
# 4. Argo CD 自动同步(~3 分钟)
|
||||
# 或手动触发同步
|
||||
argocd app sync ljwx-bookstore
|
||||
```
|
||||
|
||||
## 📊 监控和验证
|
||||
|
||||
### 查看部署状态
|
||||
|
||||
```bash
|
||||
# Argo CD 应用状态
|
||||
argocd app get ljwx-bookstore
|
||||
|
||||
# Kubernetes 资源状态
|
||||
kubectl get all -n bookstore
|
||||
|
||||
# Pod 日志
|
||||
kubectl logs -f -n bookstore -l app=bookstore
|
||||
|
||||
# Ingress 状态
|
||||
kubectl get ingress -n bookstore
|
||||
kubectl describe ingress bookstore-ingress -n bookstore
|
||||
```
|
||||
|
||||
### 健康检查
|
||||
|
||||
```bash
|
||||
# 应用健康检查
|
||||
curl https://bookstore.lingjingwanxiang.cn/fiction/actuator/health
|
||||
|
||||
# 预期响应
|
||||
{
|
||||
"status": "UP",
|
||||
"components": {
|
||||
"db": {"status": "UP"},
|
||||
"redis": {"status": "UP"},
|
||||
"diskSpace": {"status": "UP"},
|
||||
"livenessState": {"status": "UP"},
|
||||
"readinessState": {"status": "UP"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 访问测试
|
||||
|
||||
```bash
|
||||
# 测试根路径重定向
|
||||
curl -I https://bookstore.lingjingwanxiang.cn/
|
||||
# 预期: HTTP 307 重定向到 /fiction/index
|
||||
|
||||
# 访问应用
|
||||
curl -L https://bookstore.lingjingwanxiang.cn/
|
||||
# 预期: HTTP 200,返回首页 HTML
|
||||
```
|
||||
|
||||
## 🔧 故障排查
|
||||
|
||||
### Pod 无法启动
|
||||
|
||||
```bash
|
||||
# 查看 Pod 状态
|
||||
kubectl get pods -n bookstore
|
||||
|
||||
# 查看 Pod 事件
|
||||
kubectl describe pod <pod-name> -n bookstore
|
||||
|
||||
# 查看日志
|
||||
kubectl logs <pod-name> -n bookstore
|
||||
|
||||
# 常见问题:
|
||||
# 1. 镜像拉取失败 → 检查 ghcr-secret
|
||||
# 2. 健康检查失败 → 检查 MySQL/Redis 连接
|
||||
# 3. ConfigMap/Secret 不存在 → 检查依赖资源
|
||||
```
|
||||
|
||||
### Ingress 无法访问
|
||||
|
||||
```bash
|
||||
# 检查 Ingress 资源
|
||||
kubectl get ingress -n bookstore
|
||||
kubectl describe ingress bookstore-ingress -n bookstore
|
||||
|
||||
# 检查 TLS 证书
|
||||
kubectl get certificate -n bookstore
|
||||
kubectl describe certificate -n bookstore
|
||||
|
||||
# 检查 Traefik 日志
|
||||
kubectl logs -n kube-system -l app.kubernetes.io/name=traefik
|
||||
```
|
||||
|
||||
### Argo CD 同步失败
|
||||
|
||||
```bash
|
||||
# 查看同步状态
|
||||
argocd app get ljwx-bookstore
|
||||
|
||||
# 查看同步日志
|
||||
argocd app logs ljwx-bookstore
|
||||
|
||||
# 手动同步
|
||||
argocd app sync ljwx-bookstore --force
|
||||
|
||||
# 常见问题:
|
||||
# 1. Git 仓库无法访问 → 检查网络和认证
|
||||
# 2. Kustomize 配置错误 → 检查 YAML 语法
|
||||
# 3. 资源冲突 → 使用 --force 强制同步
|
||||
```
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- **代码仓库**: https://github.com/BrunoGaoSZ/ljwx-bookstore
|
||||
- **应用访问**: https://bookstore.lingjingwanxiang.cn
|
||||
- **Argo CD 指南**: [docs/argocd-migration/README.md](../../docs/argocd-migration/README.md)
|
||||
- **Flyway 迁移**: [docs/flyway-guide/README.md](../../docs/flyway-guide/README.md)
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
1. **永远不要直接修改 Kubernetes 资源** - 所有变更通过 Git 提交
|
||||
2. **使用语义化提交信息** - 便于追踪变更历史
|
||||
3. **环境隔离** - 使用 Kustomize overlays 管理不同环境
|
||||
4. **自动化测试** - CI 流程包含镜像构建和推送验证
|
||||
5. **监控告警** - 配置 Prometheus + Grafana 监控(计划中)
|
||||
|
||||
## 📝 变更历史
|
||||
|
||||
- **2025-12-20**: 配置 HTTPS Ingress + 根路径重定向
|
||||
- **2025-12-19**: 完成 GitOps 自动部署流程
|
||||
- **2025-12-18**: 配置 MySQL 和 Redis 连接
|
||||
- **2025-12-17**: 初始化 Kustomize 配置
|
||||
@@ -1,79 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: bookstore
|
||||
labels:
|
||||
app: bookstore
|
||||
managed-by: argocd
|
||||
project: ljwx-bookstore
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: bookstore
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: bookstore
|
||||
project: ljwx-bookstore
|
||||
version: v0.1.4
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-secret
|
||||
containers:
|
||||
- name: bookstore
|
||||
image: ghcr.io/brunogaosz/ljwx-bookstore/bookstore:bookstore-latest
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: "prod"
|
||||
- name: SERVER_PORT
|
||||
value: "8080"
|
||||
# Database configuration from infra Postgres
|
||||
- name: SPRING_DATASOURCE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bookstore-db-credentials
|
||||
key: SPRING_DATASOURCE_URL
|
||||
- name: SPRING_DATASOURCE_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bookstore-db-credentials
|
||||
key: SPRING_DATASOURCE_USERNAME
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: bookstore-db-credentials
|
||||
key: SPRING_DATASOURCE_PASSWORD
|
||||
# Redis configuration
|
||||
- name: SPRING_DATA_REDIS_HOST
|
||||
value: "redis-service.ljwx-health.svc.cluster.local"
|
||||
- name: SPRING_DATA_REDIS_PORT
|
||||
value: "6379"
|
||||
- name: SPRING_DATA_REDIS_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: redis-credentials
|
||||
key: REDIS_PASSWORD
|
||||
- name: SPRING_DATA_REDIS_DATABASE
|
||||
value: "0"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /fiction/actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /fiction/actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ghcr-secret
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: eyJhdXRocyI6eyJnaGNyLmlvIjp7InVzZXJuYW1lIjoiQnJ1bm9HYW9TWiIsInBhc3N3b3JkIjoiZ2hwX05UVVNwTW9kOURhcXAxeGxCTWliOGxnV09WMUJidTF5MjJ4cyIsImVtYWlsIjoieW91ci1lbWFpbEBleGFtcGxlLmNvbSIsImF1dGgiOiJRbkoxYm05SFlXOVRXanBuYUhCZlRsUlZVM0JOYjJRNVJHRnhjREY0YkVKTmFXSTRiR2RYVDFZeFFtSjFNWGt5TW5oeiJ9fX0=
|
||||
@@ -1,41 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: bookstore-ingress
|
||||
namespace: bookstore
|
||||
annotations:
|
||||
# Traefik annotations
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
traefik.ingress.kubernetes.io/redirect-to-https: "true"
|
||||
# Redirect root path to /fiction/index
|
||||
traefik.ingress.kubernetes.io/router.middlewares: bookstore-redirect-root@kubernetescrd
|
||||
# cert-manager annotation for automatic TLS certificate
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- bookstore.lingjingwanxiang.cn
|
||||
secretName: bookstore-lingjingwanxiang-tls
|
||||
rules:
|
||||
- host: bookstore.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: bookstore
|
||||
port:
|
||||
number: 8080
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: redirect-root
|
||||
namespace: bookstore
|
||||
spec:
|
||||
redirectRegex:
|
||||
regex: "^https?://[^/]+/$"
|
||||
replacement: "https://bookstore.lingjingwanxiang.cn/fiction/index"
|
||||
permanent: false
|
||||
@@ -1,6 +0,0 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ingress.yaml
|
||||
- ghcr-secret.yaml
|
||||
- redis-secret.yaml
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: redis-credentials
|
||||
type: Opaque
|
||||
stringData:
|
||||
REDIS_PASSWORD: "123456"
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: bookstore
|
||||
spec:
|
||||
selector:
|
||||
app: bookstore
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
type: ClusterIP
|
||||
@@ -1,11 +0,0 @@
|
||||
# ljwx-bookstore 通用配置(跨环境共享)
|
||||
|
||||
# 通用标签
|
||||
commonLabels:
|
||||
project: ljwx-bookstore
|
||||
managed-by: argocd
|
||||
app: bookstore
|
||||
|
||||
# 通用注解
|
||||
commonAnnotations:
|
||||
maintained-by: ljwx-team
|
||||
@@ -1,7 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
namespace: bookstore
|
||||
images:
|
||||
- name: ghcr.io/brunogaosz/ljwx-bookstore/bookstore
|
||||
newName: ghcr.io/brunogaosz/ljwx-bookstore/bookstore
|
||||
newTag: "bookstore-d917045"
|
||||
@@ -1,76 +0,0 @@
|
||||
# Bookstore 环境配置
|
||||
|
||||
# 副本数
|
||||
replicaCount: 2
|
||||
|
||||
# 镜像配置
|
||||
image:
|
||||
repository: ghcr.io/brunogaosz/ljwx-bookstore/bookstore
|
||||
tag: "main-latest" # CI 会更新为 Git SHA
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
# 服务配置
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
|
||||
# Ingress 配置
|
||||
ingress:
|
||||
enabled: true
|
||||
className: "nginx"
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
|
||||
hosts:
|
||||
- host: bookstore.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
tls:
|
||||
- secretName: bookstore-tls
|
||||
hosts:
|
||||
- bookstore.example.com
|
||||
|
||||
# 资源限制
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
# 健康检查
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health
|
||||
port: 8080
|
||||
initialDelaySeconds: 120
|
||||
periodSeconds: 30
|
||||
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 8080
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
|
||||
# 数据库配置(使用 infra postgres)
|
||||
postgresql:
|
||||
enabled: false # 使用外部数据库
|
||||
|
||||
# Redis 配置
|
||||
redis:
|
||||
enabled: false # 使用外部 Redis
|
||||
|
||||
# OpenSearch 配置
|
||||
opensearch:
|
||||
enabled: false # 使用外部 OpenSearch
|
||||
|
||||
# 环境变量(通过 Secret 注入)
|
||||
env:
|
||||
SPRING_PROFILES_ACTIVE: "prod"
|
||||
SERVER_PORT: "8080"
|
||||
# 数据库连接将通过 Secret 注入
|
||||
@@ -1,530 +0,0 @@
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ljwx-health-device-approval-config
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-boot
|
||||
component: device-approval
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
data:
|
||||
DEVICE_APPROVAL_AUTO_CLEANUP_DAYS: '30'
|
||||
DEVICE_APPROVAL_BATCH_SIZE_LIMIT: '100'
|
||||
DEVICE_APPROVAL_TIMEOUT_HOURS: '72'
|
||||
DEVICE_APPROVAL_EVENT_THREAD_POOL_SIZE: '10'
|
||||
DEVICE_APPROVAL_EVENT_QUEUE_CAPACITY: '100'
|
||||
DEVICE_APPROVAL_METRICS_ENABLED: 'true'
|
||||
DEVICE_APPROVAL_LOG_LEVEL: INFO
|
||||
DEVICE_APPROVAL_EVENT_LISTENER_LOG_LEVEL: INFO
|
||||
DEVICE_APPROVAL_METRICS_SERVICE_LOG_LEVEL: DEBUG
|
||||
DEVICE_APPROVAL_QUERY_CACHE_TTL: '300'
|
||||
DEVICE_APPROVAL_HISTORY_QUERY_OPTIMIZATION: 'true'
|
||||
application-device-approval.yml: "# Device V2.1 - \u8BBE\u5907\u7ED1\u5B9A\u5BA1\
|
||||
\u6279\u6A21\u5757\u4E13\u7528\u914D\u7F6E\n# \u81EA\u52A8\u52A0\u8F7D\u5F53 spring.profiles.include=device-approval\
|
||||
\ \u65F6\u751F\u6548\n\ndevice:\n approval:\n # \u5BA1\u6279\u6D41\u7A0B\u914D\
|
||||
\u7F6E\n auto-cleanup-days: ${DEVICE_APPROVAL_AUTO_CLEANUP_DAYS:30}\n batch-size-limit:\
|
||||
\ ${DEVICE_APPROVAL_BATCH_SIZE_LIMIT:100}\n timeout-hours: ${DEVICE_APPROVAL_TIMEOUT_HOURS:72}\n\
|
||||
\n # \u4E8B\u4EF6\u53D1\u5E03\u914D\u7F6E\n events:\n async-thread-pool-size:\
|
||||
\ ${DEVICE_APPROVAL_EVENT_THREAD_POOL_SIZE:10}\n async-queue-capacity: ${DEVICE_APPROVAL_EVENT_QUEUE_CAPACITY:100}\n\
|
||||
\n # \u53EF\u89C2\u6D4B\u6027\u914D\u7F6E\n observability:\n metrics-enabled:\
|
||||
\ ${DEVICE_APPROVAL_METRICS_ENABLED:true}\n log-level: ${DEVICE_APPROVAL_LOG_LEVEL:INFO}\n\
|
||||
\ event-listener-log-level: ${DEVICE_APPROVAL_EVENT_LISTENER_LOG_LEVEL:INFO}\n\
|
||||
\ metrics-service-log-level: ${DEVICE_APPROVAL_METRICS_SERVICE_LOG_LEVEL:DEBUG}\n\
|
||||
\n # \u6027\u80FD\u4F18\u5316\u914D\u7F6E\n performance:\n query-cache-ttl:\
|
||||
\ ${DEVICE_APPROVAL_QUERY_CACHE_TTL:300}\n history-query-optimization: ${DEVICE_APPROVAL_HISTORY_QUERY_OPTIMIZATION:true}\n\
|
||||
\n# Logback \u65E5\u5FD7\u914D\u7F6E (\u8986\u76D6\u9ED8\u8BA4\u914D\u7F6E)\n\
|
||||
logging:\n level:\n com.ljwx.modules.health.service.DeviceBindApprovalServiceImpl:\
|
||||
\ ${DEVICE_APPROVAL_LOG_LEVEL:INFO}\n com.ljwx.modules.health.listener.DeviceBindApprovalEventListener:\
|
||||
\ ${DEVICE_APPROVAL_EVENT_LISTENER_LOG_LEVEL:INFO}\n com.ljwx.modules.health.service.DeviceBindApprovalMetricsService:\
|
||||
\ ${DEVICE_APPROVAL_METRICS_SERVICE_LOG_LEVEL:DEBUG}\n"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-health-admin
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-admin
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: ljwx-admin
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-health-bigscreen
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-bigscreen
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 5225
|
||||
targetPort: 5225
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: ljwx-bigscreen
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-health-boot
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-boot
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 9998
|
||||
targetPort: 9998
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: ljwx-boot
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-health-admin
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-admin
|
||||
component: frontend
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-admin
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-admin
|
||||
component: frontend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: harbor-secret
|
||||
containers:
|
||||
- name: ljwx-admin
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-admin:dev-latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 80
|
||||
protocol: TCP
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
limits:
|
||||
cpu: 500m
|
||||
memory: 512Mi
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
restartPolicy: Always
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-health-bigscreen
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-bigscreen
|
||||
component: dashboard
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-bigscreen
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-bigscreen
|
||||
component: dashboard
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: harbor-secret
|
||||
containers:
|
||||
- name: ljwx-bigscreen
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-bigscreen:dev-latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5225
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: ENV
|
||||
value: production
|
||||
- name: DB_HOST
|
||||
value: mysql-lb.infra.svc.cluster.local
|
||||
- name: DB_PORT
|
||||
value: '3306'
|
||||
- name: DB_NAME
|
||||
value: ljwx_health
|
||||
- name: REDIS_HOST
|
||||
value: redis-lb.infra.svc.cluster.local
|
||||
- name: REDIS_PORT
|
||||
value: '6379'
|
||||
- name: LOG_LEVEL
|
||||
value: INFO
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: ljwx-boot-secrets
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 5225
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 5225
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
resources:
|
||||
limits:
|
||||
cpu: 1000m
|
||||
memory: 1Gi
|
||||
requests:
|
||||
cpu: 250m
|
||||
memory: 512Mi
|
||||
restartPolicy: Always
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-health-boot
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-boot
|
||||
component: backend
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-boot
|
||||
template:
|
||||
metadata:
|
||||
annotations:
|
||||
prometheus.io/scrape: 'true'
|
||||
prometheus.io/port: '9998'
|
||||
prometheus.io/path: /actuator/prometheus
|
||||
labels:
|
||||
app: ljwx-boot
|
||||
component: backend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: harbor-secret
|
||||
containers:
|
||||
- name: ljwx-boot
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-boot:dev-latest
|
||||
imagePullPolicy: Always
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 9998
|
||||
protocol: TCP
|
||||
env:
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: prod
|
||||
- name: SPRING_DATASOURCE_URL
|
||||
value: jdbc:mysql://mysql.infra.svc.cluster.local:3306/ljwx_health?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true
|
||||
- name: SPRING_DATASOURCE_DRIVER_CLASS_NAME
|
||||
value: com.mysql.cj.jdbc.Driver
|
||||
- name: SPRING_REDIS_HOST
|
||||
value: redis-lb.infra.svc.cluster.local
|
||||
- name: SPRING_REDIS_PORT
|
||||
value: '6379'
|
||||
- name: SPRING_REDIS_DATABASE
|
||||
value: '1'
|
||||
- name: SPRING_REDIS_PASSWORD
|
||||
value: '123456'
|
||||
- name: REDIS_HOST
|
||||
value: redis-lb.infra.svc.cluster.local
|
||||
- name: REDIS_PORT
|
||||
value: '6379'
|
||||
- name: REDIS_DB
|
||||
value: '1'
|
||||
- name: REDIS_PASSWORD
|
||||
value: '123456'
|
||||
- name: LOG_LEVEL
|
||||
value: INFO
|
||||
- name: SPRING_FLYWAY_ENABLED
|
||||
value: 'false'
|
||||
- name: SPRING_FLYWAY_BASELINE_ON_MIGRATE
|
||||
value: 'true'
|
||||
- name: SPRING_FLYWAY_LOCATIONS
|
||||
value: classpath:db/baseline
|
||||
- name: OTEL_SERVICE_NAME
|
||||
value: ljwx-boot
|
||||
- name: OTEL_EXPORTER_OTLP_ENDPOINT
|
||||
value: http://tempo.infra.svc.cluster.local:4317
|
||||
- name: OTEL_TRACES_SAMPLER
|
||||
value: parentbased_traceidratio
|
||||
- name: OTEL_TRACES_SAMPLER_ARG
|
||||
value: '0.1'
|
||||
- name: SPRING_PROFILES_INCLUDE
|
||||
value: device-approval
|
||||
envFrom:
|
||||
- secretRef:
|
||||
name: ljwx-boot-secrets
|
||||
- configMapRef:
|
||||
name: ljwx-health-device-approval-config
|
||||
livenessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /actuator/health/liveness
|
||||
port: 9998
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 5
|
||||
readinessProbe:
|
||||
failureThreshold: 3
|
||||
httpGet:
|
||||
path: /actuator/health/readiness
|
||||
port: 9998
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
resources:
|
||||
limits:
|
||||
cpu: 2000m
|
||||
memory: 2Gi
|
||||
requests:
|
||||
cpu: 500m
|
||||
memory: 1Gi
|
||||
volumeMounts:
|
||||
- name: logs
|
||||
mountPath: /app/logs
|
||||
- name: device-approval-config
|
||||
mountPath: /app/config/application-device-approval.yml
|
||||
subPath: application-device-approval.yml
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: logs
|
||||
emptyDir: {}
|
||||
- name: device-approval-config
|
||||
configMap:
|
||||
name: ljwx-health-device-approval-config
|
||||
items:
|
||||
- key: application-device-approval.yml
|
||||
path: application-device-approval.yml
|
||||
restartPolicy: Always
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-health-api-ingress
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||
traefik.ingress.kubernetes.io/router.middlewares: ljwx-health-ljwx-health-strip-api-prefix@kubernetescrd
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- health-admin.lingjingwanxiang.cn
|
||||
secretName: ljwx-health-admin-tls
|
||||
rules:
|
||||
- host: health-admin.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /api
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-health-boot
|
||||
port:
|
||||
number: 9998
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-health-ingress
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/ssl-redirect: 'true'
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: 'true'
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- health-admin.lingjingwanxiang.cn
|
||||
secretName: ljwx-health-admin-tls
|
||||
- hosts:
|
||||
- health-dashboard.lingjingwanxiang.cn
|
||||
secretName: ljwx-health-bigscreen-tls
|
||||
rules:
|
||||
- host: health-admin.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-health-admin
|
||||
port:
|
||||
number: 80
|
||||
- host: health-dashboard.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-health-bigscreen
|
||||
port:
|
||||
number: 5225
|
||||
---
|
||||
apiVersion: traefik.io/v1alpha1
|
||||
kind: Middleware
|
||||
metadata:
|
||||
name: ljwx-health-strip-api-prefix
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
stripPrefix:
|
||||
prefixes:
|
||||
- /api
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: ljwx-health-boot
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-boot
|
||||
release: monitoring
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-boot
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /actuator/prometheus
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
relabelings:
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_service_name
|
||||
targetLabel: service
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_namespace
|
||||
targetLabel: namespace
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_pod_name
|
||||
targetLabel: pod
|
||||
---
|
||||
apiVersion: monitoring.coreos.com/v1
|
||||
kind: ServiceMonitor
|
||||
metadata:
|
||||
name: ljwx-health-bigscreen
|
||||
namespace: ljwx-health
|
||||
labels:
|
||||
app: ljwx-bigscreen
|
||||
release: monitoring
|
||||
helm.sh/chart: ljwx-health-0.1.0
|
||||
app.kubernetes.io/name: ljwx-health
|
||||
app.kubernetes.io/instance: ljwx-health
|
||||
app.kubernetes.io/version: 1.0.6
|
||||
app.kubernetes.io/managed-by: Helm
|
||||
spec:
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-bigscreen
|
||||
endpoints:
|
||||
- port: http
|
||||
path: /metrics
|
||||
interval: 30s
|
||||
scrapeTimeout: 10s
|
||||
relabelings:
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_service_name
|
||||
targetLabel: service
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_namespace
|
||||
targetLabel: namespace
|
||||
- sourceLabels:
|
||||
- __meta_kubernetes_pod_name
|
||||
targetLabel: pod
|
||||
@@ -1,2 +0,0 @@
|
||||
resources:
|
||||
- all.yaml
|
||||
@@ -1,13 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
namespace: ljwx-health
|
||||
images:
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-boot
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-boot
|
||||
newTag: "latest"
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-admin
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-admin
|
||||
newTag: "latest"
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-bigscreen
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx-health/ljwx-bigscreen
|
||||
newTag: "latest"
|
||||
@@ -1,33 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ljwx-platform-admin-ui-nginx
|
||||
data:
|
||||
default.conf: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: ljwx-platform-screen-nginx
|
||||
data:
|
||||
default.conf: |
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-platform-admin-ui
|
||||
labels:
|
||||
app: ljwx-platform-admin-ui
|
||||
project: ljwx-platform
|
||||
component: frontend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-admin-ui
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-platform-admin-ui
|
||||
project: ljwx-platform
|
||||
component: frontend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: ljwx-platform-admin-ui
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-admin-ui:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
volumeMounts:
|
||||
- name: nginx-conf
|
||||
mountPath: /etc/nginx/conf.d/default.conf
|
||||
subPath: default.conf
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: nginx-conf
|
||||
configMap:
|
||||
name: ljwx-platform-admin-ui-nginx
|
||||
@@ -1,61 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-platform-screen
|
||||
labels:
|
||||
app: ljwx-platform-screen
|
||||
project: ljwx-platform
|
||||
component: screen
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-screen
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-platform-screen
|
||||
project: ljwx-platform
|
||||
component: screen
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: ljwx-platform-screen
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-screen:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 80
|
||||
name: http
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 20
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 80
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
resources:
|
||||
requests:
|
||||
cpu: "50m"
|
||||
memory: "64Mi"
|
||||
limits:
|
||||
cpu: "200m"
|
||||
memory: "128Mi"
|
||||
volumeMounts:
|
||||
- name: nginx-conf
|
||||
mountPath: /etc/nginx/conf.d/default.conf
|
||||
subPath: default.conf
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: nginx-conf
|
||||
configMap:
|
||||
name: ljwx-platform-screen-nginx
|
||||
@@ -1,92 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
labels:
|
||||
app: ljwx-platform
|
||||
project: ljwx-platform
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: ljwx-platform
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: ljwx-platform
|
||||
project: ljwx-platform
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
containers:
|
||||
- name: ljwx-platform
|
||||
image: harbor.eu.lingjingwanxiang.cn/ljwx-platform/ljwx-platform:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
name: http
|
||||
volumeMounts:
|
||||
- name: app-logs
|
||||
mountPath: /workspace/logs
|
||||
env:
|
||||
- name: APP_PORT
|
||||
value: "8080"
|
||||
- name: SPRING_PROFILES_ACTIVE
|
||||
value: "prod"
|
||||
- name: SPRING_FLYWAY_VALIDATE_ON_MIGRATE
|
||||
value: "false"
|
||||
- name: DB_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_URL
|
||||
- name: DB_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_USERNAME
|
||||
- name: DB_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_PASSWORD
|
||||
- name: SPRING_DATASOURCE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_URL
|
||||
- name: SPRING_DATASOURCE_USERNAME
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_USERNAME
|
||||
- name: SPRING_DATASOURCE_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-db
|
||||
key: DB_PASSWORD
|
||||
- name: JWT_SECRET
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: ljwx-platform-app
|
||||
key: JWT_SECRET
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 90
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
tcpSocket:
|
||||
port: http
|
||||
initialDelaySeconds: 45
|
||||
periodSeconds: 10
|
||||
resources:
|
||||
requests:
|
||||
cpu: "300m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "1"
|
||||
memory: "1Gi"
|
||||
volumes:
|
||||
- name: app-logs
|
||||
emptyDir: {}
|
||||
@@ -1,51 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: dnspod-letsencrypt
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: websecure
|
||||
traefik.ingress.kubernetes.io/router.tls: "true"
|
||||
spec:
|
||||
ingressClassName: traefik
|
||||
tls:
|
||||
- hosts:
|
||||
- platform.lingjingwanxiang.cn
|
||||
secretName: ljwx-platform-tls
|
||||
- hosts:
|
||||
- platform-admin.lingjingwanxiang.cn
|
||||
secretName: ljwx-platform-admin-tls
|
||||
- hosts:
|
||||
- platform-screen.lingjingwanxiang.cn
|
||||
secretName: ljwx-platform-screen-tls
|
||||
rules:
|
||||
- host: platform.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-platform
|
||||
port:
|
||||
number: 8080
|
||||
- host: platform-admin.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-platform-admin-ui
|
||||
port:
|
||||
number: 80
|
||||
- host: platform-screen.lingjingwanxiang.cn
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: ljwx-platform-screen
|
||||
port:
|
||||
number: 80
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- deployment-admin-ui.yaml
|
||||
- deployment-screen.yaml
|
||||
- service.yaml
|
||||
- service-admin-ui.yaml
|
||||
- service-screen.yaml
|
||||
- ingress.yaml
|
||||
- configmap-nginx.yaml
|
||||
- secret.yaml
|
||||
- regcred.yaml
|
||||
- networkpolicy.yaml
|
||||
@@ -1,160 +0,0 @@
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-default-deny
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes:
|
||||
- Ingress
|
||||
- Egress
|
||||
---
|
||||
# ── Backend ────────────────────────────────────────────────────────────────────
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-allow-ingress-from-traefik
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-allow-egress-dns-and-db
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: infra
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
---
|
||||
# ── Admin UI ───────────────────────────────────────────────────────────────────
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-admin-ui-allow-ingress-from-traefik
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-admin-ui
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-admin-ui-allow-egress-dns
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-admin-ui
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
---
|
||||
# ── Screen ─────────────────────────────────────────────────────────────────────
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-screen-allow-ingress-from-traefik
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-screen
|
||||
policyTypes:
|
||||
- Ingress
|
||||
ingress:
|
||||
- from:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: traefik
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: ljwx-platform-screen-allow-egress-dns
|
||||
spec:
|
||||
podSelector:
|
||||
matchLabels:
|
||||
app: ljwx-platform-screen
|
||||
policyTypes:
|
||||
- Egress
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
podSelector:
|
||||
matchLabels:
|
||||
k8s-app: kube-dns
|
||||
ports:
|
||||
- protocol: UDP
|
||||
port: 53
|
||||
- protocol: TCP
|
||||
port: 53
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: regcred
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuZXUubGluZ2ppbmd3YW54aWFuZy5jbiI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifSwiaGFyYm9yLmV1LmxpbmdqaW5nd2FueGlhbmcuY246ODA4MCI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifX19
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ljwx-platform-db
|
||||
type: Opaque
|
||||
stringData:
|
||||
DB_URL: jdbc:postgresql://postgres-lb.infra.svc.cluster.local:5432/postgres
|
||||
DB_USERNAME: postgres
|
||||
DB_PASSWORD: ChangeMeInProduction123!
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ljwx-platform-app
|
||||
type: Opaque
|
||||
stringData:
|
||||
JWT_SECRET: ChangeThisToAtLeast32BytesBeforeProductionUse
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-platform-admin-ui
|
||||
labels:
|
||||
app: ljwx-platform-admin-ui
|
||||
project: ljwx-platform
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-platform-admin-ui
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
type: ClusterIP
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-platform-screen
|
||||
labels:
|
||||
app: ljwx-platform-screen
|
||||
project: ljwx-platform
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-platform-screen
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
name: http
|
||||
type: ClusterIP
|
||||
@@ -1,14 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
spec:
|
||||
selector:
|
||||
app: ljwx-platform
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8080
|
||||
targetPort: 8080
|
||||
name: http
|
||||
type: ClusterIP
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
namespace: ljwx-platform
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx-platform/ljwx-platform
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform
|
||||
newTag: sha-9f1266d1
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-admin-ui
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-admin-ui
|
||||
newTag: sha-b7766e9
|
||||
- name: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-screen
|
||||
newName: harbor.eu.lingjingwanxiang.cn/ljwx/ljwx-platform-screen
|
||||
newTag: sha-b7766e9
|
||||
@@ -1,256 +0,0 @@
|
||||
# ljwx-template GitOps 配置
|
||||
|
||||
此目录包含 ljwx-template 项目的 GitOps 部署配置,配合 Argo CD 使用。
|
||||
|
||||
## 架构说明
|
||||
|
||||
### 仓库分离策略
|
||||
|
||||
**代码仓库** (`ljwx-template`):
|
||||
- 业务代码(FastAPI + Spring Boot)
|
||||
- Helm Chart (`deploy/helm/app/`)
|
||||
- CI Pipeline(GitHub Actions)
|
||||
|
||||
**GitOps 仓库** (`ljwx-deploy` - 本仓库):
|
||||
- Argo CD Application 定义
|
||||
- 环境特定配置
|
||||
- AppProject 权限定义
|
||||
|
||||
**优势**:
|
||||
- ✅ 权限分离:开发人员无需访问部署配置
|
||||
- ✅ 审批独立:部署变更有独立的 PR 流程
|
||||
- ✅ 回滚清晰:部署配置历史独立管理
|
||||
- ✅ 安全性高:Secrets 不在代码仓库
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
apps/ljwx-template/
|
||||
├── base/
|
||||
│ └── values-common.yaml # 所有环境共享配置
|
||||
└── overlays/
|
||||
├── eu-dev/
|
||||
│ └── values.yaml # EU Dev 环境配置
|
||||
├── mac-dev/
|
||||
│ └── values.yaml # Mac Dev 环境配置
|
||||
└── prod/
|
||||
└── values.yaml # 生产环境配置
|
||||
```
|
||||
|
||||
## 配置覆盖顺序
|
||||
|
||||
Argo CD 会按以下顺序合并配置:
|
||||
|
||||
1. **Helm Chart 默认值** (`ljwx-template/deploy/helm/app/values.yaml`)
|
||||
2. **通用配置** (`base/values-common.yaml`)
|
||||
3. **环境特定配置** (`overlays/{env}/values.yaml`)
|
||||
|
||||
后面的配置会覆盖前面的。
|
||||
|
||||
## 环境说明
|
||||
|
||||
### EU Dev (`overlays/eu-dev/`)
|
||||
- **用途**: EU 团队开发环境
|
||||
- **命名空间**: `dev`
|
||||
- **特点**: DEBUG 日志,1 副本,资源限制较小
|
||||
|
||||
### Mac Dev (`overlays/mac-dev/`)
|
||||
- **用途**: Mac k3s 本地开发环境
|
||||
- **命名空间**: `dev`
|
||||
- **特点**: DEBUG 日志,1 副本,本地数据库
|
||||
|
||||
### Prod (`overlays/prod/`)
|
||||
- **用途**: 生产环境
|
||||
- **命名空间**: `ljwx-template-prod`
|
||||
- **特点**: INFO 日志,3 副本,HPA,HTTPS Ingress
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 修改配置
|
||||
|
||||
1. 克隆本仓库:
|
||||
```bash
|
||||
git clone git@github.com:BrunoGaoSZ/ljwx-deploy.git
|
||||
cd ljwx-deploy/apps/ljwx-template
|
||||
```
|
||||
|
||||
2. 编辑环境特定配置:
|
||||
```bash
|
||||
# 修改 EU Dev 配置
|
||||
vim overlays/eu-dev/values.yaml
|
||||
|
||||
# 例如:调整副本数
|
||||
# apiFast:
|
||||
# replicaCount: 2
|
||||
```
|
||||
|
||||
3. 提交变更:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat(ljwx-template): increase eu-dev replicas to 2"
|
||||
git push
|
||||
```
|
||||
|
||||
4. Argo CD 自动检测变更并部署(~30 秒)
|
||||
|
||||
### 查看部署状态
|
||||
|
||||
```bash
|
||||
# CLI
|
||||
argocd app get ljwx-template-eu-dev
|
||||
|
||||
# Web UI
|
||||
open https://argocd.example.com
|
||||
```
|
||||
|
||||
### 回滚配置
|
||||
|
||||
```bash
|
||||
# Git 回滚
|
||||
git revert HEAD
|
||||
git push
|
||||
|
||||
# 或使用 Argo CD 回滚
|
||||
argocd app rollback ljwx-template-eu-dev
|
||||
```
|
||||
|
||||
## 常见配置修改
|
||||
|
||||
### 1. 调整资源限制
|
||||
|
||||
```yaml
|
||||
# overlays/eu-dev/values.yaml
|
||||
apiFast:
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi" # 从 128Mi 提升
|
||||
cpu: "500m" # 从 100m 提升
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
```
|
||||
|
||||
### 2. 更新镜像版本
|
||||
|
||||
```yaml
|
||||
# overlays/eu-dev/values.yaml
|
||||
apiFast:
|
||||
image:
|
||||
tag: "main-abc1234" # 使用特定 Git SHA
|
||||
```
|
||||
|
||||
### 3. 启用 HPA
|
||||
|
||||
```yaml
|
||||
# overlays/eu-dev/values.yaml
|
||||
apiFast:
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 2
|
||||
maxReplicas: 5
|
||||
targetCPUUtilizationPercentage: 70
|
||||
```
|
||||
|
||||
### 4. 更新数据库连接
|
||||
|
||||
```yaml
|
||||
# overlays/eu-dev/values.yaml
|
||||
database:
|
||||
host: new-postgres-host.example.com
|
||||
port: 5432
|
||||
name: new_database
|
||||
```
|
||||
|
||||
### 5. 修改日志级别
|
||||
|
||||
```yaml
|
||||
# overlays/prod/values.yaml
|
||||
apiFast:
|
||||
env:
|
||||
LOG_LEVEL: "WARNING" # 从 INFO 调整为 WARNING
|
||||
```
|
||||
|
||||
## 安全最佳实践
|
||||
|
||||
### 1. Secrets 管理
|
||||
|
||||
**禁止在 Git 中存储 Secrets!**
|
||||
|
||||
使用以下方案之一:
|
||||
- **ExternalSecrets Operator** (推荐)
|
||||
- **SealedSecrets**
|
||||
- **Vault**
|
||||
- 手动创建 K8s Secrets
|
||||
|
||||
示例(ExternalSecrets):
|
||||
```yaml
|
||||
apiVersion: external-secrets.io/v1beta1
|
||||
kind: ExternalSecret
|
||||
metadata:
|
||||
name: db-credentials-eu-dev
|
||||
spec:
|
||||
secretStoreRef:
|
||||
name: vault-backend
|
||||
target:
|
||||
name: db-credentials-eu-dev
|
||||
data:
|
||||
- secretKey: DB_USER
|
||||
remoteRef:
|
||||
key: ljwx/eu-dev/database
|
||||
property: username
|
||||
```
|
||||
|
||||
### 2. 权限控制
|
||||
|
||||
- 使用 `ljwx:developer` 角色:只读权限
|
||||
- 使用 `ljwx:operator` 角色:同步权限
|
||||
- 生产环境需要额外审批
|
||||
|
||||
### 3. 变更审计
|
||||
|
||||
所有变更都通过 Git PR:
|
||||
- 需要 Code Review
|
||||
- 自动触发 CI 检查
|
||||
- 可追溯历史
|
||||
|
||||
## 故障排查
|
||||
|
||||
### Application 无法同步
|
||||
|
||||
```bash
|
||||
# 查看详细状态
|
||||
argocd app get ljwx-template-eu-dev
|
||||
|
||||
# 查看差异
|
||||
argocd app diff ljwx-template-eu-dev
|
||||
|
||||
# 强制刷新
|
||||
argocd app sync ljwx-template-eu-dev --force
|
||||
```
|
||||
|
||||
### 配置未生效
|
||||
|
||||
检查配置覆盖顺序:
|
||||
```bash
|
||||
# 查看最终渲染的 manifest
|
||||
argocd app manifests ljwx-template-eu-dev
|
||||
```
|
||||
|
||||
### PreSync Hook 失败
|
||||
|
||||
查看 Migration Job 日志:
|
||||
```bash
|
||||
kubectl logs -n dev job/ljwx-template-migration-xxx
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- **代码仓库**: https://github.com/BrunoGaoSZ/ljwx-template
|
||||
- **Helm Chart 说明**: `ljwx-template/deploy/helm/app/README.md`
|
||||
- **完整部署指南**: `ljwx-template/deploy/argo/README.md`
|
||||
- **Argo CD 官方文档**: https://argo-cd.readthedocs.io/
|
||||
|
||||
## 联系方式
|
||||
|
||||
- 问题反馈: https://github.com/BrunoGaoSZ/ljwx-template/issues
|
||||
- 团队: ljwx-team@example.com
|
||||
@@ -1,11 +0,0 @@
|
||||
# ljwx-template 通用配置(跨环境共享)
|
||||
# 此文件定义所有环境的公共配置,具体环境在 overlays 中覆盖
|
||||
|
||||
# 通用标签
|
||||
commonLabels:
|
||||
project: ljwx-template
|
||||
managed-by: argocd
|
||||
|
||||
# 通用注解
|
||||
commonAnnotations:
|
||||
maintained-by: ljwx-team
|
||||
@@ -1,50 +0,0 @@
|
||||
# EU Dev 环境配置覆盖
|
||||
|
||||
# FastAPI 配置
|
||||
apiFast:
|
||||
enabled: true
|
||||
replicaCount: 1 # dev 环境资源有限
|
||||
image:
|
||||
tag: "main-5205c31" # CI 会自动更新为 Git SHA
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
env:
|
||||
LOG_LEVEL: "DEBUG" # dev 环境开启 debug
|
||||
WEB_CONCURRENCY: "1"
|
||||
autoscaling:
|
||||
enabled: false # dev 不启用自动扩展
|
||||
# Spring Boot 配置
|
||||
bootAdmin:
|
||||
enabled: false # dev 环境暂不启用
|
||||
# 数据库配置(指向 dev 集群的数据库)
|
||||
database:
|
||||
host: postgres-dev-eu.default.svc.cluster.local
|
||||
port: 5432
|
||||
name: app_dev_eu
|
||||
secretName: db-credentials-eu-dev
|
||||
# Redis 配置
|
||||
redis:
|
||||
host: redis-dev-eu.default.svc.cluster.local
|
||||
port: 6379
|
||||
secretName: redis-credentials-eu-dev
|
||||
# Migration 配置
|
||||
migration:
|
||||
enabled: true
|
||||
image:
|
||||
tag: "main-latest"
|
||||
# OpenTelemetry 配置
|
||||
otel:
|
||||
enabled: true
|
||||
endpoint: "http://tempo-eu-dev:4317"
|
||||
# Ingress 配置(dev 环境使用 NodePort 或禁用)
|
||||
ingress:
|
||||
enabled: false
|
||||
# ServiceMonitor(如果有 Prometheus Operator)
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 15s # dev 环境更频繁采集
|
||||
@@ -1,61 +0,0 @@
|
||||
# Mac Dev 环境配置覆盖
|
||||
|
||||
# FastAPI 配置
|
||||
apiFast:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
tag: "main-latest"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
|
||||
env:
|
||||
LOG_LEVEL: "DEBUG"
|
||||
WEB_CONCURRENCY: "1"
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
|
||||
# Spring Boot 配置
|
||||
bootAdmin:
|
||||
enabled: false
|
||||
|
||||
# 数据库配置(Mac k3s 本地数据库)
|
||||
database:
|
||||
host: postgres-mac-dev.default.svc.cluster.local
|
||||
port: 5432
|
||||
name: app_dev_mac
|
||||
secretName: db-credentials-mac-dev
|
||||
|
||||
# Redis 配置
|
||||
redis:
|
||||
host: redis-mac-dev.default.svc.cluster.local
|
||||
port: 6379
|
||||
secretName: redis-credentials-mac-dev
|
||||
|
||||
# Migration 配置
|
||||
migration:
|
||||
enabled: true
|
||||
image:
|
||||
tag: "main-latest"
|
||||
|
||||
# OpenTelemetry 配置
|
||||
otel:
|
||||
enabled: true
|
||||
endpoint: "http://tempo-mac-dev:4317"
|
||||
|
||||
# Ingress 配置
|
||||
ingress:
|
||||
enabled: false
|
||||
|
||||
# ServiceMonitor
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 15s
|
||||
@@ -1,96 +0,0 @@
|
||||
# 生产环境配置覆盖
|
||||
|
||||
# FastAPI 配置
|
||||
apiFast:
|
||||
enabled: true
|
||||
replicaCount: 3 # 生产环境多副本
|
||||
|
||||
image:
|
||||
tag: "v1.0.0" # 生产使用具体版本号
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
|
||||
env:
|
||||
LOG_LEVEL: "INFO" # 生产环境 INFO 级别
|
||||
WEB_CONCURRENCY: "4"
|
||||
|
||||
autoscaling:
|
||||
enabled: true
|
||||
minReplicas: 3
|
||||
maxReplicas: 10
|
||||
targetCPUUtilizationPercentage: 70
|
||||
|
||||
# Spring Boot 配置
|
||||
bootAdmin:
|
||||
enabled: true
|
||||
replicaCount: 2
|
||||
|
||||
image:
|
||||
tag: "v1.0.0"
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "1Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "2000m"
|
||||
|
||||
env:
|
||||
SPRING_PROFILES_ACTIVE: "prod"
|
||||
|
||||
# 数据库配置(生产 RDS/云数据库)
|
||||
database:
|
||||
host: prod-db.example.com
|
||||
port: 5432
|
||||
name: app_prod
|
||||
secretName: db-credentials-prod # 使用 ExternalSecrets 或 SealedSecrets
|
||||
|
||||
# Redis 配置(生产 ElastiCache/云 Redis)
|
||||
redis:
|
||||
host: prod-redis.example.com
|
||||
port: 6379
|
||||
secretName: redis-credentials-prod
|
||||
|
||||
# Migration 配置
|
||||
migration:
|
||||
enabled: true
|
||||
image:
|
||||
tag: "v1.0.0"
|
||||
backoffLimit: 5
|
||||
ttlSecondsAfterFinished: 3600 # 保留 1 小时
|
||||
|
||||
# OpenTelemetry 配置
|
||||
otel:
|
||||
enabled: true
|
||||
endpoint: "http://tempo-prod:4317"
|
||||
|
||||
# Ingress 配置(生产环境启用 HTTPS)
|
||||
ingress:
|
||||
enabled: true
|
||||
className: nginx
|
||||
annotations:
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
nginx.ingress.kubernetes.io/rate-limit: "100"
|
||||
hosts:
|
||||
- host: api.ljwx.example.com
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
service: api-fast
|
||||
tls:
|
||||
- secretName: api-ljwx-tls
|
||||
hosts:
|
||||
- api.ljwx.example.com
|
||||
|
||||
# ServiceMonitor
|
||||
serviceMonitor:
|
||||
enabled: true
|
||||
interval: 30s
|
||||
@@ -1,70 +0,0 @@
|
||||
# ljwx-website EU Development Environment Configuration
|
||||
# This file is automatically updated by CI/CD pipeline
|
||||
|
||||
website:
|
||||
enabled: true
|
||||
replicaCount: 1
|
||||
|
||||
image:
|
||||
repository: harbor.eu.lingjingwanxiang.cn:8080/ljwx/ljwx-website
|
||||
tag: "main-latest" # Updated by CI
|
||||
pullPolicy: IfNotPresent
|
||||
|
||||
service:
|
||||
type: ClusterIP
|
||||
port: 80
|
||||
targetPort: 80
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "32Mi"
|
||||
cpu: "25m"
|
||||
limits:
|
||||
memory: "64Mi"
|
||||
cpu: "50m"
|
||||
|
||||
autoscaling:
|
||||
enabled: false
|
||||
|
||||
probes:
|
||||
liveness:
|
||||
path: /
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
readiness:
|
||||
path: /
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
|
||||
# Ingress configuration
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik # k3s default ingress controller
|
||||
|
||||
# Fixed subdomain
|
||||
host: "website.lingjingwanxiang.cn"
|
||||
baseDomain: "lingjingwanxiang.cn"
|
||||
|
||||
path: /
|
||||
pathType: Prefix
|
||||
|
||||
annotations:
|
||||
# Traefik annotations
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web,websecure
|
||||
# cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
|
||||
tls:
|
||||
enabled: true
|
||||
secretName: "website-tls"
|
||||
# clusterIssuer: "letsencrypt-prod"
|
||||
|
||||
# ServiceMonitor (Prometheus Operator)
|
||||
serviceMonitor:
|
||||
enabled: false
|
||||
interval: 30s
|
||||
|
||||
# Namespace labels
|
||||
namespaceLabels:
|
||||
project: ljwx-website
|
||||
managed-by: argocd
|
||||
environment: dev-eu
|
||||
@@ -1,17 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: { app: nginx }
|
||||
template:
|
||||
metadata:
|
||||
labels: { app: nginx }
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:1.27
|
||||
ports:
|
||||
- containerPort: 80
|
||||
@@ -1,3 +0,0 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
@@ -1,10 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: nginx
|
||||
spec:
|
||||
selector:
|
||||
app: nginx
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
@@ -1,6 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
images:
|
||||
- name: nginx
|
||||
newName: ghcr.io/nginxinc/nginx-unprivileged
|
||||
newTag: "1.27-alpine"
|
||||
@@ -1,69 +0,0 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: youngth-guard-backend
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: youngth-guard-backend
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
spec:
|
||||
imagePullSecrets:
|
||||
- name: ghcr-secret
|
||||
containers:
|
||||
- name: backend
|
||||
image: ghcr.io/brunogaosz/youngth-guard-backend:latest
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
env:
|
||||
- name: ENV
|
||||
value: "production"
|
||||
- name: LOG_LEVEL
|
||||
value: "info"
|
||||
# Database configuration from infra Postgres
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: DATABASE_URL
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_DB
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /health
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: ghcr-secret
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
data:
|
||||
.dockerconfigjson: eyJhdXRocyI6eyJnaGNyLmlvIjp7InVzZXJuYW1lIjoiQnJ1bm9HYW9TWiIsInBhc3N3b3JkIjoiZ2hwX05UVVNwTW9kOURhcXAxeGxCTWliOGxnV09WMUJidTF5MjJ4cyIsImVtYWlsIjoieW91ci1lbWFpbEBleGFtcGxlLmNvbSIsImF1dGgiOiJRbkoxYm05SFlXOVRXanBuYUhCZlRsUlZVM0JOYjJRNVJHRnhjREY0YkVKTmFXSTRiR2RYVDFZeFFtSjFNWGt5TW5oeiJ9fX0=
|
||||
@@ -1,4 +0,0 @@
|
||||
resources:
|
||||
- deployment.yaml
|
||||
- service.yaml
|
||||
- ghcr-secret.yaml
|
||||
@@ -1,12 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: youngth-guard-backend
|
||||
spec:
|
||||
selector:
|
||||
app: youngth-guard-backend
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
type: ClusterIP
|
||||
@@ -1,163 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-migration
|
||||
annotations:
|
||||
# ============================================
|
||||
# ArgoCD PreSync Hook
|
||||
# ============================================
|
||||
# 这个 Job 会在应用部署前执行
|
||||
# 如果失败,会阻止应用部署
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
component: migration
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
component: migration
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
|
||||
# ============================================
|
||||
# Init Container: 等待数据库就绪
|
||||
# ============================================
|
||||
initContainers:
|
||||
- name: wait-for-db
|
||||
image: postgres:16-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for database to be ready..."
|
||||
until pg_isready -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER; do
|
||||
echo "Database is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready!"
|
||||
env:
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
|
||||
# ============================================
|
||||
# Main Container: Flyway Migration
|
||||
# ============================================
|
||||
containers:
|
||||
- name: flyway
|
||||
image: flyway/flyway:10-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
# 构建 Flyway 连接 URL
|
||||
export FLYWAY_URL="jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}"
|
||||
|
||||
echo "============================================"
|
||||
echo "Database Migration with Flyway"
|
||||
echo "============================================"
|
||||
echo "Database: $FLYWAY_URL"
|
||||
echo "User: $FLYWAY_USER"
|
||||
echo "Migrations: /flyway/sql"
|
||||
echo "============================================"
|
||||
|
||||
# 列出迁移文件
|
||||
echo "Available migrations:"
|
||||
ls -la /flyway/sql/ || echo "No migrations found"
|
||||
|
||||
# 执行迁移
|
||||
echo ""
|
||||
echo "Running migrations..."
|
||||
flyway -connectRetries=3 migrate
|
||||
|
||||
# 显示迁移历史
|
||||
echo ""
|
||||
echo "Migration history:"
|
||||
flyway info
|
||||
|
||||
echo ""
|
||||
echo "✅ Migration completed successfully"
|
||||
|
||||
env:
|
||||
# Flyway 配置
|
||||
- name: FLYWAY_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: FLYWAY_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
|
||||
# 从 Secret 获取连接信息
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_DB
|
||||
|
||||
# Flyway 选项
|
||||
- name: FLYWAY_BASELINE_ON_MIGRATE
|
||||
value: "true"
|
||||
- name: FLYWAY_BASELINE_VERSION
|
||||
value: "0"
|
||||
- name: FLYWAY_OUT_OF_ORDER
|
||||
value: "false"
|
||||
- name: FLYWAY_VALIDATE_ON_MIGRATE
|
||||
value: "true"
|
||||
|
||||
volumeMounts:
|
||||
- name: migrations
|
||||
mountPath: /flyway/sql
|
||||
readOnly: true
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# ============================================
|
||||
# Volumes: 挂载迁移文件
|
||||
# ============================================
|
||||
volumes:
|
||||
- name: migrations
|
||||
configMap:
|
||||
name: db-migrations
|
||||
@@ -1,140 +0,0 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
V1__initial_schema.sql: |
|
||||
-- ============================================
|
||||
-- Migration: V1__initial_schema
|
||||
-- Description: 初始化 youngth-guard 数据库 Schema
|
||||
-- Author: System
|
||||
-- Date: 2025-01-18
|
||||
-- ============================================
|
||||
-- Description:
|
||||
-- 创建 youngth-guard 应用的初始数据库结构
|
||||
-- 包括用户表、评估记录表等核心表
|
||||
--
|
||||
-- Rollback Strategy:
|
||||
-- 如需回滚,创建 V2 迁移删除这些表
|
||||
--
|
||||
-- Performance Impact:
|
||||
-- 预估执行时间:< 5 秒
|
||||
-- 无锁表操作
|
||||
-- 对应用无影响(首次创建)
|
||||
-- ============================================
|
||||
|
||||
-- ============================================
|
||||
-- Step 1: 创建用户表
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
|
||||
|
||||
COMMENT ON TABLE users IS '用户表';
|
||||
COMMENT ON COLUMN users.id IS '用户 ID(主键)';
|
||||
COMMENT ON COLUMN users.username IS '用户名(唯一)';
|
||||
COMMENT ON COLUMN users.email IS '邮箱(唯一)';
|
||||
COMMENT ON COLUMN users.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN users.updated_at IS '更新时间';
|
||||
|
||||
-- ============================================
|
||||
-- Step 2: 创建心理评估记录表
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assessments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
score INTEGER NOT NULL CHECK (score >= 0 AND score <= 100),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
assessment_type VARCHAR(50) NOT NULL DEFAULT 'general',
|
||||
notes TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_user_id ON assessments(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_status ON assessments(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_type ON assessments(assessment_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_created_at ON assessments(created_at);
|
||||
|
||||
COMMENT ON TABLE assessments IS '心理评估记录表';
|
||||
COMMENT ON COLUMN assessments.id IS '评估 ID(主键)';
|
||||
COMMENT ON COLUMN assessments.user_id IS '用户 ID(外键)';
|
||||
COMMENT ON COLUMN assessments.score IS '评估分数(0-100)';
|
||||
COMMENT ON COLUMN assessments.status IS '评估状态:pending/completed/cancelled';
|
||||
COMMENT ON COLUMN assessments.assessment_type IS '评估类型:general/anxiety/depression等';
|
||||
COMMENT ON COLUMN assessments.notes IS '评估备注';
|
||||
COMMENT ON COLUMN assessments.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN assessments.updated_at IS '更新时间';
|
||||
|
||||
-- ============================================
|
||||
-- Step 3: 创建审计日志表
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
entity_type VARCHAR(50) NOT NULL,
|
||||
entity_id INTEGER,
|
||||
changes JSONB,
|
||||
ip_address INET,
|
||||
user_agent TEXT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_action ON audit_logs(action);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
|
||||
COMMENT ON TABLE audit_logs IS '审计日志表';
|
||||
COMMENT ON COLUMN audit_logs.id IS '日志 ID(主键)';
|
||||
COMMENT ON COLUMN audit_logs.user_id IS '操作用户 ID';
|
||||
COMMENT ON COLUMN audit_logs.action IS '操作类型:create/update/delete/login等';
|
||||
COMMENT ON COLUMN audit_logs.entity_type IS '实体类型:user/assessment等';
|
||||
COMMENT ON COLUMN audit_logs.entity_id IS '实体 ID';
|
||||
COMMENT ON COLUMN audit_logs.changes IS '变更内容(JSON 格式)';
|
||||
COMMENT ON COLUMN audit_logs.ip_address IS '操作 IP 地址';
|
||||
COMMENT ON COLUMN audit_logs.user_agent IS '用户代理';
|
||||
COMMENT ON COLUMN audit_logs.created_at IS '创建时间';
|
||||
|
||||
-- ============================================
|
||||
-- Verification
|
||||
-- ============================================
|
||||
|
||||
-- 验证表已创建
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'users') THEN
|
||||
RAISE EXCEPTION 'Table users was not created!';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'assessments') THEN
|
||||
RAISE EXCEPTION 'Table assessments was not created!';
|
||||
END IF;
|
||||
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_tables WHERE schemaname = 'public' AND tablename = 'audit_logs') THEN
|
||||
RAISE EXCEPTION 'Table audit_logs was not created!';
|
||||
END IF;
|
||||
|
||||
RAISE NOTICE 'All tables created successfully!';
|
||||
END $$;
|
||||
|
||||
-- ============================================
|
||||
-- Notes:
|
||||
-- - 所有表都使用 IF NOT EXISTS 确保幂等性
|
||||
-- - 索引已根据常用查询场景优化
|
||||
-- - 审计日志表使用 BIGSERIAL 应对大量日志
|
||||
-- - 使用 JSONB 类型存储灵活的变更数据
|
||||
-- ============================================
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
creationTimestamp: null
|
||||
name: db-migrations
|
||||
@@ -1,11 +0,0 @@
|
||||
resources:
|
||||
- ../../base
|
||||
- db-migration-job.yaml
|
||||
- db-migrations-configmap.yaml
|
||||
|
||||
namespace: dev
|
||||
|
||||
images:
|
||||
- name: ghcr.io/brunogaosz/youngth-guard-backend
|
||||
newName: ghcr.io/brunogaosz/youngth-guard-backend
|
||||
newTag: "dev-b90d0d9"
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: cluster-bootstrap
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: cluster
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: argocd
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,18 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: nginx-dev
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/nginx/overlays/dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: youngth-guard-backend
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/youngth-guard/overlays/dev
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: dev
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infra-arc
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: infra/arc
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: arc-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,20 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: infra-data
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: infra/data
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: infra
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
@@ -1,60 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-template-eu-dev
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: eu-dev
|
||||
project: ljwx-template
|
||||
annotations:
|
||||
argocd.argoproj.io/manifest-generate-paths: .
|
||||
spec:
|
||||
project: ljwx
|
||||
|
||||
# 多源配置:从两个 repo 拉取配置
|
||||
sources:
|
||||
# 源 1: Helm Chart(代码仓库)
|
||||
- repoURL: 'https://github.com/BrunoGaoSZ/ljwx-template.git'
|
||||
targetRevision: main
|
||||
path: deploy/helm/app
|
||||
helm:
|
||||
valueFiles:
|
||||
# 引用源 2 的 values 文件
|
||||
- $values/apps/ljwx-template/base/values-common.yaml
|
||||
- $values/apps/ljwx-template/overlays/eu-dev/values.yaml
|
||||
|
||||
# 源 2: Values 文件(GitOps 仓库)
|
||||
- repoURL: 'https://github.com/BrunoGaoSZ/ljwx-deploy.git'
|
||||
targetRevision: main
|
||||
ref: values # 定义引用名称
|
||||
|
||||
# 目标配置:部署到哪个集群和命名空间
|
||||
destination:
|
||||
server: 'https://kubernetes.default.svc'
|
||||
namespace: dev # 使用已有的 dev 命名空间
|
||||
|
||||
# 同步策略
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # 自动删除 Git 中不存在的资源
|
||||
selfHeal: true # 自动修复手动更改
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=false # dev 命名空间已存在
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true # 先部署新资源,再删除旧资源
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
# 忽略某些字段的差异(避免误报)
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas # 如果使用 HPA,忽略 replicas 差异
|
||||
@@ -1,56 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-template-mac-dev
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: mac-dev
|
||||
project: ljwx-template
|
||||
annotations:
|
||||
argocd.argoproj.io/manifest-generate-paths: .
|
||||
spec:
|
||||
project: ljwx
|
||||
|
||||
# 多源配置:从两个 repo 拉取配置
|
||||
sources:
|
||||
# 源 1: Helm Chart(代码仓库)
|
||||
- repoURL: 'https://github.com/BrunoGaoSZ/ljwx-template.git'
|
||||
targetRevision: main
|
||||
path: deploy/helm/app
|
||||
helm:
|
||||
valueFiles:
|
||||
- $values/apps/ljwx-template/base/values-common.yaml
|
||||
- $values/apps/ljwx-template/overlays/mac-dev/values.yaml
|
||||
|
||||
# 源 2: Values 文件(GitOps 仓库)
|
||||
- repoURL: 'https://github.com/BrunoGaoSZ/ljwx-deploy.git'
|
||||
targetRevision: main
|
||||
ref: values
|
||||
|
||||
destination:
|
||||
server: 'https://kubernetes.default.svc'
|
||||
namespace: dev # Mac k3s 也使用 dev 命名空间
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=false
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-bookstore
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: bookstore
|
||||
project: ljwx-bookstore
|
||||
spec:
|
||||
project: ljwx
|
||||
source:
|
||||
repoURL: 'https://github.com/BrunoGaoSZ/ljwx-deploy.git'
|
||||
targetRevision: main
|
||||
path: apps/ljwx-bookstore/overlays/bookstore
|
||||
destination:
|
||||
server: 'https://kubernetes.default.svc'
|
||||
namespace: bookstore
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-platform
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: prod
|
||||
project: ljwx-platform
|
||||
spec:
|
||||
project: ljwx
|
||||
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
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: agent-platform
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: prod
|
||||
project: agent-platform
|
||||
spec:
|
||||
project: ljwx
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/agent-platform/overlays/prod
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: agent-platform
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,34 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-health-k3s-eu
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
labels:
|
||||
environment: k3s-eu
|
||||
project: ljwx-health
|
||||
spec:
|
||||
project: ljwx
|
||||
source:
|
||||
repoURL: "https://github.com/BrunoGaoSZ/ljwx-deploy.git"
|
||||
targetRevision: main
|
||||
path: apps/ljwx-health/overlays/k3s-eu
|
||||
destination:
|
||||
server: "https://kubernetes.default.svc"
|
||||
namespace: ljwx-health
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
@@ -1,62 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: ljwx-website-eu-dev
|
||||
namespace: argocd
|
||||
labels:
|
||||
project: ljwx-website
|
||||
environment: dev-eu
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
project: default # Or use 'ljwx-project' if you have a specific AppProject
|
||||
|
||||
source:
|
||||
# Source: ljwx-website business repository (contains Helm chart)
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-website.git
|
||||
targetRevision: main
|
||||
path: deploy/helm/app
|
||||
helm:
|
||||
# Use both base values and environment-specific overlay
|
||||
valueFiles:
|
||||
- values.yaml # Base configuration
|
||||
- ../../environments/dev-eu.yaml # Environment overlay
|
||||
|
||||
# Additional runtime overrides (managed by CI/CD)
|
||||
# These values override everything above
|
||||
values: |
|
||||
website:
|
||||
image:
|
||||
tag: "main-latest" # Will be updated by CI/CD
|
||||
|
||||
ingress:
|
||||
enabled: true
|
||||
className: traefik
|
||||
host: "website.lingjingwanxiang.cn"
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: ljwx-website-dev
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true # Auto-delete resources when removed from git
|
||||
selfHeal: true # Auto-sync when cluster state drifts from git
|
||||
allowEmpty: false
|
||||
syncOptions:
|
||||
- CreateNamespace=true # Auto-create namespace if it doesn't exist
|
||||
- PrunePropagationPolicy=foreground
|
||||
- PruneLast=true
|
||||
retry:
|
||||
limit: 5
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 3m
|
||||
|
||||
# Health assessment
|
||||
ignoreDifferences:
|
||||
- group: apps
|
||||
kind: Deployment
|
||||
jsonPointers:
|
||||
- /spec/replicas # Ignore if HPA manages replicas
|
||||
@@ -1,66 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: AppProject
|
||||
metadata:
|
||||
name: ljwx
|
||||
namespace: argocd
|
||||
finalizers:
|
||||
- resources-finalizer.argocd.argoproj.io
|
||||
spec:
|
||||
description: LJWX Template Applications
|
||||
|
||||
# 允许的源代码仓库(分离后需要两个 repo)
|
||||
sourceRepos:
|
||||
# 代码仓库(Helm chart)
|
||||
- 'https://github.com/BrunoGaoSZ/ljwx-template.git'
|
||||
- 'git@github.com:BrunoGaoSZ/ljwx-template.git'
|
||||
- 'https://github.com/BrunoGaoSZ/ljwx-bookstore.git'
|
||||
- 'git@github.com:BrunoGaoSZ/ljwx-bookstore.git'
|
||||
# GitOps 仓库(部署配置)
|
||||
- 'https://github.com/BrunoGaoSZ/ljwx-deploy.git'
|
||||
- 'git@github.com:BrunoGaoSZ/ljwx-deploy.git'
|
||||
|
||||
# 允许部署的目标集群和命名空间
|
||||
destinations:
|
||||
- namespace: 'ljwx-*'
|
||||
server: 'https://kubernetes.default.svc'
|
||||
- namespace: 'argocd'
|
||||
server: 'https://kubernetes.default.svc'
|
||||
- namespace: 'dev' # 已有的 dev 命名空间
|
||||
server: 'https://kubernetes.default.svc'
|
||||
- namespace: 'bookstore' # bookstore 命名空间
|
||||
server: 'https://kubernetes.default.svc'
|
||||
- namespace: 'agent-platform'
|
||||
server: 'https://kubernetes.default.svc'
|
||||
|
||||
# 允许创建的集群级资源(谨慎开放)
|
||||
clusterResourceWhitelist:
|
||||
- group: ''
|
||||
kind: Namespace
|
||||
|
||||
# 允许的命名空间级资源
|
||||
namespaceResourceWhitelist:
|
||||
- group: '*'
|
||||
kind: '*'
|
||||
|
||||
# 孤立资源策略(删除 Application 时的行为)
|
||||
orphanedResources:
|
||||
warn: true
|
||||
|
||||
# 角色(可选,用于细粒度权限控制)
|
||||
roles:
|
||||
- name: developer
|
||||
description: Developer role with read-only access
|
||||
policies:
|
||||
- p, proj:ljwx:developer, applications, get, ljwx/*, allow
|
||||
- p, proj:ljwx:developer, applications, sync, ljwx/*, deny
|
||||
groups:
|
||||
- ljwx-developers
|
||||
|
||||
- name: operator
|
||||
description: Operator role with sync permissions
|
||||
policies:
|
||||
- p, proj:ljwx:operator, applications, get, ljwx/*, allow
|
||||
- p, proj:ljwx:operator, applications, sync, ljwx/*, allow
|
||||
- p, proj:ljwx:operator, applications, override, ljwx/*, allow
|
||||
groups:
|
||||
- ljwx-operators
|
||||
@@ -1,7 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: default
|
||||
namespace: dev
|
||||
imagePullSecrets:
|
||||
- name: regcred
|
||||
@@ -1,4 +0,0 @@
|
||||
resources:
|
||||
- namespace-dev.yaml
|
||||
- regcred.yaml
|
||||
- default-sa.yaml
|
||||
@@ -1,4 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: dev
|
||||
@@ -1,8 +0,0 @@
|
||||
apiVersion: v1
|
||||
data:
|
||||
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuZXUubGluZ2ppbmd3YW54aWFuZy5jbjo4MDgwIjp7InVzZXJuYW1lIjoiYWRtaW4iLCJwYXNzd29yZCI6IkhhcmJvcjEyMzQ1IiwiYXV0aCI6IllXUnRhVzQ2U0dGeVltOXlNVEl6TkRVPSJ9fX0=
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: regcred
|
||||
namespace: dev
|
||||
type: kubernetes.io/dockerconfigjson
|
||||
@@ -1,146 +0,0 @@
|
||||
# 平台目标架构说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本平台采用 **GitOps + 基础设施复用** 的架构模式,实现应用的自动化发布和统一的数据库管理。
|
||||
|
||||
---
|
||||
|
||||
## 🔄 CI/CD 职责边界
|
||||
|
||||
### GitHub Actions(CI 层)
|
||||
**职责:构建和推送**
|
||||
- 代码提交后自动触发
|
||||
- 构建 Docker 镜像
|
||||
- 推送镜像到容器仓库(GHCR)
|
||||
- 更新 GitOps 仓库的镜像版本
|
||||
|
||||
**不负责:** 实际部署、数据库管理、环境配置
|
||||
|
||||
### ArgoCD(CD 层)
|
||||
**职责:部署和同步**
|
||||
- 监听 GitOps 仓库变化
|
||||
- 自动将声明式配置同步到 Kubernetes
|
||||
- 执行数据库迁移(PreSync Hook)
|
||||
- 保证集群状态与 Git 一致
|
||||
|
||||
**不负责:** 构建镜像、修改代码
|
||||
|
||||
---
|
||||
|
||||
## 🏃 GitHub Runner 定位
|
||||
|
||||
### 默认:GitHub Hosted Runner
|
||||
**适用场景:** 所有标准构建任务
|
||||
- 无需维护
|
||||
- 按使用付费
|
||||
- 适合 95% 的场景
|
||||
|
||||
### ARC (Actions Runner Controller)
|
||||
**仅在以下场景启用:**
|
||||
- 需要访问内网资源
|
||||
- 构建时间超过 6 小时
|
||||
- 需要 GPU 等特殊硬件
|
||||
|
||||
**原则:** 不使用则不部署,避免维护成本
|
||||
|
||||
---
|
||||
|
||||
## 🗄️ Infra Postgres 角色
|
||||
|
||||
### 定位:平台级共享数据库服务
|
||||
**为什么不是每个项目独立部署?**
|
||||
- 减少资源浪费(内存、存储)
|
||||
- 统一备份和恢复策略
|
||||
- 集中监控和运维
|
||||
|
||||
### 使用方式
|
||||
```
|
||||
一个 Postgres 实例 → 多个独立数据库
|
||||
├── youngth_guard_dev (项目1 - 开发环境)
|
||||
├── youngth_guard_prod (项目1 - 生产环境)
|
||||
├── other_project_dev (项目2 - 开发环境)
|
||||
└── ...
|
||||
```
|
||||
|
||||
### 隔离保证
|
||||
- **数据库级别隔离**:每个项目有独立的数据库和用户
|
||||
- **权限最小化**:应用用户只能访问自己的数据库
|
||||
- **网络隔离**:仅集群内访问,不对外暴露
|
||||
|
||||
---
|
||||
|
||||
## 🔗 应用、数据库、迁移、回滚关系
|
||||
|
||||
### 正常发布流程
|
||||
```
|
||||
1. 开发者 git push
|
||||
↓
|
||||
2. GitHub Actions 构建镜像
|
||||
↓
|
||||
3. 更新 GitOps 仓库
|
||||
↓
|
||||
4. ArgoCD 检测到变化
|
||||
↓
|
||||
5. 执行数据库迁移(PreSync Hook)
|
||||
✓ 成功 → 继续部署应用
|
||||
✗ 失败 → 阻止部署
|
||||
↓
|
||||
6. 部署新版本应用
|
||||
```
|
||||
|
||||
### 回滚策略
|
||||
|
||||
#### 应用回滚(快速)
|
||||
- **方式:** Git revert → ArgoCD 自动回滚
|
||||
- **时间:** 2-5 分钟
|
||||
- **场景:** 应用逻辑问题、性能问题
|
||||
|
||||
#### 数据库回滚(谨慎)
|
||||
- **方式 1:** 兼容性迁移(推荐)
|
||||
- 新旧版本共存的 Schema 设计
|
||||
- 不需要真正回滚
|
||||
|
||||
- **方式 2:** PITR(Point-In-Time Recovery)
|
||||
- 恢复到指定时间点
|
||||
- 用于严重数据错误
|
||||
- **注意:** 会丢失恢复点之后的数据
|
||||
|
||||
---
|
||||
|
||||
## ✅ 核心原则
|
||||
|
||||
1. **不中断服务**
|
||||
- 数据库迁移必须向后兼容
|
||||
- 应用先部署新版本,再清理旧 Schema
|
||||
|
||||
2. **可回滚**
|
||||
- 应用随时可以回到上一个版本
|
||||
- 数据库设计支持新旧版本共存
|
||||
|
||||
3. **自动化优先**
|
||||
- 减少人工操作
|
||||
- 所有变更通过 Git 触发
|
||||
|
||||
4. **基础设施复用**
|
||||
- 共享 Postgres、监控、日志
|
||||
- 降低运维复杂度
|
||||
|
||||
---
|
||||
|
||||
## 🎯 对团队的意义
|
||||
|
||||
**对开发者:**
|
||||
- 只需要 `git push`,无需关心部署细节
|
||||
- 数据库自动创建和迁移
|
||||
- 专注业务逻辑开发
|
||||
|
||||
**对运维:**
|
||||
- 统一的备份和恢复流程
|
||||
- 集中监控和告警
|
||||
- 减少重复劳动
|
||||
|
||||
**对业务:**
|
||||
- 更快的发布速度
|
||||
- 更高的系统稳定性
|
||||
- 更低的运维成本
|
||||
@@ -1,459 +0,0 @@
|
||||
# ArgoCD PreSync Hook - 数据库迁移
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档说明如何在 ArgoCD 中使用 **PreSync Hook** 自动执行数据库迁移。
|
||||
|
||||
**工作流程:**
|
||||
```
|
||||
1. git push → CI 构建镜像
|
||||
2. CI 更新 GitOps 仓库
|
||||
3. ArgoCD 检测到变化
|
||||
4. 执行 PreSync Hook(数据库迁移)
|
||||
✓ 成功 → 继续部署应用
|
||||
✗ 失败 → 阻止部署
|
||||
5. 部署新版本应用
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🎯 PreSync Hook 机制
|
||||
|
||||
### Hook 类型
|
||||
|
||||
| Hook | 执行时机 | 用途 |
|
||||
|------|---------|------|
|
||||
| **PreSync** | 同步前 | 数据库迁移、配置预检 |
|
||||
| Sync | 同步中 | 标准资源部署 |
|
||||
| PostSync | 同步后 | 数据初始化、通知 |
|
||||
| SyncFail | 失败时 | 清理、回滚、告警 |
|
||||
|
||||
### PreSync 注解
|
||||
|
||||
```yaml
|
||||
metadata:
|
||||
annotations:
|
||||
# 标记为 PreSync Hook
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
|
||||
# 删除策略:在新 Hook 执行前删除旧的
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
|
||||
# Sync Wave:-1 表示最先执行
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📁 文件组织
|
||||
|
||||
### 方式 1:ConfigMap(适合少量迁移)
|
||||
|
||||
```yaml
|
||||
# 迁移文件存储在 ConfigMap
|
||||
apps/youngth-guard/overlays/dev/
|
||||
├── db-migrations-configmap.yaml # 包含 SQL 文件
|
||||
└── db-migration-job.yaml # PreSync Job
|
||||
```
|
||||
|
||||
**优点:** 简单直接
|
||||
**缺点:** ConfigMap 大小限制 1MB,不适合大量迁移
|
||||
|
||||
### 方式 2:打包到镜像(推荐)
|
||||
|
||||
```dockerfile
|
||||
# Dockerfile
|
||||
FROM flyway/flyway:10-alpine
|
||||
|
||||
# 复制迁移文件
|
||||
COPY backend-fastapi/migrations /flyway/sql
|
||||
|
||||
# 复制配置
|
||||
COPY flyway.conf /flyway/conf/
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- 迁移文件和代码版本一致
|
||||
- 无大小限制
|
||||
- 便于审计
|
||||
|
||||
**缺点:** 需要构建专门的迁移镜像
|
||||
|
||||
### 方式 3:从 Git 拉取(灵活)
|
||||
|
||||
```yaml
|
||||
initContainers:
|
||||
- name: fetch-migrations
|
||||
image: alpine/git
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
git clone --depth=1 --branch=main \
|
||||
https://github.com/BrunoGaoSZ/youngth-guard.git /repo
|
||||
cp -r /repo/backend-fastapi/migrations/* /migrations/
|
||||
volumeMounts:
|
||||
- name: migrations
|
||||
mountPath: /migrations
|
||||
```
|
||||
|
||||
**优点:**
|
||||
- 灵活,可以指定 branch/tag
|
||||
- 不需要重新构建镜像
|
||||
|
||||
**缺点:**
|
||||
- 需要 Git 访问权限
|
||||
- 依赖外部服务
|
||||
|
||||
---
|
||||
|
||||
## 🔒 并发保护
|
||||
|
||||
### 问题:多个 Sync 同时触发
|
||||
|
||||
如果多个开发者同时部署,可能导致多个迁移 Job 并发执行。
|
||||
|
||||
### 解决方案:Flyway 内置锁
|
||||
|
||||
Flyway 使用数据库表锁防止并发:
|
||||
|
||||
```sql
|
||||
-- Flyway 自动创建的锁表
|
||||
CREATE TABLE flyway_schema_history (
|
||||
installed_rank INT NOT NULL,
|
||||
version VARCHAR(50),
|
||||
description VARCHAR(200),
|
||||
type VARCHAR(20),
|
||||
script VARCHAR(1000),
|
||||
checksum INT,
|
||||
installed_by VARCHAR(100),
|
||||
installed_on TIMESTAMP DEFAULT NOW(),
|
||||
execution_time INT,
|
||||
success BOOLEAN NOT NULL,
|
||||
PRIMARY KEY (installed_rank)
|
||||
);
|
||||
|
||||
-- 执行迁移时 Flyway 会锁定此表
|
||||
```
|
||||
|
||||
**机制:**
|
||||
1. Job A 开始迁移 → 锁定 `flyway_schema_history`
|
||||
2. Job B 尝试迁移 → 等待锁释放(超时后失败)
|
||||
3. Job A 完成 → 释放锁
|
||||
4. Job B 检测到已执行,跳过
|
||||
|
||||
### 额外保护:BackoffLimit
|
||||
|
||||
```yaml
|
||||
spec:
|
||||
backoffLimit: 0 # 失败后不重试
|
||||
```
|
||||
|
||||
避免失败后反复重试导致资源浪费。
|
||||
|
||||
---
|
||||
|
||||
## ❌ 失败路径说明
|
||||
|
||||
### 场景 1:迁移 SQL 错误
|
||||
|
||||
```
|
||||
PreSync Job 失败 (Exit Code 1)
|
||||
↓
|
||||
ArgoCD 标记 Sync 失败
|
||||
↓
|
||||
应用部署被阻止(Deployment 不会更新)
|
||||
↓
|
||||
旧版本应用继续运行
|
||||
```
|
||||
|
||||
**查看错误:**
|
||||
```bash
|
||||
# 查看 Job 日志
|
||||
kubectl -n dev logs -l component=migration --tail=100
|
||||
|
||||
# 查看 ArgoCD Application 状态
|
||||
kubectl -n argocd get application youngth-guard-backend -o yaml
|
||||
```
|
||||
|
||||
**修复步骤:**
|
||||
1. 检查迁移 SQL 语法
|
||||
2. 修复 Git 仓库中的迁移文件
|
||||
3. 手动触发 ArgoCD Sync,或等待自动同步
|
||||
4. 验证迁移成功
|
||||
|
||||
### 场景 2:数据库连接失败
|
||||
|
||||
```
|
||||
Init Container (wait-for-db) 失败
|
||||
↓
|
||||
Job 一直 Pending
|
||||
↓
|
||||
ArgoCD 等待超时(默认 5 分钟)
|
||||
↓
|
||||
Sync 失败
|
||||
```
|
||||
|
||||
**原因:**
|
||||
- Secret 不存在或错误
|
||||
- 数据库服务不可用
|
||||
- 网络策略阻止连接
|
||||
|
||||
**修复步骤:**
|
||||
```bash
|
||||
# 1. 检查 Secret
|
||||
kubectl -n dev get secret postgres-youngth-guard-dev
|
||||
|
||||
# 2. 测试连接
|
||||
kubectl run -it --rm psql-test \
|
||||
--image=postgres:16-alpine \
|
||||
--namespace=dev \
|
||||
--restart=Never \
|
||||
-- sh -c 'export $(kubectl get secret postgres-youngth-guard-dev -o jsonpath="{.data.DATABASE_URL}" | base64 -d) && pg_isready -d $DATABASE_URL'
|
||||
|
||||
# 3. 检查数据库 Pod
|
||||
kubectl -n infra get pods -l app=postgres
|
||||
```
|
||||
|
||||
### 场景 3:迁移文件缺失
|
||||
|
||||
```
|
||||
Flyway 启动
|
||||
↓
|
||||
未找到迁移文件(/flyway/sql 为空)
|
||||
↓
|
||||
Flyway 报错或跳过
|
||||
↓
|
||||
Job 成功(但实际没有执行迁移)
|
||||
```
|
||||
|
||||
**预防:**
|
||||
```yaml
|
||||
# 在 Job 中添加验证
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
# 检查迁移文件
|
||||
if [ ! "$(ls -A /flyway/sql)" ]; then
|
||||
echo "ERROR: No migration files found in /flyway/sql"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 执行迁移
|
||||
flyway migrate
|
||||
```
|
||||
|
||||
### 场景 4:迁移部分成功
|
||||
|
||||
```
|
||||
V1 迁移成功
|
||||
V2 迁移成功
|
||||
V3 迁移失败(SQL 错误)
|
||||
↓
|
||||
Flyway 记录 V3 失败
|
||||
↓
|
||||
Job 失败
|
||||
```
|
||||
|
||||
**状态:**
|
||||
- V1, V2 已应用到数据库
|
||||
- V3 标记为失败,未应用
|
||||
|
||||
**修复步骤:**
|
||||
```bash
|
||||
# 1. 连接数据库查看状态
|
||||
kubectl -n infra exec -it postgres-0 -- psql -U postgres -d youngth_guard_dev
|
||||
|
||||
# 2. 检查 Flyway 历史
|
||||
SELECT * FROM flyway_schema_history ORDER BY installed_rank;
|
||||
|
||||
# 3. 如果需要手动修复
|
||||
# 修复数据库状态,然后标记迁移为成功
|
||||
UPDATE flyway_schema_history SET success = true WHERE version = '3';
|
||||
|
||||
# 或者删除失败记录,修复 SQL 后重新运行
|
||||
DELETE FROM flyway_schema_history WHERE version = '3';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 验证迁移成功
|
||||
|
||||
### 检查清单
|
||||
|
||||
```bash
|
||||
# 1. 查看 Job 状态
|
||||
kubectl -n dev get job -l component=migration
|
||||
|
||||
# 预期输出:
|
||||
# NAME COMPLETIONS DURATION AGE
|
||||
# db-migration 1/1 15s 2m
|
||||
|
||||
# 2. 查看 Job 日志
|
||||
kubectl -n dev logs -l component=migration --tail=50
|
||||
|
||||
# 预期包含:
|
||||
# ✅ Migration completed successfully
|
||||
|
||||
# 3. 检查 Flyway 历史
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT version, description, installed_on, success FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
|
||||
# 4. 验证应用部署
|
||||
kubectl -n dev get pods -l app=youngth-guard-backend
|
||||
|
||||
# 5. 检查应用日志(应该能连接数据库)
|
||||
kubectl -n dev logs -l app=youngth-guard-backend --tail=20
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 完整集成示例
|
||||
|
||||
### 目录结构
|
||||
|
||||
```
|
||||
apps/youngth-guard/overlays/dev/
|
||||
├── kustomization.yaml
|
||||
├── deployment.yaml
|
||||
├── service.yaml
|
||||
├── db-migration-job.yaml # PreSync Hook
|
||||
└── db-migrations-configmap.yaml # 迁移文件(或使用镜像)
|
||||
```
|
||||
|
||||
### kustomization.yaml
|
||||
|
||||
```yaml
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: dev
|
||||
|
||||
resources:
|
||||
- ../../base
|
||||
- db-migration-job.yaml
|
||||
- db-migrations-configmap.yaml
|
||||
|
||||
images:
|
||||
- name: ghcr.io/brunogaosz/youngth-guard-backend
|
||||
newName: ghcr.io/brunogaosz/youngth-guard-backend
|
||||
newTag: "dev-abc1234"
|
||||
```
|
||||
|
||||
### ArgoCD Application
|
||||
|
||||
```yaml
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: youngth-guard-backend
|
||||
namespace: argocd
|
||||
spec:
|
||||
project: default
|
||||
|
||||
source:
|
||||
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
|
||||
targetRevision: main
|
||||
path: apps/youngth-guard/overlays/dev
|
||||
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: dev
|
||||
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
|
||||
# 重要:失败时不继续
|
||||
retry:
|
||||
limit: 2
|
||||
backoff:
|
||||
duration: 5s
|
||||
factor: 2
|
||||
maxDuration: 1m
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🐛 调试技巧
|
||||
|
||||
### 手动运行迁移 Job
|
||||
|
||||
```bash
|
||||
# 1. 从 PreSync Job 创建手动 Job
|
||||
kubectl -n dev get job db-migration -o yaml | \
|
||||
sed 's/argocd.argoproj.io\/hook: PreSync//' | \
|
||||
sed 's/name: db-migration/name: db-migration-manual/' | \
|
||||
kubectl apply -f -
|
||||
|
||||
# 2. 查看日志
|
||||
kubectl -n dev logs -f job/db-migration-manual
|
||||
|
||||
# 3. 清理
|
||||
kubectl -n dev delete job db-migration-manual
|
||||
```
|
||||
|
||||
### 跳过迁移(紧急情况)
|
||||
|
||||
```yaml
|
||||
# 临时移除 PreSync Hook
|
||||
metadata:
|
||||
annotations:
|
||||
# argocd.argoproj.io/hook: PreSync # 注释掉
|
||||
argocd.argoproj.io/hook: Skip
|
||||
```
|
||||
|
||||
⚠️ **危险操作!** 仅在紧急情况使用,之后必须手动执行迁移。
|
||||
|
||||
### 查看 ArgoCD 事件
|
||||
|
||||
```bash
|
||||
# 查看 Application 事件
|
||||
kubectl -n argocd describe application youngth-guard-backend
|
||||
|
||||
# 查看最近的 Sync
|
||||
kubectl -n argocd get application youngth-guard-backend -o jsonpath='{.status.operationState}'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 最佳实践
|
||||
|
||||
1. **迁移文件版本化**
|
||||
- 迁移文件提交到 Git
|
||||
- 和代码一起审查
|
||||
|
||||
2. **测试迁移**
|
||||
- 在本地数据库测试
|
||||
- 在测试环境验证
|
||||
- 使用 Flyway validate 检查
|
||||
|
||||
3. **监控迁移**
|
||||
- 设置告警(迁移失败)
|
||||
- 记录迁移时间
|
||||
- 定期审查 flyway_schema_history
|
||||
|
||||
4. **备份数据**
|
||||
- 迁移前自动备份(通过 PreSync 触发)
|
||||
- 保留最近 7 天备份
|
||||
|
||||
5. **文档化**
|
||||
- 每个迁移都有清晰注释
|
||||
- 记录回滚步骤
|
||||
- 说明业务影响
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- `docs/flyway-guide/README.md` - Flyway 规范和示例
|
||||
- `docs/db-isolation-spec.md` - 数据库隔离规范
|
||||
- `docs/architecture-overview.md` - 平台架构说明
|
||||
- `infra/postgres/README.md` - Postgres 部署文档
|
||||
- ArgoCD Hooks 官方文档:https://argo-cd.readthedocs.io/en/stable/user-guide/resource_hooks/
|
||||
@@ -1,175 +0,0 @@
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-migration
|
||||
annotations:
|
||||
# ============================================
|
||||
# ArgoCD PreSync Hook
|
||||
# ============================================
|
||||
# 这个 Job 会在应用部署前执行
|
||||
# 如果失败,会阻止应用部署
|
||||
argocd.argoproj.io/hook: PreSync
|
||||
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
component: migration
|
||||
managed-by: argocd
|
||||
|
||||
spec:
|
||||
# 不自动清理,便于查看日志
|
||||
# ttlSecondsAfterFinished: 3600
|
||||
|
||||
# 失败后不重试(避免并发问题)
|
||||
backoffLimit: 0
|
||||
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
component: migration
|
||||
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
|
||||
# ============================================
|
||||
# Init Container: 等待数据库就绪
|
||||
# ============================================
|
||||
initContainers:
|
||||
- name: wait-for-db
|
||||
image: postgres:16-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for database to be ready..."
|
||||
until pg_isready -h $POSTGRES_HOST -p $POSTGRES_PORT -U $POSTGRES_USER; do
|
||||
echo "Database is unavailable - sleeping"
|
||||
sleep 2
|
||||
done
|
||||
echo "Database is ready!"
|
||||
|
||||
env:
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
|
||||
# ============================================
|
||||
# Main Container: Flyway Migration
|
||||
# ============================================
|
||||
containers:
|
||||
- name: flyway
|
||||
image: flyway/flyway:10-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
|
||||
echo "============================================"
|
||||
echo "Database Migration with Flyway"
|
||||
echo "============================================"
|
||||
echo "Database: $FLYWAY_URL"
|
||||
echo "User: $FLYWAY_USER"
|
||||
echo "Migrations: /flyway/sql"
|
||||
echo "============================================"
|
||||
|
||||
# 列出迁移文件
|
||||
echo "Available migrations:"
|
||||
ls -la /flyway/sql/ || echo "No migrations found"
|
||||
|
||||
# 执行迁移
|
||||
echo ""
|
||||
echo "Running migrations..."
|
||||
flyway -connectRetries=3 migrate
|
||||
|
||||
# 显示迁移历史
|
||||
echo ""
|
||||
echo "Migration history:"
|
||||
flyway info
|
||||
|
||||
echo ""
|
||||
echo "✅ Migration completed successfully"
|
||||
|
||||
env:
|
||||
# Flyway 配置
|
||||
- name: FLYWAY_URL
|
||||
value: "jdbc:postgresql://$(POSTGRES_HOST):$(POSTGRES_PORT)/$(POSTGRES_DB)"
|
||||
- name: FLYWAY_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: FLYWAY_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
|
||||
# 从 Secret 获取连接信息
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_DB
|
||||
|
||||
# Flyway 选项
|
||||
- name: FLYWAY_BASELINE_ON_MIGRATE
|
||||
value: "true"
|
||||
- name: FLYWAY_BASELINE_VERSION
|
||||
value: "0"
|
||||
- name: FLYWAY_OUT_OF_ORDER
|
||||
value: "false"
|
||||
- name: FLYWAY_VALIDATE_ON_MIGRATE
|
||||
value: "true"
|
||||
|
||||
volumeMounts:
|
||||
- name: migrations
|
||||
mountPath: /flyway/sql
|
||||
readOnly: true
|
||||
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "200m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
|
||||
# ============================================
|
||||
# Volumes: 挂载迁移文件
|
||||
# ============================================
|
||||
volumes:
|
||||
- name: migrations
|
||||
configMap:
|
||||
name: db-migrations
|
||||
# 如果迁移文件很多,建议使用 initContainer 从 Git 拉取
|
||||
# 或者打包到应用镜像中
|
||||
@@ -1,59 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: db-migrations
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
component: migration
|
||||
managed-by: argocd
|
||||
data:
|
||||
# ============================================
|
||||
# 迁移文件示例
|
||||
# ============================================
|
||||
# 实际项目中,建议:
|
||||
# 1. 将迁移文件打包到应用镜像中
|
||||
# 2. 或使用 initContainer 从 Git 拉取
|
||||
# 3. 这里仅作为示例
|
||||
# ============================================
|
||||
|
||||
V1__initial_schema.sql: |
|
||||
-- ============================================
|
||||
-- Migration: V1__initial_schema
|
||||
-- Description: 初始化数据库 Schema
|
||||
-- ============================================
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS assessments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
score INTEGER NOT NULL CHECK (score >= 0 AND score <= 100),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_user_id ON assessments(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_status ON assessments(status);
|
||||
|
||||
COMMENT ON TABLE users IS '用户表';
|
||||
COMMENT ON TABLE assessments IS '心理评估记录表';
|
||||
|
||||
V2__add_phone_column.sql: |
|
||||
-- ============================================
|
||||
-- Migration: V2__add_phone_column
|
||||
-- Description: 添加手机号列
|
||||
-- ============================================
|
||||
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_phone ON users(phone) WHERE phone IS NOT NULL;
|
||||
COMMENT ON COLUMN users.phone IS '用户手机号(可选)';
|
||||
@@ -1,594 +0,0 @@
|
||||
# CI/Runner 策略说明
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档说明 GitHub Actions Runner 的选择策略,帮助团队在 **GitHub-hosted runner** 和 **Self-hosted runner (ARC)** 之间做出正确决策。
|
||||
|
||||
**核心策略:**
|
||||
- ✅ **默认使用 GitHub-hosted runner**(免费额度 + 零维护)
|
||||
- ✅ **按需启用 ARC**(特殊场景:内网访问、大量构建、特殊硬件)
|
||||
- ✅ **避免过早优化**(不要一开始就自建 runner)
|
||||
|
||||
---
|
||||
|
||||
## 🎯 快速决策树
|
||||
|
||||
```
|
||||
你的项目需要 CI/CD 吗?
|
||||
↓
|
||||
是
|
||||
↓
|
||||
是否需要访问内网资源?
|
||||
├─ 是 → 使用 ARC (Self-hosted)
|
||||
│ (例如:内网数据库、私有 API)
|
||||
│
|
||||
└─ 否 → 构建时间是否 > 10 分钟?
|
||||
├─ 否 → 使用 GitHub-hosted runner ✅
|
||||
│ (大多数项目)
|
||||
│
|
||||
└─ 是 → 月构建次数是否 > 500 次?
|
||||
├─ 否 → 使用 GitHub-hosted runner ✅
|
||||
│ (成本仍可接受)
|
||||
│
|
||||
└─ 是 → 考虑启用 ARC
|
||||
(评估成本 vs 维护成本)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 GitHub-hosted vs Self-hosted (ARC)
|
||||
|
||||
### 对比表
|
||||
|
||||
| 维度 | GitHub-hosted Runner | Self-hosted Runner (ARC) |
|
||||
|------|---------------------|-------------------------|
|
||||
| **成本** | 免费额度:2000 分钟/月<br>超出:$0.008/分钟 | K8s 节点成本:$50-200/月<br>维护成本:人力 |
|
||||
| **维护** | ✅ 零维护(GitHub 管理) | ❌ 需要维护(更新、监控、扩缩容) |
|
||||
| **安全性** | ✅ 每次构建全新环境<br>✅ 自动清理 | ⚠️ 需要配置安全策略<br>⚠️ 需要定期更新镜像 |
|
||||
| **性能** | 标准:2 核 7GB<br>Large:4 核 16GB | 可自定义(2-16 核) |
|
||||
| **启动速度** | 🟡 中等(10-30 秒) | 🟢 快速(2-10 秒) |
|
||||
| **网络访问** | ✅ 公网<br>❌ 无法访问内网 | ✅ 公网 + 内网(K8s 集群内) |
|
||||
| **并发构建** | ✅ 无限(GitHub 管理) | ⚠️ 受限于 K8s 节点资源 |
|
||||
| **缓存** | ✅ 支持 actions/cache | ✅ 支持 + 可自建缓存层 |
|
||||
| **特殊硬件** | ❌ 无 GPU/特殊设备 | ✅ 可配置 GPU/特殊设备 |
|
||||
| **适用场景** | 🎯 大多数项目 | 🎯 内网访问、大量构建、特殊硬件 |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 默认策略:使用 GitHub-hosted Runner
|
||||
|
||||
### 为什么默认选择 GitHub-hosted?
|
||||
|
||||
#### 1. **免费额度充足**
|
||||
|
||||
GitHub 提供慷慨的免费额度:
|
||||
|
||||
- **公共仓库:** 无限免费分钟
|
||||
- **私有仓库(个人账户):** 2000 分钟/月
|
||||
- **私有仓库(团队账户):** 3000 分钟/月
|
||||
- **私有仓库(企业账户):** 50000 分钟/月
|
||||
|
||||
**实际案例:**
|
||||
```
|
||||
项目:youngth-guard-backend
|
||||
构建时间:5 分钟/次
|
||||
每日提交:10 次
|
||||
月构建次数:300 次
|
||||
月消耗分钟:1500 分钟
|
||||
|
||||
结论:免费额度完全够用 ✅
|
||||
```
|
||||
|
||||
#### 2. **零维护成本**
|
||||
|
||||
GitHub-hosted runner 完全由 GitHub 管理:
|
||||
|
||||
- ✅ 自动更新操作系统和工具链
|
||||
- ✅ 自动扩缩容(并发构建)
|
||||
- ✅ 自动清理(每次构建全新环境)
|
||||
- ✅ 无需监控和告警
|
||||
- ✅ 无需担心资源不足
|
||||
|
||||
**对比 Self-hosted:**
|
||||
- ❌ 需要维护 ARC 控制器
|
||||
- ❌ 需要维护 runner 镜像
|
||||
- ❌ 需要配置扩缩容策略
|
||||
- ❌ 需要监控资源使用
|
||||
- ❌ 需要定期更新依赖
|
||||
|
||||
#### 3. **安全性最佳**
|
||||
|
||||
每次构建都是全新的虚拟机:
|
||||
|
||||
- ✅ 完全隔离(无交叉污染)
|
||||
- ✅ 无状态(自动清理密钥/缓存)
|
||||
- ✅ 防止密钥泄露(构建结束即销毁)
|
||||
|
||||
#### 4. **快速上手**
|
||||
|
||||
无需配置,开箱即用:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/build.yml
|
||||
name: Build and Deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest # 就这么简单!
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build Docker image
|
||||
run: docker build -t myapp .
|
||||
|
||||
- name: Push to GHCR
|
||||
run: |
|
||||
echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
|
||||
docker push ghcr.io/${{ github.repository }}:${{ github.sha }}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 何时启用 ARC (Self-hosted Runner)
|
||||
|
||||
### 场景 1:需要访问内网资源 ✅ 推荐
|
||||
|
||||
**问题:** GitHub-hosted runner 运行在公网,无法访问内网数据库/API。
|
||||
|
||||
**示例:**
|
||||
- 需要连接内网 Postgres 数据库进行集成测试
|
||||
- 需要调用内网 API 进行验证
|
||||
- 需要访问内网 NFS/S3 存储
|
||||
|
||||
**解决方案:** 使用 ARC,runner 运行在 K8s 集群内,可访问内网服务。
|
||||
|
||||
```yaml
|
||||
# .github/workflows/integration-test.yml
|
||||
jobs:
|
||||
test:
|
||||
runs-on: self-hosted # 使用 ARC
|
||||
|
||||
steps:
|
||||
- name: Run integration tests
|
||||
run: |
|
||||
# 可以直接访问内网数据库
|
||||
export DB_HOST=postgres-lb.infra.svc.cluster.local
|
||||
pytest tests/integration/
|
||||
```
|
||||
|
||||
### 场景 2:大量构建,成本优化 ⚠️ 需评估
|
||||
|
||||
**问题:** 超出免费额度,GitHub-hosted runner 费用高。
|
||||
|
||||
**成本对比:**
|
||||
|
||||
| 方案 | 月构建次数 | 月构建时间 | 月成本 |
|
||||
|------|----------|----------|--------|
|
||||
| GitHub-hosted | 1000 次 | 5000 分钟 | $24 (超出 3000 分钟) |
|
||||
| GitHub-hosted | 5000 次 | 25000 分钟 | $176 |
|
||||
| ARC (1 节点) | 无限 | 无限 | $50-100 (K8s 节点) + 维护 |
|
||||
|
||||
**决策:**
|
||||
- 月构建 < 5000 分钟 → GitHub-hosted 更划算 ✅
|
||||
- 月构建 > 10000 分钟 → 考虑 ARC
|
||||
- 月构建 > 50000 分钟 → 强烈推荐 ARC
|
||||
|
||||
### 场景 3:特殊硬件需求 ✅ 推荐
|
||||
|
||||
**问题:** 需要 GPU、特殊 CPU 架构(ARM)、大内存。
|
||||
|
||||
**示例:**
|
||||
- 机器学习模型训练(需要 GPU)
|
||||
- 构建 ARM 架构镜像(Apple Silicon / Raspberry Pi)
|
||||
- 大型 monorepo 构建(需要 32GB+ 内存)
|
||||
|
||||
**解决方案:** 使用 ARC,配置专用节点池。
|
||||
|
||||
```yaml
|
||||
# ARC 配置:GPU 节点池
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: gpu-runner
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
nodeSelector:
|
||||
cloud.google.com/gke-accelerator: nvidia-tesla-t4
|
||||
tolerations:
|
||||
- key: nvidia.com/gpu
|
||||
operator: Exists
|
||||
containers:
|
||||
- name: runner
|
||||
resources:
|
||||
limits:
|
||||
nvidia.com/gpu: 1
|
||||
```
|
||||
|
||||
### 场景 4:构建速度优化 🟡 可选
|
||||
|
||||
**问题:** GitHub-hosted runner 启动慢(10-30 秒)。
|
||||
|
||||
**对比:**
|
||||
- GitHub-hosted:10-30 秒启动
|
||||
- ARC (预热):2-10 秒启动
|
||||
|
||||
**决策:**
|
||||
- 构建频率低(< 50 次/天)→ 启动时间不重要 ✅
|
||||
- 构建频率高(> 200 次/天)→ 考虑 ARC
|
||||
- 需要快速反馈(如 PR 验证)→ 考虑 ARC
|
||||
|
||||
### 场景 5:合规性要求 ✅ 推荐
|
||||
|
||||
**问题:** 企业安全政策禁止使用公网 runner。
|
||||
|
||||
**示例:**
|
||||
- 金融行业(需要数据本地化)
|
||||
- 政府项目(需要在指定区域运行)
|
||||
- 内部工具(禁止代码离开内网)
|
||||
|
||||
**解决方案:** 使用 ARC,确保所有构建在内网完成。
|
||||
|
||||
---
|
||||
|
||||
## 📊 成本分析
|
||||
|
||||
### GitHub-hosted Runner 成本
|
||||
|
||||
**定价(2025 年):**
|
||||
- Linux:$0.008/分钟
|
||||
- Windows:$0.016/分钟
|
||||
- macOS:$0.08/分钟
|
||||
|
||||
**实际案例:**
|
||||
|
||||
| 项目 | 构建时间 | 月构建次数 | 月消耗分钟 | 免费额度 | 超出分钟 | 月成本 |
|
||||
|------|---------|----------|----------|---------|---------|--------|
|
||||
| 小型项目 | 3 分钟 | 200 次 | 600 分钟 | 3000 | 0 | **$0** |
|
||||
| 中型项目 | 8 分钟 | 500 次 | 4000 分钟 | 3000 | 1000 | **$8** |
|
||||
| 大型项目 | 15 分钟 | 1000 次 | 15000 分钟 | 3000 | 12000 | **$96** |
|
||||
|
||||
### ARC (Self-hosted) 成本
|
||||
|
||||
**基础成本:**
|
||||
- K8s 节点:$50-200/月(取决于云厂商和规格)
|
||||
- 存储(缓存):$10-20/月
|
||||
- 网络流量:$5-15/月
|
||||
|
||||
**维护成本:**
|
||||
- 初始搭建:4-8 小时
|
||||
- 月度维护:2-4 小时/月
|
||||
- 按工程师时薪 $50 计算:$100-200/月
|
||||
|
||||
**总成本:** $165-435/月
|
||||
|
||||
**适用场景:**
|
||||
- 月构建分钟 > 20000 → ARC 成本更低
|
||||
- 需要内网访问 → ARC 是唯一选择
|
||||
- 团队规模 > 10 人 → 维护成本分摊
|
||||
|
||||
---
|
||||
|
||||
## 🔧 ARC 配置示例
|
||||
|
||||
### 前提条件
|
||||
|
||||
当前 `ljwx-deploy` 仓库已经配置了 ARC:
|
||||
|
||||
- ✅ ARC 控制器已部署(`infra/arc/`)
|
||||
- ✅ RunnerDeployment 已配置(弹性伸缩)
|
||||
- ✅ GHCR 认证已配置(imagePullSecrets)
|
||||
|
||||
### 查看当前 ARC 状态
|
||||
|
||||
```bash
|
||||
# 查看 ARC 控制器
|
||||
kubectl -n arc-systems get pods
|
||||
|
||||
# 查看 Runner 状态
|
||||
kubectl -n arc-runners get runners
|
||||
|
||||
# 查看 RunnerDeployment
|
||||
kubectl -n arc-runners get runnerdeployments
|
||||
```
|
||||
|
||||
### 使用 ARC Runner
|
||||
|
||||
在 GitHub Actions workflow 中指定:
|
||||
|
||||
```yaml
|
||||
# .github/workflows/build-with-arc.yml
|
||||
name: Build with ARC
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
# 使用 self-hosted runner
|
||||
runs-on: self-hosted
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Build in Kubernetes
|
||||
run: |
|
||||
echo "Running in K8s cluster"
|
||||
echo "Can access internal services:"
|
||||
curl postgres-lb.infra.svc.cluster.local:5432
|
||||
|
||||
- name: Build Docker image
|
||||
run: |
|
||||
docker build -t myapp .
|
||||
docker push ghcr.io/myorg/myapp:${{ github.sha }}
|
||||
```
|
||||
|
||||
### 配置多个 Runner 类型
|
||||
|
||||
```yaml
|
||||
# infra/arc/runner-deployment-standard.yaml
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: arc-runner-standard
|
||||
namespace: arc-runners
|
||||
spec:
|
||||
replicas: 2
|
||||
template:
|
||||
spec:
|
||||
labels:
|
||||
- self-hosted
|
||||
- linux
|
||||
- x64
|
||||
- standard # 自定义标签
|
||||
resources:
|
||||
requests:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
limits:
|
||||
memory: "4Gi"
|
||||
cpu: "2000m"
|
||||
---
|
||||
# infra/arc/runner-deployment-large.yaml
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: arc-runner-large
|
||||
namespace: arc-runners
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
labels:
|
||||
- self-hosted
|
||||
- linux
|
||||
- x64
|
||||
- large # 自定义标签
|
||||
resources:
|
||||
requests:
|
||||
memory: "8Gi"
|
||||
cpu: "4000m"
|
||||
limits:
|
||||
memory: "16Gi"
|
||||
cpu: "8000m"
|
||||
```
|
||||
|
||||
在 workflow 中使用:
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
small-build:
|
||||
runs-on: [self-hosted, standard] # 使用标准 runner
|
||||
steps:
|
||||
- run: npm run build
|
||||
|
||||
large-build:
|
||||
runs-on: [self-hosted, large] # 使用大内存 runner
|
||||
steps:
|
||||
- run: mvn clean install
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 决策 Checklist
|
||||
|
||||
### 选择 GitHub-hosted Runner(推荐 ✅)
|
||||
|
||||
满足以下**所有**条件时,使用 GitHub-hosted:
|
||||
|
||||
- [ ] 无需访问内网资源
|
||||
- [ ] 月构建分钟 < 10000(企业账户 < 50000)
|
||||
- [ ] 无特殊硬件需求(GPU/大内存)
|
||||
- [ ] 无合规性要求(数据可离开内网)
|
||||
- [ ] 构建时间 < 30 分钟/次
|
||||
- [ ] 团队规模较小(< 5 人)
|
||||
|
||||
**优势:**
|
||||
- ✅ 零成本(免费额度内)
|
||||
- ✅ 零维护
|
||||
- ✅ 最高安全性
|
||||
- ✅ 无限并发
|
||||
|
||||
### 选择 ARC (Self-hosted Runner)
|
||||
|
||||
满足以下**任一**条件时,考虑 ARC:
|
||||
|
||||
- [ ] 需要访问内网服务(数据库、API、存储)
|
||||
- [ ] 月构建分钟 > 20000
|
||||
- [ ] 需要 GPU 或特殊硬件
|
||||
- [ ] 需要大内存(> 16GB)
|
||||
- [ ] 合规性要求(数据不能离开内网)
|
||||
- [ ] 需要自定义 runner 环境(特殊依赖)
|
||||
- [ ] 团队规模较大(> 10 人,维护成本分摊)
|
||||
|
||||
**权衡:**
|
||||
- ✅ 可访问内网
|
||||
- ✅ 成本可控(大量构建时)
|
||||
- ✅ 性能可定制
|
||||
- ❌ 需要维护
|
||||
- ❌ 初始搭建成本
|
||||
- ❌ 需要安全加固
|
||||
|
||||
---
|
||||
|
||||
## 🚦 推荐配置策略
|
||||
|
||||
### 阶段 1:项目启动(0-3 个月)
|
||||
|
||||
**策略:100% GitHub-hosted**
|
||||
|
||||
```yaml
|
||||
# .github/workflows/build.yml
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest # 默认
|
||||
```
|
||||
|
||||
**原因:**
|
||||
- 快速验证 CI/CD 流程
|
||||
- 无需维护基础设施
|
||||
- 免费额度足够
|
||||
|
||||
### 阶段 2:快速迭代(3-6 个月)
|
||||
|
||||
**策略:GitHub-hosted 为主,ARC 为辅**
|
||||
|
||||
```yaml
|
||||
# .github/workflows/build.yml
|
||||
jobs:
|
||||
# 普通构建:GitHub-hosted
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
# 集成测试(需要内网):ARC
|
||||
integration-test:
|
||||
runs-on: self-hosted
|
||||
steps:
|
||||
- name: Test with internal DB
|
||||
run: pytest tests/integration/
|
||||
```
|
||||
|
||||
**原因:**
|
||||
- 大部分构建仍使用 GitHub-hosted
|
||||
- 仅特殊场景使用 ARC
|
||||
- 逐步积累 ARC 运维经验
|
||||
|
||||
### 阶段 3:规模化(6 个月+)
|
||||
|
||||
**策略:按需选择,精细化管理**
|
||||
|
||||
```yaml
|
||||
# .github/workflows/matrix-build.yml
|
||||
jobs:
|
||||
# 轻量级任务:GitHub-hosted
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- run: npm run lint
|
||||
|
||||
# 标准构建:ARC (成本优化)
|
||||
build:
|
||||
runs-on: [self-hosted, standard]
|
||||
steps:
|
||||
- run: docker build .
|
||||
|
||||
# 大型构建:ARC large
|
||||
build-monorepo:
|
||||
runs-on: [self-hosted, large]
|
||||
steps:
|
||||
- run: nx run-many --target=build --all
|
||||
|
||||
# macOS 构建:GitHub-hosted (无 ARC macOS)
|
||||
build-ios:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- run: xcodebuild
|
||||
```
|
||||
|
||||
**原因:**
|
||||
- 成本最优
|
||||
- 性能最优
|
||||
- 维护成本可控
|
||||
|
||||
---
|
||||
|
||||
## 🔍 监控和优化
|
||||
|
||||
### GitHub-hosted Runner 监控
|
||||
|
||||
查看使用量:
|
||||
|
||||
1. GitHub 仓库 → Settings → Billing → Usage this month
|
||||
2. 查看每个 workflow 的耗时:Actions → Workflow → Timing
|
||||
|
||||
**优化建议:**
|
||||
- 使用 actions/cache 缓存依赖
|
||||
- 并行执行独立 jobs
|
||||
- 避免不必要的构建(配置 paths filter)
|
||||
|
||||
```yaml
|
||||
# 优化示例:仅在代码变更时构建
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- 'Dockerfile'
|
||||
- '.github/workflows/build.yml'
|
||||
```
|
||||
|
||||
### ARC Runner 监控
|
||||
|
||||
```bash
|
||||
# 查看 runner 资源使用
|
||||
kubectl -n arc-runners top pods
|
||||
|
||||
# 查看 runner 日志
|
||||
kubectl -n arc-runners logs -l app=runner
|
||||
|
||||
# 查看 HPA 状态(如果配置了自动扩缩容)
|
||||
kubectl -n arc-runners get hpa
|
||||
```
|
||||
|
||||
**优化建议:**
|
||||
- 配置 HPA 根据队列长度扩缩容
|
||||
- 使用 PVC 缓存 Docker 层
|
||||
- 定期清理旧镜像
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- `infra/arc/README.md` - ARC 部署和配置详解
|
||||
- `docs/architecture-overview.md` - 平台架构说明
|
||||
- `.github/workflows/` - 现有 workflow 示例
|
||||
- GitHub Actions 官方文档:https://docs.github.com/en/actions
|
||||
- ARC 项目:https://github.com/actions/actions-runner-controller
|
||||
|
||||
---
|
||||
|
||||
## 🎯 总结
|
||||
|
||||
**默认策略:** 使用 GitHub-hosted runner ✅
|
||||
|
||||
**例外场景:**
|
||||
1. 需要访问内网 → ARC ✅
|
||||
2. 月构建 > 20000 分钟 → 评估 ARC
|
||||
3. 需要 GPU/特殊硬件 → ARC ✅
|
||||
4. 合规性要求 → ARC ✅
|
||||
|
||||
**避免:**
|
||||
- ❌ 过早优化(项目初期就自建 runner)
|
||||
- ❌ 盲目跟风(看到别人用 ARC 就用)
|
||||
- ❌ 忽视维护成本(只看节点成本,忽略人力)
|
||||
|
||||
**记住:**
|
||||
> 最好的 runner 策略是**不需要维护的 runner**。
|
||||
>
|
||||
> 优先使用 GitHub-hosted,仅在必要时启用 ARC。
|
||||
@@ -1,516 +0,0 @@
|
||||
# 数据库复用与隔离规范
|
||||
|
||||
## 📋 规范目标
|
||||
|
||||
本规范定义了如何在共享的 infra Postgres 实例中安全地隔离多个项目的数据,确保:
|
||||
|
||||
1. **数据隔离** - 项目之间无法访问彼此的数据
|
||||
2. **权限最小化** - 应用只能访问自己需要的数据库
|
||||
3. **命名统一** - 便于管理和审计
|
||||
4. **可扩展** - 支持多项目、多环境
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ 命名规范
|
||||
|
||||
### 数据库命名规范
|
||||
|
||||
**格式:** `{project}_{env}`
|
||||
|
||||
**规则:**
|
||||
- 全部小写
|
||||
- 使用下划线分隔
|
||||
- 项目名简短清晰(推荐 2-4 个单词)
|
||||
- 环境名固定为:`dev` / `staging` / `prod`
|
||||
|
||||
**示例:**
|
||||
```
|
||||
youngth_guard_dev ✅ 正确
|
||||
youngth_guard_staging ✅ 正确
|
||||
youngth_guard_prod ✅ 正确
|
||||
|
||||
YoungthGuard_Dev ❌ 错误(大小写混合)
|
||||
youngth-guard-dev ❌ 错误(使用连字符)
|
||||
yg_dev ❌ 不推荐(缩写不清晰)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 用户命名规范
|
||||
|
||||
**格式:** `{project}_{env}_user`
|
||||
|
||||
**规则:**
|
||||
- 与数据库名对应,添加 `_user` 后缀
|
||||
- 全部小写
|
||||
- 每个数据库有独立的用户
|
||||
- 不使用超级用户或 postgres 用户
|
||||
|
||||
**示例:**
|
||||
```
|
||||
youngth_guard_dev_user ✅ 正确
|
||||
youngth_guard_staging_user ✅ 正确
|
||||
youngth_guard_prod_user ✅ 正确
|
||||
|
||||
youngth_guard_user ❌ 错误(缺少环境标识)
|
||||
admin ❌ 错误(不清晰)
|
||||
postgres ❌ 禁止(超级用户)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Secret 命名规范
|
||||
|
||||
**格式:** `postgres-{project}-{env}`
|
||||
|
||||
**规则:**
|
||||
- 使用连字符分隔(Kubernetes Secret 命名约定)
|
||||
- 创建在应用的 namespace 中
|
||||
- 包含完整的连接信息
|
||||
|
||||
**示例:**
|
||||
```
|
||||
postgres-youngth-guard-dev ✅ 正确
|
||||
postgres-youngth-guard-staging ✅ 正确
|
||||
postgres-youngth-guard-prod ✅ 正确
|
||||
|
||||
youngth-guard-db ❌ 错误(缺少 postgres 前缀)
|
||||
db-secret ❌ 错误(不清晰)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 权限最小化原则
|
||||
|
||||
### 应用用户权限清单
|
||||
|
||||
**允许的权限:**
|
||||
- ✅ 连接到指定数据库:`CONNECT`
|
||||
- ✅ 创建 Schema:`CREATE`(在自己的数据库内)
|
||||
- ✅ 在 public schema 中操作:`USAGE`, `CREATE`
|
||||
- ✅ 表的所有权限:`SELECT`, `INSERT`, `UPDATE`, `DELETE`
|
||||
- ✅ 序列权限:`USAGE`, `SELECT`, `UPDATE`
|
||||
|
||||
**禁止的权限:**
|
||||
- ❌ 超级用户权限:`SUPERUSER`
|
||||
- ❌ 创建角色:`CREATEROLE`
|
||||
- ❌ 创建数据库:`CREATEDB`
|
||||
- ❌ 复制权限:`REPLICATION`
|
||||
- ❌ 访问其他数据库
|
||||
|
||||
### 标准权限 SQL 模板
|
||||
|
||||
```sql
|
||||
-- 1. 创建数据库
|
||||
CREATE DATABASE {project}_{env};
|
||||
|
||||
-- 2. 创建用户(密码应该使用强密码)
|
||||
CREATE USER {project}_{env}_user WITH PASSWORD 'SecurePassword123!';
|
||||
|
||||
-- 3. 撤销默认权限
|
||||
REVOKE ALL ON DATABASE {project}_{env} FROM PUBLIC;
|
||||
|
||||
-- 4. 授予连接权限
|
||||
GRANT CONNECT ON DATABASE {project}_{env} TO {project}_{env}_user;
|
||||
|
||||
-- 5. 连接到目标数据库
|
||||
\c {project}_{env}
|
||||
|
||||
-- 6. 授予 Schema 权限
|
||||
GRANT USAGE, CREATE ON SCHEMA public TO {project}_{env}_user;
|
||||
|
||||
-- 7. 授予表权限(包括未来创建的表)
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO {project}_{env}_user;
|
||||
|
||||
-- 8. 授予序列权限
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO {project}_{env}_user;
|
||||
|
||||
-- 9. 授予函数执行权限
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT EXECUTE ON FUNCTIONS TO {project}_{env}_user;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Secret 内容规范
|
||||
|
||||
### Secret 必须包含的字段
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: postgres-{project}-{env}
|
||||
namespace: {app-namespace}
|
||||
labels:
|
||||
app: {project}
|
||||
env: {env}
|
||||
db-provider: infra-postgres
|
||||
type: Opaque
|
||||
stringData:
|
||||
# 必须字段
|
||||
POSTGRES_HOST: postgres-lb.infra.svc.cluster.local
|
||||
POSTGRES_PORT: "5432"
|
||||
POSTGRES_DB: {project}_{env}
|
||||
POSTGRES_USER: {project}_{env}_user
|
||||
POSTGRES_PASSWORD: {secure-password}
|
||||
|
||||
# 可选:完整连接字符串
|
||||
DATABASE_URL: postgresql://{project}_{env}_user:{password}@postgres-lb.infra.svc.cluster.local:5432/{project}_{env}
|
||||
```
|
||||
|
||||
### 密码要求
|
||||
|
||||
**强密码规则:**
|
||||
- 至少 16 字符
|
||||
- 包含大小写字母
|
||||
- 包含数字
|
||||
- 包含特殊字符
|
||||
- 不包含项目名或常见词汇
|
||||
- 每个环境使用不同密码
|
||||
|
||||
**生成密码示例:**
|
||||
```bash
|
||||
# 生成安全随机密码
|
||||
openssl rand -base64 24
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 完整示例:youngth-guard 项目
|
||||
|
||||
### 场景说明
|
||||
|
||||
项目名称:youngth-guard(留守儿童心理风险感知平台)
|
||||
环境:dev, staging, prod
|
||||
应用 Namespace:dev, staging, prod
|
||||
|
||||
---
|
||||
|
||||
### 示例 1:创建 dev 环境数据库
|
||||
|
||||
#### Step 1: 在 Postgres 中创建数据库和用户
|
||||
|
||||
```bash
|
||||
# 连接到 Postgres
|
||||
kubectl -n infra exec -it postgres-0 -- psql -U postgres
|
||||
|
||||
# 执行以下 SQL
|
||||
```
|
||||
|
||||
```sql
|
||||
-- 1. 创建数据库
|
||||
CREATE DATABASE youngth_guard_dev;
|
||||
|
||||
-- 2. 创建用户(使用强密码)
|
||||
CREATE USER youngth_guard_dev_user WITH PASSWORD 'YgDev2025!SecureP@ssw0rd';
|
||||
|
||||
-- 3. 撤销 PUBLIC 权限
|
||||
REVOKE ALL ON DATABASE youngth_guard_dev FROM PUBLIC;
|
||||
|
||||
-- 4. 授予连接权限
|
||||
GRANT CONNECT ON DATABASE youngth_guard_dev TO youngth_guard_dev_user;
|
||||
|
||||
-- 5. 连接到新数据库
|
||||
\c youngth_guard_dev
|
||||
|
||||
-- 6. 授予 Schema 权限
|
||||
GRANT USAGE, CREATE ON SCHEMA public TO youngth_guard_dev_user;
|
||||
|
||||
-- 7. 授予表权限(包括未来创建的表)
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO youngth_guard_dev_user;
|
||||
|
||||
-- 8. 授予序列权限
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT USAGE, SELECT, UPDATE ON SEQUENCES TO youngth_guard_dev_user;
|
||||
|
||||
-- 9. 授予函数执行权限
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT EXECUTE ON FUNCTIONS TO youngth_guard_dev_user;
|
||||
|
||||
-- 10. 验证权限
|
||||
\l youngth_guard_dev
|
||||
\du youngth_guard_dev_user
|
||||
```
|
||||
|
||||
#### Step 2: 创建 Kubernetes Secret
|
||||
|
||||
```bash
|
||||
# 在 dev namespace 创建 Secret
|
||||
kubectl create secret generic postgres-youngth-guard-dev \
|
||||
--namespace=dev \
|
||||
--from-literal=POSTGRES_HOST=postgres-lb.infra.svc.cluster.local \
|
||||
--from-literal=POSTGRES_PORT=5432 \
|
||||
--from-literal=POSTGRES_DB=youngth_guard_dev \
|
||||
--from-literal=POSTGRES_USER=youngth_guard_dev_user \
|
||||
--from-literal=POSTGRES_PASSWORD='YgDev2025!SecureP@ssw0rd' \
|
||||
--from-literal=DATABASE_URL='postgresql://youngth_guard_dev_user:YgDev2025!SecureP@ssw0rd@postgres-lb.infra.svc.cluster.local:5432/youngth_guard_dev'
|
||||
|
||||
# 添加标签
|
||||
kubectl label secret postgres-youngth-guard-dev \
|
||||
-n dev \
|
||||
app=youngth-guard \
|
||||
env=dev \
|
||||
db-provider=infra-postgres
|
||||
```
|
||||
|
||||
#### Step 3: 在应用中使用 Secret
|
||||
|
||||
**Deployment 配置示例:**
|
||||
|
||||
```yaml
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: youngth-guard-backend
|
||||
namespace: dev
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- name: backend
|
||||
image: ghcr.io/brunogaosz/youngth-guard-backend:latest
|
||||
env:
|
||||
# 方式 1:单独环境变量
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_DB
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
|
||||
# 方式 2:完整连接字符串
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: DATABASE_URL
|
||||
```
|
||||
|
||||
#### Step 4: 验证连接
|
||||
|
||||
```bash
|
||||
# 测试从应用 Pod 连接数据库
|
||||
kubectl run -it --rm psql-test \
|
||||
--image=postgres:16-alpine \
|
||||
--namespace=dev \
|
||||
--env="PGHOST=postgres-lb.infra.svc.cluster.local" \
|
||||
--env="PGPORT=5432" \
|
||||
--env="PGDATABASE=youngth_guard_dev" \
|
||||
--env="PGUSER=youngth_guard_dev_user" \
|
||||
--env="PGPASSWORD=YgDev2025!SecureP@ssw0rd" \
|
||||
--restart=Never \
|
||||
-- psql -c "SELECT current_database(), current_user;"
|
||||
|
||||
# 预期输出:
|
||||
# current_database | current_user
|
||||
# -------------------+--------------------------
|
||||
# youngth_guard_dev | youngth_guard_dev_user
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 示例 2:staging 和 prod 环境
|
||||
|
||||
**遵循相同模式,只需修改环境标识:**
|
||||
|
||||
```sql
|
||||
-- Staging
|
||||
CREATE DATABASE youngth_guard_staging;
|
||||
CREATE USER youngth_guard_staging_user WITH PASSWORD 'YgStaging2025!DifferentP@ss';
|
||||
-- ... 授权步骤相同 ...
|
||||
|
||||
-- Production
|
||||
CREATE DATABASE youngth_guard_prod;
|
||||
CREATE USER youngth_guard_prod_user WITH PASSWORD 'YgProd2025!AnotherSecureP@ss';
|
||||
-- ... 授权步骤相同 ...
|
||||
```
|
||||
|
||||
```bash
|
||||
# Staging Secret
|
||||
kubectl create secret generic postgres-youngth-guard-staging -n staging ...
|
||||
|
||||
# Production Secret
|
||||
kubectl create secret generic postgres-youngth-guard-prod -n prod ...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 规范检查清单
|
||||
|
||||
### 创建新项目数据库前必须确认
|
||||
|
||||
- [ ] 数据库名称符合 `{project}_{env}` 格式
|
||||
- [ ] 用户名称符合 `{project}_{env}_user` 格式
|
||||
- [ ] 密码符合强密码要求(16+ 字符,包含大小写数字特殊字符)
|
||||
- [ ] 已撤销 PUBLIC 权限
|
||||
- [ ] 仅授予必要的权限(CONNECT, USAGE, CRUD)
|
||||
- [ ] 未授予 SUPERUSER / CREATEROLE / CREATEDB 权限
|
||||
- [ ] Secret 名称符合 `postgres-{project}-{env}` 格式
|
||||
- [ ] Secret 创建在正确的 namespace
|
||||
- [ ] Secret 包含所有必需字段
|
||||
- [ ] 已验证应用可以连接
|
||||
|
||||
---
|
||||
|
||||
## 🔍 权限审计
|
||||
|
||||
### 定期审计命令
|
||||
|
||||
```sql
|
||||
-- 1. 列出所有数据库和所有者
|
||||
SELECT datname, pg_catalog.pg_get_userbyid(datdba) as owner
|
||||
FROM pg_catalog.pg_database
|
||||
WHERE datname NOT IN ('postgres', 'template0', 'template1')
|
||||
ORDER BY datname;
|
||||
|
||||
-- 2. 列出所有非系统用户
|
||||
SELECT usename, usesuper, usecreatedb, usecreaterole
|
||||
FROM pg_catalog.pg_user
|
||||
WHERE usename NOT IN ('postgres', 'replication')
|
||||
ORDER BY usename;
|
||||
|
||||
-- 3. 检查危险权限(应该为空)
|
||||
SELECT usename
|
||||
FROM pg_catalog.pg_user
|
||||
WHERE (usesuper = true OR usecreatedb = true OR usecreaterole = true)
|
||||
AND usename != 'postgres';
|
||||
|
||||
-- 4. 查看特定用户权限
|
||||
SELECT grantee, privilege_type
|
||||
FROM information_schema.table_privileges
|
||||
WHERE grantee = 'youngth_guard_dev_user'
|
||||
AND table_schema = 'public'
|
||||
LIMIT 10;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 禁止行为
|
||||
|
||||
以下操作严格禁止:
|
||||
|
||||
1. ❌ **使用 postgres 超级用户连接应用**
|
||||
- 原因:权限过大,安全风险
|
||||
|
||||
2. ❌ **应用用户访问其他数据库**
|
||||
- 原因:破坏隔离性
|
||||
|
||||
3. ❌ **在代码中硬编码密码**
|
||||
- 原因:安全风险,密码泄露
|
||||
|
||||
4. ❌ **多个项目共享同一个数据库**
|
||||
- 原因:无法独立管理,迁移困难
|
||||
|
||||
5. ❌ **在 public schema 外创建对象**
|
||||
- 原因:权限管理复杂化
|
||||
|
||||
6. ❌ **手动修改 Secret(绕过 GitOps)**
|
||||
- 原因:配置漂移,无法追踪
|
||||
|
||||
---
|
||||
|
||||
## 📊 多项目示例矩阵
|
||||
|
||||
| 项目 | 环境 | 数据库名 | 用户名 | Secret 名 | Namespace |
|
||||
|------|------|---------|--------|----------|-----------|
|
||||
| youngth-guard | dev | youngth_guard_dev | youngth_guard_dev_user | postgres-youngth-guard-dev | dev |
|
||||
| youngth-guard | staging | youngth_guard_staging | youngth_guard_staging_user | postgres-youngth-guard-staging | staging |
|
||||
| youngth-guard | prod | youngth_guard_prod | youngth_guard_prod_user | postgres-youngth-guard-prod | prod |
|
||||
| other-project | dev | other_project_dev | other_project_dev_user | postgres-other-project-dev | dev |
|
||||
| api-gateway | prod | api_gateway_prod | api_gateway_prod_user | postgres-api-gateway-prod | prod |
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ 故障排查
|
||||
|
||||
### 常见问题
|
||||
|
||||
#### 问题 1:应用无法连接数据库
|
||||
|
||||
**排查步骤:**
|
||||
```bash
|
||||
# 1. 检查 Secret 是否存在
|
||||
kubectl -n dev get secret postgres-youngth-guard-dev
|
||||
|
||||
# 2. 验证 Secret 内容
|
||||
kubectl -n dev get secret postgres-youngth-guard-dev -o yaml
|
||||
|
||||
# 3. 测试网络连接
|
||||
kubectl run -it --rm nettest \
|
||||
--image=postgres:16-alpine \
|
||||
--namespace=dev \
|
||||
--restart=Never \
|
||||
-- pg_isready -h postgres-lb.infra.svc.cluster.local -p 5432
|
||||
|
||||
# 4. 测试认证
|
||||
kubectl run -it --rm psql-test \
|
||||
--image=postgres:16-alpine \
|
||||
--namespace=dev \
|
||||
--restart=Never \
|
||||
-- psql -h postgres-lb.infra.svc.cluster.local -U youngth_guard_dev_user -d youngth_guard_dev -c "SELECT 1;"
|
||||
```
|
||||
|
||||
#### 问题 2:权限不足错误
|
||||
|
||||
**排查步骤:**
|
||||
```sql
|
||||
-- 连接到数据库
|
||||
\c youngth_guard_dev
|
||||
|
||||
-- 检查当前用户
|
||||
SELECT current_user, current_database();
|
||||
|
||||
-- 检查 Schema 权限
|
||||
SELECT schema_name, schema_owner
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name = 'public';
|
||||
|
||||
-- 检查表权限
|
||||
\dp
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
```sql
|
||||
-- 重新授予权限
|
||||
GRANT USAGE, CREATE ON SCHEMA public TO youngth_guard_dev_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA public
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO youngth_guard_dev_user;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
- PostgreSQL 官方文档:权限管理
|
||||
https://www.postgresql.org/docs/16/user-manag.html
|
||||
|
||||
- Kubernetes Secrets 最佳实践
|
||||
https://kubernetes.io/docs/concepts/configuration/secret/
|
||||
|
||||
- 本平台相关文档:
|
||||
- `docs/architecture-overview.md` - 平台架构说明
|
||||
- `infra/postgres/README.md` - Postgres 部署文档
|
||||
- `infra/postgres/backup/README.md` - 备份恢复文档
|
||||
@@ -1,743 +0,0 @@
|
||||
# 数据库升级 & 回滚操作手册
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本文档提供数据库迁移的标准操作流程,确保升级和回滚操作的安全性和可逆性。
|
||||
|
||||
**核心原则:**
|
||||
- ✅ 所有操作都必须可逆
|
||||
- ✅ 优先选择向前修复(Rollforward),而非回滚(Rollback)
|
||||
- ✅ 回滚应用 ≠ 回滚数据库
|
||||
- ✅ PITR 是最后手段,不是常规操作
|
||||
|
||||
---
|
||||
|
||||
## 🎯 决策树:出现问题时该怎么办
|
||||
|
||||
```
|
||||
检测到问题
|
||||
↓
|
||||
是否是数据库迁移导致?
|
||||
├─ 否 → 回滚应用代码(K8s Deployment)
|
||||
│ 数据库保持不变
|
||||
│
|
||||
└─ 是 → 迁移是否已部分成功?
|
||||
├─ 完全失败 → 修复迁移 SQL → 重新执行
|
||||
│ 数据库状态未变
|
||||
│
|
||||
└─ 部分成功 → 数据是否已损坏/丢失?
|
||||
├─ 否 → 编写修复迁移(Rollforward)
|
||||
│ ↓
|
||||
│ V{N+1}__fix_migration_vN.sql
|
||||
│
|
||||
└─ 是 → 触发 PITR 恢复
|
||||
⚠️ 需要审批 + 数据丢失风险
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📦 STEP 1: 升级前准备
|
||||
|
||||
### 1.1 Pre-Flight Checklist
|
||||
|
||||
```bash
|
||||
# ============================================
|
||||
# 升级前检查清单
|
||||
# ============================================
|
||||
|
||||
# 1. 确认迁移文件已合并到主分支
|
||||
git log --oneline -5
|
||||
git diff main origin/main
|
||||
|
||||
# 2. 检查 Flyway 迁移文件命名
|
||||
ls -la backend-fastapi/migrations/
|
||||
# 预期:V1__, V2__, V3__ ... 连续且符合规范
|
||||
|
||||
# 3. 在本地/测试环境验证迁移
|
||||
cd backend-fastapi
|
||||
flyway -url=jdbc:postgresql://localhost:5432/test_db \
|
||||
-user=test_user \
|
||||
-password=test_pass \
|
||||
-locations=filesystem:./migrations \
|
||||
migrate
|
||||
|
||||
# 4. 检查迁移是否幂等
|
||||
# 再次执行,应该显示 "No migrations to apply"
|
||||
flyway migrate
|
||||
|
||||
# 5. 检查生产数据库状态
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT version, description, installed_on, success FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
|
||||
|
||||
# 6. 确认备份最新
|
||||
kubectl -n infra get cronjob
|
||||
kubectl -n infra logs -l app=pgbackrest --tail=50
|
||||
|
||||
# 7. 查看最近的备份时间
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
pgbackrest --stanza=main info
|
||||
|
||||
# 8. 确认当前没有长时间运行的事务(可能导致锁等待)
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT pid, usename, application_name, state, query_start, query
|
||||
FROM pg_stat_activity
|
||||
WHERE state != 'idle'
|
||||
ORDER BY query_start;"
|
||||
```
|
||||
|
||||
**通过标准:**
|
||||
- ✅ 迁移在测试环境成功执行
|
||||
- ✅ 备份在最近 24 小时内完成
|
||||
- ✅ 无长时间运行的查询(> 30 分钟)
|
||||
- ✅ 迁移执行时间 < 5 分钟(如果 > 5 分钟,考虑维护窗口)
|
||||
|
||||
### 1.2 手动触发完整备份(推荐)
|
||||
|
||||
```bash
|
||||
# 在升级前手动触发一次完整备份
|
||||
kubectl -n infra create job --from=cronjob/pgbackrest-full-backup \
|
||||
manual-backup-before-migration-$(date +%Y%m%d%H%M%S)
|
||||
|
||||
# 等待备份完成
|
||||
kubectl -n infra wait --for=condition=complete --timeout=600s \
|
||||
job/manual-backup-before-migration-$(date +%Y%m%d%H%M%S)
|
||||
|
||||
# 检查备份状态
|
||||
kubectl -n infra logs job/manual-backup-before-migration-$(date +%Y%m%d%H%M%S)
|
||||
```
|
||||
|
||||
### 1.3 通知相关人员
|
||||
|
||||
- [ ] 通知团队即将执行数据库迁移
|
||||
- [ ] 预估迁移时间窗口
|
||||
- [ ] 准备回滚预案(如果需要)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 STEP 2: 执行升级
|
||||
|
||||
### 2.1 自动执行(通过 ArgoCD PreSync)
|
||||
|
||||
**标准流程:**
|
||||
|
||||
```bash
|
||||
# 1. CI 构建新镜像并推送
|
||||
# 2. CI 更新 GitOps 仓库中的镜像 tag
|
||||
git pull origin main
|
||||
|
||||
# 3. ArgoCD 检测到变化,自动触发 Sync
|
||||
# 4. PreSync Hook 执行数据库迁移
|
||||
# 5. 迁移成功 → 部署新应用
|
||||
# 6. 迁移失败 → 阻止部署
|
||||
|
||||
# 监控 ArgoCD Sync 进度
|
||||
kubectl -n argocd get application youngth-guard-backend -w
|
||||
|
||||
# 查看 PreSync Job 日志
|
||||
kubectl -n dev logs -l component=migration -f --tail=100
|
||||
|
||||
# 查看迁移历史
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT version, description, installed_on, success FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
```
|
||||
|
||||
### 2.2 手动执行(紧急情况/测试)
|
||||
|
||||
```bash
|
||||
# 1. 手动创建迁移 Job(移除 PreSync Hook 注解)
|
||||
kubectl -n dev apply -f - <<EOF
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: db-migration-manual-$(date +%Y%m%d%H%M%S)
|
||||
namespace: dev
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: youngth-guard-backend
|
||||
component: migration
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
initContainers:
|
||||
- name: wait-for-db
|
||||
image: postgres:16-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
echo "Waiting for database..."
|
||||
until pg_isready -h \$POSTGRES_HOST -p \$POSTGRES_PORT -U \$POSTGRES_USER; do
|
||||
sleep 2
|
||||
done
|
||||
echo "Database ready!"
|
||||
env:
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: PGPASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
containers:
|
||||
- name: flyway
|
||||
image: flyway/flyway:10-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Running migrations..."
|
||||
flyway -connectRetries=3 migrate
|
||||
echo "Migration completed!"
|
||||
flyway info
|
||||
env:
|
||||
- name: FLYWAY_URL
|
||||
value: "jdbc:postgresql://\$(POSTGRES_HOST):\$(POSTGRES_PORT)/\$(POSTGRES_DB)"
|
||||
- name: FLYWAY_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_USER
|
||||
- name: FLYWAY_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PASSWORD
|
||||
- name: POSTGRES_HOST
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_HOST
|
||||
- name: POSTGRES_PORT
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_PORT
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgres-youngth-guard-dev
|
||||
key: POSTGRES_DB
|
||||
- name: FLYWAY_BASELINE_ON_MIGRATE
|
||||
value: "true"
|
||||
- name: FLYWAY_BASELINE_VERSION
|
||||
value: "0"
|
||||
- name: FLYWAY_OUT_OF_ORDER
|
||||
value: "false"
|
||||
- name: FLYWAY_VALIDATE_ON_MIGRATE
|
||||
value: "true"
|
||||
volumeMounts:
|
||||
- name: migrations
|
||||
mountPath: /flyway/sql
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: migrations
|
||||
configMap:
|
||||
name: db-migrations
|
||||
EOF
|
||||
|
||||
# 2. 查看 Job 日志
|
||||
kubectl -n dev logs -f job/db-migration-manual-$(date +%Y%m%d%H%M%S)
|
||||
|
||||
# 3. 验证迁移成功
|
||||
kubectl -n dev get job db-migration-manual-$(date +%Y%m%d%H%M%S)
|
||||
```
|
||||
|
||||
### 2.3 升级后验证
|
||||
|
||||
```bash
|
||||
# 1. 检查迁移 Job 状态
|
||||
kubectl -n dev get job -l component=migration
|
||||
|
||||
# 预期输出:
|
||||
# NAME COMPLETIONS DURATION AGE
|
||||
# db-migration 1/1 15s 2m
|
||||
|
||||
# 2. 验证 Flyway 历史
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT version, description, installed_on, success FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
|
||||
# 预期:所有迁移 success = true
|
||||
|
||||
# 3. 检查应用 Pod 状态
|
||||
kubectl -n dev get pods -l app=youngth-guard-backend
|
||||
|
||||
# 预期:所有 Pod Running,Ready 1/1
|
||||
|
||||
# 4. 查看应用日志
|
||||
kubectl -n dev logs -l app=youngth-guard-backend --tail=50
|
||||
|
||||
# 预期:无数据库连接错误
|
||||
|
||||
# 5. 执行冒烟测试
|
||||
curl -X GET https://dev.youngth-guard.example.com/health
|
||||
# 预期:{"status": "ok"}
|
||||
|
||||
# 6. 验证数据库表结构
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"\dt" # 列出所有表
|
||||
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"\d users" # 查看 users 表结构
|
||||
```
|
||||
|
||||
**验证通过标准:**
|
||||
- ✅ 迁移 Job 成功完成(COMPLETIONS 1/1)
|
||||
- ✅ Flyway 历史中所有迁移 success = true
|
||||
- ✅ 应用 Pod 全部 Running
|
||||
- ✅ 应用日志无错误
|
||||
- ✅ 健康检查接口返回正常
|
||||
|
||||
---
|
||||
|
||||
## ⏮️ STEP 3: 回滚策略
|
||||
|
||||
### 3.1 场景 1:应用代码有 Bug,数据库迁移成功
|
||||
|
||||
**问题:** 新版本应用有功能性 Bug,但数据库迁移已成功。
|
||||
|
||||
**策略:回滚应用,保留数据库**
|
||||
|
||||
```bash
|
||||
# ============================================
|
||||
# 回滚应用到上一个版本
|
||||
# ============================================
|
||||
|
||||
# 方法 1:通过 ArgoCD 回滚
|
||||
kubectl -n argocd patch application youngth-guard-backend \
|
||||
--type merge \
|
||||
-p '{"operation": {"sync": {"revision": "previous-commit-sha"}}}'
|
||||
|
||||
# 方法 2:通过 Kubectl 回滚 Deployment
|
||||
kubectl -n dev rollout undo deployment/youngth-guard-backend
|
||||
|
||||
# 方法 3:手动更新镜像 tag
|
||||
cd ljwx-deploy
|
||||
git revert HEAD # 撤销最新的镜像 tag 更新
|
||||
git push origin main
|
||||
|
||||
# ArgoCD 自动检测并回滚应用
|
||||
|
||||
# 验证回滚
|
||||
kubectl -n dev rollout status deployment/youngth-guard-backend
|
||||
kubectl -n dev get pods -l app=youngth-guard-backend
|
||||
```
|
||||
|
||||
**重要:**
|
||||
- ❌ **不要回滚数据库迁移**
|
||||
- ✅ 旧版本应用代码必须兼容新数据库 Schema(这就是 Expand/Contract 的意义)
|
||||
- ✅ 如果旧代码不兼容,编写修复迁移(Rollforward)
|
||||
|
||||
### 3.2 场景 2:数据库迁移失败,数据库状态未变
|
||||
|
||||
**问题:** Flyway 迁移执行失败,数据库保持原状态。
|
||||
|
||||
**策略:修复迁移 SQL,重新执行**
|
||||
|
||||
```bash
|
||||
# ============================================
|
||||
# 场景:V5 迁移失败
|
||||
# ============================================
|
||||
|
||||
# 1. 查看失败的迁移
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT * FROM flyway_schema_history WHERE success = false;"
|
||||
|
||||
# 输出示例:
|
||||
# installed_rank | version | description | success
|
||||
# 5 | 5 | add_email_column | false
|
||||
|
||||
# 2. 查看错误日志
|
||||
kubectl -n dev logs -l component=migration --tail=100
|
||||
|
||||
# 3. 修复迁移文件
|
||||
# 编辑 backend-fastapi/migrations/V5__add_email_column.sql
|
||||
# 修复 SQL 语法错误
|
||||
|
||||
# 4. 删除失败记录(让 Flyway 重新尝试)
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"DELETE FROM flyway_schema_history WHERE version = '5' AND success = false;"
|
||||
|
||||
# 5. 重新执行迁移
|
||||
kubectl -n dev delete job db-migration # 删除旧 Job
|
||||
# ArgoCD 会自动重新创建 PreSync Job
|
||||
|
||||
# 或手动触发
|
||||
argocd app sync youngth-guard-backend --prune
|
||||
|
||||
# 6. 验证迁移成功
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT * FROM flyway_schema_history WHERE version = '5';"
|
||||
```
|
||||
|
||||
### 3.3 场景 3:数据库迁移部分成功,数据未损坏
|
||||
|
||||
**问题:** V5 迁移执行了一部分后失败,数据库处于中间状态但数据未丢失。
|
||||
|
||||
**策略:Rollforward(编写修复迁移)**
|
||||
|
||||
```bash
|
||||
# ============================================
|
||||
# 场景:V5 添加了列但未创建索引就失败了
|
||||
# ============================================
|
||||
|
||||
# 1. 评估当前数据库状态
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"\d users"
|
||||
|
||||
# 发现:email 列已添加,但缺少索引
|
||||
|
||||
# 2. 标记 V5 为失败(如果未自动标记)
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"UPDATE flyway_schema_history SET success = false WHERE version = '5';"
|
||||
|
||||
# 3. 编写修复迁移
|
||||
# 创建 backend-fastapi/migrations/V6__fix_v5_migration.sql
|
||||
cat > backend-fastapi/migrations/V6__fix_v5_migration.sql <<'EOF'
|
||||
-- ============================================
|
||||
-- Migration: V6__fix_v5_migration
|
||||
-- Description: 修复 V5 迁移中断导致的缺失索引
|
||||
-- ============================================
|
||||
|
||||
-- 创建缺失的索引(幂等)
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
|
||||
-- 验证数据完整性
|
||||
DO $$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_indexes WHERE indexname = 'idx_users_email') THEN
|
||||
RAISE EXCEPTION 'Index idx_users_email still missing!';
|
||||
END IF;
|
||||
END $$;
|
||||
EOF
|
||||
|
||||
# 4. 删除 V5 失败记录
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"DELETE FROM flyway_schema_history WHERE version = '5';"
|
||||
|
||||
# 5. 提交修复并部署
|
||||
git add backend-fastapi/migrations/V6__fix_v5_migration.sql
|
||||
git commit -m "fix: V6 修复 V5 迁移中断"
|
||||
git push origin main
|
||||
|
||||
# 6. ArgoCD 自动执行 V6 迁移
|
||||
```
|
||||
|
||||
**原则:**
|
||||
- ✅ **优先选择 Rollforward**(向前修复)
|
||||
- ✅ 编写 V{N+1} 修复 V{N} 的问题
|
||||
- ❌ **不要直接修改已执行的迁移文件**
|
||||
- ❌ **不要手动修改数据库 Schema**
|
||||
|
||||
### 3.4 场景 4:数据损坏或丢失 → 触发 PITR
|
||||
|
||||
**问题:** 迁移导致数据损坏或意外删除。
|
||||
|
||||
**⚠️ 警告:这是最后手段,会丢失 PITR 时间点之后的数据!**
|
||||
|
||||
**前提条件:**
|
||||
- [ ] 数据已损坏且无法通过 SQL 修复
|
||||
- [ ] 业务方批准数据丢失风险
|
||||
- [ ] 已评估 PITR 后需要手动补录的数据量
|
||||
- [ ] 确认 PITR 目标时间点的备份存在
|
||||
|
||||
**执行步骤:**
|
||||
|
||||
```bash
|
||||
# ============================================
|
||||
# PITR 恢复到迁移前的时间点
|
||||
# ============================================
|
||||
|
||||
# 1. 确定目标时间点(迁移开始前)
|
||||
TARGET_TIME="2025-01-18 10:30:00"
|
||||
|
||||
# 2. 停止应用(防止新数据写入)
|
||||
kubectl -n dev scale deployment/youngth-guard-backend --replicas=0
|
||||
|
||||
# 3. 停止 Postgres StatefulSet
|
||||
kubectl -n infra scale statefulset postgres --replicas=0
|
||||
|
||||
# 4. 创建 PITR 恢复 Job
|
||||
cat > /tmp/pitr-restore.yaml <<EOF
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: postgres-pitr-restore
|
||||
namespace: infra
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
template:
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: pgbackrest
|
||||
image: pgbackrest/pgbackrest:latest
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Starting PITR restore to ${TARGET_TIME}"
|
||||
|
||||
# 恢复到指定时间点
|
||||
pgbackrest --stanza=main \
|
||||
--type=time \
|
||||
--target="${TARGET_TIME}" \
|
||||
--delta \
|
||||
restore
|
||||
|
||||
echo "PITR restore completed!"
|
||||
env:
|
||||
- name: TARGET_TIME
|
||||
value: "${TARGET_TIME}"
|
||||
volumeMounts:
|
||||
- name: postgres-data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
- name: backup
|
||||
mountPath: /backup
|
||||
- name: pgbackrest-config
|
||||
mountPath: /etc/pgbackrest
|
||||
volumes:
|
||||
- name: postgres-data
|
||||
persistentVolumeClaim:
|
||||
claimName: postgres-data-postgres-0
|
||||
- name: backup
|
||||
persistentVolumeClaim:
|
||||
claimName: pgbackrest-backup
|
||||
- name: pgbackrest-config
|
||||
configMap:
|
||||
name: pgbackrest-config
|
||||
EOF
|
||||
|
||||
kubectl apply -f /tmp/pitr-restore.yaml
|
||||
|
||||
# 5. 等待恢复完成
|
||||
kubectl -n infra wait --for=condition=complete --timeout=1800s job/postgres-pitr-restore
|
||||
|
||||
# 6. 查看恢复日志
|
||||
kubectl -n infra logs job/postgres-pitr-restore
|
||||
|
||||
# 7. 启动 Postgres
|
||||
kubectl -n infra scale statefulset postgres --replicas=1
|
||||
|
||||
# 8. 等待 Postgres 就绪
|
||||
kubectl -n infra wait --for=condition=ready pod/postgres-0 --timeout=300s
|
||||
|
||||
# 9. 验证数据库状态
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT version, description, installed_on FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 5;"
|
||||
|
||||
# 10. 验证业务数据
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT count(*) FROM users;"
|
||||
|
||||
# 11. 清理失败的迁移记录
|
||||
# 根据 Flyway 历史决定是否需要删除失败的迁移记录
|
||||
|
||||
# 12. 重新启动应用(使用旧版本镜像)
|
||||
kubectl -n dev scale deployment/youngth-guard-backend --replicas=3
|
||||
|
||||
# 13. 验证应用功能
|
||||
kubectl -n dev get pods -l app=youngth-guard-backend
|
||||
curl https://dev.youngth-guard.example.com/health
|
||||
```
|
||||
|
||||
**PITR 后操作:**
|
||||
- [ ] 评估丢失的数据(PITR 时间点之后的写入)
|
||||
- [ ] 手动补录关键数据
|
||||
- [ ] 修复导致问题的迁移文件
|
||||
- [ ] 在测试环境重新验证修复后的迁移
|
||||
- [ ] 部署修复后的迁移
|
||||
|
||||
---
|
||||
|
||||
## 🚫 禁止行为清单
|
||||
|
||||
### 迁移期间绝对禁止的操作
|
||||
|
||||
1. **❌ 直接在生产数据库执行 DDL**
|
||||
```sql
|
||||
-- 禁止!
|
||||
psql -U postgres -d youngth_guard_prod
|
||||
ALTER TABLE users ADD COLUMN ...;
|
||||
```
|
||||
**正确做法:** 所有 Schema 变更必须通过 Flyway 迁移文件
|
||||
|
||||
2. **❌ 修改已执行的迁移文件**
|
||||
```bash
|
||||
# 禁止!
|
||||
vim backend-fastapi/migrations/V3__add_column.sql # V3 已执行
|
||||
git commit -m "fix V3"
|
||||
```
|
||||
**正确做法:** 创建新的 V4 修复 V3 的问题
|
||||
|
||||
3. **❌ 手动修改 `flyway_schema_history` 表(非紧急情况)**
|
||||
```sql
|
||||
-- 禁止!(除非明确知道后果)
|
||||
UPDATE flyway_schema_history SET success = true WHERE version = '5';
|
||||
```
|
||||
**正确做法:** 删除失败记录,修复 SQL,重新执行
|
||||
|
||||
4. **❌ 在迁移中执行大规模数据修改(无批量处理)**
|
||||
```sql
|
||||
-- 禁止!会锁表很久
|
||||
UPDATE users SET status = 'active'; -- 100万行
|
||||
```
|
||||
**正确做法:** 使用批量处理(参考 `docs/flyway-guide/README.md`)
|
||||
|
||||
5. **❌ 在迁移中删除表/列(无 Expand/Contract)**
|
||||
```sql
|
||||
-- 禁止!
|
||||
DROP TABLE old_table;
|
||||
ALTER TABLE users DROP COLUMN old_field;
|
||||
```
|
||||
**正确做法:** 先重命名为 `_deprecated`,等待几个版本后再删除
|
||||
|
||||
6. **❌ 回滚数据库迁移(非 PITR 场景)**
|
||||
```bash
|
||||
# 禁止!
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"ALTER TABLE users DROP COLUMN email;" # 试图"撤销" V5
|
||||
```
|
||||
**正确做法:** 使用 Expand/Contract 模式,或编写修复迁移
|
||||
|
||||
7. **❌ 在高峰期执行耗时迁移**
|
||||
```bash
|
||||
# 禁止!
|
||||
# 14:00 (业务高峰) 执行需要 10 分钟的迁移
|
||||
```
|
||||
**正确做法:** 在凌晨低峰期执行,或使用在线 Schema 变更工具
|
||||
|
||||
8. **❌ 未备份就执行高风险迁移**
|
||||
```bash
|
||||
# 禁止!
|
||||
# 没有检查备份就执行 DROP/TRUNCATE
|
||||
```
|
||||
**正确做法:** 先验证最近备份,必要时手动触发完整备份
|
||||
|
||||
9. **❌ 跳过测试环境直接在生产执行**
|
||||
```bash
|
||||
# 禁止!
|
||||
# 新迁移文件直接部署到生产
|
||||
```
|
||||
**正确做法:** dev → staging → prod 逐级验证
|
||||
|
||||
10. **❌ 在迁移失败时继续部署应用**
|
||||
```bash
|
||||
# 禁止!
|
||||
kubectl -n dev set image deployment/youngth-guard-backend \
|
||||
backend=ghcr.io/brunogaosz/youngth-guard-backend:v2 # 迁移失败时强制部署
|
||||
```
|
||||
**正确做法:** 修复迁移后再部署,或回滚应用到旧版本
|
||||
|
||||
---
|
||||
|
||||
## 📋 快速参考卡
|
||||
|
||||
### 常见场景速查表
|
||||
|
||||
| 场景 | 策略 | 操作 |
|
||||
|------|------|------|
|
||||
| 应用 Bug,DB 正常 | 回滚应用 | `kubectl rollout undo deployment/xxx` |
|
||||
| 迁移完全失败 | 修复 SQL 重试 | 删除失败记录 → 修复文件 → 重新执行 |
|
||||
| 迁移部分成功 | Rollforward | 编写 V{N+1} 修复迁移 |
|
||||
| 数据损坏 | PITR | 恢复到迁移前时间点 |
|
||||
| 迁移太慢 | 批量处理 | 参考 Flyway 指南中的批量更新示例 |
|
||||
| 需要回滚 Schema | Expand/Contract | 先部署代码兼容 → 再删除旧 Schema |
|
||||
|
||||
### 故障排查命令速查
|
||||
|
||||
```bash
|
||||
# 查看迁移历史
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT * FROM flyway_schema_history ORDER BY installed_rank;"
|
||||
|
||||
# 查看迁移 Job 日志
|
||||
kubectl -n dev logs -l component=migration --tail=100
|
||||
|
||||
# 检查应用状态
|
||||
kubectl -n dev get pods -l app=youngth-guard-backend
|
||||
kubectl -n dev logs -l app=youngth-guard-backend --tail=50
|
||||
|
||||
# 查看最近备份
|
||||
kubectl -n infra exec postgres-0 -- pgbackrest --stanza=main info
|
||||
|
||||
# 检查长时间运行的查询
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c \
|
||||
"SELECT pid, usename, query_start, state, query FROM pg_stat_activity WHERE state != 'idle';"
|
||||
|
||||
# 检查表结构
|
||||
kubectl -n infra exec postgres-0 -- \
|
||||
psql -U postgres -d youngth_guard_dev -c "\d table_name"
|
||||
|
||||
# 回滚应用
|
||||
kubectl -n dev rollout undo deployment/youngth-guard-backend
|
||||
|
||||
# 手动触发备份
|
||||
kubectl -n infra create job --from=cronjob/pgbackrest-full-backup manual-backup-$(date +%Y%m%d%H%M%S)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📞 升级失败联系流程
|
||||
|
||||
1. **立即操作:**
|
||||
- 停止继续部署(如果尚未完成)
|
||||
- 截图错误日志
|
||||
- 记录时间点
|
||||
|
||||
2. **评估影响:**
|
||||
- 业务功能是否受影响?
|
||||
- 数据是否损坏?
|
||||
- 用户是否受影响?
|
||||
|
||||
3. **根据决策树选择策略:**
|
||||
- 应用 Bug → 回滚应用
|
||||
- 迁移失败 → 修复重试
|
||||
- 数据损坏 → 联系 DBA + PITR
|
||||
|
||||
4. **事后复盘:**
|
||||
- 记录故障原因
|
||||
- 更新测试用例
|
||||
- 改进迁移流程
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- `docs/flyway-guide/README.md` - Flyway 规范和 Expand/Contract 模式
|
||||
- `docs/argocd-migration/README.md` - PreSync Hook 工作流程
|
||||
- `infra/postgres/backup/README.md` - 备份和 PITR 详细步骤
|
||||
- `docs/architecture-overview.md` - 平台架构说明
|
||||
@@ -1,548 +0,0 @@
|
||||
# Flyway 数据库版本化规范
|
||||
|
||||
## 📋 概述
|
||||
|
||||
Flyway 是数据库迁移管理工具,用于:
|
||||
- 版本化数据库 Schema
|
||||
- 自动化数据库升级
|
||||
- 跟踪迁移历史
|
||||
- 支持回滚(通过向后兼容设计)
|
||||
|
||||
**核心原则:**
|
||||
- ✅ 所有 Schema 变更必须通过 Flyway
|
||||
- ✅ 迁移必须向后兼容(支持新旧代码共存)
|
||||
- ✅ 禁止直接修改数据库
|
||||
- ✅ 每个迁移都有明确的版本号
|
||||
|
||||
---
|
||||
|
||||
## 📁 标准目录结构
|
||||
|
||||
### 项目中的位置
|
||||
|
||||
```
|
||||
youngth-guard/
|
||||
└── backend-fastapi/
|
||||
└── migrations/ # Flyway 迁移目录
|
||||
├── V1__initial_schema.sql
|
||||
├── V2__add_users_table.sql
|
||||
├── V3__add_email_column.sql
|
||||
├── V4__add_email_index.sql
|
||||
└── V5__remove_deprecated_column.sql
|
||||
```
|
||||
|
||||
### 多模块项目
|
||||
|
||||
```
|
||||
project-root/
|
||||
├── service-a/
|
||||
│ └── migrations/
|
||||
│ ├── V1__service_a_init.sql
|
||||
│ └── V2__service_a_update.sql
|
||||
└── service-b/
|
||||
└── migrations/
|
||||
├── V1__service_b_init.sql
|
||||
└── V2__service_b_update.sql
|
||||
```
|
||||
|
||||
**注意:** 每个服务使用独立的数据库,因此版本号独立管理。
|
||||
|
||||
---
|
||||
|
||||
## 🏷️ 命名规范
|
||||
|
||||
### 文件命名格式
|
||||
|
||||
```
|
||||
V{version}__{description}.sql
|
||||
```
|
||||
|
||||
**规则:**
|
||||
|
||||
1. **V(大写)** - 固定前缀,表示 Versioned migration
|
||||
2. **{version}** - 版本号,格式:
|
||||
- 单数字:`V1`, `V2`, `V3` ...
|
||||
- 点分版本:`V1.1`, `V1.2`, `V2.1` ...
|
||||
- 时间戳:`V20251218101500` (不推荐,难以理解)
|
||||
3. **__(双下划线)** - 分隔符
|
||||
4. **{description}** - 描述,使用下划线分隔单词
|
||||
5. **.sql** - 文件扩展名
|
||||
|
||||
### 正确示例
|
||||
|
||||
```
|
||||
✅ V1__initial_schema.sql
|
||||
✅ V2__add_users_table.sql
|
||||
✅ V3__add_email_column.sql
|
||||
✅ V4__migrate_old_data.sql
|
||||
✅ V5__add_index_on_email.sql
|
||||
✅ V6__remove_deprecated_field.sql
|
||||
✅ V1.1__hotfix_user_constraint.sql
|
||||
```
|
||||
|
||||
### 错误示例
|
||||
|
||||
```
|
||||
❌ v1__initial.sql # 小写 v
|
||||
❌ V1_initial_schema.sql # 单下划线
|
||||
❌ 1__initial_schema.sql # 缺少 V
|
||||
❌ V1-initial-schema.sql # 使用连字符
|
||||
❌ V1__Initial Schema.sql # 包含空格
|
||||
❌ V1__添加用户表.sql # 非 ASCII 字符
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📝 迁移内容规范
|
||||
|
||||
### 每个迁移文件应该
|
||||
|
||||
- ✅ 完成一个独立的功能
|
||||
- ✅ 包含清晰的注释
|
||||
- ✅ 幂等(使用 `IF NOT EXISTS` / `IF EXISTS`)
|
||||
- ✅ 可逆(通过后续迁移回滚)
|
||||
- ✅ 测试过(在本地和测试环境)
|
||||
|
||||
### 迁移文件模板
|
||||
|
||||
```sql
|
||||
-- ============================================
|
||||
-- Migration: V{version}__{description}
|
||||
-- Author: {your-name}
|
||||
-- Date: {date}
|
||||
-- Description:
|
||||
-- {详细描述这个迁移做什么}
|
||||
-- {为什么需要这个变更}
|
||||
-- ============================================
|
||||
|
||||
-- 开始事务(可选,Flyway 默认会包裹在事务中)
|
||||
BEGIN;
|
||||
|
||||
-- 你的 SQL 语句
|
||||
-- ...
|
||||
|
||||
-- 提交
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔄 Expand/Contract 模式
|
||||
|
||||
**Expand/Contract** 是实现零停机升级的关键模式。
|
||||
|
||||
### 模式说明
|
||||
|
||||
1. **Expand(扩展)** - 添加新结构,保留旧结构
|
||||
2. **Deploy(部署)** - 部署新代码,同时支持新旧结构
|
||||
3. **Contract(收缩)** - 删除旧结构
|
||||
|
||||
### 示例:添加新列并重命名
|
||||
|
||||
**场景:** 将 `users.name` 列重命名为 `users.full_name`
|
||||
|
||||
#### ❌ 错误做法(会导致停机)
|
||||
|
||||
```sql
|
||||
-- V2__rename_column.sql
|
||||
ALTER TABLE users RENAME COLUMN name TO full_name;
|
||||
```
|
||||
|
||||
**问题:** 旧代码仍在使用 `name` 列,会立即报错。
|
||||
|
||||
#### ✅ 正确做法(Expand/Contract)
|
||||
|
||||
**Step 1: Expand - 添加新列**
|
||||
|
||||
```sql
|
||||
-- V2__add_full_name_column.sql
|
||||
-- Expand: 添加新列,保留旧列
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS full_name VARCHAR(255);
|
||||
|
||||
-- 复制数据到新列
|
||||
UPDATE users SET full_name = name WHERE full_name IS NULL;
|
||||
|
||||
-- 创建触发器保持同步(可选)
|
||||
CREATE OR REPLACE FUNCTION sync_user_name()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.name IS DISTINCT FROM OLD.name THEN
|
||||
NEW.full_name := NEW.name;
|
||||
END IF;
|
||||
IF NEW.full_name IS DISTINCT FROM OLD.full_name THEN
|
||||
NEW.name := NEW.full_name;
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER sync_user_name_trigger
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION sync_user_name();
|
||||
```
|
||||
|
||||
**Step 2: Deploy - 部署新代码**
|
||||
|
||||
应用代码更新为使用 `full_name`,但数据库同时支持两列。
|
||||
|
||||
**Step 3: Contract - 删除旧列**
|
||||
|
||||
```sql
|
||||
-- V3__remove_name_column.sql
|
||||
-- Contract: 删除旧列(确认新代码已稳定运行)
|
||||
|
||||
-- 删除触发器
|
||||
DROP TRIGGER IF EXISTS sync_user_name_trigger ON users;
|
||||
DROP FUNCTION IF EXISTS sync_user_name();
|
||||
|
||||
-- 删除旧列
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS name;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚫 禁止使用的 SQL
|
||||
|
||||
以下 SQL 操作**禁止**直接在 Flyway 迁移中使用:
|
||||
|
||||
### 1. 破坏性操作(生产环境)
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:直接删除表
|
||||
DROP TABLE users;
|
||||
|
||||
-- ✅ 推荐:重命名表为 _deprecated,稍后删除
|
||||
ALTER TABLE users RENAME TO users_deprecated_20251218;
|
||||
-- 在后续迁移中删除:
|
||||
-- DROP TABLE IF EXISTS users_deprecated_20251218;
|
||||
```
|
||||
|
||||
### 2. 直接删除列(无过渡期)
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:直接删除列
|
||||
ALTER TABLE users DROP COLUMN email;
|
||||
|
||||
-- ✅ 推荐:使用 Expand/Contract
|
||||
-- V2: 标记为废弃(添加注释)
|
||||
-- V3: 应用不再使用
|
||||
-- V4: 删除列
|
||||
```
|
||||
|
||||
### 3. 修改列类型(无向后兼容)
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:直接修改类型
|
||||
ALTER TABLE users ALTER COLUMN age TYPE INTEGER;
|
||||
|
||||
-- ✅ 推荐:
|
||||
-- V2: 添加新列 age_int
|
||||
-- V3: 迁移数据
|
||||
-- V4: 应用使用新列
|
||||
-- V5: 删除旧列
|
||||
```
|
||||
|
||||
### 4. 添加 NOT NULL 约束(无默认值)
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:直接添加 NOT NULL
|
||||
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
|
||||
|
||||
-- ✅ 推荐:
|
||||
-- V2: 添加列(允许 NULL)
|
||||
-- V3: 填充数据
|
||||
-- V4: 添加 NOT NULL 约束
|
||||
```
|
||||
|
||||
### 5. 删除索引(无影响评估)
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:直接删除索引
|
||||
DROP INDEX idx_users_email;
|
||||
|
||||
-- ✅ 推荐:
|
||||
-- 1. 评估性能影响
|
||||
-- 2. 在低峰期执行
|
||||
-- 3. 监控查询性能
|
||||
-- 4. 准备回滚计划
|
||||
```
|
||||
|
||||
### 6. 大规模数据更新
|
||||
|
||||
```sql
|
||||
-- ❌ 禁止:一次性更新所有数据
|
||||
UPDATE users SET status = 'active';
|
||||
|
||||
-- ✅ 推荐:批量更新
|
||||
DO $$
|
||||
DECLARE
|
||||
batch_size INT := 1000;
|
||||
total_rows INT;
|
||||
BEGIN
|
||||
LOOP
|
||||
UPDATE users
|
||||
SET status = 'active'
|
||||
WHERE id IN (
|
||||
SELECT id FROM users
|
||||
WHERE status IS NULL
|
||||
LIMIT batch_size
|
||||
);
|
||||
|
||||
GET DIAGNOSTICS total_rows = ROW_COUNT;
|
||||
EXIT WHEN total_rows = 0;
|
||||
|
||||
PERFORM pg_sleep(0.1); -- 避免长时间锁表
|
||||
END LOOP;
|
||||
END $$;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 迁移示例
|
||||
|
||||
### 示例 1:初始化 Schema
|
||||
|
||||
```sql
|
||||
-- V1__initial_schema.sql
|
||||
-- ============================================
|
||||
-- 初始化数据库 Schema
|
||||
-- ============================================
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) NOT NULL UNIQUE,
|
||||
email VARCHAR(255) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_users_created_at ON users(created_at);
|
||||
|
||||
-- 评估记录表
|
||||
CREATE TABLE IF NOT EXISTS assessments (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
score INTEGER NOT NULL CHECK (score >= 0 AND score <= 100),
|
||||
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_user_id ON assessments(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_status ON assessments(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_assessments_created_at ON assessments(created_at);
|
||||
|
||||
-- 审计日志表
|
||||
CREATE TABLE IF NOT EXISTS audit_logs (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
user_id INTEGER REFERENCES users(id) ON DELETE SET NULL,
|
||||
action VARCHAR(50) NOT NULL,
|
||||
entity_type VARCHAR(50) NOT NULL,
|
||||
entity_id INTEGER,
|
||||
changes JSONB,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_user_id ON audit_logs(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_logs_entity ON audit_logs(entity_type, entity_id);
|
||||
|
||||
COMMENT ON TABLE users IS '用户表';
|
||||
COMMENT ON TABLE assessments IS '心理评估记录表';
|
||||
COMMENT ON TABLE audit_logs IS '审计日志表';
|
||||
```
|
||||
|
||||
### 示例 2:添加新列(Expand)
|
||||
|
||||
```sql
|
||||
-- V2__add_phone_column.sql
|
||||
-- ============================================
|
||||
-- 添加手机号列
|
||||
-- 使用 Expand 模式,保证向后兼容
|
||||
-- ============================================
|
||||
|
||||
-- 添加列(允许 NULL)
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS phone VARCHAR(20);
|
||||
|
||||
-- 创建索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_phone ON users(phone) WHERE phone IS NOT NULL;
|
||||
|
||||
-- 添加注释
|
||||
COMMENT ON COLUMN users.phone IS '用户手机号(可选)';
|
||||
```
|
||||
|
||||
### 示例 3:数据迁移(Expand/Contract - Part 1)
|
||||
|
||||
```sql
|
||||
-- V3__add_profile_data_column.sql
|
||||
-- ============================================
|
||||
-- 添加 profile_data JSON 列
|
||||
-- 准备将结构化数据迁移到 JSON
|
||||
-- ============================================
|
||||
|
||||
-- Expand: 添加新列
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS profile_data JSONB DEFAULT '{}'::jsonb;
|
||||
|
||||
-- 迁移现有数据
|
||||
UPDATE users
|
||||
SET profile_data = jsonb_build_object(
|
||||
'email', email,
|
||||
'phone', COALESCE(phone, ''),
|
||||
'created_at', created_at::text
|
||||
)
|
||||
WHERE profile_data = '{}'::jsonb;
|
||||
|
||||
-- 创建 GIN 索引支持 JSON 查询
|
||||
CREATE INDEX IF NOT EXISTS idx_users_profile_data ON users USING GIN (profile_data);
|
||||
|
||||
-- 注意:此时 email 和 phone 列仍然存在
|
||||
-- 应用代码需要逐步迁移到使用 profile_data
|
||||
```
|
||||
|
||||
### 示例 4:删除旧列(Expand/Contract - Part 2)
|
||||
|
||||
```sql
|
||||
-- V4__remove_old_profile_columns.sql
|
||||
-- ============================================
|
||||
-- 删除已迁移到 JSON 的旧列
|
||||
-- 前提:应用代码已完全切换到 profile_data
|
||||
-- ============================================
|
||||
|
||||
-- Contract: 删除旧列
|
||||
-- 注意:执行前确认应用已不再使用这些列
|
||||
|
||||
-- 先删除依赖的索引
|
||||
DROP INDEX IF EXISTS idx_users_email;
|
||||
DROP INDEX IF EXISTS idx_users_phone;
|
||||
|
||||
-- 删除列
|
||||
-- ALTER TABLE users DROP COLUMN IF EXISTS email; -- 保留 email 作为主要联系方式
|
||||
ALTER TABLE users DROP COLUMN IF EXISTS phone;
|
||||
|
||||
COMMENT ON COLUMN users.profile_data IS '用户扩展信息(JSON 格式)';
|
||||
```
|
||||
|
||||
### 示例 5:添加约束
|
||||
|
||||
```sql
|
||||
-- V5__add_user_status_constraint.sql
|
||||
-- ============================================
|
||||
-- 添加用户状态列和约束
|
||||
-- ============================================
|
||||
|
||||
-- 添加状态列
|
||||
ALTER TABLE users ADD COLUMN IF NOT EXISTS status VARCHAR(20) DEFAULT 'active';
|
||||
|
||||
-- 填充现有数据
|
||||
UPDATE users SET status = 'active' WHERE status IS NULL;
|
||||
|
||||
-- 添加 CHECK 约束
|
||||
ALTER TABLE users ADD CONSTRAINT check_user_status
|
||||
CHECK (status IN ('active', 'inactive', 'suspended', 'deleted'));
|
||||
|
||||
-- 添加索引
|
||||
CREATE INDEX IF NOT EXISTS idx_users_status ON users(status);
|
||||
|
||||
COMMENT ON COLUMN users.status IS '用户状态:active|inactive|suspended|deleted';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ✅ 最佳实践 Checklist
|
||||
|
||||
### 编写迁移前
|
||||
|
||||
- [ ] 确认变更的业务需求
|
||||
- [ ] 设计向后兼容的方案
|
||||
- [ ] 评估对现有代码的影响
|
||||
- [ ] 考虑回滚策略
|
||||
- [ ] 评估性能影响(特别是大表操作)
|
||||
|
||||
### 编写迁移时
|
||||
|
||||
- [ ] 使用正确的命名格式
|
||||
- [ ] 添加清晰的注释
|
||||
- [ ] 使用 `IF EXISTS` / `IF NOT EXISTS`
|
||||
- [ ] 遵循 Expand/Contract 模式
|
||||
- [ ] 避免大规模数据更新(或使用批量处理)
|
||||
- [ ] 添加适当的索引
|
||||
|
||||
### 测试迁移
|
||||
|
||||
- [ ] 在本地数据库测试
|
||||
- [ ] 在测试环境验证
|
||||
- [ ] 验证旧代码仍能运行
|
||||
- [ ] 验证新代码正常工作
|
||||
- [ ] 检查性能影响
|
||||
- [ ] 准备回滚迁移
|
||||
|
||||
### 部署迁移
|
||||
|
||||
- [ ] 在低峰期执行
|
||||
- [ ] 备份数据库
|
||||
- [ ] 监控执行进度
|
||||
- [ ] 验证应用功能
|
||||
- [ ] 准备紧急回滚
|
||||
|
||||
---
|
||||
|
||||
## 🔍 故障排查
|
||||
|
||||
### 问题 1:迁移失败
|
||||
|
||||
```bash
|
||||
# 查看 Flyway 历史
|
||||
SELECT * FROM flyway_schema_history ORDER BY installed_rank DESC LIMIT 10;
|
||||
|
||||
# 查看失败的迁移
|
||||
SELECT * FROM flyway_schema_history WHERE success = false;
|
||||
```
|
||||
|
||||
**解决方案:**
|
||||
1. 检查错误日志
|
||||
2. 手动修复数据库状态
|
||||
3. 标记迁移为已解决:
|
||||
```sql
|
||||
UPDATE flyway_schema_history
|
||||
SET success = true
|
||||
WHERE version = 'X' AND success = false;
|
||||
```
|
||||
|
||||
### 问题 2:需要跳过某个迁移
|
||||
|
||||
**不推荐!** 但紧急情况下:
|
||||
|
||||
```sql
|
||||
-- 手动标记迁移为已执行
|
||||
INSERT INTO flyway_schema_history (
|
||||
installed_rank, version, description, type, script, checksum,
|
||||
installed_by, installed_on, execution_time, success
|
||||
) VALUES (
|
||||
(SELECT COALESCE(MAX(installed_rank), 0) + 1 FROM flyway_schema_history),
|
||||
'5', 'skip_this_migration', 'SQL', 'V5__skip_this.sql', NULL,
|
||||
'admin', NOW(), 0, true
|
||||
);
|
||||
```
|
||||
|
||||
### 问题 3:迁移顺序错误
|
||||
|
||||
**原因:** 版本号不连续或重复
|
||||
|
||||
**解决:**
|
||||
- 使用点分版本:`V2.1`, `V2.2`
|
||||
- 或重命名文件(未执行的迁移)
|
||||
|
||||
---
|
||||
|
||||
## 📖 相关文档
|
||||
|
||||
- Flyway 官方文档:https://flywaydb.org/documentation/
|
||||
- 本平台相关:
|
||||
- `docs/architecture-overview.md` - 平台架构
|
||||
- `docs/db-isolation-spec.md` - 数据库隔离规范
|
||||
- `infra/postgres/README.md` - Postgres 部署
|
||||
@@ -1,55 +0,0 @@
|
||||
-- ============================================
|
||||
-- Migration: V{VERSION}__{DESCRIPTION}
|
||||
-- Author: {YOUR_NAME}
|
||||
-- Date: {YYYY-MM-DD}
|
||||
-- Jira: {TICKET_ID} (optional)
|
||||
-- ============================================
|
||||
-- Description:
|
||||
-- {详细描述这个迁移做什么}
|
||||
-- {为什么需要这个变更}
|
||||
-- {对现有系统的影响}
|
||||
--
|
||||
-- Rollback Strategy:
|
||||
-- {如何回滚这个变更}
|
||||
-- {是否需要后续迁移来清理}
|
||||
--
|
||||
-- Performance Impact:
|
||||
-- {预估执行时间}
|
||||
-- {是否会锁表}
|
||||
-- {对应用的影响}
|
||||
-- ============================================
|
||||
|
||||
-- 可选:开始事务
|
||||
-- BEGIN;
|
||||
|
||||
-- ============================================
|
||||
-- Step 1: {步骤描述}
|
||||
-- ============================================
|
||||
|
||||
-- 你的 SQL 语句
|
||||
-- ...
|
||||
|
||||
-- ============================================
|
||||
-- Step 2: {步骤描述}
|
||||
-- ============================================
|
||||
|
||||
-- 你的 SQL 语句
|
||||
-- ...
|
||||
|
||||
-- ============================================
|
||||
-- Verification (可选)
|
||||
-- ============================================
|
||||
|
||||
-- 验证脚本(在迁移后检查)
|
||||
-- SELECT count(*) FROM your_table;
|
||||
-- SELECT * FROM your_table LIMIT 1;
|
||||
|
||||
-- 可选:提交事务
|
||||
-- COMMIT;
|
||||
|
||||
-- ============================================
|
||||
-- Notes:
|
||||
-- {额外说明}
|
||||
-- {已知问题}
|
||||
-- {后续计划}
|
||||
-- ============================================
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
||||
# GitHub Pages Setup for Evidence Feed
|
||||
|
||||
## Enable Pages
|
||||
|
||||
1. Open repository `Settings`.
|
||||
2. Go to `Pages`.
|
||||
3. Under `Build and deployment`, choose:
|
||||
- Source: `Deploy from a branch`
|
||||
- Branch: `gh-pages`
|
||||
- Folder: `/ (root)`
|
||||
4. Save.
|
||||
|
||||
## Evidence Feed URL
|
||||
|
||||
After first successful publish, the URLs are typically:
|
||||
|
||||
- Dashboard: `https://<owner>.github.io/<repo>/`
|
||||
- Feed JSON: `https://<owner>.github.io/<repo>/evidence/index.json`
|
||||
|
||||
Example for `BrunoGaoSZ/ljwx-deploy`:
|
||||
|
||||
- `https://brunogaosz.github.io/ljwx-deploy/`
|
||||
- `https://brunogaosz.github.io/ljwx-deploy/evidence/index.json`
|
||||
|
||||
## Access Control Notes
|
||||
|
||||
- Public repo: feed is public.
|
||||
- Private/internal repo: GitHub Pages visibility depends on plan/policy.
|
||||
- Keep records free of secrets/internal-only endpoints.
|
||||
@@ -1,4 +0,0 @@
|
||||
service: <svc>
|
||||
image: harbor.omniverseai.net/app/<svc>@sha256:__DIGEST__
|
||||
replicas: 1
|
||||
resourcesProfile: mvp-small
|
||||
@@ -1,55 +0,0 @@
|
||||
# Evidence Model
|
||||
|
||||
`evidence/records/*.yaml` is the source-of-truth evidence feed for promotion and verification.
|
||||
|
||||
## Required fields
|
||||
|
||||
- `evidenceId`
|
||||
- `service`
|
||||
- `env`
|
||||
- `source.repo`
|
||||
- `source.commit`
|
||||
- `image.harbor`
|
||||
- `deploy.deployRepoCommit`
|
||||
|
||||
## Optional fields
|
||||
|
||||
- `source.workflowRun`
|
||||
- `image.ghcr`
|
||||
- `deploy.argocdApp`
|
||||
- `deploy.syncedAt`
|
||||
- `tests.smoke`
|
||||
- `tests.e2e`
|
||||
- `approvals.*`
|
||||
|
||||
## Lifecycle
|
||||
|
||||
1. Queue promoter writes/updates evidence records in `evidence/records/`.
|
||||
2. Validation script checks structure and required fields.
|
||||
3. Collection script builds `evidence/index.json` and `evidence/summary/latest.md`.
|
||||
4. Nightly workflow publishes dashboard and feed to `gh-pages` branch root.
|
||||
|
||||
## Example record (YAML)
|
||||
|
||||
```yaml
|
||||
evidenceId: 2026-03-02-frontend-sha1234
|
||||
service: frontend
|
||||
env: dev
|
||||
source:
|
||||
repo: ghcr.io/example/frontend
|
||||
commit: sha1234
|
||||
workflowRun: https://github.com/example/frontend/actions/runs/123
|
||||
image:
|
||||
ghcr: ghcr.io/example/frontend@sha256:1111111111111111111111111111111111111111111111111111111111111111
|
||||
harbor: harbor.omniverseai.net/app/frontend@sha256:2222222222222222222222222222222222222222222222222222222222222222
|
||||
deploy:
|
||||
deployRepoCommit: abcdef1234567890
|
||||
argocdApp: frontend-dev
|
||||
syncedAt: 2026-03-02T02:10:00Z
|
||||
tests:
|
||||
smoke:
|
||||
status: pass
|
||||
url: https://github.com/example/deploy/actions/runs/456
|
||||
approvals:
|
||||
releasePr: https://github.com/example/deploy/pull/789
|
||||
```
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"generatedAt": "2026-04-03T17:39:46Z",
|
||||
"counts": {
|
||||
"pending": 0,
|
||||
"promoted": 0,
|
||||
"failed": 0,
|
||||
"superseded": 0
|
||||
},
|
||||
"pending": {
|
||||
"oldestCreatedAt": "",
|
||||
"maxAgeSeconds": 0,
|
||||
"staleThresholdSeconds": 1800,
|
||||
"staleIds": [],
|
||||
"byServiceEnv": {}
|
||||
},
|
||||
"failed": {
|
||||
"last24h": 0,
|
||||
"byServiceEnv": {}
|
||||
},
|
||||
"retries": {
|
||||
"pendingMaxAttempts": 0,
|
||||
"failedMaxAttempts": 0
|
||||
},
|
||||
"ratios": {
|
||||
"supersededVsPromoted": 0.0
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://example.com/evidence.schema.json",
|
||||
"title": "Bid-MVP Evidence Record",
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": [
|
||||
"evidenceId",
|
||||
"service",
|
||||
"env",
|
||||
"source",
|
||||
"image",
|
||||
"deploy"
|
||||
],
|
||||
"properties": {
|
||||
"evidenceId": { "type": "string", "minLength": 3 },
|
||||
"service": { "type": "string", "minLength": 1 },
|
||||
"env": { "type": "string", "minLength": 1 },
|
||||
"source": {
|
||||
"type": "object",
|
||||
"required": ["repo", "commit"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"repo": { "type": "string", "minLength": 3 },
|
||||
"commit": { "type": "string", "minLength": 1 },
|
||||
"workflowRun": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"image": {
|
||||
"type": "object",
|
||||
"required": ["harbor"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"ghcr": { "type": ["string", "null"] },
|
||||
"harbor": { "type": "string", "minLength": 3 }
|
||||
}
|
||||
},
|
||||
"deploy": {
|
||||
"type": "object",
|
||||
"required": ["deployRepoCommit"],
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"deployRepoCommit": { "type": "string", "minLength": 1 },
|
||||
"argocdApp": { "type": ["string", "null"] },
|
||||
"syncedAt": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"promotedAt": { "type": ["string", "null"] },
|
||||
"tests": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"smoke": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"status": { "type": ["string", "null"] },
|
||||
"details": { "type": ["string", "null"] },
|
||||
"url": { "type": ["string", "null"] }
|
||||
}
|
||||
},
|
||||
"e2e": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"status": { "type": ["string", "null"] },
|
||||
"url": { "type": ["string", "null"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"approvals": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
"specPr": { "type": ["string", "null"] },
|
||||
"archPr": { "type": ["string", "null"] },
|
||||
"demoPr": { "type": ["string", "null"] },
|
||||
"uatPr": { "type": ["string", "null"] },
|
||||
"releasePr": { "type": ["string", "null"] },
|
||||
"prs": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
# Latest Evidence Summary
|
||||
|
||||
| service | env | harbor digest | syncedAt | smoke | links |
|
||||
| service | env | deployed digest | syncedAt | smoke | links |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
<th>evidenceId</th>
|
||||
<th>service</th>
|
||||
<th>env</th>
|
||||
<th>harbor digest</th>
|
||||
<th>deployed digest</th>
|
||||
<th>syncedAt</th>
|
||||
<th>smoke</th>
|
||||
<th>links</th>
|
||||
@@ -1,16 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: arc-github-token
|
||||
namespace: arc-system
|
||||
type: Opaque
|
||||
stringData:
|
||||
# Option A: Personal Access Token
|
||||
github_token: "ghp_xxx_replace_me"
|
||||
# Option B: GitHub App (use these keys instead of github_token)
|
||||
# github_app_id: "123456"
|
||||
# github_app_installation_id: "789012"
|
||||
# github_app_private_key: |
|
||||
# -----BEGIN RSA PRIVATE KEY-----
|
||||
# ...
|
||||
# -----END RSA PRIVATE KEY-----
|
||||
@@ -1,66 +0,0 @@
|
||||
apiVersion: argoproj.io/v1alpha1
|
||||
kind: Application
|
||||
metadata:
|
||||
name: arc-controller
|
||||
namespace: argocd
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "-1"
|
||||
spec:
|
||||
project: default
|
||||
source:
|
||||
repoURL: https://actions-runner-controller.github.io/actions-runner-controller
|
||||
chart: actions-runner-controller
|
||||
targetRevision: 0.23.3
|
||||
helm:
|
||||
values: |
|
||||
# Secret naming strategy:
|
||||
# - ARC controller always reads credentials from secret `arc-github-token`.
|
||||
# - `arc-github-token` must contain one of:
|
||||
# - github_token (PAT), or
|
||||
# - github_app_id + github_app_installation_id + github_app_private_key.
|
||||
# - Do not rename this secret without updating this Application.
|
||||
authSecret:
|
||||
enabled: true
|
||||
create: false
|
||||
name: arc-github-token
|
||||
destination:
|
||||
server: https://kubernetes.default.svc
|
||||
namespace: arc-system
|
||||
syncPolicy:
|
||||
automated:
|
||||
prune: true
|
||||
selfHeal: true
|
||||
syncOptions:
|
||||
- CreateNamespace=true
|
||||
- ServerSideApply=true
|
||||
ignoreDifferences:
|
||||
- group: apiextensions.k8s.io
|
||||
kind: CustomResourceDefinition
|
||||
name: horizontalrunnerautoscalers.actions.summerwind.dev
|
||||
jsonPointers:
|
||||
- /spec
|
||||
- /metadata/annotations
|
||||
- group: apiextensions.k8s.io
|
||||
kind: CustomResourceDefinition
|
||||
name: runnerdeployments.actions.summerwind.dev
|
||||
jsonPointers:
|
||||
- /spec
|
||||
- /metadata/annotations
|
||||
- group: apiextensions.k8s.io
|
||||
kind: CustomResourceDefinition
|
||||
name: runnerreplicasets.actions.summerwind.dev
|
||||
jsonPointers:
|
||||
- /spec
|
||||
- /metadata/annotations
|
||||
- group: apiextensions.k8s.io
|
||||
kind: CustomResourceDefinition
|
||||
name: runners.actions.summerwind.dev
|
||||
jsonPointers:
|
||||
- /spec
|
||||
- /metadata/annotations
|
||||
- group: apiextensions.k8s.io
|
||||
kind: CustomResourceDefinition
|
||||
name: runnersets.actions.summerwind.dev
|
||||
jsonPointers:
|
||||
- /spec
|
||||
- /metadata/annotations
|
||||
@@ -1,3 +0,0 @@
|
||||
resources:
|
||||
- controller.yaml
|
||||
- runner-set.yaml
|
||||
@@ -1,51 +0,0 @@
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: RunnerDeployment
|
||||
metadata:
|
||||
name: org-runner
|
||||
namespace: arc-system
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "1"
|
||||
spec:
|
||||
replicas: 1
|
||||
template:
|
||||
spec:
|
||||
organization: BrunoGaoSZ
|
||||
labels:
|
||||
- arc
|
||||
- docker
|
||||
- linux
|
||||
- self-hosted
|
||||
dockerdWithinRunnerContainer: true
|
||||
image: summerwind/actions-runner-dind:ubuntu-22.04
|
||||
env:
|
||||
- name: DOCKER_HOST
|
||||
value: unix:///var/run/docker.sock
|
||||
dockerEnv:
|
||||
- name: DOCKER_TLS_CERTDIR
|
||||
value: ""
|
||||
resources:
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "4Gi"
|
||||
requests:
|
||||
cpu: "500m"
|
||||
memory: "1Gi"
|
||||
---
|
||||
apiVersion: actions.summerwind.dev/v1alpha1
|
||||
kind: HorizontalRunnerAutoscaler
|
||||
metadata:
|
||||
name: org-runner-autoscaler
|
||||
namespace: arc-system
|
||||
annotations:
|
||||
argocd.argoproj.io/sync-wave: "2"
|
||||
spec:
|
||||
scaleTargetRef:
|
||||
name: org-runner
|
||||
minReplicas: 1
|
||||
maxReplicas: 5
|
||||
metrics:
|
||||
- type: PercentageRunnersBusy
|
||||
scaleUpThreshold: "0.75"
|
||||
scaleDownThreshold: "0.25"
|
||||
scaleUpFactor: "2"
|
||||
scaleDownFactor: "0.5"
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user