文献总结 - [2015] Hidden Technical Debt in Machine Learning Systems

一、主旨

使用ML来支撑业务系统并不像看上去那么速赢(quick win),它往往会带来高昂的后期维护开销。本文旨在警示ML从业者(increase the community’s awareness),从技术负债的角度,论述了在做ML系统设计时需要注意的容易产生负债的几个方面:系统边界、耦合、反馈循环、消费者、数据依赖、配置问题等等。

二、内容

三、Key takeaways

  1. 令人不安的趋势:开发和部署ML系统很快很便宜,但后期维护它们既难且贵

    […] a wide-spread and uncomfortable trend […] : developing and deploying ML systems is relatively fast and cheap, but maintaining them over time is difficult and expensive.

  2. 并非所有负债都是不好的,但所有负债都要支付利息/都有代价;偿还技术负债的方式有很多,例如重构,其目的不在于增加新功能,而是在于使后续优化成为可能、减少错误、增强可维护性

    Not all debt is bad, but all debt needs to be serviced.

    Technical debt may be paid down by refactoring code, improving unit tests, […]. The goal is not to add new functionality, but to enable future improvements, reduce errors, and improve maintainability.

  3. 在系统设计时考虑技术负债能够提前认识到加速工程落地会为系统维护带来的长期开销

    […] help reason about the long term costs incurred by moving quickly in software engineering.

  4. 相比一般的软件系统,ML系统更易带来技术负债,除了传统的软件系统维护问题之外,还有很多ML专有的问题(ML-specific issues),并且很多负债是系统层面上的,不是代码层面上的,所以传统的通过优化代码来减轻负债的方法是不够的

  5. ML模型的黑盒化严重,容易导致大量的含有固定假设/写死的东西的胶水代码或适配校准模块

    ML packages may be treated as black boxes, resulting in large masses of “glue code” or calibration layers that can lock in assumptions.

  6. 纠缠问题(entanglement),CACE principle: Changing Anything Changes Everything 牵一发动全身;输入n维特征,其中1维的数据分布变化会导致其他n-1维特征的权重变化;增加特征也是类似的问题;移除特征也是类似的问题;除了输入信号(input signals)以外,超参数、配置、抽样方法、收敛阈值、数据筛选等等都遵循CACE原则。

  7. 依赖负债是增加传统软件系统代码复杂度的重要原因,而对于ML系统,数据依赖是依赖当中更难发现的一种

  8. 未充分使用的数据依赖(underutilized data dependencies),还是依赖,因为不确定是否可以切断

  9. 典型的未充分使用的数据依赖

    1. 遗留特征(legacy features),过去试验性使用过,随着模型迭代被其他特征替代,但未移除
    2. 捆绑特征(bundled features),一组特征放在一起使用,但迫于ddl,未深究任一特征均有用
    3. 微增值特征(epsilon-features),为了极小的准确率提升而引入的性价比低的特征
    4. 相关特征(correlated features)
  10. 难以明晰的反馈循环会导致分析负债,即难以保证系统自身变更会从哪些方面影响自己

  11. 使用通用的类库往往会导致大量的胶水代码,后者从长期角度看是高维护成本的

    Using generic packages often results in a glue code system design pattern, […]. Glue code is costly in the long term […]

  12. 胶水代码(glue code)和流水线丛林(pipeline jungles)这类集成问题的根因往往是科研和工程的分离

    Glue code and pipeline jungles are symptomatic of integration issues that may have a root cause in overly separated “research” and “engineering” roles.

  13. 通过增加if-else分支可以快速地在一套基准代码上扩展出新方案并投入实验,但长此以往会遗留很多条件分支,难以维护,难以端到端回归测试,进而难以保证上到生产系统后不会有意外情况进入不该进入的分支,Kight Capital血淋淋的教训,45分钟内损失4650万美元。

  14. 应推崇研究与工程融合的文化,一碗水端平,模型层面准确率的提升应当与系统层面复杂度的降低有同等的重要性

    It is important to create team cultures that reward deletion of features, reduction of complexity, improvements in reproducibility, stability, and monitoring to the same degree that improvements in accuracy are valued.

    Paying down ML-related technical debt […] often only achieved by a shift in team culture. Recognizing, prioritizing, and rewarding this effort is important for the long term health of successful ML teams.

  15. 在快速发展中的ML系统团队对于减少负债和采用好实践是不自知的或不屑一顾的,但负债和代价是随时间而逐步显现的

  16. 在发展过程当中多问自己的问题:

    1. 以生产尺度测试一个全新的算法有多容易?
    2. 数据依赖的完整传导过程是什么?
    3. 一次变更对系统的影响可以被细致观测到什么程度?
    4. 改进模型或输入信号(signal)会使其他模型效果变差吗?
    5. 团队的新成员多久可以习得实战能力?
  17. Maintainable ML

  18. 为了微小的准确率提升而付出大幅增加系统复杂度的代价,这种研究方案是不可取的(Reasonable ML?)

