跟着官方文档学 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。
首先需要安装命令minikube
和kubectl
,然后使用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。
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 是一样的。
使用exit
或ctrl+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。