[toc]

0x00 前言简述

前面简单的介绍了Kubernetes基础知识以及单节点和高可以用集群的搭建, 本章将从实操来介绍Kubernetes概念和术语以及控制器,便于各位读者进行学习;


0x01 资源清单

Q: 什么是资源?

答: K8s中所有的内容都抽象为资源在资源实例化(容器被执行)之后叫做对象;

Q: 什么是Kubernetes对象?

答:Kubernetes对象指的是Kubernetes系统的持久化实体,所有这些对象合起来代表了你集群的实际情况。创建一个k8s对象就是告诉Kubernetes,您需要的集群中的工作负载是什么(集群的目标状态), 因为一个Kubernetes对象代表着用户的一个意图(a record of intent),一旦您创建了一个Kubernetes对象,Kubernetes将持续工作以尽量实现此用户的意图。

常规的应用里我们把应用程序的数据存储在数据库中,Kubernetes将其数据以Kubernetes对象的形式通过 api server 存储在 etcd 中;
Kubernetes对象数据描述的信息:

  • 集群中运行了哪些容器化应用程序(以及在哪个节点上运行)
  • 集群中对应用程序可用的资源
  • 应用程序相关的策略定义,例如,重启策略、升级策略、容错策略
  • 其他Kubernetes管理应用程序时所需要的信息

如何进行Kubernetes对象的CURD呢?

答:自带的kubectl命令或者一些管理k8s的图形化界面工具比如Kuboard或者Kubedash;

PS: kubectl、kuboard 最终都通过调用 kubernetes API 来实现对 Kubernetes 对象的操作。您也可以直接在自己的程序中调用 Kubernetes API,此时您可能要有用到 Client Libraries


基础补充:

  • 1.名称空间级别资源 (Namespace Level): 仅在此名称空间下生效k8s的系统组件是默认放在kube-system名称空间下的,而kubectl get pod等价于kubectl get pod -n default,因此查看不到k8s的系统组件。

    • 工作负载型资源(workload):Pod、ReplicaSet(调度器控制器通过标签保证Pod的副本数以及创建Pod)、Deployment、StatefulSet(有状态服务的控制器)、DaemonSet(可以在每个节点都运行一个Pod的组件)、Job、CronJob(ReplicationController在v1.11版本被废弃)

    • 服务发现及负载均衡型资源(Service Discovery LoadBalance):Service、Ingress、…

    • 配置与存储型资源:Volume(存储卷)、CSI(容器存储接口可以扩展各种各样的第三方存储卷)

    • 特殊类型的存储卷:ConfigMap(当配置中心来使用的资源类型可以达到热更新)、Secret(保存敏感数据)、DownwardAPI(把外部环境中的信息输出给容器)

  • 2.集群级资源:不管在任何名称空间下定义,在其他的名称空间下都能看得到,在定义的时候无需指定名称空间

    • Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding
  • 3.元数据型资源:提供一个指标,不像是名称空间类型又不像集群级别,本质上更像是在两者之间,但是它有自己的特点,所以更应该作为一个单独的分类,例如HPA【通过cpu的利用率进行平滑扩展】就是一个很明显的元数据类型,通过指标进行操作。

    • HPA、PodTemplate、LimitRange(根据指标进行相对应的操作)


Q: 什么是资源清单?

答: 您可以将其理解为剧本,里面规定了每一步如何操作,Kubernets只需要按照要求去做即可;
在 K8s 中一般使用Yaml格式或者Json格式的文件来创建符合我们预期期望的Pod,该yaml文件我们称为资源清单;


Q: 如何编辑资源清单?

答: 在编写清单时候必须对Kubernetes定义Pod以及控制器(Controller)的常用字段有一定的了解, 如果忘记对象的资源清单字段可以通过explain命令查看相对应的控制清单编写字段;

资源清单编写帮助: 获取资源的apiVersion的版本信息(以pod为例),该命令同时输出属性设置帮助文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
kubectl explain pod
# KIND: Pod
# VERSION: v1
# DESCRIPTION:
# Pod is a collection of containers that can run on a host. This resource is
# created by clients and scheduled onto hosts.

# FIELDS:
# apiVersion <string>
# APIVersion defines the versioned schema of this representation of an
# object. Servers should convert recognized schemas to the latest internal
# value, and may reject unrecognized values. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

# kind <string>
# Kind is a string value representing the REST resource this object
# represents. Servers may infer this from the endpoint the client submits
# requests to. Cannot be updated. In CamelCase. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds

# metadata <Object>
# Standard object's metadata. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata

# spec <Object>
# Specification of the desired behavior of the pod. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

# status <Object>
# Most recently observed status of the pod. This data may not be up to date.
# Populated by the system. Read-only. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status

kubectl explain pod.apiVersion #查看指定属性说明
# KIND: Pod
# VERSION: v1

# FIELD: apiVersion <string>

# DESCRIPTION:
# APIVersion defines the versioned schema of this representation of an
# object. Servers should convert recognized schemas to the latest internal
# value, and may reject unrecognized values. More info:
# https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources

查看k8s中有对象可用那些资源的API版本(1.19.6)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
$ kubectl api-versions

#常用
apiVersion: apps/v1
# API版本
apps/v1
v1
admissionregistration.k8s.io/v1
admissionregistration.k8s.io/v1beta1
apiextensions.k8s.io/v1
apiextensions.k8s.io/v1beta1
apiregistration.k8s.io/v1
apiregistration.k8s.io/v1beta1
authentication.k8s.io/v1
authentication.k8s.io/v1beta1
authorization.k8s.io/v1
authorization.k8s.io/v1beta1
autoscaling/v1
autoscaling/v2beta1
autoscaling/v2beta2
batch/v1
batch/v1beta1
certificates.k8s.io/v1beta1
coordination.k8s.io/v1
coordination.k8s.io/v1beta1
crd.projectcalico.org/v1
discovery.k8s.io/v1beta1
events.k8s.io/v1beta1
extensions/v1beta1
networking.k8s.io/v1
networking.k8s.io/v1beta1
node.k8s.io/v1beta1
policy/v1beta1
rbac.authorization.k8s.io/v1
rbac.authorization.k8s.io/v1beta1
scheduling.k8s.io/v1
scheduling.k8s.io/v1beta1
storage.k8s.io/v1
storage.k8s.io/v1beta1


(1) 对象字段

描述: 每个K8s控制器对象都包含了两个重要的字段,即 specstatus 字段, Kubernetes通过对应的控制器,不断地使实际状态趋向于您期望的目标状态。

  • spec 必须由您来提供,描述了您对该对象所期望的 目标状态
  • status 只能由 Kubernetes 系统来修改,描述了该对象在 Kubernetes 系统中的 实际状态

例如,一个 Kubernetes Deployment 对象可以代表一个应用程序在集群中的运行状态。当您创建 Deployment 对象时,您可以通过 Deployment 的 spec 字段指定需要运行应用程序副本数(replicas假设为3)。Kubernetes 从 Deployment 的 spec 中读取这些信息,并为您创建指定容器化应用程序的 3 个副本,再将实际的状态更新到 Deployment 的 status 字段。Kubernetes 系统将不断地比较 实际状态 staus 和 目标状态 spec 之间的差异,并根据差异做出对应的调整。
例如,如果任何一个副本运行失败了,Kubernetes 将启动一个新的副本,以替代失败的副本。

apiVersion - 必须

描述: 用来创建对象时所使用的Kubernetes API版本,可通过kubectl api-versions命令查询可用API版本;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
kubectl api-versions
admissionregistration.k8s.io/v1
apiextensions.k8s.io/v1
apiregistration.k8s.io/v1
apps/v1
authentication.k8s.io/v1
authorization.k8s.io/v1
autoscaling/v1
autoscaling/v2
autoscaling/v2beta1
autoscaling/v2beta2
batch/v1
batch/v1beta1
certificates.k8s.io/v1
coordination.k8s.io/v1
crd.projectcalico.org/v1
discovery.k8s.io/v1
discovery.k8s.io/v1beta1
events.k8s.io/v1
events.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta1
flowcontrol.apiserver.k8s.io/v1beta2
networking.k8s.io/v1
node.k8s.io/v1
node.k8s.io/v1beta1
policy/v1
policy/v1beta1
rbac.authorization.k8s.io/v1
scheduling.k8s.io/v1
storage.k8s.io/v1
storage.k8s.io/v1beta1
v1


