第一章 Kubernetes入门

在K8S中,Service是分布式集群架构的核心,一个Service对象拥有如下关键特征。

  • 拥有唯一指定的名称(比如mysql-server)
  • 拥有一个虚拟IP(Cluster IP\Servcie IP\VIP)和端口号。
  • 能够提供某种远程服务能力。
  • 被映射到提供这种服务能力的一组容器应用上。

Service的服务进程目前都基于Socket通信方式对外提供服务。虽然一个Service通常由多个相关的服务进程提供服务,每个服务进程都有一个独立的Endpoint(IP+Port)访问点,但Kubernetes能够让我们通过Service(虚拟Cluster IP+Service Port)连接到指定的Service。这个Service本身一旦创建就不再变化。

Service提供服务的这组进程放入容器中进行隔离。为此,Kubernetes设计了Pod对象,将服务进程放入到Pod。因此,就产生了一个Service与Pod的对应关系问题,K8s首先给每个Pod贴上标签(Label),然后给相应的Service定义标签选择器(Label Selector),就解决了Service与Pod的关联问题。

Pod简单介绍,Pod运行在被称为Node的节点上,可以是虚机也可以是物理机,每个Node上可以运行上百个Pod。Pod中特备地具有一个叫Pause的容器,其他称为业务容器,业务容器共享Pause容器的网络栈和Volume挂载卷。设计时尽量把相关的服务放在一个Pod中,但不是每个Pod和它里面运行的容器都能被映射到一个Service,只有提供服务(无论对内还是对外)的那组Pod才会被映射为一个服务。

在集群管理方面,k8s将集群中的机器划分为一个Master和一些Node。在Master上运行着集群管理相关的一组进程,kube-apiserver,kube-controller-manager,kube-scheduler,自动化地进行集群管理。Node作为集群地工作节点,运行着真正的应用程序,在Node上K8S管理地最小单元是Pod。在Node上运行着K8S的kubelet、kube-proxy服务进程,对Pod进行管理。

在K8S集群中,需要扩容地Service关联地Pod创建一个RC(Replication Controller),服务扩容以至服务升级等令人头疼地问题都迎刃而解。在一个RC定义文件中包括以下3个关键信息:

  • 目标Pod的定义。
  • 目标Pod需要运行地副本数量(Replicas)。
  • 要监控的目标Pod的标签。

K8S会通过RC中定义的Label筛选出Pod,监测其数量,如不达到,就复制Pod模板选择合适的Node进行启动。


Kubernetes的基本概念和术语

K8S中的大部分概念如Node,Pod,Replication Controller,Servcie等都可以被看做一种资源对象,几乎所有资源对象都可以通过K8s提供的kubectl工具(或者API编程调用)执行增删改查等操作并将其保存在etcd中持久化存储。从这个角度,K8s其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的“资源期望状态”与当前环境中的“实际资源状态”的差异来实现自动控制和自动纠错的高级功能。

在声明一个Kubernetes资源对象的时候,需要注意一个关键属性:apiVersion。

k8s平台采用“核心+外围扩展”的设计思路。k8s大部分常见的核心资源对象都归属于都属于v1这个核心API,比如Node,Pod,Service,Endpoints,Namespace,RC,PersistentVolume等。还有测试的例如extensions/v1beat1,apps/v1beta1,apps/v1beta2等API组,而在1.9版本后引入apps/v1的正式API组,正式淘汰上述三个测试组。

可以用YAML或JSON格式声明(定义或创建)一个kubernetes资源对象,每个资源对象都有自己的特定语法格式(可以理解为数据库中一个特定的表)。在版本迭代中,为了引入新特性且不影响当前功能,有两种典型方法:

  • 在每个表中,增加一个很长的备注字段,之后扩展的数据以某种格式(如XML,JSON,简单字符串拼接等)放入备注字段。程序改动范围小,但不美观。

      annotations:
        pod.beta.kubernetes.io/init-containers:'[
      	{
      		"name":"init-mydb",
      		"image":"busybox",
      		"command":[....]
      	}
      ]'
    
  • 方法2,直接修改数据库表,增加一个或多个新的列,此时程序改动范围大,风险大,但不美观。

      initContainers:
      	-name:init-mydb
      	image:busybox
      	command:
      	- xxx
    

Kubernetes为每个资源对象都增加了类似数据库表里的备注字段的通用属性Annotations

