Introduced packer image builder example (#313)

This commit is contained in:
Mikołaj Stefaniak 2021-10-04 17:10:19 +02:00 committed by GitHub
parent 777c763980
commit 65fd32c4c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 599 additions and 7 deletions

View File

@ -39,3 +39,9 @@ The example's feed tracks changes to Google Compute instances, and the Cloud Fun
<a href="./iam-delegated-role-grants" title="Delegated Role Grants"><img src="./iam-delegated-role-grants/diagram.png" align="left" width="280px"></a> This [example](./iam-delegated-role-grants) shows how to use delegated role grants to restrict service usage.
<br clear="left">
## Packer image builder
<a href="./packer-image-builder" title="Packer image builder"><img src="./packer-image-builder/diagram.png" align="left" width="280px"></a> This [example](./packer-image-builder) shows how to deploy infrastructure for a Compute Engine image builder based on [Hashicorp's Packer tool](https://www.packer.io).
<br clear="left">

View File

@ -0,0 +1,94 @@
# Compute Image builder with Hashicorp Packer
This example shows how to deploy infrastructure for a Compute Engine image builder based on
[Hashicorp's Packer tool](https://www.packer.io).
![High-level diagram](diagram.png "High-level diagram")
## Running the example
Prerequisite: [Packer](https://www.packer.io/downloads) version >= v1.7.0
Infrastructure setup (Terraform part):
1. Set Terraform configuration variables
2. Run `terraform init`
3. Run `terraform apply`
Building Compute Engine image (Packer part):
1. Enter `packer` directory
2. Set Packer configuration variables (see [Configuring Packer](#configuring-packer) below)
3. Run `packer init .`
4. Run `packer build .`
## Using Packer's service account
The following example leverages [service account impersonation](https://cloud.google.com/iam/docs/impersonating-service-accounts)
to execute any operations on GCP as a dedicated Packer service account. Depending on how you execute
the Packer tool, you need to grant your principal rights to impersonate Packer's service account.
Set `packer_account_users` variable in Terraform configuration to grant roles required to impersonate
Packer's service account to selected IAM principals.
Example: allow default [Cloud Build](https://cloud.google.com/build) service account to impersonate
Packer SA: `packer_account_users=["serviceAccount:myProjectNumber@cloudbuild.gserviceaccount.com"]`.
## Configuring Packer
Provided Packer build example uses [HCL2 configuration files](https://www.packer.io/guides/hcl) and
requires configuration of some input variables *(i.e. service accounts emails)*.
Values of those variables can be taken from the Terraform outputs.
For your convenience, Terraform can populate Packer's variable file.
You can enable this behavior by setting `create_packer_vars` configuration variable to `true`.
Terraform will use template from `packer/build.pkrvars.tpl` file and generate `packer/build.auto.pkrvars.hcl`
variable file for Packer.
Read [Assigning Variables](https://www.packer.io/guides/hcl/variables#assigning-variables) chapter
from [Packer's documentation](https://www.packer.io/docs) for more details on setting up Packer variables.
## Accessing temporary VM
Packer creates a temporary Compute Engine VM instance for provisioning. As we recommend using internal
IP addresses only, communication with this VM has to either:
* originate from the network routable on Packer's VPC *(i.e. peered VPC, over VPN or interconnect)*
* use [Identity-Aware Proxy](https://cloud.google.com/iap/docs/using-tcp-forwarding) tunnel
By default, this example assumes that IAP tunnel is needed to communicate with the temporary VM.
This might be changed by setting `use_iap` variable to `false` in Terraform and Packer
configurations respectively.
**NOTE:** using IAP tunnel with Packer requires gcloud SDK installed on the system running Packer.
## Accessing resources over the Internet
The following example assumes that provisioning of a Compute Engine VM requires access to
the resources over the Internet (i.e. to install OS packages). Since Compute VM has no public IP
address for security reasons, Internet connectivity is done with [Cloud NAT](https://cloud.google.com/nat/docs/overview).
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| project_id | Project id that references existing project. | <code title="">string</code> | ✓ | |
| *billing_account* | Billing account id used as default for new projects. | <code title="">string</code> | | <code title="">null</code> |
| *cidrs* | CIDR ranges for subnets | <code title="map&#40;string&#41;">map(string)</code> | | <code title="&#123;&#10;image-builder &#61; &#34;10.0.0.0&#47;24&#34;&#10;&#125;">...</code> |
| *create_packer_vars* | Create packer variables file using template file and terraform output. | <code title="">bool</code> | | <code title="">false</code> |
| *packer_account_users* | List of members that will be allowed to impersonate Packer image builder service account in IAM format, i.e. 'user:{emailid}'. | <code title="list&#40;string&#41;">list(string)</code> | | <code title="">[]</code> |
| *packer_source_cidrs* | List of CIDR ranges allowed to connect to the temporary VM for provisioning. | <code title="list&#40;string&#41;">list(string)</code> | | <code title="">["0.0.0.0/0"]</code> |
| *project_create* | Create project instead of using an existing one. | <code title="">bool</code> | | <code title="">true</code> |
| *region* | Default region for resources | <code title="">string</code> | | <code title="">europe-west1</code> |
| *root_node* | The resource name of the parent folder or organization for project creation, in 'folders/folder_id' or 'organizations/org_id' format. | <code title="">string</code> | | <code title="">null</code> |
| *use_iap* | Use IAP tunnel to connect to Compute Engine instance for provisioning. | <code title="">bool</code> | | <code title="">true</code> |
## Outputs
| name | description | sensitive |
|---|---|:---:|
| builder_sa | Packer's service account email. | |
| compute_sa | Packer's temporary VM service account email. | |
| compute_subnetwork | Name of a subnetwork for Packer's temporary VM. | |
| compute_zone | Name of a compute engine zone for Packer's temporary VM. | |
<!-- END TFDOC -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

View File

@ -0,0 +1,131 @@
/**
* Copyright 2021 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.
*/
locals {
compute_subnet_name = "image-builder"
compute_zone = "${var.region}-a"
packer_variables_template = "packer/build.pkrvars.tpl"
packer_variables_file = "packer/build.auto.pkrvars.hcl"
}
module "project" {
source = "../../modules/project"
name = var.project_id
parent = var.root_node
billing_account = var.billing_account
project_create = var.project_create
services = [
"compute.googleapis.com"
]
service_config = {
disable_on_destroy = false
disable_dependent_services = false
}
}
module "service-account-image-builder" {
source = "../../modules/iam-service-account"
project_id = module.project.project_id
name = "image-builder"
iam_project_roles = {
(var.project_id) = [
"roles/compute.instanceAdmin.v1",
"roles/iam.serviceAccountUser"
]
}
}
module "service-account-image-builder-vm" {
source = "../../modules/iam-service-account"
project_id = module.project.project_id
name = "image-builder-vm"
}
module "vpc" {
source = "../../modules/net-vpc"
project_id = module.project.project_id
name = "image-builder"
subnets = [
{
name = local.compute_subnet_name
ip_cidr_range = var.cidrs.image-builder
region = var.region
secondary_ip_range = null
}
]
}
module "firewall" {
source = "../../modules/net-vpc-firewall"
project_id = module.project.project_id
network = module.vpc.name
custom_rules = {
image-builder-ingress-builder-vm = {
description = "Allow image builder vm ingress traffic"
direction = "INGRESS"
action = "allow"
sources = []
ranges = var.packer_source_cidrs
targets = [module.service-account-image-builder-vm.email]
use_service_accounts = true
rules = [{ protocol = "tcp", ports = [22, 5985, 5986] }]
extra_attributes = {}
}
}
}
module "nat" {
source = "../../modules/net-cloudnat"
project_id = module.project.project_id
region = var.region
name = "default"
router_network = module.vpc.name
config_source_subnets = "LIST_OF_SUBNETWORKS"
subnetworks = [
{
self_link = module.vpc.subnet_self_links["${var.region}/${local.compute_subnet_name}"]
config_source_ranges = ["ALL_IP_RANGES"]
secondary_ranges = null
}
]
}
resource "google_service_account_iam_binding" "sa-image-builder-token-creators" {
count = length(var.packer_account_users) > 0 ? 1 : 0
service_account_id = module.service-account-image-builder.service_account.name
role = "roles/iam.serviceAccountTokenCreator"
members = var.packer_account_users
}
resource "google_project_iam_member" "project-iap-sa-image-builder" {
count = var.use_iap ? 1 : 0
project = var.project_id
member = module.service-account-image-builder.iam_email
role = "roles/iap.tunnelResourceAccessor"
}
resource "local_file" "packer-vars" {
count = var.create_packer_vars ? 1 : 0
content = templatefile(local.packer_variables_template, {
PROJECT_ID = "${var.project_id}"
COMPUTE_ZONE = "${local.compute_zone}"
BUILDER_SA = "${module.service-account-image-builder.email}"
COMPUTE_SA = "${module.service-account-image-builder-vm.email}"
COMPUTE_SUBNETWORK = "${local.compute_subnet_name}"
USE_IAP = "${var.use_iap}"
})
filename = local.packer_variables_file
}

View File

@ -0,0 +1,36 @@
/**
* Copyright 2021 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.
*/
output "builder_sa" {
description = "Packer's service account email."
value = module.service-account-image-builder.email
}
output "compute_sa" {
description = "Packer's temporary VM service account email."
value = module.service-account-image-builder-vm.email
}
output "compute_subnetwork" {
description = "Name of a subnetwork for Packer's temporary VM."
value = local.compute_subnet_name
}
output "compute_zone" {
description = "Name of a compute engine zone for Packer's temporary VM."
value = local.compute_zone
}

View File

@ -0,0 +1,23 @@
# Packer example
The following Packer example builds Compute Engine image based on Centos 8 Linux.
The image is provisioned with a sample shell scripts to update OS packages and install HTTP server.
The example uses following GCP features:
* [service account impersonation](https://cloud.google.com/iam/docs/impersonating-service-accounts)
* [Identity-Aware Proxy](https://cloud.google.com/iap/docs/using-tcp-forwarding) tunnel
<!-- BEGIN TFDOC -->
## Variables
| name | description | type | required | default |
|---|---|:---: |:---:|:---:|
| builder_sa | Image builder's service account email. | <code title="">string</code> | ✓ | |
| compute_sa | Temporary's VM service account email. | <code title="">string</code> | ✓ | |
| compute_subnetwork | Name of a VPC subnetwork for temporary VM instance. | <code title="">string</code> | ✓ | |
| compute_zone | Compute Engine zone to run temporary VM instance. | <code title="">string</code> | ✓ | |
| project_id | Project id that references existing GCP project. | <code title="">string</code> | ✓ | |
| *use_iap* | Indicates to use IAP tunnel for communication with temporary VM instance. | <code title="">bool</code> | | <code title="">true</code> |
<!-- END TFDOC -->

View File

@ -0,0 +1,8 @@
# Packer variables file template.
# Used by Terraform to generate Packer variable file.
project_id = "mstefaniak-service"
compute_zone = "europe-central2-a"
builder_sa = "image-builder@mstefaniak-service.iam.gserviceaccount.com"
compute_sa = "image-builder-vm@mstefaniak-service.iam.gserviceaccount.com"
compute_subnetwork = "image-builder"
use_iap = true

View File

@ -0,0 +1,50 @@
/**
* Copyright 2021 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.
*/
packer {
required_plugins {
googlecompute = {
version = ">= 1.0.2"
source = "github.com/hashicorp/googlecompute"
}
}
}
source "googlecompute" "centos-custom" {
project_id = var.project_id
zone = var.compute_zone
impersonate_service_account = var.builder_sa
service_account_email = var.compute_sa
subnetwork = var.compute_subnetwork
omit_external_ip = true
use_internal_ip = true
ssh_username = "packer"
use_iap = var.use_iap
source_image_family = "centos-8"
image_name = "centos-8-custom-${formatdate("YYYYMMDDhhmmss", timestamp())}"
image_description = "Custom Centos image"
}
build {
sources = ["sources.googlecompute.centos-custom"]
provisioner "shell" {
inline = ["sudo yum update -y"]
}
provisioner "shell" {
script = "install_httpd.sh"
}
}

View File

@ -0,0 +1,8 @@
# Packer variables file template.
# Used by Terraform to generate Packer variable file.
project_id = "${PROJECT_ID}"
compute_zone = "${COMPUTE_ZONE}"
builder_sa = "${BUILDER_SA}"
compute_sa = "${COMPUTE_SA}"
compute_subnetwork = "${COMPUTE_SUBNETWORK}"
use_iap = ${USE_IAP}

View File

@ -0,0 +1,19 @@
#!/bin/sh
# Copyright 2021 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
#
# https://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.
sudo yum install httpd -y
sudo systemctl enable httpd
sudo systemctl start httpd

View File

@ -0,0 +1,46 @@
/**
* Copyright 2021 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.
*/
variable "project_id" {
description = "Project id that references existing GCP project."
type = string
}
variable "compute_zone" {
description = "Compute Engine zone to run temporary VM instance."
type = string
}
variable "builder_sa" {
description = "Image builder's service account email."
type = string
}
variable "compute_sa" {
description = "Temporary's VM service account email."
type = string
}
variable "compute_subnetwork" {
description = "Name of a VPC subnetwork for temporary VM instance."
type = string
}
variable "use_iap" {
description = "Indicates to use IAP tunnel for communication with temporary VM instance."
type = bool
default = true
}

View File

@ -0,0 +1,76 @@
/**
* Copyright 2021 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.
*/
variable "billing_account" {
description = "Billing account id used as default for new projects."
type = string
default = null
}
variable "cidrs" {
description = "CIDR ranges for subnets"
type = map(string)
default = {
image-builder = "10.0.0.0/24"
}
}
variable "create_packer_vars" {
description = "Create packer variables file using template file and terraform output."
type = bool
default = false
}
variable "packer_account_users" {
description = "List of members that will be allowed to impersonate Packer image builder service account in IAM format, i.e. 'user:{emailid}'."
type = list(string)
default = []
}
variable "packer_source_cidrs" {
description = "List of CIDR ranges allowed to connect to the temporary VM for provisioning."
type = list(string)
default = ["0.0.0.0/0"]
}
variable "project_create" {
description = "Create project instead of using an existing one."
type = bool
default = true
}
variable "project_id" {
description = "Project id that references existing project."
type = string
}
variable "region" {
description = "Default region for resources"
type = string
default = "europe-west1"
}
variable "root_node" {
description = "The resource name of the parent folder or organization for project creation, in 'folders/folder_id' or 'organizations/org_id' format."
type = string
default = null
}
variable "use_iap" {
description = "Use IAP tunnel to connect to Compute Engine instance for provisioning."
type = bool
default = true
}

View File

@ -0,0 +1,13 @@
# Copyright 2021 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.

View File

@ -0,0 +1,22 @@
/**
* Copyright 2021 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.
*/
module "test" {
source = "../../../../cloud-operations/packer-image-builder"
project_id = "test-project"
packer_account_users = ["user:john@testdomain.com"]
create_packer_vars = var.create_packer_vars
}

View File

@ -0,0 +1,8 @@
# Packer variables file template.
# Used by Terraform to generate Packer variable file.
project_id = "${PROJECT_ID}"
compute_zone = "${COMPUTE_ZONE}"
builder_sa = "${BUILDER_SA}"
compute_sa = "${COMPUTE_SA}"
compute_subnetwork = "${COMPUTE_SUBNETWORK}"
use_iap = ${USE_IAP}

View File

@ -0,0 +1,20 @@
/**
* Copyright 2021 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.
*/
variable "create_packer_vars" {
type = bool
default = false
}

View File

@ -0,0 +1,32 @@
# Copyright 2021 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.
import os
import pytest
FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture")
def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR, include_bare_resources="true")
assert len(modules) == 6
assert len(resources) == 16
modules, resources = e2e_plan_runner(FIXTURES_DIR, include_bare_resources="true", create_packer_vars="true")
assert len(modules) == 6
assert len(resources) == 17

View File

@ -21,11 +21,11 @@ FIXTURES_DIR = os.path.join(os.path.dirname(__file__), "fixture")
def test_resources(e2e_plan_runner):
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR)
assert len(modules) == 11
assert len(resources) == 29
"Test that plan works and the numbers of resources is as expected."
modules, resources = e2e_plan_runner(FIXTURES_DIR)
assert len(modules) == 11
assert len(resources) == 29
modules, resources = e2e_plan_runner(FIXTURES_DIR, mig="true")
assert len(modules) == 13
assert len(resources) == 35
modules, resources = e2e_plan_runner(FIXTURES_DIR, mig="true")
assert len(modules) == 13
assert len(resources) == 35