1
2
3
T101(=Terraform 101 Study)는 Terraform 실무 실습 스터디입니다.
CloudNet@ Gasida(가시다)이 진행하시며, 책 "Terraform Up & Running"을 기반으로 진행하고 있습니다.
22년 9월 기준 학습 내용입니다. 블로그 이주로 다시 포스트합니다. 

지난 글에서는 테라폼으로 EKS Private 클러스터를 구성하였다. 오늘은 EKS 클러스터 구축 이후, 운영적인 부분으로 테라폼으로 EKS 관리를 위한 추가 설정에 대해 소개하고자 한다.

추가 설정은 총 3가지로 나눠 다룰 예정이다. 테라폼 상태 관리, 상태 격리와 마지막으로 AWS 서비스에 접근하기 위한 보안 설정이다. 테라폼 상태 관리와 상태 격리는 테라폼에서 인프라(EKS) 관리를 위한 기능이다. 구체적으로는 테라폼 상태 파일을 팀 단위에서 관리하기 위한 방법과 모듈 분리에 대한 방법을 소개할 것이다. AWS 서비스에 접근하기 위한 보안 설정은 EKS 의 기능 확장을 위한 사전 작업으로 필요한 과정이다. 기능 확장을 위해서는 Addon 이라는 모듈의 설치가 필요하며 각 Addon 들은 AWS 서비스들 이용하기에 필요한 보안 작업이다.

마찬가지로 이번 포스트에 대한 코드들은 필자의 깃허브 레파지토리에서 확인이 가능하다.

상태 관리

테라폼에서 선언한 상태 정보들은 실행 디렉토리 내 terraform.tfstate 파일에 저장된다. terraform.tfstate 파일은 테라폼 내 프라이빗 API 로 오직 테라폼 내부에서만 사용이 가능하며 직접 편집하거나 직접 읽는 코드로 작성해서는 안된다.

이처럼 테라폼에서는 tfstate 파일을 통해 상태를 관리하지만, 팀 단위의 버전 관리시 추가 작업이 필요하다. 예를 들면, 팀 단위로 테라폼을 사용했을 시 관리자마다 테라폼에 대한 상태파일이 독립적으로 생성되어 인프라 유지에 어려움이 있을 것이다.

이에 대한 해결책으로 원격 상태 스토리지를 이용한다. 원격 상태 스토리지를 이용하면 테라폼 상태 파일이 원격 스토리지에 저장되며 관리자들은 동일한 스토리지에서 상태 파일을 바라보기 때문에 버전 관리가 가능해진다.

기본 상태 파일과 원격 상태 파일에 대한 비교 (출처.StakSimplify)

기본 상태 파일과 원격 상태 파일에 대한 비교 (출처.StakSimplify)

상태 관리 구현

상태 관리는 테라폼 내 기능인 Backend 을 통해 구현이 가능하다.Backend 는 상태 파일을 저장하고 동시 수정을 차단(locking)할 수 있는 기능을 제공한다. 필자 또한 해당 기능을 사용하여 상태 파일을 원격 스토리지에 저장시키도록 설정하였다. 원격 스토리지는 AWS에서 제공하는 객체 스토리지 서비스인 S3와 동시 수정을 제어할 수 있는 DynamoDB를 사용하였다. Backend 기능을 사용하기 전 원격 스토리지인 S3, DynamoDB 구축이 필요하다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 00-ekscluster-remote-stoarge/c0-remote-storage.tf
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_s3_bucket" "t101-remote-bucket" {
  bucket = <Bucket_NAME>
}

# Enable versioning so you can see the full revision history of your state files
resource "aws_s3_bucket_versioning" "t101-remote-bucket_versioning" {
  bucket = aws_s3_bucket.t101-remote-bucket.id
  versioning_configuration {
    status = "Enabled"
  }
}

resource "aws_dynamodb_table" "t101-remote-dynamodbtable" {
  name         = "terraform-locks"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "LockID"

  attribute {
    name = "LockID"
    type = "S"
  }
}

output "s3_bucket_arn" {
  value       = aws_s3_bucket.t101-remote-bucket.arn
  description = "The ARN of the S3 bucket"
}

