K8S学习圣经5:Pod负载管理的十八般兵器

Kubernets学习圣经: 底层原理和实操

说在前面:

现在拿到offer超级难,甚至连面试电话,一个都搞不到。

尼恩的技术社群中(50+),很多小伙伴凭借 “左手云原生+右手大数据”的绝活,拿到了offer,并且是非常优质的offer,据说年终奖都足足18个月。

而云原生的核心组件是 K8S,但是 K8S 又很难。Kubernets 简称 k8s,用于自动部署,扩展和管理容器化应用程序的开源系统。也就是能帮我们部署和管理分布式系统。

市面上很多的pdf和视频,都是从技术角度来说的,讲的晦涩难懂。

在这里,尼恩从架构师视角出发,基于自己的尼恩Java 架构师知识体系和知识宇宙,对K8S的核心原理做一个宏观的介绍, 一共十二部分, 组成一本《K8S学习圣经》

《K8S学习圣经》 带大家穿透K8S,实现K8S自由,让大家不迷路。

注:本文以 PDF 持续更新,最新尼恩 架构笔记、面试题 的PDF文件,请从这里获取:码云

《K8S学习圣经》的组成

  • 第一部分:云原生(Cloud Native)的原理与演进
  • 第二部分:穿透K8S的8大宏观架构
  • 第三部分:最小化K8s环境实操
  • 第四部分:Kubernetes 基本概念
  • 第五部分:Kubernetes 工作负载
  • 第六部分:Kubernetes 的资源控制
  • 第七部分: SVC负载均衡底层原理
  • 第八部分: Ingress底层原理和实操
  • 第九部分: 蓝绿发布、金丝雀发布、滚动发布、A/B测试 实操
  • 第十部分: 服务网格Service Mesh 宏观架构模式和实操
  • 第十一部分: 使用K8S+Harber 手动部署 Springboot 应用
  • 第十二部分: CICD核心实操 :jenkins流水线部署springboot应用到k8s集群
  • 第十三部分: k8s springboot 生产实践(高可用部署、基于qps动态扩缩容、prometheus监控)
  • 第十四部分:k8s生产环境容器内部JVM参数配置解析及优化

米饭要一口一口的吃,不能急。

结合《K8S学习圣经》,尼恩从架构师视角出发,左手云原生+右手大数据 +SpringCloud Alibaba 微服务 核心原理做一个宏观的介绍。由于内容确实太多, 所以写多个pdf 电子书:

(1) 《 Docker 学习圣经 》PDF (V1已经完成)

(2) 《 SpringCloud Alibaba 微服务 学习圣经 》PDF (V1已经完成)

(3) 《 K8S 学习圣经 》PDF (coding…)

(4) 《 flink + hbase 学习圣经 》PDF (planning …)

以上学习圣经,并且后续会持续升级,从V1版本一直迭代发布。 就像咱们的《 尼恩 Java 面试宝典 》一样, 已经迭代到V60啦。

40岁老架构师尼恩的掏心窝: 通过一系列的学习圣经,带大家穿透“左手云原生+右手大数据 +SpringCloud Alibaba 微服务“ ,实现技术 自由 ,走向颠覆人生,让大家不迷路。

本PDF 《K8S 学习圣经》完整版PDF的 V1版本,后面会持续迭代和升级。供后面的小伙伴参考,提升大家的 3高 架构、设计、开发水平。

以上学习圣经的 基础知识是 尼恩的 《 高并发三部曲 》,建议在看 学习圣经之前,一定把尼恩的《 Java高并发三部曲 》过一遍,切记,切记。

第四部分:Kubernetes 基本概念

1、基础概念理解

K8S集群 是一组节点,这些节点可以是物理服务器或者虚拟机之上安装了Kubernetes平台。

K8S平台是一个围绕容器打造的分布式系统,和其他的分布式系统比如rocketmq、kafka、elasticsearch在宏观上是非常类似的。

具体介绍,请参见前面的尼恩架构视频。

etcd是Kubernetes提供默认的存储系统,保存所有集群数据,使用时需要为etcd数据提供备份计划。

K8S集群

集群 Node 分为两个核心大组件:

  • master :worker 管理+元数据管理
  • worker(node): 容器的生命周期管理

Node 1:master 管理节点

master 负责worker 管理+元数据管理

master 上的主要组件是 api-server + 一大堆的控制器

Node 2:worker计算节点

worker 上主要的组件就是 kubelet (容器管理)+ kube-proxy(流量负载均衡)

Node的核心组件

node组件运行在每一个节点上,包括master和worker节点,负责运维运行中pod并负责提供k8s运行环境。

核心组件1: kubelet

此组件是运行在每一个集群节点上的代理程序,它确保pod 中的容器处于运行状态。kubelet通过多种途径获取PodSpec定义,并确保PodSpec定义中所描述的容器处于运行和监控的状态。

node节点上创建、删除容器并利用探针进行健康性检查,向apiserver汇报pod状态和资源利用率,只要在结点上有操作动作,都归kubelet管

核心组件2: kube-proxy

kube-proxy 是一个网络代理程序,运行在集群的每一个节点上,是实现kubernets Service概念的重要部分。

kube-proxy在节点上维护网络规则,这些规则可以在集群内外正确的于Pod进行网络通信。

如果操作系统中存在packet filtering layer, kube-porxy将使用这一特性,(iptabes代理模式),否则,kube-proxy将自行转发网络请求(User space代理模式)

在node节点维护iptables转发和ipvs规则,保持容器间的正常网络通讯,
每个节点上都会有一个kubelet和kebu-proxy

核心组件3: 容器引擎

容器引擎负责运行容器,支持多种容器,包括docker,containderd,cri-o,rktlet以及任何实现的k8s容器引擎接口的容器引擎。

Pod

K8s应用最终以Pod为一个基本单位部署

Pod安排在节点上,包含一组容器和卷。

同一个Pod里的可以有一个容器,也可以多个容器。

同一个Pod里的容器共享同一个网络命名空间,可以使用localhost互相通信。Pod是短暂的,不是持续性实体。

Label

很多资源都可以打标签

一些pod有Lable,一个label 是attach到pod的一堆键值对,用来传递用户定义的属性。

比如通过label来标记前端pod容器,使用label标记后端pod容器。

然后使用selectors选择带有特定label的pod。

Deployment

应用部署用它,deployment最终会产生Pod

Service

负载均衡机制

Addons

Addons使用k8s资源( DaemonSet,Deployment等)实现集群级别的功能特性。

Addons 可以理解为 附件, 扩展, 插件。

Addons 由于提供集群级别的功能特性,

Addons 使用到的kubernetes 资源都放在kube-system名称空间下。

以下为常用的Addons

(1) DNS : 集群内布DNS解析

(2)dashborad:用户图形界面

(3)resource-usage-monitoring:容器资源监测

(4) Cluster-level Logging:集群层面日志

除了DNS Addon以外,其他的addon都不是必须的,所有的kubernetes 集群都应该有Cluster DNS

DNS

Cluster DNS 是一个 DNS 服务器,是对您已有环境中其他 DNS 服务器的一个补充,存放了 Kubernetes Service 的 DNS 记录。

Kubernetes 启动容器时,自动将该 DNS 服务器加入到容器的 DNS 搜索列表中。

Web UI (Dashboard)

Dashboard (opens new window)是一个Kubernetes集群的 Web 管理界面。用户可以通过该界面管理集群。

2、k8s对象(kubernetes Objects)

上面讲到, k8s里面操作的资源实体,就是k8s的对象,可以使用yaml来声明对象。

对象的yaml结构

在上述的 .yaml 文件中,如下字段是必须填写的:

  • apiVersion用来创建对象时所使用的Kubernetes API版本
  • kind被创建对象的类型
  • metadata用于唯一确定该对象的元数据:包括name 和namespace,如果namespace 为空,则默认值为default
  • spec描述您对该对象的期望状态

不同类型的 Kubernetes,其 spec 对象的格式不同(含有不同的内嵌字段),通过 API 手册 (opens new window)可以查看 Kubernetes 对象的字段和描述。

说明:

不同类型的 Kubernetes,其 spec 对象的格式不同(含有不同的内嵌字段),通过 API 手册 可以查看 Kubernetes 对象的字段和描述。

例如,

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

Deployment 的 spec 定义可以在 这里 找到

https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.21/

这就是我们以后要完全参照的文档。

关于 yaml文件的分割

多个资源对象的yaml文件,比如Deployment和Service ,可以放在一个文件,可以放在不同的yaml文件中单独执行,

如果放在同一个yaml文件中,需要用连续的三个短横线隔开。

2.1、一个简单的Kubernetes API 对象

Kubernetes 资源对象, 都是使用 API 对象 来配置。

kubernetes 推荐使用 配置文件 运行一个或者多个容器,当然也支持命令行方式。

API 对象 的配置文件可以是:

  • yaml 格式的文件
  • json 格式的文件

yaml 文件比较易读,因此后面都会使用 yaml 风格的文件去描述一个 kubernetes 对象的各种属性。

即:把容器的定义、参数、配置,统统记录在一个 YAML 文件中,然后用这样一句指令把它运行起来:

kubectl apply -f 资源对象的配置文件

例子:
kubectl apply -f demo-Provider.yml

使用配置文件的好处是,可以很好的记录下 kubernetes 都运行了什么。

下面是一个运行 nginx 容器的例子

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.8
        ports:
        - containerPort: 80

保存以上内容到一个 .yml 结尾的文件中, 这里用的是 nginx.yml

像这样的一个 YAML 文件,对应到 Kubernetes 中,就是一个 API Object(API 对象)。

其中的 属性,简单介绍如下:

apiVersion 指定 Kubernetes API 的版本

metadata API 对象的元数据,它也是我们从 Kubernetes 里找到这个对象的主要依据。都是 key-value 的形式,这部分的内容大部分的 API 对象都一样。

kind 指定了这个 API 对象的类型(Type)这里的类型是 Deployment。
Deployment 就是负责在 Pod 定义发生变化时,对每个副本进行滚动更新(Rolling Update)。

spec 就是对具体的 API 对象进行详细描述。这部分每个 API 对象都有自己的特征,所以内容也不一样。

selector 是一个选择器,对需要调度的目标对象进行筛选。
是针对 spec.template.metadata.labels 下定义的键值对进行筛选的。

replicas 指定副本的个数

template 定义 Pod的模板,也就是 Pod 的具体长什么样子

containers 定义关于 Pod 中容器的相关属性

当你为这个对象的各个字段填好值并提交给 Kubernetes 之后,Kubernetes 就会负责创建出这些对象所定义的容器或者其他类型的 API 资源。

要运行它,执行如下命令:

kubectl apply -f nginx.yml

其实上面这样一个 YAML 文件中定义的都是 Kubernetes API 对象。

在 Kubernetes 系统中,Kubernetes 对象 是持久化的实体。Kubernetes 使用这些实体去表示整个集群的状态。

特别地,它们描述了如下信息:

  • 哪些容器化应用在运行(以及在哪些节点上)
  • 可以被应用使用的资源
  • 关于应用运行时表现的策略,比如重启策略、升级策略,以及容错策略

Kubernetes 对象是 “目标性记录” —— 一旦创建对象,Kubernetes 系统将持续工作以确保对象存在。

通过创建对象,本质上是在告知 Kubernetes 系统,所需要的集群工作负载看起来是什么样子的, 这就是 Kubernetes 集群的 期望状态(Desired State)。

什么是k8s 资源对象

官方的介绍

https://kubernetes.io/zh/docs/concepts/overview/working-with-objects/kubernetes-objects/

