Docker 应用基础

目录

前言

基本概念

安装 Docker

Ubuntu 安装 Docker CE

macOS 安装 Docker

使用 Docker 镜像

获取镜像

运行镜像

列出镜像

镜像体积

虚悬镜像

删除本地镜像

查找镜像

设置镜像标签

创建镜像

利用 commit 构成镜像

使用 Dockerfile 定制镜像

Dockerfile 指令详解

COPY 复制文件

ADD 更高级的复制文件

CMD 容器启动命令

ENTRYPOINT 入口点

ENV 设置环境变量

ARG 构建参数

VOLUME 定义匿名卷

EXPOSE 声明端口

WORKDIR 指定工作目录

USER 指定当前用户

HEALTHCHECK 健康检查

ONBUILD 为他人做嫁衣裳

Dockerfile 多阶段构建

全部放入一个 Dockerfile

分散到多个 Dockerfile

使用多阶段构建

只构建某一阶段的镜像

构建时从其他镜像复制文件

操作 Docker 容器

新建并启动

启动已终止容器

后台运行

终止容器

attach 命令

exec 命令

导出和导入容器

导出容器

导入容器快照

删除容器

清理所有处于终止状态的容器

Docker 数据管理

数据卷

创建一个数据卷

查看所有的 数据卷

查看指定 数据卷 的信息

启动一个挂载数据卷的容器

查看数据卷的具体信息

删除数据卷

挂载主机目录

挂载一个主机目录作为数据卷

挂载一个本地主机文件作为数据卷


前言

什么是 Docker

Docker 是一个开源的应用容器引擎,基于 Go 语言 并遵从Apache2.0协议开源。

Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的 Linux 机器上,也可以实现虚拟化。

Docker 和传统虚拟化方式的不同之处。 传统虚拟机技术是虚拟出一套硬件后, 在其上运行一个完整操作系统, 在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核, 容器内没有自己的内核, 而且也没有进行硬件虚拟。 因此容器要比传统虚拟机更为轻便。

Docker 在运行时分为 Docker 引擎( 也就是服务端守护进程)和 客户端工具。 Docker 的引擎提供了一组 REST API, 被称为 Docker Remote API, 而如 docker 命令这样的客户端工具, 则是通过这组 API 与 Docker 引擎交互, 从而完成各种功能。 因此, 虽然表面上我们好像是在本机执行各种 docker 功能, 但实际上, 一切都是使用的远程调用形式在服务端( Docker 引擎) 完成。 

Docker 的用途

Docker 的主要用途,目前有三大类。

(1)提供一次性的环境。比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。

(2)提供弹性的云服务。因为 Docker 容器可以随开随关,很适合动态扩容和缩容。

(3)组建微服务架构。通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构。

 

基本概念

Docker 包括三个基本概念

  • 镜像( Image )
  • 容器( Container )
  • 仓库( Repository )

理解了这三个概念, 就理解了 Docker 的整个生命周期。

Docker 镜像

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

镜像构建时, 会一层层构建, 前一层是后一层的基础。 每一层构建完就不会再发生改变, 后一层上的任何改变只发生在自己这一层。 每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。

分层存储, Docker 在设计时, 充分利用 Union FS 的技术, 将其设计为分层存储的架构。Union FS 是有最大层数限制的不得超过 127 层。 分层存储的特征还使得镜像的复用、 定制变的更为容易。 甚至可以用之前构建好的镜像作为基础层, 然后进一步添加新的层, 以定制自己所需的内容, 构建新的镜像。

Docker 容器

镜像( Image ) 和容器( Container ) 的关系, 就像是面向对象程序设计中的类 和 实例 一样, 镜像是静态的定义, 容器是镜像运行时的实体。 容器可以被创建、 启动、 停止、 删除、 暂停等。

容器的实质是进程, 但与直接在宿主执行的进程不同, 容器进程运行于属于自己的独立的 命名空间。 因此容器可以拥有自己的 root 文件系统、 自己的网络配置、自己的进程空间, 甚至自己的用户 ID 空间。 容器内的进程是运行在一个隔离的环境里, 使用起来, 就好像是在一个独立于宿主的系统下操作一样。 这种特性使得容器封装的应用比直接在宿主运行更加安全。

镜像使用的是分层存储, 容器也是如此。 每一个容器运行时, 是以镜像为基础层, 在其上创建一个当前容器的存储层, 我们可以称这个为容器运行时读写而准备的存储层为 容器存储层。

容器存储层的生存周期和容器一样, 容器消亡时, 容器存储层也随之消亡。 因此,任何保存于容器存储层的信息都会随容器删除而丢失。容器不应该向其存储层内写入任何数据, 容器存储层要保持无状态化。所有的文件写入操作, 都应该使用 数据卷( Volume) 、 或者绑定宿主目录, 在这些位置的读写会跳过容器存储层, 直接对宿主发生读写, 其性能和稳定性更高。

