Zehao Liu

一个普通人类,自由的呼吸者

[手册][置顶]关于这个博客的介绍

1. 技术内容在职业生涯进入第三年时,我对写博客的动机变得越来越强烈。这种动机有两个: 一源自对混乱局面的无法容忍。进入任何新领域都伴随着对许多未知事物的摸索,以及对自己未曾了解的事情的无知。通过完成一个又一个的任务,我逐渐积累了更多的知识,也发现了很多之前从未涉足的领域。然而,每天却总有一种不爽的感觉。 常常,我意识到自己虽然忙碌工作,却没有深入了解常用工具,技术栈显得杂乱无章,稍微深入的问题就容易陷入困境。另一个角度,在技术品味方面,我有很多想深入了解的技术,却缺乏一个突破口,总是束手无策。因此,两种力量推动着我: 巩固基础:整理常用的,但是从来没有认真整理过的事物 突破边界:向往已久,但是没有仔细研究过的事物 二源自对中文技术博客现状的不满。除了写博客,我更多的时候在读博客。当今的中文博客内容质量堪忧,我常常读到一些杂乱的拼凑型博客,作者自己都不知道自己在写啥。我的理念是博文需要有明确的目的,能够做到这点的一个好办法就是有一个明确的写作目标分类方式,具体怎么做呢?我在下面提供了一种参考。 2. 情感内容我感觉除了技术,我也想借博客的平台分享一些我对人生和世界的见解。曾经,我一直将自己视为一个学生,不太好意思将自己对人生的思考写下来。一是担心将来自己或者他人看到时会觉得幼稚可笑,二是本身也是不太成熟的一些零散观点,没有写下来的必要。然而,随着年岁渐长,经历逐渐丰富,我的三观也变得更加完善自洽,我觉的已经逐渐趋于稳定。这也是一条充满着痛苦的探索之路。回想多年前,我对人生的种种深感迷惘,内心焦虑无助。为了理解人生、社会和世界(请别笑,真的),我尝试过各种事情。比如,疯狂阅读了100多位名人的传记,尝试理解宗教,参加禅修班,从事各种销售工作,向各种人请教关于人生意义的问题… 甚至,我谈起第一段恋爱,其中一个动机都是因为听说谈恋爱可以认清自我。 这些年,我接触的人也更加多元化,但与背景无关,我发现大多数人的选择往往是回避,或者只是机械地引用一些没有经过深入思考的旁人的观点。比如为了家庭、为了丰富人生、为了财富等等。我观察到这类人其实并不在少数,由于他们的“三观”来源于他人,没有一个自洽体系的支持,事实上难以自圆其说,因此他们的很多观点和在做一些决策时会左右摇摆,行为信念也难以坚定。虽然很多人认为讨论三观是一个无聊且无用的问题,但在我看来,它却是非常有趣的。一个人的三观体现了他对周围世界的看法、对自己人生的看法、以及由以上推论而出的对事物价值的判断。而这一切,又在潜移默化中影响着一个人的很多行为。 比如"放羊的孩子"这个问题就曾让我反复思考,多次推翻之前的结论,最终得到了可以自圆其说的结果。这个问题的题面是这样的: 一伙外来者来到一个封闭的山村,看到一个放羊的孩子。 他们问:“孩子,你将来想要做什么?” 孩子说:“放羊卖钱娶媳妇” 他们问:“娶完媳妇之后想要做什么?” 孩子说:“生孩子” 他们问:“生了孩子想让他做什么?” 孩子说:“继续放羊!” 我觉得不同的三观的人面对这一问题的感受是截然不同的。对于我来说,第一次听到,我代入了外来者的角色,只觉得“可悲无奈”。后来我有所体悟,开始带入孩子的角色,觉得“绝望恐惧”。再后来我终于顿悟了,我依然选择把自己带入孩子角色,区别是我终于觉察到了“心安喜悦”。 我会尝试用文字来描述我自己这种三观的是如何得以自洽的。它可能并不能得到很多人的认可,因为相当多的环节本质上是一种信仰,而信仰层面的问题又与一个人的成长过程和周遭环境是密不可分的。但我想,这种分享可以给许多人一个启发,即在仅依靠自身的情况下,身为一个渺小的人,依然可以坦然的面对不可避免的死亡,面对生命的虚无,面对人生不确定性的痛苦。一个自洽稳定的三观让人获得根本上的自信,让人明白“我”以何种方式“活着”,我相信这是超越财富,阶级和时空的一个问题,我相信这是极有意义的。 当然,跟上面技术问题一样,情感问题的论述同样需要好的分类方式支撑。并且情感类的问题有一个特殊的难点,非常依赖于层层递进,过于跳跃会导致论述难以被接受。所以我在下面提供一个我理解的分类方式作为骨架来支撑。 3. 博文的分类和命名从上文可以大概看出博文的类型主要有两种,一种是技术类的,一种是情感类的。技术类的还可以细分成两类:技巧类和认知类。 这样的话,就大概可以和Bloom’s taxonomy对应上: 技巧领域 认知领域(基于知识的) 情感领域(基于情感) Bloom分类法对于一个博客来说有点过于复杂,我将它简化并加入自己一些理解,针对这三类问题,以认知阶段重新划分: 技巧领域 比如shell命令,油猴脚本,部署配置,面试算法或者健身减肥这种问题。 其认知过程我理解为:发现问题–>采取行动–>信息收集总结–>重复练习–>形成机制–>可以组合单个过程对复杂问题反映–>熟练掌握。 这种问题对于博客来讲,有帮助的是信息总结和练习过程经验。最好是既有信息总结,又能给经过验证有效的练习方式。这种问题将被命名为: [手册]xxx 比如[手册]linux常用命令 认知领域(基于知识的) 相比于第一类问题,这种问题主要是复杂更高,大多已经形成一个互相关联的体系了。比如代码架构,面试架构设计,如何理解动态规划是回溯的记忆表优化,谈谈对百万qps网关的建设等这种问题。 其认知过程我理解为: 记忆:大多是在技巧领域的基础上来的,所以如果还停留这个层面就可以还是 [手册] 理解:有了一定量的案例和记忆,已经可以总结出一些共同的特点了。这个层面通常是接触了一种技术一段时间,但是了解还不够全面。命名为 [总结] 应用:已经可以用这些特点来对新问题进行解决问题。比如学习了nginx, 也看到了公司里很多的使用案例,当有一天有一个新的需求需要搭建一个正向代理,可以马上根据之前的经验完成搭建。这种情况已经有了一定的经验,可以实践了。命名为[实践] 分析:了解了所有的组成部分,可以把每个组成拆开来分析了。命名为[深入理解] 合成: 可以很轻松的修改,把其中一部分改成自己想要的,不影响整体运行。命名为[魔改] 评估:了解了这意味着已经对此种技术和同等的技术都有了一定的理解,可以将这种博文命名为[对比] 情感领域(基于情感) 情感博文的写作是相当复杂的。由于不可证伪和立足于信仰的特点,只能通过论述的方法来进行描述,很需要一个好的论述阶段划分方式: 同感:如果一个想法无法引起他人的共鸣的话,这个问题实际上对他人就是不存在的。这个阶段只谈问题,避免论述。这个层面我取名[浮日闲谈] 回应:如果可以共鸣,但实际上这个问题不被他人认为是一个值得思考的问题,那也是无法进行下去。这个阶段可以谈单个问题我认可和不认可的思维模式,但应该避免过多发散, 目标主要是提出问题。取名[夜半趣谈] 关联:这个阶段将跟我的三观联系起来,论述这种观点为什么可以跟这种三观融合,不冲突,又或者为什么冲突。取名[夜半再谈] 表征:用抽象的概念论述。如果有人直接看这个阶段的文章可能会觉得莫名其妙,或者反了他的三观,不建议直接阅读。取名[夜半再谈谈] (额外补充)答疑:随着我的读者朋友越来越多,很多人给我提了一些很有趣的问题,我觉得可以作为一个好的补充。取名为[你问我答] 4. 未来会展开的技术主题 k8s认证考试:cka&cks证书考试。虽然很多大佬说没意义,但对我入门来说还是挺有用的 golang:常用技术栈整理 python:常用技术栈整理 代码架构:读代码架构之道和凤凰架构 vim:从入门到入土 k8s 开发:go-client编写operator教程和源码分析教程 nginx整理:常用配置和八股文 网关技术整理: dns, 四层负载均衡和代理,七层负载均衡和代理,ecmp和vrrp 中间件: zookeeper和etcd整理 系统相关: 有些是常识,但是其实略复杂的东西 运维命令整理: 就算ceo站身后也能行云流水的查问题 运维工具整理:grafana+promethues+exporter监控体系, jenkins+ansible运维脚本 数据库相关: mysql,redis 5.