kind - 必须

描述:被创建对象的类型常用的有Deployment(部署)、Service(服务端口)


metadata - 必须

描述:用于唯一确定该对象的元数据,包括 namenamespace,如果 namespace 为空,则默认值为 default;

1
2
3
metadata:
name: nginx-deployment # 创建的资源对象名称
namespace: nginx-app # 设置指定的名称空间,默认为default


Annotation - 注解

描述: 从Annotation字面意思理解其用途就是注解,或者有一些小伙伴看到过它,它类使用前面所提到的Label其也是采用Key/Value键值对进行表示;

1
2
3
4
metadata:
annotation:
key1: value1
key2: value2


Q: Annotation 与 Lables 不同之处?

答: Lable 具有更为严格的命令规则,主要用于定义资源对象的元数据(Metadata)并且被Label Seletor使用
Annotation 是用户任意定义的附加信息,便于外部工具查找;

Q: Annotation 记录的信息?

A: Build 信息、Release 信息、Docker Image 信息、Docker Registry地址
A: 开发环境信息、工具名称、版本号等
A: 团队开发信息、电话号码、负责人、以及网站等


spec - 必须

描述:您对该对象的期望状态但是需注意不同类型的 Kubernetes,其 spec 对象的格式不同(含有不同的内嵌字段),通过 API 手册 可以查看 Kubernetes 对象的字段和描述;

例如,假设您想了解 Pod 的 spec 定义,可以在 这里 找到,Deployment 的 spec 定义可以在 这里 找到;


资源清单常用的字段描述:

  • 1.必须存在的属性【创建资源清单的时候没有这些属性的存在它是不允许被执行的】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #参数名称 	字段类型 	说明
    version String 这里是指的是K8SAPI的版本,目前基本上是v1,可以用 `kubectl api-version` 命令查询
    kind String 这里指的是yam文件定义的资源类型和角色,比如:Pod
    metadata Object 元数据对象,固定值就写metadata
    metadata.name String 元数据对象的名字,这里由我们编写,比如命名Pod的名字
    metadata.namespace String 元数据对象的命名空间,由我们自身定义,如果不定义的话则默认是default名称空间
    Spec Object 详细定义对象,固定值就写Spec
    spec.containers[] List 这里是Spec对象的容器列表定义,是个列表
    spec.containers[].name String 这里定义容器的名字
    spec.containers[].image String 这里定义要用到的镜像名称
  • 2.主要属性【这些属性比较重要,如果不指定的话系统会自动补充默认值】

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #参数名称 	字段类型 	说明
    spec.containers[].name String 这里定义容器的名字
    spec.containers[].image String 这里定义要用到的镜像名称
    spec.containers[].imagePullPolicy String 定义镜像拉取策略,有Always、Never、 IfNotPresent三个值可选(1)Always:意思是每次都尝试重新拉取镜像(2)Never:表示仅使用本地镜像(3)lfNotPresent:如果本地有镜像就使用本地镜像,没有就拉取在线镜像。上面三个值都没设置的话,默认是Always。
    spec.containers[].command[] List 指定容器启动命令,因为是数组可以指定多个,不指定则使用镜像打包时使用的启动命令。
    spec.containers[].args[] List 指定容器启动命令参数,因为是数组可以指定多个。
    spec.containers[].workingDir String 指定容器的工作目录,进入容器时默认所在的目录
    spec.containers[].volumeMounts[] List 指定容器内部的存储卷配置
    spec.containers[].volumeMounts[].name String 指定可以被容器挂载的存储卷的名称
    spec.containers[].volumeMounts[].mountPath String 指定可以被容器挂载的存储卷的路径
    spec.containers[].volumeMounts[].readOnly String 设置存储卷路经的读写模式,true或者false,默认为读写模式
    spec.containers[].ports[] List 指定容器需要用到的端口列表
    spec.containers[].ports[].name String 指定端口名称
    spec.containers[].ports[].containerPort String 指定容器需要监听的端口号
    spec.containers[].ports[].hostPort String 指定容器所在主机需要监听的端口号,默认跟上面containerPort相同,注意设置了hostPort同一台主机无法启动该容器的相同副本(因为主机的端口号不能相同,这样会冲突)
    spec.containers[].ports[].protocol String 指定端口协议,支持TCP和UDP,默认值为 TCP
    spec.containers[].env[] List 指定容器运行前需设置的环境变量列表
    spec.containers[].env[].name String 指定环境变量名称
    spec.containers[].env[].value String 指定环境变量值
    spec.containers[].resources Object 指定资源限制和资源请求的值(这里开始就是设置容器的资源上限)
    spec.containers[].resources.limits Object 指定设置容器运行时资源的运行上限
    spec.containers[].resources.limits.cpu String 指定CPU的限制,单位为core数,将用于docker run --cpu-shares参数这里前面文章 Pod资源限制有讲过)
    spec.containers[].resources.limits.memory String 指定MEM内存的限制,单位为MlB、GiB
    spec.containers[].resources.requests Object 指定容器启动和调度时的限制设置
    spec.containers[].resources.requests.cpu String CPU请求,单位为core数,容器启动时初始化可用数量
    spec.containers[].resources.requests.memory String 内存请求,单位为MIB、GiB,容器启动的初始化可用数量
  • 3.额外的参数项

    1
    2
    3
    4
    5
    参数名称 	字段类型 	说明
    spec.restartPolicy String 定义Pod的重启策略,可选值为Always、OnFailure、Never 默认值为Always。1.Always:Pod一旦终止运行,则无论容器是如何终止的,kubelet服务都将重启它。2.OnFailure:只有Pod以非零退出码终止时,kubelet才会重启该容器。如果容器正常结束(退出码为0),则kubelet将不会重启它。3.Never:Pod终止后,kubelet将退出码报告给Master,不会重启该Pod。
    spec.nodeSelector Object 定义Node的Label过滤标签,以key:value格式指定,选择node节点去运行
    spec.imagePullSecrets Object 定义pull镜像时使用secret名称,以name:secretkey格式指定
    spec.hostNetwork Boolean 定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。


资源清单格式:

1
2
3
4
5
6
7
8
9
apiVersion: group/apiversion #定义对象的版本模式; 如果没有给定group名称,那么默认为core可以使用kubectlapi-versions命令获取当前k8s版本上所有的apiversion版本信息(每个版本可能不同)
kind: #资源类别: 该对象表示的REST资源
metadata: #资源元数据
name: #资源自定义名称
namespace: #资源所属的名称空间
lables: #资源
annotations: #注解:主要目的是方便用户阅读查找
spec: #期望的状态(disired state)
status: #当前状态,本字段由Kubernetes自身维护,用户不能去定义

示例:以下是创建Pod所定义清单文件内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: v1 
kind: Pod
metadata:
name: pod-demo
namespace: default
1abels:
app:myapp
spec:
containers:
- name: myapp-1
image: harbor.weiyigeek.top/1ibrary/myapp:v1 # 支持多个容器运行在同一个Pod之中
- name: busybox-1
image: busybox:latest
command:
- "/bin/sh"
- "-c"
- "sleep 3600"

PS: 创建pod时如发生错误可以通过kubectl describe pod myapp-pod-namekubectl logs myapp-pod-name命令查看pod相关信息(注意如果有名称空间则需要加上);
PS: 一个Pod内支持多个容器运行所以在定义资源清单的时候,可以在spec.containers数组中指定多个运行的容器及其镜像;


0x02 NameSpace - 名称空间

描述: Namespace即名称空间,您可能在c++或者c#中听说它, 它也是K8s系统中非常重要的一个概念;

Tips : 由于创建多个集群会导致集群资源使用碎片化以及更多的维护成本,可以通过Kubernetes的NameSpace对不同工作组的需求隔离。

