0%

Hello Docker

说起来Docker,大家都或多或少有耳闻,但是我的公司因为业务场景,长尾效应,至今为止Docker只在小部分项目轻度使用。但是这不妨碍我们主动学习,应用Docker,因此,从2018年开始,我就推动Docker在测试部应用。我们的测试平台,GitLab、JIRA、Confluence等都是通过Docker部署的。

团队内部做算法测试的同学搭建开发测试环境需要安装各种第三方库,因为工作环境是内网,无法连接互联网进行安装,痛苦不堪,使用Docker将相关环境在外网构建好传入内网使用是比较方便的方式;还有工具链建设、测试平台建设用到的各种基础设施MySQL、Nginx等快速部署,使用Docker都是最佳方案。为了方便团队同学了解Docker,团队老大让我来进行一次内部Docker入门分享。

没有Docker的从前

为了提高服务器利用效率,我们将一台高性能的服务器通过vSphere虚拟化,创建多台虚拟机,提供相对独立环境隔离的操作系统环境和计算资源,我们可以对每一台虚拟机灵活分配计算资源、存储资源等,还可以对每一台虚拟机创建多个快照,看起来很棒!是的,在传统的软件开发模式下,这种方式挺好,但是这并没有解决软件的部署效率低,交付困难,开发环境与测试环境不一致,测试环境与生产环境不一致等问题,这也是为什么我们有了持续集成,却没有做好持续交付的一大根本问题。

有了Docker的现在

各大互联网厂商都在使用Docker,通过Docker结合微服务等技术他们将DevOps这一理念落地了,真正做到了持续集成、持续部署、持续交付,快速迭代,可以实现一天多次版本上线发布!回头看有了Docker我们在日常工作中能做什么,举个最简单的例子:

以往我们想部署三套环境(一套Dev环境、一套Test环境、一套Release环境),需要三个MySQL数据库,需要怎么做呢?

  • 方案1:准备三台服务器,分别安装MySQL,干干净净,互不干扰,但是浪费资源啊;
  • 方案2:一台服务器部署三个MySQL实例,分别配置不同的MySQL数据存储路径、配置文件路径、端口,操作复杂,维护困难,想想都头疼;

用Docker以后呢,我们可以这样做:

1
docker run --name release-mysql -p 3306:3306 -d mysql:5.7
2
docker run --name dev-mysql -p 3307:3306 -d mysql:5.7
3
docker run --name test-mysql -p 3308:3306 -d mysql:5.7

这样我们就有了三个独立的MySQL数据库服务!当然这是个示例,没有做数据卷映射等配置,但那些配置相比于传统方案来说,复杂度不值一提。

Docker到底是什么

Docker 是世界领先的软件容器平台。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用 Docker 可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用 Docker 可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为 Linux 和 Windows Server 应用发布新功能。

什么是容器呢?

将软件打包成标准化单元,以用于开发、交付和部署

容器镜像是轻量的、可执行的独立软件包,包含软件运行所需的所有内容:代码、运行时环境、系统工具、系统库和设置。容器化软件适用于基于 Linux 和 Windows 的应用,在任何环境中都能够始终如一地运行。容器赋予了软件独立性,使其免受外在环境差异(例如,开发和预演环境的差异)的影响,从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。

容器的优势特点

轻量

在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核;它们能够迅速启动,只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的,并共享一些公共文件。这样就能尽量降低磁盘用量,并能更快地下载镜像。

标准

Docker 容器基于开放式标准,能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。

安全

Docker 赋予应用的隔离性不仅限于彼此隔离,还独立于底层的基础设施。Docker 默认提供最强的隔离,因此应用出现问题,也只是单个容器的问题,而不会波及到整台机器。

容器 VS 虚拟机

容器和虚拟机具有相似的资源隔离和分配优势,但功能有所不同,因为容器虚拟化的是操作系统,而不是硬件,因此容器更容易移植,效率也更高。

容器

容器是一个应用层抽象,用于将代码和依赖资源打包在一起。多个容器可以在同一台机器上运行,共享操作系统内核,但各自作为独立的进程在用户空间中运行。与虚拟机相比,容器占用的空间较少(容器镜像大小通常只有几十兆),瞬间就能完成启动。

Container@2x

虚拟机

虚拟机 (VM) 是一个物理硬件层抽象,用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源,因此占用大量空间。而且 VM 启动也十分缓慢。

