参考 《第一本 docker 书》《docker 深入浅出》

第一本 docker

docker 容器

首先,我们告诉 Docker 执行 docker run 命令,并指定了-i 和-t 两个命令行参数。-i 标志保证容器中 STDIN 是开启的,尽管我们并没有附着到容器中。持久的标准输入是交互式 shell 的“半边天”,-t 标志则是另外“半边天”,它告诉 Docker 为要创建的容器分配一个伪 tty 终端。这样,新创建的容器才能提供一个交互式 shell。首先,我们告诉 Docker 执行 docker run 命令,并指定了-i 和-t 两个命令行参数。-i 标志保证容器中 STDIN 是开启的,尽管我们并没有附着到容器中。持久的标准输入是交互式 shell 的“半边天”,-t 标志则是另外“半边天”,它告诉 Docker 为要创建的容器分配一个伪 tty 终端。这样,新创建的容器才能提供一个交互式 shell。

带名字

sudo docker run –name bob_the_container -i -t ubuntu /bin/bash

重新启动

sudo docker start bob_the_container
or
sudo docker start aa3f365f0f4e

创建守护式程序

除了这些交互式运行的容器(interactive container),也可以创建长期运行的容器。守护式容器(daemonized container)没有交互式会话,非常适合运行应用程序和服务。大多数时候我们都需要以守护式来运行我们的容器。

sudo docker run --name daemon_dave -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done"

docker run 命令使用了-d 参数,因此 Docker 会将容器放到后台运行.

我们还在容器要运行的命令里使用了一个 while 循环,该循环会一直打印 hello world ,直到容器或其进程停止运行.

跟踪进程

sudo docker logs [-f] [-ft] [names]
  • -f 实时跟踪
  • -ft 加上时间戳

我们也可以跟踪容器日志的某一片段,和之前类似,只需要在 tail 命令后加入-f –tail 标志即可。例如,可以用 _docker logs –tail 10 daemon_dave_ 获取日志的最后 10 行内容。另外,也可以用docker logs –tail 0 -f daemon_dave 命令来跟踪某个容器的最新日志而不必读取整个日志文件。

日志驱动

将日志输出到指定的文件

sudo docker run --log-driver="syslog" --name daemon_dwayne -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1;done"

查看容器内进程

docker top [container]

统计信息

docker stats

在容器内启动新进程

在 Docker 1.3 之后,也可以通过 docker exec 命令在容器内部额外启动新进程。

自动重启容器

sudo docker run --restart=always --name daemon_dave -d ubuntu /
   bin/sh -c "while true; do echo hello world; sleep 1; done

在本例中,–restart 标志被设置为 always 。无论容器的退出代码是什么,Docker 都会自动重启该容器。除了 always ,还可以将这个标志设为 on-failure ,这样,只有当容器的退出代码为非 0 值的时候,才会自动重启。另外,on-failure 还接受一个可选的重启次数参数

–restart=on-failure:5
最多重启 5 次

深入容器

除了通过 docker ps 命令获取容器的信息,还可以使用 docker inspect 来获得更多的容器信息

docker inspect 命令会对容器进行详细的检查,然后返回其配置信息,包括名称、命令、网络配置以及很多有用的数据

也可以用-f 或者–format 标志来选定查看结果

sudo docker inspect --format='{{ .State.Running }}' daemon_dave

上面这条命令会返回容器的运行状态,示例中该状态为 false

删除容器

sudo docker rm `sudo docker ps -a -q`

上面的 docker ps 命令会列出现有的全部容器,-a 标志代表列出所有容器,而-q 标志则表示只需要返回容器的 ID 而不会返回容器的其他信息。

提交(commit)

也可以在提交镜像时指定更多的数据(包括标签)来详细描述所做的修改。

$ sudo docker commit -m"A new custom image" -a"James Turnbull" \ 
4aab3ce3cb76 jamtur01/apache2:webserver 

f99ebb6fed1f559258840505a0f5d5b6173177623946815366f3e3acff01adef”

