HTTP/3 with Amazon Elastic Kubernetes Service (EKS)

How to configure HTTP/3 support for Amazon Elastic Kubernetes Service (EKS). This guide shows how to setup the LoadBalancer service for EKS to support both TCP and UDP communications.

Amazon Elastic Kubernetes Service HTTP/3 configuration

This guide shows how to setup HTTP/3 support for Amazon Elastic Kubernetes Service (EKS) The instructions provided in this page are a continuation of the HTTP/3 in Emissary documentation.

Create a network load balancer (NLB)

The virtual private cloud (VPC) for your load balancer needs one public subnet in each availability zone where you have targets.

SUBNET_IDS=(<your-subnet1-id> <your-subnet2-id> <your-subnet3-id>)

aws elbv2 create-load-balancer \
  --name ${CLUSTER_NAME}-nlb \
  --type network \
  --subnets ${SUBNET_IDS}

Create a NodePort service

Now create a NodePort service for Emissary installation with two entries. Use port: 443 to include support for both TCP and UDP traffic.

# Selectors and labels removed for clarity.
apiVersion: v1
kind: Service
metadata:
  name: $productDeploymentName$-http3
  namespace: $productNamespace$
spec:
  type: NodePort
  ports:
  - name: http
    port: 80
    targetPort: 8080
    protocol: TCP
    nodePort: 30080
  - name: https
    port: 443
    targetPort: 8443
    protocol: TCP
    nodePort: 30443
  - name: http3
    port: 443
    targetPort: 8443
    protocol: UDP
    nodePort: 30443

Create target groups

Run the following command with the variables for your VPC ID and cluster name:

VPC_ID=<your-vpc-id>
CLUSTER_NAME=<your-cluster-name>

aws elbv2 create-target-group --name ${CLUSTER_NAME}-tcp-tg \
  --protocol TCP --port 30080 --vpc-id ${VPC_ID} \
  --health-check-protocol TCP \
  --health-check-port 30080 \
  --target-type instance

aws elbv2 create-target-group --name ${CLUSTER_NAME}-tcp-udp-tg \
  --protocol TCP_UDP --port 30443 --vpc-id ${VPC_ID} \
  --health-check-protocol TCP \
  --health-check-port 30443 \
  --target-type instance

Register your instances

Next, register your cluster’s instance with the the instance IDs and Amazon Resource Names (ARN).

To get your cluster’s instance IDs, enter the following command:

aws ec2 describe-instances \
  --filters Name=tag:eks:cluster-name,Values=${CLUSTER_NAME} \
  --output text
  --query 'Reservations[*].Instances[*].InstanceId' \

To get your ARNs, enter the following command:

TCP_TG_NAME=${CLUSTER_NAME}-tcp-tg-name
TCP_UDP_TG_NAME=${CLUSTER_NAME}-tcp-udp-tg-name

aws elbv2 describe-target-groups \
    --query 'TargetGroups[?TargetGroupName==`'${TCP_TG_NAME}'`].TargetGroupArn' \
    --output text
aws elbv2 describe-target-groups \
 --query 'TargetGroups[?TargetGroupName==`'${TCP_UDP_TG_NAME}'`].   TargetGroupArn' \
    --output text

Register the instances with the target groups and load balancer using the instance IDs and ARNs you retrieved.

INSTANCE_IDS=(<Id=i-07826...> <Id=i-082fd...>)
REGION=<your-region>
TG_NAME=<your-tg-name>
TCP_TG_ARN=arn:aws:elasticloadbalancing:${REGION}:079.....:targetgroup/${TG_NAME}/...
TCP_UDP_TG_ARN=arn:aws:elasticloadbalancing:${REGION}:079.....:targetgroup/${TG_NAME}/...

aws elbv2 register-targets --target-group-arn ${TCP_TG_ARN} --targets ${INSTANCE_IDS}
aws elbv2 register-targets --target-group-arn ${TCP_UDP_TG_ARN} --targets ${INSTANCE_IDS}

Create listeners in AWS.

Register your cluster’s instance with the instance IDs and ARNs.

To get the load balancer’s ARN, enter the following command:

aws elbv2 describe-load-balancers --name ${CLUSTER_NAME}-nlb \
  --query 'LoadBalancers[0].LoadBalancerArn' \
  --output text

