> 文档中心 > Python3 - k8s之深入理解 Pod

Python3 - k8s之深入理解 Pod


Python3 - k8s之深入理解 Pod

文章目录

  • Python3 - k8s之深入理解 Pod
    • 一、 静态 Pod
      • 1.1 配置文件
      • 1.2 通过 HTTP 创建静态 Pods
      • 1.3 静态pods的动态增加和删除
    • 二、 Pod Hook
      • 2.1 环境准备
      • 2.2 优雅删除资源对象
      • 2.3 Pod健康检查
    • 三、初始化容器
      • 3.1 init-pod
      • 3.2 初始化配置pod
    • 四、常见对象操作
      • 4.1 使用Replication Controller、Replica Set 管理Pod
      • 4.2 Replication Controller(RC)
      • 4.3 Replication Set(RS)
    • 五、 Deployment的使用
      • 5.1 创建
      • 5.2 滚动升级
      • 5.3 回滚Deployment
    • 六、Pod 自动扩缩容
      • 6.1 安装 influxdb
      • 6.2 安装heapster
      • 6.3 安装grafana
      • 6.5 HPA
    • 七、Job 和 Cronjob 的使用
      • 7.1 Job
      • 7.2 CronJob

一、 静态 Pod

在Kubernetes集群中除了我们经常使用到的普通的 Pod 外,还有一种特殊的 Pod,叫做Static Pod,就是我们说的静态 Pod,静态 Pod 有什么特殊的地方呢?

静态 Pod 直接由特定节点上的kubelet进程来管理,不通过 master 节点上的apiserver。无法与我们常用的控制器Deployment或者DaemonSet进行关联,它由kubelet进程自己来监控,当pod崩溃时重启该podkubelete也无法对他们进行健康检查。静态 pod 始终绑定在某一个kubelet,并且始终运行在同一个节点上。 kubelet会自动为每一个静态 pod 在 Kubernetes 的 apiserver 上创建一个镜像 Pod(Mirror Pod),因此我们可以在 apiserver 中查询到该 pod,但是不能通过 apiserver 进行控制(例如不能删除)。

创建静态 Pod 有两种方式:配置文件和 HTTP 两种方式

1.1 配置文件

配置文件就是放在特定目录下的标准的 JSON 或 YAML 格式的 pod 定义文件。用kubelet --pod-manifest-path=来启动kubelet进程,kubelet 定期的去扫描这个目录,根据这个目录下出现或消失的 YAML/JSON 文件来创建或删除静态 pod。

比如我们在 node01 这个节点上用静态 pod 的方式来启动一个 nginx 的服务。我们登录到node01节点上面,可以通过下面命令找到kubelet对应的启动配置文件

[root@k8s-node01 ~]# systemctl status kubelet● kubelet.service - kubelet: The Kubernetes Node Agent   Loaded: loaded (/usr/lib/systemd/system/kubelet.service; enabled; vendor preset: disabled)  Drop-In: /usr/lib/systemd/system/kubelet.service.d    └─10-kubeadm.conf   Active: active (running) since Mon 2022-04-04 18:32:25 CST; 2 days ago     Docs: https://kubernetes.io/docs/ Main PID: 13026 (kubelet)    Tasks: 43   Memory: 74.3M   CGroup: /system.slice/kubelet.service    └─13026 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --co...Apr 05 14:50:38 k8s-node01 kubelet[13026]: I0405 14:50:38.856208   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""Apr 05 14:50:38 k8s-node01 kubelet[13026]: I0405 14:50:38.856215   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""Apr 05 14:50:58 k8s-node01 kubelet[13026]: E0405 14:50:58.336985   13026 remote_runtime.go:295] ContainerStatus "d07a5775c1b727189585ff1...4ee747c0Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.389579   13026 reconciler.go:181] operationExecutor.UnmountVolume started for volume "...Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.396048   13026 operation_generator.go:831] UnmountVolume.TearDown succeeded for volume...Apr 05 14:50:58 k8s-node01 kubelet[13026]: I0405 14:50:58.489791   13026 reconciler.go:301] Volume detached for volume "default-token-86...ePath ""Apr 05 14:51:11 k8s-node01 kubelet[13026]: E0405 14:51:11.156490   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...dd5b0f0bApr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.094784   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...da84c631Apr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.137411   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...7698bdddApr 05 14:51:12 k8s-node01 kubelet[13026]: E0405 14:51:12.359154   13026 fsHandler.go:118] failed to collect filesystem stats - rootDisk...f845d50dHint: Some lines were ellipsized, use -l to show in full.

配置文件路径为:

[root@k8s-node01 ~]# cat /usr/lib/systemd/system/kubelet.service.d/10-kubeadm.conf # Note: This dropin only works with kubeadm and kubelet v1.11+[Service]Environment="KUBELET_KUBECONFIG_ARGS=--bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf"Environment="KUBELET_CONFIG_ARGS=--config=/var/lib/kubelet/config.yaml"# This is a file that "kubeadm init" and "kubeadm join" generates at runtime, populating the KUBELET_KUBEADM_ARGS variable dynamicallyEnvironmentFile=-/var/lib/kubelet/kubeadm-flags.env# This is a file that the user can use for overrides of the kubelet args as a last resort. Preferably, the user should use# the .NodeRegistration.KubeletExtraArgs object in the configuration files instead. KUBELET_EXTRA_ARGS should be sourced from this file.EnvironmentFile=-/etc/sysconfig/kubeletExecStart=ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_CONFIG_ARGS $KUBELET_KUBEADM_ARGS $KUBELET_EXTRA_ARGS

打开这个文件我们可以看到其中有一条如下的环境变量配置: Environment="KUBELET_SYSTEM_PODS_ARGS=--pod-manifest-path=/etc/kubernetes/manifests --allow-privileged=true"

所以如果我们通过kubeadm的方式来安装的集群环境,对应的kubelet已经配置了我们的静态 Pod 文件的路径,那就是/etc/kubernetes/manifests,所以我们只需要在该目录下面创建一个标准的 Pod 的 JSON 或者 YAML 文件即可:

如果你的 kubelet 启动参数中没有配置上面的--pod-manifest-path参数的话,那么添加上这个参数然后重启 kubelet 即可。

# 创建yaml文件[root@k8s-node01 ~]# cd /etc/kubernetes/manifests/[root@k8s-node01 manifests]# ls[root@k8s-node01 manifests]# vim static-pod.yaml
---apiVersion: v1kind: Podmetadata:  name: static-pod1  labels:    app: staticspec:  containers:  - name: web    image: nginx    ports:    - name: webport      containerPort: 80
# 查看一下, 自动生成了容器[root@k8s-node01 manifests]# docker ps | grep static096f86b8540c   nginx     "/docker-entrypoint.…"   44 seconds ago   Up 44 seconds      k8s_web_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_08ab8ee6de0fd   registry.aliyuncs.com/google_containers/pause:3.1   "/pause"   47 seconds ago   Up 47 seconds      k8s_POD_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0

1.2 通过 HTTP 创建静态 Pods

kubelet 周期地从–manifest-url=参数指定的地址下载文件,并且把它翻译成 JSON/YAML 格式的 pod 定义。此后的操作方式与–pod-manifest-path=相同,kubelet 会不时地重新下载该文件,当文件变化时对应地终止或启动静态 pod。

1.3 静态pods的动态增加和删除

如何删除或重启呢? 直接移除/etc/kubernetes/manifests/文件夹下的yaml即可, 恢复则是重新放进来

运行中的kubelet周期扫描配置的目录(我们这个例子中就是/etc/kubernetes/manifests)下文件的变化,当这个目录中有文件出现或消失时创建或删除pods。

[root@k8s-node01 manifests]# mv static-pod.yaml /tmp/[root@k8s-node01 manifests]# docker ps | grep static[root@k8s-node01 manifests]#[root@k8s-node01 manifests]# mv /tmp/static-pod.yaml ./[root@k8s-node01 manifests]# docker ps | grep static96ae6e7c78e2   nginx     "/docker-entrypoint.…"   1 second ago    Up 1 secondk8s_web_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_014b6fcd7e257   registry.aliyuncs.com/google_containers/pause:3.1   "/pause"   4 seconds ago   Up 4 seconds      k8s_POD_static-pod1-k8s-node01_default_19ab912487f4709fa8ae7020401ef414_0

其实我们用 kubeadm 安装的集群,master 节点上面的几个重要组件都是用静态 Pod 的方式运行的,我们登录到 master 节点上查看/etc/kubernetes/manifests目录:

[root@k8s-master01 ~]# cd /etc/kubernetes/manifests/[root@k8s-master01 manifests]# lsshelletcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml

那么在 k8s-master01 下创建会怎样呢?

[root@k8s-master01 ~]# cd /etc/kubernetes/manifests/[root@k8s-master01 manifests]# vim static-pod2.yaml[root@k8s-master01 manifests]# kubectl get podsNAME      READY   STATUS    RESTARTS   AGEharry-nginx-6f9f8d4465-mr2ft   1/1     Running   0   2d1hharry-nginx-6f9f8d4465-tfj78   1/1     Running   0   2d1hmy-nginx-576bb7cb54-k4gj6      1/1     Running   0   47hstatic-pod1-k8s-node01  1/1     Running   0   4m50sstatic-pod2-k8s-master011/1     Running   0   7stestservice-754455d66-m64nw    1/1     Running   2   2d13h

现在明白了吧,这种方式也为我们将集群的一些组件容器化提供了可能,因为这些 Pod 都不会受到 apiserver 的控制,不然我们这里kube-apiserver怎么自己去控制自己呢?万一不小心把这个 Pod 删掉了呢?所以只能有kubelet自己来进行控制,这就是我们所说的静态 Pod。

在这里插入图片描述

二、 Pod Hook

我们知道PodKubernetes集群中的最小单元,而 Pod 是有容器组组成的,所以在讨论 Pod 的生命周期的时候我们可以先来讨论下容器的生命周期。

实际上 Kubernetes 为我们的容器提供了生命周期钩子的,就是我们说的Pod Hook,Pod Hook 是由 kubelet 发起的,当容器中的进程启动前或者容器中的进程终止之前运行,这是包含在容器的生命周期之中。我们可以同时为 Pod 中的所有容器都配置 hook。

Kubernetes 为我们提供了两种钩子函数:

  • PostStart:这个钩子在容器创建后立即执行。但是,并不能保证钩子将在容器ENTRYPOINT之前运行,因为没有参数传递给处理程序。主要用于资源部署、环境准备等。不过需要注意的是如果钩子花费太长时间以至于不能运行或者挂起, 容器将不能达到running状态。
  • PreStop:这个钩子在容器终止之前立即被调用。它是阻塞的,意味着它是同步的, 所以它必须在删除容器的调用发出之前完成。主要用于优雅关闭应用程序、通知其他系统等。如果钩子在执行期间挂起, Pod阶段将停留在running状态并且永不会达到failed状态。

如果PostStart或者PreStop钩子失败, 它会杀死容器。所以我们应该让钩子函数尽可能的轻量。当然有些情况下,长时间运行命令是合理的, 比如在停止容器之前预先保存状态。

另外我们有两种方式来实现上面的钩子函数:

  • Exec - 用于执行一段特定的命令,不过要注意的是该命令消耗的资源会被计入容器。
  • HTTP - 对容器上的特定的端点执行HTTP请求。

2.1 环境准备

以下示例中,定义了一个Nginx Pod,其中设置了PostStart钩子函数,即在容器创建成功后,写入一句话到/usr/share/message文件中。

---apiVersion: v1kind: Podmetadata:  name: hook-demo  labels:    app: hookspec:  containers:  - name: hook-demo    image: nginx    ports:    - name: webport      containerPort: 80    lifecycle:      postStart: exec:   command: ["/bin/sh", "-c", "echo hello from the postStart Handler > /usr/share/message"]
[root@k8s-master01 kubeadm]# kubectl apply -f pod-hook1.yaml pod/hook-demo created[root@k8s-master01 kubeadm]# kubectl get podsNAME      READY   STATUS    RESTARTS   AGEhook-demo 1/1     Running   0   7s

2.2 优雅删除资源对象

当用户请求删除含有 pod 的资源对象时(如Deployment等),K8S 为了让应用程序优雅关闭(即让应用程序完成正在处理的请求后,再关闭软件),K8S提供两种信息通知:

  • 默认:K8S 通知 node 执行docker stop命令,docker 会先向容器中PID为1的进程发送系统信号SIGTERM,然后等待容器中的应用程序终止执行,如果等待时间达到设定的超时时间,或者默认超时时间(30s),会继续发送SIGKILL的系统信号强行 kill 掉进程。
  • 使用 pod 生命周期(利用PreStop回调函数),它执行在发送终止信号之前。

默认所有的优雅退出时间都在30秒内。kubectl delete 命令支持 --grace-period=选项,这个选项允许用户用他们自己指定的值覆盖默认值。值’0’代表 强制删除 pod. 在 kubectl 1.5 及以上的版本里,执行强制删除时必须同时指定 --force --grace-period=0

强制删除一个 pod 是从集群状态还有 etcd 里立刻删除这个 pod。 当 Pod 被强制删除时, api 服务器不会等待来自 Pod 所在节点上的 kubelet 的确认信息:pod 已经被终止。在 API 里 pod 会被立刻删除,在节点上, pods 被设置成立刻终止后,在强行杀掉前还会有一个很小的宽限期。

以下示例中,定义了一个Nginx Pod,其中设置了PreStop钩子函数,即在容器退出之前,优雅的关闭 Nginx:

方法1 强制删除pod:

[root@k8s-master01 kubeadm]# kubectl delete pod hook-demo --grace-period=0 --force warning: Immediate deletion does not wait for confirmation that the running resource has been terminated. The resource may continue to run on the cluster indefinitely.pod "hook-demo" force deleted

方式2 优雅的删除pod:

---apiVersion: v1kind: Podmetadata:  name: hook-demo2  labels:    app: hookspec:  containers:  - name: hook-demo2    image: nginx    ports:    - name: webport      containerPort: 80    volumeMounts:    - name: message      mountPath: /usr/share    lifecycle:      preStop: exec:   command: ["/bin/sh", "-c", "echo hello from the postStart Handler > /usr/share/message"]  volumes:  - name: message    hostPath:      path: /tmp
[root@k8s-master01 kubeadm]# kubectl apply -f pod-hook2.yaml pod/hook-demo2 created[root@k8s-master01 kubeadm]# kubectl get podsNAME      READY   STATUS    RESTARTS   AGEhook-demo 1/1     Running   0   17mhook-demo21/1     Running   0   7s

另外Hook调用的日志没有暴露个给 Pod 的 event,所以只能通过describe命令来获取,如果有错误将可以看到FailedPostStartHookFailedPreStopHook这样的 event。

2.3 Pod健康检查

liveness probe(存活探针)和readiness probe(可读性探针)

上面我们和大家一起学习了Pod中容器的生命周期的两个钩子函数,PostStartPreStop,其中PostStart是在容器创建后立即执行的,而PreStop这个钩子函数则是在容器终止之前执行的。除了上面这两个钩子函数以外,还有一项配置会影响到容器的生命周期的,那就是健康检查的探针。

Kubernetes集群当中,我们可以通过配置liveness probe(存活探针)和readiness probe(可读性探针)来影响容器的生存周期。

  • kubelet 通过使用 liveness probe 来确定你的应用程序是否正在运行,通俗点将就是是否还活着。一般来说,如果你的程序一旦崩溃了, Kubernetes 就会立刻知道这个程序已经终止了,然后就会重启这个程序。而我们的 liveness probe 的目的就是来捕获到当前应用程序还没有终止,还没有崩溃,如果出现了这些情况,那么就重启处于该状态下的容器,使应用程序在存在 bug 的情况下依然能够继续运行下去。
  • kubelet 使用 readiness probe 来确定容器是否已经就绪可以接收流量过来了。这个探针通俗点讲就是说是否准备好了,现在可以开始工作了。只有当 Pod 中的容器都处于就绪状态的时候 kubelet 才会认定该 Pod 处于就绪状态,因为一个 Pod 下面可能会有多个容器。当然 Pod 如果处于非就绪状态,那么我们就会将他从我们的工作队列(实际上就是我们后面需要重点学习的 Service)中移除出来,这样我们的流量就不会被路由到这个 Pod 里面来了。

和前面的钩子函数一样的,我们这两个探针的支持两种配置方式:

* exec:执行一段命令* http:检测某个 http 请求* tcpSocket:使用此配置, kubelet 将尝试在指定端口上打开容器的套接字。如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的。实际上就是检查端口

好,我们先来给大家演示下存活探针的使用方法,首先我们用exec执行命令的方式来检测容器的存活,如下:

[root@k8s-master01 kubeadm]# vim liveness-exec.yaml
---apiVersion: v1kind: Podmetadata:  name: liveness-exec  labels:    app: livenessspec:  containers:  - name: liveness    image: busybox    args:    - /bin/sh    - -c    - touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600    livenessProbe:      exec: command: - cat - /tmp/healthy      initialDelaySeconds: 5      periodSeconds: 5

我们这里需要用到一个新的属性:livenessProbe,下面通过exec执行一段命令,其中periodSeconds属性表示让kubelet每隔5秒执行一次存活探针,也就是每5秒执行一次上面的cat /tmp/healthy命令,如果命令执行成功了,将返回0,那么kubelet就会认为当前这个容器是存活的并且很监控,如果返回的是非0值,那么kubelet就会把该容器杀掉然后重启它。另外一个属性initialDelaySeconds表示在第一次执行探针的时候要等待5秒,这样能够确保我们的容器能够有足够的时间启动起来。大家可以想象下,如果你的第一次执行探针等候的时间太短,是不是很有可能容器还没正常启动起来,所以存活探针很可能始终都是失败的,这样就会无休止的重启下去了,对吧?所以一个合理的initialDelaySeconds非常重要。

