Docker快速入门教程:Docker价值作用、镜像、仓库、容器、Docker化改造、Cgroups、Namespace、常用命令

前言

本文为Docker快速入门的浅显教程,主要关键字如下:
在这里插入图片描述
问题先行:
在这里插入图片描述
本文为《企业级容器云架构开发指南》读书笔记。

Docker的价值

无Docker的痛点

如何正确理解Docker这个新技术的价值呢?下面我们以软件开发活动中常见的一个任务来解释和发掘Docker的价值:

“在某个Linux服务器上部署一个MySQL Server 5.7实例并创建一个库,供项目开发测试使用。”

对于MySQL DBA或者熟练的运维工程师来说,这是一个非常普通的任务,但是对于其他人来说可能是一项颇具挑战的工作。在实现上述任务的过程中,可能遇到各种意外:例如,服务器上已经有一个低版本的MySQL了,此次新安装一个5.7的高版本实例时就比较麻烦了,这里涉及一个所谓隔离的问题,因为Linux下有很多开源软件不具备隔离性,一般不能同时安装几个实例。

比较直接的解决方式是,从源码编译实现定制安装,但这个过程对技能的要求就更高了。此外,Linux下的二级制安装包经常要依赖大量的其他基础包,很多时候还要求严格版本匹配,否则就无法安装成功。这其实是Linux的一个痛点,即使YUM仓库体系也没能完全解决这个问题,与这个问题相关的另外一个痛点是同一个应用程序在不同的Linux发行版上的安装部署无法做到统一。

所以,即使是安装很常见的MySQL数据库这样的基础软件,是否能安装成功,需要多长时间,很大程度上取决于系统环境以及任务执行者的经验和能力,你无法准确预估完成此任务究竟需要半小时、半天或者是3天。

那么,如果用Docker来完成此任务,究竟需要多少时间呢?答案是5分钟就好,只要一个命令行:

docker run -d --name mysqlsrv1-p 3306:3306-e MYSQL_ROOT_PASSWORD=123456  mysql

上述命令的意思是启动一个名字为mysqlsrv1的MySQL容器,MySQL的ROOT用户密码为“123456”,并且绑定了主机上的3306端口,因此我们可以用安装Docker主机的IP地址以及3306端口访问这个容器化的MySQL Server,用起来与普通的MySQL Sever完全一样。然后我们还能用docker exec命令进入到容器内部进行操作,如查看MySQL的配置信息、数据文件,以及相关进程状态等信息。如果在一个主机上部署多个版本或者相同版本的MySQL,只要绑定不同的主机端口即可让它们和平共处并共同提供服务了。

从上面的过程可以看出Docker容器具备“即插即用”的特点——直接启动就可以用,而没有我们所熟悉的传统的复杂“安装过程”。这是为什么呢?答案就在Docker容器所使用的Docker镜像(Docker Image)里。

因为Docker镜像是把安装结果提前“固化”的一种特殊二级制程序包,这种特殊的镜像文件通过Docker Build工具自动执行安装脚本,并且把安装结果“固化”到一个特殊的压缩包里,这个压缩包内部包括了主程序运行过程中所需要的所有依赖程序与配置文件。

Docker镜像与Linux RPM包不一样,因为RPM不包括它所依赖的RPM包,而Docker镜像完全是自包含的,不需要任何第三方镜像,而且任何两个镜像之间没有依赖性,运行期不依赖于环境。因此,Docker镜像做到了与外部环境无关的隔离性,在任何一个安装了Docker运行环境的Linux系统中都能100%正确运行,也就是说任何人只要按照上面的Docker指令操作,都会是完全一样的结果,不再依赖具体任务执行者的经验和水平,所以Docker技术其实把一些运维工作标准化了。

因此在Docker的帮助下,一个新人可以很容易地达到一个具有3年经验的运维工程师的水平,也具备了在短时间内快速掌握更多常用中间件和基础设施的安装部署工作的能力。此外,由于Docker所具备的环境无关性,以及资源占用少的优势,我们可以在自己的笔记本式计算机中启动一个虚拟机来学习和检验Docker化的程序部署过程,只要在本地的虚拟机中运行成功了,那么在任何服务器中都会成功。

