一篇文章写三遍,去除杂质,提炼干货。这是一系列有温度的博客。
Docker 解决了什么痛点
环境配置相当麻烦,换一台机器,就要重来一次,费力费时。很多人想到,能不能从根本上解决问题,软件可以带环境安装?也就是说,安装的时候,把原始环境一模一样地复制过来。开发人员利用 Docker 可以消除协作编码时“在我的机器上可正常工作”的问题。
这个技术其实早就可以实现了,叫快照。
但是快照很大,因为它需要将当前环境下所有内容都打包起来,不论你是否需要。
Docker 之所以发展如此迅速,也是因为它对此给出了一个标准化的解决方案-----系统平滑移植,容器虚拟化技术。
Docker VS VMware
容器虚拟化技术
容器与虚拟机不同,不需要捆绑一整套操作系统,只需要软件工作所需的库资源和设置。系统因此而变得高效轻量并保证部署在任何环境中的软件都能始终如一地运行。
传统虚拟机技术是虚拟出一套硬件后,在其上运行一个完整操作系统,在该系统上再运行所需应用进程;容器内的应用进程直接运行于宿主的内核,容器内没有自己的内核且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。
每个容器之间互相隔离,每个容器有自己的文件系统 ,容器之间进程不会相互影响,能区分计算资源。
容器比虚拟机快的底层原理
1、docker有着比虚拟机更少的抽象层。
2、docker利用的是宿主机的内核,而不需要加载操作系统OS内核。
Docker 安装
这里有两个Docker的官网提供给大家:
Docker 官网:http://www.docker.com
Docker 仓库:·https://hub.docker.com
具体安装办法官网上都有,而我是在 Linux/Ubuntu 下安装的,所以就讲解如何在 Ubuntu 下安装Docker 了。
敲黑板:不要用 Ubuntu 系统推荐的 snap 方式安装!!!谁用谁知道。
安装步骤:
1.更新Ubuntu的apt源索引
$ sudo apt-get update
2.安装包允许apt通过HTTPS使用仓库
$ sudo dpkg --configure -a
$ sudo apt-get install apt-transport-https ca-certificates curl software-properties-common
3.添加Docker官方GPG key
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
4.设置Docker稳定版仓库
$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
5.更新apt源索引
$ sudo apt-get update
6.安装最新版本Docker CE(社区版)
$ sudo apt-get install docker-ce
查看安装Docker的版本
$ docker --version
检查Docker CE 是否安装正确
$ sudo docker run hello-world
docker 基本组成
- 镜像:说到镜像应该都不陌生了吧。Docker 镜像(Image)就是一个只读的模板。镜像可以用来创建 Docker 容器,一个镜像可以创建很多容器。
- 容器:Docker 利用容器(Container)独立运行的一个或一组应用,应用程序或服务运行在容器里面,容器就类似于一个虚拟化的运行环境,容器是用镜像创建的运行实例。就像是类和实例对象一样,镜像是静态的定义,容器是镜像运行时的实体。容器为镜像提供了一个标准的和隔离的运行环境,它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。
- 仓库:集中存放镜像文件的场所。仓库分为公开仓库(Public)和私有仓库(Private)两种形式。最大的公开仓库是 Docker Hub。
Docker 常用命令
设置开机启动: systemctl enable docker
如果你也像我一样常年不关机那就无所谓。
查看docker状态: systemctl status docker
列出本机所有的镜像:docker images
REPOSITORY:表示镜像的仓库源
TAG:镜像的标签版本号
IMAGE ID:镜像ID
CREATED:镜像创建时间
SIZE:镜像大小
同一仓库源可以有多个 TAG版本,代表这个仓库源的不同个版本,我们使用 REPOSITORY:TAG 来定义不同的镜像。
如果你不指定一个镜像的版本标签,例如你只使用 ubuntu,docker 将默认使用 ubuntu:latest 镜像。
下载镜像:docker pull 镜像名
如果对版本号、产家等数据都没有特殊需求的话,就直接
docker pull 镜像名。
如果有特殊需求的话,可以上 dockerhub 搜索,搜索不用登录。
dockerhub 上会有后续指引。
删除镜像:docker rmi 某个XXX镜像名字/ID
这里建议使用 docker rmi 镜像名字:[tag]
不要带 -f 呢,是给咱一次后悔的机会,万一那个镜像在用呢。
使用名字而不用 id 呢,是可能会有多个镜像共用一个 ID,后面会看到。
之所以要加 tag 呢,因为可能会有多个镜像一个名字的,前面已经看到咯。
修改镜像名:docker tag 旧镜像名:旧版本号 新镜像名:新版本号
比方说这样:
root@runoob:~# docker tag ubuntu:15.10 runoob/ubuntu:v3
root@runoob:~# docker images runoob/ubuntu:v3
REPOSITORY TAG IMAGE ID CREATED SIZE
runoob/ubuntu v3 4e3b13c8a266 3 months ago 136.3 MB
一般可以用来给镜像加前缀。没谁真的拿这个来给镜像改版本号或者改名字的,那不是弄虚作假嘛。
这样子修改过去的两个镜像的 ID 是一样的,两者存在链接关系。
运行镜像:docker run [-itd] 镜像名:[tag] [bash]
如果就唯一版本,那带不带版本号就看你意愿了。
这是标准的格式,可防止一些幺蛾子。
镜像运行起来产生了容器,可以说容器是镜像运行的进程载体。
-it 的选项是让容器显式的运行,且持久。
-d 的选项是让容器到后台自己玩去。
bash 是既然容器已经放到明面上来了,总得给人家一个工作台吧。也可以是 sh,看个人喜好。
当然了,有其他参数。-h 自己看。
查看容器:docker ps -a
加一个 -a,是可以查看所有的容器,包括已故的。因为有时候你会觉得自己明明启动了容器为什么却没看到,不要怀疑你自己,它就是启动了,不过因为各种原因死了。
比较多的原因有两种:
1、不适合后台运行的容器却没有 -it,导致它觉得自己没事干了,就下班了。
2、某些依赖配置的服务配置文件写错了,比如说咱自定义的 mysql。
还有一种是没有资源了,不过这个可能性不高就是了。
切入容器:docker exec -it 正在运行的容器 ID bash
如果你是以 -d 的方式启动了容器,但是这时候你想显式的切入容器中操作。还是比如咱的 mysql,现在容器起来了,咱得进去启动 mysql 啊是吧,咱得去建库、建表,运维啊。
这时候就用 exec 方式来,这里推荐容器 ID,当然也可以用容器名,对,可以给容器起名字。
退出而不关闭容器:Ctrl + p + q
记住这个操作,记住,只有这个操作。
我不会告诉你怎么关闭容器,怕你误操作了到时候哭都没地方哭。
如果你确定不想要一个容器了,显式删掉。
如果容器被关闭
如果容器被退出,该如何恢复?
docker start 容器id
还是可以抢救一下的,不过能不走到这一步就不要走到这一步。
删除一个容器:docker rm 容器ID
这里也不加 -f,给你一个后悔的机会。
构建镜像:docker commit
运行的虚拟机一旦出了故障,则在其之上的很多操作便都作废,要重头来过。但是聪明的人总是有的,所以虚拟机可以打快照。
那么在使用容器的时候,万一容器给出了故障,那不也意味着前功弃了一大半(留下多少,下一篇会讲解存储卷。)这就好比我基于 docker 构建了一个三主三从的 redis 集群,docker 一关机,我的集群就要重新搭建了。
构建镜像很简单,一行命令就搞定了,也不需要网络。
docker commit -m="镜像描述" -a="作者信息" 容器id 镜像名:版本号
当然,还有其他的 options 可以选择,需要的话:docker commit -h。
小 tips
为什么说 docker 容器一定要勤于备份呢,因为 docker 容器被误删的风险是要比 VMware 要高的,这也是上一篇我为什么只说用 Ctrl+p+q 退出 docker 的原因,不当的退出是会直接把整个容器都给你关掉的。
想象一个画面:你正吭哧吭哧的在你的 “铁索连环” 的容器上开发,这时候一个不长眼的在退出的时候误操作了,整个容器给你关了。哦豁,刺激,你是选择扁他一顿呢,还是扁他一顿呢,还是扁他一顿呢?
如果没有备份,那你扁他几顿都没用了。
镜像分层 与 联合文件系统
远的不说,我们再看一下上一篇里面的一张图,当时 run redis 的:
看是有五个 Pull complete 的。这说明什么?说明是下载了五个东西,最后拼出一个 redis 来。
这样有什么好处?如果积木搭好了为什么不融成一块?稍微思考思考。
UnionFS(联合文件系统):Union文件系统(UnionFS)是一种分层、轻量级并且高性能的文件系统,它支持对文件系统的修改作为一次提交来一层层的叠加,同时可以将不同目录挂载到同一个虚拟文件系统下(unite several directories into a single virtual filesystem)。Union 文件系统是 Docker 镜像的基础。镜像可以通过分层来进行继承,基于基础镜像(没有父镜像),可以制作各种具体的应用镜像。
Docker镜像加载原理
一遍没看明白可以多看两遍、
bootfs(boot file system) 主要包含 bootloader 和 kernel, bootloader 主要是引导加载 kernel, Linux 刚启动时会加载 bootfs 文件系统,在Docker 镜像的最底层是引导文件系统 bootfs。这一层与我们典型的 Linux/Unix 系统是一样的,包含 boot 加载器和内核。当 boot 加载完成之后整个内核就都在内存中了,此时内存的使用权已由 bootfs 转交给内核,此时系统也会卸载 bootfs。
rootfs (root file system) ,在 bootfs 之上。包含的就是典型 Linux 系统中的 /dev, /proc, /bin, /etc 等标准目录和文件。rootfs 就是各种不同的操作系统发行版,比如 Ubuntu,Centos 等等。
对于一个精简的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序库就可以了,因为底层直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。由此可见对于不同的 linux 发行版, bootfs 基本是一致的, rootfs 会有差别, 因此不同的发行版可以公用 bootfs。
==Docker镜像层都是只读的,容器层是可写的==。 当容器启动时,一个新的可写层被加载到镜像的顶部。 这一层通常被称作“容器层”,“容器层”之下的都叫“镜像层”。所有对容器的改动,无论添加、删除、还是修改文件都只会发生在容器层中。
镜像仓库 && 推送镜像到仓库
等到了工作岗位,你的导师会教你怎么用你们公司的镜像仓库的。
推送镜像到仓库的命令为:docker push 镜像名:[tags]
不过这里的镜像名会带一些域名,这个跟镜像仓库有关,不同的仓库会有不同的域名。一般镜像仓库的说明书会介绍,没有介绍可以问导师或同事。
压缩镜像:docker save
要保存镜像除了推送到远端仓库之外,还有一个办法,就是将镜像打包成一个压缩包。
这也是我为什么不讲镜像仓库的原因。
一般在网上你们看到的压缩镜像命令就是 docker save 镜像名:版本号
了,这样打包出来的文件还是会偏大。
我实习的第一个任务就是打包 docker 镜像,最初我也是这样操作的,但是还没打包完全部,就发现空间就被占用的差不多了。于是我便求助于我的导师,他给了我一个命令:
docker save xxx:yyy | gzip -9 > zzzzz.tar.gz
让我再去试试看大小。
要不说他是我导师呢,有经验!!!
实习也快结束了,我导师对我是真的好啊,这可羡煞我的两个小伙伴了哈哈。
导入镜像:docker load
前面压缩了镜像,是为了这里拿来导入,不然压缩它干嘛。
导入命令也很简单:
docker load < 镜像名.tar.gz
导入完之后:
docker images | grep xxxxx
上一篇忘了把 grep 带上了。那么多的镜像,真要一个一个去翻的话,估计要累瞎我的钛合金狗眼了,这招也是我导师教我的。
虚悬镜像
这个可以拿去和面试官吹牛逼,你连虚悬镜像都懂。
什么是虚悬镜像?镜像没有仓库名或没有标签。
这样的镜像会导致是后果呢?没什么后果,就是鸡肋了点而已,尸位素餐,看不顺眼。
删除所有虚悬镜像:
docker rmi $(docker images -q -f dangling=true)
一次不够就多删几次,这个命令有时候会有点问题。
容器数据卷
容器数据卷?是什么
但凡是数据,都逃不开一个最基本的问题:数据丢了怎么办?
所以就衍生出各种备份方案。
Docker 也是如此,你就不怕哪天容器让人给删了吗?
卷就是目录或文件,存在于一个或多个容器中,由docker挂载到容器,但不属于联合文件系统,因此能够绕过Union File System提供一些用于持续存储或共享数据的特性。
卷的设计目的就是数据的持久化,完全独立于容器的生存周期,因此Docker不会在容器删除时删除其挂载的数据卷。
如何挂载容器卷
其实这篇内容很简单,就讲一下什么是容器卷,以及怎么挂载,再演示一下就完了。
docker run -it --privileged=true -v /宿主机绝对路径目录:/容器内目录 镜像名
演示一:数据恢复
演示步骤:
1、启动 Ubuntu 容器
2、在启动时挂载容器卷
3、创建一个文件
4、关闭容器
5、删除容器
6、重新并启动容器并挂载容器卷
7、检查文件是否还存在
我对几条重要命令解释一下:
docker run -it --name myu --privileged=true -v /tmp/myHostData:/tmp/myDockerData ubuntu /bin/bash
这里我给它起了个名字,不起也可以,但是想到我自己都有名字,它没名字怪可怜的。
--privileged=true:Docker挂载主机目录访问。如果出现cannot open directory .: Permission denied,那就加上这个参数。
/tmp/myHostData:/tmp/myDockerData:在容器内 /tmp/myDockerData 目录下的所有文件及时同步到 宿主机内的 /tmp/myHostData 目录下,这两个路径可以是本不存在的,会自动创建。
可以看到,在我删除了原 myu 实例之后在启动容器,连目录都丢了,何况目录下的文件。
可以挂载多个目录,每次挂载都来个 -v 就好,下一章会看到。
演示二:宿主机数据传入容器
熟悉吗?我们以前使用 VMware 的时候,也都有从宿主机上传文件进 VMware 上的系统吧。
演示步骤:
1、确定容器未启动
2、在宿主机的挂载路径下新建文件
3、启动容器并挂载
4、查看文件是否出现在容器中
敲错命令了。。。mkdir 是创建目录。。。
演示步骤:
1、确定容器已启动
2、在宿主机的挂载路径下新建文件
3、查看文件是否出现在容器中
由此可见,这个容器卷可不仅仅是一个数据持久化的工具哦,它是可以双向通信的。
有个事儿忘了说,挂在之后一定要确定一下是否挂载上了,有个命令:
docker inspect 容器ID/容器名
看到有如下:
当然,我个人更喜欢直接再挂载路径下新建一个小文件,看它有没有通就知道了。
读写规则
容器卷也是可以设置读写规则的。默认是读写全开。
docker run -it --name myu --privileged=true -v /tmp/myHostData:/tmp/myDockerData ubuntu /bin/bash
等同于
docker run -it --name myu --privileged=true -v /tmp/myHostData:/tmp/myDockerData:rw ubuntu /bin/bash
如果这样写,就是只读权限:
docker run -it --name myu --privileged=true -v /tmp/myHostData:/tmp/myDockerData:ro ubuntu /bin/bash
只读权限下,容器只能从数据卷中读取数据,而不能将数据同步至数据卷中。
那有没有只写?很可惜,莫得。。。
卷的继承和共享
一个容器启动时,可以继承另一个容器的卷规则。
你可能会觉得,就那么几个字符都不愿意去 cv 一下嘛,还要去别的容器那边去继承来。
看似有点鸡肋,但是存在即合理不是吗?
毕竟我们现在容器卷就一个,看着简单,到下一篇我们把集群放上容器的时候你再看哈哈。
命令也很简单:
容器一完成和宿主机间的映射:
docker run -it --privileged=true -v /mydocker/u:/tmp --name u1 ubuntu
容器二继承容器一和宿主机间的映射:
docker run -it --privileged=true --volumes-from u1 --name u2 ubuntu
呐,就这么简单我就不再多解释了吧。
这时候如果有眼尖的人很快就会想到,那如果容器一挂了呢?容器二和宿主机之间还能正常通信么?
可以自己玩玩试试。