Docker新手入门之四:Docker镜像和仓库的基本使用
接下来,我们将要继续讲解Docker镜像的相关概念与操作。
Docker镜像是启动容器的基石。
本文中将会讲解如何管理、修改镜像以及镜像存储仓库Registry的相关知识。
什么是Docker镜像
Docker镜像是由文件系统叠加而成。最底端是一个文件引导系统,即bootfs。Docker用户不会与引导文件系统有直接的交互。Docker镜像的第二层是root文件系统rootfs,通常是一种或多种操作系统,例如ubuntu等。在Docker中,文件系统永远都是只读的,在每次修改时,都是进行拷贝叠加从而形成最终的文件系统。Docker称这样的文件为镜像。一个镜像可以迭代在另一个镜像的顶部。位于下方的镜像称之为父镜像,最底层的镜像称之为基础镜像。最后,当从一个镜像启动容器时,Docker会在最顶层加载一个读写文件系统作为容器。
Docker的这种机制我们称之为写时复制。
查看镜像列表
接下来,我们将详细了解一下关于镜像相关的一些操作。首先是查询镜像列表:
sudo docker images
该命令可以用于查找当前系统中所有存在的镜像列表。Ps:本地镜像默认保存在Docker宿主机的/var/lib/docker目录下。所有的镜像都是保存在仓库中,而仓库位于Registry中。默认的Registry是Docker公司运营的Docker Hub。每个镜像仓库都可以存放很多的镜像。例如ubuntu镜像仓库存放着各种不同版本的Ubuntu镜像。使用如下命令可以拉取镜像:
sudo docker pull ubuntu
上述命令会拉取所有版本的ubuntu镜像到本地。为了区分同一个仓库中不同的镜像,Docker提供了一种tag的功能。我们可以给每个版本的镜像添加一个唯一的tag来标识该镜像。此时,镜像的名称如下:仓库名称:tag。我们在运行镜像或拉取镜像时,可以直接指定对应的标签。例如:
sudo docker run -it ubuntu:16.04 /bin/bash
默认情况下,在run中如果没有指定镜像的tag,将会默认去寻找latest标签的镜像。除了仓库名称和tag以外,我们还可以将镜像分为两个类别:一种是Docker用户自己创建的用户仓库,还有一种是Docker官方维护的顶层仓库。用于仓库的完整名称是由用户名称/仓库名称组成的。而对于顶层仓库而言,则没有用户名称,直接是仓库名称。总结一下,对于一个仓库而言,完成的格式如下:[用户名称/]仓库名称:tag。
拉取镜像
在使用docker run从一个镜像启动容器时,Docker首先会现在本地查找是否存在该镜像。如果在本地没有找到该镜像,则会先从Dockers Hub上下载该镜像后在运行。Ps:如之前所说,如果没有指定tag,则默认使用latest标签。
查找镜像
我们如果想要从Docker Hub查找有哪些公共的可用镜像时,可以使用如下命令:
sudo docker search keywords
构建镜像
在上面的内容中,我们已经了解了如下查询,拉取,运行镜像,那么接下来我们将会继续学习如何修改、更新和管理自己的镜像。构建镜像的方式有以下两种:
使用docker commit构建使用docker build和Dockerfile文件来构建
相比较而言,我们更加推荐使用方法2来构建。因为方法2更加的灵活和强大。Ps:我们通常并不是从零开始构建一个镜像,而是从一个base镜像开始修改构建的。在接下来的内容中,我们首先将会简单的讲解一下如何通过docker commit来修改镜像。但更多的精力将会用于详细讲解如何利用dockerfile来构建镜像。
创建docker hub账号
在构建镜像中,很重要的一环就是共享和发布镜像。通常,我们都会将镜像推送到Docker Hub来共享镜像。我们需要进入Docker Hub首先注册一个账号:https://hub.docker.com/注册完成后,我们可以通过命令行在本地绑定对应的Docker Hub账号。
sudo docker login# Username: ***# Password:# Email: ***# Login Succeed
使用docker commit命令来创建镜像
创建Docker镜像的第一种方法就是通过commit命令。流程简介如下:
根据某个镜像创建一个容器对该容器进行一些修改提交该容器并生成一个新的镜像。
示例:
sudo docker run -it ubuntu /bin/bashroot@b3f9427a5039:/# apt-get -yqq updateroot@b3f9427a5039:/# apt-get -y install apache2root@b3f9427a5039:/# exit
可以看到,在这个过程中,我们首先根据ubuntu镜像创建了一个容器,容器id为b3f9427a5039。接下来,我们对这个容器进行了一些修改,例如安装了Apache。安装完成后,我们退出了该容器。下面,我们将会使用commit命令来创建一个新的镜像。
sudo docker commit b3f9427a5039 wangzhe0912/apache2
上面的命令表示将我们刚才的容器创建为一个wangzhe0912用户下的apache镜像。接下来,我们可以使用docker images来查看是否有该新增的镜像。
ps:在commit命令中,我们可以添加更多的参数来对镜像进行描述:
-m=”message” 可以用于对提交的镜像添加一些文本描述–author=”person” 可以用于添加提交人信息提交时,我们可以设置tag。
示例:
sudo docker commit -m=”install apache” –author=”wangzhe0912″ b3f9427a5039 wangzhe0912/apache2:v1
接下来,我们可以使用
docker inspect 镜像名
命令来查看镜像的一些详细信息:
docker inspect wangzhe0912/apache2:v1
下面,我们也可以直接根据我们新建的镜像来创建一个容器:
sudo docker run -it wangzhe0912/apache2:v1 /bin/bash
使用dockerfile来构建镜像
在上面的内容中,我们简单的讲解了如何通过commit来创建新的镜像。
下面,我们将会继续将讲解如何使用Dockerfile来创建镜像,这是一种更加强大和灵活的方式。首先,我们需要创建一个工作目录,并在目录中创建一个Dockerfile文件。
mkdir workdircd ./workdirtouch Dockerfile
这个目录我们通常称之为构建环境,Docker会在构建镜像时将构建上下文和该文件上下文的文件和目录传递给Docker守护进程。我们编辑Dockerfile如下:
# Version: 0.0.1FROM ubuntu:14.04MAINTAINER Wangzhe0912 “[email protected]”RUN apt-get updateRUN apt-get install -y nginxRUN echo “Hi, I am your contrainer” > /usr/share/nginx/html/index.htmlEXPOSE 80
下面,我们来详细剖析一下这段代码的含义:
每个Dockerfile的第一行指令都应该是FROM开头。FROM用于制定一个已经存在的镜像,后续指令都是基于该镜像来运行。这个镜像我们称之为基础镜像。MAINTAINER后说明该作者和作者邮箱信息。接下来是三条RUN指令。默认情况下,指令后的内容会通过/bin/sh -c来执行。最后设置了EXPOSE,该指令用于指定容器内的应用程序会使用哪些端口(可以指定多个)。PS:出于安全考虑,默认Docker不会自动打开这些端口,需要在docker run指令中指定需要打开的端口。
那么这段代码在运行过程中的机制是什么样子的呢?
首先从基础镜像ubuntu:14.04中启动一个容器执行一条run指令执行类似commit指令,提交一个新的镜像层。基于步骤3提交的镜像运行一个新的容器。返回步骤2,继续执行下一条运行指令,直至没有运行指令。
Ps:这样的优点的如果在Dockerfile中,存在某个步骤执行失败,我们将会得到上一步中生成的镜像,利用该镜像,我们可以进行单步调试。
基于dockerfile构建新镜像
当我们写完Dockerfile文件后,可以通过执行docker build命令来执行Dockerfile中的所有指令并生成一个新的镜像。
示例如下:
docker build -t=”wangzhe0912/nginx:v1″` .
其中,-t=”wangzhe0912/nginx:v1″指定了新生成镜像的镜像名称。 .表示的是在当前目录下寻找Dockerfile文件。
除了在本地寻找Dockerfile文件之外,我们还可以直接指定git仓库来寻找Dockerfile文件。
docker build -t=”wangzhe0912/nginx:v1″` [email protected]:wangzhe0912/docker_web
Ps:如果在构建目录中存在.dockerignore命令的文件时,那么该文件内容将会被按行分割,按行匹配过滤,类似于.gitignore文件。
Dockerfile和构建缓存
Docker在构建过程中,会从在一定的缓存机制。例如我们修改了Dockerfile的第五行后重新进行构建,那么构建过程中前四行将不会进行重复执行,而是直接从缓存中进行读取。如果我们希望强制忽略缓存时,可以额外添加–no-cache参数进行设置。
sudo docker build –no-cache -t=”wangzhe0912/nginx:v1″ .
基于构建缓存的Dockerfile模版
为了合理利用缓存机制,而又不需要手工添加命令强制清除缓存,那么可以借鉴如下模板:
FROM ubuntu:14.04MAINTAINER Wangzhe0912 “[email protected]”ENV REFRESHED_AT 2018-01-28RUN apt-get update…
观察一下该模板,我们在其中添加了一行ENV。 ENV的作用是设置环境变量。而在此时,我们的目的是用于刷新构建和缓存,当ENV的值发生变化时,将会忽略后续的缓存,重新执行。而当当ENV的值没有发生变化时,则可以充分利用缓存的机制快速构建。
查看新镜像
当构建完成后,我们就完成了一个新镜像的构建。我们可以使用如下命令来查看该镜像的完整构建过程:
docker history image_name/id
从新镜像启动容器
下面,我们将使用刚才构建的镜像来详细了解一下nginx镜像该如何运行和使用。下面我们以如下命令进行讲解:
docker run -d -p 80 –name wangzhe_nginx wangzhe0912/nginx:v1 nginx -g “daemon off;”
其中:
-d表示以后台程序进行运行-p 80用于指定在运行时公开80端口给宿主机。默认情况下,宿主机会在随机选择一个端口号映射到容器的80端口上。当然,我们也可以指定用宿主机的某个端口(例如8080)来映射到容器的80端口,使用方式如下:-p 8080:80。当然,还有一种更加简单的方式,之前我们在Dockerfile文件中指定了EXPOSE对应端口,我们可以直接使用-P来打开在Dockerfile文件中指定的端口。–name wangzhe_nginx指定容器的名称nginx -g “daemon off;”表示了容器需要执行的命令。
运行完成后,我们需要查看一下我们的容器映射到了宿主机的哪个端口中。可以执行如下命令docker ps来查看:
可以看到,宿主机的32768端口指向了容器的80端口。还有一种更加简单的端口映射查询方式如下:
docker port wangzhe_nginx 80# 0.0.0.0:32768
即指定容器名称和容器端口,可以直接查询映射的端口地址。下面,我们来访问一下该地址,看下我们的nginx服务是否已经正常启动。
curl localhost:32768# Hi, I am your contrainer
Dockerfile指令
在之前的学习中,我们已经学习到了一部分Dockerfile指令,例如FROM, ENV, RUN, EXPOSE等,在本节中,我们将会详细学习相关的一系列指令。
1.CMDCMD是一个容器启动时要运行的命令。例如,我们之前启动一个容器时的代码类似如下:
docker run -it wangzhe0912/nginx /bin/bash
其中,/bin/bash表示的是启动容器时需要执行的命令。
可以等效如下:
CMD [“/bin/bash”]
此外,我们还可以给运行命令参数相关参数,例如:
CMD [“/bin/bash”, “-l”]
PS:在启动容器时,可以通过docker run命令来覆盖CMD指令。还有在Dockerfile中只能指定一条CMD指令,如果指定多条,最后一条CMD指令将会生效。
2.ENTRYPOINTENTRYPOINT与CMD十分类似,相比较而言ENTRYPOINT更不容易被覆盖。简单的说,我们通常会设置可执行文件在ENTRYPOINT中,而在CMD中设置需要传入的参数。因为通常而言,默认运行的可执行文件通常不会发生变化,但是参数变化则相对可能会频繁一些。示例如下:
ENTRYPOINT [“/usr/sbin/nginx”]CMD [“-h”]
Ps:如果有需要,可以在docker run时,使用–entrypoint=”***”
来强制覆盖ENTRYPOINT。
3.WORKDIRWORKDIR用于在容器内设置工作目录,ENTRYPOINT和CMD命令会在指定的目录下执行。示例:
WORKDIR /opt/webappRUN bundle installWORKDIR /opt/webapp/dbENTRYPOINT [“rackup”]
Ps:-w可以在运行时覆盖工作目录。
例如:
docker run -it -w /var/log ubuntu pwd
4.ENV在镜像构建过程中设置环境变量。新设置的环境变量可以在后续任何的RUN指令中使用。
示例:
ENV RVM_PATH /home/rvm/RUN gem install unicorn
PS:在运行时可以使用-e来传递环境变量,示例:
docker run -it -e “WEB_PORT=8080” pwd
5.USERUSER指令用于设定该镜像会以什么用户去执行。默认会以root用户执行。可以在运行命令中使用-u进行覆盖。
示例:
docker run -it -u “nginx” pwd
6.VOLUMNVOLUMN用户指定向基于镜像创建的容器添加卷。卷是指可以存在于一个或者多个容器内的特定目录,这个目录可以绕过联合文件系统来共享数据或者数据持久化。以下是一些卷的特点:
卷可以在容器中共享和重用。对卷的修改是实时生效。对卷的修改不会影响镜像。卷会一直存在到没有容器使用他。
卷功能可以将数据、代码、数据库等信息添加到镜像而不是把这些内容提交到镜像。使用示例:
VOLUMN [“/opt/project”, “/data”]
7.ADDADD指令用于将构建环境下的文件和目录复制到镜像中。常用的场景例如安装一个应用程序。ADD指令需要源文件和目的文件位置两个参数。示例代码:
ADD software.lic /opt/applicaion/software.lic
Ps:ADD命令本身来有一些相对需要注意的点:ADD命令可以自动将一些压缩文件进行自动解压:
ADD latest.tar.gz /var/www/wordpress/
此外,如果目的文件夹不存在的话,Docker会自动帮助我们创建完整的路径。新创建的文件和目录的权限都是0755。还有一点是ADD指令会使之前的缓存全部失效。
8.COPYCOPY指令与ADD指令相类似,不过COPY只关注与复制文件,而不会做文件的提取和解压。
9.ONBUILDONBUILD指令可以为镜像添加触发器。当一个镜像被其他镜像用作基础镜像时,该镜像的触发器会执行。触发器可以是任何构建指令,通常是紧跟在FROM后面的。
ONBUILD ADD . /app/srcONBUILD RUN cd /app/src && make
将镜像推送至Docker Hub
当我们完成镜像的构建后,可以将其上传到Docker Hub中。我们可以使用docker push命令来推送Docker镜像。
sudo docker push wangzhe0912/nginx
推送完成后,我们可以在我们的Docker Hub中看到我们上传的镜像。
自动构建
除了我们通过命令行构建后推送镜像之外,Docker Hub本身来支持自动构建。自动构建只需要我们将Github中含有Dockerfile文件的仓库连接到Docker Hub即可。每次推送代码后,会自动触发构建一个新的镜像。
删除镜像
当我们不再需要某一个镜像时,我们可以删除该镜像。删除镜像的方式如下:
sudo docker rmi 镜像名称
执行该命令后,我们只是会删除本地的镜像,在Docker Hub中的镜像则不会自动删除。
搭建自己的Dockers Registry
利用Docker容器安装一个Registry十分简单,只需要运行对应的容器即可。
docker run -d -p 5000:5000 registry
此时,我们已经启动了一个本地的registry。下面,我们需要使用本地的registry为我们想要推送的镜像打好标签。
docker tag 镜像id 127.0.0.1:5000/wangzhe0912/nginx
然后直接直接docker push命令来推送即可。
docker push 127.0.0.1:5000/wangzhe0912/nginx
