DevOpsTerraformEKS05. AWS EKS에 Istio 설치

05. AWS EKS에 Istio 설치

Istio

Istio는 Service Mesh의 하나로 MSA 구조를 위한 트래픽 운영과 보안 측면에서 우수하며 mTLS와 Canary 배포 기능도 제공한다.

Istio는 IstioCTL이라는 CLI command로도 설치가 가능하나, Terraform에서는 보통 helm으로 설치한다.
혹시 EKS에 Istio를 사용하지 않고 Nginx Ingress 혹은 AWS App Mesh를 사용할 수도 있기 때문에 그냥 별도의 폴더에 분리해서 작성하였다.
별도의 폴더에 작성했기 때문에 provider를 다시 선언해줘야한다.

파일 구조

02-istio-terraform/
├── 0-helm-provider.tf          # Provider configurations
├── 1-istio-base.tf             # Istio foundation (CRDs)
├── 2-istiod.tf                 # Istio control plane
├── 3-istio-gateway.tf          # Istio ingress gateway + AWS resources
├── 4-route53.tf                # DNS configuration
├── istio-ingress-values.yaml.tftpl  # Gateway configuration template
├── terraform.tfvars            # 변수 값 할당
├── variables.tf                # 변수 값 선언
└── outputs.tf                  # Output values

참고로 변수 값의 파일 이름을 표준인 terraform.tfvars로 만들었다면, 실행 시에 -var-file="terraform.tfvars" 명령어를 생략해도 된다. 그래서 적용시에는 다음 명령어만 입력하면 된다.

terraform init
terraform apply --auto-approve

Providers

앞 글까지의 기본 EKS cluster를 생성하고 다음 명령어로 연결했기 때문에

aws eks update-kubeconfig --name cluster_name --region ap-northeast-2

data를 이용해 EKS cluster 이름부터 가져온다.

0-helm-provider.tf

data "aws_eks_cluster" "main" {
  name = var.eks_cluster_name
}
 
data "aws_eks_cluster_auth" "main" {
  name = var.eks_cluster_name
}

그리고 위에서 가져온 cluster 이름으로
쿠버네티스에 연결하고 helm을 사용할 수 있도록 설정해준다.

# Connect to existing EKS cluster and configure Kubernetes provider with EKS credentials
provider "kubernetes" {
  host                   = data.aws_eks_cluster.main.endpoint
  cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data)
  token                  = data.aws_eks_cluster_auth.main.token
}
 
provider "helm" {
  kubernetes {
    host                   = data.aws_eks_cluster.main.endpoint
    cluster_ca_certificate = base64decode(data.aws_eks_cluster.main.certificate_authority[0].data)
    token                  = data.aws_eks_cluster_auth.main.token
  }
}

Istio 설치

Istio는 다음 3가지 component를 설치해야한다.

1. Istio Base

CRD(Custom Resource Definitions)를 포함한 리소스를 설치한다. VirtualServices, Gateways, DestinationRules과 다른 Istio 리소스들을 포함한다.

2. Istiod

Istio의 control plane → service mesh를 설정하고 운영

3. Istio Gateway

Istio의 data plane에 속함. Ingress traffic을 담당. Envoy proxy를 포함.

3가지 설치 순서가 있다.
우선은 Istiod와 Istio Gateway에서 Istio base에 있는 CRD를 참조하기 때문에 Istio base가 먼저 설치되어야 한다.
그래서 Istiod의 코드를 보면

depends_on = [helm_release.istio_base]

이것을 넣어줘서 base 설치가 끝난 후에 설치가 되도록 한다.

1-istio-base.tf 전체 코드

resource "helm_release" "istio_base" {
  name = "my-istio-base-release"
 
  repository       = "https://istio-release.storage.googleapis.com/charts"
  chart            = "base"
  namespace        = "istio-system"
  create_namespace = true
  version          = var.istio_version
 
  set {
    name  = "global.istioNamespace"
    value = "istio-system"
  }
}

위 코드의

  set {
    name  = "global.istioNamespace"
    value = "istio-system"
  }

이 부분은 전역 parameter로 모든 Istio component에게 istio의 control plane(Istiod)이 istio-system namespace이 있다고 알려주는 역할이다.

2-istiod.tf 전체 코드

