Pulumi组件构建Skill pulumi-components

此技能用于通过Pulumi创建可重用的基础设施组件,帮助开发者和运维人员实现云资源的模块化、可组合管理。适用于基础设施即代码、DevOps和云原生架构,关键词包括Pulumi、基础设施即代码、云计算、AWS、Azure、GCP、组件化、云原生。

云原生架构 0 次安装 0 次浏览 更新于 3/25/2026

名称: pulumi-components 用户可调用: false 描述: 使用Pulumi构建可重用的基础设施组件,用于模块化、可组合的云资源。 允许工具: [Bash, Read]

Pulumi组件

使用Pulumi构建可重用的基础设施组件,以创建模块化、可组合和可维护的基础设施。

概述

Pulumi ComponentResources允许您创建更高级别的抽象,将多个云资源封装成逻辑单元。这实现了代码重用、更好的组织和更可维护的基础设施代码。

基本ComponentResource

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface WebServerArgs {
    instanceType?: pulumi.Input<string>;
    ami?: pulumi.Input<string>;
    subnetId: pulumi.Input<string>;
    vpcId: pulumi.Input<string>;
}

export class WebServer extends pulumi.ComponentResource {
    public readonly instance: aws.ec2.Instance;
    public readonly securityGroup: aws.ec2.SecurityGroup;
    public readonly publicIp: pulumi.Output<string>;

    constructor(name: string, args: WebServerArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:infrastructure:WebServer", name, {}, opts);

        const defaultOpts = { parent: this };

        // 创建安全组
        this.securityGroup = new aws.ec2.SecurityGroup(`${name}-sg`, {
            vpcId: args.vpcId,
            description: "Web服务器的安全组",
            ingress: [
                {
                    protocol: "tcp",
                    fromPort: 80,
                    toPort: 80,
                    cidrBlocks: ["0.0.0.0/0"],
                },
                {
                    protocol: "tcp",
                    fromPort: 443,
                    toPort: 443,
                    cidrBlocks: ["0.0.0.0/0"],
                },
            ],
            egress: [{
                protocol: "-1",
                fromPort: 0,
                toPort: 0,
                cidrBlocks: ["0.0.0.0/0"],
            }],
            tags: {
                Name: `${name}-sg`,
            },
        }, defaultOpts);

        // 创建EC2实例
        this.instance = new aws.ec2.Instance(`${name}-instance`, {
            instanceType: args.instanceType || "t3.micro",
            ami: args.ami,
            subnetId: args.subnetId,
            vpcSecurityGroupIds: [this.securityGroup.id],
            tags: {
                Name: `${name}-instance`,
            },
        }, defaultOpts);

        this.publicIp = this.instance.publicIp;

        this.registerOutputs({
            instance: this.instance,
            securityGroup: this.securityGroup,
            publicIp: this.publicIp,
        });
    }
}

高级VPC组件

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface VpcNetworkArgs {
    cidrBlock?: string;
    availabilityZones?: string[];
    enableNatGateway?: boolean;
    enableVpnGateway?: boolean;
    enableDnsHostnames?: boolean;
    enableDnsSupport?: boolean;
    privateSubnetCidrs?: string[];
    publicSubnetCidrs?: string[];
    tags?: { [key: string]: string };
}

export class VpcNetwork extends pulumi.ComponentResource {
    public readonly vpc: aws.ec2.Vpc;
    public readonly publicSubnets: aws.ec2.Subnet[];
    public readonly privateSubnets: aws.ec2.Subnet[];
    public readonly internetGateway: aws.ec2.InternetGateway;
    public readonly natGateways?: aws.ec2.NatGateway[];
    public readonly publicRouteTable: aws.ec2.RouteTable;
    public readonly privateRouteTables: aws.ec2.RouteTable[];
    public readonly vpcId: pulumi.Output<string>;

