现在是最终将某些东西部署到集群的时候了。通常,要部署应用程序,您需要准备一个 JSON 或 YAML 文件来描述您的应用程序包含的所有组件,并将该文件应用到您的集群。这将是声明性方法。

由于这可能是您第一次将应用程序部署到 Kubernetes,让我们选择一种更简单的方法来执行此操作。我们将使用简单的单行命令式命令来部署您的应用程序。

部署您的应用程序

部署应用程序的必要方式是使用 kubectl create deployment 命令。正如命令本身所暗示的,它会创建一个 Deployment 对象,该对象代表部署在集群中的应用程序。通过使用命令式命令,您无需在编写 YAML 或 JSON 清单时了解部署对象的结构。

创建 Deployment

在上一章中,您创建了一个 Node.js 应用程序,您将其打包到一个容器映像中并推送到 Docker Hub 以使其易于分发到任何计算机。让我们将该应用程序部署到您的 Kubernetes 集群。这是您需要执行的命令:

  1. $ kubectl create deployment kubia --image=betnevs/kubia:1.0
  2. deployment.apps/kubia created
  • 您想创建一个 deployment 对象。
  • 您希望该对象被称为 kubia。
  • 您希望部署使用容器映像 betnevs/kubia:1.0。默认情况下,镜像是从 Docker Hub 拉取的,但您也可以在镜像名称中指定镜像仓库(例如,quay.io/luksa/kubia:1.0)。

Deployment 对象现在存储在 Kubernetes API 中。这个对象的存在告诉 Kubernetes luksa/kubia:1.0 容器必须在你的集群中运行。你已经陈述了你想要的状态。 Kubernetes 现在必须确保实际状态反映您的意愿。

列出 Deployments

与 Kubernetes 的交互主要包括通过其 API 创建和操作对象。 Kubernetes 存储这些对象,然后执行操作以使它们栩栩如生。例如,当您创建一个部署对象时,Kubernetes 会运行一个应用程序。然后,Kubernetes 通过将状态写入同一个部署对象,让您了解应用程序的当前状态。您可以通过回读对象来查看状态。一种方法是列出所有 Deployment 对象,如下所示:

  1. $ kubectl get deployments
  2. NAME READY UP-TO-DATE AVAILABLE AGE
  3. kubia 0/1 1 0 6s

kubectl get deployments 命令列出集群中当前存在的所有 Deployment 对象。您的集群中只有一个部署。它运行您的应用程序的一个实例,如 UP-TO-DATE 列所示,但 AVAILABLE 列表示该应用程序尚不可用。那是因为容器没有准备好,如 READY 列所示。你可以看到一共零个容器准备好了。

您可能想知道是否可以通过运行 kubectl get containers 让 Kubernetes 列出所有正在运行的容器。让我们试试这个。

  1. $ kubectl get containers
  2. error: the server doesn't have a resource type "containers"

该命令失败,因为 Kubernetes 没有“容器”对象类型。这可能看起来很奇怪,因为 Kubernetes 都是关于运行容器的,但有一个转折点。容器不是 Kubernetes 中最小的部署单元。那么,什么是?

介绍 Pods

在 Kubernetes 中,您无需部署单独的容器,而是部署位于同一位置的容器组——即所谓的 pod。你知道,就像鲸鱼荚或豌豆荚一样。

pod 是一组一个或多个密切相关的容器(与 pod 中的豌豆不同),它们一起运行在同一个工作节点上,并且需要共享某些 Linux 命名空间,以便它们可以比其他 pod 更紧密地交互。

在上一章中,我展示了一个示例,其中两个进程使用相同的命名空间。通过共享网络命名空间,两个进程使用相同的网络接口,共享相同的 IP 地址和端口空间。通过共享 UTS 命名空间,两者都可以看到相同的系统主机名。这正是在同一个 pod 中运行容器时发生的情况。它们使用相同的网络和 UTS 命名空间以及其他命名空间,具体取决于 pod 的规范。
image.png
如图所示,您可以将每个 pod 视为包含一个应用程序的独立逻辑计算机。应用程序可以由在容器中运行的单个进程组成,也可以由主应用程序进程和附加的支持进程组成,每个进程都在单独的容器中运行。 Pod 分布在集群的所有工作节点上。

每个 pod 都有自己的 IP、主机名、进程、网络接口和其他资源。属于同一 pod 的容器认为它们是唯一在计算机上运行的容器。即使位于同一节点上,它们也看不到任何其他 pod 的进程。