VM@2x

对比传统虚拟机总结

特性 容器 虚拟机
启动 秒级 分钟级
硬盘使用 一般为 MB 一般为 GB
性能 接近原生 弱于
系统支持量 单机支持上千个容器 一般几十个

容器和虚拟机共用

将容器和虚拟机配合使用,为应用的部署和管理提供极大的灵活性。

containers-vms-together

如何使用Docker

安装Docker

Docker可以安装在Linux、Mac、Window上面,但是需要注意的是,Unix安装Docker(Linux、Mac)上对操作系统内核版本有要求(RedHat要7UX以上版本),Windows安装Docker推荐使用Win10操作系统(Win7也不是不能装,太折腾,人生苦短)。

具体安装方式Google搜一下就很多,不罗嗦了。

年轻人的第一个Docker命令

1546666360155

我们看下这条docker run hello-world命令执行后发生了什么:

  • 本地没有找到hello-world:latest这个镜像,通过pull命令从docker的仓库一个最新的镜像
  • 下载完毕后启动了hello-world容器,输出了一段介绍Docker的信息

基本概念

Docker包括三个基本概念

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

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

Docker镜像

我们都知道,操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像(Image),就相当于是一个 root 文件系统。比如官方镜像 ubuntu:18.04 就包含了完整的一套 Ubuntu 18.04 最小系统的 root 文件系统。

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

Docker 容器

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

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

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

容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。

按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 数据卷(Volume)、或者绑定宿主目录,在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。

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

Docker仓库

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

一个 Docker Registry 中可以包含多个仓库Repository);每个仓库可以包含多个标签Tag);每个标签对应一个镜像。

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

Ubuntu 镜像 为例,ubuntu 是仓库的名字,其内包含有不同的版本标签,如,16.04, 18.04。我们可以通过 ubuntu:14.04,或者 ubuntu:18.04 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 ubuntu,那将视为 ubuntu:latest

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

Docker Registry 公开服务

Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。

最常使用的 Registry 公开服务是官方的 Docker Hub,这也是默认的 Registry,并拥有大量的高质量的官方镜像。除此以外,还有 CoreOSQuay.io,CoreOS 相关的镜像存储在这里;Google 的 Google Container RegistryKubernetes 的镜像使用的就是这个服务。

由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(Registry Mirror),这些镜像服务被称为加速器。常见的有 阿里云加速器DaoCloud 加速器 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像,比直接从 Docker Hub 下载速度会提高很多。在 安装 Docker 一节中有详细的配置方法。

国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 时速云镜像仓库网易云镜像服务DaoCloud 镜像市场阿里云镜像库 等。

私有 Docker Registry

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

开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现,足以支持 docker 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。在官方的商业化版本 Docker Trusted Registry 中,提供了这些高级功能。

除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,VMWare HarborSonatype Nexus

使用 Docker 镜像

在之前的介绍中,我们知道镜像是 Docker 的三大组件之一。

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

获取镜像

之前提到过,Docker Hub 上有大量的高质量的镜像可以用,这里我们就说一下怎么获取这些镜像。

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

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

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

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

比如:

1
$ docker pull ubuntu:18.04
2
18.04: Pulling from library/ubuntu
3
bf5d46315322: Pull complete
4
9f13e0ac480c: Pull complete
5
e8988b5b3097: Pull complete
6
40af181810e7: Pull complete
7
e6f7c7e5c03e: Pull complete
8
Digest: sha256:147913621d9cdea08853f6ba9116c2e27a3ceffecf3b492983ae97c3d643fbbe
9
Status: Downloaded newer image for ubuntu:18.04

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

从下载过程中可以看到我们之前提及的分层存储的概念,镜像是由多层存储所构成。下载也是一层层的去下载,并非单一文件。下载过程中给出了每一层的 ID 的前 12 位。并且下载结束后,给出该镜像完整的 sha256 的摘要,以确保下载一致性。

在使用上面命令的时候,你可能会发现,你所看到的层 ID 以及 sha256 的摘要和这里的不一样。这是因为官方镜像是一直在维护的,有任何新的 bug,或者版本更新,都会进行修复再以原来的标签发布,这样可以确保任何使用这个标签的用户可以获得更安全、更稳定的镜像。

运行

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

1
$ docker run -it --rm \
2
    ubuntu:18.04 \
