Docker 入门与实战

[TOC]

了解Docker

什么是容器

​ 想要了解什么是Docker,首先要明白容器(container)的概念。容器是近年来兴起的一种操作系统层面的虚拟化技术,基于Linux内核的 cgroup,namespace等技术对进程进行隔离封装,由于隔离的进程独立于宿主和其他的隔离化进程,所以被称为容器。

​ 容器和虚拟机一样,都是硬件性能提升以及软件种类日渐丰富带来的硬件性能过剩与软件冲突两大问题的解决方案。所谓硬件虚拟化,就是由某个特殊的软件,虚拟出计算机的各种硬件。使用者可以在虚拟机上安装Guest OS 和各种软件,虚拟机会将Guest OS以及其他软件对硬件资源的访问,转发到底层的物理机上。而这个时候,Guest OS的开销就造成了硬件资源的浪费,例如Windows操作系统,仅仅是安装开机,不运行任何其他应用,就需要占用23G内存和 2030硬盘空间。然而虚拟机的这种架构,决定了运行在同一物理机上的每一台虚拟机都需要运行一个Guest OS,当虚拟机数量超过一定量的时候,浪费的硬件资源就相当可观了,尤其是大部分的Guest OS都是相同的。能不能所有的应用使用同一个操作系统减少硬件资源的浪费,又能避免包括Runtime library在内的软件冲突呢?于是就有了操作系统虚拟化技术,也就是容器。通过cgroup(control group)隔离并将应用与Runtime library打包在一起,不用实现自己的内核,也不需要专门的硬件虚拟,节省了Guest OS的性能开销,就实现了这个目的。

什么是Docker

​ Docker是一个开源的应用容器引擎,Docker在容器的基础上,进一步进行了封装,从文件系统、进程隔离到网络等,很大程度上简化了容器的创建和维护,使得容器虚拟化技术得到广泛的推广和使用。Docker早期基于lxc(Linux container)封装,从0.7版本后,docker去除了lxc,转而使用自行开发的libcontainer,从1.11开始,进一步演进为使用 runCcontainerd

​ 此外,Docker公司还提供了一个公共的镜像仓库(Repository),Github connect,自动构建镜像,极大简化了应用分发、部署、升级流程,这些使Docker成为最流行的容器技术。

为什么要用Docker

更加高效的利用硬件资源

​ 正如之前提到的,容器内不需要硬件虚拟和运行完整操作系统的开销,Docker对系统资源的利用率更加高,因此无论是应用执行速度、内存损耗还是文件储存速度,都比传统虚拟机技术更加高效。因此,相比传统的硬件虚拟化技术,一个相同配置的主机,可以运行更多数量的容器。

更快速的启动时间

​ 由于Docker容器应用,直接运行于Host内核,无需完整的操作系统Runtime,因此可以大大加快启动时间,大大节约开发、测试与部署的时间。

轻松迁移

​ 由于Docker直接封装了应用所需的Runtime library以及相关依赖,所以Docker的镜像可以保证应用的运行环境一致性,使得应用的迁移更加容易,无论是在哪种物理平台,使用Docker构建镜像并运行容器,都能保证应用的正常运行。

持续交付和部署

​ 使用Docker可以通过定制应用来实现持续集成、交付、部署。开发成员通过编写dockerfile来构建镜像,并结合CI系统进行集成测试,同时运维人员可以在生产环境快速部署,甚至结合CD系统进行自动部署。

总结

特性 容器 虚拟机
启动 秒级 分钟级
储存占用 MB级 GB级
性能 原生级 有损耗
HOST支持量 >1k ~10

Docker基础概念

Image

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

​ 因为镜像包含操作系统完整的root文件系统,所以体积往往较大,因此Docker设计的时候就充分利用了unionFS 的技术,将其设计为分成存储的架构。因此严格来说,镜像并非是像ISO文件那种打包文件,而是由多层文件系统联合组成。这种分层储存的特征使得镜像的复用、定制变得极为容易,可以利用之前构建好的镜像作为基础层,然后添加自己所需的内容,构建新的镜像。

Container

​ 镜像和容器之间的关系,有点类似在OOP语言中类和对象的关系。容器就是镜像在运行时的实体,镜像只是一个静态的定义,而容器可以被创建、启动、暂停、停止、删除等。

​ 容器的实质是进程,运行于属于自己的独立的namespace,因为与HOST隔离,所以容器可以拥有自己的root文件系统、自己的网络配置、自己的进程空间甚至是自己的用户ID空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作。这种特性使得容器封装的应用比直接在HOST运行更加安全。

​ 容器运行时,是以镜像为基础层,在其上创建一个当前容器的储存层。当容器消亡的时候,容器储存层也随之消亡。因此,任何保存于容器储存层的数据都会随容器删除而丢失。按照Docker最佳实践的要求,容器不应该向其储存层写入任何数据,容器储存层要保持无状态化。所有的文件写入操作,都应该使用数据卷(volume)或者绑定HOST目录,这样跳过容器储存层直接对HOST发生读写,性能和稳定性更高,即使容器删除或者重新运行,数据都能得到保存。

Registry

​ Docker image构建完成后,想要在其他服务器上使用这个镜像,就需要一个集中的储存、分发的服务,Docker registry就是这样的服务。一个Docker registry可以包含多个repository;每个仓库可以包含多个tag;每个tag对应一个镜像。通常一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本,我们常使用<repository>:<tag>来指定具体是那个版本的镜像。

