CloudFormationで、EC2に別VPCのENIをアタッチ

# はじめに
先日、EC2のアップデートで、別のVPCのENIをアタッチできるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2023/10/multi-vpc-eni-attachments/

今回はこれをCloudFormationでやってみました。

# 概要
`AWS::EC2::NetworkInterfaceAttachment`を使います。

```yaml
  EniAttach:
    Type: AWS::EC2::NetworkInterfaceAttachment
    Properties:
      DeviceIndex: 1
      InstanceId: アタッチされたいEC2
      NetworkInterfaceId: アタッチするENI（別VPC)
```

また別VPC側のENI経由でもEIPをアタッチすることで、セッションマネージャーで接続できたり、HTTPアクセスできました。

# 参考

https://dev.classmethod.jp/articles/attatch-ec2-to-multiple-vpc/

# やってみた

以下のものを作ります。

- 接続元VPC
    - パブリックサブネット
    - EC2
        - セッションマネージャーが使えるロールをアタッチ
- 接続先VPC
    - パブリックサブネット
    - ENI
- EC2にENIをアタッチ

以下がCloudFormationテンプレートです。

<details><summary>クリックで表示</summary><div>

パラメータは以下です。

- 接続元VPC名と、そのCIDR
- 接続先VPC名と、そのCIDR
- EC2のイメージID
- インスタンスタイプ
- 作るAZ
    - EC2とENIは同じAZである必要があります

一部確認用に、接続元VPCにENIを作った個所をコメントにしています。

```yaml
AWSTemplateFormatVersion: '2010-09-09'

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: Parameters
        Parameters:
          - VpcNameSource
          - VpcCIDRSource
          - VpcNameTarget
          - VpcCIDRTarget
          - ImageId
          - InstanceTypeName
          - AvailabilityZone

Parameters:

  VpcNameSource:
    Type: String
    Default: sourceVpc
    Description: Name of the VPC
  VpcCIDRSource:
    Type: String
    Default: 10.70.0.0/16
    Description: CIDR block for the VPC

  VpcNameTarget:
    Type: String
    Default: targetVpc
    Description: Name of the VPC
  VpcCIDRTarget:
    Type: String
    Default: 10.80.0.0/16
    Description: CIDR block for the VPC

  ImageId:
    Type: String
    Default: ami-098940df4d3292e9a
  InstanceTypeName:
    Type: String
    Default: t3.micro
  AvailabilityZone:
    Type: AWS::EC2::AvailabilityZone::Name

Resources:
  ##################################################
  # Source VPC
  ##################################################
  VPCSource:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VpcCIDRSource
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VpcNameSource

  InternetGatewaySource:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-igw

  VPCGatewayAttachmentSource:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref VPCSource
      InternetGatewayId: !Ref InternetGatewaySource

  PublicSubnetSource:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCSource
      AvailabilityZone: !Ref AvailabilityZone
      CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCIDRSource, 24, 8 ] ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-subnet

  PublicRouteTableSource:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPCSource
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameSource}-rtb-public
  PublicRouteSource:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachmentSource
    Properties:
      RouteTableId: !Ref PublicRouteTableSource
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGatewaySource

  SubnetRouteTableAssociationSource:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PublicSubnetSource
      RouteTableId: !Ref PublicRouteTableSource

  SecurityGroupForSource:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPCSource
      GroupDescription: "Source VPC SG"

  # EniSource:
  #   Type: AWS::EC2::NetworkInterface
  #   Properties:
  #     Tags:
  #       - Key: Name
  #         Value: SourceEni
  #     GroupSet:
  #       - !GetAtt SecurityGroupForSource.GroupId
  #     SubnetId: !Ref PublicSubnetSource

  # EC2
  SessionManagerRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: 'Allow'
            Principal:
              Service:
                - 'ec2.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      Path: '/'
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
  InstanceProfile:
    DependsOn: SessionManagerRole
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: '/'
      Roles:
        - !Ref SessionManagerRole
  InstanceOnSource:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: !Ref ImageId
      InstanceType: !Ref InstanceTypeName
      IamInstanceProfile: !Ref InstanceProfile
      SubnetId: !Ref PublicSubnetSource
      SecurityGroupIds:
        - !GetAtt SecurityGroupForSource.GroupId
      Tags:
        - Key: Name
          Value: sourceInstance
          
  ##################################################
  # Target VPC
  ##################################################
  VPCTarget:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: !Ref VpcCIDRTarget
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VpcNameTarget

  InternetGatewayTarget:
    Type: 'AWS::EC2::InternetGateway'
    Properties:
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-igw

  VPCGatewayAttachmentTarget:
    Type: 'AWS::EC2::VPCGatewayAttachment'
    Properties:
      VpcId: !Ref VPCTarget
      InternetGatewayId: !Ref InternetGatewayTarget

  PublicSubnetTarget:
    Type: 'AWS::EC2::Subnet'
    Properties:
      VpcId: !Ref VPCTarget
      AvailabilityZone: !Ref AvailabilityZone
      CidrBlock: !Select [ 0, !Cidr [ !Ref VpcCIDRTarget, 24, 8 ] ]
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-subnet

  PublicRouteTableTarget:
    Type: 'AWS::EC2::RouteTable'
    Properties:
      VpcId: !Ref VPCTarget
      Tags:
        - Key: Name
          Value: !Sub ${VpcNameTarget}-rtb-public
  PublicRouteTarget:
    Type: 'AWS::EC2::Route'
    DependsOn: VPCGatewayAttachmentTarget
    Properties:
      RouteTableId: !Ref PublicRouteTableTarget
      DestinationCidrBlock: '0.0.0.0/0'
      GatewayId: !Ref InternetGatewayTarget

  SubnetRouteTableAssociationTarget:
    Type: 'AWS::EC2::SubnetRouteTableAssociation'
    Properties:
      SubnetId: !Ref PublicSubnetTarget
      RouteTableId: !Ref PublicRouteTableTarget

  SecurityGroupForTarget:
    Type: AWS::EC2::SecurityGroup
    Properties:
      VpcId: !Ref VPCTarget
      GroupDescription: "Target VPC SG"

  EniTarget:
    Type: AWS::EC2::NetworkInterface
    Properties:
      Tags:
        - Key: Name
          Value: TargetEni
      GroupSet:
        - !GetAtt SecurityGroupForTarget.GroupId
      SubnetId: !Ref PublicSubnetTarget

  EniAttach:
    Type: AWS::EC2::NetworkInterfaceAttachment
    Properties:
      DeviceIndex: 1
      InstanceId: !Ref InstanceOnSource
      # NetworkInterfaceId: !Ref EniSource
      NetworkInterfaceId: !Ref EniTarget
```
</div></details>

実行して、作られたEC2のネットワークを確認すると、以下のように複数のVPCにアタッチされていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/ee6c0b11-e9a0-65e5-1291-6feb09757f90.png)

## 別VPCのENIを使う


### セッションマネージャーを使う

このままセッションマネージャーで接続すると、接続元VPCのENI経由で接続になります。
接続先VPCのENIでもできるかやってみました。

以下の事をやります。

1. セッションマネージャーで接続します
1. EC2のルート情報を変更して、接続先VPCのENI側に向けます
    - ここで接続が切れます
1. 接続先VPCのENIにEIPをアタッチします

まずセッションマネージャーで接続して、ルート情報を見てみます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/a7c4d2b9-d811-ac87-9ff6-83411da7cd48.png)

ルート情報を変更します。面倒なのですべて接続先VPCのENIに向けます。
以下のコマンドを実行すると、ルート情報が変更されるため、コマンドの実行結果が返ってこなくなりますが、エラーになっていないので問題ないです。
```
sudo route add -net 0.0.0.0 netmask 0.0.0.0 metric 100 ens6
```

次にEIPを割り当てて、ENIに関連付けします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/64dec16d-e6eb-5af4-9a5d-9219f534b094.png)

再度、セッションマネージャーで接続します。ルート情報を見てみると、追加している情報を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/2ab2a50d-e701-d561-1dda-35e24e174a4f.png)

### HTTPでアクセス

上記に引き続き、httpdをインストールしてアクセスしてみます。

```bash
sudo yum -y update
sudo yum -y install httpd
sudo systemctl start httpd.service
sudo systemctl enable httpd.service
```

次にセキュリティグループにインバウンドルールを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/24ff51df-3723-aefd-3958-c940979be364.png)

EIPでHTTPアクセスして、画面が表示されることが確認できました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/91874231-9f9e-7707-5cea-8d173548930d.png)


# おわりに

今回はCloudFormationを使って、EC2に別VPCのENIをアタッチさせてみました。
巨大なLANを持っていて、WWWとつなげたい場合は中継サーバーを建ててNICを2つ使って両方に接続、という仕事をやっていた自分にとっては、大変刺さるアップデートでした。

この記事がどなたかのお役に立てれば幸いです。