Skip to main content
Version: v2.0-RC

Configure Per-pod Services using Service Templates

The DxSqlAg custom resource has a section called serviceTemplates that describes what a service should look like, somewhat like how a podTemplate works.

The DxSqlAg custom resource uses the service template to create a service for each pod in the cluster. This is useful for configuring load balancers and other services for the pods.

Prerequisites

Service Template Overview

ServiceTemplates let you describe one or more Kubernetes Service definitions that will be instantiated per pod in the DxSqlAg StatefulSet. Service templates may also be added or removed to an existing DxSqlAg.

For each ServiceTemplateSpec in spec.serviceTemplates, the operator creates a Service for each replica in the StatefulSet.

Template to Service Mapping

  • You define templates under spec.serviceTemplates[].
  • For each template and each replica, the operator computes:
    • serviceName = <templateName>-<podName>
  • A Service is created with that name and a selector that targets exactly that pod.

serviceTemplates is located underneath the top-level spec:

apiVersion: dh2i.com/v1
kind: DxSqlAg
metadata:
name: dxsqlag
spec:
statefulSetSpec:
synchronousReplicas: 3
# ...
serviceTemplates:
- name: test
# ...
  • spec.sqlAgConfiguration.*Replicas (synchronous, asynchronous, and configuration-only) control how many pods (and therefore how many services per template) are created.
  • spec.serviceTemplates[] holds one or more ServiceTemplateSpec objects.

ServiceTemplateSpec

Each ServiceTemplateSpec is roughly equivalent to a V1ServiceSpec plus some metadata.

spec:
serviceTemplates:
- name: test
metadata:
labels:
role: sql
annotations:
service.beta.kubernetes.io/azure-load-balancer-internal: "true"

type: LoadBalancer
ports:
- name: sql
port: 1433
targetPort: 1433

allocateLoadBalancerNodePorts: true
externalTrafficPolicy: Local
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
ipFamilyPolicy: SingleStack
loadBalancerClass: example.com/my-lb
publishNotReadyAddresses: false
sessionAffinity: None
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800
Per-pod Selector Behavior

When generating services, the operator:

  1. Copies selector from the template (if provided) to the Service: selector.

  2. Ensures the selector includes:

    statefulset.kubernetes.io/pod-name = <podName>

This means each generated Service ends up targeting exactly one pod (the corresponding StatefulSet replica). You are responsible for ensuring the rest of the selector matches the pod labels (or you can omit selector and rely only on statefulset.kubernetes.io/pod-name if that's sufficient for your use case).

Per-Service Customizations

perServiceCustomizations lets you provide fine-grained overrides for a specific generated Service, e.g. to add an IP, add extra labels, or set different LB ranges.

Each entry is a PerServiceCustomizationSpec:

spec:
serviceTemplates:
- name: test
type: LoadBalancer
ports:
- name: sql
port: 1433
targetPort: 1433

perServiceCustomizations:
- name: test-dxsqlag-0 # full service name
metadata:
annotations:
monitoring.example.com/scrape: "true"
labels:
traffic-profile: "primary"
clusterIP: 10.0.0.50
externalIPs:
- 203.0.113.10
loadBalancerIP: 203.0.113.20
loadBalancerSourceRanges:
- 10.0.0.0/24

- name: test-dxsqlag-1
metadata:
labels:
traffic-profile: "secondary"
Name Matching

PerServiceCustomizationSpec.name is required and must match the full name of the generated service:

<templateName>-<statefulSetName>-<ordinal>

For example:

  • name: dxsqlag
  • spec.sqlAgConfiguration.synchronousReplicas: 3
  • Template: name: test

Generated services:

  • test-dxsqlag-0
  • test-dxsqlag-1
  • test-dxsqlag-2

If no PerServiceCustomizationSpec exists for a given service name, the service is created using only the template.

Ports Merge Behavior

If perServiceCustomizations[].ports is set, for each port:

  1. The operator looks for an existing port in serviceTemplate.ports[] with the same name.
  2. If a match is found, the template port is removed.
  3. The per-service port is then added.

This means per-service ports override any same-named template ports and can also add entirely new ports for that particular service instance.

Full Example

This example has a DxSqlAg with three synchronous replicas and one LoadBalancer service template, plus one per-service override that tweaks ports:

apiVersion: dh2i.com/v1
kind: DxSqlAg
metadata:
name: dxsqlag
spec:
# Your DxSqlAg StatefulSet configuration
statefulSetSpec:
# ...
sqlAgConfiguration:
synchronousReplicas: 3
serviceTemplates:
- name: test
metadata:
labels:
role: sql
type: LoadBalancer
ports:
- name: sql
protocol: TCP
port: 1433
targetPort: 1433

perServiceCustomizations:
- name: test-dxsqlag-0
metadata:
labels:
traffic-profile: "primary"
ports:
# Overrides the template's "sql" port for this service only
- name: sql
protocol: TCP
port: 1433
targetPort: 1433
nodePort: 31000

# Adds an extra health port for this service only
- name: health
protocol: TCP
port: 59999
targetPort: 59999

Result:

  • Services test-dxsqlag-0, test-dxsqlag-1, test-dxsqlag-2 are created.
  • All three are LoadBalancer services with the base template config.
  • test-dxsqlag-0:
    • Gets additional labels and LB source ranges from its PerServiceCustomizationSpec.
    • Has its sql port replaced by the per-service definition.
    • Gets an extra health port not present on other replicas.
  • test-dxsqlag-1 and test-dxsqlag-2 use only the template ports.

Automatic Load Balancer Service Template

The operator can automatically create a default LoadBalancer service template for you when createLoadBalancers is enabled in spec.sqlAgConfiguration.

When the Default Load Balancer Template is Created

When the default LB template is created, the operator looks for an existing template named lb:

  • If a template with name: lb already exists, the operator does nothing and assumes you know what you’re doing.

  • If no lb template is found, the operator appends a new ServiceTemplateSpec to your DxSqlAg:

    serviceTemplates:
    - name: lb
    type: LoadBalancer
    ports:
    - name: dxlmonitor
    port: 7979
    targetPort: 7979
    - name: dxcmonitor
    port: 7980
    targetPort: 7980
    - name: dxcmonitor-udp
    port: 7981
    targetPort: 7981
    protocol: UDP
    - name: mssql
    port: 1433
    targetPort: 1433

Customizing the Auto-Created Load Balancers

You have a couple of options if the default behavior isn’t exactly what you want:

  1. Override the lb template entirely. You can define your own ServiceTemplateSpec with name: lb. The operator will see it and skip adding the default.

  2. Use perServiceCustomizations on the lb template:

    Once the lb template exists (either created by you or by the operator), you can use perServiceCustomizations to tweak ports or metadata for specific replicas:

    spec:
    serviceTemplates:
    - name: lb
    type: LoadBalancer
    ports:
    - name: mssql
    port: 1433
    targetPort: 1433

    perServiceCustomizations:
    - name: lb-dxsqlag-0
    ports:
    - name: sql
    port: 1433
    targetPort: 1433
    nodePort: 31000 # override for this replica only
    - name: health
    port: 59999
    targetPort: 59999

In short, createLoadBalancers gives you a sane default LoadBalancer service template for DxEnterprise and SQL Server, but you can override the template completely or customize individual services as needed.

Removing Service Templates

To remove all services created from a given template, you delete the template entry from spec.serviceTemplates. The operator will reconcile and remove all corresponding templated services.

tip

If you created a serviceTemplate using spec.createLoadBalancers, flip that value to false (or remove it from the spec), otherwise the operator will re-create the service template.

Additional Information