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
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.com→ A/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)
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-tls→ Ready=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
digon 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
ChangeResourceRecordSetson the hosted zone
CoreDNS cannot resolve external host
- Inspect Corefile (forward to node
resolv.confis 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)
- A/AAAA for
- cert‑manager Certificate
intel-tls→ Ready=True (Let’s Encrypt) - Ingress
intel-server(classnginx) 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