# k8s基础2

k8s命令对于一些基础查询类操作起来非常方便,但是对于对k8s集群中资源管理及大量资源对象编排部署(创建或删除等)操作,使用简单的命令行工具是无法满足要求的,那么如何满足播仔使用k8s集群的需求呢?

k8s集群中对资源管理和资源对象编排部署都可以通过声明样式(YAML)文件来解决,也就是可以把需要对资源对象操作编辑到YAML格式文件中,我们把这种文件叫做资源清单文件,通过kubectl命令直接使用资源清单文件就可以实现对大量的资源对象进行编排部署了。

# YAML简介

YAML是一种标记语言。

  1. 它以数据做为中心,而不是以标记为重点。
  2. 一个可读性高,用来表达数据序列的格式。

# 基本语法

它的基本语法如下:

  1. 使用空格做为缩进,不支持使用tab缩进
  2. 缩进的空格数目不重要,一般是两个空格,只要相同层级的元素左侧对齐即可
  3. 字符后缩进一个空格,如冒号、逗号等
  4. 使用#标识注释,从这个字符一直到行尾,都会被解释器忽略
  5. "-"表示一段内容的开发。

# 基本数据结构

  1. 对象:又称为映射(mapping),哈希(hashes),字典(dictionary)。是键值对的集合。

    # 第一种形式
    name: Tom
    age: 18
    
    # 第二种形式,一个行内对象
    hash: { name: Tom, age: 18 }
    
    1
    2
    3
    4
    5
    6
  2. 数组:又称为序列(sequence),列表(list)。是一组按次序排列的值。

    #一组连词线开头的行,构成一个数组
    People
    - Tom
    - Jack
    
    #数组也可以采用行内表示法
    People: [Tom, Jack]
    
    1
    2
    3
    4
    5
    6
    7
  3. 纯量(scalars):单个的、不可再分的值。

    #纯量:纯量是最基本的、不可再分的值。以下数据类型都属于纯量
    
    # 数值直接以字面量的形式表示
    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  
    
    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

# 资源清单

在k8s中,一般使用YAML格式的文件来创建符合我们预期期望的pod,这样的YAML文件称为资源清单。

参数名 字段类型 说明
version String "这里是指的是K8S API的版本,目前基本上是v1,可以用kubectl api- versions命令查询"
kind String 这里指的是yam文件定义的资源类型和角色,比如: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 [].imagePullPolicy String 定义镜像拉取策略,有Always、 Never、 IfNotPresent三个值可选:
1. Always:意思是每次都尝试重新拉取镜像
2. Never:表示仅使用本地镜像
3. IfNotPresent:如果本地有镜像就使用本地镜像,没有就拉取在线镜像。
上面三个值都没设置的话,默认是 Always。
spec containers [].command[] List 指定容器启动命令,因为是数组可以指定多个。不指定则使用镜像打包时使用的启动命令。
spec.containers [].args List 指定容器启动命令参数,因为是数组可以指定多个.
spec.containers [].workDir String 指定容器的工作目录
spec.containers[].volumeMounts[] List 指定容器内部的存储卷配置
spec.containers[].volumeMounts[].name String 指定可以被容器挂载的存储卷的名称
spec.containers[].volumeMounts[].mountPath String 指定可以被容器挂载的存储卷的路径
spec.containers[].volumeMounts[].readOnly String 设置存储卷路径的读写模式, ture或者 false,默认为读写模式
spec.containers [].ports[] String 指症容器需要用到的端口列表
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参数
spec.containers[].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,容器启动时初始化可用数量
sepc.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过滤标签,以key:value格式指定。
spec.imagePullSecrets Object 定义pull镜像时使用secret名称,以name:secretkey格式指定。
spec.hostNetwork Boolean 定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。

# 资源对象

K8s中常见的资源对象如下:

  1. Pod
  2. Replicaset
  3. Deployment
  4. Service
  5. Statefulset
  6. Job & CronJob
  7. Ingress
  8. Configmap和Secret

# 命名空间

# 应用场景

某项目需要准备两套k8s集群用于开发测试及预发布,但是由于项目组可用主机资源有限,没有那么多主机可用,不能满足两套k8s集群的要求,所以就想在现有的k8s集群中运行两套环境,这个时候就是命名空间的使用。

Kubernetes 支持多个虚拟集群,它们底层依赖于同一个物理集群。 这些虚拟集群被称为命名空间。命名空间 namespace 是 k8s 集群级别的资源, 可以给不同的用户、租户、环境或项目创建对应的命名空间,例如,可以为 test、 devlopment、 production 环境分别创建各自的命名空间。