    constructor(name: string, args: VpcNetworkArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:network:VpcNetwork", name, {}, opts);

        const defaultOpts = { parent: this };
        const cidrBlock = args.cidrBlock || "10.0.0.0/16";
        const azs = args.availabilityZones || ["us-east-1a", "us-east-1b"];
        const publicCidrs = args.publicSubnetCidrs || ["10.0.1.0/24", "10.0.2.0/24"];
        const privateCidrs = args.privateSubnetCidrs || ["10.0.101.0/24", "10.0.102.0/24"];

        // 创建VPC
        this.vpc = new aws.ec2.Vpc(`${name}-vpc`, {
            cidrBlock: cidrBlock,
            enableDnsHostnames: args.enableDnsHostnames !== false,
            enableDnsSupport: args.enableDnsSupport !== false,
            tags: {
                Name: `${name}-vpc`,
                ...args.tags,
            },
        }, defaultOpts);

        this.vpcId = this.vpc.id;

        // 创建互联网网关
        this.internetGateway = new aws.ec2.InternetGateway(`${name}-igw`, {
            vpcId: this.vpc.id,
            tags: {
                Name: `${name}-igw`,
                ...args.tags,
            },
        }, defaultOpts);

        // 创建公共子网
        this.publicSubnets = [];
        for (let i = 0; i < azs.length; i++) {
            const subnet = new aws.ec2.Subnet(`${name}-public-${i}`, {
                vpcId: this.vpc.id,
                cidrBlock: publicCidrs[i],
                availabilityZone: azs[i],
                mapPublicIpOnLaunch: true,
                tags: {
                    Name: `${name}-public-${azs[i]}`,
                    Type: "public",
                    ...args.tags,
                },
            }, defaultOpts);
            this.publicSubnets.push(subnet);
        }

        // 创建私有子网
        this.privateSubnets = [];
        for (let i = 0; i < azs.length; i++) {
            const subnet = new aws.ec2.Subnet(`${name}-private-${i}`, {
                vpcId: this.vpc.id,
                cidrBlock: privateCidrs[i],
                availabilityZone: azs[i],
                tags: {
                    Name: `${name}-private-${azs[i]}`,
                    Type: "private",
                    ...args.tags,
                },
            }, defaultOpts);
            this.privateSubnets.push(subnet);
        }

        // 创建公共路由表
        this.publicRouteTable = new aws.ec2.RouteTable(`${name}-public-rt`, {
            vpcId: this.vpc.id,
            tags: {
                Name: `${name}-public-rt`,
                ...args.tags,
            },
        }, defaultOpts);

        // 创建到互联网网关的路由
        new aws.ec2.Route(`${name}-public-route`, {
            routeTableId: this.publicRouteTable.id,
            destinationCidrBlock: "0.0.0.0/0",
            gatewayId: this.internetGateway.id,
        }, defaultOpts);

        // 将公共子网与公共路由表关联
        this.publicSubnets.forEach((subnet, i) => {
            new aws.ec2.RouteTableAssociation(`${name}-public-rta-${i}`, {
                subnetId: subnet.id,
                routeTableId: this.publicRouteTable.id,
            }, defaultOpts);
        });

        // 如果启用,创建NAT网关
        if (args.enableNatGateway !== false) {
            this.natGateways = [];
            this.publicSubnets.forEach((subnet, i) => {
                const eip = new aws.ec2.Eip(`${name}-nat-eip-${i}`, {
                    vpc: true,
                    tags: {
                        Name: `${name}-nat-eip-${i}`,
                        ...args.tags,
                    },
                }, defaultOpts);

                const natGw = new aws.ec2.NatGateway(`${name}-nat-${i}`, {
                    subnetId: subnet.id,
                    allocationId: eip.id,
                    tags: {
                        Name: `${name}-nat-${i}`,
                        ...args.tags,
                    },
                }, defaultOpts);
                this.natGateways.push(natGw);
            });
        }

        // 创建私有路由表
        this.privateRouteTables = [];
        this.privateSubnets.forEach((subnet, i) => {
            const rt = new aws.ec2.RouteTable(`${name}-private-rt-${i}`, {
                vpcId: this.vpc.id,
                tags: {
                    Name: `${name}-private-rt-${i}`,
                    ...args.tags,
                },
            }, defaultOpts);

            // 如果启用,添加NAT网关路由
            if (this.natGateways && this.natGateways[i]) {
                new aws.ec2.Route(`${name}-private-route-${i}`, {
                    routeTableId: rt.id,
                    destinationCidrBlock: "0.0.0.0/0",
                    natGatewayId: this.natGateways[i].id,
                }, defaultOpts);
            }

            new aws.ec2.RouteTableAssociation(`${name}-private-rta-${i}`, {
                subnetId: subnet.id,
                routeTableId: rt.id,
            }, defaultOpts);

            this.privateRouteTables.push(rt);
        });

        this.registerOutputs({
            vpcId: this.vpcId,
            vpc: this.vpc,
            publicSubnets: this.publicSubnets,
            privateSubnets: this.privateSubnets,
            internetGateway: this.internetGateway,
            natGateways: this.natGateways,
        });
    }
}

