# Azure AKS 部署

{% hint style="success" %}
对应的[官方文档地址](https://bitwarden.com/help/azure-aks-deployment/)
{% endhint %}

本文深入探讨了如何根据 Azure 和 AKS 的具体特性来调整您的 [Bitwarden 自托管 Helm Chart 部署](/docs/self-hosting/deploy-and-configure/helm/self-host-with-helm.md)。

## 要求 <a href="#requirements" id="requirements"></a>

在继续安装之前，请确保满足以下要求：

* 已安装 [kubectl](https://kubernetes.io/docs/tasks/tools/#kubectl)。
* 已安装 [Helm 3](https://helm.sh/docs/intro/install/)。
* 您拥有 SSL 证书和密钥，或者可以通过证书提供程序创建 SSL 证书和密钥。
* 您拥有 SMTP 服务器或可以访问云端 SMTP 提供程序。
* 一个支持 ReadWriteMany 的[存储类](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes)。
* 您已从 <https://bitwarden.com/host> 获取了安装 ID 和密钥。

### 无根模式要求 <a href="#rootless-requirements" id="rootless-requirements"></a>

Bitwarden 会在启动时检测您的环境是否限制了用户容器的运行身份，并在检测到限制时自动以无根模式启动部署。要成功以无根模式部署，需满足以下两个选项之一：

* 部署[外部 MSSQL 数据库](/docs/self-hosting/deploy-and-configure/configuration-options/connect-to-an-external-mssql-database.md)，而不是 Helm 图表中默认包含的 SQL 容器。
* 使用[服务账户](/docs/self-hosting/deploy-and-configure/configuration-options/kubernetes-service-accounts.md)、[Pod 安全上下文](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/#set-the-security-context-for-a-pod)或其他方法为包含的 SQL 容器分配升的权限。

{% hint style="info" %}
虽然 Microsoft 要求 SQL 容器必须以 root 身份运行，但容器启动后会在执行应用程序代码前逐步降级为非 root 用户。
{% endhint %}

## 入口控制器 <a href="#ingress-controllers" id="ingress-controllers"></a>

本部分介绍了可在 Azure AKS 部署中使用的入口控制器的 2 个选项：

* 使用 **Azure nginx** 入口控制器，可选择与 Azure DNS 集成以进行区域管理，并与 Azure Key Vault 集成以进行证书颁发。
* 使用 **Azure 应用程序网关**入口控制器 (AGIC)，在应用程序负载均衡器后面部署 Bitwarden。

### Azure nginx <a href="#azure-nginx" id="azure-nginx"></a>

Azure 提供了一个 nginx 入口控制器选项，该选项支持应用程序路由附加组件，并且可以选择与 Azure DNS 集成以进行区域管理，以及与 Azure Key Vault 集成以进行证书颁发。如果您使用此选项：

1、[创建一个「托管」的 nginx 入口控制器](https://learn.microsoft.com/zh-cn/azure/aks/app-routing#create-the-ingress-object)。

2、在 `my-values.yaml` 文件中，将 `generic.ingress.className:` 设置为 `webapprouting.kubernetes.azure.com`。

3、在 `my-values.yaml` 文件中，取消注释以下值：

```yml
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$1
```

完成后，您可以使用命令 `kubectl get ingress -n bitwarden` 获取分配给 Azure nginx 入口控制器的 IP 地址。部署后可能需要几分钟时间，您的 IP 地址才会显示。

### Azure 应用程序网关 <a href="#azure-application-gateway" id="azure-application-gateway"></a>

然而，Azure 客户可能更倾向于使用 Azure 应用程序网关作为他们 AKS 集群的入口控制器，以便将 Bitwarden 部署在应用程序负载均衡器之后。

#### 安装图表之前 <a href="#before-installing-the-chart" id="before-installing-the-chart"></a>

如果您倾向于使用此选项，在[安装图表](/docs/self-hosting/deploy-and-configure/helm/self-host-with-helm.md#install-the-chart)**之前**，您必须：

1、[为您的群集启用 Azure 应用程序网关入口控制器](https://learn.microsoft.com/en-us/azure/application-gateway/tutorial-ingress-controller-add-on-existing)。

2、更新您的 my-values.yaml 文件，特别是 `general.ingress.className:` ， `general.ingress.annotations:` 和 `general.ingress.paths:` ：

```yml
general:
  domain: "replaceme.com"
  ingress:
    enabled: true
    className: "azure-application-gateway" # This value might be different depending on how you created your ingress controller.  Use "kubectl get ingressclasses -A" to find the name if unsure.
     ## - Annotations to add to the Ingress resource.
    annotations:
      appgw.ingress.kubernetes.io/ssl-redirect: "true"
      appgw.ingress.kubernetes.io/use-private-ip: "false" # This might be true depending on your setup.
      appgw.ingress.kubernetes.io/rewrite-rule-set: "bitwarden-ingress" # Make note of whatever you set this value to.  It will be used later.
      appgw.ingress.kubernetes.io/connection-draining: "true" # Update as necessary.
      appgw.ingress.kubernetes.io/connection-draining-timeout: "30" # Update as necessary.
    ## - Labels to add to the Ingress resource.
    labels: {}
    # Certificate options.
    tls:
      # TLS certificate secret name.
      name: tls-secret
      # Cluster cert issuer (e.g. Let's Encrypt) name if one exists.
      clusterIssuer: letsencrypt-staging
    paths:
      web:
        path: /*
        pathType: ImplementationSpecific
      attachments:
        path: /attachments/*
        pathType: ImplementationSpecific
      api:
        path: /api/*
        pathType: ImplementationSpecific
      icons:
        path: /icons/*
        pathType: ImplementationSpecific
      notifications:
        path: /notifications/*
        pathType: ImplementationSpecific
      events:
        path: /events/*
        pathType: ImplementationSpecific
      scim:
         path: /scim/*
         pathType: ImplementationSpecific
      sso:
        path: /sso/*
        pathType: ImplementationSpecific
      identity:
        path: /identity/*
        pathType: ImplementationSpecific
      admin:
        path: /admin*
        pathType: ImplementationSpecific
```

3、如果您打算使用提供的 Let's Encrypt 示例作为 TLS 证书，请将[此处链接](/docs/self-hosting/deploy-and-configure/helm/self-host-with-helm.md#example-certificate-setup)的脚本中的 `spec.acme.solvers.ingress.class:` 更新为 `"azure/application-gateway"` 。

4、在 Azure 门户中，为应用程序网关创建一个空的重写集：

1. 转到 Azure 门户中的**负载均衡** → **应用程序网关**，选择您的应用程序网关。
2. 选择**重写**选项卡。
3. 选择 ✚**重写集**按钮。
4. 将名称设置为 `my-values.yaml` 中 `appgw.ingress.kubernetes.io/rewrite-rule-set:` 指定的值，在此示例中为 `bitwarden-ingress` 。
5. 选择**下一步**然后选择**创建**。

#### 安装图表之后 <a href="#after-installing-the-chart" id="after-installing-the-chart"></a>

[安装图表](/docs/self-hosting/deploy-and-configure/helm/self-host-with-helm.md#install-the-chart)**之后**，您还需要为重写集创建规则：

1、重新打开您在安装图表之前创建的空重写集。

2、选择所有以 `pr-bitwarden-self-host-ingress...` 开头的路由路径，取消选择任何不以该前缀开头的路径，然后选择**下一步**。

3、选择 ✚**添加重写规则**按钮。您可以为重写规则指定任意名称和任意顺序。

4、添加以下条件：

* **要检查的变量类型**：服务器变量
* **服务器变量**：uri\_path
* **区分大小写**：否
* **运算符**：等于 (=)
* **Pattern to match**：`^(\/(?!admin)(?!identity)(?!sso)[^\/]*)\/(.*)`

5、添加以下操作：

* **重写类型**：URL
* **操作类型**：设置
* **组件**：URL 路径
* **URL 路径值**： `/{var_uri_path_2}`
* **重新评估路径图**：未选中

6、选择**创建**。

## 创建存储类 <a href="#creating-a-storage-class" id="creating-a-storage-class"></a>

部署需要使用您提供的支持 [ReadWriteMany](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes) 的共享存储类。以下示例是一个可以在 Azure Cloud Shell 中运行的脚本，用于创建满足要求的 Azure 文件存储类。

{% hint style="danger" %}
以下是一个说明性的示例，请根据您自己的安全要求分配权限。
{% endhint %}

```yml
cat <<EOF | kubectl apply -n bitwarden -f -
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: azure-file
  namespace: bitwarden
provisioner: file.csi.azure.com
allowVolumeExpansion: true
mountOptions:
  - dir_mode=0777
  - file_mode=0777
  - uid=0
  - gid=0
  - mfsymlinks
  - cache=strict
  - actimeo=30
parameters:
  skuName: Standard_LRS
EOF
```

您必须在 `my-values.yaml` 中设置 `sharedStorageClassName` 的值，值应与您给予类的名称相同，在本例中为：

```bash
sharedStorageClassName: "azure-file"
```

## 使用 Azure Key Vault CSI 驱动程序 <a href="#using-azure-key-vault-csi-driver" id="using-azure-key-vault-csi-driver"></a>

部署需要使用 Kubernetes secrets 对象为您的部署设置敏感值。虽然 `kubectl create secret` 命令可用于设置机密，但 Azure 客户可能更倾向于使用 Azure Key Vault 和 AKS Secrets Store CSI Driver。

{% hint style="success" %}
这些说明假设您已经设置了 Azure Key Vault。如果没有，请[立即创建一个](https://learn.microsoft.com/en-us/azure/aks/csi-secrets-store-driver#create-or-use-an-existing-azure-key-vault)。
{% endhint %}

1、使用以下命令为您的集群添加 Secrets Store CSI Driver 支持：

```bash
az aks enable-addons --addons azure-keyvault-secrets-provider --name myAKSCluster --resource-group myResourceGroup
```

该插件创建了一个用户分配的托管身份，您可以用它来进行密钥库的身份验证，然而您还有其他选择来进行身份访问控制。如果您使用已创建的用户分配的托管身份，您需要显式地为其分配 **Secret** > **Get** 的访问权限（[点击这里了解](https://learn.microsoft.com/en-us/azure/key-vault/general/assign-access-policy?tabs=azure-portal)）。

2、创建一个 SecretProviderClass，就像以下示例中所示。请注意，这个示例包含了必须替换的 `<REPLACE>` 占位符，其取决于您使用的是附带的 SQL pod 还是使用您自己的 SQL 服务器。

```yml
cat <<EOF | kubectl apply -n bitwarden -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: bitwarden-azure-keyvault-csi
  labels:
    app.kubernetes.io/component: secrets
  annotations:
spec:
  provider: azure
  parameters:
    useVMManagedIdentity: "true" # Set to false for workload identity
    userAssignedIdentityID: "<REPLACE>" # Set the clientID of the user-assigned managed identity to use
    # clientID: "<REPLACE>" # Setting this to use workload identity
    keyvaultName: "<REPLACE>"
    cloudName: "AzurePublicCloud"
    objects: |
      array:
        - |
          objectName: installationid
          objectAlias: installationid
          objectType: secret
          objectVersion: ""
        - |
          objectName: installationkey
          objectAlias: installationkey
          objectType: secret
          objectVersion: ""
        - |
          objectName: smtpusername
          objectAlias: smtpusername
          objectType: secret
          objectVersion: ""
        - |
          objectName: smtppassword
          objectAlias: smtppassword
          objectType: secret
          objectVersion: ""
        - |
          objectName: yubicoclientid
          objectAlias: yubicoclientid
          objectType: secret
          objectVersion: ""
        - |          
          objectName: yubicokey
          objectAlias: yubicokey
          objectType: secret
          objectVersion: ""
        - |
          objectName: hibpapikey
          objectAlias: hibpapikay
          objectType: secret
          objectVersion: ""
        - |          
          objectName: sapassword #-OR- dbconnectionstring if external SQL
          objectAlias: sapassword #-OR- dbconnectionstring if external SQL
          objectType: secret
          objectVersion: ""
    tenantId: "<REPLACE>"
  secretObjects:
  - secretName: "bitwarden-secret"
    type: Opaque
    data:
    - objectName: installationid
      key: globalSettings__installation__id
    - objectName: installationkey
      key: globalSettings__installation__key
      key: globalSettings__mail__smtp__username
    - objectName: smtppassword
      key: globalSettings__mail__smtp__password
    - objectName: yubicoclientid
      key: globalSettings__yubico__clientId
    - objectName: yubicokey
      key: globalSettings__yubico__key
    - objectName: hibpapikey
       key: globalSettings__hibpApiKey
    - objectName: sapassword #-OR- dbconnectionstring if external SQL
      key: SA_PASSWORD #-OR- globalSettings__sqlServer__connectionString if external SQL
EOF
```

3、使用下列命令在密钥库中设置所需的机密值：

{% hint style="danger" %}
此示例会将命令记录到您的 shell 历史记录中。可以考虑使用其他方法来安全地设置机密。
{% endhint %}

```shellscript
kvname=<REPLACE>
az keyvault secret set --name installationid --vault-name $kvname --value <REPLACE>
az keyvault secret set --name installationkey --vault-name $kvname --value <REPLACE>
az keyvault secret set --name smtpusername --vault-name $kvname --value <REPLACE>
az keyvault secret set --name smtppassword --vault-name $kvname --value <REPLACE>
az keyvault secret set --name yubicoclientid --vault-name $kvname --value <REPLACE>
az keyvault secret set --name yubicokey --vault-name $kvname --value <REPLACE>
az keyvault secret set --name hibpapikey --vault-name $kvname --value <REPLACE>
az keyvault secret set --name sapassword --vault-name $kvname --value <REPLACE>
# - OR -
# az keyvault secret set --name dbconnectionstring --vault-name $kvname --value <REPLACE>
```

4、在您的 `my-values.yaml` 文件中，设置以下值：

* `secrets.secretName` ：将此值设置为 SecretProviderClass 中定义的 `secretName` 值。
* `secrets.secretProviderClass` ：将此值设置为 SecretProviderClass 中定义的 `metadata.name` 值。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://help.ppgg.in/docs/self-hosting/deploy-and-configure/helm/azure-aks-deployment.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
