标签:成员 字符串 引用 environ bool 状态 dev function 检索
提供:ZStack云计算
本教程为CoreOS上手指南系列九篇中的第六篇。
CoreOS能够利用一系列工具以集群化与Docker容器化方式简化服务管理工作。其中etcd负责将各独立节点联系起来并提供全局数据平台,而大部分实际服务管理任务则由fleet守护进程实现。
在上一篇教程中,我们了解了如何利用fleetctl命令操纵服务及集群成员。在今天的教程中,我们将了解如何利用单元文件定义服务。
在接下来的内容中,我们将探讨如何构建fleet单元文件,外加在生产环境下提升服务健壮性的可行方法。
要完成本篇教程,大家首先需要拥有一套可用CoreOS集群。
在之前的系列教程如何利用DigitalOcean创建一套CoreOS集群当中,我们已经完成了集群创建工作。
此集群配置包含三个节点。它们能够利用专有网络接口实现彼此通信。这三台节点亦拥有公共接口以运行公共服务。各节点名称分别为:
虽然本教程中的大部分内容在于介绍单元文件的创建,但上述设备也将在后文说明特定指令的调度作用时被用到。
这里还建议大家参阅如何使用fleetctl一文。只有具备了fleetctl的相关知识,大家才能提交并在集群当中使用这些单元文件。
满足上述要求后,我们这就开始今天的学习之旅。
由于fleet服务管理机制在很大程度上依靠本地系统的systemd init系统,因此systemd单元文件自然被用于定义各项服务。
除了服务类型之外,还有其它多种单元类型定义,且通常被作为systemd单元文件中的一个子集。每种类型都会通过一段文件后缀进行类型定义,例如example.service:
尽管大家可以随意选择上述选项,但service单元仍是最为常用的条目。在本教程中,我们将单纯探讨service单元的配置。
单元文件为简单的文本文件,且结尾为“.”加上以上后缀之一。文件内容由多处区段组成。在fleet当中,大部分单元文件将采用以下基本格式:
[Unit]generic_unit_directive_1generic_unit_directive_2[Service]service_specific_directive_1service_specific_directive_2service_specific_directive_3[X-Fleet]fleet_specific_directive
区段标题及单元文件中的其它内容皆区分大小写。其中[Unit]用于定义单元的常规信息。以上与各单元类型相关的选项皆可添加在这里。
[Service]区段用于设定指向各服务单元的指令。大多数(但并非全部)单元类型都与unit-type-specific区段信息相关联。大家可以参阅常见systemd单元文件man页面以了解更多与不同单元类型相关的细节。
[X-Fleet]区段用于设定该单元的调度要求以供fleet使用。利用此区段,大家可以要求某特定条件为御前
以在主机上实现单元调度。
在这一区段中,首先对在CoreOS上运行服务中使用过的单元文件进行调整。该文件名为apache.1.service,且内容如下:
[Unit]Description=Apache web server service
# Requirements
Requires=etcd.serviceRequires=docker.serviceRequires=apache-discovery.1.service
# Dependency ordering
After=etcd.serviceAfter=docker.serviceBefore=apache-discovery.1.service[Service]
# Let processes take awhile to start up (for first run Docker containers)
TimeoutStartSec=0
# Change killmode from "control-group" to "none" to let Docker remove
# work correctly.
KillMode=none
# Get CoreOS environmental variables
EnvironmentFile=/etc/environment
# Pre-start and Start
## Directives with "=-" are allowed to fail without consequence
ExecStartPre=-/usr/bin/docker kill apacheExecStartPre=-/usr/bin/docker rm apacheExecStartPre=/usr/bin/docker pull username/apacheExecStart=/usr/bin/docker run --name apache -p ${COREOS_PUBLIC_IPV4}:80:80 \username/apache /usr/sbin/apache2ctl -D FOREGROUND
# Stop
ExecStop=/usr/bin/docker stop apache[X-Fleet]
# Don‘t schedule on the same machine as other Apache instances
X-Conflicts=apache.*.service
我们首先从[Unit]区段入手。在这里,我们的基本思路是描述该单元并添加关联性信息。首先设定相关要求。在本示例中,我们将使用部分硬性约束条件。如果我们希望fleet尝试启动额外服务,但又不会因故障而导致流程中断,则可使用Wants指令。
之后,我们需要明确列出要求的先后顺序。这一点非常重要,因为某些要求需要以特定服务正在运行为前提。另外,我们也可以在这里自动利用etcd声明我们将要构建的服务。
在[Service]区段中,我们关闭服务启动超时机制。由于服务首次在主机上运行时,容器需要自Docker注册表中提取信息,而这往往会造成启动超时。其默认时长为90秒,一般来说应该是足够的,但较为复杂的容器可能需要更长的启动时间。
而后将killmode设置为none。这是因为正常的关闭模式(control-group)有时候会导致容器移除命令失效(特别是Docker的–rmoption)。这有可能在下一次重启时带来问题。
我们可以在此环境文件中找到COREOS_PUBLIC_IPV4以及COREOS_PRIVATE_IPV4环境变量(如果创建过程中启用了专有网络机制)。我们可以利用其特定主机信息轻松配置Docker容器。
ExecStartPre各行用于清空此前尚未运行的部分以实现执行环境清理。我们可以在前两行中使用=-以确保出现问题时,systemd会忽略错误并继续执行后续命令。这一点非常重要,因为我们的预启动操作基本上清除了任何先前正在运行的服务。如果找不到已经在运行的服务,则操作自然会失败——但由于其仅仅属于清理流程,因此我们不希望其影响到服务的正常执行。最后的pre-start用于确保容器始终运行最新版本。
这条启动命令会引导Docker容器并将其与主机设备的公共IPv4接口相绑定。其使用此环境文件中的信息并轻松实现接口与端口交换。这一流程采用前台运行方式,这是因为该容器会在运行中进程结束后退出。而stop命令则尝试对容器进行正常关闭。
[X-Fleet]区段包含一条简单状态,用于强制fleet在尚未运行其它Apache服务的主机上调度该服务。这是一种简单的服务高可用性实现方式,即强制要求服务启动在不同设备上。
在以上示例中,我们已经进行了一些比较基础的配置。不过还有其它一些需要关注的重点。
下面来看构建主服务中需要注意的几点:
现在我们已经掌握了如何构建主服务,接下来需要了解传统的“sidekick”服务。这些sidekick服务与主服务相关联,且可通过etcd作为注册服务的外部点。
这个被引用于主单元文件中的文件名为apache-discovery.1.service,内容如下:
[Unit]Description=Apache web server etcd registration
# Requirements
Requires=etcd.serviceRequires=apache.1.service
# Dependency ordering and binding
After=etcd.serviceAfter=apache.1.serviceBindsTo=apache.1.service[Service]
# Get CoreOS environmental variables
EnvironmentFile=/etc/environment
# Start
## Test whether service is accessible and then register useful information
ExecStart=/bin/bash -c ‘while true; do curl -f ${COREOS_PUBLIC_IPV4}:80; if [ $? -eq 0 ]; then etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \‘{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": 80}\‘ --ttl 30; else etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}; fi; sleep 20; done‘
# Stop
ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}[X-Fleet]
# Schedule on the same machine as the associated Apache service
X-ConditionMachineOf=apache.1.service
Sidekick服务的实现方式与主服务基本一致。我们首先描述该单元的作用,而后为其提供关联性信息与排序逻辑。
这里要用到的新命令为BindsTo=。此命令会使该单元逐步执行启动、停止以及重启操作。基本上,这意味着我们能够在两个单元被载入至fleet当中后,通过操纵主单元同时管理这两个单元。这是一种单向机制,所以对sidekick单元进行控制不会影响到主单元。
在[Service]区段中,我们再次查找/etc/environment文件中的变量。此时ExecStart=指令基本上属于一条简短的bash脚本。它会尝试利用已经声明的接口与端口接入主服务。
如果连接成功,那么etcdctl命令会在etcd内的/services/apache中利用主机设备的公共IP地址设定一个键。该键的值为JSON对象,其中包含与该服务相关的信息。此键的过期时长为30秒,因此如果该单元意外停止,则对应服务信息亦不会驻留在etcd中。如果连接失败,那么该键会被立即移除——这是因为该服务无法被验证为可用。
此循环中包含一条20秒的sleep命令。这意味着每20秒(早于etcd的30秒键超时设定),此单元就会重新检查其主单元是否可用并重置该键。这基本上相当于对键上的TTL进行刷新,使其在接下来30秒中继续生效。
在这种情况下,stop命令只用于手动移除该键。意味着该服务注册将在主单元的stop命令由于BindsTo=指令而被镜像至此单元时被移除。
在[X-Fleet]区段中,我们需要确保此单元与主单元启动在同一台服务器上。尽管这套方案不允许该单元向远程设备报告服务可用性,但在本示例中,最重要的是确保BindsTo=指令正常起效。
在构建sidekick单元时,我们应当认真考虑以下几项要求:
掌握了以上几条要点,我们就能构建强大的注册单元,同时确保etcd拥有正常的信息可以使用。
虽然fleet单元文件与其它常见systemd单元文件并没有太大区别,但其中仍存在着一些值得关注的特性与陷阱。
其中最大的区别就是[X-Fleet]的存在,其可用于指引fleet制定调度决策。具体可用选项包括:
这些额外指令允许管理员更加灵活且有效地定义服务在可用设备上的运行方式。我们需要对其进行预先评估,而后方可在fleetctl加载阶段中将其传递至特定设备的systemd实例中。
这就带来了fleet中需要注意的另一项要点。事实上,fleetctl工具并不会对单元文件内[X-Fleet]区段之外的关联性要求进行评估。这意味着fleet中的各单元可能在协作时引发某些有趣的问题。
具体来讲,当fleetctl工具采取必要步骤以将目标单元操作至所需状态时,其不会考虑到该单元的关联性需求。
因此,如果我们分别提交了主与sidekick单元,但尚未在fleet中完成加载,那么输入fleetctl start main.service将加载并尝试启动main.serivce单元。然而,由于sidekick.service单元尚未被加载,而fleetctl又不会在加载与启动过程中评估关联性,因此main.service单元会发生错误。这是因为一旦设备上的systemd实例开始处理main.service单元,将无法在关联性评估阶段找到sidekick.service。
为了避免这种情况,我们可以同时手动启动这些服务,而非领先BindsTo=指令将sidekick引入运行状态:
fleetctl start main.service sidekick.service
另一种方式则是确保sidekick单元一定会在主单元运行时进行加载。其载入过程由设备选择,而该单元文件则被提交至本地systemd实例当中。这样能够确保关联性得到满意,而BindsTo=指令也能够正常执行以启动该辅助单元:
fleetctl load main.service sidekick.service
fleetctl start main.service
因此当fleetctl命令报错时,大家可以从以上几个角度进行排查。
在使用fleet时,其最为强大的概念之一就是单元模板。单元模板依赖于systemd下一种名为“实例”的特性。它们属于实例化的单元,通过处理模板单元文件被创建在运行时当中。模板文件在很大程度上类似于常规单元文件,只是其中存在几处小小的修改。如果得到正确利用,其将发挥巨大作用。
模板文件可通过在文件名中使用@来标记。大多数常规服务的文件名格式为unit.service,而模板文件的名称格式则为unit@.service。
当某单元利用模板进行实例化时,其实例标记符将位于@与.service后缀之间。此标记符可由管理员任意指定:
unit@instance_id.service
此基础单元名称可通过%p标记符在单元文件之内进行访问。同样的,给定实例标记符则可通过%i进行访问。
这意味着我们无需像之前那样一步步创建apache.1.service主单元文件,而可以直接创建一套名为apache@.service的模板:
[Unit]Description=Apache web server service on port %i
# Requirements
Requires=etcd.serviceRequires=docker.serviceRequires=apache-discovery@%i.service
# Dependency ordering
After=etcd.serviceAfter=docker.serviceBefore=apache-discovery@%i.service[Service]
# Let processes take awhile to start up (for first run Docker containers)
TimeoutStartSec=0
# Change killmode from "control-group" to "none" to let Docker remove
# work correctly.
KillMode=none
# Get CoreOS environmental variables
EnvironmentFile=/etc/environment
# Pre-start and Start
## Directives with "=-" are allowed to fail without consequence
ExecStartPre=-/usr/bin/docker kill apache.%iExecStartPre=-/usr/bin/docker rm apache.%iExecStartPre=/usr/bin/docker pull username/apacheExecStart=/usr/bin/docker run --name apache.%i -p ${COREOS_PUBLIC_IPV4}:%i:80 \username/apache /usr/sbin/apache2ctl -D FOREGROUND
# Stop
ExecStop=/usr/bin/docker stop apache.%i[X-Fleet]
# Don‘t schedule on the same machine as other Apache instances
X-Conflicts=apache@*.service
如大家所见,我们将apache-discovery.1.service关联性修改为apache-discovery@%i.service。这意味着如果我们拥有此单元文件的一个名为apache@8888.service的实例,那么其将需要一个名为apache-discovery@8888.service的sidekick单元。其中的%i已经被替换为实例标记符。在这种情况下,我们使用该标记符代表与当前所运行服务相关的动态信息,特别是Apache服务器的可用端口编号。
为了实现这一目标,我们可以变更docker的运行参数,从而将该容器的端口声明至主机上的某个端口。在该静态单元文件中,我们使用的参数为
Docker名称本身也进行了修改,这样它也会使用基于实例ID的惟一容器名称。请注意,Docker容器无法使用@标记,因此我们必须在单元文件中选择其它名称。为此,我们修改了全部在Docker容器上运行的指令。
在[X-Fleet]区段中,我们还修改了调度信息以识别这些实例化单元,而非我们之前使用的静态单元。
我们也可以利用同样的方法将sidekick单元转化为模板。
我们的新sidekick单元将被命名为apache-discovery@.service,如下所示:
[Unit]Description=Apache web server on port %i etcd registration
# Requirements
Requires=etcd.serviceRequires=apache@%i.service
# Dependency ordering and binding
After=etcd.serviceAfter=apache@%i.serviceBindsTo=apache@%i.service[Service]
# Get CoreOS environmental variables
EnvironmentFile=/etc/environment
# Start
## Test whether service is accessible and then register useful information
ExecStart=/bin/bash -c ‘while true; do curl -f ${COREOS_PUBLIC_IPV4}:%i; if [ $? -eq 0 ]; then etcdctl set /services/apache/${COREOS_PUBLIC_IPV4} \‘{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\‘ --ttl 30; else etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}; fi; sleep 20; done‘
# Stop
ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PUBLIC_IPV4}[X-Fleet]
# Schedule on the same machine as the associated Apache service
X-ConditionMachineOf=apache@%i.service
我们也用同样的方式对sidekick单元中的内容进行了调整,从而构建这套实例化版本并保证其与正确的实例化主单元相匹配。
在curl命令中,当我们检查服务的实际可用性时,我们会利用实例ID替换静态端口80,从而保证其接入正确的位置。这一点非常重要,因为我们已经在Docker命令中对主单元的端口声明映射做出了变更。
我们还修改了“port”部分以登录至etcd,旨在保证其使用同样的实例ID。变更之后,被设定在etcd中的JSON数据将完全动态。其会提取主机名称、IP地址以及服务运行所在的端口。
最后,我们再次变更[X-Fleet]区段。我们需要确保此流程与主单元实例运行在同一台设备上。
要利用模板文件进行单元实例化,我们拥有几种不同选项。
其中fleet与systemd都能够处理符号链接,这意味着我们可以创建包含完整实例ID且指向模板文件的链接,具体如下:
ln -s apache@.service apache@8888.service
ln -s apache-discovery@.service apache-discovery@8888.service
这将创建两条链接,分别名为apache@8888.service与apache-discovery@8888.service。二者皆拥有fleet与systemd运行单元所必需的信息。不过,它们会指向回对应模板,因此我们需要再做点调整。
在此之后,我们可以利用以下fleetctl命令实现服务的提交、加载或者启动:
fleetctl start apache@8888.service apache-discovery@8888.service
如果我们不希望利用符号链接定义自己的实例,则可使用另一种fleetctl内的模板提交方式:
fleetctl submit apache@.service apache-discovery@.service
只需要将实例标记符分配给运行时,我们就能在fleetctl中以模板为基础实现单元实例化。例如,大家可以使用以下命令:
fleetctl start apache@8888.service apache-discovery@8888.service
这就消除了对符号链接的需求。部分管理员倾向于使用链接机制,因为这能保证实例文件随时可用。其同时允许我们将目录传递至fleetctl,从而一次性启动全部组件。
例如,在我们的工作目录中,大家可以为模板文件建立一个名为templates的子目录,并为实例化链接版本建立名为instances的子目录。大家甚至可以为非模板单元建立static子目录。具体命令如下:
mkdir templates instances static
而后将静态文件移动到static子目录下,而模板文件则移动对templates子目录下:
mv apache.1.service apache-discovery.1.service static
mv apache@.service apache-discovery@.service templates
在这里,大家可以创建自己需要的实例链接了。假设我们的服务运行在端口5555、6666与7777上:
cd instances
ln -s ../templates/apache@.service apache@5555.service
ln -s ../templates/apache@.service apache@6666.service
ln -s ../templates/apache@.service apache@7777.service
ln -s ../templates/apache-discovery@.service apache-discovery@5555.service
ln -s ../templates/apache-discovery@.service apache-discovery@6666.service
ln -s ../templates/apache-discovery@.service apache-discovery@7777.service
而后利用以下命令即可一次性启动全部实例:
cd ..
fleetctl start instances/*
非常简单,也非常快捷。
到这里,大家应该已经掌握了fleet单元文件的构建方法了。利用单元文件带来的动态特性,我们能够确保自己的服务始终得到均匀分布、拥有正确的关联性并利用etcd注册使用信息。
在下一篇文章中,我们将探讨如何配置自己的容器,从而使用通过etcd注册的信息。如此一来,我们就能够建立对实际部署环境的认识,并将请求传递至后端中的合适容器当中。
本文来源自DigitalOcean Community。英文原文:How to Create Flexible Services for a CoreOS Cluster with Fleet Unit Files By Justin Ellingwood
翻译:diradw
如何利用fleet单元文件为CoreOS集群创建高灵活性服务
标签:成员 字符串 引用 environ bool 状态 dev function 检索
原文地址:http://blog.csdn.net/zstack_org/article/details/54945019