Pulumi 程式化基礎設施管理

使用 Pulumi 以熟悉的程式語言管理雲端基礎設施,支援 Python、TypeScript、Go 等

專案簡介

Pulumi 是一個開源的基礎設施即程式碼(IaC)平台,讓你使用熟悉的程式語言(Python、TypeScript、Go、C#)管理雲端資源,而非專用的 DSL。

GitHub Stars: 24K+

主要功能

  • 通用語言 - Python、TypeScript、Go、C#、Java
  • 多雲支援 - AWS、Azure、GCP、Kubernetes
  • 狀態管理 - 內建或自管理
  • 秘密管理 - 加密敏感資料
  • 測試支援 - 單元和整合測試

安裝

CLI 安裝

1
2
3
4
5
6
7
8
# macOS
brew install pulumi

# Linux
curl -fsSL https://get.pulumi.com | sh

# Windows
choco install pulumi

語言 SDK

1
2
3
4
5
6
7
8
# Python
pip install pulumi pulumi-aws

# TypeScript/JavaScript
npm install @pulumi/pulumi @pulumi/aws

# Go
go get github.com/pulumi/pulumi/sdk/v3/go/pulumi

快速開始

建立專案

1
2
3
4
5
# 建立新專案
pulumi new aws-python

# 或 TypeScript
pulumi new aws-typescript

專案結構

1
2
3
4
5
my-project/
├── Pulumi.yaml          # 專案設定
├── Pulumi.dev.yaml      # Stack 設定
├── __main__.py          # 主程式
└── requirements.txt     # 依賴

Python 範例

建立 S3 Bucket

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import pulumi
import pulumi_aws as aws

# 建立 S3 bucket
bucket = aws.s3.Bucket("my-bucket",
    website=aws.s3.BucketWebsiteArgs(
        index_document="index.html",
    ),
)

# 輸出 bucket 名稱
pulumi.export("bucket_name", bucket.id)
pulumi.export("bucket_url", bucket.website_endpoint)

建立 VPC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import pulumi
import pulumi_aws as aws

# VPC
vpc = aws.ec2.Vpc("main-vpc",
    cidr_block="10.0.0.0/16",
    enable_dns_hostnames=True,
    tags={"Name": "main-vpc"},
)

# Subnet
subnet = aws.ec2.Subnet("main-subnet",
    vpc_id=vpc.id,
    cidr_block="10.0.1.0/24",
    availability_zone="us-west-2a",
    tags={"Name": "main-subnet"},
)

# Internet Gateway
igw = aws.ec2.InternetGateway("main-igw",
    vpc_id=vpc.id,
)

# Route Table
route_table = aws.ec2.RouteTable("main-rt",
    vpc_id=vpc.id,
    routes=[
        aws.ec2.RouteTableRouteArgs(
            cidr_block="0.0.0.0/0",
            gateway_id=igw.id,
        ),
    ],
)

pulumi.export("vpc_id", vpc.id)
pulumi.export("subnet_id", subnet.id)

建立 EC2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import pulumi
import pulumi_aws as aws

# Security Group
sg = aws.ec2.SecurityGroup("web-sg",
    description="Allow HTTP",
    ingress=[
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=80,
            to_port=80,
            cidr_blocks=["0.0.0.0/0"],
        ),
        aws.ec2.SecurityGroupIngressArgs(
            protocol="tcp",
            from_port=22,
            to_port=22,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
    egress=[
        aws.ec2.SecurityGroupEgressArgs(
            protocol="-1",
            from_port=0,
            to_port=0,
            cidr_blocks=["0.0.0.0/0"],
        ),
    ],
)

# EC2 Instance
instance = aws.ec2.Instance("web-server",
    ami="ami-0c55b159cbfafe1f0",
    instance_type="t2.micro",
    vpc_security_group_ids=[sg.id],
    user_data="""#!/bin/bash
        yum update -y
        yum install -y httpd
        systemctl start httpd
        systemctl enable httpd
        echo "Hello from Pulumi!" > /var/www/html/index.html
    """,
    tags={"Name": "web-server"},
)

pulumi.export("public_ip", instance.public_ip)

TypeScript 範例

ECS 服務

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";

// 建立 VPC
const vpc = new awsx.ec2.Vpc("main", {
    numberOfAvailabilityZones: 2,
});

// 建立 ECS Cluster
const cluster = new aws.ecs.Cluster("cluster", {});

// 建立 ALB
const alb = new awsx.lb.ApplicationLoadBalancer("alb", {
    subnetIds: vpc.publicSubnetIds,
});

// 建立 Fargate Service
const service = new awsx.ecs.FargateService("service", {
    cluster: cluster.arn,
    taskDefinitionArgs: {
        container: {
            image: "nginx",
            cpu: 256,
            memory: 512,
            portMappings: [
                { containerPort: 80, targetGroup: alb.defaultTargetGroup },
            ],
        },
    },
    desiredCount: 2,
    networkConfiguration: {
        subnets: vpc.privateSubnetIds,
        securityGroups: [],
    },
});

export const url = alb.loadBalancer.dnsName;

Stack 管理

建立 Stack

1
2
3
4
5
# 建立開發環境
pulumi stack init dev

# 建立生產環境
pulumi stack init prod

切換 Stack

1
pulumi stack select dev

設定

1
2
3
4
5
6
# 設定變數
pulumi config set aws:region us-west-2
pulumi config set instanceType t2.micro

# 設定秘密
pulumi config set --secret dbPassword mysecretpassword

使用設定

1
2
3
4
5
import pulumi

config = pulumi.Config()
instance_type = config.get("instanceType") or "t2.micro"
db_password = config.require_secret("dbPassword")

部署操作

預覽

1
pulumi preview

部署

1
pulumi up

銷毀

1
pulumi destroy

輸出

1
2
pulumi stack output
pulumi stack output bucket_name

測試

單元測試

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import pulumi
import unittest

class MyMocks(pulumi.runtime.Mocks):
    def new_resource(self, args):
        return [args.name + "_id", args.inputs]

    def call(self, args):
        return {}

pulumi.runtime.set_mocks(MyMocks())

# 導入後測試
from main import bucket

class TestInfra(unittest.TestCase):
    @pulumi.runtime.test
    def test_bucket_name(self):
        def check_name(name):
            self.assertIn("my-bucket", name)
        bucket.id.apply(check_name)

元件

自訂元件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pulumi
import pulumi_aws as aws

class WebServer(pulumi.ComponentResource):
    def __init__(self, name, opts=None):
        super().__init__("custom:WebServer", name, {}, opts)

        self.sg = aws.ec2.SecurityGroup(f"{name}-sg",
            ingress=[...],
            opts=pulumi.ResourceOptions(parent=self),
        )

        self.instance = aws.ec2.Instance(f"{name}-instance",
            vpc_security_group_ids=[self.sg.id],
            opts=pulumi.ResourceOptions(parent=self),
        )

        self.register_outputs({
            "public_ip": self.instance.public_ip,
        })

# 使用
server = WebServer("web")

相關連結

延伸閱讀

comments powered by Disqus
Built with Hugo
Theme Stack designed by Jimmy