output "dynamodb_table_name" {
  value       = aws_dynamodb_table.t101-remote-dynamodbtable.name
  description = "The name of the DynamoDB table"
}

Backend 사용 방법은 간단하다. 위에서 생성한 S3 버켓과 DynamoDB 이름을 작성하면 된다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 
# Terraform Settings Block
terraform {
  required_version = ">= 1.0.0"
  required_providers {
    aws = {
      source = "hashicorp/aws"
      version = "~> 3.70"
     }
  }
  # Adding Backend as S3 for Remote State Storage
  backend "s3" {
    bucket = <Bucket_NAME>
    key    = "t101/eks-cluster/terraform.tfstate"
    region = "ap-northeast-2" 
 
    # For State Locking
    dynamodb_table = "t101-remote-dynamodbtable"    
  }  
}

# Terraform Provider Block
provider "aws" {
  region = var.aws_region
}

상태 격리

상태 파일 격리을 이용하면 이미 구축한 인프라 정보들을 기반으로 모듈을 분리시킬 수 있다. 이는 유지 보수성과 관리측면에서 정말 유용한 기능이다. 만약 하나의 상태 파일에서 모든 인프라 구성을 관리하게 된다면 코드에 대한 유지보수성이 떨어지며, 인프라 수정시 전체 인프라에 대한 구성 정보를 확인하게 되어 테라폼 실행 속도가 현저히 늦어질 것이다.

이번 글에서도 상태 파일을 이용하여 모듈을 분리하여 애드온을 설치한 내용들을 소개할 예정이다. 구체적으로는 지난 시간에 구축한 EKS 클러스터에 대해 독립적으로 테라폼 상태 파일을 구성하여 애드온을 설치할 예정이다. 밑의 그림은 상태 격리를 통한 모듈 분리 방법을 도식화한 그림이다. 상태 격리의 핵심은 상태 파일을 읽어오는 것으로 상태 관리에서 원격 스토리지에 저장한 상태파일을 읽어오는 것으로 이해하면 된다.

테라폼 구축과 addon 설치에 대한 모듈 분리

테라폼 구축과 addon 설치에 대한 모듈 분리

상태 파일 격리 구현

상태 파일 격리 구현은 모듈별 실행 디렉토리 분리하는 것으로 시작한다. 모듈 분리로 필자에 대한 디렉터리을 일부 확인하면 지난 시간에 구축한 EKS 클러스터 내용을 확장하였다. 또한, 앞서 구성한 원격 스토리지 저장에 대한 모듈(00-ekscluster-remote-stoarge)을 분리하였고 바로 뒤에 소개할 보안 모듈(02-eks-irsa-terraform-manifests)을 분리하였다.

모듈 분리 디렉터리

모듈 분리 디렉터리

격리 파일 구현의 핵심은 프로바이더를 통한 상태 읽기다. 원격 스토리지에 저장한 EKS 상태 파일을 읽는 방법으로 테라폼 Kubernetes 프로바이더와 Helm 프로바이더를 사용하였다. 아래 코드처럼 data를 선언하여 EKS 클러스터의 상태 파일이 저장된 파일들을 불러온다.