Docker通过制造镜像的方式规避了安装这一最复杂的问题,所以Docker化的软件就不存在安装环节了,因此可以很方便地在任意机器上启动和重启。此外,借助于Docker Engine所提供的REST API,诸如镜像打包、镜像上传、镜像下载、容器创建、容器全生命周期管理等任务都能通过编程方式来实现自动化驱动,从而实现了软件全生命周期过程中的自动化驱动,进一步提升了敏捷开发的能力。

严格来说,Docker只有两件事是它自己做的,即容器标准化和镜像打包。容器技术和虚拟机技术基本上是并行开始的,但容器技术一开始是一个专有技术,是一套API(LXC),没有面向个人,用户无法直接创建一个容器来使用,但是Docker将LXC API封装以后,将其标准化了,从而将容器技术这个只有少数人知道和掌握的高级技术变成普通用户可以使用的大众技术。为了让容器技术标准化和普及化,Docker创新设计了镜像打包技术,把容器所有依赖的环境都打成一个标准包,将底层库、程序、配置文件等全部打成一个包并制作成二进制镜像文件后,就可以随意搬迁到任意服务器上或公有云上。所以说,镜像技术是Docker最大的创新技术。

Docker的口号

Docker的口号Docker的口号很像Java的口号:“任何程序只要‘Build’一次,就可以被搬迁到任何环境中运行”,这个口号很有鼓动性。

Docker的口号有以下3个关键词,如图所示。
在这里插入图片描述
Build:大多数情况下,Build由运维工程师负责,将应用程序的安装过程固化为一个可以移植和复用的Docker镜像,这个过程是通过Dockfile以及相关的docker build命令来完成的。实际上,这是在Linux系统中安装和配置某个软件的过程,最终输出的就是一个类似光盘镜像的二进制文件。

Ship:Build完成的镜像可以上传到Docker Hub存储,并被任意一台联网的计算机下载到本地,这个过程就是“Ship”。目前所有的镜像都存储在Docker官方维护的Docker Hub镜像仓库中,在国内如果网速较慢,可以到国内的时速云上查找是否有克隆的镜像可以下载。

Run:采用docker run命令启动一个容器的过程。虽然官方宣传Docker可以运行在任何环境中,但实际上Docker只能运行在高版本的64位Linux系统中。即使如此,也较Java的兼容性好,Java希望任何设备都能兼容,结果反而是不兼容的问题频发。

Docker相关术语及概述

Image

Docker Image(镜像)相当于绿色版的二进制程序包,所有底层依赖的第三方软件都包含在镜像文件里,我们认为它是一个多合一(all in one)的光盘镜像,无须安装就可以直接运行。以常见的Java Web程序的镜像为例说明Docker镜像的特点,如图所示,这个镜像总体上分为4层,从底层到上层依次为:
在这里插入图片描述

❑ Linux外围文件和程序层,可以理解为CentOS或者Ubuntu的“壳”,这一层的文件基本上包括/root目录、/etc目录、/opt目录,以及一些基础Linux程序文件,但不包括Linux内核程序和文件,因为容器共享主机上的Linux内核。其实所有Linux发行版本的内核是一样的,只是外面加的“壳”各自不同,不同的壳使用不同的文件系统,程序包和运行工具不一样。

❑ JDK层,这部分是对应的Open JDK或者Oracle JDK程序,不同的镜像会安装不同的JDK版本。

❑ Tomcat层,这一层安装好了Apache Tomcat程序的镜像层。

❑ User App层,在Tomcat层里的Webapp目录下添加了我们所开发的Java Web程序包,从而在Tomcat启动的时候,就加载了我们的应用。

从上面的结构可以清晰地看出Docker Image的最大特点——分层继承,即上层镜像继承下层镜像,模仿了面向对象编程的“继承”思想,可以“继承”某个已有镜像,在此基础上增加自己的新内容,而不是一起从起点开始,这种设计使得Docker镜像的制作过程变得更加简单。镜像制作的过程也不神秘,即编写一个Dockerfile,里面基本都是Linux命令,如yum install安装软件、修改配置文件、复制文件到镜像中等基本命令,然后采用Docker build命令执行上述Dockerfile的操作,最后就产生了一个可用的Docker镜像。

