我们知道,镜像是多层存储,每一层都是在前一层的基础上进行的修改;容器以镜像为基础,同样是多层存储!

一、docker commit

1)启动一个容器并命名为webserver

docker run -it --name webserver -p 80:80 nginx /bin/bash

     同时映射了80端口,这样可以使用浏览器去访问这个nginx服务器。由于我在本机运行的docker,直接通过http://localhost访问,浏览器显示的画面如下。如果在虚拟机上运行docker,则需要将localhost换成虚拟机的IP

docker镜像最大支持 docker commit镜像大_运维

2)修改文件

使用'<h1> hello,my name is docker !</h1>' 替换/usr/share/nginx/html/index.html内的内容

echo '<h1> hello,my name is docker !</h1>' > /usr/share/nginx/html/index.html

3)刷新之后看到如下页面

docker镜像最大支持 docker commit镜像大_nginx_02

4)查看对容器进行了哪些修改

exit
docekr diff webserver

结果如下:

docker镜像最大支持 docker commit镜像大_docker镜像最大支持_03

可以发现除了对应的html文件之外,还有很多其他文件也被修改了!

5)将修改后的容器保存为镜像

docker commit \
    --author "Tao Wang <twang2218@gmail.com>" \
    --message "修改了默认网页" \
    webserver \
    nginx:v2

其中--author是指定修改的作者,--message记录本次修改的内容,这些都可以省略。

可以使用docker image ls查看新定制的镜像

使用docker history nginx:v2查看镜像内的历史记录

docker镜像最大支持 docker commit镜像大_docker_04

运行这个镜像,并访问

docker run --name web2 -d -p 81:80 nginx:v2

这里将新的容器命名为web2,且映射到了81端口,访问http://localhost:81可以看到和之前一样的页面。

缺点

       使用docker commit可以很方便的制作镜像,但是这种方式对于其他使用者是完全未知的,别人并不知道我们对镜像做了哪些修改,这种修改是永久性的。如果新的使用者使用该镜像创建容器修改之后继续使用docker commit,将会导致越来越臃肿。

二、dockerfile制作镜像

       如果可以把每一层的修改、安装、构建、操作的命令都写入一个脚本,用这个脚本来制作镜像,上述问题将不再存在。这个脚本就是dockerfile

       Dockerfile是一个文本文件,其中包含了一条条的指令,每一条指令构建一层,因此每一条指令的内容就是描述当前层应该如何构建。

       仍然以定制nginx镜像为例,使用dockerfile方式定制镜像

1)在本地主机新建空白文件夹myDKFile,并创建Dockerfile

mkdir myDKFile
cd myDKFile
touch Dockerfile

docker镜像最大支持 docker commit镜像大_docker_05

2)打开dockerfile并输入以下内容

FROM nginx
RUN echo '<h1>Hello,use dockerfile to build jingxiang!!</h1>' > /usr/share/nginx/html/index.html

其中:FROM指定基础镜像,RUN指定进行了哪些操作

RUN的格式有两种:

格式1:shell格式:RUN <命令>  ,这种方式就想之间在命令行中输入的命令一样,如上面的Dockerfile中的RUN 命令

格式2:exec格式:RUN ["可执行文件”,"参数1","参数2"],更像是函数调用中的格式

RUN可以像shell脚本一样执行命令,也可以像shell脚本一样把每个命令对应一个RUN。如

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也不例外,这种方式会创建7层镜像,完全没有意义。而且运行时很多不需要的东西,比如编译环境、更新的软件包等。会产生非常臃肿,多层的镜像,不仅增加了构建部署的时间,也容易出错。

上面的写法可以改成如下:

FROM debian:stretch

RUN set -x; 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

      一共14行!之前的命令只有一共目的,就是下载、编译、安装redis。因此可以用一共RUN,并且用&&把不同的指令串联起来,将之前的7层简化为1层,使用\进行换行。Dockerfile支持shell累的行尾部添加\的命令换行方式,以及行首#进行注释的格式。

      同时,还添加了清理工作的命令,删除为了编译构建所需要的软件,清理了所有下载、展开的文件,还清理了apt缓存文件。镜像是多层的,每一层的东西并不会在下一层被删除,会一直随着镜像存在,因此构建镜像时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

第3行set -x的意思,用buildDeps

中间7行和之前的完全一样

第11行

第12、13行删除对应的压缩包和新建的文件夹

第14行删除和运行时无关的软件包

3)在dockerfile文件所在目录执行docker build -t nginx:v4 .

docker镜像最大支持 docker commit镜像大_docker_06

4)查看结果---以nginx:v4镜像操作

依次输入以下指令

docker run --name DKFile -d -p 85:80 nginx:v4
docker exec -it DKFile bash

docker镜像最大支持 docker commit镜像大_运维_07

本地浏览器输入http://localhost:85,结果为:

docker镜像最大支持 docker commit镜像大_redis_08

2.1dockerfile中配置环境变量

      理论上你在ubuntu终端输入的所有指令都可以在run后面写入,但对于环境变量如果你希望配置之后使用容器时还有效,则应该使用ENV

      如在终端命令 export CC=clang-11, export CXX=clang++-11

      在run后的写法为

FROM ubuntu:20.04
RUN export CC=clang-11 \
   && export CXX=clang++-11

    这种方式使用docker build生成镜像,然后使用docker run生成容器之后使用export -p查看发现并无对应的变量设置。如果想实现这样的目的,应该改为

FROM ubuntu:20.04
ENV  CC=clang-11 
ENV CXX=clang++-11
RUN apt-get update

注意:ENV在dockerfile中的位置也很重要,为了保险,可以放在RUN之前