3
    bash
4
5
root@e7009c6ce357:/# cat /etc/os-release
6
NAME="Ubuntu"
7
VERSION="18.04.1 LTS (Bionic Beaver)"
8
ID=ubuntu
9
ID_LIKE=debian
10
PRETTY_NAME="Ubuntu 18.04.1 LTS"
11
VERSION_ID="18.04"
12
HOME_URL="https://www.ubuntu.com/"
13
SUPPORT_URL="https://help.ubuntu.com/"
14
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
15
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
16
VERSION_CODENAME=bionic
17
UBUNTU_CODENAME=bionic

docker run 就是运行容器的命令,具体格式我们会在 容器 一节进行详细讲解,我们这里简要的说明一下上面用到的参数。

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

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

最后我们通过 exit 退出了这个容器。

列出镜像

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

1
root@evi1-book:/home/evi1# docker images
2
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
3
evi1/pypiserver         1.2.5               be7e6669fce0        37 hours ago        88.5MB
4
hello-world             latest              fce289e99eb9        4 days ago          1.84kB
5
<none>                  <none>              00285df0df87        5 days ago          342 MB
6
nginx                   latest              02256cfb0e4b        9 days ago          109MB
7
alpine                  3.7                 9bea9e12e381        2 weeks ago         4.21MB
8
ubuntu                  18.04               f753707788c5        4 weeks ago         127 MB
9
ubuntu                  latest              f753707788c5        4 weeks ago         127 MB
10
mysql                   5.7                 ae6b78bedf88        7 weeks ago         372MB
11
pypiserver/pypiserver   v1.2.5              b26b8ee831d7        7 weeks ago         86MB
12
codekoala/pypi          latest              f6ab585f84ed        7 months ago        61.3MB

列表包含了 仓库名标签镜像 ID创建时间 以及 所占用的空间

其中仓库名、标签在之前的基础概念章节已经介绍过了。镜像 ID 则是镜像的唯一标识,一个镜像可以对应多个标签。因此,在上面的例子中,我们可以看到 ubuntu:18.04ubuntu:latest 拥有相同的 ID,因为它们对应的是同一个镜像。

镜像体积

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

另外一个需要注意的问题是,docker image ls 列表中的镜像体积总和并非是所有镜像实际硬盘消耗。由于 Docker 镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能会因为使用相同的基础镜像,从而拥有共同的层。由于 Docker 使用 Union FS,相同的层只需要保存一份即可,因此实际镜像硬盘占用空间很可能要比这个列表镜像大小的总和要小的多。

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

1
root@evi1-book:/home/evi1# docker system df
2
TYPE                TOTAL               ACTIVE              SIZE                RECLAIMABLE
3
Images              13                  2                   1.931GB             1.614GB (83%)
4
Containers          2                   1                   2B                  0B (0%)
5
Local Volumes       14                  0                   0B                  0B
6
Build Cache         0                   0                   0B                  0B

虚悬镜像

上面的镜像列表中,还可以看到一个特殊的镜像,这个镜像既没有仓库名,也没有标签,均为 <none>。:

1
<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) ,可以用下面的命令专门显示这类镜像:

1
$ docker image ls -f dangling=true
2
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
3
<none>              <none>              00285df0df87        5 days ago          342 MB

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

1
$ docker image prune

删除本地镜像

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

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

用 ID、镜像名、摘要删除镜像

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

比如我们有这么一些镜像:

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

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

比如这里,如果我们要删除 redis:alpine 镜像,可以执行:

1
$ docker image rm 501
2
Untagged: redis:alpine
3
Untagged: redis@sha256:f1ed3708f538b537eb9c2a7dd50dc90a706f7debd7e1196c9264edeea521a86d
4
Deleted: sha256:501ad78535f015d88872e13fa87a828425117e3d28075d0c117932b05bf189b7
5
Deleted: sha256:96167737e29ca8e9d74982ef2a0dda76ed7b430da55e321c071f0dbff8c2899b
6
Deleted: sha256:32770d1dcf835f192cafd6b9263b7b597a1778a403a109e2cc2ee866f74adf23
7
Deleted: sha256:127227698ad74a5846ff5153475e03439d96d4b1c7f2a449c7a826ef74a2d2fa
8
Deleted: sha256:1333ecc582459bac54e1437335c0816bc17634e131ea0cc48daa27d32c75eab3
9
Deleted: sha256:4fc455b921edf9c4aea207c51ab39b10b06540c8b4825ba57b3feed1668fa7c7

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

