《Docker从入门到实践》阅读笔记

2字数 8831阅读 3383

《Docker从入门到实践》阅读笔记

原书地址: https://yeasy.gitbooks.io/docker_practice/content/introduction/what.html

[TOC]

1. Docker简介

1.1 什么是Docker

1.2 为什么要用Docker

2. 基本概念

2.1 镜像

  • Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
  • 分层存储:由多层文件系统联合组成。

2.2 容器

  • 镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例 一样,镜像是静态的定义,容器是镜像运行时的实体。
  • 容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
  • 前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为容器存储层。
  • 容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
  • 按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
  • 数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。

2.3 仓库

2.3.1 Docker Registry

  • Docker仓库是一个集中的存储、分发镜像的服务。
  • 一个Docker Registry中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
  • 通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签>的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
  • 仓库名经常以 两段式路径 形式出现,比如jwilder/nginx-proxy前者往往意味着Docker Registry多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体Docker Registry的软件或服务。

2.3.2 Docker Registry 公开服务

  • Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。最常使用的 Registry 公开服务是官方的Docker Hub
  • 由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(Registry Mirror),这些镜像服务被称为加速器。常见的有 阿里云加速器DaoCloud 加速器 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像,比直接从 Docker Hub 下载速度会提高很多。在 3. 安装 Docker 一节中有详细的配置方法。
  • 国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 时速云镜像仓库网易云镜像服务DaoCloud 镜像市场阿里云镜像库 等。

2.3.3 私有 Docker Registry

  • 除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 Docker Registry 镜像,可以直接使用做为私有 Registry 服务。在 私有仓库 一节中,会有进一步的搭建私有 Registry 服务的讲解。

