k8s实战(二): kubernetes的基本使用

​ 本系列为一个k8s的实战教程系列,主要是看Kubernetes in Action这本书总结整理而来。这一篇为基础实战篇,从构建一个Docker的容器开始,再到k8s运行pod并暴露服务,还有水平伸缩应用的实战。

Docker 部分

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

创建app.js 和Dockerfile

创建一个名为app.js的文件,代码如下,在访问时会返回自己的主机名而不是宿主机名,这样在水平伸缩时可以看到http请求会切换到不同的实例上。

1
2
3
4
5
6
7
8
9
10
11
12
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);

再创建Dockerfile文件,如下

1
2
3
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

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

构建容器镜像

1
docker build -t kubia .

docker build

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

构建过程

镜像是如何构建的

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

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

镜像分层

​ 镜像不是一个大的二进制块,而是由多层组成的,不同镜像可能会共享分层,这会让存储和传输变得更加高效。比如, 如果创建了多个基于相同基础镜像(比如node:7),所有组成基础镜像的分层只会被存储一次。拉取镜像时Docker会独立下载每一层。一些分层可能已经存储在机器上了,Docker只会下载未被存储的分层。Dockerfile中每一条单独的指令都会创建一个新层。

运行容器镜像

可以使用docker images 列出本地的镜像。如下:

REPOSITORY TAG IMAGE ID CREATED SIZE
kubia latest 65fa98888038 10 minutes ago 660MB

运行镜像

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

然后可以使用curl来访问测试:curl localhost:8080

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

docker run

列出容器

​ 列出运行中的容器可以使用 docker ps 命令,所有的容器可以使用docker ps -a

获取更多容器信息

​ docker ps 只会展示容器的大部分基础信息,使用 docker inspect ${containerName} 来查看更多的信息,如docker inspect kubia-container

探索容器内部

已有容器内部运行shell

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

1
docker exec -it kubia-container bash

-i 表示保持开放标准输入流,这样才能在shell中输入命令。

-t 表示分配一个伪TTY终端。

可以看到只有三个进程,宿主机上无其他进程。

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

​ 如果现在打开另一个终端,然后列出主机操作系统上的进程,连同其他的主机进程依然会发现容器内的进程。注意: 如果是Mac或者Windows系统的话需要登录到Docker守护进程运行的VM查看这些进程。 我这里就不演示了。

​ 这证明了运行在容器中的进程是运行在主机操作系统上的。同时进程的ID在容器中与主机上不同。容器使用独立的PID Linux命名空间并且有着独立的系列号,完全独立于进程树。

容器文件系统也是独立的

​ 正如拥有独立的进程树一样,每个容器也拥有独立的文件系统。在容器内列出根目录的内容,只会展示容器内的文件,包括镜像内的所有文件,再加上容器运行时创建的任何文件(类似日志文件)。执行ls 可以看到目录结果。

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

停止和删除容器

docker stop kubia-container来停止容器,停止之后使用docker ps 就看不到容器了,使用docker ps -a可以看到。使用docker rm kubia-container可以删除容器。

向镜像仓库推送镜像

​ 目前构建的镜像只能在本机使用,为了在任何机器上都可以使用,需要把镜像推送到一个外部的镜像仓库。这里直接使用 Docker Hub镜像中心。自行注册完后会有自己的Docker Hub ID。然后推送步骤如下:

1
2
docker tag kubia yourId/kubia
docker push yourId/kubia

​ 先使用附加标签标注镜像,yourId 要替换为自己的Docker Hub ID,之后使用docker push 来推送镜像。

注意: 推送前,确保保持登陆状态,没登录的话使用 docker login命令和自己的ID登录。

​ 之后在其他机器上就可以直接使用如下命令来运行镜像了:docker run -p 8080:8080 -d yourId/kubia

Kubernetes部分

​ 在上一篇文章中,已经安装并启动了minikube,然后再上文docker部分中确保有了node的镜像。下面开始在k8s中部署这个node应用。

部署Node.js应用

使用命令:

1
kubectl run kubia --image=yourId/kubia --port=8080 --generator=run/v1

​ –image=yourId/kubia显示的是指定要运行的容器镜像,–port=8080选项告诉Kubernetes应用正在监昕8080端口。最后一个标志(–generator)需要解释一下,通常并不会使用到它,它让Kubemetes创建一个ReplicationController, 而不是Deployment。

列出Pod

​ 列出pod可以使用 kubectl get pods 来列出,如上图所示, 目前pod还在容器创建的过程中, 还没有运行起来,等过一会再执行 READY 就会变为 1/1 ,说明已经在运行中。

Pod介绍

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

如果需要查看pod更多信息,还可以使用kubectl describe pod命令。

幕后发生的事情