1.4.1 Master

Master是集群控制节点,在每个Kubernetes集群里都需要有一个Master来负责整个集群的管理和控制,基本上所有的控制命令都是发给它,它负责具体的执行过程。占据一个独立的服务器,高可用多个。

在Master上运行着以下关键的进程。

  • Kubernetes API Sever(kube-apiserver):提供了HTTP Rest接口的关键服务进行,是K8S里所有资源的增,删,改,查等操作的唯一入口,也是集群控制的入口进程。
  • Kubernetes Controller Manager(kube-controller-manager):Kubernetes里所有资源对象的自动化控制中心,可以将其理解为资源对象的“大总管”。
  • Kubernetes Scheduler(kube-scheduler):负责资源调度(Pod调度)的进程,相当于公交公司的“调度室”。

另外,在Master上通常还需要部署etcd服务,因为Kubernetes里的所有资源对象的数据都被保存在etcd中。

1.4.2 Node

除了Master,其他节点称为Node,每个Node都会被Master分配一些工作负载(Docker容器),当Node宕机时,其上的工作负载会被Master自动化地转移到其他节点上。

在每个Node上都运行着以下关键进程:

  • kubelet:负责Pod对应地容器地创建、启动、停止等任务,同时与Master密切协作,实现集群管理地基本功能。
  • kube-proxy:实现kubernetes Service的通信与负载均衡机制的重要组件。
  • Docker Engine:Docker引擎,负责本机地容器创建和管理工作。

Node可以在运行期间动态增加到Kubernetes集群中,前提是在这个节点上已经正确安装、配置和启动了上述关键进程,在默认情况下kubelet会向master注册自己,这也是kubernetes推荐地node管理方式。一旦Node被纳入集群管理的范围,kubelet就是定时向master汇报自己的操作系统、CPU和内存使用情况,Docker版本,以及当前有哪些Pod在运行等,这样Master就可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。若Node超过指定时间没有上报信息给Master会被认为是失联,且被标记为不可用Not Ready,随后Master会触发“工作负载大转移”的自动流程。

查看集群中的Node的数量:
kubectl get nodes
NAME		STATUS	ROLES	AGE	    VERSION
k8s-node-1	Ready	<none>	350d 	v1.14.0

查看某个node的详细信息
kubectl describe node <node_name>

详细信息中的解释:

  • Node的基本信息:名称,标签、创建的时间等
  • Node当前的运行状态:Node启动后会做一系列的自检工作,比如磁盘空间是否不足(DiskPressure),内存是否不存(MemoryPressure),网络是否正常(NetworkUnavailable)、PID资源是否充足(PIDPressure)。在一切正常时设置Node为Ready状态(Ready=True),该状态表示Node处于健康状态,Master将可以在其上调度新的任务了(如启动Pod)。
  • Node的主机地址和主机名
  • Node上的资源数量:描述Node可用的系统资源,包括CPU、内存数量,最大可调度Pod数量等。
  • Node可分配的资源量:描述Node当前可用于分配的资源量。
  • 主机系统信息:包括主机ID,系统UUID,Linux kernel版本号,操作系统类型与版本,Docker版本号,kubelet与kube-proxy的版本号等。
  • 当前运行的Pod列表概要信息。
  • 已分配的资源使用概要信息,例如资源申请的最低、最大允许使用量占系统总量的百分比。
  • Node相关的Event信息。
Pod

Pause容器:gcr.io/google_containers/pause-amd64。其他为业务容器。

原因:

  • 如果业务容器宕机,不代表此Pod宕机,以Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单,巧妙的解决了这个难题。
  • Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样既简化了密切关联的业务容器之间的通信问题,也很好地解决了它们之间的文件共享问题。

K8S为每个Pod分配唯一的IP地址,称之为Pod IP,一个Pod里有多个容器共享Pod IP地址。K8S允许集群内任意两个不同Pod间的容器通信,通过L2的Opne vSwitch或Flannel虚拟网络技术实现。

Pod分为:普通的Pod和静态的Pod(Static Pod)。Static Pod比较特殊,不放在集群中Master的etcd中,而被放在某个具体的Node上为一个具体文件,并且只在此Node上启动,运行。普通的Pod先创建放在etcd中,然后master会根据情况调度它到具体的node进行bending绑定,随后kubelet会将此pod实例化成一组相关的docker进行启动。当master发现某个Pod中的一个容器宕机,则master重启这个Pod(即其中所有容器),如果是Node宕机,则将此Node上的所有Pod转移到新的Node上。