数据库组件与RDS

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface DatabaseArgs {
    engine: "postgres" | "mysql" | "mariadb";
    engineVersion: string;
    instanceClass?: string;
    allocatedStorage?: number;
    databaseName: string;
    username: string;
    password: pulumi.Input<string>;
    vpcId: pulumi.Input<string>;
    subnetIds: pulumi.Input<string>[];
    backupRetentionPeriod?: number;
    multiAz?: boolean;
    allowedSecurityGroupIds?: pulumi.Input<string>[];
    allowedCidrBlocks?: string[];
}

export class Database extends pulumi.ComponentResource {
    public readonly instance: aws.rds.Instance;
    public readonly subnetGroup: aws.rds.SubnetGroup;
    public readonly securityGroup: aws.ec2.SecurityGroup;
    public readonly endpoint: pulumi.Output<string>;
    public readonly port: pulumi.Output<number>;

    constructor(name: string, args: DatabaseArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:database:Database", name, {}, opts);

        const defaultOpts = { parent: this };

        // 创建数据库子网组
        this.subnetGroup = new aws.rds.SubnetGroup(`${name}-subnet-group`, {
            subnetIds: args.subnetIds,
            tags: {
                Name: `${name}-subnet-group`,
            },
        }, defaultOpts);

        // 创建安全组
        this.securityGroup = new aws.ec2.SecurityGroup(`${name}-sg`, {
            vpcId: args.vpcId,
            description: `${name}数据库的安全组`,
            tags: {
                Name: `${name}-sg`,
            },
        }, defaultOpts);

        // 根据引擎获取端口
        const portMap = {
            postgres: 5432,
            mysql: 3306,
            mariadb: 3306,
        };
        const dbPort = portMap[args.engine];

        // 为安全组添加入站规则
        if (args.allowedSecurityGroupIds) {
            args.allowedSecurityGroupIds.forEach((sgId, i) => {
                new aws.ec2.SecurityGroupRule(`${name}-sg-rule-${i}`, {
                    type: "ingress",
                    fromPort: dbPort,
                    toPort: dbPort,
                    protocol: "tcp",
                    sourceSecurityGroupId: sgId,
                    securityGroupId: this.securityGroup.id,
                }, defaultOpts);
            });
        }

        // 为CIDR块添加入站规则
        if (args.allowedCidrBlocks) {
            new aws.ec2.SecurityGroupRule(`${name}-sg-cidr-rule`, {
                type: "ingress",
                fromPort: dbPort,
                toPort: dbPort,
                protocol: "tcp",
                cidrBlocks: args.allowedCidrBlocks,
                securityGroupId: this.securityGroup.id,
            }, defaultOpts);
        }

        // 创建RDS实例
        this.instance = new aws.rds.Instance(`${name}-instance`, {
            engine: args.engine,
            engineVersion: args.engineVersion,
            instanceClass: args.instanceClass || "db.t3.micro",
            allocatedStorage: args.allocatedStorage || 20,
            dbName: args.databaseName,
            username: args.username,
            password: args.password,
            dbSubnetGroupName: this.subnetGroup.name,
            vpcSecurityGroupIds: [this.securityGroup.id],
            backupRetentionPeriod: args.backupRetentionPeriod || 7,
            multiAz: args.multiAz || false,
            skipFinalSnapshot: true,
            tags: {
                Name: `${name}-instance`,
            },
        }, defaultOpts);

        this.endpoint = this.instance.endpoint;
        this.port = this.instance.port;

        this.registerOutputs({
            endpoint: this.endpoint,
            port: this.port,
            instance: this.instance,
        });
    }
}

容器应用程序组件(ECS)

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface ContainerAppArgs {
    vpcId: pulumi.Input<string>;
    publicSubnetIds: pulumi.Input<string>[];
    privateSubnetIds: pulumi.Input<string>[];
    containerImage: string;
    containerPort: number;
    cpu?: number;
    memory?: number;
    desiredCount?: number;
    environment?: { [key: string]: string };
    secrets?: { [key: string]: pulumi.Input<string> };
}

