Skip to content

Commit 4fc0cbe

Browse files
authored
Merge pull request #365 from ComputeCanada/incus
Add Incus to Magic Castle providers
2 parents c1f2fda + 65715f1 commit 4fc0cbe

19 files changed

Lines changed: 377 additions & 38 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ From these new possibilities emerged an open-source software project named Magic
2121
- [Microsoft Azure](azure/README.md)
2222
- [Google Cloud Platform (GCP)](gcp/README.md)
2323
- [OVH Public Cloud (OVH)](ovh/README.md)
24+
- [Incus](incus/README.md)
2425
- For more details, refer to [Magic Castle Documentation](docs)
2526

2627
## How Magic Castle Works

aws/infrastructure.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module "design" {
77
source = "../common/design"
88
cluster_name = var.cluster_name
99
domain = var.domain
10+
image = var.image
1011
instances = var.instances
1112
min_disk_size = 20
1213
pool = var.pool
@@ -122,7 +123,7 @@ data "aws_ec2_instance_type" "instance_type" {
122123
resource "aws_instance" "instances" {
123124
for_each = module.design.instances_to_build
124125
instance_type = each.value.type
125-
ami = lookup(each.value, "image", var.image)
126+
ami = each.value.image
126127
user_data = base64gzip(module.configuration.user_data[each.key])
127128
availability_zone = local.availability_zone
128129
placement_group = contains(each.value.tags, "efa") ? aws_placement_group.efa_group.id : null

azure/infrastructure.tf

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module "design" {
77
source = "../common/design"
88
cluster_name = var.cluster_name
99
domain = var.domain
10+
image = var.image
1011
instances = var.instances
1112
min_disk_size = 30
1213
pool = var.pool
@@ -88,7 +89,7 @@ resource "azurerm_linux_virtual_machine" "instances" {
8889
}
8990

9091
dynamic "source_image_reference" {
91-
for_each = can(tomap(lookup(each.value, "image", var.image))) ? [lookup(each.value, "image", var.image)] : []
92+
for_each = can(tomap(each.value.image)) ? [each.value.image] : []
9293
iterator = key
9394
content {
9495
publisher = key.value["publisher"]
@@ -97,7 +98,7 @@ resource "azurerm_linux_virtual_machine" "instances" {
9798
version = lookup(key.value, "version", "latest")
9899
}
99100
}
100-
source_image_id = can(tomap(lookup(each.value, "image", var.image))) ? null : tostring(lookup(each.value, "image", var.image))
101+
source_image_id = can(tomap(each.value.image)) ? null : tostring(each.value.image)
101102

102103
computer_name = each.key
103104
admin_username = "azure"

common/configuration/main.tf

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
variable "inventory" { }
2+
variable "post_inventory" {
3+
default = {}
4+
}
5+
locals {
6+
post_inventory = merge(var.inventory, var.post_inventory)
7+
}
8+
29
variable "config_git_url" { }
310
variable "config_version" { }
411

@@ -49,14 +56,13 @@ locals {
4956
guest_passwd = var.guest_passwd != "" ? var.guest_passwd : try(random_pet.guest_passwd[0].id, "")
5057
public_keys = [for key in var.public_keys: trimspace(key)]
5158

52-
puppetservers = { for host, values in var.inventory: host => values.local_ip if contains(values.tags, "puppet")}
53-
all_tags = toset(flatten([for key, values in var.inventory : values.tags]))
59+
all_tags = toset(flatten([for key, values in local.post_inventory : values.tags]))
5460
tag_ip = { for tag in local.all_tags :
55-
tag => [for key, values in var.inventory : values.local_ip if contains(values.tags, tag)]
61+
tag => [for key, values in local.post_inventory : values.local_ip if contains(values.tags, tag)]
5662
}
5763

5864
# add openssh public key to inventory
59-
inventory = { for host, values in var.inventory:
65+
final_inventory = { for host, values in local.post_inventory:
6066
host => merge(values, {
6167
hostkeys = {
6268
rsa = chomp(tls_private_key.rsa[values.prefix].public_key_openssh)
@@ -67,10 +73,11 @@ locals {
6773

6874
terraform_data = yamlencode({
6975
terraform = {
70-
instances = local.inventory
76+
instances = local.final_inventory
7177
tag_ip = local.tag_ip
7278
data = {
7379
sudoer_username = var.sudoer_username
80+
tf_public_key = chomp(tls_private_key.ssh.public_key_openssh)
7481
public_keys = local.public_keys
7582
cluster_name = lower(var.cluster_name)
7683
domain_name = var.domain_name
@@ -96,16 +103,11 @@ locals {
96103
domain_name = var.domain_name
97104
puppetenv_git = var.config_git_url,
98105
puppetenv_rev = var.config_version,
99-
puppetservers = local.puppetservers,
106+
puppetservers = { for host, values in var.inventory: host => try(values.local_ip, "") if contains(values.tags, "puppet")}
100107
puppetserver_password = local.puppet_passwd,
101108
sudoer_username = var.sudoer_username,
102109
ssh_authorized_keys = local.public_keys
103110
tf_ssh_public_key = tls_private_key.ssh.public_key_openssh
104-
# If there is no bastion, the terraform data has to be packed with the user_data of the puppetserver.
105-
# We do not packed it systematically because it increases the user-data size to a value that can be
106-
# near or exceeds the cloud provider limit - AWS 16KB, Azure and OpenStack 64KB, GCP 256 KB.
107-
include_tf_data = ! contains(local.all_tags, var.bastion_tag)
108-
terraform_data = local.terraform_data
109111
terraform_facts = local.terraform_facts
110112
skip_upgrade = var.skip_upgrade
111113
puppetfile = var.puppetfile
@@ -138,15 +140,15 @@ output "terraform_facts" {
138140
}
139141

140142
output "puppetservers" {
141-
value = local.puppetservers
143+
value = { for host, values in local.final_inventory: host => values.local_ip if contains(values.tags, "puppet")}
142144
}
143145

144146
output "guest_passwd" {
145147
value = local.guest_passwd
146148
}
147149

148150
output "inventory" {
149-
value = local.inventory
151+
value = local.final_inventory
150152
}
151153

152154
output "ssh_key" {
@@ -158,7 +160,7 @@ output "ssh_key" {
158160

159161
output "bastions" {
160162
value = {
161-
for host, values in var.inventory: host => values
163+
for host, values in local.final_inventory: host => values
162164
if contains(values.tags, var.bastion_tag) && contains(values.tags, "public") && (!contains(values.tags, "pool"))
163165
}
164166
}

common/configuration/puppet.yaml

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,21 @@ mounts:
88
users:
99
- name: tf
1010
system: true
11+
passwd: '*'
1112
no_user_group: true
1213
homedir: /tmp
1314
%{ if contains(tags, "puppet") }
1415
sudo: "ALL = NOPASSWD: /usr/sbin/update_etc_puppetlabs.sh *.zip"
1516
%{ endif ~}
1617
- name: ${sudoer_username}
1718
groups: adm, wheel, systemd-journal
19+
passwd: '*'
1820
homedir: /${sudoer_username}
19-
selinux_user: unconfined_u
2021
sudo: ALL=(ALL) NOPASSWD:ALL
22+
%{ if cloud_provider != "incus" }
23+
# most container images do not have selinux pre-installed
24+
selinux_user: unconfined_u
25+
%{ endif }
2126
ssh_authorized_keys:
2227
%{ for key in ssh_authorized_keys ~}
2328
- ${key}
@@ -26,15 +31,23 @@ users:
2631
runcmd:
2732
- chmod 755 /etc # avoid issue with Rocky 9.4
2833
- test ! -d /${sudoer_username} && userdel -f -r ${sudoer_username} && cloud-init clean -r
34+
%{ if cloud_provider != "incus" }
35+
# most container images do not have selinux pre-installed
2936
- restorecon -R /${sudoer_username}
37+
%{ endif }
38+
- dnf -y install openssh openssh-server rsync
3039
- echo -e "Include /etc/ssh/sshd_config.d/50-authenticationmethods.conf" >> /etc/ssh/sshd_config
3140
- sed -i '/HostKey \/etc\/ssh\/ssh_host_ecdsa_key/ s/^#*/#/' /etc/ssh/sshd_config
3241
- chmod 644 /etc/ssh/ssh_host_*_key.pub
3342
- chgrp ssh_keys /etc/ssh/ssh_host_*_key.pub
3443
- systemctl restart sshd
3544
# Make sure puppet server can be reached by name early in the process if we need to debug.
45+
# For some provider, the ip address of the puppetserver is not known in advance, so we make
46+
# sure it is not empty before adding it to /etc/hosts
3647
%{ for host, ip in puppetservers ~}
48+
%{ if ip != "" ~}
3749
- echo "${ip} ${host}" >> /etc/hosts
50+
%{ endif ~}
3851
%{ endfor ~}
3952
# Enable fastest mirror for distribution using dnf package manager
4053
- dnf config-manager --setopt=fastestmirror=True --save
@@ -44,7 +57,7 @@ runcmd:
4457
# Install required packages in runcmd instead of packages to speedup configuration
4558
# of the admin user. This reduces the risk of Terraform timing out when trying to
4659
# upload the terraform_data.yaml
47-
dnf -y install git pciutils unzip rsync
60+
dnf -y install git pciutils unzip
4861
dnf -y remove cockpit\* firewalld --exclude=iptables
4962
%{ if ! skip_upgrade ~}
5063
# Upgrade all packages except Puppet if already installed
@@ -98,9 +111,6 @@ runcmd:
98111
- /opt/puppetlabs/puppet/bin/r10k puppetfile install --moduledir=/etc/puppetlabs/code/modules --puppetfile=/etc/puppetlabs/code/Puppetfile
99112
%{ endif ~}
100113
# Wait for Terraform to scp its YAML data
101-
%{ if ! include_tf_data ~}
102-
- while [ ! -e "/etc/puppetlabs/data/terraform_data.yaml" ]; do echo "$(date -I'seconds') Waiting for terraform to scp terraform_data.yaml"; sleep 5; done
103-
%{ endif ~}
104114
%{ if node_name != keys(puppetservers)[0] }
105115
- sed -e '/certificate-authority-service/ s/^/#/' -i /etc/puppetlabs/puppetserver/services.d/ca.cfg
106116
- sed -e '/certificate-authority-disabled-service/ s/^#//' -i /etc/puppetlabs/puppetserver/services.d/ca.cfg
@@ -133,7 +143,8 @@ runcmd:
133143
- test -f /etc/magic-castle-release && systemctl start puppet || true
134144

135145
write_files:
136-
- content: restrict%{ if contains(tags, "puppet") },pty%{ else }%{ for host, ip in puppetservers },permitopen="${ip}:22"%{ endfor },port-forwarding,command="/sbin/nologin"%{ endif } ${tf_ssh_public_key}
146+
# If the ip addresses of the puppet servers are not known in advance, we cannot restrict the ssh connection to them.
147+
- content: restrict,%{ if contains(tags, "puppet") }pty%{ else }%{if length(compact(values(puppetservers))) == length(keys(puppetservers)) }%{ for host, ip in puppetservers }permitopen="${ip}:22"%{ endfor }%{ else }pty%{ endif },port-forwarding,command="/sbin/nologin"%{ endif } ${tf_ssh_public_key}
137148
path: /etc/ssh/authorized_keys.tf
138149
permissions: "0644"
139150
- content: |
@@ -183,7 +194,7 @@ write_files:
183194
if [ -f /usr/local/bin/consul ] && [ -f /usr/bin/jq ]; then
184195
/usr/local/bin/consul event -token=$(jq -r .acl.tokens.agent /etc/consul/config.json) -name=puppet $(date +%s)
185196
fi
186-
permissions: 0755
197+
permissions: "0755"
187198
path: /usr/sbin/update_etc_puppetlabs.sh
188199
- content: |
189200
---
@@ -198,16 +209,6 @@ write_files:
198209
permissions: "0640"
199210
- path: /var/log/autosign.log
200211
permissions: "0640"
201-
%{ if include_tf_data ~}
202-
- path: /etc/puppetlabs/data/terraform_data.yaml
203-
content: |
204-
${indent(6, terraform_data)}
205-
permissions: "0640"
206-
- path: /etc/puppetlabs/facts/terraform_facts.yaml
207-
content: |
208-
${indent(6, terraform_facts)}
209-
permissions: "0640"
210-
%{ endif ~}
211212
%{ if puppetfile != "" ~}
212213
- path: /etc/puppetlabs/code/Puppetfile
213214
content: |

common/design/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ locals {
1414
for prefix, attrs in var.instances : [
1515
for i in range(lookup(attrs, "count", 1)) : {
1616
(format("%s%d", prefix, i + 1)) = merge(
17+
{ image = var.image },
1718
{ disk_size = max(var.min_disk_size, [for tag in attrs.tags: lookup(local.min_disk_size_per_tags, tag, 0)]...)},
1819
{ for attr, value in attrs : attr => value if ! contains(["count"], attr) },
1920
{

common/design/variables.tf

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ variable "instances" { }
44
variable "volumes" { }
55
variable "pool" { }
66
variable "firewall_rules" { }
7-
variable "min_disk_size" { }
7+
variable "min_disk_size" { }
8+
variable "image" { }

docs/README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,17 @@ Here is an example:
455455
The image name can be a regular expression. If more than one image is returned by the query
456456
to OpenStack, the most recent is selected.
457457
458+
#### 4.6.4 Incus
459+
460+
The source of the images is [images.linuxcontainers.org/](https://images.linuxcontainers.org/).
461+
Make sure to select the `cloud` variant of the distribution image since only these images
462+
have cloud-init installed and enabled, which is required for bootstrapping Magic Castle.
463+
464+
Since the cloud variant of images is a requirement for Magic Castle, it is implemented
465+
that an Incus image name that does not end with `/cloud` has to be a local image
466+
created using the process documented in section
467+
[10.12 - Create a compute node image](#1012-create-a-compute-node-image).
468+
458469
### 4.7 instances
459470
460471
The `instances` variable is a map that defines the virtual machines that will form
@@ -1095,6 +1106,21 @@ find it automatically. Can be used to force a v4 subnet when both v4 and v6 exis
10951106
10961107
**Post build modification effect**: rebuild of all instances at next `terraform apply`.
10971108
1109+
### 5.5 Incus
1110+
1111+
#### forward_proxy (optional)
1112+
1113+
**default value**: false
1114+
1115+
When enabled, the network connections for ports with the `proxy` tags in firewall rules
1116+
are forwarded between the incus host and the proxy instance. To do so, an
1117+
[incus proxy device](https://linuxcontainers.org/incus/docs/main/reference/devices_proxy/)
1118+
is added to the instance for each port.
1119+
1120+
This setting can be enabled on at most one cluster per incus host.
1121+
1122+
**Post build modification effect**: add or remove devices forwarding proxy ports.
1123+
10981124
## 6. DNS Configuration
10991125
11001126
Some functionalities in Magic Castle require the registration of DNS records under the
@@ -1659,6 +1685,11 @@ The script `prepare4image.sh` executes the following steps in order:
16591685
12. Remove cloud-init's logs and artifacts so it can re-run
16601686
13. Power off the machine
16611687
1688+
**Warning**: When using Incus, running `prepare4image.sh` on instance is an irreversible process.
1689+
The script removes `/var/lib/cloud` which is only created when the instance is created. Therefore,
1690+
to make the instance work properly again, you will either have to taint it or change its image
1691+
attribute to the one you have just created.
1692+
16621693
#### 10.12.2 Create the image
16631694
16641695
Once the instance is powered off, access your cloud provider dashboard, find the instance
@@ -1667,6 +1698,7 @@ and follow the provider's instructions to create the image.
16671698
- [AWS](https://docs.aws.amazon.com/toolkit-for-visual-studio/latest/user-guide/tkv-create-ami-from-instance.html)
16681699
- [Azure](https://learn.microsoft.com/en-us/azure/virtual-machines/capture-image-portal)
16691700
- [GCP](https://cloud.google.com/compute/docs/machine-images/create-machine-images#create-image-from-instance)
1701+
- [Incus](https://linuxcontainers.org/incus/docs/main/howto/images_create/)
16701702
- [OpenStack](https://docs.openstack.org/horizon/latest/user/launch-instances.html#create-an-instance-snapshot)
16711703
- [OVH](https://blog.ovhcloud.com/create-and-use-openstack-snapshots/)
16721704

examples/incus/data.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
profile::nfs::server::export_paths: ['/home', '/project', '/scratch']
3+
profile::nfs::client::share_names: ['home', 'project', 'scratch']
4+
profile::slurm::controller::selinux_context: ""
5+
6+
magic_castle::site::all:
7+
- profile::base
8+
- profile::consul
9+
- profile::freeipa
10+
- profile::users::local
11+
- profile::sssd::client
12+
- profile::metrics::node_exporter
13+
- profile::rsyslog::client
14+
- profile::volumes
15+
# - swap_file
16+
# We remove swap when running in containers
17+
# because when comes time to destroy the cluster
18+
# incus is unable to run swap_off on the instance
19+
# swap file (it tries a simple rm instead)
20+
# and it results in a crash of the terraform destroy.

0 commit comments

Comments
 (0)