Docker 学习笔记(二)

889 阅读5分钟

今天继续学习 Docker,学习材料是狂神说的b站教程

1. image 加载原理

Docker 采用 UnionFS(联合文件系统)。
当下载一个 image 文件的时候,会看到一次下载很多个文件,这就是 UnionFS。它是一种分层,轻量,高性能的文件系统。UnionFS 类似于 git,每次修改文件的时候,都是一次提交,并有记录。他允许讲多个不同的目录挂载到同一个虚拟文件系统下。这就是为什么 image 之间能够共享文件,以及继承镜像的原因。

image.png

1.1 为什么 docker 里下载的 linux 系统这么小?

因为是一个精简版的,去掉了很多不需要的东西,只留下最基础的工具。甚至没有内核,用的是 host 的内核。

1.2 分层原理

每一个镜像都是分成好几层。
所有的镜像都是由一个基本层开始,然后每当用到一个新的环境,就会再加一层。比如说某个镜像要用到 linux,于是首先有一个 linux 层,然后之后这个镜像需要用到 Python 了,于是加一个 python 层,以此类推。
因此,如果以后有别的镜像也需要用到 python,那么只要版本不变,就不用下载新的 python 包了。

1.3 容器运行

image 文件是只读的,不可改变的。那么容器实例为什么可以改变呢?因为容器实例实际上是在 image 文件的顶部再加一层,叫做容器层。而我们的所有操作都是在这一层上进行的。对 image 文件本身没有任何影响。

image.png

1.4 commit

commit 和 git 的类似,可以把自己改动过的容器(也就是 image 层加上容器层)合并提交为一个新的 image 文件到自己的主机上(注意不是上传到 registry)。后续可以拷贝给别人或者上传到 registry。

2. 容器数据卷

如果数据放在容器上,那么容器一删除数据就没了。因此就有了数据持久化的需求。也就是我们需要能把数据存放在一个地方。让容器共享数据。这样的一个技术就叫做数据卷技术。
这个技术的作用,就是将容器内的目录,挂载(或者说映射)到 host 的某个目录上。这其实是一种同步机制,当容器挂载的目录内容发生改变的时候,数据就会同步到 host 上的目录。
数据卷的作用:

  1. 持久化
  2. 同步数据
  3. 容器间数据共享

2.1 数据卷的使用

作为 run 的一个参数:

$ docker run -v 主机目录:容器内目录 -it ubuntu /bin/bash

数据卷是一个双向同步的技术,改变容器内的数据,host 的对应目录也会发生改变,反之亦然。 虽然删除容器内的数据,主机的数据也会改变。但是如果是直接删除容器的话,主机的数据不会被删除,仍然保留,这就是数据持久化。

2.2 练习:mysql 配置

使用 image 前,最好去 hub 上找一下官方文档说明,了解如何配置参数,比如 mysql 可以这么配置:

$ docker run -d -p 3310:3306 -v ~/Desktop/test/mysql/conf:/etc/mysql/conf.d -v ~/Desktop/test/mysql/data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456  --name="mysql-test-volume" mysql
  • -d 表示后台运行
  • -p 表示端口映射,mysql 的端口是 3306
  • -v 可以有多个,表示同步多个目录
  • -e 表示配置,这里需要去 hub 上看官方配置参数名称,例子里是给 mysql 设置密码
  • --name 给容器实例起个名字

注意 --name 并不是 mysql 的数据库名字,这只是一个 mysql 服务,还没有创建任何数据库呢

2.3 具名挂载和匿名挂载

如果在使用 -v 的时候,不声明主机的路径,只写容器内的目录,就是匿名挂载或者具名挂载。
区别在于写不写名字

# 正常写法
$ docker run -v /home/test:/etc/nginx
# 匿名挂载(冒号都不写,只有一个容器路径)
$ docker run -v /etc/nginx
# 具名挂载(虽然不写主机路径,但是要给这个 volume 起个名字,要用到冒号)
$ docker run -v juming:/etc/nginx

注意具名和正常写主机路径的区别,主机路径是带 / 的,而具名只是一个名字,不带 /

那么具名挂载和匿名挂载的路径在哪里呢?其实在 docker 工作目录里。或者准确说,就在 /var/lib/docker/volumes/xxxx/_data 文件夹下。

一般使用具名挂载较多。

2.4 ro 和 rw

其实 -v 还可以有一个冒号,总共两个冒号 docker run -v juming:/etc/nginx:ro,第二个冒号后面只能是 ro 或者 rw。代表 read only 和 read write。默认情况下是 rw。如果是 ro,那么就不能改变容器目录内的数据了,只能通过改变 host 的数据来同步到容器内。

2.5 数据卷容器

之前提到过,数据卷可以让多个容器可以共享数据。 这里用到的依然是一个参数 --volumes-from

# 首先要正常创建一个容器
$ docker run -v ~/Desktop/test:/home --name="container01" -it ubuntu /bin/bash
# 然后第二个容器继承上一个容器
$ docker run --volumes-from container01 --name="container02" -it ubuntu /bin/bash
# 第三个容器可以继承容器1,也可以继承容器2
$ docker run --volumes-from container02 --name="container03" -it ubuntu /bin/bash

总之最终这些容器全部都挂载到同一个 host 目录下,全部共享数据,无论在哪里更改数据,其他所有容器和 host 都能看到改变。删除其中一个容器也不会删除共享数据。即使把所有容器都删除,因为已经持久化到本地了,数据依然不会被删除。

其实, --volumes-from container01 和 -v ~/Desktop/test:/home 是一样的效果。只是不用去写路径,直接继承就行了。

3. Dockerfile

基础的上一篇文章已经讲过。
这里着重讲一下 Dockerfile 的参数:

FROM ubuntu # 基础镜像,一切从这里开始构建
MAINTAINER # 维护者信息,镜像是谁写的
RUN # 构建镜像的时候需要运行的命令,也就是告诉怎么构建镜像
ADD # 要添加的东西。会自动解压。比如说从 Ubuntu 开始,还要加 python 包之类的。就可以放一个 python 压缩包。
WORKDIR # 镜像的工作目录。也就是进入容器后的初始目录。不过可以指定就是了,比如我们用了很多的 /bin/bash
VOLUME # 设置容器卷
EXPOSE # 指定暴露的端口
CMD # 指定容器创建启动后要运行的命令,只有最后一个 CMD 会生效,而且会被 docker run 的时候加的参数覆盖。
    # 可以理解为默认执行的命令,所以可以被覆盖,也只能生效一个。
ENTRYPOINT # 指定容器创建启动后要运行的命令,覆盖比较难,docker run 时候需要专门指定 --entrypoint 才能覆盖
           # 同样只能生效一个
           # 由于不会被覆盖,可以在 ENTRYPOINT 里写 ls -a,然后 docker run -l
           # 最后的效果就是 ls -al
           # CMD 做不了这样
ONBUILD # 当构建一个被继承的 Dockerfile,这个指令会被执行
COPY # 类似 ADD,不过是直接将文件拷贝到镜像中。
ENV # 构建的时候设置环境变量。

注意:
1. CMD 和 ENTRYPOINT 都只有最后一行生效。如果想执行多个命令,要么用 &&,要么写一个脚本,然后 CMD/ENTRYPOINT 运行这个脚本。
2. 一般来说,ENTRYPOINT 里写必定运行的命令。CMD 里写默认运行的命令。

构建 image 的命令为:

$ docker build -f <Dockerfile的名字,如果就叫 Dockerfile 则不用写> -t <name:tag>