使用 Cloud-init 将节点添加到您的私有云

使在家中向您的私有云添加机器类似于主要云提供商的处理方式。
101 位读者喜欢这篇文章。
Digital images of a computer desktop

Opensource.com

Cloud-init 是一种广泛使用的行业标准方法,用于初始化云实例。云提供商使用 Cloud-init 来自定义实例,包括网络配置、实例信息,甚至用户提供的配置指令。它也是在您的“家庭私有云”中使用的绝佳工具,可以为您的家庭实验室的虚拟和物理机器的初始设置和配置添加一些自动化,并了解更多关于大型云提供商如何工作的信息。有关更多详细信息和背景信息,请参阅我之前关于 Cloud-init 及其用途 的文章。

A screen showing the boot process for a Linux server running Cloud-init

运行 Cloud-init 的 Linux 服务器的启动过程 (Chris Collins, CC BY-SA 4.0)

诚然,与单个系统管理员运行的家庭实验室相比,Cloud-init 对于为许多不同客户端配置机器的云提供商更有用,并且 Cloud-init 解决的许多问题对于家庭实验室来说可能有点多余。但是,设置它并了解其工作原理是了解更多关于这项云技术的绝佳方法,更不用说它是首次启动时配置设备的绝佳方法。

本教程使用 Cloud-init 的“NoCloud”数据源,该数据源允许在传统云提供商设置之外使用 Cloud-init。本文将向您展示如何在客户端设备上安装 Cloud-init,并设置一个运行 Web 服务的容器来响应客户端的请求。您还将学习调查客户端正在从 Web 服务请求什么,并修改 Web 服务的容器以提供基本的静态 Cloud-init 服务。

在现有系统上设置 Cloud-init

Cloud-init 在新系统的首次启动时可能最有用,它可以查询配置数据并进行更改以按指示自定义系统。它可以包含在 Raspberry Pi 和单板计算机的磁盘映像中,或添加到用于配置虚拟机的映像中。为了进行测试,可以轻松地在现有系统上安装和运行 Cloud-init,或者安装新系统然后设置 Cloud-init。

作为大多数云提供商使用的主要服务,Cloud-init 在大多数 Linux 发行版上都受支持。在本示例中,我将使用 Raspberry Pi 的 Fedora 31 Server,但它可以在 Raspbian、Ubuntu、CentOS 和大多数其他发行版上以相同的方式完成。

安装并启用 cloud-init 服务

在您希望成为 Cloud-init 客户端的系统上,安装 Cloud-init 软件包。如果您使用的是 Fedora

# Install the cloud-init package
dnf install -y cloud-init

Cloud-init 实际上是四种不同的服务(至少在使用 systemd 时),每种服务都负责在启动过程的不同部分检索配置数据并执行配置更改,从而在可以完成的事情方面提供更大的灵活性。虽然您不太可能直接与这些服务进行太多交互,但了解它们是什么对于您需要排除故障时很有用。它们是

  • cloud-init-local.service
  • cloud-init.service
  • cloud-config.service
  • cloud-final.service

启用所有四种服务

# Enable the four cloud-init services
systemctl enable cloud-init-local.service
systemctl enable cloud-init.service
systemctl enable cloud-config.service
systemctl enable cloud-final.service

配置要查询的数据源

启用服务后,配置客户端将从中查询配置数据的数据源。有大量数据源类型,大多数都配置为特定的云提供商。对于您的家庭实验室,请使用 NoCloud 数据源,该数据源(如上所述)旨在在没有云提供商的情况下使用 Cloud-init。

NoCloud 允许以多种方式包含配置信息:作为内核参数中的键/值对,用于使用启动时挂载的 CD(或虚拟机情况下的虚拟 CD),包含在文件系统上的文件中,或者,如本示例所示,通过 HTTP 从指定的 URL(“NoCloud Net”选项)。

可以通过内核参数或在 Cloud-init 配置文件 /etc/cloud/cloud.cfg 中设置数据源配置。配置文件非常适合使用自定义磁盘映像设置 Cloud-init 或在现有主机上进行测试。

Cloud-init 还会合并来自 /etc/cloud/cloud.cfg.d/ 中找到的任何 *.cfg 文件的配置数据,因此为了保持整洁,请在 /etc/cloud/cloud.cfg.d/10_datasource.cfg 中配置数据源。可以使用 seedfrom 键通过以下语法告知 Cloud-init 从 HTTP 数据源读取

seedfrom: http://ip_address:port/

IP 地址和端口是您将在本文后面创建的 Web 服务。我使用了我的笔记本电脑的 IP 和端口 8080;这也可以是 DNS 名称。

创建 /etc/cloud/cloud.cfg.d/10_datasource.cfg 文件

# Add the datasource:
# /etc/cloud/cloud.cfg.d/10_datasource.cfg

