K8s代码贡献浙江大学排名第六

全球企业Kubernetes开源社区代码贡献排名:浙江大学SEL实验室排名第六。请看来自Stackalytics|Kubernetes community contribution官方网站截图。 1   从全球企业的贡献排名来看,排名状况基本变化不大,仍然是Google独占鳌头。令人欣喜的是,浙江大学SEL实验室排名又上升了一位,代码贡献量近24万行,总排名超越CoreOS达到世界第六位。这无疑是振奋人心的事情,这意味着越来越多的年轻学术人员投入Kubernetes开源社区建设,这将近一步提高中国在容器云社区的地位。   Kubernetes是当前容器集群编排3大开源方案中社区活跃度最高的。虽然现在正值入冬,但仍然挡不住 Kubernetes掀起全球关注的热潮。Kubernetes体现了Google对海量容器集群管理的经验和精华,这让Kubernetes在容器云计算框架横行天下的年代游刃有余,已经逐步成为了容器云框架中的佼佼者。从Kubernetes开放、自由、持续发展生态圈兼容的态度能看出,Kubernetes正变得越来越自信。   下图为Kubernetes模块代码贡献分析图: 2

从图中可知:Kubernetes运维和生态圈组件模块代码贡献直追核心模块kubernetes,排名2-4位的模块分别是Kubernetes生态圈、操作和界面。Kubernetes使用者越来越关心容器云框架落地而非仅仅将其作为空中楼阁远观而止。  Kubernetes在国内发展已经超过了3年的时间,但是遗憾的一点,尽管我们拥有世界上最多的开发人员,但是我们常常对社区没有话语权,国内的用户的需求无法对社区上游形 成影响,导致很多本地化定制的需求无法真正的在社区版本代码得到体现。所以如何让中国的声音出现在社区,是我们所有容器云计算人需要思考的问题。更希望越来越多的国内传统IT巨头能够意识到这个问题,投身于开源的事业中。

《Docker容器与容器云》第2版推荐

自Docker容器与容器云第1版出版以来,销量达到10000多本,得到了广大技术人员的认可,并且翻译成繁体,进入台湾市场。本书对Docker和Kubernetes的源码解析深入细致,是国内Docker界的良心之作。

经过作者们多年的实践经验积累及近一年的精心准备,浙江大学SEL实验室出版的《Docker容器与容器云》第2版,终于与我们见面了。

本书根据Docker 1.10版和Kubernetes 1.2版对第1版进行了全面更新,从实践者的角度出发,以Docker和Kubernetes为重点,沿着“基本用法介绍”到“核心原理解读”到“高级实践技巧”的思路,一本书讲透当前主流的容器和容器云技术,有助于读者在实际场景中利用Docker容器和容器云解决问题并启发新的思考。全书包括两部分,第一部分深入解读Docker容器技术,包括Docker架构与设计、核心源码解读和高级实践技巧;第二部分归纳和比较了三类基于Docker的主流容器云项目,包括专注Docker容器编排与部署的容器云、专注应用支撑的容器云以及一切皆容器的Kubernetes,进而详细解读了Kubernetes核心源码的设计与实现,最后介绍了几种典型场景下的Kubernetes最佳实践。

自本书第1版出版以来,容器生态圈已经发生了翻天覆地的变化。新的开源项目层出不穷,各个开源项目都在快速迭代演进。Docker已经从本书第1版里的1.6.2发展为当前的1.10。Kubernetes也从本书第1版里的0.16发展到了现在的1.2,并且在1.0.1版本时宣布其已经正式进入可投入生产环境(production ready)的状态。 

第3章是本书第一部分的重点。Docker 1.10版相对于本书第1版中的1.6.2版,主要的更新包括如下几个方面:

1、Docker在架构方面不断将自身解耦,逐步发展成容器运行时(runtime)、镜像构建(builder)、镜像分发(distribution)、网络(networking)、数据卷(volume)等独立的功能组件,提供daemon来管理,并通过Engine暴露一组标准的API来操作这些组件(详见本书3.2节);

2、将网络和数据卷提升为“一等公民”,提供了独立子命令进行操作,网络和数据卷具备独立的生命周期,不再依赖容器的生命周期(详见本书3.7节、3.8节);

3、网络实现方面,Docker将网络相关的实现解耦为独立的组件libnetwork,抽象出一个通用的容器网络模型(CNM),功能上也终于原生支持了跨主机通信(详见本书3.8节);

4、在扩展性方面,在1.7.0版本后就开始支持网络、volume和存储驱动(仍处于实验阶段)的插件化,开发者可以通过实现Docker提供的插件标准来定制自己的插件(详见本书3.6节、3.7节、3.8节);

5、在Docker安全方面,Docker支持了user namespace和seccomp来提高容器运行时的安全,在全新的镜像分发组件中引入可信赖的分发和基于内容存储的机制,从而提高镜像的安全性(详见本书3.5节、3.6节、3.9节)。

需要特别指出的一点是,随着容器如火如荼的发展,为了推动容器生态的健康发展,促进生态系统内各组织间的协同合作,容器的标准化也显得越来越重要。Linux基金会于2015年6月成立OCI(Open Container Initiative)组织,并针对容器格式和运行时制定了一个开放的工业化标准,即OCI标准。Docker公司率先贡献出满足OCI标准的容器运行时runC,HyperHQ公司也开源了自己的OCI容器运行时runV,相信业界会有越来越多的公司加入这个标准化浪潮中。Docker公司虽然没有在Docker 1.10版本中直接使用runC作为容器的运行时,但是已经将“修改Docker engine来直接调用runC的二进制文件为Docker提供容器引擎”写入到了1.10版本的roadmap中。本书在3.4.3节中对runC的构建和使用进行了介绍。   第8章是本书第二部分的重点。由于Kubernetes的代码始终处于积极更新之中,自本书第1版截稿以来,Kubernetes又相继发布了0.17、0.18、0.19、0.20、0.21、1.0、1.1与1.2等几个版本。主要的更新包括如下几个方面:

1、大大丰富了支撑的应用运行场景。从全面重构的long-running service的replicaSet,到呼声渐高的支持batch job的Job、可类比为守护进程的DaemonSet、负责进行应用更新的Deployment、具备自动扩展能力的HPA(Horizontal Pod Autoscaler),乃至于有状态服务的petSet,都已经或者即将涵盖在Kubernetes的支撑场景中(详见本书8.2节)。

2、加强各个组件的功能扩展或者性能调优。apiserver和controller manager为应对全新的resource和API有显著的扩展;scheduler也在丰富调度策略和多调度器协同调度上有积极的动作;kubelet在性能上也有长足的进步,使得目前单个节点上支持的pod从原来的30个增长到了110个,集群工作节点的规模也从100个跃升为1000个;多为人诟病的kube-proxy如今也鸟枪换炮,默认升级为iptables模式,在吞吐量上也更为乐观;在可以预期的未来,rescheduler将成为Kubernetes家庭中的新成员,使得重调度成为可能(详见本书8.3节);

3、兼容更多的容器后端模型、网络及存储方案。从Docker到rkt,Kubernetes展示了对容器后端开放姿态,同时它还准备以C/S模式实现对其他容器的支撑。在网络方面,Kubernetes引入了网络插件,其中最为瞩目的当属CNI;存储上的解决方案更是层出不穷,flocker、Cinder、CephFS不一而足,还增加了许多特殊用途的volume,如secret、configmap等(详见本书8.4节、8.5节);

4、增加了OpenID、Keystone等认证机制、Webhook等授权机制,以及更为丰富的多维资源管理机制admissioncontroller(详见本书8.6节);

5、另外,作为Kubernetes社区的积极参与者,我们还专门增加了8.8节,讨论当前社区正在酝酿中的一些新特性,如Ubernetes、petSet、rescheduler。我们还讨论了Kubernetes性能优化,以及Kubernetes与OCI的关系等话题。 

除了全面更新这两个重点章节之外,我们还在第1章中更新了Docker近期的“大事记”并重新整理了容器生态圈,加入了许多重要的容器云技术开源项目,以及OCI、CNCF等国际标准化组织;在第2章中,我们将Docker命令行工具的基础用法更新到了Docker 1.10版;在第4章中完善了对时下火热的“容器化思维”和“微服务”的讨论;在第6章中更新了对Docker“三剑客”——Compose、Swarm和Machine的讨论;在附录中以Docker 1.10版为标准更新了附录A的Docker安装指南,以Kubernetes 1.2为标准,更新了附录F中Kubernetes的安装指南。

如果你是初级程序员,本书可以帮助你熟悉Docker与kubernetes的基本使用;如果你正在IT领域进行高级进阶修炼,那本书也可以与你一起探索Docker与kubernetes的工作原理。无论是架构师、开发者、运维人员,还是对Docker比较好奇的读者,本书都是一本不可多得的带你从入门向高级进阶的精品书,值得大家选择!

docker2

 

最后,摘录一些读者的推荐如下——

 —— 许式伟,七牛云存储CEO : “虽然在此之前已经有了由Docker团队出的第一本Docker书,但是这是国内第一本深入解读Docker与Kubernetes原理的原创图书,这一点意义重大。本书比较完整地介绍了Docker与Kubernetes的工作原理和生态,非常有借鉴意义。” 

——肖德时,数人科技CTO: “Docker容器技术已经在国内如火如荼地流行起来,浙江大学SEL实验室目前是国内掌握Docker技术最熟练的技术团队之一,他们在国内Docker技术界一直产生着重要影响。这次他们把Docker的实战经验汇编成书,可以帮助更多的Docker爱好者学习到一手的实战经验。”

——程显峰,火币网CTO : “本书非常细致地讲解了Docker技术的来龙去脉和技术细节,更为难得是还加入了Docker生态当中的其他技术。Docker这项技术本身就是将多种思想和技术融合的产物,从生态的视角去解读技术的来龙去脉将极大地促进读者对云计算和容器技术的重新思考。”

—— 刘俊,百度运维部高级架构师,两次百度最高奖获得者 : “本书宏观上描绘了容器和容器云技术发展的浪潮和生态系统,微观上以Docker和Kubernetes为典型进行了深度分析。无论是Docker技术爱好者,还是系统架构师、云端开发者、系统管理和运维人员,都能在本书中找到适合自己阅读的要点。浙江大学SEL实验室云计算团队是一支非常优秀的云计算研究团队,很多85后、90后人才活跃在顶级社区前沿,感谢他们能将多年的知识和智慧积累分享出来!”

——郝林,微影时代架构师,《Go并发编程实战》作者 : “本书是浙江大学SEL实验室云计算团队多年深耕Docker及背后的容器技术的结晶。最大的特点就是深入,并且有各种实用案例和细致讲解。另外,这本书在怎样真正地把Docker及周边产品落地以构建灵活多变的云平台方面也进行了生动的阐释。”   ——网友 Monster-Z: “自本书第一版出版以来,Docker社区又经过了如火如荼的发展,特别是网络部分的实现已经发生了翻天覆地的变化,而本书在第一版的基础之上及时地对网络部分的内容进行了更新。本书对于docker网络最新内容深入透彻的分析,让我们这些急于想要对Docker网络部分实现原理有所了解的开发人员如醍醐灌顶。相信对Docker技术感兴趣的读者在读完本书之后会与我有相同的感受。”

