问题引出
Helm是将应用程序部署到Kubernetes的绝佳工具。我们可以打包所有deployment和service等yaml文件,并使用一个简单的命令将它们部署到集群中。
但是Helm的另一个非常酷的功能是能够轻松升级和回滚版本(在集群中运行的Helm Chart实例的术语)的功能。
现在,我们可以使用kubectl进行此操作。如果我们使用kubectl apply
升级deployment资源,则可以使用kubectl rollout undo
来回滚该升级。这很棒!这是Kubernetes的最佳功能之一。
升级deployment时,将为该deployment创建一个新的replicaset,该replicaset将在一组新的Pod中运行升级后的应用程序。
如果使用kubectl rollout undo
进行回滚,会删除最新replicaset中的容器,并回滚到旧replicaset的容器。
但是这里有一个潜在的问题。如果删除旧的replicaset会怎样?如果发生这种情况,我们将无法回滚升级。好吧,我们无法使用kubectl rollout undo
将其回滚,但是如果我们使用Helm,会发生什么?
让我们来看一个演示。
Helm环境准备
创建一个称为testchart的Chart:
删除模板目录中所有不必要的文件:
1
| rm -rf ./testchart/templates/*
|
创建一个deployment yaml文件:
1 2 3 4
| kubectl create deployment nginx \ --image=nginx:1.17 \ --dry-run=client \ --output=yaml > ./testchart/templates/deployment.yaml
|
这将创建以下yaml并将其另存为templates目录中的deployment.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: nginx name: nginx spec: replicas: 1 selector: matchLabels: app: nginx strategy: {} template: metadata: creationTimestamp: null labels: app: nginx spec: containers: - image: nginx:1.17 name: nginx resources: {} status: {}
|
创建deployment:
1
| kubectl create deployment nginx --image=nginx:1.17
|
为service生成yaml:
1 2 3 4 5
| kubectl expose deployment nginx \ --type=LoadBalancer \ --port=80 \ --dry-run=client \ --output=yaml > ./testchart/templates/service.yaml
|
这将为我们提供以下yaml并将其另存为模板目录中的service.yaml:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| apiVersion: v1 kind: Service metadata: creationTimestamp: null labels: app: nginx name: nginx spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx type: LoadBalancer status: loadBalancer: {}
|
删除deployment,模板化values.yaml 和deployment.yaml文件:
1 2 3 4
| kubectl delete deployment nginx rm ./testchart/values.yaml echo "containerImage: nginx:1.17" > ./testchart/values.yaml sed -i 's/nginx:1.17/{{ .Values.containerImage }}/g' ./testchart/templates/deployment.yaml
|
最终,deployment.yaml文件如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| apiVersion: apps/v1 kind: Deployment metadata: creationTimestamp: null labels: app: nginx name: nginx spec: replicas: 1 selector: matchLabels: app: nginx strategy: {} template: metadata: creationTimestamp: null labels: app: nginx spec: containers: - image: {{ .Values.containerImage }} name: nginx resources: {} status: {}
|
改造后的yaml中容器镜像不再是硬编码的。它将从values.yaml文件中获取nginx:1.17的值,或者我们可以使用set标志来覆盖它(我们将在一分钟内完成)。
Helm部署示例
首先,将Chart部署到Kubernetes集群中:
1
| helm install testchart ./testchart
|
该应用程序版本是Chart.yaml文件中设置的默认版本(尚未更新)
检查部署中运行的镜像版本:
1
| kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'
|
查看到的容器镜像就是Chart中values.yaml文件中定义的镜像版本。
现在升级Release,将默认的容器镜像值替换为set标志指定的值:
1
| helm upgrade testchart ./testchart --set containerImage=nginx:1.18
|
确认版本已升级(检查版本号):
另外,请确认Release历史:
这样我们就可以看到该Release的初始部署,然后是升级。应用版本保持不变,因为我没有更改Chart.yaml文件中的值。但是,镜像版本已更改,我们可以通过以下方式看到:
1
| kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'
|
因此,我们已经升级了在deployment中容器运行的镜像版本。
让我们看一下deployment的replicasets:
因此,我们为Helm版本创建的deployment有两个replicasets。最初的一个运行nginx v1.17,最新的一个运行nginx v1.18。
如果我们想使用kubectl回退升级,则可以使用(不要运行此代码!):
1
| kubectl rollout undo deployment nginx
|
这里将发生的是,删除最新replicasets下的Pod,并创建旧replicasets下的Pod,将nginx回滚到v1.17。
但是我们不会那样做,因为我们正在使用Helm。
问题复现
继续在当前环境中获取最旧的replicasets名称,并删除它::
1 2
| REPLICA_SET=$(kubectl get replicasets -o jsonpath='{.items[0].metadata.name }' --sort-by=.metadata.creationTimestamp) kubectl delete replicasets $REPLICA_SET
|
因此,我们现在只有一个replicasets:
现在尝试使用kubectl rollout undo
命令进行回滚:
1
| kubectl rollout undo deployment nginx
|
失败的原因是我们删除了旧的replicasets,因此该deployment没有历史记录,可以通过以下方式查看:
1
| kubectl rollout history deployment nginx
|
使用Helm回滚
虽然旧的replicasets被删除了,但是Helm的实现机制决定了使用Helm部署的Release会保留历史:
所以,我们可以使用Helm回滚:
1
| helm rollback testchart 1
|
查看Release状态:
查看Release历史:
查看replicasets:
旧的replicasets又回来了!怎么样?
原理探究
让我们看一下集群中的secrets:
可以看出,这些secrets中会存储Helm发布所有历史记录!初始版本(v1),升级(v2)和回滚(v3)。
让我们仔细看看v1版本:
1
| kubectl get secret sh.helm.release.v1.testchart.v1 -o json
|
嗯,这个Release内容看起来很有趣。我们可以做的是对base64进行解码,然后通过http://www.txtwizard.net/compression进行解压缩,得到结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
| { "name":"testchart", "info": { "first_deployed":"2020-08-09T11:21:20.4665817+01:00", "last_deployed":"2020-08-09T11:21:20.4665817+01:00", "deleted":"", "description":"Install complete", "status":"superseded"}, "chart":{"metadata": { "name":"testchart", "version":"0.1.0", "description":"A Helm chart for Kubernetes", "apiVersion":"v2", "appVersion":"1.16.0", "type":"application"}, "lock":null, "templates":[ { "name": "templates/deployment.yaml", "data":"YXBpVmVyc2lvbjogYXBwcy92MQpraW5kOiBEZXBsb3ltZW50Cm1ldGFkYXRhOgogIGNyZWF0aW9uVGltZXN0YW1wOiBudWxsCiAgbGFiZWxzOgogICAgYXBwOiBuZ2lueAogIG5hbWU6IG5naW54CnNwZWM6CiAgcmVwbGljYXM6IDEKICBzZWxlY3RvcjoKICAgIG1hdGNoTGFiZWxzOgogICAgICBhcHA6IG5naW54CiAgc3RyYXRlZ3k6IHt9CiAgdGVtcGxhdGU6CiAgICBtZXRhZGF0YToKICAgICAgY3JlYXRpb25UaW1lc3RhbXA6IG51bGwKICAgICAgbGFiZWxzOgogICAgICAgIGFwcDogbmdpbngKICAgIHNwZWM6CiAgICAgIGNvbnRhaW5lcnM6CiAgICAgIC0gaW1hZ2U6IHt7IC5WYWx1ZXMuY29udGFpbmVySW1hZ2UgfX0KICAgICAgICBuYW1lOiBuZ2lueAogICAgICAgIHJlc291cmNlczoge30Kc3RhdHVzOiB7fQo="},{"name":"templates/service.yaml","data":"YXBpVmVyc2lvbjogdjEKa2luZDogU2VydmljZQptZXRhZGF0YToKICBjcmVhdGlvblRpbWVzdGFtcDogbnVsbAogIGxhYmVsczoKICAgIGFwcDogbmdpbngKICBuYW1lOiBuZ2lueApzcGVjOgogIHBvcnRzOgogIC0gcG9ydDogODAKICAgIHByb3RvY29sOiBUQ1AKICAgIHRhcmdldFBvcnQ6IDgwCiAgc2VsZWN0b3I6CiAgICBhcHA6IG5naW54CiAgdHlwZTogTG9hZEJhbGFuY2VyCnN0YXR1czoKICBsb2FkQmFsYW5jZXI6IHt9Cg=="}],"values":{"containerImage":"nginx:1.17"},"schema":null,"files":[{"name":".helmignore","data":"IyBQYXR0ZXJucyB0byBpZ25vcmUgd2hlbiBidWlsZGluZyBwYWNrYWdlcy4KIyBUaGlzIHN1cHBvcnRzIHNoZWxsIGdsb2IgbWF0Y2hpbmcsIHJlbGF0aXZlIHBhdGggbWF0Y2hpbmcsIGFuZAojIG5lZ2F0aW9uIChwcmVmaXhlZCB3aXRoICEpLiBPbmx5IG9uZSBwYXR0ZXJuIHBlciBsaW5lLgouRFNfU3RvcmUKIyBDb21tb24gVkNTIGRpcnMKLmdpdC8KLmdpdGlnbm9yZQouYnpyLwouYnpyaWdub3JlCi5oZy8KLmhnaWdub3JlCi5zdm4vCiMgQ29tbW9uIGJhY2t1cCBmaWxlcwoqLnN3cAoqLmJhawoqLnRtcAoqLm9yaWcKKn4KIyBWYXJpb3VzIElERXMKLnByb2plY3QKLmlkZWEvCioudG1wcm9qCi52c2NvZGUvCg=="}]}, "manifest":"---\n# Source: testchart/templates/service.yaml\n apiVersion: v1\n kind: Service\nmetadata:\n creationTimestamp: null\n labels:\n app: nginx\n name: nginx\n spec:\n ports:\n - port: 80\n protocol: TCP\n targetPort: 80\n selector:\n app: nginx\n type: LoadBalancer\n status:\n loadBalancer: {}\n---\n# Source: testchart/templates/deployment.yaml\n apiVersion: apps/v1\n kind: Deployment\n metadata:\n creationTimestamp: null\n labels:\n app: nginx\n name: nginx\nspec:\n replicas: 1\n selector:\n matchLabels:\n app: nginx\n strategy: {}\n template:\n metadata:\n creationTimestamp: null\n labels:\n app: nginx\n spec:\n containers:\n - image: nginx:1.17\n name: nginx\n resources: {}\n status: {}\n", "version":1, "namespace":"default" }
|
BOOM!看起来就像我们的deployment和service清单!我们可以看到最初的Helm版本中包含的所有信息(确认容器镜像为nginx:1.17)!
因此,通过将这些信息作为secrets存储在目标Kubernetes集群中,即使已删除了旧的replicasets,Helm也可以回滚升级!太酷了!
不过结果还不是很清晰,查看data字段……看起来像是加密信息。
让我们解密吧!这次在命令行上:
1
| kubectl get secret sh.helm.release.v1.testchart.v1 -o jsonpath="{ .data.release }" | base64 -d | gunzip -c | jq '.chart.templates[].data' | tr -d '"' | base64 -d
|
哈!这里有deployment和service的yaml文件!
通过使用Helm,即使已删除deployment的旧replicasets,我们也可以回滚,因为Helm将Release历史记录在secrets并存储在目标Kubernetes集群中。通过使用上面的代码,我们可以解密这些secrets并查看其中包含的信息。