如何在CoreOS集群上创建并运行服务

来源:互联网 发布:新浪二手房经纪人端口 编辑:程序博客网 时间:2024/05/09 02:49

提供:ZStack云计算

系列教程

本教程为《CoreOS上手指南》系列九篇中的第五篇。

内容简介

CoreOS的一大核心优势在于,其能够立足于一点跨越完整集群实现服务管理。CoreOS平台提供多种集成化工具以简化这一工作流程。

在本篇教程中,我们将演示将服务运行于CoreOS集群之上的典型工作流程。这一流程将涉及CoreOS当中最具吸引力的几项机制与功能,而这些也成为简化应用程序设置的关键所在。

先决条件与目录

为了推进本篇教程,大家至少需要配置三台设备以构建Core集群。各位可以参阅CoreOS集群构建指南以完成准备工作。

在本示例中,这三个节点将分别为:

  • coreos-1
  • coreos-2
  • coreos-3

这三个节点应当利用其专有网络接口对其etcd客户端地址、对端地址以及集群地址进行设置。相关cloud-config文件的具体设置方式请参见前面提到的教程。

在这里,我们将遵循各基本步骤以将服务运行在CoreOS集群之上。为了便于演示,我们将设置一套简单的Apache Web服务器。我们将利用Docker设置一套容器化服务环境,而后创建一个systemd类型的单元文件,用以描述该项服务及其运行参数。

在另一个单元文件内,我们可以告知本服务如何注册至etcd,从而允许其它服务追踪本服务的细节信息。我们还需要将两项服务提交至集群,并在这里通过集群在对应设备上实现服务启动与管理。

接入一个节点并发送SSH代理

我们首先需要通过配置让服务利用SSH接入其中一台节点。

为了保证fleetctl工具能够起效以实现相邻节点间的通信,我们需要在连接过程中传递SSH代理信息。

在通过SSH进行连接之前,大家必须启动自己的SSH代理。如此一来,我们就能将证书转发至正在接入的服务器处,从而利用该设备登录其它节点。输入以下命令以启动设备上的用户代理:

eval $(ssh-agent)

接下来通过以下命令将专有密钥添加到该代理的内存存储当中:

ssh-add

现在我们的SSH代理就已经开始运行并获得了正确的专有SSH密钥。下面接入集群中的某台节点,而后转发我们的SSH代理信息。大家可以使用-A标记实现这一目标:

ssh -A core@coreos_node_public_IP

在接入一台节点后,我们即可以着手构建服务本身了。

创建Docker容器

我们首先需要创建运行服务所需要的Docker容器。大家可以采取两种方式:其一为手动创建Docker容器并加以配置,其二为创建一个Dockerfile用以描述构建所需镜像的全部必要步骤。

在本示例中,我们将利用第一种更为直观的方式构建镜像。大家可以参阅 利用Dockerfile构建Docker镜像以了解更多相关信息。我们的目标是利用Docker以Ubuntu 14.04为基础创建一套安装有Apache的镜像。

在开始之前,我们首先需要登录Docker Hub注册表。输入以下命令:

docker login

这时我们需要提供用户名、密码以及电子邮箱地址。如果大家是第一次使用,则必须提供准确信息并点击邮箱收到的验证链接。如果大家已经拥有账户,则利用已有凭证登录即可。

要创建这套镜像,首先需要利用我们使用的基础镜像启动一套Docker容器。具体命令为:

docker run -i -t ubuntu:14.04 /bin/bash

其中的具体参数解释如下:

  • run: 告知Docker我们希望利用以下参数启动一套容器。
  • -i: 以交互模式启动Docker容器。这将确保STDIN可用于该容器环境——但是否添加可按实际需求选择。
  • -t: 创建一个伪终端,允许我们利用终端访问该容器环境。
  • ubuntu:14.04: 这是是我们希望运行的库与镜像结合体。在本示例中,我们将运行Ubuntu 14.04。该镜像来自 Docker Hub中的Ubuntu Docker库。
  • /bin/bash: 这是我们希望在容器中运行的命令。由于我们希望实现终端访问,因此需要生成一条shell会话。

