no image
[Terraform] AWS ALB 프로비저닝
AWS ALB란? AWS ALB(Application Load Balancer)는 AWS에서 제공하는 Elastic Load Balancing(ELB) 서비스의 한 유형으로, 주로 애플리케이션 계층(HTTP/HTTPS)에서 즉 7계층에서 작동하는 로드 밸런싱을 제공한다. ALB는 웹 애플리케이션이나 마이크로서비스 환경에서 트래픽을 효율적으로 분산하고 관리하는 데 사용된다. AWS ALB Controller란? AWS ALB Controller는 Kubernetes에서 AWS의 Application Load Balancer(ALB)를 관리하기 위해 사용하는 Kubernetes Ingress Controller이다. AWS에서 제공하는 공식 도구이며, Kubernetes Ingress 리소스를 기반으로 AL..
2024.12.09
no image
[Terraform] Karpenter 프로비저닝
Karpenter란?Karpenter는 AWS에서 제공하는 오픈소스 Kubernetes 노드 자동 스케일링 솔루션으로, 애플리케이션 워크로드의 요구 사항에 따라 클러스터의 노드를 자동으로 확장하거나 축소하는 기능을 제공한다. 기존 Kubernetes Cluster Autoscaler보다 더 빠르고 효율적으로 작동하도록 설계되었으며, 복잡한 설정 없이 동적으로 자원을 관리할 수 있다. Karpenter 특징 1. 속도와 효율성Karpenter는 워크로드에 필요한 노드를 몇 초 안에 프로비저닝할 수 있다.기존 Cluster Autoscaler가 노드 그룹을 기반으로 작동하는 반면, Karpenter는 개별 Pod의 요구사항에 따라 최적의 인스턴스를 직접 프로비저닝한다. 2. 다양한 인스턴스 선택 Amazon..
2024.12.09
no image
[Terraform] EKS 프로비저닝
EKS란? EKS(Amazon Elastic Kubernetes Service)는 AWS에서 제공하는 완전 관리형 Kubernetes 서비스이다. Kubernetes는 컨테이너화된 애플리케이션을 배포, 확장 및 관리하기 위한 오픈소스 플랫폼이고 EKS는 이를 AWS 환경에서 간편하게 사용할 수 있도록 자동화된 관리 및 통합을 제공한다. EKS의 특징 및 장점 완전 관리형 KubernetesEKS는 Kubernetes의 Control Plane(Master Node)를 AWS가 관리한다사용자는 워커 노드만 관리하거나, Fargate를 통해 서버리스로 완전히 관리할 수도 있다AWS 서비스와의 통합Amazon VPC, IAM, Elastic Load Balancer, CloudWatch, Route 53, E..
2024.12.09
no image
[Terraform] 고가용성 Multi AZ's VPC 환경 프로비저닝
고가용성이란? 고가용성(High Availability)은 시스템 또는 서비스가 최소한의 다운타임으로 안정적이고 지속적으로 운영될 수 있는 능력을 말한다. 특히 사용자가 언제든지 서비스를 이용할 수 있도록 보장하는 것이 중요한 클라우드 환경, 네트워크 시스템, 또는 기업 애플리케이션에서 필수적인 목표이다.  고가용성 환경을 만드는 이유 Cloud wave 3기 프로젝트를 진행하며 MSA 서비스를 구축하기 위해 EKS 환경을 사용할 필요가 있었다. EKS 환경에선 Multi Subnet을 활용한 Multi node로 고가용성과 스케일링이 필수이고 Terraform을 통해 해당 환경을 구축하고자 한다.  다음과 같은 환경을 목표로 진행한다.  3개의 가용영역에 a, b, c에 각각 Public subnet ..
2024.12.09
no image
[Terraform] S3 생성
개요4-2 캡스톤 프로젝트에서 만드는 웹서비스는 여러 JSON 파일들을 실시간으로 받아올 필요가 있는데 그 떄 사용할 저장소로 S3 Bucket을 사용할 것이다.  이전에 Bastion 및 Vpc 환경을 Terraform으로 만들었던 것처럼 S3도 Terraform으로 생성한다.  S3resource "aws_s3_bucket" "s3" { bucket = "${var.common_info.env}-${var.common_info.service_name}-bucket" tags = { environment = "${var.common_tags.Environment}" }}resource "aws_s3_bucket_public_access_block" "public-access" { bucke..
2024.12.05
no image
[Terraform] AWS bastion 및 vpc 프로비저닝
TerraformTerraform은 IAC 도구 중 하나로, 클라우드 환경을 GUI가 아닌 Local에서 command 단위로 프로비저닝 할 수 있다는 장점이 있다.  이번 4-2 캡스톤 프로젝트에서 AWS 환경을 구성하기 위해 사용하였다. IAM User, Access key 생성  Local에서 aws 환경에 접근하고 리소스들을 생성할 권한을 얻을 수 있도록 미리 IAM User를 생성하고 Local 개발환경(여기서는 Vs code)에 미리 연결 시켜줄 필요가 있다. aws sts get-caller-identity AWS CLI를 구성하고 IAM User를 연결한 뒤 위 명령어를 실행하면 현재 연결되어있는 IAM User를 확인할 수 있다 위와 같은 결과가 나올 경우 정상적으로 연결이 된 것으로 T..
2024.12.05

AWS ALB란?

 

AWS ALB(Application Load Balancer)는 AWS에서 제공하는 Elastic Load Balancing(ELB) 서비스의 한 유형으로, 주로 애플리케이션 계층(HTTP/HTTPS)에서 즉 7계층에서 작동하는 로드 밸런싱을 제공한다.

 

ALB는 웹 애플리케이션이나 마이크로서비스 환경에서 트래픽을 효율적으로 분산하고 관리하는 데 사용된다.

 

AWS ALB Controller란?

 

AWS ALB Controller는 Kubernetes에서 AWS의 Application Load Balancer(ALB)를 관리하기 위해 사용하는 Kubernetes Ingress Controller이다.

 

AWS에서 제공하는 공식 도구이며, Kubernetes Ingress 리소스를 기반으로 ALB를 자동으로 생성하고 관리한다.

 

AWS ALB Controller 사용 이유

 

앞에서 말했듯이 Application Load Balancer를 사용하기 위해 Application Load Balancer controller가 필요하다.

 

프로젝트에서 MSA 도입을 위해 Istio 서비스 메쉬가 필요했고 또 DR을 위한 multi region을 구현하기 위해 Global Accelerator가 필요했다.

 

해당 서비스들을 사용하기 위해선 Application Load Balancer의 7계층 접근이 필요했고 Application Load Balancer controller를 Pod로 띄워야만 했다.

 

해당 과정 또한 Terraform으로 자동화를 한다면 매우 좋을 것이라 생각했고 AWS ALB Controller를 사용하기로 결정하였다.

 

Nginx Application Load Balancer controller가 오픈소스로서 가장 많이 사용되지만 EKS 환경에서 구축하기 때문에 AWS ALB Controller를 사용하였다.

 

Terraform code

 

resource "kubernetes_service_account" "alb_controller" {
  metadata {
    name      = var.alb_chart.name
    namespace = var.alb_chart.namespace

    annotations = {
      "eks.amazonaws.com/role-arn" = module.alb_controller_irsa.iam_role_arn
    }
  }
}

resource "helm_release" "alb_controller" {
  namespace  = var.alb_chart.namespace
  repository = var.alb_chart.repository
  name    = var.alb_chart.name
  chart   = var.alb_chart.chart
  version = var.alb_chart.version
  
  set {
    name  = "clusterName"
    value = local.eks_cluster_info.cluster_name
  }
  set {
    name  = "serviceAccount.create"
    value = "false"
  }
  set {
    name  = "serviceAccount.name"
    value = kubernetes_service_account.alb_controller.metadata.0.name
  }
  set {
    name  = "region"
    value = local.eks_cluster_info.region
  }
  set {
    name  = "vpcId"
    value = module.vpc.vpc_id
  }
  depends_on = [kubernetes_service_account.alb_controller]
}

module "alb_controller_irsa" {
  source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"

  role_name = "${local.eks_cluster_info.cluster_name}-${var.alb_chart.name}"

  attach_load_balancer_controller_policy = true

  oidc_providers = {
    one = {
      provider_arn               = module.eks.oidc_provider_arn
      namespace_service_accounts = ["kube-system:${var.alb_chart.name}"]
    }
  }
}

 

최종 아키텍처

 

앞 포스팅들(Karpenter, VPC, Bastion, EKS)을 포함해 Terraform으로 구축한 최종 아키텍처는 다음과 같다.

 

 

후기 

 

프로젝트에서 Terraform을 이용한 EKS 환경 구축을 딤당하며 배운 기술들(Terraform, AWS, Network, Kubernetes)을 제대로 활용한 것 같다는 생각이 들어서 뿌듯했고 또 많은 복습을 할 수 있었다.

 

이 경험을 살려 다른 프로젝트에서 Cloud 환경을 구축해야 하는 일이 있다면 이번처럼 Terraform을 사용할 것이고 코드를 Custom해서 빠르게 작업을 처리하고자 한다.매우 시간 효율적일 것이라 생각한다.

 

 

Karpenter란?

Karpenter는 AWS에서 제공하는 오픈소스 Kubernetes 노드 자동 스케일링 솔루션으로, 애플리케이션 워크로드의 요구 사항에 따라 클러스터의 노드를 자동으로 확장하거나 축소하는 기능을 제공한다.

 

기존 Kubernetes Cluster Autoscaler보다 더 빠르고 효율적으로 작동하도록 설계되었으며, 복잡한 설정 없이 동적으로 자원을 관리할 수 있다.

 

Karpenter 특징

 

1. 속도와 효율성

  • Karpenter는 워크로드에 필요한 노드를 몇 초 안에 프로비저닝할 수 있다.
  • 기존 Cluster Autoscaler가 노드 그룹을 기반으로 작동하는 반면, Karpenter는 개별 Pod의 요구사항에 따라 최적의 인스턴스를 직접 프로비저닝한다.

 

2. 다양한 인스턴스 선택

 

  • Amazon EC2의 다양한 인스턴스 유형을 활용하여 비용 효율적이고 성능 최적화된 리소스를 선택한다.
  • 스팟 인스턴스와 온디맨드 인스턴스를 혼합하여 비용을 절감할 수 있다.

 

3. Pod의 요구사항 기반 프로비저닝

  • Pod의 리소스 요청이나 지역,  AMI,  레이블,  태그 등 세부적인 요구사항을 기반으로 적합한 인스턴스를 자동으로 선택한다.

 

4. 유연한 노드 종료

 

  • 워크로드가 줄어들면 불필요한 노드를 종료하여 비용을 절감한다.
  • 노드 종료 전에 Kubernetes의 스케줄러와 협력하여 Pod를 안전하게 다른 노드로 이동시킨다.

 

5. 간단한 설정

 

  • 복잡한 노드 그룹 설정이 필요하지 않으며, 간단한 정책만으로 동작힌다.
  • Terraform 또는 Helm Chart를 사용하여 빠르게 설정 가능하다.

 

Karpenter 사용 이유

 

MSA 서비스에서 사용 Traffic이 증가함에 따라 Pod만 scale out하는 것이 아닌 Node 또한 scale out 해줘야 한다. 

 

Pod는 HPA를 이용하여 scale out 할 수 있지만 Node 수가 부족할 경우 한계가 존재한다.  

 

그 떄 제안되는 선택지가  Cluster autoscaler와 Karpenter이다. 

 

두 선택지를 비교한 결과 Karpenter를 사용하기로 결정했고 이유는 다음과 같다. 

 

  •  Karpenter는 스팟 인스턴스와 온디맨드 인스턴스를 혼합하여 비용을 최적화할 수 있는 반면, Cluster Autoscaler는 고정된 노드 그룹 만을 사용하기에 비용에 있어 비효율적이다.
  • Node를  프로비저닝 하는 것에 있어 Karpenter는 몇 초 이내로 빠르게 프로비저닝할 수 있는 반면, Cluster Autoscaler는 최대 몇 분이 소요될 수 있다.

Terraform 코드

 

resource "helm_release" "karpenter" {
  namespace        = "karpenter"
  create_namespace = true

  name                = "karpenter"
  repository          = "oci://public.ecr.aws/karpenter"
  repository_username = data.aws_ecrpublic_authorization_token.token.user_name
  repository_password = data.aws_ecrpublic_authorization_token.token.password
  chart               = "karpenter"
  version             = "v0.31.3"

  set {
    name  = "settings.aws.clusterName"
    value = local.eks_cluster_info.cluster_name
  }

  set {
    name  = "settings.aws.clusterEndpoint"
    value = module.eks.cluster_endpoint
  }

  set {
    name  = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
    value = module.karpenter.irsa_arn
  }

  set {
    name  = "settings.aws.defaultInstanceProfile"
    value = module.karpenter.instance_profile_name
  }

  set {
    name  = "settings.aws.interruptionQueueName"
    value = module.karpenter.queue_name
  }
}

resource "kubectl_manifest" "karpenter_provisioner" {
  yaml_body = <<-YAML
    apiVersion: karpenter.sh/v1alpha5
    kind: Provisioner
    metadata:
      name: default
    spec:
      requirements:
        - key: karpenter.sh/capacity-type
          operator: In
          values: ["spot"] #"spot"
        - key: "node.kubernetes.io/instance-type"
          operator: In
          values: ["t3.xlarge", "t3.medium", "t3.large"]
      limits:
        resources:
          cpu: 50
          memory: 192Gi
      providerRef:
        name: default
      ttlSecondsAfterEmpty: 30
  YAML

  depends_on = [
    helm_release.karpenter
  ]
}

resource "kubectl_manifest" "karpenter_node_template" {
  yaml_body = <<-YAML
    apiVersion: karpenter.k8s.aws/v1alpha1
    kind: AWSNodeTemplate
    metadata:
      name: default
    spec:
      subnetSelector:
        karpenter.sh/discovery: ${local.eks_cluster_info.cluster_name}
      securityGroupSelector:
        karpenter.sh/discovery: ${local.eks_cluster_info.cluster_name}
      tags:
        karpenter.sh/discovery: ${local.eks_cluster_info.cluster_name}
  YAML

  depends_on = [
    helm_release.karpenter
  ]
}

module "karpenter" {
  source  = "terraform-aws-modules/eks/aws//modules/karpenter"
  version = "19.20.0"

  cluster_name                    = local.eks_cluster_info.cluster_name
  irsa_oidc_provider_arn          = module.eks.oidc_provider_arn
  irsa_namespace_service_accounts = ["karpenter:karpenter"]

  create_iam_role      = false

  iam_role_arn    = module.eks.eks_managed_node_groups["service"].iam_role_arn
  irsa_use_name_prefix = false

  tags = {
    Environment = "dev"
    Terraform   = "true"
  }
}

 

Karpenter Test

 

실제 프로젝트에 사용한 Service에 의도적으로 부하를 가했고 Kube Ops view를 통해 Karpenter가 작동하는 것을 확인하였다.

 

 

EKS란?

 

EKS(Amazon Elastic Kubernetes Service)는 AWS에서 제공하는 완전 관리형 Kubernetes 서비스이다.

 

Kubernetes는 컨테이너화된 애플리케이션을 배포, 확장 및 관리하기 위한 오픈소스 플랫폼이고 EKS는 이를 AWS 환경에서 간편하게 사용할 수 있도록 자동화된 관리 및 통합을 제공한다.

 

EKS의 특징 및 장점

 

  • 완전 관리형 Kubernetes
    • EKS는 Kubernetes의 Control Plane(Master Node)를 AWS가 관리한다
    • 사용자는 워커 노드만 관리하거나, Fargate를 통해 서버리스로 완전히 관리할 수도 있다
  • AWS 서비스와의 통합
    • Amazon VPC, IAM, Elastic Load Balancer, CloudWatch, Route 53, ECR 등과 통합하여 강력하고 확장 가능한 애플리케이션 구축 가능
  • 보안 및 인증
    • IAM을 통해 Kubernetes RBAC(Role-Based Access Control)와 연동하여 강력한 보안 제공
    • 네트워크 정책으로 Pod 간 통신을 세밀하게 제어 가능
  • 유연한 배포 옵션
    • EC2, Fargate, 또는 하이브리드(온프레미스 Kubernetes 클러스터)에서 실행 가능
    • 필요에 따라 워커 노드와 리소스를 유연하게 확장 및 축소 가능
  • 자동 Kubernetes 업데이트
    • Kubernetes의 새 버전 업데이트 손쉽게 적용 가능

 

EKS를 사용하는 이유

 

Cloud wave 3기 프로젝트를 진행하며 MSA 서비스를 구축하기 위해 EKS 환경을 사용할 필요가 있었다.

 

Traffic이 많아질 때 HPA를 이용하여 특정 서비스만 Scale out 하는 Kubernetes의 장점 + AWS가 제공하는 클라우드형 Kubernetes 관리의 편리함은 우리의 프로젝트에 있어 아주 큰 요소였다. 

 

AWS Console이나 EKSCTL을 통하여 EKS를 프로비저닝하는 방법 또한 있지만 배웠던 Terraform을 활용하고 싶었기에 Terraform을 이용하여 프로비저닝해보았다. 

 

이전 포스팅에서 Terraform으로 만들었던 VPC 환경을 그대로 이용하여 EKS 환경을 구축한다.

 

Multi AZ's에 존재하는 Private Subenet 각각에 worker node가 2 개씩 존재하게끔 구성할 것이다.

 

전체 아키텍처는 다음과 같다.

 

 

특이한 점은 VPN이 사용되었다는 점인데 이는 EKS Endpoint를 접근할 떄 보안을 위해 Private하게 열어야 했기 때문이다. 

 

이렇게 함으로 EKS Endpoint로의 접근은 Bastion Server와 Local VPN IP만이 가능하다.

 

Terraform 코드

 

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = " 19.20.0"

  cluster_name    = local.eks_cluster_info.cluster_name
  cluster_version = local.eks_cluster_info.cluster_version

  cluster_endpoint_public_access  = true
  cluster_endpoint_private_access  = true

  create_cloudwatch_log_group = false

  cluster_addons = {
    coredns                = {
      most_recent = true
    }
    kube-proxy             = {
      most_recent = true
    }
    vpc-cni                = {
      most_recent = true
    }
  }

  vpc_id                   = module.vpc.vpc_id
  subnet_ids               = module.vpc.subnets_private_ids
  control_plane_subnet_ids = module.vpc.subnets_private_ids


  # cluster_endpoint_public_access_cidrs=["219.100.37.246/32"] # VPN HardCoding
  cluster_endpoint_public_access_cidrs=["0.0.0.0/0"] # 개발 시에는 속도를 위해 vpn 해제 

  create_node_security_group = false
  eks_managed_node_groups = {
    service = {
      # Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups
      ami_type       = "AL2023_x86_64_STANDARD"
      instance_types = ["t3.large"]

      min_size     = 3
      max_size     = 6
      desired_size = 3
      subnet_ids= module.vpc.subnets_private_ids

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

    }
    
    manage = {
      # Starting on 1.30, AL2023 is the default AMI type for EKS managed node groups
      ami_type       = "AL2023_x86_64_STANDARD"
      instance_types = ["t3.large"]

      min_size     = 3
      max_size     = 6
      desired_size = 3
      subnet_ids= module.vpc.subnets_private_ids

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

    }
  }
  
  # Extend cluster security group rules
  cluster_security_group_additional_rules = {
   egress_bastion = {
      description   = "bastion all ingress"
      protocol    = "-1"
      from_port   = 0
      to_port     = 0
      type        = "ingress"
      cidr_blocks = ["${module.bastion.bastion_private_ip}/32"]
   }

    egress_all = {
      description   = "allow all egress"
      protocol    = "-1"
      from_port   = 0
      to_port     = 0
      type  = "egress"
      cidr_blocks = ["0.0.0.0/0"]
    }

    egress_local = {
      description   = "local all ingress"
      protocol    = "-1"
      from_port   = 0
      to_port     = 0
      type        = "ingress"
      cidr_blocks = ["${data.external.local_ip.result.ip}/32"]
   }

  }

  # Extend node-to-node security group rules
  node_security_group_additional_rules = {

    ingress_self_all = {
      description = "Node to node all ports/protocols"
      protocol    = "-1"
      from_port   = 0
      to_port     = 0
      type        = "ingress"
      self        = true
    }
  }

  tags = {
    Environment = "test"
    Terraform   = "true"
    "karpenter.sh/discovery" = local.eks_cluster_info.cluster_name
  }
}

 

