Hits

Docker介绍一

Docker基本命令

  • docker images // 显示Docker所有镜像
  • docker pull <仓库名>[:<标签>] // 获取镜像
  • docker run <仓库名>[:<标签>] // -d后台运行一个镜像。
  • docker exec -it 仓库ID bash // 进入容器命令行。
  • docker build . // 制作一个镜像,dockerfile告诉docker怎么制作一个镜像
  • docker rmi <仓库名>[:<标签>] // 删除一个镜像
  • docker ps // 查询当前运行的镜像
  • docker stop ID // 停掉启动的docker容器

Docker简介

什么是Docker

Docker使用Google公司推出的Go语言进行开发实现,基于Linux内核的cgroupnamespace,以及AUFS类的Union FS等技术,对进程进行封装隔离,属于操作系统层面的虚拟化技术。由于隔离的进程独立于宿主和其它的隔离进程,因此也称其为容器。最初实现是基于LXC,从0.7开始去除LXC,转而使用自行开发的libcontainer,从1.11开始,则进一步演化为使用runCcontainerd

Docker在容器的基础上,进行了进一步的封装,从文件系统、网络互联到进程隔离等等,极大的简化了容器的维护和创建。使得Docker技术比虚拟技术更为轻便、快捷。

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

virtualization

virtualization

为什么使用Docker

作为一种新兴的虚拟化技术,Docker跟传统的虚拟化方式相比具有更多优势。

更高校的利用系统资源

由于容器不需要进行硬件虚拟以及运行完整操作系统等额外开销,Docker 对系统资源的利用率更高。无论是应用执行速度、内存损耗或者文件存储速度,都要比传统虚拟机技术更高效。因此,相比虚拟机技术,一个相同配置的主机,往往可以运行更多数量的应用。

更快速的启动时间

传统的虚拟机技术启动应用服务往往需要数分钟,而Docker容器应用,由于直接运行于宿主内核,无需启动完整的操作系统,因此可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。

一致的运行环境

开发过程中一个常见的问题是环境不一致性问题,由于开发环境、测试环境、生产环境不一致,导致有些bug并未在开发过程中发现,而Docker的镜像提供了除内核外完整的运行时环境,确保了应用运行环境一致性,从而不会再出现“这段代码在我机器上没问题啊”等这类问题。

持续交付和部署

对开发和运维(DevOps)人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。使用Docker可以通过定制应用镜像来实现持续集成、持续交付、部署。开发人员可以通过Dockerfile来进行镜像构建,并结合持续集成(Continuous Integration)系统进行集成测试,而运维人员则可以直接在生产环境中快速部署该镜像,甚至结合持续部署(Continuous Delivery/Deployment)系统进行自动部署。

而且使用Dockerfile使镜像构建透明化,不仅仅开发团队可以理解应用运行环境,也方便运维团队理解应用运行所需条件,帮助更好的生产环境中部署该镜像。

更轻松的迁移

由于Docker确保了执行环节的一致性,使得应用的迁移更加容易。Docker可以在很多平台上运行,无论是物理机、虚拟机、公有云、私有云,甚至是笔记本,其运行结果是一致的。因此用户可以很轻易的将一个平台上运行的应用,迁移到另一个平台上,而不用担心运行环境的变化导致应用无法正常运行的情况。

更轻松的维护和扩展

Docker使用的分层存储以及镜像的技术,使得应用重复部分的复用更为容易,也使得应用的维护更新更加简单,基于基础镜像进一步扩展镜像也变得非常简单。此外Docker团队同哥哥开源项目团队一起维护了一大批高质量的官方镜像,既可以直接在生产环境使用,又可以作为基础进一步定制,大大的降低了应用服务的镜像制作成本。

对比传统虚拟机总结

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

基本概念

Docker镜像

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

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

分层存储

因为镜像包含操作系统完整的root文件系统,其体积往往是庞大的,因此在Docker设计时,就充分利用Union FS的技术,将其设计为分层存储结构。所以严格来说,镜像并非是像一个ISO那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说是由多层文件系统联合组成。

镜像构成时,会是一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层,比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件一直跟随着镜像。因此,在构建镜像的时候,要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应在该层构建结束前清理掉。

分层存储的特征还使得镜像的复用、定制变得更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。

容器

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

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

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

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

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

数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器可以随意删除、重新run,数据却不会丢失。

仓库

Docker Registry

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

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

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

以Ubuntu镜像为例,ubuntu是仓库的名字,其内包含不同的版本标签,如:14.0416.04。我们可以通过ubuntu:14.04或者Ubuntu:16.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的公开服务。比如时速云镜像仓库网易云镜像仓库DaoCloud镜像市场阿里云镜像库等。

私有Docker Registry

除了使用公开服务外,用户还可以在本地搭建私有Docker Registry。Docker官方提供了Docker Registry镜像,可以直接使用作为私有Registry服务。在后续文章,会进一步讲解搭建私有Registry服务。

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

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

安装Docker

MacOS

使用Homebrew安装

brew cask install docker

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

##查看docker版本
$ docker --version
$ docker-compose --version
$ docker-machine --version

用docker运行一个Nginx服务器

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

要停止Nginx服务器并删除执行下面的命令

$ docker stop webserver
$ docker rm webserver

CentOS

Ubuntu、Debian

Docker需要系统内核Ubuntu 12.04以上

查看系统内核版本

$ uname -a

升级内核命令

sudo apt-get install -y --install-recommends linux-generic-lts-xenial

使用脚本自动安装

