Docker快速入门(二)

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

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 文件也是可以的。

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

docker container run hello-world

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

[@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:

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

这时候,我们打开另一个终端,通过:

docker container ls  

可以看到本机正在运行的容器实例:

[@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

此时通过

docker container kill [CONTAINER ID]  

该命令可以终止正在运行的容器。如下:

[@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 文件和容器文件。而且关闭容器并不会删除容器文件,只是容器停止运行而已。

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,以及会将其置于镜像构建上下文目录中。

原文地址:https://www.cnblogs.com/zingp/p/9009064.html

时间: 2024-10-10 08:38:46

Docker快速入门(二)的相关文章

Docker快速入门——Docker常用命令

Docker快速入门--Docker常用命令 一.Docker命令简介 1.Docker命令简介 Docker的命令清单可以通过运行sudo docker或者sudo docker help命令查看.Docker容器技术在不断演化过程中,Docker的子命令已经达到41个,其中核心子命令(例如:run)还会有复杂的参数配置.Docker命令根据功能和使用场景可以分为4个部分. 2.Docker用户组 Docker守护进程绑定的是一个unix ?socket,而不是TCP端口,默认的属主是root

Docker快速入门——Docker简介

Docker快速入门--Docker简介 一.Docker简介 1.Docker简介 Docker是Docker Inc公司开源的一项基于Ubuntu LXC技术构建的应用容器引擎,源代码托管在GitHub上,完全基于go语言开发并遵守Apache2.0协议开源.Docker可以让开发者打包应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的Linux版本机器上,也可以实现虚拟化.Docker容器完全使用沙箱机制,容器相互之间不会有任何接口,并且容器性能开销极低.Docker最初是在

Docker快速入门——Docker镜像制作

Docker快速入门--Docker镜像制作 一.Dockerfile脚本 1.Dockerfile脚本简介 Dockerfile是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容就是描述该层应当如何构建.Dockerfile文件示例如下: ## Dockerfile文件格式 # This dockerfile uses the ubuntu image # VERSION 2 - EDITION 1 # Author: docker_us

Docker快速入门——Docker-Compose

Docker快速入门--Docker-Compose 一.Docker-Compose简介 1.Docker-Compose简介 Docker-Compose项目是Docker官方的开源项目,负责实现对Docker容器集群的快速编排.Docker-Compose将所管理的容器分为三层,分别是工程(project),服务(service)以及容器(container).Docker-Compose运行目录下的所有文件(docker-compose.yml,extends文件或环境变量文件等)组成一

Docker快速入门实践-纯干货文章

Docker快速入门实践-纯干货文章,如果细看还能发现讲解视频呦!小伙伴们赶紧猛戳吧! Docker快速入门实践-纯干货文章 老男孩教育2016启用最新的官方博文地址: http://blog.oldboyedu.com/ 欢迎小伙伴们收藏关注,干货连连!

AS3多线程快速入门(二):图像处理[译]

原文链接:http://esdot.ca/site/2012/intro-to-as3-workers-part-2-image-processing 在<AS3多线程快速入门>系列教程的第一部分中,我们研究了AS3 Worker的基本原理,包括多种通信方式,还展示了一个简单例子:Hello World Worker. 在这篇文章里,我将更进一步,向你展示如何利用多线程做些有用的功能,比如图像处理!在这次例子中,我将一边给一个大位图应用锐化滤镜,一边让主UI线程持续保持在30fps的渲染帧率.

MySQL快速入门(二)

1 多表关联查询 从快速入门,我们已经学会了如何在一张表中读取数据,这是最基础简单的查询表中的数据,但是在实际中经常需要从多个表中读取数据. 本章我将会向大家介绍如何使用MySQL在多个表中查询数据. 想要从多个表中查找数据,就要用到JOIN关键字 JOIN 按照功能大致分为如下三类: 1.CROSS JOIN(交叉连接) 2.INNER JOIN(内连接或等值连接) 3.OUTER JOIN(外连接) 1.1 交叉连接 交叉连接的关键字:CROSS JOIN                  

docker快速入门

1.docker简介 docker通过内核虚拟化技术(namespace及cgroups等)来提供容器的资源隔离与安全保障等,由于docker通过操作系统层的虚拟化实现隔离,所以docker容器在运行时,不需要类似虚拟机额外的操作系统开销,提供资源利用率 2.docker  vs  kvm 3.docker组件 镜像.容器.仓库 4.docker安装 [[email protected] ~]#tee /etc/yum.repos.d/docker.repo <<-'EOF' [dockerr

Docker快速入门指南

镜像操作 搜索镜像 $ docker search centos 下载镜像 $ docker pll centos 导入镜像 $ docker load < ./docker/image/centos.tar.gz 导出镜像 $ docker save centos > /tmp/centos.tar.gz 删除镜像 如果这个镜像创建了容器不会被删除 $ docker rmi centos 查看当前拥有的镜像 $ docker images REPOSITORY TAG IMAGE ID CR