21.9.25

Доступ к Kubernetes API из SQL Server 2025

Автор: dbafromthecold.com, Accessing the Kubernetes API from SQL Server 2025

Хранимая процедура sp_invoke_external_rest_endpoint, появившаяся в версии SQL Server 2025, позволяет обращаться к внешним конечным точкам REST API прямо из SQL Server. Это открывает множество интересных возможностей. Недавно я задумался: ведь Kubernetes тоже предоставляет REST API. Можно ли обратиться к нему напрямую из SQL Server?

Давайте посмотрим, как это сделать. Пошагово план такой:

  1. Создать локальный центр сертификации (CA), приватный RSA-ключ и подписанный сертификат
  2. Развернуть обратный прокси в Kubernetes
  3. Настроить SQL Server для обращения к прокси
  4. Использовать хранимую процедуру для вызова 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


В SQL Server включаем хранимую процедуру:
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 API, но пользователь, от имени которого он запущен — system: anonymous — не имеет разрешения на получение списка подов. Для исправления создаём:
  1. Сервисную учётку в Kubernetes
  2. Роль для чтения и создания подов
  3. Включаем учётку в роль
  4. Получаем токен этой учётки
  5. Используем токен в запросах
Итак, создаём сервисную учётку: 
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.

Спасибо за внимание!

Комментариев нет:

Отправить комментарий