kubernetes里所有的资源对象都可以用YAML或者JSON格式的文件定义或描述,

apiVersion:v1
kind:Pod
metadata:
 name:myweb
 labels:
	name:myweb
spec:
 containers:
	-name:myweb
	image:kubeguide/tomcat-app:v1
	ports:
	- containerPort:8080
	env:
	- name:MYSQL_SERVICE_HOST
	  value:'mysql'
	- name:MYSQL_SERVICE_PORT
	  value:'3306'

kind声明一个Port定义。metadata里的name属性是Pod的名称,在metadata里还能定义资源对象的标签,这里声明myweb拥有一个name=myweb的标签。在Pod里所包含的容器组的定义则在spec一节中声明,这里定义了一个名为myweb、对应镜像为kubeguide/tomcat-app:v1的容器,该容器注入了名为MYSQL_SERVICE_HOST='mysql’和MYSQL_SERVICE_PORT='3306’的环境变量(env关键字),并且在8080端口(containerPort)启动容器进程。

Pod的IP加上这里的容器端口(containerPort)就组成了一个新的概念——Endpoint,它代表此Pod里的一个服务进程的对外通信地址。一个Pod也存在具有多个Endpoint的情况,比如当我们把Tomcat定义为一个Pod时,可以对外暴露管理端口与服务端口这两个Endpoint。

Pod Volume是Docker Volume的一些扩展,比如可以用分布式文件系统GlusterFS实现后端存储功能;Pod Volume是被定义在Pod上,然后被各个容器挂载到自己的文件系统中的。

Event是一个事件的记录,记录了事件的最早产生时间,最后重现时间,重复次数,发起者,类型,以及导致此事件的原因等众多信息。Event通常会被关联到某个具体的资源对象上,是排查故障的重要参考信息,Pod同样具有event。可以通过kubectl describe pod xxx来查看它的描述信息,以定位问题的成因。

Pod可以被限制使用的CPU和内存大小。K8S里以千分之一的CPU配额为最小单位,用m来表示,通常一个容器的CPU配额被定义为100-300m,即占用0.1-0.3个CPU。Memory配额的单位是内存字节数,绝对值不是相对值。

计算资源配额限制的参数:

  • Requests:该资源的最小申请量,系统必须满足。

  • Limits:该资源最大允许使用的量,不能突破,如果容器试图超过这个量,会被K8S“杀掉”重启。

      spec:
       containers:
      	- name:db
      	image:mysql
      	resources:	
      		requests:
      		 memory:"64Mi"
      		 cpu:"250m"
      		limits:
      		  memory:"128Mi"
      		  cpu:"500m"
      Mysql容器申请最少0.25个CPU以及64MiB内存,在运行过程中Mysql最多使用0.5个CPU和128MB的内存。
    
Label

一个Label是一个key=value的键值对,其中key与value由用户自己指定。一个资源对象可以定义任意数量的Label。Label可以在创建时添加,也可在创建后动态添加或删除。指定资源对象捆绑一个或多个Label来实现多维度的资源分组管理功能,以便灵活、方便地进行资源调度分配配置部署等管理工作。常用Label:

  • 版本标签:“release”:“stable”、“release”:“canary”
  • 环境标签:“environment”:“dev”、“environment”:“qa”、“environment”:“production”.
  • 架构标签:“tier”:“fronted”、“tier”:“backend”、“tier”:“middleware”
  • 分区标签:“partition”:“customerA”、“partition”:“customerB”。
  • 质量管控标签:“track”:“daily”、“tarck”:“weeklu”。

随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,K8S通过这种方式实现了类似SQL的简单又通用的对象查询机制。

Label Selector表达式有两种:基于等式的(Equality-based)和基于集合的(Set-based),前者采用等式类表达式匹配标签,下面给出具体例子。

  • name=redis-slave:匹配所有具有标签name=redis-slave的资源对象
  • env !=production:匹配所有不具有标签env=production的资源对象。

后者则使用集合操作类表达式匹配标签,具体例子。

  • name in (redis-master,redis-slave):匹配所有具有标签name=redis-master或者name=redis-slave的资源对象。
  • name not in (php-fronted):匹配所有不具有标签name=php-fronted的资源对象。

多个表达式之间用【,】进行分隔,几个条件是“AND”的关系。

