第1章 Kubernetes入门
1.1 Kubernetes是什么
首先,它是一个全新的基于容器技术的分布式架构领先方案。
其次,如果我们的系统设计遵循了k8s的设计思想,那么传统架构中那些和业务无关的底层代码和模块,都可以从我们的视线消失。我们不必再费心于负载均衡器的选项和部署实施
的问题,不必再考虑引入或者自己开发一个复杂的服务治理框架,不必再头疼于服务监控和故障处理模块的开发。
然后,k8s是一个开放平台,任何语言编写的服务,都可以被映射为k8s的Service(服务),并通过TCP通信协议进行交互。此外,k8s平台对现有的编程语言、编程框架、中间件
没有任何入侵性,因此现有的系统也很容易升级到k8s平台上。
最后,k8s是一个完备的分布式系统支持平台。k8s具有完备的集群管理能力,包括多层次的安全防护和准入机制、多租户应用支持能力、透明的服务注册和服务发现机制、内建
的职能负载均衡器、强大的故障发现和自我修复能力、服务滚动升级和在线扩容能力、可扩展的资源自动调度机制,以及多粒度的资源配额管理能力。同时,k8s提供了完善的管理工具,
这些工具包含开发、部署测试、运维监控在内的各个环节。因此,k8s是一个基于容器技术的分布式架构解决方案,并且是一个一站式的完备的分布式系统开发和支持平台。
在k8s中,Service是分布式集群架构中的核心,一个Service对象拥有如下关键特征:
1.拥有唯一指定的名称(比如mysql-server);
2.拥有一个虚拟IP(ClusterIP、Service IP 或者 VIP)和端口号;
3.能够提供某种远程服务能力;
4.被映射到提供这种服务能力的一组容器应用上
Service的服务进程目前都是基于socket通信方式对外提供服务,比如,redis,mamcache,mysql,web server,或者是实现了某个具体业务的特定的TCP Server进程。
虽然一个Service通常由多个相关的服务进程提供服务,每个服务进程都有一个独立的Endpoint(IP+Port)访问点,但k8s能够让我们通过Service(虚拟Cluster IP + Service
Port)连接到指定的Service。有了k8s内建的透明负载均衡和故障恢复机制,不管后端有多少服务进程,也不管某个服务进程是否发生故障而被重新部署到其他机器,都不会影响对服务
的正常调用。更重要的是,这个Service本事一旦创建就不再变化,这意味着我们再也不用为k8s集群中服务的ip地址变化而头疼了。
容器提供了强大的隔离功能,所有有必要把为Service提供服务的这组进程放入容器中进行隔离。为此,k8s设计了Pod对象,将每个服务进程都包装到相应的Pod中,使其成为
在Pod中运行的一个容器(Container)。为了建立Service和Pod之间的关联关系,k8s首先给每个Pod都贴上一个标签(Label),给运行的mysql的Pod贴上name=mysql标签,
给运行PHP的Pod贴上name=php的标签,然后给相应的Service定义标签选择器(Label Selector),比如MySQL Service的标签选择器的选择条件为 name=mysql,意为
该Service要作用于包含 name=mysql Label的Pod。这样就解决了Service和Pod的关联问题。
这里介绍Pod的概念。首先,Pod运行在一个被称为节点(Node)的环境中,这个节点既可以是物理机,也可以是私有云或者公有云的一个虚拟机,通常在一个节点上运行几百个Pod。
其次,在每个Pod中都运行着一个特殊的被称为Pause的容器,其他容器则被称为 业务容器。这些业务容器共享Pause容器的网络栈和Volume挂载卷,因此它们之间的通信和数据交换
都更为高效,在设计时我们可以充分利用这一特性将一组密切相关的服务进程放在容一个Pod中;最后,需要注意的是,并不是每个Pod和它里面运行的容器都能被映射到一个Service
上,只有提供服务的那组Pod才会被映射为一个服务。
在集群管理方面,k8s将集群中的机器划分为一个Master和一些Node。在master上运行着集群管理相关的一组进程 kube-apiserver、kube-controller-manager和
kube-scheduler,这些进程实现了整个集群的资源管理、Pod调度、弹性伸缩、安全控制、系统监控和纠错等管理功能,并且都是自动完成的。Node作为集群中的工作节点,
运行真正的应用程序,在Node上k8s管理的最小运行单位是Pod。在Node上运行着k8s的 kubelet,kube-proxy 服务进程,这些服务进程负责Pod的创建、启动、监控、重启、
销毁,以及实现软件模式的负载均衡器。
最后,看看传统的IT系统中服务扩容和升级两个难题。在k8s集群中,只需为需要扩容的Service关联的Pod创建一个RC(Replication Controller),在一个RC定义文件
中包含以下3个关键信息:
a) 目标Pod的定义;
b) 目标Pod需要运行的副本数量(Replicas)
c) 要监控的目标Pod的标签
在创建好RC(系统将自动创建好Pod)后,k8s会通过在RC中定义的Label筛选出对应的Pod实例并实时监控其状态和数量,如果实例数量少于定义的副本数量,则会根据在RC
中定义的Pod模板创建一个新的Pod,然后将此Pod调度到合适的Node上启动运行,直到Pod实例的数量达到预定义的目标。这个过程完全是自动化的。有了RC,服务扩容就变成一个
简单的数字游戏了,只需要修改RC中的副本数量即可。后续的服务升级也将通过RC来自动完成。
1.2 为什么要用Kubernetes
1.首先,可以轻装上阵的开发复杂系统;
2.其次,可以全面拥有微服务架构;
3.再次,可以随时随地的将系统整体搬到公有云上;
4.然后,k8s内在的服务弹性扩容机制可以让我们轻松应对突发流量;
5.最后,k8s系统架构超强的横向扩容能力可以让我们的竞争力大大提升。
1.3 从一个简单的例子开始
1.3.1 环境准备
通过 kubeadm 快速安装一个 k8s。
1.3.2 启动MySQL服务
首先,为mysql服务创建一个RC定义文件 mysql-rc.yaml,如下:
apiVersion:v1 #副本控制器RC
kind:ReplicationController
metadata:
name:mysql #RC的名称,全局唯一
spec:
replicas:1 #Pod副本的期待数量
selector:
app:mysql #符合目标的Pod拥有此标签
template: #根据此模板创建Pod的副本(实例)
metadata:
labels:
app:mysql #Pod副本拥有的标签,对应RC的Selector
sepc:
containers: #Pod内容器的定义部分
- name:mysql #容器的名称
image:mysql #容器对应的Docker Image
ports:
- containerPort:3306 #容器应用监听的端口号
env: #注入容器内的环境变量
- name:MYSQL_ROOT_PASSWORD
value:"123456"
以上YAML定义文件中的kind属性来表明此资源对象的类型,比如这里的值为 ReplicationController,表示这是一个RC;在spec一节中是RC相关属性定义,比如
spec.selector是RC的Pod标签选择器,即监控和管理拥有这些标签的Pod实例,确保在当前集群中始终有且仅有 replicas 个Pod实例在运行,这里设置 replicas=1,
表示只能运行一个mysql Pod实例。当前集群中运行的Pod数量少于 replicas时,RC会根据在 spec.template 一节中定义的Pod模板来生成一个新的Pod实例,
spec.template.metadata.labels 指定了该Pod的标签,需要特别注意的是:这里的labels必须匹配之前的spec.selector,否则此RC每创建一个无法匹配Label的
Pod,就会不停的尝试创建新的Pod,陷入恶性循环中。
在创建好 mysql-rc.yaml文件后,为了将它发布到k8s集群中,我们在Master上执行命令:
kubeclt create -f mysql-rc.yaml
接下来,用kubectl命令查看刚刚创建的RC:
kubectl get rc
查看Pod的创建情况时,可以运行下面的命令:
kubectl get pods
我们通过 docker ps 指令查看正在运行的容器,发现提供mysql服务的Pod容器已经创建并正常运行了,此外会发现mysql Pod对应的容器还多创建了一个来自谷歌的
pause容器,这就是Pod的"根容器"。
最后,创建一个与之关联的 k8s Service --- mysql 的定义文件(文件名为 mysql-src.yaml)
apiVersion:v1
kind:Service #表明是 k8s Service
metadata:
name:mysql #Service的全局唯一名称
spec:
ports:
- port:3306 #Service 提供服务的端口号
selector:
app:mysql #Service对应的Pod拥有这里定义的标签
其中,metadata.name 是 Service的服务名(ServiceName);port属性则定义了 Service 的虚端口;spec.selector 确定了哪些Pod副本(实例)对应本服务。
类似的,我们通过kubectl create 命令创建Service对象。
kubectl create -f mysql-src.yaml
查看刚刚创建的Service:
kubectl get svc
可以发现,mysql服务被分配了一个值为 169.169.253.143 的Cluster IP地址。随后,k8s集群中其他新创建的Pod就可以通过 Service的Cluster IP+端口号 3306
来连接和访问它了。
通常,Cluster IP 是在 Service 创建后由 k8s 系统自动分配的,其他Pod无法预先知道某个 Service 的Cluster IP地址,因此需要一个服务发现机制来找到这个
服务。为此,最初时,k8s巧妙的使用了Linux的环境变量来解决这个问题,后面还有其他机制。现在只需要知道,根据Service的唯一名称,容器可以从环境变量中获取Service
对应的Cluster IP 地址和端口,从而发起tcp/ip连接请求。
1.3.3 启动Tomcat应用
1.3.4 通过浏览器访问网页
1.4 Kubernetes的基本概念和术语
k8s 中的大部分概念如Node,Pod,Replication Controller,Service等都可以被看做是一种资源,几乎所有的资源对象都可以通过k8s提供的kubectl(或者API来
调用)执行增删查改等操作并将其保存在etcd中持久化存储。从这个角度看,k8s其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的"资源期望状态"与当前
环境中"实际资源状态"的差异来实现自动控制和自动纠错的高级功能。
在声明一个k8s资源对象的时候,需要注意一个关键属性:apiVersion。
k8s采用了"核心+外围扩展"的设计思路,在保持平台核心稳定的同时具备持续演进升级的优势。k8s大部分的核心资源对象都归属于v1这个核心API,比如Node,Pod,Service,
Endpoints,Namespace,RC,PersistentVolume等。在版本迭代过程中,k8s先后扩展了extensions/v1leta1,apps/v1beta1,apps/v1beta2等API组,而在1.9版本
之后引入了apps/v1这个正式的扩展API组,正式淘汰了extensions/v1beta1,apps/v1beta1,apps/v1beta2 这3个API组。
我们可以采用yaml或者json格式声明(定义或者创建)一个k8s资源对象,每个资源对象都有自己的特定语法格式(可以理解为数据库中的一张表),但随着k8s版本的持续升级,
一些资源对象会不断引入新的属性。为了在不影响当前功能的情况下引入新特性的支持,我们通常会采用如下2种方法:
方法1:在设计数据库的时候,在每个表中都添加一个很长的备注字段,之后扩展的数据以某种格式(如xml,json,字符串拼接)放入备注字段。因为数据库表的结构没有变化,
所以此时程序的改动范围是最小的,但看起来不太美观。
方法2:直接修改数据库,增加一个或者多个新的列,此时程序的改动范围比较大。但看起来比较美观。
虽然,2种方法都不完美。更优雅的做法是,先采用方法1实现这个新的特性,经过几个版本的迭代后,等新特性稳定成熟之后,可以在后续的版本中采用方法2升级到正式版。
为此,k8s为每个资源对象添加了类似数据库表里备注字段的通用属性 Annotations,实现方法1的升级。
1.4.1 Master
k8s里的Master是指集群控制节点,在每个k8s集群里都需要有一个Master来负责整个集群的管理和控制,基本上k8s的所有控制命令都发给它,它负责具体的执行过程,
我们后面执行的所有命令基本都是在Master上运行的。Master通常会占据一个独立的服务器(高可用部署建议用3台),主要是因为它太重要了,是整个集群的"首脑"。
在Master上运行着以下关键的进程:
1.k8s API Server(kube-apiserver)
提供了 HTTP Rest 接口的关键服务进程,是k8s里所有资源的增删查改等操作的唯一入口,也是集群控制的入口进程。
2.k8s Controller Manager(kube-controller-manager)
k8s里所有的资源对象的自动化控制中心,可以将其理解为资源对象的"大总管"。
3.k8s Scheduler(kube-scheduler)
负责资源调度(Pod调度)的进程,相当于公交公司的"调度室"。
另外,在Master上通常还需要部署etcd服务,因为k8s里的所有资源对象的数据都被保存在etcd中。
1.4.2 Node
除了Master,k8s集群中的其他机器被称为Node,在较早的版本中也被称为 Minion。与Master一样,Node可以是一台物理机,也可以是一台虚拟机。Node是k8s
集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker 容器),当某个Node宕机时,其他的工作负载会被Master自动转移到其他节点上。
在每个Node上都运行着以下关键进程:
1.kubelet
负责Pod对应的容器的创建、启停等任务,同时与Master密切协作,实现集群管理的基本功能。
2.kube-proxy
实现k8s Service的通信与负载均衡机制的重要组件。
3.Docker Engine
Docker引擎,负责本机的容器创建和管理工作。
Node可以在运行期间动态增加到k8s集群中,前提是这个节点已经正确的安装、配置和启动了上述关键进程,在默认情况下kubelet会向Master注册自己,这也是k8s推荐的
Node管理方式。一旦Node被纳入集群管理范围,kubelet进程就会定时向Master汇报自身的情况,如操作系统、Docker版本、机器的cpu和内存情况,以及当前有哪些Pod在
运行等,这样Master就可以获知每个Node的资源使用情况,并实现高效的负载均衡调度策略。而某个Node在超过指定时间不上报信息时,会被Master判定为"失联",Node的状态
被标记为不可用(Not Ready),随后Master会触发"工作负载大转移"的自动流程。
查看集群有多少个node
kubelet get nodes
查看某个node的详细信息
kubelet describe node <node_name>
上述命令展示了Node的如下关键信息:
1.Node的基本信息
名称、标签、创建时间等
2.Node的当前运行状态
Node启动后会做一系列的自检工作,比如磁盘空间是否不足(DiskPressure)、内存是否不足(MemoryPressure)、网络是否正常(NetworkUnavailable)、
PID资源是否充足(PIDPressure)。在一切正常时设置Node为Ready状态(Ready=true),该状态表示Node处于健康状态,Master将可以在其上调度新任务了。
3.Node的主机地址与主机名
4.Node上的资源数量,描述node可用的系统资源,包括cpu,内存数量,最大可调度的Pod数量等
5.Node可分配的资源量
描述Node当前可用于分配的资源量
6.主机系统信息
包括主机ID,系统UUID,Linux kernel版本号,操作系统类型与版本,Docker版本号,kubelet与kube-proxy的版本号等。
7.当前运行的Pod列表概要信息
8.已分配的资源使用概要信息,例如资源申请的最低、最大允许使用量占用系统总量的百分比。
9.Node相关的Event信息
1.4.3 Pod
每个Pod都有一个特殊的被称为"根容器"的Pause容器,Pause容器对应的镜像属于k8s平台的一部分,除了Pause容器,每个Pod还包含一个或者多个紧密相关的用户业务
容器。
为什么k8s会设计出一个全新的Pod概念并且Pod有这样特殊的组成结构?
原因1:
在一组容器作为一个单元的情况下,我们很难简单的对"整体"进行判断以及有效行动。比如,一个容器死亡了,此时算是整体死亡了吗?还是n/m的死亡率?引入
与业务无关并且不容易死亡的Pause容器作为Pod的根容器,以它的状态代表整个容器组的状态,就简单、巧妙多了。
原因二:
Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂接的Volume,这样就简化了密切关联的业务容器之间的通信问题,也很好的解决了它们之间的文件
共享问题。
k8s为每个Pod都分配了唯一的ip地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP地址。k8s要求底层网络支持集群内任意两个Pod之间的tcp/ip直接通信,这
通常采用虚拟二层网络技术来实现,例如 Flannel,Open vSwitch等,因此我们需要牢记一点:在k8s里,一个Pod里的容器与另外主机上的Pod容器是能够直接通信的。
Pod其实有2种类型:普通的Pod和静态Pod(Static Pod)。后者比较特殊,它并没有被存放在etcd里面,而是被存放在某个具体的node上的一个具体文件中,并且只在
此Node上启动、运行。而普通的Pod一旦被创建,就会被存放如etcd中,随后会被k8s Master调度到某个具体的Node上进行绑定(Binding),随后该Pod对应的Node上的
kubelet进程实例化成一组相关的docker容器启动。在默认情况下,当Pod里的某个容器停止时,k8s自动检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),
如果Pod所在的Node宕机,就会将这个Node上的所有Pod调度到其他节点上。
k8s的所有资源对象都可以采用yaml或者json格式的文件来定义或者描述,如下:
apiVersion:v1
kind:Pod
metadata:
name:myweb
lables:
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里还能定义资源对象的标签,这里声明myweb拥有一个name=myweb的标签,
在Pod里所包含的容器组的定义则在spec一节中声明,这里定义了一个名为myweb、对应镜像为 kubeguide/tomcat-app:v1 的容器,该容器注入了名为
MYSQL_SERVICE_HOST='mysql'和MYSQL_SERVICE_PORT='3306'的环境变量(env关键字),并且在8080端口(containerPort)启动容器进程。Pod的IP加上这里的
容器端口(containerPort),组成了一个新的概念---Endport,它代表此Pod例的一个服务进程的对外通信地址。一个Pod也存在多个Endpoint的情况。
我们所熟悉的docker Volume在k8s里也有对应的概念 --- Pod Volume,后者有一些扩展,比如可以用分布式文件系统GlusterFS实现后端存储功能;Pod Volume
是被定义在Pod上,然后被各个容器挂载到自己的文件系统中的。
这里提下Event概念,它是一个事件的记录。记录了事件的最早产生时间、最后重现时间、重复次数、发起者、类型,以及导致此次事件的原因等众多信息。Event通常会
被关联到某个具体的资源对象上,是排查故障的重要参考信息,之前我们看到Node的描述信息有Event,而Pod同样有Event记录,当我们发现某个Pod迟迟无法创建的时候,
可以用kubelet describe pod xxx 来查看它的描述信息。
每个Pod都可以对其能使用的服务器上的计算资源设置限额,当前可以设置限额的计算资源有cpu和memory两种,其中cpu的资源单位为cpu(Core)的数量,是一个绝对值
而非相对值。
对于绝大多数容器来说,一个cpu的资源配额相当大,所以在k8s里面通常以千分之一的cpu配额为最小单位,用m来表示。通常一个容器的cpu配额被定义为100~300m,
即0.1~0.3个cpu,由于cpu配额是一个绝对值,所以无论是在拥有一个Core的机器上,还是在拥有48个Core的机器上,100m这个配额所代表的的cpu的使用量都是一样的。
与cpu配额类似,Memory配额也是一个绝对值,它的单位是内存字节数。
在k8s里,一个计算资源进行配额限制时需要设定以下2个参数:
1.Requests:该资源的最小申请量,系统必须满足;
2.Limits:该资源最大允许使用的量,不能被突破。当容器试图使用超过这个量的资源时,可能会被杀掉重启。
通常,我们会把Requests设置为一个较小的数值,符合容器平时的工作负载的情况下的资源需求,而把Limits设置为峰值负载情况下资源占用的最大值。下面的定义表明,
mysql容器申请最少0.25个cpu以及64MiB内存,在运行过程中mysql容器所能使用的资源配额为0.5个cpu以及128MiB内存。
spec:
containers:
- name:db
image:mysql
resources:
requests:
memory:"64Mi"
cpu:"250m"
limits:
memory:"128Mi"
cpu:"500m"
1.4.4 Label
Label 是k8s系统中另外一个核心的概念。一个Label是一个key=value的键值对,其中key与value由用户自己指定。Label可以被附加到各种资源对象上,例如Node,
Pod,Service,RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意的资源对象上。Label通知在资源对象定义时确定,也可以在对象创建后
动态添加或者删除。
我们可以通过给指定资源对象捆绑一个或者多个不同的Label来实现多维度的资源分组管理功能,以便灵活、方便的进行资源分配、调度、配置、部署等管理工作。一些常用的
Label 如下:
1.版本标签:"release":"stable"、"release":"canary"
2.环境标签:"environment":"dev"、"environment":"qa"、"environment":"production"
3.架构标签:"tier":"frontend"、"tier":"backend"、"tier":"middleware"
4.分区标签:"partition":"customerA"、"partition":"customerB"、"partition":"customerC"
5.质量管控标签:"track":"daily"、"track":"weekly"
Label相当于我们熟悉的"标签"。给某个资源对象定义的一个Label,就相当于给它打了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有这些Label
的资源对象,k8s通过这种方式实现了类似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),前者采用等式表达式匹配
标签,如下:
a) name=redis-slave:匹配所有具有标签 name=redis-slave 的资源对象;
b) env != production:匹配所有不具有标签 env=production的资源对象
后者是使用集合操作类表达式匹配标签,如下:
a) name in (redis-master,redis-slave):匹配具有标签 name=redis-master或者 name=redis-slave 的资源对象;
b) name not in (php-frontend):匹配不具有标签 name=php-frontedn 的资源对象。
可以通过多个Label Selector表达式的组合实现复杂的条件选择,多个表达式用","进行分割,几个条件之间是"AND"的关系,如下:
name=redis-master,env!=production
以 myweb Pod 为例,Label被定义在其中meatadata中:
apiVersioin:v1
kind:Pod
metadata:
name:myweb
labels:
app:myweb
管理对象RC和Service则通过Selector字段设置需要关联的Pod的Label:
apiVersion:v1
kind:ReplicationController
metadata:
name:myweb
spec:
replicas:1
selector:
app:myweb
...
其他管理对象如Deploment,ReplicaSet,DaemonSet和Job则可以在Selector中使用基于集合的筛选条件定义,例如:
selector:
matchLabels:
app:myweb
matchExpressions:
- {key:tier, operator:In, values:[frontend]}
- {key:enviroment, operator:NotIn, values:[dev]}
matchLabels 用于定义一组Labels,与直接写在Selector中的作用相同;matchExpressions用于定义一组基于集合的筛选条件,可用的条件运算包括In,NotIn,
Exists,DoesNotExist。
如果同时设置了 matchLabels和matchExpressions,则两组条件为AND关系,即需要同时满足所有条件才能完成Selector筛选。
Label Selector 在k8s中的重要场景如下:
1.kube-controller 进程通过在资源对象RC上定义的Label Selector来筛选要监控的Pod副本数量,使Pod副本数量始终符合预期设定的全自动流程控制流程。
2.kube-proxy 进程通过Service的Label Selector来选择对应的Pod,自动建立每个Service到对应的Pod的请求转发路由表,从而实现Service的智能
负载均衡机制。
3.通过某些Node定义特定的Label,并且在Pod定义文件中使用NodeSelector这种标签调度策略,kube-scheduler进程可以实现Pod定向调度的特性。
总之,使用Label可以给对象创建多组标签,Label和Label Selector共同构成了k8s系统中核心的应用模型,使得被管理对象能够被精细的分组管理,同时实现了整个
集群的高可用性。
1.4.5 Replication Controller
RC 是k8s系统中核心的概念之一,它定义的一个期望的场景,即声明某种Pod的副本数量在任意时刻都符合某个预期值,所以RC的定义包括如下几个部分:
1.Pod期待的副本数量;
2.用于筛选目标的Pod的Label Selector;
3.当Pod的副本数量小于预期数量时,用于创建新的Pod的Pod模板(template)
在我们定义了一个RC并将其提交到k8s集群后,Master上的Controller Manager组件就得到通知,定期巡检系统中当前存活的目标Pod,并确保Pod实例数量刚好
等于此RC的期望值。
在运行时,我们可以通过修改RC的副本数量,来实现Pod的动态缩放(Scaling),这可以通过执行 kubectl scale 命令来完成:
kubectl scale rc redis-slave --replicas=3
需要注意的是,删除RC并不会影响通过该RC已经创建好的Pod。为了删除所有Pod,可以设置replicas的值为0,然后更新该RC。此外,kubectl提供了stop和delete
命令来一次性删除RC和RC控制的Pod。
Replication Controller 由于与k8s 代码中的模块 Replication Controller 同名,同时"Replication Controller"无法准确的表达它的本意,所以在
k8s 1.2中,升级为另外一个新概念 --- Replica Set,官方解释为"下一代的RC"。Replica Set与 RC当前唯一的区别是,Replica Set支持基于集合的Label
Selector,而RC只支持基于等式的Label Selector,这使得Replica Set的功能更强。举例:
apiVersion:extensions/v1beta1
kind:ReplicaSet
medadata:
name:frontend
spec:
selector:
matchLabels:
tier:frontend
matExpressions:
- {key:tier, operator:In, values:[frontend]
template:
...
kubectl 命令工具适用于 RC的绝大部分命令同样适用于Replica Set。此外,我们当前很少单独使用Replica Set,它主要被 Deployment 这个更高层的资源对象
所使用,从而形成一套Pod创建,删除,更新的编排机制。我们在使用Deployment时,无需关心它是如何创建和维护的Replica Set,这一切都是自动发生的。
最后总结下,RC的一些特性与作用:
1.在大多数情况下,我们通过定义一个RC实现Pod的创建及副本数量的自动控制;
2.在RC里包括完整的Pod定义模板;
3.RC通过Label Selector机制实现对Pod副本的自动控制;
4.通过改变RC里的Pod副本数量,可以实现Pod的扩容或者缩容;
5.通过改变RC里Pod模板的镜像版本,可以实现Pod的滚动升级。
1.4.6 Deployment
Deployment 是 k8s 在1.2版本中引入的新概念,用于更好的解决Pod的编排问题。为此,Deployment 在内部使用了Replica Set 来实现目的,我们可以把它看做是
RC的一次升级。
Deployment相对于RC的一个最大升级是我们可以随时知道当前Pod的"部署"的进程。实际上由于一个Pod的创建、调度、绑定节点以及目标Node上启动对应的容器这一
完整过程需要一定的时间,所以我们期待系统启动N个Pod的副本的目标状态,实际上是一个连续变化的"部署过程"导致的最终状态。
Deployment 的典型使用场景有以下几个:
1.创建一个Deployment 对象来生成对应的Replica Set 并完成 Pod的副本的创建;
2.检查Deployment 的状态来看部署动作是否完成(Pod副本数量是否达到预期的值);
3.更新Deployment 以创建新的Pod(比如镜像的升级);
4.如果当前Deployment 不稳定,则回滚到一个早先的Deployment 版本;
5.暂停Deployment 以便于一次性修改多个PodTemplateSpec的配置项,之后再恢复Deployment ,进行新的发布;
6.扩展Deployment 以应对高负载;
7.查看Deployment 的状态,以此作为发布是否成功的指标
8.清理不再需要的旧版本的ReplicaSets。
除了API声明与Kind类型等有所区别,Deployment 的定义域Replica Set的定义很类似:
apiVersion:extensions/v1beta1
kind:Deployment
metadata:
name:nginx-deployment
运行下面命令创建Deployment :
kubectl create -f tomcat-deployment.yaml
查看Deployment 的信息:
kubectl get deployments
对上述输出涉及的数量解释如下:
1.DESIRED:Pod副本数量的期望值,即在Deployment 里定义的Replica
2.CURRENT:当前Replica的值,实际上是 Deployment 创建的 Replica Set里的 Replica 的值,这个值不断的增加,直到达到 DESIRED 为止,
表明整个部署过程完成。
3.UP-TO-DATE:最新版本的Pod的副本数量,用于指示在滚动升级的过程中,有多少个Pod副本以及成功升级。
4.AVAILABLE:当前集群中可用的Pod副本数量,即集群中当前存活的Pod数量。
运行下面命令查看对应的Replica Set:
kubectl get rs
运行下面命令查看创建的Pod
kubelctl get pods
1.4.7 Horizontal Pod Autoscaler
通过手工执行 kubectl scale 命令,我们可以实现Pod扩容和缩容。
在k8s 1.1发布了新特性 --- Horizontal Pod Autoscaling(Pod横向自动扩容,HPA)。在k8s 1.2中HPA被升级为稳定
版本(apiVersion:autoscaling/v1),但仍然保留了旧版本(apiVersion:extension/v1beta1)。k8s从1.6版本开始,增加了根据应用自定义的指标进行自动扩容和
缩容的功能,API版本为 autoscaling/v2alpha1。
HPA与之前的RC,Deployment一样,也属于一种k8s资源对象。通过追踪分析指定RC控制的所有目标Pod的负载变化情况,来确定是否需要有针对性的调整目标Pod的
数量副本,这是HPA的实现原理。当前,HPA有以下两种方式作为Pod的负载的度量指标:
a) CPUUtilizationPercentage
b) 应用程序自定义的度量指标,比如服务在每秒内的相应请求数(TPS或者QPS)
CPUUtilizationPercentage 是一个算术平均值,即目标Pod所有副本自身的cpu利用率的平均值。一个Pod自身的cpu利用率是该Pod当前cpu使用量除以它的Pod
Request的值,比如定义一个pod的Pod Request 为0.4,而当前Pod的cpu使用率为0.2,则它的cpu使用率为50%,这样就可以算出一个RC控制的所有Pod副本的cpu
利用率的算术平均值了。如果某个时刻的CPUUtilizationPercentage 超过80%,则意味着当前pod副本数量很可能不足以支撑接下里更多的请求,需要进行动态扩容。
如果目标pod没有定义Pod Request的值,则无法使用 CPUUtilizationPercentage 实现Pod的横向扩容。除了使用CPUUtilizationPercentage,k8s 1.2
版本开始也尝试支持应用程序自定义的度量指标。
在CPUUtilizationPercentage计算过程中Pod的cpu使用量通常是 1min内的平均值,通常通过查询 Heapster 监控子系统来得到这个值,所有需要安装部署
Heapster,这样便增加了系统的复杂度和实施HAP特性的复杂度。因此,从1.7开始,k8s自身孵化了一个基础特性数据采集监控框架 --- k8s Monitoring Architecture,
从而更好的支持HPA和其他需要用到的基础性能数据的功能模块。在k8s Monitoring Architecture中,k8s定义了一套标准化API的接口 Resource Metrics API,
以方便客户端程序(如HPA)从Metrics Server中获取目标资源对象的性能数据。
下面是一个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
1.4.8 StatefulSet
在k8s中,Pod的管理对象RC,Deployment,DaemonSet和Job都是面向无状态的服务的,但其实有很多是有状态的。特别是一些复杂的中间件集群,例如mysql集群,
mongodb集群,zookeeper集群,这些集群有4个共同点:
1.每个节点都有固定的ID,通过这个ID,集群中的成员可以互相发现并通信;
2.集群的规模是比较固定的,集群规模不能随意变动;
3.集群中的每个节点都是有状态的,通常会持久化到永久存储中;
4.如果磁盘损坏,则集群里的某个节点无法正常运行,集群功能受损。
如果通过RC或者Deployment控制pod副本数量来实现上述有状态的集群,就会发现第1点是无法满足的,因为Pod的名称是随意产生的,pod的ip地址也是在运行期间
才确定的且有可能变动,我们事先无法为每个Pod都确定唯一不变的ID。另外,为了能够在其他节点上恢复某个失败的节点,这种集群中的pod需要挂接某种共享存储,为了解决
这个问题,k8s从1.4开始引入了 PetSet 这个新的 资源对象,并且在1.5版本时更名为 StatefulSet,StatefulSet从本质上来说,可以看做是 Deployment/RC 的一个
特殊变体,它有如下特性:
1.StatefulSet里的每个pod都很稳定,唯一的网络标识,可以用来发现集群内的其他成员。假设 StatefulSet 的名称为kafka,那么第1个pod叫kafka-0,第2个
叫kafka-1 ...
2.StatefulSet 控制的pod副本启动顺序是受控的,操作第n个pod时,前n-1个pod已经是运行且准备好的状态
3.StatefulSet里的pod采用稳定的持久化存储卷,通过PV或PVC来实现,删除pod时默认不会删除与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.4.9 Service
Service 服务也是k8s离的核心资源对象之一,k8s里的每个Service 其实就是微服务架构中的一个服务。
k8s的Service 定义了一个服务的访问入口地址,前端的应用(Pod)通过这个入口地址访问背后的一组由Pod副本组成的集群实例,Service 与其后端Pod副本集群之间
则是通过Label Selector来实现无缝对接的。RC的作用实际上是保证Service 的服务能力与服务质量始终服务预期标准。
既然每个pod都会被分配一个单独的ip地址,而且每个pod都提供了一个独立的Endpoint(Pod IP + ContainerPort)来被客户端访问,现在多个pod副本组成了一个
集群来提供服务,那么客户端如何来访问它们的呢?一般的做法是部署一个负载均衡器,为这组pod开启一个对外的服务端口如8080,并且将这些pod的Endpoint列表加入
8080端口的转发列表,客户端就可以通过负载均衡器的对外ip地址+服务端口来访问此服务。客户端的请求最后被转发到哪个pod,由负载均衡器的算法决定。
k8s也遵循上述的做法,运行在每个pod上的 kube-proxy 进程其实就是一个智能的负载均衡器,负载把对Service的请求转发到后端的某个pod实例上,并在内部实现
服务的负载均衡与会话保持机制。但k8s发明了一种很巧妙又影响深远的设计:Service没有共用一个负载均衡器的ip地址,每个Service都被分配了一个全局唯一的虚拟ip
地址,这个虚拟ip地址被称为Cluster IP。这样一来,每个服务就变成了具备唯一的ip地址的通信节点,服务调用就成了最基础的tcp网络通信问题。
我们知道pod的Endpoint地址会随着pod的销毁和重新创建而发生改变,因为新Pod的ip地址与之前旧的pod不同。而Service一旦被创建,K8s就会自动为它分配一个
可用的Cluster IP,而在Service的整个生命周期内,它的Cluster IP不会发生变化。于是,服务发现这个棘手的问题在k8s的架构里也可以轻松的解决:只要Service
的Name与Service的Cluster IP地址做一个dns域名映射即可完美的解决问题。
如下:
apiVersion:v1
kind:Service
metadata:
name:tomcat-service
spec:
ports:
- port:8080
selector:
tier:frontend
上述内容定义了一个名为 tomcat-service 的Service,它的服务端口为8080,拥有"tier=frontend"这个Label的所有Pod都属于它,运行下面的命令创建:
kubectl create -f tomcat-server.yaml
运行下面命令查看tomcat-service 的 Endpoint列表:
kubectl get endpoints
运行下面命令查看tomcat-service被分配的Cluster IP以及更多的信息:
kubectl get src tomcat-service -o yaml
在 spec.ports 的定义中,targetPort属性用来确定提供该服务的容易所暴露的(EXPOSE)的端口号,即具体业务进程在容器内的targetPort上提供tcp/ip接入,
port属性则定义了Service的虚端口。前面定义的tomcat服务时没有定义targetPort,则默认与port相同。
接下来看Service的多端口问题。
很多服务都存在多个端口的问题,通常一个端口提供业务服务,另外一个端口提供管理服务,比如Mycat,Codis等常见中间件。k8s Service支持多个Endpoint,
在存在多个Endpoint的情况下,要求每个Endpoint都定义一个名称来区分。如下:
apiVersion:v1
kind:Service
metadata:
name:tomcat-service
spec:
ports:
- port:8080
name:service-port
- port:8005
name:shutdown-port
....
多端口为什么要给每个端口都命名呢?这就涉及到了k8s的服务发现机制了。
2.k8s的服务发现机制
任何分布式系统都会涉及服务发现这个基础问题,大部分分布式系统都通过提供特定的api接口来实现服务发现的功能,但这样会导致平台的入侵性比较强,也增加了
测试、开发的难度。
首先,k8s中的每个Service都有唯一的Cluster IP以及唯一的名称,而名称是由开发者自己定义的,部署时也没有必要改变,所以完全可以被固定在配置中。接下来
的问题是如何通过Service名称去找到对应的Cluster IP。
最早采用了Linux的环境变量来解决这个问题,即每个Service都生成一些对应的Linux环境变量(ENV),并在每个Pod的容器启动时自动注入这些环境变量。考虑到
通过环境变量获取Service地址的方式仍然不太方便,后来k8s通过Add-On增值包引入了dns系统,把服务名作为dns域名,这样程序就可以直接通过服务名来建立通信了。
3.外部系统访问Service的问题
k8s里的3种IP,这3种IP分别如下:
a) Node IP:Node的IP地址
b) Pod IP:Pod的IP地址
c) Cluster IP: Service 的 Ip地址
首先,Node IP是k8s集群中每个节点的物理网卡的ip地址,是一个真实存在的物理网络,所有属于这个网络的服务器都能通过这网络直接通信,不管其中是否有部分节点
不属于这个k8s集群。这也表明k8s集群之外的节点访问k8s集群之内的某个节点或者tcp/ip服务时,都必须通过Node IP通信。
其次,Pod IP是每个Pod的ip地址,它是docker Engine根据 docker0 网桥的ip地址段进行分配的,通常是一个虚拟的二层网络,k8s要求位于不同Node上的Pod
都能够彼此直接通信,所以k8s里一个pod里的容器访问另外一个pod里的容器时,就是通过Pod IP所在的虚拟二层网络进行通信的,而真实的tcp/ip流量是通过Node IP
所在的物理网卡流出的。
最后,Service 的 Cluseter IP,是一种虚拟ip,更像是伪造的ip,原因有如下:
a) Cluster IP仅仅作用于k8s Service这个对象,并由k8s管理和分配ip地址(来源于Cluster IP地址池)
b) Cluster IP无法被ping,因为没有一个"实体网络对象"来响应
c) Cluster IP只能结合 Service Port组成一个具体的通信端口,单独的Cluster IP不具备tcp/ip通信的基础,并且它们属于k8s集群这样一个封闭的空间,
集群外的节点如果要访问这个通信端口,则需要做一些额外的工作
d) 在k8s集群内,Node IP 网,Pod IP网与 Cluster IP网之间的通信,采用的是k8s自己设计的一种编程方式的特殊路由规则,与我们熟知的路由规则有很大
不同。
根据上面总结,Service的Cluseter IP属于k8s集群的内部地址,无法在集群外部使用这个地址。那么矛盾来了:实际上在我们开发业务系统中肯定多少有一部分服务
要提供给k8s集群外部的应用或者用来来使用的,用户怎么用?
采用 NodePort 是解决上述问题的最直接、有效的方法。以tomcat-service为例,在Service的定义里做如下扩展即可:
apiVersion:v1
kind:Service
metadata:
name:tomcat-service
spec:
type:NodePort
ports:
- port:8080
nodeport:31002
selectors:
tier:frontend
其中,nodePort:31002这个属性表明手动指定 tomcat-service 的 NodePort为31002,否则k8s会自动分配一个可用的端口。在浏览器输入,
http://<nodePortIP>31002 即可访问。
NodePort的实现方式是在k8s集群中的每个Node上都为需要外部访问的Service开启一个对应的tcp监听端口,系统外部只要用任意一个Node的Ip地址+具体的端口号
即可访问此服务,在任意Node上运行 netstat -tlp | grep 31002
但NodePort还没有完全解决外部访问 Service的所有问题,比如负载均衡问题。对于每个Service,我们通常需要配置一个对应的lb实例来转发流量到后端Node上,
这样的确增加了工作量以及出错的概率。于是k8s提供了自动化的解决方案,如果我们的集群位于谷歌的公有云GCE上,那么只要把Service的 type=NodePort改成
type=LoadBalancer,k8s就会自动创建一个对应的 Load balancer并返回它的ip地址提供外部客户端使用。其他公有云提供商只要实现了支持此特性的驱动,则也可以
达到上述目的。
1.4.10 Job
从1.2版本开始,k8s支持批处理类型的应用,我们可以通过k8s Job 这种新的资源对象定义启动一个批处理任务。Job也控制一组容器,从这个角度说,Job也是一种特殊
的Pod副本控制器,同时Job控制Pod副本与RC等控制器的工作机制有以下区别:
1.Job所控制的Pod副本是短暂运行的,可以将其视为一组docker容器,其中的每个docker容器仅仅运行一次。当Job控制的所有Pod副本都运行结束时,对应的Job也就
结束了。Job在实现方式上与RC等副本控制器不同,job生成的pod副本是不能自动重启的,对应的pod副本的RestartPoliy都被设置为Never。因此,当对应的pod副本
都执行完成时,对应的job也就完成了控制使命,即job生成的pod在k8s中是短暂存在的。在k8s 1.5版本之后又提供了类似的crontab的定时任务---CronJob,解决了
某些批处理需要定时反复执行的问题。
2.job所控制的pod副本的工作模式能够多实例并行计算。
1.4.11 Volume
Volume(存储卷)是pod中能够被多个容器访问的共享目录。k8s的Volume概念、用途和目的与docker的Volume比较类似,但不能等价。首先,k8s中的Volume被定义在
pod上,然后被一个pod里的多个容器挂载到具体的文件目录下;其次,k8s中的Volume与pod的生命周期相同,但与容器的生命周期不相关,当容器停止或者重启时,Volume中
的数据也不会丢失。最后,k8s支持多种类型的Volume,例如GlusterFS,Ceph等先进的分布式文件系统。
Volume的使用也比较简单,在大多数情况下,我们先在pod上声明一个Volume,然后在容器里引用该Volume并挂在(Mount)到容器的某个目录上。举例来说,我们要给之前
的tomcat pod增加一个名为 datavol 的Volume,并且挂载到容器的 /mydata-data 的目录上,则只需要对pod的定义文件做如下修正即可:
template:
metadata:
lables:
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的Volume还扩展出一种非常有使用价值的功能,即容器配置
管理集中化定义与管理,这是通过ConfigMap来实现的。
k8s提供了非常丰富的Volume类型,如下:
1.emptyDir
一个emptyDir Volume是在pod分配到Node时创建的,从它的名字就可以看出,它的初始化内容为空,并且无需指定宿主机上对应的目录文件,这是因为k8s
自动分配了一个目录,当pod从Node上移除时,emptyDir中的数据也会被永久删除。emptyDir的一些用途如下:
a) 临时空间,例如用于某些应用程序运行时所需的临时目录,且无需永久保留
b) 长时间任务的中间过程 CheckPoint 的临时保存目录
c) 一个容器需要从另一个容器中获取数据的目录(多容器共享目录)
目前,用户无法控制emptyDir使用的介质种类。如果kubectl的配置使用的是磁盘,那么所有emptyDir都将被创建在磁盘上。pod在将来可以设置emptyDir
是位于磁盘,固态硬盘还是基于内存的tmpfs上。
2.hostPath
hostPath为在pod上挂在宿主机上的文件或目录,它作用:
1.容器应用程序生成的日志文件需要永久保留时,可以使用宿主机的高速文件系统进行存储。
2.需要访问宿主机上docker引擎内部数据结构的容器应用时,可以通过定义hostPath为宿主机 /var/lib/docker 目录,使容器内部应用可以直接访问
docker文件系统。
在使用这种类型的Volume时,需要注意:
1.在不同的node上具有相同配置的pod,可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致;
2.如果使用了资源配额管理,则k8s无法将hostPath在宿主机上使用的资源纳入管理。
下面使用了宿主机的/data目录定义了一个hostPath的类型的Volume:
volumes:
- name:"persistent-storage"
hostPath:
path:"/data"
3.gcePersistentDisk
使用这种类型的Volume表示使用谷歌云提供的永久磁盘(Persistent Disk,PD)存放Volume的数据,它与emptyDir不同,PD上的内容会被永久保存,当pod
被删除时,PD只是被卸载(Unmount),但不会被删除。需要注意的是,你需要先创建一个PD,才能使用 gcePersistentDisk。
使用gcePersistentDisk 有以下限制条件:
1.Node(运行kubectl的节点)需要是GCD虚拟机;
2.这些虚拟机需要与PD存在于相同的GCE项目和Zone中。
通过gcloud命令即可创建一个PD:
gcloud compute disks create --size=500GB --zone=us-centrall-a my-data-disk
定义gcePersistentDisk类型的Volume定义如下:
volumes:
- name:test-volume
gcePersistentDisk:
pdName:my-data-disk
fsType:ext4
4.awwElasticBlockStore
该类型的Volume使用亚马逊公有云提供的 EBS Volume 存储数据,需要先创建一个 EBS Volume 才能使用 awsElasticBloxkStore。
限制如下:
1.Node(运行kubectl节点)需要是aws ec2实例;
2.这些aws ec2实例需要与 EBS Volume 存在于相同的region和availability-zone中。
3.ebs只支持单个ec2实例挂载一个Volume
通过aws ec2 create-volume 创建一个 EBS Volume:
awc 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
nfs:
#改为你的nfs服务器地址
server:nfs-server.localhost
path:"/"
6.其他类型的Volume
1.iscsi
使用iSCSI存储设备上的目录挂载到pod上。
2.flocker
使用Flocker管理存储卷。
3.glusterfs
使用开源GlusterFS网络文件系统的目录挂载到Pod中。
4.rbd
使用Ceph块设备共享存储(Rados Block Device)挂载到pod中。
5.gitRepo
通过挂载一个空目录,并从git库clone一个git repository以供Pod使用。
6.secret
一个Secret Volume用于为Pod提供加密的信息,你可以将定义在k8s中的 Secret直接挂载为文件让pod访问。Secret Volume 是通过TMFS(内存
文件系统)实现的,这种类型的Volume总是不会被持久化的。
1.4.12 Persistent Volume
之前提到的Volume是被定义在pod之上的,属于计算资源的一种,而实际上,网络存储是相对独立于计算资源而存在的一种实体资源。比如在使用虚拟机的情况下,我们
通常会先定义一个网络存储,然后从中划出一个"网盘"并挂接到虚拟机上。Persistent Volume(PV)和与之相关联的Persistent Volume Claim(PVC)也起到了类型作用。
PV可以被理解为k8s集群中的某个网络存储对应的一块存储,它与Volume类似,但有以下区别:
1.PV只能是网络存储,不属于任何Node,但可以在每个Node上访问;
2.PV并不是被定义在pod上的,而是独立于pod之外定义的
3.PV目前支持的类似包括:...
下面给出了NFS类型的PV的一个yaml文件定义,声明了需要5Gi的存储空间:
apiVersion:v1
kind:PersistentVolume
metadata:
name:pv003
spec:
capacity:
storage:5Gi
accessModes:
- ReadWriteOnce
nfs:
path:/somepath
server:172.17.0.2
比较重要的是 PV的 accessModes属性,目前有以下类型:
1.ReadWriteOnce:读写权限,并且只能被单个node挂载
2.ReadOnlyMany:只读权限,允许被多个node挂载
3.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的状态,PV是有状态的对象,有以下几种:
1.Available:空闲状态
2.Bound:已经绑定到某个PVC上
3.Released:对应的PVC已经被删除,但资源还没被集群回收
4.Failed:PV自动回收失败
1.4.13 Namespace
Namespace(命名空间) 在很多情况下用于实现多租户的资源隔离,Namespace通过将集群内部的资源对象"分配"到不同的Namespace中,形成逻辑上分组的不同项目、
小组或者用户组,便于不同的分布在共享整个集群的资源的同时还能被分别管理。
k8s集群在启动后会创建一个名为default的Namespace,通过kubectl查看:
kubectl get namespaces
接下来,如果不特别指明Namespace,则用户创建的Pod,RC,Service 都将被系统创建到这个默认的default下面.
Namespace的定义:
apiVersion:v1
kind:Namespace
metadata:
name:development
一旦创建了Namespace,我们在创建资源的时候就可以指定这个资源对象属于哪个Namespace。如下,
apiVersion:v1
kind:Pod
metadata:
name:busybox
namespace:development
...
使用 kubectl get 查看,将无法显示:
kubectl get pods
这是因为不加参数的话,则kubectl get命令仅显示default命名空间的:
kubectl get pods --namespace=development
当给每个租户创建一个Namespace来实现多租户的资源隔离时,还能结合k8s的资源配额管理,限定不同的租户能占用的资源。
1.4.14 Annotation
Annotation(注解)与Label类似,也使用key/value键值对的形式进行定义。不同的是Label具有严格的命名规则,它定义的k8s对象的元数据(Metadata),并且用
于Label Selector。Annotation 则是用户任意定义的附加信息,以便于外部工具查找。很多时候,k8s的模块自身会通过Annotation标记资源对象的一些特殊信息。
通常,用Annotation来记录的信息如下:
1.build信息,release信息,docker镜像信息等,例如时间戳,release id号,PR号,镜像Hash值,docker Registry地址等;
2.日志库,监控库,分析库等资源库的地址信息
3.程序调试工具信息,如工具名称,版本号等
4.团队的联系信息
1.4.15 ConfigMap
为了能准确和深刻的理解k8s ConfigMap 的功能和价值,我们从docker说起。我们知道,docker通过将程序,依赖库,数据及配置文件"打包固化"到一个不变的镜像
文件中的做法,解决了应用部署的难题,但同时带来了棘手的问题,即 配置文件中的参数在运行期如何修改的问题。我们不可能在启动docker容器后再修改容器里的配置文件,
然后用新的配置文件重启容器的用户主进程。为了解决这个问题,docker提供了2种办法:
1.在运行时通过容器的环境变量来传递参数
2.通过Docker Volume 将容器外的配置文件映射到容器内
这2种方式都有优缺点,在大多数情况下,后一种方式更适合我们的系统,因为大多数应用通常从一个或者多个配置文件中读取参数。但这种方式也有明显的缺陷:我们必须在
目标主机上先创建好对应的配置文件,然后才能映射到容器里。
上述缺陷在分布式情况下边的更为严重,因为无论采用哪种方式,写入多台服务器上的某个指定文件,并确保这些文件保持一致,都是一个很难完成的任务。此外,在大多数
情况下,我们都希望能集中管理系统的配置参数,而不是管理一堆配置文件。针对上述问题,k8s给了一个很巧妙的实现:
a) 首先,把所有的配置项都当做 key-value 字符串,当然value 可以来自某个文本文件,比如配置项 password=123456。这些配置项可以作为 Map表的一个项,
整个Map的数据可以被持久化存储在k8s的etcd中,然后提供api以方便k8s相关组件或者客户端 curd 操作这些数据,上述专门用来保存配置参数的Map就是k8s ConfigMap
资源对象。
b) 接下来,k8s提供了一种内建机制,将存储在etcd中的ConfigMap 通过Volume映射的方式变成目标Pod内的配置文件,不管目标pod被调度到哪一台服务器上,都会
完成自动映射。进一步的,如果ConfigMap中的key-value数据被修改,则映射到pod中的"配置文件"也会随着更新。于是,k8s ConfigMap 就变成了分布式系统中
最简单且对应用无入侵的配置中心。