列出 pods

由于容器不是顶级 Kubernetes 对象,因此您无法列出它们。但是您可以列出 pod。正如下一个清单所示,通过创建 Deployment 对象,您已经部署了一个 pod。

  1. $ kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. kubia-9d785b578-p449x 0/1 Pending 0 1m

这是运行您的应用程序容器的 pod。准确地说,由于状态仍然是 Pending,应用程序,或者更确切地说是容器,还没有运行。这也在 READY 列中表示,这表明 pod 有一个未准备好的容器。

pod 处于挂起状态的原因是,分配了 pod 的工作节点必须先下载容器镜像,然后才能运行它。下载完成后,创建 Pod 的容器,Pod 进入 Running 状态。

如果 Kubernetes 无法从仓库中拉取镜像,kubectl get pods 命令将在 STATUS 列中指出这一点。如果您使用自己的镜像,请确保在 Docker Hub 上将其标记为公开。尝试在另一台计算机上使用 docker pull 命令手动拉取镜像。

如果另一个问题导致您的 pod 无法运行,或者您只是想查看有关 pod 的更多信息,您还可以使用 kubectl describe pod 命令,就像您之前查看工作节点的详细信息一样。如果 pod 有任何问题,应通过此命令显示。查看其输出底部显示的事件。对于正在运行的 pod,它们应该类似于以下清单。

  1. Events:
  2. Type Reason Age From Message
  3. ---- ------ ---- ---- -------
  4. Normal Scheduled 25s default-scheduler Successfully assigned
  5. default/kubia-9d785b578-p449x
  6. to worker2
  7. Normal Pulling 23s kubelet, worker2 Pulling image "luksa/kubia:1.0"
  8. Normal Pulled 21s kubelet, worker2 Successfully pulled image
  9. Normal Created 21s kubelet, worker2 Created container kubia
  10. Normal Started 21s kubelet, worker2 Started container kubia

了解幕后发生的事情

image.png
当您运行 kubectl create 命令时,它通过向 Kubernetes API 服务器发送 HTTP 请求,在集群中创建了一个新的 Deployment 对象。 Kubernetes 然后创建了一个新的 Pod 对象,然后将其分配或调度到其中一个工作节点。工作节点上的 Kubernetes 代理(Kubelet)知道了新创建的 Pod 对象,看到它被调度到它的节点,就指示 Docker 从仓库中拉取指定的镜像,从镜像创建一个容器,并执行它。

术语调度是指将 pod 分配给节点。pod 会立即运行,而不是在未来的某个时间点运行。就像操作系统中的 CPU 调度器如何选择在哪个 CPU 上运行进程一样,Kubernetes 中的调度器决定了每个容器应该在哪个工作节点上执行。与 OS 进程不同,一旦将 pod 分配给一个节点,它就只能在该节点上运行。即使失败了,这个 pod 实例也不会像 CPU 进程那样移动到其他节点,但可能会创建一个新的 pod 实例来替换它。

根据您用于运行 Kubernetes 集群的方式,集群中的工作节点数量可能会有所不同。该图仅显示了 pod 被调度到的工作节点。在多节点集群中,没有其他工作节点参与该过程。

让外界访问您的应用程序

您的应用程序现在正在运行,因此下一个要回答的问题是如何访问它。我提到每个 pod 都有自己的 IP 地址,但这个地址是集群内部的,不能从外部访问。为了使 pod 可以从外部访问,您将通过创建一个 Service 对象来公开它。

存在几种类型的服务对象。你决定你需要什么类型。有些仅在集群内公开 pod,而另一些则在外部公开它们。 LoadBalancer 类型的服务提供了一个外部负载均衡器,这使得该服务可以通过公共 IP 访问。这是您现在要创建的服务类型。

创建 Service

  1. $ kubectl expose deployment kubia --type=LoadBalancer --port 8080
  2. service/kubia exposed

您之前运行的创建部署命令创建了一个部署对象,而公开部署命令创建了一个服务对象。这就是运行上述命令告诉 Kubernetes 的内容:

  • 您希望将属于 kubia 部署的所有 pod 公开为新服务。
  • 您希望通过负载均衡器从集群外部访问 pod。
  • 该应用程序侦听端口 8080,因此您希望通过该端口访问它。

您没有为 Service 对象指定名称,因此它继承了 Deployment 的名称。

列出 Services