1
2
3
4
5
6
7
8
# Terraform Remote State Datasource - Remote Backend AWS S3
data "terraform_remote_state" "eks" {
  backend = "s3"
  config = {
    bucket = <Bucket_NAME>
    key    = "t101/eks-cluster/terraform.tfstate"
    region = var.aws_region
  }

그 다음 과정으로 프로바이더를 이용하여 EKS 클러스터 접근 정보를 등록한다. 접근 등록 이후 프로바이더 내 리소스들을 사용하면 자동으로 EKS 클러스터 내부에 쿠버네티스 리소스가 배포된다.

1
2
3
4
5
6
# Terraform Kubernetes Provider
provider "kubernetes" {
  host = data.terraform_remote_state.eks.outputs.cluster_endpoint 
  cluster_ca_certificate = base64decode(data.terraform_remote_state.eks.outputs.cluster_certificate_authority_data)
  token = data.aws_eks_cluster_auth.cluster.token
}

IRSA (IAM Roles for Service Accounts)

EKS addon 서비스를 사용하기 위한 추가 설정으로 AWS 서비스들에 대한 권한을 허용받기 위한 IRSA 설정이 필요하다. 설정 이유는 addon 서비스가 AWS 서비스를 이용하여 DNS, 로드밸런서, 볼륨 등의 기능을 확장하기 때문이다.

Addon 서비스 사용 AWS 서비스 목적
EBS CSI Controller AWS EBS Volumes 영구 블록 스토리지 볼륨 사용
EFS CSI Controller AWS EFS Volumes 공용 파일 스토리지 볼륨 사용
Load Balancer Controller AWS Elastic Load Balancer(ELB) 네트워크 로드밸런스 사용
External DNS Controller AWS Route53 DNS 사용

표에 보면 알겠지만 EKS addon을 요약하자면, AWS 서비스를 사용한다. 하지만 EKS 내부 클러스터에서 AWS 서비스를 사용하기 위해선 AWS 서비스를 사용할 권한이 필요하다. 예를 들어 EKS 내부 클러스터의 리소스에서 AWS S3를 사용한다고 하면 access denied 으로 S3의 접근이 안될 것이다.

S3 접근 에러 (출처.StakSimplify)

S3 접근 에러 (출처.StakSimplify)

따라서, AWS 서비스를 접근하기 위해 IRSA 설정이 필요하다. EKS IRSA은 EKS 클러스터 내 서비스 어카운트가AWS 서비스의 권한을 위임받는 과정이다. 권한 위임은 AWS 자격 증명 서비스인 IAM를 통해 진행되며 권한 위임은 다음과 같이 진행된다.

IRSA를 통한 AWS 서비스 접근 과정

IRSA를 통한 AWS 서비스 접근 과정

Pre. AWS 서비스 이용 권한을 가진 IAM Role 를 k8s Service Account에 할당한다.

  1. AWS 서비스 접근시, k8s Service Account 가 IAM에 보안 자격 증명 토큰(JWT 토큰)을 요청한다.
  2. IAM 은 AWS STS를 통해 임시 자격 증명 토큰 발급한다.
  3. AWS STS는 발급한 접근 토큰을 k8s Service Account 에 위임한다.
  4. 접근 토큰을 가진 k8s Service Account 는 AWS 서비스에 접근이 가능해진다.

IRSA 설정

IRSA 설정하기 위해서 가장 먼저 EKS에 OIDC 자격 증명 프로바이더를 연결해야한다. 여기서 OIDC 자격 프로바이더는 EKS에서 AWS 서비스를 이용할 수 있도록 지원하기 위해 필요로 한다. OIDC 자격 증명 프로바이더 생성과 연결으로 테라폼 AWS 리소스인 aws_iam_openid_connect_provider 를 사용하였다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#01-ekscluster-terraform-manifests/c6-02-iam-oidc-connect-provider.tf
data "aws_partition" "current" {}

# Resource: AWS IAM Open ID Connect Provider
resource "aws_iam_openid_connect_provider" "oidc_provider" {
  client_id_list  = ["sts.${data.aws_partition.current.dns_suffix}"]
  thumbprint_list = [var.eks_oidc_root_ca_thumbprint]
  url             = aws_eks_cluster.eks_cluster.identity[0].oidc[0].issuer # 1 
 
  tags = merge(
    {
      Name = "${var.cluster_name}-eks-irsa"
    },
    local.common_tags
  )
  • data.aws_partition 는 현재 테라폼이 운영 중인 파티션 (지역) 정보를 가져오는 것에 사용된다.
  • resource.aws_iam_openid_connect_provider.client_id_list : 자격 증명(STS) 정보가 입력된다.
  • resource.aws_iam_openid_connect_provider.thumbprint_list : 자격 증명 CA로 기본 할당(2037년까지 유효) 정보를 사용하였다.
  • resource.aws_iam_openid_connect_provider.url : 공급자로 EKS 정보가 입력된다.

IRSA 생성 시 AWS 콘솔 - IAM - 자격증명 공급자에서 다음과 같이 확인할 수 있다.

3-3.png

OIDC 자격 증명 프로바이더 설정이 끝났다. 이제 IAM 정책 생성 및 k8s ServiceAccount 에 할당하면 서비스에 접근이 가능해진다. 앞으로의 addons 서비스들도 마찬가지로 필요한 권한(IAM role)을 가진 정책을 생성하여 ServiceAccount 에 할당할 것이다. 이번 장에서는 예제로 파드에서 AWS S3에 접근하기 위한 데모를 진행하겠다.

다음의 테라폼 코드는 Iam role, policy 생성하는 코드이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_arn
#data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_extract_from_arn

# Resource: Create IAM Role and associate the EBS IAM Policy to it
resource "aws_iam_role" "irsa_iam_role" {
  name = "${local.name}-irsa-iam-role"

  # Terraform's "jsonencode" function converts a Terraform expression result to valid JSON syntax.
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRoleWithWebIdentity"
        Effect = "Allow"
        Sid    = ""
        Principal = {
          Federated = "${data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_arn}"
        }
        Condition = {
          StringEquals = {            
            "${data.terraform_remote_state.eks.outputs.aws_iam_openid_connect_provider_extract_from_arn}:sub": "system:serviceaccount:default:irsa-demo-sa"
          }
        }        

      },
    ]
  })

  tags = {
    tag-key = "${local.name}-irsa-iam-role"
  }
}

