├── README.md ├── .gitignore ├── simpleCloudInitService ├── data │ ├── meta-data │ ├── user-data │ └── Containerfile ├── img │ └── cloud-init.jpg └── README.md └── introToCloudInit └── README.md /README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | notes/ 2 | 3 | -------------------------------------------------------------------------------- /simpleCloudInitService/data/meta-data: -------------------------------------------------------------------------------- 1 | instance-id: iid-local01 2 | local-hostname: raspberry 3 | hostname: raspberry 4 | -------------------------------------------------------------------------------- /simpleCloudInitService/img/cloud-init.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clcollins/homelabCloudInit/HEAD/simpleCloudInitService/img/cloud-init.jpg -------------------------------------------------------------------------------- /simpleCloudInitService/data/user-data: -------------------------------------------------------------------------------- 1 | #cloud-config 2 | 3 | groups: 4 | - wheel 5 | 6 | write_files: 7 | # - encoding: b64 8 | # content: RG9lcyBpdCB3b3JrPwo= 9 | - content: | 10 | "Does cloud-init work?" 11 | owner: root:root 12 | permissions: '0644' 13 | path: /srv/foo 14 | - content: | 15 | "IT SURE DOES!" 16 | owner: root:root 17 | permissions: '0644' 18 | path: /srv/bar 19 | 20 | -------------------------------------------------------------------------------- /simpleCloudInitService/data/Containerfile: -------------------------------------------------------------------------------- 1 | FROM fedora:31 2 | LABEL maintainer "Chris Collins " 3 | 4 | ENV NGINX_CONF_DIR "/etc/nginx/default.d" 5 | ENV NGINX_LOG_DIR "/var/log/nginx" 6 | ENV NGINX_CONF "/etc/nginx/nginx.conf" 7 | ENV WWW_DIR "/usr/share/nginx/html" 8 | 9 | # Install Nginx and clear the yum cache 10 | RUN dnf install -y nginx \ 11 | && dnf clean all \ 12 | && rm -rf /var/cache/yum 13 | 14 | # forward request and error logs to docker log collector 15 | RUN ln -sf /dev/stdout ${NGINX_LOG_DIR}/access.log \ 16 | && ln -sf /dev/stderr ${NGINX_LOG_DIR}/error.log 17 | 18 | # Listen on port 8080, so root privileges are not required for podman 19 | RUN sed -i -E 's/(listen)([[:space:]]*)(\[\:\:\]\:)?80;$/\1\2\38080 default_server;/' $NGINX_CONF 20 | EXPOSE 8080 21 | 22 | # Allow Nginx PID to be managed by non-root user 23 | RUN sed -i '/user nginx;/d' $NGINX_CONF 24 | RUN sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/' $NGINX_CONF 25 | 26 | # Run as an unprivileged user 27 | USER 1001 28 | 29 | CMD ["nginx", "-g", "daemon off;"] 30 | -------------------------------------------------------------------------------- /introToCloudInit/README.md: -------------------------------------------------------------------------------- 1 | # Introduction to Cloud Init for your Homelab 2 | 3 | [Cloud-init](https://cloudinit.readthedocs.io/) is a standard - it would not be a stretch to say *the* standard - used by cloud providers to provide initialization and configuration data to cloud instances. It is most often used on the first boot of a new instance to automate network setup, account creation and ssh key installation: anything required to bring a new system online so it is accessible by the user. 4 | 5 | In a previous article, [Modify a disk image to create a Raspberry Pi-based homelab](LINK TO THIS ARTICLE), I showed how to customize the operating system image for single board computers like the Raspberry Pi to accomplish a similar goal. With Cloud Init, there is no need to add custom data to the image. Once it is enabled in your images, your virtual machines, physical servers, even tiny Raspberry Pis, can all behave like cloud instances in your own Private Cloud at Home. New machines can just be plugged in and turned on to automatically become part of your homelab. 6 | 7 | To be completely honest, Cloud Init is not designed with homelabs in mind. As just mentioned, you can easily modify the disk image for a given set of systems to enable SSH access and configure them after first boot. Cloud Init is designed for large-scale cloud providers who need to accommodate many customers, maintain a small set of images, and provide a mechanism for the customers to access instances without customizing an image for each of them. A homelab with a single administrator does not face the same challenges. 8 | 9 | Cloud Init is not without merit in the homelab though. One of the goals of our Private Cloud at Home project is education, and setting up Cloud Init for your homelab is a great way to gain experience with a technology used heavily by cloud providers, large and small. Cloud Init is an alternative to other initial configuration options, too. Rather than customizing each image, ISO, et cetera for every device in your homelab and face tedious updates when you want to make changes, you can just enable Cloud Init. This reduces technical debt - and is there anything worse than *personal* technical debt? Finally, using Cloud Init in your homelab allows your private cloud instances to behave the same as any public cloud instances you have or may have in the future - a true [hybrid-cloud](https://www.redhat.com/en/topics/cloud-computing/what-is-hybrid-cloud). 10 | 11 | ## An Overview of Cloud Init 12 | 13 | When an instance configured for Cloud Init boots up and the Cloud Init service (actually, four services in systemd implementations, to handle dependencies during the boot process) starts, it checks its configuration for a [DataSource](https://cloudinit.readthedocs.io/en/latest/topics/datasources.html) to determine what type of cloud it is running in. Each of the major cloud providers has a datasource configuration, telling the instance where and how to retrieve configuration information. The instance then uses the datasource information to retrieve configuration information provided by the cloud provider, such as networking information and instance identification information, and configuration data provided by the customer, such as authorized keys to be copied, user accounts to be created, and many other possible tasks. 14 | 15 | After retrieving the data, Cloud Init then configures the instance: setting up networking, copying the authorized keys, et cetera, and finally completes the boot process. It is at that point accessible to the remote user, ready for further configuration with tools like [Ansible](https://www.ansible.com/) or [Puppet](https://puppet.com/), or to receive a workload and begin its assigned tasks. 16 | 17 | ## Configuration Data 18 | 19 | As mentioned above, the configuration data used by Cloud Init comes from two potential sources, the cloud provider and the instance user. In the case of your homelab, you fill both roles: providing networking and instance information as the cloud provider, and configuration information as the user. 20 | 21 | ### Cloud Provider meta-data file 22 | 23 | In your cloud provider role, your homelab datasource will offer your private cloud instances a `meta-data` file. The [meta-data](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html#) file can contain information such as the instance-id, cloud type, or python version (Cloud Init is written in and uses Python), or a public SSH key to be assigned to the host. The meta-data file may also contain networking information if not using DHCP (or the other mechanisms Cloud Init supports such as a config file in the image, or kernel parameters). 24 | 25 | ### User-provided user-data file 26 | 27 | The real meat of Cloud Init's value is in the `user-data` file. Provided by the user to the cloud provider and included in the datasource, the [user-data](https://cloudinit.readthedocs.io/en/latest/topics/format.html) file is what turns an instance from a generic machine into a member of the user's fleet. The user-data file can come in the form of an executable script, working the same as the script would in normal circumstances, or as a [cloud config](https://cloudinit.readthedocs.io/en/latest/topics/examples.html) YAML file, which makes use of [Cloud Init's modules](https://cloudinit.readthedocs.io/en/latest/topics/modules.html) to perform configuration tasks. 28 | 29 | ## DataSource 30 | 31 | The datasource is a service provided by the cloud provider that offers the meta-data and user-data files to the instances. Instance images or ISOs are configured to tell the instance what datasource is being used. 32 | 33 | In the case of Amazon AWS, a [link-local](https://en.wikipedia.org/wiki/Link-local_address) is provided that will respond to HTTP requests from an instance with the instance's custom data. Other cloud providers have their own mechanisms as well. Luckily for our Private Cloud at Home project, there are also "NoCloud" data sources. 34 | 35 | The [NoCloud](https://cloudinit.readthedocs.io/en/latest/topics/datasources/nocloud.html) datasources allow configuration information to be provided via the kernel command like as key/value pairs, or as user-data and meta-data files provided as mounted ISO filesystems. These are useful for virtual machines, especially paired with automation to create the virtual machines themselves. 36 | 37 | There is also a `NoCloudNet` datasource that behaves similar to the AWS EC2 datasource, providing an IP or DNS name from which to retrieve user-data and meta-data via HTTP. This is most helpful for the physical machines in your homelab such as Raspberry Pis, NUCs or surplus server equipment. While NoCloud could work too, it requires more manual attention; an anti-pattern for cloud instances. 38 | 39 | ## Cloud Init for the homelab 40 | 41 | I hope this has given you an idea of what Cloud Init is, and how it may be helpful used in your homelab. It is an incredible tool already embraced by major cloud providers, and using it at home can be both educational and fun, and help to automate the addition of new physical or virtual servers to your lab. Future articles will detail how to create both simple static and more complex dynamic Cloud Init services and guide you in incorporating it into you Private Cloud at Home. 42 | -------------------------------------------------------------------------------- /simpleCloudInitService/README.md: -------------------------------------------------------------------------------- 1 | # Create a simple Cloud Init service for your homelab 2 | 3 | Cloud Init is an industry standard, widely utilized method for initializing cloud instances. Cloud providers use Cloud Init to customize instances with network configuration, instance information, and even user-provided configuration directives. Cloud Init is a great tool to add to your Private Cloud at Home to learn more about how the large cloud providers work, and to add a little automation to the initial setup and configuration of both virtual and physical machines within your homelab. For a bit more detail, check out my previous article on [what Cloud Init is and why it is useful](LINK TO PREVIOUS ARTICLE). 4 | 5 | ![A screen showing the boot process for a Linux server running cloud-init](img/cloud-init.jpg) 6 | 7 | Admittedly, Cloud Init has more utility to a cloud provider provisioning machines for many different clients. For a homelab run by a single sysadmin, much of what Cloud Init solves might be a little superfluous. However, getting it setup and learning how it works is a great way to learn more about this cloud technology, and its still useful for configuring your devices on first boot. 8 | 9 | Today we will learn about Cloud Init and its "NoCloud" datasource, designed to allow the use of Cloud Init outside of a traditional cloud provider setting. To do that we will install Cloud Init on a client device and set up a container running a web service to respond to the client's requests. We will investigate what the client is requesting from the web service and modify the web service container to serve a basic, static Cloud Init service. 10 | 11 | ## Set up Cloud Init on an existing system 12 | 13 | Cloud Init probably provides the most utility on first boot of a new system, querying for configuration data and making those changes to customize the system as directed. It can be included in a disk image for Raspberry Pis and single-board computers, or added to images used to provision virtual machines. ([Learn more about customizing disk images for your Raspberry Pi homelab.](https://opensource.com/article/20/5/disk-image-raspberry-pi)) For testing, however, it is easy to install and run Cloud Init on an existing system, or install a new system and then setup Cloud Init for testing. 14 | 15 | As a major service used by most cloud providers, Cloud Init is supported on most Linux distributions. For this example, I will be using Fedora 31 Server for the Raspberry Pi, but this can be done on Raspbian, Ubuntu, CentOS, and most other distributions the same way. 16 | 17 | ### Install and enable the cloud-init services 18 | 19 | On a system that you would like to be a Cloud Init client, install the cloud-init package with `dnf` (if using Fedora): 20 | 21 | ```shell 22 | # Install the cloud-init package 23 | dnf install -y cloud-init 24 | ``` 25 | 26 | Cloud Init is actually four different services (at least with Systemd), each in charge of retrieving config data and performing configuration changes during a different part of the boot process, allowing for greater flexibility in what can be done. While it is unlikely you will interact with these services directly very often, it is useful to know what they are in the event you need to troubleshoot something: 27 | 28 | * cloud-init-local.service 29 | * cloud-init.service 30 | * cloud-config.service 31 | * cloud-final.service 32 | 33 | Enable all four services: 34 | 35 | ```shell 36 | # Enable the four cloud-init services 37 | systemctl enable cloud-init-local.service 38 | systemctl enable cloud-init.service 39 | systemctl enable cloud-config.service 40 | systemctl enable cloud-final.service 41 | ``` 42 | 43 | ### Configure the datasource to query 44 | 45 | Once the service is enabled, configure the datasource from which the client will query the config data. There are a [large number of datasource types](https://cloudinit.readthedocs.io/en/latest/topics/datasources.html), most configured for specific cloud providers. We will be using the "NoCloud" datasource which, as mentioned, is designed for using Cloud Init without a cloud provider. 46 | 47 | NoCloud allows configuration information to be included a number of ways: as key/value pairs in kernel parameters, using a CD (or virtual CD, in the case of virtual machines) mounted at startup, a file included on the filesystem, or, in the case we will use today, via HTTP from a URL we provide (the "NoCloud Net" option). 48 | 49 | The datasource configuration itself can be provided via the kernel parameter or by setting it in the cloud-init configuration file, `/etc/cloud/cloud.cfg`. The configuration file works very well for setting up cloud-init with customized disk images, or for testing on existing hosts. 50 | 51 | Cloud Init will also merge configuration data from any `*.cfg` files found in `/etc/cloud/cloud.cfg.d/`, so to keep things cleaner, we will configure the datasource in `/etc/cloud/cloud.cfg.d/10_datasource.cfg`. Cloud Init can be told to read from an HTTP data source with the `seedfrom` key, using the syntax: 52 | 53 | `seedfrom: http://ip_address:port/` 54 | 55 | The IP address and port are those of the web service we will create further down. In my case, I used the IP of my laptop, and port 8080. This can also be a DNS name. 56 | 57 | Create the `/etc/cloud/cloud.cfg.d/10_datasource.cfg` file: 58 | 59 | ```txt 60 | # Add the datasource: 61 | # /etc/cloud/cloud.cfg.d/10_datasource.cfg 62 | 63 | # NOTE THE TRAILING SLASH HERE! 64 | datasource: 65 | NoCloud: 66 | seedfrom: http://ip_address:port/ 67 | ``` 68 | 69 | That's it for the client setup. Now, when it is rebooted, the client will attempt to retrieve configuration data from the URL you entered in the `seedfrom` key and make any configuration changes that might be necessary. 70 | 71 | Next, we need to setup a web server to listen for client requests so we can figure out what needs to be served. 72 | 73 | ## Setup a webserver to investigate client requests 74 | 75 | We can create and run a webserver quickly with [Podman](https://podman.io/) or other container orchestration tools (like Docker or Kubernetes). This example will use Podman, but the same commands can be used with Docker. 76 | 77 | To get started, we will use the `Fedora:31` container image, and create a Containerfile (for Docker this would be a Dockerfile) that installs and configures Nginx. From that Containerfile we can build a custom image and run it on the host we want to act as the cloud-init service. 78 | 79 | Create a Containerfile with the following contents: 80 | 81 | ```txt 82 | FROM fedora:31 83 | 84 | ENV NGINX_CONF_DIR "/etc/nginx/default.d" 85 | ENV NGINX_LOG_DIR "/var/log/nginx" 86 | ENV NGINX_CONF "/etc/nginx/nginx.conf" 87 | ENV WWW_DIR "/usr/share/nginx/html" 88 | 89 | # Install Nginx and clear the yum cache 90 | RUN dnf install -y nginx \ 91 | && dnf clean all \ 92 | && rm -rf /var/cache/yum 93 | 94 | # forward request and error logs to docker log collector 95 | RUN ln -sf /dev/stdout ${NGINX_LOG_DIR}/access.log \ 96 | && ln -sf /dev/stderr ${NGINX_LOG_DIR}/error.log 97 | 98 | # Listen on port 8080, so root privileges are not required for podman 99 | RUN sed -i -E 's/(listen)([[:space:]]*)(\[\:\:\]\:)?80;$/\1\2\38080 default_server;/' $NGINX_CONF 100 | EXPOSE 8080 101 | 102 | # Allow Nginx PID to be managed by non-root user 103 | RUN sed -i '/user nginx;/d' $NGINX_CONF 104 | RUN sed -i 's/pid \/run\/nginx.pid;/pid \/tmp\/nginx.pid;/' $NGINX_CONF 105 | 106 | # Run as an unprivileged user 107 | USER 1001 108 | 109 | CMD ["nginx", "-g", "daemon off;"] 110 | ``` 111 | 112 | _Note: the example Containerfile and other files used in this example can be found in the accompanying repository: [https://github.com/clcollins/homelabCloudInit/tree/master/simpleCloudInitService/data](https://github.com/clcollins/homelabCloudInit/tree/master/simpleCloudInitService/data)_ 113 | 114 | The most important part of the Containerfile above is the section changing how the logs are stored (writing to STDOUT rather than a file), so we can see requests coming into the server in the container logs. A few other changes are included so we can run the container with Podman without root privileges, and the processes in the container can run without root as well. 115 | 116 | This first pass at the webserver does not serve any cloud-init data; we will just use this to see what the cloud-init client is requesting from it. 117 | 118 | With the Containerfile created, use Podman to build and run a webserver image: 119 | 120 | ```shell 121 | # Build the container image 122 | $ podman build -f Containerfile -t cloud-init:01 . 123 | 124 | # Create a container from the new image, and run it 125 | # It will listen on port 8080 126 | $ podman run --rm -p 8080:8080 -it cloud-init:01 127 | ``` 128 | 129 | This will run the container, leaving your terminal attached and with a pseudo-TTY. It will appear that nothing is happening at first, but requests to port 8080 of the host machine will be routed to the Nginx server inside the container, and a log message will appear in the terminal window. This can be tested with `curl` from the host machine: 130 | 131 | ```shell 132 | # Use curl to send an HTTP request to the Nginx container 133 | $ curl http://localhost:8080 134 | ``` 135 | 136 | After running that curl command, you should see a log message similar to this appear in the terminal window: 137 | 138 | ```txt 139 | 127.0.0.1 - - [09/May/2020:19:25:10 +0000] "GET / HTTP/1.1" 200 5564 "-" "curl/7.66.0" "-" 140 | ``` 141 | 142 | Now comes the fun part: reboot the cloud-init client and watch the Nginx logs to see what cloud-init requests from the webserver when the client boots up! 143 | 144 | As the client finishes its boot process, you should see log messages similar to the following: 145 | 146 | ```txt 147 | 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" 148 | 127.0.0.1 - - [09/May/2020:22:44:28 +0000] "GET /meta-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-" 149 | ``` 150 | 151 | _Note: use CTRL-C to stop the running container._ 152 | 153 | You can see the request is for the `/meta-data` path - ie: `http://ip_address_of_the_webserver:8080/meta-data`. This is just a GET request - cloud-init is not POSTing (sending) any data to the webserver. It is just blindly requesting the files from the datasource URL, so it is up to the datasource to identify what host is asking. In this simple example, we are just sending generic data to any client, but a larger homelab will need a more sophisticated service. 154 | 155 | This is cloud-init requesting the [instance meta-data](https://cloudinit.readthedocs.io/en/latest/topics/instancedata.html#what-is-instance-data) information. This file can include a lot of information about the instance itself, such as the instance id, the hostname to assign the instance, the cloud id - even networking information. 156 | 157 | We will create a basic meta-data file with an instance id, and hostname for the host, and try serving that to the cloud-init client. 158 | 159 | First, create a meta-data file that can be copied into the container image: 160 | 161 | ```txt 162 | instance-id: iid-local01 163 | local-hostname: raspberry 164 | hostname: raspberry 165 | ``` 166 | 167 | The `instance-id` can be anything. However, if you subsequently change the `instance-id` after cloud-init runs and the file is served to the client, it will trigger cloud-init to re-run. You can use this mechanism to update instance configuration if you like, but be aware that it works that way. 168 | 169 | The `local-hostname` and `hostname` keys are just that; they set the hostname information for the client when cloud-init runs. 170 | 171 | Add the following line to the Containerfile to copy the meta-data file into the new image: 172 | 173 | ```txt 174 | # Copy the meta-data file into the image for Nginx to serve it 175 | COPY meta-data ${WWW_DIR}/meta-data 176 | ``` 177 | 178 | Now rebuild the image (use a new tag for easy troubleshooting) with the meta-data file, and create and run a new container with Podman: 179 | 180 | ```shell 181 | # Build a new image named cloud-init:02 182 | podman build -f Containerfile -t cloud-init:02 . 183 | 184 | # Run a new container with this new meta-data file 185 | podman run --rm -p 8080:8080 -it cloud-init:02 186 | ``` 187 | 188 | With the new container running, reboot your cloud-init client and watch the Nginx logs again: 189 | 190 | ```txt 191 | 127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-" 192 | 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" 193 | 127.0.0.1 - - [09/May/2020:22:54:32 +0000] "GET /user-data HTTP/1.1" 404 3650 "-" "Cloud-Init/17.1" "-" 194 | ``` 195 | 196 | You see this time the `/meta-data` path was served to the client. Success! 197 | 198 | However, the client is looking for a second file, at the `/user-data` path. This file contains configuration data provided by the instance owner, as opposed to data from the cloud provider. For a homelab, both of these are you. 199 | 200 | There are a [large number of user-data modules](https://cloudinit.readthedocs.io/en/latest/topics/modules.html) you can use to configure your instance. For this example, we will just use the `write_files` module to create some test files on the client and verify that cloud-init is working. 201 | 202 | Create a user-data file with the following content: 203 | 204 | ```yaml 205 | #cloud-config 206 | 207 | # Create two files with example content using the write_files module 208 | write_files: 209 | - content: | 210 | "Does cloud-init work?" 211 | owner: root:root 212 | permissions: '0644' 213 | path: /srv/foo 214 | - content: | 215 | "IT SURE DOES!" 216 | owner: root:root 217 | permissions: '0644' 218 | path: /srv/bar 219 | ``` 220 | 221 | In addition to a YAML file using the user-data modules provided by cloud init, you could also make this an executable script to be run by cloud-init. 222 | 223 | After creating the user-data file, add the following line to the Containerfile to copy it into the image when the image is rebuilt: 224 | 225 | ```txt 226 | # Copy the user-data file into the container image 227 | COPY user-data ${WWW_DIR}/user-data 228 | ``` 229 | 230 | Rebuild the image and create and run a new container, this time with the user-data information: 231 | 232 | ```shell 233 | # Build a new image named cloud-init:03 234 | podman build -f Containerfile -t cloud-init:03 . 235 | 236 | # Run a new container with this new user-data file 237 | podman run --rm -p 8080:8080 -it cloud-init:03 238 | ``` 239 | 240 | Now, reboot your cloud-init client, and watch the Nginx logs on the webserver: 241 | 242 | ```txt 243 | 127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /meta-data HTTP/1.1" 200 63 "-" "Cloud-Init/17.1" "-" 244 | 127.0.0.1 - - [09/May/2020:23:01:51 +0000] "GET /user-data HTTP/1.1" 200 298 "-" "Cloud-Init/17.1" "- 245 | ``` 246 | 247 | Success! This time both the meta-data and user-data files were served to the cloud-init client. 248 | 249 | ## Validate that cloud-init ran 250 | 251 | From the logs above, we know that cloud-init ran on the client host, and requested the meta-data and user-data files, but did it do anything with them? We can validate that cloud-init wrote the files we added in the user-data file, in the "write_files" section. 252 | 253 | On your cloud-init client, check the contents of the "/srv/foo" and "/srv/bar" files: 254 | 255 | ```shell 256 | # cd /srv/ && ls 257 | bar foo 258 | # cat foo 259 | "Does cloud-init work?" 260 | # cat bar 261 | "IT SURE DOES!" 262 | ``` 263 | 264 | Success! The files were written and have the content we expected. 265 | 266 | As mentioned above, there are plenty of other modules that can be used to configure the host. For example, the user-data file can be configured to add packages with `apt`, copy SSH authorized_keys, create users and groups, configure and run configuration management tools, and many other things. In practice, for my own Private Cloud at Home, I use it to copy my authorized_keys, create a local user and group, and setup sudo permissions. 267 | 268 | ## Conclusion 269 | 270 | In this article, we learned what Cloud Init is and why it would be useful in a homelab, especially a lab focusing on cloud technologies. You also configured a Cloud Init client, and used a webserver to investigate what the client requested from the Cloud Init datasource. Finally, we added meta-data and user-data to the webserver to create a simple Cloud Init datasource. 271 | 272 | This simple service may not be super useful for homelab, but this exercise showed how Cloud Init works. With this info, you can move on to creating a dynamic service that can configure each host with custom data, making a private cloud at home more similar to the cloud services provided by the major cloud providers. 273 | 274 | With a slightly more complicated datasource, adding new physical (or virtual) machines to your private cloud at home can be as simple as plugging them in and turning them on. 275 | --------------------------------------------------------------------------------