Skip to content

Kubernetes (三)–Deployments是如何工作的?

引言

本文介绍Deployment 理论基础以及实践。 理论基础部分包括自愈,可扩展,滚动更新和版本回滚。
实践部分包括: 如何部署一个Deployment,如何滚动更新以及如何回滚。

Depolyment 理论

在上一篇文章中我们分析了Pod,以及部署了一个Pod。但是Pod是会死掉的,英文叫mortal. Pod is mortal。Pod固有一死……
死掉了就再也无法恢复了,只能创建一个新的Pod。
Kubernetes重要的特性就是可以实现自我恢复,0宕机更新和版本回滚。仅仅靠Pod是做不到这些的。而Deployment可以做到这些。
Deployment和Pod的关系如下图所示。 Deployment 使用Pod作为模板,一个Deployment只能包含一个Pod模板。
deploy-pod
Deployment也是Kubernetes定义的一个对象,它可以通过ReplicaSets实现应用的自我恢复和扩展。
deploy-replica-pod
那什么是自我恢复呢? 如果一个Pod进入了Failed 状态,Deployment会负责创建一个新的Pod来代替它,这就叫自我恢复。
什么是可扩展呢? Deployment 可以根据负载自动的增加新的Pod,这就叫可扩展。

Depolyment如何实现自我恢复和可扩展

上面提到Deployment 利用ReplicaSets实现自我恢复和可扩展, 但是ReplicaSets只是后端一个具体的对象,从具体的角度来说,
Kubernetes 通过声明式的模型(declarative model)定义需要的状态(desired state)(就是表现就是manifest文件),并持续和当前状态进行比较来实现自我恢复和可扩展。

declarative model

声明式模型是一种理念,它只定义最后想要的状态,而忽略具体怎么做。 与之相对的叫命令式模型(imperative model),命令式模型只会具体给出每一步做什么。

reconcilication loop(对账监控程序)

ReplicaSets 会实现一个对账监控程序来持续的比较当前状态和需要的状态是否一致,当出现不一致时会发出一个信号,control plane收到相关信号后会采取对应的行动比如创建新的Pod来替代已经死掉的Pod。 再比如当你想扩展Pod时,你可以更新deployment,reconcilication检测到新的需要的状态和当前状态不一致时,就会自动扩展。

滚动更新

Kubernetes是如何实现更新的呢?
当我们需要更新通过Deployment部署的应用时,我们需要更新同一个Deployment的manifest,更新container镜像后,重新发送到API server。Kubernetes 接收到请求后会创建一个基于新的镜像的ReplicaSet, 这个时候一个Deployment包含了两个ReplicaSet,每次在新的ReplicaSet中部署一个新的Pod后,就会把老的ReplicaSet中的Pod减少一个,直到新的ReplicaSet中创建了所有的Pod,老的ReplicaSet中所有的Pod也都被销毁,这样就实现了滚动更新。

需要注意的是,这时这个老的ReplicaSet仍然存在并拥有完整的配置,包括使用哪个版本的镜像创建Pod。

版本回滚

当滚动更新后,老的ReplicaSet虽然不再管理任何Pod,但是它仍然存在且拥有完整的配置。 当需要回滚时,它会利用老的ReplicaSet的配置信息,做和滚动更新相反的操作,创建一个使用老版本镜像的Pod,销毁一个使用新版本镜像的Pod。

实践部分

这次实践部分我们还以Web 服务为例。 我们有一个Web 服务,访问它会返回一个”Hello Kubernetes Deployment!”。
hello-deploy-8000
我们通过Deployment来部署它。

部署一个Deployment

步骤还是先创建image, 然后写Deployment的manifest。
先创建一个hello-deployment.dockerfile,其内容如下:

FROM python:latest
COPY ./index.html /
CMD python -m http.server

然后生成一个image,命令如下:

docker build -t backendsite/hello-deployment:1.0 -f .\hello-deployment.dockerfile .