[实践]网关技术

前言网关的主要功能说起来很简单,就是实现域名解析和路由转发。但是当具体落地到公司项目中的时候事情就有点复杂了起来, 从链路上分析可以具体分为一下的类别: DNS: 主要负责内部域名解析,外部访问域名解析。qps非常大,稳定性要求极高,需要做好的负载均衡策略。且作为每台机器都配置的组件很可能需要经常迁移,需要vip。 NLB(Network Load Balancer): 即4层负载均衡,早前为LVS。对性能要求很高,出现了基于DPDK技术改造的DPVS。 ALB(Application Load Balancer):7层负载均衡。通常为魔改nginx。 SNAT(Source Network Address Translation): 出口网关,其技术跟NLB差别不大,实际上可以理解为一种4层代理服务。 7层代理:有4层代理,自然也有7层代理。技术跟ALB是类似的。 负载均衡手段—ECMP: 基于BGP等价路由技术的负载均衡技术,具体软件有很多,本人使用过Bird。这个技术对机房网络结构有要求,不是所有的机房都有条件用。 负载均衡手段—VRRP: 基于虚拟路由器冗余协议的负载均衡技术。准确的说这是一种冗余技术,做不到负载均衡。 衍生相关—注册中心:网关对接k8s的话,需要转发到具体容器,由于容器本身处于不断变化中,我们需要一个注册中心来记录这种变化。本人接触比较多的是zookeeper 衍生相关—-etcd: etcd通常用于存一些配置信息,方便跨region发布,便于回滚等。 当然上面也只是大致的分类,实际到具体场景,还得继续拆分成具体的产品。下面根据我的理解我来挨个分析分析。 DNSDNS成熟的软件已经很多,光我接触过的就有: Coredns, Ubound, DPDK Ubound。 其实现标准在RFC 1034 - RFC 1035也已经定义的很清楚了。所以改造空间其实有限,除了最基本的功能,还有一些功能是需要实现的: 实现控制面和数据面分离。通过公司平台让用户配置,下发到具体机器上。由于DNS的配置通常不太大,通常都是CNAME,A type。直接数据面通过api访问控制面问题也不会太大。 实现内部域名和外部域名的统一管理。相当于既可以解析内部用户的域名,也可以在内部域名不能访问时,去公有DNS拉取公有域名。这里存在一个问题,即对于公有域名的缓存策略应该怎么做。这个问题在我的职业生涯中遇到过很多次。 权重。应该支持同一个域名配置多个权重不同的解析。以实现用户可以通过更改权重,切换不同的后端,也方便进行一些测试。 循环检查。dns如果存在域名循环解析,可能直接导致集群崩溃。 除此之外,就是最重要的负载均衡策略。我理解最优的策略就是ECMP。

