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

CoreOS实现集群范围内的服务定义,可以轻松启动任何成员机器上的应用程序。在本指南中,我们将演示创建和运行自己的业务所需的基本工作流程。我们将建设安装我们的应用程序Docker容器,创建systemd式服务单位的文件,然后用车队调度和启动我们的服务。

介绍

CoreOS的一个主要优点是能够从单一点跨整个集群管理服务。 CoreOS平台提供集成工具,使此过程简单。

在本指南中,我们将演示在CoreOS集群上运行服务的典型工作流程。 这个过程将演示一些简单,实用的方法与一些CoreOS的最有趣的实用程序进行交互,以建立一个应用程序。

先决条件和目标

为了开始使用本指南,您应该有一个至少配置了三台机器的CoreOS集群。 您可以按照我们的指导中启动一个CoreOS集群在这里。

为了本指南的考虑,我们的三个节点将如下:

  • coreos-1
  • coreos-2
  • 核心-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来描述构建所需映像所需的步骤。

对于本指南,我们将使用第一种方法构建映像,因为它对于那些刚接触Docker的人来说更直接。 请点击此链接,如果您想了解更多关于如何从Dockerfile建立一个Docker形象 我们的目标是在Docker中的Ubuntu 14.04基本映像上安装Apache。

在开始之前,您需要登录或注册Docker Hub注册表。 为此,请键入:

docker login

系统将要求您提供用户名,密码和电子邮件地址。 如果这是您第一次执行此操作,将使用您提供的详细信息创建帐户,并将确认电子邮件发送到提供的地址。 如果您已经创建了过去的帐户,您将使用给定的凭据登录。

要创建图像,第一步是使用我们要使用的基本映像来启动Docker容器。 我们将需要的命令是:

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

我们在上面使用的参数是:

  • 运行 :这告诉我们要与下面的参数启动一个容器Docker。
  • -i:开始在交互模式下的Docker容器。 这将确保STDIN到容器环境将可用,即使它没有附加。
  • -t:这将创建一个伪终端,使我们能够在容器环境终端接入。
  • Ubuntu的:14.04:这是我们要运行库和图像组合。 在这种情况下,我们运行的是Ubuntu 14.04。 图像保持在中在Docker HubUbuntu的Docker库
  • /斌/ bash中 :这是我们要在容器中运行命令。 因为我们需要终端访问,我们需要产生一个shell会话。

基本图像层将从Docker Hub在线Docker注册表中下拉,并且bash会话将启动。 您将被删除到生成的shell会话。

从这里,我们可以创建我们的服务环境。 我们要安装Apache Web服务器,所以我们应该更新我们的本地包指数,并通过安装apt

apt-get update
apt-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 -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
cb58a2ea1f8f        ubuntu:14.04        "/bin/bash"         8 minutes ago       Exited (0) 55 seconds ago                       jovial_perlman

我们需要的列是“CONTAINER ID”。 在上面的例子中,这将被cb58a2ea1f8f 为了能够在所有更改后启动同一个容器,您需要将更改提交到您的用户名存储库。 您还需要为图像选择一个名称。

对于我们来说,我们会假装用户名是user_name ,但你应该跟你带着几分前登录的Docker Hub帐户名称代替这一点。 我们会打电话给我们的形象apache 提交图像更改的命令是:

docker commit container_ID user_name/apache

这将保存图像,以便您可以调用容器的当前状态。 您可以通过键入以下内容进行验证:

docker images
REPOSITORY           TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
user_name/apache     latest              42a71fb973da        4 seconds ago       247.4 MB
ubuntu               14.04               c4ff7513909d        3 weeks ago         213 MB

接下来,您应该将映像发布到Docker Hub,以便您的节点可以随意下拉并运行映像。 为此,请使用以下命令格式:

docker push user_name/apache

您现在已经使用Apache实例配置了容器映像。

创建Apache服务单元文件

现在我们有一个Docker容器可用,我们可以开始构建我们的服务文件。

Fleet管理整个CoreOS集群的服务调度。 它为用户提供了一个集中式接口,同时在本地操作每个主机的systemd init系统以完成相应的操作。

定义每个服务属性的文件是稍微修改的systemd单元文件。 如果你过去使用过systemd,你将会非常熟悉语法。

首先,创建一个名为apache@.service在你的home目录。 @表示这是一个模板服务文件。 我们将讨论一下这意味着什么。 该CoreOS图像自带的vim文本编辑器:

vim apache@.service

要启动该服务的定义,我们将创建一个[Unit]节标题,并设置有关本机的一些元数据。 我们将包括描述和指定依赖性信息。 因为我们的单元需要在etcd和Docker可用之后运行,我们需要定义该需求。

我们还需要添加我们将创建的其他服务文件作为要求。 这个第二个服务文件将负责更新etcd有关我们的服务的信息。 在这里需要它将强制它开始,当这个服务开始。 我们将解释%i在以后的服务名称:

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

接下来,我们需要告诉系统启动或停止本机时需要做什么。 我们这样做是在[Service]部分,因为我们要配置的服务。

