Kubernetes为每一个Pod都分配了惟一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。Kubernetes要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通讯,这一般采用虚拟二层网络技术来实现,例如Flannel、Open vSwitch等。所以,在Kubernetes里,一个Pod里的容器与另外主机上的Pod容器可以直接通讯。
Pod有两种类型:普通的Pod和静态Pod(Static Pod),静态Pod不存放在etcd存储里,而是存放在某个具体的Node上的一个具体文件中,而且只在此Node上启动运行。普通的Pod一旦被建立,就会被存储到etcd中,随后会被Kubernetes Master调度到某个具体的Node上并进行绑定(Binding),该Node上的kubelet进程会将其实例化成一组相关的Docker容器并启动起来。当Pod里的某个容器中止时,Kubernetes会自动检测到这个问题而且从新启动这个Pod(重启Pod里的全部容器);若是Pod所在的Node宕机,则会将这个Node上的全部Pod从新调度到其余节点上运行。
Pod、容器与Node的关系以下图:
Kubernetes里的全部资源对象均可以采用yaml或者JSON格式的文件来定义或描述,下面是一个简单的Pod资源定义文件:php
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为pod代表这是一个Pod的定义,metadata里的name属性为Pod的名字,metadata里还能定义资源对象的标签(Label),这里声明myweb拥有一个name=myweb的标签(Label)。Pod里包含的容器组的定义则在spec一节中声明,这里定义了一个名字为myweb,对应镜像为kubeguide/tomcat-app: v1的容器,该容器注入了名为MYSQL_SERVICE_HOST='mysql'和MYSQL_SERVICE_PORT='3306'的环境变量(env关键字),而且在8080端口(containerPort)上启动容器进程。Pod的IP加上这里的容器端口,就组成了一个新的概念——Endpoint,它表明着此Pod里的一个服务进程的对外通讯地址。一个Pod也存在着具备多个Endpoint的状况,好比咱们把Tomcat定义为一个Pod时,能够对外暴露管理端口与服务端口这两个Endpoint。
Docker里的Volume在Kubernetes里也有对应的概念——Pod Volume,Pod Volume有一些扩展,好比能够用分布式文件系统GlusterFS等实现后端存储功能;Pod Volume是定义在Pod之上,而后被各个容器挂载到本身的文件系统中的。对于Pod Volume的定义咱们后面会讲到。
这里顺便提一下Event概念,Event是一个事件的记录,记录了事件的最先产生时间、最后重现时间、重复次数、发起者、类型,以及致使此事件的缘由等众多信息。Event一般会关联到某个具体的资源对象上,是排查故障的重要参考信息,当咱们发现某个Pod迟迟没法建立时,能够用kubectl describe pod xxx来查看它的描述信息,用来定位问题的缘由。
每一个Pod均可以对其能使用的服务器上的计算资源设置限额,当前能够设置限额的计算资源有CPU和Memory两种,其中CPU的资源单位为CPU(Core)的数量,是一个绝对值。
对于容器来讲一个CPU的配额已是至关大的资源配额了,因此在Kubernetes里,一般以千分之一的CPU配额为最小单位,用m来表示。一般一个容器的CPU配额被定义为100-300m,即占用0.1-0.3个CPU。与CPU配额相似,Memory配额也是一个绝对值,它的单位是内存字节数。
对计算资源进行配额限定须要设定如下两个参数:前端
一般咱们应该把Requests设置为一个比较小的数值,知足容器平时的工做负载状况下的资源需求,而把Limits设置为峰值负载状况下资源占用的最大量。下面是一个资源配额的简单定义:node
spec: containers: - name: db image: mysql resources: requests: memory: "64Mi" cpu: "250m" limits: memory: "128Mi" cpu: "500m"
最小0.25个CPU及64MB内存,最大0.5个CPU及128MB内存。mysql
Label至关于咱们熟悉的“标签”,给某个资源对象定义一个Label,就至关于给它打了一个标签,随后能够经过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes经过这种方式实现了相似SQL的简单又通用的对象查询机制。
Label Selector至关于SQL语句中的where查询条件,例如,name=redis-slave这个Label Selector做用于Pod时,至关于select * from pod where pod’s name = ‘redis-slave’这样的语句。Label Selector的表达式有两种:基于等式的(Equality-based)和基于集合的(Set-based)。下面是基于等式的匹配例子。
name=redis-slave:匹配全部标签为name=redis-slave的资源对象。
env != production:匹配全部标签env不等于production的资源对象。
下面是基于集合的匹配例子nginx
还能够经过多个Label Selector表达式的组合实现复杂的条件选择,多个表达式之间用“,”进行分隔便可,几个条件之间是“AND”的关系,即同时知足多个条件,例如:git
name=redis-slave, env!=production name not in (php-frontend), env!=production
以Pod为例,Label定义在metadata中:web
apiVersion: v1 kind: Pod metadata: name: myweb labels: app: myweb
RC和Service在spec中定义Selector与Pod进行关联:redis
apiVersion: v1 kind: ReplicationController metadata: name: myweb spec: replicas: 1 selector: app: myweb template: …………
Deployment、ReplicaSet、DaemonSet和Job则能够在Selector中使用基于集合的筛选条件:sql
selector: matchLabels: app: myweb matchExpressions: - {key: tier, operator: In, values: [frontend]} - {key: environment, operator: NotIn, values: [dev]}
matchLabels用于定义一组Label,与直接写在Selector中做用相同;matchExpressions用于定义一组基于集合的筛选条件,可用的条件运算符包括:In、NotIn、Exists和DoesNotExist。
若是同时设置了matchLabels和matchExpressions,则两组条件为“AND”关系,即全部条件须要同时知足才能完成Selector的筛选。
Label Selector在Kubernetes中的重要使用场景以下:docker
下面举个复杂点的例子,假设咱们为Pod定义了3个Label:release、env和role,不一样的Pod定义了不一样的Label。以下图所示,若是咱们设置了“role=frontend”的Label Selector,则会选取到Node 1和Node 2上的Pod。
若是咱们设置“release=beta”的Label Selector,则会选取到Node 2和Node 3上的Pod,以下图所示。
总结:使用Label能够给对象建立多组标签,Label和Label Selector共同构成了Kubernetes系统中最核心的应用模型,使得被管理对象可以被精细地分组管理,同时实现了整个集群的高可用性。
RC的做用是声明Pod的副本数量在任意时刻都符合某个预期值,因此RC的定义包括以下几个部分。
下面是一个完整的RC定义的例子,即确保拥有tier=frontend标签的这个Pod(运行Tomcat容器)在整个Kubernetes集群中始终有三个副本:
apiVersion: v1 kind: ReplicationController metadata: name: frontend spec: replicas: 3 selector: tier: frontend template: metadata: labels: app: app-demo tier: frontend spec: containers: - name: tomcat-demo image: tomcat imagePullPolicy: IfNotPresent env: - name: GET_HOSTS_FROM value: dns ports: - containerPort: 80
当咱们定义了一个RC并提交到Kubernetes集群中后,Master节点上的Controller Manager组件就获得通知,按期巡检系统中当前存活的目标Pod,并确保目标Pod实例的数量恰好等于此RC的指望值。若是有过多的Pod副本在运行,系统就会停掉多余的Pod;若是运行的Pod副本少于指望值,即若是某个Pod挂掉,系统就会自动建立新的Pod以保证数量等于指望值。
经过RC,Kubernetes实现了用户应用集群的高可用性,而且大大减小了运维人员在传统IT环境中须要完成的许多手工运维工做(如主机监控脚本、应用监控脚本、故障恢复脚本等)。
下面咱们来看下Kubernetes如何经过RC来实现Pod副本数量自动控制的机制,假如咱们有3个Node节点,在RC里定义了redis-slave这个Pod须要保持两个副本,系统将会在其中的两个Node上建立副本,以下图所示。
假如Node2上的Pod2意外终止,根据RC定义的replicas数量2,Kubernetes将会自动建立并启动一个新的Pod,以保证整个集群中始终有两个redis-slave Pod在运行。
系统可能选择Node1或者Node3来建立一个新的Pod,以下图。
经过修改RC的副本数量,能够实现Pod的动态缩放(Scaling)功能。kubectl scale rc redis-slave --replicas=3
此时Kubernetes会在3个Node中选取一个Node建立并运行一个新的Pod3,使redis-slave Pod副本数量始终保持3个。
因为Replication Controller与Kubernetes代码中的模块Replication Controller同名,同时这个词也没法准确表达它的意思,因此从Kubernetes v1.2开始,它就升级成了另一个新的对象——Replica Set,官方解释为“下一代的RC”。它与RC当前存在的惟一区别是:Replica Set支持基于集合的Label selector(Set-based selector),而RC只支持基于等式的Label selector(equality-based selector),因此Replica Set的功能更强大。下面是Replica Set的定义例子(省去了Pod模板部分的内容):
apiVersion: extensions/v1beta1 kind: ReplicaSet metadata: name: frontend spec: selector: matchLabels: tier: frontend matchExpressions: - {key: tier, operator: In, values: [frontend]} template: …………
Replica Set不多单独使用,它主要被Deployment这个更高层的资源对象所使用,从而造成一整套Pod建立、删除、更新的编排机制。
RC和RS的特性与做用以下:
Deployment相对于RC的最大区别是咱们能够随时知道当前Pod“部署”的进度。一个Pod的建立、调度、绑定节点及在目标Node上启动对应的容器这一完整过程须要必定的时间,因此咱们期待系统启动N个Pod副本的目标状态,其实是一个连续变化的“部署过程”致使的最终状态。
Deployment的典型使用场景有如下几个:
Deployment的定义与Replica Set的定义相似,只是API声明与Kind类型不一样。
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: nginx-deployment
apiVersion: v1 kind: ReplicaSet metadata: name: nginx-repset
下面是Deployment定义的一个完整例子:
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: frontend spec: replicas: 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 image: tomcat imagePullPolicy: IfNotPresent ports: - containerPort: 8080
能够经过命令kubectl get deployment来查看Deployment的信息,其中的几个参数解释以下:
Pod的管理对象,除了RC、ReplicaSet、Deployment,还有DaemonSet、StatefulSet、Job等,分别用于不一样的应用场景。
HPA与RC、Deployment同样,也属于Kubernetes资源对象。经过追踪分析RC或RS控制的全部目标Pod的负载变化状况,来肯定是否须要针对性地调整目标Pod的副本数。
HPA有如下两种方式做为Pod负载的度量指标:
CPUUtilizationPercentage是一个算术平均值,即目标Pod全部副本自带的CPU利用率的平均值。一个Pod自身的CPU利用率是该Pod当前CPU的使用量除以它的Pod Request的值,好比咱们定义一个Pod的Pod Request为0.4,而当前Pod的CPU使用量为0.2,则它的CPU使用率为50%,这样咱们就能够算出来一个RC或RS控制的全部Pod副本的CPU利用率的算术平均值了。若是某一时刻CPUUtilizationPercentage的值超过80%,则意味着当前的Pod副本数极可能不足以支撑接下来更多的请求,须要进行动态扩容,而当请求高峰时段过去后,Pod的CPU利用率又会降下来,此时对应的Pod副本数应该自动减小到一个合理的水平。
下面是HPA定义的一个具体的例子:
apiVersion: autoscaling/v1 kind: HorizontalPodAutoscaler metadata: name: php-apache namespace: default spec: maxReplicas: 10 minReplicas: 2 scaleTargetRef: kind: Deployment name: php-apache targetCPUUtilizationPercentage: 90
经过HPA控制php-apache的Pod副本,当Pod副本的CPUUtilizationPercentage的值超过90%时,会进行自动扩容增长Pod副本的数量,扩容或缩容时Pod的副本数量要介于2-10之间。
除了经过yaml文件来定义HPA对象以外,还能够经过命令的方式建立:kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
Pod的管理对象RC、Deployment、DaemonSet和Job都是面向无状态的服务,但实际中有不少服务是有状态的,好比Mysql集群、MongoDB集群、ZooKeeper集群等,可使用StatefulSet来管理有状态的服务。
StatefulSet有以下一些特性:
StatefulSet除了要与PV卷捆绑使用以存储Pod的状态数据,还要与Headless Service配合使用,即在每一个StatefulSet的定义中要声明它属于哪一个Headless Service。Headless Service与普通Service的区别在于,它没有Cluster IP,若是解析Headless Service的DNS域名,则返回的是该Service对应的所有Pod的Endpoint列表。StatefulSet在Headless Service的基础上又为StatefulSet控制的每一个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-3.kafka,这些DNS名称能够直接在集群的配置文件中固定下来。
1.概述
Service其实就是咱们常常提起的微服务架构中的一个“微服务”,Pod、RC等资源对象其实都是为它做“嫁衣”的。Pod、RC或RS与Service的逻辑关系以下图所示。
经过上图咱们看到,Kubernetes的Service定义了一个服务的访问入口地址,前端的应用(Pod)经过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是经过Label Selector来实现“无缝对接”的。而RC的做用其实是保证Service的服务能力和服务质量始终处于预期的标准。
经过分析、识别并建模系统中的全部服务为微服务——Kubernetes Service,最终咱们的系统由多个提供不一样业务能力而又彼此独立的微服务单元所组成,服务之间经过TCP/IP进行通讯,从而造成了强大而又灵活的弹性集群,拥有了强大的分布式能力、弹性扩展能力、容错能力。所以,咱们的系统架构也变得简单和直观许多。
既然每一个Pod都会被分配一个单独的IP地址,并且每一个Pod都提供了一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,多个Pod副本组成了一个集群来提供服务,那么客户端如何来访问它们呢?通常的作法是部署一个负载均衡器(软件或硬件),但这样无疑增长了运维的工做量。在Kubernetes集群里使用了Service(服务),它提供了一个虚拟的IP地址(Cluster IP)和端口号,Kubernetes集群里的任何服务均可以经过Cluster IP+端口的方式来访问此服务,至于访问请求最后会被转发到哪一个Pod,则由运行在每一个Node上的kube-proxy负责。kube-proxy进程其实就是一个智能的软件负载均衡器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制。
下面是一个Service的简单定义:
apiVersion: v1 kind: Service metadata: name: tomcat-service spec: ports: - port: 8080 selector: tier: frontend
上述内容定义了一个名为“tomcat-service”的Service,它的服务端口为8080,拥有“tier=frontend”这个Label的全部Pod实例。
不少服务都存在多个端口的问题,一般一个端口提供业务服务,另一个端口提供管理服务,好比Mycat、Codis等常见中间件。Kubernetes Service支持多个Endpoint,要求每一个Endpoint定义一个名字来区分,下面是tomcat多端口的Service定义样例。
apiVersion: v1 kind: Service metadata: name: tomcat-service spec: ports: - port: 8080 name: service-port - port: 8005 name: shutdown-port selector: tier: frontend
多端口为何须要给每一个端口命名呢?这就涉及Kubernetes的服务发现机制了。
2.Kubernetes的服务发现机制
每一个Kubernetes中的Service都有一个惟一的Cluster IP及惟一的名字,而名字是由咱们本身定义的,那咱们是否能够经过Service的名字来访问呢?
最先时Kubernetes采用了Linux环境变量的方式来实现,即每一个Service生成一些对应的Linux环境变量(ENV),并在每一个Pod的容器启动时,自动注入这些环境变量,以实现经过Service的名字来创建链接的目的。
考虑到经过环境变量获取Service的IP与端口的方式仍然不方便、不直观,后来Kubernetes经过Add-On增值包的方式引入了DNS系统,把服务名做为DNS域名,这样程序就能够直接使用服务名来创建链接了。
关于DNS的部署,后续博文我会单独讲解,有兴趣的朋友能够关注个人博客。
3.外部系统访问Service的问题
Kubernetes集群里有三种IP地址,分别以下:
外部访问Kubernetes集群里的某个节点或者服务时,必需要经过Node IP进行通讯。
Pod IP是Docker Engine根据docker0网桥的IP地址段进行分配的一个虚拟二层网络IP地址,Pod与Pod之间的访问就是经过这个虚拟二层网络进行通讯的,而真实的TCP/IP流量则是经过Node IP所在的物理网卡流出的。
Service的Cluster IP具备如下特色:
咱们的应用若是想让外部访问,最经常使用的做法是使用NodePort方式。
apiVersion: v1 kind: Service metadata: name: tomcat-service spec: type: NodePort ports: - port: 8080 nodePort: 31002 selector: tier: frontend
NodePort的实现方式是在Kubernetes集群里的每一个Node上为须要外部访问的Service开启一个对应的TCP监听端口,外部系统只要用任意一个Node的IP地址+具体的NodePort端口号便可访问此服务。
NodePort尚未彻底解决外部访问Service的全部问题,好比负载均衡问题,经常使用的作法是在Kubernetes集群以外部署一个负载均衡器。
Load balancer组件独立于Kubernetes集群以外,能够是一个硬件负载均衡器,也能够是软件方式实现,例如HAProxy或者Nginx。这种方式,无疑是增长了运维的工做量及出错的几率。
因而Kubernetes提供了自动化的解决方案,若是咱们使用谷歌的GCE公有云,那么只须要将type: NodePort改为type: LoadBalancer,此时Kubernetes会自动建立一个对应的Load balancer实例并返回它的IP地址供外部客户端使用。其余公有云提供商只要实现了支持此特性的驱动,则也能够达到上述目的。
Volume是Pod中可以被多个容器访问的共享目录。Volume定义在Pod上,被一个Pod里的多个容器挂载到具体的文件目录下,当容器终止或者重启时,Volume中的数据也不会丢失。Kubernetes支持多种类型的Volume,例如GlusterFS、Ceph等分布式文件系统。
除了可让一个Pod里的多个容器共享文件、让容器的数据写到宿主机的磁盘上或者写文件到网络存储中,Kubernetes还提供了容器配置文件集中化定义与管理,经过ConfigMap对象来实现。
Kubernetes支持多种Volume类型,下面咱们一一进行介绍。
1.emptyDir
emptyDir是在Pod分配到Node时建立的,它的初始内容为空,而且无须指定宿主机上对应的目录文件,它是Kubernetes自动分配的一个目录,当Pod从Node上移除时,emptyDir中的数据也会被永久删除。
emptyDir的用途以下:
emptyDir的定义以下:
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
2.hostPath
使用hostPath挂载宿主机上的文件或目录,主要用于如下几个方面:
使用hostPath时,须要注意如下几点:
hostPath的定义以下:
volumes: - name: "persistent-storage" hostPath: path: "/data"
3.gcePersistentDisk
使用这种类型的Volume表示使用谷歌公有云提供的永久磁盘(Persistent Disk,PD)存放数据,使用gcePersistentDisk有如下一些限制条件:
经过gcloud命令建立一个PD:gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk
定义gcePersistentDisk类型的Volume的示例以下:
volumes: - name: test-volume gcPersistentDisk: pdName: my-data-disk fsType: ext4
4.awsElasticBlockStore
与GCE相似,该类型的Volume使用亚马逊公有云提供的EBS Volume存储数据,须要先建立一个EBS Volume才能使用awsElasticBlockStore。
使用awsElasticBlockStore的一些限制条件以下:
经过aws ec2 create-volume命令建立一个EBS 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: ext4
5.NFS
使用NFS网络文件系统提供的共享目录存储数据时,咱们须要在系统中部署一个NFS Server。
定义NFS类型的Volume的示例以下:
volumes: - name: nfs-volume nfs: server: nfs-server.localhost path: "/"
6.其余类型的Volume
上面提到的Volume是定义在Pod上的,属于“计算资源”的一部分,而实际上,“网络存储”是相对独立于“计算资源”而存在的一种实体资源。好比在使用云主机的状况下,咱们一般会先建立一个网络存储,而后从中划出一个“网盘”并挂载到云主机上。Persistent Volume(简称PV)和与之相关联的Persistent Volume Claim(简称PVC)实现了相似的功能。
PV与Volume的区别以下:
下面是NFS类型PV的yaml定义内容,声明了须要5G的存储空间:
apiVersion: v1 kind: PersistentVolume metadata: name: pv003 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce nfs: path: /somepath server: 172.17.0.2
PV的accessModes属性有如下类型:
若是Pod想申请使用PV资源,则首先须要定义一个PersistentVolumeClaim(PVC)对象:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi
而后在Pod的volume定义中引用上述PVC便可
volumes: - name: mypd persistentVolumeClaim: claimName: myclaim
PV是有状态的对象,它有如下几种状态:
经过将Kubernetes集群内部的资源对象“分配”到不一样的Namespace中,造成逻辑上分组的不一样项目、小组或用户组,便于不一样的分组在共享使用整个集群的资源的同时还能被分别管理。
Kubernetes集群在启动后,会建立一个名为“default”的Namespace,经过kubectl能够查看到:kubectl get namespaces
若是不特别指明Namespace,则用户建立的Pod、RC、RS、Service都奖被系统建立到这个默认的名为default的Namespace中。
下面是Namespace的定义示例:
apiVersion: v1 kind: Namespace metadata: name: development
定义一个Pod,并指定它属于哪一个Namespace:
apiVersion: v1 kind: Pod metadata: name: busybox namespace: development spec: containers: - image: busybox command: - sleep - "3600" name: busybox
使用kubectl get命令查看Pod状态信息时,须要加上--namespace参数,指定查看哪一个namespace下的资源对象,不加这个参数则默认查看 default下的资源对象。kubectl get pods --namespace=development
当咱们给每一个租户建立一个Namespace来实现多租户的资源隔离时,还能结合Kubernetes的资源配额管理,限定不一样租户能占用的资源,例如CPU使用量、内存使用量等。
Annotation与Label相似,也使用key/value键值对的形式进行定义。不一样的是Label具备严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),而且用于Label Selector。而Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找。一般Kubernetes的模块会经过Annotation的方式标记资源对象的一些特殊信息。
使用Annotation来记录的信息以下:
注:本文内容摘自《Kubernetes权威指南:从Docker到Kubernetes实践全接触(记念版》,并精减了部份内部,并且对部份内容作了相应的调整,能够帮助你们加深对Kubernetes的各类资源对象的理解和定义方法,关注个人博客,跟我一块儿开启k8s的学习之旅吧。