[root@k8s-master01 kubeadm]# kubectl apply -f liveness-exec.yaml pod/liveness-exec created

另外我们在容器启动的时候,执行了如下命令:

 ~ /bin/sh -c "touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 600"

意思是说在容器最开始的30秒内有一个/tmp/healthy文件,在这30秒内执行cat /tmp/healthy命令都会返回一个成功的返回码。30秒后,我们删除这个文件,现在执行cat /tmp/healthy是不是就会失败了,这个时候就会重启容器了。

我们来创建下该Pod,在30秒内,查看PodEvent

~ kubectl describe pod liveness-exec

我们可以观察到容器是正常启动的,在隔一会儿,比如40s后,再查看下PodEvent,在最下面有一条信息显示 liveness probe失败了,容器被删掉并重新创建。

然后通过kubectl get pod liveness-exec可以看到RESTARTS值加1了。

同样的,我们还可以使用HTTP GET请求来配置我们的存活探针,我们这里使用一个liveness镜像来验证演示下,

---apiVersion: v1kind: Podmetadata:  name: liveness-http  labels:    name: livenessspec:  containers:  - name: liveness    image: cnych/liveness    args:    - /server    livenessProbe:      httpGet: path: /healthz port: 8080      initialDelaySeconds: 3      periodSeconds: 3

同样的,根据periodSeconds属性我们可以知道kubelet需要每隔3秒执行一次liveness probe,该探针将向容器中的 server 的8080端口发送一个 HTTP GET 请求。如果 server 的 /healthz 路径的 handler 返回一个成功的返回码,kubelet就会认定该容器是活着的并且很健康,如果返回失败的返回码,kubelet将杀掉该容器并重启它。。initialDelaySeconds 指定kubelet在该执行第一次探测之前需要等待3秒钟。

通常来说,任何大于200小于400的返回码都会认定是成功的返回码。其他返回码都会被认为是失败的返回码。

我们可以来查看下上面的healthz的实现

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {    duration := time.Now().Sub(started)    if duration.Seconds() > 10 { w.WriteHeader(500) w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))    } else { w.WriteHeader(200) w.Write([]byte("ok"))    }})

大概意思就是最开始前10s返回状态码200,10s过后就返回500的status_code了。所以当容器启动3秒后,kubelet 开始执行健康检查。第一次健康监测会成功,因为是在10s之内,但是10秒后,健康检查将失败,因为现在返回的是一个错误的状态码了,所以kubelet将会杀掉和重启容器。

同样的,我们来创建下该Pod测试下效果,10秒后,查看 Pod 的 event,确认liveness probe失败并重启了容器。

~ kubectl describe pod liveness-http

然后我们来通过端口的方式来配置存活探针,使用此配置,kubelet将尝试在指定端口上打开容器的套接字。 如果可以建立连接,容器被认为是健康的,如果不能就认为是失败的。

[root@k8s-master01 kubeadm]# kubectl get podsNAME      READY   STATUS      RESTARTS   AGEhook-demo 1/1     Running     0   148mliveness-exec    0/1     CrashLoopBackOff   7   14mliveness-http    0/1     CrashLoopBackOff   6   7m7s
---apiVersion: v1kind: Podmetadata:  name: liveness-readiness  labels:    name: liveness-readinessspec:  containers:  - name: liveness-readiness    image: cnych/liveness    args:    - /server    livenessProbe:      httpGet: path: /healthz port: 8080      initialDelaySeconds: 5      periodSeconds: 5    readinessProbe:      tcpSocket: port: 8080      initialDelaySeconds: 3      periodSeconds: 3

我们可以看到,TCP 检查的配置与 HTTP 检查非常相似,只是将httpGet替换成了tcpSocket。 而且我们同时使用了readiness probeliveness probe两种探针。 容器启动后5秒后,kubelet将发送第一个readiness probe(可读性探针)。 该探针会去连接容器的8080端,如果连接成功,则该 Pod 将被标记为就绪状态。然后Kubelet将每隔10秒钟执行一次该检查。

除了readiness probe之外,该配置还包括liveness probe。 容器启动15秒后,kubelet将运行第一个 liveness probe。 就像readiness probe一样,这将尝试去连接到容器的8080端口。如果liveness probe失败,容器将重新启动。

有的时候,应用程序可能暂时无法对外提供服务,例如,应用程序可能需要在启动期间加载大量数据或配置文件。 在这种情况下,您不想杀死应用程序,也不想对外提供服务。 那么这个时候我们就可以使用readiness probe来检测和减轻这些情况。 Pod中的容器可以报告自己还没有准备,不能处理Kubernetes服务发送过来的流量。

从上面的YAML文件我们可以看出readiness probe的配置跟liveness probe很像,基本上一致的。唯一的不同是使用readinessProbe而不是livenessProbe。两者如果同时使用的话就可以确保流量不会到达还未准备好的容器,准备好过后,如果应用程序出现了错误,则会重新启动容器。

另外除了上面的initialDelaySecondsperiodSeconds属性外,探针还可以配置如下几个参数:

* timeoutSeconds:探测超时时间,默认1秒,最小1秒。* successThreshold:探测失败后,最少连续探测成功多少次才被认定为成功。默认是 1,但是如果是`liveness`则必须是 1。最小值是 1。* failureThreshold:探测成功后,最少连续探测失败多少次才被认定为失败。默认是 3,最小值是 1。

这就是liveness probe(存活探针)和readiness probe(可读性探针)的使用方法。在Pod的生命周期当中,我们已经学习了容器生命周期中的钩子函数和探针检测,接下来讲解Pod层面生命周期的一个阶段:初始化容器。

三、初始化容器

上面我们学习了容器的健康检查的两个探针:liveness probe(存活探针)和readiness probe(可读性探针)的使用方法,我们说在这两个探针是可以影响容器的生命周期的,包括我们之前提到的容器的两个钩子函数PostStartPreStop。我们今天要给大家介绍的是Init Container(初始化容器)。

Init Container就是用来做初始化工作的容器,可以是一个或者多个,如果有多个的话,这些容器会按定义的顺序依次执行,只有所有的Init Container执行完后,主容器才会被启动。我们知道一个Pod里面的所有容器是共享数据卷和网络命名空间的,所以Init Container里面产生的数据可以被主容器使用到的。

是不是感觉Init Container和之前的钩子函数有点类似啊,只是是在容器执行前来做一些工作,是吧?从直观的角度看上去的话,初始化容器的确有点像PreStart,但是钩子函数和我们的Init Container是处在不同的阶段的,我们可以通过下面的图来了解下:

在这里插入图片描述

从上面这张图我们可以直观的看到PostStartPreStop包括livenessreadiness是属于主容器的生命周期范围内的,而Init Container是独立于主容器之外的,当然他们都属于Pod的生命周期范畴之内的,现在我们应该明白Init Container和钩子函数之类的区别了吧。

另外我们可以看到上面我们的Pod右边还有一个infra的容器,这是一个什么容器呢?我们可以在集群环境中去查看下人任意一个Pod对应的运行的Docker容器,我们可以发现每一个Pod下面都包含了一个pause-amd64的镜像,这个就是我们的infra镜像,我们知道Pod下面的所有容器是共享同一个网络命名空间的,这个镜像就是来做这个事情的,所以每一个Pod当中都会包含一个这个镜像。

最开始 Pod 启动不起来就是因为这个 infra 镜像没有被拉下来,因为默认该镜像是需要到谷歌服务器上拉取的,所以需要提前拉取到节点上面。

我们说Init Container主要是来做初始化容器工作的,那么他有哪些应用场景呢?

  • 等待其他模块Ready:这个可以用来解决服务之间的依赖问题,比如我们有一个 Web 服务,该服务又依赖于另外一个数据库服务,但是在我们启动这个 Web 服务的时候我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个 InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器 Web 服务被启动起来,这个时候去连接数据库就不会有问题了。
  • 做初始化配置:比如集群里检测所有已经存在的成员节点,为主容器准备好集群的配置信息,这样主容器起来后就能用这个配置信息加入集群。
  • 其它场景:如将 pod 注册到一个中央数据库、配置中心等。

3.1 init-pod

我们先来给大家演示下服务依赖的场景下初始化容器的使用方法,如下Pod的定义方法

---apiVersion: v1kind: Podmetadata:  name: init-pod  labels:    app: initspec:  initContainers:  - name: init-myservice    image: busybox    command: ['sh', '-c', 'until nslookpup myservice; do echo waiting for myservice; sleep 2; done;']  - name: init-mydb    image: busybox    command: ['sh', '-c', 'until nslookpup mydb; do echo waiting for mydb; sleep 2; done;']  containers:  - name: main-container    image: busybox    command: ["sh", "-c", "echo The app is running! && sleep 3600"]

Service的对应YAML内容:

---apiVersion: v1kind: Servicemetadata:  name: myservicespec:  ports:  - protocol: TCP    port: 80    targetPort: 6379---apiVersion: v1kind: Servicemetadata:  name: mydbspec:  ports:  - protocol: TCP    port: 80    targetPort: 6378
[root@k8s-master01 kubeadm]# kubectl get podsNAME      READY   STATUS      RESTARTS   AGEhook-demo 1/1     Running     0   3h23minit-pod  1/1     Running     0   15m

我们可以先创建上面的Pod,然后查看下Pod的状态,然后再创建下面的Service,对比下前后状态。

我们在Pod启动过程中,初始化容器会按顺序在网络和数据卷初始化之后启动。每个容器必须在下一个容器启动之前成功退出。如果由于运行时或失败退出,导致容器启动失败,它会根据PodrestartPolicy指定的策略进行重试。 然而,如果 Pod 的 restartPolicy 设置为 Always,Init 容器失败时会使用 RestartPolicy 策略。

在所有的初始化容器没有成功之前,Pod将不会变成 Ready状态。正在初始化中的Pod处于Pending状态,但应该会将条件Initializing设置为 true。

3.2 初始化配置pod

接下来我们再来尝试创建一个做初始化配置工作的Pod

---apiVersion: v1kind: Podmetadata:  name: init-demo  labels:    app: initspec:  initContainers:  - name: install    image: busybox    command:    - wget    - "-O"    - "/work-dir/index.html"    - https://www.baidu.com    volumeMounts:    - name: workdir      mountPath: /work-dir  containers:  - name: nginx    image: nginx    ports:    - containerPort: 80    volumeMounts:    - name: workdir      mountPath: /usr/share/nginx/html  volumes:  - name: workdir    emptyDir: {}

我们可以看到这里又出现了volumesspec.volumes指的是Pod中的卷,spec.containers.volumeMounts,是将指定的卷 mount 到容器指定的位置,相当于docker里面的-v 宿主机目录:容器目录,我们前面用到过hostPath,我们这里使用的是emptyDir{},这个就相当于一个共享卷,是一个临时的目录,生命周期等同于Pod的生命周期。

