helm相关的一些实践操作,db migrate、私有helm repo。持续更新…
主要内容
- 使用helm进行数据库迁移
- 调节helm hook 优先级
- 构建一个自己的helm仓库
- helm –set-string 用法
如何使用helm进行数据库迁移
在我们将新版本的服务部署到k8s上面时,有的服务会有对数据库进行migrate的操作。这时我们就需要在 部署服务之前,提前运行数据库的修改指令。
方法1:使用kubectl
主要思路是我们可以在CICD工具上,使用kubectl在集群创建一个一次性的pod。运行它时引用该服务在集群里 创建好的secret资源文件,运行的command是我们要执行的db migrate命令。
下面是我们之前在使用Jenkins时的用法,部分相关片断。
kubeDBMigrate
定义的片断:
#!groovy
/* Usage example:
runMigrate(imageFullName: "registry.xxx.com/hi42/pipeline-test:staging-344ea60", namespace: "devops", command: '"sleep","30"', configMap: "fluentd-test-config")
*/
def call(Map migrateConfig = [:]) {
if (migrateConfig.context && migrateConfig.config) {
defaultContext = migrateConfig.context
defaultConfig = migrateConfig.config
} else {
defaultContext = "kubernetes-admin@kubernetes"
defaultConfig = "/home/user_name/.kube/jenkins-k8s-config"
}
// pod name(Optional,default value 'db-migrate')
if (!migrateConfig.taskName) {
podName = "db-migrate"
} else {
podName = migrateConfig.taskName
}
// Default's envfrom is Secret
if (!migrateConfig.envFrom) {
jsonContent = """{"spec": {"containers": [{"image": "$migrateConfig.imageFullName", "command": [$migrateConfig.command], "name": "$podName", "envFrom": [{"secretRef": {"name": "$migrateConfig.configMap"}}]}]}}"""
} else if (migrateConfig.envFrom == 'configmap') {
jsonContent = """{"spec": {"containers": [{"image": "$migrateConfig.imageFullName", "command": [$migrateConfig.command], "name": "$podName", "envFrom": [{"configMapRef": {"name": "$migrateConfig.configMap"}}]}]}}"""
}
// start a pod exec db migrate command
def runMigrateCmd = [
"kubectl run",
"$podName",
"--image=$migrateConfig.imageFullName",
"--attach=true --rm=true --restart=Never",
"--namespace $migrateConfig.namespace",
"--context=$defaultContext",
"--kubeconfig=$defaultConfig",
"--overrides='$jsonContent'"
]
sh(runMigrateCmd.join(" "))
Jenkinsfile
文件中调用kubeDBMigrate
的片断:
stage("Deploy to Production") {
waitForApprove("Deploy to Production?", 3)
node("${buildAgent}") {
kubeDBMigrate(imageFullName: "${imageName}:${imageTag}", namespace: "${namespace}", command: '"bundle", "exec", "rails", "db:migrate"', configMap: "${prodConfigMap}", envFrom: "configmap")
PRODUCT_DEPLOY_CONTAINER.each { key, value ->
kubeDeployToServer(namespace: "${namespace}", deploymentName: key, imageFullName: "${imageName}:${imageTag}", containerName: value)
}
}
}
在Jenkinsfile中,一旦某个命令执行失败,那么整个流水线将会失败。通过上面jenkinsfile中的片断, 可以看出我们是在deploy production服务之前运行了 db migrate 指令。
TIPS: –restart 可选 [Always, OnFailure, Never],根据值的不同,创建相对应的资源类型,[Deployment, Job, Pod]
方法2:使用Helm Hook
根据helm官方文档介绍,使用helm hook的作用可以在helm install 集群之前或之后创建加载的资源(这里的资源是指helm所支持的所有类型)。
正常的helm instal 执行流程:
- helm客户端运行 helm install foo
- chart 被加载到Tiller中
- 经过一些验证,Tiller将根据动态的内容生成foo 的yaml文件。
- Tiller将生成的yaml资源加载到Kubernetes中
- Tiller将版本名称(和其他数据)返回给helm客户端
- helm客户端退出
添加了hook之后的Chart 执行流程:
- helm客户端运行 helm install foo
- chart 被加载到Tiller中
- 经过一些验证,Tiller将根据动态的内容生成foo 的yaml文件。
- Tiller准备执行pre-install hook(将hook资源加载到Kubernetes中)
- Tiller按权重(hook-weight)对Hook进行排序(默认情况下权重为0)
- 然后Tiller首先加载最低权重的hook(从负到正)
- Tiller会一直等hook “Ready”(CRD除外)
- Tiller将生成的资源加载到Kubernetes中。请注意,如果设置了–wait标志,Tiller将等待所有资源都处于就绪状态,并且在准备就绪状态之前不会运行post-install hook
- Tiller执行post-install hook(加载hook资源)
- Tiller等到hook “Ready”
- Tiller将版本名称(和其他数据)返回给客户端
- helm客户端退出
我们这里使用了带hook的job资源,去处理最开始的问题. 即在使用helm部署新版本的服务之前,先执行数据库migrate 操作。
如果要想把一个资源变成hook类型的,只要在annotations添加helm.sh/hook 就行了,如下:
apiVersion: batch/v1
kind: Job
metadata:
name: db-migrate-{{ .Chart.Name }}-{{ .Values.migrateJobNameSuffix }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-delete-policy": hook-succeeded
spec:
activeDeadlineSeconds: 3600
template:
spec:
restartPolicy: Never
{{- if .Values.imagePullSecrets }}
imagePullSecrets:
{{ toYaml .Values.imagePullSecrets | indent 8 }}
{{- end }}
containers:
- name: db-migrate
image: {{ .Values.image.repository }}:{{ .Values.image.tag }}
envFrom:
- secretRef:
name: {{ .Chart.Name }}-migrate-job-{{ .Values.migrateJobNameSuffix }}
command:
- rake
- db:migrate
"helm.sh/hook": pre-install,pre-upgrade: 表示在更新或安装chart之前执行的操作
"helm.sh/hook-delete-policy": hook-succeeded:删除策略是在 资源执行成功之后
调整hook的优先级
接着上一节,在第一次使用hook执行db migrate之后,发现正常Chart里的 secret资源无法被hook里的资源引用到, 这样的话,就得在首次安装这个Chart之前,手动创建一个secret供job使用。后来想了想,可以根据hook-weight 在定义一个高优先级的hook资源,这样后面的带hook 的job就可以引用到了。如下:
apiVersion: v1
kind: Secret
metadata:
name: {{ .Chart.Name }}-migrate-job-{{ .Values.migrateJobNameSuffix }}
annotations:
"helm.sh/hook": pre-install,pre-upgrade
"helm.sh/hook-delete-policy": hook-succeeded,hook-failed
"helm.sh/hook-weight": "-88"
type: Opaque
data:
DB_PORT: {{ .Values.pgPort | toString | b64enc }}
helm.sh/hook-weight: 只要该值小于job的权重(默认值0)即可
TIPS:这里提一下secret里的一个小知识,就是templates 的secret资源里面的字段值要是string类型的,
像port 数字会自动判断为int,所以需要使用toString
方法给转换成string。
搭建一个自己的helm仓库
项目多了之后,需要一个集中存储Chart的地方。Helm repository的支持方式很简单,有个index.html, 可提供web下载的就行了。
主要思路是:
helm dependency -> helm package -> helm repo index -> Web Server(nginx)
用python根据上面的思路自己写了个简单粗糙的项目: helm-private-repository
另外还有一个比较好的helm 仓库项目推荐给大家:chartmuseum
Helm template中资源引用value 值会强制转换为int
最近在项目中遇到一个问题:helm chart中有些资源文件里,定义了一些变量,变量值是从–set时获得的, 但是到了实际helm解析的时候,将该值转换了int,k8s会报错,说资源名字不符合规范。即使在资源的定义变量后面 加上toString 也不好使。
具体案例:gitlab的 commit-short-hash。当遇到8位是纯数字(49113771)的时候,helm就转换成
这种格式4.9113771e+07
. 导致无法创建资源,流水线失败。错误如下:
Error: Job.batch "db-migrate-xxxx-4.9113771e+07" is invalid: [metadata.name: Invalid value: "db-migrate-xxxx-4.9113771e+07": a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character (e.g. 'example.com', regex used for validation is '[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*'),
解决的方法:在使用helm命令设置该变量值时,将--set
替换成 --set-string
.