Skip to content

Commit d7fa213

Browse files
committed
Add an image-build role and playbook
1 parent ec47d1b commit d7fa213

File tree

9 files changed

+388
-0
lines changed

9 files changed

+388
-0
lines changed

build-image.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
3+
# Provision the infrastructure using Terraform
4+
- name: Build image
5+
hosts: openstack
6+
roles:
7+
- image_build
8+
9+
tasks:
10+
- name: Set cluster_image fact
11+
set_fact:
12+
cluster_image: "{{ image_build_data.artifact_id }}"
13+
14+
- name: Print cluster_image UUID
15+
debug:
16+
msg: "{{ cluster_image }}"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
---
2+
image_build_terraform_project_path: "{{ playbook_dir }}/terraform-image-build"
3+
image_build_cluster_id: "image-build"
4+
5+
# Regex to capture existing cloud image names to use as the
6+
# OpenHPC Slurm base-image
7+
image_build_existing_image_regex: "^Rocky-8-GenericCloud-Base-8.7-.*"
8+
# Attributes to sort the list of existing base images returned by
9+
# image_build_existing_image_regex. See
10+
# https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/data-sources/images_image_ids_v2#sort
11+
image_build_existing_image_sort_attributes: "name,updated_at"
12+
13+
# Attach a floating IP to the Packer build instance
14+
image_build_attach_floating_ip: false
15+
16+
# Use a volume for the root disk of the Packer build instance
17+
image_build_use_blockstorage_volume: false
18+
19+
# Packer image format (only used when image_build_use_blockstorage_volume: true
20+
image_build_image_disk_format: "qcow2"
21+
22+
# Metadata items to set on the Packer image
23+
image_build_metadata: {}
24+
25+
# The directory that contains the openstack.pkr.hcl to build the Slurm image
26+
image_build_packer_root_path: "{{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance/packer"
27+
28+
# Vars to apply to the builder group
29+
image_build_builder_group_vars:
30+
update_log_path: /tmp/update_log
31+
appliances_repository_root: "{{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance"
32+
33+
# ansible_ssh_common_args for Packer build
34+
image_build_ansible_ssh_common_args: >-
35+
'-o ProxyCommand="ssh -W %h:%p -q
36+
{% if image_build_ssh_bastion_private_key_file is defined %}
37+
-i {{ image_build_ssh_bastion_private_key_file }}
38+
{% endif %}
39+
{{ image_build_ssh_bastion_username }}@{{ image_build_ssh_bastion_host }}"'