export class ContainerApp extends pulumi.ComponentResource {
    public readonly cluster: aws.ecs.Cluster;
    public readonly taskDefinition: aws.ecs.TaskDefinition;
    public readonly service: aws.ecs.Service;
    public readonly loadBalancer: aws.lb.LoadBalancer;
    public readonly targetGroup: aws.lb.TargetGroup;
    public readonly dnsName: pulumi.Output<string>;

    constructor(name: string, args: ContainerAppArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:container:ContainerApp", name, {}, opts);

        const defaultOpts = { parent: this };

        // 创建ECS集群
        this.cluster = new aws.ecs.Cluster(`${name}-cluster`, {
            tags: {
                Name: `${name}-cluster`,
            },
        }, defaultOpts);

        // 创建ALB安全组
        const albSg = new aws.ec2.SecurityGroup(`${name}-alb-sg`, {
            vpcId: args.vpcId,
            description: "ALB的安全组",
            ingress: [
                {
                    protocol: "tcp",
                    fromPort: 80,
                    toPort: 80,
                    cidrBlocks: ["0.0.0.0/0"],
                },
                {
                    protocol: "tcp",
                    fromPort: 443,
                    toPort: 443,
                    cidrBlocks: ["0.0.0.0/0"],
                },
            ],
            egress: [{
                protocol: "-1",
                fromPort: 0,
                toPort: 0,
                cidrBlocks: ["0.0.0.0/0"],
            }],
            tags: {
                Name: `${name}-alb-sg`,
            },
        }, defaultOpts);

        // 创建应用负载均衡器
        this.loadBalancer = new aws.lb.LoadBalancer(`${name}-alb`, {
            internal: false,
            loadBalancerType: "application",
            securityGroups: [albSg.id],
            subnets: args.publicSubnetIds,
            tags: {
                Name: `${name}-alb`,
            },
        }, defaultOpts);

        // 创建目标组
        this.targetGroup = new aws.lb.TargetGroup(`${name}-tg`, {
            port: args.containerPort,
            protocol: "HTTP",
            vpcId: args.vpcId,
            targetType: "ip",
            healthCheck: {
                enabled: true,
                path: "/health",
                interval: 30,
                timeout: 5,
                healthyThreshold: 2,
                unhealthyThreshold: 2,
            },
            tags: {
                Name: `${name}-tg`,
            },
        }, defaultOpts);

        // 创建ALB监听器
        new aws.lb.Listener(`${name}-listener`, {
            loadBalancerArn: this.loadBalancer.arn,
            port: 80,
            protocol: "HTTP",
            defaultActions: [{
                type: "forward",
                targetGroupArn: this.targetGroup.arn,
            }],
        }, defaultOpts);

        // 创建任务执行角色
        const taskExecRole = new aws.iam.Role(`${name}-task-exec-role`, {
            assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
                Service: "ecs-tasks.amazonaws.com",
            }),
            tags: {
                Name: `${name}-task-exec-role`,
            },
        }, defaultOpts);

        new aws.iam.RolePolicyAttachment(`${name}-task-exec-policy`, {
            role: taskExecRole.name,
            policyArn: "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy",
        }, defaultOpts);

        // 构建环境变量
        const envVars = Object.entries(args.environment || {}).map(([name, value]) => ({
            name,
            value,
        }));

        // 构建密钥
        const secretVars = Object.entries(args.secrets || {}).map(([name, valueFrom]) => ({
            name,
            valueFrom,
        }));

        // 创建任务定义
        const containerDef = pulumi.all([
            pulumi.output(args.containerImage),
            pulumi.output(args.containerPort),
        ]).apply(([image, port]) => JSON.stringify([{
            name: `${name}-container`,
            image: image,
            cpu: args.cpu || 256,
            memory: args.memory || 512,
            essential: true,
            portMappings: [{
                containerPort: port,
                protocol: "tcp",
            }],
            environment: envVars,
            secrets: secretVars.length > 0 ? secretVars : undefined,
            logConfiguration: {
                logDriver: "awslogs",
                options: {
                    "awslogs-group": `/ecs/${name}`,
                    "awslogs-region": aws.getRegion().then(r => r.name),
                    "awslogs-stream-prefix": "ecs",
                },
            },
        }]));

        // 创建CloudWatch日志组
        new aws.cloudwatch.LogGroup(`${name}-logs`, {
            name: `/ecs/${name}`,
            retentionInDays: 7,
            tags: {
                Name: `${name}-logs`,
            },
        }, defaultOpts);

        this.taskDefinition = new aws.ecs.TaskDefinition(`${name}-task`, {
            family: name,
            cpu: String(args.cpu || 256),
            memory: String(args.memory || 512),
            networkMode: "awsvpc",
            requiresCompatibilities: ["FARGATE"],
            executionRoleArn: taskExecRole.arn,
            containerDefinitions: containerDef,
            tags: {
                Name: `${name}-task`,
            },
        }, defaultOpts);

        // 创建服务安全组
        const serviceSg = new aws.ec2.SecurityGroup(`${name}-service-sg`, {
            vpcId: args.vpcId,
            description: "ECS服务的安全组",
            ingress: [{
                protocol: "tcp",
                fromPort: args.containerPort,
                toPort: args.containerPort,
                securityGroups: [albSg.id],
            }],
            egress: [{
                protocol: "-1",
                fromPort: 0,
                toPort: 0,
                cidrBlocks: ["0.0.0.0/0"],
            }],
            tags: {
                Name: `${name}-service-sg`,
            },
        }, defaultOpts);

        // 创建ECS服务
        this.service = new aws.ecs.Service(`${name}-service`, {
            cluster: this.cluster.arn,
            taskDefinition: this.taskDefinition.arn,
            desiredCount: args.desiredCount || 2,
            launchType: "FARGATE",
            networkConfiguration: {
                subnets: args.privateSubnetIds,
                securityGroups: [serviceSg.id],
                assignPublicIp: false,
            },
            loadBalancers: [{
                targetGroupArn: this.targetGroup.arn,
                containerName: `${name}-container`,
                containerPort: args.containerPort,
            }],
            tags: {
                Name: `${name}-service`,
            },
        }, defaultOpts);

        this.dnsName = this.loadBalancer.dnsName;

        this.registerOutputs({
            dnsName: this.dnsName,
            cluster: this.cluster,
            service: this.service,
        });
    }
}

