This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Namespace — 所有资源统一放在 resource-library 命名空间
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
@@ -0,0 +1,27 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Secret — 敏感凭证
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 部署前请务必修改下面所有占位密码!
|
||||
# 推荐用 kubectl 在集群内生成(避免把明文提交到 git):
|
||||
# kubectl -n resource-library create secret generic resource-library-secret \
|
||||
# --from-literal=MYSQL_ROOT_PASSWORD='...' \
|
||||
# --from-literal=MYSQL_PASSWORD='...' \
|
||||
# --from-literal=SECRET_KEY="$(python -c 'import secrets;print(secrets.token_hex(32))')" \
|
||||
# --from-literal=ADMIN_PASSWORD='...'
|
||||
# 或使用 sealed-secrets / external-secrets 管理。
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: resource-library-secret
|
||||
namespace: resource-library
|
||||
type: Opaque
|
||||
stringData:
|
||||
# MySQL root 密码(仅 init 容器和 DBA 使用)
|
||||
MYSQL_ROOT_PASSWORD: "CHANGE_ME_root_password"
|
||||
# 应用使用的业务账号密码
|
||||
MYSQL_PASSWORD: "CHANGE_ME_app_password"
|
||||
# Flask SECRET_KEY(用于 session/签名),至少 32 字节随机
|
||||
SECRET_KEY: "CHANGE_ME_please_run_python_secrets_token_hex_32"
|
||||
# 首次启动自动创建的管理员密码
|
||||
ADMIN_PASSWORD: "CHANGE_ME_Admin@123456"
|
||||
@@ -0,0 +1,31 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# ConfigMap — 非敏感环境变量
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: resource-library-config
|
||||
namespace: resource-library
|
||||
data:
|
||||
# MySQL
|
||||
MYSQL_DATABASE: "resource_library"
|
||||
MYSQL_USER: "resource_library"
|
||||
|
||||
# Flask
|
||||
FLASK_ENV: "production"
|
||||
LOG_LEVEL: "info"
|
||||
|
||||
# Gunicorn
|
||||
GUNICORN_WORKERS: "4"
|
||||
GUNICORN_TIMEOUT: "120"
|
||||
GUNICORN_BIND: "0.0.0.0:5000"
|
||||
|
||||
# 上传限制
|
||||
MAX_UPLOAD_SIZE_MB: "500"
|
||||
|
||||
# 管理员账号(首次启动时由 init_db.py 创建)
|
||||
ADMIN_USERNAME: "admin"
|
||||
ADMIN_EMAIL: "admin@example.com"
|
||||
|
||||
# 数据库等待超时(秒)
|
||||
DB_WAIT_SECONDS: "120"
|
||||
@@ -0,0 +1,119 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# MySQL 8.0 — StatefulSet + Headless Service
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# - 使用 StatefulSet 保证 Pod 名和存储稳定(mysql-0)
|
||||
# - Headless Service 提供稳定 DNS:mysql.resource-library.svc.cluster.local
|
||||
# - 数据持久化到 PVC(20Gi,按需修改)
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: mysql
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
clusterIP: None # Headless
|
||||
selector:
|
||||
app.kubernetes.io/name: mysql
|
||||
ports:
|
||||
- name: mysql
|
||||
port: 3306
|
||||
targetPort: 3306
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: mysql
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: mysql
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
serviceName: mysql
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: mysql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: mysql
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 999 # mysql 用户组
|
||||
containers:
|
||||
- name: mysql
|
||||
image: mysql:8.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
args:
|
||||
- "--character-set-server=utf8mb4"
|
||||
- "--collation-server=utf8mb4_unicode_ci"
|
||||
- "--default-authentication-plugin=mysql_native_password"
|
||||
- "--innodb-buffer-pool-size=256M"
|
||||
ports:
|
||||
- name: mysql
|
||||
containerPort: 3306
|
||||
env:
|
||||
- name: MYSQL_ROOT_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: resource-library-secret
|
||||
key: MYSQL_ROOT_PASSWORD
|
||||
- name: MYSQL_DATABASE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: resource-library-config
|
||||
key: MYSQL_DATABASE
|
||||
- name: MYSQL_USER
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: resource-library-config
|
||||
key: MYSQL_USER
|
||||
- name: MYSQL_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: resource-library-secret
|
||||
key: MYSQL_PASSWORD
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/mysql
|
||||
resources:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- mysqladmin ping -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}"
|
||||
initialDelaySeconds: 60
|
||||
periodSeconds: 15
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 5
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- mysqladmin ping -h 127.0.0.1 -uroot -p"${MYSQL_ROOT_PASSWORD}"
|
||||
initialDelaySeconds: 15
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 6
|
||||
volumeClaimTemplates:
|
||||
- metadata:
|
||||
name: data
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
# storageClassName: "standard" # 取消注释并改为你集群实际的 StorageClass
|
||||
resources:
|
||||
requests:
|
||||
storage: 20Gi
|
||||
@@ -0,0 +1,23 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# PVC — 上传文件存储
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 注意:
|
||||
# - 若 App Deployment replicas > 1,accessModes 必须是 ReadWriteMany
|
||||
# (需要 NFS / CephFS / cloud-file 等支持 RWX 的 StorageClass)
|
||||
# - 单副本场景下 ReadWriteOnce 即可
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: resource-library-uploads
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce # 多副本改为 ReadWriteMany
|
||||
# storageClassName: "standard" # 按集群情况填写
|
||||
resources:
|
||||
requests:
|
||||
storage: 50Gi
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Flask 应用 — Deployment + ClusterIP Service
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# - 镜像请替换为你实际推送到镜像仓库的 tag
|
||||
# - 健康检查路径 /auth/login 复用 Dockerfile 中的 HEALTHCHECK
|
||||
# - 单副本;多副本需要 RWX PVC,并把 init_db 逻辑迁移到 Job
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: resource-library-app
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: 5000
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: resource-library-app
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
replicas: 1
|
||||
strategy:
|
||||
type: Recreate # 单副本 + RWO PVC 时避免滚动卡住
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
spec:
|
||||
securityContext:
|
||||
fsGroup: 1000
|
||||
containers:
|
||||
- name: app
|
||||
# TODO: 替换为实际仓库地址,例如 ghcr.io/you/resource-library:1.0.0
|
||||
image: resource-library:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5000
|
||||
env:
|
||||
# 数据库连接串由多个片段拼接(使用 MySQL Service 的 DNS 名)
|
||||
- name: DATABASE_URL
|
||||
value: "mysql+pymysql://$(MYSQL_USER):$(MYSQL_PASSWORD)@mysql.resource-library.svc.cluster.local:3306/$(MYSQL_DATABASE)"
|
||||
envFrom:
|
||||
- configMapRef:
|
||||
name: resource-library-config
|
||||
- secretRef:
|
||||
name: resource-library-secret
|
||||
volumeMounts:
|
||||
- name: uploads
|
||||
mountPath: /app/app/static/uploads
|
||||
resources:
|
||||
requests:
|
||||
cpu: "200m"
|
||||
memory: "512Mi"
|
||||
limits:
|
||||
cpu: "2"
|
||||
memory: "2Gi"
|
||||
startupProbe:
|
||||
# entrypoint 先等 DB、再 init_db,首次启动可能较久
|
||||
httpGet:
|
||||
path: /auth/login
|
||||
port: 5000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 5
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 60 # 最多 5min
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /auth/login
|
||||
port: 5000
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /auth/login
|
||||
port: 5000
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 5
|
||||
failureThreshold: 3
|
||||
volumes:
|
||||
- name: uploads
|
||||
persistentVolumeClaim:
|
||||
claimName: resource-library-uploads
|
||||
@@ -0,0 +1,39 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Ingress — 外部访问入口
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 需要集群已安装 Ingress 控制器(nginx-ingress / traefik 等)。
|
||||
# 若使用 cert-manager 自动签发 TLS,取消相关注解与 tls 段。
|
||||
# 需支持大文件上传,因此显式放宽 proxy body size / 超时。
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: resource-library
|
||||
namespace: resource-library
|
||||
labels:
|
||||
app.kubernetes.io/name: resource-library-app
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
annotations:
|
||||
# nginx-ingress
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "512m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
|
||||
# cert-manager(可选):自动申请 Let's Encrypt 证书
|
||||
# cert-manager.io/cluster-issuer: "letsencrypt-prod"
|
||||
spec:
|
||||
ingressClassName: nginx # 按集群实际 IngressClass 调整
|
||||
tls:
|
||||
- hosts:
|
||||
- prl.example.com # TODO: 改成你的域名
|
||||
secretName: resource-library-tls
|
||||
rules:
|
||||
- host: prl.example.com # TODO: 改成你的域名
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: resource-library-app
|
||||
port:
|
||||
number: 80
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
# Kubernetes 部署说明
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
k8s/
|
||||
├── 00-namespace.yaml # 命名空间
|
||||
├── 01-secret.yaml # 敏感凭证(部署前必改)
|
||||
├── 02-configmap.yaml # 非敏感配置
|
||||
├── 10-mysql.yaml # MySQL StatefulSet + Headless Service
|
||||
├── 20-app-pvc.yaml # 上传文件 PVC
|
||||
├── 21-app.yaml # Flask Deployment + Service
|
||||
├── 30-ingress.yaml # 外部访问入口
|
||||
└── kustomization.yaml # Kustomize 汇总
|
||||
```
|
||||
|
||||
## 部署前准备
|
||||
|
||||
1. **构建并推送镜像**
|
||||
|
||||
```bash
|
||||
docker build -t ghcr.io/<org>/resource-library:1.0.0 .
|
||||
docker push ghcr.io/<org>/resource-library:1.0.0
|
||||
```
|
||||
|
||||
然后修改 `21-app.yaml` 中的 `image:` 字段,或在 `kustomization.yaml` 里用
|
||||
`images:` 字段覆盖 tag。
|
||||
|
||||
2. **修改 Secret**
|
||||
|
||||
编辑 `01-secret.yaml`,把所有 `CHANGE_ME_*` 替换成真实强密码。
|
||||
推荐直接用 `kubectl` 创建,避免明文入库:
|
||||
|
||||
```bash
|
||||
kubectl create namespace resource-library
|
||||
|
||||
kubectl -n resource-library create secret generic resource-library-secret \
|
||||
--from-literal=MYSQL_ROOT_PASSWORD="$(openssl rand -base64 24)" \
|
||||
--from-literal=MYSQL_PASSWORD="$(openssl rand -base64 24)" \
|
||||
--from-literal=SECRET_KEY="$(python -c 'import secrets;print(secrets.token_hex(32))')" \
|
||||
--from-literal=ADMIN_PASSWORD='StrongAdmin@2026'
|
||||
```
|
||||
|
||||
同时从 `kustomization.yaml` 的 `resources:` 中移除 `01-secret.yaml`,避免被覆盖。
|
||||
|
||||
3. **确认 StorageClass**
|
||||
|
||||
`10-mysql.yaml` 和 `20-app-pvc.yaml` 中 `storageClassName` 默认注释掉,
|
||||
会走集群默认 StorageClass。可用下面的命令确认:
|
||||
|
||||
```bash
|
||||
kubectl get sc
|
||||
```
|
||||
|
||||
4. **修改域名与 Ingress**
|
||||
|
||||
编辑 `30-ingress.yaml`,把 `prl.example.com` 改成真实域名。
|
||||
如不使用 cert-manager,请手工准备 TLS secret:
|
||||
|
||||
```bash
|
||||
kubectl -n resource-library create secret tls resource-library-tls \
|
||||
--cert=fullchain.pem --key=privkey.pem
|
||||
```
|
||||
|
||||
## 部署
|
||||
|
||||
```bash
|
||||
kubectl apply -k k8s/
|
||||
```
|
||||
|
||||
查看状态:
|
||||
|
||||
```bash
|
||||
kubectl -n resource-library get pods,svc,pvc,ingress
|
||||
kubectl -n resource-library logs -f deploy/resource-library-app
|
||||
```
|
||||
|
||||
## 常见运维
|
||||
|
||||
- **重启应用**
|
||||
|
||||
```bash
|
||||
kubectl -n resource-library rollout restart deploy/resource-library-app
|
||||
```
|
||||
|
||||
- **进入 MySQL 执行 SQL**(例如修复 `resources.folder_id` 缺失)
|
||||
|
||||
```bash
|
||||
kubectl -n resource-library exec -it mysql-0 -- \
|
||||
mysql -uroot -p"$MYSQL_ROOT_PASSWORD" resource_library
|
||||
```
|
||||
|
||||
- **备份数据库**
|
||||
|
||||
```bash
|
||||
kubectl -n resource-library exec mysql-0 -- \
|
||||
sh -c 'exec mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" resource_library' \
|
||||
> backup.sql
|
||||
```
|
||||
|
||||
## 多副本扩容注意事项
|
||||
|
||||
默认为 1 个应用副本。如要扩容:
|
||||
|
||||
1. PVC `accessModes` 必须改成 `ReadWriteMany`,且集群需要支持 RWX 的
|
||||
StorageClass(NFS/CephFS/云厂商 file 存储)。
|
||||
2. `entrypoint.sh` 中的 `init_db` 在多副本并发执行时会重复,建议把初始化逻辑
|
||||
拆成独立的 Kubernetes **Job**,在 Deployment 启动前一次性跑完。
|
||||
3. Gunicorn 本身无状态,Session 存在 cookie 中,水平扩容无额外依赖。
|
||||
@@ -0,0 +1,31 @@
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
# Kustomization — 一键部署入口
|
||||
# ───────────────────────────────────────────────────────────────
|
||||
# 用法:
|
||||
# kubectl apply -k k8s/
|
||||
# 卸载:
|
||||
# kubectl delete -k k8s/
|
||||
# kubectl delete pvc -n resource-library --all # 如需清理数据
|
||||
# ═══════════════════════════════════════════════════════════════
|
||||
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||
kind: Kustomization
|
||||
|
||||
namespace: resource-library
|
||||
|
||||
resources:
|
||||
- 00-namespace.yaml
|
||||
- 01-secret.yaml
|
||||
- 02-configmap.yaml
|
||||
- 10-mysql.yaml
|
||||
- 20-app-pvc.yaml
|
||||
- 21-app.yaml
|
||||
- 30-ingress.yaml
|
||||
|
||||
commonLabels:
|
||||
app.kubernetes.io/part-of: resource-library
|
||||
|
||||
# 如需给应用镜像打 tag,可用如下写法覆盖:
|
||||
# images:
|
||||
# - name: resource-library
|
||||
# newName: ghcr.io/your-org/resource-library
|
||||
# newTag: "1.0.0"
|
||||
Reference in New Issue
Block a user