# NameSpace特性

命名空间可以在多租户的情况下,实现资源隔离:

  • 属于逻辑隔离
  • 属于管理边界
  • 不属于网络边界
  • 可以针对每个namespace做资源配额

# 命名空间操作

可以使用如下命令查看命名空间:

kubectl get namespace

  • default:用户创建的pod默认在此命名空间
  • kube-public:所有用户均可以访问,包括未认证用户
  • kube-node-lease:kubernetes集群节点租约状态 v1.13 加入
  • kube-system:kubernetes集群在使用,可使用如下命令查看相关pods。

    kubectl get pods --namespace kube-system

  1. 创建命名空间:

    # 使用命令:
    kubectl create namespace test
    
    # 创建应用资源清单文件
    vi create-ns.yaml
    
    apiVersion: v1
    kind: Namespace
    metadata:
      name: demons1
    
    kubectl apply -f create-ns.yaml
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  2. 删除命名空间:

    # 使用命令
    kubectl delete namespace test  
    
    kubectl delete -f create-ns.yaml
    
    1
    2
    3
    4

# 资源限额

下面是在命名空间中做资源限额:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: mem-cpu-quota
  namespace: test
spec:
  hard:
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi
1
2
3
4
5
6
7
8
9
10
11

创建的 ResourceQuota 对象将在 test 名字空间中添加以下限制:

  • 每个容器必须设置内存请求(memory request),内存限额(memory limit), cpu 请求(cpu request)和 cpu 限额(cpu limit)。
  • 所有容器的内存请求总额不得超过 2 GiB。
  • 所有容器的内存限额总额不得超过 4 GiB。
  • 所有容器的 CPU 请求总额不得超过 2 CPU。
  • 所有容器的 CPU 限额总额不得超过 4 CPU。

创建pod需要指定这些信息:

apiVersion: v1 
kind: Pod 
metadata: 
  name: pod-test 
  namespace: test 
  labels: 
    app: tomcat-pod-test 
spec: 
  containers: 
  - name: tomcat-test 
    ports: 
    - containerPort: 8080 
    image: xianchao/tomcat-8.5-jre8:v1 
    imagePullPolicy: IfNotPresent 
    resources: 
      requests: 
        memory: "100Mi" 
        cpu: "500m" 
    limits: 
        memory: "2Gi" 
        cpu: "2"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# Pod

在k8s集群中是不能直接运行容器的,k8s集群的最小调度单元为Pod,我们需要在Pod中运行容器。可以把pod看成是一个“豌豆荚”,里面有很多“豆子”(容器)。一个豌豆荚里的豆子,它们吸收着共同的营养成分、肥料、水分等,Pod和容器的关系也是一样,Pod里面的容器共享pod的空间、资源、网络、存储等。

Pod 是 kubernetes 中最小的资源管理组件,Pod 也是最小化运行容器化应用的资源对象。一个 Pod 代表着集群中运行的一个进程。kubernetes 中其他大多数组件都是围绕着 Pod 来进行支撑和扩展 Pod 功能的,例如用于管理 Pod 运行的 StatefulSet 和 Deployment 等控制器对象,用于暴露 Pod 应用的 Service 和 Ingress 对象,为 Pod 提供存储的 PersistentVolume 存储资源对象等。

pod 相当于一个逻辑主机:比方说我们想要部署一个 tomcat 应用,如果不用容器,我们可能会部署到物理机、虚拟机或者云主机上,那么出现 k8s 之后,我们就可以定义一个 pod 资源,在 pod 里定义一些容器,所以 pod 充当的是一个逻辑主机的角色。Pod 中可以同时运行多个容器。同一个 Pod 中的容器会自动的分配到同一个 node 上。同一个 Pod 中的容器共享资源、网络环境,它们总是被同时调度,pod将这些容器、网络资源和存储资源作为一个单一的可管理实体包装在一起。

# 为啥使用Pod