[夜半趣谈]虚无主义和当代青年2

前言我一直很是纠结要不要把三观这种东西写下来,因为据我观察很多人的三观(世界观,人生观,价值观)是混乱的,并未能够自洽。而在这种情况下,很容易出现立场对立。所以在我正式写之前,我一定要申明我的观念。 我认为一个人看待世界/人生/价值的方式,本身是由其自己的成长经历和思想观念所决定的。一个所谓‘自洽’的三观本质上是对以往所有本已经接受的观念的去杂存精。换句话说,这个事情在我看来就是一个人的选择倾向而已,是没有什么对错的。 为什么我很追求‘自洽’?因为如果不自洽,在很多事情的选择上会左右摇摆,将无法真正认清自己想要什么。此时,我们只能通过“比较”的方式去给自己的选择做定位,在很多时候,这样的选择是很内耗的。消除这种内耗最好的方式就是:自己给自己一个明确的逻辑。不再左右摇摆,举棋不定。 为什么我要看你的三观?我从来不会鼓励任何人去接受我的三观,我认为这是很不理智的行为。讨论三观问题本身是一件很难找到答案的事情,因为它既非人类逻辑内的推理问题,它包含了相当多的不可解释的部分,我们只能用信仰去填补;也不是一个可以“证伪”的科学问题,难以得出一定范围内的正确答案。所以,我将三观写下来,仅仅是给个参考,如果你接受不了也是很正常的。作为身边朋友的“情感垃圾桶”,曾经有一些朋友告诉我他们内心的迷茫和痛苦,这种感受我也有过,非常感同身受。我试图帮助他们梳理人生的三观,从根本上去探寻原因。可这真的不容易,知识爆炸的年代,我们的一生相信了太多太多的“观点”,他们矛盾的共存在在我们的脑海中,我很少能够见到最终将他们梳理完成,并形成自己“道”的人。当然,这所谓的“道”本质上也只不过是自己给自己熬的心灵鸡汤,毕竟每个人都将面对“死亡”,“衰老”,“不确定”的终极问题。当熬出了这碗鸡汤,我们或许能更轻松坦然的走完这场人生之路吧。 世界观:如果相信世界是虚无的,是否更容易放下?我相信每个人在出生的时候,不会有人会问出世界是不是假的?这么一个看着有点傻的问题。我从思想实验“缸中之脑”得到启发,开始思考这一问题。“缸中之脑”讲的是如果人的所有的感官情绪都是通过神经信号产生的,那么有没有一种可能,我们每个人其实都只有一个大脑,浸泡在一个智能的水缸中,Ø›我们所有的感受不过是超级电脑的模拟。 这个当年的问题现在已经在各种文艺作品中展现,也不是什么新鲜的观点,比如《西部世界》《楚门的世界》等等。而无独有偶,各种宗教和思想中,也都能找到类似的观点,比如“庄周梦蝶”,佛教对“空”的阐述。其实世界是不是假的?这个问题是无解的,我们永远难以辩驳这个观点。更深刻的原因是人的能力是受限的。我们所看到的世界,无非是光照到物体上折射到我们眼睛中罢了,你能说我们看到的就是世界真实的样子嘛?红外线看不到就不是真实存在的吗?触觉,嗅觉,听觉又何尝不是如此。甚至于人类的逻辑真的能够彻底的理解这个世界吗? 人生观:如果人总有一死,生命的意义究竟在哪里?我在[浮日闲谈]虚无主义和当代青年中写过一段话。我认为“任何的事情,总是咋一看很有意义,却经不起拉长看放大看”。我相信所谓的这些“意义”,都是构建在一个又一个故事上面,比如每个人都听过“成功”会获得幸福的故事,听过“家乡”需要热爱的故事,听过“努力”最终获得回报的故事。但这些故事是否经的住时间,空间的考验呢?当我们把时间拉长到一千年,一万年,这些意义还存在吗?或者,短一点,一百年吧,当你已经死去,过去所苦苦追求的意义,所谓的成功是否只是一场梦幻泡影呢? 价值观:如此消除所有的意义,人生岂不是毫无价值?非也,我只是想探寻探寻向外界追求的所谓“意义”是否是真的可靠的。我相信上面这些朴素的道理,所有人都是明白的,至少是想过的。只是,当红尘滚滚而来,生活忙忙碌碌,无暇进一步思考罢了。但如果我们放弃物质的追求,只相信世界的空虚,生命的虚无的话,岂不是人人都要“出家”?这明显也是不合理的。我们如何在两个不合理之间,找到人生的价值,使得我们心安理得生活呢?我观察,这是很多人包括曾经的我痛苦的根源之一。我相信我们需要找到内心的动力,但是对于物质的追求又是不可能放弃的,这需要我们向内心去追寻一种可以和外界要求不冲突的力量。每个人可能不一样,我会讲讲我所找到的力量,但是就像在[手册][置顶]关于这个博客的介绍中讲到的,[夜半趣谈]的目标是让你看到这里,让这个问题成为我们的共识,是一个值得讨论的问题,但不会去做具体论述。我将在[夜半再谈]中写写我的理解。当然,一家之言罢了。

