diff --git a/README.md b/README.md index b3913a8b65..f76c1e6805 100644 --- a/README.md +++ b/README.md @@ -125,7 +125,7 @@ Then perform the following commands on the root folder: | region | The region to host the cluster in (required) | string | - | yes | | regional | Whether is a regional cluster (zonal cluster if set false. WARNING: changing this after cluster creation is destructive!) | string | `true` | no | | remove_default_node_pool | Remove default node pool while setting up the cluster | string | `false` | no | -| service_account | The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account | string | `` | no | +| service_account | The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account. May also specify `create` to automatically create a cluster-specific service account | string | `` | no | | stub_domains | Map of stub domains and their resolvers to forward DNS queries for a certain domain to an external DNS server | map | `` | no | | subnetwork | The subnetwork to host the cluster in (required) | string | - | yes | | zones | The zones to host the cluster in (optional if regional cluster / required if zonal) | list | `` | no | @@ -179,6 +179,7 @@ following project roles: - roles/compute.viewer - roles/container.clusterAdmin - roles/container.developer +- roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser ### Enable APIs diff --git a/autogen/README.md b/autogen/README.md index 5f5c9123ed..50cda2d5a1 100644 --- a/autogen/README.md +++ b/autogen/README.md @@ -1,6 +1,6 @@ # Terraform Kubernetes Engine Module -This module handles opinionated Google Cloud Platform Kubernetes Engine cluster creation and configuration with Node Pools, IP MASQ, Network Policy, etc. {% if private_cluster %}This particular submodule creates a [private cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters){% endif %} +This module handles opinionated Google Cloud Platform Kubernetes Engine cluster creation and configuration with Node Pools, IP MASQ, Network Policy, etc.{% if private_cluster %} This particular submodule creates a [private cluster](https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters){% endif %} The resources/services/activations/deletions that this module will create/trigger are: - Create a GKE cluster with the provided addons @@ -189,6 +189,7 @@ following project roles: - roles/compute.viewer - roles/container.clusterAdmin - roles/container.developer +- roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser ### Enable APIs diff --git a/autogen/cluster_regional.tf b/autogen/cluster_regional.tf index 240f23e797..a9462ed12d 100644 --- a/autogen/cluster_regional.tf +++ b/autogen/cluster_regional.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } {% if private_cluster %} @@ -127,7 +127,7 @@ resource "google_container_node_pool" "pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/autogen/cluster_zonal.tf b/autogen/cluster_zonal.tf index f56fde248c..20a72ec582 100644 --- a/autogen/cluster_zonal.tf +++ b/autogen/cluster_zonal.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "zonal_primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } {% if private_cluster %} @@ -127,7 +127,7 @@ resource "google_container_node_pool" "zonal_pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/autogen/sa.tf b/autogen/sa.tf new file mode 100644 index 0000000000..89c55f2e66 --- /dev/null +++ b/autogen/sa.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2018 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. + */ + +{{ autogeneration_note }} + +locals { + service_account_list = "${compact(concat(google_service_account.cluster_service_account.*.email, list("dummy")))}" + service_account = "${var.service_account == "create" ? element(local.service_account_list, 0) : var.service_account}" +} + +resource "google_service_account" "cluster_service_account" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${var.project_id}" + account_id = "tf-gke-${substr(var.name, 0, 20)}" + display_name = "Terraform-managed service account for cluster ${var.name}" +} + +resource "google_project_iam_member" "cluster_service_account-log_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_service_account.cluster_service_account.project}" + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-metric_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-log_writer.project}" + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-monitoring_viewer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-metric_writer.project}" + role = "roles/monitoring.viewer" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} diff --git a/autogen/variables.tf b/autogen/variables.tf index ab4cd5470a..f1ccae2754 100644 --- a/autogen/variables.tf +++ b/autogen/variables.tf @@ -208,7 +208,7 @@ variable "monitoring_service" { } variable "service_account" { - description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account" + description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account. May also specify `create` to automatically create a cluster-specific service account" default = "" } {% if private_cluster %} diff --git a/cluster_regional.tf b/cluster_regional.tf index 774b0ee102..34030ef0c5 100644 --- a/cluster_regional.tf +++ b/cluster_regional.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } @@ -121,7 +121,7 @@ resource "google_container_node_pool" "pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/cluster_zonal.tf b/cluster_zonal.tf index c56fa348d5..7b1c9a699d 100644 --- a/cluster_zonal.tf +++ b/cluster_zonal.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "zonal_primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } @@ -121,7 +121,7 @@ resource "google_container_node_pool" "zonal_pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/examples/simple_zonal/README.md b/examples/simple_zonal/README.md index edd120aa05..9a1585534b 100644 --- a/examples/simple_zonal/README.md +++ b/examples/simple_zonal/README.md @@ -10,7 +10,6 @@ This example illustrates how to create a simple cluster. | Name | Description | Type | Default | Required | |------|-------------|:----:|:-----:|:-----:| | cluster_name_suffix | A suffix to append to the default cluster name | string | `` | no | -| compute_engine_service_account | Service account to associate to the nodes in the cluster | string | - | yes | | credentials_path | The path to the GCP credentials JSON file | string | - | yes | | ip_range_pods | The secondary ip range to use for pods | string | - | yes | | ip_range_services | The secondary ip range to use for pods | string | - | yes | diff --git a/examples/simple_zonal/main.tf b/examples/simple_zonal/main.tf index a837cba357..1923d398b1 100644 --- a/examples/simple_zonal/main.tf +++ b/examples/simple_zonal/main.tf @@ -34,7 +34,7 @@ module "gke" { subnetwork = "${var.subnetwork}" ip_range_pods = "${var.ip_range_pods}" ip_range_services = "${var.ip_range_services}" - service_account = "${var.compute_engine_service_account}" + service_account = "create" } data "google_client_config" "default" {} diff --git a/examples/simple_zonal/variables.tf b/examples/simple_zonal/variables.tf index ebb151e38a..10130ab2d1 100644 --- a/examples/simple_zonal/variables.tf +++ b/examples/simple_zonal/variables.tf @@ -51,7 +51,3 @@ variable "ip_range_pods" { variable "ip_range_services" { description = "The secondary ip range to use for pods" } - -variable "compute_engine_service_account" { - description = "Service account to associate to the nodes in the cluster" -} diff --git a/modules/private-cluster/README.md b/modules/private-cluster/README.md index 0fbcb1ffac..f0611c7631 100644 --- a/modules/private-cluster/README.md +++ b/modules/private-cluster/README.md @@ -131,7 +131,7 @@ Then perform the following commands on the root folder: | region | The region to host the cluster in (required) | string | - | yes | | regional | Whether is a regional cluster (zonal cluster if set false. WARNING: changing this after cluster creation is destructive!) | string | `true` | no | | remove_default_node_pool | Remove default node pool while setting up the cluster | string | `false` | no | -| service_account | The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account | string | `` | no | +| service_account | The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account. May also specify `create` to automatically create a cluster-specific service account | string | `` | no | | stub_domains | Map of stub domains and their resolvers to forward DNS queries for a certain domain to an external DNS server | map | `` | no | | subnetwork | The subnetwork to host the cluster in (required) | string | - | yes | | zones | The zones to host the cluster in (optional if regional cluster / required if zonal) | list | `` | no | @@ -185,6 +185,7 @@ following project roles: - roles/compute.viewer - roles/container.clusterAdmin - roles/container.developer +- roles/iam.serviceAccountAdmin - roles/iam.serviceAccountUser ### Enable APIs diff --git a/modules/private-cluster/cluster_regional.tf b/modules/private-cluster/cluster_regional.tf index 947682a292..b6a77059f0 100644 --- a/modules/private-cluster/cluster_regional.tf +++ b/modules/private-cluster/cluster_regional.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } @@ -127,7 +127,7 @@ resource "google_container_node_pool" "pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/modules/private-cluster/cluster_zonal.tf b/modules/private-cluster/cluster_zonal.tf index 41dd02f926..51269e6a61 100644 --- a/modules/private-cluster/cluster_zonal.tf +++ b/modules/private-cluster/cluster_zonal.tf @@ -81,7 +81,7 @@ resource "google_container_cluster" "zonal_primary" { name = "default-pool" node_config { - service_account = "${lookup(var.node_pools[0], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[0], "service_account", local.service_account)}" } } @@ -127,7 +127,7 @@ resource "google_container_node_pool" "zonal_pools" { disk_size_gb = "${lookup(var.node_pools[count.index], "disk_size_gb", 100)}" disk_type = "${lookup(var.node_pools[count.index], "disk_type", "pd-standard")}" - service_account = "${lookup(var.node_pools[count.index], "service_account", var.service_account)}" + service_account = "${lookup(var.node_pools[count.index], "service_account", local.service_account)}" preemptible = "${lookup(var.node_pools[count.index], "preemptible", false)}" oauth_scopes = [ diff --git a/modules/private-cluster/sa.tf b/modules/private-cluster/sa.tf new file mode 100644 index 0000000000..7ac956eef8 --- /dev/null +++ b/modules/private-cluster/sa.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2018 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. + */ + +// This file was automatically generated from a template in ./autogen + +locals { + service_account_list = "${compact(concat(google_service_account.cluster_service_account.*.email, list("dummy")))}" + service_account = "${var.service_account == "create" ? element(local.service_account_list, 0) : var.service_account}" +} + +resource "google_service_account" "cluster_service_account" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${var.project_id}" + account_id = "tf-gke-${substr(var.name, 0, 20)}" + display_name = "Terraform-managed service account for cluster ${var.name}" +} + +resource "google_project_iam_member" "cluster_service_account-log_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_service_account.cluster_service_account.project}" + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-metric_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-log_writer.project}" + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-monitoring_viewer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-metric_writer.project}" + role = "roles/monitoring.viewer" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} diff --git a/modules/private-cluster/variables.tf b/modules/private-cluster/variables.tf index eabed2c2a6..448832cd57 100644 --- a/modules/private-cluster/variables.tf +++ b/modules/private-cluster/variables.tf @@ -208,7 +208,7 @@ variable "monitoring_service" { } variable "service_account" { - description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account" + description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account. May also specify `create` to automatically create a cluster-specific service account" default = "" } diff --git a/sa.tf b/sa.tf new file mode 100644 index 0000000000..7ac956eef8 --- /dev/null +++ b/sa.tf @@ -0,0 +1,50 @@ +/** + * Copyright 2018 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. + */ + +// This file was automatically generated from a template in ./autogen + +locals { + service_account_list = "${compact(concat(google_service_account.cluster_service_account.*.email, list("dummy")))}" + service_account = "${var.service_account == "create" ? element(local.service_account_list, 0) : var.service_account}" +} + +resource "google_service_account" "cluster_service_account" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${var.project_id}" + account_id = "tf-gke-${substr(var.name, 0, 20)}" + display_name = "Terraform-managed service account for cluster ${var.name}" +} + +resource "google_project_iam_member" "cluster_service_account-log_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_service_account.cluster_service_account.project}" + role = "roles/logging.logWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-metric_writer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-log_writer.project}" + role = "roles/monitoring.metricWriter" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} + +resource "google_project_iam_member" "cluster_service_account-monitoring_viewer" { + count = "${var.service_account == "create" ? 1 : 0}" + project = "${google_project_iam_member.cluster_service_account-metric_writer.project}" + role = "roles/monitoring.viewer" + member = "serviceAccount:${google_service_account.cluster_service_account.email}" +} diff --git a/test/fixtures/simple_zonal/example.tf b/test/fixtures/simple_zonal/example.tf index f928502053..2874e4b91e 100644 --- a/test/fixtures/simple_zonal/example.tf +++ b/test/fixtures/simple_zonal/example.tf @@ -17,14 +17,13 @@ module "example" { source = "../../../examples/simple_zonal" - project_id = "${var.project_id}" - credentials_path = "${local.credentials_path}" - cluster_name_suffix = "-${random_string.suffix.result}" - region = "${var.region}" - zones = ["${slice(var.zones,0,1)}"] - network = "${google_compute_network.main.name}" - subnetwork = "${google_compute_subnetwork.main.name}" - ip_range_pods = "${google_compute_subnetwork.main.secondary_ip_range.0.range_name}" - ip_range_services = "${google_compute_subnetwork.main.secondary_ip_range.1.range_name}" - compute_engine_service_account = "${var.compute_engine_service_account}" + project_id = "${var.project_id}" + credentials_path = "${local.credentials_path}" + cluster_name_suffix = "-${random_string.suffix.result}" + region = "${var.region}" + zones = ["${slice(var.zones,0,1)}"] + network = "${google_compute_network.main.name}" + subnetwork = "${google_compute_subnetwork.main.name}" + ip_range_pods = "${google_compute_subnetwork.main.secondary_ip_range.0.range_name}" + ip_range_services = "${google_compute_subnetwork.main.secondary_ip_range.1.range_name}" } diff --git a/test/integration/simple_zonal/controls/gcloud.rb b/test/integration/simple_zonal/controls/gcloud.rb index cfd45420e8..ce11017228 100644 --- a/test/integration/simple_zonal/controls/gcloud.rb +++ b/test/integration/simple_zonal/controls/gcloud.rb @@ -79,6 +79,16 @@ describe "node pool" do let(:node_pools) { data['nodePools'].reject { |p| p['name'] == "default-pool" } } + it "uses an automatically created service account" do + expect(node_pools).to include( + including( + "config" => including( + "serviceAccount" => starting_with("tf-gke-simple-zonal-cluster@"), + ), + ), + ) + end + it "has autoscaling enabled" do expect(node_pools).to include( including( diff --git a/variables.tf b/variables.tf index b185c9b7d5..e4f7cac1c3 100644 --- a/variables.tf +++ b/variables.tf @@ -208,6 +208,6 @@ variable "monitoring_service" { } variable "service_account" { - description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account" + description = "The service account to default running nodes as if not overridden in `node_pools`. Defaults to the compute engine default service account. May also specify `create` to automatically create a cluster-specific service account" default = "" }