凌云时刻 · 技术

导读:真正引领大家进入到基础设施代码化的是 terraform,现在 pulumi 正在追赶它。

作者 | 陈小天
来源 | 程序人生

前言

好久不写 devOps 代码,程序君感觉莫名手欠。最近看着一个开源项目 pulumi 比较有意思,这个周末就在自己的 aws 账号里作死尝试了一把,嗯,还挺香。究竟有多香呢,我们来一起探索吧。

在具体深入 pulumi 前,我们先来回答两个问题:

1. 为什么要让基础设施代码化(Infrastructure as Code)?

2. 基础设施代码化领域都有哪些产品?

我们知道,在 2006 年亚马逊通过 AWS 撬动开云服务的巨大蛋糕后,云服务便以不可阻挡之势深入互联网的各个角落。如今,除了一线互联网大厂和准大厂外,其它大大小小的公司都在使用云服务。

然而渐渐地,传统的运维手段在云时代开始难以为继,一个 devOps 动辄要管理成百上千的机器,如果手工去干,谁受得了?

所以逐渐催生出来基础设施代码化的需求。通过代码,我们可以更好地描述软件系统对基础设施的需求,更容易审核增量更新,也(潜在地)更容易测试变更,以及更容易复制和扩展现有的工作。

这样下来最终导致的结果是,我们可以更进一步用更少的人力来管理更多的设施,还更加高效和更难出错。听起来是不是很讽刺?我们程序员就是这么浪,自己开心地写代码断自己的后路。

不过这就是从工业革命以来时代发展的必然:高效的生产力战胜并消灭低效的生产力

基础设施代码化起源于 AWS 的 cloudformation,它于 2011 年发布。通过 cloudformation,用户可以使用脚本来描述 AWS 上的资源的 CRUD。

但真正引领大家进入到基础设施代码化的,是 terraform,它的 v0.1 版本发布于 2014 年 7 月。巧的是,同年 9 月,kubernetes 第一个 release v0.2 在 github 上发布。

两者的使用场景虽然大不一样,但竞争的领域都是基础设施代码化这一块,关于 kubernetes 的前世今生,我们先放下不表。terraform 的初衷是通过对不同云服务的各种资源的抽象,让大家可以以几乎同样的方式撰写 AWS,Azure,google cloud,openstack 以及阿里云的基础设施的代码。

注意,很多人误解以为 terraform 可以一份代码搞定多种云,这是不对的。

就像 react native / flutter 一套代码搞定多个端一样,你只是不需要写不同语言的实现而已,具体到各种云的细节,还是需要不同的实现。

terraform 的崛起

terraform 背后的公司是 Hashicorp,就是在基础设施工具领域里大名鼎鼎的 concul(服务发现),vault(密钥管理),nomad(服务运行时,这个没怎么用过)等工具的开发者。Hashicorp 财务稳健,客户数量和收入连续四年翻番,今年 3 月份,赶巧在美国疫情爆发前敲定了 1.75 亿美金的 E 轮融资,富得流油,投后估值 51 亿美金,可见这个领域未来巨大的潜力。

hashicorp 为 terraform 设计了一套语言 HCL(Hashicorp Configuration Language)来描述基础设施资源的状态。比如我们要在 AWS 上创建一台运行 openresty 的 EC2,可以这么写:

provider "aws" {region = "us-west-2"
}data "aws_ami" "openresty" {most_recent = truefilter {name   = "name"values = ["openresty-*"]}filter {name   = "virtualization-type"values = ["hvm"]}owners = ["xxxx"] # 我个人的 aws 账号 ID
}resource "aws_security_group" "lb_sg" {name        = "lb_sg"description = "allow http/https access"vpc_id      = "${aws_vpc.main.id}"ingress {from_port   = 443to_port     = 443protocol    = "tcp"cidr_blocks = ["0.0.0.0/0"]}ingress {from_port   = 80to_port     = 80protocol    = "tcp"cidr_blocks = ["0.0.0.0/0"]}egress {from_port   = 0to_port     = 0protocol    = "-1"cidr_blocks = ["0.0.0.0/0"]}
}resource "aws_instance" "lb" {ami           = "${data.aws_ami.openresty.id}"instance_type = "t2.micro"tags = {Name = "lb"}
}resource "aws_network_interface_sg_attachment" "sg_attachment" {security_group_id    = "${aws_security_group.lb_sg.id}"network_interface_id = "${aws_instance.lb.primary_network_interface_id}"
}

