选择往往比努力更重要

偶尔深夜躺下时,我常常在想,我是怎么就走到这了?这都是源于毕业后的一系列选择吧!

也时常感慨,选择往往比努力更重要。回顾过去十余年,这几个决定对我走到今天起到了至关重要的影响。

1. 毕业之后去上海

2009年刚毕业时,我的目标很简单,就是找到一份和专业相关的工作,不论在哪,不论具体做什么,没任何计划,只要能提供一份能养活自己的工作就行。

当时,有同学已经找到手机测试的工作,并被派到上海出差,我得知之后他们还在招聘,也应聘了这家公司。

还记得应聘完不久,我就坐上了回家的火车。列车从沈阳北站出发时,我接到了公司的电话,通知我面试通过,可以来签合同了。就在火车到达沈阳站时,我毫不犹豫地下了车,还退掉了火车票,开始了人生的第一次真正选择。

我的第一份测试工作就这样开始了。那时候真的很敢闯,说走就走,然后就独自一个人去了上海。

上海对我来说是一个新鲜的、充满魔力的城市,即使是住在公司郊区的宿舍,生活依然快乐和精彩。

在上海,我去过世博会、首家苹果店开业,当然还有南京路、外滩、杭州西湖;还看过刘翔在钻石联赛跑110米跨栏,看周杰伦的上海演唱会。周末的晚上还会和同事一起聚餐到半夜,那时候大家都是刚毕业的小伙子,同事之间相处起来更像是同学。

2. 从上海回到沈阳

在上海出差了一年多,又到了选择的时候,是回到沈阳分公司还是辞职在上海找一份新的工作?我和一部分同事选择回到沈阳分公司。

回到沈阳,那里的办公室、食堂、工厂和宿舍都在一个大大的厂区里,周围是郊区,只有周末才能出去逛逛。

我感觉到在这个郊区,我就是这个工厂里的螺丝钉,看到了那种一眼望到头的生活。

日子虽然过得平稳,但不应该是我这个二十来岁的年龄该追求的,我知道这不会持续太久,如果不及时调整,等到有一天没什么一技之长时会很被动。我开始思考:如果将来我想去大连,我现在的这份手机测试是很难找到工作的,因此我要转做 Web 测试,哪里要我,我就去哪里,我需要的就是相关工作经验。

后来我是在沈阳东软面试上了北京东软的岗位,去北京的工资是3000,于是我毅然决然去了北京。

3. 从沈阳到北京

那是2011年3月,来之前我联系了大学同学刚哥。我可以在找到房子之前在他那里借宿一段时间。

就这样,我就坐上了去北京的火车。

我如愿做上了我心中真正的软件测试工作,也从那时候开始,我才开始上手了一点脚本、数据库相关的知识。

在北京东软,我经历了两个外派项目,发现我不喜欢这样的外派方式。一是上班无定所,二是定期更换并认识新同事。不到一年时间,我打算换工作了。

我第一个投递的简历是百度,面试了但不知道为什么反正就是没有通过,可能那时候确实是太菜了,才做了不到一年的 web 测试新手,还想面试百度?想想也不太可能。

后来又投了几个简历,误打误撞我就面试了京东,且最终通过了面试。记得当时的工资是6500,这对我来说真的是一笔巨款啊,足足比我刚来北京的时候翻了一倍。

在京东工作的两三年,是我真正入行测试的几年。身边确实有一些值得学习的同事,在他们身上我学到了 Python、Jenkins、性能测试、以及功能测试。

虽然我学到了这些技能,但我知道如果我想回到大连找到一份不错的工作,光会点技术还不够,我还得会英语,因为在大连只有外企的待遇还不错。

因此我在北京就开始搜索大连的外企,另外也开始准备学习英语。

我报名了新东方,学费好几千块钱,我记得上一堂课可能都要几十到上百,另外我觉得这样性价比不高。听了一两次试听课后,我果断退了学费。

我觉得最好的学习英语的环境就是加入外企。我开始在北京尝试去面试大连的外企,正好有一家在北京有分公司,我就去面试了,但可惜面试没通过。

4. 从北京回到大连

没有办法,在北京想找到大连外企实在不太现实,我辞掉了京东的工作回到大连找。

回到大连之后休息了几天,发现自己没有工作实在没心情做其他任何事情,然后就开始了找工作。

去面试过花旗的外派,也没有成功。那时候我还有一个想法就是转开发!

这是受在京东的同事影响。当时有一个测试同事,他会开发且技术不错,负责给开发团队写单元测试。我当时觉得这个很厉害,我也想做那种被认为有技术含量的工种。

因此我当时的另一个计划是:如果有任何一家公司愿意招聘我这个只有测试工作经验的人,培养我做开发,我其实愿意少拿钱,甚至免费为他们工作。我当时离开京东时的工资已经有一万二了,尽管如此但我也想要转开发,即使是从一个实习生开始。可惜当时市场没有给我这个机会,我没有在短期内找到这样的开发岗位。因为我需要一份工作,后来就找到一个6000多薪资的测试小主管工作。在这个岗位上我工作了几个月,发现这不是我想继续工作的环境,并在骑驴找马继续寻找一份外企工作。

后来我又误打误撞投了我之前在北京投递过的外企,这次面试得挺不错的,并正式被录用进入了外企。在这里开启了一段十年的职业生涯,是我最为努力的十年。

5. 在外企从测试转开发然后DevOps

上面说过我想转开发。在我加入到新公司后,组内有机会可以从测试转前端开发的机会。我当时极度羡慕这样的机会,可惜晚了一步,人够了,因此我就只能在测试岗位上继续发光发热。

直到2018年,因为公司业务调整,我又有了转开发的机会,但需要考核。

虽然对我来说是一个很大的挑战,但我认为这对我来说是在做对的事情。两个原因:

  • 如果编程技能比较强,我可以成为测试里技术最好的人;
  • 通常开发比测试有更多的话语权,更多的机会,比如说技术移民。
    我毫不犹豫地申请去做开发!

那是一段压力非常大的日子,我之前在#码农随笔系列里写过文章记录过那段日子。但最后我还是成功做了开发。再后来团队需要一个Build工程师,说真的,真是为我准备的角色,太适合我了!

