0%

为什么Helm可以解决Kubernetes原生回滚问题?

问题引出

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
helm create testchart	

删除模板目录中所有不必要的文件:

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

helm-install-1

该应用程序版本是Chart.yaml文件中设置的默认版本(尚未更新)

检查部署中运行的镜像版本:

1
kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'

get-container-image-1

查看到的容器镜像就是Chart中values.yaml文件中定义的镜像版本。

现在升级Release,将默认的容器镜像值替换为set标志指定的值:

1
helm upgrade testchart ./testchart --set containerImage=nginx:1.18

确认版本已升级(检查版本号):

1
helm list

helm-upgrade-1

另外,请确认Release历史:

1
helm history testchart

helm-history-updated

这样我们就可以看到该Release的初始部署,然后是升级。应用版本保持不变,因为我没有更改Chart.yaml文件中的值。但是,镜像版本已更改,我们可以通过以下方式看到:

1
kubectl get deployment -o jsonpath='{ .items[*].spec.template.spec.containers[*].image }{"\n"}'

get-container-image-2

因此,我们已经升级了在deployment中容器运行的镜像版本。

让我们看一下deployment的replicasets:

1
kubectl get replicasets

get-replicasets-1

因此,我们为Helm版本创建的deployment有两个replicasets。最初的一个运行nginx v1.17,最新的一个运行nginx v1.18。

如果我们想使用kubectl回退升级,则可以使用(不要运行此代码!):

1
kubectl rollout undo deployment nginx

kubectl-rollout-undo

这里将发生的是,删除最新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:

1
kubectl get replicasets

get-replicasets-2

现在尝试使用kubectl rollout undo命令进行回滚:

1
kubectl rollout undo deployment nginx

kubectl-rollout-undo-2

失败的原因是我们删除了旧的replicasets,因此该deployment没有历史记录,可以通过以下方式查看:

1
kubectl rollout history deployment nginx

kubectl-rollout-history

使用Helm回滚

虽然旧的replicasets被删除了,但是Helm的实现机制决定了使用Helm部署的Release会保留历史:

1
helm history testchart

helm-history-2

所以,我们可以使用Helm回滚:

1
helm rollback testchart 1

helm-rollback

查看Release状态:

1
helm list

helm-list-rollback

查看Release历史:

1
helm history testchart

helm-rollback-history

查看replicasets:

1
kubectl get replicasets

get-replicasets-3

旧的replicasets又回来了!怎么样?

原理探究

让我们看一下集群中的secrets:

1
kubectl get secrets

kubectl-get-secrets

可以看出,这些secrets中会存储Helm发布所有历史记录!初始版本(v1),升级(v2)和回滚(v3)。

让我们仔细看看v1版本:

1
kubectl get secret sh.helm.release.v1.testchart.v1 -o json

kubectl-get-secrets-2

嗯,这个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

decode-helm-secret

哈!这里有deployment和service的yaml文件!

通过使用Helm,即使已删除deployment的旧replicasets,我们也可以回滚,因为Helm将Release历史记录在secrets并存储在目标Kubernetes集群中。通过使用上面的代码,我们可以解密这些secrets并查看其中包含的信息。