[pve-devel] [PATCH 2/8 container] cloudinit: basic implementation
Mira Limbeck
m.limbeck at proxmox.com
Thu Feb 13 12:29:33 CET 2025
On 2/13/25 12:01, Fiona Ebner wrote:
> Am 10.02.25 um 13:07 schrieb Daniel Herzig:
>> From: Leo Nunner <l.nunner at proxmox.com>
>>
>> The code to generate the actual configuration works pretty much the same
>> as with the VM system. We generate an instance ID by hashing the user
>> configuration, causing cloud-init to run every time said configuration
>> changes.
>>
>> Instead of creating a config drive, we write files directly into the
>> volume of the container. We create a folder at
>> '/var/lib/cloud/seed/nocloud-net' and write the files 'user-data',
>> 'vendor-data' and 'meta-data'. Cloud-init looks at the instance ID
>> inside 'meta-data' to decide whether it should run (again) or not.
>>
>> Custom scripts need to be located inside the snippets directory, and
>> overwrite the default generated configuration file.
>>
>> Signed-off-by: Leo Nunner <l.nunner at proxmox.com>
>> ---
>> src/PVE/LXC.pm | 1 +
>> src/PVE/LXC/Cloudinit.pm | 114 ++++++++++++++++++++++++++++++++++++++
>> src/PVE/LXC/Makefile | 1 +
>> src/lxc-pve-prestart-hook | 5 ++
>> 4 files changed, 121 insertions(+)
>> create mode 100644 src/PVE/LXC/Cloudinit.pm
>>
>> diff --git a/src/PVE/LXC.pm b/src/PVE/LXC.pm
>> index 4d20645..35bb6b5 100644
>> --- a/src/PVE/LXC.pm
>> +++ b/src/PVE/LXC.pm
>> @@ -40,6 +40,7 @@ use PVE::Tools qw(
>> use PVE::Syscall qw(:fsmount);
>>
>> use PVE::LXC::CGroup;
>> +use PVE::LXC::Cloudinit;
>> use PVE::LXC::Config;
>> use PVE::LXC::Monitor;
>> use PVE::LXC::Tools;
>
> Hmm, seems like this import is unused. Can you double check?
>
>> diff --git a/src/PVE/LXC/Cloudinit.pm b/src/PVE/LXC/Cloudinit.pm
>> new file mode 100644
>> index 0000000..3e8617b
>> --- /dev/null
>> +++ b/src/PVE/LXC/Cloudinit.pm
>> @@ -0,0 +1,114 @@
>> +package PVE::LXC::Cloudinit;
>> +
>> +use strict;
>> +use warnings;
>> +
>> +use Digest::SHA;
>> +use File::Path;
>
> Missing includes:
>
> use URI::Escape;
>
> And we also need a dependency on liburi-perl in debian/control ;)
>
> use PVE::JSONSchema;
>
>> +use PVE::LXC;
>
> use PVE::Storage;
> use PVE::Tools;
>
>> +
>> +sub gen_cloudinit_metadata {
>> + my ($user) = @_;
>> +
>> + my $uuid_str = Digest::SHA::sha1_hex($user);
>
> Hmm, shouldn't this also depend on the vendor data? Otherwise, if only
> the vendor data changes, then it will still have the same instance ID.
>
> Seems like for VMs, we only use user and network data here.
>
> @Mira do you know more by chance?
I don't think vendor-data should be part of the instance-id. It's used
to create a first configuration that a user can override via the user
config.
The vendor-data won't be used again once it's already configured.
I'm not a 100% sure, but changing the instance-id leads to rerunning
lots of modules (e.g. User, Network and others), but the vendor-data
parts do not.
Only a complete `cloud-init clean` should trigger the modules using
vendor-data to run again.
https://cloudinit.readthedocs.io/en/latest/explanation/vendordata.html#vendor-data
>> + return cloudinit_metadata($uuid_str);
>> +}
>> +
>> +sub cloudinit_metadata {
>> + my ($uuid) = @_;
>> + my $raw = "";
>> +
>> + $raw .= "instance-id: $uuid\n";
>> +
>> + return $raw;
>> +}
>> +
>> +sub cloudinit_userdata {
>> + my ($conf) = @_;
>> +
>> + my $content = "#cloud-config\n";
>> +
>> + my $username = $conf->{ciuser};
>> + my $password = $conf->{cipassword};
>> +
>> + $content .= "user: $username\n" if defined($username);
>> + $content .= "password: $password\n" if defined($password);
>> +
>> + if (defined(my $keys = $conf->{sshkeys})) {
>> + $keys = URI::Escape::uri_unescape($keys);
>> + $keys = [map { my $key = $_; chomp $key; $key } split(/\n/, $keys)];
>> + $keys = [grep { /\S/ } @$keys];
>> + $content .= "ssh_authorized_keys:\n";
>> + foreach my $k (@$keys) {
>> + $content .= " - $k\n";
>> + }
>> + }
>> + $content .= "chpasswd:\n";
>> + $content .= " expire: False\n";
>> +
>> + if (!defined($username) || $username ne 'root') {
>> + $content .= "users:\n";
>> + $content .= " - default\n";
>> + }
>> +
>> + $content .= "package_upgrade: true\n" if $conf->{ciupgrade};
>
> For VMs, we default to true here. I'd like to keep it consistent.
>
>> +
>> + return $content;
>> +}
>> +
>> +sub read_cloudinit_snippets_file {
>> + my ($storage_conf, $volid) = @_;
>> +
>> + my ($full_path, undef, $type) = PVE::Storage::path($storage_conf, $volid);
>
> The qemu-server implementation does things a bit differently here using
> parse_volname() and abs_filesystem_path(). The latter makes sure to
> activate the storage/volume, which is desirable. I'd either add a call
> to activate the volume here too, or align the helpers.
>
>> + die "$volid is not in the snippets directory\n" if $type ne 'snippets';
>> + return PVE::Tools::file_get_contents($full_path, 1 * 1024 * 1024);
>> +}
>> +
>
> ---snip 8<---
>
>> diff --git a/src/lxc-pve-prestart-hook b/src/lxc-pve-prestart-hook
>> index fdaead2..c9f8ff0 100755
>> --- a/src/lxc-pve-prestart-hook
>> +++ b/src/lxc-pve-prestart-hook
>> @@ -13,6 +13,7 @@ use POSIX;
>> use PVE::CGroup;
>> use PVE::Cluster;
>> use PVE::LXC::Config;
>> +use PVE::LXC::Cloudinit;
>
> Nit: not ordered alphabetically
>
>> use PVE::LXC::Setup;
>> use PVE::LXC::Tools;
>> use PVE::LXC;
More information about the pve-devel
mailing list