数据卷的生存周期独立于容器, 容器消亡, 数据卷不会消亡。 因此, 使用数据卷后, 容器删除或者重新运行之后, 数据却不会丢失。

Docker 仓库

镜像构建完成后, 可以很容易的在当前宿主机上运行, 但是, 如果需要在其它服务器上使用这个镜像, 我们就需要一个集中的存储、 分发镜像的服务, Docker Registry 就是这样的服务。

一个 Docker Registry 中可以包含多个 仓库( Repository ); 每个仓库可以包含多个 标签( Tag ); 每个标签对应一个镜像。通常, 一个仓库会包含同一个软件不同版本的镜像, 而标签就常用于对应该软件的各个版本。 我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。 如果不给出标签, 将以 latest 作为默认标签。

仓库名经常以 两段式路径 形式出现, 比如 jwilder/nginx-proxy , 前者往往意味着 Docker Registry 多用户环境下的用户名, 后者则往往是对应的软件名。 但这并非绝对, 取决于所使用的具体 Docker Registry 的软件或服务。

仓库又可以分为两种形式:

  • public(公有仓库)
  • private(私有仓库)

Docker Registry 公有仓库是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。最常使用的 Registry 公开服务是官方的 Docker Hub, 这也是默认的 Registry, 并拥有大量的高质量的官方镜像。

除了使用公开服务外, 用户还可以在本地搭建私有 Docker Registry。 Docker 官方提供了 Docker Registry 镜像, 可以直接使用做为私有 Registry 服务。

 

安装 Docker

Docker 从 17.03 版本之后分为 CE(Community Edition: 社区版) 和 EE(Enterprise Edition: 企业版),我们用社区版就可以了。

Ubuntu 安装 Docker CE

Docker CE 支持以下版本的 Ubuntu 操作系统 :

  • Bionic 18.04 (LTS)
  • Xenial 16.04 (LTS)

卸载旧版本, 旧版本的 Docker 称为 docker 或者 docker-engine , 使用以下命令卸载旧版本 :

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

更新 apt 软件包缓存, 并安装 docker-ce :

$ sudo apt-get update

$ sudo apt-get install docker-ce

使用脚本自动安装, 在测试或开发环境中 Docker 官方为了简化安装流程, 提供了一套便捷的安装脚本, Ubuntu 系统上可以使用这套脚本安装 :

$ curl -fsSL get.docker.com -o get-docker.sh

$ sudo sh get-docker.sh --mirror Aliyun

启动 Docker CE :

$ sudo systemctl enable docker

$ sudo systemctl start docker

建立 docker 用户组

默认情况下, docker 命令会使用 Unix socket 与 Docker 引擎通讯。 而只有root 用户和 docker 组的用户才可以访问 Docker 引擎的 Unix socket。 出于安全考虑, 一般 Linux 系统上不会直接使用 root 用户。 因此, 更好地做法是将需要使用 docker 的用户加入 docker 用户组。

建立 docker 组 :

$ sudo groupadd docker

将当前用户加入 docker 组 :

$ sudo usermod -aG docker $USER

退出当前终端并重新登录, 进行如下测试。测试 Docker 是否安装正确 :

$ docker run hello-world

macOS 安装 Docker

Docker for Mac 要求系统最低为 macOS El Capitan 10.11。

使用 Homebrew 安装,Homebrew 的 Cask 已经支持 Docker for Mac, 因此可以很方便的使用 Homebrew Cask 来进行安装 :

$ brew cask install docker

手动下载安装,如果需要手动下载, 请下载 Stable 或 Edge 版本的 Docker for Mac。如同 macOS 其它软件一样, 安装也非常简单, 双击下载的 .dmg 文件, 然后将那只叫 Moby 的鲸鱼图标拖拽到 Application 文件夹即可。

启动终端后, 通过命令可以检查安装后的 Docker 版本:

$ docker --version
Docker version 18.09.0, build 4d60db4

$ docker-compose --version
docker-compose version 1.23.2, build 1110ad01

$ docker-machine --version
docker-machine version 0.16.0, build 702c267f

使用 Docker 镜像

Docker 运行容器前需要本地存在对应的镜像, 如果本地不存在该镜像, Docker 会从镜像仓库下载该镜像。

获取镜像

从 Docker 镜像仓库获取镜像的命令是 docker pull 。 其命令格式为:

docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]