从上面的描述中我们可以总结下相对于容器,Pod有的优势:

  1. Pod 是由一组紧耦合的容器组成的容器组,当然目前最流行的就是 Docker、 containerd、 podman容器, Pod 就可以作为一个或者多个容器的载体。
  2. Pod 中的所用容器会被一致调度、同节点部署,并且在一个“共享环境” 中运行。 Pod 想成一个车:车里面好多座位,每个座位都坐不同的人,每个座位想成是一个容器, 这里的“共享环境” 包括以下几点:
    • 所有容器共享一个 IP 地址和端口空间,意味着容器之间可以通过 localhost 高效访问,不能有端口冲突
    • 允许容器之间共享存储卷,通过文件系统交互信息
  3. 有些容器需要紧密联系, 需要一起工作。 Pod 提供了比容器更高层次的抽象, Pod 中的所有容器使用同一个网络的 namespace,即相同的 IP 地址和 Port 空间。它们可以直接用 localhost 通信。同样的,这些容器可以共享存储,当 K8s 挂载 Volume 到 Pod 上,本质上是将 volume 挂载到 Pod 中的每一个容器里。

常见的业务场景有;

  • 代码自动发版更新
  • 收集业务日志

# Pod网络

Pod 是有 IP 地址的,注意 pod 不是共享物理机 ip,由网络插件(calico、 flannel、 weave)划分的ip, 每个 pod 都被分配唯一的 IP 地址。

Docker创建新容器的时候,通过--net container参数,指定其和已经存在的某个容器共享一个Network Namespace,如下图右边黄色所创建的container,其网卡共享左边容器,自己不会有独立的IP,而是共享左边容器的IP,端口等网络资源。

dockernet

docker run --name container2 --net=container:none -it --privileged=true centos

而在k8s中,启动Pod时,会先启动一个pause的容器,然后将后续的所有容器都link到pause容器上,以实现网络共享。

podnet

# Pod存储

创建 Pod 的时候可以指定挂载的存储卷。POD 中的所有容器都可以访问共享卷,允许这些容器共享数据。Pod 只要挂载持久化数据卷,Pod 重启之后数据还是会存在的。

# Pod使用

在k8s中有两种Pod使用:

  • 自主式Pod
  • 控制器管理的Pod

下面的Pod操作是直接定义一个Pod资源,在控制器那部分是使用控制器来管理Pod。下面是创建Pod的流程:

pod_create

具体创建流程如下:

  1. 通过 kubectl 命令向 apiserver 提交创建 pod 的请求, apiserver 接收到 pod 创建请求后, 会将 pod 的属性信息(metadata)写入 etcd。
  2. apiserver 触发 watch 机制准备创建 pod,信息转发给调度器 scheduler,调度器使用调度算法选择 node,调度器将 node 信息给 apiserver, apiserver 将绑定 node 信息写入 etcd。
  3. apiserver 又通过 watch 机制,调用 kubelet,指定 pod 信息, 调用容器运行时创建并启动 pod 内的容器。
  4. 创建完成之后反馈给 kubelet, kubelet 又将 pod 的状态信息给 apiserver,apiserver 又将 pod 的状态信息写入 etcd。

# Pod的生命周期

参考文章:

Pod的生命周期如下:

podlifecycle

podlifecycle2

podlifecycle3

主要分三个阶段:

  1. 创建pause容器,初始化网络卷,共享网络栈。
  2. 初始化阶段,也叫Init容器,简称initC。
  3. 应用容器(应用运行的容器),也是主容器,主容器运行阶段,简称MainC。

# 第一阶段

一个pod创建以后,首先会创建一个pause容器,创建这个容器的目的有两个:

  • 第一个是创建网络栈
  • 第二个是共享数据卷。

# 第二阶段

一个Pod至少会有两种容器:pause和应用容器,其实还有一个init容器,它和pause容器很类似,是专用的特殊容器。但是不同的是,init容器是用户级容器,由用户来定义,而pause容器则是系统级容器,并不是由用户定义的。init容器从名字上其用途是运行一些初始化任务,来保证容器运行环境,init容器总是运行到成功完成为止。它有以下特点:

  • 初始化容器不是在pod创建过程中一直运行的,而是在mainC启动之前执行,init 容器必须先于 应用容器启动,仅当 init 容器完成后,才能运行应用容器。类似脚本运行,只要运行成功后就被销毁。Init容器启动失败后,Pod会一直重启,但如果重启策略是never,他不会重新启动。
  • 一个 Pod 允许有多个 init 容器,做不同的初始化任务,当有多个initC的时候,多个initC之间执行是串行的。执行第一个initC的时候,其他initC等待。只有第一个执行成功了,才会执行第二个initC。
  • initC可有可无,但一旦有,就必须执行成功,才能执行后面的操作,如果执行失败,就会将整个pod删除重建。而且是只要有一个initC执行失败,就会删除整个pod重建。
  • 虽然 init 容器与应用容器是两个类别的容器,但由于属于同一个 Pod ,因此容器的名字,是不能重复的。
    • 应用容器定义在 Pod.Spec.Containers,是必填字段,而 init 是定义在 Pod.Spec.initContainers 中,是可选字段。
    • init 容器没有 Lifecycle actions, Readiness probes, Liveness probes 和 Startup probes,而这些应用容器都有。
  • 当计算资源总量时,如果只有应用容器,请求的资源总量应该是所有应用容器的各自请求的资源总和。但有init容器时,pod的资源总量计算公式为:

    max(应用容器请求资源之和,max(所有的 init 容器请求资源))