1
$ docker image rm centos
2
Untagged: centos:latest
3
Untagged: centos@sha256:b2f9d1c0ff5f87a4743104d099a3d561002ac500db1b9bfa02a783a46e0d366c
4
Deleted: sha256:0584b3d2cf6d235ee310cf14b54667d889887b838d3f3d3033acd70fc3c48b8a
5
Deleted: sha256:97ca462ad9eeae25941546209454496e1d66749d53dfa2ee32bf1faabd239d38

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

1
$ docker image ls --digests
2
REPOSITORY                  TAG                 DIGEST                                                                    IMAGE ID            CREATED             SIZE
3
node                        slim                sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228   6e0c4c8e3913        3 weeks ago         214 MB
4
5
$ docker image rm node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228
6
Untagged: node@sha256:b4f0e0bdeb578043c1ea6862f0d40cc4afe32a4a582f3be235a3b164422be228

导出和导入镜像

根据我们公司的工作环境,互联网隔绝,必然要从外网下载或构建好镜像传入内网使用

下面我们就以evi1/pypiserver:1.2.5这个镜像为例演示下

首先我们在外网查看下镜像信息

1
root@evi1-book:/tmp/docker_images# docker images
2
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
3
evi1/pypiserver         1.2.5               be7e6669fce0        38 hours ago        88.5MB

导出镜像

通过docker save命令来执行导出镜像

1
root@evi1-book:/tmp/docker_images# docker save --help
2
3
Usage:	docker save [OPTIONS] IMAGE [IMAGE...]
4
5
Save one or more images to a tar archive (streamed to STDOUT by default)
6
7
Options:
8
  -o, --output string   Write to a file, instead of STDOUT

针对我们这里的镜像,命令应如下书写

1
root@evi1-book:/tmp/docker_images# docker save -o evi1-pypiserver_1.2.5.tar evi1/pypiserver:1.2.5

此处注意几点:

  • save -o 后面的参数是我们要保存的文件, 我们约定使用.tar结尾,此时导出的文件是为压缩的文件

  • tar文件我们按照{用户名}-{镜像名}_{标签}.tar格式来命名,这样方便管理,统一格式

  • 镜像参数我们使用镜像名称+标签的方式,这样有一个好处,导入后的镜像自带名称和标签,无需手动命名,否则导入的镜像会显示为如下样子

    1
    REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
    2
    <none>              <none>              be7e6669fce0        38 hours ago        88.5MB

至此我们就可以将镜像传入内网了,但是这个文件导出后大小为88.5MB,我们还可以压缩下,加快传输速度

1
root@evi1-book:/tmp/docker_images# tar czvf evi1-pypiserver_1.2.5.tar.gz evi1-pypiserver_1.2.5.tar
2
evi1-pypiserver_1.2.5.tar
3
root@evi1-book:/tmp/docker_images# du -sh *
4
89M	evi1-pypiserver_1.2.5.tar
5
33M	evi1-pypiserver_1.2.5.tar.gz

看到文件压缩后大小仅为原始镜像的1/3,所以大家从外网拉取镜像时要保持压缩的好习惯

导入镜像

镜像压缩包被传入内网后,我们先将压缩包上传至Docker所在的服务器任意目录下(通常我们会建立一个目录专门存放各种镜像压缩包,也便于我们后续建立私有镜像仓库,统一管理)

第一步先解压镜像压缩包

1
root@evi1-book:/tmp/docker_images# tar zxvf pypiserver_1.2.5.tar.gz

解压后我们得到一个evi1-pypiserver_1.2.5.tar文件

接下里开始真正的导入镜像操作docker load

1
root@evi1-book:/tmp/docker_images# docker load --help
2
3
Usage:	docker load [OPTIONS]
4
5
Load an image from a tar archive or STDIN
6
7
Options:
8
  -i, --input string   Read from tar archive file, instead of STDIN
9
  -q, --quiet          Suppress the load output

针对我们这里的镜像,命令应如下书写

1
root@evi1-book:/tmp/docker_images# docker load < evi1-pypiserver_1.2.5.tar

然后我们通过docker image ls就能看到导入的镜像了