我们要做的第一件事是禁用服务启动超时。 因为我们的服务是Docker容器,当它首次在每个主机上启动时,镜像将必须从Docker Hub服务器下拉,从而可能导致第一次运行时间超过通常的启动时间。

我们要设置KillMode为“无”,这样systemd将使我们的“停止”命令来杀死泊坞过程。 如果我们离开这个,systemd会认为Docker进程失败,当我们调用stop命令。

我们还希望在开始我们的服务之前确保我们的环境清洁。 这是特别重要的,因为我们将通过名称引用我们的服务,Docker只允许单个容器以每个唯一的名称运行。

我们将需要使用我们想要使用的名称杀死任何剩余容器,然后删除它们。 正是在这一点上,我们实际上从Docker Hub下拉图像。 我们要源/etc/environment文件中。 这包括变量,例如运行服务的主机的公共和专用IP地址:

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

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull user_name/apache

=-语法前两个ExecStartPre行表示正在拟订的线可以失败,并且单元文件还是会继续。 因为这些命令只有在具有该名称的容器存在时才会成功,如果找不到容器,它们将失败。

您可能已经注意到%iStapling在上面的指令apache的容器名称的结束。 我们正在创建的服务文件实际上是一个模板单元文件 这意味着在运行文件时,fleet将自动用适当的值替换一些信息。 阅读提供的链接上的信息,了解更多。

在我们的情况下, %i将任何地方替换它与服务文件的名称的对的右侧的部分的文件中存在@的之前.serviceStapling。 我们的文件被简单地命名为apache@.service虽然。

虽然我们将提交文件fleetctlapache@.service ,当我们加载该文件,我们会加载它作为apache@ PORT_NUM .service ,其中“PORT_NUM”将是我们要启动此服务器的端口。 我们将根据将运行的端口标记我们的服务,以便我们可以轻松地区分它们。

接下来,我们需要真正启动实际的Docker容器:

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

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull user_name/apache
ExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80 user_name/apache /usr/sbin/apache2ctl -D FOREGROUND

我们把传统的docker run命令,并通过它的一些参数。 我们通过它的名称与我们上面使用的相同的格式。 我们还要将一个端口从我们的Docker容器暴露到我们的主机的公共接口。 主机的端口号将从取%i变量,它是实际允许我们指定的端口。

我们将使用COREOS_PUBLIC_IPV4变量(我们从源环境文件所)要明确我们所要绑定的主机接口。 我们可以离开这里,但它设置了我们以后容易修改,如果我们想改变为一个私人接口(如果我们是负载平衡,例如)。

我们早些时候引用我们上传到Docker Hub的Docker容器。 最后,我们调用将在容器环境中启动我们的Apache服务的命令。 因为Docker容器一旦给出命令就关闭,我们要在前台运行我们的服务,而不是作为守护进程运行。 这将允许我们的容器继续运行,而不是一旦成功生成子进程就退出。

接下来,我们需要指定当服务需要停止时调用的命令。 我们将停止容器。 容器清理在每次重新启动时完成。

我们也希望添加一段名为[X-Fleet] 本节专门为船队指示如何安排服务。 在这里,您可以添加限制,以便您的服务必须或不得在与其他服务或机器状态相关的某些安排中运行。

我们希望我们的服务仅在尚未运行Apache Web服务器的主机上运行,​​因为这将为我们提供一种创建高可用性服务的简单方法。 我们将使用通配符捕获我们可能运行的任何apache服务文件:

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

[Service]
TimeoutStartSec=0
KillMode=none
EnvironmentFile=/etc/environment
ExecStartPre=-/usr/bin/docker kill apache%i
ExecStartPre=-/usr/bin/docker rm apache%i
ExecStartPre=/usr/bin/docker pull user_name/apache
ExecStart=/usr/bin/docker run --name apache%i -p ${COREOS_PUBLIC_IPV4}:%i:80 user_name/apache /usr/sbin/apache2ctl -D FOREGROUND
ExecStop=/usr/bin/docker stop apache%i

[X-Fleet]
X-Conflicts=apache@*.service

这样,我们完成了我们的Apache服务器单元文件。 我们现在将创建一个伴随服务文件以使用etcd注册该服务。

使用Etcd注册服务状态

为了记录在集群上启动的服务的当前状态,我们将要写一些条目到etcd。 这就是所谓的注册etcd。

为了做到这一点,我们将启动一个最小的配套服务,可以更新etcd的服务器可用于流量。

新的服务文件将被称为apache-discovery@.service 立即打开:

vim apache-discovery@.service

我们从开始[Unit]部分,就像我们以前那样。 我们将描述服务的目的,那么我们就建立了一个名为指令BindsTo

BindsTo指令标识依赖该服务的样子,为的状态信息。 如果列出的服务停止,我们正在写入的单位也将停止。 我们将使用这个,如果我们的Web服务器单元意外失败,这个服务将更新etcd以反映这些信息。 这解决了在etcd中有可能被其他服务错误使用的陈旧信息的潜在问题:

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

对于[Service]部分,我们想再次与源主机的IP地址信息,环境文件。