为啥使用init容器呢?

  • 应用容器处于安全考虑不希望使用一些python、sed、awk这样的工具使得容易变得臃肿,可以由Init容器提前完成相应的功能,再给到应用容器使用
  • Init容器有属于自己的单独namespace,实现权限的转换,通过Init先获取到权限后再给到应用容器,这样避免应用容器直接获取到权限来的安全
  • 在Pod中加入Init容器能起到Pod应用容器的启动顺序,mysql-Init容器-Apache+php,Init起到检测mysql是否正常

init容器的应用场景:

假设我们有一个 Web 服务,该服务又依赖于另外一个数据库服务。但是在在启动这个 Web 服务的时候,我们并不能保证依赖的这个数据库服务就已经启动起来了,所以可能会出现一段时间内 Web 服务连接数据库异常。要解决这个问题的话我们就可以在 Web 服务的 Pod 中使用一个InitContainer,在这个初始化容器中去检查数据库是否已经准备好了,准备好了过后初始化容器就结束退出,然后我们的主容器 Web 服务被启动起来,这个时候去连接数据库就不会有问题了。

# 第三阶段

真正给用户提供服务的容器,一定要封装在mainC里,而不是initC里面。因为initC执行完毕就销毁了。下面是主容器的特点:

  1. 主容器一定是等到初始化容器全部成功执行完毕以后才执行。主容器至少要有一个。
  2. 在主容器运行的阶段,mainC可以在任何时候启动,在任何时候结束。整体是并行执行。
  3. 当mainC有多个的时候,容器名和端口不能重复。因为他们在同一个pod中,共享网络和存储卷
  4. 多个MainC时,执行顺序是按照代码执行的逻辑执行,但不需要等待,可以理解为每个MainC都是一个单独的线程,时间到了,就执行,不需要等待上一个mainC执行完才执行。
  5. 在mainC上可以加钩子函数。常用的钩子函数有两个:启动前和关闭前。
    • 启动前:在mainC启动之前,有很多元数据要注入,比如:环境变量。在初始化结束之后,接下来就要去执行启动脚本了,在执行启动脚本之前执行的一些命令。但注意:启动命令,也就是启动前的钩子函数,不一定会在启动脚本执行之前完成。
    • 关闭前:关闭前的钩子函数,一定会保证是在主命令MainC执行完以后才执行
  6. 钩子函数的作用范围是每一个MainC。每一个MainC可以有自己的启动前和关闭前钩子函数,也可以没有。
  7. MainC上除了有钩子函数,还有探针。老版本探针有两种:就绪探测、存活探测。新版本增加了一种,探测前探针。

# 探针检测

下面是三种探针:

  • 探测前探针(startupProbe:启动探针,kubernetes v1.16引入):在就绪探测和存活探测之前都要有一段时间让mainC做准备工作,但是应该留多长时间呢?这个是不确定的,这个时候,我们就用到了探测前探针。比如有一个就绪探测需要用到80端口,探测前探针就需要提前去检测80端口就绪了么?如果没就绪就继续等待,如果就绪了,那就可以进行就绪探测。如果配置了启动探测,则会先禁用所有其它的探测,直到startupProbe检测成功为止,如果startupProbe探测失败,则kubelet将杀死容器,容器将按照重启策略进行下一步操作,如果容器没有提供启动探测,则默认状态为成功。
  • 就绪探测(ReadinessProbe):准备好了,提供给用户使用;没有准备好,就不提供给用户使用。就绪探测的时间点是在启动后的一段时间,就像是问准备好了么?准备好了,就可以对外访问了。这里可以设置重试间隔,比如2s问一次。直到回答准备好了,这时就绪探针就结束了。
  • 存活探测(LivenessProbe):就像上报心跳,如果活着,就上报活着;如果死了,就要删除pod,然后重建。mainC开始以后,会间隔一段时间开始探测,间隔的这段时间是留出来让程序做准备工作的。

