cloud-init
Cloud-init是用于初始化云实例的一种广泛使用的行业标准方法。 云提供商使用Cloud-init来通过网络配置,实例信息甚至用户提供的配置指令来自定义实例。 它也是在您的“家中私有云”中使用的绝佳工具,可以为homelab虚拟机和物理机的初始设置和配置增加一点自动化,并了解有关大型云提供商如何工作的更多信息。 有关更多详细信息和背景知识,请参阅我上一篇有关Cloud-init的文章以及它为什么有用的文章 。
诚然,对于由许多系统客户端配置的云服务提供商而言,Cloud-init比由单个系统管理员运行的homelab更有用,而且Cloud-init解决的许多问题对于homelab可能是多余的。 但是,进行设置并学习其工作方式是了解此云技术的一种好方法,更不用说这是在首次启动时配置设备的好方法。
本教程使用Cloud-init的“ NoCloud”数据源,该数据源允许在传统的云提供程序设置之外使用Cloud-init。 本文将向您展示如何在客户端设备上安装Cloud-init并设置一个运行Web服务的容器以响应客户端的请求。 您还将学习调查客户端从Web服务请求的内容,并修改Web服务的容器以提供基本的静态Cloud-init服务。
在现有系统上设置Cloud-init
在新系统的首次启动中,Cloud-init可能最有用,它可以查询配置数据并进行更改以按指示自定义系统。 它可以包含在Raspberry Pi和单板计算机的磁盘映像中 ,也可以添加到用于配置虚拟机的映像中。 对于测试,很容易在现有系统上安装和运行Cloud-init或安装新系统然后设置Cloud-init。
作为大多数云提供商使用的一项主要服务,大多数Linux发行版都支持Cloud-init。 对于此示例,我将在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
- 云最终服务
启用所有四个服务:
# 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
配置数据源/etc/cloud/cloud.cfg.d/10_datasource.cfg
。 通过使用以下语法,可以告诉Cloud-init使用seedfrom密钥从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服务器来侦听客户端请求,以便您确定需要提供的服务。
设置网络服务器以调查客户请求
您可以使用Podman或其他容器编排工具(例如Docker或Kubernetes)快速创建和运行Web服务器。 该示例使用Podman,但是相同的命令也适用于Docker。
首先,使用Fedora:31容器映像并创建一个用于安装和配置Nginx的Containerfile(对于Docker,这将是Dockerfile)。 通过该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;"]
注意:示例容器文件和本示例中使用的其他文件可以在该项目的GitHub存储库中找到 。
上面Containerfile最重要的部分是更改日志存储方式的部分(写入STDOUT而不是文件),因此您可以在容器日志中看到进入服务器的请求。 其他一些更改使您可以在没有root特权的情况下使用Podman运行容器,也可以在没有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日志,以查看客户端启动时来自Web服务器的Cloud-init请求。
客户端完成启动过程后,您应该看到类似于以下内容的日志消息:
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不会将任何数据发布(发送)到Web服务器。 它只是盲目地从数据源URL请求文件,因此由数据源确定主机在询问什么。 这个简单的示例只是将通用数据发送到任何客户端,但是更大的homelab需要更复杂的服务。
在这里,Cloud-init正在请求实例元数据信息。 该文件可以包含有关实例本身的很多信息,例如实例ID,分配实例的主机名,云ID甚至是网络信息。
创建具有实例ID和主机主机名的基本元数据文件,然后尝试将其提供给Cloud-init客户端。
首先,创建一个可以复制到容器映像中的元数据文件:
instance-id: iid-local01
local-hostname: raspberry
hostname: raspberry
实例ID可以是任何东西。 但是,如果在Cloud-init运行并将文件提供给客户端后更改实例ID,它将触发Cloud-init重新运行。 您可以使用此机制来更新实例配置,但应注意,它可以那样进行。
本地主机名和主机名键就是这样。 它们在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是否正常工作。
创建一个具有以下内容的用户数据文件:
#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提供的用户数据模块的YAML文件之外,您还可以将此文件设置为Cloud-init运行的可执行脚本。
创建用户数据文件后,将以下行添加到Containerfile中,以便在重建映像时将其复制到映像中:
# Copy the user-data file into the container image
COPY user-data ${WWW_DIR}/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" "-
成功! 这次,元数据和用户数据文件都被提供给了Cloud-init客户端。
验证Cloud-init是否已运行
从上面的日志中,您知道Cloud-init在客户端主机上运行并请求元数据和用户数据文件,但是它对它们做了什么? 您可以在write_files部分中验证Cloud-init是否写入了您添加到用户数据文件中的文件。
在您的Cloud-init客户端上,检查/srv/foo
和/srv/bar
文件的内容:
# cd /srv/ && ls
bar foo
# cat foo
"Does cloud-init work?"
# cat bar
"IT SURE DOES!"
如上所述,还有许多其他模块可用于配置主机。 例如,可以将用户数据文件配置为使用apt添加软件包,复制SSHauthorized_keys,创建用户和组,配置和运行配置管理工具以及许多其他功能。 我在家中的私有云中使用它来复制我的authenticated_keys,创建本地用户和组以及设置sudo权限。
接下来可以做什么
Cloud-init在家庭实验室(特别是专注于云技术的实验室)中很有用。 本文中演示的简单服务可能对于homelab并不是超级有用,但是既然您知道Cloud-init的工作原理,那么您就可以继续创建动态服务,该服务可以为每个主机配置自定义数据,从而在主页更类似于主要云提供商提供的服务。
使用稍微复杂一些的数据源,在家中将新的物理(或虚拟)计算机添加到您的私有云中就像插入并打开它们一样简单。
翻译自: https://opensource.com/article/20/5/create-simple-cloud-init-service-your-homelab
cloud-init