具体的选项可以通过 docker pull --help 命令看到, 这里我们说一下镜像名称的格式。

  • Docker 镜像仓库地址: 地址的格式一般是 <域名/IP>[:端口号] 。 不写的话,默认地址是 Docker Hub。
  • 仓库名: 如之前所说, 这里的仓库名是两段式名称, 即 <用户名>/<软件名> 。对于 Docker Hub, 如果不给出用户名, 则默认为 library , 也就是官方镜像。

比如:

$ docker pull ubuntu:18.04

上面的命令中没有给出 Docker 镜像仓库地址, 因此将会从 Docker Hub 获取镜像。 而镜像名称是 ubuntu:18.04 , 因此将会获取官方镜像 library/ubuntu 仓库中标签为 18.04 的镜像。

注:优化基础镜像的方法就是选用合适的更小的基础镜像, 常用的 Linux 系统镜像一般有 Ubuntu 、CentOs 、Alpine ,其中  Alpine 更推荐使用。 Alpine 是一个高度精简又包含了基本工具的轻量级 Linux 发行版,基础镜像只有 4.41M,各开发语言和框架都有基于 Alpine 制作的基础镜像。

运行镜像

有了镜像后, 我们就能够以这个镜像为基础启动并运行一个容器。docker run 就是运行容器的命令, 以上面的 ubuntu:18.04 为例。

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

列出镜像

要想列出已经下载下来的镜像, 可以使用 docker image ls 命令。默认的 docker image ls 列表中只会显示顶层镜像, 如果希望显示包括中间层镜像在内的所有镜像的话, 需要加 -a 参数。还支持强大的过滤器参数 --filter ,或者简写 -f 。

$ docker image ls

REPOSITORY          TAG             IMAGE ID            CREATED             SIZE
hello-world         latest          fce289e99eb9        6 months ago        1.84kB

各个选项说明:

  • REPOSITORY:表示镜像的仓库源
  • TAG:镜像的标签
  •  IMAGE ID:镜像ID
  • CREATED:镜像创建时间
  • SIZE:镜像大小

镜像体积

docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。 由于 Docker 镜像是多层存储结构, 并且可以继承、 复用, 因此不同镜像可能会因为使用相同的基础镜像, 从而拥有共同的层。

通过以下命令来便捷的查看镜像、 容器、 数据卷所占用的空间:

$ docker system df

虚悬镜像

由于新旧镜像同名, 旧镜像名称被取消, 从而出现仓库名、 标签均为 <none> 的镜像。 这类无标签镜像也被称为 虚悬镜像(dangling image) , 可以用下面的命令专门显示这类镜像:

$ docker image ls -f dangling=true

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

$ docker image prune

删除本地镜像

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

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

其中, <镜像> 可以是 镜像短 ID 、 镜像长 ID 、 镜像名 或者 镜像摘要 。

删除行为分为两类, 一类是 Untagged , 另一类是 Deleted 。 当我们使用上面命令删除镜像的时候, 实际上是在要求删除某个标签的镜像。 因为一个镜像可以对应多个标签, 因此当我们删除了所指定的标签后, 可能还有别的标签指向了这个镜像那么 Delete 行为就不会发生。所以并非所有的 docker image rm 都会产生删除镜像的行为, 有可能仅仅是 Untagged 取消了某个标签而已。 当该镜像所有的标签都被取消了, 该镜像很可能会失去了存在的意义, 因此会触发 Delete 删除行为。 如果有某个其它镜像正依赖于当前镜像的某一层。 这种情况, 依旧不会触发删除该层的行为。 直到没有任何层依赖当前层时, 才会真实的删除当前层。

查找镜像

可以使用 docker search 命令来搜索镜像。比如我们需要一个httpd的镜像来作为我们的web服务。我们可以通过 docker search 命令搜索 httpd 来寻找适合我们的镜像:

$ docker search httpd

NAME               DESCRIPTION                         STARS      OFFICIAL    AUTOMATED
httpd              The Apache HTTP Server Project      2537       [OK]                
centos/httpd                                           23                     [OK]

各个选项说明:

  • NAME:镜像仓库源的名称
  • DESCRIPTION:镜像的描述
  • OFFICIAL:是否docker官方发布

设置镜像标签

使用 docker tag 命令,为镜像添加一个新的标签:

$ docker tag 860c279d2fec runoob/centos:dev

REPOSITORY       TAG     IMAGE ID         CREATED         SIZE
runoob/centos    6.7     860c279d2fec     5 hours ago     190.6 MB
runoob/centos    dev     860c279d2fec     5 hours ago     190.6 MB

创建镜像

当我们从docker镜像仓库中下载的镜像不能满足我们的需求时,我们可以通过以下两种方式对镜像进行更改。

  • 从已经创建的容器中更新镜像,并且提交这个镜像
  • 使用 Dockerfile 指令来创建一个新的镜像

利用 commit 构成镜像