name=redis-slave,env!=production
name not in (php-frontend),env!=production

Pod的Label定义在metadata。而管理对象RC和Service则通过Selector字段设置需要关联的Pod的Label。
其他管理对象如Deployment、ReplicaSet、DaemonSet和Job则可以在Selector中使用基于集合的筛选条件定义。例如:

selector:
 matchLabels:
	app:myweb
 matchExpressions:
	- {key:tier,operator:In, values:[frontend]}
	- {key:enviroment, operator:NotIn, values:[dev]}

matchLabels用于定义一组Label,与直接写在Selector中的作用相同;matchExpressions用于定义一组基于集合的筛选条件,可用的条件运算符包括In、NotIn,Exists和DoesNotExists。如果同时设置了matchLabels和matchExpressions则两组为AND关系。

Label Selector在Kubernetes中的重要使用情景:

  • kube-controller筛选Label知道Pod的数量,完成自动数量控制。
  • kube-proxy通过筛选Service的Label选择对应的Pod,建立每个Service到对应Pod的请求转发路由表,实现Service的负载均衡机制。
  • 通过对Node进行Label,在Pod中加入NodeSelector的Label,kube-scheduler可以实现Pod的定向调度。
Replication Controller

RC定义了一个期望的场景,定义如下:

  • Pod期望的副本数量
  • 用于筛选目标Pod的Label Selector。
  • 当Pod的副本数量小于预期的数量时,用于创建新Pod的Pod模板(template)

通过RC,K8S实现了用户应用集群的高可用性。且在运行时,可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling),这可以通过执行kubectl scale命令来一键完成:

kubectl scale rc redis-slave --replicas=3

需要注意的是,删除RC并不会影响通过该RC已经创建号的Pod。为了删除所有的Pod,可以设置replicas的值为0,然后更新该RC。K8S还具有“滚动升级”(Rolling Update),升级版本用。

Replication Controller在K8S的v1.2中,升级为另外一个新概念——Replica Set,官方解释为“下一代的RC”。Replica Set与RC当前的唯一区别是,Repelica Set支持基于集合的Label Selector,而RC只支持基于等价的Label Selector。

kubectl命令行工具适用于RC的大部分命令也适用于RS,但RS很少单独使用,它主要被Deployment这个更高层的资源对象所使用,从而形成了一整套Pod创建、删除、更新的编排机制。我们在使用Deployment时,无需关心它是如何创建和维护Replica Set的,这一切都是自动发生的。

最后总结一下RC的一些特性与作用:

  • 定义一个RC实现Pod的创建及副本数量的自动控制。
  • 在RC里包括完整的Pod定义模板
  • RC通过Label Selector机制实现对Pod副本的自动控制
  • 通过改变RC里的Pod的数量实现动态扩缩容。
  • 通过改变RC里的镜像的版本号实现Pod的滚动升级。
Deployment

内部使用Replica Set来实现目的,无论从Deployment的作用与目的、YAML定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级。最大升级在于我们随时可以知道当前Pod“部署”的进度。启动N个Pod副本的目标状态。

Deployment的典型使用场景:

  • 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建。
  • 检查Deployment的状态来看部署动作是否完成(Pod副本数量是否达到预期)。
  • 更新Deployment以创建新的Pod(比如镜像升级)。
  • 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本。
  • 暂停Deployment以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment,进行新的发布。
  • 扩展Deployment以应对高负载。
  • 查看Deployment的状态,以此作为发布是否成功的指标。
  • 清理不再需要的旧版本ReplicaSets。

除了Kind和API声明有所区别,Deployment的定义与Replica Set的定义很类似:

apiVersion:extensions/v1beta1  			apiVersion:v1
kind:Deployment 						kind:ReplicaSet
metadata:								metadata
	name:nginx-deployment				 name:nginx-repset

查看Deployment的信息
kubectl get deployments
NAME			DESIRED		CURRENT		UP-TO-DATE		AVAILABLE   AGE
tomcat-deploy 	1			1			1				1			4m
  • DESIRED:Pod副本数量的期望值,即再Deployment里定义的Replica
  • CURRENT:当前Replica的值,实际上是Deployment创建Replica Set里的Replica值,这个值不断增加,直到达到DESIRED为止,表明整个部署过程完成。
  • UP-TO-DATE:最新版本的Pod的副本数量,用于指示再滚动升级的过程中,有多少个Pod副本已经升级。
  • AVAILABLE:当前集群中可用的Pod副本数量,即集群中当前存活的Pod数。

