kubernetes 中的大部分概念如 Node,Pod,Replication Controller,Sercice等都可以看作一种"资源对象",几乎所有的资源对象都可以通过kubernetes提供的kubectl工具(或者API编程调用)执行增,删,改,查等操作并将其保存在etcd中持久化存储。kubernets其实是一个高度自动化的资源控制系统,它通过跟踪对比etcd库里保存的"资源期望状态"与当前环境中的"实际资源状态"的差异来实现自动控制和自动纠错的高级功能
1.master
kubernetes 里的master指的是集群控制节点,每个kubernetes集群里需要一个master节点来负责整个集群的管理和控制,基本上kubernetes所有的控制命令都是发给它,它来负责具体的执行过程,所有的执行命令都是在master节点上运行,master节点通常会占据一个独x86服务器(虚拟机),一个主要的原因是它太重要了,它是整个集群的"首脑",如果它宕机或者不可用,我们所有的控制命令都将失效
Master节点运行的进程
Kubernetes API Server(kube-apiserver),提供了HTTP Rest 接口的关键服务进程,是Kubernetes 里所有资源的增,删,改,查等操作的唯一入口,也是集群控制的入口进程
Kubernetes Controller Manager(kube-controller-manager),Kubernetes里所有资源对象的自动化控制中心,可以理解为资源对象的"大总管"
Kubernetes Seheduler(kube-scheduler),负责资源调度(pod调度)的进程,相当于公交公司的"调度室"
在master节点上还需启动一个etcd Server进程,因为Kubernetes 里的所有资源对象的数据保存在etcd中
2.Node
除了Master,Kubernetes集群中的其他机器被称为Node节点,比较早的版本中也被称为Minion.与master一样,Node节点可以是一台物理主机,也可以是一台虚拟机,Node节点才是Kubernetes集群中的工作负载节点,每个Node都会被Master分配一些工作负载(Docker容器),当某个Node宕机时,其上的工作负载会被Master自动转移到其他节点上
每个Node节点上都运行着一下一组关键进程
kubelet: 负责pod对应的容器的创建,启停等任务,同时与Master节点密切协作,实现集群管理的基本功能
kube-proxy: 实现Kubernetes Service的通信与负载均衡机制的重要组件。
Docker Engine (docker):Docker 引擎,负责本机的容器创建和管理工作
Node 节点可以在运行期间动态增加到Kubernetes集群中,前提是这个节点上已经正确安装,配置和启动了上述关键进程,在默认情况下kubelet会向Master 注册自己,这也是Kubernetes推荐的Node管理方式,一旦Node被纳入集群管理范围,kubelet进程就会定时向Master节点汇报自身情况.例如操作系统,Docker版本,机器的CPU和内存情况,以及之前有哪些Pod在运行等这样Master可以获知每个Node的资源使用情况,并实现高效均衡的资源调度策略。而某个Node超过指定时间不上报信息时,会被Master判定为"失联",Node的状态被标记为不可用(Not Ready),随后Master会触发"工作负载大转移"的自动流程
2.1 查看集群中的节点
[root@server ~]# kubectl get node
NAME STATUS AGE
127.0.0.1 Ready 5d
2.2 查看节点详细信息
[root@server ~]# kubectl describe node 127.0.0.1
Name: 127.0.0.1
Role:
Labels: beta.kubernetes.io/arch=amd64
beta.kubernetes.io/os=linux
kubernetes.io/hostname=127.0.0.1
Taints: <none>
CreationTimestamp: Thu, 12 Sep 2019 16:02:18 +0800
Phase:
Conditions:
Type Status LastHeartbeatTime LastTransitionTime Reason Message
---- ------ ----------------- ------------------ ------ -------
OutOfDisk False Wed, 18 Sep 2019 00:05:37 +0800 Thu, 12 Sep 2019 16:02:18 +0800 KubeletHasSufficientDisk kubelet has sufficient disk space available
MemoryPressure False Wed, 18 Sep 2019 00:05:37 +0800 Thu, 12 Sep 2019 16:02:18 +0800 KubeletHasSufficientMemory kubelet has sufficient memory available
DiskPressure False Wed, 18 Sep 2019 00:05:37 +0800 Thu, 12 Sep 2019 16:02:18 +0800 KubeletHasNoDiskPressure kubelet has no disk pressure
Ready True Wed, 18 Sep 2019 00:05:37 +0800 Mon, 16 Sep 2019 14:15:37 +0800 KubeletReady kubelet is posting ready status
Addresses: 127.0.0.1,127.0.0.1,127.0.0.1
Capacity:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 995896Ki
pods: 110
Allocatable:
alpha.kubernetes.io/nvidia-gpu: 0
cpu: 1
memory: 995896Ki
pods: 110
System Info:
Machine ID: 7503d765072b4bdb9696f7807539358b
System UUID: 89FD4D56-2F9A-E512-92B4-042FEAF4CAAA
Boot ID: d7de7e4d-d385-483c-8f30-7755bc244bcc
Kernel Version: 3.10.0-957.el7.x86_64
OS Image: CentOS Linux 7 (Core)
Operating System: linux
Architecture: amd64
Container Runtime Version: docker://1.13.1
Kubelet Version: v1.5.2
Kube-Proxy Version: v1.5.2
ExternalID: 127.0.0.1
Non-terminated Pods: (2 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits
--------- ---- ------------ ---------- --------------- -------------
default mysql-cc20c 0 (0%) 0 (0%) 0 (0%) 0 (0%)
default myweb-vg6hk 0 (0%) 0 (0%) 0 (0%) 0 (0%)
Allocated resources:
(Total limits may be over 100 percent, i.e., overcommitted.
CPU Requests CPU Limits Memory Requests Memory Limits
------------ ---------- --------------- -------------
0 (0%) 0 (0%) 0 (0%) 0 (0%)
No events.
关键信息如下:
Node基本信息: 名称,标签,创建时间等
Node当前运行状态,Node启动以后会做一系列的自检工作,比如磁盘是否满了,如果满了就标注OutOfDisk=True,否则继续检查内存是否不足(MemoryPressure=True),最后一切正常,就切换为Ready状态(Ready=True),这种情况表示Node处于健康状态,可以在其上创建新的pod
Node的主机地址与主机名
Node上的资源总量:描述Node可用的系统资源,包括CPU,内存数量,最大可调度Pod数量等,注意到目前Kubernetes已经实验性的支持GPU资源分配了(alpha.kubernetes.io/nvidia-gpu=0)
Node 可分配资源量:描述Node当前可用于分配的资源量。
主机系统信息:包括主机的唯一表示UUID,Linux kernel 版本号,操作系统类型与版本,Kubernetes 版本号,kubelet与kube-proxy的版本号等
当前正在运行的Pod列表概要信息。
已分配的资源使用概要信息,例如资源申请的最低,最大允许使用量占系统总量的百分比
Node相关的Event信息
3. Pod
Pod 是Kubernetes的最重要也最基本的概念,每个Pod都有一个特殊的被称为"根容器"的Pause容器。Pause容器对应的镜像属于Kubernetes平台的一部分,除了Pause容器,每个Pod还包含一个或多个紧密相关的用户业务容器
pod的概念和结构
在一组容器作为一个单元的情况下,我们难以对"整体"简单地进行判断急有效的进行行动。引入业务无关并且不易死亡的Pause容器作为根容器,以它的状态代表整个容器组的状态,就简单,巧妙的解决了这个难题
pod里多个业务容器共享Pause容器的ip,共享Pause容器挂接的Volume,这样即简化了亲切关联的业务容器之间的通信问题,也很好的解决了它们之间的文件共享问题
Kubernetes 为每个Pod都分配了唯一的IP地址,称之为Pod IP,一个Pod里的多个容器共享Pod IP 地址,Kubernetes,要求底层网络支持集群内任意两个Pod之间的TCP/IP直接通信,这通常采用虚拟二层网络技术来实现,例如Flannel,Openvswitch等,在kubernetes里,一个pod里的容器与另外主机上的Pod容器能够直接通信
https://blog.csdn.net/wanchaopeng/article/details/103372099
Pod其实有两种类型:普通的Pod及静态Pod(static pod),后者比较特殊,它并不存放在kubernetes的etcd存储里,而是存放在某个具体的Node上的一个具体文件中,并且只在此Node上启动运行,而普通的Pod一旦创建,就会被放入到etcd存储中,随后会被Kubernetes Master调度到某个具体的Node 上运行并绑定(Binding)随后该Pod被对应的Node上的kubelet进程实例化成一组相关的Docker容器并启动起来,在默认情况下,当pod里的某个容器停止时,Kubernetes会被检测到这个问题并且重新启动这个Pod(重启Pod里的所有容器),如果Pod所在的Node宕机,则会将这个Node上的所有Pod重新调度到其他节点上
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: '172.17.0.2'
- 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_SERVER_HOST='mysql'和MYSQL_SERVER_PORT='3306'的环境变量(env关键字),并且在8080端口(containerPort)上启动容器进程。Pod的IP加上这里的容器端口(containerPort),就组成了一个新的概念Endpoint的情况,比如当我们把Tomcat定义为一个Pod的时候,可以对外暴露管理端口与服务端口这两个Endpoint
Docker Volume在kubernetes里也有对应的概念---Pod Volume,后者有一些拓展,比如可以用分布式文件系统GlusterFS实现后端存储功能;Pod Volume是定义在Pod之上,然后被各个容器挂载到自己的文件系统中的
每个pod都可以对其能使用的服务器上的计算资源设置限额,当前可以设置限额的计算资源有CPU和memory两种,其中cpu的资源单位为CPU(Core)的数量,是一个绝对值而非绝对值
一个cpu的配额对于绝大多数容器来说是相当大的一个资源配额了,所以,在kubernetes里,通常以千分之一的CPU配额为最小单位,以m来表示。通常一个容器的CPU配额被定义为100~300m,即占用0.1~0.3个CPU。由于CPU配额是一个绝对值,所以无论在拥有一个Core的机器上,还是拥有54个Core的机器上,100m这个配额所代表的CPU的使用量都是一样的,与CPU配额类似,Memory配额也是一个绝对值,它的单位是内存字节数
在kubernetes里,一个计算资源进行配额限定需要设定以下两个参数
Requests: 该资源的最小申请量,系统必须满足条件
Limits: 该资源最大允许使用的量,不能被突破,当容器试图使用超过这个量的资源时,可能会被Kubernetes Kill 并重启
通常我们会把Request 设置为一个比较小的数值,符合容器时的工作负载情况下的资源需求,而把Limit设置为峰值负载情况下资源占用的最大量,比如下面的这短定义,表明MySQL容器申请最少0.25个CPU及64Mib内存,在运行过程中MySQL容器所能使用的配额为0.5个CPU及128Mib内存
spec:
containers: #Pod内容器定义的部分
- name: mysql #容器的名称
image: mysql:5.7 #容器对应的Docker Image
resources:
requests:
memory: "64Mi"
cpu: "250m"
limits:
memory: "128Mi"
cpu: "500m"
4.label(标签)
Label是Kubernetes系统中另外一个核心概念,一个是Label是一个key=value的键值对,其中key与value由用户自己指定。Label可以附加到各种资源对象上,例如Node,Pod,Service,RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以添加到任意数量的资源对象上去,Label通常在资源对象定义时确定,也可以在对象创建后动态添加或者删除
我们可以通过给指定的资源对象捆绑一个或多个不同的Label来实现多维度的资源分组管理功能,以便于灵活,方便地进行资源分配,调度,配置,部署等管理工作。例如:部署不同版本的应用到不同的环境中;或者监控和分析应用(日志记录,监控,告警)等,Label示例如下
版本标签: "release":"stable","release":"canary"
环境标签: "environment":"dev","environment":"qa","environment":"production"
架构标签: "tire":"frontend","tire":"backend","tier":"minddleware"
分区标签: "partition":"customerA","partition":"customerB"...
质量管控标签: "track":"daily","track":"weekly"
Label相当于我们熟悉的"标签",给某个资源对象定义的一个Label,就相当于给它打了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选拥有某些Label的资源对象,Kubernetes通过这种方式实现了类似SQL的简单又通用的对象查询机制
Label Selector可以被类比为SQL语句中的where查询条件,例如,name=redis-slave这个Label Selector作用于Pod时,可以类比为select * from pod's name = 'redis-slave'这样的语句,当前又两种Label Selector 的表达式: 基于等式的(Equality-based)和基于集合的(Set-based),前者采用"等式类"的表达式匹配标签,下面是一些具体的例子
name=redis-slave: 匹配所有具有标签name=redis-slave的资源对象
env !=production: 匹配所有不具有标签env=production的资源对象,比如env=test就是满足此田间的标签之一。
而后者使用几个曹祖的表达式匹配标签,例子如下
name in (redis-master,redis-slave):匹配所有具有标签name=redis-master 或者 name=redis-slave的资源对象
name not in (php-frontend): 匹配所有不具有标签name=php-frontend的资源对象。
可以通过多个Label Selector表达式的组合实现复杂的条件选择,多个表达式之间用","进行分隔即可,几个条件之间的"AND"的关系,即同事满足多个条件,如下
name=redis-slave,env!=production
name notin (php-frontend),env!=production
Label Selector在Kubernetes 中的重要使用场景如下
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"定向调度"的特性
前列中使用name=XXX的Label Selector。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系统中最核心的应用模型,使得被管理对象能够精细的分组管理,同事实现了整个集群的高可用性
5. Replication Controller (RC)
Replication Colltroller(以后简称RC)深入描述
RC是Kubernetes 系统中的核心概念之一,简单来说,它其实是定义了一个期望的场景,即声明某种Pod的副本数量在人任意时刻都符合某个预期值,RC定义如下
1.Pod期待的副本数(replicas)
2.用于筛选目标Pod的Label Selector
3.当Pod的副本数量小于预期数量的时候,用于创建新Pod的Pod模板(template)
如下RC例子,即确保拥有tier=frontend标签的这个Pod(运行Tomcat容器)在整个Kubernetes集群中始终只有一个副本
apiVersion: v1
kind: ReplicationController
metadata:
name: frontend
spec:
replicas: 1
selector:
tier: frontend
template:
metadata:
labels:
app: app-demo
tier: frontend
spec:
contaainers:
- 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,可以说,通过RC,kubernetes实现了用户应用集群的高可用性
下面我们以3个Node节点的集群为例,说明Kubernetes如何通过RC来实现Pod副本数量自动控制的机制,假如我们的Rc里定义redis-slave这个需要保持3个副本,系统将可能在其中两个Node上创建Pod,
1.两个Node上创建redis-slave Pod的情形
假设Node2上的Pod2意外终止,根据Rc定义的replicas数量2,Kubernetes将会自动创建并启动一个新的Pod,以保证整个集群中始终有两个redis-slave pod在运行
2.系统可能选择Node1或者Node3 来创建一个新的Pod
此外在运行时,我们可以通过修改Rc的副本数量,来实现Pod的动态缩放(Scaling)功能,这个可以通过执行Kuberctl scale命令来一键完成:
kubectl scale rc redis-slave --replicas=3
scaling的执行结果如下
注意:
删除Rc并不会影响通过该Rc已创建好的Pod,为了删除所有Pod,可以设置replicas的值为0,然后更新该RC,另外kubectl提供了stop和delete命令来一次性删除Rc和rc控制的全部Pod
当我们应用升级时,通常会通过Build一个新的Docker镜像,并用新的镜像版本来替代旧的版本的方式达到目的,在系统升级过程中,我们希望是平滑的方式,比如当前系统中10个对应旧版本的Pod,最佳的方法是旧版本的Pod每次停止一个,同事创建一个新版本的Pod,在整个升级过程中,此消彼长,而运行中的Pod数量始终是10个,一段时间后,当所有的pod都已经是最新版本的时候,升级完成,通过RC机制,Kubernetes很容易就实现了这种高级实用的特性,被称为"滚动升级"
由于Replication Controller与Kubernetes代码中的模块Replication Controller同名,同时这个词也无法准确表达它的本意,所以在Kubernetes 1.2 的时候它升级成另外一个新的概念---Replica Set,官方解释为"下一代的RC",它与RC当前存在的唯一区别是Replica Sets支持基于集合的Label selector(Set-based selector),而RC只支持基于等式的Label Selector(equality-base selector),这使得Replica Set的功能更强,下面是等价于之前Rc例子的Replica Set的定义
apiVersion: extensions/v1beta1
kind: ReplicaSet
metadata:
name: frontend
spec:
selector:
matchLabels:
tier: frontend
matchExpressions:
- {key: tier, operator: In, values: [frontend]}
template:
........
kubectl命令行工具适用于RC的绝大部分命令都同样适用于Replica Set,此外,当前我们很少单独使用Replica Set,它主要被Deployment这个逢高层的资源对象使用,从而形成一整套Pod创建,删除,更新的编排机制。当我们使用Deployment时,无须关心它是如何创建和维护Replica Set的,这一切是自动发生的
Replica Set 与 Deployment 这两个重要资源对象逐步替换了之前的RCd的作用。是Kubernetes 1.3里Pod自动扩容(伸缩)这个告警功能实现的基础,也将继续在Kubernetes 未来的版本中发挥重要的作用
总结关于RC (Replica Set)的一些特性和作用
1. 在大多数情况下,我们通过定义一个RC实现Pod的创建过程及副本数量的自动控制
2. RC里包括完整的Pod的定义模板
3. RC通过Label Selector 机制实现对Pod副本的自动控制
4. 通过改变RC里的Pod 副本数量,可以实现Pod的扩容或缩容功能
5. 通过该百年RC里Pod模板中的镜像版本,可以实现Pod的滚动升级功能
6.Deployment
Deployment 是Kubernetes 1.2 引入的概念,引入的目的是为了更好的解决Pod 的编排问题,为此,Deployment在内部使用了Replica Set来实现母的,无论从Deployment 的作用与目的,它的YAM定义,还是从它的具体命令行操作来看,我们都可以把它看作RC的一次升级,两者的相似度超过90%
Deployment相对于RC的一个最大升级是我们可以随时知道当前Pod"部署"的进度。实际上由于一个Pod的创建,调度,绑定节点及在目标Node上启动对应的容器这一完整过程需要一定的时间,所以我们期待系统启动N个Pod副本的目标状态,实际上是一个连续变化的"部署过程"导致的最终状态
Deployment的典型使用场景
1. 创建一个Deployment对象来生成对应的Replica Set并完成Pod副本的创建过程
2. 检查Deployment的状态来看部署动作是否完成(Pod副本的数量是否达到预期的值)
3. 更新呢Deployment以创建新的Pod(比如镜像升级)
4. 如果当前Deployment不稳定,则回滚到一个早先的Deployment版本
5. 挂起或者恢复一个Deployment
Deployment的定义与Replica Set的定义很类似,除了API声明与Kind类型等有所区别:
apiVersion: extensions/v1beta1 apiVersion: v1
kind: Deployment kind: ReplicaSet
metadata: metadata:
name: nginx-deployment name: nginx-repset
例子:
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
创建Deployment
[root@server /data/kubernetes]# kubectl create -f tomcat-deployment.yaml
deployment "frontend" created
查看Deployment 信息
[root@server /data/kubernetes]# kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
frontend 1 1 1 1 59m
上述输出数量解释:
DESIRED: Pod副本数量的期望值,即Deployment里定义的Replica.
CURRENT: 当前Replica 的值,实际上是Deployment所创建的Replica Set里的Replica值,这个值不断增加,直接达到DESIRED为止,表明整个部署过程完成
UP-TO-DATE: 最新版本的Pod的副本数量,用于指示在滚动升级的过程中,有多少个Pod副本已经成功升级
AVAILABLE:当前集群中可用的Pod的副本数量,即集群中当前存活的Pod数量
查看对应的Replica Set,
[root@server /data/kubernetes]# kubectl get rs
NAME DESIRED CURRENT READY AGE
frontend-141477217 1 1 1 1h
[root@server /data/kubernetes]# kubectl describe deployments
Name: frontend
Namespace: default
CreationTimestamp: Wed, 18 Sep 2019 19:54:45 +0800
Labels: app=app-demo
tier=frontend
Selector: tier=frontend,tier in (frontend)
Replicas: 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
OldReplicaSets: <none>
NewReplicaSet: frontend-141477217 (1/1 replicas created)
No events.
7.Horizontal Pod AUTOSCALER(HPA)
Horizontal Pod AUTOSCALER 简称HPA,意思是Pod横向扩容,与之前RC,Deployment一样,属于Kubernetes 资源对象,通过追踪分析rc控制的所有目标POD的负载变化情况,来确定是否需要针对性地调整目标Pod的副本数,这是HPA实现原理
HPA有两种方式作为pod的度量指标
CPUUtilizationPercentage
应用程序自定义的度量指标,比如服务在每秒内的相应请求数(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的CPU利用率又会降下来,此时对应的POd副本数应该自动减少到一个合理的水平
CPUUtilizationPercentage计算过程中使用到的Pod的CPU使用量通常是1分钟内的平均值,目前通过查询Heapster扩展组件来得到这个值,所以需要安装部署Heapster,这样一来便增加了系统的复杂度和实施HPA特性的复杂度,因此,未来的计划是Kubernetes自身实现一个基础行能数据采集模板,从而更好地支持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副本CPUtiliationpercentage的值超过90%时会触发自动动态扩容行为,扩容或缩容时必须满足的一个约束条件是Pod的副本数要介于1与10之间
除了可以通过直接定义yaml文件并调用kuberctrl create 的命令来创建一个HPA资源对象的方式,我们还能通过下面的简单命令行直接创建等价的HPA对象
kubectl autoscale deployment php-apache --cpu-percent=90 --min=1 --max=10
8.service(服务)
1.概述
Service 也是Kubernetes 里的最核心的资源对象之一,Kubernetes里的每个Service其实就是我们经常提起的为服务架构中的一个"微服务",之前我们所说的Pod,RC等资源对象其实都是为"服务"--kubernets Service 服务
kubernetes的Service定义了一个服务的访问入口地址,前端应用(Pod)通过这个入口地址访问其背后的一组由Pod副本组成的集群实例,Service与其后端Pod副本集群之间则是通过Label Selector来实现"无缝对接"的。而RC的作用实际上是保证Service的服务能力和服务质量始终处于预期的标准
通过分析,识别并见面系统中的所有服务为微服务---Kubernetes Service,最终我们系统由多个提供不同业务能力而又彼此对的微服务单元组成,服务之间通过TCP/IP进行通信,从而形成了我们强大而灵活的弹性网络,拥有了清大的分布式能力,弹性拓展你呢后来,容错能力,与此同时,我们的程序架构也变得简单和直观许多
既然每个Pod都会分配一个单独的IP地址,而且每个Pod都提供一个独立的Endpoint(Pod IP+ContainerPort)以被客户端访问,现在多个Pod副本组成一个集群来提供服务,被客户端访问,一般是部署一个负载均衡器(软件或硬件),这组Pod开启一个对外的服务端口如8000端口,并且将这些Pod的Endpoint列表加入8000端口的转发列表中,客户端就可以通过负载均衡器的对外ip地址+服务端口来访问次服务,而客户端的请求被转发到那个Pod,则由负载均衡器的算法决定
Kubernetes也循环了上述常规做法,运行在每个Node上的kube-proxy进程其实就是一个智能的软件负载器,它负责把对Service的请求转发到后端的某个Pod实例上,并在内部实现服务的负载均衡与会话保持机制,但Kubernetes发明了一种很巧妙又影响深远的设计: Service 不是共用一个负载均衡器的IP地址,而是每个Service分配了一个全局唯一的虚拟IP地址,这个虚拟IP被称为Cluster IP,这样一来,这个服务就变成了具备唯一IP地址的通信节点,服务调用就变成了最基础的TCP网络通信问题
Pod的Endpoint地址会随着Pod的销毁和重新创建而发生改变,因为新的Pod的ip地址与之前旧Pod的不同,而Service 一旦创建,Kubernetes 就会自动为它分配一个可用的Cluster IP,而且在Service的整个生命周期内,它的Cluster IP不会发生改变。于是,服务发现这个棘手的问题在Kubernetes的架构里也得以轻松解决: 只要用Service的Name与Service的Cluster IP地址做一个DNS域名映射即可解决
tomcat-service.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
selector:
tier: frontend
kubectl create -f tomcat-service.yaml
查看service的Endpoint
[root@server /data/tomcat]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 192.168.1.52:6443 7d
mysql 172.17.0.2:3306 3d
myweb 172.17.0.3:8080 2d
tomcat-service 172.17.0.4:8080 28s
tomcat-server分配的Cluster ip
[root@server /data/tomcat]# kubectl get svc tomcat-service -o yaml
apiVersion: v1
kind: Service
metadata:
creationTimestamp: 2019-09-19T09:18:30Z
name: tomcat-service
namespace: default
resourceVersion: "688768"
selfLink: /api/v1/namespaces/default/services/tomcat-service
uid: 72300d76-dabe-11e9-a54c-000c29f4caaa
spec:
clusterIP: 10.254.192.228
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
tier: frontend
sessionAffinity: None
type: ClusterIP
status:
loadBalancer: {}
在spec.ports 的定义中,targetPort 属性用来确定提供该服务的容器所暴露(EXPOSE)的端口号,即具体业务进程在容器内的tagetPort,上提供TCP/IP介入;而Port属性则定义了Service的虚端口号,即具默认targetPort与port相同
很多服务存在多个端口的问题,通常一个端口提供业务服务,另外一个端口提供管理服务,比如Mycat,Codis等常见中间件.Kubernetes Service支持多个Endpoint,在存在多个Endpoint的情况下,要求每个Endpoint定义一个名字来区分,
例子如下:
apiVersion: v1
kind: Service
metadata:
name: tomcat-service
spec:
ports:
- port: 8080
name: service-port
- port: 8005
name: shutdown-port
selector:
tier: frontend
2.Kubernetes的服务发现机制
每个Kubernetes中的Service都有一个唯一的Cluster IP 以及唯一的名字,而名字是由开发者自己定义的,部署的时候也没必要改变,完全固定在配置中。通过Service的名字得到对应的Cluster IP
考虑到环境变量的方式获取Service的IP与端口的方式不太方便,后来Kubernetes通过Add-on增值包的方式引入了DNS系统,把服务名作为DNS域名,这样一来,程序就可以直接使用服务名来建立通信链接了。目前Kubernetes上的大部分应用采用了DNS服务发现机制
3. 外部系统访问Service的问题
Kubernetes三种ip
Node IP: Node 节点的IP地址
Pod IP: Pod的IP地址
Cluster IP: Service 的ip地址
首先,Node IP 是Kubernetes集群中每个节点的物理网卡的IP地址,这是一个真实存在的物理网络,所有属于这个网络的服务器之间都能通过这个网络直接通信,不管它们中是否有部分节点不属于这个Kubernetes集群,这也表明了Kubernetes集群之外的节点访问Kubernetes集群之内的某个节点或者TCP/IP服务的时候,必须通过Node IP 通信
其次,Pod IP 是每个Pod的IP地址,它是Docker Engine根据docker0 网桥的IP地址段进行分配的,通常是一个虚拟的二层网络,Kubernetes要求位于不同的Node上的Pod 能够彼此直接通信,所以Kubernetes 里一个Pod里的容器访问另外一个Pod的容器,就是通过Pod IP 所在虚拟二层网络进行通信的,而真实的TCP/IP流量则是通过Node IP所在的物理网卡流出的
Service 的Cluster IP,它是一个虚拟的IP
Cluster IP 仅仅作用于Kubernetes Service这个对象,并由Kubernetes管理和分配IP地址(来源于Cluster IP地址池)
Cluster IP 无法被Ping,因为没有一个"实体网络对象"来响应
Cluster IP 只能结合Service Port组成一个具体的通信端口,单独的Cluster IP 不具备TCP/IP通信的基础,并且属于 Kubernetes集群这样一个封闭的空间,集群之外的节点如果要访问这个通信端口,则需要做一些额外的工作在Kubernetes集群之内,Node IP网,Pod IP网之间的通信,采用的是Kubernetes 自己涉及的一种编程方式的特殊路由规则,与我们数值的IP由有很大的不同
总结: Service的Cluster IP属于Kubernetes集群内部的地址,无法在集群外部使用这个地址。
采用NodePort解决此问题的最直接,最有效,最常用的做法。如下定义扩展如下(后面加井号)
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,否则Kubernetes会自动分配一个可用的端口,(http://nodePordIP:31002)
NidePort的实现方式是在Kubernetes集群里的每个Node上为需要外部风闻的Service开启一个对应的TCP建通端口,外部系统只要用任意一个Node的IP地址+具体的IP地址端口号即可访问此服务,在任意Node上运行netstat命令,即可看到NodePort端口被监听
netstat -tnlp |grep 31002
但NodePort 还没完全解决外部访问Service的所有问题,比如负载问题,假如集群中有10个Node,则此时最好有一个负载均衡器,外部的请求只需访问此负载均衡的IP地址由负载均衡器负责转发流量到后面某个Node的NodePort上
如上图中的Load balancer组件独立于Kubernetes集群之外,通常是一个硬件的负载均衡器,或者是以软件方式实现的,例如HAProxy或者N。对于每个Service,我们通常需要配置一个对应的Load balancer 实例来转发流量到后端的Node上,这样增加了工作量及出错的概率,因此Kubernetes 提供了自动化的解决方案,如果我们的集群运行在谷歌的GCE公有云上,那么只要我们把Service的type=NodePort改为type = LoadBalancer,此时Kubernetes会自动创建一个对应的Load balancer实例并返回它的IP地址供外部客户端使用,
9. Volume(存储卷)
Volume是Pod中能够被多个容器访问的共享目录。Kubernetes的Volume概念,用途和目的与Docker 的Volume 比较类似,单两者不能等价。首先,Kubernetes中的Volume定义的Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下;其次,Kubernetes中的Volume与Pod的生命周期相同,但与容器的生命周期不相关,当容器终止或者启动时,Volume中的数据也不会丢失,最后,Kubernetes支持多种类型的Volume,例如GlusterFS,Ceph等先进的分布式文件系统
Volume的使用也比较简单,在大多数情况下,我们现在Pod上声明一个Volumn,然后在容器里引用该Volume并Mount到容器里的某个目录上,举例说,我们要给之前的Tomcat Pod增加一个名字为dataVol的Volume,并且Mount到容器的/mydata-data目录上,则只要对Pod的定义文件修改如下(加#)
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里的多个容器共享文件,让容器的数据写到宿主机的磁盘上或者写文件到网络存储中,Kubernetes 的Volume还扩展出了一种非常有实用价值的功能,即容器配置文件集中化定义与管理,这是通过ConfigMap这个新的资源对象来实现的
Kubernetes的Volume类型
1.emptyDir
一个emptyDir Volume是在Pod分配到Node时创建的,从它的名称可以看出,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为这是Kubernetes自动分配的一个目录,当Pod从Node上移除时,emptyDir中的数据会被永久删除,emptyDir如下
1.1 临时空间,例如用于某些程序运行时所需的临时目录,且无需永久保留。
1.2 长时间任务的中间过程CheckPoint的临时保存目录
1.3 一个容器需要从另外一个容器中获取数据的目录(多容器共享目录)
目前,用户无法控制emptyDir使用的介质种类。如果kubelet的配置是使用硬盘,那么所有emptyDir都将创建在该硬盘上。Pod在将来可以设置emptyDir是位于硬盘,固态硬盘上还是基于内存的tmpfs上,上面的例子采用了emptyDir类的Volume.
2. hostPath
hostPath为在Pod上挂载宿主机上的文件或目录,它通常可以用于一下几个方面。
2.1 容器应用程序生成的日志文件需要永久保存时,可以使用宿主机的高速文件系统进行存储
2.2 容器访问宿主机上Docker引擎内部数据结构的容器应用时,可以通过定义的hostPath为宿主机/var/lib/docker 目录,使容器内部应用可以直接访问Docker的文件系统。
使用池中类型的Volume时,注意以下几点
1.1 在不同的Node上具有相同配置的Pod可能会因为宿主机上的目录和文件不同而导致对Volume上目录和文件的访问结果不一致
1.2 如果使用了资源配额管理,则Kubernetes 无法将hostPath在宿主机上使用的资源纳入管理
下面例子中使用宿主机的/data目录定义了一个hostPath类型的Volume;
volumes:
- name: "persistent-storage"
hostPath:
path: "/data"
3. gcePersistentDisk
使用这种类型的Volume表示使用谷歌公有云提供的永久磁盘(Persistent Disk, PD)存放Volume的数据,它与EmptyDir不同,PD上的内容会被永久保存,当Pod被删除时,PD只是被卸载(Unmout)但不会被删除。需要注意额是你需要先创建一个永久磁盘(PD),才能使用gecPersistentDisk
限制条件如下
1. Node(运行kubelet的节点)需要是GCE虚拟机
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
# This GCE PD must already exist.
gcePersistentDisk:
pdName: my-data-disk
fsType: ext4
4. awkElasticBlockStore
与GCE类似,该类型的Volume使用亚马逊共有云提供的EBS Volume存储数据,需要先创建一个EBS Volume才能使用awsElasticBlockStore.
限制如下:
1,Node (运行kubelet的节点)需要AWS EC2实例
2,这些AWS EC2实例需要与EBS volume 存在于相同的region和availability-zone中
3,EBS只支持单个EC2实例mount一个volume.
通过awk 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
# This AWS EBS volume must already exist.
awkElasticBlockStore:
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:使用Linux块设备共享存储(Rados Block Device)挂载到Pod中
5. gitRepo: 通过挂载一个空目录,并从GIT库clone 一个git repository以供Pod使用
6. secret: 一个secret volume用于为Pod提供加密的信息,聂可以将定义在Kubernetes中的secret直接挂载为文件让Pod访问。secret volume 是通过tmfs(内存文件系统)实现的,所以这种类型的volume总是不会持久化的
10, Persistent Volume
之前我们提到的Volume是定义在Pod上的,属于“计算资源”的一部分,而实际上,“网络存储”是相对独立于“计算资源”而存在的一种实体资源。比如在使用虚机的情况下,我们通常会定义一个网络存储,然后从中划出一个”网盘“并连接到虚拟机上。Persistent Volume(简称PV)和与之相关联的Persistent Volume Claim(简称PVC)也起到了类似的作用
PV可以理解成Kubernetes集群中的某个网络存储中对应的一块存储,它与Volume很类似,区别如下
1. PV只能是网络存储,不属于任何Node,但可以在每个Node上访问。
2. pv并不是定义的Pod上的,而是独立于Pod之外定义
3. PV目前只有几种类型: GCE Persistent Disks, NFS , RBD , iSCSCI , AWs, ElasticBlockStore,GlusterFS等
下面是一个NFS类型PV的一个yaml定义文件,声明了需要5Gi的存储空间
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0003
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(PVC)对象:
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自动回收失败
11. Namespace(命名空间)
Namespace(命名空间)是Kubernetes系统中的另外一个非常重要的概念,Namespace在很多情况下用于实现多租户的资源隔离。Namespace 通过将集群内部的资源对象“分配”到不同的Namespace中,形成逻辑上分组的不同项目,小组或用户组,便于不同的分组在共享使用整个集群的资源的同事还能被分别管理。
Kubernetes集群自动启动后,会创建一个名为"default"的Namespace,通过kubectl可以查看到:
[root@server /data/volume]# kubectl get namespace
NAME STATUS AGE
default Active 30d
kube-system Active 30d
接下来,如果不特别知名Namespace,则用户创建的Pod,RC,Service都将被系统创建到这个默认的名为default的Namespace中。
Namespace的定义很简单。如果所示的yaml定义了名为development的Namespace
apiVersion: v1
kind: Namespace
metadata:
name: development
一旦创建了Namespace,我们在创建资源对象时就可以指定这个资源对象属于那个Namespace.如下,我们定义了一个名为busybox的Pod,放入development这个Namespace里:
apiVersion: v1
kind: Pod
netadata:
name: busybox
namespace: development
spec:
containers:
- image: busybox
command:
- sleep
- "3600"
name: busybox
此时,使用kubectl get 命令无法查看将无法显示
因为不加参数,则kubectl get 命令将仅显示属于"default"命名空间的资源对象。
可以在kubectl命令中加入--namespace参数来查某个命名空间的对象:
kubectl get pods --namespace=development
当我们给每个租户创建一个Namespace来实现多租户的资源隔离时,还能结合Kubernetes的资源配额管理,限定不同租户能占用的资源,例如CPU使用量,内存使用量等。
12. Annotation (注解)
Annotation与Label类似,也使用key/value键值对的形式来进行定义。不同的是Label具有严格的命名规则,它定义的是Kubernetes对象的元数据(Metadata),并且用于Label Selector。而Annotation则是用户任意定义的"附加"信息。以便于外部工具进行查找,很多时候,Kubernetes的模块自身会通过Annotation的方式标记资源对象的一些特殊信息
Annotation信息如下
1.build信息,release信息,Docker镜像信息,例如时间戳,release id号,PR号,镜像hash值,docker registry地址等
2. 日志库,监控库,分析库等资源库的地址信息
3. 程序调试工具信息,例如工具名称,版本号等
4. 团队的联系信息,例如电话号码,负责人名称,网址等。
13.小结
如上是Kubernetes系统的核心组件,共同构成Kubernetes系统核心的框架和计算模型。通过对他们进行灵活组合,用户就可以快速,方便的对容器集群进行配置,创建和管理。