目录

1.1资源对象概述

1.2 集群类

1.3 应用类

1.  service and pod:应用类相关的资源对象主要是围绕service和pod来进行说明的。

2.Lable与标签选择器

3.pod与deployment

4.service的cluster IP地址

5.service的外网访问问题

6.有状态的应用集群

7.与应用运维相关对象


考虑到K8s相关的概念和术语非常多,它们之间的关系也比较复杂,本篇文章将主要讲解一下K8s的一些基本概念和术语。

1.1资源对象概述

 K8s中的基本概念和术语大多是围绕资源对象(Resource Object)来说的,而资源对象在总体上可以分为以下两类:

         (1)某种资源的对象,如pod、存储卷(volume)、节点(node)、服务(service)

         (2)与资源对象相关联的动作与事物,如标签(label)、命令空间(namespace)、部署(deployment)、注解(annotation)HPA、PVC


资源对象一般包括几个通用属性:版本(version)、类别(kind)、名称、标签、注解

        (1)在版本信息 里包括此对象所属的资源组,一些资源对象的属性会随着版本的升级而变化,在定义资源对象时要注意这一点。

        (2)类别(kind)用于定义资源对象的类型

        (3)资源对象的名称(name)、标签、注解这三个属性属于资源对象的元数据(metadata)

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
  - name: nginx-container
    image: nginx:1.19.0
    ports:
    - containerPort: 80

----------------------------
apiVersion: v1: 指定了 Kubernetes API 的版本,这里是 v1。
kind: Pod: 表明这是一个 Pod 资源。
metadata: 包含 Pod 的元数据,比如 name 和 labels。
name: nginx-pod: Pod 的名称。
labels: 标签,用于标识和选择 Pod。
spec: Pod 的规范,定义了 Pod 的具体内容。
containers: 包含一个或多个容器的列表。
name: nginx-container: 容器的名称。
image: nginx:1.19.0: 指定容器使用的镜像,这里是 Nginx 的官方镜像。
ports: 定义容器的端口映射。
containerPort: 80: 容器内部监听的端口,这里是 Nginx 默认的 HTTP 端口。

        如上所示,我们可以使用YAML或JSON格式声明(定义或创建)一个K8s资源德源对象,每个资源对象(pod、service、PV、PVC等)都有自己的特定结构定义(可以理解为数据库中一个特定的表),并统一保存在etcd(非关系型DB)集群中。然后,所有的资源都可以通过K8s提供的kubectl命令行工具(或API调用)进行增、删、改、查等操作。

        为了更好的理解K8s的基本概念和术语,特别是数量众多的资源对象,这里可以按照其功能或用途对其进行分类,可将其分为集群类、应用类、存储类安全类这四大类

1.2 集群类

        集群类的相关资源概念(组件)可参考  二、Kubernetes组件

1.3 应用类
1.  service and pod:应用类相关的资源对象主要是围绕service和pod来进行说明的。

        在K8s中,service是一种抽象的资源概念,在它的YAMl或JSON文件中,可以定义外部请求访问容器的方式,无论这些应用的pod如何分布。service都会为一组pod提供统一的访问接口,使得外部请求可以负责均衡的分发到后端pod上

主要特点:

        (1)负载均衡:service内置了负载均衡器,将进入的流量均匀的分发到后端pod上

        (2)服务发现:service通过DNS环境变量提供服务发现,使得pod可以发现互相通信

        (3)抽象和解耦:service 抽象了后端 Pod 的具体实现,使得可以更改后端 Pod 而不影响访问它们的方式。

        (4)支持多种访问模式:Service 支持多种访问模式,包括 ClusterIP(默认,仅在集群内部可访问)、NodePort、LoadBalancer 和 ExternalName。


service类型:

       

ClusterIP(默认类型):

  • 虚拟的服务IP地址,适用于K8s集群内部的pof访问
  • 通过虚拟的 ClusterIP 暴露服务。

NodePort:

  • 在集群的所有节点上打开一个端口(NodePort),可以通过任何节点的 IP 和 NodePort 访问 Service。
  • 使用宿主机的端口,通过node的ip地址和端口号就可以访问服务。

LoadBalancer:

  • 为 Service 分配一个外部的负载均衡器,适用于云服务提供商。
  • 通过云提供商的负载均衡器将外部流量转发到 Service。

ExternalName:

  • 通过返回一个外部 DNS 名称和 CNAME 记录,可以将 Service 映射到外部服务。
  • 这不会导致创建任何代理或负载均衡器。

        一般来说,service指的是无状态的服务,通常由多个程序副本提供服务。