# NOTE THE TRAILING SLASH HERE!
datasource:
  NoCloud:
    seedfrom: http://ip_address:port/

客户端设置就完成了。现在,当客户端重新启动时,它将尝试从您在 seedfrom 键中输入的 URL 检索配置数据,并进行任何必要的配置更改。

下一步是设置一个 Web 服务器来监听客户端请求,以便您可以确定需要提供什么。

设置 Web 服务器以调查客户端请求

您可以使用 Podman 或其他容器编排工具(如 Docker 或 Kubernetes)快速创建和运行 Web 服务器。本示例使用 Podman,但相同的命令也适用于 Docker。

要开始使用,请使用 Fedora:31 容器映像并创建一个 Containerfile(对于 Docker,这将是 Dockerfile),它安装和配置 Nginx。从该 Containerfile,您可以构建自定义映像并在要充当 Cloud-init 服务的宿主机上运行它。

使用以下内容创建 Containerfile

FROM fedora:31

ENV NGINX_CONF_DIR "/etc/nginx/default.d"
ENV NGINX_LOG_DIR "/var/log/nginx"
ENV NGINX_CONF "/etc/nginx/nginx.conf"
ENV WWW_DIR "/usr/share/nginx/html"

# Install Nginx and clear the yum cache
RUN dnf install -y nginx \
      && dnf clean all \
      && rm -rf /var/cache/yum

# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout ${NGINX_LOG_DIR}/access.log \
    && ln -sf /dev/stderr ${NGINX_LOG_DIR}/error.log

# Listen on port 8080, so root privileges are not required for podman
RUN sed -i -E 's/(listen)([[:space:]]*)(\[\:\:\]\:)?80;$/\1\2\38080 default_server;/' $NGINX_CONF
EXPOSE 8080

# Allow Nginx PID to be managed by non-root user
RUN sed -i '/user nginx;/d' $NGINX_CONF
RUN sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/' $NGINX_CONF

# Run as an unprivileged user
USER 1001

CMD ["nginx", "-g", "daemon off;"]

注意:本示例中使用的示例 Containerfile 和其他文件可以在此项目的 GitHub 存储库 中找到。

上面 Containerfile 中最重要的部分是更改日志存储方式的部分(写入 STDOUT 而不是文件),这样您就可以在容器日志中看到进入服务器的请求。一些其他更改使您可以使用 Podman 在没有 root 权限的情况下运行容器,以及在没有 root 权限的情况下在容器中运行进程。

Web 服务器的第一个版本不提供任何 Cloud-init 数据;只需使用它来查看 Cloud-init 客户端正在从中请求什么。

创建 Containerfile 后,使用 Podman 构建和运行 Web 服务器映像

# Build the container image
$ podman build -f Containerfile -t cloud-init:01 .

# Create a container from the new image, and run it
# It will listen on port 8080
$ podman run --rm -p 8080:8080 -it cloud-init:01

这将运行容器,使您的终端保持连接并带有伪 TTY。起初看起来什么也没有发生,但是对宿主机 8080 端口的请求将被路由到容器内的 Nginx 服务器,并且终端窗口中将显示一条日志消息。可以使用宿主机上的 curl 命令进行测试

# Use curl to send an HTTP request to the Nginx container
$ curl http://localhost:8080

运行该 curl 命令后,您应该在终端窗口中看到类似于以下的日志消息

127.0.0.1 - - [09/May/2020:19:25:10 +0000] "GET / HTTP/1.1" 200 5564 "-" "curl/7.66.0" "-"

现在到了有趣的部分:重新启动 Cloud-init 客户端并观看 Nginx 日志,以查看客户端启动时 Cloud-init 从 Web 服务器请求什么。

当客户端完成其启动过程时,您应该看到类似于以下的日志消息

2020/05/09 22:44:28 [error] 2#0: *4 open() "/usr/share/nginx/html/meta-data" failed (2: No such file or directory), client: 127.0.0.1, server: _, request: "GET /meta-data HTTP/1.1", host: "instance-data:8080"
127.0.0.1 - - [09/May/2020:22:44:28 +0000] "GET /meta-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-"

注意:使用 Ctrl+C 停止正在运行的容器。

您可以看到请求是针对 /meta-data 路径,即 http://ip_address_of_the_webserver:8080/meta-data。这只是一个 GET 请求——Cloud-init 没有 POST(发送)任何数据到 Web 服务器。它只是盲目地从数据源 URL 请求文件,因此由数据源来识别主机正在请求什么。这个简单的示例只是将通用数据发送到任何客户端,但是更大的家庭实验室将需要更复杂的服务。

在这里,Cloud-init 正在请求实例元数据信息。此文件可以包含有关实例本身的大量信息,例如实例 ID、要分配给实例的主机名、云 ID,甚至网络信息。

创建一个包含实例 ID 和主机名的基本元数据文件,并尝试将其提供给 Cloud-init 客户端。