当我们运行一个容器的时候( 如果不使用卷的话) , 我们做的任何文件修改都会被记录于容器存储层里。 而 Docker 提供了一个 docker commit 命令, 可以将容器的存储层保存下来成为镜像。 换句话说, 就是在原有镜像的基础上, 再叠加上容器的存储层, 并构成新的镜像。 以后我们运行这个新镜像的时候, 就会拥有原有容器最后的文件变化。

以定制一个 Web 服务器为例子,用 nginx 镜像启动一个容器, 命名为 webserver , 并且映射了 80 端口:

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

-d:后台运行容器,并返回容器ID:

-p:指定端口映射,格式为:主机(宿主)端口:容器端口 

使用 docker exec 命令进入容器, 修改其内容:

$ docker exec -it webserver bash

root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit

我们以交互式终端方式进入 webserver 容器, 并执行了 bash 命令, 也就是获得一个可操作的 Shell。 然后, 用 <h1>Hello, Docker!</h1> 覆盖了 /usr/share/nginx/html/index.html 的内容。

我们修改了容器的文件, 也就是改动了容器的存储层。 我们可以通过 docker diff 命令看到具体的改动:

$ docker diff webserver

docker commit 的语法格式为:

docker commit [选项] <容器ID或容器名> [<仓库名>[:<标签>]]

$ docker commit --author "name"  --message "信息说明"  webserver  nginx:v2

可以用 docker history 具体查看镜像内的历史记录:

$ docker history nginx:v2

镜像类似于只读磁盘, 统一文件系统  (union file system)  技术能够将不同的层整合成一个文件系统。 容器 = 镜像 + 读写层, 容器运行时创建的 容器存储层 就像是可读可写的内存。

使用 Dockerfile 定制镜像

从刚才的 docker commit 中, 我们可以了解到, 镜像的定制实际上就是定制每一层所添加的配置、 文件。 我们可以把每一层修改、 安装、 构建、 操作的命令都写入一个脚本, 用这个脚本来构建、 定制镜像。这个脚本就是 Dockerfile。

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

以之前定制 nginx 镜像为例, 这次使用 Dockerfile 来定制。在一个空白目录中, 建立一个文本文件, 并命名为 Dockerfile,其内容为:

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

FROM 指定基础镜像。所谓定制镜像, 那一定是以一个镜像为基础, 在其上进行定制。因此一个 Dockerfile 中 FROM 是必备的指令,并且必须是第一条指令。

Docker 还存在一个特殊的镜像, 名为 scratch 。 这个镜像是虚拟的概念, 并不实际存在, 它表示一个空白的镜像。如果以 scratch 为基础镜像的话, 意味着不以任何镜像为基础, 接下来所写的指令将作为镜像第一层开始存在。

RUN 指令是用来执行命令行命令的。其格式有两种:

shell 格式: RUN  <命令> , 就像直接在命令行中输入的命令一样。

exec 格式: RUN  ["可执行文件", "参数1", "参数2"] , 这更像是函数调用中的格式。
RUN ["/bin/bash", "-c", "echo hello"]

RUN 就像 Shell 脚本一样可以执行命令, 但是我们不可以像 Shell 脚本一样把每个命令对应一个 RUN,因为 Dockerfile 中每一个指令都会建立一层, RUN 也不例外。

正确的写法应该是仅仅使用一个 RUN 指令, 并使用 && 将各个所需命令串联起来。 Dockerfile 还支持 Shell 类的行尾添加 \ 的
命令换行方式, 以及行首 # 进行注释的格式。

此外, 命令的最后应该添加清理工作的命令,这是很重要的一步, 因为镜像是多层存储, 每一层的东西并不会在下一层
被删除, 会一直跟随着镜像。 因此镜像构建时, 一定要确保每一层只添加真正需要添加的东西, 任何无关的东西都应该清理掉,否则将构建出很臃肿的镜像。

RUN buildDeps='gcc libc6-dev make wget' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.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

以上所有的命令只有一个目的, 就是编译、 安装 redis 可执行文件。

使用 docker build 命令进行镜像构建。 其格式为:

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

// 使用当前目录的 Dockerfile 创建镜像
$ docker build -t nginx:v3 .

// 使用URL github.com/creack/docker-firefox 的 Dockerfile 创建镜像
$ docker build github.com/creack/docker-firefox

// 也可以通过 -f Dockerfile 指定文件的位置创建镜像
$ docker build -f /path/to/a/Dockerfile .

--tag, -t:镜像的名字及标签,在这里我们指定了最终镜像的名称 -t nginx:v3 

