状态: 过期
这篇文章不再是最新的。 如果您有兴趣为本文编写更新,请参阅DigitalOcean 想要发布您的技术教程!
原因: 2016年12月22日,CoreOS宣布不再维护fleet。 CoreOS 建议使用 Kubernetes 来满足所有集群需求。
请参阅: 有关在没有队列的情况下在 CoreOS 上使用 Kubernetes 的指导,请参阅 CoreOS 上的 Kubernetes 文档。
介绍
CoreOS 的主要优势之一是能够从单点管理整个集群中的服务。 CoreOS 平台提供了集成工具来简化此过程。
在本指南中,我们将演示在您的 CoreOS 集群上运行服务的典型工作流程。 这个过程将展示一些简单、实用的方法来与 CoreOS 的一些最有趣的实用程序进行交互,以设置应用程序。
先决条件和目标
为了开始使用本指南,您应该拥有一个至少配置了三台机器的 CoreOS 集群。 您可以在此处按照我们的 指南引导 CoreOS 集群 。
在本指南中,我们的三个节点如下所示:
- coreos-1
- coreos-2
- coreos-3
这三个节点应使用其 etcd 客户端地址和对等地址以及队列地址的专用网络接口进行配置。 这些应该使用 cloud-config 文件进行配置,如上面的指南中所示。
在本指南中,我们将介绍在 CoreOS 集群上运行服务的基本工作流程。 出于演示目的,我们将设置一个简单的 Apache Web 服务器。 我们将介绍使用 Docker 设置容器化服务环境,然后我们将创建一个 systemd 样式的单元文件来描述服务及其操作参数。
在配套单元文件中,我们将告诉我们的服务向 etcd 注册,这将允许其他服务跟踪其详细信息。 我们将把我们的两个服务都提交给fleet,在那里我们可以在整个集群的机器上启动和管理服务。
连接到节点并传递您的 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
我们上面使用的论点是:
- run:这告诉 Docker 我们要使用以下参数启动一个容器。
- -i:以交互方式启动Docker容器。 这将确保容器环境的 STDIN 可用,即使它没有附加。
- -t:这会创建一个伪 TTY,允许我们终端访问容器环境。
- ubuntu:14.04:这是我们要运行的存储库和映像组合。 在本例中,我们运行的是 Ubuntu 14.04。 映像保存在 Docker Hub 的 Ubuntu Docker 存储库中。
- /bin/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
的文件。 @
表示这是一个模板服务文件。 我们稍后会讨论这意味着什么。 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
设置为“none”,以便 systemd 允许我们的“stop”命令终止 Docker 进程。 如果我们忽略这一点,systemd 会在我们调用 stop 命令时认为 Docker 进程失败。
在开始我们的服务之前,我们还希望确保我们的环境是干净的。 这一点尤其重要,因为我们将通过名称引用我们的服务,而 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
行的 =-
语法表明这些准备行可能会失败,并且单元文件仍将继续。 由于这些命令仅在存在具有该名称的容器时才会成功,因此如果找不到容器,它们将失败。
您可能已经注意到上述指令中 apache 容器名称末尾的 %i
后缀。 我们正在创建的服务文件实际上是一个模板单元文件。 这意味着在运行文件时,fleet 将自动用适当的值替换一些信息。 阅读提供的链接中的信息以了解更多信息。
在我们的例子中,%i
将被替换为它存在于文件中的任何位置,并替换为 @
右侧 .service
后缀之前的服务文件名称部分。 我们的文件只是简单地命名为 apache@.service
。
虽然我们会使用 apache@.service
将文件提交到 fleetctl
,但是当我们加载文件时,我们会将其加载为 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 循环。 在循环中,我们将使用用于修改 etcd 值的 etcdctl
命令在 /announce/services/apache%i
处设置 etcd 存储中的键。 %i
将替换为我们将在 @
和 .service
后缀之间加载的服务名称部分,这也是 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
您现在拥有可以在 etcd 中记录 Apache 服务器当前健康状态的 Sidekick 服务。
使用单元文件和队列
您现在有两个服务模板。 我们可以将这些直接提交到 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 -
这些模板现在存在于我们的集群范围的初始化系统中。
由于我们使用的模板依赖于在特定主机上进行调度,因此我们接下来需要加载文件。 这将允许我们使用端口号为这些文件指定新名称。 这是 fleetctl
查看 [X-Fleet]
部分以查看调度要求的时候。
由于我们没有做任何负载平衡,我们将只在端口 80 上运行我们的 Web 服务器。 我们可以通过在 @
和 .service
后缀之间指定来加载每个服务:
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 地址,但是显示为 fleetctl
的 IP 是私有地址(因为我们在 cloud-config 中传入了 $private_ipv4
创建此示例集群)。
但是,我们已经向 etcd 注册了公共 IP 地址和端口号。 要获取该值,您可以使用 etcdctl
实用程序来查询我们设置的值。 如果你还记得,我们设置的键是 /announce/services/apachePORT_NUM
。 因此,要获取我们服务器的详细信息,请键入:
etcdctl get /announce/services/apache80
104.131.15.192:80
如果我们在 Web 浏览器中访问此页面,我们应该会看到我们创建的非常简单的页面:
我们的服务部署成功。 让我们尝试使用不同的端口加载另一个实例。 我们应该期望 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
我们可以看到这两个新服务都被安排在同一个新主机上。 启动网络服务器:
fleetctl start apache@9999.service
现在,我们可以获得这个新主机的公共 IP 地址:
etcdctl get /announce/services/apache9999
104.131.15.193:9999
如果我们访问指定的地址和端口号,我们应该看到另一个 Web 服务器:
我们现在已经在我们的集群中部署了两个 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 容器,其中安装了我们想要运行的服务,并且我们创建了一个队列单元文件来告诉 CoreOS 如何管理我们的容器。 我们已经实现了一个 sidekick 服务,以使我们的 etcd 数据存储与我们的 Web 服务器的状态信息保持同步。 我们使用fleetctl管理我们的服务,在不同的主机上安排服务。
在以后的指南中,我们将继续探索我们在本文中简要介绍的一些领域。