Migration Guide: DxEnterpriseSqlAg to DxSqlAg
This guide provides step-by-step instructions for manually migrating a DxEnterpriseSqlAg custom resource to a DxSqlAg custom resource.
This guide uses an in-place migration using the same resource identity (namespace/name). As part of that process, the v1 DxEnterpriseSqlAg must be deleted before creating the v2 DxSqlAg, which causes downtime.
Prerequisites
Before starting the migration, ensure you have:
kubectlinstalled and configured with access to your Kubernetes cluster.- Cluster permissions to read/write custom resources and manage pods/PVCs.
- A backup of your current
DxEnterpriseSqlAgresource. - A backup of your volumes.
- A downtime window for the migration.
- DxOperator v1 installed.
- DxOperator v2 installed.
- DxOperator v1 and v2 can be installed in the same Kubernetes cluster.
What Changed in v2
Key Structural Changes in the Custom Resource Definition (CRD)
DxOperator v2 introduces DxSqlAg, the successor to the v1 DxEnterpriseSqlAg custom resource. It adopts StatefulSets, adds support for Service Templates, moves several settings into a nested configuration, and removes some of the older template-based terminology.
At a high level, the v2 schema:
- Reorganizes top-level settings under nested configuration
- Replaces older
templatewording withstatefulSetSpecandpodSpec - Introduces a few new configuration options for finer control during and after migration
The main structural differences are outlined below.
| v1 → v2 | Notes |
|---|---|
spec.*Replicas → spec.sqlAgConfiguration.*Replicas | Moves all replica counts into nested availability group configuration. |
spec.availability* → spec.sqlAgConfiguration.availability* | Moves availability group settings into nested AG configuration. |
spec.createLoadBalancers → spec.sqlAgConfiguration.createLoadBalancers | Moves auto-generated load balancers into nested AG configuration. |
spec.template.spec → spec.statefulSetSpec.podSpec | Replaces the old pod-template-based structure with a StatefulSet-based structure. statefulSetSpec and podSpec also support metadata. |
N/A → spec.sqlAgConfiguration.disableModeSwitching | New field. During migration, set disableModeSwitching to true. After migration succeeds, it can be set back to false. |
N/A → spec.sqlAgConfiguration.disableTunnels | New field. Must be set to false for v1 to v2 migration. |
spec.template.spec.dxEnterpriseContainer.joinExistingCluster → spec.statefulSetSpec.podSpec.dxEnterpriseContainer.joinTarget | Replaces joinExistingCluster, which is now fully deprecated. |
Label Changes in v2
Some labels changed for v2.
| v1 → v2 | Notes |
|---|---|
dh2i.com/entity → dh2i.com/entity-name | Renamed to avoid label conflicts. |
dh2i.com/availability-mode → N/A | Availability modes managed internally by DxOperator. |
N/A → dh2i.com/active-vhost | Follows whichever pod is the active Vhost member (the primary replica). |
Other New v2 Features
- Scale Down: Shrink or expand the Availability Group according to your needs. See the scale-down guide for more information.
- ServiceTemplates: Define custom service configurations. See the service templates guide for more information.
- VolumeClaimConfiguration metadata: Add labels/annotations to DxOperator-managed PVCs.
Quick Reference: Field Mapping Table
Field Mapping Table
| v1 Path → v2 Path | Notes |
|---|---|
spec.synchronousReplicas → spec.sqlAgConfiguration.synchronousReplicas | Direct copy |
spec.asynchronousReplicas → spec.sqlAgConfiguration.asynchronousReplicas | Direct copy |
spec.configurationOnlyReplicas → spec.sqlAgConfiguration.configurationOnlyReplicas | Direct copy |
spec.availabilityGroupName → spec.sqlAgConfiguration.availabilityGroupName | Direct copy |
spec.availabilityGroupClusterType → spec.sqlAgConfiguration.availabilityGroupClusterType | Direct copy |
spec.availabilityGroupListenerPort → spec.sqlAgConfiguration.availabilityGroupListenerPort | Omit if 0 |
spec.availabilityGroupOptions → spec.sqlAgConfiguration.availabilityGroupOptions | Omit if null |
spec.createLoadBalancers → spec.sqlAgConfiguration.createLoadBalancers | Direct copy |
N/A → spec.sqlAgConfiguration.disableModeSwitching | Set to true |
N/A → spec.sqlAgConfiguration.disableTunnels | Set to false |
spec.template.spec.* → spec.statefulSetSpec.podSpec.* | Wrap in StatefulSet |
spec.template.metadata → spec.statefulSetSpec.podSpec.metadata | Pod template metadata |
spec.template.spec.dxEnterpriseContainer.* → spec.statefulSetSpec.podSpec.dxEnterpriseContainer.* | Direct copy |
spec.template.spec.dxEnterpriseContainer.joinExistingCluster → spec.statefulSetSpec.podSpec.dxEnterpriseContainer.joinTarget | Convert if true |
spec.template.spec.mssqlServerContainer.* → spec.statefulSetSpec.podSpec.mssqlServerContainer.* | Direct copy |
spec.template.spec.dxEnterpriseContainer.volumeClaimConfiguration → spec.statefulSetSpec.podSpec.dxEnterpriseContainer.volumeClaimConfiguration | Direct copy |
spec.template.spec.mssqlServerContainer.volumeClaimConfiguration → spec.statefulSetSpec.podSpec.mssqlServerContainer.volumeClaimConfiguration | Direct copy |
Pre-Migration Validation
Check for Configuration-Only Replicas
Check whether the DxEnterpriseSqlAg has configuration-only replicas and whether configuration-only PVCs exist.
kubectl get dxenterprisesqlag <resource-name> -n <namespace> -o jsonpath='{.spec.configurationOnlyReplicas}'
kubectl get pvc -n <namespace> -l "dh2i.com/entity=<resource-name>,dh2i.com/availability-mode=ConfigurationOnly"
If that value is greater than 0 or a PVC is listed, see the troubleshooting section.
Configuration-only replicas may complicate migration. Migration is lower risk when the highest-ordinal replica is configuration-only. See the troubleshooting section for more information.
Verify Pod Health
Ensure all pods are running and ready before migration:
kubectl get pods -n <namespace> -l "dh2i.com/entity=<resource-name>"
All pods should show Running and READY status.
Check for Active Alerts
Choose one pod (for example, dxesqlag-0), to run the following command. Exec into the DxEnterprise container and check for alerts using dxcli get-alerts.
kubectl exec -n <namespace> <pod-name> -c dxe -- dxcli get-alerts
Resolve any critical alerts before proceeding.
Pay careful attention to any alerts for the SQL Server Availability group.
Backup Your Configuration
Export the current DxEnterpriseSqlAg to YAML
kubectl get dxenterprisesqlag <resource-name> -n <namespace> -o yaml > dxenterprise-sqlag-backup.yaml
Back up other cluster resources. Volumes and PVCs are the most important.
See Kubernetes documentation on Volume Snapshots for information on backing up volumes.
Velero is a common tool used for volume backups and migrations.
Migration Procedure
Step 1: Export the Current DxEnterpriseSqlAg
Export the YAML again, but save it to a different filename.
kubectl get dxenterprisesqlag <resource-name> -n <namespace> -o yaml > v1-resource.yaml
Step 2: Map V1 to V2
Create a new file v2-resource.yaml. Convert the contents of v1-resource.yaml to a DxSqlAg.
The YAML examples below illustrate a simple configuration before and after conversion.
This migration assumes an in-place migration with the same resource identity (name/namespace). Ensure the name and namespace of the v1 and v2 resources match before continuing.
- v1-resource.yaml
- v2-resource.yaml
apiVersion: dh2i.com/v1
kind: DxEnterpriseSqlAg
metadata:
name: dxsqlag
namespace: default
spec:
synchronousReplicas: 2
asynchronousReplicas: 1
configurationOnlyReplicas: 0
availabilityGroupName: AG1
availabilityGroupClusterType: EXTERNAL
createLoadBalancers: true
template:
metadata:
labels:
app: dxsqlag
spec:
nodeSelector:
kubernetes.io/os: linux
dxEnterpriseContainer:
acceptEula: true
clusterSecret: dxe
image: dh2i/dxe:23.0
vhostName: vhost1
volumeClaimConfiguration:
resources:
requests:
storage: 1Gi
mssqlServerContainer:
acceptEula: true
image: mcr.microsoft.com/mssql/server:2022-latest
mssqlPID: Developer
mssqlSecret: mssql
volumeClaimConfiguration:
resources:
requests:
storage: 2Gi
apiVersion: dh2i.com/v1
# Change kind to DxSqlAg
kind: DxSqlAg
metadata:
name: dxsqlag
namespace: default
spec:
# Move AG configuration options underneath sqlAgConfiguration
sqlAgConfiguration:
synchronousReplicas: 2
asynchronousReplicas: 1
configurationOnlyReplicas: 0
availabilityGroupName: AG1
availabilityGroupClusterType: EXTERNAL
createLoadBalancers: true
# Disable mode switching until migration completes and do not disable tunnels
disableModeSwitching: true
disableTunnels: false
# Rename template to statefulSetSpec, rename spec to podSpec
statefulSetSpec:
podSpec:
metadata:
labels:
app: dxsqlag
nodeSelector:
kubernetes.io/os: linux
dxEnterpriseContainer:
acceptEula: true
clusterSecret: dxe
image: dh2i/dxe:23.0
vhostName: vhost1
volumeClaimConfiguration:
resources:
requests:
storage: 1Gi
mssqlServerContainer:
acceptEula: true
image: mcr.microsoft.com/mssql/server:2022-latest
mssqlPID: Developer
mssqlSecret: mssql
volumeClaimConfiguration:
resources:
requests:
storage: 2Gi
Step 3: Review and Validate the Mapped YAML
There are key structural differences in the Custom Resource Definition between v1 and v2. Carefully review the converted YAML:
- All AG fields moved under
sqlAgConfiguration spec.templaterenamed tospec.statefulSetSpecspec.template.specrenamed tospec.statefulSetSpec.podSpec- Set
disableModeSwitching: trueuntil the migration completes - Set
disableTunnels: false - Convert
dxEnterpriseContainer.joinExistingClustertodxEnterpriseContainer.joinTarget - Kind
DxEnterpriseSqlAgrenamed toDxSqlAg
Additionally, double-check the following:
- Ensure all required fields are present
- Verify replica counts are correct
- Verify secret names match
- Check that container images and secrets are properly referenced
- Verify
joinTargetwas converted correctly ifjoinExistingClusterwas used in v1
After reviewing the YAML, validate the v2 manifest against the Kubernetes cluster before deleting the v1 resource. This catches schema and validation errors before migration.
kubectl apply --dry-run=server -f v2-resource.yaml
Step 4: Delete the DxEnterpriseSqlAg
Delete v1 before creating v2:
Deleting the custom resource will cause downtime. Proceed with caution.
If you skip this step and the v1 resource still exists, the v2 creation will fail due to pod name conflicts.
kubectl delete -f v1-resource.yaml
Wait for the resource and its pods to be fully deleted, then verify deletion:
kubectl get dxenterprisesqlag <resource-name> -n <namespace>
kubectl get pods -n <namespace> -l "dh2i.com/entity=<resource-name>"
Step 5: Create the DxSqlAg Resource
kubectl apply -f v2-resource.yaml
Post-Migration Validation
Verify that the v2 resource was created and the cluster started correctly
Verify Resource Creation
kubectl get dxsqlag <resource-name> -n <namespace> -o yaml
Monitor Pod Creation
DxOperator will create a StatefulSet, and the pods should appear shortly. Watch their progress:
kubectl get pods -n <namespace> -l "dh2i.com/entity-name=<resource-name>" -w
Wait for all pods to reach Running and Ready status.
Check Pod Health
All pods should be Running and Ready.
kubectl get pods -n <namespace> -l "dh2i.com/entity-name=<resource-name>"
Check for Alerts
Choose one pod (for example, dxesqlag-0), to run the following command. Exec into the DxEnterprise container and check alerts using dxcli get-alerts.
There should be no critical alerts for your cluster, Vhost, or availability group.
kubectl exec -n <namespace> <pod-name> -c dxe -- dxcli get-alerts
Test SQL Connectivity
Connect to SQL Server and verify AG status:
kubectl exec -n <namespace> <pod-name> -c mssql -- /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P <password> -Q "SELECT * FROM sys.availability_groups"
Your mssql-tools directory might be different than the example.
Troubleshooting
Check DxOperator v2 Logs
Many migration failures are easiest to diagnose from the DxOperator v2 logs. This is especially useful when:
- the
DxSqlAgresource is created but pods are not reconciled as expected - pods are stuck in
Pendingor repeatedly recreated - the cluster does not form correctly after migration
- PVCs or availability modes are not handled as expected
First, get the name of the DxOperator v2 pod in the dxoperator-v2-system namespace:
kubectl get pods -n dxoperator-v2-system
Then get logs from that pod:
kubectl logs -n dxoperator-v2-system <dxoperator-v2-pod-name>
Review the logs for reconciliation errors, validation failures, DxEnterprise issues, or availability group issues.
Issue: Configuration-Only Replicas Present During Migration
Configuration-only replicas can complicate migration from DxEnterpriseSqlAg to DxSqlAg.
In v1, configuration-only replicas share the same pod naming and ordinal sequence as synchronous and asynchronous replicas. In v2, configuration-only replicas are placed in a separate StatefulSet with different pod names.
This change was necessary because a configuration-only replica cannot change its availability mode after it is created1. A replica created as configuration-only cannot later become synchronous or asynchronous, and a synchronous or asynchronous replica cannot later become configuration-only. Because of that restriction, v2 must give configuration-only replicas their own pod identities and PVCs instead of keeping them mixed into the main ordinal sequence.
Even when migration succeeds, leftover configuration-only PVCs from the v1 layout should be deleted. If they are left behind, they may cause conflicts later.
Lower-risk case: highest ordinal is configuration-only
If the highest-ordinal pod is configuration-only, DxOperator v2 can usually handle the migration automatically. It can remove that member from the mixed v1 layout and recreate it in the dedicated v2 configuration-only StatefulSet.
For example:
| Pod | Availability Mode |
|---|---|
dxsqlag-0 | Synchronous |
dxsqlag-1 | Synchronous |
dxsqlag-2 | Asynchronous |
dxsqlag-3 | ConfigurationOnly |
Even in this easier case, delete any leftover configuration-only PVCs from the old v1 layout after migration so they do not cause problems later.
More complicated case: configuration-only replica is in the middle
The more complicated case is when a configuration-only replica appears in the middle of the ordinal sequence. For example:
| Pod | Availability Mode |
|---|---|
dxsqlag-0 | Synchronous |
dxsqlag-1 | Synchronous |
dxsqlag-2 | ConfigurationOnly |
dxsqlag-3 | Synchronous |
dxsqlag-4 | Asynchronous |
This layout does not map cleanly to the v2 pod topology because v2 separates configuration-only replicas into a different StatefulSet.
In this case, use the following steps:
-
Delete the v1
DxEnterpriseSqlAg. -
Delete the leftover configuration-only PVCs from the v1 layout.
kubectl delete pvc -n <namespace> -l "dh2i.com/entity=<resource-name>,dh2i.com/availability-mode=ConfigurationOnly" -
Create the v2
DxSqlAg. -
Validate that the DxEnterprise cluster is otherwise healthy.
-
Manually remove the member occupying the old configuration-only ordinal using
dxcli remove-cluster-members.kubectl exec -n <namespace> <resource-name>-0 -c dxe -- dxcli remove-cluster-members <pod-name>
In the example above, that removes dxsqlag-2 from the cluster using the dxsqlag-0 pod. DxOperator v2 will fill that freed ordinal by taking the highest-ordinal pod and recreating it in that slot. In the example, that means dxsqlag-4 will be removed and re-created as dxsqlag-2.
Because this affects a data-bearing replica, SQL Server data replication will occur. Plan for additional migration time while that replica is recreated and resynchronized. This can be mitigated by ensuring that the last member of the DxEnterpriseSqlAg is an asynchronous replica.
After migration, verify the resulting topology and review the DxOperator v2 logs if reconciliation does not behave as expected.
Issue: Pod Stuck in Pending State
Symptom: Pods remain in Pending state indefinitely.
Possible Causes:
- Insufficient cluster resources
- Node selector/toleration mismatches
- PVC binding issues
Solution:
Diagnose the condition by looking at the pod details and DxOperator v2 logs.
kubectl describe pod <pod-name> -n <namespace>
In the pod details, check events and conditions for specific errors.
Issue: DxEnterprise Cluster Not Forming
Symptom: Pods are running but DxEnterprise cluster is not healthy.
Possible Causes:
- Incorrect
joinTargetconfiguration - Cluster secret issues
- Network connectivity problems
Solution:
Check DxEnterprise logs and DxOperator v2 logs for errors.
kubectl logs -n <namespace> <pod-name> -c dxe
Issue: SQL Server Not Starting
Symptom: MSSQL container is not ready.
Possible Causes:
- Incorrect MSSQL secrets
- Insufficient resources
- Volume mount issues
Solution:
Check the MSSQL container and DxOperator v2 logs for errors.
kubectl logs -n <namespace> <pod-name> -c mssql
Conclusion
Manual migration from DxEnterpriseSqlAg to DxSqlAg requires careful attention to the structural differences between the two resource types. The key changes involve:
- Nested configuration: Most spec fields are now under
sqlAgConfiguration - StatefulSet wrapper: Pod specifications are now under
statefulSetSpec.podSpec - Join target conversion:
joinExistingClusteris replaced withjoinTarget - New fields:
disableModeSwitchinganddisableTunnelsare new in v2
Always test migrations in a non-production environment first and maintain backups of your original configuration.