S3静态网站组件

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface StaticWebsiteArgs {
    domainName?: string;
    indexDocument?: string;
    errorDocument?: string;
    enableCdn?: boolean;
    certificateArn?: pulumi.Input<string>;
}

export class StaticWebsite extends pulumi.ComponentResource {
    public readonly bucket: aws.s3.Bucket;
    public readonly bucketPolicy: aws.s3.BucketPolicy;
    public readonly distribution?: aws.cloudfront.Distribution;
    public readonly websiteUrl: pulumi.Output<string>;

    constructor(name: string, args: StaticWebsiteArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:web:StaticWebsite", name, {}, opts);

        const defaultOpts = { parent: this };

        // 创建S3存储桶
        this.bucket = new aws.s3.Bucket(`${name}-bucket`, {
            bucket: args.domainName || undefined,
            website: {
                indexDocument: args.indexDocument || "index.html",
                errorDocument: args.errorDocument || "error.html",
            },
            tags: {
                Name: `${name}-bucket`,
            },
        }, defaultOpts);

        // 公共访问阻止设置
        new aws.s3.BucketPublicAccessBlock(`${name}-public-access-block`, {
            bucket: this.bucket.id,
            blockPublicAcls: args.enableCdn !== false,
            blockPublicPolicy: args.enableCdn !== false,
            ignorePublicAcls: args.enableCdn !== false,
            restrictPublicBuckets: args.enableCdn !== false,
        }, defaultOpts);

        if (args.enableCdn !== false) {
            // 创建CloudFront OAI
            const oai = new aws.cloudfront.OriginAccessIdentity(`${name}-oai`, {
                comment: `${name}的OAI`,
            }, defaultOpts);

            // CloudFront的存储桶策略
            this.bucketPolicy = new aws.s3.BucketPolicy(`${name}-bucket-policy`, {
                bucket: this.bucket.id,
                policy: pulumi.all([this.bucket.arn, oai.iamArn]).apply(([bucketArn, oaiArn]) =>
                    JSON.stringify({
                        Version: "2012-10-17",
                        Statement: [{
                            Effect: "Allow",
                            Principal: {
                                AWS: oaiArn,
                            },
                            Action: "s3:GetObject",
                            Resource: `${bucketArn}/*`,
                        }],
                    })
                ),
            }, defaultOpts);

            // 创建CloudFront分发
            this.distribution = new aws.cloudfront.Distribution(`${name}-cdn`, {
                enabled: true,
                defaultRootObject: args.indexDocument || "index.html",
                origins: [{
                    originId: "s3Origin",
                    domainName: this.bucket.bucketRegionalDomainName,
                    s3OriginConfig: {
                        originAccessIdentity: oai.cloudfrontAccessIdentityPath,
                    },
                }],
                defaultCacheBehavior: {
                    targetOriginId: "s3Origin",
                    viewerProtocolPolicy: "redirect-to-https",
                    allowedMethods: ["GET", "HEAD", "OPTIONS"],
                    cachedMethods: ["GET", "HEAD"],
                    compress: true,
                    forwardedValues: {
                        queryString: false,
                        cookies: {
                            forward: "none",
                        },
                    },
                    minTtl: 0,
                    defaultTtl: 3600,
                    maxTtl: 86400,
                },
                restrictions: {
                    geoRestriction: {
                        restrictionType: "none",
                    },
                },
                viewerCertificate: args.certificateArn ? {
                    acmCertificateArn: args.certificateArn,
                    sslSupportMethod: "sni-only",
                    minimumProtocolVersion: "TLSv1.2_2021",
                } : {
                    cloudfrontDefaultCertificate: true,
                },
                aliases: args.domainName ? [args.domainName] : undefined,
                customErrorResponses: [{
                    errorCode: 404,
                    responseCode: 200,
                    responsePagePath: `/${args.errorDocument || "error.html"}`,
                }],
                tags: {
                    Name: `${name}-cdn`,
                },
            }, defaultOpts);

            this.websiteUrl = this.distribution.domainName.apply(d => `https://${d}`);
        } else {
            // 公共存储桶策略
            this.bucketPolicy = new aws.s3.BucketPolicy(`${name}-bucket-policy`, {
                bucket: this.bucket.id,
                policy: this.bucket.arn.apply(bucketArn =>
                    JSON.stringify({
                        Version: "2012-10-17",
                        Statement: [{
                            Effect: "Allow",
                            Principal: "*",
                            Action: "s3:GetObject",
                            Resource: `${bucketArn}/*`,
                        }],
                    })
                ),
            }, defaultOpts);

            this.websiteUrl = this.bucket.websiteEndpoint.apply(e => `http://${e}`);
        }

        this.registerOutputs({
            websiteUrl: this.websiteUrl,
            bucket: this.bucket,
            distribution: this.distribution,
        });
    }
}