build image

接下来写Deployment 部分,创建一个hello-deployment.manifest.yml的文件来定义manifest.
Deployment 的manifest 格式如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-deployment
  minReadySeconds: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: hello-deployment
    spec:
      containers:
      - name: hello-deployment
        image: backendsite/hello-deployment:1.0
        ports:
        - containerPort: 8000

总体上来讲所有的Kubernetes manifest 都只分为4部分: apiVersion, kind, metadata, spec。在Kubernetes(二)–Pods是如何工作的,我们也对这4个property做了简单介绍。
我们首先关注下apiVersion 部分在Pod的manifest apiVersion 是v1(完整的是core/v1 core是一个特殊的api group可以省略), 这里是 apps/v1,可见Pod和Deployment在不同的api group下。
然后我们重点关注Deployment的spec 部分, .spec.replicas 定义了生成几个Pod。.spec.selector 定义了这个Deployment能够管理的Pod必须要有的label。
.spec.strategy 定义了执行升级的相关策略。

介绍完manifest后,现在我们来部署它

kubectl apply -f hello-deployment.manifest.yml

deploy-result
部署成功后,我们分别使用

kubectl get deploy hello-deploy
kubectl describe deploy hello-deploy
kubectl get rs

来查看deployment部署后的情况和对应的ReplicaSet的情况,可以看到这个时候只有一个ReplicaSet.
get-describe-getrs

接下来我们来访问这个web 服务。要想访问它,这次我们创建一个Service。
hello-service.manifest.yml 内容如下

apiVersion: v1
kind: Service
metadata:
  name: hello-svc
  labels:
    app: hello-deployment
spec:
  type: NodePort
  ports:
  - port: 8000
    nodePort: 30001
    protocol: TCP
  selector:
    app: hello-deployment

部署这个Service。

kubectl apply -f hello-service.manifest.yml

然后我们访问: localhost:30001就可以得到结果了。
30001

滚动更新

现在来模拟更新的操作:
我觉得只显示”Hello Kubernetes Deployment!” 还不够好,我准备第二版显示成”Hello Kubernetes Deployment! 请访问 www.backendsite.com 或者微信号搜索公众号后端小站。”
因此我新发布了一个image: backendsite/hello-deployment:2.0, 然后我更新deployment manifest。创建一个hello-deployment-v2-manifest.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-deploy
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-deployment
  minReadySeconds: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: hello-deployment
    spec:
      containers:
      - name: hello-deployment
        image: backendsite/hello-deployment:2.0 # 注意这里的version
        ports:
        - containerPort: 8000

然后更新Deployment 以实现滚动更新

kubectl apply -f hello-deployment-v2-manifest.yml --record

重新访问localhost:30001 结果如下所示:
30001 v2

更新后我们再来观察下ReplicaSet

kubectl get rs

可以看到有两个ReplicaSet。并且其中一个下面没有Pod。
rs-after-udpate

版本回滚

现在我看了看新的内容,觉得不满意,觉得还是回滚下比较好。
那么首先查看下Deployment相关的rollout 历史记录,可以看到有两个版本

kubectl rollout history deployment hello-deploy

rollout history

然后我们执行下面的命令来回滚到1.0 版本

kubectl rollout undo deployment hello-deploy --to-revision=1

然后尴尬的是在同一浏览器里再次访问localhost:30001 显示内容并没有回到1.0版本, 即使再开一个隐私窗口显示内容仍然是2.0版本, 而不是期待的1.0版本
我多花了好几个小时在这个问题上,一开始我怀疑是因为我本地的index.html更新了,所以永远是最新的,但是我登上了对应的container,发现不同版本的container的index.html是不同的(符合预期)。

然后,卡在这个问题,最后我换了个浏览器解决了,正确的显示出了1.0 版本的内容。
chrome在这个测试上有坑……

Published inbash shell

Comments are closed.

Author Copyriht by BackendSite