resource "helm_release" "istiod" {
  name = "my-istiod-release"
 
  repository       = "https://istio-release.storage.googleapis.com/charts"
  chart            = "istiod"
  namespace        = "istio-system"
  create_namespace = true
  version          = var.istio_version
 
  set {
    name  = "telemetry.enabled"
    value = "true"
  }
 
  set {
    name  = "global.istioNamespace"
    value = "istio-system"
  }
 
  set {
    name  = "meshConfig.ingressService"
    value = "istio-gateway"
  }
 
  set {
    name  = "meshConfig.ingressSelector"
    value = "gateway"
  }
 
  depends_on = [helm_release.istio_base]
}

3-istio-gateway.tf 코드

resource "helm_release" "gateway" {
  name = var.gateway_name
 
  repository       = "https://istio-release.storage.googleapis.com/charts"
  chart            = "gateway"
  namespace        = var.istio_namespace
  create_namespace = true
  version          = var.istio_version
 
  depends_on = [
    helm_release.istio_base,
    helm_release.istiod
  ]
 
  # overrides - change default CLB to NLB
  values = [templatefile("istio-ingress-values.yaml.tftpl", {
    lb_security_group_id               = aws_security_group.istio-gateway-lb.id
    lb_healthcheck_healthy_threshold   = var.lb_healthcheck_healthy_threshold
    lb_healthcheck_unhealthy_threshold = var.lb_healthcheck_unhealthy_threshold
    lb_healthcheck_timeout             = var.lb_healthcheck_timeout
    lb_healthcheck_interval            = var.lb_healthcheck_interval
  })]
}

아래쪽 values 부분:
우선 일반적인 .yaml 파일에는 변수를 넣을 수 없다.
변수를 넣기 위해서는 똑같이 .yaml 파일로 같은 문법으로 작성하되 확장자만 .yaml.tftpl로 작성하고 위처럼 사용할 변수를 인자로 넣어준다.

istio-ingress-values.yaml.tftpl

labels:
  istio: gateway
 
# Ensure proper resources for the gateway
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 1000m
    memory: 1024Mi
 
# Configure service for AWS NLB with ACM certificate
service:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: nlb
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-attributes: load_balancing.cross_zone.enabled=true
    service.beta.kubernetes.io/aws-load-balancer-security-groups: ${lb_security_group_id}
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    # Increase health check threshold for more stability
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-healthy-threshold: "${lb_healthcheck_healthy_threshold}"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-unhealthy-threshold: "${lb_healthcheck_unhealthy_threshold}"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-timeout: "${lb_healthcheck_timeout}"
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-interval: "${lb_healthcheck_interval}"
  type: LoadBalancer
  ports:
  - name: http
    port: 80
    targetPort: 80
  - name: https
    port: 443
    targetPort: 443

Azure혹은 GCP는 default가 NLB로 알고 있는데, AWS는 기본적으로 Istio를 설치하면 자동으로 CLB(Classic Load Balancer)가 설치가 되는데, AWS에서는 NLB 혹은 ALB의 설치를 권장한다. 이를 위해 위에서처럼 nlb를 annotation으로 위와 같이 명시해 주어야지 설치가 된다.
이전 글에 설명한대로 80포트와 443포트는 Istio Gateway에 TLS termination을 시켜주기 위해 그대로 통과시킨다.

3-istio-gateway.tf 추가 코드

resource "null_resource" "cleanup_loadbalancer" {
  triggers = {
    gateway_name = helm_release.gateway.name
    namespace    = helm_release.gateway.namespace
  }
 
  provisioner "local-exec" {
    when    = destroy
    command = <<-EOT
      echo "Cleaning up LoadBalancer services in istio-ingress namespace..."
      kubectl delete svc -n istio-ingress --all --timeout=60s || true
      echo "Waiting for LoadBalancer cleanup..."
      sleep 30
    EOT
  }
 
  depends_on = [helm_release.gateway]
}

위 Terraform 코드에서 우리가 helm_release를 통해 Istio gateway를 설치하면 자동으로 Load Balancer가 붙여지는 구조이고, 우리가 명시적으로 Load Balancer를 생성하는 resource구문을 사용하지 않기 때문에, 생성할 때는 잘 되지만, terraform destroy시 LB가 삭제가 안 되는 경우가 생긴다. 그래서 terraform destory시에 자동으로 생성된 LB가 삭제될 수 있도록 LB를 handle 해주는 null resource를 만든다.