K8S Proxy 源码分析

前言 在分析kube-proxy之前,我们需要先知道k8s中另外一个概念service。 service 是一组具有相同 label pod 集合的抽象,集群内外的各个服务可以通过 service 进行互相访问。 当创建一个 service 对象时也会对应创建一个 endpoint 对象,endpoint 存放的是pod服务的ip地址,service 只是将多个pod进行关联起来。 当用户通过service访问后端pod服务时,实际的路由转发都是由 kubernetes 中的 kube-proxy 组件来实现,因此,service 必须结合 kube-proxy 使用。 kube-proxy 组件可以运行在 kubernetes 集群中的每一个节点上也可以只运行在单独的几个节点上,其会根据 service 和 endpoints 的变动来刷新节点上 iptables 或者 ipvs 中保存的路由规则。 service 工作原理 endpoint controller 负责监听 service 和对应 pod 的变化,更新对应 service 的 endpoints 对象。当用户创建 service 后 endpoints controller 会监听 pod 的状态,当 pod 处于 running 且准备就绪时,endpoints controller 会将 pod ip 记录到 endpoints 对象中。 kube-proxy 会监听 service 和 endpoints 的变化并调用其代理模块在主机上刷新路由规则。 总结就是,endpoint controller负责更新service ip和其对应后端服务endpoint ip的关系。而kube-proxy则负责更新各个节点的iptables或者ipvs使得流量能够被正常转发。 service 暴露服务方式 ClusterIp ClusterIP 类型的 service 是 kubernetes 集群默认的服务暴露方式,它只能用于集群内部通信,可以被各 pod 访问,其访问方式为: pod ---> ClusterIP:ServicePort --> (iptables)DNAT --> PodIP:containePort NodePort client ---> NodeIP:NodePort ---> ClusterIP:ServicePort ---> (iptables)DNAT ---> PodIP:containePort LoadBalancer Ingress service 数据包流动分析 实验背景就是,我们启动三个nginx pod,pod3和pod2在node2中,而pod1在node1中。网络设备结构图如下: pod2和pod3在同一个node中,为了简洁暂未画出。
Read more...

Kubelet 创建pod流程分析

