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

运行容器 P24

运行 P24

可以运行 Docker 客户端可执行文件来执行各种 Docker 命令。例如:可以试着从 Docker Hub 的公共镜像仓库拉取、运行镜像。 Docker Hub 中有许多随时可用的常见镜像,其中就包括 busybox ,可以用来运行简单的命令,例如: echo "Hello world"P24

docker run busybox echo "Hello world"

原理 P25

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

执行 docker run 命令后: P25

  1. Docker 检查 busybox:lastest 镜像是否存在于本机。如果不存在,则会自动从 Docker 镜像中心拉取镜像
  2. Docker 基于 busybox:lastest 镜像创建一个容器并在容器中运行命令 echo "Hello world"

运行镜像 P25

docker run <image>
docker run <image>:<tag>

Docker 支持同一镜像的多个版本,每个版本必须有唯一的 tag 名,当引用镜像没有显式地指定 tag 时, Docker 会默认指定 tag 为 latestP25

创建应用 P26
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);

这个应用会接收 HTTP 请求并响应应用运行的服务器真实主机名(非宿主机名),当其部署在 Kubernetes 上并进行伸缩时(水平伸缩,复制应用到多个节点),可以发现 HTTP 请求切换到了应用的不同实例上。 P26

创建 Dockerfile P27
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
  • FROM 行定义了镜像的起始内容(构建所基于的基础镜像) P27
  • ADD 行把 app.js 文件从本地添加到镜像的根目录,并保持文件名为 app.js P27
  • ENTRYPOINT 行定义了当镜像被运行时需要执行的命令 P27
构建容器镜像 P27
docker build -t kubia .

上述命令会基于当前目录下的 Dockerfile 文件中的指令创建一个名为 kubia 的镜像。 P27

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

镜像是如何构建的 P28

构建过程不是由 Docker 客户端运行的,而是将整个目录的文件上传到 Docker 守护进程并在那里进行的。如果在一台非 Linux 操作系统中使用 Docker ,客户端运行在宿主机操作系统上,而守护进程运行在一个虚拟机内。 P28

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

镜像分层 P28

镜像不是一个大的二进制块,而是由许多层组成的,不同镜像可能会共享分层,这样会让存储和传输变得更加高效。 P28

构建镜像时, Dockerfile 中每一条单独的指令都会创建一个新层,最后一层会被标记为指定的镜像名。 P29

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

可以通过 docker images 查看本地存储的镜像。

运行容器镜像 P30
docker run --name kubia-container -p 8080:8080 -d kubia

上述命令会基于 kubia 镜像创建一个叫 kubia-container 的新容器。 -d 表示这个容器与命令行分离,意味着在后台运行。 -p 8080:8080 表示本机上的 8080 端口会映射到容器内的 8080 端口,所以可以通过 localhost:8080 访问这个应用。 P30

列出所有运行中的容器 P30

docker ps 会打印出每一个容器的 ID 和名称、容器运行所使用的镜像、容器中执行代码命令、创建时间、以及当前状态。 P30

  • -a: 查看所有容器,包括运行中的和已停止的

获取更多的容器信息 P30

docker inspect kubia-container 可以查看包含容器底层信息的长 JSON 。 P31

探索运行容器的内部 P31

在已有的容器内部运行 shell P31

docker exec -ti kubia-container bash

上述命令会在已有的 kubia-container 容器内部运行 bash 。 bash 进程会和主容器进程拥有相同的命名空间,可以用于查看 Node.js 和应用是如何在容器里运行的。 P31

  • -t: 分配一个伪终端 (TTY) ,可以显示命令提示符
  • -i: 确保标准输入流保持开放,可以在 shell 中输入命令

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

停止和删除容器 P32
  • docker stop kubia-container: 停止容器,容器停止后仍然存在 P32
  • docker rm kubia-container: 删除容器,所有内容都会被删除并且无法再次启动 P32
向镜像仓库推送镜像 P33

使用附加标签标注镜像 P33

docker tag kubia idealism/kubia

上述命令不会重命名标签,而是给同一个镜像创建一个额外的标签。 kubiaidealism/kubia 指向同一个镜像 ID ,实际上是同一个镜像的两个标签。 P33

推送镜像 P33

docker push idealism/kubia 可以将本地的镜像推送到 idealism 名下的 kubia

在不同机器上运行镜像 P33

推送完成之后,就可以通过 docker run --name kubia-container -p 8080:8080 -d kubia 在任何机器上运行同一个镜像了。

配置 Kubernetes 集群 P34

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