——网友 XiaoYu: “之前对于浙江大学的SEL实验室早有耳闻,也深知他们在云计算领域有着深厚的积淀。这次对他们著作的第二版进行了深入的研读,可以看得出,在保持了第一版架构的同时,对各个章节的内容又进行了大量的扩充,紧紧跟随着Docker以及k8s社区的发展步伐。相信不论是对于初涉容器领域的新人,还是希望深入理解Docker容器实现原理的开发人员,这都是一本不可多得的好书。” 

——网友 Mongo: “在学习了本书的第一版之后,就一直期待着本书第二版的出版。于是当得到第二版出版的消息之后,就迫不及待入手了一本。捧读之后,发现在内容上与第一版有了较大的变化,这应该与Docker本身快速的发展有关,当然也从侧面反应了容器技术在时下的热度。相信对于像我这样Docker技术的一线使用者来说,本书固有的深度以及第二版的时效性都将让我们对Docker的使用和理解都更上一层楼。”

——网友 YaoDD: “之前在学习k8s的过程中,一直苦于没有深入到代码层面的书籍可供参考。国内出版的大多数图书更多的是对官方文档的”翻译”,而并没有进一步深入的解读。直到发现了浙江大学SEL实验室出版的《Docker容器与容器云》一书,其中对k8s的各个组件做到了深入源码级的原理分析,让人直呼过瘾。所以在该书第二版出版的第一时间就购买并阅读了,发现在保持第一版分析深度的同时,又加入了许多社区最新的研发成果。如果你想对k8s有着更加深入的理解,那么本书将是你的不二选择。”    ——网友 DockerLover: “作为一名Kubernetes企业级应用的一线实践者,早就对浙大的SEL实验室慕名已久。该实验室的研究人员为Kubernetes社区共享了大量的代码,数量上堪比华为等巨头企业,尤其是其中的张磊博士,更是Kubernetes的member,技术实力可见一斑。因此本书不论是深度广度,还是对于学习容器技术的指导意义都是毋庸置疑的。” 

——网友 GaryRong: “自Docker出现以来,一股容器热潮已经席卷了整个互联网,如火如荼,方兴未艾。而浙大SEL实验室在云计算和PaaS领域深耕多年,作为docker技术在国内第一批的学习者和实践者,积累了丰富的经验。而出版的《Docker 容器与容器云》从第一版到第二版,对低到底层容器技术的实现,高到整个容器生态圈的发展都有着独到而深刻的理解。相信本书一定不会让你失望。 ” 

kubernetes node components – kubelet

kubelet作为k8s集群node上的重要组件,一直饱受关注。下面请随笔者一起walk through the code.

文 何思玫

1. Brief introduction of kubelet

管中窥豹,可见一斑。我们首先从k8s的官方文档中对kubelet的描述中一探究竟。

The kubelet is the primary “node agent” that runs on each node. 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 echanisms (primarily through the apiserver) and ensures that the containers described in those PodSpecs are running and healthy. Other than from an PodSpec from the apiserver, there are three ways that a container manifest can be provided to the Kubelet.

File: Path passed as a flag on the command line. This file is rechecked every 20 seconds (configurable with a flag).

HTTP endpoint: HTTP endpoint passed as a parameter on the command line. This endpoint is checked every 20 seconds (also configurable with a flag).

HTTP server: The kubelet can also listen for HTTP and respond to a simple API (underspec’d currently) to submit a new manifest.

从上述描述中我们可以看到,kubelet是在集群中每个node上都必须存在的组件,负责维护管理pod。

它的工作主要基于四种source—— apiserver的PodSpec,File,HTTP endpoint及HTTP server——来更新pod。其中,File通过--file-check-frequency参数传入, HTTP通过--http-check-frequency参数。

2. Walk through the src code

文章分析的代码版本>v1.0.1, HEAD 6129d3d4。

kubelet相关代码的主入口在cmd/kublet下,调用方法的实现可能会在pkg/kubelet下。文中为了便利读者,也会就具体函数再进行标明。 下面我们就根据代码逻辑继续一次源码解读的DFS。

kubelet.go#main函数的主要流程包括如下三个步骤:

  • 创建一个KubeletServer实例
  • 根据命令行参数加载flag
  • 启动该kubeletServer。

如下图1所示。

kubelet-go-main

图1 kubelet.go#main

注意到,Run函数负责执行具体的kubelet的构建过程,在正常情况下是永远不会exit的。下面我们就来对该函数进行进一步的分析。

2.1 server.go#Run

如上图所示,KubeletServer.Run函数主要包括以下三个主要流程:

  • 配置并构建KubeletConfig实例。
  • 调用RunKubelet(&KubeletConfig,nil)函数真正地运行kubelet,该函数接受一个KubeletConfig类型的实例作为参数,包含了kubelet的配置信息。
  • 根据KubeletServer.HealthzPort的配置(即命令行参数--healthz-port),为/healthz url注册一个默认的handler(http.DefaultServeMux),启动health server。

可见,Run函数的核心步骤在于RunKubelet函数。

2.2 server.go#RunKubelet

我们首先通过略显复杂的图2来了解RunKubelet函数。它的使用场景非常明确,在node上创建并运行kubelet。 Runkubelet 图2 RunKubelet函数可以被大致划分为三大步骤。

Step1: 配置KubeletConfig信息,包括Hostname,NodeName,Recorder等。KubeletConfig这个结构包含了运行kubelet需要的所有参数,在可见的将来可能会和KubeletServer结构进行merge,不再单独存在。

  • 处理Hostname/NodeName信息
  • 创建了一个EventBroadcaster,创建一条包含kubelet及其NodeName配置的api.EventSource,将其注册到该EventBroadcaster中,用以记录kubelet产生的event并发送给apiserver。
  • 配置capabilities:系--allow-privileged--HostNetworkSources参数传入的值。
  • 配置使用docker拉取镜像时的credentialprovider的路径。
  • 配置OSInterface信息