API 对象 的配置文件可以是:

  • yaml 格式的文件
  • json 格式的文件

有了配置文件之后, 然后让k8s根据yaml的声明创建出这个对象;

kubectl apply/create/run /expose. . .

要点1:对象的创建、修改,或者删除,都需要用到 Kubernetes API

操作 Kubernetes 对象 —— 无论是创建、修改,或者删除 —— 需要使用Kubernetes API

比如,当使用 kubectl 命令行接口时,CLI 会执行必要的 Kubernetes API 调用

要点2:对象Kubernetes系统的持久化实体,通过etcd持久化

Kubernetes对象指的是Kubernetes系统的持久化实体,所有这些对象合起来,代表了你集群的实际情况。

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

具体来说,这些数据(Kubernetes对象)描述了:

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

在想要创建的 Kubernetes 对象对应的 .yaml 文件中,需要配置如下的字段:

  • apiVersion - 创建该对象所使用的 Kubernetes API 的版本
  • kind - 想要创建的对象的类别,比如 Pod, Deployment,Service 等
  • metadata - 帮助唯一性标识对象的一些数据,包括一个 name 字符串、 UID 和可选的 namespace

你也需要提供对象的 spec 字段。

对象 spec 的精确格式对每个 Kubernetes 对象来说是不同的,包含了特定于该对象的嵌套字段。

2.2、kubernetes API的版本

k8s是分布式系统,它本身有各个组件,各个组件之间的通信,对外提供的都是rest接口的http服务。

这些接口就统称为 k8s API。

k8s的api也很有特点,首先它是分组的,它有很多api组。这些api组都有不同的功能,有的api组负责权限,有的api组负责存储。

每个api组还有版本的区分,它其实也有大小版本区分,但是不是我们常用的1.1.1这种版本号,k8s api 大的版本都是以v1, v2 这种为迭代的,

每个大的版本里面区别三个等级

第一种是Alpha等级,这个等级就是还在调试的,基本我们不作为开发者的话,这种等级的接口版本不会接触到。

它会在大版本后面直接跟着alpha,比如v1alpha2, 就代表是v1大版本的alpha等级第2小版本。

第二个等级就是Beta等级,这个等级说明接口基本可以使用了,也经过完整测试了。

会比正常的稳定版本有更多的功能。它的版本格式如v1beta2。

第三个等级就是Stable版本,这个等级说明这个是个稳定版,可以放心使用。

一、查看apiversion 可用版本

查看apiversion 可用版本,在k8s中查看apiversion可用版本的命令如下:

kubectl api-versions

sudo kubectl api-versions

在安装有k8s的机器上执行命令,可用看到类似如下的输出:

二、各种apiVersion的含义

1、alpha

  • 该软件可能包含错误。启用一个功能可能会导致bug。
  • 随时可能会丢弃对该功能的支持,恕不另行通知。

2、beta

  • 软件经过很好的测试。启用功能被认为是安全的。
  • 默认情况下功能是开启的。
  • 细节可能会改变,但功能在后续版本不会被删除。

3、stable

  • 该版本名称命名方式:vX这里X是一个整数。
  • 稳定版本、放心使用。
  • 将出现在后续发布的软件版本中。

4、v1

  • Kubernetes API的稳定版本,包含很多核心对象:pod、service等。

5、apps/v1beta2

  • 在kubernetes1.8版本中,新增加了apps/v1beta2的概念,apps/v1beta1同理。
    DaemonSet,Deployment,ReplicaSet 和StatefulSet的当时版本迁入apps/v1beta2,兼容原有的extensions/v1beta1。

6、apps/v1

  • kubernetes1.9版本中,引入apps/v1,deployment等资源从extensions/v1beta1,
    apps/v1beta1apps/v1beta2迁入apps/v1,原来的v1beta1等被废弃。
    apps/v1代表:包含一些通用的应用层的api组合,如:Deployments, RollingUpdates, and ReplicaSets。

7、batch/v1

  • 代表job相关的api组合。
    在kubernetes1.8版本中,新增了batch/v1beta1,后CronJob 已经迁移到了
    batch/v1beta1,然后再迁入batch/v1。

8、autoscaling/v1

  • 代表自动扩缩容的api组合,kubernetes1.8版本中引入。 这个组合中后续的alpha 和
    beta版本将支持基于memory使用量、其他监控指标进行扩缩容。

9、extensions/v1beta1

  • deployment等资源在1.6版本时放在这个版本中,后迁入到apps/v1beta2,再到apps/v1中统一管理。

10、certificates.k8s.io/v1beta1

  • 安全认证相关的api组合。

11、authentication.k8s.io/v1

  • 资源鉴权相关的api组合。

补充说明:Kubernetes的官方文档中并没有对apiVersion的详细解释,而且因为K8S本身版本也在快速迭代,有些资源在低版本还在beta阶段,到了高版本就变成了stable。

2.3、获取各个属性字段的含义

kubernetes 对象的定义字段,非常多,

如果 需要了解 各个属性字段的含义,有两个方式:

方式1:从命令行获取方式

方式2:官方 API 文档方式

方式1:从命令行获取方式

kubectl explain 查看 创建对象所使用的API版本,

比如: kubectl explain deloyment.apiVersion命令获取deloyment的 api 版本。

kubectl explain pod

kubectl explain deployment

kubectl explain deployment.spec

kubectl explain deployment.spec.template
方式2:官方 API 文档方式

Kubernetes API 参考 能够帮助我们找到任何我们想创建的对象的 spec 格式。

例如:

2.4、管理k8s对象

Kubernetes对象指的是Kubernetes系统的持久化实体,所有这些对象合起来,代表了你集群的实际情况。常规的应用里,我们把应用程序的数据存储在数据库中,Kubernetes将其数据以Kubernetes对象的形式通过 api server存储在 etcd 中。具体来说,这些数据(Kubernetes对象)描述了:

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

一个Kubernetes对象代表着用户的一个意图(a record of intent),一旦您创建了一个Kubernetes对象,Kubernetes将持续工作,以尽量实现此用户的意图。创建一个 Kubernetes对象,就是告诉Kubernetes,您需要的集群中的工作负载是什么(集群的 目标状态)。

管理方式 操作对象 推荐的环境 参与编辑的人数 学习曲线
指令性的命令行 Kubernetes对象;kubectl xxxxx。 cka 开发环境 1+ 最低
指令性的对象配置 单个 yaml 文件 生产环境 1 适中
声明式的对象配置 包含多个 yaml 文件的多个目录。kustomize 生产环境 1+ 最高

同一个Kubernetes对象应该只使用一种方式管理,否则可能会出现不可预期的结果

1、命令式

用户通过向 kubectl 命令提供参数的方式,直接操作集群中的 Kubernetes 对象。

命令式 就是 命令行 的模式,一个一个的敲命令。

kubectl run nginx --image nginx
kubectl create deployment nginx --image nginx
apply -f : 没有就创建,有就修改

此时,用户无需编写或修改 .yaml 文件。

这是在 Kubernetes 集群中执行一次性任务的一个简便的办法。

由于这种方式直接修改 Kubernetes 对象,也就无法提供历史配置查看的功能。

优点

与编写.yaml文件进行配置的方式相比,优点

  • 命令简单,容易记忆
  • 只需要一个步骤,就可以对集群执行变更

缺点

  • 无法进行review管理
  • 不提供日志审计
  • 没有创建新对象的模板
2、指令性

使用指令性的对象配置(imperative object configuration)时,需要向 kubectl 命令指定具体的操作(create,replace,apply,delete等),

可选参数以及至少一个配置文件的名字。

配置文件中必须包括一个完整的对象的定义,可以是 yaml 格式,也可以是 json 格式。

#创建对象
kubectl create -f nginx.yaml
#删除对象
kubectl delete -f nginx.yaml -f redis.yaml
#替换对象
kubectl replace -f nginx.yaml

与指令性命令行相比的优点:

  • 对象配置文件可以存储在源代码管理系统中,例如 git
  • 对象配置文件可以整合进团队的变更管理流程,并进行审计和复核
  • 对象配置文件可以作为一个模板,直接用来创建新的对象

与指令性命令行相比的缺点:

  • 需要理解对象配置文件的基本格式
  • 需要额外编写 yaml 文件

与声明式的对象配置相比的优点:

  • 指令性的对象配置更简单更易于理解
  • 指令性的对象配置更成熟

与声明式的对象配置相比的缺点:

  • 指令性的对象配置基于文件进行工作,而不是目录
  • 如果直接更新 Kubernetes 中对象,最好也同时修改配置文件,否则在下一次替换时,这些更新将丢失
3、声明式

当使用声明式的对象配置时,用户操作本地存储的Kubernetes对象配置文件,然后,在将文件传递给 kubectl 命令时,并不指定具体的操作,由 kubectl 自动检查每一个对象的状态并自行决定是创建、更新、还是删除该对象。

使用这种方法时,可以直接针对一个或多个文件目录进行操作(对不同的对象可能需要执行不同的操作)。

示例

处理 configs 目录中所有配置文件中的Kubernetes对象,根据情况创建对象、或更新Kubernetes中已经存在的对象。可以先执行 diff 指令查看具体的变更,然后执行 apply 指令执行变更

kubectl diff -f configs/
kubectl apply -f configs/

#递归处理目录中的内容:
kubectl diff -R -f configs/
kubectl apply -R -f configs/

#移除
kubectl delete -f configs/

与指令性的对象配置相比,优点有:

  • 直接针对Kubernetes已有对象的修改将被保留,即使这些信息没有合并到配置文件中。
  • 声明式的对象配置可以支持多文件目录的处理,可以自动探测应该对具体每一个对象执行什么操作(创建、更新、删除)

缺点:

  • 声明式的对象配置复杂度更高,Debug更困难
  • 部分更新对象时,带来复杂的合并操作

2.5、对象名称与ID

Kubernetes REST API 中,可以通过 namespace + name 唯一性地确定一个 RESTFUL 对象,

例如:

/api/v1/namespaces/{namespace}/pods/{name}
Names

同一个名称空间下,同一个类型的对象,可以通过 name 唯一性确定。

如果删除该对象之后,可以再重新创建一个同名对象。

依据命名规则,Kubernetes对象的名字应该:

  • 最长不超过 253个字符
  • 必须由小写字母、数字、减号 - 、小数点 . 组成
  • 某些资源类型有更具体的要求

例如,下面的配置文件定义了一个 name 为 nginx-demo 的 Pod,该 Pod 包含一个 name 为 nginx 的容器:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-demo ##pod的名字
spec:
  containers:
  - name: nginx ##容器的名字
    image: nginx:1.7.9
    ports:
    - containerPort: 80 UIDs
UID

Kubernetes 中的 UID 是全局唯一的标识符(UUIDs,符合规范 ISO/IEC 9834-8 以及 ITU-T X.667)

UID 唯一确定一个 Kubernetes 对象,

这个对象不是 RESTFUL 对象 , 是Kubernetes 创建的资源对象

Kubernetes 集群中,每创建一个对象,都有一个唯一的 UID。

UID 用于区分多次创建的同名对象(如前所述,按照名字删除对象后,重新再创建同名对象时,两次创建的对象 name 相同,但是 UID 不同。)

Kubernetes 所有的对象都是通过 UID 唯一性确定

UID 是由 Kubernetes 系统生成的,唯一标识某个 Kubernetes 对象的字符串。

用于区分多次创建的同名 RESTFUL 对象(如前所述,按照名字删除对象后,重新再创建同名对象时,两次创建的对象 name 相同,但是 UID 不同。)

