Photo by Taylor Vick on Unsplash

AWS PrivateLink provides a secure and reliable way to connect VPCs within the same region, but it doesn’t directly support connections between VPCs in different regions. To address this limitation, inter-Region VPC peering offers a viable solution.

Inter-Region VPC peering enables private connectivity between VPCs in different AWS regions ( have a look into [this previous article](https://awsmorocco.com/aws-multi-region-vpc-peering-using- terraform-a0b8aabf084b) for a deep dive into AWS VPC peering).

How it works:

Image

1 — The Service Provider VPC hangs out in region A (let’s say us-east-1). Now, the Service Consumer VPC wants a local interface VPC endpoint, but in a different spot, region B (like us-west-2).

2 — If you’re not keen on directly connecting the Consumer VPC and the main Service Provider VPC (not the best idea, they say), you can birth another VPC in the same region as the consumer (us-west-2). Connect this new kid (let’s call it Outpost VPC) in the same region as the customer to the main Service Provider VPC.

3 — Time to play matchmaker! Enable PrivateLink between the Outpost VPC (us- west-2) and the Consumer VPC (also us-west-2). Create a VPC endpoint Service in the Outpost VPC with the NLB that gets its targets from the Service Provider VPC (reachable through the peering).

This whole shebang makes even more sense when the Consumer VPC and the Service Provider VPCs are in different accounts or when a direct connection between them is a no-go for security or compliance reasons.

Cross-account inter- region PrivateLink

Image

Let’s Set it Up:

1 — Create VPCs to emulate the Service provider, Service Provider Outpost and the Consumer VPC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    module "vpc_service_provider" {
      source = "terraform-aws-modules/vpc/aws"
      name   = "awsmorocco_service_provider_vpc"
      cidr   = "10.10.0.0/16"

      azs             = ["us-east-1a"]
      private_subnets = ["10.10.1.0/24"]
      public_subnets  = ["10.10.2.0/24"]

      enable_nat_gateway = true
      single_nat_gateway = true

      enable_dns_hostnames = true
      enable_dns_support   = true

      providers = {
        aws = aws.service_provider
      }
    }

    module "vpc_service_provider_outpost" {
      source = "terraform-aws-modules/vpc/aws"
      name   = "awsmorocco_service_provider_outpost"
      cidr   = "10.11.0.0/16"

      azs             = ["us-west-2a"]
      private_subnets = ["10.11.1.0/24"]
      public_subnets  = ["10.11.2.0/24"]

      enable_nat_gateway = true
      single_nat_gateway = true

      enable_dns_hostnames = true
      enable_dns_support   = true

      providers = {
        aws = aws.service_provider_outpost
      }
    }

    module "vpc_service_consumer" {
      source = "terraform-aws-modules/vpc/aws"
      name   = "awsmorocco_service_consumer"
      cidr   = "10.2.0.0/16"

      azs                = ["us-west-2a"]
      private_subnets    = ["10.12.1.0/24"]
      public_subnets     = ["10.12.2.0/24"]
      enable_nat_gateway = true
      single_nat_gateway = true

      enable_dns_hostnames = true
      enable_dns_support   = true

      providers = {
        aws = aws.consumer
      }
    }

The providers configuration looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15

    provider "aws" {
      alias  = "consumer"
      region = "us-east-1"
    }

    provider "aws" {
      alias  = "service_provider"
      region = "us-west-2"
    }

    provider "aws" {
      alias  = "service_provider_outpost"
      region = "us-west-2"
    }

2 — Once the VPCs are created, enable the peering between the Service provider VPC and the Outpost PVC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    resource "aws_vpc_peering_connection" "this" {
      vpc_id      = module.vpc_service_provider_outpost.vpc_id
      peer_vpc_id = module.vpc_service_provider.vpc_id
      peer_region = "us-east-1"
      auto_accept = false

      provider = aws.service_provider_outpost
    }

    resource "aws_vpc_peering_connection_accepter" "this" {
      provider                  = aws.service_provider
      vpc_peering_connection_id = aws_vpc_peering_connection.this.id
      auto_accept               = true
    }

    resource "aws_vpc_peering_connection_options" "this" {
      vpc_peering_connection_id = aws_vpc_peering_connection.this.id
      accepter {
        allow_remote_vpc_dns_resolution = true
      }

      provider = aws.service_provider
    }

    locals {
      requester_route_tables_ids = concat(module.vpc_service_provider_outpost.public_route_table_ids, module.vpc_service_provider_outpost.private_route_table_ids)
      accepter_route_tables_ids  = concat(module.vpc_service_provider.public_route_table_ids, module.vpc_service_provider.private_route_table_ids)
    }

    resource "aws_route" "requester" {
      count                     = length(local.requester_route_tables_ids)
      route_table_id            = local.requester_route_tables_ids[count.index]
      destination_cidr_block    = module.vpc_service_provider.vpc_cidr_block
      vpc_peering_connection_id = aws_vpc_peering_connection.this.id

      provider = aws.service_provider_outpost
    }

    resource "aws_route" "accepter" {
      count                     = length(local.accepter_route_tables_ids)
      route_table_id            = local.accepter_route_tables_ids[count.index]
      destination_cidr_block    = module.vpc_service_provider_outpost.vpc_cidr_block
      vpc_peering_connection_id = aws_vpc_peering_connection.this.id

      provider = aws.service_provider
    }

3 — Once the VPCs are created and the peering is established, you can proceed and enable/create the PrivateLink setup between the Service Consumer VPC and the Service Provider VPC:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

    data "aws_caller_identity" "current" {}

    resource "aws_lb" "nlb" {
      name               = "service-provider-nlb"
      internal           = true
      load_balancer_type = "network"
      subnets            = module.vpc_service_provider.private_subnets

      enable_deletion_protection = false

      provider = aws.service_provider_outpost
    }

    resource "aws_vpc_endpoint_service" "this" {
      acceptance_required        = false
      network_load_balancer_arns = [aws_lb.nlb.arn]

      provider = aws.service_provider_outpost
    }

    resource "aws_vpc_endpoint_service_allowed_principal" "this" {
      vpc_endpoint_service_id = aws_vpc_endpoint_service.this.id
      principal_arn           = data.aws_caller_identity.current.arn

      provider = aws.service_provider_outpost
    }

    resource "aws_vpc_endpoint" "this" {
      service_name      = aws_vpc_endpoint_service.this.service_name
      subnet_ids        = module.vpc_service_consumer.private_subnets
      vpc_endpoint_type = "Interface"
      vpc_id            = module.vpc_service_consumer.vpc_id

      provider = aws.consumer
    }

4 — Finally, deploy EC2 instances within the Service Provider VPCs, each running a straightforward Flask API. Register these instances as targets in the Network Load Balancer (NLB), as elaborated in a prior article explaining the intricacies of PrivateLink.

Additionally, instantiate a public instance within the Consumer VPC and attempt to access the service — specifically, the Flask application — exposed by the deployed instances, following the guidelines outlined in the aforementioned article.

The Benefits of this setup:

  • Security : Traffic remains private within AWS’s private fiber network
  • Scalability : Connect VPCs across different AWS regions
  • Agility : Provide local access without immediate service deployment.

The Limitations to Consider:

  • Latency : Increased latency due to inter-region communication
  • Costs : Data transfer costs for inter-Region VPC peering

Conclusion

In conclusion, the implementation of Inter-Region VPC peering, coupled with AWS PrivateLink, offers a robust and flexible solution for SaaS providers expanding their services across diverse regions. This architectural approach ensures secure and private connectivity within AWS’s infrastructure, allowing for scalability and local access without immediate service deployment.

Read More:

AWS Inter-Region PrivateLink using Terraform was originally published in AWSMorocco on Medium, where people are continuing the conversation by highlighting and responding to this story.

The content, views, and opinions expressed on this blog, awsmorocco.com, are solely those of the authors and contributors and not those of Amazon Web Services (AWS) or its affiliates. This blog is independent and not officially endorsed by, associated with, or sponsored by Amazon Web Services or any of its affiliates.

All trademarks, service marks, trade names, trade dress, product names, and logos appearing on the blog are the property of their respective owners, including in some instances Amazon.com, Inc. or its affiliates. Amazon Web Services®, AWS®, and any related logos are trademarks or registered trademarks of Amazon.com, Inc. or its affiliates.

awsmorocco.com aims to provide informative and insightful commentary, news, and updates about Amazon Web Services and related technologies, tailored for the Moroccan community. However, readers should be aware that this content is not a substitute for direct, professional advice from AWS or a certified AWS professional.

We make every effort to provide timely and accurate information but make no claims, promises, or guarantees about the accuracy, completeness, or adequacy of the information contained in or linked to from this blog.

For official information, please refer to the official Amazon Web Services website or contact AWS directly.