Q: namespace 有何作用?

A: 它可以通过不同的资源对象调度到不同的Namespace中, 从逻辑上形成不同项目、小组或用户组,便于不同的分组之间能够共享使用整个集群的资源同时还能分别管理;
简而言之: namespace 能够帮助不同的租户(多租户管理)共享一个k8s集群的资源,使得整个集群的配置非常灵、方便。

Q: namespace 命名规则?

A: 只能包括[a-z0-9]并且最大长度为63位;

Tips : 默认情况下k8s集群会创建一个默认的名称空间即Default, 如果采用资源对象未指定namespace时,则用户所创建的所有资源对象如Pod、RC、RS、Deployment、Service都将被分配到default的namespace之中;


namespace 创建查看

  • Step 1.名称空间创建的两种方式命令行、yaml配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # 方式1
    kubectl create namespace dev-ops
    # namespace/dev-ops created

    # 方式2
    apiVersion: v1
    kind: Namespace
    metadata:
    name: dev-ops

    kubecrl create -f namespace-create.yaml
    # namespace/dev-ops created
  • Step 2.查看名称空间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ~$ kubectl get ns
    # NAME STATUS AGE
    # default Active 89d
    # dev-ops Active 16s

    ~$ kubectl describe ns dev-ops
    # Name: dev-ops
    # Labels: <none>
    # Annotations: <none>
    # Status: Active

    # No resource quota. # 后续讲解资源配额

    # No LimitRange resource.
  • Step 3.删除名称空间(非常注意删除名称空间是非常注意是该namespace下存在pvc/deployment/rc等资源对象, 否则删除名称空间后其资源将被销毁)

    1
    2
    kubectl delete namespace dev-ops # 慎用 -force,-f 选项;
    kubectl delete -f namespace-create.yaml


namespace 配额

描述: 默认情况下创建的namespace并不会对资源进行配额,如果需要对某一个Namespace配额则需要配合ResourceQuota使用;

基础示例: ResourceQuota 配额管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# (1) 创建名称空间
kubectl create namespace dev-ops

# (2) resource-quote-demo.yaml | 资源清单yaml文件
cat > K8s/Day1/resource-quote-demo.yaml <<'end'
apiVersion: v1
kind: ResourceQuota
metadata:
name: resource-quote-demo
spec:
hard:
# 表示全部Container的内存requests总和不能超过1G,而内存Limits的总和不能超过2GiB (CPU 与之相同)
requests.cpu: "1"
requests.memory: 1Gi
limits.cpu: "2"
limits.memory: 2Gi
end

# (3) 创建ResourceQuota并为其指定名称空间
~/K8s/Day1$ kubectl create -f resource-quote-demo.yaml -n dev-ops
# resourcequota/resource-quote-demo created

# (4) 获取名称空间的配额信息
~/K8s/Day1$ kubectl get resourcequota -n dev-ops # -o yaml 可以查看到使用的情况
# NAME AGE REQUEST LIMIT
# resource-quote-demo 73s requests.cpu: 0/1, requests.memory: 0/1Gi limits.cpu: 0/2, limits.memory: 0/2Gi
~/K8s/Day1$ kubectl describe resourcequota -n dev-ops
# Name: resource-quote-demo
# Namespace: dev-ops
# Resource Used Hard
# -------- ---- ----
# limits.cpu 0 2
# limits.memory 0 2Gi
# requests.cpu 0 1
# requests.memory 0 1Gi

# (5) 如果在该名称空间中创建Pod并设置了resource,则 通过 -o yaml 可以查看到该名称空间使用的情况
~/K8s/Day1$ kubectl get resourcequota -n dev-ops -o yaml
# status:
# hard:
# limits.cpu: "2"
# limits.memory: 2Gi
# requests.cpu: "1"
# requests.memory: 1Gi
# used:
# limits.cpu: "1"
# limits.memory: "1.5Gi"
# requests.cpu: "800m"
# requests.memory: "700Mi"

Tips : 如果此时再创建一个Pod并设置resources.requests.memory的值为700Mi。则会显示错误由于请求700Mi已经超出剩余的剩余的Memoryquota的值,所以新创建Pod的请求将会被终止;

Tips : 非常建议每个Container设置CPU/Memory的request与limit值(涉及到Qos后文讲述)

Tips :我们不单单可以对Namespace做名称空间资源限制还可以通过其对应Container、Pod数量配额(副本数)、API对象的配额等;


0x03 Pod 基础&进阶

我们知道 Pod 是Kubernetes里最小单元,部署在节点之上包含一组容器(Container)和卷(Volume), 同一个Pod中的容器共享同一个网络命名空间即可以通过localhost进行相互通信;
简单的说 Pod 是一组可以在主机上运行的容器,该资源由客户端创建并调度到主机上。

先来提出几个问题&回答:

  • (1) 如果Pod生命周期是短暂的那么如何才能持久化容器数据,即使在Pod被销毁或者重启到其它机器上存在?
    • 答: k8s支持(Volume,Persistent Volumes)的概念所以可以使用持久化的卷类型;
  • (2) 如何创建大批量的实例副本?
    • 答: 建议采用 Replication Controller / Replica Set / Deployment / StateSetful 使用Pod模板创建多份拷贝;
  • (3) 如果Pod生命周期是短暂的那么重建pod后意味着IP地址可能会发生变化,那如何才能从前端容器正确可靠的指向后端容器?
    • 答: 使用 Service 服务发现对象


Pod 分类

  • 自助式pod 只要pod退出了,此类型的pod不会被重建,该pod没有管理者,死亡后不会被拉起。
  • 控制器管理的pod【生产环境中大多数都是选择控制器去管理pod】 在控制器的生命周期里始终要维持pod的副本数目

自助式pod与控制器管理的pod有何区别?

答: 其生命周期被管理的机制不太一致


(1) Pod Template & Controller

描述: 我们一般不会在k8s中直接创建单个Pod,因为其的生命周期是短暂的(即用后即焚的实体),当Pod被创建后都会被k8s调度到集群的Node之上, 直到Pod进程终止被删除, 因为缺少资源而被驱逐或者Node故障之前这个Pod都会一直保持在那个Node上;

注意事项:

  • 1.重启Pod中的容器和重启Pod是两种概念,因为Pod只提供容器运行环境并保持容器的运行状态所有重启容器并不会导致Pod重启, 最外层还有个Pause容器在运行;
  • 2.自助式Pod并不会自愈,如果当Pod运行的Node节点故障或者调度器本身故障该Pod就会被删除,同样的如果Pod所在Node缺少资源或者Pod处于维护状态也将会被驱逐,所以常常使用Controller来管理Pod的。

Controller 可以创建和管理多个Pod并且提供副本管理、滚动升级和集群级别的自愈能力;
例如:当一个Node故障Controller就能自动将该节点上的Pod调度到其他健康的Node上;


常用于创建Pod的控制器列表:
一般来说Pod并不会自动消失,除非是特意将它们进行销毁如人为操作或者控制器操作, 该规则唯一例外的是成功或者失败的Phase超过一段时间(由Master确定操作)的Pod将过期并被自动销毁;
如下列控制器:

  • Replication Controller
  • Delopyment
  • StatefulSet
  • DaemonSet
  • Job

Tips : 通常Controller会通过Pod Template来创建相应的Pod, 而又为什么建议使用控制器创建Pod而非直接创建呢?;

答: 因为单独的Pod在机器故障的情况下没有办法自动复原而控制器则可以按照期望副本数进行构建复原;


什么是Pod模板?
答: Pod 模板是包含了其他对象(例如RC、Jobs和DeamonSets)中的Pod定义。Controller 控制器使用 Pod模板 以及 selector选择器 来创建实际需要的Pod;


(1) Pod 资源配额与限额
描述: 每个Pod都可以对其能使用的服务器上的计算机资源比如CPU和Memory进行设置限额,值得注意的是CPU的资源单位为CPU(Core)的数量是一个绝对值而非相对值,同样Memory也是一个绝对值;

