Kubernetes In Action :2、开始使用Kubernetes和Docker

发布于:2022-12-19 ⋅ 阅读:(451) ⋅ 点赞:(0)

2、开始使用Kubernetes和Docker

说明

本章内容涵盖

  1. 使用Docker创建、运行及共享容器镜像
  2. 在本地部署单节点的Kubernetes集群
  3. 在Google Kubernetes Engine上部署Kubernetes集群
  4. 配置和使用命令行客户端 —— kubectl
  5. 在Kubernetes上部署应用并进行水平伸缩

在深入学习Kubernetes的概念之前,先来看看如何创建一个简单的应用,把它打包成容器镜像并在远端的Kubernetes集群(如托管在Google Kubernetes Engine中)或本地单节点集群中运行。这会对整个Kubernetes体系有较好的了解,并且会让接下来几个章节对Kubernetes基本概念的学习变得简单。

2.1 创建、运行及共享容器镜像

正如在之前章节所介绍的,在Kubernetes中运行应用需要打包好的容器镜像。本节将会对Docker的使用做简单的介绍。接下来的几节中将会介绍:

  1. 安装Docker并运行第一个 “Hello world” 容器
  2. 创建一个简单的Node.js应用并部署在Kubernetes中
  3. 把应用打包成可以独立运行的容器镜像
  4. 基于镜像运行容器
  5. 把镜像推送到Docker Hub,这样任何人在任何地方都可以使用

2.1.1 安装Docker并运行Hello World容器

首先,需要在Linux主机上安装Docker。如果使用的不是Linux操作系统,就需要启动Linux虚拟机(VM)并在虚拟机中运行Docker。如果使用的是Mac或Windows系统,Docker将会自己启动一个虚拟机并在虚拟机中运行Docker守护进程。Docker客户端可执行文件可以在宿主操作系统中使用,并可以与虚拟机中的守护进程通信。

根据操作系统的不同,按照http://docs.docker.com/engine/installation/上的指南安装Docker。安装完成后,可以通过运行Docker客户端可执行文件来执行各种Docker命令。例如,可以试着从Docker Hub的公共镜像仓库拉取、运行镜像,Docker hub中有许多随时可用的常见镜像,其中就包括 busybox,可以用来运行简单的 echo "Hello world" 命令。

运行Hello World容器

busybox是一个单一可执行文件,包含多种标准UNIX命令行工具,如:echo、ls、gzip 等。除了包含 echo 命令的 busybox 命令,也可以使用如Fedora、Ubuntu等功能完备的镜像。

如何才能运行 busybox 镜像呢?无须下载或者安装任何东西。使用 docker run 命令然后指定需要运行的镜像的名字,以及需要执行的命令(可选),如下面这段代码。

代码清单2.1 使用Docker运行一个Hello world容器

$ docker run busybox echo "Hello world"

Unable to find image 'busybox:latest' locally

latest: Pulling from docker.io/busybox

9al63e0b8d13: Pull complete

fef924a0204a: Pull complete

Digest: sha256:97473e34e31le6c1b3f61f2a721d038dle5eef17d98d1353a513007cf46ca6bd

Status: Downloaded newer image for docker.io/busybox:latest

Hello world

这或许看起来并不那么令人印象深刻,但非常棒的是仅仅使用一个简单的命令就下载、运行一个完整的“应用”,而不用安装应用或是做其他的事情。目前的应用是单一可执行文件(busybox),但也可以是一个有许多依赖的复杂应用。整个配置运行应用的过程是完全一致的。同样重要的是应用是在容器内部被执行的,完全独立于其他所有主机上运行的进程。

背后的原理

图 2.1 展示了执行 docker run 命令之后发生的事情。首先,Docker会检查busybox:latest 镜像是否已经存在于本机。如果没有,Docker会从http://docker.io的Docker镜像中心拉取镜像。镜像下载到本机之后,Docker基于这个镜像创建一个容器并在容器中运行命令。echo 命令打印文字到标准输出流,然后进程终止,容器停止运行。

图2.1 在一个基于busybox镜像的容器中运行echo "Hello world" 

 

运行其他镜像

运行其他的容器镜像和运行busybox镜像是一样的,甚至可能更简单,因为你可以不需要指定执行命令。就像例子中的 echo "Hello world",被执行的命令通常都会被包含在镜像中,但也可以根据需要进行覆盖。在浏览器中搜索http://hub.docker.com或其他公开的镜像中心的可用镜像之后,可以像这样在Docker中运行镜像:

$ docker run <image>

容器镜像的版本管理

当然,所有的软件包都会更新,所以通常每个包都不止一个版本。Docker支持同一镜像的多个版本。每一个版本必须有唯一的tag名。当引用镜像没有显式地指定tag时,Docker会默认指定tag为latest。如果想要运行别的版本的镜像,需要像这样指定镜像的版本:

$ docker run <image>:<tag>

2.1.2 创建一个简单的 Node.js 应用

现在有了一个可以工作的Docker环境来创建应用。接下来会构建一个简单的Node.js Web应用,并把它打包到容器镜像中。这个应用会接收HTTP请求并响应应用运行的主机名。这样,应用运行在容器中,看到的是自己的主机名而不是宿主机名,即使它也像其他进程一样运行在宿主机上。这在后面会非常有用,当应用部署在Kubernetes上并进行伸缩时(水平伸缩,复制应用到多个节点),你会发现HTTP请求切换到了应用的不同实例上。

应用包含一个名为app.js的文件,详见下面的代码清单。

代码清单2.2 一个简单的Node.js应用:app.js

const http = require('http');const os = require('os');

console.log("Kubia server starting...");

var handler = function(request, response) {

  console.log("Received request from " + request.connection.remoteAddress);

  response.writeHead(200);

  response.end("You've hit " + os.hostname() + "\n");
};

var www = http.createServer(handler);
www.listen(8080);

代码清晰地说明了实现的功能。这里在8080端口启动了一个HTTP服务器。服务器会以状态码 200 OK 和文字 "You've hit <hostname>" 来响应每个请求。请求handler会把客户端的IP打印到标准输出,以便日后查看。注意 返回的主机名是服务器真实的主机名,不是客户端发出的HTTP请求中头的 Host 字段。

现在可以直接下载安装Node.js来测试代码了,但是这不是必需的,因为可以直接用Docker把应用打包成镜像,这样在需要运行的主机上就无须下载和安装其他的东西(当然不包括安装Docker来运行镜像)。

2.1.3 为镜像创建Dockerfile

