Hearth is the infrastructure home for the letemcook ecosystem. Ported from coherence-mcp/infra: - Terraform modules (VPC, EKS, IAM, NLB, S3, storage) - Kubernetes manifests (Forgejo, ingress, cert-manager, karpenter) - Deployment scripts (phased rollout) Status: Not deployed. EKS cluster needs to be provisioned. Next steps: 1. Bootstrap terraform backend 2. Deploy phase 1 (foundation) 3. Deploy phase 2 (core services including Forgejo) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
398 lines
11 KiB
HCL
398 lines
11 KiB
HCL
# EKS Module - Kubernetes Cluster with Karpenter
|
|
# RFC 0039: ADR-Compliant Foundation Infrastructure
|
|
# ADR 0004: "Set It and Forget It" Auto-Scaling Architecture
|
|
#
|
|
# Architecture:
|
|
# - EKS control plane (~$73/mo)
|
|
# - Fargate profile for Karpenter (chicken-egg problem)
|
|
# - Karpenter for auto-scaling nodes
|
|
# - No managed node groups (Karpenter handles all compute)
|
|
# - FIPS-compliant node configuration
|
|
|
|
locals {
|
|
cluster_name = var.cluster_name
|
|
}
|
|
|
|
# EKS Cluster
|
|
resource "aws_eks_cluster" "main" {
|
|
name = local.cluster_name
|
|
version = var.cluster_version
|
|
role_arn = aws_iam_role.cluster.arn
|
|
|
|
vpc_config {
|
|
subnet_ids = concat(var.private_subnet_ids, var.public_subnet_ids)
|
|
endpoint_private_access = true
|
|
endpoint_public_access = var.enable_public_access
|
|
public_access_cidrs = var.public_access_cidrs
|
|
security_group_ids = [aws_security_group.cluster.id]
|
|
}
|
|
|
|
# Enable all log types for security compliance
|
|
enabled_cluster_log_types = [
|
|
"api",
|
|
"audit",
|
|
"authenticator",
|
|
"controllerManager",
|
|
"scheduler"
|
|
]
|
|
|
|
# Encryption configuration for secrets
|
|
encryption_config {
|
|
provider {
|
|
key_arn = aws_kms_key.eks.arn
|
|
}
|
|
resources = ["secrets"]
|
|
}
|
|
|
|
tags = merge(var.tags, {
|
|
Name = local.cluster_name
|
|
})
|
|
|
|
depends_on = [
|
|
aws_iam_role_policy_attachment.cluster_AmazonEKSClusterPolicy,
|
|
aws_iam_role_policy_attachment.cluster_AmazonEKSVPCResourceController,
|
|
aws_cloudwatch_log_group.cluster,
|
|
]
|
|
}
|
|
|
|
# CloudWatch Log Group for EKS
|
|
resource "aws_cloudwatch_log_group" "cluster" {
|
|
name = "/aws/eks/${local.cluster_name}/cluster"
|
|
retention_in_days = 30
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# KMS Key for EKS secrets encryption
|
|
resource "aws_kms_key" "eks" {
|
|
description = "EKS cluster secrets encryption key"
|
|
deletion_window_in_days = 7
|
|
enable_key_rotation = true
|
|
|
|
tags = merge(var.tags, {
|
|
Name = "${local.cluster_name}-eks-secrets"
|
|
})
|
|
}
|
|
|
|
resource "aws_kms_alias" "eks" {
|
|
name = "alias/${local.cluster_name}-eks-secrets"
|
|
target_key_id = aws_kms_key.eks.key_id
|
|
}
|
|
|
|
# EKS Cluster IAM Role
|
|
resource "aws_iam_role" "cluster" {
|
|
name = "${local.cluster_name}-cluster"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = "sts:AssumeRole"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "eks.amazonaws.com"
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSClusterPolicy" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
|
|
role = aws_iam_role.cluster.name
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "cluster_AmazonEKSVPCResourceController" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
|
|
role = aws_iam_role.cluster.name
|
|
}
|
|
|
|
# Cluster Security Group
|
|
resource "aws_security_group" "cluster" {
|
|
name = "${local.cluster_name}-cluster"
|
|
description = "EKS cluster security group"
|
|
vpc_id = var.vpc_id
|
|
|
|
tags = merge(var.tags, {
|
|
Name = "${local.cluster_name}-cluster"
|
|
"kubernetes.io/cluster/${local.cluster_name}" = "owned"
|
|
})
|
|
}
|
|
|
|
resource "aws_security_group_rule" "cluster_egress" {
|
|
type = "egress"
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
security_group_id = aws_security_group.cluster.id
|
|
description = "Allow all outbound traffic"
|
|
}
|
|
|
|
# Node Security Group (for Karpenter-managed nodes)
|
|
resource "aws_security_group" "node" {
|
|
name = "${local.cluster_name}-node"
|
|
description = "Security group for EKS nodes"
|
|
vpc_id = var.vpc_id
|
|
|
|
tags = merge(var.tags, {
|
|
Name = "${local.cluster_name}-node"
|
|
"kubernetes.io/cluster/${local.cluster_name}" = "owned"
|
|
"karpenter.sh/discovery" = local.cluster_name
|
|
})
|
|
}
|
|
|
|
resource "aws_security_group_rule" "node_egress" {
|
|
type = "egress"
|
|
from_port = 0
|
|
to_port = 0
|
|
protocol = "-1"
|
|
cidr_blocks = ["0.0.0.0/0"]
|
|
security_group_id = aws_security_group.node.id
|
|
description = "Allow all outbound traffic"
|
|
}
|
|
|
|
resource "aws_security_group_rule" "node_ingress_self" {
|
|
type = "ingress"
|
|
from_port = 0
|
|
to_port = 65535
|
|
protocol = "-1"
|
|
source_security_group_id = aws_security_group.node.id
|
|
security_group_id = aws_security_group.node.id
|
|
description = "Allow nodes to communicate with each other"
|
|
}
|
|
|
|
resource "aws_security_group_rule" "node_ingress_cluster" {
|
|
type = "ingress"
|
|
from_port = 443
|
|
to_port = 443
|
|
protocol = "tcp"
|
|
source_security_group_id = aws_security_group.cluster.id
|
|
security_group_id = aws_security_group.node.id
|
|
description = "Allow cluster API to communicate with nodes"
|
|
}
|
|
|
|
resource "aws_security_group_rule" "node_ingress_cluster_kubelet" {
|
|
type = "ingress"
|
|
from_port = 10250
|
|
to_port = 10250
|
|
protocol = "tcp"
|
|
source_security_group_id = aws_security_group.cluster.id
|
|
security_group_id = aws_security_group.node.id
|
|
description = "Allow cluster API to communicate with kubelet"
|
|
}
|
|
|
|
resource "aws_security_group_rule" "cluster_ingress_node" {
|
|
type = "ingress"
|
|
from_port = 443
|
|
to_port = 443
|
|
protocol = "tcp"
|
|
source_security_group_id = aws_security_group.node.id
|
|
security_group_id = aws_security_group.cluster.id
|
|
description = "Allow nodes to communicate with cluster API"
|
|
}
|
|
|
|
# Fargate Profile for Karpenter
|
|
# Karpenter needs to run on Fargate to avoid chicken-egg problem
|
|
resource "aws_eks_fargate_profile" "karpenter" {
|
|
count = var.enable_karpenter ? 1 : 0
|
|
|
|
cluster_name = aws_eks_cluster.main.name
|
|
fargate_profile_name = "karpenter"
|
|
pod_execution_role_arn = aws_iam_role.fargate.arn
|
|
|
|
subnet_ids = var.private_subnet_ids
|
|
|
|
selector {
|
|
namespace = "karpenter"
|
|
}
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# Fargate Profile for kube-system (CoreDNS, etc)
|
|
resource "aws_eks_fargate_profile" "kube_system" {
|
|
cluster_name = aws_eks_cluster.main.name
|
|
fargate_profile_name = "kube-system"
|
|
pod_execution_role_arn = aws_iam_role.fargate.arn
|
|
|
|
subnet_ids = var.private_subnet_ids
|
|
|
|
selector {
|
|
namespace = "kube-system"
|
|
}
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# Fargate IAM Role
|
|
resource "aws_iam_role" "fargate" {
|
|
name = "${local.cluster_name}-fargate"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = "sts:AssumeRole"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "eks-fargate-pods.amazonaws.com"
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "fargate_AmazonEKSFargatePodExecutionRolePolicy" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSFargatePodExecutionRolePolicy"
|
|
role = aws_iam_role.fargate.name
|
|
}
|
|
|
|
# OIDC Provider for IRSA
|
|
data "tls_certificate" "cluster" {
|
|
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
|
|
}
|
|
|
|
resource "aws_iam_openid_connect_provider" "cluster" {
|
|
client_id_list = ["sts.amazonaws.com"]
|
|
thumbprint_list = [data.tls_certificate.cluster.certificates[0].sha1_fingerprint]
|
|
url = aws_eks_cluster.main.identity[0].oidc[0].issuer
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# Node IAM Role (for Karpenter-managed nodes)
|
|
resource "aws_iam_role" "node" {
|
|
name = "${local.cluster_name}-node"
|
|
|
|
assume_role_policy = jsonencode({
|
|
Version = "2012-10-17"
|
|
Statement = [
|
|
{
|
|
Action = "sts:AssumeRole"
|
|
Effect = "Allow"
|
|
Principal = {
|
|
Service = "ec2.amazonaws.com"
|
|
}
|
|
}
|
|
]
|
|
})
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "node_AmazonEKSWorkerNodePolicy" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
|
|
role = aws_iam_role.node.name
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "node_AmazonEKS_CNI_Policy" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
|
|
role = aws_iam_role.node.name
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "node_AmazonEC2ContainerRegistryReadOnly" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
|
|
role = aws_iam_role.node.name
|
|
}
|
|
|
|
resource "aws_iam_role_policy_attachment" "node_AmazonSSMManagedInstanceCore" {
|
|
policy_arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
|
|
role = aws_iam_role.node.name
|
|
}
|
|
|
|
resource "aws_iam_instance_profile" "node" {
|
|
name = "${local.cluster_name}-node"
|
|
role = aws_iam_role.node.name
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# EKS Addons
|
|
resource "aws_eks_addon" "vpc_cni" {
|
|
cluster_name = aws_eks_cluster.main.name
|
|
addon_name = "vpc-cni"
|
|
addon_version = var.vpc_cni_version
|
|
resolve_conflicts_on_create = "OVERWRITE"
|
|
resolve_conflicts_on_update = "OVERWRITE"
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_eks_addon" "coredns" {
|
|
cluster_name = aws_eks_cluster.main.name
|
|
addon_name = "coredns"
|
|
addon_version = var.coredns_version
|
|
resolve_conflicts_on_create = "OVERWRITE"
|
|
resolve_conflicts_on_update = "OVERWRITE"
|
|
|
|
# CoreDNS needs compute to run
|
|
depends_on = [aws_eks_fargate_profile.kube_system]
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_eks_addon" "kube_proxy" {
|
|
cluster_name = aws_eks_cluster.main.name
|
|
addon_name = "kube-proxy"
|
|
addon_version = var.kube_proxy_version
|
|
resolve_conflicts_on_create = "OVERWRITE"
|
|
resolve_conflicts_on_update = "OVERWRITE"
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
resource "aws_eks_addon" "ebs_csi_driver" {
|
|
cluster_name = aws_eks_cluster.main.name
|
|
addon_name = "aws-ebs-csi-driver"
|
|
addon_version = var.ebs_csi_version
|
|
service_account_role_arn = var.ebs_csi_role_arn
|
|
resolve_conflicts_on_create = "OVERWRITE"
|
|
resolve_conflicts_on_update = "OVERWRITE"
|
|
|
|
tags = var.tags
|
|
}
|
|
|
|
# aws-auth ConfigMap for Karpenter nodes
|
|
resource "kubernetes_config_map_v1_data" "aws_auth" {
|
|
metadata {
|
|
name = "aws-auth"
|
|
namespace = "kube-system"
|
|
}
|
|
|
|
data = {
|
|
mapRoles = yamlencode([
|
|
{
|
|
rolearn = aws_iam_role.node.arn
|
|
username = "system:node:{{EC2PrivateDNSName}}"
|
|
groups = [
|
|
"system:bootstrappers",
|
|
"system:nodes",
|
|
]
|
|
},
|
|
{
|
|
rolearn = aws_iam_role.fargate.arn
|
|
username = "system:node:{{SessionName}}"
|
|
groups = [
|
|
"system:bootstrappers",
|
|
"system:nodes",
|
|
"system:node-proxier",
|
|
]
|
|
}
|
|
])
|
|
}
|
|
|
|
force = true
|
|
|
|
depends_on = [aws_eks_cluster.main]
|
|
}
|
|
|
|
# Data source for current region
|
|
data "aws_region" "current" {}
|
|
|
|
# Data source for current account
|
|
data "aws_caller_identity" "current" {}
|