kubelet在容器上周期性的执行探针以检测容器的健康状态,kubelet通过调用被容器实现的处理器来实现检测,在Kubernetes中有三类处理器:

  • ExecAction :在容器中执行一个指定的命令。如果命令的退出状态为0,则判断认为是成功的;
  • TCPSocketAction :在容器IP地址的特定端口上执行一个TCP检查,如果端口处于打开状态,则视为成功;
  • HTTPGetAcction :在容器IP地址的特定端口和路径上执行一个HTTP Get请求使用container的IP地址和指定的端口以及请求的路径作为url,用户可以通过host参数设置请求的地址,通过scheme参数设置协议类型(HTTP、HTTPS)如果其响应代码在200~400之间,设为成功。

健康检测的结果为下面三种情况:

  • Success :表示容器通过检测
  • Failure :表示容器没有通过检测
  • Unknown :表示容器容器失败

在Pod中的容器可能会由于异常等原因导致其终止退出,Kubernetes提供了重启策略以重启容器。重启策略对同一个Pod的所有容器起作用,容器的重启由Node上的kubelet执行。注意,这里的重启是指在Pod的宿主Node上进行本地重启,而不是调度到其它Node上。Pod支持三种重启策略,在配置文件中通过restartPolicy字段设置重启策略:

  • Always:只要退出就会重启,不管是怎么退出的。
  • OnFailure:只有pod以非0退出码退出时,才会重启容器。正常退出(退出码为0),则不会重启。
  • Never:只要退出就不再重启,不管是怎么退出的。

# Pod操作

  1. 查看命名空间的某个Pod:

    kubectl get pods --namespace default

  2. 创建Pod

    # 查看yaml文件语法
    kubectl explain pod.spec.containers
    
    vi create-pod.yaml
    
    apiVersion: v1
    kind: Pod
    metadata:
      name: pod1
    spec:
      containers:
      - name: ngninx-pod
        image: nginx:latest
        ports:
        - name: nginxport
          containerPort: 80
    
    kubectl apply -f create-pod.yaml
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  3. 通过命令行创建

    # 运行一个容器
    kubectl run tomcat --image=xianchao/tomcat-8.5-jre8:v1 --image-pullpolicy='IfNotPresent' --port=8080
    # 查看pod都有哪些标签
    kubectl get pods --show-labels
    # 查看pod的详细信息
    kubectl describe pods tomcat-test
    # 查看 pod 是否创建成功
    kubectl get pods -o wide -l app=nginx
    # 查看Deployment
    kubectl get deploy -l app=nginx-deploy
    # 查看 Replicaset
    kubectl get rs -l app=nginx
    # 查看Pod的IP以及被调度到所在的节点
    kubectl get pods -owide
    # 查看Pod日志
    kubectl logs tomcat-test
    # 进入刚才创建的Pod
    kubectl exec -it tomcat-test -- /bin/bash
    # 假如 pod 里有多个容器,进入到 pod 里的指定容器
    kubectl exec -it tomcat-test -c tomcat-java -- /bin/bash
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  4. 删除Pod

    kubectl delete pods pod1
    kubectl delete -f create-pod.yaml

# 标签

标签其实就一对 key/value ,被关联到对象上,比如 Pod,标签的使用我们倾向于能够表示对象的特殊特点,就是一眼就看出了这个 Pod 是干什么的,标签可以用来划分特定的对象(比如版本,服务类型等),标签可以在创建一个对象的时候直接定义,也可以在后期随时修改,每一个对象可以拥有多个标签,但是,key 值必须是唯一的。创建标签之后也可以方便我们对资源进行分组管理。如果对 pod 打标签,之后就可以使用标签来查看、删除指定的 pod。在 k8s 中,大部分资源都可以打标签。

常见的操作:

# 给pod打标签
kubectl label pods tomcat-test release=v1
# 删除标签,在后面加-
kubectl label nodes nodename zone-
# 查看默认名称空间下所有 pod 资源的标签
kubectl get pods --show-labels
# 查看默认名称空间下指定 pod 具有的所有标签
kubectl get pods tomcat-test --show-labels
# 列出默认名称空间下标签 key 是 release 的 pod,不显示标签
kubectl get pods -l release
# 列出默认名称空间下标签 key 是 release、值是 v1 的 pod,不显示标签
kubectl get pods -l release=v1
# 列出默认名称空间下标签 key 是 release 的所有 pod,并打印对应的标签值
kubectl get pods -L release
# 查看所有名称空间下的所有 pod 的标签
kubectl get pods --all-namespaces --show-labels
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 节点选择器

