Hepapi Blog - hepapi.com

AWS Istanbul Local Zone ile KVKK Uyumlu Cloud Mimarisi - Bölüm 3: Node'lar ve Servisler

Written by Berat Uyanık | Jun 4, 2026 7:43:55 AM

Bu blog, AWS Istanbul Local Zone ile KVKK Uyumlu Cloud Mimarisi - Bölüm 2: Temel Altyapı'nın devamıdır.

 

Bu yazıda EKS cluster, Istanbul worker node group, Karpenter ile dinamik scaling, EBS CSI driver ve ALB Controller kurulumunu gerçek kod örnekleriyle anlatıyorum.

 

6. EKS Cluster Kurulumu

Control Plane Kısıtlaması

EKS control plane Local Zone subnet'lerini kabul etmez ve minimum 2 farklı standart AZ gerektirir. slice() ile ilk iki subnet seçilir:

# env/hepapi/eu-central-1/prod/eks/eks/terragrunt.hcl
terraform {
  source = "tfr:///terraform-aws-modules/eks/aws//?version=${local.env_vars.locals.module_versions.eks}"

  # apply sonrası kubeconfig otomatik güncellenir
  after_hook "after_hook" {
    commands = ["apply"]
    execute  = [
      "aws", "eks", "update-kubeconfig",
      "--region",  "${include.root.locals.region}",
      "--name",    "${local.env_vars.locals.eks.cluster_name}",
      "--profile", "${local.env_vars.locals.aws_profile}"
    ]
  }
}

inputs = {
  cluster_name    = local.env_vars.locals.eks.cluster_name
  cluster_version = "1.35"

  vpc_id = dependency.vpc.outputs.vpc_id

  # KRITIK: Local Zone control plane'de çalışmaz
  # Sadece index 0 (eu-central-1a) ve 1 (eu-central-1b)
  subnet_ids = slice(dependency.vpc.outputs.private_subnets, 0, 2)

  authentication_mode             = "API"
  cluster_endpoint_private_access = true
  cluster_endpoint_public_access  = true
  cluster_service_ipv4_cidr       = "10.240.0.0/16"
  enable_irsa                     = true

  cluster_addons = {
    coredns = {
      addon_version = "v1.13.2-eksbuild.3"
    }
    eks-pod-identity-agent = {}
    vpc-cni = {
      addon_version = "v1.21.1-eksbuild.1"
      configuration_values = jsonencode({
        env = {
          # Prefix delegation: node başına daha fazla pod IP'si
          ENABLE_PREFIX_DELEGATION = "true"
          WARM_PREFIX_TARGET       = "1"
        }
      })
    }
  }

  # Karpenter node discovery için security group tag'i
  node_security_group_tags = {
    "karpenter.sh/discovery/${local.env_vars.locals.eks.cluster_name}" = local.env_vars.locals.eks.cluster_name
  }
}

 

SSO ile EKS Erişimi

EKS access entry için IAM user ARN değil, SSO permission set role ARN kullanın. Bu yaklaşımla o permission set'e sahip tüm kullanıcılar tek tanımla admin yetkisi alır ve her kullanıcı için ayrı access entry yazmaya gerek kalmaz.

SSO role ARN'ını bulun:

aws iam list-roles --profile hepapi-sso \
  --query 'Roles[?contains(RoleName, `AWSReservedSSO`)].{Name:RoleName,Arn:Arn}' \
  --output table
# env.hcl — access_entries
access_entries = {
  hepapi = {
    # Tüm SSO AdministratorAccess kullanıcıları bu role ile gelir
    principal_arn = "arn:aws:iam::xxxxxxxxxxxxx:role/aws-reserved/sso.amazonaws.com/AWSReservedSSO_AdministratorAccess_xxxxxxxxxxxxxxxx"
    policy_associations = {
      policy = {
        policy_arn   = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy"
        access_scope = { type = "cluster" }
      }
    }
  }
}

 

7. Istanbul Worker Node Group

Node group yalnızca private_subnets[2] (Istanbul) subnet'ine kurulur:

# env/hepapi/eu-central-1/prod/eks/node-group/terragrunt.hcl
inputs = {
  name            = "hepapi-istanbul-node-group"
  cluster_name    = dependency.eks.outputs.cluster_name
  cluster_version = dependency.eks.outputs.cluster_version

  # Sadece Istanbul Local Zone subnet (index 2)
  subnet_ids = slice(dependency.vpc.outputs.private_subnets, 2, 3)

  instance_types = ["m7i.xlarge"]
  capacity_type  = "ON_DEMAND"  # Local Zone'da Spot desteklenmez

  min_size     = 1
  max_size     = 2
  desired_size = 1

  block_device_mappings = {
    xvda = {
      device_name = "/dev/xvda"
      ebs = {
        volume_size = 80
        volume_type = "gp3"
      }
    }
  }

  # SSM ile SSH yerine güvenli node erişimi
  iam_role_additional_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }

  # Workload scheduling için node label'ları
  # Bu label'lar Deployment nodeSelector'da kullanılır
  labels = {
    topology = "local-zone"
    zone     = "istanbul"
  }
}