对于实际的启动命令,我们要运行一个简单的无限bash循环。 在循环中,我们将使用etcdctl命令,它用于修改ETCD值,以设置在ETCD店一键/announce/services/apache%i %i将与我们将在之间加载的服务名称的部分来代替@.serviceStapling,这再次将Apache服务的端口号。

此密钥的值将设置为节点的公共IP地址和端口号。 我们还将对该值设置60秒的到期时间,以便在服务以某种方式死亡时,键将被删除。 然后我们将睡45秒。 这将提供与到期的重叠,使得我们总是在到达其超时之前更新TTL(存活时间)值。

对于停止行动,我们就会移除具有相同的关键etcdctl工具,标志着该服务为不可用:

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

[Service]
EnvironmentFile=/etc/environment
ExecStart=/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 service
BindsTo=apache@%i.service

[Service]
EnvironmentFile=/etc/environment
ExecStart=/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

您现在有了sidekick服务,可以记录Apache服务器在etcd中的当前运行状况。

使用单元文件和Fleet

您现在有两个服务模板。 我们可以直接提交到这些fleetctl使我们的集群知道它们:

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

您应该能够通过键入以下内容查看新的服务文件:

fleetctl list-unit-files
UNIT                HASH    DSTATE      STATE       TMACHINE
apache-discovery@.service   26a893f inactive    inactive    -
apache@.service         72bcc95 inactive    inactive    -

模板现在存在于我们的集群范围的init系统中。

由于我们使用依赖于在特定主机上调度的模板,因此我们需要接下来加载文件。 这将允许我们使用端口号指定这些文件的新名称。 这是当fleetctl着眼于[X-Fleet]部分,查看调度的要求是什么。

由于我们没有做任何负载均衡,我们将只运行在端口80上我们的网站服务器,我们可以通过指定的加载每个服务@.serviceStapling:

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

您应该获得有关服务正在加载的集群中的哪台主机的信息:

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

如您所见,这些服务都已加载到同一台机器上,这是我们指定的。 由于我们的apache-discovery服务文件绑定到我们的Apache服务,我们可以简单地开始以后双方开始我们的服务:

fleetctl start apache@80.service

现在,如果你问我们的集群上运行哪些单元,我们应该看到以下内容:

fleetctl list-units
UNIT                MACHINE             ACTIVE  SUB
apache-discovery@80.service 41f4cb9a.../10.132.248.119  active  running
apache@80.service       41f4cb9a.../10.132.248.119  active  running

看来我们的Web服务器已启动并正在运行。 在我们的服务文件,我们告诉Docker绑定到主机服务器的公网IP地址,而IP显示为fleetctl是私有地址(因为我们传递$private_ipv4在云的配置创建此示例集群时)。

但是,我们已经用etcd注册了公共IP地址和端口号。 为了获取值,可以使用etcdctl工具来查询我们已经设置的值。 如果你还记得,我们设置键分别为/announce/services/apache PORT_NUM 所以要获得我们的服务器的详细信息,请键入:

etcdctl get /announce/services/apache80
104.131.15.192:80

如果我们在我们的网络浏览器中访问此页面,我们应该看到我们创建的非常简单的页面:

CoreOS基本网页

我们的服务已成功部署。 让我们尝试使用不同的端口加载另一个实例。 我们应该期望Web服务器和关联的sidekick容器将被安排在同一主机上。 但是,由于我们在我们的Apache服务文件的约束,我们应该期待这台主机是从一个服务香港港口80服务不同

让我们加载一个在端口9999上运行的服务:

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

我们可以看到,这两个新服务都安排在同一个新主机上。 启动Web服务器:

fleetctl start apache@9999.service

现在,我们可以获得这个新主机的公共IP地址:

etcdctl get /announce/services/apache9999
104.131.15.193:9999

如果我们访问指定的地址和端口号,我们应该看到另一个Web服务器:

CoreOS基本网页

我们现在在我们的集群中部署了两个Web服务器。

如果停止Web服务器,sidekick容器应该也停止:

fleetctl stop apache@80.service
fleetctl list-units
UNIT                MACHINE             ACTIVE      SUB
apache-discovery@80.service 41f4cb9a.../10.132.248.119  inactive    dead
apache-discovery@9999.service   855f79e4.../10.132.248.120  active  running
apache@80.service       41f4cb9a.../10.132.248.119  inactive    dead
apache@9999.service     855f79e4.../10.132.248.120  active  running

您可以检查etcd键是否已删除:

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

这似乎工作正如预期。

结论

通过遵循本指南,您现在应该熟悉使用CoreOS组件的一些常见方法。

我们已经创建了我们自己的Docker容器,其中包含我们想要在其中安装的服务,我们创建了一个fleet单元文件来告诉CoreOS如何管理我们的容器。 我们实现了一个sidekick服务,以保持我们的etcd数据存储是最新的与我们的Web服务器的状态信息。 我们使用fleetctl管理我们的服务,在不同的主机上调度服务。

在后面的指南中,我们将继续探讨我们在本文中简要介绍的一些领域。