云计算时代操作系统Kubernetes之自己动手实现编排系统(三)

笔者坚信不疑的一个观点是,万物想“通”,这个“通”更多是从高阶的抽象的角度,设计一个高可用的系统和在家里给女儿做番茄炒蛋是一个道理。我们以番茄炒蛋为例,如果要让孩子中午能吃到美味健康的番茄炒蛋,我们需要先清洗番茄,然后把番茄切碎,在洗番茄和切番茄的时候,如果还有人手,可以并行的准备鸡蛋液,当所有的材料准备好后,就可以热油,油热了后,先炒鸡蛋,鸡蛋炒好后,洗锅,然后超西红柿,西红柿炒的七分熟,然后倒入鸡蛋,翻炒3分钟,出锅前调味,然后中午孩子就有美味又健康的家常菜西红柿炒鸡蛋吃了。你如果把上边的整个过程和计算机程序做个对比,你会发现有串行处理,并行处理,多线程等待(比如西红柿还没有切好的时候,即便是把鸡蛋炒好了,也不能进入下一步),资源互斥(锅在炒鸡蛋的时候,就不能炒西红柿)等等。给孩子中午做西红柿炒鸡蛋是一项工作,我们将这项任务拆解为多个任务来完成,而从某种抽象层面来看编排系统的任务的话,其实并没有两样。

对于大部分企业来说,基本都会有自己的官方网站,网站会运行在Apache web服务器上,这就是一个任务。大部分网站都有数据库支持来提供数据,数据库服务器部署在单独的服务器上,安装了MYSQL或者PostgreSQL,来持久化保存动态更新的数据,这也是一个任务(Task)。我们在做西红柿炒蛋的时候,并不是空手就可以做出来,需要特定的“运行环境”,首先需要有厨房,厨房里有电冰箱来提供所需的食材,另外我们需要煤气灶和锅,刀子,案板,打蛋器和橱柜,碗等。同理运行一个任务也需要“运行环境”,特别对于编排系统来说,需要一台物理机,以及容器化平台,比如Docker。Docker平台给我们的任务提供了运行需要的所有资源,比如CPU,内存,网络接口(le0和lo)等等。对于像任何编排系统,任务都是最最基础的组成元素(还记得笔者上篇文章介绍动态配置,POD是整个Kubernetes的基石),如下图所示,我们对第一篇文章中介绍的编排系统模型进行了一点优化:

《图1.1 编排系统的核心职责是从用户接受任务,然后在工作节点运行起来》

如上图所示,编排系统最终选择了工作节点#2来运行我们提交的任务T1,图中的蓄念表示,虽然编排系统的调度器考虑了工作节点#1和#3,但是最终还是选择了工作节点#2来运行我们提交的任务。那么我们如何让Task任务在工作节点上运行起来呢?这是笔者这篇文章的主体,我们会让Task结构的代码完整起来,让我们测试用的代码可以在本地Docker环境上运行起来。因此你可以看到Docker提供的容器化运行环境是核心,为了让内容更加完整,我们从Docker的一些基础知识开始。

相信关注笔者系列文章的同学应该对Docker不会陌生,虽然说最新版本的Kubernetes已经把Containerd作为默认的容器运行时,但是考虑到读者的续写成本以及体感,我们的julia平台会构建在Docker容器化平台之上,笔者的确考虑后续迁移到Containerd上,切换成本不会太高。简单来说,容器技术让开发人员将自己的应用程序,依赖,甚至包括整个操作系统的文件系统打包成标准镜像,镜像会被推到企业的中心仓库,这样Devops团队就不用担心兼容性,依赖等问题,因为容器在打包的时候,已经基本把依赖也打包了,比如说访问数据库的三方库等。

Docker不光提供了一套完善和企业级部署ready的打包工具,同时也提供了一套部署工具,我们很容易在安装了Docker的机器上,通过docker run来启动一个容器实例,比如一台Postgres数据库实例,来支持我们的日常开发,这比下载安装包,安装到本机,进行配置后启动要方便的多。如下边的命令,在安装了Docker的机器上,可以顺利启动一台指定版本的postgrep数据库服务器:

docker run -it -p 5432:5432 --name postgres -e POSTGRES_USER=julia -e POSTGRES_PASSWORD=yunpandaughter postgres:13

当数据库运行起来之后,我们首先通过pg的客户端登陆到数据库实例:psql -h localhost -p 5439 -U julia,然后就可以创建数据库,创建表等,这些SQL操作和使用传统方式安装的数据库服务器并无两样。

当容器运行起来之后,我们还可以使用docker inspect来获取容器实例的详细信息,docker inspect和笔者在Kubernetes系列文章中介绍的kubectl describe pod yunpan-ssl类似,会返回非常全面的容器实例状态信息,为了节省篇幅,并且我们已经多次在前边的文章中展示过,大家可以在自己的本地环境验证。

最后,当任务的工作已经完成,或者我们需要主动退出,可以使用docker stop yunpan-ssl(容器的ID或者名字)命令,虽然说这个命令不会有任何打印输出,但是如果我们再使用docker inspect命令查看容器实例的状态时,会发现这个时候容器实例的状态从running变成exited。

在我们的julia容器编排平台上,工作节点负责启动,停止和提供任务运行所需要的上下文环境信息(可以回想一下西红柿炒鸡蛋的例子,厨房,冰箱,刀具,厨具等)。工作节点不能自己单独完成任务启动,停止工作,需要借助于Docker平台提供的API。幸运的是,Docker提供的操作容器实例的API可以通过HTTP协议来访问,这就意味着我们可以使用httpclient,curl这样的工具或者客户端变成代码库来控制容器的启动和运行。比如我们在自己的Docker环境中,通过命令行curl --unix-socket /run/docker.sock http://docker/containers/12345678910/json 就能获取和docker inspect返回的相同容器实例信息。

当然对于我们即将要开发实现的julia编排平台来说,我们可以使用Go的http客户端端代码库,虽然说可行,但是我们要处理很多细节,比如HTTP方法,状态码,序列化,反序列化等,因此为了更加聚焦核心工作,我们会使用Docker提供的SDK。SDK封装了Docker API的细节,给开发人员提供了更加友好的编程接口,比如创建,运行和停止任务等,Docker的SDK提供如下所示的6个方法,我们会在代码中重度依赖:

- NewClientWithOpts方法: 创建访问Dokcer客户端的实例并返回给调用者。

- ImagePull方法: 从仓库pull容器进项到工作节点。

- ContainerCreate方法: 基于提供的配置信息创建容器实例

- ContainerStart方法: 给Docker引擎发送启动新创建容器实例的请求

- ContainerStop方法: 发送请求给Docker引擎来停止指定的容器实例

- ContainerRemove: 从工作节点上删除容器实例

坦白讲,我们在自己本地环境中使用docker命令来操作容器,本质上使用的也是Docker SDK,如下图所示:

《图1.2 Julia平台和通过Docker命令操作容器本质相同,都使用SDK》

如上图所示,使用代码操作容器实例和使用docker命令行工具并没有实质的区别,并且使用SDK可以让我们的开发工具得到极大的简化。

为了让我们的应用程序能在Docker上运行起来,我们需要提供配置信息给Docker引擎。配置信息就如同我们做西红柿炒蛋的菜谱一样,比如我们需要2个鸡蛋,3个西红柿,并且且西红柿,热油以及炒鸡蛋需要按照确定的流程,并且每个子任务有固定的时间。对于启动容器实例来讲,我们最少需要提供镜像的ID,名称,进程运行的端口号,需要的硬件资源等信息。

具体来讲,在julia编排系统中,我们使用Config结构来管理配置信息。Config结构封装了容器任务运行需要的所有配置信息,如下图所示:

《图1.3 Julia平台任务的配置信息结构》

如上图所示的是julia编排平台上,任务的配置管理结构。坦白讲,对于熟悉Kubernetes平台的同学来说,加上笔者对这些字段基本上都给了注释,因此不太需要太多的画蛇添足般解释。但是未了内容的完整性,我们稍微啰嗦一下。

Name字段用来在julia平台上唯一识别一个任务,并且容器的名称也是直接拷贝自这个字段;Image字段用来记录容器进程运行的软件包(镜像);Memory和Disk字段在julia平台有来个作用,调度器用这个信息来帮任务找到合适的工作节点运行起来,而worker进程用这两个字段的值来请求Docker分配足够的资源来运行应用程序。

Env字段用来给容器进程传递配置信息(这是我们的老朋友了,笔者前边几篇关于Kubernetes配置管理的文章,就是为了让大家对这些概念有深入的理解,不至于到这篇文章看不懂),在我们后续的例子中,我们会使用两个环境变量:“-e POSTGRES_USER=julia”和“-e POSTGRES_PASSWORD=secret”来分别指定postgres数据库的用户名和密码(由于我们的目的是开发学习用的调度平台,我知道这样不安全,大家在Kubernetes中部署应用的话,记得要使用Secret对象)。

最后,RestartPolicy字段少不了,用来告诉Docker daemon当应用crash的时候,该咋办。这个字段对于编排平台来说异常重要,因为这是容器实现“健壮”的根本,我们的julia平台上只接:受空字符串,always,unless-stopped和on-faliure。当我们将重启策略设置为always的时候,容器实例会在所有情况下重启。unless-stopped只有在容器执行docker stop之后才会重启,而on-falure只有容器出现运行错误的时候才会重启(返回非0的状态码)。

完成了任务的配置文件定义之后,终于到了最激动人心的时刻了,我们来具体实现如何启动和停止一个任务。从第一篇文章开始,笔者多次强调,在我们的julia编排系统上,worker对象负责启动任务。

我们先从给task代码文件增加Docker结构开始,如下图所示:

《图1.4 Docker结构用来封装Docker相关的信息》

如上图所示的Docker结构定义,字段都有注释,因此笔者就不累述了。为了简化变成,我们增加一个DockerResult结构,用来封装访问调用后的返回信息。DockerResult结构中有error字段,用来记录错误信息;Action字段用来记录具体执行的操作是什么,比如start或者stop;ContainerId字段用来记录具体的容器实例,以及Result字段用来记录Action操作后的更多信息。

好了,有了这些结构后,接下来,我们来实现为任务创建和运行容器实例的代码。具体来说,我们为Docker结构增加叫Run的方法,Run方法的第一步是从镜像仓库下载镜像。为了pull镜像,Run方法首先需要创建一个context,用来在不同的上下文传递数据。

接着我们的Run方法调用Dockre客户端实例的ImagePull方法,这个方法接受我们上边创建的context对象,镜像名称,以及其他需要的信息。ImagePull方法会返回两个对象:一个实现了io.ReadCloser对象和一个error对象,这两个对象分别保存在reader和err变量中。

接下来,代码逻辑会检查error值,如果不为nil,那么方法直接打印error message并返回一个DockerResult对象。最后,Run方法会把通过io.Copy方法把reader变量的值拷贝到os.Stdout。

注:io.Copy是Golang标准库io代码库提供的方法,用来把数据从源(reader)拷贝的目标(os.Stdout)。

当我们顺利从仓库下载完镜像数据后,接下来,我们的“菜谱上”写的是准备运行容器需要的配置信息。由于我们即将使用Docker SDK客户端来访问Docker API,因此先了解一下ContainerCreate方法的具体签名很有必要,如下图所示:

《图1.5 ContinerCreate方法签名》

如上图所示,ContainerCreate方法的第一个参数是context,这个我们前边介绍过;第二个参数是任务的配置信息,我们会将自己定义的Config结构的信息拷贝过来;第三个参数是个container.HostConfig类型的指针,这个参数设置了任务对可运行工作节点的要求,比如必须是一台安装了linux某个版本操作系统的机器;第四个参数是网络接口,用来指定网络详细信息,比如network id,ip地址等。

第五个参数是指向specs.Platform类型的指针,用来设置诸如CPU架构等信息;最后一个参数是容器的名称,为字符串类型。有了对这个方法输入参数的深入理解,我们接下来编写代码将信息从Config类型转换成方法需要的参数类型,具体的代码如下图所示:

《图1.6 准备调用ContainerCreate方法需要的参数》

如上图所示,首先我们设置重启策略,创建变量rp,并把我们的Config结构中的重启策略嵌入进去。接下来定义变量r用来声明任务需要的计算资源;接下来我们创建容器配置变量cc,类型为container.Config,我们会把在自己的Config类型把Image和Env信息拷贝进去;最后,我们把rp和r变量组合成类型为container.HostConfig的变量hc,并增加PublishAllPorts=true字段,来告诉Docker将应用暴露在随机选择的端口上对外提供服务。

好了,到这里为止,我们的准备工作已经做完了,我们终于可以创建容器并启动起来了。由于我们也介绍过ContainerCreate方法的签名了,因此直接调用这个方法,大家需要注意我们的第四个和五个参数为nil,意味当前不会用到这个参数,可以忽略。代码如下图所示:

《图1.7 调用ContainerCreate和ContainerStart方法》

如上图所示,ContainerCreate方法返回两个值,一个container.ContainerCreateCreatedBody类型的指针和一个error类型。我们把error信息保存在err变量上,接下来检查err变量,如果有任何错误,我们就打印出来,并返回对应的DockerResult。

接下来我们就启动容器,ContainerStart方法接受容器的ID,这个ID来自于ContainerCreate的第一个类型为container.ContainerCreateCreatedBody的返回值。由于我们目前还没有启动的其他选项,因此传递一个空的types.ContainerStartOptions类型的参数。ContainerStart方法只有一个error返回值,因此我们的代码检查时候方法调用是否有错误,如果有就打印出来,并封装到DockerResult返回。

到这里为止,如果一切顺利,我们的容器实例就成功在Docker平台上运行起来了,最后我们做一些收尾工作,将container ID加入配置对象,调用ContainerLogs来将返回值写到stdout,如下图所示:

收尾工作

注:Run方法中启动容器的代码和Docker run基本一致,也就是说我们通过docker run运行容器,背后指定的代码逻辑就是我们的Run方法。

完成了容器启动方法,最后我们来编写Stop方法,坦白讲Stop方法比Run方法简单多了,直接上代码如下:

《图1.8 容器停止方法代码》

如上图所示,读者需要注意的是,Stop方法和我们执行docker stop的处理机制完成一样。

在运行我们的代码之前,还剩下最后一步,更新main方法,我们需要增加createContainer方法来设置任务的配置,以及增加stopContainer方法来停止容器是的运行。接着,我们在main方法中分别调用这两个方法,来创建和停止容器实例。如下图所示:

《图1.9 更新main方法来创建和停止容器》

代码写好了,赶紧运行起来看看,在自己的环境中执行 go run main.go,如果一切顺利,你就会看到如下图所示的输出,说明一切按预期工作。

《图1.10 本地运行的结果》

好了,今天这篇文章内容就这么多了,关于julia编排平台的源代码,笔者很快会在下篇文章末尾放出来(还在完善中..),敬请关注!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容