roles/image_build/tasks/main.yml

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
- debug:
3+
msg: |
4+
terraform_backend_type: {{ terraform_backend_type }}
5+
terraform_state: {{ terraform_state }}
6+
7+
- name: Run prechecks
8+
include_tasks: prechecks.yml
9+
10+
- name: Install Terraform binary
11+
include_role:
12+
name: stackhpc.terraform.install
13+
14+
- name: Make Terraform project directory
15+
file:
16+
path: "{{ image_build_terraform_project_path }}"
17+
state: directory
18+
19+
- name: Write backend configuration
20+
copy:
21+
content: |
22+
terraform {
23+
backend "{{ terraform_backend_type }}" { }
24+
}
25+
dest: "{{ image_build_terraform_project_path }}/backend.tf"
26+
27+
- name: Template Terraform files into project directory
28+
template:
29+
src: "{{ item }}.j2"
30+
dest: "{{ image_build_terraform_project_path }}/{{ item }}"
31+
loop:
32+
- outputs.tf
33+
- providers.tf
34+
- resources.tf
35+
36+
- name: Provision infrastructure
37+
include_role:
38+
name: stackhpc.terraform.infra
39+
vars:
40+
terraform_project_path: "{{ image_build_terraform_project_path }}"
41+
42+
- name: Set image build infrastructure facts
43+
set_fact:
44+
image_build_network_id: "{{ terraform_provision.outputs.network_id.value }}"
45+
image_build_floating_ip_network: "{{ terraform_provision.outputs.floating_ip_network_id.value }}"
46+
image_build_ssh_keypair_name: "{{ terraform_provision.outputs.ssh_keypair_name.value }}"
47+
image_build_ssh_private_key_file: "{{ cluster_ssh_private_key_file }}"
48+
image_build_source_image_id: "{{ terraform_provision.outputs.source_image_name.value.ids | first }}"
49+
image_build_security_group_id: "{{ terraform_provision.outputs.security_group_id.value }}"
50+
51+
- name: Make Packer vars file
52+
template:
53+
src: builder.pkrvars.hcl.j2
54+
dest: /tmp/builder.pkrvars.hcl
55+
56+
- name: Create temporary image-build inventory directory
57+
ansible.builtin.tempfile:
58+
state: directory
59+
prefix: image-build
60+
register: image_build_inventory
61+
62+
- name: Symlink "everything" layout to image-build inventory
63+
file:
64+
state: link
65+
src: "{{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance/environments/common/layouts/everything"
66+
dest: "{{ image_build_inventory.path }}/groups"
67+
68+
- name: Symlink CAAS group_vars to image-build inventory
69+
file:
70+
state: link
71+
src: "{{ playbook_dir }}/group_vars"
72+
dest: "{{ image_build_inventory.path }}/group_vars"
73+
74+
- name: Add builder vars to image-build inventory hosts file
75+
copy:
76+
dest: "{{ image_build_inventory.path }}/hosts"
77+
content: |
78+
[builder:vars]
79+
{% if image_build_ssh_bastion_host is defined %}
80+
ansible_ssh_common_args={{ image_build_ansible_ssh_common_args }}
81+
{% endif %}
82+
{% for k,v in image_build_builder_group_vars.items() -%}
83+
{{ k }}={{ v }}
84+
{% endfor -%}
85+
86+
- name: Create temporary file for ansible.cfg
87+
ansible.builtin.tempfile:
88+
state: file
89+
suffix: ansible.cfg
90+
register: ansible_cfg_file
91+
92+
- name: Template image-build ansible.cfg
93+
template:
94+
src: ansible.cfg.j2
95+
dest: "{{ ansible_cfg_file.path }}"
96+
97+
- name: Packer init
98+
command:
99+
cmd: |
100+
packer init .
101+
chdir: "{{ image_build_packer_root_path }}"
102+
103+
- name: Build image with packer
104+
command:
105+
cmd: |
106+
packer build -only openstack.openhpc -var-file=/tmp/builder.pkrvars.hcl openstack.pkr.hcl
107+
chdir: "{{ image_build_packer_root_path }}"
108+
environment:
109+
ANSIBLE_CONFIG: "{{ ansible_cfg_file.path }}"
110+
PACKER_LOG: 1
111+
PACKER_LOG_PATH: "/tmp/packer-build.log"
112+
113+
- name: Parse packer-manifest.json
114+
set_fact:
115+
packer_manifest: "{{ lookup('file', '/tmp/builder.manifest.json') | from_json }}"
116+
117+
- name: Extract image-build data
118+
set_fact:
119+
image_build_data: "{{ packer_manifest.builds | selectattr('packer_run_uuid', 'eq', packer_manifest.last_run_uuid) | first }}"
120+
121+
- name: Destroy infrastructure
122+
include_role:
123+
name: stackhpc.terraform.infra
124+
vars:
125+
terraform_project_path: "{{ image_build_terraform_project_path }}"
126+
cluster_state: absent
127+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
3+
- name: Ensure builder access mode
4+
fail:
5+
msg: >-
6+
Set either image_build_ssh_bastion_host or
7+
image_build_attach_floating_ip to access the image
8+
build instance via a bastion or directly
9+
when:
10+
- image_build_ssh_bastion_host is defined
11+
- image_build_attach_floating_ip is defined and image_build_attach_floating_ip
12+
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[defaults]
2+
any_errors_fatal = True
3+
gathering = smart
4+
forks = 30
5+
host_key_checking = False
6+
remote_tmp = /tmp
7+
roles_path = {{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance/ansible/roles
8+
inventory = {{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance/environments/common/inventory,{{ image_build_inventory.path }}
9+
10+
[ssh_connection]
11+
ssh_args = -o ControlMaster=auto -o ControlPersist=240s -o PreferredAuthentications=publickey -o UserKnownHostsFile=/dev/null
12+
pipelining = True
13+
# This is important because we are using one of the hosts in the play as a jump host
14+
# This ensures that if the proxy connection is interrupted, rendering the other hosts
15+
# unreachable, the connection is retried instead of failing the entire play
16+
retries = 10
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
repo_root = "{{ playbook_dir }}/vendor/stackhpc/ansible-slurm-appliance"
2+
environment_root = "{{ playbook_dir }}/image_build"
3+
networks = ["{{ image_build_network_id }}"]
4+
{% if image_build_ssh_bastion_host is defined %}
5+
ssh_bastion_host = "{{ image_build_ssh_bastion_host }}"
6+
{% endif %}
7+
{% if image_build_ssh_bastion_username is defined %}
8+
ssh_bastion_username = "{{ image_build_ssh_bastion_username }}"
9+
{% endif %}
10+
{% if image_build_ssh_bastion_private_key_file is defined %}
11+
ssh_bastion_private_key_file = "{{ image_build_ssh_bastion_private_key_file }}"
12+
{% endif %}
13+
{% if image_build_attach_floating_ip %}
14+
floating_ip_network = "{{ image_build_floating_ip_network }}"
15+
{% endif %}
16+
security_groups = ["{{ image_build_security_group_id }}"]
17+
fatimage_source_image = "{{ image_build_source_image_id }}"
18+
ssh_keypair_name = "{{ image_build_ssh_keypair_name }}"
19+
ssh_private_key_file = "{{ image_build_ssh_private_key_file }}"
20+
flavor = "{{ image_build_flavor_name }}"
21+
metadata = {
22+
{% for k,v in image_build_metadata.items() %}
23+
"{{ k }}" = "{{ v }}"
24+
{% endfor %}
25+
}
26+
use_blockstorage_volume = {{ image_build_use_blockstorage_volume | string | lower }}
27+
{% if image_build_use_blockstorage_volume %}
28+
volume_size = {{ image_build_volume_size }}
29+
image_disk_format = "{{ image_build_image_disk_format }}"
30+
{% endif %}
31+
manifest_output_path = "/tmp/builder.manifest.json"
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
output "network_id" {
2+
description = "The image build network ID"
3+
value = data.openstack_networking_network_v2.caas_image_build_network.id
4+
}
5+
6+
output "ssh_keypair_name" {
7+
description = "The name of the SSH keypair generated for image build"
8+
value = openstack_compute_keypair_v2.image_build_keypair.name
9+
}
10+
11+
output "source_image_name" {
12+
description = "The id of the image used to build the cluster nodes"
13+
value = data.openstack_images_image_ids_v2.image_build_source_image
14+
}
15+
16+
output "cluster_ssh_private_key" {
17+
description = "The private component of the SSH keypair generated for image build"
18+
value = openstack_compute_keypair_v2.image_build_keypair.private_key
19+
sensitive = true
20+
}
21+
22+
output "floating_ip_network_id" {
23+
description = "Network to allocate floating IPs from"
24+
value = data.openstack_networking_network_v2.caas_image_build_external_network.id
25+
}
26+
27+
output "security_group_id" {
28+
description = "Security group ID to associate with the builder instance"
29+
value = openstack_networking_secgroup_v2.caas_image_build_secgroup.id
30+
}
31+
32+
# Empty values to keep ansible-collection-terraform happy
33+
output "cluster_nodes" {
34+
value = []
35+
}
36+
37+
output "cluster_gateway_ip" {
38+
value = ""
39+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
terraform {
2+
required_version = ">= 0.14"
3+
4+
# We need the OpenStack provider
5+
required_providers {
6+
openstack = {
7+
source = "terraform-provider-openstack/openstack"
8+
}
9+
}
10+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#jinja2: trim_blocks:False
2+
3+
######
4+
###### Image build network
5+
######
6+
7+
data "openstack_networking_network_v2" "caas_image_build_external_network" {
8+
external = true
9+
}
10+
11+
{% if image_build_network_name is not defined %}
12+
# Create a network
13+
resource "openstack_networking_network_v2" "caas_image_build_network" {
14+
name = "caas-image-build"
15+
admin_state_up = "true"
16+
}
17+
18+
resource "openstack_networking_subnet_v2" "caas_image_build_subnet" {
19+
name = "caas-image-build"
20+
network_id = "${openstack_networking_network_v2.caas_image_build_network.id}"
21+
cidr = "192.168.244.0/24"
22+
{% if image_build_nameservers is defined %}
23+
dns_nameservers = [
24+
{% for nameserver in image_build_nameservers %}
25+
"{{ nameserver }}"{{ ',' if not loop.last }}
26+
{% endfor %}
27+
]
28+
{% endif %}
29+
ip_version = 4
30+
}
31+
32+
resource "openstack_networking_router_v2" "caas_image_build_router" {
33+
name = "caas-image-build"
34+
admin_state_up = true
35+
external_network_id = "${data.openstack_networking_network_v2.caas_image_build_external_network.id}"
36+
}
37+
38+
resource "openstack_networking_router_interface_v2" "caas_image_build_router_interface" {
39+
router_id = "${openstack_networking_router_v2.caas_image_build_router.id}"
40+
subnet_id = "${openstack_networking_subnet_v2.caas_image_build_subnet.id}"
41+
}
42+
{% endif %}
43+
44+
# Get existing network resource data by name, from either the created
45+
# network or the network name if supplied
46+
data "openstack_networking_network_v2" "caas_image_build_network" {
47+
{% if image_build_network_name is not defined %}
48+
network_id = "${openstack_networking_network_v2.caas_image_build_network.id}"
49+
{% else %}
50+
name = "{{ image_build_network_name }}"
51+
{% endif %}
52+
}
53+
54+
######
55+
###### Image build keypair
56+
######
57+
58+
resource "openstack_compute_keypair_v2" "image_build_keypair" {
59+
name = "caas-image-build"
60+
}
61+
62+
######
63+
###### Image build base image
64+
######
65+
66+
data "openstack_images_image_ids_v2" "image_build_source_image" {
67+
name_regex = "{{ image_build_existing_image_regex }}"
68+
sort = "{{ image_build_existing_image_sort_attributes }}"
69+
}
70+
71+
######
72+
###### Image build security groups
73+
######
74+
75+
# Security group to hold specific rules for the image build instance
76+
resource "openstack_networking_secgroup_v2" "caas_image_build_secgroup" {
77+
name = "caas-image_build"
78+
description = "Specific rules for caas image build"
79+
delete_default_rules = true # Fully manage with terraform
80+
}
81+
82+
## Allow all egress for the image build instance
83+
resource "openstack_networking_secgroup_rule_v2" "caas_image_build_secgroup_egress_v4" {
84+
direction = "egress"
85+
ethertype = "IPv4"
86+
security_group_id = "${openstack_networking_secgroup_v2.caas_image_build_secgroup.id}"
87+
}
88+
89+
## Allow ingress on port 22 (SSH) from anywhere for the image build instance
90+
resource "openstack_networking_secgroup_rule_v2" "caas_image_build_secgroup_ingress_ssh_v4" {
91+
direction = "ingress"
92+
ethertype = "IPv4"
93+
protocol = "tcp"
94+
port_range_min = 22
95+
port_range_max = 22
96+
security_group_id = "${openstack_networking_secgroup_v2.caas_image_build_secgroup.id}"
97+
}
98+

0 commit comments

Comments
 (0)