注:本文首发于CSDN,转载请标明出处。
【编者按】作为一个操作系统,CoreOS 采用了高度精简的系统内核及外围定制,将许多原本需要复杂人工操作或者第三方软件支持的功能在操作系统级别进行了实现,同时剔除了其他对于服务器系统非核心的软件,比如GUI和包管理器。CSDN特邀ThoughtWorks的软件工程师林帆带来了“漫步云端:CoreOS实践指南”系列文章,带大家了解CoreOS的精华和推荐的实践方法。本文为基础第四篇:集群的指挥所Fleet,CoreOS中的Fleet服务通过Etcd(一种分布式数据存储服务)获得集群的服务信息并通过DBus接口操作Systemd控制集群中任意节点的服务状态。
作者简介:
林帆,生在80后尾巴的IT攻城狮,ThoughtWorks成都办公室CloudOps小组成员,平时喜欢在业余时间研究DevOps相关的应用,目前在备考AWS认证和推广Docker相关技术。
CoreOS的一个特点就是原生支持集群内跨节点操作,这种能力是如何实现的呢。简单来说,要进行跨节点的服务控制,至少需要满足以下两个条件:
- 首先,一个节点的进程不可能直接获取和控制另一个节点上的服务状态,因此必须有一种消息通信机制;
- 其次,当消息到达目的节点后,目的节点上需要有一个服务进程具备获得和控制所在节点服务的能力,并且这个进程必须与CoreOS已有的服务管理工具Systemd很好的配合,否则就会发生冲突。
在 这个系列的上一篇中讲解了Systemd在单节点的系统启动管理和服务生命周期控制方面的能力。在篇末我们提到Systemd的能耐其实远不止这么多,不夸张的说,其包罗万象程度的足够再单独开辟一个系列专栏来写。其中有一项比较偏门的特性,是它和DBus消息总线的集成能力,从 文档来看,就是一系列复杂的API。但正是在这些API的存在,使得Systemd具备了业务扩展的潜力。
回到最开始问题中的两个必要条件。对于第一点,CoreOS设计了一套完整的分布式数据存储服务方法,并提供了可扩展API,集群中的任何其他服务都可以通过这套机制向集群中的其他节点发布消息和同步数据,这部分的细节会在这个系列的下一篇的文章里详述。而对于第二点,正是Systemd的DBus扩展补上了关键的一环。基于这样的设计思路,一个跨节点控制的解决方案已经跃跃欲出。现在,让我们欢迎今天的主角:Fleet。
Fleet的基本操作
Fleet服务实际上是由一个运行在每一个节点上的后台服务进程fleetd和一个与用户交互的控制台程序fleetctl组成。从使用的角度上说,我们只需要关注fleetctl命令的操作方法。事实上fleetctl的命令参数和上篇中的systemctl非常相似。
从这一节开始,我们将开始用到之前建立的集群中的其他几个节点。在开始操作集群之前,第一步依然是通过vagrant ssh进入集群中任意一个节点(比如core-01)。不过,这次进入的时候需要带上一个附加参数“-- -A”,注意中间有一个空格。
vagrant ssh core-01 -- -A
这里的“-- -A”前两的个横杆表示ssh参数的起始。之后的 -A 是标准的 ssh 命令参数,表示将主机的SSH秘钥传递到虚拟机里面,这样做是为了之后的 fleetctl ssh 命令能够成功登陆到集群的其他节点里面。在实际应用中也可以通过在节点里面生成自己的SSH Key发布到需要跳转的其他节点达到相同的目的。
- 获取集群信息
进入节点后,通过fleetctl list-machines命令用来查看整个集群的基本信息。
$ fleetctl list-machines
MACHINE IP METADATA
14ffe4c3... 10.132.249.212 -
1af37f7c... 10.132.249.206 -
9e389e93... 10.132.248.177 -
可以看到Fleet成功获取了集群中的三个节点的ID标识和IP地址,节点的ID标识是在第一次启动时生成的,它被保存在各个节点的 /etc/machine-id文件中。METADATA是节点在启动时可以通过user-data文件配置的键值标签信息,通过逗号分隔。例如启动前将user-data修改如下:
#cloud-config
. . .
coreos:
fleet:
public-ip: $private_ipv4
metadata: region=china,private-ip=$private_ipv4
就会看到类似以下的输出了。
MACHINE IP METADATA
0acdd9bf... 10.0.2.15 private-ip=172.17.8.103,region=china
f2558aaa... 10.0.2.13 private-ip=172.17.8.101,region=china
f260afd8... 10.0.2.14 private-ip=172.17.8.102,region=china
这种METADATA标识对于集群节点的区分和识别是十分有用的,如果使用得当就能够很好的帮助集群的管理。
PS:如果注意观察,你也许已经发现了,上面的METADATA的两个标签的顺序和在user-data配置时候是不一样的,这正是由于了METADATA的数据是作为键值存储的,因此输出时候会按照键的字母序重新排序显示。
- 显示集群服务
通过fleetctl list-units命令列出集群里通过Fleet管理的服务。虽然现在core-01节点已经运行了一个Hello服务,这时如果在任意节点上执行 fleetctl list-units,会看见输出的结果是空的,因为Fleet并不会接管直接由节点本地Systemd添加的服务。换句话说,Fleet和Systemd分别有自己的服务Unit记录,而Fleet会将其所管理的Unit添加到集群中某一个节点的Systemd上,并负责在删除自己记录时将对应的Systemd记录删除(这个说法不完全准确,具体过程参见集群上的服务生命周期小节),而Systemd上其他不由Fleet创建的记录是不能通过Fleet的命令管理的。
$ fleetctl list-units稍后在添加了集群级别的服务以后,我们再来观察它的输出结果。
UNIT MACHINE ACTIVE SUB
- 节点跳转
通过fleetctl ssh命令来快速的从一个节点进入另一个节点,参数可以是目的节点的ID标识或目的节点上运行的任意一个服务名称。
core@core-01 ~ $ fleetctl list-machines # 找到一个节点ID
...
f260afd8... 10.0.2.14 private-ip=172.17.8.102,region=china
core@core-01 ~ $ fleetctl ssh f260afd8 # 通过ID跳转到指定节点,也可以直接使用服务名称
...
Are you sure you want to continue connecting (yes/no)? yes # 第一次跳转时需要确认
...
CoreOS (alpha)
core@core-02 ~ $ # 已经跳到core-02节点上了
core@core-02 ~ $ exit # 回到之前的节点
core@core-01 ~ $ # 回到了core-01节点
这里比较常遇到的错误是下面这两种:
1.
Failed building SSH client: SSH_AUTH_SOCK environment variable is not set. Verify ssh-agent is running. See https://github.com/coreos/fleet/blob/master/Documentation/using-the-client.md for help.
2.
Failed building SSH client: ssh: handshake failed: ssh: unable to authenticate, attempted methods [none publickey], no supported methods remain出现这两种错误的原因是,当前节点的SSH代理没有配置或目标节点不接受当前节点的登陆秘钥。对于Vagrant创建的集群,一般是因为在vagrant ssh进入节点的时候没有加上“-- -A”参数造成的。
- 跨节点执行命令
相比跳转,fleetctl ssh 命令更常用的一个功能是在指定节点远程执行特定命令。比如打印出运行了Hello服务的节点的 /etc/environment 文件内容。
$ fleetctl ssh hello cat /etc/environment这里的参数 hello 即告诉Fleet在“运行 hello.service”服务的节点执行指定命令。
COREOS_PUBLIC_IPV4=10.0.2.13
COREOS_PRIVATE_IPV4=172.17.8.101
来自集群另一端的Hello World
- Fleet的Unit文件
在服务管理方面Fleet使用和Systemd相似的Unit文件进行配置。不同的地方在于,Fleet额外支持一个X-Fleet配置段,用于指定服务可以在哪些节点上运行。例如下面这个Unit文件。
[Unit]
Description=Hello World
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker kill busybox1
[X-Fleet]
# Don't schedule on the same machine as other hello instances
X-Conflicts=hello*.service
最后的X-Fleet 配置段的 X-Conflicts 指定了这个 Hello 服务不能运行在“任意已经分配了任何名字以hello开头的服务”的节点上。关于X-Fleet配置段的其他选项,我们会在后面单独的一篇文章介绍Unit配置文件时再详细说明。
- 在集群上运行服务
由于Fleet需要在集群层面上对服务进行管理,因此它的服务管理流程与Systemd略有不同。最明显的两个区别是:Fleet没有指定Unit文件必须放置在哪些目录下,而是直接通过参数的方式告诉fleetctl命令。使用上面的内容在用户的主目录下创建hello.service文件,然后来通过fleetctl start启动这个服务。
$ fleetctl start ${HOME}/hello.service
Unit hello.service launched on 0acdd9bf.../10.0.2.15
可以看到在运行完start命令后,fleetctl返回了实际运行服务的节点IP,这个节点可能和执行fleetctl的节点不是同一个。之后通过 fleetctl list-units 可以看到Hello服务已经在这个节点上运行了。
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
hello.service 0acdd9bf.../10.0.2.15 active running
集群上的服务生命周期
刚刚的启动流程看起来很简单,不是么?在实际的使用中,如果为了省事,用Fleet启动一个服务,这样做就可以了。但这种做法其实会带来的服务管理上的麻烦,特别当启动出错的时候。因此比较全面的理解Fleet在背后默默做了哪些工作,详细的了解一下Fleet模型中,集群上的服务生命周期是很有必要的。
下面这个图描述了Fleet中的服务从初始到运行,最后终止结束的完整过程中实际存在的几个阶段。![](http://cms.csdnimg.cn/article/201501/14/54b6066461f2f.jpg)
- 提交服务
服务的提交阶段,这个步骤仅仅是在Fleet服务中完成的,目的是将指定的Unit文件添加到Fleet的记录缓存。此时Fleet并不会与Systemd进行通信。通过 fleetctl list-unit-files 和 fleetctl list-units 命令可以看到,Unit文件被提交后,并没有出现在后者的记录中。此时这个Unit文件已经被注册为一个Fleet可识别的Unit名称,但还不是一个可以执行的的服务。
$ fleetctl submit ${HOME}/hello.service
$ fleetctl list-unit-files
UNIT HASH DSTATE STATE TARGET
hello.service 4bff33d inactive inactive -
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
值得指出的是,这一步需要将完整的Unit文件路径作为参数传递给fleetctl,这也是服务在集群的整个Fleet生命周期中唯一一次需要提供完整路径的地方。
使用 fleetctl cat 命令可以打印出已经缓存了的Unit文件内容。
$ fleetctl cat hello.service和Systemd相似的,这里同样有一个容易犯错地方。由于Fleet已经缓存了整个Unit文件,之后如果原来的Unit文件内容发生改变,单纯的停止并重新启动服务并不会加载新变化的内容。此时若是需要让新的Unit文件被使用,需要重新提交这个文件,也就是再次执行这个submit操作。因此,这里再次强调使用者应该对Fleet管理服务的生命周期有一定了解。
[Unit]
Description=Hello World
After=docker.service
Requires=docker.service
[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker kill busybox1
[X-Fleet]
X-Conflicts=hello.service
- 加载服务
加载服务实际上是根据Unit文件的 X-Fleet 配置段条件,将服务传递到符合条件的特定节点的本地Systemd系统的过程,这个过程中Fleet通过DBus API与节点的Systemd进行了通信。
$ fleetctl load hello.service可以看到,现在 fleetctl list-units 已经识别到这个服务了。至此,这个服务的准备工作已经全部就绪。
Unit hello.service loaded on 0acdd9bf.../110.0.2.15
$ fleetctl list-unit-files
UNIT HASH DSTATE STATE TARGET
hello.service 4bff33d loaded loaded 0acdd9bf.../10.0.2.15
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
hello.service 0acdd9bf.../10.0.2.15 failed failed
- 启动服务
启动服务是完成的服务运行周期的最后一个步骤。如之前所提过的,这里如果传递的参数不是服务的名称而是Unit文件的路径,Fleet也会自动完成Unit文件的提交和加载,并启动相应服务。这种做法仅仅在手工操作时可取,若是在自动操作脚本中,假如服务启动出现异常,单纯的从脚本的输出中就很难判断是哪个环节出了问题,会带来管理和排查的困难。
$ fleetctl start hello.service
Unit hello.service launched on 0acdd9bf.../10.0.2.15
$ fleetctl list-unit-files
UNIT HASH DSTATE STATE TARGET
hello.service 4bff33d launched launched 0acdd9bf.../10.0.2.15
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
hello.service 0acdd9bf.../10.0.2.15 activating start-pre
刚刚启动的服务会处于start-pre状态(服务正在执行Unit文件中的ExecStartPre部分操作),几分钟后再次查看服务的状态,服务状态就会变为running了。
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
hello.service 0acdd9bf.../10.0.2.15 active running
- 停止服务
服务不能无休止的运行,终归有需要停止的时候。服务的停止同样会经过三个过程,依次为stop、unload和destroy,其本质上与启动过程一一对应。
$ fleetctl stop hello.service
Unit hello.service loaded on 0acdd9bf.../10.0.2.15
$ fleetctl unload hello.service
Unit hello.service inactive
$ fleetctl destroy hello.service
Destroyed hello.service
服务移除后系统又回到了初始的状态。
$ fleetctl list-unit-files
UNIT HASH DSTATE STATE TARGET
$ fleetctl list-units
UNIT MACHINE ACTIVE SUB
- 服务自动启动
在一边阅读一边实际操作的读者可能已经发现,Fleet的管理工具fleetctl没有enable和disable这两个操作。实际上,只要服务通过fleetctl start运行起来以后,就已经是自动启动的了。可以通过运行节点本地的Systemd证实(在做这个验证前最好移除之前通过Systemd添加的本地Hello服务,以免影响结果可信度)。
$ fleetctl ssh hello # 跳转到运行Hello服务的节点
$ systemctl list-units | grep hello
hello2.service loaded active running Hello World
$ systemctl list-unit-files | grep hello
hello2.service enabled
服务状态和日志
Fleet同样具有跨节点查看服务状态和日志的能力。通过fleetctl status加上服务的名称就能查看服务的基本状态,而不用关心服务运行在哪一个节点上面。
$ fleetctl status hello.service
...
Are you sure you want to continue connecting (yes/no)? yes <= 第一次使用的时候需要确认
● hello.service - Hello World
...
Main PID: 4964 (docker)
CGroup: /system.slice/hello.service
└─4964 /usr/bin/docker run --name busybox1 busybox /bin/sh -c while true; do echo Hello World; sleep 1; done
Jan 10 08:58:07 core-03 docker[4964]: Hello World
Jan 10 08:58:08 core-03 docker[4964]: Hello World
Jan 10 08:58:09 core-03 docker[4964]: Hello World
...
同样的方式可以查看到任意节点上查看服务输出的日志,相应的命令是 fleetctl journal 。顺带一提,当服务程序使用Systemd/Fleet的方式管理后,其运行过程中输出到std::out 的内容都会被重定向到日志中。
$ fleetctl journal hello.service
-- Logs begin at Sat 2015-01-10 02:41:59 UTC, end at Sat 2015-01-10 08:58:56 UTC. --
Jan 10 08:58:46 core-03 docker[4964]: Hello World
Jan 10 08:58:47 core-03 docker[4964]: Hello World
Jan 10 08:58:48 core-03 docker[4964]: Hello World
...
fleetctl fournal常用的参数有:指定输出日志行数的 --lines 和跟随日志输出的 --follow (或-f) 。
fleetctl journal --lines 20 hello.service
fleetctl journal -f hello.service
小结
在这一篇中,我们详细了解了在CoreOS集群中通过Fleet查看和管理集群的操作。CoreOS中的Fleet服务通过Etcd(一种分布式数据存储服务)获得集群的服务信息并通过DBus接口操作Systemd控制集群中任意节点的服务状态,在CoreOS集群上提供了跨节点,分布式,高容错的服务部署和管理能力。
在下一篇中,我们会深入CoreOS的另一个服务模块,Fleet幕后的数据功臣——Etcd的使用。敬请期待。(作者/林帆 责编/周小璐)
系列链接:
【相关活动】 Docker上海Meetup活动将在2015年01月18日于UCloud摩尔会议室举行,演讲内容鞥富:联科集团的联席合伙人吴秉宗博士的《应用程序容器“Docker”与基础设施容器“Joyent”SmartMachines 的融合》、点融网运维技术主管万林涛 《Docker在Devops流程中的使用(以点融p2p互联网金融应用为例)》、Cisco高级工程师杨章显《Kubernetes系统架构介绍(Kuberenetes各核心部件的实现原理及其协同工作)》,以及UCloud高级技术专家罗成对《Docker与数据库的应用结合》。报名地址:http://huiyi.csdn.net/activity/product/goods_list?project_id=1646
如需要了解更多Docker相关的资讯或是技术文档可访问Docker技术社区;如有更多的疑问请在Dcoker技术论坛提出,我们会邀请专家回答。CSDN Docker技术交流QQ群:303806405。