Pulumi

快速補充 Pulumi Hello World 的部分

1
2
3
4
brew install pulumi/tap/pulumi

# or Linux distro
curl -fsSL https://get.pulumi.com | sh
  • 如果使用 Python 的話,版本必須高於 3.7 且 pip 也需安裝。
  • Pulumi 可以使用 aws config 或者直接吃 export AWS_ACCESS_KEY_ID=<YOUR_ACCESS_KEY_ID>, export AWS_SECRET_ACCESS_KEY=<YOUR_SECRET_ACCESS_KEY>

切換 AWS profile 並確認 active session

1
2
3
4
5
asp
asp <profile-name>

## check active session
aws sts get-caller-identity

在開始之前我們必須建立一個 S3 bucket 供 Pulumi State and Backends 使用 (官方也提供 Pulumi cloud)
如果沒有公有雲儲存服務可以使用的話,其實也能使用 local file system 作為 state and backend 存放地點

1
aws s3api create-bucket --bucket eric-pulumi-state-backend --create-bucket-configuration LocationConstraint=ap-northeast-1

接著登入 S3 (其實是讓 pulumi 嘗試存取 S3 並建立 .pulumi/meta.yaml)

1
2
3
4
pulumi login s3://eric-pulumi-state-backend

##
Logged in to <hostname> as <account> (s3://eric-pulumi-state-backend)

建立工作目錄、pulumi 專案

1
2
mkdir pulumi-hello-world && cd pulumi-hello-world
pulumi new aws-python

pulumi-new-aws-python

pulumi new aws-python 代表建立一個「我要用 python 寫 AWS IaC 的環境」,因為 state file 裡面可能有機敏資訊所以需要密碼保護

1
2
3
4
5
6
7
tree . -L 1
.
├── Pulumi.pulumi-hello-world.yaml
├── Pulumi.yaml
├── __main__.py
├── requirements.txt
└── venv
  • Pulumi.yaml: 存放 project 資訊
1
2
3
4
5
6
7
8
cat Pulumi.yaml

name: pulumi-hello-world
runtime:
  name: python
  options:
    virtualenv: venv
description: A minimal AWS Python Pulumi program
  • Pulumi.pulumi-hello-world.yaml: stack 的設定檔
1
2
3
4
5
cat Pulumi.pulumi-hello-world.yaml

encryptionsalt: v1:+M475vXfAmM=:v1:W/KxvJxxVxg0O:gygHlvxxxQKUxxxzrV4tsvOMCmg==
config:
  aws:region: ap-northeast-1
  • main.py: 我們要寫的 IaC 在這邊
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
"""An AWS Python Pulumi program"""

import pulumi
from pulumi_aws import s3

# Create an AWS resource (S3 Bucket)
bucket = s3.Bucket('pulumi-hello-world-bucket')

# Export the name of the bucket
pulumi.export('bucket_name', bucket.id)
1
pulumi up

pulumi-up

接著我們來修改一下剛才建立的 S3 bucket Using versioning

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
"""An AWS Python Pulumi program"""

import pulumi
from pulumi_aws import s3

# Create an AWS resource (S3 Bucket)
bucket = s3.Bucket(
    'pulumi-hello-world-bucket',
    acl="private",
    versioning=s3.BucketVersioningArgs(enabled=True,)
                   )

# Export the name of the bucket
pulumi.export('bucket_name', bucket.id)

pulumi-modify

▲ 再次 pulumi up

最後清除

1
pulumi destroy

pulumi-destory

如果需要刪除整個 stack 的話可以使用 pulumi stack rm,會一併刪除 state history file

To delete the stack itself, run pulumi stack rm. Note that this removes the stack entirely from Pulumi Cloud, along with all of its update history.


Getting started with the AWS CDK

這篇筆記會紀錄我認識 AWS CDK (Cloud Development Kit) 的過程。 AWS CDK 作為官方提供搭建基礎設施 (infra) 的 IaC 解決方案,支援多種語言撰寫 例如: NodeJS, TypeScript, JavaScript, Python, Java, C#, Go 。

值得一提的是,不論使用何種語言,後端都會是 NodeJS

All supported languages use the same backend, which runs on Node.js

Install AWS CDK

Install the AWS CDK

AWS CDK Toolkit (cdk command)

1
2
brew install npm
npm install -g aws-cdk

自動補齊

CDK command shall have autocomplete feature

ZSH

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
###-begin-cdk-completions-###
_cdk_yargs_completions()
{
  local reply
  local si=$IFS
  IFS=$'
' reply=($(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" cdk --get-yargs-completions "${words[@]}"))
  IFS=$si
  _describe 'values' reply
}
compdef _cdk_yargs_completions cdk
###-end-cdk-completions-###

Bash

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
###-begin-cdk-completions-###
_cdk_yargs_completions()
{
    local cur_word args type_list

    cur_word="${COMP_WORDS[COMP_CWORD]}"
    args=("${COMP_WORDS[@]}")

    # ask yargs to generate completions.
    type_list=$(cdk --get-yargs-completions "${args[@]}")

    COMPREPLY=( $(compgen -W "${type_list}" -- ${cur_word}) )

    # if no match was found, fall back to filename completion
    if [ ${#COMPREPLY[@]} -eq 0 ]; then
      COMPREPLY=()
    fi

    return 0
}
complete -o default -F _cdk_yargs_completions cdk
###-end-cdk-completions-###

主要觀念

  • 我們透過 Python, NodeJS … 寫出來的 IaC 被稱作 CDK app
  • 每個 CDK app 都包含一個或多個 stack (其實就是 CloudFormation 的 stack)
  • Stack 包含 constructs,每個 construct 包含一至多個 AWS resources。例如:Amazon S3 buckets, Lambda functions, or Amazon DynamoDB tables

其中 constructs 又分為三層:

Layer 1: AWS CloudFormation-only

  • 其實就是直接使用 CloudFormation 來創建 infra,當 AWS 有新服務時也會在短時間內支援
  • 名字開頭都是 Cfn,例如: CfnBucket 就是 Amazon S3 bucket
  • 所有 L1 資源都會定義在 aws-cdk-lib 當中。

Layer 2: Curated

  • 這類 constructs 由 AWS 團隊精心提供,內容包含 L1 並且搭配 最佳實踐 或 推薦安全性設定
  • 例如: Bucket 是 Amazon S3 bucket 的 L2 construct
  • aws-cdk-lib 支援多數穩定的 L2 constructs

Layer 3: Patterns

  • Patterns 結合多個 AWS resources 來創建架構 (architecture)
  • aws-cdk-lib 支援多數穩定的 L3 constructs

init with Python

1
2
3
mkdir aws-cdk-hello-world
cd aws-cdk-hello-world
cdk init --language python

cdk-init-with-python

▲ 使用 cdk init 初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
total 56
drwxr-xr-x@ 14 eric.ke  staff   448B Apr 18 17:04 .
drwxr-x---+ 49 eric.ke  staff   1.5K Apr 19 10:29 ..
drwxr-xr-x@ 12 eric.ke  staff   384B Apr 19 10:12 .git
-rw-r--r--@  1 eric.ke  staff   119B Apr 18 16:48 .gitignore
drwxr-xr-x@  6 eric.ke  staff   192B Apr 18 16:48 .venv
-rw-r--r--@  1 eric.ke  staff   1.6K Apr 18 16:48 README.md
-rw-r--r--@  1 eric.ke  staff   979B Apr 18 16:48 app.py
drwxr-xr-x@  5 eric.ke  staff   160B Apr 18 17:04 aws_cdk_hello_world
-rw-r--r--@  1 eric.ke  staff   2.0K Apr 18 16:48 cdk.json
drwxr-xr-x@  7 eric.ke  staff   224B Apr 18 17:04 cdk.out
-rw-r--r--@  1 eric.ke  staff    14B Apr 18 16:48 requirements-dev.txt
-rw-r--r--@  1 eric.ke  staff    47B Apr 18 16:48 requirements.txt
-rw-r--r--@  1 eric.ke  staff   437B Apr 18 16:48 source.bat
drwxr-xr-x@  4 eric.ke  staff   128B Apr 18 16:48 test

當我們執行 cdk init --language python 時,預設會幫我們建立 Python Virtualenv使用 source .venv/bin/activate 可以啟動獨立環境。

一但啟用 virtualenv 之後就能透過 pip install -r requirements.txt 安裝相依套件 (當然是在虛擬獨立環境)

常用指令

bootstrap

在使用 cdk deploy 部署 infra 之前,必須要先執行 bootstrap 這個前置動作來產生 cdk 所需環境。通常會創造一個名為 CDKToolkit 的 Amazon Cloudfomation stack。

1
2
3
4
# clean profile first
asp
asp <profile-name>
cdk bootstrap

產生 CloudFormation template

1
2
3
4
5
6
cdk synth

## if many stacks
cdk synth MyStack
cdk synth Stack1 Stack2
cdk synth "*"     # all stacks in app

others

  • dk ls 列出 app 內所有 stack
  • cdk diff 列出當前 IaC 與已部署的 app 差異
  • cdk docs 打開瀏覽器查閱 docs

Authentication 認證

~/.aws/config 來說分為兩種類型:

  1. 一般帳號
  2. SSO 帳號 AWS IAM Identity Center (AWS Single Sign-On 的後繼者) - 集中管理人力對多個 AWS 帳戶和應用程式的存取權

範例如下:

1
2
3
4
5
6
7
8
[profile dev]
sso_session = my-sso
sso_account_id = 111122223333
sso_role_name = SampleRole

[sso-session my-sso]
sso_region = us-east-1
sso_start_url = https://my-sso-portal.awsapps.com/start

查詢當前的令牌 (active session) 可以使用:

1
aws sts get-caller-identity

在使用 cdk bootstrap 之前必須先:

  • (推薦) 使用 asp <profile> 指定 profile 並且使用 aws sts get-caller-identity 確認
  • (如果是透過 asp <profile> 切換 active session 的) 即便指定的 profile 內已經有設定 region 也必須 Specifying Region and other configuration或者在 config [default] 設定 region 也可以
  • (承上) 總之 region 可以被定義在 (1) config default profile (2) 環境變數 AWS_DEFAULT_REGION
  • CDK 直接指定 profile 使用。cdk bootstrap --profile <profile>

cloudformation-full-access

▲ CDK bootstrap 需要 cloudformation 權限。

ssm-full-access

▲ CDK bootstrap 需要 ssm:PutParameter 權限。

ecr-full-access

▲ CDK bootstrap 需要 ecr:CreateRepository 權限。

cdk-bootstrap-success

▲ CDK bootstrap 成功畫面。

Hello World - S3

在開始之前想先介紹一下 cdk init 幫我們建立的檔案結構

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
.
├── README.md
├── app.py
├── aws_cdk_hello_world
│   ├....
│   └── aws_cdk_hello_world_stack.py
├── cdk.json
├── cdk.out
│   ├── AwsCdkHelloWorldStack.assets.json
│   ├── AwsCdkHelloWorldStack.template.json
│   ├── ....
├── requirements-dev.txt
├── requirements.txt
├── source.bat
└── tests
    ├── ...
  • app.py 基本上 hello world 不會去動它
  • ./aws_cdk_hello_world/aws_cdk_hello_world_"stack".py 是我們寫 code 的地方
  • 關於更多請參考 Project structure | AWS CDK Workshop

stack-py

  • def __init__ : 這是一個 Python class 中的 constructor (建構子),當建立一個新的物件時,會先呼叫這個函數來初始化物件。在 AWS CDK 中,Stack 類別是用來表示一個 CloudFormation stack,因此這個 constructor 是在繼承 Stack 類別的過程中被定義的。
  • 該 constructor 的第一個參數 self 表示建立的物件本身,後面的 scope 參數則是用來表示建立的這個 Stack 所屬的範疇 (scope)。在 AWS CDK 中,一個 Stack 可能包含其他的 Stack 或資源,因此透過 scope 參數可以指定該 Stack 所屬的父 Stack 或應用程式。第二個參數 construct_id 則是用來表示該 Stack 的唯一識別名稱。
  • **kwargs 表示接受任意數量的關鍵字參數,用來接收 Stack 相關的其他設定,例如指定該 Stack 所使用的環境 (environment) 等等。
  • super().__init__(scope, construct_id, **kwargs) 則是呼叫了父類別 Stack 的 constructor,來初始化這個 Stack 物件。在 constructor 中可以使用其他 AWS CDK 提供的 class 來定義該 Stack 所需的資源,例如上述程式碼中被註解掉的 Queue class。
  • 箭頭符號 -> 被稱為 type hint,用來指定函數返回的型別。例如,-> None 表示函數沒有返回值。

接下來我將透過 Your first AWS CDK app - AWS Cloud Development Kit (AWS CDK) v2 建立一個 S3 的 Hello world

進入 python virtualenv,並安裝所需套件

1
2
source .venv/bin/activate
python -m pip install -r requirements.txt

建立

s3-bucket-app

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from aws_cdk import (
    Stack,
)
from constructs import Construct
import aws_cdk as cdk
import aws_cdk.aws_s3 as s3

class AwsCdkHelloWorldStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here
        bucket = s3.Bucket(self, "EricFirstBucket", versioned=True)

開始部署

1
2
cdk synthesize
cdk deploy

eric-first-bucket

▲ 透過 CDK 建立的 S3 bucket

cloudformation-helloworld-stack

▲ 透過 CDK 建立的 Cloudformation stack resource 列表

cdk-toolkit-bucket

▲ 當初 cdk bootstrap 所創造的 S3 bucket 現在裡面有 Cloudformation template 的 json 檔了

修改

建立成功後,我們來修改 IaC code 看看會發生什麼事情~

對剛才建立的 S3 bucket 新增 removal_policy=cdk.RemovalPolicy.DESTROYauto_delete_objects=True 這兩項屬性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
from aws_cdk import (
    Stack,
)
from constructs import Construct
import aws_cdk as cdk
import aws_cdk.aws_s3 as s3

class AwsCdkHelloWorldStack(Stack):

    def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
        super().__init__(scope, construct_id, **kwargs)

        # The code that defines your stack goes here
        bucket = s3.Bucket(self, "EricFirstBucket", versioned=True,
                           removal_policy=aws_cdk.RemovalPolicy.DESTROY,
                           auto_delete_objects=True
                           )

cdk-diff

cdk diff 顯示異動項目

最後部署新版

1
cdk deploy

刪除

1
cdk destory

更多資源

Next steps