我们在创建 pod 资源的时候,pod 会根据 schduler 进行调度,那么默认会调度到随机的一个工作节点,如果我们想要 pod 调度到指定节点或者调度到一些具有相同特点的 node 节点,怎么办呢?

  1. 第一种方法,可以使用 pod 中的 nodeName

    spec: 
      nodeName: node1Name 
      containers:
    
    1
    2
    3
  2. 第二种方法,nodeSelector 字段指定要调度到的 node 节点

    # 给node打个标签
    kubectl label nodes node2Name mylabel=testNode
    # 在yaml文件中选择指定节点
    spec: 
      nodeSelector: 
        mylabel:testNode
    
    1
    2
    3
    4
    5
    6

同一个 yaml 文件里定义 pod 资源,如果同时定义了 nodeName 和 NodeSelector,那么条件必须都满足才可以,有一个不满足都会调度失败。

# 节点亲和性

Pod的亲和性分为:

  • nodeAffinity:node 节点亲和性,pod 倾向于哪个 node
    • preferredDuringSchedulingIgnoredDuringExecution
    • requiredDuringSchedulingIgnoredDuringExecution
  • podAffinity:pod 节点亲和性,pod 倾向于哪个 pod
    • preferredDuringSchedulingIgnoredDuringExecution
    • requiredDuringSchedulingIgnoredDuringExecution
  • podAntiAffinity:pod 反亲和性

注意:

  1. prefered 表示有节点尽量满足这个位置定义的亲和性,这不是一个必须的条件,软亲和性;require 表示必须有节点满足这个位置定义的亲和性,这是个硬性条件,硬亲和性
  2. Node 节点亲和性针对的是 pod 和 node 的关系,Pod 调度到 node 节点的时候匹配的条件。weight 是相对权重,权重越高,pod 调度的几率越大

Pod的亲和性主要是两种:

  1. podAffinity是pod 和 pod 更倾向腻在一起,把相近的 pod 结合到相近的位置,如同一区域,同一机架,这样的话 pod 和 pod 之间更好通信,比方说有两个机房,这两个机房部署的集群有 1000 台主机,那么我们希望把 nginx 和 tomcat 都部署同一个地方的 node 节点上,可以提高通信效率;
  2. podAntiAffinity:pod 和 pod 更倾向不腻在一起,如果部署两套程序,那么这两套程序更倾向于反亲和性,这样相互之间不会有影响。

注意:

  1. 我们要判断 pod 跟别的 pod 亲和,跟哪个 pod 亲和,需要靠 labelSelector,通过 labelSelector选则一组能作为亲和对象的 pod 资源。
  2. topologyKey是位置拓扑的键,这个是必须字段。如果node有一个标签rack=rack1,那么topologyKey为rack的是同一位置。

# 污点和容忍度

在 Kubernetes 中,节点亲和性 NodeAffinity 是 Pod 上定义的一种属性,能够使 Pod 按我们的要求调度到某个节点上,而 Taints(污点) 则恰恰相反,它是 Node 上的一个属性,可以让 Pod 不能调度到带污点的节点上,甚至会对带污点节点上已有的 Pod 进行驱逐。当然,对应的 Kubernetes 可以给 Pod 设置 Tolerations(容忍) 属性来让 Pod 能够容忍节点上设置的污点,这样在调度时就会忽略节点上设置的污点,将 Pod 调度到该节点。一般时候 Taints 通常与 Tolerations 配合使用。

  • 节点亲和性:是pod的一种属性(偏好或硬性要求),它使pod被吸引到一类特定的节点。
  • taints:污点,使节点能够排斥一类特定的 pod。Taint 和 toleration 相互配合,可以用来避免pod 被分配到不合适的节点上。定义在节点上,是键值数据。
    • NoSchedule:不会被调度
    • PreferNoSchedule:尽量不调度
    • NoExecute:驱逐节点
  • tolerations:容忍度,定义在pod上,可以定义能容忍哪些污点
    • effect
    • key
    • operator
    • tolerationSeconds
    • value

污点和容忍的关系如下:

  • 一个 node 可以有多个污点;
  • 一个 pod 可以有多个容忍;
  • kubernetes 执行多个污点和容忍方法类似于过滤器

下面是常见的操作:

# 添加污点
# 对于下面的例子来说,它的key是target,value是one,effect是NoSchedule
kubectl taint nodes node1 target=one:NoSchedule

# 删除污点
kubectl taint nodes node1 target:NoSchedule-

# 一个 toleration 和一个 taint 相“匹配”是指它们有一样的key和effect ,operator 默认是 Equal ,即value 相等才匹配。如果operator是Exists,则 toleration 不能指定 value。
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoSchedule"

tolerations:
- key: "key"
  operator: "Exists"
  effect: "NoSchedule"

# 如果一个 toleration 的 effect 为空,则 key 值与之相同就能完成匹配 ,taint的effect可以是任意值。
tolerations:
- key: "key"
  operator: "Exists"

# 如果这个pod的所在节点打了该污点,配置了tolerationSeconds那么pod 还将继续在节点上运行 一段时间,如下3600 秒
tolerations:
- key: "key"
  operator: "Equal"
  value: "value"
  effect: "NoExecute"
  tolerationSeconds: 3600
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

一个 toleration 和一个 taint 相“匹配”是指它们有一样的key和effect。他们的匹配规则:

  1. 如果operator的值是Exists,则value属性可省略。
  2. 如果operator的值是Equal,则表示其key与value之间的关系是equal(等于)。
  3. 如果不指定operator属性,则默认值为Equal。
  4. 空的key如果再配合Exists,就能匹配所有的key与value,也就是能容忍所有node上的所有Taints。
  5. 空的effect,匹配所有的effect。

污点的使用场景:

  • 专用节点:如果你想将某些节点专门分配给特定的一组用户使用,你可以给这些节点添加一个 taint。如果要使pod只能被分配到专用节点,那么你可以给节点打个标签,将 pod 的节点亲和性与容忍结合使用。
  • 配备了特殊硬件的节点:在部分节点配备了特殊硬件(比如 GPU)的集群中,我们希望不需要这类硬件的 pod 不要被分配到这些特殊节点,以便为后继需要这类硬件的 pod 保留资源。

当某种条件为真时,node的控制器会自动给节点添加一个污点,当前内置的污点包括:

node.kubernetes.io/not-ready:节点未准备好。这相当于节点状态 Ready 的值为 “False”。
node.kubernetes.io/unreachable:node controller 访问不到节点
node.kubernetes.io/out-of-disk:节点磁盘耗尽。
node.kubernetes.io/memory-pressure:节点存在内存压力。
node.kubernetes.io/disk-pressure:节点存在磁盘压力。
node.kubernetes.io/network-unavailable:节点网络不可用。
node.kubernetes.io/unschedulable: 节点不可调度。
1
2
3
4
5
6
7

比如一个使用了很多本地状态的应用程序在网络断开时,仍然希望停留在当前节点上运行一段较长的时间,愿意等待网络恢复以避免被驱逐。在这种情况下,pod 的 toleration 可能是下面这样的:

tolerations:
- key: "node.kubernetes.io/unreachable"
  operator: "Exists"
  effect: "NoExecute"
  tolerationSeconds: 6000
1
2
3
4
5

# 控制器

# 控制器应用场景

Pod是可以直接删除的,如果在生产过程中误操作,Pod同样也会轻易被删除,因此,我们需要在k8s集群中引入另一种概念Controller(控制器),它相当于一个状态机,用于在k8s集群中以loop方式监视和控制Pod具体状态,如果其发现Pod被删除,将会重新拉起一个Pod,以让Pod一直保持在用户期望状态。

Kubernetes中的Replicaset,管理Pod,使pod副本的数量始终维持在预设的个数。

Deployment管理Replicaset和Pod的副本控制器,Deployment可以管理多个Replicaset,是比Replicaset更高级的控制器,也即是说在创建Deployment的时候,会自动创建Replicaset,由Replicaset再创建Pod,Deployment能对Pod扩容、缩容、滚动更新和回滚、维持Pod数量。

deployment

# 控制器特性

  • 用于对应用运行的资源对象进行监控。
  • 当Pod出现问题时,会把Pod重新拉起,以达到用户的期望状态。
  • 具有上线部署、滚动升级、创建副本、回滚到以前某一版本(成功/ 稳定)等功能。
  • Deployment包含ReplicaSet,除非需要自定义升级功能或者根本不需要升级Pod,否则还是建议使用Deployment而不直接使用Replica Set。

# 控制器分类

常见的控制器有:

控制器名称 作用
Deployment 声明式更新控制器,用于发布无状态应用
ReplicaSet 副本集控制器,用于对Pod进行副本规模扩大或剪裁
StatefulSet 有状态副本集,用于发布有状态应用
DaemonSet 在k8s集群每一个Node上运行一个副本,用于发布监控或日志收集类等应用
Job 运行一次性作业任务
CronJob 运行周期性作业任务

# 控制器常用操作

  1. 命令创建控制器:

    kubectl run nginx-app --image=nginx:latest --image-pull-policy=IfNotPresent --replicas=2
    kubectl get deployment.apps
    kubectl get replicaset.apps
    
    1
    2
    3
  2. 通过资源清单创建控制器:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
        name: nginx-apps
        labels:
            app: nginx
    spec:
        replicas: 1
        selector:
            matchLabels:
                app: nginx
        template:
            metadata:
                labels:
                    app: nginx
            spec:
                containers:
                -   name: nginxapp
                    image: nginx:latest
                    imagePullPolicy: IfNotPresent
                    ports:
                    - containerPort: 80
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    kubectl apply -f create-deployment-app.yaml

  3. 删除控制器

    kubectl delete deployment.apps nginx-app
    kubectl delete -f create-deployment-app.yaml
    
    1
    2

# Service

# 使用场景

我们通过Controller创建应用,可是当我们访问应用时,发现一个问题,由于Pod的状态不是人为控制的,Pod IP是创建时分配的,如果在Pod被误删除,被Controller重新拉起一个新的Pod时,我们发现Pod IP地址是变化着的,我们如果访问必须更换IP地址,这样对于大量的Pod运行应用来说,我们对Pod完全无法控制的,因此在k8s集群中我们引入另一个概念Service。为了解决这个问题,在kubernetes中定义了service资源对象,Service 定义了一个服务访问的入口,客户端通过这个入口即可访问服务背后的应用集群实例,service是一组Pod的逻辑集合,这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector实现的。

# Service特性

  • 不是实体服务,是一条iptables或ipvs的转发规则

  • 通过Service为pod客户端提供访问pod方法,即客户端访问pod入口

  • Service通过Pod标签与Pod进行关联

  • Node,Service和Pod之间的关系如下:

    service_node

# Service类型

Service有如下类型:

  • ClusterIP:默认类型,分配一个集群内部可以访问的虚拟IP
  • NodePort:在每个Node上分配一个端口作为外部访问入口
  • LoadBalancer:工作在特定的Cloud Provider上,例如Google Cloud,AWS,OpenStack
  • ExternalName:表示把集群外部的服务引入到集群内部中来,即实现了集群内部pod和集群外部的服务进行通信

service中要区分下几个端口类型:

  • port:k8s集群内部访问service的端口,即通过clusterIP: port可以访问到某个service。
  • nodePort:nodePort是外部访问k8s集群中service的端口,通过nodeIP: nodePort可以从外部访问到某个service。(30000-32767)
  • targetPort:targetPort是pod的端口,从port和nodePort来的流量经过kube-proxy流入到后端pod的targetPort上,最后进入容器。

上面三个端口是pod内部容器的端口,有一个是在pod中配置的,名为containerPort。

  • containerPort是pod内部容器的端口,targetPort映射到containerPort。

# Service创建

  1. 通过命令行创建:

    kubectl run nginx-app --image=nginx:latest --image-pull-policy=IfNotPresent --replicas=4  
    kubectl get deployment.apps  
    kubectl expose deployment.apps nginx-app --type=ClusterIP --target-port=80 --port=80  
    # 访问service以实现访问Pod的目的
    kubectl get service
    
    1
    2
    3
    4
    5
  2. 通过资源文件创建:

    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
        name: nginx-app-nodeport
    spec:
        replicas: 1
        selector:
            matchLabels:
                app: nginx-app
        template:
            metadata:
                labels:
                    app: nginx-app
            spec:
                containers:
                  - name: nginx
                    image: nginx:latest
                    imagePullPolicy: IfNotPresent
                    ports:
                      - containerPort: 80
    ---
    apiVersion: v1
    kind: Service
    metadata:
        name: nginx-app
    spec:
        selector:
            app: nginx-app
        type: NodePort
        ports:
            - protocol: TCP
              nodePort: 30001
              port: 80
              targetPort: 80
    
    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

    kubectl apply -f createdeployment-service-nodeport.yaml

  3. 删除service:

    kubectl delete service nginx-app
    kubectl delete -f createdeployment-service-clusterip.yaml