在Kubernets中CPU常以千分之一的CPU配额作为最小的单元通常用m表示,而Memory配额单位是内存字节数通常用Mi表示;
例如: 通常一个容器的配额被定义为100~300m即占用0.1~0.3个CPU,由于CPU配额是个绝对值所以说无论是1C或者48C的机器上100m代表的配额都是一样的;

1
2
3
4
5
6
7
8
9
resources:
# 限制
limits:
cpu: 500m
memory: 500Mi
# 请求(依赖)
requests:
cpu: 500m
memory: 6Gi


示例.Pod Template & Pod resources

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: apps/v1        # apiserver版本
kind: Deployment # 绑定动作
metadata:
name: nginx-deployment # 应用 + 绑定类型
namespace: nginx-app # 名称空间
labels:
app: nginx
spec:
selector: # 选择器,匹配Pod模板中的标签才知道运行了几个副本实例;
matchLabels:
app: nginx
role: master
tier: backend
replicas: 2 # 运行 2 个容器化应用程序副本
template: # Pod 模板
metadata:
labels:
app: nginx
role: master
tier: backend
spec:
containers:
- name: nginx
image: nginx:1.7.9
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 800m
memory: 800Mi
ports:
- containerPort: 8080 # 暴露的端口

Yaml 配置文件相关操作:

1
2
3
#1.使用 kube apply 命令可以创建该 .yaml 文件中的 Deployment 对象
kubectl apply -f deployment.yaml
kubectl apply -f https://weiyigeek.top/k8s/deployment.yaml


Tips : 可以采用直接 RUN 创建Pod, 实际上不加port也能通过服务进行发现实际工作中一定要加上为了后期问题的排查

1
kubectl run nginx --image nginx:latest --port=80 --replicas=2


(2) Static Pod (静态)

Q: 什么是静态Pod?

答: 在 k8s 中由kubelet创建并运行的Pod, 该种类型的Pod可以在某个节点上长期运行(即静态Pod)。
简单说静态 pod 是由 kubelet 创建和管理的只在特定node上存在的 pod,并且只在kubelet所在的Node上运行。


静态Pod特点:

  • 1.静态 pod 总是由某个节点上的 kubelet 创建和管理的, 即不能通过 api-server 来管理,所以无法和 RC,RS,Deployment 或者 DaemonSet控制器进行关联。
  • 2.kubelet 无法对 静态 pod 进行健康检查。
  • 3.如果把 pod 的yaml描述文件放到这个目录中,等kubelet扫描到文件,会自动在本机创建出来 pod;
  • 4.如果把 pod 的yaml文件更改了,kubelet也会识别到,会自动更新 pod;
  • 5.如果把 pod 的yaml文件删除了,kubelet会自动删除掉pod;
  • 6.如果当 kubelet 发现静态Pod停止掉时候,将会重新启动静态Pod;

Tips: 因为静态 pod 不能被 api-server 直接管理,所以它的更新删除操作不能由 kubectl 来执行,只能直接修改或删除文本文件。

静态Pod创建的两种方式:

  • 1.HTTP: kubelet 周期地从采用 --manifest-url 参数指定的地址下载文件,并且将它翻译成JSON/YAML格式的Pod定义(实际与下面差距不大只是增加了下账的步骤)
  • 2.配置文件:kubelet 启动时采用 --pod-manifest-path=/etc/kubernetes/manifests 指定yaml文件存放目录 会定期扫描这个目录,并根据这个目录下的 .yaml 或 .json 文件进行创建和更新操作

例如将static-nginx-web.yaml文件加入到该文件夹之中, 重启 Kubelet 服务或者等待几秒即可创建静态 Pod;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
sudo tee /etc/kubernetes/manifests/static-pod.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: static-nginx-web
lables:
app: nginx
spec:
containers:
- name : web
image: nginx:latest
imagePullPolicy: IfNotPresent
ports:
- name: nginx-port
containerPort: 80
protocol: TCP
EOF

操作流程:

  • (1) 写入到/etc/kubernetes/manifests/目录后查看kubelet状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    $ systemctl status kubelet
    # ● kubelet.service - kubelet: The Kubernetes Node Agent
    # Loaded: loaded (/lib/systemd/system/kubelet.service; enabled; vendor preset: enabled)
    # Drop-In: /etc/systemd/system/kubelet.service.d
    # └─10-kubeadm.conf
    # Active: active (running) since Wed 2021-01-20 03:18:21 UTC; 1h 24min ago
    # Docs: https://kubernetes.io/docs/home/
    # Main PID: 1436080 (kubelet)
    # Tasks: 19 (limit: 9450)
    # Memory: 50.7M
    # CGroup: /system.slice/kubelet.service
    # └─1436080 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --network-plugin=cni --pod-infra-container-image=
  • (2) 静态 Pod 查看

    1
    2
    3
    4
    5
    $ ls /etc/kubernetes/manifests
    etcd.yaml kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml static-pod.yaml

    $ kubectl get pod -o wide | grep "static"
    # static-nginx-web-weiyigeek-107 1/1 Running 0 82m 172.16.0.197 weiyigeek-107 <none> <none>
  • (3) 移除静态 Pod

    1
    2
    3
    # 注意:移除静态Pod虽然用api server可以删除静态Pod,但他很快自动重新生成Pod,所以通过删除静态Pod的yaml配置资源文件则完成移除静态Pod;
    $ rm /etc/kubernetes/manifests/static-pod.yaml
    $ kubectl get pod


(3) Pod 生命周期

Q: 主要通过以下层面了解Pod的声明周期;

  1. 在Pod被创建后经历的过程及其状态;
  2. Pod中容器探测纠察;

Pod 创建过程与各对象生命周期流程图:

WeiyiGeek.k8s中Pod创建过程与生命周期

WeiyiGeek.k8s中Pod创建过程与生命周期


简单说明:

  • 1.前面我们讲解了Kubectl、KubeAPI-Server、ETCD、Kubelet、CRI等组件此处不在累述;
  • 2.Pause 容器: Pod 创建之初的创建它是Pod里容器共享网络栈以及存储卷必备的;
  • 3.Init C(初始化容器): Pod 能够具有一个或多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的Init容器。
    • 2.1 Init容器与普通的容器区别: Init容器总是运行到成功完成为止,每个Init容器都必须在下一个Init容器启动之前成功完成(有一定的顺序)。
    • 2.2 如果Pod的Init容器失败 Kubernetes会不断地重启该Pod,直到Init容器成功为止。但是如果 Pod 对应的 restartPolicy【默认为Always】 设置为Never,它将不会重新启动。
  • 4.Mian C(主容器 ): 在Pod中可以运行一个多个容器它们都有各自的生命周期, 当Pod中主容器结束退出时Pod将结束;
  • 5.Start / Stop : 在容器启动或者停止时候执行的脚本
  • 6.Readiness : 在主容器启动后进行其应用进程探测,完成后容器的状态将改变成Running;
  • 7.Liveness : 在主容器的生命周期都有它的身影,其作用是在主容器的进程与其检测结果不一致的情况下将执行重启或者删除容器;


Pod Phase

描述: Pod的status字段是保存在一个PodStatus对象中,该 PodStatus 中有一个 phase 字段。
Pod的相位(phase) 是 Pod 在其生命周期中的简单宏观概述。

PS : 该阶段并不是对容器或Pod的综合汇总,也不是为了做为综合状态机, 并且Pod相位的数量和含义是严格指定的。
PS : 除了本文档中列举的状态外不应该在假定Pod有其他的phase值

Pod phase 可能存在的几种状态值:

  • 挂起(Pending):Pod 已被 Kubernetes系统接受,但有一个或者多个容器镜像尚未创建。等待时间包括调度Pod的时间和通过网络下载镜像的时间,这可能需要花点时间
  • 运行中(Running):该Pod 已经绑定到了一个节点上,Pod中所有的容器都已被创建。至少有一个容器正在运行,或者正处于启动或重启状态
  • 成功(Succeeded):Pod中的所有容器都被成功终止,并且不会再重启
  • 失败(Failed):Pod中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说容器以非0状态退出或者被系统终止
  • 未知(Unknown):因为某些原因无法取得Pod的状态,通常是因为与Pod所在主机通信失败