위 코드는 전체 코드 중 일부이며 깃허브에 추가 설정이 있다.

 

https://github.com/Leejeuk213/Cloud_Wave_Project

 

GitHub - Leejeuk213/Cloud_Wave_Project: EKS 기반의 MSA 서비스 도입

EKS 기반의 MSA 서비스 도입 . Contribute to Leejeuk213/Cloud_Wave_Project development by creating an account on GitHub.

github.com

 

고가용성이란?

 

고가용성(High Availability)은 시스템 또는 서비스가 최소한의 다운타임으로 안정적이고 지속적으로 운영될 수 있는 능력을 말한다.

 

특히 사용자가 언제든지 서비스를 이용할 수 있도록 보장하는 것이 중요한 클라우드 환경, 네트워크 시스템, 또는 기업 애플리케이션에서 필수적인 목표이다. 

 

고가용성 환경을 만드는 이유

 

Cloud wave 3기 프로젝트를 진행하며 MSA 서비스를 구축하기 위해 EKS 환경을 사용할 필요가 있었다.

 

EKS 환경에선 Multi Subnet을 활용한 Multi node로 고가용성과 스케일링이 필수이고 Terraform을 통해 해당 환경을 구축하고자 한다. 

 

다음과 같은 환경을 목표로 진행한다. 

 