docker build 命令构建镜像, 其实并非在本地构建, 而是在服务端, 也就是 Docker 引擎中构建的。如何才能让服务端获得本地文件?这就引入了上下文的概念。当构建的时候, 用户会指定构建镜像上下文的路径, docker build 命令得知这个路径后, 会将路径下的所有内容打包, 然后上传给 Docker 引擎。 这样 Docker 引擎收到这个上下文包后, 展开就会获得构建镜像所需的一切文件。

docker build 命令最后有一个 . 。 . 并不是表示当前目录, .  实际上是在指定上下文的目录,表示使用当前路径做为上下文目录进行构建 。一般来说, 应该会将 Dockerfile 置于一个空目录下, 或者项目根目录下。 如果该目录下没有所需文件, 那么应该把所需文件复制一份过来。 如果目录下有些东西确实不希望构建时传给 Docker 引擎, 那么可以用 .gitignore 一样的语法写一个 .dockerignore , 该文件是用于剔除不需要作为上下文传递给 Docker 引擎的。

Dockerfile 指令详解

Dockerfile 功能很强大, 它提供了十多个指令。

COPY 复制文件

COPY [--chown=<user>:<group>] <源路径>... <目标路径>

COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]

和 RUN 指令一样, 也有两种格式, 一种类似于命令行, 一种类似于函数调用。

COPY 指令将从构建上下文目录中 <源路径> 的文件/目录复制到新的一层的镜像内的 <目标路径> 位置。

<源路径> 可以是多个, 甚至可以是通配符, 其通配符规则要满足 Go 的 filepath.Match 规则, 如:

COPY hom* /mydir/

COPY hom?.txt /mydir/

<目标路径> 可以是容器内的绝对路径, 也可以是相对于工作目录的相对路径( 工作目录可以用 WORKDIR 指令来指定) 。 目标路径不需要事先创建, 如果目录不存在会在复制文件前先行创建缺失目录。

此外, 还需要注意一点, 使用 COPY 指令, 源文件的各种元数据都会保留。 比如读、 写、 执行权限、 文件变更时间等。 这个特性对于镜像定制很有用。 特别是构建相关文件都在使用 Git 进行管理的时候。

在使用该指令的时候还可以加上 --chown=<user>:<group> 选项来改变文件的所属用户及所属组。

ADD 更高级的复制文件

ADD 指令和 COPY 的格式和性质基本一致。 但是在 COPY 基础上增加了一些功能。

如果 <源路径> 为一个 tar 压缩文件的话, 压缩格式为 gzip , bzip2 以及 xz 的情况下, ADD 指令将会自动解压缩这个压缩文件到 <目标路径> 。

在 Docker 官方的 Dockerfile 最佳实践文档 中要求, 尽可能的使用 COPY , 因为 COPY 的语义很明确, 就是复制文件而已, 而 ADD 则包含了更复杂的功能, 其行为也不一定很清晰。 最适合使用 ADD 的场合, 就是所提及的需要自动解压缩的场合。

另外需要注意的是, ADD 指令会令镜像构建缓存失效, 从而可能会令镜像构建变得比较缓慢。

CMD 容器启动命令

每个 Dockerfile 中只能有一个 CMD 如果有多个那么只执行最后一个。 CMD 指令的格式和 RUN 相似, 也是两种格式:

shell 格式: CMD <命令>

exec 格式: CMD ["可执行文件", "参数1", "参数2"...]
CMD ["systemctl", "start", "mysql.service"]

参数列表格式: CMD ["参数1", "参数2"...] 。 在指定了 ENTRYPOINT 指令后, 用 CMD 指定具体的参数。

容器是进程。 既然是进程, 那么在启动容器的时候, 需要指定所运行的程序及参数。 CMD 指令就是用于指定默认的容器主进程的启动命令的。 容器就是为了主进程而存在的, 主进程退出, 容器就失去了存在的意义, 从而退出, 其它辅助进程不是它需要关心的东西。

在运行时可以指定新的命令来替代镜像设置中的这个默认命令, 在指令格式上, 一般推荐使用 exec 格式。 如果使用 shell 格式的话, 实际的命令会被包装为 sh -c 的参数的形式进行执行。 比如:

CMD echo $HOME

在实际执行中, 会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

ENTRYPOINT 入口点

ENTRYPOINT 的格式和 RUN 指令格式一样, 分为 exec 格式和 shell 格式。且 ENTRYPOINT 也只能存在一个,若存在多个那么只执行最后一个。 ENTRYPOINT 的目的和 CMD 一样, 都是在指定容器启动程序及参数。 ENTRYPOINT 在运行时也可以替代, 不过比 CMD 要略显繁琐, 需要通过docker run 的参数 --entrypoint 来指定。

当指定了 ENTRYPOINT 后, CMD 的含义就发生了改变, 不再是直接的运行其命令, 而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令, 换句话说实际执行时, 将变为:

<ENTRYPOINT> "<CMD>"

ENV 设置环境变量

ENV <key> <value>

ENV <key1>=<value1> <key2>=<value2>...

这个指令很简单, 就是设置环境变量而已, 无论是后面的其它指令, 如 RUN , 还是运行时的应用, 都可以直接使用这里定义的环境变量。

ENV VERSION=1.0 DEBUG=on \
    NAME="Happy Feet"

这个例子中演示了如何换行, 以及对含有空格的值用双引号括起来的办法, 这和 Shell 下的行为是一致的。定义了环境变量, 那么在后续的指令中, 就可以使用这个环境变量。如:定义了环境变量 VERSION , 其后的指令可以多次使用 $VERSION 来进行操作定制。 

ARG 构建参数

ARG <参数名>[=<默认值>]

构建参数和 ENV 的效果一样, 都是设置环境变量。 所不同的是, ARG 所设置的构建环境的环境变量, 在将来容器运行时是不会存在这些环境变量的。但是 docker history 还是可以看到所有值的。

Dockerfile 中的 ARG 指令是定义参数名称, 以及定义其默认值。 该默认值可以在构建命令 docker build 中用 --build-arg <参数名>=<值> 来覆盖。

VOLUME 定义匿名卷

VOLUME ["<路径1>", "<路径2>"...]

VOLUME <路径>

VOLUME /data

这里的 /data 目录就会在运行时自动挂载为匿名卷, 任何向 /data 中写入的信息都不会记录进容器存储层。

EXPOSE 声明端口

EXPOSE <端口1> [<端口2>...] 

EXPOSE 指令是声明运行时容器提供服务端口, 这只是一个声明, 在运行时并不会因为这个声明应用就会开启这个端口的服务。 在 Dockerfile 中写入这样的声明有两个好处, 一个是帮助镜像使用者理解这个镜像服务的守护端口, 以方便配置映射; 另一个用处则是在运行时使用随机端口映射时, 也就是 docker run -P 时, 会自动随机映射 EXPOSE 的端口。

要将 EXPOSE 和在运行时使用 -p <宿主端口>:<容器端口> 区分开来。 -p , 是映射宿主端口和容器端口, 换句话说, 就是将容器的对应端口服务公开给外界访问, 而 EXPOSE 仅仅是声明容器打算使用什么端口而已, 并不会自动在宿主进行端口映射。

WORKDIR 指定工作目录

WORKDIR <工作目录路径>

使用 WORKDIR 指令可以来指定工作目录( 或者称为当前目录) , 以后各层的当前目录就被改为指定的目录, 如该目录不存在, WORKDIR 会自动建立目录。 如果需要改变以后各层的工作目录的位置, 那么应该使用 WORKDIR 指令。

USER 指定当前用户

USER <用户名>[:<用户组>]

USER 指令和 WORKDIR 相似, 都是改变环境状态并影响以后的层。 WORKDIR 是改变工作目录, USER 则是改变之后层的执行 RUN , CMD 以及 ENTRYPOINT 这类命令的身份。 USER 只是帮助你切换到指定用户而已, 这个用户必须是事先建立好的, 否则无法切换。

如果以 root 执行的脚本, 在执行期间希望改变身份, 不要使用 su 或者 sudo , 这些都需要比较麻烦的配置, 而且在 TTY 缺失的环境下经常出错。 建议使用 gosu 。

# 建立 redis 用户, 并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/
    releases/download/1.7/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD, 并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTHCHECK 健康检查

HEALTHCHECK [选项] CMD <命令> : 设置检查容器健康状况的命令

HEALTHCHECK NONE : 如果基础镜像有健康检查指令, 使用这行可以屏蔽掉其健康检查指令

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常。这是 Docker 1.12 引入的新指令。

Docker  通过 HEALTHCHECK 指令指定一行命令, 用这行命令来判断容器主进程的服务状态是否还正常, 从而比较真实的反应容器实际状态。 当在一个镜像指定了 HEALTHCHECK 指令后, 用其启动容器, 初始状态会为 starting , 在 HEALTHCHECK 指令检查成功后变为 healthy , 如果连续一定次数失败, 则会变为 unhealthy 。

HEALTHCHECK 支持下列选项:

  • --interval=<间隔> : 两次健康检查的间隔, 默认为 30 秒;
  • --timeout=<时长> : 健康检查命令运行超时时间, 如果超过这个时间, 本次健康检查就被视为失败, 默认 30 秒;
  • --retries=<次数> : 当连续失败指定次数后, 则将容器状态视为 unhealthy , 默认 3 次。

和 CMD , ENTRYPOINT 一样, HEALTHCHECK 只可以出现一次, 如果写了多个,只有最后一个生效。

