typescript 如何确保部署包含VPC的CDK Stack后AWS Elastic IP保持不变?

gcmastyq  于 2023-05-08  发布在  TypeScript
关注(0)|答案(1)|浏览(135)

我正在部署一个CDK堆栈,其中包括一个VPC,该VPC在AWS中具有静态弹性IP。
我想确保IP保持不变,即使我重新部署包含VPC的整个堆栈,因为我需要避免用户使用新的白名单IP(在部署时重新生成)更新他们的API密钥。
我目前正在使用下面的CDK代码“创建”弹性IP:

const natGatewayProvider = NatInstanceProvider.instance({
    instanceType: new InstanceType('t3.micro')
});
const vpcFargate = new Vpc(stack, 'WhiteVpcFargate', {
    vpcName: 'white-vpc-fargate',
    natGateways: 1, // 👈 Automatically creates an Elastic IP
    natGatewayProvider: natGatewayProvider,
    maxAzs: 2
});

// I'm creating more EIPs and associating to that NAT
const allEIPs = natGatewayProvider.configuredGateways.map((nat, index) =>{
        for(let i=0;i<3;i++){
            new CfnEIP(stack, `NatInstanceEIP${index + 1}_${i}`, {
                instanceId: nat.gatewayId,
                tags: [
                    { key: 'Name', value: `NatInstanceEIP${index + 1}_${i}` },
                ],
            })
        }
    }
)

什么是确保弹性IP保持不变的最佳实践,即使在重新部署包含VPC的整个堆栈后?
我想我应该在不同的CDK堆栈中创建EIP,使用CLI手动创建它们,或者在云控制台上创建它们,然后以某种方式引用AWS CDK中NAT网关定义上的这些EIP,使用CfnEIPAssociation进行关联,但我不确定这是否是正确的路径。

tcomlyy6

tcomlyy61#

可能不是最好的解决方案,但我最终创建了EIP,然后将它们与NAT相关联。
EIP创建的第一部分必须在CDK之外完成,因此我使用aws-sdk一次性NodeJs脚本。

弹性公网IP一次创建:

import AWS, {EC2} from 'aws-sdk';
const ec2 = new AWS.EC2({region: 'use-your-region-here'});

const createElasticIPs = async (count: number): Promise<any[]> => {
    const elasticIps: any[] = [];
    for (let i = 0; i < count; i++) {
        const result = await ec2.allocateAddress({
            Domain: 'vpc'
        }).promise();
        console.log(`Created Elastic IP ${result.PublicIp} with Allocation ID ${result.AllocationId}`);
        elasticIps.push({
            AllocationId: result.AllocationId ?? 'no-allocation-id',
            PublicIp: result.PublicIp ?? 'no-public-ip'
        });
    }
    return elasticIps;
};

我运行这个方法,并使用输出中打印的AllocationIds来使用AWS CDK设置NAT网关。(如果您丢失了createElasticIPs方法的执行日志,您仍然可以通过AWS控制台看到每个EIP的AllocationId)。

CDK NAT网关定义:

const natGatewayProvider = new CustomNatProvider({
        allocationIds: [
            'eipalloc-1', // <-- Use those your one-time script logged
            'eipalloc-2', // <-- Use those your one-time script logged
            'eipalloc-3', // <-- Use those your one-time script logged
        ]
    });

    const vpcFargate = new Vpc(stack, 'WhiteVpcFargate', {
        natGateways: 1,
        natGatewayProvider: natGatewayProvider,
        maxAzs: 2
    });

也许我应该使用CDK来提取ID,而不是将它们设置为硬编码...
下面是CustomNatProvider的实现:

import {
    CfnNatGateway,
    ConfigureNatOptions,
    GatewayConfig,
    NatProvider,
    PrivateSubnet,
    RouterType
} from "aws-cdk-lib/aws-ec2";

export interface MyNatGatewayProps {
    allocationIds: string[];
}

export class CustomNatProvider extends NatProvider {

    private gateways: PrefSet<string> = new PrefSet<string>();

    private allocationIds: string[] = [];

    constructor(private props: MyNatGatewayProps) {
        super();
        this.allocationIds = props.allocationIds;
    }

    public configureNat(options: ConfigureNatOptions) {
        // Create the NAT gateways
        for (const sub of options.natSubnets) {
            if(this.allocationIds.length > 0){
                sub.addNatGateway = () => {
                    const test = this.allocationIds[0]
                    const ngw = new CfnNatGateway(sub, `NATGateway`, {
                        subnetId: sub.subnetId,
                        allocationId: this.allocationIds[0]
                    });
                    this.allocationIds.shift();
                    return ngw;
                };
            }
            const gateway = sub.addNatGateway();
            this.gateways.add(sub.availabilityZone, gateway.ref);
        }
        // Add routes to them in the private subnets
        for (const sub of options.privateSubnets) {
            this.configureSubnet(sub);
        }
    }

    public configureSubnet(subnet: PrivateSubnet) {
        const az = subnet.availabilityZone;
        const gatewayId = this.gateways.pick(az);
        subnet.addRoute('DefaultRoute', {
            routerType: RouterType.NAT_GATEWAY,
            routerId: gatewayId,
            enablesInternetConnectivity: true,
        });
    }

    public get configuredGateways(): GatewayConfig[] {
        return this.gateways.values().map((x: any[]) => ({ az: x[0], gatewayId: x[1] }));
    }

}

class PrefSet<A> {
    private readonly map: Record<string, A> = {};
    private readonly vals = new Array<[string, A]>();
    private next: number = 0;

    public add(pref: string, value: A) {
        this.map[pref] = value;
        this.vals.push([pref, value]);
    }

    public pick(pref: string): A {
        if (this.vals.length === 0) {
            throw new Error('Cannot pick, set is empty');
        }

        if (pref in this.map) { return this.map[pref]; }
        return this.vals[this.next++ % this.vals.length][1];
    }

    public values(): Array<[string, A]> {
        return this.vals;
    }
}

相关问题