3개의 가용영역에 a, b, c에 각각 Public subnet 1개와 Private Subnet 1개를 생성하고 a에만 RDS 전용 Subnet과 Nat gateway를 생성한다.

 

또 Internet Gateway를 하나 생성한다.

 

Terraform Code

 

Code는 너무 많아 직접 첨부하기 힘들 것 같아 깃허브 주소를 대신 남기려 한다.

 

https://github.com/Leejeuk213/Cloud_Wave_Project

 

GitHub - Leejeuk213/Cloud_Wave_Project: EKS 기반의 MSA 서비스 도입

EKS 기반의 MSA 서비스 도입 . Contribute to Leejeuk213/Cloud_Wave_Project development by creating an account on GitHub.

github.com

 

 

 

생성 결과

 

 

'IAC > Terraform' 카테고리의 다른 글

[Terraform] AWS ALB 프로비저닝  (0) 2024.12.09
[Terraform] Karpenter 프로비저닝  (1) 2024.12.09
[Terraform] EKS 프로비저닝  (0) 2024.12.09
[Terraform] S3 생성  (0) 2024.12.05
[Terraform] AWS bastion 및 vpc 프로비저닝  (0) 2024.12.05

[Terraform] S3 생성

dlwpdnr213
|2024. 12. 5. 21:53

 