2.6、对象规约(Spec)与状态(Status)

对象规约(Spec)与状态(Status)

几乎每个 Kubernetes 对象包含两个嵌套的对象字段spec、status,它们负责管理对象的配置,两个字段的中文含义:

spec : 对象规约

status : 对象 状态

spec 描述你希望对象所具有的特征: 期望状态(Desired State) 。

status 描述了对象的 当前状态(Current State),它是由 Kubernetes 系统和组件 设置并更新的。

spec 规约

当您在 Kubernetes 中创建一个对象时,您必须提供 对象规约(Spec).,例如如下所述:

  • 名称
  • 使用什么镜像
  • 多少个副本
  • 挂载的文件或目录
  • 使用多少资源等

spec 是静态的,是存在于 YAML 文件中的。

可以使用 kubectl 命令行创建对象,业可以编写 .yaml 格式的文件进行创建,包含对象规约(Spec)

apiVersion: apps/v1
kind: Deployment 
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # 运行 2 个容器化应用程序副本
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
        - containerPort: 80
#1、部署
kubectl apply -f deployment.yaml
#2、移除
kubectl delete -f deployment.yaml

集群中所有的资源,都在那里? k8s只依赖一个存储就是etcd。

status 状态

status 描述了对象在系统中的 当前状态(Current State),它是由 Kubernetes 系统和组件 设置并更新的。

在大多数情况下,用户不需要对此进行更改。

使用如下命令可获取:

kubectl get pods 具体的一个容器名称 -o yaml

在任何时刻,Kubernetes 控制平面 都一直积极地管理着对象的实际状态,以使之与期望状态相匹配。

例如,Kubernetes 中的 Deployment 对象能够表示运行在集群中的应用。

当创建 Deployment 时,可能需要设置 Deployment 的 spec,以指定该应用需要有 3 个副本运行。

Kubernetes 系统读取 Deployment 规约,并启动我们所期望的应用的 3 个实例 —— 更新状态以与规约相匹配。

如果这些实例中有的失败了(一种状态变更),Kubernetes 系统通过执行修正操作 来响应规约和状态间的不一致 —— 在这里意味着它会启动一个新的实例来替换。

scheduler先计算应该去哪个节点部署

对象的spec和status

每一个 Kubernetes 对象都包含了两个重要的字段:

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

Kubernetes通过对应的控制器,不断地使实际状态趋向于您期望的目标状态

关于对象 spec、status 的更多信息,可参阅 [Kubernetes API 约定](community/api-conventions.md at master · kubernetes/community (github.com))。

2.7、名称空间

Kubernetes通过名称空间(namespace)在同一个物理集群上支持多个虚拟集群。

kubectl get namespaces
kubectl describe namespaces <name> 
#隔离 mysql mapper.xml--》dao. 

Kubernetes 安装成功后,默认有初始化了三个名称空间:

  • default 默认名称空间,如果 Kubernetes 对象中不定义 metadata.namespace 字段,该对象将放在此名称空间下
  • kube-system Kubernetes系统创建的对象放在此名称空间下
  • kube-public 此名称空间自动在安装集群是自动创建,并且所有用户都是可以读取的(即使是那些未登录的用户)。主要是为集群预留的,例如,某些情况下,某些Kubernetes对象应该被所有集群用户看到。

除了默认的命名空间,还可以创建名称空间

apiVersion: v1
kind: Namespace
metadata:
  name: my-namespace
kubectl create -f ./my-namespace.yaml

kubectl delete -f ./my-namespace.yaml
#直接用命令
kubectl create namespace <名称空间的名字> 

#删除
kubectl delete namespaces <名称空间的名字> 

名称空间的名字必须与 DNS 兼容:

  • 不能带小数点.
  • 不能带下划线_
  • 使用数字、小写字母和减号 - 组成的字符串

默认情况下,安装Kubernetes集群时,会初始化一个 default 名称空间,用来将承载那些未指定名称空间的 Pod、Service、Deployment等对象

创建了命名空间之后,可以使用命名空间,为请求设置命名空间

#要为当前请求设置命名空间,请使用 --namespace 参数。

kubectl run nginx --image=nginx --namespace=<insert-namespace-name-here> kubectl get pods --namespace=<insert-namespace-name-here> 
#在对象yaml中使用命名空间
apiVersion: v1
kind: Pod
metadata:
name: nginx-demo ##pod的名字
namespace: default #不写就是default
spec:
containers:
  - name: nginx ##容器的名字
  image: nginx:1.7.9
  ports:
    - containerPort: 80

名称空间的用途之一是 对象隔离

为不同团队的用户(或项目)提供虚拟的集群空间,也可以用来区分开发环境/测试环境、准上线环境/生产环境。

名称空间为 名称 提供了作用域。

名称空间内部的同类型对象不能重名,但是跨名称空间可以有同名同类型对象。名称空间不可以嵌套,任何一个Kubernetes对象只能在一个名称空间中。

名称空间的用途之二是 团队隔离

名称空间可以用来在不同的团队(用户)之间划分集群的资源,在 Kubernetes 将来的版本中,同名称空间下的对象将默认使用相同的访问控制策略。

注意:当KUbernetes对象之间的差异不大时,无需使用名称空间来区分,例如,同一个软件的不同版本,只需要使用 labels 来区分即可

名称空间的使用参考:

1)基于环境隔离(prod,test)

prod:部署的所有应用

test:部署的所有应用

2)基于产品线的名称空间(商城,android,ios,backend);

3)基于团队隔离

如何访问其他名称空间的东西?

原则是:名称空间资源隔离,网络不隔离

1)不能访问其他 名称空间下的配置,如果把其他空间的配置直接拿来用,是不可以的。

2)但是,可以访问其他空间的网络地址,网络地址没有屏蔽。

查看名称空间

执行命令 kubectl get namespaces 可以查看名称空间,输出结果如下所示:

NAME          STATUS    AGEdefault       Active    1dkube-system   Active    1dkube-public   Active    1d

Kubernetes 安装成功后,默认有初始化了三个名称空间:

  • default默认名称空间,如果 Kubernetes 对象中不定义metadata.namespace 字段,该对象将放在此名称空间下
  • kube-systemKubernetes系统创建的对象放在此名称空间下
  • kube-public此名称空间自动在安装集群是自动创建,并且所有用户都是可以读取的(即使是那些未登录的用户)。主要是为集群预留的,例如,某些情况下,某些Kubernetes对象应该被所有集群用户看到。
在执行请求的时设定namespace

执行 kubectl 命令时,可以使用 --namespace 参数指定名称空间,例如:

kubectl run nginx --image=nginx --namespace=<您的名称空间>
kubectl get pods --namespace=<您的名称空间>
设置名称偏好

可以通过 set-context 命令改变当前 kubectl 上下文 的名称空间,后续所有命令都默认在此名称空间下执行

kubectl config set-context --current --namespace=<您的名称空间>

# 验证结果
kubectl config view --minify | grep namespace:
名称空间与DNS

当您创建一个 Service 时,Kubernetes 为其创建一个对应的 DNS 条目。

该 DNS 记录的格式为 <service-name>.<namespace-name>.svc.cluster.local,也就是说,如果在容器中只使用 <service-name>,其DNS将解析到同名称空间下的 Service。

这个特点在多环境的情况下非常有用,例如将开发环境、测试环境、生产环境部署在不同的名称空间下,应用程序只需要使用 <service-name> 即可进行服务发现,无需为不同的环境修改配置。

如果您想跨名称空间访问服务,则必须使用完整的域名(fully qualified domain name,FQDN)

名称空间的作用

一个Kubernetes集群应该可以满足多组用户的不同需要。

Kubernetes名称空间可以使不同的项目、团队或客户共享同一个 Kubernetes 集群。

实现的方式是,提供:

名称 的作用域

为不同的名称空间定义不同的授权方式和资源分配策略Resource Quota 和resource limit range

每一个用户组都期望独立于其他用户组进行工作。通过名称空间,每个用户组拥有自己的:

  • Kubernetes 对象(Pod、Service、Deployment等)
  • 授权(谁可以在该名称空间中执行操作)
  • 资源分配(该用户组或名称空间可以使用集群中的多少计算资源)

可能的使用情况有:

  • 集群管理员通过一个Kubernetes集群支持多个用户组
  • 集群管理员将集群中某个名称空间的权限分配给用户组中的受信任的成员
  • 集群管理员可以限定某一个用户组可以消耗的资源数量,以避免其他用户组受到影响
  • 集群用户可以使用自己的Kubernetes对象,而不会与集群中的其他用户组相互干扰
并非所有对象都在命名空间中

大多数 kubernetes 资源(例如 Pod、Service、副本控制器等)都位于某些命名空间中。

但是命名空间资源本身并不在命名空间中。而且底层资源,例如 nodes 和持久化卷不属于任何命名空间。

大部分的 Kubernetes 对象(例如,Pod、Service、Deployment、StatefulSet等)都必须在名称空间里。但是某些更低层级的对象,是不在任何名称空间中的,例如 nodes、persistentVolumes、storageClass 等

执行一下命令可查看哪些 Kubernetes 对象在名称空间里,哪些不在:

# 在名称空间里
kubectl api-resources --namespaced=true

# 不在名称空间里
kubectl api-resources --namespaced=false

2.8 、标签和选择器

标签(Label)是附加在Kubernetes对象上的一组名值对,其意图是按照对用户有意义的方式来标识Kubernetes对象,同时,又不对Kubernetes的核心逻辑产生影响。

标签可以用来组织和选择一组Kubernetes对象。

您可以在创建Kubernetes对象时为其添加标签,也可以在创建以后再为其添加标签。

每个Kubernetes对象可以有多个标签,同一个对象的标签的 Key 必须唯一,例如:

metadata:
labels:
key1: value1
key2: value2

使用标签(Label)可以高效地查询和监听Kubernetes对象,在Kubernetes界面工具(如 Kubenetes Dashboard 或 Kuboard)和 kubectl 中,标签的使用非常普遍。

那些非标识性的信息应该记录在 注解(annotation)

为什么要使用标签

使用标签,用户可以按照自己期望的形式组织 Kubernetes 对象之间的结构,而无需对 Kubernetes 有任何修改。

应用程序的部署或者批处理程序的部署通常都是多维度的(例如,多个高可用分区、多个程序版本、多个微服务分层)。管理这些对象时,很多时候要针对某一个维度的条件做整体操作,例如,将某个版本的程序整体删除,这种情况下,如果用户能够事先规划好标签的使用,再通过标签进行选择,就会非常地便捷。

标签的例子有:

  • release: stable 、 release: canary
  • environment: dev 、 environment: qa 、 environment: production
  • tier: frontend 、 tier: backend 、 tier: cache
  • partition: customerA 、 partition: customerB
  • track: daily 、 track: weekly

上面只是一些使用比较普遍的标签,您可以根据您自己的情况建立合适的使用标签的约定。

句法和字符集

标签是一组名值对(key/value pair)。标签的 key 可以有两个部分:可选的前缀和标签名,通过 / 分隔。

  • 标签名:
    • 标签名部分是必须的
    • 不能多于 63 个字符
    • 必须由字母、数字开始和结尾
    • 可以包含字母、数字、减号-、下划线_、小数点.
  • 标签前缀:
    • 标签前缀部分是可选的
    • 如果指定,必须是一个DNS的子域名,例如:k8s.eip.work
    • 不能多于 253 个字符
    • 使用 / 和标签名分隔

