Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Blueprint for VPC lattice with kubernetes api controllers #1830

Closed
wants to merge 12 commits into from
Closed
101 changes: 101 additions & 0 deletions patterns/blueprint-vpc-lattice/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Application Networking with Amazon VPC Lattice and Amazon EKS

This pattern demonstrates where a service in one EKS cluster communicates with a service in another cluster and VPC, using VPC Lattice. Besides it also shows how service discovery works, with support for using custom domain names for services. It also demonstrates how VPC Lattice enables services in EKS clusters with overlapping CIDRs to communicate with each other without the need for any networking constructs like private NAT Gateways and Transit Gateways.
neelaruban marked this conversation as resolved.
Show resolved Hide resolved

- [Documentation](https://aws.amazon.com/vpc/lattice/)
- [Launch Blog](https://aws.amazon.com/blogs/containers/amazon-vpc-cni-now-supports-kubernetes-network-policies/)
neelaruban marked this conversation as resolved.
Show resolved Hide resolved

## Scenario

The solution architecture used to demonstrate cross-cluster connectivity with VPC Lattice is shown in the following diagram. The following are the relevant aspects of this architecture.

1. Two VPCs are setup in the same AWS Region, both using the same RFC 1918 address range 192.168.48.0/20
2. An EKS cluster is provisioned in each of the VPC.
3. An HTTP web service is deployed to the EKS cluster in Cluster1-vpc , exposing a set of REST API endpoints. Another REST API service is deployed to the EKS cluster in Cluster2-vpc and it communicates with an Aurora PostgreSQL database in the same VPC.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a common pattern that we see employed? Are there other scenarios that are better suited for this pattern?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the very basic/simple pattern for VPC lattice connecting two different EKS clusters using lattice . There are other patterns in which only one EKS cluster will act as a control plane for routes for the rest of the clusters .

AWS Gateway API controller is used in both clusters to manage the Kubernetes Gateway API resources such as Gateway and HTTPRoute. These custom resources orchestrate AWS VPC Lattice resources such as Service Network, Service, and Target Groups that enable communication between the Kubernetes services deployed to the clusters.

![img.png](img/img.png)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please be sure that this renders correctly on the published documentation site


## Deploy

See [here](https://aws-ia.github.io/terraform-aws-eks-blueprints/getting-started/#prerequisites) for the prerequisites and steps to deploy this pattern.

1. set up the first cluster with its own VPC and the second with an aurora postgres DB
neelaruban marked this conversation as resolved.
Show resolved Hide resolved

```shell
# setting up the cluster1
cd cluster1
terraform init
terraform apply

cd ../cluster2
terraform init
terraform apply
```

2. Initialize the aurora postgres database for cluster2 vpc refer [here](./cluster2/postgres-setup/README.md)
3. Initialize Kubernetes secrets for cluster2

```shell
# assuming you are already in the /cluster2 folder
chmod +x secrets.sh && ./secrets.sh
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
```
4. Deploy the kubernetes artefacts for cluster2

Deploy the datastore service to the EKS cluster in cluster2. This service fronts an Aurora PostgreSQL database and exposes REST API endpoints with path-prefixes /popular and /summary. To demonstrate canary shifting of traffic, deploy two versions of the datastore service to the cluster as shown below.

```shell
# Apply Kubernetes set of manifests to both clusters that defines the GatewayClass and Gateway resources. The Gateway API controller then creates a Lattice service network with the same name, eks-lattice-network, as that of the Gateway resource if one doesn’t exist and attaches the VPCs to the service network.
export CLUSTER_2=cluster2
export AWS_DEFAULT_REGION=$(aws configure get region)
export AWS_ACCOUNT_NUMBER=$(aws sts get-caller-identity --query "Account" --output text)

export CTX_CLUSTER_2=arn:aws:eks:$AWS_DEFAULT_REGION:${AWS_ACCOUNT_NUMBER}:cluster/$CLUSTER_2
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use aws eks update-kubeconfig --name <cluster-name> for this. update to use this and replace the use of the targetted contexts below



kubectl apply --context="${CTX_CLUSTER_2}" -f ./$CLUSTER_2/gateway-lattice.yml # GatewayClass and Gateway
kubectl apply --context="${CTX_CLUSTER_2}" -f ./$CLUSTER_2/route-datastore-canary.yml # HTTPRoute and ClusterIP Services
kubectl apply --context="${CTX_CLUSTER_2}" -f ./$CLUSTER_2/datastore.yml # Deployment
```

5. Deploy the gateway lattice and the frontend service on cluster1

The frontend service is configured to communicate with the datastore service in cluster1 using its custom domain name.

```shell
export CLUSTER_1=cluster1
export AWS_DEFAULT_REGION=$(aws configure get region)
export AWS_ACCOUNT_NUMBER=$(aws sts get-caller-identity --query "Account" --output text)

aws eks update-kubeconfig --name $CLUSTER_1 --region $AWS_DEFAULT_REGION
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why we are mixing these, but we should only need to use aws eks update-kubeconfig --name <cluster-name> and can remove the other exported bash variables


export CTX_CLUSTER_1=arn:aws:eks:$AWS_DEFAULT_REGION:${AWS_ACCOUNT_NUMBER}:cluster/$CLUSTER_1


kubectl apply --context="${CTX_CLUSTER_1}" -f ./$CLUSTER_1/gateway-lattice.yml # GatewayClass and Gateway
kubectl apply --context="${CTX_CLUSTER_1}" -f ./$CLUSTER_1/frontend.yml # Frontend service
```

neelaruban marked this conversation as resolved.
Show resolved Hide resolved
## Testing if cluster1 service could talk to cluster2 service via VPC lattice

Shell commands below uses kubectl port-forward to forward outgoing traffic from a local port to the server port 3000 on one of the pods of the frontend service, which allows us to test this use case end-to-end without needing any load balancer.

```shell
POD=$(kubectl -context="${CTX_CLUSTER_1}" get pod -n apps -l app=frontend -o jsonpath="{.items[0].metadata.name}")
kubectl -context="${CTX_CLUSTER_1}" -n apps port-forward ${POD} 80:3000 # Port Forwarding

curl -X GET http://localhost/popular/category|jq
curl -X GET http://localhost/summary|jq # you could retry the summary to see if you get a different results from different versions
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This now introduces a new dependency required for users to run the pattern - jq. Do we need to use jq or can we just use the raw output?
  2. What should users expect to see here? How do they know if the pattern is setup and working as intended?


```

## Destroy

To teardown and remove the resources created in this example:

```shell
cd cluster1
terraform apply -destroy -autoapprove
cd ../cluster2
terraform apply -destroy -autoapprove
```
61 changes: 61 additions & 0 deletions patterns/blueprint-vpc-lattice/cluster1/frontend.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
namespace: apps
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: go
image: public.ecr.aws/awsvijisarathy/k8s-frontend:v1
imagePullPolicy: Always
env:
- name: DATASTORE_SERVICE_URL
value: datastore.sarathy.io
ports:
- name: http
containerPort: 3000
protocol: TCP
resources:
requests:
cpu: "50m"
memory: "128Mi"
livenessProbe:
httpGet:
path: /live
port: 3000
initialDelaySeconds: 10
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 10
timeoutSeconds: 1
periodSeconds: 10
failureThreshold: 3
---
apiVersion: v1
kind: Service
metadata:
name: frontend-svc
namespace: apps
spec:
type: ClusterIP
ports:
- port: 80
protocol: TCP
targetPort: 3000
selector:
app: frontend
22 changes: 22 additions & 0 deletions patterns/blueprint-vpc-lattice/cluster1/gateway-lattice.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: GatewayClass
metadata:
name: amazon-vpc-lattice
spec:
controllerName: application-networking.k8s.aws/gateway-api-controller

---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
name: eks-lattice-network
spec:
gatewayClassName: amazon-vpc-lattice
listeners:
- name: http
protocol: HTTP
port: 80
allowedRoutes:
namespaces:
from: All
193 changes: 193 additions & 0 deletions patterns/blueprint-vpc-lattice/cluster1/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
provider "aws" {
region = local.region
}

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]
}
}
}

