1
0
Fork 0
mirror of https://github.com/imjasonh/terraform-playground synced 2026-07-07 23:35:16 +00:00

Merge pull request #3 from imjasonh/imjasonh-patch-1

This commit is contained in:
Jason Hall 2025-11-28 13:12:48 -06:00 committed by GitHub
commit 436b5adcd0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 239 additions and 0 deletions

View file

@ -0,0 +1,71 @@
#cloud-config
# This cloud-init configuration defines systemd services to run Docker containers
# on Container-Optimized OS (COS) with full observability support.
write_files:
%{ for name, container in containers ~}
- path: /etc/systemd/system/${name}.service
permissions: 0644
owner: root
content: |
[Unit]
Description=Containerized application: ${name}
# Wait for the Docker socket to be ready
Wants=docker.socket
After=docker.service
[Service]
# Crucial for persistence: ensure the service restarts on exit.
Restart=${container.restart_policy}
RestartSec=5s
# Stop and remove any pre-existing container with the same name.
ExecStartPre=/usr/bin/docker stop ${name} || true
ExecStartPre=/usr/bin/docker rm ${name} || true
# Pull the image before starting.
ExecStartPre=/usr/bin/docker pull ${container.image}
# The main execution command: docker run.
# When logging is enabled, use the gcplogs driver to send container logs to Cloud Logging.
ExecStart=/usr/bin/docker run \
--name ${name} \
%{ if enable_logging ~}
--log-driver=gcplogs \
--log-opt gcp-project=${project_id} \
--log-opt gcp-log-cmd=true \
--log-opt gcp-meta-zone=true \
--log-opt gcp-meta-name=true \
--log-opt labels=container_name \
--label container_name=${name} \
%{ endif ~}
%{ for e in container.env ~}
-e ${e.name}="${e.value}" \
%{ endfor ~}
${container.image} \
%{ if container.command != "" ~}
"${container.command}" \
%{ endif ~}
%{ for arg in container.args ~}
# Escape single quotes: end quoted string, add escaped quote, resume quoted string
'${replace(arg, "'", "'\\''")}' \
%{ endfor ~}
# ExecStop is called when the service is stopped manually.
ExecStop=/usr/bin/docker stop ${name}
[Install]
WantedBy=multi-user.target
%{ endfor ~}
runcmd:
# Reload the systemd configuration to recognize the new service files.
- systemctl daemon-reload
%{ for name, container in containers ~}
# Enable and start ${name} service.
- systemctl enable ${name}.service
- systemctl start ${name}.service
%{ endfor ~}

64
container-vm/main.tf Normal file
View file

@ -0,0 +1,64 @@
# Fetch the latest source image based on the input variables (defaulting to COS)
data "google_compute_image" "source_image" {
family = var.source_image_family
project = var.source_image_project
}
# Render the cloud-init YAML template using dynamic module inputs
locals {
rendered_cloud_init = templatefile("${path.module}/cloud-init.yaml.tftpl", {
containers = var.containers,
enable_logging = var.enable_logging,
project_id = var.project_id,
})
}
# The Instance Template resource itself
resource "google_compute_instance_template" "container_vm_template" {
project = var.project_id
name_prefix = "systemd-container-tmpl-"
description = "Runs a container using Docker, managed by a systemd unit via cloud-init."
machine_type = var.machine_type
region = var.region
labels = var.labels
# Disk configuration using the fetched OS image
disk {
source_image = data.google_compute_image.source_image.self_link
auto_delete = true
boot = true
}
# Network configuration (private VM - no external IP)
network_interface {
network = var.network
subnetwork = var.subnetwork
}
# Service Account configuration (needed for Google Cloud API access, including image pulls)
service_account {
email = var.service_account_email
scopes = ["cloud-platform"]
}
# Metadata for cloud-init and observability
metadata = {
user-data = local.rendered_cloud_init
google-logging-enabled = tostring(var.enable_logging)
google-monitoring-enabled = tostring(var.enable_monitoring)
cos-metrics-enabled = tostring(var.enable_cos_metrics)
}
scheduling {
automatic_restart = true
on_host_maintenance = "MIGRATE"
}
# Enable shielded VM features for security
shielded_instance_config {
enable_secure_boot = true
enable_vtpm = true
enable_integrity_monitoring = true
}
}

10
container-vm/outputs.tf Normal file
View file

@ -0,0 +1,10 @@
output "instance_template_self_link" {
description = "The self-link of the created instance template. Use this to deploy VMs or MIGs."
value = google_compute_instance_template.container_vm_template.self_link
}
output "instance_template_name" {
description = "The name of the created instance template."
value = google_compute_instance_template.container_vm_template.name
}

94
container-vm/variables.tf Normal file
View file

@ -0,0 +1,94 @@
variable "project_id" {
description = "The GCP project ID."
type = string
}
variable "region" {
description = "The region where the compute template is created."
type = string
default = "us-central1"
}
variable "machine_type" {
description = "The machine type for the VM instance template."
type = string
default = "e2-medium"
}
variable "containers" {
description = "A map of containers to run on the VM. Each key is the container name, and the value specifies the container configuration."
type = map(object({
image = string
command = optional(string, "")
args = optional(list(string), [])
env = optional(list(object({
name = string
value = string
})), [])
restart_policy = optional(string, "always")
}))
validation {
condition = length(var.containers) > 0
error_message = "At least one container must be specified."
}
validation {
condition = alltrue([for name, c in var.containers : can(regex("^.+@sha256:[a-f0-9]{64}$", c.image))])
error_message = "All container images must be referenced by digest (e.g., 'gcr.io/my-project/my-app@sha256:<64 hex chars>')."
}
}
variable "service_account_email" {
description = "The email of the service account to use for the VM. Must be created outside this module."
type = string
}
# Defaulting to COS, which has Docker/containerd pre-installed.
variable "source_image_family" {
description = "The OS image family to use. COS is recommended."
type = string
default = "cos-stable"
}
variable "source_image_project" {
description = "The project hosting the source image."
type = string
default = "cos-cloud"
}
variable "network" {
description = "The VPC network to attach the VM to (e.g., 'projects/PROJECT/global/networks/NETWORK' or 'NETWORK')."
type = string
}
variable "subnetwork" {
description = "The subnetwork to attach the VM to (e.g., 'projects/PROJECT/regions/REGION/subnetworks/SUBNET' or 'SUBNET')."
type = string
}
# Observability variables
variable "enable_logging" {
description = "Enable Cloud Logging for the VM and containers. When true, container logs are sent to Cloud Logging using the gcplogs driver."
type = bool
default = true
}
variable "enable_monitoring" {
description = "Enable Cloud Monitoring for the VM. When true, VM metrics are collected and sent to Cloud Monitoring."
type = bool
default = true
}
variable "enable_cos_metrics" {
description = "Enable Container-Optimized OS metrics collection for additional container and system metrics."
type = bool
default = true
}
variable "labels" {
description = "Labels to apply to the instance template for organization and filtering in Cloud Monitoring and Logging."
type = map(string)
default = {}
}