diff --git a/modules/gke-nodepool/README.md b/modules/gke-nodepool/README.md
index 0828190e..17df73a8 100644
--- a/modules/gke-nodepool/README.md
+++ b/modules/gke-nodepool/README.md
@@ -4,6 +4,10 @@ This module allows simplified creation and management of individual GKE nodepool
## Example usage
+### Module defaults
+
+If no specific node configuration is set via variables, the module uses the provider's defaults only setting OAuth scopes to a minimal working set (devstorage read-only, logging and monitoring write) and the node machine type to `n1-standard-1`. The service account set by the provider in this case is the GCE default service account.
+
```hcl
module "cluster-1-nodepool-1" {
source = "../modules/gke-nodepool"
@@ -14,6 +18,21 @@ module "cluster-1-nodepool-1" {
}
```
+### Internally managed service account
+
+To have the module auto-create a service account for the nodes, set the `node_service_account_create` variable to `true`. When a service account is created by the module, OAuth scopes are set to `cloud-platform` by default. The service account resource and email (in both plain and IAM formats) are then available in outputs to assign IAM roles from your own code.
+
+```hcl
+module "cluster-1-nodepool-1" {
+ source = "../modules/gke-nodepool"
+ project_id = "myproject"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
+ node_service_account_create = true
+}
+```
+
## Variables
@@ -28,23 +47,24 @@ module "cluster-1-nodepool-1" {
| *management_config* | Optional node management configuration. | object({...})
| | null
|
| *max_pods_per_node* | Maximum number of pods per node. | number
| | null
|
| *name* | Optional nodepool name. | string
| | null
|
-| *node_config_disk_size* | Node disk size, defaults to 100GB. | number
| | 100
|
-| *node_config_disk_type* | Node disk type, defaults to pd-standard. | string
| | pd-standard
|
-| *node_config_guest_accelerator* | Map of type and count of attached accelerator cards. | map(number)
| | {}
|
-| *node_config_image_type* | Nodes image type. | string
| | null
|
-| *node_config_labels* | Kubernetes labels attached to nodes. | map(string)
| | {}
|
-| *node_config_local_ssd_count* | Number of local SSDs attached to nodes. | number
| | 0
|
-| *node_config_machine_type* | Nodes machine type. | string
| | n1-standard-1
|
-| *node_config_metadata* | Metadata key/value pairs assigned to nodes. Set disable-legacy-endpoints to true when using this variable. | map(string)
| | null
|
-| *node_config_min_cpu_platform* | Minimum CPU platform for nodes. | string
| | null
|
-| *node_config_oauth_scopes* | Set of Google API scopes for the nodes service account. Include logging-write, monitoring, and storage-ro when using this variable. | list(string)
| | ["logging-write", "monitoring", "monitoring-write", "storage-ro"]
|
-| *node_config_preemptible* | Use preemptible VMs for nodes. | bool
| | null
|
-| *node_config_sandbox_config* | GKE Sandbox configuration. Needs image_type set to COS_CONTAINERD and node_version set to 1.12.7-gke.17 when using this variable. | string
| | null
|
-| *node_config_service_account* | Service account used for nodes. | string
| | null
|
-| *node_config_shielded_instance_config* | Shielded instance options. | object({...})
| | null
|
-| *node_config_tags* | Network tags applied to nodes. | list(string)
| | null
|
| *node_count* | Number of nodes per instance group, can be updated after creation. Ignored when autoscaling is set. | number
| | null
|
+| *node_disk_size* | Node disk size, defaults to 100GB. | number
| | 100
|
+| *node_disk_type* | Node disk type, defaults to pd-standard. | string
| | pd-standard
|
+| *node_guest_accelerator* | Map of type and count of attached accelerator cards. | map(number)
| | {}
|
+| *node_image_type* | Nodes image type. | string
| | null
|
+| *node_labels* | Kubernetes labels attached to nodes. | map(string)
| | {}
|
+| *node_local_ssd_count* | Number of local SSDs attached to nodes. | number
| | 0
|
| *node_locations* | Optional list of zones in which nodes should be located. Uses cluster locations if unset. | list(string)
| | null
|
+| *node_machine_type* | Nodes machine type. | string
| | n1-standard-1
|
+| *node_metadata* | Metadata key/value pairs assigned to nodes. Set disable-legacy-endpoints to true when using this variable. | map(string)
| | null
|
+| *node_min_cpu_platform* | Minimum CPU platform for nodes. | string
| | null
|
+| *node_preemptible* | Use preemptible VMs for nodes. | bool
| | null
|
+| *node_sandbox_config* | GKE Sandbox configuration. Needs image_type set to COS_CONTAINERD and node_version set to 1.12.7-gke.17 when using this variable. | string
| | null
|
+| *node_service_account* | Service account email. Unused if service account is auto-created. | string
| | null
|
+| *node_service_account_create* | Auto-create service account. | bool
| | false
|
+| *node_service_account_scopes* | Scopes applied to service account. Default to: 'cloud-platform' when creating a service account; 'devstorage.read_only', 'logging.write', 'monitoring.write' otherwise. | list(string)
| | []
|
+| *node_shielded_instance_config* | Shielded instance options. | object({...})
| | null
|
+| *node_tags* | Network tags applied to nodes. | list(string)
| | null
|
| *upgrade_config* | Optional node upgrade configuration. | object({...})
| | null
|
| *workload_metadata_config* | Metadata configuration to expose to workloads on the node pool. | string
| | GKE_METADATA_SERVER
|
@@ -53,4 +73,7 @@ module "cluster-1-nodepool-1" {
| name | description | sensitive |
|---|---|:---:|
| name | Nodepool name. | |
+| service_account | Service account resource. | |
+| service_account_email | Service account email. | |
+| service_account_iam_email | Service account email. | |
diff --git a/modules/gke-nodepool/main.tf b/modules/gke-nodepool/main.tf
index 73eb8850..51823954 100644
--- a/modules/gke-nodepool/main.tf
+++ b/modules/gke-nodepool/main.tf
@@ -14,6 +14,38 @@
* limitations under the License.
*/
+locals {
+ service_account_email = (
+ var.node_service_account_create
+ ? (
+ length(google_service_account.service_account) > 0
+ ? google_service_account.service_account[0].email
+ : null
+ )
+ : var.node_service_account
+ )
+ service_account_scopes = (
+ length(var.node_service_account_scopes) > 0
+ ? var.node_service_account_scopes
+ : (
+ var.node_service_account_create
+ ? ["https://www.googleapis.com/auth/cloud-platform"]
+ : [
+ "https://www.googleapis.com/auth/devstorage.read_only",
+ "https://www.googleapis.com/auth/logging.write",
+ "https://www.googleapis.com/auth/monitoring.write"
+ ]
+ )
+ )
+}
+
+resource "google_service_account" "service_account" {
+ count = var.node_service_account_create ? 1 : 0
+ project = var.project_id
+ account_id = "tf-gke-${var.cluster_name}-${var.name}"
+ display_name = "Terraform GKE ${var.cluster_name} ${var.name}."
+}
+
resource "google_container_node_pool" "nodepool" {
provider = google-beta
@@ -29,21 +61,21 @@ resource "google_container_node_pool" "nodepool" {
version = var.gke_version
node_config {
- disk_size_gb = var.node_config_disk_size
- disk_type = var.node_config_disk_type
- image_type = var.node_config_image_type
- labels = var.node_config_labels
- local_ssd_count = var.node_config_local_ssd_count
- machine_type = var.node_config_machine_type
- metadata = var.node_config_metadata
- min_cpu_platform = var.node_config_min_cpu_platform
- oauth_scopes = var.node_config_oauth_scopes
- preemptible = var.node_config_preemptible
- service_account = var.node_config_service_account
- tags = var.node_config_tags
+ disk_size_gb = var.node_disk_size
+ disk_type = var.node_disk_type
+ image_type = var.node_image_type
+ labels = var.node_labels
+ local_ssd_count = var.node_local_ssd_count
+ machine_type = var.node_machine_type
+ metadata = var.node_metadata
+ min_cpu_platform = var.node_min_cpu_platform
+ oauth_scopes = local.service_account_scopes
+ preemptible = var.node_preemptible
+ service_account = local.service_account_email
+ tags = var.node_tags
dynamic guest_accelerator {
- for_each = var.node_config_guest_accelerator
+ for_each = var.node_guest_accelerator
iterator = config
content {
type = config.key
@@ -53,8 +85,8 @@ resource "google_container_node_pool" "nodepool" {
dynamic sandbox_config {
for_each = (
- var.node_config_sandbox_config != null
- ? [var.node_config_sandbox_config]
+ var.node_sandbox_config != null
+ ? [var.node_sandbox_config]
: []
)
iterator = config
@@ -65,8 +97,8 @@ resource "google_container_node_pool" "nodepool" {
dynamic shielded_instance_config {
for_each = (
- var.node_config_shielded_instance_config != null
- ? [var.node_config_shielded_instance_config]
+ var.node_shielded_instance_config != null
+ ? [var.node_shielded_instance_config]
: []
)
iterator = config
diff --git a/modules/gke-nodepool/outputs.tf b/modules/gke-nodepool/outputs.tf
index 96ccb269..c96301a8 100644
--- a/modules/gke-nodepool/outputs.tf
+++ b/modules/gke-nodepool/outputs.tf
@@ -18,3 +18,25 @@ output "name" {
description = "Nodepool name."
value = google_container_node_pool.nodepool.name
}
+
+output "service_account" {
+ description = "Service account resource."
+ value = (
+ var.node_service_account_create
+ ? google_service_account.service_account[0]
+ : null
+ )
+}
+
+output "service_account_email" {
+ description = "Service account email."
+ value = local.service_account_email
+}
+
+output "service_account_iam_email" {
+ description = "Service account email."
+ value = join("", [
+ "serviceAccount:",
+ local.service_account_email == null ? "" : local.service_account_email
+ ])
+}
diff --git a/modules/gke-nodepool/variables.tf b/modules/gke-nodepool/variables.tf
index e8685c7d..dd823309 100644
--- a/modules/gke-nodepool/variables.tf
+++ b/modules/gke-nodepool/variables.tf
@@ -66,85 +66,93 @@ variable "name" {
default = null
}
-variable "node_config_disk_size" {
+variable "node_disk_size" {
description = "Node disk size, defaults to 100GB."
type = number
default = 100
}
-variable "node_config_disk_type" {
+variable "node_disk_type" {
description = "Node disk type, defaults to pd-standard."
type = string
default = "pd-standard"
}
-variable "node_config_guest_accelerator" {
+variable "node_guest_accelerator" {
description = "Map of type and count of attached accelerator cards."
type = map(number)
default = {}
}
-variable "node_config_image_type" {
+variable "node_image_type" {
description = "Nodes image type."
type = string
default = null
}
-variable "node_config_labels" {
+variable "node_labels" {
description = "Kubernetes labels attached to nodes."
type = map(string)
default = {}
}
-variable "node_config_local_ssd_count" {
+variable "node_local_ssd_count" {
description = "Number of local SSDs attached to nodes."
type = number
default = 0
}
-variable "node_config_machine_type" {
+variable "node_machine_type" {
description = "Nodes machine type."
type = string
default = "n1-standard-1"
}
-variable "node_config_metadata" {
+variable "node_metadata" {
description = "Metadata key/value pairs assigned to nodes. Set disable-legacy-endpoints to true when using this variable."
type = map(string)
default = null
}
-variable "node_config_min_cpu_platform" {
+variable "node_min_cpu_platform" {
description = "Minimum CPU platform for nodes."
type = string
default = null
}
-variable "node_config_oauth_scopes" {
- description = "Set of Google API scopes for the nodes service account. Include logging-write, monitoring, and storage-ro when using this variable."
- type = list(string)
- default = ["logging-write", "monitoring", "monitoring-write", "storage-ro"]
-}
-
-variable "node_config_preemptible" {
+variable "node_preemptible" {
description = "Use preemptible VMs for nodes."
type = bool
default = null
}
-variable "node_config_sandbox_config" {
+variable "node_sandbox_config" {
description = "GKE Sandbox configuration. Needs image_type set to COS_CONTAINERD and node_version set to 1.12.7-gke.17 when using this variable."
type = string
default = null
}
-variable "node_config_service_account" {
- description = "Service account used for nodes."
+variable "node_service_account" {
+ description = "Service account email. Unused if service account is auto-created."
type = string
default = null
}
-variable "node_config_shielded_instance_config" {
+variable "node_service_account_create" {
+ description = "Auto-create service account."
+ type = bool
+ default = false
+}
+
+# scopes and scope aliases list
+# https://cloud.google.com/sdk/gcloud/reference/compute/instances/create#--scopes
+variable "node_service_account_scopes" {
+ description = "Scopes applied to service account. Default to: 'cloud-platform' when creating a service account; 'devstorage.read_only', 'logging.write', 'monitoring.write' otherwise."
+ type = list(string)
+ default = []
+}
+
+variable "node_shielded_instance_config" {
description = "Shielded instance options."
type = object({
enable_secure_boot = bool
@@ -153,13 +161,13 @@ variable "node_config_shielded_instance_config" {
default = null
}
-variable "node_config_tags" {
+variable "node_tags" {
description = "Network tags applied to nodes."
type = list(string)
default = null
}
-# variable "node_config_taint" {
+# variable "node_taint" {
# description = "Kubernetes taints applied to nodes."
# type = string
# default = null
diff --git a/networking/hub-and-spoke-peering/main.tf b/networking/hub-and-spoke-peering/main.tf
index d5c09cc6..a84ddef6 100644
--- a/networking/hub-and-spoke-peering/main.tf
+++ b/networking/hub-and-spoke-peering/main.tf
@@ -220,12 +220,12 @@ module "cluster-1" {
}
module "cluster-1-nodepool-1" {
- source = "../../modules/gke-nodepool"
- name = "nodepool-1"
- project_id = var.project_id
- location = module.cluster-1.location
- cluster_name = module.cluster-1.name
- node_config_service_account = module.service-account-gke-node.email
+ source = "../../modules/gke-nodepool"
+ name = "nodepool-1"
+ project_id = var.project_id
+ location = module.cluster-1.location
+ cluster_name = module.cluster-1.name
+ node_service_account = module.service-account-gke-node.email
}
# roles assigned via this module use non-authoritative IAM bindings at the
diff --git a/networking/shared-vpc-gke/README.md b/networking/shared-vpc-gke/README.md
index 2bc72fda..dcb1384a 100644
--- a/networking/shared-vpc-gke/README.md
+++ b/networking/shared-vpc-gke/README.md
@@ -39,7 +39,6 @@ There's a minor glitch that can surface running `terraform destroy`, where the s
|---|---|:---:|
| gke_clusters | GKE clusters information. | |
| projects | Project ids. | |
-| service_accounts | GCE and GKE service accounts. | |
| vms | GCE VMs. | |
| vpc | Shared VPC. | |
diff --git a/networking/shared-vpc-gke/main.tf b/networking/shared-vpc-gke/main.tf
index bee0d814..bf38397b 100644
--- a/networking/shared-vpc-gke/main.tf
+++ b/networking/shared-vpc-gke/main.tf
@@ -74,8 +74,8 @@ module "project-svc-gke" {
}
iam = {
"roles/container.developer" = [module.vm-bastion.service_account_iam_email],
- "roles/logging.logWriter" = [module.service-account-gke-node.iam_email],
- "roles/monitoring.metricWriter" = [module.service-account-gke-node.iam_email],
+ "roles/logging.logWriter" = [module.cluster-1-nodepool-1.service_account_iam_email],
+ "roles/monitoring.metricWriter" = [module.cluster-1-nodepool-1.service_account_iam_email],
"roles/owner" = var.owners_gke
}
}
@@ -220,14 +220,5 @@ module "cluster-1-nodepool-1" {
project_id = module.project-svc-gke.project_id
location = module.cluster-1.location
cluster_name = module.cluster-1.name
- node_config_service_account = module.service-account-gke-node.email
-}
-
-# roles assigned via this module use non-authoritative IAM bindings at the
-# project level, with no risk of conflicts with pre-existing roles
-
-module "service-account-gke-node" {
- source = "../../modules/iam-service-account"
- project_id = module.project-svc-gke.project_id
- name = "gke-node"
+ node_service_account_create = true
}
diff --git a/networking/shared-vpc-gke/outputs.tf b/networking/shared-vpc-gke/outputs.tf
index 26acf421..d3f0e80a 100644
--- a/networking/shared-vpc-gke/outputs.tf
+++ b/networking/shared-vpc-gke/outputs.tf
@@ -28,14 +28,6 @@ output "projects" {
}
}
-output "service_accounts" {
- description = "GCE and GKE service accounts."
- value = {
- bastion = module.vm-bastion.service_account_email
- gke_node = module.service-account-gke-node.email
- }
-}
-
output "vpc" {
description = "Shared VPC."
value = {
diff --git a/tests/modules/gke_nodepool/__init__.py b/tests/modules/gke_nodepool/__init__.py
new file mode 100644
index 00000000..6913f02e
--- /dev/null
+++ b/tests/modules/gke_nodepool/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2020 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.
diff --git a/tests/modules/gke_nodepool/fixture/main.tf b/tests/modules/gke_nodepool/fixture/main.tf
new file mode 100644
index 00000000..c77136e4
--- /dev/null
+++ b/tests/modules/gke_nodepool/fixture/main.tf
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2020 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 = "../../../../modules/gke-nodepool"
+ project_id = "my-project"
+ cluster_name = "cluster-1"
+ location = "europe-west1-b"
+ name = "nodepool-1"
+ node_service_account = var.node_service_account
+ node_service_account_create = var.node_service_account_create
+ node_service_account_scopes = var.node_service_account_scopes
+}
diff --git a/tests/modules/gke_nodepool/fixture/variables.tf b/tests/modules/gke_nodepool/fixture/variables.tf
new file mode 100644
index 00000000..c0d3c922
--- /dev/null
+++ b/tests/modules/gke_nodepool/fixture/variables.tf
@@ -0,0 +1,32 @@
+/**
+ * Copyright 2020 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 "node_service_account" {
+ type = string
+ default = null
+}
+
+variable "node_service_account_create" {
+ type = bool
+ default = false
+}
+
+# scopes and scope aliases list
+# https://cloud.google.com/sdk/gcloud/reference/compute/instances/create#--scopes
+variable "node_service_account_scopes" {
+ type = list(string)
+ default = []
+}
diff --git a/tests/modules/gke_nodepool/test_plan.py b/tests/modules/gke_nodepool/test_plan.py
new file mode 100644
index 00000000..1a71b4d4
--- /dev/null
+++ b/tests/modules/gke_nodepool/test_plan.py
@@ -0,0 +1,64 @@
+# Copyright 2020 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')
+OAUTH_SCOPE = ['https://www.googleapis.com/auth/cloud-platform']
+OAUTH_SCOPES = [
+ 'https://www.googleapis.com/auth/devstorage.read_only',
+ 'https://www.googleapis.com/auth/logging.write',
+ 'https://www.googleapis.com/auth/monitoring.write']
+
+
+def test_defaults(plan_runner):
+ "Test resources created with variable defaults."
+ _, resources = plan_runner(FIXTURES_DIR)
+ assert len(resources) == 1
+ node_config = resources[0]['values']['node_config'][0]
+ assert node_config['oauth_scopes'] == OAUTH_SCOPES
+ assert 'service_account' not in node_config
+
+
+def test_external_sa(plan_runner):
+ "Test resources created with externally managed sa."
+ _, resources = plan_runner(
+ FIXTURES_DIR, node_service_account='foo@example.org')
+ assert len(resources) == 1
+ node_config = resources[0]['values']['node_config'][0]
+ assert node_config['oauth_scopes'] == OAUTH_SCOPES
+ assert node_config['service_account'] == 'foo@example.org'
+
+
+def test_external_scopes(plan_runner):
+ "Test resources created with externally defined scopes."
+ oauth_scopes = '["https://www.googleapis.com/auth/cloud-platform"]'
+ _, resources = plan_runner(
+ FIXTURES_DIR, node_service_account_scopes=oauth_scopes)
+ assert len(resources) == 1
+ node_config = resources[0]['values']['node_config'][0]
+ assert node_config['oauth_scopes'] == OAUTH_SCOPE
+ assert 'service_account' not in node_config
+
+
+def test_internal_sa(plan_runner):
+ "Test resources created with internally managed sa."
+ _, resources = plan_runner(FIXTURES_DIR, node_service_account_create='true')
+ assert len(resources) == 2
+ node_config = resources[0]['values']['node_config'][0]
+ assert node_config['oauth_scopes'] == OAUTH_SCOPE
+ assert 'service_account' not in node_config
diff --git a/tests/networking/shared_vpc_gke/test_plan.py b/tests/networking/shared_vpc_gke/test_plan.py
index c0c0b1c6..7f0bf42f 100644
--- a/tests/networking/shared_vpc_gke/test_plan.py
+++ b/tests/networking/shared_vpc_gke/test_plan.py
@@ -23,5 +23,5 @@ 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(modules) == 10
assert len(resources) == 43