Minikube 是一个构建单节点集群的工具,对于测试 Kubernetes 和本地开发应用都非常有用。 P35

  • minikube start --nodes 4: 启动一个四节点的 Kubernetes 集群,包含一个主节点和三个工作节点。
  • kuibectl cluster-info: 展示集群信息
  • minikube ssh: 登录到 Minikube VM 从内部访问,可以查看在节点上运行的进程
  • kubectl get nodes: 列出集群的节点
  • kubectl describe node minikube: 查看指定节点的更多信息

使用 kubectl get nodes 查看当前集群的节点信息如下:

NAME           STATUS   ROLES    AGE   VERSION
minikube       Ready    master   56m   v1.18.2
minikube-m02   Ready    <none>   13m   v1.18.2
minikube-m03   Ready    <none>   11m   v1.18.2
minikube-m04   Ready    <none>   10m   v1.18.2

可以发现有三个节点的角色是 <none> ,需要手动将它们的 ROLES 设置为 worker ,运行如下命令即可:

kubectl label node minikube-m02 node-role.kubernetes.io/worker=worker
kubectl label node minikube-m03 node-role.kubernetes.io/worker=worker
kubectl label node minikube-m04 node-role.kubernetes.io/worker=worker

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

在 Kubernetes 上运行第一个应用 P40

部署 Node.js 应用 P40
kubectl create deployment kubia --image=idealism/kubia

上述命令无须配置文件即可创建一个名为 kubia 的 development (可以使用 kubectl get developments 查看),同时自动创建一个名称前缀为 kubia 的 pod (可以使用 kubectl get pods 查看)。 P40

介绍 pod P41

Kubernetes 不直接处理单个容器,它使用多个共存容器的理念,这组容器叫作 pod 。 P41

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

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

列出 pod P41

  • kubectl get pods: 查看 pod 的重要信息: pod 名称、 READY 、状态、重启次数和 AGE
  • kubectl describe pod <pod-name>: 查看指定 pod 的详细信息

幕后发生的事情 P42

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

上图显示了在 Kubernetes 中运行容器镜像所必须的两个步骤: P42

  1. 构建镜像并将其推送到 Docker Hub
  2. 运行 kubectl 命令
    1. 命令向 Kubernetes API 服务器发送一个 REST HTTP 请求
    2. 创建一个新的 pod ,调度器将其调度到一个工作节点上
    3. Kubelet 看到 pod 被调度到节点上,就告知 Docker 从镜像中心中拉取指定的镜像,因为本地没有该镜像
    4. 下载镜像后, Docker 创建并运行容器

调度 (scheduling): 将 pod 分配给一个节点。 pod 会立即执行,而不是将要执行。 P43

访问 Web 应用 P43

通过 kubectl describe pod <pod-name> 可以看到 pod 自己的 IP ,不过这个地址只能从集群内部访问。通过创建一个 LoadBalancer 类型的服务,可以创建一个外部的负载均衡,通过负载均衡的公共 IP 就可以访问 pod 。 P43

创建一个服务对象 P43

kubectl expose deployment kubia --type=LoadBalancer --name kubia-http --port=8080: 对外暴露之前创建的 Pod P43

大多数资源都有缩写: P43

  • po: pod 的缩写
  • svc: service 的缩写
  • rc: replicationcontroller 的缩写 (目前已 不推荐使用该控制器
  • deploy: deployment 的缩写
  • rs: replicaset 的缩写

由于我们使用的是 minikube ,所以没有集成 LoadBalancer ,运行完上述命令后,使用 kubectl get services 会得到如下结果:

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          37m
kubia-http   LoadBalancer   10.100.74.254  <pending>     8080:32342/TCP   5m52s

可以发现 LoadBalancerEXTERNAL-IP 一直处于 <pending>,我们可以使用 minikube 自带的 minikube tunnel 命令可以完成暴露,运行完后,可得如下结果:

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)          AGE
kubernetes   ClusterIP      10.96.0.1      <none>         443/TCP          39m
kubia-http   LoadBalancer   10.100.74.254  10.100.74.254  8080:32342/TCP   7m56s

列出服务 P44

expose 命令的输出是一个名为 kubia-http 的服务。服务是类似于 pod 和 node 的对象,因此可以通过 kubectl get services 命令查看新创建的服务对象。

使用外部 IP 访问服务 P44

curl 10.100.74.254:8080
# 输出如下:
# You've hit kubia-5b6b94f7f8-h6dbr

可以发现应用将 pod 名称作为它的主机名。 P45

使用 minikube 为集群添加节点可能会导致新增的节点无法访问(在这里耗时几小时终于搞明白了,还是要多读文档 ),可以按照文档中进行设置;当然也有可能是等待的时间不够,设置等操作还没有结束(按照前面的配置了后,还是无法访问,以为哪里操作有问题,各种操作顺序都试了还是无法访问,最后等待了近 10 min 就自动好了。。。)

