Хранимая процедура sp_invoke_external_rest_endpoint
, появившаяся в версии SQL Server 2025, позволяет обращаться к внешним конечным точкам REST API прямо из SQL Server. Это открывает множество интересных возможностей. Недавно я задумался: ведь Kubernetes тоже предоставляет REST API. Можно ли обратиться к нему напрямую из SQL Server?
Давайте посмотрим, как это сделать. Пошагово план такой:
- Создать локальный центр сертификации (CA), приватный RSA-ключ и подписанный сертификат
- Развернуть обратный прокси в Kubernetes
- Настроить SQL Server для обращения к прокси
- Использовать хранимую процедуру для вызова Kubernetes API через прокси
Создание сертификата
Первым делом нужно создать приватный ключ и подписанный сертификат. Было бы удобно просто загрузить корневой сертификат Kubernetes-кластера, но (и поверьте, я пробовал дольше, чем хотелось бы признаться) — это не сработает. В ответ получите только раздражающую ошибку:
Msg 31608, Level 16, State 24, Procedure sys.sp_invoke_external_rest_endpoint_internal, Line 1 [Batch Start Line 0]
An error occurred, failed to communicate with the external rest endpoint. HRESULT: 0x80072ee7.
Поэтому остаётся вариант с созданием собственного подписанного сертификата.
Примечание: я запускаю SQL Server 2025 в WSL на Windows 11, обращаясь к локальному Kubernetes-кластеру с установленным Metallb, работающему в Hyper-V.
Зададим переменные:
DOMAIN="api.dbafromthecold.local"
DAYS_VALID=365
NAMESPACE="default"
CA_KEY="CA.key"
CA_CERT="CA.pem"
Создаём локальный центр сертификации:
openssl genrsa -out $CA_KEY 2048
openssl req -x509 -new -nodes -key $CA_KEY -sha256 -days 3650 -out $CA_CERT \
-subj "/C=IE/ST=Local/L=Test/O=TestCA/CN=My Local CA"
Создаём ключ и запрос на сертификат:
openssl genrsa -out $DOMAIN.key 2048
openssl req -new -key $DOMAIN.key -out $DOMAIN.csr \
-subj "/C=IE/ST=Local/L=Test/O=TestAPI/CN=$DOMAIN"
Подписываем запрос при помощи CA и выпускаем серверный сертификат:
openssl x509 -req -in $DOMAIN.csr -CA $CA_CERT -CAkey $CA_KEY -CAcreateserial \
-out $DOMAIN.crt -days $DAYS_VALID -sha256
Развёртывание обратного прокси nginx
Создаём секрет в Kubernetes для хранения сертификата и ключа:
kubectl create secret tls k8s-api-cert \
--cert=$DOMAIN.crt --key=$DOMAIN.key \
--namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
Разворачиваем контроллер ingress:
helm upgrade --install ingress-nginx ingress-nginx \
--repo https://kubernetes.github.io/ingress-nginx \
--namespace ingress-nginx --create-namespace
Проверяем, что ingress-контроллер запущен:
kubectl get all -n ingress-nginx
Создаём ConfigMap с конфигурацией прокси:
kubectl create configmap k8s-api-nginx-conf --from-literal=nginx.conf='
events {}
http {
server {
listen 443 ssl;
ssl_certificate /etc/nginx/certs/tls.crt;
ssl_certificate_key /etc/nginx/certs/tls.key;
location / {
proxy_pass https://10.0.0.41:6443;
proxy_ssl_verify off;
}
}
}
' -n $NAMESPACE --dry-run=client -o yaml | kubectl apply -f -
Примечание: 10.0.0.41:6443 — это IP-адрес и порт kube-apiserver.
Эта конфигурация указывает nginx принимать HTTPS-соединения (необходимые для sp_invoke_external_rest_endpoint), завершать TLS-соединение с помощью созданного нами секрета и перенаправлять запрос на сервер Kubernetes API.
Разверните nginx, подключив конфигурационную карту и секретный ключ TLS для включения обратного прокси:
kubectl apply -f - <<eof -="" 1="" 443="" apiversion:="" app:="" apps="" certs="" code="" configmap:="" containerport:="" containers:="" deployment="" eof="" etc="" image:="" k8s-api-cert="" k8s-api-nginx-conf="" k8s-api-proxy="" kind:="" labels:="" matchlabels:="" metadata:="" mountpath:="" name:="" namespace:="" nginx-cert="" nginx-conf="" nginx.conf="" nginx:latest="" nginx="" ports:="" readonly:="" replicas:="" secret:="" secretname:="" selector:="" spec:="" subpath:="" template:="" true="" v1="" volumemounts:="" volumes:=""></eof>
Далее сервис:
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: k8s-api-proxy
namespace: $NAMESPACE
spec:
replicas: 1
selector:
matchLabels:
app: k8s-api-proxy
template:
metadata:
labels:
app: k8s-api-proxy
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 443
volumeMounts:
- name: nginx-conf
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
- name: nginx-cert
mountPath: /etc/nginx/certs
readOnly: true
volumes:
- name: nginx-conf
configMap:
name: k8s-api-nginx-conf
- name: nginx-cert
secret:
secretName: k8s-api-cert
EOF
Создайте службу для развёртывания nginx:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Service
metadata:
name: k8s-api-proxy
namespace: $NAMESPACE
spec:
selector:
app: k8s-api-proxy
ports:
- protocol: TCP
port: 443
targetPort: 443
EOF
А теперь создайте входной ресурс для перенаправления трафика на службу nginx:
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: k8s-api-ingress
namespace: $NAMESPACE
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
tls:
- hosts:
- $DOMAIN
secretName: k8s-api-cert
rules:
- host: $DOMAIN
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: k8s-api-proxy
port:
number: 443
EOF
Проверяем поды и ingress:
kubectl get pods
kubectl get ingress
Хорошо, теперь мы почти готовы подключиться к Kubernetes API. Но сначала нам нужно настроить SQL Server так, чтобы он доверял сертификату. Для этого нам нужно скопировать сертификат в /var/opt/mssql/security/ca-certificates. По умолчанию это место не существует, поэтому его нужно создать.:
mkdir /var/opt/mssql/security/ca-certificates
cp $CA_CERT /var/opt/mssql/security/ca-certificates/$CA_CERT
chown mssql:mssql /var/opt/mssql/security/ca-certificates/$CA_CERT
Перезапускаем SQL Server:
sudo systemctl restart mssql-server
В журнале ошибок будет строка:
Successfully placed CA.crt in trusted root store
О, и если мы хотим провести тестирование с помощью curl... нам нужно поместить сертификат в хранилище доверенных сертификатов сервера::
sudo cp $CA_CERT /usr/local/share/ca-certificates/myCA.crt
sudo update-ca-certificates
Но прежде чем приступить к тестированию, нам нужно убедиться, что SQL Server может распознать наше доменное имя. Это означает, что нужно добавить запись в файл: /etc/hosts
:
10.0.0.50 api.dbafromthecold.local
И проверяем:
curl https://api.dbafromthecold.local/version
EXECUTE sp_configure 'external rest endpoint enabled', 1;
RECONFIGURE WITH OVERRIDE;
И выполняем запрос:
DECLARE @version NVARCHAR(MAX);
EXEC sp_invoke_external_rest_endpoint
@url = 'https://api.dbafromthecold.local/version',
@method = 'GET',
@response = @version OUTPUT
PRINT @version
Попробуем теперь получить список подов в кластере. Если попробовать сейчас, то ответ будет 403 Forbidden: пользователь system:anonymous
не имеет прав.
{
"status": "Failure",
"message": "pods is forbidden: User \"system:anonymous\" cannot list resource \"pods\" in API group \"\" in the namespace \"default\"",
"reason": "Forbidden",
"details": {
"kind": "pods"
},
"code": 403
}
- Сервисную учётку в Kubernetes
- Роль для чтения и создания подов
- Включаем учётку в роль
- Получаем токен этой учётки
- Используем токен в запросах
kubectl create serviceaccount api-reader
А также роль, которая позволяет просматривать и создавать модули:
kubectl apply -f - <<EOF
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: default
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch","create"]
EOF
Теперь привяжите эту роль к учетке:
kubectl create rolebinding pod-reader-binding \
--role=pod-reader \
--serviceaccount=default:api-reader \
--namespace=default
Создайте токен для этой учётки:
kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
name: api-reader-token
annotations:
kubernetes.io/service-account.name: api-reader
type: kubernetes.io/service-account-token
EOF
И возьмите токен:
kubectl get secret api-reader-token -o jsonpath="{.data.token}" | base64 -d
Так мы получим большой длинный токен, который можно использовать в параметре хранимой процедуры... вот так:
DECLARE @pods NVARCHAR(MAX);
EXEC sp_invoke_external_rest_endpoint
@url = 'https://api.dbafromthecold.local/api/v1/namespaces/default/pods',
@headers = '{"Authorization":"Bearer XXXXX..
большой длинный токен..XXXX"}',
@method = 'GET',
@response = @pods OUTPUT
PRINT @pods
Но так мы получим просто большой кусок JSON... давайте попробуем немного его проанализировать:
DECLARE @pods NVARCHAR(MAX);
EXEC sp_invoke_external_rest_endpoint
@url = 'https://api.dbafromthecold.local/api/v1/namespaces/default/pods',
@headers = '{"Authorization":"Bearer XXXXX..
большой длинный токен..XXXX"}',
@method = 'GET',
@response = @pods OUTPUT
SELECT
pod_name,
namespace,
container_image,
pod_ip,
status
FROM OPENJSON(@pods, '$.result.items')
WITH (
pod_name NVARCHAR(100) '$.metadata.name',
namespace NVARCHAR(100) '$.metadata.namespace',
container_image NVARCHAR(100) '$.spec.containers[0].image',
pod_ip NVARCHAR(50) '$.status.podIP',
status NVARCHAR(50) '$.status.phase'
);
Результат:!!!!!!!!!!!!!!!!!!!!!!!!!!!
pod_name namespace container_image pod_ip status
k8s-api-proxy-abcde default nginx:latest 10.244.0.5 Running
Создание пода вызовом процедуры в SQL Server
Так как в Role было разрешение create
, можно создать pod:
DECLARE @deploy NVARCHAR(MAX);
EXEC sp_invoke_external_rest_endpoint
@url = 'https://api.dbafromthecold.local/api/v1/namespaces/default/pods',
@headers = '{"Authorization":"Bearer eyXXXXX....XXXX"}',
@method = 'POST',
@payload = '{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "nginx",
"namespace": "default"
},
"spec": {
"containers": [
{
"name": "nginx",
"image": "nginx:latest",
"ports": [
{
"containerPort": 80
}
]
}
]
}
}',
@response = @deploy OUTPUT
PRINT @deploy
Проверяем:
kubectl get pods
Готово, pod создан!
Заключение
Таким образом, можно обращаться к Kubernetes API через обратный прокси прямо из SQL Server 2025 с помощью sp_invoke_external_rest_endpoint
. Это открывает путь к интеграции и автоматизации прямо из T-SQL.
Спасибо за внимание!
Комментариев нет:
Отправить комментарий