文献总结 - [2020] Borg the Next Generation

原文链接:Borg: the Next Generation

一、主旨

Borg在2011年公开了当年五月的真实负载记录,并在2019年再次公开了当年五月的新一份真实负载记录。文章尝试分析对比两份真实数据,以窥Borg这些年的发展。

二、内容

略。
文章结构不重要,客观的数据对比可以再次精读,这里仅对有建设性的分析做摘取和总结。

三、关键点

1. 资源需求的极端多样以及资源占用的极端长尾分布

Borg集群中,不同任务对资源的需求是极端多样的,比世界上处理最多样化的计算任务的超算中心还要更多样1-2个数量级。
资源占用方面,1%的任务要占用超过99%的资源总量(CPU和内存皆如此),0.1%的任务都要占掉超过93%的资源总量,相较于普遍认知的80-20规律,有着更加极端的长尾效应。
其实这是符合常理的,当集群规模变得巨大,业务众多,任务量爆炸且各不相同,必然会带来资源需求的多样性;而集团资源必然向核心业务倾斜,一定会有极少量业务要拿走大部分资源,带来实际资源使用上的长尾效应。

2. 超售比的提高带来整体的资源利用率上升

离线任务采用BE资源的情况显著增加,作为资源利用率提升的主要原因。

The average utilization has increased over 8 years, mostly due to an increase in consumption from the best-effort batch tier […]

CPU的超售比从2011年的1.25上升到2019年的1.5。
内存的超售在2011年几乎没有(Borg2015中将内存称为incompressible resource),但2019年也有了1.5的超售比。
鉴于2019年的数据涵盖了8个集群,在分集群视角上,有1个集群的内存超售比超过了2,另有2个集群的内存超售比接近2。

3. 痕迹数据/监控数据的采集

尽可能多地采到各方面的细节。
自动化地验证一些invariants,例如:实际占用的计算资源数量应小于集群的资源总量、一个任务的“提交”操作在时间维度上应当早于“结束”。文章指出,在实际采数据的时候往往会出现反直觉的数据表现,从而影响了数据本身的可信度,并且,自动化的验证流程应当尽早纳入数据采集的过程中。当这些反直觉现象出现时,应当研究其背后的原因并做出合理的解释。

Given the vagaries of large-scale trace data collection, we found that most of these invariants were violated occasionally. […]
Automated validation […] one-off scripts to a repeatable pipeline […] In retrosepct, we should have started with that […]

4. 可解释的调度是研究方向

两方面好处:(1)面向运维,可以明晰集群的整体运行状态;(2)面向用户,可以有指导性作用。

It would be nice to be able to provide explanations for why the scheduler made the decisions it made - either to help system operators understand what is going on (or is about to), or to provide guidance to end users on how they could better use the cluster.

四、引申

  1. Borg2019数据集:J. Wilkes. Google cluster-usage traces v3. Technical report at https: //github.com/google/cluster-data, Google, Mountain View, CA, USA, Nov. 2019.
  2. 阿里的数据集:Alibaba cluster data: using 270 GB of open source data to understand Alibaba data centers. Blog post, url = https://www.alibabacloud.com/blog/594340, Jan. 2019.
  3. Azure的数据集:Azure Public Dataset. https://github.com/Azure/AzurePublicDataset. Accesses 2020-03.
  4. Borg2011数据集的分析:E. Cortez, A. Bonde, A. Muzio, M. Russinovich, M. Fontoura, and R. Bianchini. Resource central: Understanding and predicting work- loads for improved resource management in large cloud platforms. In 26th Symposium on Operating Systems Principles (SOSP), pages 153–167, Shanghai, China, 2017. ACM.

文献总结 - [2016] Burns_Borg, Omega, and Kubernetes

原文链接:Borg, Omega, and Kubernetes: Lessons learned from three container-management systems over a decade