这段代码读起来并不复杂:

首先我们声明了用 aws provider 来创建资源,所以下述的资源都会创建在 aws 的 us-west-2 区域,就是美国西海岸俄勒冈的数据中心。

然后我们描述要使用的 AMI(Amazon Machine Image),这里我使用了我自己个人账号下的通过 packer(也是 hashicorp 的一个开源项目)构建好的名为 "openresty-xxx" 的 AMI。

随后描述一个资源:security group,开放 80/443 端口。

之后描述一个资源:EC2 实例,使用刚才描述的 AMI,实例大小用 t2.micro。

最后,描述如何把 security group 和 EC2 实例绑定起来。

从这段代码我们可以看出,terraform 是声明式语言(Declarative Language),它描述这个脚本运行完云平台应该具有什么状态。所以 terraform 脚本在运行的时候,会拿代码中的状态和服务器端的状态进行对比,得出一个 diff,然后生成为实现这个 diff 所需要的 cloudformation(对于 aws 而言)代码,最后执行之。

当然,如果每次都去云平台拿所有相关资源的状态,效率太低,所以 terraform 会将上一次执行完的结果的状态保存在本地或者公共的存储(一般是 S3),对比代码和上一次执行完保存的状态即可。

虽然 terraform 写起来很简单,但当我们撰写越来越多的 terraform 代码后,我们会发现,要能够很好地复用代码,还是要下一番功夫的。

terraform 支持模块(module),一个模块就像一个函数,有输入输出,以及函数的主体。上面的代码如果封装成一个模块,那么其输入可以是 security group 想要开放的端口,EC2 实例的大小,磁盘大小,使用的 AMI 的名字等等,而输出可以是 EC2 实例的 id,public / private IP 等等。

除了模块外,terraform 还支持各种各样的 provider,比如各个云服务商的基础设施相关的 provider,以及丰富的在软件生命周期内可能涉及的各种 IT 服务,比如管理代码的 github,处理监控的 datadog,静态网站部署的 netlify,监控报警用的 opsgenie,进行单点登录(SSO)的 okta 等。

这些 provider 让 terraform 的生命力非常旺盛,前景非常广阔。

目前,大部分基础设施代码化的工作还聚焦在生产环境的代码化上面,而未来企业的 IT 系统的架构的代码化,将会是一座巨大的金矿,这也是程序君持续看好 Hashicorp 这家公司的主要原因。

前面都在吹 terraform 的特点和优势,我们也来看看 terraform 的问题:

 状态管理还处在原始社会

terraform 作为开源软件,既有开源软件生态丰富代码相对难以作恶的优势,又有开源软件只重视核心功能不注重使用体验的劣势。

状态管理是 terraform 用户体验非常差的一环,由于没有提供相应的功能,客户只能自己在开源社区里找解决方案。

目前 AWS 上常用的方案是 S3 存储状态,DynamoDB 用来加锁。如果多个人部署同一个 stack,就简单粗暴去 DynamoDB 拿锁排队。这个方案在几十人的团队里还凑合,再大就会有很多麻烦。

另外,状态的版本控制基本上没有,或者只能通过状态使用的存储引擎做版本管理(比如 S3),很难有效对比多个状态之间的差异。

 缺乏可视化的手段

状态的展示,部署的过程其实都可能做很多可视化的事情,让整体体验更好一些,减少 devOps 犯错。然而,terraform 并没有做这方面的支持。

 代码表现力一般

用于描述基础设施的代码是否需要强大的表现力?强大的表现力是福还是祸?这块一直有争论。

然而,实际使用的时候,我们总是绕不开循环,条件判断,以及对字符串做处理等各种工作。而 terraform 在这一块的表现力太弱,使得代码写起来非常冗长,很多时候不得不复制粘贴。

 terraform cloud 才刚刚起步

头两个问题也许在 terraform 的企业版中得到解决,但我和我的公司都没有用过,具体怎么样不得而知,也许是迫于接下来要讲的 pulumi 在市场上的压力吧。

Hashicorp 在 2019 年 9 月开始提供 terraform cloud,为小团队解决这两个问题。然而,目前 terraform cloud 更像是一个临时拼凑的 CI 工具,还有很长的路要走。

pulumi 闪亮登场

pulumi 诞生于 2017 年,是微软和亚马逊云服务的老兵 Joe Duffy(CEO) 和 Luke Hoban(CTO)创建的,对标 terraform 的一款软件。