Pod 的生命周期示意图(从图中可以看到Pod状态的变化)

WeiyiGeek.Pod 的生命周期示意图

WeiyiGeek.Pod 的生命周期示意图


示例1.Pod Phase 与 Pod 运行状态查看

1
2
3
4
5
$ kubectl get  pod redis-cluster-operator-6669898858-rwm7s -o yaml  | grep " phase"
phase: Running

$ kubectl describe pod redis-cluster-operator-6669898858-rwm7s | grep "^Status"
Status: Running


Pod 重启策略

描述: 当 Pod 中某一个 Container 处于退出状态(Exited)时, Kubelet 会根据 Pod Sepc 中 restartPolicy 字段的取值(Always [缺省]、OnFailure和Never 进行相应的操作;

1
2
3
kubectl explain statefulset.spec.template.spec | grep "restartPolicy"
restartPolicy. The name for an init container or normal container must be
restartPolicy <string> # only Support Always


重启策略说明:

  • Always : 默认重启策略只要有一个Container处于Exited时,即会重启
  • OnFailure : 当容器异常退出或退出状态不为0时,即会重启
  • Never : 无论如何都不重启


补充说明:

  • (1) 当一个pod设置重启策略restartPolicy则适用于Pod 中的所有容器, 触发规则时通过同一节点上的kubelet重新启动容器。
    restartPolicy 仅指, 失败的容器由 kubelet 以五分钟为上限的指数退避延迟(10秒,20秒,40秒...) 重新启动,并在成功执行十分钟后重置。

  • (2) 不同控制器对不同Pod重启策略的处理机制是不一样的例如

    • 2.1 使用 Job 控制器创建POD运行预期会终止, 如批量计算此时Job仅适用于重启策略为 OnFailure 或 Never 的 Pod;
    • 2.2 使用 RC、RS、Deployment、StatefulSet控制器创建的Pod预期是不会终止的,如Web服务器注意RC仅适用于具有 RestartPolicy 为 Always 的Pod。
    • 2.3 使用 DeamonSet 控制器创建的Pod将会在每台工作节点上运行,提供特定于机器的系统服务;

Tips : 如Pod文档中所述一旦绑定到一个节点,Pod将永远不会重新绑定到另一个节点。


Pod状态示例

  • ⒈Pod 中只有一个容器并且正在运行,容器成功退出

    1
    2
    3
    4
    5
    ·记录事件完成 
    ·如果restartPolicy为:
     Always:重启容器; #Pod phase 仍为 Running
    OnFailure: #Pod phase 变成 Succeeded
     Never: #Pod phase 变成 Succeeded
  • ⒉Pod 中只有一个容器并且正在运行,容器退出失败

    1
    2
    3
    4
    5
    ·记录失败事件
    ·如果restartPolicy为:
    Always:重启容器; #Pod phase 仍为 Running
    OnFailure:重启容器;#Pod phase 仍为 Running
    Never: #Pod phase 变成 Failed
  • ⒊Pod 中有两个容器并且正在运行。
    如果容器1退出失败

    1
    2
    3
    4
    5
    记录失败事件·
    如果restartPolicy为:
    Always:重启容器; #Pod phase仍为Running
    OnFailure:重启容器;#Pod phase仍为Running
    Never:不重启容器; #Pod phase仍为Running
  • 4.如果有容器1没有处于运行状态并且容器2退出:

    1
    2
    3
    4
    5
    记录失败事件
    如果restartPolicy为:
    Always:重启容器; #Pod phase仍为Running
    OnFailure:重启容器; #Pod phase仍为Running
    Never: #Pod phase变成Failed
  • ⒌Pod 中只有一个容器并处于运行状态。容器运行时内存超出限制

    1
    2
    3
    4
    5
    ·容器以失败状态终止记录0OM事件
    ·如果restartPolicy为:
    Always:重启容器; #Pod phase仍为Running
    OnFailure:重启容器;#Pod phase仍为Running
    Never:记录失败事件;#Pod phase仍为Failed
  • ⒍Pod 正在运行磁盘故障

    1
    2
    3
    ·杀掉所有容器,记录适当事件
    ·Pod phase 变成 Failed
    ·如果使用控制器来运行 Pod 将在其他节点重建
  • ⒎Pod 正在运行,其节点被分段
    1
    2
    3
    ·节点控制器等待直到超时
    ·节点控制器将Pod phase设置为Failed
    ·如果是用控制器来运行,Pod 将在别处重建

Pod Init 容器

Q: 什么是 Init Container?
描述: 一个Pod中可以有一个或者多个 Init Container 即初始化容器操作, 其启动顺序受yaml文件中描述顺序影响进行启动(串行方式), 这表示了每个容器必须在下一个容器启动之前成功退出,否则将会阻止后续的 init Container 与 app Container 服务启动,例如 init Container 1 -> init Container 2 .. -> init Container n -> app Container;


Q: Init容器的作用有那些?

答: 因为Init容器具有与应用程序容器分离的单独镜像,所以利用其特征主要作为服务依赖处理, 例如某个服务A需依赖db或者Memcached, 那么可以利用服务A pod的Init Container判断dbdb或者Memcached是否正常提供服务,如果启动服务失败或者工作异常,则设置init 容器也将启动失败并且pod中的app 容器也将不会运行。

所以它们的启动相关代码具有其它优势:

(1) 它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这些实用工具的
(2) 它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如创建镜像没必要FROM另一个镜像,只需要在安装过程中使用类似sed、awk、python或dig这样的工具。
(3) 应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
(4) Init 容器使用Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因此它们能够具有访问Secret的权限而应用程序容器则不能(简单的说 Init Container也可被授权访问应用Container无权访问的内容)。
(5) 它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以Init容器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。


init 容器补充说明:

  • 在Pod启动过程中Init 容器会按顺序在网络和数据卷初始化之后启动【即在pause启动之后启动,pod第一个启动的容器并不是Init而是pause, 但pause仅仅只负责初始化网络和数据卷其它什么操作都不能做,我们可操作的空间几乎为0因此不做描述】。

  • 如果由于运行时或失败退出,将导致容器启动失败,它会根据Pod的restart Policy指定的策略进行重试。然而,如果Pod的restartPolicy设置为Always,Init 容器失败时会使用RestartPolicy 策略

  • 从上面Pod 创建过程与各对象生命周期流程图中可知Init Container必须要在Pod状态变成Ready之前完成(其不需要readiness), 以及在资源的request和limits与app container 有些许差别,除以上两种init 容器 与 app 容器 大致相同;

  • 在所有的Init容器没有成功之前,Pod将不会变成Ready状态。Init容器的端口将不会在Service中进行聚集。正在初始化中的Pod处于Pending状态,但应该会将Initializing状态设置为true

  • 如果Pod 重启,所有Init容器必须重新执行,因此Init容器应该配置为幂等的状态。

  • Init 容器具有应用容器的所有字段。除了readiness Probe因为Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态(会在验证过程中强制执行)

  • 在 Pod 中的每个app和Init容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误;当然你可以加上一个名称空间来防止冲突;

  • 对Init容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改Init容器的image字段,等价于重启该Pod
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    $ kubectl edit -n test pod myapp-pod
    ......
    # 以一个 init C 容器 为例
    initContainers:
    - command:
    - sh
    - -c
    - until nslookup myservice;do echo waiting for myservice;sleep 2; done;
    image: busybox # 表明的意思是只有在更改镜像名称时 Init 容器才会重新执行init C阶段, 修改其他字段无任何效果;
    imagePullPolicy: Always
    name: init-myservice
    resources: {}
    terminationMessagePath: /dev/termination-log
    terminationMessagePolicy: File
    volumeMounts:
    - mountPath: /var/run/secrets/kubernetes.io/serviceaccount
    name: default-token-bk9vl
    readOnly: true


Init 容器使用简单示例:

  • Pod资源清单
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    cat > ~/K8s/Day3/initC.yaml <<'EOF'
    # 名称空间的创建
    apiVersion: v1
    kind: Namespace
    metadata:
    name: test # 创建的名称空间的名称(注意建议和labels保持一致并且为小写)
    labels:
    name: test
    ---
    kind: Pod # 对象的REST资源类型注意首字母必须大小(坑)
    apiVersion: v1
    metadata:
    name: myapp-pod # pod的名称
    namespace: test # 该Pod加入的名称空间
    labels:
    app: myapp #该pod所拥有的标签
    version: v1
    author: weiyigeek
    spec:
    containers: # 要运行容器的列表
    - name: myapp-main-container
    image: busybox
    command: ['sh','-c','echo The app is running!&& sleep 3600']
    initContainers: # 在Main C容器创建前运行的Init C容器,注意安装定义的顺序依次执行;
    - name: init-myservice # 阶段1.以简单的探测服务是否存再来运行主容器以达到容器间依赖问题的解决;
    image: busybox
    command: ['sh','-c','until nslookup myservice;do echo waiting for myservice;sleep 2; done;'] # 返回为0时候进入下一init C容器阶段
    - name: init-mydb # 阶段2
    image: busybox
    command: ['sh','-c','until nslookup mydb;do echo waiting for mydb;sleep 2;done;']
    - name: init-serverA
    images: busybox
    command: ['sh','-c',"curl --connect-timeout 3 --max-time 5 --retry 10 --retry-delay 5 --retry-max-time 60 http://serverB:port/Path/"]
    EOF
  • Service 资源清单
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    cat > ~/K8s/Day3/dependService.yaml <<'EOF'
    kind: Service
    apiVersion: v1
    metadata:
    name: myservice
    namespace: test
    spec:
    selector:
    app: myapp
    ports:
    - protocol: TCP
    port: 80
    targetPort: 9376
    ---
    kind: Service
    apiVersion: v1
    metadata:
    name: mydb
    namespace: test
    spec:
    selector:
    author: weiyigeek
    ports:
    - protocol: TCP
    port: 80
    targetPort: 9377
    EOF

执行部署:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
# 0.部署测试init C的pod资源清单
weiyigeek@ubuntu:~/K8s/Day3$ kubectl create -f initC.yaml
# namespace/test created
# pod/myapp-pod created

# 1.查看 名称空间与Pod 状态(此时可以看见Init0/2还未通过则该Pod的状态为Not Ready, 是由于Init 容器检测未通过)
weiyigeek@ubuntu:~/K8s/Day3$ kubectl get pod -n test
# NAME READY STATUS RESTARTS AGE
# myapp-pod 0/1 Init:0/3 0 33s


# 2.部署测试init C的Service资源清单
kubectl create -f dependService.yaml
# service/myservice created
# service/mydb created

# 3.查其容器运行流程日志以及状态
weiyigeek@ubuntu:~$ kubectl describe pod -n test myapp-pod
# Init Containers:
# init-myservice: # 阶段1
# Container ID: docker://70ab169e9359e448c55267ff818f210a54b0348dc436cd4abd2b4db27fc4932c
# Image: busybox
# Image ID: docker-pullable://busybox@sha256:a9286defaba7b3a519d585ba0e37d0b2cbee74ebfe590960b0b1d6a5e97d1e1d
# Port: <none>
# Host Port: <none>
# Command:
# sh
# -c
# until nslookup myservice;do echo waiting for myservice;sleep 2; done;
# State: Terminated
# Reason: Completed
# Exit Code: 0 # 正常退出
# Started: Sun, 08 Nov 2020 22:49:58 +0800
# Finished: Sun, 08 Nov 2020 22:51:06 +0800
# Ready: True # 容器状态
# Restart Count: 0
# Environment: <none>
# Mounts:
# /var/run/secrets/kubernetes.io/serviceaccount from default-token-bk9vl (ro)
# init-mydb: # 阶段2
# Container ID: docker://306e36d57d75c92f3df10756140f3901dbcc22c1fd89439b8e365a348ca4cb6b
# Image: busybox
# Image ID: docker-pullable://busybox@sha256:a9286defaba7b3a519d585ba0e37d0b2cbee74ebfe590960b0b1d6a5e97d1e1d
# Port: <none>
# Host Port: <none>
# Command:
# sh
# -c
# until nslookup mydb;do echo waiting for mydb;sleep 2;done;
# State: Terminated
# Reason: Completed
# Exit Code: 0
# Started: Sun, 08 Nov 2020 22:51:12 +0800
# Finished: Sun, 08 Nov 2020 22:51:17 +0800
# Ready: True
# Restart Count: 0
# Environment: <none>
# Mounts:
# /var/run/secrets/kubernetes.io/serviceaccount from default-token-bk9vl (ro)
...
Events: # Pod 创建 以及 容器创建的事件过程
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 113s default-scheduler Successfully assigned test/myapp-pod to k8s-node-4

Normal Pulling 113s kubelet Pulling image "busybox"
Normal Pulled 108s kubelet Successfully pulled image "busybox" in 4.499850144s
Normal Created 108s kubelet Created container init-myservice
Normal Started 108s kubelet Started container init-myservice

Normal Pulling 40s kubelet Pulling image "busybox"
Normal Pulled 34s kubelet Successfully pulled image "busybox" in 5.078096509s
Normal Created 34s kubelet Created container init-mydb
Normal Started 34s kubelet Started container init-mydb

Normal Pulling 28s kubelet Pulling image "busybox"
Normal Pulled 25s kubelet Successfully pulled image "busybox" in 3.215811094s
Normal Created 25s kubelet Created container myapp-main-container
Normal Started 25s kubelet Started container myapp-main-container

# 4.在Init C容器启动完毕后,Main C进行启动正常后状态为Runing
weiyigeek@ubuntu:~$ kubectl get pod -n test
# NAME READY STATUS RESTARTS AGE
# myapp-pod 1/1 Running 0 2m8s

Tips : 如Pod重启了所有的Init Container将会重新运行,由于K8s禁止init Container使用 readiness 探针, 则可以使用Pod定义 activeDeadineSeconds (包含 init Container 启动时间) 和 Container 的 livenessProbe 防止 init 容器重复失败


Pod 容器探针

Q: 什么是容器探针(Probe)?

答: 探针是由各个节点(Node)的kubelet对容器执行的定期诊断,而执行诊断时kubelet需调用由容器实现的Handler 处理程序, 注意它不是在Main C之内这是为了便于减少主容器运行压力。


Q: 为什么需要探针?

答: 对于线上业务来说保证服务的正常是稳定是重中之重,而对故障服务的及时处理避免影响业务以及快速恢复一直是开发运维的难点。而采用 K8s 提供的两种探针方式(readiness Probe、Liveness Probe)进行健康检查服务,如检测到故障服务将会被及时下线及通过重启服务的方式使服务自动恢复;


Q: readiness Probe英 /ˈredinəs/(准备就绪) 英 /prəʊb/ 与 liveness Probe/laivnis/(生存) 探测方式?

  • readiness Probe【就绪指针或叫就绪探针-针对于服务】: 指示判断容器是否准备好服务请求。如果就绪探测失败(服务未加载或工作加载异常),端点控制器将从与 Pod 所匹配的Service 的端点中删除该Pod的IP地址。

    • 简单的说当服务没有Ready时会将其从服务的Load Balancer中移除,并不会再接受或者响应任何请求;
    • 如果容器不提供就绪探针, 则默认状态为Success;初始延迟之前的就绪状态默认为Failure。
  • liveness Probe【存活指针或叫存活探针-针对于容器】: 指示判断容器是否正在运行。如果存活探测失败(服务Crash或者死锁等情况发送时),则kubelet会杀死容器,而容器将受到其重启策略(spec.restartPolicy: Always)的影响进行重启容器或者分发到其它机器上进行运行;

    • 如果容器不提供存活探针, 则默认状态为Success;


Q: 探针如何实现服务可用性与自动恢复?

1) 如服务的 Readiness Probe 健康检查失败, 则故障服务实例从Service Endpoint中剔除,外部请求将不会转发到该故障服务实例中;
2) 如对该故障服务实例设置了 Liveness 的探针将会失效, 并且该Container会被Kill掉,然后根据restartPolicy策略将会在当前节点重启容器或者其它可用节点重建容器;