为了把应用打包成镜像,首先需要创建一个叫Dockerfile的文件,它包含了一系列构建镜像时会执行的指令。Dockerfile文件需要和app.js文件在同一目录,并包含下面代码清单中的命令。

代码清单2.3 构建应用容器镜像的Dockerfile

FROM node:7

ADD app.js /app.js

ENTRYPOINT ["node", "app.js"]

From 行定义了镜像的起始内容(构建所基于的基础镜像)。这个例子中使用的是 node 镜像的tag 7 版本。第二行中把app.js文件从本地文件夹添加到镜像的根目录,保持app.js这个文件名。最后一行定义了当镜像被运行时需要被执行的命令,这个例子中,命令是 node app.js。

选择基础镜像

你或许在想,为什么要选择这个镜像作为基础镜像。因为这个应用是Node.js应用,镜像需要包含可执行的 node 二进制文件来运行应用。你也可以使用任何包含这个二进制文件的镜像,或者甚至可以使用Linux发行版的基础镜像,如 fedora或ubuntu,然后在镜像构建的时候安装Node.js。但是由于 node镜像是专门用来运行Node.js应用的,并且包含了运行应用所需的一切,所以把它当作基础镜像。

2.1.4 构建容器镜像

现在有了Dockerfile和app.js文件,这是用来构建镜像的所有文件。运行下面的Docker命令来构建镜像:

$ docker build -t kubia 

图2.2展示了镜像构建的过程。用户告诉Docker需要基于当前目录(注意命令结尾的点)构建一个叫kubia的镜像,Docker会在目录中寻找Dockerfile,然后基于其中的指令构建镜像。

图2.2 基于Dockerfile构建一个新的容器镜像 

镜像是如何构建的

构建过程不是由Docker客户端进行的,而是将整个目录的文件上传到Docker守护进程并在那里进行的。Docker客户端和守护进程不要求在同一台机器上。如果你在一台非Linux操作系统中使用Docker,客户端就运行在你的宿主操作系统上,但是守护进程运行在一个虚拟机内。由于构建目录中的文件都被上传到了守护进程中,如果包含了大量的大文件而且守护进程不在本地运行,上传过程会花费更多的时间。

提示 不要在构建目录中包含任何不需要的文件,这样会减慢构建的速度——尤其当Docker守护进程运行在一个远端机器的时候。

在构建过程中,Docker首次会从公开的镜像仓库(Docker Hub)拉取基础镜像(node:7),除非已经拉取过镜像并存储在本机上了。

镜像分层

镜像不是一个大的二进制块,而是由多层组成的,在运行busybox例子时你可能已经注意到(每一层有一行Pull complete),不同镜像可能会共享分层,这会让存储和传输变得更加高效。比如,如果创建了多个基于相同基础镜像(比如例子中的 node:7)的镜像,所有组成基础镜像的分层只会被存储一次。拉取镜像的时候,Docker会独立下载每一层。一些分层可能已经存储在机器上了,所以Docker只会下载未被存储的分层。

你或许会认为每个Dockerfile只创建一个新层,但是并不是这样的。构建镜像时,Dockerfile中每一条单独的指令都会创建一个新层。镜像构建的过程中,拉取基础镜像所有分层之后,Docker在它们上面创建一个新层并且添加app.js。然后会创建另一层来指定镜像被运行时所执行的命令。最后一层会被标记为kubia:latest。图2.3 展示了这个过程,同时也展示另外一个叫 other:latest 的镜像如何与我们构建的镜像共享同一层Node.js镜像。

图2.3 容器镜像是由多层组成的,每一层可以被不同镜像复用 

构建完成时,新的镜像会存储在本地。下面的代码展示了如何通过Docker列出本地存储的镜像:

代码清单2.4 列出本地存储的镜像

s docker images

REPOSITORY  TAG         IMAGE ID            CREATED         VIRTUAL SIZE

kubia       latest      d30ecc7419e7        1 minute ago    637.1 MB

比较使用Dockerfile和手动构建镜像

Dockerfile是使用Docker构建容器镜像的常用方式,但也可以通过运行已有镜像容器来手动构建镜像,在容器中运行命令,退出容器,然后把最终状态作为新镜像。用Dockerfile构建镜像是与此相同的,但是是自动化且可重复的,随时可以通过修改Dockerfile重新构建镜像而无须手动重新输入命令。

2.1.5 运行容器镜像

以下的命令可以用来运行镜像:

$ docker run --name kubia-container -p 8080:8080 -d kubia

这条命令告知Docker基于 kubia 镜像创建一个叫 kubia-container 的新容器。这个容器与命令行分离(-d 标志),这意味着在后台运行。本机上的8080端口会被映射到容器内的8080端口(-p 8080:8080 选项),所以可以通过http://localhost:8080 访问这个应用。

如果没有在本机上运行Docker守护进程(比如使用的是Mac或Windows系统,守护进程会运行在VM中),需要使用VM的主机名或IP来代替localhost运行守护进程。可以通过 DOCKER_HOST 这个环境变量查看主机名。

访问应用

现在试着通过 http://localhost:8080 访问你的应用(确保使用Docker主机名或IP替换localhost):

$ curl localhost:8080

You've hit 44d76963e8e1

这是应用的响应。现在应用运行在容器中,与其他东西隔离。可以看到,应用把 44d76963e8e1 作为主机名返回,这并不是宿主机的主机名。这个十六进制数是Docker容器的ID。

列出所有运行中的容器

下面的代码清单列出了所有的运行中的容器,可以查看列表(为了更好的可读性,列表被分成了两行显示)。

代码清单2.5 列出运行中的容器

# docker ps

CONTAINER ID   IMAGE    COMMAND        CREATED            STATUS        PORTS                     NAMES

9d3e6e24ab3c   kubia    "node app.js"   4 minutes ago   Up 4 minutes   0.0.0.0:8080->8080/tcp   kubia-container

有一个容器在运行。Docker会打印出每一个容器的ID和名称、容器运行所使用的镜像,以及容器中执行的命令。

获取更多的容器信息

docker ps 只会展示容器的大部分基础信息。可以使用 docker inspect查看更多的信息:

$ docker inspect kubia-container

Docker会打印出包含容器底层信息的长JSON。

2.1.6 探索运行容器的内部

我们来看看容器内部的环境。由于一个容器里可以运行多个进程,所以总是可以运行新的进程去看看里面发生了什么。如果镜像里有可用的shell二进制可执行文件,也可以运行一个shell。

在已有的容器内部运行shell

镜像基于的Node.js镜像包含了bash shell,所以可以像这样在容器内运行shell:

$ docker exec -it kubia-container bash