和 terraform 一样, pulumi 也采用了开源 + 增值服务的方案。也许是发现很多用户都受制于上述的状态管理和可视化的问题,pulumi 走得比较坚决,缺省就帮助用户保存状态(虽然也允许用户自己保存状态)。

这使得 pulumi 上手的难度比 terraform 瞬间低了一个层级。

pulumi 另一个特点是使用你所熟悉的编程语言来编写 devOps 代码。它支持 javascript / typescript / go / python / dotnet core (C#, F#, VB) 等多种语言,因为开源,所以第三方也可以加入新的语言的支持。

比如上文中创建一个 openresty EC2 实例的代码,用 typescript 可以这么写:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
const ami = pulumi.output(aws.getAmi({filters: [{name: "name",values: ["openresty-*"],}],owners: ["xxxx"], // 我个人的 aws 账号 IDmostRecent: true,
}));const group = new aws.ec2.SecurityGroup("lb_sg", {ingress: [{ protocol: "tcp", fromPort: 80, toPort: 80, cidrBlocks: ["0.0.0.0/0"] },{ protocol: "tcp", fromPort: 443, toPort: 443, cidrBlocks: ["0.0.0.0/0"] },],egress: [{ protocol: "-1", fromPort: 0, toPort: 0, cidrBlocks: ["0.0.0.0/0"] },]
});export const server = new aws.ec2.Instance("lb", {instanceType: "t2.micro",securityGroups: ["default", group.name],ami: ami.id,tags: {"Name": "lb",},
});

这段代码任何一个 nodejs 工程师应该都能看得懂。我就不详细介绍了。

可以看到,在做这样简单的资源管理时,pulumi 代码和 terraform 代码无论是长度还是逻辑都差不多。

但当你想写如下的代码时,两者高下立现:

for (let item of require("fs").readdirSync(siteDir)) {let filePath = require("path").join(siteDir, item);let object = new aws.s3.BucketObject(item, {bucket: bucket,source: new pulumi.asset.FileAsset(filePath),     // use FileAsset to point to a filecontentType: mime.getType(filePath) || undefined, // set the MIME type of the file});
}

这如果你是 terraform 的用户,不妨想想这样的代码如何在 terraform 里完成。

如果说这个例子让你仅仅感受到 terraform 语言本身的局限,那么接下来这个例子则诠释了基础设施代码化的未来:

import * as aws from "@pulumi/aws";
import * as https from "https";export const table = new aws.dynamodb.Table("hackernews", {attributes: [{ name: "id", type: "S", }],hashKey: "id",billingMode: "PAY_PER_REQUEST",
});export const cron = aws.cloudwatch.onSchedule("daily-yc-snapshot", "cron(30 8 * * ? *)", () => {https.get("https://news.ycombinator.com", res => {let content = "";res.setEncoding("utf8");res.on("data", chunk => content += chunk);res.on("end", () => new aws.sdk.DynamoDB.DocumentClient().put({TableName: table.name.get(),Item: { date: Date.now(), content },}).promise());}).end();
});

在这段代码中,部署脚本和 lambda 代码水乳交融,浑然天成。

onSchedule 的回调是一个 lambda 函数,这个 aws lambda 函数隐含的配置和权限都被 pulumi 根据上下文自动设置好,无比自然,即便你需要为 lambda 做更细致配置,只需要把 () => 换做下述代码:

new aws.lambda.CallbackFunction("hackerNewsCrawler", {memorySize: 256 /*MB*/,callback: (e) => {// lambda dcode},
}

为什么我觉得这是基础设施代码化的未来呢?

因为现在互联网软件的开发越来越离不开基础设施的运维,而 Serverless 会加速这一过程。未来的编程语言一定是能够无缝地结合运维,开发者在开发各种各样的系统时,会直接或者间接地在撰写分配资源的代码。

这么说大家可能还是比较困惑,我们打个比方。

如果把 AWS 看做是一个操作系统,那么 API Gateway,Kenesis,ELB,S3 Stream 就是在处理这个操作系统的外部输入,而对应的 lambda 就是对外部输入的响应;SQS / S3 stream 等也是这个操作系统的 IPC(进程间通讯),对应的 lambda 就是处理进程间消息的手段。

当你构建 unix 系统下的服务时,资源已经在那里,你只需要撰写服务的业务逻辑就好;而在云系统下做服务时,你往往需要同时撰写分配资源和处理业务逻辑的代码。这样的代码如果一部分交由 devOps 来写,一部分由 app 开发者撰写,那么开发效率一定是很低的。

因而,terraform 代表着上一代的 devOps,即大部分运维的活还是 devOps 干;而 pulumi 代表着下一代的 devOps,大部分运维的活直接由程序员完成,甚至很多应用的逻辑和资源部署的逻辑是放在一起的。

这,也是为何 pulumi 要支持多种开发语言。

我一开始对这一点非常不解,觉得支持多个语言是在给自己下套,让自己分心不能专注把核心功能做好,为什么不只提供 typescript 的支持并将其做到极致呢?

但考虑到未来资源部署和业务逻辑的代码的界限会渐渐模糊,开发者会为自己项目撰写大量 devOps 代码的这一趋势,pulumi 的下注就显得目标清晰且有远见了。

如果只做 javascript/typescript 支持,那么一套 golang 撰写的服务,还需要用 typescript 来撰写 devOps 代码,显然无法很好地充分利用开发者的才智。

结语

当然,作为一个还不到三年的项目,pulumi 的缺点也是显而易见的:

1. 它的生态比 terraform 还差得很远,这里需要时间慢慢追赶。然而对创业公司来说,时间往往是最大的敌人。terraform 也许很快上市,也许很快成为一个价值数百亿美金的「巨头」,它可以等待 pulumi 培育好了市场,利用自己在行业中的口碑和地位不慌不忙地追赶。

2. 资源部署和业务逻辑代码的混合,挑战不小,pulumi 还需要在更复杂的业务场景下证明自己走出的路是可行。目前绝大多数组合使用简单 serverless 的场景,pulumi 已经完全干趴下 serverless framework。但 pulumi 还需要更复杂的场景,更完备的客户的使用案例来证明自己。

3. 用开发人员熟悉的代码描述资源,表现力足够强,但会不会难以阅读和调试?会不会抢了 devOps 的饭碗而导致其很难推行?这个问题和 terraform 第三个问题是一个硬币的两面。公有公理,婆有婆理。有时候我们会大大低估人类的固执和墨守成规,在很多传统 IT 公司,这意味着 IT 部门和研发部门间蛋糕的分配,甚至研发部门内部组织结构间蛋糕的分配。康威定律告诉我们:

设计系统的架构受制于产生这些设计的组织的沟通结构。

因而应用 pulumi 意味着组织架构的调整,所以新兴公司(穷小子)更容易使用 pulumi,而传统公司(富二代)更容易使用 terraform。

4. 用 pulumi 提供的状态管理方案,虽然很容易上手,但规模大一点的公司都会有疑虑。所以 pulumi 还需要提供 on premise(本地软件)的支持。

END

往期精彩文章回顾

重磅!容器存储解决方案蓝皮书发布

这本书,值2000亿!

闲鱼靠什么支撑起万亿的交易规模?

请回答2020

后疫情时代这家在线教育机构如何乘“云”而上

Darabonba:多语言SDK开发终极解决方案

阿里云吴天议:应用驱动云网络深度融合

如何轻松搞定SAP HANA数据库备份?

进阶之路:Java 日志框架全画传(下)

进阶之路:Java 日志框架全画传(中)

长按扫描二维码关注凌云时刻

每日收获前沿技术与科技洞见

让基础设施代码化更加容易,pulumi 都做了些什么?相关推荐

  1. 从架构演进的角度聊聊Spring Cloud都做了些什么?

    Spring Cloud作为一套微服务治理的框架,几乎考虑到了微服务治理的方方面面,之前也写过一些关于Spring Cloud文章,主要偏重各组件的使用,本次分享主要解答这两个问题:Spring Cl ...

  2. 从Java类到对象的创建过程都做了些啥?内存中的对象是啥样的?

    转载自   从Java类到对象的创建过程都做了些啥?内存中的对象是啥样的? 先回顾一下Java程序执行的过程: Java程序执行时,第一步系统创建虚拟机进程,然后虚拟器用类加载器Class Loade ...

  3. fastreport调用frf文件直接打印_打印日志时 Logback 内部都做了些什么

    文 | 百川 on 资产管理 一.引言 Logback 是一个优秀的开源日志框架,我们很多项目都使用它来记录日志.实际使用时,通常仅需要一行语句即可记录相应的日志信息,如 logger.info(&q ...

  4. 软件测试(开发)的V模型都包括哪些阶段,具体都做了些什么?

    软件测试(开发)的V模型都包括哪些阶段,具体都做了些什么? 一.软件测试(开发)的V模型大致可以划分为以下几个不同的阶段: 1.客户需求分析 2.软件需求分析 3.概要设计 4.详细设计 5.编码(软 ...

  5. 黑客讲故事:攻下隔壁女生路由器后,我都做了些什么~

    黑客讲故事:攻下隔壁女生路由器后,我都做了些什么 其实这个问题可以理解为:蹭网之后,能做些什么? 不少人的八卦心窥探欲还是很强烈的,强烈到让人恐惧.所以很多人喜欢看一些八卦文章,比如:如何优雅的窥探别 ...

  6. BCG-MFC 库对《支持重新启动管理器》都做了些什么

    一般用VS2010以上版本,创建MFC的程序都会有这个选项,如下图所示,如果选择了这几项,那么应用程序都做了哪些工作呢?下面一一道来 首先,为了让你自己创建的应用程序下次重新打开的时候能记住上一次的布 ...

  7. 从小游戏到APP,登顶iOS排行榜,这款PVP竞技篮球手游都做了些什么?

    2019年,在市场上大部分篮球小游戏都只是「纯投篮」玩法时,一款主打 PVP 实时 1V1 对抗的休闲竞技小游戏<单挑篮球>让玩家眼前一亮,上线微信.QQ.头条等平台后的1年间累积玩家达到 ...

  8. 编译器在构造函数里都做了些什么?

    我们都知道,C++是一种面向对象的语言,其中一个重要特性是多态性.多态性是通过基类指针指向子类对象,并通过这个基类指针调用子类函数(虚函数)来实现的.但是,看下面这个例子,我可以通过在构造函数里增加一 ...

  9. 这三天低效率开发的总结,我都做了些什么啊?

    4月15日 一大早起来,本来想测试一下服务端程序.把二手笔记本的ubuntu打开,把自己原来笔记本的windows打开.客户端运行在windows上,服务端运行在ubuntu上.测试了一下发现服务端发 ...

  10. 计算机开机是什么,你了解电脑开机时都做了些什么吗?

    (一)学会如何看开机信息 为使用电脑时间比较长的老用户,难免会到朋友或同事家帮忙维护.维修电脑,有时还要帮人购买PC,这时我们肯定会带上硬件检测软件.不过,谁也不能随身总是带着这些软件,如果手头上没有 ...

最新文章

  1. Cisco防火墙技术汇总[转贴]
  2. Kali国内源更新sources.list
  3. 网络布线,多图、有细节,【炮哥】讲诉,值得无经验午饭手抄
  4. go初学者安装echo框架
  5. [ZZ88]送给即将毕业奔三的男人们的16条忠告
  6. 于计算机交朋友教案,三年级上册信息技术教案-2与计算机交朋友|人教版 (2)
  7. 从一个Android码农视角回顾2018GDD大会
  8. ROC和AUC介绍以及如何计算AUC
  9. PHP文件系统-文件下载
  10. 清华大学计算机学院主页,计算机图形学基础课程主页 | 清华大学计算机系
  11. 微软内部文件:Longhorn相关工作外包印度
  12. 安装使用sublime 对比工具sublimerge
  13. Centos6.x 64位 安装JDK
  14. Super超图,GIS软件
  15. Solaris 10的第二次更新要点
  16. pyqt QTableView详细用法
  17. Racket 8.3下载安装(Win10)
  18. faceapp一直显示选择服务器,faceapp提示choosing a serve_faceapp提示choosing a serve解决办法_玩游戏网...
  19. 企业管理之道:中层管理者,到底该管理什么?
  20. 我辛辛苦苦做了一个月的项目,组长年底用来写了晋升PPT

热门文章

  1. Intellij IDEA 12 详细开发教程(三)忽略编译错误设置和快捷键
  2. 取消UltraEdit提示“文件可能不是DOS格式”
  3. 设计模式-第四篇之代理模式
  4. 【JavaScript创建对象】
  5. Struts配置文件的加载顺序
  6. HBase集成(准备篇)
  7. 安装Mercurial进行版本管理
  8. C#应用中的LINQ 查询语言
  9. 传智播客 C/C++学习笔记 字符串替换
  10. 传智播客 C/C++学习笔记 二级指针作为输出参数以及空间的释放