Kubernetes应用程序组件

import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";

export interface K8sAppArgs {
    namespace?: string;
    image: string;
    replicas?: number;
    port: number;
    resources?: {
        requests?: {
            memory?: string;
            cpu?: string;
        };
        limits?: {
            memory?: string;
            cpu?: string;
        };
    };
    environment?: { [key: string]: string };
    secrets?: { [key: string]: string };
    enableIngress?: boolean;
    ingressHost?: string;
}

export class K8sApp extends pulumi.ComponentResource {
    public readonly namespace: k8s.core.v1.Namespace;
    public readonly deployment: k8s.apps.v1.Deployment;
    public readonly service: k8s.core.v1.Service;
    public readonly ingress?: k8s.networking.v1.Ingress;
    public readonly configMap?: k8s.core.v1.ConfigMap;
    public readonly secret?: k8s.core.v1.Secret;

    constructor(name: string, args: K8sAppArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:k8s:K8sApp", name, {}, opts);

        const defaultOpts = { parent: this };
        const ns = args.namespace || "default";

        // 如果指定,创建命名空间
        if (args.namespace && args.namespace !== "default") {
            this.namespace = new k8s.core.v1.Namespace(`${name}-ns`, {
                metadata: {
                    name: args.namespace,
                },
            }, defaultOpts);
        }

        // 为环境变量创建ConfigMap
        if (args.environment && Object.keys(args.environment).length > 0) {
            this.configMap = new k8s.core.v1.ConfigMap(`${name}-config`, {
                metadata: {
                    name: `${name}-config`,
                    namespace: ns,
                },
                data: args.environment,
            }, defaultOpts);
        }

        // 创建Secret
        if (args.secrets && Object.keys(args.secrets).length > 0) {
            this.secret = new k8s.core.v1.Secret(`${name}-secret`, {
                metadata: {
                    name: `${name}-secret`,
                    namespace: ns,
                },
                stringData: args.secrets,
            }, defaultOpts);
        }

        // 构建环境变量
        const envVars: any[] = [];

        if (this.configMap) {
            Object.keys(args.environment || {}).forEach(key => {
                envVars.push({
                    name: key,
                    valueFrom: {
                        configMapKeyRef: {
                            name: `${name}-config`,
                            key: key,
                        },
                    },
                });
            });
        }

        if (this.secret) {
            Object.keys(args.secrets || {}).forEach(key => {
                envVars.push({
                    name: key,
                    valueFrom: {
                        secretKeyRef: {
                            name: `${name}-secret`,
                            key: key,
                        },
                    },
                });
            });
        }

        // 创建Deployment
        this.deployment = new k8s.apps.v1.Deployment(`${name}-deployment`, {
            metadata: {
                name: `${name}-deployment`,
                namespace: ns,
                labels: {
                    app: name,
                },
            },
            spec: {
                replicas: args.replicas || 3,
                selector: {
                    matchLabels: {
                        app: name,
                    },
                },
                template: {
                    metadata: {
                        labels: {
                            app: name,
                        },
                    },
                    spec: {
                        containers: [{
                            name: name,
                            image: args.image,
                            ports: [{
                                containerPort: args.port,
                            }],
                            env: envVars.length > 0 ? envVars : undefined,
                            resources: args.resources,
                            livenessProbe: {
                                httpGet: {
                                    path: "/health",
                                    port: args.port,
                                },
                                initialDelaySeconds: 30,
                                periodSeconds: 10,
                            },
                            readinessProbe: {
                                httpGet: {
                                    path: "/ready",
                                    port: args.port,
                                },
                                initialDelaySeconds: 10,
                                periodSeconds: 5,
                            },
                        }],
                    },
                },
            },
        }, defaultOpts);

        // 创建Service
        this.service = new k8s.core.v1.Service(`${name}-service`, {
            metadata: {
                name: `${name}-service`,
                namespace: ns,
                labels: {
                    app: name,
                },
            },
            spec: {
                selector: {
                    app: name,
                },
                ports: [{
                    port: 80,
                    targetPort: args.port,
                    protocol: "TCP",
                }],
                type: args.enableIngress ? "ClusterIP" : "LoadBalancer",
            },
        }, defaultOpts);

        // 如果启用,创建Ingress
        if (args.enableIngress && args.ingressHost) {
            this.ingress = new k8s.networking.v1.Ingress(`${name}-ingress`, {
                metadata: {
                    name: `${name}-ingress`,
                    namespace: ns,
                    annotations: {
                        "kubernetes.io/ingress.class": "nginx",
                        "cert-manager.io/cluster-issuer": "letsencrypt-prod",
                    },
                },
                spec: {
                    tls: [{
                        hosts: [args.ingressHost],
                        secretName: `${name}-tls`,
                    }],
                    rules: [{
                        host: args.ingressHost,
                        http: {
                            paths: [{
                                path: "/",
                                pathType: "Prefix",
                                backend: {
                                    service: {
                                        name: `${name}-service`,
                                        port: {
                                            number: 80,
                                        },
                                    },
                                },
                            }],
                        },
                    }],
                },
            }, defaultOpts);
        }

        this.registerOutputs({
            deployment: this.deployment,
            service: this.service,
            ingress: this.ingress,
        });
    }
}