一、主旨

Google内研或主导的三个集群管理/编排系统,三者各有不同的起始目标,因此其架构和特点也各有不同。但K8s作为Borg和Omega的形式后继,有着后发优势,发扬了前两者的一些优势,也吸取了前两者的一些教训,此文可从一定程度上深化我们对K8s一些decisions的理解。

Lessons learned from three container-management systems over a decade

二、内容梳理

三、重点分析

  1. 架构的扩展性和可持续性/一致性 (consistency) 对于高速发展中的系统非常重要,业务扩张必然带来需求增加,如果不能做到新功能实现与原有系统的一致/连贯,必然会对系统带来额外的复杂度,不论是开发、维护还是部署、使用。
  2. 容器技术为系统架构、云计算、数据中心、PaaS均带来突破性变革(普遍共识)。
  3. K8s的一致的控制循环——调谐:从顶层业务系统视角出发,拆分成不同的系统组件,基于容器,包装成不同的对象,每个对象有不同的控制器,但控制逻辑均为调谐,通过一个个小的控制循环达成整体业务系统的控制。
  4. 原文举例说明了一个非常实用的在生产环境在线排查问题的方法:(带入Anylearn视角)后端出问题,卡死或响应异常,定位到某个pod后,可以直接扔掉该pod的标签(该pod还在正常存续中),让上层的ReplicaSet或其他replication controller的selector过滤不到这个pod,便会再启动一个pod(因为它认为丢失了一个)。这样一来,既可以“重启”后端的副本,恢复业务,同时可以留住pod,保存现场,乃至上到pod内排查问题,两不耽误。
  5. 建模其实也非常重要。Google对于Borg最不满意的一点就是其基于index的容器组织逻辑,容器数量成规模后,中间丢一个index就可能会有问题,维护所有的index-container mapping也是个麻烦事。基于label的组织和过滤就要灵活得多。带入Anylearn视角,我们现在的数据建模可能需要三思的有:训练项目与训练任务的从属关系模型、资源与算法/数据集/模型/文件的继承关系模型、模型未拆分成细粒度对象、文件未拆分成细粒度对象,等等。

四、引申

做分布式锁(本文中提到选主相关应用)的Chubby: Burrows, M. 2006. The Chubby lock service for loosely coupled distributed systems. Symposium on Operating System Design and Implementation (OSDI), Seattle, WA.

文献总结 - [2015] Large-scale cluster management at Google with Borg

原文链接:Large-scale cluster management at Google with Borg

一、主旨

Borg是Google内部从21世纪初就开始研发的大规模集群管理系统,属于底层基础架构,服务上层业务应用的部署和运行。
Borg对K8s有深远意义,从某种意义上可以说Borg是K8s的前身,但两者并不一样,且时至今日在Google内部Borg也并没有被K8s替代,应当也没有被替代的趋势。

二、内容

三、对K8s的影响

  1. 应用级别的多构件关联
    1. Borg中只有job概念可以串联多构件
    2. K8s中可通过label来松散串联多构件
  2. 网络地址空间
    1. Borg中所有任务共用节点IP和端口,容器端口直接映射节点端口,空间狭小
    2. K8s中pod自带网络地址空间,上层还有svc可以封装和控制是否映射到节点端口
  3. 用户倾向
    1. Borg最初目的是优先服务大团队
    2. K8s社区决定玩家结构
  4. 多容器的调度单元——pod
    1. Alloc in Borg: app + logsaver + dataloader 模式,分段开发维护
    2. Pod in K8s: init containers + helper containers/sidecars
  5. 运维的可观测辅助
    1. Debugging information
    2. Events
  6. Master即内核——Borgmaster in Borg = kube-apiserver in K8s

四、同类系统