基础镜像层将取自Docker Hub的在线Docker注册表,同时启动一条bash会话。这时我们的当前进程正是该shell会话。

在这里,我们可以进一步创建服务环境。首先是安装Apache Web稆,因此我们应当更新本地软件包目录并利用apt命令完成安装:

apt-get updateapt-get install apache2

在安装结束后,我们可以对其默认index.html文件进行编辑:

echo”<h1>Running from Docker on CoreOS</h1>” > /var/www/html/index.html

完成之后,大家可以退出bash会话:

exit

现在回到主机设备,大家需要获取该Docker容器的容器ID。我们可以通过以下命令要求Docker显示其最新进程信息:

docker ps -lCONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMEScb58a2ea1f8f        ubuntu:14.04"/bin/bash"8 minutes ago       Exited (0) 55 seconds ago                       jovial_perlman

我们需要的是“CONTAINER ID”一列。在以上示例中,其ID为cb58a2ea1f8f。为了能够在之后重新启动该容器并应用做出的全部变更,我们需要将各项变更提交至对应用户名的库中,同时为该镜像选定一个名称。

在本示例中,我们将用户名设定为user_name,但大家需要将其替换成自己的实际Docker Hub账户名称。我们将自己的镜像命名为apache。提交镜像变更的命令为:

docker commit container_IDuser_name/apache

这条命令将保存镜像,帮助我们在需要时随时回调容器的当前状态。各位可以通过以下命令进行验证:

docker imagesREPOSITORY           TAG                 IMAGE ID            CREATED             VIRTUAL SIZEuser_name/apache     latest              42a71fb973da        4 seconds ago       247.4 MBubuntu               14.04               c4ff7513909d        3 weeks ago         213 MB

接下来,我们应当将该镜像发布至Docker Hub,这样节点就能够正常获取并运行该镜像了。具体命令如下:

docker push user_name/apache

现在我们已经拥有了Apache实例所需要的容器镜像。

创建Apache Service Unit文件

现在我们已经拥有可用的Docker容器了,接下来开始构建服务文件。

Fleet管理着整套CoreOS集群中的服务调度工作。其为用户提供一套集中化界面,同时亦通过本地方式操作每台主机的systemd init系统以完成必要的执行活动。

用于定义每项服务之具体属性的各文件基本上就是稍加修改过的systemd unit文件。如果大家曾经使用过systemd,应该不会对其相关语法感到陌生。

我们首先在主目录下创建一个apache@.service的文件。其中的@表示其属于模板服务文件。CoreOS镜像中自带vim文件编辑器:

vim apache@.service

要进行服务定义,我们首先创建一个[Unit]分标题,并为该unit设置部分元数据。我们需要添加描述并指定关联性信息。由于我们的两个unit将需要同时运行在etcd与Docker当中,因此需要对相关要求做出定义。

我们还需要添加另一个服务文件,将利用它容纳具体要求。这第二个服务文件将负责利用服务信息对etcd进行更新。将其添加至要求中能够确保服务启动时这个文件也随之一同运行。我们稍后再来解释服务名称中%i的含义:

[Unit]Description=Apache web server serviceAfter=etcd.serviceAfter=docker.serviceRequires=apache-discovery@%i.service

接下来我们需要告知系统,此unit在启动或者停止时需要执行哪些操作。这些任务可通过[Service]部分实现,毕竟咱们设置的就是服务嘛。

我们首先需要禁用该服务在超时情况下进行重启的设定。由于我们的服务属于Docker容器,因此每当在各主机上进行首次启动时,相关镜像都需要自Docker Hub服务器处获取——这可能会带来较正常情况更长的启动时间。

我们还需要将KillMode设置为“none”,这样systemd将允许我们利用“stop”命令以关闭该Docker进程。如果不加修改,那么systemd会在我们调用stop命令时认为该Docker进行发生了错误。

