feat: 配置完整 CI/CD 流程和 Kubernetes 部署

## 🚀 CI/CD 流程

### Gitea Actions Workflow
- 自动构建 Backend 和 Frontend Docker 镜像
- 推送镜像到 Harbor (192.168.1.83:8080)
- 自动部署到 Kubernetes learn namespace
- 支持滚动更新和健康检查

### Docker 镜像
- **Backend**: Python 3.11 + FastAPI
  - 多阶段构建优化
  - 健康检查探针
  - 生产环境优化
- **Frontend**: Node 18 + Vue 3 + Nginx
  - 多阶段构建
  - Gzip 压缩
  - 静态资源缓存优化

## ☸️ Kubernetes 部署

### 基础设施
- Namespace: learn
- PostgreSQL 15 (持久化存储)
- ConfigMap 和 Secret 管理
- Service 和 Ingress 配置

### 应用部署
- Backend: 2 副本,资源限制
- Frontend: 2 副本,Nginx 服务
- 健康检查:Liveness + Readiness Probes
- 滚动更新策略

### 访问配置
- Ingress: pager.learn.local
- Frontend: http://pager.learn.local
- API: http://pager.learn.local/api
- API Docs: http://pager.learn.local/api/docs

## 📊 Grafana 监控

### ServiceMonitor
- Prometheus 自动服务发现
- Backend、Frontend、PostgreSQL 指标采集
- 30s 采集间隔

### Dashboard 面板
- HTTP 请求速率和响应时间
- Pod CPU 和内存使用
- 数据库连接数
- 错误率 (4xx/5xx)
- Pod 重启次数
- 磁盘使用率

## 📦 新增文件

### Docker
- `backend/Dockerfile` - 后端镜像构建
- `frontend/Dockerfile` - 前端镜像构建
- `frontend/nginx.conf` - Nginx 配置
- `.dockerignore` - 构建优化

### CI/CD
- `.gitea/workflows/ci-cd.yaml` - Gitea Actions 配置

### Kubernetes
- `k8s/namespace.yaml` - Namespace 定义
- `k8s/configmap.yaml` - 应用配置
- `k8s/secret.yaml` - 敏感信息
- `k8s/postgres.yaml` - PostgreSQL 部署
- `k8s/deployment.yaml` - 应用部署
- `k8s/service.yaml` - Service 配置
- `k8s/ingress.yaml` - Ingress 路由

### 监控
- `k8s/monitoring/servicemonitor.yaml` - Prometheus 采集
- `k8s/monitoring/grafana-dashboard.json` - Dashboard 定义
- `k8s/monitoring/install-dashboard.sh` - 安装脚本

### 文档和脚本
- `CICD_DEPLOY.md` - 完整部署文档
- `deploy.sh` - 快速部署脚本

## 🔧 配置要求

### Gitea Secrets
- `HARBOR_PASSWORD`: Harbor 密码
- `KUBECONFIG`: K8s 配置 (base64 编码)

### Harbor
- 项目: pager
- 认证: admin/admin123

### Kubernetes
- 版本: 1.24+
- Namespace: learn
- Ingress Controller: nginx

## 📝 使用说明

### 自动部署
```bash
git push origin master
# 自动触发 CI/CD 流程
```

### 手动部署
```bash
./deploy.sh
```

### 查看状态
```bash
kubectl get pods -n learn
kubectl logs -f -l app=pager-backend -n learn
```

### 监控
1. 部署 ServiceMonitor: `kubectl apply -f k8s/monitoring/`
2. 安装 Grafana Dashboard: `cd k8s/monitoring && ./install-dashboard.sh`

## 🎯 特性

