この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは。AWS事業本部コンサルティング部に所属している今泉(@bun76235104)です。
みなさんGo言語好きですか?
AWS CDKではGo言語で構築可能ですが、検索するとTypeScriptの記事が多いですよね?
最近私はAWS CDKのGo言語を使ってシンプルにEC2を構築したりしています。
今回作成する構成は以下のようなシンプルな構成です。
ゴールは以下のようにHub VPCのEC2インスタンスからSpoke VPCのEC2インスタンスにpingで疎通確認をすることです。
なお、今回作成したコードは細かくファイルを分けており、すべてを紹介する訳ではありません。
全体を確認したい場合は以下のリポジトリをご確認ください。
記述したコード
重要そうなポイントを抜粋しつつ、コードを紹介します。
ディレクトリ構成は以下のようになっています。
.
├── README.md
├── cdk.json
├── cmd
│ ├── hub_spoke #Transit Gateway周りのリソース群を作成
│ │ ├── hub.go
│ │ ├── main.go
│ │ ├── route_establish .go
│ │ └── route_table.go
│ ├── network # VPCやルートテーブルなどのリソース群を作成
│ │ ├── route_table.go
│ │ └── vpc.go
│ └── server # EC2周りのリソース群を作成
│ └── main.go
├── go.mod
├── go.sum
├── transit_gw.go #今回のmainファイルに該当
└── transit_gw_test.go
VPC・EC2関連リソースの作成
VPC・VPCエンドポイントなどのリソースについて、構造体とメソッドを用意して2セット作成しています。
VPC関連リソースを作成するコード
以下のように構造体とメソッドを準備しています。
cmd/network/vpc.go
type Network struct {
scope constructs.Construct
vpcName string
cidr string
hasSSMEndpoint bool
}
func (nr Network) CreateNetworkResources() awsec2.Vpc {
// VPC
vpc := awsec2.NewVpc(nr.scope, &nr.vpcName, &awsec2.VpcProps{
IpAddresses: awsec2.IpAddresses_Cidr(jsii.String(nr.cidr)),
MaxAzs: jsii.Number(2),
EnableDnsSupport: jsii.Bool(true),
EnableDnsHostnames: jsii.Bool(true),
VpcName: jsii.String(nr.vpcName),
SubnetConfiguration: &[]*awsec2.SubnetConfiguration{
{
Name: jsii.String("TransitGateway"),
SubnetType: awsec2.SubnetType_PRIVATE_ISOLATED,
// Transit Gatewayのアタッチメントを配置するサブネットのためCidrMaskは小さくしている
CidrMask: jsii.Number(28),
},
{
Name: jsii.String("Private"),
SubnetType: awsec2.SubnetType_PRIVATE_ISOLATED,
CidrMask: jsii.Number(24),
},
},
})
// 指定した時のみVPCエンドポイントを追加
if nr.hasSSMEndpoint {
vpc.AddInterfaceEndpoint(jsii.String("SSM"), &awsec2.InterfaceVpcEndpointOptions{
Service: awsec2.InterfaceVpcEndpointAwsService_SSM(),
})
vpc.AddInterfaceEndpoint(jsii.String("SSMMessage"), &awsec2.InterfaceVpcEndpointOptions{
Service: awsec2.InterfaceVpcEndpointAwsService_SSM_MESSAGES(),
})
vpc.AddInterfaceEndpoint(jsii.String("EC2Messag"), &awsec2.InterfaceVpcEndpointOptions{
Service: awsec2.InterfaceVpcEndpointAwsService_EC2_MESSAGES(),
})
}
return vpc
}
これをmain処理から以下のように2セット作成しています。(HubVPCとSpokeVPC分)
transit_gw.go
// Hub(Shared) VPCに該当
sharedNetworkResource := network.NewNetwork(stack, "SharedVpc", "10.10.0.0/16", true)
sharedVpc := sharedNetworkResource.CreateNetworkResources()
severResource := server.NewServer(stack, "SharedVPCInstance", sharedVpc)
severResource.CreateServerResources()
// SpokeVPCに該当
workloadNetwork := network.NewNetwork(stack, "WorkloadVpc", "10.20.0.0/16", false)
workloadVpc := workloadNetwork.CreateNetworkResources()
workloadServer := server.NewServer(stack, "WorkloadVPCInstance", workloadVpc)
workloadServer.CreateServerResources()
次にEC2の作成部分は以下のようになっています。
EC2を作成
cmd/server/main.go
type Server struct {
scope constructs.Construct
name string
vpc awsec2.Vpc
}
func (sr Server) CreateServerResources() {
sg := awsec2.NewSecurityGroup(sr.scope, jsii.String(sr.name+"SG"), &awsec2.SecurityGroupProps{
AllowAllOutbound: jsii.Bool(true),
Vpc: sr.vpc,
})
// allow sg to inbound icmp
sg.AddIngressRule(awsec2.Peer_Ipv4(jsii.String("10.0.0.0/8")), awsec2.Port_AllIcmp(), jsii.String("allow icmp"), nil)
awsec2.NewInstance(sr.scope, jsii.String(sr.name), &awsec2.InstanceProps{
InstanceType: awsec2.InstanceType_Of(awsec2.InstanceClass_T3, awsec2.InstanceSize_MICRO),
MachineImage: awsec2.MachineImage_LatestAmazonLinux(&awsec2.AmazonLinuxImageProps{
Generation: awsec2.AmazonLinuxGeneration_AMAZON_LINUX_2,
}),
SsmSessionPermissions: jsii.Bool(true),
Vpc: sr.vpc,
SecurityGroup: sg,
VpcSubnets: &awsec2.SubnetSelection{
SubnetGroupName: jsii.String("Private"),
},
})
}
こちらもmain処理から各VPC内のサブネットに作成できるように呼び出します。
// HubVPCに配置するEC2
severResource := server.NewServer(stack, "SharedVPCInstance", sharedVpc)
severResource.CreateServerResources()
// SpokeVPC配置するEC2
workloadServer := server.NewServer(stack, "WorkloadVPCInstance", workloadVpc)
workloadServer.CreateServerResources()
Transit Gateway周りのリソース群を作成
ここから以下ブログを参考に順にリソースを作成します。
- Transit Gatewayを作成
- Transit Gatewayのアタッチメントを作成(各VPCの専用サブネットを指定)
- Transit Gatewayのルートテーブルを作成
- 2で作ったアタッチメントと3で作ったルートテーブルを紐付け(アソシエーション)
- アタッチメントしたVPCからルートテーブルに経路を伝播(プロパゲーション)
- Transit Gatewayのルートテーブルにルートを追加
1. Transit Gatwayを作成
以下のファイルでTransit Gatewayを作成するための構造体とメソッドを用意します。
cmd/hub_spoke/hub.go
type Hub struct {
scope constructs.Construct
}
func (h Hub) CreateTransitGateway() awsec2.CfnTransitGateway {
return awsec2.NewCfnTransitGateway(h.scope, jsii.String("TransitGateway"), &awsec2.CfnTransitGatewayProps{
DefaultRouteTableAssociation: jsii.String("disable"),
DefaultRouteTablePropagation: jsii.String("disable"),
})
}
以下のように呼び出します。
hub := NewHub(hp.scope)
// Transit Gatewayを作成
tgw := hub.CreateTransitGateway()
次にTransit Gatewayのアタッチメントを各VPCのTransit Gateway専用のサブネットに作成します。
2. Transit Gatewayのアタッチメントを作成(各VPCの専用サブネットを指定)
これまでと同様に構造体とメソッドを用意します。
cmd/hub_spoke/hub.go
type VpcAttachment struct {
name string
vpc awsec2.Vpc
tgw awsec2.CfnTransitGateway
subnetGroupName string
}
func (va VpcAttachment) Attach() awsec2.CfnTransitGatewayAttachment {
return awsec2.NewCfnTransitGatewayAttachment(va.vpc, jsii.String("VpcAttachment"), &awsec2.CfnTransitGatewayAttachmentProps{
// ↓各VPCのTransit Gateway専用のサブネットを指定
SubnetIds: va.vpc.SelectSubnets(&awsec2.SubnetSelection{SubnetGroupName: &va.subnetGroupName}).SubnetIds,
TransitGatewayId: va.tgw.Ref(),
VpcId: va.vpc.VpcId(),
Tags: &[]*awscdk.CfnTag{
{
Key: jsii.String("Name"),
Value: jsii.String(va.name),
},
},
})
}
以下のように呼び出します。
cmd/hub_spoke/main.go
// Transit GatewayにHub(Shared)VPCをアタッチ
attachmentShared := NewVpcAttachment("HubVpcAttachment", hp.sharedVpc, tgw, "TransitGateway")
attachmentSharedVpc := attachmentShared.Attach()
// Transit GatewayにSpoke VPCをアタッチ
attchmentWorkload := NewVpcAttachment("SpokeVpcAttachment", hp.hubVpc, tgw, "TransitGateway")
attachmentWorkloadVpc := attchmentWorkload.Attach()
次にTransit Gateway用のルートテーブルを作成します。
3. Transit Gatewayのルートテーブルを作成
Transit Gateway用のルートテーブルを作成する構造体・メソッドを用意します。
cmd/hub_spoke/route_table.go
type RouteTable struct {
name string
tgw awsec2.CfnTransitGateway
}
func (ra RouteTable) Create() awsec2.CfnTransitGatewayRouteTable {
return awsec2.NewCfnTransitGatewayRouteTable(ra.tgw, jsii.String("RouteTable"), &awsec2.CfnTransitGatewayRouteTableProps{
TransitGatewayId: ra.tgw.Ref(),
Tags: &[]*awscdk.CfnTag{
{
Key: jsii.String("Name"),
Value: jsii.String(ra.name),
},
},
})
}
以下のように呼び出します。
cmd/hub_spoke/main.go
// Transit Gatewayのルートテーブル作成
rt := NewRouteTable("RouteTable", tgw)
routeTable := rt.Create()
以下4と5を行います。
4.2で作ったアタッチメントと3で作ったルートテーブルを紐付け(アソシエーション)
5.アタッチメントしたVPCからルートテーブルに経路を伝播(プロパゲーション)
4,5 ルートテーブルへのアタッチメントのアソシエーションとプロパゲーション
構造体とメソッドを以下のように定義します。
cmd/hub_spoke/route_establish.go
type VpcRouteAssociation struct {
name string
vpcAttachment awsec2.CfnTransitGatewayAttachment
routeTable awsec2.CfnTransitGatewayRouteTable
}
func (vra VpcRouteAssociation) Create() {
// Association
awsec2.NewCfnTransitGatewayRouteTableAssociation(vra.vpcAttachment, jsii.String(vra.name+"Association"), &awsec2.CfnTransitGatewayRouteTableAssociationProps{
TransitGatewayAttachmentId: vra.vpcAttachment.Ref(),
TransitGatewayRouteTableId: vra.routeTable.Ref(),
})
// Propagation
awsec2.NewCfnTransitGatewayRouteTablePropagation(vra.vpcAttachment, jsii.String(vra.name+"Propagation"), &awsec2.CfnTransitGatewayRouteTablePropagationProps{
TransitGatewayAttachmentId: vra.vpcAttachment.Ref(),
TransitGatewayRouteTableId: vra.routeTable.Ref(),
})
}
これまでと同様に呼び出します。
cmd/hub_spoke/main.go
// Hub(Shared)VPCのアタッチメントとルートをアソシエーション・プロパゲーション
hubVpcRouteAssociation := NewVpcRouteAssociation("HubVpcAssocation", attachmentSharedVpc, routeTable)
hubVpcRouteAssociation.Create()
// Spoke VPCのアタッチメントとルートをアソシエーション・プロパゲーション
spokeVpcRouteAssociation := NewVpcRouteAssociation("SpokeVpcAssociation", attachmentWorkloadVpc, routeTable)
spokeVpcRouteAssociation.Create()
次にHub(Shared)VPCとSpoke VPCが相互に通信できるようにTransit Gatewayのルートテーブルにルートを追加します。
6. Transit Gatewayのルートテーブルにルートを追加
cmd/hub_spoke/route_establish.go
type VpcsConnection struct {
hubVpc awsec2.Vpc
hubVpcAttachment awsec2.CfnTransitGatewayAttachment
spokeVpc awsec2.Vpc
spokeVpcAttachment awsec2.CfnTransitGatewayAttachment
routetable awsec2.CfnTransitGatewayRouteTable
}
func (cv VpcsConnection) Create() {
awsec2.NewCfnTransitGatewayRoute(cv.spokeVpc, jsii.String("ToSpokeVpc"), &awsec2.CfnTransitGatewayRouteProps{
DestinationCidrBlock: cv.spokeVpc.VpcCidrBlock(),
TransitGatewayAttachmentId: cv.spokeVpcAttachment.Ref(),
TransitGatewayRouteTableId: cv.routetable.Ref(),
})
awsec2.NewCfnTransitGatewayRoute(cv.hubVpc, jsii.String("ToHubVpc"), &awsec2.CfnTransitGatewayRouteProps{
DestinationCidrBlock: cv.hubVpc.VpcCidrBlock(),
TransitGatewayAttachmentId: cv.hubVpcAttachment.Ref(),
TransitGatewayRouteTableId: cv.routetable.Ref(),
})
}
長くなりましたがこれでTransit Gateway周りのリソースを作成できました。
各サブネットのルートテーブルにTransit Gatewayへのルートを追加
最後に各サブネットのルートテーブルにTransit Gatewayへのルートを追加します。
サブネットと紐づいているルートテーブルへルートを追加
各VPCのEC2を配置しているサブネットからTransit Gatewayにたどり着けるようにルートを追加します。
cmd/network/route_table.go
type routeToTransitGateway struct {
scope constructs.Construct
name string
vpc awsec2.Vpc
tgw awsec2.CfnTransitGateway
tgwAttachment awsec2.CfnTransitGatewayAttachment
}
func (rttg routeToTransitGateway) CreateRouteToTransitGateway() {
subnets := rttg.vpc.SelectSubnets(&awsec2.SubnetSelection{
SubnetGroupName: jsii.String("Private"),
}).Subnets
for i, subnet := range *subnets {
routeName := fmt.Sprintf("%s%d", rttg.name, i)
awsec2.NewCfnRoute(rttg.scope, jsii.String(routeName), &awsec2.CfnRouteProps{
RouteTableId: subnet.RouteTable().RouteTableId(),
DestinationCidrBlock: jsii.String("0.0.0.0/0"),
TransitGatewayId: rttg.tgw.Ref(),
}).AddDependency(rttg.tgwAttachment)
}
}
上記の構造体とメソッドを呼び出します。
transit_gw.go
// EC2が属するサブネットのルートテーブルからTransit Gatewayへのルートを追加
routeHubSubnetToTransit := network.NewRouteToTransitGateway(stack, "HubSubnetToTransitGW", sharedVpc, hubResult.Tgw, hubResult.HubAttachment)
routeHubSubnetToTransit.CreateRouteToTransitGateway()
routeSpokeSubnetToTransit := network.NewRouteToTransitGateway(stack, "SpokeSubnetToTransitGW", workloadVpc, hubResult.Tgw, hubResult.SpokeAttachment)
routeSpokeSubnetToTransit.CreateRouteToTransitGateway()
これにより以下の図のような構成ができました。
EC2からEC2にpingしてみる
最後に以下のようにpingマンドによりVPCを跨いだEC2間で疎通確認をしてみます。
今回はそれぞれのVPCにインターネットへの経路はありませんが、Session ManagerによるEC2への接続をできるようにしている(Hub VPCのみ)ため、接続してpingコマンドを実行します。
ping 10.20.1.209
以下のように無事疎通を確認できました!
64 bytes from 10.20.1.209: icmp_seq=1 ttl=254 time=0.879 ms
64 bytes from 10.20.1.209: icmp_seq=2 ttl=254 time=0.718 ms
64 bytes from 10.20.1.209: icmp_seq=3 ttl=254 time=0.603 ms
64 bytes from 10.20.1.209: icmp_seq=4 ttl=254 time=0.551 ms
64 bytes from 10.20.1.209: icmp_seq=5 ttl=254 time=0.564 ms
64 bytes from 10.20.1.209: icmp_seq=6 ttl=254 time=0.603 ms
64 bytes from 10.20.1.209: icmp_seq=7 ttl=254 time=0.552 ms
最後に: 参考ブログまとめ
今回構築するにあたって以下の記事を参考にしました。
どれも良い記事なのでTransit Gatewayとは?なぜEC2を配置するサブネットとTransit Gatewayのアタッチメントを配置するサブネットを分けているの?と思った方はご参照ください。
- Transit Gatewayを利用してVPC間で通信してみた | DevelopersIO
- この記事を参考に必要な手順を整理しました
- Transit Gatewayでインスペクション用VPCに通信を集約する場合はTransit Gatewayのアプライアンスモードを有効にしよう | DevelopersIO
- この記事はTypeScriptでTransit Gatewayなどを構築していたため、参考にしながらGoに置き換えました
- [AWS Black Belt Online Seminar] AWS Transit Gateway 資料及び QA 公開 | Amazon Web Services ブログ
- わかりやすいサービスの説明とQAで概念再整理に使わせていただきました
次回は異なるVPC間での名前解決の共有について引き続きAWS CDK With Go言語で構成を作って見ようと思います。
このブログが誰かの時間を1秒でも削ればうれしいです。
以上今泉でした。