那么Docker镜像的实质是什么?
按照Docker的做法,我们把安装配置过程写成一个Dockerfile文件,然后通过Docker build命令执行其中的指令,最后产生一个永久性的镜像,这个镜像可以在任何地方运行,不再耗费安装时间,节省了大量人力成本。因此,Docker镜像的实质是指把Linux上某个应用的安装和配置活动提前“固化”并且自动化和标准化的过程,即把原来重复N次的人工安装配置过程,提前“固化”并“标准化”。

>> [插图]

Docker Registry

我们制作好的Docker镜像存放在哪里才能方便以后的多机器部署要求呢?
答案是Docker镜像仓库(Docker Registry)。

Docker Registry是Docker镜像的集中托管地,它完全参考了Git Hub的思想,具有版本管理功能,不同的是Git Hub托管的是源码,而Docker Registry托管的是二进制镜像文件。放入Docker Registry里面的每一个镜像都有一个Tag(标签),可以将Tag看作镜像的版本,“镜像名称+Tag”形成了镜像的唯一标识,因此同一个镜像可以在Docker Registry中保持多个版本,有了Docker Registry,容器升级或回退到某个历史版本都很容易实现。

Container

有了Docker镜像,我们就可使用这个镜像启动一个Docker容器(Container)了,Docker镜像类似光盘,而Docker容器就是从这个光盘启动的一个“迷你虚拟机”。

Docker与虚拟机有何不同?
首先,一个物理机上的每台虚拟机都拥有一个完整的操作系统;但容器没有自己的操作系统,所有容器共享宿主机的内核,没有自己的独立内核与操作系统。由于操作系统和内核本身要占用大量的系统资源,所以虚拟机浪费了不少硬件资源;而容器共享同一个内核,所以容器更加节省物理资源,损耗小,因而更加轻量级。

其次,由于有独立的操作系统,所以每个虚拟机之间是完全隔离的,一个虚拟机宕机了不会影响其他虚拟机的正常运行,所以,虚拟机系统的整体稳定性非常高。而容器共享内核,因此当宿主机内核崩溃时,会导致所有容器一起烟消云散。

Docker容器生命周期

从图中可以看到,Docker容器有running(运行中)、paused(挂起)、stopped(已停止)以及deleted(已删除)4种状态。而常用的docker run命令其实内部是连续触发了create与start两个事件的一个简单流程。另外,从这个状态图来看,running与paused状态的容器是不能直接删除的,只有先stop后才能删除容器。
>> [插图]

Docker提供的容器生命周期管理功能对于微服务架构很有意义,每个微服务对象都有定义过程、启动过程,如果某个微服务产了问题,可以先挂起,待故障解决后再恢复运行或者删除实例。

Volume

Docker镜像是分层文件系统,可以认为分层文件系统所管理的文件列表存储在一个特殊的“压缩包”里。利用Docker镜像产生的容器则使用了一种特殊的分层文件管理系统,它的作用是让访问分层文件系统的进程看到一个常规的“扁平”文件目录,而不是背后的“压缩包”。虽然Docker特有的分层文件管理系统解决了进程透明访问分层文件的问题,但带来的代价是I/O操作性能的降低。此外,由于容器内部的文件系统是完全隔离的,因此在宿主机上是无法直接看到和访问容器内部文件的,为了解决这两个问题,Docker引入了Volume(挂接卷)的概念,允许把宿主机上的某个文件目录(或文件)映射到容器内部的某个指定目录(或文件)上,容器内部读写这个Volume的时候,直接采用了宿主机的I/O机制,这样一来I/O操作就没有损耗了,如图所示。
在这里插入图片描述
Docker Volume可以挂接宿主机上的一个文件目录或者具体的某个文件,用法也非常简单,就是在docker run命令里采用“-v”参数进行映射,使用起来也非常简便,Docker Volume具有以下几个常规用途。
1)容器与主机共享文件,通过Volume的方式让容器中的某个目录访问主机,实现文件共享。
2)将容器中的复杂配置文件放到宿主机上,通过Volume方式挂接到容器内,实现配置文件与镜像分离的目的,某些容器内的配置文件非常复杂,如果用环境变量传递参数,然后写一个定制脚本生成环境变量,会比较费时费力,还容易出错。
3)共享存储,Docker Volume采用了插件机制,比较容易扩展,因此Kubernetes等第三方系统提供了更多的选择,如NFS共享存储,以及GlusterFS分布式文件系统,解决了分布式情况下的文件共享问题。

