chore: reconcile with origin/main preferring local tree snapshot

This commit is contained in:
2026-03-02 15:16:16 +08:00
parent 4c8bf87238
commit 772074cf4e
185 changed files with 6338 additions and 10057 deletions
+2
View File
@@ -1 +1,3 @@
client/data/mysql/mysql.sock
README.md
.git/
+2 -2
View File
@@ -72,11 +72,11 @@ jobs:
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
if git diff --quiet; then
echo "No changes to publish"
exit 0
fi
git add -A
git commit -m "pages: update evidence feed [skip ci]"
git push origin HEAD:gh-pages
+624
View File
@@ -0,0 +1,624 @@
# Gitea CI/CD 快速配置指南
## 📋 适用场景
为新项目快速配置基于 Gitea Actions + Argo CD 的完整 GitOps 工作流。
## ✅ 前置条件
### 1. 基础设施检查
```bash
# 检查 Gitea Actions Runner
ls -la /Users/brunogao/gitea-runners/runner3/.runner
# 应显示: labels: ["macos:docker:host"] 或 ["docker-builder"]
# 检查 Harbor Registry
docker login harbor.omniverseai.net
# 应成功登录
# 检查 Argo CD
kubectl get application -n argocd
# 应看到 apps-bootstrap 和其他应用
```
### 2. 准备信息
- **项目名称**: 如 `my-app`
- **Gitea 仓库**: `http://192.168.1.83:33000/gao/my-app`
- **Harbor 镜像**: `harbor.omniverseai.net/ljwx/my-app`
- **K8s Namespace**: `my-app-dev` (推荐独立 namespace)
- **Gitea Token**: 从 Gitea 设置 → 应用 → 访问令牌生成
## 🚀 配置步骤
### Step 1: 项目仓库配置 Gitea Workflow
#### 1.1 创建 Workflow 文件
在项目根目录创建 `.gitea/workflows/ci.yaml`:
```yaml
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: harbor.omniverseai.net
IMAGE_NAME: ljwx/my-app # 修改为你的项目名
jobs:
# ==================== Job 1: Lint & Test ====================
lint-test:
runs-on: ubuntu-latest # K8s runner,快速
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci --legacy-peer-deps
- name: Run linter
run: npm run lint
- name: Type check
run: npm run build:check || npm run build
# ==================== Job 2: Build & Push ====================
build-push:
needs: lint-test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: docker-builder # 宿主机 runner,有完整 Docker 环境
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Extract metadata
id: meta
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
echo "tag=main-${SHORT_SHA}" >> $GITHUB_OUTPUT
echo "short_sha=${SHORT_SHA}" >> $GITHUB_OUTPUT
- name: Log in to Harbor
run: echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin
- name: Set up Docker Buildx
run: |
docker buildx create --name ci-builder --use --driver docker-container || docker buildx use ci-builder
docker buildx inspect --bootstrap
- name: Build and push Docker image
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }} \
--tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest \
--push \
--progress plain \
.
- name: Image digest
run: |
docker buildx imagetools inspect ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.tag }}
# ==================== Job 3: Update Deploy Repo ====================
update-deploy:
needs: build-push
runs-on: ubuntu-latest
env:
IMAGE_TAG: main-${{ github.sha }}
steps:
- name: Checkout code
run: |
SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-7)
echo "IMAGE_TAG=main-${SHORT_SHA}" >> $GITHUB_ENV
- name: Verify tools
run: |
node --version
npm --version
git --version
yq --version
- name: Update deployment repository
run: |
# 克隆 ljwx-deploy 仓库 (使用 token 认证)
git clone http://gao:${{ secrets.DEPLOY_REPO_TOKEN }}@192.168.1.83:33000/gao/ljwx-deploy.git /tmp/ljwx-deploy
cd /tmp/ljwx-deploy
# 配置 git
git config user.name "Gitea Actions"
git config user.email "actions@gitea.local"
# 导航到 overlay 目录(修改为你的项目路径)
cd apps/my-app/overlays/my-app-dev
# 更新镜像 tag
yq eval ".images[0].newTag = \"${IMAGE_TAG}\"" -i kustomization.yaml
# 提交并推送
git add kustomization.yaml
git commit -m "chore: update my-app to ${IMAGE_TAG}"
git push origin main
# ==================== Job 4: Build PR (仅验证) ====================
build-pr:
needs: lint-test
if: github.event_name == 'pull_request'
runs-on: docker-builder
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
run: |
docker buildx create --name ci-builder --use --driver docker-container || docker buildx use ci-builder
docker buildx inspect --bootstrap
- name: Build Docker image (no push)
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--progress plain \
.
```
#### 1.2 配置 Gitea Secrets
**推荐方式: 使用 Organization Secrets (一次配置,所有项目共享)**
详细配置步骤请参考: **[GITEA-SECRETS-SETUP.md](GITEA-SECRETS-SETUP.md)**
```bash
# Organization 级别配置 (推荐):
# http://192.168.1.83:33000/gao/settings/secrets
# 添加以下 3 个 Secrets,所有项目自动可用:
# - HARBOR_USERNAME: Harbor 用户名
# - HARBOR_PASSWORD: Harbor 密码
# - DEPLOY_REPO_TOKEN: Gitea 访问令牌 (用于更新 ljwx-deploy 仓库)
# 或者项目级别配置 (不推荐):
# http://192.168.1.83:33000/gao/my-app/settings/secrets
# 需要为每个项目单独配置
```
> 💡 **提示**: 如果已经配置了 Organization Secrets,可以跳过此步骤
#### 1.3 确保 Dockerfile 存在
在项目根目录创建 `Dockerfile.prod` (或 `Dockerfile`):
```dockerfile
# 第一阶段:构建
FROM harbor.omniverseai.net/library/node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --legacy-peer-deps
COPY . .
RUN npm run build
# 第二阶段:生产
FROM harbor.omniverseai.net/library/nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost/health || exit 1
CMD ["nginx", "-g", "daemon off;"]
```
### Step 2: ljwx-deploy 仓库配置
#### 2.1 创建 Namespace 配置
```bash
cd /path/to/ljwx-deploy
# 创建 namespace 配置文件
cat > cluster/namespace-my-app-dev.yaml <<EOF
apiVersion: v1
kind: Namespace
metadata:
name: my-app-dev
labels:
name: my-app-dev
environment: dev
app: my-app
---
apiVersion: v1
kind: ResourceQuota
metadata:
name: my-app-quota
namespace: my-app-dev
spec:
hard:
requests.cpu: "500m"
requests.memory: "512Mi"
limits.cpu: "1"
limits.memory: "1Gi"
persistentvolumeclaims: "5"
services: "5"
pods: "10"
---
apiVersion: v1
kind: LimitRange
metadata:
name: my-app-limits
namespace: my-app-dev
spec:
limits:
- max:
cpu: "500m"
memory: "512Mi"
min:
cpu: "10m"
memory: "32Mi"
default:
cpu: "100m"
memory: "128Mi"
defaultRequest:
cpu: "50m"
memory: "64Mi"
type: Container
EOF
# 更新 cluster/kustomization.yaml
yq eval '.resources += ["namespace-my-app-dev.yaml"]' -i cluster/kustomization.yaml
```
#### 2.2 创建应用部署配置
```bash
# 创建目录结构
mkdir -p apps/my-app/{base,overlays/my-app-dev}
# 创建 base/deployment.yaml
cat > apps/my-app/base/deployment.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
labels:
app: my-app
component: web
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
component: web
spec:
containers:
- name: my-app
image: harbor.omniverseai.net/ljwx/my-app:latest
ports:
- name: http
containerPort: 80
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "25m"
limits:
memory: "128Mi"
cpu: "100m"
EOF
# 创建 base/service.yaml
cat > apps/my-app/base/service.yaml <<EOF
apiVersion: v1
kind: Service
metadata:
name: my-app
labels:
app: my-app
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: my-app
EOF
# 创建 base/kustomization.yaml
cat > apps/my-app/base/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
EOF
# 创建 overlays/my-app-dev/kustomization.yaml
cat > apps/my-app/overlays/my-app-dev/kustomization.yaml <<EOF
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: my-app-dev
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/my-app
newTag: "latest" # CI 会自动更新这个 tag
replicas:
- name: my-app
count: 1 # dev 环境使用 1 个副本
EOF
```
#### 2.3 创建 Argo CD Application
```bash
# 创建 Application 配置
cat > argocd-apps/60-my-app-dev.yaml <<EOF
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: my-app-dev
namespace: argocd
spec:
project: default
source:
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: apps/my-app/overlays/my-app-dev
destination:
server: https://kubernetes.default.svc
namespace: my-app-dev
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
EOF
```
#### 2.4 提交到 Git
```bash
cd /path/to/ljwx-deploy
# 提交所有变更
git add .
git commit -m "feat: add my-app deployment configuration"
git push origin main
# 等待 30 秒让 apps-bootstrap 自动创建 Application
sleep 30
```
### Step 3: 验证部署
#### 3.1 验证 Argo CD Application
```bash
# 检查 Application 是否自动创建
kubectl get application my-app-dev -n argocd
# 应该显示:
# NAME SYNC STATUS HEALTH STATUS
# my-app-dev Synced Healthy
```
#### 3.2 验证 Namespace 和资源
```bash
# 检查 namespace
kubectl get namespace my-app-dev
# 检查资源配额
kubectl get resourcequota,limitrange -n my-app-dev
# 检查部署状态
kubectl get all -n my-app-dev
```
#### 3.3 测试完整 CI/CD 流程
```bash
# 在项目仓库中做一个测试提交
cd /path/to/my-app
echo "# Test CI/CD" >> README.md
git add README.md
git commit -m "test: verify CI/CD pipeline"
git push origin main
# 观察流程:
# 1. Gitea Actions 开始构建 (约 3-5 分钟)
# 2. 镜像推送到 Harbor
# 3. ljwx-deploy 仓库自动更新
# 4. Argo CD 检测到变更并同步 (约 30 秒)
# 5. 新 Pod 启动
# 检查 Gitea Actions
# http://192.168.1.83:33000/gao/my-app/actions
# 检查 ljwx-deploy 最新提交
cd /path/to/ljwx-deploy
git pull origin main
git log -1 --oneline
# 应该看到: chore: update my-app to main-xxxxxxx
# 检查 Pod 镜像更新
kubectl describe pod -n my-app-dev -l app=my-app | grep Image:
```
## 🎯 关键配置点
### 1. Runner 选择
```yaml
# 轻量级任务 (lint, test) → K8s runner
runs-on: ubuntu-latest
# Docker 构建任务 → 宿主机 runner
runs-on: docker-builder # 或 macos
```
### 2. Multi-arch 构建
```yaml
# 必须使用 buildx + atomic push
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \ # 一步完成 build + push
.
```
### 3. Kustomize 路径
```yaml
# CI workflow 中的路径要匹配
cd apps/my-app/overlays/my-app-dev
# Argo CD Application 中的路径
path: apps/my-app/overlays/my-app-dev
```
### 4. Namespace 独立性
```yaml
# 推荐: 每个应用独立 namespace
namespace: my-app-dev
# 而不是: 共享 namespace
namespace: dev # ❌ 避免
```
## 🔧 常见问题排查
### 问题 1: Build & Push 失败
**症状**: Docker build 失败或推送失败
**检查**:
```bash
# 1. 检查 runner 标签
cat /Users/brunogao/gitea-runners/runner3/.runner
# 2. 检查 Harbor 凭据
echo "$HARBOR_PASSWORD" | docker login harbor.omniverseai.net -u "$HARBOR_USERNAME" --password-stdin
# 3. 检查 buildx builder
docker buildx ls
```
**解决**: 使用 `docker-builder` 标签的 runner,确保 buildx 正确设置
### 问题 2: Update Deploy Repo 不执行
**症状**: Job 显示成功但 ljwx-deploy 未更新
**检查**:
```bash
# 1. 验证 DEPLOY_REPO_TOKEN
# Gitea → 设置 → 应用 → 访问令牌
# 2. 检查路径是否正确
ls -la ljwx-deploy/apps/my-app/overlays/my-app-dev/kustomization.yaml
# 3. 测试 git clone
git clone http://gao:YOUR_TOKEN@192.168.1.83:33000/gao/ljwx-deploy.git /tmp/test
```
**解决**:
- 确保 token 有 `repo` 权限
- 确保路径 `apps/my-app/overlays/my-app-dev` 存在
### 问题 3: Argo CD 不自动同步
**症状**: ljwx-deploy 更新了但 K8s 未部署
**检查**:
```bash
# 1. 检查 Application 状态
kubectl describe application my-app-dev -n argocd
# 2. 检查 apps-bootstrap
kubectl get application apps-bootstrap -n argocd
# 3. 手动触发同步
kubectl patch application my-app-dev -n argocd \
--type merge \
-p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{"revision":"HEAD"}}}'
```
**解决**:
- 确保 Application 文件名匹配 `*-dev.yaml` 模式
- 等待 3 分钟(Argo CD 默认同步间隔)
### 问题 4: Pod CrashLoopBackOff
**症状**: Pod 启动失败
**检查**:
```bash
# 1. 查看 Pod 日志
kubectl logs -n my-app-dev -l app=my-app --tail=50
# 2. 查看 Pod 事件
kubectl describe pod -n my-app-dev -l app=my-app
# 3. 检查资源限制
kubectl get limitrange -n my-app-dev -o yaml
```
**解决**:
- 确保 Dockerfile 正确配置
- 确保 `/health` 端点存在
- 调整 resources.requests 满足 LimitRange
## 📚 参考文档
- **ljwx-website 成功案例**: `.gitea/workflows/ci.yaml`
- **Gitea Actions 文档**: https://docs.gitea.com/usage/actions/overview
- **Argo CD App of Apps**: `ARGOCD-APP-MANAGEMENT.md`
- **Namespace 策略**: `NAMESPACE-STRATEGY.md`
- **GitOps 原则**: `PROMPT_BLUEPRINT_V2.md`
## 🎉 总结
完成上述步骤后,你的项目将拥有:
✅ 完整的 CI/CD 自动化流程
✅ Multi-arch Docker 镜像
✅ GitOps 声明式部署
✅ 独立的 Namespace 资源隔离
✅ Argo CD 自动同步
✅ 零手动 kubectl 操作
**记住**: Git commit 即部署,完全自动化!🚀
+393
View File
@@ -0,0 +1,393 @@
# Gitea Organization Secrets 配置指南
## 📋 目标
配置组织级别的 Secrets,实现一次配置、所有项目共享,无需在每个项目中重复配置。
## 🎯 方案选择
### 方案对比
| 方案 | 优点 | 缺点 | 推荐度 |
|------|------|------|--------|
| **Organization Secrets** | ✅ 一次配置<br>✅ 所有项目共享<br>✅ 集中管理 | ⚠️ 需要 Organization | ⭐⭐⭐⭐⭐ |
| **Repository Secrets** | ✅ 项目隔离 | ❌ 每个项目都要配置<br>❌ 维护成本高 | ⭐⭐ |
| **Global Runner Secrets** | ✅ 全局可用 | ❌ 不安全<br>❌ 难以管理 | ⭐ |
**推荐**: 使用 Organization Secrets
## 🚀 快速配置 (Organization Secrets)
### Step 1: 检查当前组织结构
```bash
# 检查你的 Gitea 项目所属组织
# 访问: http://192.168.1.83:33000/gao
# 确认所有项目都在 "gao" 组织/用户下:
# - ljwx-website
# - ljwx-deploy
# - backend
# - frontend
# - quality-dashboard
# - (其他项目)
```
### Step 2: 配置 Organization Secrets
#### 方式 1: Web UI 配置 (推荐)
1. **访问组织设置**
```
http://192.168.1.83:33000/gao
```
2. **导航到 Secrets 页面**
```
组织首页 → Settings → Secrets (或 Actions → Secrets)
```
3. **添加以下 Secrets**
**Secret 1: HARBOR_USERNAME**
```
Name: HARBOR_USERNAME
Value: admin (或你的 Harbor 用户名)
```
**Secret 2: HARBOR_PASSWORD**
```
Name: HARBOR_PASSWORD
Value: [你的 Harbor 密码]
```
**Secret 3: DEPLOY_REPO_TOKEN**
```
Name: DEPLOY_REPO_TOKEN
Value: [你的 Gitea Personal Access Token]
```
4. **验证配置**
- 点击 "Add Secret" 后应该看到 secret 列表
- 确认所有 3 个 secrets 都已添加
#### 方式 2: Gitea API 配置 (批量)
```bash
# 设置变量
GITEA_URL="http://192.168.1.83:33000"
ORG_NAME="gao"
GITEA_TOKEN="your-admin-token" # 需要 admin 或 owner 权限
# 添加 HARBOR_USERNAME
curl -X PUT "${GITEA_URL}/api/v1/orgs/${ORG_NAME}/actions/secrets/HARBOR_USERNAME" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"data": "admin"
}'
# 添加 HARBOR_PASSWORD
curl -X PUT "${GITEA_URL}/api/v1/orgs/${ORG_NAME}/actions/secrets/HARBOR_PASSWORD" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"data": "your-harbor-password"
}'
# 添加 DEPLOY_REPO_TOKEN
curl -X PUT "${GITEA_URL}/api/v1/orgs/${ORG_NAME}/actions/secrets/DEPLOY_REPO_TOKEN" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"data": "your-deploy-token"
}'
```
### Step 3: 生成 DEPLOY_REPO_TOKEN (如果还没有)
```bash
# 1. 访问个人设置
http://192.168.1.83:33000/user/settings/applications
# 2. 生成新的访问令牌
Name: gitea-actions-deploy
Scopes:
- repo (必须)
- write:repository (必须)
# 3. 复制生成的 token
# 示例: 7ea68abccfb9de287305ab23b82c8e9a66fb6814
# 4. 将 token 添加到 Organization Secrets (见 Step 2)
```
### Step 4: 更新项目 Workflow (移除项目级 Secrets)
现在项目的 `.gitea/workflows/ci.yaml` 可以直接使用 Organization Secrets,无需任何修改:
```yaml
# ✅ 直接使用,无需在项目中配置
- name: Log in to Harbor
run: echo "${{ secrets.HARBOR_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin
- name: Update deployment repository
run: |
git clone http://gao:${{ secrets.DEPLOY_REPO_TOKEN }}@192.168.1.83:33000/gao/ljwx-deploy.git /tmp/ljwx-deploy
```
**优先级规则**:
1. Repository Secrets (项目级) - 最高优先级
2. Organization Secrets (组织级) - 如果项目没有配置
3. Global Secrets (全局) - 最低优先级
### Step 5: 清理项目级 Secrets (可选)
如果之前在项目中配置了 Secrets,现在可以删除:
```bash
# 访问每个项目的 Settings → Secrets
http://192.168.1.83:33000/gao/ljwx-website/settings/secrets
http://192.168.1.83:33000/gao/backend/settings/secrets
http://192.168.1.83:33000/gao/frontend/settings/secrets
# 删除以下 Secrets (因为已经在 Organization 级别配置):
# - HARBOR_USERNAME
# - HARBOR_PASSWORD
# - DEPLOY_REPO_TOKEN
```
## ✅ 验证配置
### 测试 1: 检查 Organization Secrets
```bash
# 访问组织 Secrets 页面
http://192.168.1.83:33000/gao/settings/secrets
# 应该看到:
# - HARBOR_USERNAME
# - HARBOR_PASSWORD
# - DEPLOY_REPO_TOKEN
```
### 测试 2: 新项目自动可用
```bash
# 1. 创建新项目
http://192.168.1.83:33000/repo/create
# 2. 添加 workflow 文件 (使用 secrets)
mkdir -p .gitea/workflows
cat > .gitea/workflows/test.yaml <<'EOF'
name: Test Secrets
on: push
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Test Harbor login
run: |
echo "Testing Harbor credentials..."
docker login harbor.omniverseai.net -u "${{ secrets.HARBOR_USERNAME }}" --password-stdin <<< "${{ secrets.HARBOR_PASSWORD }}"
echo "✅ Login successful!"
EOF
# 3. 提交并推送
git add .gitea/workflows/test.yaml
git commit -m "test: verify organization secrets"
git push origin main
# 4. 查看 Actions 运行结果
# 应该显示 "✅ Login successful!"
```
### 测试 3: 现有项目验证
```bash
# 在 ljwx-website 项目中触发 CI
cd /path/to/ljwx-website
git commit --allow-empty -m "test: verify org secrets"
git push origin main
# 查看 Actions 日志
http://192.168.1.83:33000/gao/ljwx-website/actions
# 检查 Build & Push job:
# ✅ Harbor 登录成功
# ✅ 镜像推送成功
# ✅ ljwx-deploy 更新成功
```
## 🔐 Secrets 管理最佳实践
### 1. Secrets 命名规范
```yaml
# ✅ 推荐: 清晰的语义命名
HARBOR_USERNAME
HARBOR_PASSWORD
DEPLOY_REPO_TOKEN
DOCKER_REGISTRY_URL
# ❌ 避免: 模糊的命名
SECRET1
TOKEN
PASSWORD
```
### 2. Secrets 轮换策略
```bash
# 定期更新 Secrets (建议 90 天)
# 1. 生成新的 DEPLOY_REPO_TOKEN
http://192.168.1.83:33000/user/settings/applications
# 2. 更新 Organization Secret
http://192.168.1.83:33000/gao/settings/secrets
# 编辑 DEPLOY_REPO_TOKEN → 输入新 token
# 3. 撤销旧 token
http://192.168.1.83:33000/user/settings/applications
# 删除旧的 token
# 4. 验证所有项目仍正常工作
# 触发 CI 测试
```
### 3. Secrets 作用域控制
```yaml
# Organization Secrets 适用于:
✅ 共享的基础设施凭据 (Harbor, Gitea)
✅ 通用的 API tokens
✅ 部署凭据
# Repository Secrets 适用于:
✅ 项目特定的凭据
✅ 需要隔离的敏感信息
✅ 不同环境的配置 (dev/staging/prod)
```
### 4. 安全检查清单
```bash
# ✅ 定期检查
- [ ] Secrets 是否有过期时间
- [ ] Token 权限是否最小化
- [ ] 是否有未使用的 Secrets
- [ ] Workflow 日志是否泄露 Secrets
- [ ] 是否定期轮换凭据
# ❌ 避免
- [ ] 在 Workflow 中 echo Secrets
- [ ] 将 Secrets 写入文件
- [ ] 在 PR 中暴露 Secrets
- [ ] 使用过于宽泛的权限
```
## 📊 当前项目配置总结
### 组织级别 (gao)
配置以下 3 个 Organization Secrets,所有项目自动可用:
```yaml
HARBOR_USERNAME: admin
HARBOR_PASSWORD: [Harbor 管理员密码]
DEPLOY_REPO_TOKEN: [Gitea Personal Access Token with repo scope]
```
### 项目列表 (自动继承)
以下项目无需单独配置,自动使用 Organization Secrets:
```
✅ ljwx-website - 已验证
✅ ljwx-deploy - 仅被引用,不需要 CI
✅ backend - 可配置 CI
✅ frontend - 可配置 CI
✅ quality-dashboard - 可配置 CI
✅ nginx - 可配置 CI
✅ [未来新项目] - 自动可用
```
### Workflow 模板 (通用)
```yaml
# 所有项目都使用相同的 secrets 引用
secrets.HARBOR_USERNAME
secrets.HARBOR_PASSWORD
secrets.DEPLOY_REPO_TOKEN
# 无需在每个项目中重复配置!
```
## 🎯 快速开始新项目
现在为新项目添加 CI/CD 只需 2 步:
```bash
# Step 1: 创建 workflow 文件
mkdir -p .gitea/workflows
cp /path/to/ljwx-website/.gitea/workflows/ci.yaml .gitea/workflows/
# Step 2: 修改项目名称
sed -i 's/ljwx\/ljwx-website/ljwx\/my-new-app/g' .gitea/workflows/ci.yaml
# ✅ 完成!Secrets 自动可用,无需配置
```
## 🔄 迁移现有项目
如果现有项目已经配置了 Repository Secrets:
```bash
# 1. 验证 Organization Secrets 已配置
curl -X GET "http://192.168.1.83:33000/api/v1/orgs/gao/actions/secrets" \
-H "Authorization: token ${GITEA_TOKEN}"
# 2. 删除项目级 Secrets (可选)
# http://192.168.1.83:33000/gao/[project]/settings/secrets
# 3. 测试 workflow
git commit --allow-empty -m "test: verify org secrets"
git push origin main
# 4. 检查运行结果
# http://192.168.1.83:33000/gao/[project]/actions
```
## 📝 总结
### 配置前 (每个项目都要配置)
```
ljwx-website → Settings → Secrets → HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN
backend → Settings → Secrets → HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN
frontend → Settings → Secrets → HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN
quality-dash... → Settings → Secrets → HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN
[每个新项目都要重复配置] ❌
```
### 配置后 (一次配置,全局共享)
```
gao (Organization) → Settings → Secrets → HARBOR_USERNAME, HARBOR_PASSWORD, DEPLOY_REPO_TOKEN
↓
└─ 所有项目自动继承 ✅
- ljwx-website
- backend
- frontend
- quality-dashboard
- [未来所有新项目]
```
**效果**:
- ✅ 配置时间: 15 分钟 → 2 分钟
- ✅ 维护成本: 降低 80%
- ✅ 新项目开箱即用
- ✅ 集中管理,安全性提升
🎉 **一次配置,永久受益!**
+89
View File
@@ -0,0 +1,89 @@
# Bid-MVP Factory Control Planes Implementation Plan (ljwx-deploy)
## Phase 0 Inventory
### Existing
- GitOps repo with Argo CD apps under `argocd-apps/` and workload manifests under `apps/`.
- Service images currently mostly controlled in overlay `kustomization.yaml` `images` blocks.
- Existing utility script: `scripts/create-app-secret.sh`.
- No `.github/workflows/` currently.
### Missing (to add)
- Evidence control plane:
- `evidence/README.md`
- `evidence/schema/evidence-record.schema.json`
- `evidence/records/*.json`
- generated `evidence/index.json`
- Evidence scripts:
- `scripts/evidence/collect.py`
- `scripts/evidence/validate.py`
- Pages site source:
- `site/index.html`
- `site/app.js`
- optional `site/styles.css`
- GitHub Actions workflows:
- nightly evidence collect + gh-pages publish
- auto-repair (phase 5)
- Release queue and promoter:
- `release/queue.yaml`
- `scripts/promoter/deploy_promoter.py`
- `scripts/promoter/README.md`
- k8s CronJob manifests for promoter
- Dev image pin files:
- `envs/dev/<service>.yaml`
- Smoke checks:
- `scripts/smoke/run_smoke.py`
- k8s smoke runner CronJob or Job manifest
- Auto repair loop:
- `scripts/repair/recipes.yaml`
- `scripts/repair/run_repair.py`
### Existing promoter/test-runner/evidence/pages status
- Promoter: not present.
- Test-runner/smoke runner: not present.
- Evidence model/feed: not present.
- Pages/gh-pages publishing workflow: not present.
## Phase 2 Scope (Evidence + Pages)
- Add evidence schema/model and scripts.
- Add nightly workflow that validates records, builds `evidence/index.json`, and publishes dashboard to `gh-pages` branch root.
- Keep GitHub as source of truth; no external state required.
## Phase 3 Scope (Release Queue + Auto Promote)
- Add queue state machine (`pending/promoted/failed`) with retry metadata.
- Add promoter logic for per-service serialization and supersede semantics.
- Verify Harbor digest existence via v2 manifest API (HEAD/GET) to avoid replication timing assumptions.
- Pin dev image in `envs/dev/<svc>.yaml` as digest reference.
- Write evidence records for promotion attempts/results.
## Phase 4 Scope (Post-deploy Smoke)
- Add smoke runner to check Argo app health/sync (or endpoint readiness fallback).
- Write smoke results into evidence record chain.
## Phase 5 Scope (Auto Repair Loop)
- Add repair recipes + runner for format/lint/codegen style fixes.
- Add automation notes/scripts for commit→PR→checks→auto-fix→recheck→merge (dev).
- Add fail-after-N behavior to open issue with minimal repro/logs.
- Add final `TEST.md` end-to-end dry run instructions.
## Phase-by-Phase Verification Commands (deploy repo)
- Phase 0:
- `test -f PLAN.md && sed -n '1,120p' PLAN.md`
- Phase 2:
- `python3 scripts/evidence/validate.py`
- `python3 scripts/evidence/collect.py --out evidence/index.json`
- `python3 -m json.tool evidence/index.json >/dev/null`
- Phase 3:
- `python3 scripts/promoter/deploy_promoter.py --help`
- `python3 scripts/promoter/deploy_promoter.py --dry-run`
- Phase 4:
- `python3 scripts/smoke/run_smoke.py --help`
- Phase 5:
- `python3 scripts/repair/run_repair.py --help`
## Commit Plan (deploy repo)
- `phase0(deploy): inventory + PLAN.md`
- `phase2(deploy): evidence model/scripts/pages workflow/site`
- `phase3(deploy): release queue + promoter + cronjob + runbook`
- `phase4(deploy): smoke runner + cronjob + evidence integration docs`
- `phase5(deploy): auto-repair recipes/runner + workflow + TEST.md`
+392
View File
@@ -0,0 +1,392 @@
# Gitea CI/CD 快速参考卡片
## 🎯 一次性配置 (只需做一次)
### 1. Organization Secrets 配置
```bash
# 访问组织设置
http://192.168.1.83:33000/gao/settings/secrets
# 添加 3 个 Secrets:
HARBOR_USERNAME = admin
HARBOR_PASSWORD = [Harbor 密码]
DEPLOY_REPO_TOKEN = [Gitea Token]
```
**详细步骤**: [GITEA-SECRETS-SETUP.md](GITEA-SECRETS-SETUP.md)
### 2. 生成 DEPLOY_REPO_TOKEN
```bash
# 访问个人设置
http://192.168.1.83:33000/user/settings/applications
# 生成新 Token:
Name: gitea-actions-deploy
Scopes: repo, write:repository
```
---
## 📦 为新项目添加 CI/CD (5 分钟)
### Step 1: 复制 Workflow 文件
```bash
cd /path/to/new-project
# 从 ljwx-website 复制模板
curl -o .gitea/workflows/ci.yaml \
http://192.168.1.83:33000/gao/ljwx-website/raw/branch/main/.gitea/workflows/ci.yaml
```
### Step 2: 修改项目名称
```bash
# 替换镜像名称 (只需修改这一处)
sed -i 's/ljwx\/ljwx-website/ljwx\/my-app/g' .gitea/workflows/ci.yaml
# 或手动修改:
vim .gitea/workflows/ci.yaml
# 找到: IMAGE_NAME: ljwx/ljwx-website
# 改为: IMAGE_NAME: ljwx/my-app
```
### Step 3: 提交并推送
```bash
git add .gitea/workflows/ci.yaml
git commit -m "ci: add Gitea Actions workflow"
git push origin main
# ✅ CI 自动运行,无需配置 Secrets!
```
---
## 🚀 为新项目添加 CD (10 分钟)
### Step 1: 创建 Namespace 配置
```bash
cd /path/to/ljwx-deploy
# 复制模板并修改
cp cluster/namespace-ljwx-website-dev.yaml cluster/namespace-my-app-dev.yaml
# 替换所有 ljwx-website → my-app
sed -i 's/ljwx-website/my-app/g' cluster/namespace-my-app-dev.yaml
# 更新 cluster/kustomization.yaml
yq eval '.resources += ["namespace-my-app-dev.yaml"]' -i cluster/kustomization.yaml
```
### Step 2: 创建应用配置
```bash
# 复制模板
cp -r apps/ljwx-website apps/my-app
# 更新配置
sed -i 's/ljwx-website/my-app/g' apps/my-app/base/*.yaml
sed -i 's/ljwx-website/my-app/g' apps/my-app/overlays/ljwx-website-dev/kustomization.yaml
# 重命名 overlay
mv apps/my-app/overlays/ljwx-website-dev apps/my-app/overlays/my-app-dev
# 更新 namespace
sed -i 's/namespace: ljwx-website-dev/namespace: my-app-dev/' \
apps/my-app/overlays/my-app-dev/kustomization.yaml
```
### Step 3: 创建 Argo CD Application
```bash
# 复制模板
cp argocd-apps/60-ljwx-website-dev.yaml argocd-apps/60-my-app-dev.yaml
# 替换内容
sed -i 's/ljwx-website/my-app/g' argocd-apps/60-my-app-dev.yaml
```
### Step 4: 提交并推送
```bash
git add .
git commit -m "feat: add my-app deployment configuration"
git push origin main
# 等待 30 秒
sleep 30
# ✅ Argo CD 自动创建 Application 并部署!
kubectl get application my-app-dev -n argocd
```
---
## 📋 完整文档索引
| 文档 | 用途 | 场景 |
|------|------|------|
| **[GITEA-SECRETS-SETUP.md](GITEA-SECRETS-SETUP.md)** | Organization Secrets 配置 | 首次设置 |
| **[GITEA-CICD-SETUP-GUIDE.md](GITEA-CICD-SETUP-GUIDE.md)** | 完整 CI/CD 配置指南 | 新项目配置 |
| **[ARGOCD-APP-MANAGEMENT.md](ARGOCD-APP-MANAGEMENT.md)** | Argo CD App of Apps 使用 | CD 管理 |
| **[NAMESPACE-STRATEGY.md](NAMESPACE-STRATEGY.md)** | Namespace 策略分析 | 架构设计 |
| **[QUICK-REFERENCE.md](QUICK-REFERENCE.md)** | 快速参考卡片 | 日常查阅 |
---
## 🔍 常用命令速查
### CI/CD 调试
```bash
# 查看 Gitea Actions 运行
http://192.168.1.83:33000/gao/[project]/actions
# 查看 Argo CD 应用状态
kubectl get application -n argocd
# 查看 Pod 状态
kubectl get pods -n [namespace] -l app=[app-name]
# 查看 Pod 日志
kubectl logs -n [namespace] -l app=[app-name] --tail=50
# 手动触发 Argo CD 同步
kubectl patch application [app-name] -n argocd \
--type merge \
-p '{"operation":{"initiatedBy":{"username":"admin"},"sync":{"revision":"HEAD"}}}'
```
### 资源检查
```bash
# 查看 namespace 资源配额使用情况
kubectl get resourcequota -n [namespace]
kubectl describe resourcequota -n [namespace]
# 查看 Pod 资源使用
kubectl top pods -n [namespace]
# 查看 Node 资源使用
kubectl top nodes
```
### Harbor 镜像管理
```bash
# 登录 Harbor
docker login harbor.omniverseai.net
# 查看镜像列表
curl -u admin:password \
http://harbor.omniverseai.net/api/v2.0/projects/ljwx/repositories
# 查看镜像 tags
docker buildx imagetools inspect harbor.omniverseai.net/ljwx/[app]:latest
# 手动拉取镜像
docker pull harbor.omniverseai.net/ljwx/[app]:main-[sha]
```
### Git 操作
```bash
# 克隆 ljwx-deploy
git clone http://192.168.1.83:33000/gao/ljwx-deploy.git
# 查看最近提交
cd ljwx-deploy
git log --oneline -10
# 查看特定应用的变更历史
git log --oneline -- apps/ljwx-website/overlays/ljwx-website-dev/kustomization.yaml
# 回滚到上一个版本
git revert HEAD
git push origin main
# Argo CD 会自动回滚部署
```
---
## ⚡ 快速测试流程
### 测试完整 GitOps 流程
```bash
# 1. 修改代码
cd /path/to/project
echo "// Test CI/CD" >> src/App.tsx
# 2. 提交并推送
git add .
git commit -m "test: verify GitOps flow"
git push origin main
# 3. 观察 CI (约 3-5 分钟)
# http://192.168.1.83:33000/gao/[project]/actions
# ✅ Lint & Test → ✅ Build & Push → ✅ Update Deploy
# 4. 观察 CD (约 30 秒)
watch kubectl get pods -n [namespace] -l app=[app-name]
# 应看到旧 Pod Terminating,新 Pod Running
# 5. 验证镜像更新
kubectl describe pod -n [namespace] -l app=[app-name] | grep Image:
# 应显示新的 image tag: main-[new-sha]
# 6. 验证健康检查
kubectl exec -n [namespace] deployment/[app-name] -- \
wget -q -O- http://localhost/health
# 应返回: healthy
```
---
## 🎯 Workflow 配置关键点
### Runner 选择
```yaml
# 轻量级任务 (lint, test)
runs-on: ubuntu-latest # K8s runner
# Docker 构建任务
runs-on: docker-builder # 宿主机 runner (macos 标签也可用)
```
### Multi-arch 构建
```yaml
# ✅ 正确: 使用 buildx + atomic push
- name: Set up Docker Buildx
run: |
docker buildx create --name ci-builder --use || docker buildx use ci-builder
docker buildx inspect --bootstrap
- name: Build and push
run: |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--push \
.
# ❌ 错误: 分离的 build 和 push
docker build -t image .
docker push image # 会导致竞态条件
```
### Kustomize 镜像更新
```yaml
# ✅ 正确: 使用 yq 更新 kustomization.yaml
cd apps/my-app/overlays/my-app-dev
yq eval ".images[0].newTag = \"${IMAGE_TAG}\"" -i kustomization.yaml
# ❌ 错误: 尝试更新 Helm values.yaml
yq eval ".image.tag = \"${IMAGE_TAG}\"" -i values.yaml # 不存在
```
---
## 🔐 安全提示
### Secrets 安全使用
```yaml
# ✅ 安全的 Secrets 使用
- run: echo "${{ secrets.PASSWORD }}" | docker login ... --password-stdin
# ❌ 危险: 泄露 Secrets
- run: echo "Password is ${{ secrets.PASSWORD }}" # 会在日志中显示
- run: docker login -p ${{ secrets.PASSWORD }} ... # 会在进程列表中显示
```
### Token 权限最小化
```bash
# DEPLOY_REPO_TOKEN 需要的权限:
✅ repo # 读取仓库
✅ write:repository # 写入仓库
# 不需要的权限:
❌ admin:org # 组织管理
❌ delete:packages # 删除包
❌ admin:gpg_key # GPG 密钥管理
```
---
## 📊 配置时间对比
### 使用本指南
```
Organization Secrets (一次性): 5 分钟
新项目 CI 配置: 2 分钟
新项目 CD 配置: 8 分钟
---------------------------------
总计 (首次): 15 分钟
总计 (后续每个项目): 10 分钟
```
### 传统方式
```
每个项目 Secrets 配置: 5 分钟
手动编写 Workflow: 30 分钟
手动创建 K8s 资源: 20 分钟
手动配置 Argo CD: 10 分钟
---------------------------------
总计 (每个项目): 65 分钟
```
**效率提升: 85%** 🚀
---
## 💡 故障排查速查表
| 问题 | 排查命令 | 常见原因 |
|------|----------|----------|
| CI 失败 | 查看 Gitea Actions 日志 | Runner 标签错误、Secrets 未配置 |
| Build 失败 | `docker buildx ls` | buildx 未初始化 |
| Push 失败 | `docker login harbor.omniverseai.net` | Harbor 凭据错误 |
| Update Deploy 未执行 | 检查 ljwx-deploy 最新 commit | Token 权限不足、路径错误 |
| Argo CD 未同步 | `kubectl get application -n argocd` | Application 文件名不匹配 pattern |
| Pod 启动失败 | `kubectl describe pod -n [ns]` | 资源不足、镜像拉取失败 |
| Health check 失败 | `kubectl logs -n [ns]` | /health 端点不存在 |
---
## 🎓 学习路径
### 阶段 1: 基础配置 (第 1 天)
1. 阅读 [GITEA-SECRETS-SETUP.md](GITEA-SECRETS-SETUP.md)
2. 配置 Organization Secrets
3. 测试现有项目 (ljwx-website)
### 阶段 2: 新项目实践 (第 2-3 天)
1. 阅读 [GITEA-CICD-SETUP-GUIDE.md](GITEA-CICD-SETUP-GUIDE.md)
2. 为一个新项目配置完整 CI/CD
3. 测试完整 GitOps 流程
### 阶段 3: 高级应用 (第 4-5 天)
1. 阅读 [ARGOCD-APP-MANAGEMENT.md](ARGOCD-APP-MANAGEMENT.md)
2. 理解 App of Apps 模式
3. 阅读 [NAMESPACE-STRATEGY.md](NAMESPACE-STRATEGY.md)
4. 规划多环境部署 (dev/staging/prod)
### 阶段 4: 运维优化 (持续)
1. 监控 CI/CD 性能
2. 优化 Docker 镜像大小
3. 调整资源配额
4. 实施安全最佳实践
---
**🎉 开始使用 Gitea GitOps,享受自动化部署的乐趣!**
+114
View File
@@ -0,0 +1,114 @@
# ljwx-deploy
GitOps 配置仓库,使用 ArgoCD 管理 Kubernetes 集群。
## 目录结构
- `infra/` - 基础设施组件(Gitea Runner 等)
- `argocd-apps/` - ArgoCD Application 定义
- `apps/` - 应用配置
- `cluster/` - 集群级别配置
## 组件
### Gitea Runner (act_runner)
- 支持 Gitea Actions 的弹性 runner
- 自动伸缩: min 1, max 5 (基于 CPU/Memory)
- 支持 Docker build/push
- Harbor registry: https://harbor.omniverseai.net
#### 配置说明
Runner labels:
- `ubuntu-latest` - Ubuntu 22.04 环境
- `ubuntu-22.04` - 明确指定版本
- `docker` - 支持 Docker 操作
- `self-hosted` - 自托管标识
弹性伸缩策略:
- 最小副本: 1
- 最大副本: 5
- CPU 阈值: 70%
- Memory 阈值: 80%
- 扩容: 30秒内最多翻倍或增加2个
- 缩容: 5分钟稳定期,每分钟最多减少50%
## 使用方法
### 1. 获取 Gitea Runner Token
在 Gitea 管理界面:
1. 访问: http://192.168.1.83:33000/admin/actions/runners
2. 点击"创建新的 Runner"
3. 复制生成的 Registration Token
### 2. 创建 Runner Token Secret
```bash
kubectl -n gitea-runner create secret generic runner-token \
--from-literal=token=YOUR_REGISTRATION_TOKEN
```
### 3. 部署 Runner
ArgoCD 会自动同步并部署 runner。
### 4. 在仓库中使用
创建 `.gitea/workflows/ci.yml`:
```yaml
name: CI
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest # 或 docker, self-hosted
steps:
- uses: actions/checkout@v4
- name: 构建镜像
run: |
docker build -t myapp:${{ gitea.sha }} .
docker push harbor.omniverseai.net/myproject/myapp:${{ gitea.sha }}
```
## 监控
```bash
# 查看 runner pods
kubectl -n gitea-runner get pods -w
# 查看 HPA 状态
kubectl -n gitea-runner get hpa
# 查看 runner 日志
kubectl -n gitea-runner logs -l app=gitea-runner -f
```
## 故障排查
### Runner 未注册
检查 token 是否正确:
```bash
kubectl -n gitea-runner get secret runner-token -o jsonpath='{.data.token}' | base64 -d
```
### Runner 无法连接 Gitea
检查网络连通性:
```bash
kubectl -n gitea-runner run test --rm -it --image=curlimages/curl -- \
curl http://192.168.1.83:33000
```
### Docker 权限问题
Runner 使用宿主机 Docker socket,确保有权限访问。
# Test stability
-72
View File
@@ -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"
-25
View File
@@ -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
-7
View File
@@ -1,7 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: regcred
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuZXUubGluZ2ppbmd3YW54aWFuZy5jbiI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifX19
-17
View File
@@ -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,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
+7 -20
View File
@@ -4,43 +4,30 @@ 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
image: harbor.omniverseai.net/ljwx/backend-template:dev
ports:
- containerPort: 8000
name: http
- name: http
containerPort: 8000
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
@@ -55,8 +42,8 @@ spec:
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
memory: "128Mi"
cpu: "100m"
limits:
cpu: 500m
memory: 512Mi
memory: "512Mi"
cpu: "500m"
+4
View File
@@ -4,3 +4,7 @@ kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/name: backend
app.kubernetes.io/part-of: youngth-guard
+4 -8
View File
@@ -4,17 +4,13 @@ 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
- port: 8000
targetPort: http
protocol: TCP
name: http
selector:
app: backend
@@ -5,3 +5,11 @@ namespace: dev
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/backend-template
newTag: "dev"
replicas:
- name: backend
count: 1
+44
View File
@@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
labels:
app: frontend
component: web
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
component: web
spec:
containers:
- name: frontend
image: harbor.omniverseai.net/ljwx/frontend-dashboard:dev
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
+10
View File
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/name: frontend
app.kubernetes.io/part-of: youngth-guard
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: frontend
labels:
app: frontend
component: web
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: frontend
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/frontend-dashboard
newTag: "dev"
replicas:
- name: frontend
count: 1
-326
View File
@@ -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 配置
+173
View File
@@ -0,0 +1,173 @@
# ljwx-bookstore DNS 配置指南
## 域名信息
- **生产域名**: `book.omniverseai.net`
- **DNS提供商**: Cloudflare
- **SSL证书**: Let's Encrypt (自动通过cert-manager获取)
## DNS记录配置
### 步骤1: 登录 Cloudflare
1. 访问 https://dash.cloudflare.com/
2. 选择域名 `omniverseai.net`
3. 进入 DNS 管理页面
### 步骤2: 添加 A 记录
需要添加以下DNS记录:
| 类型 | 名称 | 内容 | 代理状态 | TTL |
|------|------|------|----------|-----|
| A | book | `<INGRESS_IP>` | 仅DNS(灰色云朵) | Auto |
**获取 Ingress IP**:
```bash
kubectl get ingress ljwx-bookstore -n ljwx-bookstore \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}'
```
**当前 Ingress IP**:
```bash
# 从集群中获取
192.168.194.177 # 示例,请使用实际IP
```
### 步骤3: Cloudflare 配置建议
#### SSL/TLS 设置
1. 进入 `SSL/TLS` → `概述`
2. 加密模式选择: **完全(严格)** 或 **完全**
3. 推荐: **完全(严格)** - 确保端到端加密
#### 代理状态
- **开发/测试**: 仅DNS(灰色云朵)- 直连Kubernetes
- **生产环境**: 代理(橙色云朵)- 使用Cloudflare CDN和DDoS防护
#### 其他推荐配置
- 启用 **自动HTTPS重写**
- 启用 **始终使用HTTPS**
- 配置 **最小TLS版本**: TLS 1.2
## 验证步骤
### 1. DNS解析验证
```bash
# 检查DNS记录
dig book.omniverseai.net +short
# 或使用nslookup
nslookup book.omniverseai.net
```
**期望输出**: 应该返回Ingress Controller的IP地址
### 2. SSL证书验证
等待2-5分钟,cert-manager会自动申请证书:
```bash
# 检查Certificate资源
kubectl get certificate -n ljwx-bookstore
# 查看证书详情
kubectl describe certificate ljwx-bookstore-tls -n ljwx-bookstore
# 检查证书Secret
kubectl get secret ljwx-bookstore-tls -n ljwx-bookstore
```
**期望状态**: `READY=True`
### 3. 访问测试
```bash
# HTTP 自动重定向到 HTTPS
curl -I http://book.omniverseai.net
# HTTPS 访问
curl -I https://book.omniverseai.net
# 浏览器访问
open https://book.omniverseai.net/fiction/index
```
### 4. SSL证书检查
```bash
# 使用 openssl 检查证书
echo | openssl s_client -servername book.omniverseai.net \
-connect book.omniverseai.net:443 2>/dev/null | \
openssl x509 -noout -dates -subject
# 在线检查
# 访问 https://www.ssllabs.com/ssltest/
# 输入: book.omniverseai.net
```
## 故障排查
### DNS未解析
```bash
# 检查Cloudflare DNS设置
# 1. 确认A记录存在
# 2. 确认IP地址正确
# 3. 清除本地DNS缓存: sudo dscacheutil -flushcache (macOS)
```
### 证书未自动生成
```bash
# 检查 cert-manager 日志
kubectl logs -n cert-manager -l app=cert-manager --tail=50
# 检查 Certificate Events
kubectl describe certificate ljwx-bookstore-tls -n ljwx-bookstore
# 检查 CertificateRequest
kubectl get certificaterequest -n ljwx-bookstore
kubectl describe certificaterequest -n ljwx-bookstore
# 常见问题:
# 1. Cloudflare API Token权限不足
# 2. DNS记录配置错误
# 3. ClusterIssuer配置问题
```
### HTTPS访问失败
```bash
# 检查 Ingress 状态
kubectl get ingress ljwx-bookstore -n ljwx-bookstore
kubectl describe ingress ljwx-bookstore -n ljwx-bookstore
# 检查 Ingress Controller 日志
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=50
```
## 生产环境清单
部署到生产环境前,确认以下事项:
- [ ] DNS A记录已添加并解析正确
- [ ] SSL证书自动获取成功(`kubectl get certificate -n ljwx-bookstore`)
- [ ] HTTPS强制重定向已启用
- [ ] 健康检查正常工作
- [ ] 应用可通过 https://book.omniverseai.net 访问
- [ ] SSL证书A级评分(ssllabs.com测试)
- [ ] Cloudflare防火墙规则已配置(可选)
- [ ] 监控和告警已设置(可选)
## 相关资源
- Ingress配置: `ingress.yaml`
- cert-manager文档: https://cert-manager.io/docs/
- Cloudflare API: https://developers.cloudflare.com/api/
- Let's Encrypt文档: https://letsencrypt.org/docs/
## 更新历史
- 2026-01-01: 初始配置,添加 book.omniverseai.net 域名
@@ -0,0 +1,173 @@
# ljwx-bookstore Secret 管理
## 概述
为了符合安全最佳实践和GitOps原则,ljwx-bookstore的Secret**不在Git中存储**。
## Secret 创建方式
### 自动化脚本(推荐)
使用提供的脚本从infra namespace自动获取密码并创建Secret:
```bash
# 在 ljwx-deploy 仓库根目录执行
./scripts/create-app-secret.sh ljwx-bookstore
```
### 手动创建
如果自动化脚本不可用,使用以下命令手动创建:
```bash
# 从 infra namespace 获取基础设施密码
MYSQL_PASSWORD=$(kubectl get secret infra-credentials -n infra -o jsonpath='{.data.mysql-root-password}' | base64 -d)
REDIS_PASSWORD=$(kubectl get secret infra-credentials -n infra -o jsonpath='{.data.redis-password}' | base64 -d)
# 创建 ljwx-bookstore Secret
kubectl create secret generic ljwx-bookstore-secret \
--from-literal=DB_HOST='mysql-infra.infra.svc.cluster.local' \
--from-literal=DB_PORT='3306' \
--from-literal=DB_NAME='org_fiction' \
--from-literal=DB_USER='root' \
--from-literal=DB_PASSWORD="${MYSQL_PASSWORD}" \
--from-literal=REDIS_HOST='redis-infra-master.infra.svc.cluster.local' \
--from-literal=REDIS_PORT='6379' \
--from-literal=REDIS_PASSWORD="${REDIS_PASSWORD}" \
--from-literal=WX_APP_SECRET="placeholder" \
--from-literal=WX_MCH_KEY="placeholder" \
--from-literal=MAIL_PASSWORD="placeholder" \
--from-literal=SMS_TENCENT_APPKEY="placeholder" \
--from-literal=SMS_ALIYUN_ACCESS_KEY_SECRET="xxx" \
--from-literal=STORAGE_ALIYUN_ACCESS_KEY_ID="placeholder" \
--from-literal=STORAGE_ALIYUN_ACCESS_KEY_SECRET="placeholder" \
--from-literal=STORAGE_TENCENT_SECRET_ID="placeholder" \
--from-literal=STORAGE_TENCENT_SECRET_KEY="placeholder" \
--from-literal=AI_CLOUD_API_KEY="" \
--namespace=ljwx-bookstore
```
## Secret 架构
```
┌─────────────────────────────────────┐
│ infra namespace │
│ ┌─────────────────────────────────┐ │
│ │ infra-credentials Secret │ │ ← 唯一真实来源
│ │ - mysql-root-password │ │ (infra团队管理)
│ │ - redis-password │ │
│ │ - postgres-password │ │
│ └─────────────────────────────────┘ │
└──────────────┬──────────────────────┘
│
│ 手动复制密码
│ (未来: External Secrets Operator自动同步)
▼
┌─────────────────────────────────────┐
│ ljwx-bookstore namespace │
│ ┌─────────────────────────────────┐ │
│ │ ljwx-bookstore-secret │ │ ← 手动创建(一次性)
│ │ - DB_PASSWORD (从infra复制) │ │
│ │ - REDIS_PASSWORD (从infra复制) │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
```
## 部署流程
### 新环境部署
1. **创建 infra namespace 基础设施密码**(infra团队负责)
```bash
kubectl create secret generic infra-credentials \
--from-literal=mysql-root-password='YOUR_PASSWORD' \
--from-literal=redis-password='YOUR_PASSWORD' \
--namespace=infra
```
2. **创建应用 Secret**
```bash
./scripts/create-app-secret.sh ljwx-bookstore
```
3. **部署应用**(Argo CD自动同步)
```bash
argocd app sync ljwx-bookstore
```
### 密码轮换
当infra团队更新基础设施密码时:
```bash
# 1. infra团队更新 infra-credentials
kubectl patch secret infra-credentials -n infra \
--type='json' \
-p='[{"op": "replace", "path": "/data/mysql-root-password", "value": "BASE64_NEW_PASSWORD"}]'
# 2. 重新创建应用Secret
kubectl delete secret ljwx-bookstore-secret -n ljwx-bookstore
./scripts/create-app-secret.sh ljwx-bookstore
# 3. 重启应用
kubectl rollout restart deployment ljwx-bookstore -n ljwx-bookstore
```
## 验证
```bash
# 检查Secret是否存在
kubectl get secret ljwx-bookstore-secret -n ljwx-bookstore
# 验证密码值
kubectl get secret ljwx-bookstore-secret -n ljwx-bookstore \
-o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# 应该输出与infra-credentials中相同的密码
```
## 路线图
**当前状态**(过渡方案):
- ✅ Secret不在Git中(符合安全最佳实践)
- ✅ 使用脚本从infra统一获取密码(避免手动输入错误)
- ⚠️ 需要手动创建一次Secret
**未来计划**(完全GitOps):
- [ ] 部署 External Secrets Operator
- [ ] 迁移到 ExternalSecret CRD
- [ ] 实现自动密码同步
- [ ] 无需任何手动操作
## 故障排查
### Secret未找到
```bash
# 检查Secret是否存在
kubectl get secret ljwx-bookstore-secret -n ljwx-bookstore
# 如果不存在,创建它
./scripts/create-app-secret.sh ljwx-bookstore
```
### 密码不正确
```bash
# 比较应用Secret和infra Secret
echo "infra密码:"
kubectl get secret infra-credentials -n infra -o jsonpath='{.data.mysql-root-password}' | base64 -d
echo ""
echo "应用密码:"
kubectl get secret ljwx-bookstore-secret -n ljwx-bookstore -o jsonpath='{.data.DB_PASSWORD}' | base64 -d
# 如果不一致,重新创建
kubectl delete secret ljwx-bookstore-secret -n ljwx-bookstore
./scripts/create-app-secret.sh ljwx-bookstore
```
## 相关文档
- 详细架构设计:`/docs/GITOPS_SECRET_MANAGEMENT.md`
- infra namespace密码管理:`/infra/secrets/README.md`
+91
View File
@@ -0,0 +1,91 @@
---
# =============================================================================
# ljwx-bookstore ConfigMap Configuration
# 应用配置和非敏感环境变量
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-bookstore-config
namespace: ljwx-bookstore
labels:
app: ljwx-bookstore
component: config
data:
# 应用基础配置
SPRING_PROFILES_ACTIVE: "prod"
SERVER_PORT: "8844"
# JVM配置
JAVA_OPTS: "-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0 -Djava.security.egd=file:/dev/./urandom -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof"
# 数据库连接池配置(非敏感)
HIKARI_MAXIMUM_POOL_SIZE: "20"
HIKARI_MINIMUM_IDLE: "5"
HIKARI_CONNECTION_TIMEOUT: "20000"
HIKARI_IDLE_TIMEOUT: "300000"
# AI服务配置
AI_ENABLED: "true"
AI_LOCAL_ENABLED: "true"
AI_LOCAL_PROVIDER: "ollama"
AI_LOCAL_OLLAMA_URL: "http://ollama.devops.svc.cluster.local:11434"
AI_LOCAL_DEFAULT_MODEL: "qwen2.5:7b"
AI_LOCAL_FALLBACK_MODEL: "llama3.2:8b"
AI_CLOUD_ENABLED: "false"
# OpenSearch配置
AI_OPENSEARCH_HOST: "opensearch.monitoring.svc.cluster.local"
AI_OPENSEARCH_PORT: "9200"
AI_OPENSEARCH_PARAGRAPH_INDEX: "ai_paragraphs"
# 对象存储配置
HGBOOK_STORAGE_ACTIVE: "aliyun"
HGBOOK_STORAGE_ALIYUN_REGION_ID: "cn-zhangjiakou"
HGBOOK_STORAGE_ALIYUN_BUCKET_NAME: "hg-tech"
# 监控配置
MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: "health,info,metrics,prometheus"
MANAGEMENT_ENDPOINT_HEALTH_SHOW_DETAILS: "always"
MANAGEMENT_METRICS_EXPORT_PROMETHEUS_ENABLED: "true"
# 日志配置
LOGGING_LEVEL_ROOT: "INFO"
LOGGING_LEVEL_CN_BOOK_BUS: "DEBUG"
LOGGING_PATTERN_CONSOLE: "%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n"
# Flyway配置
SPRING_FLYWAY_ENABLED: "true"
SPRING_FLYWAY_BASELINE_ON_MIGRATE: "true"
SPRING_FLYWAY_LOCATIONS: "classpath:db/migration"
# MyBatis Plus配置
MYBATIS_CONFIGURATION_MAP_UNDERSCORE_TO_CAMEL_CASE: "true"
MYBATIS_CONFIGURATION_CACHE_ENABLED: "true"
# 微信支付配置(非敏感)
HGBOOK_WX_APP_ID: "wxe92bc58a93b04a87"
HGBOOK_WX_MCH_ID: "1623644809"
HGBOOK_WX_NOTIFY_URL: "http://bookstore.192.168.1.83.nip.io/wx/order/pay-notify"
# 邮件配置(非敏感)
HGBOOK_NOTIFY_MAIL_ENABLE: "true"
HGBOOK_NOTIFY_MAIL_HOST: "smtp.qq.com"
HGBOOK_NOTIFY_MAIL_PORT: "465"
HGBOOK_NOTIFY_MAIL_USERNAME: "38455629@qq.com"
HGBOOK_NOTIFY_MAIL_SENDFROM: "38455629@qq.com"
HGBOOK_NOTIFY_MAIL_SENDTO: "gaojunivas@gmail.com"
# 短信配置(非敏感)
HGBOOK_NOTIFY_SMS_ENABLE: "true"
HGBOOK_NOTIFY_SMS_ACTIVE: "tencent"
HGBOOK_NOTIFY_SMS_SIGN: "和光文化"
HGBOOK_NOTIFY_SMS_TENCENT_APPID: "1400311284"
# OpenTelemetry配置
OTEL_EXPORTER_OTLP_ENDPOINT: "http://otel-collector-opentelemetry-collector.tracing.svc.cluster.local:4317"
OTEL_SERVICE_NAME: "ljwx-bookstore"
OTEL_TRACES_SAMPLER: "parentbased_traceidratio"
OTEL_TRACES_SAMPLER_ARG: "0.1"
OTEL_METRICS_EXPORTER: "prometheus"
OTEL_LOGS_EXPORTER: "none"
+90
View File
@@ -0,0 +1,90 @@
---
# =============================================================================
# Database Initialization Job
# 在 infra namespace 的 MySQL 中创建 org_fiction 数据库
# =============================================================================
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-bookstore-db-init
namespace: ljwx-bookstore
labels:
app: ljwx-bookstore
component: db-init
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "-1"
spec:
ttlSecondsAfterFinished: 300
backoffLimit: 3
template:
metadata:
labels:
app: ljwx-bookstore
component: db-init
spec:
restartPolicy: Never
containers:
- name: db-init
image: mysql:8.0
command:
- /bin/bash
- -c
- |
set -e
echo "=== Starting Database Initialization ==="
echo "Connecting to MySQL at ${DB_HOST}:${DB_PORT}..."
# 等待 MySQL 就绪
until mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" -e "SELECT 1" &>/dev/null; do
echo "MySQL not ready, waiting 2 seconds..."
sleep 2
done
echo "MySQL is ready! Creating database if not exists..."
# 创建数据库
mysql -h"${DB_HOST}" -P"${DB_PORT}" -u"${DB_USER}" -p"${DB_PASSWORD}" <<EOF
CREATE DATABASE IF NOT EXISTS ${DB_NAME} CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL PRIVILEGES ON ${DB_NAME}.* TO '${DB_USER}'@'%';
FLUSH PRIVILEGES;
SELECT 'Database ${DB_NAME} created or already exists' AS status;
EOF
echo "=== Database Initialization Completed Successfully ==="
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_HOST
- name: DB_PORT
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_PORT
- name: DB_NAME
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_NAME
- name: DB_USER
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
+177 -46
View File
@@ -1,79 +1,210 @@
---
# =============================================================================
# ljwx-bookstore Deployment Configuration
# Spring Boot 应用部署,连接 infra namespace 的 MySQL 和 Redis
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: bookstore
name: ljwx-bookstore
namespace: ljwx-bookstore
labels:
app: bookstore
managed-by: argocd
project: ljwx-bookstore
app: ljwx-bookstore
component: backend
version: v1.0.0
spec:
replicas: 2
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: bookstore
app: ljwx-bookstore
component: backend
template:
metadata:
labels:
app: bookstore
project: ljwx-bookstore
version: v0.1.4
app: ljwx-bookstore
component: backend
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8844"
prometheus.io/path: "/fiction/actuator/prometheus"
spec:
imagePullSecrets:
- name: ghcr-secret
# 初始化容器 - 等待数据库就绪
initContainers:
- name: wait-for-mysql
image: busybox:1.35
command:
- sh
- -c
- |
echo "Waiting for MySQL to be ready..."
until nc -z mysql-infra.infra.svc.cluster.local 3306; do
echo "MySQL not ready, sleeping for 2 seconds..."
sleep 2
done
echo "MySQL is ready!"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 100m
memory: 256Mi
# 主容器
containers:
- name: bookstore
image: ghcr.io/brunogaosz/ljwx-bookstore/bookstore:bookstore-latest
- name: ljwx-bookstore
image: harbor.omniverseai.net/ljwx/ljwx-bookstore:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
- name: http
containerPort: 8844
protocol: TCP
# 环境变量 - 从 ConfigMap
envFrom:
- configMapRef:
name: ljwx-bookstore-config
# 环境变量 - 从 Secret(敏感信息)
env:
- name: SPRING_PROFILES_ACTIVE
value: "prod"
- name: SERVER_PORT
value: "8080"
# Database configuration from infra Postgres
- name: SPRING_DATASOURCE_URL
# 数据库配置
- name: DB_HOST
valueFrom:
secretKeyRef:
name: bookstore-db-credentials
key: SPRING_DATASOURCE_URL
- name: SPRING_DATASOURCE_USERNAME
name: ljwx-bookstore-secret
key: DB_HOST
- name: DB_PORT
valueFrom:
secretKeyRef:
name: bookstore-db-credentials
key: SPRING_DATASOURCE_USERNAME
- name: SPRING_DATASOURCE_PASSWORD
name: ljwx-bookstore-secret
key: DB_PORT
- name: DB_NAME
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
name: ljwx-bookstore-secret
key: DB_NAME
- name: DB_USER
valueFrom:
secretKeyRef:
name: redis-credentials
name: ljwx-bookstore-secret
key: DB_USER
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: DB_PASSWORD
# Redis配置
- name: REDIS_HOST
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: REDIS_HOST
- name: REDIS_PORT
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: REDIS_PORT
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: REDIS_PASSWORD
- name: SPRING_DATA_REDIS_DATABASE
value: "0"
# 微信支付配置
- name: WX_APP_SECRET
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: WX_APP_SECRET
- name: WX_MCH_KEY
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: WX_MCH_KEY
# 邮件配置
- name: MAIL_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: MAIL_PASSWORD
# 短信配置
- name: SMS_TENCENT_APPKEY
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: SMS_TENCENT_APPKEY
# 对象存储配置
- name: STORAGE_ALIYUN_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: STORAGE_ALIYUN_ACCESS_KEY_ID
- name: STORAGE_ALIYUN_ACCESS_KEY_SECRET
valueFrom:
secretKeyRef:
name: ljwx-bookstore-secret
key: STORAGE_ALIYUN_ACCESS_KEY_SECRET
# 资源配置
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# 健康检查
livenessProbe:
httpGet:
path: /fiction/actuator/health
port: 8080
port: 8844
scheme: HTTP
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /fiction/actuator/health
port: 8080
port: 8844
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
startupProbe:
httpGet:
path: /fiction/actuator/health
port: 8844
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
successThreshold: 1
# 生命周期
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
# 重启策略
restartPolicy: Always
terminationGracePeriodSeconds: 60
dnsPolicy: ClusterFirst
@@ -1,7 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: ghcr-secret
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJnaGNyLmlvIjp7InVzZXJuYW1lIjoiQnJ1bm9HYW9TWiIsInBhc3N3b3JkIjoiZ2hwX05UVVNwTW9kOURhcXAxeGxCTWliOGxnV09WMUJidTF5MjJ4cyIsImVtYWlsIjoieW91ci1lbWFpbEBleGFtcGxlLmNvbSIsImF1dGgiOiJRbkoxYm05SFlXOVRXanBuYUhCZlRsUlZVM0JOYjJRNVJHRnhjREY0YkVKTmFXSTRiR2RYVDFZeFFtSjFNWGt5TW5oeiJ9fX0=
+51 -35
View File
@@ -1,41 +1,57 @@
---
# =============================================================================
# ljwx-bookstore Ingress Configuration
# 域名: book.omniverseai.net (HTTP only)
# =============================================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: bookstore-ingress
namespace: bookstore
name: ljwx-bookstore
namespace: ljwx-bookstore
labels:
app: ljwx-bookstore
component: backend
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
# Nginx Ingress 配置
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
# 禁用SSL重定向 - HTTP only
nginx.ingress.kubernetes.io/ssl-redirect: "false"
# 健康检查
nginx.ingress.kubernetes.io/health-check-path: "/fiction/actuator/health"
# CORS 配置(如果需要)
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "http://book.omniverseai.net"
spec:
ingressClassName: traefik
tls:
- hosts:
- bookstore.lingjingwanxiang.cn
secretName: bookstore-lingjingwanxiang-tls
ingressClassName: nginx
# 路由规则
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
# 生产域名
- host: book.omniverseai.net
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-bookstore
port:
number: 80
# 本地测试域名
- host: bookstore.192.168.1.83.nip.io
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: ljwx-bookstore
port:
number: 80
+30 -2
View File
@@ -1,6 +1,34 @@
---
# =============================================================================
# Kustomize Base Configuration for ljwx-bookstore
# =============================================================================
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-bookstore
resources:
- namespace.yaml
# Secret 由 infra 团队手动创建,不在 Git 中管理
# TODO: 后续迁移到 External Secrets Operator 实现完全 GitOps
- configmap.yaml
- deployment.yaml
- service.yaml
- ingress.yaml
- ghcr-secret.yaml
- redis-secret.yaml
- db-init-job.yaml
commonLabels:
app: ljwx-bookstore
managed-by: kustomize
# 镜像替换(将由 CD 流程动态更新)
images:
- name: harbor.omniverseai.net/ljwx/ljwx-bookstore
newTag: latest
# 配置生成器行为
generatorOptions:
disableNameSuffixHash: true
# 构建元数据
buildMetadata: [originAnnotations, transformerAnnotations]
+13
View File
@@ -0,0 +1,13 @@
---
# =============================================================================
# ljwx-bookstore Namespace
# =============================================================================
apiVersion: v1
kind: Namespace
metadata:
name: ljwx-bookstore
labels:
name: ljwx-bookstore
project: ljwx
environment: production
managed-by: argocd
@@ -1,7 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: redis-credentials
type: Opaque
stringData:
REDIS_PASSWORD: "123456"
+18 -7
View File
@@ -1,12 +1,23 @@
---
# =============================================================================
# ljwx-bookstore Service Configuration
# =============================================================================
apiVersion: v1
kind: Service
metadata:
name: bookstore
name: ljwx-bookstore
namespace: ljwx-bookstore
labels:
app: ljwx-bookstore
component: backend
spec:
selector:
app: bookstore
ports:
- protocol: TCP
port: 8080
targetPort: 8080
type: ClusterIP
selector:
app: ljwx-bookstore
component: backend
ports:
- name: http
port: 80
targetPort: 8844
protocol: TCP
sessionAffinity: None
@@ -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 注入
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-bookstore
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/ljwx-bookstore
newTag: "main-9e937b1"
replicas:
- name: ljwx-bookstore
count: 1
-530
View File
@@ -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
+64
View File
@@ -0,0 +1,64 @@
---
# =============================================================================
# ljwx-health Common ConfigMap
# 公共配置(非敏感信息)
# =============================================================================
apiVersion: v1
kind: ConfigMap
metadata:
name: ljwx-health-config
namespace: ljwx-health
labels:
app: ljwx-health
data:
# Spring 配置
SPRING_PROFILES_ACTIVE: "k8s"
# 数据库配置(连接 infra namespace)
DB_HOST: "mysql-infra.infra.svc.cluster.local"
DB_PORT: "3306"
DB_NAME: "ljwx_health"
DB_USER: "root"
# Spring DataSource 配置
SPRING_DATASOURCE_URL: "jdbc:mysql://mysql-infra.infra.svc.cluster.local:3306/ljwx_health?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true"
SPRING_DATASOURCE_USERNAME: "root"
SPRING_DATASOURCE_DRIVER_CLASS_NAME: "com.mysql.cj.jdbc.Driver"
# Redis 配置(连接 infra namespace)
REDIS_HOST: "redis-infra-master.infra.svc.cluster.local"
REDIS_PORT: "6379"
REDIS_DB: "1"
# Spring Redis 配置
SPRING_REDIS_HOST: "redis-infra-master.infra.svc.cluster.local"
SPRING_REDIS_PORT: "6379"
SPRING_REDIS_DATABASE: "1"
# 日志配置
LOG_LEVEL: "info"
# OSS 配置
OSS_BUCKET_NAME: "ljwx-health"
OSS_ENDPOINT: "http://minio.infra.svc.cluster.local:9000"
# Ollama 配置
OLLAMA_API_URL: "http://ollama.infra.svc.cluster.local:11434"
OLLAMA_MODEL_NAME: "ljwx-health-enhanced:latest"
# LJWX 配置
LJWX_LOGO_UPLOAD_PATH: "/app/uploads"
LJWX_LICENSE_ENABLED: "false"
LJWX_LICENSE_TRIAL_MODE: "false"
# 时区配置
TZ: "Asia/Shanghai"
# Flyway 数据库迁移配置
# ✅ 启用 Flyway - 使用新基线 V1.0.0(2026-01-03)
SPRING_FLYWAY_ENABLED: "true"
SPRING_FLYWAY_BASELINE_ON_MIGRATE: "true" # 已有数据库从 baseline 开始
SPRING_FLYWAY_BASELINE_VERSION: "0"
SPRING_FLYWAY_LOCATIONS: "classpath:db/migration"
SPRING_FLYWAY_VALIDATE_ON_MIGRATE: "true"
SPRING_FLYWAY_CLEAN_DISABLED: "true" # 生产环境禁用 clean
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
# =============================================================================
# Script: create-secret.sh
# Description: 创建 ljwx-health-secret Secret
# Usage: ./create-secret.sh
# =============================================================================
set -e
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# 默认值
NAMESPACE="ljwx-health"
SECRET_NAME="ljwx-health-secret"
# 从环境变量或提示用户输入
read_secret() {
local var_name=$1
local prompt=$2
local default_value=$3
if [ -n "${!var_name}" ]; then
echo "${!var_name}"
elif [ -n "$default_value" ]; then
read -p "$prompt [$default_value]: " value
echo "${value:-$default_value}"
else
read -p "$prompt: " value
echo "$value"
fi
}
echo -e "${GREEN}=== LJWX Health Secret 创建工具 ===${NC}\n"
# 数据库配置
echo -e "${YELLOW}数据库配置(连接 infra namespace MySQL)${NC}"
DB_PASSWORD=$(read_secret "DB_PASSWORD" "数据库密码" "123456")
# Redis 配置
echo -e "\n${YELLOW}Redis 配置(连接 infra namespace Redis)${NC}"
REDIS_PASSWORD=$(read_secret "REDIS_PASSWORD" "Redis 密码" "123456")
# OSS 配置
echo -e "\n${YELLOW}OSS 配置(MinIO)${NC}"
OSS_ACCESS_KEY=$(read_secret "OSS_ACCESS_KEY" "OSS Access Key" "minioadmin")
OSS_SECRET_KEY=$(read_secret "OSS_SECRET_KEY" "OSS Secret Key" "minioadmin")
# 创建 namespace(如果不存在)
echo -e "\n${GREEN}创建 namespace: $NAMESPACE${NC}"
kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
# 删除旧 Secret(如果存在)
echo -e "${GREEN}删除旧 Secret(如果存在)${NC}"
kubectl delete secret $SECRET_NAME -n $NAMESPACE --ignore-not-found=true
# 创建新 Secret
echo -e "${GREEN}创建 Secret: $SECRET_NAME${NC}"
kubectl create secret generic $SECRET_NAME \
-n $NAMESPACE \
--from-literal=DB_PASSWORD="$DB_PASSWORD" \
--from-literal=REDIS_PASSWORD="$REDIS_PASSWORD" \
--from-literal=OSS_ACCESS_KEY="$OSS_ACCESS_KEY" \
--from-literal=OSS_SECRET_KEY="$OSS_SECRET_KEY"
# 验证
echo -e "\n${GREEN}验证 Secret:${NC}"
kubectl get secret $SECRET_NAME -n $NAMESPACE
echo -e "\n${GREEN}✅ Secret 创建成功!${NC}"
echo -e "\n${YELLOW}提示:${NC}"
echo " 1. Secret 包含敏感信息,请妥善保管"
echo " 2. 数据库连接地址: mysql-infra.infra.svc.cluster.local:3306"
echo " 3. Redis 连接地址: redis-infra.infra.svc.cluster.local:6379"
echo " 4. Argo CD 应用将通过 GitOps 自动部署"
echo " 5. 访问地址: http://health-admin.omniverseai.net (HTTP)"
echo " 6. 大屏地址: http://health-dashboard.omniverseai.net (HTTP)"
@@ -0,0 +1,91 @@
---
# =============================================================================
# ljwx-admin Deployment Configuration
# Vue3 前端管理后台(Nginx 静态服务)
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-admin
namespace: ljwx-health
labels:
app: ljwx-admin
component: frontend
version: v1.0.0
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: ljwx-admin
component: frontend
template:
metadata:
labels:
app: ljwx-admin
component: frontend
version: v1.0.0
annotations:
kubectl.kubernetes.io/restartedAt: "20260103-072537"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: harbor-registry
containers:
- name: ljwx-admin
image: harbor.omniverseai.net/ljwx-health/ljwx-admin:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 80
protocol: TCP
# 环境变量
env:
- name: TZ
value: "Asia/Shanghai"
# 后端 API 地址(通过 Ingress 访问)
- name: VITE_API_URL
value: "https://health-admin.omniverseai.net/api"
# 资源配置
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
# 健康检查
livenessProbe:
httpGet:
path: /
port: 80
scheme: HTTP
initialDelaySeconds: 10
periodSeconds: 30
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /
port: 80
scheme: HTTP
initialDelaySeconds: 5
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
successThreshold: 1
# 重启策略
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
@@ -0,0 +1,162 @@
---
# =============================================================================
# ljwx-bigscreen Deployment Configuration
# Flask 大屏监控服务
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-bigscreen
namespace: ljwx-health
labels:
app: ljwx-bigscreen
component: dashboard
version: v1.0.0
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: ljwx-bigscreen
component: dashboard
template:
metadata:
labels:
app: ljwx-bigscreen
component: dashboard
version: v1.0.0
annotations:
kubectl.kubernetes.io/restartedAt: "20260103-072537"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: harbor-registry
# 初始化容器 - 等待数据库就绪
initContainers:
- name: wait-for-mysql
image: busybox:1.35
command:
- sh
- -c
- |
echo "Waiting for MySQL to be ready..."
until nc -z mysql-infra.infra.svc.cluster.local 3306; do
echo "MySQL not ready, sleeping for 2 seconds..."
sleep 2
done
echo "MySQL is ready!"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 100m
memory: 256Mi
# 主容器
containers:
- name: ljwx-bigscreen
image: harbor.omniverseai.net/ljwx-health/ljwx-bigscreen:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 5225
protocol: TCP
# 环境变量 - 从 ConfigMap
envFrom:
- configMapRef:
name: ljwx-health-config
# 环境变量 - 从 Secret(敏感信息)
env:
# 数据库配置
- name: MYSQL_HOST
value: "mysql-infra.infra.svc.cluster.local"
- name: MYSQL_PORT
value: "3306"
- name: MYSQL_DATABASE
value: "ljwx_health"
- name: MYSQL_USER
value: "root"
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
# Redis 配置
- name: REDIS_HOST
value: "redis-infra-master.infra.svc.cluster.local"
- name: REDIS_PORT
value: "6379"
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: REDIS_PASSWORD
# Flask 配置
- name: FLASK_ENV
value: "production"
- name: FLASK_APP
value: "run.py"
# 资源配置
resources:
requests:
cpu: 250m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi
# 健康检查
livenessProbe:
httpGet:
path: /health
port: 5225
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /health
port: 5225
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
startupProbe:
httpGet:
path: /health
port: 5225
scheme: HTTP
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 20
successThreshold: 1
# 生命周期
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 10"]
# 重启策略
restartPolicy: Always
terminationGracePeriodSeconds: 30
dnsPolicy: ClusterFirst
+173
View File
@@ -0,0 +1,173 @@
---
# =============================================================================
# ljwx-boot Deployment Configuration
# Spring Boot 后端服务,连接 infra namespace 的 MySQL 和 Redis
# =============================================================================
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-boot
namespace: ljwx-health
labels:
app: ljwx-boot
component: backend
version: v1.0.0
spec:
replicas: 1
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: ljwx-boot
component: backend
template:
metadata:
labels:
app: ljwx-boot
component: backend
version: v1.0.0
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9998"
prometheus.io/path: "/actuator/prometheus"
kubectl.kubernetes.io/restartedAt: "20260103-080347"
spec:
# Harbor 镜像拉取凭证
imagePullSecrets:
- name: harbor-registry
# 初始化容器 - 等待数据库就绪
initContainers:
- name: wait-for-mysql
image: busybox:1.35
command:
- sh
- -c
- |
echo "Waiting for MySQL to be ready..."
until nc -z mysql-infra.infra.svc.cluster.local 3306; do
echo "MySQL not ready, sleeping for 2 seconds..."
sleep 2
done
echo "MySQL is ready!"
resources:
requests:
cpu: 50m
memory: 128Mi
limits:
cpu: 100m
memory: 256Mi
# 主容器
containers:
- name: ljwx-boot
image: harbor.omniverseai.net/ljwx-health/ljwx-boot:latest
imagePullPolicy: Always
ports:
- name: http
containerPort: 9998
protocol: TCP
# 环境变量 - 从 ConfigMap
envFrom:
- configMapRef:
name: ljwx-health-config
# 环境变量 - 从 Secret(敏感信息)
env:
# 数据库密码(旧格式)
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
# Spring 数据库密码(新格式)
- name: SPRING_DATASOURCE_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
# Redis 密码(旧格式)
- name: REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: REDIS_PASSWORD
# Spring Redis 密码(新格式)
- name: SPRING_REDIS_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: REDIS_PASSWORD
# OSS 密钥
- name: OSS_ACCESS_KEY
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: OSS_ACCESS_KEY
- name: OSS_SECRET_KEY
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: OSS_SECRET_KEY
# 资源配置
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 2000m
memory: 2Gi
# 健康检查
livenessProbe:
httpGet:
path: /actuator/health/liveness
port: 9998
scheme: HTTP
initialDelaySeconds: 120
periodSeconds: 30
timeoutSeconds: 10
failureThreshold: 3
successThreshold: 1
readinessProbe:
httpGet:
path: /actuator/health/readiness
port: 9998
scheme: HTTP
initialDelaySeconds: 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
successThreshold: 1
startupProbe:
httpGet:
path: /actuator/health
port: 9998
scheme: HTTP
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
successThreshold: 1
# 生命周期
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
# 重启策略
restartPolicy: Always
terminationGracePeriodSeconds: 60
dnsPolicy: ClusterFirst
+67
View File
@@ -0,0 +1,67 @@
---
# =============================================================================
# ljwx-health Ingress Configuration
# 路由配置:
# - health-admin.omniverseai.net -> ljwx-admin (Vue3 前端)
# - health-dashboard.omniverseai.net -> ljwx-bigscreen (Flask 大屏)
# - health-admin.omniverseai.net/api -> ljwx-boot (后端 API)
# =============================================================================
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ljwx-health-ingress
namespace: ljwx-health
labels:
app: ljwx-health
annotations:
# CORS 配置
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: "*"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization"
# 请求大小限制
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
# 超时配置
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
ingressClassName: nginx
rules:
# =========================================================================
# Rule 1: health-admin.omniverseai.net
# =========================================================================
- host: health-admin.omniverseai.net
http:
paths:
# 后端 API 路由
- path: /api
pathType: Prefix
backend:
service:
name: ljwx-boot
port:
number: 9998
# 前端静态资源
- path: /
pathType: Prefix
backend:
service:
name: ljwx-admin
port:
number: 80
# =========================================================================
# Rule 2: health-dashboard.omniverseai.net
# =========================================================================
- host: health-dashboard.omniverseai.net
http:
paths:
# 大屏应用
- path: /
pathType: Prefix
backend:
service:
name: ljwx-bigscreen
port:
number: 5001
@@ -0,0 +1,70 @@
---
# =============================================================================
# Flyway Repair Job
# 修复失败的迁移记录,清理 flyway_schema_history 表
# =============================================================================
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-flyway-repair
namespace: ljwx-health
labels:
app: ljwx-health
component: flyway-repair
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "1" # 在 init-db (wave 0) 之后运行
spec:
ttlSecondsAfterFinished: 300
backoffLimit: 3
template:
metadata:
labels:
app: ljwx-health
component: flyway-repair
spec:
restartPolicy: Never
containers:
- name: flyway-repair
image: mysql:8.0
command:
- sh
- -c
- |
echo "检查并修复 Flyway schema history..."
# 删除所有失败的迁移记录
mysql -h mysql-infra.infra.svc.cluster.local -u root -p${MYSQL_ROOT_PASSWORD} ljwx_health <<EOF
-- 检查 flyway_schema_history 表是否存在
SELECT CASE WHEN COUNT(*) > 0 THEN 'Flyway history table exists'
ELSE 'No flyway history table found' END AS status
FROM information_schema.tables
WHERE table_schema = 'ljwx_health'
AND table_name = 'flyway_schema_history';
-- 显示所有迁移记录
SELECT CONCAT('Total migrations before cleanup: ', COUNT(*)) AS count FROM flyway_schema_history;
-- 完全删除 flyway_schema_history 表,让 Flyway 重新开始
DROP TABLE IF EXISTS flyway_schema_history;
SELECT 'Flyway schema history table dropped. Flyway will recreate it on next run.' AS result;
EOF
echo "✅ Flyway schema history 修复完成"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
+59
View File
@@ -0,0 +1,59 @@
---
# =============================================================================
# Database Initialization Job
# 在 ljwx-boot 启动前创建数据库,让 Flyway 可以运行迁移
# =============================================================================
apiVersion: batch/v1
kind: Job
metadata:
name: ljwx-health-init-db
namespace: ljwx-health
labels:
app: ljwx-health
component: init-db
annotations:
argocd.argoproj.io/hook: PreSync
argocd.argoproj.io/hook-delete-policy: BeforeHookCreation
argocd.argoproj.io/sync-wave: "0" # 首先运行
spec:
ttlSecondsAfterFinished: 300 # 5分钟后自动清理
backoffLimit: 3
template:
metadata:
labels:
app: ljwx-health
component: init-db
spec:
restartPolicy: Never
containers:
- name: init-database
image: mysql:8.0
command:
- sh
- -c
- |
echo "检查并创建 ljwx_health 数据库..."
mysql -h mysql-infra.infra.svc.cluster.local -u root -p${MYSQL_ROOT_PASSWORD} <<EOF
CREATE DATABASE IF NOT EXISTS ljwx_health
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
SHOW DATABASES LIKE 'ljwx_health';
SELECT 'Database ljwx_health is ready' AS status;
EOF
echo "✅ 数据库初始化完成"
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: ljwx-health-secret
key: DB_PASSWORD
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 200m
memory: 256Mi
+21 -1
View File
@@ -1,2 +1,22 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- all.yaml
- namespace.yaml
- configmap.yaml
- job-init-db.yaml
- job-flyway-repair.yaml
- deployment-boot.yaml
- deployment-admin.yaml
- deployment-bigscreen.yaml
- service-boot.yaml
- service-admin.yaml
- service-bigscreen.yaml
- ingress.yaml
commonLabels:
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/managed-by: kustomize
+9
View File
@@ -0,0 +1,9 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: ljwx-health
labels:
name: ljwx-health
app.kubernetes.io/name: ljwx-health
app.kubernetes.io/component: health-monitoring
+19
View File
@@ -0,0 +1,19 @@
---
apiVersion: v1
kind: Service
metadata:
name: ljwx-admin
namespace: ljwx-health
labels:
app: ljwx-admin
component: frontend
spec:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 80
protocol: TCP
selector:
app: ljwx-admin
component: frontend
@@ -0,0 +1,19 @@
---
apiVersion: v1
kind: Service
metadata:
name: ljwx-bigscreen
namespace: ljwx-health
labels:
app: ljwx-bigscreen
component: dashboard
spec:
type: ClusterIP
ports:
- name: http
port: 5001
targetPort: 5001
protocol: TCP
selector:
app: ljwx-bigscreen
component: dashboard
+19
View File
@@ -0,0 +1,19 @@
---
apiVersion: v1
kind: Service
metadata:
name: ljwx-boot
namespace: ljwx-health
labels:
app: ljwx-boot
component: backend
spec:
type: ClusterIP
ports:
- name: http
port: 9998
targetPort: 9998
protocol: TCP
selector:
app: ljwx-boot
component: backend
@@ -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"
@@ -0,0 +1,12 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx-health/ljwx-admin
newTag: "main-1f8b811"
@@ -0,0 +1,12 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx-health/ljwx-bigscreen
newTag: "main-1f8b811"
@@ -0,0 +1,12 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-health
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx-health/ljwx-boot
newTag: "main-1f8b811"
@@ -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
-92
View File
@@ -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: {}
-51
View File
@@ -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
-160
View File
@@ -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
-8
View File
@@ -1,8 +0,0 @@
apiVersion: v1
kind: Secret
metadata:
name: regcred
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: eyJhdXRocyI6eyJoYXJib3IuZXUubGluZ2ppbmd3YW54aWFuZy5jbiI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifSwiaGFyYm9yLmV1LmxpbmdqaW5nd2FueGlhbmcuY246ODA4MCI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJIYXJib3IxMjM0NSIsImF1dGgiOiJZV1J0YVc0NlNHRnlZbTl5TVRJek5EVT0ifX19
-18
View File
@@ -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
-14
View File
@@ -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
-256
View File
@@ -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
+44
View File
@@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: ljwx-website
labels:
app: ljwx-website
component: web
spec:
replicas: 2
selector:
matchLabels:
app: ljwx-website
template:
metadata:
labels:
app: ljwx-website
component: web
spec:
containers:
- name: ljwx-website
image: harbor.omniverseai.net/ljwx/ljwx-website:latest
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "25m"
limits:
memory: "128Mi"
cpu: "100m"
@@ -4,6 +4,6 @@ kind: Kustomization
resources:
- deployment.yaml
- service.yaml
- ingress.yaml
- secret.yaml
- regcred.yaml
commonLabels:
app: ljwx-website
@@ -1,14 +1,15 @@
apiVersion: v1
kind: Service
metadata:
name: agent-platform
name: ljwx-website
labels:
app: agent-platform
app: ljwx-website
spec:
selector:
app: agent-platform
ports:
- name: http
port: 8000
targetPort: http
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: ljwx-website
@@ -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
@@ -0,0 +1,11 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: ljwx-website-dev
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/ljwx-website
newTag: "main-abde1fb"
replicas:
- name: ljwx-website
count: 1
+2 -2
View File
@@ -2,5 +2,5 @@ resources:
- ../../base
images:
- name: nginx
newName: ghcr.io/nginxinc/nginx-unprivileged
newTag: "1.27-alpine"
newName: harbor.omniverseai.net/dockerhub/library/nginx
newTag: "alpine"
@@ -0,0 +1,44 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: quality-dashboard
labels:
app: quality-dashboard
component: monitoring
spec:
replicas: 1
selector:
matchLabels:
app: quality-dashboard
template:
metadata:
labels:
app: quality-dashboard
component: monitoring
spec:
containers:
- name: dashboard
image: harbor.omniverseai.net/ljwx/quality-dashboard:dev
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "256Mi"
cpu: "200m"
@@ -0,0 +1,10 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
commonLabels:
app.kubernetes.io/name: quality-dashboard
app.kubernetes.io/part-of: youngth-guard
+16
View File
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
name: quality-dashboard
labels:
app: quality-dashboard
component: monitoring
spec:
type: ClusterIP
ports:
- port: 80
targetPort: http
protocol: TCP
name: http
selector:
app: quality-dashboard
@@ -0,0 +1,15 @@
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: dev
resources:
- ../../base
images:
- name: harbor.omniverseai.net/ljwx/quality-dashboard
newTag: "dev"
replicas:
- name: quality-dashboard
count: 1
-69
View File
@@ -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
-7
View File
@@ -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
-12
View File
@@ -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 -1
View File
@@ -6,7 +6,7 @@ metadata:
spec:
project: default
source:
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: cluster
destination:
+35
View File
@@ -0,0 +1,35 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: apps-bootstrap
namespace: argocd
# This finalizer ensures Application cascading delete
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
# Source: argocd-apps directory contains all Application manifests
source:
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: argocd-apps
# Only include Application resources, exclude this bootstrap file
directory:
recurse: false
include: '*-dev.yaml' # Only auto-create *-dev.yaml applications
# Destination: Create Applications in argocd namespace
destination:
server: https://kubernetes.default.svc
namespace: argocd
# Sync policy: Auto-create/delete Applications based on Git
syncPolicy:
automated:
prune: true # Auto-delete Applications removed from Git
selfHeal: true # Auto-fix manual changes
allowEmpty: false
syncOptions:
- CreateNamespace=false # argocd namespace already exists
+1 -1
View File
@@ -6,7 +6,7 @@ metadata:
spec:
project: default
source:
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: apps/nginx/overlays/dev
destination:
+20
View File
@@ -0,0 +1,20 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: gitea-runner
namespace: argocd
spec:
project: default
source:
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: infra/gitea-runner
destination:
server: https://kubernetes.default.svc
namespace: gitea-runner
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
+1 -1
View File
@@ -6,7 +6,7 @@ metadata:
spec:
project: default
source:
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: infra/arc
destination:
@@ -1,14 +1,14 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: youngth-guard-backend
name: backend-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: apps/youngth-guard/overlays/dev
path: apps/backend/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: dev
-60
View File
@@ -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 差异
-56
View File
@@ -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,17 +1,17 @@
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: infra-data
name: frontend-dev
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/BrunoGaoSZ/ljwx-deploy.git
repoURL: http://192.168.1.83:33000/gao/ljwx-deploy.git
targetRevision: main
path: infra/data
path: apps/frontend/overlays/dev
destination:
server: https://kubernetes.default.svc
namespace: infra
namespace: dev
syncPolicy:
automated:
prune: true
-34
View File
@@ -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
-34
View File
@@ -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

Some files were not shown because too many files have changed in this diff Show More