Services 是 API 对象,就像 Kubernetes 中的 Pods、Deployments、Nodes 和几乎所有其他东西一样,因此您可以通过执行 kubectl get services 来列出它们,如下一个清单所示。

  1. Listing 3.13 Listing Services
  2. $ kubectl get svc
  3. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  4. kubernetes ClusterIP 10.19.240.1 <none> 443/TCP 34m
  5. kubia LoadBalancer 10.19.243.17 <pending> 8080:30838/TCP 4s

注意使用缩写 svc 而不是 services。大多数资源类型都有一个短名称,您可以使用它来代替完整的对象类型(例如,po 是 pods 的缩写,no 是 nodes 的缩写,deploy 是 deployments 的缩写)。

该列表显示了两个服务及其类型、IP 和它们公开的端口。暂时忽略 kubernetes 服务,仔细看看 kubia 服务。它还没有外部 IP 地址。它是否得到一个外部 IP 地址取决于您如何部署集群。

您已经使用 kubectl get 命令列出了集群中的各种内容,这些都是 Kubernetes 对象类型。您可以通过运行 kubectl api-resources 显示所有支持类型的列表。该列表还显示了每种类型的短名称以及在 JSON/YAML 文件中定义对象所需的一些其他信息,您将在接下来的章节中了解这些信息。

了解负载均衡器服务

虽然 Kubernetes 允许您创建所谓的 LoadBalancer 服务,但它本身并不提供负载均衡器。如果您的集群部署在云中,Kubernetes 可以要求云基础设施提供负载均衡器并将其配置为将流量转发到您的集群。基础设施告诉 Kubernetes 负载均衡器的 IP 地址,这将成为您服务的外部地址。

下图显示了创建服务对象、配置负载均衡器以及它如何将连接转发到集群的过程。
image.png
负载均衡器的配置需要一些时间,所以让我们再等几秒钟,然后再次检查 IP 地址是否已经分配。这一次,您将仅使用名称显示 kubia 服务,而不是列出所有服务,如下面的清单所示。

  1. $ kubectl get svc kubia
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. kubia LoadBalancer 10.19.243.17 35.246.179.22 8080:30838/TCP 82s

现在显示外部 IP。这意味着负载均衡器已准备好将请求转发到您的应用程序以供世界各地的客户端使用。

如果您使用 Docker Desktop 部署集群,负载均衡器的 IP 地址显示为 localhost,指的是您的 Windows 或 macOS 机器,而不是运行 Kubernetes 和应用程序的 VM。如果您使用 Minikube 创建集群,则不会创建负载均衡器,但您可以通过其他方式访问该服务。稍后再谈。

通过负载均衡器访问您的应用程序

  1. $ curl 35.246.179.22:8080
  2. Hey there, this is kubia-9d785b578-p449x. Your IP is ::ffff:1.2.3.4.

到目前为止,你只需要执行以下两个命令:

  • kubectl create deployment
  • kubectl expose deployment

    水平扩容你的应用

    您现在拥有一个正在运行的应用程序,该应用程序由一个 Deployment 表示并通过一个 Service 对象向外部公开。现在让我们创造一些额外的魔法。

在容器中运行应用程序的主要好处之一是您可以轻松扩展应用程序部署。您当前正在运行应用程序的单个实例。想象一下,您突然看到更多用户在使用您的应用程序。单个实例无法再处理负载。您需要运行额外的实例来分配负载并向您的用户提供服务。这称为向外扩展。使用 Kubernetes,这很简单。

增加正在运行的应用程序实例的数量

为了部署您的应用程序,您已经创建了一个 Deployment 对象。默认情况下,它运行您的应用程序的单个实例。要运行其他实例,您只需使用以下命令扩展 Deployment 对象:

  1. $ kubectl scale deployment kubia --replicas=3
  2. deployment.apps/kubia scaled

您现在已经告诉 Kubernetes,您想要运行您的 pod 的三个精确副本。请注意,您还没有指示 Kubernetes 做什么。您还没有告诉它再添加两个 pod。您只需设置新的所需副本数量,并让 Kubernetes 确定它必须采取什么操作才能达到新的所需状态。

这是 Kubernetes 中最基本的原则之一。无需告诉 Kubernetes 做什么,您只需设置系统的新期望状态,然后让 Kubernetes 实现它。为此,它检查当前状态,将其与所需状态进行比较,识别差异并确定必须采取什么措施来协调它们。

查看水平扩展的结果