3. 安装Docker {#install}

  • 版本命名规则:
    项目|说明
    -|-
    版本格式|YY.MM
    Stable版本|每季度发行
    Edge版本|每月发行
  • 同时 Docker 划分为 CE 和 EE。CE,即社区版(免费,支持周期三个月);EE, 即企业版,强调安全,付费使用。

3.1 Ubuntu

3.1.1 卸载旧版本

$sudo apt-get remove docker docker-engine docker.io

3.1.2 安装可选内核模块

$ sudo apt-get update
$ sudo apt-get install linux-image-extra-$(uname -r) \ 
> linux-image-extra-virtual

3.1.3 使用APT安装

$ sudo apt-get update

$ sudo apt-get install \
>    apt-transport-https \
>    ca-certificates \
>    curl \
>    software-properties-common

鉴于国内网络问题,强烈建议使用国内源,官方源请在注释中查看。

为了确认所下载软件包的合法性,需要添加软件源的 GPG 密钥。

$ curl -fsSL \
> https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu/gpg \ 
> | sudo apt-key add -

然后,我们需要向source.list 中添加 Docker 软件源

$ sudo add-apt-repository "deb \
> [arch=amd64] https://mirrors.ustc.edu.cn/docker-ce/linux/ubuntu \
> $(lsb_release -cs) stable"

3.1.4 安装Docker CE

$ sudo apt-get update
$ sudo apt-get install docker-ce

3.1.5 使用脚本自动安装

$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun

3.1.6 启动Docker CE

$ sudo systemctl enable docker
$ sudo systemctl start docker

3.1.7 建立docker用户组

  • 建立Docker组:
$ sudo groupadd docker
  • 将当前用户加入docker组:
$ sudo usermod -aG docker $USER

退出当前终端并重新登录,进行如下测试。

3.1.8 测试Docker是否安装正确

若出现如下信息,说明安装成功:

$ docker run hello-world

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
Status: Downloaded newer image for hello-world:latest

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/

3.2 Debian

3.3 CentOS

3.4 Raspberry Pi

3.5 macOS

3.6 Windows PC

3.7 镜像加速器

国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。Docker 官方和国内很多云服务商都提供了国内加速器服务,例如:

Ubuntu 16.04+、Debian 8+、CentOS 7
对于使用 systemd 的系统,请在 /etc/docker/daemon.json 中写入如下内容(如果文件不存在请新建该文件)

{
  "registry-mirrors": [
    "https://registry.docker-cn.com"
  ]
}

之后重启服务

$ sudo systemctl daemon-reload
$ sudo systemctl restart docker

检查是否生效:
  配置加速器之后,如果拉取镜像仍然十分缓慢,请手动检查加速器配置是否生效,在命令行执行docker info,如果从结果中看到了如下内容,说明配置成功。

Registry Mirrors:
 https://registry.docker-cn.com/

4. 使用Docker镜像

Docker的三大组件:镜像、容器和仓库

4.1 获取镜像

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
  • Docker 镜像仓库地址:地址的格式一般是 <域名/IP>[:端口号]。默认地址是 Docker Hub。
  • 仓库名:如之前所说,这里的仓库名是两段式名称,即 <用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为 library,也就是官方镜像。
  • 比如:
$ docker pull ubuntu:16.04
16.04: Pulling from library/ubuntu
bf5d46315322: Pull complete
9f13e0ac480c: Pull complete
e8988b5b3097: Pull complete
40af181810e7: Pull complete
e6f7c7e5c03e: Pull complete
Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
Status: Downloaded newer image for ubuntu:16.04

  上面的命令中没有给出 Docker 镜像仓库地址,因此将会从 Docker Hub 获取镜像。而镜像名称是 ubuntu:16.04,因此将会获取官方镜像 library/ubuntu 仓库中标签为 16.04 的镜像。
  下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。

运行:
  有了镜像后,我们就能够以这个镜像为基础启动并运行一个容器。以上面的 ubuntu:16.04 为例,如果我们打算启动里面的 bash 并且进行交互式操作的话,可以执行下面的命令。

$ docker run -it --rm ubuntu:16.04 bash

root@a1765e7e0b4b:/# cat /etc/os-release
NAME="Ubuntu"
VERSION="16.04.3 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.3 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial

参数介绍:

  • -it:这是两个参数,一个是 -i:交互式操作,一个是 -t 终端。我们这里打算进入 bash 执行一些命令并查看返回结果,因此我们需要交互式终端。
  • --rm:这个参数是说容器退出后随之将其删除。默认情况下,为了排障需求,退出的容器并不会立即删除,除非手动 docker rm。我们这里只是随便执行个命令,看看结果,不需要排障和保留结果,因此使用 --rm 可以避免浪费空间。
  • ubuntu:16.04:这是指用 ubuntu:16.04 镜像为基础来启动容器。
  • bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 bash

  进入容器后,我们可以在 Shell 下操作,执行任何所需的命令。这里,我们执行了 cat /etc/os-release,这是 Linux 常用的查看当前系统版本的命令,从返回的结果可以看到容器内是 Ubuntu 16.04.4 LTS 系统。最后我们通过 exit 退出了这个容器。

4.2 列出镜像

要想列出已经下载下来的镜像,可以使用 docker image ls 命令。

$ docker image ls
REPOSITORY    TAG    IMAGE ID        CREATED        SIZE
ubuntu        16.04  0458a4468cbc    3 days ago     112MB
hello-world   latest f2a91732366c    2 months ago   1.85kB

镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签。

镜像体积
  如果仔细观察,会注意到,这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同。比如,ubuntu:16.04 镜像大小,在这里是 127 MB,但是在 Docker Hub 显示的却是 50 MB。这是因为 Docker Hub 中显示的体积是压缩后的体积。在镜像下载和上传过程中镜像是保持着压缩状态的,因此 Docker Hub 所显示的大小是网络传输中更关心的流量大小。而 docker image ls 显示的是镜像下载到本地后,展开的大小。

虚悬镜像(dangling image)
  有些镜像既没有仓库名,也没有标签,均为<none>。这个镜像原本是有镜像名和标签的,随着官方镜像维护,发布了新版本后,重新 docker pull 时,旧的镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>。除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。可以用下面的命令专门显示这类镜像:

$ docker image ls -f dangling=true

一般来说,虚悬镜像已经失去了存在的价值,可以随意删除,可以用下面的命令删除。

$ docker image prune

中间层镜像
  为了加速镜像构建、重复利用资源,Docker 会利用中间层镜像。所以在使用一段时间后,可能会看到一些依赖的中间层镜像。默认的 docker image ls 列表中只会显示顶层镜像,如果希望显示包括中间层镜像在内的所有镜像的话,需要加 -a 参数。

$ docker image ls -a

  这样会看到很多无标签的镜像,与之前的虚悬镜像不同,这些无标签的镜像很多都是中间层镜像,是其它镜像所依赖的镜像。这些无标签镜像不应该删除,否则会导致上层镜像因为依赖丢失而出错。实际上,这些镜像也没必要删除,因为之前说过,相同的层只会存一遍,而这些镜像是别的镜像的依赖,因此并不会因为它们被列出来而多存了一份,无论如何你也会需要它们。只要删除那些依赖它们的镜像后,这些依赖的中间层镜像也会被连带删除。

列出部分镜像
  不加任何参数的情况下,docker image ls 会列出所有顶级镜像,但是有时候我们只希望列出部分镜像。docker image ls 有好几个参数可以帮助做到这个事情。

  • 根据仓库名列出镜像
$ docker image ls ubuntu
  • 列出特定的某个镜像,也就是说指定仓库名和标签
$ docker image ls ubuntu:16.04
  • docker image ls 还支持强大的过滤器参数 --filter,或者简写 -f。之前我们已经看到了使用过滤器来列出虚悬镜像的用法,它还有更多的用法。比如,我们希望看到在 mongo:3.2 之后(查看'之前',将since改为before)建立的镜像,可以用下面的命令:
$ docker image ls -f since=mongo:3.2

以特定格式显示
  默认情况下,docker image ls 会输出一个完整的表格,但是我们并非所有时候都会需要这些内容。比如,刚才删除虚悬镜像的时候,我们需要利用 docker image ls 把所有的虚悬镜像的 ID 列出来,然后才可以交给 docker image rm 命令作为参数来删除指定的这些镜像,这个时候就用到了 -q 参数。

$ docker image ls -q
5f515359c7f8
05a60462f8ba
fe9198c04d62
00285df0df87
f753707788c5
f753707788c5
1e0c3dd64ccd

  自定义格式返回(可将输出结果直接作为下一条命令的输入),如下面的命令会直接列出镜像结果,并且只包含镜像ID和仓库名:

$ docker image ls --format "{{.ID}}: {{.Repository}}"
5f515359c7f8: redis
05a60462f8ba: nginx
fe9198c04d62: mongo
00285df0df87: <none>
f753707788c5: ubuntu
f753707788c5: ubuntu
1e0c3dd64ccd: ubuntu

或者打算以表格等距显示,并且有标题行,和默认一样,不过自己定义列:

$ docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"
IMAGE ID            REPOSITORY          TAG
5f515359c7f8        redis               latest
05a60462f8ba        nginx               latest
fe9198c04d62        mongo               3.2
00285df0df87        <none>              <none>
f753707788c5        ubuntu              16.04
f753707788c5        ubuntu              latest
1e0c3dd64ccd        ubuntu              14.04

4.3 删除镜像

如果要删除本地的镜像,可以使用 docker image rm 命令,其格式为:

$ docker image rm [选项] <镜像1> [<镜像2> ...]

其中,<镜像> 可以是 镜像短 ID镜像长 ID镜像名 或者 镜像摘要
比如我们有这么一些镜像:

$ docker image ls
REPOSITORY      TAG     IMAGE ID        CREATED     SIZE
centos          latest  0584b3d2cf6d    3 weeks ago 196.5 MB
redis           alpine  501ad78535f0    3 weeks ago 21.03 MB
docker          latest  cf693ec9b5c7    3 weeks ago 105.1 MB
nginx           latest  e43d811ce2f4    5 weeks ago 181.5 MB

我们可以用镜像的完整 ID,也称为 长 ID,来删除镜像。使用脚本的时候可能会用长 ID,但是人工输入就太累了,所以更多的时候是用 短 ID 来删除镜像。 docker image ls 默认列出的就已经是短 ID 了,一般取前3个字符以上,只要足够区分于别的镜像就可以了。
比如这里,如果我们要删除 redis:alpine 镜像,可以执行:

$ docker image rm 501
Untagged: redis:alpine
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7

我们也可以用镜像名,也就是 <仓库名>:<标签>,来删除镜像。

$ docker image rm centos
Untagged: centos:latest
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38

当然,更精确的是使用 镜像摘要 删除镜像。

$ docker image ls --digests
REPOSITORY  TAG     DIGEST      IMAGE ID        CREATED     SIZE
node        slim    sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228   6e0c4c8e3913        3 weeks ago         214 MB

$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228

Untagged和Delete

  • 删除行为分为UntaggedDelete两类:
  • 我们使用上面命令删除镜像的时候,是在取消某个镜像的标签,即 Untagged
  • 一个镜像可以对应多个标签,只有某个镜像的所有标签都被取消,该镜像才可能会被Delete(为什么是可能下文解释)。
  • 由于镜像的多层结构,有可能某个其它镜像或容器正依赖于当前镜像的某一层。在这样的情况下,该镜像所有标签都被取消该镜像也不会被删除。



docker image ls命令配合使用
比如,我们需要删除所有仓库名为 redis 的镜像:

$ docker image rm $(docker image ls -q redis)

或者删除所有在 mongo:3.2 之前的镜像:

$ docker image rm $(docker image ls -q -f before=mongo:3.2)

4.4利用 commit 理解镜像构成

// 构建镜像,其中90表示绑定服务器的端口,80表示镜像端口,浏览器访问90端口正常
$ docker run --name webserver -d -p 90:80 nginx 

// 更改Web服务器内容
$ docker exec -it webserver bash
root@3729b97e8226:# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit

// 用commit能够定制自己的镜像,不过使用Dockerfile更好,因此不细说commit

4.5 使用Dockerfile定制镜像

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

// 修改服务器内容
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile

// 内容
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
  • FROM指定基础镜像,FROM scratch表示以空白镜像为基础
  • RUN有两种格式,第一种更常见:
  • RUN <命令>
  • RUN ["可执行文件", "参数1", "参数2"]

注:每使用一个RUN就会加一层镜像,所以对于没必要记录的命令可以使用如下形式

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

//注意:最后一句删除非常重要

构建镜像

// 在Dockerfile目录下,执行以下命令,
// 格式为docker build [选项] <上下文路径/URL/->
// 执行结束后,即可像上文运行nginx一样运行nginx:v3
$ docker build -t nginx:v3 .

  docker是C/S结构,并非所有命令都是在本地执行,如docker build在Docker引擎(即服务器端)执行,因此我们在执行该命令时需要给出上下文。如Dockerfile中的命令COPY ./package.json /app/就是复制给出的上下文下的package.json。
  在给出上下文后,该目录下的内容会被发送到服务器,可以使用.dockerignore文件制定不需要发送的文件。

4.6 Dockerfile指令详解

// COPY <源路径>... <目标路径>,支持正则匹配
COPY hom* /mydir/
COPY hom?.txt /mydir/

// ADD和COPY类似,能够自动将gzip, bzip2 以及 xz压缩格式自动解压,
// 无需自动解压时,使用COPY
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /

// CMD 容器启动命令,用于指定默认的容器主进程的启动命令,类似RUN
// 使用该格式'CMD ["可执行文件", "参数1", "参数2"...]'
// 注:CMD是注进程,退出后整个容器退出,因此不能后台执行
CMD service nginx start //后台执行nginx,执行完立刻退出
CMD ["nginx", "-g", "daemon off;"] //前台执行,正确

// ENTRYPOINT和CMD类似,不过可以继续加参数(如docker run myip -i)
// 或者执行脚本
ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
ENTRYPOINT ["docker-entrypoint.sh"]

// ENV <key> <value>
ENV NODE_VERSION 7.2.0
RUN curl -SLO https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz

// VOLUME 定义匿名卷
// '/data'目录就会在运行时自动挂载为匿名卷,任何向'/data'中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。
VOLUME /data

// WORKDIR 指定工作目录
WORKDIR <工作目录路径>

// USER 指定当前用户
USER <用户名>

// ONBUILD 当内容与该项目相关时,前面加上ONBUILD即可重用
FROM node:slim
RUN mkdir /app
WORKDIR /app
ONBUILD COPY ./package.json /app
ONBUILD RUN [ "npm", "install" ]
ONBUILD COPY . /app/
CMD [ "npm", "start" ]

5. 操作容器

容器是独立运行的应用和他们的环境。

5.1 启动

  启动分为:新建容器并启动、启动终止状态的容器;由于docker容器非常轻量级,用户选择随时新建删除。

新建并启动
命令为docker run,来看下面的例子:

$ docker run ubuntu:14.04 /bin/echo 'Hello world'
Hello world
$ docker run -t -i ubuntu:14.04 /bin/bash
root@af8bae53bdd3:/#
//-t 让Docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
//-i 则让容器的标准输入保持打开。

当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:

  • 检查本地是否存在指定的镜像,不存在就从公有仓库下载
  • 利用镜像创建并启动一个容器
  • 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
  • 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
  • 从地址池配置一个 ip 地址给容器
  • 执行用户指定的应用程序
  • 执行完毕后容器被终止

启动已终止容器
首先利用docker ps查看已终止容器,再利用docker container start <id>命令启动

5.2 守护态运行

看下下面例子:

//直接运行
$ docker run ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world
//使用-d使容器在后台运行,获取id,具体输出在log中查看
$ docker run -d ubuntu:17.10 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a
//使用启动时返回的id,也可以通过docker container ls命令来查看容器信息。
$ docker container logs [container ID or NAMES]
hello world
hello world
hello world

5.3 终止容器

  • 使用docker container stop终止容器;容器中的应用结束,容器也将终止
  • 终止状态的容器可以用docker container ls -a命令看到。
  • 处于终止状态的容器,可以通过docker container start命令来重新启动。
  • docker container restart可以重启容器

5.4 进入容器

  使用-d后,容器进入后台运行,有时候需要进入容器进行操作,可以使用docker attachdocker exec,推荐使用docker exec

  • attach命令
$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550

$ docker container ls
CONTAINER ID ……
243c32535da7 ……

$ docker attach 243c
root@243c32535da7:/#

//注:如果从这个 stdin 中 exit,会导致容器的停止。
  • exec命令
      该命令可以使用-i-t参数;相对attach命令,如果从这个 stdin 中 exit,不会导致容器的停止。看下面例子:
$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6

$ docker container ls
CONTAINER ID ……
69d137adef7a ……

$ docker exec -i 69d1 bash
ls
bin
boot
dev
...

$ docker exec -it 69d1 bash
root@69d137adef7a:/#

5.5 导出和导入容器

  • 导出容器
$ docker container ls -a
CONTAINER ID ……
7691a814370e ……

$ docker export 7691a814370e > ubuntu.tar
  • 导入容器
    将容器导入为镜像
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY    TAG   IMAGE ID        CREATED     VIRTUAL SIZE
test/ubuntu   v1.0  9d37a6082e97    20s ago     171.3 MB

//也可以通过指定 URL 或者某个目录来导入
$ docker import http://ex.com/eximage.tgz ex/imagerepo

5.6 删除容器

  • 使用docker container rm来删除一个处于终止状态的容器。如果要删除一个运行中的容器,可以添加 -f 参数。
  • docker container prune命令可以清理掉所有处于终止状态的容器。

6. 访问仓库

7. 数据管理

7.1 数据卷

数据卷时用来存放容器共享数据的目录。

10. Docker三剑客之Compose项目

10.1 安装

$ sudo curl -L https://github.com/docker/compose/releases/download/1.17.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

10.2 命令补全

$ sudo sh -c 'curl -L https://raw.githubusercontent.com/docker/compose/1.8.0/contrib/completion/bash/docker-compose > /etc/bash_completion.d/docker-compose'

10.3 使用

  • 服务 (service):一个应用容器,实际上可以运行多个相同镜像的实例。
  • 项目 (project):由一组关联的应用容器组成的一个完整业务单元。

命令说明

-f, --file FILE 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定。

-p, --project-name NAME 指定项目名称,默认将使用所在目录名称作为项目名。

--x-networking 使用 Docker 的可拔插网络后端特性

--x-network-driver DRIVER 指定网络后端的驱动,默认为 bridge

--verbose 输出更多调试信息。

-v, --version 打印版本并退出。

命令使用说明

build

格式为 docker-compose build [options] [SERVICE...]。

构建(重新构建)项目中的服务容器。

服务容器一旦构建后,将会带上一个标记名,例如对于 web 项目中的一个 db 容器,可能是 web_db。

可以随时在项目目录下运行 docker-compose build 来重新构建服务。

选项包括:

--force-rm 删除构建过程中的临时容器。

--no-cache 构建镜像过程中不使用 cache(这将加长构建过程)。

--pull 始终尝试通过 pull 来获取更新版本的镜像。

config

验证 Compose 文件格式是否正确,若正确则显示配置,若格式错误显示错误原因。

down

此命令将会停止 up 命令所启动的容器,并移除网络

exec

进入指定的容器

help

获得一个命令的帮助

images

列出 Compose 文件中包含的镜像

kill

格式为 docker-compose kill [options] [SERVICE...]。

通过发送 SIGKILL 信号来强制停止服务容器。

支持通过 -s 参数来指定发送的信号,例如通过如下指令发送 SIGINT 信号。

$ docker-compose kill -s SIGINT

logs

格式为 docker-compose logs [options] [SERVICE...]。

查看服务容器的输出。默认情况下,docker-compose 将对不同的服务输出使用不同的颜色来区分。可以通过 --no-color 来关闭颜色。

该命令在调试问题的时候十分有用。

pause

格式为 docker-compose pause [SERVICE...]。

暂停一个服务容器。

port

格式为 docker-compose port [options] SERVICE PRIVATE_PORT。

打印某个容器端口所映射的公共端口。

选项:

--protocol=proto 指定端口协议,tcp(默认值)或者 udp。

--index=index 如果同一服务存在多个容器,指定命令对象容器的序号(默认为 1)。

ps

格式为 docker-compose ps [options] [SERVICE...]。

列出项目中目前的所有容器。

选项:

-q 只打印容器的 ID 信息。

pull

格式为 docker-compose pull [options] [SERVICE...]。

拉取服务依赖的镜像。

选项:

--ignore-pull-failures 忽略拉取镜像过程中的错误。

push

推送服务依赖的镜像到 Docker 镜像仓库。

restart

格式为 docker-compose restart [options] [SERVICE...]。

重启项目中的服务。

选项:

-t, --timeout TIMEOUT 指定重启前停止容器的超时(默认为 10 秒)。

rm

格式为 docker-compose rm [options] [SERVICE...]。

删除所有(停止状态的)服务容器。推荐先执行 docker-compose stop 命令来停止容器。

选项:

-f, --force 强制直接删除,包括非停止状态的容器。一般尽量不要使用该选项。

-v 删除容器所挂载的数据卷。

run

格式为 docker-compose run [options] [-p PORT...] [-e KEY=VAL...] SERVICE [COMMAND] [ARGS...]。

在指定服务上执行一个命令。

例如:

$ docker-compose run ubuntu ping docker.com
将会启动一个 ubuntu 服务容器,并执行 ping docker.com 命令。

默认情况下,如果存在关联,则所有关联的服务将会自动被启动,除非这些服务已经在运行中。

该命令类似启动容器后运行指定的命令,相关卷、链接等等都将会按照配置自动创建。

两个不同点:

给定命令将会覆盖原有的自动运行命令;

不会自动创建端口,以避免冲突。

如果不希望自动启动关联的容器,可以使用 --no-deps 选项,例如

$ docker-compose run --no-deps web python manage.py shell
将不会启动 web 容器所关联的其它容器。

选项:

-d 后台运行容器。

--name NAME 为容器指定一个名字。

--entrypoint CMD 覆盖默认的容器启动指令。

-e KEY=VAL 设置环境变量值,可多次使用选项来设置多个环境变量。

-u, --user="" 指定运行容器的用户名或者 uid。

--no-deps 不自动启动关联的服务容器。

--rm 运行命令后自动删除容器,d 模式下将忽略。

-p, --publish=[] 映射容器端口到本地主机。

--service-ports 配置服务端口并映射到本地主机。

-T 不分配伪 tty,意味着依赖 tty 的指令将无法运行。

scale

格式为 docker-compose scale [options] [SERVICE=NUM...]。

设置指定服务运行的容器个数。

通过 service=num 的参数来设置数量。例如:

$ docker-compose scale web=3 db=2
将启动 3 个容器运行 web 服务,2 个容器运行 db 服务。

一般的,当指定数目多于该服务当前实际运行容器,将新创建并启动容器;反之,将停止容器。

选项:

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

start

格式为 docker-compose start [SERVICE...]。

启动已经存在的服务容器。

stop

格式为 docker-compose stop [options] [SERVICE...]。

停止已经处于运行状态的容器,但不删除它。通过 docker-compose start 可以再次启动这些容器。

选项:

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

top

查看各个服务容器内运行的进程。

unpause

格式为 docker-compose unpause [SERVICE...]。

恢复处于暂停状态中的服务。

up
格式为 docker-compose up [options] [SERVICE...]。

该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。

链接的服务都将会被自动启动,除非已经处于运行状态。

可以说,大部分时候都可以直接通过该命令来启动一个项目。

默认情况,docker-compose up 启动的容器都在前台,控制台将会同时打印所有容器的输出信息,可以很方便进行调试。

当通过 Ctrl-C 停止命令时,所有容器将会停止。

如果使用 docker-compose up -d,将会在后台启动并运行所有的容器。一般推荐生产环境下使用该选项。

默认情况,如果服务容器已经存在,docker-compose up 将会尝试停止容器,然后重新创建(保持使用 volumes-from 挂载的卷),以保证新启动的服务匹配 docker-compose.yml 文件的最新内容。如果用户不希望容器被停止并重新创建,可以使用 docker-compose up --no-recreate。这样将只会启动处于停止状态的容器,而忽略已经运行的服务。如果用户只想重新部署某个服务,可以使用 docker-compose up --no-deps -d <SERVICE_NAME> 来重新创建服务并后台停止旧服务,启动新服务,并不会影响到其所依赖的服务。

选项:

-d 在后台运行服务容器。

--no-color 不使用颜色来区分不同的服务的控制台输出。

--no-deps 不启动服务所链接的容器。

--force-recreate 强制重新创建容器,不能与 --no-recreate 同时使用。

--no-recreate 如果容器已经存在了,则不重新创建,不能与 --force-recreate 同时使用。

--no-build 不自动构建缺失的服务镜像。

-t, --timeout TIMEOUT 停止容器时候的超时(默认为 10 秒)。

version

格式为 docker-compose version。

打印版本信息。

10.4 compose模板文件

默认的模板文件名称为 docker-compose.yml

version: '3' //表示模板文件版本,不同版本支持的docker引擎不同

services:
  app1:
    image: examples/web //表示基础镜像
  app2:
    build: ./dir //指定Dockerfile所在目录,通过Dockerfile构建镜像
  app3:
    build:
      context: ./dir //指定Dockerfile所在目录
      dockerfile: Dockerfile-alternate //指定Dockerfile文件名
      args:
        buildno: 1 //指定参数
  app4:
      build:
          context: .
          cache_from: //指定缓存
            - alpine:latest
            - corp/web_app:3.14

cap_add, cap_drop
指定容器的内核能力(capacity)分配。

例如,让容器拥有所有能力可以指定为:

cap_add:
  - ALL

去掉 NET_ADMIN 能力可以指定为:

cap_drop:
  - NET_ADMIN

command
覆盖容器启动后默认执行的命令。
command: echo "hello world"

configs
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节。

cgroup_parent
指定父 cgroup 组,意味着将继承该组的资源限制。

例如,创建了一个 cgroup 组名称为 cgroups_1。

cgroup_parent: cgroups_1
container_name

指定容器名称。默认将会使用 项目名称服务名称序号 这样的格式。

container_name: docker-web-container

注意: 指定容器名称后,该服务将无法进行扩展(scale),因为 Docker 不允许多个容器具有相同的名称。

deploy
仅用于 Swarm mode,详细内容请查看 Swarm mode 一节

devices
指定设备映射关系。

devices:
  - "/dev/ttyUSB1:/dev/ttyUSB0"
depends_on

解决容器的依赖、启动先后的问题。以下例子中会先启动 redis db 再启动 web

version: '3'

services:
  web:
    build: .
    depends_on:
      - db
      - redis

  redis:
    image: redis

  db:
    image: postgres
注意:web 服务不会等待 redis db 「完全启动」之后才启动。

dns
自定义 DNS 服务器。可以是一个值,也可以是一个列表。

dns: 8.8.8.8

dns:
  - 8.8.8.8
  - 114.114.114.114

dns_search
配置 DNS 搜索域。可以是一个值,也可以是一个列表。

dns_search: example.com

dns_search:
  - domain1.example.com
  - domain2.example.com

tmpfs
挂载一个 tmpfs 文件系统到容器。

tmpfs: /run
tmpfs:
  - /run
  - /tmp

env_file
从文件中获取环境变量,可以为单独的文件路径或列表。

如果通过 docker-compose -f FILE 方式来指定 Compose 模板文件,则 env_file 中变量的路径会基于模板文件路径。

如果有变量名称与 environment 指令冲突,则按照惯例,以后者为准。

env_file: .env

env_file:
  - ./common.env
  - ./apps/web.env
  - /opt/secrets.env

环境变量文件中每一行必须符合格式,支持 # 开头的注释行。

# common.env: Set development environment
PROG_ENV=development

environment
设置环境变量。你可以使用数组或字典两种格式。

只给定名称的变量会自动获取运行 Compose 主机上对应变量的值,可以用来防止泄露不必要的数据。

environment:
  RACK_ENV: development
  SESSION_SECRET:

environment:
  - RACK_ENV=development
  - SESSION_SECRET

如果变量名称或者值中用到 true|false,yes|no 等表达 布尔 含义的词汇,最好放到引号里,避免 YAML 自动解析某些内容为对应的布尔语义。这些特定词汇,包括

y|Y|yes|Yes|YES|n|N|no|No|NO|true|True|TRUE|false|False|FALSE|on|On|ON|off|Off|OFF

expose
暴露端口,但不映射到宿主机,只被连接的服务访问。

仅可以指定内部端口为参数

expose:
 - "3000"
 - "8000"
external_links

注意:不建议使用该指令。

链接到 docker-compose.yml 外部的容器,甚至并非 Compose 管理的外部容器。

external_links:
 - redis_1
 - project_db_1:mysql
 - project_db_1:postgresql
extra_hosts

类似 Docker 中的 --add-host 参数,指定额外的 host 名称映射信息。

extra_hosts:
 - "googledns:8.8.8.8"
 - "dockerhub:52.1.157.61"

会在启动后的服务容器中 /etc/hosts 文件中添加如下两条条目。

8.8.8.8 googledns
52.1.157.61 dockerhub

healthcheck
通过命令检查容器是否健康运行。

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost"]
  interval: 1m30s
  timeout: 10s
  retries: 3

image
指定为镜像名称或镜像 ID。如果镜像在本地不存在,Compose 将会尝试拉去这个镜像。

image: ubuntu
image: orchardup/postgresql
image: a4bc65fd
labels

为容器添加 Docker 元数据(metadata)信息。例如可以为容器添加辅助说明信息。

labels:
  com.startupteam.description: "webapp for a startup team"
  com.startupteam.department: "devops department"
  com.startupteam.release: "rc3 for v1.0"

links
注意:不推荐使用该指令。

logging
配置日志选项。

logging:
  driver: syslog
  options:
    syslog-address: "tcp://192.168.0.42:123"

目前支持三种日志驱动类型。

driver: "json-file"
driver: "syslog"
driver: "none"

options 配置日志驱动的相关参数。

options:
  max-size: "200k"
  max-file: "10"

network_mode
设置网络模式。使用和 docker run 的 --network 参数一样的值。

network_mode: "bridge"
network_mode: "host"
network_mode: "none"
network_mode: "service:[service name]"
network_mode: "container:[container name/id]"
networks

配置容器连接的网络。

version: "3"
services:

  some-service:
    networks:
     - some-network
     - other-network

networks:
  some-network:
  other-network:

pid
跟主机系统共享进程命名空间。打开该选项的容器之间,以及容器和宿主机系统之间可以通过进程 ID 来相互访问和操作。

pid: "host"

ports
暴露端口信息。

使用宿主端口:容器端口 (HOST:CONTAINER) 格式,或者仅仅指定容器的端口(宿主将会随机选择端口)都可以。

ports:
 - "3000"
 - "8000:8000"
 - "49100:22"
 - "127.0.0.1:8001:8001"

注意:当使用 HOST:CONTAINER 格式来映射端口时,如果你使用的容器端口小于 60 并且没放到引号里,可能会得到错误结果,因为 YAML 会自动解析 xx:yy 这种数字格式为 60 进制。为避免出现这种问题,建议数字串都采用引号包括起来的字符串格式。

secrets
存储敏感数据,例如 mysql 服务密码。

version: "3"
services:

mysql:
  image: mysql
  environment:
    MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
  secrets:
    - db_root_password
    - my_other_secret

secrets:
  my_secret:
    file: ./my_secret.txt
  my_other_secret:
    external: true

security_opt
指定容器模板标签(label)机制的默认属性(用户、角色、类型、级别等)。例如配置标签的用户名和角色名。

security_opt:
    - label:user:USER
    - label:role:ROLE

stop_signal
设置另一个信号来停止容器。在默认情况下使用的是 SIGTERM 停止容器。

stop_signal: SIGUSR1

sysctls
配置容器内核参数。

sysctls:
  net.core.somaxconn: 1024
  net.ipv4.tcp_syncookies: 0

sysctls:
  - net.core.somaxconn=1024
  - net.ipv4.tcp_syncookies=0

ulimits
指定容器的 ulimits 限制值。

例如,指定最大进程数为 65535,指定文件句柄数为 20000(软限制,应用可以随时修改,不能超过硬限制) 和 40000(系统硬限制,只能 root 用户提高)。

  ulimits:
    nproc: 65535
    nofile:
      soft: 20000
      hard: 40000

volumes
数据卷所挂载路径设置。可以设置宿主机路径 (HOST:CONTAINER) 或加上访问模式 (HOST:CONTAINER:ro)。

该指令中路径支持相对路径。

volumes:
 - /var/lib/mysql
 - cache/:/tmp/cache
 - ~/configs:/etc/configs/:ro

其它指令
此外,还有包括 domainname, entrypoint, hostname, ipc, mac_address, privileged, read_only, shm_size, restart, stdin_open, tty, user, working_dir 等指令,基本跟 docker run 中对应参数的功能一致。

指定服务容器启动后执行的入口文件。

entrypoint: /code/entrypoint.sh
指定容器中运行应用的用户名。

user: nginx
指定容器中工作目录。

working_dir: /code
指定容器中搜索域名、主机名、mac 地址等。

domainname: your_website.com
hostname: test
mac_address: 08-00-27-00-0C-0A

允许容器中运行一些特权命令。

privileged: true
指定容器退出后的重启策略为始终重启。该命令对保持服务始终运行十分有效,在生产环境中推荐配置为 always 或者 unless-stopped。

restart: always
以只读模式挂载容器的 root 文件系统,意味着不能对容器内容进行修改。

read_only: true
打开标准输入,可以接受外部输入。

stdin_open: true
模拟一个伪终端。

tty: true
读取变量
Compose 模板文件支持动态读取主机的系统环境变量和当前目录下的 .env 文件中的变量。

例如,下面的 Compose 文件将从运行它的环境中读取变量 ${MONGO_VERSION} 的值,并写入执行的指令中。

version: "3"
services:

db:
  image: "mongo:${MONGO_VERSION}"

如果执行 MONGO_VERSION=3.2 docker-compose up 则会启动一个 mongo:3.2 镜像的容器;如果执行 MONGO_VERSION=2.8 docker-compose up 则会启动一个 mongo:2.8 镜像的容器。

若当前目录存在 .env 文件,执行 docker-compose 命令时将从该文件中读取变量。

在当前目录新建 .env 文件并写入以下内容。

# 支持 # 号注释
MONGO_VERSION=3.6
执行 docker-compose up 则会启动一个 mongo:3.6 镜像的容器。

推荐阅读更多精彩内容