DH2i Blog Article

Build a Test Lab for SQL Server 2025 on Kubernetes Using Minikube

Rodney Treweek
/
October 1, 2025

Introduction

In this tutorial (with accompanying demo video), we will demonstrate a simple and effective way to deploy a Microsoft SQL Server Always On Availability Group (AG) in Kubernetes with minikube and DxOperator – DH2i’s SQL Server Operator for Kubernetes.

We will use minikube to create a single-node Kubernetes cluster for demonstration purposes. Within this environment, we will deploy a SQL Server Always On Availability Group consisting of one primary and two secondary replicas (configurable with synchronous or asynchronous replication), managed by DxOperator.

By the end of this guide, you should be able to:

  • Create a single-node minikube cluster
  • Deploy a SQL Server Always On Availability Group with one primary and two secondary replicas using DxOperator
  • Create a SQL Server Always On Availability Group Listener
  • Connect to the SQL Server Always On Availability Group
  • Perform a failover from the primary to a secondary replica

Prerequisites

Install Minikube on Windows

First, install minikube on Windows by following minikube’s instructions.

Create the Cluster

We will create a Kubernetes environment that will consist of a single master node. This is usually sufficient for most testing. We will use the Hyper-V driver for this. We can create the cluster using the Hyper-V driver by opening a PowerShell terminal as Administrator and running the following command:

minikube start --driver=hyperv --memory 8092MB

The cluster may take several minutes to create, but minikube will output updates as things are happening. Once it says “Done!”, use the following command to see the node you just created:

Create a Kubernetes environment.

kubectl get nodes

Deploy a SQL Server Availability Group using DxOperator

Install DxOperator

First, you need to download the DxOperator YAML:

curl.exe --output v1-cu2.yaml --url https://dxoperator.dh2i.com/dxesqlag/files/v1-cu2.yaml

Then, apply the YAML file to the Kubernetes cluster:

kubectl apply -f v1-cu2.yaml

Apply the YAML file to the Kubernetes cluster.

Create a DxEnterpriseSqlAg

Create secrets for the DxEnterprise passkey (a user defined value, e.g., 1234), license key, and the SQL Server SA account using the following commands. Make sure to fill in values for the commands below.

kubectl create secret generic dxe --from-literal=DX_PASSKEY=<pass> --from-literal=DX_LICENSE=<license_key>
kubectl create secret generic mssql --from-literal=MSSQL_SA_PASSWORD=<pass>

Copy the example DxEnterpriseSqlAg.yaml file below to your local machine.

There are many configuration options for the DxEnterpriseSqlAg custom resource beyond those that are shown in this example. View the DxEnterpriseSqlAg API Reference for more information.

apiVersion: dh2i.com/v1
kind:  DxEnterpriseSqlAg
metadata:
  name: dxesqlag
spec:
  synchronousReplicas: 3
  asynchronousReplicas: 0
  # ConfigurationOnlyReplicas are only allowed with availabilityGroupClusterType set to EXTERNAL
  configurationOnlyReplicas: 0            
  availabilityGroupName: AG1
  # Listener port for the availability group (uncomment to apply)
  #availabilityGroupListenerPort: 14033
  # For a contained availability group, add the option CONTAINED
  availabilityGroupOptions: null
  # Valid options are EXTERNAL (automatic failover) and NONE (no automatic failover)
  availabilityGroupClusterType: EXTERNAL
  createLoadBalancers: true
  template:
    metadata:
      labels:
        label: example
      annotations:
        annotation: example
    spec:
      dxEnterpriseContainer:
        image: "docker.io/dh2i/dxe:latest"
        imagePullPolicy: Always
        acceptEula: true
        clusterSecret: dxe
        vhostName: VHOST1
        joinExistingCluster: false
        # QoS – guaranteed (uncomment to apply)
        #resources:
          #limits:
            #memory: 1Gi
            #cpu: '1'
        # Configuration options for the required persistent volume claim for DxEnterprise
        volumeClaimConfiguration:
          storageClassName: null
          resources:
            requests:
              storage: 1Gi
      mssqlServerContainer:
        image: "mcr.microsoft.com/mssql/server:latest"
        imagePullPolicy: Always
        mssqlSecret: mssql
        acceptEula: true
        mssqlPID: Developer
        # Only set this to a value if you created a ConfigMap
        mssqlConfigMap: null
        # QoS – guaranteed (uncomment to apply)
        #resources:
          #limits:
            #memory: 2Gi
            #cpu: '2'
        # Configuration options for the required persistent volume claim for SQL Server
        volumeClaimConfiguration:
          storageClassName: null
          resources:
            requests:
              storage: 2Gi
      # Additional side-car containers, such as mssql-tools (uncomment to apply)
      #containers:
      #- name: mssql-tools
          #image: "mcr.microsoft.com/mssql-tools"
          #command: [ "/bin/sh" ]
          #args: [ "-c", "tail -f /dev/null" ]

