• IT学习笔记--Kubernetes


    kubernetes组件:

    一个Kubernetes集群主要是由控制节点(master)、工作节点(node)构成,每个节点都会安装不同的组件。

    master:集群的控制平面,负责集群的决策

    • ApiServer:资源操作的唯一入口,接收用户输入的命令,提供认证、授权、API注册和发现等机制
    • Scheduler:负责集群的资源调度,按照预定的调度策略将Pod调度到相应的node节点上
    • ControllerManager:负责维护集群的状态,比如程序部署安排、故障检测、自动扩展、滚动更新等
    • Etcd:负责存储集群中各种资源对象的信息
    • Kuberctl: 命令行配置工具

    node:集群的数据平面,负责给容器提供运行环境

    • Kubelet:负责维护容器的声明周期,即通过控制Docker来创建、更新、销毁容器
    • KubeProxy:负责提供容器内部的服务发现和负载均衡
    • Docker:负责节点上容器的各种操作

     下面,以部署一个Nginx服务来说明Kubernetes系统各个组件调用关系:

    1. 首先要明确,一旦Kubernetes环境启动之后,master和node都会将自身的信息存储到etcd数据库中
    2. 一个Nginx服务的安装请求首先会被发送到master节点的ApiServe组件
    3. ApiServer组件会调用Scheduler组件来决定到底应该把这个服务安装到哪个node节点上
    4. ApiServer调用ControllerManager去调度Node节点安装Nginx服务
    5. Kubelet接收到指令后,会通知Docker,然后Docker来启动一个Nginx的Pod
    6. 至此一个Nginx服务就启动成功了,如果需要访问Nginx,就需要通过KubeProxy来对Pod产生访问的代理,这样外界用户就可以访问集群中的Nginx服务了。

    kubernetes概念:

    • Master:集群控制节点,每个集群至少需要一个Master节点负责集群管控
    • Node:工作负载节点,由Master分配容器到这些Node工作节点上,然后Node节点上的Docker负责容器的运行
    • Pod:Kubernetes的最小控制单元,容器都是运行在Pod中的,一个Pod可以有一个或多个容器
    • Controller:控制器,通过它来实现对Pod的管理,比如启动Pod、停止Pod、伸缩Pod的数量等
    • Service:Pod对外服务的统一入口,下面可以维护着同一类的多个Pod
    • Label:标签,用于对Pod进行分类,同一类Pod会拥有相同的标签
    • Namespace:命名空间,用来隔离Pod的运行环境

    kubernetes在集群启动之后,会默认创建几个namespace:default、kube-node-lease、kube-public、kube-system。

    默认情况下,kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中,可能不想让两个Pod之间进行互相的访问,那此时就可以将两个Pod划分到不同的namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上的"组",以方便不同的组的资源进行隔离使用和管理。

    Pod:

    Pod是kubernetes集群进行管理的最小单元,程序要运行必须部署在容器中,而容器必须存在于Pod中。Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器。同一个Pod中的容器共享 IP 地址,进程间的通讯(IPC,Inter-Process Communication,进程间通信),主机名以及其他资源。

    • 同一 Node 中的 Pod 的默认路由都是 docker0 的地址,由于他们关联在同一个 docker0 网桥上,地址段相同,所以能直接通信。
    • 不同 Node 中的 Pod 间通信要满足两个条件: Pod 的 IP 不能冲突;将 Pod 的 IP 和所在的 Node 的 IP 关联起来,通过这个关联让 Pod 可以互相访问。

    每一个Pod中都可以包含一个或多个容器,这些容器可以分为两类:

    1. 用户程序所在的容器,数量可多可少
    2. Pause容器,这是每个Pod都会有的一个根容器,它的作用有两个:
    • 可以以它为依据,评估整个Pod的健康状态
    • 可以在根容器上设置IP地址,其它容器都以此IP(Pod IP),以实现Pod内部的网络通信。这里Pod内部的通讯,Pod的之间的通讯采用虚拟二层网络技术来实现。

    Pod定义:

    下面是Pod的资源清单:

    1. apiVersion: v1 #必选,版本号,例如:v1
    2. kind: Pod #必选,资源类型,例如:Pod
    3. metadata: #必选,元数据
    4. name: string #必选,Pod名称,
    5. namaspace: string #Pod所属命名空间,默认是“default”
    6. labels: #自定义标签列表
    7. - name: string
    8. annotations:
    9. - name: string
    10. spec: #必选,Pod中容器的详细定义
    11. containers: #必选,Pod中容器列表
    12. - name: string #必选,容器名称
    13. images: string #必选,容器镜像名称
    14. imagePullPolicy: [Always | Never | IfNotPresent] #获取镜像的策略
    15. command: [string] #容器的启动命令列表,如果不指定,使用打包时使用的启动命令
    16. args: [string] #容器的启动命令参数列表
    17. workingDir: string #容器的工作目录
    18. volumeMounts: #挂载到容器内部的存储卷位置
    19. - name: string #引用Pod定义的共享存储卷的名称,需要用volumns[]部分定义的卷名
    20. mountPath: string #存储卷在容器内mount的绝对路径,应少于512字符
    21. readOnly: boolean #是否是只读模式
    22. ports: #需要暴露的端口号列表
    23. - name: string #端口的名称
    24. containerPort: int #容器需要监听的端口号
    25. hostPort: int #容器所在主机需要监听的端口号,默认与Container相同
    26. protocol: string #端口协议,支持TCP和UDP,默认TCP
    27. env: #容器运行时需要配置的环境变量列表
    28. - name: string #环境变量名称
    29. value: string #环境变量的值
    30. resources: #资源限制和请求的设置
    31. limits: #资源限制的设置
    32. cpu: string #CPU的限制,单位是core数量,将用于 docker run --cpu-shares参数
    33. memory: string #内存限制,单位以Mib/Gib,将用于docker run --memory参数
    34. requests: #资源请求设置
    35. cpu: string #CPU请求,容器启动的初始可用数量
    36. memory: string #内存请求,容器启动的初始可用数量
    37. livenessProbe: #对Pod内各个容器健康检查的设置,当探测无响应几次后将自动重启该容器
    38. exec: #对Pod容器内检查方式设置为exec方式
    39. command: [string] #exec方式需要制定的命令或脚本
    40. httpGet: # 对Pod内各个容器健康检查方法设置为httpGet,需要指定path和port
    41. path: string
    42. port: int
    43. host: string
    44. scheme: string
    45. httpHeaders:
    46. - name: string
    47. value: string
    48. tcpSocket: # 对Pod内各个容器健康检查方式设置为tcpSocket
    49. port: int
    50. initialDelaySeconds: number #容器内完成首次探测的时间,单位为秒
    51. timeoutSeconds: number #对容器健康探测等待响应的超时时间,单位为秒,默认1秒
    52. periodSeconds: number #对容器健康检查定期探测时间,单位为秒,默认10秒一次
    53. successThreshold: 0
    54. failureThreshold: 0
    55. securityContext:
    56. privileged: false
    57. restartPolicy: [Always | Never | OnFailure] #Pod重启策略
    58. nodeName: #设置nodeName表示将该Pod调度到指定名称的Node节点上
    59. nodeSelector: object #设置nodeSelector表示将该Pod调度到包含这个label的Node上
    60. imagePullSecrets: #Pull镜像时使用的secret名称,以key:secretkey格式指定
    61. - name: string
    62. hostNetwork: false #是够使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
    63. volumes: #在该Pod上定义共享存储卷列表
    64. - name: string #共享存储卷名称(volumns类型有很多种)
    65. emptyDir: {} #类型为emptyDor的存储卷,与Pod同生命周期的一个临时目录。为空值
    66. hostPath: #类型为hostPath的存储卷,表示挂载Pod所在宿主机的目录
    67. path: string
    68. secret: #类型为secret的存储卷,表示挂载集群定义的secret对象到容器内部
    69. secretName: string
    70. items:
    71. - key: string
    72. path: string
    73. configMap: #类型为configMap的存储卷,挂载预定义的configMap对象到容器内部
    74. name: string
    75. items:
    76. - key: string
    77. path: string

    在这里,可通过一个命令来查看每种资源的可配置项:

    kubectl explain 资源类型         查看某种资源可以配置的一级属性
    kubectl explain 资源类型.属性     查看属性的子属性

    1. [root@k8s-master01 ~]# kubectl explain pod
    2. KIND: Pod
    3. VERSION: v1
    4. FIELDS:
    5. apiVersion
    6. kind
    7. metadata
    8. spec
    9. status
    10. [root@k8s-master01 ~]# kubectl explain pod.metadata
    11. KIND: Pod
    12. VERSION: v1
    13. RESOURCE: metadata
    14. FIELDS:
    15. annotations
    16. clusterName
    17. creationTimestamp
    18. deletionGracePeriodSeconds <integer>
    19. deletionTimestamp
    20. finalizers <[]string>
    21. generateName
    22. generation <integer>
    23. labels
    24. managedFields <[]Object>
    25. name
    26. namespace
    27. ownerReferences <[]Object>
    28. resourceVersion
    29. selfLink
    30. uid
    31. 在kubernetes中基本所有资源的一级属性都是一样的,主要包含5部分:

      • apiVersion: 版本,由kubernetes内部定义,版本号必须可以用 kubectl api-versions 查询到
      • kind: 类型,由kubernetes内部定义,版本号必须可以用 kubectl api-resources 查询到
      • metadata:元数据,主要是资源标识和说明,常用的有name、namespace、labels等
      • spec:描述,这是配置中最重要的一部分,里面是对各种资源配置的详细描述
      • status: 状态信息,里面的内容不需要定义,由kubernetes自动生成

      在上面的属性中,spec是接下来研究的重点,继续看下它的常见子属性:

      • containers: 容器列表,用于定义容器的详细信息
      • nodeName: 根据nodeName的值将pod调度到指定的Node节点上
      • nodeSelector: 根据NodeSelector中定义的信息选择将该Pod调度到包含这些label的Node 上
      • hostNetwork:是否使用主机网络模式,默认为false,如果设置为true,表示使用宿主机网络
      • volumes:存储卷,用于定义Pod上面挂在的存储信息
      • restartPolicy: 重启策略,表示Pod在遇到故障的时候的处理策略

       Pod生命周期:

      我们一般将pod对象从创建至终的这段时间范围称为pod的生命周期,它主要包含下面的过程:

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

       

      在整个生命周期中,Pod会出现5种状态相位),分别如下:

      • 挂起(Pending):apiserver已经创建了pod资源对象,但它尚未被调度完成或者仍处于下载镜像的过程中
      • 运行中(Running):pod已经被调度至某节点,并且所有容器都已经被kubelet创建完成
      • 成功(Succeeded):pod中的所有容器都已经成功终止并且不会被重启
      • 失败(Failed):所有容器都已经终止,但至少有一个容器终止失败,即容器返回了非0值的退出状态
      • 未知(Unknown):apiserver无法正常获取到pod对象的状态信息,通常由网络通信失败所导致
      创建和终止:

      pod的创建过程:

      1. 用户通过kubectl或其他api客户端提交需要创建的pod信息给apiServer
      2. apiServer开始生成pod对象的信息,并将信息存入etcd,然后返回确认信息至客户端
      3. apiServer开始反映etcd中的pod对象的变化,其它组件使用watch机制来跟踪检查apiServer上的变动
      4. scheduler发现有新的pod对象要创建,开始为Pod分配主机并将结果信息更新至apiServer
      5. node节点上的kubelet发现有pod调度过来,尝试调用docker启动容器,并将结果回送至apiServer
      6. apiServer将接收到的pod状态信息存入etcd中。

       

      pod的终止过程:

      1. 用户向apiServer发送删除pod对象的命令
      2. apiServcer中的pod对象信息会随着时间的推移而更新,在宽限期内(默认30s),pod被视为dead
      3. 将pod标记为terminating状态
      4. kubelet在监控到pod对象转为terminating状态的同时启动pod关闭过程
      5. 端点控制器监控到pod对象的关闭行为时将其从所有匹配到此端点的service资源的端点列表中移除
      6. 如果当前pod对象定义了preStop钩子处理器,则在其标记为terminating后即会以同步的方式启动执行
      7. pod对象中的容器进程收到停止信号
      8. 宽限期结束后,若pod中还存在仍在运行的进程,那么pod对象会收到立即终止的信号
      9. kubelet请求apiServer将此pod资源的宽限期设置为0从而完成删除操作,此时pod对于用户已不可见
      初始化容器

      初始化容器是在pod的主容器启动之前要运行的容器,主要是做一些主容器的前置工作,它具有两大特征:

      1. 初始化容器必须运行完成直至结束,若某初始化容器运行失败,那么kubernetes需要重启它直到成功完成
      2. 初始化容器必须按照定义的顺序执行,当且仅当前一个成功之后,后面的一个才能运行

      初始化容器有很多的应用场景,下面列出的是最常见的几个:

      • 提供主容器镜像中不具备的工具程序或自定义代码
      • 初始化容器要先于应用容器串行启动并运行完成,因此可用于延后应用容器的启动直至其依赖的条件得到满足
       钩子函数:

      钩子函数能够感知自身生命周期中的事件,并在相应的时刻到来时运行用户指定的程序代码。

      kubernetes在主容器的启动之后和停止之前提供了两个钩子函数:

      • post start:容器创建之后执行,如果失败了会重启容器
      • pre stop :容器终止之前执行,执行完成之后容器将成功终止,在其完成之前会阻塞删除容器的操作

      钩子处理器支持使用下面三种方式定义动作:

      • Exec命令:在容器内执行一次命令

        1. ……
        2. lifecycle:
        3. postStart:
        4. exec:
        5. command:
        6. - cat
        7. - /tmp/healthy
        8. ……
      • TCPSocket:在当前容器尝试访问指定的socket

        1. ……
        2. lifecycle:
        3. postStart:
        4. tcpSocket:
        5. port: 8080
        6. ……
      • HTTPGet:在当前容器中向某url发起http请求

        1. ……
        2. lifecycle:
        3. postStart:
        4. httpGet:
        5. path: / #URI地址
        6. port: 80 #端口号
        7. host: 192.168.5.3 #主机地址
        8. scheme: HTTP #支持的协议,http或者https
        9. ……
      容器探测:

      容器探测用于检测容器中的应用实例是否正常工作,是保障业务可用性的一种传统机制。如果经过探测,实例的状态不符合预期,那么kubernetes就会把该问题实例" 摘除 ",不承担业务流量。kubernetes提供了两种探针来实现容器探测,分别是:

      • liveness probes:存活性探针,用于检测应用实例当前是否处于正常运行状态,如果不是,k8s会重启容器
      • readiness probes:就绪性探针,用于检测应用实例当前是否可以接收请求,如果不能,k8s不会转发流量

      上面两种探针目前均支持三种探测方式:

      • Exec命令:在容器内执行一次命令,如果命令执行的退出码为0,则认为程序正常,否则不正常

        1. ……
        2. livenessProbe:
        3. exec:
        4. command:
        5. - cat
        6. - /tmp/healthy
        7. ……
      • TCPSocket:将会尝试访问一个用户容器的端口,如果能够建立这条连接,则认为程序正常,否则不正常

        1. ……
        2. livenessProbe:
        3. tcpSocket:
        4. port: 8080
        5. ……
      • HTTPGet:调用容器内Web应用的URL,如果返回的状态码在200和399之间,则认为程序正常,否则不正常

        1. ……
        2. livenessProbe:
        3. httpGet:
        4. path: / #URI地址
        5. port: 80 #端口号
        6. host: 127.0.0.1 #主机地址
        7. scheme: HTTP #支持的协议,http或者https
        8. ……
       重启策略:

      一旦容器探测出现了问题,kubernetes就会对容器所在的Pod进行重启,其实这是由pod的重启策略决定的,pod的重启策略有 3 种,分别如下:

      • Always :容器失效时,自动重启该容器,这也是默认值。
      • OnFailure : 容器终止运行且退出码不为0时重启

      重启策略适用于pod对象中的所有容器,首次需要重启的容器,将在其需要时立即进行重启,随后再次需要重启的操作将由kubelet延迟一段时间后进行,且反复的重启操作的延迟时长以此为10s、20s、40s、80s、160s和300s,300s是最大延迟时长。

      Pod调度:

      在默认情况下,一个Pod在哪个Node节点上运行,是由Scheduler组件采用相应的算法计算出来的,这个过程是不受人工控制的。但是在实际使用中,这并不满足的需求,因为很多情况下,我们想控制某些Pod到达某些节点上,那么应该怎么做呢?这就要求了解kubernetes对Pod的调度规则,kubernetes提供了四大类调度方式:

      • 自动调度:运行在哪个节点上完全由Scheduler经过一系列的算法计算得出
      • 定向调度:NodeName、NodeSelector
      • 亲和性调度:NodeAffinity、PodAffinity、PodAntiAffinity
      • 污点(容忍)调度:Taints、Toleration
      定向调度:

      定向调度,指的是利用在pod上声明nodeName或者nodeSelector,以此将Pod调度到期望的node节点上。注意,这里的调度是强制的,这就意味着即使要调度的目标Node不存在,也会向上面进行调度,只不过pod运行失败而已。

      其中:

      NodeName用于强制约束将Pod调度到指定的Name的Node节点上。这种方式,其实是直接跳过Scheduler的调度逻辑,直接将Pod调度到指定名称的节点。

      NodeSelector用于将pod调度到添加了指定标签的node节点上。它是通过kubernetes的label-selector机制实现的,也就是说,在pod创建之前,会由scheduler使用MatchNodeSelector调度策略进行label匹配,找出目标node,然后将pod调度到目标节点,该匹配规则是强制约束。

      亲和性调度:

      上一节,介绍了两种定向调度的方式,使用起来非常方便,但是也有一定的问题,那就是如果没有满足条件的Node,那么Pod将不会被运行,即使在集群中还有可用Node列表也不行,这就限制了它的使用场景。

      基于上面的问题,kubernetes还提供了一种亲和性调度(Affinity)。它在NodeSelector的基础之上的进行了扩展,可以通过配置的形式,实现优先选择满足条件的Node进行调度,如果没有,也可以调度到不满足条件的节点上,使调度更加灵活。

      Affinity主要分为三类:

      • nodeAffinity(node亲和性): 以node为目标,解决pod可以调度到哪些node的问题
      • podAffinity(pod亲和性) : 以pod为目标,解决pod可以和哪些已存在的pod部署在同一个拓扑域中的问题
      • podAntiAffinity(pod反亲和性) : 以pod为目标,解决pod不能和哪些已存在pod部署在同一个拓扑域中的问题
      NodeAffinity(Node亲和性):

      首先来看一下NodeAffinity的可配置项:

      1. pod.spec.affinity.nodeAffinity
      2.   requiredDuringSchedulingIgnoredDuringExecution  Node节点必须满足指定的所有规则才可以,相当于硬限制
      3.     nodeSelectorTerms  节点选择列表
      4.       matchFields   按节点字段列出的节点选择器要求列表
      5.       matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
      6.         key    键
      7.         values 值
      8.         operator 关系符 支持Exists, DoesNotExist, In, NotIn, Gt, Lt
      9.   preferredDuringSchedulingIgnoredDuringExecution 优先调度到满足指定的规则的Node,相当于软限制 (倾向)
      10.     preference   一个节点选择器项,与相应的权重相关联
      11.       matchFields   按节点字段列出的节点选择器要求列表
      12.       matchExpressions   按节点标签列出的节点选择器要求列表(推荐)
      13.         key    键
      14.         values 值
      15.         operator 关系符 支持In, NotIn, Exists, DoesNotExist, Gt, Lt
      16.     weight 倾向权重,在范围1-100。
      17.     
      18. 关系符使用说明:
      19. - matchExpressions:
      20.   - key: nodeenv              # 匹配存在标签的key为nodeenv的节点
      21.     operator: Exists
      22.   - key: nodeenv              # 匹配标签的key为nodeenv,且value是"xxx"或"yyy"的节点
      23.     operator: In
      24.     values: ["xxx","yyy"]
      25.   - key: nodeenv              # 匹配标签的key为nodeenv,且value大于"xxx"的节点
      26.     operator: Gt
      27.     values: "xxx"

      NodeAffinity规则设置的注意事项:

      • 如果同时定义了nodeSelector和nodeAffinity,那么必须两个条件都得到满足,Pod才能运行在指定的Node上
      • 如果nodeAffinity指定了多个nodeSelectorTerms,那么只需要其中一个能够匹配成功即可
      • 如果一个nodeSelectorTerms中有多个matchExpressions ,则一个节点必须满足所有的才能匹配成功
      • 如果一个pod所在的Node在Pod运行期间其标签发生了改变,不再符合该Pod的节点亲和性需求,则系统将忽略此变化。
      PodAffinity(Pod亲和性):

      PodAffinity主要实现以运行的Pod为参照,实现让新创建的Pod跟参照pod在一个区域的功能。

      首先来看一下PodAffinity的可配置项:

      1. pod.spec.affinity.podAffinity
      2. requiredDuringSchedulingIgnoredDuringExecution 硬限制
      3. namespaces 指定参照pod的namespace
      4. topologyKey 指定调度作用域
      5. labelSelector 标签选择器
      6. matchExpressions 按节点标签列出的节点选择器要求列表(推荐)
      7. key 键
      8. values 值
      9. operator 关系符 支持In, NotIn, Exists, DoesNotExist.
      10. matchLabels 指多个matchExpressions映射的内容
      11. preferredDuringSchedulingIgnoredDuringExecution 软限制
      12. podAffinityTerm 选项
      13. namespaces
      14. topologyKey
      15. labelSelector
      16. matchExpressions
      17. key 键
      18. values 值
      19. operator
      20. matchLabels
      21. weight 倾向权重,在范围1-100

      topologyKey用于指定调度时作用域,例如:

      • 如果指定为kubernetes.io/hostname,那就是以Node节点为区分范围
      • 如果指定为beta.kubernetes.io/os,则以Node节点的操作系统类型来区分
      PodAntiAffinity(Pod非亲和性):

      PodAntiAffinity主要实现以运行的Pod为参照,让新创建的Pod与参照Pod不在一个区域中的功能。

      它的配置方式和选项跟PodAffinty是一样的,这里不再做详细解释。

      污点和容忍:
      污点(Taints):

      前面的调度方式都是站在Pod的角度上,通过在Pod上添加属性,来确定Pod是否要调度到指定的Node上,其实我们也可以站在Node的角度上,通过在Node上添加污点属性,来决定是否允许Pod调度过来。

      Node被设置上污点之后就和Pod之间存在了一种相斥的关系,进而拒绝Pod调度进来,甚至可以将已经存在的Pod驱逐出去。

      污点的格式为:key=value:effect, key和value是污点的标签,effect描述污点的作用,支持如下三个选项:

      • PreferNoSchedule:kubernetes将尽量避免把Pod调度到具有该污点的Node上,除非没有其他节点可调度
      • NoSchedule:kubernetes将不会把Pod调度到具有该污点的Node上,但不会影响当前Node上已存在的Pod
      • NoExecute:kubernetes将不会把Pod调度到具有该污点的Node上,同时也会将Node上已存在的Pod驱离

      使用kubectl设置和去除污点的命令示例如下:

      1. # 设置污点
      2. kubectl taint nodes node1 key=value:effect
      3. # 去除污点
      4. kubectl taint nodes node1 key:effect-
      5. # 去除所有污点
      6. kubectl taint nodes node1 key-
      容忍(Toleration):

      上面介绍了污点的作用,我们可以在node上添加污点用于拒绝pod调度上来,但是如果就是想将一个pod调度到一个有污点的node上去,这时候应该怎么做呢?这就要使用到容忍。

       污点就是拒绝,容忍就是忽略,Node通过污点拒绝pod调度上去,Pod通过容忍忽略拒绝。

      Pod控制器:

      Pod控制器是管理pod的中间层,使用Pod控制器之后,只需要告诉Pod控制器,想要多少个什么样的Pod就可以了,它会创建出满足条件的Pod并确保每一个Pod资源处于用户期望的目标状态。如果Pod资源在运行中出现故障,它会基于指定策略重新编排Pod。

      Pod是kubernetes的最小管理单元,在kubernetes中,按照pod的创建方式可以将其分为两类:

      • 自主式pod:kubernetes直接创建出来的Pod,这种pod删除后就没有了,也不会重建
      • 控制器创建的pod:kubernetes通过控制器创建的pod,这种pod删除了之后还会自动重建

      在kubernetes中,有很多类型的pod控制器,每种都有自己的适合的场景,常见的有下面这些:

      • ReplicationController:比较原始的pod控制器,已经被废弃,由ReplicaSet替代
      • ReplicaSet:保证副本数量一直维持在期望值,并支持pod数量扩缩容,镜像版本升级
      • Deployment:通过控制ReplicaSet来控制Pod,并支持滚动升级、回退版本
      • Horizontal Pod Autoscaler:可以根据集群负载自动水平调整Pod的数量,实现削峰填谷
      • DaemonSet:在集群中的指定Node上运行且仅运行一个副本,一般用于守护进程类的任务
      • Job:它创建出来的pod只要完成任务就立即退出,不需要重启或重建,用于执行一次性任务
      • Cronjob:它创建的Pod负责周期性任务控制,不需要持续后台运行
      • StatefulSet:管理有状态应用

        ReplicaSet(rs):

      ReplicaSet的主要作用是保证一定数量的pod正常运行,它会持续监听这些Pod的运行状态,一旦Pod发生故障,就会重启或重建。同时它还支持对pod数量的扩缩容和镜像版本的升降级。

      ReplicaSet的资源清单文件:

      1. apiVersion: apps/v1 # 版本号
      2. kind: ReplicaSet # 类型
      3. metadata: # 元数据
      4. name: # rs名称
      5. namespace: # 所属命名空间
      6. labels: #标签
      7. controller: rs
      8. spec: # 详情描述
      9. replicas: 3 # 副本数量
      10. selector: # 选择器,通过它指定该控制器管理哪些pod
      11. matchLabels: # Labels匹配规则
      12. app: nginx-pod
      13. matchExpressions: # Expressions匹配规则
      14. - {key: app, operator: In, values: [nginx-pod]}
      15. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
      16. metadata:
      17. labels:
      18. app: nginx-pod
      19. spec:
      20. containers:
      21. - name: nginx
      22. image: nginx:1.17.1
      23. ports:
      24. - containerPort: 80

      在这里面,需要新了解的配置项就是spec下面几个选项:

      • replicas:指定副本数量,其实就是当前rs创建出来的pod的数量,默认为1

      • selector:选择器,它的作用是建立pod控制器和pod之间的关联关系,采用的Label Selector机制,即:在pod模板上定义label,在控制器上定义选择器,就可以表明当前控制器能管理哪些pod了

      • template:模板,就是当前控制器创建pod所使用的模板板,里面其实就是前一章学过的pod的定义

      Deployment(deploy):

      为了更好的解决服务编排的问题,kubernetes在V1.2版本开始,引入了Deployment控制器。值得一提的是,这种控制器并不直接管理pod,而是通过管理ReplicaSet来间接管理Pod,即:Deployment管理ReplicaSet,ReplicaSet管理Pod。所以Deployment比ReplicaSet功能更加强大。

      Deployment主要功能有下面几个:

      • 支持ReplicaSet的所有功能
      • 支持发布的停止、继续
      • 支持滚动升级和回滚版本
      1. apiVersion: apps/v1 # 版本号
      2. kind: Deployment # 类型
      3. metadata: # 元数据
      4. name: # rs名称
      5. namespace: # 所属命名空间
      6. labels: #标签
      7. controller: deploy
      8. spec: # 详情描述
      9. replicas: 3 # 副本数量
      10. revisionHistoryLimit: 3 # 保留历史版本
      11. paused: false # 暂停部署,默认是false
      12. progressDeadlineSeconds: 600 # 部署超时时间(s),默认是600
      13. strategy: # 策略
      14. type: RollingUpdate # 滚动更新策略
      15. rollingUpdate: # 滚动更新
      16. maxSurge: 30% # 最大额外可以存在的副本数,可以为百分比,也可以为整数
      17. maxUnavailable: 30% # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
      18. selector: # 选择器,通过它指定该控制器管理哪些pod
      19. matchLabels: # Labels匹配规则
      20. app: nginx-pod
      21. matchExpressions: # Expressions匹配规则
      22. - {key: app, operator: In, values: [nginx-pod]}
      23. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
      24. metadata:
      25. labels:
      26. app: nginx-pod
      27. spec:
      28. containers:
      29. - name: nginx
      30. image: nginx:1.17.1
      31. ports:
      32. - containerPort: 80

      Horizontal Pod Autoscaler(hpa):

      在前面的课程中,我们已经可以实现通过手工执行kubectl scale命令实现Pod扩容或缩容,但是这显然不符合Kubernetes的定位目标--自动化、智能化。 Kubernetes期望可以实现通过监测Pod的使用情况,实现pod数量的自动调整,于是就产生了Horizontal Pod Autoscaler(HPA)这种控制器。

      HPA可以获取每个Pod利用率,然后和HPA中定义的指标进行对比,同时计算出需要伸缩的具体值,最后实现Pod的数量的调整。其实HPA与之前的Deployment一样,也属于一种Kubernetes资源对象,它通过追踪分析RC控制的所有目标Pod的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数,这是HPA的实现原理。

       

       DaemonSet(DS):

      DaemonSet类型的控制器可以保证在集群中的每一台(或指定)节点上都运行一个副本。一般适用于日志收集、节点监控等场景。也就是说,如果一个Pod提供的功能是节点级别的(每个节点都需要且只需要一个),那么这类Pod就适合使用DaemonSet类型的控制器创建。

       

       DaemonSet控制器的特点:

      • 每当向集群中添加一个节点时,指定的 Pod 副本也将添加到该节点上
      • 当节点从集群中移除时,Pod 也就被垃圾回收了

      DaemonSet的资源清单文件:

      1. apiVersion: apps/v1 # 版本号
      2. kind: DaemonSet # 类型
      3. metadata: # 元数据
      4. name: # rs名称
      5. namespace: # 所属命名空间
      6. labels: #标签
      7. controller: daemonset
      8. spec: # 详情描述
      9. revisionHistoryLimit: 3 # 保留历史版本
      10. updateStrategy: # 更新策略
      11. type: RollingUpdate # 滚动更新策略
      12. rollingUpdate: # 滚动更新
      13. maxUnavailable: 1 # 最大不可用状态的 Pod 的最大值,可以为百分比,也可以为整数
      14. selector: # 选择器,通过它指定该控制器管理哪些pod
      15. matchLabels: # Labels匹配规则
      16. app: nginx-pod
      17. matchExpressions: # Expressions匹配规则
      18. - {key: app, operator: In, values: [nginx-pod]}
      19. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
      20. metadata:
      21. labels:
      22. app: nginx-pod
      23. spec:
      24. containers:
      25. - name: nginx
      26. image: nginx:1.17.1
      27. ports:
      28. - containerPort: 80

      Job:

      Job主要用于负责批量处理(一次要处理指定数量任务)短暂的一次性(每个任务仅运行一次就结束)任务。Job特点如下:

      • 当Job创建的pod执行成功结束时,Job将记录成功结束的pod数量
      • 当成功结束的pod达到指定的数量时,Job将完成执行

      Job的资源清单文件:

      1. apiVersion: batch/v1 # 版本号
      2. kind: Job # 类型
      3. metadata: # 元数据
      4. name: # rs名称
      5. namespace: # 所属命名空间
      6. labels: #标签
      7. controller: job
      8. spec: # 详情描述
      9. completions: 1 # 指定job需要成功运行Pods的次数。默认值: 1
      10. parallelism: 1 # 指定job在任一时刻应该并发运行Pods的数量。默认值: 1
      11. activeDeadlineSeconds: 30 # 指定job可运行的时间期限,超过时间还未结束,系统将会尝试进行终止。
      12. backoffLimit: 6 # 指定job失败后进行重试的次数。默认是6
      13. manualSelector: true # 是否可以使用selector选择器选择pod,默认是false
      14. selector: # 选择器,通过它指定该控制器管理哪些pod
      15. matchLabels: # Labels匹配规则
      16. app: counter-pod
      17. matchExpressions: # Expressions匹配规则
      18. - {key: app, operator: In, values: [counter-pod]}
      19. template: # 模板,当副本数量不足时,会根据下面的模板创建pod副本
      20. metadata:
      21. labels:
      22. app: counter-pod
      23. spec:
      24. restartPolicy: Never # 重启策略只能设置为Never或者OnFailure
      25. containers:
      26. - name: counter
      27. image: busybox:1.30
      28. command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 2;done"]

        CronJob(cj):

      CronJob控制器以Job控制器资源为其管控对象,并借助它管理pod资源对象,Job控制器定义的作业任务在其控制器资源创建之后便会立即执行,但CronJob可以以类似于Linux操作系统的周期性任务作业计划的方式控制其运行时间点及重复运行的方式。也就是说,CronJob可以在特定的时间点(反复的)去运行job任务。

      CronJob的资源清单文件:

      1. apiVersion: batch/v1beta1 # 版本号
      2. kind: CronJob # 类型
      3. metadata: # 元数据
      4. name: # rs名称
      5. namespace: # 所属命名空间
      6. labels: #标签
      7. controller: cronjob
      8. spec: # 详情描述
      9. schedule: # cron格式的作业调度运行时间点,用于控制任务在什么时间执行
      10. concurrencyPolicy: # 并发执行策略,用于定义前一次作业运行尚未完成时是否以及如何运行后一次的作业
      11. failedJobHistoryLimit: # 为失败的任务执行保留的历史记录数,默认为1
      12. successfulJobHistoryLimit: # 为成功的任务执行保留的历史记录数,默认为3
      13. startingDeadlineSeconds: # 启动作业错误的超时时长
      14. jobTemplate: # job控制器模板,用于为cronjob控制器生成job对象;下面其实就是job的定义
      15. metadata:
      16. spec:
      17. completions: 1
      18. parallelism: 1
      19. activeDeadlineSeconds: 30
      20. backoffLimit: 6
      21. manualSelector: true
      22. selector:
      23. matchLabels:
      24. app: counter-pod
      25. matchExpressions: 规则
      26. - {key: app, operator: In, values: [counter-pod]}
      27. template:
      28. metadata:
      29. labels:
      30. app: counter-pod
      31. spec:
      32. restartPolicy: Never
      33. containers:
      34. - name: counter
      35. image: busybox:1.30
      36. command: ["bin/sh","-c","for i in 9 8 7 6 5 4 3 2 1; do echo $i;sleep 20;done"]

       Service:

      在kubernetes中,pod是应用程序的载体,我们可以通过pod的ip来访问应用程序,但是pod的ip地址不是固定的,这也就意味着不方便直接采用pod的ip对服务进行访问。

      为了解决这个问题,kubernetes提供了Service资源,Service会对提供同一个服务的多个pod进行聚合,并且提供一个统一的入口地址。通过访问Service的入口地址就能访问到后面的pod服务。

      Service在很多情况下只是一个概念,真正起作用的其实是kube-proxy服务进程,每个Node节点上都运行着一个kube-proxy服务进程。当创建Service的时候会通过api-server向etcd写入创建的service的信息,而kube-proxy会基于监听的机制发现这种Service的变动,然后它会将最新的Service信息转换成对应的访问规则。

       kube-proxy工作模式:

       kube-proxy目前支持三种工作模式:

      • userspace 模式
      • iptables 模式
      • ipvs 模式
      userspace 模式:

      userspace模式下,kube-proxy会为每一个Service创建一个监听端口,发向Cluster IP的请求被Iptables规则重定向到kube-proxy监听的端口上,kube-proxy根据LB算法选择一个提供服务的Pod并和其建立链接,以将请求转发到Pod上。

      该模式下,kube-proxy充当了一个四层负责均衡器的角色。由于kube-proxy运行在userspace中,在进行转发处理时会增加内核和用户空间之间的数据拷贝,虽然比较稳定,但是效率比较低。

      iptables 模式:

      iptables模式下,kube-proxy为service后端的每个Pod创建对应的iptables规则,直接将发向Cluster IP的请求重定向到一个Pod IP。

      该模式下kube-proxy不承担四层负责均衡器的角色,只负责创建iptables规则。该模式的优点是较userspace模式效率更高,但不能提供灵活的LB策略,当后端Pod不可用时也无法进行重试。

      ipvs 模式:

      ipvs模式和iptables类似,kube-proxy监控Pod的变化并创建相应的ipvs规则。ipvs相对iptables转发效率更高。除此以外,ipvs支持更多的LB算法。

       Service类型:

      Service的资源清单文件:

      1. kind: Service # 资源类型
      2. apiVersion: v1 # 资源版本
      3. metadata: # 元数据
      4. name: service # 资源名称
      5. namespace: dev # 命名空间
      6. spec: # 描述
      7. selector: # 标签选择器,用于确定当前service代理哪些pod
      8. app: nginx
      9. type: # Service类型,指定service的访问方式(ClusterIP、NodePort、LoadBalancer、ExternalName)
      10. clusterIP: # 虚拟服务的ip地址
      11. sessionAffinity: # session亲和性,支持ClientIP、None两个选项
      12. ports: # 端口信息
      13. - protocol: TCP
      14. port: 3017 # service端口
      15. targetPort: 5003 # pod端口
      16. nodePort: 31122 # 主机端口

       Service常用类型:

      • ClusterIP:默认值,它是Kubernetes系统自动分配的虚拟IP,只能在集群内部访问
      • HeadLiness:不需要或不想要负载均衡,以及单独的 Service IP
      • NodePort:将Service通过指定的Node上的端口暴露给外部,通过此方法,就可以在集群外部访问服务
      • LoadBalancer:使用外接负载均衡器完成到服务的负载分发,注意此模式需要外部云环境支持
      • ExternalName: 把集群外部的服务引入集群内部,直接使用
      ClusterIP类型的Service:

      Endpoint是kubernetes中的一个资源对象,存储在etcd中,用来记录一个service对应的所有pod的访问地址,它是根据service配置文件中selector描述产生的。

      一个Service由一组Pod组成,这些Pod通过Endpoints暴露出来,Endpoints是实现实际服务的端点集合。换句话说,service和pod之间的联系是通过endpoints实现的。

       负载分发策略:

      对Service的访问被分发到了后端的Pod上去,目前kubernetes提供了两种负载分发策略:

      • 如果不定义,默认使用kube-proxy的策略,比如随机、轮询
      • 基于客户端地址的会话保持模式,即来自同一个客户端发起的所有请求都会转发到固定的一个Pod上。此模式可以使在spec中添加sessionAffinity:ClientIP选项
       HeadLiness类型的Service:

      在某些场景中,开发人员可能不想使用Service提供的负载均衡功能,而希望自己来控制负载均衡策略,针对这种情况,kubernetes提供了HeadLiness Service,这类Service不会分配Cluster IP,如果想要访问service,只能通过service的域名进行查询。

      NodePort类型的Service:

      在之前的样例中,创建的Service的ip地址只有集群内部才可以访问,如果希望将Service暴露给集群外部使用,那么就要使用到另外一种类型的Service,称为NodePort类型。NodePort的工作原理其实就是将service的端口映射到Node的一个端口上,然后就可以通过NodeIP:NodePort来访问service了。

       LoadBalancer类型的Service:

      LoadBalancer和NodePort很相似,目的都是向外部暴露一个端口,区别在于LoadBalancer会在集群的外部再来做一个负载均衡设备,而这个设备需要外部环境支持的,外部服务发送到这个设备上的请求,会被设备负载之后转发到集群中。

      ExternalName类型的Service:

      ExternalName类型的Service用于引入集群外部的服务,它通过externalName属性指定外部一个服务的地址,然后在集群内部访问此service就可以访问到外部的服务了。

       Ingress:

      前面已经提到,Service对集群之外暴露服务的主要方式有两种:NotePort和LoadBalancer,但是这两种方式,都有一定的缺点:

      • NodePort方式的缺点是会占用很多集群机器的端口,那么当集群服务变多的时候,这个缺点就愈发明显
      • LB方式的缺点是每个service需要一个LB,浪费、麻烦,并且需要kubernetes之外设备的支持

      基于这种现状,kubernetes提供了Ingress资源对象,Ingress只需要一个NodePort或者一个LB就可以满足暴露多个Service的需求。工作机制大致如下图表示:

      实际上,Ingress相当于一个7层的负载均衡器,是kubernetes对反向代理的一个抽象,它的工作原理类似于Nginx,可以理解成在Ingress里建立诸多映射规则,Ingress Controller通过监听这些配置规则并转化成Nginx的反向代理配置 , 然后对外部提供服务。在这里有两个核心概念:

      • ingress:kubernetes中的一个对象,作用是定义请求如何转发到service的规则
      • ingress controller:具体实现反向代理及负载均衡的程序,对ingress定义的规则进行解析,根据配置的规则来实现请求转发,实现方式有很多,比如Nginx, Contour, Haproxy等等

      Ingress(以Nginx为例)的工作原理如下:

      1. 用户编写Ingress规则,说明哪个域名对应kubernetes集群中的哪个Service
      2. Ingress控制器动态感知Ingress服务规则的变化,然后生成一段对应的Nginx反向代理配置
      3. Ingress控制器会将生成的Nginx配置写入到一个运行着的Nginx服务中,并动态更新
      4. 到此为止,其实真正在工作的就是一个Nginx了,内部配置了用户定义的请求转发规则

      Volume:

      在前面已经提到,容器的生命周期可能很短,会被频繁地创建和销毁。那么容器在销毁时,保存在容器中的数据也会被清除。这种结果对用户来说,在某些情况下是不乐意看到的。为了持久化保存容器的数据,Kubernetes引入了Volume的概念。

      Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下,Kubernetes通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。Volume的生命周期不与Pod中单个容器的生命周期相关,当容器终止或者重启时,Volume中的数据也不会丢失。

      Kubernetes的Volume支持多种类型,比较常见的有下面几个:

      • 简单存储:EmptyDir、HostPath、NFS
      • 高级存储:PV、PVC
      • 配置存储:ConfigMap、Secret

       基本存储:

      EmptyDir:

      EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一个空目录。

      EmptyDir是在Pod被分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为Kubernetes会自动分配一个目录,当Pod销毁时, EmptyDir中的数据也会被永久删除。 EmptyDir用途如下:

      • 临时空间,例如用于某些应用程序运行时所需的临时目录,且无须永久保留
      • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
      HostPath:

      EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。

      HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据依据可以存在于Node主机上。

      NFS:

      HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比较常用的用NFS、CIFS。

      NFS是一个网络文件存储系统,可以搭建一台NFS服务器,然后将Pod中的存储直接连接到NFS系统上,这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。

       高级存储:

      前面已经学习了使用NFS提供存储,此时就要求用户会搭建NFS系统,并且会在yaml配置nfs。由于Kubernetes支持的存储系统有很多,要求客户全都掌握,显然不现实。为了能够屏蔽底层存储实现的细节,方便用户使用, Kubernetes引入PV和PVC两种资源对象。

      PV(Persistent Volume)是持久化卷的意思,是对底层的共享存储的一种抽象。一般情况下PV由Kubernetes管理员进行创建和配置,它与底层具体的共享存储技术有关,并通过插件完成与共享存储的对接。

      PVC(Persistent Volume Claim)是持久卷声明的意思,是用户对于存储需求的一种声明。换句话说,PVC其实就是用户向Kubernetes系统发出的一种资源需求申请。

      使用了PV和PVC之后,工作可以得到进一步的细分:

      • 存储:存储工程师维护
      • PV: Kubernetes管理员维护
      • PVC:Kubernetes用户维护
       PV:

      PV是存储资源的抽象,下面是资源清单文件:

      1. apiVersion: v1
      2. kind: PersistentVolume
      3. metadata:
      4. name: pv2
      5. spec:
      6. nfs: # 存储类型,与底层真正存储对应
      7. capacity: # 存储能力,目前只支持存储空间的设置
      8. storage: 2Gi
      9. accessModes: # 访问模式
      10. storageClassName: # 存储类别
      11. persistentVolumeReclaimPolicy: # 回收策略

       PV 的关键配置参数说明:

      • 存储类型

        底层实际存储的类型,Kubernetes支持多种存储类型,每种存储类型的配置都有所差异

      • 存储能力(capacity)

      目前只支持存储空间的设置( storage=1Gi ),不过未来可能会加入IOPS、吞吐量等指标的配置

      • 访问模式(accessModes)

        用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

        • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
        • ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载
        • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

        需要注意的是,底层不同的存储类型可能支持的访问模式不同

      • 回收策略(persistentVolumeReclaimPolicy)

        当PV不再被使用了之后,对其的处理方式。目前支持三种策略:

        • Retain (保留) 保留数据,需要管理员手工清理数据
        • Recycle(回收) 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
        • Delete (删除) 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务

        需要注意的是,底层不同的存储类型可能支持的回收策略不同

      • 存储类别

        PV可以通过storageClassName参数指定一个存储类别

        • 具有特定类别的PV只能与请求了该类别的PVC进行绑定
        • 未设定类别的PV则只能与不请求任何类别的PVC进行绑定
      • 状态(status)

        一个 PV 的生命周期中,可能会处于4中不同的阶段:

        • Available(可用): 表示可用状态,还未被任何 PVC 绑定
        • Bound(已绑定): 表示 PV 已经被 PVC 绑定
        • Released(已释放): 表示 PVC 被删除,但是资源还未被集群重新声明
        • Failed(失败): 表示该 PV 的自动回收失败
       PVC:

      PVC是资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。下面是资源清单文件:

      1. apiVersion: v1
      2. kind: PersistentVolumeClaim
      3. metadata:
      4. name: pvc
      5. namespace: dev
      6. spec:
      7. accessModes: # 访问模式
      8. selector: # 采用标签对PV选择
      9. storageClassName: # 存储类别
      10. resources: # 请求空间
      11. requests:
      12. storage: 5Gi

       PVC 的关键配置参数说明:

      • 访问模式(accessModes):用于描述用户应用对存储资源的访问权限
      • 选择条件(selector):通过Label Selector的设置,可使PVC对于系统中己存在的PV进行筛选

      • 存储类别(storageClassName):PVC在定义时可以设定需要的后端存储的类别,只有设置了该class的pv才能被系统选出

      • 资源请求(Resources ):描述对存储资源的请求

      声明周期:

      PVC和PV是一一对应的,PV和PVC之间的相互作用遵循以下生命周期:

      • 资源供应:管理员手动创建底层存储和PV
      • 资源绑定:用户创建PVC,kubernetes负责根据PVC的声明去寻找PV,并绑定。

      在用户定义好PVC之后,系统将根据PVC对存储资源的请求在已存在的PV中选择一个满足条件的

      • 一旦找到,就将该PV与用户定义的PVC进行绑定,用户的应用就可以使用这个PVC了
      • 如果找不到,PVC则会无限期处于Pending状态,直到等到系统管理员创建了一个符合其要求的PV

      PV一旦绑定到某个PVC上,就会被这个PVC独占,不能再与其他PVC进行绑定了。

      • 资源使用:用户可在pod中像volume一样使用pvc

      Pod使用Volume的定义,将PVC挂载到容器内的某个路径进行使用。

      • 资源释放:用户删除pvc来释放pv

      当存储资源使用完毕后,用户可以删除PVC,与该PVC绑定的PV将会被标记为“已释放”,但还不能立刻与其他PVC进行绑定。通过之前PVC写入的数据可能还被留在存储设备上,只有在清除之后该PV才能再次使用。

      • 资源回收:Kubernetes根据pv设置的回收策略进行资源的回收

      对于PV,管理员可以设定回收策略,用于设置与之绑定的PVC释放资源之后如何处理遗留数据的问题。只有PV的存储空间完成回收,才能供新的PVC绑定和使用

       配置存储:

      ConfigMap:

      ConfigMap是一种比较特殊的存储卷,它的主要作用是用来存储配置信息的。

      Secret:

      在kubernetes中,还存在一种和ConfigMap非常类似的对象,称为Secret对象。它主要用于存储敏感信息,例如密码、秘钥、证书等等。

      资源管理: 

       在Kubernetes中所有内容都抽象为资源,用户通过操作资源来管理Kubernetes。

      资源管理方式:

      • 命令式对象管理:直接使用命令去操作Kubernetes资源
      • 命令式对象配置:通过命令配置和配置文件去操作Kubernetes资源
      • 声明对象配置:通过apply命令和配置文件去操作Kubernetes资源(创建和更新)
      类型操作对象适用环境优点缺点
      命令式对象管理对象测试简单只能操作活动对象,无法审计、跟踪
      命令式对象配置文件开发可以审计、跟踪项目大时,配置文件多,操作麻烦
      声明对象配置目录开发支持目录操作意外情况下难以调试

      命令式对象管理:

      kubectl是Kubernetes集群的命令行工具,通过它能够对集群本身进行管理,并能够在集群上进行容器化应用的安装部署。kubectl命令的语法如下:

      kubectl [command] [type] [name] [flags]

      command:指定要对资源执行的操作,例如create、get、delete
      type:指定资源类型,比如deployment、pod、service
      name:指定资源的名称,名称大小写敏感
      flags:指定额外的可选参数

      1. # 查看所有pod
      2. kubectl get pod
      3. # 查看某个pod
      4. kubectl get pod [pod_name]
      5. # 查看某个pod,以yaml格式展示结果
      6. kubectl get pod [pod_name] -o yaml
      7. # 查看某个pod,展示详细信息
      8. kubectl get pod [pod_name] -o wide

      (1)command:

      基本命令:

      命令作用
      create创建一个资源
      edit编辑一个资源
      get获取一个资源
      patch更新一个资源
      delete删除一个资源
      explain解释一个资源

      运行和调试:

      命令作用
      run在集群中运行一个指定镜像
      expose暴露资源给Service
      describe显示资源内部信息
      logs输出容器在Pod中的日志
      attach进入运行中的容器
      exec执行容器中的一个命令
      cp在Pod内外复制文件
      rollout管理资源的发布
      scale扩缩容Pod的数量
      autoscale自动调整Pod的数量

      高级命令:

      命令作用
      apply通过文件对资源进行管理
      label更新资源上的标签

      其它命令:

      命令作用
      cluster-info显示集群信息
      version显示当前Server和Client的版本

      (2) type:

      Kubernetes中资源类型繁多,下面列举一下常用的资源,如果有需要可以通过下列命令查看资源列表:

      kubectl api-resources

      集群级别资源:

      资源名称缩写资源作用
      nodesno集群组成部分
      namespacens隔离Pod

      Pod资源:

      资源名称缩写资源作用
      podspo装载容器

      Pod资源控制器:

      资源名称缩写资源作用
      replicationcontrollersrc控制Pod资源
      replicasetsrs控制Pod资源
      deploymentsds控制Pod资源
      daemonsetsds控制Pod资源
      jobs控制Pod资源
      cronjobscj控制Pod资源
      horizontalpodautoscalershpa控制Pod资源
      statefulsetssts控制Pod资源

      服务发现资源:

      资源名称缩写资源作用
      servicessvc统一Pod对外接口
      ingressing统一Pod对外接口

      存储资源:

      资源名称缩写资源作用
      volumeattachments存储
      persistentvolumespv存储
      persistentvolumeclaimspvc存储

      配置资源:

      资源名称缩写资源作用
      configmapscm配置
      secrets配置

       下面以一个namespace、pod的创建和删除简单演示下列命令的使用:

      1. # 创建一个namespace
      2. kubectl create namespace dev
      3. # 获取namesapce
      4. kubectl get ns
      5. # 在dev namespace下创建并运行一个nginxPod
      6. kubectl run pod --image=nginx -n dev
      7. # 查看新建的Pod (只要不是对default命名空间下资源的操作都需要加上-n参数,指明操作的命名空间)
      8. Kubectl get pod -n dev
      9. NAME READY STATUS RESTARTS AGE
      10. pod-864f9875b9-2vdfq 1/1 Running 0 60s
      11. # 删除指定Pod
      12. kubectl delete pods pod-864f9875b9-2vdfq -n dev
      13. # 删除指定namespace
      14. kubectl delete ns dev

      命令式对象配置:

      命令式对象配置就是使用命令配合配置文件一起来操作Kubernetes资源。

      (1)创建一个nginxpod.yaml,内容如下:

      1. apiVersion: v1
      2. kind: Namespace
      3. metadata:
      4. name: dev
      5. ---
      6. apiVersion: v1
      7. kind: Pod
      8. metadata:
      9. name: nginxpod
      10. namespace: dev
      11. spec:
      12. containers:
      13. - name: nginx-containers
      14. image: nginx:1.17.1

      (2)执行create命令,创建资源:

      1. [root@node01 ~]# kubectl create -f nginxpod.yaml
      2. namespace/dev created
      3. pod/nginxpod created

      此时发现创建了两个资源,分别是namespace和pod

      (3)执行get命令查看资源:

      1. [root@node01 ~]# kubectl get -f nginxpod.yaml
      2. NAME STATUS AGE
      3. namespace/dev Active 2m56s
      4. NAME READY STATUS RESTARTS AGE
      5. pod/nginxpod 1/1 Running 0 2m55s

      (4)删除资源:

      1. [root@node01 ~]# kubectl delete -f nginxpod.yaml
      2. namespace "dev" deleted
      3. pod "nginxpod" deleted

      声明式对象配置:

      声明式对象配置与命令式对象配置很相似,但是它只有一个命令apply,主要用于资源的创建与更新。

      1. # 首次执行一次kubectl apply -f nginxpod.yaml文件,代表创建资源
      2. [root@node01 ~]# kubectl apply -f nginxpod.yaml
      3. namespace/dev created
      4. pod/nginxpod created
      5. # 第二次执行kubectl apply -f nginxpod.yaml文件,代表更新配置,但是由于配置文件内容并没有改变,所以会显示
      6. namespace/dev unchanged
      7. pod/nginxpod unchanged

      其实声明式对象配置就是使用apply描述一个资源的最终状态(在yaml文件中定义)。如果描述的资源不存在,则创建该资源;如果描述的资源存在,则尝试更新该资源。

      Node节点运行kubectl

      默认情况下只能在Master节点上运行kubectl命令,我们需要将Master节点~/.kube文件夹复制到Node节点上才能在Node节点上运行该命令:

      1. # 将家目录下,kube文件夹拷贝到node02节点的家目录下(这里写node02是因为hosts中配置node02的IP)
      2. scp ~/.kube/ node02:~/

      常见资源的基础操作:

      Namespace:

      Namespace是kubernetes系统中的一种非常重要资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。

      默认情况下,kubernetes集群中的所有的Pod都是可以相互访问的。但是在实际中,可能不想让两个Pod之间进行互相的访问,那此时就可以将两个Pod划分到不同的namespace下。kubernetes通过将集群内部的资源分配到不同的Namespace中,可以形成逻辑上的“组”,以方便不同的组的资源进行隔离使用和管理。

      可以通过kubernetes的授权机制,将不同的namespace交给不同租户进行管理,这样就实现了多租户的资源隔离。此时还能结合kubernetes的资源配额机制,限定不同租户能占用的资源,例如CPU使用量、内存使用量等等,来实现租户可用资源的管理。

      在集群启动后,会有几个默认的namespace:

      1. [root@node01 ~]# kubectl get namespace
      2. NAME STATUS AGE
      3. default Active 18h #所有未指定namespace的资源都会被分配到default命名空间中
      4. kube-node-lease Active 18h #集群节点间的心跳维护,从v1.13开始引入
      5. kube-public Active 18h #此命名空间下的资源可以被所有人访问(包括未认证用户)
      6. kube-system Active 18h #所有由Kubernetes系统创建的资源都处于这个命名空间

       下面来看Namespace资源的具体操作:

      查看namespace:

      1. kubectl get namespace
      2. kubectl get ns
      3. #查看指定Namespace
      4. kubectl get ns [名称]
      5. #指定输出格式
      6. kubectl get ns [名称] -o [wide|json|yaml]
      7. #查看namespace详情
      8. kubectl describe ns [名称]
      9. [root@node01 ~]# kubectl describe ns kube-system
      10. Name: kube-system
      11. Labels:
      12. Annotations:
      13. Status: Active #Active 表示命名空间正在使用,Terminating表示正在删除该命名空间
      14. No resource quota.#resource quota 针对命名空间做的资源限制
      15. No LimitRange resource. #LimitRange针对命名空间中的每个组件做资源限制

      新增namespace:kubectl create ns [名称]
      删除namespace:kubectl delete ns [名称]
      声明式配置
      首先准备一个yaml文件:ns-dev.yaml 

      apiVersion: v1
      kind: Namespace
      metadata: 
        name: dev

      Pod:

      Pod是kubernetes集群进行管理的最小单元,程序要运行必须部署在容器中,而容器必须存在于Pod中Pod可以认为是容器的封装,一个Pod中可以存在一个或者多个容器。

      Kubernetes在集群启动之后,集群中的各个组件也都是以Pod方式运行的。

      创建并运行

      Kubernetes没有提供单独运行Pod的命令,都是通过Pod控制器来实现的

      1. # 命令格式:kubectl run [pod控制器名称] [参数]
      2. # --image 镜像版本
      3. # --port 暴露端口
      4. # --namespace 命名空间
      5. kubectl run nginx --image=nginx:1.17.1 --port=80 --namespace=dev

      查看Pod信息:

      查看Pod基本信息:kubectl get pod -n dev
      查看Pod详细信息:kubectl describe pod nginx-64777cd554-8rdmn -n dev

      访问Pod:

      获取IP:kubectl get pod -n dev -o wide
      访问Pod:curl 10.244.2.5:80

      删除Pod:

      删除指定Pod:kubectl delete pod nginx-64777cd554-8rdmn -n dev
      删除Pod控制器:kubectl delete deployment nginx -n dev

      Label:

       Label是kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识,用来对它们进行区分和选择。Label的特点:

      • 一个Label会以kev/value键值对的形式附加到各种对象上,如Node、Pod、Service等等
      • 一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上去
      • Label通常在资源对象定义时确定,当然也可以在对象创建后动态添加或者删除

      可以通过Label实现资源的多维度分组,以便方便灵活地进行资源分配、调度、配置、部署等管理工作。

       一些常用的Label示例如下:

      • 版本标签:“version”:”release”,“version”:”stable”
      • 环境标签:“env”:”dev”,“env”:”test”

      标签定义完毕之后,还要考虑到标签的选择,这就是Label Selector。Label Selector用于查询和筛选拥有某些标签的资源对象。 

      为Pod打标签:

      1. # 命令格式:kubectl label pod [pod名称] [-n 命名空间] [key=value]
      2. [root@node01 ~]# kubectl label pod nginx-64777cd554-vblj9 -n dev version=1.0
      3. pod/nginx-64777cd554-vblj9 labeled

      查看Pod上的标签:

      1. [root@node01 ~]# kubectl get pod -n dev --show-labels
      2. NAME READY STATUS RESTARTS AGE LABELS
      3. nginx-64777cd554-vblj9 1/1 Running 0 6m7s pod-template-hash=64777cd554,run=nginx,version=1.0

      更新标签:

      kubectl label pod nginx-64777cd554-vblj9 -n dev version=1.0 --overwrite

      删除标签:

      1. # 在key后面紧跟一个-号即可删除标签
      2. [root@node01 ~]# kubectl label pod nginx-64777cd554-vblj9 -n dev version-
      3. pod/nginx-64777cd554-vblj9 labeled

      通过标签筛选(Label Selector):

       通过Label进行筛选我们需要使用到Label Selector,当前有两种Label Selector:

      • 基于等式的Label Selector

      name=slave:选择所有Label信息中包含key=“name”且value=“slave”的资源对象。

      env!=production:选择所有Label信息中存在key为“env”,但是这个Label的value不等于production的资源对象。

      • 基于集合的Label Selector

      name in (master,slave)

      env not in (production,test)

      标签选择器可以同时使用多个,多个Label Selector之间使用逗号分割:

      name=slave,env not in (production,test)

      1. #使用-l参数指定标签信息进行筛选
      2. kubel get pod -l "version=1.0" -n dev --show-labels
      3. #删除指定标签的Pod
      4. [root@node01 ~]# kubectl delete pod -l env=test -n dev
      5. pod "nginx-64777cd554-vblj9" deleted
      6. pod "nginx1-79d7bd676b-4tjpt" deleted

       声明式配置:

      1. apiVersion: apps/v1
      2. kind: Pod
      3. metadata:
      4. name: nginx
      5. namespace: dev
      6. labels:
      7. version: "3.0"
      8. env: "test"

      Service:

      虽然每个Pod都会分配一个单独的IP,然而却存在如下两个问题:

      Pod IP会随着Pod的重建而发生变化
      Pod IP仅仅是集群内可见的虚拟IP,外部无法访问
      这样外部如果需要访问这个服务就会非常困难。因此Kubernetes设计了Service来解决这个问题。

      Service可以看做是一组同类Pod对外提供的访问接口。接祖Service,应用可以方便地实现服务发现和负载均衡。

      创建Service:

      1. #命令格式:kubectl expose deployment [Pod控制器名称] --name=[service名称] --type-ClusterIP --port=[service端口] --target-port=[目标端口] -n [命名空间]
      2. [root@node01 ~]# kubectl expose deployment nginx --name=svc-nginx --type=ClusterIP --port=80 --target-port=80 -n dev
      3. service/svc-nginx exposed

      查看Service:

      1. [root@node01 ~]# kubectl get service -n dev
      2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
      3. svc-nginx ClusterIP 10.109.216.54 80/TCP 37s

      删除Service:

      1. [root@node01 ~]# kubectl delete service svc-nginx -n dev
      2. service "svc-nginx" deleted

      声明式配置:

      1. apiVersion: apps/v1
      2. kind: Service
      3. metadata:
      4. name: svc-nginx
      5. namespace: dev
      6. spec:
      7. clusterIP: 10.109.179.231 #IP可省略,省略后会自动申请
      8. ports:
      9. - port: 80
      10. protocol: TCP
      11. targetPort: 80
      12. selector:
      13. run: nginx
      14. type: ClusterIP

      安全认证:

      Kubernetes作为一个分布式集群的管理工具,保证集群的安全性是其一个重要的任务。所谓的安全性其实就是保证对Kubernetes的各种客户端进行认证和鉴权操作。

      客户端:

      在Kubernetes集群中,客户端通常有两类:

      • User Account:一般是独立于kubernetes之外的其他服务管理的用户账号。
      • Service Account:kubernetes管理的账号,用于为Pod中的服务进程在访问Kubernetes时提供身份标识。

      认证、授权与准入控制:

      ApiServer是访问及管理资源对象的唯一入口。任何一个请求访问ApiServer,都要经过下面三个流程:

      • Authentication(认证):身份鉴别,只有正确的账号才能够通过认证
      • Authorization(授权): 判断用户是否有权限对访问的资源执行特定的动作
      • Admission Control(准入控制):用于补充授权机制以实现更加精细的访问控制功能。

      认证管理:

      Kubernetes集群安全的最关键点在于如何识别并认证客户端身份,它提供了3种客户端身份认证方式:

      • HTTP Base认证:通过用户名+密码的方式认证

      这种认证方式是把“用户名:密码”用BASE64算法进行编码后的字符串放在HTTP请求中的Header Authorization域里发送给服务端。服务端收到后进行解码,获取用户名及密码,然后进行用户身份认证的过程。

      • HTTP Token认证:通过一个Token来识别合法用户

      这种认证方式是用一个很长的难以被模仿的字符串--Token来表明客户身份的一种方式。每个Token对应一个用户名,当客户端发起API调用请求时,需要在HTTP Header里放入Token,API Server接到Token后会跟服务器中保存的token进行比对,然后进行用户身份认证的过程。

      • HTTPS证书认证:基于CA根证书签名的双向数字证书认证方式

      这种认证方式是安全性最高的一种方式,但是同时也是操作起来最麻烦的一种方式。

       授权管理:

      授权发生在认证成功之后,通过认证就可以知道请求用户是谁, 然后Kubernetes会根据事先定义的授权策略来决定用户是否有权限访问,这个过程就称为授权。

      每个发送到ApiServer的请求都带上了用户和资源的信息:比如发送请求的用户、请求的路径、请求的动作等,授权就是根据这些信息和授权策略进行比较,如果符合策略,则认为授权通过,否则会返回错误。

      API Server目前支持以下几种授权策略:

      • AlwaysDeny:表示拒绝所有请求,一般用于测试
      • AlwaysAllow:允许接收所有请求,相当于集群不需要授权流程(Kubernetes默认的策略)
      • ABAC:基于属性的访问控制,表示使用用户配置的授权规则对用户请求进行匹配和控制
      • Webhook:通过调用外部REST服务对用户进行授权
      • Node:是一种专用模式,用于对kubelet发出的请求进行访问控制
      • RBAC:基于角色的访问控制(kubeadm安装方式下的默认选项)

      RBAC(Role-Based Access Control) 基于角色的访问控制,主要是在描述一件事情:给哪些对象授予了哪些权限

      其中涉及到了下面几个概念:

      • 对象:User、Groups、ServiceAccount
      • 角色:代表着一组定义在资源上的可操作动作(权限)的集合
      • 绑定:将定义好的角色跟用户绑定在一起

      RBAC引入了4个顶级资源对象:

      • Role、ClusterRole:角色,用于指定一组权限
      • RoleBinding、ClusterRoleBinding:角色绑定,用于将角色(权限)赋予给对象

       Role、ClusterRole:

      一个角色就是一组权限的集合,这里的权限都是许可形式的(白名单)。

      1. # Role只能对命名空间内的资源进行授权,需要指定nameapce
      2. kind: Role
      3. apiVersion: rbac.authorization.k8s.io/v1beta1
      4. metadata:
      5. namespace: dev
      6. name: authorization-role
      7. rules:
      8. - apiGroups: [""] # 支持的API组列表,"" 空字符串,表示核心API群
      9. resources: ["pods"] # 支持的资源对象列表
      10. verbs: ["get", "watch", "list"] # 允许的对资源对象的操作方法列表
      1. # ClusterRole可以对集群范围内资源、跨namespaces的范围资源、非资源类型进行授权
      2. kind: ClusterRole
      3. apiVersion: rbac.authorization.k8s.io/v1beta1
      4. metadata:
      5. name: authorization-clusterrole
      6. rules:
      7. - apiGroups: [""]
      8. resources: ["pods"]
      9. verbs: ["get", "watch", "list"]

      需要详细说明的是,rules中的参数:

      apiGroups: 支持的API组列表

      "","apps", "autoscaling", "batch"

      resources:支持的资源对象列表

      "services", "endpoints", "pods","secrets","configmaps","crontabs","deployments","jobs",
      "nodes","rolebindings","clusterroles","daemonsets","replicasets","statefulsets",
      "horizontalpodautoscalers","replicationcontrollers","cronjobs"

      verbs:对资源对象的操作方法列表

      "get", "list", "watch", "create", "update", "patch", "delete", "exec"

      RoleBinding、ClusterRoleBinding:

      角色绑定用来把一个角色绑定到一个目标对象上,绑定目标可以是User、Group或者ServiceAccount。

       # RoleBinding可以将同一namespace中的subject绑定到某个Role下,则此subject即具有该Role定义的权限
      kind: RoleBinding
      apiVersion: rbac.authorization.k8s.io/v1beta1
      metadata:
        name: authorization-role-binding
        namespace: dev
      subjects:
      - kind: User
        name: heima
        apiGroup: rbac.authorization.k8s.io
      roleRef:
        kind: Role
        name: authorization-role
        apiGroup: rbac.authorization.k8s.io

      # ClusterRoleBinding在整个集群级别和所有namespaces将特定的subject与ClusterRole绑定,授予权限
      kind: ClusterRoleBinding
      apiVersion: rbac.authorization.k8s.io/v1beta1
      metadata:
       name: authorization-clusterrole-binding
      subjects:
      - kind: User
        name: heima
        apiGroup: rbac.authorization.k8s.io
      roleRef:
        kind: ClusterRole
        name: authorization-clusterrole
        apiGroup: rbac.authorization.k8s.io 

      RoleBinding引用ClusterRole进行授权:

      RoleBinding可以引用ClusterRole,对属于同一命名空间内ClusterRole定义的资源主体进行授权。

        一种很常用的做法就是,集群管理员为集群范围预定义好一组角色(ClusterRole),然后在多个命名空间中重复使用这些ClusterRole。这样可以大幅提高授权管理工作效率,也使得各个命名空间下的基础性授权规则与使用体验保持一致。

      # 虽然authorization-clusterrole是一个集群角色,但是因为使用了RoleBinding
      # 所以heima只能读取dev命名空间中的资源
      kind: RoleBinding
      apiVersion: rbac.authorization.k8s.io/v1beta1
      metadata:
        name: authorization-role-binding-ns
        namespace: dev
      subjects:
      - kind: User
        name: heima
        apiGroup: rbac.authorization.k8s.io
      roleRef:
        kind: ClusterRole
        name: authorization-clusterrole
        apiGroup: rbac.authorization.k8s.io 

       准入控制:

       通过了前面的认证和授权之后,还需要经过准入控制处理通过之后,apiserver才会处理这个请求。

      准入控制是一个可配置的控制器列表,可以通过在Api-Server上通过命令行设置选择执行哪些准入控制器:

      1. --admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,
      2. DefaultStorageClass,ResourceQuota,DefaultTolerationSeconds

      只有当所有的准入控制器都检查通过之后,apiserver才执行该请求,否则返回拒绝。

      当前可配置的Admission Control准入控制如下:

      • AlwaysAdmit:允许所有请求
      • AlwaysDeny:禁止所有请求,一般用于测试
      • AlwaysPullImages:在启动容器之前总去下载镜像
      • DenyExecOnPrivileged:它会拦截所有想在Privileged Container上执行命令的请求
      • ImagePolicyWebhook:这个插件将允许后端的一个Webhook程序来完成admission controller的功能。
      • Service Account:实现ServiceAccount实现了自动化
      • SecurityContextDeny:这个插件将使用SecurityContext的Pod中的定义全部失效
      • ResourceQuota:用于资源配额管理目的,观察所有请求,确保在namespace上的配额不会超标
      • LimitRanger:用于资源限制管理,作用于namespace上,确保对Pod进行资源限制
      • InitialResources:为未设置资源请求与限制的Pod,根据其镜像的历史资源的使用情况进行设置
      • NamespaceLifecycle:如果尝试在一个不存在的namespace中创建资源对象,则该创建请求将被拒绝。当删除一个namespace时,系统将会删除该namespace中所有对象。
      • DefaultStorageClass:为了实现共享存储的动态供应,为未指定StorageClass或PV的PVC尝试匹配默认的StorageClass,尽可能减少用户在申请PVC时所需了解的后端存储细节
      • DefaultTolerationSeconds:这个插件为那些没有设置forgiveness tolerations并具有notready:NoExecute和unreachable:NoExecute两种taints的Pod设置默认的“容忍”时间,为5min
      • PodSecurityPolicy:这个插件用于在创建或修改Pod时决定是否根据Pod的security context和可用的PodSecurityPolicy对Pod的安全策略进行控制

      参考文献:

      study-notes/工具使用/Kubernetes/subfile at master · bigcoder84/study-notes · GitHub

    32. 相关阅读:
      考研英语作文主题词
      经典游戏案例:仿植物大战僵尸
      MySQL常见配置参数及命令
      vue项目实际开发的问题及实用技巧分享(三)
      【100天精通Python】Day69:Python可视化_实战:导航定位中预测轨迹和实际轨迹的3D动画,示例+代码
      CTF是黑客大赛?新手如何入门CTF?
      前端之一阶段[HTML、CSS]问题记录
      k8s集群安装网络插件calico常见问题
      JAVA毕业设计飞机航班信息查询系统计算机源码+lw文档+系统+调试部署+数据库
      【加载数据--自定义自己的Dataset类】
    33. 原文地址:https://blog.csdn.net/xudasong123/article/details/139269776