Docker快速入门(二) Docker快速入门(一)

上篇文章《Docker快速入门(一)》介绍了docker的基本概念和image的相关操做,本篇将进一步介绍image,容器和Dockerfile。php

1 image文件

(1)Docker 把应用程序及其依赖,打包在 image 文件里面。
(2)只有经过这个image文件,才能生成 Docker 容器。image 文件能够看做是容器的模板。Docker 根据 image 文件生成容器的实例。
(3)同一个 image 文件,能够生成多个同时运行的容器实例。
(4)image 是二进制文件。实际开发中,一个 image 文件每每经过继承另外一个 image 文件,加上一些个性化设置而生成。
(5)image 文件是通用的,一台机器的 image 文件拷贝到另外一台机器,照样可使用。
(6)通常来讲,为了节省时间,咱们应该尽可能使用别人制做好的 image 文件,而不是本身制做。即便要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制做。
(7)为了方便共享,image 文件制做完成后,能够上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最经常使用的 image 仓库。此外,出售本身制做的 image 文件也是能够的。html

下面咱们举例介绍几个命令:node

docker container run hello-world

该命令会根据 image 文件,生成一个正在运行的容器实例。
注意:docker container run命令具备自动抓取 image 文件的功能。若是发现本地没有指定的 image 文件,就会从仓库自动抓取。所以,前面的docker image pull命令并非必需的步骤。
若是运行成功,以下:python

[@sjs_123_183 ~]# docker container run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://cloud.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/engine/userguide/

[@sjs_123_183 ~]#

输出以上内容后,hello world就会中止运行,容器自动终止。有些容器提供服务,并不会自动终止,如Ubuntu的image:mysql

[@sjs_123_183 ~]# docker container run -it ubuntu bash
root@cd902f829884:/#
root@cd902f829884:/# pwd
/
root@cd902f829884:/#  

这时候,咱们打开另外一个终端,经过:linux

docker container ls  

能够看到本机正在运行的容器实例:nginx

[@sjs_123_183 ~]# docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
cd902f829884        ubuntu              "bash"              50 seconds ago      Up 50 seconds                           nifty_pike

此时经过git

docker container kill [CONTAINER ID]  

该命令能够终止正在运行的容器。以下:golang

[@sjs_123_183 ~]# docker container ls
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
cd902f829884        ubuntu              "bash"              4 minutes ago       Up 4 minutes                            nifty_pike
[@sjs_123_183 ~]# docker container kill cd9
cd9

2 容器文件

image 文件生成的容器实例,自己也是一个文件,称为容器文件。也就是说,一旦容器生成,就会同时存在两个文件: image 文件和容器文件。并且关闭容器并不会删除容器文件,只是容器中止运行而已。web

docker container kill [CONTAINER ID]    # 列出正在运行的容器
docker container ls --all              # 列出本机全部容器,包括终止运行的容器  

以下:

[@sjs_123_183 ~]# docker container ls --all
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
cd902f829884        ubuntu              "bash"                   6 minutes ago       Exited (137) About a minute ago                       nifty_pike
3ca03ca1cd7c        hello-world         "/hello"                 11 minutes ago      Exited (0) 11 minutes ago                             laughing_booth
d225defb10db        ubuntu              "bash"                   13 minutes ago      Exited (137) 13 minutes ago                           fervent_galileo
77182065e27d        ubuntu              "bash"                   16 minutes ago      Exited (127) 15 minutes ago                           ecstatic_kilby
ebf4e2421f51        hello-world         "/hello"                 20 minutes ago      Exited (0) 20 minutes ago                             heuristic_albattani
081ccb2d6eed        nginx               "nginx -g 'daemon of…"   4 weeks ago         Exited (0) 4 weeks ago                                adoring_poincare
fea01895c580        hello-world         "/hello"                 4 weeks ago         Exited (0) 4 weeks ago                                vibrant_goldwasser

命令的输出结果之中,包括容器的 ID, 即CONTAINER ID。不少地方都须要提供这个 ID,好比上一节终止容器运行的docker container kill命令。
终止运行的容器文件,依然会占据硬盘空间,可使用docker container rm [CONTAINER ID]命令删除。参见:

[@sjs_123_183 ~]# docker container ls --all
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                            PORTS               NAMES
cd902f829884        ubuntu              "bash"                   6 minutes ago       Exited (137) About a minute ago                       nifty_pike
3ca03ca1cd7c        hello-world         "/hello"                 11 minutes ago      Exited (0) 11 minutes ago                             laughing_booth
d225defb10db        ubuntu              "bash"                   13 minutes ago      Exited (137) 13 minutes ago                           fervent_galileo
77182065e27d        ubuntu              "bash"                   16 minutes ago      Exited (127) 15 minutes ago                           ecstatic_kilby
ebf4e2421f51        hello-world         "/hello"                 20 minutes ago      Exited (0) 20 minutes ago                             heuristic_albattani
081ccb2d6eed        nginx               "nginx -g 'daemon of…"   4 weeks ago         Exited (0) 4 weeks ago                                adoring_poincare
fea01895c580        hello-world         "/hello"                 4 weeks ago         Exited (0) 4 weeks ago                                vibrant_goldwasser
[@sjs_123_183 ~]# docker container rm 3ca ebf
3ca
ebf
[@sjs_123_183 ~]# docker container ls --all
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                        PORTS               NAMES
cd902f829884        ubuntu              "bash"                   10 minutes ago      Exited (137) 5 minutes ago                        nifty_pike
d225defb10db        ubuntu              "bash"                   17 minutes ago      Exited (137) 17 minutes ago                       fervent_galileo
77182065e27d        ubuntu              "bash"                   20 minutes ago      Exited (127) 19 minutes ago                       ecstatic_kilby
081ccb2d6eed        nginx               "nginx -g 'daemon of…"   4 weeks ago         Exited (0) 4 weeks ago                            adoring_poincare
fea01895c580        hello-world         "/hello"                 4 weeks ago         Exited (0) 4 weeks ago                            vibrant_goldwasser  

上面的例子是咱们经过容器ID,删除两个hello-world容器文件。

3 编写Dockerfile 

学会使用 image 文件之后,接下来的问题就是,如何能够生成 image 文件?若是你要推广本身的软件,势必要本身制做 image 文件。这就须要用到 Dockerfile 文件。它是一个文本文件,用来配置 image。Docker 根据 该文件生成二进制的 image 文件。镜像的定制实际上就是定制每一层所添加的配置、文件。若是咱们能够把每一层修改、安装、构建、操做的命令都写入一个脚本,用这个脚原本构建、定制镜像,能够重复的使用、镜像构建透明。Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层所以每一条指令的内容,就是描述该层应当如何构建

以 nginx 镜像为例,此次咱们使用 Dockerfile 来定制。

mkdir -p /search/odin/xnginx    # 新建一个目录
cd  /search/odin/xnginx         # cd 到该目录下
touch Dockerfile                # 建立一个叫Dockerfile的文件    

在Dockerfile中写入下面两行,保存退出:

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

这个Dockerfile很简单,涉及到了两条指令,FROM 和 RUN。

(1)FROM指定基础镜像
定制镜像,那必定是以一个镜像为基础,在其上进行定制。就像咱们以前运行了一个 nginx 镜像的容器,再进行修改同样,基础镜像是必须指定的。
FROM 就是指定基础镜像,所以一个 Dockerfile 中 FROM 是必备的指令而且必须是第一条指令

在 Docker Store 上有很是多的高质量的官方镜像,有能够直接拿来使用的服务类的镜像,如 nginx、redis、mongo、mysql、httpd、php、tomcat 等;也有一些方便开发、构建、运行各类语言应用的镜像,如 node、openjdk、python、ruby、golang 等。能够在其中寻找一个最符合咱们最终目标的镜像为基础镜像进行定制。
若是没有找到对应服务的镜像,官方镜像中还提供了一些更为基础的操做系统镜像,如 ubuntu、debian、centos、fedora、alpine 等,这些操做系统的软件库为咱们提供了更广阔的扩展空间。

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

FROM scratch
...