[总结]三年经验的运维策略

本人虽然只有工作三年,实际经手的系统却着实不少。抛开技术问题不谈,我想从策略的角度来总结,每次接触到新的组件的时候如何做才能把组件运行好。其中的经验来源于书本和博文的,最经典的当然就是google的两本《SRE:Google运维解密》和《Google SRE工作手册》,但书中的情景是基于google完善的基础架构,sre工作规范和培养制度的。实际工作中,我觉得很少有机会把这么成熟的组件和运维体系交付给我们。此时,更需要运维人员有智慧的分阶段的推动组件成熟化。其中的经验多数来源于我观察同事们工作后的总结,和曾经的行为规范。下面我根据一个新同学接触系统的过程,和组件本身成熟度两个维度来分析这个过程: [新同学视角]接触组件 1. 建立依赖关系图明白我们负责的系统依赖于哪些别的组件,这个阶段可以用draw.io 2. 建立三张Failure Modes表通过1的图我们可以列举出系统中的所有组件,然后根据这些组件建立三张表: By Dependency 这个表的意思是系统所依赖的另一个组件或系统如果有问题会怎么样,我们是否有应对的措施: Failure Mode ID Component Dependency Possible Failure Functional Impact SLO Priority Possible Cause(s) Detection Point(s) Detection Delay (seconds) SOP 这个系统中的哪一个组件 依赖外部的哪一个系统或组件 失败后的表现,比如监控哪个指标下降 失败的影响 影响等级 可能的原因 检测方式 By Components 这个表的意思是组成系统的每个具体组件如果有问题会怎么样,具体表格可以参考上面 By Functions 对于整个系统,存在哪些功能,如果这个功能失效了会有什么影响。 [系统视角]系统的成熟度在建立起了对于系统的基本认知后,我们需要对系统的成熟程度做一个基本的分类。以对短期和对长期发展有一个预期的目标。成熟度的划分: 初级阶段这个阶段系统还有大量手工操作需要操作,运维方式通常为playbook或者多个shell脚本的组合。此时系统是极容易出问题的,我们应该根据上面的表格制定运维规范和完善运维体系,包含以下几个重要的方面: 1.1 事故预期SOP 根据上面的表格,预估可能出现的问题,针对这些问题在测试环境搭建并实际测试,根据测试结果制定SOP。SOP的要求是明确事故检测方式,事故定位方法,事故恢复脚本。 1.2 监控系统搭建 监控系统包括核心指标监控面板、报警渠道搭建、还有和7*24H值班团队一起制定报警升级方案,即无人可以响应的情况下的兜底人员或处理方式。 1.3 日志系统的配置 1.4 系统搭建脚本 一般sre也是需要负责系统搭建的,但在这个阶段,系统本身也只是处于不断开发的初级阶段。开发人员交付的搭建方式也可能只是原始的手动搭建方式。可以通过python脚本自动化其中的一部分繁琐的手动流程,降低工作量。可以从[手册]效率工具得到一些启发。 中级阶段本阶段是从手工转向自动化的重要阶段,其主要目的是将1.4 系统搭建体系自动化。方式是开发一个SRE专用平台来处理运维操作,这个根据团队架构师设计的方案每个公司可能不同,我目前所经历的是采用k8s-operator的方式。这个过程本身也有很多坑,本博客也会陆续更新更多的博文来尽量梳理清楚。 高级阶段在中级阶段,我们已经通过自动化平台的建设完成了至少1.4的功能。由于在中级阶段积累了大量的api,这些api可能来源于公司内部的已有系统,比如报警系统,日志平台。因此我们有能力去做下面的工作来使得运维过程实现正向循环,即手动操作越来越少,自动化操作越来越多: 3.1 开发机器人,自动化检查报警,完成对报警的初级过滤,减少报警对运维人员的干扰 3.2 自动化执行SOP, 对于某些常规的固定操作,交给机器人来自动做。比如磁盘满了这种问题 3.3 用户查询接口,用户经常会需要运维人员去机器上检查一个什么问题。这种问题也应该通过接口的方式交给用户自己解决,减轻运维工作量。 尾声从工作量的角度,整个过程一开始是最累人的,需要大量的人手去分担,才有可能让大家有空闲开发,进入下一个阶段。只要越过了(自动化工作时间〉人工介入工作时间)节点之后,后面的工作就会变得轻松和更有意义起来。从SRE的角度来看系统发展,一开始的时候SOP和报警爆炸是常见问题,因为没人知道在实际线上环境上线之后会出现什么意料之外的问题,会倾向于能上就上。SRE作为系统稳定的维护者,需要跟开发人员有一个好的沟通,了解每个报警背后的逻辑,不断优化报警和SOP, 尽可能的通过更少的人工介入来更准确的掌握线上情况。 另外,有两个问题还没有提到: SLO/SLA的问题。我一直做内部平台,还没有接触过先制定SLO/SLA再制定运维计划的场景,所以就没什么谈论的资格。 用户问产品使用问题,这个以前是很难搞的,非常浪费SRE的时间。可很多人不喜欢看文档,就会导致sre被问爆。现在的可能的解决方法是运用大语言模型,但这个方面我还没有实践过。