Step2: createAndInitKubelet,顾名思义,创建并初始化kubelet。

builder在这里定义为createAndInitKubelet。

注意到这里返回了三个变量值,其中k为KubeletBootstrap(kubelet的interface),podCfg是config.PodConfig,最后一个是error变量。 podCfg变量是通过pc = makePodSourceConfig(kc)返回的,其中kc为传入的kubeletConfig变量。k则由NewMainKubelet函数返回。

Step3: 通过startKubelet启动kubelet,传入参数包括上一个函数的返回值及kcfg。

下面我们将就Step2和Step3进行更为详细的阐述。

2.2.1 server.go#createAndInitKubelet

这节内容主要包括两部分,首先来看makePodSourceConfig函数。

Section 1: makePodSourceConfig

该函数的返回值是一个config.PodConfig变量,用于将不同source的pod configuration合并到一个结构中,结构如下所示。

下面,我们具体来看makePodSourceConfig完成了什么工作。源码如下:

  • 使用NewPodConfig函数创建一个PodConfig变量cfg,用到了config.PodConfigNotificationSnapshotAndUpdateskc.RecorderPodConfigNotificationSnapshotAndUpdates的更新规则是,在增删pod的时候发送SET消息,其它变更发送UPDATE消息。其中,cfg.Updates是一个缓存大小为50的channel,收发数据类型为kubelet.PodUpdate(即对于某个pod的某种更新操作)。
  • 向podConfig变量cfg加入config source,包括 file/hhtp(url)/apiserver source。如同在开篇综述中所说,File类型和HTTP类型的pod configuration的变更,是通过定时查询对应source完成的。
  • 此处以更为常见的apiserver为例,我们使用NewSourceApiserver监听apiserver中podSpec的变更。该函数接收三个参数,第一个参数为用于向apiserver发起请求的Client,第二个参数为NodeName,第三个参数是由cfg.Channel返回的一个只读channel,类型是chan <- interface{}

也许读者会好奇,这个Channel函数具体做了哪些工作呢? 它会创建并返回一个interface{}类型的channel,并且启动go routine持续监听该Channel,并且将监听到的update进行merge。

merge是在NewPodConfig阶段决定的,即此前newPodStorage(updates, mode, recorder)返回的podStorage的Merge函数。该Merge函数定义在pkg/kubelet/config/config.go中,在该过程中会将监听的channel中更新的信息更新到cfg.updates中。限于篇幅,此处只展示PodConfigNotificationSnapshotAndUpdates对应的case,其它仅以”…”代替。

也就是说,cfg.Channel返回的是一个未知具体类型的被持续监听的channel(且该channel在merge之后会影响到原cfg),那么写channel的工作由谁完成呢?我们相信这是由config source(此处即apiserver)来完成的,也会在NewSourceApiserver函数分析中进行阐释。

  • 通过NewListWatchFromClient函数构造了一个ListWatch
  • 调用newSourceApiserverFromLW来watch并pull(拉取)apiserver的更新。

    // newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver. func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) { send := func(objs []interface{}) { var pods []api.Pod for _, o := range objs { pods = append(pods, o.(api.Pod)) } updates <- kubelet.PodUpdate{pods, kubelet.SET, kubelet.ApiserverSource} } cache.NewReflector(lw, &api.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0).Run() }

可以看到,newSourceApiserverFromLW的执行核心只有一行代码,将创建出来的Reflector Run起来。

NewReflector函数创建一个Reflector对象,用于保证传入的第三个参数Store里的内容是update to date的。第三个参数以send函数为参数构建一个UndeltaStore,其PushFunc为send函数。 UndeltaStore listens to incremental updates and sends complete state on every change.也就是说,UndeltaStore不仅接受从Reflector来的update,并且还会使用UndeltaStore.PushFunc来进行change的deliver。这里就是写入到Cfg.Updates里,即完成了update的写操作。

Note:之前在集群运行过程中遇到过一个error,(可能在将来会upgrage成warning),即watcher超时,导致kubelet报错,位于reflector.go line#225。更新的内容来自listWatch,即apiserver。这里相当于是写updates。

Section 2: NewMainKubelet