确保这套环境会优先启动我们的服务。这一点非常重要,因为我们需要利用名称对服务进行引用,而Docker只允许一套容器以惟一名称加以运行。

我们还需要关闭一切与所需要容器重名的容器并将其移除。这一过程与自Docker Hub处获取镜像同时发生。我们也需要核定/etc/environment文件的来源,其中包含运行当前服务之主机的公共与专有IP地址等变量:

[Unit]Description=Apache web server serviceAfter=etcd.serviceAfter=docker.serviceRequires=apache-discovery@%i.service[Service]TimeoutStartSec=0KillMode=noneEnvironmentFile=/etc/environmentExecStartPre=-/usr/bin/docker kill apache%iExecStartPre=-/usr/bin/docker rm apache%iExecStartPre=/usr/bin/docker pull user_name/apache

其中ExecStartPre行内的=-语法代表,这两行准备可以失败,而unit文件仍能够继续运行。由于这些命令只能在容器名称存在时成功运行,因此容器不存在时其需要正常失败。

大家可能注意到了该apache容器名称结尾的%i字样。我们创建的这个服务文件实际上属于 unit文件模板。这意味着在运行该文件时,fleet将自动利用正确值替换其中的部分信息。

在本示例中,文件中的全部%i都将被替换为.service后缀文件中@前面的部分。不过我们的文件名比较简单,为apache@.service。

虽然我们利用apache@.service名称将该文件提交至flletctl,但在载入该文件时,我们需要将其加载为apache@PORTNUM.service——其中”PORT_NUM”代表我们启动此服务器所使用的端口。我们会根据该端口标记服务的运行位置,从而轻松对其加以区分。

接下来启动该Docker容器:

[Unit]Description=Apache web server serviceAfter=etcd.serviceAfter=docker.serviceRequires=apache-discovery@%i.service[Service]TimeoutStartSec=0KillMode=noneEnvironmentFile=/etc/environmentExecStartPre=-/usr/bin/docker kill apache%iExecStartPre=-/usr/bin/docker rm apache%iExecStartPre=/usr/bin/docker pull user_name/apacheExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80 user_name/apache /usr/sbin/apache2ctl -D FOREGROUND

我们要求该docker容器运行命令并向其提供几条参数。这里发送的名称格式与之前提到的相同。我们还需要在Docker容器内向主机设备的公共接口发布一个端口。该主机设备的端口编号将取自%i变量,这意味着我们能够指定该端口。

我们将使用COREOS_PUBLIC_IPV4变量(来自我们之前进行过溯源的环境文件)以声明所要绑定的主机接口。这里不声明亦可,但做出声明能够帮助我们在未来将其变更为专有接口(例如需要进行负载均衡)时更轻松地完成修改。

我们引用此前上传至Docker Hub的Docker容器。最后,我们调用该命令以在容器环境下启动这项Apache服务。只要与之对应的命令退出,该Docker容器就会关闭——我们希望将服务运行在前台中,而非守护进程中。通过这种方式,我们的容器就能够持续运行,而不会在生成子进程后退出。

接下来,我们需要在服务需要停止时指定该命令进行调用。这样容器就会被正常关闭。每次重启时,容器都会被清空。

我们还需要添加名为[X-Fleet]的部分。这部分内容专门用于向fleet发送指令以实现服务调度。在这里,大家可以添加一些约束条件,从而根据其它服务或者设备本身的状态设定本服务必须运行或者不可运行。

我们希望这项服务只运行在尚未运行Apache Web服务器的主机之上,因为这能保证我们更轻松地创建高可用性服务。下面我们将使用一条通配符来捕捉一切可能正在运行的apahce服务文件:

[Unit]Description=Apache web server serviceAfter=etcd.serviceAfter=docker.serviceRequires=apache-discovery@%i.service[Service]TimeoutStartSec=0KillMode=noneEnvironmentFile=/etc/environmentExecStartPre=-/usr/bin/docker kill apache%iExecStartPre=-/usr/bin/docker rm apache%iExecStartPre=/usr/bin/docker pull user_name/apacheExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80user_name/apache /usr/sbin/apache2ctl -D FOREGROUNDExecStop=/usr/bin/docker stop apache%i[X-Fleet]X-Conflicts=apache@*.service