如何用Docker改造传统项目

哪些应用适合Docker化改造?

符合以下条件的应用是适合进行Docker化改造的。
1)频繁修改和升级的系统。
2)不太稳定,容易死机或者导致CPU、内存等过度消耗的应用。
3)大量使用开源技术和中间件的系统。

首先,很少变动的系统不适合进行Docker化改造,因为很少变动的系统意味着没有什么业务需求支撑,如果花很大代价将其进行改造,就是一种投资浪费。系统改动升级越频繁,Docker化的价值越大,因为Docker镜像后的系统部署和升级都很容易实现标准化和自动化,大大提升了项目组的工作效率,减少了重复劳动,Docker化以后,镜像都有版本,因此升级失败后的回滚也变得更容易和可靠。

其次,Docker容器因为共享Linux内核,所以那些与Linux底层密切相关的应用是不适合Docker化的,因为这些容器大量操作内核API,可能导致内核不稳定,如内核崩溃,此时其他容器都受牵连,这是非常严重的。所以这种情况是不能进行容器化的,必须要独立出去。而对于那些不太稳定、容易“自杀”或者容易导致CPU和内存过度消耗的应用,则很适合Docker化,因为Docker可以做CPU和内存的资源配额限制,例如,一个Docker容器只分配1GB的内存,则此容器内的进程如果申请超过1GB的内存,就可能会被Docker Engine“杀死”,如果该容器限定了使用0.5个CPU,那它永远不会得到超过0.5个CPU的使用量,因此Docker化以后,这种过度消耗资源的进程会被约束,不会导致系统问题。另外,如果某个进程不稳定并且容易“自杀”,则可以在容器化的时候设置它的启动策略为“restart=always”,这样如果Docker Engine发现这个容器“死了”,就会自动尝试重启这个容器。

Docker化改造案例

这里以一个简单的案例来说明如何完成旧系统的Docker化改造过程。图是一个传统并且有代表性的Java Web系统,这个系统是运行在Tomcat上的一个Web应用,通过JDBC访问MySQL数据库并且采用JSP展示数据。
在这里插入图片描述
Web应用可以做成一个新镜像(myweb_image),这个镜像以Tomcat镜像为基础,增加应用代码,启动容器的时候,把Tomcat的8080端口映射到宿主机的8080端口,随后就可以通过宿主机的“IP地址+8080端口”来访问应用了。

另外,MySQL有现成的可用,由于MySQL数据库文件需要高性能I/O读写,所以需要把MySQL容器的数据库文件通过Docker Volume映射到宿主机上的本地文件目录中。MyWeb容器需要访问MySQL数据库,因此需要把MySQL数据库的连接信息通过环境变量传递到容器中,而不是通过传统的读取本地配置文件的方式,这是程序中唯一要改造的地方。
在这里插入图片描述
myweb_image这个新镜像的结构,如图所示。
在这里插入图片描述

Docker高级进阶

容器基础之Cgroups

一句话来概括Cgroups的作用,就是限制进程所使用的资源配额。
Cgroups是谷歌发明的,谷歌想用最节省资源的方式将一个主机的资源被多个进程分享复用,但虚拟机是非常耗费资源的,所以虚拟机的这条路走不通,于是谷歌发明了Cgroups。

Cgroups中的一个重要概念是“子系统”,每种子系统就是一个资源的分配器,也就是资源控制器,控制资源的分配。例如,CPU子系统是控制CPU时间分配的,memory子系统是控制内存分配的。只要在系统中创建一个Cgroups子系统节点,并将控制的属性写入,然后将需要控制的进程ID写入这个节点,就实现了目标进程某种资源限制的目标。可以用多种不同的Cgroups子系统来控制同一个进程,从而实现目标进程占用的CPU、内存、I/O等资源的全面限制。

