K8s 基本资源

Pod 详解

一个 Kubernetes(K8s)中的 Pod 是一个部署的最小单位,它是由一个或多个容器组成的,这些容器共享网络和存储资源。Pod 可以包含多个相关联的容器,它们在同一主机上运行并具有共享的网络命名空间。

Pod 定义

Pod 是 Kubernetes(K8s)中的一个基本构建模块,代表一组并置的容器。它是一个应用程序的单一运行实例,由共享资源且关系紧密的一个或多个应用容器组成,为它们提供相同的环境。这些容器共享相同的网络命名空间,它们可以通过 IPC(Inter-Process Communication)进行通信,并共享相同的主机名和网络接口。尽管容器在逻辑上运行在同一个 Pod 内,但它们之间仍然保持着一定的隔离性。

在实际应用中,我们往往会部署一组相关的 Pod,而不是单独部署容器。当一个 Pod 包含多个容器时,这些容器总是运行在同一个工作节点上,一个 Pod 不会跨越多个工作节点。

Pod 具有以下特点:

  • 容器部分隔离:Kubernetes 通过配置 Docker,使同一个 Pod 内的所有容器共享相同的 Linux 命名空间。它们共享相同的主机名和网络接口,容器之间可以通过 IPC 进行通信。其他的 MNT(Mount),USR(User),和 PID(Process ID)命名空间是独立的。
  • 容器共享网络命名空间:在同一个 Pod 中的容器处于相同的网络命名空间中,意味着它们共享 Pod 的 IP 地址和端口。需要注意的是,容器绑定的端口号不能相同,否则会导致端口冲突。同一个 Pod 中的容器可以直接通过回环口(loopback)进行通信。
  • 平坦的 Pod 间网络:Kubernetes 集群中的所有 Pod 都处于同一个共享网络地址空间中,Pod 之间可以直接通过 Pod 的 IP 地址来相互访问。不管实际节点的网络拓扑结构如何,都不需要进行网络地址转换(NAT),就像在局域网中一样。

组织 Pod

尽管 Pod 看起来像一个独立的机器,但不应该将多个应用程序填充到一个 Pod 中。每个 Pod 应该只包含紧密相关的组件或进程,保持 Pod 的轻量级。这样可以最大限度地创建更多的 Pod:

  • 将多层应用程序分散到多个 Pod 中

    一个典型的应用程序由前端和后端数据库组成。如果将它们放在同一个 Pod 中,它们作为整体只能在一个节点上运行,无法利用其他节点的计算资源。将它们拆分后,可以在不同的节点上分别部署前端和后端应用程序,提高基础架构的利用率。

  • 基于扩缩容的考虑

    Pod 是扩缩容的基本单位,通常前端和后端组件具有不同的扩缩容需求。后端数据库相对于无状态的前端更难以扩展,因此需要将它们分开部署到单独的 Pod 中。

  • 使用 Sidecar 容器

    将多个容器添加到单个 Pod 的主要原因是应用程序可能由一个主进程和多个辅助进程组成。辅助进程所在的容器被称为 Sidecar 容器,通常用于日志收集、数据处理、存储和通信等功能。

  • Pod 中使用多个容器

    当多个容器作为一个整体不可分离且需要一起进行扩缩容时,考虑将它们放入同一个 Pod 中。

生命周期

Pod 对象有 5 种状态:

  • Pending(等待中)

    API Server 创建了 Pod 资源对象并已存储在 etcd 中,但它尚未完成调度,或者仍在下载镜像中。

  • Running(运行中)

    Pod 已被调度到某个节点,并且所有容器都已由 Kubelet 创建完成。

  • Succeeded(已成功)

    Pod 中的所有容器都已成功终止,并且不会重新启动。

  • Failed(失败)

    所有容器都已终止,但至少有一个容器终止失败,返回了非零的退出状态。

  • Unknown(未知)

    API Server 无法正常获取 Pod 对象的状态信息,通常是由于节点失联造成的。

Pause 容器

每个 Pod 都有一个特殊的被称为根容器的 Pause 容器,Pause 容器对应的镜像属于 Kubernetes 平台的一部分,而其他容器则是用户业务容器。

在启动一个 Pod 资源后,可以查看运行中的节点 Docker 进程:

[root@server5-node1 ~]$ docker ps
CONTAINER ID   IMAGE                                                                 COMMAND                  CREATED              STATUS              PORTS     NAMES
e9d77706b60a   luksa/kubia-pet-peers                                                 "node app.js"            About a minute ago   Up About a minute             k8s_kubia_kubia-2_default_49130122-540c-4c8b-98aa-23c84e9bfc9a_0
fe1980dc3fb7   registry.cn-hangzhou.aliyuncs.com/google_containers/pause-amd64:3.2   "/pause"                 About a minute ago   Up About a minute             k8s_POD_kubia-2_default_49130122-540c-4c8b-98aa-23c84e9bfc9a_0

附加 Pause 容器会先于应用容器创建,它不执行任何操作,只运行一个 pause 命令。

由于 Pod 内的容器共享同一个网络和 Linux 命名空间,将与业务无关且不易终止的 Pause 容器作为 Pod 的根容器,可以通过它的状态来代表整个容器组的状态,从而更简单地判断 Pod 的运行状态。Pause 容器的生命周期与 Pod 绑定,如果基础 Pod 在此期间被关闭,Kubelet 会重新创建它以及 Pod 中的所有容器。

另一方面,如果 Pod 中有多个容器,可以通过 Pause 容器挂载外部卷并共享 Pause 容器的 IP,从而简化了业务容器之间的通信问题。

描述文件

创建 Pod 或其他资源通常需要提供 JSON 或 YAML 描述文件来向 REST API 提供信息。可以使用 kubectl get 命令加上 -o yaml 参数来查看 YAML 定义:

[user1@server6 ~]$ kubectl get pod kubia -o yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: "2021-11-02T21:50:57Z"
  labels:
    run: kubia
  name: kubia

几乎所有的 Kubernetes 资源中都可以找到 Pod 定义文件的结构:

  • apiVersion:Kubernetes API 的版本。可以使用 kubectl api-resources 命令查找当前资源所使用的 API 版本。
  • kind:资源类型。
  • metadata:元数据,包括名称、命名空间、标签和其他容器相关信息。
  • spec:实际描述 Pod 内容的规范,例如容器和卷。
  • status:包含当前运行的 Pod 的信息,例如容器状态和内部 IP 信息。

可以使用 kubectl explain pods 命令查看关于 YAML 文件中各属性的说明。如果需要了解更详细的信息,可以直接查看属性,例如使用 kubectl explain pod.status

[user1@server6 ~]$ kubectl explain pod.status

FIELDS:
   conditions   <[]Object>
     Current service state of pod. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-conditions

   containerStatuses    <[]Object>
     The list has one entry per container in the manifest. Each entry is
     currently the output of `docker inspect`. More info:
     https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle#pod-and-container-status

以下是一个基本的 Pod YAML 描述文件示例:

[user1@server6 ~]$ vi kubia-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: alice-pod
spec:
  containers:
  - image: assassing/kubia
    name: assassing
    ports:
    - containerPort: 8080
      protocol: TCP

在 Pod 定义中指定的端口只是为了可读性,并没有实际作用。如果容器绑定到地址为 0.0.0.0 的端口以接收连接,即使端口没有在 Pod spec 中指定,其他 Pod 仍然可以连接到该端口。

创建 Pod

可以使用 kubectl create 命令根据 YAML 文件创建包括 Pod 在内的任何资源:

[user1@server6 ~]$ kubectl create -f kubia-pod.yaml 
pod/alice-pod created
[user1@server6 ~]$ kubectl get pod
NAME        READY   STATUS    RESTARTS   AGE
alice-pod   1/1     Running   0          12s

除了 YAML 格式,还可以使用 JSON 文件来创建资源。

查看 Pod 详情

可以将详细信息输出为 JSON 格式:

