适用于:Linux 上的 SQL Server
本文包含有关使用 StatefulSet 在 Kubernetes 上运行 SQL Server 容器的最佳做法和指南。 建议在 Kubernetes 中为每个 Pod 部署一个 SQL Server 容器(实例)。 这样,你就在 Kubernetes 群集中为每个 Pod 部署了一个 SQL Server 实例。
同样,部署脚本建议通过将 replicas 值设置为 1 来部署一个 SQL Server 实例。 如果输入的数字大于1作为replicas值,则会获得具有相关名称的相应数量的 SQL Server 实例。 例如,在下面的脚本中,如果将数字 2 分配为 replicas 的值,你就会部署两个 SQL Server Pod,名称分别为 mssql-0 和 mssql-1。
建议每个部署脚本对应一个 SQL Server 的另一个原因是,这样就可以为每个部署的 SQL Server 实例单独更改配置值、版本、跟踪标志和其他设置。
在以下示例中,StatefulSet 工作负载名称应与 .spec.template.metadata.labels 值匹配,本例中为 mssql。 有关详细信息,请参阅 StatefulSet。
重要
SA_PASSWORD 环境变量已弃用。 请改用 MSSQL_SA_PASSWORD。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mssql # name of the StatefulSet workload, the SQL Server instance name is derived from this. We suggest to keep this name same as the .spec.template.metadata.labels, .spec.selector.matchLabels and .spec.serviceName to avoid confusion.
spec:
serviceName: "mssql" # serviceName is the name of the service that governs this StatefulSet. This service must exist before the StatefulSet, and is responsible for the network identity of the set.
replicas: 1 # only one pod, with one SQL Server instance deployed.
selector:
matchLabels:
app: mssql # this has to be the same as .spec.template.metadata.labels
template:
metadata:
labels:
app: mssql # this has to be the same as .spec.selector.matchLabels. See <https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/>:
spec:
securityContext:
fsGroup: 10001
containers:
- name: mssql # container name within the pod.
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
name: tcpsql
env:
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_ENABLE_HADR
value: "1"
- name: MSSQL_AGENT_ENABLED
value: "1"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql
key: MSSQL_SA_PASSWORD
volumeMounts:
- name: mssql
mountPath: "/var/opt/mssql"
volumeClaimTemplates:
- metadata:
name: mssql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
如果你仍然选择使用相同的部署来部署 SQL Server 实例的多个副本,请参考下一部分介绍的这一方案。 不过,这些是独立的 SQL Server 实例,而不是副本(不像 SQL Server 中的可用性组副本)。
选择工作负载类型
选择正确的工作负载部署类型不会影响性能,但 StatefulSet 确实提供了标识粘性要求。
StatefulSet 工作负载
SQL Server 是一个数据库应用程序,因此大多数情况下应部署为 StatefulSet 工作负载类型。 将工作负载部署为 StatefulSet 有助于提供唯一网络标识、持久稳定的存储等功能。 有关这一类型的工作负载的详细信息,请参阅 Kubernetes 文档。
在使用与 StatefulSet 工作负载相同的部署 YAML 脚本部署 SQL Server 容器的多个副本时,需要考虑的一个重要参数是 Pod 管理策略,即 .spec.podManagementPolicy。
此设置有两个可能的值:
OrderedReady:这是默认值,行为如部署和缩放保证中所述。
Parallel:这是一种备用策略,用于并行创建和启动 Pod(在本例中为 SQL Server Pod),而无需等待创建其他 Pod。同样,在终止期间,所有 Pod 都会被并行删除。 如果要部署彼此独立的 SQL Server 实例,并且不打算按顺序启动或删除 SQL Server 实例,可以使用此选项。
apiVersion: apps/v1 kind: StatefulSet metadata: name: mssql spec: serviceName: "mssql" replicas: 2 # two independent SQL Server instances to be deployed podManagementPolicy: Parallel selector: matchLabels: app: mssql template: metadata: labels: app: mssql spec: securityContext: fsGroup: 10001 containers: - name: mssql image: mcr.microsoft.com/mssql/server:2022-latest ports: - containerPort: 1433 name: tcpsql env: - name: ACCEPT_EULA value: "Y" - name: MSSQL_ENABLE_HADR value: "1" - name: MSSQL_AGENT_ENABLED value: "1" - name: MSSQL_SA_PASSWORD valueFrom: secretKeyRef: name: mssql key: MSSQL_SA_PASSWORD volumeMounts: - name: mssql mountPath: "/var/opt/mssql" volumeClaimTemplates: - metadata: name: mssql spec: accessModes: - ReadWriteOnce resources: requests: storage: 8Gi
由于部署在 Kubernetes 上的 SQL Server Pod 是彼此独立的,所以 Parallel 通常是用于 podManagementPolicy 的值。
使用并行策略创建 Pod 后,以下示例是kubectl get all的示例输出:
NAME READY STATUS RESTARTS AGE
pod/mssql-0 0/1 ContainerCreating 0 4s
pod/mssql-1 0/1 ContainerCreating 0 4s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 201.0.0.1 <none> 443/TCP 61d
NAME READY AGE
statefulset.apps/mssql 1/1 4s
部署工作负载
在需要将 SQL Server 容器部署为无状态数据库应用程序的场景中,例如当数据持久性不重要时,可以将部署类型用于 SQL Server。 一些这样的示例用于测试/QA 或 CI/CD 目的。
通过命名空间隔离
命名空间提供了一种机制,用于隔离单个 Kubernetes 群集中的资源组。 有关命名空间及其使用场景的详细信息,请参阅命名空间。
从 SQL Server 的角度来看,如果你计划在同时还托管其他资源的 Kubernetes 群集上运行 SQL Server Pod,则应在它们自己的命名空间中运行 SQL Server Pod,以便于管理。 例如,假设有多个部门共享同一 Kubernetes 群集,你需要为销售团队部署一个 SQL Server 实例,为营销团队部署另一个实例。 你将创建名为 sales 和 marketing 的两个命名空间,如以下示例所示:
kubectl create namespace sales
kubectl create namespace marketing
若要检查命名空间是否已创建,请运行 kubectl get namespaces,你将看到类似于以下输出的列表。
NAME STATUS AGE
default Active 39d
kube-node-lease Active 39d
kube-public Active 39d
kube-system Active 39d
marketing Active 7s
sales Active 26m
现在,可以使用以下示例中所示的示例 YAML,在每个命名空间中部署 SQL Server 容器。 请注意添加到部署 YAML 的 namespace 元数据,因此此部署的所有容器和服务都将部署在 sales 命名空间中。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
storageAccountType: Standard_LRS
kind: Managed
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mssql-sales
namespace: sales
labels:
app: mssql-sales
spec:
serviceName: "mssql-sales"
replicas: 1
selector:
matchLabels:
app: mssql-sales
template:
metadata:
labels:
app: mssql-sales
spec:
securityContext:
fsGroup: 10001
containers:
- name: mssql-sales
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
name: tcpsql
env:
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_ENABLE_HADR
value: "1"
- name: MSSQL_AGENT_ENABLED
value: "1"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql
key: MSSQL_SA_PASSWORD
volumeMounts:
- name: mssql
mountPath: "/var/opt/mssql"
volumeClaimTemplates:
- metadata:
name: mssql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
---
apiVersion: v1
kind: Service
metadata:
name: mssql-sales-0
namespace: sales
spec:
type: LoadBalancer
selector:
statefulset.kubernetes.io/pod-name: mssql-sales-0
ports:
- protocol: TCP
port: 1433
targetPort: 1433
name: tcpsql
若要查看资源,可以使用指定的命名空间运行 kubectl get all 命令来查看这些资源:
kubectl get all -n sales
NAME READY STATUS RESTARTS AGE
pod/mssql-sales-0 1/1 Running 0 17m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/mssql-sales-0 LoadBalancer 10.0.251.120 20.23.79.52 1433:32052/TCP 17m
NAME READY AGE
statefulset.apps/mssql-sales 1/1 17m
命名空间还可用于限制在命名空间中创建的资源和 Pod,使用 限制范围和/或资源配额策略来管理命名空间内的总体资源创建。
配置 Pod 服务质量
在单个 Kubernetes 群集上部署多个 Pod 时,必须相应地共享资源,以确保 Kubernetes 群集高效运行。 可以配置 Pod,以便为其分配特定的服务质量 (QoS)。
Kubernetes 使用 QoS 类做出有关调度和驱逐 Pod 的决策。 有关不同 QoS 类的详细信息,请参阅为 Pod 配置服务质量。
从 SQL Server 的角度来看,对于基于生产的工作负载,建议使用 QoS 作为 Guaranteed 来部署 SQL Server Pod。 考虑到 SQL Server Pod 只有一个运行的 SQL Server 容器实例来实现该 Pod 的有保证的 QoS,你需要为容器指定 CPU 和内存请求,它应等于内存和 CPU 限制。 这确保节点提供并提交部署期间指定的所需资源,并为 SQL Server Pod 提供可预测的性能。
下面是在默认命名空间中部署一个 SQL Server 容器的示例部署 YAML,由于未指定资源请求,但根据有保证的服务质量示例中的准则规定了限制,因此我们看到下面示例创建的 Pod 已将 QoS 设置为 Guaranteed。 如果你没有指定资源请求,Kubernetes 会将资源限制视为等于资源请求。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: azure-disk
provisioner: kubernetes.io/azure-disk
parameters:
storageaccounttype: Standard_LRS
kind: Managed
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mssql
labels:
app: mssql
spec:
serviceName: "mssql"
replicas: 1
selector:
matchLabels:
app: mssql
template:
metadata:
labels:
app: mssql
spec:
securityContext:
fsGroup: 10001
containers:
- name: mssql
command:
- /bin/bash
- -c
- cp /var/opt/config/mssql.conf /var/opt/mssql/mssql.conf && /opt/mssql/bin/sqlservr
image: mcr.microsoft.com/mssql/server:2022-latest
resources:
limits:
memory: 2Gi
cpu: '2'
ports:
- containerPort: 1433
env:
- name: ACCEPT_EULA
value: "Y"
- name: MSSQL_ENABLE_HADR
value: "1"
- name: MSSQL_SA_PASSWORD
valueFrom:
secretKeyRef:
name: mssql
key: MSSQL_SA_PASSWORD
volumeMounts:
- name: mssql
mountPath: "/var/opt/mssql"
- name: userdata
mountPath: "/var/opt/mssql/userdata"
- name: userlog
mountPath: "/var/opt/mssql/userlog"
- name: tempdb
mountPath: "/var/opt/mssql/tempdb"
- name: mssql-config-volume
mountPath: "/var/opt/config"
volumes:
- name: mssql-config-volume
configMap:
name: mssql
volumeClaimTemplates:
- metadata:
name: mssql
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
- metadata:
name: userdata
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
- metadata:
name: userlog
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
- metadata:
name: tempdb
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 8Gi
可以运行 kubectl describe pod mssql-0 命令将 QoS 视为 Guaranteed,输出类似于以下代码片段。
...
QoS Class: Guaranteed
Node-Selectors: <none>
Tolerations: node.kubernetes.io/memory-pressure:NoSchedule op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
...
对于性能和可用性不是高优先级的非生产工作负载,可以考虑将 QoS 设置为 Burstable 或 BestEffort。
可突发的 QoS 示例
为了定义 Burstable YAML 示例,你可以指定资源 请求,而不是资源 限制;或者指定高于 请求 的 限制。 下面的代码只显示与上一示例的不同,以便定义可突发工作负载。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mssql
labels:
app: mssql
spec:
serviceName: "mssql"
replicas: 1
selector:
matchLabels:
app: mssql
template:
metadata:
labels:
app: mssql
spec:
securityContext:
fsGroup: 10001
containers:
- name: mssql
command:
- /bin/bash
- -c
- cp /var/opt/config/mssql.conf /var/opt/mssql/mssql.conf && /opt/mssql/bin/sqlservr
image: mcr.microsoft.com/mssql/server:2022-latest
resources:
requests:
memory: 2Gi
cpu: '2'
可以运行 kubectl describe pod mssql-0 命令将 QoS 视为 Burstable,输出类似于以下代码片段。
...
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: node.kubernetes.io/memory-pressure:NoSchedule op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
...
最佳 QoS 示例
若要定义 BestEffort YAML 示例,请删除资源请求和资源限制。 你最终会获得最佳 QoS,如创建一个分配有 BestEffort QoS 类的 Pod 中定义的那样。 与前面一样,下面的代码只显示与 Guaranteed 示例的区别,以便定义最佳工作负载。 这些是 SQL Server Pod 最不推荐的选项,因为它们可能是第一批在资源争用的情况下终止的选项。 即使对于测试和质量保证场景,我们也建议对 SQL Server 使用“突增性能选项”。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mssql
labels:
app: mssql
spec:
serviceName: "mssql"
replicas: 1
selector:
matchLabels:
app: mssql
template:
metadata:
labels:
app: mssql
spec:
securityContext:
fsGroup: 10001
containers:
- name: mssql
command:
- /bin/bash
- -c
- cp /var/opt/config/mssql.conf /var/opt/mssql/mssql.conf && /opt/mssql/bin/sqlservr
image: mcr.microsoft.com/mssql/server:2022-latest
ports:
- containerPort: 1433
可以运行 kubectl describe pod mssql-0 命令将 QoS 视为 BestEffort,输出类似于以下代码片段。
...
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/memory-pressure:NoSchedule op=Exists
node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
...
控制组 (cgroup) v2 支持
SQL Server 检测并遵守控制组(cgroup)v2 限制条件,从 SQL Server 2025(17.x)和 SQL Server 2022(16.x)累积更新(CU)20 开始。 这些约束通过 CPU 和内存资源在 Linux 内核中提供精细的控制,并在 Docker、Kubernetes 和 OpenShift 环境中改进资源隔离。
在早期版本中,Kubernetes 群集(例如,Azure Kubernetes Service v1.25+)上的容器化部署可能会遇到内存不足(OOM)错误,因为SQL Server未强制实施容器规范中定义的内存限制。 对 cgroup v2 的支持解决了此问题。
检查 cgroup 版本
stat -fc %T /sys/fs/cgroup
结果如下所示:
| 结果 | Description |
|---|---|
cgroup2fs |
使用 cgroup v2 |
cgroup |
使用 cgroup v1 |
切换到 cgroup v2
最简单的路径是选择支持现装的 cgroup v2 的分发版。
如果需要手动切换,请将以下参数添加到 GRUB 配置:
systemd.unified_cgroup_hierarchy=1
然后更新 GRUB。 例如,在 Ubuntu 上运行:
sudo update-grub
在 Red Hat Enterprise Linux (RHEL)上运行:
sudo grub2-mkconfig -o /boot/grub2/grub.cfg
使用 cgroup v2 报告的 CPU 限制
使用 cgroup v2 配置 CPU 限制时,SQL Server错误日志中未显示配置的 CPU 核心计数。 相反,它会继续报告主机 CPU 总数。
若要将SQL Server计划程序和查询计划(例如并行度决策)与 cgroup v2 中定义的预期 CPU 计数保持一致,请应用以下配置。
配置处理器相关性
显式设置SQL Server处理器相关性以匹配 cgroup 执行配额。 在以下示例中,cgroup 配额是八核主机上的四个 CPU:
ALTER SERVER CONFIGURATION
SET PROCESS AFFINITY CPU = 0 TO 3;
此配置可确保 SQL Server 仅为指定数量的 CPU 创建调度程序。 有关详细信息,请参阅 ALTER SERVER CONFIGURATION 和 Use PROCESS AFFINITY for Node and/or CPU。
启用跟踪标志 8002(建议)
启用跟踪标记 8002 以在 SQLPAL 层使用软关联性:
sudo /opt/mssql/bin/mssql-conf traceflag 8002 on
默认情况下,计划程序绑定到关联掩码中定义的特定 CPU。 跟踪标志 8002 允许计划程序改为跨 CPU 移动,这通常会提高性能,同时仍遵循相关性和 cgroup 约束。 有关详细信息,请参阅 DBCC TRACEON - 跟踪标志。
启用跟踪标志后重启SQL Server。
预期行为
重启后:
SQL Server仅创建关联设置定义的计划程序数(例如,4 个计划程序)。
Linux 内核继续强制实施 cgroup v2 CPU 执行配额。
查询优化和并行决策基于预期的 CPU 计数,而不是主机 CPU 总数。
注释
SQL Server错误日志可能会继续显示主机 CPU 总数。 此日志记录和显示行为不会影响 cgroup v2 或处理器相关性的实际 CPU 使用率、计划程序创建或 CPU 强制实施。
有关详细信息,请参阅以下资源: