K8s in Action 阅读笔记——【2】First steps with Docker and Kubernetes
2.1 Creating, running, and sharing a container image
2.1.1 Installing Docker and running a Hello World container
在电脑上安装好Docker环境后,执行如下命令,
$ docker run busybox echo "Hello world"
# Unable to find image 'busybox:latest' locally
# latest: Pulling from library/busybox
# 325d69979d33: Pull complete
# Digest:
# sha256:560af6915bfc8d7630e50e212e08242d37b63bd5c1ccf9bd4acccf116e262d5b
# Status: Downloaded newer image for busybox:latest
# Hello world
这看起来并不是很令人印象深刻,但考虑到整个“应用程序”是一个命令下载并执行的,而不必安装任何应用程序或其他依赖项,这十分不错。在此例中,该应用程序是一个单独的可执行文件(busybox),但也可以是具有许多依赖项的非常复杂的应用程序。设置和运行整个应用程序的过程将完全相同。此外,该应用程序在容器内运行,与计算机上运行的其他进程完全隔离。
UNDERSTANDING WHAT HAPPENS BEHIND THE SCENES
图2.1展示了当你执行docker run命令时发生的情况。首先,Docker检查本地计算机上是否已经存在busybox:latest镜像。由于它不存在,Docker从http://docker.io的Docker Hub镜像库中拉取该镜像。在镜像下载到你的机器之后,Docker从该镜像创建一个容器,并在其中运行命令。echo命令将文本打印到STDOUT,然后该进程终止,容器停止运行。
RUNNING OTHER IMAGES
运行其他已存在的容器镜像和你运行busybox镜像一样简单。实际上,它通常更加简单,因为通常你无需像示例中那样指定要执行的命令(echo “Hello world”)。应该执行的命令通常已经嵌入到镜像本身中,但如果你想的话,也可以进行覆盖。在浏览公共可用镜像的http://hub.docker.com或其他公共仓库之后,你可以像这样告诉Docker运行该镜像:
$ docker run <image>
VERSIONING CONTAINER IMAGES
Docker支持在同一名称下拥有同一个镜像的多个版本或变量。每个变体必须具有唯一的标签。当引用镜像时没有明确指定标签,Docker将会假定你正在引用所谓的最新标签。要运行不同版本的镜像,你可以像下面这样在镜像名称中指定标签:
$ docker run <image>:<tag>
2.1.2 Creating a trivial Node.js app
接下来构建一个简单的Node.js Web 应用程序,并将其打包成一个容器镜像。该应用程序将接受HTTP请求并响应运行它的机器的主机名。这样,你将看到在容器内运行的应用程序能够看到自己的主机名,而不是宿主机的主机名,即使它像其他进程一样在主机上运行。当你在Kubernetes上部署并缩放该应用程序时(横向扩展;即运行多个应用程序实例),这将非常有用。你将看到你的HTTP请求命中应用程序的不同实例。
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服务器。服务器回应HTTP响应状态码200 OK,并在每个请求中返回文本“ You’ve hit ”。请求处理程序还将客户端的IP地址记录到标准输出,此后会需要这个记录。
2.1.3 Creating a Dockerfile for the image
要将你的应用程序封装成一个镜像,你首先需要创建一个名为 Dockerfile 的文件,它将包含 Docker 在构建镜像时执行的一系列操作指令。Dockerfile 需要在与 app.js 文件相同的文件夹中,并且应包含以下代码清单中的命令。
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
FROM 行定义了你将用作起点的容器镜像(你所构建的基础镜像)。在这种情况下,你使用了 node 容器镜像,标签为 7。在第二行中,你将本地目录中的 app.js 文件添加到镜像的根目录下,名称相同(app.js)。最后,在第三行中,你定义了当有人运行镜像时应执行的命令。在你的例子中,命令是 node app.js
。
2.1.4 Building the container image
现在,你已经拥有了 Dockerfile 和 app.js 文件,你拥有了构建镜像所需的一切。要构建它,请运行以下 Docker 命令:
$ docker build -t kubia .
图2.2展示了构建过程中发生了什么。你告诉 Docker 基于当前目录的内容构建一个名为 kubia 的镜像(请注意构建命令末尾的句点)。Docker 将在目录中查找 Dockerfile,并根据文件中的指令构建镜像。
UNDERSTANDING HOW AN IMAGE IS BUILT
构建过程不是由Docker客户端执行的。相反,整个目录的内容都被上传到Docker守护进程中并在那里构建镜像。客户端和守护进程不需要在同一台机器上。如果您在非Linux操作系统上使用Docker,客户端位于主机操作系统上,但守护进程在虚拟机内运行。由于构建目录中的所有文件都被上传到守护进程中,如果包含许多大文件并且守护进程未在本地运行,则上传可能需要更长时间。
不要在构建目录中包含任何不必要的文件,因为它们会拖慢构建过程,特别是当Docker守护进程在远程计算机上运行时。
UNDERSTANDING IMAGE LAYERS
镜像不是一个单一的大二进制块,而是由多个层组成的。不同的镜像可能共享多个层,这使得存储和传输镜像更加高效。例如,如果您基于相同的基础镜像(如示例中的node:7)创建多个镜像,则所有层组成的基础镜像只会被存储一次。此外,在拉取镜像时,Docker将逐个下载每个层。因为你的计算机上可能已经存储了多个图层,所以Docker只会下载那些不包含的层。
你可能认为每个Dockerfile只创建一个新的层,但实际情况并非如此。在构建镜像时,每个Dockerfile中的每个单独命令都会创建一个新的层。在构建图像期间,在拉取基础镜像的所有层之后,Docker会在它们的顶部创建一个新的层,并将app.js文件添加到其中。然后,它将创建另一个层,指定当运行镜像时应执行的命令。最后一个层将被标记为kubia:latest。这在图2.3 中显示,图中还显示了一个名为other:latest的不同镜像如何使用与自己的镜像相同的Node.js镜像的层。
可以查看本地计算机上有哪些镜像
$ docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# kubia latest 30b8a5def3f5 21 minutes ago 660MB
# busybox latest 8135583d97fe 42 hours ago 4.86MB
2.1.5 Running the container image
运行已经构建的镜像
$ docker run --name kubia-container -p 8080:8080 -d kubia
# 7d6e488c7431a1e690eb99d48d40a0b330f8d16de7a941c84e8d0d4ee241cedc
这条命令告诉 Docker 从 kubia镜像中运行一个名为 kubia-container 的新容器。容器将与控制台分离(-d标志),这意味着它将在后台运行。本地机器上的8080端口将映射到容器内部的8080端口(-p 8080:8080选项),这样你就可以通过 http://localhost:8080 访问应用程序。
yjq@DESKTOP-NKBDBDM:~$ curl http://localhost:8080/
# You've hit 7d6e488c7431
这是你应用程序的响应。你的小型应用程序现在在容器内运行,并与其他所有内容隔离开来。正如你所看到的,它返回的主机名是 7d6e488c7431,而不是你主机的实际主机名。这个十六进制数是 Docker 容器的 ID。
可以查看已经运行的容器的信息:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7d6e488c7431 kubia "node app.js" 3 minutes ago Up 3 minutes 0.0.0.0:8080->8080/tcp kubia-container
docker ps
命令仅显示有关容器的最基本信息。要查看附加信息,可以使用 docker inspect
:
这会输出一系列json信息。
2.1.6 Exploring the inside of a running container
上文使用的Node.js镜像包含 bash shell,因此你可以像这样在容器内运行 shell:
$ docker exec -it kubia-container bash
这会在已有的 kubia-container 容器中运行 bash。bash 这个进程将拥有与主容器进程相同的 Linux 命名空间,这样就可以从容器内部探索。
-it
选项代表两个选项的缩写:
-i
,确保 STDIN 保持打开状态,因为这样才能输入 shell 命令;-t
,则配一个伪终端(TTY)。
如果想像平常一样使用 shell,那么两者都是必需的。(如果将第一个选项省略,则无法输入任何命令;如果将第二个选项省略,命令提示符将无法显示,并且有些命令会提示 TERM 变量未设置。)
$ docker exec -it kubia-container bash
root@7d6e488c7431:/# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.2 0.1 614436 25316 ? Ssl 02:57 0:00 node app.js
root 18 0.2 0.0 20248 3188 pts/0 Ss 02:57 0:00 bash
root 24 0.0 0.0 17504 2048 pts/0 R+ 02:57 0:00 ps aux
容器内部进程的 ID 和主机上不一样。容器使用自己的 PID Linux 命名空间,并具有完全隔离的进程树,有自己的数字序列。
就像具有独立的进程树一样,每个容器也具有独立的文件系统。列出容器内部根目录的内容将只显示容器中的文件,其中包括镜像中的所有文件和容器运行时创建的任何文件(如日志文件和类似文件),如下面的列表所示。
root@7d6e488c7431:/# ls
app.js bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
该文件夹包含了 app.js 文件和其他系统目录,这些都是正在使用的 node:7 基础镜像的一部分。要退出容器,需要运行 exit 命令退出 shell,并返回到主机机器。
root@7d6e488c7431:/# exit
exit
2.1.7 Stopping and removing a container
停止一个容器:
$ docker stop kubia-container
kubia-container
这将停止容器中运行的主进程,进而停止容器,因为容器内没有其他进程正在运行。容器本身仍然存在,可以使用 docker ps -a
命令查看它。-a 选项打印出所有容器,包括正在运行和已停止的容器。要真正删除一个容器,需要使用 docker rm
命令进行删除。
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7d6e488c7431 kubia "node app.js" 13 hours ago Exited (137) About a minute ago kubia-container
docker rm kubia-container
2.1.8 Pushing the image to an image registry
你构建的镜像目前只能在本地机器上使用。为了让你在任何其他机器上运行它,你需要将该镜像推送到外部镜像注册表中可以将镜像推送到 Docker Hub(http://hub.docker.com),这是一个公共可用的镜像仓库之一。其他广泛使用的镜像仓库包括 Quay.io 和 Google Container Registry。
先将镜像打上自己仓库的tag:docker tag kubia yijunquan/kubia
这不是对标签进行重命名,而是为同一镜像创建了一个附加标签。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
kubia latest 30b8a5def3f5 13 hours ago 660MB
yijunquan/kubia latest 30b8a5def3f5 13 hours ago 660MB
kubia和luksa/kubia都指向相同的镜像ID,因此它们实际上是具有两个标签的同一个镜像。
使用docker login
命令登录账号后,可以使用如下命令进行推送:
$ docker push yijunquan/kubia
Using default tag: latest
The push refers to repository [docker.io/yijunquan/kubia]
8c6ceb8d582a: Pushed
ab90d83fa34a: Mounted from library/node
8ee318e54723: Mounted from library/node
e6695624484e: Mounted from library/node
da59b99bbd3b: Mounted from library/node
5616a6292c16: Mounted from library/node
f3ed6cb59ab0: Mounted from library/node
654f45ecb7e3: Mounted from library/node
2c40c66f7667: Mounted from library/node
latest: digest: sha256:bf61996e401e52a9b054844ee7026c2d27c40ba33b7eb5bee2ae8f9cd1118419 size: 2213
在将镜像推送到 Docker Hub 后,该镜像将可供所有人使用。
2.2 Setting up a Kubernetes cluster
可以参考如下教程
https://blog.csdn.net/Lingoesforstudy/article/details/122554937(ubuntu18)
https://www.cnblogs.com/renshengdezheli/p/16686769.html(centos7)
2.3 Running your first app on Kubernetes
2.3.1 Deploying your Node.js app
部署你的应用程序最简单的方法是使用kubectl run命令,它将创建所有必要的组件,而无需处理JSON或YAML。尝试运行之前创建并推送到Docker Hub的镜像:
root@yjq-k8s1:~# kubectl run kubia --image=yijunquan/kubia --port=8080 -n k8s-in-action
pod/kubia created
INTRODUCING PODS
Kubernetes并不是直接处理单个容器。相反,它使用多个共同存在的容器,这组容器称为Pod。
Pod是一组一个或多个紧密相关的容器,它们将始终在同一工作节点和相同的Linux命名空间中一起运行。每个Pod就像一个独立的逻辑机器,具有自己的IP、主机名、进程等,运行一个单一的应用程序。该应用程序可以是一个单独的进程,运行在一个单独的容器中,也可以是一个主应用程序进程和其他支持进程,每个进程都在它自己的容器中运行。Pod中的所有容器看起来都在同一个逻辑机器上运行,而其他Pod中的容器,即使它们在同一工作节点上运行,也看起来是在不同的机器上运行。
由下图可知,每个Pod都有自己的IP,并包含一个或多个容器,每个容器运行着一个应用程序进程。Pod分布在不同的工作节点上。
LISTING PODS
列出刚才所创建的Pod:
root@yjq-k8s1:~# kubectl get pod -n k8s-in-action
NAME READY STATUS RESTARTS AGE
kubia 1/1 Running 0 6m45s
要查看有关Pod的更多信息,你也可以使用kubectl describe pod
命令。
UNDERSTANDING WHAT HAPPENED BEHIND THE SCENES
为了更好地理解在 Kubernetes 中运行容器镜像的步骤,请看图2.6。其中显示了执行此操作的两个步骤。首先,构建镜像并将其推送到 Docker Hub 是必需的,因为只有在本地构建的镜像只能在本地机器上使用,但是你需要使其可以被运行在工作节点上的 Docker 守护程序访问。当运行 kubectl 命令时,它向 Kubernetes API 服务器发送 REST HTTP请求以在集群中创建一个新的 ReplicationController 对象。然后,ReplicationController 创建一个新的 Pod,Scheduler 将其调度到工作节点之一上。该节点上的 Kubelet 发现 Pod已被调度到该节点上并指示 Docker 从注册表中拉取所需的镜像,因为该镜像在本地不可用。等镜像下载完成后,Docker创建并运行容器。其他两个节点的显示仅是为了提供上下文,它们并不在过程中扮演任何角色,因为Pod未被调度到它们上面。