当然,特殊情况下也可以部署有状态的单实例服务,比如像PostgreSQL这种存储数据类得服务。与我们常理解的服务不同,k8s里的service具有一个唯一的全局虚拟Cluster IP地址,service一旦被创建,k8s就会自动的为她分配一个可用的cluster IP,并且在service的整个生命周期中,它的cluster IP 就不会变,客户端可以通过这个虚拟IP地址+服务的端口号直接访问该服务,在通过k8s集群的DNS服务,就可以实现service name(域名) 到cluster IP地址的DNS映射功能,我们只需要使用服务的名称(DNS名称)即可访问到目标服务。“服务发现”这个传统架构中棘手的问题在这里就得到完美的解决。


接下来说说与service相关密切相关的核心资源对象 “pod”

         pod是k8s中最重要的核心概念之一,如下方所示是pod的组成示意图,我看看到的每个pod都有一个特殊的容器---> Pause容器。Pause容器是一个轻量级的容器,它不运行任何实际的应用或服务,在pod初始化阶段,pod里面的第一个启动的容器就是Pause容器,;来为pod创建所需的资源和命名空间。除了Pause容器,每个pod里还包含一个或多个紧密相关的用户业务容器。

        那么为什么k8s会设计出一个全新的pod概念并且pod有这样的特殊的组成结构呢?原因如下:

  • 为多进程之间的协作提供一个抽象的模型,拿pod作为k8s集群中最小的工作单位,让多个应用进程能够一起有效的调度和伸缩。
  • pod里的多个容器共享Pause的IP,共享Pause容器挂载的存储卷(volume),这样简化了容器之间的通信问题,同时也很好地解决了它们之间的文件共享问题。

        k8s为每个pod都分配了唯一的IP地址,称为pod IP,一个pod里的多个容器共享pod IP地址。k8s要求底层网咯支持集群内任意两个pod之间的TCP/IP直接通信,这里通常使用二层网络技术实现,如Flannel、Open vSwith等;因此,我们要牢记一点:在k8s里,一个pod里的容器与另外主机上的pod容器能够直接通信。

        pod类型其实也有两类:普通pod和静态pod(static pod)之分。静态pod比较特殊,它并没有直接在etcd中而是被固定到了某个指定的节点上(master或node),并且一直在指定节点上运行,由kubelet直接管理,不会被各种service、deployment、cronjob等控制器管理;而普通的pod一旦被创建,它的信息就会被存放到etcd存储,随后被master调度到某个具体的node上并绑定,该pod及pod所包含的容器就会被对应的node上的组件kubelet管理并启动,

        默认情况下,当pod里的某个容器停止时,k8s会自动检测到这个问题并重新启动这个pod(重启pod里的所有容器),那如果pod所在node节点宕机,k8s就会将这个node上的所有pod调度到其他可用node节点上。pod、容器、与node之间的关系如下图所示。

apiVersion: v1
kind: Pod
metadata:
  name: myweb
  lables: 
    name: myweb
  spec:
    containers: 
    - name : myweb
      image: kubeguide/tomcat-app: v1
      port: 
    - containerPort: 8080

-------------------------------------------------
以上的yaml文件中,定义了kind属性为Pod,表明这是一个pod类型的资源对象;
metadata里的name属性为pod的名称,在metadata里还能定义资源对象的标签,这里声明myweb拥有一个name=mtweb标签。
在pod里所包含的容器组定义则是在spec部分中声明,这里定义了一个名为myweb且对应的镜像为对象为kubeguide/tomcat-app:v1的容器,并在8080段口(containerPort)启动容器。
pod的IP加上这里的容器端口(containerPort)组成了一个新的概念--> Endpoint,代表此pod里的一个服务进程的对外通信地址。
一个pod也存在具有多个Endpoint的情况,比如当我们把ftp定义为一个pod时,可以对外暴露控制端口与传输端口这两个endpoint。

2.Lable与标签选择器

        Label(标签)标签是K8s中另一个核心的概念,一个label是一个key=value键值对,当中的key和value可以由用户自己定义。Label可以被附加到各种资源对象上,例如node、pod、service、deployment等,一个资源对象可以定义任意数量的label,同一个label也可以被添加到任意数量的资源对象上。Label通常在资源对象定义时确定,也可以在对象创建后进行添加或删除。我们可以通过对一个指定的资源对象打上多个不同的标签来实现对各种资源对象灵活、方便地进行资源分配、调度、配置、部署等管理工作。

        给某个资源对象定义一个Label,就相当于给它打上了一个标签,随后可以通过标签选择器(selector)来筛选和调用含有对应标签(Label)的资源对象。k8s通过这种方式实现了类似于SQl的简单又通用的对象查询机制。Label selector类似于SQL语句中的where查询条件,例如,“name=redis-slave”,这个Label slector作用于pod时,可以被类似于“select * from pod where pod'_name = redis-slave”这样的语句。

        Label也是pod的重要属性之一,其重要性仅次于pod的端口,我们几乎看不到没有Label的pod。以myweb为例,下面给它设定app = myweb 的标签:

        

