缘起
众所周知,Helm管理k8s manifest做法是分别定义value.yaml 和 template 文件,而我们的部署环境往往有多套,开发、测试、预发和线上,不同环境存在一些策略或者配置差异,例如:
- 线上环境需要启动initContainer执行skywalking agent的部署工作,而非线上环境却往往不需要这一步
- 线上k8s的yaml文件一般配置反亲和性策略实现服务高可用,而非线上环境就无所谓了
显而易见,单个value.yaml文件是无法满足这种参差不齐的多环境状况;解决方法也很简单,就是衍生出多个valve文件,value-dev.yaml,value-test.yaml分别对应不同环境。
牵一发而动全身,为了适配这种情况,我们的template也需要采用条件语句兼容多环境的配置,如下所示:
...
{{- if eq .Values.env "prod" -}}
initcontainer:
...
{{- end }}
{{- if eq .Values.env "prod" -}}
podAntiAffinity:
...
{{- end }}
{{- if eq .Values.env "prod" -}}
Volumes:
...
{{- end }}
...
复制代码
少量的if/else语句还能接受,但如果过多的if/else充斥在template中,就会造成template可读性变差
那么有没有相对优雅的实现?还真有,那就是Kustomize
Kustomize的哲学
与helm不同的是,Kustomize的做法是首先定义一个基础模板,不同环境基于基础模板再定义个性化的配置和参数。
这种做法其实就是类似于OOP编程中父类和子类的关系,子类集成父类的属性和方法,并可按需重写相应的属性和方法
kustomize的约定:
- 基础模板放在base目录中,其他的个性化配置放在overlay目录中
- 每个目录中都必须有一个kustomization.yaml文件,用来申明文件之间的引用关系
- overlay中的配置引用base目录中的基础yaml文件并可做相应的覆盖
base
看起来kustomize 似乎就是为多环境编排量身定做的.按照kustomize的做法,我们可以将各环境通用的yaml配置放在base目录中,其他环境的个性化、定制化参数放在overlay目录中 最终形成的目录结构如下:
.
├── base # 放置每个环境都会用到的通用配置
│ ├── deployment.yaml
│ ├── kustomization.yaml
│ └── service.yaml
└── overlay
├── prod
│ ├── kustomization.yaml
│ ├── podAntiAffinity.yaml
│ └── volumes.yaml
└── test
├── env.yaml
├── kustomization.yaml
└── volumes.yaml
复制代码
注意:每个目录中的kustomization.yaml文件是必须的
- kustomization.yaml: 定义一些公共配置或者是引用其他yaml文件的内容
- commonLabels: 定义公共标签,所有资源都会用到的标签
- resources: 引用同目录下的哪些文件
cat base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
commonLabels: #定义公共标签,所有yaml都会引用的标签
app: application-name
resources: #定义引用同目录下的哪些yaml文件
- deployment.yaml
- service.yaml
复制代码
base目录中放置deployment.yaml 和serivce.yaml,并被kustomization.yaml文件所引用,当然让你也可以放置ingressl.yaml configmap.yaml等更多的对象
cat base/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-name
namespace: namespace-A
spec:
replicas: 1
selector:
matchLabels:
app: application-name
template:
metadata:
labels:
app: application-name
spec:
imagePullSecrets:
- name: registry-pull-secret
containers:
- name: application-name
image: application-image
imagePullPolicy: Always
ports:
- containerPort: application-port
name: port
复制代码
overlay
overlay中建立两个目录
- prod: 存放线上环境的yaml编排文件
- test: 存放测试环境的yaml编排文件
再来看下prod目录下的kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
bases: # 定义基础yaml模板文件
- ../../base # 表示引用base目录中的基础yaml文件
patches: # 引用同目录下的个性化文件,并覆盖基础Yaml文件
- podAntiAffinity.yaml # 表示引用同目录中podAntiAffinity.yaml的文件
复制代码
再来看下podAntiAffinity.yaml的内容,定义了节点反亲和性的配置
cat overlay/prod/podAntiAffinity.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: application-name # 引用base中已经定义的deployment
spec:
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- application-name
topologyKey: kubernetes.io/hostname
复制代码
yaml文件的生成需要借助kusomize命令,下载安装kustomize
先了解一些基础命令:
# 生成编排文件,默认输出到终端
kustomize build -path
# 使用 -o 选项输出到指定路径
kustomize build -path -o target_file_path
# Kubernetes v1.14 版本之后,kustomize已经成为了kubectl内置的子命令
kubectl kustomize -path
复制代码
万事俱备,我们执行命令生成最终文件
# 生成用于线上环境的yaml
kubectl kustomize overlay/prod
apiVersion: v1
kind: Service
metadata:
labels:
app: application-name
name: application-name
namespace: namespace-A
spec:
ports:
- name: http
port: port
protocol: TCP
targetPort: application-port
selector:
app: application-name
---
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: application-name
name: application-name
namespace: namespace-A
spec:
replicas: 1
selector:
matchLabels:
app: application-name
template:
metadata:
labels:
app: application-name
spec:
`affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- application-name
topologyKey: kubernetes.io/hostname`
containers:
- image: image-name
imagePullPolicy: Always
name: application-name
ports:
- containerPort: application-port
name: application-port
imagePullSecrets:
- name: registry-pull-secret
复制代码
与原先base目录的基础deployment.yaml的仔细对比下,已经成功的将podAntiAffinity配置应用上了
实际的工作场景中,我们往往会将yaml文件保存在gitlab中做版本管理,而不是本地目录;没关系,kustomize也支持remote target, 简直完美!
target="https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6"
kubectl kustomize $target
apiVersion: v1
kind: Pod
metadata:
labels:
app: myapp
name: dev-myapp-pod
spec:
containers:
- image: nginx:1.7.9
name: nginx
复制代码
与Jenkins的集成
kustomize结合Jenkins的 pipeline,实现k8s的发布
// 伪代码
pipeline {
agent {}
parameters {
string(name: 'application' description: '应用名',trim: true)
string(name: 'deployEnv', description: '部署环境', trim: true)
environment {
manifestRepo="gitlab.com/walrus/manifest/${prarams.application}/overlay/${params.deployEnv}"
}
......
stage('部署'){
steps {
script {
sh "
kubectl kustomize ${env.manifestRepo} | kubectl apply -f -"
}
}
}
}
复制代码