diff --git a/.github/workflows/multichain-prod.yml b/.github/workflows/multichain-prod.yml new file mode 100644 index 000000000..c3b1cc405 --- /dev/null +++ b/.github/workflows/multichain-prod.yml @@ -0,0 +1,58 @@ +name: Deploy Multichain Prod. +on: + workflow_dispatch: + inputs: + network: + type: choice + options: + - mainnet + - testnet + description: mainnet or testnet network + required: true + image: + description: Full Artifact Registry image with tag (e.g. us-east1-docker.pkg.dev/pagoda-discovery-platform-prod/multichain/multichain-< testnet | mainnet >) + required: true + tag: + description: Image tag that you wish to deploy, either by SHA or Version/latest + +jobs: + build-mpc-recovery: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + name: "Checkout mpc-recovery" + + - name: Login to Artifact Registry + uses: docker/login-action@v2 + with: + registry: us-east1-docker.pkg.dev + username: _json_key + password: ${{ secrets.GCP_CREDENTIALS_PROD }} + + - name: Build Docker image and push to Google Artifact Registry + id: docker-push-tagged + uses: docker/build-push-action@v4 + with: + push: true + file: ./Dockerfile.multichain + tags: "${{ github.event.inputs.image }}:${{ github.event.inputs.tag }}" + + deploy: + runs-on: ubuntu-latest + steps: + - id: 'auth' + uses: 'google-github-actions/auth@v2' + with: + credentials_json: '${{ secrets.GCP_CREDENTIALS_PROD }}' + + - name: 'Set up Cloud SDK' + uses: 'google-github-actions/setup-gcloud@v2' + + - name: 'Set project' + run: 'gcloud config set project pagoda-discovery-platform-prod' + + - name: 'Update Nodes' + run: | + gcloud compute instances update-container multichain-${{ github.event.inputs.network }}-0 + gcloud compute instances update-container multichain-${{ github.event.inputs.network }}-1 + gcloud compute instances update-container multichain-${{ github.event.inputs.network }}-2 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7d7a7b9de..569512ae4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ flamegraph*.svg tmp +*.log \ No newline at end of file diff --git a/infra/modules/instance-from-tpl/main.tf b/infra/modules/instance-from-tpl/main.tf new file mode 100644 index 000000000..f3dad9f93 --- /dev/null +++ b/infra/modules/instance-from-tpl/main.tf @@ -0,0 +1,83 @@ +locals { + hostname = var.hostname == "" ? "default" : var.hostname + num_instances = length(var.static_ips) == 0 ? var.num_instances : length(var.static_ips) + + # local.static_ips is the same as var.static_ips with a dummy element appended + # at the end of the list to work around "list does not have any elements so cannot + # determine type" error when var.static_ips is empty + static_ips = concat(var.static_ips, ["NOT_AN_IP"]) + + zones = length(var.zones) == 0 ? data.google_compute_zones.available.names : var.zones + + instance_group_count = min( + local.num_instances, + length(local.zones), + ) +} + +############### +# Data Sources +############### + +data "google_compute_zones" "available" { + project = var.project_id + region = var.region + status = "UP" +} + +resource "google_compute_instance_from_template" "compute_instance" { + provider = google + count = local.num_instances + name = local.hostname + project = var.project_id + zone = local.zones[count.index % length(local.zones)] + + network_interface { + network = var.network + subnetwork = var.subnetwork + subnetwork_project = var.subnetwork_project + network_ip = length(var.static_ips) == 0 ? "" : element(local.static_ips, count.index) + + dynamic "access_config" { + # convert to map to use lookup function with default value + for_each = lookup({ for k, v in var.access_config : k => v }, count.index, []) + content { + nat_ip = access_config.value.nat_ip + network_tier = access_config.value.network_tier + } + } + + dynamic "ipv6_access_config" { + # convert to map to use lookup function with default value + for_each = lookup({ for k, v in var.ipv6_access_config : k => v }, count.index, []) + content { + network_tier = ipv6_access_config.value.network_tier + } + } + } + + dynamic "network_interface" { + for_each = var.additional_networks + content { + network = network_interface.value.network + subnetwork = network_interface.value.subnetwork + subnetwork_project = network_interface.value.subnetwork_project + network_ip = length(network_interface.value.network_ip) > 0 ? network_interface.value.network_ip : null + dynamic "access_config" { + for_each = network_interface.value.access_config + content { + nat_ip = access_config.value.nat_ip + network_tier = access_config.value.network_tier + } + } + dynamic "ipv6_access_config" { + for_each = network_interface.value.ipv6_access_config + content { + network_tier = ipv6_access_config.value.network_tier + } + } + } + } + + source_instance_template = var.instance_template +} diff --git a/infra/modules/instance-from-tpl/metadata.yaml b/infra/modules/instance-from-tpl/metadata.yaml new file mode 100644 index 000000000..aa432cf65 --- /dev/null +++ b/infra/modules/instance-from-tpl/metadata.yaml @@ -0,0 +1,286 @@ +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-vm-mig + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: Managed Instance Group (MIG) + source: + repo: https://github.com/terraform-google-modules/terraform-google-vm + sourceType: git + dir: /modules/mig + version: 10.1.1 + actuationTool: + flavor: Terraform + version: ">=0.13.0" + description: {} + content: + examples: + - name: additional_disks + location: examples/instance_template/additional_disks + - name: alias_ip_range + location: examples/instance_template/alias_ip_range + - name: autoscaler + location: examples/mig/autoscaler + - name: disk_snapshot + location: examples/compute_instance/disk_snapshot + - name: encrypted_disks + location: examples/instance_template/encrypted_disks + - name: full + location: examples/mig/full + - name: full + location: examples/umig/full + - name: healthcheck + location: examples/mig/healthcheck + - name: mig_stateful + location: examples/mig_stateful + - name: multiple_interfaces + location: examples/compute_instance/multiple_interfaces + - name: named_ports + location: examples/umig/named_ports + - name: next_hop + location: examples/compute_instance/next_hop + - name: simple + location: examples/compute_instance/simple + - name: simple + location: examples/instance_template/simple + - name: simple + location: examples/mig/simple + - name: simple + location: examples/mig_with_percent/simple + - name: simple + location: examples/preemptible_and_regular_instance_templates/simple + - name: simple + location: examples/umig/simple + - name: static_ips + location: examples/umig/static_ips + - name: tags + location: examples/compute_instance/tags + interfaces: + variables: + - name: autoscaler_name + description: Autoscaler name. When variable is empty, name will be derived from var.hostname. + varType: string + defaultValue: "" + - name: autoscaling_cpu + description: Autoscaling, cpu utilization policy block as single element array. https://www.terraform.io/docs/providers/google/r/compute_autoscaler#cpu_utilization + varType: |- + list(object({ + target = number + predictive_method = string + })) + defaultValue: [] + - name: autoscaling_enabled + description: Creates an autoscaler for the managed instance group + varType: string + defaultValue: "false" + - name: autoscaling_lb + description: Autoscaling, load balancing utilization policy block as single element array. https://www.terraform.io/docs/providers/google/r/compute_autoscaler#load_balancing_utilization + varType: list(map(number)) + defaultValue: [] + - name: autoscaling_metric + description: Autoscaling, metric policy block as single element array. https://www.terraform.io/docs/providers/google/r/compute_autoscaler#metric + varType: |- + list(object({ + name = string + target = number + type = string + })) + defaultValue: [] + - name: autoscaling_mode + description: Operating mode of the autoscaling policy. If omitted, the default value is ON. https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_autoscaler#mode + varType: string + defaultValue: null + - name: autoscaling_scale_in_control + description: Autoscaling, scale-in control block. https://www.terraform.io/docs/providers/google/r/compute_autoscaler#scale_in_control + varType: |- + object({ + fixed_replicas = number + percent_replicas = number + time_window_sec = number + }) + defaultValue: + fixed_replicas: null + percent_replicas: null + time_window_sec: null + - name: cooldown_period + description: The number of seconds that the autoscaler should wait before it starts collecting information from a new instance. + varType: number + defaultValue: 60 + - name: distribution_policy_target_shape + description: MIG target distribution shape (EVEN, BALANCED, ANY, ANY_SINGLE_ZONE) + varType: string + defaultValue: null + - name: distribution_policy_zones + description: The distribution policy, i.e. which zone(s) should instances be create in. Default is all zones in given region. + varType: list(string) + defaultValue: [] + - name: health_check + description: Health check to determine whether instances are responsive and able to do work + varType: |- + object({ + type = string + initial_delay_sec = number + check_interval_sec = number + healthy_threshold = number + timeout_sec = number + unhealthy_threshold = number + response = string + proxy_header = string + port = number + request = string + request_path = string + host = string + enable_logging = bool + }) + defaultValue: + check_interval_sec: 30 + enable_logging: false + healthy_threshold: 1 + host: "" + initial_delay_sec: 30 + port: 80 + proxy_header: NONE + request: "" + request_path: / + response: "" + timeout_sec: 10 + type: "" + unhealthy_threshold: 5 + - name: health_check_name + description: Health check name. When variable is empty, name will be derived from var.hostname. + varType: string + defaultValue: "" + - name: hostname + description: Hostname prefix for instances + varType: string + defaultValue: default + - name: instance_template + description: Instance template self_link used to create compute instances + varType: string + defaultValue: null + required: true + - name: max_replicas + description: The maximum number of instances that the autoscaler can scale up to. This is required when creating or updating an autoscaler. The maximum number of replicas should not be lower than minimal number of replicas. + varType: number + defaultValue: 10 + - name: mig_name + description: Managed instance group name. When variable is empty, name will be derived from var.hostname. + varType: string + defaultValue: "" + - name: mig_timeouts + description: "Times for creation, deleting and updating the MIG resources. Can be helpful when using wait_for_instances to allow a longer VM startup time. " + varType: |- + object({ + create = string + update = string + delete = string + }) + defaultValue: + create: 5m + delete: 15m + update: 5m + - name: min_replicas + description: The minimum number of replicas that the autoscaler can scale down to. This cannot be less than 0. + varType: number + defaultValue: 2 + - name: named_ports + description: Named name and named port. https://cloud.google.com/load-balancing/docs/backend-service#named_ports + varType: |- + list(object({ + name = string + port = number + })) + defaultValue: [] + - name: project_id + description: The GCP project ID + varType: string + defaultValue: null + - name: region + description: The GCP region where the managed instance group resides. + varType: string + defaultValue: null + required: true + - name: scaling_schedules + description: Autoscaling, scaling schedule block. https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_autoscaler#scaling_schedules + varType: |- + list(object({ + disabled = bool + duration_sec = number + min_required_replicas = number + name = string + schedule = string + time_zone = string + })) + defaultValue: [] + - name: stateful_disks + description: Disks created on the instances that will be preserved on instance delete. https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-disks-in-migs + varType: |- + list(object({ + device_name = string + delete_rule = string + })) + defaultValue: [] + - name: stateful_ips + description: Statful IPs created on the instances that will be preserved on instance delete. https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-ip-addresses-in-migs + varType: |- + list(object({ + interface_name = string + delete_rule = string + is_external = bool + })) + defaultValue: [] + - name: target_pools + description: The target load balancing pools to assign this group to. + varType: list(string) + defaultValue: [] + - name: target_size + description: The target number of running instances for this managed instance group. This value should always be explicitly set unless this resource is attached to an autoscaler, in which case it should never be set. + varType: number + defaultValue: 1 + - name: update_policy + description: The rolling update policy. https://www.terraform.io/docs/providers/google/r/compute_region_instance_group_manager#rolling_update_policy + varType: |- + list(object({ + max_surge_fixed = number + instance_redistribution_type = string + max_surge_percent = number + max_unavailable_fixed = number + max_unavailable_percent = number + min_ready_sec = number + replacement_method = string + minimal_action = string + type = string + })) + defaultValue: [] + - name: wait_for_instances + description: Whether to wait for all instances to be created/updated before returning. Note that if this is set to true and the operation does not succeed, Terraform will continue trying until it times out. + varType: string + defaultValue: "false" + outputs: + - name: health_check_self_links + description: All self_links of healthchecks created for the instance group. + - name: instance_group + description: Instance-group url of managed instance group + - name: instance_group_manager + description: An instance of google_compute_region_instance_group_manager of the instance group. + - name: self_link + description: Self-link of managed instance group + requirements: + roles: + - level: Project + roles: + - roles/owner + - roles/compute.admin + - roles/compute.networkAdmin + - roles/iam.serviceAccountUser + - roles/compute.instanceAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - compute.googleapis.com + - iam.googleapis.com + \ No newline at end of file diff --git a/infra/modules/instance-from-tpl/outputs.tf b/infra/modules/instance-from-tpl/outputs.tf new file mode 100644 index 000000000..1d668a9b8 --- /dev/null +++ b/infra/modules/instance-from-tpl/outputs.tf @@ -0,0 +1,9 @@ + +output "available_zones" { + description = "List of available zones in region" + value = data.google_compute_zones.available.names +} + +output "self_links" { + value = google_compute_instance_from_template.compute_instance[*].self_link +} diff --git a/infra/modules/instance-from-tpl/variables.tf b/infra/modules/instance-from-tpl/variables.tf new file mode 100644 index 000000000..0663d53f9 --- /dev/null +++ b/infra/modules/instance-from-tpl/variables.tf @@ -0,0 +1,107 @@ +variable "project_id" { + type = string + description = "The GCP project ID" + default = null +} + +variable "network" { + description = "Network to deploy to. Only one of network or subnetwork should be specified." + type = string + default = "" +} + +variable "region" { + description = "The GCP region where the unmanaged instance group resides." + type = string +} + +variable "subnetwork" { + description = "Subnet to deploy to. Only one of network or subnetwork should be specified." + type = string + default = "" +} + +variable "subnetwork_project" { + description = "The project that subnetwork belongs to" + type = string + default = "" +} + +variable "additional_networks" { + description = "Additional network interface details for GCE, if any." + default = [] + type = list(object({ + network = string + subnetwork = string + subnetwork_project = string + network_ip = string + access_config = list(object({ + nat_ip = string + network_tier = string + })) + ipv6_access_config = list(object({ + network_tier = string + })) + })) +} + +variable "hostname" { + description = "Hostname of instances" + type = string + default = "" +} + +variable "static_ips" { + type = list(string) + description = "List of static IPs for VM instances" + default = [] +} + +variable "num_instances" { + description = "Number of instances to create. This value is ignored if static_ips is provided." + type = string + default = "1" +} + +variable "named_ports" { + description = "Named name and named port" + type = list(object({ + name = string + port = number + })) + default = [] +} + +variable "instance_template" { + description = "Instance template self_link used to create compute instances" + type = string +} + +variable "access_config" { + description = "Access configurations, i.e. IPs via which the VM instance can be accessed via the Internet." + type = list(list(object({ + nat_ip = string + network_tier = string + }))) + default = [] +} + +variable "ipv6_access_config" { + description = "IPv6 access configurations. Currently a max of 1 IPv6 access configuration is supported. If not specified, the instance will have no external IPv6 Internet access." + type = list(list(object({ + network_tier = string + }))) + default = [] +} + +variable "hostname_suffix_separator" { + type = string + description = "Separator character to compose hostname when add_hostname_suffix is set to true." + default = "-" +} + +variable "zones" { + type = list(string) + description = "(Optional) List of availability zones to create VM instances in" + default = [] +} diff --git a/infra/modules/instance-from-tpl/versions.tf b/infra/modules/instance-from-tpl/versions.tf new file mode 100644 index 000000000..43ed0a439 --- /dev/null +++ b/infra/modules/instance-from-tpl/versions.tf @@ -0,0 +1,19 @@ +terraform { + required_version = ">=0.13.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.48, < 6" + } + google-beta = { + source = "hashicorp/google-beta" + version = ">= 4.48, < 6" + } + } + provider_meta "google" { + module_name = "blueprints/terraform/terraform-google-vm:mig/v10.1.1" + } + provider_meta "google-beta" { + module_name = "blueprints/terraform/terraform-google-vm:mig/v10.1.1" + } +} diff --git a/infra/modules/mig_template/main.tf b/infra/modules/mig_template/main.tf new file mode 100644 index 000000000..87192d85c --- /dev/null +++ b/infra/modules/mig_template/main.tf @@ -0,0 +1,198 @@ +######### +# Locals +######### + +locals { + source_image = var.source_image != "" ? var.source_image : "centos-7-v20201112" + source_image_family = var.source_image_family != "" ? var.source_image_family : "centos-7" + source_image_project = var.source_image_project != "" ? var.source_image_project : "centos-cloud" + + boot_disk = [ + { + source_image = var.source_image != "" ? format("${local.source_image_project}/${local.source_image}") : format("${local.source_image_project}/${local.source_image_family}") + disk_size_gb = var.disk_size_gb + disk_type = var.disk_type + disk_labels = var.disk_labels + auto_delete = var.auto_delete + boot = "true" + }, + ] + + all_disks = concat(local.boot_disk, var.additional_disks) + + # NOTE: Even if all the shielded_instance_config or confidential_instance_config + # values are false, if the config block exists and an unsupported image is chosen, + # the apply will fail so we use a single-value array with the default value to + # initialize the block only if it is enabled. + shielded_vm_configs = var.enable_shielded_vm ? [true] : [] + + gpu_enabled = var.gpu != null + alias_ip_range_enabled = var.alias_ip_range != null + on_host_maintenance = ( + var.preemptible || var.enable_confidential_vm || local.gpu_enabled || var.spot + ? "TERMINATE" + : var.on_host_maintenance + ) + automatic_restart = ( + # must be false when preemptible or spot is true + var.preemptible || var.spot ? false : var.automatic_restart + ) + preemptible = ( + # must be true when preemtible or spot is true + var.preemptible || var.spot ? true : false + ) +} + +#################### +# Instance Template +#################### +resource "google_compute_instance_template" "tpl" { + name_prefix = "${var.name_prefix}-" + project = var.project_id + machine_type = var.machine_type + labels = var.labels + metadata = var.metadata + tags = var.tags + can_ip_forward = var.can_ip_forward + metadata_startup_script = var.startup_script + region = var.region + min_cpu_platform = var.min_cpu_platform + resource_policies = var.resource_policies + dynamic "disk" { + for_each = local.all_disks + content { + auto_delete = lookup(disk.value, "auto_delete", null) + boot = lookup(disk.value, "boot", null) + device_name = lookup(disk.value, "device_name", null) + disk_name = lookup(disk.value, "disk_name", null) + disk_size_gb = lookup(disk.value, "disk_size_gb", lookup(disk.value, "disk_type", null) == "local-ssd" ? "375" : null) + disk_type = lookup(disk.value, "disk_type", null) + interface = lookup(disk.value, "interface", lookup(disk.value, "disk_type", null) == "local-ssd" ? "NVME" : null) + mode = lookup(disk.value, "mode", null) + source = lookup(disk.value, "source", null) + source_image = lookup(disk.value, "source_image", null) + source_snapshot = lookup(disk.value, "source_snapshot", null) + type = lookup(disk.value, "disk_type", null) == "local-ssd" ? "SCRATCH" : "PERSISTENT" + labels = lookup(disk.value, "disk_labels", null) + + dynamic "disk_encryption_key" { + for_each = compact([var.disk_encryption_key == null ? null : 1]) + content { + kms_key_self_link = var.disk_encryption_key + } + } + } + } + + dynamic "service_account" { + for_each = var.service_account == null ? [] : [var.service_account] + content { + email = lookup(service_account.value, "email", null) + scopes = lookup(service_account.value, "scopes", null) + } + } + + network_interface { + network = var.network + subnetwork = var.subnetwork + subnetwork_project = var.subnetwork_project + network_ip = length(var.network_ip) > 0 ? var.network_ip : null + nic_type = var.nic_type + stack_type = var.stack_type + dynamic "access_config" { + for_each = var.access_config + content { + nat_ip = access_config.value.nat_ip + network_tier = access_config.value.network_tier + } + } + dynamic "ipv6_access_config" { + for_each = var.ipv6_access_config + content { + network_tier = ipv6_access_config.value.network_tier + } + } + dynamic "alias_ip_range" { + for_each = local.alias_ip_range_enabled ? [var.alias_ip_range] : [] + content { + ip_cidr_range = alias_ip_range.value.ip_cidr_range + subnetwork_range_name = alias_ip_range.value.subnetwork_range_name + } + } + } + + dynamic "network_interface" { + for_each = var.additional_networks + content { + network = network_interface.value.network + subnetwork = network_interface.value.subnetwork + subnetwork_project = network_interface.value.subnetwork_project + network_ip = length(network_interface.value.network_ip) > 0 ? network_interface.value.network_ip : null + nic_type = network_interface.value.nic_type + stack_type = network_interface.value.stack_type + queue_count = network_interface.value.queue_count + dynamic "access_config" { + for_each = network_interface.value.access_config + content { + nat_ip = access_config.value.nat_ip + network_tier = access_config.value.network_tier + } + } + dynamic "ipv6_access_config" { + for_each = network_interface.value.ipv6_access_config + content { + network_tier = ipv6_access_config.value.network_tier + } + } + dynamic "alias_ip_range" { + for_each = network_interface.value.alias_ip_range + content { + ip_cidr_range = alias_ip_range.value.ip_cidr_range + subnetwork_range_name = alias_ip_range.value.subnetwork_range_name + } + } + } + } + + lifecycle { + create_before_destroy = "true" + } + + scheduling { + preemptible = local.preemptible + automatic_restart = local.automatic_restart + on_host_maintenance = local.on_host_maintenance + provisioning_model = var.spot ? "SPOT" : null + instance_termination_action = var.spot ? var.spot_instance_termination_action : null + } + + advanced_machine_features { + enable_nested_virtualization = var.enable_nested_virtualization + threads_per_core = var.threads_per_core + } + + dynamic "shielded_instance_config" { + for_each = local.shielded_vm_configs + content { + enable_secure_boot = lookup(var.shielded_instance_config, "enable_secure_boot", shielded_instance_config.value) + enable_vtpm = lookup(var.shielded_instance_config, "enable_vtpm", shielded_instance_config.value) + enable_integrity_monitoring = lookup(var.shielded_instance_config, "enable_integrity_monitoring", shielded_instance_config.value) + } + } + + confidential_instance_config { + enable_confidential_compute = var.enable_confidential_vm + } + + network_performance_config { + total_egress_bandwidth_tier = var.total_egress_bandwidth_tier + } + + dynamic "guest_accelerator" { + for_each = local.gpu_enabled ? [var.gpu] : [] + content { + type = guest_accelerator.value.type + count = guest_accelerator.value.count + } + } +} diff --git a/infra/modules/mig_template/metadata.yaml b/infra/modules/mig_template/metadata.yaml new file mode 100644 index 000000000..505f43098 --- /dev/null +++ b/infra/modules/mig_template/metadata.yaml @@ -0,0 +1,320 @@ +apiVersion: blueprints.cloud.google.com/v1alpha1 +kind: BlueprintMetadata +metadata: + name: terraform-google-vm-instance-template + annotations: + config.kubernetes.io/local-config: "true" +spec: + info: + title: instance_template + source: + repo: https://github.com/terraform-google-modules/terraform-google-vm + sourceType: git + dir: /modules/instance_template + version: 10.1.1 + actuationTool: + flavor: Terraform + version: ">=0.13.0" + description: {} + content: + examples: + - name: additional_disks + location: examples/instance_template/additional_disks + - name: alias_ip_range + location: examples/instance_template/alias_ip_range + - name: autoscaler + location: examples/mig/autoscaler + - name: disk_snapshot + location: examples/compute_instance/disk_snapshot + - name: encrypted_disks + location: examples/instance_template/encrypted_disks + - name: full + location: examples/mig/full + - name: full + location: examples/umig/full + - name: healthcheck + location: examples/mig/healthcheck + - name: mig_stateful + location: examples/mig_stateful + - name: multiple_interfaces + location: examples/compute_instance/multiple_interfaces + - name: named_ports + location: examples/umig/named_ports + - name: next_hop + location: examples/compute_instance/next_hop + - name: simple + location: examples/compute_instance/simple + - name: simple + location: examples/instance_template/simple + - name: simple + location: examples/mig/simple + - name: simple + location: examples/mig_with_percent/simple + - name: simple + location: examples/preemptible_and_regular_instance_templates/simple + - name: simple + location: examples/umig/simple + - name: static_ips + location: examples/umig/static_ips + - name: tags + location: examples/compute_instance/tags + interfaces: + variables: + - name: access_config + description: Access configurations, i.e. IPs via which the VM instance can be accessed via the Internet. + varType: |- + list(object({ + nat_ip = string + network_tier = string + })) + defaultValue: [] + - name: additional_disks + description: List of maps of additional disks. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#disk_name + varType: |- + list(object({ + disk_name = string + device_name = string + auto_delete = bool + boot = bool + disk_size_gb = number + disk_type = string + disk_labels = map(string) + source_snapshot = optional(string) + })) + defaultValue: [] + - name: additional_networks + description: Additional network interface details for GCE, if any. + varType: |- + list(object({ + network = string + subnetwork = string + subnetwork_project = string + network_ip = string + nic_type = string + stack_type = string + queue_count = number + access_config = list(object({ + nat_ip = string + network_tier = string + })) + ipv6_access_config = list(object({ + network_tier = string + })) + alias_ip_range = list(object({ + ip_cidr_range = string + subnetwork_range_name = string + })) + })) + defaultValue: [] + - name: alias_ip_range + description: | + An array of alias IP ranges for this network interface. Can only be specified for network interfaces on subnet-mode networks. + ip_cidr_range: The IP CIDR range represented by this alias IP range. This IP CIDR range must belong to the specified subnetwork and cannot contain IP addresses reserved by system or used by other network interfaces. At the time of writing only a netmask (e.g. /24) may be supplied, with a CIDR format resulting in an API error. + subnetwork_range_name: The subnetwork secondary range name specifying the secondary range from which to allocate the IP CIDR range for this alias IP range. If left unspecified, the primary range of the subnetwork will be used. + varType: |- + object({ + ip_cidr_range = string + subnetwork_range_name = string + }) + defaultValue: null + - name: auto_delete + description: Whether or not the boot disk should be auto-deleted + varType: string + defaultValue: "true" + - name: automatic_restart + description: (Optional) Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user). + varType: bool + defaultValue: true + - name: can_ip_forward + description: Enable IP forwarding, for NAT instances for example + varType: string + defaultValue: "false" + - name: disk_encryption_key + description: The id of the encryption key that is stored in Google Cloud KMS to use to encrypt all the disks on this instance + varType: string + defaultValue: null + - name: disk_labels + description: Labels to be assigned to boot disk, provided as a map + varType: map(string) + defaultValue: {} + - name: disk_size_gb + description: Boot disk size in GB + varType: string + defaultValue: "100" + - name: disk_type + description: Boot disk type, can be either pd-ssd, local-ssd, or pd-standard + varType: string + defaultValue: pd-standard + - name: enable_confidential_vm + description: Whether to enable the Confidential VM configuration on the instance. Note that the instance image must support Confidential VMs. See https://cloud.google.com/compute/docs/images + varType: bool + defaultValue: false + - name: enable_nested_virtualization + description: Defines whether the instance should have nested virtualization enabled. + varType: bool + defaultValue: false + - name: enable_shielded_vm + description: Whether to enable the Shielded VM configuration on the instance. Note that the instance image must support Shielded VMs. See https://cloud.google.com/compute/docs/images + varType: bool + defaultValue: false + - name: gpu + description: GPU information. Type and count of GPU to attach to the instance template. See https://cloud.google.com/compute/docs/gpus more details + varType: |- + object({ + type = string + count = number + }) + defaultValue: null + - name: ipv6_access_config + description: IPv6 access configurations. Currently a max of 1 IPv6 access configuration is supported. If not specified, the instance will have no external IPv6 Internet access. + varType: |- + list(object({ + network_tier = string + })) + defaultValue: [] + - name: labels + description: Labels, provided as a map + varType: map(string) + defaultValue: {} + - name: machine_type + description: Machine type to create, e.g. n1-standard-1 + varType: string + defaultValue: n1-standard-1 + - name: metadata + description: Metadata, provided as a map + varType: map(string) + defaultValue: {} + - name: min_cpu_platform + description: "Specifies a minimum CPU platform. Applicable values are the friendly names of CPU platforms, such as Intel Haswell or Intel Skylake. See the complete list: https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform" + varType: string + defaultValue: null + - name: name_prefix + description: Name prefix for the instance template + varType: string + defaultValue: default-instance-template + - name: network + description: The name or self_link of the network to attach this interface to. Use network attribute for Legacy or Auto subnetted networks and subnetwork for custom subnetted networks. + varType: string + defaultValue: "" + - name: network_ip + description: Private IP address to assign to the instance if desired. + varType: string + defaultValue: "" + - name: nic_type + description: Valid values are "VIRTIO_NET", "GVNIC" or set to null to accept API default behavior. + varType: string + defaultValue: null + - name: on_host_maintenance + description: Instance availability Policy + varType: string + defaultValue: MIGRATE + - name: preemptible + description: Allow the instance to be preempted + varType: bool + defaultValue: false + - name: project_id + description: The GCP project ID + varType: string + defaultValue: null + - name: region + description: Region where the instance template should be created. + varType: string + defaultValue: null + - name: resource_policies + description: A list of self_links of resource policies to attach to the instance. Modifying this list will cause the instance to recreate. Currently a max of 1 resource policy is supported. + varType: list(string) + defaultValue: [] + - name: service_account + description: Service account to attach to the instance. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#service_account. + varType: |- + object({ + email = string + scopes = set(string) + }) + defaultValue: null + required: true + - name: shielded_instance_config + description: Not used unless enable_shielded_vm is true. Shielded VM configuration for the instance. + varType: |- + object({ + enable_secure_boot = bool + enable_vtpm = bool + enable_integrity_monitoring = bool + }) + defaultValue: + enable_integrity_monitoring: true + enable_secure_boot: true + enable_vtpm: true + - name: source_image + description: Source disk image. If neither source_image nor source_image_family is specified, defaults to the latest public CentOS image. + varType: string + defaultValue: "" + - name: source_image_family + description: Source image family. If neither source_image nor source_image_family is specified, defaults to the latest public CentOS image. + varType: string + defaultValue: centos-7 + - name: source_image_project + description: Project where the source image comes from. The default project contains CentOS images. + varType: string + defaultValue: centos-cloud + - name: spot + description: Provision a SPOT instance + varType: bool + defaultValue: false + - name: spot_instance_termination_action + description: Action to take when Compute Engine preempts a Spot VM. + varType: string + defaultValue: STOP + - name: stack_type + description: The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are `IPV4_IPV6` or `IPV4_ONLY`. Default behavior is equivalent to IPV4_ONLY. + varType: string + defaultValue: null + - name: startup_script + description: User startup script to run when instances spin up + varType: string + defaultValue: "" + - name: subnetwork + description: The name of the subnetwork to attach this interface to. The subnetwork must exist in the same region this instance will be created in. Either network or subnetwork must be provided. + varType: string + defaultValue: "" + - name: subnetwork_project + description: The ID of the project in which the subnetwork belongs. If it is not provided, the provider project is used. + varType: string + defaultValue: "" + - name: tags + description: Network tags, provided as a list + varType: list(string) + defaultValue: [] + - name: threads_per_core + description: The number of threads per physical core. To disable simultaneous multithreading (SMT) set this to 1. + varType: number + defaultValue: null + - name: total_egress_bandwidth_tier + description: Egress bandwidth tier setting for supported VM families + varType: string + defaultValue: DEFAULT + outputs: + - name: name + description: Name of instance template + - name: self_link + description: Self-link of instance template + - name: self_link_unique + description: Unique self-link of instance template (recommended output to use instead of self_link) + - name: tags + description: Tags that will be associated with instance(s) + requirements: + roles: + - level: Project + roles: + - roles/owner + - roles/compute.admin + - roles/compute.networkAdmin + - roles/iam.serviceAccountUser + - roles/compute.instanceAdmin + services: + - cloudresourcemanager.googleapis.com + - storage-api.googleapis.com + - serviceusage.googleapis.com + - compute.googleapis.com + - iam.googleapis.com + \ No newline at end of file diff --git a/infra/modules/mig_template/outputs.tf b/infra/modules/mig_template/outputs.tf new file mode 100644 index 000000000..45f93af81 --- /dev/null +++ b/infra/modules/mig_template/outputs.tf @@ -0,0 +1,19 @@ +output "self_link_unique" { + description = "Unique self-link of instance template (recommended output to use instead of self_link)" + value = google_compute_instance_template.tpl.self_link_unique +} + +output "self_link" { + description = "Self-link of instance template" + value = google_compute_instance_template.tpl.self_link +} + +output "name" { + description = "Name of instance template" + value = google_compute_instance_template.tpl.name +} + +output "tags" { + description = "Tags that will be associated with instance(s)" + value = google_compute_instance_template.tpl.tags +} diff --git a/infra/modules/mig_template/variables.tf b/infra/modules/mig_template/variables.tf new file mode 100644 index 000000000..65c977b62 --- /dev/null +++ b/infra/modules/mig_template/variables.tf @@ -0,0 +1,365 @@ +variable "project_id" { + type = string + description = "The GCP project ID" + default = null +} + +variable "name_prefix" { + description = "Name prefix for the instance template" + type = string + default = "default-instance-template" +} + +variable "machine_type" { + description = "Machine type to create, e.g. n1-standard-1" + type = string +} + +variable "min_cpu_platform" { + description = "Specifies a minimum CPU platform. Applicable values are the friendly names of CPU platforms, such as Intel Haswell or Intel Skylake. See the complete list: https://cloud.google.com/compute/docs/instances/specify-min-cpu-platform" + type = string + default = null +} + +variable "can_ip_forward" { + description = "Enable IP forwarding, for NAT instances for example" + type = string + default = "false" +} + +variable "tags" { + type = list(string) + description = "Network tags, provided as a list" + default = [] +} + +variable "labels" { + type = map(string) + description = "Labels, provided as a map" + default = {} +} + +variable "preemptible" { + type = bool + description = "Allow the instance to be preempted" + default = false +} + +variable "spot" { + type = bool + description = "Provision a SPOT instance" + default = false +} + +variable "automatic_restart" { + type = bool + description = "(Optional) Specifies whether the instance should be automatically restarted if it is terminated by Compute Engine (not terminated by a user)." + default = true +} + +variable "on_host_maintenance" { + type = string + description = "Instance availability Policy" + default = "MIGRATE" +} + +variable "spot_instance_termination_action" { + description = "Action to take when Compute Engine preempts a Spot VM." + type = string + default = "STOP" + + validation { + condition = contains(["STOP", "DELETE"], var.spot_instance_termination_action) + error_message = "Allowed values for spot_instance_termination_action are: \"STOP\" or \"DELETE\"." + } +} + +variable "region" { + type = string + description = "Region where the instance template should be created." + default = null +} + +variable "enable_nested_virtualization" { + type = bool + description = "Defines whether the instance should have nested virtualization enabled." + default = false +} + +variable "threads_per_core" { + description = "The number of threads per physical core. To disable simultaneous multithreading (SMT) set this to 1." + type = number + default = null +} + +variable "resource_policies" { + type = list(string) + description = "A list of self_links of resource policies to attach to the instance. Modifying this list will cause the instance to recreate. Currently a max of 1 resource policy is supported." + default = [] +} + +####### +# disk +####### +variable "source_image" { + description = "Source disk image. If neither source_image nor source_image_family is specified, defaults to the latest public CentOS image." + type = string + default = "" +} + +variable "source_image_family" { + description = "Source image family. If neither source_image nor source_image_family is specified, defaults to the latest public CentOS image." + type = string + default = "centos-7" +} + +variable "source_image_project" { + description = "Project where the source image comes from. The default project contains CentOS images." + type = string + default = "centos-cloud" +} + +variable "disk_size_gb" { + description = "Boot disk size in GB" + type = string + default = "100" +} + +variable "disk_type" { + description = "Boot disk type, can be either pd-ssd, local-ssd, or pd-standard" + type = string + default = "pd-standard" +} + +variable "disk_labels" { + description = "Labels to be assigned to boot disk, provided as a map" + type = map(string) + default = {} +} + +variable "disk_encryption_key" { + description = "The id of the encryption key that is stored in Google Cloud KMS to use to encrypt all the disks on this instance" + type = string + default = null +} + +variable "auto_delete" { + description = "Whether or not the boot disk should be auto-deleted" + type = string + default = "true" +} + +variable "additional_disks" { + description = "List of maps of additional disks. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#disk_name" + type = list(object({ + disk_name = string + device_name = string + auto_delete = bool + boot = bool + disk_size_gb = number + disk_type = string + disk_labels = map(string) + source_snapshot = optional(string) + })) + default = [] +} + +#################### +# network_interface +#################### +variable "network" { + description = "The name or self_link of the network to attach this interface to. Use network attribute for Legacy or Auto subnetted networks and subnetwork for custom subnetted networks." + type = string + default = "" +} + +variable "subnetwork" { + description = "The name of the subnetwork to attach this interface to. The subnetwork must exist in the same region this instance will be created in. Either network or subnetwork must be provided." + type = string + default = "" +} + +variable "subnetwork_project" { + description = "The ID of the project in which the subnetwork belongs. If it is not provided, the provider project is used." + type = string + default = "" +} + +variable "network_ip" { + description = "Private IP address to assign to the instance if desired." + type = string + default = "" +} + +variable "nic_type" { + description = "Valid values are \"VIRTIO_NET\", \"GVNIC\" or set to null to accept API default behavior." + type = string + default = null + + validation { + condition = var.nic_type == null || var.nic_type == "GVNIC" || var.nic_type == "VIRTIO_NET" + error_message = "The \"nic_type\" variable must be set to \"VIRTIO_NET\", \"GVNIC\", or null to allow API default selection." + } +} + +variable "stack_type" { + description = "The stack type for this network interface to identify whether the IPv6 feature is enabled or not. Values are `IPV4_IPV6` or `IPV4_ONLY`. Default behavior is equivalent to IPV4_ONLY." + type = string + default = null +} + +variable "additional_networks" { + description = "Additional network interface details for GCE, if any." + default = [] + type = list(object({ + network = string + subnetwork = string + subnetwork_project = string + network_ip = string + nic_type = string + stack_type = string + queue_count = number + access_config = list(object({ + nat_ip = string + network_tier = string + })) + ipv6_access_config = list(object({ + network_tier = string + })) + alias_ip_range = list(object({ + ip_cidr_range = string + subnetwork_range_name = string + })) + })) + validation { + condition = alltrue([ + for ni in var.additional_networks : ni.nic_type == "GVNIC" || ni.nic_type == "VIRTIO_NET" || ni.nic_type == null + ]) + error_message = "In the variable additional_networks, field \"nic_type\" must be either \"GVNIC\", \"VIRTIO_NET\" or null." + } + validation { + condition = alltrue([ + for ni in var.additional_networks : ni.stack_type == "IPV4_ONLY" || ni.stack_type == "IPV4_IPV6" || ni.stack_type == null + ]) + error_message = "In the variable additional_networks, field \"stack_type\" must be either \"IPV4_ONLY\", \"IPV4_IPV6\" or null." + } +} + +variable "total_egress_bandwidth_tier" { + description = "Egress bandwidth tier setting for supported VM families" + type = string + default = "DEFAULT" + validation { + condition = contains(["DEFAULT", "TIER_1"], var.total_egress_bandwidth_tier) + error_message = "Allowed values for bandwidth_tier are 'DEFAULT' or 'TIER_1'." + } +} + +########### +# metadata +########### + +variable "startup_script" { + description = "User startup script to run when instances spin up" + type = string + default = "" +} + +variable "metadata" { + type = map(string) + description = "Metadata, provided as a map" + default = {} +} + +################## +# service_account +################## + +variable "service_account" { + type = object({ + email = string + scopes = set(string) + }) + description = "Service account to attach to the instance. See https://www.terraform.io/docs/providers/google/r/compute_instance_template#service_account." +} + +########################### +# Shielded VMs +########################### +variable "enable_shielded_vm" { + type = bool + default = false + description = "Whether to enable the Shielded VM configuration on the instance. Note that the instance image must support Shielded VMs. See https://cloud.google.com/compute/docs/images" +} + +variable "shielded_instance_config" { + description = "Not used unless enable_shielded_vm is true. Shielded VM configuration for the instance." + type = object({ + enable_secure_boot = bool + enable_vtpm = bool + enable_integrity_monitoring = bool + }) + + default = { + enable_secure_boot = true + enable_vtpm = true + enable_integrity_monitoring = true + } +} + +########################### +# Confidential Compute VMs +########################### +variable "enable_confidential_vm" { + type = bool + default = false + description = "Whether to enable the Confidential VM configuration on the instance. Note that the instance image must support Confidential VMs. See https://cloud.google.com/compute/docs/images" +} + +########################### +# Public IP +########################### +variable "access_config" { + description = "Access configurations, i.e. IPs via which the VM instance can be accessed via the Internet." + type = list(object({ + nat_ip = string + network_tier = string + })) + default = [] +} + +variable "ipv6_access_config" { + description = "IPv6 access configurations. Currently a max of 1 IPv6 access configuration is supported. If not specified, the instance will have no external IPv6 Internet access." + type = list(object({ + network_tier = string + })) + default = [] +} + +########################### +# Guest Accelerator (GPU) +########################### +variable "gpu" { + description = "GPU information. Type and count of GPU to attach to the instance template. See https://cloud.google.com/compute/docs/gpus more details" + type = object({ + type = string + count = number + }) + default = null +} + +################## +# alias IP range +################## +variable "alias_ip_range" { + description = <