apiVerison: v1
kind: Pod
metadata: 
  name: myweb
  labels: 
    app:myweb
----------------------------------------------
那么在对应的service myweb 资源对象就是通过下面的标签选择器方式与myweb pod发生关联的:
spec:
  selector: 
    app: myweb

所以我们看到在service很重要的一个属性就是标签选择器,如果不小心将标签选择器写错了,就会出现发生故障的闹剧。如果匹配到了另一种pod实例,而且对应的容器端口恰好正确,服务可以正常访问,则很难排查问题,特别是在有众多service的复杂系统中。

3.pod与deployment

        前面提到,大多service都是用于部署无状态服务,可以由多个副本pod实例提供服务。通常情况下每个service对应的pod服务实例数量都是固定的,如果一个一个的手动创建pod实例,就劳费心神了,最好是用模版的思路,既提供一个pod模版(template),然后由程序帮我们根据指定的模版创建指定数量的pod实例。这就是deployment这个资源对象的坐拥所在。

        看一下之前资源对象为deployment的yaml文件案例

apiVersion: apps/v1
kind: Deploymnet
spec:
  replicas: 2
  selector: 
    matchLabels:
      app: myweb
  template: 
    metadata: 
      labels:
        app: myweb
    spec:

------------------------------------------------
这里有几个很重要的属性:
    replicas: pod的副本数量
    selector:目标pod的选择器
    template: 用于自动创建新的pod副本的模版

        那么只有一个pod副本实例时,是否西需要使用deployment来来自动创建pod呢?在大多数情况下,一般是需要的。正是因为deploymnet除了自动创建pod副本外,还有一个很重要的特性:自动控制。举个例子,如果pod所在的node节点发生宕机,k8s就会第一时间收到宕机事件,并自动创建一个新的pod对象,将其调度到其他合适的node节点,k8s会基于deployment 的yaml文件中所声明的replicas数量保持一致。

        以下是关于创建一个基于tomcat-deployment的deployment 的yaml文件,如下所示:

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: frontend
spec:
  splicas:1
  selector: 
    matchLabels:
      tier:frontend
    matchExpressions:
      - {key: tier,operator: In, values: {frontend}}
    template:
      metadata:
        labels:
          app: app-demo
          tier: frontend
      spec: 
        containers:
        - name: tomcat-demo
          inmage: tomcat
          imagePullPolicy: IfNotPresent
          ports:
          - containerPort: 8080

使用deployment的适用场景有:

  • 创建一个deployment对象来完成相应的pod副本数量的创建
  • 检查deployment创建的pod副本数量是否符合预期数量
  • 更新deployment以创建新的pod(镜像升级),如果当前deployment不稳定,则回滚到先前稳定的版本
  • 扩展(增加新的)deployment来应对高负载场景

 下方图片显示了pod、deployment、service之间的逻辑关系

        从图片可以看出,k8s中的service定义了一个服务的访问入口地址,前端的应用(pod)通过这个入口地址访问其背后的一组有pod副本组成的集群实例、service与其后端pod副本集群之间则是通过Label selector实现无缝对接的,deployment实际上用于保证service的服务能力和服务质量始终服务预期标准。

4.service的cluster IP地址

        既然每个pod都会被endpoint所记录pod IP +端口号用于接受客户端访问,那么,现在多个pod之上有组成了一个集群(service或deploymnet)来提供服务,客户端改如何访问他们呢?

传统的做法是搞一个负载均衡器(nginx或一些其他软件或硬件),为这组pod开放一个对外服务的8080端口,并且将这些pod的endpoint列表加入8080端口的转发列表中(如nginx配置文件中的pass参数),客户端就可以通过负载均衡器的对外Ip地址+8080端口来访问此服务了。

