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.

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.