创建、运行及共享容器镜像 P23
运行容器 P24
运行 P24
可以运行 Docker 客户端可执行文件来执行各种 Docker 命令。例如:可以试着从 Docker Hub 的公共镜像仓库拉取、运行镜像。 Docker Hub 中有许多随时可用的常见镜像,其中就包括 busybox
,可以用来运行简单的命令,例如: echo "Hello world"
。 P24
docker run busybox echo "Hello world"
原理 P25
执行 docker run
命令后: P25
- Docker 检查
busybox:lastest
镜像是否存在于本机。如果不存在,则会自动从 Docker 镜像中心拉取镜像 - Docker 基于
busybox:lastest
镜像创建一个容器并在容器中运行命令echo "Hello world"
运行镜像 P25
docker run
docker run :
Docker 支持同一镜像的多个版本,每个版本必须有唯一的 tag 名,当引用镜像没有显式地指定 tag 时, Docker 会默认指定 tag 为 latest
。 P25
创建应用 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.jsP27
-
ENTRYPOINT
行定义了当镜像被运行时需要执行的命令P27
构建容器镜像 P27
docker build -t kubia .
上述命令会基于当前目录下的 Dockerfile 文件中的指令创建一个名为 kubia
的镜像。 P27
镜像是如何构建的 P28
构建过程不是由 Docker 客户端运行的,而是将整个目录的文件上传到 Docker 守护进程并在那里进行的。如果在一台非 Linux 操作系统中使用 Docker ,客户端运行在宿主机操作系统上,而守护进程运行在一个虚拟机内。 P28
提示: 不要在构建目录中包含任何不需要的文件,这样会减慢构建的速度,尤其是 Docker 守护进程运行在一个远端机器的时候。 P28
镜像分层 P28
镜像不是一个大的二进制块,而是由许多层组成的,不同镜像可能会共享分层,这样会让存储和传输变得更加高效。 P28
构建镜像时, Dockerfile 中每一条单独的指令都会创建一个新层,最后一层会被标记为指定的镜像名。 P29
可以通过 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
上述命令不会重命名标签,而是给同一个镜像创建一个额外的标签。 kubia
和 idealism/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 13m v1.18.2
minikube-m03 Ready 11m v1.18.2
minikube-m04 Ready 10m v1.18.2
可以发现有三个节点的角色是
,需要手动将它们的 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 上运行第一个应用 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
列出 pod P41
-
kubectl get pods
: 查看 pod 的重要信息: pod 名称、 READY 、状态、重启次数和 AGE -
kubectl describe pod
: 查看指定 pod 的详细信息
幕后发生的事情 P42
上图显示了在 Kubernetes 中运行容器镜像所必须的两个步骤: P42
- 构建镜像并将其推送到 Docker Hub
- 运行 kubectl 命令
- 命令向 Kubernetes API 服务器发送一个 REST HTTP 请求
- 创建一个新的 pod ,调度器将其调度到一个工作节点上
- Kubelet 看到 pod 被调度到节点上,就告知 Docker 从镜像中心中拉取指定的镜像,因为本地没有该镜像
- 下载镜像后, Docker 创建并运行容器
调度 (scheduling): 将 pod 分配给一个节点。 pod 会立即执行,而不是将要执行。 P43
访问 Web 应用 P43
通过 kubectl describe pod
可以看到 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 443/TCP 37m
kubia-http LoadBalancer 10.100.74.254 8080:32342/TCP 5m52s
可以发现 LoadBalancer
的 EXTERNAL-IP
一直处于
,我们可以使用 minikube 自带的 minikube tunnel
命令可以完成暴露,运行完后,可得如下结果:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 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
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
查看应用运行在哪个节点上 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
将端口映射到本地并能成功访问。