在K8s中也是类似的做法,k8s内部在每个node上都运行了一套全局的虚拟负载均衡器,自动注入并实时更新集群中所有service的路由表,通过iptables或ipvs机制,把对service的请求转发到相应的后端pod实例上。不仅如此,k8s还采用了一种很巧妙的设计-->cluster IP地址,我们知道pod的endpoint地址会随着pod的删除和重新创建而改变,因为新的pod的IP地址与之前的旧的pod不同。service一旦被创建,K8s就会为她自动分配一个全局唯一的虚拟IP地址--->cluster IP ,在service的整个生命周期内,其cluster IP 地址不会发生改变,这样一来,无论service下的pod怎样删除与新建,还是通过service的 cluster IP 唯一地址来访问新pod(新的应用),远程服务之间的通信问题就变成了基础的TCP/IP网络通信问题。

5.service的外网访问问题

        前面提到,服务的cluster IP地址在K8s集群内才能被访问,那么如何让集群外的应用访问到内部的服务呢?这是个相对复杂的问题,要想弄明白这个问题的解决思路和办法,我们需要先弄明白K8s的三种IP,分别如下:

  • node IP: node的IP地址
  • pod IP:pod的ip地址
  • service IP:service的IP地址

        首先node IP是K8s中每个node节点的物理网卡的IP地址,是一个真实存在的物理网络,所有属于这个网络的服务器都能通过这个网络直接通信,不管其中是否有部分节点不属于这个k8s集群。这也表明K8s集群之外的节点访问K8s集群内的某个节点或者TCP/IP服务时,都必须通过node IP通信。

        其次,pod IP是每个pod的IP地址,在使用docker作为容器支持引擎情况下,它是docker engine 根据docker0 网桥的IP地址进行分段分配的,通常是一个虚拟二层网络。前面说过,k8s要求位于不同node上的pod都能够彼此直接通信,所以K8s中一个pod里的容器访问到另一个pod里的容器时,就是通过pod IP所在的虚拟网络二层进行通信的,而真实的TCP/IP流量是通过node IP所在的物理网卡流出来的。

        在k8s集群中,service的cluster IP地址只能用于集群内部组件通信,无法在集群外直接使用此IP地址。为了解决这个问题,k8s引入了另一种service的类型---> node port 这个概念,以tomcat-service为例,在service的定义里做出如下扩展即可

apiVersion: v1
kind: Service
metadata: 
  name: tomcat-service
spec:
  type: NodePort
  ports:
  - port: 8080
    nodePort: 31002
  selector:
    tier: frontend

----------------------------
其中,nodePort:31002这个属性表明手动指定tomcat-service的nodeport为31002,
否则K8s会自动为其分配一个库用的端口。接下来在浏览器里访问http://<node Port IP>:31002/,
就可以看到tomcat的欢迎界面了

        nodeport的实现方式是,在k8s集群的每个node上都为需要外部访问的service开启一个对应的TCP监听端口,外部系统只要用任意一个node 的IP+nodeport端口号即可访问该服务,在任意node上运行netstat命令,就可以看到有nodeport端口被监听;

sudo netstat -antup | grep 31002

tcp6  0  0 [::]:31002   [::]:*   LISTEN    1125/kube-proxy

        但是node IP并没有完全解决外部访问service的所有问题,比如负载均衡问题。假如在我们的集群中,有10个node节点,则此时最好有个负载均衡服务器来做转发和“降压”,外部的请求只需要访问负载均衡器的IP,有负载均衡器转发流量到后端某个node上的nodeport上,如下图所示:

        上图中的负载均衡器位于k8s集群之外,通常是一个软件的负载均衡器,也可以通过软件实现,例如HA proxy、nginx。对于每个service,我们通常需要配置一个对应的负载均衡器来实现流量的转发到后端的node上,这的确增加了工作量及出错得概率。

        于是K8s提出了自动化解决方案,是service类型其中之一:LB(load balance),比如我们集群运行在谷歌的公有云GCE上那只要把service的“type=NodePort”改为“type=LoadBalancer”,K8s就会自动创建一个对应的负载均衡器实例并返回她的IP地质提供给外部客户客户端使用。


        nodeport的确功能强大器通用强,但也有弊端,就是每个service都需要在node上独占一个端口,而端口的数量又是有限的(nodeport默认端口范围30000-32767),那能不能让多个service共用一个对外的端口呢?这就是后来的增加的Ingress资源对象所解决的问题。在一定程度上,我们可以把Ingress的实现机制理解为基于nginx的支持虚拟主机的HTTP代理。下面是一个Ingress的实例:

kind: Ingress 
metadata: 
  name: name-virtual-host-ingress 
spec: 
  rules: 
  - host: foo.ba r . com 
  http: 
    paths  
    - backend: 
       serviceName: service l 
       servicePort: 80 
