Skip to main content
Version: v2.0

Migration Guide: DxEnterpriseSqlAg to DxSqlAg

This guide provides step-by-step instructions for manually migrating a DxEnterpriseSqlAg custom resource to a DxSqlAg custom resource.

In-Place Migration

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:

  • kubectl installed and configured with access to your Kubernetes cluster.
  • Cluster permissions to read/write custom resources and manage pods/PVCs.
  • A backup of your current DxEnterpriseSqlAg resource.
  • 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 template wording with statefulSetSpec and podSpec
  • Introduces a few new configuration options for finer control during and after migration

The main structural differences are outlined below.

v1 → v2Notes
spec.*Replicasspec.sqlAgConfiguration.*ReplicasMoves all replica counts into nested availability group configuration.
spec.availability*spec.sqlAgConfiguration.availability*Moves availability group settings into nested AG configuration.
spec.createLoadBalancersspec.sqlAgConfiguration.createLoadBalancersMoves auto-generated load balancers into nested AG configuration.
spec.template.specspec.statefulSetSpec.podSpecReplaces the old pod-template-based structure with a StatefulSet-based structure. statefulSetSpec and podSpec also support metadata.
N/A → spec.sqlAgConfiguration.disableModeSwitchingNew field. During migration, set disableModeSwitching to true. After migration succeeds, it can be set back to false.
N/A → spec.sqlAgConfiguration.disableTunnelsNew field. Must be set to false for v1 to v2 migration.
spec.template.spec.dxEnterpriseContainer.joinExistingClusterspec.statefulSetSpec.podSpec.dxEnterpriseContainer.joinTargetReplaces joinExistingCluster, which is now fully deprecated.

Label Changes in v2

Some labels changed for v2.

v1 → v2Notes
dh2i.com/entitydh2i.com/entity-nameRenamed to avoid label conflicts.
dh2i.com/availability-mode → N/AAvailability modes managed internally by DxOperator.
N/A → dh2i.com/active-vhostFollows 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 PathNotes
spec.synchronousReplicasspec.sqlAgConfiguration.synchronousReplicasDirect copy
spec.asynchronousReplicasspec.sqlAgConfiguration.asynchronousReplicasDirect copy
spec.configurationOnlyReplicasspec.sqlAgConfiguration.configurationOnlyReplicasDirect copy
spec.availabilityGroupNamespec.sqlAgConfiguration.availabilityGroupNameDirect copy
spec.availabilityGroupClusterTypespec.sqlAgConfiguration.availabilityGroupClusterTypeDirect copy
spec.availabilityGroupListenerPortspec.sqlAgConfiguration.availabilityGroupListenerPortOmit if 0
spec.availabilityGroupOptionsspec.sqlAgConfiguration.availabilityGroupOptionsOmit if null
spec.createLoadBalancersspec.sqlAgConfiguration.createLoadBalancersDirect copy
N/A → spec.sqlAgConfiguration.disableModeSwitchingSet to true
N/A → spec.sqlAgConfiguration.disableTunnelsSet to false
spec.template.spec.*spec.statefulSetSpec.podSpec.*Wrap in StatefulSet
spec.template.metadataspec.statefulSetSpec.podSpec.metadataPod template metadata
spec.template.spec.dxEnterpriseContainer.*spec.statefulSetSpec.podSpec.dxEnterpriseContainer.*Direct copy
spec.template.spec.dxEnterpriseContainer.joinExistingClusterspec.statefulSetSpec.podSpec.dxEnterpriseContainer.joinTargetConvert if true
spec.template.spec.mssqlServerContainer.*spec.statefulSetSpec.podSpec.mssqlServerContainer.*Direct copy
spec.template.spec.dxEnterpriseContainer.volumeClaimConfigurationspec.statefulSetSpec.podSpec.dxEnterpriseContainer.volumeClaimConfigurationDirect copy
spec.template.spec.mssqlServerContainer.volumeClaimConfigurationspec.statefulSetSpec.podSpec.mssqlServerContainer.volumeClaimConfigurationDirect 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

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.

Availability Group Alerts

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.

Backup Volumes

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.

Resources Must Match

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.

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

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.template renamed to spec.statefulSetSpec
  • spec.template.spec renamed to spec.statefulSetSpec.podSpec
  • Set disableModeSwitching: true until the migration completes
  • Set disableTunnels: false
  • Convert dxEnterpriseContainer.joinExistingCluster to dxEnterpriseContainer.joinTarget
  • Kind DxEnterpriseSqlAg renamed to DxSqlAg

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 joinTarget was converted correctly if joinExistingCluster was 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:

downtime

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"
tip

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 DxSqlAg resource is created but pods are not reconciled as expected
  • pods are stuck in Pending or 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.

caution

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:

PodAvailability Mode
dxsqlag-0Synchronous
dxsqlag-1Synchronous
dxsqlag-2Asynchronous
dxsqlag-3ConfigurationOnly
caution

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:

PodAvailability Mode
dxsqlag-0Synchronous
dxsqlag-1Synchronous
dxsqlag-2ConfigurationOnly
dxsqlag-3Synchronous
dxsqlag-4Asynchronous

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:

  1. Delete the v1 DxEnterpriseSqlAg.

  2. 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"
  3. Create the v2 DxSqlAg.

  4. Validate that the DxEnterprise cluster is otherwise healthy.

  5. 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.

warning

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 joinTarget configuration
  • 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:

  1. Nested configuration: Most spec fields are now under sqlAgConfiguration
  2. StatefulSet wrapper: Pod specifications are now under statefulSetSpec.podSpec
  3. Join target conversion: joinExistingCluster is replaced with joinTarget
  4. New fields: disableModeSwitching and disableTunnels are new in v2

Always test migrations in a non-production environment first and maintain backups of your original configuration.

Additional Information

Footnotes

  1. Microsoft - High Availability Considerations