개요

4-2 캡스톤 프로젝트에서 만드는 웹서비스는 여러 JSON 파일들을 실시간으로 받아올 필요가 있는데 그 떄 사용할 저장소로 S3 Bucket을 사용할 것이다. 

 

이전에 Bastion 및 Vpc 환경을 Terraform으로 만들었던 것처럼 S3도 Terraform으로 생성한다. 

 

S3

resource "aws_s3_bucket" "s3" {
  bucket = "${var.common_info.env}-${var.common_info.service_name}-bucket"

  tags = {
    environment = "${var.common_tags.Environment}"
  }
}

resource "aws_s3_bucket_public_access_block" "public-access" {
  bucket = aws_s3_bucket.s3.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}

resource "aws_s3_bucket_policy" "bucket-policy" {
  bucket = aws_s3_bucket.s3.id

  depends_on = [
    aws_s3_bucket_public_access_block.public-access
  ]

  policy = <<POLICY
{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Sid":"PublicRead",
      "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::${aws_s3_bucket.s3.id}/*"]
    }
  ]
}
POLICY
}

 

또 프로젝트에서 사용할 React와 Fastapi에서 정상 접근할 수 있도록 CORS 설정을 해준다.

 

resource "aws_s3_bucket_cors_configuration" "example" {
  bucket = aws_s3_bucket.s3.id

  cors_rule {
      allowed_headers = ["*"]
      allowed_methods = ["GET", "POST", "PUT"]
      allowed_origins = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3000
  }
}

 

