1. 什么是 Docker ?
- Go 语言实现
- 基于 Linux 内核 CGroup, namespace, UFS 等技术:
- CGroup : Control Groups 的缩写,控制 CPU,内存,IO等资源限制。
- namespace : 资源隔离,通过namespace,让一个进程是一个独立的盒子,拥有自己的 PID,网络,hostname等等,不能访问别的 namespace 的资源。
- UFS: 镜像和容器的管理,Docker 目前支持的联合文件系统包括 OverlayFS, AUFS, Btrfs, VFS, ZFS 和 Device Mapper,默认使用 overlay2
- 操作系统层面的虚拟化技术。
- 与传统的虚拟化技术对比:
- Open VZ 类似,但是虚拟出来的是 VPS,利用底层的内核,虚拟出一整套的操作系统。
2. 为什么要使用Docker,有哪些优缺点?
- 优点:
- 不需要搭建各种环境,应用的依赖打包到镜像内部,所以不需要重复安装各种底层依赖工具。需要一个 nodejs,直接使用现成的 nodejs 镜像拉取到本地。比如 某些代码,依赖了指定的 Linux 版本,在 Mac 本地开发调试就很麻烦,这样可以用 Docker 基于 Centos 的镜像来运行代码。
- 隔离性带来的好处,每个进程相当于一个系统,比如同一个 jar 要运行多个进程,会造成端口冲突等等。
- 迁移方便,只需要在目的 Host 安装 Docker 即可。
- 更高效的利用系统资源: 不需要虚拟硬件,也不需要允许整套完整操作系统。
- 启动时间等等。
- 缺点:
- 需要更新的 Linux 内核:Version 3.10 or higher of the Linux kernel. CentOS 6 需要更新内核。
- 基于 Linux 内核实现,在 Windows 和 MacOS 上的兼容是通过实现一个 Linux 虚拟机,然后在虚拟机里面安装 Docker 来实现,不能原生支持。
3. Docker 的基本概念
镜像(Image)
- Docker 镜像是一个特殊的文件系统,简单的理解就是包含了,当前应用需要运行所依赖的所有文件。
- 多层结构: 依赖于 Linux 的 UFS 存储驱动,采用分层结构最好的好处就是——资源共享和存储空间优化。一般在构建镜像的时候,都会有一个基本镜像,这样,这个基本镜像就只用存储一份。同时,构建的时候,会一层层构建,前一层是后一层的基础,后一层会指向前一层。这样,镜像的复用和定制变得就更简单。
- 两个属性:名称 和 tag.
参考:10张图带你深入理解Docker容器和镜像
容器(Container):
- 容器 和 镜像 的关系就像 实例 和 类的关系一样。容器是根据镜像 + 读写层(容器存储层) 来创建的一个运行时的实体。
- 容器的本质是一个进程(相对于宿主机),进程拥有自己独立的 namespace,简单的看,就可以看做一个 独立的 Linux server,容器和宿主机相互之间是隔离的。
- 容器中可以有多个线程,比如启动一个 Nginx 容器,Nginx 的 worker_processes 定义为 8,那么在宿主机中,只能看到一个 Nginx 的 docker 进程。但是在容器中,nginx 有 8 个进程。
在Host中:
在容器中:
- 容器中可以有多个线程,比如启动一个 Nginx 容器,Nginx 的 worker_processes 定义为 8,那么在宿主机中,只能看到一个 Nginx 的 docker 进程。但是在容器中,nginx 有 8 个进程。
- 容器的状态:创建,启动,停止,删除,暂停。
- 容器的内核是使用的宿主机的内核,所以,这个也是镜像比较小的原因。
- 结构:一个镜像 + 读写层(容器存储层) = 容器。
- 容器存储层的生命周期跟容器的生命周期一样,容器删除了,存储层的数据也就丢失了,停止容器,但是没有删除,就不会丢失。所以,容器不应该像存储层写入任何数据,容器要保证无状态化,如果需要持久化数据,可以挂载本地的目录到容器。
仓库(Repository)
- 一个镜像是一个仓库,一个镜像有多个 tag 。所以,一个仓库的意思是指 一个镜像的多个版本的集合。比如 ubuntu 是仓库的名字,有多个不同的版本,18.04,16.04 这个是标签。
- Docker Registry : 包含多个仓库 Repository的集合。大多时候,仓库的名字经常以两段路径出现,比如 jwilder/nginx-proxy:1.0,前面往往表示在这个 Registry 中这个仓库所属的用户名,第二段表示仓库名,冒号后面表示版本。
- Docker 官方的 Registry: Docker Hub
- 可以使用官方提供的 docker-registry 镜像搭建自己的私有 Registry,但是官方提供的只有服务端 API 实现,没有图形界面,用户管理,访问控制等功能,商业版本提供了高级功能。
- 除了官方提供的 docker-registry 镜像,还有第三方软件实现了 Docker Registry API,提供了用户界面。比如,Harbor 和 Sonatype Nexus.
- 跟 Maven 区别。
4. 如何 安装 Docker ?
参考链接:https://yuanmomo.net/2019/08/05/docker-install/
5. Docker 镜像
拉取镜像
配置镜像加速器
docker pull xxx:xxx 拉取指定镜像的指定版本,如果不指定版本,那就用最新的 latest (不建议)
默认从 Docker 官方的 Docker hub 拉取镜像。
拉取镜像的时候,其实拉取的是很多层,一层层的结构。
1
2
3
4
5
6
7
8» docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
5b7339215d1d: Pull complete
14ca88e9f672: Pull complete
a31c3b1caad4: Pull complete
b054a26005b7: Pull complete
Digest: sha256:9b1702dcfe32c873a770a32cfd306dd7fc1c4fd134adfb783db68defc8894b3c
Status: Downloaded newer image for ubuntu:18.04
- 查看镜像
- docker images -a
- 虚悬镜像,
为不同的镜像打相同的tag,会导致旧的镜像没有tag而变成虚悬镜像。 - 中间镜像,docker build 产生的中间镜像,因为有上层镜像的依赖,所以不能删除。
- 删除本地镜像
- docker rmi xxx:xxx 注意镜像名和版本号
- Untagged 和 Deleted 的区别 : 镜像的唯一标识是 id 和 摘要。可能一个镜像有多个名称和标签,所以在删除的时候,优先删除标签。当一个镜像没有标签指向的时候,才会触发 delete 行为。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16[root@test #] docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
yuanmomo/nodejs-hexo 1.0 57f58e397d26 8 days ago 107MB
[root@test #] docker tag yuanmomo/nodejs-hexo:1.0 yuanmomo/nodejs-hexo:3.0
[root@test #] docker tag yuanmomo/nodejs-hexo:1.0 hexo-local:2.0
[root@test #] docker images -a
REPOSITORY TAG IMAGE ID CREATED SIZE
hexo-local 2.0 57f58e397d26 8 days ago 107MB
yuanmomo/nodejs-hexo 1.0 57f58e397d26 8 days ago 107MB
yuanmomo/nodejs-hexo 3.0 57f58e397d26 8 days ago 107MB
[root@test #] docker rmi yuanmomo/nodejs-hexo:3.0 hexo-local:2.0
Untagged: yuanmomo/nodejs-hexo:3.0
Untagged: hexo-local:2.0
docker commit 制作镜像(慎用)
- 使用 docker run 运行一个镜像, 启动一个容器,
- 在容器中增加某些包,依赖,修改某些文件,比如 某某配置文件
- 然后用 docker diff 可以查看文件变动的记录
- 使用 docker commit 可以帮当前运行的容器打包成一个镜像。
- docker commit 优点 :
- 被入侵后, 保存入侵现成 这种需要保存容器所有内容的特殊场合
- docker commit 缺点 :
- 会将很多无用的文件修改保存下来,导致有很多无关的内容被打包,导致镜像极其臃肿。
- 黑箱镜像,只有制作人知道镜像的内容,其他人都不知道,就算制作人,过段时间,也可能会忘记。黑箱镜像的维护成本非常痛苦。
Dockerfile 定制镜像
- 编写 Dockerfile,第一行为 FROM,表示以某个镜像为基础镜像来定制镜像。(scratch镜像)
- RUN 执行一个命令,比如安装某个包: apt, yum;修改某个文件:sed
- ENTRYPOINT 和 CMD 的比较:
- ENTRYPOINT 和 CMD 都是让容器启动的时候,运行一个指令。
- docker run -it image_name CMD 命令行中的 CMD 会替换 Dockerfile 中的 CMD 命令的内容。
- ENTRYPOINT 是将 docker run -it image_name CMD 命令行中的 CMD 作为一个参数传递给 ENTRYPOINT. 这样可以在运行镜像的时候,动态的增加一些参数。
- 因为命令行 中的 CMD 会作为 ENTRYPOINT 的一个参数,这样的话,可以在运行 CMD之前执行一些操作,然后再执行 CMD。比如给 redis 运行的时候切换账号
- Volumn 匿名卷的作用
- 在 Dockerfile 中定义一个挂在目录,存储数据,防止用户没有使用 -v 或者 –mount 挂在目录,而将数据写到容器的读写层,当容器销毁后数据丢失。
- 使用 docker build 根据文件制作镜像
- 需要注意的是,在 docker build 打包镜像的时候,遇到一个 RUN 命令,就会提交一层镜像,所以尽量合并 RUN,COPY,ADD 命令,UFS是有最大层数限制的,128层,层数越多,性能越低。同时在安装包后,删除下载包,缓存,中间的编译文件等等,尽量缩减包的大小。
- docker build 在编译的时候,在构建新的一层的时候,会判断是否有缓存,如果有缓存,则会直接优先使用缓存中的层,节约空间和编译时间:
- docker build 缓存的规则:
- 禁止使用 cache特性,build 时增加参数 –no-cache=true
- 如果用一个已经存在的镜像做为父镜像,那么在执行下一句的时候,会对该镜像的所有子镜像做检查,如果找到有一个子镜像是用相同的语句构建,那么使用cache.
- 大多数情况下,简单的比较构建语句是否相同即可,但是排除后面的特殊情况。
- 对于使用 ADD 和 COPY 指令,会检查当前文件,或者文件夹下面的所有文件是否有修改(最后修改时间和最后访问时间除外),不管是任何文件的新增,内容的修改,都会认为缓存失效,并且重新编译这层镜像。
- 除了 ADD 和 COPY 指令外,其它的指令不会去对文件做校验,比如 apt-get -y update这样的命令,是不会去检查所有文件,这个时候,这个命令字符串将作为关键字查找镜像。
- 缓存一旦失效,那么这个 Dockerfile 中后面所有的层, 将全部重新编译。所以说,在编写Dockerfile 时,有一个注意的就是,不经常变动的文件或者命令,尽量写在前面,以此减少在缓存失效后,需要重新编译的镜像层数。
- 构建上下文(重要):
- docker build 有一个重要的参数 “.” 或者某个目录。
- Docker 运行分为服务端和客户端(C/S)。Docker 服务端是一个服务端守护进程,/usr/bin/dockerd,并提供了一组 REST API,Docker Remote API。
- 客户端使用 docker 命令,通过调用 REST API 接口和 docker 引擎通信。
- docker build 在构建的时候,指定的路径,比如”.”,叫做上下文路径。docker build 执行的时候,会把指定的这个目录打包发送到 docker 引擎。docker 引擎收到这个上下文的包的时候,会展开这个包,就获得了构建所需要的所有文件。
- Dockerfile 中的 ADD, COPY 进行文件操作的时候,源路径都是相对路径。
- .dockerignore 配置不发送到 docker 引擎的文件列表,类似 .gitignore
- Dockerfile 多阶构建
- 场景:有一个 C++ 的项目,源码和编译后的so库文件。
- 旧的方式,编写两个 Dockerfile 文件 和一个 shell 脚本。第一个 Dockerfile 包含代码,安装依赖库,编译测试打包。第二个 Dockerfile 负责安装运行环境,就不需要源码,编译中的依赖,比如 cmake,pb 等等。shell 脚本负责调用 docker build 来整合两个 Dockerfile.
- 新的方式,编写一个 Dockerfile,使用 FROM xxx:xxx as phase,来制定阶段,后面的phase 可以通过 COPY –from 来使用前面 phase 产生的文件。使用 docker builder –target来编译指定阶段的镜像。
其他的镜像制作方式
- docker build 使用 git repo
- docker build 使用 tar 包
- docker build 从标准输入读取 Dockerfile 进行构建
- docker build 从标准输入中读取上下文压缩包进行构建
- 从 rootfs 压缩包导入
6. Docker 容器的操作
- 容器有几种状态:created|restarting|running|removing|paused|exited|dead
- 常用的状态: running, paused, exited.
- 启动容器:
- 创建容器,并运行 : docker run
- 检查本地仓库是否有镜像,没有镜像,从远程仓库去拉取镜像。
- 利用镜像创建一个容器。
- 分配一个文件系统,在容器的最上层,挂在一个读写层。
- 配置虚拟网卡。
- 分配ip地址。
- 用户指令。
- 运行完成后退出容器。
- 重新启动停止的容器 : docker start
- 创建容器,并运行 : docker run
- 后台运行: docker run -it: -i 交互模式;-t: 分配一个终端)。没有 -t ,只有 -i的时候,看不到 Linux 的命令提示符。
- docker 运行的命令,比如 nginx ,就不能是 service nginx start/restart. 而应该是 ./nginx.
- 因为 service nginx start 这个命令运行完成了过后,命令结束,那么容器就会退出,如果使用 ./nginx,则会一直卡在运行 nginx,容器就不会退出。
- 如果有得时候,运行的时候,需要强制容器不退出,那么可以在命令后面添加一个 top命令,或者while,echo 命令,强制 CMD 运行不能完成。
- -d 参数,这个参数时用来控制 docker 运行方式,前台还是后台,直观的就是日志是到宿主机还是在后台。宿主机的日志可以直接看到;后台的日志,需要使用 docker logs container-name 查看。需要注意的是,容器是否能长久运行(能不能保持运行的状态),不是取决于有没有使用 -d 参数,而是 docker run 后面的指令。
- 终止: docker stop
- 进入容器:
- docker attach,如果从 attach 中 exit,会导致容器exit停止,所以不建议使用。
- docker exec -it xxx /bin/bash (镜像需要安装 bash,如果没有安装,则进入不了)
- 删除 docker rm [-f] ,不加 -f, 如果容器在 running 状态则不会删除,需要先 stop. -f 就可以直接讲running 状态的容器删除。
- 注意 docker run 容器的用户权限,像redis,需要用 redis 的用户运行,部分用户可能需要用 root 账号运行(比如需要 gdb 调试等等)。
7. Docker 仓库
- Docker Hub 公共仓库,
- 私有仓库
- Harbor 仓库搭建 https://yuanmomo.net/2019/07/01/docker-harbor-config/
8. Docker 网络配置
- Harbor 仓库搭建 https://yuanmomo.net/2019/07/01/docker-harbor-config/
- 参考:https://yuanmomo.net/2019/06/13/docker-network/
9. Docker 数据操作 https://docs.docker.com/storage/
- Volumes(挂载卷)
- - Bind mounts(挂载点)
- - tmpfs mount(仅限 Linux)
10. Docker 三剑客 Compose, Swarm, Machine
11. 容器编排工具 k8s(Kubernetes) VS Docker Swarm
12. Docker 运维和监控