在 HEALTHCHECK [选项] CMD 后面的命令, 格式和 ENTRYPOINT 一样, 分为 shell 格式, 和 exec 格式。 命令的返回值决定了该次健康检查的 成功 与 否: 0 : 成功; 1 : 失败; 2 : 保留, 不要使用这个值。

当运行该镜像后, 可以通过以下命令查看状态:

docker container ls 

为了帮助排障, 健康检查命令的输出( 包括 stdout 以及 stderr ) 都会被存储于健康状态里, 可以通过以下命令查看:

docker inspect

ONBUILD 为他人做嫁衣裳

ONBUILD <其它指令> 

ONBUILD 是一个特殊的指令, 它后面跟的是其它指令, 比如 RUN , COPY 等, 而这些指令, 在当前镜像构建时并不会被执行。 只有当以当前镜像为基础镜像, 去构建下一级镜像的时候才会被执行。

Dockerfile 多阶段构建

在 Docker 17.05 版本之前, 我们构建 Docker 镜像时, 通常会采用两种方式。

全部放入一个 Dockerfile

一种方式是将所有的构建过程编包含在一个 Dockerfile 中, 包括项目及其依赖库的编译、 测试、 打包等流程, 这里可能会带来的一些问题:

  • 镜像层次多, 镜像体积较大, 部署时间变长
  • 源代码存在泄露的风险

分散到多个 Dockerfile

另一种方式, 就是我们事先在一个 Dockerfile 将项目及其依赖库编译测试打包好后, 再将其拷贝到运行环境中, 这种方式需要编写两个 Dockerfile 和一些编译脚本才能将其两个阶段自动整合起来, 这种方式虽然可以很好地规避第一种方式存在的风险, 但明显部署过程较复杂。

使用多阶段构建

为解决以上问题, Docker v17.05 开始支持多阶段构建 ( multistage builds )。使用多阶段构建我们就可以很容易解决前面提到的问题, 并且只需要编写一个 Dockerfile :

FROM golang:1.9-alpine as builder
RUN apk --no-cache add git
WORKDIR /go/src/github.com/go/helloworld/
RUN go get -d -v github.com/go-sql-driver/mysql
COPY app.go .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
FROM alpine:latest as prod
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=0 /go/src/github.com/go/helloworld/app .
CMD ["./app"]

只构建某一阶段的镜像

可以使用 as 来为某一阶段命名, 例如:

FROM golang:1.9-alpine as builder

当只想构建 builder 阶段的镜像时, 增加 --target=builder 参数即可:

docker build --target builder -t username/imagename:tag .

构建时从其他镜像复制文件

从上一阶段的镜像中复制文件:

COPY --from=0 /go/src/github.com/go/helloworld/app .

也可以复制任意镜像中的文件:

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

操作 Docker 容器

简单的说, 容器是独立运行的一个或一组应用, 以及它们的运行态环境。

启动容器有两种方式, 一种是基于镜像新建一个容器并启动, 另外一个是将在终止状态( stopped ) 的容器重新启动。

新建并启动

$ docker run

下面的命令则启动一个 bash 终端, 允许用户进行交互:

$ docker run -t -i ubuntu:18.04 /bin/bash

其中, -t 选项让Docker分配一个伪终端( pseudo-tty) 并绑定到容器的标准输入上, -i 则让容器的标准输入保持打开。

启动已终止容器

将一个已经终止的容器启动运行:

$ docker container start

后台运行

更多的时候, 需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。 此时, 可以通过添加 -d 参数来实现。

$ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"

可以通过 docker container ls 命令来查看容器信息。

要获取容器的输出信息, 可以通过 docker container logs 命令。

$ docker container logs [container ID or NAMES]

终止容器

可以使用以下命令来终止一个运行中的容器。此外, 当 Docker 容器中指定的应用终结时, 容器也自动终止。

$ docker container stop

终止状态的容器可以用 docker container ls -a 命令看到。

处于终止状态的容器, 可以通过 docker container start 命令来重新启动。 此外, docker container restart 命令会将一个运行态的容器终止, 然后再重新启动它。

进入容器

在使用 -d 参数时, 容器启动后会进入后台。 某些时候需要进入容器进行操作, 包括使用 docker attach 命令或 docker
exec 命令。推荐使用 docker exec 命令。

attach 命令

$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550

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

注意: 如果从这个 stdin 中 exit, 会导致容器的停止。

exec 命令

docker exec 后边可以跟多个参数, 如 -i -t 参数。更多参数说明使用 docker exec --help 查看。

$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6

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

如果从这个 stdin 中 exit, 不会导致容器的停止。 这就是为什么推荐大家使用 docker exec 的原因。

导出和导入容器