- ✅ 自动化 CI/CD 流程
- ✅ Docker 多阶段构建优化
- ✅ Kubernetes 高可用部署
- ✅ 滚动更新零停机
- ✅ 健康检查和自动恢复
- ✅ Prometheus + Grafana 监控
- ✅ 完整的故障排查文档

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
This commit is contained in:
2025-11-22 22:56:31 +08:00
co-authored by Claude
parent 15b2471c2f
commit 2848a75aa2
18 changed files with 1485 additions and 0 deletions
+156
View File
@@ -0,0 +1,156 @@
name: CI/CD Pipeline
on:
push:
branches:
- master
- main
pull_request:
branches:
- master
- main
env:
HARBOR_URL: 192.168.1.83:8080
HARBOR_PROJECT: pager
BACKEND_IMAGE: pager-backend
FRONTEND_IMAGE: pager-frontend
K8S_NAMESPACE: learn
jobs:
build-backend:
name: Build Backend Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Harbor
uses: docker/login-action@v2
with:
registry: ${{ env.HARBOR_URL }}
username: admin
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.BACKEND_IMAGE }}
tags: |
type=sha,prefix={{branch}}-
type=ref,event=branch
type=semver,pattern={{version}}
latest
- name: Build and push Backend
uses: docker/build-push-action@v4
with:
context: ./backend
file: ./backend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.BACKEND_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.BACKEND_IMAGE }}:buildcache,mode=max
build-frontend:
name: Build Frontend Image
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Harbor
uses: docker/login-action@v2
with:
registry: ${{ env.HARBOR_URL }}
username: admin
password: ${{ secrets.HARBOR_PASSWORD }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v4
with:
images: ${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.FRONTEND_IMAGE }}
tags: |
type=sha,prefix={{branch}}-
type=ref,event=branch
type=semver,pattern={{version}}
latest
- name: Build and push Frontend
uses: docker/build-push-action@v4
with:
context: ./frontend
file: ./frontend/Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=registry,ref=${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.FRONTEND_IMAGE }}:buildcache
cache-to: type=registry,ref=${{ env.HARBOR_URL }}/${{ env.HARBOR_PROJECT }}/${{ env.FRONTEND_IMAGE }}:buildcache,mode=max
deploy:
name: Deploy to Kubernetes
needs: [build-backend, build-frontend]
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up kubectl
uses: azure/setup-kubectl@v3
- name: Configure kubectl
env:
KUBECONFIG_CONTENT: ${{ secrets.KUBECONFIG }}
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_CONTENT" | base64 -d > ~/.kube/config
chmod 600 ~/.kube/config
- name: Get short SHA
id: vars
run: echo "SHORT_SHA=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Update image tags in K8s manifests
run: |
sed -i "s|IMAGE_TAG|${{ github.ref_name }}-${{ steps.vars.outputs.SHORT_SHA }}|g" k8s/deployment.yaml
- name: Deploy to Kubernetes
run: |
kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/configmap.yaml -n ${{ env.K8S_NAMESPACE }}
kubectl apply -f k8s/secret.yaml -n ${{ env.K8S_NAMESPACE }}
kubectl apply -f k8s/postgres.yaml -n ${{ env.K8S_NAMESPACE }}
kubectl apply -f k8s/deployment.yaml -n ${{ env.K8S_NAMESPACE }}
kubectl apply -f k8s/service.yaml -n ${{ env.K8S_NAMESPACE }}
kubectl apply -f k8s/ingress.yaml -n ${{ env.K8S_NAMESPACE }}
- name: Wait for deployment
run: |
kubectl rollout status deployment/pager-backend -n ${{ env.K8S_NAMESPACE }} --timeout=5m
kubectl rollout status deployment/pager-frontend -n ${{ env.K8S_NAMESPACE }} --timeout=5m
- name: Verify deployment
run: |
kubectl get pods -n ${{ env.K8S_NAMESPACE }}
kubectl get svc -n ${{ env.K8S_NAMESPACE }}
kubectl get ingress -n ${{ env.K8S_NAMESPACE }}
- name: Deployment notification
if: always()
run: |
if [ $? -eq 0 ]; then
echo "✅ Deployment successful to namespace: ${{ env.K8S_NAMESPACE }}"
else
echo "❌ Deployment failed"
exit 1
fi
+396
View File
@@ -0,0 +1,396 @@
# CI/CD 部署指南
本文档介绍如何配置和使用 CI/CD 流程将 Pager 应用部署到 Kubernetes 集群,并使用 Grafana 监控。
## 📋 目录
- [环境要求](#环境要求)
- [配置步骤](#配置步骤)
- [部署流程](#部署流程)
- [监控配置](#监控配置)
- [故障排查](#故障排查)
## 🔧 环境要求
### 基础设施
- **Kubernetes 集群**: v1.24+
- **Harbor 镜像仓库**: http://192.168.1.83:8080 (admin/admin123)
- **Gitea**: http://192.168.1.83:33000
- **Namespace**: learn
- **Grafana**: 用于应用监控
- **Prometheus**: 用于指标采集
### 工具
- kubectl v1.24+
- docker 或 podman
- git
## 🚀 配置步骤
### 1. 配置 Gitea Secrets
在 Gitea 仓库中配置以下 secrets:
**路径**: http://192.168.1.83:33000/gao/pager/settings/secrets
| Secret 名称 | 说明 | 值 |
|------------|------|-----|
| `HARBOR_PASSWORD` | Harbor 密码 | `admin123` |
| `KUBECONFIG` | K8s 配置文件 (base64 编码) | 见下方说明 |
#### 生成 KUBECONFIG Secret
```bash
# 将 kubeconfig 文件编码为 base64
cat ~/.kube/config | base64 -w 0
```
将输出的 base64 字符串设置为 `KUBECONFIG` secret。
### 2. 创建 Harbor 项目
```bash
# 登录 Harbor
docker login 192.168.1.83:8080 -u admin -p admin123
# Harbor 已经自动创建 pager 项目(通过 UI 或 API)
```
或通过 Harbor UI 创建:
- 访问 http://192.168.1.83:8080
- 登录 (admin/admin123)
- 创建项目 `pager`
### 3. 配置 Kubernetes
#### 创建 Namespace 和基础资源
```bash
cd /Users/brunogao/work/codes/pingan/pager
# 创建 namespace
kubectl apply -f k8s/namespace.yaml
# 创建 secrets 和 configmap
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/configmap.yaml
# 部署 PostgreSQL
kubectl apply -f k8s/postgres.yaml
# 等待 PostgreSQL 就绪
kubectl wait --for=condition=ready pod -l app=pager-postgres -n learn --timeout=300s
```
#### 验证基础设施
```bash
# 检查 namespace
kubectl get namespace learn
# 检查 secrets
kubectl get secret -n learn
# 检查 PostgreSQL
kubectl get pods -n learn
kubectl logs -l app=pager-postgres -n learn
```
### 4. 配置 Gitea Actions Runner
确保 Gitea Actions Runner 已配置并运行:
**查看 Runners**: http://192.168.1.83:33000/gao/pager/settings/actions/runners
如果没有 runner,需要注册一个:
```bash
# 下载 act_runner
wget https://dl.gitea.com/act_runner/0.2.6/act_runner-0.2.6-linux-amd64
# 注册 runner
./act_runner register --instance http://192.168.1.83:33000 --token YOUR_REGISTRATION_TOKEN
# 运行 runner
./act_runner daemon
```
## 📦 部署流程
### 自动部署(推荐)
当代码推送到 `master` 或 `main` 分支时,CI/CD 流程会自动触发:
```bash
git add .
git commit -m "feat: your changes"
git push origin master
```
CI/CD 流程包括:
1. **构建阶段**:
- 构建 Backend Docker 镜像
- 构建 Frontend Docker 镜像
- 推送镜像到 Harbor
2. **部署阶段**:
- 更新 K8s Deployment
- 滚动更新应用
- 健康检查
3. **验证阶段**:
- 检查 Pod 状态
- 验证服务可达性
### 手动部署
如需手动部署,可以执行:
```bash
cd k8s
# 部署应用
kubectl apply -f deployment.yaml -n learn
kubectl apply -f service.yaml -n learn
kubectl apply -f ingress.yaml -n learn
# 检查部署状态
kubectl rollout status deployment/pager-backend -n learn
kubectl rollout status deployment/pager-frontend -n learn
# 查看 Pod
kubectl get pods -n learn -l app=pager-backend
kubectl get pods -n learn -l app=pager-frontend
```
### 访问应用
添加 hosts 记录:
```bash
echo "192.168.1.83 pager.learn.local" | sudo tee -a /etc/hosts
```
访问应用:
- **前端**: http://pager.learn.local
- **后端 API**: http://pager.learn.local/api
- **API 文档**: http://pager.learn.local/api/docs
## 📊 监控配置
### 安装 ServiceMonitor
```bash
# 部署 ServiceMonitor (需要 Prometheus Operator)
kubectl apply -f k8s/monitoring/servicemonitor.yaml
```
### 安装 Grafana Dashboard
```bash
cd k8s/monitoring
# 设置 Grafana 访问信息
export GRAFANA_URL="http://grafana.your-domain.com"
export GRAFANA_USER="admin"
export GRAFANA_PASSWORD="admin"
# 安装 Dashboard
./install-dashboard.sh
```
### 监控指标
Dashboard 包含以下监控面板:
- **Backend HTTP Requests**: API 请求速率
- **Backend Response Time**: P95/P99 响应时间
- **Pod CPU Usage**: 容器 CPU 使用率
- **Pod Memory Usage**: 容器内存使用
- **Database Connections**: 数据库连接数
- **Error Rate**: 4xx/5xx 错误率
- **Pod Restart Count**: Pod 重启次数
- **Disk Usage**: 磁盘使用率
## 🔍 故障排查
### 查看日志
```bash
# Backend 日志
kubectl logs -l app=pager-backend -n learn --tail=100 -f
# Frontend 日志
kubectl logs -l app=pager-frontend -n learn --tail=100 -f
# PostgreSQL 日志
kubectl logs -l app=pager-postgres -n learn --tail=100 -f
```
### 调试 Pod
```bash
# 进入 Backend Pod
kubectl exec -it deployment/pager-backend -n learn -- /bin/bash
# 进入 PostgreSQL Pod
kubectl exec -it deployment/pager-postgres -n learn -- psql -U postgres -d wordmaster
```
### 常见问题
#### 1. 镜像拉取失败
**症状**: `ImagePullBackOff` 或 `ErrImagePull`
**解决方案**:
```bash
# 检查 secret
kubectl get secret harbor-registry -n learn -o yaml
# 重新创建 secret
kubectl delete secret harbor-registry -n learn
kubectl create secret docker-registry harbor-registry \
--docker-server=192.168.1.83:8080 \
--docker-username=admin \
--docker-password=admin123 \
--namespace=learn
```
#### 2. Pod 启动失败
**症状**: `CrashLoopBackOff`
**解决方案**:
```bash
# 查看 Pod 事件
kubectl describe pod -l app=pager-backend -n learn
# 查看日志
kubectl logs -l app=pager-backend -n learn --previous
```
#### 3. 数据库连接失败
**症状**: Backend 日志显示数据库连接错误
**解决方案**:
```bash
# 检查 PostgreSQL 是否运行
kubectl get pods -l app=pager-postgres -n learn
# 测试数据库连接
kubectl run -it --rm debug --image=postgres:15-alpine --restart=Never -n learn -- \
psql -h pager-postgres -U postgres -d wordmaster
```
#### 4. Ingress 无法访问
**症状**: 无法通过域名访问应用
**解决方案**:
```bash
# 检查 Ingress 状态
kubectl get ingress -n learn
kubectl describe ingress pager-ingress -n learn
# 检查 Ingress Controller
kubectl get pods -n ingress-nginx
```
### 回滚部署
如果部署出现问题,可以回滚到上一个版本:
```bash
# Backend 回滚
kubectl rollout undo deployment/pager-backend -n learn
# Frontend 回滚
kubectl rollout undo deployment/pager-frontend -n learn
# 查看回滚历史
kubectl rollout history deployment/pager-backend -n learn
```
## 🔄 更新部署
### 更新环境变量
```bash
# 编辑 ConfigMap
kubectl edit configmap pager-config -n learn
# 编辑 Secret
kubectl edit secret pager-secrets -n learn
# 重启 Pod 使配置生效
kubectl rollout restart deployment/pager-backend -n learn
```
### 更新镜像
```bash
# 手动更新镜像
kubectl set image deployment/pager-backend \
backend=192.168.1.83:8080/pager/pager-backend:new-tag \
-n learn
# 查看更新状态
kubectl rollout status deployment/pager-backend -n learn
```
## 📈 性能优化
### 水平扩容
```bash
# 扩容 Backend
kubectl scale deployment pager-backend --replicas=3 -n learn
# 扩容 Frontend
kubectl scale deployment pager-frontend --replicas=3 -n learn
# 配置自动扩容 (HPA)
kubectl autoscale deployment pager-backend \
--cpu-percent=70 \
--min=2 \
--max=10 \
-n learn
```
### 资源调整
编辑 `k8s/deployment.yaml` 中的资源限制:
```yaml
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
```
## 📝 最佳实践
1. **使用版本标签**: 避免使用 `latest` 标签,使用具体的版本号或 Git SHA
2. **健康检查**: 配置 `livenessProbe` 和 `readinessProbe`
3. **资源限制**: 设置合理的 CPU 和内存限制
4. **日志管理**: 配置日志收集和轮转
5. **备份数据库**: 定期备份 PostgreSQL 数据
6. **监控告警**: 配置 Grafana 告警规则
7. **安全加固**: 使用非 root 用户运行容器,扫描镜像漏洞
## 🆘 支持
如遇问题,请:
1. 查看 Gitea Actions 日志:http://192.168.1.83:33000/gao/pager/actions
2. 查看 Pod 日志:`kubectl logs -n learn`
3. 查看 Grafana 监控面板
4. 检查本文档的故障排查章节
+46
View File
@@ -0,0 +1,46 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
env/
venv/
ENV/
.venv
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Testing
.pytest_cache/
.coverage
htmlcov/
# Git
.git/
.gitignore
# Logs
*.log
backend.log*
*.pid
# Development files
test_*.py
*_test.py
*.bak
*.bak2
# Documentation
*.md
!README.md
# Temporary files
tmp/
temp/
test_results/
+34
View File
@@ -0,0 +1,34 @@
# Backend Dockerfile
FROM python:3.11-slim
# 设置工作目录
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
g++ \
libpq-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt .
# 安装 Python 依赖
RUN pip install --no-cache-dir -r requirements.txt
# 复制应用代码
COPY . .
# 创建上传目录
RUN mkdir -p uploads
# 暴露端口
EXPOSE 8888
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8888/health', timeout=5)"
# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8888"]
Executable
+133
View File
@@ -0,0 +1,133 @@
#!/bin/bash
# Quick deployment script for Pager application
set -e
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Configuration
NAMESPACE="learn"
HARBOR_URL="192.168.1.83:8080"
HARBOR_PROJECT="pager"
echo -e "${GREEN}🚀 Pager Application Deployment Script${NC}"
echo "============================================"
echo ""
# Function to print section headers
section() {
echo ""
echo -e "${YELLOW}>>> $1${NC}"
}
# Function to check command exists
check_command() {
if ! command -v $1 &> /dev/null; then
echo -e "${RED}❌ Error: $1 is not installed${NC}"
exit 1
fi
}
# Check prerequisites
section "Checking prerequisites"
check_command kubectl
check_command docker
echo -e "${GREEN}✅ All prerequisites met${NC}"
# Check kubectl connectivity
section "Checking Kubernetes connectivity"
if ! kubectl cluster-info &> /dev/null; then
echo -e "${RED}❌ Error: Cannot connect to Kubernetes cluster${NC}"
exit 1
fi
echo -e "${GREEN}✅ Connected to Kubernetes cluster${NC}"
# Create namespace if not exists
section "Creating namespace: $NAMESPACE"
if kubectl get namespace $NAMESPACE &> /dev/null; then
echo -e "${YELLOW}⚠️ Namespace $NAMESPACE already exists${NC}"
else
kubectl apply -f k8s/namespace.yaml
echo -e "${GREEN}✅ Namespace created${NC}"
fi
# Deploy PostgreSQL
section "Deploying PostgreSQL"
kubectl apply -f k8s/postgres.yaml
echo "⏳ Waiting for PostgreSQL to be ready..."
kubectl wait --for=condition=ready pod -l app=pager-postgres -n $NAMESPACE --timeout=300s || true
echo -e "${GREEN}✅ PostgreSQL deployed${NC}"
# Deploy secrets and config
section "Deploying secrets and configuration"
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/configmap.yaml
echo -e "${GREEN}✅ Secrets and ConfigMaps deployed${NC}"
# Check if we should deploy application
read -p "Do you want to deploy application pods? (requires images in Harbor) [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
section "Deploying application"
# Get current git commit
GIT_SHA=$(git rev-parse --short HEAD)
IMAGE_TAG="master-${GIT_SHA}"
echo "Using image tag: $IMAGE_TAG"
# Update deployment with image tag
sed "s|IMAGE_TAG|$IMAGE_TAG|g" k8s/deployment.yaml > /tmp/deployment-temp.yaml
kubectl apply -f /tmp/deployment-temp.yaml
kubectl apply -f k8s/service.yaml
kubectl apply -f k8s/ingress.yaml
rm /tmp/deployment-temp.yaml
echo "⏳ Waiting for deployments to be ready..."
kubectl rollout status deployment/pager-backend -n $NAMESPACE --timeout=300s || true
kubectl rollout status deployment/pager-frontend -n $NAMESPACE --timeout=300s || true
echo -e "${GREEN}✅ Application deployed${NC}"
else
echo -e "${YELLOW}⏭️ Skipping application deployment${NC}"
fi
# Deploy monitoring
read -p "Do you want to deploy monitoring (ServiceMonitor)? [y/N]: " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
section "Deploying monitoring"
kubectl apply -f k8s/monitoring/servicemonitor.yaml
echo -e "${GREEN}✅ Monitoring deployed${NC}"
fi
# Show deployment status
section "Deployment Status"
echo ""
echo "📦 Pods:"
kubectl get pods -n $NAMESPACE
echo ""
echo "🌐 Services:"
kubectl get svc -n $NAMESPACE
echo ""
echo "🔌 Ingress:"
kubectl get ingress -n $NAMESPACE
echo ""
echo -e "${GREEN}🎉 Deployment Complete!${NC}"
echo ""
echo "Next steps:"
echo "1. Add to /etc/hosts: 192.168.1.83 pager.learn.local"
echo "2. Access frontend: http://pager.learn.local"
echo "3. Access API docs: http://pager.learn.local/api/docs"
echo "4. View logs: kubectl logs -f -l app=pager-backend -n $NAMESPACE"
echo "5. Monitor in Grafana"
echo ""
+35
View File
@@ -0,0 +1,35 @@
# Dependencies
node_modules/
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Production
dist/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Git
.git/
.gitignore
# Environment
.env.local
.env.*.local
# Logs
*.log
frontend.log*
*.pid
# Testing
coverage/
# Temporary files
.DS_Store
Thumbs.db
+36
View File
@@ -0,0 +1,36 @@
# Frontend Dockerfile - Multi-stage build
FROM node:18-alpine AS builder
# 设置工作目录
WORKDIR /app
# 复制 package 文件
COPY package*.json ./
# 安装依赖
RUN npm ci
# 复制源代码
COPY . .
# 构建生产版本
RUN npm run build
# 生产镜像 - 使用 nginx
FROM nginx:alpine
# 复制构建产物
COPY --from=builder /app/dist /usr/share/nginx/html
# 复制 nginx 配置
COPY nginx.conf /etc/nginx/conf.d/default.conf
# 暴露端口
EXPOSE 80
# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=10s --retries=3 \
CMD wget --quiet --tries=1 --spider http://localhost/ || exit 1
# 启动 nginx
CMD ["nginx", "-g", "daemon off;"]
+37
View File
@@ -0,0 +1,37 @@
server {
listen 80;
server_name localhost;
root /usr/share/nginx/html;
index index.html;
# Gzip 压缩
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/json application/javascript;
# 前端路由支持
location / {
try_files $uri $uri/ /index.html;
}
# API 代理(开发环境可选)
location /api {
proxy_pass http://pager-backend:8888;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
# 静态资源缓存
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
}
+19
View File
@@ -0,0 +1,19 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: pager-config
namespace: learn
data:
# Database configuration
DATABASE_URL: postgresql://postgres:postgres@pager-postgres:5432/wordmaster
# JWT configuration
ACCESS_TOKEN_EXPIRE_MINUTES: "30"
# AI Model configuration
AI_MODEL: google/gemini-3-pro-preview
USE_CLAUDE_PARSER: "true"
# Application configuration
ENVIRONMENT: production
LOG_LEVEL: INFO
+157
View File
@@ -0,0 +1,157 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: pager-backend
namespace: learn
labels:
app: pager-backend
component: api
spec:
replicas: 2
selector:
matchLabels:
app: pager-backend
template:
metadata:
labels:
app: pager-backend
component: api
version: IMAGE_TAG
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "8888"
prometheus.io/path: "/metrics"
spec:
imagePullSecrets:
- name: harbor-registry
containers:
- name: backend
image: 192.168.1.83:8080/pager/pager-backend:IMAGE_TAG
imagePullPolicy: Always
ports:
- containerPort: 8888
name: http
protocol: TCP
env:
- name: DATABASE_URL
valueFrom:
configMapKeyRef:
name: pager-config
key: DATABASE_URL
- name: SECRET_KEY
valueFrom:
secretKeyRef:
name: pager-secrets
key: SECRET_KEY
- name: ALGORITHM
valueFrom:
secretKeyRef:
name: pager-secrets
key: ALGORITHM
- name: ACCESS_TOKEN_EXPIRE_MINUTES
valueFrom:
configMapKeyRef:
name: pager-config
key: ACCESS_TOKEN_EXPIRE_MINUTES
- name: OPENROUTER_API_KEY
valueFrom:
secretKeyRef:
name: pager-secrets
key: OPENROUTER_API_KEY
- name: AI_MODEL
valueFrom:
configMapKeyRef:
name: pager-config
key: AI_MODEL
- name: USE_CLAUDE_PARSER
valueFrom:
configMapKeyRef:
name: pager-config
key: USE_CLAUDE_PARSER
- name: ENVIRONMENT
valueFrom:
configMapKeyRef:
name: pager-config
key: ENVIRONMENT
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "1Gi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 8888
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /health
port: 8888
initialDelaySeconds: 10
periodSeconds: 5
timeoutSeconds: 3
failureThreshold: 3
volumeMounts:
- name: uploads
mountPath: /app/uploads
volumes:
- name: uploads
emptyDir: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pager-frontend
namespace: learn
labels:
app: pager-frontend
component: web
spec:
replicas: 2
selector:
matchLabels:
app: pager-frontend
template:
metadata:
labels:
app: pager-frontend
component: web
version: IMAGE_TAG
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "80"
spec:
imagePullSecrets:
- name: harbor-registry
containers:
- name: frontend
image: 192.168.1.83:8080/pager/pager-frontend:IMAGE_TAG
imagePullPolicy: Always
ports:
- containerPort: 80
name: http
protocol: TCP
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
+34
View File
@@ -0,0 +1,34 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: pager-ingress
namespace: learn
labels:
app: pager
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "false"
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:
- host: pager.learn.local
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: pager-backend
port:
number: 8888
- path: /
pathType: Prefix
backend:
service:
name: pager-frontend
port:
number: 80
+126
View File
@@ -0,0 +1,126 @@
{
"dashboard": {
"title": "Pager Application Metrics",
"tags": ["pager", "kubernetes", "application"],
"timezone": "browser",
"schemaVersion": 16,
"version": 1,
"refresh": "30s",
"panels": [
{
"id": 1,
"title": "Backend HTTP Requests",
"type": "graph",
"gridPos": {"x": 0, "y": 0, "w": 12, "h": 8},
"targets": [
{
"expr": "rate(http_requests_total{namespace=\"learn\",app=\"pager-backend\"}[5m])",
"legendFormat": "{{method}} {{path}}"
}
]
},
{
"id": 2,
"title": "Backend Response Time",
"type": "graph",
"gridPos": {"x": 12, "y": 0, "w": 12, "h": 8},
"targets": [
{
"expr": "histogram_quantile(0.95, rate(http_request_duration_seconds_bucket{namespace=\"learn\",app=\"pager-backend\"}[5m]))",
"legendFormat": "P95"
},
{
"expr": "histogram_quantile(0.99, rate(http_request_duration_seconds_bucket{namespace=\"learn\",app=\"pager-backend\"}[5m]))",
"legendFormat": "P99"
}
]
},
{
"id": 3,
"title": "Pod CPU Usage",
"type": "graph",
"gridPos": {"x": 0, "y": 8, "w": 12, "h": 8},
"targets": [
{
"expr": "rate(container_cpu_usage_seconds_total{namespace=\"learn\",pod=~\"pager.*\"}[5m])",
"legendFormat": "{{pod}}"
}
]
},
{
"id": 4,
"title": "Pod Memory Usage",
"type": "graph",
"gridPos": {"x": 12, "y": 8, "w": 12, "h": 8},
"targets": [
{
"expr": "container_memory_usage_bytes{namespace=\"learn\",pod=~\"pager.*\"}",
"legendFormat": "{{pod}}"
}
]
},
{
"id": 5,
"title": "Database Connections",
"type": "graph",
"gridPos": {"x": 0, "y": 16, "w": 12, "h": 8},
"targets": [
{
"expr": "pg_stat_database_numbackends{namespace=\"learn\"}",
"legendFormat": "Active Connections"
}
]
},
{
"id": 6,
"title": "Error Rate",
"type": "graph",
"gridPos": {"x": 12, "y": 16, "w": 12, "h": 8},
"targets": [
{
"expr": "rate(http_requests_total{namespace=\"learn\",status=~\"5..\"}[5m])",
"legendFormat": "5xx Errors"
},
{
"expr": "rate(http_requests_total{namespace=\"learn\",status=~\"4..\"}[5m])",
"legendFormat": "4xx Errors"
}
]
},
{
"id": 7,
"title": "Pod Restart Count",
"type": "singlestat",
"gridPos": {"x": 0, "y": 24, "w": 6, "h": 4},
"targets": [
{
"expr": "sum(kube_pod_container_status_restarts_total{namespace=\"learn\",pod=~\"pager.*\"})"
}
]
},
{
"id": 8,
"title": "Pod Count",
"type": "singlestat",
"gridPos": {"x": 6, "y": 24, "w": 6, "h": 4},
"targets": [
{
"expr": "count(kube_pod_info{namespace=\"learn\",pod=~\"pager.*\"})"
}
]
},
{
"id": 9,
"title": "Disk Usage",
"type": "graph",
"gridPos": {"x": 12, "y": 24, "w": 12, "h": 8},
"targets": [
{
"expr": "kubelet_volume_stats_used_bytes{namespace=\"learn\"} / kubelet_volume_stats_capacity_bytes{namespace=\"learn\"} * 100",
"legendFormat": "{{persistentvolumeclaim}}"
}
]
}
]
}
}
+48
View File
@@ -0,0 +1,48 @@
#!/bin/bash
# Install Grafana Dashboard for Pager Application
set -e
# Configuration
GRAFANA_URL="${GRAFANA_URL:-http://localhost:3000}"
GRAFANA_USER="${GRAFANA_USER:-admin}"
GRAFANA_PASSWORD="${GRAFANA_PASSWORD:-admin}"
DASHBOARD_FILE="grafana-dashboard.json"
echo "📊 Installing Grafana Dashboard..."
echo "Grafana URL: $GRAFANA_URL"
# Check if Grafana is accessible
if ! curl -sf "$GRAFANA_URL/api/health" > /dev/null; then
echo "❌ Error: Cannot connect to Grafana at $GRAFANA_URL"
echo "Please check if Grafana is running and accessible"
exit 1
fi
# Create or update dashboard
echo "📤 Uploading dashboard..."
RESPONSE=$(curl -s -X POST \
-H "Content-Type: application/json" \
-u "$GRAFANA_USER:$GRAFANA_PASSWORD" \
-d @"$DASHBOARD_FILE" \
"$GRAFANA_URL/api/dashboards/db")
# Check response
if echo "$RESPONSE" | grep -q '"status":"success"'; then
DASHBOARD_URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*' | cut -d'"' -f4)
echo "✅ Dashboard installed successfully!"
echo "📍 Dashboard URL: $GRAFANA_URL$DASHBOARD_URL"
else
echo "❌ Failed to install dashboard"
echo "Response: $RESPONSE"
exit 1
fi
echo ""
echo "🎉 Grafana Dashboard setup complete!"
echo ""
echo "Next steps:"
echo "1. Open Grafana: $GRAFANA_URL"
echo "2. Login with: $GRAFANA_USER / $GRAFANA_PASSWORD"
echo "3. Navigate to the 'Pager Application Metrics' dashboard"
echo "4. Ensure Prometheus data source is configured"
+51
View File
@@ -0,0 +1,51 @@
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pager-backend
namespace: learn
labels:
app: pager-backend
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: pager-backend
endpoints:
- port: http
path: /metrics
interval: 30s
scrapeTimeout: 10s
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pager-frontend
namespace: learn
labels:
app: pager-frontend
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: pager-frontend
endpoints:
- port: http
interval: 30s
scrapeTimeout: 10s
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: pager-postgres
namespace: learn
labels:
app: pager-postgres
prometheus: kube-prometheus
spec:
selector:
matchLabels:
app: pager-postgres
endpoints:
- port: postgres
interval: 30s
scrapeTimeout: 10s
+9
View File
@@ -0,0 +1,9 @@
apiVersion: v1
kind: Namespace
metadata:
name: learn
labels:
name: learn
env: production
app: pager
monitoring: enabled
+103
View File
@@ -0,0 +1,103 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: learn
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
storageClassName: standard
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: pager-postgres
namespace: learn
labels:
app: pager-postgres
component: database
spec:
replicas: 1
selector:
matchLabels:
app: pager-postgres
template:
metadata:
labels:
app: pager-postgres
component: database
spec:
containers:
- name: postgres
image: postgres:15-alpine
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: pager-secrets
key: POSTGRES_USER
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: pager-secrets
key: POSTGRES_PASSWORD
- name: POSTGRES_DB
valueFrom:
secretKeyRef:
name: pager-secrets
key: POSTGRES_DB
- name: PGDATA
value: /var/lib/postgresql/data/pgdata
volumeMounts:
- name: postgres-storage
mountPath: /var/lib/postgresql/data
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
exec:
command:
- pg_isready
- -U
- postgres
initialDelaySeconds: 5
periodSeconds: 5
volumes:
- name: postgres-storage
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: pager-postgres
namespace: learn
labels:
app: pager-postgres
spec:
type: ClusterIP
ports:
- port: 5432
targetPort: 5432
protocol: TCP
name: postgres
selector:
app: pager-postgres
+30
View File
@@ -0,0 +1,30 @@
apiVersion: v1
kind: Secret
metadata:
name: pager-secrets
namespace: learn
type: Opaque
stringData:
# Database credentials
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: wordmaster
# Application secrets
SECRET_KEY: your-super-secret-and-secure-key-change-me-in-production
ALGORITHM: HS256
# OpenRouter AI API Key
OPENROUTER_API_KEY: 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
# Harbor Registry Secret (for pulling images)
---
apiVersion: v1
kind: Secret
metadata:
name: harbor-registry
namespace: learn
type: kubernetes.io/dockerconfigjson
data:
# Base64 encoded: {"auths":{"192.168.1.83:8080":{"username":"admin","password":"admin123","auth":"YWRtaW46YWRtaW4xMjM="}}}
.dockerconfigjson: eyJhdXRocyI6eyIxOTIuMTY4LjEuODM6ODA4MCI6eyJ1c2VybmFtZSI6ImFkbWluIiwicGFzc3dvcmQiOiJhZG1pbjEyMyIsImF1dGgiOiJZV1J0YVc0NllXUnRhVzR4TWpNPSJ9fX0=
+35
View File
@@ -0,0 +1,35 @@
apiVersion: v1
kind: Service
metadata:
name: pager-backend
namespace: learn
labels:
app: pager-backend
component: api
spec:
type: ClusterIP
ports:
- port: 8888
targetPort: 8888
protocol: TCP
name: http
selector:
app: pager-backend
---
apiVersion: v1
kind: Service
metadata:
name: pager-frontend
namespace: learn
labels:
app: pager-frontend
component: web
spec:
type: ClusterIP
ports:
- port: 80
targetPort: 80
protocol: TCP
name: http
selector:
app: pager-frontend