在这条命令里,我们指定了更多的信息选项。首先-m 选项用来指定新创建的镜像的提交信息。同时还指定了–a 选项,用来列出该镜像的作者信息。接着指定了想要提交的容器的 ID。最后的 jamtur01/apache2 指定了镜像的用户名和仓库名,并为该镜像增加了一个 webserver 标签。

Dockerfile

我们推荐使用 Dockerfile 方法来代替 docker commit ,因为通过前者来构建镜像更具备可重复性、透明性以及幂等性。
一旦有了 Dockerfile,我们就可以 使用 docker build 命令基于该 Dockerfile 中的指令构建一个新的镜像。

Dockerfile 介绍

必要参数:

  • FROM creates a layer from the ubuntu:18.04 Docker image.
  • COPY adds files from your Docker client’s current directory.
  • RUN builds your application with make.
  • CMD specifies what command to run within the container.

默认情况下,RUN 指令会在 shell 里使用命令包装器/bin/sh -c 来执行。如果是在一个不支持 shell 的平台上运行或者不希望在 shell 中运行(比如避免 shell 字符串篡改),也可以使用 exec 格式的 RUN 指令,如代码清单 4-23 所示。

RUN [ “apt-get”, “ install”, “-y”, “nginx” ]

在这种方式中,我们使用一个数组来指定要运行的命令和传递给该命令的每个参数。

可以指定多个 EXPOSE 指令来向外部公开多个端口。

基于 Dockerfile 构建新镜像

$ cd static_web
$ sudo docker build -t="jamtur01/static_web" .
Sending build context to Docker daemon 2.56 kB
Sending build context to Docker daemon
Step 0 : FROM ubuntu:14.04
 ---> ba5877dc9bec
Step 1 : MAINTAINER James Turnbull "[email protected]"
 ---> Running in b8ffa06f9274
 ---> 4c66c9dcee35
Removing intermediate container b8ffa06f9274
Step 2 : RUN apt-get update
 ---> Running in f331636c84f7
 ---> 9d938b9e0090
Removing intermediate container f331636c84f7
Step 3 : RUN apt-get install -y nginx
 ---> Running in 4b989d4730dd
  ---> 93fb180f3bc9
Removing intermediate container 4b989d4730dd
Step 4 : RUN echo 'Hi, I am in your container' >/usr/share/
 nginx/html/index.html
 ---> Running in b51bacc46eb9
 ---> b584f4ac1def
Removing intermediate container b51bacc46eb9
Step 5 : EXPOSE 80
 ---> Running in 7ff423bd1f4d
 ---> 22d47c8cb6e5
Successfully built 22d47c8cb6e5

sudo docker build -t=”jamtur01/static_web” .

上面命令中最后的.告诉 Docker 到本地目录中去找 Dockerfile 文件。也可以指定一个 Git 仓库的源地址来指定 Dockerfile 的位置

sudo docker build -t=”jamtur01/static_web:v1” \ git@github.com:jamtur01/docker-static_web

dockerfile 构建缓存

sudo docker build –no-cache -t=”jamtur01/static_web” .

可以通过添加 –no-cache 来不构建缓存,但是缓存能大大加快重复构建时消耗的时间.

FROM ubuntu:14.04 
MAINTAINER James Turnbull "[email protected]" 
ENV REFRESHED_AT 2014-07-01 
RUN apt-get -qq update

让我们一步步来分析一下这个新的 Dockerfile 。首先,我通过 FROM 指令为新镜像设置了一个基础镜像 ubuntu:14.04 。接着,我又使用 MAINTAINER 指令添加了自己的详细联系信息。之后我又使用了一条新出现的指令 ENV 来在镜像中设置环境变量。在这个例子里,我通过 ENV 指令来设置了一个名为 REFRESHED_AT 的环境变量,这个环境变量用来表明该镜像模板最后的更新时间。最后,我使用了 RUN 指令来运行 apt-get -qq update 命令。该指令运行时将会刷新 APT 包的缓存,用来确保我们能将要安装的每个软件包都更新到最新版本。

有了这个模板,如果想刷新一个构建,只需修改 ENV 指令中的日期。这使 Docker 在命中 ENV 指令时开始重置这个缓存,并运行后续指令而无须依赖该缓存。也就是说,RUN apt-get update 这条指令将会被再次执行,包缓存也将会被刷新为最新内容。

查看镜像

docker history [name\id]
查看构建这个镜像的每一层,以及 dockerfile 指令

从新镜像启动容器

$ sudo docker run -d -p 80 --name static_web jamtur01/static_web \ 
nginx -g "daemon off;" 
6751b94bb5c001a650c918e9a7f9683985c3eb2b026c2f1776e61190669494

在这里,我使用 docker run 命令,基于刚才构建的镜像的名字,启动了一个名为 static_web 的新容器。我们同时指定了-d 选项,告诉 Docker 以分离(detached)的方式在后台运行。这种方式非常适合运行类似 Nginx 守护进程这样的需要长时间运行的进程。我们也指定了需要在容器中运行的命令:nginx -g “daemon off;” 。这将以前台运行的方式启动 Nginx,来作为我们的 Web 服务器。

我们可以使用 docker inspect 或者 docker port 命令来查看容器内的 80 端口具体被绑定到了宿主机的哪个端口上。

Docker 还提供了一个更简单的方式,即-P 参数,该参数可以用来对外公开在 Dockerfile 中通过 EXPOSE 指令公开的所有端口

Dockerfile 指令

参考

CMD

需要注意的是,要运行的命令是存放在一个数组结构中的。这将告诉 Docker 按指定的原样来运行该命令。当然也可以不使用数组而是指定 CMD 指令,这时候 Docker 会在指定的命令前加上/bin/sh -c 。这在执行该命令的时候可能会导致意料之外的行为, 所以 Docker 推荐一直使用以数组语法来设置要执行的命令

在 Dockerfile 中只能指定一条 CMD 指令。如果指定了多条 CMD 指令,也只有最后一条 CMD 指令会被使用。如果想在启动容器时运行多个进程或者多条命令,可以考虑使用类似 Supervisor 这样的服务管理工具。

ENTRYPOINT

我们也可以组合使用 ENTRYPOINT 和 CMD 指令来完成一些巧妙的工作。比如,我们可能想在 Dockerfile 里指定代码清单 4-55 所示的内容。

代码清单4-55 同时使用ENTRYPOINT 和CMD 指令
ENTRYPOINT ["/usr/sbin/nginx"] 
CMD ["-h"]

此时当我们启动一个容器时,任何在命令行中指定的参数都会被传递给 Nginx 守护进程。比如,我们可以指定-g “daemon off”; 参数让 Nginx 守护进程以前台方式运行。如果在启动容器时不指定任何参数,则在 CMD 指令中指定的-h 参数会被传递给 Nginx 守护进程,即 Nginx 服务器会以/usr/sbin/nginx -h 的方式启动,该命令用来显示 Nginx 的帮助信息。

WORKDIR

WORKDIR 指令用来在从镜像创建一个新容器时,在容器内部设置一个工作目录,ENTRYPOINT 和/或 CMD 指定的程序会在这个目录下执行。

WORKDIR /opt/webapp/db 
RUN bundle install 
WORKDIR /opt/webapp 
ENTRYPOINT [ "rackup" ]

这里,我们将工作目录切换为/opt/webapp/db 后运行了 bundle install 命令,之后又将工作目录设置为/opt/webapp ,最后设置了 ENTRYPOINT 指令来启动 rackup 命令。
可以通过-w 标志在运行时覆盖工作目录

$ sudo docker run -ti -w /var/log ubuntu pwd 
/var/log

该命令会将容器内的工作目录设置为/var/log 。

ENV

ENV 指令用来在镜像构建过程中设置环境变量

USER

USER 指令用来指定该镜像会以什么样的用户去运行