导出容器

如果要导出本地某个容器, 可以使用 docker export 命令。

$ docker export 7691a814370e > ubuntu.tar

导入容器快照

可以使用 docker import 从容器快照文件中再导入为镜像, 例如:

$ cat ubuntu.tar | docker import - test/ubuntu:v1.0

删除容器

可以使用 docker container rm 来删除一个处于终止状态的容器。 例如:

$ docker container rm trusting_newton

如果要删除一个运行中的容器, 可以添加 -f 参数。 Docker 会发送 SIGKILL 信号给容器。

清理所有处于终止状态的容器

用 docker container ls -a 命令可以查看所有已经创建的包括终止状态的容器, 如果数量太多要一个个删除可能会很麻烦, 用下面的命令可以清理掉所有处于终止状态的容器。

$ docker container prune

Docker 数据管理

如何在 Docker 内部以及容器之间管理数据, 在容器中管理数据主要有两种方式:

  • 数据卷( Volumes)
  • 挂载主机目录 (Bind mounts)

数据卷

数据卷 是一个可供一个或多个容器使用的特殊目录, 它绕过 UFS, 可以提供很多有用的特性:

  • 数据卷 可以在容器之间共享和重用
  • 对 数据卷 的修改会立马生效
  • 对 数据卷 的更新, 不会影响镜像
  • 数据卷 默认会一直存在, 即使容器被删除

创建一个数据卷

$ docker volume create my-vol

查看所有的 数据卷

$ docker volume ls

查看指定 数据卷 的信息

$ docker volume inspect my-vol

启动一个挂载数据卷的容器

在用 docker run 命令的时候, 使用 --mount 标记来将 数据卷 挂载到容器里。 在一次 docker run 中可以挂载多个 数据卷 。

$ docker run -d -P \
    --name web \
    # -v my-vol:/wepapp \
    --mount source=my-vol,target=/webapp \
    training/webapp \
    python app.py

查看数据卷的具体信息

$ docker inspect web

数据卷 信息在 "Mounts" Key 下面

删除数据卷

数据卷 是被设计用来持久化数据的, 它的生命周期独立于容器, Docker 不会在容器被删除后自动删除 数据卷。

$ docker volume rm my-vol

无主的数据卷可能会占据很多空间, 要清理请使用以下命令:

$ docker volume prune

挂载主机目录

挂载一个主机目录作为数据卷

使用 --mount 标记可以指定挂载一个本地主机的目录到容器中去。

$ docker run -d -P \
    --name web \
    # -v /src/webapp:/opt/webapp \
    --mount type=bind,source=/src/webapp,target=/opt/webapp,readonly \
    training/webapp \
    python app.py

上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp 目录。 这个功能在进行测试的时候十分方便, 比如用户可以放置一些程序到本地目录中, 来查看容器是否正常工作。 

Docker 挂载主机目录的默认权限是 读写 , 用户也可以通过在 --mount 增加 readonly 指定为 只读 。

挂载一个本地主机文件作为数据卷

--mount 标记也可以从主机挂载单个文件到容器中。

$ docker run --rm -it \
    # -v $HOME/.bash_history:/root/.bash_history \
    --mount type=bind,source=$HOME/.bash_history,target=/root/.ba
    sh_history \
    ubuntu:18.04 \
    bash

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Docker是一种开源的容器化平台,可以帮助开发者将应用程序及其依赖项打包成一个独立的容器,实现应用程序的快速部署和跨平台运行。下面是Docker应用部署的一般步骤: 1. 安装Docker:首先需要在目标机器上安装Docker引擎,可以根据不同的操作系统选择相应的安装方式。 2. 编写Dockerfile:Dockerfile是一个文本文件,用于定义Docker镜像的构建过程。在Dockerfile中,你可以指定基础镜像、安装依赖、复制文件、设置环境变量等。 3. 构建镜像:使用Docker命令行工具执行`docker build`命令,根据Dockerfile构建镜像。该命令会根据Dockerfile中的指令逐步执行,并生成一个可运行的镜像。 4. 运行容器:使用`docker run`命令来创建并运行一个容器。在运行容器时,可以指定端口映射、环境变量、数据卷等参数。 5. 配置网络:如果应用程序需要与其他容器或主机进行通信,可以使用Docker网络功能进行配置。可以选择使用默认的桥接网络,或者创建自定义网络。 6. 监控和管理:Docker提供了一系列命令和工具来监控和管理容器。例如,可以使用`docker ps`命令查看正在运行的容器,使用`docker logs`命令查看容器的日志。 7. 扩展和更新:如果需要扩展应用程序的规模或更新应用程序的版本,可以使用Docker Swarm或Kubernetes等容器编排工具来管理多个容器的部署和调度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值