SQL Server Docker Containers: How to Build a SQL Server Availability Group (AG) in Kubernetes Using DH2i DxEnterprise Smart High Availability Software
Abstract
This whitepaper tackles the problem of SQL Server high availability and SQL Server clustering for SQL Server Docker containers. It describes how easy it is to build a SQL Server AG in Kubernetes using SQL Server Docker containers with the enhanced SQL Server clustering of DH2i's DxEnterprise Smart High Availability software to solve the challenge of provisioning new digital transformation business systems.
Audience
This white paper is for IT professionals such as DBAs, database architects, engineers, and CIOs who are interested in learning how to build a SQL Server AG in Kubernetes using SQL Server Linux and SQL Server Docker containers in Azure Kubernetes to support the high availability and disaster recovery needs of new business applications.
Business Challenge
A 2020 McKinsey global study found that as a result of the COVID-19 pandemic, companies have been pushed "over the technology tipping point." The research found that as digital adoption has ramped up in response to the pandemic-related changes in the business environment, enterprises have accelerated the digitization of their customer and supply-chain interactions—as well as their internal operations.
Companies had already started increasing digital transformation (DX) projects—replacing manual IT tasks with software to automate the test, configuration, and runtime processes—and for good reason. It's been recognized for decades that DX can not only dramatically improve IT and business capabilities, but it can also save money. As a result, those organizations that leverage DX can create and maintain a competitive advantage by:
- Transforming the employee experience (EX) and customer experience (CX)
- Increasing top-line growth
- Contributing a substantial boost to the bottom line
But while many companies had already started to recognize the value of a DX approach, the pandemic introduced a new sense of corporate urgency in meeting goals related to digital adoption. Organizations that previously had the luxury of remaining reluctant about e-commerce were suddenly forced to accelerate their DX work. Projects that were planned to take years had to happen in only months or less. The recent Dell Digital Transformation Index 2020 survey showed that eight in ten organizations fast-tracked their DX projects in 2020.
These DX initiatives are in turn contributing to an explosion of software container use. The use of containers in production has increased to 92%, up from 84% last year, and up 300% from in 2016, according to Cloud Native Computing Foundation (CNCF) Cloud Native Survey 2020. Additionally, the survey showed that there's been steady growth in the number of containers that organizations run. Those using more than 5,000 containers hit 23% in 2020, up 109% from 11% in 2016. Those using more than 250 containers hit 61% in 2020, up from 57% in 2019.
These DX initiatives are also driving the adoption of stateful containers. Containers were originally designed to be entirely stateless and ephemeral. A container would spin up, do its job, and disappear, without leaving any record of what happened while it was running. However, as DX projects generally require moving applications to containers, companies have run into a problem: Many applications need to retain state, which creates a need for stateful containers such as databases. The survey results confirm this with 55% of respondents indicating that they use stateful containers in production, 12% are evaluating stateful containers, and 11% plan to use stateful containers in the next 12 months. Only 22% of respondents only use stateless containers.
Another important trend affecting DX projects are the data center deployment strategies for containers. According to the CNCF survey the public cloud remains the most popular data center approach for container deployment. It's used 64% of the time - a slight increase from 62% last year. Private cloud or on-premises usage showed a significant year over year increase. It grew to 52% from 45%. While Hybrid cloud usage showed a slight decrease from 38% to 36%, it remains an important deployment option.
A key challenge for organizations is how to provision new stateful containers, such as SQL Server Docker containers, that meet the tier 1 service-level agreements (SLA) for high availability and disaster recovery requirements of DX business systems. Many of these business systems will potentially involve managing thousands of stateful containers. These containers will need to be deployed fast and efficiently in public cloud, private cloud/on-premises and/or hybrid cloud datacenter environments.
Solution Overview
In this paper, we expand on information that is available from DH2i, Microsoft and Azure. We provide a SQL Server AG use case that highlights the benefits of deploying a near-zero high availability and disaster recovery (HADR) architecture for SQL Server Docker containers in production. In addition, we explore the intersection of the enhanced SQL Server clustering capabilities of DH2i DxEnterprise (DxE) Smart High Availability software, 2019 SQL Server Docker container, and Azure Kubernetes Services (AKS). The use case that we present is designed to show how DevOps, Database Architects and others can easily create a SQL Server AG that uses the enhanced SQL Server clustering capabilities of DxE with SQL Server Docker containers and AKS. Using the enhanced SQL Server clustering capabilities of DxE with SQL Server Docker containers enables tier 1 high availability and disaster recovery (HADR) with failover automation across hybrid and multiple cloud environments.
Use Case Overview
Our use case demonstrates the advantages of using the enhanced SQL Server clustering capabilities of DH2i DxEnterprise (DxE) Smart High Availability software, Microsoft 2019 SQL Server Docker containers and Azure Kubernetes Service (AKS) for a SQL Server AG that is hosted in Azure.
The use case will show how a SQL Server AG using the enhanced SQL Server clustering capabilities of DxE with SQL Server database Docker containers in AKS enable a near-zero RTO high availability and disaster recovery (HADR) architecture for SQL Server Docker container-based applications. With this solution, developers, database architects and others can easily take full advantage of SQL Server AG in Kubernetes to ensure SQL Server database downtime is near-zero with automatic failover across Azure availability zones.
In the use case, we create a Linux SQL Server AG using the enhanced SQL Server clustering capabilities of DxE with SQL Server Docker containers in AKS. Taking advantage of built-in Software Defined Perimeter (SDP) capabilities of DxE's enhanced SQL Server clustering, the SQL Server AG will be built without needing to use a private VPN or any other cumbersome network configuration. With DxE, SQL Server Docker containers and AKS we will easily and quickly provision the SQL Server AG with readable secondary replicas and an attached sample database. We will show how to access the primary SQL Server Docker container replica and readable secondary replicas and how easy it is to scale the secondary replica SQL Server Docker containers to support new workload requests. Lastly, we show application resilience by demonstrating fully automatic failover of the primary SQL Server Docker containers to replicas across availability zones.
Use Case Detail: SQL Server AG in AKS
This guide describes how to create and deploy a DxEnterprise + SQL Server container image to an Azure Kubernetes Service (AKS) cluster for Availability Groups. Using this guide, the user will create a DxEnterprise + SQL Server container image and push the image to a repository, deploy and configure an AKS cluster and a StatefulSet, and use DxEnterprise to create and configure the availability group.
This guide covers the deployment of three AG replicas. When deploying availability groups, please consider the Microsoft SQL Server quorum requirements for automatic failover described in this KB: Quorum Considerations for SQL Server Availability Groups.
Prerequisites
- A machine with the Azure and Kubernetes CLIs installed and logged into an Azure account. For information about installing the Azure CLI and logging in, view Microsoft documentation. To install the Kubernetes CLI, run the command
az aks install-cli. Alternatively, the Azure Cloud Shell may be used. - Docker installed on a Linux VM and logged in to a Docker account with push and pull privileges. This can be the same VM that has the Azure and Kubernetes CLIs installed. For more information about installing Docker, view Docker documentation. Use
docker loginto log into a Docker account. - A valid repository for the image on a registry service such as Docker Hub. For more information about creating repositories on Docker Hub, view Docker documentation. This guide relies on Docker instructions, but it is perfectly acceptable to use another registry service such as Azure Container Registry.
- A valid DxEnterprise license with availability group management features and tunnels enabled. A fully featured developer edition is available for free for non-production use at https://dh2i.com/dxenterprise-dxodyssey-developer-edition. To purchase DxEnterprise software for production workloads, visit https://dh2i.com/store/.
Prepare the Cluster
This section describes how to create the container image, create the AKS cluster, and deploy the StatefulSet in Azure.
Create the Container Image
This section describes how to create a custom Docker image that combines the SQL Server container image from Docker Hub with a customized DxEnterprise package meant specifically for containers.
-
Copy the contents of the Dockerfile below to a file your current working directory.
infoThis image is built from the
mcr.microsoft.com/mssql/server:2019-latestimage on Docker Hub. This image:-
Installs .NET Core 3.1 and
net-tools.net-toolsis not required for the container to run, but it may help with diagnosing network issues. -
Copies the latest DxEnterprise tarball from https://repos.dh2i.com/container/, unpacks it, and creates the necessary user permissions.
-
Assigns the default user as
mssqland sets the entrypoint to a pre-defined script.
FROM mcr.microsoft.com/mssql/server:2019-latest
USER root
#Install dotnet and net-tools
RUN apt-get update \
&& ACCEPT_EULA=Y apt-get upgrade -y \
&& apt-get install -y wget \
&& wget --no-dns-cache https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb \
&& dpkg -i packages-microsoft-prod.deb \
&& apt-get update \
&& apt-get install -y dotnet-runtime-3.1 net-tools \
&& dpkg --purge packages-microsoft-prod \
&& apt-get purge -y wget \
&& apt-get clean \
&& rm packages-microsoft-prod.deb \
&& rm -rf /var/lib/apt/lists/*
#Download and unpack DxE, setup permissions
ADD https://repos.dh2i.com/container/ ./dxe.tgz
RUN tar zxvf dxe.tgz && rm dxe.tgz \
&& chown -R mssql /var/opt/mssql \
&& chmod -R 777 /opt/dh2i /etc/dh2i
#Finish setup
EXPOSE 7979 7985
ENV DX_HAS_MSSQLSERVER=1 \
MSSQL_ENABLE_HADR=1
USER mssql
ENTRYPOINT ["/opt/dh2i/sbin/dxstart.sh"] -
-
Replace the
<repository_name>variable in the command below the name of your registry, repository, and tag (i.e.,dh2i/dxemssql:mytest), and run the command.docker build -t <repository_name> -f dxemssql.dockerfile ./
Push the Image to a Repository
-
Login to your Docker account on the local machine using
docker login. -
Push the image to Docker Hub using the command below, replacing
<repository_name>with the repository name you used in step 2 of the previous section.docker push <repository_name>
Create the Kubernetes Cluster
-
Create a resource group in Azure for the Kubernetes cluster.
az group create -n <resource_group> -l <region> -
Create an Azure Kubernetes Service (AKS) Cluster in the resource group. For more information about CLI parameters, view Microsoft documentation.
infoYou can utilize Azure availability zones in select regions by using the
--zonesparameter. Availability zones distribute your Kubernetes node pool(s) across Azure infrastructure within a given region for higher availability. For more information about Azure availability zones, see Microsoft Documentationaz aks create -g <resource_group> -n <cluster_name> --disable-rbac --generate-ssh-keys --zones 1 2 3 -
Obtain the access credentials for the AKS cluster.
az aks get-credentials -g <resource_group> -n <cluster_name>
Create the StatefulSet
-
Write the StatefulSet YAML configuration below to a file.
#DxEnterprise + MSSQL StatefulSet
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: dxemssql
spec:
serviceName: "dxemssql"
replicas: 3
selector:
matchLabels:
app: dxemssql
template:
metadata:
labels:
app: dxemssql
spec:
securityContext:
fsGroup: 10001
containers:
- name: dxemssql
image: <Docker_ID>/<repository>
env:
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_ENABLE_HADR
value: "1"
- name: SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql
key: SA_PASSWORD
volumeMounts:
- name: dxe
mountPath: "/etc/dh2i"
- name: mssql
mountPath: "/var/opt/mssql"
volumeClaimTemplates:
- metadata:
name: dxe
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
- metadata:
name: mssql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi -
In the YAML, use
sedor a text editor to insert your registry and repository into the container configuration. If you're usingsed, replace theREGISTRYandREPOSITORYvariables with the name of your registry and repository. For example:REGISTRY="dh2i"
REPOSITORY="dxemssql"
sed -i "s/image:.*/image: $REGISTRY\/$REPOSITORY/" dxemssql.yamlPrivate RegistriesIf you're using a private registry, the
dxemssql.yamlfile needs to be modified to use those credentials. For more information about using private registries with Kubernetes, view Kubernetes documentation. -
Create a credential for the SQL instance.
kubectl create secret generic mssql --from-literal=SA_PASSWORD="<password>" -
Apply the StatefulSet configuration.
kubectl apply -f dxemssql.yaml -
Check the status of the pods using one or both of the commands below. Proceed to the next section after the first pod’s status updates to “running”.
kubectl get pods
kubectl describe pods
Create the Availability Group
This section describes how to start the availability group on the primary, then join the two secondary Kubernetes pods to the DxEnterprise cluster and the availability group.
Configure the Primary Pod and Add the Availability Group
All DxCLI commands can be run from outside the container using the command format kubectl exec <pod_name> -- dxcli <command>. This format will be used for all of the subsequent DxCLI command examples.
-
Activate the DxEnterprise license using the command
dxcli activate-sever.kubectl exec dxemssql-0 -- dxcli activate-server AAAA-BBBB-CCCC-DDDD -
Add a Vhost to the cluster using the command
dxcli cluster-add-vhost.kubectl exec dxemssql-0 -- dxcli cluster-add-vhost vhost1 *127.0.0.1 dxemssql-0 -
Encrypt the SQL Server sysadmin password for DxEnterprise using the command
dxcli encrypt-text. The encrypted password will be used to create the availability group in the next step.kubectl exec dxemssql-0 -- dxcli encrypt-text p@ssw0rd -
Add an availability group to the Vhost using the command
dxcli add-ags. The SQL Server sysadmin password must be encrypted using the commanddxcli encrypt-textfrom the previous step, replacing the encrypted password in the example below.kubectl exec dxemssql-0 -- dxcli add-ags vhost1 ags1 \
"dxemssql-0|mssqlserver|sa|6pnFaDrRS+W/F+dkRuPKAA==|5022|synchronous_commit|50221" -
Set a One-Time PassKey (OTPK) using the command
dxcli set-otpk. The output from this command will be used to join the other pods to the DxEnterprise cluster.kubectl exec dxemssql-0 -- dxcli set-otpk -
Set a cluster passkey using the command
dxcli cluster-set-secret-ex. A passkey is required to remotely manage your DxEnterprise cluster.kubectl exec dxemssql-0 -- dxcli cluster-set-secret-ex p@ssw0rd
Join the Second Pod to the DxEnterprise Cluster
-
Activate the DxEnterprise license for the second pod using the command
dxcli activate-server.kubectl exec dxemssql-1 -- dxcli activate-server AAAA-BBBB-CCCC-DDDD -
Join the second pod to the DxEnterprise cluster using the command
dxcli join-cluster-ex. Use the default NAT proxy of match.dh2i.com and the OTPK generated in step 5 of the previous section.kubectl exec dxemssql-1 -- dxcli join-cluster-ex \
match.dh2i.com XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX true -
Add the second pod to the existing availability group using the command
dxcli add-ags-node. The SQL Server sysadmin password must be encrypted using the commanddxcli encrypt-text.infoThe commands
dxcli add-agsanddxcli add-ags-nodetake identical parameter sets. The only parameters which must be unique to each command are thenode_nameand thetunnel_port.The functionality difference between the commands is that
add-agscreates the availability group under a Vhost, whileadd-ags-nodeadds a pod to an existing Vhost and availability group.kubectl exec dxemssql-1 -- dxcli add-ags-node vhost1 ags1 \
"dxemssql-1|mssqlserver|sa|6pnFaDrRS+W/F+dkRuPKAA==|5022|synchronous_commit|50222"
Join the Third Pod to the DxEnterprise Cluster
-
Activate the DxEnterprise license for the third pod using the command
dxcli activate-server.kubectl exec dxemssql-2 -- dxcli activate-server AAAA-BBBB-CCCC-DDDD -
Join the third pod to the DxEnterprise cluster using the command
dxcli join-cluster-ex.kubectl exec dxemssql-2 -- dxcli join-cluster-ex \
match.dh2i.com XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX true -
Add the third pod to the existing availability group using the command
dxcli add-ags-node.kubectl exec dxemssql-2 -- dxcli add-ags-node vhost1 ags1 \
"dxemssql-2|mssqlserver|sa|6pnFaDrRS+W/F+dkRuPKAA==|5022|synchronous_commit|50223"
Add an Availability Group Database
-
If a database is not already available, create it using
sqlcmdon the primary.kubectl exec dxemssql-0 -- /opt/mssql-tools/bin/sqlcmd \
-S localhost -U SA -P p@ssw0rd -Q "create database db1" -
Add databases to the availability group from the primary using the command
dxcli add-ags-databases.kubectl exec dxemssql-0 -- dxcli add-ags-databases vhost1 ags1 db1 -
Verify the details of the availability group using the command
dxcli get-ags-detail.kubectl exec dxemssql-0 -- dxcli get-ags-detail vhost1 ags1
Create Headless Services
Headless services give your DxEMSSQL pods an alternative method of connecting each other locally for additional redundancy.
-
Copy the YAML below to a file.
#Headless services for local connections/resolution
apiVersion: v1
kind: Service
metadata:
name: dxemssql-0
spec:
clusterIP: None
selector:
statefulset.kubernetes.io/pod-name: dxemssql-0
ports:
- name: dxl
protocol: TCP
port: 7979
- name: dxc-tcp
protocol: TCP
port: 7980
- name: dxc-udp
protocol: UDP
port: 7981
- name: sql
protocol: TCP
port: 1433
- name: listener
protocol: TCP
port: 14033
---
apiVersion: v1
kind: Service
metadata:
name: dxemssql-1
spec:
clusterIP: None
selector:
statefulset.kubernetes.io/pod-name: dxemssql-1
ports:
- name: dxl
protocol: TCP
port: 7979
- name: dxc-tcp
protocol: TCP
port: 7980
- name: dxc-udp
protocol: UDP
port: 7981
- name: sql
protocol: TCP
port: 1433
- name: listener
protocol: TCP
port: 14033
---
apiVersion: v1
kind: Service
metadata:
name: dxemssql-2
spec:
clusterIP: None
selector:
statefulset.kubernetes.io/pod-name: dxemssql-2
ports:
- name: dxl
protocol: TCP
port: 7979
- name: dxc-tcp
protocol: TCP
port: 7980
- name: dxc-udp
protocol: UDP
port: 7981
- name: sql
protocol: TCP
port: 1433
- name: listener
protocol: TCP
port: 14033 -
Apply the configuration.
kubectl apply -f headless-services.yaml
Test Failover
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. Find which pod is the primary.kubectl exec dxemssql-0 -- dxcli get-ags-detail vhost1 ags1 -
Delete the primary pod.
kubectl delete pod <PRIMARY_POD> -
Wait a few seconds, then run
dxcli get-ags-detailagain to check the status of the AG. Your database should be active and synchronized on one of your other pods. 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, set its role back to primary by running the command
dxcli vhost-start-nodekubectl exec dxemssql-0 -- dxcli vhost-start-node vhost1 dxemssql-0
Make Your Cluster Externally Accessible
Load balancers can be used to access pods in a Kubernetes cluster. Kubernetes load balancers provide access to cluster resources via an externally-accessible IP address: you define the load balancer configuration in the YAML, and Kubernetes takes care of load balancing incoming connections for you.
- Set an AG listener port using the command
dxcli add-ags-listener.
kubectl exec dxemssql-0 -- dxcli add-ags-listener vhost1 ags1 14033
-
Copy the load balancer configuration to your local machine.
#Example load balancer service
#Access for SQL server, AG listener, and DxE management
apiVersion: v1
kind: Service
metadata:
name: dxemssql-cluster-lb
spec:
type: LoadBalancer
selector:
app: dxemssql
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 -
Provision a static IP.
infoWhen creating a Kubernetes load balancer service, the load balancer is assigned an IP address that only persists for the load balancers lifespan. This IP address will change if the service is deleted and recreated. Reference Microsoft documentation for further information about static IPs in Kubernetes. The steps below describe how to create and assign a persistent static IP.
-
Create a Public IP address resource with a static allocation method and standard SKU using the Azure CLI. The public IP address should be assigned to the additional resource group Azure created during the initial Kubernetes cluster deployment. The name of this auto-generated resource group follows the pattern
MC_<resource-group>_<cluster-name>_<region>. To list the name, run the commandaz group list | grep “MC_”. For example:az network public-ip create -g MC_examplegroup_examplecluster_westus2 -n exampleStaticIPName --sku Standard --allocation-method static -
Copy the
ipAddressvalue from the JSON output. -
Use
sedor a text editor to add the static IP to the service YAML file. Replace the<Public_IP>value with the IP address copied in step 2. Note in the command below that there are two spaces after the backslash.sed -i '/.*type:.*/a \ loadBalancerIP: <Public_IP>' service.yaml
-
-
Apply the configuration.
kubectl apply -f service.yaml -
List the service and note its public IP address.
kubectl get service -
To verify connectivity, you can connect to the SQL instance (port 1433) or AG listener (port 14033) via SSMS/sqlcmd and the load balancer IP address.
sqlcmd -S <ip_address> -U sa -P <sql_password>
Make Each Pod Externally Accessible
Load balancers can also be assigned to a single pod. This requires some slight modification of the YAML example from the previous section. Each pod participating in the AG will require a unique service configuration like the one below, and will need a static IP defined.
-
For each pod in the availability group, replace
<lb_ip>and<pod_name>in the example below and save the file.#Example load balancer service
#Access for SQL server, AG listener, and DxE management
apiVersion: v1
kind: Service
metadata:
#Unique name
name: <pod_name>-lb
spec:
type: LoadBalancer
loadBalancerIP: <lb_ip>
selector:
#Assign load balancer to a specific pod
statefulset.kubernetes.io/pod-name: <pod_name>
ports:
- name: sql
protocol: TCP
port: 1433
targetPort: 1433
- name: listener
protocol: TCP
port: 14033
targetPort: 14033
- name: dxe
protocol: TCP
port: 7979
targetPort: 7979tipMultiple YAML configurations can be saved in one file using a line of three dashes (
---) between each configuration. -
Apply the service configuration.
kubectl apply -f pod-service.yaml
Use Tunnels for Faster Connections to the Listener
Connections to the AG listener via the Kubernetes load balancer may be slow due to how Kubernetes handles traffic to pods that do not have an active listener. To alleviate this issue, a DxEnterprise tunnel can used to redirect listener traffic from the secondaries to the primary to avoid timeouts. Run the dxcli add-tunnel command to add the tunnel to the Vhost.
kubectl exec dxemssql-0 -- dxcli add-tunnel listener true ".ACTIVE" "127.0.0.1:14033" ".INACTIVE,0.0.0.0:14033" vhost1
The add-tunnel command given above uses .ACTIVE and .INACTIVE designations to determine where to route traffic. In DxEnterprise, the .ACTIVE node is always the primary replica, and .INACTIVE will be every secondary replica in the Vhost. The command will create tunnel origins (listeners) on the secondaries on port 14033 that route traffic to the primary pod.
Configure Replicas for Read-Only Access
Availability Group secondaries can be configured to allow read-only access to their databases using T-SQL.
-
Verify the details of your service configuration before continuing. Obtain the IP addresses for each of the load balancers assigned to your pods.
kubectl get service -
Copy the T-SQL below into a file.
USE [master]
GO
--Only allow read-write connections in the primary role
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
PRIMARY_ROLE (ALLOW_CONNECTIONS = READ_WRITE)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
PRIMARY_ROLE (ALLOW_CONNECTIONS = READ_WRITE)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
PRIMARY_ROLE (ALLOW_CONNECTIONS = READ_WRITE)
)
GO
--Allow read-only connections in the secondary role
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = READ_ONLY)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = READ_ONLY)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = READ_ONLY)
)
GO -
Copy the T-SQL to the
/etc/dh2ifolder on the primary pod.kubectl cp read-only-access.sql dxemssql-0:/etc/dh2iWhich pod is my primary?If you're unsure which pod is your primary, run the command
dxcli get-ags-detailand search for<replica server name>after<role_desc>.kubectl exec dxemssql-0 -- dxcli get-ags-detail vhost1 ags1 | grep PRIMARYIf
dxemssql-0isn't the primary, you can fail your availability group back todxemssql-0using the commanddxcli vhost-start-node.kubectl exec dxemssql-0 -- dxcli vhost-start-node vhost1 dxemssql-0 -
Execute the T-SQL, replacing
<sql_password>with the SQL Server password.kubectl exec dxemssql-0 -- /opt/mssql-tools/bin/sqlcmd \
-S localhost -U SA -P <sql_password> -i /etc/dh2i/read-only-access.sql
Verify Access
-
Copy the YAML configuration below to a file. This will create a
mssql-toolspod within the Kubernetes cluster which you will use to access the AG. Alternatively, you can installsqlcmdon a remote machine outside the cluster.apiVersion: v1
kind: Pod
metadata:
name: mssql-tools
spec:
containers:
- name: sqlcmd
image: mcr.microsoft.com/mssql-tools
command: ["/bin/bash", "-c", "--"]
args: ["while true; do sleep 30; done;"]
imagePullPolicy: IfNotPresent
restartPolicy: Always -
Apply the configuration.
kubectl apply -f mssql-tools.yaml -
Create a database table on the primary using the
mssql-toolspod. Replace<primary_lb_ip>with the IP address of your primary pod, and<sql_password>with the SQL Server password.kubectl exec mssql-tools -- /opt/mssql-tools/bin/sqlcmd \
-S <primary_lb_ip> -U sa -P <sql_password> -Q "CREATE TABLE db1.dbo.table1 (column_1 int)"infoIf a database isn't available in the AG, create one using the instructions from the availability group database section.
-
Now read the table contents from one of the secondaries, using the
<secondary_lb_ip>instead.kubectl exec mssql-tools -- /opt/mssql-tools/bin/sqlcmd \
-S <secondary_lb_ip> -U sa -P <sql_password> -K ReadOnly \
-d db1 -Q "SELECT column_1 FROM db1.dbo.table1"sqlcmd parameterssqlcmdrequires some additional parameters:-K ReadOnlyspecifies read-only intent.-d db1issues aUSEdatabase_name statement for the connection.
-
Finally, attempt to read from the primary using ReadOnly intent. This command should fail.
kubectl exec mssql-tools -- /opt/mssql-tools/bin/sqlcmd \
-S <primary_lb_ip> -U sa -P <sql_password> -K ReadOnly \
-d db1 -Q "SELECT column_1 FROM db1.dbo.table1"
Configure Read-Only Routing with Load Balancing
Now that your replicas are configured for read-only access, you can configure the availability group so listener connections with read-only intent are routed to the secondary replicas.
-
Verify your listener port using the command
dxcli get-ags-detail.kubectl exec dxemssql-0 -- dxcli get-ags-detail vhost1 ags1 | grep listener -
Copy the T-SQL below into a file, replacing
<lb_ip>with the IP address of the load balancer associated with that pod.USE [master]
GO
--Update the read-only routing URLs
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
SECONDARY_ROLE (READ_ONLY_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
SECONDARY_ROLE (READ_ONLY_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
SECONDARY_ROLE (READ_ONLY_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
--Update the read-only routing list with AG members in load-balanced configuration
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-1', 'dxemssql-2')))
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-0', 'dxemssql-2')))
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-0', 'dxemssql-1')))
)
GOrouting priorityThe normal behavior of SQL Server is to route incoming read-only connections to the first available replica on the list, with no regard to load balancing. Adding an extra set of parentheses around the two secondary replicas configures SQL Server to load balance the two secondaries. This feature is included in the T-SQL example above.
-
Copy the T-SQL to the
/etc/dh2ifolder on the primary pod.kubectl cp read-routing.sql dxemssql-0:/etc/dh2i -
Execute the T-SQL.
kubectl exec dxemssql-0 -- /opt/mssql-tools/bin/sqlcmd \
-S localhost -U SA -P <sql_password> -i /etc/dh2i/read-routing.sql -
Test a read-intent connection to the AG listener on
dxemssql-0. This command should output the name of one of the secondaries.kubectl exec mssql-tools -- /opt/mssql-tools/bin/sqlcmd \
-S <primary_lb_ip>,<ag_listener> -U sa -P <sql_password> -M \
-K ReadOnly -d db1 -Q "SELECT column_1 FROM db1.dbo.table1; SELECT @@SERVERNAME"
Enable Read-Write Routing to the Primary
In addition to routing read-only intent connections from the primary to the secondaries, you can also route read-write intent connections from the secondaries to the primary.
-
Copy the T-SQL below to a file, replacing
<lb_ip>with the IP address of the load balancer associated with that pod.USE [master]
GO
--Allow all connections in the secondary role instead of READ_ONLY
--Read-write intent connections will be routed to the primary
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
)
GO
--Update the read-write routing URLs
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
PRIMARY_ROLE (READ_WRITE_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
PRIMARY_ROLE (READ_WRITE_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
PRIMARY_ROLE (READ_WRITE_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO -
Copy the T-SQL to the primary pod.
kubectl cp write-routing.sql dxemssql-0:/etc/dh2i -
Execute the T-SQL.
kubectl exec dxemssql-0 -- /opt/mssql-tools/bin/sqlcmd \
-S localhost -U SA -P <sql_password> -i /etc/dh2i/write-routing.sql -
Test a read-write connection to the database on
dxemssql-1ordxemssql-2.kubectl exec mssql-tools -- /opt/mssql-tools/bin/sqlcmd \
-S <secondary_lb_ip> -U sa -P <sql_password> -M -d db1 \
-Q "CREATE TABLE db1.dbo.table2 (column_1 int); SELECT @@SERVERNAME"
Add Replicas to the Cluster
-
To scale a StatefulSet up, run the command
kubectl scalealong with the total quantity of replicas for the StatefulSet.kubectl scale --replicas=4 statefulset/dxemssql -
Wait for the pod status to change to
ready.kubectl get pods -
Activate the DxEnterprise license for the fourth pod using the command
dxcli activate-server.kubectl exec dxemssql-3 -- dxcli activate-server AAAA-BBBB-CCCC-DDDD -
Join the fourth pod to the DxEnterprise cluster using the command
dxcli join-cluster-ex.kubectl exec dxemssql-3 -- dxcli join-cluster-ex \
match.dh2i.com XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX truetipIf the join command fails, try setting an OTPK for the DxEnterprise cluster again using the command
dxcli set-otpkand use that OTPK to join the cluster.kubectl exec dxemssql-0 -- dxcli set-otpk -
Add the fourth pod to the existing availability group using the command
dxcli add-ags-node.kubectl exec dxemssql-3 -- dxcli add-ags-node vhost1 ags1 \
"dxemssql-3|mssqlserver|sa|6pnFaDrRS+W/F+dkRuPKAA==|5022|synchronous_commit|50224"
Update Network Configuration
After you add the pod to the cluster, you might need to update one or more service configurations.
Add to Headless Service File
Headless services allow DNS resolution in situations where NAT matchmaking may be unavailable. An initial configuration is provided in the headless service section, which can be expanded upon with the following YAML:
#Headless services for local connections/resolution
apiVersion: v1
kind: Service
metadata:
name: dxemssql-3
spec:
clusterIP: None
selector:
statefulset.kubernetes.io/pod-name: dxemssql-3
ports:
- name: dxl
protocol: TCP
port: 7979
- name: dxc-tcp
protocol: TCP
port: 7980
- name: dxc-udp
protocol: UDP
port: 7981
- name: sql
protocol: TCP
port: 1433
- name: listener
protocol: TCP
port: 14033
Use 3 dashes (---) to separate service definitions.
Paste that YAML into the headless-services.yaml file. Then run kubectl apply to update the service configuration.
kubectl apply -f headless-services.yaml
Add the Pod to the Load Balancer Configuration
If each pod in the cluster is externally accessible with an IP address, you'll need to add the new pod to the service definition to give it a public IP too.
App-wide load balancers (i.e., selector: app: dxemssql) do not need to be updated to route to the new pod: Kubernetes should route to them automatically. However, any services that use a pod selector (statefulset.kubernetes.io/pod-name) will need the new pod added to the configuration file.
Follow the instructions in the networking guide to make the new pod externally accessible with its own IP address.
Update Read-Only & Read-Write Replicas
Please review the configuring replicas section before continuing. Also, verify the details of your service configuration.
Obtain the IP addresses for each of the load balancers assigned to your pods.
kubectl get service
Update Read-Only Access
Adding read-only/read-write access to the replica follows the same steps in the Configure Replicas for Read-Only Access section. The only modification required is to use the T-SQL below instead of the T-SQL supplied in that section.
Copy the T-SQL below into a file. Use the steps from the remainder of the section to apply the configuration.
USE [master]
GO
--Only allow read-write connections in the primary role
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
PRIMARY_ROLE (ALLOW_CONNECTIONS = READ_WRITE)
)
GO
--Allow read-only connections in the secondary role
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = READ_ONLY)
)
GO
Update Read-Only Routing
In addition to adding a read-only routing URL, the read-only routing list must be updated for each AG member to reflect the new membership.
Copy the T-SQL below into a file, replacing <lb_ip> with the IP address of the load balancer associated with that pod. Use the steps from the remainder of the configure read-only routing section to apply the configuration.
USE [master]
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
SECONDARY_ROLE (READ_ONLY_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
--Update the read-only routing list with AG members in load-balanced configuration
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-0' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-1', 'dxemssql-2', 'dxemssql-3')))
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-1' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-0', 'dxemssql-2', 'dxemssql-3')))
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-2' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-0', 'dxemssql-1', 'dxemssql-3')))
)
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
PRIMARY_ROLE (READ_ONLY_ROUTING_LIST = (('dxemssql-0', 'dxemssql-1', 'dxemssql-2')))
)
GO
Update Read-Write Routing
Adding read-write routing to the replica follows the same steps in the read-write routing section. The only modification required is to use the T-SQL below instead of the T-SQL supplied in that section.
Copy the T-SQL below to a file, replacing <lb_ip> with the IP address of the load balancer associated with that pod. Use the steps from the remainder of the section to apply the new read-write routing configuration.
USE [master]
GO
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
SECONDARY_ROLE (ALLOW_CONNECTIONS = ALL)
)
GO
--Update the read-write routing URLs
ALTER AVAILABILITY GROUP AGS1 MODIFY REPLICA ON N'dxemssql-3' WITH (
PRIMARY_ROLE (READ_WRITE_ROUTING_URL = 'TCP://<lb_ip>:1433')
)
GO
Remove Replicas From the Cluster
If you plan to keep save your volumes for the AG member, you only need to run the kubectl scale command. Otherwise, if you choose to scale down your replica and delete your volumes for that pod, you should deactivate the DxEnterprise license beforehand.
-
Run the command
dxcli deactivate-serverto deactivate the license for that pod.kubectl exec dxemssql-0 -- dxcli deactivate-server dxemssql-3 -
Scale the StatefulSet down using
kubectl scale.kubectl scale --replicas=3 statefulset/dxemssql -
Delete the persistent volume claims for the pod.
kubectl delete pvc dxe-dxemssql-3
kubectl delete pvc mssql-dxemssql-3dangerDeleting volumes can result in unintended data loss.
Conclusion
Many organizations want to transform their business operations by deploying a new class of applications based on stateful containers such as such as SQL Server Docker containers in Kubernetes. Their challenge is how do they ensure these to-be-deployed systems meet their tier 1 service-level agreements (SLA) for high availability and disaster recovery requirements.
In our use case we showed how a database architect can setup a SQL Server AG in Kubernetes using the enhanced SQL Server clustering capabilities of DH2i DxEnterprise (DxE) Smart High Availability software. Using DxE with SQL Server Docker containers in Azure Kubernetes Service (AKS) the database architect can achieve near-zero database-level high availability and business continuity with SQL Server Docker container-based applications.
Additionally, in our use case we showed how with DxE, SQL Server Docker containers and AKS a database architect can easily and quickly provision and scale the SQL Server AG with readable secondary replicas to support new workload requests. Lastly, we showed application resilience by demonstrating fully automatic near-zero failover of the primary database replica across availability zones.