---
cid: 67
title: docker (1)
slug: 67
date: 2019/08/07 09:56:00
updated: 2019/08/07 10:00:08
status: publish
author: harumonia
categories:
  - 源流清泉
  - Docker
tags:
thumbStyle: default
hidden: false
---

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

<!-- more -->

# 第一本 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）没有交互式会话，非常适合运行应用程序和服务。大多数时候我们都需要以守护式来运行我们的容器。

```shell
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 ，直到容器或其进程停止运行.

### 跟踪进程

```shell
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* 命令来跟踪某个容器的最新日志而不必读取整个日志文件。

### 日志驱动

将日志输出到指定的文件

```shell
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 命令在容器内部额外启动新进程。

### 自动重启容器

```shell
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 标志来选定查看结果

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

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

### 删除容器

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

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

### 提交(commit)

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

```bash
$ 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 介绍](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/)

**必要参数**:

- 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 构建新镜像

```shell
$ 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 "james@example.com"
　---> 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** 来不构建缓存,但是缓存能大大加快重复构建时消耗的时间.

```bash
FROM ubuntu:14.04　
MAINTAINER James Turnbull "james@example.com"　
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 指令

### 从新镜像启动容器

```bash
$ 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 指令

[参考](http://docs.docker.com/reference/builder/)

#### CMD

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

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

#### ENTRYPOINT

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

```bash
代码清单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 指定的程序会在这个目录下执行。

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

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

```bash
$ 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 所示。

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

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

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

#### COPY

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

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

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

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

#### LABEL

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

```bash
代码清单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 指令

```bash
ARG build
ARG webapp_user=user
```

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

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

#### ONBUILD

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

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

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

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

```bash
FROM ubuntu:14.04　
MAINTAINER James Turnbull "james@example.com"　
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"]
```

现在我们就来构建该镜像

```bash
$ 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

```bash
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 镜像构建容器

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

现在,开始测试 docker 容器

```bash
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](https://docs.docker.com/get-started/)
[Docker 学习新手笔记：从入门到放弃](https://hijiangtao.github.io/2018/04/17/Docker-in-Action/)

## 摘录

### 术语定义

**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](#)

### 常用命令集

```bash
docker container ls
docker container ls --all
docker container ls -aq

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

### 参数集

-d detached 单独地
