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:
@@ -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
@@ -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. 检查本文档的故障排查章节
|
||||
@@ -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/
|
||||
@@ -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"]
|
||||
@@ -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 ""
|
||||
@@ -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
|
||||
@@ -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;"]
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
Executable
+48
@@ -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"
|
||||
@@ -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
|
||||
@@ -0,0 +1,9 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: learn
|
||||
labels:
|
||||
name: learn
|
||||
env: production
|
||||
app: pager
|
||||
monitoring: enabled
|
||||
@@ -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
|
||||
@@ -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=
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user