Subchapter 28.22
references/phases/generate/generate-eks.mdMarkdown13 KBView on GitHub
Conditional generation fragment. Fires (gate in its prose) only when
aws-design.jsoncontainsaws_service: "EKS". When active, it produces the EKS Terraform + kubernetes/ manifests; otherwise the Fargate generation path applies and this fragment is skipped.
Generate EKS cluster Terraform:
Node group selection:
design.eks_cluster.node_group_type == "managed" → emit ONLY the aws_eks_node_group resource block below. Do NOT emit the self-managed resources.design.eks_cluster.node_group_type == "self-managed" → emit ONLY the aws_launch_template + aws_autoscaling_group + aws_security_group blocks below. Do NOT emit aws_eks_node_group.# EKS Cluster
resource "aws_eks_cluster" "main" {
name = "<cluster_name>"
role_arn = aws_iam_role.eks_cluster.arn
version = "<kubernetes_version>"
vpc_config {
subnet_ids = [<subnet references from VPC design>]
security_group_ids = [aws_security_group.eks_cluster.id]
}
depends_on
If data stores exist in the design, add security group rules for pod-to-service communication:
Generate Kubernetes manifests:
kubernetes/namespace.yaml (one per unique heroku_app):
apiVersion: v1
kind: Namespace
metadata:
name: <heroku-app-name>
labels:
app.kubernetes.io/managed-by: heroku-migrationkubernetes/<app>-<process-type>-deployment.yaml (one per formation):
apiVersion: apps/v1
kind: Deployment
metadata:
name: <process-type>
namespace: <heroku-app-name>
labels:
app: <process-type>
app.kubernetes.io/name: <process-type>
app.kubernetes.io/part-of: <heroku-app-name>
spec:
replicas: <quantity>
selector:
matchLabels:
app: <process-type>
template:
metadata:
labels:
app: <process-type>
spec:
containers:
- name: <process-type>
image: <placeholder-image>
resources:
requests:
cpu: "<from aws-design.json: aws_config.resources.requests.cpu>"
memory: "<from aws-design.json: aws_config.resources.requests.memory>"
limits:
cpu: "<from aws-design.json: aws_config.resources.limits.cpu>"
memory: "<from aws-design.json: aws_config.resources.limits.memory>"
env:
- name: PORT
value: "8080" # Heroku injects $PORT dynamically; 8080 is the default here. If your app binds to a different port, update this value and the containerPort/targetPort to match.
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: <heroku-app-name>-config
key: DATABASE_URL
optional: true
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: <heroku-app-name>-config
key: REDIS_URL
optional: true
ports:
- containerPort: 8080 # Matches PORT env var; only for web processeskubernetes/<app>-web-service.yaml (only for web process types):
apiVersion: v1
kind: Service
metadata:
name: web
namespace: <heroku-app-name>
annotations:
service.beta.kubernetes.io/aws-load-balancer-type: "external"
service.beta.kubernetes.io/aws-load-balancer-scheme: "internet-facing"
service.beta.kubernetes.io/aws-load-balancer-target-type: "ip"
alb.ingress.kubernetes.io/target-type: "ip"
spec:
type: LoadBalancer
selector:
app: web
ports:
- port: 80
targetPort: 8080
protocol: TCPAdd these sections after Prerequisites, before Data Migration:
## EKS Cluster Setup
1. Apply EKS Terraform:
```bash
cd terraform/
terraform init
terraform apply
```
2. Configure kubectl access:
```bash
aws eks update-kubeconfig --name heroku-migration-cluster --region <region>
```
3. Verify node group readiness:
```bash
kubectl get nodes
# All nodes should show STATUS: Ready
```
4. Verify AWS Load Balancer Controller:
```bash
kubectl get deployment -n kube-system aws-load-balancer-controller
# Should show AVAILABLE: 1+
```
## Deploy Workloads to EKS
1. Create namespace:
```bash
kubectl apply -f kubernetes/namespace.yaml
```
2. Deploy all workloads:
```bash
kubectl apply -f kubernetes/
```
3. Verify pods are running:
```bash
kubectl get pods -n <namespace>
# All pods should show STATUS: Running
```
4. Verify load balancer (web services):
```bash
kubectl get svc -n <namespace>
# EXTERNAL-IP should be provisioned within 2–5 minutes
```
## Configure Pod-to-Service Access
> Include this section ONLY when EKS services coexist with data stores (RDS, ElastiCache, MSK).
1. **IAM Roles for Service Accounts (IRSA):**
```bash
# The OIDC provider was created by Terraform. Create a service account:
kubectl create serviceaccount <app>-sa -n <namespace>
kubectl annotate serviceaccount <app>-sa -n <namespace> \
eks.amazonaws.com/role-arn=arn:aws:iam::<account>:role/<app>-pod-role
```
2. **Verify security group rules** (created by Terraform):
- Pods → RDS on port 5432
- Pods → ElastiCache on port 6379
- Pods → MSK on port 9092
3. **Store connection strings in Kubernetes Secrets:**
```bash
kubectl create secret generic db-credentials -n <namespace> \
--from-literal=DATABASE_URL='postgres://user:pass@rds-endpoint:5432/db'
```
4. **Reference secrets in Deployments** (update container env):
```yaml
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-credentials
key: DATABASE_URL
```Omit all EKS sections when the design contains only Fargate-only compute (no EKS services).
Include this note at the end of the “Deploy Workloads to EKS” section:
> **Recommended next steps (not auto-generated):**
>
> - Add **liveness and readiness probes** to each Deployment. Heroku performs health checks automatically; Kubernetes requires explicit probe configuration for reliable restarts and traffic routing.
> - Consider adding a **Horizontal Pod Autoscaler (HPA)** if your workloads need dynamic scaling. The generated manifests use fixed `replicas` matching your Heroku formation quantity. HPA can replace or supplement this for traffic-driven scaling.
> - Review **resource limits** — the generated limits allow CPU bursting (2× request). Tune after observing actual usage in production.After generating eks.tf, the combined Terraform in terraform/ must pass terraform validate. If validation fails, log the error to generation-warnings.json and continue.
When eks.tf is generated, add the Helm provider to main.tf:
terraform {
required_providers {
helm = {
source = "hashicorp/helm"
version = "~> 2.12"
}
}
}
provider "helm" {
kubernetes {
host = aws_eks_cluster.main.endpoint
cluster_ca_certificate = base64decode(aws_eks_cluster.main.certificate_authority[0].data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
args = ["eks", "get-token", "--cluster-name", aws_eks_cluster.main.name]
command = "aws"
}
}
}