2018 年 11 月

第 33 卷,第 12 期

容器 - 使用 Azure Kubernetes 服务确保正常运行

作者 Chander Dhall

在容器业务流程领域,似乎每个人都在谈论 Kubernetes。Kubernetes 最初是由 Google 设计的开放源代码平台,用于跨虚拟机 (VM) 群集组织安排 Docker(或任何开放容器计划)容器,同时支持部署、回滚、缩放和其他许多功能。管理 Kubernetes 群集可能是一项非常复杂的工作,所以 Azure 团队提供了托管解决方案 Azure Kubernetes 服务 (AKS),大大简化了管理过程。

在本文中,我将展示如何部署 AKS 群集、创建安全的 Azure 容器注册表 (ACR)、部署 ASP.NET Web API 应用程序,以及通过 Kubernetes Ingress 和 Azure DNS 在 Internet 上公开此应用程序。本文并不旨在深入探讨 Kubernetes,只是提供了使用 Azure AKS 产品/服务快速运行应用程序所需了解的一切信息。

Azure AKS 的优势之一是,控制平面(包括主节点和配置节点)是完全托管的。Kubernetes 控制平面通常包含至少一个主节点,以及一个、三个或五个 etcd 配置节点。可以想象,管理这些核心服务可能会很麻烦,且需要支付高额费用。使用 AKS,只需单击一下按钮,即可升级核心服务或横向扩展其他工作节点。此外,对于这些管理节点,暂时无需支付额外费用。只需支付运行服务的工作节点的费用。

若要了解本文中的代码,可以访问 bit.ly/2zaFQeq。存储库中包含的是,基本 ASP.NET Web API 应用程序,以及 Dockerfile 和 Kubernetes 清单。

创建 Kubernetes AKS 群集

Azure CLI 用于创建和管理 Azure 云订阅中的资源。bit.ly/2OcwFQb 中介绍了它。在本文中,我将从始至终使用它来管理各种 Azure 服务。Azure Cloud Shell (bit.ly/2yESmTP) 是基于 Web 的 shell,可便于开发人员运行命令,而无需在本地安装 CLI。

首先,使用下面这段代码,创建用于保留 AKS 群集和容器注册表的资源组:

> group create --name my-aks-cluster --location eastus2

创建资源组后,我便创建包含一个节点的群集。尽管可使用与 B1 可突增映像一样小的 VM,但建议使用至少为 2 核的 7GB 内存实例(D 系列或更高版本)。如果小于此大小,就会在群集扩展和升级时有不可靠的趋势。还需要注意的一点是,AKS 暂仅支持单一类型节点,因此稍后无法决定纵向扩展到更大的 VM 实例。今后将支持通过多节点池添加不同类型的节点,但现在必须确保所选择的节点大小能够满足计划运行的服务的需求。

创建群集时,可以在一旁闲着,并耐心等待,因为通常最长需要 10 到 12 分钟才能完成。下面是用于启动操作的代码:

> az aks create --resource-group my-aks-cluster --name my-aks-cluster
  --node-count 1 --generate-ssh-keys --kubernetes-version 1.11.2
  --node-vm-size Standard_D2s_v3

用户必须有 Docker 注册表,才能将 Docker 映像置入 ASK 群集。虽然使用公共 Docker 注册表对于开放源代码分发而言是可以接受的,但大多数项目都希望在专用注册表中保护应用程序代码。

通过 Azure 容器注册表 (ACR),Azure 提供了安全的托管 Docker 注册表解决方案。若要设置 ACR 实例,请运行下面的命令:

> az acr create --resource-group my-aks-cluster
  --name <REGISTRY_NAME> --sku Basic --admin-enabled true

请注意,对于 Azure 上的所有 ACR 托管注册表名称,注册表名称必须都是唯一的。

Kubernetes CLI

Kubectl 用于与 AKS 群集进行交互。它适用于所有 OS,且安装方法有多种。有关详细信息,可以访问 bit.ly/2Q58CnJ。此外,还有一个基于 Web 的仪表板,它非常适用于快速概览群集状态,但它并未涵盖可用的所有 API 操作,你可能经常会转而求助于 kubectl CLI。即使你并不喜欢命令行操作,但随着时间推移,也很可能会开始赞赏 kubectl 的强大功能。通过结合使用 Azure CLI,无需离开 shell,即可执行任何操作。