[手册]效率工具

python制作命令行工具最灵活的方式就是用python打包上传到pypi上,使用者通过pip安装后使用。整个过程推荐使用poetry来管理,非常方便。 1 2 3 bumpversion patch poetry build poetry publish -r {公司pypi} 具体使用可以参考python技术栈 shell脚本python试一把利器,但有的简单的需求用python有小题大做的嫌疑。可以采用shell函数,然后把函数放置系统环境变量中,如果你会环境变量还不太清楚可以查看[手册]Linux实用命令(一)。这样我们就可以调用函数来处理一些问题了。比如: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 smc-auto () { processed_server=$(echo $1 | sed 's/-/./g') toc_cluster=$({{your_api}}) if [[ $toc_cluster == "null" ]] then echo "Server not found: $processed_server" return 1 fi if [[ $toc_cluster == "{{something}}" ]] then {{commond1}} else {{commond2}} fi } popclip真的超级推荐popclip,这个工具可以让我们编写脚本,在选中的时候自动执行某些命令。比如选中一个ip, 然后自动登录,很方便。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # PopClip Terminal name: Terminal icon: iconify:logos:terminal regex: '(\w+-(\w+-){2,}\w+)|\b((25[0-5]|2[0-4][0-9]|[01]?

[总结]Golang技术栈

