首次提交:初始化项目
This commit is contained in:
17
010-中间件/001-redis/deploy.sh
Normal file
17
010-中间件/001-redis/deploy.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 创建命名空间
|
||||
kubectl create namespace redis
|
||||
|
||||
# 部署 Redis
|
||||
kubectl apply -f redis-deployment.yaml
|
||||
|
||||
# 等待 Redis 启动
|
||||
echo "等待 Redis 启动..."
|
||||
kubectl wait --for=condition=ready pod -l app=redis -n redis --timeout=300s
|
||||
|
||||
# 显示状态
|
||||
echo "Redis 部署完成!"
|
||||
kubectl get pods -n redis
|
||||
kubectl get pvc -n redis
|
||||
kubectl get svc -n redis
|
||||
52
010-中间件/001-redis/readme.md
Normal file
52
010-中间件/001-redis/readme.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Redis 部署说明
|
||||
|
||||
## 配置信息
|
||||
|
||||
- **命名空间**: redis
|
||||
- **存储**: 使用 Longhorn 提供 5Gi 持久化存储
|
||||
- **镜像**: redis:7-alpine
|
||||
- **持久化**: 启用 RDB + AOF 双重持久化
|
||||
- **内存限制**: 2GB
|
||||
- **访问地址**: redis.redis.svc.cluster.local:6379
|
||||
|
||||
## 部署方式
|
||||
|
||||
```bash
|
||||
bash deploy.sh
|
||||
```
|
||||
|
||||
## 持久化配置
|
||||
|
||||
### RDB 快照
|
||||
- 900秒内至少1个key变化
|
||||
- 300秒内至少10个key变化
|
||||
- 60秒内至少10000个key变化
|
||||
|
||||
### AOF 日志
|
||||
- 每秒同步一次
|
||||
- 自动重写阈值: 64MB
|
||||
|
||||
## 内存策略
|
||||
|
||||
- 最大内存: 2GB
|
||||
- 淘汰策略: allkeys-lru (所有key的LRU算法)
|
||||
|
||||
## 连接测试
|
||||
|
||||
在集群内部测试连接:
|
||||
|
||||
```bash
|
||||
kubectl run redis-test --rm -it --image=redis:7-alpine -- redis-cli -h redis.redis.svc.cluster.local ping
|
||||
```
|
||||
|
||||
## 备份说明
|
||||
|
||||
Redis 数据存储在 Longhorn 卷上,可以通过 Longhorn UI 创建快照和备份到 S3。
|
||||
|
||||
## 监控
|
||||
|
||||
可以通过以下命令查看 Redis 状态:
|
||||
|
||||
```bash
|
||||
kubectl exec -n redis $(kubectl get pod -n redis -l app=redis -o jsonpath='{.items[0].metadata.name}') -- redis-cli info
|
||||
```
|
||||
123
010-中间件/001-redis/redis-deployment.yaml
Normal file
123
010-中间件/001-redis/redis-deployment.yaml
Normal file
@@ -0,0 +1,123 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: redis-pvc
|
||||
namespace: redis
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: redis-config
|
||||
namespace: redis
|
||||
data:
|
||||
redis.conf: |
|
||||
# Redis 配置
|
||||
bind 0.0.0.0
|
||||
protected-mode yes
|
||||
port 6379
|
||||
tcp-backlog 511
|
||||
timeout 0
|
||||
tcp-keepalive 300
|
||||
|
||||
# 持久化配置
|
||||
save 900 1
|
||||
save 300 10
|
||||
save 60 10000
|
||||
stop-writes-on-bgsave-error yes
|
||||
rdbcompression yes
|
||||
rdbchecksum yes
|
||||
dbfilename dump.rdb
|
||||
dir /data
|
||||
|
||||
# AOF 持久化
|
||||
appendonly yes
|
||||
appendfilename "appendonly.aof"
|
||||
appendfsync everysec
|
||||
no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# 内存管理
|
||||
maxmemory 2gb
|
||||
maxmemory-policy allkeys-lru
|
||||
|
||||
# 日志
|
||||
loglevel notice
|
||||
logfile ""
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
command:
|
||||
- redis-server
|
||||
- /etc/redis/redis.conf
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
name: redis
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /data
|
||||
- name: config
|
||||
mountPath: /etc/redis
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
tcpSocket:
|
||||
port: 6379
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- redis-cli
|
||||
- ping
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: redis-pvc
|
||||
- name: config
|
||||
configMap:
|
||||
name: redis-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis
|
||||
namespace: redis
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
ports:
|
||||
- port: 6379
|
||||
targetPort: 6379
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
25
010-中间件/002-postgresql/deploy.sh
Normal file
25
010-中间件/002-postgresql/deploy.sh
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 创建命名空间
|
||||
kubectl create namespace postgresql
|
||||
|
||||
# 部署 PostgreSQL
|
||||
kubectl apply -f postgresql-deployment.yaml
|
||||
|
||||
# 等待 PostgreSQL 启动
|
||||
echo "等待 PostgreSQL 启动..."
|
||||
kubectl wait --for=condition=ready pod -l app=postgresql -n postgresql --timeout=300s
|
||||
|
||||
# 显示状态
|
||||
echo "PostgreSQL 部署完成!"
|
||||
kubectl get pods -n postgresql
|
||||
kubectl get pvc -n postgresql
|
||||
kubectl get svc -n postgresql
|
||||
|
||||
echo ""
|
||||
echo "连接信息:"
|
||||
echo " 主机: postgresql-service.postgresql.svc.cluster.local"
|
||||
echo " 端口: 5432"
|
||||
echo " 用户: postgres"
|
||||
echo " 密码: postgres123"
|
||||
echo " 数据库: postgres"
|
||||
167
010-中间件/002-postgresql/postgresql-deployment.yaml
Normal file
167
010-中间件/002-postgresql/postgresql-deployment.yaml
Normal file
@@ -0,0 +1,167 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: postgresql-pvc
|
||||
namespace: postgresql
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: postgresql-secret
|
||||
namespace: postgresql
|
||||
type: Opaque
|
||||
stringData:
|
||||
POSTGRES_PASSWORD: "postgres123"
|
||||
POSTGRES_USER: "postgres"
|
||||
POSTGRES_DB: "postgres"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: postgresql-config
|
||||
namespace: postgresql
|
||||
data:
|
||||
postgresql.conf: |
|
||||
# 连接设置
|
||||
listen_addresses = '*'
|
||||
max_connections = 100
|
||||
|
||||
# 内存设置
|
||||
shared_buffers = 256MB
|
||||
effective_cache_size = 1GB
|
||||
maintenance_work_mem = 64MB
|
||||
work_mem = 4MB
|
||||
|
||||
# WAL 设置
|
||||
wal_level = replica
|
||||
max_wal_size = 1GB
|
||||
min_wal_size = 80MB
|
||||
|
||||
# 日志设置
|
||||
logging_collector = on
|
||||
log_directory = 'log'
|
||||
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
|
||||
log_statement = 'all'
|
||||
log_duration = on
|
||||
|
||||
# 性能优化
|
||||
random_page_cost = 1.1
|
||||
effective_io_concurrency = 200
|
||||
|
||||
pg_hba.conf: |
|
||||
# TYPE DATABASE USER ADDRESS METHOD
|
||||
local all all trust
|
||||
host all all 0.0.0.0/0 md5
|
||||
host all all ::0/0 md5
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: StatefulSet
|
||||
metadata:
|
||||
name: postgresql
|
||||
namespace: postgresql
|
||||
spec:
|
||||
serviceName: postgresql
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgresql
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgresql
|
||||
spec:
|
||||
containers:
|
||||
- name: postgresql
|
||||
image: postgres:16-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
name: postgresql
|
||||
env:
|
||||
- name: POSTGRES_USER
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgresql-secret
|
||||
key: POSTGRES_USER
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgresql-secret
|
||||
key: POSTGRES_PASSWORD
|
||||
- name: POSTGRES_DB
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: postgresql-secret
|
||||
key: POSTGRES_DB
|
||||
- name: PGDATA
|
||||
value: /var/lib/postgresql/data/pgdata
|
||||
volumeMounts:
|
||||
- name: data
|
||||
mountPath: /var/lib/postgresql/data
|
||||
- name: config
|
||||
mountPath: /etc/postgresql
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "2Gi"
|
||||
cpu: "1000m"
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- postgres
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 10
|
||||
readinessProbe:
|
||||
exec:
|
||||
command:
|
||||
- pg_isready
|
||||
- -U
|
||||
- postgres
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
volumes:
|
||||
- name: data
|
||||
persistentVolumeClaim:
|
||||
claimName: postgresql-pvc
|
||||
- name: config
|
||||
configMap:
|
||||
name: postgresql-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgresql
|
||||
namespace: postgresql
|
||||
spec:
|
||||
selector:
|
||||
app: postgresql
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
clusterIP: None
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgresql-service
|
||||
namespace: postgresql
|
||||
spec:
|
||||
selector:
|
||||
app: postgresql
|
||||
ports:
|
||||
- port: 5432
|
||||
targetPort: 5432
|
||||
protocol: TCP
|
||||
type: ClusterIP
|
||||
99
010-中间件/002-postgresql/readme.md
Normal file
99
010-中间件/002-postgresql/readme.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# PostgreSQL 16 部署说明
|
||||
|
||||
## 配置信息
|
||||
|
||||
- **命名空间**: postgresql
|
||||
- **版本**: PostgreSQL 16 (Alpine)
|
||||
- **存储**: 使用 Longhorn 提供 10Gi 持久化存储
|
||||
- **内存限制**: 2GB
|
||||
- **访问地址**: postgresql-service.postgresql.svc.cluster.local:5432
|
||||
|
||||
## 默认凭证
|
||||
|
||||
- **用户名**: postgres
|
||||
- **密码**: postgres123
|
||||
- **数据库**: postgres
|
||||
|
||||
⚠️ **安全提示**: 生产环境请修改默认密码!
|
||||
|
||||
## 部署方式
|
||||
|
||||
```bash
|
||||
bash deploy.sh
|
||||
```
|
||||
|
||||
## 数据库配置
|
||||
|
||||
### 连接设置
|
||||
- 最大连接数: 100
|
||||
- 监听地址: 所有接口 (*)
|
||||
|
||||
### 内存配置
|
||||
- shared_buffers: 256MB
|
||||
- effective_cache_size: 1GB
|
||||
- work_mem: 4MB
|
||||
|
||||
### WAL 配置
|
||||
- wal_level: replica (支持主从复制)
|
||||
- max_wal_size: 1GB
|
||||
|
||||
### 日志配置
|
||||
- 记录所有 SQL 语句
|
||||
- 记录执行时间
|
||||
|
||||
## 连接测试
|
||||
|
||||
在集群内部测试连接:
|
||||
|
||||
```bash
|
||||
kubectl run pg-test --rm -it --image=postgres:16-alpine --env="PGPASSWORD=postgres123" -- psql -h postgresql-service.postgresql.svc.cluster.local -U postgres -c "SELECT version();"
|
||||
```
|
||||
|
||||
## 数据持久化
|
||||
|
||||
PostgreSQL 数据存储在 Longhorn 卷上:
|
||||
- 数据目录: /var/lib/postgresql/data/pgdata
|
||||
- 可以通过 Longhorn UI 创建快照和备份到 S3
|
||||
|
||||
## 常用操作
|
||||
|
||||
### 查看日志
|
||||
```bash
|
||||
kubectl logs -n postgresql postgresql-0 -f
|
||||
```
|
||||
|
||||
### 进入数据库
|
||||
```bash
|
||||
kubectl exec -it -n postgresql postgresql-0 -- psql -U postgres
|
||||
```
|
||||
|
||||
### 创建新数据库
|
||||
```bash
|
||||
kubectl exec -n postgresql postgresql-0 -- psql -U postgres -c "CREATE DATABASE myapp;"
|
||||
```
|
||||
|
||||
### 创建新用户
|
||||
```bash
|
||||
kubectl exec -n postgresql postgresql-0 -- psql -U postgres -c "CREATE USER myuser WITH PASSWORD 'mypassword';"
|
||||
kubectl exec -n postgresql postgresql-0 -- psql -U postgres -c "GRANT ALL PRIVILEGES ON DATABASE myapp TO myuser;"
|
||||
```
|
||||
|
||||
## 备份与恢复
|
||||
|
||||
### 手动备份
|
||||
```bash
|
||||
kubectl exec -n postgresql postgresql-0 -- pg_dump -U postgres postgres > backup.sql
|
||||
```
|
||||
|
||||
### 恢复备份
|
||||
```bash
|
||||
cat backup.sql | kubectl exec -i -n postgresql postgresql-0 -- psql -U postgres postgres
|
||||
```
|
||||
|
||||
## 监控
|
||||
|
||||
查看数据库状态:
|
||||
|
||||
```bash
|
||||
kubectl exec -n postgresql postgresql-0 -- psql -U postgres -c "SELECT * FROM pg_stat_activity;"
|
||||
```
|
||||
32
010-中间件/003-navigation/Dockerfile
Normal file
32
010-中间件/003-navigation/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM python:3.11-alpine
|
||||
|
||||
# 安装 nginx
|
||||
RUN apk add --no-cache nginx
|
||||
|
||||
# 创建工作目录
|
||||
WORKDIR /app
|
||||
|
||||
# 复制生成器脚本
|
||||
COPY generator.py /app/
|
||||
COPY index.html /usr/share/nginx/html/
|
||||
|
||||
# 创建 nginx 配置
|
||||
RUN mkdir -p /run/nginx && \
|
||||
echo 'server {' > /etc/nginx/http.d/default.conf && \
|
||||
echo ' listen 80;' >> /etc/nginx/http.d/default.conf && \
|
||||
echo ' root /usr/share/nginx/html;' >> /etc/nginx/http.d/default.conf && \
|
||||
echo ' index index.html;' >> /etc/nginx/http.d/default.conf && \
|
||||
echo ' location / {' >> /etc/nginx/http.d/default.conf && \
|
||||
echo ' try_files $uri $uri/ =404;' >> /etc/nginx/http.d/default.conf && \
|
||||
echo ' }' >> /etc/nginx/http.d/default.conf && \
|
||||
echo '}' >> /etc/nginx/http.d/default.conf
|
||||
|
||||
# 启动脚本
|
||||
RUN echo '#!/bin/sh' > /app/start.sh && \
|
||||
echo 'nginx' >> /app/start.sh && \
|
||||
echo 'python3 /app/generator.py' >> /app/start.sh && \
|
||||
chmod +x /app/start.sh
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
CMD ["/app/start.sh"]
|
||||
19
010-中间件/003-navigation/deploy.sh
Normal file
19
010-中间件/003-navigation/deploy.sh
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 部署导航服务
|
||||
echo "部署导航服务..."
|
||||
kubectl apply -f deployment.yaml
|
||||
|
||||
# 等待部署完成
|
||||
echo "等待导航服务启动..."
|
||||
kubectl wait --for=condition=ready pod -l app=navigation -n navigation --timeout=300s
|
||||
|
||||
# 显示状态
|
||||
echo ""
|
||||
echo "导航服务部署完成!"
|
||||
kubectl get pods -n navigation
|
||||
kubectl get svc -n navigation
|
||||
kubectl get ingress -n navigation
|
||||
|
||||
echo ""
|
||||
echo "访问地址: https://dh.u6.net3w.com"
|
||||
179
010-中间件/003-navigation/deployment.yaml
Normal file
179
010-中间件/003-navigation/deployment.yaml
Normal file
@@ -0,0 +1,179 @@
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: navigation
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: navigation-html
|
||||
namespace: navigation
|
||||
data:
|
||||
index.html: |
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>K3s 服务导航</title>
|
||||
<style>
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
.container { max-width: 1200px; margin: 0 auto; }
|
||||
header { text-align: center; color: white; margin-bottom: 40px; padding: 40px 20px; }
|
||||
h1 { font-size: 3em; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); }
|
||||
.subtitle { font-size: 1.2em; opacity: 0.9; }
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.service-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
.service-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
|
||||
}
|
||||
.service-icon { font-size: 3em; margin-bottom: 15px; }
|
||||
.service-name { font-size: 1.5em; font-weight: bold; margin-bottom: 10px; color: #333; }
|
||||
.service-url { color: #667eea; font-size: 0.9em; word-break: break-all; }
|
||||
.service-description { color: #666; margin-top: 10px; font-size: 0.9em; }
|
||||
footer { text-align: center; color: white; padding: 20px; opacity: 0.8; }
|
||||
.update-time {
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.stats { background: rgba(255,255,255,0.1); padding: 15px; border-radius: 10px; margin-top: 20px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🚀 K3s 服务导航</h1>
|
||||
<p class="subtitle">快速访问您的所有服务</p>
|
||||
</header>
|
||||
<div class="services-grid" id="services"></div>
|
||||
<footer>
|
||||
<div class="stats" id="stats">加载中...</div>
|
||||
<div class="update-time">最后更新: <span id="updateTime">-</span></div>
|
||||
</footer>
|
||||
</div>
|
||||
<script>
|
||||
const services = [
|
||||
{ name: 'Longhorn', icon: '💾', url: 'https://longhorn.u6.net3w.com', description: '分布式块存储管理' },
|
||||
{ name: 'Grafana', icon: '📊', url: 'https://grafana.u6.net3w.com', description: '监控数据可视化' },
|
||||
{ name: 'Prometheus', icon: '📈', url: 'https://prometheus.u6.net3w.com', description: '指标监控系统' },
|
||||
{ name: 'Alertmanager', icon: '🔔', url: 'https://alertmanager.u6.net3w.com', description: '告警管理系统' },
|
||||
{ name: 'MinIO S3', icon: '🗄️', url: 'https://s3.u6.net3w.com', description: '对象存储 API' },
|
||||
{ name: 'MinIO Console', icon: '🎛️', url: 'https://console.s3.u6.net3w.com', description: 'MinIO 管理控制台' },
|
||||
{ name: '测试页面', icon: '🧪', url: 'https://test.u6.net3w.com', description: '测试服务' }
|
||||
];
|
||||
|
||||
function renderServices() {
|
||||
const container = document.getElementById('services');
|
||||
container.innerHTML = services.map(service => `
|
||||
<a href="${service.url}" class="service-card" target="_blank">
|
||||
<div class="service-icon">${service.icon}</div>
|
||||
<div class="service-name">${service.name}</div>
|
||||
<div class="service-url">${service.url}</div>
|
||||
<div class="service-description">${service.description}</div>
|
||||
</a>
|
||||
`).join('');
|
||||
document.getElementById('stats').textContent = `共 ${services.length} 个服务`;
|
||||
}
|
||||
|
||||
function updateTime() {
|
||||
document.getElementById('updateTime').textContent = new Date().toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
renderServices();
|
||||
updateTime();
|
||||
setInterval(updateTime, 60000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: navigation
|
||||
namespace: navigation
|
||||
spec:
|
||||
# replicas 由 KEDA 管理,不设置固定值
|
||||
selector:
|
||||
matchLabels:
|
||||
app: navigation
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: navigation
|
||||
spec:
|
||||
containers:
|
||||
- name: nginx
|
||||
image: nginx:alpine
|
||||
ports:
|
||||
- containerPort: 80
|
||||
resources:
|
||||
requests:
|
||||
cpu: 50m
|
||||
memory: 64Mi
|
||||
limits:
|
||||
cpu: 200m
|
||||
memory: 128Mi
|
||||
volumeMounts:
|
||||
- name: html
|
||||
mountPath: /usr/share/nginx/html
|
||||
volumes:
|
||||
- name: html
|
||||
configMap:
|
||||
name: navigation-html
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: navigation
|
||||
namespace: navigation
|
||||
spec:
|
||||
selector:
|
||||
app: navigation
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 80
|
||||
type: ClusterIP
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: navigation-ingress
|
||||
namespace: navigation
|
||||
annotations:
|
||||
traefik.ingress.kubernetes.io/router.entrypoints: web
|
||||
spec:
|
||||
rules:
|
||||
- host: dh.u6.net3w.com
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: navigation
|
||||
port:
|
||||
number: 80
|
||||
265
010-中间件/003-navigation/generator.py
Normal file
265
010-中间件/003-navigation/generator.py
Normal file
@@ -0,0 +1,265 @@
|
||||
#!/usr/bin/env python3
|
||||
import re
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
# 服务图标映射
|
||||
SERVICE_ICONS = {
|
||||
'longhorn': '💾',
|
||||
'grafana': '📊',
|
||||
'prometheus': '📈',
|
||||
'alertmanager': '🔔',
|
||||
's3': '🗄️',
|
||||
'console': '🎛️',
|
||||
'minio': '🗄️',
|
||||
'dh': '🏠',
|
||||
'test': '🧪',
|
||||
'default': '🌐'
|
||||
}
|
||||
|
||||
# 服务描述映射
|
||||
SERVICE_DESCRIPTIONS = {
|
||||
'longhorn': '分布式块存储管理',
|
||||
'grafana': '监控数据可视化',
|
||||
'prometheus': '指标监控系统',
|
||||
'alertmanager': '告警管理系统',
|
||||
's3': '对象存储 API',
|
||||
'console': 'MinIO 管理控制台',
|
||||
'minio': 'MinIO 对象存储',
|
||||
'dh': '服务导航页面',
|
||||
'test': '测试服务',
|
||||
'default': 'K3s 服务'
|
||||
}
|
||||
|
||||
def parse_caddyfile(caddyfile_path):
|
||||
"""解析 Caddyfile 并提取域名"""
|
||||
services = []
|
||||
|
||||
try:
|
||||
with open(caddyfile_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# 匹配域名配置块 (domain.com {)
|
||||
pattern = r'([a-zA-Z0-9.-]+\.u6\.net3w\.com)\s*\{'
|
||||
matches = re.findall(pattern, content)
|
||||
|
||||
for domain in matches:
|
||||
# 提取子域名前缀
|
||||
subdomain = domain.split('.')[0]
|
||||
|
||||
# 获取图标和描述
|
||||
icon = SERVICE_ICONS.get(subdomain, SERVICE_ICONS['default'])
|
||||
description = SERVICE_DESCRIPTIONS.get(subdomain, SERVICE_DESCRIPTIONS['default'])
|
||||
|
||||
# 生成服务名称
|
||||
name = subdomain.capitalize()
|
||||
if subdomain == 's3':
|
||||
name = 'MinIO S3'
|
||||
elif subdomain == 'console':
|
||||
name = 'MinIO Console'
|
||||
elif subdomain == 'dh':
|
||||
name = '导航页面'
|
||||
|
||||
services.append({
|
||||
'name': name,
|
||||
'icon': icon,
|
||||
'url': f'https://{domain}',
|
||||
'description': description
|
||||
})
|
||||
|
||||
# 排序:导航页面放最后
|
||||
services.sort(key=lambda x: (x['name'] == '导航页面', x['name']))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error parsing Caddyfile: {e}")
|
||||
|
||||
return services
|
||||
|
||||
def generate_html(services):
|
||||
"""生成 HTML 页面"""
|
||||
|
||||
services_html = '\n'.join([
|
||||
f'''
|
||||
<a href="{service['url']}" class="service-card" target="_blank">
|
||||
<div class="service-icon">{service['icon']}</div>
|
||||
<div class="service-name">{service['name']}</div>
|
||||
<div class="service-url">{service['url']}</div>
|
||||
<div class="service-description">{service['description']}</div>
|
||||
</a>
|
||||
'''
|
||||
for service in services
|
||||
])
|
||||
|
||||
html_template = f'''<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>K3s 服务导航</title>
|
||||
<style>
|
||||
* {{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}}
|
||||
|
||||
.container {{
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}}
|
||||
|
||||
header {{
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 40px;
|
||||
padding: 40px 20px;
|
||||
}}
|
||||
|
||||
h1 {{
|
||||
font-size: 3em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}}
|
||||
|
||||
.subtitle {{
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}}
|
||||
|
||||
.services-grid {{
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}}
|
||||
|
||||
.service-card {{
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}}
|
||||
|
||||
.service-card:hover {{
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
|
||||
}}
|
||||
|
||||
.service-icon {{
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
}}
|
||||
|
||||
.service-name {{
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}}
|
||||
|
||||
.service-url {{
|
||||
color: #667eea;
|
||||
font-size: 0.9em;
|
||||
word-break: break-all;
|
||||
}}
|
||||
|
||||
.service-description {{
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
}}
|
||||
|
||||
footer {{
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
opacity: 0.8;
|
||||
}}
|
||||
|
||||
.update-time {{
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
|
||||
.stats {{
|
||||
background: rgba(255,255,255,0.1);
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-top: 20px;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🚀 K3s 服务导航</h1>
|
||||
<p class="subtitle">快速访问您的所有服务</p>
|
||||
</header>
|
||||
|
||||
<div class="services-grid">
|
||||
{services_html}
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="stats">
|
||||
共 {len(services)} 个服务
|
||||
</div>
|
||||
<div class="update-time">
|
||||
最后更新: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
'''
|
||||
|
||||
return html_template
|
||||
|
||||
def main():
|
||||
caddyfile_path = '/etc/caddy/Caddyfile'
|
||||
output_path = '/usr/share/nginx/html/index.html'
|
||||
|
||||
print(f"Navigation page generator started at {datetime.now()}")
|
||||
print(f"Caddyfile path: {caddyfile_path}")
|
||||
print(f"Output path: {output_path}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
# 解析 Caddyfile
|
||||
services = parse_caddyfile(caddyfile_path)
|
||||
print(f"Found {len(services)} services")
|
||||
|
||||
# 生成 HTML
|
||||
html = generate_html(services)
|
||||
|
||||
# 写入文件
|
||||
Path(output_path).parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(output_path, 'w', encoding='utf-8') as f:
|
||||
f.write(html)
|
||||
|
||||
print(f"Updated navigation page at {datetime.now()}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
|
||||
# 每 5 分钟更新一次
|
||||
time.sleep(300)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
193
010-中间件/003-navigation/index.html
Normal file
193
010-中间件/003-navigation/index.html
Normal file
@@ -0,0 +1,193 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>K3s 服务导航</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
text-align: center;
|
||||
color: white;
|
||||
margin-bottom: 40px;
|
||||
padding: 40px 20px;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 10px;
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2em;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 25px;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.service-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 15px 40px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
font-size: 3em;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.service-url {
|
||||
color: #667eea;
|
||||
font-size: 0.9em;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.service-description {
|
||||
color: #666;
|
||||
margin-top: 10px;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.update-time {
|
||||
background: rgba(255,255,255,0.2);
|
||||
padding: 10px 20px;
|
||||
border-radius: 20px;
|
||||
display: inline-block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<header>
|
||||
<h1>🚀 K3s 服务导航</h1>
|
||||
<p class="subtitle">快速访问您的所有服务</p>
|
||||
</header>
|
||||
|
||||
<div class="services-grid" id="services">
|
||||
<!-- 服务卡片将由 JavaScript 动态生成 -->
|
||||
</div>
|
||||
|
||||
<footer>
|
||||
<div class="update-time">
|
||||
最后更新: <span id="updateTime">加载中...</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 服务配置
|
||||
const services = [
|
||||
{
|
||||
name: 'Longhorn',
|
||||
icon: '💾',
|
||||
url: 'https://longhorn.u6.net3w.com',
|
||||
description: '分布式块存储管理'
|
||||
},
|
||||
{
|
||||
name: 'Grafana',
|
||||
icon: '📊',
|
||||
url: 'https://grafana.u6.net3w.com',
|
||||
description: '监控数据可视化'
|
||||
},
|
||||
{
|
||||
name: 'Prometheus',
|
||||
icon: '📈',
|
||||
url: 'https://prometheus.u6.net3w.com',
|
||||
description: '指标监控系统1'
|
||||
},
|
||||
{
|
||||
name: 'Alertmanager',
|
||||
icon: '🔔',
|
||||
url: 'https://alertmanager.u6.net3w.com',
|
||||
description: '告警管理系统'
|
||||
},
|
||||
{
|
||||
name: 'MinIO S3',
|
||||
icon: '🗄️',
|
||||
url: 'https://s3.u6.net3w.com',
|
||||
description: '对象存储 API'
|
||||
},
|
||||
{
|
||||
name: 'MinIO Console',
|
||||
icon: '🎛️',
|
||||
url: 'https://console.s3.u6.net3w.com',
|
||||
description: 'MinIO 管理控制台'
|
||||
}
|
||||
];
|
||||
|
||||
// 渲染服务卡片
|
||||
function renderServices() {
|
||||
const container = document.getElementById('services');
|
||||
container.innerHTML = services.map(service => `
|
||||
<a href="${service.url}" class="service-card" target="_blank">
|
||||
<div class="service-icon">${service.icon}</div>
|
||||
<div class="service-name">${service.name}</div>
|
||||
<div class="service-url">${service.url}</div>
|
||||
<div class="service-description">${service.description}</div>
|
||||
</a>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 更新时间
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
document.getElementById('updateTime').textContent = now.toLocaleString('zh-CN');
|
||||
}
|
||||
|
||||
// 初始化
|
||||
renderServices();
|
||||
updateTime();
|
||||
|
||||
// 每分钟更新一次时间
|
||||
setInterval(updateTime, 60000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
199
010-中间件/003-navigation/readme.md
Normal file
199
010-中间件/003-navigation/readme.md
Normal file
@@ -0,0 +1,199 @@
|
||||
# 导航页面服务
|
||||
|
||||
## 功能说明
|
||||
|
||||
自动生成的服务导航页面,定时读取 Caddy 配置文件中的域名,动态生成美观的导航网页。
|
||||
|
||||
## 访问地址
|
||||
|
||||
https://dh.u6.net3w.com
|
||||
|
||||
## 特性
|
||||
|
||||
- 🔄 **自动更新**: 每 5 分钟自动读取 Caddyfile 并更新导航页面
|
||||
- 🎨 **美观界面**: 渐变背景、卡片式布局、悬停动画
|
||||
- 📱 **响应式设计**: 自适应各种屏幕尺寸
|
||||
- 🚀 **快速访问**: 一键跳转到所有服务
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ Caddy ConfigMap │
|
||||
│ (包含所有域名配置) │
|
||||
└──────────────┬──────────────────────┘
|
||||
│
|
||||
│ 挂载为只读卷
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Navigation Pod │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Generator Container │ │
|
||||
│ │ - 读取 Caddyfile │ │
|
||||
│ │ - 解析域名 │ │
|
||||
│ │ - 生成 HTML │ │
|
||||
│ │ - 每 5 分钟更新 │ │
|
||||
│ └───────────┬───────────────────┘ │
|
||||
│ │ 共享卷 │
|
||||
│ ↓ │
|
||||
│ ┌───────────────────────────────┐ │
|
||||
│ │ Nginx Container │ │
|
||||
│ │ - 提供静态文件服务 │ │
|
||||
│ │ - 端口 80 │ │
|
||||
│ └───────────────────────────────┘ │
|
||||
└─────────────────────────────────────┘
|
||||
│
|
||||
↓
|
||||
┌─────────────────────────────────────┐
|
||||
│ Traefik Ingress │
|
||||
│ dh.u6.net3w.com │
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## 部署方式
|
||||
|
||||
```bash
|
||||
bash deploy.sh
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
1. **Generator 容器**:
|
||||
- 从 Caddy ConfigMap 挂载 Caddyfile
|
||||
- Python 脚本解析 Caddyfile,提取所有 `*.u6.net3w.com` 域名
|
||||
- 根据子域名前缀匹配图标和描述
|
||||
- 生成完整的 HTML 页面
|
||||
- 写入共享卷 `/usr/share/nginx/html/index.html`
|
||||
- 每 5 分钟重复一次
|
||||
|
||||
2. **Nginx 容器**:
|
||||
- 从共享卷读取 HTML 文件
|
||||
- 提供 HTTP 服务(端口 80)
|
||||
|
||||
3. **Ingress**:
|
||||
- 将 `dh.u6.net3w.com` 路由到 navigation 服务
|
||||
|
||||
## 服务图标映射
|
||||
|
||||
| 子域名 | 图标 | 描述 |
|
||||
|--------|------|------|
|
||||
| longhorn | 💾 | 分布式块存储管理 |
|
||||
| grafana | 📊 | 监控数据可视化 |
|
||||
| prometheus | 📈 | 指标监控系统 |
|
||||
| alertmanager | 🔔 | 告警管理系统 |
|
||||
| s3 | 🗄️ | 对象存储 API |
|
||||
| console | 🎛️ | MinIO 管理控制台 |
|
||||
| dh | 🏠 | 服务导航页面 |
|
||||
| test | 🧪 | 测试服务 |
|
||||
| 其他 | 🌐 | K3s 服务 |
|
||||
|
||||
## 添加新服务
|
||||
|
||||
当你在 Caddyfile 中添加新的域名配置时,导航页面会在 5 分钟内自动更新。
|
||||
|
||||
### 自定义图标和描述
|
||||
|
||||
编辑 ConfigMap 中的 `generator.py`,修改以下字典:
|
||||
|
||||
```python
|
||||
SERVICE_ICONS = {
|
||||
'newservice': '🎯', # 添加新服务的图标
|
||||
# ...
|
||||
}
|
||||
|
||||
SERVICE_DESCRIPTIONS = {
|
||||
'newservice': '新服务的描述', # 添加新服务的描述
|
||||
# ...
|
||||
}
|
||||
```
|
||||
|
||||
然后重新部署:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment.yaml
|
||||
kubectl rollout restart deployment navigation -n navigation
|
||||
```
|
||||
|
||||
## 手动触发更新
|
||||
|
||||
如果需要立即更新导航页面:
|
||||
|
||||
```bash
|
||||
# 重启 generator 容器
|
||||
kubectl rollout restart deployment navigation -n navigation
|
||||
```
|
||||
|
||||
## 查看日志
|
||||
|
||||
```bash
|
||||
# 查看 generator 日志
|
||||
kubectl logs -n navigation -l app=navigation -c generator -f
|
||||
|
||||
# 查看 nginx 日志
|
||||
kubectl logs -n navigation -l app=navigation -c nginx -f
|
||||
```
|
||||
|
||||
## 自定义样式
|
||||
|
||||
如果需要修改页面样式,编辑 ConfigMap 中的 HTML 模板部分,然后重新部署。
|
||||
|
||||
## 故障排查
|
||||
|
||||
### 页面无法访问
|
||||
|
||||
```bash
|
||||
# 检查 Pod 状态
|
||||
kubectl get pods -n navigation
|
||||
|
||||
# 检查 Ingress
|
||||
kubectl get ingress -n navigation
|
||||
|
||||
# 检查服务
|
||||
kubectl get svc -n navigation
|
||||
```
|
||||
|
||||
### 页面未更新
|
||||
|
||||
```bash
|
||||
# 查看 generator 日志
|
||||
kubectl logs -n navigation -l app=navigation -c generator
|
||||
|
||||
# 检查 Caddy ConfigMap
|
||||
kubectl get configmap caddy-config -n default -o yaml
|
||||
|
||||
# 手动触发更新
|
||||
kubectl rollout restart deployment navigation -n navigation
|
||||
```
|
||||
|
||||
### Caddyfile 无法读取
|
||||
|
||||
确保 navigation 命名空间可以访问 default 命名空间的 ConfigMap:
|
||||
|
||||
```bash
|
||||
# 检查 ConfigMap 挂载
|
||||
kubectl describe pod -n navigation -l app=navigation
|
||||
```
|
||||
|
||||
## 性能优化
|
||||
|
||||
- **更新频率**: 默认 5 分钟,可在 `generator.py` 中修改 `time.sleep(300)`
|
||||
- **缓存**: Nginx 自动缓存静态文件
|
||||
- **资源限制**: 可根据需要添加资源限制
|
||||
|
||||
## 卸载
|
||||
|
||||
```bash
|
||||
kubectl delete namespace navigation
|
||||
```
|
||||
|
||||
## 未来改进
|
||||
|
||||
- [ ] 添加服务健康状态检查
|
||||
- [ ] 支持服务分组
|
||||
- [ ] 添加搜索功能
|
||||
- [ ] 支持深色模式
|
||||
- [ ] 添加访问统计
|
||||
|
||||
---
|
||||
|
||||
**导航页面让您的服务一目了然!** 🚀
|
||||
6
010-中间件/deployment.yaml_statefulset.yaml.md
Normal file
6
010-中间件/deployment.yaml_statefulset.yaml.md
Normal file
@@ -0,0 +1,6 @@
|
||||
核心部署方案:StatefulSet vs Deployment
|
||||
对于 Redis 独立 Pod:
|
||||
|
||||
不要用 Deployment: 它是为无状态设计的,Pod 名字随机,且多个副本会争抢同一个存储。
|
||||
|
||||
推荐使用 StatefulSet: 专门用于数据库。Pod 名字固定(如 redis-0),且能完美绑定 Longhorn 的磁盘。
|
||||
0
010-中间件/kustomization.yaml
Normal file
0
010-中间件/kustomization.yaml
Normal file
12
010-中间件/pvc.yaml
Normal file
12
010-中间件/pvc.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: redis-data
|
||||
namespace: middleware
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn # 指向你刚装好的 Longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Redis 主要是内存存储,5G 足够放持久化文件了
|
||||
103
010-中间件/readme.md
Normal file
103
010-中间件/readme.md
Normal file
@@ -0,0 +1,103 @@
|
||||
note
|
||||
kubectl apply -f /home/fei/k3s/010-中间件/001-redis/redis-pvc.yaml
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
既然你的基础设施已经有了 **PG16、S3、Longhorn 和 Helm**,那么 Redis 的规划应该朝着“标准生产环境”看齐。
|
||||
|
||||
我们将 Redis 放在一个新的分类文件夹下,与基础设施解耦。
|
||||
|
||||
---
|
||||
|
||||
## 1. 目录结构规划
|
||||
|
||||
建议在你的 `k3s` 根目录下创建 `010-中间件`,专门存放这类有状态的服务:
|
||||
|
||||
```text
|
||||
/home/fei/k3s/
|
||||
├── 009-基础设施/
|
||||
│ ├── 002-s3/
|
||||
│ ├── 003-helm/
|
||||
│ └── 004-longhorn/
|
||||
└── 010-中间件/
|
||||
└── 001-redis/
|
||||
├── kustomization.yaml (或 deploy.sh)
|
||||
├── pvc.yaml (声明 Longhorn 存储)
|
||||
├── redis-conf.yaml (配置密码和持久化)
|
||||
└── deployment.yaml (或 statefulset.yaml)
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心部署方案:StatefulSet vs Deployment
|
||||
|
||||
对于 Redis 独立 Pod:
|
||||
|
||||
* **不要用 Deployment:** 它是为无状态设计的,Pod 名字随机,且多个副本会争抢同一个存储。
|
||||
* **推荐使用 StatefulSet:** 专门用于数据库。Pod 名字固定(如 `redis-0`),且能完美绑定 Longhorn 的磁盘。
|
||||
|
||||
---
|
||||
|
||||
## 3. 关键配置清单 (YAML 逻辑)
|
||||
|
||||
### A. 存储规划 (`pvc.yaml`)
|
||||
|
||||
利用 Longhorn 提供的动态供应,不需要手动创建 PV。
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: redis-data
|
||||
namespace: middleware
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
storageClassName: longhorn # 指向你刚装好的 Longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi # Redis 主要是内存存储,5G 足够放持久化文件了
|
||||
|
||||
```
|
||||
|
||||
### B. 配置规划 (`redis-conf.yaml`)
|
||||
|
||||
为了安全和持久化,通过 `ConfigMap` 管理配置:
|
||||
|
||||
* **密码:** 必须设置 `requirepass`。
|
||||
* **持久化:** 开启 `appendonly yes` (AOF),每秒同步一次,保证数据在 Longhorn 上的安全。
|
||||
|
||||
---
|
||||
|
||||
## 4. 性能与资源限制 (Resources)
|
||||
|
||||
在 Kubernetes 中跑 Redis,最忌讳不设限制。如果 Redis 内存泄露,会撑爆整个 K3s 节点。
|
||||
|
||||
* **Memory Limit:** 必须设置。例如给 2GB 内存。
|
||||
* **maxmemory:** Redis 内部配置应略小于 Pod 限制(如 1.5GB),防止被系统 OOM Kill。
|
||||
|
||||
---
|
||||
|
||||
## 5. 进阶:如何监控?
|
||||
|
||||
既然你已经有了这些基础设施,建议 Redis 规划中预留 **Prometheus Exporter** 的位置。
|
||||
|
||||
* 在 Redis Pod 旁边加一个辅助容器(Sidecar),专门把 Redis 的运行状态(命中率、内存占用、连接数)转为监控指标。
|
||||
|
||||
---
|
||||
|
||||
## 6. 你的执行清单 (Action Plan)
|
||||
|
||||
1. **创建命名空间:** `kubectl create ns middleware`。
|
||||
2. **编写 values.yaml:** 虽然可以用原生 YAML,但我建议你继续用 Helm 模式。
|
||||
* 使用 Bitnami 的 Redis Chart:`helm repo add bitnami https://charts.bitnami.com/bitnami`。
|
||||
* 在你的 `001-redis` 文件夹下创建一个 `my-values.yaml`。
|
||||
|
||||
|
||||
3. **关联 Longhorn:** 在 `my-values.yaml` 中设置 `global.storageClass: longhorn`。
|
||||
4. **关联 S3 (可选):** 如果你想把 Redis 的 RDB 定期传到 S3,可以写一个简单的 `CronJob`。
|
||||
|
||||
**你需要我为你提供一份完整的、可直接在 `001-redis` 文件夹下运行的 `helm install` 命令和对应的 `values.yaml` 配置吗?**
|
||||
0
010-中间件/redis-conf.yaml
Normal file
0
010-中间件/redis-conf.yaml
Normal file
0
010-中间件/statefulset.yaml
Normal file
0
010-中间件/statefulset.yaml
Normal file
Reference in New Issue
Block a user