这样一来,Apache服务器的unit文件就设置完成了。接下来创建一个与之配合的服务文件,用于将该服务注册至etcd。

将服务状态注册至ETCD

为了记录该服务自启动后的状态,我们需要在etcd当中写入一些条目——也就是所谓注册至etcd。

要实现这项目标,我们将启动一个最小化配合服务,其负责在服务器可接收流量时对etcd进行更新。

这个新的服务文件将被命名为apache-discovery@.service。将其打开:

vim apache-discovery@.service

我们首先来看[Unit]部分。与之前提到的一样,我们需要在这里描述该服务的作用,而后设置一条名为BindsTo的指令。

BinddsTo指令用于标记该服务获取状态信息时所需要的关联性。如果被列入其中的服务已经停止,那么我们所编写的这个unit文件将同时停止。通过这种方式,一旦我们的Web服务器发生意外故障,此服务将更新etcd以反映此信息。另外,这也防止了etcd中的状态信息被其它服务错误使用的潜在可能性:

[Unit]Description=Announce Apache@%i serviceBindsTo=apache@%i.service

在[Service]部分,我们希望利用主机IP地址信息对该环境文件进行重新溯源。

为了启动这项命令,我们需要运行一个简单的无限bash循环。在该循环中,我们将使用etcdctl命令,其用于修改etcd值,并在存储于/announce/services/apache%i处的etcd中设定一个key。其中%i将被替换为我们所需载入的服务名称中@与.service后缀之间的部分,也就是该Apache服务的端口编号。

该key的值将被设定为该节点的公共IP地址与端口编号。我们还将为该值设定60秒有效期,这样该key就将在服务关闭后被移除。在此之后休眠45秒,由此实现的过期时效重叠能够保证TTL(即生存时间)值始终能在超时前得到更新。

要结束活动,我们可以直接利用etcdctl移除该key,并将该服务标记为不可用:

[Unit]Description=Announce Apache@%i serviceBindsTo=apache@%i.service[Service]EnvironmentFile=/etc/environmentExecStart=/bin/sh -c "while true; do etcdctl set /announce/services/apache%i ${COREOS_PUBLIC_IPV4}:%i --ttl 60; sleep 45; done"ExecStop=/usr/bin/etcdctl rm /announce/services/apache%i

最后一项任务则是添加一个条件,以确保此服务启动时所处的主机与其报告给Web服务器的结果相符。这样在该主机发生故障时,etcd信息将得到正确变更:

[Unit]Description=Announce Apache@%i serviceBindsTo=apache@%i.service[Service]EnvironmentFile=/etc/environmentExecStart=/bin/sh -c "while true; do etcdctl set /announce/services/apache%i ${COREOS_PUBLIC_IPV4}:%i --ttl 60; sleep 45; done"ExecStop=/usr/bin/etcdctl rm /announce/services/apache%i[X-Fleet]X-ConditionMachineOf=apache@%i.service

现在我们已经拥有了自己的配合服务,它能够在etcd当中正确记录Apache服务器的当前运行状态了。

与Unit文件与Fleet协作

现在我们已经拥有两套服务模板。大家可以将其直接提交至fleetctl当中,从而让集群意识到它们的存在:

fleetctl submit apache@.service apache-discovery@.service

通过以下命令,我们就能查看到自己的新服务文件:

fleetctl list-unit-filesUNIT                HASH    DSTATE      STATE       TMACHINEapache-discovery@.service   26a893f inactive    inactive    -apache@.service         72bcc95 inactive    inactive    -

这些模板如今已经存在于我们集群内的init系统中。

由于我们需要根据特定主机的调度情况使用这些模板,因此接下来需要进行文件载入。在这里,我们要利用端口编号为这些文件指定新的名称。如此一来,fleetctl就会在[X-Fleet]部分寻找具体调度要求。