SysName Ref
Apache Mesos B. Hindman, A. Konwinski, M. Zaharia, A. Ghodsi,A. Joseph, R. Katz, S. Shenker, and I. Stoica.Mesos: aplatform for fine-grained resource sharing in the data center.InProc. USENIX Symp. on Networked Systems Design andImplementation (NSDI), 2011
YARN V. K. Vavilapalli, A. C. Murthy, C. Douglas, S. Agarwal,M. Konar, R. Evans, T. Graves, J. Lowe, H. Shah, S. Seth,B. Saha, C. Curino, O. O’Malley, S. Radia, B. Reed, andE. Baldeschwieler.Apache Hadoop YARN: Yet AnotherResource Negotiator.InProc. ACM Symp. on CloudComputing (SoCC), Santa Clara, CA, USA, 2013.
Tupperware A. Narayanan.Tupperware: containerized deployment atFacebook.http://www.slideshare.net/dotCloud/tupperware-containerized-deployment-at-facebook,June 2014.
Apache Aurora (retired) Apache Aurora.http://aurora.incubator.apache.org/, 2014.
Autopilot https://aurora.apache.org/
Quincy (on Borg) M. Isard, V. Prabhakaran, J. Currey, U. Wieder, K. Talwar,and A. Goldberg.Quincy: fair scheduling for distributedcomputing clusters.InProc. ACM Symp. on OperatingSystems Principles (SOSP), 2009.
Cosmos P. Helland.Cosmos: big data and big challenges.http://research.microsoft.com/en-us/events/fs2011/helland\_cosmos\_big\_data\_and\_big\_challenges.pdf, 2011.
Apollo E. Boutin, J. Ekanayake, W. Lin, B. Shi, J. Zhou, Z. Qian,M. Wu, and L. Zhou.Apollo: scalable and coordinatedscheduling for cloud-scale computing.InProc. USENIXSymp. on Operating Systems Design and Implementation(OSDI), Oct. 2014.
Fuxi Z. Zhang, C. Li, Y. Tao, R. Yang, H. Tang, and J. Xu.Fuxi: afault-tolerant resource management and job schedulingsystem at internet scale.InProc. Int’l Conf. on Very LargeData Bases (VLDB), pages 1393–1404. VLDB EndowmentInc., Sept. 2014.
Omega M. Schwarzkopf, A. Konwinski, M. Abd-El-Malek, andJ. Wilkes.Omega: flexible, scalable schedulers for largecompute clusters.InProc. European Conf. on ComputerSystems (EuroSys), Prague, Czech Republic, 2013.
Kubernetes https://kubernetes.io/

五、文献引申

  1. 大规模系统的关键因素:J. Hamilton.On designing and deploying internet-scaleservices.InProc. Large Installation System AdministrationConf. (LISA), pages 231–242, Dallas, TX, USA, Nov. 2007.
  2. 系统性能评测的建模:D. G. Feitelson.Workload Modeling for Computer SystemsPerformance Evaluation.Cambridge University Press, 2014.
  3. 资源利用率相关指标和实验:A. Verma, M. Korupolu, and J. Wilkes.Evaluating jobpacking in warehouse-scale computing.InIEEE Cluster,pages 48–56, Madrid, Spain, Sept. 2014.
  4. 资源调度worst-fit:Y. Amir, B. Awerbuch, A. Barak, R. S. Borgstrom, andA. Keren.An opportunity cost approach for job assignmentin a scalable computing cluster.IEEE Trans. Parallel Distrib.Syst., 11(7):760–768, July 2000.
  5. 真实工作负载记录(数据集):J. Wilkes.More Google cluster data.http://googleresearch.blogspot.com/2011/11/more-google-cluster-data.html, Nov. 2011.
  6. 上述数据集的使用:
    1. 数据集分析:C. Reiss, A. Tumanov, G. Ganger, R. Katz, and M. Kozuch.Heterogeneity and dynamicity of clouds at scale: Googletrace analysis.InProc. ACM Symp. on Cloud Computing(SoCC), San Jose, CA, USA, Oct. 2012.
    2. 使用:O. A. Abdul-Rahman and K. Aida.Towards understandingthe usage behavior of Google cloud users: the mice andelephants phenomenon.InProc. IEEE Int’l Conf. on CloudComputing Technology and Science (CloudCom), pages272–277, Singapore, Dec. 2014.
    3. 使用:S. Di, D. Kondo, and W. Cirne.Characterization andcomparison of cloud versus Grid workloads.InInternationalConference on Cluster Computing (IEEE CLUSTER), pages230–238, Beijing, China, Sept. 2012.
    4. 使用:S. Di, D. Kondo, and C. Franck.Characterizing cloudapplications on a Google data center.InProc. Int’l Conf. onParallel Processing (ICPP), Lyon, France, Oct. 2013.
    5. 使用:Z. Liu and S. Cho.Characterizing machines and workloadson a Google cluster.InProc. Int’l Workshop on Schedulingand Resource Management for Parallel and DistributedSystems (SRMPDS), Pittsburgh, PA, USA, Sept. 2012.
  7. Borg后续
    1. 2016_Burns_Borg, Omega, and Kubernetes: Lessons learned from three container-management systems over a decade
    2. 2020_Tirmazi_Borg: the next generation

