From aab08068c348d8a428fc36ed6eca993efa98e299 Mon Sep 17 00:00:00 2001 From: dxinn <1554389441@qq.com> Date: Tue, 27 Jan 2026 18:21:17 +0800 Subject: [PATCH] =?UTF-8?q?2026-01-27=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...pace.yaml => 03-monitoring-namespace.yaml} | 0 .../Collector_v3/03-otel-gateway.yaml | 96 ------ .../Collector_v3/04-monitoring-rbac.yaml | 38 +++ OpenTelemetry/Collector_v3/04-otel-agent.yaml | 88 ------ .../Collector_v3/05-otel-gateway.yaml | 57 ++++ OpenTelemetry/Collector_v3/06-otel-agent.yaml | 55 ++++ OpenTelemetry/readme.txt | 10 +- .../s1-lessie-server01/s4_lessie_search.yml | 19 ++ filebast/us-prod-03/filebeat.yml | 19 +- filebast/us-prod-03/lessie-email.yml | 20 ++ jenkins/流水线配置/lessie-email-prod.conf | 126 ++++++++ jenkins/流水线配置/lessie-email.conf | 34 ++- jenkins/流水线配置/lessie-list-test-web.conf | 65 ++++ ...rod-web copy.conf => lessie-next-prod-web.conf} | 0 .../systemd守护进程/lessie-email.service | 19 ++ nginx/admin.scalelink.cn.conf | 75 +++++ nginx/app.lessie.ai.conf | 288 +++++------------- 17 files changed, 588 insertions(+), 421 deletions(-) rename OpenTelemetry/Collector_v3/{monitoring-namespace.yaml => 03-monitoring-namespace.yaml} (100%) delete mode 100644 OpenTelemetry/Collector_v3/03-otel-gateway.yaml create mode 100644 OpenTelemetry/Collector_v3/04-monitoring-rbac.yaml delete mode 100644 OpenTelemetry/Collector_v3/04-otel-agent.yaml create mode 100644 OpenTelemetry/Collector_v3/05-otel-gateway.yaml create mode 100644 OpenTelemetry/Collector_v3/06-otel-agent.yaml create mode 100644 filebast/s1-lessie-server01/s4_lessie_search.yml create mode 100644 filebast/us-prod-03/lessie-email.yml create mode 100644 jenkins/流水线配置/lessie-email-prod.conf create mode 100644 jenkins/流水线配置/lessie-list-test-web.conf rename jenkins/流水线配置/{lessie-next-prod-web copy.conf => lessie-next-prod-web.conf} (100%) create mode 100644 jenkins/流水线配置/systemd守护进程/lessie-email.service create mode 100644 nginx/admin.scalelink.cn.conf diff --git a/OpenTelemetry/Collector_v3/monitoring-namespace.yaml b/OpenTelemetry/Collector_v3/03-monitoring-namespace.yaml similarity index 100% rename from OpenTelemetry/Collector_v3/monitoring-namespace.yaml rename to OpenTelemetry/Collector_v3/03-monitoring-namespace.yaml diff --git a/OpenTelemetry/Collector_v3/03-otel-gateway.yaml b/OpenTelemetry/Collector_v3/03-otel-gateway.yaml deleted file mode 100644 index 76dd265..0000000 --- a/OpenTelemetry/Collector_v3/03-otel-gateway.yaml +++ /dev/null @@ -1,96 +0,0 @@ -apiVersion: opentelemetry.io/v1beta1 -kind: OpenTelemetryCollector -metadata: - name: otel-gateway - namespace: opentelemetry-operator-system -spec: - mode: deployment - replicas: 1 - serviceAccount: otel-gateway-collector # Operator 会自动创建并绑定权限 - config: - receivers: - otlp: - protocols: - grpc: - endpoint: 0.0.0.0:4317 - http: - endpoint: 0.0.0.0:4318 - - # --- 核心:采集 K8s 集群状态指标 --- - # 采集 Deployment, DaemonSet, StatefulSet, HPA, Node 等资源的状态 - k8s_cluster: - collection_interval: 30s - node_conditions_to_report: [Ready, MemoryPressure, DiskPressure, PIDPressure] - allocatable_types_to_report: [cpu, memory] - - processors: - batch: - send_batch_size: 1000 - timeout: 10s - memory_limiter: - check_interval: 1s - limit_percentage: 70 - spike_limit_percentage: 30 - - # 增加 K8s 元数据标签 (这也是 Gateway 的重要作用) - k8sattributes: - extract: - metadata: - - k8s.namespace.name - - k8s.pod.name - - k8s.deployment.name - - k8s.statefulset.name - - k8s.daemonset.name - - k8s.cronjob.name - - k8s.job.name - - k8s.node.name - pod_association: - - sources: - - from: resource_attribute - name: k8s.pod.ip - - sources: - - from: resource_attribute - name: k8s.pod.uid - - sources: - - from: connection - - exporters: - # 1. 导出 Metrics 到外部 Prometheus (使用 Remote Write) - prometheusremotewrite: - endpoint: "http://10.0.0.38:9090/api/v1/write" - # 如果有 Basic Auth,在此配置 - # external_labels: - # cluster: "test-k8s-cluster" - - # 2. 导出 Traces 到外部 Tempo (使用 OTLP gRPC) - # otlp/tempo: - # endpoint: "<你的TEMPO_IP>:4317" - # tls: - # insecure: true - - # 3. 导出 Logs 到外部 Elasticsearch (可选) - # elasticsearch: - # endpoints: ["http://<你的ES_IP>:9200"] - # logs_index: "k8s-logs" - - debug: - verbosity: basic - - service: - pipelines: - metrics: - receivers: [otlp, k8s_cluster] - processors: [memory_limiter, batch] - # 确保 k8sattributes 在 batch 之前或之后取决于架构,Gateway通常主要做转发 - # 这里 k8s_cluster 产生的数据自带标签,otlp 来的数据应在 Agent 端打好标签 - exporters: [prometheusremotewrite] - - traces: - receivers: [otlp] - processors: [memory_limiter, batch] - exporters: [otlp/tempo] - - # logs: - # receivers: [otlp] - # processors: [memory_limiter, batch] - # exporters: [elasticsearch] \ No newline at end of file diff --git a/OpenTelemetry/Collector_v3/04-monitoring-rbac.yaml b/OpenTelemetry/Collector_v3/04-monitoring-rbac.yaml new file mode 100644 index 0000000..b0e28bb --- /dev/null +++ b/OpenTelemetry/Collector_v3/04-monitoring-rbac.yaml @@ -0,0 +1,38 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: otel-collector-sa + namespace: monitoring + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: otel-collector-role +rules: +- apiGroups: [""] + resources: ["events", "nodes", "nodes/proxy", "nodes/stats", "services", "endpoints", "pods", "namespaces", "replicationcontrollers", "resourcequotas"] + verbs: ["get", "list", "watch"] +- apiGroups: ["apps"] + resources: ["statefulsets", "daemonsets", "deployments", "replicasets"] + verbs: ["get", "list", "watch"] +- apiGroups: ["batch"] + resources: ["jobs", "cronjobs"] + verbs: ["get", "list", "watch"] +- apiGroups: ["autoscaling"] + resources: ["horizontalpodautoscalers"] + verbs: ["get", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: otel-collector-binding +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: otel-collector-role +subjects: +- kind: ServiceAccount + name: otel-collector-sa + namespace: monitoring \ No newline at end of file diff --git a/OpenTelemetry/Collector_v3/04-otel-agent.yaml b/OpenTelemetry/Collector_v3/04-otel-agent.yaml deleted file mode 100644 index 4715af8..0000000 --- a/OpenTelemetry/Collector_v3/04-otel-agent.yaml +++ /dev/null @@ -1,88 +0,0 @@ -apiVersion: opentelemetry.io/v1beta1 -kind: OpenTelemetryCollector -metadata: - name: otel-agent - namespace: opentelemetry-operator-system -spec: - mode: daemonset - hostNetwork: true # 建议开启,以便更准确获取 Host 指标 - config: - receivers: - # 1. 采集 Pod 和 容器 的资源使用情况 (CPU/Mem) - kubeletstats: - collection_interval: 20s - auth_type: "serviceAccount" - endpoint: "${env:K8S_NODE_NAME}:10250" - insecure_skip_verify: true - metric_groups: - - node - - pod - - container - - # 2. 采集宿主机物理指标 - hostmetrics: - collection_interval: 20s - scrapers: - cpu: - memory: - load: - filesystem: - network: - - # 3. (可选) 采集日志 - # filelog: - # include: [/var/log/pods/*/*/*.log] - # ... - - processors: - batch: - send_batch_size: 500 - timeout: 5s - memory_limiter: - check_interval: 1s - limit_mib: 400 - spike_limit_mib: 100 - - # 资源检测:自动识别云厂商(腾讯云)信息、主机名等 - resourcedetection: - detectors: [system] # 如果在腾讯云CVM上,可以尝试加入 'tencentcloud' 但 system 通常足够 - timeout: 2s - override: false - - # 关键:给指标打上 K8s 标签 (Pod Name, Namespace, Node Name) - k8sattributes: - passthrough: false - extract: - metadata: - - k8s.pod.name - - k8s.pod.uid - - k8s.deployment.name - - k8s.namespace.name - - k8s.node.name - pod_association: - - sources: - - from: resource_attribute - name: k8s.pod.uid - - sources: - - from: resource_attribute - name: k8s.pod.ip - - sources: - - from: connection - - exporters: - # 发送给集群内的 Gateway Service - otlp: - endpoint: "otel-gateway-collector.opentelemetry-operator-system.svc.cluster.local:4317" - tls: - insecure: true - - service: - pipelines: - metrics: - receivers: [kubeletstats, hostmetrics] - processors: [resourcedetection, k8sattributes, memory_limiter, batch] - exporters: [otlp] - - # traces: # 如果应用配置了 sidecar 或其他方式发送 trace 到本地 agent - # receivers: [otlp] - # exporters: [otlp] \ No newline at end of file diff --git a/OpenTelemetry/Collector_v3/05-otel-gateway.yaml b/OpenTelemetry/Collector_v3/05-otel-gateway.yaml new file mode 100644 index 0000000..5ef6111 --- /dev/null +++ b/OpenTelemetry/Collector_v3/05-otel-gateway.yaml @@ -0,0 +1,57 @@ +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: otel-gateway + namespace: monitoring +spec: + mode: deployment + image: otel/opentelemetry-collector-contrib:0.144.0 + replicas: 1 + serviceAccount: otel-collector-sa + env: + - name: K8S_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + config: + receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4317 + http: + endpoint: 0.0.0.0:4318 + k8s_cluster: + collection_interval: 30s + k8s_events: {} + + processors: + batch: + send_batch_size: 1000 + timeout: 10s + resourcedetection: + detectors: [env, system, k8snode] + + exporters: + debug: + verbosity: detailed + + otlp_http/prometheus: + endpoint: "http://10.0.0.38:9090/api/v1/otlp" + + elasticsearch: + endpoints: ["http://10.0.0.38:9200"] + logs_index: "k8s-test-cluster-events" + user: "elastic" + password: "-0NiIBOJGn2CATuPWzNc" + + service: + pipelines: + metrics: + receivers: [otlp, k8s_cluster] + processors: [resourcedetection, batch] + exporters: [otlp_http/prometheus] + logs: + receivers: [k8s_events] + processors: [batch] + exporters: [elasticsearch, debug] \ No newline at end of file diff --git a/OpenTelemetry/Collector_v3/06-otel-agent.yaml b/OpenTelemetry/Collector_v3/06-otel-agent.yaml new file mode 100644 index 0000000..86b98a0 --- /dev/null +++ b/OpenTelemetry/Collector_v3/06-otel-agent.yaml @@ -0,0 +1,55 @@ +apiVersion: opentelemetry.io/v1beta1 +kind: OpenTelemetryCollector +metadata: + name: otel-agent + namespace: monitoring +spec: + mode: daemonset + image: otel/opentelemetry-collector-contrib:0.144.0 + serviceAccount: otel-collector-sa + env: + - name: K8S_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + # --- 新增:在这里定义集群名称,Prod 环境改一下这个值即可 --- + - name: CLUSTER_NAME + value: "test-k8s-cluster" + config: + receivers: + hostmetrics: + collection_interval: 30s + scrapers: + cpu: {} + memory: {} + kubeletstats: + collection_interval: 30s + auth_type: "serviceAccount" + endpoint: "https://${env:K8S_NODE_NAME}:10250" + insecure_skip_verify: true + + processors: + batch: {} + resourcedetection: + detectors: [env, system, k8snode] + + # --- 新增:强制给所有指标打上集群名称标签 --- + resource: + attributes: + - key: k8s.cluster.name + value: ${env:CLUSTER_NAME} + action: insert + + exporters: + otlp: + endpoint: "otel-gateway-collector.monitoring.svc.cluster.local:4317" + tls: + insecure: true + + service: + pipelines: + metrics: + receivers: [hostmetrics, kubeletstats] + # 注意:这里要加上 resource 处理器 + processors: [resourcedetection, resource, batch] + exporters: [otlp] \ No newline at end of file diff --git a/OpenTelemetry/readme.txt b/OpenTelemetry/readme.txt index 9182f0f..7bb919a 100644 --- a/OpenTelemetry/readme.txt +++ b/OpenTelemetry/readme.txt @@ -49,4 +49,12 @@ graph TD graph LR A[指标接收器] -->|metrics流水线| B[指标处理器] --> C[Prometheus导出器] D[日志接收器] -->|logs流水线| E[日志处理器] --> F[ES导出器] - G[追踪接收器] -->|traces流水线| H[追踪处理器] --> I[Tempo导出器] \ No newline at end of file + G[追踪接收器] -->|traces流水线| H[追踪处理器] --> I[Tempo导出器] + + + +10.0.0.38:9090 + +10.0.0.38:9200 +elastic +-0NiIBOJGn2CATuPWzNc \ No newline at end of file diff --git a/filebast/s1-lessie-server01/s4_lessie_search.yml b/filebast/s1-lessie-server01/s4_lessie_search.yml new file mode 100644 index 0000000..f664807 --- /dev/null +++ b/filebast/s1-lessie-server01/s4_lessie_search.yml @@ -0,0 +1,19 @@ +- type: log + id: s4_lessie_search + enabled: true + paths: + - /data/webapps/lessie_sourcing_agents_s4/logs/lessie_sourcing_agents*.log + include_lines: ['^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}'] # 只包含匹配该正则表达式的行 + fields: + application: lessie-search + log_type: lessie_search.log + environment: s4 + instance: weblessie-server-01 + ip: 43.130.56.138 + fields_under_root: true + ignore_older: 24h # 忽略旧日志文件(避免处理已归档的日志) + scan_frequency: 10s # 定期扫描新文件的频率 + clean_inactive: 25h # 清除超过一天未更新的文件 + close_inactive: 5m # 文件超过5分钟无更新则关闭 + close_renamed: true # 处理被重命名的文件 + start_position: beginning # 从文件的开头读取 \ No newline at end of file diff --git a/filebast/us-prod-03/filebeat.yml b/filebast/us-prod-03/filebeat.yml index b22d797..f2c2e97 100644 --- a/filebast/us-prod-03/filebeat.yml +++ b/filebast/us-prod-03/filebeat.yml @@ -1,6 +1,6 @@ # 配置索引模板名称和模式 -setup.template.name: "us-prod-03" -setup.template.pattern: "us-prod-03*" +setup.template.name: "us-prod" +setup.template.pattern: "us-prod*" setup.template.enabled: true setup.ilm.enabled: true @@ -22,6 +22,15 @@ processors: overwrite_keys: true add_error_key: true + - decode_json_fields: + when: + equals: + log_type: lessie-email.log + fields: ["message"] + target: "" + overwrite_keys: true + add_error_key: true + - dissect: when: equals: @@ -32,8 +41,6 @@ processors: ignore_missing: true overwrite_keys: true - - - dissect: when: equals: @@ -49,10 +56,10 @@ processors: #输出 output.elasticsearch: - hosts: ["http://106.53.194.199:9200"] + hosts: ["http://106.53.194.199:9201"] username: "admin" password: "123456" - index: "%{[environment]}-%{[application]}-%{+yyyy.MM.dd}" # 按天分割索引 + index: "%{[environment]}-%{[application]}-%{+yyyy.MM}" # 按月割索引 bulk_max_size: 50 # 单批次传输最大文档数 worker: 1 # 并行工作线程数 timeout: 15s diff --git a/filebast/us-prod-03/lessie-email.yml b/filebast/us-prod-03/lessie-email.yml new file mode 100644 index 0000000..4054878 --- /dev/null +++ b/filebast/us-prod-03/lessie-email.yml @@ -0,0 +1,20 @@ +- type: log + id: us_pord_03_lessie-email + enabled: true + paths: + - /data/webapps/lessie-email/logs/lessie_email.log + fields: + application: lessie-email # 自定义字段,标识应用名称 + log_type: lessie-email.log # 自定义字段,标识日志类型 + environment: us-pord # 自定义字段,标识机器环境名称 + instance: us-prod-03 # 自定义字段,标识机器名称 + fields_under_root: true + ignore_older: 24h # 忽略旧日志文件(避免处理已归档的日志) + scan_frequency: 10s # 定期扫描新文件的频率 + clean_inactive: 25h # 清除超过一天未更新的文件 + close_inactive: 5m # 文件超过5分钟无更新则关闭 + close_renamed: true # 处理被重命名的文件 + start_position: beginning # 从文件的开头读取 + + + \ No newline at end of file diff --git a/jenkins/流水线配置/lessie-email-prod.conf b/jenkins/流水线配置/lessie-email-prod.conf new file mode 100644 index 0000000..43884eb --- /dev/null +++ b/jenkins/流水线配置/lessie-email-prod.conf @@ -0,0 +1,126 @@ +pipeline { + agent any + + parameters { + gitParameter( + branchFilter: 'origin/(.*)', + defaultValue: 'zhangze-init', + name: 'GIT_BRANCH', + type: 'PT_BRANCH_TAG', + selectedValue: 'DEFAULT', + sortMode: 'NONE', + description: '选择代码分支: ', + quickFilterEnabled: true, + tagFilter: '*', + listSize: "5" + ) + } + + environment { + REMOTE_HOST = '49.51.41.243' + REMOTE_PROJECT_PATH = '/data/webapps/lessie-email' + } + + stages { + + stage('Checkout 代码') { + steps { + git branch: "${params.GIT_BRANCH}", credentialsId: 'fly_gitlab_auth', url: 'http://172.24.16.20/python/lessie-email.git' + } + } + + stage('同步文件') { + steps { + sh """ + rsync -avz --exclude '.venv' --exclude '.git' ${WORKSPACE}/ ${REMOTE_HOST}:${REMOTE_PROJECT_PATH}/ + """ + } + } + + stage('安装 & 启动服务') { + steps { + sh """ + ssh ${REMOTE_HOST} ' + cd ${REMOTE_PROJECT_PATH} + # 1. 同步依赖 + uv sync + + # 2. 先尝试 delete 再 start + pm2 delete "lessie-email" || true + + # 3. 使用 PM2 启动/重启服务,正确的 PM2 语法: pm2 start <程序> --name <名字> -- <程序参数> + # --name: 任务名称 + # -o / -e: 将标准输出和错误输出打到同一个固定文件,方便 Filebeat 采集 + # --restart-delay: 崩溃后等 5 秒再重启,防止死循环刷屏 + + ENV=production pm2 start uv --name "lessie-email" \ + -o "/data/webapps/lessie-email/logs/lessie_email.log" \ + -e "/data/webapps/lessie-email/logs/lessie_email.log" \ + --restart-delay 5000 \ + -- run uvicorn app.main:app --host 0.0.0.0 --port 8031 --log-config logging_config.json + + # 3. 保存当前列表,确保服务器重启后能自动恢复这些进程 + pm2 save + ' + """ + } + } + + stage('检查服务') { + steps { + script { + def checkResult = sh returnStatus: true, script: """ + ssh ${REMOTE_HOST} ' + set -e + LOG_LATEST="${REMOTE_PROJECT_PATH}/logs/lessie_email.log" + + # 1. 检查端口是否监听(用 ss 原生过滤,精准可靠) + echo "【检查 1/3】检测端口 8031 是否监听..." + if ! ss -tnl \'sport = :8031\' >/dev/null 2>&1; then + echo "端口 8031 未监听!" + # 显示当前监听状态供排查 + echo "当前监听情况:" + ss -tnl | grep -E ":(8031|LISTEN)" + exit 1 + fi + echo "端口 8031 正在监听" + + # 2. 检查进程是否存在 + echo "【检查 2/3】检测 uvicorn 进程是否存活..." + if ! pgrep -f "uvicorn.*app.main:app" >/dev/null; then + echo "未找到 uvicorn 进程!" + exit 1 + fi + echo "uvicorn 进程存在" + + # 3. 显示最新日志(最后 20 行) + echo "【检查 3/3】显示启动日志(最后 20 行)..." + echo "────────────────────────────" + if [ -f "\$LOG_LATEST" ]; then + tail -n 20 "\$LOG_LATEST" + else + echo "日志文件不存在:\$LOG_LATEST" + exit 1 + fi + echo "────────────────────────────" + echo "服务启动成功!" + ' + """ + + if (checkResult != 0) { + error "服务启动检查失败!请查看上述日志排查问题。" + } + } + } + } + } + + post { + success { + echo '部署成功' + } + failure { + echo '部署失败,请检查日志' + } + } +} diff --git a/jenkins/流水线配置/lessie-email.conf b/jenkins/流水线配置/lessie-email.conf index efa92b4..5866df6 100644 --- a/jenkins/流水线配置/lessie-email.conf +++ b/jenkins/流水线配置/lessie-email.conf @@ -37,28 +37,30 @@ pipeline { } } - stage('下线服务') { - steps { - sh """ - ssh ${REMOTE_HOST} ' - sh /data/sh/kill_lessie_emial.sh - ' - """ - } - } - stage('安装 & 启动服务') { steps { sh """ ssh ${REMOTE_HOST} ' cd ${REMOTE_PROJECT_PATH} + # 1. 同步依赖 uv sync - source .venv/bin/activate - TIMESTAMP=\$(date +"%Y%m%d_%H%M%S") - LOGFILE="${REMOTE_PROJECT_PATH}/logs/lessie_email_\${TIMESTAMP}.log" - nohup env ENV=s4 uv run uvicorn app.main:app --host 0.0.0.0 --port 8031 > "\$LOGFILE" 2>&1 & - // nohup env ENV=s4 uv run uvicorn app.main:app --host 0.0.0.0 --port 8031 --log-config logging_config.json > "\$LOGFILE" 2>&1 & - ln -sf "\$LOGFILE" ${REMOTE_PROJECT_PATH}/logs/lessie_email_latest.log + + # 2. 先尝试 delete 再 start + pm2 delete "lessie-email" || true + + # 3. 使用 PM2 启动/重启服务,正确的 PM2 语法: pm2 start <程序> --name <名字> -- <程序参数> + # --name: 任务名称 + # -o / -e: 将标准输出和错误输出打到同一个固定文件,方便 Filebeat 采集 + # --restart-delay: 崩溃后等 5 秒再重启,防止死循环刷屏 + + ENV=s4 pm2 start uv --name "lessie-email" \ + -o "/data/webapps/lessie-email/logs/lessie_email.log" \ + -e "/data/webapps/lessie-email/logs/lessie_email.log" \ + --restart-delay 5000 \ + -- run uvicorn app.main:app --host 0.0.0.0 --port 8031 + + # 3. 保存当前列表,确保服务器重启后能自动恢复这些进程 + pm2 save ' """ } diff --git a/jenkins/流水线配置/lessie-list-test-web.conf b/jenkins/流水线配置/lessie-list-test-web.conf new file mode 100644 index 0000000..42aa5e2 --- /dev/null +++ b/jenkins/流水线配置/lessie-list-test-web.conf @@ -0,0 +1,65 @@ +pipeline { + agent any + + parameters { + gitParameter( + branchFilter: 'origin/(.*)', + defaultValue: 'main', + name: 'GIT_BRANCH', + type: 'PT_BRANCH_TAG', + selectedValue: 'DEFAULT', + sortMode: 'NONE', + description: '选择代码分支: ', + quickFilterEnabled: true, + tagFilter: '*', + listSize: "5" + ) + } + + environment { + REMOTE_HOST = '192.168.70.15' + REMOTE_PROJECT_PATH = '/data/webapps/lessie-list' + } + + stages { + stage('Checkout 代码') { + steps { + git branch: "${params.GIT_BRANCH}", credentialsId: 'fly_gitlab_auth', url: 'http://172.24.16.20/web/lessie-list.git' + } + } + + stage('同步') { + steps { + sh """ + rsync -avz --delete --exclude='node_modules' ${WORKSPACE}/ ${REMOTE_HOST}:${REMOTE_PROJECT_PATH}/ + """ + } + } + + stage('安装启动') { + steps { + sh """ + ssh ${REMOTE_HOST} ' + cd ${REMOTE_PROJECT_PATH} && + pm2 delete lessie-list || true && + pm2 list && + nvm use 22.21.1 && + npm install && + npm run build && + pm2 start ecosystem.config.cjs --env test && + pm2 save + ' + """ + } + } + } + + post { + success { + echo '部署成功' + } + failure { + echo '部署失败,请检查日志' + } + } +} diff --git a/jenkins/流水线配置/lessie-next-prod-web copy.conf b/jenkins/流水线配置/lessie-next-prod-web.conf similarity index 100% rename from jenkins/流水线配置/lessie-next-prod-web copy.conf rename to jenkins/流水线配置/lessie-next-prod-web.conf diff --git a/jenkins/流水线配置/systemd守护进程/lessie-email.service b/jenkins/流水线配置/systemd守护进程/lessie-email.service new file mode 100644 index 0000000..b213562 --- /dev/null +++ b/jenkins/流水线配置/systemd守护进程/lessie-email.service @@ -0,0 +1,19 @@ +# /etc/systemd/system/lessie-email.service + +[Unit] +Description=Lessie Email Service +After=network.target + +[Service] +Type=simple +User=root +Group=root +WorkingDirectory=/data/webapps/lessie-email +# 使用虚拟环境中的完整路径 +Environment="ENV=production" +ExecStart=/data/webapps/lessie-email/ .venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8031 --log-config logging_config.json +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/nginx/admin.scalelink.cn.conf b/nginx/admin.scalelink.cn.conf new file mode 100644 index 0000000..21fd891 --- /dev/null +++ b/nginx/admin.scalelink.cn.conf @@ -0,0 +1,75 @@ +server { + listen 443 ssl; + server_name admin.scalelink.cn; + ssl_certificate /data/tengine/conf/certificate/admin.scalelink.cn_bundle.crt; + ssl_certificate_key /data/tengine/conf/certificate/admin.scalelink.cn.key; + ssl_session_timeout 5m; + ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_prefer_server_ciphers on; + + # ========= 反误判 Header ========= + add_header X-Robots-Tag "noindex, nofollow, nosnippet" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "same-origin" always; + add_header X-Admin-System "Scalelink-Internal-Console" always; + add_header Server "Scalelink-Gateway" always; + + # ========= 阻断搜索引擎 ========= + location = /robots.txt { + default_type text/plain; + return 200 "User-agent: *\nDisallow: /\n"; + } + + # ========= 前端 admin 页面 ========= + location / { + root /data/tengine/html/fly_moon_web/dist; + index index.html index.htm; + try_files $uri $uri/ /index.html; + + add_header X-Robots-Tag "noindex, nofollow, nosnippet" always; + add_header X-Admin-System "Scalelink-Internal-Console" always; + add_header X-Frame-Options "DENY" always; + add_header Referrer-Policy "same-origin" always; + + # admin 页面不缓存(钓鱼站通常强缓存) + add_header Cache-Control "no-store, private"; + } + + location = /login { + limit_req zone=login_limit burst=5 nodelay; + try_files $uri $uri/ /index.html; + } + + # ========= API(原有逻辑) ========= + location ^~ /prod-api { + client_max_body_size 100m; + proxy_pass http://43.153.21.64:8080; + proxy_set_header Host 43.153.21.64; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_connect_timeout 3s; + proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504; + } + location /prod-api/monitor/job { + proxy_pass http://task_backend$uri; + 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; + proxy_redirect off; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root html; + } +} + +server { + listen 80; + server_name admin.scalelink.cn; + return 301 https://$host$request_uri; +} + \ No newline at end of file diff --git a/nginx/app.lessie.ai.conf b/nginx/app.lessie.ai.conf index 83536d8..bbdf494 100644 --- a/nginx/app.lessie.ai.conf +++ b/nginx/app.lessie.ai.conf @@ -1,237 +1,54 @@ upstream app_lessie_ai_backend { - #ip_hash; - server 10.0.0.12:7001 weight=10 max_fails=3 fail_timeout=30s; - server 10.0.0.7:7001 weight=10 max_fails=3 fail_timeout=30s; - server 10.0.0.11:7001 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; -} - -upstream go_backend { ip_hash; - #consistent_hash $remote_addr; - #hash $uri consistent; - server 10.0.0.10:8100 weight=10 max_fails=3 fail_timeout=30s; - server 10.0.0.8:8100 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; - #sticky cookie srv_id expires=1h domain=app.lessie.ai path=/; -} - -upstream java_agent_backend { - server 129.204.158.54:8070 weight=10 max_fails=3 fail_timeout=30s; - server 43.138.204.95:8070 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; -} - -log_format app_lessie_ai_log '客户端IP: $remote_addr | 用户: $remote_user | 时间: $time_local | ' - '请求方法和路径: "$request" | 状态码: $status | 响应大小: $body_bytes_sent | ' - '来源页面: "$http_referer" | 客户端UA: "$http_user_agent" | ' - '上游服务器: $upstream_addr | 上游响应耗时: $upstream_response_time | ' - '请求总耗时: $request_time | Host: $host'; - - -server { - listen 443 ssl; - server_name app.lessie.ai; - - ssl_certificate /data/tengine/certificate/lessie.ai.pem; - ssl_certificate_key /data/tengine/certificate/lessie.ai.key; - - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers HIGH:!aNULL:!MD5; - - # 单独日志文件 - access_log /data/tengine/logs/app_lessie_ai_access.log app_lessie_ai_log; - error_log /data/tengine/logs/app_lessie_ai_error.log; - - # 前端静态文件 - location / { - root /data/tengine/html/app.lessie_ai_agent/dist/; - index index.html; - try_files $uri $uri/ /index.html; - } - - # 精确匹配 index.html,禁用缓存 - location = /index.html { - root /data/tengine/html/app.lessie_ai_agent/dist/; - add_header Cache-Control "no-cache, no-store, must-revalidate"; - } - # 静态资源开启长缓存(带 hash) - location ~* \.(js|css|woff2|json|svg|png|jpg|jpeg|gif|ico|ttf|otf|eot|mp4|webm|webp)$ { - root /data/tengine/html/app.lessie_ai_agent/dist/; - add_header Cache-Control "public, max-age=31536000, immutable"; - } - - - # go中转服务 - #location ~ ^/(debug/pprof|api/chat/v1/stream|api/conversation/v1|api/conversation/conversation_name/v1|api/share|api/showcase|api/searches) { - #location ~ ^/(debug/pprof|api/chat/v1/stream|api/conversation/|api/share|api/showcase|api/searches) { - location ~ ^/(debug/pprof|api/chat/v1/stream|api/conversation/|api/shares|api/showcases|api/searches) { - proxy_pass http://go_backend; - 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; - - proxy_buffering off; - proxy_cache off; - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_request_buffering off; - - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; - - proxy_read_timeout 2000s; - proxy_send_timeout 2000s; - - if ($request_method = OPTIONS ) { - return 204; - } - } - - - - # python对话接口 - location /api/chat/stream { - proxy_pass http://app_lessie_ai_backend; - 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; - proxy_buffering off; - proxy_cache off; - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_request_buffering off; - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; - # 增加客户端到Nginx的连接超时时间 - proxy_read_timeout 1000s; - proxy_send_timeout 1000s; - if ($request_method = OPTIONS ) { - return 204; - } - } - - # python其它接口 - location /api/ { - proxy_pass http://app_lessie_ai_backend; - 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; - proxy_buffering off; - proxy_cache off; - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_request_buffering off; - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; - if ($request_method = OPTIONS ) { - return 204; - } - } - - # 打到国内prod的agent.jar包 - location /prod-api/agent/ { - proxy_pass http://java_agent_backend; - proxy_set_header Host 129.204.158.54; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - - proxy_intercept_errors off; - proxy_buffering off; - proxy_cache off; - proxy_set_header Connection keep-alive; - - client_max_body_size 300M; - - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; - - if ($request_method = OPTIONS ) { - return 204; - } - } - # 打到国内prod的agent.jar包 - location /prod-api/system { - proxy_pass http://java_agent_backend; - proxy_set_header Host 129.204.158.54; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_intercept_errors off; - proxy_buffering off; - proxy_cache off; - proxy_set_header Connection keep-alive; - client_max_body_size 300M; - add_header 'Access-Control-Allow-Origin' "$http_origin" always; - add_header 'Access-Control-Allow-Credentials' 'true' always; - add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; - add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; - if ($request_method = OPTIONS ) { - return 204; - } - } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root html; - } -} - -server { - listen 80; - server_name app.lessie.ai; - return 301 https://$host$request_uri; -} - - - - - - - - - -# ===========全硅谷区=============== -upstream app_lessie_ai_backend { - #ip_hash; server 10.0.0.12:7001 weight=10 max_fails=3 fail_timeout=30s; server 10.0.0.7:7001 weight=10 max_fails=3 fail_timeout=30s; server 10.0.0.11:7001 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; + server 10.0.0.2:7001 weight=10 max_fails=3 fail_timeout=30s; + server 10.0.0.13:7001 weight=10 max_fails=3 fail_timeout=30s; } upstream go_backend { ip_hash; server 10.0.0.10:8100 weight=10 max_fails=3 fail_timeout=30s; server 10.0.0.8:8100 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; } upstream java_agent_backend { server 10.0.0.10:8070 weight=10 max_fails=3 fail_timeout=30s; server 10.0.0.8:8070 weight=10 max_fails=3 fail_timeout=30s; - keepalive 128; } +upstream lessie_email_backend { + server 10.0.0.8:8031; +} + + log_format app_lessie_ai_log '客户端IP: $remote_addr | 用户: $remote_user | 时间: $time_local | ' '请求方法和路径: "$request" | 状态码: $status | 响应大小: $body_bytes_sent | ' '来源页面: "$http_referer" | 客户端UA: "$http_user_agent" | ' '上游服务器: $upstream_addr | 上游响应耗时: $upstream_response_time | ' '请求总耗时: $request_time | Host: $host'; +map $request_uri $uri_no_args { + "~^([^?]*)" $1; +} + +map $uri_no_args $has_bad_percent { + "~*%(2f|5c|00|2e|20|09)" 1; + default 0; +} + server { listen 443 ssl; server_name app.lessie.ai; + server_tokens off; + + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + add_header X-Content-Type-Options "nosniff" always; + + if ($has_bad_percent) { return 403; } + if ($request_method ~* (TRACE|TRACK)) { return 405; } ssl_certificate /data/tengine/certificate/lessie.ai.pem; ssl_certificate_key /data/tengine/certificate/lessie.ai.key; @@ -248,26 +65,34 @@ server { root /data/tengine/html/app.lessie_ai_agent/dist/; index index.html; try_files $uri $uri/ /index.html; + if ($request_method !~ ^(GET|HEAD)$) { return 405; } } # 精确匹配 index.html,禁用缓存 location = /index.html { root /data/tengine/html/app.lessie_ai_agent/dist/; add_header Cache-Control "no-cache, no-store, must-revalidate"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + add_header X-Content-Type-Options "nosniff" always; + if ($request_method !~ ^(GET|HEAD)$) { return 405; } } # 静态资源开启长缓存(带 hash) location ~* \.(js|css|woff2|json|svg|png|jpg|jpeg|gif|ico|ttf|otf|eot|mp4|webm|webp)$ { root /data/tengine/html/app.lessie_ai_agent/dist/; add_header Cache-Control "public, max-age=31536000, immutable"; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + add_header X-Content-Type-Options "nosniff" always; + if ($request_method !~ ^(GET|HEAD)$) { return 405; } } - # go中转服务 location ~ ^/(debug/pprof|api/chat|api/conversation|api/shares|api/showcases|api/searches) { proxy_pass http://go_backend; 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-For $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_buffering off; @@ -280,6 +105,7 @@ server { add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + add_header 'X-Content-Type-Options' 'nosniff' always; proxy_read_timeout 2000s; proxy_send_timeout 2000s; @@ -289,7 +115,6 @@ server { } } - # python对话接口 location /api/chat/stream { proxy_pass http://app_lessie_ai_backend; @@ -306,6 +131,7 @@ server { add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + add_header 'X-Content-Type-Options' 'nosniff' always; # 增加客户端到Nginx的连接超时时间 proxy_read_timeout 1000s; proxy_send_timeout 1000s; @@ -330,6 +156,7 @@ server { add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + add_header 'X-Content-Type-Options' 'nosniff' always; if ($request_method = OPTIONS ) { return 204; } @@ -338,7 +165,7 @@ server { # 打到国内prod的agent.jar包 location /prod-api/agent/ { proxy_pass http://java_agent_backend; - proxy_set_header Host 129.204.158.54; + proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -353,6 +180,7 @@ server { add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + add_header 'X-Content-Type-Options' 'nosniff' always; if ($request_method = OPTIONS ) { return 204; @@ -361,7 +189,7 @@ server { # 打到国内prod的agent.jar包 location /prod-api/system { proxy_pass http://java_agent_backend; - proxy_set_header Host 129.204.158.54; + 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_intercept_errors off; @@ -373,11 +201,39 @@ server { add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + add_header 'X-Content-Type-Options' 'nosniff' always; if ($request_method = OPTIONS ) { return 204; } } + # lessis-email 的 api 的代理配置 + location /email-api/ { + proxy_pass http://lessie_email_backend; + 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; + + client_max_body_size 50m; + + proxy_buffering off; + proxy_cache off; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_request_buffering off; + + add_header 'Access-Control-Allow-Origin' "$http_origin" always; + add_header 'Access-Control-Allow-Credentials' 'true' always; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; + add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,X-Requested-With,Accept,Origin' always; + + if ($request_method = OPTIONS ) { + return 204; + } + } + + error_page 500 502 503 504 /50x.html; location = /50x.html { root html; @@ -387,6 +243,10 @@ server { server { listen 80; server_name app.lessie.ai; + server_tokens off; + add_header X-Frame-Options "SAMEORIGIN" always; + add_header Content-Security-Policy "frame-ancestors 'self'" always; + add_header X-Content-Type-Options "nosniff" always; + if ($request_method ~* (TRACE|TRACK)) { return 405; } return 301 https://$host$request_uri; -} -# ===========全硅谷区=============== \ No newline at end of file +} \ No newline at end of file