2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VPCエンドポイントの集約について改めてまとめる

2
Last updated at Posted at 2026-03-23

VPCエンドポイントの集約について改めてまとめる

皆様こんにちは!SREチームの佐藤です!

弊社では、今まで分散管理していたAWSアカウントを集約管理に移行しており、アカウント集約が完了したからにはコスト最適な形を目指したいということで、今回はVPCエンドポイントの集約を検討してみたいと思います!
少し前にRoute53 Profilesを使ったVPCエンドポイントの集約方法が紹介されていましたが、キャッチアップできていなかったので、使ってみたいと思います!

そこそこコストがかかるリソースを作りますので、試してみたいという方はご留意ください。
私はre:Inventのクレジットがあったので、ブログで還元します(笑)

VPCエンドポイントとは?

VPCエンドポイントは、VPC内のリソースからAWSサービスのAPIを実行したい場合に、パブリックインターネットを経由せずにサービスエンドポイントにアクセスするために利用するリソースです。
以下の図の左側のようにインターネットへの経路がないVPC内のEC2インスタンスが、S3のAPIを実行しようとすると、ResolverからパブリックのIPアドレスを返されてしまい、出口がないためアクセスできません。
そこで、右側のようにVPCエンドポイントを作成してあげると、VPCのResolverにサービスエンドポイントへのレコードが登録され、VPCエンドポイント経由でサービスエンドポイントにアクセスできるようになります。
(よくわからないよーという方は、後ほど挙動を確認しているのでいったん読み進めてみてください。)
あれ?結局VPCの外に出てない?と思ったそこのあなた!VPCエンドポイント自体はVPC内のリソースですが、そこからサービスエンドポイントまでの通信はAWSの専有ネットワークを通じて行われます。

test-Page-2.drawio.png

AWSの専有ネットワークというのはAWSリージョン間、AZ間の通信、AWSが所有するWANだと思っていただければと思います。
ネットワークとセキュリティが絡むAWSの話をする際、混同しないように通常のインターネットのことをパブリックインターネットと呼ぶこともよくあります。
そりゃインターネットなんだからパブリックだろと思っていた方はそういう意図だったのかと覚えておきましょう!

単一アカウントでのVPCエンドポイントの挙動

さて、実際の挙動を少し覗いてみましょう。
以下の構成を作成するCloudFormationテンプレートを用意しました。(パラメーターはこだわりなければデフォルトで問題ないです)

test-Page-1.drawio.png

step1.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: >
  VPC System - Private VPC with App/Endpoint/TGW subnets across 2 AZs,
  SSM VPC Endpoints, and an EC2 instance.

# ------------------------------------------------------------
# Parameters
# ------------------------------------------------------------
Parameters:
  SystemName:
    Type: String
    Default: system
    Description: System name used for resource naming.

  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

# ------------------------------------------------------------
# Resources
# ------------------------------------------------------------
Resources:
  # System VPC 1
  # ========================
  # VPC
  # ========================
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub vpc-${SystemName}

  # ========================
  # Subnets - App
  # ========================
  PriAppApne1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.0/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-app-apne1a

  PriAppApne1c:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.16/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-app-apne1c

  # ========================
  # Subnets - Endpoint
  # ========================
  PriEpApne1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.32/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-ep-apne1a

  PriEpApne1c:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.48/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-ep-apne1c

  # ========================
  # Subnets - TGW
  # ========================
  PriTgwApne1a:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.64/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-tgw-apne1a

  PriTgwApne1c:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      CidrBlock: 10.0.0.80/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: pri-tgw-apne1c

  # ========================
  # Route Tables
  # ========================
  RouteTableApp:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-pri-app-${SystemName}

  RouteTableEp:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-pri-ep-${SystemName}

  RouteTableTgw:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub rtb-pri-tgw-${SystemName}

  # --- Route Table Associations ---
  RtAssocAppA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1a
      RouteTableId: !Ref RouteTableApp

  RtAssocAppC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1c
      RouteTableId: !Ref RouteTableApp

  RtAssocEpA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1a
      RouteTableId: !Ref RouteTableEp

  RtAssocEpC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1c
      RouteTableId: !Ref RouteTableEp

  RtAssocTgwA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1a
      RouteTableId: !Ref RouteTableTgw

  RtAssocTgwC:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1c
      RouteTableId: !Ref RouteTableTgw

  # ========================
  # Security Groups
  # ========================
  SgEc2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub sg-ec2-${SystemName}

  SgVpcEndpoint:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
      VpcId: !Ref VPC
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 10.0.0.0/24
      Tags:
        - Key: Name
          Value: !Sub sg-vpce-${SystemName}

  # ========================
  # VPC Endpoints (SSM)
  # ========================
  VpceSSM:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1a
        - !Ref PriEpApne1c
      SecurityGroupIds:
        - !Ref SgVpcEndpoint

  VpceSSMMessages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1a
        - !Ref PriEpApne1c
      SecurityGroupIds:
        - !Ref SgVpcEndpoint

  VpceEC2Messages:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VPC
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1a
        - !Ref PriEpApne1c
      SecurityGroupIds:
        - !Ref SgVpcEndpoint

  # ========================
  # IAM Role for EC2 (SSM)
  # ========================
  Ec2SsmRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub role-ec2-ssm-${SystemName}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref Ec2SsmRole

  # ========================
  # EC2 Instance
  # ========================
  Ec2Instance:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: !Ref LatestAmiId
      SubnetId: !Ref PriAppApne1a
      IamInstanceProfile: !Ref Ec2InstanceProfile
      SecurityGroupIds:
        - !Ref SgEc2
      Tags:
        - Key: Name
          Value: !Sub ec2-${SystemName}

# ------------------------------------------------------------
# Outputs
# ------------------------------------------------------------
Outputs:
  VpcId:
    Description: VPC ID
    Value: !Ref VPC

  Ec2InstanceId:
    Description: EC2 Instance ID
    Value: !Ref Ec2Instance

  VpceSSMId:
    Description: SSM VPC Endpoint ID
    Value: !Ref VpceSSM

  VpceSSMMessagesId:
    Description: SSM Messages VPC Endpoint ID
    Value: !Ref VpceSSMMessages

  VpceEC2MessagesId:
    Description: EC2 Messages VPC Endpoint ID
    Value: !Ref VpceEC2Messages

スタックの作成ができたら、EC2インスタンスにセッションマネージャーでアクセスします。

全然関係ないですが、昔よりセッションマネージャーに接続できるようになるの早くなりましたよね

ssmのエンドポイントへの名前解決を確認してみましょう!

sh-5.2$ nslookup ssm.ap-northeast-1.amazonaws.com
Server:         10.0.0.2 ←.2はVPCのResolverのIPアドレスです
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   ssm.ap-northeast-1.amazonaws.com
Address: 10.0.0.38 ←作成したVPCエンドポイントのIPアドレスです
Name:   ssm.ap-northeast-1.amazonaws.com
Address: 10.0.0.54 ←作成したVPCエンドポイントのIPアドレスです

次に作成していないサービスのエンドポイントに名前解決をしてみます。

sh-5.2$ nslookup s3.ap-northeast-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.155.255 ←パブリックのS3サービスエンドポイントのIPアドレスです
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.157.45
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.157.226
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.127
Name:   s3.ap-northeast-1.amazonaws.com
Address: 52.219.199.8
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.118
Name:   s3.ap-northeast-1.amazonaws.com
Address: 3.5.159.85

VPCエンドポイントを作成したサービスはVPCエンドポイントのIPアドレスが返され、作成していないサービスはパブリックのIPアドレスが返されていることがわかりますね!
インターネットへの口がないため、s3へのリクエストをしても何も返ってきません。

VPCエンドポイントの集約

さて、ここからが本題です。
VPCエンドポイントは、安くはないサービスです。

東京リージョンでは各AZのVPCエンドポイント1つあたりの料金が $0.014/h です。
これだけだと高いかどうかわからないですよね(笑)
よく利用するVPCエンドポイントを2AZに展開した場合の課金額を試算しましょう。

以下のサービスを利用するとします。

  • ssm
  • ssm-messages
  • ec2-messages
  • logs
  • metrics

この場合、月額は以下のようになります。

0.014 * 24 * 30 * 10 = 100.8

大体$100くらいかかりますね。
え?そんなに高くないって?
これ1アカウントですからね、10アカウントでこれを利用すればそれだけで$1000です。
ちょっとしたシステム1つ分です。

この料金を削減するために、VPCエンドポイントを集約しましょうという話が出てくるわけです!

ほんとに安いの?

勘のいい人は、集約したら集約したで新たにかかる費用があるんじゃない?と思われるかと思います。
主な要素はTransit Gatewayの通信料金かと思います。
通信量を変数にして集約する場合としない場合の損益分岐点を求めてみましょう。

条件は以下とします。

項目 単位
月間時間 730 時間
AZ数(VPCE配置) 2 AZ
アカウント数(集約対象) 5 アカウント
エンドポイントの種類 5 種類

分散構成と集約構成の月額コストの比較は以下の通りです。

image.png

月間通信量 (GB, 全EP合計) 分散構成 月額 (USD) 集約構成 月額 (USD) 差額 (分散-集約) (USD) 判定
0 $511.00 $102.20 $408.80 集約有利
50 $511.50 $104.70 $406.80 集約有利
100 $512.00 $107.20 $404.80 集約有利
200 $513.00 $112.20 $400.80 集約有利
500 $516.00 $127.20 $388.80 集約有利
1,000 $521.00 $152.20 $368.80 集約有利
1,500 $526.00 $177.20 $348.80 集約有利
2,000 $531.00 $202.20 $328.80 集約有利
2,500 $536.00 $227.20 $308.80 集約有利
3,000 $541.00 $252.20 $288.80 集約有利
4,000 $551.00 $302.20 $248.80 集約有利
5,000 $561.00 $352.20 $208.80 集約有利
7,500 $586.00 $477.20 $108.80 集約有利
10,000 $611.00 $602.20 $8.80 集約有利
15,000 $661.00 $852.20 ($191.20) 分散有利
20,000 $711.00 $1,102.20 ($391.20) 分散有利
50,000 $1,011.00 $2,602.20 ($1,591.20) 分散有利

この条件の場合、合計の通信量が10TBを超える場合は分散の方がよさそうです。
logsなどの通信量が多いサービスを検討するときには気を付けたいですね。

もちろんアカウント数やAZ数、種類が増えれば損益分岐点の通信量も増加します。

エンドポイント集約前準備

さっそくエンドポイントの集約を試していきたいですが、今回は以下のような構成を想定して分散構成から集約構成への移行を検討するので、step1.yamlで作成したCloudFormationスタックを以下のテンプレートで更新してください。

構成は以下の図のようになっています。
少しボリュームがありますが、システム用VPCを2つ、集約用VPCを1つ用意してそれらをTGWで疎通させています。
今回は全疎通の設定にしています。

image.png

ややこしいのでセッションマネージャーのために必要なVPCエンドポイントは表記してないですが、すべてのVPCに作成されています。

step2.yaml

AWSTemplateFormatVersion: "2010-09-09"
Description: >
  VPC System - Private VPC with App/Endpoint/TGW subnets across 2 AZs,
  SSM VPC Endpoints, and an EC2 instance.

# ------------------------------------------------------------
# Parameters
# ------------------------------------------------------------
Parameters:

  LatestAmiId:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-default-x86_64

# ------------------------------------------------------------
# Resources
# ------------------------------------------------------------
Resources:
  # Shared Resources
  # ========================
  # IAM Role for EC2 (SSM)
  # ========================
  Ec2SsmRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub role-ec2-ssm
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: ec2.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore
        - arn:aws:iam::aws:policy/ReadOnlyAccess

  Ec2InstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - !Ref Ec2SsmRole

  Tgw:
    Type: AWS::EC2::TransitGateway
    Properties:
      DefaultRouteTableAssociation: enable
      DefaultRouteTablePropagation: enable
      Tags:
        - Key: Name
          Value: tgw

  # System VPC 1
  # ========================
  # VPC
  # ========================
  VpcSystem1:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub vpc-system-1

  # ========================
  # Subnets - App
  # ========================
  PriAppApne1aSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.0/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-app-apne1a

  PriAppApne1cSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.16/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-app-apne1c

  # ========================
  # Subnets - Endpoint
  # ========================
  PriEpApne1aSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.32/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-ep-apne1a

  PriEpApne1cSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.48/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-ep-apne1c

  # ========================
  # Subnets - TGW
  # ========================
  PriTgwApne1aSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.64/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-tgw-apne1a

  PriTgwApne1cSystem1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem1
      CidrBlock: 10.0.0.80/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys1-pri-tgw-apne1c
  # ========================
  # TGW Attachment
  # ========================
  TgwAttachmentSystem1:
    Type: AWS::EC2::TransitGatewayAttachment
    Properties:
      TransitGatewayId: !Ref Tgw
      VpcId: !Ref VpcSystem1
      SubnetIds:
        - !Ref PriTgwApne1aSystem1
        - !Ref PriTgwApne1cSystem1

  # ========================
  # Route Tables
  # ========================
  RouteTableAppSystem1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem1
      Tags:
        - Key: Name
          Value: !Sub sys1-rtb-pri-app

  RouteTableEpSystem1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem1
      Tags:
        - Key: Name
          Value: !Sub sys1-rtb-pri-ep

  RouteTableTgwSystem1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem1
      Tags:
        - Key: Name
          Value: !Sub sys1-rtb-pri-tgw

  # --- Route ---
  RouteToTgwSystem1:
    DependsOn: TgwAttachmentSystem1
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableAppSystem1
      DestinationCidrBlock: 0.0.0.0/0
      TransitGatewayId: !Ref Tgw

  # --- Route Table Associations ---
  RtAssocAppASystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1aSystem1
      RouteTableId: !Ref RouteTableAppSystem1

  RtAssocAppCSystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1cSystem1
      RouteTableId: !Ref RouteTableAppSystem1

  RtAssocEpASystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1aSystem1
      RouteTableId: !Ref RouteTableEpSystem1

  RtAssocEpCSystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1cSystem1
      RouteTableId: !Ref RouteTableEpSystem1

  RtAssocTgwASystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1aSystem1
      RouteTableId: !Ref RouteTableTgwSystem1

  RtAssocTgwCSystem1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1cSystem1
      RouteTableId: !Ref RouteTableTgwSystem1

  # ========================
  # Security Groups
  # ========================
  SgEc2System1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VpcSystem1
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub sg-ec2-system-1

  SgVpcEndpointSystem1:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
      VpcId: !Ref VpcSystem1
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 10.0.0.0/24
      Tags:
        - Key: Name
          Value: !Sub sg-vpce-system-1

  # ========================
  # VPC Endpoints (SSM)
  # ========================
  VpceSSMSystem1:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem1
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem1
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem1

  VpceSSMMessagesSystem1:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem1
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem1
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem1

  VpceEC2MessagesSystem1:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem1
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem1
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem1

  # ========================
  # EC2 Instance
  # ========================
  Ec2InstanceSystem1:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: !Ref LatestAmiId
      SubnetId: !Ref PriAppApne1aSystem1
      IamInstanceProfile: !Ref Ec2InstanceProfile
      SecurityGroupIds:
        - !Ref SgEc2System1
      Tags:
        - Key: Name
          Value: !Sub ec2-system-1

  # System VPC 2
  # ========================
  # VPC
  # ========================
  VpcSystem2:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.1.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub vpc-system-2

  # ========================
  # Subnets - App
  # ========================
  PriAppApne1aSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.0/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-app-apne1a

  PriAppApne1cSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.16/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-app-apne1c

  # ========================
  # Subnets - Endpoint
  # ========================
  PriEpApne1aSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.32/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-ep-apne1a

  PriEpApne1cSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.48/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-ep-apne1c

  # ========================
  # Subnets - TGW
  # ========================
  PriTgwApne1aSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.64/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-tgw-apne1a

  PriTgwApne1cSystem2:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcSystem2
      CidrBlock: 10.1.0.80/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: sys2-pri-tgw-apne1c
  # ========================
  # TGW Attachment
  # ========================
  TgwAttachmentSystem2:
    Type: AWS::EC2::TransitGatewayAttachment
    Properties:
      TransitGatewayId: !Ref Tgw
      VpcId: !Ref VpcSystem2
      SubnetIds:
        - !Ref PriTgwApne1aSystem2
        - !Ref PriTgwApne1cSystem2

  # ========================
  # Route Tables
  # ========================
  RouteTableAppSystem2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem2
      Tags:
        - Key: Name
          Value: !Sub sys2-rtb-pri-app

  RouteTableEpSystem2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem2
      Tags:
        - Key: Name
          Value: !Sub sys2-rtb-pri-ep

  RouteTableTgwSystem2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcSystem2
      Tags:
        - Key: Name
          Value: !Sub sys2-rtb-pri-tgw

  # --- Route ---
  RouteToTgwSystem2:
    DependsOn: TgwAttachmentSystem2
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableAppSystem2
      DestinationCidrBlock: 0.0.0.0/0
      TransitGatewayId: !Ref Tgw

  # --- Route Table Associations ---
  RtAssocAppASystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1aSystem2
      RouteTableId: !Ref RouteTableAppSystem2

  RtAssocAppCSystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriAppApne1cSystem2
      RouteTableId: !Ref RouteTableAppSystem2

  RtAssocEpASystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1aSystem2
      RouteTableId: !Ref RouteTableEpSystem2

  RtAssocEpCSystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1cSystem2
      RouteTableId: !Ref RouteTableEpSystem2

  RtAssocTgwASystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1aSystem2
      RouteTableId: !Ref RouteTableTgwSystem2

  RtAssocTgwCSystem2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1cSystem2
      RouteTableId: !Ref RouteTableTgwSystem2

  # ========================
  # Security Groups
  # ========================
  SgEc2System2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for EC2 instance
      VpcId: !Ref VpcSystem2
      SecurityGroupIngress:
        - IpProtocol: -1
          CidrIp: 0.0.0.0/0
      Tags:
        - Key: Name
          Value: !Sub sg-ec2-system-2

  SgVpcEndpointSystem2:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints (allow HTTPS from VPC)
      VpcId: !Ref VpcSystem2
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 10.1.0.0/24
      Tags:
        - Key: Name
          Value: !Sub sg-vpce-system-2

  # ========================
  # VPC Endpoints (SSM)
  # ========================
  VpceSSMSystem2:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem2
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem2
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem2

  VpceSSMMessagesSystem2:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem2
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem2
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem2

  VpceEC2MessagesSystem2:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcSystem2
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aSystem2
      SecurityGroupIds:
        - !Ref SgVpcEndpointSystem2

  # ========================
  # EC2 Instance
  # ========================
  Ec2InstanceSystem2:
    Type: AWS::EC2::Instance
    Properties:
      InstanceType: t3.micro
      ImageId: !Ref LatestAmiId
      SubnetId: !Ref PriAppApne1aSystem2
      IamInstanceProfile: !Ref Ec2InstanceProfile
      SecurityGroupIds:
        - !Ref SgEc2System2
      Tags:
        - Key: Name
          Value: !Sub ec2-system-2

  # Common VPC
  # ========================
  # VPC
  # ========================
  VpcCommon:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.2.0.0/24
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Sub vpc-common

  # ========================
  # Subnets - Endpoint
  # ========================
  PriEpApne1aCommon:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcCommon
      CidrBlock: 10.2.0.0/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name 
          Value: common-pri-ep-apne1a

  PriEpApne1cCommon:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcCommon
      CidrBlock: 10.2.0.16/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: common-pri-ep-apne1c

  # ========================
  # Subnets - TGW
  # ========================
  PriTgwApne1aCommon:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcCommon
      CidrBlock: 10.2.0.32/28
      AvailabilityZone: ap-northeast-1a
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: common-pri-tgw-apne1a

  PriTgwApne1cCommon:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VpcCommon
      CidrBlock: 10.2.0.48/28
      AvailabilityZone: ap-northeast-1c
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: common-pri-tgw-apne1c

  # ========================
  # TGW Attachment
  # ========================
  TgwAttachmentCommon:
    Type: AWS::EC2::TransitGatewayAttachment
    Properties:
      TransitGatewayId: !Ref Tgw
      VpcId: !Ref VpcCommon
      SubnetIds:
        - !Ref PriTgwApne1aCommon
        - !Ref PriTgwApne1cCommon

  # ========================
  # Route Tables
  # ========================
  RouteTableEpCommon:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcCommon
      Tags:
        - Key: Name
          Value: !Sub common-rtb-pri-ep

  RouteTableTgwCommon:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VpcCommon
      Tags:
        - Key: Name
          Value: !Sub common-rtb-pri-tgw

  # --- Route ---
  RouteToTgwCommon:
    DependsOn: TgwAttachmentCommon
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref RouteTableEpCommon
      DestinationCidrBlock: 0.0.0.0/0
      TransitGatewayId: !Ref Tgw

  # --- Route Table Associations ---
  RtAssocEpACommon:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1aCommon
      RouteTableId: !Ref RouteTableEpCommon

  RtAssocEpCCommon:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriEpApne1cCommon
      RouteTableId: !Ref RouteTableEpCommon

  RtAssocTgwACommon:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1aCommon
      RouteTableId: !Ref RouteTableTgwCommon

  RtAssocTgwCCommon:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PriTgwApne1cCommon
      RouteTableId: !Ref RouteTableTgwCommon

  # ========================
  # Security Groups
  # ========================
  SgVpcEndpointCommon:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Security group for VPC Endpoints (allow HTTPS from all VPC)
      VpcId: !Ref VpcCommon
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 10.0.0.0/8
        - IpProtocol: tcp
          FromPort: 443
          ToPort: 443
          CidrIp: 172.0.0.0/24
      Tags:
        - Key: Name
          Value: !Sub sg-vpce-common

  # ========================
  # VPC Endpoints (SSM)
  # ========================
  VpceSSMCommon:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcCommon
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssm
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aCommon
      SecurityGroupIds:
        - !Ref SgVpcEndpointCommon

  VpceSSMMessagesCommon:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcCommon
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ssmmessages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aCommon
      SecurityGroupIds:
        - !Ref SgVpcEndpointCommon

  VpceEC2MessagesCommon:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      VpcId: !Ref VpcCommon
      ServiceName: !Sub com.amazonaws.${AWS::Region}.ec2messages
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      SubnetIds:
        - !Ref PriEpApne1aCommon
      SecurityGroupIds:
        - !Ref SgVpcEndpointCommon

以下のような分散構成から、集約構成への変更をやってみます。

【分散構成】
image.png

【集約構成】
image.png

まずは分散構成を作成しますが、その前に今はec2のAPIが利用できないことを確認しましょう。
ec2-system-1か2にセッションマネージャーで接続し、以下を実行してみます。

aws ec2 describe-vpcs

とくに何も出力が出ないかと思います。
冒頭に確認した挙動と同じですね。

それでは、sys1とsys2のVPCにec2のVPCエンドポイントを作成しましょう。
マネジメントコンソールのVPCからエンドポイントを開き、エンドポイントを作成をクリックします。

image.png

以下の設定値で作成します。(記載のないものはデフォルト)

項目
名前タグ ec2-ep-sys1
タイプ AWSのサービス
サービス com.amazonaws.ap-northeast-1.ec2
VPC vpc-system-1
プライベートDNS名を有効化
サブネット sys1-pri-ep-apne1a, sys1-pri-ep-apne1c
セキュリティグループ ${StackName}-SgVpcEndpointSystem1-xxxxxx

【プライベートDNS名の有効化について】
プライベートDNS名を有効化オプションをオンにすると、パブリックDNS名が自動的にVPCエンドポイントのプライベートIPアドレスに自動的に解決されるようになります。
これだけだとわかりにくいですね、逆にオフにしてしまうとどうなるのかというと、VPCエンドポイントのエンドポイント固有のDNS名を指定しないと、エンドポイントに名前解決ができません。

<オンの場合>
以下のように、パブリックDNS名に対して名前解決をするとVPCエンドポイントのIPアドレスが返ってきています。

sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.36
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.61

したがって、オンの場合は、通常のAPI実行と変わらないコマンドあるいはプログラミングの書き方で実行ができます。

aws ec2 describe-vpcs

<オフの場合>
オフにした場合は、以下のようにエンドポイント固有のDNS名を指定して名前解決をする必要があります。
以下のようにパブリックDNS名に対して名前解決をするとパブリックIPアドレスが返ってきます。

sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 52.195.201.182

したがって、エンドポイントを指定せずにAPIを叩くとサービスエンドポイントに到達できずに失敗してしまいます。
以下のようにエンドポイントURLを指定する必要があります。

aws ec2 describe-vpcs --endpoint-url https://vpce-0ec1580e6b38d1c17-51uap8e7.ec2.ap-northeast-1.vpce.amazonaws.com

同様の手順で、sys2のVPCにも作成しましょう。

項目
名前タグ ec2-ep-sys2
タイプ AWSのサービス
サービス com.amazonaws.ap-northeast-1.ec2
VPC vpc-system-2
プライベートDNS名を有効化
サブネット sys2-pri-ep-apne1a, sys2-pri-ep-apne1c
セキュリティグループ ${StackName}-SgVpcEndpointSystem2-xxxxxx

これで、sys1とsys2のVPCからはAPIが利用できるようになりました。

エンドポイントの集約

いくつかやり方があるかと思いますが、Route53 Private Hosted Zoneを使う方法が最も一般的かと思います。

今回はRoute53 Profilesを使ってみたいので、以下のブログを参考に構築を進めます。

まずは、先ほどと同様の手順でVPCエンドポイントを作成します。
今回はプライベートDNS名を無効にします。(※プライベートホストゾーンでパブリックDNS名に対するレコードを登録するため)

項目
名前タグ ec2-ep-common
タイプ AWSのサービス
サービス com.amazonaws.ap-northeast-1.ec2
VPC vpc-common
プライベートDNS名を有効化
サブネット common-pri-ep-apne1a, common-pri-ep-apne1c
セキュリティグループ ${StackName}-SgVpcEndpointCommon-xxxxxx

Route53 > ホストゾーン > ホストゾーンの作成をクリックします。

image.png

以下の設定値で作成します。

項目
ドメイン名 ec2.ap-northeast-1.amazonaws.com
タイプ プライベートホストゾーン
VPC vpc-common

image.png

ホストゾーンが作成できたら、VPCエンドポイントへのレコードを登録します。

image.png

以下の設定値で作成します。

項目
エイリアス オン
トラフィックのルーティング先 VPCエンドポイントへのエイリアス、東京、ec2-ep-commonのプライベートDNS名

image.png

今まではここまでやったらあとは各VPCをプライベートホストゾーンに関連付ければ作業完了でした。
ただ、VPCエンドポイント×アカウント数分やる必要があり、クロスアカウントの場合、CLIでの実行が必須で、承認のCLIも打たないといけないというかなり面倒な作業です。
Route53 Profilesを使うことでこの辺りが簡単になるようなので早速試してみましょう!
以下の手順でプロファイルを作成します。

Route53の画面から、プロファイルを作成します。

image.png

以下の設定値で作成します。

項目
プロファイル名 common-profile

20260321011235.png

プロファイルの作成ができたらVPCを関連付けましょう。

image.png
image.png

次にプライベートホストゾーンを関連付けます。

image.png
image.png

これで、sys1とsys2のVPCからもAPIが利用できるようになりました。
早速試してみましょう!

sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.36
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.0.0.61

sys1側にVPCエンドポイントがある場合はこちらが優先されるようですね!
ではVPCエンドポイントを削除して、再度試してみます。

sh-5.2$ nslookup ec2.ap-northeast-1.amazonaws.com
Server:         10.0.0.2
Address:        10.0.0.2#53

Non-authoritative answer:
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.2.0.29
Name:   ec2.ap-northeast-1.amazonaws.com
Address: 10.2.0.10

vpc-commonのIPアドレスが返ってきてますね!

切り替え時に、CLIを繰り返し実行していましたが、1回だけ実行が失敗しました。
瞬断はあるようです。(1秒未満)

クロスアカウントの場合は、Route53 ProfileをAWS RAMを使用して、共有し、各アカウント側でProfileにVPCをAssociateするといった手順になります。
Control Towerなど導入されている場合はVPCを作成するタイミングで自動化されるように組んでおきたいですね。

エンドポイントの直接関連付け

記事の手順に沿って実施してみましたが、Route53 ProfilesのタブにVPC endpointsがあったため、同じような手順で関連付けを試したところ、プライベートホストゾーンを作成しなくても解決が可能でした!
実際に利用するならこちらかもしれませんね!

image.png

レイテンシに懸念は?

TGWを経由する分、レイテンシに影響が出るのでは?と懸念される方もいらっしゃるかと思います。
分散型と集約型でdescribe-vpcsを100回実行した結果を比較してみます。

項目 分散型 集約型
平均 935.77ms 941.85ms
中央値 926.5ms 929.0ms
最小 900ms 893ms
最大 1524ms 1833ms
p95 953ms 952ms

ほとんど同じレイテンシですので、レイテンシについては問題なさそうです。

Route53 Profilesの費用は?

VPCのアソシエーション数次第ですが、基本的には月額$540くらいと考えておけばよさそうです。

移行時の確認点

  • エンドポイントのDNS名を指定している箇所がないか
  • 瞬断を許容できるか

まとめ

今回はVPCエンドポイントの集約方法について改めてまとめてみました!
Route53 Profiles非常に便利そうですね!ただし、コストがかなり高めな印象です。
一律同じように名前解決できるようにするような環境の場合は、コストに対してのメリットが少ないかもしれません。
以下のように、いくつかのプロファイルにVPCをグループ分けできる場合は管理面のオーバーヘッドまで考えてコストメリットがありそうです!

【Profile1】
ssm
ssmmessages
ec2messages

【Profile2】
自社ドメインのPHZ

【Profile3】
自社ドメインのPHZ
ssm
ssmmessages
ec2messages
logs
ecr.api

瞬断が発生するのがネックになりそうですが、それを乗り越えてでもコストメリットがある場合も多いと思いますので是非皆さんも検討してみてはいかがでしょうか?

それでは、よいAWSライフを!

弊社では一緒に働く仲間を募集中です!

現在、様々な職種を募集しております。
カジュアル面談も可能ですので、ご連絡お待ちしております!

募集内容等詳細は、是非採用サイトをご確認ください。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?