- host: bar.foo.com 
  http: 
    paths: 
    - backend: 
      serviceName: service2 
      servicePort: 80
-----------------------------------------------------
在以上 Ingress的定义中,到虚拟域名frst.bar.com请求的流量会被路由到 service1.到 second.foo.com请求的流量会被路由到 service2。通过上面的例子,我们也可以看出,Ingress其实只能将多个HTTP(HTTPS)的 Service“聚合”,通过虚拟域名或者 URLPath的特征进行路由转发功能。考虑到常见的微服务都采用了HTTPREST协议,所以Ingress这种聚合多个Service 并将其暴露到外网的做法还是很有效的。
6.有状态的应用集群

        我们知道,Deployment对象是用来实现无状态服务的多副本自动控制功能的,那么有状态的服务,比如 ZooKeeper 集群、MySQL 高可用集群(3节点集群)、Kafka 集群等是怎么实现自动部署和管理的呢?这个问题就复杂多了,这些一开始是依赖 StatefulSet 解决的,但后来发现对于一些复杂的有状态的集群应用来说,StatefulSet还是不够通用和强大,所以后面又出现了Kubernetes Operator。
我们先说说 StatefulSet。StatefulSet 之前曾用过 PetSet 这个名称,很多人都知道,在IT 世界里,有状态的应用被类比为宠物(Pet),无状态的应用则被类比为牛羊,每个宠物在主人那里都是“唯一的存在”,宠物生病了,我们是要花很多钱去治疗的,需要我们用心照料,而无差别的牛羊则没有这个待遇。总结下来,在有状态集群中一般有如下特殊共性。

每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。

  • 集群的规模是比较固定的,集群规模不能随意变动。
  • 集群中的每个节点都是有状态的,通常会持久化数据到永久存储中,每个节点在重启后都需要使用原有的持久化数据。
  • 集群中成员节点的启动顺序(以及关闭顺序)通常也是确定的。
  • 如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。

如果通过 Deployment 控制 Pod 副本数量来实现以上有状态的集群,我们就会发现上述很多特性大部分难以满足,比如 Deployment 创建的Pod因为Pod 的名称是随机产生的我们事先无法为每个Pod 都确定唯一不变的D,不同Pod的启动顺序也无法保证,所以在集群中的某个成员节点宕机后,不能在其他节点上随意启动一个新的Pod 实例。另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决有状态集群这种复杂的特殊应用的建模,Kubernetes引人了专门的资源对象--StatefulSet。StatefulSet 从本质上来说,可被看作 DeploymentRC的一个特殊变种,它有如下特性。

  • StatefulSet 里的每个 Pod 都有稳定、唯一的网络标识,可以用来发现集群内的其他成员股设 StatefulSet 的名称为 kafka,那么第1个Pod 叫 kafka-0,第2个叫 kafka-1,以此类推。
  • StatefulSet 控制的 Pod 副本的启停顺序是受控的,操作第n个Pod时,前n-1个Pod 已经是运行且准备好的状态。
  • StatefulSet 里的 Pod 采用稳定的持久化存储卷,通过 PV或PVC 来实现,删除 Pod时默认不会删除与 StatefulSet相关的存储卷(为了保证数据安全)。
7.与应用运维相关对象

        首先就是 HPA(Horizontal Pod Autoscaler ),如果我们用 Deployment 来控制 Pod 的本数量,则可以通过手工运行kubectlscale 命令来实现Pod扩容或缩容。如果仅仅到此止,则显然不符合谷歌对Kubernetes的定位目标--自动化、智能化。采用手动控制的方式是不现实的,因此就有了后来的HPA 这高级功能。我们可以将 HPA 理解为 Pod 横向自动扩容,即自动控制 Pod数量的增加或减少通过追踪分析指定 Deployment 控制的所有目标Pod 的负载变化情况,来确定是否需要有针对性地调整目标Pod的副本数量,这是HPA的实现原理。下面是一个 HPA 定义的例子:

apiVersion:autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name:php-apache
  namespace:default
speC:
  maxReplicas:10
  minReplicas:1
scaleTargetRef:
  kind: Deployment
  name: php-apache
targetCPUUtilizationPercentage:90
--------------------------------------------------------
根据上面的定义,我们可以知道这个HPA控制的目标对象是一个名为php-apache 
的Deployment里的 Pod副本,当这些Pod副本的 CPU 利用率的值超过 90%时,
会触发自动动态扩缩容,限定pod副本数量为1-10

声明:以上内容均是个人理解,详情可参考k8s权威指南

        

    

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