1. บทนำ: Pulumi คืออะไร?
Infrastructure as Code สมัยใหม่
Pulumi เป็น Platform สำหรับ Infrastructure as Code (IaC) ที่แตกต่างจากเครื่องมืออื่นๆ ตรงที่คุณสามารถใช้ภาษาโปรแกรมจริง เช่น TypeScript, Python, Go, C#, และ Java เพื่อสร้างและจัดการโครงสร้างพื้นฐานในคลาวด์
จุดเด่นของ Pulumi
- ใช้ภาษาโปรแกรมจริง - ไม่ต้องเรียนรู้ Domain-Specific Language ใหม่
- Component Architecture - สร้าง reusable components ได้
- Built-in Secrets Management - จัดการ secrets อย่างปลอดภัย
- Multi-cloud Support - รองรับ AWS, Azure, GCP, Kubernetes
ทำไมถึงได้รับความนิยม?
- เร็วและมีประสิทธิภาพ: ใช้ภาษาโปรแกรมจริงทำให้เขียน logic ซับซ้อนได้ง่าย
- Type Safety: TypeScript และ Go มี type checking
- DevOps Culture: เหมาะสมกับ CI/CD และ GitOps workflows
- Tooling: ใช้ editor และ IDE ที่คุ้นเคยได้เลย
ความแตกต่างกับเครื่องมืออื่น
ต่างจาก Terraform (HCL) หรือ AWS CloudFormation (YAML/JSON) ที่ใช้ declarative syntax การเขียนด้วย Pulumi คุณสามารถใช้ loop, function, class และ all programming constructs ที่คุณคุ้นเคยได้ ทำให้โครงสร้างพื้นฐานมีความยืดหยุ่นและ maintain ได้ง่ายขึ้น
2. สิ่งที่ต้องเตรียม (Prerequisites)
รายการที่ต้องมี
ความรู้พื้นฐาน
ความรู้พื้นฐานเกี่ยวกับ Cloud Computing (AWS/Azure/GCP), container, และ Infrastructure concepts
ภาษาโปรแกรม
พื้นฐานการเขียนโปรแกรมในภาษาที่เลือกใช้ (TypeScript, Python, หรือ Go)
บัญชี Cloud
AWS, Azure, หรือ GCP account และ IAM credentials ที่มีสิทธิ์สร้างทรัพยากร
Node.js (สำหรับ TypeScript)
Node.js version 16 หรือใหม่กว่า (สำหรับผู้ที่จะใช้ TypeScript)
CLI ที่ติดตั้งแล้ว
ติดตั้ง AWS CLI, Azure CLI, หรือ GCloud CLI (ไม่บังคับแต่แนะนำ)
หมายเหตุสำคัญ
ถ้าคุณไม่แน่ใจว่าจะเลือกภาษาใด ขอแนะนำว่าเริ่มต้นด้วย TypeScript หรือ Python เพราะมี ecosystem และ documentation ที่สมบูรณ์ที่สุด รวมถึง community ที่ใหญ่ที่สุด
3. การติดตั้งและตั้งค่าเบื้องต้น (Installation & Setup)
การติดตั้ง Pulumi CLI
Pulumi CLI สามารถติดตั้งได้หลายวิธี ทั้งผ่าน npm, package manager หรือ direct download
curl -fsSL 'https://get.pulumi.com' | sh
# ตรวจสอบเวอร์ชัน
pulumi version
# ดูคำสั่งทั้งหมด
pulumi help
หมายเหตุ: หลังติดตั้งเสร็จ คุณอาจต้อง restart terminal หรือ sourcing shell configuration (เช่น source ~/.bashrc)
เลือกภาษาสำหรับ Project
ตอนสร้าง project ใหม่ คุณต้องเลือกภาษาที่จะใช้ในการเขียน infrastructure code
TypeScript (Node.js)
# สร้าง directory สำหรับ project
mkdir my-pulumi-app && cd my-pulumi-app
# สร้าง project ใหม่ด้วย TypeScript
pulumi new typescript
# ติดตั้ง dependencies
npm install
Python
# สร้าง directory สำหรับ project
mkdir my-pulumi-app && cd my-pulumi-app
# สร้าง project ใหม่ด้วย Python
pulumi new python
# สร้าง virtual environment (แนะนำ)
python -m venv .venv
source .venv/bin/activate
# ติดตั้ง dependencies
pip install -r requirements.txt
Go
# สร้าง directory สำหรับ project
mkdir my-pulumi-app && cd my-pulumi-app
# สร้าง project ใหม่ด้วย Go
pulumi new go
# ติดตั้ง dependencies
go mod tidy
การตั้งค่า Cloud Provider
ก่อนใช้งาน Pulumi กับ cloud provider คุณต้อง set credentials ให้กับ Pulumi CLI
AWS
# Option 1: ใช้ AWS CLI
aws configure
# Option 2: ตั้งค่าด้วย environment variables
export AWS_ACCESS_KEY_ID="YOUR_ACCESS_KEY"
export AWS_SECRET_ACCESS_KEY="YOUR_SECRET_KEY"
export AWS_DEFAULT_REGION="us-west-2"
Azure
# ใช้ Azure CLI
az login
# หรือใช้ service principal
export ARM_CLIENT_ID="YOUR_CLIENT_ID"
export ARM_CLIENT_SECRET="YOUR_CLIENT_SECRET"
export ARM_TENANT_ID="YOUR_TENANT_ID"
export ARM_SUBSCRIPTION_ID="YOUR_SUBSCRIPTION_ID"
Google Cloud
# ใช้ GCloud CLI
gcloud auth application-default login
# หรือใช้ service account key
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account-key.json"
4. แนวคิดหลักของ Pulumi (Core Concepts)
Stack
Stack คือ deployment instance หรือ environment หนึ่งๆ เช่น dev, staging, prod หรือ environment ตามภูมิภาค
# ดู stack ทั้งหมด
pulumi stack ls
# สร้าง stack ใหม่
pulumi stack init dev
# เลือก stack ที่จะใช้
pulumi stack select prod
# ดูค่าปัจจุบัน
pulumi stack
Resource
Resource คือ unit ของ infrastructure ที่ Pulumi จัดการ เช่น EC2 instance, S3 bucket, VPC, หรือ Kubernetes pods
import * as aws from "@pulumi/aws";
// S3 Bucket Resource
const bucket = new aws.s3.Bucket("my-bucket", {
bucket: "my-unique-bucket-name",
});
// Lambda Function Resource
const lambdaFunc = new aws.lambda.Function("my-function", {
code: new pulumi.AssetArchive({
".": new pulumi.FileArchive("./lambda"),
}),
runtime: "nodejs14.x",
handler: "index.handler",
role: lambdaRole.arn,
});
Config & Secrets
Pulumi มี built-in secrets management ที่เข้ากับ cloud provider ของคุณ คุณสามารถระบุ secrets ได้โดยใช้ flag --secret
# ตั้งค่า config ทั่วไป
pulumi config set databaseHost db.example.com
# ตั้งค่า secrets (จะถูกเข้ารหัส)
pulumi config set --secret awsSecretKey "YOUR_SECRET_KEY"
# ดูค่า config ทั้งหมด
pulumi config
# ดูค่า secrets อย่างปลอดภัย
pulumi config get awsSecretKey --secret
Provider
Provider คือ Pulumi package ที่ให้ resource types สำหรับ cloud service และ services ต่างๆ
import * as aws from "@pulumi/aws";
// ตั้งค่า provider
const provider = new aws.Provider("myProvider", {
region: "us-west-2",
profile: "my-profile",
});
// ใช้ provider บน resource
const bucket = new aws.s3.Bucket("my-bucket", {}, { provider });
Pulumi Architecture Overview
Pulumi ใช้ architecture แบบ client-server ที่ CLI ทำหน้าที่เป็น client ที่ส่งคำสั่งไปยัง Pulumi Service (หรือ state backend อื่นๆ) และใช้ cloud provider APIs ในการสร้าง infrastructure
Developer เขียนโค้ดและรันคำสั่ง pulumi up
Pulumi CLI ส่งคำสั่งไปยัง Pulumi Service และ cloud provider APIs
Cloud Provider สร้าง infrastructure ตามที่ต้องการ
5. ตัวอย่างโค้ด (Real-World Examples)
ตัวอย่าง AWS Deployment (TypeScript)
ตัวอย่างการสร้างโครงสร้างพื้นฐานบน AWS แบบง่ายด้วย Pulumi และ TypeScript
1. โครงสร้างไฟล์
my-pulumi-app/
|-- Pulumi.yaml # Project configuration
|-- tsconfig.json # TypeScript configuration
|-- package.json # Node.js dependencies
`-- index.ts # Main infrastructure code
2. main/index.ts - สร้าง S3 Bucket และ Lambda
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
// Configuration
const config = new pulumi.Config();
const environment = config.get("environment") || "dev";
// Create Security Group for Lambda
const lambdaSecurityGroup = new aws.ec2.SecurityGroup("lambda-sg", {
description: "Security group for Lambda functions",
ingress: [{
protocol: "-1",
fromPort: 0,
toPort: 0,
cidrBlocks: ["0.0.0.0/0"],
}],
});
// Create S3 Bucket
const bucket = new aws.s3.Bucket(`my-${environment}-app-bucket`, {
bucket: `my-${environment}-app-bucket-${pulumi.getStack()}`,
tags: {
Environment: environment,
ManagedBy: "Pulumi",
},
});
// Create IAM Role for Lambda
const lambdaRole = new aws.iam.Role("lambda-role", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Effect: "Allow",
Principal: {
Service: "lambda.amazonaws.com",
},
}],
}),
});
// Attach basic policy to role
new aws.iam.RolePolicyAttachment("lambda-basic", {
role: lambdaRole.name,
policyArn: aws.iam.ManagedPolicies.AWSLambdaBasicExecutionRole,
});
// Create Lambda Function
const lambdaFunction = new aws.lambda.Function("my-function", {
code: new pulumi.asset.AssetArchive({
".": new pulumi.asset.FileArchive("./lambda"),
}),
runtime: "nodejs14.x",
handler: "index.handler",
role: lambdaRole.arn,
vpcConfig: {
securityGroupIds: [lambdaSecurityGroup.id],
},
environment: {
variables: {
S3_BUCKET: bucket.bucket,
ENVIRONMENT: environment,
},
},
});
// Export outputs
export const bucketName = bucket.bucket;
export const lambdaArn = lambdaFunction.arn;
3. Pulumi config (pulumi.yaml)
name: aws-lambda-app
runtime: nodejs
description: AWS Lambda and S3 application with Pulumi
config:
aws:region:
value: us-west-2
environment:
value: dev
4. รันและ deploy
# สร้าง stack ใหม่
pulumi new typescript --name aws-lambda-app
# ติดตั้ง dependencies
npm install @pulumi/aws
# ตั้งค่า configuration
pulumi config set environment prod
# Preview changes
pulumi preview
# Deploy
pulumi up
# ลบ infrastructure
pulumi destroy
ตัวอย่าง Kubernetes Deployment (Python)
ตัวอย่างการ deploy แอปพลิเคชันบน Kubernetes ด้วย Pulumi และ Python
1. โครงสร้างไฟล์
kubernetes-app/
|-- Pulumi.yaml
|-- requirements.txt
`-- __main__.py # Main Python code
2. __main__.py - Kubernetes Deployment
import pulumi
import pulumi_aws as aws
import pulumi_kubernetes as k8s
from pulumi_kubernetes.apps.v1 import Deployment
from pulumi_kubernetes.core.v1 import Service, PodTemplateSpec, PodSpec, Container
# Configuration
config = pulumi.Config()
app_name = config.get("appName") or "my-app"
# Create EKS Cluster
cluster = aws.eks.Cluster(
f"{app_name}-cluster",
name=f"{app_name}-cluster",
role_arn=aws.iam.Role("eks-role").arn,
vpc_config=aws.eks.ClusterVpcConfigArgs(
public_access_cidrs=["0.0.0.0/0"],
),
)
# Create Kubeconfig
kubeconfig = pulumi.Output.all(cluster.name, cluster.endpoint, cluster.certificate_authority.apply(lambda cp: cp.data)).apply(
lambda args: f"""{{"apiVersion": "v1","kind": "ConfigMap","clusters": [{{"cluster": {{"server": "{args[1]}","certificate-authority-data": "{args[2]}"}},"name": "eks"}}],"contexts": [{{"context": {{cluster": "eks","user": "eks"}},"name": "eks"}}],"current-context": "eks"}}"""
)
# Create Kubernetes Provider
k8s_provider = k8s.Provider(
f"{app_name}-k8s",
kubeconfig=kubeconfig,
)
# Kubernetes Deployment
deployment = Deployment(
f"{app_name}-deployment",
metadata=k8s.meta.v1.ObjectMetaArgs(
name=f"{app_name}-deployment",
labels={"app": app_name},
),
spec=k8s.apps.v1.DeploymentSpecArgs(
selector=k8s.meta.v1.LabelSelectorArgs(
match_labels={"app": app_name},
),
replicas=3,
template=k8s.meta.v1.PodTemplateSpecArgs(
metadata=k8s.meta.v1.ObjectMetaArgs(
labels={"app": app_name},
),
spec=k8s.core.v1.PodSpecArgs(
containers=[Container(
name=app_name,
image="nginx:latest",
ports=[k8s.core.v1.ContainerPortArgs(container_port=80)],
)],
),
),
),
opts=pulumi.ResourceOptions(provider=k8s_provider),
)
# Kubernetes Service
service = Service(
f"{app_name}-service",
metadata=k8s.meta.v1.ObjectMetaArgs(
name=f"{app_name}-service",
labels={"app": app_name},
),
spec=k8s.core.v1.ServiceSpecArgs(
selector={"app": app_name},
ports=[k8s.core.v1.ServicePortArgs(port=80, target_port=80)],
type="LoadBalancer",
),
opts=pulumi.ResourceOptions(provider=k8s_provider),
)
# Export outputs
pulumi.export("cluster_name", cluster.name)
pulumi.export("cluster_endpoint", cluster.endpoint)
pulumi.export("deployment_name", deployment.metadata["name"])
Component Architecture (Reuse)
Pulumi อนุญาตให้สร้าง components ที่สามารถ reuse ได้โดยการสร้างคลาสที่สืบทอดจาก pulumi.ComponentResource
1. สร้าง component ชื่อ WebApp
import pulumi
from pulumi import ResourceOptions
from pulumi_aws import s3, lambda_, iam, apigatewayv2
from typing import Optional, Mapping, Any
class WebApp(pulumi.ComponentResource):
def __init__(
self,
name: str,
opts: Optional[ResourceOptions] = None,
**kwargs: Any,
):
super().__init__("myorg:components:WebApp", name, opts)
# S3 Bucket for static files
self.bucket = s3.Bucket(
f"{name}-bucket",
bucket=f"{name}-{pulumi.get_stack()}-{pulumi.get_project()}",
tags={"Component": "WebApp"},
)
# IAM Role for Lambda
self.lambda_role = iam.Role(
f"{name}-lambda-role",
assume_role_policy="""{
"Version": "2012-10-17",
"Statement": [{
"Action": "sts:AssumeRole",
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"}
}]
}""",
)
# Lambda Function
self.function = lambda_.Function(
f"{name}-function",
runtime="python3.9",
handler="index.handler",
role=self.lambda_role.arn,
code=pulumi.asset.FileArchive("./lambda"),
environment={
"variables": {
"S3_BUCKET": self.bucket.bucket,
}
},
)
# API Gateway
self.api = apigatewayv2.Api(
f"{name}-api",
name=f"{name}-api",
protocol_type="HTTP",
)
#Export outputs
self.bucket_url = self.bucket.bucket_domain_name
self.api_url = self.api.api_endpoint
# Register resources
self.register_outputs({
"bucket_url": self.bucket_url,
"api_url": self.api_url,
})
2. ใช้งาน component
import pulumi
from components.web_app import WebApp
# Create two different web apps
dev_app = WebApp(
"dev-web-app",
opts=pulumi.ResourceOptions(parent=self),
)
prod_app = WebApp(
"prod-web-app",
opts=pulumi.ResourceOptions(parent=self),
)
# Export outputs
pulumi.export("dev_app_url", dev_app.api_url)
pulumi.export("prod_app_url", prod_app.api_url)