尽管 kubectl scale 部署命令似乎是必要的,但它显然告诉 Kubernetes 扩展您的应用程序,该命令实际上所做的是修改指定的部署对象。正如您将在后面的章节中看到的那样,您可以简单地编辑对象而不是发出命令式指令。让我们再次查看 Deployment 对象,看看 scale 命令是如何影响它的:

  1. $ kubectl get deploy
  2. NAME READY UP-TO-DATE AVAILABLE AGE
  3. kubia 3/3 3 3 18m

三个实例现已更新并可用,三个容器中的三个已准备就绪。从命令输出中并不清楚这一点,但是这三个容器不是同一个 pod 实例的一部分。共有三个 pod,每个 pod 有一个容器。您可以通过列出 pod 来确认这一点:

  1. $ kubectl get pods
  2. NAME READY STATUS RESTARTS AGE
  3. kubia-9d785b578-58vhc 1/1 Running 0 17s
  4. kubia-9d785b578-jmnj8 1/1 Running 0 17s
  5. kubia-9d785b578-p449x 1/1 Running 0 18m

如您所见,现在存在三个 pod。如 READY 列所示,每个都有一个容器,并且所有容器都准备好了。所有的 pod 都在运行。

列出 pods 时显示 pods 的主机节点

如果您使用单节点集群,则所有 pod 都在同一个节点上运行。但是在多节点集群中,三个 pod 应该分布在整个集群中。要查看 pod 被安排到哪些节点,您可以使用 -o wide 选项显示更详细的 pod 列表:

  1. $ kubectl get pods -o wide
  2. NAME ... IP NODE
  3. kubia-9d785b578-58vhc ... 10.244.1.5 worker1
  4. kubia-9d785b578-jmnj8 ... 10.244.2.4 worker2
  5. kubia-9d785b578-p449x ... 10.244.2.3 worker2

在列出其他对象类型时,您还可以使用 -o 宽输出选项来查看附加信息。

宽输出显示一个 pod 被调度到一个节点,而另外两个都被调度到另一个节点。 Scheduler 通常会平均分配 Pod,但这取决于它的配置方式。您将在第 21 章中了解有关调度的更多信息。

了解 POD 调度到的工作节点的原因并不重要

无论它们在哪个节点上运行,您的应用程序的所有实例都具有相同的操作系统环境,因为它们在从同一容器映像创建的容器中运行。您可能还记得上一章中唯一可能不同的是操作系统内核,但这仅在不同节点使用不同内核版本或加载不同内核模块时才会发生。

此外,每个 pod 都有自己的 IP,并且可以以相同的方式与任何其他 pod 进行通信 - 其他 pod 是否在同一个工作节点上、另一个节点位于同一服务器机架上,甚至是完全不同的数据中心都没关系。

到目前为止,您还没有为 pod 设置任何资源要求,但如果设置了,每个 pod 都会被分配请求数量的计算资源。只要满足 pod 的要求,哪个节点提供这些资源对 pod 无关紧要。

因此,您不应该关心 pod 被安排在哪里。这也是为什么默认的 kubectl get pods 命令不显示列出的 pod 的工作节点信息的原因。在 Kubernetes 的世界中,它并不那么重要。

如您所见,扩展应用程序非常容易。一旦您的应用程序投入生产并且需要对其进行扩展,您可以使用单个命令添加其他实例,而无需手动安装、配置和运行其他副本。

应用程序本身必须支持水平缩放。 Kubernetes 不会神奇地使您的应用程序可扩展。它只是让复制它变得微不足道。

使用 Service 时观察所有三个 pod 的请求

当外部发起多个请求时,每个请求以随机顺序到达不同的 pod。这就是 Kubernetes 中的 Services 在多个 Pod 实例后面时所做的事情。它们充当 Pod 前面的负载均衡器。让我们使用下图可视化系统。
image.png
如图所示,您不应将这种由 Kubernetes 服务本身提供的负载均衡机制与在 GKE 或其他云厂商提供的额外负载均衡器混淆。即使您使用 Minikube 并且没有外部负载均衡器,您的请求仍然由服务本身分布在三个 Pod 中。如果您使用 GKE,实际上有两个负载均衡器在起作用。图中显示了云厂商提供的负载均衡器在节点之间分发请求,Service 在 Pod 之间分发请求。

我知道现在这可能很混乱,但在第 10 章中应该会变得清晰。

了解已部署的应用程序

为了结束本章,让我们回顾一下您的系统是由什么组成的。有两种方法可以查看您的系统——逻辑视图和物理视图。您刚刚在已经看到了物理视图。在三个工作节点上部署了三个正在运行的容器。如果你在云中运Kubernetes,云基础设施也为你创建了一个负载均衡器。

