最近在梳理公司的k8s服务和对应数据库连接关系,由于服务太多开发团队也不完全清楚有哪些,于时我决定从服务的配置文件去分析先拉出服务
名称和对应数据库连接地址,同时尝试写成服务的形式监控git上的配置文件路径,若有新的服务自动从中解析出相关数据库连接信息;
服务采用go编写,而本次分享则是服务核心,对不定长yaml文件的解析思路:
环境:
在config目录下有许多服务配置文件目录,在配置文件的目录下会有yaml配置文件,由于负责发布的人员不同,yaml文件的形式也不一定相同
比如:有的人喜欢多个配置文件写在一个yaml里的,有的人会写多个yaml;
同时,由于服务有的不需要连接数据库,所以有的yaml文件没有数据库连接信息;
第三方解析包:
gopkg.in/yaml.v2
yaml.v2是目前git上比较火的go语言解析yaml的第三方包,他的解析yaml主要是使用Unmarshal方法,具体后面再讨论
解题思路:
yaml文件的基本配置有:
apiVersion
metadata
kind
以上的三个几乎是所有的yaml文件都必带的,spec标签则有的可以代有的不需要,仔细研读服务的配置文件,
发现配置文件中有数据库连接信息的,都是带有以下形式的标签:
data:
application.properties:
所有的服务name都在metadata下面,所以name比较好取,不好取的是data下边的数据库连接信息,由于公司有.net/java/python
三种服务在k8s里面跑,连接数据库的方式也不一样,有的会在application标签下有的则不是,但统一都会在data标签下;
那么解析数据库连接地址时,需要解析data,无法确定data标签的内层结构时,需要做通配符正则匹配;
如果不用第三方包,直接用正则处理也是可以的,但是无法得到yaml的精准层次结构,所以本次使用第三方包;
go yaml.v2包:
yaml.v2是一个可以构建或者解析yaml文件的包;Marshal是构建yaml,Unmarshal是解析yaml,构建yaml需要任意类型参数,
一般都已struct形式构建好有层次结构的参数,而解析需要入参byte类型的yaml数据,同时返回出一个map;
所以解析yaml的时候,需要给Unmarshal一个map参数让他存储解析返回值;注意,这个map的类型可以是interface类型也可以是
自定义的struct类型或者常规string类型
源码:
package main
import (
"bytes"
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"regexp"
"strings"
)
//声明一个全局存储解析数据库连接配置文件的slice
var configlist []string
//yaml配置文件路径
var path string = "C:\\Users\\lovecat\\Desktop\\configs"
//数据库连接匹配的配置文件路径
var config_path string = "C:\\Users\\lovecat\\Desktop\\configs\\readpath_config.txt"
//声明所需解析yaml的struct,注意namespace和spec其实举例的这个配置yaml配置文件根本没有
//但是并不妨碍我声明一个他的结构,因为有的配置文件有这些,如果我需要namespace,那么我就可以调用,没有namespace的配置文件
//只是返回空值而已,并不会报错
type name struct{
Name string `yaml:"name"`
Namespace string `yaml:"namespace"`
}
type yl struct{
Metadata name `yaml:"metadata"`
//由于我并不知道data和spec下面的层级结构,但是go-yaml每层返回的都是map,所以直接定义一个
//map结构的类型来承接,如果定义成map string,那么返回的map中value会全部被转成string类型
//string类型正则比较好处理,如果想保留原map,只需要定义成map interface{}类型,该map则会
//保留解析出的原格式,但是实际层级提取不好处理,建议少用interface类型
Data map[string]string `yaml:"data"`
Spec map[string]string `yaml:"spec"`
}
func main() {
var data,new_data []string
d,_ :=Dir_read(path,data)
for _,v := range d{
d,_ = Dir_read(v,d)
}
for _,v1 := range d{
getpath,re := Data_format(v1,"-")
if re{
new_data = append(new_data,getpath)
}
}
read_config(config_path)
//以下循环是遍历所有yaml文件夹下面的yaml,所以省略掉,本次以指定yaml举例
//for _,v2 := range new_data{
// read_yaml(v2)
//}
//举例的指定yaml配置文件
read_yaml("C:\\Users\\lovecat\\Desktop\\configs\\CollectService\\CollectService-D")
}
//入参:地址路径,文件地址分析,不是重点
func Dir_read(indata string,data []string) ([]string,error){
dataname,err := ioutil.ReadDir(indata)
if err != nil {
return data,err
} else{
for _,v := range dataname{
if v.IsDir(){
new_path := indata+"\\"+v.Name()
data = append(data,new_path)
}else{
_,bo := Data_format(v.Name(),".")
if bo{
data = append(data,indata)
}
}
}
return data,err
}
}
//入参:文件名,切分字符 出参:文件指针,是否为指定文件
//格式化配置文件夹路径下的文件,由于配置文件夹下有多种文件,所以我这里要解析一次,不是yaml解析的重点
func Data_format(indata,sptype string) (string,bool){
datalist := strings.Split(indata,sptype)
fi := datalist[len(datalist)-1]
switch sptype {
case "-":
if strings.ToUpper(fi) == "D"{
return indata,true
}else {
return fi, false
}
case ".":
if strings.ToUpper(fi) == "YAML"{
return indata,true
}else {
return indata, false
}
case "\\":
return fi,true
default:
return fi,false
}
}
//读取配置文件,读取的是数据库匹配规则配置文件
func read_config(inpath string) ([]string,error){
file,err := os.Open(inpath)
if err != nil{
return configlist,err
}
defer file.Close()
con,err := ioutil.ReadAll(file)
b := strings.Split(string(con),",\r\n")
for _,v := range b{
configlist = append(configlist,v)
}
return configlist,err
}
//读取yaml文件,解析yaml文件
func read_yaml(inpath string) {
//yaml文件换成列表声明
var filelist []string
//获取入参路径下的文件并判断是yaml文件则该文件路径添加到yaml列表中
dataname,err := ioutil.ReadDir(inpath)
if err != nil {
}
for _,v:= range dataname{
_,b :=Data_format(v.Name(),".")
if b {
file := inpath+"\\"+v.Name()
filelist = append(filelist,file)
}
}
//遍历yaml列表中yaml文件并且获取指定数据匹配
for _,f := range filelist{
file,err := os.Open(f)
if err != nil{
}
defer file.Close()
//读取yaml文件数据
yaml_con,err := ioutil.ReadAll(file)
//切分yaml文件为独立文件
yaml_split := []byte("---")
con1 := bytes.Split(yaml_con,yaml_split)
//遍历独立apiversion
for _,yl_split := range con1{
//新建换成yaml数据的map,te可以构建成make(map[interface]interface{})或者make(map[string]interface{}) 或者struct
//建议解析yaml统一构建成struct,如果不定的标签,再struct中把他定义成interface{}的map或者string类型的map即可,若配置文件没有该
//标签,则该标签返回为空指针,并不会报错,简单的来说多定义struct参数,多的包含少的,即使yaml配置文件没有该参数也不会报错,如果定义少了
//没有数据存储,解析出来的文件反而使用更加不便
//例:te := make(map[string]interface{})/te := make(map[interface{}]interface{})/建议少使用interface{}类型
var te yl
err = yaml.Unmarshal(yl_split,&te)
//遍历匹配的配置文件
for _,v :=range configlist{
ok,_ := regexp.Match(v,yl_split)
if ok{
//解析yaml
//err = yaml.Unmarshal(yl_split,&te)
for _,v1 := range te.Data{
//构建正则规则,匹配mysql和对应具体的数据库
var ex_db_url string = v+"/.*\\?"
db_url := regexp.MustCompile(ex_db_url).FindString(v1)
//如果为空字符则连接的是oracle,没有具体的数据库指向
if db_url == "" {
ex_db_url = v
db_url = regexp.MustCompile(ex_db_url).FindString(v1)
}
//判断连接归属
var db_type string
//以下已屏蔽链接地址
switch v {
case "A":
db_type = "阿里云RDS"
case "B":
db_type = "阿里云RDS"
case "C":
db_type = "阿里云RM"
case "D":
db_type = "阿里云RM"
case "E":
db_type = "北艾NDB"
case "F":
db_type = "北艾8.0"
case "G":
db_type = "北艾5.7"
case "H":
db_type = "Oracle Master"
case "I":
db_type = "Oracle Slave"
default:
db_type = "未配置"
}
fmt.Println("服务名称:"+te.Metadata.Name+",","数据库连接地址:"+db_url+",","数据库实例所属:"+db_type+",","配置文件路径:"+inpath)
}
}
}
}
}
}
数据库配置文件,信息已处理过,只代表格式:
rm-kgkytukll.mysql.rds.aliyuncs.com:3306,
rm-dhdhfjghk.mysql.rds.aliyuncs.com:3306,
adagfhfhjg.mysql.rds.aliyuncs.com:3306,
sddhthjjgsg.mysql.rds.aliyuncs.com:3306,
172.40.51.22:3306,
172.40.51.31:3310,
172.40.51.31:3311,
172.40.51.50:1521,
172.40.51.51:1521
yaml配置文件,数据已修改,只代表格式:
apiVersion: v1
kind: ConfigMap
metadata:
name: collectservice
data:
application.properties: |
spring.application.name=CollectService
server.port=80
spring.datasource.mysql.url=jdbc:mysql://172.40.51.31:3310/collectdb?useUnicode=&=UTF-8&=&=
spring.datasource.mysql.cryptid=collectservice
---
apiVersion: v1
kind: Service
metadata:
name: collectservice
spec:
selector:
app: collectservice
ports:
- name: http
port: 80
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: collectservice
labels:
app: collectservice
spec:
selector:
matchLabels:
app: collectservice
replicas: 1
template:
metadata:
labels:
app: collectservice
spec:
containers:
- name: collectservice
image: 172.98.35.26:5000/collectservice:54657696
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /keeplive
port: 80
failureThreshold: 3
initialDelaySeconds: 30
periodSeconds: 10
successThreshold: 1
timeoutSeconds: 3
readinessProbe:
httpGet:
path: /keeplive
port: 80
initialDelaySeconds: 20
periodSeconds: 10
timeoutSeconds: 5
resources:
requests:
memory: 1Gi
limits:
memory: 4Gi
volumeMounts:
- name: config
mountPath: /application.properties
subPath: application.properties
volumes:
- name: config
configMap:
name: collectservice
输出结果:
GOROOT=C:\Go #gosetup
GOPATH=C:\Users\lovecat\go;C:\Users\lovecat\Desktop\go\golang #gosetup
C:\Go\bin\go.exe build -o C:\Users\lovecat\AppData\Local\Temp\___go_build_main_go.exe C:/Users/lovecat/OneDrive/MyProgram/ServerProgram/GoProgram/Hires.com/main.go #gosetup
C:\Users\lovecat\AppData\Local\Temp\___go_build_main_go.exe #gosetup
服务名称:collectservice, 数据库连接地址:172.40.51.31:3310/collectdb?, 数据库实例所属:未配置, 配置文件路径:C:\Users\lovecat\Desktop\configs\CollectService\CollectService-D
Process finished with exit code 0
总结:
很明显,这个yaml写了三个pod的配置文件,所以规则中我是写了切分之后再循环解析的,对于yaml的不定长解析方式,
建议构建struct并尽量多构建几个已知的结构,对于不确定的结构,构建成map[string]interface{}或者map[interface{}]interface{}来存储即可,interface{}的断言不是很好处理,建议能不用就不要用.