Skip to main content

CodeTogether Intel on AWS EKS

This document describes how to deploy the Intel application to Amazon EKS using a delegated Route 53 public zone, NGINX Ingress, cert‑manager with Let’s Encrypt (HTTP‑01), and external‑dns. All names and domains are generic and should be replaced with organization‑specific values.

Environment Summary

  • Cloud: AWS (EKS, Route 53, NLB/ALB)
  • DNS: Parent zone hosted externally (e.g., example.com), delegated sub‑zone in Route 53 (e.g., prod.example.com)
  • Cluster: EKS
  • Ingress: NGINX Ingress Controller
  • TLS: cert‑manager + Let’s Encrypt (HTTP‑01)
  • DNS automation: external‑dns (Route 53 provider)
  • Data: Cassandra (single‑pod demonstration)
  • Application: Intel (Helm chart intel — replace with actual chart)
  • Public host: intel.prod.example.com

Variables (replace as needed)

# DNS & cluster
export PARENT_DOMAIN="example.com"
export SUBZONE="prod.example.com"
export APP_HOST="intel.${SUBZONE}"
export CLUSTER_NAME="intel-eks"
export AWS_REGION="us-east-1"
export CONTACT_EMAIL="admin@example.com"

1 Architecture

Internet ─▶ Route53 zone (prod.example.com)
▲ (delegated from external parent zone: example.com)

external-dns (kube-system)
│ writes A/AAAA/TXT

NLB/ALB ─▶ NGINX Ingress (ingressClass: nginx)
└─▶ Service intel-server (ClusterIP :1080)
└─▶ Pod intel-server-…

cert-manager (ClusterIssuer: letsencrypt-prod, HTTP-01 via solver Ingress)
Cassandra Stateful workload for Intel
tip

The diagram represents one common topology. Teams may choose ALB Ingress Controller directly, or terminate TLS at NGINX; both patterns are viable.


2 Prerequisites

  • AWS account with IAM permissions for EKS, EC2, ELB, and Route 53
  • A parent domain (example.com) hosted by any DNS provider
  • A delegated public Route 53 hosted zone for prod.example.com
  • Local tooling: kubectl, eksctl, Helm, AWS CLI

3 Create the EKS Cluster (example)

# Create a small demo cluster (adjust versions and sizes as needed)
eksctl create cluster \
--name ${CLUSTER_NAME} \
--region ${AWS_REGION} \
--version 1.33 \
--nodegroup-name ng-ops \
--nodes 1 --nodes-min 1 --nodes-max 1 \
--node-type t3.medium \
--managed
# Confirm cluster access
kubectl get nodes -o wide

4 Route 53 Hosted Zone & Delegation

Create (or confirm) a public hosted zone for the sub‑domain (e.g., prod.example.com) and delegate it from the parent provider.

# (Recommended) Associate IAM OIDC provider for IRSA-enabled addons
eksctl utils associate-iam-oidc-provider \
--cluster "${CLUSTER_NAME}" \
--region "${AWS_REGION}" \
--approve
# Create sub-zone (public) in Route 53
export HOSTED_ZONE="${SUBZONE}"
HZ_ID=$(aws route53 create-hosted-zone \
--name "${HOSTED_ZONE}" \
--caller-reference "$(date +%s)" \
--hosted-zone-config Comment="Intel EKS zone",PrivateZone=false \
--query 'HostedZone.Id' --output text)
# Show NS records to copy into the parent zone
aws route53 list-resource-record-sets --hosted-zone-id "$HZ_ID" \
--query 'ResourceRecordSets[?Type==`NS`]' --output table

Update the parent DNS provider (for example.com) to add an NS delegation for prod.example.com using the four Route 53 NS values.

Validate the delegation:

# Query a public resolver
dig +short NS ${SUBZONE} @1.1.1.1

5 Install NGINX Ingress Controller

helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace \
--set controller.ingressClassResource.name=nginx \
--set controller.ingressClass=nginx
kubectl -n ingress-nginx get svc,deploy,pod -o wide

