如何使用Confd和Etcd在CoreOS中动态重新配置服务

来自菜鸟教程
跳转至:导航、​搜索

介绍

CoreOS 允许您在跨机器集群的 Docker 容器中轻松运行服务。 这样做的过程通常涉及启动一个或多个服务实例,然后将每个实例注册到 etcd,CoreOS 的分布式键值存储。

通过利用这种模式,相关服务可以获得有关基础设施状态的有价值信息,并使用这些知识来告知自己的行为。 这使得服务可以在重要的 etcd 值更改时动态配置自己。

在本指南中,我们将讨论一个名为 confd 的工具,该工具专门用于观察分布式键值存储的变化。 它在 Docker 容器中运行,用于触发配置修改和服务重新加载。

先决条件和目标

为了阅读本指南,您应该对 CoreOS 及其组件有基本的了解。 在之前的指南中,我们设置了 CoreOS 集群并熟悉了一些用于管理集群的工具。

以下是您在开始阅读本文之前应阅读的指南。 我们将修改这些指南中描述的一些服务的行为,因此虽然理解材料很重要,但在使用本指南时应该重新开始:

此外,为了更熟悉我们将使用的一些管理工具,您需要阅读以下指南:

“如何创建灵活服务”指南对本指南尤为重要,因为模板化的 main + sidekick 服务将作为我们将在本指南中设置的前端服务的基础。 如前所述,尽管上述指南讨论了 Apache 和 Sidekick 服务的创建,但本指南有一些配置更改,可以更轻松地从头开始。 我们将在本指南中创建这些服务的修改版本。

在本教程中,我们将专注于使用 Nginx 创建一个新的应用程序容器。 这将作为我们可以从模板文件生成的各种 Apache 实例的反向代理。 Nginx 容器将配置 confd 以监视我们的 sidekick 服务负责的服务注册。

我们将从本系列中一直使用的相同的三机集群开始。

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

当您阅读完前面的指南并准备好三机集群后,请继续。

配置后端 Apache 服务

我们将从设置后端 Apache 服务开始。 这将主要反映上一个指南的最后一部分,但由于一些细微的差异,我们将在此处运行整个过程。

登录您的一台 CoreOS 机器以开始使用:

ssh -A core@ip_address

Apache 容器设置

我们将从创建基本的 Apache 容器开始。 这实际上与上一个指南相同,因此如果您的 Docker Hub 帐户中已经有该映像可用,则无需再次执行此操作。 我们将这个容器基于 Ubuntu 14.04 容器映像。

我们可以拉下基础镜像并通过键入以下内容启动容器实例:

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

容器启动后,您将进入 bash 会话。 从这里,我们将更新本地的 apt 包索引并安装 apache2

apt-get update
apt-get install apache2 -y

我们还将设置默认页面:

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

我们现在可以退出容器,因为它处于我们需要的状态:

exit

通过键入以下内容登录或在 Docker Hub 中创建您的帐户:

docker login

您必须提供您的 Docker Hub 帐户的用户名、密码和电子邮件地址。

接下来,获取刚刚离开的实例的容器 ID:

docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
1db0c9a40c0d        ubuntu:14.04        "/bin/bash"         2 minutes ago       Exited (0) 4 seconds ago                       jolly_pare

上面突出显示的字段是容器 ID。 复制您在自己的计算机上看到的输出。

现在,使用该容器 ID、您的 Docker Hub 用户名和映像名称进行提交。 我们将在这里使用“apache”:

docker commit 1db0c9a40c0d user_name/apache

将新镜像推送到 Docker Hub:

docker push user_name/apache

现在可以在您的服务文件中使用此图像。

创建 Apache 服务模板单元文件

现在您有了可用的容器,您可以创建一个模板单元文件,以便 fleetsystemd 可以正确管理服务。

在开始之前,让我们设置一个目录结构,以便我们可以保持井井有条:

cd ~
mkdir static templates instances

现在,我们可以在 templates 目录中创建我们的模板文件:

vim templates/apache@.service

将以下信息粘贴到文件中。 您可以按照先前关于 创建灵活车队单元文件 的指南获取有关我们正在使用的每个选项的详细信息:

[Unit]
Description=Apache web server service on port %i

# Requirements
Requires=etcd.service
Requires=docker.service
Requires=apache-discovery@%i.service