由于我们不需要进行负载均衡,因此将Web服务器运行在端口80上即可。我们可以通过指定@与.service后缀之间的部分来载入每项服务:

fleetctl load apache@80.servicefleetctl load apache-discovery@80.service

这时我们已经能够了解到该服务被载入到了集群内的哪台主机之上:

Unit apache@80.service loaded on 41f4cb9a.../10.132.248.119Unit apache-discovery@80.service loaded on 41f4cb9a.../10.132.248.119

如大家所见,这两项服务被载入到同一台设备上,也就是我们所指定的主机。由于我们的apache-discovery服务文件与Apache服务进行了绑定,所以我们可以直接启动后者以实现两项服务的初始化:

fleetctl start apache@80.service

现在,如果大家查看集群内有哪些unit文件正在运行,则可获得以下结果:

fleetctl list-unitsUNIT                MACHINE             ACTIVE  SUBapache-discovery@80.service 41f4cb9a.../10.132.248.119  active  runningapache@80.service       41f4cb9a.../10.132.248.119  active  running

看起来我们的Web服务器已经启动并开始运行了。在服务文件中,我们要求Docker绑定至该主机服务器的公共IP地址。但fleetctl中显示的IP为专有地址(这是因为我们创建这套示例集群时,在cloud-config文件中使用了$private_ipv4)。

不过我们已经将该公共IP地址与端口编号注册至etcd,因此要获取对应值,可以使用etcdctl以查询此前设定的值。大家应该还记得,我们此前设置的key为/announce/services/apachePORT_NUM。要获取服务器细节信息,可以使用以下命令:

etcdctl get /announce/services/apache80104.131.15.192:80

通过浏览器访问此页面时,大家应该看到如下所示的简单页面:

到这里我们的服务就已经部署成功了。下面尝试利用其它端口载入另一个实例。按照预期,该Web服务器与相关配合容器将被调度至同一主机。然而由于我们在Apache服务文件中加入了约束条件,因此实际承载新实例的主机应该不同于承载端口80服务的主机。

下面将新服务运行在端口9999上:

fleetctl load apache@9999.service apache-discovery@9999.serviceUnit apache-discovery@9999.service loaded on 855f79e4.../10.132.248.120Unit apache@9999.service loaded on 855f79e4.../10.132.248.120

可以看到两项新服务都被调度到了同一台新主机上。启动此Web服务器:

fleetctl start apache@9999.service

现在我们可以看到这台新主机的公共IP地址了:

etcdctl get /announce/services/apache9999104.131.15.193:9999

如果我们访问其指定的地址与端口编号,则应该能看到另一套Web服务器:

到这里,我们的集群已经部署有两套Web服务器。

如果大家关闭其中一套Web服务器,则与之匹配的容器也将同时关闭:

fleetctl stop apache@80.servicefleetctl list-unitsUNIT                MACHINE             ACTIVE      SUBapache-discovery@80.service 41f4cb9a.../10.132.248.119  inactive    deadapache-discovery@9999.service   855f79e4.../10.132.248.120  active  runningapache@80.service       41f4cb9a.../10.132.248.119  inactive    deadapache@9999.service     855f79e4.../10.132.248.120  active  running

通过检查,大家会发现其etcd key也被一并移除:

etcdctl get /announce/services/apache80Error:  100: Key not found (/announce/services/apache80) [26693]

圆满成功,所有结果都符合预期。

总结

通过今天的教程,大家应该已经熟悉了CoreOS各组件的常见用法。

我们已经立足于需要运行的服务构建起自己的Docker容器,同时创建了fleet unit文件以告知CoreOS如何管理这些容器。另外,我们还实现了一项匹配服务,负责利用Web服务器的状态信息对etcd数据存储内容进行更新。总而言之,大家已经了解了如何利用fleetctl调度服务在不同主机上实现服务管理。

本文来源自DigitalOcean Community。英文原文:How To Create and Run a Service on a CoreOS Cluster by Justin Ellingwood

翻译:diradw

0 0
原创粉丝点击