1. 简介

Docker 由 Docker 公司开发,是一个能把应用程序自动部署到容器的开源引擎。

官网:https://www.docker.com/

大多数 Docker 容器启动非常快速,同时一台宿主机也可以运行很多容器,使用户可以充分利用系统资源。

Docker 让开发人员和运维人员的职责分离,开发人员只需要关心容器中运行的程序,运维人员只需要关心如何管理容器。

Docker 还鼓励面向服务的架构和微服务架构,推荐单个容器只运行一个应用程序或进程,组成分布式的应用程序模型,使扩展和调试应用程序变得简单。

Docker 包含三个基本概念:

  • 镜像(Image):镜像相是一个 root 文件系统,用户基于镜像来运行容器。
  • 容器(Container):容器基于镜像启动,容器中可以运行一个或多个进程,是镜像运行的实体。
  • 仓库(Registry/Repository):Registry 是镜像仓库平台,保存用户构建的镜像,分为公有和私有,Docker 公司的公共 Registry 叫做 Docker Hub,用户可以下载和上传镜像,也可以架设自己的私有 Registry。而 Repository 指的是 Registry 中某个用户下的某个仓库,和 Github 中的仓库类似。

Docker Hub 是官方的 Registry,地址为:https://hub.docker.com/。也可以在本地运行自己的 Registry,对应的镜像为 registry。

1.1 架构

Docker 是客户端/服务器(C/S)架构的程序,客户端只需向服务器或守护进程发出请求,服务器或守护进程完成所有工作并返回结果。Docker 提供了一个命令行工具 docker 来与守护进程交互,客户端可以访问本机的守护进程,也可以连接到另一台宿主机上的守护进程。

Docker 结构如下图所示,客户端连接到守护进程,守护进程会访问在本地的镜像以及仓库中的镜像,并且创建和管理容器。

1.2 镜像

Docker 镜像是由文件系统叠加而成的。镜像的最底端是一个引导文件系统 bootfs,往上一层是容器中的基础镜像,是某种操作系统如 Ubuntu 或 Debian 等,而上面还会有 0 到多层的镜像,每一层表示基于镜像的修改再次提交的改动叠加,最上面一层则是容器,是从镜像启动创建容器后的空间。

Docker 使用联合加载(union mount)技术,对于从内核到基础镜像到多层镜像的文件系统叠加到一起,最终封装为包含所有底层的文件和目录的镜像文件。除了容器这一层可以读写,下面的其他层都是只读的。

这样一层层的镜像堆叠,位于下面的镜像称为父镜像(parent image),依此类推知道镜像栈最底部的基础镜像(base image)。当从一个镜像启动容器时,Docker 会在该镜像的最顶层加载一个读写文件系统,执行我们指定的程序或命令。

Dockerfile

Dockerfile 是一个定义文件,它使用基于 DSL(Domain Specific Language)语法的指令,用来构建一个 Docker 镜像。

首先我们需要创建一个空目录,进入该目录,创建 Dockerfile 并在里面编辑。这个目录称为构建环境(build environment),这个环境称为上下文(context),Docker 将会在构建镜像时将上下文中的文件和目录上传到 Docker 守护进程,守护进程就能直接访问到用户想存储到镜像的任何代码、文件、数据。

mkdir my_image
cd my_image
touch Dockerfile

Dockerfile 指令大体上的流程是:从基础镜像运行一个容器,执行一条指令,对容器作出修改,然后提交一个新的镜像层,再基于提交的镜像运行一个新的容器,执行下一条指令,直到所有指令执行完毕。

如果 Dockerfile 某条指令由于某些原因失败了,用户将得到一个可以使用的镜像,这对调试很有帮助。当构建过程失败时,可以将生成的镜像用 docker run 来构建容器,然后再在运行的容器内执行失败的命令,从而发现问题,然后就可以修改 Dockerfile 并再次尝试构建。

Dockerfile 的每条指令都是大写,注释以 # 开头。

下面是一个 Dockerfile 的例子。

# nginx server
FROM ubuntu:12.04
MAINTAINER moondo "a.mail.com"
RUN apt-get update && apt-get install -y nginx
RUN echo 'this is a new container' > /usr/share/nginx/html/index.html
EXPOSE 80

每个 Dockerfile 的第一条指令必须是 FROM 指令,它指定一个已存在的镜像作为基础镜像(base image)。

MAINTAINER 指令标识该镜像的作者和邮件地址。

RUN 指令在当前镜像运行指定命令,并创建一个新的镜像层。指令默认使用命令包装器 /bin/sh -c,使用 exec 格式的 RUN 指令可以避免不支持 shell 的平台或不希望使用 shell 运行。