Failed to initialize NVML: Driver/library version mismatch

显卡驱动与NVML库版本不一致问题。

可到/usr/lib/x86_64-linux-gnu/路径下查找libnvidia-ml*相关软链和文件:

1
2
3
4
5
root@10-101-72-17:/usr/lib/x86_64-linux-gnu# ll | grep libnvidia-ml
lrwxrwxrwx 1 root root 17 Jan 29 01:08 libnvidia-ml.so -> libnvidia-ml.so.1
lrwxrwxrwx 1 root root 26 Jan 29 01:08 libnvidia-ml.so.1 -> libnvidia-ml.so.470.103.01
-rw-r--r-- 1 root root 1828056 Jan 6 20:11 libnvidia-ml.so.470.103.01
-rwxr-xr-x 1 root root 1823960 Nov 15 15:58 libnvidia-ml.so.470.42.01*

一般libnvidia-ml.so为软链指向libnvidia-ml.so.1又为软链指向一个libnvidia-ml.so.xxx.yyy.zzz的文件,xxx.yyy.zzz为NVML版本号。

如果该目录下存在多个不同版本号的NVML动态链接库文件(如上述代码段中所示),则有可能手动安装过显卡驱动并随驱动安装了一个版本的NVML,而系统又因各种原因新安装了另一个版本,导致驱动版本与NVML版本不匹配。

这里一个可能的原因是Ubuntu的系统自动更新,可通过查阅/var/log/apt/history.log日志,看是否自动更新过libnvidia或libcuda等可能与显卡相关的组件。

简单粗暴的解决方案:重启节点并重新安装驱动,手动安装或apt均可,保证将驱动和NVML刷成一致的版本即可。

另一个不想重启的解决思路(未验证):如果是手动安装的驱动,由系统更新造成了版本问题,不重启时直接重装原来装过的驱动时会失败,或许可以下载与系统更新出的NVML版本完全一致的驱动文件再手动安装。

Linux传输工具rsync

简介

远程传输工具rsync的命令语法与scp相似,原理与scp不同,最主要的特性是支持增量传输和断点续传(-P选项),先抄一段帮助文档的使用说明:

1
2
3
4
5
6
7
8
9
Usage: rsync [OPTION]... SRC [SRC]... DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST:DEST
or rsync [OPTION]... SRC [SRC]... [USER@]HOST::DEST
or rsync [OPTION]... SRC [SRC]... rsync://[USER@]HOST[:PORT]/DEST
or rsync [OPTION]... [USER@]HOST:SRC [DEST]
or rsync [OPTION]... [USER@]HOST::SRC [DEST]
or rsync [OPTION]... rsync://[USER@]HOST[:PORT]/SRC [DEST]
The ':' usages connect via remote shell, while '::' & 'rsync://' usages connect
to an rsync daemon, and require SRC or DEST to start with a module name.

在做目录级的传输时,需要特别注意的是,源目录路径末尾有没有斜杠是会有不同效果的:

  • 带斜杠,如/data/,则同步/data目录下的所有文件到目标路径下
  • 不带斜杠,如/data,则同步/data目录到目标路径下,作为子目录

一些有用的options

  • -a 全家桶选项,组合了多个其他选项(-rlptgoD),基本无脑使用,并且对于目录来说-a是包含-r
  • -z 对于未压缩的文件做压缩传输(gzip)
  • -P 断点续传
  • -e 通过ssh连接时,如果需要走key,可以写成-e "ssh -i /path/to/sshkey"
  • --bwlimit 限制网络传输速度,值的单位为KB/s

例子

Push to remote

1
rsync -azP /data/ user@1.2.3.4:/data

将本地/data目录下的所有文件推送到远程1.2.3.4机器的/data目录下。

Pull from remote

1
rsync -e "ssh -i ~/.ssh/mykey" --bwlimit=3000 -azP user@1.2.3.4:/data/mydata.tar /data/

~/.ssh/mykey作为ssh key,限制速度约为3MB/s,将远程1.2.3.4机器的/data/mydata.tar文件拉取到本地/data目录下。

Kubernetes Finalizer机制

一、Finalizer简介

Finalizer是K8s资源删除流程中的一种控制机制

