DevOps CI/CD 分析(四)之编写K8S yaml模版分析四中我们编写了K8S yaml模版文件,其中有很多
{{...}}
这种定义,所以本节我们的重点就是将K8S yaml模版通过Go模版库将{{...}}
这类定义替换成我们外部传入的参数,最终生成我们所需要的K8S yaml文件。
Go模版实现
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"html/template"
"io"
"log"
"os"
"strings"
)
//主入口函数
func main() {
var args, argsFiles, ymlFiles, out string
//解析命令行参数
flag.StringVar(&args, "args", "", "参数格式为`key=value;` 多个参数使用 `;` 分隔")
flag.StringVar(&argsFiles, "args-files", "", "文件参数格式为`*.properties` 多个参数使用 `;` 分隔")
flag.StringVar(&ymlFiles, "yml-files", "", "请输入需要替换的模版文件,多个文件以 `;` 分隔")
flag.StringVar(&out, "out", "---", "多个输出的分隔")
//开始解析命令行参数
flag.Parse()
//定义一个Map对象,key=字符串类型,值=任意类型
argsMap := make(map[string]interface{})
if args != "" {
//解析命令行中直接传递的`key=value`形式的参数
parseArgs(argsMap, args)
}
if argsFiles != "" {
//解析传递的参数文件,properties格式的文件
parseArgsFiles(argsMap, argsFiles)
}
//解析需要替换的模版文件, 多个文件以`;`分隔
template, error := template.ParseFiles(strings.Split(ymlFiles, ";")...)
if error != nil {
log.Printf("解析yml文件错误 error:%s", error)
os.Exit(-1)
}
//获取模版集合
templates := template.Templates()
//遍历获取到每个模版
for index := range templates {
//执行模版替换
error = templates[index].Execute(os.Stdout, argsMap)
if error != nil {
log.Printf("替换模版文件异常 name:%s,error:%s", templates[index].Name(), error.Error())
os.Exit(-1)
} else {
//最后文件结尾以`---`结束
io.Copy(os.Stdout, bytes.NewBufferString(fmt.Sprintf("\n%s\n", out)))
}
}
os.Exit(0)
}
//解析`key=value;key1=value1...`
func parseArgs(ctx map[string]interface{}, args string) {
argsSplit := strings.Split(args, ";")
for _, value := range argsSplit {
if value == "" {
continue
}
parseKeyValue(ctx, value)
}
}
//解析命令行中的`key=value`并put到map集合中
func parseKeyValue(ctx map[string]interface{}, args string) {
keyValue := strings.SplitN(args, "=", 2)
if len(keyValue) != 2 {
log.Printf("参数 %s 格式不正确,请检查是否为`key=value`格式", args)
os.Exit(-1)
} else {
ctx[keyValue[0]] = keyValue[1]
}
}
//解析文件类型的参数
func parseArgsFiles(ctx map[string]interface{}, argsFiles string) {
argsSplit := strings.Split(argsFiles, ";")
for _, file := range argsSplit {
dealArgsFiles(ctx, file)
}
}
//解析文件类型的参数
func dealArgsFiles(ctx map[string]interface{}, file string) {
f, error := os.Open(file)
if error != nil {
log.Printf("文件错误 %s ,error: %s", file, error.Error())
os.Exit(-1)
}
defer f.Close()
read := bufio.NewReader(f)
for {
line, error := read.ReadString('\n')
line = strings.TrimSpace(line)
//如果是注释,则继续处理下一行
if line == "" || strings.HasPrefix(line, "#") {
if io.EOF == error {
break
}
continue
}
//未知异常
if error != nil && line == "" {
log.Printf(error.Error())
break
}
parseKeyValue(ctx, line)
}
}
上面我们主要讲解下map[string]interface{}
这个map对象,原型为map[key_type]value_type
,我们这里的interface{}是一个空接口,所有类型都实现了空接口,所以我们可以理解成Java中的Map<String, Object>
,然后通过Go本身自带的模版库template,我们可以很方便的替换我们的yml文件,得到最终我们需要的Kubernetes yaml文件,然后通过kubectl apply -f deploy.yml
命令发布到Kubernetes环境
args.properties模版参数
env=prod
appName=appName
namespace=namespace
template.deploy.yml模版文件
{{$isProd := eq .env "prod"}}
apiVersion: v1
kind: Service
metadata:
name: {{.appName}}
namespace: {{.namespace}}
labels:
app: {{.appName}}
spec:
ports:
- port: 80
name: http
targetPort: 80
- port: 443
name: https
targetPort: 80
selector:
app: {{.appName}}
---
apiVersion: v1
kind: Service
metadata:
name: {{.service1}}
namespace: {{.namespace}}
labels:
app: {{.appName}}
spec:
ports:
- port: 80
name: http
targetPort: 80
- port: 443
name: https
targetPort: 80
selector:
app: {{.appName}}
---
apiVersion: v1
kind: Service
metadata:
name: {{.service2}}
namespace: {{.namespace}}
labels:
app: {{.appName}}
spec:
ports:
- port: 80
name: http
targetPort: 80
- port: 443
name: https
targetPort: 80
selector:
app: {{.appName}}
编译和使用
- 编译并指定执行文件名字为template(
go build -o template
) - 执行替换并输出到deploy.yml文件中(
./template -args-files args.properties -yml-files template.deploy.yml >> deploy.yml
)