# 使用 /bin/sh -c 运行
RUN apt-get install -y nginx

# exec 格式,不使用默认命令包装器,Docker 推荐形式
RUN ["apt-get","install","-y","nginx"]

EXPOSE 指令告诉 Docker 该容器内的应用程序将使用的容器内端口,创建容器时并不会因此自动打开该端口,需要在创建容器时指定。

Dockerfile 常用指令如下:

# FROM 指定基础镜像
FROM ubuntu:12.04

# MAINTAINER 标识该镜像的作者和邮件地址
MAINTAINER moondo "a.mail.com"

# ENV 设置环境变量
ENV PATH /root

# RUN 运行命令,并创建一个新的镜像层
RUN apt-get update && apt-get install -y nginx

# CMD 指定容器启动时运行的命令
# docker run 传入的命令可以覆盖 CMD 指令,不指定则会执行 CMD 指定的命令
# 一个 Dockerfile 只能指定一条 CMD 指令,如果有多条,只有最后一条会被使用
CMD /bin/bash -l

# ENTRYPOINT 指定容器启动时运行的命令,相比 CMD 不会被 docker run 的命令覆盖
# docker run 的 --entrypoint 可以覆盖该指令
ENTRYPOINT ["/usr/sbin/nginx"]

# EXPOSE 声明应用程序将使用的容器内端口
EXPOSE 80

# WORKDIR 在容器内部设置工作目录,CMD 和 ENTRYPOINT 将在该目录执行
WORKDIR /opt/myapp

# USER 指定运行时的用户,不指定则默认为 root
USER nginx

# VOLUME 向容器添加卷
# 一个卷可以存在于一个或多个容器的特定目录,在容器间共享,对卷的修改不影响镜像,卷会一直存在直到没有任何容器使用它
# 可以将数据、代码等内容添加到镜像,而不用提交到镜像,从而在多个容器间共享内容
VOLUME ["/opt/project"]

# ADD 将构建环境的文件和目录复制到镜像中
# 只能添加构建目录中的文件或目录,文件源也可以是 URL 格式
Add a.lic /opt/myapp/a.lic

# COPY 将构建环境的文件和目录复制到镜像中
# 只复制文件,不能使用 URL,也不会提取和解压文件
COPY conf.d/ /etc/nginx/

# STOPSIGNAL 设置容器停止时发送的系统信号,可以用信号的数字或名称
STOPSIGNAL SIGKILL

# ARG 定义可以在构建镜像时传递进来的变量
# 如在构建时使用 --build-arg env=prod,可以预先赋值作为默认值
ARG env
ARG env=prod

# ONBUILD 添加触发器,当镜像被其它镜像作为基础镜像时,触发执行指令
ONBUILD ADD . /app/src

CMD 指令会被 docker run 的指令覆盖,ENTRYPOINT 指令不会被覆盖,可以通过以下方式来实现默认参数和可选参数。当 docker run 不指定命令,则以 ENTRYPOINT 作为命令而以 CMD 中的指令为参数,如果指定了则还会使用 ENTRYPOINT 的指令,但是会覆盖 CMD 的指令。

ENTRYPOINT ["/usr/sbin/nginx"]
CMD ["-h"]

2. 命令

2.1 服务器

查看 Docker 程序状态,该命令会返回所有容器和镜像的数量、驱动程序、存储驱动、以及基本配置。

docker info

2.2 仓库

登陆仓库

登陆到 Docker 镜像仓库,如果没有指定镜像仓库地址,默认为 Docker Hub。

docker login

# 登陆私有仓库
docker login -u <admin> -p <passwd> <url>

退出登录仓库

退出登录的仓库。

docker logout

2.3 镜像

列出镜像

列出当前机器上可用的镜像。

docker images

# 查看指定镜像
docker images <image>[:<tag>]

查找镜像

从镜像仓库查找可用镜像。

docker search <image>

拉取镜像

从镜像仓库中拉取镜像到本地。

每个镜像会带有标签,表示不同的版本,默认拉取的是 latest 的镜像,除非指定镜像标签。

docker pull <image>[:<tag>]

# 拉取 latest 镜像
docker pull ubuntu
docker pull ubuntu:latest

# 拉取指定标签的镜像
docker pull ubuntu:12.04

构建镜像

从已有镜像创建的容器修改后创建新的镜像。

选项:

  • -m"":指定镜像的提交信息
  • -a"":指定镜像的作者信息
docker commit <container> <image>[:<tag>]

使用 Dockerfile 构建镜像,比较推荐这一种方式。

选项:

  • -t:指定仓库、名称和标签
  • –build-arg env=prod:构建时传入变量
docker build -t <repository>/<image>[:<tag>] <dir>