以 scratch 为基础镜像的话,意味着你不以任何镜像为基础,接下来所写的指令将做为镜像第一层开始存在。
不以任何系统为基础,直接将可执行文件复制进镜像的作法并不罕见,好比 swarm、coreos/etcd。对于 Linux 下静态编译的程序来讲,并不须要有操做系统提供运行时支持,所需的一切库都已经在可执行文件里了,所以直接 FROM scratch 会让镜像体积更加小巧。使用 Go 语言 开发的应用不少会使用这种方式来制做镜像,这也是为何有人认为 Go 是特别适合容器微服务架构的语言的缘由之一。

(2)RUN 执行命令
RUN 指令是用来执行命令行命令的。因为命令行的强大能力,RUN 指令在定制镜像时是最经常使用的指令之一。其格式有两种:

格式一: 
shell 格式:RUN <命令>,就像直接在命令行中输入的命令同样。刚才写的 Dockerfile 中的 RUN 指令就是这种格式。
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html

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

RUN 就像 Shell 脚本同样能够执行命令,不少初学者在写Dockerfile的时候会像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 层镜像。这是彻底没有意义的,并且不少运行时不须要的东西,都被装进了镜像里,好比编译环境、更新的软件包等等。结果就是产生很是臃肿、很是多层的镜像,不只仅增长了构建部署的时间,也很容易出错。

正确的写法是:

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

(1)以前全部的命令只有一个目的,就是编译、安装 redis 可执行文件。
所以没有必要创建不少层,这只是一层的事情。这里没有使用不少个 RUN 对一一对应不一样的命令,而是仅仅使用一个 RUN 指令,并使用 && 将各个所需命令串联起来。将以前的 7 层,简化为了 1 层。在撰写 Dockerfile 的时候,要常常提醒本身,这并非在写 Shell 脚本,而是在定义每一层该如何构建。
(2)这里为了格式化还进行了换行。
Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,好比换行、缩进、注释等,会让维护、排障更为容易,这是一个比较好的习惯。
(3)还能够看到这一组命令的最后添加了清理工做的命令,删除了为了编译构建所须要的软件,清理了全部下载、展开的文件,而且还清理了 apt 缓存文件。
这是很重要的一步,咱们以前说过,镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。所以镜像构建时,必定要确保每一层只添加真正须要添加的东西,任何无关的东西都应该清理掉。
不少人初学 Docker 制做出了很臃肿的镜像的缘由之一,就是忘记了每一层构建的最后必定要清理掉无关文件

4 构建镜像

到目前为止,咱们明白了这个 Dockerfile 的内容,接下来就让咱们构建这个镜像吧。在 Dockerfile 文件所在目录执行:

docker build -t nginx:v2 .  

详细以下:

[@sjs_123_183 xnginx]# docker build -t nginx:v2 .
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM nginx
 ---> c5c4e8fa2cf7
Step 2/2 : RUN echo '<h1>Hello, Docker! Hello, xnginx!</h1>' > /usr/share/nginx/html/index.html
 ---> Running in e955070ac2c9
Removing intermediate container e955070ac2c9
 ---> 1beca7b40dee
Successfully built 1beca7b40dee
Successfully tagged nginx:v2
[@sjs_123_183 xnginx]#

从命令的输出结果中,咱们能够清晰的看到镜像的构建过程。
在 Step 2 中,如同咱们以前所说的那样,RUN 指令启动了一个容器 e955070ac2c9,执行了所要求的命令,并最后提交了这一层 1beca7b40dee,随后删除了所用到的这个容器 e955070ac2c9。
这里咱们使用了 docker build 命令进行镜像构建。其格式为:

docker build [选项] <上下文路径/URL/->

这里咱们指定了最终镜像的名称 -t nginx:v2,如今让咱们启动本身构建的容器:

docker run --name webserver -d -p 80:80 nginx:v2

这条命令会用 nginx 镜像启动一个容器,命名为 webserver,而且映射了 80 端口,这样咱们能够用curl 命令去访问这个 nginx 服务器。详情以下:

[@sjs_123_183 ~]# docker run --name webserver -d -p 80:80 nginx:v2
a9f012a96d98262bffd30286c9d23dfe929b032c2149fa67105d29ddde71b763
[@sjs_123_183 ~]# curl http://127.0.0.1:80
<h1>Hello, Docker! Hello, xnginx!</h1>
[@sjs_123_183 ~]#  

