DevOps CI/CD 分析(四)之編寫K8S yaml模版分析四中我們編寫了K8S yaml模版文件,其中有很多
{{...}}
這種定義琼懊,所以本節(jié)我們的重點(diǎn)就是將K8S yaml模版通過(guò)Go模版庫(kù)將{{...}}
這類定義替換成我們外部傳入的參數(shù)航唆,最終生成我們所需要的K8S yaml文件午衰。
Go模版實(shí)現(xiàn)
package main
import (
"bufio"
"bytes"
"flag"
"fmt"
"html/template"
"io"
"log"
"os"
"strings"
)
//主入口函數(shù)
func main() {
var args, argsFiles, ymlFiles, out string
//解析命令行參數(shù)
flag.StringVar(&args, "args", "", "參數(shù)格式為`key=value;` 多個(gè)參數(shù)使用 `;` 分隔")
flag.StringVar(&argsFiles, "args-files", "", "文件參數(shù)格式為`*.properties` 多個(gè)參數(shù)使用 `;` 分隔")
flag.StringVar(&ymlFiles, "yml-files", "", "請(qǐng)輸入需要替換的模版文件,多個(gè)文件以 `;` 分隔")
flag.StringVar(&out, "out", "---", "多個(gè)輸出的分隔")
//開始解析命令行參數(shù)
flag.Parse()
//定義一個(gè)Map對(duì)象戈擒,key=字符串類型,值=任意類型
argsMap := make(map[string]interface{})
if args != "" {
//解析命令行中直接傳遞的`key=value`形式的參數(shù)
parseArgs(argsMap, args)
}
if argsFiles != "" {
//解析傳遞的參數(shù)文件,properties格式的文件
parseArgsFiles(argsMap, argsFiles)
}
//解析需要替換的模版文件, 多個(gè)文件以`;`分隔
template, error := template.ParseFiles(strings.Split(ymlFiles, ";")...)
if error != nil {
log.Printf("解析yml文件錯(cuò)誤 error:%s", error)
os.Exit(-1)
}
//獲取模版集合
templates := template.Templates()
//遍歷獲取到每個(gè)模版
for index := range templates {
//執(zhí)行模版替換
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 {
//最后文件結(jié)尾以`---`結(jié)束
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("參數(shù) %s 格式不正確阴颖,請(qǐng)檢查是否為`key=value`格式", args)
os.Exit(-1)
} else {
ctx[keyValue[0]] = keyValue[1]
}
}
//解析文件類型的參數(shù)
func parseArgsFiles(ctx map[string]interface{}, argsFiles string) {
argsSplit := strings.Split(argsFiles, ";")
for _, file := range argsSplit {
dealArgsFiles(ctx, file)
}
}
//解析文件類型的參數(shù)
func dealArgsFiles(ctx map[string]interface{}, file string) {
f, error := os.Open(file)
if error != nil {
log.Printf("文件錯(cuò)誤 %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)
//如果是注釋,則繼續(xù)處理下一行
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{}
這個(gè)map對(duì)象丐膝,原型為map[key_type]value_type
量愧,我們這里的interface{}是一個(gè)空接口钾菊,所有類型都實(shí)現(xiàn)了空接口,所以我們可以理解成Java中的Map<String, Object>
偎肃,然后通過(guò)Go本身自帶的模版庫(kù)template煞烫,我們可以很方便的替換我們的yml文件,得到最終我們需要的Kubernetes yaml文件累颂,然后通過(guò)kubectl apply -f deploy.yml
命令發(fā)布到Kubernetes環(huán)境
args.properties模版參數(shù)
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}}
編譯和使用
- 編譯并指定執(zhí)行文件名字為template(
go build -o template
) - 執(zhí)行替換并輸出到deploy.yml文件中(
./template -args-files args.properties -yml-files template.deploy.yml >> deploy.yml
)