查看对应的Replica Set
kubectl get rs
NAME 						DESIRED		CURRENT		AGE
tomcat-deploy-1640611518	1			1			1m

查看Replica Set创建的对应的Pod
kubectl get pods
NAME							READY		STATUS		RESTARTS		AGE	
tomcat-deploy-1640611518-zhrsc	1/1			Running		0				3m
可以发现Pod的命名以Deployment对应的Replica Set的名称为前缀,这种命名很清晰地表明了一个Replica Set创建了哪些Pod,对于Pod滚动升级这种复杂的过程来说,容易排查错误。

Pod的管理对象,除了RC和Deployment,还包括ReplicaSet、DaemonSet、StatefulSet、Job等,分别应用于不同的场景。

Horizontal Pod Autoscaler

Pod横向自动扩容,HPA。在K8S 1.2升级为稳定版本(apiVersion:autoscaling/v1),但仍保留了旧版本(apiVersion:extensions/v1beta1)。K8S的 1.6版本后,增强了根据应用自定义的指标进行自动扩容和缩容的功能,API版本为autoscaling/v2alphal,并不断演进。

原理:针对指定RC控制的所有目标Pod的负载变化情况,来确定是否需要有针对性地调整目标Pod的副本数量。

HPA度量Pod负载的指标:

  • CPUUtilizationPercentage(所有的CPU使用量的平均值,一个Pod的CPU利用率是该Pod当前CPU的使用量除以它的Pod Request的值)(如果目标Pod没有定义Pod Request就不能实现Pod HPA)
  • 应用程序自定义的度量指标,比如服务在每秒内的相应请求数(TPS或QPS)

CPUUtilizationPercentage是1min内的平均值,通常通过查询Heapster监控子系统来得到,需要安装部署Heapster,这样便增加了系统的复杂度和实时HPA特性的复杂度。因此,从版本1.7开始,有了一个基础性能数据采集监控框架——k8s Monitoring Architecture ,从而更好地支持HPA和其他需要使用到基础性能数据的功能模块。在Kubernetes Monitoring Architecture 中,kubernetes定义了一套标准化的API接口Resource Metrics API,以方便客户端应用程序(如HPA)从Metrics Server中获取目标资源对象的性能数据。到K8S 1.8则Resource Metrics API被升级为metrics.k8s.io/v1beta1。

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副本的CPUUtilizationPercentage的值超过90%的时候,会触发自动动态扩容行为,在扩容或缩容时必须满足的一个约束条件是Pod的副本数为1-10之间。

除了可以直接通过创建YAML文件并且调用kubectl create的命令来创建一个HPA资源对象的方式,还可以通过以下命令创建等价的HPA对象:

kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
StatefulSet

Pod的管理对象RC、Deployment、DaemonSet和Job都面向无状态服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群。这些集群有4个共同点:

  • 每个节点都有固定的身份ID,通过这个ID,集群中的成员可以相互发现并通信。
  • 集群的规模是比较固定的,集群规模不能随意变动。
  • 集群中的每个节点都是有状态的,通常会持续化数据到永久存储中。
  • 如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。

RC或Deployment产生Pod的名称是随机的,IP也是随机的不满足上述第一点,不是有状态服务。另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的Pod需要挂接某种共享存储,为了解决这个问题,就有了StatefulSet。从本质上说StatefulSet是Deployment/RC的变种。有以下特性:

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

StatefulSet不仅需要PV卷存储状态数据,而且还需要Headless Service配合使用,即在每个StatefulSet定义中都要声明它属于哪个Headless Service。Headless Service与普通Service区别在于它没有Cluster IP,如果解析Headless Service返回的是该Service对应的全部Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为每一个Pod创建了一个DNS域名,这个域名的格式:

$(podname).$(headless service name)

例如3节点的Kafka的StatefulSet集群,对应的Headless service名字为Kafka,StatefulSet名称为kafka,则StatefulSet里的3个Pod的DNS名称分别是kafka-0.kafka,kafka-1.kafka,kafka-2.kafka,这些名字都可以在配置文件中固定下来。

Service

Service没有共用一个负载均衡器的IP地址,每个Service都被分配了一个全局唯一的虚拟IP地址,这个虚拟IP地址被称为Cluster IP。每个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题。

服务发现问题的解决:只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可完美解决问题。

kubectl get endpoints
可以看见产生一个Pod的IP地址以及Pod内容器暴露的端口号。

kubectl get svc tomcat-service -o yaml
查看Cluster IP
spec:
 clusterIP:169.169.65.227
 ports:
 - port:8080
   protocol:TCP
   targetPort:8080
在spec.ports的定义中,targetPort属性用来确定提供该服务的容器所暴露(EXPOSURE)端口号;port属性则定义了Service的虚端口号。前面定义Tomcat服务时没有指定targetPort名则默认targetPort与port相同。

Service的多端口问题
要求每个Endpoint都定义一个名称来区分
spec:
	ports:
	- port:8080
	name:service-port
	- port:8005
	name:shutdown-port

kubernetes的服务发现机制

早期使用Linux环境变量解决这个服务发现的问题,每个Service的IP地址及端口都有标准的命名规范,遵循这个命名规范,就可以通过代码访问系统环境变量来得到所需的信息,实现服务的调用。

后来,增加了Add-On增值包引入DNS系统,把服务名作为DNS域名,这样程序就可以直接使用服务名来建立通信连接。

外部系统访问Service的问题
  • Node IP:每个Node的物理网卡的IP地址,是一个真实存在的物理网络,外部必须通过Node IP通信。
  • Pod IP:是Docker Engine根据docker0网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,不同Pod间的通信是通过Pod IP所在的虚拟二层网络进行通信的,而真实的TCP/IP流量通过Node IP所在的物理网卡流出的。
  • Cluster IP:虚拟的IP
    • Cluster IP仅作用于Service这个对象,并由kubernetes管理和分配IP地址(来源于Cluster IP地址池)
    • Cluster IP无法被ping,因为没有一个实体网络对象响应
    • Cluster IP只能结合Service Port组成一个具体的通信端口,单独的ClusterIP不具备TCP/IP通信的基础,并且它们属于K8S集群这样一个封闭的空间,集群外的节点如果要访问这个通信端口,还需要做一些额外的工作。
    • 在k8s集群内,NodeIP网,Pod IP网与Cluster IP网之间的通信,采用的是K8S自己设计的一种编程方式的特殊路由规则,与我们熟知的IP路由有很大的不同。

采用NodePort解决外部访问问题。

apiVersion:v1
kind:Node
metadata:
	name:tomcat-service
spec:
type:NodePort
ports:
	- port:8080
		nodePort:31002
selecor:
	tier:frontend
nodePort:31002这个属性表明手动指定tomcat-service的NodePort为31002,否则k8s会自动分配一个可用的端口。
接下来可以在浏览器里访问http://<nodePortIP>:310002/就可以访问了。

NodePort的实现方式是在K8S集群里的每个NODE上都为需要外部访问的Service开启一个对应的TCP监听端口,外部系统只要用任意一个Node的IP地址+具体的NodePort端口号即可访问此服务,在任意Node上运行netstat命令,就可以看到NodePort端口被监听。
netstat -tlp | grep 31002

但NodePort还没有完全解决外部访问Service的所有问题,比如负载均衡问题。Load balancer组件独立于Kubernetes集群之外,通常是一个硬件的负载均衡器或以软件方式实现,例如HAProxy或者Nginx。于是,k8s提供了自动化的解决方案,如果我们的集群运行在谷歌的公有云GCE上,那么只要把Service的type改为type=LoadBalancer,k8s就会自动创建一个对应的负载均衡器实例并返回它的IP地址供外部客户端使用。

Job

批处理任务通常并行(或者串行)启动多个计算进程去处理一批工作先(work item),在处理完成后,整个批处理任务结束。

Job 于RC的区别:

  • Job控制的Pod是短暂运行的,且不能自动重启,且还新增了一个CronJob,解决某些批处理任务需要定时反复执行的问题。
  • Job所控制的Pod副本的工作模式能够多实例并行计算。
Volume

Volume(存储卷)是Pod中能够被多个容器访问的共享目录。k8s的Volume与Pod的生命周期关联,而不与Docker相关联,docker可重启终止,且不会丢失数据。k8s支持多种类型的Volume,例如GlusterFS、Ceph等先进的分布式文件系统。

使用:先在Pod里声明一个Volume,然后容器引用该Volumn并挂载到容器里的某个目录上。例如:

template:
	metadata:
	labels:
		app:app-demo
		tier:frontend
spec:
	volumes:
	- name: datavol
		emptyDir:{}
	containers:
	- name:tomcat-demo
	  image:tomcat
	volumeMounts:
	- mountPath: /mydata-data
	name:datavol
	imagePullPolicy:IfNotPresent

除了可以让一个Pod里的多个容器共享文件,让容器的数据写到宿主机的磁盘上或者写文件到网络存储中,K8S的Volumn还扩展出了一种非常有实用价值的功能,即容器配置文件集中化定义与管理,这是通过ConfigMap这种新的资源对象来实现的。

k8s提供了丰富的Volume类型,例如:

emptyDir
  • emptyDir:初始内容为,且不需要指定宿主机的目录,当Pod在Node上移除时,就清空目录。
  • 用于临时存放
  • 长时间任务的中间过程CheckPoint的临时保存目录
  • 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)。

目前,无法指定emptyDir的介质种类。

hostPath

hostPath为在Pod上挂载宿主机上的文件和目录,用途:

  • 容器应用程序生产的日志文件需要永久保存,可以使用宿主机的高速文件系统进行存储。
  • 需要访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机/var/lib/docker目录,使容器内部应用可以直接访问docker的文件系统。

使用此类型的Volume,需要注意:

  • 在不同的Node上,Pod配置相同可能访问到不同的文件目录。
  • 如果使用了资源配额管理,则K8S无法将hostPath在宿主机上使用的资源纳入管理。

例如:

volumes:
- name: "persistent-storage"
  hostPath:
	path:"/data"
gcePersistentDisk

使用谷歌云提供的永久磁盘(PD)存放Volume的数据,永久保存。

使用gcePersistentDisk有以下一些限制条件:

  • Node(运行kubelet的节点)需要的是GCE虚拟机

  • 这些虚拟机需要与PD存在于相同的GCE项目和Zone中

     通过一个gcloud命令即可创建一个PD
     	gcloud compute disks create --size=500GB --zone=us-centall-a my-data-disk
    
     定义gcePersistentDisk类型的volumes的示例:
     volumes:
     	- name: test-volume
     		# This GCE PDmust already exist.
     		gcePesistentDisk:
     			pdName: my-data-disk
     		fsType:ext4
    
awsElasticBlockStore

需要创建一个EBS Volume才能使用。

限制:

  • Node需要是AWS EC2实例

  • 这些AWS EC2实例需要与EBS Volume存在于相同的region和availability-zone中

  • EBS只支持单个EC2实例挂载一个Volume

      aws ec2 create-volume --availability-zone eu-west-la --size 10 --volume-type gp2
    
      定义awsElasticBlockStore类型的Volume的示例:
      volumes:
      	name:test-volume
      	awsElasticBlockStore:
      		volumeID:aws://<availability-zone>/<volume-id>
      		fsType:etx4
    
NFS

使用NFS网络文件系统提供的共享目录存储数据时,需要在系统中部署一个NFS Server。定义NFS类型的Volume的示例如下:
volumes:
name:test-volumes
nfs:
#改为你NFS服务器地址
server:nfs-server.localhost
path:"/"

其他类型的Volume
  • iscsi:使用iSCSI存储设备上的目录挂载到Pod中
  • flocker:使用Flocker管理存储卷
  • glusterfs:使用开源GlusterFS网络文件系统的目录挂载到Pod中
  • rbd:使用Ceph块设备共享存储(Rados Block Device)挂载到Pod中
  • gitRepo:通过挂载一个空目录,并从Git库clone一个git reoisutiry以供Pod使用
  • secret:一个Secret Volume用于为Pod提供加密的信息,你可以将定义在K8S中的Secret直接挂载为文件让Pod访问。Secret Volume是通过TMFS(内存文件系统)实现的,这种类型的Volume总是不会被持久化的。
Persistent Volume

网络存储是相对独立于计算资源而存在的一种实体资源,被称为PV和与之相关联的Persistent Volume Claim(PVC)。PV可以被理解为K8S集群中的某个网络存储对应的一块存储,它有Volume类似,但有以下区别:

  • PV只能是网络存储,不属于任何Node,但可以在每个Node上访问
  • PV并不是被定义在Pod上的,而是独立于Pod之外
  • PV目前支持的类型包括:gcePersistentDisk、AWSElasticBlockStore,AzureFile,AzureDisk,FC(Fibre Channel),Flocker,NFS,iSCSI,RBD,CephFS,Cinder,GlusterFS,VsphereVolume,Quobyte Volumes,Vmware Photon,Portworx Volumes,ScaleIO Volumes和HostPath(仅供单机测试)。