# Dependency ordering
After=etcd.service
After=docker.service
Before=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.%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_PRIVATE_IPV4}:%i:80 \
user_name/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
Conflicts=apache@*.service

我们在这里所做的一项修改是使用私有接口而不是公共接口。 由于我们所有的 Apache 实例都将通过 Nginx 反向代理 传递流量,而不是处理来自开放 Web 的连接,这是一个好主意。 请记住,如果您在 DigitalOcean 上使用私有接口,您启动的服务器必须在创建时选择了“私有网络”标志。

此外,请记住更改 user_name 以引用您的 Docker Hub 用户名,以便正确下载 Docker 文件。

创建 Sidekick 模板单元文件

现在,我们将对 Sidekick 服务执行相同的操作。 我们将根据稍后需要的信息稍作修改。

在编辑器中打开模板文件:

vim templates/apache-discovery@.service

我们将在此文件中使用以下信息:

[Unit]
Description=Apache web server on port %i etcd registration

# Requirements
Requires=etcd.service
Requires=apache@%i.service

# Dependency ordering and binding
After=etcd.service
After=apache@%i.service
BindsTo=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_PRIVATE_IPV4}:%i; \
    if [ $? -eq 0 ]; then \
      etcdctl set /services/apache/${COREOS_PRIVATE_IPV4} \'${COREOS_PRIVATE_IPV4}:%i\' --ttl 30; \
    else \
      etcdctl rm /services/apache/${COREOS_PRIVATE_IPV4}; \
    fi; \
    sleep 20; \
  done'

# Stop
ExecStop=/usr/bin/etcdctl rm /services/apache/${COREOS_PRIVATE_IPV4}

[X-Fleet]
# Schedule on the same machine as the associated Apache service
MachineOf=apache@%i.service

上述配置与上一指南中的配置在一些方面有所不同。 我们调整了etcdctl set命令设置的值。 我们没有传递 JSON 对象,而是设置了一个简单的 IP 地址 + 端口组合。 这样,我们可以直接读取这个值来找到访问这个服务所需的连接信息。

我们还调整了信息以指定私有接口,就像我们在其他文件中所做的那样。 如果您没有此选项可用,请将其设为公开。

实例化您的服务

现在,让我们创建这些服务的两个实例。

首先,让我们创建符号链接。 移动到您创建的 ~/instances 目录并链接以定义它们将在其上运行的端口。 我们想在端口 7777 上运行一项服务,在端口 8888 上运行另一项服务:

cd ~/instances
ln -s ../templates/apache@.service apache@7777.service
ln -s ../templates/apache@.service apache@8888.service
ln -s ../templates/apache-discovery@.service apache-discovery@7777.service
ln -s ../templates/apache-discovery@.service apache-discovery@8888.service

现在,我们可以通过将 ~/instances 目录传递给 fleet 来启动这些服务:

fleetctl start ~/instances/*

在您的实例启动后(这可能需要几分钟),您应该能够看到您的伙伴创建的 etcd 条目:

etcdctl ls --recursive /
/coreos.com
/coreos.com/updateengine
/coreos.com/updateengine/rebootlock
/coreos.com/updateengine/rebootlock/semaphore
/services
/services/apache
/services/apache/10.132.249.206
/services/apache/10.132.249.212

如果您询问这些条目之一的值,您可以看到您获得了一个 IP 地址和一个端口号:

etcdctl get /services/apache/10.132.249.206
10.132.249.206:8888

您可以使用 curl 检索页面并确保其正常运行。 如果您将服务配置为使用私有网络,这只会在您的机器内工作:

curl 10.132.249.206:8888
<h1>Running from Docker on CoreOS</h1>

我们现在已经设置了后端基础架构。 我们下一步是熟悉 confd 以便我们可以观察 etcd 中的 /services/apache 位置的变化并每次重新配置 Nginx。

创建 Nginx 容器

我们将从用于 Apache 服务的同一 Ubuntu 14.04 基础启动 Nginx 容器。

安装软件

通过键入以下内容启动一个新容器:

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

更新本地 apt 包缓存并安装 Nginx。 我们还需要安装 curl,因为基础镜像不包含它,我们需要它来暂时从 GitHub 获取稳定的 confd 包:

apt-get update
apt-get install nginx curl -y

现在,我们可以在浏览器中访问 GitHub 上 confd发布页面 。 我们需要找到最新稳定版本的链接。 在撰写本文时,即 v0.5.0,但这可能已经改变。 右键单击该工具的 Linux 版本的链接,然后选择“复制链接地址”或任何可用的类似选项。

现在,回到您的 Docker 容器中,使用复制的 URL 下载应用程序。 我们将把它放在 /usr/local/bin 目录中。 我们需要选择 confd 作为输出文件:

cd /usr/local/bin
curl -L https://github.com/kelseyhightower/confd/releases/download/v0.5.0/confd-0.5.0<^>-linux-amd64 -o confd

现在,使文件可执行,以便我们可以在容器中使用它:

chmod +x confd

我们也应该借此机会创建 confd 期望的配置结构。 这将在 /etc 目录中:

mkdir -p /etc/confd/{conf.d,templates}

创建 Confd 配置文件以读取 Etcd 值

现在我们已经安装了我们的应用程序,我们应该开始配置 confd。 我们将从创建配置文件或模板资源文件开始。

confd 中的配置文件用于设置服务以检查某些 etcd 值并在检测到更改时启动操作。 这些使用 TOML 文件格式,易于使用且相当直观。

首先在我们的配置目录中创建一个名为 nginx.toml 的文件:

vi /etc/confd/conf.d/nginx.toml

我们将在这里构建我们的配置文件。 添加以下信息:

[template]

# The name of the template that will be used to render the application's configuration file
# Confd will look in `/etc/conf.d/templates` for these files by default
src = "nginx.tmpl"

# The location to place the rendered configuration file
dest = "/etc/nginx/sites-enabled/app.conf"

# The etcd keys or directory to watch.  This is where the information to fill in
# the template will come from.
keys = [ "/services/apache" ]

# File ownership and mode information
owner = "root"
mode = "0644"

# These are the commands that will be used to check whether the rendered config is
# valid and to reload the actual service once the new config is in place
check_cmd = "/usr/sbin/nginx -t"
reload_cmd = "/usr/sbin/service nginx reload"

上面的文件有解释一些基本想法的注释,但我们可以在下面查看您的选项:

指示 必需的? 类型 描述
源代码 是的 细绳 将用于呈现信息的模板的名称。 如果它位于 /etc/confd/templates 之外,则应使用整个路径。
目的地 是的 细绳 应放置呈现的配置文件的文件位置。
钥匙 是的 字符串数组 模板需要正确渲染的 etcd 键。 如果模板设置为处理子键,这可以是一个目录。
所有者 细绳 将被赋予渲染配置文件所有权的用户名。
团体 细绳 将被赋予对呈现的配置文件的组所有权的组。
模式 细绳 应该为渲染文件设置的八进制权限模式。
check_cmd 细绳 用于检查呈现的配置文件语法的命令。
reload_cmd 细绳 应该用于重新加载应用程序配置的命令。
字首 细绳 etcd 层次结构的一部分,位于 keys 指令中的键之前。 这可用于使 .toml 文件更灵活。

我们创建的文件告诉我们一些关于我们的 confd 实例将如何运行的重要信息。 我们的 Nginx 容器将使用存储在 /etc/confd/templates/nginx.conf.tmpl 的模板来渲染将放置在 /etc/nginx/sites-enabled/app.conf 的配置文件。 该文件将被授予 0644 权限集,并且所有权将授予 root 用户。

confd 应用程序将在 /services/apache 节点处查找更改。 当看到更改时,confd 将查询该节点下的新信息。 然后它将为 Nginx 渲染一个新的配置。 它将检查配置文件的语法错误,并在文件到位后重新加载 Nginx 服务。

我们现在已经创建了模板资源文件。 我们应该处理将用于渲染 Nginx 配置文件的实际模板文件。

创建 Confd 模板文件

对于我们的模板文件,我们将使用 confd 项目的 GitHub 文档 中的示例来帮助我们开始。

创建我们在上面的配置文件中引用的文件。 将此文件放在我们的 templates 目录中:

vi /etc/confd/templates/nginx.tmpl

在这个文件中,我们基本上只是重新创建了一个标准的 Nginx 反向代理配置文件。 然而,我们将使用一些 Go 模板语法来替换 confdetcd 中提取的一些信息。

首先,我们使用“上游”服务器配置块。 本节用于定义 Nginx 可以向其发送请求的服务器池。 格式一般是这样的:

upstream pool_name {
    server server_1_IP:port_num;
    server server_2_IP:port_num;
    server server_3_IP:port_num;
}

这允许我们将请求传递给 pool_name,Nginx 将选择一个已定义的服务器来处理请求。

我们的模板文件背后的想法是解析 etcd 以获取我们的 Apache Web 服务器的 IP 地址和端口号。 因此,与其静态定义上游服务器,不如在文件渲染时动态填写这些信息。 我们可以通过对动态内容使用 Go 模板 来做到这一点。

为此,我们将改为使用它作为我们的块:

upstream apache_pool {
{{ range getvs "/services/apache/*" }}
    server {{ . }};
{{ end }}
}

让我们解释一下发生了什么。 我们打开了一个块来定义一个名为 apache_pool 的上游服务器池。 在内部,我们指定我们使用双括号开始一些 Go 语言代码。

在这些括号中,我们指定保存我们感兴趣的值的 etcd 端点。 我们使用 range 使列表可迭代。

我们使用它来将从 etcd 中的 /services/apache 位置下方检索到的所有条目传递到 range 块中。 然后,我们可以使用“模板:”和“”中表示插入值的单个点来获取当前迭代中键的值。 我们在范围循环中使用它来填充服务器池。 最后,我们用 模板:End 指令结束循环。

注意:记得在循环内的server指令后面加上分号。 忘记这一点将导致配置无效。

设置服务器池后,我们可以使用代理通道将所有连接定向到该池。 这只是作为反向代理的标准服务器块。 需要注意的一件事是 access_log,它使用我们将立即创建的自定义格式:

upstream apache_pool {
{{ range getvs "/services/apache/*" }}
    server {{ . }};
{{ end }}
}

server {
    listen 80 default_server;
    listen [::]:80 default_server ipv6only=on;

    access_log /var/log/nginx/access.log upstreamlog;

    location / {
        proxy_pass http://apache_pool;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

这将响应端口 80 上的所有连接并将它们传递到通过查看 etcd 条目生成的 apache_pool 处的服务器池。

当我们处理服务的这方面时,我们应该删除默认的 Nginx 配置文件,这样我们以后就不会遇到冲突。 我们将删除启用默认配置的符号链接:

rm /etc/nginx/sites-enabled/default

现在也是配置我们在模板文件中引用的日志格式的好时机。 这必须放在配置的 http 块中,该块在主配置文件中可用。 现在打开它:

vi /etc/nginx/nginx.conf

我们将添加一个 log_format 指令来定义我们想要记录的信息。 它将记录正在访问的客户端以及请求传递到的后端服务器。 我们将记录一些有关这些程序所用时间的数据:

. . .
http {
    ##
    # Basic Settings
    ##
    log_format upstreamlog '[$time_local] $remote_addr passed to: $upstream_addr: $request Upstream Response Time: $upstream_response_time Request time: $request_time';

    sendfile on;
    . . .

完成后保存并关闭文件。

创建脚本以运行 Confd

我们需要创建一个脚本文件,该文件将在适当的时候使用我们的模板资源文件和模板文件调用 confd

脚本必须做两件事才能使我们的服务正常工作:

  • 它必须在容器启动时运行,以根据后端基础架构的当前状态设置初始 Nginx 设置。
  • 它必须继续监视 Apache 服务器的 etcd 注册更改,以便它可以根据可用的后端服务器重新配置 Nginx。

我们将从 Marcel de Graaf 的 GitHub 页面 获取我们的脚本。 这是一个不错的、简单的脚本,它 正是 我们需要的。 我们只会对我们的场景进行一些小的编辑。

让我们将此脚本放在我们的 confd 可执行文件旁边。 我们称之为 confd-watch

vi /usr/local/bin/confd-watch

我们将从传统的 bash 标头开始,以识别我们需要的解释器。 然后我们将设置一些 bash 选项,以便脚本在出现任何问题时立即失败。 它将返回最后一个失败或运行的命令的值。

#!/bin/bash

set -eo pipefail

接下来,我们要设置一些变量。 通过使用 bash 参数替换,我们将设置默认值,但构建了一些灵活性,让我们在调用脚本时覆盖硬编码值。 这基本上只是独立设置连接地址的每个组件,然后将它们组合在一起以获得所需的完整地址。

使用以下语法创建参数替换:${var_name:-default_value}。 这具有使用 var_name 的值(如果给定且不为空)的属性,否则默认为 default_value

我们默认使用 etcd 默认期望的值。 这将允许我们的脚本在没有额外信息的情况下正常运行,但我们可以在调用脚本时根据需要进行自定义:

#!/bin/bash

set -eo pipefail

export ETCD_PORT=${ETCD_PORT:-4001}
export HOST_IP=${HOST_IP:-172.17.42.1}
export ETCD=$HOST_IP:$ETCD_PORT

我们现在将使用 confd 通过读取调用此脚本时可用的 etcd 中的值来呈现 Nginx 配置文件的初始版本。 我们将使用 until 循环不断尝试构建初始配置。

如果 etcd 不可用,或者 Nginx 容器在后端服务器之前上线,则可能需要循环构造。 这允许它反复轮询 etcd 直到它最终可以产生一个有效的初始配置。

我们调用的实际 confd 命令执行一次然后退出。 这样我们就可以等待 5 秒,直到下一次运行,让我们的后端服务器有机会注册。 我们连接到使用默认值或传入参数构建的完整 ETCD 变量,并使用模板资源文件来定义我们想要执行的操作:

#!/bin/bash

set -eo pipefail

export ETCD_PORT=${ETCD_PORT:-4001}
export HOST_IP=${HOST_IP:-172.17.42.1}
export ETCD=$HOST_IP:$ETCD_PORT

echo "[nginx] booting container. ETCD: $ETCD"

# Try to make initial configuration every 5 seconds until successful
until confd -onetime -node $ETCD -config-file /etc/confd/conf.d/nginx.toml; do
    echo "[nginx] waiting for confd to create initial nginx configuration"
    sleep 5
done

在设置了初始配置之后,我们脚本的下一个任务应该是建立一个持续轮询的机制。 我们希望确保检测到任何未来的更改,以便更新 Nginx。

为此,我们可以再次调用 confd。 这一次,我们要设置一个连续的轮询间隔,并将进程置于后台,使其无限期地运行。 我们将传入相同的 etcd 连接信息和相同的模板资源文件,因为我们的目标仍然相同。

confd 进程放入后台后,我们可以使用制作的配置文件安全地启动 Nginx。 由于这个脚本将被称为我们的 Docker “运行”命令,我们需要让它在前台运行,这样容器就不会在此时退出。 我们可以通过跟踪日志来做到这一点,让我们可以访问我们一直在记录的所有信息:

#!/bin/bash

set -eo pipefail

export ETCD_PORT=${ETCD_PORT:-4001}
export HOST_IP=${HOST_IP:-172.17.42.1}
export ETCD=$HOST_IP:$ETCD_PORT

echo "[nginx] booting container. ETCD: $ETCD."

# Try to make initial configuration every 5 seconds until successful
until confd -onetime -node $ETCD -config-file /etc/confd/conf.d/nginx.toml; do
    echo "[nginx] waiting for confd to create initial nginx configuration."
    sleep 5
done

# Put a continual polling `confd` process into the background to watch
# for changes every 10 seconds
confd -interval 10 -node $ETCD -config-file /etc/confd/conf.d/nginx.toml &
echo "[nginx] confd is now monitoring etcd for changes..."

# Start the Nginx service using the generated config
echo "[nginx] starting nginx service..."
service nginx start

# Follow the logs to allow the script to continue running
tail -f /var/log/nginx/*.log

完成此操作后,保存并关闭文件。

我们需要做的最后一件事是使脚本可执行:

chmod +x /usr/local/bin/confd-watch

现在退出容器以返回主机系统:

exit

提交并推送容器

现在,我们可以提交容器并将其推送到 Docker Hub,以便我们的机器可以将其拉下。

找出容器 ID:

docker ps -l
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                          PORTS               NAMES
de4f30617499        ubuntu:14.04        "/bin/bash"         22 hours ago        Exited (0) About a minute ago                       stupefied_albattani

突出显示的字符串是我们需要的容器 ID。 使用此 ID 以及您的 Docker Hub 用户名和您要用于此映像的名称来提交容器。 我们将在本指南中使用名称“nginx_lb”:

docker commit de4f30617499 user_name/nginx_lb

如有必要,登录到您的 Docker Hub 帐户:

docker login

现在,您应该向上推提交的图像,以便您的其他主机可以根据需要将其拉下:

docker push user_name/nginx_lb

构建 Nginx 静态单元文件

下一步是构建一个单元文件,它将启动我们刚刚创建的容器。 这将让我们使用 fleet 来控制进程。

由于这不是模板,我们将其放入我们在此目录开头创建的 ~/static 目录中:

vim static/nginx_lb.service

我们将从标准的 [Unit] 部分开始描述服务并定义依赖关系和排序:

[Unit]
Description=Nginx load balancer for web server backends

# Requirements
Requires=etcd.service
Requires=docker.service

# Dependency ordering
After=etcd.service
After=docker.service

接下来,我们需要定义文件的 [Service] 部分。 我们将超时设置为零并再次将 killmode 调整为 none,就像我们对 Apache 服务文件所做的那样。 我们将再次拉入环境文件,以便我们可以访问运行此容器的主机的公共和私有 IP 地址。

然后,我们将清理我们的环境,以确保该容器的任何先前版本都被杀死并删除。 我们拉下刚刚创建的容器,以确保我们始终拥有最新版本。

最后,我们将启动容器。 这包括启动容器,为其命名我们在 remove 和 kill 命令中引用的名称,并将运行它的主机的公共 IP 地址传递给它以映射端口 80。 我们将编写的 confd-watch 脚本称为运行命令。

[Unit]
Description=Nginx load balancer for web server backends

# Requirements
Requires=etcd.service
Requires=docker.service

# Dependency ordering
After=etcd.service
After=docker.service

[Service]
# Let the process 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 nginx_lb
ExecStartPre=-/usr/bin/docker rm nginx_lb
ExecStartPre=/usr/bin/docker pull user_name/nginx_lb
ExecStart=/usr/bin/docker run --name nginx_lb -p ${COREOS_PUBLIC_IPV4}:80:80 \
user_name/nginx_lb /usr/local/bin/confd-watch

现在,我们需要理清的是停止命令和fleet调度方向。 我们希望这个容器只在没有运行其他负载平衡实例或后端 Apache 服务器的主机上启动。 这将使我们的服务能够有效地分散负载:

[Unit]
Description=Nginx load balancer for web server backends

# Requirements
Requires=etcd.service
Requires=docker.service

# Dependency ordering
After=etcd.service
After=docker.service

[Service]
# Let the process 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 nginx_lb
ExecStartPre=-/usr/bin/docker rm nginx_lb
ExecStartPre=/usr/bin/docker pull user_name/nginx_lb
ExecStart=/usr/bin/docker run --name nginx_lb -p ${COREOS_PUBLIC_IPV4}:80:80 \
user_name/nginx_lb /usr/local/bin/confd-watch

# Stop
ExecStop=/usr/bin/docker stop nginx_lb

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

完成后保存并关闭文件。

运行 Nginx 负载均衡器

您应该已经在本教程前面运行了两个 Apache 实例。 您可以通过键入以下内容进行检查:

fleetctl list-units
UNIT             MACHINE             ACTIVE  SUB
apache-discovery@7777.service   197a1662.../10.132.249.206  active  running
apache-discovery@8888.service   04856ec4.../10.132.249.212  active  running
apache@7777.service     197a1662.../10.132.249.206  active  running
apache@8888.service     04856ec4.../10.132.249.212  active  running

您还可以通过键入以下内容来仔细检查他们是否正确地向 etcd 注册了自己:

etcdctl ls --recursive /services/apache
/services/apache/10.132.249.206
/services/apache/10.132.249.212

我们现在可以尝试启动我们的 Nginx 服务:

fleetctl start ~/static/nginx_lb.service
Unit nginx_lb.service launched on 96ec72cf.../10.132.248.177

服务启动可能需要一分钟左右,具体取决于拉下图像需要多长时间。 启动后,如果用fleetctl journal命令查看日志,应该可以从confd看到一些日志信息。 它应该看起来像这样:

fleetctl journal nginx_lb.service
-- Logs begin at Mon 2014-09-15 14:54:05 UTC, end at Tue 2014-09-16 17:13:58 UTC. --
Sep 16 17:13:48 lala1 docker[15379]: 2014-09-16T17:13:48Z d7974a70e976 confd[14]: INFO Target config /etc/nginx/sites-enabled/app.conf out of sync
Sep 16 17:13:48 lala1 docker[15379]: 2014-09-16T17:13:48Z d7974a70e976 confd[14]: INFO Target config /etc/nginx/sites-enabled/app.conf has been updated
Sep 16 17:13:48 lala1 docker[15379]: [nginx] confd is monitoring etcd for changes...
Sep 16 17:13:48 lala1 docker[15379]: [nginx] starting nginx service...
Sep 16 17:13:48 lala1 docker[15379]: 2014-09-16T17:13:48Z d7974a70e976 confd[33]: INFO Target config /etc/nginx/sites-enabled/app.conf in sync
Sep 16 17:13:48 lala1 docker[15379]: ==> /var/log/nginx/access.log <==
Sep 16 17:13:48 lala1 docker[15379]: ==> /var/log/nginx/error.log <==
Sep 16 17:13:58 lala1 docker[15379]: 2014-09-16T17:13:58Z d7974a70e976 confd[33]: INFO /etc/nginx/sites-enabled/app.conf has md5sum a8517bfe0348e9215aa694f0b4b36c9b should be 33f42e3b7cc418f504237bea36c8a03e
Sep 16 17:13:58 lala1 docker[15379]: 2014-09-16T17:13:58Z d7974a70e976 confd[33]: INFO Target config /etc/nginx/sites-enabled/app.conf out of sync
Sep 16 17:13:58 lala1 docker[15379]: 2014-09-16T17:13:58Z d7974a70e976 confd[33]: INFO Target config /etc/nginx/sites-enabled/app.conf has been updated

如您所见,confd 寻找 etcd 的初始配置。 然后它开始 nginx。 之后,我们可以看到 etcd 条目已被重新评估并生成了新配置文件的行。 如果新生成的文件与原文件的md5sum不匹配,则切换出文件并重新加载服务。

这允许我们的负载平衡服务最终跟踪我们的 Apache 后端服务器。 如果 confd 似乎在不断更新,可能是因为您的 Apache 实例过于频繁地刷新其 TTL。 您可以在 Sidekick 模板中增加 sleep 和 TTL 值来避免这种情况。

要查看负载均衡器的运行情况,您可以从运行 Nginx 服务的主机请求 /etc/environments 文件。 这包含主机的公共 IP 地址。 如果您想使此配置更好,请考虑运行一个使用 etcd 注册此信息的 sidekick 服务,就像我们为 Apache 实例所做的那样:

fleetctl ssh nginx_lb cat /etc/environment
COREOS_PRIVATE_IPV4=10.132.248.177
COREOS_PUBLIC_IPV4=104.131.16.222

现在,如果我们在浏览器中访问公共 IPv4 地址,我们应该会看到我们在 Apache 实例中配置的页面:

现在,如果您再次查看日志,您应该能够看到指示哪个后端服务器实际传递了请求的信息:

fleetctl journal nginx_lb
. . .
Sep 16 18:04:38 lala1 docker[18079]: 2014-09-16T18:04:38Z 51c74658196c confd[28]: INFO Target config /etc/nginx/sites-enabled/app.conf in sync
Sep 16 18:04:48 lala1 docker[18079]: 2014-09-16T18:04:48Z 51c74658196c confd[28]: INFO Target config /etc/nginx/sites-enabled/app.conf in sync
Sep 16 18:04:48 lala1 docker[18079]: [16/Sep/2014:18:04:48 +0000] 108.29.37.206 passed to: 10.132.249.212:8888: GET / HTTP/1.1 Upstream Response Time: 0.003 Request time: 0.003

结论

如您所见,可以设置您的服务以检查 etcd 以获取配置详细信息。 像 confd 这样的工具可以通过允许连续轮询重要条目来使这个过程相对简单。

在本指南的示例中,我们将 Nginx 服务配置为使用 etcd 生成其初始配置。 我们还在后台对其进行了设置,以不断检查更改。 这与基于模板的动态配置生成相结合,使我们能够始终如一地了解后端服务器的最新情况。