1
root@xxxx:/home/root# docker image ls evi1/pypiserver
2
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
3
evi1/pypiserver     1.2.5               be7e6669fce0        38 hours ago        88.5MB

操作 Docker 容器

容器是 Docker 又一核心概念。

简单的说,容器是独立运行的一个或一组应用,以及它们的运行态环境。对应的,虚拟机可以理解为模拟运行的一整套操作系统(提供了运行态环境和其他系统环境)和跑在上面的应用。

启动容器

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

因为 Docker 的容器实在太轻量级了,很多时候用户都是随时删除和新创建容器。

新建并启动

所需要的命令主要为 docker run

例如,下面的命令输出一个 “Hello World”,之后终止容器。

1
$ docker run ubuntu:18.04 /bin/echo 'Hello world'
2
Hello world

这跟在本地直接执行 /bin/echo 'hello world' 几乎感觉不出任何区别。

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

1
$ docker run -t -i ubuntu:18.04 /bin/bash
2
root@af8bae53bdd3:/#

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

在交互模式下,用户可以通过所创建的终端来输入命令,例如

1
root@af8bae53bdd3:/# pwd
2
/
3
root@af8bae53bdd3:/# ls
4
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var

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

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

启动已终止容器

可以利用 docker container start 命令,直接将一个已经终止的容器启动运行。

容器的核心为所执行的应用程序,所需要的资源都是应用程序运行所必需的。除此之外,并没有其它的资源。可以在伪终端中利用 pstop 来查看进程信息。

1
root@ba267838cc1b:/# ps
2
  PID TTY          TIME CMD
3
    1 ?        00:00:00 bash
4
   11 ?        00:00:00 ps

可见,容器中仅运行了指定的 bash 应用。这种特点使得 Docker 对资源的利用率极高,是货真价实的轻量级虚拟化。

后台运行

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

下面举两个例子来说明一下。

如果不使用 -d 参数运行容器。

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

容器会把输出的结果 (STDOUT) 打印到宿主机上面

如果使用了 -d 参数运行容器。

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

此时容器会在后台运行并不会把输出的结果 (STDOUT) 打印到宿主机上面(输出结果可以用 docker logs 查看)。

注: 容器是否会长久运行,是和 docker run 指定的命令有关,和 -d 参数无关。

使用 -d 参数启动后会返回一个唯一的 id,也可以通过 docker container ls 命令来查看容器信息。

1
$ docker container ls
2
CONTAINER ID  IMAGE         COMMAND               CREATED        STATUS       PORTS NAMES
3
77b2dc01fe0f  ubuntu:18.04  /bin/sh -c 'while tr  2 minutes ago  Up 1 minute        agitated_wright

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

1
$ docker container logs [container ID or NAMES]
2
hello world
3
hello world
4
hello world
5
. . .

终止容器

可以使用 docker container stop 来终止一个运行中的容器。

此外,当 Docker 容器中指定的应用终结时,容器也自动终止。

例如对于上一章节中只启动了一个终端的容器,用户通过 exit 命令或 Ctrl+d 来退出终端时,所创建的容器立刻终止。

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

1
docker container ls -a
2
CONTAINER ID        IMAGE                    COMMAND                CREATED             STATUS                          PORTS               NAMES
3
ba267838cc1b        ubuntu:18.04             "/bin/bash"            30 minutes ago      Exited (0) About a minute ago                       trusting_newton
4
98e5efa7d997        training/webapp:latest   "python app.py"        About an hour ago   Exited (0) 34 minutes ago                           backstabbing_pike

处于终止状态的容器,可以通过 docker container start 命令来重新启动。

此外,docker container restart 命令会将一个运行态的容器终止,然后再重新启动它。

进入容器

在使用 -d 参数时,容器启动后会进入后台。

某些时候需要进入容器进行操作,包括使用 docker attach 命令或 docker exec 命令,推荐大家使用 docker exec 命令,原因会在下面说明。

attach 命令

docker attach 是 Docker 自带的命令。下面示例如何使用该命令。

1
$ docker run -dit ubuntu
2
243c32535da7d142fb0e6df616a3c3ada0b8ab417937c853a9e1c251f499f550
3
4
$ docker container ls
5
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
6
243c32535da7        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           nostalgic_hypatia
7
8
$ docker attach 243c
9
root@243c32535da7:/#

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

exec 命令

-i -t 参数

docker exec 后边可以跟多个参数,这里主要说明 -i -t 参数。

