Kubernetes 简称为 K8s
最近想要通过 K8s 部署一些前端应用。但苦于之前没有接触过集群相关的概念,操作时,对 Deployment、Pod、Service 和 Ingress 等概念不是很理解,导致对部署的流程不太清晰(例如,在创建 Deployment 时,不清楚要填哪些选项,那些选项有什么作用和区别, Pod,Sercvice 的又是什么,它们之间有什么联系,什么时候需要创建 Ingress,Ingress 又有什么用等等)。带着这些问题,自己学习了一番,在此对 K8s 中一些常见概念进行记录和总结。
K8s(Kubernetes)是一个开源容器编排工具,用于自动部署、扩展和管理容器化应用。它可以帮助我们管理由成百上千个容器组成的应用程序,也可以帮助们在不同的环境中(例如,物理机、虚拟机、云环境或者混合的部署环境)管理这些应用程序。
K8s 最初由 Google 开发,它在 Google 的大规模容器编排系统(Borg)的基础上演化而来。它现在由 Cloud Native Computing Foundation (CNCF) 管理,并且已经成为了 CNCF 的顶级项目。
容器是一种轻量级的虚拟化技术,可以将应用程序与其依赖项打包在一起,并以可移植、可重复和安全的方式运行。
微服务成本的上升增加了容器技术的使用,因为容器为小型独立应用程序(如微服务)提供了完美的宿主。微服务技术中容器的兴起造成现在的应用程序由数百甚至数千个容器组成,而目前使用脚本和自制工具跨多个环境来管理这些容器负载是非常复杂的,有时甚至是不可能的。因此,这个特定的场景导致了对容器编排技术的需求。
K8s 主要解决的是容器化应用程序的编排和管理问题。使用 K8s,开发人员可以更轻松地在多个主机上运行容器化的应用程序,并通过 K8s 提供的一些功能,比如资源调度、负载平衡、扩展和状态恢复等,来提高应用程序的可用性和可扩展性。
K8s 还可以解决一些更复杂的问题,比如服务发现、持久化存储和访问控制等。它通过提供一组 API 和构建块,比如 Pods、Services 和 Replica Sets,来帮助开发人员解决这些问题。
总之,K8s 的主要作用是解决容器化应用程序的编排和管理问题,从而帮助开发人员更轻松地构建和维护分布式系统。
K8s提供了以下功能:
这些特性可以大大简化容器集群的管理和运维工作,使得开发人员能够更专注于业务开发,而不必担心底层的架构问题。
本文将介绍 K8s 中最基本的一些组件,并通过一个简单的带数据库的JavaScript 应用案例来展示这些组件是如何帮助用户部署应用的,以及每个组件所扮演的角色是什么。
我们先从 K8s 的基础设置开始,也就是一个 Woker Node,在 K8s 的术语中,Node
就是一个简单的服务器,它可以是物理机或虚拟机,而 K8s 的基本组件或最小单位是一个 Pod
, Pod 其实就是对容器的抽象。如果你熟悉 Docker 容器或容器镜像,那么 Pod 实际上就是在容器上创建了运行环境或更高级的层。K8s 想要抽象掉容器运行时或容器技术,因此开发者不必直接使用 Docker 或者其他容器技术,只需与 K8s 层进行交互即可。
假设我们有一个 my-app
Pod,和一个具有自己容器的 DB
Pod。Pod 通常意味着在其内部运行一个应用程序容器。你可以在一个 Pod 中运行多个容器,但通常只有当你有一个主应用程序,并且有一个辅助容器或其他服务在 Pod 中运行时才会这样。你可能会觉得,这并不特别,你仅仅有一个服务和两个运行在它上面的容器,并在其上面添加了抽象层而已。现在让我们看看它们在 K8s 的世界中是如何相互通信的。
K8s 提供了一个内置的虚拟网络,意味着每个 Pod (而不是每个容器)都会拥有自己的 IP 地址。每个 Pod 使用该 IP 地址与其他 Pod 进行通信,这是一个内部 IP 地址。所以 my-app
容器可以使用 IP 地址与 DB
容器进行通信。在 K8s 中 Pod 组件也是一个重要概念,它是短暂的,这意味着它很容易挂掉。当这种情况发生时,例如 DB
因为容器崩溃、应用程序内部崩溃、或者用于运行它的 Node 资源耗尽而挂掉,Pod 就会跟着挂掉。一旦发生这种情况,它的位置上就会创建一个新的 Pod。这样一来,它将获得一个新的 IP 地址,显然,如果我们使用 IP 地址进行通信,每次 Pod 重启时都要进行调整是非常不方便的。
因此,K8s 的另一个组件被称为 Service
,用于解决此问题。
小结:
Service 基本上是一个静态 IP 地址或永久 IP 地址,可以附加到每个 Pod,所以说 my-app
Pod 会有自己的 Service,DB
Pod 也会有自己的 Service。好处是 Service 和 Pod 的生命周期不相关。因此,即使 Pod 挂掉,Service 及其 IP 地址也会保留。所以我们不必再次更改端点( IP 地址和端口号)。
现在,若我们想要通过浏览器访问应用程序,我们必须创建一个外部 Service。外部 Service 是一个打开来自外部来源的通信服务,但显然我们不希望我们的 DB
对公共请求开放,为此我们会创建一个称为内部 Service 的东西,这是我们在创建一个 Service 时所指定的类型。
我们可以看到外部 Service 的 URL 似乎不太实用,它是一个带有 Node IP 地址和 Service 端口号的 HTTP 协议链接。如果我们想快速测试一些东西,但不是最终产品,这确实对于测试目的很友好。
但是,如果我们想用安全协议和域名与应用程序通信,我们会希望 URL 看起来像下面这样。
为此,K8s 有另一个组件叫做 Ingress
。当请求过来时,会先到 Ingress,然后再转发给 Service。
小结:
如前所述,Pod 之间使用 Service 进行通信。假如 my-app
有一个数据库端点,我们称之为 mongo-db-service
,它用于与 DB
通信。我们应该在哪里配置这个数据库的 URL 或端点呢,通常我们会在应用程序属性文件中,或将其作为某种外部环境变量,但它还是存在于应用程序构建后的镜像中。
假如 Service 的端点或 Service 的名字在这种情况下改变为 mongo-db
,我们将不得不在应用程序中重新调整 URL,用一个新版本重新构建应用程序,并且把它推送到镜像仓库,然后在 Pod 中拉取这个新的镜像,并重新启动整个程序。
这对于像数据库 URL 这样的小改变来说有点过于麻烦了。为此 K8s 有一个叫做 configMap
的组件,它的基本作用是将外部配置应用于应用程序。configMap 通常包含配置数据,比如数据库的 URL 或我们使用的一些其他 Service。在 K8s 中,我们只需要将它连接到 Pod,这样 Pod 就可以获得 configMap 包含的数据。现在,如果我们改变了 Service 的名字,或者说 Service 的端点,我们只需要调整 configMap。这样一来,我们就不必构建新的镜像并经历整个周期了。
再一想,外部配置的 Pod 也可以是数据库用户名和密码,对吧?在应用部署过程中,这些密码或其他凭据可能也会发生变化,但是在配置中以纯文本格式放置密码或其他凭据是不安全的,即使它是外部配置。为此 K8s 还有另一个组件称为 Secret
。
Secret 和 configMap 类似,但不同点是它用于存储密钥类数据凭据。例如,它不是以纯文本格式存储的,而是以 base64 编码格式存储。因此,Secret 将包含诸如凭据之类的东西。当然,我们也可以将数据库的用户(user)放在 configMap 中,但像密码之类的,我们不希望其他人访问的东西应该放在 Secret 中。就像 configMap一样,我们只需将它连接到 Pod,Pod 就可以看到这些数据,并从 Secret 中读取。
现在,我们已经了解了 K8s 中 几乎所有最常用的基本组件。我们了解了 Pod,学习了 Service 的用法,Ingress 组件的用处,还了解了使用 configMap 和 Secret 的外部配置。
小结:
现在让我们来看另一个非常重要的概念,数据存储,以及它在 K8s 中是如何工作的。我们的应用程序所使用的 DB
Pod,它有一些数据或生成某些数据。已目前的情况,如果 DB
容器或 Pod 重新启动,那么数据将会丢失,这显然是有问题的,并且非常不方便。
我们当然希望数据库数据或日志数据能够长期可靠地保存,为此,我们可以在 K8s 中使用另一个组件来实现这一点,即 Volumes
。
它的工作原理基本上是将物理存储附加到 Pod 上,该存储可能在本地机器上,即 Pod 正在运行的同一服务器 Node 上,也可能在远程存储上,即在 K8s 集群之外。它可以是云存储,也可以是我们自己的本地存储,不属于 K8s 集群。因此,我们只需要在它上面有一个外部参考。
现在,当DB
Pod 或容器重新启动时,所有数据都将保存在那里。重要的是要理解 K8s 集群及其所有组件和存储之间的区别,无论它是本地存储还是远程存储,都可以将存储看作是插入 K8s 集群的外部硬盘,因为重点是 K8s 集群明确不管理任何数据持久性。这意味着我们作为 K8s 的用户或管理员将负责备份、复制和管理数据,并确保数据保存在适当的硬件上等等,因为 K8s 并不关心这些数据。
小结
现在我们的应用程序正常的运行,用户能够通过浏览器访问我们部署的应用。
让我们考虑一个问题,如果 my-app
Pod 崩溃或者我们需要重新启动 Pod,这种情况下会发生什么?答案是,基本上在一段时间内用户都不能访问我们的应用程序。
如果这发生在生产环境中,将是一件非常糟糕的事情。这正是分布式系统和容器的优势所在,我们不仅要复制my-app
Pod 和 DB
Pod 等,还要在多台服务器上复制所有内容,这样我们就可以在另一个 Node 上运行应用程序的副本,并将其连接到 Service。请记住 Service 就像一个带有 DNS 名称的持久静态 IP 地址,当 Pod 崩溃时,我们不用不断地调整端点。 Service 也是一个负载均衡器,这意味着 Service 实际上会捕获请求并转发到负载较轻的 Pod,它具有这两种功能。
简单来说,为了在 K8s 中创建 my-app
Pod 的第二个副本,我们不用再创建一个 Pod,而是会定义一个 my-app
Pod 的蓝图,并指定想要运行多少个该 Pod 的副本。这个组件或者蓝图被称为 Deployment
,它是 K8s 的另一个组件。
实际上,我们不会直接操作 Pod,而是会创建 Deployment,因为在 Deployment 中我们可以指定副本数量,也可以扩展或缩减需要的 Pod 的副本数量。我们之前说过 Pod 是对容器的一层抽象,Deployment 又是对 Pod 的一层抽象,它使得与 Pod 交互、复制以及进行其他配置变得更加方便。
在实践中,我们主要会操作 Deployment 而不是 Pod。如果 my-app
Pod 的一个副本崩溃了,Service 将会把请求转发到另一个副本上,因此, my-app
仍然对用户可用。
到这里,你可能会问,数据库 Pod 怎么办?如果 DB
Pod 崩溃了,我们的应用也不能使用, 因此我们也需要一个 DB
副本。然而,我们不能使用 Deployment 来复制 DB
,原因是数据库是有状态的。也就是说,如果我们有 DB
的多个副本,它们都需要访问同一个共享的数据存储,并且需要一种机制来管理哪些 Pod 目前正在写入该存储,或者哪些 Pod 正在从该存储中读取,以避免数据不一致的问题。
这种机制由 K8s 中的另一个组件 StatefulSet
提供。
该组件专门用于数据库类型的应用,MySQL、mongodb、elasticsearch 或其他任何具有状态的应用或数据库,都应该使用 StatefulSet 而不是 Deployment 创建,这是一个非常重要的区别。
StatefulSet 和 Deployment 一样,可以确保复制 Pod,并对它们进行扩展或缩减,但同时也能确保数据库的读写是同步的,从而避免数据库不一致的问题。但是,需要注意的是,在 K8s 集群中使用 StatefulSet 部署数据库应用会比较繁琐。这比使用 Deployment 更难,因为 Deployment 中没有所有这些挑战。这就是为什么在 K8s 集群外部托管数据库应用也是一种常见的做法,在 K8s 集群中只有 Deployment 或无状态应用,它们可以完美地复制和扩展,并与外部数据库进行通信。
现在,我们有两个 my-app
容器和两个 DB
副本,它们都有负载均衡。这意味着即使 Node1 整个服务器重启或崩溃, Node2 上的应用程序和数据库容器也会运行。应用程序仍然可以被用户访问,直到这两个副本都被重新创建。因此,我们可以避免故障停机时间(downtime)。
小结:
总而言之,我们已经了解最常用的 K8s 组件。我们从 Pod 开始, Service 用于 Pod 之间进行通信。然后是 Ingress 组件,它用于将流量路由到集群中。我们还使用 configMap 和 Secret 进行了外部配置,并使用了 Volumn 来持久化数据。最后,我们了解了具有复制机制的 Pod 蓝图,如 Deployment 和 StatefulSet。其中 StatefulSet 专用于像数据库这样的有状态应用程序。
虽然K8s 提供了更多组件,但上述这些都是真正的核心基本组件,只使用这些核心组件,实际上我们就能构建非常强大的 K8s 集群。