# 从当前目录构建镜像
docker build -t huanglianjing/my_image:v1 .

# 从 Git 仓库构建镜像
docker build -t huanglianjing/my_image:v1 git@github.com:huanglianjing/my_image

镜像层级

查看镜像的每一层的指令。

docker history <image>

镜像删除

删除本地镜像。

对于镜像仓库上的镜像,需要在浏览器登录然后在页面上操作删除。

docker rmi <image>

镜像推送

将镜像推送到镜像仓库。

docker push <image>

2.4 容器

查看容器列表

查看已有的容器。

选项:

  • -a:查看所有运行中和已暂停的容器,不带改选项只会返回运行中的容器
  • -q:只返回容器 ID
# 查看所有容器的列表
docker ps -a

# 查看运行中的容器的列表
docker ps

创建容器

指定镜像名称和标签,首先检查本地是否存在该标签的镜像,如果没有就从镜像仓库中查找并下载镜像。然后使用该镜像创建一个新的容器,并且执行指定的命令。

每个容器有它的容器 ID 和名称,创建容器时会自动分配一个随机的名称,也可以在创建时指定名称。

选项:

  • –name <container_name>:为容器指定名称
  • -i:开启容器的 STDIN
  • -t:为创建的容器分配一个伪 tty 终端,一遍提供交互式 shell
  • -d:让容器在后台运行,启动守护式的容器
  • -p <port>:控制容器公开给宿主机的网络端口,-p p2 将容器内的 p2 端口随机映射到宿主机 32768~61000 的某个端口,-p p1:p2 将容器内的 p2 端口映射到宿主机的 p1 端口
  • -P:对外公开所有 EXPOSE 指令的端口,绑定到宿主机随机端口上
  • –rm:容器退出后将其删除
  • -v file:file:映射本地文件或目录与容器中的文件或目录
  • –log-driver="":控制容器的日志驱动,默认为 json-file,即开启 docker logs 命令,syslog 将禁用 docker logs 命令并将容器日志输出重定向到 Syslog,none 将禁用所有容器的日志
  • –restart=:设置容器错误停止时的重启条件,always 表示无论容器退出代码是什么都会自动重启,on-failure:5 表示退出代码非 0 才重启,可以设置最多重试 5 次
  • –entrypoint:覆盖 ENTRYPOINT 指令
  • –env:设置环境变量
  • -w <dir>:覆盖 WORKDIR 目录
docker run <image>[:<tag>] <cmd>

# 创建容器并进入交互式终端,exit 命令将会暂停交互式容器
docker run -i -t ubuntu /bin/bash

# 创建守护式容器,进行端口映射,后台执行
docker run -p 8080:80 -d nginx
# 宿主机访问端口: curl 127.0.0.1:8000

只创建容器而不启动,具体选项和用法与 docker run 一样。

docker create <image>[:<tag>] <cmd>

启动容器

启动已经停止运行的容器,可以通过容器 ID 或容器名称来指定。

docker start <container>

重启容器

重启一个运行中的容器。

docker restart <container>

附着容器

附着在一个运行中的容器,进入交互式终端。

docker attach <container>

停止容器

停止运行中的容器。

# 向容器发送 SIGTERM 信号,对于守护式容器立刻停止,对于交互式容器将等待
docker stop <container>

# 向容器发送 SIGKILL 信号,立即退出
docker kill <container>

启动新进程

在容器内部额外启动新进程。

# 运行交互命令
docker exec -i -t <container> <cmd>

# 运行后台任务
docker exec -d <container> <cmd>

容器日志

获取容器的日志。

选项:

  • -f:监控日志追加内容
  • –tail <n>:显示最后 n 行
  • -t:为每条日志加上时间戳
docker logs <container>

# 监控日志追加内容
docker logs -f <container>

# 获取日志最后 10 行内容
docker logs --tail 10 <container>

# 监控日志追加内容,不需要读取整个日志文件
docker logs --tail 0 -f <container>

容器进程

查看容器内部运行的进程。

docker top <container>

容器端口

查看容器端口映射情况,容器内的哪个端口映射到了宿主机的哪个端口。

docker port <container> [<port>]

容器统计信息

查看一个或多个容器实时更新的的统计信息,包括 CPU、内存、硬盘 IO、网络 IO。

docker stats <containers>

获取更多容器信息

获取更多容器信息,包括名称、命令、网络配置等信息。

docker inspect <containers>

删除容器

将一个容器删除。

docker rm <containers>

导出容器

将本地容器导出为本地文件。

docker export -o <file> <container>
docker export <container> > <file>

导入容器

从容器导出的文件导入为镜像。

docker import <file>|<url> <image>
cat <file> | docker import - <image>

3. 参考