Tips : 如此时服务自我恢复了(网络问题导致),其将会自动重新加入Service Endpoint对外提供服务, 基于上述机制整个服务实现了自身可用与自动恢复;


Probe 探测反馈结果

成功(Success):容器通过了诊断。
失败(Failure):容器未通过诊断。
未知(Unknown):诊断失败因此不会采取任何行动【这将导致容器挂死,因为探针不执行成功的话容器会一直在等待】


Probe 监控检查方式
Probe Handler 处理程序类型:

ExecAction:在容器 Container 内执行指定 Shell 命令。 如果命令退出时返回码为0则认为诊断成功。
TCPSocketAction: 对指定端口上的容器 Container 的IP地址、端口进行TCP检查。如果端口打开则诊断被认为是成功的。
HTTPGetAction: 对指定的端口和路径上的容器 Container 的IP地址、端口、Path路径执行HTTPGet请求。如果响应的状态码大于等于200且小于400 [2xx:成功,3xx:跳转] 则诊断被认为是成功的;


Probe 使用建议

1.建议对全部服务实例同时设置 服务 Readiness 探针容器 Liveiness 探针进行健康检查;
2.在对端口TCPSocketAction检测的同时一般建议使用ExecAction或者HTTPGetAction去进行健康检查;
3.无论采用哪种类型探针建议Readiness 探针的时间短于 Liveiness 探针时间,当然您也可以设置一致其目的都是为了将故障服务下线并等待恢复;


