上一篇作者提到了兩套 Infrastructure as Code 工具,分別是 Terraform 跟 Pulumi,大家對於前者可能會是比較熟悉,那本篇用一個實際案例『建立 AWS S3 並上傳靜態網站』來跟大家分享如何從無開始一步一步使用 Pulumi。本教學使用的程式碼都可以在 GitHub 上面瀏覽及下載。教學會拆成七個章節:
- 建立 Pulumi 新專案
- 設定 AWS 環境
- 初始化 Pulumi 架構 (建立 S3 Bucket)
- 更新 AWS 架構 (S3 Hosting)
- 設定 Pulumi Stack 環境變數 (教學二)
- 建立第二個 Pulumi Stack 環境 (教學二)
- 刪除 Pulumi Stack 環境 (教學二)
教學影片
- 00:00 Pulumi 應用實作簡介
- 01:30 章節一: 用 Pulumi 建立新專案
- 02:39 用 Pulumi CLI 初始化專案
- 04:47 介紹 Pulumi 產生的 AWS Go 目錄結構內容
- 06:10 章節二: 設定 AWS 環境
- 08:36 章節三: 建立 AWS S3 Bucket
- 13:44 指定 S3 Bucket 名稱
- 16:09 章節四: 將 S3 Bucket 變成 Web Host
- 16:25 上傳 index.html 到 S3 Bucket 內
- 18:22 設定 S3 Bucket 為 Web Host 讀取 index.html
- 19:17 取得 AWS S3 的 Web URL
- 20:55 修改 S3 Object 的 Permission (ACL)
- 22:45 心得感想
用 Pulumi 建立新專案
步驟一: 安裝 Pulumi CLI 工具
首先你要在自己電腦安裝上 Pulumi CLI 工具,請參考官方網站,根據您的作業環境有不同的安裝方式,底下以 Mac 環境為主
brew install pulumi
透過 brew 即可安裝成功,那升級工具透過底下即可
brew upgrade pulumi
或者您沒有使用 brew,也可以透過 curl 安裝
curl -fsSL https://get.pulumi.com | sh
測試 CLI 指令
$ pulumi version
v2.20.0
有看到版本資訊就是安裝成功了
步驟二: 初始化專案
透過 pulumi new -h
可以看到說明
Usage:
pulumi new [template|url] [flags]
Flags:
-c, --config stringArray Config to save
--config-path Config keys contain a path to a property in a map or list to set
-d, --description string The project description; if not specified, a prompt will request it
--dir string The location to place the generated project; if not specified, the current directory is used
-f, --force Forces content to be generated even if it would change existing files
-g, --generate-only Generate the project only; do not create a stack, save config, or install dependencies
-h, --help help for new
-n, --name string The project name; if not specified, a prompt will request it
-o, --offline Use locally cached templates without making any network requests
--secrets-provider string The type of the provider that should be used to encrypt and decrypt secrets (possible choices: default, passphrase, awskms, azurekeyvault, gcpkms, hashivault) (default "default")
-s, --stack string The stack name; either an existing stack or stack to create; if not specified, a prompt will request it
-y, --yes Skip prompts and proceed with default values
可以選擇的 Template 超多,那我們這次用 AWS 搭配 Go 語言的 Temaplate 當作範例
$ pulumi new aws-go --dir demo
This command will walk you through creating a new Pulumi project.
Enter a value or leave blank to accept the (default), and press <ENTER>.
Press ^C at any time to quit.
project name: (demo)
project description: (A minimal AWS Go Pulumi program)
Created project 'demo'
Please enter your desired stack name.
To create a stack in an organization, use the format <org-name>/<stack-name> (e.g. `acmecorp/dev`).
stack name: (dev)
Created stack 'dev'
aws:region: The AWS region to deploy into: (us-east-1) ap-northeast-1
Saved config
Installing dependencies...
Finished installing dependencies
Your new project is ready to go! ✨
To perform an initial deployment, run 'cd demo', then, run 'pulumi up'
步驟三: 檢查專案目錄結構
└── demo
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── go.mod
├── go.sum
└── main.go
其中 main.go
就是主程式檔案
package main
import (
"github.com/pulumi/pulumi-aws/sdk/v3/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an AWS resource (S3 Bucket)
bucket, err := s3.NewBucket(ctx, "my-bucket", nil)
if err != nil {
return err
}
// Export the name of the bucket
ctx.Export("bucketName", bucket.ID())
return nil
})
}
設定 AWS 環境
在使用 Pulumi 之前要先把 AWS 環境建立好
前置作業
請先將 AWS 環境設定完畢,請用 aws configure
完成 profile 設定
aws configure --profile demo
步驟一: 設定 AWS Region
可以參考 AWS 官方的 Available Regions,並且透過 Pulumi CLI 做調整
cd demo && pulumi config set aws:region ap-northeast-1
切換到 demo 目錄,並執行 pulumi config
步驟二: 設定 AWS Profile
如果你有很多環境需要設定,請使用 AWS Profile 作切換,不要用 default profile。其中 demo
為 profile 名稱
pulumi config set aws:profile demo
初始化 Pulumi 架構 (建立 S3 Bucket)
步驟一: 建立新的 S3 Bucket
bucket, err := s3.NewBucket(ctx, "my-bucket", nil)
if err != nil {
return err
}
步驟二: 執行 Pulumi CLI 預覽
透過底下指令可以直接預覽每個操作步驟所做的改變:
pulumi up
可以看到底下預覽:
Previewing update (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/previews/db6a9e4e-f391-4cc4-b50c-408319b3d8e2
Type Name Plan
+ pulumi:pulumi:Stack demo-dev create
+ └─ aws:s3:Bucket my-bucket create
Resources:
+ 2 to create
Do you want to perform this update? [Use arrows to move, enter to select, type to filter]
yes
> no
details
選擇最後的 details:
Do you want to perform this update? details
+ pulumi:pulumi:Stack: (create)
[urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
+ aws:s3/bucket:Bucket: (create)
[urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
acl : "private"
bucket : "my-bucket-e3d8115"
forceDestroy: false
可以看到更詳細的建立步驟及權限,在此步驟可以詳細知道 Pulumi 會怎麼設定 AWS 架構,透過此預覽方式避免人為操作失誤。
步驟三: 執行部署
看完上面的預覽,我們最後就直接執行:
Do you want to perform this update? yes
Updating (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/updates/3
Type Name Status
+ pulumi:pulumi:Stack demo-dev created
+ └─ aws:s3:Bucket my-bucket created
Outputs:
bucketName: "my-bucket-9dd3052"
Resources:
+ 2 created
Duration: 17s
透過上述 UI 也可以看到蠻多詳細的資訊
步驟四: 顯示更多 Bucket 詳細資訊
// Export the name of the bucket
ctx.Export("bucketID", bucket.ID())
ctx.Export("bucketName", bucket.Bucket)
執行 pulumi up
Updating (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/updates/4
Type Name Status
pulumi:pulumi:Stack demo-dev
Outputs:
+ bucketID : "my-bucket-9dd3052"
bucketName: "my-bucket-9dd3052"
Resources:
2 unchanged
Duration: 7s
步驟五: 更新 Bucket 名稱
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an AWS resource (S3 Bucket)
bucket, err := s3.NewBucket(ctx, "my-bucket", &s3.BucketArgs{
Bucket: pulumi.String("foobar-1234"),
})
if err != nil {
return err
}
// Export the name of the bucket
ctx.Export("bucketID", bucket.ID())
ctx.Export("bucketName", bucket.Bucket)
return nil
})
}
透過 pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/previews/7180c121-235c-40cc-9ae2-d0f68455296f
Type Name Plan Info
pulumi:pulumi:Stack demo-dev
+- └─ aws:s3:Bucket my-bucket replace [diff: ~bucket]
Outputs:
~ bucketID : "my-bucket-9dd3052" => output<string>
~ bucketName: "my-bucket-9dd3052" => "foobar-1234"
Resources:
+-1 to replace
1 unchanged
Do you want to perform this update? details
pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
--aws:s3/bucket:Bucket: (delete-replaced)
[id=my-bucket-9dd3052]
[urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
+-aws:s3/bucket:Bucket: (replace)
[id=my-bucket-9dd3052]
[urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
~ bucket: "my-bucket-9dd3052" => "foobar-1234"
++aws:s3/bucket:Bucket: (create-replacement)
[id=my-bucket-9dd3052]
[urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
~ bucket: "my-bucket-9dd3052" => "foobar-1234"
--outputs:--
~ bucketID : "my-bucket-9dd3052" => output<string>
~ bucketName: "my-bucket-9dd3052" => "foobar-1234"
可以看到系統會砍掉舊的,在建立一個新的 bucket
更新 AWS 架構 (S3 Hosting)
上個步驟教大家如何建立 Infra 架構,那這單元教大家如何將使用 S3 當一個簡單的 Web Hosting。
- 將 index.html 放入 S3 內
- 設定 S3 當作 Web Hosting
- 測試 S3 Hosting
步驟一: 建立 index.html 放入 S3 內
建立 content/index.html
檔案,內容如下
<html>
<body>
<h1>Hello Pulumi S3 Bucket</h1>
</body>
</html>
修改 main.go
,將 index.html
加入到 S3 bucket 內
index := path.Join("content", "index.html")
_, err = s3.NewBucketObject(ctx, "index.html", &s3.BucketObjectArgs{
Bucket: bucket.Bucket,
Source: pulumi.NewFileAsset(index),
})
if err != nil {
return err
}
其中目錄結構如下
├── demo
│ ├── Pulumi.dev.yaml
│ ├── Pulumi.yaml
│ ├── content
│ │ └── index.html
│ ├── go.mod
│ ├── go.sum
│ └── main.go
部署到 S3 Bucket 內
$ pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/previews/a0ac1b69-06b9-4109-800d-20618b36e5c8
Type Name Plan
pulumi:pulumi:Stack demo-dev
+ └─ aws:s3:BucketObject index.html create
Resources:
+ 1 to create
2 unchanged
Do you want to perform this update? details
pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
+ aws:s3/bucketObject:BucketObject: (create)
[urn=urn:pulumi:dev::demo::aws:s3/bucketObject:BucketObject::index.html]
acl : "private"
bucket : "foobar-1234"
forceDestroy: false
key : "index.html"
source : asset(file:77aab46) { content/index.html }
步驟二: 設定 S3 為 Web Hosting
修改 main.go
bucket, err := s3.NewBucket(ctx, "my-bucket", &s3.BucketArgs{
Bucket: pulumi.String("foobar-1234"),
Website: s3.BucketWebsiteArgs{
IndexDocument: pulumi.String("index.html"),
},
})
index := path.Join("content", "index.html")
_, err = s3.NewBucketObject(ctx, "index.html", &s3.BucketObjectArgs{
Bucket: bucket.Bucket,
Source: pulumi.NewFileAsset(index),
Acl: pulumi.String("public-read"),
ContentType: pulumi.String(mime.TypeByExtension(path.Ext(index))),
})
最後設定輸出 URL:
ctx.Export("bucketEndpoint", bucket.WebsiteEndpoint)
最後完整程式碼如下:
package main
import (
"mime"
"path"
"github.com/pulumi/pulumi-aws/sdk/v3/go/aws/s3"
"github.com/pulumi/pulumi/sdk/v2/go/pulumi"
)
func main() {
pulumi.Run(func(ctx *pulumi.Context) error {
// Create an AWS resource (S3 Bucket)
bucket, err := s3.NewBucket(ctx, "my-bucket", &s3.BucketArgs{
Bucket: pulumi.String("foobar-1234"),
Website: s3.BucketWebsiteArgs{
IndexDocument: pulumi.String("index.html"),
},
})
if err != nil {
return err
}
index := path.Join("content", "index.html")
_, err = s3.NewBucketObject(ctx, "index.html", &s3.BucketObjectArgs{
Bucket: bucket.Bucket,
Source: pulumi.NewFileAsset(index),
Acl: pulumi.String("public-read"),
ContentType: pulumi.String(mime.TypeByExtension(path.Ext(index))),
})
if err != nil {
return err
}
// Export the name of the bucket
ctx.Export("bucketID", bucket.ID())
ctx.Export("bucketName", bucket.Bucket)
ctx.Export("bucketEndpoint", bucket.WebsiteEndpoint)
return nil
})
}
執行 pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/previews/edbaefca-f723-4ac5-aabd-7cb638636612
Type Name Plan Info
pulumi:pulumi:Stack demo-dev
~ ├─ aws:s3:Bucket my-bucket update [diff: +website]
~ └─ aws:s3:BucketObject index.html update [diff: ~acl,contentType]
Outputs:
+ bucketEndpoint: output<string>
Resources:
~ 2 to update
1 unchanged
Do you want to perform this update? details
pulumi:pulumi:Stack: (same)
[urn=urn:pulumi:dev::demo::pulumi:pulumi:Stack::demo-dev]
~ aws:s3/bucket:Bucket: (update)
[id=foobar-1234]
[urn=urn:pulumi:dev::demo::aws:s3/bucket:Bucket::my-bucket]
+ website: {
+ indexDocument: "index.html"
}
--outputs:--
+ bucketEndpoint: output<string>
~ aws:s3/bucketObject:BucketObject: (update)
[id=index.html]
[urn=urn:pulumi:dev::demo::aws:s3/bucketObject:BucketObject::index.html]
~ acl : "private" => "public-read"
~ contentType: "binary/octet-stream" => "text/html; charset=utf-8"
Do you want to perform this update? yes
Updating (dev)
View Live: https://app.pulumi.com/appleboy/demo/dev/updates/7
Type Name Status Info
pulumi:pulumi:Stack demo-dev
~ ├─ aws:s3:Bucket my-bucket updated [diff: +website]
~ └─ aws:s3:BucketObject index.html updated [diff: ~acl,contentType]
Outputs:
+ bucketEndpoint: "foobar-1234.s3-website-ap-northeast-1.amazonaws.com"
bucketID : "foobar-1234"
bucketName : "foobar-1234"
Resources:
~ 2 updated
1 unchanged
Duration: 13s
步驟三: 測試 URL
透過底下指令可以拿到 S3 的 URL:
pulumi stack output bucketEndpoint
透過 CURL 指令測試看看
$ curl -v $(pulumi stack output bucketEndpoint)
* Trying 52.219.16.96...
* TCP_NODELAY set
* Connected to foobar-1234.s3-website-ap-northeast-1.amazonaws.com (52.219.16.96) port 80 (#0)
> GET / HTTP/1.1
> Host: foobar-1234.s3-website-ap-northeast-1.amazonaws.com
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< x-amz-id-2: 0NrZfFxZNOs+toz0/86FiASG+MyQE6f+KbKNi4wzcDtmn5mTnQoxupVybR464X8Oi6HDMjSU+i8=
< x-amz-request-id: A55BD2534EDC94A9
< Date: Thu, 11 Feb 2021 03:30:42 GMT
< Last-Modified: Thu, 11 Feb 2021 03:29:14 GMT
< ETag: "46e94ba24774d0c4a768f9461e6b9806"
< Content-Type: text/html; charset=utf-8
< Content-Length: 70
< Server: AmazonS3
<
<html>
<body>
<h1>Hello Pulumi S3 Bucket</h1>
</body>
</html>
* Connection #0 to host foobar-1234.s3-website-ap-northeast-1.amazonaws.com left intact
心得
上述已經可以將靜態網站放在 AWS S3 上面了,下一篇會教大家底下三個章節
- 設定 Pulumi Stack 環境變數
- 建立第二個 Pulumi Stack 環境
- 刪除 Pulumi Stack 環境