[user1@server6 ~]$ kubectl get po alice-pod -o json
{
    "apiVersion": "v1",
    "kind": "pod",
    "metadata": {
        "creationTimestamp": "2021-11-02T23:37:42Z",
        "name": "alice-pod",
        "namespace": "default",
        "resourceVersion": "6027",
        "uid": "de10a52f-d380-4341-9c11-2bd516cf0a02"
    },

查看 Pod 日志

使用 kubectl logs 命令查看日志,无需像 docker logs 命令一样在容器所在的主机上查询:

[user1@server6 ~]$ kubectl logs alice-pod 
Runing...

如果 Pod 中包含多个容器,则必须通过 -c 参数指定容器名称:

[root@localhost flannel]$ kubectl logs httpd-app-5bc589d9f7-ggmwq -c httpd-app
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.1.5. Set the 'ServerName' directive globally to suppress this message
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 10.244.1.5. Set the 'ServerName' directive globally to suppress this message
[Thu Jul 18 16:28:44.242092 2019] [mpm_event:notice] [pid 1:tid 139744563483776] AH00489: Apache/2.4.39 (Unix) configured -- resuming normal operations
[Thu Jul 18 16:28:44.242331 2019] [core:notice] [pid 1:tid 139744563483776] AH00094: Command line: 'httpd -D FOREGROUND'

每当日志文件达到 10 MB 大小时,容器日志会自动进行轮替。同时,当 Pod 被删除时,日志也会被删除。

执行命令

使用类似 Docker 命令的格式 kubectl exec -it 来进入容器,尽管这样做是可行的,但会提示该命令已被废弃:

[root@server4-master ~]$ kubectl exec -it kubiaex-f4wkw /bin/bash
kubectl exec [pod] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [pod] -- [COMMAND] instead.

这是因为当要在容器中运行带有空格的命令时,kubectl 会将空格后面的内容作为 kubectl 的参数进行解析。正确的用法是在容器命令之前使用两个减号 -- 进行分隔:

[root@server4-master ~]$ kubectl exec -it kubiaex-f4wkw -- bash

转发 Pod 端口

除了使用 Service 的方式,还可以通过端口转发来连接 Pod。命令是 kubectl port-forward <pod名称> 本地端口:pod端口

例如,将 alice-pod 的 8080 端口转发到本地的 8888 端口:

[user1@server6 ~]$ kubectl port-forward alice-pod 8888:8080
Forwarding from 127.0.0.1:8888 -> 8080
Forwarding from [::1]:8888 -> 8080
Handling connection for 8888
[root@server6 ~]$ curl 127.0.0.1:8888
Hostname: alice-pod

这种方法可以用于调试应用程序。

删除 Pod

当 API 服务器接收到删除 Pod 的请求后,它首先修改了 etcd 中的状态,并将事件通知给 Kubelet 和端点控制器(Endpoint Controller):

  • 当 Kubelet 接收到 Pod 终止通知时,会执行停止前钩子,发送 SIGTERM 信号,最后完成杀死容器。
  • 端点控制器则负责从 Pod 对应的服务中移除这个 Pod 的地址,主要通过向 API 服务器发送 REST 请求来修改 Endpoint API 对象。然后 API 服务器会通知所有的客户端,特别是 kube-proxy 服务在节点上更新 iptables 规则,以阻止新的连接被转发到停止状态的 Pod 上。

使用 kubectl delete 命令来删除 Pod,可以用空格分隔要删除的多个 Pod:

[user1@server6 ~]$ kubectl delete pod alice-pod alice-pod-v1
pod "alice-pod" deleted
pod "alice-pod-v1" deleted

在删除 Pod 的过程中,实际上是请求终止该 Pod 中的所有容器。Kubernetes 向进程发送一个 SIGTERM 信号,默认等待 30 秒使其正常关闭。如果超过 30 秒,则通过 SIGKILL 终止进程。

通过 -l 参数来通过标签选择 Pod 并删除:

[root@localhost ~]$ kubectl delete pod -l run=assassing
pod "assassing-5c54d7b988-76kdz" deleted
pod "assassing-5c54d7b988-hbxtp" deleted

删除当前命名空间内所有 Pod,但保留命名空间:

[root@localhost ~]$ kubectl delete pod --all

删除当前命名空间内的所有资源,但保留命名空间:

[user1@server6 ~]$ kubectl delete all --all
service "kubernetes" deleted
service "kubia" deleted
service "kubia-http" deleted

初始化容器

可以在 Pod 中加入一个 init 容器来检查依赖服务的请求是否被响应,获得响应后再让主容器启动。

初始化容器运行失败会重启,直到成功完成。如果 spec.restartPolicy 字段为 Never 时不会重启。

初始化容器通过 spec.initContainers 来定义:

spec:
  containers:
  - name: nodejs
    image: luksa/kubia
    ports:
    - name: http
      containerPort: 8080
    resources:
      requests:
        cpu: 100m
  initContainers:
  - name: init
    image: busybox
    command:
    - sh
    - -C
    - 'while true; do echo "waiting"; wget http://fortune -q -T 1 -O /dev/null > /dev/null 2>/dev/null && break; sleep 5; done; echo "Done"'

终止宽限期

Pod 的关闭是通过 API 服务器删除 Pod 的对象来触发的。当服务器接收到 HTTP DELETE 请求后,API 服务器并没有立即删除对象,而是给 Pod 设置一个 deletionTimestamp 值,Pod 开始停止运行。

节点上的 Kubelet 会开始终止 Pod 中的每个容器。Pod 向容器的主进程发送 SIGTERM 信号,等待容器自行关闭。若等待终止宽限期(Termination Grace Period)超时,Kubelet 会使用 SIGKILL 信号强制关闭进程。

终止宽限期可以通过 Pod spec 中的 terminationGracePeriodSeconds 字段来设置,其默认值为 30 秒。

也可以在删除 Pod 时指定宽限时间:

[root@k8s-master 6]$ kubectl delete po kubia --grace-period=5

使用强制删除 --force 选项时要注意 StatefulSet 管理的 Pod。强制删除 Pod 会导致控制器不等待被删除 Pod 中的容器完成关闭,而直接创建一个替代的 Pod,可能会导致相同 Pod 的两个实例同时运行,从而造成集群服务工作异常。因此,只有在 Pod 无法与集群中的其他成员通信的情况下,才应该使用强制删除。

一种更好的解决方法是使用一个专门持续运行的 Pod,来持续检查是否存在孤立的数据。

命名空间

K8s 的命名空间(Namespace)与用于互相隔离进程的 Linux 命名空间不同,它并不提供对正在运行的对象的任何隔离,只是简单地为对象名称提供一个作用域,从逻辑上进行隔离。这样,在不同的命名空间中可以多次使用相同的资源名称。

命名空间为资源名称提供了一个作用域。可以通过命名空间将资源分配到生产、开发和测试环境。资源名称只需要在命名空间内保持唯一即可。

在 K8s 集群安装完成后,会自动创建两个命名空间:一个是默认的 default 空间,另一个是系统级的 kube-system。当创建资源对象时没有指定命名空间时,默认会将其存放到 default 命名空间中。

查看命名空间

默认情况下,在 default 命名空间中进行操作。可以使用 kubectl get namespacekubectl get ns 来查看所有命名空间:

[user1@server6 ~]$ kubectl get namespace
NAME                   STATUS   AGE
default                Active   4h12m
kube-node-lease        Active   4h12m
kube-public            Active   4h12m
kube-system            Active   4h12m
kubernetes-dashboard   Active   4h11m

创建命名空间

可以通过 YAML 文件创建命名空间。命名空间的名称不能包含点号:

[user1@server6 ~]$ vi my-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: my-namespace
[user1@server6 ~]$ kubectl create -f my-namespace.yaml 
namespace/my-namespace created

也可以直接使用 create namespace 命令来创建命名空间:

[user1@server6 ~]$ kubectl create namespace my-ns
namespace/my-ns created

使用命名空间

可以在创建 Pod 的 YAML 文件中,在 metadata 字段中添加 namespace: 来指定命名空间:

[user1@server6 ~]$ vi kubia-ns.yaml
apiVersion: v1
kind: Pod
metadata:
  name: alice-ns
  namespace: my-namespace
spec:
  containers:
  - image: assassing/kubia
    name: assassing

也可以在使用 kubectl create 命令创建资源时,通过添加 -n 参数来指定命名空间:

[user1@server6 ~]$ kubectl create -f kubia-ns.yaml -n my-namespace 
pod/alice-ns created

查询命名空间

要查看命名空间下的 Pod,可以在查询命令后添加 -n 选项来指定命名空间:

[user1@server6 ~]$ kubectl get pod -n my-namespace
NAME       READY   STATUS    RESTARTS   AGE
alice-ns   1/1     Running   0          45s

可以使用 --all-namespaces 参数来查看所有命名空间下的 Pod:

[user1@server6 ~]$ kubectl get pod --all-namespaces

删除命名空间

可以直接删除整个命名空间,命名空间下的 Pod 将会随之删除:

[user1@server6 ~]$ kubectl delete ns my-namespace
namespace "my-namespace" deleted

标签和注解

在 Kubernetes 中,标签注解是用于给资源对象添加元数据的机制。

定义

标签是可以附加到资源上的任意键值对,通过标签选择器可以选择具有特定标签的资源,从而能够一次性操作所有具有相同标签的资源。在资源内,一个标签的键(key)必须是唯一的,因此一个资源可以拥有多个标签,并且可以随时修改和添加标签。

通过给指定的资源对象绑定一个或多个不同的标签,可以实现多维度的资源分组管理。常见的标签包括版本标签(release:stable)、环境标签(environment:dev)、架构标签(tier:frontend)、区域标签(partition:HK)和质量控制标签(track:daily)等。这些标签可以根据实际需求来定义和使用,以实现更灵活的资源管理和操作。

指定标签

创建一个新的 kubia-label.yaml 文件,添加 labels 字段:

[user1@server6 ~]$ vi kubia-label.yaml
apiVersion: v1
kind: Pod
metadata:
  name: alice-pod-v1
  labels:
    creation_method: manual
    env: prod
spec:
  containers:
  - image: assassing/kubia
    name: assassing
    ports:
    - containerPort: 8080
      protocol: TCP
[user1@server6 ~]$ kubectl create -f kubia-label.yaml 
pod/alice-pod-v1 created

查看标签

使用 --show-labels 参数来查看标签:

[user1@server6 ~]$ kubectl get pods --show-labels
NAME           READY   STATUS    RESTARTS   AGE   LABELS
alice-pod      1/1     Running   0          32m   <none>
alice-pod-v1   1/1     Running   0          53s   creation_method=manual,env=prod

可以使用 -L 来显示指定的标签而不是显示所有标签:

[user1@server6 ~]$ kubectl get pods -L env,creation_method
NAME           READY   STATUS    RESTARTS   AGE     ENV    CREATION_METHOD
alice-pod      1/1     Running   0          34m            
alice-pod-v1   1/1     Running   0          2m55s   prod   manual

修改标签

要为现有的 Pod 添加标签,可以使用 kubectl label 命令:

[user1@server6 ~]$ kubectl label pod alice-pod env=test
pod/alice-pod labeled
[user1@server6 ~]$ kubectl get pods -L env
NAME           READY   STATUS    RESTARTS   AGE     ENV
alice-pod      1/1     Running   0          36m     test
alice-pod-v1   1/1     Running   0          4m29s   prod

如果要修改已存在的标签,需要使用 --overwrite 参数,以防止在添加新标签时无意中更改现有标签的值:

[user1@server6 ~]$ kubectl label pod alice-pod env=prod --overwrite 
pod/alice-pod labeled
[user1@server6 ~]$ kubectl get pods -L env
NAME           READY   STATUS    RESTARTS   AGE     ENV
alice-pod      1/1     Running   0          37m     prod
alice-pod-v1   1/1     Running   0          5m47s   prod

一个标签可以附加于多个对象。

删除标签

例如,要删除名为 alice-pod-v1 上的 creation_method 标签:

[user1@server6 ~]$ kubectl label pod alice-pod-v1 creation_method-
pod/alice-pod-v1 labeled

注意,在删除标签时要在标签名后添加 - 符号。

标签选择器

标签选择器允许选择标记有特定标签的 Pod 子集并进行操作。可以使用以下条件:

  • 包含(或不包含)使用特定键的标签。

    例如选择所有不存在 env 键的标签的资源:!env

  • 包含具有特定键和值的标签。

    同时选择两个标签,标签之间用逗号分开。例如:app=pc,rel=beta

    选择带有 env 标签且值为 proddev 的 Pod:env in (prod,dev)

  • 包含具有特定键的标签,但值与指定的不同。

    选择带有 env 标签且值不等于 prod 的 Pod:env!=prod

    选择 env 标签且值不为 proddev 的 Pod:env notin (prod,dev)

例如,列出标签 env=prod 的 Pod:

[user1@server6 ~]$ kubectl get po -l env=prod
NAME           READY   STATUS    RESTARTS   AGE
alice-pod      1/1     Running   0          53m
alice-pod-v1   1/1     Running   0          21m

使用 ! 做选择时,必须将条件用单引号括起来,否则会被 Bash 解释。例如,列出标签 key 不存在 env 的 Pod:

[user1@server6 ~]$ kubectl get po -l '!env'
NAME           READY   STATUS    RESTARTS   AGE
alice-pod      1/1     Running   0          54m
alice-pod-v1   1/1     Running   0          23m

在金丝雀发布时,可以使用标签选择器一次删除所有金丝雀 Pod。

节点标签

标签可以附加到任何 Kubernetes 对象上,包括节点。因此,在添加新节点时,可以使用标签对节点进行分类。例如,一组美国服务器的节点,可以添加标签 location=US

[root@localhost ~]$ kubectl label node k8s-node1 location=US
node/k8s-node1 labeled
[root@localhost ~]$ kubectl get nodes -l location=US
NAME        STATUS   ROLES    AGE   VERSION
k8s-node1   Ready    <none>   85m   v1.15.0

如果需要将 Pod 部署到美国服务器,可以在 YAML 文件中添加一个节点选择器 nodeSelector

[root@localhost ~]$ vi kubia-US.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: alice-pod-us
spec:
  nodeSelector:
    location: "US"
  containers:
  - image: assassing/kubia
    name: assassing
[root@localhost ~]$ kubectl get pod --all-namespaces -o wide
NAMESPACE  NAME         READY  STATUS RESTARTS AGE IP          NODE       NOMINATED NODE
default    alice-pod-us 1/1    Running 0       32s 10.244.1.6  k8s-node1  <none>      
default    alice-pod-v1 1/1    Running 0       26m 10.244.2.5  k8s-node2  <none>      

添加注解

注解只是为了保存标识信息而存在,不能像标签一样进行分组,但可以容纳更多信息,主要用于工具使用。

可以在描述文件中或使用 kubectl annotate 命令来添加注解:

[user1@server6 ~]$ kubectl annotate pod alice-pod build="20160602"
pod/alice-pod annotated
[user1@server6 ~]$ kubectl describe pod alice-pod
Labels:       env=prod
Annotations:  build: 20160602

探针和钩子

探针和钩子是容器化应用程序中常用的两种机制,用于增强应用程序的可靠性和健壮性。

存活探针

Kubernetes 可以通过存活探针(Liveness Probe)检查容器是否仍在运行。可以为 Pod 中的每个容器单独指定存活探针,如果探测失败,Kubernetes 将定期执行探测并重新启动容器。

Kubernetes 提供了三种探测容器的机制:

  • ExecAction:在容器内执行一个命令,并根据返回的状态码进行诊断。状态码为 0 表示成功,否则表示容器不健康。
  • TCPSocketAction:通过尝试与容器的特定 TCP 端口建立连接进行诊断,如果端口能够打开,则表示容器正常。
  • HTTPGetAction:通过向容器的 IP 地址的指定端口和路径发起 HTTP GET 请求进行诊断。响应码为 2xx 或 3xx 时表示成功,其他任何响应码表示失败。

存活探针由各个节点上的 Kubelet 服务负责执行,而主节点上的控制组件不参与此过程。

HTTP 探针

HTTP 探针用于检测 Web 服务器是否正常响应请求,非常有用。在这里,我们使用一个 Node.js 程序,该程序在第 5 次请求后返回状态码 500:

const http = require('http');
const os = require('os');

console.log("Kubia server starting...");

var requestCount = 0;

var handler = function(request, response) {
  console.log("Received request from " + request.connection.remoteAddress);
  requestCount++;
  if (requestCount > 5) {
    response.writeHead(500);
    response.end("I'm not well. Please restart me!");
    return;
  }
  response.writeHead(200);
  response.end("You've hit " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

构建镜像的方法可以参考快速入门中的内容。下面是用于创建 Pod 的 YAML 文件示例:

[root@server4-master ~]$ vi kubia-live.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-liveness
spec:
  containers:
  - image: luksa/kubia-unhealthy
    name: kubia
    livenessProbe:
      httpGet:
        path: /
        port: 8080
[root@server4-master ~]$ kubectl create -f kubia-live.yaml 

上述配置定义了一个使用 HTTP GET 方法的存活探针,它会检查 Pod 的 8080 端口以确定容器的健康状态。

启动 Pod 后,观察 Pod 的状态:

[root@server4-master ~]$ watch kubectl get po
NAME             READY   STATUS    RESTARTS      AGE       
kubia-liveness   1/1     Running   2 (12h ago)   4m8s  

大约两分钟后,存活探针检测到返回码为 500,即会重新启动容器,并增加重启计数。如此循环。

可以使用以下命令查看 Pod 的日志。由于 kubectl logs 命令只会打印当前容器的日志,如果想查看之前容器的日志,需要使用 --previous 选项:

[root@server4-master ~]$ kubectl logs kubia-liveness --previous
Kubia server starting...
Received request from ::ffff:192.168.2.206
Received request from ::ffff:192.168.2.206
Received request from ::ffff:192.168.2.206
Received request from ::ffff:192.168.2.206

日志显示的请求访问都来自存活探针。再次查看 Pod 的描述:

[root@server4-master ~]$ kubectl describe po kubia-liveness
    Last State:     Terminated
      Reason:       Error
      Exit Code:    137
      Started:      Wed, 03 Nov 2021 14:38:46 +0800
      Finished:     Wed, 03 Nov 2021 14:40:33 +0800
Events:
  Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Pulled     12h                kubelet            Successfully pulled image "luksa/kubia-unhealthy" in 40.786546605s
  Normal   Pulled     12h                kubelet            Successfully pulled image "luksa/kubia-unhealthy" in 3.188020575s
  Normal   Created    12h (x3 over 12h)  kubelet            Created container kubia
  Normal   Pulled     12h                kubelet            Successfully pulled image "luksa/kubia-unhealthy" in 3.281767877s
  Normal   Started    12h (x3 over 12h)  kubelet            Started container kubia
  Warning  Unhealthy  12h (x9 over 12h)  kubelet            Liveness probe failed: HTTP probe failed with statuscode: 500

可以清楚地看到上次退出代码为 137 = 128 + 9,表示进程由外部信号终止,其中 9 代表 SIGKILL 强制终止信号。当容器被强制终止时,会创建一个全新的容器而不是重启原先的容器。

Exec 探针

Exec 探针只有一个可用属性 command,用于指定要执行的命令:

livenessProbe:
  exec:
    command: ["test", "-e", "/tmp/healthy"]

TCP 探针

相比于 HTTP 探测,TCP 探针更高效且节约资源,但精确度稍低。毕竟连接建立成功并不意味着页面资源可用:

livenessProbe:
  tcpSocket:
    port: 443

附加属性

可以给存活探针增加附加属性,可设置以下五个属性:

  • initialDelaySeconds:表示容器启动后等待多少秒开始探测,默认为 0 秒。
  • timeoutSeconds:表示响应时间超过多少秒为失败,默认为 1 秒。
  • periodSeconds:表示探针探测周期的时间间隔,以秒为单位,默认为 10 秒。
  • successThreshold:表示探测成功几次后将 Pod 的状态恢复为正常,默认为 1 次。
  • failureThreshold:表示连续探测失败几次后重启容器,默认为 3 次。

其中,最常用的是设置初始化等待时间,将默认的立即开始探测改为 15 秒:

[root@localhost ~]$ vi kubia-http-liveness-probe.yaml 
    livenessProbe:
      httpGet:
        path: /
        port: 8080
      initialDelaySeconds: 15

就绪探针

就绪探针用于定期调用并确定特定的 Pod 是否已准备好接收客户端请求。当容器的就绪探针返回成功时,表示容器已经准备好接收请求。Kubernetes 只能检查在容器中运行的应用程序是否响应一个简单的 GET 请求,或者响应特定的 URL 路径。因此,在开发应用程序时需要添加就绪探针的逻辑。

就绪探针有三种类型,与存活探针相同。与存活探针的目的不同,就绪探针用于确保 Pod 已准备好接收请求。

就绪探针的工作原理如下:在启动容器时配置一个等待时间,等待时间过后开始执行第一次就绪检查。之后,会定期(默认为每 10 秒)调用就绪探针,并根据结果采取相应的操作。如果容器未通过就绪检查,Pod 不会被终止或重新启动,只是从服务的终端点(endpoints)中移除,等待下一次探测检查。当再次就绪时,将重新添加该 Pod,以确保客户端只与正常的 Pod 进行交互。

添加就绪探针

修改已存在的 Replication Controller 对象 kubia,并添加就绪探针的配置,可以执行以下操作:

[root@k8s-master ~]$ kubectl edit rc kubia
...
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: kubia
    spec:
      containers:
      - image: luksa/kubia
        readinessProbe:
          exec:
            command:
            - ls
            - /var/ready
        imagePullPolicy: Always
        name: kubia
...

上述的 Exec 类型就绪探针将定期在容器内执行 ls /var/ready 命令,如果文件存在则返回退出码 0,否则探测失败。

测试效果

由于并不存在 /var/ready 文件,因此删除容器后自动创建的容器会一直失败,READY 项显示为 0/1:

[root@k8s-master ~]$ kubectl get pod
NAME                             READY   STATUS    RESTARTS   AGE
kubia-2vvrx                      1/1     Running   0          108m
kubia-6j8vf                      1/1     Running   0          108m
kubia-fq6lt                      0/1     Running   0          64s

直到手动添加 ready 文件,使就绪探针返回成功,容器的状态才会变为运行中:

[root@k8s-master ~]$ kubectl exec kubia-fq6lt -- touch /var/ready
[root@k8s-master ~]$ kubectl get pod
NAME                             READY   STATUS    RESTARTS   AGE
kubia-2vvrx                      1/1     Running   0          110m
kubia-6j8vf                      1/1     Running   0          110m
kubia-fq6lt                      1/1     Running   0          2m40s

周期钩子

周期钩子是 Kubernetes 中的一种机制,用于在 Pod 的生命周期事件发生时执行特定的操作。它可以在 Pod 的不同阶段(如启动前、启动后、终止前)触发预定义的脚本或命令。周期钩子可以用于执行初始化操作、配置设置、资源准备、清理等任务。

周期钩子主要包括以下三种类型:

  1. 容器生命周期钩子(Container Lifecycle Hook):在容器的生命周期事件中触发钩子操作。可以在容器启动前(PreStart)或容器终止前(PostStart)执行自定义的脚本或命令。例如,可以在容器启动前等待其他服务可用,或在容器终止前进行资源清理操作。
  2. 初始化容器(Init Container):在 Pod 中的一个或多个初始化容器在主容器启动之前执行。初始化容器可以用于进行初始化设置、数据加载、依赖检查等任务。只有当所有初始化容器成功完成后,主容器才会启动。
  3. Pod 生命周期钩子(Pod Lifecycle Hook):在整个 Pod 的生命周期事件中触发钩子操作。可以在 Pod 启动前(PostStart)或 Pod 终止前(PreStop)执行自定义的脚本或命令。例如,可以在 Pod 启动前进行资源注册、配置加载等操作,或在 Pod 终止前进行清理、数据保存等操作。

生命周期钩子和探针相似,都可以在容器内部执行命令或发送 GET 请求到指定的 URL。但是,生命周期钩子是针对容器而不是 Pod 的。

钩子处理可以使用 HTTP 或 Exec 方式进行实现。

启动后钩子

启动后钩子是在容器的主进程启动之后立即执行的。可以利用该钩子在应用程序启动时执行额外的工作,例如向外部监听器发送应用程序已启动的信号。启动后钩子和主进程并行执行,直到钩子执行完毕之前,容器将一直处于等待状态。此时,Pod 的状态将显示为 Pending。如果钩子执行失败或返回非零状态码,主容器将被终止:

containers:
- name: nodejs
  image: luksa/kubia
  lifecycle:
    postStart:
      exec:
        command:
        - sh
        - -C
        - "echo 'failed'; sleep 5; exit 250"

在上面的示例中,echo、sleep 和 exit 是与容器的主进程一起执行的。通常情况下,可以通过在容器镜像中存储的 shell 脚本或可执行二进制文件来运行这些钩子。

如果钩子程序执行失败,可以在 Pod 的事件中看到 FailedPostStartHook 的警告信息。

停止前钩子

停止前钩子是在容器被终止之前立即执行的。当一个容器需要停止运行时,在发送 SIGTERM 信号给容器进程之前,将执行钩子程序。

添加停止前钩子与添加启动后钩子类似:

containers:
- name: nodejs
  image: luksa/kubia
  lifecycle:
    preStop:
      httpGet:
        port: 8080
        path: shutdown

上述命令会向 http://POD_IP:8080/shutdown 这个 URL 发送 HTTP GET 请求。

还可以设置 scheme(HTTP 或 HTTPS)、host 和请求的 httpHeaders。默认情况下,host 的值为 Pod 的 IP 地址。

与启动后钩子不同的是,无论停止前钩子的执行结果如何,容器都将被终止。