Probe 使用示例

  • (1) 就绪指针 or 就绪探针
    readinessProbe字段: 定期检查集装箱服务准备情况(运行状态)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $ kubectl explain pod.spec.containers.readinessProbe | egrep "FIELDS|>"
    RESOURCE: readinessProbe <Object>
    FIELDS:
    exec <Object>
    httpGet <Object>
    tcpSocket <Object>
    initialDelaySeconds <integer>
    periodSeconds <integer>
    timeoutSeconds <integer>
    failureThreshold <integer>
    successThreshold <integer>

简单实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# (1) httpGet 检测方案 
cat > readinessProbe-httpget.yaml<<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: test
spec:
containers:
- name: readiness-httpget-container
image: harbor.weiyigeek.top/test/nginx:v2.2
imagePullPolicy: IfNotPresent #镜像下载策略如果存在则不下载
readinessProbe: # 就绪探针
httpGet: # 检测方案
port: 80 # 请求端口
path: /index1.html # 请求路径
initialDelaySeconds: 1 # 初始化检测延时(1秒以后)
timeoutSeconds: 1 # 请求等待超时时间 (1秒以后)
periodSeconds: 3 # 重试的检测时间(3秒以后)
EOF


# (2) 部署资源清单
~/K8s/Day4$ kubectl create -f readinessProbe-httpget.yaml
pod/readiness-httpget-pod created


# (3) 查看 & 结果
~/K8s/Day4$ kubectl get pod -n test | grep "readiness-httpget-pod"
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
readiness-httpget-pod 1/1 Running 0 40m 10.244.1.14 k8s-node-4 <none> <none> # 可以看见READY不是就绪的


~/K8s/Day4$ kubectl describe -n test pod readiness-httpget-pod
# Events:
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal Scheduled 7m35s default-scheduler Successfully assigned test/readiness-httpget-pod to k8s-node-4
# Normal Pulling 7m35s kubelet Pulling image "harbor.weiyigeek.top/test/nginx:v2.2"
# Normal Pulled 7m35s kubelet Successfully pulled image "harbor.weiyigeek.top/test/nginx:v2.2" in 179.781569ms
# Normal Created 7m34s kubelet Created container readiness-httpget-container
# Normal Started 7m34s kubelet Started container readiness-httpget-container
Warning Unhealthy 2m32s (x101 over 7m32s) kubelet Readiness probe failed: HTTP probe failed with statuscode: 404 # 关键点


# (4) 进入容器中创建index1.html文件
~/K8s/Day4$ kubectl exec -n test readiness-httpget-pod -it sh
# echo "readiness-httpget-pod" > /usr/share/nginx/html/index1.html
# ls /usr/share/nginx/html/
# 50x.html host.html index.html index1.html

~/K8s/Day4$ curl http://10.244.1.14/index1.html
# readiness-httpget-pod

# (5) 添加文件后容器状态的转变
~/K8s/Day4$ kubectl get pod -n test
# NAME READY STATUS RESTARTS AGE
# readiness-httpget-pod 1/1 Running 0 9m28s # 此时可以看见READY已经就绪

~/K8s/Day4$ kubectl logs -n test readiness-httpget-pod
# 2020/11/09 08:37:06 [error] 9#9: *128 open() "/usr/share/nginx/html/index1.html" failed (2: No such file or directory), client: 10.244.1.1, server: localhost, request: "GET /index1.html HTTP/1.1", host: "10.244.1.14:80"
# 10.244.1.1 - - [09/Nov/2020:08:37:06 +0000] "GET /index1.html HTTP/1.1" 404 153 "-" "kube-probe/1.19" "-"
# 10.244.1.1 - - [09/Nov/2020:08:37:09 +0000] "GET /index1.html HTTP/1.1" 200 22 "-" "kube-probe/1.19" "-"


  • (2) 存活指针 or 存活探针
    HttpGet方式: 即 通过 Http 请求的方式验证应用服务或者Pod容器是否正常运行;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    apiVersion: v1 
    kind: Pod
    metadata:
    name: liveness-httpget-pod
    namespace: default
    spec:
    containers:
    - name: liveness-httpget-container
    image: harbor.weiyigeek.top/test/nginx:v2.2
    imagePu11Policy: IfNotPresent
    ports:
    - name: http
    containerPort: 80
    1ivenessProbe:
    httpGet:
    port: http
    path: /index.html
    httpHeaders:
    - name: X-Custom-Header
    value: WeiyiGeek
    initialDelaySeconds: 1 # 延迟1秒以后开始检测
    timeoutSeconds: 10 # 每次访问时最大的超时时间
    periodSeconds: 3 # 每3秒重复检测一次


Exec方式: 即通过验证容器中服务正常启动后的标志性文件是被存在创建;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1 
kind: Pod
metadata:
name: liveness-exec-pod
namespace: test
spec:
containers:
- name: liveness-exec-container
image: hub.coreqi.cn/1ibrary/busybox
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/live;sleep 60;rm -rf /tmp/live;sleep 3600"]
livenessProbe: #探活指针
exec: #执行命令
command: ["test","-e","/tmp/live"] #检测文件是否存在
initialDelaySeconds: 1 # 容器启动1秒后执行
periodSeconds: 3 # 重试的循环时间为3秒

Tcp方式: 我们以此方式进行演示上面与下面的操作与之类似;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
cat > ~/K8s/Day4/livenessProbe-tcpSocket.yaml <<'EOF'
apiVersion: v1
kind: Pod
metadata:
name: livenessprobe-tcpsocket # 注意只能小写
namespace: test
spec:
containers:
- name: nginx
image: harbor.weiyigeek.top/test/nginx:v2.2
livenessProbe:
tcpSocket:
port: 81 # 检测 81 端口 (实际启动的端口是80所以它会一直重试检测)
initialDelaySeconds: 5 # 初始化后5s执行
timeoutSeconds: 1 # 连接超时
periodSeconds: 3 # 重试间隔3s
EOF

操作步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 1.部署资源清单
~/K8s/Day4$ kubectl create -f livenessProbe-tcpSocket.yaml
# pod/livenessprobe-tcpsocket created

# 2.结果查看(关键点)
~/K8s/Day4$ kubectl get pod -n test -o wide
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# livenessprobe-tcpsocket 1/1 Running 0 起初 23s 10.244.1.15 k8s-node-4 <none> <none>

