diff --git a/CHANGELOG.md b/CHANGELOG.md index fd0002b..1902a26 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +# v0.5.0 + +新特性(Features) + +* 支持配置nginx日志(access_log) + +优化 + +* 健康检查接口:检查nginx配置是否正确 + # v0.4.0 新特性(Features) diff --git a/api/v1/handle.go b/api/v1/handle.go index 26a426e..d2bf0c5 100644 --- a/api/v1/handle.go +++ b/api/v1/handle.go @@ -2,6 +2,7 @@ package v1 import ( "github.com/gin-gonic/gin" + "openfly/common" ) type Res struct { @@ -11,5 +12,16 @@ type Res struct { } func Health(c *gin.Context) { - c.String(200, "alive") + _, gerr := common.GNginx.Test() + if gerr != nil { + c.JSON(200, Res{ + Code: 1, + Msg: gerr.Error(), + }) + return + } + c.JSON(200, Res{ + Code: 0, + Msg: "healthy", + }) } diff --git a/common/datastruct.go b/common/datastruct.go index 194a8cf..7f25714 100644 --- a/common/datastruct.go +++ b/common/datastruct.go @@ -9,6 +9,8 @@ type NginxConfL4 struct { IncludeFiles []string `json:"includeFiles,omitempty"` WhiteList []WhiteListItem `json:"whiteList,omitempty"` Comments []string `json:"comments,omitempty"` + // 日志 + Log NginxLog `json:"log,omitempty"` // 限速 ProxyUploadRate string `json:"proxyUploadRate,omitempty"` ProxyDownloadRate string `json:"proxyDownloadRate,omitempty"` @@ -41,3 +43,10 @@ type UpstreamHost struct { FailTimeoutSecond int `json:"failTimeoutSecond,omitempty"` IsBackup bool `json:"isBackup,omitempty"` } +type NginxLog struct { + Mod string `json:"mod,omitempty"` // off:不打印日志;local:单独打印日志;global/其他:继承全局配置。 + FormatName string `json:"formatName,omitempty"` + Path string `json:"path,omitempty"` + Buffer string `json:"buffer,omitempty"` + Flush string `json:"flush,omitempty"` +} diff --git a/common/nginx.go b/common/nginx.go index ba63a3f..bde1a1a 100644 --- a/common/nginx.go +++ b/common/nginx.go @@ -78,6 +78,17 @@ func (n *Nginx) CheckConfigL4(l4s []NginxConfL4) *gerror.Gerr { if gerr != nil { return gerr } + // 参数校验:日志 + reLogBuffer := "^[0-9]+[km]?$" + regular = regexp.MustCompile(reLogBuffer) + if l4.Log.Buffer != "" && !regular.MatchString(l4.Log.Buffer) { + return gerror.NewErr(fmt.Sprintf("invalid buffer: %s,Expected: %s", l4.Log.Buffer, reLogBuffer)) + } + reLogFlush := "^[0-9]+[sm]?$" + regular = regexp.MustCompile(reLogFlush) + if l4.Log.Flush != "" && !regular.MatchString(l4.Log.Flush) { + return gerror.NewErr(fmt.Sprintf("invalid flush: %s,Expected: %s", l4.Log.Flush, reLogFlush)) + } } return nil } @@ -102,6 +113,11 @@ func (n *Nginx) GenConfigL4(l4 NginxConfL4) (string, *gerror.Gerr) { confUpstream := n.genConfigL4Upstream(l4.Upstream, l4.Listen) // 生成server语句 confServer = append(confServer, fmt.Sprintf("listen %d;", l4.Listen)) + // 生成access_log语句 + confLog := n.genConfigL4Log(l4) + if confLog != "" { + confServer = append(confServer, confLog) + } // 生成proxy_pass语句 confServer = append(confServer, fmt.Sprintf("proxy_pass %d;", l4.Listen)) // 限速 @@ -136,6 +152,35 @@ func (n *Nginx) GenConfigL4(l4 NginxConfL4) (string, *gerror.Gerr) { } return fmt.Sprintf("%s\n%s\nserver {\n\t%s\n}", commentStr, confUpstream, strings.Join(confServer, "\n\t")), nil } +func (n *Nginx) genConfigL4Log(l4 NginxConfL4) string { + if l4.Log.Mod == "local" { + // 日志路径 + if l4.Log.Path == "" { + l4.Log.Path = filepath.Join(conf.GConf.L4.Log.Path, fmt.Sprintf("%d.stream.log", l4.Listen)) + } + ret := fmt.Sprintf("access_log %s", l4.Log.Path) + // 日志格式 + if l4.Log.FormatName == "" { + l4.Log.FormatName = conf.GConf.L4.Log.FormatNameDefault + } + if l4.Log.FormatName != "" { + ret = ret + " " + l4.Log.FormatName + } + // buffer + if l4.Log.Buffer != "" { + ret = ret + " " + fmt.Sprintf("buffer=%s", l4.Log.Buffer) + } + // flush + if l4.Log.Flush != "" { + ret = ret + " " + fmt.Sprintf("flush=%s", l4.Log.Flush) + } + return ret + ";" + } else if l4.Log.Mod == "off" { + return "access_log off;" + } else { + return "" + } +} func (n *Nginx) genConfigL4Upstream(upstream Upstream, port int) string { var confUpstream []string // 生成upstream中的server语句 diff --git a/common/nginx_test.go b/common/nginx_test.go index 1e568bd..a8ded57 100644 --- a/common/nginx_test.go +++ b/common/nginx_test.go @@ -61,6 +61,13 @@ func TestNginx_GenConfigL4(t *testing.T) { ProxyUploadRate: "20M", ProxyConnectTimeout: "1s", ProxyTimeout: "2m", + Log: NginxLog{ + Mod: "local", + FormatName: "main", + Path: "", + Buffer: "5k", + Flush: "5s", + }, WhiteList: []WhiteListItem{ { conf.Allow, @@ -163,3 +170,54 @@ func TestNginx_CheckConfigL4(t *testing.T) { fmt.Println(gerr) } } + +func TestNginx_genConfigL4Log(t *testing.T) { + l4 := NginxConfL4{Listen: 8081} + // 关闭日志 + l4.Log = NginxLog{ + Mod: "off", + FormatName: "", + Path: "", + Buffer: "", + Flush: "", + } + conf := GNginx.genConfigL4Log(l4) + fmt.Println(conf) + // 输出空 + l4.Log = NginxLog{ + Mod: "global", + } + conf = GNginx.genConfigL4Log(l4) + fmt.Println(conf) + // 输出空 + l4.Log = NginxLog{ + Mod: "hosydfoshdfw0efwef", + } + conf = GNginx.genConfigL4Log(l4) + fmt.Println(conf) + // 自动生成日志路径 + l4.Log = NginxLog{ + Mod: "local", + Path: "", + } + conf = GNginx.genConfigL4Log(l4) + fmt.Println(conf) + // 指定日志路径和log_format + l4.Log = NginxLog{ + Mod: "local", + Path: "/var/log/nginx/test.log", + FormatName: "test_log", + } + conf = GNginx.genConfigL4Log(l4) + fmt.Println(conf) + // 指定buffer和flush + l4.Log = NginxLog{ + Mod: "local", + Path: "/var/log/nginx/test.log", + FormatName: "test_log", + Buffer: "5k", + Flush: "5s", + } + conf = GNginx.genConfigL4Log(l4) + fmt.Println(conf) +} diff --git a/conf/conf.go b/conf/conf.go index 87e648f..7eba2d5 100644 --- a/conf/conf.go +++ b/conf/conf.go @@ -8,9 +8,10 @@ import ( ) var ( - GConf ConfigFileStruct - PathData = "./data" - DefaultPathBak = "./bak" + GConf ConfigFileStruct + PathData = "./data" + DefaultPathBak = "./bak" + DefaultPathL4Log = "/var/log/nginx/l4" ) // ConfigFileStruct 配置文件结构体 @@ -41,6 +42,12 @@ type ConfigFileStruct struct { MaxFailsDefault int `toml:"maxFailsDefault"` FailTimeoutDefault int `toml:"failTimeoutDefault"` } `toml:"nginx"` + L4 struct { + Log struct { + Path string `toml:"path"` + FormatNameDefault string `toml:"formatNameDefault"` + } `toml:"log"` + } `toml:"l4"` } // ParseConfig 解析配置文件 @@ -75,4 +82,12 @@ func CheckAndInit() { if err != nil { log.Fatal(err) } + // 创建L4日志目录 + if GConf.L4.Log.Path == "" { + GConf.L4.Log.Path = DefaultPathL4Log + } + err = os.MkdirAll(GConf.L4.Log.Path, 0770) + if err != nil { + log.Fatal(err) + } } diff --git a/conf/const.go b/conf/const.go index 85d1359..4e99ea6 100644 --- a/conf/const.go +++ b/conf/const.go @@ -1,7 +1,7 @@ package conf var ( - Version = "v0.4.0" + Version = "v0.5.0" NgFileExtension = ".conf" EtcdSubPathL4 = "/l4" ) diff --git a/config.toml b/config.toml index 330d72b..1dced8a 100644 --- a/config.toml +++ b/config.toml @@ -22,4 +22,9 @@ prefix="/openfly" [nginx] weightDefault=10 maxFailsDefault=99999 -failTimeoutDefault=10 \ No newline at end of file +failTimeoutDefault=10 + +[l4.log] +# Example: "/var/log/nginx/l4" +path="/var/log/nginx/l4" +formatNameDefault="main" \ No newline at end of file diff --git a/docs/v0.5.0/api.md b/docs/v0.5.0/api.md new file mode 100644 index 0000000..602704e --- /dev/null +++ b/docs/v0.5.0/api.md @@ -0,0 +1,105 @@ +# 健康检查 + +```shell +curl http://127.0.0.1:1216/v1/health +``` + +# 鉴权 + +```shell +# 获取Token +token=$(curl -s -XPOST http://127.0.0.1:1216/v1/login -d "{\"username\":\"admin\",\"password\":\"admin\"}" -H "Content-Type: application/json" | jq -r .data) +echo ${token} +# 访问需要鉴权的接口 +curl -H "Authorization: ${token}" http://127.0.0.1:1216/xxx +``` + +# Nginx相关API + +```shell +# 删除配置 +curl -XDELETE -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/delete?listen=30001 +# 禁用指定端口的配置(启用为on,禁用为off) +curl -XPOST -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/switch -d "listen=30001&switch=off" +# 获取指定端口的配置 +curl -s -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/get?listen=30001 +# 获取所有配置 +curl -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/getAll +# 新增L4配置 +curl -i -H "Content-Type: application/json" -XPOST -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/add -d ' +{ + "listen":30001, + "upstream":{ + "hosts":[ + { + "ip":"1.1.1.1", + "port":53 + } + ] + } +}' +# 更新L4配置 +curl -i -H "Content-Type: application/json" -XPOST -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/set -d ' +{ + "listen":30001, + "upstream":{ + "hosts":[ + { + "ip":"1.1.1.1", + "port":53 + } + ] + } +}' +``` + +# 所有支持的参数 + +```shell +curl -i -H "Content-Type: application/json" -XPOST -H "Authorization: ${token}" http://127.0.0.1:1216/v1/admin/nginx/set -d ' +{ + "disable": false, + "listen": 30001, + "comments": ["我是注释","I am the comment."], + "includeFiles": ["/etc/nginx/my.conf"], + "proxyUploadRate": "10M", + "proxyDownloadRate": "20M", + "proxyConnectTimeout": "10s", + "proxyTimeout": "10m", + "log": { // 默认值:继承全局配置 + "mod": "local", // off:不打印日志;local:单独打印日志;global/其他:继承全局配置。 + "path": "/var/log/nginx/test.stream.log", + "formatName": "my_log_format", + "buffer": "5k", + "flush": "5s" + }, + "upstream":{ + "hosts":[ + { + "ip": "1.1.1.1", + "port": 53, + "isBackup": false, + "weight": 100, + "maxFails": 10, + "failTimeoutSecond":2 + } + ], + "isHash": true, + "hashField": "test_field", + "interval": 10, + "rise": 2, + "fall": 2, + "timeout": 1000 + }, + "whiteList": [ + { + "type": "allow", + "target": "1.1.1.1" + }, + { + "type": "deny", + "target": "all" + } + ] +}' +``` \ No newline at end of file diff --git a/docs/v0.5.0/dev.md b/docs/v0.5.0/dev.md new file mode 100644 index 0000000..fe56222 --- /dev/null +++ b/docs/v0.5.0/dev.md @@ -0,0 +1,6 @@ +# 一些测试case + +```shell +# 写入有问题的配置 +etcdctl put /openfly/l4/30001 '{"listen":30001,"upstream":{"hosts":[{"ip":"","port":53}]}}' +``` \ No newline at end of file diff --git a/docs/v0.5.0/faq.md b/docs/v0.5.0/faq.md new file mode 100644 index 0000000..a0335e1 --- /dev/null +++ b/docs/v0.5.0/faq.md @@ -0,0 +1,18 @@ +如何优雅退出 + +* 发送term(15号)给openfly进程,可以在INFO日志中查看到退出过程。示例:```kill `` + +若nginx因配置错误无法启动,如何解决? + +1. 保证nginx本身配置无错误。 +2. 通过openfly接口 修改/删除 错误配置。 +3. 通过etcd 修改/删除 错误配置。 + +添加重复接口会报错吗? + +* 会 + +若配置有问题,会导致线上问题吗? + +* 不会。 +* nginx -t失败后配置不生效。 \ No newline at end of file