Node'un Istanbul'da çalıştığını doğrulayın:

kubectl get nodes -o custom-columns='NAME:.metadata.name,ZONE:.metadata.labels.topology\.kubernetes\.io/zone'

 

8. Karpenter ile Dinamik Scaling

Static node group yetmediğinde Karpenter devreye girer. Istanbul için özel yapılandırma gerektiriyor - yanlış yapılandırılırsa Karpenter Frankfurt'ta node açar, Istanbul'da değil.

Karpenter IAM Modülü

# env/hepapi/eu-central-1/prod/eks/karpenter/karpenter-module/terragrunt.hcl
terraform {
  source = "tfr:///terraform-aws-modules/eks/aws//modules/karpenter//?version=20.33.1"
}

inputs = {
  cluster_name                    = dependency.eks.outputs.cluster_name
  enable_v1_permissions           = true
  enable_irsa                     = true
  enable_pod_identity             = true
  create_pod_identity_association = true

  node_iam_role_additional_policies = {
    AmazonSSMManagedInstanceCore = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
  }
}

 

Karpenter Helm Chart

# env/hepapi/eu-central-1/prod/eks/karpenter/karpenter-controller-helm/terragrunt.hcl
inputs = {
  name          = "karpenter"
  namespace     = "kube-system"
  chart_version = "1.1.2"
  helm_repo_url = "oci://public.ecr.aws/karpenter"

  sets = [
    { name  = "serviceAccount.name",
      value = dependency.karpenter-module.outputs.service_account },
    { name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn",
      value = dependency.karpenter-module.outputs.iam_role_arn },
    { name  = "settings.interruptionQueue",
      value = dependency.karpenter-module.outputs.queue_name },
    { name  = "settings.clusterName",
      value = dependency.eks.outputs.cluster_name },
    { name  = "settings.clusterEndpoint",
      value = dependency.eks.outputs.cluster_endpoint },
    { name  = "replicas", value = "1" }
  ]
}

 

NodePool - Istanbul'a Özgü Yapılandırma

Bu NodePool tanımında iki kritik requirement var:

  • topology.kubernetes.io/zone: eu-central-1-ist-1a → Sadece Istanbul'da node aç
  • instance-generation Gt 6 → Sadece 7. nesil seç (c7i/m7i/r7i) — 6. nesil desteklenmiyor
# modules/karpenter/karpenter_node_pool.tf
resource "kubectl_manifest" "karpenter_node_pool" {
  yaml_body = <<-YAML
    apiVersion: karpenter.sh/v1
    kind: NodePool
    metadata:
      name: hepapi-istanbul
      labels:
        capacity-type: on-demand
    spec:
      template:
        metadata:
          labels:
            capacity-type: on-demand
            topology: local-zone
            zone: istanbul
        spec:
          nodeClassRef:
            group: karpenter.k8s.aws
            kind: EC2NodeClass
            name: default
          requirements:
            - key: "karpenter.k8s.aws/instance-category"
              operator: In
              values: ["c", "m", "r"]
            - key: "karpenter.k8s.aws/instance-generation"
              operator: Gt
              values: ["6"]           # 7. nesil ve üzeri: c7i, m7i, r7i
            - key: "kubernetes.io/arch"
              operator: In
              values: ["amd64"]
            - key: "karpenter.sh/capacity-type"
              operator: In
              values: ["on-demand"]   # Spot yok!
            - key: "topology.kubernetes.io/zone"
              operator: In
              values: ["eu-central-1-ist-1a"]  # Sadece Istanbul
      limits:
        cpu: 50
      disruption:
        consolidationPolicy: WhenEmptyOrUnderutilized
        consolidateAfter: 60s
  YAML
}

Karpenter'ın doğru çalıştığını doğrulayın:

# Karpenter pod'u çalışıyor mu?
kubectl get pods -n kube-system -l app.kubernetes.io/name=karpenter

# NodePool durumu
kubectl get nodepool hepapi-istanbul

# Karpenter tarafından açılan node'lar
kubectl get nodes -l karpenter.sh/nodepool=hepapi-istanbul

Pod'ları Istanbul node'larına yönlendirmek için nodeSelector ekleyin:

spec:
  nodeSelector:
    topology: local-zone
    zone: istanbul

 

