发布于 2026-01-06 0 阅读
0

Pulumi 与 Terraform 在基础设施即代码方面的比较

Pulumi 与 Terraform 在基础设施即代码方面的比较

最近我的很多工作都离不开 Terraform。它为基础设施即代码带来的模块化特性真的非常吸引我。如果你还没看过我之前关于 Terraform 的文章,不妨去读一读。

虽然 Terraform 是个很棒的工具,但花点时间探索其他选择总是值得的。过于局限于某种工具、语言或框架,在大多数情况下都可能导致灾难性的后果。了解其他选择及其优缺点非常重要。几个月前,我在基础设施即代码领域注意到Pulumi这个工具。

Pulumi 的有趣之处在于,它消除了基础设施即代码 (IaC) 的最大门槛之一——无需学习其他编程语言。与 Terraform 不同,它不需要学习其他领域特定语言 (DSL)。如果您熟悉 JavaScript、TypeScript、Python 或 Go,即可立即开始编写基础设施代码。

本文将简要介绍 Pulumi 及其入门方法。之后,我们将使用 Pulumi 在 AWS 中设置静态网站存储桶和 CloudFront 分发,这个用例我在其他 Terraform 示例中也用过。最后,我们将探讨两者之间的优缺点。

Pulumi入门指南

和大多数技术相关的事项一样,我们首先需要安装 Pulumi。如果您运行的是 macOS,我们可以直接从 . 获取brew

$ brew install pulumi
Enter fullscreen mode Exit fullscreen mode

快速检查版本信息应该可以发现我们已经安装了所有必需的组件。

$ pulumi version
Enter fullscreen mode Exit fullscreen mode

打印出来了吗?太好了,我们进入下一步。

与 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
Enter fullscreen mode Exit fullscreen mode

首先系统会提示您登录 Pulumi,请按回车键在浏览器中打开登录页面。登录后,您可以返回终端完成设置。

  1. 项目名称保留默认值。
  2. 项目描述保持默认设置即可。
  3. -dev同时创建堆栈。
  4. no当出现创建选项提示时,请选择。

现在我们应该能够查看pulumi-static文件夹的内容,并看到我们有两个 Pulumi YAML 文件,一个index.js,当然还有我们的包文件。

如果我们打开pulumi.io并登录,会看到我们创建了一个项目,pulumi-static其中包含两个堆栈。我们也可以通过在命令行中列出堆栈来验证这一点。

$ pulumi stack ls
Enter fullscreen mode Exit fullscreen mode

现在让我们深入了解一下这个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;
Enter fullscreen mode Exit fullscreen mode

这段代码代表了一些 AWS 基础设施。更具体地说,我们可以看到它创建了一个名为 `<bucket_name>` 的新 S3 存储桶my-bucket,然后导出该存储桶的名称。请注意,这只是传统的 JavaScript 代码,无需学习其他领域特定语言。

我们可以从命令行运行这段代码来配置我们的 AWS 基础设施。首先,我们运行npm install,然后就可以运行我们的up命令了。

$ npm install
$ pulumi up
Enter fullscreen mode Exit fullscreen mode

选择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
Enter fullscreen mode Exit fullscreen mode

在确认提示中选择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
Enter fullscreen mode Exit fullscreen mode

太棒了!我们使用 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
        ]
      }
    ]
  });
}
Enter fullscreen mode Exit fullscreen mode

website在这里,我们为正在创建的存储桶添加一个配置属性。然后,我们定义一个BucketPolicy附加到网站存储桶的策略。该策略允许任何人s3:GetObject对我们的存储桶执行操作,这对于从 S3 存储桶托管静态网站至关重要。

up我们可以通过在命令行运行另一个命令,将这些更改应用到我们的堆栈中。

$ pulumi up
Enter fullscreen mode Exit fullscreen mode

我们应该会看到存储桶策略将被创建,并且我们当前的存储桶将被更新。

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
Enter fullscreen mode Exit fullscreen mode

更新完成后,我们应该会看到 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
Enter fullscreen mode Exit fullscreen mode

太棒了!我们现在有了一个配置好的 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
        ]
      }
    ]
  });
}

Enter fullscreen mode Exit fullscreen mode

我们在这里添加的关键内容是变量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
Enter fullscreen mode Exit fullscreen mode

确认更新后,我们应该会看到 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
Enter fullscreen mode Exit fullscreen mode

就这样,我们已经在 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