## 官方脚本安装
curl -sSL https://get.docker.com/ | sh
## 阿里云安装脚本
curl -sSL http://acs-public-mirror.oss-cn-hangzhou.aliyuncs.com/docker-engine/internet | sh -
## DaoCloud的安装脚本
curl -sSL https://get.daocloud.io/docker | sh

安装Docker

$ sudo apt-get install docker-engine

启动Docker 引擎

$ sudo service docker start

建立Docker用户组

$ sudo groupadd docker

将当前用户加入docker

$ sudo usermod -aG docker $USER

镜像加速器

使用镜像

  • 从仓库获取镜像
  • 管理本地主机上的镜像
  • 介绍镜像的基本与那里

获取镜像

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

docker pull [选项] [Docker Registry url] <仓库名>:<标签>

具体的选项可以通过docker pull --help命令得到,镜像名称的格式为: * Docker Registry地址:地址的格式一般是<域名/IP>[:端口号]。默认地址是Docker Hub * 仓库名:如之前所说,这里的仓库名是两段式名称,既<用户名>/<软件名>。对于Docker Hub,如果不给出用户名,则默认为library,也就是官方镜像。

例如:

$ docker pull ubuntu:14.04

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

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

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

如果从Docker Hub下载镜像非常缓慢,可以参照后面的章节配置加速器。

运行下载的Docker镜像,运行Ubuntu:14.04,并启动里面的bash进行交互式操作。

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

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

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

列出镜像

docker images 可列出已经下载下来的镜像。 列表包含了仓库名、标签、镜像ID、创建时间以及所占用的空间。

镜像体积

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

虚悬镜像

docker images列表中有个特殊的镜像,既没有仓库名,也没有标签,均为<none>。:

<none>   <none>   00285df0df87   5 days ago   342MB

这个镜像原来是有镜像名和标签的,原来为mongo:3.2,随着官方镜像维护,发布了新版本后,重新docker pull mongo:3.2时,mongo:3.2这个镜像名被移到了新下载的镜像身上,而旧的镜像上的这个名词则被取消,从而成为了<none>。除了docker pull可能导致这种情况,docker builder也同样可以导致这种现象。由于新旧镜像同名,旧镜像名称被取消,从而现仓库名、标签均为<none>的镜像。这类无标签镜像也被称为虚悬镜像(dangling image) ,可以用下面的命令专门显示这类镜像:

$ docker image -f dangling=true

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

$ docker rmi $(docker images -q -f dangling=true)

中间层镜像

为了加速镜像构建、重复利用资源,Docker会利用中间层镜像。所以使用一段时间后,可能会看到一些依赖的中间层镜像,默认的docker images列表中只会显示顶层镜像,如果希望显示中间层镜像在内的所有镜像的话,需要加-a参数。

$ docker images -a

列出部分镜像

## 根据仓库名列出镜像
$ docker images ubuntu

## 列出特定的某个镜像,也就是说指定仓库名和标签
$ docker images ubuntu:16.04

## 过滤参数,--filter, -f, 列出mongo:3.2之后建立的镜像。
## 查看某个镜像之前的把since换成before
$ docker images -f since=mongo:3.2

## 以特定的格式显示,把所有虚悬镜像的ID列出来
$ docker images -q

## --filter 配合 -q产生指定范围的ID列表,然后送给另外一个docker命令作为参数,从而针对这组实体成批的进行某种操作的做法在Docker命令行使用中非常常见。
## 列出镜像结果,并且只包含镜像Id和仓库名。
$ docker images --format "{{.ID}}:{{.Repository}}"

## 以表格等距显示,并且有标题行,和默认医院,不过自己定义列
$ docker images --format "table {{.ID}}\t{{.Repository}}\t{{.Tag}}"

利用commit理解镜像构成

## 定制一个web服务器的例子
$ docker run --name webserver -d -p 80:80 nginx
## 访问localhost可以查看启动的nginx web服务
## 修改nginx欢迎页面
$ docker exec -it webserver #!/usr/bin/env bash
root@123:/# echo '<h1>Hello Docker!</h1> > /usr/share/nginx/html/index.html'
root@123:/# exit
exit
## 刷新浏览器localhost
## 我们修改了容器的文件,也改动了容器的存储层,可以通过docker diff查看具体改动
$ docker diff webserver
## 通过commit保存修改后的镜像
$ docker commit [选项] <容器ID或者容器名> [<仓库名>[:<标签>]]
$ docker commit \
  --author "lzw <liuzhiwang4480@gmail.com>" \
  --message "修改了nginx默认网页" \
  webserver \
  nginx:v2

慎用docker commit

使用docker commit命令虽然可以比较直观的理解镜像分层的概念,但实际不会这样使用。

使用docker commit意味着所有对镜像的操作是黑箱操作,生成的镜像也被称为黑箱镜像。换句话说,就是除了制作镜像的人知道执行过什么命令、怎么生产的镜像,别人根本无从所知。而且即使是这个制作镜像的人,过一段时间也无法记清具体操作。虽然docker diff或许可以告诉得到一些线索,但是远远不到可以确保生成一致镜像的地步,这种黑箱镜像的维护工作是非常痛苦的。

而且,回顾之前提及的镜像所使用的分层存储的概念,除当前层外,之前的每一层都是不会发生改变的,换句话说说,任何修改的结果仅仅是在当前层进行的标记、添加、修改,而不会改动上一层。如果使用docker commit制作镜像,以及后期修改的话,每一次修改都会让镜像更加臃肿一次,所删除的上一层的东西并不会丢失,会一直如影随形的跟着这个镜像,即使根本无法访问到,这会让镜像更加臃肿。

使用Dockerfile定制镜像

本文链接:参与评论 »

--EOF--

提醒:本文最后更新于 361 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「docker相关知识学习」的其它文章 »

Comments