只用 -i 参数时,由于没有分配伪终端,界面没有我们熟悉的 Linux 命令提示符,但命令执行结果仍然可以返回。

-i -t 参数一起使用时,则可以看到我们熟悉的 Linux 命令提示符。

1
$ docker run -dit ubuntu
2
69d137adef7a8a689cbcb059e94da5489d3cddd240ff675c640c8d96e84fe1f6
3
4
$ docker container ls
5
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
6
69d137adef7a        ubuntu:latest       "/bin/bash"         18 seconds ago      Up 17 seconds                           zealous_swirles
7
8
$ docker exec -i 69d1 bash
9
ls
10
bin
11
boot
12
dev
13
...
14
15
$ docker exec -it 69d1 bash
16
root@69d137adef7a:/#

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

更多参数说明请使用 docker exec --help 查看。

导出和导入容器

导出容器

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

1
$ docker container ls -a
2
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                    PORTS               NAMES
3
7691a814370e        ubuntu:18.04        "/bin/bash"         36 hours ago        Exited (0) 21 hours ago                       test
4
$ docker export 7691a814370e > ubuntu.tar

这样将导出容器快照到本地文件。

导入容器快照

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

1
$ cat ubuntu.tar | docker import - test/ubuntu:v1.0
2
$ docker image ls
3
REPOSITORY          TAG                 IMAGE ID            CREATED              VIRTUAL SIZE
4
test/ubuntu         v1.0                9d37a6082e97        About a minute ago   171.3 MB

此外,也可以通过指定 URL 或者某个目录来导入,例如

1
$ docker import http://example.com/exampleimage.tgz example/imagerepo

注:用户既可以使用 docker load 来导入镜像存储文件到本地镜像库,也可以使用 docker import 来导入一个容器快照到本地镜像库。这两者的区别在于容器快照文件将丢弃所有的历史记录和元数据信息(即仅保存容器当时的快照状态),而镜像存储文件将保存完整记录,体积也要大。此外,从容器快照文件导入时可以重新指定标签等元数据信息。

删除容器

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

1
$ docker container rm  trusting_newton
2
trusting_newton

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

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

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

1
$ docker container prune

访问仓库

仓库(Repository)是集中存放镜像的地方。

Docker Hub

目前 Docker 官方维护了一个公共仓库 Docker Hub,其中已经包括了数量超过 15,000 的镜像。大部分需求都可以通过在 Docker Hub 中直接下载镜像来实现。

注册

你可以在 https://hub.docker.com 免费注册一个 Docker 账号。

登录

可以通过执行 docker login 命令交互式的输入用户名及密码来完成在命令行界面登录 Docker Hub。

你可以通过 docker logout 退出登录。

拉取镜像

你可以通过 docker search 命令来查找官方仓库中的镜像,并利用 docker pull 命令来将它下载到本地。

例如以 centos 为关键词进行搜索:

1
$ docker search centos
2
NAME                                            DESCRIPTION                                     STARS     OFFICIAL   AUTOMATED
3
centos                                          The official build of CentOS.                   465       [OK]
4
tianon/centos                                   CentOS 5 and 6, created using rinse instea...   28
5
blalor/centos                                   Bare-bones base CentOS 6.5 image                6                    [OK]
6
saltstack/centos-6-minimal                                                                      6                    [OK]
7
tutum/centos-6.4                                DEPRECATED. Use tutum/centos:6.4 instead. ...   5                    [OK]

可以看到返回了很多包含关键字的镜像,其中包括镜像名字、描述、收藏数(表示该镜像的受关注程度)、是否官方创建、是否自动创建。

官方的镜像说明是官方项目组创建和维护的,automated 资源允许用户验证镜像的来源和内容。

根据是否是官方提供,可将镜像资源分为两类。

一种是类似 centos 这样的镜像,被称为基础镜像或根镜像。这些基础镜像由 Docker 公司创建、验证、支持、提供。这样的镜像往往使用单个单词作为名字。

还有一种类型,比如 tianon/centos 镜像,它是由 Docker 的用户创建并维护的,往往带有用户名称前缀。可以通过前缀 username/ 来指定使用某个用户提供的镜像,比如 tianon 用户。

另外,在查找的时候通过 --filter=stars=N 参数可以指定仅显示收藏数量为 N 以上的镜像。

下载官方 centos 镜像到本地。