前言目前我主攻golang。golang将作为主要的服务编写代码,所以会涵盖我所能接触的的绝大多数后端技术。另外,有两类问题我认为是超越语言的,不会在这里提及,一是关于代码设计模式和服务架构方面的问题,二是业务相关(包括通用的业务),比如token登录这种问题。大类上分为以下类别: 测试:单元测试,mock测试,request测试,性能测试 CI/CD: 主要是CI 常用框架五件套:web,数据库连接,配置,log,监控 文档编写工具 0. 从功能角度看需要什么工具总结下来发现内容有点多,所以只做简单介绍。针对其中的某些简单叙述不够或者文档未说明的点,后面单开文章描述。 测试: 单元测试,mock测试 CI: 持续集成工具 go-client: 适用于开发k8s operator的库 viper: config配置 gin: web框架 go-svc: golang包装器 gorm: mysql redis kafka etcd和zookeeper go routine和channel klog,glog: log库 sentry: 在线debug工具 swger,puml: 文档工具 测试我觉得相比开发来说,测试是更加重要的。 单元测试: 在一个多人开发项目中,我们有时很难知道别人的改动是否对我们这块的功能有影响,所以对于关键节点编写单元测试,并且把自动运行单测写入到CI中是至关重要的。看似增加了工作量,可从长期来看,是可以减少大量的功能测试时间,减少潜在风险的。 API测试:这个之前我用postman, 后来看到别的同事直接把测试案例用http里面,感觉是一种更好的办法。 1 2 3 ### cluster sync PUT {{host}} api/v1/cluster/sync/ Authorization: Bearer {{auth_token}} Mock测试: mock测试对于接口测试非常重要。设想一个场景:我需要获取一份数据,但是数据来源有多个,这种情况下我们可能会先编写一个接口用来隔离具体获取数据源的struct。我们如何在不编写实际数据源获取的struct的情况下测试方发是否有效呢,就可以用mock了。 CICI太重要了,第一是跟上面说的一样,可以很好的实现自动测试。此外,还可以配置一系列静态代码检查,格式化,检查函数复杂度等等。可以让我们的代码质量提升一个数量级。在多人开发系统中是必不可少的。 gitlab-ci 可以根据stage来编写流水线,当然,实际使用要复杂的多。多数情况会结合makefile来使用。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 build-job: stage: build script: - echo "Hello, $GITLAB_USER_LOGIN!
0%