#拉取busybox官方镜像,启动容器并执行输出"Hello Docker"
#********** Begin *********##********** End **********#
Docker 最初是 dotCloud 公司创始人 Solomon Hykes 在法国期间发起的一个公司内部项目,它是基于 dotCloud 公司多年云服务技术的一次革新,并于 2013 年 3月以 Apache 2.0 授权协议开源),主要项目代码在 GitHub 上进行维护。Docker 项目后来还加入了 Linux 基金会,并成立推动开放容器联盟。
Docker 自开源后受到广泛的关注和讨论,至今其 GitHub 项目已经超过 3 万 6 千个星标和一万多个 fork。甚至由于 Docker 项目的火爆,在 2013 年底,dotCloud公司决定改名为 Docker。Docker 最初是在 Ubuntu 12.04 上开发实现的;Red Hat 则从 RHEL 6.5 开始对 Docker 进行支持;Google 也在其 PaaS 产品中广泛应用Docker。
Docker 使用 Google 公司推出的 Go 语言 进行开发实现,基于 Linux 内核的cgroup,namespace,以及 AUFS 类的 Union FS 等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离的进程,因此也称其为容器。最初实现是基于 LXC,从 0.7 以后开始去除 LXC,转而使用自行开发的 libcontainer,从 1.11 开始,则进一步演进为使用 runC 和containerd。
Docker 在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的创建和维护。使得 Docker 技术比虚拟机技术更为轻便、快捷。
在docker的官方网站上提到了docker的典型场景:
Docker 使用客户端-服务器 (C/S) 架构模式,使用远程API来管理和创建Docker容器。Docker 容器通过 Docker 镜像来创建。容器与镜像的关系类似于面向对象编程中的对象与类。
Docker采用 C/S架构 Docker daemon 作为服务端接受来自客户的请求,并处理这些请求(创建、运行、分发容器)。 客户端和服务端既可以运行在一个机器上,也可通过 socket 或者RESTful API 来进行通信。
Docker daemon 一般在宿主主机后台运行,等待接收来自客户端的消息。 Docker 客户端则为用户提供一系列可执行命令,用户用这些命令实现跟 Docker daemon 交互。
虚拟化的核心是对资源进行抽象,目标往往是为了在同一个机器上运行多个系统或应用,从而提高系统资源的利用率。虚拟化分为很多类型,比如常见的硬件辅助虚拟化(VMware workstation、 KVM等)。Docker所代表的容器虚拟化技术属于操作系统级虚拟化:内核通过创建多个虚拟的操作系统实例(内核和库)来隔离不同的进程。
如下图所示,传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;而容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核,而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
作为一种新兴的虚拟化方式,Docker 跟传统的虚拟化方式相比具有众多的优势。
更高效的利用系统资源
由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。
更快速的启动时间:
传统的虚拟机技术启动应用服务往往需要数分钟,而 Docker 容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。
一致的运行环境
开发过程中一个常见的问题是环境一致性问题。由于开发环境、测试环境、生产环境不一致,导致有些 bug 并未在开发过程中被发现。而 Docker 的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现 “这段代码在我机器上没问题啊” 这类问题。
持续交付和部署
对开发和运维( DevOps) 人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过 Dockerfile 来进行镜像构建,并结合 持续集成(Continuous Integration) 系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment) 系统进行自动部署。而且使用 Dockerfile 使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。
更轻松的迁移
由于 Docker 确保了执行环境的一致性,使得应用的迁移更加容易。Docker 可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将在一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。
更轻松的维护和扩展
Docker 使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外,Docker 团队同各个开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。
镜像( Image)
容器( Container)
仓库( Repository)
Docker 镜像
我们都知道,操作系统分为内核和用户空间。对于 Linux 而言,内核启动后,会挂载 root 文件系统为其提供用户空间支持。而Docker 镜像(Image),就相当于是一个 root文件系统。比如官方镜像 ubuntu:14.04 就包含了完整的一套Ubuntu 14.04 最小系统的 root 文件系统。
Docker 镜像是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数( 如匿名卷、环境变量、用户等)。镜像不包含任何动态数据,其内容在构建之后也不会被改变。
Docker容器
镜像( Image) 和容器( Container)的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的命名空间。因此容器可以拥有自己的 root 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。
Docker Registry
仓库(Repository)是集中存放镜像的地方。另外一个非常相似的单词Registry是注册服务器(例如Docker Hub就是一个官方的Registry)。注册服务器是管理仓库的具体服务器,每个服务器上可以有多个仓库,而每个仓库下面有多个镜像。
从这方面来说,仓库可以被认为是一个具体的项目或目录。例如对于仓库地址dl.dockerpool.com/ubuntu来说,dl.dockerpool.com 是注册服务器地址,ubuntu 是仓库名。(一般而言,一个仓库会存放同一种类型的镜像,例如ubuntu的仓库。)
一个 Docker Registry 中可以包含多个仓库(Repository);每个仓库可以包含多个标签(Tag);每个标签对应一个镜像。通常,一个仓库会包含同一个软件不同版本的镜像会,而标签就常用于对应该软件的各个版本。我们可以通过 <仓库名>:<标签> 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 latest 作为默认标签。
以 Ubuntu 镜像为例, ubuntu是仓库的名字,其内包含有不同的版本标签,比如,14.04 , 16.04 。我们可以通过 ubuntu:14.04 ,或者 ubuntu:16.04来具体指定所需哪个版本的镜像。如果忽略了标签,比如ubuntu ,那将视为ubuntu:latest 。
仓库名经常以两段式路径形式出现,比如training/webapp,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。
在没有使用Docker之前,如果我们要准备一个ubuntu的运行环境,那么首先我们得安装一个ubuntu的系统,多么麻烦,多么耗时啊!!而使用Docker之后,只需要从Docker Hub中拉取一个ubuntu镜像,基于该ubuntu镜像启动一个容器,就可以在容器中做任何在ubuntu系统中做的事情,整个过程大约2-3分钟,而且主要的时间是用于下载镜像!是不是非常方便呢?
本关的任务是学习准备一个容器的运行环境,更准确的说,应该是拉取一个具备某个运行环境的镜像,要求学习者参照示例完成“从Docker Hub中拉取一个busybox:1.27
镜像”的功能。
在Docker的官方镜像仓库Docker Hub中保存了各种各样的镜像,这些镜像中保存了各种各样的运行环境。例如包含Linux运行环境的“ubuntu”镜像、“centos”镜像、“busybox”镜像等,提供数据库服务的“mysql”镜像、“oracle”镜像、“redis”镜像等。提供程序运行环境的“java”镜像、“python”镜像、“c++”镜像等等。基本上我们日常工作所需要的运行环境在Docker Hub中都会有对应的镜像(Docker Hub官网:https://hub.docker.com/ )(这些镜像不是凭空出现的,这是镜像构建者们辛勤的劳动成果。每一个Docker的使用者都应该感谢这些镜像构建者们!!)
但是在安装完Docker之后,本地是没有任何镜像的。下面介绍如何从Docker Hub中拉取镜像(或者说下载镜像)。
默认情况下,使用docker pull
命令,会从官方的Docker Hub库中将镜像拉取到本地。
首先介绍这条命令的格式:
docker pull [OPTIONS] <仓库名>:<标签>
其中,
docker pull
: Docker拉取镜像的命令关键词[OPTIONS]
:命令选项仓库名
:仓库名的格式一般为<用户名>/<软件名>
。对于Docker Hub,如果不指定用户名,则默认为library
,即官方镜像。标签
:标签是区分镜像不同版本的一个重要参数,<仓库名>:<标签>
会唯一确定一个镜像。默认为latest
。 例如,我们要从Docker Hub官方仓库拉取一个Ubuntu 14.04
的官方镜像,其语句如下:
docker pull ubuntu:14.04`
首先,如果tag
值为空,即没有指定标签,就会使用默认tag
,也就是latest
,如果tag
值不为空,就使用指定的tag
。
然后,默认情况下,会在Docker Hub中寻找名为“repoName”的仓库,如果仓库不存在,返回错误信息。如果仓库存在,就从仓库中拉取对应tag
的镜像。例如如果执行docker pull ubuntu:14.04
,那么将从“ubuntu”仓库中拉取tag
为14.04
的镜像,而如果执行docker pull ubuntu
,会从“ubuntu”仓库中拉取tag
为latest
的镜像
(在Docker Hub
中有很多个镜像仓库,一般情况下会将同一类型的镜像放在同一个仓库中,例如在一个ubuntu仓库中由很多个ubuntu镜像组成,包括ubuntu:14.04
、ubuntu:16.04
、`ubuntu:latest
等等镜像)
最后,将拉取的镜像存储到本地的指定位置。
执行docker pull ubuntu
之后,会有下面的执行结果。(执行命令之前,本地并不存在ubuntu:latest
镜像)。
如下图所示,因为没有显示地指定tag
,所以就使用默认tag
,也就是latest
。于是会在Docker Hub中从ubuntu仓库中拉取ubuntu:latest
镜像,当将镜像一层层下载完成后,存储到本地。
[root@localhost Desktop]# docker pull ubuntu
Using default tag: latest
latest: Pulling from library/ubuntu
e0a742c2abfd: Pull complete
486cb8339a27: Pull complete
dc6f0d824617: Pull complete
4f7a5649a30e: Pull complete
672363445ad2: Pull complete
Digest: sha256:84c334414e2bfdcae99509a6add166bbb4fa4041dc3fa6af08046a66fed3005f
Status: Downloaded newer image for ubuntu:latest
[root@localhost Desktop]#
那从Docker Hub中拉取一个不存在的镜像会怎么样呢?如下所示,执行docker pull aaa
后,会在Docker Hub中查找aaa
仓库,由于不存在aaa
仓库,所以没有找到,返回了错误信息。
[root@localhost Desktop]# docker pull aaa
Using default tag: latest
Error response from daemon: repository aaa not found: does not exist or no pull access
由于“伟大的墙”的原因,在国内从Docker Hub中拉取镜像的速度可能会比较慢,国内很多云服务商都提供了镜像加速器服务,例如阿里、网易等等。
以Linux系统配置阿里云加速器为例,只需要将下面的命令复制到Linux的终端,以root用户的身份执行之后,就成功的配置了阿里云加速器了!
#以root用户执行以下操作
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{#下面的URL可以替换为你自己的阿里云加速地址
"registry-mirrors": ["https://jxus37ad.mirror.aliyuncs.com "]
}
EOF
systemctl daemon-reload
systemctl restart docker
如果将Docker比作一艘轮船,那容器就是轮船上一个一个的集装箱,而镜像就是组成集装箱的基本材料。docker容器是Docker中至关重要的一部分,而这一节我们介绍如何启动一个容器。
本关任务是学习启动一个容器,要求学习者参照示例完成“创建并启动一个名为firstContainer
的容器,该容器具备busybox
的运行环境,并在启动时输出一个hello world
”。
在拉取到一个镜像之后,也就为容器准备了运行环境。下面我们将介绍如何使用镜像启动一个容器。
启动容器有两种方式,一种是基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped
)的容器重新启动。
docker run
命令会基于指定的镜像创建一个容器并且启动它。docker run
的基本语法如下:
docker run [OPTIONS] 镜像名 [COMMAND] [ARG]
其中,
docker run
: Docker创建并启动容器的命令关键词OPTIIONS
: 命令选项,最常用的包括-d
后台运行容器并返回容器ID,-i
以交互模式运行容器,-t
为容器分配一个伪输入终端,--name
指定启动容器的名称。更多选项请参考docker帮助文档。镜像名
: 以<仓库名>:<标签>
的方式来指定COMMAND
: 设置启动命令,该命令在容器启动后执行ARG
: 其他一些参数Docker 在后台运行的标准操作包括:
第三条到第五条看不懂没有什么关系,后面我们会介绍相关的知识,相信到时你一定能够明白,但是现在我们要知道这个过程,了解这个过程能够帮助我们更好的使用命令。好,接下来我们介绍几个实例。
创建并启动一个容器,容器中具有ubuntu的运行环境,输出hello docker。
只需要一条命令:docker run ubuntu:14.04 echo 'hello docker'
,问题就解决了,是不是很简单啊!!是的,现在我们来讲一下它里面的操作,用来之前学过的知识点。
首先由于本地不存在ubuntu:14.04
镜像,所以先到Docker Hub
中下载镜像;(其实也就是先执行docker pull ubuntu:14.04
)
然后在下载完镜像之后,使用镜像创建。由于一个ubuntu
镜像包含了一个ubuntu
系统的所有内容,使用镜像启动后,容器中就具备了ubuntu
的运行环境了。
启动容器后,执行echo 'hello docker'
启动命令,执行完启动命令之后终止容器。
创建并启动一个容器,容器中具有ubuntu的运行环境,容器名为firstContainer,为容器分配一个终端,与用户进行交互。
其中,-i
选项告诉Docker保持标准输入输出流对容器开放,-t
选项让Docker分配一个伪终端(pseudo-tty
)并绑定到容器的标准输入上。--name
为容器设置容器名。
注意docker run
是创建一个新容器并启动,所以这条命令创建的容器与上个实例的创建的容器不是同一个容器。而且由于本地已经存在ubuntu:latest
镜像了,所以并不需要再次从Docker Hub中下载,而是直接使用本地的ubuntu:latest
镜像构建容器。
启动容器之后,我们进入容器内部并在终端进行与容器交互。我们可以根据左侧的命令提示符判断自己是否在容器内部。例如上面的例子,当左侧的命令提示符为root@localhost
时,表示我们在容器外部,而命令提示符为:root@fe263c9359dd/
时,表示我们在容器内部,且容器的ID是fe263c9359dd
。我们可以通过exit
退出当前的容器。
虽然Docker容器是非常轻量的,这意味着一般情况下,我们在启动完容器并完成操作之后都会将容器删除掉。但是有些时候我们会进入之前创建的容器,而docker run
每次都会创建一个新容器,显然不符合我们的需求。这种时候,可以使用docker start
命令,使用容器名或者容器id启动一个已经终止的容器。
docker start [OPTIONS] 容器 [容器2...]
其中,
docker start
: Docker启动容器的命令关键词OPTIIONS
: 命令选项容器
: 需要启动的容器,该容器用“容器ID”或“容器名”表示,如果指定了多个容器,那么就将这些容器都启动。 假设一个名为firstContainer
的容器处于终止状态,现在需要将它启动,可以这么做:执行docker start firstContainer
,命令执行后,尝试启动firstContainer
容器,并执行该容器的启动命令。
但是如果想启动第一个实例创建的容器,既不知道容器的名字(因为我没有指定)而且也不知道它的id。该怎么办呢?
docker中有这样一条命令docker ps
,可以查看容器的信息,包括容器ID,基础镜像,启动命令,创建时间,当前状态,端口号,容器名字。
如果不加任何参数,只执行docker ps
,将会显示所有运行中的容器。例如执行docker ps
,如下图所示,在当前的Docker环境中,只有一个正在运行的容器,它的容器Id是fe263c9359dd,基于ubuntu:latest镜像,启动命令为”/bin/bash”,创建时间为2分钟之前,当前状态为”Up 2 minutes”,也就是已经运行了2分钟了,容器名为:firstContainer。
而如果docker ps –a
命令,可以查看Docker环境中所有的容器,包括已经停止的容器。执行docker ps –a
后,如下图所示:除了名为firstContainer的容器外,还可以看到一个id为ee826f1d58ff
的容器容器(容器id随机生成)。但是这个容器的当前状态为Exited (0) 3 minutes ago
,这表示它是处于终止状态的,而且是在3分钟前退出的。
对于这个处于终止状态的容器,可以通过docker start ee826f1d58ff
或者docker start gracious_lewin
启动该容器了。
实际情况中,使用docker start ee826f1d58ff
去启动第一个实例的容器,然后使用docker ps
查看,会看不到该容器。这也就是说明了在执行docker ps
时,容器Id为ee826f1d58ff
的容器并不是处于运行状态,而是处于终止状态。
docker ps
的执行结果如下所示:容器Id为ee826f1d58ff
的容器的当前状态为Exited (0) 2 seconds ago
!!!这表示ee826f1d58ff
容器确实处于终止状态,但是它是2秒之前退出的,注意是2秒前!这表明2秒前启动过容器,但是由于某种原因,容器终止了!!
实际情况下,执行docker start ee826f1d58ff
启动容器id为ee826f1d58ff
的容器了!!但是在执行完启动命令之后,该容器就立即结束了,至于为什么会结束,下一关我们将为您揭晓答案!!
本关的编程任务是补全step3/startcontainer.sh文件中的内容,要求创建并启动一个容器。具体要求如下:
#以ubuntu镜像为基础,创建并在后台启动了一个名为firstContainer的容器(-d看不懂没关系,下一关会介绍的)
docker run -itd --name firstContainer ubuntu /bin/bash
#将firstContainer容器停止!
#********** Begin *********#
docker stop firstContainer
#********** End **********#
#删除所有容器
#********** Begin *********#
docker rm -f $(docker ps -a -q)
#********** End **********#