data "aws_availability_zones" "available" {}
data "aws_ecrpublic_authorization_token" "token" {}
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
data "aws_caller_identity" "identity" {}
data "aws_region" "current" {}
neelaruban marked this conversation as resolved.
Show resolved Hide resolved

locals {
name = basename(path.cwd)
region = "us-west-2"

vpc_cidr = "192.168.48.0/20"
azs = slice(data.aws_availability_zones.available.names, 0, 3)

tags = {
Blueprint = local.name
GithubRepo = "github.com/aws-ia/terraform-aws-eks-blueprints"
}
}

################################################################################
# Cluster
################################################################################

module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 19.16"

cluster_name = local.name
cluster_version = "1.27" # Must be 1.25 or higher
cluster_endpoint_public_access = true

vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets

eks_managed_node_groups = {
initial = {
instance_types = ["m5.large"]

min_size = 1
max_size = 2
desired_size = 1
}
}

tags = local.tags
}

################################################################################
# Supporting Resources
################################################################################

module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.0"

name = local.name
cidr = local.vpc_cidr

azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(local.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 (demo application)
################################################################################

module "addons" {
source = "aws-ia/eks-blueprints-addons/aws"
version = "~> 1.0"

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

# EKS Addons
eks_addons = {
coredns = {}
kube-proxy = {}
vpc-cni = {
preserve = true
most_recent = true # Must be 1.14.0 or higher
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its because the aws gateway API controller requires the latest version of the VPC CNI


timeouts = {
create = "25m"
delete = "10m"
}

}
}
enable_aws_gateway_api_controller = true
aws_gateway_api_controller = {
repository_username = data.aws_ecrpublic_authorization_token.token.user_name
repository_password = data.aws_ecrpublic_authorization_token.token.password
# awsRegion, clusterVpcId, clusterName, awsAccountId are required for case where IMDS is NOT AVAILABLE, e.g Fargate, self-managed clusters with IMDS access blocked
set = [{
name = "clusterVpcId"
value = module.vpc.vpc_id
},
{
name = "clusterName"
value = module.eks.cluster_name
},
{
name = "awsAccountId"
value = local.region
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is incorrect - please remove

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for pointing that out

},
{
name = "awsAccountId"
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
value = data.aws_caller_identity.identity.account_id
},
{
name = "awsRegion"
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
value = local.region
}
]

}
tags = local.tags
}

data "aws_ec2_managed_prefix_list" "ipv4" {
name = "com.amazonaws.${data.aws_region.current.name}.vpc-lattice"
}

data "aws_ec2_managed_prefix_list" "ipv6" {
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
name = "com.amazonaws.${data.aws_region.current.name}.ipv6.vpc-lattice"
}


# configure security group to receive traffic from the VPC Lattice network. You must set up security groups so that they allow all Pods communicating with VPC Lattice to allow traffic from the VPC Lattice managed prefix lists. Lattice has both an IPv4 and IPv6 prefix lists available
resource "aws_security_group_rule" "vpc_lattice_ipv4_ingress" {
description = "VPC lattice ipv4 ingress"
type = "ingress"
security_group_id = module.eks.cluster_security_group_id
from_port = 0
to_port = 0
protocol = "-1"
prefix_list_ids = [data.aws_ec2_managed_prefix_list.ipv4.id]
}

resource "aws_security_group_rule" "vpc_lattice_ipv6_ingress" {
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
description = "VPC lattice ivp6 ingress"
type = "ingress"
security_group_id = module.eks.cluster_security_group_id
from_port = 0
to_port = 0
protocol = "-1"
prefix_list_ids = [data.aws_ec2_managed_prefix_list.ipv6.id]
}
4 changes: 4 additions & 0 deletions patterns/blueprint-vpc-lattice/cluster1/outputs.tf
Original file line number Diff line number Diff line change
@@ -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}"
neelaruban marked this conversation as resolved.
Show resolved Hide resolved
}
Loading