封装自动生成 traefik 代理配置的 nodejs 插件代码
This commit is contained in:
1
docker/traefik/README.md
Normal file
1
docker/traefik/README.md
Normal file
@@ -0,0 +1 @@
|
||||
docker-compose down && docker-compose up -d
|
||||
@@ -20,5 +20,5 @@ services:
|
||||
working_dir: /app
|
||||
volumes:
|
||||
- ./:/app # 挂载当前目录到容器
|
||||
command: sh -c "npm install && node nacos-watcher.js"
|
||||
command: sh -c "npm install && node src/index.js"
|
||||
restart: always
|
||||
@@ -1,8 +1,8 @@
|
||||
http:
|
||||
routers:
|
||||
main-app-router:
|
||||
rule: PathPrefix(`/main-app`)
|
||||
service: main-app-service
|
||||
app-product-router:
|
||||
rule: PathPrefix(`/app-product`)
|
||||
service: app-product-service
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
@@ -10,9 +10,9 @@ http:
|
||||
- ipWhiteList
|
||||
- rateLimit
|
||||
- retries
|
||||
app-product-router:
|
||||
rule: PathPrefix(`/app-product`)
|
||||
service: app-product-service
|
||||
main-app-router:
|
||||
rule: PathPrefix(`/main-app`)
|
||||
service: main-app-service
|
||||
entryPoints:
|
||||
- web
|
||||
middlewares:
|
||||
@@ -31,14 +31,14 @@ http:
|
||||
- rateLimit
|
||||
- retries
|
||||
services:
|
||||
main-app-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://192.168.31.101:1300
|
||||
app-product-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://192.168.31.101:1301
|
||||
main-app-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: http://192.168.31.101:1300
|
||||
app-shared-service:
|
||||
loadBalancer:
|
||||
servers:
|
||||
|
||||
61
docker/traefik/src/config/index.js
Normal file
61
docker/traefik/src/config/index.js
Normal file
@@ -0,0 +1,61 @@
|
||||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
filePath: path.join(__dirname, '../../dynamic.yml'),
|
||||
nacosApi: {
|
||||
baseUrl: 'http://192.168.31.101:8848/nacos/v1/ns',
|
||||
apiList: {
|
||||
instanceInfo: '/instance/list',
|
||||
serviceList: '/service/list',
|
||||
}
|
||||
},
|
||||
nacosConfig: {
|
||||
nameSpace: 'public',
|
||||
},
|
||||
// 每隔五秒检查一下最新配置
|
||||
check_time: 10000,
|
||||
// traefik 通用中间件配置
|
||||
traefikConfig: {
|
||||
middlewaresStr: ['compress', 'ipWhiteList', 'rateLimit', 'retries'],
|
||||
middlewaresConfig: {
|
||||
// 限流中间件(独立配置,关键修复)
|
||||
rateLimit: {
|
||||
rateLimit: {
|
||||
average: 100,
|
||||
burst: 200,
|
||||
period: '1s',
|
||||
sourceCriterion: {
|
||||
ipStrategy: {depth: 2}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 压缩中间件
|
||||
compress: {
|
||||
compress: {
|
||||
excludedContentTypes: ['text/event-stream'],
|
||||
minResponseBodyBytes: 1024
|
||||
}
|
||||
},
|
||||
// IP白名单中间件(示例:允许本地和192.168.31网段)
|
||||
ipWhiteList: {
|
||||
ipWhiteList: {
|
||||
sourceRange: ['127.0.0.1/32', '192.168.31.0/24']
|
||||
}
|
||||
},
|
||||
// // 重定向中间件(HTTP→HTTPS)
|
||||
// redirectToHttps: {
|
||||
// redirectScheme: {
|
||||
// scheme: 'https',
|
||||
// permanent: true
|
||||
// }
|
||||
// },
|
||||
// 重试中间件(关键修复:将重试配置移到这里)
|
||||
retries: {
|
||||
retry: {
|
||||
attempts: 3,
|
||||
initialInterval: '200ms'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
docker/traefik/src/http/index.js
Normal file
38
docker/traefik/src/http/index.js
Normal file
@@ -0,0 +1,38 @@
|
||||
const axios = require('axios');
|
||||
const config = require('../config');
|
||||
const {AxiosError} = require("axios");
|
||||
|
||||
class Http {
|
||||
instance = null;
|
||||
|
||||
constructor() {
|
||||
this.instance = axios.create({
|
||||
baseURL: config.nacosApi.baseUrl,
|
||||
timeout: 10000,
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
})
|
||||
|
||||
this.ResponseInterceptor();
|
||||
}
|
||||
|
||||
|
||||
async get(params) {
|
||||
return await this.instance.get(params.url, {
|
||||
params: params.data,
|
||||
});
|
||||
}
|
||||
|
||||
async ResponseInterceptor() {
|
||||
this.instance.interceptors.response.use(
|
||||
(response) => {
|
||||
return response.data;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new Http
|
||||
9
docker/traefik/src/index.js
Normal file
9
docker/traefik/src/index.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const utils = require('./utils')
|
||||
const config = require('./config')
|
||||
|
||||
const uti = new utils();
|
||||
|
||||
setInterval(
|
||||
uti.getServiceName.bind(uti),
|
||||
config.check_time
|
||||
)
|
||||
109
docker/traefik/src/utils/index.js
Normal file
109
docker/traefik/src/utils/index.js
Normal file
@@ -0,0 +1,109 @@
|
||||
const config = require("../config")
|
||||
const http = require("../http")
|
||||
const yaml = require("js-yaml");
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
|
||||
class Utils {
|
||||
|
||||
// 存储 Nacos上 服务列表 的服务名称
|
||||
ServiceNameList = []
|
||||
|
||||
constructor() {
|
||||
this.getServiceName()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Nacos上的服务名称数组
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async getServiceName() {
|
||||
console.log("1: [getServiceName] 从Nacos 获取服务名..")
|
||||
const {doms} = await http.get({
|
||||
url: config.nacosApi.apiList.serviceList,
|
||||
data: {pageNo: 1, pageSize: 20},
|
||||
});
|
||||
if (doms.length === 0) {
|
||||
throw new Error("No service found for service list");
|
||||
}
|
||||
this.serviceName = doms;
|
||||
console.log("2: [getServiceName] 服务名为:", this.serviceName)
|
||||
await this.generateTraefikConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 Nacos 获取服务实例
|
||||
* @param serviceName
|
||||
* @returns {Promise<*|*[]>}
|
||||
*/
|
||||
async getServiceInstances(serviceName) {
|
||||
const {hosts} = await http.get({
|
||||
url: config.nacosApi.apiList.instanceInfo,
|
||||
data: {serviceName, namespaceId: config.nacosConfig.nameSpace},
|
||||
});
|
||||
return hosts || []
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 生成 traefik 动态配置
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async generateTraefikConfig() {
|
||||
const traefikConf = {
|
||||
http: {
|
||||
routers: {},
|
||||
services: {},
|
||||
middlewares: config.traefikConfig.middlewaresConfig,
|
||||
}
|
||||
};
|
||||
|
||||
for (const serviceName of this.serviceName) {
|
||||
const instances = await this.getServiceInstances(serviceName);
|
||||
if (instances.length === 0) {
|
||||
console.warn(`服务 ${serviceName} 无可用实例`);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 1. 配置服务(服务名 + 实例列表)
|
||||
traefikConf.http.services[`${serviceName}-service`] = {
|
||||
loadBalancer: {
|
||||
servers: instances.map(inst => ({
|
||||
url: `http://${inst.ip}:${inst.port}`,
|
||||
})),
|
||||
}
|
||||
};
|
||||
|
||||
// 2. 配置路由(路径前缀 + 绑定服务)
|
||||
traefikConf.http.routers[`${serviceName}-router`] = {
|
||||
rule: `PathPrefix(\`/${serviceName}\`)`, // 如 /main、/product
|
||||
service: `${serviceName}-service`,
|
||||
entryPoints: ['web'],
|
||||
// 应用中间件(压缩、IP白名单、限流、重试等)
|
||||
middlewares: config.traefikConfig.middlewaresStr,
|
||||
};
|
||||
}
|
||||
|
||||
this.stringToYaml(traefikConf)
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成 yaml 配置文件
|
||||
* @param conf
|
||||
*/
|
||||
stringToYaml(conf) {
|
||||
console.log("3: [stringToYaml] 生成配置:", JSON.stringify(conf, null, 2));
|
||||
console.log()
|
||||
console.log()
|
||||
|
||||
const yamlString = yaml.dump(conf, {
|
||||
indent: 2, // 缩进空格数
|
||||
skipInvalid: true, // 跳过无效类型
|
||||
noRefs: true // 禁止引用(防止循环引用问题)
|
||||
});
|
||||
fs.writeFileSync(config.filePath, yamlString, "utf8");
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Utils
|
||||
Reference in New Issue
Block a user