安装 kubectl 后,便可以运行下面的命令,以在本地导入凭据,从而对群集进行身份验证:

> az aks get-credentials --resource-group my-aks-cluster
  --name my-aks-cluster

运行此命令可使用群集 URI 以及签名颁发机构和凭据来更新 ~/.kube/config。它还添加了将群集设置为当前配置的上下文。kubectl 配置可以保留多个群集的上下文,运行 kubectl config 命令即可轻松切换。此外,还可以使用开放源代码实用工具(kubectx 和 kubectxwin),从而更轻松地切换上下文。

导入凭据后,可通过运行 kubectl get nodes 命令来列出正在运行的节点,从而测试与群集的连接性。应该会看到如下内容:

> kubectl get nodes
NAME                     STATUS    ROLES     AGE       VERSION
aks-default-34827916-0   Ready     agent     1d        v1.11.2

添加用于部署的容器注册表机密

Kubernetes 提供了一种使用机密存储敏感数据的安全方法。例如,为了防止 ACR 凭据存储在源代码中,应创建机密,并通过 Kubernetes 部署清单引用机密。若要检索 ACR 服务的凭据,请运行下面的命令:

> az acr credential show --name <REGISTRY_NAME>

接下来,使用 kubectl 生成一种特殊类型的机密 (docker-registry),专门用于存储 Docker 提供的凭据令牌。后面的代码使用从 ACR 查询中检索到的凭据,创建 my-docker-creds 机密。请注意,用户名区分大小写,ACR 默认对内置管理员帐户使用小写的用户名:

> kubectl create secret docker-registry my-docker-creds
  --docker-server=<REGISTRY_NAME>.azurecr.io --docker-username=<REGISTRY_USERNAMEE>
  --docker-password=<REGISTRY_PASSWORD> --docker-email=<ANY_EMAIL>

最后,运行下面的命令,以确认机密是否已创建:

> kubectl describe secrets my-docker-creds
Name:         my-docker-creds
Namespace:    default
Type:  kubernetes.io/dockerconfigjson
Data
====
.dockerconfigjson:  223 bytes

创建 Docker 容器

AKS 中的所有应用程序都部署为 Docker 容器。下面的 Dockerfile 代码创建可传送到群集的 Docker 映像:

FROM microsoft/dotnet:2.1-sdk AS builder
COPY . /app
WORKDIR /app
RUN dotnet publish -f netcoreapp2.1 -c Debug -o /publish
FROM microsoft/dotnet:2.1.3-aspnetcore-runtime
COPY --from=builder /publish .
ENTRYPOINT ["/bin/bash", "-c", "dotnet WebApiApp.dll"]

此 Dockerfile 使用多阶段方法,将生成拆分为独立的生成阶段和运行时阶段。这样就不用添加整个 SDK 用于分发,从而大大减少了映像总大小。

将映像推送到注册表

Docker 处理本地映像和容器的概念,以执行映像。无法将 Docker 映像直接推送到群集。相反,必须将映像托管在 Kubernetes 可以访问的位置,以便将映像从本地拉取到群集。ACR 注册表是一个安全位置,可便于在开发环境、持续集成环境和群集环境之间集中管理映像。

必须使用格式 <REGISTRY>/<REPOSITORY>/<IMAGE>:<TAG> 生成和标记映像,以告知 Docker 向上游推送映像的位置。使用存储库(可任意命名),可以将注册表映像分离到逻辑组。下面的代码展示了如何在将映像推送到 ACR 前生成和标记映像。尽管支持最新标记,但在使用 Kubernetes 时,强烈建议使用语义化版本控制。如果可以利用版本号,管理部署和回滚就会更加轻松。代码如下:

> az acr login --name <REGISTRY_NAME>
> docker build -t <REGISTRY_NAME>.azurecr.io/api/my-webapi-app:1.0 .
> docker push <REGISTRY_NAME>.azurecr.io/api/my-webapi-app:1.0
baf6b1178a5b: Pushed
b3f8eefa2758: Pushed
393dd8f4a879: Pushed
0ad9ffac9ae9: Pushed
8ea427f58308: Pushed
cdb3f9544e4c: Pushed
1.0: digest: sha256:47399f3b2365a9 size: 1579

