概述
微服务架构是替代以单个进程或几个进程运行在服务器上为部署方式的一种架构,它将单体应用分解成若干个可独立运行的组件。微服务的解耦性确保他们可以独立开发、部署、升级、伸缩。
如何部署、管理这些微服务,并充分利用宿主机的硬件资源,诞生了K8S。它将人员分为开发人员和系统管理员,系统管理员负责处理硬件、集群相关的事务,开发人员只需要提交自己的应用和描述,K8S就会按照描述,把应用启动起来,并暴露定义的接口。
一、Kubernetes介绍
1.1 容器技术
虚拟机可以隔离不同的微服务环境,Linux容器技术也可以。容器和虚拟机相比开销要小得多,容器里运行的进程实际上运行在宿主机上,但是和其他进程隔离,开销仅是容器消耗的资源。
容器使用两个机制来隔离运行在同一个操作系统上的多个进程
Linux命名空间:可以在某个命名空间运行一个进程,进程只能看到这个命名空间下的资源。当然,会存在多种类型的命名空间,所以一个进程不单单只属于某一个命名空间,而属于每个类型的一个命名空间。
内核的cgroups:限制进程能使用的资源量(CPU、内存、网络带宽等)不能超过被分配的量。
1.2 Kubernetes架构
- 主节点
主节点承载着K8S控制和管理整个系统的控制面板。控制面板的组件持有并控制集群状态,但它们不运行应用,应用由工作节点运行。
API服务器:应用和其他控制面板组件都要和它通信
Scheduler:调度应用
Controller Manager:执行集群级别的功能,如复制组件、秩序跟踪工作节点,处理失败节点
etcd:分布式数据存储,持久化存储集群配置
- 工作节点
运行用户部署的应用
Docker、rkt或其他容器类型
Kubelet:与API服务器通信,管理它所在节点的容器
kube-proxy:负责组件之间的负载均衡网络流量
K8S采用声明式的控制流,所有的资源声明都保存在etcd,所有的组件通过API Server来声明或监听。
二、Pod
类型
自主式Pod
控制器管理的Pod
字段
1 | apiVersion: v1 |
在k8s中,一般使用yaml格式文件来创建符合我们期望的Pod,常用字段如下
参数名 | 字段类型 | 说明 |
---|---|---|
version | String | K8S API版本 |
kind | String | yaml文件定义的资源类型和角色,例如:Pod |
metadata | Object | 元数据对象, |
metadata.name | String | 元数据对象的名字 |
metadata.namespace | String | 元数据对象的命名空间 |
Spec | Object | 详细定义对象 |
spec.containers[] | list | Spec对象的容器列表定义 |
spec.containers[].name | String | 定义容器名字 |
spec.containers[].image | String | 定义容器镜像 |
spec.containers[].imagePullPolicy | String | 定义镜像拉去策略:Always,Never,IfNotPresent |
spec.containers[].command[] | List | 指定容器启动命令 |
spec.containers[].args[] | List | 指定容器启动命令参数 |
spec.containers[].workingDir | String | 指定容器的工作目录 |
spec.containers[].volumeMounts[] | List | 指定容器内部的存储卷配置 |
spec.containers[].volumeMounts[].name | String | 指定可以被容器挂载的存储卷的名称 |
spec.containers[].volumeMounts[].mountPath | String | 指定可以被容器挂载的存储卷的路径 |
spec.containers[].volumelMounts[].readOnly | String | 默认为读写模式 |
spec.containers[].ports[] | List | 指定容器需要用到的端口列表 |
spec.containers[].ports[].name | String | 指定端口名称 |
spec.containers[].ports[].containerPort | String | 指定容器需要监听的端口号 |
spec.containers[].ports[].hostPort | String | 指定容器所在主机需要监听的端口号,默认跟上面containerPort相同,注意设置了hostPort |
spec.containers[].ports[].protocol | String | 指定端口协议,支持TCP和UDP,默认值为TCP |
spec.containers[].env[] | List | 指定容器运行前需设置的环境变量列表 |
spec.containers[].env[].name | String | 指定环境变量名称 |
spec.containers[].env[].value | String | 指定环境变量值 |
spec.containers[].resources | Object | 设置容器的资源上限 |
spec.containers[].resources.limits | Object | 指定设置容器运行时资源的运行上限 |
spec.containers[].resources.requests | Object | 指定容器启动和调度时的限制设置 |
spec.restartPolicy | String | 定义Pod的重启策略,可选值为Always、OnFailure、Never,默认值为Always. |
spec.nodeSelector | Object | 定义Node的Label过滤标签,以key:value格式指定 |
spec.imagePullSecrets | Object | 定义pul镜像时使用secret名称,以name:secretkey格式指定 |
spec.hostNetwork | Boolean | 定义是否使用主机网络模式,默认值为false。设置true表示使用宿主机网络,不使用docker网桥,同时设置了true将无法在同一台宿主机上启动第二个副本。 |
控制器
ReplicationController 用来确保容器应用的副本数始终保持在用户定义的副本数,即如果有容器异常退出,会自动创建新的Pod来替代;而如果出现多余的异常容器也会被自动回收。新版本的K8S建议市容RS来替代RC
ReplicaSet和RC没有本质的不同,只是名字不一样,RS支持集合式的selector
Deployment为Pod和RS提供了一个声明式定义方法,用来替代以前的RC来方便管理应用。典型的应用场景包括:
- 定义Deployment来创建Pod和RS
- 滚动升级和回滚应用
- 扩容和缩容
- 暂停和继续Deployment
Horizontal Pod Auto scaling仅适用于Deployment和RS,在V1版本中仅支持根据Pod的CPU利用率扩容,在V1alpha版本中,支持根据内存和用户自定义的metric扩缩容
StatefulSet为了解决有状态服务的问题,其应用场景包括
- 稳定的持久化存储,即Pod重新调度后还能访问到相同的持久化数据
- 稳定的网络标识,即Pod重新调度后其Podname和HostName不变
- 有序部署,有序扩展,即Pod是有顺序的,部署和扩展的时候根据定义的顺序依次进行(0~n-1)
- 有序收缩,有序删除(n-1~0)
DaemonSet确保全部(或者一些)Node上运行一个Pod的副本,当有Node加入集群时,也会为他们创建一个Pod。当有Node从集群中移除时,Pod会收回。删除DaemonSet也会删除它创建的Pod。用法
- 在每个Node上运行日志收集daemon
- 在每个Node上运行监控daemon
Job负责批处理任务,即仅执行一次的任务,它保证批处理任务的一个或多个Pod成功结束
Cron Job管理基于时间Job,即周期性的运行Job
探针
探针是由kubelet对容器执行的定期诊断。要执行诊断,kubelet调用由容器实现。有三种类型的处理程序:
ExecAction:在容器内执行指定命令。如果命令退出时返回码为0则认为诊断成功。
TCPSocketAction:对指定端口上的容器的IP地址进行TCP检查。如果端口打开,则诊断被认为是成功的。
HTTPGetAction:对指定的端口和路径上的容器的IP地址执行HTTP Get请求。如果响应状态码大于等于200且小于400,则诊断被认为是成功的
每次探测都将获得以下三种结果之一:
成功:容器通过了诊断。
失败:容器未通过诊断。
未知:诊断失败,因此不会采取任何行动
livenessProbe:指示容器是否正在运行。如果存活探测失败,则 kubelet 会杀死容器,并且容器将受到其重启策略的影响。如果容器不提供存活探针,则默认状态为Success
readinessProbe:指示容器是否准备好服务请求。如果就绪探测失败,端点控制器将从与 Pod 匹配的所有Service的端点中删除该Pod 的IP地址。初始延迟之前的就绪状态默认为Failure。如果容器不提供就绪探针,则默认状态为Success
三、Service
pod会在node间被调度,一组功能相同的pod需要对外提供一个稳定地址,而service就是pod对外的门户。
Service会通过selector绑定多个pod,service通过clusterIP对外接收请求,然后分配给绑定的pod。
1 | apiVersion: v1 |
Service类型
ClusterIP:默认类型,自动分配一个仅Cluster内部可以访问的虚拟IP
NodePort: 在 ClusterlP基础上为Service在每台机器上绑定一个端口,这样就可以通过:NodePort 来访问该服务
LoadBalancer:在NodePort的基础上,借助cloud provider创建一个外部负载均衡器,并将请求转发到: NodePort
服务并不是和pod直接相连的,service创建endpoint,并将流量导向endpoint
endpoint包含了所有后端Pod的IP:Port对
Client -> Service -> Pod
为了实现以上的功能,主要需要以下几个组件的协同工作:
apiserver 用户通过kubectl命令向apiserver发送创建service的命令,apiserver接收到请求后将数据存储到etcd中
kube-proxy kubernetes的每个节点中都有一个叫做kube-porxy的进程,这个进程负责感知service,pod的变化,并将变化的信息写入本地的iptables规则中
iptables使用NAT等技术将virtualIP的流量转至endpoint中
四、configMap和Secret
4.1 configMap
ConfigMap功能在Kubernetes1.2版本中引入,许多应用程序会从配置文件、命令行参数或环境变量中读取配置信息。ConfigMap API给我们提供了向容器中注入配置信息的机制,ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象
1 | kubectl create configmap my-config |
使用
- 使用configMap来替代环境变量
可以通过envFrom
属性字段将所有条目暴露作为环境变量。
- 传递 ConfigMap 条目作为命令行参数
以利用configMap条目初始化某个环境变量,然后再在参数字段中引用该环境变量,形如 “$ENV_VAR”
4.2 Secret
secret和configMap最大的区别在于,secrets用于保存敏感的数据。 secret的数据都不会被写入磁盘,而是挂载在内存盘中。
Secret的大小限于1MB。
Secret最好不要通过环境变量进入container(日志、报错、子进程继承环境等,可能导致泄密),建议使用secret volume来访问Secret
五、卷
pod中的每个容器都有自己的独立文件系统,因为文件系统来自容器镜像。 k8s通过在pod中定义卷,使得存储持久化,和pod共享生命周期,而不会随着容器的重启消失。
emptyDir:用于存储临时数据的简单目录,可以设置为Memory medium,直接存储在内存中
hostPath:用于将目录从工作节点的文件系统挂载到pod中
gitRepo:通过git仓库的内容初始化的卷,不会实时同步,只有在Pod创建时同步
nfs:挂载到pod中的NFS共享卷
云磁盘
网络存储
k8s内部资源卷
- configMap
- Secret
- downwardAPI
PersistentVolume和PersistentVolumeClaim,将对存储的需求和底层的存储技术解耦;将底层存储技术的配置交给k8s集群管理员,而应用开发人员(即k8s使用者)只需要提需求即可
注:PersistentVolume是集群级别的资源,不受namespace限制
PersistentVolume(PV)
是由管理员设置的存储,它是群集的一部分。就像节点是集群中的资源一样,PV也是集群中的资源。PV是Volume之类的卷插件,但具有独立于使用PV的Pod的生命周期。
PersistentVolumeClaim(PVC)
是用户存储的请求。它与Pod相似。Pod消耗节点资源,PVC消耗PV 资源。Pod可以请求特定级别的资源(CPU和内存)。声明可以请求特定的大小和访问模式(例如,可以以读/写一次或只读多次模式挂载)
持久化卷声明的保护
PVC保护的目的是确保由pod 正在使用的PVC不会从系统中移除,因为如果被移除的话可能会导致数据丢失当启用PVC保护 alpha功能时,如果用户删除了一个pod 正在使用的PVC,则该PVC不会被立即删除。PVC的删除将被推迟,直到PVC 不再被任何pod使用
PV访问模式
ReadWriteonce——该卷可以被单个节点以读/写模式挂载
ReadOnlyMany——该卷可以被多个节点以只读模式挂载
ReadWriteMany——该卷可以被多个节点以读/写模式挂载
StorageClass
管理员可以手动通过置备程序创建PV,或者直接创建对应的StorageClass,然后用户创建PVC时,会自动根据StorageClass的设置调用置备程序创建出可供使用的PV。
六、调度
简介
Scheduler是kubernetes的调度器,主要的任务是把定义的pod分配到集群的节点上。
公平:如何保证每个节点都能被分配资源
资源高效利用:集群所有资源最大化被使用
效率:调度的性能要好,能够尽快地对大批量的pod完成调度工作
灵活:允许用户根据自己的需求控制调度的逻辑
Sheduler是作为单独的程序运行的,启动之后会一直监听API Server,获取PodSpec.NodeName为空的pod,对每个pod都会创建一个binding,表明该pod应该放到哪个节点上
调度过程
预选和优选
调度分为几个部分:首先是过滤掉不满足条件的节点,这个过程称为predicate ;然后对通过的节点按照优先级排序,这个是priority ;最后从中选择优先级最高的节点。如果中间任何一步骤有错误,就直接返回错误
Predicate有一系列的算法可以使用:
PodFitsResources :节点上剩余的资源是否大于pod请求的资源
PodFitsHost :如果pod指定了NodeName,检查节点名称是否和NodeName匹配
PodFitsHostPorts :节点上已经使用的port是否和pod申请的port冲突
PodSelectorMatches :过滤掉和pod指定的label不匹配的节点
NoDiskConflict :已经mount的volume和pod指定的volume不冲突,除非它们都是只读
如果在predicate过程中没有合适的节点,pod 会一直在pending 状态,不断重试调度,直到有节点满足条件。
经过这个步骤,如果有多个节点满足条件,就继续priorities过程:按照优先级大小对节点排序
优先级由一系列键值对组成,键是该优先级项的名称,值是它的权重(该项的重要性)。这些优先级选项包括:
LeastRequestedPriority :通过计算CPU和Memory的使用率来决定权重,使用率越低权重越高。换句话说,这个优先级指标倾向于资源使用比例更低的节点
BalancedResourceAllocation :节点上CPU和Memory使用率越接近,权重越高。这个应该和上面的一起使用,不应该单独使用
ImageLocalityPriority :倾向于已经有要使用镜像的节点,镜像总大小值越大,权重越高
通过算法对所有的优先级项目和权重进行计算,得出最终的结果
节点亲和性
pod.spec.nodeAfinity
preferredDuringSchedulingIgnoredDuringExecution:软策略
requiredDuringSchedulingIgnoredDuringExecution:硬策略
键值运算关系
In:label的值在某个列表中
NotIn:label的值不在某个列表中
Gt:label的值大于某个值
Lt:label的值小于某个值
Exists:某个label存在
DoesNotExist:某个label不存在
Pod亲和性
pod.spec.afinity.podAfinity/podAntiAfinity
preferredDuringSchedulingIgnoredDuringExecution:软策略
requiredDuringSchedulingIgnoredDuringExecution:硬策略
Taint和Toleration
Taint和toleration相互配合,可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用一个或多个taint,这表示对于那些不能容忍这些taint的pod,是不会被该节点接受的。如果将toleration应用于pod上,则表示这些pod可以(但不要求)被调度到具有匹配taint的节点上
每个污点有一个key和value作为污点的标签,其中value可以为空,effect描述污点的作用。当前taintefect支持如下三个选项:
NoSchedule:表示k8s将不会将Pod调度到具有该污点的Node上
PreferNoSchedule:表示k8s将尽量避免将Pod调度到具有该污点的Node上
NoExecute:表示k8s将不会将Pod调度到具有该污点的Node上,同时会将Node上已经存在的Pod驱逐出去
设置了污点的Node将根据taint的effect:NoSchedule、PreferNoSchedule、NoExecute和Pod之间产生互斥的关系,Pod将在一定程度上不会被调度到Node上。但我们可以在Pod上设置容忍( Toleration ),意思是设置了容忍的Pod将可以容忍污点的存在,可以被调度到存在污点的Node上
指定调度节点
Pod.spec.nodeName将Pod直接调度到指定的Node节点上,会跳过Scheduler的调度策略,该匹配规则是强制匹配
Pod.spec.nodeSelector:通过kubernetes的label-selector机制选择节点,由调度器调度策略匹配label,而后调度Pod到目标节点,该匹配规则属于强制约束