main:添加核心文件并初始化项目
新增内容: - 创建基础项目结构。 - 添加 `.gitignore` 和 `.dockerignore` 文件。 - 编写 `pyproject.toml` 和依赖文件。 - 添加算法模块及示例算法。 - 实现核心功能模块(日志、错误处理、指标)。 - 添加开发和运行所需的相关脚本文件及文档。
This commit is contained in:
31
deployment/Dockerfile
Normal file
31
deployment/Dockerfile
Normal file
@@ -0,0 +1,31 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装系统依赖
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
gcc \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# 复制依赖文件
|
||||
COPY requirements.txt .
|
||||
|
||||
# 安装 Python 依赖
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# 复制应用代码
|
||||
COPY src/ ./src/
|
||||
|
||||
# 创建非 root 用户
|
||||
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
|
||||
USER appuser
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8000
|
||||
|
||||
# 健康检查
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"
|
||||
|
||||
# 启动命令
|
||||
CMD ["uvicorn", "src.functional_scaffold.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
33
deployment/Dockerfile.redis-exporter
Normal file
33
deployment/Dockerfile.redis-exporter
Normal file
@@ -0,0 +1,33 @@
|
||||
# Redis Exporter Dockerfile
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# 安装依赖
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir redis prometheus-client
|
||||
|
||||
# 复制 exporter 代码
|
||||
COPY src/functional_scaffold/core/metrics_redis_exporter.py .
|
||||
|
||||
# 暴露端口
|
||||
EXPOSE 8001
|
||||
|
||||
# 启动 HTTP 服务器提供指标
|
||||
CMD ["python", "-c", "\
|
||||
from http.server import HTTPServer, BaseHTTPRequestHandler; \
|
||||
from metrics_redis_exporter import get_metrics; \
|
||||
class MetricsHandler(BaseHTTPRequestHandler): \
|
||||
def do_GET(self): \
|
||||
if self.path == '/metrics': \
|
||||
self.send_response(200); \
|
||||
self.send_header('Content-Type', 'text/plain; version=0.0.4'); \
|
||||
self.end_headers(); \
|
||||
self.wfile.write(get_metrics()); \
|
||||
else: \
|
||||
self.send_response(404); \
|
||||
self.end_headers(); \
|
||||
def log_message(self, format, *args): pass; \
|
||||
server = HTTPServer(('0.0.0.0', 8001), MetricsHandler); \
|
||||
print('Redis Exporter 启动在端口 8001'); \
|
||||
server.serve_forever()"]
|
||||
108
deployment/docker-compose.yml
Normal file
108
deployment/docker-compose.yml
Normal file
@@ -0,0 +1,108 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/Dockerfile
|
||||
ports:
|
||||
- "8111:8000"
|
||||
environment:
|
||||
- APP_ENV=development
|
||||
- LOG_LEVEL=INFO
|
||||
- METRICS_ENABLED=true
|
||||
# 方案1:Pushgateway 配置
|
||||
- PUSHGATEWAY_URL=pushgateway:9091
|
||||
- METRICS_JOB_NAME=functional_scaffold
|
||||
# 方案2:Redis 配置
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_METRICS_DB=0
|
||||
volumes:
|
||||
- ../src:/app/src
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- redis
|
||||
- pushgateway
|
||||
healthcheck:
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"]
|
||||
interval: 30s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
start_period: 5s
|
||||
|
||||
# Redis - 用于集中式指标存储(方案2)
|
||||
redis:
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- "6379:6379"
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
command: redis-server --appendonly yes
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 3s
|
||||
retries: 3
|
||||
|
||||
# Pushgateway - 用于短生命周期任务的指标推送(方案1,推荐)
|
||||
pushgateway:
|
||||
image: prom/pushgateway:latest
|
||||
ports:
|
||||
- "9091:9091"
|
||||
restart: unless-stopped
|
||||
command:
|
||||
- '--persistence.file=/data/pushgateway.data'
|
||||
- '--persistence.interval=5m'
|
||||
volumes:
|
||||
- pushgateway_data:/data
|
||||
|
||||
# Redis Exporter - 将 Redis 指标导出为 Prometheus 格式(方案2需要)
|
||||
redis-exporter:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: deployment/Dockerfile.redis-exporter
|
||||
ports:
|
||||
- "8001:8001"
|
||||
environment:
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_METRICS_DB=0
|
||||
depends_on:
|
||||
- redis
|
||||
restart: unless-stopped
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
ports:
|
||||
- "9090:9090"
|
||||
volumes:
|
||||
- ../monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
- prometheus_data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- pushgateway
|
||||
- redis-exporter
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "3000:3000"
|
||||
environment:
|
||||
- GF_SECURITY_ADMIN_PASSWORD=admin
|
||||
volumes:
|
||||
- grafana_data:/var/lib/grafana
|
||||
- ../monitoring/grafana:/etc/grafana/provisioning
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- prometheus
|
||||
|
||||
volumes:
|
||||
prometheus_data:
|
||||
grafana_data:
|
||||
redis_data:
|
||||
pushgateway_data:
|
||||
53
deployment/kubernetes/deployment.yaml
Normal file
53
deployment/kubernetes/deployment.yaml
Normal file
@@ -0,0 +1,53 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: functional-scaffold
|
||||
labels:
|
||||
app: functional-scaffold
|
||||
spec:
|
||||
replicas: 3
|
||||
selector:
|
||||
matchLabels:
|
||||
app: functional-scaffold
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: functional-scaffold
|
||||
spec:
|
||||
containers:
|
||||
- name: functional-scaffold
|
||||
image: functional-scaffold:latest
|
||||
imagePullPolicy: IfNotPresent
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
name: http
|
||||
env:
|
||||
- name: APP_ENV
|
||||
value: "production"
|
||||
- name: LOG_LEVEL
|
||||
value: "INFO"
|
||||
- name: METRICS_ENABLED
|
||||
value: "true"
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /healthz
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /readyz
|
||||
port: 8000
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 10
|
||||
timeoutSeconds: 3
|
||||
failureThreshold: 3
|
||||
31
deployment/kubernetes/service.yaml
Normal file
31
deployment/kubernetes/service.yaml
Normal file
@@ -0,0 +1,31 @@
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: functional-scaffold
|
||||
labels:
|
||||
app: functional-scaffold
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8000
|
||||
protocol: TCP
|
||||
name: http
|
||||
selector:
|
||||
app: functional-scaffold
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: functional-scaffold-metrics
|
||||
labels:
|
||||
app: functional-scaffold
|
||||
spec:
|
||||
type: ClusterIP
|
||||
ports:
|
||||
- port: 8000
|
||||
targetPort: 8000
|
||||
protocol: TCP
|
||||
name: metrics
|
||||
selector:
|
||||
app: functional-scaffold
|
||||
40
deployment/serverless/aliyun-fc.yaml
Normal file
40
deployment/serverless/aliyun-fc.yaml
Normal file
@@ -0,0 +1,40 @@
|
||||
# 阿里云函数计算配置
|
||||
ROSTemplateFormatVersion: '2015-09-01'
|
||||
Transform: 'Aliyun::Serverless-2018-04-03'
|
||||
Resources:
|
||||
functional-scaffold:
|
||||
Type: 'Aliyun::Serverless::Service'
|
||||
Properties:
|
||||
Description: '算法工程化 Serverless 脚手架'
|
||||
LogConfig:
|
||||
Project: functional-scaffold-logs
|
||||
Logstore: function-logs
|
||||
VpcConfig:
|
||||
VpcId: 'vpc-xxxxx'
|
||||
VSwitchIds:
|
||||
- 'vsw-xxxxx'
|
||||
SecurityGroupId: 'sg-xxxxx'
|
||||
prime-checker:
|
||||
Type: 'Aliyun::Serverless::Function'
|
||||
Properties:
|
||||
Description: '质数判断算法服务'
|
||||
Runtime: custom-container
|
||||
MemorySize: 512
|
||||
Timeout: 60
|
||||
InstanceConcurrency: 10
|
||||
CAPort: 8000
|
||||
CustomContainerConfig:
|
||||
Image: 'registry.cn-hangzhou.aliyuncs.com/your-namespace/functional-scaffold:latest'
|
||||
Command: '["uvicorn", "src.functional_scaffold.main:app", "--host", "0.0.0.0", "--port", "8000"]'
|
||||
EnvironmentVariables:
|
||||
APP_ENV: production
|
||||
LOG_LEVEL: INFO
|
||||
METRICS_ENABLED: 'true'
|
||||
Events:
|
||||
httpTrigger:
|
||||
Type: HTTP
|
||||
Properties:
|
||||
AuthType: ANONYMOUS
|
||||
Methods:
|
||||
- GET
|
||||
- POST
|
||||
46
deployment/serverless/aws-lambda.yaml
Normal file
46
deployment/serverless/aws-lambda.yaml
Normal file
@@ -0,0 +1,46 @@
|
||||
# AWS Lambda 配置(使用 Lambda Container Image)
|
||||
AWSTemplateFormatVersion: '2010-09-09'
|
||||
Transform: AWS::Serverless-2016-10-31
|
||||
Description: FunctionalScaffold Serverless Application
|
||||
|
||||
Globals:
|
||||
Function:
|
||||
Timeout: 60
|
||||
MemorySize: 512
|
||||
Environment:
|
||||
Variables:
|
||||
APP_ENV: production
|
||||
LOG_LEVEL: INFO
|
||||
METRICS_ENABLED: 'true'
|
||||
|
||||
Resources:
|
||||
FunctionalScaffoldFunction:
|
||||
Type: AWS::Serverless::Function
|
||||
Properties:
|
||||
PackageType: Image
|
||||
ImageUri: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/functional-scaffold:latest'
|
||||
Events:
|
||||
ApiEvent:
|
||||
Type: Api
|
||||
Properties:
|
||||
Path: /{proxy+}
|
||||
Method: ANY
|
||||
Policies:
|
||||
- AWSLambdaBasicExecutionRole
|
||||
|
||||
FunctionalScaffoldApi:
|
||||
Type: AWS::Serverless::Api
|
||||
Properties:
|
||||
StageName: prod
|
||||
Cors:
|
||||
AllowMethods: "'*'"
|
||||
AllowHeaders: "'*'"
|
||||
AllowOrigin: "'*'"
|
||||
|
||||
Outputs:
|
||||
ApiUrl:
|
||||
Description: "API Gateway endpoint URL"
|
||||
Value: !Sub "https://${FunctionalScaffoldApi}.execute-api.${AWS::Region}.amazonaws.com/prod/"
|
||||
FunctionArn:
|
||||
Description: "Function ARN"
|
||||
Value: !GetAtt FunctionalScaffoldFunction.Arn
|
||||
Reference in New Issue
Block a user