The controller will provision an AWS load balancer. The ADDRESS will look like: a0845bf73030741b2905e005db6fa787-3228f0f887038d46.elb.us-east-1.amazonaws.com.


6 Install cert‑manager & ClusterIssuer

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set crds.install=true
# ClusterIssuer (Let’s Encrypt prod, HTTP-01 via Ingress)
cat <<'YAML' | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: ${CONTACT_EMAIL}
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
YAML
kubectl -n cert-manager get pods

7 Install external‑dns (Route 53)

Create an IAM role for external‑dns that allows route53:ChangeResourceRecordSets and route53:List* on the sub‑zone. Then install with Helm.

helm repo add bitnami https://charts.bitnami.com/bitnami
helm upgrade --install external-dns bitnami/external-dns \
--namespace kube-system \
--set provider=aws \
--set policy=upsert-only \
--set txtOwnerId=intel-demo \
--set domainFilters[0]="${SUBZONE}" \
--set aws.zoneType=public

kubectl -n kube-system logs deploy/external-dns --tail=200

Expected records created by external‑dns:

  • intel.prod.example.comA/AAAA alias to the ELB
  • Ownership TXT records for the host

8 Cassandra Installation (demo)

helm repo add bitnami https://charts.bitnami.com/bitnami
helm upgrade --install cassandra bitnami/cassandra \
--namespace intel --create-namespace \
--set replicaCount=1

kubectl -n intel get pods -l app.kubernetes.io/name=cassandra -o wide

For production, configure storage classes, resource requests/limits, and multi‑AZ/replication per organizational requirements.


9 Deploy Intel (Helm)

note

Prerequisites: cluster & ingress basics from the Kubernetes install guide, TLS secret creation (see the TLS section above), and any SSO specifics from SSO configuration.

This deployment produces a Deployment (intel-server), Service (intel-server, ClusterIP:1080), and Ingress (intel-server, class nginx).

Option A: Helm CLI (example)

helm upgrade --install intel codetogether/codetogether-intel \
--namespace intel \
--set ingress.enabled=true \
--set ingress.className=nginx \
--set ingress.hosts[0].host=${APP_HOST} \
--set ingress.tls[0].secretName=intel-tls \
--set ingress.tls[0].hosts[0]=${APP_HOST}

Option B: values.yaml (snippet)

image:
repository: <ECR_REPOSITORY>/intel
tag: <TAG>
pullPolicy: IfNotPresent

imageCredentials:
enabled: true
pullSecret: intel-pull-secret

ingress:
enabled: true
className: nginx
hosts:
- host: intel.prod.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: intel-tls
hosts:
- intel.prod.example.com

service:
port: 1080
targetPort: http

After deployment, check:

kubectl -n intel get deploy,svc,ingress,pod
kubectl -n intel get ingress intel-server -o wide # shows ELB address

10 TLS Issuance (HTTP‑01) — What Happens