经过浏览器也是能够访问的,若是你是本机的浏览器须要访问:http://localhost。此处因为我在远程的linux上,只须要访问ip便可。

5 上下文路径

细心的同窗可能已经注意到docker build 命令最后有一个 . 号。. 表示当前目录,而 Dockerfile 就在当前目录,所以很多初学者觉得这个路径是在指定 Dockerfile 所在路径,这么理解实际上是不许确的。
前文已经说到docker build 的命令格式,这个 . 号是指定上下文路径。那么什么是上下文呢?

(1)首先咱们要理解 docker build 的工做原理。Docker 在运行时分为 Docker 引擎(也就是服务端守护进程)和客户端工具。Docker 的引擎提供了一组 REST API,被称为 Docker Remote API,而如 docker 命令这样的客户端工具,则是经过这组 API 与 Docker 引擎交互,从而完成各类功能。
所以,虽然表面上咱们好像是在本机执行各类 docker 功能,但实际上,一切都是使用的远程调用形式在服务端(Docker 引擎)完成。也由于这种 C/S 设计,让咱们操做远程服务器的 Docker 引擎变得垂手可得。

(2)当咱们进行镜像构建的时候,并不是全部定制都会经过 RUN 指令完成,常常会须要将一些本地文件复制进镜像,好比经过 COPY 指令、ADD 指令等。而 docker build 命令构建镜像,其实并不是在本地构建,而是在服务端,也就是 Docker 引擎中构建的。那么在这种客户端/服务端的架构中,如何才能让服务端得到本地文件呢
这就引入了上下文的概念。当构建的时候,用户会指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的全部内容打包,而后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会得到构建镜像所需的一切文件。

若是在 Dockerfile 中这么写:

COPY ./package.json /app/

这并非要复制执行 docker build 命令所在的目录下的 package.json,也不是复制 Dockerfile 所在目录下的 package.json,而是复制 上下文(context) 目录下的 package.json
所以,COPY 这类指令中的源文件的路径都是相对路径。这也是初学者常常会问的为何 COPY ../package.json /app 或者 COPY /opt/xxxx /app 没法工做的缘由,由于这些路径已经超出了上下文的范围,Docker 引擎没法得到这些位置的文件。若是真的须要那些文件,应该将它们复制到上下文目录中去。
如今就能够理解刚才的命令 docker build -t nginx:v2 . 中的这个 .,其实是在指定上下文的目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。

在docker build命令的输出内容中有这么一句:

Sending build context to Docker daemon  2.048kB  

这实际上就是发送上下文的过程。

(1)理解构建上下文对于镜像构建是很重要的,避免犯一些不该该的错误。
好比有些初学者在发现 COPY /opt/xxxx /app 不工做后,因而干脆将 Dockerfile 放到了硬盘根目录去构建,结果发现 docker build 执行后,在发送一个几十 GB 的东西,极为缓慢并且很容易构建失败。那是由于这种作法是在让 docker build 打包整个硬盘,这显然是使用错误。

(2)正确的作法是,将 Dockerfile 置于一个空目录下,或者项目根目录下
若是该目录下没有所需文件,那么应该把所需文件复制一份过来。若是目录下有些东西确实不但愿构建时传给 Docker 引擎,那么能够用 .gitignore 同样的语法写一个 .dockerignore,该文件是用于剔除不须要做为上下文传递给 Docker 引擎的。

(3)那么为何会有人误觉得 . 是指定 Dockerfile 所在目录呢?这是由于在默认状况下,若是不额外指定 Dockerfile 的话,会将上下文目录下的名为 Dockerfile 的文件做为 Dockerfile。这只是默认行为,实际上 Dockerfile 的文件名并不要求必须为 Dockerfile,并且并不要求必须位于上下文目录中,好比能够用 -f ../Dockerfile.php 参数指定某个文件做为 Dockerfile。固然,通常你们习惯性的会使用默认的文件名 Dockerfile,以及会将其置于镜像构建上下文目录中。

相关文章
相关标签/搜索