这会在已有的kubia-container容器内部运行bash。bash 进程会和主容器进程拥有相同的命名空间。这样可以从内部探索容器,查看Node.js和应用是如何在容器里运行的。-it 选项是下面两个选项的简写:

  • -i,确保标准输入流保持开放。需要在shell中输入命令。
  • -t,分配一个伪终端(TTY)。

如果希望像平常一样使用shell,需要同时使用这两个选项(如果缺少第一个选项就无法输入任何命令。如果缺少第二个选项,那么命令提示符不会显示,并且一些命令会提示 TERM 变量没有设置)。

从内部探索容器

下面的代码展示了如何使用shell查看容器内运行的进程。

代码清单2.6 从容器内列出进程

# ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND

root         1  0.0  1.4 614436 26376 ?        Ssl  08:55   0:00 node app.js

root        12  0.1  0.1  20248  3196 pts/0    Ss   09:01   0:00 bash

root        19  0.0  0.1  17504  2000 pts/0    R+   09:02   0:00 ps aux

只看到了三个进程,宿主机上没有看到其他进程。

容器内的进程运行在主机操作系统上

如果现在打开另一个终端,然后列出主机操作系统上的进程,连同其他的主机进程依然会发现容器内的进程,如代码清单2.7所示。

注意 如果使用的是Mac或者Windows系统,需要登录到Docker守护进程运行的VM查看这些进程。

代码清单2.7 运行在主机操作系统上的容器进程

$ ps aux | grep app.js

USER PID CPU MEM vsz RSS TTY STAT START TIME COMMAND

root 382 0.0 0.1 676380 16504 ? S1 12:31 0:00 node app.js

这证明了运行在容器中的进程是运行在主机操作系统上的。如果你足够敏锐,会发现进程的ID在容器中与主机上不同。容器使用独立的PID Linux命名空间并且有着独立的系列号,完全独立于进程树。

容器的文件系统也是独立的

正如拥有独立的进程树一样,每个容器也拥有独立的文件系统。在容器内列出根目录的内容,只会展示容器内的文件,包括镜像内的所有文件,再加上容器运行时创建的任何文件(类似日志文件),如下面的代码清单所示。

代码清单2.8 容器拥有完整的文件系统

root@44d76963e8el:/# 1s

app.js boot etc lib mediabin  dev home 1ib64 mnt

opt root sbin sys usrpros run srv tmp var

其中包含app.js文件和其他系统目录,这些目录是正在使用的 node:7 基础镜像的一部分。可以使用 exit 命令来退出容器返回宿主机(类似于登出ssh session)。

提示 进入容器对于调试容器内运行的应用来说是非常有用的。出错时,需要做的第一件事是查看应用运行的系统的真实状态。需要记住的是,应用不仅拥有独立的文件系统,还有进程、用户、主机名和网络接口。

2.1.7 停止和删除容器

可以通过告知Docker停止 kubia-container 容器来停止应用:

$ docker stop kubia-container

因为没有其他的进程在容器内运行,这会停止容器内运行的主进程。容器本身仍然存在并且可以通过 docker ps-a 来查看。-a 选项打印出所有的容器,包括运行中的和已经停止的。想要真正地删除一个容器,需要运行 docker rm :

$ docker rm kubia-container

这会删除容器,所有的内容会被删除并且无法再次启动。

2.1.8 向镜像仓库推送镜像