9. EBS CSI Driver - Zone-Aware Storage

Local Zone'da storage kurulumunda yapılan en yaygın hata: StorageClass'a hardcoded zone topology eklemek.

Yanlış yaklaşım:

# BUNU YAPMAYIN — pod başka node'a taşınırsa volume erişilemez
volumeBindingMode: Immediate
allowedTopologies:
  - matchLabelExpressions:
    - key: topology.kubernetes.io/zone
      values: ["eu-central-1-ist-1a"]

Doğru yaklaşım: WaitForFirstConsumer - volume, pod'un schedule edildiği node'un zone'unda oluşturulur. Zone değişirse volume de değişir.

# modules/ebs-csi/main.tf
resource "helm_release" "aws_ebs_csi_driver" {
  name       = "aws-ebs-csi-driver"
  namespace  = "kube-system"
  repository = "https://kubernetes-sigs.github.io/aws-ebs-csi-driver/"
  chart      = "aws-ebs-csi-driver"
  version    = var.chart_version

  set = [
    { name  = "controller.serviceAccount.create", value = "true" },
    { name  = "controller.serviceAccount.name",   value = "ebs-csi-controller-sa" },
    { name  = "controller.serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn",
      value = var.ebs_csi_role_arn },
  ]
}

resource "kubectl_manifest" "storageclass" {
  yaml_body = <<-YAML
    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: ${var.storageclassname}
    provisioner: ebs.csi.aws.com
    parameters:
      tagSpecification_1: "Name=-"
      tagSpecification_2: "Namespace="
    allowVolumeExpansion: true
    volumeBindingMode: WaitForFirstConsumer  # Pod'u takip et, zone hardcode etme
  YAML
}

 

10. AWS Load Balancer Controller

ALB'yi Istanbul Local Zone'da oluşturmak için alb.ingress.kubernetes.io/subnets annotation'ına Local Zone subnet adını vermek yeterli.

IAM IRSA Role

# modules/aws-alb-controller-role/main.tf
module "lb_role" {
  source  = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
  version = "~> 5.52.0"  # v6 dizin yapısını değiştirdi, pinlemek şart

  role_name                              = "eks-lb-controller-${var.cluster_name}"
  attach_load_balancer_controller_policy = true

  oidc_providers = {
    main = {
      provider_arn               = var.oidc_provider_arn
      namespace_service_accounts = ["kube-system:aws-load-balancer-controller"]
    }
  }
}

resource "kubernetes_service_account" "service-account" {
  metadata {
    name      = "aws-load-balancer-controller"
    namespace = "kube-system"
    annotations = {
      "eks.amazonaws.com/role-arn"               = module.lb_role.iam_role_arn
      "eks.amazonaws.com/sts-regional-endpoints" = "true"
    }
  }
}

 

Helm Chart

# env/hepapi/eu-central-1/prod/eks/alb-controller/aws-alb-controller/terragrunt.hcl
inputs = {
  name          = "aws-load-balancer-controller"
  namespace     = "kube-system"
  chart_version = "1.11.0"
  helm_repo_url = "https://aws.github.io/eks-charts"

  sets = [
    { name = "serviceAccount.create", value = "false" },
    { name = "serviceAccount.name",   value = "aws-load-balancer-controller" },
    { name = "clusterName",           value = dependency.eks.outputs.cluster_name },
    { name = "vpcId",                 value = dependency.vpc.outputs.vpc_id },
    # eu-central-1 ECR mirror (us-east-1 değil!)
    { name = "image.repository",
      value = "602401143452.dkr.ecr.eu-central-1.amazonaws.com/amazon/aws-load-balancer-controller" }
  ]
}

 

Ingress Örneği - Local Zone İçin Annotation'lar

ALB'yi Istanbul Local Zone'da oluşturmak için tek yapmanız gereken alb.ingress.kubernetes.io/subnets annotation'ına Local Zone subnet adını vermek. AWS Load Balancer Controller bu subnet'i okuyarak ALB'yi doğrudan Istanbul'da açar.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app
  namespace: default
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
    # ZORUNLU: pod IP'lerine doğrudan yönlendirme için IP modunu kullan
    alb.ingress.kubernetes.io/target-type: ip
    # ALB'yi Istanbul Local Zone subnet'inde oluştur
    alb.ingress.kubernetes.io/subnets: eu-central-1-ist-1a
    alb.ingress.kubernetes.io/healthcheck-path: /health
spec:
  ingressClassName: alb
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: my-app-service
                port:
                  number: 80

 

Önceki: Bölüm 2: Temel Altyapı   

Serinin devamı → Bölüm 4: Production'a Taşımak - Sorunlar, Maliyet ve Best Practices için takipte kalın.