diff --git a/.ansible-lint b/.ansible-lint index 31cc7af4a9..d4432b0c7a 100644 --- a/.ansible-lint +++ b/.ansible-lint @@ -1,2 +1,5 @@ skip_list: - yaml + +mock_roles: +- googlecloudplatform.google_cloud_ops_agents diff --git a/resources/packer/custom-image/README.md b/resources/packer/custom-image/README.md index 4333f6ae54..d7bad151ff 100644 --- a/resources/packer/custom-image/README.md +++ b/resources/packer/custom-image/README.md @@ -1,25 +1,70 @@ ## Description This resource is an example of creating an image with Packer using the HPC -Toolkit. The image created is based on the [HPC VM image](https://console.cloud.google.com/marketplace/product/click-to-deploy-images/hpc-vm-image) -and updates packages using ansible as a provisioner. +Toolkit. Packer operates by provisioning a short-lived VM in Google Cloud and +executing scripts to customize the VM for repeated usage. This Packer "template" +installs Ansible and supports the execution of user-specified Ansible playbooks +to customize the VM. ### Example +The following example assumes operation in Cloud Region us-central1 and +in zone us-central1-c. You may substitute your own preferred region and zone. +You will need a Cloud VPC Network that allows + +* either public IP addresses or Identity-Aware Proxy (IAP) tunneling of SSH + connections +* outbound connections to the public internet + +If you already have such a network, identify its subnetwork in us-central1 or +your region of choice. If not, you can create one with this simple blueprint: + ```yaml -- source: ./resources/network/vpc - kind: terraform - id: network1 - settings: - network_name: $(vars.deployment_name) - -- source: ./resources/packer/custom-image - kind: packer - id: my-custom-image - settings: - subnetwork: (("${var.deployment_name}-central1")) - ansible_playbook_files: - - ./example.yaml +--- +blueprint_name: image-builder + +vars: + project_id: ## Set Project ID here ## + deployment_name: image-builder-001 + region: us-central1 + zone: us-central1-c + +resource_groups: +- group: network + resources: + - source: resources/network/vpc + kind: terraform + id: network1 + outputs: + - subnetwork_name +``` + +The subnetwork name will be printed to the terminal after running `terraform +apply`. The following parameters will create a 100GB image without exposing the +build VM on the public internet. Create a file `input.auto.pkvars.hcl`: + +```hcl +project_id = "## Set Project ID here ##" +zone = "us-central1-c" +subnetwork = "## Set Subnetwork here ##" +use_iap = true +omit_external_ip = true +disk_size = 100 + +ansible_playbooks = [ + { + playbook_file = "./example-playbook.yml" + galaxy_file = "./requirements.yml" + extra_arguments = ["-vv"] + } +] +``` + +Substitute appropriate values for `project_id`, `zone`, and `subnetwork`. +Then execute + +```shell +packer build . ``` @@ -43,11 +88,22 @@ No resources. | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| -| [ansible\_extra\_arguments](#input\_ansible\_extra\_arguments) | n/a | `list(string)` |
[| no | -| [ansible\_playbook\_files](#input\_ansible\_playbook\_files) | n/a | `list(string)` | n/a | yes | -| [ansible\_user](#input\_ansible\_user) | n/a | `string` | `"packer"` | no | +| [ansible\_playbooks](#input\_ansible\_playbooks) | n/a |
"-vv"
]
list(object({| `[]` | no | +| [disk\_size](#input\_disk\_size) | Size of disk image in GB | `number` | `null` | no | +| [machine\_type](#input\_machine\_type) | VM machine type on which to build new image | `string` | `"n2d-standard-4"` | no | +| [omit\_external\_ip](#input\_omit\_external\_ip) | Provision the image building VM without a public IP address | `bool` | `false` | no | | [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes | -| [subnetwork](#input\_subnetwork) | n/a | `string` | n/a | yes | +| [service\_account\_email](#input\_service\_account\_email) | The service account email to use. If null or 'default', then the default Compute Engine service account will be used. | `string` | `null` | no | +| [service\_account\_scopes](#input\_service\_account\_scopes) | Service account scopes to attach to the instance. See
playbook_file = string
galaxy_file = string
extra_arguments = list(string)
}))
[| no | +| [ssh\_username](#input\_ssh\_username) | Username to use for SSH access to VM | `string` | `"packer"` | no | +| [subnetwork](#input\_subnetwork) | Name of subnetwork in which to provision image building VM | `string` | n/a | yes | +| [tags](#input\_tags) | Assign network tags to apply firewall rules to VM instance | `list(string)` | `null` | no | +| [use\_iap](#input\_use\_iap) | Use IAP proxy when connecting by SSH | `bool` | `false` | no | +| [use\_os\_login](#input\_use\_os\_login) | Use OS Login when connecting by SSH | `bool` | `false` | no | +| [zone](#input\_zone) | Cloud zone in which to provision image building VM | `string` | n/a | yes | ## Outputs diff --git a/resources/packer/custom-image/example.yaml b/resources/packer/custom-image/example-playbook.yml similarity index 87% rename from resources/packer/custom-image/example.yaml rename to resources/packer/custom-image/example-playbook.yml index b86bcea1bb..5b6a5fb7cf 100644 --- a/resources/packer/custom-image/example.yaml +++ b/resources/packer/custom-image/example-playbook.yml @@ -19,14 +19,10 @@ become: true tags: install roles: - - role: google-cloud-ops-agents-ansible + - role: googlecloudplatform.google_cloud_ops_agents vars: agent_type: ops-agent tasks: - - name: Upgrade all packages - yum: - name: '*' - state: latest - name: add EPEL yum: name: diff --git a/resources/packer/custom-image/image.pkr.hcl b/resources/packer/custom-image/image.pkr.hcl index 4108795317..bc837382ad 100644 --- a/resources/packer/custom-image/image.pkr.hcl +++ b/resources/packer/custom-image/image.pkr.hcl @@ -12,33 +12,49 @@ // See the License for the specific language governing permissions and // limitations under the License. +locals { + toolkit_venv = "/usr/local/toolkit" +} + source "googlecompute" "hpc_centos_7" { project_id = var.project_id image_name = "example-${formatdate("YYYYMMDD't'hhmmss'z'", timestamp())}" image_family = "example-v1" - machine_type = "n2d-standard-4" + machine_type = var.machine_type + disk_size = var.disk_size + omit_external_ip = var.omit_external_ip + use_internal_ip = var.omit_external_ip subnetwork = var.subnetwork - source_image_family = "hpc-centos-7" - source_image_project_id = ["cloud-hpc-image-public"] - ssh_username = "packer" - tags = ["builder"] - zone = "us-central1-a" + source_image = var.source_image + source_image_family = var.source_image_family + source_image_project_id = var.source_image_project_id + ssh_username = var.ssh_username + tags = var.tags + use_iap = var.use_iap + use_os_login = var.use_os_login + zone = var.zone } build { name = "example" sources = ["sources.googlecompute.hpc_centos_7"] - dynamic "provisioner" { - # labels adds each element of below list to the right of dynamic block - # i.e. this creates multiple ansible provisioners - labels = ["ansible"] - for_each = var.ansible_playbook_files + provisioner "shell" { + execute_command = "sudo -H sh -c '{{ .Vars }} {{ .Path }}'" + script = "scripts/install_ansible.sh" + } + # this will end up installing custom roles/collections from ansible-galaxy + # under /home/packer until we modify /etc/ansible/ansible.cfg to identify + # a directory that will remain after Packer is complete + dynamic "provisioner" { + # using labels this way effectively creates 'provisioner "ansible-local"' blocks + labels = ["ansible-local"] + for_each = var.ansible_playbooks content { - playbook_file = provisioner.value - extra_arguments = var.ansible_extra_arguments - user = var.ansible_user + playbook_file = provisioner.value.playbook_file + galaxy_file = provisioner.value.galaxy_file + extra_arguments = provisioner.value.extra_arguments } } diff --git a/resources/packer/custom-image/requirements.yml b/resources/packer/custom-image/requirements.yml new file mode 100644 index 0000000000..6db83e3e2c --- /dev/null +++ b/resources/packer/custom-image/requirements.yml @@ -0,0 +1,17 @@ +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +--- +roles: +- name: googlecloudplatform.google_cloud_ops_agents diff --git a/resources/packer/custom-image/scripts/install_ansible.sh b/resources/packer/custom-image/scripts/install_ansible.sh new file mode 100644 index 0000000000..7294212b25 --- /dev/null +++ b/resources/packer/custom-image/scripts/install_ansible.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# Copyright 2022 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +apt_wait() { + while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do + echo "Sleeping for dpkg lock" + sleep 3 + done + while fuser /var/lib/apt/lists/lock >/dev/null 2>&1; do + echo "Sleeping for apt lists lock" + sleep 3 + done + if [ -f /var/log/unattended-upgrades/unattended-upgrades.log ]; then + echo "Sleeping until unattended-upgrades finishes" + while fuser /var/log/unattended-upgrades/unattended-upgrades.log >/dev/null 2>&1; do + sleep 3 + done + fi +} + +if ! command -v ansible-playbook >/dev/null 2>&1; then + if [ -f /etc/centos-release ] || [ -f /etc/redhat-release ] || [ -f /etc/oracle-release ] || [ -f /etc/system-release ]; then + yum -y install epel-release + yum -y install ansible + + elif [ -f /etc/debian_version ] || grep -qi ubuntu /etc/lsb-release || grep -qi ubuntu /etc/os-release; then + echo 'WARNING: unsupported installation of ansible in debian / ubuntu' + apt_wait + apt-get update + DEBIAN_FRONTEND=noninteractive apt-get install -y ansible + else + echo 'Unsupported distribution' + exit 1 + fi +fi diff --git a/resources/packer/custom-image/variables.pkr.hcl b/resources/packer/custom-image/variables.pkr.hcl index 1fe45b1270..fd88574991 100644 --- a/resources/packer/custom-image/variables.pkr.hcl +++ b/resources/packer/custom-image/variables.pkr.hcl @@ -16,20 +16,102 @@ variable "project_id" { type = string } +variable "machine_type" { + description = "VM machine type on which to build new image" + type = string + default = "n2d-standard-4" +} + +variable "disk_size" { + description = "Size of disk image in GB" + type = number + default = null +} + +variable "zone" { + description = "Cloud zone in which to provision image building VM" + type = string +} + variable "subnetwork" { - type = string + description = "Name of subnetwork in which to provision image building VM" + type = string +} + +variable "omit_external_ip" { + description = "Provision the image building VM without a public IP address" + type = bool + default = false +} + +variable "tags" { + description = "Assign network tags to apply firewall rules to VM instance" + type = list(string) + default = null +} + +variable "source_image_project_id" { + description = <
"cloud-hpc-image-public"
]