从Docker到Kubernetes
1 简介
1.1 什么是容器
传统的虚拟化技术,比如 VMWare, 目标是创建完整的虚拟机。为了运行应用,除了部署应用本身及其依赖(通常几十MB),还得安装整个操作系统(几十GB)。
如图所示,由于所有的容器共享同一个 Host OS,这使得容器在体积上要比虚拟机小很多。另外,启动容器不需要启动整个操作系统,所以容器部署和启动速度更快,开销更小,也更容易迁移。
1.2 为什么需要容器
简略回答是:容器使软件具备了超强的可移植能力。
如今的系统在架构上较十年前已经变得非常复杂了。开发人员通常使用多种服务(比如 MQ,Cache,DB)构建和组装应用,而且应用很可能会部署到不同的环境,比如虚拟服务器,私有云和公有云。
一方面应用包含多种服务,这些服务有自己所依赖的库和软件包;另一方面存在多种部署环境,服务在运行时可能需要动态迁移到不同的环境中。这就产生了一个问题:如何让每种服务能够在所有的部署环境中顺利运行?
Docker 将集装箱思想,运用到软件打包上,为代码提供了一个基于容器的标准化运输系统。Docker 可以将任何应用及其依赖打包成一个轻量级、可移植、自包含的容器。容器可以运行在几乎所有的操作系统上。
容器意味着环境隔离和可重复性。开发人员只需为应用创建一次运行环境,然后打包成容器便可在其他机器上运行。另外,容器环境与所在的 Host 环境是隔离的,就像虚拟机一样,但更快更简单。
1.3 容器是如何工作的
Docker 的核心组件包括:
-
Docker 客户端 - Client
-
Docker 服务器 - Docker daemon
-
Docker 镜像 - Image
-
Registry 镜像仓库
-
Docker 容器 - Container
Docker 架构如下图所示:
Docker 采用的是 Client/Server 架构。客户端向服务器发送请求,服务器负责构建、运行和分发容器。客户端和服务器可以运行在同一个 Host 上,客户端也可以通过 socket 或 REST API 与远程的服务器通信。
1.4 案例:安装docker
1、windows 10+:
-
安装Docker Desktop
-
安装wsl_update (WSL2 Linux内核更新包)
-
重启
2、Linux:
curl -fsSL https://get.docker.com/ | sh
# daocloud.io 国内镜像
curl -sSL https://get.daocloud.io/docker | sh
如何验证安装完成?
$ docker version
Client:
Cloud integration: v1.0.29
Version: 20.10.17
API version: 1.41
Go version: go1.17.11
Git commit: 100c701
Built: Mon Jun 6 23:09:02 2022
OS/Arch: windows/amd64
Context: default
Experimental: true
Server: Docker Desktop 4.12.0 (85629)
Engine:
Version: 20.10.17
API version: 1.41 (minimum version 1.12)
Go version: go1.17.11
Git commit: a89b842
Built: Mon Jun 6 23:01:23 2022
OS/Arch: linux/amd64
Experimental: false
containerd:
Version: 1.6.8
GitCommit: 9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6
runc:
Version: 1.1.4
GitCommit: v1.1.4-0-g5fd4c4d
docker-init:
Version: 0.19.0
GitCommit: de40ad0
2 镜像
2.1 什么是镜像
可将 Docker 镜像视为只读模板,通过它可以创建 Docker 容器。例如某个镜像可能包含一个 Ubuntu 操作系统、一个用户开发的 Web 应用。
如何查看系统里已经存在的镜像?
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest b18a6628e020 6 weeks ago 67.3MB
ubuntu latest 08d22c0ceb15 7 weeks ago 77.8MB
centos 7 2661b85c5b47 9 months ago 645MB
apache/skywalking-oap-server 6.6.0-es7 60cf77fe2aee 3 years ago 194MB
apache/skywalking-ui 6.6.0 da10535e9797 3 years ago 126MB
elasticsearch 7.5.1 2bd69c322e98 3 years ago 779MB
hello-world latest a115c17f9b48 12 seconds ago 13.3kB
nginx latest 6efc10a0510f 2 weeks ago 142MB
上图列出了本机所有的镜像列表,包含:ubuntu、elasticsearch等。
镜像有多种生成方法:
- 可以从无到有开始创建镜像;
- 也可以下载并使用别人创建好的现成的镜像;
- 还可以在现有镜像上创建新的镜像。
如果需要下载使用别人已经制作好的镜像,可以通过docker pull
拉取:
docker pull alpine
docker pull ubuntu
docker pull apache/skywalking-ui
docker pull busybox
未加域名前缀,默认从docker hub拉取。
2.2 基础镜像
base 镜像有两层含义:
- 不依赖其他镜像,从
scratch
构建 (scratch指的是空镜像,什么也没有)。 - 其他镜像可以之为基础进行扩展。
所以,能称作 base 镜像的通常都是各种 Linux 发行版的 Docker 镜像,比如 Ubuntu, Debian, CentOS 等。
我们以 Ubuntu 为例考察 base 镜像包含哪些内容。下载镜像:
$ docker pull ubuntu
查看镜像并运行:
$ docker images ubuntu
REPOSITORY TAG IMAGE ID CREATED SIZE
ubuntu latest 08d22c0ceb15 7 weeks ago 77.8MB
$ docker run -it ubuntu /bin/bash
root@aca480369011:/# uname -r
5.10.16.3-microsoft-standard-WSL2
root@aca480369011:/# ls /
bin boot dev etc home lib lib32 lib64 libx32 media mnt opt proc root run sbin srv sys tmp usr var
我们发现ubuntu
镜像只有77.8MB!这比直接安装ubuntu
系统小多了。如果加上我们自己的应用程序,例如jar包及环境,大小也就几百M,对于应用程序移植来说简直是方便极了!
现在来看一下这个镜像为什么这么小。
众所周知,Linux 操作系统由内核空间和用户空间组成。如下图所示:
内核空间是 kernel,Linux 刚启动时会加载 bootfs 文件系统,之后 bootfs 会被卸载掉。用户空间的文件系统是 rootfs,包含我们熟悉的 /dev, /proc, /bin 等目录。对于 base 镜像来说,底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。
而对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了。相比其他 Linux 发行版,CentOS 的 rootfs 已经算臃肿的了,alpine 还不到 10MB。
不同 Linux 发行版的区别主要就是 rootfs。所以 Docker 可以同时支持多种 Linux 镜像,模拟出多种操作系统环境。
2.3 常用镜像命令
# 搜索镜像 docker search [-s] IMAGE
docker search ubuntu
# 下载镜像 docker pull [OPTIONS] NAME[:TAG|@DIGEST]
docker pull ubuntu:16.04
# 查看已下载镜像列表 docker images [-a]
docker images
# 给镜像添加标签 docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]
docker tag ubuntu:16.04 ubuntu
# 删除镜像 docker rmi [OPTIONS] IMAGE [IMAGE...]
docker rmi ubuntu:latest
docker rmi 12543ced0f6f
# 导出镜像 docker save [OPTIONS] IMAGE [IMAGE...]
docker save -o ubuntu_latest.tar ubuntu:latest
# 导入镜像
docker load --input ubuntu_latest.tar
docker load < ubuntu_latest.tar
# 从容器生成镜像
docker commit -m "create images" -a "yujc33" 2c74d574293f yujc33/test:v1
3 容器
3.1 运行容器
Docker 容器就是 Docker 镜像的运行实例。(可以理解为java里的对象与Class的关系)
用户可以通过 CLI(docker)或是 API 启动、停止、移动或删除容器。可以这么认为,对于应用软件,镜像是软件生命周期的构建和打包阶段,而容器则是启动和运行阶段。
docker run -d --name getting-started -p 80:80 docker/getting-started
这里面我们运行了一个 Docker 容器,使用了 docker/getting-started
镜像。
-d
参数让容器以后台运行(detached
模式)。
--name
参数将容器命名为getting-started
。省略该参数将随机命名。
-p
参数将主机的 80 端口与容器的 80 端口进行映射,允许通过主机的 IP 地址访问容器的 HTTP 服务。
因此,该命令会启动容器并将其连接到主机的端口80。容器的HTTP服务可以通过主机的 IP地址 访问, 例如 http://127.0.0.1。
下面的命令可以查看所有的容器状态:
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a9f67b3f2b7f docker/getting-started "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:80->80/tcp compassionate_kare
2c1da9b06896 docker/getting-started "/docker-entrypoint.…" 10 days ago Exited (255) 3 minutes ago 0.0.0.0:80->80/tcp exciting_blackwell
-a
代表查看所有状态容器,去掉该参数则是表示仅查看运行中容器。STATUS
是容器状态。
docker logs a9f67b3f2b7f
该命令用于查看容器内的标准输出。a9f67b3f2b7f
是容器ID。
docker exec -it a9f67b3f2b7f /bin/sh
该命令是在已经在运行的容器中执行一个交互式的 shell。-it
标志为执行一个交互式的终端操作,可以通过 shell 和命令行接口进行交互,/bin/sh
是要在容器中执行的命令行 shell。如果需要从容器中退出,则输入exit
即可。
docker exec a9f67b3f2b7f /bin/ls /
该命令是在容器中执行/bin/ls /
命令。这也说明了我们不用进入shell交互界面也可以执行命令。
3.2 常用容器命令
create 创建容器
run 运行容器(相当于create+start)
pause 暂停容器
unpause 取消暂停继续运行容器
stop 发送 SIGTERM 停止容器
kill 发送 SIGKILL 快速停止容器
start 启动容器
restart 重启容器
attach attach 到容器启动进程的终端
exec 在容器中启动新进程,通常使用 "-it" 参数
logs 显示容器启动进程的控制台输出,用 "-f" 持续打印
rm 从磁盘中删除容器
3.3 容器资源限额
下面是对容器进行限额的一些示例:
# 允许该容器最多使用 200M 的内存和 100M 的 swap
docker run -m 200M --memory-swap=300M docker/getting-started
# 设置容器使用 CPU 的权重。如果不指定,默认值为 1024
# container_A 的 cpu share 1024,是 container_B 的两倍。当两个容器都需要 CPU 资源时,container_A 可以得到的 CPU 是 container_B 的两倍。这种按权重分配 CPU 只会发生在 CPU 资源紧张的情况下
docker run --name "container_A" -c 1024 docker/getting-started
docker run --name "container_B" -c 512 docker/getting-started
3.4 容器实现底层技术
容器底层是通过cgroup实现资源限额的,通过 namespace 实现资源隔离。
前面我们看到的 -m
、-c
实际上就是在配置 cgroup。我们可以在宿主机 /sys/fs/cgroup
中找到它。在 /sys/fs/cgroup/cpu/docker
目录中,Linux 会为每个容器创建一个 cgroup 目录,以容器长ID 命名:
目录中包含所有与 cpu 相关的 cgroup 配置,文件 cpu.shares
保存的就是 --cpu-shares
的配置,值为 512。
同样的,/sys/fs/cgroup/memory/docker
和 /sys/fs/cgroup/blkio/docker
中保存的是内存以及 Block IO 的 cgroup 配置。
在每个容器中,我们都可以看到文件系统,网卡等资源,这些资源看上去是容器自己的。Linux 实现这种方式的技术是 namespace。namespace 管理着 host 中全局唯一的资源,并可以让每个容器都觉得只有自己在使用它。换句话说,namespace 实现了容器间资源的隔离。
Linux 使用了六种 namespace,分别对应六种资源:Mount、UTS、IPC、PID、Network 和 User。
- Mount: 让容器看上去拥有整个文件系统。
- UTS: 让容器有自己的 hostname。
- IPC: 让容器拥有自己的共享内存和信号量(semaphore)来实现进程间通信,而不会与 host 和其他容器的 IPC 混在一起。
- PID: 容器在 host 中以进程的形式运行。
- Network: 让容器拥有自己独立的网卡、IP、路由等资源。
- User: 让容器能够管理自己的用户,host 不能看到容器中创建的用户。
4 再探镜像
上文了解了镜像的基本操作,本节进一步了解镜像相关内容。
4.1 Dockerfile
我们可以将镜像的内容和创建步骤描述在一个文本文件中,这个文件被称作 Dockerfile,通过执行 docker build <docker-file>
命令可以构建出 Docker 镜像。
以官方的hello-world
镜像为例,它的Dockerfile文件内容:
# 此镜像是从白手起家,从 0 开始构建。
FROM scratch
# 将文件“hello”二进制文件复制到镜像的根目录。不依赖任何环境
COPY hello /
# 容器启动时,执行 /hello
CMD ["/hello"]
执行docker build -t hello-world ./
就会构建出一个镜像,大小仅为13kb。
需要注意的是,Docker 会缓存已有镜像的镜像层,构建新镜像时,如果某镜像层已经存在,就直接使用,无需重新创建。如果我们希望在构建镜像时不使用缓存,可以在 docker build
命令中加上 --no-cache
参数。
Dockerfile 中每一个指令都会创建一个镜像层,上层是依赖于下层的。无论什么时候,只要某一层发生变化,其上面所有层的缓存都会失效。
下面列出了 Dockerfile
中最常用的指令,完整列表和说明可参看官方文档。
FROM
指定 base
镜像。
MAINTAINER
设置镜像的作者,可以是任意字符串。可选。
COPY
将文件从 build context
复制到镜像。
COPY 支持两种形式:
COPY src dest
COPY ["src", "dest"]
注意:src 只能指定 build context
中的文件或目录。
ADD
与 COPY 类似,从 build context
复制文件到镜像。不同的是,如果 src 是归档文件(tar, zip, tgz, xz 等),文件会被自动解压到 dest。
ENV
设置环境变量,环境变量可被后面的指令使用。例如:
...
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
...
EXPOSE
指定容器中的进程会监听某个端口,Docker 可以将该端口暴露出来。
VOLUME
将文件或目录声明为 volume。
WORKDIR
为后面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令设置镜像中的当前工作目录。
RUN
在容器中运行指定的命令。
CMD
容器启动时运行指定的命令。
Dockerfile 中可以有多个 CMD 指令,但只有最后一个生效。CMD 可以被 docker run 之后的参数替换。
ENTRYPOINT
设置容器启动时运行的命令。
Dockerfile 中可以有多个 ENTRYPOINT 指令,但只有最后一个生效。CMD 或 docker run 之后的参数会被当做参数传递给 ENTRYPOINT。
RUN
、CMD
和 ENTRYPOINT
这三个 Dockerfile
指令看上去很类似,很容易混淆。简单的说:
- RUN 执行命令并创建新的镜像层,RUN 经常用于安装软件包。
- CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够被
docker run
后面跟的命令行参数替换。 - ENTRYPOINT 配置容器启动时运行的命令。ENTRYPOINT 看上去与 CMD 很像,它们都可以指定要执行的命令及其参数。不同的地方在于 ENTRYPOINT 不会被忽略,一定会被执行,即使运行 docker run 时指定了其他命令。
比如下面的 Dockerfile
片段:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
当容器通过 docker run -it [image]
启动时,输出为:
Hello world
而如果通过 docker run -it [image] Docker
启动,则输出为:
Hello Docker
最佳实践:
- 使用 RUN 指令安装应用和软件包,构建镜像。
- 如果 Docker 镜像的用途是运行应用程序或服务,比如运行一个 MySQL,应该优先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可为 ENTRYPOINT 提供额外的默认参数,同时可利用 docker run 命令行替换默认参数。
- 如果想为容器设置默认的启动命令,可使用 CMD 指令。用户可在 docker run 命令行中替换此默认命令。
4.2 Dockerfile 多阶段构建
Docker 17.05版本以后,新增了Dockerfile多阶段构建。所谓多阶段构建,实际上是允许一个Dockerfile 中出现多个 FROM
指令。这样做有什么意义呢?
每一条 FROM 指令都是一个构建阶段,多条 FROM 就是多阶段构建,虽然最后生成的镜像只能是最后一个阶段的结果,但是,能够将前置阶段中的文件拷贝到后边的阶段中,这就是多阶段构建的最大意义。
最大的使用场景是将编译环境和运行环境分离,比如,之前我们需要构建一个Go语言程序,那么就需要用到go命令等编译环境,我们的Dockerfile可能是这样的:
FROM golang:1.10.3
COPY server.go /build/
WORKDIR /build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
ENTRYPOINT ["/build/server"]
基础镜像 golang:1.10.3
是非常庞大的,因为其中包含了所有的Go语言编译工具和库,而运行时候我们仅仅需要编译后的 server
程序就行了,不需要编译时的编译工具,最后生成的大体积镜像就是一种浪费。
最后将编译接口拷贝到镜像中就行了,那么Dockerfile的基础镜像并不需要包含Go编译环境:
# 编译阶段
FROM golang:1.10.3
COPY server.go /build/
WORKDIR /build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 GOARM=6 go build -ldflags '-w -s' -o server
# 运行阶段
FROM scratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=0 /build/server /
ENTRYPOINT ["/server"]
这个 Dockerfile 的玄妙之处就在于 COPY 指令的 --from=0
参数,从前边的阶段中拷贝文件到当前阶段中,多个FROM语句时,0代表第一个阶段。除了使用数字,我们还可以给阶段命名,比如:
# 编译阶段 命名为 builder
FROM golang:1.10.3 as builder
# ... 省略
# 运行阶段
FROM scratch
# 从编译阶段的中拷贝编译结果到当前镜像中
COPY --from=builder /build/server /
4.3 Registry
Registry 是存放 Docker 镜像的仓库,Registry 分私有和公有两种。
Docker Hub(https://hub.docker.com/) 是默认的 Registry,由 Docker 公司维护,上面有数以万计的镜像,用户可以自由下载和使用。
出于对速度或安全的考虑,我们也可以创建自己的私有 Registry。
docker pull
命令可以从 Registry 下载镜像。docker run
命令则是先下载镜像(如果本地没有),然后再启动容器。docker push
将镜像上传到 Registry。需要登录才能使用。docker login
登录Registry。
5 容器平台技术:Kubernetes
5.1 Kubernetes是什么
Kubernetes (K8s) 是 Google 在 2014 年发布的一个开源项目。最初,Google 开发了一个叫 Borg 的系统(现在命令为 Omega)来调度如此庞大数量的容器和工作负载。在积累了这么多年的经验后,Google 决定重写这个容器管理系统,并将其贡献到开源社区,让全世界都能受益。这个项目就是 Kubernetes。简单的讲,Kubernetes 是 Google Omega 的开源版本。
目前 Kubernetes 已经成为发展最快、市场占有率最高的容器编排引擎产品。
与Docker的关系?
Kubernetes与Docker之间的关系可以说是相辅相成的。 Docker为Kubernetes提供了强大的容器运行时环境,而Kubernetes则为Docker容器提供了自动化管理和编排的能力。 简而言之,Docker解决了应用程序的打包和运行问题,而Kubernetes解决了应用程序的分布式管理和扩展问题。
5.2 创建 k8s 集群
这里创建的平台只是单机版的,仅用于学习测试,不适用生产环境。
方法一:启用Docker Desktop里的Kubernetes
-
打开 Docker Desktop,并在设置中启用 Kubernetes。
-
等待 Kubernetes 启动完成后,打开命令行工具(例如 PowerShell 或者 CMD),使用
kubectl
命令行工具连接到 Kubernetes 集群。可以使用以下命令检查 Kubernetes 是否已经启动:
kubectl cluster-info
如果输出类似于以下内容,则表示 Kubernetes 已经启动:
Kubernetes control plane is running at https://localhost:6443
KubeDNS is running at https://localhost:6443/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
方法二:安装minikube
https://github.com/kubernetes/minikube/releases/latest/download/minikube-windows-amd64.exe
minikube下载下来后,重命名为minikube
,添加环境变量,无需安装,然后命令行执行minikube start
,等待自动安装完成:
> minikube start --image-mirror-country='cn'
😄 Microsoft Windows 10 Enterprise Ltsc 2021 10.0.19044.3324 Build 19044.3324 上的 minikube v1.31.2
✨ 自动选择 docker 驱动
📌 使用具有 root 权限的 Docker Desktop 驱动程序
👍 正在集群 minikube 中启动控制平面节点 minikube
🚜 正在拉取基础镜像 ...
💾 正在下载 Kubernetes v1.27.4 的预加载文件...
...
5.3 k8s 核心功能
本节带领大家快速体验 k8s 的核心功能:应用部署、访问、缩容扩容(Scale Up/Down) 以及滚动更新。
1、部署应用
kubectl create deployment kubernetes-bootcamp --image=docker.io/jocatalin/kubernetes-bootcamp:v1
这里我们通过 kubectl create
部署了一个应用,命名为 kubernetes-bootcamp
。
这里 deployment
是 Kubernetes 的术语,可以理解为应用
。
Kubernetes 还有一个重要术语 Pod
。Pod 是容器的集合,通常会将紧密相关的一组容器
放到一个 Pod 中,同一个 Pod 中的所有容器共享 IP 地址和 Port 空间,也就是说它们在一个 network namespace 中。Pod 是 Kubernetes 调度的最小单位,同一 Pod 中的容器始终被一起调度。
查看deployment、pod列表:
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
kubernetes-bootcamp 1/1 1 1 11s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-5459c99d8c-4djlb 1/1 Running 0 13s
Tips: 删除命令:
- 删除应用:kubectl delete deployments kubernetes-bootcamp
- 删除pod: kubectl delete pods kubernetes-bootcamp
注意:单独删除pod,k8s会启动新的pod,删除deployment才可以正确删除应用。
2、访问应用
默认情况下,所有 Pod 只能在集群内部访问。如果需要将应用程序暴露到外部网络中,可以使用以下命令创建一个 Service:
kubectl expose deployments/kubernetes-bootcamp --port=8085 --target-port=8080 --type=LoadBalancer
在这个命令中,我们使用了以下参数:
deployments/kubernetes-bootcamp
:要暴露的 Deployment 的名称。deployments
写成``deployment`也可以,不区分单数复数。--target-port
选项指定了容器的端口。--port
选项指定了 Service 暴露的端口。--type=LoadBalancer
:Service 的类型,即负载均衡器类型。这意味着 Kubernetes 会自动为该 Service 创建一个负载均衡器,并将其公开到外部网络中。
等待 Service 启动完成后,可以使用以下命令查看 Service 的状态:
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 112m
kubernetes-bootcamp LoadBalancer 10.100.251.11 localhost 8080:30067/TCP 3s
在浏览器访问:http://localhost:8080/,可以看到如下信息:
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5459c99d8c-4djlb | v=1
补充说明:上面示例里,
8080:30067/TCP
表示该 Service 在集群内部使用30067
端口进行通信,而在集群外部使用8080
端口进行通信。这是因为 Kubernetes 集群中的 Pod 可能会频繁启动和停止,而 Service 可以为这些 Pod 提供一个稳定的网络入口。当外部网络中的请求到达 Service 时,Kubernetes 会将这些请求转发到运行该 Pod 的节点上,并使用节点上的30067
端口与该 Pod 进行通信。因此,要访问该 Service 提供的应用程序,可以使用
http://EXTERNAL-IP:8080
,其中EXTERNAL-IP
是分配给该 Service 的外部 IP 地址。
Tips: 删除服务:
- kubectl delete services kubernetes-bootcamp
3、Scale 应用
默认情况下应用只会运行一个副本,可以通过 kubectl get deployments
查看副本数。执行如下命令将副本数增加到 2 个:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-5459c99d8c-4djlb 1/1 Running 0 22m
$ kubectl scale deployments/kubernetes-bootcamp --replicas=2
deployment.apps/kubernetes-bootcamp scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-5459c99d8c-4djlb 1/1 Running 0 22m
kubernetes-bootcamp-5459c99d8c-mhldt 1/1 Running 0 3s
通过 curl
访问应用,可以看到每次请求发送到不同的 Pod,2个副本轮询处理,这样就实现了负载均衡:
$ curl -s http://localhost:8080/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5459c99d8c-4djlb | v=1
$ curl -s http://localhost:8080/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-5459c99d8c-mhldt | v=1
要缩容 (scale down) 也很方便,执行命令:
$ kubectl scale deployments/kubernetes-bootcamp --replicas=1
deployment.apps/kubernetes-bootcamp scaled
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-5459c99d8c-4djlb 1/1 Running 0 31m
kubernetes-bootcamp-5459c99d8c-mhldt 1/1 Terminating 0 9m29s
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
kubernetes-bootcamp-5459c99d8c-4djlb 1/1 Running 0 32m
4、滚动更新
当前应用使用的 image 版本为 v1,执行如下命令将其升级到 v2:
$ kubectl set image deployments/kubernetes-bootcamp kubernetes-bootcamp=jocatalin/kubernetes-bootcamp:v2
deployment.apps/kubernetes-bootcamp image updated
$ curl -s http://localhost:8080/
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-c564db98f-x4hp6 | v=2
命令格式:
kubectl set image deployment/<deployment-name> <container-name>=<new-image-name>:<new-tag>
通过 kubectl get pods
可以观察滚动更新的过程:v1 的 Pod 被逐个删除,同时启动了新的 v2 Pod。更新完成后访问新版本应用。
如果要回退到 v1 版本也很容易,执行 kubectl rollout undo
命令:
kubectl rollout undo deployments/kubernetes-bootcamp
如果要回退到指定版本,需要先获取版本号(不是镜像的tag):
$ kubectl rollout history deployment/kubernetes-bootcamp
deployment.apps/kubernetes-bootcamp
REVISION CHANGE-CAUSE
1 <none>
4 <none>
5 <none>
6 <none>
$ kubectl rollout undo deployment/kubernetes-bootcamp --to-revision=4
deployment.apps/kubernetes-bootcamp rolled back
Tips:如果pod创建异常,可以使用
kubectl describe pod kubernetes-bootcamp-665c496b44-mhrpw
命令查看详情。
5.4 k8s 重要概念
Kubernetes 有几个重要概念,它们是组成 Kubernetes 集群的基石,必须了解。
- Cluster :Cluster 是计算、存储和网络资源的集合,Kubernetes 利用这些资源运行各种基于容器的应用。
- Master :Master 是 Cluster 的大脑,它的主要职责是调度,即决定将应用放在哪里运行。
- Node :Node 的职责是运行容器应用。Node 由 Master 管理,Node 负责监控并汇报容器的状态,并根据 Master 的要求管理容器的生命周期。
- Pod :Pod 是 Kubernetes 的最小工作单元。每个 Pod 包含一个或多个容器。Pod 中的容器会作为一个整体被 Master 调度到一个 Node 上运行。
- Replication Controller:K8s 通常不会直接创建 Pod,而是通过通过 Controller 来管理 Pod 的。Controller 中定义了 Pod 的部署特性,比如有几个副本,在什么样的 Node 上运行等。Controller会确保任何时候Kubernetes集群中有指定数量的pod副本(replicas)在运行。
- Service :Service 定义了外界访问一组特定 Pod 的方式。Service 有自己的 IP 和端口,Service 为 Pod 提供了负载均衡。
为了满足不同的业务场景,Kubernetes 提供了多种 Controller:
- Deployment: 是最常用的 Controller。Deployment 可以管理 Pod 的多个副本,并确保 Pod 按照期望的状态运行。
- ReplicaSet: 实现了 Pod 的多副本管理。使用 Deployment 时会自动创建 ReplicaSet,也就是说 Deployment 是通过 ReplicaSet 来管理 Pod 的多个副本,我们通常不需要直接使用 ReplicaSet。
- DaemonSet: 用于每个 Node 最多只运行一个 Pod 副本的场景。正如其名称所揭示的,DaemonSet 通常用于运行 daemon。
- StatefuleSet: 能够保证 Pod 的每个副本在整个生命周期中名称是不变的。而其他 Controller 不提供这个功能,当某个 Pod 发生故障需要删除并重新启动时,Pod 的名称会发生变化。同时 StatefuleSet 会保证副本按照固定的顺序启动、更新或者删除。
- Job: 用于运行结束就删除的应用。而其他 Controller 中的 Pod 通常是长期持续运行。
最后了解下Namespace
(命名空间)的概念:
用于将一个物理的 Cluster 逻辑上划分成多个虚拟 Cluster,每个 Cluster 就是一个 Namespace。不同 Namespace 里的资源是完全隔离的。Kubernetes 默认创建了两个 Namespace。
default
-- 创建资源时如果不指定,将被放到这个 Namespace 中。kube-system
-- Kubernetes 自己创建的系统资源将放到这个 Namespace 中。
5.5 k8s 通过YAML文件创建资源
上文我们之间通过命令行kubectl create
创建资源,k8s还支持通过配置文件和 kubectl apply
创建。下面的示例实现了与上面例子相同的效果:
# kubernetes-bootcamp-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: kubernetes-bootcamp-yml #应用名
spec:
selector:
matchLabels:
app: kubernetes-bootcamp-yml
replicas: 2
template:
metadata:
labels:
app: kubernetes-bootcamp-yml
spec:
containers:
- name: kubernetes-bootcamp-yml
image: docker.io/jocatalin/kubernetes-bootcamp:v1
ports:
- containerPort: 8080
# kubernetes-bootcamp-service.yml
apiVersion: v1
kind: Service
metadata:
name: kubernetes-bootcamp-yml #服务名
spec:
selector:
app: kubernetes-bootcamp-yml
type: LoadBalancer
ports:
- name: http
port: 9051 #对外服务端口
targetPort: 8080 #容器端口
命令行执行:
kubectl apply -f kubernetes-bootcamp-deployment.yml
kubectl apply -f kubernetes-bootcamp-service.yml
即可创建出应用(Deployment)及服务(Service):
$ kubectl apply -f kubernetes-deployment-deployment.yml
deployment.apps/kubernetes-bootcamp-yml created
$ kubectl apply -f kubernetes-bootcamp-service.yml
service/kubernetes-bootcamp-yml created
$ kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
kubernetes-bootcamp-yml 2/2 2 2 6m36s
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes-bootcamp-yml LoadBalancer 10.107.237.111 localhost 9051:31499/TCP 10m
$ curl -s http://localhost:9051
Hello Kubernetes bootcamp! | Running on: kubernetes-bootcamp-yml-66955668d4-prx6f | v=1
Tips:
kubectl apply
不但能够创建 Kubernetes 资源,也能对资源进行更新,非常方便。不过 Kubernets 还提供了几个类似的命令,例如kubectl create
、kubectl replace
、kubectl edit
和kubectl patch
。为避免造成不必要的困扰,尽量只使用
kubectl apply
,此命令已经能够应对超过 90% 的场景,事半功倍。
Tips: 如果已经创建了一个 Service,但是忘记了将端口映射到容器端口,可以使用
kubectl edit
命令编辑 Service 的配置,然后添加targetPort
字段。例如,以下命令将使用默认编辑器打开名为kubernetes-bootcamp
的 Service 的配置:kubectl edit svc kubernetes-bootcamp
然后您可以在配置文件中添加
targetPort
字段,如下所示:apiVersion: v1 kind: Service metadata: name: kubernetes-bootcamp-yml spec: type: LoadBalancer ports: - port: 9051 targetPort: 8080 selector: app: kubernetes-bootcamp
保存并退出编辑器后,Kubernetes 将自动更新 Service 的配置,并将端口映射到容器端口上。
5.6 Kubernetes Dashboard
前面章节 Kubernetes 所有的操作我们都是通过命令行工具 kubectl
完成的。为了提供更丰富的用户体验,Kubernetes 还开发了一个基于 Web 的 Dashboard,用户可以用 Kubernetes Dashboard 部署容器化的应用、监控应用的状态、执行故障排查任务以及管理 Kubernetes 各种资源。
yml文件下载地址:https://github.com/AliyunContainerService/k8s-for-docker-desktop
# 创建 dashboard 资源
$ kubectl apply -f kubernetes-dashboard.yaml
# 查看 Deployment 的运行状态
$ kubectl --namespace=kubernetes-dashboard get deployment -n kubernetes-dashboard
# 查看 Pod 的运行状态
$ kubectl --namespace=kubernetes-dashboard get pods -n kubernetes-dashboard
# 授权kube-system默认服务账号
$ kubectl apply -f kube-system-default.yaml
# 通过代理的方式访问 dashboard
$ kubectl proxy
创建 dashboard 资源的输出:
namespace/kubernetes-dashboard created
serviceaccount/kubernetes-dashboard created
service/kubernetes-dashboard created
secret/kubernetes-dashboard-certs created
secret/kubernetes-dashboard-csrf created
secret/kubernetes-dashboard-key-holder created
configmap/kubernetes-dashboard-settings created
role.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrole.rbac.authorization.k8s.io/kubernetes-dashboard created
rolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
clusterrolebinding.rbac.authorization.k8s.io/kubernetes-dashboard created
deployment.apps/kubernetes-dashboard created
service/dashboard-metrics-scraper created
deployment.apps/dashboard-metrics-scraper created
Dashboard 会在 kubernetes-dashboard
namespace 中创建自己的 Deployment 和 Service。
这里需要使用 API Server 的形式访问的 dashboard ,具体的地址为:http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/
我们可以使用下面的 powershell 命令打印出 token,然后登录到 dashboard 中查看整个集群的信息
$TOKEN=((kubectl -n kube-system describe secret default | Select-String "token:") -split " +")[1]
kubectl config set-credentials docker-desktop --token="${TOKEN}"
echo $TOKEN
登录dashboard的时候:
输入上文控制台输出的内容即可进入控制台。
5.7 k8s常用命令参考
kubectl get pods
:列出所有 Pod。kubectl get services
:列出所有 Service。kubectl get deployments
:列出所有 Deployment。kubectl get configmaps
:列出所有 ConfigMap。kubectl describe pod <pod-name>
:显示 Pod 的详细信息。kubectl describe service <service-name>
:显示 Service 的详细信息。kubectl describe deployment <deployment-name>
:显示 Deployment 的详细信息。kubectl describe configmap <configmap-name>
:显示 ConfigMap 的详细信息。kubectl describe secret <secret-name>
:显示 Secret 的详细信息。kubectl create deployment <deployment-name> --image=<image-name>
:创建一个 Deployment。kubectl edit svc my-service
: 编辑指定 Service 的配置信息。该命令将打开一个编辑器,允许您修改 Service 的 YAML 配置文件kubectl expose deployment my-deployment --port=8080 --target-port=80 --type=NodePort
创建一个 Service,并将其暴露到集群外部kubectl scale deployment my-deployment --replicas=3
调整指定资源的副本数kubectl apply -f <yaml-file>
:使用 YAML 文件创建或更新资源。kubectl delete <resource-type> <resource-name>
:删除指定的资源。- kubectl delete deployment kubernetes-dashboard
- kubectl --namespace=kubernetes-dashboard delete deployment --all
- kubectl delete namespace kubernetes-dashboard 删除空间
kubectl exec <pod-name> <command>
:在 Pod 中执行命令。kubectl exec -it <pod-name> /bin/bash
:进入 Pod 中的 Bash shell。
参考文档
1、Docker学习笔记
https://www.cnblogs.com/52fhy/p/5638571.html#docker镜像
2、Docker Docs: How to build, share, and run applications | Docker Documentation
https://docs.docker.com/
3、https://minikube.sigs.k8s.io/docs/start/
本文优先在公众号"飞鸿影的博客(fhyblog)"发布,欢迎关注公众号及时获取最新文章推送!
作者:飞鸿影
出处:http://52fhy.cnblogs.com/
版权申明:没有标明转载或特殊申明均为作者原创。本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。