​ 默认的registry是官方的Docker Hub,也是最常使用的registry。(一般来说,在国内使用Docker hub会受网络环境的影响,国内的一些云服务商提供了针对Docker hub的镜像mirror

安装Docker

​ Docker目前分为CE和EE两大版本,这里主要介绍CE在Windows 10、macOS、主流Linux上的安装。

Ubuntu

系统要求

​ Docker CE支持以下版本的Ubuntu系统:

  • Disco 19.04

  • Cosmic 18.10

  • Bionic 18.04(LTS)

  • Xenial 16.04(LTS)

    一般来说,在生产环境更加推荐LTS长期支持版本,更加稳定。

卸载旧版本

​ 旧版本的Docker 称为 docker或者docker-engine,使用以下命令卸载旧版本:

1
2
3
$ sudo apt-get remove docker \
docker-engine \
docker.io

使用apt安装

​ 首先要添加使用HTTPS传输的软件包以及CA证书。

1
2
3
4
5
6
$ sudo apt-get update
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common

​ 为了确认安装包的合法性,需要添加软件源的GPG密钥。

1
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 

​ 然后向source.list中添加Docker软件源

1
2
3
4
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) \
stable"

​ 更新apt软件包缓存,并安装docker-ce:

1
2
$ sudo apt-get update
$ sudo apt-get install docker-ce

使用脚本自动安装

​ Docker官方提供了一套简便的安装脚本,Ubuntu系统上可以使用这个脚本安装

1
2
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh

启动Docker CE

1
2
$ sudo systemctl enable docker
$ sudo systemctl start docker

建立docker用户组

​ 默认情况下,docker命令会使用Unix socket与docker引擎通信,而只有root和docker组的用户可以访问Docker引擎的Unix socket。因此为了安全起见,更好的做法就是将使用docker的用户加入docker用户组。

​ 建立docker用户组

1
$ sudo groupadd docker

​ 将当前用户加入docker用户组

1
$ sudo usermod -aG docker $USER

测试Docker是否安装正确

1
$ docker run hello-world

​ 正常情况下,将会输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
d1725b59e92d: Pull complete
Digest: sha256:0add3ace90ecb4adbf7777e9aacf18357296e799f81cabc9fde470971e499788
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/

For more examples and ideas, visit:
https://docs.docker.com/get-started/

使用镜像

在之前讲到镜像是一种特殊的文件系统,Docker在运行容器前需要本地存在对应的镜像,如果本地不准在该镜像,Docker会从镜像仓库下载该镜像。

获取镜像

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

1
docker pull [option] [registry address[:port]/]repo:[tag]
  • Docker镜像仓库地址格式一般是:。默认是Docker Hub
  • 仓库名:一般仓库名是两段式名称,即username/tag。对于Docker Hub如果不给出用户名,则默认为library,也就是官方镜像。

管理镜像

列出镜像

​ 我们可以使用 docker images ls 列出本地主机上的镜像。

​ 列表包含了 repository、tag、image id、created以及size。其中关于仓库名和标签的概念在前面已经提到,镜像ID则是镜像的唯一标识,一个镜像可以对应多个tag。因此,如何判断镜像的唯一性,就需要用到image id。

​ Docker image还支持通过参数筛选出特定的镜像,例如根据仓库名列出镜像,只需在ls后面加上仓库名。此外,Docker image ls 还支持过滤器参数,–filter,可以使用filter的强大功能进行筛选。

删除镜像

​ 当一个镜像没有被相应的容器运行时,我们可以使用docker rmi <image id>或者docker image rm来删除它。在Docker镜像管理中,有一个专门的命令docker image prune用来删除虚悬镜像。所谓虚悬镜像,即一个仓库名与tag均为的镜像,往往在docker pull新版本的镜像后,因为原来的镜像名被转移到了新pull的镜像,原镜像的名称变成了none而出现。除了Docker pull以外,Docker build也同样能导致这种情况,新的镜像与原有镜像同名,则原有镜像名称被取消,变为虚悬镜像。一般来说,虚悬镜像已经失去了存在的价值,可以随意删除,所有才有了专门的prune命令。

​ 在我们运行docker rmi时,通过输出的信息我们可以发现,Docker删除镜像的行为由Untag和Delete组成,即当我们删除镜像时,删除的是指定的标签,如果还有别的标签指向了该镜像,那么就不会进行Delete,当一个镜像的所有标签都被取消后,才会触发删除,即当没有任何层依赖当前层的时候,才会真实的删除当前层。这也是为什么在删除镜像之前,需要先删除所有依赖该镜像的容器。

查看镜像体积

​ 由于Docker镜像采用了多层储存结构,所以当我们使用Docker image ls命令列出的镜像体积总和并不等于所占硬盘空间,由于多层储存的结构,不同的镜像可能使用了相同的基础镜像,从而拥有共同的层,而相同的层只需要保存一份即可,因此实际硬盘占用空间要比列表镜像大小的总和小很多。可以使用Docker system df来查看镜像、容器与数据卷所占用的空间。

使用容器

​ 简单的来说,容器是独立运行的一个或一组应用,以及它们的runtime环境。接下来我们就从容器的生命周期讲一讲如何使用容器。

启动容器

​ 启动容器有两种,一种是基于镜像新建一个容器并启动,另一种是重新启动已终止的容器。

新建容器并启动

Docker run是新建容器的命令,例如现在我需要在开发环境上部署一个openCV开发环境

打赏

谢谢你的喜欢~

支付宝
微信