1
$ docker pull centos
2
Pulling repository centos
3
0b443ba03958: Download complete
4
539c0211cd76: Download complete
5
511136ea3c5a: Download complete
6
7064731afe90: Download complete

推送镜像

用户也可以在登录后通过 docker push 命令来将自己的镜像推送到 Docker Hub。

以下命令中的 username 请替换为你的 Docker 账号用户名。

1
$ docker tag ubuntu:18.04 username/ubuntu:18.04
2
3
$ docker image ls
4
5
REPOSITORY                                               TAG                    IMAGE ID            CREATED             SIZE
6
ubuntu                                                   18.04                  275d79972a86        6 days ago          94.6MB
7
username/ubuntu                                          18.04                  275d79972a86        6 days ago          94.6MB
8
9
$ docker push username/ubuntu:18.04
10
11
$ docker search username
12
13
NAME                      DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED
14
username/ubuntu

Docker 数据管理

types-of-mounts

在容器中管理数据主要有两种方式:

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

数据卷

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

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

注意:数据卷 的使用,类似于 Linux 下对目录或文件进行 mount,镜像中的被指定为挂载点的目录中的文件会隐藏掉,能显示看的是挂载的 数据卷

创建一个数据卷

1
$ docker volume create my-vol

查看所有的 数据卷

1
$ docker volume ls
2
3
local               my-vol

在主机里使用以下命令可以查看指定 数据卷 的信息

1
$ docker volume inspect my-vol
2
[
3
    {
4
        "Driver": "local",
5
        "Labels": {},
6
        "Mountpoint": "/var/lib/docker/volumes/my-vol/_data",
7
        "Name": "my-vol",
8
        "Options": {},
9
        "Scope": "local"
10
    }
11
]

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

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

下面创建一个名为 web 的容器,并加载一个 数据卷 到容器的 /webapp 目录。

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

查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

1
$ docker inspect web

数据卷 信息在 “Mounts” Key 下面

1
"Mounts": [
2
    {
3
        "Type": "volume",
4
        "Name": "my-vol",
5
        "Source": "/var/lib/docker/volumes/my-vol/_data",
6
        "Destination": "/app",
7
        "Driver": "local",
8
        "Mode": "",
9
        "RW": true,
10
        "Propagation": ""
11
    }
12
],

删除数据卷

1
$ docker volume rm my-vol

数据卷 是被设计用来持久化数据的,它的生命周期独立于容器,Docker 不会在容器被删除后自动删除 数据卷,并且也不存在垃圾回收这样的机制来处理没有任何容器引用的 数据卷。如果需要在删除容器的同时移除数据卷。可以在删除容器的时候使用 docker rm -v 这个命令。

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

1
$ docker volume prune

挂载主机目录

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

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

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

上面的命令加载主机的 /src/webapp 目录到容器的 /opt/webapp目录。这个功能在进行测试的时候十分方便,比如用户可以放置一些程序到本地目录中,来查看容器是否正常工作。本地目录的路径必须是绝对路径,以前使用 -v 参数时如果本地目录不存在 Docker 会自动为你创建一个文件夹,现在使用 --mount 参数时如果本地目录不存在,Docker 会报错。

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

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

加了 readonly 之后,就挂载为 只读 了。如果你在容器内 /opt/webapp 目录新建文件,会显示如下错误

1
/opt/webapp # touch new.txt
2
touch: new.txt: Read-only file system

查看数据卷的具体信息

在主机里使用以下命令可以查看 web 容器的信息

1
$ docker inspect web

挂载主机目录 的配置信息在 “Mounts” Key 下面

1
"Mounts": [
2
    {
3
        "Type": "bind",
4
        "Source": "/src/webapp",
5
        "Destination": "/opt/webapp",
6
        "Mode": "",
7
        "RW": true,
8
        "Propagation": "rprivate"
9
    }
10
],

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

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

1
$ docker run --rm -it \
2
   # -v $HOME/.bash_history:/root/.bash_history \
3
   --mount type=bind,source=$HOME/.bash_history,target=/root/.bash_history \
4
   ubuntu:18.04 \
5
   bash
6
7
root@2affd44b4667:/# history
8
1  ls
9
2  diskutil list

这样就可以记录在容器输入过的命令了。

Reference

https://www.docker-cn.com

https://yeasy.gitbooks.io/docker_practice/