例子:NFS类型PV的一个YAML定义文件,声明需要5G的存储空间

apiVersion:v1
kind:PersistentVolume
metadata:
	name:pv0003
spec:
	capacity:
	storage:5Gi
	accessModes:
	- ReadWriteOnce
	nfs:
		path: /somepath
		server:172.17.0.2

比较重要的PV的accessModes属性,有以下类型:

  • ReadWriteOnly:读写权限,并且只能被单个Node挂载。
  • ReadOnlyMang:只读权限,允许被多个Node挂载。
  • ReadWriteMany:读写权限,允许被多个Node挂载。

如果Pod想申请某种类型的PV,则首先需要定义一个PersistentVolumeClaim对象:

kind:PersistentVolumeClaim
apiVersion:v1
metadata:
name:myclaim
spec:
	accessModes:
	- ReadWriteOnce
	resources:
		requests:
		storage:8Gi

在Pod的Volume定义中引用上述PVC即可:
volumes:
- name: mypd
persistentVolumeClaim:
claimName:myclaim

PV是有状态的对象:

  • Available:空闲状态
  • Bound:已经绑定到某个PVC上
  • Released:对应的PVC已经被删除,但资源还没有被集群收回
  • Failed:PV自动回收失败
Namespce

用于多租户的资源隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。

查询namespace
kubectl get namespaces
NAME 			LABELS			STATUS
default			<none>			Active
如果不加声明,都会被定义到default的namespace

apiVersion:v1
kind:Namespace   ##
metadata:
	name:development
定义了一个名谓development的Namespace

一旦创建了Namespace,在创建资源对象时就可以指定这个资源对象属于哪个Namespace,例如:

apiVersion:v1
kind:Pod
metadata:
	name:busybox
	namespace:development ##
spec:
  containers:
	- image:busybox
	command:
		- sleep
		- "3600"
	name:busybox

如果不加参数使用kubectl get pods仅仅显示属于default命名空间的资源对象。
kubectl get pods --namespace=development
NAME 			READY		STATUS 		RESTARTS		AGE
busybox			1/1			Running		0 				1m
当给每个租户创建一个Namespace来实现多租户的资源隔离时,还能结合Kubernetes的资源配额管理,限定不同租户能占用的资源。
Annotation

注解,Annotation是用户任意定义的附加信息,以便于外部工具查找。通常,用Annotation记录的信息如下:

  • build信息,release信息,docker镜像信息等,例如时间戳,release id号,PR号、镜像Hash值,DOcker Registry地址等。
  • 日志库,监控库,分析库等资源库的地址信息
  • 程序调试工具信息,例如工具名称,版本号等
  • 团队的联系信息,例如电话号码,负责人名称,网址等
ConfigMap

配置文件中的参数在运行期间如何修改的问题。
Docker提供了两种方式:

  • 在运行时通过容器的环境变量来传递参数;
  • 通过Docker Volume将容器外的配置文件映射到容器内。

通常后一种方法适合我们的系统,但这也有个缺陷:我们必须在目标主机上先创建号对应的配置文件,然后才能映射到容器里。希望能集中管理系统的配置文件。

首先,把所有的配置项都当作key-value字符串,当然value可以来自某个文本文件,比如配置项passwd=123456,user=root,host=192.168.8.4用于表示俩你今儿FTP服务器的配置参数。这些配置项可以作为Map表中的一个项,整个Map的数据可以被持久化存储在K8S的etcd数据库中,然后提供API以方便K8S相关组件或客户应用CRUD操作这些数据,上述专门用来保存配置参数的Map就是K8S ConfigMap资源对象。

接下来,K8S提供一种内建机制,将存储在etcd中的ConfigMap通过Volume映射的方式变成目标Pod内的配置文件,不管目标Pod被调度到哪台服务器上,都会完成自动映射。进一步的,如果ConfigMap中的key-value数据被修改,则映射到Pod中的“配置文件”也会随之自动更新。于是,k8s ConfigMap就成了分布式系统中最为简单(使用简单,但背后实现比较复杂)且对应用无侵入的配置中心。

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