内存池原理大揭秘

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由[amc](https://cloud.tencent.com/dev…云+社区专栏在 C 语言的动态申请内存技术中,相比起 alloc/free 系统调用,内存池(memory pool)是与现在系统中请求一大片连续的内存空间,然后在运行时根据实际需要分配出去的技术。使用内存池的优点有:速度远比 malloc/free 快,因为减少了系统调用的次数,特别是频繁申请/释放内存块的情况避免了频繁申请/释放内存之后,系统的大量内存碎片节省空间分类根据分配出去的内存大小,内存池可以分为两类:Fixed-size Allocation每次分配出去的内存单元(称为 unit 或者 cell)的大小为程序预先定义的值。释放内存块时,则只需要简单地挂回内存池链表中即可。又称为 “固定尺寸缓冲池”。常规的做法是:将不同 unit size 的内存池整合在一起,以满足不同内存块大小的使用需求Variable-size allocation不分配固定长度,内存的分配只是在一大块空闲的内存上滑动。优点是分配效率很高,缺点是成批地回收内存,因为释放的内存无法直接重复利用。使用这种需要合理规划每块内存的管理区域,所以又叫做 “基于区域的” 内存管理。使用这种做法的分配器,举例有 Apache Portable Runtime 中的 apr_pool 工具。本文不讨论这种内存池。原理和结构概念和数据结构定长内存池有一些基本和必要的概念,需要定义在内存池的结构数据中。以下命名方式使用变体的匈牙利命名法,比如 nNext,n表示变量类型为整形。类似地,p表示指针。Memory Unit每次程序调用 MemPool_Alloc 获取一个内存区域后,会获得一块连续的内存区域。管理一个这样的内存区域的单元就成为内存单元 unit,有时也称作 chunk。每个 unit 需要包含以下数据:nNext:整型数据,表示下一个可供分配的 unit 的标识号。功能请参见后问pData[]:实际的内存区域,其大小在创建时由调用方指定Memory Block一个内存块,内存块中保存着一系列的内存单元。这个数据结构需要包含以下基本信息:nSize:整型数据,表示该 block 在内存中的大小nFree:整型,表示剩下有几个 unit 未被分配nFirst:整型,表示下一个可供分配的 unit 的标识号pNext:指针,指向下一个 memory blockMemory Pool一个内存池总的管理数据结构,换句话说,是一个内存池对象。pBlock:指针,指向第一个 memory blocknUnitSize:整型,表示每个 unit 的尺寸nInitSize:整型,表示第一个 block 的 unit 个数nGrowSize:整型,表示在第一个 block 之外再继续增加的每个 block 的 unit 个数函数接口作为一个内存池,需要实现以下一些基本的函数接口,或者说可以是对象方法:memPoolCreate()创建一个 memory pool,必须的参数为 unit size,可选参数为上文 memory pool 的 nInitSize 和 nGrowSize。memPoolDestroy()销毁整个 memory pool 并交还给操作系统。memPoolAlloc()从 memory pool 中分配一个 unit,其尺寸是预先定义的 unit size。memPoolFree()释放一个指定的 unit。工作过程现在我们用一个 unit size 为 1024、init size 为 4(每一个 block 有 4 个 units)的 memory pool 为例,解释一下内存池的工作原理。下文假设整型的宽度为 4 个字节。创建 memory pool程序开始,调用并创建一个 memory pool。此时调用的函数为 memPoolCreate(),程序会创建一个数据结构,相应的结构体成员及其取值如下:memory pool alloc当调用者第一次请求 memPoolAlloc() 时,内存池发现 block 链表为空,于是想系统申请内存,创建 memory block,并初始化如下(其中地址值为假设值):其中 nSize = 4112 = sizeof(memPool) + nInitSize * sizeof(memUnit)。每一个 nNext 依次加一,各指代着跟着自己的下一个 unit。最后一个 unit 的 nNext 值无意义,因此不说明其取值。然后返回需要的 unit 中的内存。返回内存的逻辑如下:内存池在 block 中查询 nFree 成员由于 nFree > 0,表示有未分配的 unit,因此继续在该 block 中查看 nFirst 成员nFirst 等于 0,表示该 block 中位置为 0 的 unit 可用。因此内存池可以将这个 unit 中的 pData 地址返回给调用方。 pData 的地址值计算方式为:pBlock + sizeof(memBlock) + nFirst * (sizeof(memUnit)) + sizeof(nNext) = 0x10010nFree 减一修改 nFirst 的值,标记下一个可用的 unit。注意这里的 nFirst 切切不能简单地加一,而是取返回给调用方的 unit 所对应的 nNext 的值,也就是下图(2)处原来的值 1将 pData 的地址值返回。为便于说明,这块区域我们标记为 CA操作后各数据结构的状态如下:第二次调用 alloc 的情况类似。调用后各数据结构的状态如下:memory pool free我们先看看结果:首先程序会检查 CA 的地址值,很快就会发现,地址 0x10010 位于上述第一个 block 的范围之内(0x10000 <= 0x10010 <= (0x10000 + 4112))。再计算偏移值可以很快得出其对应的 nNext 标号,也就是上图中的(2)位置。回收 unit,此时需要标记相应的成员值以标示 unit 的回收状态。首先查看 nFirst 的值,参见上前幅图,nFirst 的值为 3,表示位置(3)处的 unit 是可用的。因此我们首先把 (2) 处的 nNext 值设置为 3,将其加回到可用 unit 的链表中将 nFirst 的值修改为 0,也就是代表刚刚回收回来的 unit 的标号,而(2)处的值赋值为 2,表示b(3)的 unit其实可以看到,上面就是一个简单的链表操作。根据上面的过程,如果 CB 也释放了的话,那么 memory pool 的状态则会变成这样:到这个时候,由于整个 block 已经完全回收了(nFree == nInitSize),那么根据不同的策略,可以考虑将整个 block 从内存中释放掉。block 满我们回到 alloc 的逻辑中,可以看到内存池最开始会检查 block 的 nFree 成员。如果 nFree == 0 的时候,那么就会在该 block 的 pNext 中去找到下一个 block,再去检查 nFree。如果发现 block 链表已经结束了,那就意味着当前所有的 block 已满,必须创建新的 block。在实际设计中,我们需要考虑选取合适的 init size 和 grow size 值。从上面的算法中可以看到,如果 alloc/free 调用非常频繁时,第一个 block 的使用效率是非常高的。变体或改进有些简化的版本中,可以不使用 pNext 来维护链表,也就是只有一个 block,并且内存的使用有一个明确且受控的上限值。这经常用在没有 malloc 系统调用的 RTOS 或者是一些对内存非常敏感的嵌入式系统中。如果要用于多线程环境中,那么 memory pool 结构体需要加上锁参考资料《C++应用程序性能优化》 - 内存池 章节Memory Pool Basic Concepts相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 14, 2018 · 2 min · jiezi

AIOps 在腾讯的探索和实践

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由LemonLu发表于云+社区专栏赵建春 腾讯 技术运营通道主席 腾讯 社交网络运营部助理总经理 AIOps 白皮书核心编写专家我今天要讲的主题,AIOps,是一个比较新的话题,其实从概念的提出到我们做,只有差不多一年的时间。一个新事物,有其发展的周期,在腾讯里面我们做了比较多的探索,但是肯定还是有不足的地方,就像咱们看到的 AI 的发展也还有很多不足的地方。今天带来一些案例跟大家分享,希望对大家有一些借鉴和参考的意义。1 从一个 NLP 故事说起 首先我想从一个 NLP 小的故事来说起。在二十世纪三四十年代,人们大量尝试用机器的方式去理解自然语言,开始是用类似于左图一样的语法树的基于规则的方式处理的,但后来逐渐地变化为以统计的方式去做。到了二十世纪七十年代之后,基于规则的句法分析逐渐地走到了尽头。1972年的时候,自然语言处理领域大师贾里尼克加入了IBM。1974年左右,他在 IBM 提出了基于统计概念的语音识别概念,之后自然语音识别的效果就不断被突破。2005年谷歌基于统计方面的翻译系统全面超过基于规则的翻译系统,规则方法固守的最后一个语音识别的堡垒被拔掉了。可以看到二十世纪七十年代前,基于规则的自然语言处理的学者和专家们,注定是失落的。为什么用这个故事来做引子,是因为我觉得咱们的运维环境,每年会有大量的开发人员加入,写了大量的代码交给我们。随着业务量的增长,设备会不断增加,系统会越来越庞大,复杂度成指数级增长,而这些压力全给了我们,还有如我们的监控 log 等数据也是非常的海量。所以我觉得运维系统和自然语言处理是有相似之处的,语言是非常复杂的,量级也是非常大的。第二个,运维的经验,本质上就是一组组的规则。在我们的运维环境里,很多自动化的运维系统,就是一组组规则的实现。规则有一个好处,就是易理解,但往往有场景遗漏。规则肯定是一个人写的,一个人面对海量的数据时,处理这些问题会显得力不从心。AIOps 不是替代 DevOps 的,而是对 DevOps 的一个辅助和补充,是对里面规则化部分进行 AI 化的改造的过程。规则是我们的经验,也是我们的负担,就好比20世纪70年代前的那些专家,我们也需要进行一个转型。那什么是AI呢?AI就是从大量输入中总结出准确预测的规律(模型)。我们通过x1 x2 x3这样的大量的输入,通过统计一些参数,abcdwb这样的一些参数,让我们在新的环境中来拟合的预测做一些数值、01、概率型的预测。包括强化的学习,也是通过另外一种形式获取数据进行统计,通过每次的探测,你能知道这一次成功和受益是多少,是正收益或者是负收益,还是零收益。其实我们只要有足够的数据的量级,可以重复地去探测,并且获得反馈。易于接入 AI 的场景是特征清晰、正负特征易抽取,就是什么是对的,什么是错的,我们可以比较好分类,有持续地反馈。数据+算法+训练,可以训练出一个模型,这和之前的规则是有差异的,可以认为是一种有记忆功能的模型。但是如果要接触AIOps会发现一个问题,就是我可能是一个小团队,或者说我缺乏算法专家,还有即使用了别人的算法模型,我还希望了解这个算法的原理。最后一个是,提供算法的一方和使用算法的一方,都不愿意提供数据,担心数据泄露给对方,那双方都有这样一个担忧,这是面临的困难。那对于以前的运维的环境里面规则来讲,其实你可以认为他是API,或者是一些编写的逻辑处理,特点就是很少会变,因为是人编写的,所以容易理解,专家总结了,和数据无关,他写好就放在那里,类似 if-else,case swich 等。但是 在AI,前面讲了,其实是一组带有记忆能力的 API,这个记忆能力是从哪里来的,就是对数据有依赖的,从数据里面统计学习而来的,同时环境里面不断地在积累这个数据,可能不断有新的案例进来。所以这个模型时刻地在变,非常复杂,它可能是决策树的决策路径、回归参数或神经网络的网络结构及路径权重。因为它的各种的算法,决策树的神经网络的结构,以及他的权重,或者是回归参数相当复杂,这个不是人编写出来的,所以就难理解。2 从API到学件 所以 AIOps 我们可以来一个从 API 到学件的转变,“学件” 概念是南京大学的周志华老师提出来的,他是国内 AI 领域的泰斗级的人物,非常厉害,他提出学件是通过数据可以不断地学习,随着数据的不断地加入会更好,另外它的算法是公开的,你也可以了解它是怎么实现的。你也可以拿过来用,通过我的数据训练好模型后给你,但没有把数据交给你,把参数、网络结构这些东西交给你,并没有把数据交给你,来解决数据安全问题。你也可以用自己的数据重新去训练改进适应自己环境的模型,所以是可演进的。算法也是公开可了解的,拿来可以重用,来解决里面的一些问题。这是我们前一段时间和业界同仁一起编写的 AIOps 白皮书的一个能力框架,我不展开来细讲。我们大体的想法就是说最底层就是各种机器的学习算法,这个算法和我们的实际环境场景结合起来,通过训练一些单个的 AIOps 学件,单点场景也可以解决问题,之后把单点学件串联起来组成 AIOps 的串联应用场景,最终就可以形成一个智能调度的模型,去解决我们的运维环节的成本、质量、效率等运维关心的问题。AIOps 五级分类:一级,尝试应用二级,单点应用三级,串联引用第四是智能解决大多数比较重要场景的串联问题第五级,既然都提到AI,我们还是希望可以有更大的梦想。我们是否可以有一个就是像黑客帝国里的天网一样的智能运维大脑,能做到质量、成本、效率多目标优化。 比如在推荐场景里,我即希望用户规模越来越大,也希望活跃越来越高,同时希望他的消费水平越来越高,但是这三个目标是有冲突的,就像我们的成本和质量是有冲突的,但是我们希望它在多目标里有一个比较好的均衡,最高级别的时候,连多目标都可以进行同步的优化去平衡。总体来讲,希望 AIOps 是 DevOps 的一个补充,然后从单点到串联到智能调度这样一个过程,去解决运维里成本、质量和效率的问题。然后我们团队跟高效运维社区做了一些实践和理论方面的探索和尝试,今天也希望通过这几个单点的串联质量效率这些纬度跟大家分享一下。3 我们的实践案例分享1. 单点案例:成本 - 内存存储智能降冷单点第一个是成本,就是内存存储智能降冷,因为我们是社交网络业务,用户规模大,又有大量的访问,这样就导致团队喜欢用内存型的KV存储。上线的时候,请求量可能很高,但是随着时间的推移,他的数据量不断地增长,访问密度反而在下降,对我们的成本造成很大的压力。那大家会想到降冷,但是降冷之前大家都熟悉就是利用数据的最晚使用时间按规则处理,但是这个你想想其实只有一个指标,这个数据的最后使用时间,作为特征去分析,其实远远不够的。我们对每一类数据做了非常多的特征的抽样提取,有几十个特征,如周期的热度变化这些,就是如图上这些,还有一些没有写出来的。然后我们同学根据的经验,因为他们之前手工处理过很多,就会有一些经验,哪些数据条目是可以降冷的,把他标注之后,用逻辑回归和随机森林,去学习和训练,其实就是做分类,机器学习绝大部分都是做分类。做一个分类之后,上面是 LR 和 逻辑的回归,下面是随机森林。那在随机森林,在 30 棵树的时候效果最好,因为随机森林本来就是一个 bagging 的方法,对稳定性效果有提高。最终的效果就是说,我们把数据进行了一个下沉,把接近 90% 的数据,下沉到硬盘上,我们的访问量并没有下降,SS D数据没有造成访问压力,可以看到下沉和下降是非常精准的。而且这里面的数据延迟和成功率几乎没有变化,其实之前的同事通过人工的设置做下沉的设置,其实效率是非常的低,这个模块提升了 8 到 10 倍的下沉的效率,这是第一个案例是成本的。2. 单点案例:质量 — 统一监控去阈值质量,大家可以看到统一监控去阈值是很有意义的一件事情。监控有两种情况,一种是成功率的监控,它应该是一个直线,正常应该在 100% 左右,但它会往下掉。第二个就是类似于一个累计性的曲线,或者 CPU 的曲线,这个曲线监控其实是非常的千变万化的。之前我们可能是通过设置阈值的方法,最大值最小值,阈值设置这样的方式,去设置告警。这个曲线一直在变化,最大值和最小值也一直在变化,然后他的形式也非常的多变,也很难去设置这样的东西。那我们做了两种的方式第一个是成功率的方式,我们使用了 3sigma 方式,来自于工业界,是来控制产品的次品率的,如果是 3sigma 是 99.7% 是正品,其实用这个方式我们统计出来的告警里面,超过正常值范围里面的多少多我们认为是多少个次品,把它找出来。第二步用孤立森林,就是长的相似的一类的东西,是比较难分类的,要通过很多步才可以去到叶子节点上,所以看到这个 Gap,这一块就是说在比较浅的叶子的节点,就是异常的节点。我们通过第一步统计的方式,第二步的无监督方式找到一场。目前最后一步我们还是加了一些规则,让告警更可靠。这个规则其实就是看到我在什么时候告警和恢复,这样一个逻辑既然是一个规则,在未来我们会进一步做一个 AI 化的改造。那对于这个曲线型的监控,目前我们就是因为曲线不是属于正态分布的,一个曲线是一个曲线,所以极差很大。我们把它做了一个分段的 3sigma,就是一个小时一个段,过去7天进行一个采样。还有曲线我们可以用多项式去拟合这个曲线,我们用 3sigma、统计方法、多项式拟合几种方法作为第一步,就是相当于推荐系统里的多路召回。第二步依然就是孤立森林,和前面讲的原理一致。第三步就是有监督的人工标注,就是图上画圈的有些告警有一些不应该告警的标注,标注训练集后去训练自动地分类。为了获得更多的样本库,同事们用这个叫相关系数的协方差算法,寻找更多的样本库。大家可以关注一下,就是说去找一些相似的曲线,对训练不好的模型,就再进行打包去训练。总的方式,通过三级的过滤找到异常的告警。我们有十万多台设备,超过 120 万个监控视图,其实之前我们 70% 以上都没有设告警,因为很难每个都设一个最高值最低值,所以说目前就把这些模块都纳入到这个监控里面去,百分之百覆盖,这是一个监控区域值,去设置的一个案例。3. 串联应用案例:质量 — ROOT智能根源异常分析第三个案例,就是质量的串联案例,异常根因分析,其实我们的同学其实在很多的场合分享过。我们对我们的系统做了很多这样的访问关系统计,生成一个业务访问关系视图,就是业务的访问关系是什么样子的,最后就会画出这样一个图来,就像蜘蛛网一样,这只是其中的一个部分,但是故障发生之后,到底哪一个是根源的问题。根因分析最开始的做法,是通过先降维关系的方式,右图左边这一列全是同一个模块,由这个模块产生的每一条路径,我们都列出来,就是右边多条路径,这个路径模块里面,把告警出现叠加在模块上,然后设置一个人为定义的面积算法,从面积的大小,就判定哪个模块是异常的根源,虽然是基于规则的,但之前效果还不错,可以帮助我们找到可能是根源的 TOPN 告警,但现在我们又把它做了一些基于AI算法的更新。中间这一排,就是前面介绍的根因分析的主逻辑。在告警叠加前面,访问相关的模块才是导致根源告警的模块,所以我们先通过访问关系紧密度进行社团 Group 划分,把一些互相访问紧密的模块,做了一个聚类。然后告警严重程度接近的,互相导致告警的可能性也更高一些,再用 DBSCAN 类的密度聚类算法,进行聚类。最后再用频繁项集和相关系数等去找一些重复出现的,就是相关性的,贡献度和支持度这样的一些方式去找这个原因。另外我们在做交流的时候,也有人给我们提出一个建议,就是用贝叶斯算法来找TOP根因的概率,因为这个是一个概率性的统计,我们目前也在进行实验和测试。4. 智能调度案例:效率 — 织云全自动扩容再来看一个智能的调度的案例,我之前想智能调度是一个很宏大的目标,并不是只是有点像这样的东西是一个小的改进,那我们智能的全自控的扩容流程。之前我们在很多的场合讲过智能的工作的流程,他实际上把一个业务模块上需要的资源全部登记进去,之后通过申请设备、获取的资源,发布部署、发布自检、业务测试、灰度上线这样的 6 大步,实际上有二十多小步,我们会组合,组合成不同的流程,那我们看一下这个流程。(视频播放时对视频内容的简要解释)一个模块的自动扩容,先是添加一些资源,其实就是上线一个新的业务,但是正常情况下他是自动执行的。会有一些业务包,会有一些基础包,还有一些权限,这个权限也是基础化的东西。我们监控看到压力不断地增长, CPU 增长到 75% 以上,随着增长,我们发现这个系统压力超标了,系统自动启动了扩容,这个是加快的了好几倍的样子。这样一个过程,就是刚才列了 20 多步的扩容的步骤,就自动地在执行,执行之后企业微信提示对这个模块进行快速上线,然后监控到他有问题,就是快速地上线。上了两台新的设备,然后这两台新的设备,负载在增加,老的流量在逐渐地下降,最后达到一个平衡。这就是我们腾讯织云的全自动扩容,目前其中的容量预测及很多监控都已经经过了一定程度的AI化的改造。我还想重点讲下其中有一个叫平衡木的模块。为什么要平衡木,因为监控这一块也讲过了,平衡木对于一个模块来讲,一个模块几百台上千台的设备都有,虽然对它设的同样的压力权重,这是真实环境里的数据,就是上下差异非常大。然后我们通过平衡木这样的一个调整,就可以把上面这样的负载曲线,基本上可以缩成一条线,第二个的曲线容量就变成的 40%,也就是说你通过这样的一个调整,让你的支撑能力,增长了 22%,那它是怎么做的?实际上我们有一个机器学习,就是希望我们的模块里面,每台单机的负载,尽可能的一致,设置一个loss function,我们用梯度下降的方式,找到这个 loss function 里参数w1~wn的设置。之后通过几轮调整,使得所有设备的负载趋于一致。5. 更多单点或串联应用还有更多的单点和串联应用,其中一些由于之前在 GOPS 上海分享过,这里只是简述一下,做为一些案例参考。第一就是多维下钻智能分析,因为一个 APP 上线之后,你可能会有播放平台,运营商有域名,每个里面有几百个设备,运营商里面有多个运营商,一旦出现一个问题,有可能是魔方里面的块出现问题,很难定位。比如我们找到前后数据差异化很大,但访问量很小,那它就不是核心的,核心的就是差异性越大,贡献度越大的那些异常,就可能是出问题的那个。大家都知道啤酒尿布的案例,那频繁项集算法,就是 A 告警出现之后,B 告警肯定会出现,同时 AB 出现的概率还会超过一定的比例,我们就可以找到这样的一些东西,对他进行合并的告警。第三个就是我们一直有一个智能体检变更报告的东西,就像之前谷歌的讲师也讲到,谷歌通过各种的监控方式发现这次变更之后,是否出现故障。比如可能流量增高,或者有异常告警。我们的变更检测报告,会自动监测变更后模块的各项指标变化,来辅助工程师判断,这次变更是否正常,输出分 2 类,正常情况不输出可以忽略:一类是变更后导致了数据的大幅波动,但可能不是异常,如流量大幅增长;一类是变更后可能出现了异常,如产生了大量 coredump,需要去处理。如何处理也是一个二分类的问题,我们也会在未来改造。上一次也讲到我们智能客服的问题,利用 NLP 技术,可以做信息检索类、操作执行类的智能客服工具机器人。4 思考和展望做一点简单的思考和展望, AIOps 才刚刚起步,但是做的时候,可以考虑把它做成类似公共的组件这样的形式,就是学件,不同的是它们带了一种学习的结果在里面。有些场景是公司的内部的场景,可以在内部变成一个学件来用。但是对于一些共性的东西,比如说监控,各个公司的监控场景是一样的,如果我们把它定义成标准的上报的格式,标准的时间区间,训练好了 AIOps 的模型,大家认可了效果,就可以一起来使用。“学件”这个词,周志华老师也在多个场合去讲过。通过编写类似这些共识的上报格式,命名规范等,会让公共的 AIOps 组件变得更加的共享、共识,这个也是我们未来希望更多放到标准里面东西。这个也是AIOps的标准委员会存在的一个意义。下面这个图是我们织云的 Metis 平台,我们希望先在内部形成更多的学件库,再去解决一些串联的问题,刚才也举了一些例子,也希望在未来的几个月,或者一段时间里,开放出来跟大家一起学习,共同地探讨和改进,可能我的演讲就到这里了。最后用一句很有名的话做个总结:一般情况下,人们常常会高估一个新技术在一两年内的影响而低估其在未来五到十年的影响。而 AIOps 就是这么一个技术,作为 DevOps 的辅助和改进,相信它一定会让 IT 运维变的更简单从容,真正地成为运维同学,运维线的一个助手工具和智能化的大脑。谢谢大家。相关阅读【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

November 12, 2018 · 1 min · jiezi

“网红架构师”解决你的Ceph 运维难题

欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~本文由Tstack发表于云+社区专栏本文为长篇连续剧,将分多个篇幅发表,主要介绍了从动手部署环境到后期运营故障处理过程中常见的问题,内容由浅入深,是居家旅行运维Ceph的必备良药。Q1. 环境预准备绝大多数MON创建的失败都是由于防火墙没有关导致的,亦或是SeLinux没关闭导致的。一定一定一定要关闭每个每个每个节点的防火墙(执行一次就好,没安装报错就忽视):CentOSsed -i ’s/SELINUX=.*/SELINUX=disabled/’ /etc/selinux/configsetenforce 0systemctl stop firewalld systemctl disable firewalld# iptables -Fservice iptables stopQ2. 清理环境MON部署不上的第二大问题就是在旧的节点部署MON,或者在这个节点部署MON失败了,然后重新new再mon create-initial,请查看要部署MON的节点上的/var/lib/ceph/mon/目录下是否为空,如果不为空,说明已经在这个目录部署过MON,再次部署会检测子目录下的done文件,由于有了这个文件,就不会再建立新的MON数据库,并且不会覆盖之,导致了部署时的各种异常,这里就不赘述了,直接给出万能清理大法:对于任何需要新部署MON的节点,请到这个节点下执行如下指令,确保环境已经清理干净:ps aux|grep ceph |awk ‘{print $2}’|xargs kill -9ps -ef|grep ceph#确保此时所有ceph进程都已经关闭!!!如果没有关闭,多执行几次。rm -rf /var/lib/ceph/mon/*rm -rf /var/lib/ceph/bootstrap-mds/*rm -rf /var/lib/ceph/bootstrap-osd/*rm -rf /var/lib/ceph/bootstrap-rgw/*rm -rf /etc/ceph/rm -rf /var/run/ceph/请直接复制粘贴,遇到过好些个自己打错打漏删了目录的。Q3. 部署前最后的确认这里介绍的都是个案,不过还是需要提一下:确保每个节点的hostname都设置正确,并且添加至/etc/hosts文件中,然后同步到所有节点下。克隆出来的虚拟机或者批量建的虚拟机有可能发生此情形。确保以下目录在各个节点都存在:/var/lib/ceph//var/lib/ceph/mon//var/lib/ceph/osd//etc/ceph//var/run/ceph/上面的目录,如果Ceph版本大于等于jewel,请确认权限均为ceph:ceph,如果是root:root,请自行chown。Q4. 安装Ceph官网指导方法是使用ceph-deploy install nodeX,但是因为是国外的源,速度慢得令人发指,所以我们换到阿里的源,并且使用yum install的方式安装,没差啦其实,这样反而还快点,毕竟多个节点一起装。很多安装失败的都是因为没有添加epel源请在每个存储节点都执行以下指令,来安装Ceph:yum clean allrm -rf /etc/yum.repos.d/.repowget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repowget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.reposed -i ‘/aliyuncs/d’ /etc/yum.repos.d/CentOS-Base.reposed -i ‘/aliyuncs/d’ /etc/yum.repos.d/epel.reposed -i ’s/$releasever/7.2.1511/g’ /etc/yum.repos.d/CentOS-Base.repoecho “[ceph]name=cephbaseurl=http://mirrors.aliyun.com/ceph/rpm-hammer/el7/x86_64/gpgcheck=0[ceph-noarch]name=cephnoarchbaseurl=http://mirrors.aliyun.com/ceph/rpm-hammer/el7/noarch/gpgcheck=0” > /etc/yum.repos.d/ceph.repoyum install ceph ceph-radosgw -y这里是安装的hammer版本的Ceph,如果需要安装jewel版本的,请执行:sed -i ’s/hammer/jewel/’ /etc/yum.repos.d/ceph.repoyum install ceph ceph-radosgw -y如果安装了jewel版本的Ceph,想要换回hammer版本的Ceph,可以执行下面的指令:卸载Ceph客户端rpm -qa |grep ceph -v |awk '{print $3}' |xargs rpm -e –nodeps更改ceph.repo里面的Ceph版本sed -i ’s/jewel/hammer/’ /etc/yum.repos.d/ceph.repoyum install ceph ceph-radosgw -yQ5. ceph-deploy这里我要开启话唠模式:① Ceph-deploy 是什么?Ceph-deploy是Ceph官方给出的用于部署Ceph的一个工具,这个工具几乎全部是Python写的脚本,其代码位于/usr/lib/python2.7/site-packages/ceph_deploy目录下(1.5.36版本)。最主要的功能就是用几个简单的指令部署好一个集群,而不是手动部署操碎了心,敲错一个地方就可能失败。所以对于新人来说,或者说以我的经验,接触Ceph少于一个月的,又或者说,集群规模不上PB的,都没有必要手动部署,Ceph-deploy完全足够了。② Ceph-deploy怎么装?这个包在ceph的源里面:yum install ceph-deploy -y③Ceph-deploy装在哪?既然Ceph-deploy只是个部署Ceph的脚本工具而已,那么这个工具随便装在哪个节点都可以,并不需要单独为了装这个工具再搞个节点,我一般习惯放在第一个节点,以后好找部署目录。④Ceph-deploy怎么用?详细的指令暂时不介绍,下面会有,在安装好后,需要在这个节点新建一个目录,用作部署目录,这里是强烈建议建一个单独的目录的,比如我习惯在集群的第一个节点下建一个/root/cluster目录,为了以后好找。Ceph-deploy的所有的指令都需要在这个目录下执行。包括new,mon,osd等等一切ceph-deploy的指令都需要在这个部署目录下执行!最后一遍,所有的ceph-deploy的指令都要在部署目录下执行!否则就会报下面的错:[ceph_deploy][ERROR ] ConfigError: Cannot load config: [Errno 2] No such file or directory: ‘ceph.conf’; has ceph-deploy new been run in this directory?⑤ Ceph-deploy怎么部署集群?我们暂且把部署目录所在的节点叫做部署节点。Ceph-deploy通过SSH到各个节点,然后再在各个节点执行本机的Ceph指令来创建MON或者OSD等。所以在部署之前,你需要从部署节点ssh-copy-id到各个集群节点,使其可以免秘钥登陆。⑥Ceph-deploy部署的日志在哪里?就在部署目录下面的ceph-deploy-ceph.log文件,部署过程中产生的所有的日志都会保存在里面,比如你大半年前敲的创建OSD的指令。在哪个目录下执行ceph-deploy指令,就会在这个目录下生成log,如果你跑到别的目录下执行,就会在执行目录里生成log再记下第四点的错。当然,这个LOG最有用的地方还是里面记录的部署指令,你可以通过cat ceph-deploy-ceph.log |grep “Running command"查看到创建一个集群所需的所有指令,这对你手动建立集群或者创建秘钥等等等等有着很大的帮助!!!⑦ Ceph-deploy版本写这段时的最新的版本号为1.5.36,下载链接为ceph-deploy-1.5.36-0.noarch.rpm, 之前的1.5.35里面有点bug在这个版本被修复了,如果使用1.5.25部署遇到了问题,可以更新至这个版本,会绕过一些坑。更新到1.5.36之后,腰也不酸了,退了不疼了,Ceph也能部署上了。Q6. ceph-deploy new 做了什么进入部署目录,执行ceph-deploy new node1 node2 node3,会生成两个文件(第三个是ceph-deploy-ceph.log,忽视之):[root@blog cluster]# lsceph.conf ceph-deploy-ceph.log ceph.mon.keyringnew后面跟的是你即将部署MON的节点的hostname,推荐三个就够了,需要是奇数个MON节点。不要因为只有两个节点就搞两个MON,两个节点请用一个MON,因为两个MON挂掉一个,集群也就挂了,和一个MON挂掉一个效果是一样的。生成的ceph.conf默认情况下长成这样:[root@blog cluster]# cat ceph.conf [global]fsid = 13b5d863-75aa-479d-84ba-9e5edd881ec9mon_initial_members = blogmon_host = 1.2.3.4auth_cluster_required = cephxauth_service_required = cephxauth_client_required = cephx会调用uuidgen生成一个fsid,用作集群的唯一ID,再将new后面的主机加入到mon_initial_members和mon_host里面,剩下的三行大家都是一样的,默认开启CephX认证。下面有一节会专门介绍这个,需要注意的是,部署的时候,千万不要动这三行 下面会有一节介绍之。还有一个文件ceph.mon.keyring:[root@blog cluster]# cat ceph.mon.keyring [mon.]key = AQB1yWRYAAAAABAAhMoAcadfCdy9VtAaY79+Sw==caps mon = allow 除了key的内容不一样,剩下的都会是一样的。因为是开启了CephX认证了,所以MON直接的通讯是需要一个秘钥的,key的内容就是秘钥。是不是对Ceph里面的明文认证感到吃惊,有总比没有强。如果,你再次执行new,会生成新的ceph.conf和新的ceph.mon.keyring,并将之前的这两个文件给覆盖掉,新旧文件唯一不同的就是fsid和key的内容,但是对Ceph来说,这就是两个集群了。这里说一下我个人非常非常非常反感的一个问题,有的朋友喜欢在/etc/ceph/目录下面执行ceph-deploy的命令,这么做和在部署目录下面做一般是没有差别的,因为这两个目录下面都有ceph.conf和ceph.client.admin.keyring,但是我还是强烈推荐创建独立的部署目录,因为/etc/ceph目录是Ceph节点的运行目录,为了体现各自的功能性,也为了安全性,请不要在/etc/ceph**目录下部署集群!!!Q7. 为ceph-deploy添加参数Ceph-deploy的log还是很有看头的,查看ceph-deploy new blog(blog是我的一台主机)的log:[root@blog cluster]# ceph-deploy new blog[ceph_deploy.conf][DEBUG ] found configuration file at: /root/.cephdeploy.conf[ceph_deploy.cli][INFO ] Invoked (1.5.36): /usr/bin/ceph-deploy new blog[ceph_deploy.cli][INFO ] ceph-deploy options:[ceph_deploy.cli][INFO ] username : None[ceph_deploy.cli][INFO ] func : <function new at 0x288e2a8>[ceph_deploy.cli][INFO ] verbose : False[ceph_deploy.cli][INFO ] overwrite_conf : False[ceph_deploy.cli][INFO ] quiet : False[ceph_deploy.cli][INFO ] cd_conf : <ceph_deploy.conf.cephdeploy.Conf instance at 0x28eccf8>[ceph_deploy.cli][INFO ] cluster : ceph[ceph_deploy.cli][INFO ] ssh_copykey : True[ceph_deploy.cli][INFO ] mon : [‘blog’][ceph_deploy.cli][INFO ] public_network : None[ceph_deploy.cli][INFO ] ceph_conf : None[ceph_deploy.cli][INFO ] cluster_network : None[ceph_deploy.cli][INFO ] default_release : False[ceph_deploy.cli][INFO ] fsid : None[ceph_deploy.new][DEBUG ] Creating new cluster named ceph可以看到有很多的参数被列出来了,比如:mon : [‘blog’],也有很多参数是False或者None, 这些参数能否被设置呢? 因为这里我们可以看到有fsid : None 这个参数,难道集群的fsid可以被指定吗?抱着这些疑惑,我就去看完了ceph-deploy的所有代码,答案是:可以设置。所有上面的参数都可以使用参数的形式进行设置,只需要在前面加上两个–,比如对于fsid可以执行:ceph-deploy new blog –fsid xx-xx-xx-xxxx如果想要查看每个执行可指定的参数,可以-h:[root@blog cluster]# ceph-deploy new -husage: ceph-deploy new [-h] [–no-ssh-copykey] [–fsid FSID] [–cluster-network CLUSTER_NETWORK] [–public-network PUBLIC_NETWORK] MON [MON …]…optional arguments: -h, –help show this help message and exit –no-ssh-copykey do not attempt to copy SSH keys –fsid FSID provide an alternate FSID for ceph.conf generation –cluster-network CLUSTER_NETWORK specify the (internal) cluster network –public-network PUBLIC_NETWORK specify the public network for a cluster这里就可以看到可以指定–cluster-network,–public-network,等等,如果optional arguments里面没有介绍这个参数,可以直接使用–xxarg的方式指定,比如–overwrite-conf,–verbose等等,能不能设置这些参数,自己动手试一下就知道了。需要注意的是,参数的位置根据指令而异,比如–overwrite-conf参数是跟在ceph-deploy后面的,而–public-network是跟在new后面的:ceph-deploy –overwrite-conf –verbose new blog –fsid a-a-a-a[root@blog cluster]# cat ceph.conf |grep fsidfsid = a-a-a-aQ8. Public VS Cluster如果非要在刚刚生成的ceph.conf里面添加什么的话,那么可能就要加public_network或者cluster_network了。那么这两个配置项有什么用呢?这里简单得介绍下Ceph的Public(外网或者叫公网或者前端网)和Cluster(内网或者叫集群网或者叫后端网)这两个网络,在Ceph中,存在以下三种主要的网络通讯关系:client-> mon =>public : 也就是客户端获取集群状态,或者叫客户端与MON通讯走的网络,是走的外网。client-> osd => public : 也就是客户端向OSD直接写入数据走的也是外网。osd<-> osd => cluster :也就是OSD之间的数据克隆,恢复走的是内网,客户端写第一份数据时通过外网写,对于三副本剩下的两个副本OSD之间通过内网完成数据复制。当OSD挂掉之后产生的recover,走的也是内网。通常,我们会将外网配置为千兆网,而内网配置成万兆网,这是有一定原因的:客户端可能由成百上千的计算节点组成,外网配成万兆成本太高。存储节点一般只有几个到几十个节点,配置了万兆内网可以大大加快故障恢复速度,而且剩余的两副本写速度会大大加快,万兆网的性价比极高。举个例子,集群坏掉一个OSD千兆需要一小时,那么万兆网只需要五六分钟,一定程度上增加了集群的安全性。借用官网的这张图来说明集群的网络走势:再假设你的节点有两个网段172.23.0.1和3.3.4.1,还记得我们上一节ceph-deploy new的时候是可以指定public_network和cluster_network的吗!如果不指定这两个参数,那么ceph-deploy怎么知道用哪个IP作为这个节点的mon_host的IP呢,其实他是随便选的,如果它选了172网段但是你想使用3.3网段作为这个节点的mon_host的IP,那么只需要指定–public-network 172.23.0.0/24 就可以了,其中的/24就相当于一个掩码,表示前面的IP的前24位,也就是172.23.0.XXX,只要你的主机上有一个处于这个范围内的IP,那么就会选择这个IP作为公网IP。类似的,/16表示范围:172.23.XXX.XXX。 如果想指定内网IP,那么只要指定–cluster-network 3.3.4.1/24就可以了。一般情况下,会在new生成的ceph.conf文件里加入public_network配置项以指定公网IP。当然你的MON主机上需要有至少一个IP在公网范围内。除了在生成的ceph.conf文件中加入公网IP的方式,我们还可以使用参数的方式来指定公网IP:[root@ceph-1 cluster]# ceph-deploy new ceph-1 –public-network 172.23.0.0/24[ceph_deploy.cli][INFO ] Invoked (1.5.36): /usr/bin/ceph-deploy new ceph-1 –public-network 172.23.0.0/24[ceph_deploy.cli][INFO ] ceph-deploy options:…[ceph_deploy.cli][INFO ] public_network : 172.23.0.0/24…[ceph-1][DEBUG ] IP addresses found: [u'172.23.0.101’, u'10.0.2.15’][ceph_deploy.new][DEBUG ] Resolving host ceph-1[ceph_deploy.new][DEBUG ] Monitor ceph-1 at 172.23.0.101[ceph_deploy.new][DEBUG ] Monitor initial members are [‘ceph-1’][ceph_deploy.new][DEBUG ] Monitor addrs are [u'172.23.0.101’][ceph_deploy.new][DEBUG ] Writing monitor keyring to ceph.mon.keyring…[ceph_deploy.new][DEBUG ] Writing initial config to ceph.conf…[root@ceph-1 cluster]# cat ceph.conf [global]fsid = d2a2bccc-b215-4f3e-922b-cf6019068e76public_network = 172.23.0.0/24mon_initial_members = ceph-1mon_host = 172.23.0.101auth_cluster_required = cephxauth_service_required = cephxauth_client_required = cephx查看部署log可以发现参数配置已经生效,而这个节点有两个IP,public_nwtwork这个参数限定了公网IP的搜索范围,生成的ceph.conf文件内也包含了public_network这个参数。Q9. 参数是下划线还是空格分隔这里只是简单的提一下这个小困惑,对于以下的两个参数书写方式,哪种会有问题呢:public_network = 172.23.0.1/24public network = 172.23.0.1/24osd_journal_size = 128osd journal size = 128这两种参数的书写方式其实都是正确的,说到底是因为底层调用的是Python的argparse模块。这两种方式都是等效的,所以不需要担心。Q10. ceph-deploy mon create-initial如何一次性通过这一步坑哭了多少迫切加入Ceph世界的新人,看到的最多的就是5s,10s,10s, 15s,20s。。。然后报了错。再执行,再报错。所以这里给出以下的预检清单,如果被报错失败所烦恼,请认真执行各个子项,尤其是失败后要执行清理环境:请确保所有节点都安装了Ceph。请确保所有节点的防火墙等都关闭了。参考环境预准备一节请前往各个MON节点清理干净,不论你是否相信这个节点是干净的。参考清理环境一节。请确保各个MON节点下存在以下目录,并且对于Jewel版本及之后的请确保目录权限为ceph:ceph。参考部署前最后的确认一节。请在ceph-deploy new生成的ceph.conf内添加public_network配置项,参考Public VS Cluster一节。这些总结来之不易,我帮过上百个人解决过部署问题和集群故障。我相信在认真确认过之后是肯定可以通过的(反正前三点如果有问题一般是不会建好MON的,为什么不认真确认下呢),我遇到过绝大多数都是因为防火墙没关,或者手动删除了一些目录,或者没有修改权限导致的问题。相对来说,新环境只要关了防火墙就可以一次性通过,旧环境或者失败的环境只要清理环境就可以通过了。Q11. mon create-initial 做了什么简单介绍下流程:ceph-deploy读取配置文件中的mon_initial_members的各个主机,然后依次SSH前往各个主机:将部署目录下的ceph.conf推送到新节点的/etc/ceph/目录下。创建/var/lib/ceph/mon/$cluster-$hostname/目录。检查MON目录下是否有done文件,如果有则直接跳到第6步。将ceph.mon.keyring拷贝到新节点,并利用该秘钥在MON目录下建立MON数据库。在MON目录下建立done文件,防止重新建立MON。启动MON进程。查看/var/run/ceph/$cluster-mon.$hostname.asokSOCKET文件,这个是由MON进程启动后生成的,输出MON状态。在所有的MON都建立好后,再次前往各个主机,查看所有主机是否运行并且到达法定人群(quorum)。如果有没到到的,直接结束报错。如果都到达了,执行下一步。调用auth get-or-create方法创建(如果不存在)或者拉取(已经存在)MON节点上的以下几个keyring到部署目录中:ceph.bootstrap-mds.keyringceph.bootstrap-osd.keyringceph.bootstrap-rgw.keyringceph.client.admin.keyring指令结束。Q12. mon create-initial 为什么会失败我不喜欢讲怎么做,我愿意花很大的篇幅介绍为什么会造成各种各样的问题,如果知道了原因,你自然知道该怎么做,所以才会理解Ceph,而不是机械的去敲指令。综合上面的所有小节,我来总结下这一步失败的基本上所有可能的原因:所谓MON的quorum,相当于多个MON形成的一个群体,它们之间需要通过网络发送数据包来通讯达成某种协议,如果打开了防火墙,会阻断数据交流。所以不能构成群体,一直等待(5s->10s->10s->15s->20s)其他MON的数据包,既然被阻断了这样的等待是没有意义的,等了30s还没有正常,就可以直接ctrl+z去检查了。我在配置文件里面添加了pubilc_network,但是有个主机的所有IP都不在公网IP段内,那么这个MON是建不好的,因为没有IP用作MON使用,public_network相当于一个过滤器。搭好了一台虚拟机后,直接克隆了两台,没有修改主机名,导致socket文件路径名识别错误,报了异常,不过这很少发生。如果在旧的MON节点上再次部署新的MON,再又没有清理环境,之前的MON数据库会保留着done文件,MON数据库里面还是记录着之前fsid,keyring等等,和新集群是两套完全不同的,所以这个节点的MON自然到达不了MON群体。即使你单单删除了/var/lib/ceph/mon下的东西,而没有清理那些keyring,也有可能会因为收集了旧集群的秘钥而发生稀奇古怪的问题。对于Jewel,你一不小心删除了/var/lib/ceph/mon目录,或者其他的OSD目录或者/var/run/ceph目录,然后又重建了目录,依然部署不上,是因为Jewel的所有Ceph指定都是运行在ceph:ceph用户下的,自然不能在root权限目录下建立任何文件,修改权限即可。Ceph生成MON数据库是依照主机的hostname来命名至目录/var/lib/ceph/mon/${cluster}-${hostname}的,而检测SOCKET文件则是用ceph.conf里面的mon_initial_members里面的名字来检测的 ,如果mon_initial_members里面的名字和真是的主机名不一致,就会报错。 一旦你运行了ceph-deploy mon create-initial指令,并且失败了,有极大的可能性已经在某些节点建立好了MON的数据库,再次执行可能会因为旧的环境导致再次失败,所以如果失败了,执行一下第二节中的清理环境即可。清理完毕后,再执行ceph-deploy mon create-initial。相关阅读RMAN 配置、监控与管理Hadoop学习11–Ha集群配置启动rsync 服务部署详解 【每日课程推荐】机器学习实战!快速入门在线广告业务及CTR相应知识 ...

October 15, 2018 · 3 min · jiezi

有赞容器化实践

前言容器化已经成为一种趋势,它可以解决很多运维中的痛点,比如效率、成本、稳定性等问题,而接入容器的过程中往往也会碰到很多问题和不便。在有赞最开始做容器化是为了快速交付开发测试环境,在容器化的过程中,我们碰到过容器技术、运维体系适配、用户使用习惯改变等各种问题,本文主要介绍有赞容器化过程中碰到的问题以及采取的方案。有赞容器化的初衷在有赞同时会有很多个项目、日常在并行开发,环境的抢占问题严重影响了开发、测试和上线的效率,我们需要给每个项目提供一套开发联调(daily)、测试环境(qa),并且随着项目、日常的生命周期项目环境也会随着创建和销毁,我们最早的容器化需求就是怎么解决环境快速交付的问题。[有赞环境]上面是有赞大致的研发流程,在标准流程中我们有四套稳定环境,分别是 Daily 环境、Qa 环境、预发环境和测试环境。我们的开发、测试、联调工作一般并不会直接在稳定环境中进行,而是会拉一套独立的项目环境出来,随着代码经过开发、测试、预发验收最终发布到生产环境后再同步回 Daily/Qa 的稳定环境中。[项目环境]我们提供了一套以最小的资源投入满足最大项目并行度的环境交付方案,在 Daily/Qa 稳定环境的基础上,隔离出N个项目环境,在项目环境里只需要创建该项目所涉及应用的计算资源,其它缺失的服务调用由稳定环境提供,在项目环境里,我们大量使用了容器技术。[持续交付]后面我们又在项目环境快速交付的解决方案的基础上实现了持续交付流水线,目前已经有超过 600 套项目/持续交付环境,加上 Daily/Qa 稳定环境,涉及计算实例四五千个,这些计算实例无论是 cpu 还是内存使用率都是非常低的,容器化可以非常好的解决环境交付的效率问题,以及提高资源使用率来节省成本的投入。有赞容器化方案我们的容器化方案基于 kubernetes(1.7.10)和 docker(1.12.6)、docker(1.13.1),下面介绍一下我们在各个方面遇到的问题以及解决方案。网络有赞后端主要是 java 应用,采用定制的 dubbo 服务化方案,过程中无法做到整个单元全量容器化,和原有集群在网络路由上互通也就成了刚需,由于我们无法解决公有云上 overlay 网络和公有云网络的互通问题,所以一开始我们放弃了 overlay 网络方案,采用了托管网络下的 macvlan 方案,这样既解决了网络互通的问题也不存在网络性能问题,但是也就享受不到公有云弹性资源的优势了。随着有赞多云架构的发展以及越来越多的云厂商支持容器 overlay 网络和 vpc 网络打通,弹性资源的问题才得到了缓解。隔离性容器的隔离主要利用内核的 namespace 和 cgroup 技术,在进程、cpu、内存、IO等资源隔离限制上有比较好的表现,但其他方面和虚拟机相比存在着很多的不足,我们在使用过程中碰到最多的问题是容器里看到的 cpu 数和内存大小不准确,因为/proc文件系统无法隔离,导致容器里的进程"看到"的是物理机的 cpu 数以及内存大小。内存问题我们的 java 应用会根据服务器的内存大小来决定 jvm 参数应该怎么配置,我们是采用 lxcfs 方案来规避的。CPU 数的问题因为我们有超卖的需求以及 kubernetes 默认也是采用 cpu share 来做 cpu 限制,虽然我们使用了 lxcfs,CPU 数还是不准的。jvm 以及很多 Java sdk 都会根据系统的 CPU 数来决定创建多少线程,导致 java 应用在线程数和内存使用上都比虚拟机多的多,严重影响运行,其他类型的应用也有类似的问题。我们会根据容器的规格内置一个环境变量 NUM_CPUS,然后比如 nodejs 应用就会按照这个变量来创建它的 worker 进程数。在解决 java 类应用的问题时,我们索性通过 LD_PRELOAD 将 JVM_ActiveProcessorCount 函数覆盖掉,让它直接返回 NUM_CPUS 的值[1]。应用接入在容器化之前,有赞的应用已经全部接入到发布系统,在发布系统里已经标准化了应用的打包、发布流程,所以在应用接入方面成本还是比较小的,业务方无需提供 Dockerfile。nodejs, python,php-soa 等用 supervisord 托管的应用,只需要在 git 仓库里提供 app.yaml 文件定义运行需要的 runtime 和启动命令即可。java 标准化启动的应用业务方无需改动java 非标准化的应用需要做标准化改造镜像集成容器镜像我们分了三层,依次为 stack 层(os),runtime 层(语言环境),应用层(业务代码和一些辅助agent),应用以及辅助 agent 由 runit 来启动。由于我们的配置还没有完全分离,在应用层目前还是每个环境独立打包,镜像里除了业务代码之外,我们还会根据业务的语言类型放一些辅助的 agent。我们一开始也想将各种 agent 拆成多个镜像,然后每个 pod 运行多个容器,后来因为解决不了 pod 里容器的启动顺序(服务启动有依赖)问题,就把所有服务都扔到一个容器里去运行了。我们的容器镜像集成过程也是通过 kubernetes 来调度的(会调度到指定的打包节点上),在发布任务发起时,管控系统会在集群中创建一个打包的 pod,打包程序会根据应用类型等参数编译代码、安装依赖,并且生成 Dockerifile,然后在这个 pod 中使用 docker in docker 的方式来集成容器镜像并推送到仓库。为了加速应用的打包速度,我们用 pvc 缓存了 python 的 virtualenv,nodejs 的 node_modules,java 的 maven 包等文件。另外就是 docker 早的版本里,Dockerfile ADD 指令是不支持指定文件属主和分组的,这样会带来一个问题就是需要指定文件属主时(我们的应用是以 app 账号运行的)需要多运行一次 RUN chown,这样镜像也就多了一层数据,所以我们打包节点的 docker 版本采用了官方比较新的 ce 版本,因为新版本支持 ADD –chown 特性。负载均衡(ingress)有赞的应用内部调用有比较完善的服务化和 service mesh 方案,集群内的访问不用过多考虑,负载均衡只需要考虑用户和系统访问的 http 流量,在容器化之前我们已经自研了一套统一接入系统,所以在容器化负载均衡上我们并没有完整按照 ingress 的机制来实现 controller,ingress 的资源配置是配在统一接入里的,配置里面转发的 upstream 会和 kubernetes 里的 service 关联,我们只是做了一个 sync 程序 watch kube-api,感知 service 的变化来实时更新统一接入系统中 upstream 的服务器列表信息。容器登录和调试在容器化接入过程中开发会反馈是控制台比较难用,虽然我们优化了多次,和 iterm2 等的体验还是有所不足,最终我们还是放开了项目/持续交付环境这种需要频繁登陆调试的 ssh 登陆权限。另外一个比较严重的问题是,当一个应用启动后健康检查有问题会导致 pod 一直在重新调度,而在开发过程中开发肯定是希望看到失败现场的,我们提供了调试发布模式,让容器不做健康检查。日志有赞有专门的日志系统,我们内部叫天网,大部分日志以及业务监控数据都是通过 sdk 直接打到天网里去了,所以容器的标准输出日志仅仅作为一种辅助排查问题的手段。我们容器的日志收集采用的是 fluentd,经过 fluentd 处理后按照天网约定的日志格式打到 kafka,最终由天网处理进入 es 做存储。灰度发布我们涉及到灰度发布的流量主要包含三部分:用户端的 http 访问流量应用之间的 http 调用应用之间的 dubbo 调用首先,我们在入口的统一接入上统一打上灰度需要用的各种维度的标签(比如用户、店铺等),然后需要对统一接入、http client 以及 dubbo client 做改造,目的是让这些标签能够在整个调用链上透传。我们在做容器灰度发布时,会发一个灰度的 deployment,然后在统一接入以及灰度配置中心配置灰度规则,整个链路上的调用方都会感知这些灰度规则来实现灰度发布。标准环境容器化标准环境的出发点和项目环境类似,标准稳定环境中的 daily,qa,pre 以及 prod 中超过一半运行在低水位的服务器的资源非常浪费。因为成本考虑 daily,qa,pre 里都是以单台虚拟机运行的,这样一旦需要发布稳定环境将会造成标准稳定环境和项目环境的短暂不可用。虚拟机交付速度比较慢,使用虚拟机做灰度发布也比较复杂。虚拟机往往会存在几年甚至更长的时间,运行过程中操作系统以及基础软件版本的收敛非常麻烦。标准环境容器化推进经过之前项目/持续交付的上线和迭代,大部分应用本身已经具备了容器化的条件。不过对于上线来说,需要整个运维体系来适配容器化,比如监控、发布、日志等等。目前我们生产环境容器化准备基本完成,生产网已经上了部分前端 nodejs 应用,其他应用也在陆续推动中,希望以后可以分享更多生产环境中的容器化经验。结束语以上是有赞在容器化上的应用,以及在容器化过程中碰到的一些问题和解决方案,我们生产环境的容器化还处于开始阶段,后面还会碰到各种个样的问题,希望能够和大家互相学习,后面能够有更多的经验分享给大家。参考文献[1] https://github.com/fabianenar… ...

September 29, 2018 · 1 min · jiezi