例如,使用docker run命令的时候增加参数:-m 100m表示限制该容器能使用的最大内存为100 MB。而CPU的限制比较复杂,有相对权重模式、CPU时间百分比等几种方式可以选择。

容器基础之Namespace

虽然使用Cgroups可以把容器所用的资源配额进行限制,大家可以很好地共享主机资源,避免相互影响,但此时一个主机上的不同进程仍然可以相互“看到”彼此的存在。例如,A进程可以“看到”B进程,所以A进程可以调用操作系统的API把B进程“杀掉”。另外,A进程与B进程仍然使用同样的宿主机文件系统、网络堆栈,这样仍然会产生严重的干扰问题。如何解决这个严重问题呢?这就要靠Linux Namespace的能力了。

Linux Namespace与Cgroups一样,属于Linux内核级的功能,它可以实现不同进程所访问的资源的完全隔离,包括网络栈、文件系统、进程编号、主机名、用户等所有关键资源。Namespace对不同的容器进行隔离后的效果怎么样呢?简单来说,运行在不同的Namespace下的容器,拥有完全独立的进程编号,进程ID都是从1开始的,“看不到”其他的进程,容器拥有自己独立的主机名、独立的网络栈、独立的IP地址、独立的文件系统、独立的用户,也就是说Namespace让Docker容器成为真正的“容器”。

Docker的容器原理Cgroups实现资源配额管理,Namespace实现资源隔离,两者的结合相当于虚拟机。但容器还有一个与虚拟机显著不同的特点,即容器一定是与某个进程相关的,容器内部有一个用户指定的进程以前台模式运行,当这个用户进程停止时,容器就停止了,我们不可能启动一个没有前台进程运行的容器。

Docker的分层镜像原理

分层镜像是Docker的另外一个核心特性。我们知道Docker镜像是分层结构,即由多层文件系统所组成,但从容器中进程的视角来看,这又是一个扁平化的文件系统,并且Docker的每一层镜像都是只读的,那么Docker容器又是如何做到在容器内读写文件的呢?答案很简单,Docker容器的分层文件系统在只读的镜像分层文件系统之上叠加了一层“可写层”,如图所示。
在这里插入图片描述

Docker常用命令入门

查看启动的Docker容器列表:

docker ps

容器ID(Container ID)是容器的唯一标识,可以用下面的命令进入Tomcat容器中排查问题,以及了解容器的运行情况,如查看容器的IP地址以及容器内运行的进程:

docker exec -it b3a839b2e739 bash    

我们看到容器的IP地址是不同于Docker主机上的虚拟二层网络IP地址的,容器内也只运行了一个Tomcat的主进程,同时在容器内也看不到主机上的其他进程,这就是容器隔离机制的一个表现。

容器如果暂时不用了,可以挂起(docker pause <containerId>)或者停止(docker stop<containerId>),挂起或者停止的容器可以恢复运行,而容器中的数据仍然存在,如果是永久性地不使用了,则可以删除容器(docker rm <containerId>),此时容器所占用的磁盘空间也会被清理。

有时候我们需要把一个容器持久化成镜像,即通过启动一个容器,并且在容器中执行一些Linux命令来完成某个软件的安装配置,完成以后,将其“固化”为一个新的镜像,这时我们可以用下面的命令实现:

docker commit <containerId>  newImageName

然后,这个新镜像则可以通过docker save命令导出为本地压缩包: docker save <imageName > <imageName.tar>把镜像压缩包复制到另一台机器上,就可以使用下面的命令加载到Docker本地镜像中了: docker load < <image.tar>然后用docker images命令可以列出本地所有的镜像: docker images

不再使用的镜像,可以删除(docker rmi <imageName>),如果一个镜像被某个容器使用就无法删除,即使这个容器已经停止运行,我们需要先删除对应的容器,用docker ps -a可以列出所有的容器,命令结果中会同时给出容器的状态,如运行中或者是已经结束。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值