# GCE Managed Instance Group module

This module allows creating a managed instance group supporting one or more application versions via instance templates. Optionally, a health check and an autoscaler can be created, and the managed instance group can be configured to be stateful.

This module can be coupled with the [`compute-vm`](../compute-vm) module which can manage instance templates, and the [`net-lb-int`](../net-lb-int) module to assign the MIG to a backend wired to an Internal Load Balancer. The first use case is shown in the examples below.

Stateful disks can be created directly, as shown in the last example below.

<!-- BEGIN TOC -->
- [Examples](#examples)
  - [Simple Example](#simple-example)
  - [Multiple Versions](#multiple-versions)
  - [Health Check and Autohealing Policies](#health-check-and-autohealing-policies)
  - [Autoscaling](#autoscaling)
  - [Update Policy](#update-policy)
  - [Stateful MIGs - MIG Config](#stateful-migs-mig-config)
  - [Stateful MIGs - Instance Config](#stateful-migs-instance-config)
  - [Flexible MIGs](#flexible-migs)
- [Variables](#variables)
- [Outputs](#outputs)
- [Fixtures](#fixtures)
<!-- END TOC -->

## Examples

### Simple Example

This example shows how to manage a simple MIG that leverages the `compute-vm` module to manage the underlying instance template. The following sub-examples will only show how to enable specific features of this module, and won't replicate the combined setup.

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
    nat        = false
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  target_size       = 2
  instance_template = module.nginx-template.template.self_link
}
# tftest modules=2 resources=2 inventory=simple.yaml e2e
```

### Multiple Versions

If multiple versions are desired, use more `compute-vm` instances for the additional templates used in each version (not shown here), and reference them like this:

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
    nat        = false
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  target_size       = 3
  instance_template = module.nginx-template.template.self_link
  versions = {
    canary = {
      instance_template = module.nginx-template.template.self_link
      target_size = {
        fixed = 1
      }
    }
  }
}
# tftest modules=2 resources=2 inventory=multiple.yaml e2e
```

### Health Check and Autohealing Policies

Autohealing policies can use an externally defined health check, or have this module auto-create one:

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link,
    subnetwork = var.subnet.self_link,
    nat        = false,
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  target_size       = 3
  instance_template = module.nginx-template.template.self_link
  auto_healing_policies = {
    initial_delay_sec = 30
  }
  health_check_config = {
    enable_logging = true
    http = {
      port = 80
    }
  }
}
# tftest modules=2 resources=3 inventory=health-check.yaml e2e
```

### Autoscaling

The module can create and manage an autoscaler associated with the MIG. When using autoscaling do not set the `target_size` variable or set it to `null`. Here we show a CPU utilization autoscaler, the other available modes are load balancing utilization and custom metric, like the underlying autoscaler resource.

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
    nat        = false
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  target_size       = 3
  instance_template = module.nginx-template.template.self_link
  autoscaler_config = {
    max_replicas    = 3
    min_replicas    = 1
    cooldown_period = 30
    scaling_signals = {
      cpu_utilization = {
        target = 0.65
      }
    }
  }
}
# tftest modules=2 resources=3 inventory=autoscaling.yaml e2e
```

### Update Policy

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
    nat        = false
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  target_size       = 3
  instance_template = module.nginx-template.template.self_link
  update_policy = {
    minimal_action = "REPLACE"
    type           = "PROACTIVE"
    min_ready_sec  = 30
    max_surge = {
      fixed = 1
    }
  }
}
# tftest modules=2 resources=2 inventory=policy.yaml e2e
```

### Stateful MIGs - MIG Config

Stateful MIGs have some limitations documented [here](https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-migs#limitations). Enforcement of these requirements is the responsibility of users of this module.

You can configure a disk defined in the instance template to be stateful  for all instances in the MIG by configuring in the MIG's stateful policy, using the `stateful_disk_mig` variable. Alternatively, you can also configure stateful persistent disks individually per instance of the MIG by setting the `stateful_disk_instance` variable. A discussion on these scenarios can be found in the [docs](https://cloud.google.com/compute/docs/instance-groups/configuring-stateful-disks-in-migs).

An example using only the configuration at the MIG level can be seen below.

Note that when referencing the stateful disk, you use `device_name` and not `disk_name`. Specifying an existing disk in the template (and stateful config) only allows a single instance to be managed by the MIG, typically coupled with an autohealing policy (shown in the examples above).

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source       = "./fabric/modules/compute-vm"
  project_id   = var.project_id
  name         = "nginx-template"
  zone         = "${var.region}-b"
  tags         = ["http-server", "ssh"]
  machine_type = "e2-small"
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  attached_disks = {
    data-1 = {
      source = {
        attach = google_compute_disk.test-disk.name
      }
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test-2"
  target_size       = 1
  instance_template = module.nginx-template.template.self_link
  stateful_disks = {
    data-1 = false
  }
}
# tftest modules=2 resources=3 fixtures=fixtures/attached-disks.tf inventory=mig-config.yaml e2e
```

### Stateful MIGs - Instance Config

Here is an example defining the stateful config at the instance level. As in the example above, specifying an existing disk in the template (and stateful config) only allows a single instance to be managed by the MIG, typically coupled with an autohealing policy (shown in the examples above).

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source       = "./fabric/modules/compute-vm"
  project_id   = var.project_id
  name         = "nginx-template"
  zone         = "${var.region}-b"
  tags         = ["http-server", "ssh"]
  machine_type = "e2-small"
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  attached_disks = {
    data-1 = {
      source = {
        attach = google_compute_disk.test-disk.name
      }
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = "${var.region}-b"
  name              = "mig-test"
  instance_template = module.nginx-template.template.self_link
  stateful_config = {
    instance-1 = {
      minimal_action                 = "NONE",
      most_disruptive_allowed_action = "REPLACE"
      preserved_state = {
        disks = {
          data-1 = {
            source = google_compute_disk.test-disk.id
          }
        }
        metadata = {
          foo = "bar"
        }
      }
    }
  }
}
# tftest modules=2 resources=4 fixtures=fixtures/attached-disks.tf  inventory=stateful.yaml e2e
```

### Flexible MIGs

This module can create regional MIGs with a variety of instance types ranked by preference.

```hcl
module "cos-nginx" {
  source = "./fabric/modules/cloud-config-container/nginx"
}

module "nginx-template" {
  source     = "./fabric/modules/compute-vm"
  project_id = var.project_id
  name       = "nginx-template"
  zone       = "${var.region}-b"
  tags       = ["http-server", "ssh"]
  network_interfaces = [{
    network    = var.vpc.self_link
    subnetwork = var.subnet.self_link
    nat        = false
    addresses  = null
  }]
  boot_disk = {
    source = {
      image = "projects/cos-cloud/global/images/family/cos-stable"
    }
  }
  create_template = {}
  metadata = {
    user-data = module.cos-nginx.cloud_config
  }
}

module "nginx-mig" {
  source            = "./fabric/modules/compute-mig"
  project_id        = var.project_id
  location          = var.region
  name              = "mig-test"
  target_size       = 3
  instance_template = module.nginx-template.template.self_link
  distribution_policy = {
    target_shape = "BALANCED" # "ANY_SINGLE_ZONE"
  }
  update_policy = {
    minimal_action = "REFRESH"
    max_unavailable = {
      fixed = 3
    }
    type                         = "OPPORTUNISTIC"
    regional_redistribution_type = "NONE"
  }
  instance_flexibility_policy_selections = {
    preferred-1 = {
      rank = 1
      machine_types = [
        "n2-standard-8",
        "n2-standard-16"
      ]
    }
    preferred-2 = {
      rank = 2
      machine_types = [
        "c4-highcpu-8",
        "c4-highcpu-16"
      ]
    }
  }
}
# tftest modules=2 resources=2  inventory=flexible.yaml e2e
```
<!-- BEGIN TFDOC -->
## Variables

| name | description | type | required | default |
|---|---|:---:|:---:|:---:|
| [instance_template](variables.tf#L212) | Instance template for the default version. | <code>string</code> | ✓ |  |
| [location](variables.tf#L217) | Compute zone or region. | <code>string</code> | ✓ |  |
| [name](variables.tf#L222) | Managed group name. | <code>string</code> | ✓ |  |
| [project_id](variables.tf#L233) | Project id. | <code>string</code> | ✓ |  |
| [all_instances_config](variables.tf#L17) | Metadata and labels set to all instances in the group. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [auto_healing_policies](variables.tf#L26) | Auto-healing policies for this group. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [autoscaler_config](variables.tf#L35) | Optional autoscaler configuration. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [default_version_name](variables.tf#L83) | Name used for the default version. | <code>string</code> |  | <code>&#34;default&#34;</code> |
| [description](variables.tf#L89) | Optional description used for all resources managed by this module. | <code>string</code> |  | <code>&#34;Terraform managed.&#34;</code> |
| [distribution_policy](variables.tf#L95) | Distribution policy for regional MIG. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [health_check_config](variables.tf#L104) | Optional auto-created health check configuration, use the output self-link to set it in the auto healing policy. Refer to examples for usage. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [instance_flexibility_policy_selections](variables.tf#L177) | Instance flexibility policy selections. Only applicable to regional instances. | <code>map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> |  | <code>&#123;&#125;</code> |
| [instance_lifecycle_policy](variables.tf#L191) | The instance lifecycle policy for the MIG. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [named_ports](variables.tf#L227) | Named ports. | <code>map&#40;number&#41;</code> |  | <code>null</code> |
| [stateful_config](variables.tf#L238) | Stateful configuration for individual instances. | <code>map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> |  | <code>&#123;&#125;</code> |
| [stateful_disks](variables.tf#L257) | Stateful disk configuration applied at the MIG level to all instances, in device name => on permanent instance delete rule as boolean. | <code>map&#40;bool&#41;</code> |  | <code>&#123;&#125;</code> |
| [target_pools](variables.tf#L264) | Optional list of URLs for target pools to which new instances in the group are added. | <code>list&#40;string&#41;</code> |  | <code>&#91;&#93;</code> |
| [target_size](variables.tf#L270) | Group target size, leave null when using an autoscaler. | <code>number</code> |  | <code>null</code> |
| [update_policy](variables.tf#L276) | Update policy. Minimal action and type are required. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |
| [versions](variables.tf#L297) | Additional application versions, target_size is optional. | <code>map&#40;object&#40;&#123;&#8230;&#125;&#41;&#41;</code> |  | <code>&#123;&#125;</code> |
| [wait_for_instances](variables.tf#L310) | Wait for all instances to be created/updated before returning. | <code>object&#40;&#123;&#8230;&#125;&#41;</code> |  | <code>null</code> |

## Outputs

| name | description | sensitive |
|---|---|:---:|
| [autoscaler](outputs.tf#L17) | Auto-created autoscaler resource. |  |
| [group_manager](outputs.tf#L26) | Instance group resource. |  |
| [health_check](outputs.tf#L35) | Auto-created health-check resource. |  |
| [id](outputs.tf#L44) | Fully qualified group manager id. |  |

## Fixtures

- [attached-disks.tf](../../tests/fixtures/attached-disks.tf)
<!-- END TFDOC -->
