Skip to content

Commit

Permalink
Merge pull request #156 from GoogleCloudPlatform/packer_simple_update
Browse files Browse the repository at this point in the history
Align Packer provisioning with startup-script support for Ansible
  • Loading branch information
tpdownes authored Mar 21, 2022
2 parents 592a0d8 + 8c3b605 commit 0c8e122
Show file tree
Hide file tree
Showing 9 changed files with 293 additions and 56 deletions.
3 changes: 3 additions & 0 deletions .ansible-lint
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
skip_list:
- yaml

mock_roles:
- googlecloudplatform.google_cloud_ops_agents
94 changes: 75 additions & 19 deletions resources/packer/custom-image/README.md
Original file line number Diff line number Diff line change
@@ -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 .
```

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
Expand All @@ -43,11 +88,22 @@ No resources.

| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
| <a name="input_ansible_extra_arguments"></a> [ansible\_extra\_arguments](#input\_ansible\_extra\_arguments) | n/a | `list(string)` | <pre>[<br> "-vv"<br>]</pre> | no |
| <a name="input_ansible_playbook_files"></a> [ansible\_playbook\_files](#input\_ansible\_playbook\_files) | n/a | `list(string)` | n/a | yes |
| <a name="input_ansible_user"></a> [ansible\_user](#input\_ansible\_user) | n/a | `string` | `"packer"` | no |
| <a name="input_ansible_playbooks"></a> [ansible\_playbooks](#input\_ansible\_playbooks) | n/a | <pre>list(object({<br> playbook_file = string<br> galaxy_file = string<br> extra_arguments = list(string)<br> }))</pre> | `[]` | no |
| <a name="input_disk_size"></a> [disk\_size](#input\_disk\_size) | Size of disk image in GB | `number` | `null` | no |
| <a name="input_machine_type"></a> [machine\_type](#input\_machine\_type) | VM machine type on which to build new image | `string` | `"n2d-standard-4"` | no |
| <a name="input_omit_external_ip"></a> [omit\_external\_ip](#input\_omit\_external\_ip) | Provision the image building VM without a public IP address | `bool` | `false` | no |
| <a name="input_project_id"></a> [project\_id](#input\_project\_id) | n/a | `string` | n/a | yes |
| <a name="input_subnetwork"></a> [subnetwork](#input\_subnetwork) | n/a | `string` | n/a | yes |
| <a name="input_service_account_email"></a> [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 |
| <a name="input_service_account_scopes"></a> [service\_account\_scopes](#input\_service\_account\_scopes) | Service account scopes to attach to the instance. See<br>https://cloud.google.com/compute/docs/access/service-accounts. | `list(string)` | `null` | no |
| <a name="input_source_image"></a> [source\_image](#input\_source\_image) | Source OS image to build from | `string` | `null` | no |
| <a name="input_source_image_family"></a> [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 |
| <a name="input_source_image_project_id"></a> [source\_image\_project\_id](#input\_source\_image\_project\_id) | A list of project IDs to search for the source image. Packer will search the<br>first project ID in the list first, and fall back to the next in the list,<br>until it finds the source image. | `list(string)` | <pre>[<br> "cloud-hpc-image-public"<br>]</pre> | no |
| <a name="input_ssh_username"></a> [ssh\_username](#input\_ssh\_username) | Username to use for SSH access to VM | `string` | `"packer"` | no |
| <a name="input_subnetwork"></a> [subnetwork](#input\_subnetwork) | Name of subnetwork in which to provision image building VM | `string` | n/a | yes |
| <a name="input_tags"></a> [tags](#input\_tags) | Assign network tags to apply firewall rules to VM instance | `list(string)` | `null` | no |
| <a name="input_use_iap"></a> [use\_iap](#input\_use\_iap) | Use IAP proxy when connecting by SSH | `bool` | `false` | no |
| <a name="input_use_os_login"></a> [use\_os\_login](#input\_use\_os\_login) | Use OS Login when connecting by SSH | `bool` | `false` | no |
| <a name="input_zone"></a> [zone](#input\_zone) | Cloud zone in which to provision image building VM | `string` | n/a | yes |

## Outputs

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
44 changes: 30 additions & 14 deletions resources/packer/custom-image/image.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down
17 changes: 17 additions & 0 deletions resources/packer/custom-image/requirements.yml
Original file line number Diff line number Diff line change
@@ -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
47 changes: 47 additions & 0 deletions resources/packer/custom-image/scripts/install_ansible.sh
Original file line number Diff line number Diff line change
@@ -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
100 changes: 91 additions & 9 deletions resources/packer/custom-image/variables.pkr.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -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 = <<EOD
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.
EOD
type = list(string)
default = [
"cloud-hpc-image-public"
]
}

variable "source_image" {
description = "Source OS image to build from"
type = string
default = null
}

variable "source_image_family" {
description = "Alternative to source_image. Specify image family to build from latest image in family"
type = string
default = "hpc-centos-7"
}

variable "service_account_email" {
description = "The service account email to use. If null or 'default', then the default Compute Engine service account will be used."
type = string
default = null
}

variable "service_account_scopes" {
description = <<EOD
Service account scopes to attach to the instance. See
https://cloud.google.com/compute/docs/access/service-accounts.
EOD
type = list(string)
default = null
}

variable "use_iap" {
description = "Use IAP proxy when connecting by SSH"
type = bool
default = false
}

variable "ansible_playbook_files" {
type = list(string)
variable "use_os_login" {
description = "Use OS Login when connecting by SSH"
type = bool
default = false
}

variable "ansible_extra_arguments" {
type = list(string)
default = ["-vv"]
variable "ssh_username" {
description = "Username to use for SSH access to VM"
type = string
default = "packer"
}

variable "ansible_user" {
type = string
default = "packer"
variable "ansible_playbooks" {
type = list(object({
playbook_file = string
galaxy_file = string
extra_arguments = list(string)
}))
default = []
}
Loading

0 comments on commit 0c8e122

Please sign in to comment.