2.3 在Kubernetes上运行第一个应用
![](https://book.img.zhangyue01.com/group61/M00/77/93/CmQUOF8KrwOEGX6fAAAAAB76RfE908976629.png?v=ywDc_OF-&t=CmQUOF8KrwM.)
因为这可能是第一次,所以会使用最简单的方法在Kubernetes上运行应用程序。通常,需要准备一个JSON或YAML,包含想要部署的所有组件描述的配置文件,但是因为还没有介绍可以在Kubernetes中创建的组件类型,所以这里将使用一个简单的单行命令来运行应用。
2.3.1 部署Node.js应用
部署应用程序最简单的方式是使用 kubectl run 命令,该命令可以创建所有必要的组件而无需JSON或YAML文件。这样的话,我们就不需要深入了解每个组件对象的结构。试着运行之前创建、推送到Docker Hub的镜像。下面是在Kubernetes中运行的代码:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EU7xBAAAAAEoFaNs302279014.jpg?v=Gtm50uC-&t=CmQUOF8Kru8.)
--image=luksa/kubia 显示的是指定要运行的容器镜像,--port=8080 选项告诉Kubernetes应用正在监听8080端口。最后一个标志(--generator)需要解释一下,通常并不会使用到它,它让Kubernetes创建一个ReplicationController,而不是Deployment。稍后你将在本章中了解到什么是ReplicationController,但是直到第9章才会介绍Deployment,所以不会在这里创建Deployment。
正如前面命令的输出所示,已经创建了一个名为kubia的ReplicationController。如前所述,我们将在本章的后面看到。从底层开始,把注意力放在创建的容器上(可以假设已经创建了一个容器,因为在 run 命令中指定了一个容器镜像)。
介绍pod
你或许在想,是否有一个列表显示所有正在运行的容器,可以通过类似于kuberctl get containers的命令获取。这并不是Kubernetes的工作,它不直接处理单个容器。相反,它使用多个共存容器的理念。这组容器就叫作pod。
一个pod是一组紧密相关的容器,它们总是一起运行在同一个工作节点上,以及同一个Linux命名空间中。每个pod就像一个独立的逻辑机器,拥有自己的IP、主机名、进程等,运行一个独立的应用程序。应用程序可以是单个进程,运行在单个容器中,也可以是一个主应用进程或者其他支持进程,每个进程都在自己的容器中运行。一个pod的所有容器都运行在同一个逻辑机器上,而其他pod中的容器,即使运行在同一个工作节点上,也会出现在不同的节点上。
为了更好地理解容器、pod和节点之间的关系,请查看图 2.5。如你所见,每个pod都有自己的IP,并包含一个或多个容器,每个容器都运行一个应用进程。pod分布在不同的工作节点上。
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EJ00KAAAAAFhzrvs148638291.jpg?v=LLrlkfue&t=CmQUOV8Kru8.)
图2.5 容器、pod及物理工作节点之间的关系
列出pod
不能列出单个容器,因为它们不是独立的Kubernetes对象,但是可以列出pod。让我们看看如何使用 kubectl 在下面的代码清单中列出pod。
代码清单2.14 列出pod
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-ER3hmAAAAAHopU-o018979034.jpg?v=-QZAWFHz&t=CmQUOF8Kru8.)
pod仍然处于挂起状态,pod的单个容器显示为还未就绪的状态(这是 READY列中的 0/1的含义)。pod还没有运行的原因是:该pod被分配到的工作节点正在下载容器镜像,完成之后才可以运行。下载完成后,将创建pod的容器,然后pod会变为运行状态,如下面的代码清单所示。
代码清单2.15 再次列出pod查看pod的状态是否变化
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EZHFfAAAAAKgyb7k348288022.jpg?v=A-Nowv10&t=CmQUOV8Kru8.)
要查看有关pod的更多信息,还可以使用 kubectl describe pod 命令,就像之前查看工作节点一样。如果pod停留在挂起状态,那么可能是Kubernetes无法从镜像中心拉取镜像。如果你正在使用自己的镜像,确保它在Docker Hub上是公开的。为了确保能够成功地拉取镜像,可以试着在另一台机器上使用 docker pull命令手动拉取镜像。
幕后发生的事情
为了可视化所发生的事情,请看图2.6。它显示了在Kubernetes中运行容器镜像所必需的两个步骤。首先,构建镜像并将其推送到Docker Hub。这是必要的,因为在本地机器上构建的镜像只能在本地机器上可用,但是需要使它可以访问运行在工作节点上的Docker守护进程。
当运行 kubectl 命令时,它通过向Kubernetes API服务器发送一个REST HTTP请求,在集群中创建一个新的ReplicationController对象。然后,ReplicationController创建了一个新的pod,调度器将其调度到一个工作节点上。Kubelet看到pod被调度到节点上,就告知Docker从镜像中心中拉取指定的镜像,因为本地没有该镜像。下载镜像后,Docker创建并运行容器。
展示另外两个节点是为了显示上下文。它们没有在这个过程中扮演任何角色,因为pod没有调度到它们上面。
定义 术语调度(scheduling)的意思是将pod分配给一个节点。pod会立即运行,而不是将要运行。
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EFWhfAAAAAPe68tE076866881.jpg?v=SpLnSnUB&t=CmQUOV8Kru8.)
图2.6 在Kubernetes中运行luksa/kubia容器镜像
2.3.2 访问Web应用
如何访问正在运行的pod?我们提到过每个pod都有自己的IP地址,但是这个地址是集群内部的,不能从集群外部访问。要让pod能够从外部访问,需要通过服务对象公开它,要创建一个特殊的 LoadBalancer 类型的服务。因为如果你创建一个常规服务(一个 ClusterIP 服务),比如pod,它也只能从集群内部访问。通过创建 LoadBalancer 类型的服务,将创建一个外部的负载均衡,可以通过负载均衡的公共IP访问pod。
创建一个服务对象
要创建服务,需要告知Kubernetes对外暴露之前创建的ReplicationController:
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-ESNM3AAAAAMpXelo564770504.jpg?v=uBOKINqs&t=CmQUOV8Kru8.)
注意 我们这里用的是 replicationcontroller 的缩写 rc。大多数资源类型都有这样的缩写,所以不必输入全名(例如,pods 的缩写是 po,service 的缩写是 svc,等等)。
列出服务
expose 命令的输出中提到一个名为kubian-http 的服务。服务是类似于pod和Node的对象,因此可以通过运行 kubectl get services 命令查看新创建的服务对象,如下面的代码清单所示。
代码清单2.16 列出服务
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EOvu5AAAAAChxuhQ277281212.jpg?v=vPSWa4fl&t=CmQUOF8Kru8.)
该列表显示了两个服务。暂时忽略 kubernetes 服务,仔细查看创建的kubian-http 服务。它还没有外部IP地址,因为Kubernetes运行的云基础设施创建负载均衡需要一段时间。负载均衡启动后,应该会显示服务的外部IP地址。让我们等待一段时间并再次列出服务,如下面的代码清单所示。
代码清单2.17 再次列出服务并查看是否分配了外部IP
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EcC-DAAAAAN5pXuo245502900.jpg?v=OlofcSbH&t=CmQUOV8Kru8.)
现在有外部IP了,应用就可以从任何地方通过http://104.155.74.57:8080 访问。
注意 Minikube不支持 LoadBalancer 类型的服务,因此服务不会有外部IP。但是可以通过外部端口访问服务。在下一节的提示中将介绍这是如何做到的。
使用外部IP访问服务
现在可以通过服务的外部IP和端口向pod发送请求:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EMFz8AAAAAHNa3z0531950461.jpg?v=6jo1NbRA&t=CmQUOF8Kru8.)
现在,应用程序在三个节点的Kubernetes集群(如果使用Minikube,则是一个单节点集群)上运行起来了。如果你忘了建立整个集群所需的步骤,那么只需两个简单的命令就可以让你的应用运行起来,并且让全世界的用户都能访问它。
提示 使用Minikube的时候,可以运行 minikube service kubia-http获取可以访问服务的IP和端口。
如果仔细观察,会发现应用将pod名称作为它的主机名。如前所述,每个pod都像一个独立的机器,具有自己的IP地址和主机名。尽管应用程序运行在工作节点的操作系统中,但对应用程序来说,它似乎是在一个独立的机器上运行,而这台机器本身就是应用程序的专用机器,没有其他的进程一同运行。
2.3.3 系统的逻辑部分
到目前为止,主要介绍了系统实际的物理组件。三个工作节点是运行Docker和Kubelet的VM,还有一个控制整个系统的主节点。实际上,我们并不知道主节点是否管理着Kubernetes控制层的所有组件,或者它们是否跨多个节点。这并不重要,因为你只与单点访问的API服务器进行交互。
除了这个系统的物理视图,还有一个单独的、逻辑的视图。之前已经提到过pod、ReplicationController和服务。所有这些都将在后面几章中介绍,但是让我们先快速地看看它们是如何组合在一起的,以及它们在应用中扮演什么角色。
ReplicationController、pod和服务是如何组合在一起的
正如前面解释过的,没有直接创建和使用容器。相反,Kubernetes的基本构件是pod。但是,你并没有真的创建出任何pod,至少不是直接创建。通过运行kubectl run 命令,创建了一个ReplicationController,它用于创建pod实例。为了使该pod能够从集群外部访问,需要让Kubernetes将该ReplicationController管理的所有pod由一个服务对外暴露。图2.7给出了这三种元素组合的大致情况。
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EHeOdAAAAAB7D2cs374449906.jpg?v=u5iB6uRS&t=CmQUOV8Kru8.)
图2.7 由ReplicationController、pod和服务组成的系统
pod和它的容器
在你的系统中最重要的组件是pod。它只包含一个容器,但是通常一个pod可以包含任意数量的容器。容器内部是Node.js进程,该进程绑定到8080端口,等待HTTP请求。pod有自己独立的私有IP地址和主机名。
ReplicationController的角色
下一个组件是 kubia ReplicationController。它确保始终存在一个运行中的pod实例。通常,ReplicationController用于复制pod(即创建pod的多个副本)并让它们保持运行。示例中没有指定需要多少pod副本,所以ReplicationController创建了一个副本。如果你的pod因为任何原因消失了,那么ReplicationController将创建一个新的pod来替换消失的pod。
为什么需要服务
系统的第三个组件是 kubian-http 服务。要理解为什么需要服务,需要学习有关pod的关键细节。pod的存在是短暂的,一个pod可能会在任何时候消失,或许因为它所在节点发生故障,或许因为有人删除了pod,或者因为pod被从一个健康的节点剔除了。当其中任何一种情况发生时,如前所述,消失的pod将被ReplicationController替换为新的pod。新的pod与替换它的pod具有不同的IP地址。这就是需要服务的地方——解决不断变化的pod IP地址的问题,以及在一个固定的IP和端口对上对外暴露多个pod。
当一个服务被创建时,它会得到一个静态的IP,在服务的生命周期中这个IP不会发生改变。客户端应该通过固定IP地址连接到服务,而不是直接连接pod。服务会确保其中一个pod接收连接,而不关心pod当前运行在哪里(以及它的IP地址是什么)。
服务表示一组或多组提供相同服务的pod的静态地址。到达服务IP和端口的请求将被转发到属于该服务的一个容器的IP和端口。
2.3.4 水平伸缩应用
现在有了一个正在运行的应用,由ReplicationController监控并保持运行,并通过服务暴露访问。现在让我们来创造更多魔法。
使用Kubernetes的一个主要好处是可以简单地扩展部署。让我们看看扩容pod有多容易。接下来要把运行实例的数量增加到三个。
pod由一个ReplicationController管理。让我们来查看 kubectl get 命令:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EXbTAAAAAACrsN0k152576794.jpg?v=Yo4RV4dd&t=CmQUOF8Kru8.)
使用kubectl get列出所有类型的资源
一直在使用相同的基本命令 kubectl get 来列出集群中的资源。你已经使用此命令列出节点、pod、服务和ReplicationController对象。不指定资源类型调用kubectl get可以列出所有可能类型的对象。然后这些类型可以使用各种 kubectl 命令,例如 get、describe 等。列表还显示了前面提到的缩写。
该列表显示了一个名为kubia 的单个ReplicationController。DESIRED 列显示了希望ReplicationController保持的pod副本数,而 CURRENT 列显示当前运行的pod数。在示例中,希望pod副本为1,而现在就有一个副本正在运行。
增加期望的副本数
为了增加pod的副本数,需要改变ReplicationController期望的副本数,如下所示:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EQ7GlAAAAALZcK1s553807751.jpg?v=I3iBGxst&t=CmQUOF8Kru8.)
现在已经告诉Kubernetes需要确保pod始终有三个实例在运行。注意,你没有告诉Kubernetes需要采取什么行动,也没有告诉Kubernetes增加两个pod,只设置新的期望的实例数量并让Kubernetes决定需要采取哪些操作来实现期望的状态。
这是Kubernetes最基本的原则之一。不是告诉Kubernetes应该执行什么操作,而是声明性地改变系统的期望状态,并让Kubernetes检查当前的状态是否与期望的状态一致。在整个Kubernetes世界中都是这样的。
查看扩容的结果
前面增加了pod的副本数。再次列出ReplicationController查看更新后的副本数:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EFawbAAAAAC3aQjw992028933.jpg?v=fmFyMcHN&t=CmQUOF8Kru8.)
由于pod的实际数量已经增加到三个(从 CURRENT 列中可以看出),列出所有的pod时显示的应该是三个而不是一个:
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EFFVXAAAAAA2zDYc424957281.jpg?v=u7Eb1s3l&t=CmQUOV8Kru8.)
正如你所看到的,有三个pod而不是一个。两个已经在运行,一个仍在挂起中,一旦容器镜像下载完毕并启动容器,挂起的pod会马上运行。
正如你所看到的,给应用扩容是非常简单的。一旦应用在生产中运行并且需要扩容,可以使用一个命令添加额外的实例,而不必手动安装和运行其他副本。
记住,应用本身需要支持水平伸缩。Kubernetes并不会让你的应用变得可扩展,它只是让应用的扩容或缩容变得简单。
当切换到服务时请求切换到所有三个pod上
因为现在应用的多个实例在运行,让我们看看如果再次请求服务的URL会发生什么。会不会总是切换到应用的同一个实例呢?
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EfDfXAAAAAAi_MPA860312577.jpg?v=jaFvVPHH&t=CmQUOF8Kru8.)
请求随机地切换到不同的pod。当pod有多个实例时Kubernetes服务就会这样做。服务作为负载均衡挡在多个pod前面。当只有一个pod时,服务为单个pod提供一个静态地址。无论服务后面是单个pod还是一组pod,这些pod在集群内创建、消失,这意味着它们的IP地址会发生变化,但服务的地址总是相同的。这使得无论有多少pod,以及它们的地址如何变化,客户端都可以很容易地连接到pod。
可视化系统的新状态
让我们可视化一下现在的系统,看看和以前相比发生了什么变化。图2.8显示了系统的新状态。仍然有一个服务和一个ReplicationController,但是现在有三个pod实例,它们都是由ReplicationController管理的。服务不再将所有请求发送到单个pod,而是将它们分散到所有三个pod中,如前面使用 curl 进行的实验所示。
作为练习,现在可以尝试通过进一步增加ReplicationController的副本数来启动附加实例,甚至可以尝试减小副本数。
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EbXwWAAAAAEeUAHw922992630.jpg?v=B1KqBpS9&t=CmQUOV8Kru8.)
图2.8 由同一ReplicationController管理并通过服务IP和端口暴露的pod的三个实例
2.3.5 查看应用运行在哪个节点上
你可能想知道pod被调度到哪个节点上。在Kubernetes的世界中,pod运行在哪个节点上并不重要,只要它被调度到一个可以提供pod正常运行所需的CPU和内存的节点就可以了。
不管调度到哪个节点,容器中运行的所有应用都具有相同类型的操作系统。每个pod都有自己的IP,并且可以与任何其他pod通信,不论其他pod是运行在同一个节点上,还是运行在另一个节点上。每个pod都被分配到所需的计算资源,因此这些资源是由一个节点提供还是由另一个节点提供,并没有任何区别。
列出pod时显示pod IP和pod的节点
如果仔细观察,可能已经注意到 kubectl get pods 命令甚至没有显示任何关于这些pod调度到的节点的信息。这是因为它通常不是pod最重要的信息。
但是可以使用-o wide 选项请求显示其他列。在列出pod时,该选项显示pod的IP和所运行的节点:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-EQvYZAAAAAOfPXdY633728417.jpg?v=zwae2eRh&t=CmQUOF8Kru8.)
使用kubectl describe查看pod的其他细节
还可以使用 kubectl describe 命令来查看节点,该命令显示了pod的许多其他细节,如下面的代码清单所示。
代码清单2.18 使用kubectl describe描述一个pod
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-Ectf2AAAAAKANCk0174711063.jpg?v=-B_rSHLX&t=CmQUOV8Kru8.)
这展示pod的一些其他信息,pod调度到的节点、启动的时间、pod使用的镜像,以及其他有用的信息。
2.3.6 介绍Kubernetes dashboard
在结束这个初始实践的章节之前,让我们看看探索Kubernetes集群的另一种方式。
到目前为止,只使用了 kubectl 命令行工具。如果更喜欢图形化的web用户界面,你会很高兴地听到Kubernetes也提供了一个不错的(但仍在开发迭代的)web dashboard。
dashboard可以列出部署在集群中的所有pod、ReplicationController、服务和其他部署在集群中的对象,以及创建、修改和删除它们,如图2.9所示。
尽管你不会在本书中使用dashboard,在 kubectl 创建或修改对象之后,还是可以随时打开它,快速查看集群中部署内容的图形化视图。
访问GKE集群的dashboard
如果你正在使用Google Kubernetes Engine,可以通过 kubectl clusterinfo 命令找到dashboard的URL:
![](https://book.img.zhangyue01.com/group61/M00/77/AE/CmQUOV8Kru-EEQY8AAAAAL83JRo277351553.jpg?v=9iO-vAOP&t=CmQUOV8Kru8.)
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-Ef1SGAAAAAJ0Hxc0513933029.jpg?v=NS8rkPY8&t=CmQUOF8Kru8.)
图2.9 Kubernetes dashboard的页面截图
如果在浏览器中打开这个URL,将会显示用户名和密码提示符。可以运行以下命令找到用户名和密码:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-ESV2PAAAAAEps0po208094767.jpg?v=hXDP8TNN&t=CmQUOF8Kru8.)
访问Minikube的dashboard
要打开使用Minikube的Kubernetes集群的dashboard,请运行以下命令:
![](https://book.img.zhangyue01.com/group61/M00/77/8F/CmQUOF8Kru-ESr3sAAAAAMcGHi8971744705.jpg?v=LmKjkukj&t=CmQUOF8KrvA.)
dashboard将在默认浏览器中打开。与GKE不同的是,不需要输入任何凭证来访问它。