생성 확인

 

 

 

EC2 인스턴스 역할 추가

 

서비스 컨테이너들이(React, Fastapi)는 S3에 접근하기 위해서는 권한이 있어야 한다. AWS IAM USER를 생성한 뒤 서비스 컨테이너 내부에서 환경변수 등을 통해 직접 연결하는 방식도 있고  EC2 인스턴스 자체에 S3를 사용할 권한을 부여하는 방법이 있다. 

Fastapi 같은 경우 같은 Python 언어로 만들어진  Boto3 라이브러리를 사용하여 EC2 인스턴스 자체에 권한이 있을경우 아래에서 띄워지는 컨테이너들이 S3에 접근할 권한을 자동적으로 취득한다.

 

다음은 terraform으로 역할을 생성하는 과정이다. 

 

esource "aws_iam_policy" "s3_access_policy" {
  name        = "ec2_s3_access_policy"
  description = "Allow EC2 to access S3 buckets"

  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect   = "Allow",
        Action   = [
          "s3:ListBucket",
          "s3:GetObject",
          "s3:PutObject"
        ],
        Resource = [
          "arn:aws:s3:::dev-jeus-bucket",          # S3 버킷
          "arn:aws:s3:::dev-jeus-bucket/*"         # 버킷 내 객체
        ]
      }
    ]
  })
}