首先,创建一个可以复制到容器映像中的元数据文件

instance-id: iid-local01
local-hostname: raspberry
hostname: raspberry

实例 ID 可以是任何内容。但是,如果您在 Cloud-init 运行后更改实例 ID 并且该文件已提供给客户端,则会触发 Cloud-init 再次运行。您可以使用此机制来更新实例配置,但您应该意识到它是这样工作的。

local-hostname 和 hostname 键就是这样;它们在 Cloud-init 运行时设置客户端的主机名信息。

将以下行添加到 Containerfile 以将元数据文件复制到新映像中

# Copy the meta-data file into the image for Nginx to serve it
COPY meta-data ${WWW_DIR}/meta-data

现在,使用元数据文件重建映像(使用新标签以便于故障排除),并使用 Podman 创建并运行新容器

# Build a new image named cloud-init:02
podman build -f Containerfile -t cloud-init:02 .

# Run a new container with this new meta-data file
podman run --rm -p 8080:8080 -it cloud-init:02

在新容器运行时,重新启动您的 Cloud-init 客户端并再次观看 Nginx 日志

127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-"
2020/05/09 22:54:32 [error] 2#0: *2 open() "/usr/share/nginx/html/user-data" failed (2: No such file or directory), client: 127.0.0.1, server: _, request: "GET /user-data HTTP/1.1", host: "instance-data:8080"
127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /user-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-"

您看到这次 /meta-data 路径已提供给客户端。成功!

但是,客户端正在查找 /user-data 路径下的第二个文件。此文件包含实例所有者提供的配置数据,而不是云提供商的数据。对于家庭实验室,这两者都是您。

大量用户数据模块可用于配置您的实例。对于本示例,只需使用 write_files 模块在客户端上创建一些测试文件并验证 Cloud-init 是否正在工作。

使用以下内容创建 user-data 文件

#cloud-config

# Create two files with example content using the write_files module
write_files:
 - content: |
     "Does cloud-init work?"
   owner: root:root
   permissions: '0644'
   path: /srv/foo
 - content: |
    "IT SURE DOES!"
   owner: root:root
   permissions: '0644'
   path: /srv/bar

除了使用 Cloud-init 提供的 user-data 模块的 YAML 文件外,您还可以将其制作成 Cloud-init 运行的可执行脚本。

创建 user-data 文件后,将以下行添加到 Containerfile 以在重建映像时将其复制到映像中

# Copy the user-data file into the container image
COPY user-data ${WWW_DIR}/user-data

重建映像并创建和运行新容器,这次带有 user-data 信息

# Build a new image named cloud-init:03
podman build -f Containerfile -t cloud-init:03 .

# Run a new container with this new user-data file
podman run --rm -p 8080:8080 -it cloud-init:03

现在,重新启动您的 Cloud-init 客户端,并观看 Web 服务器上的 Nginx 日志

127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-"
127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /user-data HTTP/1.1" 200 298 "-" "Cloud-Init/17.1" "-

成功!这次元数据和 user-data 文件都已提供给 Cloud-init 客户端。

验证 Cloud-init 是否运行

从上面的日志中,您知道 Cloud-init 在客户端主机上运行并请求了元数据和 user-data 文件,但是它对它们做了什么吗?您可以验证 Cloud-init 是否在 user-data 文件的 write_files 部分中写入了您添加的文件。

在您的 Cloud-init 客户端上,检查 /srv/foo/srv/bar 文件的内容

# cd /srv/ && ls
bar foo
# cat foo
"Does cloud-init work?"
# cat bar
"IT SURE DOES!"

成功!文件已写入并具有您期望的内容。

如上所述,还有许多其他模块可用于配置主机。例如,可以将 user-data 文件配置为使用 apt 添加软件包、复制 SSH authorized_keys、创建用户和组、配置和运行配置管理工具以及许多其他操作。我在家里的私有云中使用它来复制我的 authorized_keys,创建一个本地用户和组,并设置 sudo 权限。

接下来可以做什么

Cloud-init 在家庭实验室中很有用,尤其是在专注于云技术的实验室中。本文中演示的简单服务对于家庭实验室来说可能不是非常有用,但是既然您知道了 Cloud-init 的工作原理,您就可以继续创建动态服务,该服务可以使用自定义数据配置每个主机,使家庭私有云更类似于主要云提供商提供的服务。

通过稍微复杂的数据源,向您的家庭私有云添加新的物理(或虚拟)机器可以像插入它们并打开电源一样简单。

接下来要阅读什么
标签
Chris Collins
Chris Collins 是 Red Hat 的 SRE 和 OpenSource.com 特约撰稿人,他对自动化、容器编排及其周围的生态系统充满热情,并且喜欢在家中重现企业级技术以获得乐趣。

1 条评论

Nginx 日志

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.