Migrate Public OpenSearch to VPC OpenSearch
This page describes how to migrate an existing AWS OpenSearch Service cluster from a public endpoint configuration to a VPC-private deployment. This migration applies to customers upgrading from Qrvey v8 or earlier, where the AWS OpenSearch domain was deployed with a publicly accessible endpoint and all traffic between the EKS cluster and the OpenSearch domain traveled through a NAT Gateway.
Qrvey supports deploying the AWS OpenSearch Service domain in private VPC subnets using the opensearch_config variable. For new deployment setup, see Deployment on AWS.
Overview
In Qrvey v8 and earlier, the AWS OpenSearch cluster was deployed with a public endpoint by default. This meant all traffic between the EKS cluster and the OpenSearch domain traveled through a NAT Gateway, incurring data-transfer costs and exposing the endpoint outside the VPC.
Starting in v9.3, Qrvey supports migrating the AWS OpenSearch Service domain to private subnets of your VPC. This eliminates NAT Gateway traversal and makes the domain accessible only from within the VPC.
Key Benefits
- Cost reduction — Eliminates NAT Gateway data-transfer charges for OpenSearch traffic.
- Security — The domain is only accessible from within the VPC (no public endpoint).
- Encryption — The system enforces TLS v1.2. Encryption at rest and node-to-node encryption are enabled by default.
- Low-downtime migration — Both clusters can coexist during the migration window, allowing the platform to continue operating.
IAM Policy Dual-Cluster Compatibility
During the migration window, the old public domain and the new VPC domain coexist. The IAM policies (elasticsearch_policy and opensearch_policy) include Resource ARNs for both domains, ensuring pods can access either cluster without permission errors.
When migration is complete and the old domain is removed, the old ARN entries become inert (no matching resource). There is no need to immediately remove them.
Before You Begin
- The existing environment must have a working public AWS OpenSearch domain.
opensearch_config.enabledmust be set totrueinconfig.json.elasticsearch.hostmust be set to the current public domain endpoint (non-empty).- Ensure sufficient EBS storage on the new domain to hold all existing indices.
Migrate a Public OpenSearch Domain to VPC OpenSearch
Step 1: Update config.json
Add the opensearch_config block while keeping the existing elasticsearch block intact:
{
"variables": {
"elasticsearch": {
"host": "https://search-qrvey-xxxxx-xxxxxxxxxx.us-west-2.es.amazonaws.com",
"cluster_name": "qrvey-xxxxx",
"auth_user": "",
"auth_password": ""
},
"opensearch_config": {
"enabled": true,
"instance_type": "r6g.large.search",
"instance_count": 2,
"volume_size": 100
}
}
}
Important: Do not remove the
elasticsearchblock during this step. Both clusters must coexist during migration.
Step 2: Deploy the VPC Domain
Run the apply command to create the VPC OpenSearch domain without triggering migration yet:
docker run --platform=linux/amd64 -v $(pwd)/config.json:/app/qrvey/config.json -it --rm qrvey.azurecr.io/qrvey-terraform-aws:${qrvey_version} apply
This command performs the following tasks:
- Creates the VPC OpenSearch domain and all supporting infrastructure.
- Updates the platform ConfigMap to point to the new VPC domain.
- Restarts services to pick up the new endpoint.
After this step, the platform starts writing new data to the VPC domain. However, historical data from the old public domain is not yet available.
Step 3: Run the Migration
Trigger the data migration from the old public domain to the new VPC domain using the --migrate-opensearch flag:
docker run --platform=linux/amd64 -v $(pwd)/config.json:/app/qrvey/config.json -it --rm qrvey.azurecr.io/qrvey-terraform-aws:${qrvey_version} apply --migrate-opensearch
This command performs the following tasks:
- Creates the S3 snapshot bucket and IAM snapshot role (if not already present).
- Deploys a Kubernetes Job that runs the migration script.
- Takes a snapshot of all indices from the old public cluster (stored in S3) and restores them to the new VPC cluster.
The command waits for the job to complete (timeout: 2 hours) and streams logs to the terminal.
To monitor progress separately:
kubectl logs -f job/<migration-job-name> -n qrveyapps
Note: Previously loaded data is not available in the platform until this step completes. Loading data for new datasets is also blocked during migration.
Step 4: Verify the Migration
After the job completes, verify that the indices are present on the new cluster:
# Get the new domain endpoint from Terraform outputs
docker run --platform=linux/amd64 -v $(pwd)/config.json:/app/qrvey/config.json -it --rm qrvey.azurecr.io/qrvey-terraform-aws:${qrvey_version} output
# Check indices (run from within the VPC or cluster)
curl -s "https://vpc-qrvey-xxxxx-vpc-xxxxxxxxxx.us-west-2.es.amazonaws.com/_cat/indices?v&s=index"
Step 5: Remove the Old Domain Reference
-
After verifying the indices, clear the
elasticsearchvalues inconfig.json:{
"variables": {
"elasticsearch": {
"host": "",
"cluster_name": "",
"auth_user": "",
"auth_password": ""
},
"opensearch_config": {
"enabled": true,
"instance_type": "r6g.large.search",
"instance_count": 2,
"volume_size": 100
}
}
} -
Run the apply command:
docker run --platform=linux/amd64 -v $(pwd)/config.json:/app/qrvey/config.json -it --rm qrvey.azurecr.io/qrvey-terraform-aws:${qrvey_version} apply -
After confirming the migration was successful, remove the old public OpenSearch domain:
a. Delete the old domain from the AWS Console or its Terraform/CloudFormation stack.
b. Confirm that
elasticsearch.hostis cleared inconfig.json.The IAM policies still contain ARN entries for the old domain. This is harmless because the old domain no longer exists.
Entrypoint Flags Reference
--migrate-opensearch
Command: docker run ... apply --migrate-opensearch
Triggers the OpenSearch S3 snapshot migration job. Only runs when both conditions are met:
opensearch_config.enabled = trueelasticsearch.hostis set (non-empty)
The entrypoint performs the following tasks:
- Removes previous migration jobs from the Terraform state.
- Deletes pre-existing migration jobs from the cluster.
- Runs
terraform applyto create the migration job (timeout: two hours). - Streams job logs to the terminal in real-time.
--skip-all-hooks
Command: docker run ... apply --skip-all-hooks
Skips all pre/post hooks, validations, restarts, and cleanup. Equivalent to passing all of the following flags at once:
--skip-restart--skip-data-sync-jobs--skip-cleanup-orphans--skip-workflows-hooks
Additionally skips:
- Network CIDR validation
- EC2 instance quota validation
- Pre-apply hooks (suspend cronjobs, disable data sync)
- Post-apply hooks (translation sync, permissions, re-enable data sync)
- Rollout restart
- Helm deployment readiness wait
Use this flag when running targeted applies (--target) where you only need Terraform to apply specific resources without the full deployment lifecycle:
# Example: Apply only the general configmap, skip everything else
docker run ... apply --target=kubectl_manifest.general_configmap --skip-all-hooks
Terraform Outputs
The following outputs are available after deploying with opensearch_config.enabled = true:
| Output | Description |
|---|---|
opensearch_domain_endpoint | The HTTPS endpoint of the VPC OpenSearch domain |
opensearch_domain_arn | The ARN of the VPC OpenSearch domain |
opensearch_domain_name | The domain name (qrvey-<deployment_id>-vpc) |
opensearch_snapshot_bucket | The S3 bucket used for migration snapshots |
opensearch_snapshot_role_arn | The IAM role ARN used by OpenSearch for S3 access |
All outputs return empty strings when opensearch_config.enabled = false.