Compartilhar via


Implantar contêineres Linux no SQL Server no Kubernetes com StatefulSets

Aplica-se a: SQL Server – Linux

Este artigo contém as melhores práticas e orientações para executar contêineres do SQL Server no Kubernetes com StatefulSets. Recomendamos a implantação de um contêiner SQL Server (instância) por pod no Kubernetes. Assim, você terá uma instância do SQL Server implantada por pod no cluster do Kubernetes.

Da mesma forma, recomendamos implantar o script de implantação em uma instância do SQL Server definindo o valor de replicas como 1. Se você inserir um número maior que 1 como o valor de replicas, obterá esse número de instâncias do SQL Server com nomes co-relacionados. Por exemplo, no script abaixo, se você atribuiu o número de 2 como o valor de replicas, seriam implantados dois pods do SQL Server, com os nomes mssql-0 e mssql-1, respectivamente.

Outra razão pela qual recomendamos um SQL Server por script de implantação é para permitir que alterações nos valores de configuração, edição, sinalizadores de rastreamento e outras configurações sejam feitas independentemente para cada instância do SQL Server implantada.

No exemplo a seguir, o nome da carga de trabalho StatefulSet deve corresponder ao valor de .spec.template.metadata.labels, que nesse caso é mssql. Para mais informações, confira StatefulSets.

Importante

A variável de ambiente SA_PASSWORD foi preterida. Use MSSQL_SA_PASSWORD em vez disso.

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, as documented [here](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:2019-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

Se você ainda optar por implantar mais de uma réplica da instância do SQL Server usando a mesma implantação, esse cenário será abordado na próxima seção. No entanto, essas são instâncias separadas e independentes do SQL Server, e não réplicas (ao contrário das réplicas do grupo de disponibilidade no SQL Server).

Escolher o tipo de carga de trabalho

Escolher o tipo certo de implantação de carga de trabalho não afeta o desempenho, mas o StatefulSet fornece requisitos de permanência de identidade.

Cargas de trabalho StatefulSet

O SQL Server é um aplicativo de banco de dados e, portanto, deve ser implantado principalmente como um tipo de carga de trabalho StatefulSet. A implantação de cargas de trabalho como StatefulSet ajuda a fornecer recursos como identificações de rede exclusivas, armazenamento persistente e estável, e muito mais. Para obter mais informações sobre esse tipo de carga de trabalho, confira a documentação do Kubernetes.

Ao implantar mais de uma réplica de contêineres do SQL Server usando o mesmo script YAML de implantação como uma carga de trabalho StatefulSet, as políticas de gerenciamento de pods, ou seja, .spec.podManagementPolicy, são parâmetros impostantes a serem considerados.

Há dois valores possíveis para essa configuração:

  • OrderedReady: este é o comportamento e o valor padrão conforme descrito nas garantias de implantação e dimensionamento.

  • Parallel: esta é a política alternativa que cria e inicia os pods (nesse caso, os pods do SQL Server) em paralelo, sem esperar que outros pods sejam criados. Da mesma forma, todos os pods são excluídos em paralelo durante o encerramento. Você pode usar essa opção ao implantar instâncias do SQL Server independentes umas das outras, ou quando não pretende seguir uma ordem para iniciar ou excluir as instâncias do 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:2019-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
    

Como os pods do SQL Server implantados no Kubernetes são independentes uns dos outros, Parallel é o valor normalmente usado para podManagementPolicy.

A amostra a seguir é o exemplo de saída de kubectl get all, logo após a criação dos pods usando uma política paralela:

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

Cargas de trabalho de implantação

Você pode usar o tipo de implantação do SQL Server em cenários em que deseja implantar contêineres do SQL Server como aplicativos de banco de dados sem estado, por exemplo, quando a persistência de dados não é crítica. Alguns desses exemplos são para fins de teste/QA ou CI/CD.

Isolamento por meio de namespaces

Os namespaces fornecem um mecanismo para isolar grupos de recursos em um único cluster do Kubernetes. Para obter mais informações sobre namespaces e quando usá-los, confira Namespaces.

Da perspectiva do SQL Server, para executar pods do SQL Server em um cluster do Kubernetes que também hospede outros recursos, execute os pods do SQL Server em seu próprio namespace para facilitar o gerenciamento e a administração. Por exemplo, considere que você tem vários departamentos compartilhando o mesmo cluster do Kubernetes e deseja implantar uma instância do SQL Server para a equipe de Vendas e outra para a equipe de Marketing. Você criará dois namespaces chamados sales e marketing, conforme mostrado no exemplo a seguir:

kubectl create namespace sales
kubectl create namespace marketing

Para verificar se os namespaces foram criados, execute kubectl get namespaces, você verá uma lista semelhante à saída a seguir.

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

Agora você pode implantar contêineres do SQL Server em cada um desses namespaces usando o YAML de amostra mostrado no exemplo a seguir. Observe que os metadados namespace são adicionados ao YAML de implantação, portanto, todos os contêineres e serviços dessa implantação serão implantados no 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:2019-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

Para ver os recursos, você pode executar o comando kubectl get all com o namespace especificado para ver estes recursos:

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

Os namespaces também podem ser usados para limitar os recursos e pods criados em um namespace, usando o intervalo de limite e/ou as políticas de cota de recursos para gerenciar a criação geral de recursos em um namespace.

Configurar a Qualidade de Serviço do pod

Ao implantar vários pods em um único cluster do Kubernetes, você deve compartilhar recursos de forma adequada, de modo a garantir a execução eficiente do cluster Kubernetes. Você pode configurar pods para que sejam atribuídos a uma QoS (qualidade de serviço) específica.

O Kubernetes usa classes QoS para tomar decisões sobre agendamento e remoção de pods. Para obter mais informações sobre as diferentes classes de QoS, confira Configurar a Qualidade de Serviço de Pods.

Da perspectiva do SQL Server, recomendamos que você implante os pods do SQL Server usando a QoS como Guaranteed para cargas de trabalho baseadas em produção. Considerando que um pod do SQL Server tem apenas uma instância de contêiner do SQL Server em execução para obter a QoS garantida para esse pod, você precisará especificar as solicitações de CPU e memória para o contêiner que devem ser iguais aos limites de memória e CPU. Isso garante que os nós forneçam e confirmem os recursos necessários especificados durante a implantação e tenham desempenho previsível para os pods do SQL Server.

Aqui está um YAML de implantação de amostra que implanta um contêiner do SQL Server no namespace padrão e, como as solicitações de recursos não foram especificadas, mas os limites foram especificados de acordo com as diretrizes do exemplo de qualidade de serviço garantida, vemos que o pod que foi criado no exemplo a seguir tem a QoS definida como Guaranteed. Quando você não especifica as solicitações de recursos, o Kubernetes considera os limites de recursos iguais às solicitações de recursos.

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:2019-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

Você pode executar o comando kubectl describe pod mssql-0 para visualizar a QoS como Guaranteed, com saída semelhante ao trecho a seguir.

...
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
...

Em cargas de trabalho de não produção, em que o desempenho e a disponibilidade não são de alta prioridade, considere definir a QoS como Burstable ou BestEffort.

Exemplo de QoS com capacidade de intermitência

Para definir um exemplo YAML Burstable, você especifica as solicitações de recursos, não os limites de recursos; ou você especifica os limites, que são mais altos que as solicitações. O código a seguir exibe apenas a diferença do exemplo anterior, para definir uma carga de trabalho com capacidade de intermitência.

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:2019-latest
     resources:
      requests:
       memory: 2Gi
       cpu: '2'

Você pode executar o comando kubectl describe pod mssql-0 para visualizar a QoS como Burstable, com saída semelhante ao trecho a seguir.

...
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
...

Exemplo de QoS de melhor esforço

Para definir um exemplo de YAML BestEffort, remova as solicitações de recursos e os limites de recursos. Você terminará com a QoS de melhor esforço, conforme definido em Criar um Pod atribuído a uma classe de QoS de BestEffort. Conforme mencionado anteriormente, o código a seguir exibe apenas a diferença do exemplo Guaranteed para definir uma carga de trabalho de melhor esforço. Essas são as opções menos recomendadas para pods do SQL Server, pois provavelmente seriam as primeiras a serem encerradas no caso de contenção de recursos. Mesmo para cenários de teste e controle de qualidade, recomendamos o uso da opção com capacidade de intermitência para 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:2019-latest
          ports:
            - containerPort: 1433

Você pode executar o comando kubectl describe pod mssql-0 para visualizar a QoS como BestEffort, com saída semelhante ao trecho a seguir.

...
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
...