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'
标签:kubectl,Kubernetes,kubernetes,容器,bootcamp,镜像,docker,Docker
From: https://www.cnblogs.com/52fhy/p/17789260.html