01 . Docker原理部署及常用操作命令
Docker的来源及构造:
容器是一种基础工具:泛指任何用于容纳其他物品的工具,可以部分或完全封闭,被用于容纳,储存,运输物品: 物品可以被放置在容器中,而容器可以保护内容物:
人类使用容器的历史有十万年,甚至可能有数百万年的历史:
# 容器类型:
# 瓶,罐,箱,篮,桶,袋
但我们重点在LXC这里;
虚拟化和容器的关系:
主机级虚拟化:虚拟化的是整个完整的物理硬件平台,比如VMware,KVM.里面跑的是与宿主机不一样的系统
两种类型:
类型一: 直接在硬件上装一个虚拟机管理器,不装宿主机,在虚拟机管理器上跑需要的操作系统:
类型二: Vmware,kvm
完整意义的操作系统: 我们自己在上面装一个内核,内核上面有一个用户空间,用户空间里面跑进程。但是运行内核并不是我们主要目的,内核核心作用在于资源分配和管理。
就是说真正在应用空间跑的应用程序才是能产生生产力的,就好比我们跑一个web服务,内核其实很少提供服务。
而出于通用目的的而设计的资源管理平台,操作系统管理内核。我们并不太需要:
但是内核也不得不有,原因在于要想在一组硬件平台跑软件程序,而现在的软件都是针对于内核调用和系统调用,库调用研发的,而不安装这些没法运行程序,所以内核看上去没多大用,但是也必不可少;
更何况,我们一旦有了用户空间后,里面运行很多进程,这些进程为了协调也需要内核。
但假设我们创建一个虚拟机,只为了运行一个nginx,tomcat,却不得不去安装内核,用户空间,再去跑tomcat,代价就有点大了。
而在这里如果我们用二级虚拟化来分析的话:
如果为了运行一个进程,就要实现两级调度和资源分派。
第一,自己虚拟机有一个内核,已经实现了一次内存虚拟化,cpu调度以及IO调度和io 管理。
但是真正虚拟机也是被宿主机内核管理一次,这个中间浪费可以想一下。
传统的主机虚拟化技术,确实能让我们一组硬件平台之上实现跨系统传进的隔离和试验,调试,资源高效利用。而带来的资源开销不容忽视的,而很多时候,我们创建虚拟机只是为了运行一个和几个附有生产责任的进程而已,为此;
减少中间层就是一个好的办法,
比如在二级虚拟化我们把虚拟机那一层给抽掉,只保留进程,但是我们加虚拟机就是为了环境隔离,比如说一台机器跑两个或者五个nginx,我们一台机器没有那么多套接字和80端口。
而我们虚拟机就是为了资源隔离,就算把里面的nginx翻江倒海,坏的也是哪一个虚拟机。
所以这里的隔离才是我们的目标,我们虽然想抽掉这一层内核,但是不能让他回到在一个锅里吃饭那个环境,所以要让每一个组进程互相不可干扰的,只不过共享一个底层资源而已。
因此我们想要的环境就是:
创建一个又一个隔离环境,我们要运行隔离的程序就让他跑在隔离环境中,内核提供的是内核空间,进程试运行在用户空间,而这里就能实现将用户空间隔离成多个用户空间,互不干扰,这里一般都有一个空间是有特权的,一般第一个。而这里众多用户空间运行的都是同一个内核,被同一个内核管理的。但是运行时候能看到的边界只能是当前用户空间的边界,虽然没有主机级虚拟化隔离那么彻底。
更重要是,这个用户空间放进程,给进程提供运行环境,并且保护内部进程不被其他进程所干扰.叫做容器.
虚拟的隔离环境管理器
一层硬件平台容器不是什么新概念,最早出现在2000年,当年叫jail,监狱,监禁之意,里面就像一个沙箱,就算里面那个程序bug,有了故障,异常行为,也无法影响这个容器之外的程序运行,这也是jail初衷。
容器并非起源于 Linux,但开源世界的最精彩之处就在于借鉴、修改和改进,容器也不例外.
但这个概念非常有吸引力。
2001 年,通过 Jacques Gélinas 的 [VServer 项目,]隔离环境的实施进入了 Linux 领域。正如 Gélinas 所说,这项工作的目的是“在高度独立且安全的单一环境中运行多个通用 Linux 服务器 [sic]。” 在完成了这项针对 Linux 中多个受控制用户空间的基础性工作后,Linux 容器开始逐渐成形并最终发展成了现在的模样。
一般来讲,就算一个程序被远程劫持在里面搞起破坏,他的边界也仅仅是哪个容器的边界,最初只是为了应用的安全运行。
后来有人把这个技术山寨到Linux平台上,叫Vserver(chroot),在一个子目录定义一个根,让里面的程序以为他就是根。
chroot他能实现的你看上去的那层空间,底层还是同一个内核,进程运行是特权模式还是普通模式,表面很简单,真正实现起来涉及东西至少要有;
Docker容器vs传统虚拟机
虚拟机 | 容器 | |
---|---|---|
占用磁盘空间 | 非常大,GB级 | 小,MB甚至KB |
启动速度 | 慢,分钟级 | 快,秒级 |
运行形态 | 运行于Hypervisor上 | 直接运行在宿主机内核上 |
并发性 | 一台宿主机上十几个,最多几十个 | 上百个,甚至数百上千个 |
性能 | 逊于宿主机 | 接近宿主机本机进程 |
资源利用率 | 低 | 高 |
实现容器机制的六点
在一个单独用户空间当中,它主要目的是隔离使用,要让里面进程以为他就运行在底层内核之上,有六点:
# 1. 主机名和域名: UTS
# 2. 根文件系统 : MOUNT
# 3. 应该有自己的IPC,进程间的专用通道,如果能让两个用户空间进程能互相通信,也没有意义了,共享内存,拒绝跨用户空间。 IPC
# 4. 给每个用户空间做一个假的init,只能有一个,pid,1,每个系统正常来说只有一个,为了隔离,给每个空间伪装一个。
# 5. 必须给每个用户伪装一个root,实现效果就是在真正系统只是普通用户,在他所属用户空间里面可以为所欲为,让进程看起来是root.
# 6. IP地址,互相通信,而在内核级别,tcp协议栈只有一个,
为了容器机制的实现,Linux内核对着六种明成空间原生支持,namespaces。直接通过系统调用对外进行输出,
直到今天,所谓的容器就是靠六个namespaces和chroot来实现的。
听上去容易,这六种名成空间有一个是到内核3.8才实现的,所以要想完美使用容器,要内核版本3.8以上。centos6天然排除在外.
namespace | 系统调用参数 | 隔离内容 | 内核版本 |
---|---|---|---|
UTS | clone_newuts | 主机名和域名 | 2.6.19 |
IPC | clone_newipc | 信号量、消息队列和共享内存 | 2.6.19 |
PID | clone_newpid | 进程编号 | 2.6.24 |
Network | clon_newnet | 网络设备、网络栈、端口等 | 2..29 |
Mount | clone_newns | 挂载点(文件系统) | 2.4.19 |
User | clone_newuser | 用户和用户组 | 3.8 |
容器级虚拟化:
资源限制: 我们可以在整体资源做比例性分配,也可以在单一用户空间做核心绑定,你只能使用几个核;不然的话一个进程内存泄露整个系统都蹦了;或者一个进程占用了绝大部分CPU.
内存是压缩不了的,多一点都不能,一给就收不回来了,因为内存是压缩不了的;
实现这个Control Cgroups
blkio: # 块设备IO
cpu: # CPU
cpuacct: # CPU资源使用报告
cpuset: # 多处理器平台上的cpu集合
devices: # 设备访问
freezer: # 挂起或恢复任务
memory: # 内存用量及报告
perf_event: # 对cgroup中的任务进行统一性能测试
net_cls: # cgroup中的任务创建的数据报文的类别;
划分成组后,在不同组内进行资源分配,组还可以再分。
即使有了chroot,nameespaces, control cgroups,容器的隔离能力比起主机级别虚拟化依然差很多,因为容器毕竟属于同一个内核,而主机级虚拟化那么好,因为内核本来就是天然的隔离,因此为了加强安全性,防止里面的进程通过漏洞绕过边界伸到别的用户空间,通过selinux各种安全机制加强容器的边界,为了支撑容器做的更加完善。
容器实现就是靠chroot,nameespaces,cgroups核心技术实现,而这些在内核已经实现了。
容器本来是靠jail启发有了vserver,后来为了让这种容器更加易用,
因为创建容器要自己写代码,系统调用,克隆等来实现的,但麻烦程度很大。
所以我们最好把实现容器的这种功能做成一种工具,极大的简化容器的使用,于是就有了LXC的解决方案,
LXCx container,最早一批真正把完整的容器技术用一组简易使用的工具和摸板来极大的简化了容器的使用方案。
lxc-create : 创建一个容器,template,摸板一组脚本,
创建一个明成空间后,这脚本自动执行后,自动实现安装过程,指向了你所打算创建哪一类明成空间的系统发行版所属的仓库,从仓库中镜像下载过来,安装,生成这个新的名成空间,就可以像虚拟机一样使用。
所有的明成空间都这样实现,而lxc就靠这样这一组工具包快速实现创建空间,利用模板完成内部所需要各种文件的安装,同时还有些工具可以通过chroot切换来切换去,于是我们就可以愉快的使用了,跟虚拟机没多大区别;
lxc在容器技术推广绝对功不可没,但依然有很高门槛:
# 1. 学好lxc命令
# 2. 必要时候需要定制模板,
# 3. 如果在里面运行Mysql,如果服务在里面出现故障,如何迁移数据;
# 4. 批量创建也不容易
虽然极大的简化了容器使用,但是在复杂程度没有多大降低的,更何况他的隔离性没有虚拟机那么好,
好处是性能资源方面的节约,让每一个进程直接使用宿主机的性能。
于是后来出现了docker:
lxc增强版,也不是容器,容器的一个易用前端工具。
lxc批量创建挺难,docker就在这个上面找突破点,docker早期核心就是lxc,他是lxc的二次封装发行版。
功能上利用lxc做容器管理引擎,创建容器时不再使用模板安装,而是使用一种镜像技术,
我们尝试着把一个操作系统用户空间所需要用到的所需要所有组件事先准备编排好,编排好后整体打包成一个文件,叫做镜像文件,这个镜像文件是放在一个集中统一的仓库中的,我把大家都会用到的最小化centos,ubuntu分别放在仓库内;
而在这里我们在这个最小化系统里面先装好源码nginx,再打包成一个镜像,也放入这个仓库中,当启动创建容器时候,需用镜像,把镜像拖到本地,基于镜像启动容器。所以docker极大的简化了容器使用程度;
比如说你想跑一个tomcat,nginx直接docker run就完成了
为了易于管理,docker还采用另外一种方式,在一个用户空间当中,我们尝试只运行一组进程或一个进程,我们目的就是为了运行一个有生产力的程序,比如我们在主机上要跑tomcat,nginx,nginx运行在nginx容器中,tomcat运行在tomcat容器中,二者用容器间通信逻辑进行通信,所以以后一个容器只运行一个进程,这也是docker的目的;
lxc就是当虚拟机使用,到时候管理极为不便;
而docker这里实现功能:
不用容器,一个内核之上运行各种进程 ,大家属于同一个用户空间,共享同一组主机名,用户,ipc,pid,在同一个可视范围内,如果一个黑客劫持一个进程以他做跳板可以威胁到其他进程
而docker,把他们给圈起来了,彼此间不可见,而且这个容器只为了这一个进程,最小化定义的;
坏处,占用更多空间,如果服务坏了,调试工具只针对一个容器有效,而如果加上调试工具违反了唯一进程目的,所以带来问题:本来调试一个进程极为简单,可能没有工具:
而要想调试得突破他的边界:
好处;删除了不影响别人:
给运维带来极大不便利,给开发带来巨大好处,分发容易了,一次编写到处运行。现在环境都是异构的,
centos5,6,7/Ubuntu/deepin/suse/kali/redhat/AIX
要开发一个软件,要开发每一种平台的软件,各自组织一个团队开发,只需要打包一个镜像,不管你是windows,linux,unix跟内核没关系,跟操作系统没关系,他有自己的明成空间
跟java类似这种效果,但是java有很多版本,6,7,8
以前部署需要发布,变更,故障处理
有了镜像,直接one,就行了,但是还需要接路由器和调度器,如果有一个容器编排工具,之间run结束,甚至连run都不需要你手动执行;
像以前的java容器只能支持java一种语言;
而docker不会管你是什么语言;
随之带来运维的问题,发布操作用编排工具来实现,docker必须要使用编排工具来管理,不用的话手动管理比我们直接管理应用程序更麻烦,增加我们运维环境复杂度;但确实是降低开发压力;
运维工作就是维稳,以往调试很容易,而容器可能没有调试工具,这么一来就意味着,我以后做镜像需要为每一个镜像自带调试工具,这么一来意味着做镜像需要自带一些工具,以前能共享的,现在不能,但他们却是隔离的;
docker还有一个好处: 批量创建,他创建容器采用
分层构建,联合挂载;使得我们以后镜像分发没有那么庞大,比如说我在一个系统上运行三个容器,都是基于底层centos构建,在这里只用一个centos基础镜像,三个上层nginx,tomcat,mariadb,底层镜像是共享的,底层镜像是只读的,要想改,在每一层联合挂载镜像栈最顶层额外附加一个新层,这个层才是能读能写的;
迁移很困难,如果要把容器迁移到其他地方,但这里是有状态的,真正在使用容器时,不会在本地存储有效数据,他会在文件系统上共享存储,而用户存储数据,这个服务不小心宕掉了,再找一个主机重新数据加载就行了,再访问数据还在,所以数据脱离宿主机,脱离容器而持久,容器可以丢到任何主机上;
存储需要一个外置持久性存储,程序是由指令+数据组成,
把容器当进程使用,启动一个容器,就是为了运行一个进程,进程一终止,把容器删了,不要了,下次重新创建,重新挂载
容器不需要持久,容器和进程一样有生命周期,从创建而开始,从停止到结束,跟我们主机都没多大关系,可以运行在任何主机上;
在docker之上建设一个层次,看那个主机更闲,来个调度法则,如果需要持久数据,给个web存储空间,挂载上去存储数据,一旦任务结束直接容器一删,结束,这个组件能帮我们把要启动容器调度于整个底层集群环境中某一个docker主机之上,当要构建lnmp,谁先启动,docker就解决不来这种功能,我们需要一个docker基础之上,能够把这种应用程序依赖关系,从属关系,隶属关系反映在启动关闭时的次序和管理逻辑中,这种功能叫容器编排工具;
# 在docker之后出现了各种编排工具,
machine+swarm+compose: # compose只能单机编排,
mesos: # 统一资源调度和分配的 + 实现编排需要加上 marathon
kubernetes -- > k8s
google使用容器有十几年历史了,据说每一周销毁和新建容器多达几十亿。
docker因缘巧合摸到了这个门道,并且做成开源软件,谷歌就坐不住了,自己本来做为独门武器,那小子居然找到一种办法还公开所有人使用,本来我最有话语权,秘而不宣藏起来,这样才能称为独门武器,必要时候必杀技;
但是docker已然独霸话语权了,好在docker也不是铁板一块,后来出来出来另一个公司,谷歌就在后面大力扶植反对派,后来发现难以跟docker抗衡,上不来台面;
容器编排工具在谷歌已经跑了十几年了,该踩的坑都踩的差不多了;
docker三两年做不到;
于是kubemetes横空出世,占据了百分八十的市场,成为市场的标准,还成立了CNCF 容器标准组织,docker有一个问题,docker在编排上没有经验,义无建树,技术没走好,没有吸引更多的土豪进来投资,上市做独角兽,虽然互联网上火的不要不要的,但是无法变现,决定,把开源版拆分为企业版和社区版,将社区改名,把docker流量引入企业版,后来改名叫moby,把所有docker引入企业版,
之所以这样 docker是因为一家商业公司,谷歌在做k8s时候向大家表明我是没有任何意图的,于是把k8s源代码捐给了cncf,cncf是由很多组织联合成立的,主导权不再属于谷歌,属于IMB,微软等等,不会被人说想把k8s私有化,一年四个版本发布,go研发的
后来docker研发了一个容器引擎,libcontainer,替换了lxc,docker已经被cncf挟持了,cncf自己玩,把docker排除在外,如果以后容器要走下去肯定要开源,标准化,谁来负责标准化,cncf就可以做,定义标准,但这样太欺负docker了,所以给docker个机会,你来定标准化,同时做一款开源软件。
现在新的docker引擎已经是runC了,
虽然k8s支持很多种容器,但常见的还是docker+k8s;
镜像管理基础
docker架构形式:只考虑单机之上,整体架构是这样的:
整体是一个dockerhosts: docker server端
dockerhost 就是运行有docker进程(守护进程)的主机,被称为docerhost,dockerserver;
docker接收到创建和启动容器命令后,将在本地创建容器,一个dockerhost上可以启动多个容器,此处我们可能运行的分别不属于各种不同程序的容器,而容器取决于镜像来实现,如果本地没有,dockerdaemon自动链接到registries上,而后从中获得镜像后,先存储到本地一个能够专门存储所谓镜像存储空间中,要求得是特殊并且特殊的文件系统,overlay2,
这里面可能有多个镜像文件,镜像本身是只读的,而且镜像在registries放的时候仓库名就是应用程序名,而后仓库内可以放多个镜像,而且这些镜像通常属于同一个应用程序不同版本,我们用标签来识别;
docker镜像:
含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动docker容器:
采用分层构建机制,大体分为两层,最底层为bootfs,其之为rootfs
真正让用户拿来去构建用户空间并运行进程容器的是rootfs;
bootfs: 用于系统引导的文件系统,包括bootloader和kernel ,容器启动完成后会被卸载以节约内存资源
这里的kernel仅仅用于引导并启动一个用户空间,启动完之后就没有了以节约内存资源,毕竟很有可能我们用户空间跟底层内核还是有一点不同之处的,向上就是rootfs了;
# rootfs: 位于bootfs之上,表现为docker容器的根文件系统;
# rootfs: 位于bootfs之上,表现为docker容器的根文件系统
传统模式中,系统启动时,内核挂载bootfs时会首先将其挂载为只读模式,完整性自检完成后将其重新挂载为读写模式;
docker中,rootfs由内核挂载为“只读”模式,而后通过“联合挂载技术”额外挂载一个“可写层”;
这里的分层构建
我们做一个apache镜像,运行httpd镜像,我们很有可能在一个底层的非常基础系统镜像之上一个纯净最小化centos版本,在他之上添加一个编辑器,相当于vim,除此之外添加一个httpd,每添加一个软件都是一个独立的层次,这里是三层,底下bootfs那一层在容器启动时,一旦被引导了完了rootfs时候再卸载并移除,不是删除文件,而是从内存中移除;
而这时候底层只有三层,base image 用来构建一个系统基本组成,/bin;如果要用到额外一个工具,需要额外安装;
但是对于我们镜像来讲,底层镜像是不会动的,额外安装一个vim 他会在里面额外生成一个vim 层,再装个nginx,生成个nginx层;叠加在一起挂载的,这三层都是只读的,因此,对于一个容器来讲,仅能在writoble上能写,而且如果删除容器,writable也会被删除;
含有启动容器所需要的文件系统及其内容,因此,其用于创建并启动docker容器:
采用分层构建机制,大体分为两层,最底层为bootfs,其之为rootfs
真正让用户拿来去构建用户空间并运行进程容器的是rootfs;
bootfs: 用于系统引导的文件系统,包括bootloader和kernel ,容器启动完成后会被卸载以节约内存资源
这里的kernel仅仅用于引导并启动一个用户空间,启动完之后就没有了以节约内存资源,毕竟很有可能我们用户空间跟底层内核还是有一点不同之处的,向上就是rootfs了;
镜像分层构建和联合挂载依赖于文件系统的支撑
早起用到的是Aufs,高级多层统一文件系统:
最早被docker拿来用于实现联合挂载的Linux文件系统,
aufs是之前的unionfs重新实现,重写后依然很烂,三万行代码,一个ext4才四五千代码,这是要被整合进内核的,因此被申请时,次次被拒绝,一直到不申请,aufs一直都不是内核中自有的文件系统,想用需要向内核打补丁,centos不会干这种事情,因为他们以保守稳定为初衷,ubuntu是很早一批把aufs打包进内核,早些时候想要使用docker需使用ubuntu.
aufs的竞争产品是overlayfs(),后者自从3.18版本才开始被合并到linux内核;
docker的分层镜像,除了aufs,docker还支持btrfs,devicemapper和vsf等;
docker默认是aufs; centos7用的是devicemapper;在试用联合挂载很差,不稳定,因为它使用target driver;
比较成熟的支持的文件系统必须要能够是docker info当中的overlay2,xfs,overlay2是一种抽象的二级文件系统,他需要建立在本地文件系统之上;
构建镜像时,镜像做好之后,应该有一个统一存储的位置,叫做doceker registry
启动容器时,docker daemon会试图从本地获取相关的镜像: 本地镜像不存在时,将其从registry中下载该镜像并保存到本地;
如果我们没有特别指定,那么他就是registry,如果要指向别的registry我们必须在镜像的访问路径当中给明服务器地址;否则访问默认的registry,除非我们修改默认;
Docker Registry分类
# Registry用于保存docker镜像,包括镜像的层次结构和元数据
# 用户可自建Registry,也可以使用官方的docker hub
分类:
# Sponsor Registry 第三方的registry,供客户和docker社区使用
# Mirror Registry 第三方的registry,只让客户使用
# Vendor Registry 由发布docker 镜像的供应商提供的registry提供给买了红帽系统使用
# Private Registry # 通过设有防火墙和额外的安全层的私有实体提供的registry 不消耗互联网带宽,尤其是本地大规模容器自建本地registry;
OCI
由linux基金会于2015年6月创立
旨在围绕容器格式和运行时制定一个开放的工业化标准
RunC: 无论是客户端还是服务端,都由docker一个程序提供,他有很多子程序,他可以监听在一个套件字之上;
docker有三种类型套接字,
docker启动容器就是基于镜像启动,在镜像基础之上,为一个容器创建一个专用可写层;
containers:容器,
lmages: 镜像 镜像来自于Registry,注册表,可以称为docker的镜像仓库,默认就是docker hub,默认本地是没有的,镜像是分层构建的,所以下载到本地后,可以共享多个上层镜像使用,因为镜像是只读的,所以启动容器就是基于镜像来启动,在镜像基础上为一个容器创建一个专用的可写层,从而来启动这个容器。
所以这里镜像也需要在docker本地存储,因此这有专门的仓库来放镜像,而镜像拥有几十万之多,所以放到一个公共的仓库,需要时候拉取过来加载到本地,这里的协议是http/https,默认是加密的,需要明确定义成不安全才可以使用;
docker的运行过程中尤其是创建容器时可能有一点慢,原因是他要下载一次镜像,取决于他的宽带;
因为服务器在国外, 为了能使加速访问,docker在大陆这边做了一个docker镜像服务器,docker.cn,加速不太好,可以使用阿里,科大,所以要想使用docker,必须要能接入到互联网。
docker镜像是分层构建的
仓库: 一个docker拥有两重功能,第一,他提供镜像提供的仓库,第二,他还提供用户来获取镜像时的认证等功能,还提供了当前服务器所有可用镜像的索引;
所以镜像也会有应用到不同程序版本的镜像,为了让镜像跟应用程序版本有一定的关联,给镜像外面加了一个标签,仓库名+标签才能唯一标识一个镜像。如果只给了仓库名,那就是默认最新版;一个镜像可以有多个标签,在仓库名+标签外面加上 stable最新版,稳定版什么的;
镜像是静态的;
# 容器: 动态,生命周期,类似于程序;
#任何images,networks,volumes,plugins可以支持增删改查的,因为他们都是对象;
# 依赖的基础环境:
# 64 bits CPU
# Linux KERNEL 3.10+ CentOS6也支持docker,2.6.32,打了补丁;
如果我们要是 就使用docker,如果我们要使用在这的仓库就下载docker,区别EE和CE;
Docker 从 1.13 版本之后采用时间线的方式作为版本号,分为社区版 CE 和企业版 EE,社区版是免费提供给个人开发者和小型团体使用的,企业版会提供额外的收费服务,比如经过官方测试认证过的基础设施、容器、插件等。
社区版按照 stable 和 edge 两种方式发布,每个季度更新 stable 版本,如 17.06,17.09;每个月份更新 edge 版本,如17.09,17.10。
Docker的部署
初始化环境
init_security() {
systemctl stop firewalld
systemctl disable firewalld &>/dev/null
setenforce 0
sed -i '/^SELINUX=/ s/enforcing/disabled/' /etc/selinux/config
sed -i '/^GSSAPIAu/ s/yes/no/' /etc/ssh/sshd_config
sed -i '/^#UseDNS/ {s/^#//;s/yes/no/}' /etc/ssh/sshd_config
systemctl enable sshd crond &> /dev/null
rpm -e postfix --nodeps
echo -e "\033[32m [安全配置] ==> OK \033[0m"
}
init_security
init_yumsource() {
if [ ! -d /etc/yum.repos.d/backup ];then
mkdir /etc/yum.repos.d/backup
fi
mv /etc/yum.repos.d/* /etc/yum.repos.d/backup 2>/dev/null
if ! ping -c2 www.baidu.com &>/dev/null
then
echo "您无法上外网,不能配置yum源"
exit
fi
curl -o /etc/yum.repos.d/163.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo &>/dev/null
yum clean all
timedatectl set-timezone Asia/Shanghai
echo "nameserver 114.114.114.114" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
echo -e "\033[32m [YUM Source] ==> OK \033[0m"
}
init_yumsource
安装必要系统工具和软件源
# 安装一些必要的系统工具
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
# 添加软件源信息
# docker 官方源
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# 阿里云源
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
安装并启动Docker-ce
# 安装前可以先更新 yum 缓存:
sudo yum makecache fast
# CentOS7安装 Docker-ce
yum -y install docker-ce # CentOS 中安装
apt-get install docker-ce # Ubuntu 中安装
pacman -S docker # Arch 中安装
emerge --ask docker # Gentoo 中安装
# 如果想安装特定版本的Docker-ce版本,先列出repo中可用版本,然后选择安装
yum list docker-ce --showduplicates |sort -r
Loading mirror speeds from cached hostfile
Loaded plugins: fastestmirror
Installed Packages
docker-ce.x86_64 3:19.03.4-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.4-3.el7 @docker-ce-stable
docker-ce.x86_64 3:19.03.3-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.2-3.el7 docker-ce-stable
docker-ce.x86_64 3:19.03.1-3.el7 docker-ce-stable
yum install docker-ce-<VERSION STRING>
# 选择安装 docker-ce-18.06.1.ce
yum install docker-ce-18.06.1.ce
# Docker镜像加速
# 没有启动/etc/docker目录不存在,需要自己创建,docker启动也会自己创建
# 为了期望我们的镜像下载快一点,应该定义一个镜像加速器,加速器在国内
mkdir /etc/docker
vim /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
# 启动Docker后台服务
systemctl start docker && systemctl enable docker
systemctl daemon-reload # 守护进程重启
# 通过运行hello-world镜像,验证是否正确安装了docker,或者通过查看版本
docker run hello-world
docker version
Client: Docker Engine - Community
Version: 19.03.4
API version: 1.40
Go version: go1.12.10
Git commit: 9013bf583a
Built: Fri Oct 18 15:52:22 2019
OS/Arch: linux/amd64
Experimental: false
CentOS/RHEL的用户需要注意的事项
在 Ubuntu/Debian 上有 UnionFS 可以使用,如 aufs 或者 overlay2 ,而 CentOS 和 RHEL 的内核中没有相关驱动。因此对于这类系统,一般使用 devicemapper 驱动利用 LVM 的一些 机制来模拟分层存储。这样的做法除了性能比较差外,稳定性一般也不好,而且配置相对复 杂。Docker 安装在 CentOS/RHEL 上后,会默认选择 devicemapper ,但是为了简化配置, 其 devicemapper 是跑在一个稀疏文件模拟的块设备上,也被称为 loop-lvm 。这样的选择是 因为不需要额外配置就可以运行 Docker,这是自动配置唯一能做到的事情。但是 loop-lvm 的做法非常不好,其稳定性、性能更差,无论是日志还是 docker info 中都会看到警告信 息。官方文档有明确的文章讲解了如何配置块设备给 devicemapper 驱动做存储层的做法,这 类做法也被称为配置 direct-lvm 。
除了前面说到的问题外, devicemapper + loop-lvm 还有一个缺陷,因为它是稀疏文件,所 以它会不断增长。用户在使用过程中会注意到 /var/lib/docker/devicemapper/devicemapper/data 不断增长,而且无法控制。很多人会希望删 除镜像或者可以解决这个问题,结果发现效果并不明显。原因就是这个稀疏文件的空间释放 后基本不进行垃圾回收的问题。因此往往会出现即使删除了文件内容,空间却无法回收,随 着使用这个稀疏文件一直在不断增长。 所以对于 CentOS/RHEL 的用户来说,在没有办法使用 UnionFS 的情况下,一定要配置 direct-lvm 给 devicemapper ,无论是为了性能、稳定性还是空间利用率。 或许有人注意到了 CentOS 7 中存在被 backports 回来的 overlay 驱动,不过 CentOS 里的 这个驱动达不到生产环境使用的稳定程度,所以不推荐使用。
标准化开发测试和生产环境
对于大部分企业来说,搭建PaaS既没有那个精力,也没那个必要,用Docker做个人的sandbox用处又小了点。可以用Docker来标准化开发、测试、生产环境。
Docker占用资源小,在一台E5128G内存的服务器上部署100个容器都绰绰有余,可以单独抽一个容器或者直接在宿主物理主机上部署samba,利用samba的home分享方案将每个用户的home目录映射到开发中心和测试部门的Windows机器上。
针对某个项目组,由架构师搭建好一个标准的容器环境供项目组和测试部门使用,每个开发工程师可以拥有自己单独的容器,通过 docker run -v 将用户的home 目录映射到容器中。需要提交测试时,只需要将代码移交给测试部门,然后分配一个容器使用 -v加载测试部门的 home目录启动即可。这样,在公司内部的开发、测试基本就统一了,不会出现开发部门提交的代码,测试部门部署不了的问题。
测试部门发布测试通过的报告后,架构师再次检测容器环境,就可以直接交由部署工程师将代码和容器分别部署到生产环境中了。这种方式的部署横向性能的扩展性也极好。
Docker镜像使用
管理命令
Docker --help
container 管理容器
image 管理镜像
network 管理网络
命令:
attach 介入到一个正在运行的容器
build 根据 Dockerfile 构建一个镜像
commit 根据容器的更改创建一个新的镜像
cp 在本地文件系统与容器中复制 文件/文件夹
create 创建一个新容器
exec 在容器中执行一条命令
images 列出镜像
kill 杀死一个或多个正在运行的容器
logs 取得容器的日志
pause 暂停一个或多个容器的所有进程
ps 列出所有容器
pull 拉取一个镜像或仓库到 registry
push 推送一个镜像或仓库到 registry
rename 重命名一个容器
restart 重新启动一个或多个容器
rm 删除一个或多个容器
rmi 删除一个或多个镜像
run 在一个新的容器中执行一条命令
search 在 Docker Hub 中搜索镜像
start 启动一个或多个已经停止运行的容器
stats 显示一个容器的实时资源占用
stop 停止一个或多个正在运行的容器
tag 为镜像创建一个新的标签
top 显示一个容器内的所有进程
unpause 恢复一个或多个容器内所有被暂停的进程
Docker服务管理
service docker start # 启动 docker 服务,守护进程
service docker stop # 停止 docker 服务
service docker status # 查看 docker 服务状态
chkconfig docker on # 设置为开机启动
systemctl stop docker
systemctl start docker
systemctl enable docker
systemctl daemon-reload # 守护进程重启
镜像管理
获取镜像
# 镜像可以看做我们平时装系统的镜像,里面就是一个运行环境
# Docker Hub上有大量的高质量镜像可以用,这里就说一下怎么获取这些镜像,
# 从镜像仓库获取镜像的命令是docker pull,其命令格式为:
docker pull [选项] [Docker Registry 地址[:端口号]/]仓库名[:标签]
docker search centos # 搜索docker官方提供的centos镜像
docker search centos --filter=stars=100 # 查找stars数至少为100的镜像
docker pull centos # 默认从官网拉取
docker pull centos:7.7.1908 # 默认拉取centos8,需要指定版本才能下载7.
docker pull daocloud.io/library/centos # 从daocloud拉取
docker dao pull centos # 从daocloud拉取,国内仓库
注意
我们使用docker image ls时候会发现,镜像体积的所占用空间在Docker Hub上看到的镜像大小不同,比如nginx镜像在docker hub官网上是50多兆,而把他pull下来就变成一百多兆了,这是因为docker hub所显示大小是网络传输中更关心的流量大小,而docker image ls显示的是镜像下载到本地展开后的各层所占空间的综合,因为镜像下载到本地后,更关心的是磁盘空间占用的大小.
另一个需要注意问题是,docker image ls列表中的镜像体积综合并非是所有镜像实际硬盘消耗,由于Docker镜像是多层存储结构,并且可以继承、复用,因此不同镜像可能因为使用相同的基础镜像,从而拥有共同的层,由于Docker使用UnionFS,相同的层只需要保存一份即可,因此实际占用硬盘空间很可能比这个列表镜像大小的总和小的多.
查看镜像
docker system df # 查看镜像、容器、数据卷所占用的空间.
docker images # 查看已下载的镜像
docker rm image_id # 删除镜像,指定镜像id
docker images # 查看已下载的镜像
docker images -q # 只查看所有镜像的id
docker inspect imageID # 查看镜像详情
删除镜像
docker rm image_id # 删除镜像,指定镜像id
docker rmi RepositoryName --force
# 删除镜像,指定镜像名,<仓库名>:<标签> --force镜像在使用中强制删除
# 如果镜像正在被未运行的容器使用,则需要强制删除,但是如果正在被运行的容器使用,则强制删除也无法删除
docker image ls -a
# 这样会看到很多无标签的镜像,这些无标签镜像很多都是中间层镜像,
# 是其他镜像所需要依赖的镜像,这些无标签镜像不应该删除,否则会导致上层镜像因为缺失依赖而出错,
# 实际上也没必要删除,因为相同的层只会存一遍,而这些镜像是别的镜像的依赖,
# 因此并不会因为他们被列出来而多存了一份,无论如何你也会需要他们,
# 删除那些依赖他们的镜像,这些中间层镜像也会被连带删除.
# 删除所有仓库名为redis的镜像:
docker image rm $(docker image ls -q redis)
# 删除所有镜像
# none 默认为 docker.io
docker rmi $(docker images | grep none | awk '{print $3}' | sort -r)
docker rmi $(docker images -q)
容器管理
启动容器有两种方式,一种基于镜像新建一个容器并启动,另外一个是将在终止状态(stopped)的容器重新启动.
因为docker的容器太轻量级了,很多时候用户都是随时删除和重建.
创建容器
# 容器就像是一个类的实例(比如一个基于CentOS7镜像启动的虚拟机)
# 连接进行进入命令行模式,exit命令退出。
docker run -t -i nginx:latest /bin/bash
-i # 交互式操作,让容器的标准输入保持打开.
-t # 让docker分配一个伪终端(pseudo-tty)并绑定到容器的标准输入上
nginx:latest # 基于centos构建的nginx镜像
/bin/bash # 放在镜像后的命令,这里我们希望有个交互式shell,因此用的是/bin/bash
# 当我们基于镜像启动一个实例的时候,此时他就是容器了.
# 就好比CentOS7.iso镜像和已经运行了的CentOS7虚拟机一样.
# 同一个镜像可以启动多个容器
# 创建运行容器且连接到容器
docker run -it --rm -d --cidfile="id.txt" centos /bin/bash
-i # 捕获标准输入输出,保持交互式的意思
-t # 分配一个终端或控制台,每一个控制台都要伴随一个shell
--rm # 退出时就删除该容器,默认情况下,每个容器在退出时,他的文件系统会保存下来,这样一方面有利于调试,
# 因为可以通过查看日志等方式来确定最终状态;另一方面,也可以报错容器所产生的数据,
# 如果仅仅需要短暂的运行一个容器,且不需要保存容器中的数据,
# 就可以在exit容器时自动清理掉容器及产生的数据,但此选项不能与-d共用
/bin/bash # 容器运行起来之后运行的程序,也可以是任何的命令,/bin/echo hello
--cidfile # 指定容器运行之后container长id的存放文件位置
-d # 如果不使用-d参数运行容器,容器会把输出的结果(STDOUT)打印到宿主机上面,
# 如果使用了-d参数会返回一个id,也可以通过docker ps 查看容器信息.
# 要获取容器的输出信息,可以通过docker container logs命令查看
# 容器能否长久运行是和docker run指定的命令有关,和-d参数无关.
当利用docker run来创建容器时,Docker在后台运行的标准操作包括:
# 1. 检查本地是否有指定的镜像,不存在就从公有仓库下载
# 2. 利用镜像创建并启动一个容器
# 3. 分配一个文件系统,并在只读的镜像层外面挂载一层可读写层.
# 4. 从宿主主机配置的网桥接口中桥接一个虚拟接口道容器中去.
# 5. 从地址池中配置一个ip地址给容器.
# 6. 执行用户指定的应用程序.
# 7. 执行完毕后容器被终止.
Docker容器服务管理
docker start my-nginx # 启动一个已经存在的容器
docker restart my-nginx # 重启容器
docker stop my-nginx # 停止运行一个容器
docker kill my-nginx # 杀死一个运行中的容器
docker rename my-nginx new-nginx # 重命名容器
docker rm new-nginx # 删除容器
docker stop $(docker ps -q) & docker rm $(docker ps -aq) # 停掉所有容器并删除
docker container prune # 删除所有处于终止状态的容器.
docker logs [containerID/Names] # 查看日志
docker logs my-nginx # 查看 my-nginx 容器日志
# 使用docker exec命令进入一个已经在运行的容器
docker exec -it [containerID/Names] /bin/bash # 进入容器
docker attach 7968b44369 # 会附加该容器的标准输出到当前命令行
# 启动状态的容器,执行任务
# 通过exec命令可以创建两种任务:后台型任务和交互型任务
# 后台型任务:docker exec -it test /bin/bash
# 交互型任务:docker attach 7968
docker run centos echo "hello world" # 在docker容器中运行hello world!
docker run centos yum install -y wget # 在docker容器中,安装wget软件
docker ps # 列出包括未运行的容器
docker ps -a # 查看所有容器(包括正在运行和已停止的)
docker ps -a -q # 查看所有容器的ID
docker ps -s # 查看容器使用了多少内存
docker ps -qf status=running # 查看某种状态的容器ID
docker ps -l # 列出最近一次启动的容器
docker inspect 7657b3785bcf
# 查看容器详细配置信息,包含容器名,环境变量,运行命令,主机配置,网络配置,数据卷配置等,json格式;
docker inspect -f {{.State.Pid}} 44fc0f0582d9 # 获取id为 44fc0f0582d9 的PID进程编号
docker inspect --format '{{.Config.Image}}' 7657b3485 # 获取当前运行镜像版本
docker inspect --format='{{.NetworkSettings.IPAddress}}' 7657b3485 # 获取当前运行镜像的IP地址
# 打印该容器输出
docker run -it -d --name test centos /bin/bash -c "while true; do echo hello world;sleep 2;done"
docker logs test
# 监控容器运行
docker logs container_id/container_name
--tail: # 选项可以指定查看最后几条日志
-t: # 选项则可以对日志条目附加时间戳
-f: # 选项可以跟踪日志的输出,直到手动停止
# 运行远程机器上的容器
docker run -it -d -h 39.108.140.0 daocloud.io/centos:7
# 断开容器
# 断开与容器的连接,并且关闭容器
[root@7968b4436989 /]# exit
[root@7968b4436989 /]# docker stop 7968b443
# 只断开和容器的连接而不关闭容器
# 快捷键: ctrl+p+q
# 关闭运行中的容器
# 如果此时有其他终端正在对他进行交互会自动中断
# docker stop contrainer_id/name //发送SIGTERM信号,可忽略,15信号
# docker kill contrainer_id/name //发送SIGKILL信号,9信号
容器的导入导出
# 导出容器
# 镜像打包
# 方案一: export
# 利用export把正在运行的容器直接导出为tar包的镜像文件,可以用-o或>
docker run --name my-nginx -d -p 8080:80 some-centent-nginx:1.2
docker export my-nginx > youmen_nginx.tar or docker export -o youmen_nginx.tar my-nginx
scp youmen_nginx.tar 120.77.248.31:
docker import youmen_nginx.tar
docker tag 121d8 mynginx:1 # 设置镜像名字
docker import youmen_nginx.tar mynginx:1.1 # 导入时即设置镜像名字
# 方案二: 利用save直接把镜像打包出来
docker save -o suibian.tar library/centos:latest
scp suibian.tar 192.168.135.161:
docker load < suibian.tar # 导入之后使用原名
# 导入也可以通过指定URL或者某个目录来导入
docker import http://example.com/exampleimage.tgz example/imagerepo
# 区别介绍
# docker save:将一个镜像导出为文件,保存的是该镜像的所有历史记录;
# docker export:将一个容器导出为文件,保存的是容器当时的状态,即容器快照;
# docker load:将镜像存储文件导入到本地镜像库;
# docker import:导入一个容器快照到本地镜像库;
docker save和docker export之间的区别:
# 1> docker save是将镜像保存为tar包,且会保存该镜像的父层、标签、所有历史等信息;
# docker export是将容器文件系统保存为tar包,仅仅保存的是容器当时的状态(快照);
# 2> docker save可以同时指定多个镜像名称;docker export只能指定一个容器名称;
# 3> docker save保存的镜像文件tar包使用docker load命令加载还原;
# docker export保存的容器快照tar包使用docker import命令导入还原;
# 4> docker save保存的tar包文件通常比docker export导出的文件要大;
docker load和docker import之间的区别
# 1)docker load将镜像存储文件导入到本地镜像库;docker import将容器快照文件导入到本地镜像库;
# 2)docker load不能指定url;而docker import可以指定url来进行导入;
容器服务管理及开机启动设置
# Docker容器开机启动设置
sudo docker run --restart=always -it centos /bin/bash
--restart=always # 默认情况下docker重启之后所有容器会被关闭,这个选项的意思是容器随docker engine自启动
# 如果创建时候未指定--restart=always,可通过docker update命令设置:
docker update --restart=always 7b5f30fe77c0
# 注意Docker服务开启启动
# restart参数介绍
# no:容器退出时候,不重启容器
# on-failure: 只有在非0状态退出时才重新启动容器
# always:无论退出状态是如何,都重启容器
# unless-stopped: 在容器退出时总是重启容器,但是不考虑在Docker守护进程启动时就已经停止了的容器
# 在使用on-failure策略时,指定Docker将尝试重新启动容器的最大次数;
# 默认情况下,Docker将尝试永远重新启动容器
# sudo docker run --restart=on-failure:5 <image>