~/K8s/Day4$ kubectl get pod -n test -o wide -w # 生命周期内探测为满足条件退出 重启
# NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
# livenessprobe-tcpsocket 1/1 Running 2 关键点 91s 10.244.1.15 k8s-node-4 <none> <none>
# ....
# livenessprobe-tcpsocket 1/1 Running 3 2m7s 10.244.1.15 k8s-node-4 <none> <none>
# .... 5次重启后探测还未通过,返回崩溃循环 并且此时状态NotReady
# livenessprobe-tcpsocket 0/1 CrashLoopBackOff 5 4m13s 10.244.1.15 k8s-node-4 <none> <none>
# livenessprobe-tcpsocket 1/1 Running 6 5m42s 10.244.1.15 k8s-node-4 <none> <none>
# livenessprobe-tcpsocket 0/1 CrashLoopBackOff 6 6m26s 10.244.1.15 k8s-node-4 <none> <none>

# 3.Pod 运行事件查看详细信息
~/K8s/Day4$ kubectl describe pod -n test livenessprobe-tcpsocket
# Events: # 通过Eventsxiang详细描述信息可知Contianer liveness不健康,其将被Kill掉重新生成。
# Type Reason Age From Message
# ---- ------ ---- ---- -------
# Normal Scheduled 8m7s default-scheduler Successfully assigned test/livenessprobe-tcpsocket to k8s-node-4
# Normal Killing 6m30s (x3 over 7m54s) kubelet Container nginx failed liveness probe, will be restarted
# Normal Pulled 6m (x4 over 8m6s) kubelet Container image "harbor.weiyigeek.top/test/nginx:v2.2" already present on machine
# Normal Created 6m (x4 over 8m6s) kubelet Created container nginx
# Normal Started 6m (x4 over 8m6s) kubelet Started container nginx
# Liveness probe 检测信息
# Warning Unhealthy 5m54s (x10 over 8m) kubelet Liveness probe failed: dial tcp 10.244.1.15:81: connect: connection refused
# Warning BackOff 3m5s (x6 over 3m54s) kubelet Back-off restarting failed container


Init Container 使用示例: 一个用来检查 redis 是否启动成功,另外一个用来检查 mysql 是否启动成功,开始部署的时候,首先会通过 nslookup 检查 redis 是否成功启动,检测到 redis 启动了之后,域名解析也就会成功,然后会检查 mysql 的状态 mysql 也成功启动之后才会开始启动 reservation-server container。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: reservation-server
image: weiyigeek/activityreservation:dev
ports:
- name: http
containerPort: 80
protocol: TCP
livenessProbe:
httpGet:
path: /health
port: http
initialDelaySeconds: 60
periodSeconds: 10
readinessProbe:
httpGet:
path: /api/notice
port: http
initialDelaySeconds: 60
periodSeconds: 10
initContainers:
- name: init-redis
image: busybox:1.31
command: ['sh', '-c', 'until nslookup redis-server; do echo waiting for redis; sleep 2; done;']
- name: init-mysql
image: busybox:1.31
command: ['sh', '-c', 'until nslookup mysql-server; do echo waiting for mysql; sleep 2; done;']


Pod Hook

描述: Pod hook(钩子)是由Kubernetes管理的kubelet发起的,它在Pod生命周期(Pod Lifecycle)内提供两种钩子 (Hook) 分别在容器中的进程启动前 (postStart)者容器中的进程终止之前 (preStop)运行,可以同时为Pod中的所有容器都配置 hook, 它们包含在容器的生命周期之中。

Hook 事件分类:

  • 1) PostStart: 在容器创建启动之后PostStart Hook将会被立即执行,值得注意的是容器里的Entrypoint 和 Poststart的执行顺序前后并不确定
  • 2) PreStart: 在容器被终止之前执行采用一种阻塞式的方式,即在PreStop Hook执行完毕之后销毁容器的动作才会执行;


Hook Handler 分类:
描述: 容器通过实现和注册一个Handler来实现对Hook的访问有两种类型的 Hook Handlers;

  • exec: 执行 一个 Shell命令
  • http:向 一个endpoint 发送HTTP请求

Tips : 如果 PostStartPreStop hook 执行失败容器将会被Kill掉;


简单实例: 在 Pod 生存周期内在容器创建或者在容器结束时, 指定执行的命令或者脚本;

  • 1.将镜像上传到Harbor仓库之中

    1
    2
    $ docker tag nginx:latest harbor.weiyigeek.top/test/nginx:latest
    $ docker push harbor.weiyigeek.top/test/nginx:latest
  • 2.Pod资源清单的编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    $ kubectl explain Pod.spec.containers.lifecycle

    cat > ~/K8s/Day4/lifecycle-demoStartStop.yaml<<'EOF'
    apiVersion: v1
    kind: Pod
    metadata:
    name: lifecycle-demo
    namespace: test
    labels:
    app: nginx
    spec:
    containers:
    - name: lifecycle-demo-container
    image: harbor.weiyigeek.top/test/nginx
    imagePullPolicy: IfNotPresent
    lifecycle: # [重点生存周期]
    postStart: # 启动时运行
    exec:
    command: ["/bin/sh","-c","echo ${HOSTNAME},${NGINX_VERSION}> /usr/share/nginx/html/info.html"]
    preStop: # 退出时运行(需要正常退出防止数据损坏)
    exec:
    command: ["/usr/sbin/nginx","-s","quit"]
    restartPolicy: OnFailure
    EOF
  • 3.部署资源清单与部署信息查看

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    # (1) 执行部署
    weiyigeek@ubuntu:~/K8s/Day4$ kubectl create -f lifecycle-demoStartStop.yaml
    # pod/lifecycle-demo created

    weiyigeek@ubuntu:~/K8s/Day4$ kubectl get pod -n test -o wide | grep "lifecycle-demo"
    # lifecycle-demo 1/1 Running 0 41s 10.244.1.13 k8s-node-4 <none> <none>

    weiyigeek@ubuntu:~/K8s/Day4$ kubectl logs -n test lifecycle-demo
    # /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
    # /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
    # /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
    # 10-listen-on-ipv6-by-default.sh: Getting the checksum of /etc/nginx/conf.d/default.conf
    # 10-listen-on-ipv6-by-default.sh: Enabled listen on IPv6 in /etc/nginx/conf.d/default.conf
    # /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
    # /docker-entrypoint.sh: Configuration complete; ready for start up

    # (2) 访问查看
    ~/K8s/Day4$ curl http://10.244.1.13/info.html
    # lifecycle-demo,1.19.4

    # (3) 停止主容器然后删除Pod
    kubectl delete -f lifecycle-demoStartStop.yaml
    # pod "lifecycle-demo" deleted


命令补充说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 删除Deployment创建的所有Pod
~$ kubectl get deployment
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-deployment 3/3 3 3 2d14h

~$ kubectl delete deployment --all # 先删除deployment
deployment.apps "nginx-deployment" deleted

~$ kubectl delete pod --all
pod "nginx-deployment-7f5d9779c6-dr5h8" deleted
pod "nginx-deployment-7f5d9779c6-hhl7k" deleted
pod "nginx-deployment-7f5d9779c6-sk2f4" deleted

~$ kubectl delete svc nginx-deployment

~$ kubectl exec nginx-deployment-7f5d9779c6-dr5h8 -it -- /bin/sh env
# KUBERNETES_SERVICE_PORT=443
# KUBERNETES_PORT=tcp://10.96.0.1:443
# HOSTNAME=nginx-deployment-7f5d9779c6-dr5h8
# HOME=/root
# PKG_RELEASE=1~buster
# TERM=xterm
# KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
# NGINX_VERSION=1.19.4
# PATH=/usr/local/nginx/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
# KUBERNETES_PORT_443_TCP_PORT=443
# NJS_VERSION=0.4.4
# KUBERNETES_PORT_443_TCP_PROTO=tcp
# IMAGE_VERSION=2.2
# KUBERNETES_SERVICE_PORT_HTTPS=443
# KUBERNETES_PORT_443_TCP=tcp://10.96.0

# 创建pod的Init容器查看
kubectl get pod
kubecrl describe pod