创建 HTTPS 入口控制器并在 Azure Kubernetes 服务 (AKS) 中使用自己的 TLS 证书

入口控制器是一个软件片段,为 Kubernetes 服务提供反向代理、可配置的流量路由和 TLS 终止。 Kubernetes 入口资源用于配置各个 Kubernetes 服务的入口规则和路由。 借助入口控制器和入口规则,可以使用单个 IP 地址将流量路由到 Kubernetes 群集中的多个服务。

本文介绍如何在 Azure Kubernetes 服务 (AKS) 群集中部署 NGINX 入口控制器。 生成自己的证书,并创建用于入口路由的 Kubernetes 机密。 最后,在 AKS 群集中运行两个应用程序(可通过单个 IP 地址访问其中的每个应用程序)。

也可执行以下操作:

准备阶段

本文使用 Helm 3支持的 Kubernetes 版本上安装 NGINX 入口控制器。 确保使用最新版本的 Helm,并且有权访问 ingress-nginx Helm 存储库。 本文中概述的步骤可能与 Helm chart、NGINX 入口控制器或 Kubernetes 的先前版本不兼容。

有关配置和使用 Helm 的详细信息,请参阅在 Azure Kubernetes 服务 (AKS) 中使用 Helm 安装应用程序

此外,本文假设你已有一个带有集成 ACR 的 AKS 群集。 若要更详细地了解如何创建具有集成 ACR 的 AKS 群集,请参阅使用 Azure 容器注册表从 Azure Kubernetes 服务进行身份验证

本文还要求运行 Azure CLI 2.0.64 或更高版本。 运行 az --version 即可查找版本。 如果需要进行安装或升级,请参阅安装 Azure CLI

将 Helm 图表使用的映像导入 ACR

本文使用 NGINX 入口控制器 Helm 图表,它依赖于三个容器映像。

使用 az acr import 将这些映像导入 ACR。

REGISTRY_NAME=<REGISTRY_NAME>
SOURCE_REGISTRY=k8sgcr.azk8s.cn
CONTROLLER_IMAGE=ingress-nginx/controller
CONTROLLER_TAG=v1.0.4
PATCH_IMAGE=ingress-nginx/kube-webhook-certgen
PATCH_TAG=v1.1.1
DEFAULTBACKEND_IMAGE=defaultbackend-amd64
DEFAULTBACKEND_TAG=1.5

az acr import --name $REGISTRY_NAME --source $SOURCE_REGISTRY/$CONTROLLER_IMAGE:$CONTROLLER_TAG --image $CONTROLLER_IMAGE:$CONTROLLER_TAG
az acr import --name $REGISTRY_NAME --source $SOURCE_REGISTRY/$PATCH_IMAGE:$PATCH_TAG --image $PATCH_IMAGE:$PATCH_TAG
az acr import --name $REGISTRY_NAME --source $SOURCE_REGISTRY/$DEFAULTBACKEND_IMAGE:$DEFAULTBACKEND_TAG --image $DEFAULTBACKEND_IMAGE:$DEFAULTBACKEND_TAG

注意

除了将容器映像导入 ACR 之外,还可以将 Helm 图表导入 ACR。 有关详细信息,请参阅将 Helm 图表推送和拉取到 Azure 容器注册表

创建入口控制器

若要创建入口控制器,请使用 Helm 来安装 nginx-ingress。 对于增加的冗余,NGINX 入口控制器的两个副本会在部署时具备 --set controller.replicaCount 参数。 若要充分利用正在运行的入口控制器副本,请确保 AKS 群集中有多个节点。

还需要在 Linux 节点上计划入口控制器。 Windows Server 节点不应运行入口控制器。 使用 --set nodeSelector 参数指定节点选择器,以告知 Kubernetes 计划程序在基于 Linux 的节点上运行 NGINX 入口控制器。

提示

以下示例为名为 ingress-basic 的入口资源创建 Kubernetes 命名空间,目的是在该命名空间内执行操作。 根据需要为你自己的环境指定一个命名空间。 如果 AKS 群集未启用 Kubernetes RBAC,请将 --set rbac.create=false 添加到 Helm 命令中。

提示

若要为对群集中容器的请求启用客户端源 IP 保留,请将 --set controller.service.externalTrafficPolicy=Local 添加到 Helm install 命令中。 客户端源 IP 存储在 X-Forwarded-For 下的请求头中。 使用启用了“客户端源 IP 保留”的入口控制器时,TLS 直通将不起作用。

# Add the ingress-nginx repository
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx

# Set variable for ACR location to use for pulling images
ACR_URL=<REGISTRY_URL>

# Use Helm to deploy an NGINX ingress controller
helm install nginx-ingress ingress-nginx/ingress-nginx \
    --version 4.0.13 \
    --namespace ingress-basic --create-namespace \
    --set controller.replicaCount=2 \
    --set controller.nodeSelector."kubernetes\.io/os"=linux \
    --set controller.image.registry=$ACR_URL \
    --set controller.image.image=$CONTROLLER_IMAGE \
    --set controller.image.tag=$CONTROLLER_TAG \
    --set controller.image.digest="" \
    --set controller.image.repository=$SOURCE_REGISTRY/$CONTROLLER_IMAGE \
    --set controller.admissionWebhooks.patch.nodeSelector."kubernetes\.io/os"=linux \
    --set controller.service.annotations."service\.beta\.kubernetes\.io/azure-load-balancer-health-probe-request-path"=/healthz \
    --set controller.admissionWebhooks.patch.image.registry=$ACR_URL \
    --set controller.admissionWebhooks.patch.image.image=$PATCH_IMAGE \
    --set controller.admissionWebhooks.patch.image.tag=$PATCH_TAG \
    --set controller.admissionWebhooks.patch.image.digest="" \
    --set defaultBackend.nodeSelector."kubernetes\.io/os"=linux \
    --set defaultBackend.image.registry=$ACR_URL \
    --set defaultBackend.image.image=$DEFAULTBACKEND_IMAGE \
    --set defaultBackend.image.tag=$DEFAULTBACKEND_TAG \
    --set defaultBackend.image.repository=$SOURCE_REGISTRY/$DEFAULTBACKEND_IMAGE \
    --set defaultBackend.image.digest=""

在安装过程中,将为入口控制器创建一个 Azure 公共 IP 地址。 此公共 IP 地址在入口控制器的寿命期内是静态的。 如果你删除入口控制器,则公共 IP 地址分配会丢失。 如果你然后创建了另外的入口控制器,则会分配新的公共 IP 地址。 如果希望保持使用此公共 IP 地址,则可以改为创建具有静态公共 IP 地址的入口控制器

若要获取公共 IP 地址,请使用 kubectl get service 命令。

kubectl --namespace ingress-basic get services -o wide -w nginx-ingress-ingress-nginx-controller

将 IP 地址分配给服务需要几分钟时间。

$ kubectl --namespace ingress-basic get services -o wide -w nginx-ingress-ingress-nginx-controller

NAME                                     TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                      AGE   SELECTOR
nginx-ingress-ingress-nginx-controller   LoadBalancer   10.0.74.133   EXTERNAL_IP     80:32486/TCP,443:30953/TCP   44s   app.kubernetes.io/component=controller,app.kubernetes.io/instance=nginx-ingress,app.kubernetes.io/name=ingress-nginx

请记下此公共 IP 地址,因为在最后一个步骤中测试部署时需要用到。

尚未创建入口规则。 如果浏览到公共 IP 地址,将显示 NGINX 入口控制器的默认 404 页面。

生成 TLS 证书

在本文中,我们将生成使用 openssl 的自签名证书。 对于生产用途,应该通过提供商或你自己的证书颁发机构 (CA) 请求受信任的已签名证书。 下一步骤将使用 TLS 证书和 OpenSSL 生成的私钥来生成 Kubernetes 机密。

以下示例生成有效期为 365 天的、名为 aks-ingress-tls.crt 的 2048 位 RSA X509 证书。 私钥文件名为 aks-ingress-tls.key。 Kubernetes TLS 机密需要这两个文件。

本文使用 demo.azure.com 使用者公用名,无需进行更改。 对于生产用途,请为 -subj 参数指定自己的组织值:

openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
    -out aks-ingress-tls.crt \
    -keyout aks-ingress-tls.key \
    -subj "/CN=demo.azure.com/O=aks-ingress-tls"

创建 TLS 证书的 Kubernetes 机密

若使 Kubernetes 能够对入口控制器使用 TLS 证书和私钥,需要创建并使用一个机密。 机密只需定义一次,它使用上一步骤中创建的证书和密钥文件。 然后,可以在定义入口路由时引用此机密。

以下示例创建名为 aks-ingress-tls 的机密:

kubectl create secret tls aks-ingress-tls \
    --namespace ingress-basic \
    --key aks-ingress-tls.key \
    --cert aks-ingress-tls.crt

运行演示应用程序

已配置入口控制器和包含你的证书的机密。 若要查看运行中的入口控制器,请在 AKS 群集中运行两个演示应用程序。 此示例使用 kubectl apply 来部署一个简单“Hello world”应用程序的两个实例。

创建“aks-helloworld.yaml”文件,并将其复制到以下示例 YAML 中:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: aks-helloworld
spec:
  replicas: 1
  selector:
    matchLabels:
      app: aks-helloworld
  template:
    metadata:
      labels:
        app: aks-helloworld
    spec:
      containers:
      - name: aks-helloworld
        image: mcr.azk8s.cn/azuredocs/aks-helloworld:v1
        ports:
        - containerPort: 80
        env:
        - name: TITLE
          value: "Welcome to Azure Kubernetes Service (AKS)"
---
apiVersion: v1
kind: Service
metadata:
  name: aks-helloworld
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    app: aks-helloworld

创建“ingress-demo.yaml”文件,并将其复制到以下示例 YAML 中:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: ingress-demo
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ingress-demo
  template:
    metadata:
      labels:
        app: ingress-demo
    spec:
      containers:
      - name: ingress-demo
        image: mcr.azk8s.cn/azuredocs/aks-helloworld:v1
        ports:
        - containerPort: 80
        env:
        - name: TITLE
          value: "AKS Ingress Demo"
---
apiVersion: v1
kind: Service
metadata:
  name: ingress-demo
spec:
  type: ClusterIP
  ports:
  - port: 80
  selector:
    app: ingress-demo

使用 kubectl apply 运行这两个演示应用程序:

kubectl apply -f aks-helloworld.yaml --namespace ingress-basic
kubectl apply -f ingress-demo.yaml --namespace ingress-basic

创建入口路由

两个应用程序现在都在 Kubernetes 群集中运行,但它们配置了服务类型 ClusterIP。 因此,无法通过 Internet 访问它们。 若要公开发布这两个应用程序,请创建 Kubernetes 入口资源。 该入口资源配置将流量路由到这两个应用程序之一的规则。

在以下示例中,传往地址 https://demo.azure.com/ 的流量将路由到名为 aks-helloworld 的服务。 传往地址 https://demo.azure.com/hello-world-two 的流量将路由到 ingress-demo 服务。 在本文中,无需更改这些演示主机名。 对于生产用途,请提供在证书请求和生成过程中指定的名称。

提示

如果在证书请求过程中指定的主机名(CN 名称)与在入口路由中定义的主机不匹配,则入口控制器将显示“Kubernetes 入口控制器虚构证书”警告。 请确保证书和入口路由主机名匹配。

tls 节告知入口路由要对主机 demo.azure.com 使用名为 aks-ingress-tls 的机密。 同样,对于生产用途,请指定自己的主机地址。

