'

ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [project] AWS EKS IAC with Terraform
    FISA 2026. 5. 28. 12:36

     

    AWS 네트워크 설계: VPC와 서브넷

    AWS에서 네트워크의 시작은 VPC입니다.

    Virtual Private Cloud의 약자로, AWS 안에 만드는 나만의 가상 네트워크입니다.

     

    VPC 셋팅 시, CIDR(가용IP대역)을 설정해야 하는데, 10.0.0.0/24로 잡았다가 문제가 생겼습니다.

    IP를 256개만 쓸 수 있는데, EKS는 Pod마다 VPC IP를 하나씩 할당하기때문에 적어서 문제가 생긴다고 합니다.

    그래서 삭제하고 10.0.0.0/16으로 다시 생성 해 주었습니다

    서브넷은 public 2개 private 3개로 구성 하였습니다. 

    public Subnet에는 ALB, NAT Gateway, Bastion이 들어가고,

    Private Subnet에는 EKS 워커노드, RDS, RedisSentinel이 들어갑니다. private subnet을 3개로 잡은 이유는 EKS가 워커노드를 배포할 때 AZ를 3개를 요구합니다. ETCD?  무슨 투표방식 그것땜에 그런 것 같아요.

     

    NAT Gateway는 private 서브넷의 Pod들이 카카오 OAuth나 공공데이터 API같은 외부 인터넷을 호출할 때 필요 합니다.

     

     

     

     

     


    WireGuard로 온프렘-AWS 연결하기

    alarm 서비스는 온프렘에 있고 AWS의 서비스들과 SQS 메시지를 주고받아야 합니다. 온프렘과 AWS 사이의 통신을 위해 WireGuard VPN 터널을 구성했습니다.

     

    처음에 구성 중 헷갈렸던 부분이, 온프렘 pfsense의 wanIP가 172.21.x.x대역인데, 이게 사설 IP입니다.

    공유기와 인터넷이되어서 외부 대역인 줄 알았지만 학원에서 사설 IP로 연결해둬서, 인터넷에 직접 연결이 불가능합니다.

    그래서 Bastion에서 pfsense로 연결을 시도했지만 실패하는 이유가

    Bastion은 당연히 pfsense 172대역을 모르기때문입니다. 그래서 학원 공유기에서 포트포워딩을 해주면 되는데, 학원 공유기를 건들 수는 없기 때문에...

     

    pfsense의 peer설정에서 dynamic endpoint를 해제하고, endpoint를 bastion 공인 ip로 지정했습니다. 

    그래서 pfsense쪽에서 먼저 ping test로 패킷을 보내면, 공인 ip인 Bastion쪽에선 당연히 이쪽을 인식하게되고, 서로 소통이 가능해 졌습니다

     

     

     

     


    Terraform이 뭐고 왜 쓰는가

    인프라를 AWS 콘솔에서 클릭클릭으로 만들다 보면 한 가지 문제가 생깁니다. 내가 뭘 만들었는지, 어떤 설정을 했는지 기록이 남지 않기 때문이죠. 팀원이 동일한 환경을 만들려면 처음부터 다시 클릭해야 하고, 실수로 뭔가 지워도 복구가 까다롭습니다.

    Terraform은 이 문제를 코드로 해결합니다. 인프라 상태를 HCL이라는 언어로 선언하면 Terraform이 현재 상태와 비교해서 뭘 만들고 수정할 지 계산해줍니다. 삭제도 terraform destory 한 번이면 만들었던 리소스를 전부 지울 수 있어서 비용관리도 편합니다.

    Terraform의 핵심 명령어는 세 가지입니다.

    terraform init은 필요한 플러그인(provider)을 다운로드하는 준비 단계

    terraform plan은 코드대로 실행하면 어떤 리소스가 생성·수정·삭제될지 미리 보여주는 시뮬레이션

    terraform apply가 되어야 AWS에 리소스가 생성

     

     

    그럼 이 terraform과 외부 서비스는 어떻게 연결하고 어떻게 서로 인식하지?

    우선 terraform을 깔아야합니다. 그리고 코드단에서, provider(aws,azure,kubernetes,google, .... )를 지정해주면 통신이 됩니다. 결론은 provider는 통신 플러그인이라고 할 수 있습니다.

     

     

     

     

     


    [ code ][ Main.tf ] terraform의 기본셋팅

    # main.tf
    # =============================================
    # Terraform 기본 설정
    # provider = Terraform이 특정 기술 스택과 통신할 때 필요한 플러그인
    #
    # 단계별 구성:
    # 1단계 (현재): aws provider → EKS 클러스터 + 워커노드 EC2 생성
    # 2단계 (EKS 완성 후): kubernetes/helm provider 주석 해제 → Istio, Ingress 설치
    # 3단계 (Jenkins): Terraform 영역 밖 → Jenkinsfile에서 ECR push + kubectl 배포
    # =============================================
    
    terraform {
      required_version = ">= 1.0" # 1.0이상 = 안정화 버전, 하위호환성 보장
    
      required_providers {
        # -----------------------------------------------
        # 1단계 (현재 활성)
        # AWS API와 연결하여 EKS, IAM, ECR 등 AWS 리소스 생성/관리/삭제
        # ~> 5.0 = 5.x 버전 허용, 6.0은 차단 (breaking change 방지)
        # -----------------------------------------------
        aws = {
          source  = "hashicorp/aws"
          version = "~> 5.0"
        }
    
        # -----------------------------------------------
        # 2단계: EKS 생성 후 주석 해제
        # kubernetes provider: EKS 위에 Namespace, ConfigMap 등 k8s 리소스 배포
        # helm provider: Istio, AWS Load Balancer Controller 등 Helm Chart 설치
        # 주석 해제 후 terraform init 재실행 필요
        # -----------------------------------------------
        # kubernetes = {
        #   source  = "hashicorp/kubernetes"
        #   version = "~> 2.0" # k8s 1.32 (우리 EKS 버전) 완벽 지원
        # }
        # helm = {
        #   source  = "hashicorp/helm"
        #   version = "~> 2.0" # Helm 3.x 지원 (Istio, ingress-nginx 등)
        # }
      }
    }
    
    # -----------------------------------------------
    # 1단계 (현재 활성): AWS Provider 설정
    # aws configure로 설정한 credentials 자동으로 읽어옴
    # -----------------------------------------------
    provider "aws" {
      region = var.region # ap-northeast-2 (서울 리전)
    
      # 실수로 다른 AWS 계정에 apply되는 것 방지
      # 계정 ID 틀리면 terraform apply 자체가 차단됨
      allowed_account_ids = [var.aws_account_id]
    
      # 모든 AWS 리소스에 자동으로 태그 부착
      # - Project: 어떤 프로젝트 소속인지 (비용 추적, 리소스 필터링)
      # - ManagedBy: 콘솔 수동 생성 리소스와 구분 (이 태그 있으면 수동으로 건드리면 안 됨)
      # - Environment: dev/staging/prod 환경 구분
      default_tags {
        tags = {
          Project     = var.project
          ManagedBy   = "terraform"
          Environment = var.environment
        }
      }
    }
    
    # -----------------------------------------------
    # 2단계: Kubernetes Provider (EKS 생성 후 주석 해제)
    # EKS 위에 Namespace, ConfigMap 등 k8s 리소스 배포 시 사용
    # EKS 인증은 AWS IAM 기반 → aws cli로 임시 토큰 동적 발급
    # 하드코딩된 토큰은 15분 후 만료되므로 exec 방식 사용
    # -----------------------------------------------
    # provider "kubernetes" {
    #   host                   = aws_eks_cluster.oka.endpoint
    #   cluster_ca_certificate = base64decode(aws_eks_cluster.oka.certificate_authority[0].data)
    #
    #   exec {
    #     api_version = "client.authentication.k8s.io/v1beta1" # EKS 1.32 지원 안정 버전
    #     args        = ["eks", "get-token", "--cluster-name", aws_eks_cluster.oka.name]
    #     command     = "aws" # aws cli로 임시 토큰 발급
    #   }
    # }
    
    # -----------------------------------------------
    # 2단계: Helm Provider (EKS 생성 후 주석 해제)
    # Istio, AWS Load Balancer Controller 등 Helm Chart 설치 시 사용
    # kubernetes provider와 동일한 EKS 인증 방식 사용
    # -----------------------------------------------
    # provider "helm" {
    #   kubernetes {
    #     host                   = aws_eks_cluster.oka.endpoint
    #     cluster_ca_certificate = base64decode(aws_eks_cluster.oka.certificate_authority[0].data)
    #
    #     exec {
    #       api_version = "client.authentication.k8s.io/v1beta1"
    #       args        = ["eks", "get-token", "--cluster-name", aws_eks_cluster.oka.name]
    #       command     = "aws"
    #     }
    #   }
    # }
    
    # -----------------------------------------------
    # 3단계: 서비스 Pod 배포 (Terraform 영역 밖)
    # Jenkins 파이프라인에서 처리 → Jenkinsfile 참고
    #
    # 흐름:
    # GitHub push → Jenkins 감지
    # → Gradle JAR 빌드
    # → Docker 이미지 빌드
    # → ECR push (oka-jenkins-user IAM 권한 사용)
    # → aws eks update-kubeconfig (EKS 접근)
    # → kubectl apply -f k8s/ (Pod 배포)
    # -----------------------------------------------

     

     main.tf의 코드입니다.

     

    Terraform의 기본 설정을 담당하고 있습니다. 여기서 어떤 provider를 쓸 것인지 지정해줍니다.

    우리는 eks를 쓸거니 당연히 aws와 연결해야하고, kubernetes도 쓸 것이니 kubernetes도 연결해야하고, istio도 쓸 것이니 helm과 istio도 연결해야합니다.

    그러나...

     

    eks를 제외하고선, eks와 붙어야하는 서비스기 때문에 eks가 생성되어있고, eks값이필요합니다.

    그래서 단계별로 나누어서 aws연결과 eks셋팅부터 해줍니다.

     

    required_verison은, terraform버젼을 명시합니다. 현재 최신 버젼이 1.56인가? 1. 중반대 버젼이었기때문에... 0.버젼보단 1.0이상으로 설정해줍니다. 딱 버젼을 명시해야하는게 아니라 >= 이상이다를 허용해서 알아서 착착 잘 다운해주는 것 같습니다.

     

    그리고 required_providers{ }안에 어떤 프로바이더를 쓸 것인지 명시해줍니다.

    hashicorp/aws를 써야 aws를 쓸 수 있는 거 같아요 AWS권장 Terrform가이드  에서 hashipcorp를 쓰고 있으니 따라 쓰면 됩니다.

    버젼같은경우엔 6.대버젼이 최근에 나온걸로 알고있는데, 5버젼이 안정화되어있으니 새로나온 것 보단 5버젼을 쓰도록 합니다.

     

    그리고 provider마다의 설정을 provider "aws"와 같이 해줍니다.

    aws같은경우엔 region, allowed_account_ids, default_tags등을 명시해줘야합니다.

    region이야 당연히 서울 쓸것이고, allowed_account_ids는 저희가 마스터계정에서 팀별로 나누어서 계정ID를 쓰고있기 때문에 다른 계정에 apply되는것을 방지하고자 명시해줬고, default태그로는 저희서비스인 oka , 

     

     

     

     


    [ code ][ IAM.tf ] EKS가 AWS를 쓰려면 권한이 필요하다

    EKS를 만들기 전에 먼저 해야 할 일이 있습니다.

     

    EKS 자체, 그리고 EKS 위에서 돌아가는 EC2 워커노드들이 AWS API를 호출할 수 있도록 IAM 역할을 만들어줘야 합니다. IAM 역할은 "이 서비스가 저 서비스를 사용할 수 있다"는 권한 위임입니다

    iam.tf에서는 크게 세 가지 역할을 만듭니다.

    첫 번째는 EKS 컨트롤 플레인 역할입니다. EKS 클러스터 자체가 ALB를 만들거나, EC2 정보를 조회하거나, CloudWatch에 로그를 보낼 때 이 역할을 사용합니다.

    assume_role_policy에서 eks.amazonaws.com만 이 역할을 사용할 수 있도록 제한합니다

    두 번째는 EKS 워커노드 역할입니다.

    EC2 워커노드가 동작하려면 세 가지 정책이 필요합니다. AmazonEKSWorkerNodePolicy는 노드가 EKS 클러스터에 참여하고, 클러스터 정보를 조회하고, 노드 상태를 업데이트하는 기본 권한입니다. AmazonEKS_CNI_Policy는 Pod마다 VPC IP를 하나씩 할당하기 위한 네트워크 권한인데, 이 정책이 없으면 Pod IP 할당이 안 돼서 Pod가 아예 뜨지 않습니다. AmazonEC2ContainerRegistryReadOnly는 ECR에서 Docker 이미지를 읽어오는 권한입니다. ReadOnly로 제한한 이유는 워커노드는 이미지를 가져오기만 하면 되고, push는 Jenkins에서 별도로 처리하기 때문이다. 최소 권한 원칙입니다.

    세 번째는 Jenkins 전용 IAM 유저입니다. 온프렘 Jenkins가 ECR에 이미지를 push하고 EKS에 배포할 때 사용합니다. terraform apply 후 outputs에서 이 유저의 액세스 키를 확인해서 Jenkins Credentials에 등록해야 합니다.

    force_detach_policies = true는 terraform destroy 시 역할에 정책이 붙어있으면 삭제가 안 되는 에러를 방지합니다. 이 옵션이 있으면 역할 삭제 전에 연결된 정책을 자동으로 먼저 제거해줍니다.

     

    # iam.tf
    # EKS 클러스터, 워커노드, Jenkins CI/CD가 AWS 리소스에 접근하기 위한 IAM 역할 정의
    # EKS는 직접 AWS API를 호출하기 때문에 반드시 IAM 역할이 필요함
    
    # =============================================
    # EKS 컨트롤 플레인 역할
    # EKS 클러스터 자체(마스터 노드)가 AWS API 호출할 때 사용
    # ex) 로드밸런서 생성, EC2 정보 조회, CloudWatch 로그 전송 등
    # =============================================
    resource "aws_iam_role" "eks_cluster_role" {
      name = "oka-eks-cluster-role"
    
      # assume_role_policy = "누가 이 역할을 사용할 수 있냐"를 정의
      assume_role_policy = jsonencode({
        Version = "2012-10-17" # IAM 정책 문법 버전, 항상 이 값으로 고정
        Statement = [{
          Action = "sts:AssumeRole" # 이 역할을 빌려 쓰겠다는 액션
          Effect = "Allow"
          Principal = {
            Service = "eks.amazonaws.com" # EKS 서비스만 이 역할 사용 가능
          }
        }]
      })
    
      # terraform destroy 시 역할에 정책이 붙어있으면 삭제 에러 발생
      # true로 설정하면 역할 삭제 전 연결된 정책 자동으로 먼저 제거
      force_detach_policies = true
    }
    
    # AmazonEKSClusterPolicy: EC2 정보 조회, 로드밸런서 생성/삭제, Auto Scaling 제어, CloudWatch 로그 전송
    resource "aws_iam_role_policy_attachment" "eks_cluster_policy" {
      policy_arn = "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
      role       = aws_iam_role.eks_cluster_role.name
    }
    
    # =============================================
    # EKS 워커노드 역할
    # Worker Node(EC2)가 AWS API 호출할 때 사용
    # ex) ECR에서 이미지 pull, VPC 네트워크 설정, EKS 클러스터 참여 등
    # 컨트롤 플레인과 역할 분리 이유: 최소 권한 원칙(Least Privilege)
    # =============================================
    resource "aws_iam_role" "eks_node_role" {
      name = "oka-eks-node-role"
    
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = {
            Service = "ec2.amazonaws.com" # EC2(워커노드)만 이 역할 사용 가능
          }
        }]
      })
    
      force_detach_policies = true
    }
    
    # 워커노드가 EKS 클러스터에 참여하기 위한 기본 권한 (클러스터 정보 조회, 노드 상태 업데이트)
    resource "aws_iam_role_policy_attachment" "eks_worker_node_policy" {
      policy_arn = "arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy"
      role       = aws_iam_role.eks_node_role.name
    }
    
    # Pod에 VPC IP 할당을 위한 CNI 권한
    # 이 정책 없으면 Pod IP 할당 안 돼서 Pod가 아예 뜨지 않음
    resource "aws_iam_role_policy_attachment" "eks_cni_policy" {
      policy_arn = "arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy"
      role       = aws_iam_role.eks_node_role.name
    }
    
    # ECR에서 도커 이미지 pull 권한
    # ReadOnly인 이유: 워커노드는 이미지를 읽기만 하면 됨
    # 이미지 push는 Jenkins IAM 유저에서 별도 처리
    resource "aws_iam_role_policy_attachment" "eks_ecr_policy" {
      policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
      role       = aws_iam_role.eks_node_role.name
    }
    
    # =============================================
    # Jenkins CI/CD 전용 IAM 유저
    # 온프렘 Jenkins에서 AWS ECR push, EKS 배포할 때 사용
    # 최소 권한 원칙: ECR push + EKS 배포에 필요한 권한만 부여
    # =============================================
    resource "aws_iam_user" "jenkins" {
      name = var.jenkins_user_name
    
      tags = {
        Description = "Jenkins CI/CD 전용 IAM 유저"
      }
    }
    
    # Jenkins 유저용 액세스 키 생성
    # terraform apply 후 outputs에서 확인하여 Jenkins credentials에 등록
    resource "aws_iam_access_key" "jenkins" {
      user = aws_iam_user.jenkins.name
    }
    
    # Jenkins 전용 IAM 정책
    resource "aws_iam_policy" "jenkins_policy" {
      name        = "oka-jenkins-policy"
      description = "Jenkins CI/CD 전용 정책"
    
      policy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          # ECR 관련 권한: 이미지 push/pull, 로그인 토큰 발급
          {
            Effect = "Allow"
            Action = [
              "ecr:GetAuthorizationToken",       # ECR 로그인 토큰 발급
              "ecr:BatchCheckLayerAvailability", # 이미지 레이어 확인
              "ecr:GetDownloadUrlForLayer",      # 이미지 레이어 다운로드
              "ecr:BatchGetImage",               # 이미지 조회
              "ecr:InitiateLayerUpload",         # 이미지 업로드 시작
              "ecr:UploadLayerPart",             # 이미지 레이어 업로드
              "ecr:CompleteLayerUpload",         # 이미지 업로드 완료
              "ecr:PutImage"                     # 이미지 push
            ]
            Resource = "*"
          },
          # EKS 관련 권한: kubeconfig 업데이트, 클러스터 정보 조회
          {
            Effect = "Allow"
            Action = [
              "eks:DescribeCluster", # 클러스터 정보 조회
              "eks:ListClusters"     # 클러스터 목록 조회
            ]
            Resource = "*"
          }
        ]
      })
    }
    
    resource "aws_iam_user_policy_attachment" "jenkins_policy_attachment" {
      user       = aws_iam_user.jenkins.name
      policy_arn = aws_iam_policy.jenkins_policy.arn
    }

     

     

     


    [ code ][ eks.tf ] EKS 클러스터 구성: 컨트롤 플레인과 워커노드

    EKS는 Elastic Kubernetes Service로, AWS가 Kubernetes 컨트롤 플레인을 관리해주는 서비스입니다.

    컨트롤 플레인은 Kubernetes API 서버, etcd, 스케줄러가 실행되는 마스터 영역인데, EKS에서는 AWS가 이 부분을 관리해주므로 EC2 콘솔에서 보이지 않습니다. 우리가 관리하는 건 워커노드, 즉 실제 Pod가 올라가는 EC2들입니다.

     

    eks.tf의 aws_eks_cluster에서 컨트롤 플레인을 정의합니다.

    endpoint_private_access = true는 VPC 내부에서 EKS API 서버에 접근할 수 있게 해주는 것이고,

    endpoint_public_access = true는 인터넷에서도 접근할 수 있게 해주는 것입니다.

     

    지금은 로컬에서 kubectl을 편하게 쓰기 위해 public을 열어뒀지만, 보안을 강화하려면 false로 바꾸고 Bastion을 통해서만 접근하도록 제한할 수 있습니다.

    aws_eks_node_group으로 노드 그룹을 정의하면 Auto Scaling Group이 자동으로 생성되고, 그 ASG가 EC2를 지정한 수만큼 만들어서 클러스터에 등록시킵니다. 직접 EC2를 만들 필요가 없다. 컨트롤 플레인이 EC2를 만드는 것이 아니라, Node Group(ASG)이 만들어줍니다.

    인스턴스 타입은 t3.large를 선택했습니다.

    서비스 6개에 각 Pod 메모리를 512MB로 잡고, Istio sidecar가 각 Pod마다 128MB씩 추가로 붙고, Prometheus와 Grafana가 약 1.5GB를 쓴다. t3.medium(4GB) 3대로는 총 12GB가 한계인데 빠듯할 것 같습니다.  

    scaling config에서 min_size를 1이 아닌 2로 잡은 이유는, 1대면 그 노드가 죽었을 때 서비스 전체가 다운되기 때문입니다. 비용 절약이 필요할 때는 노드를 0대로 스케일 다운할 수 있습니다. EKS 컨트롤 플레인은 끄더라도 시간당 $0.10이 계속 청구되지만, 워커노드는 0대로 줄이면 EC2 비용이 발생하지 않습니다.

    depends_on은 이 리소스를 만들기 전에 먼저 완료되어야 할 것들을 지정합니다.

    워커노드 생성 전에 IAM 정책 3개가 모두 역할에 연결되어 있어야 합니다. 정책이 없는 상태로 노드를 생성하면 ECR 이미지 pull이 불가하고, VPC CNI 설정이 안 되고, 클러스터 참여 자체가 불가능합니다.

     

    # eks.tf
    # EKS 클러스터와 워커노드 그룹 정의
    # 컨트롤 플레인(마스터)은 AWS가 관리, 워커노드만 우리가 관리
    
    # =============================================
    # EKS 컨트롤 플레인 (마스터 노드)
    # AWS가 직접 관리하는 영역 - EC2로 안 보임
    # 쿠버네티스 API 서버, etcd, 스케줄러 등이 여기서 실행됨
    # =============================================
    resource "aws_eks_cluster" "oka" {
      name     = var.eks_cluster_name    # "oka-eks"
      role_arn = aws_iam_role.eks_cluster_role.arn # iam.tf의 컨트롤 플레인 역할 ARN 참조
      version  = var.eks_cluster_version # "1.32"
    
      vpc_config {
        # EKS 워커노드가 위치할 서브넷 (private 3개, AZ별 1개씩)
        subnet_ids = [
          var.private_subnet_2a, # oka-private-2a
          var.private_subnet_2b, # oka-private-2b
          var.private_subnet_2c  # oka-private-2c
        ]
    
        # private: VPC 내부(워커노드, Bastion)에서 API 서버 접근 가능
        # WireGuard 터널 통해 온프렘에서도 접근 가능
        endpoint_private_access = true
    
        # public: 인터넷에서 API 서버 접근 가능
        # true인 이유: 로컬 개발환경에서 kubectl 접근 편의상 열어둠
        # 보안 강화 시 false로 변경 후 Bastion 통해서만 접근
        endpoint_public_access = var.eks_endpoint_public_access
      }
    
      # IAM 정책이 역할에 연결된 후 클러스터 생성해야
      # "역할에 권한 없는 상태로 클러스터 생성" 에러 방지
      depends_on = [
        aws_iam_role_policy_attachment.eks_cluster_policy
      ]
    }
    
    # =============================================
    # EKS 워커노드 그룹
    # 실제 Pod가 올라가는 EC2 인스턴스들
    # Node Group 설정하면 Auto Scaling Group이 EC2 자동 생성
    # =============================================
    resource "aws_eks_node_group" "oka" {
      cluster_name    = aws_eks_cluster.oka.name # 어떤 클러스터에 속하는 노드그룹인지 참조
      node_group_name = "oka-node-group"
      node_role_arn   = aws_iam_role.eks_node_role.arn # iam.tf의 워커노드 역할 ARN 참조
    
      subnet_ids = [
        var.private_subnet_2a,
        var.private_subnet_2b,
        var.private_subnet_2c
      ]
    
      # t3.large 선택 이유:
      # 서비스 6개 + Istio sidecar + Prometheus/Grafana 올리면
      # t3.medium(4GB) × 3 = 12GB로 빠듯, t3.large(8GB) × 3 = 24GB 여유
      instance_types = [var.eks_node_instance_type]
    
      scaling_config {
        desired_size = var.eks_node_desired_size # 평상시 3대 (AZ별 1대씩)
        min_size     = var.eks_node_min_size     # 최소 2대 (1대면 죽었을 때 전체 다운)
        max_size     = var.eks_node_max_size     # 최대 6대 (트래픽 급증 시 자동 확장)
        # 비용 절약 시 아래 명령어로 노드 0대로 스케일 다운 가능:
        # aws eks update-nodegroup-config --cluster-name oka-eks \
        # --nodegroup-name oka-node-group \
        # --scaling-config minSize=0,maxSize=6,desiredSize=0
      }
    
      # 정책 없는 상태로 노드 생성하면:
      # - ECR 이미지 pull 불가 → Pod 실행 안 됨
      # - VPC CNI 설정 불가 → Pod IP 할당 안 됨
      # - 클러스터 참여 불가 → 노드 자체가 Ready 상태 안 됨
      depends_on = [
        aws_iam_role_policy_attachment.eks_worker_node_policy,
        aws_iam_role_policy_attachment.eks_cni_policy,
        aws_iam_role_policy_attachment.eks_ecr_policy
      ]
    }

     

     

     


    [ code ][ istio.tf ]Istio: 사이드카 방식의 서비스 메시

    MSA 환경에서 서비스가 6개가 되면 서비스 간 통신이 복잡해집니다. A 서비스가 B 서비스를 호출할 때 암호화는 어떻게 할지, 장애가 났을 때 어떻게 차단할지, 각 서비스 간 트래픽 현황은 어떻게 모니터링할지. 이걸 각 서비스 코드에서 직접 처리하면 중복 코드가 폭발한다. Istio는 이 문제를 사이드카 패턴으로 해결합니다.

    사이드카는 오토바이 옆에 붙는 보조 칸처럼, 각 서비스 Pod 옆에 envoy proxy라는 컨테이너를 자동으로 하나씩 붙여줍니다.

    대빵 이스티오(마스터이스티오d)는 eks옆에 붙어있을겁니다.

     

    모든 네트워크 트래픽은 이 proxy를 통해서만 들어오고 나갑니다. 서비스 코드는 암호화, 로깅, 로드밸런싱, 서킷브레이커를 전혀 신경 쓰지 않아도 됩니다. 사이드카가 다 처리해줍니다.

    Istio는 세 단계로 설치합니다.

    먼저 istio-base를 설치해서 CRD(Custom Resource Definition)를 등록합니다.

    CRD는 Kubernetes에 없던 새로운 리소스 타입을 정의하는 것입니다. Istio의 VirtualService, DestinationRule, Gateway 같은 개념들이 여기서 등록됩니다.

     

    그 다음 istiod(컨트롤 플레인)를 설치합니다. istiod 안에는 트래픽 규칙을 배포하는 Pilot, mTLS 인증서를 관리하는 Citadel, 설정을 검증하는 Galley가 통합되어 있습니다. 마지막으로 istio-ingress를 설치해서 외부 트래픽이 클러스터로 들어오는 진입점을 만든다.

    istiod의 CPU 요청량을 100m(0.1 core), 메모리를 128Mi로 잡은 것은 Istio 공식 권장 최솟값입니다. 이 값은 limit(최대)이 아니라 request(최소 보장)입니다. t3.large 3대에서 서비스들이 요청하는 총 리소스를 계산해보면 약 1600m CPU, 5GB 메모리 정도인데 이 값으로 충분히 여유가 있습니다.

    Istio Ingress Gateway를 설치할 때 AWS 로드밸런서 타입을 NLB(Network Load Balancer)로 지정했습니다. ALB(Application Load Balancer)는 HTTP 헤더를 읽고 L7 레벨에서 라우팅하는데, Istio가 이미 L7 처리를 담당하므로 ALB와 역할이 중복됩니다. NLB는 TCP/UDP 레벨인 L4에서 그냥 패킷을 전달하기만 하므로, L7 처리는 Istio에 맡기고 NLB는 순수 전달 역할만 하게 됩니다. 

     

    # istio.tf
    # Istio 서비스 메시 설치 (2단계 - variables.tf에서 istio_enabled = true로 변경)
    #
    # Istio 역할 (gateway 서버와 역할 분리):
    # - gateway service(Spring Cloud Gateway): 외부 진입점, JWT 검증, 라우팅 로직
    # - Istio: gateway 이후 서비스 간 내부 통신 암호화(mTLS), 서킷브레이커, 모니터링
    #
    # 설치 순서:
    # 1. istio-base  → CRD 등록
    # 2. istiod      → 컨트롤 플레인 (sidecar 주입 담당)
    # 3. istio-ingress → NLB 생성, 외부 트래픽 진입점
    # 4. namespace   → sidecar injection 활성화
    # 5. PeerAuthentication → mTLS 정책 적용
    
    # -----------------------------------------------
    # 2단계: variables.tf에서 istio_enabled = true로 변경 시 자동 생성
    # -----------------------------------------------
    
    # Istio 전용 네임스페이스
    # 모든 Istio 컴포넌트(istiod, ingress gateway)는 이 네임스페이스에 설치됨
    resource "kubernetes_namespace" "istio_system" {
      count = var.istio_enabled ? 1 : 0
    
      metadata {
        name = "istio-system"
      }
    
      depends_on = [aws_eks_node_group.oka]
    }
    
    # 우리 서비스들이 배포될 네임스페이스
    # istio-injection = "enabled" 라벨이 핵심
    # 이 라벨이 있으면 이 네임스페이스에 Pod가 새로 뜰 때마다
    # istiod가 감지해서 envoy proxy(sidecar) 컨테이너를 자동으로 옆에 붙여줌
    # 이 라벨 없으면 istiod가 설치돼도 sidecar가 하나도 주입 안 됨
    resource "kubernetes_namespace" "moneylog" {
      count = var.istio_enabled ? 1 : 0
    
      metadata {
        name = "moneylog"
        labels = {
          istio-injection = "enabled"
        }
      }
    
      depends_on = [helm_release.istiod]
    }
    
    # Istio Base (CRD 설치)
    # CRD = Custom Resource Definition
    # Istio 고유 리소스 타입을 Kubernetes에 등록
    # ex) VirtualService, DestinationRule, Gateway, PeerAuthentication 등
    # istiod보다 반드시 먼저 설치해야 함
    resource "helm_release" "istio_base" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istio-base"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "base"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          defaultRevision = "default"
        })
      ]
    
      depends_on = [kubernetes_namespace.istio_system]
    }
    
    # istiod (Istio 컨트롤 플레인)
    # - Pilot: 트래픽 관리 규칙을 각 sidecar에 배포
    # - Citadel: mTLS 인증서 발급/관리 (서비스 간 통신 암호화 담당)
    # - Galley: 설정 검증
    # - 각 Pod에 envoy proxy sidecar 자동 주입
    #
    # istiod는 워커노드 위에 Pod로 올라가서 다른 Pod들을 감시하다가
    # istio-injection=enabled 네임스페이스에 새 Pod가 뜨면 sidecar를 자동 주입
    resource "helm_release" "istiod" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istiod"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "istiod"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          pilot = {
            resources = {
              requests = {
                cpu    = "100m"  # Istio 공식 권장 최솟값 (0.1 core)
                memory = "128Mi" # Istio 공식 권장 최솟값
                # t3.large 3대 = 총 6000m CPU, 24GB 메모리
                # 전체 리소스 사용량 약 1600m, 5GB → 충분히 여유있음
              }
            }
          }
        })
      ]
    
      depends_on = [helm_release.istio_base]
    }
    
    # Istio Ingress Gateway
    # 외부 트래픽을 클러스터 내부로 전달하는 진입점
    # AWS NLB 자동 생성
    #
    # NLB 선택 이유:
    # ALB(L7)는 HTTP 헤더 읽고 라우팅 → Istio가 이미 L7 처리하므로 중복
    # NLB(L4)는 TCP 레벨에서 그냥 전달 → L7 처리는 Istio에 맡기고 NLB는 전달만
    # 레이턴시 낮고 처리 빠름
    #
    # 트래픽 흐름:
    # 사용자 → NLB → Istio Ingress Gateway → gateway service → 각 서비스
    resource "helm_release" "istio_ingress" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istio-ingress"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "gateway"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          service = {
            type = "LoadBalancer"
            annotations = {
              "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb"
            }
          }
        })
      ]
    
      depends_on = [helm_release.istiod]
    }
    
    # mTLS 정책 (PeerAuthentication)
    # Istio가 설치되면 기본값은 PERMISSIVE 모드
    # PERMISSIVE = mTLS와 일반 평문 통신 둘 다 허용 (개발 초기에 편하지만 보안 약함)
    # STRICT = mTLS만 허용, 평문 통신 완전 차단 (보안 강하지만 sidecar 없는 Pod는 통신 불가)
    #
    # 지금은 PERMISSIVE로 설정:
    # - 초기 개발 단계라 모든 서비스에 sidecar가 붙기 전에 통신이 끊기면 안 됨
    # - 안정화되면 STRICT로 변경 권장
    resource "kubernetes_manifest" "mtls_policy" {
      count = var.istio_enabled ? 1 : 0
    
      manifest = {
        apiVersion = "security.istio.io/v1beta1"
        kind       = "PeerAuthentication"
        metadata = {
          name      = "default"
          namespace = "istio-system" # 메시 전체에 적용 (istio-system에 설정하면 전체 적용)
        }
        spec = {
          mtls = {
            mode = "PERMISSIVE"
            # 안정화 후 STRICT로 변경:
            # mode = "STRICT"
          }
        }
      }
    
      depends_on = [helm_release.istiod]
    }

     

     

     


    [ code ][ ingress.tf ]Ingress : 외부 트래픽이 들어오는 길

    Istio가 있는데 왜 ingress.tf가 따로 필요한가 처음에 의아했습니다.

     

    Istio Ingress Gateway는 클러스터 내부 트래픽을 제어하는 것이고, AWS Load Balancer Controller는 Kubernetes의 Ingress 리소스를 AWS ALB로 자동 변환해주는 것입니다. 인터넷 트래픽이 AWS ALB로 들어오고, ALB가 Istio Ingress Gateway로 전달하고, Istio가 내부 서비스로 라우팅하는 구조입니다. 둘은 같이 쓸 수 있고 역할이 명확히 분리됩니다.

    AWS Load Balancer Controller는 ALB를 생성하고 관리하기 위해 AWS API를 호출해야 합니다. 그런데 이걸 실행하는 건 EC2가 아니라 Pod입니다. EC2는 IAM 역할을 직접 붙일 수 있지만 Pod는 그럴 수 없습니다. 이 문제를 해결하는 게 IRSA(IAM Roles for Service Accounts)입니다.

     

    Kubernetes의 ServiceAccount와 AWS IAM Role을 OIDC(OpenID Connect) 프로토콜로 연동하는 방식입니다. LB Controller Pod가 실행되면, 자신의 Kubernetes ServiceAccount를 OIDC를 통해 AWS IAM에 검증받고, IAM Role의 임시 자격증명을 발급받아 ALB 생성 API를 호출할 수 있게 됩니다. 쉽게 말하면 Pod에게 "이 Pod는 이 IAM 역할을 쓸 수 있어"라고 위임해주는 메커니즘입니다.

     
    # ingress.tf
    # AWS Load Balancer Controller 설치 (2단계 - variables.tf에서 ingress_enabled = true로 변경)
    #
    # 역할:
    # - k8s Ingress 리소스를 AWS ALB로 자동 프로비저닝
    # - Istio Gateway와 함께 사용하여 외부 트래픽 라우팅
    #
    # IRSA (IAM Roles for Service Accounts):
    # - Pod가 AWS IAM 역할을 사용할 수 있게 해주는 인증 방식
    # - LB Controller Pod가 AWS API(ALB 생성 등) 호출할 때 필요
    # - OIDC Provider를 통해 k8s ServiceAccount와 IAM Role 연동
    
    # -----------------------------------------------
    # 2단계: variables.tf에서 ingress_enabled = true로 변경 시 자동 생성
    # -----------------------------------------------
    
    # OIDC Provider 조회
    # Pod가 AWS IAM 역할을 사용할 수 있게 해주는 인증 연동
    data "aws_iam_openid_connect_provider" "eks" {
      count = var.ingress_enabled ? 1 : 0
      url   = aws_eks_cluster.oka.identity[0].oidc[0].issuer
    }
    
    # LB Controller 전용 IAM 역할 (IRSA 방식)
    # OIDC를 통해 특정 ServiceAccount에만 이 역할 사용 허용
    resource "aws_iam_role" "lb_controller" {
      count = var.ingress_enabled ? 1 : 0
      name  = "oka-lb-controller-role"
    
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Effect = "Allow"
          Principal = {
            Federated = data.aws_iam_openid_connect_provider.eks[0].arn
          }
          Action = "sts:AssumeRoleWithWebIdentity"
          Condition = {
            StringEquals = {
              # kube-system 네임스페이스의 aws-load-balancer-controller ServiceAccount만 이 역할 사용 가능
              "${replace(aws_eks_cluster.oka.identity[0].oidc[0].issuer, "https://", "")}:sub" = "system:serviceaccount:kube-system:aws-load-balancer-controller"
            }
          }
        }]
      })
    
      force_detach_policies = true
    }
    
    # ALB/NLB 생성, 수정, 삭제 권한
    resource "aws_iam_role_policy_attachment" "lb_controller" {
      count      = var.ingress_enabled ? 1 : 0
      policy_arn = "arn:aws:iam::aws:policy/ElasticLoadBalancingFullAccess"
      role       = aws_iam_role.lb_controller[0].name
    }
    
    # AWS Load Balancer Controller 설치
    # k8s Ingress 리소스 → AWS ALB 자동 생성
    resource "helm_release" "istio_base" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istio-base"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "base"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          defaultRevision = "default"
        })
      ]
    
      depends_on = [kubernetes_namespace.istio_system]
    }
    
    resource "helm_release" "istiod" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istiod"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "istiod"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          pilot = {
            resources = {
              requests = {
                cpu    = "100m"
                memory = "128Mi"
              }
            }
          }
        })
      ]
    
      depends_on = [helm_release.istio_base]
    }
    
    resource "helm_release" "istio_ingress" {
      count = var.istio_enabled ? 1 : 0
    
      name       = "istio-ingress"
      repository = "https://istio-release.storage.googleapis.com/charts"
      chart      = "gateway"
      namespace  = kubernetes_namespace.istio_system[0].metadata[0].name
      version    = var.istio_version
    
      values = [
        yamlencode({
          service = {
            type = "LoadBalancer"
            annotations = {
              "service.beta.kubernetes.io/aws-load-balancer-type" = "nlb"
            }
          }
        })
      ]
    
      depends_on = [helm_release.istiod]
    }
    728x90

    'FISA' 카테고리의 다른 글

    [PROJECT] ai 추천시스템 성능평가1  (0) 2026.06.05
    [Project]RDS설정 중 생긴 일  (0) 2026.05.30
    [Project]성능 평가 스토리  (0) 2026.05.27
    [project] 작업 순서  (0) 2026.05.20
    [회고]20주차_우리FISA클라우드 엔지니어링  (0) 2026.05.16

    댓글

Designed by Tistory.
티스토리 친구하기