跟着官方文档学 Kubernetes 基础知识

本教程主要包括以下几点:

  • 在集群上部署容器化应用程序
  • 弹性部署
  • 更新容器化应用程序
  • 调试容器化应用程序

一、Kubernetes 集群

Kubernetes 集群是一个高可用的计算机集群,每个计算机作为独立单元互相连接工作。

Kubernetes 允许将容器化的应用部署到集群,而不是部署到某个特定的计算机中。

与过去的直接将应用打包部署相比,容器化应用更灵活、更可用。

Kubernetes 能够以更高效的方式跨集群自动分发和调用应用容器。

一个 Kubernetes 集群中包含下面两种资源:

  • Master:主节点,用来调度整个集群
  • Nodes:子节点,负责运行应用

1 Master

Master 协调集群中的所有活动,例如调度应用、维护应用所需状态、应用扩容和应用更新。

2 Node

Node 是一个虚拟机或者物理机,它在 Kubernetes 集群中充当工作机器的角色。

每个 Node 都有 Kubelet ,这是管理 Node 的工具,也是 Node 与 Master 的通信渠道。

Node 内应该有用于处理容器操作的工具,如 Docker 或 rkt。

处理生产级流量的 Kubernetes 集群至少应具有三个 Node。

3 创建集群

默认已安装好 Docker,在创建集群时将会使用 Docker。

首先需要安装命令minikubekubectl,然后使用minikube启动集群:

minikube start

因为国内的网络环境问题,可能在拉取镜像的时候会非常慢,耐心等待或者使用代理进行加速。

启动成功后终端会打印类似下面的内容:

根据现有的配置文件使用 docker 驱动程序
Starting control plane node minikube in cluster minikube
...
正在 Docker 20.10.5 中准备 Kubernetes v1.20.2…
Verifying Kubernetes components...
▪ Using image gcr.io/k8s-minikube/storage-provisioner:v5
...
Done! kubectl is now configured to use "minikube" cluster and "default" namespace by default

查看kubectl版本:

kubectl version

得到类似输出:

Client Version: version.Info{Major:"1", Minor:"21", GitVersion:"v1.21.0", GitCommit:"cb303e613a121a29364f75cc67d3d580833a7479", GitTreeState:"archive", BuildDate:"2021-04-09T16:47:30Z", GoVersion:"go1.16.3", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"20", GitVersion:"v1.20.2", GitCommit:"faecb196815e248d3ecfb03c680a4507229c2a56", GitTreeState:"clean", BuildDate:"2021-01-13T13:20:00Z", GoVersion:"go1.15.5", Compiler:"gc", Platform:"linux/amd64"}

查看集群信息:

kubectl cluster-info

信息如下所示:

Kubernetes control plane is running at https://192.168.49.2:8443
KubeDNS is running at https://192.168.49.2:8443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
...

二、部署应用

1 创建部署

部署应用的命令格式为:

kubectl create deployment [deployment name] --image=[image url]

官方示例中的代码为:

kubectl create deployment kubernetes-bootcamp --image=gcr.io/google-samples/kubernetes-bootcamp:v1

但 GFW 将 gcr.io 挡在了墙外,国内网络是无法直接从这个仓库中拉取镜像的。

国内有人将镜像保存到了阿里云仓库中,所以下面将仓库换为阿里云仓库:

kubectl create deployment kubernetes-bootcamp --image=registry.cn-beijing.aliyuncs.com/typ/kubernetes-bootcamp:v1

结果:

deployment.apps/kubernetes-bootcamp created

2 查询部署

命令:

kubectl get deployments

结果:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   1/1     1            1           53s

3 查看 app

默认情况下 Kubernetes 内的 pod 是在私有网络上进行通信,外部不可直接访问。

可以用 kubectl 创建一个能够与集群私有网络进行通信的代理。

在新的标签页或新的终端窗口中使用下面的命令创建代理:

echo -e "\n\n\n\e[92mStarting Proxy. After starting it will not output a response. Please click the first Terminal Tab\n"; 
kubectl proxy

代理启动:

Starting Proxy. After starting it will not output a response. Please click the first Terminal Tab

Starting to serve on 127.0.0.1:8001