cert‑manager creates temporary solver resources (Pod, Service, Ingress) like:

  • cm-acme-http-solver-xxxxx (Pod)
  • cm-acme-http-solver-xxxxx (Service)
  • cm-acme-http-solver-xxxxx (Ingress with path /.well-known/acme-challenge/*)

If in‑cluster DNS cannot resolve ${APP_HOST} yet, HTTP‑01 self‑check may fail (e.g., no such host). Once delegation is confirmed and external‑dns creates A/AAAA records to the ELB, validation succeeds and the certificate becomes Ready=True.

Validation examples:

# Public resolution and trace
dig +short NS ${SUBZONE} @1.1.1.1

# A/AAAA for the app host
dig +short A ${APP_HOST} @1.1.1.1
dig +short AAAA ${APP_HOST} @1.1.1.1

# Check ACME challenge URL (during validation)
curl -v http://${APP_HOST}/.well-known/acme-challenge/<TOKEN>

# Final TLS check
kubectl -n intel get certificate intel-tls
# → READY=True, secret populated

11 Post‑Issuance Status

  • Certificate/intel-tlsReady=True (Let’s Encrypt)
  • Ingress exposes 80/443; TLS terminates using intel-tls
  • Public probe:
curl -I https://${APP_HOST}

During rollout or propagation, short‑lived 503 responses may appear.


12 Operations Runbook

Health & Status

# Ingress / Controller
kubectl -n ingress-nginx get pods,svc
kubectl -n ingress-nginx logs deploy/ingress-nginx-controller --tail=200

# cert-manager
kubectl -n cert-manager get pods,clusterissuer,certificate,order,challenge
kubectl -n cert-manager logs deploy/cert-manager --tail=200

# external-dns
kubectl -n kube-system logs deploy/external-dns --tail=200

# App & data
kubectl -n intel get deploy,svc,pod
kubectl -n intel logs deploy/intel-server --tail=200

DNS Checks

aws route53 list-hosted-zones-by-name --dns-name ${SUBZONE}
aws route53 list-resource-record-sets --hosted-zone-id <ZONE_ID> \
--query "ResourceRecordSets[?starts_with(Name, '${APP_HOST}.') || Type=='NS']"

dig +trace ${APP_HOST}

Certificate Renewal

Let’s Encrypt certificates auto‑renew via cert‑manager before expiry. Monitor Order/Challenge resources and cert‑manager logs for failures.

Rolling the Application

helm upgrade intel ./charts/intel -n intel --set image.tag=<new>
kubectl -n intel rollout status deploy/intel-server

13 Troubleshooting Notes

HTTP‑01 self‑check fails (no such host)

  • Confirm sub‑zone delegation from parent DNS to Route 53
  • Ensure external‑dns created A/AAAA for ${APP_HOST} to the ELB
  • Wait for propagation; verify with dig on public resolvers

503 from Ingress

  • Check service endpoints:
    kubectl -n intel get endpoints intel-server -o wide
  • Verify at least one backend Pod is Ready and the port mapping matches (Service :1080 → container http)
  • Inspect NGINX events and logs for upstream errors

external‑dns not updating

  • Verify flags: --domain-filter, --aws-zone-type=public, --policy=upsert-only, --txt-owner-id
  • Confirm IAM permissions allow ChangeResourceRecordSets on the hosted zone

CoreDNS cannot resolve external host

  • Inspect Corefile (forward to node resolv.conf is typical)
  • If using custom VPC DNS, ensure egress to public resolvers is allowed

14 Useful Commands

# Inspect ACME solver resources
kubectl -n intel get ing,svc,pod -l acme.cert-manager.io/http01-solver=true -o wide

# Watch ACME lifecycle
kubectl -n intel get challenge,order
kubectl -n intel describe challenge <name>
kubectl -n intel describe order <name>

# ELB address from Ingress
kubectl -n intel get ingress intel-server -o wide

# Public curl checks
curl -I https://${APP_HOST}
curl -v http://${APP_HOST}/.well-known/acme-challenge/<TOKEN>

15 Final State (Expected)

  • Public Route 53 zone ${SUBZONE} — Zone ID recorded
  • NS for the sub‑zone delegated from parent DNS → Route 53 (four AWS NS values)
  • external‑dns created:
    • A/AAAA for ${APP_HOST} (alias to ELB)
    • Ownership TXT records (for the host)
  • cert‑manager Certificate intel-tlsReady=True (Let’s Encrypt)
  • Ingress intel-server (class nginx) exposing 80/443 for host ${APP_HOST}
  • Service intel-server (ClusterIP:1080) with healthy endpoints
  • Application reachable via HTTPS at ${APP_HOST}

Notes on Production Hardening

  • Size node groups for expected workload; consider multi‑AZ and PodDisruptionBudgets
  • Define resource requests/limits; configure HPAs where applicable
  • Use IRSA for controller/service IAM; lock down IAM and security groups
  • Configure persistent storage and backups for Cassandra; plan for replication/availability
  • Add observability (CloudWatch, Prometheus/Grafana), log aggregation, and alerts