虽然不同集群中的物理视图存在差异,但逻辑视图始终相同,无论您使用的是小型开发集群还是具有数千个节点的大型生产集群。如果您不是管理集群的人,您甚至无需担心集群的物理视图。如果一切都按预期工作,那么您只需要担心逻辑视图。让我们仔细看看这个观点。

了解代表您的应用程序的 API 对象

逻辑视图由您在 Kubernetes API 中直接或间接创建的对象组成。下图显示了对象如何相互关联。
image.png
API 对象如下:

  • 您创建的 Deployment 对象
  • 基于 Deployment 自动创建的 Pod 对象
  • 您手动创建的 Service 对象

刚才提到的三者之间还有其他对象,但你现在不需要知道它们。您将在接下来的章节中了解它们。

还记得我在第 1 章中解释过 Kubernetes 抽象了基础设施吗?应用程序的逻辑视图就是一个很好的例子。没有节点,没有复杂的网络拓扑,没有物理负载平衡器。只是一个简单的视图,仅包含您的应用程序和支持对象。让我们看看这些对象如何组合在一起以及它们在您的小型设置中扮演什么角色。

Deployment 对象表示应用程序部署。它指定哪个容器镜像包含您的应用程序以及 Kubernetes 应该运行多少个应用程序副本。每个副本都由一个 Pod 对象表示。 Service 对象表示这些副本的单个通信入口点。

理解 Pods

您的系统中最基本和最重要的部分是 Pod。每个 pod 定义包含一个或多个组成 pod 的容器。当 Kubernetes 使 pod 生效时,它会运行其定义中指定的所有容器。只要 Pod 对象存在,Kubernetes 就会尽最大努力确保其容器保持运行。只有在删除 Pod 对象时才会关闭它们。

理解 Deployment 的作用

当您第一次创建 Deployment 对象时,只创建了一个 Pod 对象。但是,当您在 Deployment 上增加所需的副本数量时,Kubernetes 会创建额外的副本。 Kubernetes 确保 pod 的实际数量始终与所需数量相匹配。

如果一个或多个 Pod 消失或它们的状态未知,Kubernetes 会以实际的 Pod 数量恢复到所需的副本数量。当操作删除 pod 时,一个 pod将会消失。而当运行 pod 的节点由于网络或节点故障不再报告其状态时,它的状态是未知的。

严格来说,一个 Deployment 的结果只不过是创建了一定数量的 Pod 对象。您可能想知道是否可以直接创建 Pod,而不是让 Deployment 为您创建它们。您当然可以这样做,但如果您想运行多个副本,则必须手动单独创建每个 pod,并确保为每个 pod 指定一个唯一名称。然后,如果它们突然消失或运行它们的节点发生故障,您还必须时刻关注您的 pod 以替换它们。这就是为什么你几乎从不直接创建 Pod 而是使用 Deployment 的原因。

理解为什么需要 Service

系统的第三个组件是 Service 对象。通过创建它,您告诉 Kubernetes 您需要一个到您的 pod 的通信入口点。无论当前部署了多少副本,该服务都会为您提供一个 IP 地址来与您的 pod 通信。如果服务由多个 pod 支持,则它充当负载均衡器。但是即使只有一个 pod,你还是想通过一个服务来暴露它。要了解原因,您需要了解有关 pod 的重要细节。

Pod 是短暂的,它随时可能消失。当它的主机节点发生故障时,当有人无意中删除了 pod 时,或者当 pod 被从一个健康的节点中逐出以便为其他更重要的 pod 腾出空间时,就会发生这种情况。如上一节所述,当通过 Deployment 创建 Pod 时,丢失的 Pod 会立即被新的 Pod 替换。这个新的 Pod 与它取代的 Pod 不同。这是一个全新的 pod,具有新的 IP 地址。

如果您没有使用服务并且已将客户端配置为直接连接到原始 pod 的 IP,那么您现在需要重新配置所有这些客户端以连接到新 pod 的 IP。使用服务时,这不是必需的。与 pod 不同,Service 不是短暂的。创建服务时,会为其分配一个静态 IP 地址,该地址在服务生命周期内不会更改。

客户端应该连接到 Service 的 IP,而不是直接连接到 pod。这确保了它们的连接始终路由到健康的 pod,即使服务背后的 pod 集不断变化。如果您决定水平扩展部署,它还可以确保负载均匀分布在所有 pod 上。