​ 如图, 当运行kubectl命令时,它通过向Kubemetes API服务器发送一个REST HTTP请求,在集群中创建一个新的ReplicationController对象。然后,ReplicationController创建了一个新的pod,调度器将其调度到一个工作节点上。Kubelet看到pod被调度到节点上,就告知Docker从镜像中心中拉取指定的镜像,因为本地没有该镜像。下载镜像后,Docker创建并运行容器。展示另外两个节点是为了显示上下文。它们没有在这个过程中扮演任何角色,因为pod没有调度到它们上面。

访问Web应用

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

创建一个服务对象

要创建服务,需要告知Kubemetes对外暴露之前创建的ReplicationController。执行如下命令:

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

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

列出服务

命令:kubectl get services 或者kubectl get svc 查看创建的服务对象。

​ 可以看到是外部IP显示pending中,因为Kubernetes运行的云基础设施创建负载均衡需要一段时间。负载均衡启动后,应该会显示服务的外部IP地址。

注意: minikube的话这里一直都会是pending,想要看外网ip使用命令

1
minikube service kubia-http

​ 这会自动打开浏览器并访问外部IP和端口,如果不想自动打开浏览器,只获取外部地址的话,可以使用命令minikube service kubia-http --url 来获得地址,之后自己可以使用curl来测试。如下图所示:

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

pod和它的容器

​ 在你的系统中最重要的组件是pod。它只包含一个容器,但是通常一个pod可以包含任意数量的容器。容器内部是Node.js进程,该进程绑定到8080端口, 等待HTTP请求。pod有自己独立的私有IP地址和主机名。

ReplicationController的角色

​ kubia ReplicationController。 它确保始终存在一个运行中的pod实例。 通常,ReplicationController用于复制pod(即创建pod的多个副本)并让它们保持运行。示例中没有指定需要多少pod副本, 所以ReplicationController创建了一个副本。 如果你的pod因为任何原因消失了,那么ReplicationController将创建一个新的pod来替换消失的pod。

为什么需要服务

​ kubia-http服务。 要理解为什么需要服务,需要学习有关pod的关键细节。pod的存在是短暂的,一个pod可能会在任何时候消失,或许因为它所在节点发生故障,或许因为有人删除了pod,或者因为pod被从一个健康的节点剔除了。当其中任何一种情况发生时,如前所述,消失的pod将被Replication Controller替换为新的pod。新的pod与替换它的pod具有不同的IP地址。这就是需要服务的地方解决不断变化的pod IP地址的问题,以及在一个固定的IP和端口对上对外暴露多个pod。

​ 当一个服务被创建时,它会得到一个静态的IP,在服务的生命周期中这个IP不会发生改变。客户端应该通过固定IP地址连接到服务,而不是直接连接pod。服务会确保其中一个pod接收连接,而不关心pod当前运行在哪里(以及它的IP地址是什么)。服务表示一组或多组提供相同服务的pod的静态地址。到达服务IP和端口的请求将被转发到属于该服务的一个容器的IP和端口。

水平伸缩应用

​ pod由一个ReplicationController管理。查看rc,会返回如下格式:

1
kubectl get rc

NAME DESIRED CURRENT READY AGE
kubia 1 1 1 47h

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

增加期望副本数

执行命令:

1
kubectl scale rc kubia --replicas=3

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

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

​ 查看扩容的结果如下:kubectl get rckubectl get po 能看到结果

curl测试请求

​ 结果如下,请求会随机的切换到不同的pod。当pod有多个实例时Kubernetes服务就会这样做。服务作为负载均衡挡在多个pod前面。

curl 192.168.99.100:32369
You’ve hit kubia-tqrvc
curl 192.168.99.100:32369
You’ve hit kubia-q989q
curl 192.168.99.100:32369
You’ve hit kubia-q989q
curl 192.168.99.100:32369
You’ve hit kubia-rctfz

查看应用运行在哪个节点上

​ 容器中运行的所有应用都具有相同类型的操作系统。每个pod都有自己的IP,并且可以与任何其他pod通信,不论其他pod是运行在同一个节点上,还是运行在另一个节点上。每个pod都被分配到所需的计算资源,因此这些资源是由一个节点提供还是由另一个节点提供,并没有任何区别。

列出pod时显示pod IP和pod的节点

1
kubectl get pods -o wide

总结

​ 本文主要是对书第二章的整理,从Docker创建应用应用开始到在k8s中部署并水平扩容应用结束。看完本博客,你应该知道:

  • Docker创建应用并会拉取和向镜像仓库推送镜像。
  • 进入运行中的容器并检查环境(docker exec )。
  • 在Kubernetes中查看pod、svc、rc等操作。
  • 在Kubernetes中运行pod并可以在集群外访问。
  • 通过改变ReplicationController来做水平伸缩。
Author: Chen JK
Link: https://winterck.github.io/2020/04/03/k8s%E5%AE%9E%E6%88%98-%E4%BA%8C-%EF%BC%9A-kubernetes%E7%9A%84%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.
Donate
  • 微信
  • 支付寶

Comment