我们通过pkg/kubelet/kubelet.go#NewMainKubelet函数构造了一个Kubelet。在该函数中创建各个kubelet需要的组件,部分参见如下列表

  • ContainerGC manages garbage collection of dead containers。负责进行对死掉的container进行垃圾回收。
  • ImageManager manages lifecycle of all images. 负责管理image。
  • DiskSpaceManager manages policy for diskspace management for disks holding docker images and root fs. 负责进行磁盘管理。
  • StatusManager updates pod statuses in apiserver. Writes only when new status has changed.该结构体中的podStatusChannel负责buffer podStatusSyncRequest,是一个缓冲区大小为1000的channel。 从apiserver中更新pod状态。
  • ReadinessManager maintains the readiness information(probe results) of containers over time to allow for implementation of health thresholds.负责收集container的readiness information(readiness info具体指什么并不明晰,是一个bool型,可能代表该container是否准备好?)。
  • RefManager manages the references for the containers. The references are used for reporting events such as creation, failure, etc.
  • VolumeManager manages the volumes for the pods running on the kubelet. Currently it only does book keeping, but it can be expanded to take care of the volumePlugins.
  • OOMWatcher watches cadvisor for system oom’s and records an event for every system oom encountered.
  • 初始化network plugin。这是个alpha feature。
  • ContainerRuntime 可以是DockerManager或者是rkt,默认为docker。
  • ContainerManager manages the containers running on a machine. -(周期性)配置网络参数(iptables rules)。 -(周期性)向master节点发送其它各个node的状态。synchronizes node status to master, registering the kubelet first if necessary.
  • 启动ContainerRuntime。ContainerRuntime是Runtime(interface)的一个实现,定义在pkg/kubelet/container/runtime.go中,包含了SyncPod/GetPodStatus等重要方法。以”docker”为例,它将是一个DockerManager,定义在pkg/kubelet/dockertools/manager.go中,并且实现了Runtime的所有方法。
  • PodManager stores and manages access to the pods. Kubelet discovers pod updates from 3 sources: file, http, and apiserver.三个update pod的来源,均在上例中的makePodSourceConfig 中进行了注册。 特别地,对于非apiserver来源的pod,apiserver是不知道其变更的。为了对其进行更新,我们创建了static pod。A mirror pod has the same pod full name (name and namespace) as its static counterpart (albeit different metadata such as UID, etc). By leveraging the fact that kubelet reports the pod status using the pod full name, the status of the mirror pod always reflects the actual status of the static pod. When a static pod gets deleted, the associated orphaned mirror pod will also be removed. 根据GetPodByFullName的原则,mirror pod会映射原pod的状态。
  • 为containerRuntime设置RuntimeCache。RuntimeCache caches a list of pods.该Cache中pod的状态会通过它的两个方法GetPodsForceUpdateIfOlder来更新。
  • 创建PodWorkersklet.podWorkers = newPodWorkers(runtimeCache, klet.syncPod, recorder) 第二个参数将作为podWorker.syncPodFn,即用以sync the desired stated of pod.实现为‵func (kl *Kubelet) syncPod`(定义在pkg/kubelet/kubelet.go)。
  • 在metrics中注册RuntimeCache。 创建目录,默认根目录在/var/lib/kubelet下,并在根目录下创建pods目录和plugins目录。
  • 初始化所有注册的(即可以访问kubelet的)plugin。
  • 建立记录容器的log目录(/var/log/containers)。

至此,所有相关组件或目录文件都已经被创建完毕,该函数返回一个Kubelet实例。

2.2.2 startKubelet

如果读者还记得我们在2.2节中的下段描述,“Step 3: 通过startKubelet启动kubelet,传入参数包括上一个函数的返回值及kcfg。” 那将是一个莫大的幸事,因为我们终于要从一个非常深的源码栈中跳出进入另一个更深的栈了:) 创建完毕kubelet的,下边无疑要进行的是运行操作。

根据运行类别分为两种,其一为RunOnce(只运行一次),其二为startKubelet(持续运行不退出);两种情况本质的处理逻辑是类似的,下面谨以第二种为例进行讲述。

该函数的核心——启动kubelet——由如下一行代码完成,

podCfg.Updates()返回podCfg.updates,类型为<-chan kubelet.PodUpdate,即前述提及的缓冲区为50的用以存储Pod更新信息的channel,让我们来一览其结构。

Kubelet.Run()定义在pkg/kubelet/kubelet.go中,它完成的工作包括

  • 定义kubelet.logServer
  • 如果kl.resourceContainer不为空(命令行参数--resource-container,”Absolute name of the resource-only container to create and run the Kubelet in (Default: “/kubelet.”) ),则将kubelet移入该container中。
  • 启动imageManager。运行时该imageManager负责持续list image和container,并在其Record中更新。
  • 启动cadvisor。
  • 启动containerManager。
  • 启动oomWatcher。
  • 启动周期性更新uptime。
  • 启动statusManager。
  • syncLoop(updates, kl)。

statusManager.StartsyncLoop具体做了什么工作,可能会引起读者的兴趣。下面我们就具体进行分析。为了让结构显得更为明晰,我们还是新起一个小章节:)

2.2.2.1 statusManager的启动

我们知道statusManager负责监听从apiserver对于pod的更新,更新的读写通过podStatusSyncRequest类型的channelpodStatusChannel

statusManager.Start执行核心工作的代码如下

即持续运行s.syncBatch(),这个函数负责同apiserver交互来获得pod status的更新(located @pkg/kubelet/status_manager.go),在没有更新时会block。这是因为在该函数中会等待s.podStatusChannel的返回值。从另一个角度而言,这是在读channel,写channel的操作应该由apiserver完成。

2.2.2.2 syncLoop

syncLoop这个函数使用到了传入的channel参数,即podCfg.updates,第二个参数为kubelet实例本身。它的函数体异常简单,但是却为我们执行了非常重要的sync pod的核心任务。

syncLoopIteration方法在检查各种配置之后,启动select监听updates(channel)上的数据,一旦有更新的数据,则执行podManager.UpdatePods方法,负责update the internal pods with those provided by the update,实际上就是在podManager的列表里进行相应的更新,并进行相应的标记——把每个pod(以types.UID标记)及其SyncPodType记录好(该函数执行完毕后,所有现有的pod的状态都是”sync”)。

然后,则是最为重要的sync pod

handler.SyncPods方法实际为pkg/kubelet/kubelet.go#SyncPods,进行的工作为SyncPods synchronizes the configured list of pods (desired state) with the host current state。具体包括如下步骤:

  1. 根据传入的现存pod列表将statusManager中的不存在的pod entry删除。
  2. 通过admitPods函数过滤terminated& notfitting & outofdisk pods,返回的pod列表即为desired pod。
  3. 对照desired pod列表,进行sync工作,具体是由Kubelet.podWorkers.UpdatePod来完成的。
  4. 把不存在的pod对应的podWorkers释放掉。这里就体现了维护podWorkers.podUpdates这个field的重要性。
  5. 杀死不需要的pod。
  6. 杀死孤儿volume。
  7. 删除不存在的pod对应的dir
  8. 删除孤儿mirror pod

下面我们来具体分析一下第3条Kubelet.podWorkers.UpdatePod进行的工作。

如果该pod在podWorkers.podUpdates里不存在,则需要进行创建,此时调用podWorkers.managePodLoop。 然后,根据podWorkers.isWorking[pod.UID]的值更新对应podUpdates的值或者传入podWorkers.lastUndeliveredWorkUpdate。这是对于对应pod状态的一个记录。

下面我们具体来看一下创建新pod的过程经历了什么。

从代码中可以看到,核心步骤调用了podWorkers.syncPodFn,这是在创建podWorker时构建的,具体指向了func (kl *Kubelet) syncPod(定义在pkg/kubelet/kubelet.go,完整的传参列表

这个函数在做什么呢? Let’s figure it out.

  • canRunPod:确认是否有足够的权限运行该pod。
  • 创建pod data存储的目录,包括root目录(以pod的UID命名)/volumes/plugins(默认在/var/lib/kubelet/pods下)
  • 为pod创建reference
  • mount volume,并且在volumeManager中设置相应的entry。volume是pod的manifest里边的Spec.Volumes下的信息。
  • 更新statusManager中的pod信息。根据传入参数updateType的不同,a)对于create:kl.statusManager.SetPodStatus(pod, podStatus),在statusManager里为新的pod设置StartTime,并且将新的podStatusSyncRequest传给statusManager.podStatusChannel。b)否则,通过kl.containerRuntime.GetPodStatus(pod)获得pod信息(返回值是api.PodStatus )。但是,在现在这个context中,应该为a).
  • 获得inspect pod的manifest中pod.Spec.ImagePullSecrets的信息。(这个secret是用来pull image的)
  • sync pod(在下文中再进行详细论述)
  • 根据该pod是否为static pod,在podManager中进行了相应的设置。

sync pod的过程,以DockerManager.SyncPod为例。有鉴于函数体比较长,在此就不再贴出源码。其功能非常明晰——目的在于使得running pod能够match对应的desired pod。

  • 计算running pod和desired pod之前的差距,返回的信息里包括StartInfraContainer(是否需要创建infra container),InfraContainerId(infra container ID),ContainersToStart(需要start的container),ContainersToKeep(需要keep的container)。
  • 根据返回的信息决定是否重启infra container((即gcr.io/google_containers/pause:0.8.0),配置network及其它container

至此,Kubelet.syncLoopIteration算是基本完成了。这也就意味着kubelet已经在node上运行起来,充当其pod的维护者的角色了。

3. Summary

总之,从宏观角度上而言,kubelet进行的工作就是从apiserver/file/http中获取pod更新的信息,并且定期进行sync pod。其中有大量的工作都涉及了channel的使用,也希望读者在阅读的过程中能够加以注意。

4S: Services Account, Secret, Security Context and Security in Kubernetes

Service Account, Secrets和Security Contexts作为保证kubernetes集群Security的策略被引入,相关代码一直处于快速变更与迭代中。本文谨从design和初级实践的视角对其进行概略性的分析,以飨读者。

文 何思玫

1. 集群安全(Security in Kubernetes)

众所周知,集群安全的首要关注点无疑是隔离性。进程之间的相互隔离,进程与集群基础设施的严格界限,用户与管理员之前的天然角色区分,都应该被考虑到隔离性的范畴内。 统而言之,集群安全性必须考虑如下几个目标: (1) 保证容器与其运行的宿主机的隔离。 (2) 限制容器对于基础设施及其它容器的影响权限,运行拥有特权模式的容器是不被推荐的行为。 (3) 最小权限原则——对所有组件权限的合理限制。 (4) 通过清晰地划分组件的边界来减少需要加固和加以保护的系统组件数量。 (5) 普通用户和管理员的角色区分,同时允许在必要的时候将管理员权限委派给普通用户。 (6) 允许集群上运行的应用拥有secret data。 涉及安全,authentication和authorization是不能绕过的两个话题。下面我们就先来了解一下k8s在这两个issue所提供的支持。

2. Authentication

k8s目前支持三种认证方式,包括certificates/tokens/http basic auth。 client certificate authentication是双向认证的方式,可以经由easyrsa等证书生成工具生成服务器端并客户端证书。 Token authentication:单向认证方式,为kube-apiserver提供- -token_ auth_file,格式为一个有3columns的csv file:token,user name,user id。此处为使用该认证方法的常见的elasticsearch case。 Basic authentication:传入明文用户名密码作为apiserver的启动参数,不支持在不重启apiserver的前提下进行用户名/密码修改。

更多细节详见官方相关文档

3. Authorization

--authorization-mode,apiserver的参数,用于定义对secure port设置何种authorization policy,包括三种AlwaysAllow/AlwayDeny/ABAC,第一种policy允许所有对apiserver的API request,与之相反,第二种则会block所有的API request,第三种则为Attribute-Based Access Control,即对于不同request attribute,有不同的access control。 下面我们着重讨论ABAC mode。

3.1 Request Attributes

在考虑authorization时,一个request有5种attribute需要考虑: – user – group – readOnly – resource(如只访问API endpoint,如/api/v1/namesapces/default/pods,或者其它杂项endpoint,如/version,此时的resource是空字符串) – namespace

3.2 Policy File Format

对于ABAC mode,还需要specify --authorization-policy-file参数,例子参见此处

除此之外,apiserver还可以通过调用Authorizer(interface)来决定是否允许某个API操作。

4. UserAccount

这个概念适用于所有希望与k8s集群交互的个体(通常可以认为是human),因此用户名通常是human-readable,目前并不作为一个代码中的类型单独出现,一般通过config file来读取和感知。

5. Service Account

Service Account概念的引入是基于这样的使用场景:运行在pod里的进程需要调用k8s API(如scheduler/replication controller/minitor system等)以及非k8s API的其它服务(如image repository/被mount到pod上的NFS volumes中的file等)。我们使用Service Account来为pod提供id。 Service Account和第4节中提及的User account可能会带来一定程度上的混淆,下面我们就先从概念的层面将其sort it out。

  • user account通常是为human设计的,而service account则是为跑在pod里的process。
  • user account是global的,即跨namespace使用;而service account是namespaced的,即仅在belonging的namespace下使用。
  • 创建一个新的user account通常需要较高的特权并且需要经过比较复杂的business process(即对于集群的访问权限的创建),而service account则不然。
  • 不同的auditing consideration。
5.1 Design Overview

Service Account包括如下几个元素:

  • name,用以作为id
  • principal,用以authenticated及authorized
  • security context,定义linux capabilities等与系统相关的参数。
  • secrets
5.2 Use Cases

通常我们在使用kubectlbinary来与集群交互时,我们(User Account)都会通过apiserver的认证(如果该集群设置了认证方式的话),可以被看作某个特定的User Account。类似地,当pod希望与apiserver进行交互时,也需要使用特定的认证机制,即Service Account。

当创建pod且没有经过特殊设置时,它将会被默认地分发到该namespace下default的service account,可以通过kubectl get pods/podname -o yaml的命令行查看spec.serviceAccount的field)。

5.2.1 default service account

每个namespace在创建时都会有一个default的service Account,如下所示

5.2.2 Create your own service Account

用户可以方便地通过yaml文件等形式创建自己的serviceAccount,如

通过`kubectl get < resource > -o yaml获取详细信息