现在构建的镜像只可以在本机使用。为了在任何机器上都可以使用,可以把镜像推送到一个外部的镜像仓库。为了简单起见,不需要搭建一个私有的镜像仓库,而是可以推送镜像到公开可用的Docker Hub(http://hub.docker.com)镜像中心。另外还有其他广泛使用的镜像中心,如http://Quay.io和Google Container Registry。

在推送之前,需要重新根据Docker Hub的规则标注镜像。Docker Hub允许向以你的Docker Hub ID开头的镜像仓库推送镜像。可以在http://hub.docker.com上注册Docker Hub ID。下面的例子中会使用笔者自己的ID(luksa),请在每次出现时替换自己的ID。

使用附加标签标注镜像

一旦知道了自己的ID,就可以重命名镜像,现在镜像由 kubia 改为luksa/kubia(用自己的Docker Hub ID代替 luksa):

$ docker tag kubia luksa/kubia

这不会重命名标签,而是给同一个镜像创建一个额外的标签。可以通过docker images 命令列出本机存储的镜像来加以确认,如下面的代码清单所示。

代码清单2.9 一个容器镜像可以有多个标签

# docker images | head

REPOSITORY              TAG         IMAGE ID       CREATED             SIZE

kubia                   v2          8af4d2d1750a   2 minutes ago       82.7MB

正如所看到的,kubia 和 luksa/kubia 指向同一个镜像ID,所以实际上是同一个镜像的两个标签。

向Docker Hub推送镜像

在向Docker Hub推送镜像之前,先需要使用 docker login 命令和自己的用户ID登录,然后就可以像这样向Docker Hub推送 yourid/kubia 镜像:

$ docker push luksa/kubia

在不同机器上运行镜像

在推送完成之后,镜像便可以给任何人使用。可以在任何机器上运行下面的命令来运行镜像:

$ docker run -p 8080:8080 luksa/kubia

这非常简单。最棒的是应用每次都运行在完全一致的环境中。如果在你的机器上正常运行,也会在所有的Linux机器上正常运行。无须担心主机是否安装了Node.js。事实上,就算安装了,应用也并不会使用,因为它使用的是镜像内部安装的。

2.2 配置Kubernetes集群

现在,应用被打包在一个容器镜像中,并通过Docker Hub给大家使用,可以将它部署到Kubernetes集群中,而不是直接在Docker中运行。但是需要先设置集群。

设置一个完整的、多节点的Kubernetes集群并不是一项简单的工作,特别是如果你不精通Linux和网络管理的话。一个适当的Kubernetes安装需要包含多个物理或虚拟机,并需要正确地设置网络,以便在Kubernetes集群内运行的所有容器都可以在相同的扁平网络环境内相互连通。

安装Kubernetes集群的方法有许多。这些方法在http://kubernetes.io的文档中有详细描述。我们不会在这里列出所有,因为内容在不断变化,但Kubernetes可以在本地的开发机器、自己组织的机器集群或是虚拟机提供商(Google Compute Engine、Amazon EC2、Microsoft Azure等)上运行,或者使用托管的Kubernetes集群,如Google Kubernetes Engine(以前称为Google Container Engine)。

在这一章中,将介绍用两种简单的方法构建可运行的Kubernetes集群,你将会看到如何在本地机器上运行单节点Kubernetes集群,以及如何访问运行在Google Kubernetes Engine(GKE)上的托管集群。

第三个选项是使用 kubeadm 工具安装一个集群,这会在附录B中介绍,这里的说明向你展示了如何使用虚拟机建立一个三节点的Kubernetes集群,但是建议你在阅读本书的前11章之后再尝试。

另一个选择是在亚马逊的AWS(Amazon Web Services)上安装Kubernetes。为此,可以查看 kops 工具,它是在前面一段提到的 kubeadm 基础之上构建的,可以在http://github.com/kubernetes/kops中找到。它帮助你在AWS上部署生产级、高可用的Kubernetes集群,并最终会支持其他平台(Google Kubernetes Engine、VMware、vSphere等)。

2.2.1 用Minikube运行一个本地单节点Kubernetes集群

使用Minikube是运行Kubernetes集群最简单、最快捷的途径。Minikube是一个构建单节点集群的工具,对于测试Kubernetes和本地开发应用都非常有用。

虽然我们不能展示与管理多节点应用相关的一些Kubernetes特性,但是单节点集群足以探索本书中讨论的大多数主题。

安装Minikube

Minikube是一个需要下载并放到路径中的二进制文件。它适用于OSX、Linux和Windows系统。最好访问GitHub上的Minikube代码仓库(http://github.com/kubernetes/minikube),按照说明来安装它。

例如,在OSX和Linux系统上,可以使用一个命令下载Minikube并进行设置。对于OSX系统,命令是这样的:

$ curl -Lo minikube https://storage.googleapis.com/minikube/releases/v0.23.0/minikube-darwin-amd64 && chmod +x minikube && sudo mv minikube/usr/local/bin/

在Linux系统中,可以下载另一个版本(将URL中的 “darwin” 替换为“linux”)。在Windows系统中,可以手动下载文件,将其重命名为minikube.exe,并把它加到路径中。Minikube在VM中通过VirtualBox或KVM运行Kubernetes,所以在启动Minikube集群之前,还需要安装VM。

使用Minikue启动一个Kubernetes集群

当你在本地安装了Minikube之后,可以立即使用下面的命令启动Kubernetes集群。

代码清单2.10 启动一个Minikube虚拟机

$ minikube start

starting local Kubernetes cluster...

starting vM...

SSH-ing files into vM....

...

kubectl is now configured to use the cluster.

启动集群需要花费超过一分钟的时间,所以在命令完成之前不要中断它。


安装Kubernetes客户端(kubectl)

要与Kubernetes进行交互,还需要 kubectl CLI客户端。同样,需要做的就是下载它,并放在路径中。例如,OSX系统的最新稳定版本可以通过以下命令下载并安装:

$ /$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt) /bin/darwin/amd64/kubectl
&& chmod +x kubectl
&& sudo mv kubectl /usr/local/bin/

要下载用于Linux或Windows系统的kubectl,用 linux 或 windows 替换URL中的 darwin。

注意 如果你需要使用多个Kubernetes集群(例如,Minikube和GKE),请参考附录A,了解如何在不同的 kubectl 上下文中设置和切换。

使用kubectl查看集群是否正常工作

要验证集群是否正常工作,可以使用以下所示的 kubectl cluster-info命令。

代码清单2.11 展示集群信息

$ kubectl cluster-info

Kubernetes master is running at https://192.168.99.100:8443

KubeDNS is running at https://192.168.99.100:8443/api/v1/proxy/...

kubernetes-dashboard is running at https://192.168.99.100:8443/api/v1/.....

这里显示集群已经启动。它显示了各种Kubernetes组件的URL,包括API服务器和Web控制台。

提示 可以运行 minikube ssh 登录到Minikube VM并从内部探索它。例如,可以查看在节点上运行的进程。

2.2.2 使用Google Kubernetes Engine托管Kubernetes集群

如果你想探索一个完善的多节点Kubernetes集群,可以使用托管的Google Kubernetes Engine(GKE)集群。这样,无须手动设置所有的集群节点和网络,因为这对于刚开始使用Kubernetes的人来说太复杂了。使用例如GKE这样的托管解决方案可以确保不会出现配置错误、不工作或部分工作的集群。

配置一个Google Cloud项目并且下载必需的客户端二进制

在设置新的Kubernetes集群之前,需要设置GKE环境。因为这个过程可能会改变,所以不在这里列出具体的说明。阅读https://cloud.google.com/containerengine/docs/before-begin中的说明后就可以开始了。

整个过程大致包括:

  1. 注册谷歌账户,如果你还没有注册过。
  2. 在Google Cloud Platform控制台中创建一个项目。
  3. 开启账单。这会需要你的信用卡信息,但是谷歌提供了为期12个月的免费试用。而且在免费试用结束后不会自动续费。
  4. 开启Kubernetes Engine API。
  5. 下载安装Google Cloud SDK(这包含 gcloud 命令行工具,需要创建一个Kubernetes集群)。
  6. 使用 gcloud components install kubectl 安装 kubectl 命令行工具。

注意 某些操作(例如步骤2中的操作)可能需要几分钟才能完成,所以在此期间可以喝杯咖啡放松一下。

创建一个三节点Kubernetes集群

完成安装后,可以使用下面代码清单中的命令创建一个包含三个工作节点的Kubernetes集群。

代码清单2.12 在GKE上创建一个三节点集群

$ gcloud container clusters create kubia --num-nodes 3 --machine-tvpe fl-micro

Creating cluster kubia... done

Created [https://container.googleapis.com/y1/proiects/kubial-1227/zones/europe-west1-d/clusters/kubia]

kubeconfig entry generated for kubia.

NAME ZONE MST VER MASTER IP  TYPE NODE VER NUM NODES STATUSkubia eu-wld 1.5.3 104.155.92.30 fl-micro 1.5.3:  RUNNING

现在已经有一个正在运行的Kubernetes集群,包含了三个工作节点,如图 2.4所示。你在使用三个节点来更好地演示适用于多节点的特性,如果需要的话可以使用较少数量的节点。

获取集群概览

图 2.4 能够让你对集群,以及如何与集群交互有一个初步的认识。每个节点运行着Docker、Kubelet和kube-proxy。可以通过 kubectl 命令行客户端向运行在主节点上的Kubernetes API服务器发出REST请求以与集群交互。

 图2.4 如何与三节点Kubernetes集群进行交互

通过列出集群节点查看集群是否在运行

现在可以使用kubectl命令列出集群中的所有节点,如下面的代码清单所示。

代码清单2.13 使用kubectl列出集群节点

# kubectl get nodes

NAME         STATUS     ROLES                  AGE   VERSION

k8s-master   NotReady   control-plane,master   10h   v1.20.2

k8s-node1    NotReady   <none>                 9h    v1.20.2

k8s-node2    NotReady   <none>                 9h    v1.20.2

kubectl get 命令可以列出各种Kubernetes对象。你将会经常使用到它,但它通常只会显示对象最基本的信息。

提示 可以使用 gcloud compute ssh <node-name> 登录到其中一个节点,查看节点上运行了什么。

查看对象的更多信息

要查看关于对象的更详细的信息,可以使用 kubectl describe 命令,它显示了更多信息:

# kubectl describe node k8s-master

Name:               k8s-master

Roles:              control-plane,master

Labels:             beta.kubernetes.io/arch=amd64

                    beta.kubernetes.io/os=linux

                    kubernetes.io/arch=amd64

                    kubernetes.io/hostname=k8s-master

                    kubernetes.io/os=linux

                    node-role.kubernetes.io/control-plane=

                    node-role.kubernetes.io/master=

Annotations:        kubeadm.alpha.kubernetes.io/cri-socket: /var/run/dockershim.sock

                    node.alpha.kubernetes.io/ttl: 0

                    volumes.kubernetes.io/controller-managed-attach-detach: true

CreationTimestamp:  Fri, 12 Feb 2021 21:32:22 +0800

Taints:             node-role.kubernetes.io/master:NoSchedule

                    node.kubernetes.io/not-ready:NoSchedule

Unschedulable:      false

Lease:

  HolderIdentity:  k8s-master

  AcquireTime:     <unset>

  RenewTime:       Sat, 13 Feb 2021 07:41:25 +0800

Conditions:

  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message

  ----             ------  -----------------                 ------------------                ------                       -------

  MemoryPressure   False   Sat, 13 Feb 2021 07:38:24 +0800   Fri, 12 Feb 2021 21:32:19 +0800   KubeletHasSufficientMemory   kubelet has sufficient memory available

  DiskPressure     False   Sat, 13 Feb 2021 07:38:24 +0800   Fri, 12 Feb 2021 21:32:19 +0800   KubeletHasNoDiskPressure     kubelet has no disk pressure

  PIDPressure      False   Sat, 13 Feb 2021 07:38:24 +0800   Fri, 12 Feb 2021 21:32:19 +0800   KubeletHasSufficientPID      kubelet has sufficient PID available

  Ready            False   Sat, 13 Feb 2021 07:38:24 +0800   Fri, 12 Feb 2021 21:32:19 +0800   KubeletNotReady              runtime network not ready: NetworkReady=false reason:NetworkPluginNotReady message:docker: network plugin is not ready: cni config uninitialized

Addresses:

  InternalIP:  192.168.137.100

  Hostname:    k8s-master

Capacity:

  cpu:                2

  ephemeral-storage:  38770180Ki

  hugepages-1Gi:      0

  hugepages-2Mi:      0

  memory:             3880576Ki

  pods:               110

Allocatable:

  cpu:                2

  ephemeral-storage:  35730597829

  hugepages-1Gi:      0

  hugepages-2Mi:      0

  memory:             3778176Ki

  pods:               110

System Info:

  Machine ID:                 4eec0f78eb7f4eb8b33480fe97fca874

  System UUID:                5A5A9919-F309-7748-9C28-CB280C32FD39

  Boot ID:                    dc707f60-c19b-4883-889a-81cac673950c

  Kernel Version:             3.10.0-1160.15.2.el7.x86_64

  OS Image:                   CentOS Linux 7 (Core)

  Operating System:           linux

  Architecture:               amd64

  Container Runtime Version:  docker://20.10.3

  Kubelet Version:            v1.20.2

  Kube-Proxy Version:         v1.20.2

PodCIDR:                      10.244.0.0/24

PodCIDRs:                     10.244.0.0/24

Non-terminated Pods:          (5 in total)

  Namespace                   Name                                  CPU Requests  CPU Limits  Memory Requests  Memory Limits  AGE

  ---------                   ----                                  ------------  ----------  ---------------  -------------  ---

  kube-system                 etcd-k8s-master                       100m (5%)     0 (0%)      100Mi (2%)       0 (0%)         10h

  kube-system                 kube-apiserver-k8s-master             250m (12%)    0 (0%)      0 (0%)           0 (0%)         10h

  kube-system                 kube-controller-manager-k8s-master    200m (10%)    0 (0%)      0 (0%)           0 (0%)         10h

  kube-system                 kube-proxy-mskzh                      0 (0%)        0 (0%)      0 (0%)           0 (0%)         10h

  kube-system                 kube-scheduler-k8s-master             100m (5%)     0 (0%)      0 (0%)           0 (0%)         10h

Allocated resources:

  (Total limits may be over 100 percent, i.e., overcommitted.)

  Resource           Requests    Limits

  --------           --------    ------

  cpu                650m (32%)  0 (0%)

  memory             100Mi (2%)  0 (0%)

  ephemeral-storage  100Mi (0%)  0 (0%)

  hugepages-1Gi      0 (0%)      0 (0%)

  hugepages-2Mi      0 (0%)      0 (0%)

Events:

  Type    Reason                   Age                From        Message

  ----    ------                   ----               ----        -------

  Normal  NodeHasSufficientMemory  10h (x5 over 10h)  kubelet     Node k8s-master status is now: NodeHasSufficientMemory

  Normal  NodeHasNoDiskPressure    10h (x5 over 10h)  kubelet     Node k8s-master status is now: NodeHasNoDiskPressure

  Normal  NodeHasSufficientPID     10h (x4 over 10h)  kubelet     Node k8s-master status is now: NodeHasSufficientPID

  Normal  Starting                 10h                kubelet     Starting kubelet.

  Normal  NodeHasSufficientMemory  10h                kubelet     Node k8s-master status is now: NodeHasSufficientMemory

  Normal  NodeHasNoDiskPressure    10h                kubelet     Node k8s-master status is now: NodeHasNoDiskPressure

  Normal  NodeHasSufficientPID     10h                kubelet     Node k8s-master status is now: NodeHasSufficientPID

  Normal  NodeAllocatableEnforced  10h                kubelet     Updated Node Allocatable limit across pods

  Normal  Starting                 10h                kube-proxy  Starting kube-proxy.

  Normal  Starting                 18m                kubelet     Starting kubelet.

  Normal  NodeHasSufficientMemory  18m (x8 over 18m)  kubelet     Node k8s-master status is now: NodeHasSufficientMemory

  Normal  NodeHasNoDiskPressure    18m (x8 over 18m)  kubelet     Node k8s-master status is now: NodeHasNoDiskPressure

  Normal  NodeHasSufficientPID     18m (x7 over 18m)  kubelet     Node k8s-master status is now: NodeHasSufficientPID

  Normal  NodeAllocatableEnforced  18m                kubelet     Updated Node Allocatable limit across pods

  Normal  Starting                 18m                kube-proxy  Starting kube-proxy.

这里省略了 describe 命令的实际输出,因为内容非常多且在书中是完全不可读的。输出显示了节点的状态、CPU和内存数据、系统信息、运行容器的节点等。

在前面的 kubectl describe 示例中,显式地指定了节点的名称,但也可以执行一个简单的 kubectl describe node 命令,而无须指定节点名,它将打印出所有节点的描述信息。

提示 当只有一个给定类型的对象存在时,不指定对象名就运行 description和 get 命令是很提倡的,这样不会浪费时间输入或复制、粘贴对象的名称。

当我们讨论减少输入的时候,开始在Kubernetes运行第一个应用程序之前,先学习如何让 kubectl 命令的使用变得更容易。

2.2.3 为kubectl配置别名和命令行补齐

kubectl 会被经常使用。很快你就会发现每次不得不打全命令是非常痛苦的。在继续之前,花一分钟为kubectl 设置别名和tab命令补全可让使用变得简单。

创建别名

在整本书中,一直会使用 kubectl 可执行文件的全名,但是你可以添加一个较短的别名,如 k,这样就不用每次都输入 kubectl 了。如果还没有设置别名,这里会告诉你如何定义。将下面的代码添加到 ~/.bashrc 或类似的文件中:

alias k=kubectl

注意 如果你已经在用 gcloud 配置集群,就已经有可执行文件 k 了。

为kuebctl配置tab补全

即使使用短别名k,仍然需要输入许多内容。幸运的是,kubectl命令还可以配置bash和zsh shell的代码补全。tab补全不仅可以补全命令名,还能补全对象名。例如,无须在前面的示例中输入整个节点名,只需输入

$ kubectl desc<TAB> no<TAB> gke-ku<TAB>

需要先安装一个叫作 bashcompletion 的包来启用bash中的tab命令补全,然后可以运行接下来的命令(也需要加到 ~/.bashrc 或类似的文件中):

$ source <(kubectl completion bash)

但是需要注意的是,tab命令行补全只在使用完整的 kubectl 命令时会起作用(当使用别名 k 时不会起作用)。需要改变 kubectl completion 的输出来修复:

$ source <(kubectl completion bash | sed s/kubectl/k/g)

注意 不幸的是,在写作本书之时,别名的shell命令补全在MacOS系统上并不起作用。如果需要使用命令行补全,就需要使用完整的 kubectl 命令。

现在你已经准备好无须输入太多就可以与集群进行交互。现在终于可以在Kubernetes上运行第一个应用了。

2.3 在Kubernetes上运行第一个应用

因为这可能是第一次,所以会使用最简单的方法在Kubernetes上运行应用程序。通常,需要准备一个JSON或YAML,包含想要部署的所有组件描述的配置文件,但是因为还没有介绍可以在Kubernetes中创建的组件类型,所以这里将使用一个简单的单行命令来运行应用。

2.3.1 部署Node.js应用

部署应用程序最简单的方式是使用 kubectl run 命令,该命令可以创建所有必要的组件而无需JSON或YAML文件。这样的话,我们就不需要深入了解每个组件对象的结构。试着运行之前创建、推送到Docker Hub的镜像。下面是在Kubernetes中运行的代码:

kubectl run kubia --image=luksa/kubia:v1 --port=8080 --generator=run/v1

--image=luksa/kubia 显示的是指定要运行的容器镜像,

--port=8080 选项告诉Kubernetes应用正在监听8080端口。

最后一个标志(--generator)需要解释一下,通常并不会使用到它,它让Kubernetes创建一个ReplicationController,而不是Deployment。稍后你将在本章中了解到什么是ReplicationController,但是直到第9章才会介绍Deployment,所以不会在这里创建Deployment。

正如前面命令的输出所示,已经创建了一个名为kubia的ReplicationController。如前所述,我们将在本章的后面看到。从底层开始,把注意力放在创建的容器上(可以假设已经创建了一个容器,因为在 run 命令中指定了一个容器镜像)。

 冬日暖阳的笔记(新版本中已有变化)

kubectl run kubia --image=jliudong/kubia:v1 --port=8080 --generator=run/v1
Flag --generator has been deprecated, has no effect and will be removed in the future. pod/kubia created

原因是生成器参数已经被弃用。参考 https://kubernetes.io/zh/docs/reference/kubectl/conventions/#%E7%94%9F%E6%88%90%E5%99%A8
解决办法:

  • 去掉 --generator 参数
 kubectl run kubia --image=jliudong/kubia --port=8080

 pod/kubia created
  • 将其作为 Service 公开:
 kubectl expose pod kubia --type=NodePort

 service/kubia exposed

选项 --type=NodePort 指定 Service 的类型。

  • 检查 Pod 是否启动并运行:

 现在Pod 已经启动,但是必须等到 Pod 启动完全才能通过暴露的 Service 访问它。

$ kubectl get pod

 NAME       READY   STATUS             RESTARTS   AGE 
 kubia      0/1     ImagePullBackOff   0          3m25s

如果输出显示 STATUS 为 ImagePullBackOff,则表明 正在后台拉取镜像:

$ kubectl get pod

 NAME       READY   STATUS             RESTARTS   AGE 
 kubia      0/1     ContainerCreating   0          3m25s

如果输出显示 STATUS 为 ContainerCreating,则表明 Pod 仍在创建中:

$ kubectl get pod

 NAME       READY   STATUS       RESTARTS   AGE
 kubia      0/1     Running      0          3m25s

如果输出显示 STATUS 为 Running,则表明 Pod 现在正在运行。

  • 获取暴露 Service 的 URL 以查看 Service 的详细信息:
$ kubectl get svc

 NAME    TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
 kubia   NodePort   10.99.75.144           8080:31463/TCP   15m
  • 删除 Service:
$ kubectl delete services kubia

介绍pod

你或许在想,是否有一个列表显示所有正在运行的容器,可以通过类似于 kubectl get pods 的命令获取。这并不是Kubernetes的工作,它不直接处理单个容器。相反,它使用多个共存容器的理念。这组容器就叫作pod。

一个pod是一组紧密相关的容器,它们总是一起运行在同一个工作节点上,以及同一个Linux命名空间中。每个pod就像一个独立的逻辑机器,拥有自己的IP、主机名、进程等,运行一个独立的应用程序。应用程序可以是单个进程,运行在单个容器中,也可以是一个主应用进程或者其他支持进程,每个进程都在自己的容器中运行。一个pod的所有容器都运行在同一个逻辑机器上,而其他pod中的容器,即使运行在同一个工作节点上,也会出现在不同的节点上。

为了更好地理解容器、pod和节点之间的关系,请查看图 2.5。如你所见,每个pod都有自己的IP,并包含一个或多个容器,每个容器都运行一个应用进程。pod分布在不同的工作节点上。图2.5 容器、pod及物理工作节点之间的关系

 

列出pod

不能列出单个容器,因为它们不是独立的Kubernetes对象,但是可以列出pod。让我们看看如何使用 kubectl 在下面的代码清单中列出pod。

代码清单2.14 列出pod

# kubectl get pod

NAME       READY   STATUS    RESTARTS   AGE

kubia      1/1     Running   0          3m17s

pod仍然处于挂起状态,pod的单个容器显示为还未就绪的状态(这是 READY列中的 0/1的含义)。pod还没有运行的原因是:该pod被分配到的工作节点正在下载容器镜像,完成之后才可以运行。下载完成后,将创建pod的容器,然后pod会变为运行状态,如下面的代码清单所示。

代码清单2.15 再次列出pod查看pod的状态是否变化

# kubectl get pods

NAME    READY   STATUS    RESTARTS   AGE

kubia   0/1     Pending   0          5h26m

要查看有关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会立即运行,而不是将要运行。

图2.6 在Kubernetes中运行luksa/kubia容器镜像

 

2.3.2 访问Web应用

如何访问正在运行的pod?我们提到过每个pod都有自己的IP地址,但是这个地址是集群内部的,不能从集群外部访问。要让pod能够从外部访问,需要通过服务对象公开它,要创建一个特殊的 LoadBalancer 类型的服务。因为如果你创建一个常规服务(一个 ClusterIP 服务),比如pod,它也只能从集群内部访问。通过创建 LoadBalancer 类型的服务,将创建一个外部的负载均衡,可以通过负载均衡的公共IP访问pod。

创建一个服务对象

要创建服务,需要告知Kubernetes对外暴露之前创建的ReplicationController:

# kubectl expose rc kubia --type=LoadBalancer --name kubia-http

service/kubia-http exposed

冬日暖阳的笔记
创建rc
 

kubectl create -f kubia.yaml
replicationcontroller/kubia created

注意 我们这里用的是 replicationcontroller 的缩写 rc。大多数资源类型都有这样的缩写,所以不必输入全名(例如,pods 的缩写是 po,service 的缩写是svc,等等)。

列出服务

expose 命令的输出中提到一个名为kubian-http 的服务。服务是类似于pod和Node的对象,因此可以通过运行 kubectl get services 命令查看新创建的服务对象,如下面的代码清单所示。

代码清单2.16 列出服务

# kubectl get services

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE

kubia-http   LoadBalancer   10.99.89.223   <pending>     8080:30525/TCP   3m7s

冬日暖阳的笔记
此时可以使用集群IP进行访问

curl http://10.99.89.223:8080

在负载均衡模式下,会随机访问其中的服务,效果类似如下:
shell [root@k8s-master ~]# curl http://10.108.137.49:8080

You've hit kubia-ww77s

[root@k8s-master ~]# curl http://10.108.137.49:8080

You've hit kubia-n7fvq

该列表显示了两个服务。暂时忽略 kubernetes 服务,仔细查看创建的kubian-http 服务。它还没有外部IP地址,因为Kubernetes运行的云基础设施创建负载均衡需要一段时间。负载均衡启动后,应该会显示服务的外部IP地址。让我们等待一段时间并再次列出服务,如下面的代码清单所示。

代码清单2.17 再次列出服务并查看是否分配了外部IP

# kubectl get svc

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE

kubia-http   LoadBalancer   10.99.89.223   <pending>     8080:30525/TCP   95s

现在有外部IP了,应用就可以从任何地方通过 http://104.155.74.57:8080 访问。

注意 Minikube不支持 LoadBalancer 类型的服务,因此服务不会有外部IP。但是可以通过外部端口访问服务。在下一节的提示中将介绍这是如何做到的。

使用外部IP访问服务

现在可以通过服务的外部IP和端口向pod发送请求:

$ curl 104.155.74.57:8080

现在,应用程序在三个节点的Kubernetes集群(如果使用Minikube,则是一个单节点集群)上运行起来了。如果你忘了建立整个集群所需的步骤,那么只需两个简单的命令就可以让你的应用运行起来,并且让全世界的用户都能访问它。

提示 使用Minikube的时候,可以运行 minikube service kubia-http获取可以访问服务的IP和端口。

如果本地访问异常,可以尝试

kubectl port-forward service/kubia-http 7080:8080

然后在新开终端使用curl 127.0.0.1:7080进行访问

如果仔细观察,会发现应用将pod名称作为它的主机名。如前所述,每个pod都像一个独立的机器,具有自己的IP地址和主机名。尽管应用程序运行在工作节点的操作系统中,但对应用程序来说,它似乎是在一个独立的机器上运行,而这台机器本身就是应用程序的专用机器,没有其他的进程一同运行。

冬日暖阳的笔记
如果无法从指定的位置拉取镜像,会造成服务无法访问的情况,但Kubernetes并没有给出很好的提示,可以通过 kubectl get pod -o wide 来查看pod的运行状况,进行相应调整。
 

$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kubia-4j9fr 0/1 ImagePullBackOff 0 13m 10.244.2.6 k8s-node2 kubia-kvdxz 0/1 ImagePullBackOff 0 13m 10.244.1.5 k8s-node1


最终排查的问题是因为 kubia.yaml 文件中 image: jliudong/kubia:latest 应为 image: jliudong/kubia:v1 。
参考 Chapter02/kubia.yaml

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给出了这三种元素组合的大致情况。

图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 命令:

$ kubectl get replicationcontrollers

使用kubectl get列出所有类型的资源

一直在使用相同的基本命令 kubectl get 来列出集群中的资源。你已经使用此命令列出节点、pod、服务和ReplicationController对象。不指定资源类型调用 kubectl get 可以列出所有可能类型的对象。然后这些类型可以使用各种 kubectl 命令,例如 getdescribe 等。列表还显示了前面提到的缩写。

该列表显示了一个名为kubia 的单个ReplicationController。DESIRED 列显示了希望ReplicationController保持的pod副本数,而 CURRENT 列显示当前运行的pod数。在示例中,希望pod副本为1,而现在就有一个副本正在运行。

增加期望的副本数

为了增加pod的副本数,需要改变ReplicationController期望的副本数,如下所示:

$ kubectl scale rc kubia --replicas=3

replicationcontroller/kubia scaled

现在已经告诉Kubernetes需要确保pod始终有三个实例在运行。注意,你没有告诉Kubernetes需要采取什么行动,也没有告诉Kubernetes增加两个pod,只设置新的期望的实例数量并让Kubernetes决定需要采取哪些操作来实现期望的状态。

这是Kubernetes最基本的原则之一。不是告诉Kubernetes应该执行什么操作,而是声明性地改变系统的期望状态,并让Kubernetes检查当前的状态是否与期望的状态一致。在整个Kubernetes世界中都是这样的。

查看扩容的结果

前面增加了pod的副本数。再次列出ReplicationController查看更新后的副本数:

$ kubectl get rc

 

由于pod的实际数量已经增加到三个(从 CURRENT 列中可以看出),列出所有的pod时显示的应该是三个而不是一个:

$ kubectl get pods

正如你所看到的,有三个pod而不是一个。两个已经在运行,一个仍在挂起中,一旦容器镜像下载完毕并启动容器,挂起的pod会马上运行。

正如你所看到的,给应用扩容是非常简单的。一旦应用在生产中运行并且需要扩容,可以使用一个命令添加额外的实例,而不必手动安装和运行其他副本。

记住,应用本身需要支持水平伸缩。Kubernetes并不会让你的应用变得可扩展,它只是让应用的扩容或缩容变得简单。

切换到服务时请求切换到所有三个pod上

因为现在应用的多个实例在运行,让我们看看如果再次请求服务的URL会发生什么。会不会总是切换到应用的同一个实例呢?

请求随机地切换到不同的pod。当pod有多个实例时Kubernetes服务就会这样做。服务作为负载均衡挡在多个pod前面。当只有一个pod时,服务为单个pod提供一个静态地址。无论服务后面是单个pod还是一组pod,这些pod在集群内创建、消失,这意味着它们的IP地址会发生变化,但服务的地址总是相同的。这使得无论有多少pod,以及它们的地址如何变化,客户端都可以很容易地连接到pod。

可视化系统的新状态

让我们可视化一下现在的系统,看看和以前相比发生了什么变化。图2.8显示了系统的新状态。仍然有一个服务和一个ReplicationController,但是现在有三个pod实例,它们都是由ReplicationController管理的。服务不再将所有请求发送到单个pod,而是将它们分散到所有三个pod中,如前面使用 curl 进行的实验所示。

作为练习,现在可以尝试通过进一步增加ReplicationController的副本数来启动附加实例,甚至可以尝试减小副本数。

图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和所运行的节点:

# kubectl get pod -o wide

NAME          READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES

kubia-n7fvq   1/1     Running   0          3h42m   10.244.1.6    k8s-node1   <none>           <none>

kubia-qrb4q   1/1     Running   0          2m9s    10.244.2.11   k8s-node2   <none>           <none>

kubia-ww77s   1/1     Running   0          3h42m   10.244.2.7    k8s-node2   <none>           <none>

使用kubectl describe查看pod的其他细节

还可以使用 kubectl describe 命令来查看节点,该命令显示了pod的许多其他细节,如下面的代码清单所示。

代码清单2.18 使用kubectl describe描述一个pod

$ kubectl describe  pods/kubia
Name:         kubia
Namespace:    default
Priority:     0
Node:         minikube/192.168.49.2
Start Time:   Tue, 13 Sep 2022 10:47:12 +0800
Labels:       run=kubia
Annotations:  <none>
Status:       Pending
IP:           172.17.0.10
IPs:
  IP:  172.17.0.10
Containers:
  kubia:
    Container ID:   
    Image:          jliudong/kubia
    Image ID:       
    Port:           8080/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       ImagePullBackOff
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-ztllb (ro)
Conditions:
  Type              Status
  Initialized       True 
  Ready             False 
  ContainersReady   False 
  PodScheduled      True 
Volumes:
  kube-api-access-ztllb:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason     Age                   From               Message
  ----     ------     ----                  ----               -------
  Normal   Scheduled  57m                   default-scheduler  Successfully assigned default/kubia to minikube
  Normal   Pulling    55m (x4 over 57m)     kubelet            Pulling image "jliudong/kubia"
  Warning  Failed     55m (x4 over 57m)     kubelet            Failed to pull image "jliudong/kubia": rpc error: code = Unknown desc = Error response from daemon: manifest for jliudong/kubia:latest not found: manifest unknown: manifest unknown
  Warning  Failed     55m (x4 over 57m)     kubelet            Error: ErrImagePull
  Warning  Failed     55m (x6 over 57m)     kubelet            Error: ImagePullBackOff
  Normal   BackOff    2m6s (x236 over 57m)  kubelet            Back-off pulling image "jliudong/kubia"

这展示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:

kubectl cluster-info | grep dashboard

图2.9 Kubernetes dashboard的页面截图 

如果在浏览器中打开这个URL,将会显示用户名和密码提示符。可以运行以下命令找到用户名和密码:

$ gcloud container clusters describe kubia | grep -E "(username | password):"

访问Minikube的dashboard

要打开使用Minikube的Kubernetes集群的dashboard,请运行以下命令:

$ minikube dashboard

dashboard将在默认浏览器中打开。与GKE不同的是,不需要输入任何凭证来访问它。

2.4 本章小结

希望这个初始实践章节已经向你展示了Kubernetes并不是很难上手的复杂平台,希望你已经准备好深入学习关于它的所有知识。读完这一章,你应该知道如何:

  • 拉取并且运行任何公开的镜像。
  • 把应用打包成容器镜像,并且推送到远端的公开镜像仓库让大家都可以使用。
  • 进入运行中的容器并检查运行环境。
  • 在GKE上创建一个多节点的K8s集群。
  • 为kubectl命令行工具设置别名和tab补全。
  • 在Kubernetes集群中列出查看节点、pod、服务和ReplicationController。
  • 在Kubernetes中运行容器并可以在集群外访问。
  • 了解pod、ReplicationController和服务是关联的基础场景。
  • 通过改变ReplicationController的复本数对应用进行水平伸缩。
  • 在Minikube和GKE中访问基于web的 Kubernetes dashboard 。
本文含有隐藏内容,请 开通VIP 后查看