精华内容
下载资源
问答
  • Docker 容器技术 — 容器存储

    千次阅读 多人点赞 2020-10-02 10:40:46
    文章目录目录容器存储数据卷管理挂载时创建卷创建卷后挂载数据容器管理 容器存储 数据卷管理 核心选项: -v 宿主机目录:指定挂载到容器内的目录。 映射多个宿主机目录,只需要多写几个 -v 即可。 挂载时创建卷 ...

    目录

    容器存储

    数据卷管理

    核心选项:

    • -v 宿主机目录:指定挂载到容器内的目录。

    映射多个宿主机目录,只需要多写几个 -v 即可。

    挂载时创建卷

    • 挂载卷:
    docker run -d -p 80:80 -v /data:/usr/share/nginx/html nginx:latest
    
    • 设置共享卷,使用同一个卷启动一个新的容器:
    docker run -d -p 8080:80 -v /data:/usr/share/nginx/html nginx:latest 
    

    创建卷后挂载

    • 查看卷列表:
    docker volume ls
    
    
    • 查看未被容器使用的数据盘
    docker volume ls -f dangling=true
    
    • 创建一个卷:
    $ docker volume create 
    f3b95f7bd17da220e63d4e70850b8d7fb3e20f8ad02043423a39fdd072b83521
    
    $ docker volume ls 
    DRIVER              VOLUME NAME
    local               f3b95f7bd17da220e63d4e70850b8d7fb3e20f8ad02043423a39fdd072b83521
    
    • 查看卷路径:
    $ docker volume inspect <volume_name> 
    [
        {
            "CreatedAt": "2018-02-01T00:39:25+08:00",
            "Driver": "local",
            "Labels": {},
            "Mountpoint": "/var/lib/docker/volumes/clsn/_data",
            "Name": "clsn",
            "Options": {},
            "Scope": "local"
        }
    ]
    
    • 使用卷创建容器:
    docker run -d -p 9000:80 -v <volume_name>:/usr/share/nginx/html nginx:latest 
    
    • 删除卷
    docker rm -v <volume_name>
    

    数据容器管理

    可以创建一个特殊的容器,来充当数据容器,也就是在创建容器时指定这个容器的数据盘,然后让其他容器可以使用这个数据容器作为他们的数据盘。

    • 创建一个数据容器:
    docker create -v /mnt -it --name newnginx docker.io/nginx /bin/bash
    
    • 利用此数据容器容器运行一个容器
    docker run --volumes-from newnginx --name nginx1 -it docker.io/nginx /bin/bash
    
    展开全文
  • 对于无状态应用来说,无论是节点故障,还是扩容,在容器平台上都能轻松应对,但对于有状态应用来说,就没那么容易了,必须要配合合适的容器存储软件,才能满足业务的需求。 为了满足容器对存储的迫切需求,Kubern...

    根据2019年最新的Gartner的分析数据显示,到2022年,全球75%的企业将在生产环境中使用容器。而从2018年开始,在容器平台上运行的应用程序,已经有超过一半的是有状态应用,例如数据库等。对于无状态应用来说,无论是节点故障,还是扩容,在容器平台上都能轻松应对,但对于有状态应用来说,就没那么容易了,必须要配合合适的容器存储软件,才能满足业务的需求。

    为了满足容器对存储的迫切需求,Kubernetes从v1.2版本开始支持FlexVolume, 但由于FlexVolume对于宿主操作系统有侵入性,因此使用上还是有一定局限性。Kubernetes从 v1.9版本开始支持Container Storage Interface(CSI) , 到v1.13版本GA, 可以说CSI 是未来Kubernetes支持存储的大方向,任何一个现有或者新生的存储软件要想支持容器编排平台(Kubernetes、Mesos、Docker Swarm等),都需要实现CSI接口。从官方的列表https://kubernetes-csi.github.io/docs/drivers.html可以看到,已经有大量的存储软件支持了CSI接口,为Kubernetes平台之上的容器提供数据持久化的能力。

    焱融云YRCloudFile支持的CSI接口

    焱融云YRCloudFile支持的CSI接口然而,CSI接口只是容器存储的必要条件,如何从众多的存储中选择一个合适的,我们来逐条分析一下。

    Kubernetes的整合能力

    • Prometheus监控展示

    Prometheus作为CNCF版图中重要的一员,已经在Kubernetes平台中占领了C位,从用户的角度来说,容器管理可以使用Kubernetes平台,对计算节点以及容器的监控,可以采用Grafana+Prometheus以及AlertManager。如果容器存储可以把监控数据统一暴露到容器平台内,对于用户来说可以统一观察到所有集群的状态,提高运维的效率。YRCloudFile提供Prometheus exporter,为Kubernetes平台中的Prometheus server提供存储集群的实时监控数据。

    • QoS支持

    容器基于Cgroup实现了对CPU,Memory资源使用的限制,对于存储,用户同样希望可以限制Quota, 甚至IOPS以及Bandwidth, 避免某一个容器的恶意使用影响所在节点甚至整个集群的正常工作。YRCloudFile支持用户设置PV的Quota、IOPS、Bandwidth,控制PV的QoS,确保集群内的数据访问不受异常访问的影响。

    容器应用特性

    • ReadWriteMany(RWX)

    从社区目前支持的CSI 存储列表中我们可以看到,容器存储基本上分为两种类型,一种为文件系统存储,一种为块存储。基于块存储的方案大多无法支持ReadWriteMany的访问方式,例如 CephRBD, OpenEBS等,这对一些应用的部署方式将产生影响,例如典型的WordPress高可靠部署时,需要一个共享的PV来保持数据的一致,虽然OpenEBS也提供了WorkAround的解决方案,但这个方案采用的是一个NFS的pod来做reexport,很明显,NFS server存在单点故障的风险。同样地,在ElasticSearch,以及一些共享文件访问的场景中,RWX或ROX都是必须支持的读写模式。

    YRCloudFile支持RWX、RWO、ROX读写模式,为各种业务提供所需的读写访问模式。

    • 大文件小文件混合读写

    随着各式各样的应用运行在容器平台之上,对数据的IO模式也不再统一,既有大量的小IO, 动辄上亿的小文件,也有对带宽吞吐有要求的大文件。以GlusterFS举例,曾碰到典型的用户场景,就是存储的都是大文件(例如日志)时工作良好,但是当容器内需要产生海量小文件时,性能上就捉襟见肘了。

    上层应用中文件大小和文件数量的趋势

    上层应用中文件大小和文件数量的趋势YRCloudFile针对此场景做了特定的优化,小IO的随机读写的能力远高于GluserFS, 同时YRCloudFile支持底层直接操作物理磁盘(Raw Disk Storage Engine),减小了物理服务器上文件系统的损耗,进一步提升了集群读写性能。

    • PV数据的“天眼查”

    容器相比虚拟机的一个优势就是资源消耗小,所以同样的物理规模,可运行的容器数量要远大于虚拟机数量,PV(容器持久化数据卷)的数量也因此会达到比较大的规模,如何从众多的PV中找到业务瓶颈,例如IOPS消耗高、存储容量消耗多的PV,甚至用户如何了解某个PV内的数据的热度分布,是否可以删除一些历史数据?

    在客户的实际生产环境中,用户创建了数千个PV,逐个排查Pod来寻找读写热点和瓶颈几乎是不可能的任务,通过YRCloudFile的PV Hot Spot功能,可以帮助用户快速定位数据读写热点。

    在单个PV内部,哪些数据是经常访问的?哪些是冷数据?哪些文件占用空间多?YRCloudFile的PV Insight功能可以帮助客户达成上述目的。

    可用性

    • 容器重建

    容器应用的一个特点就是启动快,同时当容器平台发现某一个容器出现问题时,会自动利用调度器,在其他的节点上快速重建容器。

    在使用基于块的容器存储时,例如CephRBD, 由于块设备的特性,系统首先要从故障节点进行umount/detach,成功后才能重新attach到新的宿主服务器,进而mount到容器内,对于块设备来说,detach/attach是一个耗时的操作。当计算节点故障,需要对该节点上大量有状态Pod触发重建操作时,更是对业务恢复时间造成影响。

    YRCloudFile采用分布式文件结构,数据在所有的节点都可见,对所有节点上的容器都是透明的,避免了detach/attach的操作,极大缩短了容器跨节点重建的过程。正所谓你看见看不见,数据都在那里,不离不弃。

    高性能

    业务的性能需求无极限。存储性能高,意味着上层业务处理能力会更强。YRCloudFile可以充分利用高性能的网络设备,从网络传输层进行对应的优化,可以更好地发挥RDMA网络的能力,对小IO读写性能有大幅提升。

    总结

    无论是软件定义的分布式存储,还是传统的集中是存储,存储是多种多样,但是当我们面临容器存储的选择时,需要考虑多方面的因素。传统存储由于所处时代以及解决的主要问题的不同,无法满足容器平台全部的需求。新兴的软件定义存储良莠不齐,需要根据容器平台应用的特点,来选择一款合适的容器存储软件,YRCloudFile无疑是客户建设容器云平台时的最佳选择。


    关于焱融云


    焱融云是一家以软件定义存储技术为核心竞争力的高新技术企业,在分布式存储等关键技术上拥有自主知识产权,是容器存储的领导者。焱融云针对各行业业务特性,打造个性化行业解决方案,提供一站式的产品与服务。焱融云系列产品已服务于金融、政府、制造业、互联网等行业的众多客户。了解更多焱融科技信息,请访问官网http://www.yanrongyun.com

    展开全文
  • c++ set容器存储指针,智能指针用法

    千次阅读 2018-12-29 12:04:25
    c++ set容器存储指针,智能指针用法Set容器存储c++内置变量Set容器存储指向类对象的智能智能对象 Set容器存储c++内置变量 set容器存储基础变量像int,char,或者是string对象,我们不需要去写比较函数,但是如果存储的...

    Set容器存储c++内置变量

    set容器存储基础变量像int,char,或者是string对象,我们不需要去写比较函数,但是如果存储的是自定义类对象或者是结构体,那就需要重载<运算符或者重载(),在构造set容器时作为第二个参数传入,set容器存储内置变量,我就不记录了,这篇文章主要讲存储类对象以及用指针或者智能指针存储的用法

    Set容器存储指向类对象的智能智能对象

    我们知道stl存储对象时有一个特点,就是其实是对对象进行拷贝,然后存储,同时如果声明的是基类对象,而实际插入时是子类对象,那和子类相关的成员变量和函数就会被剥离,也就是不支持多态.所以我们一般选择存储指针,但是由于存储普通指针时,stl容器会对指针和指针指向的内存也进行拷贝,所以容器只会对它拷贝的那个指针变量指向的内存进行管理,而之前的内存需要我们自己去释放,所以使用智能指针就是最佳的选择.至于使用shared_ptr还是unique_ptr,这里需要根据自己需求来选,他们的区别,可以自己去了解,我只写使用shared_ptr的示例:

    #include <iostream>
    #include <set>
    #include <memory>
    #include <functional>
    #include <unordered_set>
    using namespace std;
    
    namespace set_test
    {
      struct song
      {
      	int m_id;
      	int m_hot;
      	song(int id, int hot)
      	{
    
      		this->m_id = id;
      		this->m_hot = hot;
      	}
      	~song() {
      		cout << "~song:m_id=" << m_id << ",m_hot=" << m_hot << endl;
      	}
      	//set容器中存放类对象时,自定义比较函数方式1:内部重载<
      	bool operator<(const struct song & right)const   //重载<运算符
      	{
      		if (this->m_id == right.m_id)     //根据id去重
      			return false;
      		else
      		{
      			if (this->m_hot != right.m_hot)
      			{
      				return this->m_hot > right.m_hot;      //降序
      			}
      			else
      			{
      				return this->m_id > right.m_id;
      			}
      		}
      	};
    
      	//set容器中存放类对象智能指针时,自定义比较函数方式1:friend方式重载<
      	friend	bool operator <(const shared_ptr< song>& left, const shared_ptr< song>&  right)
      	{
      		//return false;
      		if (left->m_id == right->m_id)     //根据id去重
      			return false;
      		else
      		{
      			if (left->m_hot != right->m_hot)
      			{
      				return left->m_hot < right->m_hot;      //降序
      			}
      			else
      			{
      				return left->m_id < right->m_id;
      			}
    
      		}
      	}
      };
    
    
      //set容器中存放类对象时,自定义比较函数方式2:指定set_comp为函数对象(必须申明为const引用)
      struct set_comp
      {
      	bool operator()(const struct song& left, const struct song&  right)  //重载()运算符
      	{
    
      		if (left.m_id == right.m_id)     //根据id去重
      			return false;
      		else
      		{
      			if (left.m_hot != right.m_hot)
      			{
      				return left.m_hot > right.m_hot;      //降序
      			}
      			else
      			{
      				return left.m_id > right.m_id;
      			}
    
      		}
      	}
    
      };
    
      //set容器中存放类对象智能指针时,自定义比较函数方式2:指定set_share_ptr_compptr为函数对象(必须申明为const引用)
      struct set_share_ptr_compptr
      {
      	bool operator()(const shared_ptr <song>& left, const shared_ptr<song>& right)  //重载()运算符
      	{
      		if (left->m_id == right->m_id)     //根据id去重
      			return false;
      		else
      		{
      			if (left->m_hot != right->m_hot)
      			{
      				return left->m_hot > right->m_hot;      //降序
      			}
      			else
      			{
      				return left->m_id > right->m_id;
      			}
    
      		}
      	}
    
      };
    
      void print_set(std::set<shared_ptr< song>>& prSet)
      {
      	for (auto& it : prSet)
      	{
      		std::cout << "id:" << it->m_id << ",hot:" << it->m_hot << endl;
      	}
      }
    
      //set存储类对象
      void test1()
      {
      	cout << "set存储类对象,使用类成员函数作为比较函数:" << endl;
    
      	std::set<song> mySet;     //使用重载的<
      	auto s1 = song(10, 100);
      	auto s2 = song(20, 200);
      	auto s3 = song(20, 300);//重复的插不进
    
      	auto s4 = song(40, 300);
      	auto s5 = song(50, 200);
      	auto s6 = song(60, 400);
      	auto s7 = song(50, 300);
    
      	mySet.insert(s1);    //插入s1
      	mySet.insert(s2);    //插入s2
      	mySet.insert(s3);    //s3和s2的id相同,不插入
      	mySet.insert(s4);    //插入s4
      	mySet.insert(s5);    //s5 m_hot比s4 m_hot小在s4后面
      	mySet.insert(s6);    //s6 m_hot比s4 m_hot大在s4前面
      	mySet.insert(s7);    //s7 m_hot和s4 一样大,但是s7 m_id比s4 m_id大,
    
      	cout << "set存储类对象,使用函数对象作为比较函数:" << endl;
      	std::set<song, set_comp> mySet1;    //使用外部定义的的()
      	for (auto&val : mySet)
      	{
      		mySet1.emplace(val);
      	}
      }
    
      //set存储智能指针
      void test2()
      {
      	cout << "set存储类对象智能指针,使用类成员函数作为比较函数:" << endl;
      	std::set<shared_ptr< song>> mySet;      //使用重载的<
      	auto s1 = std::make_shared<song>(10, 100);
      	auto s2 = std::make_shared<song>(20, 200);
      	auto s3 = std::make_shared<song>(20, 300);//重复的插不进
      	auto s4 = std::make_shared<song>(30, 300);
      	auto s5 = std::make_shared<song>(40, 200);
      	auto s6 = std::make_shared<song>(40, 200);//重复的插不进
      	auto s7 = std::make_shared<song>(50, 400);//重复的插不进
    
      	mySet.insert(s1);    //插入s1
      	mySet.insert(s2);    //插入s2
      	mySet.insert(s3);    //s3和s2的id相同,不插入
      	mySet.insert(s4);    //插入s4
      	mySet.insert(s5);    //插入s5,看和s4是如何排序的
      	mySet.insert(s6);    //看是否插的进
      	mySet.insert(s7);    //看是否插的进
    
      	//输出一次
      	print_set(mySet);
    
      	//修改一个值,再看输出
      	for (auto &info : mySet)
      	{
      		if (info->m_id == 10)
      		{
      			info->m_hot = 1000;
      			break;
      		}
      	}
      	cout << "修改后输出" << endl;
    
      	std::set<shared_ptr< song>> mySet1;      //使用重载的<
      	mySet.swap(mySet1);
      	//输出一次
      	print_set(mySet);
      	print_set(mySet1);
    
      	cout << "修改后再插入新值:" << endl;
      	mySet1.insert(std::make_shared<song>(60, 600));
    
      	print_set(mySet1);
    
      	/*
      	修改容器对象成员变量值,不会引起重新排序,即使插入新值也只是新值进行排序,被修改的值不会排序
      	*/
    
    
      	cout << "set存储类对象智能指针,使用函数对象作为比较函数:" << endl;
      	std::set<shared_ptr< song>, set_share_ptr_compptr> mySet2;    //使用外部定义的的()
      	for (auto&val : mySet1)
      	{
      		mySet2.emplace(val);
      	}
      }
    
      //测试遍历一个set删除另外一个set中的元素
      void test3()
      {
      	set < shared_ptr<song>> set1, set2;
    
      	for (int i = 0; i < 10; i++)
      	{
      		set1.insert(make_shared<song>(i, i * 10));
      	}
      	for (auto& str : set1)
      	{
      		set2.insert(str);
      	}
    
      	//删除第一个
      	set2.erase(*(set1.begin())); //用元素关键值删除
      	set2.erase(set2.begin());//用迭代器删除
    
      	//遍历set2,删除set1中的元素
      	for (auto it = set2.begin(); it != set2.end();)
      	{
      		set1.erase(*it);
      		if ((*it)->m_id != 9)
      		{
      			set2.erase(it++);//迭代器先加再删除
      			//it = set2.erase(it);//等价于上面那种写法
      		}
      		else
      		{
      			it++;
      		}
      	}
    
      	//因为自定义了比较函数,比较函数中当m_id相同时就可以判断是同一个元素,所以可以通过构建临时对象来删除;
      	set2.erase(make_shared<song>(9, 90));
      }
    
      void test_all()
      {
      	test1();
      	test2();
      	test3();
      };
    
    }
    
    展开全文
  • 1. 容器存储背景 1.1、容器 VS 虚拟机 容器技术是目前云计算中不可或缺的一部分,相比于传统虚拟机而言,容器技术在操作系统级别为虚拟化提供了一种更轻量化的选择。传统虚拟机(VM)在虚拟化硬件和主机操作系统之...

    1. 容器存储背景

    1.1、容器 VS 虚拟机

    容器技术是目前云计算中不可或缺的一部分,相比于传统虚拟机而言,容器技术在操作系统级别为虚拟化提供了一种更轻量化的选择。传统虚拟机(VM)在虚拟化硬件和主机操作系统之上通过hypervisor管理层运行客户端操作系统的完整副本,运行过程占用大量空间,限制了单台物理主机上可部署的虚拟机数量,而启动时间长使得虚拟机托管短生命周期的应用程序代价过高。下图为虚拟机和Docker容器的系统层次结构对比图。

     基于容器的虚拟化技术通过在应用程序之间共享主机操作系统的库和内核来解决这些问题,它不再需要运行整个臃肿的客户端操作系统,而是通过命名空间namespace机制使每个应用程序都运行在一个独立的命名空间,也就是容器中。简单理解来看,容器就是通过namespace机制打包隔离运行的一组“容器化”进程。运行在主机操作系统之上的后台进程Docker守护进程则取代了Hypervisor,它可以直接与主机操作系统通信,负责管理Docker容器为其分配资源,而应用的所有依赖包括源代码都打包在Docker镜像。相比于虚拟机而言,容器产生的开销更小,占用的空间更少,启动的速度更快。

    1.2、镜像与层

    Docker 镜像作为Docker容器的基础,由一系列层堆叠组成,每个层代表镜像的Dockerfile中的一条指令。除了最上层容器层之外,每个层都是只读了,每个层与之前的层只有一部分差异。当创建并启动一个新的容器时,Docker会加载只读镜像层并在其上(即镜像栈顶部)添加一个可读写层,这个层通常被称为“容器层”。如果运行中的容器修改了现有的一个已经存在的文件,那该文件将会从读写层下面的只读层复制到读写层,该文件的只读版本仍然存在,只是已经被读写层中该文件的副本所隐藏。当删除Docker容器,并通过该镜像重新启动时,之前的更改将会丢失。在Docker中,只读层及在顶部的读写层的组合被称为Union File System(联合文件系统)。下图显示了一个基于 Ubuntu 15.04 镜像的容器。

    1.3、容器与层

    容器和镜像之间的主要区别是顶部的可写层。所有对容器添加新的或修改现有数据的内容都存储在该可写层中。当容器被删除时,可写层也被删除,底层镜像则保持不变。由于每个容器都有自己的可写的容器层,并且所有更改都存储在此容器层中,因此多个容器可以共享对相同基础镜像的访问权限,并且拥有自己的数据状态。下图显示了共享相同 Ubuntu 15.04 镜像的多个容器。Docker 使用存储驱动程序来管理镜像层和可写容器层的内容。每个存储驱动程序都处理实现的方式不同,但所有驱动程序都使用可堆叠的镜像层和写入时复制(CoW)策略。存储驱动程序处理有关这些层相互交互方式的详细信息,不同的存储驱动程序可以使用,在不同情况下各有优缺点。

    由于容器灵活、高效、易于扩展,并面向云计算,因此希望将其应用范围扩展到徽服务之外,例如大型的容器化的数据库、甚至存储系统,但是通过容器设计企业级的、长期存储数据的应用目前仍是困难的。

    1.4、容器持久化数据存储

    Docker容器架构包括三种类型的数据存储 1)第一类是镜像存储,可利用现有的共享存储进行交付,要求类似于服务器虚拟化环境中虚拟机镜像分发保护的平台架构。容器镜像的数据存储容量相对完整的虚拟机镜像较小,可以高效地存储、共享。其缺点在于无法存储动态应用程序的数据。2)第二类是容器的管理数据,如存储配置、日志记录等管理数据。3)第三类是容器应用数据的存储,容器使用分层文件系统,将所有新写入的数据存储在临时虚拟层,无法直接修改最底层的镜像,写入日录和文件。一旦容器消失,所有的临时存储都将丢失。

    容器上承载的应用分为有状态和无状态,容器本身更倾向于无状态化、可水平拓展的应用,但并不意味所有的应用都是无状态的,一些应用服务的状态需要保存比如日志等相关信息,因此需要持久化存储。而容器的特性决定了容器本身是非持久化的,当容器被删除时其上的数据也一并删除。

    针对Docker容器的持久化数据访问有多种选择:Docker存储驱动(可以理解为Docker文件系统驱动);Docker卷;直接设备访问(device mapper,也可以理解为一种存储驱动)。但这三种方式目前仍然存在各自的问题:

    • Docker存储驱动:1)、对文件进行分层访问,需要通过多个文件或持久层复制数据,存在空间和速度问题;2)、必须为驱动程序配置整个Docker服务守护程序,且除非重新配置和重新安装否则无法更改。
    • Docker数据卷:1)、卷在容器启动时指定,不能后续添加;2)、数据不隔离;3)、由于卷驱动程序中的内部同步点,多线程应用程序不能很好地扩展。
    • 直接设备访问:1)、容器启动时指定并授予对设备的直接访问权限,且在重启时要求完全相同的驱动程序,新驱动安装后必须重新启动容器;2)、必须授予违反隔离原则的容器特殊权限,因为它允许每个容器在使用相同规范启动的容器中访问或覆盖共享驱动程序中的数据。

    2. Docker容器存储

    2.1、存储驱动

    Docker 使用存储驱动程序来管理镜像层和可写容器层的内容与数据操作。每个存储驱动程序都处理实现的方式不同,但所有驱动程序都使用可堆叠的镜像层和写入时复制(CoW)策略。

    2.1.1、AUFS

    AUFS是一个联合文件系统,AUFS存储驱动是文件级存储驱动。AUFS能透明覆盖一或多个现有文件系统的层状文件系统,把多层合并成文件系统的单层表示,这些目录在AUFS术语中称为分支,在Docker术语中称为层。简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统,这种文件系统可以一层一层地叠加修改文件。无论底下有多少层都是只读的,只有最上层的文件系统是可写的。

    当需要修改一个文件时,AUFS创建在可写层使用CoW创建该文件的副本,将文件从只读层复制到上层可写层进行修改,结果保存在可写层。在Docker中,底下的只读层就是image,可写层就是Container,从上层至下层具有覆盖性,当上层和下层存在相同文件时,上层文件会覆盖下层文件。当执行读操作时,由上至下检索,当找到文件时读取。AUFS因此会因为过深的镜像层数影响读写性能。AUFS的逻辑结构如上图所示。

    容器的每个镜像层/容器层在Docker主机上表示为/var/lib/docker/中的子目录。AUFS通过联合挂载提供了所有层的统一视图,关于镜像和容器层的所有信息都存储在子目录/var/lib/docker/aufs/中:

    • diff/:存储每个layer的内容,每个都存储在一个单独的子目录中。包括可写容器层中的改变也保存在该文件子目录对应的文件目录下。
    • layers/:有关镜像层堆叠方式的元数据。该目录包含Docker主机上每个镜像或容器层的一个文件,每个文件都包含堆栈中其下面所有层的ID(其父项)。
    • mnt/:挂载点,每个映像或容器层一个,用于为容器集合和挂载统一文件系统。对于只读镜像这些目录始终为空。

    2.1.2、BTRFS

    Btrfs被称为下一代写时复制文件系统,并入Linux内核。Btrfs存储驱动是文件级驱动,但也能像Device mapper一样直接操作底层设备。Btrfs把文件系统的一部分配置为一个完整的子文件系统,称为subvolume。采用“子卷”的一个大的文件系统可以被划分为多个子文件系统,这些子文件系统共享底层的设备空间,在需要磁盘空间时便从底层设备中分配,类似于应用调用 malloc()分配内存。1)当写入新文件时,btrfs通过用时分配为容器快照分配一个新的数据块,文件写在这个空间里;2)当修改已有文件时,btrfs从文件当前存在的层读取原始数据,使用CoW分配一个新的原始数据快照并在其中修改数据,并仅将修改的块写入容器的可写层,再更新快照中的文件系统元数据指向此新数据;3)当读数据时,针对快照执行的读取与针对子卷执行的读取基本相同;4)当删除底层中的文件/目录时,Btrfs会屏蔽底层中文件/目录的存在。若先创建文件然后将其删除,则此操作将在Btrfs文件系统本身中执行,并回收该空间。

    为了灵活利用设备空间,Btrfs 将磁盘空间划分为多个chunk,而每个chunk可以使用不同的磁盘空间分配策略,比如某些chunk只存放metadata,某些chunk只存放数据。这种方式有很多优点比如Btrfs支持动态添加设备而无需解挂载文件系统或重启Docker。用户在系统中增加新的磁盘之后,可以使用Btrfs命令将该设备直接添加到文件系统中。

    Btrfs存储驱动与设备映射或其他驱动的工作方式不同,整个/var/lib/docker/目录存储在Btrfs卷上。Btrfs存储驱动将每个镜像和容器存储在自己的Btrfs子卷或快照中:镜像的基础层存储为子卷,而子镜像层和容器存储为快照,如上图。Btrfs把一个大的文件系统当成一个资源池,配置成多个完整的子文件系统,可以往资源池里加新的子文件系统,而基础镜像则是子文件系统的快照,每个子镜像和容器都有自己的快照,这些快照则都是subvolume的快照。

    2.1.3、Overlay / Overlay2

    OverlayFS是一种现代化联合文件系统,是文件级驱动,类似AUFS但相对更快速和稳定。Overlay存储驱动在Linux内核3.18后支持,Overlay2存储驱动在Linux内核4.0以后支持,可能的情况下选择后者会更高效。Overlay存储驱动的结构如下图所示:

    OverlayFS将单个Linux主机上的两个目录分层,并将它们显示为单个目录。这些目录称为层,统一过程称为联合挂载,下层目录称为lowerdir,上层目录称为upperdir,统一视图通过命名为merged的目录公开。当镜像层和容器层包含相同文件的情况下,容器层“获胜”并且隐藏镜像层中相同文件的存在。当需要修改一个文件时,使用CoW将文件从只读的lowerdir复制到可写的upperdir进行修改,结果保存在upperdir层。

    Overlay:和AUFS驱动不同的是Overlay只有两层,镜像层是lowerdir,容器层是upperdir。统一视图通过名为merged的目录公开,该目录实际上是容器挂载点。Overlay驱序仅包含两层,意味着多层镜像不能实现为多个OverlayFS层。 因此每个镜像层都在/var/lib/docker/overlay下实现为自己的目录,然后使用硬链接作为应用与较低层共享数据。但硬链接的使用导致过度使用inode,并且可能需要对后端文件系统进行额外配置。

    Overlay2:驱动程序则支持多达128个较低的OverlayFS层。此功能为与层相关的Docker命令(如build、commit)提供了更好的性能,且在后端文件系统上消耗的inode更少。

    2.1.4、ZFS

    ZFS 是革命性的下一代文件系统,它从根本上改变了文件系统的管理方式,ZFS 完全抛弃了“卷管理”,不再创建虚拟的卷,而是把所有设备集中到一个存储池中来进行管理,用“存储池”的概念来管理物理存储空间。文件系统过去都是构建在物理设备之上的,为了管理这些物理设备并为数据提供冗余,“卷管理”的概念提供了一个单设备的映像。而ZFS创建在虚拟的被称为“zpools”的存储池之上。每个存储池由若干虚拟设备(virtual devices,vdevs)组成,zpool上的文件系统可以使用这些虚拟设备的总存储容量。Zpool的大小可在运行时通过动态添加专用设备来扩容,且ZFS可限制每个容器的可用存储空间。

    ZFS包括:1)文件系统:精简配置,按需从zpool分配空间;2)快照:文件系统的只读空间高效的副本;3)克隆:快照的可读写副本,存储与之前层的差异。文件系统、快照和克隆都从底层zpool分配空间。下图为创建克隆的过程:1)从文件系统创建只读快照;2)从快照创建可写克隆,包含与父镜像层的任何差异。

    每个运行中容器的统一文件系统都挂载在/var/lib/docker/zfs/graph/中的挂载点上。镜像的基础层是ZFS文件系统,每个孩子层都是基于其下层ZFS快照的ZFS克隆。一个容器是基于其源镜像顶层ZFS快照的ZFS克隆。快照是只读的,而克隆是可写的。下图为一个基于双层镜像的运行时容器。

    Docker里ZFS首先从zpool里分配一个ZFS文件系统给镜像的基础层;而其他镜像层则是这个ZFS文件系统快照的克隆;当容器启动时则在镜像最顶层生成一个可写层。当要写一个新文件时,使用按需分配将一个新的数据快从zpool里生成,新的数据写入这个块,而这个新空间存于容器层(ZFS的克隆)。当要修改一个已存在的文件时,为要修改的快分配一个新空间并使用写时复制把原始数据复制到新空间完成修改。

    2.1.5、Device Mapper

    Docker的 device mapper 存储驱动程序利用内核框架Device Mapper的精简配置和快照功能进行镜像和容器管理,其使用专用块设备而不是格式化的文件系统,并在块级别上对文件进行操作。它提供一种从逻辑设备到物理设备的映射框架机制,在该机制下用户可以方便的根据需要制定存储资源的管理策略。Device mapper存储驱动可通过将物理存储添加到Docker主机来拓展这些设备。

    通过Device mapper 存储驱动程序启动 Docker 时,所有镜像及容器层都保存在 /var/lib/docker/devicemapper/,它由一个或多个块级设备支持。Device mapper驱动首先在块设备上创建一个资源池,然后在资源池上创建一个带有文件系统的基本设备,所有镜像都是这个基本设备的快照,而容器则是镜像的快照。所以在容器里看到的文件系统是资源池上基本设备的文件系统的快照,初始不为容器分配空间:1)当写入新文件时,通过用时分配为容器快照分配新的块并写入数据;2)当要修改已有文件时,使用CoW为容器快照分配块空间,将要修改的数据复制到在容器快照中新的块里再进行修改。

    如上图所示,Docker 主机上有两个容器在运行,分别是 ubuntu 和 busybox。每个镜像层都是下面层的快照,每个镜像的最底层是池中base device 的快照。在运行容器时,容器是它所依赖的镜像的快照。

    2.1.6、Docker存储驱动的对比及适应场景

    如下图所示,为Docker存储驱动的对比以及适用场景。

    2.2、数据卷

    当容器删除后,任何写到容器但没有保存到数据卷的数据都会和容器一同删除。为了能够保存(持久化)数据以及共享容器间的数据,Docker提出了Volume的概念。简单理解来看,Volume就是一个目录或者文件,它可以绕过默认的联合文件系统或存储驱动,而以正常的文件或者目录的形式存在于宿主机上。这增强了数据卷与存储驱动程序控制的独立性,当容器被删除时,存储在数据卷中的任何数据都会保留在 Docker 主机上。

    数据卷作为Docker 主机文件系统上的一个目录或文件,直接挂载到容器中,而不被存储驱动程序控制。对数据卷的读写操作绕过存储驱动程序,并以本地主机速度运行。可以将任意数量的数据卷装入容器,多个容器也可以共享一个或多个数据卷。上图显示了一个运行两个容器的独立 Docker 主机:每个容器都存在于Docker主机本地存储区/var/lib/docker/内其自己的地址空间中,在主机/data 目录上还有一个共享数据卷,作为数据卷被直接挂载到两个容器中。

    Docker数据卷可以将容器以及容器产生的数据分离开来,从而使得删除容器时,不会影响相关的数据。可以通过两种方式来初始化Volume:数据卷、数据卷容器:

    • 数据卷:数据卷的使用类似于 Linux 下对目录进行 mount,挂载一个主机目录作为数据卷,你想在容器中使用主机上的某个目录,你可以通过-v参数来指定:docker run -v /host/path:/some/path ;
    • 数据卷容器:授权一个容器访问另一个容器的Volume,使用--volumes-from参数来执行。针对持久性数据在容器间共享的情况,可以通过创建一个数据卷容器,然后从此容器上挂载数据。常见的使用场景是使用纯数据容器来持久化数据库、配置文件或者数据文件等。

    容器持久化数据卷方案中持久层的生产价值,不是通过弹性,而是可以拓展数据卷通过灵活可编程,例如通过设计的API来扩展存储。Docker已发布容器卷插件规范,允许第三方厂商的数据卷在Docker引擎中提供数据服务。这种机制意味着外置存储可以超过容器的生命周期而独立存在,且各种存储设备只要满足接口API标准,就可接入Docker容器的运行平台中。即现有的各种存储可以通过简单的驱动程序封装,从而实现和Docker容器的对接。可以说,驱动程序实现了和容器引擎的北向接口,底层则调用后端存储的功能完成数据存取等任务。目前已经实现的Docker Volume Plugin中,后端存储包括常见的NFS,GlusterFS和块设备等。

    2.3、容器存储的应用以及与传统存储的比较

    2.3.1、容器存储的应用场景

    容器持久化的解决方案在于需要不需要保存数据,以及保存在哪?

    • 当数据保存在本地(故障率高):那就是本地硬盘,即通过上述容器存储驱动以及数据卷来对本地数据进行存取操作。
    • 当数据保存在外部:1)分布式系统(并行处理),那么就需要分布式系统支持Volume Plugin;2)共享存储(高可用),那么外部存储需要支持Volume Plugin的驱动程序,问题即转变为选择哪种存储/存储软件,以及其是否支持Volume Plugin。

    2.3.2、容器存储与传统存储的比较

    1、容器存储

    容器存储简单理解来看其实就是通过存储驱动以及数据卷实现对容器镜像、容器管理数据、以及容器应用数据的存储,其本质上仍然需要依赖传统的文件系统来支撑。

    对于存储驱动,其对应的挂载目录均需要使用对应的后端文件系统进行格式化挂载管理,如下图所示;而对于数据卷,其依赖于主机文件系统的管理操作。

    2、文件系统存储、块存储和对象存储,三种存储系统实现容器持久存储对比?

    这三者都是通过实现Volume Plugin来支持,与标准的文件系统,块,对象在使用上的差别是类似的:

    • 块存储比较快,扩展弱,价格贵
    • 文件系统存储性能中等,扩展强,价格中等,分布式环境可用
    • 对象存储性能弱,扩展极强,价格低,一般针对分布式环境或者云环境。

    所以从实现上是没多大区别的,只要Driver支持。

    目前看来,块存储的应用最为广泛,主要是块存储的技术储备最多,并且广泛使用。对象存储次之,现在对象存储基于S3/swift/ OSS接口,随着公有云的普及,被广泛使用。文件存储,目前还是NAS传统存储的天下,性能和可靠性最为稳定,cephFS目前还是不足以满足生产环境的使用。

    展开全文
  • k8s1.8引入的特性,限制容器存储空间的使用;对于容器资源隔离来说,非常有用,万一应用程序失控,写大量日志把node空间写满,影响就大了。 使用很简单,和cpu\memcache一样,如: resources: requests...
  • docker 镜像与容器存储目录结构精讲

    万次阅读 2015-12-14 22:41:18
    docker 镜像与容器存储结构分析 很多朋友在初学 docker 的时候非常迷茫,不清楚docker是怎样的一种存储方式,其实docker 的镜像与容器都存储在 /var/lib/docker 下面,那么基于不同的系统又有不同的存储方式,在...
  • 容器中直接储存类(如vector),则当push_back一个 D 时,其派生部分会被"切掉". 因此在用容器储存带有继承关系的对象时, 我们将选用基类的指针(最好是智能指针). 目的是保证容器的兼容性. */ #include #...
  • k8s1.8引入的特性,限制容器存储空间的使用;对于容器资源隔离来说,非常有用,万一应用程序失控,写大量日志把node空间写满,影响就大了。 使用很简单,和cpu\memcache一样,如: resources: requests: cpu: 1...
  • 容器存储的今生来世

    千次阅读 2017-12-06 08:41:54
    容器生态现状容器生态,对比2015年之前已经有重大变化,2015-2016年间,互联网、新兴企业都在将其Workload向容器环境迁。容器已不仅仅是两年前的部署工具,更是一种成熟的技术和平台,已经普遍的应用在公有云的运营...
  • Kubernetes 主要优势之一就是具有强大的 Volume Plugin System(存储卷插件系统),这个优势可以让许多不同类型的存储系统能够:按需要自动...Kubernetes 1.9 引入了容器存储接口(CSI)Alpha 版本,这使得安装 Volu...
  • 迁移 Docker 容器储存位置

    千次阅读 2019-07-17 18:47:23
    迁移 Docker 容器储存位置 这篇文章初稿是写在 迁移服务器上的 GitLab 发布后,因为各种原因(忙碌+拖延症),一直躺在草稿箱种,考虑到使用容器的同学越来越多,系统减肥、数据迁移扩容类数据需求还是有的,所以...
  • 发现公司的confluence服务器的docker容器路径默认存储在/var/lib/docker/下 并没有利用到挂载的data云盘,导致根目录磁盘一直比较爆满 之前建confluence的程序猿只设置了volume 容器内的日志会挂载到data盘,但运行...
  • 近日,CNCF 发布了最新版本的Cloud Native Landscape,焱融云的存储软件 YRCloudFile 被列入云原生全景图谱,位于云原生存储象限中,是国内首个被收录的容器持久化存储产品。借助 YRCloudFile,企业在构建云原生体系...
  • 获取spring的ioc容器,并根据id获取对象,调用getbeans()方法 如下xml配置,将对象放进: <bean id="IAccountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean> ApplicationContext的三个...
  • C++序列容器存储智能指针详解

    千次阅读 2019-09-14 10:13:45
    通常用容器保存指针比保存对象更好,而且大多数时候,保存智能指针比原生指针好。下面是一些原因: 在容器中保存指针需要复制指针而不是它所指向的对象。复制指针通常比复制对象快。 在容器中保存指针可以得到多态...
  • 建议1:在创建一个容器时,如果能够预先估计出它可能存放的最大元素数目,就给它预先分配足够数量的存储空间,以避免存储空间频繁重分配操作。 建议2:尽量在容器尾部执行插入操作,这里插入操作效率最高。
  • C++中用Vector容器存储矩阵

    万次阅读 2015-04-29 22:59:28
    主要记录两点: 1、用vector定义可变长度数组 2、用vector按多字段值排序(数组中存储具有多个属性的类,按照类的某一属性进行排序)
  • 同时对处于主导地位的、较为流行的开源存储项目进行阐述,包括SPDK、ISA-L、OpenSDS、Ceph、OpenStack Swift与Cinder、容器存储等。本书内容基本不涉及具体源码,主要围绕各个项目的起源与发展、实现原理与框架、要...
  • 【Java】利用容器存储表格数据

    万次阅读 2019-07-18 09:35:33
    // HashMap存储行数据 Map < String , Object > row1 = new HashMap < > ( ) ; row1 . put ( "id" , 1001 ) ; row1 . put ( "姓名" , "张三" ) ; row1 . put ( "薪水" , 20000 ) ; row1...
  • 前言Forrester 预测到 2022 年,全球组织/公司在生产环境中运行容器化应用的比例将提升到超过75%。本蓝皮书将在此背景下,讨论容器存储的现实需求以及阿里云文件存储在云原生和...
  • 作者简介 Benjamin Hindman, 美国Mesosphere公司创始人,首席架构师,CNCF技术委员会委员。Apache Mesos项目管理委员会主席,项目的创造者之一。 俞捷博士,现任美国...现在主要负责容器存储和网络方面的...
  • set容器存储自定义类型数据

    千次阅读 2015-10-13 16:04:46
    set容器存储自定义类型数据 #include using namespace std; #include class Student { public: Student(char*name,int age) { strcpy(this->name,name); this->age=age; } ...
  • 修改docker镜像和容器存储的路径

    千次阅读 2018-06-04 16:01:13
    centos7 原有的路径/var/lib/docker ,但是最近磁盘大小问题,需要将镜像存储到新的挂载磁盘下。 cd /etc/systemd/system/multi-user.target.wants vim docker.service ExecStart=/usr/bin/dockerd --graph=/data/...
  • 社区版是免费提供给个人开发者和小型团体使用的,企业版会提供额外的收费服务,比如经过官方测试认证过的基础设施、容器、插件等。 社区版按照stable和edge两种方式发布,每个季度更新stable版本,如17.06,17.09;...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 514,246
精华内容 205,698
关键字:

容器存储