# 역할에 정책 연결
resource "aws_iam_role_policy_attachment" "ec2_role_policy_attach" {
  role       = aws_iam_role.ec2_role.name
  policy_arn = aws_iam_policy.s3_access_policy.arn
}

resource "aws_iam_role" "ec2_role" {
  name = "ec2_s3_access_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "ec2.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

# IAM 인스턴스 프로파일 생성
resource "aws_iam_instance_profile" "ec2_instance_profile" {
  name = "ec2_instance_profile"
  role = aws_iam_role.ec2_role.name
}

 

해당 부분을 ec2 bastion을 생성한 파일에 추가한다.

iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name

 

결과 확인

 

 

역할이 권한과 잘 연결되고 또 ec2 인스턴스에 역할이 할당된 것을 확인 가능하다.

 

 

 

Terraform

Terraform은 IAC 도구 중 하나로, 클라우드 환경을 GUI가 아닌 Local에서 command 단위로 프로비저닝 할 수 있다는 장점이 있다. 

 

이번 4-2 캡스톤 프로젝트에서 AWS 환경을 구성하기 위해 사용하였다.

 

IAM User, Access key 생성

 

 

Local에서 aws 환경에 접근하고 리소스들을 생성할 권한을 얻을 수 있도록 미리 IAM User를 생성하고 Local 개발환경(여기서는 Vs code)에 미리 연결 시켜줄 필요가 있다.

 

aws sts get-caller-identity

 

AWS CLI를 구성하고 IAM User를 연결한 뒤 위 명령어를 실행하면 현재 연결되어있는 IAM User를 확인할 수 있다 위와 같은 결과가 나올 경우 정상적으로 연결이 된 것으로 Terraform을 사용할 준비가 된 것이다. 

 

리소스 모듈화

 

전체 폴더 구조이다. 가능한 모든 리소스들을 모듈화해서 사용하도록 노력하였다. 

유지보수를 편하게 하기 위함이다. 

 

Bastion

Bastion은 주로 AWS EC2 서버를 의미하며 관리용 서버로 사용된다. 우리 프로젝트에선 서비스들을 도커 컨테이너로 올릴 때 사용할 메인 서버로 사용한다.

 

보안 그룹 설정

EC2 내부에 접근할 서비스들의 Port들을 미리 설정해줄 수 있다. SSH, TCP, 여러 서비스 Port들을 설정한다.

 

resource "aws_security_group" "bastion_sg" {
  vpc_id = var.vpc_id

  # allow all outbound
  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  # allow ssh
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # TCP port 80 for HTTP
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # TCP port 443 for HTTPS
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  # allow test nginx
  ingress {
    from_port   = 8000
    to_port     = 8000
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "test nginx"
  }

  # fastapi 8080
  ingress {
    from_port   = 8080
    to_port     = 8080
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "fast-api"
  }

  # react 3000
  ingress {
    from_port   = 3000
    to_port     = 3000
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
    description = "react"
  }

  tags = {
    Name = "${var.common_info.env}-${var.common_info.service_name}-bastion-sg"
  }
}

 

SSH Key 생성

ec2 서버 접근의 보안을 위해서 ssh key 또한 생성해준다. 

 

# Create RSA key of size 4096 bits
resource "tls_private_key" "bastion_key" {
  algorithm = "RSA"
  rsa_bits  = 4096
}

# # Create local file
# resource "local_file" "bastion_key" {
#   content  = tls_private_key.bastion_key.private_key_pem
#   filename = "./bastion_key.pem"
# }

# Create AWS key pair
resource "aws_key_pair" "bastion_key" {
  key_name   = "bastion_key"
  public_key = tls_private_key.bastion_key.public_key_openssh
}

 

 

bastion 생성

 

resource "aws_instance" "bastion" {
  ami           = "ami-0ee82191e264e07cc"  # Amazon Linux 2 AMI (region-specific) seoul
  instance_type = "t2.micro"
  subnet_id     = var.subnets_public_ids[0]
  key_name      = "bastion_key"  # SSH key

  vpc_security_group_ids = [aws_security_group.bastion_sg.id]

  iam_instance_profile = aws_iam_instance_profile.ec2_instance_profile.name

  user_data = <<-EOF
  #!/bin/bash
  sudo yum update -y
  sudo yum install -y docker
  sudo service docker start
  sudo usermod -a -G docker ec2-user

  sudo timedatectl set-timezone Asia/Seoul

  mkdir -p /home/ec2-user/.docker/cli-plugins
  curl -SL https://github.com/docker/compose/releases/download/v2.29.6/docker-compose-linux-x86_64 -o /home/ec2-user/.docker/cli-plugins/docker-compose
  chmod +x /home/ec2-user/.docker/cli-plugins/docker-compose

  sudo dd if=/dev/zero of=/swapfile bs=128M count=16
  sudo chmod 600 /swapfile
  sudo mkswap /swapfile
  sudo swapon /swapfile
  echo '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab
  EOF

  tags = {
    Name = "${var.common_info.env}-${var.common_info.service_name}-bastion-server"
  }
}

 

시기에 따라 AMI id는 바뀔 수 있기에 AWS GUI에서 실제로 확인을 하고 하드코딩해야 정상적으로 실행된다. 

 

AWS 프리티어 계정을 사용하기 위해서 t2.micro 인스턴스를 사용하였고, user_data를 미리 설정하여 도커를 설치하고, 시간을 동기화하고, OOM 문제를 예방하기 위해 스왑메모리를 사용한다. 

 

 

VPC

우리 프로젝트에선 한 개의 가용영역에 하나의 Public subenet만을 이용할 것이기에 Nat Gateway는 따로 생성하지 않았다.

 

DB를 사용하였더라면 Private Subnet과 함께 생성했겠지만 S3 Bucket만을 사용할 예정이다. 

 

Internet gateway

resource "aws_internet_gateway" "internet_gateway" {
 vpc_id = aws_vpc.vpc.id

 tags = {
    "Name" = "${var.common_info.service_name}-igw"
  }
}

 

 

Subnet

 

resource "aws_subnet" "subnets_public" {
  for_each = var.vpc_info.cidr_blocks_public

  vpc_id     = aws_vpc.vpc.id
  cidr_block = each.value.cidr_block
  availability_zone = each.value.availability_zone
  map_public_ip_on_launch = true
  tags = merge(
    {
      Name = "${var.common_info.env}-${each.value.subnet_name}"
    }
  )
}

# resource "aws_subnet" "subnets_private" {
#   for_each = var.vpc_info.cidr_blocks_private

#   vpc_id     = aws_vpc.vpc.id
#   cidr_block = each.value.cidr_block
#   availability_zone = each.value.availability_zone

#   tags = merge(
#     {
#       Name = "${var.common_info.env}-${each.value.subnet_name}"
#     }
#   )
# }

#resource "aws_subnet" "subnets_private_db" {
#  for_each = var.vpc_info.cidr_blocks_private_db

#  vpc_id     = aws_vpc.vpc.id
#  cidr_block = each.value.cidr_block
#  availability_zone = each.value.availability_zone

#  tags = merge(
#    {
#      Name = "${var.common_info.env}-${each.value.subnet_name}"
#    }
#  )
#}

# resource "aws_subnet" "subnets_private_db" {
#   vpc_id     = aws_vpc.vpc.id
#   cidr_block = var.vpc_info.cidr_blocks_private_db["private_db_a"].cidr_block
#   availability_zone = var.vpc_info.cidr_blocks_private_db["private_db_a"].availability_zone

#   tags = merge(
#     {
#       Name = "${var.common_info.env}-${var.vpc_info.cidr_blocks_private_db["private_db_a"].subnet_name}"
#     }
#   )
# }

 

 

Routing table

 

resource "aws_route_table" "route_table_public" {
 vpc_id = aws_vpc.vpc.id

 tags = merge(
   {
     Name    =  "${var.common_info.env}-${var.common_info.service_name}-public"
   }
 )
}

# resource "aws_route_table" "route_table_private" {
#  vpc_id = aws_vpc.vpc.id

#  for_each = var.vpc_info.cidr_blocks_private

#  tags = merge(
#    {
#      Name    = "${var.common_info.env}-${each.value.subnet_name}"
#    },
#    var.common_tags
#  )
# }

# resource "aws_route_table" "route_table_private_db" {
#  vpc_id = aws_vpc.vpc.id

#  tags = merge(
#    {
#      Name    = "${var.common_info.env}-${var.common_info.service_name}-private-db"
#    },
#    var.common_tags
#  )
# }

resource "aws_route" "routes_public" {
 route_table_id         = aws_route_table.route_table_public.id
 destination_cidr_block = "0.0.0.0/0"
 gateway_id             = aws_internet_gateway.internet_gateway.id
}

# resource "aws_route" "routes_private" {
#  count = length(var.vpc_info.cidr_blocks_private)

#  route_table_id = aws_route_table.route_table_private[keys(var.vpc_info.cidr_blocks_private)[count.index]].id
#  destination_cidr_block = "0.0.0.0/0"
#  nat_gateway_id = aws_nat_gateway.nat_gateway.id
# }


# resource "aws_route" "routes_private" {
#   for_each               = var.vpc_info.cidr_blocks_private

#   route_table_id         = aws_route_table.route_table_private[each.key].id
#   destination_cidr_block = "0.0.0.0/0"
  
#   nat_gateway_id         = lookup(var.vpc_info.private_to_public_map, each.key, null) != null ? aws_nat_gateway.nat_gateway[lookup(var.vpc_info.private_to_public_map, each.key)].id : null
# }

resource "aws_route_table_association" "route_table_association_public" {
 for_each = var.vpc_info.cidr_blocks_public
 
 subnet_id = aws_subnet.subnets_public[each.key].id
 route_table_id = aws_route_table.route_table_public.id
}

# resource "aws_route_table_association" "route_table_association_private" {
#  for_each = var.vpc_info.cidr_blocks_private
 
#  subnet_id = aws_subnet.subnets_private[each.key].id
#  route_table_id = aws_route_table.route_table_private[each.key].id
# }

#resource "aws_route_table_association" "route_table_association_private_db" {
# for_each = var.vpc_info.cidr_blocks_private_db

# subnet_id      = aws_subnet.subnets_private_db[each.key].id
# route_table_id = aws_route_table.route_table_private_db.id
#}

# resource "aws_route_table_association" "route_table_association_private_db" {
#  subnet_id      = aws_subnet.subnets_private_db.id
#  route_table_id = aws_route_table.route_table_private_db.id
# }

 

 

Vpc

 

resource "aws_vpc" "vpc" {
  cidr_block = var.vpc_info.cidr_block_vpc
  enable_dns_support = true
  enable_dns_hostnames = true

  tags = {
    "Name" = "${var.common_info.env}-${var.vpc_info.vpc_name}"
  }
}

 

Main.tf

모듈들을 main.tf에서 최종적으로 생성한다. Terraform cli는 이 Main.tf가 있는 폴더나 디렉토리에서 실행되어야 한다.

 

module "vpc" {
  source = "../../modules/vpc"

  common_info = local.common_info
  common_tags = local.common_tags
  vpc_info = local.vpc_info
}

module "bastion" {
  source = "../../modules/bastion"

  common_info = local.common_info
  common_tags = local.common_tags
  vpc_info = local.vpc_info
  vpc_id = module.vpc.vpc_id

  subnets_public_ids  = module.vpc.subnets_public_ids
}

 

 

Terraform apply 및 결과 확인

 

 

 

정상적으로 생성이 된 것을 확인 가능하다.

 

Terraform destroy

 

 

삭제된 것 확인 가능하다.

 

Bastion ssh 접근 확인

Mobaxterm을 사용해서 확인하였다.

 

 

정상적으로 접근이 되는 것을 확인 가능하다.