Pulumi 与 Terraform 在基础设施即代码方面的比较
最近我的很多工作都离不开 Terraform。它为基础设施即代码带来的模块化特性真的非常吸引我。如果你还没看过我之前关于 Terraform 的文章,不妨去读一读。
- 如何在 CI/CD 管道中使用 Cypress 和 AWS CodeBuild 运行浏览器测试
- 使用 Terraform 和 AWS,只需约 30 秒即可创建带有 Git 仓库的 CI/CD 流水线
- 如何使用 GitHub 和 AWS 优雅地持续部署静态网站
虽然 Terraform 是个很棒的工具,但花点时间探索其他选择总是值得的。过于局限于某种工具、语言或框架,在大多数情况下都可能导致灾难性的后果。了解其他选择及其优缺点非常重要。几个月前,我在基础设施即代码领域注意到Pulumi这个工具。
Pulumi 的有趣之处在于,它消除了基础设施即代码 (IaC) 的最大门槛之一——无需学习其他编程语言。与 Terraform 不同,它不需要学习其他领域特定语言 (DSL)。如果您熟悉 JavaScript、TypeScript、Python 或 Go,即可立即开始编写基础设施代码。
本文将简要介绍 Pulumi 及其入门方法。之后,我们将使用 Pulumi 在 AWS 中设置静态网站存储桶和 CloudFront 分发,这个用例我在其他 Terraform 示例中也用过。最后,我们将探讨两者之间的优缺点。
Pulumi入门指南
和大多数技术相关的事项一样,我们首先需要安装 Pulumi。如果您运行的是 macOS,我们可以直接从 . 获取brew。
$ brew install pulumi
快速检查版本信息应该可以发现我们已经安装了所有必需的组件。
$ pulumi version
打印出来了吗?太好了,我们进入下一步。
与 Terraform 类似,Pulumi 支持多种云提供商,包括 AWS、Azure、Google Cloud、OpenStack、Kubernetes,甚至支持他们自己的云框架。
本文将介绍如何使用 Pulumi 和 AWS。如果您尚未安装和配置AWS CLI,请立即进行安装和配置。默认情况下,Pulumi 将使用您在配置 AWS CLI 时创建的凭证aws configure。
将 Pulumi 与 Amazon Web Services 结合使用
首先,我们将使用以下命令创建一个基本的 Pulumi 项目。
$ pulumi new aws-javascript --dir pulumi-static
首先系统会提示您登录 Pulumi,请按回车键在浏览器中打开登录页面。登录后,您可以返回终端完成设置。
- 项目名称保留默认值。
- 项目描述保持默认设置即可。
-dev同时创建堆栈。no当出现创建选项提示时,请选择。
现在我们应该能够查看pulumi-static文件夹的内容,并看到我们有两个 Pulumi YAML 文件,一个index.js,当然还有我们的包文件。
如果我们打开pulumi.io并登录,会看到我们创建了一个项目,pulumi-static其中包含两个堆栈。我们也可以通过在命令行中列出堆栈来验证这一点。
$ pulumi stack ls
现在让我们深入了解一下这个index.js文件,并配置一些 AWS 基础设施。打开文件后,我们发现里面已经有一些代码了。我们先暂停一下,讨论一下这段代码目前的作用。
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const bucket = new aws.s3.Bucket("my-bucket");
exports.bucketName = bucket.bucketDomainName;
这段代码代表了一些 AWS 基础设施。更具体地说,我们可以看到它创建了一个名为 `<bucket_name>` 的新 S3 存储桶my-bucket,然后导出该存储桶的名称。请注意,这只是传统的 JavaScript 代码,无需学习其他领域特定语言。
我们可以从命令行运行这段代码来配置我们的 AWS 基础设施。首先,我们运行npm install,然后就可以运行我们的up命令了。
$ npm install
$ pulumi up
选择pulumi-static-dev我们要配置的堆栈。
一旦我们选定了堆栈,就会看到一份资源创建、更新或销毁的计划。这与terraform plan命令非常相似。
Previewing update (pulumi-static-dev):
Type Name Plan
+ pulumi:pulumi:Stack pulumi-static-pulumi-static-dev create
+ └─ aws:s3:Bucket my-bucket create
Resources:
+ 2 to create
在确认提示中选择yes创建存储桶。这将根据我们刚才查看的代码创建我们的 S3 存储桶。堆栈创建完成后,我们应该会看到来自 Pulumi 的以下日志。
Updating (pulumi-static-dev):
Type Name Status
+ pulumi:pulumi:Stack pulumi-static-pulumi-static-dev created
+ └─ aws:s3:Bucket my-bucket created
Outputs:
bucketName: "my-bucket-ed42950.s3.amazonaws.com"
Resources:
+ 2 created
太棒了!我们使用 Pulumi 成功配置了第一个 AWS 基础设施。接下来,让我们修改基础设施,不仅创建一个 S3 存储桶,还要在该存储桶上启用静态网站托管。
我们使用 Pulumi 来配置我们的静态网站基础设施
要为我们的存储桶启用 S3 网站托管功能,我们需要添加一个额外的属性以及一个允许公开读取的存储桶策略。在 Pulumi 中,这也很容易实现。以下是为存储桶添加网站托管功能后的代码。
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const websiteBucket = new aws.s3.Bucket("my-bucket", {
website: {
indexDocument: "index.html"
}
});
let bucketPolicy = new aws.s3.BucketPolicy("publicReadPolicy", {
bucket: websiteBucket.bucket,
policy: websiteBucket.bucket.apply(publicReadPolicyForBucket)
});
exports.websiteUrl = siteBucket.websiteEndpoint;
function publicReadPolicyForBucket(bucketName) {
return JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: "*",
Action: ["s3:GetObject"],
Resource: [
`arn:aws:s3:::${bucketName}/*` // policy refers to bucket name explicitly
]
}
]
});
}
website在这里,我们为正在创建的存储桶添加一个配置属性。然后,我们定义一个BucketPolicy附加到网站存储桶的策略。该策略允许任何人s3:GetObject对我们的存储桶执行操作,这对于从 S3 存储桶托管静态网站至关重要。
up我们可以通过在命令行运行另一个命令,将这些更改应用到我们的堆栈中。
$ pulumi up
我们应该会看到存储桶策略将被创建,并且我们当前的存储桶将被更新。
Previewing update (pulumi-static-dev):
Type Name Plan Info
pulumi:pulumi:Stack pulumi-static-pulumi-static-dev
~ ├─ aws:s3:Bucket my-bucket update [diff: +website]
+ └─ aws:s3:BucketPolicy publicReadPolicy create
Resources:
+ 1 to create
~ 1 to update
2 changes. 1 unchanged
更新完成后,我们应该会看到 S3 静态网站托管 URL 被输出。
Outputs:
- bucketName: "my-bucket-e4e2278.s3.amazonaws.com"
+ websiteUrl: "my-bucket-e4e2278.s3-website-us-west-2.amazonaws.com"
Resources:
+ 1 created
~ 1 updated
2 changes. 1 unchanged
太棒了!我们现在有了一个配置好的 S3 存储桶,用来托管静态网站,这一切都是通过 Pulumi 使用 JavaScript 完成的。
让我们再进一步扩展,添加一个位于 S3 网站存储桶前端的 CloudFront 分发。这需要我们创建一个新的分发,并将我们的 S3 存储桶设置为源。以下是将 CloudFront CDN 添加到我们的基础架构后的代码。
"use strict";
const pulumi = require("@pulumi/pulumi");
const aws = require("@pulumi/aws");
const websiteBucket = new aws.s3.Bucket("my-bucket", {
website: {
indexDocument: "index.html"
}
});
let bucketPolicy = new aws.s3.BucketPolicy("publicReadPolicy", {
bucket: websiteBucket.bucket,
policy: websiteBucket.bucket.apply(publicReadPolicyForBucket)
});
const cloudFrontDistribution = new aws.cloudfront.Distribution("myBucketDistribution", {
enabled: true,
defaultRootObject: "index.html",
origins: [
{
customOriginConfig: {
httpPort: 80,
httpsPort: 443,
originProtocolPolicy: "match-viewer",
originSslProtocols: ["TLSv1", "SSLv3"]
},
originId: websiteBucket.websiteEndpoint,
domainName: websiteBucket.websiteEndpoint
}
],
defaultCacheBehavior: {
viewerProtocolPolicy: "redirect-to-https",
allowedMethods: ["GET", "HEAD", "OPTIONS"],
cachedMethods: ["GET", "HEAD", "OPTIONS"],
forwardedValues: {
cookies: { forward: "none" },
queryString: false,
},
targetOriginId: websiteBucket.websiteEndpoint
},
restrictions: {
geoRestriction: {
locations: [],
restrictionType: "none"
}
},
viewerCertificate: {
cloudfrontDefaultCertificate: true
}
});
exports.websiteUrl = cloudFrontDistribution.domainName;
function publicReadPolicyForBucket(bucketName) {
return JSON.stringify({
Version: "2012-10-17",
Statement: [
{
Effect: "Allow",
Principal: "*",
Action: ["s3:GetObject"],
Resource: [
`arn:aws:s3:::${bucketName}/*` // policy refers to bucket name explicitly
]
}
]
});
}
我们在这里添加的关键内容是变量cloudFrontDistribution。它定义了我们 CDN 分发的所有组件,包括默认缓存行为、S3 网站存储桶的源地址,以及最终用户的默认限制和 SSL 证书。
另一次运行pulumi up应该可以配置我们的新 CloudFront 分发。
$ pulumi up
Previewing update (pulumi-static-dev):
Type Name Plan
pulumi:pulumi:Stack pulumi-static-pulumi-static-dev
+ └─ aws:cloudfront:Distribution myBucketDistribution create
Resources:
+ 1 to create
3 unchanged
确认更新后,我们应该会看到 CloudFront 域名显示出来。
Updating (pulumi-static-dev):
Type Name Status
pulumi:pulumi:Stack pulumi-static-pulumi-static-dev
+ └─ aws:cloudfront:Distribution myBucketDistribution created
Outputs:
~ websiteUrl: "my-bucket-e4e2278.s3-website-us-west-2.amazonaws.com" => "d2stkileejh34y.cloudfront.net"
Resources:
+ 1 created
3 unchanged
就这样,我们已经在 AWS 上配置好了支持静态网站的基础设施。我们有一个 S3 存储桶用于托管,还有一个 CloudFront 分发网络用于内容分发。所有这些都是通过 Pulumi 运行的 JavaScript 代码配置的,该代码用于配置我们的基础设施。
是不是很棒?我们能够编写自己熟悉且喜爱的代码,在亚马逊云服务 (AWS) 上配置所需的基础设施。我们还可以继续扩展这段代码,为我们的发行版添加 DNS 别名,甚至上传静态网站文件。
最后,让我们回顾一下 Pulumi 与 Terraform 的一些相似之处、不同之处、优势和劣势。
反思普鲁米
值得注意的是,Pulumi 与 Terraform 是不同的工具,而且坦白说,工具领域永远不嫌多。每个进入基础设施即代码领域的工具都会带来不同的理念和观点。我们可能认同其中一些,也可能不认同另一些,这完全正常。选择最适合你工作流程的工具即可。
话虽如此,以下是我在使用 Pulumi 时的一些想法。
- 用你日常使用的编程语言编写基础设施简直太棒了!避免学习另一种领域特定语言(DSL)带来的学习曲线,这本身就是一大优势。此外,通过像 JavaScript 这样的代码来表示我们的基础设施,我们可以轻松地使用我们已经熟悉且喜爱的框架来添加测试。测试一直是许多使用 Terraform 的开发者的难题,虽然 Terraform 也有相应的解决方案,但不如 JavaScript 代码测试那样容易上手。
plan在基础设施配置方面,命令行非常强大。很高兴看到 Pulumi 和 Terraform 在计划方面有共通之处。通过代码配置基础设施时,能够直观地看到哪些资源将被创建、更新和销毁,这非常有帮助。- 使用命令行工具需要登录,这很奇怪。使用 Terraform 时,我们可以安装 CLI 并立即使用。但 Pulumi 并非如此,我认为这很不方便。
- 状态,状态,状态。Terraform 和 Pulumi 都通过某种状态文件来跟踪你的基础设施。它们正是通过状态文件来知道需要添加、更新和删除哪些内容。两者的区别在于,Pulumi 默认将状态存储在 Web 后端(因此需要上面提到的登录步骤)。而 Terraform 则默认将状态存储在本地。
- 文档必填字段。这真的让我在使用 Pulumi 配置 CloudFront 分发时抓狂。我甚至不得不打开他们的源代码,才能看到创建新分发时他们期望的值。在我看来,Terraform 在这方面做得更好,所有内容都集中在一个地方记录,并准确地告诉我哪些是必需的属性。
- 别把我的巧克力和你的 CloudFormation 混为一谈。像 Terraform 和 Pulumi 这样的工具存在的意义就在于,它们比 CloudFormation 更友好一些。我并非对 CloudFormation 有什么意见,只是有时候它的操作方式需要一些额外的理解,我宁愿避免这种转变。使用 Pulumi 时,由于缺乏文档,我不得不查阅 CloudFormation 中用于配置 CloudFront 分发等资源的具体参数。这主要针对 AWS 内部的基础设施,所以实际情况可能有所不同。
这是我使用 Pulumi 几周后的感想。如果要在新项目中使用 Pulumi 而不是 Terraform,我是否会选择它?不太可能,因为我已经熟练掌握了 Terraform 的 HCL,所以不需要像其他人那样经历学习曲线。此外,我发现 Terraform 的文档更好,这大大加快了我的开发速度。
Pulumi 是一款功能强大的工具,它提供了 Terraform 的许多优点,包括使用您已经熟悉且喜爱的语言编写代码。但是,它仍然存在一些需要改进的不足之处。
不妨亲自尝试一下,用它做点什么,看看它的优点是否大于潜在的缺点。掌握多种工具总没坏处!欢迎在下方提问或分享您的经验。
您是否渴望了解更多关于亚马逊网络服务的信息?
如果您想开启 AWS 之旅,却不知从何入手,不妨了解一下我的课程。本课程专注于在 AWS 上托管、保护和部署静态网站,让您在实际使用中学习超过 6 种不同的 AWS 服务。掌握基础知识后,我们还可以深入学习两个额外的章节,涵盖基础设施即代码 (IaC) 和持续部署 (CD) 等更高级的主题。
文章来源:https://dev.to/kylegalbraith/how-pulumi-compares-to-terraform-for-infrastruct-as-code-434j