第四章 使用docker镜像和仓库
docker镜像是由文件系统叠加而成。最底端是一个引导文件系统,即bootfs,第二层是root文件系统rootfs。
一次同时加载多个文件系统,但从外面看起来,只能看到一个文件系统,联合加载会把各层文件系统叠加起来,这样最终的文件系统会包含所有底层的文件和目录
docker将这样的文件系统称之为镜像。
在同个镜像中,docker用写时复制来保证镜像都是只读的,并且以后永远不会变化。
docker images
利用这个命令可以看到我们的镜像。
镜像保存在/var/lib/docker目录下。其中containers目录是所有的容器。
这个镜像是我们在公有registry中拉取下来的,地址是这个。dockerhub
docker pull
可以拉取镜像仓库中的镜像。
通常会下载最新的镜像,可以在镜像名后面增加标签来指定要下载的镜像内容。
在拉取镜像的时候指定标签是个好习惯,从而能让你知道你要拉取的镜像的内容。
区分仓库名,docker中的仓库有两种,用户仓库和顶层仓库。
用户仓库是docker用户自己创建的,可能存在风险,命名由两部分组成,用户名/仓库名。
顶层仓库是有docker管理人管理的,是安全的,命名只有仓库名部分,如Ubuntu。
查找镜像
用docker search
可以在docker仓库中查找镜像,查找列表可以看到仓库名,镜像描述,用户评价,是否官方,自动构建
构建镜像
最好使用docker build命令和Dockerfile文件。虽然也可以使用docker commit,但是不推荐。
docker commit
docker可以将当前状态的容器,提交成为一个镜像,这样就不用了每次都需要重新执行相关的命令成为一个你想要的镜像了。而且他是基于镜像差异去提交的,所以该次commit会很小。
Dockerfile
利用Dockerfile可以构建一个属于你自己的镜像,而且镜像构建过程是透明的。
比如新建一个目录,在该目录下创建Dockerfile文件。内容是:
FROM ubuntu
MAINTAINER huangzelin "[email protected]" #标识该镜像的所有者和联系方式
RUN apt-get update
RUN apt-get install -y nginx
RUN echo 'this is huangzelin' > /usr/share/nginx/html/index.html
EXPOSE 80 #暴露该镜像的80端口
在当前目录下执行
docker build -t="static_web"
等构建完毕就能看到刚刚构建的镜像了。
除了指定目录以外,还可以通过指定git地址来获取Dockerfile,这样在多服务器的情况下,也能统一一份Dockerfile了。
运行该镜像。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker run -t -i static_web /bin/bash
root@038c56a4542d:/# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
可以看到该镜像已经安装了nginx。
docker在构建的过程中会将每一步的构建结果都提交为镜像,所以每一步都会有缓存,如果你在第四步发生了变动,那么第一到三步是不用重新构建的。所以最基础的构建应该放在前面 如果不想要使用缓存,可以在build的时候指定
docker build --no-cache -t="static_web" .
利用docker history
可以查看镜像的构建过程。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
static_web latest 6c9a53fca598 19 minutes ago 158MB
9eed8ce3f990 5 days ago 158MB
ubuntu latest d70eaf7277ea 4 weeks ago 72.9MB
hello-world latest bf756fb1ae65 10 months ago 13.3kB
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker history 6c9a53fca598
IMAGE CREATED CREATED BY SIZE COMMENT
6c9a53fca598 19 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B
a2283161422c 19 minutes ago /bin/sh -c echo 'this is huangzelin' > /usr/… 19B
631da22a6ab8 19 minutes ago /bin/sh -c apt-get install -y nginx 59.2MB
136d23864f2b 19 minutes ago /bin/sh -c apt-get update 26.1MB
78e58f68d48e 20 minutes ago /bin/sh -c #(nop) MAINTAINER huangzelin "83… 0B
d70eaf7277ea 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B
4 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B
4 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 0B
4 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B
4 weeks ago /bin/sh -c #(nop) ADD file:435d9776fdd3a1834… 72.9MB
让nginx镜像能真正跑起来。
docker run -d -p 80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
-d:是让nginx以分离的方式跑起来。
nginx -g "daemon off;":这是在指定容器中要运行的命令。
-p:是暴露容器的端口给宿主机,这里没有指定绑定到宿主机的那些端口,就会随机选择一个4915365535一个比较大的端口号来映射到容器的80端口中。
可以通过docker ps
来查看端口分配情况。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ec4077f4ae92 static_web "nginx -g 'daemon of…" 11 minutes ago Up 11 minutes 0.0.0.0:32768->80/tcp test_nginx
可以看到32768指向了容器的80端口。
打开本地的32768页面。
还可以通过docker port查看端口映射情况。
root@DESKTOP-3JK8RKR:/data/docker_test/static_web# docker port test_nginx 80
0.0.0.0:32768
但是这种随机的端口实用性不大,我们更多的是指定特定的端口去映射。
docker run -d -p 80:80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
这样我们就指定了本地的80端口映射到容器的80端口了,但是需要注意,一个端口只能被映射一次。
还可以
docker run -d -p 127.0.0.1:80:80 --name test_nginx static_web nginx -g "daemon off;"
ec4077f4ae9259d63b2b6fbfb541a70129d4b0c0deaeae26d6f6f60c2463623b
来指定绑定的ip。
docker还有个-P选项,这个选项会暴露容器中EXPOSE指定的端口,不过不建议,因为多个端口提供服务违反了一个docker一个容器只运行一个服务的建议。
Dockerfile指令
CMD
cmd用于指定一个容器启动时要运行的命令,这有点儿类似RUN指令,只是RUN指令是指定镜像被构建时要运行的命令,而CMD是指定容器被启动时要运行的命令。这和docker run在后面指定容器要运行的命令非常相似。
当然也可以为要运行的命令指定参数,如
CMD ["/bin/bash", "-l"]
需要注意的是,我们在docker run的时候,显式给他启动命令,会覆盖Dockerfile里面的CMD指令。
如果在Dockerfile中指定了多条CMD命令,也只有最后一条会生效。
ENTRYPOINT
ENTRYPOINT和CMD又有点类似,刚刚提到,docker run会覆盖掉CMD指令,那我们有时候并不想覆盖指令,而只是想指定启动参数呢。
那么ENTRYPOINT就能做到。
ENTRYPOINT ["/usr/sbin/nginx"]
docker run -d -p 127.0.0.1:80:80 --name test_nginx static_web -g "daemon off;"
那么我们的容器启动命令就是
["/usr/sbin/nginx", -g, "daemon off"]
和CMD组合能构成一个默认启动的Dockerfile。
ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]
WORKDIR
WORKDIR 会指定一个工作目录,进入到该目录内,ENTRYPOINT,RUN或CMD指定的程序将会在这个目录下执行。
比如:
WORKDIR /opt/webapp/db
RUN bundle install
WORKDIR /opt/webapp
ENTRYPOINT ["rackup"]
相当于我们cd到/opt/webapp/db目录下,然后执行安装bundle,之后再cd到/opt/webapp目录下,等待启动命令的启动。
我们可以在docker run的时候指定-w在运行时覆盖工作目录。
docker run -ti -w /var/log ubuntu pwd
/var/log
可以看到目录切到了/var/log下面。
ENV
ENV用来设置镜像的环境变量。
ENV RVM_PATH /home/rvm/
加入环境变量之后可以直接使用
WORKDIR $RVM_PATH
可以 docker run的时候用-e标志来传递环境变量。
docker run -ti -e "WEB_PORT=8080" ubuntu env
USER
USER指令用来指定该镜像会以什么样的用户去运行。
默认是root。
也可以在docker run的时候指定-u去修改用户。
VOLUME
VOLUME指令用来向基于镜像创建的容器添加卷。这个卷可以在一个或多个容器中共享数据和持久化。
卷会一直存在直到没有任何容器再使用它。
卷功能让我们可以讲数据,数据库或者其他东西添加到镜像中,而不是将这些东西提交到镜像中。
ADD
ADD指令用来将构建环境的文件或目录复制到镜像中。
ADD software.lic /opt/application/software.lic
指向源文件的位置参数还可以是一个URL。
docker判断文件还是目录是看后面有没有以/
结尾。
而且如果传输的源文件是tar包,docker还会帮我们解压缩。
ADD lastest.tar.gz /var/www/wordpress/
这条命令会把lastest.tar.gz解压缩到/var/www/wordpress/目录下。不过url的行为可能不一样。
PS:ADD会使构建缓存失效。
COPY
COPY和ADD类似,但是COPY不能从URL中获取数据,也不会自动解压缩。
ONBUILD
ONBUILD指令能为镜像添加触发器。当一个镜像被用作其他镜像的基础镜像时,触发器会被执行。可以认为这些指令是在FORM之后指定的。
ONBUILD ADD ./app/src
ONBUILD RUN cd /app/src && make
ONBUILD指令可以通过在镜像上运行docker inspect命令来查看。
ONBUILD只会被继承一次,所以只会被子镜像中执行,而不会在孙子镜像中执行。
删除镜像
如果你不需要一个镜像了,你可以用
docker rmi image_name
进行删除。