创建名为 hello-world-ingress.yaml 的文件,并将其复制到以下示例 YAML 中。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: hello-world-ingress
  namespace: ingress-basic
  annotations:
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - demo.azure.com
    secretName: aks-ingress-tls
  rules:
  - host: demo.azure.com
    http:
      paths:
      - path: /hello-world-one(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: aks-helloworld
            port:
              number: 80
      - path: /hello-world-two(/|$)(.*)
        pathType: Prefix
        backend:
          service:
            name: ingress-demo
            port:
              number: 80
      - path: /(.*)
        pathType: Prefix
        backend:
          service:
            name: aks-helloworld
            port:
              number: 80

使用 kubectl apply -f hello-world-ingress.yaml 命令创建入口资源。

kubectl apply -f hello-world-ingress.yaml

示例输出中显示创建了入口资源。

$ kubectl apply -f hello-world-ingress.yaml

ingress.networking.k8s.io/hello-world-ingress created

测试入口配置

若要使用虚构的 demo.azure.com 主机测试证书,请使用 curl 并指定 --resolve 参数。 此参数告知要将 demo.azure.com 名称映射到入口控制器的公共 IP 地址。 请按以下示例中所示,指定自己的入口控制器的公共 IP 地址:

curl -v -k --resolve demo.azure.com:443:EXTERNAL_IP https://demo.azure.com

未为此地址提供其他路径,因此入口控制器默认为 / 路由。 第一个演示应用程序已返回,如以下精简版示例输出中所示:

$ curl -v -k --resolve demo.azure.com:443:EXTERNAL_IP https://demo.azure.com

[...]
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <link rel="stylesheet" type="text/css" href="/static/default.css">
    <title>Welcome to Azure Kubernetes Service (AKS)</title>
[...]

curl 命令中的 -v 参数输出详细信息,包括收到的 TLS 证书。 在输出 curl 结果的中途,可以验证是否使用了你自己的 TLS 证书。 即使使用的是自签名证书, -k 参数也会继续加载页面。 以下示例显示了使用“颁发者:CN=demo.azure.com; O=aks-ingress-tls”证书:

[...]
* Server certificate:
* subject: CN=demo.azure.com; O=aks-ingress-tls
* start date: Oct 22 22:13:54 2018 GMT
* expire date: Oct 22 22:13:54 2019 GMT
* issuer: CN=demo.azure.com; O=aks-ingress-tls
* SSL certificate verify result: self signed certificate (18), continuing anyway.
[...]

现在向地址添加“/hello-world-two”路径,例如 https://demo.azure.com/hello-world-two。 第二个使用自定义标题的演示应用程序已返回,如以下精简版示例输出中所示:

$ curl -v -k --resolve demo.azure.com:443:EXTERNAL_IP https://demo.azure.com/hello-world-two

[...]
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <link rel="stylesheet" type="text/css" href="/static/default.css">
    <title>AKS Ingress Demo</title>
[...]

清理资源

本文使用了 Helm 来安装入口组件和示例应用。 在部署 Helm 图表时,会创建若干 Kubernetes 资源。 这些资源包括 pod、部署和服务。 若要清理这些资源,可以删除整个示例命名空间,也可以删除单个资源。

删除示例命名空间以及所有资源

若要删除整个示例命名空间,请使用 kubectl delete 命令并指定命名空间名称。 将会删除命名空间中的所有资源。

kubectl delete namespace ingress-basic

单独删除资源

也可采用更细致的方法来删除单个已创建的资源。 使用 helm list 命令列出 Helm 版本。

helm list --namespace ingress-basic

查找名为“nginx-ingress”的图表,如以下示例输出中所示:

$ helm list --namespace ingress-basic

NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
nginx-ingress           ingress-basic   1               2020-01-06 19:55:46.358275 -0600 CST    deployed        nginx-ingress-1.27.1    0.26.1 

使用 helm uninstall 命令卸载这些版本。

helm uninstall nginx-ingress --namespace ingress-basic

下面的示例将卸载 NGINX 入口部署。

$ helm uninstall nginx-ingress --namespace ingress-basic

release "nginx-ingress" uninstalled

接下来,删除两个示例应用程序:

kubectl delete -f aks-helloworld.yaml --namespace ingress-basic
kubectl delete -f ingress-demo.yaml --namespace ingress-basic

删除将流量定向到示例应用的入口路由:

kubectl delete -f hello-world-ingress.yaml

删除证书机密:

kubectl delete secret aks-ingress-tls --namespace ingress-basic

最后,可以删除自身命名空间。 使用 kubectl delete 命令并指定命名空间名称。

kubectl delete namespace ingress-basic

后续步骤

本文包含 AKS 的一些外部组件。 若要详细了解这些组件,请参阅以下项目页面:

也可执行以下操作: