📝

CloudFormation での VPC 作成時に CIDR が一意になるようにデプロイしてみた

に公開

カスタムリソースの Lambda で実現してみました。

1. Lambda 関数の作成

以下の設定で作成しました。

  • ランタイム: Python 3.13
  • IAM ロール: AdministratorAccess 権限を付与
  • コード: 以下の通り
import json
import boto3
import urllib3
from ipaddress import ip_network

ec2 = boto3.client('ec2')
http = urllib3.PoolManager()

# 使用可能なCIDR候補リスト
AVAILABLE_CIDRS = [
    '10.0.0.0/16',
    '10.1.0.0/16',
    '10.2.0.0/16',
    '10.3.0.0/16',
    '10.4.0.0/16'
]

def lambda_handler(event, context):
    print(f"Received event: {json.dumps(event)}")
    
    response_data = {}
    response_status = 'SUCCESS'
    reason = None
    
    try:
        if event['RequestType'] == 'Delete':
            send_response(event, context, 'SUCCESS', {})
            return
        
        # 既存のVPCのCIDRを取得
        response = ec2.describe_vpcs()
        existing_cidrs = [vpc['CidrBlock'] for vpc in response['Vpcs']]
        
        print(f"Existing VPC CIDRs: {existing_cidrs}")
        print(f"Available CIDR candidates: {AVAILABLE_CIDRS}")
        
        # 既存CIDRと重複しない候補を探す
        available_cidr = None
        for candidate_cidr in AVAILABLE_CIDRS:
            candidate_network = ip_network(candidate_cidr)
            is_overlap = False
            
            for existing_cidr in existing_cidrs:
                existing_network = ip_network(existing_cidr)
                if candidate_network.overlaps(existing_network):
                    is_overlap = True
                    print(f"{candidate_cidr} overlaps with {existing_cidr}")
                    break
            
            if not is_overlap:
                available_cidr = candidate_cidr
                print(f"Found available CIDR: {available_cidr}")
                break
        
        if available_cidr:
            response_data = {
                'AvailableCidr': available_cidr,
                'ExistingCidrs': ','.join(existing_cidrs),
                'CheckedCandidates': ','.join(AVAILABLE_CIDRS)
            }
        else:
            response_status = 'FAILED'
            reason = f"All CIDR candidates overlap with existing VPCs. Existing: {existing_cidrs}"
            response_data = {
                'ExistingCidrs': ','.join(existing_cidrs),
                'CheckedCandidates': ','.join(AVAILABLE_CIDRS)
            }
            
    except Exception as e:
        print(f"Error: {str(e)}")
        response_status = 'FAILED'
        reason = str(e)
    
    send_response(event, context, response_status, response_data, reason)

def send_response(event, context, status, response_data, reason=None):
    response_body = {
        'Status': status,
        'Reason': reason or f'See CloudWatch Log Stream: {context.log_stream_name}',
        'PhysicalResourceId': context.log_stream_name,
        'StackId': event['StackId'],
        'RequestId': event['RequestId'],
        'LogicalResourceId': event['LogicalResourceId'],
        'Data': response_data
    }
    
    print(f"Response body: {json.dumps(response_body)}")
    
    json_response = json.dumps(response_body).encode('utf-8')
    headers = {'Content-Type': 'application/json'}
    
    http.request('PUT', event['ResponseURL'], body=json_response, headers=headers)

今回は使用可能な VPC CIDR を配列として保持していますが、必要に応じてデータベースや Systems Manager Parameter Store に保存する方法も考えられます。

2. CloudFormation テンプレートの作成

最小構成などで Lambda 関数の ARM はハードコーディングしています。

AWSTemplateFormatVersion: "2010-09-09"
Description: "VPC with CIDR overlap check using Custom Resource"

Resources:
  CidrChecker:
    Type: Custom::CidrChecker
    Properties:
      ServiceToken: arn:aws:lambda:ap-northeast-1:012345678901:function:test

  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !GetAtt CidrChecker.AvailableCidr
      EnableDnsHostnames: true
      EnableDnsSupport: true

3. 既存 VPC の CIDR を確認

既存 VPC の CIDR は以下の通りです。

  • 10.0.0.0/16
  • 172.31.0.0/16

4. デプロイ

手順 2 のテンプレートをデプロイします。
デプロイ完了後、既存 VPC の CIDR とは別の CIDR で VPC が作成されていれば OK です。

Lambda の実行ログには以下のように CIDR チェックを実行した記録が残っています。

Existing VPC CIDRs: ['10.0.0.0/16', '172.31.0.0/16']
Available CIDR candidates: ['10.0.0.0/16', '10.1.0.0/16', '10.2.0.0/16', '10.3.0.0/16', '10.4.0.0/16']
10.0.0.0/16 overlaps with 10.0.0.0/16
Found available CIDR: 10.1.0.0/16

まとめ

今回は CloudFormation での VPC 作成時に CIDR が一意になるようにデプロイしてみました。
どなたかの参考になれば幸いです。

Discussion