赞
踩
Kubernetes中的Master是集群的控制节点,每个Kubernetes集群里都需要有一个Master节点来负责整个集群的管理和控制。
Master节点上运行着以下一组关键进程:
从Master运行的进程就可以看出Master的重要性,所以通常会单独占有一个服务器(高可用部署建议用3台服务器),一旦Master不可用,那么对集群内容器的管理都将失效:
除此之外,Master节点还会运行etcd(分布式数据存储服务):kubernetes里的所有资源对象的数据全部是保存在etcd。
集群中,非Master的节点被称为Node节点,Node节点才是Kuberbetes集群中的工作节点,每个Node都会被Master分配一些负载(Pod),当某个Node挂掉时,其上的工作负载会被Master自动转移到其他Node节点上。
每个Node节点上都运行着以下一组关键进程:
主要作用:
apiserver也是作为一个Service运行在k8s集群中,Service的名字是“kubernetes”,在集群内可以通过该域名来访问他,并且的它的Cluster IP地址是整个地址池的第一个地址!
apiserver提供一套REST接口用于管理资源对象的增、删、改、查和监听变化;还提供了一套特殊的proxy接口,将受到的REST请求转发到某个Node上的kubelet守护进程。
集群中各个功能模块之间就是通过apiserver提供的REST接口(GET、LIST和WATCH方法)来实现,从而实现各模块之间的信息交互。例如:
作为集群内部的管理控制中心,负责集群内的Node、Pod副本、Endpoint(服务端点)、Namespace(命名空间)、ServiceAccount(服务账号)、ResourceQuota(资源定额)等对象的管理。
内部包含:Relication Controller、Node Controller、ResourceQuota Controller、Namespace Controller、ServiceAccount Controller、Token Controller、Service Controller、Endpoint Controller等多个Controller,一种Controller就是管理一种资源对象。
Relication Controller
这里的Relication Controller不是资源对象RC,而是“副本控制器”。核心作用是确保集群中一个RC关联的Pod副本数量保持预设值:
Node Controller
kubelet进程在启动时,会通过apiserver注册自身的节点信息,并定时向apiserver汇报状态信息。
apiserver在收到这些信息后,会将信息更新到etcd中,etcd中存储的节点信息包括:节点健康状态、节点资源、节点名称、节点地址信息、操作系统版本、Docker版本、kubelet版本等。
Node Controller通过apiserver实时获取Node的相关信息,实现管理和监控集群中的各个节点的相关控制功能。节点的健康状态包含:就绪true、未就绪false和未知unknown。
例如,有个Node节点很久没有上报自己的状态,Node Controller就会变更该Node节点的状态为未就绪,然后出发集群的恢复动作(像重建未就绪节点上的Pod)。
ResourceQuota Controller
资源配额管理,该功能用于避免由于某些业务进程的涉及缺陷,导致整个系统运行絮乱甚至以外死机。在k8s中,主要由三个层次的资源配置管理:
k8s的资源配额管理是通过Admission Controller(准入控制)来控制的,当用户通过apiserver请求创建或修改资源时,Admission Controller会计算当前配合使用情况,如果不符合约束,则创建对象失败。
NamespaceController
用于可以通过apiserver创建或删除的Namesapce,Namespace Controller定时通过apiserver拉取Namespace的信息。如果Namespace被标记为优雅删除,则将该Namespace的状态设置为“Timinating”并保存到etcd中。
同时Namespace Controller删除该Namespace下的ServiceAccount、RC、Pod、secret、PersistentVolume、ListRange、ResourceQuota和Event等资源对象。
Service Controller
Service Controller属于k8s集群与外部的云平台之间的一个接口控制器。
Service Controller监听Service的变化,如果是一个LoadBalancer类型的Service,则Service Controller确保外部云平台上该Service对他的LoadBalancer实例被相应的创建、删除及更新理由转发表。
Endpoint Controller
Endpoint Controller监听Service和对应Pod的变化:
Kubernetes Scheduler在整个系统中承担“承上启下”的重要功能:
通过API创建新的Pod或者Controller Manager为补足副本数创建Pod后,Scheduler根据特定的调度算法和调度策略,将Pod绑定到集群的某个Node节点上,并写入etcd中。这就是所谓的“承上”。
Node接地上的Kubelet进程通过API Server的watch接口监听Pod和Node的绑定事件,当有新的Pod被绑定到自己身上,kubelet就会进行处理,负责Pod的下半生。这就是所谓的“启下”。
Scheduler默认调度流程分为两步:
预选策略有:
默认的预选策略就是前面5个,要5个都通过了,node节点才会进行备选列表。
优选策略有:
在Kubernetes集群中,每个Node节点上都会启动一个kubelet服务进程。主要的职责有:
节点管理
一般把kubelet的启动参数设置为“--register-node”设置为true,向运行在Master上的API Server注册节点信息。并在之后,定时向API Server发送节点的新信息,API Server在接收到这些信息后,将这些信息写入etcd。
Pod管理
大部分情况下(非静态Pod),kubelet通过API Server监听etcd目录,同步Pod列表。
当发现有新的绑定到本节点的Pod,就会按照Pod清单的要求创建Pod。
如果发现删除本节点的Pod,则删除相应的Pod,并通过Docker client删除Pod中的容器。
如果监听到的信息是修改,则停止原来的Pod,启动新的Pod。
容器健康检查
执行LivenessProbe探针来判断容器健康状态。
执行ReadinessProbe探针来判断容器是否启动完成。
cAdvisor资源监控
cAdvisor自动查找在其所在节点上的容器,自动采集CPU、内存、文件系统和网络的使用统计信息。并且通过它所在节点机器的Root容器,采集并分析节点机器的全面使用情况。
Kubernetes集群上的每个Node上都会运行一个kube-proxy服务进程,这个进程可以看做Service的透明代理兼负载均衡器,其核心功能是将某个Service的访问请求转发到后端的多个Pod实例上。
kube-proxy通过查询和监听API Server中Service和Endpoints的变化,为每个Service都创建一个“服务代理对象”。
服务代理对象是kube-proxy内部的一个数据结构,其中包含了一个用于监听此服务请求的SocketServer。
SocketServer端口是随机选择一个本地空闲的端口,然后再通过修改Iptables,将Service的Cluster IP和NodePort上的流量转发到该随机端口上(NAT)。
kube-proxy内部创建了一个负载均衡器——LoadBalancer,上面保存了Service到对应后端的Endpoints列表的动态转发路由表(反向代理)。
相关资料:k8s四层负载均衡--Service - 运维人在路上 - 博客园
Pod是kubernetes里最重要也是最基本的概念。
每个Pod都有一个特殊的被称为“根容器”的Pause容器,Pause容器术语Kubernetes平台的一部分;除了Pause容器外,一个Pod还会包含一个或多个紧密相关的用户业务容器。如下图:
kubernetes将Pod设计成这样的原因有:
Pod里的多个业务容器共享Pause容器的IP,共享Pause容器挂载的Volume,这样就简化了密切关联的业务容器之间的通讯问题,例如另个容器之间需要共享文件,就很容易做到了。
每个Pod都会被分配一个唯一的IP,称为Pod IP,一个Pod中的多个容器共享Pod的IP地址。
Pod可以分为普通Pod和静态Pod。
普通Pod
普通的Pod一旦创建,就会被存储到etcd中,随后被Master中的Scheduler调度到某个具体的Node上并进行绑定,随后该Pod被对应的Node上的kubelet进程实例化成一组相关的Docker容器,并启动起来。
静态Pod
不受Master节点的管理,而是直接在Node节点上直接运行,并且创建的静态Pod也不会存储到etcd中。
有两种配置方式:
无法与ReplicationController、Deployment或者DaemonSet进行关联,并且kubelet也无法对它们进行健康检查。
Pod生命周期:
Pod的yaml定义如下:
- apiVersion: v1
- #版本号
- kind: Pod
- #Pod
- metadata:
- #元数据
- name: string
- #pod的名称
- namespace: string
- #pod所属的命名空间
- labels:
- #自定义标签列表
- - name: string
- annotations:
- #自定义注释列表
- - name: string
- spec:
- #初始化容器的配置
- initContainers:
- - name:
- image:
- command:
- #pod容器中的详细定义
- containers:
- #pod容器列表
- - name: string
- #容器的名称
- image: string
- #容器的镜像名称
- imagePullPolicy: [Always | Never | IfNotPresent]
- #镜像拉取策略,默认Always
- command: [string]
- #容器的启动命令列表,如不指定则使用镜像打包时实用的启动命令
- args: [string]
- #容器启动的命令参数列表
- workingDir: string
- #容器的工作目录
- volumeMounts:
- #挂载到容器内部的存储卷配置
- - name: string
- #引用pod定义的共享存储卷名称,需要与volumes[]部分定义的共享存储卷名称
- mountPath: string
- #存储卷在容器内Mount的绝对路径,应少于512字符
- readOnly: boolean
- #是否为只读模式,默认读写模式
- ports:
- #容器需要暴露的端口列表
- - name: string
- #端口的名称
- containerPort: int
- #容器需要监听的端口号
- hostPort: int
- #容器所在主机需要监听的端口号,默认与containerPort相同。设置hostPort时,同一台宿主机将无法启动该容器的第二副本。
- protocol: string
- #端口协议,支持TCP、UDP,默认TCP
- env:
- #容器运行前需要设置的环境变量列表
- - name: string
- #环境变量名称
- value: string
- #环境变量的值
- resources:
- #资源限制和资源请求的设置
- limits:
- #资源限制设置
- cpu: string
- #CPU限制,单位为core数,
- memory: string
- #内存限制,单位可为MiB,GiB
- requests:
- #资源限制设置
- cpu: string
- #CPU请求,容器启动初始可用数量
- memory: string
- #内存请求,容器启动初始可用数量
- livenessProbe:
- #对pod内各容器健康检查的设置,到探测到无响应几次后,系统将重启容器,可以设置的方法:exec、httpGet和tcpSocket
- #对于一个容器仅需设置一种健康检查方法
- exec:
- #对pod内各容器健康检查的设置
- command: [string]
- #exec方式需要指定的命令或脚本
- httpGet:
- #对pod各容器健康检查设置 ,需要指定path、port
- path: string
- port: number
- host: string
- scheme: string
- httpHeaders:
- - name: string
- value: string
- tcpSocket:
- #对pod内各容器健康检查的设置
- port: number
- initialDelaySeconds: 0
- #容器启动完成后首次探测时间,单位0s
- timeoutSeconds: 0
- #对容器健康检查的探测等待响应时间设置,默认1s,超过该时间设置,将认为容器不健康,会重启该容器
- periodSeconds: 0
- #对容器健康检查的定期探测时间设置,默认10s探测一次
- successThreshold: 0
- failureThreshold: 0
- securityContext:
- privileged: false
- restartPolicy: [Always | Never | OnFailure]
- #pod的重启策略
- nodeSelecrets: object
- #设置Node的Label,以key:value格式指定,pod将被调度到具有这些label的Node上
- imagePullScerets:
- #pull镜像时使用的Secret名称,以namespace:secretkey格式指定
- - name: string
- hosNetwork: false
- #是否使用主机网络模式,默认false,设置true表示设置容器使用宿主机网络,不在使用Docker网桥,该pod将无法在同一台宿主机上启动2个副本
- volumes:
- #该pod上定义的共享存储卷列表
- - name: string
- #共享存储卷的名称。在pod中每个存储卷定义一个名称。容器定义部分volumeMounts[].name将应用定义名称
- #Volume的常用类型:emptyDir、hostPath、secret、configMap、nfs等等
- emptyDir: {}
- #类行为emptyDir的存储卷,表示与pod同生命周期的一个临时目录 ,其值为一个空对象。
- hostPath:
- #类型为hostPath的存储卷,表示挂载pod所在的主机目录
- path: string
- #通过此处指定
- secret:
- #类型为secret的存储卷,表示挂载集群预定义的secret对象到容器内部
- secretName: string
- items:
- - key: string
- path: string
- configMap:
- name: string
- items:
- - keys: string
- path: string
其中container的livenessProbe、resources和restartPolicy跟开发关系较大。
livenessProbe(健康检查)
即容器的健康检查,如果容器不健康,会被重启。健康检查的方式可以有exec、httpGet和tcpSocket三种。
resources(资源)
即容器的资源请求(requests)和资源限制(limits)。Master的Scheduler根据requests的来作为分配Node的一个依据,Node根据limits来对容器使用的资源做限制。
restartPolicy(重启策略)
spec.initContainers也很有意思:
初始化容器可以在应用启动之前进行一些初始化操作:
使用skywalking的时候,需要在容器力能访问到skywalking-agent.jar的java agent代理包。
我们是怎么达成这个效果的?
改变了服务的基础镜像!在java镜像的基础来,加多了skywalking一层,形成了新基础镜像,所有业务服务都以此作为基础镜像。
如果是在k8s里,其实就可以利用initContainers来实现相同的效果。一个很小的镜像,包含了skywalking-agent.jar包,把这个包cp到Pod上定义的一个类型为emptyDir的volumeMounts上,业务服务容器也挂载这个volumeMounts。
Label是Kubernetes的另外一个核心概念。一个是Label就是一个键值对key=value,key和value都由用户自己指定。
Label可以附加到各种资源对象上,例如Node、Pod、Service、RC等,一个资源对象可以定义任意数量的Label,同一个Label也可以被添加到任意数量的资源对象上。
Label通常在定义资源对象的时候确定,当然,也可以时候再给对象动态添加或删除。
Labek相当于我们熟悉的“标签”,给某个资源对象定义一个Label,相当于给它打上了一个标签,随后可以通过Label Selector(标签选择器)查询和筛选有指定标签的资源对象。
Label Selector的表达式有两种表达式:
支持多个Label Selector表达式的组合实现复杂条件选择,多个表达式之间用“,”分隔,多个表达式之间是AND关系,例如:
name=redis-slave,env!=production,查询的是name是redis-slave且env不是production的对象。
通过Label和Label Selector可以很方便实现资源的分组管理功能。在K8s中的重要场景如下:
Annotation(注解)与Label类似,也使用key/value键值对的形式进行定义。
不同的是,Label具有严格的命令规则,它定义的是kubernetes对象的元数据,并且用于Label Selector。
而Annotation则是用户任意定义的“附加”信息,以便于外部工具进行查找,很多时候,kubernetes的模块自身会通过Annotation的方式标记资源对象的一些特殊信息。
通常来说,用Annotation来记录的信息如下:
Volume(存储卷)是Pod中能被多个容器访问的共享目录:
常用的Volume类型有:emptyDir、hostPath、secret、configMap、nfs等等。
我们业务服务最可能使用的是host'Path和emptyDir。
emptyDir是在Pod分配到Node时创建的,当Pod从Node上移除的时候,emptyDir的数据也会被删除。适合用来做这些用途:
hostPath则是挂载宿主机上的文件或目录,常用于这些用途:
在pod中定义一个emptyDir的volume,r然后在containers中引用,挂载到/data0/java/logs目录下,配置如下:
- spec:
- volumes:
- - name: datavol
- emptyDir: {}
- containers:
- - name: demo
- volumeMounts:
- - name: datavol
- mountPath: /data0/java/logs/
PV可以理解成kubernetes集群中的某个网络存储中的对应的一块存储,它与Volume很类似,但有一下区别:
在使用虚拟机的情况下,我们通常会先定义一个网络存储,然后从中划出一个“网盘”并挂接到虚拟机上。Persistent Volume和与相关联的Persistent Volume Claim(简称PVC)也起到了类似的作用。
下面是一个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属性,目前有一下类型:
PV是定义资源,PVC则是对资源的请求。
相关的yaml配置:
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: myclaim
- spec:
- accessModes:
- - ReadWriteOnce
- resources:
- requests:
- storage: 8Gi
- storageClassName: slow
- selector:
- matchLabels:
- release: "stable"
- matchExpressions:
- - {key: environment, operator: In, values: [dev]}
PV和PVC的相互作用遵循这个生命周期:
Provisioning(配置)->Biding(绑定)->Using(使用)->Releasing(释放)->Recycling(回收)
Provisioning:配置PVC。
Biding:k8s会自动关联合适的PV,如果没有合适的PV,则PVC会一直是Pending状态,只有关联PV之后,Pod才可以挂载使用PVC。
Using:Pod使用PVC。
Releasing:删除Pod定义的时候,PVC就是释放的状态,但是它还不能被用于另外一个Pod定义,数据还是保存在PVC上。
Recycling:会对卷的数据进行清理,回收后可重复利用。
应用部署的一个最佳时间是将应用所需的配置信息与程序进行分离,这样可以使得应用程序更好的复用,通过不同的配置也能实现更灵活的功能。
在我们公司,使用的是apollo来实现程序和配置的分离,但其实,k8s也提供了类似的功能,就是ConfigMap了。
ConfigMap供容器使用的典型用法:
ConfigMap以一个或多个key/value的形式保存在kubernetes系统中供应用使用,既可以用于表示变量的值(例如apploglavel=info),也可以用于表示一个完整的配置文件的内容(例如server.xml='<?xml...>...')。
ConfigMap的yaml定义如下:
- apiVersion: v1
- kind: ConfigMap
- metadarta:
- name: app-config
- data:
- apploglevel: info
- key-server.xml: |
- <?xml versiob="1.0" encoding="utf-8">
- <server ....></server>
在Pod中可以这样使用:
-
- ...
- spec:
- containers:
- - name: app
- env:
- - name: APPLOGLEVEL
- valueFrom:
- configMapKeyRef:
- name:app-config
- key: apploglevel
- volumeounts:
- - name: serverxml
- mountPath: /configfiles
- volumes:
- - name: serverxml
- configMap:
- name: app-config
- items:
- - key: key-server.xml
- path: server.xml
RC就是一种根据Pod的Label,来管理Pod副本数的资源对象。下面是一个RC的部分定义:
- apiVersion: v1 #指定api版本,此值必须在kubectl apiversion中
- kind: ReplicationController #指定创建资源的角色/类型
- metadata: #资源的元数据/属性
- name: test-rc #资源的名字,在同一个namespace中必须唯一
- labels:
- k8s-app: apache
- software: apache
- project: test
- app: test-rc
- version: v1
- spec:
- replicas: 2 #副本数量2
- selector: #RC通过spec.selector来筛选要控制的Pod
- software: apache
- project: test
- app: test-rc
- version: v1
- name: test-rc
- template: #这里Pod的定义
- metadata:
- labels: #Pod的label,可以看到这个label与spec.selector相同
- software: apache
- project: test
- app: test-rc
- version: v1
- name: test-rc
- spec: ......
ReclicationController本身是资源对象,所以也有自己的metadata,可以定义自己的name和labels等信息。
spec.relicas表示需要维持Pod的副本数。
spec.selector则表示要管理哪些Pod(通过Label Selector实现)。
spec.template则是要管理的Pod的模板,例如期望的副本数spec.relicas=2,但是当前Pod只有1个副本存活,则RC会根据spec.template创建一个新的Pod。
需要注意,spec.selector中和label必须和spec.template.metadat.labels的内容匹配!
不匹配会出现什么问题?
RC的label selector选择不到它创建的pod,认为当前运行的pod数量不满足期望值,会一直创建~
需要注意,删除RC并不会直接删除RC管理的Pod!
应该将RC的副本数设置为0,再更新RC。
Replica Set是新一代的RC,一样是用于管理Pod的副本数保持为期望值。不过Replica Set的标签选择能力比RC更强!
Replica Set的yaml定义如下:
- apiVersion: apps/v1
- kind: ReplicaSet
- metadata:
- name: nginx
- labels:
- app: nginx
- spec:
- replicas: 3 #副本数量
- selector: #标签选择器
- matchLabels:
- app: nginx
- matchExpressions:
- - {key: version, Operator: In, values: {1.16}}
- template: # Pod的模板
- metadata:
- labels:
- app: nginx
- spec:
- containers:
- - name: nginx
- image: nginx:1.16
Replica Set的spec.selector.matchLabels与R承德spec.selector作用相同。
但是replica Set支持多了matchExpressions,可以用于定义一组基于集合的筛选条件,可以用的条件运算符有:In、NotIn、Exists和DoesNotExist。
如果同时定义了matchLabels和matchExpressions,则需要同时满足两者,是AND关系。
虽然Relica Set比Replication Controller更加强大,但是k8s并不建议直接使用Relica Set,而是使用Deployment,Deployment没自动创建对应的Relica Set来管理Pod的副本。
为什么建议不要直接使用Relica Set,而是使用Deployment呢?
假设我们现在有一个服务a,在生产环境上运行着4个副本。那么当我们要发布新版本的a服务到生产的时候,我们期望发布过程是怎样的?
这几件事情,使用relica set不是说不能做到,还就会需要很多人工干预。但是deployment将这些事情都封装好,我们只要在配置文件中定义好我们的策略即可。deployment的yaml定义如下:
- apiVersion: extensions/v1beta1
- kind: Deployment
- metadata: <Object>
- spec: <Object>
- minReadySeconds: <integer> #设置pod准备就绪的最小秒数
- paused: <boolean> #表示部署已暂停并且deploy控制器不会处理该部署
- progressDeadlineSeconds: <integer>
- strategy: <Object> #将现有pod替换为新pod的部署策略
- rollingUpdate: <Object> #滚动更新配置参数,仅当类型为RollingUpdate
- maxSurge: <string> #滚动更新过程产生的最大pod数量,可以是个数,也可以是百分比
- maxUnavailable: <string> #
- type: <string> #部署类型,Recreate,RollingUpdate
- replicas: <integer> #pods的副本数量
- selector: <Object> #pod标签选择器,匹配pod标签,默认使用pods的标签
- matchLabels: <map[string]string>
- key1: value1
- key2: value2
- matchExpressions: <[]Object>
- operator: <string> -required- #设定标签键与一组值的关系,In, NotIn, Exists and DoesNotExist
- key: <string> -required-
- values: <[]string>
- revisionHistoryLimit: <integer> #设置保留的历史版本个数,默认是10
- rollbackTo: <Object>
- revision: <integer> #设置回滚的版本,设置为0则回滚到上一个版本
- template: ......
spec.strategy就是部署的策略:
spec.strategy.type: Recreate就是停止旧Pod,直接重新建新的Pod;
spec.strategy.type: RollingUpdate就是滚动升级,一部分一部分的替换。采用滚动升级的策略,还能设置spec.strategy.rollingUpdate.maxSurge和spec.strategy.rollingUpdate.maxUnavailable。
spec.strategy.rollingUpdate.maxSurge:50%,按刚才a服务的例子,滚动升级的过程中,最多允许出现6个副本同时运行。
spec.strategy.rollingUpdate.maxUnavailable:50%,按刚才a服务的例子,最少需要运行2个副本。
Service是k8s中最核心的资源对象之一。
Service的作用就是把一组Pod副本组合成同一个集群,所有Pod请求都由Service接收,Service再将请求转发到组成集群的Pod中。
Service的yaml定义如下:
- apiVersion: v1 #API的版本号,版本号可以用 kubectl api-versions 查询到
- kind: Service #表明资源对象,例如Pod、RC、Service、Namespace及Node等
- metadata: #资源对象的元数据定义
- name: engine #service名称
- spec: #资源对象的详细定义,持久化到etcd中保存
- type: ClusterIP #Service类型,ClusterIP供kubernates集群内部pod访问
- ports: #暴露的端口列表
- - port: 8080 #Service监听的端口,对应ClusterIP,即ClusterIP+ServicePort供集群内部pod访问的
- targetPort: 8080 #对应pod中容器的端口
- protocol: TCP #协议,支持TCP、UDP,默认TCP
- name: http #端口名称
- selector: #label选择器,管理label对应的pod
- name: enginehttpmanage #pod的label
- status: #公有云上才会使用到
- loadbalancer:
- ingress:
- ip:
- host:
spec.ports[].targetPort不是必填的,没有指定targetPort的情况下,默认targetPort等于port。ports是一个数组,可以支持多端口服务。Selector决定了Service由哪些Pod组成。
k8s中,每个Pod都会被分配一个单独的IP地址,而且每个Pod都提供了一个独立的Endpoint(Pod IP + ContainerPort)。存在多个Pod的时候,别人要怎么访问这些pod呢?
如果要客户端记录下所有Pod的Endpoint,然后客户端自己做负载均衡,那就太为难调用者了。
更好的做法是部署一个负载均衡器,代理这组Pod的请求,Pod的调用者直接把请求发送到负载均衡器,再由负载均衡器把请求转发给这组Pod中的一个。
在k8s里,这个负载均衡器就是Service。Service一旦被创建,就会被分配一个可用的ClusterIP,而且在Service的整个声明周期中,它的Cluster IP都不会发生改变。
创建Service的时候,除了分配一个固定的Cluster IP,还在添加一个DNS域名解析:service的name→cluser IP。这就是kubernetes中的服务发现机制,简单明了,比什么dubbo、springCloud好理解多了~
像在房多多里,就可以用微服务的域名作为service的name,例如调用http://project.zqb.ep.fdd:65535/projects,就能拿到项目列表。
不过Service的ClusterIP并不是一个真实的IP,有自己的局限性:
总结下来,就是Cluster IP只在kubernetes中生效,集群外部无法直接访问到这个地址。那怎么才能让集群外部访问到Service呢?
这个时候,就要使用NodePort:
- apiVersion: v1
- kind: Service
- metadata:
- name: engine
- spec:
- type: NodePort
- ports:
- - port: 8080
- nodePort: 31002
- protocol: TCP
- name: http
- selector:
- name: enginehttpmanage
这样配置,所有Node节点就会在31002端口开启TCP监听,从集群外部访问访问任意一个节点的31002端口,请求都会被正确转发到service管理的后端Pod上。
不过这样子,就会需要在所有Node的前面再部署一个负载均衡,例如nginx,作为流量的统一入口,再分发到各个Node节点上。
一般来讲,k8s中的每个Service对应我们微服务中的一个“微服务”。
到这里,可能会有点困惑,微服务不应该是Deployment么,为什么变成Service了?Pod、RelicaSet、Deployment和Service的关系是怎样的?
deployment根据Pod的标签关联到Pod,是为了管理pod的生命周期,pod挂了,重新启动一个新的pod;滚动升级;回滚等。
service根据Pod的标签关联到pod,是为了让外部访问到pod,给pod做负载均衡。
这里就涉及另外一个问题:Pod是受Deployment控制的,会有Pod销毁,也会有新的Pod被创建,Service需要转发的Pod列表是会动态变化的!k8s如何解决这种变化?
有一种特殊的Service,是不需要设置Cluster IP的,这种Service被称为Headless Service(无头服务)。配置如下:
- apiVersion: v1
- kind: Service
- metadata:
- name: nginx
- labels:
- app: nginx
- spec:
- ports
- - port: 80
- clusterIP: None
- selector:
- app: nginx
其中指定了clusterIP=None,表示不需要分配cluster ip地址。无法直接访问该Service,目的只是为了能通过Service来得到Label Selector选择的后端Pod,将Pod列表返回给客户端。
多用于StatefulSet,来搭建“去中心化”类的应用集群(集群中的节点需要感知到其他节点的加入/退出集群,通过Service的Label Selector实现)。
对于基于HTTP的服务来说,不同的URL地址经常对应到不同的后端服务,这些应用层的转发机制仅通过Kubernetes的Service机制是无法实现的。
例如:
是不是类似我们的统一网关功能~
Ingress对象是k8s v1.1版本添加的,专门用于不同URL的访问请求转发到后端不同的Service。
Kubernetes使用一个Ingress策略定义和一个具体的Ingress Controller,两者结合实现一个完成的Ingress负载均衡器。
Ingress Controller:
定义Ingress策略之前,需要先部署Ingress Controller。在K8s中,Ingress Controller将以Pod的形式运行,通过监控apiserver的/ingress接口,实现配置的动态变更。基本流程如下:
这个谷歌有提供现成的nginx-ingress-controller镜像。这个镜像需要配置一个默认的backend服务,用于在客户端访问的url地址不存在时,能够返回一个404应答。
定义Ingress策略:
- apiVersion: extensions/v1betal
- kind: Ingress
- metadata:
- name: website-ingress
- spec:
- rules:
- - host: api.fangdd.com
- http:
- paths:
- - path: /bcc-bp-fdd
- backend:
- serviceName: bcc-bp-fdd
- servicePort: 8080
这样定义,访问api.fangdd.com/bcc-bp-fdd的请求,就被被转发到bcc-bp-fdd服务的8080端口。
Ingerss可以实现如下的转发效果:
DaemonSet
DaemonSet与Deployment有点类型,都是通过label管理一批Pod。
比较特别的是,在DaemonSet里,不需要指定Pod的副本数,它会给每个Node都分配运行一个Pod,每个Node有且只有一个Pod。
像Filebeat这种,拉取每个Node节点上的日志的,就适合定义成DaemonSet。
yaml配置:
- apiVersion: apps/v1
- kind: DaemonSet
- metadata:
- name: deamonset-example
- labels:
- app: daemonset
- spec:
- selector:
- matchLabels:
- name: deamonset-example
- template:
- metadata:
- labels:
- name: deamonset-example
- spec:
- containers:
- - name: daemonset-example
- image: wangyanglinux/myapp:v3
在k8s中,Pod管理对象RC、Deployent、DaemonSet和Job都是面向无状态的服务。但现实中有很多服务是有状态的,特别是一些复杂的中间件集群,例如Mysql几圈、Kafka集群、Zookeeper集群等。
这些有状态的集群有一下的共同点:
为了实现支持有状态的集群,StatefulSet被设计出了如下特性:
StatefulSet处理要跟PV卷捆绑使用,以存储Pod的状态数据,还要与Headless Service配合使用。需要在StatefulSet的定义中,指定它属于哪个Headless Service。
Event是一个事件记录,记录了事件最早产生时间、最后重现时间、重复次数、发起者、类型,以及导致该事件的原因等信息。
Event通常会关联到某个具体的资源对象上,是排查故障的重要参考。例如有个Pod一直启动不了,可以用kubelet describe pod xxx 来查看它的信息描述,利用输出的Event信息来定位问题原因。
Namespace(命名空间)是kubernetes系统中的另一个非常重要的概念,Namespace在很多情况下用于多租户隔离。Namespace通过将集群内部的资源对象“分配”到不同的Namespace,形成逻辑上分组的不同项目、小组或用户组,便于不同的分组在共享使用整个集群的资源的同时还能被分别管理。
Downward API可以通过一下两种方式将Pod信息注入容器内部:
我们生产的k8s节点配置是16核128G,那么我们的java服务使用的内存是多少呢?
能直接通过-Xms=1G -Xmx=2G这样指定初始1G内存,最大2G内存么?
这个是不合适的,例如-Xmx=2G,但是Pod的resources.requests.memory和resources.limits.memory都动态调整为4G,那指定java服务最大堆内存为2G就不合适了。
最好的方式,是根据Pod配置的resources来动态识别自己能使用的内存。JDK 版本java 8u191+新增里几个启动参数,专门支持Docker环境:
既然是比例,然分母是谁?resources.requests.memory还是resources.limits.memory?
是resources.limits.memory!个人觉得,选择limits,是因为真的允许Pod使用到这么多内存。如果使用requests,就丢失了limits的作用。
那么对于java服务,requests和limits应该怎么配置?例如一个服务,95%时间使用1G内存,5%的时候使用2G内存,应该怎么配置好的?
requests.memory: 1Gi和limits.memory: 2Gi 似乎是比较合理的?
但可能不太对,配置成requests.memory: 2Gi和limits.memory: 2Gi 更合适一点。
我们java的堆大小,是扩大之后,并不会缩小,因为我们只指定了MaxRAMPercentage值。当达到业务高峰之后,服务占用的内存就会是2G,并不会因为空闲了,就把内存还给操作系统。
而k8s分配Pod到Node节点的时候,是按requests的值来分配,只会认为node节点被占用1G内存。这就容易造成k8s节点的负载不均衡了。
查看jvm中的各种参数以及默认值 - smile_lg - 博客园
调整jvm参数_假笨说Java堆大小动态调整的JVM参数_weixin_39890452的博客-CSDN博客
jdk1.8.191 JVM内存参数 InitialRAMPercentage和MinRAMPercentage - fengjian1585 - 博客园
定义Service之后,可以把Service名作为域名,加上Service暴露的端口来访问业务服务。例如定义一个Service,名字为project.zqb.ep.fdd,暴露的端口为65535,则可以通过http://project.zqb.ep.fdd:65535/来访问业务服务接口。
这样子,只要知道我们要调用的业务服务在k8s中定义的Service的name和暴露的端口(还记得发布系统中,创建申请创建一个服务的时候,都会分配或指定一个端口么,这就是原因),就能调用到业务服务的接口,完全不需要引入SpringCloud或者Dubbo。
使用k8s自身的服务发现机制,看起来很美好,但是存在一个问题,负载不均衡!
例如我们业务服务提供的是http接口,客户端调用的时候,采用长连接的方式,那么在第一次调用后,就会跟Service背后的某个Pod建立了场链接,之后的所有调用,都会调用这个Pod。这样,请求就没有均衡的分配到Service代表的所有Pod上。
解决方式有:
具体分析可以看:解决k8s中的长连接负载均衡问题
个人认为“在客户端实现负载均衡”和“在服务端实现负载均衡”是不大可取的,使用service mesh或者ingress是比较好的选择。
在发生oom的时候,有优先干掉oom_score_adj分高的。对于oom_score_adj分,BestEffort>Burstable>Guaranteed。
Node节点上的资源,并不能全部都交给Pod使用,操作系统需要使用一部分,k8s的进程本身也需要消耗一部分。所以有两个配置,可以预留这两部分的内存:
--system-reserved=memory=1.5Gi #系统保留资源
--kube-reserved=memory=1.5Gi #k8s保留资源
kubelet持续监控主机的资源使用情况,并尽量防止计算资源被耗尽。一旦出现资源紧缺的迹象,kubelet就会主动终止部分pod的运行,以回收资源。
触发驱逐的信息有:
信号 | 压力类型 | 描述 |
---|---|---|
memory.available | MemoryPressue | 内存不足 |
nodefs.available | DiskPressure | 内盘不足 |
nodefs.inodesFree | DiskPressure | 文件系统的inode不足 |
imagefs.available | DiskPressure | 镜像文件存储空间不足 |
imagefs.inodesFree | DiskPressure | 镜像文件的inode不足 |
当出现MemoryPressue:Scheduler不再调度新的BestEffort pod到这个节点。
当出现DiskPressure:不再向这个节点调度pod
软驱逐
系统资源达到软驱逐阈值并在超过宽限期之后才会执行驱逐动作。
--eviction-soft:描述驱逐阈值,例如:memory.available<1.5G
--eviction-soft-grace-period:驱逐宽限期,memory.available=1m30s
--eviction-max-pod-grace-period:终止pod最大宽限时间,单位s
硬驱逐
系统资源达到硬驱逐阈值时立即执行驱逐动作。
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
当发生MemoryPressue,开始驱逐的时候,Node会对运行在它之上的Pod打分,优先干掉oom_score_adj高的Pod。
回收资源的时候,为了让系统更稳定,不会一直波动(一会标识自己正常,一会标识自己存在内存压力),就有了其他配置:
--eviction-minimum-reclaim:每一次 eviction 必须至少回收多少资源。
--eviction-pressure-transition-period:默认为5分钟,达到pressure condition的时间,超过阈值时,节点会被设置为memory pressure或者disk pressure,然后开启pod eviction。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。