Lambda函数组件

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

export interface LambdaFunctionArgs {
    runtime: aws.lambda.Runtime;
    handler: string;
    code: pulumi.asset.AssetArchive | pulumi.asset.FileArchive;
    environment?: { [key: string]: string };
    timeout?: number;
    memorySize?: number;
    vpcConfig?: {
        subnetIds: pulumi.Input<string>[];
        securityGroupIds: pulumi.Input<string>[];
    };
    policies?: pulumi.Input<string>[];
    layers?: pulumi.Input<string>[];
}

export class LambdaFunction extends pulumi.ComponentResource {
    public readonly function: aws.lambda.Function;
    public readonly role: aws.iam.Role;
    public readonly logGroup: aws.cloudwatch.LogGroup;
    public readonly arn: pulumi.Output<string>;

    constructor(name: string, args: LambdaFunctionArgs, opts?: pulumi.ComponentResourceOptions) {
        super("custom:serverless:LambdaFunction", name, {}, opts);

        const defaultOpts = { parent: this };

        // 创建IAM角色
        this.role = new aws.iam.Role(`${name}-role`, {
            assumeRolePolicy: aws.iam.assumeRolePolicyForPrincipal({
                Service: "lambda.amazonaws.com",
            }),
            tags: {
                Name: `${name}-role`,
            },
        }, defaultOpts);

        // 附加基本Lambda执行策略
        new aws.iam.RolePolicyAttachment(`${name}-basic-policy`, {
            role: this.role.name,
            policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
        }, defaultOpts);

        // 如果提供VPC配置,附加VPC执行策略
        if (args.vpcConfig) {
            new aws.iam.RolePolicyAttachment(`${name}-vpc-policy`, {
                role: this.role.name,
                policyArn: "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole",
            }, defaultOpts);
        }

        // 附加其他策略
        if (args.policies) {
            args.policies.forEach((policyArn, i) => {
                new aws.iam.RolePolicyAttachment(`${name}-policy-${i}`, {
                    role: this.role.name,
                    policyArn: policyArn,
                }, defaultOpts);
            });
        }

        // 创建CloudWatch日志组
        this.logGroup = new aws.cloudwatch.LogGroup(`${name}-logs`, {
            name: `/aws/lambda/${name}`,
            retentionInDays: 14,
            tags: {
                Name: `${name}-logs`,
            },
        }, defaultOpts);

        // 创建Lambda函数
        this.function = new aws.lambda.Function(`${name}-function`, {
            runtime: args.runtime,
            handler: args.handler,
            code: args.code,
            role: this.role.arn,
            timeout: args.timeout || 30,
            memorySize: args.memorySize || 256,
            environment: args.environment ? {
                variables: args.environment,
            } : undefined,
            vpcConfig: args.vpcConfig,
            layers: args.layers,
            tags: {
                Name: `${name}-function`,
            },
        }, defaultOpts);

        this.arn = this.function.arn;

        this.registerOutputs({
            arn: this.arn,
            function: this.function,
        });
    }
}