代理启动后可以直接 8001 端口访问 API,如查询版本:

curl http://localhost:8001/version
{
  "major": "1",
  "minor": "20",
  "gitVersion": "v1.20.2",
  "gitCommit": "faecb196815e248d3ecfb03c680a4507229c2a56",
  "gitTreeState": "clean",
  "buildDate": "2021-01-13T13:20:00Z",
  "goVersion": "go1.15.5",
  "compiler": "gc",
  "platform": "linux/amd64"
}

API server 会为每个 pod 根据其名称创建端点 (endpoint),我们可以使用代理访问这些端点。

首先,获取 pod 名称,并将这个名称保存到环境变量POD_NAME中:

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME

成功执行的输出:

Name of the Pod: kubernetes-bootcamp-ccd8cdbf6-224hf

要想使用代理,就需要一直启动代理进程,这是一件很麻烦的事。为了在不使用代理的情况下访问新的部署,需要使用 service,后面会讲。

三、了解应用

1 查看 POD 和工作节点

1.1 Pods

在上一节创建 Deployment 时,Kubernetes 添加了一个 Pod 来托管你的应用实例。

Pod 是 Kubernetes 抽象出来的,表示一组或多个应用程序容器,以及这些容器的一些共享资源

  • 共享存储:卷
  • 网络:唯一的集群 ip
  • 有关每个容器如何运行的信息

Pod 中的容器共享 ip 地址和端口,始终位于同一位置并且共同调度,并在同一工作节点上的共享上下文中运行。

Pod 是 Kubernetes 平台上的原子单元。当我们在 Kubernetes 中创建 Deployment 时,该 Deployment 会在其中创建包含容器的 Pod(不是直接创建容器)。每个 pod 都与调度它的工作节点绑定,并保持住直到终止或删除。如果工作节点发生故障,则会在集群中的其他可用工作节点上调度相同的 Pod。

pods 示意图

1.2 工作节点 / Node

一个 Pod 总是运行在工作节点上。工作节点是 Kubernetes 中的参与计算的机器,可以是虚拟机或物理机,具体取决于集群。

每个工作节点由主节点管理。工作节点可以有多个 Pod,Kubernetes 主节点会自动处理集群中的工作节点上对 Pods 的调度。

主节点的自动调度基于每个工作节点的可用资源。

每个工作节点至少运行:

  • Kubelet,负责 Kubernetes 主节点和工作节点之间通信的过程,管理 Pod 和机器上运行的容器
  • 容器运行时负责从仓库中拉取容器镜像,解压缩容器以降运行应用程序

工作节点示意图

1.3 故障排查

前一节中已经使用过Kubectl命令,本节中将使有这个命令的更多方法:

  • kubectl get - 列出资源
  • kubectl describe - 显示有关资源的详细信息
  • kubectl logs - 打印 pod 和其中容器的日志
  • kubectl exec - 在 pod 中的容器上执行命令

2 实际操作

2.1 检查程序配置

前一节点已经部署了一个应用,所以集群也就创建了一个 Pod,查看一下所有 Pod:

kubectl get pods

会得到类似下面的输出:

NAME                                  READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running   0          95m

接下来查看容器的详细信息:

kubectl describe pods

输出:

Name:         kubernetes-bootcamp-ccd8cdbf6-224hf
Namespace:    default
Priority:     0
Node:         minikube/192.168.49.2
Start Time:   Wed, 19 May 2021 08:50:47 +0800
Labels:       app=kubernetes-bootcamp
              pod-template-hash=ccd8cdbf6
Annotations:  <none>
Status:       Running
IP:           172.17.0.3
IPs:
  IP:           172.17.0.3