Create a TCP listener on port 80 that that forwards to the TargetGroup {TCP_TG_ARN}.

aws elbv2 create-listener --load-balancer-arn ${LB_ARN} \
  --protocol TCP --port 80 \
  --default-actions Type=forward,TargetGroupArn=${TCP_TG_ARN}

Create a TCP_UDP listener on port 443 that forwards to the TargetGroup {TCP_UDP_TG_ARN}.

aws elbv2 create-listener --load-balancer-arn ${LB_ARN} \
  --protocol TCP_UDP --port 443 \
  --default-actions Type=forward,TargetGroupArn=${TCP_UDP_TG_ARN}

Update the security groups

Now you need to update your security groups to receive traffic. This security group covers all node groups attached to the EKS cluster:

aws eks describe-cluster --name ${CLUSTER_NAME} | grep clusterSecurityGroupId

Now authorize the cluster security group to allow internet traffic:

for x in ${CLUSTER_SG}; do \
  aws ec2 authorize-security-group-ingress --group-id $$x --protocol tcp --port 30080 --cidr 0.0.0.0/0; \
  aws ec2 authorize-security-group-ingress --group-id $$x --protocol tcp --port 30443 --cidr 0.0.0.0/0; \
  aws ec2 authorize-security-group-ingress --group-id $$x --protocol udp --port 30443 --cidr 0.0.0.0/0; \
done

Get the DNS name for the load balancers

Enter the following command to get the DNS name for your load balancers and create a CNAME record at your domain provider:

aws elbv2 describe-load-balancers --name ${CLUSTER_NAME}-nlb \
  --query 'LoadBalancers[0].DNSName' \
  --output text

Create Listener resources

Now you need to create the Listener resources for Emissary. The first Listener in the example below handles traffic for HTTP/1.1 and HTTP/2, while the second Listener handles all HTTP/3 traffic.

kubectl apply -f - <<EOF
# This is a standard Listener that leverages TCP to serve HTTP/2 and HTTP/1.1 traffic.
# It is bound to the same address and port (0.0.0.0:8443) as the UDP listener.
apiVersion: getambassador.io/v3alpha1
kind: Listener
metadata:
  name: $productDeploymentName$-https-listener
  namespace: $productNamespace$
spec:
  port: 8443
  protocol: HTTPS
  securityModel: XFP
  hostBinding:
    namespace:
      from: ALL
---
# This is a Listener that leverages UDP and HTTP to serve HTTP/3 traffic.
# NOTE: Raw UDP traffic is not supported. UDP and HTTP must be used together.
apiVersion: getambassador.io/v3alpha1
kind: Listener
metadata:
  name: $productDeploymentName$-https-listener-udp
  namespace: $productNamespace$
spec:
  port: 8443
  # Order is important here. UDP must be last item. HTTP is required.
  protocolStack:
    - TLS
    - HTTP
    - UDP
  securityModel: XFP
  hostBinding:
    namespace:
      from: ALL
EOF

Create a Host resource

Create a Host resource for your domain name.

kubectl apply -f - <<EOF
apiVersion: getambassador.io/v3alpha1
kind: Host
metadata:
  name: $productDeploymentName$-aws-host
  namespace: $productNamespace$
spec:
  hostname: <your-hostname>
  acmeProvider:
    authority: none
  tlsSecret:
    name: tls-cert # The QUIC network protocol requires TLS with a valid certificate
  tls:
    min_tls_version: v1.3
    max_tls_version: v1.3
    alpn_protocols: h2,http/1.1
EOF

Apply the quote service and a Mapping to test the HTTP/3 configuration.

Finally, apply the quote service to a Emissary Mapping.

kubectl apply -f https://app.getambassador.io/yaml/v2-docs/$version$/quickstart/qotm.yaml
kubectl apply -f - <<EOF
---
apiVersion: getambassador.io/v3alpha1
kind: Mapping
metadata:
  name: quote-backend
spec:
  hostname: "*"
  prefix: /backend/
  service: quote
  docs:
    path: "/.ambassador-internal/openapi-docs"
EOF

Now verify the connection:

$ curl -i http://<your-hostname>/backend/

Your domain now shows that it is being served with HTTP/3.