初始化容器执行完,会下载一个 html 文件映射到emptyDir{},而主容器也是和 spec.volumes 里的emptyDir{} 进行映射,所以nginx容器的/usr/share/nginx/html`目录下会映射 index.html 文件。

我们来创建下该Pod

[root@k8s-master01 kubeadm]# kubectl create -f initconfig.yaml[root@k8s-master01 kubeadm]# kubectl get pod init-demo NAME READY   STATUS    RESTARTS   AGEinit-demo   1/1     Running   0   5m33s
[root@k8s-master01 kubeadm]# kubectl exec -it init-demo -- /bin/bashroot@init-demo:/# cd /usr/share/nginx/html/root@init-demo:/usr/share/nginx/html# lsindex.htmlroot@init-demo:/usr/share/nginx/html# cat index.html <!DOCTYPE html><!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css><title>百度一下,你就知道</title></head> <body link=#0000cc> 

如果我们看到有百度相关的信息那么证明我们上面的初始化的工作就完成了。

这就是我们初始化容器的使用方法,到这里我们就把Pod的整个生命周期当中的几个主要阶段讲完了,第一个是容器的两个钩子函数:PostStartPreStop,还有就是容器健康检查的两个探针:liveness probereadiness probe,以及本次的Init Container。下面开始我们来讲解一些常用的控制器和Pod的结合。

四、常见对象操作

4.1 使用Replication Controller、Replica Set 管理Pod

前面我们学习了Pod的一些基本使用方法,而且前面我们都是直接来操作的Pod,假如我们现在有一个Pod正在提供线上的服务,我们来想想一下我们可能会遇到的一些场景:

  • 某次运营活动非常成功,网站访问量突然暴增
  • 运行当前Pod的节点发生故障了,Pod不能正常提供服务了

第一种情况,可能比较好应对,一般活动之前我们会大概计算下会有多大的访问量,提前多启动几个Pod,活动结束后再把多余的Pod杀掉,虽然有点麻烦,但是应该还是能够应对这种情况的。

第二种情况,可能某天夜里收到大量报警说服务挂了,然后起来打开电脑在另外的节点上重新启动一个新的Pod,问题也很好的解决了。

如果我们都人工的去解决遇到的这些问题,似乎又回到了以前刀耕火种的时代了是吧,如果有一种工具能够来帮助我们管理Pod就好了,Pod不够了自动帮我新增一个,Pod挂了自动帮我在合适的节点上重新启动一个Pod,这样是不是遇到上面的问题我们都不需要手动去解决了。

幸运的是,Kubernetes就为我们提供了这样的资源对象:

  • Replication Controller:用来部署、升级Pod
  • Replica Set:下一代的Replication Controller
  • Deployment:可以更加方便的管理PodReplica Set

4.2 Replication Controller(RC)

Replication Controller简称RCRCKubernetes系统中的核心概念之一,简单来说,RC可以保证在任意时间运行Pod的副本数量,能够保证Pod总是可用的。如果实际Pod数量比指定的多那就结束掉多余的,如果实际数量比指定的少就新启动一些Pod,当Pod失败、被删除或者挂掉后,RC都会去自动创建新的Pod来保证副本数量,所以即使只有一个Pod,我们也应该使用RC来管理我们的Pod

我们想想如果现在我们遇到上面的问题的话,可能除了第一个不能做到完全自动化,其余的我们是不是都不用担心了,运行Pod的节点挂了,RC检测到Pod失败了,就会去合适的节点重新启动一个Pod就行,不需要我们手动去新建一个Pod了。如果是第一种情况的话在活动开始之前我们给Pod指定10个副本,结束后将副本数量改成2,这样是不是也远比我们手动去启动、手动去关闭要好得多,而且我们后面还会给大家介绍另外一种资源对象HPA可以根据资源的使用情况来进行自动扩缩容,这样以后遇到这种情况,我们就真的可以安心的去睡觉了。

现在我们来使用RC来管理我们前面使用的NginxPodYAML文件如下:

---apiVersion: v1kind: ReplicationControllermetadata:  name: rc-demo  labels:    app: rcspec:  replicas: 3  template:    metadata:      labels: app: rc    spec:      containers:      - name: nginx-demo image: nginx ports: - containerPort: 80

上面的YAML文件相对于我们之前的Pod的格式:

  • kind:ReplicationController
  • spec.replicas: 指定Pod副本数量,默认为1
  • spec.selector: RC通过该属性来筛选要控制的Pod
  • spec.template: 这里就是我们之前的Pod的定义的模块,但是不需要apiVersionkind
  • spec.template.metadata.labels: 注意这里的Podlabels要和spec.selector相同,这样RC就可以来控制当前这个Pod了。

这个YAML文件中的意思就是定义了一个RC资源对象,它的名字叫rc-demo,保证一直会有3个Pod运行,Pod的镜像是nginx镜像。

注意spec.selectorspec.template.metadata.labels这两个字段必须相同,否则会创建失败的,当然我们也可以不写spec.selector,这样就默认与Pod模板中的metadata.labels相同了。所以为了避免不必要的错误的话,不写为好。

[root@k8s-master01 kubeadm]# kubectl create -f rc-demo.yaml replicationcontroller/rc-demo created[root@k8s-master01 kubeadm]# kubectl get podsNAME      READY   STATUS      RESTARTS   AGErc-demo-54b47    1/1     Running     0   16src-demo-s995l    1/1     Running     0   16src-demo-vpnvl    1/1     Running     0   16s
[root@k8s-master01 kubeadm]# kubectl get pods -o wideNAME   READY   STATUS      RESTARTS   AGE     IP     NODE    rc-demo-54b47 1/1     Running     0   103s    10.244.1.86   k8s-node01rc-demo-s995l 1/1     Running     0   103s    10.244.2.93   k8s-node02rc-demo-vpnvl 1/1     Running     0   103s    10.244.1.85   k8s-node01# 查看rc[root@k8s-master01 kubeadm]# kubectl get rcNAME      DESIRED   CURRENT   READY   AGErc-demo   3  3  35m3s# 然后我们通过RC来修改下Pod的副本数量为2:[root@k8s-master01 kubeadm]# kubectl apply -f rc-demo.yaml# 或者直接编辑[root@k8s-master01 kubeadm]# kubectl edit rc rc-demo

而且我们还可以用RC来进行滚动升级,比如我们将镜像地址更改为nginx:1.7.9:

[root@k8s-master01 kubeadm]# kubectl rolling-update rc-demo --image=nginx:1.7.9

但是如果我们的Pod中多个容器的话,就需要通过修改YAML文件来进行修改了:

[root@k8s-master01 kubeadm]# kubectl rolling-update rc-demo -f rc-demo.yaml

如果升级完成后出现了新的问题,想要一键回滚到上一个版本的话,使用RC只能用同样的方法把镜像地址替换成之前的,然后重新滚动升级。

在这里插入图片描述

4.3 Replication Set(RS)

Replication Set简称RS,随着Kubernetes的高速发展,官方已经推荐我们使用RSDeployment来代替RC了,实际上RSRC的功能基本一致,目前唯一的一个区别就是RC只支持基于等式的selector(env=dev或environment!=qa),但RS还支持基于集合的selector(version in (v1.0, v2.0)),这对复杂的运维管理就非常方便了。

kubectl命令行工具中关于RC的大部分命令同样适用于我们的RS资源对象。不过我们也很少会去单独使用RS,它主要被Deployment这个更加高层的资源对象使用,除非用户需要自定义升级功能或根本不需要升级Pod,在一般情况下,我们推荐使用Deployment而不直接使用Replica Set

最后我们总结下关于RC/RS的一些特性和作用吧:

  • 大部分情况下,我们可以通过定义一个RC实现的Pod的创建和副本数量的控制
  • RC中包含一个完整的Pod定义模块(不包含apiversionkind
  • RC是通过label selector机制来实现对Pod副本的控制的
  • 通过改变RC里面的Pod副本数量,可以实现Pod的扩缩容功能
  • 通过改变RC里面的Pod模板中镜像版本,可以实现Pod的滚动升级功能(但是不支持一键回滚,需要用相同的方法去修改镜像地址)

好,我就给大家介绍了使用RC或者RS来管理我们的Pod,下面来给大家介绍另外一种更加高级也是现在推荐使用的一个资源对象Deployment

五、 Deployment的使用

前面我们学习了Replication ControllerReplica Set两种资源对象,RCRS的功能基本上是差不多的,唯一的区别就是RS支持集合的selector。我们也学习到了用RC/RS来控制Pod副本的数量,也实现了滚动升级Pod的功能。现在看上去似乎一切都比较完美的运行着,但是我们上面最后也提到了现在我们推荐使用Deployment这种控制器了,而不是我们之前的RC或者RS,这是为什么呢?

没有对比就没有伤害对吧,我们来对比下二者之间的功能吧,首先RCKubernetes的一个核心概念,当我们把应用部署到集群之后,需要保证应用能够持续稳定的运行,RC就是这个保证的关键,主要功能如下:

  • 确保Pod数量:它会确保Kubernetes中有指定数量的Pod在运行,如果少于指定数量的PodRC就会创建新的,反之这会删除多余的,保证Pod的副本数量不变。
  • 确保Pod健康:当Pod不健康,比如运行出错了,总之无法提供正常服务时,RC也会杀死不健康的Pod,重新创建新的。
  • 弹性伸缩:在业务高峰或者低峰的时候,可以用过RC来动态的调整Pod数量来提供资源的利用率,当然我们也提到过如果使用HPA这种资源对象的话可以做到自动伸缩。
  • 滚动升级:滚动升级是一种平滑的升级方式,通过逐步替换的策略,保证整体系统的稳定性,这个我们上面已经给大家演示过了。

Deployment同样也是Kubernetes系统的一个核心概念,主要职责和RC一样的都是保证Pod的数量和健康,二者大部分功能都是完全一致的,我们可以看成是一个升级版的RC控制器,那Deployment又具备那些新特性呢?

  • RC的全部功能:Deployment具备上面描述的RC的全部功能
  • 事件和状态查看:可以查看Deployment的升级详细进度和状态
  • 回滚:当升级Pod的时候如果出现问题,可以使用回滚操作回滚到之前的任一版本
  • 版本记录:每一次对Deployment的操作,都能够保存下来,这也是保证可以回滚到任一版本的基础
  • 暂停和启动:对于每一次升级都能够随时暂停和启动

作为对比,我们知道Deployment作为新一代的RC,不仅在功能上更为丰富了,同时我们也说过现在官方也都是推荐使用Deployment来管理Pod的,比如一些官方组件kube-dnskube-proxy也都是使用的Deployment来管理的,所以当大家在使用的使用也最好使用Deployment来管理Pod

5.1 创建

在这里插入图片描述

可以看出一个Deployment拥有多个Replica Set,而一个Replica Set拥有一个或多个Pod。一个Deployment控制多个rs主要是为了支持回滚机制,每当Deployment操作时,Kubernetes会重新生成一个Replica Set并保留,以后有需要的话就可以回滚至之前的状态。 下面创建一个Deployment,它创建了一个Replica Set来启动3个nginx pod,yaml文件如下:

---apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploy  labels:    app: nginx-demospec:  replicas: 3  selector:    matchLabels:      app: nginx  template:    metadata:      labels: app: nginx    spec:      containers:      - name: nginx image: nginx:1.7.9 ports: - containerPort: 80
# 创建[root@k8s-master01 kubeadm]# kubectl create -f deploy-demo.yaml deployment.apps/nginx-deploy created# 查询一下[root@k8s-master01 kubeadm]# kubectl get podsNAME   READY   STATUS      RESTARTS   AGEnginx-deploy-54f57cf6bf-9lbns      1/1     Running     0   50snginx-deploy-54f57cf6bf-h9wxl      1/1     Running     0   50snginx-deploy-54f57cf6bf-mb2cc      1/1     Running     0   50s[root@k8s-master01 kubeadm]# kubectl get deploymentsNAME    READY   UP-TO-DATE   AVAILABLE   AGEnginx-deploy   3/3     3     3    116s[root@k8s-master01 kubeadm]# kubectl get rsNAME DESIRED   CURRENT   READY   AGEnginx-deploy-54f57cf6bf   3  3  32m31s

上面的Deployment的yaml文件中的replicas:3将会保证我们始终有3个POD在运行。

由于DeploymentRC的功能大部分都一样的,我们上面已经和大家演示了大部分功能了,我们这里重点给大家演示下Deployment的滚动升级和回滚功能。

5.2 滚动升级

现在我们将刚刚保存的yaml文件中的nginx镜像修改为nginx:1.13.3,然后在spec下面添加滚动升级策略:

minReadySeconds: 5strategy:  # indicate which strategy we want for rolling update  type: RollingUpdate  rollingUpdate:    maxSurge: 1    maxUnavailable: 1
  • minReadySeconds:
    • Kubernetes在等待设置的时间后才进行升级
    • 如果没有设置该值,Kubernetes会假设该容器启动起来后就提供服务了
    • 如果没有设置该值,在某些极端情况下可能会造成服务不正常运行
  • maxSurge:
    • 升级过程中最多可以比原先设置多出的POD数量
    • 例如:maxSurage=1,replicas=5,则表示Kubernetes会先启动1一个新的Pod后才删掉一个旧的POD,整个升级过程中最多会有5+1个POD。
  • maxUnavaible:
    • 升级过程中最多有多少个POD处于无法提供服务的状态
    • maxSurge不为0时,该值也不能为0
    • 例如:maxUnavaible=1,则表示Kubernetes整个升级过程中最多会有1个POD处于无法服务的状态。

修改后如下:

---apiVersion: apps/v1kind: Deploymentmetadata:  name: nginx-deploy  labels:    app: nginx-demospec:  replicas: 3  revisionHistoryLimit: 15  minReadySeconds: 5  strategy:    type: RollingUpdate    rpllingUpdate:      maxSurge: 1      maxUnavailable: 1  selector:    matchLabels:      app: nginx  template:    metadata:      labels: app: nginx    spec:      containers:      - name: nginx image: nginx ports: - containerPort: 80
# 应用更新一下[root@k8s-master01 kubeadm]# kubectl apply -f deploy-demo.yaml --record=trueWarning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl applydeployment.apps/nginx-deploy configured

然后我们可以使用rollout命令:

  • 查看状态:

    [root@k8s-master01 kubeadm]# kubectl rollout status deployment/nginx-deploydeployment "nginx-deploy" successfully rolled out
  • 暂停升级

    [root@k8s-master01 kubeadm]# kubectl rollout pause deployment deploy-demo.yaml 
  • 继续升级

    [root@k8s-master01 kubeadm]# kubectl rollout resume deployment deploy-demo.yaml 

    升级结束后,继续查看rs的状态:

    [root@k8s-master01 kubeadm]# kubectl get rsNAME DESIRED   CURRENT   READY   AGEnginx-deploy-54f57cf6bf   3  3  312m

    查看升级过程:

    [root@k8s-master01 kubeadm]# kubectl rollout status deployment/nginx-deployWaiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...Waiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...Waiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...Waiting for deployment "nginx-deploy" rollout to finish: 2 out of 3 new replicas have been updated...Waiting for deployment "nginx-deploy" rollout to finish: 2 old replicas are pending termination...Waiting for deployment "nginx-deploy" rollout to finish: 2 of 3 updated replicas are available...Waiting for deployment "nginx-deploy" rollout to finish: 2 of 3 updated replicas are available...deployment "nginx-deploy" successfully rolled out

    根据AGE我们可以看到离我们最近的当前状态是:3,和我们的yaml文件是一致的,证明升级成功了。用describe命令可以查看升级的全部信息:

[root@k8s-master01 kubeadm]# kubectl describe deployment/nginx-deployName:     nginx-deployNamespace:defaultCreationTimestamp:      Thu, 07 Apr 2022 16:58:23 +0800Labels:   app=nginx-demoAnnotations:     deployment.kubernetes.io/revision: 2   kubectl.kubernetes.io/last-applied-configuration:     {"apiVersion":"apps/v1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"nginx-demo"},"name":"nginx-deploy","namespace":"...Selector: app=nginxReplicas: 3 desired | 3 updated | 3 total | 3 available | 0 unavailableStrategyType:    RollingUpdateMinReadySeconds: 5RollingUpdateStrategy:  1 max unavailable, 1 max surgePod Template:  Labels:  app=nginx  Containers:   nginx:    Image: nginx    Port:  80/TCP    Host Port:    0/TCP    Environment:  <none>    Mounts:<none>  Volumes: <none>Conditions:  Type    Status  Reason  ----    ------  ------  Available      True    MinimumReplicasAvailable  Progressing    True    NewReplicaSetAvailableOldReplicaSets:  <none>NewReplicaSet:   nginx-deploy-6fcf476c4 (3/3 replicas created)Events:  Type    Reason      Age    From     Message  ----    ------      ----   ----     -------  Normal  ScalingReplicaSet  16m    deployment-controller  Scaled up replica set nginx-deploy-54f57cf6bf to 3  Normal  ScalingReplicaSet  2m23s  deployment-controller  Scaled up replica set nginx-deploy-6fcf476c4 to 1  Normal  ScalingReplicaSet  2m23s  deployment-controller  Scaled down replica set nginx-deploy-54f57cf6bf to 2  Normal  ScalingReplicaSet  2m23s  deployment-controller  Scaled up replica set nginx-deploy-6fcf476c4 to 2  Normal  ScalingReplicaSet  2m16s  deployment-controller  Scaled down replica set nginx-deploy-54f57cf6bf to 0  Normal  ScalingReplicaSet  2m16s  deployment-controller  Scaled up replica set nginx-deploy-6fcf476c4 to 3

5.3 回滚Deployment

[root@k8s-master01 kubeadm]# kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deploy REVISION  CHANGE-CAUSE1  <none>2  <none>3  kubectl apply --filename=deploy-demo.yaml --record=true4  kubectl apply --filename=deploy-demo.yaml --record=true

从上面的结果可以看出在执行Deployment升级的时候最好带上record参数,便于我们查看历史版本信息。

默认情况下,所有通过kubectl xxxx --record都会被kubernetes记录到etcd进行持久化,这无疑会占用资源,最重要的是,时间久了,当你kubectl get rs时,会有成百上千的垃圾RS返回给你,那时你可能就眼花缭乱了。

上生产时,我们最好通过设置Deployment的.spec.revisionHistoryLimit来限制最大保留的revision number,比如15个版本,回滚的时候一般只会回滚到最近的几个版本就足够了。其实rollout history中记录的revision都和ReplicaSets一一对应。如果手动delete某个ReplicaSet,对应的rollout history就会被删除,也就是还说你无法回滚到这个revison了。

rollout historyReplicaSet的对应关系,可以在kubectl describe rs $RSNAME返回的revision字段中得到,这里的revision就对应着rollout history返回的revison

同样我们可以使用下面的命令查看单个revison的信息:

[root@k8s-master01 kubeadm]# kubectl rollout history deployment nginx-deploy --revision=3deployment.apps/nginx-deploy with revision #3Pod Template:  Labels:app=nginx pod-template-hash=6757fb8478  Annotations:  kubernetes.io/change-cause: kubectl apply --filename=deploy-demo.yaml --record=true  Containers:   nginx:    Image:      nginx:1.10.0    Port:80/TCP    Host Port:  0/TCP    Environment: <none>    Mounts:     <none>  Volumes:      <none>

假如现在要直接回退到当前版本的前一个版本:

# 回退到当前版本的前一个版本[root@k8s-master01 kubeadm]# kubectl rollout undo deployment nginx-deploydeployment.apps/nginx-deploy rolled back

当然也可以用revision回退到指定的版本:

[root@k8s-master01 kubeadm]# kubectl rollout undo deployment nginx-deploy --to-revision=1deployment.apps/nginx-deploy rolled back

现在可以用命令查看Deployment现在的状态了。

[root@k8s-master01 kubeadm]# kubectl get rsNAME DESIRED   CURRENT   READY   AGEharry-nginx-6f9f8d4465    2  2  22d7hmy-nginx-576bb7cb541  1  12d20hnginx-deploy-54f57cf6bf   3  3  329mnginx-deploy-6757fb8478   0  0  08m17snginx-deploy-6fcf476c4    0  0  014mnginx-deploy-bb4469db5    0  0  06m53stestservice-754455d66     1  1  12d19h# 删除指定rs[root@k8s-master01 kubeadm]# kubectl delete rs nginx-deploy-6fcf476c4 replicaset.apps "nginx-deploy-6fcf476c4" deleted# rs和history一一对应[root@k8s-master01 kubeadm]#  kubectl rollout history deployment nginx-deploydeployment.apps/nginx-deploy REVISION  CHANGE-CAUSE4  kubectl apply --filename=deploy-demo.yaml --record=true5  kubectl apply --filename=deploy-demo.yaml --record=true6  <none>

六、Pod 自动扩缩容

在前面的介绍中,我们提到过通过手工执行kubectl scale命令和在Dashboard上操作可以实现Pod的扩缩容,但是这样毕竟需要每次去手工操作一次,而且指不定什么时候业务请求量就很大了,所以如果不能做到自动化的去扩缩容的话,这也是一个很麻烦的事情。如果Kubernetes系统能够根据Pod当前的负载的变化情况来自动的进行扩缩容就好了,因为这个过程本来就是不固定的,频繁发生的,所以纯手工的方式不是很现实。

幸运的是Kubernetes为我们提供了这样一个资源对象:Horizontal Pod Autoscaling(Pod水平自动伸缩),简称HPAHAP通过监控分析RC或者Deployment控制的所有Pod的负载变化情况来确定是否需要调整Pod的副本数量,这是HPA最基本的原理。

在这里插入图片描述

HPAkubernetes集群中被设计成一个controller,我们可以简单的通过kubectl autoscale命令来创建一个HPA资源对象,HPA Controller默认30s轮询一次(可通过kube-controller-manager的标志--horizontal-pod-autoscaler-sync-period进行设置),查询指定的资源(RC或者Deployment)中Pod的资源使用率,并且与创建时设定的值和指标做对比,从而实现自动伸缩的功能。

当你创建了HPA后,HPA会从Heapster或者用户自定义的RESTClient端获取每一个一个Pod利用率或原始值的平均值,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值并进行相应的操作。目前,HPA可以从两个地方获取数据:

  • Heapster:仅支持CPU使用率
  • 自定义监控:我们到后面的监控的部分再给大家讲解这部分的使用方法

本小节来给大家介绍从Heapster获取监控数据来进行自动扩缩容的方法,所以首先我们得安装Heapster,前面我们在kubeadm搭建集群的博文中,实际上我们已经默认把Heapster相关的镜像都已经拉取到节点上了,所以接下来我们只需要部署即可,我们这里使用的是Heapster 1.4.2 版本的,前往Heapstergithub页面:

https://github.com/kubernetes-retired/heapster/tree/v1.4.2/deploy/kube-config/influxdb

6.1 安装 influxdb

---apiVersion: apps/v1kind: Deploymentmetadata:  name: monitoring-influxdb  namespace: kube-systemspec:  replicas: 1  selector:    matchLabels:      k8s-app: influxdb  template:    metadata:      labels: task: monitoring k8s-app: influxdb    spec:      containers:      - name: influxdb image: mirrorgooglecontainers/heapster-influxdb-amd64:v1.3.3 volumeMounts: - mountPath: /data   name: influxdb-storage      volumes:      - name: influxdb-storage emptyDir: {}---apiVersion: v1kind: Servicemetadata:  labels:    task: monitoring    kubernetes.io/cluster-service: 'true'    kubernetes.io/name: monitoring-influxdb  name: monitoring-influxdb  namespace: kube-systemspec:  ports:  - port: 8086    targetPort: 8086  selector:    k8s-app: influxdb

6.2 安装heapster

---apiVersion: v1kind: ServiceAccountmetadata:  name: heapster  namespace: kube-system---apiVersion: rbac.authorization.k8s.io/v1kind: ClusterRoleBindingmetadata:  name: heapster-adminroleRef:  apiGroup: rbac.authorization.k8s.io  kind: ClusterRole  name: cluster-adminsubjects:  - kind: ServiceAccount    name: heapster    namespace: kube-system---apiVersion: apps/v1kind: Deploymentmetadata:  name: heapster  namespace: kube-systemspec:  replicas: 1  selector:    matchLabels:      k8s-app: heapster  template:    metadata:      labels: task: monitoring k8s-app: heapster    spec:      serviceAccountName: heapster      containers:      - name: heapster image: mirrorgooglecontainers/heapster-amd64:v1.5.2 imagePullPolicy: IfNotPresent command: - /heapster - --sink=influxdb:http://monitoring-influxdb.kube-system.svc:8086 - --source=kubernetes:https://kubernetes.default?kubeletHttps=true&kubeletPort=10250&insecure=true---apiVersion: v1kind: Servicemetadata:  labels:    task: monitoring    kubernetes.io/cluster-service: 'true'    kubernetes.io/name: Heapster  name: heapster  namespace: kube-systemspec:  ports:  - port: 80    targetPort: 8082  selector:    k8s-app: heapster
[root@k8s-master01 heapster]# kubectl create -f heapster.yaml serviceaccount/heapster createdclusterrolebinding.rbac.authorization.k8s.io/heapster-admin createddeployment.apps/heapster createdservice/heapster created

6.3 安装grafana

---apiVersion: apps/v1kind: Deploymentmetadata:  name: monitoring-grafana  namespace: kube-systemspec:  replicas: 1  selector:    matchLabels:      k8s-app: grafana  template:    metadata:      labels: task: monitoring k8s-app: grafana    spec:      containers:      - name: grafana image: mirrorgooglecontainers/heapster-grafana-amd64:v4.4.3 ports: - containerPort: 3000   protocol: TCP volumeMounts: - mountPath: /var   name: grafana-storage env: - name: INFLUXDB_HOST   value: monitoring-influxdb - name: GF_SERVER_HTTP_PORT   value: "3000" - name: GF_AUTH_BASIC_ENABLED   value: "false" - name: GF_AUTH_ANONYMOUS_ENABLED   value: "true" - name: GF_AUTH_ANONYMOUS_ORG_ROLE   value: Admin - name: GF_SERVER_ROOT_URL   value: /      volumes:      - name: grafana-storage emptyDir: {}---apiVersion: v1kind: Servicemetadata:  labels:    kubernetes.io/cluster-service: 'true'    kubernetes.io/name: monitoring-grafana  name: monitoring-grafana  namespace: kube-systemspec:  ports:  - port: 80    targetPort: 3000  selector:    k8s-app: grafana# https://github.com/kubernetes-retired/heapster/blob/v1.4.2/deploy/kube-config/influxdb/grafana.yaml 
[root@k8s-master01 heapster]# kubectl apply -f grafana.yaml deployment.apps/monitoring-grafana createdWarning: kubectl apply should be used on resource created by either kubectl create --save-config or kubectl applyThe Service "monitoring-grafana" is invalid: * spec.ports[0].name: Required value* spec.ports[1].name: Required value# 查看是否安装成功^C[root@k8s-master01 heapster]# kubectl get pods -n kube-system NAMEREADY   STATUS    RESTARTS   AGEheapster-6c9b7d6f65-zg8661/1     Running   0   3m39smonitoring-grafana-6ccbd5776c-8nqtp    1/1     Running   0   2m43smonitoring-influxdb-55f468d9fd-5wjv4   1/1     Running   0   4m39s
# 查看heapster日志确认是否有报错[root@k8s-master01 heapster]# kubectl logs -f heapster-684b58b45c-rbtrn  -n kube-systemI0408 04:11:30.5082361 heapster.go:78] /heapster --sink=influxdb:http://monitoring-influxdb.kube-system.svc:8086 --source=kubernetes:https://kubernetes.default?kubeletHttps=true&kubeletPort=10250&insecure=trueI0408 04:11:30.5082771 heapster.go:79] Heapster version v1.5.2I0408 04:11:30.5084821 configs.go:61] Using Kubernetes client with master "https://kubernetes.default" and version v1I0408 04:11:30.5084931 configs.go:62] Using kubelet port 10250I0408 04:11:30.5193151 influxdb.go:312] created influxdb sink with options: host:monitoring-influxdb.kube-system.svc:8086 user:root db:k8sI0408 04:11:30.5193361 heapster.go:202] Starting with InfluxDB SinkI0408 04:11:30.5193421 heapster.go:202] Starting with Metric SinkI0408 04:11:30.5298881 heapster.go:112] Starting heapster on port 8082I0408 04:12:05.0434151 influxdb.go:274] Created database "k8s" on influxDB server at "monitoring-influxdb.kube-system.svc:8086"

6.5 HPA

我们将该目录下面的yaml文件保存到我们的集群上,然后使用kubectl命令行工具创建即可,另外创建完成后,如果需要在Dashboard当中看到监控图表,我们还需要在Dashboard中配置上我们的heapster-host

同样的,我们来创建一个Deployment管理的Nginx Pod,然后利用HPA来进行自动扩缩容。定义DeploymentYAML文件如下:(hap-demo.yaml)

---apiVersion: apps/v1kind: Deploymentmetadata:  name: hpa-demo  labels:    app: hpaspec:  replicas: 1  revisionHistoryLimit: 15  minReadySeconds: 5  strategy:    type: RollingUpdate    rollingUpdate:      maxSurge: 1      maxUnavailable: 1  selector:    matchLabels:      app: nginx  template:    metadata:      labels: app: nginx    spec:      containers:      - name: nginx image: nginx:1.7.9 resources:   requests:     cpu: 100m ports: - containerPort: 80
# 创建[root@k8s-master01 heapster]# kubectl create -f hpa-demo.yaml deployment.apps/hpa-demo created[root@k8s-master01 heapster]# kubectl get deploymentsNAME    READY   UP-TO-DATE   AVAILABLE   AGEhpa-demo1/1     1     1    92s[root@k8s-master01 heapster]# kubectl get podsNAME   READY   STATUS    RESTARTS   AGEhpa-demo-7f9cc99d8b-rp9qv   1/1     Running   0   13s

查看静态目录:

systemctl status kubelet[root@k8s-master01 ~]# cd /etc/kubernetes/manifests/[root@k8s-master01 manifests]# lsetcd.yaml  kube-apiserver.yaml  kube-controller-manager.yaml  kube-scheduler.yaml  static-pod2.yaml

为不安装metric-server,我们做一下修改:

[root@k8s-master01 manifests]# vim kube-controller-manager.yaml 
 11   containers: 12   - command: 13     - kube-controller-manager 14     - --horizontal-pod-autoscaler-use-rest-clients=false # 在这里增加这一行

现在我们来创建一个HPA,可以使用kubectl autoscale命令来创建:

# 创建hpa[root@k8s-master01 heapster]# kubectl autoscale deployment hpa-demo --min=1 --max=10 --cpu-percent=5horizontalpodautoscaler.autoscaling/hpa-demo autoscaled

此命令创建了一个关联资源 hpa-nginx-deploy 的HPA,最小的 pod 副本数为1,最大为10。HPA会根据设定的 cpu使用率(10%)动态的增加或者减少pod数量。

当然出来使用kubectl autoscale命令来创建外,我们依然可以通过创建YAML文件的形式来创建HPA资源对象。如果我们不知道怎么编写的话,可以查看上面命令行创建的HPAYAML文件:

# 查询一下[root@k8s-master01 heapster]# kubectl get hpaNAMEREFERENCE      TARGETS MINPODS   MAXPODS   REPLICAS   AGEhpa-demo   Deployment/hpa-demo   <unknown>/5%   1  10 1   23s# 查看详细的[root@k8s-master01 heapster]# kubectl describe hpa hpa-demo Name: hpa-demoNamespace:   defaultLabels:      <none>Annotations: <none>CreationTimestamp:  Fri, 08 Apr 2022 13:02:23 +0800Reference:   Deployment/hpa-demoMetrics:     ( current / target )  resource cpu on pods  (as a percentage of request):  <unknown> / 5%Min replicas:1Max replicas:10Deployment pods:    1 current / 0 desiredConditions:  Type    Status  Reason     Message  ----    ------  ------     -------  AbleToScale    True    SucceededGetScale the HPA controller was able to get the target's current scale  ScalingActive  False   FailedGetResourceMetric  the HPA was unable to compute the replica count: missing request for cpuEvents:  Type     Reason   Age From  Message  ----     ------   --------  -------  Warning  FailedGetResourceMetric9s (x3 over 39s)  horizontal-pod-autoscaler  missing request for cpu  Warning  FailedComputeMetricsReplicas  9s (x3 over 39s)  horizontal-pod-autoscaler  invalid metrics (1 invalid out of 1), first error is: failed to get cpu utilization: missing request for cpu
---apiVersion: apps/v1kind: Deploymentmetadata:  name: hpa-demo  labels:    app: hpaspec:  replicas: 1  revisionHistoryLimit: 15  minReadySeconds: 5  strategy:    type: RollingUpdate    rollingUpdate:      maxSurge: 1      maxUnavailable: 1  selector:    matchLabels:      app: nginx  template:    metadata:      labels: app: nginx    spec:      containers:      - name: nginx image: nginx:1.7.9 resources:   requests:     cpu: 100m ports: - containerPort: 80
# 压力测试一下[root@k8s-master01 heapster]# kubectl run -i --tty test-hpa --image=busybox /bin/shkubectl run --generator=deployment/apps.v1 is DEPRECATED and will be removed in a future version. Use kubectl run --generator=run-pod/v1 or kubectl create instead.If you don't see a command prompt, try pressing enter./ # while true; do wget -q -O- http://10.244.1.111; done; 

同时我们查看相关资源hpa-nginx-deploy的副本数量,副本数量已经从原来的1变成了3。

NAMEREADY   UP-TO-DATE   AVAILABLE   AGEhpa-demo   3/3     1     1    35m[root@k8s-master01 heapster]# kubectl get hpaNAMEREFERENCE      TARGETS MINPODS   MAXPODS   REPLICAS   AGEhpa-demo   Deployment/hpa-demo   36%/5%   1  10 1   18m

同样的这个时候我们来关掉busybox来减少负载,然后等待一段时间观察下HPADeployment对象

NAMEREADY   UP-TO-DATE   AVAILABLE   AGEhpa-demo   1/1     1     1    35m[root@k8s-master01 heapster]# kubectl get hpaNAMEREFERENCE      TARGETS MINPODS   MAXPODS   REPLICAS   AGEhpa-demo   Deployment/hpa-demo   0%/5%   1  10 1   18m

hpa-deployment整合一下:

---apiVersion: apps/v1kind: Deploymentmetadata:  name: hpa-demo  labels:    app: hpaspec:  replicas: 1  revisionHistoryLimit: 15  minReadySeconds: 5  strategy:    type: RollingUpdate    rollingUpdate:      maxSurge: 1      maxUnavailable: 1  selector:    matchLabels:      app: nginx  template:    metadata:      labels: app: nginx    spec:      containers:      - name: nginx image: nginx:1.7.9 resources:   requests:     cpu: 100m ports: - containerPort: 80---apiVersion: autoscaling/v1kind: HorizontalPodAutoscalermetadata:  name: hpa-demo  namespace: defaultspec:  maxReplicas: 10 #资源最大副本数  minReplicas: 1  #资源最小副本数  scaleTargetRef:    apiVersion: apps/v1    kind: Deployment  #需要伸缩的资源类型    name: hpa-demo    #需要伸缩的资源名称  targetCPUUtilizationPercentage: 90  #触发伸缩的cpu使用率

当前的HPA只有CPU使用率这一个指标,还不是很灵活的,在后面的学习中我们来根据我们自定义的监控来自动对Pod进行扩缩容。

七、Job 和 Cronjob 的使用

上面我们学习了Pod自动伸缩的方法,我们使用到了HPA这个资源对象,我们在后面的学习中还会和大家接触到HPA的。今天我们来给大家介绍另外一类资源对象:Job,我们在日常的工作中经常都会遇到一些需要进行批量数据处理和分析的需求,当然也会有按时间来进行调度的工作,在我们的Kubernetes集群中为我们提供了JobCronJob两种资源对象来应对我们的这种需求。

Job负责处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束。而CronJob则就是在Job上加上了时间调度。官网文档: https://kubernetes.io/zh/docs/concepts/workloads/controllers/job/

7.1 Job

我们用Job这个资源对象来创建一个任务,我们定一个Job来执行一个倒计时的任务,定义YAML文件:

---apiVersion: batch/v1beta1kind: Jobmetadata:  name: job-demospec:  template:    metadata:      name: job-demo    spec:      containers:      - name: counter image: busybox command:    - "bin/sh"   - "-c"   - "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done"      restartPolicy: Never  backoffLimit: 4
# 创建job[root@k8s-master01 kubeadm]# kubectl create -f job-demo.yaml job.batch/job-demo created# 查看job[root@k8s-master01 kubeadm]# kubectl get jobsNAMECOMPLETIONS   DURATION   AGEjob-demo   1/1    4s  9s
# 查看详细描述[root@k8s-master01 kubeadm]# kubectl describe job job-demoName:    job-demoNamespace:      defaultSelector:controller-uid=5eb4b70e-59e1-4b47-8c73-12e53adb8825Labels:  controller-uid=5eb4b70e-59e1-4b47-8c73-12e53adb8825  job-name=job-demoAnnotations:    <none>Parallelism:    1Completions:    1Start Time:     Fri, 08 Apr 2022 15:57:39 +0800Completed At:   Fri, 08 Apr 2022 15:57:43 +0800Duration:4sPods Statuses:  0 Running / 1 Succeeded / 0 FailedPod Template:  Labels:  controller-uid=5eb4b70e-59e1-4b47-8c73-12e53adb8825    job-name=job-demo  Containers:   counter:    Image:      busybox    Port:<none>    Host Port:  <none>    Command:      bin/sh      -c      for i in 9 8 7 6 5 4 3 2 1; do echo $i; done    Environment:  <none>    Mounts:<none>  Volumes: <none>Events:  Type    Reason     Age    From     Message  ----    ------     ----   ----     -------  Normal  SuccessfulCreate  2m47s  job-controller  Created pod: job-demo-ntp5d

注意查看我们的Pod的状态,同样我们可以通过kubectl logs来查看当前任务的执行结果。

7.2 CronJob

CronJob其实就是在Job的基础上加上了时间调度,我们可以:在给定的时间点运行一个任务,也可以周期性地在给定时间点运行。这个实际上和我们Linux中的crontab就非常类似了。

一个CronJob对象其实就对应中crontab文件中的一行,它根据配置的时间格式周期性地运行一个Job,格式和crontab也是一样的。

crontab的格式如下:

分 时 日 月 星期 要运行的命令 第1列分钟0~59 第2列小时0~23) 第3列日1~31 第4列月1~12 第5列星期0~7(0和7表示星期天) 第6列要运行的命令

现在,我们用CronJob来管理我们上面的Job任务,

---apiVersion: batch/v1beta1kind: CronJobmetadata:  name: cronjob-demospec:  schedule: "*/1 * * * *"  jobTemplate:    spec:      template: name: job-demo      spec: containers: - name: counter   image: busybox   command:      - "bin/sh"     - "-c"     - "for i in 9 8 7 6 5 4 3 2 1; do echo $i; done" restartPolicy: OnFailure
# 创建[root@k8s-master01 kubeadm]# vim cronjob-demo.yaml [root@k8s-master01 kubeadm]# kubectl create -f cronjob-demo.yaml cronjob.batch/cronjob1 created[root@k8s-master01 kubeadm]# kubectl get cronjobsNAMESCHEDULE      SUSPEND   ACTIVE   LAST SCHEDULE   AGEcronjob1   */1 * * * *   False     0 <none>   12shello      * * * * *     False     0 60s      100s[root@k8s-master01 kubeadm]# kubectl get jobsNAME    COMPLETIONS   DURATION   AGEcronjob1-1649469540   1/1    13s 24shello-1649469480      1/1    11s 84shello-1649469540      1/1    1s  24sjob-demo1/1    4s  18h# 查看进度[root@k8s-master01 kubeadm]# kubectl get podsNAME   READY   STATUS      RESTARTS   AGEcronjob1-1649469540-jtwl7   0/1     Completed   0   88scronjob1-1649469600-6wwhc   0/1     Completed   0   28s

注意:如何出现 error: unable to recognize “cronjob.yaml”: no matches for kind “CronJob” in version “batch/v1beta1” 错误, 解决方法如下

vim /etc/kubernetes/manifests/kube-apiserver.yaml- --runtime-config=batch/v1=true或者- --runtime-config=batch/v1beta1=truesystemctl restart kubeletkubectl api-versions | grep batch/v2

在这里插入图片描述

当然,也可以用kubectl run来创建一个CronJob

一旦不再需要 Cron Job,简单地可以使用 kubectl 命令删除它:

[root@k8s-master01 kubeadm]# kubectl delete cronjob cronjob1 cronjob.batch "cronjob1" deleted

这将会终止正在创建的 Job。然而,运行中的 Job 将不会被终止,不会删除 Job 或 它们的 Pod。为了清理那些 Job 和 Pod,需要列出该 Cron Job 创建的全部 Job,然后删除它们:

[root@k8s-master01 kubeadm]# kubectl get jobsNAME COMPLETIONS   DURATION   AGEhello-1649470200   1/1    1s  2m46shello-1649470260   1/1    1s  106shello-1649470320   1/1    1s  46sjob-demo    1/1    4s  18h[root@k8s-master01 kubeadm]# kubectl delete jobs hello-1649470200 hello-1649470260 ..

一旦 Job 被删除,由 Job 创建的 Pod 也会被删除。注意,所有由名称为 “hello” 的 Cron Job 创建的 Job 会以前缀字符串 “hello-” 进行命名。如果想要删除当前 Namespace 中的所有 Job,可以通过命令 kubectl delete jobs --all 立刻删除它们。

好啦🌶, 关于pod就先介绍到这里, 喜欢就点个❤吧~ ☕️