前言 上一节我们分析了kubelet启动的基本流程,这一节我们分析一下当有新的pod分配到当前节点时,kubelet如何创建一个pod syncLoop 上一节最后我们分析到kl.syncLoop(updates, kl),是kubelet里面一个主循环逻辑,看一下它的具体实现: 它从不同的管道(文件、URL 和 apiserver)监听变化,并把它们汇聚起来。 当有新的变化发生时,它会调用对应的处理函数,保证 pod 处于期望的状态。 如果 pod 没有变化,它也会定期保证所有的容器和最新的期望状态保持一致。这个方法是 for 循环,不会退出。 func (kl *Kubelet) syncLoop(updates <-chan kubetypes.PodUpdate, handler SyncHandler) { klog.Info("Starting kubelet main sync loop.") syncTicker := time.NewTicker(time.Second) defer syncTicker.Stop() housekeepingTicker := time.NewTicker(housekeepingPeriod) defer housekeepingTicker.Stop() plegCh := kl.pleg.Watch() const ( base = 100 * time.Millisecond max = 5 * time.Second factor = 2 ) duration := base for { if err := kl.runtimeState.runtimeErrors(); err != nil { klog.Infof("skipping pod synchronization - %v", err) // exponential backoff time.Sleep(duration) duration = time.Duration(math.Min(float64(max), factor*float64(duration))) continue } // reset backoff if we have a success duration = base kl.
Read more...

Kube Scheduler 优选策略源码分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 前言 预选阶段完成后,进入优选阶段,将需要调度的Pod列表和Node列表传入各种优选算法进行打分,最终整合成结果集HostPriorityList //pkg/scheduler/core/generic_scheduler.go:189 // Schedule tries to schedule the given pod to one of the nodes in the node list. // If it succeeds, it will return the name of the node. // If it fails, it will return a FitError error with reasons. func (g *genericScheduler) Schedule(pod *v1.Pod, pluginContext *framework.PluginContext) (result ScheduleResult, err error) { ... trace.Step("Basic checks done") startPredicateEvalTime := time.Now() filteredNodes, failedPredicateMap, filteredNodesStatuses, err := g.findNodesThatFit(pluginContext, pod) if err != nil { return result, err } // Run "postfilter" plugins. postfilterStatus := g.framework.RunPostFilterPlugins(pluginContext, pod, filteredNodes, filteredNodesStatuses) if !
Read more...

Kubelet 架构分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 前言 kubelet是k8s集群中节点的代理人,通过watch api server,来处理发送给本节点的任务,看一下官方对kubelet的描述: The kubelet is the primary "node agent" that runs on each node. It can register the node with the apiserver using one of: the hostname; a flag to override the hostname; or specific logic for a cloud provider. The kubelet works in terms of a PodSpec. A PodSpec is a YAML or JSON object that describes a pod. The kubelet takes a set of PodSpecs that are provided through various mechanisms (primarily through the apiserver) and ensures that the containers described in those PodSpecs are running and healthy.
Read more...

K8S Scheduler 源码分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 前言 kube-scheduler 是master节点中重要的组件之一,它同watch接口监听api server中需要调度的pod,然后通过其预选,优选算法来确定pod需要被调度到哪个节点上去。 看一下官方对scheduler 调度流程的描述:The Kubernetes Scheduler 主要步骤如下: 首先通过预选阶段过滤掉不合适的节点,例如根据podSpec中对系统资源的需求,会计算出所有节点剩余的资源是否可以满足pod需求,如果不满足则该节点会被抛弃。 其次通过预选阶段的节点,会再次进行优选。比如在满足pod资源需求的基础上,会优选出当前负载最小节点,保证每个集群节点资源被均衡使用。 最后选择打分最高的节点作为目标节点,如果得分相同则会随机选择一个,进行调度。 For given pod: +---------------------------------------------+ | Schedulable nodes: | | | | +--------+ +--------+ +--------+ | | | node 1 | | node 2 | | node 3 | | | +--------+ +--------+ +--------+ | | | +-------------------+-------------------------+ | | v +-------------------+-------------------------+ Pred. filters: node 3 doesn't have enough resource +-------------------+-------------------------+ | | v +-------------------+-------------------------+ | remaining nodes: | | +--------+ +--------+ | | | node 1 | | node 2 | | | +--------+ +--------+ | | | +-------------------+-------------------------+ | | v +-------------------+-------------------------+ Priority function: node 1: p=2 node 2: p=5 +-------------------+-------------------------+ | | v select max{node priority} = node 2 入口 scheduler使用cobra创建命令行客户端,进入NewSchedulerCommand
Read more...

Replica Set Controller 源码分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 前言 前面文章已经总结到,deployment controller通过replicaset来控制pod的创建和删除,进而实现更高级操作例如,滚动更新,回滚等。本文继续从源码角度 分析replicaset controller如何控制pod的创建和删除。 在分析源码前先考虑一下 replicaset 的使用场景,在平时的操作中其实我们并不会直接操作 replicaset,replicaset 也仅有几个简单的操作,创建、删除、更新等。 源码分析 入口 我们在上一篇文章中也说道controller manager是所有k8s 资源controller的管控中心,包含的主要逻辑就是高可用选择leader,然后 遍历所有的controllers,对每一个controller进行初始化并启动,也就是说只要集群启动,所有的controller也会都启动起来,开始list-watch api server,来准备创建资源。 cmd/kube-controller-manager/app/controllermanager.go:386 func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc { controllers := map[string]InitFunc{} controllers["endpoint"] = startEndpointController controllers["endpointslice"] = startEndpointSliceController ... controllers["deployment"] = startDeploymentController controllers["replicaset"] = startReplicaSetController } 接下来进入startReplicaSetController, 发现通过NewReplicaSetController初始化了ReplicaSetController,并并调用run方法启动controller。 cmd/kube-controller-manager/app/apps.go:69 func startReplicaSetController(ctx ControllerContext) (http.Handler, bool, error) { if !ctx.AvailableResources[schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "replicasets"}] { return nil, false, nil } go replicaset.NewReplicaSetController( ctx.InformerFactory.Apps().V1().ReplicaSets(), ctx.InformerFactory.Core().V1().Pods(), ctx.ClientBuilder.ClientOrDie("replicaset-controller"), replicaset.BurstReplicas, ).Run(int(ctx.ComponentConfig.ReplicaSetController.ConcurrentRSSyncs), ctx.Stop) return nil, true, nil } 我们先分析NewReplicaSetController看一下初始化ReplicaSetController的具体步骤:发现他会监听rs的add,update,delete操作,和pod的add,update,delete操作。 当这两种资源发送变化时,将key(name+namespace)添加到queue队列中去。 //pkg/controller/replicaset/replica_set.go:126 func NewBaseController(rsInformer appsinformers.ReplicaSetInformer, podInformer coreinformers.PodInformer, kubeClient clientset.Interface, burstReplicas int, gvk schema.GroupVersionKind, metricOwnerName, queueName string, podControl controller.
Read more...

K8S Controller Manager 源码分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 前言 Controller Manager内部包含Replication Controller、Node Controller、 ResourceQuota Controller、Namespace Controller、ServiceAccount Controller、Token Controller、Service Controller及Endpoint Controller这8种Controller, 每个Controller,它们通过API Server提供的(List-Watch)接口实时监控集群中特定资源的状态变化,当发生各种故障 导致某资源对象的状态发生变化时,Controller会尝试将其状态调整为期望的状态。 代码总体解析 入口 # cmd/kube-controller-manager/controller-manager.go func main() { rand.Seed(time.Now().UnixNano()) command := app.NewControllerManagerCommand() logs.InitLogs() defer logs.FlushLogs() if err := command.Execute(); err != nil { os.Exit(1) } } 进入NewControllerManagerCommand 函数,可以看到新建ControllerManager需要两步 启动参数配置,及Run函数,进入Run函数。 func NewControllerManagerCommand() *cobra.Command { s, err := options.NewKubeControllerManagerOptions() if err != nil { klog.Fatalf("unable to initialize command options: %v", err) } cmd := &cobra.Command{ Use: "kube-controller-manager", Run: func(cmd *cobra.Command, args []string) { verflag.PrintAndExitIfRequested() utilflag.PrintFlags(cmd.Flags()) c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List()) if err != nil { fmt.Fprintf(os.Stderr, "%v\n", err) os.
Read more...

K8S Api Server 源码分析

版本环境 kubernetes版本:kubernetes:v1.16.0 go环境:go version go1.13.4 darwin/amd64 Api Server架构 kubernetes api server 提供了增删改查,list,watch各类资源的接口,对请求进行验证,鉴权及admission controller, 请求成功后将结果更新到后端存储etcd(也可以是其他存储)中。 api server 架构从上到下可以分为以下几层: 1.Api层,主要包含三种api core api: 主要在/api/v1下 分组api: 其path为/apis/$NAME/$VERSION 暴露系统状态的api: 如/metrics,/healthz,/logs,/openapi等 $ curl -k https://127.0.0.1:56521 { "paths": [ "/api", "/api/v1", "/apis", "/apis/", "/apis/admissionregistration.k8s.io", "/apis/admissionregistration.k8s.io/v1beta1", ... "/healthz", "/healthz/autoregister-completion", "/healthz/etcd", ... "/logs", "/metrics", "/openapi/v2", "/swagger-2.0.0.json", "/swaggerapi", "/version" 2.访问控制层 当用户访问api接口时,访问控制层将会做如下校验: 对用户进行身份校验(authentication)–> 校验用户对k8s资源对象访问权限(authorization) –> 根据配置的资源访问许可逻辑(admission control),判断是否访问。 下面分别对这三种校验方式进行说明: 2.1 Authentication 身份校验 k8s提供了三种客户端身份校验方式:https双向证书认证,http token方式认证, http base 认证也就是用户名密码方式 a. 基于CA根证书签名的双向数字证书认证 client和server向CA机构申请证书 client->server,server下发服务端证书,client利用证书验证server是否合法 client发送客户端证书给server,server利用证书校验client是否合法 client和server利用随机密钥加密消息,然后通信 apiserver启动的时候通过–client-ca-file=XXXX配置签发client证书的CA,当client发送证书过来时,apiserver使用CA验证,如果证书合法,则可进行通信。 这种方式的优点是安全,缺点是无法撤销用户证书,创建集群就绑定了证书。 $ k get pods kube-apiserver-kind-control-plane -n kube-system -o yaml - command: - kube-apiserver - --authorization-mode=Node,RBAC - --advertise-address=172.17.0.2 - --allow-privileged=true - --client-ca-file=/etc/kubernetes/pki/ca.crt b.
Read more...

CoreDNS生产案:pod出现dns解析大量失败的问题

问题 收到阿里云K8S集群监控告警: CoreDNS 5分钟内NXDOMAIN响应百分比大于50% k8s.coredns.response[aliyun,172.22.82.25:9153,NXDOMAIN] 是coredns的172.22.82.25的这个pod出现dns解析大量失败的问题。(解析成功的日志是NOERROR,解析失败是:NXDMAIN) 分析 首先是查看这个coredns pod的日志: [root@iZbp16er8wkobo2a165ekzZ ~]# kubectl get pods -n kube-system -o wide | grep coredns |grep 172.22.82.25 coredns-57dc86754b-9schh 1/1 Running 0 17d 172.22.82.25 cn-hangzhou.xxx.xxx.xxx.xxx <none>[root@iZbp16er8wkobo2a165ekzZ ~]# kubectl logs -n kube-system coredns-57dc86754b-9schh | tail -10 2020-03-05T13:06:29.745Z [INFO] 172.22.0.66:54106 - 24311 "A IN metrics.cn-hangzhou.aliyuncs.com. udp 50 false 512" NOERROR qr,rd,ra 190 0.00003395s 2020-03-05T13:06:29.745Z [INFO] 172.22.0.66:56730 - 20278 "AAAA IN metrics.cn-hangzhou.aliyuncs.com. udp 50 false 512" NOERROR qr,rd,ra 232 0.000072002s 2020-03-05T13:06:29.748Z [INFO] 172.22.0.66:41047 - 53211 "AAAA IN metrics.cn-hangzhou.aliyuncs.com.kube-system.svc.cluster.local. udp 80 false 512" NXDOMAIN qr,rd,ra 173 0.00004196s 2020-03-05T13:06:29.748Z [INFO] 172.22.0.66:40581 - 40714 "A IN metrics.cn-hangzhou.aliyuncs.com.kube-system.svc.cluster.local. udp 80 false 512" NXDOMAIN qr,rd,ra 173 0.
Read more...

Kubernetes中leaderelection实现组件高可用

在Kubernetes中,通常kube-schduler和kube-controller-manager都是多副本进行部署的来保证高可用,而真正在工作的实例其实只有一个。 这里就利用到 leaderelection 的选主机制,保证leader是处于工作状态,并且在leader挂掉之后,从其他节点选取新的leader保证组件正常工作。今天就来看看这个包的使用以及它内部是如何实现的。 基本使用 以下是一个简单使用的例子,编译完成之后同时启动多个进程,但是只有一个进程在工作,当把leader进程kill掉之后,会重新选举出一个leader进行工作,即执行其中的 run 方法: /* 例子来源于client-go中的example包中, */ package main import ( "context" "flag" "os" "os/signal" "syscall" "time" "github.com/google/uuid" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/leaderelection" "k8s.io/client-go/tools/leaderelection/resourcelock" "k8s.io/klog" ) func buildConfig(kubeconfig string) (*rest.Config, error) { if kubeconfig != "" { cfg, err := clientcmd.BuildConfigFromFlags("", kubeconfig) if err != nil { returnnil, err } return cfg, nil } cfg, err := rest.InClusterConfig() if err != nil { returnnil, err } return cfg, nil } func main() { klog.InitFlags(nil) var kubeconfig string var leaseLockName string var leaseLockNamespace string var id string flag.StringVar(&kubeconfig, "kubeconfig", "", "absolute path to the kubeconfig file") flag.
Read more...