我们可以指定用户名或 UID 以及组或 GID,甚至是两者的组合

VOLUME

VOLUME 指令用来向基于镜像创建的容器添加卷。一个卷是可以存在于一个或者多个容器内的特定的目录,这个目录可以绕过联合文件系统,并提供如下共享数据或者对数据进行持久化的功能。

  • 卷可以在容器间共享和重用。
  • 一个容器可以不是必须和其他容器共享卷。
  • 对卷的修改是立时生效的。
  • 对卷的修改不会对更新镜像产生影响。
  • 卷会一直存在直到没有任何容器再使用它。

卷功能让我们可以将数据(如源代码)、数据库或者其他内容添加到镜像中而不是将这些内容提交到镜像中,并且允许我们在多个容器间共享这些内容。我们可以利用此功能来测试容器和内部的应用程序代码,管理日志,或者处理容器内部的数据库。

ADD

ADD 指令用来将构建环境下的文件和目录复制到镜像中

比如,在安装一个应用程序时。ADD 指令需要源文件位置和目的文件位置两个参数,如代码清单 4-69 所示。

代码清单4-69 使用ADD 指令
ADD software.lic /opt/application/software.lic

指向源文件的可以是一个 URL

ADD 指令会使得构建缓存变得无效,这一点也非常重要。如果通过 ADD 指令向镜像添加一个文件或者目录,那么这将使 Dockerfile 中的后续指令都不能继续使用之前的构建缓存。

COPY

COPY 指令非常类似于 ADD ,它们根本的不同是 COPY 只关心在构建上下文中复制本地文件,而不会去做文件提取(extraction)和解压(decompression)的工作。COPY 指令的使用如代码清单 4-72 所示。

代码清单4-72 使用COPY 指令
COPY conf.d/ /etc/apache2/

这条指令将会把本地 conf.d 目录中的文件复制到/etc/apache2/ 目录中。

COPY 指令的目的位置则必须是容器内部的一个绝对路径。

LABEL

LABEL 指令用于为 Docker 镜像添加元数据。元数据以键值对的形式展现。我们可以来看一个例子,见代码清单 4-73。

代码清单4-73 添加LABEL 指令
LABEL version="1.0"
LABEL location="New York" type="Data Center" role="Web Server"

LABEL 指令以 label=”value”的形式出现。可以在每一条指令中指定一个元数据,或者指定多个元数据,不同的元数据之间用空格分隔。推荐将所有的元数据都放到一条 LABEL 指令中,以防止不同的元数据指令创建过多镜像层。

STOPSINGAL

STOPSIGNAL 指令用来设置停止容器时发送什么系统调用信号给容器。这个信号必须是内核系统调用表中合法的数,如 9,或者 SIGNAME 格式中的信号名称,如 SIGKILL。

ARG

ARG 指令用来定义可以在 docker build 命令运行时传递给构建运行时的变量,我们只需要在构建时使用–build-arg 标志即可。用户只能在构建时指定在 Dockerfile 文件中定义过的参数。
代码清单 4-75  添加 ARG 指令

ARG build
ARG webapp_user=user

上面例子中第二条 ARG 指令设置了一个默认值,如果构建时没有为该参数指定值,就会使用这个默认值。

读到这里,也许你会认为使用 ARG 来传递证书或者秘钥之类的信息是一个不错的想法。但是,请千万不要这么做。你的机密信息在构建过程中以及镜像的构建历史中会被暴露。

ONBUILD

ONBUILD 指令能为镜像添加触发器(trigger)。当一个镜像被用做其他镜像的基础镜像时(比如用户的镜像需要从某未准备好的位置添加源代码,或者用户需要执行特定于构建镜像的环境的构建脚本),该镜像中的触发器将会被执行。

触发器会在构建过程中插入新指令,我们可以认为这些指令是紧跟在 FROM 之后指定的。

比如,我们为 Apache2 镜像构建一个全新的 Dockerfile ,该镜像名为 jamtur01/ apache2 ,如代码清单 4-80 所示。

代码清单 4-80  新的 ONBUILD 镜像 Dockerfile

FROM ubuntu:14.04 
MAINTAINER James Turnbull "[email protected]" 
RUN apt-get update && apt-get install -y apache2 
ENV APACHE_RUN_USER www-data 
ENV APACHE_RUN_GROUP www-data 
ENV APACHE_LOG_DIR /var/log/apache2 
ONBUILD ADD . /var/www/ 
EXPOSE 80 
ENTRYPOINT ["/usr/sbin/apache2"] 
CMD ["-D", "FOREGROUND"]

现在我们就来构建该镜像

$ sudo docker build -t="jamtur01/apache2" . 
... 
Step 7 : ONBUILD ADD . /var/www/ 
---> Running in 0e117f6ea4ba 
---> a79983575b86 
Successfully built a79983575b86

在新构建的镜像中包含一条 ONBUILD 指令,该指令会使用 ADD 指令将构建环境所在的目录下的内容全部添加到镜像中的/var/www/ 目录下。 我们可以轻而易举地将这个 Dockerfile 作为一个通用的 Web 应用程序的模板,可以基于这个模板来构建 Web 应用程序

这里有好几条指令是不能用在 ONBUILD 指令中的,包括 FROM 、MAINTAINER 和 ONBUILD 本身。之所以这么规定是为了防止在 Dockerfile 构建过程中产生递归调用的问题。

推送到 DockerHub && 自动构建

在测试中使用 Docker

mkdir nginx && cd nginx
wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/5/sample/nginx/global.conf
wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/5/sample/nginx/nginx.conf

在配置文件 nginx.conf 中, daemon off; 选项阻止 Nginx 进入后台,强制其在前台运行。这是因为要想保持 Docker 容器的活跃状态,需要其中运行的进程不能中断。默认情况下,Nginx 会以守护进程的方式启动,这会导致容器只是短暂运行,在守护进程被 fork 启动后,发起守护进程的原始进程就会退出,这时容器就停止运行了。

docker build -t jamtur01/nginx .

从本地构建 dockerfile

从 Sample 网站和 Nginx 镜像构建容器

$ mkdir website && cd website
$ wget https://raw.githubusercontent.com/jamtur01/dockerbook-code/master/code/5/sample/website/index.html
$ cd ..
over

现在,开始测试 docker 容器

sudo docker run -d -p 80 --name website \
-v $PWD/website:/var/www/html/website \
jamtur01/nginx nginx

-v 这个选项允许我们将宿主机的目录作为卷,挂载到容器里。

现在稍微偏题一下,我们来关注一下卷这个概念。卷在 Docker 里非常重要,也很有用。卷是在一个或者多个容器内被选定的目录,可以绕过分层的联合文件系统(Union File System),为 Docker 提供持久数据或者共享数据。这意味着对卷的修改会直接生效,并绕过镜像。当提交或者创建镜像时,卷不被包含在镜像里。

卷可以在容器间共享。即便容器停止,卷里的内容依旧存在。

回到刚才的例子。当我们因为某些原因不想把应用或者代码构建到镜像中时,就体现出卷的价值了。例如:

  • 希望同时对代码做开发和测试;
  • 代码改动很频繁,不想在开发过程中重构镜像;
  • 希望在多个容器间共享代码。

参考资料

网站链接

Docker Doc
Docker 学习新手笔记:从入门到放弃

摘录

术语定义

images :An image is an executable package that includes everything needed to run an application–the code, a runtime, libraries, environment variables, and configuration files.

container :A container is a runtime instance of an image–what the image becomes in memory when executed (that is, an image with state, or a user process). You can see a list of your running containers with the command, docker ps, just as you would in Linux.

container和VM的区别

5c3f63cdcb67e40b1e9a3f1676b17868.png

常用命令集

docker container ls
docker container ls --all
docker container ls -aq

docker rm `docker ps -a|grep Exited|awk '{print $1}'`

参数集

-d detached 单独地