目录
用了一个月的零零散散的时间对k8s的各个组件进行了系统的学习,整理了一下自己对于各个组件的大致运行机制的理解
k8s整体架构
k8s架构图如下所示
k8s集群由Master节点和Node节点构成
Master节点上的组件为:
-
etcd,存储整个集群的数据和状态信息
-
apiserver,提供了资源操作的唯一入口,并提供认证、授权、鉴权等访问控制功能
-
controller manager, 中央控制管理器,负责维护集群的状态,比如故障检测、自动扩展、滚动更新等
-
scheduler负责资源的调度,按照预定的调度策略将Pod调度到相应的机器上
Node节点上的组件为:
-
kubelet负责维护容器的生命周期,同时也负责Volume(CVI)和网络(CNI)的管理
-
kube-proxy负责为Service提供cluster内部的服务发现和负载均衡
-
Container runtime负责镜像管理以及Pod和容器的真正运行(k8s不仅支持docker容器,也支持其他实现Container Runtime Interface接口的容器,所以使得k8s具有集成可扩展性)
除了核心组件,k8s还提供了一些插件:
-
kube-dns负责为整个集群提供DNS服务
-
Heapster提供资源监控
-
Dashboard提供GUI
-
Federation提供跨可用区的集群
-
Fluentd-elasticsearch提供集群日志采集、存储与查询
分层架构
如果按照k8s的整体功能来进行分层划分,可以大致划分为下图所示的分层架构
-
核心层:Kubernetes最核心的功能,对外提供API构建高层的应用,对内提供插件式应用执行环境(比如,容器运行,网络环境等)
-
应用层:部署(无状态应用、有状态应用、批处理任务、集群应用等)和路由(服务发现、DNS解析等)
-
管理层:系统度量(如基础设施、容器和网络的度量),自动化(如自动扩展、动态Provision等)以及策略管理(RBAC、Quota、PSP、NetworkPolicy等)
-
接口层:kubectl命令行工具、客户端SDK以及集群联邦
-
生态系统:在接口层之上的庞大容器集群管理调度的生态系统,可以划分为两个范畴
-
Kubernetes外部:日志、监控、配置管理、CI、CD、Workflow、FaaS、OTS应用、ChatOps等
-
Kubernetes内部:CRI、CNI、CVI、镜像仓库、Cloud Provider、集群自身的配置和管理等
-
组件详解
API Server
Kubernetes API Server的核心功能是提供Kubernetes各类 资源对象(如Pod、RC、Service等)的增、删、改、查及Watch等HTTP Rest接口,成为集群内各个功能模块之间数据交互和通信的中心枢纽, 是整个系统的数据总线和数据中心。除此之外,它还是集群管理的入口、资源配额控制的入口,并提供了完备的集群安全机制
最常用的kubectl命令其实内部就是访问api server提供的restful接口获取集群资源信息
Api Server的架构
如上图所示,Api Server主要分为三个层次
-
Api 层用于提供各种API接口
-
访问控制层,即鉴权和身份验证,资源验证
-
注册表层,Kubernetes把所有资源对象都保存在注册表 (Registry)中,针对注册表中的各种资源对象都定义了:资源对象的 类型、如何创建资源对象、如何转换资源的不同版本,以及如何将资源 编码和解码为JSON或ProtoBuf格式进行存储,for example: pod的元数据信息(这个元数据信息可以类比为数据库的表结构)
-
etcd数据库,用于持久化存储k8s资源对象的KV数据库
test环境中的api server
node2# ps -ef|grep api
root 21175 21144 2 Mar12 ? 03:20:35 kube-apiserver --advertise-address=10.65.8.43 --allow-privileged=true --apiserver-count=3 --authorization-mode=Node,RBAC --bind-address=0.0.0.0 --client-ca-file=/etc/kubernetes/pki/ca.crt --enable-admission-plugins=NodeRestriction --enable-bootstrap-token-auth=true --endpoint-reconciler-type=lease --etcd-cafile=/etc/ssl/etcd/ssl/ca.pem --etcd-certfile=/etc/ssl/etcd/ssl/node-node2.pem --etcd-keyfile=/etc/ssl/etcd/ssl/node-node2-key.pem --etcd-servers=https://10.65.8.42:2379,https://10.65.8.43:2379,https://10.65.8.44:2379,https://10.65.8.45:2379,https://10.65.8.176:2379 --insecure-port=0 --kubelet-client-certificate=/etc/kubernetes/pki/apiserver-kubelet-client.crt --kubelet-client-key=/etc/kubernetes/pki/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalDNS,InternalIP,Hostname,ExternalDNS,ExternalIP --proxy-client-cert-file=/etc/kubernetes/pki/front-proxy-client.crt --proxy-client-key-file=/etc/kubernetes/pki/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/etc/kubernetes/pki/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --runtime-config=admissionregistration.k8s.io/v1beta1 --secure-port=7443 --service-account-key-file=/etc/kubernetes/pki/sa.pub --service-cluster-ip-range=100.127.224.0/20 --service-node-port-range=30000-32767 --storage-backend=etcd3 --tls-cert-file=/etc/kubernetes/pki/apiserver.crt --tls-private-key-file=/etc/kubernetes/pki/apiserver.key --v=4
由上可知,每个master节点都有一个kube-apiserver进程
List-Watch异步消息机制
在K8s中etcd存储了各种数据和状态信息,而只有API Server能够直接与ETCD进行交互,也就是其他的组件获取需要的信息只能通过API Server来获取,并且其他组件通过监听API server来执行相应的操作,这种机制在k8s集群中叫List-Watch机制,流程图如下所示
由于k8s不希望除了api server的其他组件能够直接访问或者监听etcd,所以k8s自己复写了一套watch接口,其他组件来监听api server就能watch api server,并且还可以选择性地监听需要的信息,比如scheduler,kubelete需要的通知会有些是不同的
List-watch主要作用为:
-
1、组件启动时通过"List"接口,获取所需资源的全量的数据并且缓存到内存中
-
2、组件向apiserver而不是etcd发起watch请求,在组件启动时就进行订阅,告诉apiserver需要知道什么数据发生变化。Watch是一个典型的发布-订阅模式。
3、组件向apiserver发起的watch请求是可以带条件的,例如,scheduler想要watch的是所有未被调度的Pod,也就是满足Pod.destNode=""的Pod来进行调度操作;而kubelet只关心自己节点上的Pod列表。apiserver向etcd发起的watch是没有条件的,只能知道某个数据发生了变化或创建、删除,但不能过滤具体的值。也就是说对象数据的条件过滤必须在apiserver端而不是etcd端完成。
在kubectl命令上带上watch参数,可以看到背后的一系列流程
node2# kubectl get po -n demo --watch -v 7
I0320 11:07:42.774438 17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.775212 17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.779140 17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.790768 17010 loader.go:359] Config loaded from file /root/.kube/config
I0320 11:07:42.791435 17010 round_trippers.go:416] GET https://10.65.8.42:6443/api/v1/namespaces/demo/pods?limit=500
I0320 11:07:42.791462 17010 round_trippers.go:423] Request Headers:
I0320 11:07:42.791477 17010 round_trippers.go:426] Accept: application/json
I0320 11:07:42.791489 17010 round_trippers.go:426] User-Agent: kubectl/v1.13.5 (linux/amd64) kubernetes/2166946
I0320 11:07:42.803740 17010 round_trippers.go:441] Response Status: 200 OK in 12 milliseconds
I0320 11:07:42.804192 17010 round_trippers.go:416] GET https://10.65.8.42:6443/api/v1/namespaces/demo/pods?resourceVersion=328639400&watch=true
I0320 11:07:42.804208 17010 round_trippers.go:423] Request Headers:
I0320 11:07:42.804219 17010 round_trippers.go:426] Accept: application/json
I0320 11:07:42.804227 17010 round_trippers.go:426] User-Agent: kubectl/v1.13.5 (linux/amd64) kubernetes/2166946
I0320 11:07:42.805324 17010 round_trippers.go:441] Response Status: 200 OK in 1 milliseconds
打开另一个窗口,创建pod
node2# kubectl create -f pod.yaml
可以观察到刚才的watch接口立即监听到了pod的创建, 并且打印了pod创建的过程的信息
NAME READY STATUS RESTARTS AGE
demo-pod 0/1 Pending 0 0s
demo-pod 0/1 Pending 0 0s
demo-pod 0/1 ContainerCreating 0 0s
demo-pod 1/1 Running 0 12s
K8s 的informer模块封装了 list-watch的API, 其他具体调用方只需要指定资源,编写时间处理函数就行
如下图所示,informer首先通过通过List接口罗列所有的资源,然后再调用watch接口监听资源的变更时间,每次有变更事件时会将变更时间放到FIFO队列中,由另一个协程从队列中取出事件,并调用相应的注册函数处理
此外,还维护了一个缓存,因为每个组件处理变更时可能要查询资源信息,为了降低api server的负载,会从本地缓存中读取
以k8s master的pod.go为例:
先看NewFilteredPodInformer(kubernetes-master/staging/src/k8s.io/client-go/informers/core/v1/pod.go)方法创建一个informer
func NewFilteredPodInformer(client kubernetes.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
return cache.NewSharedIndexInformer(
&cache.ListWatch{
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Pods(namespace).List(context.TODO(), options)
},
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
if tweakListOptions != nil {
tweakListOptions(&options)
}
return client.CoreV1().Pods(namespace).Watch(context.TODO(), options)
},
},
&corev1.Pod{},
resyncPeriod,
indexers,
)
}
再看cache.NewSharedIndexInformer类所在的run方法func (s *sharedIndexInformer) Run(stopCh <-chan struct{})
func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{ //关键信息1
KnownObjects: s.indexer,
EmitDeltaTypeReplaced: true,
})
cfg := &Config{
Queue: fifo,
ListerWatcher: s.listerWatcher,
ObjectType: s.objectType,
FullResyncPeriod: s.resyncCheckPeriod,
RetryOnError: false,
ShouldResync: s.processor.shouldResync,
Process: s.HandleDeltas, //关键信息2
WatchErrorHandler: s.watchErrorHandler,
}
func() {
s.startedLock.Lock()
defer s.startedLock.Unlock()
s.controller = New(cfg)
s.controller.(*controller).clock = s.clock
s.started = true
}()
// Separate stop channel because Processor should be stopped strictly after controller
processorStopCh := make(chan struct{})
var wg wait.Group
defer wg.Wait() // Wait for Processor to stop
defer close(processorStopCh) // Tell Processor to stop
wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run)
wg.StartWithChannel(processorStopCh, s.processor.run) //关键信息3
defer func() {
s.startedLock.Lock()
defer s.startedLock.Unlock()
s.stopped = true // Don't want any new listeners
}()
s.controller.Run(stopCh) //关键信息4
}
上面代码中标记了4个关键信息
-
1、可以看到初始话了一个fifo队列,作为Config的参数,这个FIFO队列就是上面说到的用于存放监听到API Server回调消息的事件的队列
-
2、Config还有一个关键参数Process,赋值是s.HandleDeltas,最后通过Config生成了一个controller对象
HandleDeltas方法就是对于FIFO队列中每一个监听事件进行的处理,代码如下:
可以看到调用processor.distribute方法分发到相应的协程进行处理
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
s.blockDeltas.Lock()
defer s.blockDeltas.Unlock()
// from oldest to newest
for _, d := range obj.(Deltas) {
switch d.Type {
case Sync, Replaced, Added, Updated:
s.cacheMutationDetector.AddObject(d.Object)
if old, exists, err := s.indexer.Get(d.Object); err == nil && exists {
if err := s.indexer.Update(d.Object); err != nil {
return err
}
isSync := false
switch {
case d.Type == Sync:
// Sync events are only propagated to listeners that requested resync
isSync = true
case d.Type == Replaced:
if accessor, err := meta.Accessor(d.Object); err == nil {
if oldAccessor, err := meta.Accessor(old); err == nil {
// Replaced events that didn't change resourceVersion are treated as resync events
// and only propagated to listeners that requested resync
isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()
}
}
}
s.processor.distribute(updateNotification{oldObj: old, newObj: d.Object}, isSync)
} else {
if err := s.indexer.Add(d.Object); err != nil {
return err
}
s.processor.distribute(addNotification{newObj: d.Object}, false)
}
case Deleted:
if err := s.indexer.Delete(d.Object); err != nil {
return err
}
s.processor.distribute(deleteNotification{oldObj: d.Object}, false)
}
}
return nil
}
-
3、s.processor.run,方法就是通过channel接收上面步骤2分发过来的事件,进行相应的处理
func (p *processorListener) run() {
// this call blocks until the channel is closed. When a panic happens during the notification
// we will catch it, **the offending item will be skipped!**, and after a short delay (one second)
// the next notification will be attempted. This is usually better than the alternative of never
// delivering again.
stopCh := make(chan struct{})
wait.Until(func() {
for next := range p.nextCh {
switch notification := next.(type) {
case updateNotification:
p.handler.OnUpdate(notification.oldObj, notification.newObj)
case addNotification:
p.handler.OnAdd(notification.newObj)
case deleteNotification:
p.handler.OnDelete(notification.oldObj)
default:
utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))
}
}
// the only way to get here is if the p.nextCh is empty and closed
close(stopCh)
}, 1*time.Second, stopCh)
}
-
4、最后一步是controller的run方法,有两个作用,1是生成Reflector对应,并调用Reflector的Run方法,而Reflector中的Run方法中是开启ListWatch,即前面提到的先通过List接口获取所有需要的资源,再对所有资源进行Watch(监听)
另一个作用就是for循环从FIFO队列中取出监听事件,并调用步骤2中的HandleDeltas方法进行处理
func (c *controller) Run(stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
go func() {
<-stopCh
c.config.Queue.Close()
}()
r := NewReflector(
c.config.ListerWatcher,
c.config.ObjectType,
c.config.Queue,
c.config.FullResyncPeriod,
)
r.ShouldResync = c.config.ShouldResync
r.WatchListPageSize = c.config.WatchListPageSize
r.clock = c.clock
if c.config.WatchErrorHandler != nil {
r.watchErrorHandler = c.config.WatchErrorHandler
}
c.reflectorMutex.Lock()
c.reflector = r
c.reflectorMutex.Unlock()
var wg wait.Group
wg.StartWithChannel(stopCh, r.Run) //调用Reflector的Run方法开启ListWatch
wait.Until(c.processLoop, time.Second, stopCh) //循环从FIFO队列中获取监听回调时间
wg.Wait()
}
模块之间的通信
Kubernetes api server负责各个模块的通信
1、每个node上的kubelet进程会定时访问api-server的restful接口上报自身状态
2、kubelet进程也会通过List-Watch机制监听pod信息
3、kube-controller-manager中的Node Controller模块通过API Server提供 的Watch接口实时监控Node的信息
4、scheduler通过List-Watch机制监听到新建pod的信息之后,会选择合适的node列表,将pod绑定到目标节点上
Controller Manager
在k8s集群中有多种Controller,分别对特定的资源进行监控和做相应的调整,常见的有Replication Controller(项目中最常用的Deployment可以看做Replication Controller的最新版), Node Controller(监听Node节点的状态),总共可分为以下几大类的Controller
而Controller Manager正是对资源的核心管理者,也就是是master中的每个Controller保证了各类资源保持预期状态,而Controller Manager保证了各个Controller保持在预期状态
每个Controller的作用分别如下:
Replication Controller
Replication Controller的作用是管理Pod副本,因此具有重新调度,弹性伸缩,滚动更新等功能
目前普遍使用Deployment和Replica Set代替Replication Controller,Replica Set相对于Replication Controller而言,优势是支持多标签selector, Replica Set由Deployment来进行管理,Deployment控制的是Replica Set的对象
Node Controller
Node Controller负责管理Node节点。
每个Node节点有一个kubelet进程,进程在启动时会向API Server注册自身的节点信息,并且之后会定时上报,Api Server会将其存储在ETCD中,存储的这些信息包括节点健康状况,节点资源,节点地址信息,操作系统版本,docker版本,kubelete版本等信息
节点健康状况包含就绪、未就绪、Unknown三种
使用kubectl get nodes命令可以查看所有nodes节点信息
而Node Controller也通过监听API Server实时获取Node的相关信息,来实现管理和监控集群中的node节点
Node Controller核心工作过程如下:
Node Controller会在本地实时保存nodeStatusMap,存储各个状态信息,每次读取最新的Node信息会进行更新
ResourceQuota Controller
Resource Quota Controller为资源配额管理,支持三个层次的资源配额管理
-
容器级别,对container cpu,memory进行限制
-
Pod级别,对pod内所有container的可用资源进行限制
-
Namespace级别
对于容器和pod,k8s提供LimitRanger,而NameSpace级别,提供ResourceQuota,如下所示:
- apiVersion: v1
kind: ResourceQuota
metadata:
name: pods-medium
spec:
hard:
cpu: "10"
memory: 20Gi
pods: "10"
scopeSelector:
matchExpressions:
- operator : In
scopeName: PriorityClass
values: ["medium"]
创建ResourceQuata Contrller之后会保存到etcd中,当通过API Server创建资源时,API Server会先通过准入可控制器(可以理解为网关逻辑中的一个check项), 读取对应的资源统计信息,判断是否超出范围。
同时,ResourceQuata Controller也会定期去统计各类资源信息
Namespace Controller
namespace controller主要是用来当要删除namespace资源时,进行删除操作,并且阻止在该namespace下的资源创建
EndPoint Controller
Endpoint的作用如下图所示,Endpoint可以看做是一个service下的路由表,保存了service下pod的访达地址
kubectl get endpoints -n namespace命令可以查看endpoint信息
, endpoint对象被每个node节点上的kube-proxy使用,kube-proxy获取每个service的endpoint,实现Service的负载均衡
Endpoint Controller的功能通过监听Service,pod的创建或者更新,来及时更新和维护service对应的Endpoint对象的
Service Controller
service controller属于Kubernetes集群 与外部的云平台之间的一个接口控制器,暂不展开讨论
ServiceAccount Controller,Token Controller
k8s集群内部,SeriviceAccount Controller和Token Controller作用的是Service Account,Service Account是和User Account独立的两个账号
-
UserAccount, 针对管理员而言,比如我们使用kubeclt命令或者在网页上k8s dashboard上,这个账户的权限是全局性的
-
Service Account, 针对pod内的进程而言,有些情况下,pod内需要访问api server并进行一些操作,而这些操作默认的名为default的是没有的,那么可能需要新建Service Account代替default,在Deployment中通过serviceAccount字段来设置
而每个pod内部都会生成相应的token,crt等基本的鉴权文件用于和api server的交互和身份验证
node2# kubectl exec -it -n *******
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/ ca.crt namespace token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/ ca.crt namespace token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
..2021_02_25_12_37_32.328705902/ ..data/ ca.crt namespace token
root@bin$cd /run/secrets/kubernetes.io/serviceaccount/
root@serviceaccount$ll
total 4
drwxrwxrwt 3 root root 140 Feb 25 20:37 ./
drwxr-xr-x 3 root root 4096 Feb 25 20:59 ../
drwxr-xr-x 2 root root 100 Feb 25 20:37 ..2021_02_25_12_37_32.328705902/
lrwxrwxrwx 1 root root 31 Feb 25 20:37 ..data -> ..2021_02_25_12_37_32.328705902/
lrwxrwxrwx 1 root root 13 Feb 25 20:37 ca.crt -> ..data/ca.crt
lrwxrwxrwx 1 root root 16 Feb 25 20:37 namespace -> ..data/namespace
lrwxrwxrwx 1 root root 12 Feb 25 20:37 token -> ..data/token
root@serviceaccount$cat ..data/token
eyJhbGciOiJSUzI1NiIsImtpZCI6InhKc3MtZlhmS0pTSnFPUWN3WEVURXVNM3lrTnBHcXpUdk5Sd3Y1NXI4WE0ifQ.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJhcGMtdHJhbnNhY3Rpb24tdm4tdGVzdCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJkZWZhdWx0LXRva2VuLTRkamJ0Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9zZXJ2aWNlLWFjY291bnQubmFtZSI6ImRlZmF1bHQiLCJrdWJlcm5ldGVzLmlvL3NlcnZpY2VhY2NvdW50L3NlcnZpY2UtYWNjb3VudC51aWQiOiI0ODNiZjJkZC1iN2U3LTQ3NGUtODQ5Zi0zY2RiNTRmYzcwOGIiLCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6YXBjLXRyYW5zYWN0aW9uLXZuLXRlc3Q6ZGVmYXVsdCJ9.bOyVr3SufsO4VovSl7CE5oLACilSaWAPkqWEkS-56G9vK3fD8clwM5olBQ_1chdI9BQ5mzZk5CjhyWkwxGbaq26P2S5gBK-UOzOSmUAoYlSO6wYeYKBD_EUirjL6HI5CsIQtoHX2F5fKQTCXSmlVK9rYqtJzosLlsYqwgNDFigweocovA0eFCPloDr1xOIdAkvorA8PF04PyPE0VrPI_6TK4e0CzAvX3enTUpwQgmgp-ouk4naCS3k0UdSAxmgB4-KAxawGJjYn4uF-WLaLWakPrS-0MdwPjjweJB0IupHLtp0nVKmL_QKbhkHXgrl8dn7PjLZh9DXrClXNuE8IeBwroot@serviceaccount$
root@serviceaccount$cat ..data/ca.crt
-----BEGIN CERTIFICATE-----
MI--------略
-----END CERTIFICATE-----
root@serviceaccount$
Scheduler
Scheduler在整个系统调度中起承上启下的作用,与APi Server, Controller Manager三个组件是master节点的最重要的三个组件,
总体机制是监听待调度pod的创建,并选择合适的Node更新etcd信息,最后被对应Node节点的kubelet进程监听到,进行后一步的操作,具体流程如下
调度流程
scheduler调度分为两个子流程,一个是预选,一个是确定最优
预选流程
预选是筛选出符合要求的候选节点,因为并不是所有的节点都会满足条件,比如有的节点资源不足以创建新的容器,有的pod在配置文件中指定了一些条件(比如在ip为某个范围内的机器,或者指定了标签)
默认预选策略如下:
-
NoDiskConflict
待部署的pod和Node节点上的pod是否存在Volume挂载冲突
-
PodFitsResources
待部署的pod和节点上已有的所有pod的需求资源是否已经超过节点拥有的资源
-
PodSelectorMatches
判断待部署的pod,是否通过配置参数spec.nodeSelector制定标签,并且节点上是否包含该标签
可以通过命令:
kubectl label nodes <node-name> <label-key>=<label-value>
来设置node的标签,在pod中再用spec.nodeSelector来指定标签 -
PodFitsHost
判断待部署Pod的spec.nodeName域所指定的节点名称和备选节点的名称是否一致
-
PodFitsPorts
判断待部署Pod所用的端口列表中的端口是否在备选节点中已被占用
每个节点只有通过上面5个默认预选策略后才能进入下一步的最优策略
最优策略
最优策略有多种
https://kubernetes.io/docs/reference/scheduling/policies/
-
LeastRequestedPriority
从备选Node中选出资源消耗最小的节点
计算已运行的pod和备选pod的CPU总占用量和内存总占用量(占用量为requested), 再通过以下公式 来计算,
cpu((capacity – sum(requested)) * 10 / capacity) + memory((capacity – sum(requested)) * 10 / capacity) / 2,
节点的优先级就由节点空闲资源与节点总容量的比值来决定
-
SelectorSpreadPriority
为了更好的容灾,属于同一个service, replication controller的多个pod副本,计量调度到不同的节点上去
-
BalancedResourceAllocation
由节点的资源使用率来决定,CPU和内存的使用率越接近,权重越高
kubelet
kubelet运行在每个node节点上,用于处理master节点下发的任务,以及管理自身的pod和pod中的容器
节点管理
kubelet进程启动之后会向API server注册节点信息,并定时向API Server同步节点信息(默认是10s),API server会将信息存入etcd
Pod管理
Kubelete通过以下方式来获取要运行的pod清单
静态文件配置
在node节点上查看kubelet进程
node2# ps -ef|grep kubelet
root 1864 1 5 Jan07 ? 2-20:25:22 /usr/bin/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --kubeconfig=/etc/kubernetes/kubelet.conf --config=/var/lib/kubelet/config.yaml --cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=gcr.io/google-containers/pause:3.1
可以看到kubelet进程有几个启动时的参数,其中, --config就是配置了kubelet的一些参数,apc项目中,该文件是/var/lib/kubelet/config.yaml, 可以看到两个参数
staticPodPath: /etc/kubernetes/manifests --静态pod文件的目录
fileCheckFrequency: 20s --kubelet进程检查pod文件目录的间隔
查看master节点的静态pod文件目录如下,说明master节点kubelet进程启动时会默认启动这三个组件对应的pod
node2# ls
kube-apiserver.yaml kube-controller-manager.yaml kube-scheduler.yaml
在目录中新建一个pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: demo-pod
namespace: demo
labels:
app: demo
spec:
containers:
- name: demo
image: gcr.io/google-samples/kubernetes-bootcamp:v1
ports:
- containerPort: 8080
再查看pod,说明确实是主动检查了目录
node2# kubectl get po -n demo
NAME READY STATUS RESTARTS AGE
demo-pod-node2 0/1 ContainerCreating 0 7s
NAME READY STATUS RESTARTS AGE
demo-pod-node2 0/1 ContainerCreating 0 7s
HTTP URL
跟静态文件配置类似,不过是通过url来获取,通过配置--manifest-url=<manifest-url>参数来获取待部署的pod列表
静态文件配置和url配置的pod都属于静态pod(static pod),static pod有kubelet进程直接管理,并且kubelet进程会向API Server创建一个对应的mirror pod, 这样就可以通过api server来进行查看
API Server
一般情况下创建的pod都是通过API Server方式创建的(使用kubectl命令或者由scheduler调度)
kubelet进程通过API server client使用Watch-List的方式监听“/registry/nodes/$”当前节点的名称
和“/registry/pods”目录,将获取的信息同步到本地缓存中。
使用如下命令查看etcd信息,可以看到etcd存储的pod列表
node2# etcdctl --cert=/etc/ssl/etcd/ssl/ca.pem --key=/etc/ssl/etcd/ssl/ca-key.pem get / --prefix | grep -a registry/pods
可以再查看具体pod信息
etcdctl --cert=/etc/ssl/etcd/ssl/ca.pem --key=/etc/ssl/etcd/ssl/ca-key.pem get /registry/pods/${namespace}/${pod}
每个node节点上kubelet,都会监听所有针对pod的操作,一旦有新的pod绑定到node上,就会进行创建,而有本地pod发生更新或者删除操作,kubelet会做出对应的操作
https://juejin.cn/post/6856431987302465550
容器健康检查
Pod通过两类探针来检查容器的健康状态,由kubelet进程向容器发送探测
-
LivenessProbe探针,用于判断容器是否健康并反馈给kubelet
-
ReadinessProbe探针,用于判断容器是否启动完成,且准备接收请求。 如果ReadinessProbe探针检测到容器启动失败,则Pod的状态将被修改,当就绪时就能开始接收流量
apc项目中探针如下
readinessProbe:
tcpSocket:
port: $POD_PORT
initialDelaySeconds: 5
periodSeconds: 10
livenessProbe:
tcpSocket:
port: $POD_PORT
initialDelaySeconds: 15
periodSeconds: 20
探针处理方式有三种类型:
1、ExecAction:在容器内部执行一个命令,如果该命令的退出 状态码为0,则表明容器健康。
2、TCPSocketAction:通过容器的IP地址和端口号执行TCP检 查,如果端口能被访问,则表明容器健康。
3、HTTPGetAction:通过容器的IP地址和端口号及路径调用 HTTP Get方法,如果响应的状态码大于等于200且小于等于400,则认为 容器状态健康。
资源监控
k8s集群中有三类指标需要进行关注和保证稳定运行
-
kubernetes基础组件。也就是组成kubernetes的应用进程,如api-server、controller-manager、scheduler、kubelet等
-
node节点。也就是组成kubernetes的机器
-
Pod/容器。也就是业务进程的运行环境
kube-proxy
kube-proxy的作用是Service背后的代理兼负载均衡器,核心功能是将某个Service的访问请求转发到后端的pod实例上去
它负责建立和删除包括更新IPVS规则、通知API SERVER自己的更新,或者从API SERVER哪里获取其他kube-proxy的IPVS规则变化来更新自己的
kube-proxy的总体流程如下:
pod或者外部client访问Service对应的Cluster IP, 再由kube-proxy根据路由规则转发到不同的pod上
kube-proxy路由的具体方式
https://kubernetes.io/docs/concepts/services-networking/service/
https://arthurchiao.art/blog/cracking-k8s-node-proxy/#4-implementation-1-proxy-via-userspace-socket
kube-proxy转发
最老的k8s版本中kube-proxy转发如下图所示,kube-proxy监听service和pod信息,每个Service创建时,kube-proxy进程更新iptables(防火墙),将service的cluster IP代理到kube-proxy,当在某个node上请求service上,会转发到kube-proxy,再由kube-proxy转发到service背后的pod
iptables代理模式
上面的kube-proxy转发需要先从内核态切换到用户态,性能不好,目前的方法是kube-proxy只用来更新iptables规则,而iptables规则直接由cluster ip转发到pod
IPVS模式
IPVS模式跟iptables类似,但是IPVS由于其设计和定位,它的存储和查找路由规则更高,专门用于高性能负载均衡,比iptables性能更高