如果省略标签前缀,则标签的 key 将被认为是专属于用户的。Kubernetes的系统组件(例如,kube-

scheduler、kube-controller-manager、kube-apiserver、kubectl 或其他第三方组件)向用户的Kubernetes 对象添加标签时,必须指定一个前缀。 kubernetes.io/k8s.io/ 这两个前缀是 Kubernetes 核心组件预留的。

标签的 value 必须:

  • 不能多于 63 个字符
  • 可以为空字符串
  • 如果不为空,则
    • 必须由字母、数字开始和结尾
    • 可以包含字母、数字、减号 - 、下划线 _ 、小数点 .
apiVersion: v1
kind: Pod
metadata:
  name: label-demo
  labels:
    environment: production
    app: nginx
spec:
  containers:
  - name: nginx
    image: nginx:1.7.9
    ports:
    - containerPort: 80

标签选择器

通常来讲,会有多个Kubernetes对象包含相同的标签。通过使用标签选择器(label selector),用户/客户端可以选择一组对象。标签选择器(label selector)是 Kubernetes 中最主要的分类和筛选手段。

Kubernetes api server支持两种形式的标签选择器, equality-based 基于等式的 和 set-based 基于集合的 。标签选择器可以包含多个条件,并使用逗号分隔,此时只有满足所有条件的 Kubernetes 对象才会被选中

使用基于等式的选择方式,可以使用三种操作符 =、==、!=。

前两个操作符含义是一样的,都代表相等,后一个操作符代表不相等

#
kubectl get pods -l environment=production,tier=frontend
# 选择了标签名为 ènvironment` 且 标签值为 `production` 的Kubernetes对象
environment = production
# 选择了标签名为 `tier` 且标签值不等于 `frontend` 的对象,以及不包含标签 `tier` 的对象
tier != frontend

使用基于集合的选择方式

Set-based 标签选择器可以根据标签名的一组值进行筛选。支持的操作符有三种:in、notin、exists。例如

# 选择所有的包含 ènvironment` 标签且值为 `production` 或 `qà 的对象
environment in (production, qa)
# 选择所有的 `tier` 标签不为 `frontend` 和 `backend`的对象,或不含 `tier` 标签的对象

tier notin (frontend, backend)
# 选择所有包含 `partition` 标签的对象
partition
# 选择所有不包含 `partition` 标签的对象
!partition

# 选择包含 `partition` 标签(不检查标签值)且 ènvironment` 不是 `qà 的对象
partition,environment notin (qa)

kubectl get pods -l 'environment in (production),tier in (frontend)' 
#Job、Deployment、ReplicaSet 和 DaemonSet 同时支持基于等式的选择方式和基于集合的选择方式。
# 例如:

selector:
  matchLabels:
    component: redis
  matchExpressions:
    - {
    
    key: tier, operator: In, values: [cache]}
    - {
    
    key: environment, operator: NotIn, values: [dev]}

# matchLabels 是一个 {key,value} 组成的 map。map 中的一个 {key,value} 条目相当于matchExpressions 中的一个元素,其 key 为 map 的 key,operator 为 In, values 数组则只包含 value 一个元素。matchExpression 等价于基于集合的选择方式,支持的 operator 有 In、NotIn、Exists 和 DoesNotExist。当 operator 为 In 或 NotIn 时,values 数组不能为空。所有的选择条件都以 AND 的形式合并计算,即所有的条件都满足才可以算是匹配
#添加或者修改标签
kubectl label --help

# Update pod 'foo' with the label 'unhealthy' and the value 'true'. 
kubectl label pods foo unhealthy=true

# Update pod 'foo' with the label 'status' and the value 'unhealthy', overwriting any existing value. 

kubectl label --overwrite pods foo status=unhealthy

# Update all pods in the namespace
kubectl label pods --all status=unhealthy

# Update a pod identified by the type and name in "pod.json" 
kubectl label -f pod.json status=unhealthy

# Update pod 'foo' only if the resource is unchanged from version 1. 
kubectl label pods foo status=unhealthy --resource-version=1

# Update pod 'foo' by removing a label named 'bar' if it exists. 
# Does not require the --overwrite flag. 
kubectl label pods foo bar-

2.9、注解annotation

注解(annotation)可以用来向 Kubernetes 对象的 metadata.annotations 字段添加任意的信息。

Kubernetes 的客户端或者自动化工具可以存取这些信息以实现其自定义的逻辑。

metadata:
  annotations:
    key1: value1
    key2: value2

2.8、字段选择器

字段选择器 ( Field selectors )允许您根据一个或多个资源字段的值 筛选 Kubernetes 资源

下面是一些使用字段选择器查询的例子:

  • metadata.name=my-service
  • metadata.namespace!=default
  • status.phase=Pending
kubectl get pods --field-selector status.phase=Running

三个命令玩转所有的yaml写法‘

  • kubectl get xxx -oyaml
  • kubectl create deploy xxxxx --dry-run-client -oyaml
  • kubectl explain pod.spec.xx

写完yaml kubectl apply -f 即可

2.10、认识kubectl 客户端命令

区分一下,这些容易混淆的词儿:Minikube、 kubectl、 kubelet、kubeadm

Minikube是一种可以在本地轻松运行一个单节点Kubernetes群集的工具。

Kubectl是一个命令行工具,可以使用该工具控制Kubernetes集群管理器,如检查群集资源,创建、删除和更新组件,查看应用程序。kubectl是API,供我们调用,键入命令对k8s资源进行管理。

Kubelet是一个代理服务,它在每个节点上运行,并使从服务器与主服务器通信。kubelet 用来调用下层的container管理器,从而对底层容器进行管理。

kubeadm是管理器,我们可以使用它进行k8s节点的管理。

minikube 只能够搭建 Kubernetes 环境,要操作 Kubernetes,还需要另一个专门的客户端工具kubectl。

kubectl 的作用有点类似之前我们学习容器技术时候的工具 docker ,它也是一个命令行工具,作用也比较类似,同样是与 Kubernetes 后台服务通信,把我们的命令转发给 Kubernetes,实现容器和集群的管理功能。

kubectl 是一个与 Kubernetes、minikube 彼此独立的项目,所以不包含在 minikube 里,但 minikube 提供了安装它的简化方式,你只需执行下面的这条命令:

minikube kubectl

它就会把与当前 Kubernetes 版本匹配的 kubectl 下载下来,存放在内部目录(例.minikube/cache/linux/v1.23.1)

其中的kubeadm是管理器,我们可以使用它进行k8s节点的管理。最常用的功能有三个:

(1)init:初始化k8s节点,运行此命令来搭建 Kubernetes 控制平面节点。

(2)join:将worker节点加入到k8s集群

(3)reset:尽最大努力还原init或者join对集群的影响

其中的 kubectl 就是 那一个命令行工具,可以使用该工具控制Kubernetes集群管理器,如检查群集资源,创建、删除和更新组件,查看应用程序。kubectl是API,供我们调用,键入命令对k8s资源进行管理。

所以,在 minikube 环境里,我们会用到两个客户端:

客户端1 : minikube 管理 Kubernetes 集群环境;

客户端2:kubectl 操作实际的 Kubernetes 功能;

kubectl的所有命令参考:

通过 kubectl , 我们就可以使用它来对 Kubernetes “发号施令”了。

命令参考: https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands

2.11、自动补全

k8s集群包括很多的命令,我们可以配置"tab"键的命令补全功能,帮助我们输入k8s相关的命令。

https://kubernetes.io/zh/docs/tasks/tools/included/optional-kubectl-configs-bash-linux/

# 安装
yum install bash-completion

# 自动补全
echo 'source <(kubectl completion bash)' >>~/.bashrc

kubectl completion bash >/etc/bash_completion.d/kubectl

source /usr/share/bash-completion/bash_completion

2.12、给idea安装kubernetes插件

资源对象,都是通过 yaml 文件编写,但是 yaml文件的格式, 很容易 出错,

所以可以通过工具来协助 资源对象 yaml 文件的编辑

idea有kubernetes插件, 可以方便的编写 yaml 文件

1、plugins kubernetes

kubernetes plugin 为我们提供了一些便捷操作: 比如 命令提示、模板生成等。

File > Settings > Plugins > Marketplace > 在 Type / to see options里 搜索 Kubernetes

下面我们简单介绍几个简单的功能演示:

2、快速生成kubernetes 资源文件模板

idea 中并未提供yaml的快捷创建方式,我们设置一下:

File > Settings > Editor > File and Code Templates > Files > Create Template

如图所示,我们创建yml类型的模板文件,而后我们在新建文件时就可以选择yaml类型的文件了。

我们新建一个test.yml 文件,使用 k* 命令去创建kubernetes 资源模板文件

3、输入k即可触发自动命令提示

我们只需要在 yaml 文件中 输入k即可触发自动命令提示:

  • kdep ---- kubernetes deployment
  • kcm ---- kubernetes configMap
  • kpod ---- kubernetes pod
  • kres ---- kubernetes generic resource
  • kser ---- kubernetes service

选择我们要创建的模板类型,生成模板,我们在name处 输入名称,它会自动填充其他属性,而后我们只需要 输入要引用的 image 即可

在image 处也会有提示,包括版本信息

我们把鼠标放到某个属性上(restartPolicy),也会有api提示,

第五部分:Kubernetes 工作负载

什么是Workloads?

问题1: 什么是工作负载(Workloads)?

  • 工作负载是运行在 Kubernetes 上的一个应用程序。
  • 一个应用很复杂,可能由单个组件或者多个组件共同完成。
    无论怎样我们,可以用一组Pod来表示一个 工作负载 (应用程序)

问题2:什么是Pod?

Pod又是一组容器(Containers)

所以关系又像是这样

  • 工作负载(Workloads)控制一组Pod
  • Pod控制一组容器(Containers)

比如Deploy(部署) 一个3个副本的nginx Pod(3个Pod)应用,每个nginx Pod里面是真正的nginx容器(container)

所以,在Kubernetes中,Pod是最小的管理单元,包含一个或者一组紧密关联的容器。

但是,单独的Pod并不能保障总是可用,比如我们创建一个nginx的Pod,因为某些原因,该Pod被意外删除,我们希望其能够自动新建一个同属性的Pod。

如何实现pod的高可用?很遗憾,单纯的Pod并不能满足需求。

为此,Kubernetes实现了一系列控制器来管理Pod,使Pod的期望状态和实际状态保持一致。

目前常用的控制器有:

  • Deployment
  • StatefulSet
  • DaemonSet
  • Job/CronJob

Pod的原理与生命周期

1、什么是Pod

再一次深入到一个基础问题: 什么是Pod?

在部署第一个应用程序中创建Deployment后,k8s创建了一个pod(容器组)来放置应用程序实例(container容器)。

pod容器组是一个k8s中一个抽象的概念,用于存放一组 container(可包含一个或多个 container 容器,即图上正方体),以及这些container(容器)的一些共享资源。这些资源包括:

  • 共享存储,称为卷(Volumes),即图上紫色圆柱
  • 网络,每个pod(容器组)在集群中有个唯一的ip,pod(容器组)中的container(容器)共享该ip地址
  • container(容器)的基本信息,例如容器的镜像版本,对外暴露的端口等

例如,pod可能即包含带有Java应用程序的container容器,也可以包含另一个非Java的container容器。

pod中的容器共享ip地址和端口空间(同一pod中的不同container端口不能相互冲突),始终同一node位置并共同调度,并在同一node节点上的共享上下文中运行。

同一个pod内的容器可以使用localhost+端口互相访问。