可以看到,token(secret)被自动生成并ref了。如果在之后创建pod的时候ref这个自建的serviceAccount,在spec.serviceAccount中指定即可。

通过如下命令可以删除serviceAccount。

5.2.3Adding Secrets to a service account

注意到,我们可以不使用serviceAccount自建的secrets,而是选用我们自己生成的secrets,这是一种consume secret的方式。 首先创建secret,kubectl create -f secret.yaml

然后创建一个comsume该secret,kubectl create -f serviceaccount.yaml

最后在pod的manifest中指定其serviceaccount,kubectl create -f busybox4.yaml

已了解如何创建一个引用自建secret的serviceaccount,但是如果在exsiting serviceaccount中添加引用,目前官方文档mark it as TODO。笔者认为此用法应该与上例类似,但笔者采用这种方式consume secret时,serviceaccount似乎并不对该secret有维护作用。 Update:此用法确与上例类似,参见issue#12012,笔者后续会考虑对相关doc进行更新。

6. Secrets

secret是用于存放sensitive information的数据结构,可以通过manual或者automatic两种方式创建。 secret的use case主要有两种,或者作为volume mount到pod上,或者用于kubelet在为pod里的container拉取镜像时。 使用serviceAccount自建的secret是一种保证secure的推荐方式,当然可以由用户disable或者overridden,如果用户愿意承担额外的风险的话。

