Complete lab setup for monitoring SQL Server on Kubernetes with Datadog Agent, featuring custom metric collection based on the official Datadog documentation.
This lab demonstrates:
- SQL Server 2022 deployment on Minikube
- Datadog Agent installation via Helm with SQL Server integration
- Autodiscovery annotations v2 for automatic integration configuration
- Custom SQL queries to collect business metrics from application data
- Working solution for ODBC Driver 18 SSL certificate issues
- Minikube installed and running
- kubectl configured
- Helm 3.x installed
- Datadog API key
minikube start --driver=docker --cpus=4 --memory=7000kubectl create namespace datadog
kubectl create secret generic datadog-secret \
--from-literal api-key=YOUR_DATADOG_API_KEY \
-n datadogCreate datadog-values.yaml:
datadog:
site: "datadoghq.com"
apiKeyExistingSecret: "datadog-secret"
clusterName: "sqlserver-lab"
kubelet:
tlsVerify: false
logs:
enabled: true
containerCollectAll: true
clusterAgent:
enabled: true
agents:
enabled: trueInstall with Helm:
helm repo add datadog https://helm.datadoghq.com
helm repo update
helm install datadog-agent datadog/datadog \
-f datadog-values.yaml \
-n datadogCreate sqlserver-deployment.yaml:
---
apiVersion: v1
kind: ConfigMap
metadata:
name: sqlserver-init
namespace: default
data:
init.sql: |
-- Create Datadog user
CREATE LOGIN datadog WITH PASSWORD = 'Password123!';
CREATE USER datadog FOR LOGIN datadog;
GRANT SELECT on sys.dm_os_performance_counters to datadog;
GRANT VIEW SERVER STATE to datadog;
GRANT VIEW ANY DEFINITION to datadog;
GO
-- Create test database
CREATE DATABASE testdb;
GO
USE testdb;
GO
-- Create orders table
CREATE TABLE orders (
order_id INT PRIMARY KEY IDENTITY(1,1),
customer_name VARCHAR(100),
order_date DATETIME DEFAULT GETDATE(),
total_amount DECIMAL(10,2),
status VARCHAR(20)
);
GO
-- Insert sample data
INSERT INTO orders (customer_name, total_amount, status) VALUES
('Alice Johnson', 150.50, 'completed'),
('Bob Smith', 200.00, 'completed'),
('Charlie Brown', 75.25, 'pending'),
('Diana Prince', 300.00, 'completed'),
('Eve Adams', 120.75, 'cancelled');
GO
-- Grant access to testdb
USE testdb;
GO
CREATE USER datadog FOR LOGIN datadog;
GRANT SELECT ON SCHEMA::dbo TO datadog;
GO
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: sqlserver
namespace: default
labels:
app: sqlserver
spec:
replicas: 1
selector:
matchLabels:
app: sqlserver
template:
metadata:
labels:
app: sqlserver
tags.datadoghq.com/service: "sqlserver"
tags.datadoghq.com/env: "lab"
annotations:
ad.datadoghq.com/sqlserver.checks: |
{
"sqlserver": {
"init_config": {},
"instances": [
{
"host": "%%host%%,1433",
"username": "datadog",
"password": "Password123!",
"connector": "odbc",
"driver": "ODBC Driver 18 for SQL Server",
"connection_string": "TrustServerCertificate=yes;Trusted_Connection=no;",
"tags": ["env:lab", "cluster:sqlserver-lab"],
"custom_queries": [
{
"query": "SELECT status, COUNT(*) as order_count, SUM(total_amount) as total_revenue FROM testdb.dbo.orders GROUP BY status",
"columns": [
{"name": "status", "type": "tag"},
{"name": "order.count", "type": "gauge"},
{"name": "order.revenue", "type": "gauge"}
],
"tags": ["query:order_stats"]
},
{
"query": "SELECT COUNT(*) as pending_orders FROM testdb.dbo.orders WHERE status = 'pending'",
"columns": [
{"name": "order.pending_count", "type": "gauge"}
],
"tags": ["query:pending_orders"]
}
]
}
]
}
}
ad.datadoghq.com/sqlserver.logs: '[{"source": "sqlserver", "service": "sqlserver"}]'
spec:
containers:
- name: sqlserver
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
name: sqlserver
env:
- name: ACCEPT_EULA
value: "Y"
- name: SA_PASSWORD
value: "YourStrong@Passw0rd"
- name: MSSQL_PID
value: "Developer"
resources:
requests:
memory: "2Gi"
cpu: "1000m"
limits:
memory: "4Gi"
cpu: "2000m"
volumeMounts:
- name: init-script
mountPath: /docker-entrypoint-initdb.d
volumes:
- name: init-script
configMap:
name: sqlserver-init
---
apiVersion: v1
kind: Service
metadata:
name: sqlserver
namespace: default
spec:
selector:
app: sqlserver
ports:
- protocol: TCP
port: 1433
targetPort: 1433
type: ClusterIPDeploy SQL Server:
kubectl apply -f sqlserver-deployment.yamlWait for the pod to be ready:
kubectl wait --for=condition=ready pod -l app=sqlserver --timeout=180sInitialize the database:
kubectl exec deployment/sqlserver -- \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'YourStrong@Passw0rd' -C \
-i /docker-entrypoint-initdb.d/init.sqlGrant testdb permissions:
kubectl exec deployment/sqlserver -- \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P 'YourStrong@Passw0rd' -C \
-Q "USE testdb; CREATE USER datadog FOR LOGIN datadog; GRANT SELECT ON SCHEMA::dbo TO datadog;"kubectl exec -n datadog daemonset/datadog-agent -c agent -- \
agent status | grep -A 20 "sqlserver"Expected output:
sqlserver (22.9.1)
------------------
Instance ID: sqlserver:xxxxx [OK]
Total Runs: X
Metric Samples: Last Run: 264+
Service Checks: Last Run: 1
Average Execution Time : ~60ms
Last Successful Execution Date : <timestamp>
kubectl exec -n datadog daemonset/datadog-agent -c agent -- \
agent check sqlserverkubectl exec -n datadog daemonset/datadog-agent -c agent -- \
sh -c "agent check sqlserver 2>&1 | grep -i 'sqlserver.order'"You should see:
sqlserver.order.count- Order counts by status (completed, pending, cancelled)sqlserver.order.revenue- Revenue by statussqlserver.order.pending_count- Total pending orders
kubectl exec deployment/sqlserver -- \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U datadog -P 'Password123!' -C \
-Q "SELECT status, COUNT(*) as order_count, SUM(total_amount) as total_revenue FROM testdb.dbo.orders GROUP BY status"Expected result:
status order_count total_revenue
-------------------- ----------- -------------
cancelled 1 120.75
completed 3 650.50
pending 1 75.25
| Metric Name | Type | Description | Tags |
|---|---|---|---|
sqlserver.order.count |
gauge | Number of orders by status | status:completed/pending/cancelled, query:order_stats |
sqlserver.order.revenue |
gauge | Total revenue by status | status:completed/pending/cancelled, query:order_stats |
sqlserver.order.pending_count |
gauge | Total count of pending orders | query:pending_orders |
The integration also collects 260+ standard metrics including:
sqlserver.stats.connections- Active connectionssqlserver.buffer.cache_hit_ratio- Buffer cache efficiencysqlserver.memory.database_cache- Database cache memorysqlserver.database.files.size- Database file sizessqlserver.tempdb.file_space_usage.*- TempDB usage- Many more from
sys.dm_os_performance_counters
All metrics are tagged with:
cluster:sqlserver-labenv:lab- Kubernetes tags (namespace, pod, deployment, etc.)
- Database-specific tags
The key to this setup is using Autodiscovery annotations v2 format, which provides a cleaner structure:
ad.datadoghq.com/sqlserver.checks: |
{
"sqlserver": {
"init_config": {},
"instances": [...]
}
}For ODBC Driver 18 for SQL Server (the only driver available in Datadog Agent), you must include:
"driver": "ODBC Driver 18 for SQL Server"
"connection_string": "TrustServerCertificate=yes;Trusted_Connection=no;"Why these parameters?
TrustServerCertificate=yes- Required for Driver 18 to accept self-signed certificatesTrusted_Connection=no- Required for SQL authentication (not Windows auth)- Driver 17 is NOT available in the Datadog Agent container
Custom queries follow the Datadog documentation format:
"custom_queries": [
{
"query": "SELECT column1, column2, column3 FROM table WHERE condition",
"columns": [
{"name": "column1_name", "type": "tag"}, # Becomes a tag
{"name": "metric.name", "type": "gauge"}, # Becomes a gauge metric
{"name": "metric.count", "type": "rate"} # Becomes a rate metric
],
"tags": ["query:descriptive_name"] # Additional tags
}
]Column Types:
tag- Value becomes a tag on the metricgauge- Absolute value metricrate- Rate of change metriccount- Monotonic counter
Error:
SSL Provider: [error:0A000086:SSL routines::certificate verify failed:self-signed certificate]
Solution:
Add to connection_string:
"connection_string": "TrustServerCertificate=yes;Trusted_Connection=no;"Error:
configured odbc driver ODBC Driver 17 for SQL Server not in list of installed drivers
Solution: Use ODBC Driver 18 (the only driver in Datadog Agent):
"driver": "ODBC Driver 18 for SQL Server"Error:
The server principal "datadog" is not able to access the database "testdb"
Solution: Grant database-level permissions:
USE testdb;
CREATE USER datadog FOR LOGIN datadog;
GRANT SELECT ON SCHEMA::dbo TO datadog;Symptoms:
- Standard metrics work but custom queries don't appear
- No errors in logs
Checklist:
- β Verify datadog user has SELECT permission on tables
- β Check SQL syntax is valid
- β Ensure column names match exactly
- β Test query manually with datadog user
- β Verify annotations v2 format is correct (valid JSON)
- β
Check pod annotations:
kubectl describe pod -l app=sqlserver
# Check agent status
kubectl exec -n datadog daemonset/datadog-agent -c agent -- agent status
# Run manual check with verbose output
kubectl exec -n datadog daemonset/datadog-agent -c agent -- agent check sqlserver -l debug
# View agent logs
kubectl logs -n datadog daemonset/datadog-agent -c agent --tail=100
# Check SQL Server logs
kubectl logs -l app=sqlserver --tail=50
# Test database connection
kubectl exec deployment/sqlserver -- \
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U datadog -P 'Password123!' -C \
-Q "SELECT @@VERSION"
# View pod annotations
kubectl get pod -l app=sqlserver -o yaml | grep -A 50 "annotations:"CREATE TABLE orders (
order_id INT PRIMARY KEY IDENTITY(1,1),
customer_name VARCHAR(100),
order_date DATETIME DEFAULT GETDATE(),
total_amount DECIMAL(10,2),
status VARCHAR(20) -- 'completed', 'pending', 'cancelled'
);Sample Data:
| order_id | customer_name | order_date | total_amount | status |
|---|---|---|---|---|
| 1 | Alice Johnson | 2025-10-24 | 150.50 | completed |
| 2 | Bob Smith | 2025-10-24 | 200.00 | completed |
| 3 | Charlie Brown | 2025-10-24 | 75.25 | pending |
| 4 | Diana Prince | 2025-10-24 | 300.00 | completed |
| 5 | Eve Adams | 2025-10-24 | 120.75 | cancelled |
-- Server-level permissions
GRANT SELECT on sys.dm_os_performance_counters to datadog;
GRANT VIEW SERVER STATE to datadog;
GRANT VIEW ANY DEFINITION to datadog;
-- Database-level permissions (per database)
USE testdb;
CREATE USER datadog FOR LOGIN datadog;
GRANT SELECT ON SCHEMA::dbo TO datadog;Edit the custom_queries array in the annotations:
{
"query": "SELECT region, SUM(sales) as total_sales FROM sales_data GROUP BY region",
"columns": [
{"name": "region", "type": "tag"},
{"name": "sales.total", "type": "gauge"}
],
"tags": ["query:regional_sales"]
}Add min_collection_interval to instance config:
"instances": [
{
"host": "%%host%%,1433",
"min_collection_interval": 30, # seconds (default: 15)
...
}
]"tags": [
"env:production",
"team:backend",
"application:ecommerce",
"custom:value"
]# Delete SQL Server
kubectl delete deployment sqlserver
kubectl delete service sqlserver
kubectl delete configmap sqlserver-init
# Uninstall Datadog
helm uninstall datadog-agent -n datadog
# Delete namespace
kubectl delete namespace datadog
# Stop minikube
minikube stop
# OR delete cluster entirely
minikube delete- Datadog SQL Server Integration
- Collect SQL Server Custom Metrics
- Autodiscovery Annotations v2
- SQL Server Troubleshooting
β
Use Autodiscovery annotations v2 for cleaner configuration
β
ODBC Driver 18 requires TrustServerCertificate=yes
β
Trusted_Connection=no is required for SQL authentication
β
Database permissions must be granted at both server and database level
β
Custom queries can collect business metrics from any table
β
Tag by dimensions (like status) for better metric slicing in Datadog
Lab Created: October 24, 2025
Cluster: sqlserver-lab (minikube)
Datadog Site: datadoghq.com
SQL Server Version: 2022 RTM-CU21 (Developer Edition)
Datadog Agent Version: 7.71.2