系统的逻辑部分 P45

Deployment 、 pod 和服务是符合组合在一起的 P45

Kubernetes 的基本构建是 pod 。但是,我们前面并没有真的直接创建出任何 pod 。通过运行 kubectl create deployment 命令,创建了一个 Deployment 控制器,它用于创建 pod 实例。为了使该 pod 能够从集群外部访问,需要让 Kubernetes 将该 Deployment 管理的所有 pod 由一个服务对外暴露。 P45

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

pod 和它的容器 P45

系统中最重要的组件是 pod ,一个 pod 中可以包含任意数量的容器。 pod 有自己独立的私有 IP 地址和主机名。 P46

Deployment 的角色 P46

Deployment 控制器确保始终存在一个运行中的 pod 实例。通常, Deployment 用于复制 pod (即创建 pod 的多个副本)并让它们保持运行。如果 pod 因为任何原因消失了,那么 Deployment 将创建一个新的 pod 来替换消失的 pod 。 P46

为什么需要服务 P46

系统的第三个组件是 kubia-http 服务。 pod 的存在是短暂的,一个 pod 可能会在任何时候消失(1. 所在节点发生故障; 2. 有人删除了 pod ; 3. pod 被从一个健康的节点剔除了),此时消失的 pod 将被 Deployment 替换为新的 pod 。新旧 pod 具有不同的 IP 地址。所以需要一个服务来解决不断变化的 pod IP 地址的问题,以及在一个固定的 IP 和端口对上对外暴露多个 pod 。 P46

服务表示一组或多组提供相同服务的 pod 的静态地址。到达服务 IP 和端口的请求将被转发到属于该服务的一个容器的 IP 和端口。 P46

水平伸缩 P46
kubectl get deployments
# 输出如下:
# NAME    READY   UP-TO-DATE   AVAILABLE   AGE
# kubia   1/1     1            1           49m

使用 kubectl get 可以查看所有类型的资源,上述命令查看所有的 Deployment ,目前仅有一个名为 kubia 的控制器,总共需要 1 个 pod ,目前已有 1 个 pod 已准备好, 1 个 pod 已是最新版本, 1 个 pod 可用。

增加期望的副本数 P47

kubectl scale deployment kubia --replicas=3
# 输出如下:
# deployment.apps/kubia scaled

上述命令告诉 Kubernetes 需要确保 pod 始终有三个实例在运行。这是 Kubernetes 最基本的原则之一。不告诉 Kubernetes 应该执行什么操作,而是声明性地改变系统的期望状态,并让 Kubernetes 检查当前的状态是否与期望的状态一致。 P47

打到服务上到请求会打到所有到三个 pod 上 P47

curl 10.100.74.254:8080
# 由于存在三个 pod ,所以有以下三种输出
# You've hit kubia-5b6b94f7f8-h6dbr
# You've hit kubia-5b6b94f7f8-wwbdh
# You've hit kubia-5b6b94f7f8-zzxks

当 pod 有多个实例时 Kubernetes 服务作为负载均衡挡在多个 pod 前面,请求随机地打到不同的 pod 。 P48

Kubernetes 实战 —— 02. 开始使用 Kubernetes 和 Docker-LMLPHP

查看应用运行在哪个节点上 P49
kubectl get pods -o wide
# 输出如下:
# NAME                     READY   STATUS    RESTARTS   AGE   IP           NODE           ...
# kubia-5b6b94f7f8-h6dbr   1/1     Running   0          12m   10.244.3.2   minikube-m04   ...
# kubia-5b6b94f7f8-wwbdh   1/1     Running   0          12m   10.244.2.2   minikube-m03   ...
# kubia-5b6b94f7f8-zzxks   1/1     Running   0          55m   10.244.1.2   minikube-m02   ...

-o wide: 显示 pod 的 IP 和所运行的节点,以及其他部分信息。 P49

介绍 Kubernetes dashboard P50

访问 Minikube 的 dashboard P51

minikube dashboard

以上命令会启动 dashboard ,并输出可访问的网址供浏览器中打开。 P51

实际操作时,发现这个命令会一直卡在 Verifying proxy health ... 这一行,随后会返回 503 错误,目前 github 上已有类似的 issue 。我按照讨论中的方法发现, kubernetes-dashboard namespace 下的两个 pod 都成功启动了,并且可以通过 kubectl port-forward -n kubernetes-dashboard pod/kubernetes-dashboard-696dbcc666-gbgwl 9090:9090 将端口映射到本地并能成功访问。

03-07 21:07