GCP Terraform refactor (#46)
This is an updated version of the previous [GCP terraform](https://github.com/fleetdm/fleet/tree/main/infrastructure/dogfood/terraform/gcp) with more options and updated usage of modules and Cloud Run (v1 -> v2) which means we were able to ditch the manged VPC connectors (extra cost) and go with the VPC direct access. I think now that I have cloud run jobs figured out we'll be able to add a proper migration addon or module.
This commit is contained in:
+223
@@ -0,0 +1,223 @@
|
||||
# Terraform Google Cloud Fleet Deployment
|
||||
|
||||
This Terraform project automates the deployment of Fleet Device Management (Fleet) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including:
|
||||
|
||||
* A new Google Cloud Project (via `terraform-google-modules/project-factory`)
|
||||
* VPC Network and Subnets
|
||||
* Cloud NAT for outbound internet access
|
||||
* Cloud SQL for MySQL (database for Fleet)
|
||||
* Memorystore for Redis (caching for Fleet)
|
||||
* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured)
|
||||
* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key)
|
||||
* Cloud Run v2 Service (for the main Fleet application)
|
||||
* Cloud Run v2 Job (for database migrations)
|
||||
* External HTTP(S) Load Balancer with managed SSL certificate
|
||||
* Cloud DNS for managing the Fleet endpoint
|
||||
* Appropriate IAM service accounts and permissions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html).
|
||||
2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install).
|
||||
3. **GCP Account & Permissions:**
|
||||
* A Google Cloud Organization ID.
|
||||
* A Billing Account ID.
|
||||
* Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has:
|
||||
* `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module).
|
||||
* `Billing Account User` role on the Billing Account.
|
||||
* Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project.
|
||||
* Alternatively, use a Service Account with appropriate permissions.
|
||||
4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain).
|
||||
5. **(Optional) Fleet License Key:** If you have a premium Fleet license, you can provide it.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **Clone the repository (if applicable) or ensure you have all the files.**
|
||||
2. **Create a `terraform.tfvars` file:**
|
||||
Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values.
|
||||
|
||||
**Example `terraform.tfvars.example`:**
|
||||
```hcl
|
||||
# Required: GCP Organization and Billing
|
||||
org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012"
|
||||
billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01"
|
||||
|
||||
# Required: DNS Configuration
|
||||
# The public DNS zone name (e.g., "example.com." - note the trailing dot)
|
||||
# This project will create/manage this zone in Cloud DNS.
|
||||
dns_zone_name = "your-domain.com."
|
||||
# The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.")
|
||||
dns_record_name = "fleet.your-domain.com."
|
||||
|
||||
# Optional: Project Configuration
|
||||
project_name = "my-fleet-project" # Name for the new GCP project
|
||||
random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique)
|
||||
|
||||
# Optional: Labels
|
||||
labels = {
|
||||
application = "fleet"
|
||||
environment = "production"
|
||||
owner = "devops-team"
|
||||
}
|
||||
|
||||
# --- byo-project module specific variables (passed through from root) ---
|
||||
# You can override defaults from byo-project/variables.tf here if needed,
|
||||
# or set values for variables that don't have defaults.
|
||||
|
||||
# Example: Fleet Configuration including migration execution
|
||||
fleet_config = {
|
||||
# image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version
|
||||
# license_key = "YOUR_FLEET_LICENSE_KEY"
|
||||
# installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique
|
||||
# fleet_cpu = "1000m"
|
||||
# fleet_memory = "4096Mi"
|
||||
# debug_logging = false
|
||||
# min_instance_count = 1
|
||||
# max_instance_count = 3
|
||||
# exec_migration = true # Set to true to run migrations on `terraform apply`
|
||||
# (e.g., when changing image_tag)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
|
||||
**Key Variables to Set:**
|
||||
|
||||
* `org_id`: Your GCP Organization ID.
|
||||
* `billing_account_id`: Your GCP Billing Account ID.
|
||||
* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.**
|
||||
* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.**
|
||||
* `project_name`: A descriptive name for the project to be created.
|
||||
* `fleet_config.image_tag`: The Docker image tag for the Fleet version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`).
|
||||
* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade.
|
||||
* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your Fleet license key if you have one.
|
||||
|
||||
Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults.
|
||||
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
1. **Authenticate with GCP:**
|
||||
Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions.
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
|
||||
2. **Initialize Terraform:**
|
||||
Navigate to the root directory of the project (where `main.tf` is located) and run:
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
3. **Plan the Deployment:**
|
||||
Review the changes Terraform will make:
|
||||
```bash
|
||||
terraform plan -out=tfplan
|
||||
```
|
||||
|
||||
4. **Apply the Configuration:**
|
||||
Provision the resources:
|
||||
```bash
|
||||
terraform apply tfplan
|
||||
```
|
||||
This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning.
|
||||
|
||||
5. **Update DNS (if not using Cloud DNS for the parent zone):**
|
||||
If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone.
|
||||
* Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`):
|
||||
```bash
|
||||
# First, get the project ID and zone name if you don't know them
|
||||
PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1)
|
||||
DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable
|
||||
|
||||
gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \
|
||||
--project=${PROJECT_ID} \
|
||||
--format="value(nameServers)"
|
||||
```
|
||||
* Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider.
|
||||
|
||||
If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external.
|
||||
|
||||
## Accessing Fleet
|
||||
|
||||
Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes):
|
||||
|
||||
* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`).
|
||||
* You should be greeted by the Fleet setup page or login screen.
|
||||
|
||||
## Key Resources Created (within the `byo-project` module)
|
||||
|
||||
* **VPC Network (`fleet-network`):** Custom network for Fleet resources.
|
||||
* **Subnet (`fleet-subnet`):** Subnetwork within the VPC.
|
||||
* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources.
|
||||
* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database.
|
||||
* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance.
|
||||
* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage.
|
||||
* **Secret Manager Secrets:**
|
||||
* `fleet-db-password-xxxx`: Stores the generated MySQL user password.
|
||||
* `fleet-private-key-xxxx`: Stores a generated private key for Fleet server.
|
||||
* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions.
|
||||
* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application.
|
||||
* **Cloud Run Job (`fleet-migration-job`):** For database migrations.
|
||||
* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service.
|
||||
* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`.
|
||||
* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS.
|
||||
* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP.
|
||||
|
||||
(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.)
|
||||
|
||||
## Cleaning Up
|
||||
|
||||
To destroy all resources created by this Terraform project:
|
||||
1. Navigate to the root directory of the project.
|
||||
2. Run the destroy command:
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
3. Confirm by typing `yes` when prompted.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice.
|
||||
* **Fleet Configuration:** This Terraform setup provisions the infrastructure. Further Fleet application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment.
|
||||
* **Security:**
|
||||
* Review IAM permissions granted.
|
||||
* The Load Balancer is configured for HTTPS and redirects HTTP.
|
||||
* Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access.
|
||||
* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load.
|
||||
|
||||
|
||||
### Networking Diagram
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph External
|
||||
Internet[(Internet)]
|
||||
Users[Web Console / fleetd agent]
|
||||
GitHub[(GitHub - Vulnerability Resources)]
|
||||
end
|
||||
|
||||
subgraph "Google Cloud Platform (GCP)"
|
||||
subgraph VPC [VPC]
|
||||
direction LR
|
||||
subgraph PublicFacing [Public Zone]
|
||||
LB[Global Load Balancer]
|
||||
end
|
||||
subgraph PrivateZone [Private Zone]
|
||||
CloudRun[Cloud Run: Fleet App]
|
||||
CloudSQL[Cloud SQL for MySQL]
|
||||
Redis[Memorystore for Redis]
|
||||
NAT[Cloud NAT]
|
||||
end
|
||||
|
||||
CloudRun --> Redis
|
||||
CloudRun --> CloudSQL
|
||||
CloudRun --> NAT
|
||||
end
|
||||
end
|
||||
|
||||
Users -- "fleet.yourdomain.com" --> Internet
|
||||
Internet -- "fleet.yourdomain.com" --> LB
|
||||
LB --> CloudRun
|
||||
NAT --> GitHub
|
||||
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
header-from: .header.md
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
# Terraform Google Cloud Fleet Deployment
|
||||
|
||||
This Terraform project automates the deployment of Fleet Device Management (Fleet) on the Google Cloud Platform (GCP). It provisions a new GCP project (or uses an existing one if configured) and deploys all necessary infrastructure components, including:
|
||||
|
||||
* A new Google Cloud Project (via `terraform-google-modules/project-factory`)
|
||||
* VPC Network and Subnets
|
||||
* Cloud NAT for outbound internet access
|
||||
* Cloud SQL for MySQL (database for Fleet)
|
||||
* Memorystore for Redis (caching for Fleet)
|
||||
* Google Cloud Storage (GCS) bucket (for Fleet software installers, S3-compatible access configured)
|
||||
* Google Secret Manager (for storing sensitive data like database passwords and Fleet server private key)
|
||||
* Cloud Run v2 Service (for the main Fleet application)
|
||||
* Cloud Run v2 Job (for database migrations)
|
||||
* External HTTP(S) Load Balancer with managed SSL certificate
|
||||
* Cloud DNS for managing the Fleet endpoint
|
||||
* Appropriate IAM service accounts and permissions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **Terraform:** Version `~> 1.11` (as specified in `main.tf`). Install from [terraform.io](https://www.terraform.io/downloads.html).
|
||||
2. **Google Cloud SDK (`gcloud`):** Installed and configured. Install from [cloud.google.com/sdk](https://cloud.google.com/sdk/docs/install).
|
||||
3. **GCP Account & Permissions:**
|
||||
* A Google Cloud Organization ID.
|
||||
* A Billing Account ID.
|
||||
* Credentials configured for Terraform to interact with GCP. This typically means running `gcloud auth application-default login` with a user account that has:
|
||||
* `Organization Administrator` or `Folder Creator` & `Project Creator` roles at the Organization/Folder level (for the `project-factory` module).
|
||||
* `Billing Account User` role on the Billing Account.
|
||||
* Once the project is created, subsequent applies for resources within the project will generally require `Owner` or `Editor` on the provisioned project.
|
||||
* Alternatively, use a Service Account with appropriate permissions.
|
||||
4. **Registered Domain Name:** You will need a domain name that you can manage DNS records for. This project will create a Cloud DNS Managed Zone for this domain (or a subdomain).
|
||||
5. **(Optional) Fleet License Key:** If you have a premium Fleet license, you can provide it.
|
||||
|
||||
## Configuration
|
||||
|
||||
1. **Clone the repository (if applicable) or ensure you have all the files.**
|
||||
2. **Create a `terraform.tfvars` file:**
|
||||
Copy `terraform.tfvars.example` (you'll need to create this, see example below) to `terraform.tfvars` and populate it with your specific values.
|
||||
|
||||
**Example `terraform.tfvars.example`:**
|
||||
```hcl
|
||||
# Required: GCP Organization and Billing
|
||||
org_id = "YOUR_ORGANIZATION_ID" # e.g., "123456789012"
|
||||
billing_account_id = "YOUR_BILLING_ACCOUNT_ID" # e.g., "012345-6789AB-CDEF01"
|
||||
|
||||
# Required: DNS Configuration
|
||||
# The public DNS zone name (e.g., "example.com." - note the trailing dot)
|
||||
# This project will create/manage this zone in Cloud DNS.
|
||||
dns_zone_name = "your-domain.com."
|
||||
# The fully qualified domain name for your Fleet instance (e.g., "fleet.your-domain.com.")
|
||||
dns_record_name = "fleet.your-domain.com."
|
||||
|
||||
# Optional: Project Configuration
|
||||
project_name = "my-fleet-project" # Name for the new GCP project
|
||||
random_project_id = true # Set to false if you want to use project_name as project_id (must be globally unique)
|
||||
|
||||
# Optional: Labels
|
||||
labels = {
|
||||
application = "fleet"
|
||||
environment = "production"
|
||||
owner = "devops-team"
|
||||
}
|
||||
|
||||
# --- byo-project module specific variables (passed through from root) ---
|
||||
# You can override defaults from byo-project/variables.tf here if needed,
|
||||
# or set values for variables that don't have defaults.
|
||||
|
||||
# Example: Fleet Configuration including migration execution
|
||||
fleet_config = {
|
||||
# image_tag = "fleetdm/fleet:v4.x.y" # Specify your desired Fleet version
|
||||
# license_key = "YOUR_FLEET_LICENSE_KEY"
|
||||
# installers_bucket_name = "my-unique-fleet-installers" # Must be globally unique
|
||||
# fleet_cpu = "1000m"
|
||||
# fleet_memory = "4096Mi"
|
||||
# debug_logging = false
|
||||
# min_instance_count = 1
|
||||
# max_instance_count = 3
|
||||
# exec_migration = true # Set to true to run migrations on `terraform apply`
|
||||
# (e.g., when changing image_tag)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
**Key Variables to Set:**
|
||||
* `org_id`: Your GCP Organization ID.
|
||||
* `billing_account_id`: Your GCP Billing Account ID.
|
||||
* `dns_zone_name`: The DNS zone that will be created/managed in Cloud DNS (e.g., `mydomain.com.`). **Must end with a dot.**
|
||||
* `dns_record_name`: The specific DNS record for Fleet (e.g., `fleet.mydomain.com.`). **Must end with a dot.**
|
||||
* `project_name`: A descriptive name for the project to be created.
|
||||
* `fleet_config.image_tag`: The Docker image tag for the Fleet version you want to deploy (e.g., `fleetdm/fleet:v4.67.3`).
|
||||
* `fleet_config.exec_migration`: Set to `true` when you are upgrading the `fleet_config.image_tag` to automatically run database migrations. Set to `false` if you want to manage migrations manually or if it's not an image upgrade.
|
||||
* `fleet_config.license_key` (Optional, inside the `fleet_config` object): Your Fleet license key if you have one.
|
||||
|
||||
Review `variables.tf` and `byo-project/variables.tf` for all available options and their defaults.
|
||||
|
||||
## Deployment Steps
|
||||
|
||||
1. **Authenticate with GCP:**
|
||||
Ensure your `gcloud` CLI is authenticated with the account having the necessary permissions.
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
|
||||
2. **Initialize Terraform:**
|
||||
Navigate to the root directory of the project (where `main.tf` is located) and run:
|
||||
```bash
|
||||
terraform init
|
||||
```
|
||||
|
||||
3. **Plan the Deployment:**
|
||||
Review the changes Terraform will make:
|
||||
```bash
|
||||
terraform plan -out=tfplan
|
||||
```
|
||||
|
||||
4. **Apply the Configuration:**
|
||||
Provision the resources:
|
||||
```bash
|
||||
terraform apply tfplan
|
||||
```
|
||||
This process can take a significant amount of time (10–20 minutes or more), especially for the initial project creation, API enablement, and Cloud SQL instance provisioning.
|
||||
|
||||
5. **Update DNS (if not using Cloud DNS for the parent zone):**
|
||||
If `dns_zone_name` (e.g., `fleet.your-domain.com.`) is a new zone created by this Terraform, and your parent domain (e.g., `your-domain.com.`) is managed elsewhere (e.g., GoDaddy, Cloudflare), you need to delegate this new zone.
|
||||
* Get the Name Servers (NS) records for the newly created `google_dns_managed_zone` (`${var.prefix}-zone`):
|
||||
```bash
|
||||
# First, get the project ID and zone name if you don't know them
|
||||
PROJECT_ID=$(terraform output -raw project_id 2>/dev/null || gcloud projects list --filter="name~^my-fleet-project" --format="value(projectId)" | head -n 1)
|
||||
DNS_ZONE_INTERNAL_NAME="fleet-zone" # Or as configured by 'prefix' variable
|
||||
|
||||
gcloud dns managed-zones describe ${DNS_ZONE_INTERNAL_NAME} \
|
||||
--project=${PROJECT_ID} \
|
||||
--format="value(nameServers)"
|
||||
```
|
||||
* Add these NS records to your parent domain's DNS settings at your domain registrar or DNS provider.
|
||||
|
||||
If `dns_zone_name` (e.g., `your-domain.com.`) was an *existing* zone you own and manage via Cloud DNS in this project, Terraform would add records to it directly. The current setup *creates* the zone, so delegation is key if the parent is external.
|
||||
|
||||
## Accessing Fleet
|
||||
|
||||
Once the deployment is complete, database migrations have run successfully, and DNS has propagated (which can take a few minutes):
|
||||
|
||||
* Open your web browser and navigate to the `dns_record_name` you configured (e.g., `https://fleet.your-domain.com`).
|
||||
* You should be greeted by the Fleet setup page or login screen.
|
||||
|
||||
## Key Resources Created (within the `byo-project` module)
|
||||
|
||||
* **VPC Network (`fleet-network`):** Custom network for Fleet resources.
|
||||
* **Subnet (`fleet-subnet`):** Subnetwork within the VPC.
|
||||
* **Cloud Router & NAT (`fleet-cloud-router`, `fleet-vpc-nat`):** For outbound internet access from Cloud Run and other private resources.
|
||||
* **Cloud SQL for MySQL (`fleet-mysql-xxxx`):** Managed MySQL database.
|
||||
* **Memorystore for Redis (`fleet-cache`):** Managed Redis instance.
|
||||
* **GCS Bucket (`fleet-installers-xxxx`):** For S3-compatible storage.
|
||||
* **Secret Manager Secrets:**
|
||||
* `fleet-db-password-xxxx`: Stores the generated MySQL user password.
|
||||
* `fleet-private-key-xxxx`: Stores a generated private key for Fleet server.
|
||||
* **Service Account (`fleet-run-sa`):** Used by Cloud Run services/jobs with necessary permissions.
|
||||
* **Cloud Run Service (`fleet-service`):** Hosts the main Fleet application.
|
||||
* **Cloud Run Job (`fleet-migration-job`):** For database migrations.
|
||||
* **Network Endpoint Group (`fleet-neg`):** Connects the Load Balancer to the Cloud Run service.
|
||||
* **Cloud DNS Managed Zone (`fleet-zone`):** Manages DNS records for `dns_zone_name`.
|
||||
* **External HTTP(S) Load Balancer (`fleet-lb`):** Provides public access to Fleet via HTTPS.
|
||||
* **DNS A Record:** Points `dns_record_name` to the Load Balancer's IP.
|
||||
|
||||
(Note: `xxxx` indicates a random suffix added by some modules/resources for uniqueness.)
|
||||
|
||||
## Cleaning Up
|
||||
|
||||
To destroy all resources created by this Terraform project:
|
||||
1. Navigate to the root directory of the project.
|
||||
2. Run the destroy command:
|
||||
```bash
|
||||
terraform destroy
|
||||
```
|
||||
3. Confirm by typing `yes` when prompted.
|
||||
|
||||
## Important Considerations
|
||||
|
||||
* **Permissions:** The user or service account running Terraform needs extensive permissions, especially for project creation. For ongoing management within the project, a role like `Project Editor` or more granular roles might suffice.
|
||||
* **Fleet Configuration:** This Terraform setup provisions the infrastructure. Further Fleet application configuration (e.g., SSO, SMTP, agent options) will need to be done through the Fleet UI or API after deployment.
|
||||
* **Security:**
|
||||
* Review IAM permissions granted.
|
||||
* The Load Balancer is configured for HTTPS and redirects HTTP.
|
||||
* Cloud SQL and Memorystore are not publicly accessible and are connected via Private Service Access.
|
||||
* **Scalability:** Cloud Run scaling parameters (`min_instance_count`, `max_instance_count`) and database/cache tier can be adjusted in `variables.tf` (via `fleet_config`, `database_config`, `cache_config`) to suit your load.
|
||||
|
||||
### Networking Diagram
|
||||
```mermaid
|
||||
graph TD
|
||||
subgraph External
|
||||
Internet[(Internet)]
|
||||
Users[Web Console / fleetd agent]
|
||||
GitHub[(GitHub - Vulnerability Resources)]
|
||||
end
|
||||
|
||||
subgraph "Google Cloud Platform (GCP)"
|
||||
subgraph VPC [VPC]
|
||||
direction LR
|
||||
subgraph PublicFacing [Public Zone]
|
||||
LB[Global Load Balancer]
|
||||
end
|
||||
subgraph PrivateZone [Private Zone]
|
||||
CloudRun[Cloud Run: Fleet App]
|
||||
CloudSQL[Cloud SQL for MySQL]
|
||||
Redis[Memorystore for Redis]
|
||||
NAT[Cloud NAT]
|
||||
end
|
||||
|
||||
CloudRun --> Redis
|
||||
CloudRun --> CloudSQL
|
||||
CloudRun --> NAT
|
||||
end
|
||||
end
|
||||
|
||||
Users -- "fleet.yourdomain.com" --> Internet
|
||||
Internet -- "fleet.yourdomain.com" --> LB
|
||||
LB --> CloudRun
|
||||
NAT --> GitHub
|
||||
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
| Name | Version |
|
||||
|------|---------|
|
||||
| <a name="requirement_terraform"></a> [terraform](#requirement\_terraform) | ~> 1.11 |
|
||||
| <a name="requirement_google"></a> [google](#requirement\_google) | 6.35.0 |
|
||||
|
||||
## Providers
|
||||
|
||||
No providers.
|
||||
|
||||
## Modules
|
||||
|
||||
| Name | Source | Version |
|
||||
|------|--------|---------|
|
||||
| <a name="module_fleet"></a> [fleet](#module\_fleet) | ./byo-project | n/a |
|
||||
| <a name="module_project_factory"></a> [project\_factory](#module\_project\_factory) | terraform-google-modules/project-factory/google | ~> 18.0.0 |
|
||||
|
||||
## Resources
|
||||
|
||||
No resources.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Name | Description | Type | Default | Required |
|
||||
|------|-------------|------|---------|:--------:|
|
||||
| <a name="input_billing_account_id"></a> [billing\_account\_id](#input\_billing\_account\_id) | billing account id | `any` | n/a | yes |
|
||||
| <a name="input_cache_config"></a> [cache\_config](#input\_cache\_config) | Configuration for the Memorystore (Redis) instance. | <pre>object({<br> name = string<br> tier = string<br> engine_version = string<br> connect_mode = string<br> memory_size = number<br> })</pre> | <pre>{<br> "connect_mode": "PRIVATE_SERVICE_ACCESS",<br> "engine_version": null,<br> "memory_size": 1,<br> "name": "fleet-cache",<br> "tier": "STANDARD_HA"<br>}</pre> | no |
|
||||
| <a name="input_database_config"></a> [database\_config](#input\_database\_config) | Configuration for the Cloud SQL (MySQL) instance. | <pre>object({<br> name = string<br> database_name = string<br> database_user = string<br> collation = string<br> charset = string<br> deletion_protection = bool<br> database_version = string<br> tier = string<br> })</pre> | <pre>{<br> "charset": "utf8mb4",<br> "collation": "utf8mb4_unicode_ci",<br> "database_name": "fleet",<br> "database_user": "fleet",<br> "database_version": "MYSQL_8_0",<br> "deletion_protection": false,<br> "name": "fleet-mysql",<br> "tier": "db-n1-standard-1"<br>}</pre> | no |
|
||||
| <a name="input_dns_record_name"></a> [dns\_record\_name](#input\_dns\_record\_name) | The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.') | `string` | n/a | yes |
|
||||
| <a name="input_dns_zone_name"></a> [dns\_zone\_name](#input\_dns\_zone\_name) | The DNS name of the managed zone (e.g., 'my-fleet-infra.com.') | `string` | n/a | yes |
|
||||
| <a name="input_fleet_config"></a> [fleet\_config](#input\_fleet\_config) | Configuration for the Fleet application deployment. | <pre>object({<br> installers_bucket_name = string<br> image_tag = string<br> fleet_cpu = string<br> fleet_memory = string<br> debug_logging = bool<br> license_key = optional(string)<br> min_instance_count = number<br> max_instance_count = number<br> exec_migration = bool<br> extra_env_vars = optional(map(string))<br> extra_secret_env_vars = optional(map(object({<br> secret = string<br> version = string<br> })))<br> })</pre> | <pre>{<br> "debug_logging": false,<br> "exec_migration": true,<br> "extra_env_vars": {},<br> "extra_secret_env_vars": {},<br> "fleet_cpu": "1000m",<br> "fleet_memory": "4096Mi",<br> "image_tag": "fleetdm/fleet:v4.67.3",<br> "installers_bucket_name": "",<br> "max_instance_count": 5,<br> "min_instance_count": 1<br>}</pre> | no |
|
||||
| <a name="input_fleet_image"></a> [fleet\_image](#input\_fleet\_image) | n/a | `string` | `"v4.67.3"` | no |
|
||||
| <a name="input_labels"></a> [labels](#input\_labels) | resource labels | `map(string)` | <pre>{<br> "application": "fleet"<br>}</pre> | no |
|
||||
| <a name="input_location"></a> [location](#input\_location) | The general location for resources, e.g., 'us' for GCS buckets. | `string` | `"us"` | no |
|
||||
| <a name="input_org_id"></a> [org\_id](#input\_org\_id) | organization id | `any` | n/a | yes |
|
||||
| <a name="input_prefix"></a> [prefix](#input\_prefix) | A prefix used for naming resources within the byo-project module. | `string` | `"fleet"` | no |
|
||||
| <a name="input_project_name"></a> [project\_name](#input\_project\_name) | n/a | `string` | `"fleet"` | no |
|
||||
| <a name="input_random_project_id"></a> [random\_project\_id](#input\_random\_project\_id) | n/a | `bool` | `true` | no |
|
||||
| <a name="input_region"></a> [region](#input\_region) | The GCP region for regional resources. | `string` | `"us-central1"` | no |
|
||||
| <a name="input_vpc_config"></a> [vpc\_config](#input\_vpc\_config) | Configuration for the VPC network and subnets. | <pre>object({<br> network_name = string<br> subnets = list(object({<br> subnet_name = string<br> subnet_ip = string<br> subnet_region = string<br> subnet_private_access = bool<br> }))<br> })</pre> | <pre>{<br> "network_name": "fleet-network",<br> "subnets": [<br> {<br> "subnet_ip": "10.10.10.0/24",<br> "subnet_name": "fleet-subnet",<br> "subnet_private_access": true,<br> "subnet_region": "us-central1"<br> }<br> ]<br>}</pre> | no |
|
||||
|
||||
## Outputs
|
||||
|
||||
| Name | Description |
|
||||
|------|-------------|
|
||||
| <a name="output_cloud_router_name"></a> [cloud\_router\_name](#output\_cloud\_router\_name) | The name of the Cloud Router created for NAT. |
|
||||
| <a name="output_cloud_run_service_location"></a> [cloud\_run\_service\_location](#output\_cloud\_run\_service\_location) | The location of the deployed Fleet Cloud Run service. |
|
||||
| <a name="output_cloud_run_service_name"></a> [cloud\_run\_service\_name](#output\_cloud\_run\_service\_name) | The name of the deployed Fleet Cloud Run service. |
|
||||
| <a name="output_dns_managed_zone_name"></a> [dns\_managed\_zone\_name](#output\_dns\_managed\_zone\_name) | The name of the Cloud DNS managed zone created for Fleet. |
|
||||
| <a name="output_dns_managed_zone_name_servers"></a> [dns\_managed\_zone\_name\_servers](#output\_dns\_managed\_zone\_name\_servers) | The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these. |
|
||||
| <a name="output_fleet_application_url"></a> [fleet\_application\_url](#output\_fleet\_application\_url) | The primary URL to access the Fleet application (via the Load Balancer). |
|
||||
| <a name="output_fleet_service_account_email"></a> [fleet\_service\_account\_email](#output\_fleet\_service\_account\_email) | The email address of the service account used by the Fleet Cloud Run service. |
|
||||
| <a name="output_load_balancer_ip_address"></a> [load\_balancer\_ip\_address](#output\_load\_balancer\_ip\_address) | The external IP address of the HTTP(S) Load Balancer. |
|
||||
| <a name="output_mysql_instance_connection_name"></a> [mysql\_instance\_connection\_name](#output\_mysql\_instance\_connection\_name) | The connection name for the Cloud SQL instance (used by Cloud SQL Proxy). |
|
||||
| <a name="output_mysql_instance_name"></a> [mysql\_instance\_name](#output\_mysql\_instance\_name) | The name of the Cloud SQL for MySQL instance. |
|
||||
| <a name="output_redis_host"></a> [redis\_host](#output\_redis\_host) | The host IP address of the Memorystore for Redis instance. |
|
||||
| <a name="output_redis_instance_name"></a> [redis\_instance\_name](#output\_redis\_instance\_name) | The name of the Memorystore for Redis instance. |
|
||||
| <a name="output_redis_port"></a> [redis\_port](#output\_redis\_port) | The port number of the Memorystore for Redis instance. |
|
||||
| <a name="output_software_installers_bucket_name"></a> [software\_installers\_bucket\_name](#output\_software\_installers\_bucket\_name) | The name of the GCS bucket for Fleet software installers. |
|
||||
| <a name="output_software_installers_bucket_url"></a> [software\_installers\_bucket\_url](#output\_software\_installers\_bucket\_url) | The gsutil URL of the GCS bucket for Fleet software installers. |
|
||||
| <a name="output_vpc_network_name"></a> [vpc\_network\_name](#output\_vpc\_network\_name) | The name of the VPC network created. |
|
||||
| <a name="output_vpc_network_self_link"></a> [vpc\_network\_self\_link](#output\_vpc\_network\_self\_link) | The self-link of the VPC network created. |
|
||||
| <a name="output_vpc_subnets_names"></a> [vpc\_subnets\_names](#output\_vpc\_subnets\_names) | List of subnet names created in the VPC. |
|
||||
@@ -0,0 +1,18 @@
|
||||
module "memstore" {
|
||||
source = "terraform-google-modules/memorystore/google"
|
||||
version = "~> 14.0"
|
||||
|
||||
name = var.cache_config.name
|
||||
redis_version = var.cache_config.engine_version
|
||||
tier = var.cache_config.tier
|
||||
memory_size_gb = var.cache_config.memory_size
|
||||
|
||||
project_id = var.project_id
|
||||
region = var.region
|
||||
enable_apis = true
|
||||
transit_encryption_mode = "DISABLED"
|
||||
authorized_network = module.vpc.network_id
|
||||
connect_mode = var.cache_config.connect_mode
|
||||
|
||||
depends_on = [module.private-service-access.peering_completed]
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
|
||||
locals {
|
||||
# --- Shared Container Configuration ---
|
||||
fleet_image_tag = var.fleet_config.image_tag
|
||||
fleet_resources_limits = {
|
||||
cpu = var.fleet_config.fleet_cpu
|
||||
memory = var.fleet_config.fleet_memory
|
||||
}
|
||||
fleet_secrets_env_vars = merge(var.fleet_config.extra_secret_env_vars, {
|
||||
FLEET_MYSQL_PASSWORD = {
|
||||
secret = google_secret_manager_secret.database_password.secret_id
|
||||
version = "latest"
|
||||
},
|
||||
FLEET_SERVER_PRIVATE_KEY = {
|
||||
secret = google_secret_manager_secret.private_key.secret_id
|
||||
version = "latest"
|
||||
}
|
||||
})
|
||||
fleet_env_vars = merge(var.fleet_config.extra_env_vars, {
|
||||
FLEET_LICENSE_KEY = var.fleet_config.license_key
|
||||
FLEET_SERVER_FORCE_H2C = "true"
|
||||
FLEET_MYSQL_PROTOCOL = "tcp"
|
||||
FLEET_MYSQL_ADDRESS = "${module.mysql.private_ip_address}:3306"
|
||||
FLEET_MYSQL_USERNAME = var.database_config.database_user
|
||||
FLEET_MYSQL_DATABASE = var.database_config.database_name
|
||||
FLEET_REDIS_ADDRESS = "${module.memstore.host}:${module.memstore.port}"
|
||||
FLEET_REDIS_USE_TLS = "false"
|
||||
#FLEET_UPGRADES_ALLOW_MISSING_MIGRATIONS = "1"
|
||||
FLEET_LOGGING_JSON = "true"
|
||||
FLEET_LOGGING_DEBUG = var.fleet_config.debug_logging
|
||||
FLEET_SERVER_TLS = "false"
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_BUCKET = google_storage_bucket.software_installers.id
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_ACCESS_KEY_ID = google_storage_hmac_key.key.access_id
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_SECRET_ACCESS_KEY = google_storage_hmac_key.key.secret
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_ENDPOINT_URL = "https://storage.googleapis.com"
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_FORCE_S3_PATH_STYLE = "true"
|
||||
FLEET_S3_SOFTWARE_INSTALLERS_REGION = var.region
|
||||
})
|
||||
|
||||
fleet_vpc_network_id = module.vpc.network_id
|
||||
# Use the direct construction for the subnet ID key as discussed
|
||||
fleet_vpc_subnet_id = "fleet-subnet"
|
||||
}
|
||||
|
||||
module "fleet-service" {
|
||||
source = "GoogleCloudPlatform/cloud-run/google//modules/v2"
|
||||
version = "0.17.2"
|
||||
|
||||
service_name = "fleet-api"
|
||||
project_id = var.project_id
|
||||
location = var.region
|
||||
create_service_account = false
|
||||
service_account = google_service_account.fleet_run_sa.email
|
||||
enable_prometheus_sidecar = false
|
||||
cloud_run_deletion_protection = false
|
||||
|
||||
vpc_access = {
|
||||
network_interfaces = {
|
||||
network = local.fleet_vpc_network_id
|
||||
subnetwork = local.fleet_vpc_subnet_id
|
||||
}
|
||||
egress = "ALL_TRAFFIC"
|
||||
}
|
||||
ingress = "INGRESS_TRAFFIC_INTERNAL_LOAD_BALANCER"
|
||||
timeout = "300s"
|
||||
service_scaling = {
|
||||
min_instance_count = 0
|
||||
}
|
||||
template_scaling = {
|
||||
min_instance_count = 0
|
||||
max_instance_count = 0
|
||||
}
|
||||
|
||||
containers = [
|
||||
{
|
||||
container_image = local.fleet_image_tag
|
||||
ports = {
|
||||
name = "h2c"
|
||||
container_port = 8080
|
||||
}
|
||||
# container_command = ["/bin/sh"]
|
||||
# container_args = [
|
||||
# "-c",
|
||||
# "fleet prepare --no-prompt=true db; exec fleet serve"
|
||||
# ]
|
||||
|
||||
startup_probe = {
|
||||
initial_delay_seconds = 30
|
||||
timeout_seconds = 2
|
||||
period_seconds = 60
|
||||
failure_threshold = 3
|
||||
|
||||
tcp_socket = {
|
||||
port = 8080
|
||||
}
|
||||
}
|
||||
|
||||
liveness_probe = {
|
||||
initial_delay_seconds = 30
|
||||
timeout_seconds = 2
|
||||
failure_threshold = 3
|
||||
period_seconds = 60
|
||||
http_get = {
|
||||
path = "/healthz"
|
||||
http_headers = []
|
||||
}
|
||||
}
|
||||
|
||||
resources = {
|
||||
limits = local.fleet_resources_limits
|
||||
}
|
||||
|
||||
env_vars = local.fleet_env_vars
|
||||
env_secret_vars = local.fleet_secrets_env_vars
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# --- Cloud Run Job (Migrations) ---
|
||||
resource "google_cloud_run_v2_job" "fleet_migration_job" {
|
||||
|
||||
name = "fleet-migration"
|
||||
location = var.region
|
||||
project = var.project_id
|
||||
|
||||
template {
|
||||
template { # Double template for jobs
|
||||
service_account = google_service_account.fleet_run_sa.email # Defined in iam.tf
|
||||
|
||||
# Define vpc_access block directly
|
||||
vpc_access {
|
||||
network_interfaces {
|
||||
network = local.fleet_vpc_network_id
|
||||
subnetwork = local.fleet_vpc_subnet_id
|
||||
}
|
||||
egress = "ALL_TRAFFIC"
|
||||
}
|
||||
|
||||
timeout = "3600s"
|
||||
|
||||
containers {
|
||||
image = local.fleet_image_tag
|
||||
# Define resources block directly
|
||||
resources {
|
||||
limits = local.fleet_resources_limits
|
||||
}
|
||||
|
||||
dynamic "env" {
|
||||
for_each = local.fleet_env_vars
|
||||
content {
|
||||
name = env.key
|
||||
value = env.value
|
||||
}
|
||||
}
|
||||
dynamic "env" {
|
||||
for_each = local.fleet_secrets_env_vars
|
||||
content {
|
||||
name = env.key
|
||||
value_source {
|
||||
secret_key_ref {
|
||||
secret = env.value.secret
|
||||
version = env.value.version
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
command = ["fleet"]
|
||||
args = ["prepare", "db", "--no-prompt=true"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [
|
||||
google_service_account.fleet_run_sa,
|
||||
google_secret_manager_secret_version.database_password,
|
||||
]
|
||||
}
|
||||
|
||||
data "google_client_config" "default" {}
|
||||
|
||||
resource "terracurl_request" "exec" {
|
||||
count = var.fleet_config.exec_migration ? 1 : 0
|
||||
name = "exec-job"
|
||||
url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}:run"
|
||||
method = "POST"
|
||||
headers = {
|
||||
Authorization = "Bearer ${data.google_client_config.default.access_token}"
|
||||
Content-Type = "application/json",
|
||||
}
|
||||
response_codes = [200]
|
||||
// no-op destroy
|
||||
// we don't use terracurl_request data source as that will result in
|
||||
// repeated job runs on every refresh
|
||||
destroy_url = "https://run.googleapis.com/v2/${google_cloud_run_v2_job.fleet_migration_job.id}"
|
||||
destroy_method = "GET"
|
||||
destroy_response_codes = [200]
|
||||
destroy_headers = {
|
||||
Authorization = "Bearer ${data.google_client_config.default.access_token}"
|
||||
Content-Type = "application/json",
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_compute_region_network_endpoint_group" "neg" {
|
||||
name = "${var.prefix}-neg"
|
||||
region = var.region
|
||||
project = var.project_id
|
||||
network_endpoint_type = "SERVERLESS" # This type works for Cloud Run v2 services
|
||||
cloud_run {
|
||||
service = module.fleet-service.service_name
|
||||
}
|
||||
depends_on = [module.fleet-service]
|
||||
}
|
||||
|
||||
data "google_project" "project" {
|
||||
project_id = var.project_id
|
||||
}
|
||||
|
||||
resource "google_cloud_run_v2_service_iam_member" "allow_lb_invoker" {
|
||||
project = var.project_id
|
||||
location = module.fleet-service.location
|
||||
name = module.fleet-service.service_name
|
||||
role = "roles/run.invoker"
|
||||
member = "allUsers"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
resource "random_id" "suffix" {
|
||||
byte_length = 5
|
||||
}
|
||||
|
||||
|
||||
module "private-service-access" {
|
||||
source = "terraform-google-modules/sql-db/google//modules/private_service_access"
|
||||
version = "~> 25.0"
|
||||
|
||||
project_id = var.project_id
|
||||
vpc_network = module.vpc.network_name
|
||||
deletion_policy = "ABANDON"
|
||||
}
|
||||
|
||||
module "mysql" {
|
||||
source = "terraform-google-modules/sql-db/google//modules/mysql"
|
||||
version = "~> 25.0"
|
||||
|
||||
name = var.database_config.name
|
||||
project_id = var.project_id
|
||||
deletion_protection = var.database_config.deletion_protection
|
||||
database_version = var.database_config.database_version
|
||||
tier = var.database_config.tier
|
||||
region = var.region
|
||||
random_instance_name = true
|
||||
enable_default_user = true
|
||||
enable_default_db = true
|
||||
user_name = var.database_config.database_user
|
||||
db_name = var.database_config.database_name
|
||||
db_collation = var.database_config.collation
|
||||
db_charset = var.database_config.charset
|
||||
|
||||
ip_configuration = {
|
||||
ipv4_enabled = false
|
||||
# We never set authorized networks, we need all connections via the
|
||||
# public IP to be mediated by Cloud SQL.
|
||||
authorized_networks = []
|
||||
require_ssl = false
|
||||
private_network = module.vpc.network_self_link
|
||||
}
|
||||
|
||||
module_depends_on = [module.private-service-access.peering_completed]
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
# -------------------------------------
|
||||
# Service Account for Cloud Run Service & Job
|
||||
# -------------------------------------
|
||||
|
||||
resource "google_service_account" "fleet_run_sa" {
|
||||
project = var.project_id
|
||||
account_id = "${var.prefix}-run-sa"
|
||||
display_name = "Service Account for Fleet Cloud Run Service and Jobs"
|
||||
}
|
||||
|
||||
resource "google_project_iam_member" "fleet_run_sa_sql_instance_user" {
|
||||
project = var.project_id
|
||||
role = "roles/cloudsql.instanceUser"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
}
|
||||
|
||||
# Recommended for Cloud Run standard logging
|
||||
resource "google_project_iam_member" "fleet_run_sa_log_writer" {
|
||||
project = var.project_id
|
||||
role = "roles/logging.logWriter"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
}
|
||||
|
||||
# Recommended for Cloud Run standard metrics
|
||||
resource "google_project_iam_member" "fleet_run_sa_monitoring_writer" {
|
||||
project = var.project_id
|
||||
role = "roles/monitoring.metricWriter"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
}
|
||||
|
||||
|
||||
resource "google_secret_manager_secret_iam_member" "fleet_run_sa_db_secret_access" {
|
||||
project = var.project_id
|
||||
secret_id = google_secret_manager_secret.database_password.id
|
||||
role = "roles/secretmanager.secretAccessor"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
|
||||
depends_on = [google_secret_manager_secret.database_password]
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_iam_member" "fleet_run_sa_private_key_secret_access" {
|
||||
project = var.project_id
|
||||
secret_id = google_secret_manager_secret.private_key.id
|
||||
role = "roles/secretmanager.secretAccessor"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
|
||||
depends_on = [google_secret_manager_secret.private_key]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
locals {
|
||||
# Clean the DNS record name for use in managed SSL cert domains (remove trailing dot)
|
||||
managed_ssl_domain = trim(var.dns_record_name, ".")
|
||||
}
|
||||
|
||||
# Create/Manage the DNS Zone in Cloud DNS
|
||||
resource "google_dns_managed_zone" "fleet_dns_zone" {
|
||||
project = var.project_id
|
||||
name = "${var.prefix}-zone"
|
||||
dns_name = var.dns_zone_name
|
||||
}
|
||||
|
||||
# Configure the External HTTP(S) Load Balancer
|
||||
module "fleet_lb" {
|
||||
source = "GoogleCloudPlatform/lb-http/google//modules/serverless_negs"
|
||||
version = "~> 12.0"
|
||||
|
||||
project = var.project_id
|
||||
name = "${var.prefix}-lb" # e.g., fleet-lb
|
||||
|
||||
# SSL Configuration
|
||||
ssl = true
|
||||
https_redirect = true # Enforce HTTPS
|
||||
managed_ssl_certificate_domains = [local.managed_ssl_domain]
|
||||
|
||||
# Backend Configuration
|
||||
backends = {
|
||||
default = {
|
||||
description = "Backend for Fleet Cloud Run service"
|
||||
enable_cdn = false # Set to true if you want Cloud CDN
|
||||
protocol = "HTTP"
|
||||
groups = [
|
||||
{
|
||||
group = google_compute_region_network_endpoint_group.neg.id
|
||||
}
|
||||
]
|
||||
|
||||
log_config = {
|
||||
enable = true
|
||||
sample_rate = 1.0 # Log all requests
|
||||
}
|
||||
|
||||
# IAP (Identity-Aware Proxy) - disabled by default
|
||||
iap_config = {
|
||||
enable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
depends_on = [google_compute_region_network_endpoint_group.neg]
|
||||
}
|
||||
|
||||
# Create the DNS A Record for the Load Balancer
|
||||
resource "google_dns_record_set" "fleet_dns_record" {
|
||||
project = var.project_id
|
||||
managed_zone = google_dns_managed_zone.fleet_dns_zone.name
|
||||
name = var.dns_record_name
|
||||
type = "A"
|
||||
ttl = 300 # Time-to-live in seconds
|
||||
|
||||
# Point to the external IP address of the created load balancer
|
||||
rrdatas = [module.fleet_lb.external_ip]
|
||||
|
||||
depends_on = [module.fleet_lb]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
terraform {
|
||||
required_version = "~> 1.11"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "6.35.0"
|
||||
}
|
||||
|
||||
terracurl = {
|
||||
source = "devops-rob/terracurl"
|
||||
version = "~> 1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "google_client_config" "current" {}
|
||||
@@ -0,0 +1,89 @@
|
||||
output "fleet_application_url" {
|
||||
description = "The primary URL to access the Fleet application (via the Load Balancer)."
|
||||
value = "https://${google_dns_record_set.fleet_dns_record.name}"
|
||||
}
|
||||
|
||||
output "load_balancer_ip_address" {
|
||||
description = "The external IP address of the HTTP(S) Load Balancer."
|
||||
value = module.fleet_lb.external_ip
|
||||
}
|
||||
|
||||
output "cloud_run_service_name" {
|
||||
description = "The name of the deployed Fleet Cloud Run service."
|
||||
value = module.fleet-service.service_name
|
||||
}
|
||||
|
||||
output "cloud_run_service_location" {
|
||||
description = "The location of the deployed Fleet Cloud Run service."
|
||||
value = module.fleet-service.location // Check the actual output name
|
||||
}
|
||||
|
||||
output "mysql_instance_name" {
|
||||
description = "The name of the Cloud SQL for MySQL instance."
|
||||
value = module.mysql.instance_name
|
||||
}
|
||||
|
||||
output "mysql_instance_connection_name" {
|
||||
description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)."
|
||||
value = module.mysql.instance_connection_name
|
||||
}
|
||||
|
||||
output "redis_instance_name" {
|
||||
description = "The name of the Memorystore for Redis instance."
|
||||
value = module.memstore.id
|
||||
}
|
||||
|
||||
output "redis_host" {
|
||||
description = "The host IP address of the Memorystore for Redis instance."
|
||||
value = module.memstore.host
|
||||
}
|
||||
|
||||
output "redis_port" {
|
||||
description = "The port number of the Memorystore for Redis instance."
|
||||
value = module.memstore.port
|
||||
}
|
||||
|
||||
output "software_installers_bucket_name" {
|
||||
description = "The name of the GCS bucket for Fleet software installers."
|
||||
value = google_storage_bucket.software_installers.name
|
||||
}
|
||||
|
||||
output "software_installers_bucket_url" {
|
||||
description = "The gsutil URL of the GCS bucket for Fleet software installers."
|
||||
value = google_storage_bucket.software_installers.url
|
||||
}
|
||||
|
||||
output "fleet_service_account_email" {
|
||||
description = "The email address of the service account used by the Fleet Cloud Run service."
|
||||
value = google_service_account.fleet_run_sa.email
|
||||
}
|
||||
|
||||
output "dns_managed_zone_name" {
|
||||
description = "The name of the Cloud DNS managed zone created for Fleet."
|
||||
value = google_dns_managed_zone.fleet_dns_zone.name
|
||||
}
|
||||
|
||||
output "dns_managed_zone_name_servers" {
|
||||
description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these."
|
||||
value = google_dns_managed_zone.fleet_dns_zone.name_servers
|
||||
}
|
||||
|
||||
output "vpc_network_name" {
|
||||
description = "The name of the VPC network created."
|
||||
value = module.vpc.network_name
|
||||
}
|
||||
|
||||
output "vpc_network_self_link" {
|
||||
description = "The self-link of the VPC network created."
|
||||
value = module.vpc.network_self_link
|
||||
}
|
||||
|
||||
output "vpc_subnets_names" {
|
||||
description = "List of subnet names created in the VPC."
|
||||
value = module.vpc.subnets_names
|
||||
}
|
||||
|
||||
output "cloud_router_name" {
|
||||
description = "The name of the Cloud Router created for NAT."
|
||||
value = module.cloud_router.router.name
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
resource "random_pet" "suffix" {
|
||||
length = 1
|
||||
}
|
||||
|
||||
resource "random_password" "private_key" {
|
||||
length = 32
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret" "database_password" {
|
||||
project = var.project_id
|
||||
secret_id = "fleet-db-password-${random_pet.suffix.id}"
|
||||
replication {
|
||||
auto {}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_version" "database_password" {
|
||||
secret = google_secret_manager_secret.database_password.name
|
||||
secret_data = module.mysql.generated_user_password
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret" "private_key" {
|
||||
project = var.project_id
|
||||
secret_id = "fleet-private-key-${random_pet.suffix.id}"
|
||||
replication {
|
||||
auto {}
|
||||
}
|
||||
}
|
||||
|
||||
resource "google_secret_manager_secret_version" "private_key" {
|
||||
secret = google_secret_manager_secret.private_key.name
|
||||
secret_data = random_password.private_key.result
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
resource "google_storage_hmac_key" "key" {
|
||||
project = var.project_id
|
||||
service_account_email = google_service_account.fleet_run_sa.email
|
||||
}
|
||||
|
||||
resource "google_storage_bucket" "software_installers" {
|
||||
project = var.project_id
|
||||
name = var.fleet_config.installers_bucket_name
|
||||
location = var.location
|
||||
force_destroy = true
|
||||
|
||||
uniform_bucket_level_access = true
|
||||
}
|
||||
|
||||
resource "google_storage_bucket_iam_member" "hmac_sa_storage_admin" {
|
||||
bucket = google_storage_bucket.software_installers.name
|
||||
role = "roles/storage.objectAdmin"
|
||||
member = "serviceAccount:${google_service_account.fleet_run_sa.email}"
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
variable "project_id" {
|
||||
description = "GCP project ID"
|
||||
}
|
||||
|
||||
variable "location" {
|
||||
default = "us"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
default = "us-central1"
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
default = "fleet"
|
||||
}
|
||||
|
||||
variable "dns_zone_name" {
|
||||
description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "dns_record_name" {
|
||||
description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "cache_config" {
|
||||
type = object({
|
||||
name = string
|
||||
tier = string
|
||||
engine_version = string
|
||||
connect_mode = string
|
||||
memory_size = number
|
||||
})
|
||||
default = {
|
||||
name = "fleet-cache"
|
||||
tier = "STANDARD_HA"
|
||||
engine_version = null // defaults to version 7
|
||||
connect_mode = "PRIVATE_SERVICE_ACCESS"
|
||||
memory_size = 1
|
||||
}
|
||||
}
|
||||
|
||||
variable "database_config" {
|
||||
type = object({
|
||||
name = string
|
||||
database_name = string
|
||||
database_user = string
|
||||
collation = string
|
||||
charset = string
|
||||
deletion_protection = bool
|
||||
database_version = string
|
||||
tier = string
|
||||
})
|
||||
default = {
|
||||
name = "fleet-mysql"
|
||||
database_name = "fleet"
|
||||
database_user = "fleet"
|
||||
collation = "utf8mb4_unicode_ci"
|
||||
charset = "utf8mb4"
|
||||
deletion_protection = false
|
||||
database_version = "MYSQL_8_0"
|
||||
tier = "db-n1-standard-1"
|
||||
}
|
||||
}
|
||||
|
||||
variable "vpc_config" {
|
||||
type = object({
|
||||
network_name = string
|
||||
subnets = list(object({
|
||||
subnet_name = string
|
||||
subnet_ip = string
|
||||
subnet_region = string
|
||||
subnet_private_access = bool
|
||||
}))
|
||||
})
|
||||
|
||||
default = {
|
||||
network_name = "fleet-network"
|
||||
subnets = [
|
||||
{
|
||||
subnet_name = "fleet-subnet"
|
||||
subnet_ip = "10.10.10.0/24"
|
||||
subnet_region = "us-central1"
|
||||
subnet_private_access = true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
variable "fleet_config" {
|
||||
type = object({
|
||||
installers_bucket_name = string
|
||||
image_tag = string
|
||||
fleet_cpu = string
|
||||
fleet_memory = string
|
||||
debug_logging = bool
|
||||
license_key = optional(string)
|
||||
min_instance_count = number
|
||||
max_instance_count = number
|
||||
exec_migration = bool
|
||||
extra_env_vars = optional(map(string))
|
||||
extra_secret_env_vars = optional(map(object({
|
||||
secret = string
|
||||
version = string
|
||||
})))
|
||||
})
|
||||
default = {
|
||||
image_tag = "fleetdm/fleet:v4.67.3"
|
||||
installers_bucket_name = ""
|
||||
fleet_cpu = "1000m"
|
||||
fleet_memory = "4096Mi"
|
||||
debug_logging = false
|
||||
min_instance_count = 1
|
||||
max_instance_count = 5
|
||||
exec_migration = true
|
||||
extra_env_vars = {}
|
||||
extra_secret_env_vars = {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
# Example from your vpc.tf (ensure this part is correct)
|
||||
|
||||
|
||||
locals {
|
||||
network_name = "${var.prefix}-network"
|
||||
subnet_name = "${var.prefix}-subnet"
|
||||
}
|
||||
|
||||
|
||||
module "vpc" {
|
||||
source = "terraform-google-modules/network/google"
|
||||
version = "11.0.0"
|
||||
|
||||
project_id = var.project_id
|
||||
network_name = var.vpc_config.network_name
|
||||
subnets = var.vpc_config.subnets
|
||||
}
|
||||
|
||||
module "cloud_router" {
|
||||
source = "terraform-google-modules/cloud-router/google"
|
||||
version = "7.0"
|
||||
name = "${var.prefix}-cloud-router"
|
||||
project = var.project_id
|
||||
network = module.vpc.network_name
|
||||
region = var.region
|
||||
|
||||
nats = [{
|
||||
name = "${var.prefix}-vpc-nat"
|
||||
}]
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
terraform {
|
||||
required_version = "~> 1.11"
|
||||
required_providers {
|
||||
google = {
|
||||
source = "hashicorp/google"
|
||||
version = "6.35.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
provider "google" {
|
||||
# Credentials used here need Org/Folder level permissions
|
||||
default_labels = var.labels
|
||||
}
|
||||
|
||||
|
||||
module "project_factory" {
|
||||
source = "terraform-google-modules/project-factory/google"
|
||||
version = "~> 18.0.0"
|
||||
|
||||
name = var.project_name
|
||||
random_project_id = var.random_project_id
|
||||
org_id = var.org_id
|
||||
billing_account = var.billing_account_id
|
||||
|
||||
default_service_account = "delete"
|
||||
|
||||
# Enable baseline APIs needed by most projects + your app stack
|
||||
activate_apis = [
|
||||
"compute.googleapis.com",
|
||||
"sqladmin.googleapis.com",
|
||||
"redis.googleapis.com",
|
||||
"run.googleapis.com",
|
||||
"vpcaccess.googleapis.com",
|
||||
"secretmanager.googleapis.com",
|
||||
"storage.googleapis.com",
|
||||
"dns.googleapis.com",
|
||||
"iam.googleapis.com",
|
||||
"cloudresourcemanager.googleapis.com",
|
||||
"serviceusage.googleapis.com",
|
||||
"servicenetworking.googleapis.com",
|
||||
"logging.googleapis.com",
|
||||
"monitoring.googleapis.com",
|
||||
"memorystore.googleapis.com",
|
||||
"serviceconsumermanagement.googleapis.com",
|
||||
"networkconnectivity.googleapis.com"
|
||||
]
|
||||
|
||||
labels = var.labels
|
||||
}
|
||||
|
||||
module "fleet" {
|
||||
source = "./byo-project"
|
||||
project_id = module.project_factory.project_id
|
||||
dns_record_name = var.dns_record_name
|
||||
dns_zone_name = var.dns_zone_name
|
||||
vpc_config = var.vpc_config
|
||||
fleet_config = var.fleet_config
|
||||
cache_config = var.cache_config
|
||||
database_config = var.database_config
|
||||
region = var.region
|
||||
location = var.location
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
output "fleet_application_url" {
|
||||
description = "The primary URL to access the Fleet application (via the Load Balancer)."
|
||||
value = module.fleet.fleet_application_url
|
||||
}
|
||||
|
||||
output "load_balancer_ip_address" {
|
||||
description = "The external IP address of the HTTP(S) Load Balancer."
|
||||
value = module.fleet.load_balancer_ip_address
|
||||
}
|
||||
|
||||
output "cloud_run_service_name" {
|
||||
description = "The name of the deployed Fleet Cloud Run service."
|
||||
value = module.fleet.cloud_run_service_name
|
||||
}
|
||||
|
||||
output "cloud_run_service_location" {
|
||||
description = "The location of the deployed Fleet Cloud Run service."
|
||||
value = module.fleet.cloud_run_service_location
|
||||
}
|
||||
|
||||
output "mysql_instance_name" {
|
||||
description = "The name of the Cloud SQL for MySQL instance."
|
||||
value = module.fleet.mysql_instance_name
|
||||
}
|
||||
|
||||
output "mysql_instance_connection_name" {
|
||||
description = "The connection name for the Cloud SQL instance (used by Cloud SQL Proxy)."
|
||||
value = module.fleet.mysql_instance_connection_name
|
||||
}
|
||||
|
||||
output "redis_instance_name" {
|
||||
description = "The name of the Memorystore for Redis instance."
|
||||
value = module.fleet.redis_instance_name
|
||||
}
|
||||
|
||||
output "redis_host" {
|
||||
description = "The host IP address of the Memorystore for Redis instance."
|
||||
value = module.fleet.redis_host
|
||||
}
|
||||
|
||||
output "redis_port" {
|
||||
description = "The port number of the Memorystore for Redis instance."
|
||||
value = module.fleet.redis_port
|
||||
}
|
||||
|
||||
output "software_installers_bucket_name" {
|
||||
description = "The name of the GCS bucket for Fleet software installers."
|
||||
value = module.fleet.software_installers_bucket_name
|
||||
}
|
||||
|
||||
output "software_installers_bucket_url" {
|
||||
description = "The gsutil URL of the GCS bucket for Fleet software installers."
|
||||
value = module.fleet.software_installers_bucket_url
|
||||
}
|
||||
|
||||
output "fleet_service_account_email" {
|
||||
description = "The email address of the service account used by the Fleet Cloud Run service."
|
||||
value = module.fleet.fleet_service_account_email
|
||||
}
|
||||
|
||||
output "dns_managed_zone_name" {
|
||||
description = "The name of the Cloud DNS managed zone created for Fleet."
|
||||
value = module.fleet.dns_managed_zone_name
|
||||
}
|
||||
|
||||
output "dns_managed_zone_name_servers" {
|
||||
description = "The authoritative name servers for the created Cloud DNS managed zone. Delegate your domain to these."
|
||||
value = module.fleet.dns_managed_zone_name_servers
|
||||
}
|
||||
|
||||
output "vpc_network_name" {
|
||||
description = "The name of the VPC network created."
|
||||
value = module.fleet.vpc_network_name
|
||||
}
|
||||
|
||||
output "vpc_network_self_link" {
|
||||
description = "The self-link of the VPC network created."
|
||||
value = module.fleet.vpc_network_self_link
|
||||
}
|
||||
|
||||
output "vpc_subnets_names" {
|
||||
description = "List of subnet names created in the VPC."
|
||||
value = module.fleet.vpc_subnets_names
|
||||
}
|
||||
|
||||
output "cloud_router_name" {
|
||||
description = "The name of the Cloud Router created for NAT."
|
||||
value = module.fleet.cloud_router_name
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
variable "project_name" {
|
||||
default = "fleet"
|
||||
}
|
||||
|
||||
variable "org_id" {
|
||||
description = "organization id"
|
||||
}
|
||||
|
||||
variable "billing_account_id" {
|
||||
description = "billing account id"
|
||||
}
|
||||
|
||||
variable "labels" {
|
||||
description = "resource labels"
|
||||
default = { application = "fleet" }
|
||||
type = map(string)
|
||||
}
|
||||
|
||||
variable "fleet_image" {
|
||||
default = "v4.67.3"
|
||||
}
|
||||
|
||||
variable "dns_zone_name" {
|
||||
description = "The DNS name of the managed zone (e.g., 'my-fleet-infra.com.')"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "dns_record_name" {
|
||||
description = "The DNS record for Fleet (e.g., 'fleet.my-fleet-infra.com.')"
|
||||
type = string
|
||||
}
|
||||
|
||||
variable "random_project_id" {
|
||||
default = true
|
||||
}
|
||||
variable "location" {
|
||||
description = "The general location for resources, e.g., 'us' for GCS buckets."
|
||||
type = string
|
||||
default = "us"
|
||||
}
|
||||
|
||||
variable "region" {
|
||||
description = "The GCP region for regional resources."
|
||||
type = string
|
||||
default = "us-central1"
|
||||
}
|
||||
|
||||
variable "prefix" {
|
||||
description = "A prefix used for naming resources within the byo-project module."
|
||||
type = string
|
||||
default = "fleet"
|
||||
}
|
||||
|
||||
variable "cache_config" {
|
||||
description = "Configuration for the Memorystore (Redis) instance."
|
||||
type = object({
|
||||
name = string
|
||||
tier = string
|
||||
engine_version = string
|
||||
connect_mode = string
|
||||
memory_size = number
|
||||
})
|
||||
default = {
|
||||
name = "fleet-cache"
|
||||
tier = "STANDARD_HA"
|
||||
engine_version = null // defaults to version 7
|
||||
connect_mode = "PRIVATE_SERVICE_ACCESS"
|
||||
memory_size = 1
|
||||
}
|
||||
}
|
||||
|
||||
variable "database_config" {
|
||||
description = "Configuration for the Cloud SQL (MySQL) instance."
|
||||
type = object({
|
||||
name = string
|
||||
database_name = string
|
||||
database_user = string
|
||||
collation = string
|
||||
charset = string
|
||||
deletion_protection = bool
|
||||
database_version = string
|
||||
tier = string
|
||||
})
|
||||
default = {
|
||||
name = "fleet-mysql"
|
||||
database_name = "fleet"
|
||||
database_user = "fleet"
|
||||
collation = "utf8mb4_unicode_ci"
|
||||
charset = "utf8mb4"
|
||||
deletion_protection = false
|
||||
database_version = "MYSQL_8_0"
|
||||
tier = "db-n1-standard-1"
|
||||
}
|
||||
}
|
||||
|
||||
variable "vpc_config" {
|
||||
description = "Configuration for the VPC network and subnets."
|
||||
type = object({
|
||||
network_name = string
|
||||
subnets = list(object({
|
||||
subnet_name = string
|
||||
subnet_ip = string
|
||||
subnet_region = string
|
||||
subnet_private_access = bool
|
||||
}))
|
||||
})
|
||||
default = {
|
||||
network_name = "fleet-network"
|
||||
subnets = [
|
||||
{
|
||||
subnet_name = "fleet-subnet"
|
||||
subnet_ip = "10.10.10.0/24"
|
||||
subnet_region = "us-central1"
|
||||
subnet_private_access = true
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
variable "fleet_config" {
|
||||
description = "Configuration for the Fleet application deployment."
|
||||
type = object({
|
||||
installers_bucket_name = string
|
||||
image_tag = string
|
||||
fleet_cpu = string
|
||||
fleet_memory = string
|
||||
debug_logging = bool
|
||||
license_key = optional(string)
|
||||
min_instance_count = number
|
||||
max_instance_count = number
|
||||
exec_migration = bool
|
||||
extra_env_vars = optional(map(string))
|
||||
extra_secret_env_vars = optional(map(object({
|
||||
secret = string
|
||||
version = string
|
||||
})))
|
||||
})
|
||||
default = {
|
||||
image_tag = "fleetdm/fleet:v4.67.3"
|
||||
installers_bucket_name = "" # Bucket names must be globally unique
|
||||
fleet_cpu = "1000m"
|
||||
fleet_memory = "4096Mi"
|
||||
debug_logging = false
|
||||
min_instance_count = 1
|
||||
max_instance_count = 5
|
||||
exec_migration = true
|
||||
extra_env_vars = {}
|
||||
extra_secret_env_vars = {}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user