之前这个角色还叫Build工程师,因为我非常有热情把这个工作做好,因此我做了很多Build之外的事情,因此我的职责也在慢慢扩大到DevOps相关领域,之后大家都称我为DevOps工程师,负责DevOps/Build/Release相关的事情。

6. 从大连到欧洲

早在几年前,我开始有了想走出去看看的想法,想在日本、欧美范围内找一个可以提供签证的DevOps工作。如果再走不出去,到了四十岁我可能就没什么机会了。

这个想法的产生可能源自于我此前自由行去过泰国、日本、美国的缘故。之后我喜欢听《静说日本》、《随口说美国》这些音频节目。

我更新了自己的LinkedIn,期间偶尔有人联系过我新加坡的机会。说实话,能不能去成还不好说,但我对那里不是很感冒,给我的感觉那边比较卷。

在经历了疫情、孩子出生后,这个想法就搁置了,也没有任何进展。在经历了众所周知的大环境后,突然有了机会。

但说实话这时候我顾虑比当初去上海,回沈阳、去北京、回大连时多得多。

因为家庭、因为孩子、因为父母,做出决定是不容易的。但在过往的所有选择里,没有完美的机会,在这个不确定的时代,唯一不变的就是变化。况且这样的机会以后几乎都不会再有了。

留下来可能需要被迫去卷无意义的事情,就像大人卷工作、教育,小孩卷学习。想到此,我知道又到了该选择的时候,那就是走出去。

最后

自毕业以来,特别是近十年的外企工作,我挺努力的。努力工作、不妥协地去完成;坚持业余时间以教促学的写了七八年的博客和公众号,贡献开源项目。

我坚信努力的价值,但同时也提醒自己不要陷入“自我感动”的误区。努力固然重要,但不能让战术上的勤奋掩盖战略上的懒惰。我们必须抬头看路,抓住关键的机会,做出明智的决策。毕竟,如果方向错误,再多的努力也可能是徒劳的。


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

DevOps进阶:揭秘首席DevOps工程师的职责与技能

想象一下,你是一名 DevOps 工程师,不论初级、中级还是高级,老板总有一天拍拍你的肩膀说:“加油干,兄弟,未来你就是我们的首席 DevOps 工程师了!”你可能会心想:“啥是首席 DevOps?这是个什么新饼?”

今天就带你了解一下,所谓的“首席 DevOps 工程师”到底干啥,职责是什么?

我们一起看看,顺便找准未来的职业发展方向。毕竟,谁都希望能进阶到高级角色嘛,对吧?

首席 DevOps 工程师是干啥的?

在今天这个技术跑得比人快的世界里,首席 DevOps 工程师是关键角色,帮助企业搞定基础设施,让软件交付又快又稳。这可不光是架个服务器那么简单,真正的大活儿是当好开发和运营团队之间的“桥梁”,推动 DevOps 文化在公司生根发芽。

那么他们的日常是啥呢?总结起来,有三个主要工作:

  1. 设计并维护基础设施 —— 和开发团队配合,搭建弹性、可扩展的基础设施,满足业务需求。
  2. 自动化所有能自动化的事情 —— 减少手动操作,提升代码发布和测试的效率。
  3. 推动团队文化变革 —— 推广 CI/CD 最佳实践,优化大家的工作方式。

核心职责

1. 协调开发和运营

在你成为首席 DevOps 工程师后,你的头号任务就是让开发和运营两边配合得像一个人。持续集成、持续部署这些词你得说得像背诗一样顺溜,同时,基础设施得稳如老狗。

2. 实现自动化和流程优化

自动化是 DevOps 的灵魂,首席 DevOps 工程师就是得把那些繁琐的手动任务尽可能自动化,不断优化,让所有事情跑得又快又稳。

3. 保证系统可靠性和效率

系统跑不稳,CI/CD就得停摆,所以你要设计出能撑得住风浪的基数设施。遇到高负载或者故障,系统照样得稳住。定期监控、优化,是你的日常功课。

成为首席 DevOps 工程师需要哪些技能?

1. 技术要硬

技术基础是标配,Python、Bash 这些脚本语言得熟悉,Docker 这种容器技术也得懂,Ansible、Chef 这些配置管理工具是你日常操作。最重要的是,云平台(比如 AWS 和 Azure)管理经验不能少。

2. 领导力和管理能力

技术大牛不稀奇,领导力大牛才是硬通货。你得激励团队,帮他们成长,创造出协作的氛围。别忘了,技术再牛,不会跟不同层级的利益相关者沟通,那也白搭。

3. 解决问题的能力

技术上碰到过问题可以迅速找到问题的根源,给出靠谱的解决方案。更重要的是,做决策时得能平衡技术需求和业务目标,让公司上下都买账。

对公司有啥好处?

1. 加强沟通协作

作为首席 DevOps,你不仅仅写代码,还得跨团队沟通协调,让大家更默契,工作更顺畅。减少内部摩擦,提升效率。

2. 加快产品交付

优化流程、自动化任务,你能让产品的交付速度快得像坐火箭。市场变化快,你得让公司跟得上,产品能快速迭代,企业才能有竞争力。

3. 提高系统稳定性和安全性

稳定性和安全性很重要,你得建立起强大的监控体系,防止潜在的威胁,在安全和稳定方面给公司打下坚实基础。

总结一下

首席 DevOps 工程师可不是单纯的技术专家,你要靠技术提升效率,推动合作,保证产品交付和系统的稳定性。这过程里,你不仅是一个技术领袖,更是团队文化的推动者。

想成为首席 DevOps?那就不仅要技术过硬,还要培养领导力和解决问题的能力。

希望这篇轻松的文章能帮你找到未来的努力方向,毕竟,我们都在为更高的目标努力着!


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

约定式分支规范中文版正式发布!

上周正式发布《约定式分支规范》后,受到了广泛关注,不少人询问何时推出中文版。

经过一周末的努力,中文版已正式上线,详情请访问:**https://conventional-branch.github.io/zh/**。

此外,我们对原始版本进行了部分细微调整。以下为《约定式分支规范》中文版的完整内容。

概述

约定式分支是指 Git 分支的结构化和标准化命名约定,旨在使分支更具可读性和可操作性。我们建议了一些您可能想要使用的分支前缀,但您也可以指定自己的命名约定。一致的命名约定使按类型识别分支变得更加容易。