Controlled By:  ReplicaSet/kubernetes-bootcamp-ccd8cdbf6
Containers:
  kubernetes-bootcamp:
    Container ID:   docker://0c9a846eee702ba0ee9f3885dfbc080048d754a636baeba17b9915fe3ec6d94e
    Image:          registry.cn-beijing.aliyuncs.com/typ/kubernetes-bootcamp:v1
    Image ID:       docker-pullable://registry.cn-beijing.aliyuncs.com/typ/kubernetes-bootcamp@sha256:34e5a47d302ee20039e5f0eb1e2f49785dafee3d97cac704befba6c1c7c938fc
    Port:           <none>
    Host Port:      <none>
    State:          Running
      Started:      Wed, 19 May 2021 08:50:55 +0800
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-bfzgv (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             True 
  ContainersReady   True 
  PodScheduled      True 
Volumes:
  default-token-bfzgv:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-bfzgv
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:          <none>

2.2 在终端中显示 app

在开始学习服务之前,还是要用代理来访问集群 API,创建代理:

kubectl proxy

获取 Pod 名称:

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME

用 curl 查看 app 的相关信息,下面是官方示例中的命令:

curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME/proxy/

如果按照官方命令来是无法访问 Pod 的,因为这条命令默认访问的是 Pod 的 80 端口,实际是 Pod 使用的是 8080 端口,所以命令需要改成:

curl http://localhost:8001/api/v1/namespaces/default/pods/$POD_NAME:8080/proxy/

或者在创建部署时指定端口为 8080。

输出:

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1

2.3 查看容器日志

可以使用下面的命令查看日志:

kubectl logs $POD_NAME

输出:

Kubernetes Bootcamp App Started At: 2021-05-19T00:50:55.081Z | Running On:  kubernetes-bootcamp-ccd8cdbf6-224hf 

Running On: kubernetes-bootcamp-ccd8cdbf6-224hf | Total Requests: 1 | App Uptime: 6461.413 seconds | Log Time: 2021-05-19T02:38:36.494Z
Running On: kubernetes-bootcamp-ccd8cdbf6-224hf | Total Requests: 2 | App Uptime: 6544.418 seconds | Log Time: 2021-05-19T02:39:59.499Z

Pod 中只有一个容器,所以不需要指定容器名称

2.4 在容器中执行命令

一旦 Pod 启动并运行,就可以直接在容器上执行命令。比如列出容器的环境变量:

kubectl exec $POD_NAME -- env

输出:

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=kubernetes-bootcamp-ccd8cdbf6-224hf
KUBERNETES_SERVICE_PORT_HTTPS=443
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PROTO=tcp
KUBERNETES_PORT_443_TCP_PORT=443
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_SERVICE_PORT=443
NPM_CONFIG_LOGLEVEL=info
NODE_VERSION=6.3.1
HOME=/root

接下来在容器中启动bash会话:

kubectl exec -ti $POD_NAME -- bash

我们现在在运行 NodeJS 程序的容器中打开了一个控制台,程序的源码在server.js文件中:

cat server.js

用 curl 检查程序是否启动:

curl localhost:8080

此处得到的输出结果与 2.2 是一样的。

使用exitctrl+D退出容器控制台。

四、对外暴露应用

1 使用 Service 暴露应用

Pod 和 Pod 副本有着不同的 IP 地址,却提供相同的功能。同一时刻,只有一个 Pod 能够运行,当正在运行的 Pod 挂掉后,需要一种方法能够自动启用一个副本以便保持程序的正常运行。由此引入了 Service 。

Kubernetes 中的服务(Service)是一种抽象概念,它定义了 Pod 的逻辑集和访问 Pod 协议。Service 使从属 Pod 之间的松耦合成为可能。

Service 使用 yaml(推荐) 或 json 来定义。Service 下的一组 Pod 通用由 LabelSelector 来标记。

尽管每个 Pod 都有一个唯一的 IP 地址,但是如果没有 Service,这些 IP 不会暴露在集群外部。Service 允许应用程序接收流量。

Service 也可以用修改type的方式暴露:

  • ClusterIP (默认) - 在集群的内部 IP 上公开 Service 。这种类型使得 Service 只能从集群内访问。
  • NodePort - 使用 NAT 在集群中每个选定 Node 的相同端口上公开 Service 。使用<NodeIP>:<NodePort> 从集群外部访问 Service。是 ClusterIP 的超集。
  • LoadBalancer - 在当前云中创建一个外部负载均衡器(如果支持的话),并为 Service 分配一个固定的外部IP。是 NodePort 的超集。
  • ExternalName - 通过返回带有该名称的 CNAME 记录,使用任意名称(由 spec 中的externalName指定)公开 Service。不使用代理。这种类型需要kube-dns的v1.7或更高版本。

Service 通过一组 Pod 路由通信。前文说到,Service 是一种抽象概念,它允许 Pod 死亡并在 Kubernetes 中复制,而不影响应用程序。在依赖的 Pod 之间(如 Pod1 是前端,Pod2 是后端)互相发现和创建路由连接是由 Service 处理的。

Service 使用标签(Label)和选择器(Selector)来匹配一组 Pod,Label 和 Selector 是允许对 Kubernetes 中的对象进行逻辑操作的一种分组原语。

标签 / Label 是附加在对象上的键值对,可以以多种方式使用:

  • 指定用于开发,测试和生产的对象
  • 嵌入版本标签
  • 使用 Label 将对象进行分类

标签和选择器

标签可以在创建时或之后附加到对象上,可以随时被修改,下面开始实际操作。

2 实际操作

2.1 创建新 service

首先查看所有 Pod:

kubectl get pods

然后列出集群服务列表:

kubectl get services

结果:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   13h

在最开始创建集群时,会默认创建一个名为 kubernetes 的 Service。接下来我们将使用NodePort作为类型参数和kubectl expose命令创建一个新的 Service 并将其暴露给外部网络:

kubectl expose deployment/kubernetes-bootcamp --type="NodePort" --port 8080

输出:

service/kubernetes-bootcamp exposed

然后再去获取一个服务列表:

kubectl get services

结果:

NAME                  TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes            ClusterIP   10.96.0.1      <none>        443/TCP          17h
kubernetes-bootcamp   NodePort    10.107.32.64   <none>        8080:31335/TCP   51s

可以看到,多了一个叫kubernetes-bootcamp的 Service。此然,我们还能看到,每个服务都有一个唯一的CLUSTER-IP、内部端口PORT(s)和外部 IPEXTERNAL-IP。外部 IP 就是节点的 IP。

想要查看对外开放的端口,我们需要使用describe service命令:

kubectl describe services/kubernetes-bootcamp

结果:

Name:                     kubernetes-bootcamp
...
NodePort:                 <unset>  31335/TCP
...

NodePort的值就是我们要找的端口了。

创建名为NODE_PORT的环境变量,将查到的端口值赋给它:

export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')
echo NODE_PORT=$NODE_PORT

现在,我们可以使用curl在集群外部测试 app了,因为此时节点的 IP 和端口都已经暴给了外部网络:

curl $(minikube ip):$NODE_PORT

结果:

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1

2.2 使用和添加标签 / Labels

Deployment 为 Pod 自动创建了一个标签,使用describe deployment可以查看标签名:

kubectl describe deployment

结果:

Name:                   kubernetes-bootcamp
...
Pod Template:
  Labels:  app=kubernetes-bootcamp
  Containers:
   kubernetes-bootcamp:
...

接下来,用这个标签来查询 POD 列表,查询的命令为:

kubectl get pods -l app=kubernetes-bootcamp

kubectl的命令后面加上可选参数-l-l后跟着标签名即可。

查询已存在的服务也是用相同的办法使用-l

kubectl get services -l app=kubernetes-bootcamp

获取 POD 名并保存到环境变量:

export POD_NAME=$(kubectl get pods -o go-template --template '{{range .items}}{{.metadata.name}}{{"\n"}}{{end}}')
echo Name of the Pod: $POD_NAME

要添加新标签,我们需要使用label命令,后面跟着对象类型、对象名称和新的标签名:

kubectl label pod $POD_NAME version=v1

查看是否成功设置了新的标签:

kubectl describe pods $POD_NAME

结果:

Name:         kubernetes-bootcamp-ccd8cdbf6-224hf
...
Labels:       app=kubernetes-bootcamp
              pod-template-hash=ccd8cdbf6
              version=v1
...

可以看到,新的标签已经绑定到了 POD 上,下面可以用刚刚添加的标签查询 POD:

kubectl get pods -l version=v1

结果:

NAME                                  READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running   0          7h12m

2.3 删除 service

删除 service 的命令为delete service,使用此命令时可以使用标签:

kubectl delete service -l app=kubernetes-bootcamp

结果:

service "kubernetes-bootcamp" deleted

确认一下 service 是否删除:

kubectl get services

结果:

NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.96.0.1    <none>        443/TCP   18h

可以看到,kubernetes-bootcamp服务已被删除。

为了确保被删除服务之前的 IP 和端口已经失效,使用curl访问一下:

curl $(minikube ip):$NODE_PORT

结果:

curl: (7) Failed to connect to 192.168.49.2 port 31335: 拒绝连接

这证明了该程序无法从集群之外访问,你可以确认该程序仍然在 POD 内部运行:

kubectl exec -ti $POD_NAME -- curl localhost:8080

结果:

Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1

可以看到程序仍在运行,因为还有 deployment 在管理着此程序。

如果想关闭此程序,你还需要删除 deployment。

五、应用程序的扩展

1 运行应用程序的多个实例

在前文,我们创建了一个 Deployment,然后通过 Service 让其可以公开访问。Deployment 仅为运行这个应用程序创建了一个 Pod,当流量增加或一个 Pod 不满足需求时,就需要扩展应用程序。

应用程序的扩展是通过改变 Deployment 中的副本数量实现的,也就是添加新的 POD,并资源调度的请求分配到有可用资源的节点上。压缩则反之。

Kubernetes 还支持 Pods 的自动伸缩,但这并不在本教程的讨论范围之内。

在压缩 Pods 时,将数量压缩到 0 也是可以的,但这将会终止 Deployment 上所有已经部署的 Pods。

运行应用程序的多个实例需要在它们之间分配流量。Service 有一种负载均衡器类型,可以将网络流量均衡地分配到外部可访问的 Pods 上。Service 会一直通过 endpoint 来监视 Pods 的运行,保证流量只分配到可用的 Pods 上。

一旦有了多个应用实例,就可以在不宕机的情况下滚动更新,之后的小节中会讲这些内容。

下面通过实际操作体验一下应用程序的伸缩。

2 实际操作

2.1 扩展一个 deployment

get deployments的几个参数的解释:

  • Name 集群中 deployments 的名称
  • READY 当前副本数量 / 所需副本数量
  • UP-TO-DATE 显示已更新到所需状态的副本数量
  • AVAILABLE 显示你的用ynr可用的应用程序的副本数量
  • AGE 显示程序已运行的时长

要查看 deployment 创建的副本集(ReplicaSet),运行:

kubectl get rs

结果:

NAME                            DESIRED   CURRENT   READY   AGE
kubernetes-bootcamp-ccd8cdbf6   1         1         1       23h

副本集的名字是 deploymeny 名 + 随机字符串

两个重要的参数:

  • DESIRED 显示在你创建 delpoyment 的定义的应用程序所需的副本数量
  • CURRENT 当前正在运行多个数副本

现在,我们将 deployment 扩展到 4 个副本。

扩展副本需要使用kubectl scale命令,后面跟着 deployment 的类型、名称和所需要的实例数量:

kubectl scale deployments/kubernetes-bootcamp --replicas=4

再获取一次 deployments 列表:

kubectl get deployments

结果:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   4/4     4            4           23h

可以看到,扩展已经生效,现在有了 4 个应用程序的实例,接下来,看一下 Pod 的数量有没有发生变化:

kubectl get pods -o wide

结果:

NAME                                  READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running   1          23h     172.17.0.3   minikube   <none>           <none>
kubernetes-bootcamp-ccd8cdbf6-ddd7b   1/1     Running   0          2m22s   172.17.0.4   minikube   <none>           <none>
kubernetes-bootcamp-ccd8cdbf6-tjfkf   1/1     Running   0          2m22s   172.17.0.6   minikube   <none>           <none>
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Running   0          2m22s   172.17.0.5   minikube   <none>           <none>

现在有了 4 个 Pods,都有独立的 IP 地址。这个变化已经在 deployment 事件日志中注册,用describe命令查看一下:

kubectl describe deployments/kubernetes-bootcamp

结果:

Name:                   kubernetes-bootcamp
...
Replicas:               4 desired | 4 updated | 4 total | 4 available | 0 unavailable
...
Events:
  Type    Reason             Age    From                   Message
  ----    ------             ----   ----                   -------
  Normal  ScalingReplicaSet  4m37s  deployment-controller  Scaled up replica set kubernetes-bootcamp-ccd8cdbf6 to 4

2.2 负载均衡

检查一下负载均衡服务是否加载,使用上一章节中对外暴露 IP 和端口的方法查找 IP 和端口:

kubectl describe services/kubernetes-bootcamp

将端口保存到环境变量:

export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')
echo NODE_PORT=$NODE_PORT

多次使用curl连接 IP 和端口(最少十次):

curl $(minikube ip):$NODE_PORT

结果:

➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-tjfkf | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-tjfkf | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-zmsm5 | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-ddd7b | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-zmsm5 | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-zmsm5 | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-zmsm5 | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-ddd7b | v=1
➜  ~ curl $(minikube ip):$NODE_PORT
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-ccd8cdbf6-224hf | v=1

从结果可以看到,curl每次发出的请求都能到达不同的 Pod,说明负载均衡在正常工作。

2.3 压缩

将服务压缩到 2 个副本,一样使用scale命令:

kubectl scale deployments/kubernetes-bootcamp --replicas=2

查看 deployment 列表:

kubectl get deployments

结果:

NAME                  READY   UP-TO-DATE   AVAILABLE   AGE
kubernetes-bootcamp   2/2     2            2           23h

可以看到,副本数量已经减少到了 2 个。

下面再获取 Pod 列表:

kubectl get pods -o wide

结果:

NAME                                  READY   STATUS    RESTARTS   AGE   IP           NODE       NOMINATED NODE   READINESS GATES
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running   1          23h   172.17.0.3   minikube   <none>           <none>
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Running   0          17m   172.17.0.5   minikube   <none>           <none>

说明确定有 2 个 Pod 被终结了。

六、滚动更新

1 执行滚动更新

通常情况下,用户是希望程序是一直可用的,但开发者却可能每天要多次更新应用程序,这就需要使用滚动更新。

滚动更新允许通过使用新的实例逐步更新 Pod 实例,在不宕机的前提下进行 deployment 更新。新的 Pod 将在具有可用资源的节点上进行调度。

在上一章节中,我们将应用程序扩展为多个实例,这就是在不影响应用程序可用性的前提下执行的更新。

默认情况下,更新期间不可用的 Pod 的最大值和可以添加的新的 Pod 的数量都是 1。这两个值可以配置为数量或百分比。

在 Kubernetes 中,更新是有版本控制的,任何 deployment 都可以恢复到以前的版本。

滚动更新示意图

应用程序当前版本

更新一个应用实例

更新多个应用实例

更新全部应用实例

与应用程序的扩展类似,如果公开了 deployment ,服务将在更新期间仅对可用的 Pod 进行负载均衡。

滚动更新允许以下操作:

  • 将用应程序从一个环境提升到另一个环境(通过容器镜像更新)
  • 回滚到以前的版本
  • 持续集成和持续交付应用程序,无需停机

2 实际操作

2.1 更新 app 版本

上一章的最后,我们压缩了应用程序,这里还需要扩展回 4 个实例,因为 2 个实例太少。

kubectl scale deployments/kubernetes-bootcamp --replicas=4

使用get pods命令看一下当前的 Pods 名称:

NAME                                  READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running   1          24h
kubernetes-bootcamp-ccd8cdbf6-rgptt   1/1     Running   0          3m21s
kubernetes-bootcamp-ccd8cdbf6-wglmx   1/1     Running   0          3m21s
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Running   0          68m

使用set image命令将应用程序更新到 v2:

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2

更新命令执行很快,但真正的更新操作是需要一定时间的,我们可以多次调用get pods查看更新进度:

NAME                                  READY   STATUS              RESTARTS   AGE
kubernetes-bootcamp-769746fd4-k7kwz   0/1     ContainerCreating   0          6s
kubernetes-bootcamp-769746fd4-t76fm   0/1     ContainerCreating   0          6s
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running             1          24h
kubernetes-bootcamp-ccd8cdbf6-rgptt   1/1     Terminating         0          4m6s
kubernetes-bootcamp-ccd8cdbf6-wglmx   1/1     Running             0          4m6s
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Running             0          69m
NAME                                  READY   STATUS              RESTARTS   AGE
kubernetes-bootcamp-769746fd4-jbwdn   0/1     ContainerCreating   0          1s
kubernetes-bootcamp-769746fd4-k7kwz   1/1     Running             0          34s
kubernetes-bootcamp-769746fd4-t76fm   0/1     ContainerCreating   0          34s
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Running             1          24h
kubernetes-bootcamp-ccd8cdbf6-rgptt   0/1     Terminating         0          4m34s
kubernetes-bootcamp-ccd8cdbf6-wglmx   1/1     Terminating         0          4m34s
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Running             0          69m
NAME                                  READY   STATUS        RESTARTS   AGE
kubernetes-bootcamp-769746fd4-jbwdn   1/1     Running       0          10s
kubernetes-bootcamp-769746fd4-k7kwz   1/1     Running       0          43s
kubernetes-bootcamp-769746fd4-t76fm   1/1     Running       0          43s
kubernetes-bootcamp-769746fd4-xj2p6   1/1     Running       0          9s
kubernetes-bootcamp-ccd8cdbf6-224hf   1/1     Terminating   1          24h
kubernetes-bootcamp-ccd8cdbf6-rgptt   0/1     Terminating   0          4m43s
kubernetes-bootcamp-ccd8cdbf6-wglmx   1/1     Terminating   0          4m43s
kubernetes-bootcamp-ccd8cdbf6-zmsm5   1/1     Terminating   0          70m
NAME                                  READY   STATUS    RESTARTS   AGE
kubernetes-bootcamp-769746fd4-jbwdn   1/1     Running   0          94s
kubernetes-bootcamp-769746fd4-k7kwz   1/1     Running   0          2m7s
kubernetes-bootcamp-769746fd4-t76fm   1/1     Running   0          2m7s
kubernetes-bootcamp-769746fd4-xj2p6   1/1     Running   0          93s

可以看到,更新过程会经历创建新容器、运行新实例和终止旧实例的过程,最后 4 个实例全部更新到了新的版本。

2.2 验证更新

重复的代码不再讲解,直接执行:

kubectl describe services/kubernetes-bootcamp
export NODE_PORT=$(kubectl get services/kubernetes-bootcamp -o go-template='{{(index .spec.ports 0).nodePort}}')
echo NODE_PORT=$NODE_PORT
# 多次执行 curl
curl $(minikube ip):$NODE_PORT

能够看到curl请求能够分发到不同的 Pod,且每个 Pod 都已经更新到了 v2 版本。

可以通过rollout status命令来确认更新:

kubectl rollout status deployments/kubernetes-bootcamp

结果:

deployment "kubernetes-bootcamp" successfully rolled out

也可以通过describe查看 Pod 的镜像来检验更新:

kubectl describe pods

每个 Pod 的Image字段都变成了 v2 的镜像。

2.3 回滚

使用不存在的 v10 镜像更新:

kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v10

查看所有 Pods 的镜像:

kubectl get pods

结果:

NAME                                   READY   STATUS             RESTARTS   AGE
kubernetes-bootcamp-5d9987bd6f-cljsh   1/1     Running            0          4m45s
kubernetes-bootcamp-5d9987bd6f-frvfc   1/1     Running            0          4m43s
kubernetes-bootcamp-5d9987bd6f-mgvvl   1/1     Running            0          4m45s
kubernetes-bootcamp-7b6fc6c7d9-4d6z9   0/1     ImagePullBackOff   0          118s
kubernetes-bootcamp-7b6fc6c7d9-t7spw   0/1     ImagePullBackOff   0          118s

因为没有 v10 镜像,所以出现拉取错误,这时我们回滚到上一个正常运行的版本:

kubectl rollout undo deployments/kubernetes-bootcamp

rollout命令默认是恢复到上一个版本,但你也可以指定恢复的版本,比如恢复到版本 3:

kubectl rollout undo daemonset/abc --to-revision=3

再查看所有 Pods 的描述:

kubectl describe pods

能够看到当前使用的镜像是 v2。

posted @ 2021-05-20 21:35  thepoy  阅读(210)  评论(0编辑  收藏  举报