[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