diff --git a/docs/terraform.md b/docs/terraform.md index cb8a8280b..08134a71f 100644 --- a/docs/terraform.md +++ b/docs/terraform.md @@ -20,7 +20,14 @@ This setup supports the deployment of the following configurations: This approach is particularly suitable for deploying and configuring: - Oracle databases on RHEL or Oracle Linux -- High-availability configurations for database and application clusters +- Two-node Oracle Data Guard deployments, where: + + The user provides: + + * zone1 and subnetwork1 for the primary node + * zone2 and subnetwork2 for the standby node + + Only 2-node Data Guard setups are currently supported. --- @@ -43,6 +50,22 @@ This infrastructure is modular and customizable, allowing you to tailor it to sp --- +## Instance naming convention +For single-instance deployments, the VM will be named using the pattern: +"-1". +Example: If instance_name = "oracle-db", the resulting VM will be oracle-db-1. + +For multi-node Oracle Data Guard deployments: + +* Primary node: "-1" +* Standby node: "-2" + +Example: If instance_name = "oracle-db", the primary VM will be oracle-db-1 and the standby VM will be oracle-db-2. + + +--- + + ## Pre-requisites To use this Terraform and Ansible integration, ensure you have the following tools installed: diff --git a/roles/workload-agent/tasks/metric_collection.yml b/roles/workload-agent/tasks/create_db_user.yml similarity index 85% rename from roles/workload-agent/tasks/metric_collection.yml rename to roles/workload-agent/tasks/create_db_user.yml index 12fa28b40..ea3f95e4f 100644 --- a/roles/workload-agent/tasks/metric_collection.yml +++ b/roles/workload-agent/tasks/create_db_user.yml @@ -46,13 +46,3 @@ PATH: "{{ oracle_home }}/bin:/usr/local/bin:/bin:/usr/bin:/usr/local/sbin:/usr/sbin" no_log: true tags: workload-agent - -- name: Copy workload-agent's configuration file to the database VM - template: - src: "configuration.json.j2" - dest: "/etc/google-cloud-workload-agent/configuration.json" - owner: root - group: root - mode: u=rw,go=r - notify: Restart workload-agent - tags: workload-agent diff --git a/roles/workload-agent/tasks/main.yml b/roles/workload-agent/tasks/main.yml index 21054c829..5ee85ebdb 100644 --- a/roles/workload-agent/tasks/main.yml +++ b/roles/workload-agent/tasks/main.yml @@ -18,9 +18,23 @@ file: install.yml tags: workload-agent -- name: Configure Google Cloud Agent for Compute Workloads to collect Oracle metrics and send them to Google Cloud Monitoring +- name: Create a database user for the Google Cloud Agent for Compute Workloads to collect Oracle metrics include_tasks: - file: metric_collection.yml + file: create_db_user.yml + # Run for single-instance setup or for the primary in multi-node Data Guard setup. + # For standby setup, install-oracle.sh generates the [primary] group in the inventory file. when: - oracle_metrics_secret | length > 0 + - "groups['primary'] is not defined" + +- name: Copy workload-agent's configuration file to the database VM + template: + src: "configuration.json.j2" + dest: "/etc/google-cloud-workload-agent/configuration.json" + owner: root + group: root + mode: u=rw,go=r + when: + - oracle_metrics_secret | length > 0 + notify: Restart workload-agent tags: workload-agent diff --git a/terraform/main.tf b/terraform/main.tf index f9d97f83f..89bd43032 100644 --- a/terraform/main.tf +++ b/terraform/main.tf @@ -16,7 +16,6 @@ locals { fs_disks = [ { auto_delete = true - boot = false device_name = "oracle_home" disk_size_gb = var.oracle_home_disk.size_gb disk_type = var.oracle_home_disk.type @@ -26,7 +25,6 @@ locals { asm_disks = [ { auto_delete = true - boot = false device_name = "data" disk_size_gb = var.data_disk.size_gb disk_type = var.data_disk.type @@ -34,7 +32,6 @@ locals { }, { auto_delete = true - boot = false device_name = "reco" disk_size_gb = var.reco_disk.size_gb disk_type = var.reco_disk.type @@ -42,7 +39,6 @@ locals { }, { auto_delete = true - boot = false device_name = "swap" disk_size_gb = var.swap_disk_size_gb disk_type = var.swap_disk_type @@ -79,70 +75,149 @@ locals { additional_disks = concat(local.fs_disks, local.asm_disks) project_id = var.project_id + + is_multi_instance = ( + var.zone1 != "" && var.zone2 != "" && var.subnetwork1 != "" && var.subnetwork2 != "" + ) + + instances = local.is_multi_instance ? { + "${var.instance_name}-1" = { + zone = var.zone1 + subnetwork = var.subnetwork1 + role = "primary" + } + "${var.instance_name}-2" = { + zone = var.zone2 + subnetwork = var.subnetwork2 + role = "standby" + } + } : { + "${var.instance_name}-1" = { + zone = var.zone1 + subnetwork = var.subnetwork1 + role = "primary" + } + } } -module "instance_template" { - source = "terraform-google-modules/vm/google//modules/instance_template" - version = "~> 13.0" - - name_prefix = format("%s-template", var.instance_name) - region = var.region - project_id = local.project_id - network = coalesce(var.network, var.subnetwork) - subnetwork = coalesce(var.subnetwork, var.network) - subnetwork_project = local.project_id - service_account = { - email = var.vm_service_account - scopes = ["https://www.googleapis.com/auth/cloud-platform"] +data "google_compute_image" "os_image" { + family = var.source_image_family + project = var.source_image_project +} + +resource "time_static" "template_suffix" {} + +locals { + template_suffix = formatdate("YYYYMMDDhhmmss", time_static.template_suffix.rfc3339) +} + +resource "google_compute_instance_template" "default" { + name = "${var.instance_name}-${local.template_suffix}" + project = var.project_id + machine_type = var.machine_type + + network_interface { + # gets overridden during instance creation + subnetwork = var.subnetwork1 + } + disk { + boot = true + auto_delete = true + source_image = data.google_compute_image.os_image.self_link + disk_type = var.boot_disk_type + disk_size_gb = var.boot_disk_size_gb } - machine_type = var.machine_type - source_image_family = var.source_image_family - source_image_project = var.source_image_project - disk_size_gb = var.boot_disk_size_gb - disk_type = var.boot_disk_type - auto_delete = true + dynamic "disk" { + for_each = local.additional_disks + content { + boot = false + auto_delete = disk.value.auto_delete + device_name = disk.value.device_name + disk_size_gb = disk.value.disk_size_gb + disk_type = disk.value.disk_type + labels = disk.value.disk_labels + } + } + service_account { + email = var.vm_service_account + scopes = ["cloud-platform"] + } metadata = { metadata_startup_script = var.metadata_startup_script enable-oslogin = "TRUE" } - additional_disks = local.additional_disks - tags = var.network_tags } -module "compute_instance" { - source = "terraform-google-modules/vm/google//modules/compute_instance" - version = "~> 13.0" - - region = var.region - zone = var.zone - network = coalesce(var.network, var.subnetwork) - subnetwork = coalesce(var.subnetwork, var.network) - subnetwork_project = local.project_id - hostname = var.instance_name - instance_template = module.instance_template.self_link - deletion_protection = false - - access_config = var.assign_public_ip ? [{ - nat_ip = null - network_tier = "PREMIUM" - }] : [] +resource "google_compute_instance_from_template" "database_vm" { + for_each = local.instances + + name = each.key + zone = each.value.zone + project = var.project_id + source_instance_template = google_compute_instance_template.default.self_link + + network_interface { + subnetwork = each.value.subnetwork + + dynamic "access_config" { + for_each = var.assign_public_ip ? [1] : [] + content {} + } + } + } resource "random_id" "suffix" { byte_length = 4 } +locals { + database_vm_nodes = [ + for vm in google_compute_instance_from_template.database_vm : { + name = vm.name + zone = vm.zone + ip = vm.network_interface[0].network_ip + role = local.instances[vm.name].role + } + ] +} + +locals { + common_flags = join(" ", compact([ + length(local.asm_disk_config) > 0 ? "--ora-asm-disks-json '${jsonencode(local.asm_disk_config)}'" : "", + length(local.data_mounts_config) > 0 ? "--ora-data-mounts-json '${jsonencode(local.data_mounts_config)}'" : "", + "--swap-blk-device /dev/disk/by-id/google-swap", + var.ora_swlib_bucket != "" ? "--ora-swlib-bucket ${var.ora_swlib_bucket}" : "", + var.ora_version != "" ? "--ora-version ${var.ora_version}" : "", + var.ora_backup_dest != "" ? "--backup-dest ${var.ora_backup_dest}" : "", + var.ora_db_name != "" ? "--ora-db-name ${var.ora_db_name}" : "", + var.ora_db_container != "" ? "--ora-db-container ${var.ora_db_container}" : "", + var.ntp_pref != "" ? "--ntp-pref ${var.ntp_pref}" : "", + var.ora_release != "" ? "--ora-release ${var.ora_release}" : "", + var.ora_edition != "" ? "--ora-edition ${var.ora_edition}" : "", + var.ora_listener_port != "" ? "--ora-listener-port ${var.ora_listener_port}" : "", + var.ora_redo_log_size != "" ? "--ora-redo-log-size ${var.ora_redo_log_size}" : "", + var.db_password_secret != "" ? "--db-password-secret ${var.db_password_secret}" : "", + var.oracle_metrics_secret != "" ? "--oracle-metrics-secret ${var.oracle_metrics_secret}" : "", + var.install_workload_agent ? "--install-workload-agent" : "", + var.skip_database_config ? "--skip-database-config" : "", + var.ora_pga_target_mb != "" ? "--ora-pga-target-mb ${var.ora_pga_target_mb}" : "", + var.ora_sga_target_mb != "" ? "--ora-sga-target-mb ${var.ora_pga_target_mb}": "", + var.data_guard_protection_mode != "" ? "--data-guard-protection-mode '${var.data_guard_protection_mode}'": "" + ])) +} + resource "google_compute_instance" "control_node" { project = var.project_id name = "${var.control_node_name_prefix}-${random_id.suffix.hex}" machine_type = var.control_node_machine_type - zone = var.zone - + zone = var.zone1 + scheduling { max_run_duration { seconds = 604800 @@ -157,8 +232,7 @@ resource "google_compute_instance" "control_node" { } network_interface { - network = coalesce(var.network, var.subnetwork) - subnetwork = coalesce(var.subnetwork, var.network) + subnetwork = var.subnetwork1 subnetwork_project = local.project_id dynamic "access_config" { @@ -174,37 +248,16 @@ resource "google_compute_instance" "control_node" { metadata_startup_script = templatefile("${path.module}/scripts/setup.sh.tpl", { gcs_source = var.gcs_source - instance_name = module.compute_instance.instances_details[0].name - instance_zone = module.compute_instance.instances_details[0].zone - ip_addr = module.compute_instance.instances_details[0].network_interface[0].network_ip - asm_disk_config = jsonencode(local.asm_disk_config) - data_mounts_config = jsonencode(local.data_mounts_config) - swap_blk_device = "/dev/disk/by-id/google-swap" - ora_swlib_bucket = var.ora_swlib_bucket - ora_version = var.ora_version - ora_backup_dest = var.ora_backup_dest - ora_db_name = var.ora_db_name - ora_db_container = var.ora_db_container - ntp_pref = var.ntp_pref - ora_release = var.ora_release - ora_edition = var.ora_edition - ora_listener_port = var.ora_listener_port - ora_redo_log_size = var.ora_redo_log_size - db_password_secret = var.db_password_secret - install_workload_agent = var.install_workload_agent - oracle_metrics_secret = var.oracle_metrics_secret - skip_database_config = var.skip_database_config - ora_pga_target_mb = var.ora_pga_target_mb - ora_sga_target_mb = var.ora_sga_target_mb + database_vm_nodes_json = jsonencode(local.database_vm_nodes) + common_flags = local.common_flags deployment_name = var.deployment_name - data_guard_protection_mode = var.data_guard_protection_mode }) metadata = { enable-oslogin = "TRUE" } - depends_on = [module.compute_instance] + depends_on = [google_compute_instance_from_template.database_vm] } output "control_node_log_url" { @@ -212,3 +265,7 @@ output "control_node_log_url" { value = "https://console.cloud.google.com/logs/query;query=resource.labels.instance_id%3D${urlencode(google_compute_instance.control_node.instance_id)};duration=P30D?project=${urlencode(var.project_id)}" } +output "database_vm_names" { + description = "Names of the created database VMs from instance templates" + value = [for vm in google_compute_instance_from_template.database_vm : vm.name] +} diff --git a/terraform/scripts/setup.sh.tpl b/terraform/scripts/setup.sh.tpl index 6fbde4025..bb8226062 100644 --- a/terraform/scripts/setup.sh.tpl +++ b/terraform/scripts/setup.sh.tpl @@ -1,7 +1,5 @@ #!/bin/bash -set -Eeuo pipefail - control_node_name="$(curl -s http://metadata.google.internal/computeMetadata/v1/instance/name -H 'Metadata-Flavor: Google')" # The zone value from the metadata server is in the format 'projects/PROJECT_NUMBER/zones/ZONE'. # extracting the last part @@ -47,6 +45,31 @@ EOF done } +send_ansible_completion_status() { + exit_code=$1 + if [[ $exit_code -eq 0 ]]; then + state="ansible_completed_success" + else + state="ansible_completed_failure" + fi + + timestamp=$(date --rfc-3339=seconds) + payload=$(cat <