现在,运行下面的命令,以确认是否已向上游推送映像:

> az acr login --name <REGISTRY_NAME>
> az acr repository list --name <REGISTRY_NAME>

部署应用程序

Kubernetes 使用清单来描述群集中的每个对象。清单是通过 Kubernetes API 管理的 yaml 文件。部署清单类型用于描述资源、映像源和所需的应用程序状态。图 1 是一个简单清单,它告知 Kubernetes 要使用哪个容器、所需的正在运行容器和标签的实例数,以帮助描述群集中的应用程序。它还在拉取远程映像时添加机密名称,以对 ACR 进行身份验证。

图 1:Deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-webapi-app
spec:
  selector:
    matchLabels:
      app: my-webapi-app
  replicas: 2
  template:
    metadata:
      labels:
        app: my-webapi-app
    spec:
      containers:
      - name: my-webapi-app
        image: <REGISTRY_NAME>.azurecr.io/api/my-webapi-app:1.0
        livenessProbe:
          initialDelaySeconds: 10
          path: /health
          periodSeconds: 5
        ports:
        - containerPort: 80
      imagePullSecrets:
      - name: my-docker-creds

运行下面的命令,以部署清单:

> kubectl apply -f ./deployment.yaml

Kubernetes 利用 Pod 概念,将一个或多个容器分入群集中可缩放的逻辑实例。通常情况下,每个 Pod 都有一个容器。这样一来,便能独立缩放应用程序的任何服务。常见的错误观念是,将应用程序的所有服务(如 Web 应用程序和数据库)都放在一个 Pod 中。这样做不仅无法让 Web 前端独立于数据库缩放,你还会因此失去 Kubernetes 的诸多优势。

常见的情况是,可以在 Pod 中添加其他容器,这就是“侧车”概念。假设有一个容器,用于观测应用程序容器,并提供指标或日志记录。在这种情况下,将这两个容器置于一个 Pod 中会带来真正的优势。否则,在透彻了解容器分组限制(和优势)前,通常最好一个 Pod 内放置一个容器。

部署完成后,便能运行下面的命令,以检查应用程序 Pod 状态:

> kubectl get pods
NAME                             READY     STATUS    RESTARTS   AGE
my-webapi-app-64cdf6b449-9hsks   2/2       Running   0          2m

请注意,为了满足副本集要求,运行了两个 Pod 实例。

创建服务

至此,应用程序 Docker 容器已部署到群集,必须使用服务,它才具有可发现性。Kubernetes 服务让你的 Pod 能够被群集中的其他 Pod 发现。为此,它使用群集的内部 DNS 注册自身。它还跨所有 Pod 副本进行负载均衡,并在 Pod 升级期间管理 Pod 可用性。服务是非常强大的 Kubernetes 概念,这是在滚动部署、蓝/绿部署和 Canary 部署升级期间对应用程序提供可用性所必需。下面的命令创建用于部署的服务:

> kubectl expose deployment/my-webapi-app
service "my-webapi-app" exposed

现在,运行下面的命令,以查看在群集中运行的服务:

> kubectl get services
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
my-web-api   ClusterIP   10.0.0.157   <none>        443/TCP   1d

默认情况下,只能从群集中访问服务,因此没有 external-ip。使用 kubectl CLI,可以方便地在本地计算机和群集之间打开代理,从而以交互方式检查它是否在运行,如下面的代码所示:

> kubectl port-forward services/my-webapi-app 8080:80
> curl http://localhost:8080
StatusCode        : 200
StatusDescription : OK
Content           : Hello from our Kubernetes hosted service!

添加 HTTP 路由

Kubernetes 默认处于受保护状态,必须显式公开要从群集外部访问的服务。从安全角度来看,这是一项非常棒的设计功能,但可能会令首次使用的用户感到困惑。若要访问群集内基于 HTTP 的服务,最常见方法是使用 Kubernetes Ingress 控制器。使用 Ingress 控制器,可以通过 HTTP 代理入口点,根据主机名和路径将请求路由到内部服务。