pod(容器组)是k8s集群上的最基本的单元。当我们在k8s 上创建Deployment 时,会在集群上创建包含容器的pod(而不是直接创建容器)。

每个pod都与运行它的worker节点(node)绑定,并保持在那里知道终止或呗删除。

如果节点(node)发生故障,则会在集群中的其他可用节点(node)上运行相同的pod(从同样的镜像创建container,使用相同的配置,ip地址不同,pod名字不同)。

  • pod 是一组容器(可包含一个或多个应用程序容器),以及共享存储(卷volumes)、ip 地址和有关如何运行容器的信息。
  • 如果多个容器紧密耦合并且需要共享磁盘等资源,则他们应该被部署在同一个pod(容器组)中。

下图显示一个node(节点)上含有4个pod(容器组)

pod(容器组)总是在node(节点)上运行。

node(节点)是kubernetes集群中的计算机节点。可以是物理机或虚拟机。每个node(节点)都由master管理。

一个node(节点)上可以部署多个pod(容器组),kubernetes master会根据每一个node(节点)上的可用资源情况,自动调度pod(容器组)到最佳的node(节点)上。

一个kubernetes node(节点)至少运行:

  • kubelet, 负责master节点和worker节点之间通信的进程;管理pod(容器组)和pod(容器组)内运行的Container(容器)。
  • 容器运行环境(如docker)负责下载镜像、创建和运行容器等。

一个Pod 是一组(一个或多个) 容器(docker容器)的集合(就像在豌豆荚中);

这些容器共享存储、网络、以及怎样运行这些容器的声明。

我们一般不直接创建Pod,而是创建一些工作负载由他们来创建Pod

Pod的形式

  • Pod对容器有自恢复能力(Pod自动重启失败的容器)
  • Pod自己不能恢复自己,Pod被删除就真的没了(100,MySQL、Redis、Order)还是希望k8s
  • 集群能自己在其他地方再启动这个Pod
  • 单容器Pod
  • 多容器协同Pod。我们可以把另外的容器称为 SideCar(为应用赋能)
  • Pod 天生地为其成员容器提供了两种共享资源: 网络存储

一个Pod由一个Pause容器设置好整个Pod里面所有容器的网络、名称空间等信息

systemctl status可以观测到。Pod和容器进程关系

  • kubelet启动一个Pod,准备两个容器,一个是Pod声明的应用容器(nginx),另外一个是Pause。Pause给当前应用容器设置好网络空间各种的。

2、Pod使用

可以编写deploy等各种工作负载的yaml文件,最终创建出pod,也可以直接创建

Pod的模板如下

# 这里是 Pod 模版
apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
    - name: hello
      image: busybox
      command: ['sh', '-c', 'echo "Hello, Kubernetes!" && sleep 3600']
  restartPolicy: OnFailure

使用如下的命令创建pod 对象

kubectl apply -f ./hello1.yml

删除 创建pod 对象

kubectl delete -f ./hello1.yml

查看 命令行,打印的日志

 kubectl logs -f  my-pod

3、Pod生命周期

这个是重点来的哦,因为在实际运用的过程中,对于Pod生命周期每个阶段的进行检测,进而分析和排错。

再来一个流程图

Pod生命周期 主要包括:

1)pod创建过程
2)运行初始化容器(init container)过程
3)运行主容器(main container)
容器启动后钩子(post start)、容器终止前钩子(pre stop)
容器的存活性探测(liveness probe)、就绪性探测(readiness probe)
4)pod终止过程

“根容器” : Pause 容器

每个pod中都存在一个pause容器。在 Pod 启动时,pause容器总是创建的第一个容器。这个是k8s自动创建的,是pod 的根容器。

当您在Kubernetes集群的节点上执行,docker ps时,您会发现一些名为pause的容器正在运行:

$ docker ps
CONTAINER ID        IMAGE                           COMMAND ...
...
3b45e983c859        gcr.io/google_containers/pause-amd64:3.0    "/pause" ...
...
dbfc35b00062        gcr.io/google_containers/pause-amd64:3.0    "/pause" ...
...
c4e998ec4d5d        gcr.io/google_containers/pause-amd64:3.0    "/pause" ...
...
508102acf1e7        gcr.io/google_containers/pause-amd64:3.0    "/pause" ...

您可能很好奇:这些pause容器是什么? 为什么会有这么多这样的容器?

每个Pod都有一个特殊的被称为“根容器(父容器)”的Pause 容器。

Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或者多个紧密相关的用户业务容器。

Pause容器 存在的原因:

原因一:在一组容器作为一个单元的情况下,难以对整体的容器简单地进行判断及有效地进行行动。

比如,一个容器死亡了,此时是算整体挂了么?那么引入与业务无关的Pause容器作为Pod的根容器,以它的状态代表着整个容器组的状态,这样就可以解决该问题。

原因二:Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂载的Volume,这样简化了业务容器之间的通信问题,也解决了容器之间的文件共享问题。

在Docker容器世界里边,容器之间原本是被 Linux Namespace 和 cgroups 隔开的。但是,同一个的Pod内部的多个容器,需要打破这种隔离。

那么,Pod是怎么去打破这个隔离,然后共享某些事情和某些信息。这就是 Pod 的设计要解决的核心问题所在。

回到Docker,打破容器隔离,可以通过——通过创建一个父容器实现,

例如:

(1)创建容器A,作为父容器,容器ID假设为AID
(2)创建容器B,作为子容器,容器ID假设为BID,启动时指定PID命名空间标识为–pid=container:AID
(3)创建容器C,作为子容器,容器ID假设为CID,启动时指定PID命名空间标识为–pid=container:AID

此时,容器B与容器C共享相同的PID命名空间(即容器A的PID命名空间) ,这样,就实现了 PID 命名空间的共享。

关于 Linux Namespace 和 cgroups 的知识,请参考 尼恩的 《 Docker 学习圣经 》 PDF。

总之 ,在Kubernetes中,pause容器作为您pod中所有容器的父容器,根容器, 也叫 Infra container 。

pause容器( Infra container)有多个核心职责:

(1)它作为在pod中共享Linux名称空间的基础容器。

(2)启用PID(进程ID)名称空间共享后,它将作为每个pod的PID 1进程(根进程),并回收僵尸进程。

(3)共享Pause容器挂载的Volume,这样简化了业务容器之间的通信问题,也解决了容器之间的文件共享问题

(4)共享Pause容器所分配的网络资源或者 Network Namespace,共享给整个 Pod 的其他容器,其他所有容器都会通过 Join Namespace 的方式加入到 Infra container 的 Network Namespace 中。

pause容器( Infra container)是一个非常小的镜像,大概 700KB 左右,是一个 C 语言写的、永远处于 “暂停” 状态的容器。

由于有了这样一个 Infra container 之后,其他所有容器可以共享存储和网络,其他容器都会通过 Join Namespace 的方式加入到 Infra container 的 核心的Namespace 中。

Pod中容器也有分类

除了Pod中的pause容器( Infra container)之外, 在Pod中,容器也有分类:

  • 标准容器 Application Container
  • 初始化容器 Init Container
  • 边车容器 Sidecar Container
  • 临时容器 Ephemeral Container

一般来说,我们部署的大多是标准容器( Application Container)

Pod生命周期的 两个阶段

上图主要描述了在容器环境初始化完成之后,pod从创建到退出,中间这段时间经历的过程;

从大的方向上看,pod生命周期分两个阶段:

  • 第一阶段是 运行初始化容器 阶段: Init Container。比如有3个初始化容器1、2、3,那么会按顺序把1、2、3的InitC都执行初始化。
  • 第二阶段是 运行主容器 阶段 : Main Container;

对于Init Container来说,一个pod中可以定义多个初始化容器,他们必须是串行执行,只有当所有的初始化容器执行完后,对应的主容器才会启动;

如上图所示,会按顺序把1、2、3的InitC都执行完成之后,才进入Main Container阶段

Main Container (运行主容器 阶段)生命周期又分为三个子阶段:

  • 第一子阶段是运行post start hook,这个阶段是主容器运行之后立即需要做的事;
  • 第二子阶段是主容器正常运行的阶段,在这个阶段中,我们可以定义对容器的健康状态(liveness)检查和就绪状态(readness)检查;
  • 第三子阶段是运行pre stop hook,这个阶段主要做容器即将退出前需要做的事;

其中,第二子阶段是主容器正常运行的阶段 ,以定义对容器的健康状态(liveness)检查和就绪状态(readness)检查;

readness——就绪检测,检测成功后,Pod状态改为Ready

liveness——生存检测,

在对于对容器的健康状态检查和就绪状态检查的时候,我们也可以定义开始检查的延迟时长;为啥要开始检查的延迟时长呢 ?因为有些容器存在容器显示running状态,比如Java 服务,有的时候启动要30s以上,内部程序还没有初始化,如果立即做健康状态检查,可能存在健康状态为不健康,从而导致容器重启的状况;

总之,Pod启动过程中的要点是:

  • Pod启动,会先依次执行所有初始化容器,有一个失败,则Pod不能启动
  • 接下来启动所有的应用容器(每一个应用容器都必须能一直运行起来),Pod开始正式工作,一个启动失败就会尝试重启Pod内的这个容器,Pod只要是NotReady,Pod就不对外提供服务了

Pod的Init 初始化容器 (附Demo)

Pod的Init 初始化容器,就是init容器成功启动,应用Pod才能开始启动 如比:

  • 数据库启动完成后,才能开始启动应用
  • 在init容器中,根据需要生成配置文件,然后在主应用pod中启动应用

Pod的Init 初始化容器 Demo案例

编写yaml测试生命周期

  • 应用容器生命周期钩子
  • 初始化容器

yaml文件 如下:

运行yaml文件后,观察Pod,此时正处于Init状态

因为在Demo Pod的spec字段中,定义了initContainers部分,这就是 初始化容器

在Pod启动中,会首先执行command中的命令,之后执行成功返回后,Pod才会正常启动。

查看my-pod的log日志

kubectl logs -f  my-pod --container=init-myservice

Pod 钩子Hook方法 (附Demo)

Pod钩子作用于:

  • Pod 创建后立刻执行(PostStart)
  • Pod 容器终止之前立刻执行(PreStop)

PostStart 钩子:

容器创建后立即执行,注意由于是异步执行,它无法保证一定在 ENTRYPOINT 之前运行。

如果失败,容器会被杀死,并根据 RestartPolicy 决定是 否重启

PreStop 钩子:

在容器终止之前调用,是阻塞的,需要执行完该步骤后,才能真正执行终止容器的步骤。

容器终止前执行,常用于资源清理。

执行完成之后容器将成功终止,如果失败,容器同样也会被杀死。

在其完成之前 会阻塞删除容器的操作

钩子的回调函数支持三种方式定义动作:

  • exec:执行一段命令
  • HTTP:发送HTTP请求
  • TCPSocket:在容器尝试访问指定的socket

exec:在容器内执行命令,如果命令的退出状态码是 0 表示执行成功,否则表示失败

……
  lifecycle:
    postStart:
      exec:
        command:
        - cat
        - /tmp/healthy
……

httpGet:向指定 URL 发起 GET 请求,如果返回的 HTTP 状态码在 [200, 400) 之间表示请求成功,否则表示失败

……
  lifecycle:
    postStart:
      httpGet:
        path: /login   # URI地址
        port: 80  # 端口号
        host: 192.168.126.100 # 主机地址
        scheme: HTTP   # 支持的协议,http或https

# http://192.168.126.100:80/login
……

TCPSocket:在容器尝试访问指定的socket

……
  lifecycle:
    postStart:
      tcpSocket:
        port: 8080
……

Pod 钩子Hook方法 Demo


执行pod的创建和删除

查看 宿主机 上 的文件里边内容

Pod 健康检查(探针)机制

Pod 的基础探针分为liveness 探针和readiness探针 , 1.16版本新增了startupProbe探针

  • liveness 为容器存活探针
  • readiness 为容器就绪探针(可读性)
  • startupProbe(这个1.16版本增加的)`

根据官网api手册,在探针上,可以使用三种探测方法

  • exec :执行命令的检测方式
  • httpGet :根据http请求返回的检测方式
  • tcpSocket :检查容器TCP状态,如果能连接则正常(eg:连接数据库3389)

Pod 健康检查(探针)机制 内容太多,稍后另开 一个小节,做专门介绍。

这里来看一个 简单的 Pod 健康检查(探针)机制 Demo

上面的yaml做了两件事情

  • 定义一个pod,这个pod会在启动时,创建/tmp/healthy的文件,然后等待30秒后,删除这个文件
  • 每隔5秒,就会执行cat /tmp/healthy命令,如果检测到执行命令失败,则这个Pod将会重启

因为一开始创建容器的时候,是有/tmp/healthy文件的,

但是, 在30秒后,这个文件将会被删除,

随后检测失败,这个容器再次被重启,如此循环

通过 pod,可以看到重启的次数

Pod 的状态和重启策略

在介绍 Pod 的重启策略前, 首先得了解Pod 的状态

Pod 常见的状态

Pending:

挂起,我们在请求创建pod时,条件不满足,调度没有完成,没有任何一个节点能满足调度条件。已经创建了但是没有适合它运行的节点叫做挂起,这其中也包含集群为容器创建网络,或者下载镜像的过程。

Running:

Pod内所有的容器都已经被创建,且至少一个容器正在处于运行状态、正在启动状态或者重启状态。

Succeeded

Pod中所以容器都执行成功后退出,并且没有处于重启的容器。

Failed:

Pod中所以容器都已退出,但是至少还有一个容器退出时为失败状态。

Unknown

未知状态,所谓pod是什么状态是apiserver和运行在pod节点的kubelet进行通信获取状态信息的,如果节点之上的kubelet本身出故障,那么apiserver就连不上kubelet,得不到信息了,就会看Unknown

Pod重启策略

根据restartPolicy来定义重启策略

1)Always:但凡 Pod 对象终止就将其重启,此为默认设定。

2)OnFailure:仅在 Pod 对象出现错误时方才将其重启。

3)Never: 从不重启。

Pod 的重启,只会在当前同一 node 节点上尝试重启 Pod。

只有第一次重启是立刻执行,后面需要重启时,kubelet 会延迟时间进行,且反复 的重启操作的延迟时长依次为 10秒、20秒、40秒、80秒、160秒和300秒,300秒是最大延迟时长。

注意: 如果pod的restartpolicy没有设置,那么默认值是Always。
Pod常见状态转换场景

不同的重启策略下,Pod常见状态转换 过程

临时容器:线上排错。

有些容器基础镜像。线上没法排错。

使用临时容器进入这个Pod。临时容器共享了Pod的所有的命名空间。

临时容器有Debug的一些命令,排错完成以后,只要exit退出容器,临时容器自动删除

Java:dump, jre 50mb。jdk 150mb

jre 50mb:jdk作为临时容器

临时容器需要开启特性门控 --feature-gates="EphemeralContainers=true" ,

生产环境所有组件,api-server、kubelet、scheduler、controller-manager都得配置

minikube 环境下, 需要在启动命令下,加上 门控

minikube start  --kubernetes-version=v1.23.1     --force --driver=docker --cpus 4  --memory 5120 --feature-gates=EphemeralContainers=true

使用方法:

首先部署一个pod,内部标准容器运行业务进程镜像 :

启动之后,查看一下 my-app 业务 pod,如下

通过临时容器查看Java容器log日志

以下命令将在 pod demo-provider-deployment-54445d6849-ccc5k 中创建一个新的临时容器。临时容器的镜像是busybox:latest。

kubectl debug -it pods/demo-provider-deployment-54445d6849-ccc5k --image=busybox:latest 

创建之后,就可以进入到 临时容易的 命令交互 窗口,进行 debug 操作

比如通过 ifconfig 查看 ip 等等。

主pod的视角来看,可使用 kubectl describe pod 命令可以看到一个主容器有一个新字段 “Ephemeral Containers”,此部分包含临时容器及其属性。

kubectl describe pod demo-provider-deployment-54445d6849-ccc5k

但是,此时看不到进程的命名空间, 看不到目标容器的进程id

默认情况下,目标容器的进程命名空间共享不能应用于临时容器,

如果要看进程id,因此必须创建目标容器的副本。

这种场景下,需要进行 --share-processesflag 在与 --copy-to 一起使用时,从而实现进程命名空间共享。

这些标志的底层原理:将现有的 Pod spec定义复制到新定义中,并在spec中启用了进程命名空间共享。

kubectl debug -it pods/demo-provider-deployment-54445d6849-ccc5k --image=busybox:latest --share-processes --copy-to=debug-pod

运行 ps 命令以查看正在运行的进程。可以从 busybox 容器中看到 /pause,从java 容器中看到 java进程。

使用进程命名空间,也是可访问的共享容器文件系统,这对于调试非常有用。

比如,可以使用 /proc/pid/root 路径去访问容器的文件系统。

从上面的输出中,我们知道java 的PID为8。

从 /proc/8/root 可以访问 Java的日志, 可以说,非常方便

使用完成之后,通过kubectl delete pod debug-pod 删除临时pod

临时容器的参数说明:

--copy-to   指定新pod的名称
--replace=true   是否删除原容器
--same-node=true  是否调度到和原容器一样的node上
--share-processes=true  是否共享容器pid空间

个别场景下,可以启动一个跟需要调试pod一样配置的debug容器如下:

kubectl debug mypod -it \
--container=debug \
--image=busybox \
--copy-to=my-debugger \
--same-node=true \
--share-processes=true 

当以节点为目标调用时,kubectl debug 将创建一个带有node名称的pod,并且调度到该节点。

同时该容器还具备了hostIPC、hostNetwork和hostPID这些特权模式。

不可思议的是Worker节点的根文件系统还被mount到了debug容器下的/host目录下。

使用临时容器进行 dump 转储

生产环境的容器云上出了个性能问题,做性能分析 ,如果使用 JDK 自带的 jmap 收集堆dump,很容易出现了内存溢出导致了容器崩溃。

为啥呢?

jmap 本身是启动了另一个 JVM 来收集目标应用的 JVM堆的信息的。

而低于 Java 8 update 191 版本的 JDK 会直接读到物理节点的内存,然后根据物理节点去申请内存,直接导致了容器崩溃。

如果 OpenJDK 8 低于 191 的版本,很容易导致容器崩溃, 被kill

或者由于其他原因,导致容器的内存占用超过了容器的限制,被k8s或 docker kill 掉了。

如何规避容器内做堆 dump 导致容器崩溃的问题。

可以用临时容器进行 dump 转储

基于制作Java Debug 镜像

.注意,openjdk:8-jre-alpine 没有jmap + jstack + jcmd + jinfo功能

解决方案:

通过jattach动态附加机制向JVM进程发送命令的实用程序。

jattach是一个把jmap + jstack + jcmd + jinfo功能在一个单一的小程序。

使用jattach不需要安装JDK,只使用JRE。

jattach支持Linux容器。

安装jattach,详情访问:GitHub - jattach/jattach: JVM Dynamic Attach utility

1.下载jattach
https://github.com/apangin/jattach/releases
https://github.com/jattach/jattach/releases/tag/v1.0

2.add jattach到容器内部

build镜像然后发布后,使用临时容器进行 dump 转储

kubectl debug -it pods/demo-provider-deployment-54445d6849-8bp49 --image=nien/jattach:v1.0.1 --share-processes --copy-to=debug-pod

进入容器内执行

jattach 18 dumpheap  /proc/18/root/work/dumpheap.hprof
jattach 18 threaddump > /proc/18/root/work/threaddump.hprof

这样就把内存快照和当时的CPU信息导出了,导出到

/proc/18/root/work/threaddump.hprof

这个目录的对应关系如下

/work => /data/k8s/demo-provider/work

生产场景,可以让运维把/data/k8s/demo-provider/work 这个目录挂载到一个外部磁盘上,就可以拿到导出的信息了

以上的文件关系复杂, 如果不懂,请参见后续的尼恩云原生视频

导出之后的文件,如下图:

临时容器不能用jmap、jps而只能用jattach

jattach 的转储功能,类似于jmap :

jmap -dump:live,format=b,file=/proc/19/root/work/dumpheap19.hprof 19

但是jmap、jps 等命令,在临时容器里边用不了

大家可以用尼恩的容器试试效果就知道了,这里只能用jattach

40岁老架构师尼恩爬坑记:

这里也是一个坑,开始尼恩也以为可以用jmap、jps ,结果 弄了半天的 镜像,最终用不了。

jattach指令集:
  • load : load agent library(导入agent库)
  • properties : print system properties(打印系统属性)
  • agentProperties : print agent properties(打印agent属性)
  • datadump : show heap and thread summary(显示堆和线程概要信息)
  • threaddump : dump all stack traces (like jstack)(导出栈信息)
  • dumpheap : dump heap (like jmap)(导出堆信息)
  • inspectheap : heap histogram (like jmap -histo)(显示堆信息直方图)
  • setflag : modify manageable VM flag(修改可管理的VM参数)
  • printflag : print VM flag(打印VM参数)
  • jcmd : execute jcmd command(执行jcmd命令)

例如如使用:jcmd命令:

jattach <pid> jcmd "help -all"

例如:使用dumpheap命令导出堆载信息

jattach <pid> dumpheap <heap dump file path> 

jattach 1 jcmd 'GC.heap_dump -all ./123.hprof'

临时容器的配置

临时容器与常规容器共享相同的spec。但是,某些字段被禁用,并且某些行为被更改。下面列出了一些重大变化;检查临时容器规范以获取完整列表。

(1)它们不会重新启动。

(2)不允许定义资源。

(3)不允许使用端口。

(4)不允许使用启动、活动和就绪探测。

临时容器的使用场景

有些时候 Pod 的配置参数使得在某些情况下很难执行故障排查。

例如,在容器镜像中不包含 shell 或者你的应用程序在启动时崩溃的情况下, 就不能通过运行 kubectl exec 来排查容器故障。在这些情况下,你可以使用 kubectl debug 来创建 Pod 的副本,通过更改配置帮助调试。

临时容器功能无疑给调试排查问题带来了很大便利,而进程命名空间共享允许高级调试功能。如果你也使用在 Kubernetes 集群中运行的应用程序,那么值得花时间尝试这些功能。不难想象,一些团队甚至使用这些工具自动执行工作流,例如在readiness probes探针失败时自动修复其他容器。

静态Pod

静态pod直接由某个节点上的kubelet程序进行管理,不需要api server介入,静态pod也不需要关联任何RC,完全是由kubelet程序来监控,不受Deployment和DaemonSet控制。

当kubelet发现静态pod停止掉的时候,重新启动静态pod。

并且始终在某一个节点上运行 kubelet 会自动为每一个静态Pod在 apiserver 上创建一个Pod,因此可以在 apiserver 中查询到该 pod ,但不能通过 apiserver 进行控制(例如不能删除)。

在 /etc/kubernetes/manifests 位置放的所有Pod.yaml文件,机器启动kubelet自己就把他启动起来。

静态Pod一直守护在他的这个机器上

进入 /etc/kubernetes/manifests 位置,可以看到 里边的yaml 文件

如何找到 静态pod 的文件路径

从 node上的 kubelet进程的启动命令,也能一步一步,找出 静态pod 的文件路径

systemctl status kubelet

systemctl restart kubelet


cat /var/lib/kubelet/config.yaml

静态Pod实操

创建 Pod的yaml 文件

scp到/etc/kubernetes/manifests目录下

重启 kubelet 进程, kubectl自动在apiserver中创建了一个pod

如果删除 pod,只需要在/etc/kubernetes/manifests中删除这个静态pod的yaml文件就可以自动删除了。

删除yaml文件,然后重新启动kubelet进程

看不到 pod了

Pod 资源需求和限制

Pod资源限制分为requestslimits, 是Pod内所有容器所消耗资源的总和。

  • requests:定义容器运行时至少需要资源, 定义了容器使用资源的最低保障。
    requests会影响到节点的调度,节点内所有pod 的requests加起来,不能超过节点资源。
  • limits:“limits” 属性定义容器运行时最多能分配的资源,定义了容器可以使用资源的上限。
    limits 不会影响节点的调度,所有pod加起来可以超过节点资源。

CPU计算方式为:1核 等于 1000m

内存计算方式为: 按照通常的计算方式计算

注意:

CPU为可压缩资源,当节点内部资源紧张的时候,会压缩每个pod的CPU资源,

而内存为不可压缩资源,当节点内部资源开始紧张后,会直接被OOM进程杀死

关于OOM:
Oom-kill就是out-of-memory, 在linux内核中有一层保护机制,用于避免linux在内存不足的时候不至于严重的问题,把无关紧要的进程杀掉。

注意:虽然容器受到 requests 和 limits的使用限制,但是,在容器里边使用 top 命令在容器内看到的资源总数,会是节点级别的可用总量。

Pod的Probe 探针机制(健康检查机制)

Probe 探针 背景

在 Kubernetes 中 Pod 是最小的计算单元,而一个 Pod 又由多个容器组成,相当于每个容器就是一个应用,应用在运行期间,可能因为某也意外情况致使程序挂掉。

那么,如何监控这些容器状态稳定性?

那么,如何保证服务在运行期间不会发生问题?

或者说,一旦发生问题,能进行重启?

解决以上三个问题,有一个共性的前提: 如何对Pod进行存活性探测?

所以,存活性探测,就成为了重中之重的事情。

为了解决POD的存活性探测问题, kubernetes 推出了存活性探针机制。

当然,有了存活性探针,能保证程序在运行中如果挂掉能够自动重启,但是还是不够的。

比如说,在Kubernetes 中启动Pod,显示明明Pod已经启动成功,且能访问里面的端口,但是却返回错误信息。

再比如说,在执行滚动更新时候,总会出现一段时间,Pod对外提供网络访问,但是访问却发生404,

由于以上的问题都是因为Pod已经成功启动,但是 Pod 的的容器中应用程序还在启动中导致,考虑到这点,在存活性探针的基础上,Kubernetes推出了就绪性探针机制。

另外,有的容器启动慢,我们也叫他慢容器, 针对这类容器,K8S专门提供了一类探针,叫做startupProbe启动探针。 只有startupProbe启动探针 探测成功之后,存活性探针就绪性探针 才发生作用。

K8S 的3种探针

  • readinessProbe
  • livenessProbe
  • startupProbe(这个1.16版本增加的)

Pod 的基础探针分为liveness 探针和readiness探针 , 1.16版本新增了startupProbe探针

  • liveness 为容器存活探针
  • readiness 为容器就绪探针(可读性)
  • startupProbe(这个1.16版本增加的)

每个容器三种探针(Probe)

  • 启动探针 startupProbe
    • kubelet 使用启动探针,来检测应用是否已经启动。如果启动就可以进行后续的探测检查。
      慢容器一定指定启动探针。一直在等待启动
    • 启动探针是一次性的,探测 成功以后就不用了,剩下存活探针和就绪探针持续运行
    • 后来才加的 (1.16版本),一次性成功探针。
  • 存活探针 liveness prob
    • kubelet 使用存活探针来检测容器是否正常存活。
      有些容器可能产生死锁【应用程序在运行,但是无法继续执行后面的步骤】, 如果检测失败就会重新启动这个容器
    • initialDelaySeconds: 3600(长了导致可能应用一段时间不可用) 5(短了陷入无限启动循环)
  • 就绪探针 readiness prob
    • kubelet 使用就绪探针,来检测容器是否准备好了可以接收流量。
    • 当一个 Pod 内的所有容器都准备好了,才能把这个 Pod 看作就绪了。
    • 就绪探针用途就是:Service后端负载均衡多个Pod,如果某个Pod还没就绪,就会从service负载均衡里面剔除,不会放流量进来

谁利用这些探针探测?

kubelet会主动按照配置,给Pod里面的所有容器发送响应的探测请求

接下来,首先看看 两个基础的 prob:livenessProbe和readinessProbe

livenessProbe和readinessProbe 的区别

(1)livenessProbe

livenessProbe:存活性探针,用于判断容器是不是健康,如果不满足健康条件,那么 Kubelet 将根据 Pod 中设置的 restartPolicy (重启策略)来判断,Pod 是否要进行重启操作。

LivenessProbe按照配置去探测 ( 进程、或者端口、或者命令执行后是否成功等等),来判断容器是不是正常。如果探测不到,代表容器不健康(可以配置连续多少次失败才记为不健康),则 kubelet 会杀掉该容器,并根据容器的重启策略做相应的处理。

如果未配置存活探针,则默认容器启动为通过(Success)状态。即探针返回的值永远是 Success。即Success后pod状态是RUNING

(2)readinessProbe

readinessProbe 就绪性探针,用于判断容器内的程序是否存活(或者说是否健康),只有程序(服务)正常, 容器开始对外提供网络访问(启动完成并就绪)。

容器启动后按照readinessProbe配置进行探测,无问题后结果为成功即状态为 Success。pod的READY状态为 true,从0/1变为1/1。

如果失败继续为0/1,状态为 false。

若未配置就绪探针,则默认状态容器启动后为Success。

对于此pod、此pod关联的Service资源、EndPoint 的关系,都将基于 Pod 的 Ready 状态进行设置。

如果 Pod 运行过程中 Ready 状态变为 false,则系统自动从 Service资源 关联的 EndPoint 列表中去除此pod,届时service资源接收到GET请求后,kube-proxy将一定不会把流量引入此pod中,通过这种机制就能防止将流量转发到不可用的 Pod 上。

如果 Pod 恢复为 Ready 状态。将再会被加回Service资源 关联的 Endpoint 列表。kube-proxy也将有概率通过负载机制会引入流量到此pod中。

(3)就绪存活两种探针的区别

就绪存活两种探针的相同之处:

ReadinessProbe 和 livenessProbe 可以使用相同探测方式,

就绪存活两种探针的不同之处:

二者对 Pod 的处置方式不同。

readinessProbe 当检测失败后,将 Pod 的 IP:Port 从对应的 EndPoint 列表中删除。

livenessProbe 当检测失败后,将杀死容器并根据 Pod 的重启策略来决定作出对应的措施。

存活探针 (liveness)的三种探测方法

上面提到,ReadinessProbe 和 livenessProbe 可以使用相同探测方式,所以,这里以livenessProbe 为例,介绍一下probe相关的三种探测方法。

根据官网api手册,查到liveness提供三种探测方法

  • exec :执行命令的检测方式
  • httpGet :根据http请求返回的检测方式
  • tcpSocket :检查容器TCP状态,如果能连接则正常(eg:连接数据库3389)
exec 方式示例

在容器中执行指定的命令,如果执行成功,退出码为 0 则探测成功。

探针探测结果有以下值:
Success:表示通过检测。
Failure:表示未通过检测。
Unknown:表示检测没有正常进行。

上面的yaml做了两件事情

  • 定义一个pod,这个pod会在启动时,创建/tmp/healthy的文件,然后等待30秒后,删除这个文件
  • 每隔5秒,就会执行cat /tmp/healthy命令,如果检测到执行命令失败,则这个Pod将会重启

因为一开始创建容器的时候,是有/tmp/healthy文件的,

但是30秒后,这个文件将会被删除,随后检测失败

随后检测失败,这个容器再次被重启,如此循环

通过 pod,可以看到重启的次数

httpGet 方式示例

httpGet 方式通过容器的IP地址、端口号及路径调用 HTTP Get方法,如果响应的状态码大于等于200且小于400,则认为容器 健康。

探针探测结果有以下值:
Success:表示通过检测。
Failure:表示未通过检测。
Unknown:表示检测没有正常进行。

httpGet的方式是检查返回码,而不是检测某个具体的端口,例如8080

apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: registry.cn-shenzhen.aliyuncs.com/zwh-kolla/liveness (官方测试用的镜像,我下载到了阿里云上)
    args:
    - /server
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

上面用到参数介绍:

periodSeconds:指定kubelet应每3秒执行一次活跃度探测

initialDelaySeconds:告诉kubelet应该在执行第一次探测之前等待3秒

name: Custom-Headervalue: Awesome: 自定义HTTP请求headers,这里定义的 Custom-Header 对应的value为 Awesome

httpGet探测方式有如下可选的控制字段:

scheme: 用于连接host的协议,默认为HTTP。
host:要连接的主机名,默认为Pod IP,可以在http request head中设置host头部。
port:容器上要访问端口号或名称。
path:http服务器上的访问URI。
httpHeaders:自定义HTTP请求headers,HTTP允许重复headers。

httpGet探测方式下,任何大于或等于200且小于400的代码表示成功,任何其他代码表示失败。

通过自定义HTTP请求headers

livenessProbe:
  failureThreshold: 5       #检测失败5次表示未就绪
  initialDelaySeconds: 20   #延迟加载时间
  periodSeconds: 10         #重试时间间隔
  timeoutSeconds: 5         #超时时间设置
  successThreshold: 2       #检查成功为2次表示就绪
  httpGet:
   port: 8080
   path: /health
   httpHeaders:
   - name: end-user
   - value: Jason
TCP 方式示例

TCP 方式通过容器的 IP 地址和端口号执行 TCP 检 查,如果能够建立 TCP 连接,则表明容器健康。

探针探测结果有以下值:
Success:表示通过检测。
Failure:表示未通过检测。
Unknown:表示检测没有正常进行。

TCP方式,就是类似于telnet, 去检测某一个指定的端口

apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: k8s.gcr.io/goproxy:0.1
    ports:
    - containerPort: 8080
    readinessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 5
      periodSeconds: 10
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

这个示例中,使用了两个探针readinessProbelivenessProbe ,并且探测的模式都是TCP

  • 第一步:使用readinessProbe字段,容器启动完成5秒后,每隔10秒访问一次8080端口,能访问则容器正常。
  • 第二步:使用livenessProbe字段,容器启动完成15秒后,每隔20秒访问一次8080端口,能访问则容器正常。

TCP 检查方式和 HTTP 检查方式非常相似,在容器启动 initialDelaySeconds 参数设定的时间后,kubelet 将发送第一个 livenessProbe 探针,尝试连接容器的 8080 端口,

TCP 探测类似于telnet 8080端口,如果telnet 连接失败,则将杀死 Pod 重启容器。

使用命名的端口

可以使用命名的ContainerPort作为HTTP或TCP liveness检查:

ports:
- name: liveness-port
  containerPort: 8080
  hostPort: 8080

livenessProbe:
  httpGet:
  path: /healthz
  port: liveness-port

就绪探针 (readiness)

Pod 的ReadinessProbe 探针使用方式和 LivenessProbe 探针探测方法一样,也是支持三种,只是一个是用于探测应用的存活,一个是判断是否对外提供流量的条件。

回顾一下readinessProbe和livenessProbe区别,两种配对Pod的处置方式不同

  • 就绪探针readinessProbe探测失败时,就绪探针不会删除或重启Pod,而是会显示“未就绪”状态
  • 存活探针livenessProbe探测失败时,则Kill容器并根据Pod的重启策略来决定作出对应的措施

LivenessProbe 和 ReadinessProbe 两种探针的相关属性

探针(Probe)有许多可选字段,可以用来更加精确的控制Liveness和Readiness两种探针的行为(Probe):

  • initialDelaySeconds:容器启动后要等待多少秒后存活和就绪探测器才被初始化,默认是 0 秒,最小值是 0。
  • periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
  • successThreshold:探测器在失败后,被视为成功的最小连续成功数。注意是连续成功次数,默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
  • failureThreshold:当探测失败时,Kubernetes 的重试次数,重试次数用完就放弃。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃的话, Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。
ReadinessProbe 探针使用示例

这里用一个 Springboot 项目,设置 ReadinessProbe 探测的方式是httpGet,地址为 SpringBoot 项目的 7700端口下的 /actuator/health 接口,

如果探测成功则代表内部程序以及启动,就开放对外提供接口访问,否则内部应用没有成功启动,暂不对外提供访问,直到就绪探针探测成功。

ReadinessProbe + LivenessProbe 配合使用示例

一般程序中需要设置两种探针结合使用,并且也要结合实际情况,来配置初始化检查时间和检测间隔,

下面列一个简单的 SpringBoot 项目的 Deployment 例子。

以上案例中存活探针 参数意思:

上面 Pod 中启动的容器是一个 SpringBoot 应用,其中引用了 Actuator 组件,提供了 /actuator/health 健康检查地址,

在pod启动后,初始化等待120s后,livenessProbe开始工作,

去请求HTTP://podIP:7700/actuator/health 接口,

类似于curl -I HTTP://podIP:7700/actuator/health 接口,

考虑到请求会有延迟(curl -I后一直出现假死状态),所以给这次请求操作一直持续60s 到超时(timeoutSeconds),

如果60s (timeoutSeconds)内访问返回数值在>=200且<=400代表第一次检测success,

如果是其他的http状态码,或者60s后还没有返回,执行类似(ctrl+c)中断,并反回failure失败。

等待60s (periodSeconds)后,再一次的去请求 HTTP://podIP:7700/actuator/health 接口。

如果有连续的2次(successThreshold)都是success,代表无问题。

如果期间有连续的5次(failureThreshold)都是failure,代表有问题,直接重启pod,此操作会伴随pod的整个生命周期.

另外,terminationGracePeriodSeconds 是SpringBoot 优雅停机给一个缓冲时间。在 Kubernetes 中,Pod 停止时 kubelet 会先给容器中的主进程发 SIGTERM 信号来通知进程进行 shutdown 以实现优雅停止,如果超时进程还未完全停止则会使用 SIGKILL 来强行终止。

上面的数值,尼恩为了测试,被我调的很大,

实际环境中,可以变小一点,如下所示

livenessProbe:
  failureThreshold: 5       #检测失败5次表示未就绪
  initialDelaySeconds: 20   #延迟加载时间
  periodSeconds: 10          #重试时间间隔
  timeoutSeconds: 5         #超时时间设置
  successThreshold: 2       #检查成功为2次表示就绪
  httpGet:
    scheme: HTTP
    port: 8081
    path: /actuator/health

注意terminationGracePeriodSeconds不能用于就绪态探针(readinessProbe),如果将它应用于readinessProbe将会被APIserver接口所拒绝

以上案例中就绪探针 参数意思:

和 存活探针的参数,基本相同。

回顾一下readinessProbe和livenessProbe区别,两种配对Pod的处置方式不同

  • 存活探针livenessProbe探测失败时,则Kill容器并根据Pod的重启策略来决定作出对应的措施
  • 就绪探针readinessProbe探测失败时,就绪探针不会删除或重启Pod,而是会显示“未就绪”状态

健康检查+优雅停机 = 0宕机

start完成以后,liveness和readness并存。

liveness失败导致重启。readness失败导致不给Service负载均衡网络中加,不接受流量。

startupProbe探针

1、startupProbe探针介绍

k8s 在1.16版本后增加startupProbe探针,

主要解决在复杂的程序(慢启动应用)中readinessProbe、livenessProbe探针无法更好的判断程序是否启动、是否存活。

进而引入startupProbe探针,在readinessProbe、livenessProbe之前,提供前置的探测服务。

2、startupProbe探针与另两种区别

如果三个探针同时存在,先执行startupProbe探针,其他两个探针将会被暂时禁用,

直到pod满足startupProbe探针配置的条件,其他2个探针启动,如果不满足按照规则重启容器

另外两种探针在容器启动后,会按照配置,直到容器消亡才停止探测,而startupProbe探针只是在容器启动后按照配置满足一次后,不在进行后续的探测。

3、startupProbe探针方法、属性

startupProbe探针的使用方法跟 ReadinessProbe 和 livenessProbe 相同,

startupProbe 对 Pod 的处置跟livenessProbe 方式相同,失败重启。

startupProbe探针属性跟 ReadinessProbe 和 livenessProbe 相同

  • initialDelaySeconds:容器启动后要等待多少秒后存活和就绪探测器才被初始化,默认是 0 秒,最小值是 0。
  • periodSeconds:执行探测的时间间隔(单位是秒)。默认是 10 秒。最小值是 1。
  • timeoutSeconds:探测的超时后等待多少秒。默认值是 1 秒。最小值是 1。
  • successThreshold:探测器在失败后,被视为成功的最小连续成功数。注意是连续成功次数,默认值是 1。 存活和启动探测的这个值必须是 1。最小值是 1。
  • failureThreshold:当探测失败时,Kubernetes 的重试次数,重试次数用完就放弃。 存活探测情况下的放弃就意味着重新启动容器。 就绪探测情况下的放弃的话, Pod 会被打上未就绪的标签。默认值是 3。最小值是 1。

注:在startupProbe执行完之后,其他2种探针的所有配置才全部启动,

所以,如果配置了startupProbe, 其他2种探针如果配置了initialDelaySeconds,建议不要给太长

4、为什么要使用startupProbe、使用场景

startupProbe官方的解释(可以定义一个启动探针,该探针将推迟所有其他探针,直到 Pod 完成启动为止),

startupProbe 启动探针存在的意义是不是:

如果服务A启动需要1分钟 ,我们存活探针探测的时候设置的是initialDelaySeconds 10s后开始探测,然后她探测的时候发现服务不正常,然后就开始重启Pod陷入死循环,但是如果意义在这个地方,那我们可以把探测时间调整大一点,failureThreshold 把这个也多设置几次就行了啊。

为什么还要单独的设置一个startupProbe呢?

startupProbe的存在的本质

本质上,讲启动探测从 存活探测 的过程中剥离,或者解耦。

之前,livenessProbe 身兼多职,既要负责 存活探测,又要负责 启动探测。

对于慢应用而言, 存活探测需要比较长时间, 如果把那么长的时间,用于存活探测, 会导致应用死亡很长时间之后,才能被发现。

所以,对于慢启动应用,可专门设置一个存活探针,各司其职。

startupProbe 和 livenessProbe 最大的区别就是startupProbe在探测成功之后就不会继续探测了,而livenessProbe在pod的生命周期中一直在探测。

startupProbe的本质分

如果没有startupProbe探针的话我们只设置livenessProbe探针话会存在如下问题:

一个服务如果前期启动需要很长时间,那么它后面死亡未被发现的时间就越长,为什么会这么说呢?

假设我们一个服务A启动完成需要2分钟,那么我们如下开始定义livenessProbe

livenessProbe:
  httpGet:
    path: /test
    prot: 80
failureThreshold: 1
initialDelay:5
periodSeconds: 5

如果我们这样定义的话,那pod 5s就会根据重启策略进行一次重启,这个时候你会发现pod一直会陷入死循环,那我们可以按照上面的猜想把配置改成这样

livenessProbe:
  httpGet:
    path: /test
    prot: 80
failureThreshold: 6
initialDelay:40
periodSeconds: 5

你肯定会说你看这样不就行了吗?

这样的话pod就不会陷入死循环能启动起来了,确实这样pod能够启动起来了,

但是你有没有考虑过这样一个问题,当我们启动完成之后,在后期的探测中,你需要6*5=30s才能发现这个pod不可用,这个时候你的服务已经停止运行了30s你才发现,这在生产中有可能是不会被原谅的。

还有就是这边只是我们假设一个服务A需要1分钟才能起来,但是在实际生产中你如何定义这些值呢???
针对上面这两个问题引入startupProbe之后都解决了

livenessProbe:
  httpGet:
    path: /test
    prot: 80
failureThreshold: 1
initialDelay:5
periodSeconds: 5
 
startupProbe:
  httpGet:
    path: /test
    prot: 80
failureThreshold: 60
initialDelay:5
periodSeconds: 5

我们这样设置之后,由于startupProbe探针的存在,程序有60*5s=300s的启动时间,

一旦startupProbe探针探测成功之后,就会被livenessProbe接管,这样在运行中出问题livenessProbe就能在15=5s内发现。

如果启动探测是3分钟内还没有探测成功,则接受Pod的重启策略进行重启。

技术自由的实现路径:

实现你的 架构自由:

吃透8图1模板,人人可以做架构

10Wqps评论中台,如何架构?B站是这么做的!!!

阿里二面:千万级、亿级数据,如何性能优化? 教科书级 答案来了

峰值21WQps、亿级DAU,小游戏《羊了个羊》是怎么架构的?

100亿级订单怎么调度,来一个大厂的极品方案

2个大厂 100亿级 超大流量 红包 架构方案

… 更多架构文章,正在添加中

实现你的 响应式 自由:

响应式圣经:10W字,实现Spring响应式编程自由

这是老版本 《Flux、Mono、Reactor 实战(史上最全)

实现你的 spring cloud 自由:

Spring cloud Alibaba 学习圣经》 PDF

分库分表 Sharding-JDBC 底层原理、核心实战(史上最全)

一文搞定:SpringBoot、SLF4j、Log4j、Logback、Netty之间混乱关系(史上最全)

实现你的 linux 自由:

Linux命令大全:2W多字,一次实现Linux自由

实现你的 网络 自由:

TCP协议详解 (史上最全)

网络三张表:ARP表, MAC表, 路由表,实现你的网络自由!!

实现你的 分布式锁 自由:

Redis分布式锁(图解 - 秒懂 - 史上最全)

Zookeeper 分布式锁 - 图解 - 秒懂

实现你的 王者组件 自由:

队列之王: Disruptor 原理、架构、源码 一文穿透

缓存之王:Caffeine 源码、架构、原理(史上最全,10W字 超级长文)

缓存之王:Caffeine 的使用(史上最全)

Java Agent 探针、字节码增强 ByteBuddy(史上最全)

实现你的 面试题 自由:

4000页《尼恩Java面试宝典 》 40个专题

以上尼恩 架构笔记、面试题 的PDF文件,请到《技术自由圈》公众号领取↓↓↓

猜你喜欢

转载自blog.csdn.net/crazymakercircle/article/details/129937569