# Associate IAM Role and Policy
resource "aws_iam_role_policy_attachment" "irsa_iam_role_policy_attach" {
  policy_arn = "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
  role       = aws_iam_role.irsa_iam_role.name
}
  • resource.aws_iam_role.irsa_iam_role 는 IRSA 에서 STS 을 이용하기 위해 처음 role 생성시 K8s serviceaccount 에 STS 사용 위임정책을 선언한다.
  • resource.aws_iam_role_policy_attachment.irsa_iam_role_policy_attach 는 S3 서비스를 읽기 위한 정책을 role에 할당한다.

IAM 설정이 끝났다면 AWS 서비스를 사용할 K8s 리소스에 생성한 serviceaccount를 할당하면 IRSA 설정이 끝난다. 아래 코드는 S3 버켓을 확인하기 위한 잡 리소스이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Resource: Kubernetes Job
resource "kubernetes_job_v1" "irsa_demo" {
  metadata {
    name = "irsa-demo"
  }
  spec {
    template {
      metadata {
        labels = {
          app = "irsa-demo"
        }
      }
      spec {
        service_account_name = kubernetes_service_account_v1.irsa_demo_sa.metadata.0.name 
        container {
          name    = "irsa-demo"
          image   = "amazon/aws-cli:latest"
          args = ["s3", "ls"]
          #args = ["ec2", "describe-instances", "--region", "${var.aws_region}"] # Should fail as we don't have access to EC2 Describe Instances for IAM Role
        }
        restart_policy = "Never"
      }
    }
  }
}
  • 쿠버네티스 리소스 선언 파일 상에서는 별도의 IRSA 설정 필요없이 serviceaccount만 할당하면 된다!

사족이지만 테라폼에서 쿠버네티스 리소스 생성시, context deadlin exceeded 에러를 볼 수 있다.

3-4.png

해당 에러는 테라폼 실행 PC에서 EKS 컨트롤플레인의 API 서버에 접근하지 못할 때 발생한다. 이를 해결하기 위해서는 API 서버의 엔드포인트를 프라이빗으로 열고 테라폼 실행 PC에서만 접근이 가능하도록 설정하면 된다.

Serviceaccount 할당 후 쿠버네티스 잡을 테라폼으로 배포하면 다음과 같이 잡의 로그를 통해 S3의 버켓을 확인할 수 있다.

3-5.png

끝으로

이것으로 테라폼에서 EKS 관리를 위한 3가지의 과정을 끝냈다! 다음 포스트 글에서는 EKS 기능 확장을 위한 addon 설치 방법을 다룰 예정이다.