本系列为一个k8s的实战教程系列,主要是看Kubernetes in Action这本书总结整理而来。这一篇为基础实战篇,从构建一个Docker的容器开始,再到k8s运行pod并暴露服务,还有水平伸缩应用的实战。
Docker 部分
创建一个简单的Node.js应用
创建app.js 和Dockerfile
创建一个名为app.js的文件,代码如下,在访问时会返回自己的主机名而不是宿主机名,这样在水平伸缩时可以看到http请求会切换到不同的实例上。
1 | const http = require('http'); |
再创建Dockerfile文件,如下
1 | FROM node:7 |
From行定义了镜像的起始内容(构建所基于的基础镜像)。这个例子中使用的是node镜像的tag7版本。第二行中把app.js文件从本地文件夹添加到镜像的根目录,保持app.js这个文件名。最后一行定义了当镜像被运行时需要被执行的命令,这个例子中,命令是node app. js
构建容器镜像
1 | docker build -t kubia . |
下图是镜像构建的过程。用户告诉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 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 | docker tag kubia 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 rc
和 kubectl 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来做水平伸缩。