6.1 Secrets类型一览

secret的类型目前有三种,如下所示

Opaque类型是默认的用户自建类型,即任意string(实际上也并不是十分任意,data label的key/value必须满足特定的要求:Each key must be a valid DNS_SUBDOMAIN or leading dot followed by valid DNS_SUBDOMAIN. Each value must be a base64 encoded string as described in https://tools.ietf.org/html/rfc4648#section-4)。

第二种类型的例子如下所示

第三种类型一般用于向private registry拉取镜像,在6.3节中再做阐述。

6.2 manually specify a secret

如上述所示,使用kubectl create命令即可创建一个secret。创建完毕之后,可以使用serviceAccount来consume,也可以手动添加到pod的manifest中,如下例所示:

More example here。

6.3 Manually specifying an imagePullSecret

这个方法仅在GKE/GCE或其它cloud-provider场景下推荐使用,创建一个类型为kubernetes.io/dockercfg的secret,并且在pod manifest的imagePullSecrets label下使用。因为缺乏实验平台,在此不作赘述。

总而言之,目前k8s对于secret的support仍处于不十分完善的阶段,secret可能以多种方式暴露给外部用户甚至是attacker。

7. Security context

Security context是用以对容器进行限制,使得不同的运行容器之前能够实现较为明晰的隔离,以及降低其影响宿主机和其它容器的可能性。通俗而言,容器中的security context用于表征在创建及运行容器时,它能够使用及访问的资源参数。 该概念将会被用到如下两处:

  • kubelet用于创建及运行container。
  • 作为serviceAccount的一部分定义在pod中。

这个概念具体如何使用在k8s场景中基本上可以认为在servicAccount中的securityContext label下,目前代码只实现了capabilities和privileged,在此不展开说明。

Kubernetes代码走读之Minion Node 组件 kube-proxy

Kube-proxy是kubernetes 里运行在minion节点上的一个组件, 它起的作用是一个服务代理的角色. 本文的内容将分为以下两部分, 源代码来自kubernetes release-0.8.1, 代码有删节,省略的代码或log输出用...表示:

1 Kube-proxy 简介

2 Kube-proxy代码解读

1 Kube-proxy简介

Kube-proxy网络代理运行在每个minion节点上。网上很多人所说这个proxy是kubernetes里的SDN组件,我本人并不这么认为, 我认为可以把他看成是一个高级的反向代理.它的功能反映了定义在每个节点上Kubernetes API中的Services信息,并且可以做简单的TCP流转发或在一组服务后端做round robin的流转发.服务端点目前通过与docker link 兼容的环境变量指定的端口被发现, 这些端口由服务代理打开.目前,用户必须在代理上选择一个端口以暴露服务.

Continue reading

Docker网络详解及pipework源码解读与实践

Docker作为目前最火的轻量级容器技术,有很多令人称道的功能,如Docker的镜像管理。然而,Docker同样有着很多不完善的地方,网络方面就是Docker比较薄弱的部分。因此,我们有必要深入了解Docker的网络知识,以满足更高的网络需求。本文首先介绍了Docker自身的4种网络工作方式,然后通过3个样例 —— 将Docker容器配置到本地网络环境中、单主机Docker容器的VLAN划分、多主机Docker容器的VLAN划分,演示了如何使用pipework帮助我们进行复杂的网络设置,以及pipework是如何工作的。

1. Docker的4种网络模式

我们在使用docker run创建Docker容器时,可以用--net选项指定容器的网络模式,Docker有以下4种网络模式:

  • host模式,使用--net=host指定。
  • container模式,使用--net=container:NAME_or_ID指定。
  • none模式,使用--net=none指定。
  • bridge模式,使用--net=bridge指定,默认设置。

Continue reading

Kubernetes Minion Node 组件 之 Kubelet

Kubelet是Google Kubernetes 集群minion工作节点上的一个重要组件.本文将作者阅读代码和亲身的使用经验相结合带你深入理解kubelet. 因为kubernetes代码处在火热迭代开发中,版本日新月异, 本文的源代码来自2014年12月22日github上kubernetes的master最新代码, commit id 119fe37f064905d, 由于kubelet代码量大,变量多,所以文中展示的代码有删节,省略的部分用…表示.

文章内容分为一下几个部分:

  1. Kubelet简介
  2. Kubelet源码分析
  3. Kubelet 启动docker容器实例分析

1 Kubelet简介

Kubelet运行在Kubernetes Minion Node上. 它是container agent的继任者(使用golang重写),它是GCE镜像的一部分. Kubelet二进制程序负责维护在特定主机上运行的一组容器。它从配置文件或是从etcd 服务器上同步容器配置清单。容器配置清单是描述一个pod的文件. Kubelet采用一组使用各种机制提供的清单并确保这些清单描述的容器被启动并持续运行. 有以下几种方式提供给kubelet一个容器清单:

Continue reading

Google Kubernetes设计文档之网络篇

摘要:Kubernetes是Google开源的容器集群管理系统,构建于Docker之上,为容器化的应用提供资源调度、部署运行、服务发现、扩容缩容等功能。其从Docker默认网络模型中独立出来形成了一套自己的网络模型,本文将详细介绍。

模型和动机

Kubernetes从Docker默认的网络模型中独立出来形成一套自己的网络模型。该网络模型的目标是:每一个pod都拥有一个扁平化共享网络命名空间的IP,通过该IP,pod就能够跨网络与其它物理机和容器进行通信。一个pod一个IP模型创建了一个干净、反向兼容的模型,在该模型中,从端口分配、网络、域名解析、服务发现、负载均衡、应用配置和迁移等角度,pod都能够被看成虚拟机或物理机。

另一方面,动态端口分配需要以下方面的支持: 1,固定端口(例如:用于外部可访问服务)和动态分配端口; 2,分割集中分配和本地获取的动态端口; 不过,这不但使调度复杂化(因为端口是一种稀缺资源),而且应用程序的配置也将变得复杂,具体表现为端口冲突、重用和耗尽。 3,使用非标准方法进行域名解析(例如:etcd而不是DNS); 4,对使用标准域名/地址解析机制的程序(例如:web浏览器)使用代理和/或重定向。 5,除了监控和缓存实例的非法地址/端口变化外,还要监控用户组成员变化以及阻止容器/pod迁移(例如:使用CRIU)。 NAT将地址空间分段的做法引入了额外的复杂性,这将带来诸如破坏自注册机制等问题。

Continue reading

Google Kubernetes设计文档之Pod篇

摘要:Kubernetes是Google开源的容器集群管理系统,构建于Docker之上,为容器化的应用提供资源调度、部署运行、服务发现、扩容缩容等功能。CSDN联合浙江大学SEL实验室共同翻译其设计文档,本文为系列的第二篇:Pod。

在Kubernetes中,创建、调度和管理的最小部署单位是Pod,而不是容器。

1.什么是Pod

一个Pod对应于由若干容器组成的一个容器组,同个组内的容器共享一个存储卷(volume)。Pod主要是在容器化环境中建立了一个面向应用的“逻辑主机”模型,它可以包含一个或多个相互间紧密联系的容器。在没有容器化技术的场景里,同个Pod内的“容器”都在同一台物理或虚拟主机上运行。 Pod与容器一样都不是持续存在的,在容器的生命周期里,每个Pod被分配到节点上运行直至运行结束或被删除。当一个节点消失时,该节点上的Pod也随之被删除。每个Pod实体只会被调度一次,不会重复分配给别的节点,由replication controller负责创建新的Pod来替代旧的(在未来也可能有新的API用于Pod迁移)。

Continue reading

Google Kubernetes设计文档之安全篇

摘要:Kubernetes是Google开源的容器集群管理系统,构建于Docker之上,为容器化的应用提供资源调度、部署运行、服务发现、扩容缩容等功能。本文为其设计文档系列的第一篇:安全。

1.设计目标

本文讲述了Kubernetes的容器、API和基础设施在安全方面的设计原则。

  1. 保证容器与其运行的宿主机之间有明确的隔离;
  2. 限制容器对基础设施或者其它容器造成不良影响的能力;
  3. 最小特权原则——限定每个组件只被赋予了执行操作所必需的最小特权,由此确保可能产生的损失达到最小;
  4. 通过清晰地划分组件的边界来减少需要加固和加以保护的系统组件数量。

Continue reading