Appearance
概述
Docker 是一款针对程序开发人员和系统管理员来开发、部署、运行应用的一款虚拟化平台。Docker 可以让你像使用集装箱一样快速的组合成应用,并且可以像运输标准集装箱一样,尽可能的屏蔽代码层面的差异。使用 Docker,可以让应用的部署、测试和分发都变得前所未有的高效和轻松!
基本概念
Docker 包括三个基本概念:
- 镜像(Image)
- 容器(Container)
- 仓库(Repository)
理解了这三个概念,就理解了 Docker 的整个生命周期。
镜像
我们都知道,操作系统分为 内核 和 用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 不包含 任何动态数据,其内容在构建之后也不会被改变。
容器
镜像(Image)和容器(Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
仓库
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,Docker Registry 就是这样的服务。
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
安装
Docker 分为 stable、test 和 nightly 三个更新频道。
官方网站上有各种环境下的安装指南,这里主要介绍 Docker 在 CentOS 和 macOS 上的安装。
CentOS
系统要求:Docker 支持 64 位版本 CentOS 7/8,并且要求内核版本不低于 3.10。 CentOS 7 满足最低内核的要求,但由于内核版本比较低,部分功能(如 overlay2 存储层驱动)无法使用,并且部分功能可能不太稳定。
卸载旧版本:
旧版本的 Docker 称为 docker 或者 docker-engine,使用以下命令卸载旧版本:
bash
sudo yum remove docker \
docker-client \
docker-client-latest \
docker-common \
docker-latest \
docker-latest-logrotate \
docker-logrotate \
docker-selinux \
docker-engine-selinux \
docker-engine使用 yum 安装
执行以下命令安装依赖包:
bash
sudo yum install -y yum-utils鉴于国内网络问题,建议使用国内源。执行下面的命令添加 yum 软件源:
bash
sudo yum-config-manager \
--add-repo \
https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
$ sudo sed -i 's/download.docker.com/mirrors.aliyun.com\/docker-ce/g' /etc/yum.repos.d/docker-ce.repo
# 官方源
# sudo yum-config-manager \
# --add-repo \
# https://download.docker.com/linux/centos/docker-ce.repo如果需要测试版本的 Docker 请执行以下命令:
bash
sudo yum-config-manager --enable docker-ce-test安装 Docker:
更新 yum 软件源缓存,并安装 docker-ce:
bash
sudo yum install docker-ce docker-ce-cli containerd.ioCentOS8 额外设置
由于 CentOS8 防火墙使用了 nftables,但 Docker 尚未支持 nftables, 我们可以使用如下设置使用 iptables:
更改 /etc/firewalld/firewalld.conf:
bash
# FirewallBackend=nftables
FirewallBackend=iptables或者执行如下命令:
bash
firewall-cmd --permanent --zone=trusted --add-interface=docker0
firewall-cmd --reload启动 Docker
bash
sudo systemctl enable docker
sudo systemctl start dockerMacOS
系统要求:Docker Desktop for Mac 要求系统最低为 macOS Mojave 10.14。
使用 Homebrew 安装
Homebrew 的 Cask 已经支持 Docker Desktop for Mac,因此可以很方便的使用 Homebrew Cask 来进行安装:
bash
brew install --cask docker手动下载安装
如果需要手动下载,请点击以下 链接 下载 Docker Desktop for Mac。
如果你的电脑搭载的是 M1 芯片(arm64 架构),请点击以下 链接 下载 Docker Desktop for Mac。可以在官方文档查阅已知的问题。
如同 macOS 其它软件一样,安装也非常简单,双击下载的 .dmg 文件,然后将那只叫 Moby 的鲸鱼图标拖拽到 Application 文件夹即可(其间需要输入用户密码)。
镜像加速
国内从 Docker Hub 拉取镜像有时会遇到困难,此时可以配置镜像加速器。国内很多云服务商都提供了国内加速器服务,例如:
- 阿里云加速器(点击管理控制台 -> 登录账号(淘宝账号) -> 右侧镜像工具 -> 镜像加速器 -> 复制加速器地址)
- 网易云加速器 https://hub-mirror.c.163.com
- 百度云加速器 https://mirror.baidubce.com
由于镜像服务可能出现宕机,建议同时配置多个镜像。各个镜像站测试结果可以到 docker-practice/docker-registry-cn-mirror-test 查看。
Ubuntu 16.04+、Debian 8+、CentOS 7+
目前主流 Linux 发行版均已使用 systemd 进行服务管理的服务器,可以根据以下步骤配置镜像:
- 执行以下命令,查看是否在
docker.service文件中配置过镜像地址bashsystemctl cat docker | grep '\-\-registry\-mirror' - 如果步骤 1 有输出,那么请执行
systemctl cat docker查看ExecStart=出现的位置,修改对应的文件内容去掉--registry-mirror参数及其值,然后执行步骤 3 - 在
/etc/docker/daemon.json中写入如下内容(如果文件不存在请新建该文件):bash注意:一定要保证该文件符合 json 规范,否则 Docker 将不能启动。{ "registry-mirrors": [ "https://hub-mirror.c.163.com", "https://mirror.baidubce.com" ] } - 重新启动服务bash
sudo systemctl daemon-reload sudo systemctl restart docker
macOS
对于使用 macOS 的用户,在任务栏点击 Docker Desktop 应用图标 -> Perferences,在左侧导航菜单选择 Docker Engine,在右侧像下边一样编辑 json 文件。修改完成之后,点击 Apply & Restart 按钮,Docker 就会重启并应用配置的镜像地址了:
json
{
"registry-mirrors": [
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com"
]
}使用镜像
获取镜像
从 Docker 镜像仓库获取镜像的命令是 docker pull:
bash
$ docker pull --help
Usage: docker pull [OPTIONS] NAME[:TAG|@DIGEST]
Pull an image or a repository from a registry
Options:
-a, --all-tags Download all tagged images in the repository
--disable-content-trust Skip image verification (default true)
--platform string Set platform if server is multi-platform capable
-q, --quiet Suppress verbose output命令的格式为 docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签],可以通过 docker pull --help 命令查看,这里我们说一下镜像名称的格式:
- 镜像仓库地址:地址的格式一般是
<域名/IP>[:端口号]。默认地址是 Docker Hub(<docker.io>)。 - 仓库名:仓库名是两段式名称,即
<用户名>/<软件名>。对于 Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。
比如:
bash
$ docker pull ubuntu:18.04
18.04: Pulling from library/ubuntu
92dc2a97ff99: Pull complete
be13a9d27eb8: Pull complete
c8299583700a: Pull complete
Digest: sha256:4bc3ae6596938cb0d9e5ac51a1152ec9dcac2a1c50829c74abd9c4361e321b26
Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04完成后,即可随时使用该镜像了,例如创建一个容器,让其中运行 bash 应用:
bash
$ sudo docker run -t -i ubuntu:18.04 /bin/bash
root@fe7fc4bd8fc9:/#列出镜像
要想列出已经下载下来的镜像,可以使用 docker image ls 命令:
bash
$ docker image ls --help
Usage: docker image ls [OPTIONS] [REPOSITORY[:TAG]]
List images
Aliases:
ls, list
Options:
-a, --all Show all images (default hides intermediate images)
--digests Show digests
-f, --filter filter Filter output based on conditions provided
--format string Pretty-print images using a Go template
--no-trunc Don't truncate output
-q, --quiet Only show image IDsbash
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
redis latest 5f515359c7f8 5 days ago 183 MB
nginx latest 05a60462f8ba 5 days ago 181 MB
mongo 3.2 fe9198c04d62 5 days ago 342 MB
<none> <none> 00285df0df87 5 days ago 342 MB
ubuntu 18.04 329ed837d508 3 days ago 63.3MB
ubuntu bionic 329ed837d508 3 days ago 63.3MB列表包含了仓库名、标签、镜像 ID、创建时间 以及所占用的空间。
镜像体积
可以通过 docker system df 命令来便捷的查看镜像、容器、数据卷所占用的空间:
bash
$ docker system df
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 0 1.992GB 1.992GB (100%)
Containers 1 0 62.82MB 62.82MB (100%)
Local Volumes 9 0 652.2MB 652.2MB (100%)
Build Cache 0B 0B注意:
- 这里标识的所占用空间和在 Docker Hub 上看到的镜像大小不同,因为 Docker Hub 中显示的体积是压缩后的体积。
- 镜像体积总和并非是所有镜像实际硬盘消耗,由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。
虚悬镜像
上面的镜像列表中,还可以看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 <none>:
bash
<none> <none> 00285df0df87 5 days ago 342 MB这个镜像原本是有镜像名和标签的,原来为 mongo:3.2,随着官方镜像维护,发布了新版本后,重新 docker pull mongo:3.2 时,mongo:3.2 这个镜像名被转移到了新下载的镜像身上,而旧的镜像上的这个名称则被取消,从而成为了 <none>。除了 docker pull 可能导致这种情况,docker build 也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而出现仓库名、标签均为 <none> 的镜像。这类无标签镜像也被称为虚悬镜像(dangling image),可以用下面的命令专门显示这类镜像:
bash
$ docker image ls -f dangling=true
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 00285df0df87 5 days ago 342 MB一般来说,虚悬镜像已经失去了存在的价值,是可以随意删除的,可以用下面的命令删除:
bash
$ docker image prune其他示例
bash
# 显示包括中间层镜像在内的所有镜像
docker image ls -a
# 根据仓库名列出镜像
docker image ls ubuntu
# 列出指定仓库名和标签的某个镜像
docker image ls ubuntu:18.04
# 利用过滤器参数 -f / --filter,列出在 mongo:3.2 之后建立的镜像
docker image ls -f since=mongo:3.2
# 利用过滤器参数 -f / --filter,列出在 mongo:3.2 之前建立的镜像
docker image ls -f before=mongo:3.2
# 如果镜像构建时,定义了 LABEL,还可以通过 LABEL 来过滤
docker image ls -f label=com.example.version=0.1
# 列出所有镜像,并且只展示镜像 ID
docker image ls -q
# 列出所有镜像,并且只包含镜像 ID 和仓库名
docker image ls --format "{{.ID}}: {{.Repository}}"
# 列出所有镜像,以表格等距显示,并且有标题行
docker image ls --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"删除镜像
如果要删除本地的镜像,可以使用 docker image rm 命令:
bash
$ docker image rm --help
Usage: docker image rm [OPTIONS] IMAGE [IMAGE...]
Remove one or more images
Aliases:
rm, rmi, remove
Options:
-f, --force Force removal of the image
--no-prune Do not delete untagged parents注意:本地的镜像删除操作不会同步到远程仓库,远程仓库中的镜像删除需要调用相关接口实现。
其格式为:$ docker image rm [选项] <镜像1> [<镜像2> ...]。其中,<镜像> 可以是镜像短 ID、镜像长 ID、镜像名或者镜像摘要。
比如我们有这么一些镜像:
bash
$ 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我们可以用以下操作删除镜像:
bash
# 根据「短ID」删除镜像(一般取前3个字符以上,只要足够区分于别的镜像就可以了)
docker image rm 501
# 根据「镜像名」(<仓库名>:<标签>)删除镜像
docker image rm centos可以使用 docker image ls -q 来配合使用 docker image rm,这样可以成批的删除希望删除的镜像。比如:
bash
# 删除所有仓库名为 redis 的镜像
docker image rm $(docker image ls -q redis)
# 删除所有在 mongo:3.2 之前的镜像
docker image rm $(docker image ls -q -f before=mongo:3.2)定制镜像
下面以定制一个 Web 服务器为例子,来讲解镜像是如何构建的:
bash
docker run --name webserver -d -p 8088:80 nginx这条命令会用 nginx 镜像启动一个容器,命名为 webserver,并且将本地 8088 端口映射到了容器的 80 端口,这样我们可以用通过 http://localhost:8088 访问这个 nginx 服务器。

