0%

Docker学习笔记

Docker命令

获取镜像

docker pull [选项] [Docker 仓库地址[:端口号]]仓库名[:标签]

  • []内的属于可选参数,所以最基本的命令就是 docker pull mongo 这样的.
  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。

例如: docker pull mongo
这里没有给定Docker仓库地址就用默认的Docker Hub,没有给定标签就拿最新的

1
Using default tag: latest

具体的tag可以去hub.docker.com里查看

列出镜像

docker image ls


列表包含了 仓库名、标签、镜像 ID、创建时间 以及 所占用的空间。

其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个 标签。

实例化镜像

docker run -p 本地端口:容器端口 -d --name Name 镜像

例如
docker run -p 27017:27017 -d --name mongoDB mongo
就是在指定主机端口27017映射容器端口27017端口后台运行mongo,别名为mongoDB
这个时候就会在后台27017端口运行容器mongoDB,这个时候在Docker里运行的mongo就和在本地运行一样

如果不映射端口的话,我们就需要知道docker中的redis的IP地址:

1
docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${容器id}

可以通过docker ps看到容器id

1
2
$ docker inspect --format '{{ .NetworkSettings.IPAddress }}' b7378e4328d6
172.17.0.3

然后去连这个IP地址的对应端口的服务./redis-cli -h 172.17.0.3
参考文章

镜像体积

docker image ls里展示的SIZE是指本地解压缩以后的镜像体积,会比Docker Hub中显示大.
docker image ls中展示的体积也并非是实际硬盘消耗的体积,因为Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
你可以通过以下命令来便捷的查看镜像、容器、数据卷所占用的空间。

1
docker system df

虚悬镜像

有一种特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 <none>。:

<none> <none> 00285df0df87 5 days ago 342 MB
这个镜像原本是有镜像名和标签的,原来为 mongo:3.2,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时,mongo:3.2 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>。除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。这类无标签镜像也被称为 虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像:

1
2
3
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 00285df0df87 5 days ago 342 MB

一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除。

1
docker image prune

删除镜像

docker image rm [选项] <镜像1> [<镜像2> ...]
这里的<镜像> 可以是 镜像短 ID、镜像长 ID、镜像名 或者 镜像摘要
例如:

  • docker image rm 0d35b744b3012621
  • docker image rm 0d35b744b30126210d35b744b30126210d35b744b3012621
  • docker image rm redis

定制化镜像

只是使用别人的镜像最多就是方便安装一个服务,一个系统,一个软件.这显然不够,Docker能做到更多.Dockerfile文件就是用来做这个事的.

首先在一个空白文件夹中创建一个名为Dockerfile的文件 touch Dockerfile
Dockerfile文件由2部分组成,FROM 镜像名RUN shell脚本
例如:

1
2
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

定制化镜像,一般是在别人的镜像基础上进行调整,FROM的就是基础镜像,这里就是继承了Docker Hub的nginx镜像,然后执行RUN shell脚本生成新的镜像
如果shell脚本比较复杂也可以写成脚本文件用exec格式去执行
exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。

RUN shell脚本

以下内容引用自docker_practice

既然 RUN 就像 Shell 脚本一样可以执行命令,那么我们是否就可以像 Shell 脚本一样把每个命令对应一个 RUN 呢?比如这样:

1
2
3
4
5
6
7
8
9
FROM debian:stretch

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

之前说过,Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

而上面的这种写法,创建了 7 层镜像。这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。 这是很多初学 Docker 的人常犯的一个错误。

Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

上面的 Dockerfile 正确的写法应该是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FROM debian:stretch

RUN buildDeps='gcc libc6-dev make wget' \
&& apt-get update \
&& apt-get install -y $buildDeps \
&& wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
&& mkdir -p /usr/src/redis \
&& tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
&& make -C /usr/src/redis \
&& make -C /usr/src/redis install \
&& rm -rf /var/lib/apt/lists/* \
&& rm redis.tar.gz \
&& rm -r /usr/src/redis \
&& apt-get purge -y --auto-remove $buildDeps

首先,之前所有的命令只有一个目的,就是编译、安装 redis 可执行文件。因此没有必要建立很多层,这只是一层的事情。因此,这里没有使用很多个 RUN 对一一对应不同的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将之前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

并且,这里为了格式化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,比如换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。

此外,还可以看到这一组命令的最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。这是很重要的一步,我们之前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

很多人初学 Docker 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。

构建镜像

1
2
3
4
docker build -t 镜像:标签 <上下文路径/URL/->

例如:
docker build -t nginx:v2 .

构建完成以后就可以用docker image ls查看新的标签为v2的nginx镜像了

镜像构建上下文(Context)

你可能会留意到docker build -t nginx:v2 .这里最后加了一个.,这个.表示的是当前目录,而 Dockerfile 就在当前目录,因此不少初学者以为这个路径是在指定 Dockerfile 所在路径,这么理解其实是不准确的.
这里其实一个上下文路径.因为Dockerfile中可能会执行ADD,COPY之类的操作,这类操作会用到的路径就是以这个上下文路径为标准的.例如:
如果在 Dockerfile 中这么写:

1
COPY ./package.json /app/

这并不是要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json。

所以为了避免出现路径的bug,最好是将要用的文件都复制到Dockerfile文件的同个目录下,然后docker build nginx .时采用默认的Dockerfile当前路径.如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

具体细节和docker build 的其他用法参考文档