要点

  1. 以目的为导向的分支名称:每个分支名称都清楚地表明了其目的,使所有开发人员都能轻松了解该分支的用途。
  2. 与 CI/CD 集成:通过使用一致的分支名称,它可以帮助自动化系统(如持续集成/持续部署管道)根据分支类型触发特定操作(例如,从发布分支自动部署)。
  3. 团队协作:它通过明确分支目的来鼓励团队内部的协作,减少误解并使团队成员更容易在任务之间切换而不会产生混淆。

规范

分支命名前缀

分支规范通过 feature/bugfix/hotfix/release/chore/ 来描述,其结构应如下:


<type>/<description>
  • main:主要开发分支(例如 mainmasterdevelop
  • **feature/**:用于新功能(例如 feature/add-login-page
  • **bugfix/**:用于错误修复(例如 bugfix/fix-header-bug
  • **hotfix/**:用于紧急修复(例如 hotfix/security-patch
  • **release/**:用于准备发布的分支(例如 release/v1.2.0
  • **chore/**:用于非代码任务,如依赖项、文档更新(例如 chore/update-dependencies

基本规则

  1. 小写字母和连字符分隔:始终使用小写字母,并使用连字符分隔单词。例如,feature/new-loginbugfix/header-styling
  2. 仅使用字母数字和连字符:仅使用小写字母 (a-z)、数字 (0-9) 和连字符。避免使用空格、标点符号和下划线等特殊字符。
  3. 无连续连字符:确保连字符单独使用,没有连续的连字符(例如,feature/new-login,而不是 feature/new--login)。
  4. 无尾随连字符:请勿在分支名称末尾添加连字符。例如,使用 feature/new-login 而不是 feature/new-login-
  5. 清晰简洁:使分支名称具有描述性但简洁。名称应清楚地表明正在完成的工作。
  6. 包括 Jira(或其他工具)票号:如果适用,请包括项目管理工具中的票号,以便于跟踪。例如,对于票号 T-123,分支名称可以是 feature/T-123-new-login

结论

  • 清晰的沟通:仅凭分支名称就能清楚地了解代码更改的目的。
  • 自动化友好:轻松挂接自动化流程(例如,针对 feature, release 等的不同工作流程)。
  • 可扩展性:适用于许多开发人员同时处理不同任务的大型团队。

总之,约定式分支旨在改善 Git 工作流程中的项目组织、沟通和自动化。

常见问题

如果团队成员不符合此规范,可以使用哪些工具来自动识别?

你可以使用 commit-check 来检查分支规范,或者如果你的代码托管在 GitHub 上,则使用 commit-check-action


感谢大家的关注!最后,欢迎Star、转发或建议。


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

Conventional Branch Specification Released!

Summary

Conventional Branch refers to a structured and standardized naming convention for Git branches which aims to make branch more readable and actionable. We’ve suggested some branch prefixes you might want to use but you can also specify your own naming convention. A consistent naming convention makes it easier to identify branches by type.

Key Points

  1. Purpose-driven Branch Names: Each branch name clearly indicates its purpose, making it easy for all developers to understand what the branch is for.
  2. Integration with CI/CD: By using consistent branch names, it can help automated systems (like Continuous Integration/Continuous Deployment pipelines) to trigger specific actions based on the branch type (e.g., auto-deployment from release branches).
  3. Team Collaboration : It encourages collaboration within teams by making branch purpose explicit, reducing misunderstandings and making it easier for team members to switch between tasks without confusion.

Specification

Branch Naming Prefixes

The branch specification by describing with feature/, bugfix/, hotfix/, release/ and chore/ and it should be structured as follows:


<type>/<description>
  • main: The main development branch (e.g., main, master, or develop)
  • feature/: For new features (e.g., feature/add-login-page)
  • bugfix/: For bug fixes (e.g., bugfix/fix-header-bug)
  • hotfix/: For urgent fixes (e.g., hotfix/security-patch)
  • release/: For branches preparing a release (e.g., release/v1.2.0)
  • chore/: For non-code tasks like dependency, docs updates (e.g., chore/update-dependencies)

Basic Rules

  1. Lowercase and Hyphen-Separated: Always use lowercase letters, and separate words with hyphens. For example, feature/new-login or bugfix/header-styling.
  2. Alphanumeric and Hyphens Only: Use only lowercase letters (a-z), numbers (0-9), and hyphens. Avoid special characters like spaces, punctuation, and underscores.
  3. No Consecutive Hyphens: Ensure that hyphens are used singly, with no consecutive hyphens (e.g., feature/new-login, not feature/new--login).
  4. No Trailing Hyphens: Do not add a hyphen at the end of the branch name. For instance, use feature/new-login instead of feature/new-login-.
  5. Clear and Concise: Make branch names descriptive but concise. The name should clearly indicate the work being done.
  6. Include Jira (or Other Tool) Ticket Numbers: If applicable, include the ticket number from your project management tool to make tracking easier. For example, for a ticket T-123, the branch name could be feature/T-123-new-login.

Conclusion

  • Clear Communication: The branch name alone provides a clear understanding of its purpose the code change.
  • Automation-Friendly: Easily hooks into automation processes (e.g., different workflows for feature, release, etc.).
  • Scalability: Works well in large teams where many developers are working on different tasks simultaneously.

In summary, conventional branch is designed to improve project organization, communication, and automation within Git workflows.

FAQ

What tools can be used to automatically identify if a team member does not meet this specification?

You can used commit-check to check branch specification or commit-check-action if your codes are hosted on GitHub.


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

初步了解 PyPA(Python Packaging Authority)下的知名项目和关系

PyPA(Python Packaging Authority)是管理和维护 Python 包相关工具的一个社区组织。PyPA 管理的知名项目包括 pip、packaging、setuptools、wheel、twine、build 等等。了解这些项目的关于有助于我们更好的了解 Python 的生态系统。

以下是这些项目的介绍以及它们之间的关系:

  1. pip

作用:pip 是 Python 的包管理工具,用于安装和管理 Python 库和依赖项。通过 pip,用户可以从 Python Package Index (PyPI) 或其他包源下载并安装所需的 Python 包。
关系:pip 依赖于 setuptools 和 wheel 来处理包的构建和安装。它是最常用的 Python 包管理工具,也是官方推荐的包安装方法。

  1. setuptools

作用:setuptools 是一个用于打包 Python 项目的工具,可以创建分发包(distribution packages)并发布到 PyPI。它扩展了 Python 标准库中的 distutils,提供了更多功能,如依赖管理、插件系统等。
关系:setuptools 是创建 Python 包时常用的工具,pip 使用 setuptools 来安装源代码形式的 Python 包。setuptools 生成的分发包通常是 .tar.gz 或 .zip 文件格式。

  1. packaging

作用:packaging 提供了用于与 Python 包打包和分发相关的核心实用工具和标准实现。它实现了一些与包版本、依赖关系解析等有关的 PEP(Python Enhancement Proposals)。
关系:packaging 是 setuptools 和 pip 等工具的底层依赖,用于处理包的版本比较、依赖解析等低层次操作。

  1. wheel

作用:wheel 是一种 Python 包的打包格式,作为 setuptools 打包格式 .egg 的替代方案。它是目前推荐的发布格式,可以避免编译步骤,安装速度更快。
关系:pip 优先安装 wheel 格式的包,因为它可以直接安装,而不需要像 .tar.gz 那样进行编译。setuptools 可以生成 wheel 格式的包。

  1. virtualenv

作用:virtualenv 用于创建独立的 Python 环境,可以避免不同项目之间的包依赖冲突。每个虚拟环境都有自己独立的 Python 可执行文件和库集合。
关系:pip 被用于管理 virtualenv 中的包。virtualenv 是创建隔离环境的工具,但近年来 Python 标准库中的 venv 模块也能提供类似功能。

  1. twine

作用:twine 是用于将 Python 包上传到 PyPI 的工具。与 setuptools 的 python setup.py upload 方法不同,twine 更加安全,支持上传 wheel 文件和 .tar.gz 文件。
关系:twine 通常与 setuptools 或 wheel 一起使用,负责将构建好的包上传到 PyPI。

  1. build

作用:build 是一个简单的命令行工具,用于构建 Python 项目。它可以使用 PEP 517 接口构建包,而不依赖于 setuptools。
关系:build 提供了比 setuptools 更简洁的构建方式,但它依赖于 setuptools 或其他构建后端(如 flit、poetry)来实际完成构建过程。

  1. pyproject.toml

作用:pyproject.toml 不是一个工具,而是一种配置文件格式。它被用来描述项目的构建需求,并支持使用不同的构建后端,如 setuptools、flit、poetry 等。
关系:pip 和 build 等工具会读取 pyproject.toml 文件,了解如何构建和安装项目。

他们之间的关系总结

  • pip 作为包管理工具,与所有这些项目都有交互关系,尤其依赖 setuptools 和 wheel 来安装包。
  • setuptools 负责包的创建和打包,并使用 packaging 处理版本和依赖。
  • wheel 是打包格式,pip 更倾向于安装 wheel 格式的包。
  • virtualenv 用来创建隔离的环境,pip 被用来在这些环境中安装依赖。
  • twine 用来安全地上传包到 PyPI,通常与 setuptools 和 wheel 结合使用。
  • build 是一个新兴的构建工具,使用 PEP 517 接口,可以使用 setuptools 作为构建后端。

这些工具共同构成了 Python 包管理和分发的完整生态系统,简化了 Python 开发者的工作流程。


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

我记得

最近开车的时候我总爱听赵雷的歌,尤其是那首《我记得》。

我觉得这首歌就是回忆的交响曲,每每听到就仿佛看到了一部老电影。它像是一种声音,一种触动人心的触感,让我们回忆起那些已经消逝的时光,反思我们的生活方式和人生的方向。

歌词和旋律中都充满了满满的怀旧情感,总是让我回想起来这一段时间来的所有人多我的问候和祝福。

趁着他们都还很鲜活我想把他们全都记录一下,也叫《我记得》:

  • 6月14日(周五)我所在的1024足球队就为我准备了聚餐,当时还只有我的签证获批,这已经是一个好的开始了,当晚把我这几年的啤酒全喝了,还好酒精度数不是很大,回到家也没有迷糊。
  • 6月27日(周四)与公司的同事和领导去吃了钱库里自助,当时的心情还是复杂的,因为明天就要毕业了,我们在一起照了一张欢快的合影。
  • 6月28日(周五)毕业当天很多同事过来跟我告别、以及他们把我送出公司的门和大楼门的情景,最后我和大超一起下山,他回家,我等公交车。
  • 6月30日(周日)我去小平岛恰巧碰到了同事大叔,我去送洗鞋,他去买菜。我们就站在马路边站着聊了很久,最后分别前拍了一张合影。
  • 7月1日(周一)跟当年的发小和老同桌一起吃了午饭,绕着腾飞走了一圈,最后他陪我一起去4s保养、补胎,聊了一下午,好久没有单独和一个人聊这么久了,感觉很好,拍了合影,留作纪念
  • 7月4日(周四)我独自带着女儿回到老家让二老再看看他们的大孙女,整个往返车程6个小时,女儿都是坐在宝宝安全座椅上,表现的非常棒。
  • 7月7日(周日)和妻子、女儿去老丈人家吃了一个美味的午饭;还是当天,下午和教练去市馆游泳,回到腾飞后喝了杯咖啡、吃了点小食、最后绕着腾飞走了半圈,聊到意犹未尽。
  • 7月8日(周一)去跟原公司同事蹭场打羽毛球,
  • 7月9日(周二)跟1024足球队踢了我在这边的最后一场球了,对方今天有一个超强的守门员,给人印象深刻,另外一个深刻的地方可能就是我的一记后场吊射进球了吧。
  • 7月10日(周三)启程再次去日本拿卡,另外还去了一趟东京迪斯尼,尽管当天下了雨,但感受还是很好。
  • 7月14日(周日)从日本回来,当天下午前同事朋友去羊汤馆吃了一顿饭,聊了几个小时。
  • 7月15日(周一)见了发小,他在海边钓鱼,我也去了,那是我第一次使用鱼竿钓鱼,并且钓到了一条小小小鱼。
  • 7月16日(周二)启程出发了,虽然心中有万般不舍,但到了改走的时候了。我知道此时的难过是真的,我也知道我一定会走出这一步也是真的。当晚做高铁到了北京,见到机场见到大学同学,一起聊了一会,但因为已经很晚了,就叙旧到此。
  • 7月17日(周三)经过了十几个小时的飞行,我们一家人终于到了维村。至此,我短暂的毕业季就结束了。

很怀念,因此写下只言片语,记录一切发生过的美好。

这些记忆会提醒我,无论生活如何变迁,我们都不会忘记过去,但也要对未来充满希望和期待,体验到了生活的美好和希望。

生活里虽然充满了困难和挑战,但只要我们对过去有回忆,对未来有期待,那么生活就充满了意义和价值。

美好的音乐也会触动着我们的情感,引发我们的思考,带给我们力量和希望。


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

告别Rocket中国,回连十年再启程

​今天(6月28日)是我在Rocket中国分公司的最后一天,也是效力的第十个年头。这是我职业生涯中效力过最长的一家公司,想借此机会写下一些文字,为这十年画上一个句号。

光阴似箭,岁月如梭。转眼间,十年悄然逝去。唯有认真努力地生活,才能不被时光的流逝所感叹。我一直很喜欢的一句话是:“种一棵树最好的时间是十年前,其次是现在。

受同事的影响,从加入到公司以后我开始爱上软件这个行业,也想成为别人眼中的技术专家。我从最初的测试工程师转为开发人员,最终投身于DevOps领域,在这个过程中收获了快乐和成就。

十年征程,感恩有你。在Rocket中国的十年里,我结识了众多优秀的同事和朋友,从他们身上我学到了很多,也受到了他们的深刻影响。

无数次的团队聚餐、活动、旅游、足球、羽毛球、游泳等等,感谢你们一路的相伴,让这段旅程更加精彩和难忘。

也感谢我的家人,一直以来对我的包容、理解、支持和爱。是你们给了我坚强的后盾,让我无畏前行。感谢这十年里所有美好的回忆。

三十年风雨,五座城市。三十年来,我辗转生活过五个城市:出生在辽宁庄河,大学就读于沈阳,第一份工作在上海,后来又在北京工作了三年。直到2014年,我从京东裸辞,回到了家乡大连。

大连是我最喜欢的城市之一,这里风景优美、气候宜人。我在这里买房、结婚、生子,完成了人生中许多重要的阶段。

天下没有不散的宴席。随着最后一天日期的临近,我的心情也变得复杂。

其中既有对未来的期待和憧憬,也有对挑战和未知的担忧,以及对家人的牵挂。我期待着自己、妻子和孩子都能在未来的道路上变得更好、更强;但从工作到生活,这无疑将充满挑战。我无法确定这一过程是否会顺利,也不确定需要多长时间。

选择欧洲,开启新篇章。或许有人会问,在国内难道不能养家糊口吗?为什么要跑到那么远的地方?其实,这并非对错之分,而是选择。一个人的选择往往与他的经历息息相关。

以前,我从未想过出国,直到加入Rocket中国之后,身边优秀的同事陆续带着家人和孩子去了澳大利亚、欧洲、日本等地。此外,我也有机会去美国出差、去日本旅游,看到了更加广阔精彩的世界。

这些经历逐渐在我心中萌生了一个想法:如果有机会走出去,那将是一次充满挑战和乐趣的人生体验。

我钦佩那些主动打破舒适圈的人,而我则一直被动地等待着机会的降临。如果机会来临,我愿意勇敢地尝试。

恰逢公司业务调整,项目要移至欧洲。对于其他人来说,这可能是一个坏消息,但对我而言,这是一个难得的机会。虽然这并非一个完美的契机,但人生哪有那么多完美呢?

这次机会将让我有机会在全英文的工作环境中锻炼自己;我的两岁女儿也将有机会接受不同的教育,开启她的第二甚至第三外语学习之旅。

对于我们一家来说,这都是一次全新的挑战和开始。虽然我们已经过了35岁,但我始终觉得,我们依然年轻,可以不断学习和探索,保持对世界的好奇心。

选择欧洲的另一个原因是,如果留在国内,程序员想要延续职业生涯到四十岁甚至五十岁、六十岁会非常困难,而在欧美国家相对容易一些。

此外,随着年龄的增长,我对“好公司”的定义也在不断变化。

比如刚毕业去上海工作时,我觉得SIMCom是最好的公司;后来去北京东软工作时,我觉得东软也还不错;再后来,我加入了京东,觉得这可能是我这辈子能加入的最好的公司了;直到我加入到现在这家外企,这十年来我一直觉得这才是一家好公司。

如果还按照现在的标准在大连甚至其他城市来寻找新的工作,恐怕只能在梦里才能实现了。


最后,感谢在Rocket中国这十年里遇到的每一位朋友和同事。

衷心感谢每位同事的临别前的问候和祝福。

希望未来我们在人生的旅途上能够再次相聚。

再见!👋


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

你的软件究竟从哪里来?

软件真是个有趣又深奥的东西,它由看似神奇的代码片段组成,这些代码运行在最终的终端上,本身却并非生命体,但拥有自己的生命周期。

软件最初是源代码的形式,仅仅是存放在某个仓库的文本文件,然后通过独特的构建过程,这些源代码会转变为其他形式。例如交付到 web 服务器的压缩 JavaScript 代码块、包含框架代码和业务逻辑的容器镜像,或者针对特定处理器架构编译的原始二进制文件。

这种最终的形态转变,也就是源代码生成的其他形式,通常被称为“软件制品”。在创建之后,制品通常会处于休眠状态,等待被使用。它们可能会被放置在软件包注册表(例如 npm、RubyGems、PyPI 等)或容器注册表(例如 GitHub Packages、Azure Container Registry、AWS ECR 等)中,也可能作为附加到 GitHub 版本发布的二进制文件,或者仅仅以 ZIP 文件的形式存储在某个 Blob 存储服务中。

最终,有人会决定拾取该制品并使用它。他们可能会解压缩包、执行代码、启动容器、安装驱动程序、更新固件 - 无论采用何种方式,构建完成的软件都将开始运行。

这标志着生产生命周期的顶峰,该周期可能需要大量人力投入、巨额资金,并且鉴于现代世界依赖软件运行,其重要性不言而喻。

然而,在许多情况下,我们并不能完全保证所运行的制品就是我们构建的制品。制品经历的旅程细节要么丢失,要么模糊不清,很难将制品与其来源的源代码和构建指令联系起来。

这种缺乏对制品生命周期的可见性是当今许多最严峻的安全挑战的根源。在整个软件开发生命周期 (SDLC) 中,有机会保护代码转换为制品的流程 - 如此一来,可以消除威胁行为者破坏最终软件并造成严重后果的风险。一些网络安全挑战似乎难以成功应对,但这种情况并非如此。让我们深入了解一些背景知识。

哈希值和签名

假设你的目录中有一个文件,并且你想要确保它明天与今天完全相同。你该怎么做?一个好的方法是通过安全的哈希算法生成文件的哈希值。

以下是如何使用 OpenSSL 和 SHA-256 算法完成此操作:

openssl dgst -sha256 ~/important-file.txt

现在,你拥有了一个哈希值(也称为散列值),它是一个由字母和数字组成的 64 字符字符串,代表该文件的唯一指纹。只要更改该文件中的任何内容,然后再次运行哈希函数,你就会得到不同的字符串。你可以将哈希值写在某个地方,然后第二天回来尝试相同的过程。如果你两次没有得到相同的哈希值字符串,则文件中的某些内容已发生更改。

到目前为止,我们可以确定某个文件是否被篡改。如果我们想要对制品进行声明怎么办?如果我们想说“我今天看到了这个制品,我(系统或人)保证这个东西就是我看到的东西”,该怎么办?此时,你需要的是软件制品签名;你需要将哈希值字符串通过加密算法进行处理,以生成另一个字符串,代表使用唯一密钥“签名”该指纹的过程。
如果你随后希望其他人能够确认你的签名,则需要使用非对称加密:使用你的私钥签名哈希值,并提供相应的公钥,以便任何获取你文件的人都可以进行验证。

你可能已经知道,非对称加密是互联网上几乎所有信任的基础。它可以帮助你安全地与银行互动,也可以帮助 GitHub 安全地交付你的存储库内容。我们使用非对称加密来支持诸如 TLS 和 SSH 等技术,以创建可信赖的通信通道,但我们也使用它通过签名来创建信任软件的基础。

Windows、macOS、iOS、Android 等操作系统都具有用于确保可执行软件制品的可信来源的机制,方法是强制要求存在签名。这些系统是现代软件世界中极其重要的组件,构建它们非常困难。

不仅仅是签名 - 还要证明

当我们思考如何展示关于软件制品的更多可信赖信息时,签名是一个好的开端。它表示“某个可信赖的系统确实看到了这个东西”。但是,如果你真的想在整个软件开发生命周期 (SDLC) 的安全性方面取得重大进步,那么你就需要超越简单的签名,而是要考虑证明。

证明是一种事实断言,是对制品或制品所做的声明,并由可被认证的实体创建。之所以可以进行认证,是因为声明已签名,并且用于签名的密钥是可信的。

最重要和最基础的证明类型之一是断言有关制品来源和创建的事实 - 它来自的源代码和将源代码转换为制品的构建指令,我们称之为来源证明。

我们选择的来源证明规范来自 SLSA 项目。SLSA 是考虑软件供应链安全性的绝佳方式,因为它为软件的生产者和消费者提供了一个通用的框架,用于推理安全保证和边界,而这与特定的系统和技术栈无关。

SLSA 基于 in-toto 项目的工作,提供了一种用于生成软件制品来源证明的标准化架构。in-toto 是一个 CNCF 毕业项目,其存在目的之一是提供一系列有关供应链和构建过程的相关信息的标准化元数据架构。

构建这样的东西需要什么?

GitHub 作为托管大量代码和构建管道的全球最大软件开发平台,对此进行了大量的思考。构建认证服务需要许多活动部件。

这样做意味着有一种方法可以:

  • 颁发证书(本质上是绑定到某个经过身份验证的身份的公钥)。
  • 确保这些证书不会被滥用。
  • 在众所周知的上下文中启用工件的安全签名。
  • 以最终用户可以信任的方式验证这些签名。

这意味着设置证书颁发机构 (CA) 并拥有某种客户端应用程序,你可以使用它来验证与该颁发机构颁发的证书相关联的签名。

为了防止证书被滥用,你需要 (1) 维护证书吊销列表或 (2) 确保签名证书是短期的,这意味着需要某种时间戳机构的反签名(可以提供权威印章,表明证书仅在其有效期内用于生成签名)。

这就是 Sigstore 的作用所在,它是一个开源项目,提供 X.509 CA 和基于 RFC 3161 的时间戳机构。它还允许你使用 OIDC 令牌进行身份验证,许多 CI 系统已经生成了令牌并将其与其工作负载相关联。

Sigstore 对软件签名的作用与 Let’s Encrypt 对 TLS 证书的作用相同:使其简单、透明且易于采用。

GitHub 通过在技术指导委员会中的席位帮助监督 Sigstore 项目的治理,是服务器应用程序和多个客户端库的维护者,并且(与来自 Chainguard、Google、RedHat 和 Stacklok 的人员一起)组成了 Sigstore 公共物品实例的运营团队,该团队的存在是为了支持 OSS 项目的公共证明。

Sigstore 需要符合更新框架 (TUF) 规定的标准的安全信任根。这允许客户端跟上 CA 底层密钥的轮换,而无需更新其代码。TUF 的存在是为了缓解在现场更新代码时可能出现的大量攻击媒介。许多项目都使用它来更新长期运行的遥测代理、提供安全的固件更新等。

有了 Sigstore,就可以创建防篡改的纸质跟踪,将工件与 CI 联系起来。这一点非常重要,因为以无法伪造的方式签署软件和捕获出处细节,意味着软件消费者有办法执行他们自己的规则,以确定他们正在执行的代码的来源。

原文:https://github.blog/2024-04-30-where-does-your-software-really-come-from/


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

代码签名(Code Signing) - GaraSign

上次我在 代码签名(Code Signing)的文章中时候提到了 GaraSign,这是我在工作中使用到的另一个代码签名工具。

鉴于关于 GaraSign 的使用并没有多少中文资料,本篇我将介绍关于 GaraSign 的一些实线,希望对你有帮助。

代码签名

这里再次说明什么是代码签名。代码签名证书用于对应用程序、驱动程序、可执行文件和软件程序进行数字签名,客户可通过这种方式验证他们收到的代码未被网络罪犯和黑客篡改或破坏。签名后的交付产品结合了加密令牌和证书,用户可在安装产品前对其进行验证。代码签名可确认谁是软件作者,并证明代码在签名后未被修改或篡改。

Garasign 解决方案

GaraSign 是一个基于 SaaS 的安全协调平台,可对企业基础设施、服务和数据进行集中管理。GaraSign 可与所有主要操作系统、平台和工具的本地客户端集成,确保现有工作流不受干扰,同时改善其整体安全态势和合规性。

GaraSign 由以下组件组成:

  • 加密令牌 - 存储签名密钥的加密设备(通常是一个或多个 HSM - Hardware Security Modules)
  • GaraSign 签名服务器 - 位于存储签名密钥的加密令牌前的 REST 服务器
  • GaraSign Signing 客户端 - 允许与之集成的签名工具在本地散列数据并将签名生成脱载至 GaraSign Signing 服务器的客户端。

garasign components

Garasign 代码签名散列方法 - 大幅提高速度

garasign approach

安装 GaraSign

关于如何安装 GaraSign 这里不过去介绍,可以到官网找相关的安装文档。这里要注意目前 GaraSign 对操作系统版本的要求还是很高的,比如

  • Windows 最低要求是 Windows 2019, Win10 and Win11
  • Linux 最低要求是 RHEL 7.9, 8.0, 9.0,CentOS 7.9, 8.0, 9.0,Rocky 8.0

如果你的构建环境还是比较旧的或是不符合其支持的版本,建议你向我一样设置一台专用的 GaraSign 机器(推荐 Linux)。

如果你使用 Jenkins 来构建,可以将这台机器设置为一台 Jenkins agent,通过创建一个 Jenkins pipeline,这样其他所有的要需要发布的包都可以通过这个 pipeline 来进行签名。

如何签署独立签名

如果你已经设置好了 GaraSign 环境,以 Linux 为例,那么就可以通过下面的命令进行签署。

注:在 Windows 与 Linux 在签署不同的类型文件所使用到的命令不同,因此推荐在 Linux 进行签名会更加简单。

openssl dgst -sign <private key file> -keyform PEM -sha256 -out <signature-file-name.sig> -binary <binary file to sign>

具体的实施

加入你的 Artifacts 存在 Artifactory 上面,下面就 Jenkins 为例,来实施一个可以自动签名的 pipeline。包括:

  1. 从 Artifactory 上下载需要签名的 Artifacts
  2. 使用 GaraSign 进行签名
  3. 验证 GaraSign 是否成功
  4. 上传签名文件和公钥到 Artifactory 上的同一个目录下
pipeline{

agent {
node {
label 'garasign'
}
}

parameters {
string(
name: 'REPO_PATH',
defaultValue: '',
description: 'Repository Path on Artifactory. eg. generic-stage/test_repo/devel/54/mybuild_1.1.0_752d0821_64bit.exe'
)
}

environment {
BOT = credentials("BOT-credential")
ART_URL = "https://my.org.com/artifactory"
}

stages {
stage('GaraSign'){
steps {
script {
if (! params.REPO_PATH){
error "REPO_PATH can not empty, exit!"
}
// Update Job description
def manualTrigger = true
currentBuild.upstreamBuilds?.each { b ->
currentBuild.description = "Triggered by: ${b.getFullDisplayName()}\n${REPO_PATH}"
manualTrigger = false
}
if (manualTrigger == true) { currentBuild.description = "Manual sign: ${REPO_PATH}" }

sh '''
# download artifacts
curl -u${BOT_USR}:${BOT_PSW} -O ${ART_URL}/${REPO_PATH}
file_name=$(basename ${REPO_PATH})
repo_folder=$(dirname ${REPO_PATH})

# garasign
openssl dgst -sign grs.privkey.pem -keyform PEM -sha256 -out $file_name.sig -binary $file_name

# verify
grs.pem.pub.key
output=$(openssl dgst -verify grs.pem.pub.key -keyform PEM -sha256 -signature $file_name.sig -binary $file_name)
if echo "$output" | grep -q "Verified OK"; then
echo "Output is Verified OK"
else
echo "Output is not Verified OK"
exit 1
fi

# upload signature file (.sig) and public key (.pem.pub.key)
curl -u${BOT_USR}:${BOT_PSW} -T $file_name.sig ${ART_URL}/${repo_folder}/
curl -u${BOT_USR}:${BOT_PSW} -T grs.pem.pub.key ${ART_URL}/${repo_folder}/
'''
}
}
}
}
}

如何验证独立签名

还是以 Linux 为例,使用如下命令可以进行签名的验证。

openssl dgst -verify <public key file> -signature <signature> <file to verify>

当你的 Artifacts 已经进行了签名,在提供给客户的时候,你不但需要提供发布的包,而且需要提供签名文件 (.sig) 和公钥 (.pem.pub.key)。

举个例子,如下 CLI 产品分别提供了 Windows,Linux 和 AIX 三个平台的安装包,客户可以参考如下进行签名验证。

# 下载安装包、签名文件和公钥
$ ls
cli.pem.pub.key CLI_AIX_1.1.0.zip CLI_AIX_1.1.0.zip.sig CLI_LINUXX86_1.1.0.zip CLI_LINUXX86_1.1.0.zip.sig CLI_WINDOWS_1.1.0.zip CLI_WINDOWS_1.1.0.zip.sig

# 验证签名
openssl dgst -verify cli.pem.pub.key -signature CLI_AIX_1.1.0.zip.sig CLI_AIX_1.1.0.zip
Verified OK

openssl dgst -verify cli.pem.pub.key -signature CLI_LINUXX86_1.1.0.zip.sig CLI_LINUXX86_1.1.0.zip
Verified OK

openssl dgst -verify cli.pem.pub.key -signature CLI_WINDOWS_1.1.0.zip.sig CLI_WINDOWS_1.1.0.zip
Verified OK

# 当包和签名文件不符时会验证失败
openssl dgst -verify cli.pem.pub.key -signature CLI_AIX_1.1.0.zip.sig CLI_LINUXX86_1.1.0.zip
Verification Failure

以上就是关于 GaraSign 的实现分享,如有任何问题或是建议咱们评论区见。


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」

Python 软件基金会 (PFS) 基础设施概览

Python 软件基金会 (PFS) 或许大家比较熟知,它是开源 Python 编程语言背后的组织,致力于为 Python 和 Python 社区的发展壮大创造条件。

继上次我们看完了 Apache 的基础设施介绍,本篇文章我们一起来看看 Python 软件基金会 (PFS) 的基础设施,看看可以从中学到哪些。

PSF 基础设施概述

PSF 运行各种基础设施服务来支持其使命,从 PyCon 站点CPython Mercurial 服务器。本页的目标是列举所有这些服务,它们在哪里运行,以及主要联系人是谁。

基础架构团队

基础架构团队最终负责维护 PSF 基础设施。但是,它不需要成为运行 PSF 服务的基础设施。事实上,大多数的日常运营服务由不在基础设施团队中的人员处理。这基础设施团队可以协助建立新服务并为维护人员提供建议的个别服务。其成员通常还处理对敏感的更改全球系统,例如 DNS。目前的团队成员是:

  • Alex Gaynor (has no responsibilities)
  • Benjamin Peterson
  • Benjamin W. Smith
  • Donald Stufft
  • Ee Durbin (PSF Director of Infrastructure)
  • Noah Kantrowitz

联系基础架构团队的最佳方式是发送邮件 infrastructure-staff@python。也经常有人在 Libera 的 #python-infra 频道联系他们。

基础设施提供商

PSF 为其基础架构使用多个不同的云提供商和服务。

Fastly
Fastly 慷慨捐赠其内容分发网络(CDN)到 PSF。我们最高的流量服务(即 PyPI, www.python.org, docs.python.org)使用此 CDN 来改善最终用户延迟。

DigitalOcean
DigitalOcean 是当前的主要托管对于大多数基础设施,此处部署的服务由 Salt 管理。

Heroku
Heroku 托管了许多 CPython 核心工作流机器人,短暂的或概念验证的应用程序,以及其他适合部署在 Heroku 的 Web 应用程序。

Gandi
Gandi 是我们的域名注册之星。

Amazon Route 53
Amazon Route 53 托管所有域的 DNS,它目前由基础设施人员手动管理。

DataDog
DataDog 提供指标、仪表板和警报。

Pingdom
Pingdom 提供监控,当服务中断时向我们投诉。

PagerDuty
PagerDuty 用于 PSF 的待命轮换基础设施员工在一线,志愿者作为后援。

OSUOSL
俄勒冈州立大学开源实验室举办一个 PSF 的硬件服务器,speed.python.org 用于运行基准测试,此主机是使用 Chef 和他们的配置管理位于 PSF-Chef Git 存储库中。

数据中心

PSF DC Provider Region
ams1 Digital Ocean AMS3
nyc1 Digital Ocean NYC3
sfo1 Digital Ocean SFO2

各种服务的详细信息

本部分列举了 PSF 服务、有关其托管的一般情况以及所有者的联系信息。

Buildbot
buildbot master 是由 python-dev@python 运行的服务。特别是 Antoine Pitrou and Zach Ware.

bugs.python.org
bugs.python.org 由 PSF 在 DigitalOcean 上托管,由 Roundup 提供支持。它还部署了 bugs.jython.org 和 issues.roundup-tracker.org。

docs.python.org
Python 文档托管在 DigitalOcean 上,通过 Fastly 提供。负责人是 Julien Palard。

hg.python.org
CPython Mercurial 存储库托管在 Digital Ocean VM 上。负责人是 Antoine Pitrou 和 Georg Brandl。

mail.python.org
python.org Mailman 实例托管在 https://mail.python.org 和 SMTP(Postfix)上。所有查询都应定向到 postmaster@python。

planetpython.org 和 planet.jython.org
它们托管在 DigitalOcean VM 上。Planet 代码和配置托管在 GitHub 上,并由团队在 planet@python。

pythontest.net
pythontest.net 托管 Python 测试套件。python-dev@python 对其最终负责维护。

speed.python.org
speed.python.org 是一个跟踪 Python 性能的 Codespeed 实例。Web 界面托管在 DigitalOcean VM 上,基准测试在 strongfy 上运行机器在 OSUOSL 上,由 Buildbot 主节点调度。由 speed@python 和 Zach Ware 维护。

wiki.python.org
它托管在 DigitalOcean VM 上。负责人是 Marc-André Lemburg。

www.jython.org
这是从 Amazon S3 存储桶托管的。设置非常简单,不应该需要很多调整,但基础设施工作人员如有需要可以对它进行调整。

www.python.org
主要的 Python 网站是一个 Django 应用程序,托管在 Heroku。它的源代码可以在 GitHub 上找到,并且该网站的问题可能是报告给 GitHub 问题跟踪器
Python 下载 (即 https://www.python.org/ftp/ 下的所有内容)都托管在单独的 DigitalOcean 虚拟机。整个网站都在 Fastly 后面。还有用于测试站点的 https://staging.python.org。http://legacy.python.org 是从静态镜像托管的旧网站。

PyCon
PyCon 网站托管在 Heroku 上。联系地址是 pycon-site@python。

PyPI
Python 包索引的负载最多 任何 PSF 服务。它的源代码可在 GitHub 上找到。
它的所有基础设施都在 由 pypi-infra 配置的 AWS,它以 Fastly 为首。基础设施是由 Ee Durbin, Donald Stufft, 和 Dustin Ingram 维护的,联系地址是 admin@pypi。

PyPy properties
PyPy 网站托管在 DigitalOcean VM 上并进行维护作者:pypy-dev@python。

如需要参看原文。可访问地址

最后

从 PFS 基础设施来看,他们更多的使用了 Cloud 服务商和技术来部署他们的应用。因此想要参与到 PFS 基础设施管理用,需要对 CDN,DigitalOcean,Heroku,Amazon Route 53,Amazon S3,DataDog,Pingdom 这些技术有比较深入的使用经验。

我们羡慕那些可以为开源软件全职工作的人,他们拿着薪水做着很多程序员都羡慕的事情。

但拥有这样的工作需要我们自己的技能可以匹配的上,并且积极主动的去寻求这些机会,才有可能得到一份全职开源软件工程师的职位。共勉!


转载本站文章请注明作者和出处,请勿用于任何商业用途。欢迎关注公众号「DevOps攻城狮」