A while back I wrote about how to use a Cross Platform (or Clusterless) Availability Group to seed a database from a Windows SQL instance into a pod in Kubernetes.
I was talking with a colleague last week and they asked, “What if the existing Windows instance is already in an Availability Group?”
This is a fair question, as it’s fairly rare (in my experience) to run a standalone SQL instance in production…most instances are in some form of HA setup, be it a Failover Cluster Instance or an Availability Group.
Failover Cluster Instances will work with a clusterless Availability Group but it’s a different story when it comes to existing Availability Groups.
A Linux node cannot be added to an existing Windows Availability Group (trust me, I tried for longer than I’m going to admit) so the only way to do it is to use a Distributed Availability Group.
So let’s run through the process!
Here is the existing Windows Availability Group: –
Just a standard, 2 node AG with one database already synchronized across the nodes. It’s that database we are going to seed over to the pod running on the Kubernetes cluster using a Distributed Availability Group.
So here’s the Kubernetes cluster: –
kubectl get nodes
Four nodes, one control plane node and three worker nodes.
OK, so first thing to do is deploy a statefulset running one SQL Server pod (using a file called sqlserver-statefulset.yaml): –
kubectl apply -f .sqlserver-statefulset.yaml
Here’s the manifest of the statefulset: –
apiVersion: apps/v1 kind: StatefulSet metadata: name: mssql-statefulset spec: serviceName: "mssql" replicas: 1 podManagementPolicy: Parallel selector: matchLabels: name: mssql-pod template: metadata: labels: name: mssql-pod annotations: stork.libopenstorage.org/disableHyperconvergence: "true" spec: securityContext: fsGroup: 10001 hostAliases: - ip: "10.225.115.129" hostnames: - "z-ap-sql-10" containers: - name: mssql-container image: mcr.microsoft.com/mssql/server:2022-CU15-ubuntu-20.04 ports: - containerPort: 1433 name: mssql-port env: - name: MSSQL_PID value: "Developer" - name: ACCEPT_EULA value: "Y" - name: MSSQL_AGENT_ENABLED value: "1" - name: MSSQL_ENABLE_HADR value: "1" - name: MSSQL_SA_PASSWORD value: "Testing1122" volumeMounts: - name: sqlsystem mountPath: /var/opt/mssql - name: sqldata mountPath: /var/opt/sqlserver/data volumeClaimTemplates: - metadata: name: sqlsystem spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: mssql-sc - metadata: name: sqldata spec: accessModes: - ReadWriteOnce resources: requests: storage: 25Gi storageClassName: mssql-sc
Like my last post, this is pretty stripped down. No resources limits, tolerations etc. It has two persistent volumes, one for the system databases and one for the user databases from a storage class already configured in the cluster.
One thing to note: –
hostAliases: - ip: "10.225.115.129" hostnames: - "z-ap-sql-10"
Here an entry in the pod’s hosts file is being created for the listener of the Windows Availability Group.
Next thing to do is deploy two services, one so that we can connect to the SQL instance (on port 1433) and one for the AG (port 5022): –
kubectl apply -f .sqlserver-services.yaml
Here’s the manifest for the services: –
apiVersion: v1 kind: Service metadata: name: mssql-service spec: ports: - name: mssql-ports port: 1433 targetPort: 1433 selector: name: mssql-pod type: LoadBalancer --- apiVersion: v1 kind: Service metadata: name: mssql-ha-service spec: ports: - name: mssql-ha-ports port: 5022 targetPort: 5022 selector: name: mssql-pod type: LoadBalancer
NOTE – We could use just one service with multiple ports configured but I’m keeping them separate here to try and keep things as clear as possible.
Check that everything looks OK: –
kubectl get all
Now we need to create master key, login, and user in all instances: –
CREATE MASTER KEY ENCRYPTION BY PASSWORD = '<C0m9L3xP@55w0rd!>'; CREATE LOGIN [dbm_login] WITH PASSWORD = '<C0m9L3xP@55w0rd!>'; CREATE USER dbm_user FOR LOGIN dbm_login;
Then create a certificate in the SQL instance in the pod: –
CREATE CERTIFICATE dbm_certificate WITH SUBJECT = 'Mirroring_certificate', EXPIRY_DATE = '20301031'
Backup that certificate: –
BACKUP CERTIFICATE dbm_certificate TO FILE = '/var/opt/mssql/data/dbm_certificate.cer' WITH PRIVATE KEY ( FILE = '/var/opt/mssql/data/dbm_certificate.pvk', ENCRYPTION BY PASSWORD = '<C0m9L3xP@55w0rd!>' );
Copy the certificate locally: –
kubectl cp mssql-statefulset-0:var/opt/mssql/data/dbm_certificate.cer ./dbm_certificate.cer -n prod kubectl cp mssql-statefulset-0:var/opt/mssql/data/dbm_certificate.pvk ./dbm_certificate.pvk -n prod
And then copy the files to the Windows boxes: –
Copy-Item dbm_certificate.cer \z-ap-sql-02E$SQLBackup1 -Force Copy-Item dbm_certificate.pvk \z-ap-sql-02E$SQLBackup1 -Force Copy-Item dbm_certificate.cer \z-ap-sql-03E$SQLBackup1 -Force Copy-Item dbm_certificate.pvk \z-ap-sql-03E$SQLBackup1 -Force
Once the files are on the Windows boxes, we can create the certificate in each Windows SQL instance: –
CREATE CERTIFICATE dbm_certificate AUTHORIZATION dbm_user FROM FILE = 'E:SQLBackup1dbm_certificate.cer' WITH PRIVATE KEY ( FILE = 'E:SQLBackup1dbm_certificate.pvk', DECRYPTION BY PASSWORD = '' )
OK, great! Now we need to create a mirroring endpoint in the SQL instance in the pod: –
CREATE ENDPOINT [Hadr_endpoint] STATE = STARTED AS TCP ( LISTENER_PORT = 5022, LISTENER_IP = ALL) FOR DATA_MIRRORING ( ROLE = ALL, AUTHENTICATION = WINDOWS CERTIFICATE [dbm_certificate], ENCRYPTION = REQUIRED ALGORITHM AES ); ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED; GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [dbm_login];
There are already endpoints in the Windows instances, but we need to update them to use the certificate for authentication: –
ALTER ENDPOINT [Hadr_endpoint] STATE = STARTED AS TCP ( LISTENER_PORT = 5022, LISTENER_IP = ALL) FOR DATABASE_MIRRORING ( AUTHENTICATION = WINDOWS CERTIFICATE [dbm_certificate] , ENCRYPTION = REQUIRED ALGORITHM AES ); GRANT CONNECT ON ENDPOINT::[Hadr_endpoint] TO [dbm_login];
Now we can create a one node Clusterless Availability Group in the SQL instance in the pod: –
CREATE AVAILABILITY GROUP [AG2] WITH (CLUSTER_TYPE=NONE) FOR REPLICA ON 'mssql-statefulset-0' WITH ( ENDPOINT_URL = 'TCP://mssql-statefulset-0.com:5022', FAILOVER_MODE = MANUAL ,AVAILABILITY_MODE = SYNCHRONOUS_COMMIT ,BACKUP_PRIORITY = 50 ,SEEDING_MODE = AUTOMATIC ,SECONDARY_ROLE(ALLOW_CONNECTIONS = NO) )
No listener here, we are going to use the mssql-ha-service as the endpoint for the Distributed Availability Group.
OK, so on the primary node of the Windows Availability Group, we can create the Distributed Availability Group: –
CREATE AVAILABILITY GROUP [DistributedAG] WITH (DISTRIBUTED) AVAILABILITY GROUP ON 'AG1' WITH ( LISTENER_URL = 'tcp://Z-AP-SQL-10:5022', AVAILABILITY_MODE = ASYNCHRONOUS_COMMIT, FAILOVER_MODE = MANUAL, SEEDING_MODE = AUTOMATIC ), 'AG2' WITH ( LISTENER_URL = 'tcp://10.225.115.131:5022', AVAILABILITY_MODE = ASYNCHRONOUS_COMMIT, FAILOVER_MODE = MANUAL, SEEDING_MODE = AUTOMATIC );
We could use a host file entry for the URL in AG2 (I did that in the previous post) but here we’ll just use the IP address of the mssql-ha-service.
OK, nearly there! We now have to join the Availability Group in the SQL instance in the pod: –
ALTER AVAILABILITY GROUP [DistributedAG] JOIN AVAILABILITY GROUP ON 'AG1' WITH ( LISTENER_URL = 'tcp://Z-AP-SQL-10:5022', AVAILABILITY_MODE = ASYNCHRONOUS_COMMIT, FAILOVER_MODE = MANUAL, SEEDING_MODE = AUTOMATIC ), 'AG2' WITH ( LISTENER_URL = 'tcp://10.225.115.131:5022', AVAILABILITY_MODE = ASYNCHRONOUS_COMMIT, FAILOVER_MODE = MANUAL, SEEDING_MODE = AUTOMATIC );
And that should be it! If we now connect to the SQL instance in the pod…the database is there!
There it is! OK, one thing I haven’t gone through here is how to get auto-seeding working from Windows into a Linux SQL instance…but I went through the process in my previous post here.
So that’s how to seed a database from a SQL instance that is in a Windows Availability Group into a SQL instance running in a pod in a Kubernetes cluster using a Distributed Availability Group!
Phew! Thanks for reading!