在 Ingress 被添加到 Kubernetes 前,公开服务的主要方法是,使用 LoadBalancer 服务类型。这会导致负载均衡器数量激增(每个服务一个负载均衡器),每个都需要单独管理。使用 Ingress,群集中的所有服务都可供一个 Azure 负载均衡器访问,从而大大降低了成本和复杂性。 

ASK 提供了一个便捷加载项,用于为群集扩展用作处理这些请求的 Ingress 控制器的 Nginx 代理。可以运行下面的命令,以通过 Azure CLI 启用它:

> az aks enable-addons --resource-group my-aks-cluster
  --name my-aks-cluster --addons http_application_routing

可发出图 2**** 中的命令,以确认路由服务是否正在运行。

图 2:确认路由服务是否正在运行

> kubectl get pods --all-namespaces
NAMESPACE     NAME                                                              READY     
kube-system   addon-http-application-routing-default-http-backend-74d455htfw9   1/1      
kube-system   addon-http-application-routing-external-dns-7cf57b9cc7-lqhl5      1/1     
kube-system   addon-http-application-routing-nginx-ingress-controller-5595b2v   1/1

列表中应该会列出三个新控制器:Ingress 控制器、外部 DNS 和默认后端。默认后端用于在找不到现有服务的路由时,提供对客户端的响应。它非常类似于典型 ASP.NET 应用程序中的“404 找不到”处理程序,不同之处在于,它是作为单独的 Docker 容器运行。值得注意的是,尽管 HTTP 加载项非常适用于试验,但它并不适用于生产。

公开服务

Ingress 合并了 Ingress 控制器和 Ingress 定义。每个服务都有 Ingress 定义,用于告知 Ingress 控制器如何公开服务。下面的命令获取群集的 DNS 名称:

> az aks show --resource-group my-aks-cluster
  --name my-aks-cluster --query
  addonProfiles.httpApplicationRouting.config.HTTPApplicationRoutingZoneName
  -o table
Result
--------------------------------------
<CLUSTER_PREFIX>.eastus2.aksapp.io

Ingress 注释 kubernetes.io/ingress.class 通知 Ingress 控制器处理此规范,如图 3 所示。使用前面解析的群集 DNS 名称,将为主机添加子域和“/”根路径。此外,还必须添加服务名称及其内部公开的端口,以告知 Ingress 控制器在群集内路由请求的位置。

图 3:Ingress.yaml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-webapi-app
  annotations:
    kubernetes.io/ingress.class: addon-http-application-routing
spec:
  rules:
  - host: my-webapi-app.<CLUSTER_PREFIX>.eastus2.aksapp.io
    http:
      paths:
      - path: /
        backend:
          serviceName: my-webapi-app
          servicePort: 80

然后,可以运行下面的命令,以应用 Ingress 清单:

> kubectl apply -f ./ingress.yaml

DNS 条目可能需要几分钟才能完成创建和传播,请耐心等待。可以在 Azure 门户中检查 DNS 服务的状态,如下所示:

> curl http://my-webapi-app.<CLUSTER_PREFIX>.eastus2.aksapp.io
StatusCode        : 200
StatusDescription : OK
Content           : Hello from our Kubernetes hosted service!

总结

此时,我们有包含一个节点的 AKS 群集,它与使用受保护机密托管应用程序 Docker 映像的 ACR 服务一起运行。若要探索 Azure AKS Kubernetes 可以提供的其他许多功能,这应该是一个很好的起点。我有一个简单的理念,即“购买对你有用的产品,生成让你与众不同的内容。” 可以看到,Azure 简化了 Kubernetes,这样开发人员和 DevOps 专业人员都能专注于更重要的任务。


Chander Dhall**** 是 Cazton 的 CEO,他曾八次成为 Microsoft MVP,不仅是 Google 开发人员专家,还是架构和实现解决方案领域的世界知名技术领导者。**

衷心感谢以下 Microsoft 专家对本文的技术审阅:Brendan Burns


在 MSDN 杂志论坛讨论这篇文章