diff --git a/docs/patterns/vpc-lattice.md b/docs/patterns/vpc-lattice.md new file mode 100644 index 0000000000..76cf5e399f --- /dev/null +++ b/docs/patterns/vpc-lattice.md @@ -0,0 +1,7 @@ +--- +title: Amazon VPC Lattice +--- + +{% + include-markdown "../../patterns/vpc-lattice/README.md" +%} diff --git a/patterns/vpc-lattice/README.md b/patterns/vpc-lattice/README.md new file mode 100644 index 0000000000..19cf4489b6 --- /dev/null +++ b/patterns/vpc-lattice/README.md @@ -0,0 +1,14 @@ +# Amazon VPC Lattice + +This folder contains use case-driven patterns covering different aspects of the Amazon VPC Lattice service. + +## Use cases + +- [Simple Client to Server Communication](./client-server-communication/) + + This pattern describes how to expose a simple API within an Amazon EKS cluster deployed in VPC A to a client application hosted in VPC B through Amazon VPC Lattice. + +## Supporting resources + +- [Documentation](https://docs.aws.amazon.com/vpc-lattice/latest/ug/what-is-vpc-lattice.html) +- [AWS Gateway API Controller](https://www.gateway-api-controller.eks.aws.dev/) diff --git a/patterns/vpc-lattice/client-server-communication/README.md b/patterns/vpc-lattice/client-server-communication/README.md new file mode 100644 index 0000000000..e79a725494 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/README.md @@ -0,0 +1,39 @@ +# Amazon VPC Lattice - Simple Client to Server Communication + +This pattern demonstrates how to expose an EKS cluster hosted application to an internal consumer through Amazon VPC Lattice. + +## Scenario + +With this soluton we showcase how to configure Amazon VPC Lattice using the AWS Gateway API Controller in order to manage Amazon VPC Lattice resources through native K8S Gateway API objects. This pattern deploys two distinct VPCs with a client application running in one of them and a server application in the other. The server application is deployed inside an EKS cluster and made exposed to the client application through Amazon VPC Lattice which establishes connectivity between the two applications. Further we demonstrate how to configure a custom domain name for the exposed service using Amazon Route53 and the external-dns project. + +![diagram](assets/diagram.png) + + +## Deploy + +See [here](https://aws-ia.github.io/terraform-aws-eks-blueprints/getting-started/#prerequisites) for the prerequisites and steps to deploy this pattern. + +## Validate + +In order to test the connectivty between the client and server, please follow the steps outlined below: + +1. Login to the management console of your AWS account and navigate to the EC2 service +2. Select your the EC2 Instance with the name **client**, click **Connect**, choose **Session Manager** and click **Connect** +3. Within the console test the connecvity to the server application by entering the following command: + + ```sh + $ curl -i http://server.example.com + HTTP/1.1 200 OK + date: Thu, 14 Dec 2023 08:29:39 GMT + content-length: 54 + content-type: text/plain; charset=utf-8 + + Requesting to Pod(server-6f487b9bcd-5qm4v): server pod + + ``` + +## Destroy + +{% + include-markdown "../../../docs/_partials/destroy.md" +%} diff --git a/patterns/vpc-lattice/client-server-communication/assets/diagram.png b/patterns/vpc-lattice/client-server-communication/assets/diagram.png new file mode 100644 index 0000000000..6cea6fd489 Binary files /dev/null and b/patterns/vpc-lattice/client-server-communication/assets/diagram.png differ diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/.helmignore b/patterns/vpc-lattice/client-server-communication/charts/demo-application/.helmignore new file mode 100644 index 0000000000..0e8a0eb36f --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/Chart.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/Chart.yaml new file mode 100644 index 0000000000..7e6d29f9b3 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/Chart.yaml @@ -0,0 +1,5 @@ +apiVersion: v2 +name: demo-application +description: A Helm chart to deploy the demo-application +type: application +version: 1.0.0 diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/deployment.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/deployment.yaml new file mode 100644 index 0000000000..1b8e744907 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/deployment.yaml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: server + labels: + app: server +spec: + replicas: 2 + selector: + matchLabels: + app: server + template: + metadata: + labels: + app: server + spec: + containers: + - name: server + image: public.ecr.aws/x2j8p8w7/http-server:latest + env: + - name: PodName + value: "server pod" diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway-class.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway-class.yaml new file mode 100644 index 0000000000..d4a925745f --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway-class.yaml @@ -0,0 +1,6 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: GatewayClass +metadata: + name: amazon-vpc-lattice +spec: + controllerName: application-networking.k8s.aws/gateway-api-controller diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway.yaml new file mode 100644 index 0000000000..29e842cbeb --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/gateway.yaml @@ -0,0 +1,11 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: my-services + namespace: apps +spec: + gatewayClassName: amazon-vpc-lattice + listeners: + - name: http + protocol: HTTP + port: 80 diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/httproute.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/httproute.yaml new file mode 100644 index 0000000000..e708960296 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/httproute.yaml @@ -0,0 +1,20 @@ +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: server + namespace: apps +spec: + hostnames: + - server.example.com + parentRefs: + - name: my-services + sectionName: http + rules: + - backendRefs: + - name: server + kind: Service + port: 8090 + matches: + - path: + type: PathPrefix + value: / diff --git a/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/service.yaml b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/service.yaml new file mode 100644 index 0000000000..e6fed42551 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/charts/demo-application/templates/service.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: server +spec: + selector: + app: server + ports: + - protocol: TCP + port: 8090 + targetPort: 8090 diff --git a/patterns/vpc-lattice/client-server-communication/client.tf b/patterns/vpc-lattice/client-server-communication/client.tf new file mode 100644 index 0000000000..145db1ecf6 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/client.tf @@ -0,0 +1,102 @@ +################################################################################ +# Client application (with private access over SSM Systems Manager) +################################################################################ + +module "client" { + source = "terraform-aws-modules/ec2-instance/aws" + version = "5.5.0" + + name = "client" + + instance_type = "t2.micro" + subnet_id = module.client_vpc.private_subnets[0] + create_iam_instance_profile = true + iam_role_description = "IAM role for client" + iam_role_policies = { + AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore" + } + vpc_security_group_ids = [module.client_sg.security_group_id] + + tags = local.tags +} + +module "vpc_endpoints" { + source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints" + version = "~> 5.0" + + vpc_id = module.client_vpc.vpc_id + + endpoints = { for service in toset(["ssm", "ssmmessages", "ec2messages"]) : + replace(service, ".", "_") => + { + service = service + subnet_ids = module.client_vpc.private_subnets + private_dns_enabled = true + tags = { Name = "${local.name}-${service}" } + } + } + + security_group_ids = [module.endpoint_sg.security_group_id] + + tags = local.tags +} + +module "client_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = "client" + description = "Security Group for EC2 Instance Egress" + + vpc_id = module.client_vpc.vpc_id + + egress_with_cidr_blocks = [ + { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = "0.0.0.0/0" + + }, + ] + + tags = local.tags +} + +module "endpoint_sg" { + source = "terraform-aws-modules/security-group/aws" + version = "~> 5.0" + + name = "ssm-endpoint" + description = "Security Group for EC2 Instance Egress" + + vpc_id = module.client_vpc.vpc_id + + ingress_with_cidr_blocks = [for subnet in module.client_vpc.private_subnets_cidr_blocks : + { + from_port = 443 + to_port = 443 + protocol = "TCP" + cidr_blocks = subnet + } + ] + + tags = local.tags +} + +################################################################################ +# Client VPC +################################################################################ + +module "client_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.4" + + name = local.name + cidr = local.client_vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.client_vpc_cidr, 4, k)] + + tags = local.tags +} diff --git a/patterns/vpc-lattice/client-server-communication/eks.tf b/patterns/vpc-lattice/client-server-communication/eks.tf new file mode 100644 index 0000000000..0f50052063 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/eks.tf @@ -0,0 +1,197 @@ +################################################################################ +# Cluster +################################################################################ + +module "eks" { + source = "terraform-aws-modules/eks/aws" + version = "~> 19.21" + + cluster_name = local.name + cluster_version = "1.28" + cluster_endpoint_public_access = true + enable_irsa = true + + vpc_id = module.cluster_vpc.vpc_id + subnet_ids = module.cluster_vpc.private_subnets + + eks_managed_node_groups = { + initial = { + instance_types = ["m5.large"] + + min_size = 3 + max_size = 10 + desired_size = 3 + } + } + + tags = local.tags +} + +################################################################################ +# Cluster VPC +################################################################################ + +module "cluster_vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 5.4" + + name = local.name + cidr = local.cluster_vpc_cidr + + azs = local.azs + private_subnets = [for k, v in local.azs : cidrsubnet(local.cluster_vpc_cidr, 4, k)] + public_subnets = [for k, v in local.azs : cidrsubnet(local.cluster_vpc_cidr, 8, k + 48)] + + enable_nat_gateway = true + single_nat_gateway = true + + public_subnet_tags = { + "kubernetes.io/role/elb" = 1 + } + + private_subnet_tags = { + "kubernetes.io/role/internal-elb" = 1 + } + + tags = local.tags +} + +################################################################################ +# EKS Addons (AWS Gateway API Controller) +################################################################################ + +module "addons" { + source = "aws-ia/eks-blueprints-addons/aws" + version = "~> 1.12" + + cluster_name = module.eks.cluster_name + cluster_endpoint = module.eks.cluster_endpoint + cluster_version = module.eks.cluster_version + oidc_provider_arn = module.eks.oidc_provider_arn + + enable_aws_gateway_api_controller = true + aws_gateway_api_controller = { + chart_version = "v1.0.3" + create_namespace = true + namespace = "aws-application-networking-system" + source_policy_documents = [data.aws_iam_policy_document.gateway_api_controller.json] + set = [ + { + name = "clusterName" + value = module.eks.cluster_name + }, + { + name = "log.level" + value = "debug" + }, + { + name = "clusterVpcId" + value = module.cluster_vpc.vpc_id + }, + { + name = "defaultServiceNetwork" + value = "" + }, + { + name = "latticeEndpoint" + value = "https://vpc-lattice.${local.region}.amazonaws.com" + } + ] + wait = true + } + enable_external_dns = true + external_dns_route53_zone_arns = try([aws_route53_zone.primary.arn], []) + external_dns = { + set = [ + { + name = "domainFilters[0]" + value = "example.com" + }, + { + name = "policy" + value = "sync" + }, + { + name = "sources[0]" + value = "crd" + }, + { + name = "sources[1]" + value = "ingress" + }, + { + name = "txtPrefix" + value = module.eks.cluster_name + }, + { + name = "extraArgs[0]" + value = "--crd-source-apiversion=externaldns.k8s.io/v1alpha1" + }, + { + name = "extraArgs[1]" + value = "--crd-source-kind=DNSEndpoint" + }, + { + name = "crdSourceApiversion" + value = "externaldns.k8s.io/v1alpha1" + }, + { + name = "crdSourceKind" + value = "DNSEndpoint" + } + ] + } + + tags = local.tags +} + +data "aws_iam_policy_document" "gateway_api_controller" { + statement { + sid = "" + effect = "Allow" + resources = ["*"] # For testing purposes only (highly recommended limit access to specific resources for production usage) + + actions = [ + "vpc-lattice:*", + "iam:CreateServiceLinkedRole", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeSecurityGroups", + "logs:CreateLogDelivery", + "logs:GetLogDelivery", + "logs:UpdateLogDelivery", + "logs:DeleteLogDelivery", + "logs:ListLogDeliveries", + "tag:GetResources", + ] + } +} + +################################################################################ +# Demo applications +################################################################################ + +resource "helm_release" "demo_application" { + name = "demo-application" + chart = "./charts/demo-application" + create_namespace = true + namespace = "apps" + + depends_on = [module.addons] +} + +################################################################################ +# Update cluster security group to allow access from VPC Lattice +################################################################################ + +data "aws_ec2_managed_prefix_list" "vpc_lattice_ipv4" { + name = "com.amazonaws.${local.region}.vpc-lattice" +} + +resource "aws_vpc_security_group_ingress_rule" "cluster_sg_ingress" { + security_group_id = module.eks.node_security_group_id + + prefix_list_id = data.aws_ec2_managed_prefix_list.vpc_lattice_ipv4.id + ip_protocol = "-1" +} diff --git a/patterns/vpc-lattice/client-server-communication/lattice.tf b/patterns/vpc-lattice/client-server-communication/lattice.tf new file mode 100644 index 0000000000..c8ddd7f661 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/lattice.tf @@ -0,0 +1,42 @@ +################################################################################ +# VPC Lattice service network +################################################################################ + +resource "aws_vpclattice_service_network" "this" { + name = "my-services" + auth_type = "NONE" + + tags = local.tags +} + +resource "aws_vpclattice_service_network_vpc_association" "cluster_vpc" { + vpc_identifier = module.cluster_vpc.vpc_id + service_network_identifier = aws_vpclattice_service_network.this.id +} + +resource "aws_vpclattice_service_network_vpc_association" "client_vpc" { + vpc_identifier = module.client_vpc.vpc_id + service_network_identifier = aws_vpclattice_service_network.this.id +} + +resource "time_sleep" "wait_for_lattice_resources" { + depends_on = [helm_release.demo_application] + + create_duration = "120s" +} + +################################################################################ +# Custom domain name for VPC lattice service +# Records will be created by external-dns using DNSEndpoint objects which +# are created by the VPC Lattice gateway api controller when creating HTTPRoutes +################################################################################ + +resource "aws_route53_zone" "primary" { + name = "example.com" + + vpc { + vpc_id = module.client_vpc.vpc_id + } + + tags = local.tags +} diff --git a/patterns/vpc-lattice/client-server-communication/main.tf b/patterns/vpc-lattice/client-server-communication/main.tf new file mode 100644 index 0000000000..4412f1b1dd --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/main.tf @@ -0,0 +1,52 @@ +provider "aws" { + region = local.region +} + +data "aws_availability_zones" "available" { + #Do not include local zones + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + + +provider "kubernetes" { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + # This requires the awscli to be installed locally where Terraform is executed + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } +} + +provider "helm" { + kubernetes { + host = module.eks.cluster_endpoint + cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data) + + exec { + api_version = "client.authentication.k8s.io/v1beta1" + command = "aws" + # This requires the awscli to be installed locally where Terraform is executed + args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name] + } + } +} + +locals { + name = basename(path.cwd) + region = "us-west-2" + + cluster_vpc_cidr = "10.0.0.0/16" + client_vpc_cidr = "10.1.0.0/16" + azs = slice(data.aws_availability_zones.available.names, 0, 3) + + tags = { + Blueprint = local.name + GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints" + } +} diff --git a/patterns/vpc-lattice/client-server-communication/outputs.tf b/patterns/vpc-lattice/client-server-communication/outputs.tf new file mode 100644 index 0000000000..c952ef95d0 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/outputs.tf @@ -0,0 +1,4 @@ +output "configure_kubectl" { + description = "Configure kubectl: make sure you're logged in with the correct AWS profile and run the following command to update your kubeconfig" + value = "aws eks update-kubeconfig --name ${module.eks.cluster_name} --alias ${module.eks.cluster_name} --region ${local.region}" +} diff --git a/patterns/vpc-lattice/client-server-communication/variables.tf b/patterns/vpc-lattice/client-server-communication/variables.tf new file mode 100644 index 0000000000..e69de29bb2 diff --git a/patterns/vpc-lattice/client-server-communication/versions.tf b/patterns/vpc-lattice/client-server-communication/versions.tf new file mode 100644 index 0000000000..9adde16313 --- /dev/null +++ b/patterns/vpc-lattice/client-server-communication/versions.tf @@ -0,0 +1,22 @@ +terraform { + required_version = ">= 1.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.30" + } + helm = { + source = "hashicorp/helm" + version = ">= 2.9" + } + kubernetes = { + source = "hashicorp/kubernetes" + version = ">= 2.24" + } + time = { + source = "hashicorp/time" + version = ">= 0.10" + } + } +}