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)` |
[
"-vv"
]
| 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 |
list(object({
playbook_file = string
galaxy_file = string
extra_arguments = list(string)
}))
| `[]` | 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
https://cloud.google.com/compute/docs/access/service-accounts. | `list(string)` | `null` | no | +| [source\_image](#input\_source\_image) | Source OS image to build from | `string` | `null` | no | +| [source\_image\_family](#input\_source\_image\_family) | Alternative to source\_image. Specify image family to build from latest image in family | `string` | `"hpc-centos-7"` | no | +| [source\_image\_project\_id](#input\_source\_image\_project\_id) | A list of project IDs to search for the source image. Packer will search the
first project ID in the list first, and fall back to the next in the list,
until it finds the source image. | `list(string)` |
[
"cloud-hpc-image-public"
]
| 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 = </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 in debian / ubuntu' - apt update - apt install -y software-properties-common - add-apt-repository --yes --update ppa:ansible/ansible - apt install -y ansible - + 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 diff --git a/tools/validate_configs/test_configs/packer.yaml b/tools/validate_configs/test_configs/packer.yaml index 084929bbd1..3a0a227be2 100644 --- a/tools/validate_configs/test_configs/packer.yaml +++ b/tools/validate_configs/test_configs/packer.yaml @@ -37,5 +37,10 @@ resource_groups: id: my-custom-image settings: subnetwork: "subnet-central1" - ansible_playbook_files: - - ./example.yaml + use_iap: true + omit_external_ip: true + disk_size: 100 + ansible_playbooks: + - playbook_file: ./example-playbook.yml + galaxy_file: ./requirements.yml + extra_arguments: ["-vv"]