在现实中,K8s中的资源往往不是完全独立和无状态的,2个资源对象之间可能会有依赖关系,随意删除一个对象可能会对依赖它的其他对象产生影响。因此,这些复杂的资源对象的删除流程也需要引入复杂的处理逻辑。

Finalizer就是服务于这类需求的一种机制,它可以在资源的删除过程中增加一个步骤,为这类复杂的删除逻辑的实现提供了可能。

举一个不太严格的例子,这里我们可以类比RDBMS里的外键:book表的作者字段外键关联到writer表。当我们不定义外键的ON DELETE逻辑时,一个writer条目是无法先于他/她的著作books被删除的。我们有至少两种处理writer deletion的方式:(1)定义外键ON DELETE,可以是CASCADESET NULL等等;(2)在外部系统中实现先删除books再删除writer的逻辑。两种处理方式的根本思想都是,在切实地删除writer之前,先“清理干净”它的依赖关系。Finalizer就可以想象成这个“清理”操作的插槽。

再举一个不太严格的例子,我们还可以类比很多面向对象的编程框架中的对象生命周期钩子(hook),比如Android框架中Activity的onDestroy和Vue框架中的beforeDestroy,在一个对象即将被删除之前,调用一个通过钩子函数/方法/扩展插槽定义的一套处理逻辑。Finalizer也可以想象成这种钩子插槽。

二、Finalizer原理

实际上,Finalizer的原理非常简单优雅,它在K8s对象上的存在形式其实是一系列标签,类似annotations。它本身并不定义流程细节或实现具体逻辑。K8s在接收到一个资源对象删除请求时,会先在对象上打上一些标记,包括deletionTimestamp,表示该对象已进入删除流程,当检测到对象上有finalizers标签(通常在资源对象的metadata字段中)时,删除流程会被挂起,直到所有finalizers标签被移除时,才继续进行删除流程(后面还会经历其他类似的阶段,例如ownerRefenreces,最终才会实际删除对象),如下图所示:

(Finalization状态转移图,引自:https://kubernetes.io/blog/2021/05/14/using-finalizers-to-control-deletion/)

在删除流程因finalizers标签被挂起时,对象控制器(controller-manager)通过对finalizer标签的捕获,可以得知这里可能有在删除对象之前需要处理的事情,再由控制器或其他组件/控制器实际去处理这些事情。当控制器认为所有该处理的事情都处理得当的时候,控制器来移除finalizers标签,从而将删除流程进行下去,直到最终实际删除掉对象。处理finalizers的思路是很开阔的:可以是主动地由本对象的controller来直接处理相关的清理工作,也可以是被动地等待外部组件或其他对象的控制器去完成它们的工作。

三、K8s原生Finalizer示例

K8s的一些原生资源对象会自动被加上一些finalizers。由于这些对象的控制器也是原生的,在不做扩展的情况下,这些原生的finalizers都是被定义好的,不应随意添加不被原生控制器识别的finalizers,避免无法删除对象的问题。

  1. PVC和PV分别原生自带kubernetes.io/pvc-protectionkubernetes.io/pv-protectionfinalizers标签的,顾名思义,其目的在于保护持久化存储不被误删,避免挂载了存储的工作负载产生问题。

  2. Namespace也是自带一个kubernetes的finalizers标签的,只不过,不同于其他资源对象的metadata.finalizers标签,ns是spec.finalizers,其作用是相同的。

四、某系统中的Finalizer实践

类似Pod-PVC-PV的挂载依赖关系,实际工作的某系统当中的后端组件(以CRD进行了抽象,称Backend)与自研存储(亦以CRD进行了抽象,称Cave)之间也有这层挂载依赖。在日常的开发调试以及部署时的蓝绿、红黑、灰度等过程中,经常要对后端组件进行卸载重装。如果在卸载时,先删除了Cave的话,那么挂载了它的后端容器便无法优雅删除。这是因为Cave本质是基于NFS的文件存储,而NFS的设计是在客户端unmount阶段中依然需要NFS服务端进行响应,若Cave已删除,则相当于NFS服务端失去响应,那么客户端的unmount操作会一直被挂起。

不止于此,由于Kubelet是处理工作负载的总控制程序,unmount这个操作也是由它去实际执行的,unmount的挂起也造成Kubelet处理unmount的子进程被挂起,从而导致Kubelet本身也无法再优雅停止。当然,这并不会造成集群系统的不可用,但当我们需要重启Kubelet时,就会造成Kubelet与集群失联。这也是之前某次生产集群开启Feature Gates时真实遇到的问题(因需要重启Kubelet)。比较straightforward的解决方法就是重启大法……但可想而知,这会对集群的可用性产生影响。

因此,我们应当确保在删除一个Cave组件之前,挂载了它的后端工作负载均已被删除。这个思想与pv-protection如出一辙。

我们之前已有Operator实现了Cave的控制器,Cave的Finalizer机制便是在Cave控制器的调谐方法中进行扩展的。

cave_controller.goview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
caveProtectionFinalizer := "abc/cave-protection"

// IN RECONCILATION

// Some reconcilation logic in before...

if cave.ObjectMeta.DeletionTimestamp.IsZero() {
// Cave is not being deleted:
// append finalizer to protect cave from being deleted before backend & hpo
// otherwise backend & hpo might fail to unmount cave
// which make kubelet hang
if !containsFinalizer(cave.GetFinalizers(), caveProtectionFinalizer) {
controllerutil.AddFinalizer(&cave, caveProtectionFinalizer)
if err := r.Update(ctx, &cave); err != nil {
reqLogger.Error(err, "unable to add finalizer on cave resource", "cave", cave.Name)
}
return ctrl.Result{Requeue: true}, nil
}
} else {
reqLogger.Info("got deletion request", "cave", cave.Name)
// Cave is being deleted:
// the deletion should be pending until backend pods are terminated
if containsFinalizer(cave.GetFinalizers(), caveProtectionFinalizer) {
// List backend pods
var backendPods v1.PodList
if err := r.List(ctx, &backendPods, client.InNamespace(req.Namespace), client.MatchingLabels{"app": "anylearn-backend"}); err != nil {
reqLogger.Error(err, "unable to list backend pods")
return ctrl.Result{}, err
}
// Count pods using current cave
nbOccupants := 0
for _, pod := range backendPods {
for _, v := range pod.Spec.Volumes {
if v.Name == cave.Name && v.NFS.Server == cave.Status.ServiceIP {
nbOccupants++
reqLogger.Info("Pod still using cave", pod.Name, cave.Name)
}
}
}
// Remove finalizer if backend pods are gone so that cave deletion can be truely proceeded
if nbOccupants == 0 {
controllerutil.RemoveFinalizer(&cave, caveProtectionFinalizer)
if err := r.Update(ctx, &cave); err != nil {
reqLogger.Error(err, "unable to remove protection finalizer from cave resource", "cave", cave.Name)
return ctrl.Result{Requeue: true}, err
}
// End of reconcilation
return ctrl.Result{}, nil
} else {
// Still got some pods using this cave, requeue the reconcilation
return ctrl.Result{Requeue: true, RequeueAfter: 20 * time.Second}, nil
}
}
}

// Some reconcilation logic in after...

可以看到,我们在调谐Cave时,首先要确保当一个Cave未被请求删除时,即deletionTimestamp为空时,用来保护Cave不先于后端被删除的Finalizer要加装到Cave对象上(升级考虑,旧系统已部署的Cave未采用Finalizer,而更新升级时我们不希望卸载重装Cave)。再来,当Cave已被请求删除时,判断finalizers标签是否存在,如果不存在则说明该Cave删除前的清理工作已处理完成,可以正常删除,便就此跳出调谐,交由K8s接管真正的Cave删除工作。若finalizers仍存在,则说明上一次调谐过后,还存在有与此Cave对象有挂载关系的后端pod,那么这里再重新list出所有后端pods,并计算出与此Cave对象有挂载关系的后端pods的数量(具体条件在本系统中由挂载卷名称name和挂载属性nfs.server共同决定):若数量为零,则移除finalizers并跳出调谐,交由K8s接管Cave的删除;若数量不为零,则强制安排间隔一段时间后的下一次调谐,以期在稍后的调谐时后端可以被清理干净。

五、相关资料

踩坑Nvidia显卡驱动及CUDA

这周新机器和显卡逐渐到位,开始装显卡驱动和CUDA,对过程中遇到的一些问题做个总结。

Boot

显卡总共有4种:A100、RTX2080Ti、K80和K40(包括K40c和K40m两种,这里不做区分),系统是Ubuntu20.04。一上来直接尝试用apt装驱动,所有机器统一装了nvidia-driver-460-server,结果几乎所有机器都重启失败,只能重装系统。这里推测是apt里的n卡驱动会把Xorg桌面程序装上并带起来,但处理不善导致系统无法启动。重装后手动切了booting target到命令行,避免桌面GUI的启动:(相关链接

1
sudo systemctl set-default multi-user.target

A100

驱动>450,cuda>11
用apt带的nvidia-driver-460系列安装后有几率无法重启,原因未知。官网cuda安装包,按装440 cuda10.2会报错,报显卡驱动的安装错误,错误码256。cuda11以上的runfile安装完成后,提示可以通过/usr/local/cuda/bin/cuda-uninstaller进行卸载,但实际上这个卸载器并不存在。而10.2的runfile安装包是装了这个卸载程序的。cuda11安装完成后,需要进一步ln几个库才能正常使用:

1
2
3
4
cd /usr/local/cuda-11.4/targets/x86_64-linux/lib && \
ln -s libcusolver.so.11 libcusolver.so.10 && \
ln -s ../../../extras/CUPTI/lib64/libcupti.so libcupti.so && \
ln -s ../../../extras/CUPTI/lib64/libcupti.so.11.4 libcupti.so.11.0

K80

可以装440+cuda10.2也可以装470+cuda11.4,相对来说兼容性更好,直接通过cuda runfile安装即可,另需按cuda11安装后所需的ln操作。

K40c/K40m

用apt带的nvidia-driver-460系列安装后有几率无法重启,原因未知。用官网cuda runfile安装包会报错,同样是报显卡驱动的安装错误,错误码256。其实是470驱动不支持k40,k40最高只能到460(截至20210708)。按官方support matrix,460驱动也可以搭配cuda11.4,因此可以官网下载460纯驱动先进行安装,安装前要停掉ubuntu默认的Nouveau驱动:

1
2
3
4
5
6
vi /etc/modprobe.d/blacklist-nouveau.conf

blacklist nouveau
options nouveau modeset=0

update-initramfs -u

装完驱动后,再用cuda11.4的runfile装cuda,安装时去掉自带的470驱动安装,仅安装cuda。这样安装完后就是460驱动的k40c/k40m+cuda11.4了。但是这样装完,daemon-reload或重启之后,nvidia-smi会报driver/library version mismatch。可能还是需要装cuda11.2才能完美匹配。

Harbor新手村

简介

Harbor是云原生的开源的通用artifact registry,CNCF毕业项目。它主要支持docker images和OCI-compatible artifacts相关的镜像管理,以及针对K8s构件的Helm chart管理。其特色为面向生产的artifact治理、安全保障和审计管理。

功能介绍

Harbor官方总结的系统特性有五个方面:

  1. 私有化便捷部署
  2. 多租户
  3. 镜像治理
  4. 安全与合规
  5. 可扩展性

其中镜像治理安全合规是重中之重,覆盖了镜像一致性、磁盘占用增长、镜像部署策略等镜像管理相关的重要问题。

阅读全文

Flask同步启动子线程问题

原有需求

Flask提供一套后端RESTful API,另有一个入口负责直连TCP传输文件

原有逻辑

Flask通过自带的dev WSGI启动,同时通过子线程启动一个socketserver标准库的ThreadingTCPServer,称之为FileSocketServer,这个socket server依赖Flask后端的部分组件来处理文件存储和数据库读写。我们把所有业务逻辑去掉,Flask只保留一个根路径的接口,socketserver处理请求(流式)时只是简单地向一个文件写入REQ,用来确认它是否正常工作。

app0.pyview raw
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from socketserver import StreamRequestHandler, ThreadingTCPServer
import threading

from flask import Flask


class DummySocketHandler(StreamRequestHandler):
def handle(self):
with open("socket.log", "a") as f:
f.write("REQ\n")


def init():
socket_server = ThreadingTCPServer(("0.0.0.0", 32470),
DummySocketHandler)
threading.Thread(target=socket_server.serve_forever).start()


app = Flask(__name__)
app.debug = True

@app.route('/')
def home():
print("Hey", flush=True)
return "Hey"


if __name__ == '__main__':
init()
app.run(host="0.0.0.0", port=8888)

另外准备了一个简单的脚本对socketserver尝试建立连接。

test_connect.pyview raw
1
2
3
4
5
6
7
8
9
10
import socket

if __name__ == '__main__':
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.connect(("0.0.0.0", 32470))
print("Connection OK")
except Exception as e:
print("Connection KO")
print(e)

阅读全文