何时使用此技能

使用 pulumi-components 技能当您需要:

  • 创建可重用的基础设施抽象
  • 将多个资源封装成逻辑单元
  • 为您的组织构建基础设施库
  • 实现复杂的多资源模式
  • 确保一致的资源配置
  • 创建更高级别的基础设施API
  • 跨项目共享基础设施代码
  • 构建有见解的基础设施模板
  • 管理资源关系和依赖项
  • 创建自包含的基础设施模块

最佳实践

  1. 使用父关系:创建子资源时始终设置 { parent: this } 以维持正确的资源层次结构
  2. 注册输出:在构造函数末尾调用 registerOutputs() 以暴露组件属性
  3. 类型安全:使用TypeScript接口作为组件参数,具有清晰类型
  4. 输入类型:对于可以是其他资源输出的参数,使用 pulumi.Input<T>
  5. 命名约定:为子资源名称添加组件名称前缀以提高清晰度
  6. 默认选项:为所有子资源创建一个设置父级的 defaultOpts 对象
  7. 文档:添加JSDoc注释解释组件目的和用法
  8. 组合优于继承:倾向于创建组合其他组件的组件
  9. 单一职责:每个组件应封装一个单一逻辑基础设施单元
  10. 显式依赖项:不要依赖隐式依赖项;在代码中使其显式
  11. 资源组:在组件中所有资源上一致地使用标签
  12. 错误处理:在创建资源之前在构造函数中验证输入
  13. 不可变性:避免在构造后修改组件状态
  14. 导出类型化输出:导出强类型输出供使用者使用
  15. 提供程序配置:允许通过选项传递提供程序配置

常见陷阱

  1. 缺少父级:忘记设置 parent: this 会破坏资源层次结构并防止正确删除
  2. 未注册输出:忘记 registerOutputs() 会阻止输出跟踪
  3. 错误的类型URN:使用错误的组件类型格式(应为 category:subcategory:Name
  4. 循环依赖项:在组件之间创建循环引用
  5. 不正确的输出处理:对于依赖值不使用 pulumi.Output.apply()
  6. 硬编码值:硬编码应可配置的参数的值
  7. 缺少资源名称:未添加子资源名称前缀可能导致名称冲突
  8. 不一致的标签:未在所有组件资源上应用一致的标签
  9. 过于复杂的组件:创建做太多事情的组件
  10. 抽象层次不当:在错误抽象层次上创建组件(过高或过低)
  11. 缺少验证:在创建资源之前未验证必需参数
  12. 状态突变:在构造后突变组件状态
  13. 隐式依赖项:依赖Pulumi推断依赖项而不是显式说明
  14. 缺少错误消息:不为无效配置提供有用的错误消息
  15. 紧密耦合:创建与特定实现过于紧密耦合的组件

资源