上一篇文章建立了 VPC、Internet Gateway、Default Route Table、Public Subnet,這篇文章會接續前篇內容繼續將其他資源建立完畢。
繼續來建立 Private Subnet,建立的流程與 Public Subnet 一樣。差別在於不特別指定 routeTableId。如果不指定 routeTableId 的話,Subnet
component resource 會自動建立一個 Route Table 並關聯至所建立的 Subnet (可參考 Day14 的 Public Subnet 部分)。因此就能做到每個 Private Subnet 都有一個獨立的 Route Table。
const privateSubnets = pulumi.all([numOfAzs, numOfPrivateSubnet, usedAzs]).apply(([numOfAzs, numOfPrivateSubnet, usedAzs]) => {
const subnets = [];
for (let subnetNum = 0; subnetNum < numOfPrivateSubnet; subnetNum++) {
const azName = usedAzs[subnetNum % numOfAzs];
const subnetName = `${name}-subnet-private-${azName}-${(subnetNum % numOfAzs) + 1}`;
subnets.push(new Subnet(subnetName, {
vpcId: vpc.id,
cidrBlock: divSubnets.privateSubnet[subnetNum],
availabilityZone: azName,
tags: Object.assign({
Name: args.vpcName ? `${name}-subnet-private${subnetNum + 1}-${azName}` : undefined,
}, args.additionalTags),
}, {parent: this}));
}
return subnets;
});
NatGateway
Component Resource建立了 Private Subnet 後,需要建立 NAT Gateway 才能讓 Private Subnet 可以存取 Internet。建立 NAT Gateway 時,需要先建立一個 EIP (Elastic IP),並將 EIP 綁定到 NAT Gateway 中。在這裡,將 EIP 與 NAT Gateway 包裝成一個 Component Resource。
interface NatGatewayArguments {
subnetId: Input<string>;
tags?: Input<{ [key: string]: Input<string> }>;
}
class NatGateway extends ComponentResource {
public natGateway!: aws.ec2.NatGateway;
constructor(name: string, args: NatGatewayArguments, opts: pulumi.ComponentResourceOptions = {}) {
super('pulumi-practice:vpc:NatGateway', name, args, opts);
const natGatewayEip = new aws.ec2.Eip(name, {
tags: args.tags
}, {parent: this});
this.natGateway = new aws.ec2.NatGateway(name, {
subnetId: args.subnetId,
allocationId: natGatewayEip.id,
tags: args.tags
}, {parent: this});
}
}
NAT Gateway 的建立方式區分三種
這邊就直接使用 if 條件判斷要建立的類型分別處理。
if (args.natGateway === 'One') {
} else if (args.natGateway === 'OnePerAz') {
}
如果沒有任何 Private Subnet 的話,就不需要建立 NAT Gateway 了。所以一開始會先判斷 Private Subnet 的數量決定是否建立 NAT Gateway。
接著就是建立 NatGateway
component resource,並將 nat gateway 指派給 subnet。
privateSubnets.apply(subnets => {
// 如果沒有 Private Subnet 就不建立 NAT Gateway
if (subnets.length === 0) {
return;
}
const natGateway = new NatGateway(`${name}-nat-gateway`, {
subnetId: publicSubnets[0].subnet.id,
tags: Object.assign({
Name: args.vpcName ? `${name}-nat-gateway` : undefined,
}, args.additionalTags)
}, {parent: this});
privateSubnets.apply(subnets => {
subnets.forEach(subnet => {
subnet.addNatGateway(natGateway.natGateway.id);
});
});
});
前面所使用的 addNatGateway 方法並不在 Subnet
類別中,需要加入這個方法。
public addNatGateway(natGatewayId: Input<string>) {
if (!this.routeTable) {
throw new Error('RouteTable not found');
}
new aws.ec2.Route(this.name, {
routeTableId: this.routeTable!.id,
destinationCidrBlock: '0.0.0.0/0',
natGatewayId: natGatewayId,
}, {parent: this});
}
另一個狀況是要為每個可用區都建立一個 NAT Gateway。這個需求就比較麻煩,必須分幾個步驟來達成
pulumi.all([numOfAzs]).apply(([numOfAzs]) => {
for (let azNum = 0; azNum < numOfAzs; azNum++) {
const azName = pulumi.output(usedAzs).apply((usedAz => usedAz[azNum]));
// 1. 找到該可用區的第一個 Public Subnet (因為 NAT Gateway 需要放在 Public Subnet 中)
const subnet = pulumi.all([azName, publicSubnets]).apply(([azName, publicSubnets]) => {
return publicSubnets.find(subnet => subnet.subnet.availabilityZone.apply(az => az === azName))!;
});
pulumi.all([azName, privateSubnets]).apply(([azName, privateSubnets]) => {
// 2. 找到所有該可用區的 Private Subnet,如果沒有就不建立 NAT Gateway
const subnetInAz = privateSubnets.filter(subnet => subnet.subnet.availabilityZone.apply(az => az === azName));
if (subnetInAz.length === 0) {
return;
}
// 3. 建立 NAT Gateway
const natGateway = new NatGateway(`${name}-nat-gateway-${azNum}`, {
subnetId: subnet.subnet.id,
tags: Object.assign({
Name: args.vpcName ? `${name}-nat-gateway-${azNum}` : undefined,
}, args.additionalTags)
}, {parent: this});
// 4. 設定每個 Private Subnet 的 NAT Gateway
subnetInAz.forEach(subnet => subnet.addNatGateway(natGateway.natGateway.id));
});
}
});
最後一個部分是要設定 S3 Endpoint。建立完 s3Endpont 後,需要透過 VpcEndpointRouteTableAssociation
將 Endpoint 設定到 Route Table 中。
const region = await aws.getRegion({});
if (args.vpcEndpoints === 'S3Gateway') {
const s3Endpoint = new aws.ec2.VpcEndpoint(`${args.vpcName}-s3-endpoint`, {
vpcId: vpc.id,
serviceName: `com.amazonaws.${region.name}.s3`,
tags: Object.assign({
Name: `${args.vpcName}-s3-endpoint`
}, args.additionalTags)
}, {parent: this});
// 將 S3 Endpoint 加入 Default Route Table
new aws.ec2.VpcEndpointRouteTableAssociation(`${name}-public-s3-endpoint`, {
routeTableId: defaultRouteTable.id,
vpcEndpointId: s3Endpoint.id,
}, {parent: s3Endpoint});
// 將 S3 Endpoint 加入 Public Subnet 的 Route Table
privateSubnets.apply(subnets => {
subnets.forEach(subnet => {
subnet.subnet.availabilityZone.apply(az => {
new aws.ec2.VpcEndpointRouteTableAssociation(`${subnet.name}-s3-endpoint`, {
routeTableId: subnet.routeTable!.id,
vpcEndpointId: s3Endpoint.id,
}, {parent: s3Endpoint});
});
});
});
}
建立的所有資源都有指定 parent,可以從下方輸出的結果看到,所有的資源都會在 Vpc
component resource 內。
以上就是第一部分的內容,建立 AWS Network Infrastructure,範例中還有很多程式碼可以調整。例如可以將建立不同資源拆成不同的 method,不需都擠在 initialize 中。或是可以將建立出來的資源變成 Vpc
類別的屬性。就不再贅述重構的內容了。
從建立一個彈性的 VPC component resource 的過程中,可以發現以下常用的 Pulumi 技巧:
opts
參數傳送 parent
可以讓建立的資源有階層性。下一篇的內容將會基於目前的 AWS 資源,繼續建立 Kubernetes 叢集。