【Docker(三)】创建自己的镜像

有些话、适合烂在心里 提交于 2019-12-07 14:58:59

    创建镜像有两种方式:commit命令方式与Dockerfile方式。但是一般不建议使用commit命令方式。本文将两种方式均简单说明一下。

commit命令方式

    commit是直接在命令行使用,可以在某个镜像的容器基础上进行镜像定制。也就是说可以运行某个镜像的容器,在将容器进行自己需要的改动后再这个基础上定制镜像。

命令:docker commit [-a '作者信息' -m '镜像说明'] 容器ID 新镜像命名

    这样就定制了一个基于容器ID的新镜像

Dockerfile方式:

    镜像的定制实际上就是定制每一层所添加的配置、文件。如果我们可以把每一层修改、安装、构建、操作的命令都写入一个脚本,这个脚本就是 Dockerfile。

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

    应该在一个空白目录中,建立一个文本文件,并命名为 Dockerfile

$ mkdir 111
$ cd 111
$ vi Dockerfile

内容为:

FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
    FROM是制定基础镜像,所谓定制镜像,那一定是以一个镜像为基础,在其上进行定制。就像我们之前运行了一个 nginx 镜像的容器,再进行修改一样,基础镜像是必须指定的。而 FROM 就是指定基础镜像,因此一个 DockerfileFROM 是必备的指令,并且必须是第一条指令。    

    在 Docker Store 上有非常多的高质量的官方镜像,有可以直接拿来使用的服务类的镜像,如 nginxredismongomysqlhttpdphptomcat 等;也有一些方便开发、构建、运行各种语言应用的镜像,如 nodeopenjdkpythonrubygolang 等。可以在其中寻找一个最符合我们最终目标的镜像为基础镜像进行定制。

    如果没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操作系统镜像,如 ubuntudebiancentosfedoraalpine 等,这些操作系统的软件库为我们提供了更广阔的扩展空间。

    除了选择现有镜像为基础镜像外,Docker 还存在一个特殊的镜像,名为 scratch。这个镜像是虚拟的概念,并不实际存在,它表示一个空白的镜像。

FROM scratch
...

如果你以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将作为镜像第一层开始存在。

    RUN指令是用来执行命令行命令的。由于命令行的强大能力,RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  • shell 格式:RUN <命令>,就像直接在命令行中输入的命令一样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • exec 格式:RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式。

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

FROM debian:jessie

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.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 正确的写法应该是这样:

FROM debian:jessie

RUN buildDeps='gcc libc6-dev make' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-3.2.5.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 制作出了很臃肿的镜像的原因之一,就是忘记了每一层构建的最后一定要清理掉无关文件。

创建自己的镜像:

    现在我要创建一个Ubuntu16.04的镜像,并包含有webrtc的媒体服务器kurento,使这个镜像的容器的启动运行kurento例程。我将这个镜像创建过程分开,一半用commit命令方式,一半用Dockerfile方式。

    1、拉取官方Ubuntu16.04镜像

docker search ubuntu:16.04


选择第一个ubuntu:

docker pull ubuntu

运行docker images查看:


2、运行并进入容器,安装上openjdk8,maven以及kurento并退出容器

docker run -it --name ubuntu0702 ubuntu:16.04

进入容器后,安装openjdk8,maven:

apt-get update
apt-get install openjdk-8-jdk
apt-get install maven
安装kurento(参照官网http://doc-kurento.readthedocs.io/en/stable/user/installation.html):

# KMS for Ubuntu 16.04 (Xenial)
DISTRO="xenial"

apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 5AFA7A83

tee "/etc/apt/sources.list.d/kurento.list" >/dev/null <<EOF
# Kurento Media Server - Release packages
deb [arch=amd64] http://ubuntu.openvidu.io/6.7.1 $DISTRO kms6
EOF

apt-get update
apt-get install kurento-media-server




退出容器:

exit     按(ctrl+D)
3、使用commit制作镜像

查看当前所有容器

docker ps -a

第一个容器ID是759e74e5aba9是刚刚做了改动的容器,我们就基于这个容器来定制镜像。

docker commit -a 'cxy' -m 'jdk and maven' 759e74e5aba9 ubuntu0702-1

下面就是新的镜像唯一ID说明定制镜像成功:

使用docker images查看镜像已存在:


运行这个镜像:

docker run -it --name ubuntu-1 ubuntu0702-1


启动kurento:

service kurento-media-server start

运行成功。

4、使用Dockerfile构建镜像

其实上面用commit命令定制的镜像也完全可以使用Dockerfile来实现,并且更简单。

接着上面来,我们在前三步创建的镜像ubuntu0702-1的基础上来使用Dockerdfile来构建。

新建空白目录,建立文件名为Dockerfile的文件:

$ mkdir myubuntu
$ cd myubuntu
$ vi Dockerfile

内容为:

#以镜像ubuntu0702-1为基础镜像
FROM ubuntu0702-1
#将当前目录下的222目录里所有kurento例程拷贝到镜像容器/目录下
COPY /222 /

我在myubuntu/222目录下拷贝了自己的例程:


创建镜像kurento_rtsp2sip :

docker build -t='kurento_rtsp2sip' .

最后那个点"."不能少。表示在当前目录,这是在制定上下文路径,关于这里参考https://yeasy.gitbooks.io/docker_practice/content/image/build.html

查看镜像以及运行容器:

docker images

成功。

5、创建一个镜像,使得其容器能运行就运行kurento例程

在kurneto_rtsp2sip基础上构建一个打开容器就能运行例程的镜像kurneto-ipcom。

Dockerfile文件如下:

FROM kurento_rtsp2sip
CMD cd /kurento-tutorial-java-test/rtsp2sip-0701-websocket && service kurento-media-server start && mvn exec:java 

构建好镜像后运行容器,主机端口8443映射到容器端口8443(因为例程我制定的端口就是8443)。


打开本机https://localhost:8443,如下:

例程运行成功。

完毕。

参考文档:https://yeasy.gitbooks.io/docker_practice/content/image/build.html




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!