Apply the DxEnterpriseSqlAg custom resource using kubectl apply:

kubectl apply -f DxEnterpriseSqlAg.yaml

Create the DxEnterprise-managed SQL Server Availability Group.

Verify the status of the pods:

kubectl get pods -w

Verify the status of the Kubernetes pods.

After giving the cluster at least a couple of minutes to setup, verify the state of the AG using dxcli get-ags-detail:

kubectl exec -c dxe dxesqlag-0 -- dxcli get-ags-detail vhost1 ag1 | kubectl exec -ic dxe dxesqlag-0 -- dxcli format-xml

Verify the status of your SQL Server Availability Group.

Create an Availability Group Listener

Kubernetes load balancers provide access to cluster resources via an externally accessible IP address: you define the load balancer configuration in a YAML, and Kubernetes takes care of load-balancing incoming connections for you.

The previous DxEnterpriseSqlAg.yaml example contained a commented-out line for the spec.availabilityGroupListenerPort. Uncomment out this line, then run kubectl apply again to update the custom resource with the new listener port.

Copy the the load balancer configuration Service.yaml file below to your local machine.

#Example load balancer service
#Access for SQL server, AG listener, and DxE management
apiVersion: v1
kind: Service
metadata:
  name: dxesqlag-cluster-lb
spec:
  type: LoadBalancer
  selector:
    dh2i.com/entity: dxesqlag
  ports:
  - name: sql
    protocol: TCP
    port: 1433
    targetPort: 1433
  - name: listener
    protocol: TCP
    port: 14033
    targetPort: 14033
  - name: dxe
    protocol: TCP
    port: 7979
    targetPort: 7979

Apply the service YAML:

kubectl apply -f Service.yaml

List the service and note its public IP address:

kubectl get service

List the service and note its public IP address.

Open a new PowerShell terminal as Administrator and run:

minikube tunnel

Open a new PowerShell terminal as Administrator.

To verify connectivity, try connecting to the SQL instance (port 1433) or AG listener (port 14033) via SSMS/sqlcmd using the load balancer public IP address:

sqlcmd -S <ip_address> -U sa -P <sql_password>

Failover Test

To verify that your AG is working correctly, you can simulate a failover on the primary.

Verify the state of the AG before failover. To do this, run the command dxcli get-ags-detail. Look for the status of the database for each AG member in the output details and find whichever pod is the primary.

kubectl exec -c dxe dxesqlag-1 -- dxcli get-ags-detail vhost1 ag1 | kubectl exec -ic dxe dxesqlag-1 -- dxcli format-xml

Delete the primary pod:

kubectl delete pod dxesqlag-0

Wait a few seconds, then run the dxcli get-ags-detail command again to check the status of the databases:

kubectl exec -c dxe dxesqlag-1 -- dxcli get-ags-detail vhost1 ag1 | kubectl exec -ic dxe dxesqlag-1 -- dxcli format-xml

After a few minutes, the deleted pod should automatically rejoin the cluster and synchronize with the Availability Group.

After the pod rejoins the cluster and synchronizes, you can set its role back to primary by running the command dxcli vhost-start-node:

kubectl exec -c dxe dxesqlag-0 -- dxcli vhost-start-node vhost1 dxesqlag-0

Cleanup

Cleaning up after testing is a good practice to save space on your computer. To clean up what you just created, we need to delete the deployments and the nodes. Here are the commands to delete both:

Removing Custom Resources and DxOperator

Delete the DxEnterpriseSqlAg custom resource:

kubectl delete -f DxEnterpriseSqlAg.yaml

Delete the persistent volume claims (PVCs):

kubectl delete pvc --all

Delete the DxOperator YAML.

Warning: Running this command will delete the DxEnterpriseSqlAg CRD. If any DxEnterpriseSqlAg custom resources remain in the Kubernetes cluster, Kubernetes will destroy them as part of the deletion process. Only run this command if you intend on removing every DxEnterpiseSqlAg too!

To list all DxEnterpriseSqlAgs, run ‘kubectl get dxesqlag’.

 

kubectl delete -f v1-cu2.yaml

Delete Minikube VM

To delete minikube, run the following command:

minikube delete --all

Cleaning up your test SQL Server Availability Group is a good practice to save space on your computer after testing/demonstration.

If you’re interested in learning more about DH2i’s approach to smart high availability technology, get signed up for a one-on-one demo today.

The author
Rodney Treweek

Native. Containerized. Anywhere in Between.

DH2i gets you closer to zero downtime.