Certification track: Professional Cloud Database Engineer (PCDE)
Elasticsearch on GKE Autopilot
Elasticsearch is an open-source distributed search and analytics engine based on Apache Lucene. This module deploys a single-node Elasticsearch cluster on GKE Autopilot on top of the App_GKE foundation, which provisions and manages the shared Google Cloud and Kubernetes infrastructure.
This guide focuses on the cloud services Elasticsearch uses and how to explore and operate them from the Google Cloud Console and the command line. For the mechanics common to every GKE application — Workload Identity, ingress, autoscaling, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC Service Controls, backups, and the deployment lifecycle — refer to the App_GKE foundation guide rather than repeating them here.
What Elasticsearch costs on RAD, and how that compares
Elasticsearch on RAD's GKE Autopilot module costs about US$136.37 a month. A self-managed server is cheaper in cash, but then you run the database, backups, security patches and upgrades yourself. Figures are as at 8 October 2026; sources are listed at the end of this section.
If you only need it occasionally — studying, a demo, a seasonal business — a RAD-managed project can be deleted and restored within 30 days for a handful of credits, so that US$136.37 becomes a few dollars a month instead. See Pause it for free, below.
What you pay on RAD
| In a Google Cloud project you own | In a project RAD manages for you | |
|---|---|---|
| Module fee, once per deployment | 110 credits (US$11 at the top-up price) | 99 credits (10% lower) |
| Build time | A few credits per build | The same |
| Google Cloud running cost | Billed by Google to your own billing account, about US$136.37 a month for the default configuration (table below) | Metered hourly in credits; RAD publishes 105 credits a day, about 3,150 a month (about US$315 at the top-up price) |
| Who owns what | You own the project and its billing; RAD deploys and updates it | RAD owns the project, with guardrails, quotas and budget alerts; you get console access, and a minimum purchased balance is held in reserve |
10 credits cost US$1 on a one-off top-up, and less on a monthly plan. In a project RAD manages, the database, file server and network are shared by every application in the project, so a second application does not add a second database.
Default running cost in your own project (us-central1, Google list prices):
| Resource (module default) | Per month |
|---|---|
| GKE Autopilot pod, 2x vCPU / 4 GiB | US$79.35 |
| Cloud SQL for PostgreSQL, 1 vCPU / 3.75 GB, zonal, 10 GB SSD | US$51.02 |
| Cloud NAT and networking | US$5 |
| Cloud Storage (add-ons, backups) | US$1 |
| GKE cluster management fee | shared across every GKE app in the project; $0 if this is your only cluster, else ~73 |
| Total | about US$136.37 |
Note: this module's Overview below states it is entirely self-contained, with no Cloud SQL and no Cloud Storage bucket. The Cloud SQL and Cloud Storage lines above appear to be inherited from the shared cost template rather than real spend for this module — flagged here for verification against the live Terraform rather than quietly dropped.
How it compares
- Elastic (the company behind Elasticsearch) sells Elastic Cloud on a resource-based, pay-as-you-go basis with no flat published price to cite here, so the honest comparison is a bare VPS: a Hetzner CPX22 or DigitalOcean 2 vCPU/4 GB instance runs about US$24 a month, or a GCP Compute Engine e2-standard-2 about US$49 — close to this module's own 2 vCPU/4 GiB default — plus your own time running and patching Elasticsearch, JVM-tuning it, and managing its storage yourself.
- A single self-managed node has no automated snapshotting out of the box; RAD's module runs it as a GKE StatefulSet with a persistent volume, which survives a pod restart, but you still need to export snapshots for anything you cannot afford to lose (see Pay only while you use it, below).
Pause it for free: delete a RAD-managed project, restore it when you need it
If Elasticsearch runs in a project RAD manages for you, you have a second option that goes well beyond scaling to zero: delete the whole project, and restore it within 30 days for close to nothing. This suits Elasticsearch you only need occasionally — studying, a demo, a seasonal business — far better than running it continuously.
- How it works. Deleting a RAD-managed project unlinks its billing first, then asks Google to delete the project. Google does not remove the project immediately: it keeps it, recoverable, for 30 days. Because billing is already unlinked, nothing is charged while it waits. Unlike deleting one module, this does not tear down the GKE pod or its persistent disk one by one — the whole project, and everything in it, simply stops.
- Restoring costs a handful of credits, not a rebuild. Within 30 days, the project's owner can restore it. RAD asks Google to undelete the project and reattaches its billing account, then asks you to run Update on each deployment to confirm everything came back. Because nothing was individually destroyed, that Update finds the same resources already there — it is a check, not a rebuild, and an Update never charges the module fee again. That costs a handful of credits (under US$1) in total for a typical Elasticsearch-sized deployment chain, against the 104 credits (US$10.35) a full redeploy costs.
- So a month of occasional use can cost a few dollars, not US$136.37. Deploy Elasticsearch, use it for a while, delete the project. Restore it next time you want it, confirm with Update, and delete it again when you're done. You pay only for the module fee once, the builds, and whatever hours Elasticsearch was actually live.
- What this needs. You must own the project (not a bring-your-own one RAD only manages billing for), and you restore it yourself within the 30 days — after that, Google deletes it for good. Restoring is admitted like creating a new project: your purchased credit balance must still clear the tier's floor (100 credits for the sandbox tier most study and demo use fits). Google says most services are fully working again within 36 hours of a restore.
- One real gap: the index itself. Elasticsearch keeps its data on a Persistent Disk, not a database with its own automated backup — nothing here copies that disk out for you. Export a snapshot before deleting if you want your indices back, even for a 30-day pause.
Pay only while you use it, the other way: delete and redeploy
GKE keeps at least one pod running, so it never scales to zero on its own. The option above only applies to a RAD-managed project; in your own project, or once the 30-day window has passed, the way to stop paying is to delete the deployment and deploy it again when you need it.
- What a redeploy costs. The module fee again plus the builds: about 114 credits (US$11.45), and about an hour, because RAD recreates the project and shared services before Elasticsearch. Deleting saves money once Elasticsearch would otherwise sit unused for about 3 days or more in your own project (about US$4.55 a day), or about 1 day or more in a RAD-managed one (105 credits a day).
- Delete everything Elasticsearch uses. Elasticsearch's only running cost is its own GKE pod and its persistent disk — there is no separate Cloud SQL instance despite how a generic cost template might list one (see the note on the running-cost table below) — so deleting it stops nearly all of its own cost right away.
- Keep your data first. Elasticsearch's indexed data lives entirely on its own Persistent Disk volume, which is deleted with the deployment. Export an index snapshot to a Cloud Storage bucket outside the project before deleting if you want to keep it — GKE's PersistentVolumes are not backed up automatically.
- Note your settings. RAD does not recreate a deleted deployment for you; you enter the settings again when you deploy.
Lab sessions and Managed Environments
- Lab sessions, for training. A trainer runs a session for a class. Each participant gets Elasticsearch in their own Google Cloud project for 15 minutes to 24 hours, within an allowance the trainer sets. Either the trainer funds every place, or each participant pays for their own. Everything is deleted when the session ends and unused credits go back to the trainer.
- Managed Environments, for consultancies. A partner runs Elasticsearch for a client from a ring-fenced wallet it funds, and settles with the client directly. If the wallet runs low, billing pauses and the data is kept, so nobody receives an unexpected charge. At the end the partner hands the project over and the deployments become the client's own.
Credits can be bought in more than 20 currencies, including XAF, XOF, NGN, GHS, KES and ZAR, by card, bank transfer or mobile money.
Sources (8 October 2026): Google Cloud list prices from the Cloud Billing Catalog API; RAD's own fees and daily-credit estimates from radmodules.dev/pricing; Delete and restore projects for the pause/restore mechanics and window.
1. Overview
Elasticsearch runs as a StatefulSet workload. The deployment wires together a focused set of Google Cloud services:
| Capability | Google Cloud service | Notes |
|---|---|---|
| Compute | GKE Autopilot | Elasticsearch StatefulSet pod, 2 vCPU / 4 GiB by default |
| Persistent storage | Persistent Disk (SSD) | 30 GiB PVC at /usr/share/elasticsearch/data, survives pod restarts |
| Secrets | Secret Manager | Optional secrets injected as environment variables |
| Ingress | Cloud Load Balancing | LoadBalancer Service on port 9200 for cross-namespace access |
| Image registry | Artifact Registry | Elasticsearch image mirrored from Elastic's registry |
No Cloud SQL, no Redis, no GCS buckets — Elasticsearch is entirely self-contained; all data lives in its PVC.
Sensible defaults worth knowing up front:
- StatefulSet with PVC is the required workload type. Setting
stateful_pvc_enabled = true(which auto-selects StatefulSet) gives each pod a dedicated SSD volume that survives restarts, rolling updates, and node evictions. Losing the PVC means losing all indexed data. - Single-node mode (
discovery.type = single-node) is enforced at plan time —max_instance_countis fixed at1. Increasing it without changing the discovery type creates isolated single-node clusters, not a distributed cluster. - JVM heap must be at most half of
memory_limit. Elasticsearch needs the other half for OS page cache and JVM overhead. Violating this ratio triggers OOM kills under search load. A plan-time precondition enforces this rule. cluster_nameis immutable after first index. Changing it after documents are indexed causes Elasticsearch to treat the existing PVC data as foreign and reject it. Choose a meaningful name before the first deploy.- Termination grace period is 120 seconds to allow Elasticsearch to flush in-memory segment writes to disk cleanly before the pod is forcibly removed.
- This module is the required dependency for
RAGFlow_GKE. After deployment, theelasticsearch_endpointoutput (http://<external-ip>:9200) is passed to RAGFlow'selasticsearch_hostsvariable.
2. Google Cloud Services & How to Explore Them
All commands assume you have run
gcloud container clusters get-credentials <cluster> --region <region> --project <project>
and that PROJECT, REGION, and NAMESPACE are set. The namespace and other
identifiers are reported in the deployment Outputs.
A. GKE Autopilot — the Elasticsearch workload
Elasticsearch runs as a Kubernetes StatefulSet on GKE Autopilot. Autopilot bills for the CPU and memory the pod actually requests. A PodDisruptionBudget keeps the pod available during node upgrades.
- Console: Kubernetes Engine → Workloads → select the Elasticsearch StatefulSet to see pod status, events, and the PVC attachment. Kubernetes Engine → Services & Ingress shows the LoadBalancer external IP on port 9200.
- CLI:
kubectl get statefulsets,pods,svc,pvc -n "$NAMESPACE"
kubectl logs -n "$NAMESPACE" <pod-name> --tail=100
kubectl describe pvc -n "$NAMESPACE" # confirm PVC is Bound
See App_GKE for Autopilot scaling, PDB, and the StatefulSet lifecycle.
B. Persistent Disk — index storage
All Elasticsearch indexes and shard files reside on a Persistent Disk (SSD) backed
PersistentVolumeClaim. The PVC is provisioned by the standard-rwo (or premium-rwo)
StorageClass and mounted at /usr/share/elasticsearch/data.
- Console: Kubernetes Engine → Storage → PersistentVolumeClaims to see size and binding state. Compute Engine → Disks shows the underlying disk.
- CLI:
kubectl get pvc -n "$NAMESPACE"
kubectl describe pvc -n "$NAMESPACE" <pvc-name>
# Check disk usage inside the pod:
kubectl exec -n "$NAMESPACE" <pod-name> -- df -h /usr/share/elasticsearch/data
Monitor disk-fill watermarks — Elasticsearch switches indexes to read-only at 95% capacity by default. Plan PVC size with 50–100% headroom above expected data volume.
C. Elasticsearch service endpoint
The Elasticsearch HTTP API is exposed on port 9200 through a Kubernetes LoadBalancer
Service. This external IP is the elasticsearch_endpoint output and is passed directly
to RAGFlow and other consumers.
- Console: Kubernetes Engine → Services & Ingress → select the service for the external IP and port mapping.
- CLI:
kubectl get svc -n "$NAMESPACE"
# Verify the cluster is up and healthy:
curl http://<elasticsearch-endpoint>/_cluster/health?pretty
curl http://<elasticsearch-endpoint>/_cat/indices?v
# List all indexes:
curl http://<elasticsearch-endpoint>/_cat/indices?h=index,docs.count,store.size
D. Secret Manager
Optional secrets (such as custom credentials or API keys) are stored in Secret Manager and injected as environment variables at pod startup.
- Console: Security → Secret Manager.
- CLI:
gcloud secrets list --project "$PROJECT"
gcloud secrets versions access latest --secret=<secret-name> --project "$PROJECT"
See App_GKE for the Secret Store CSI integration and rotation.
E. Artifact Registry — container image
The official docker.elastic.co/elasticsearch/elasticsearch:<version> image is
mirrored into Artifact Registry before each deployment (when enable_image_mirroring = true). This avoids Elastic registry rate limits and keeps images within your VPC
perimeter.
- Console: Artifact Registry → select the repository for tags, digests, and vulnerability scan results.
- CLI:
gcloud artifacts repositories list --project "$PROJECT" --location "$REGION"
gcloud artifacts docker images list "$REGION-docker.pkg.dev/$PROJECT/<repo>" --project "$PROJECT"
F. Networking & ingress
The LoadBalancer Service exposes Elasticsearch on port 9200. A static external IP can
be reserved. Cloud Armor can be layered on the Ingress backend for WAF protection (though
for Elasticsearch, network-level restrictions — firewall rules and enable_network_segmentation
— are usually preferred over browser-facing WAF rules).
- Console: Network services → Load balancing; VPC network → IP addresses.
- CLI:
kubectl get ingress,svc -n "$NAMESPACE"
gcloud compute addresses list --project "$PROJECT"
See App_GKE for custom domains, static IPs, and Cloud Armor details.
G. Cloud Logging & Monitoring
Pod stdout/stderr (Elasticsearch logs) flow to Cloud Logging. GKE metrics flow to Cloud
Monitoring. Optional uptime checks can probe /_cluster/health.
- Console: Logging → Logs Explorer; Monitoring → Dashboards / Alerting.
- CLI:
gcloud logging read 'resource.type="k8s_container" AND resource.labels.namespace_name="'"$NAMESPACE"'"' \
--project "$PROJECT" --limit 50
3. Elasticsearch Application Behaviour
- Single-node operation. The module deploys Elasticsearch with
discovery.type = single-node, which disables cluster coordination. This is the correct and supported mode for a one-pod deployment. Do not increasemax_instance_countbeyond1without also overridingdiscovery.type— running multiple single-node clusters in parallel results in data isolation, not distribution. - Persistent index storage. All data lives in the PVC at
/usr/share/elasticsearch/data. The path is enforced — changingstateful_pvc_mount_pathwithout updating thepath.datasetting causes writes to land in the ephemeral container layer and be silently lost on the next pod restart. - JVM heap sizing.
ES_JAVA_OPTSis automatically set to-Xms<heap> -Xmx<heap>from thees_java_heapvariable. Elasticsearch's heap must be no more than half ofmemory_limit— the rest is consumed by Lucene's off-heap segment cache, JVM metaspace, and native memory. A plan-time precondition enforces this rule. - mmap disabled. GKE Autopilot does not allow privileged
initContainersto raisevm.max_map_count. Accordingly, the module setsnode.store.allow_mmap = false, which prevents mmap-based memory-mapped files and incurs a minor sequential-read penalty compared to mmap mode. This is a known GKE Autopilot constraint. - Health probes are hardcoded to TCP, unconditionally.
elasticsearch.tfsets both the startup probe and the liveness probe totype = "TCP"regardless ofenable_xpack_security— a simple port-open check on 9200, not an HTTP request to/_cluster/health. The startup probe allows up to 60 attempts at a 10s period (≈10 minutes total, on top of a 30s initial delay) to accommodate cold-node provisioning, image pull, and JVM/shard-recovery boot time; the liveness probe uses a 60s initial delay, 30s period, and 3-attempt threshold. This is deliberate: an HTTP probe would return401 Unauthorizedonce X-Pack security is enabled, and would fail before Elasticsearch's own bootstrap checks (implied passed once port 9200 is open) complete. Thestartup_probe_configandhealth_check_configvariables have no effect on this module —App_GKE(modules.tf) always prefers the app-hardcoded probe over the value forwarded from these variables, so there is nothing for an operator to configure or override here, including whenenable_xpack_security = true. - X-Pack security is disabled by default. With
enable_xpack_security = false, the HTTP endpoint accepts unauthenticated requests. Any caller who can reach port 9200 can read, write, or delete all indexes. This is acceptable for a cluster accessible only within the VPC; enable it for public or multi-tenant deployments. - X-Pack auth is fully automated when enabled. Setting
enable_xpack_security = truetriggerselasticsearch_auth.tfto generate a random password, store it in Secret Manager assecret-<prefix>-<application_name>-elastic-password, and inject it into the container asELASTIC_PASSWORD— the official image bootstraps theelasticsuperuser with this value on first start of an empty data directory. No operator setup is required. The username (alwayselastic) and the secret ID are exposed via theelasticsearch_usernameandelasticsearch_password_secret_idoutputs (see Outputs) — retrieve the password withgcloud secrets versions access latest --secret=<elasticsearch_password_secret_id>. Consuming modules such as RAGFlow or Zammad can reference the same secret ID deterministically to authenticate against this cluster. - Cluster name is baked into node identity. Changing
cluster_nameafter the first index is created causes Elasticsearch to treat the existing PVC data as foreign and fail to start. A rename requires destroying the PVC and re-indexing all documents. - No initialization jobs are required. Elasticsearch bootstraps itself on first start. There is no database or user creation step.
- Termination grace period. Kubernetes waits 120 seconds after sending SIGTERM before forcibly killing the pod. This allows Elasticsearch to flush translog entries and close shards cleanly, avoiding a potentially slow recovery from the translog on the next startup.
4. Configuration Variables
Variables are grouped exactly as they appear on the deployment platform. Only settings specific to or notable for Elasticsearch are listed; every other input is inherited from App_GKE with its standard behaviour and defaults.
Group 1 — Project & Identity
| Variable | Default | Description |
|---|---|---|
project_id | (required) | Target Google Cloud project. |
region | us-central1 | Region for the workload and regional resources. |
Group 2 — Deployment Environment
| Variable | Default | Description |
|---|---|---|
tenant_id | demo | Short suffix that makes resource names unique per environment. |
support_users | [] | Emails granted project access and monitoring alerts. |
resource_labels | {} | Labels applied to all resources for cost/ownership tracking. |
Group 3 — Application Identity
| Variable | Default | Description |
|---|---|---|
application_name | elasticsearch | Base name for resources. Do not change after first deploy. |
application_display_name | Elasticsearch | Friendly name shown in the Console. |
application_description | (set) | Workload description annotation. |
application_version | 8.13.4 | Elasticsearch image version tag; increment to roll out a new version. |
cluster_name | ragflow | Sets cluster.name inside Elasticsearch. Immutable after first index — rename requires full PVC destroy and re-index. |
Group 4 — Runtime & Scaling
| Variable | Default | Description |
|---|---|---|
deploy_application | true | Set false to provision the namespace and IAM without deploying the workload. |
container_image_source | prebuilt | Always prebuilt — Elasticsearch uses the official Elastic image. |
container_image | "" | Override image URI; leave empty to use the official Elasticsearch image. |
enable_image_mirroring | true | Mirror the image from Elastic's registry into Artifact Registry before deploy. |
min_instance_count | 1 | Keep at 1 for single-node mode. |
max_instance_count | 1 | Fixed at 1 — enforced at plan time. Increasing without overriding discovery.type creates isolated clusters. |
container_port | 9200 | Elasticsearch HTTP API port. |
cpu_limit | 2000m | CPU per pod. 2 vCPU is the recommended baseline; scale to 4000m for heavy indexing. |
memory_limit | 4Gi | Memory per pod. Must be at least 2× es_java_heap. |
es_java_heap | 512m | JVM heap (-Xms and -Xmx). Must be ≤ half of memory_limit — enforced at plan time. |
enable_xpack_security | false | Enable X-Pack security (authentication). When false, port 9200 is unauthenticated. |
enable_vertical_pod_autoscaling | false | Let Autopilot tune resource requests automatically. |
timeout_seconds | 300 | Load balancer backend timeout (0–3600 seconds). |
termination_grace_period_seconds | 120 | Seconds Kubernetes waits after SIGTERM for segment flush before force-killing. |
deployment_timeout | 1800 | Seconds Terraform waits for the StatefulSet rollout to complete. |
Group 5 — Environment Variables & Secrets
| Variable | Default | Description |
|---|---|---|
environment_variables | {} | Extra env vars merged into the container after the auto-injected Elasticsearch settings; can override any auto-set value (e.g., discovery.type, ES_JAVA_OPTS). |
secret_environment_variables | {} | Map of env var → Secret Manager secret name. |
secret_rotation_period | 2592000s | Secret Manager rotation notification cadence. |
secret_propagation_delay | 30 | Seconds to wait after secret creation before proceeding. |
Group 6 — GKE Backend & Cluster
| Variable | Default | Description |
|---|---|---|
gke_cluster_name | "" | GKE cluster name; leave empty for auto-discovery. |
namespace_name | "" | Kubernetes namespace; auto-generated when empty. |
workload_type | null | Auto-resolves to StatefulSet when stateful_pvc_enabled = true. |
service_type | LoadBalancer | LoadBalancer is required for cross-namespace access from RAGFlow. Use ClusterIP only when both workloads share the same namespace. |
session_affinity | None | Session affinity. None is correct for Elasticsearch (stateless HTTP). |
enable_network_segmentation | false | Create Kubernetes NetworkPolicy resources to restrict ingress/egress. |
termination_grace_period_seconds | 120 | Also shown in Group 4; set once here. |
deployment_timeout | 1800 | Also shown in Group 4; set once here. |
network_tags | ["nfsserver"] | GKE node/pod network tags for firewall rules. |
Group 7 — StatefulSet & Persistence
Data persistence is critical — all Elasticsearch index data lives in the PVC.
| Variable | Default | Description |
|---|---|---|
stateful_pvc_enabled | null | Set true for all deployments — auto-selects StatefulSet. Without a PVC, all indexes are lost on every pod restart. |
stateful_pvc_size | 30Gi | PVC size. Plan with 50–100% headroom; Elasticsearch goes read-only at 95% disk use. |
stateful_pvc_mount_path | /usr/share/elasticsearch/data | Do not change — must match Elasticsearch's path.data setting. |
stateful_pvc_storage_class | standard-rwo | StorageClass. Use premium-rwo for high-throughput vector kNN indexing. StorageClass cannot be changed after PVC creation. |
stateful_headless_service | null | Set true to create a headless Service for stable pod DNS entries. |
stateful_pod_management_policy | null | OrderedReady or Parallel. |
stateful_update_strategy | null | RollingUpdate or OnDelete. |
stateful_fs_group | 0 | Inert for this module. elasticsearch.tf and main.tf hardcode stateful_fs_group = 1000 (Elasticsearch's UID/GID) unconditionally on the call into App_GKE; this variable's value is never read. Declared for App_GKE interface compatibility only — no operator action is needed. |
Group 8 — Resource Quota
| Variable | Default | Description |
|---|---|---|
enable_resource_quota | false | Cap namespace CPU/memory/object counts. |
quota_cpu_requests / quota_cpu_limits | "" | CPU quota strings (e.g., "4000m"). |
quota_memory_requests / quota_memory_limits | "" | Must use binary units (4Gi, 8192Mi) — bare integers are read as bytes by Kubernetes and block all pod scheduling. |
Group 9 — Reliability Policies
| Variable | Default | Description |
|---|---|---|
enable_pod_disruption_budget | true | Protect availability during node upgrades. |
pdb_min_available | 1 | For a single-node cluster, "1" prevents any voluntary disruption — scale to 2+ replicas before reducing this. |
enable_topology_spread | false | Spread pods across zones. |
Group 10 — Observability & Health
| Variable | Default | Description |
|---|---|---|
startup_probe_config | HTTP /_cluster/health, 18 retries | Inert for this module. The module always deploys a hardcoded TCP startup probe (30s initial delay, 60-attempt threshold) regardless of this variable's value — no operator action needed, including when enable_xpack_security = true. See Application Behaviour. |
health_check_config | HTTP /_cluster/health, 3 retries | Inert for this module. The module always deploys a hardcoded TCP liveness probe (60s initial delay, 3-attempt threshold) regardless of this variable's value — no operator action needed. |
uptime_check_config | disabled | Optional Cloud Monitoring uptime check against /_cluster/health. |
alert_policies | [] | Optional metric alert policies. |
Group 11 — Jobs & Scheduled Tasks
| Variable | Default | Description |
|---|---|---|
initialization_jobs | [] | Kubernetes Jobs run before the Elasticsearch pod starts. Not required for Elasticsearch — it bootstraps itself. |
cron_jobs | [] | Recurring Kubernetes CronJobs (e.g., index lifecycle management tasks). |
additional_services | [] | Sidecar or helper GKE services deployed alongside Elasticsearch. |
Group 12 — CI/CD & GitHub Integration
Standard App_GKE Cloud Build / Cloud Deploy integration — see
App_GKE. Key inputs: enable_cicd_trigger,
github_repository_url, github_token, enable_cloud_deploy,
enable_binary_authorization, binauthz_evaluation_mode.
Group 13 — Filesystem (NFS)
NFS is not required for Elasticsearch — all data lives in the StatefulSet PVC.
The NFS variables are present for foundation-interface compatibility but default to
disabled (enable_nfs = false). See App_GKE for details.
Group 14 — Cloud Storage & Artifact Registry
Cloud Storage buckets are not required for Elasticsearch (create_cloud_storage = false by default). The Artifact Registry variables control the image retention policy
for the mirrored Elasticsearch image:
| Variable | Default | Description |
|---|---|---|
max_images_to_retain | 7 | Maximum recent images to keep in Artifact Registry. |
delete_untagged_images | true | Automatically delete untagged images. |
image_retention_days | 30 | Age after which images are eligible for deletion. |
manage_storage_kms_iam / enable_artifact_registry_cmek | false | CMEK encryption options. |
Group 17 — Backup & Maintenance
| Variable | Default | Description |
|---|---|---|
backup_schedule | 0 2 * * * | Backup cron schedule (UTC). For Elasticsearch, consider native Elasticsearch Snapshots to GCS rather than OS-level backups. |
backup_retention_days | 7 | Retention in days; raise for production/compliance. |
Group 19 — Custom Domain, Static IP & Networking
| Variable | Default | Description |
|---|---|---|
enable_custom_domain | true | Provision Ingress for custom hostname + managed certificate. Only takes effect once application_domains is non-empty. |
application_domains | [] | Hostnames to serve. |
reserve_static_ip | true | Stable external IP across redeploys. |
network_tags | ["nfsserver"] | GKE pod network tags for firewall rules. |
Group 20 — Identity-Aware Proxy (IAP)
IAP is not recommended for Elasticsearch — use network-level controls
(enable_network_segmentation, firewall rules) instead. IAP variables are present
for completeness.
| Variable | Default | Description |
|---|---|---|
enable_iap | false | Require Google sign-in in front of Elasticsearch. |
iap_authorized_users / iap_authorized_groups | [] | Who may access. |
iap_oauth_client_id / iap_oauth_client_secret | "" | Required when IAP is enabled. |
iap_support_email | "" | Shown on the OAuth consent screen. |
Group 21 — Cloud Armor
| Variable | Default | Description |
|---|---|---|
enable_cloud_armor | false | Attach a Cloud Armor (WAF) policy to the Ingress backend. |
admin_ip_ranges | [] | CIDRs allowed privileged access. |
cloud_armor_policy_name | default-waf-policy | Policy name. |
enable_cdn | false | Not applicable for Elasticsearch. |
Group 22 — VPC Service Controls & Audit Logging
| Variable | Default | Description |
|---|---|---|
enable_vpc_sc | false | Enforce a VPC-SC perimeter (requires organization_id). |
vpc_cidr_ranges / vpc_sc_dry_run | (set) | Access level CIDRs / dry-run mode. |
enable_audit_logging | false | Detailed Cloud Audit Logs. |
5. Outputs
These values are returned on a successful deployment and are the quickest way to locate and explore the running resources.
| Output | Description |
|---|---|
elasticsearch_endpoint | Primary output for RAGFlow. http://<service_external_ip>:9200. Pass this to RAGFlow_GKE's elasticsearch_hosts variable. Returns null until the external IP is assigned. |
service_name | Kubernetes Service name. |
namespace | Namespace the workload runs in. |
service_cluster_ip | In-cluster ClusterIP. |
stage_service_cluster_ips | Map of ClusterIPs for stage-specific services (Cloud Deploy). |
service_external_ip | External LoadBalancer IP. |
api_url | Service URL. |
statefulset_name | Name of the StatefulSet resource. |
storage_buckets | Created Cloud Storage buckets (empty list — no buckets are provisioned). |
network_name / network_exists / regions | VPC network, presence, available regions. |
container_image / container_registry | Deployed image and Artifact Registry repo. |
monitoring_enabled / monitoring_notification_channels | Monitoring status and channels. |
initialization_jobs | Names of any setup jobs run before the workload. |
deployment_id / tenant_id / resource_prefix | Naming identifiers. |
project_id / project_number | Project identifiers. |
cicd_enabled / cicd_configuration | CI/CD status and details (repo, trigger, registry). |
github_repository_url / github_repository_owner / github_repository_name | Connected GitHub repository details. |
artifact_registry_repository / cloudbuild_trigger_name / cloudbuild_trigger_id | Registry and build trigger. |
kubernetes_ready | true when the cluster endpoint is available and all workload resources are deployed. false on the first apply of a new cluster — the CI/CD pipeline must re-run apply to complete the deployment. |
vpc_sc_enabled / vpc_sc_perimeter_name / vpc_sc_dry_run_mode | VPC-SC status. |
audit_logging_enabled / artifact_registry_cmek_enabled | Audit logging and CMEK status. |
elasticsearch_username | Elasticsearch superuser username. Always "elastic". Only meaningful when enable_xpack_security = true. |
elasticsearch_password_secret_id | Secret Manager secret ID holding the auto-generated elastic superuser password. Empty string when enable_xpack_security = false. |
6. Configuration Pitfalls & Sensible Defaults
Risk: Critical (data loss / outage / security) — High (service degraded) — Medium (cost or partial degradation) — Low (minor).
| Setting | Sensible value | Risk | Consequence if wrong |
|---|---|---|---|
stateful_pvc_enabled | true | Critical | Without a PVC all indexes are stored in the ephemeral pod filesystem and permanently lost on every restart, rolling update, or node eviction. |
stateful_pvc_mount_path | /usr/share/elasticsearch/data | Critical | Must match path.data. A mismatch silently writes indexes to the ephemeral layer — data is lost on each restart. |
cluster_name | set once | Critical | Immutable after first index. Renaming causes Elasticsearch to reject all PVC data as foreign; a full re-index is required. |
es_java_heap vs memory_limit | heap ≤ memory_limit / 2 | Critical | Heap exceeding half the container memory competes with Lucene's page cache; OOM kills occur under search load. Plan-time precondition enforces this. |
stateful_fs_group | n/a — hardcoded | n/a | The module always passes stateful_fs_group = 1000 (Elasticsearch's UID/GID) to App_GKE, regardless of this variable. Not an operator concern; listed here only because the variable exists on the interface. |
max_instance_count | 1 | Critical | Increasing without overriding discovery.type creates isolated single-node clusters. Enforced at plan time. |
quota_memory_requests / _limits | binary units (4Gi) | Critical | Bare integers are bytes and block all scheduling immediately. |
enable_xpack_security | true for production | High | With false, any caller who can reach port 9200 can read, write, or delete all indexes without credentials. |
stateful_pvc_size | size with 50–100% headroom | High | An undersized PVC triggers flood-stage watermark protection at 95% full; the index becomes read-only. |
stateful_pvc_storage_class | standard-rwo (or premium-rwo for production) | Medium | standard-rwo is adequate for typical search workloads; high-throughput vector kNN indexing benefits from premium-rwo. StorageClass cannot be changed after PVC creation. |
memory_limit | ≥ 2 × es_java_heap | Critical | Insufficient memory headroom triggers OOM kills during search/indexing. |
startup_probe_config / health_check_config | n/a — hardcoded to TCP | n/a | The module always deploys hardcoded TCP probes regardless of these variables' values, so an HTTP-vs-X-Pack mismatch cannot occur; there is nothing to override. Listed here only because the variables exist on the interface. |
enable_image_mirroring | true | Low | Disabling mirroring pulls directly from Elastic's registry; rate limits can cause intermittent deployment failures. |
application_version | 8.13.4 (or locked version) | Medium | Major version upgrades (7.x → 8.x) may require index compatibility checks; do not upgrade without reviewing the Elasticsearch migration guide. |
pdb_min_available vs min_instance_count | headroom | Medium | pdb_min_available = "1" with a single-pod cluster prevents any voluntary disruption (e.g., node upgrades) from proceeding. Scale to 2+ pods or accept the constraint. |
For the foundation behaviour referenced throughout — IAM and Workload Identity, autoscaling, ingress and certificates, CI/CD, Cloud Armor, IAP, Binary Authorization, VPC-SC, backups, and image mirroring — see App_GKE.
Related guides
- Hands-on lab: Elasticsearch on GKE Autopilot — deploy it step by step, with the console screens and commands at each stage.
- Deployed alongside MongoDB on GKE Autopilot, Meilisearch on GKE Autopilot, Qdrant on Google Cloud Run in the Shared Data Services solution.
Need RAD to do something it does not do yet? Request it on the roadmap, or vote on what is already there.