k8s系列四——资源对象
pod概念
思考:为什么k8s会引出pod这个概念,容器不能解决么?
我的理解:一组密切相关的服务使用容器的话,如果他们的镜像不在一个容器里的话,那么就需要配置反向代理进行通信,没办法进行localhost实现通信
pod类型
- 自主式pod:(不是被控制器管理的Pod):死亡后不会被拉起来,也不会有人创建新的Pod
每个Pod里运行着一个特殊的被称为Pause容器,其他容器为业务容器,这些业务容器共享Pause容器的网络栈和Volume挂载卷,因此他们之间通信和数据交互更为高效。
在设计时我们可以充分利用这一特性将一组密切相关的服务进程放入同一个Pod中,同一个Pod里的容器之间仅需通过localhost就能互相通信,这样也意味着同一个pod内容器的端口不能冲突!!!。
-
控制器管理的Pod:
POD控制器类型:
-
ReplicationController & ReplicaSet & Deployment
ReplicationController :确保期望值,少了就创建新的Pod替代,多了会自动回收。(官方已抛弃!)
新版本的K8S种建议使用ReplicaSet来取代ReplicationController,没有本质不同,但只有ReplicaSet 支持集合式的selector(每个Pod有不同标签,ReplicaSet 操作Pod可以按照标签条件操作)
虽然ReplicaSet 可以独立,但是一般还是建议使用Deployment来自动管理ReplicaSet ,这样就无需担心跟其他机制不兼容问题(如ReplicaSet 不支持rolling-update(滚动更新),但是Deployment支持(Deployment并不支持Pod创建),所以这俩要一起运行。)
滚动更新的含义:
比如有两个pod,里面的镜像是V1版本,现如今我们需要V2版本的,那么k8s就会先启动一个V2版本的pod,然后删除其中一个v1版本,然后再启动一个V2版本pod,在删除最后一个V1版本的
Deployment原理
创建3个pod为V1版本的镜像
滚动更新,更新V1到V2,新建个RS然后创建1个V2,删除1个V1
从而达到滚动更新的状态,此时RS,停用、保留,可以回滚
如果v2版本有bug,还可以回滚,undo即可
旧的RS启用,开始回滚老版V1以此类推。
-
HPA(HorizontalPodAutoScale)
HPA(HorizontalPodAutoScale)根据利用率平滑扩展仅适用于DaemonSet和ReplicaSet ,在V1版本中支持根据Pod的利用率扩容,在vlalpha版本中,支持根据内存和用户自定义的metric扩缩容。
HPA基于RS定义,并且监控V2Pod的资源利用率
当cpu > 80 后,符合条件,会创建Pod
每次创建后判断条件,符合后继续创建,直到最大值。期间如果cpu < 80 了,就会停止创建。使用率小就回收,直到最小值,实现水平自动扩展(弹性伸缩)。
-
StatefulSet
为了解决有状态服务的问题(Deployment和RS是为了解决无状态服务而设计(Docker主要也是)),其场景包括:
- 稳定的持久化存储,即有个Pod死了,重新调度回来以后还是能访问到相同的持久化数据(数据不丢失),基于PVC实现。
- 稳定的网络标识,即重新调度后的PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,按照顺序进行M>A>N(从0到N-1,在下一个Pod运行前,之前所有的Pod必须是running和Ready状态),基于init containers来实现。
- 有序收缩,有序删除(即从N-1到0) Nagix > Apache >Mysql
-
DaemonSet
确保全部(或者一些)Node上 运行一个Pod的副本。当有Node加入集群时,也会为他们新增一个Pod。当有Node从集群移除时,这些Pod也会被回收。删除DaemonSet将会删除它创建的所有Pod。除非打污点,正常情况所有Node都会运行一个且只有一个Pod。
典型用法:
- 运行集群存储 daemon,例如在每个Node上运行glusterd、ceph
- 在每个Node上运行日志收集daemon,例如fluentd、logstash
- 在每个Node上运行监控daemon,例如Prometheus Node Exporter、Zabix Agent 都可以封装在DaemonSet中在每个Node上运行,帮我们收集数据。
可以在一个node里面运行多个daemonset,也可以将多个组装成一个pod运行!
-
Job,Cronjob
job负责批处理任务,即仅执行一次的任务,他保证批处理任务的一个或者多个Pod成功结束。
比如要备份数据库,备份代码可以放到统一Pod里,再放到Job里执行,与Linux直接运行不同点是是封装好的Job可以重复利用,并且脚本执行异常退出可以重复执行,并且可以设置正常退出次数才算Job执行成功
Cronjob管理基于时间的Job,即
- 在给定时间点运行一次
- 周期性地在给定时间点运行
-
pod的状态
Pod
的status
字段是一个PodStatus
的对象,PodStatus
中有一个phase
字段。
无论是手动创建还是通过Deployment
等控制器创建,Pod
对象总是应该处于其生命进程中以下几个相位(phase
)之一。
-
挂起(
Pending
):API Server
创建了pod
资源对象已存入etcd
中,但它尚未被调度完成,或者仍处于从仓库下载镜像的过程中。 -
运行中(
Running
):Pod
已经被调度至某节点,并且所有容器都已经被kubelet
创建完成。 -
成功(
Succeeded
):Pod
中的所有容器都已经成功终止并且不会被重启 -
失败(
Failed
):Pod
中的所有容器都已终止了,并且至少有一个容器是因为失败终止。即容器以非0
状态退出或者被系统禁止。 -
未知(
Unknown
):Api Server
无法正常获取到Pod
对象的状态信息,通常是由于无法与所在工作节点的kubelet
通信所致。
服务发现
Client访问service的IP和端口,使用RR(Round ribbon轮询)等算法间接访问到Pod。
客户端访问一组pod,service是通过标签来进行收集pod,进行统一代理
k8s的pod与pod之间的通讯方案:
网络通讯方式
网络通讯模式:
Kubernetes的网络模型假定了所有Pod都在一个可以直接连通的扁平的网络空间中(所有的pod都可以通过对方IP"直接到达",其实底层有很多转换机制),这在GCE(Google Compute Engine) 里面是现成的网络模型,K8S假定这个网络已存在。而在私有云搭建K8S集群,就不能假定这个网络已经存在了。我们需要自己实现这个网络假设,将不同节点上的Docker容器之间互相访问先打通,然后再运行K8S。
同一个Pod内的多个容器间:lo pause
各Pod之间的通讯: Overlay Network
Pod与Service之间的通讯:各节点的Iptables规则,新版本支持LVS 转发上限、效率更高
网络解决方案K8S+Flannel
Flannel是CoreOS团队针对K8S设计的一个网络规划服务,简单来说他,他的功能是让集群中的不同节点主机创建的Docker容器具有全集群唯一的虚拟IP主机。而且它还能在这些IP之间建立一个覆盖网络(Overlay Network),通过这个覆盖网络,将数据包原封不动地传递到目标容器内
ETCD之Flannel提供说明:
- 存储管理Flannel可分配的IP地址段资源
- 监控ETCD中每个Pod的实际地址,并在内存中建立维护Pod节点路由表
总结:
不同情况下网络通信方式
-
同一个 Pod 内部通讯:同一个 Pod 共享同一个网络命名空间,共享同一个 Linux(pod内的pause) 协议栈;
-
Pod1 至 Pod2(不同pod之间的访问)
- Pod1 与 Pod2 在同一台机器,由 Docker0 网桥直接转发请求至 Pod2,不需要经过 Flannel
- Pod1 与 Pod2 不在同一台主机,Pod的地址是与docker0在同一个网段的,但docker0网段与宿主机网卡是两个完全不同的IP网段,并且不同Node之间的通信只能通过宿主机的物理网卡进行。将Pod的IP和所在Node的IP关联起来,通过 这个关联让Pod可以互相访问
-
Pod 至 Service 的网络
目前基于性能考虑,全部为 iptables 维护和转发(最新版转发模式可以修改为LVS模式)
-
Pod 到外网
Pod 向外网发送请求,查找路由表, 转发数据包到宿主机的网卡,宿主网卡完成路由选择后,iptables执 行Masquerade,把源 IP 更改为宿主网卡的 IP,然后向外网服务器发送请求
-
外网访问 Pod:Service
组件通讯示意图
节点网络:真实的,物理网络
pod网络与service网络均为虚拟的,私有网络
资源清单
等同于剧本,里面已经写好了每一步怎么去做,k8s接收到剧本之后去执行,从而达到预期
资源:K8s 中所有的内容都抽象为资源, 资源实例化之后,叫做对象
K8S 中的资源
依据资源的主要功能作为分类标准,Kubernetes
的API
对象大体可分为五个类别,如下:
级别 | 类型 | 名称 | 备注 |
---|---|---|---|
名称空间 | 工作负载(Workload) | Pod、ReplicaSet、Deployment、StatefulSet、DaemonSet、Job、Cronjob ( ReplicationController 在 v1.11 版本被废弃 ) | |
名称空间 | 负载均衡(Discovery &LB) | Service、Ingress、… | |
名称空间 | 配置和存储(Config&Storage) | Volume( 存储卷 )、CSI( 容器存储接口,可以扩展各种各样的第三方存储卷 ) | |
名称空间 | 特殊类型的存储卷 | ConfigMap( 当配置中心来使用的资源类型,热更新 )、Secret(保存敏感数据)、 DownwardAPI(把外部环境中的信息输出给容器) | |
集群级资源 | 集群(Cluster) | Namespace、Node、Role、ClusterRole、RoleBinding、ClusterRoleBinding | 一旦经过定义以后,在全集群中都能被可见以及调用 |
元数据型资源 | 元数据(metadata) | HPA、PodTemplate、LimitRange | 根据某些指标进行操作 |
资源清单
资源清单含义:
在 k8s 中,一般使用 yaml 格式的文件来创建符合我们预期期望的 pod ,这样的 yaml 文件我们一般 称为资源清单
简单说明
是一个可读性高,用来表达数据序列的格式。YAML 的意思其实是:仍是一种标记语言,但为了强调这种语言以数 据做为中心,而不是以标记语言为重点
基本语法
- 缩进时不允许使用Tab键,只允许使用空格
- 缩进的空格数目不重要,只要相同层级的元素左侧对齐即可
- #标识注释,从这个字符一直到行尾,都会被解释器忽略
YAML 支持的数据结构
- 对象:键值对的集合,又称为映射(mapping)/ 哈希(hashes) / 字典(dictionary)
- 数组:一组按次序排列的值,又称为序列(sequence) / 列表 (list)
- 纯量(scalars):单个的、不可再分的值
对象类型:对象的一组键值对,使用冒号结构表示
name: Steve
age: 18
Yaml 也允许另一种写法,将所有键值对写成一个行内对象
hash: { name: Steve, age: 18 }
数组类型:一组连词线开头的行,构成一个数组
animal:
- cat
- dog
数组也可以采用行内表示法
animal: [Cat, Dog]
复合结构:对象和数组可以结合使用,形成复合结构
languages:
-Ruby
-Perl
websites:
YAML: yaml.org
Ruby: ruby-lang.org
Perl: use.perl.org
纯量:纯量是最基本的、不可再分的值。以下数据类型都属于纯量
1 字符串 布尔值 整数 浮点数 Null
2 时间 日期
数值直接以字面量的形式表示
number: 12.30
布尔值用true和false表示
isSet: true
null用 ~ 表示
parent: ~
时间采用 ISO8601 格式
iso8601: 2001-12-14t21:59:43.10-05:00
日期采用复合 iso8601 格式的年、月、日表示
date: 1976-07-31
YAML 允许使用两个感叹号,强制转换数据类型
e: !!str 123
f: !!str true
字符串
字符串默认不使用引号表示
str: 这是一行字符串
如果字符串之中包含空格或特殊字符,需要放在引号之中
str: '内容: 字符串'
单引号和双引号都可以使用,双引号不会对特殊字符转义
s1: '内容\n字符串'
s2: "内容\n字符串"
单引号之中如果还有单引号,必须连续使用两个单引号转义
str: 'labor''s day'
字符串可以写成多行,从第二行开始,必须有一个单空格缩进。换行符会被转为 空格
str: 这是一段
多行
字符串
多行字符串可以使用|保留换行符,也可以使用>折叠换行
this:|
Foo
Bar
that: >
Foo
Bar
+ 表示保留文字块末尾的换行,- 表示删除字符串末尾的换行
s1: |
Foo
s2: |+
Foo
s3: |-
Foo
常用字段的解释
必须存在的属性:
参数名 | 字段类型 | 说明 |
---|---|---|
version | string | 这里是指的是K8S API的版本,目前基本上是v1,可以用kubectl api-versions命令查询 |
kind | string | 这里指的是yaml文件定义的资源类型和角色,比如:Pod |
metadata | object | 元数据对象,固定值就写metadata |
metadata. name | string | 元数据对象的名字,这里由我们编写,比如命名Pod的名字 |
metadata.namespace | string | 元数据对象的命名空间,由我们自身定义 |
spec | object | 详细定义对象,固定值就写Spec |
spec.containers[] | list | 这里是Spec对象的容器列表定义,是个列表 |
spec.containers[]. name | string | 这里定义容器的名字 |
spec.containers[]. image | string | 这里定义要用到的镜像名称 |
主要对象:
参数名 | 字段类型 | 说明 |
---|---|---|
spec.containers[].name | string | 定义容器的名字 |
spec.containers[J.image | string | 定义要用到的镜像名称 |
spec.containers[]. imagePullPolicy | string | 定义镜像拉取策略,有Always、Never、lfNotPresent三个值可选 (1) Always:意思是每次都尝试重新拉取镜像 (2)Never:表示仅使用本地镜像 (3)IfNotPresent:如果本地有镜像就使用本地镜像,没有就拉取在线镜像。 上面三个值都没设置的话,默认是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[]J.readOnly | String | 设置存储卷路径的读写模式,ture 或者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[j.env[.value | String | 指定环境变量值 |
spec.containers[J.resources | object | 指定资源限制和资源请求的值(这里开始就是设置容器的资源上限) |
spec.containers[].resources.limits | object | 指定设置容器运行时资源的运行上限 |
spec.containers[J. resources.limits.cpu | String | 指定CPU的限制,单位为core数,将用于docker run --cpu-shares参数(这里前面文章Pod资源限制有讲过) |
spec.containers[]J.resources.limits.memory | String | 指定MEM内存的限制,单位为MIB、GiB |
spec.containers[].resources.requests | object | 指定容器启动和调度时的限制设置 |
spec.containers[].resources.requests.cpu | String | CPU请求,单位为core数,容器启动时初始化可用数量 |
spec.containers[].resources.requests.memory | String | 内存请求,单位为MIB、GiB,容器启动的初始化可用数量 |
额外的参数项:
参数名 | 字段类型 | 说明 |
---|---|---|
spec.restartPolicy | String | 定义Pod的重启策略,可选值为Always、OnFailure,默认值为Always. 1.Always: Pod—且终止运行,则无论容器是如何终止的,kubelet服务都将重启它。 2.OnFailure:只有Pod以非零退出码终止时,kubelet才会重启该容器。如果容器正常结束(退出码为0),则kubelet将不会重启它。 3. Never: Pod终止后,kubelet将退出码报告给Master,不会重启该Pod。 |
spec.nodeSelector | object | 定义Node的Label过滤标签,以keyvalue格式指定 |
spec.imagePullSecrets | object | 定义pull镜像时使用secret名称,以name:secretkey格式指定 |
spec.hostNetwork | Boolean | 定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。 |
如果想了解更详细的,可以使用命令:
kubectl explain pod
# 结果显示
[root@k8s-master ~]# kubectl explain pods
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/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/api-conventions.md#types-kinds
metadata <Object>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata
spec <Object>
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/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/api-conventions.md#spec-and-status
# 如果需要了解某一级字段表示的对象之下的二级对象字段时,只需要指定其二级字段的对象名称即可,三级和四级字段对象等的查看方式依次类推。例如查看Pod资源的Spec对象支持嵌套使用的二级字段:
[root@k8s-master ~]# kubectl explain pods.spec
# 显示如下:
RESOURCE: spec <Object>
DESCRIPTION:
Specification of the desired behavior of the pod. More info:
https://git.k8s.io/community/contributors/devel/api-conventions.md#spec-and-status
PodSpec is a description of a pod.
FIELDS:
activeDeadlineSeconds <integer>
Optional duration in seconds the pod may be active on the node relative to
StartTime before the system will actively try to mark it failed and kill
associated containers. Value must be a positive integer.
affinity <Object>
If specified, the pod's scheduling constraints
automountServiceAccountToken <boolean>
AutomountServiceAccountToken indicates whether a service account token
should be automatically mounted.
.....
标签相关命令
[root@k8s-master ~]# kubectl get pods --show-labels #查看pod信息时,并显示对象的标签信息
NAME READY STATUS RESTARTS AGE LABELS
pod-demo 2/2 Running 5 5h13m app=myapp,tier=frontend
[root@k8s-master ~]# kubectl get pods -l app #过滤包含app标签的pod
NAME READY STATUS RESTARTS AGE
pod-demo 2/2 Running 5 5h20m
[root@k8s-master ~]# kubectl get pods -l app,tier #过滤同时包含app,tier标签的pod
NAME READY STATUS RESTARTS AGE
pod-demo 2/2 Running 5 5h20m
[root@k8s-master ~]# kubectl get pods -L app #显示有app键的标签信息
NAME READY STATUS RESTARTS AGE APP
pod-demo 2/2 Running 5 5h21m myapp
[root@k8s-master ~]# kubectl get pods -L app,tier #显示有app和tier键的标签信息
NAME READY STATUS RESTARTS AGE APP TIER
pod-demo 2/2 Running 5 5h21m myapp frontend
排查问题用到得命令:
kubectl describe pod mynginx-pod
第二个 test容器报错了
查看test容器
kubectl log mynginx-pod -c test
kubectl logs mynginx-pod -c test
80端口被占用
容器生命周期
pod创建过程
kubctl ——> api ——> kublet ——> CRI(下图为CRI内部流程)
- 用户通过
kubectl
或其他API
客户端提交了Pod Spec
给API Server
。 API Server
尝试着将Pod
对象的相关信息存入etcd
中,待写入操作执行完成,API Server
即会返回确认信息至客户端。API Server
开始反映etcd
中的状态变化。- 所有的
kubernetes
组件均使用“watch”
机制来跟踪检查API Server
上的相关的变动。 - kube-scheduler
(调度器)通过其
“watcher”觉察到
API Server创建了新的
Pod对象但尚未绑定至任何工作节点。
- kube-scheduler
为
Pod对象挑选一个工作节点并将结果信息更新至
API Server。
调度结果信息由
API Server更新至
etcd存储系统,而且
API Server也开始反映此
Pod对象的调度结果
- Pod
被调度到的目标工作节点上的
kubelet尝试在当前节点上调用
Docker启动容器,并将容器的结果状态返回送至
API Server。
- API Server
将
Pod状态信息存入
etcd系统中。
在
etcd确认写入操作成功完成后,
API Server将确认信息发送至相关的
kubelet`,事件将通过它被接受。
CRI流程
- 会创建一个名为PAUSE的基础容器;
- 创建一个或者多个initC容器,创建完成即刻死亡(此步骤可以没有);
- 创建MainC容器:
- 在启动时候会执行一个START的脚本命令,结束的时候会执行一个STOP的脚本命令;
- 在容器启动过程中,会经过readiness健康就绪检查,如果检查通过,则可对外提供服务,未通过检测的容器意味着其尚未准备就绪,端点控制器(如
Service
对象)会将其IP
从所有匹配到此Pod
对象的Service
对象的端点列表中移除;检测通过之后,会再将其IP
添加至端点列表中; - 在容器运行过程中,一直会有一个liveiness健康存活检查,用于判定容器是否处于“运行”(
Running
)状态,如果不符合存活规则,容器对外则不可用,kubelet
将杀死容器并根据重启策略(restartPolicy
)决定是否将其重启;未定义存活检测的容器的默认状态为“Success
”。
pod生命周期中重要行为
Init 容器
Pod 能够具有多个容器,应用运行在容器里面,但是它也可能有一个或多个先于应用容器启动的 Init 容器
Init 容器与普通的容器非常像,除了如下两点:
- Init 容器总是运行到成功完成为止;
- 每个 Init 容器都必须在下一个 Init 容器启动之前成功完成
如果 Pod 的 Init 容器失败,Kubernetes 会不断地重启该 Pod,直到 Init 容器成功为止。然而,如果 Pod 对应的 restartPolicy 为 Never,它不会重新启动
Init 容器的作用
因为 Init 容器具有与应用程序容器分离的单独镜像,所以它们的启动相关代码具有如下优势:
-
它们可以包含并运行实用工具,但是出于安全考虑,是不建议在应用程序容器镜像中包含这 些实用工具的;
比如提前创建 mainC 所必需的文件,数据 -
它们可以包含使用工具和定制化代码来安装,但是不能出现在应用程序镜像中。例如,创建 镜像没必要 FROM 另一个镜像,只需要在安装过程中使用类似 sed、 awk、 python 或 dig 这样的工具;
-
应用程序镜像可以分离出创建和部署的角色,而没有必要联合它们构建一个单独的镜像。
-
Init 容器使用 Linux Namespace,所以相对应用程序容器来说具有不同的文件系统视图。因 此,它们能够具有访问 Secret 的权限,而应用程序容器则不能。
比如 mainC在启动后,需要运行或者调用 linux某个文件夹下的所有数据 A,但A mainC没有权限访问;
如果 我把A 的权限放给mainC,这就会导致mainC的操作会影响到A的所有数据,而mainC只需要A中的某一个文件;在这种情况下,我们完全可以把A的权限赋予给intC,再由intC写入到mainC -
它们必须在应用程序容器启动之前运行完成,而应用程序容器是并行运行的,所以 Init 容 器能够提供了一种简单的阻塞或延迟应用容器的启动的方法,直到满足了一组先决条件。
一个pod内包含2个容器,apache依赖mysql,因为是并行运行的,有可能apache先于mysql启动,这个时候apache又访问不到mysql(因为mysql没启动起来),所以存活探针探测到就会一直重启;
这个时候我们可以添加一个initC,用来去探测mysql是否正常,如果正常了,就退出initC容器,apache容器在启动就可以了。
init 模板
apiVersion: v1
kind: Pod
metadata:
name: myapp-pod
labels:
app: myapp
spec:
containers:
- name: myapp-container
image: busybox
command: ['sh', '-c', 'echo The app is running! && sleep 3600']
initContainers:
- name: init-myservice
image: busybox
command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;']
- name: init-mydb
image: busybox
command: ['sh', '-c', 'until nslookup mydb; do echo waiting for mydb; sleep 2; done;']
上述yaml的意思为:
启动一个pod,这个pod内部运行一个容器 myapp,在成功启动myapp之前,初始化了两个initC,主要为了探测myservice,mydb是否存在,如果都探测成功,initC容器退出,开始启动myapp主容器;如果不成功,则myapp不会启动
kind: Service
apiVersion: v1
metadata:
name: myservice
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9376
---
kind: Service
apiVersion: v1
metadata:
name: mydb
spec:
ports:
- protocol: TCP
port: 80
targetPort: 9377
特殊说明
- 在Pod 启动过程中,Init容器会按顺序在 网络和数据卷(即pause容器) 初始化之后启动。每个容器必须在下一个容器启动之前成功退出;
- 如果由于运行时或失败退出,将导致容器启动失败,它会根据 Pod 的 restartPolicy指定的策略进行重试。然而,如果 Pod 的restartPolicy设置为 Always,Init 容器失败时会使用RestartPolicy策略;
- 在所有的 Init容器没有成功之前,Pod 将不会变成Ready状态。Init容器的端l将不会在Service 中进行聚集。正在初始化中的Pod 处于Pending 状态,但应该会将Initializing 状态设置为true;
- 如果Pod重启,所有Init容器必须重新执行;
- 对Init 容器spec的修改被限制在容器image字段,修改其他字段都不会生效。更改 Init容器的image字段,等价于重启该Pod。
- Init容器具有应用容器的所有字段。除了readinessProbe,因为 Init容器无法定义不同于完成(completion)的就绪(readiness)之外的其他状态。这会在验证过程中强制执行;
- 在 Pod 中的每个app 和Init容器的名称必须唯一;与任何其它容器共享同一个名称,会在验证时抛出错误;
容器探测
容器探测(container probe
)是Pod
对象生命周期中的一项重要的日常任务,它是kubelet
对容器周期性执行的健康状态诊断,诊断操作由容器的处理器(handler
)进行定义。Kubernetes
支持三种处理器用于Pod
探测:
-
ExecAction
:在容器内执行指定命令,并根据其返回的状态码进行诊断的操作称为Exec
探测,状态码为0
表示成功,否则即为不健康状态。 -
TCPSocketAction
:通过与容器的某TCP
端口尝试建立连接进行诊断,端口能够成功打开即为正常,否则为不健康状态。 -
HTTPGetAction
:通过向容器IP
地址的某指定端口的指定path
发起HTTP GET
请求进行诊断,响应码为2xx
或3xx
时即为成功,否则为失败。
任何一种探测方式都可能存在三种结果:“Success”(成功)
、“Failure”(失败)
、“Unknown”(未知)
,只有success
表示成功通过检测。
容器探测分为两种类型:
- 存活性探测(livenessProbe):用于判定容器是否处于“运行”(
Running
)状态;一旦此类检测未通过,kubelet
将杀死容器并根据重启策略(restartPolicy
)决定是否将其重启;未定义存活检测的容器的默认状态为“Success
”。 - 就绪性探测(readinessProbe):用于判断容器是否准备就绪并可对外提供服务;如果就绪探测失败,端点控制器将从与 Pod 匹配的 所有 Service 的端点中删除该 Pod 的 IP 地址。初始延迟之前的就绪状态默认为 Failure。如果容 器不提供就绪探针,则默认状态为 Success。
检测探针 - 就绪检测(符合条件才READY状态) 测试:
# 前提: myapp文件中只有index.html,没有index1.html
apiVersion: v1
kind: Pod
metadata:
name: readiness-httpget-pod
namespace: default
spec:
containers:
- name: readiness-httpget-container
image: wangyanglinux/myapp:v1
imagePullPolicy: IfNotPresent
readinessProbe:
httpGet:
port: 80
path: /index1.html
# 容器启动后多长时间进行检测
initialDelaySeconds: 1
# 重试时间
periodSeconds: 3
运行后的结果:
[root@k8s -masterol~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
liveness-tcp-pod 1/1 Running 1 7d18h
readiness-http 0/1 Running 0 2m44s
通过上面测试可以看出,readinessProbe
发起的测试就会失败,此时我们再查看pod
的状态会发现并不会将pod
删除重新启动,只是在READY字段
可以看出,当前的Pod
处于未就绪状态。
如果是就绪状态,则显示:
[root@k8s-master ~]# kubectl get pods 查看pod状态
NAME READY STATUS RESTARTS AGE
liveness-tcp-pod 1/1 Running 1 7d18h
readiness-http 1/1 Running 0 7s
存活性探测示例(设置exec探针示例)
apiVersion: v1
kind: Pod
metadata:
name: liveness-exec-pod
namespace: default
labels:
test: liveness-exec
spec:
containers:
- name: liveness-exec-container
image: busybox:latest
imagePullPolicy: IfNotPresent
command: ["/bin/sh","-c","touch /tmp/healthy; sleep 60; rm -f /tmp/healthy; sleep 3600"]
livenessProbe:
exec:
command: ["test","-e","/tmp/healthy"]
initialDelaySeconds: 1
periodSeconds: 3
[root@k8s-master ~]# kubectl create -f manfests/liveness-exec.yaml #创建pod
pod/liveness-exec-pod created
[root@k8s-master ~]# kubectl get pods #查看pod
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 0 6s
#等待一段时间后再次查看其状态
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
liveness-exec-pod 1/1 Running 2 2m46s
上面的资源清单中定义了一个pod
对象,基于busybox
镜像启动一个运行“touch /tmp/healthy; sleep 30; rm -rf /tmp/healthy; sleep 3600"
命令的容器,此命令在容器启动时创建了/tmp/healthy
文件,并于60
秒之后将其删除。存活性探针运行”test -e /tmp/healthy"
命令检查/tmp/healthy
文件的存在性,若文件存在则返回状态码0
,表示成功通过测试。在60秒内使用“kubectl describe pods/liveness-exec-pod”
查看其详细信息,其存活性探测不会出现错误。而超过60
秒之后,再执行该命令查看详细信息,可以发现存活性探测出现了故障,并且还可通过“kubectl get pods"
查看该pod
的重启的相关信息。
存活性探测示例(设置HTTP探针示例)
基于
HTTP
的探测(HTTPGetAction
)向目标容器发起一个HTTP
请求,根据其响应码进行结果判定,响应码如2xx
或3xx
时表示测试通过。通过该命令”# kubectl explain pod.spec.containers.livenessProbe.httpGet
“查看httpGet
定义的字段host :请求的主机地址,默认为Pod IP,也可以在httpHeaders中使用“Host:”来定义。
httpHeaders <[]Object>:自定义的请求报文首部。
port :请求的端口,必选字段。
path :请求的HTTP资源路径,即URL path。
scheme :建立连接使用的协议,仅可为HTTP或HTTPS,默认为HTTP。
[root@k8s-master ~]# vim manfests/liveness-httpget.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-http
namespace: default
labels:
test: liveness
spec:
containers:
- name: liveness-http-demo
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
# 配合start进行使用,除此之外,还可以跟stop配合使用,作用是,在启动容器过程中,会在探测之前优先执行一些内容或者容器启动之后执行一些内容
lifecycle:
postStart:
exec:
command: ["/bin/sh", "-c", "echo Healthz > /usr/share/nginx/html/healthz"]
livenessProbe:
httpGet:
path: /healthz
port: http
scheme: HTTP
[root@k8s-master ~]# kubectl create -f manfests/liveness-httpget.yaml #创建pod
pod/liveness-http created
[root@k8s-master ~]# kubectl get pods #查看pod
NAME READY STATUS RESTARTS AGE
liveness-http 1/1 Running 0 7s
[root@k8s-master ~]# kubectl describe pods/liveness-http #查看liveness-http详细信息
......
Containers:
liveness-http-demo:
......
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 09 Sep 2019 15:43:29 +0800
Ready: True
Restart Count: 0
......
上面清单中定义的httpGet
测试中,通过lifecycle
中的postStart hook
创建了一个专用于httpGet
测试的页面文件healthz
,请求的资源路径为"/healthz"
,地址默认为Pod IP
,端口使用了容器中顶一个端口名称http
,这也是明确了为容器指明要暴露的端口的用途之一。并查看健康状态检测相关的信息,健康状态检测正常时,容器也将正常运行。下面通过“kubectl exec”
命令进入容器删除由postStart hook
创建的测试页面healthz
。再次查看容器状态
poststart:容器创建后立即执行,主要用于资源部署、环境准备等(注意是容器创建后,不是服务启动后!!!!)
prestop:容器终止之前立即被调用。主要用于优雅关闭应用程序、通知其他系统等!!
command&args 与 postart 优先级:没有优先级关系,基本会同时执行
[root@k8s-master ~]# kubectl exec pods/liveness-http -it -- /bin/sh #进入到上面创建的pod中
# rm -rf /usr/share/nginx/html/healthz #删除healthz测试页面
[root@k8s-master ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
liveness-http 1/1 Running 1 10m
[root@k8s-master ~]# kubectl describe pods/liveness-http
......
Containers:
liveness-http-demo:
......
Port: 80/TCP
Host Port: 0/TCP
State: Running
Started: Mon, 09 Sep 2019 15:53:04 +0800
Last State: Terminated
Reason: Completed
Exit Code: 0
Started: Mon, 09 Sep 2019 15:43:29 +0800
Finished: Mon, 09 Sep 2019 15:53:03 +0800
Ready: True
Restart Count: 1
......
通过上面测试可以看出,当发起http
请求失败后,容器将被杀掉后进行了重新构建。
存活性探测示例(设置TCP探针示例)
基于
TCP
的存活性探测(TCPSocketAction
)用于向容器的特定端口发起TCP
请求并建立连接进行结果判定,连接建立成功即为通过检测。相比较来说,它比基于HTTP
的探测要更高效、更节约资源,但精确度略低,毕竟连接建立成功未必意味着页面资源可用。通过该命令”# kubectl explain pod.spec.containers.livenessProbe.tcpSocket“
查看tcpSocket
定义的字段host :请求连接的目标IP地址,默认为Pod IP
port :请求连接的目标端口,必选字段
[root@k8s-master ~]# vim manfests/liveness-tcp.yaml
apiVersion: v1
kind: Pod
metadata:
name: liveness-tcp-pod
namespace: default
labels:
test: liveness-tcp
spec:
containers:
- name: liveness-tcp-demo
image: nginx:1.12
imagePullPolicy: IfNotPresent
ports:
- name: http
containerPort: 80
livenessProbe:
tcpSocket:
port: http
上面清单中定义的tcpSocket
测试中,通过向容器的80
端口发起请求,如果端口正常,则表明正常运行。
什么时候使用存活(liveness)和就绪(readiness)探针?
如果容器中的进程能够在遇到问题或不健康的情况下自行崩溃,则不一定需要存活探针,kubelet
将根据Pod
的restartPolicy
自动执行正确的操作。
如果希望容器在探测失败时被杀死并重新启动,那么请指定一个存活探针,并指定restartPolicy
为Always
或OnFailure
。
如果要仅在探测成功时才开始向Pod
发送流量,请指定就绪探针。在这种情况下,就绪探针可能与存活探针相同,但是spec
中的就绪探针的存在意味着Pod
将在没有接收到任何流量的情况下启动,并且只有在探针探测成功才开始接收流量。
如果希望容器能够自行维护,可以指定一个就绪探针,该探针检查与存活探针不同的端点。
注意:如果只想在Pod
被删除时能够排除请求,则不一定需要使用就绪探针;在删除Pod
时,Pod
会自动将自身置于未完成状态,无论就绪探针是否存在。当等待Pod
中的容器停止时,Pod
仍处于未完成状态。
容器的重启策略
PodSpec
中有一个restartPolicy
字段,可能的值为Always
、OnFailure
和Never
。默认为Always
。restartPolicy
适用于Pod
中的所有容器。而且它仅用于控制在同一节点上重新启动Pod
对象的相关容器。首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet
延迟一段时间后进行,且反复的重启操作的延迟时长依次为10秒、20秒、40秒... 300秒
是最大延迟时长。事实上,一旦绑定到一个节点,Pod
对象将永远不会被重新绑定到另一个节点,它要么被重启,要么终止,直到节点发生故障或被删除。
- Always:但凡
Pod
对象终止就将其重启,默认值 - OnFailure:仅在
Pod
对象出现错误时方才将其重启 - Never:从不重启
Pod phase 可能存在的值
挂起(Pending):Pod 已被 Kubernetes 系统接受,但有一个或者多个容器镜像尚未创建。等待时间 包括调度 Pod 的时间和通过网络下载镜像的时间,这可能需要花点时间
运行中(Running):该 Pod 已经绑定到了一个节点上,Pod 中所有的容器都已被创建。至少有一个容 器正在运行,或者正处于启动或重启状态
成功(Succeeded):Pod 中的所有容器都被成功终止,并且不会再重启
失败(Failed):Pod 中的所有容器都已终止了,并且至少有一个容器是因为失败终止。也就是说,容 器以非 0 状态退出或者被系统终止
未知(Unknown):因为某些原因无法取得 Pod 的状态,通常是因为与 Pod 所在主机通信失败