iT邦幫忙

2023 iThome 鐵人賽

DAY 15
0
Cloud Native

30 天學習 Pulumi:用各種程式語言控制雲端資源系列 第 15

[Day 15] 實戰練習 (2) - 建立 AWS Network Infrastructure 後半

  • 分享至 

  • xImage
  •  

上一篇文章建立了 VPC、Internet Gateway、Default Route Table、Public Subnet,這篇文章會接續前篇內容繼續將其他資源建立完畢。

建立 Private 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 類型

NAT Gateway 的建立方式區分三種

  1. 每個 availability zone 一個 NAT Gateway
  2. 只建立一個 NAT Gateway
  3. 不建立 NAT Gateway

這邊就直接使用 if 條件判斷要建立的類型分別處理。

if (args.natGateway === 'One') {

} else if (args.natGateway === 'OnePerAz') {

}

全部建立一個 NAT Gateway

如果沒有任何 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

另一個狀況是要為每個可用區都建立一個 NAT Gateway。這個需求就比較麻煩,必須分幾個步驟來達成

  1. 找到該可用區的第一個 Public Subnet (因為 NAT Gateway 需要放在 Public Subnet 中)
  2. 找到所有該可用區的 Private Subnet,如果沒有就不建立 NAT Gateway
  3. 建立 NAT Gateway
  4. 設定每個 Private Subnet 的 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

最後一個部分是要設定 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 內。

https://ithelp.ithome.com.tw/upload/images/20230930/20162822lnYgMfkjt7.png

小結

以上就是第一部分的內容,建立 AWS Network Infrastructure,範例中還有很多程式碼可以調整。例如可以將建立不同資源拆成不同的 method,不需都擠在 initialize 中。或是可以將建立出來的資源變成 Vpc 類別的屬性。就不再贅述重構的內容了。

從建立一個彈性的 VPC component resource 的過程中,可以發現以下常用的 Pulumi 技巧:

  1. 資源的 argument 使用 Input 型別,但開始處理後,儘量轉成 Output 處理
  2. pulumi.all、pulumi.output、Output.apply 等非同步取得 Output 型別值的方法很常使用,務必熟悉。
  3. 透過 opts 參數傳送 parent 可以讓建立的資源有階層性。

下一篇的內容將會基於目前的 AWS 資源,繼續建立 Kubernetes 叢集。


上一篇
[Day 14] 實戰練習 (1) - 建立 AWS Network Infrastructure 前半
下一篇
[Day 16] 實戰練習 (3) - 建立 EKS Cluster
系列文
30 天學習 Pulumi:用各種程式語言控制雲端資源30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言