现在,我们想要修改访问的页面内容,可以通过 docker commit 或者 Dockerfile 来定制镜像。
利用 docker commit 定制镜像
首先,使用 docker exec 命令进入容器,修改其内容:
bash
$ docker exec -it webserver bash
root@3729b97e8226:/# echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
root@3729b97e8226:/# exit
exit以上,我们以交互终端的方式进入 webserver,并执行了 bash 命令,用 <h1>Hello, Docker!</h1> 覆盖了 /usr/share/nginx/html/index.html 的内容,现在我们再刷新浏览器的话,会发现内容被改变了:

我们修改了容器的文件,也就是改动了容器的存储层。我们可以通过 docker diff 命令看到具体的改动:
bash
$ docker diff webserver
C /root
A /root/.bash_history
C /run
C /usr
C /usr/share
C /usr/share/nginx
C /usr/share/nginx/html
C /usr/share/nginx/html/index.html
C /var
C /var/cache
C /var/cache/nginx
A /var/cache/nginx/client_temp
A /var/cache/nginx/fastcgi_temp
A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp定制好了变化,我们希望能将其保存下来形成镜像。
要知道,当我们运行一个容器的时候(如果不使用卷的话),我们做的任何文件修改都会被记录于容器存储层里。而 Docker 提供了一个 docker commit 命令,可以将容器的存储层保存下来成为镜像。换句话说,就是在原有镜像的基础上,再叠加上容器的存储层,并构成新的镜像。以后我们运行这个新镜像的时候,就会拥有原有容器最后的文件变化。
docker commit 的语法格式为:
bash
$ docker commit --help
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Create a new image from a container's changes
Options:
-a, --author string Author (e.g., "John Hannibal Smith <hannibal@a-team.com>")
-c, --change list Apply Dockerfile instruction to the created image
-m, --message string Commit message
-p, --pause Pause container during commit (default true)我们可以用下面的命令将容器保存为镜像:
bash
$ docker commit \
--author "Jimco <zqianduan@gmail.com>" \
--message "修改了默认网页" \
webserver \
nginx:v2
sha256:07e33465974800ce65751acc279adc6ed2dc5ed4e0838f8b86f0c87aa1795214其中 --author 是指定修改的作者,而 --message 则是记录本次修改的内容。这点和 git 版本控制相似,不过这里这些信息可以省略留空。然后,我们可以在 docker image ls 中看到这个新定制的镜像:
bash
$ docker image ls nginx
REPOSITORY TAG IMAGE ID CREATED SIZE
nginx v2 07e334659748 9 seconds ago 181.5 MB
nginx 1.11 05a60462f8ba 12 days ago 181.5 MB
nginx latest e43d811ce2f4 4 weeks ago 181.5 MB我们还可以用 docker history 具体查看镜像内的历史记录,如果比较 nginx:latest 的历史记录,我们会发现新增了我们刚刚提交的这一层。
bash
$ docker history nginx:v2
IMAGE CREATED CREATED BY SIZE COMMENT
07e334659748 54 seconds ago nginx -g daemon off; 95 B 修改了默认网页
e43d811ce2f4 4 weeks ago /bin/sh -c #(nop) CMD ["nginx" "-g" "daemon 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) EXPOSE 443/tcp 80/tcp 0 B
<missing> 4 weeks ago /bin/sh -c ln -sf /dev/stdout /var/log/nginx/ 22 B
<missing> 4 weeks ago /bin/sh -c apt-key adv --keyserver hkp://pgp. 58.46 MB
<missing> 4 weeks ago /bin/sh -c #(nop) ENV NGINX_VERSION=1.11.5-1 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) MAINTAINER NGINX Docker Ma 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
<missing> 4 weeks ago /bin/sh -c #(nop) ADD file:23aa4f893e3288698c 123 MB新的镜像定制好后,我们可以来运行这个镜像:
bash
docker run --name web2 -d -p 8089:80 nginx:v2这里我们命名为新的服务为 web2,并且映射到 8089 端口。访问 http://localhost:8089 看到结果,其内容应该和之前修改后的 webserver 一样。
WARNING
慎用 docker commit:
使用 docker commit 命令虽然可以比较直观的帮助理解镜像分层存储的概念,但是实际环境中并不会这样使用。因为在容器中一个简单的命令,可能会有大量的无关内容被改动/添加,将会导致镜像极为臃肿。
此外,使用 docker commit 意味着所有对镜像的操作都是黑箱操作,生成的镜像也被称为黑箱镜像,换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生成的镜像,别人根本无从得知。而且,即使是这个制作镜像的人,过一段时间后也无法记清具体的操作。这种黑箱镜像的维护工作是非常痛苦的。
利用 Dockerfile 定制镜像
Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
还以之前定制 nginx 镜像为例,这次我们使用 Dockerfile 来定制。在一个空白目录中,建立一个文本文件,并命名为 Dockerfile:
bash
$ mkdir mynginx
$ cd mynginx
$ touch Dockerfile其内容为:
bash
FROM nginx
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html这个 Dockerfile 很简单,一共就两行。涉及到了两条指令,FROM 和 RUN:
FROM: 指定基础镜像RUN: 执行命令。其格式有两种:- shell 格式:
RUN <命令>,就像直接在命令行中输入的命令一样 - exec 格式:
RUN ["可执行文件", "参数1", "参数2"],这更像是函数调用中的格式
- shell 格式:
每一个 RUN 的行为,就和手动建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。即多个 RUN 命令,会创建多层镜像,这是完全没有意义的,而且很多运行时不需要的东西,都被装进了镜像里,比如编译环境、更新的软件包等等。结果就是产生非常臃肿、非常多层的镜像,不仅仅增加了构建部署的时间,也很容易出错。
在执行多个命令时,我们的 Dockerfile 可以这样写:
bash
FROM debian:stretch
RUN set -x; 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有了 Dockerfile,接下里就可以使用 docker build 命令进行镜像构建:
bash
docker build -t nginx:v3 .docker build 命令的格式为:
bash
$ docker build --help
Usage: docker build [OPTIONS] PATH | URL | -
Build an image from a Dockerfile
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
--build-arg list Set build-time variables
--cache-from strings Images to consider as cache sources
--disable-content-trust Skip image verification (default true)
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile')
--iidfile string Write the image ID to the file
--isolation string Container isolation technology
--label list Set metadata for an image
--network string Set the networking mode for the RUN instructions during build (default "default")
--no-cache Do not use cache when building the image
-o, --output stringArray Output destination (format: type=local,dest=path)
--platform string Set platform if server is multi-platform capable
--progress string Set type of progress output (auto, plain, tty). Use plain to show container output (default "auto")
--pull Always attempt to pull a newer version of the image
-q, --quiet Suppress the build output and print image ID on success
--secret stringArray Secret file to expose to the build (only if BuildKit enabled): id=mysecret,src=/local/secret
--ssh stringArray SSH agent socket or keys to expose to the build (only if BuildKit enabled) (format:
default|<id>[=<socket>|<key>[,<key>]])
-t, --tag list Name and optionally a tag in the 'name:tag' format
--target string Set the target build stage to build.以上,我们指定了最终镜像的名称 -t nginx:v3,构建成功后,我们可以像之前运行 nginx:v2 那样来运行这个镜像,其结果会和 nginx:v2 一样。
镜像构建上下文:
在上面构建镜像的命令中,会看到 docker build 命令最后有一个 .。. 表示当前目录,这是在指定上下文路径。
当我们进行镜像构建的时候,并非所有定制都会通过 RUN 指令完成,经常会需要将一些本地文件复制进镜像,比如通过 COPY 指令、ADD 指令等。用户指定构建镜像上下文的路径,docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。
其他 docker build 用法示例:
bash
# 直接从 Git repo 中构建镜像
docker build -t hello-world https://github.com/docker-library/hello-world.git#master:amd64/hello-world
# 用给定的 tar 压缩包构建
docker build http://server/context.tar.gz
# 从标准输入中读取 Dockerfile 进行构建
docker build - < Dockerfile
# or
cat Dockerfile | docker build -
# 从标准输入中读取上下文压缩包进行构建
docker build - < context.tar.gz操作容器
启动
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(exited)的容器重新启动。因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。
新建并启动
所需要的命令主要为 docker run。比如,下面的命令输出一个「Hello World」,之后终止容器:
bash
$ docker run ubuntu:18.04 /bin/echo 'Hello world'
Hello world下面的命令则启动一个 bash 终端,允许用户进行交互:
bash
$ docker run -t -i ubuntu:18.04 /bin/bash
root@af8bae53bdd3:/#其中,-t 选项让 Docker 分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上,-i 则让容器的标准输入保持打开。 在交互模式下,用户可以通过所创建的终端来输入命令,例如:
bash
root@af8bae53bdd3:/# pwd
/
root@af8bae53bdd3:/# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var当利用 docker run 来创建容器时,Docker 在后台运行的标准操作包括:
- 检查本地是否存在指定的镜像,不存在就从 registry 下载
- 利用镜像创建并启动一个容器
- 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层
- 从宿主主机配置的网桥接口中桥接一个虚拟接口到容器中去
- 从地址池配置一个 ip 地址给容器
- 执行用户指定的应用程序
- 执行完毕后容器被终止
启动已终止容器
可以利用 docker container start 命令,直接将一个已经终止(exited)的容器启动运行。
容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 ps 或 top 来查看进程信息:
bash
root@ba267838cc1b:/# ps
PID TTY TIME CMD
1 ? 00:00:00 bash
11 ? 00:00:00 ps可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。
守护态运行
更多的时候,需要让 Docker 在后台运行而不是直接把执行命令的结果输出在当前宿主机下。此时,可以通过添加 -d 参数来实现。比如,如果不使用 -d 参数运行容器:
bash
$ docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
hello world
hello world
hello world
hello world容器会把输出的结果 (STDOUT) 打印到宿主机上面。如果使用了 -d 参数运行容器:
bash
$ docker run -d ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
77b2dc01fe0f3f1265df143181e7b9af5e05279a884f4776ee75350ea9d8017a此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。
注:容器是否会长久运行,是和
docker run指定的命令有关,和-d参数无关。
使用 -d 参数启动后会返回一个唯一的 id,也可以通过 docker container ls 命令来查看容器信息:
bash
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77b2dc01fe0f ubuntu:18.04 /bin/sh -c 'while tr 2 minutes ago Up 1 minute agitated_wright要获取容器的输出信息,可以通过 docker container logs 命令:
bash
$ docker container logs [container ID or NAMES]
hello world
hello world
hello world
...终止
使用 docker container stop 来终止一个运行中的容器。此外,当 Docker 容器中指定的应用终结时,容器也自动终止。
比如:
bash
docker run ubuntu:18.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"未使用守护态运行,则用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。
处于终止状态的容器,可以通过 docker container start 命令来重新启动(若该容器处于运行态,则会先将它终止,然后重启)。
进入
在使用 -d 参数时,容器启动后会进入后台。此时,可使用 docker attach 命令或 docker exec 命令进入容器。
docker attach
bash
$ docker attach --help
Usage: docker attach [OPTIONS] CONTAINER
Attach local standard input, output, and error streams to a running container
Options:
--detach-keys string Override the key sequence for detaching a container
--no-stdin Do not attach STDIN
--sig-proxy Proxy all received signals to the process (default true)使用示例:
bash
$ docker run -dit ubuntu
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
243c32535da7 ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds nostalgic_hypatia
$ docker attach 243c
root@243c32535da7:/#注意:如果从这个 stdin 中 exit,会导致容器的停止。
docker exec
bash
$ docker exec --help
Usage: docker exec [OPTIONS] CONTAINER COMMAND [ARG...]
Run a command in a running container
Options:
-d, --detach Detached mode: run command in the background
--detach-keys string Override the key sequence for detaching a container
-e, --env list Set environment variables
--env-file list Read in a file of environment variables
-i, --interactive Keep STDIN open even if not attached
--privileged Give extended privileges to the command
-t, --tty Allocate a pseudo-TTY
-u, --user string Username or UID (format: <name|uid>[:<group|gid>])
-w, --workdir string Working directory inside the container这里主要说明 -i、-t 参数:
- 只用
-i参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回 - 当
-i、-t参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符
bash
$ docker run -dit ubuntu
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
$ docker container ls
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
69d137adef7a ubuntu:latest "/bin/bash" 18 seconds ago Up 17 seconds zealous_swirles
$ docker exec -i 69d1 bash
ls
bin
boot
dev
...
$ docker exec -it 69d1 bash
root@69d137adef7a:/#如果从这个 stdin 中 exit,不会导致容器的停止。因此,通常建议使用 docker exec 命令来进入容器。
删除
可以使用 docker container rm 来删除一个处于终止状态的容器。命令格式如下:
bash
$ docker container rm --help
Usage: docker container rm [OPTIONS] CONTAINER [CONTAINER...]
Remove one or more containers
Options:
-f, --force Force the removal of a running container (uses SIGKILL)
-l, --link Remove the specified link
-v, --volumes Remove anonymous volumes associated with the container举个例子:
bash
$ docker container rm trusting_newton
trusting_newton如果要删除一个运行中的容器,可以添加 -f 参数。Docker 会发送 SIGKILL 信号给容器。
用 docker container ls -a 命令可以查看所有已经创建的包括终止状态的容器,如果数量太多要一个个删除可能会很麻烦,用下面的命令可以清理掉所有处于终止状态的容器:
bash
# 清理所有处理终止状态的容器
docker container prune导入和导出
如果要导出本地某个容器,可以使用 docker export 命令:
bash
$ docker container ls -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7691a814370e ubuntu:18.04 "/bin/bash" 36 hours ago Exited (0) 21 hours ago test
# 导出容器快照到本地文件
$ docker export 7691a814370e > ubuntu.tar可以使用 docker import 从容器快照文件中再导入为镜像,例如:
bash
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
$ docker image ls
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
test/ubuntu v1.0 9d37a6082e97 About a minute ago 171.3 MB此外,也可以通过指定 URL 或者某个目录来导入,例如:
bash
$ docker import http://example.com/exampleimage.tgz example/imagerepo注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。
访问仓库
Docker Hub
Docker Hub 是官方维护的公共仓库,大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。
此处我们不再介绍 Docker Hub 的注册等基本操作,默认已拥有 Docker Hub 账号:
bash
# 登录,交互式的输入用户名及密码
docker login
# 登出
docker logout
# 查找官方镜像
docker search centos
# 拉取镜像
docker pull centos
# 推送镜像,username 替换为 Docker 账号用户名
docker push username/ubuntu:18.04私有仓库
数据管理
数据卷是一个可供一个或多个容器使用的特殊目录,它绕过 UFS,可以提供很多有用的特性:
- 数据卷可以在容器之间共享和重用
- 对数据卷的修改会立马生效
- 对数据卷的更新,不会影响镜像
- 数据卷默认会一直存在,即使容器被删除
注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会复制到数据卷中(仅数据卷为空时会复制)。
操作数据卷的命令为 docker volume ,格式为:
bash
$ docker volume --help
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove all unused local volumes
rm Remove one or more volumes创建数据卷:
bash
# 创建名为 my-vol 的数据卷
docker volume create my-vol
# 查看所有数据卷
docker volume ls
# 查看指定数据卷的信息
docker inspect my-vol启动一个挂载数据卷的容器:
bash
# 使用 --mount 标记来将数据卷挂载到容器里
# 以下为创建一个名为 web 的容器,并加载一个数据卷到容器的 /usr/share/nginx/html 目录
docker run -d -P \
--name web \
# -v my-vol:/usr/share/nginx/html \
--mount source=my-vol,target=/usr/share/nginx/html \
nginx:alpine删除数据卷:
bash
# 删除指定数据卷
docker volume rm my-vol
# 删除容器的同时移除数据卷
docker rm -v 80255f8143de
# 清理无主的数据卷
docker volume prune使用网络
外部访问容器
容器中可以运行一些网络应用,要让外部也可以访问这些应用,可以通过 -P 或 -p 参数来指定端口映射:
-P: 随机映射一个端口到内部容器开放的网络端口-p: 指定要映射的端口,并且,在一个指定端口上只可以绑定一个容器。支持的格式有ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort
bash
# 随机映射一个端口到内部容器开放的网络端口
docker run -d -P nginx:alpine
# 本地的 80 端口映射到容器的 80 端口
docker run -d -p 80:80 nginx:alpine
# 查看指定容器的访问记录
docker logs 84c424384521
# 映射到指定地址的指定端口
docker run -d -p 127.0.0.1:80:80 nginx:alpine
# 绑定 localhost 的任意端口到容器的 80 端口(本地主机会自动分配一个端口)
docker run -d -p 127.0.0.1::80 nginx:alpine
# 使用 udp 标记来指定 udp 端口
docker run -d -p 127.0.0.1:80:80/udp nginx:alpine
# 绑定多个端口
docker run -d \
-p 80:80 \
-p 443:443 \
nginx:alpine
# 查看指定容器的端口配置
docker port 84c424384521容器互联
如果之前有 Docker 使用经验,那么应该使用过 --link 参数来使容器互联。随着 Docker 网络的完善,我们可以将容器加入自定义的 Docker 网络来连接多个容器,而不是使用 --link 参数。
bash
# 新建名为 my-net 的 Docker 网络
# -d 参数指定 Docker 网络类型,有 bridge/overlay,overlay 网络类型用于 Swarm mode
docker network create -d bridge my-net
# 运行一个容器并连接到新建的 my-net 网络
docker run -dit --name busybox1 --network my-net busybox sh
# 再运行一个容器并加入到 my-net 网络
$ docker run -dit --name busybox2 --network my-net busybox sh以上,我们运行了 busybox1 和 busybox2 两个容器,并把他们加入了同一个 Docker 网络,下面我们可以用 ping 命令来进行验证:
bash
# 进入 busybox1 容器
docker exec -it 0d8ada2c4dc5 sh
# ping 验证
ping busybox2Docker Compose
快速入门
术语
首先介绍几个术语:
- 服务(service):一个应用容器,实际上可以运行多个相同镜像的实例。
- 项目(project):由一组关联的应用容器组成的一个完整业务单元。
可见,一个项目可以由多个服务(容器)关联而成,Compose 面向项目进行管理。使用 Compose 基本上有三个步骤:
- 使用
Dockerfile定义应用程序的环境,这样它就可以在任何地方复制 - 用
docker-compose.yml定义组成应用程序的服务,使其可以在一个独立的环境中一起运行 - 执行
docker-Compose up命令,Compose 启动并运行整个应用程序
场景
最常见的项目是 web 网站,该项目应该包含 web 应用和缓存。下面我们用 Node 来建立一个能够记录页面访问次数的 web 网站。
web 应用:
js
from flask import Flask
from redis import Redis
app = Flask(__name__)
redis = Redis(host='redis', port=6379)
@app.route('/')
def hello():
count = redis.incr('hits')
return 'Hello World! 该页面已被访问 {} 次。\n'.format(count)
if __name__ == "__main__":
app.run(host="0.0.0.0", debug=True)Dockerfile:
dockerfile
FROM python:3.6-alpine
ADD . /code
WORKDIR /code
RUN pip install redis flask
CMD ["python", "app.py"]编写 docker-compose.yml 文件:
yml
version: '3'
services:
web:
build: .
ports:
- "5000:5000"
redis:
image: "redis:alpine"运行 compose 项目:
bash
docker-compose up此时访问本地 5000 端口,每次刷新页面,计数就会加 1。
命令说明
模板文件
使用 docker-compose 主要的任务是编写 docker-compose.yml 文件。以下是一个 WordPress 的 yaml 文件模板:
yaml
version: '3' # 定义版本,不指定默认为版本 1
services: # 定义容器,就像 docker run
db: # 容器名称,也是 network 中 DNS 名称
image: mysql:5.7 # 镜像,如果自定义镜像可以不指定这个参数,而用 build
volumes: # 定义数据卷,类似 -v
- db_data:/var/lib/mysql
- .:/lxl80 # 挂载当前目录到容器中的 /lxl80 目录
restart: always # 类似 --restart。'no' 默认,不自动重启;always 总是自动重启;on-failure 当失败时自动重启;unless-stopped 除非手动停止,否者一直重启
environment: # 定义环境变量,类似 -e
MYSQL_ROOT_PASSWORD: somewordpress
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: wordpress
wordpress: # 第二个容器
labels: # 为容器添加 Docker 元数据(metadata)信息。
cn.lixl.title: "This label will appear on all containers for the web service"
depends_on: # 定义容器之间的关系。启动 wordpress 时会先启动 db
- db
image: wordpress:latest
ports: # 端口,类似 -p
- "8000:80"
restart: always
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: wordpress
volumes: # 可选,需要创建的数据卷,类似 docker volume create
db_data:
networks: # 可选,需要创建的网络,类似 docker network create