본문 바로가기!

EKS

[EKS] AWS EKS의 Storage & Nodegroup

728x90
반응형

1.  Kubenetes Storage 

  • 파드(컨테이너) 환경에서 별도의 마운트를 진행하지 않는다면 데이터는 컨테이너가 종료될 경우 모두 삭제됩니다.
  • 즉, 상태가 저장되지 않는(Stateless) 형태로 동작합니다.
  • 하지만 Kubenetes로 운영을 하다보면 DB(데이터베이스)와 같이 데이터가 보존될 필요가 있는 경우가 생기게 되며, Kubenetes에서는 PV/PVC를 통해 데이터를 마운트 시켜 상태를 보존할 수 있는 Stateful 형태로 관리할 수 있습니다.

 

  • Persistent Volume(PV)은 실제 스토리지 볼륨으로 PV는 Kubernetes Pods의 수명 주기와 별개로 자체의 수명 주기를 가집니다. 예를 들어 NFS, AWS EBS, Ceph등을 사용할 수 있습니다.
  • 접근 모드(Access mode)
    • 접근 모드는 PV가 생성될 때 설정되며 Kubernetes가 볼륨을 마운트하는 방법으로 Persistent Volumes은 세 가지의 접근 모드 지원됩니다.
      • ReadWriteOnce: 볼륨은 동시에 하나의 노드에서만 읽기/쓰기를 허용합니다.
      • ReadOnlyMany: 볼륨은 동시에 여러 노드에서 읽기 전용 모드를 허용합니다.
      • ReadWriteMany: 볼륨은 동시에 여러 노드에서 읽기/쓰기를 허용합니다.
  • PersistentVolumeClaim(PVC)은 Kubernetes는 PV를 포드에 연결하는데 필요한 추가 추상화 계층을 뜻합니다.

 

 

2.  CSI (Container Storage Interface)

  • Kubernetes source code 내부에 존재하는 AWS EBS provisioner는 당연히 Kubernetes release lifecycle을 따라서 배포되므로, provisioner 신규 기능을 사용하기 위해서는 Kubernetes version을 업그레이드해야 하는 제약 사항이 있습니다.
  • 따라서, Kubernetes 개발자는 Kubernetes 내부에 내장된 provisioner (in-tree)를 모두 삭제하고, 별도의 controller Pod을 통해 동적 provisioning을 사용할 수 있도록 만들었습니다. 이것이 바로 CSI (Container Storage Interface) driver입니다.

 

  • AWS EBS CSI driver 역시 아래와 같은 구조를 가지는데, 오른쪽 StatefulSet 또는 Deployment로 배포된 controller Pod이 AWS API를 사용하여 실제 EBS volume을 생성하는 역할을 합니다.
  • 왼쪽 DaemonSet으로 배포된 node Pod은 AWS API를 사용하여 Kubernetes node (EC2 instance)에 EBS volume을 attach 해줍니다.

 

3.  AWS EBS Controller

3-1)  EBS CSI Driver

  • Elastic Block Store CSI Driver는 Amazon EBS 볼륨의 수명주기를 관리하기 위해 컨테이너 조정자가 사용하는 CSI 인터페이스를 제공합니다.
  • persistentvolume, persistentvolumeclaim의 accessModes는 ReadWriteOnce로 설정해야 합니다.
    • EBS Storage의 기본 설정이 동일 AZ(가용역역)에 있는 EC2 인스턴스에 배포된 파드에 연결되기 때문입니다.

 

3-2)  Terraform을 통한 EBS CSI Driver 배포

  • EBS CSI Driver를 위한 Role 생성을 생성합니다.
data "aws_iam_policy_document" "ebs_csi_role_assume_role_policy" {
  statement {
    sid     = ""
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_iam_openid_connect_provider.oidc_cluster.url, "https://", "")}:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_iam_openid_connect_provider.oidc_cluster.url, "https://", "")}:sub"
      values   = ["system:serviceaccount:kube-system:ebs-csi-controller-sa"]
    }

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.oidc_cluster.arn]
    }
  }
}

resource "aws_iam_role" "ebs_csi_driver_role" {
  assume_role_policy = data.aws_iam_policy_document.ebs_csi_role_assume_role_policy.json
  name               = "${local.company_name}-ebs-csi-driver-role"

  depends_on = [aws_iam_openid_connect_provider.oidc_cluster]

  tags = {
    "ServiceAccount"          = "ebs-csi-controller-sa"
    "ServiceAccountNameSpace" = "kube-system"
  }
}

resource "aws_iam_role_policy_attachment" "ebs_csi_driver_policy_attach" {
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy"
  role       = aws_iam_role.ebs_csi_driver_role.name
}

 

  • Helm을 통해 EBS CSI Driver를 생성하는 경우 CSI Driver를 사용하는 Storage Class를 Custom하게 설치할 것이기 때문에 다음과 같이 yaml파일을 생성합니다.
  • eks 생성 시 기본 제공하는 'gp2'보다 'gp3' 이 성능과 가격 측면에서 우월하기 때문에 gp3로 생성합니다. 
controller:
  replicaCount: 3
  region: ap-northeast-2
  serviceAccount:
    create: false
    # name: ebs-csi-controller-sa
    # annotations:
    #   eks.amazonaws.com/role-arn: ${ebs-csi-controller-role-arn}

node:
  tolerateAllTaints: true

storageClasses:
  - name: gp3
    annotations:
      storageclass.kubernetes.io/is-default-class: "true"
    volumeBindingMode: WaitForFirstConsumer
    reclaimPolicy: Delete
    allowVolumeExpansion: true
    parameters:
      type: gp3
      csi.storage.k8s.io/fstype: ext4

 

  • Terraform의 Kubenetes Provider를 사용하여 Service Account를 생성합니다.
  • Helm Provider를 이용하여 EBS CSI Driver를 생성합니다.
  • Helm Provider에서 Service Account를 생성하지 않고 미리 만든 Service Account를 적용할 것이기 때문에 set을 통해 sa create 부분을 false로 적용하였습니다.
resource "kubernetes_service_account" "ebs_csi_service_account" {
  metadata {
    name      = "ebs-csi-controller-sa"
    namespace = "kube-system"
    labels = {
      "app.kubernetes.io/name" = "aws-ebs-csi-driver"
    }
    annotations = {
      "eks.amazonaws.com/role-arn" = module.eks.ebs_csi_driver_role_arn
    }
  }
  depends_on = [module.eks]
}

resource "helm_release" "aws_ebs_csi_driver" {
  name = "aws-ebs-csi-driver"

  repository = "https://kubernetes-sigs.github.io/aws-ebs-csi-driver"
  chart      = "aws-ebs-csi-driver"
  namespace  = "kube-system"
  version    = "2.24.0"

  set {
    name  = "controller.serviceAccount.create"
    value = "false"
  }
  set {
    name  = "controller.serviceAccount.name"
    value = "ebs-csi-controller-sa"
  }

  values = [
    templatefile("${path.module}/custom-yaml/ebs_csi_values.yaml",
      {
        ebs-csi-controller-role-arn = module.eks.ebs_csi_driver_role_arn,
      }
    )
  ]

  depends_on = [
    kubernetes_service_account.ebs_csi_service_account,
    module.eks
  ]
}

 

  • 생성확인

 

 

3-3)  PVC/PV Pod 테스트

# PVC 생성
cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3
EOT
kubectl apply -f awsebs-pvc.yaml
kubectl get pvc,pv

# 파드 생성
cat <<EOT > awsebs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim
EOT
kubectl apply -f awsebs-pod.yaml

 

 

# 워커노드에서 파드에 추가한 EBS 볼륨 모니터링
while true; do aws ec2 describe-volumes --filters Name=tag:ebs.csi.aws.com/cluster,Values=true --query "Volumes[].{VolumeId: VolumeId, VolumeType: VolumeType, InstanceId: Attachments[0].InstanceId, State: Attachments[0].State}" --output text; date; sleep 1; done

 

 

# PVC, 파드 확인
kubectl get pvc,pv,pod
kubectl get VolumeAttachment

 

 

curl https://krew.sh/df-pv | bash
source ~/.bashrc
kubectl df-pv

 

  • 현재 pv 의 이름을 기준하여 4G > 10G 로 증가 : .spec.resources.requests.storage의 4Gi 를 10Gi로 변경 테스트
kubectl get pvc ebs-claim -o jsonpath={.spec.resources.requests.storage} ; echo
kubectl get pvc ebs-claim -o jsonpath={.status.capacity.storage} ; echo
kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

  • 변경 후 확인

 

 

4.  Snapshot CSI Controller

4-1) Snapshot CSI Controller

  • CSI(컨테이너 스토리지 인터페이스) Snapshot Controller를 사용하면 Amazon EBS CSI 드라이버와 같은 호환 가능한 CSI 드라이버에서 스냅샷 기능을 사용할 수 있습니다.

 

  • Install Snapshot CRDs
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl apply -f snapshot.storage.k8s.io_volumesnapshots.yaml,snapshot.storage.k8s.io_volumesnapshotclasses.yaml,snapshot.storage.k8s.io_volumesnapshotcontents.yaml
kubectl get crd | grep snapshot
kubectl api-resources  | grep snapshot

 

 

  • Install Common Snapshot Controller
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
curl -s -O https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/master/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
kubectl apply -f rbac-snapshot-controller.yaml,setup-snapshot-controller.yaml
kubectl get deploy -n kube-system snapshot-controller
kubectl get pod -n kube-system -l app=snapshot-controller

 

  • Install Snapshotclass
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/examples/kubernetes/snapshot/manifests/classes/snapshotclass.yaml
kubectl apply -f snapshotclass.yaml
kubectl get vsclass # 혹은 volumesnapshotclasses

 

 

4-2)  PVC/Pod 생성 테스트

  • PVC 및 파드 생성
# PVC 생성
cat <<EOT > awsebs-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 4Gi
  storageClassName: gp3
EOT
kubectl apply -f awsebs-pvc.yaml
kubectl get pvc,pv

# 파드 생성
cat <<EOT > awsebs-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  terminationGracePeriodSeconds: 3
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo \$(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim
EOT
kubectl apply -f awsebs-pod.yaml

 

  • Pod 의 파일 내용 추가 저장 확인

 

  • VolumeSnapshot 생성 : Create a VolumeSnapshot referencing the PersistentVolumeClaim name >> EBS 스냅샷 확인
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ebs-volume-snapshot.yaml
cat ebs-volume-snapshot.yaml | yh
kubectl apply -f ebs-volume-snapshot.yaml

 

  • VolumeSnapshot 확인
kubectl get volumesnapshot
kubectl get volumesnapshot ebs-volume-snapshot -o jsonpath={.status.boundVolumeSnapshotContentName} ; echo
kubectl describe volumesnapshot.snapshot.storage.k8s.io ebs-volume-snapshot
kubectl get volumesnapshotcontents

 

 

  • VolumeSnapshot ID 확인
kubectl get volumesnapshotcontents -o jsonpath='{.items[*].status.snapshotHandle}' ; echo

 

 

5.  AWS EFS Controller

5-1)  EFS CSI Driver

  • Elastic File System CSI Driver는 AWS에서 실행되는 Kubernetes 클러스터가 Amazon EFS 파일 시스템의 수명 주기를 관리할 수 있도록 하는 CSI 인터페이스를 제공합니다.

 

5-2)  Terraform을 통한 EFS CSI Driver 배포

  • EFS CSI Driver를 위한 Role 생성을 생성합니다.
data "http" "efs_controller_policy" {
  url = "https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json"

  request_headers = {
    Accept = "application/json"
  }
}

resource "aws_iam_policy" "efs_controller_policy" {
  name        = "${local.company_name}-efs-controller-policy"
  description = "efs controller policy"
  policy      = data.http.efs_controller_policy.response_body
}

data "aws_iam_policy_document" "efs_csi_role_assume_role_policy" {
  statement {
    sid     = ""
    effect  = "Allow"
    actions = ["sts:AssumeRoleWithWebIdentity"]

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_iam_openid_connect_provider.oidc_cluster.url, "https://", "")}:aud"
      values   = ["sts.amazonaws.com"]
    }

    condition {
      test     = "StringEquals"
      variable = "${replace(aws_iam_openid_connect_provider.oidc_cluster.url, "https://", "")}:sub"
      values   = ["system:serviceaccount:kube-system:efs-csi-*"]
    }

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.oidc_cluster.arn]
    }
  }
}

resource "aws_iam_role" "efs_csi_driver_role" {
  assume_role_policy = data.aws_iam_policy_document.efs_csi_role_assume_role_policy.json
  name               = "${local.company_name}-efs-csi-driver-role"

  depends_on = [aws_iam_openid_connect_provider.oidc_cluster]

  tags = {
    "ServiceAccount"          = "efs-csi-controller-sa"
    "ServiceAccountNameSpace" = "kube-system"
  }
}

resource "aws_iam_role_policy_attachment" "efs_csi_driver_policy_attach" {
  policy_arn = aws_iam_policy.efs_controller_policy.arn
  role       = aws_iam_role.efs_csi_driver_role.name
}

 

  • Terraform의 Kubenetes Provider를 사용하여 Service Account를 생성합니다.
  • Helm Provider를 이용하여 EFS CSI Driver를 생성합니다.
  • Helm Provider에서 Service Account를 생성하지 않고 미리 만든 Service Account를 적용할 것이기 때문에 set을 통해 sa create 부분을 false로 적용하였습니다.
resource "kubernetes_service_account" "efs_csi_service_account" {
  metadata {
    name      = "efs-csi-controller-sa"
    namespace = "kube-system"
    labels = {
      "app.kubernetes.io/name" = "aws-efs-csi-driver"
    }
    annotations = {
      "eks.amazonaws.com/role-arn" = module.eks.efs_csi_driver_role_arn
    }
  }
  depends_on = [module.eks]
}



resource "helm_release" "aws_efs_csi_driver" {
  name = "aws-efs-csi-driver"

  repository = "https://kubernetes-sigs.github.io/aws-efs-csi-driver"
  chart      = "aws-efs-csi-driver"
  namespace  = "kube-system"
  version    = "2.5.0"

  set {
    name  = "controller.serviceAccount.create"
    value = "false"
  }
  
  set {
    name  = "controller.serviceAccount.name"
    value = "efs-csi-controller-sa"
  }

  depends_on = [
    kubernetes_service_account.efs_csi_service_account,
    module.eks
  ]
}

 

  • 생성 확인

 

 

5-3)  Terraform을 통한 EFS 배포

  • EFS CSI Driver를 사용하기 위해서는 EFS가 생성되어 있어야합니다.
resource "aws_security_group" "efs_sg" {
  name   = "efs_sg"
  vpc_id = module.vpc.vpc_id

  ingress {
    from_port   = 2049
    to_port     = 2049
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# EFS 파일 시스템 생성
resource "aws_efs_file_system" "efs" {
  encrypted = true
  performance_mode = "generalPurpose"
  throughput_mode = "bursting"

  lifecycle_policy {
    transition_to_ia = "AFTER_30_DAYS"
  }
}


resource "aws_efs_mount_target" "mount" {
  count           = length(module.subnets.ap_subnet_id)
  file_system_id  = aws_efs_file_system.efs.id
  subnet_id       = element(module.subnets.ap_subnet_id, count.index)
  security_groups = [aws_security_group.efs_sg.id]
}

 

  • 생성 확인

 

 

5-4)  EFS 파일시스템을 다수의 파드가 사용하게 설정 테스트

  • 실습 코드 clone
git clone https://github.com/kubernetes-sigs/aws-efs-csi-driver.git /root/efs-csi
cd /root/efs-csi/examples/kubernetes/multiple_pods/specs && tree

 

  • EFS 스토리지클래스 생성 및 확인
cat storageclass.yaml | yh
kubectl apply -f storageclass.yaml
kubectl get sc efs-sc

 

 

  • PV 생성 및 확인 : volumeHandle을 자신의 EFS 파일시스템ID로 변경
EfsFsId=$(aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text)
sed -i "s/fs-4af69aab/$EfsFsId/g" pv.yaml

 

kubectl apply -f pv.yaml
kubectl get pv; kubectl describe pv

 

 

  • PVC 생성 및 확인
cat claim.yaml | yh
kubectl apply -f claim.yaml
kubectl get pvc

 

 

  • 파드 생성 및 연동 : 파드 내에 /data 데이터는 EFS를 사용
cat pod1.yaml pod2.yaml | yh
kubectl apply -f pod1.yaml,pod2.yaml

 

 

  • 파드 정보 확인 : PV에 5Gi 와 파드 내에서 확인한 NFS4 볼륨 크리 8.0E의 차이는 무엇? 파드에 6Gi 이상 저장 가능한가?
kubectl exec -ti app1 -- sh -c "df -hT -t nfs4"
kubectl exec -ti app2 -- sh -c "df -hT -t nfs4"

 

 

  • 공유 저장소 저장 동작 확인

 

 

 

6.  NodeGroup

  • EKS Node는 관리형 노드그룹관리가 가능하며, 부하가 높아지면 Auto Scailing 기능을 제공합니다.
  • 온디맨드 인스턴스, 스팟 인스턴스, EKS 관리형 노드 그룹 3가지 프로세서 유형을 제공합니다.
  • 또한 인텔, AMD 및 ARM(AWS Graviton) 프로세서를 선택해서 사용 가능하며, AWS는 AWS Graviton 프로세서를 아마존 EC2에서 실행되는 클라우드 워크로드에 최적의 가격 성능을 제공하고 있습니다.
  • Graviton 기반 인스턴스는 인스턴스 유형 명명 규칙

 

 

  • Terraform을 통한 ARM 기반 graviton 프로세서를 사용하는 t4g.medium 을 노드 그룹에 생성 및 확인
resource "aws_eks_node_group" "test_worker_node_group" {
  cluster_name    = aws_eks_cluster.cluster.name
  node_group_name = "${local.company_name}-${local.server_env}-gravition-eksng"
  node_role_arn   = aws_iam_role.worker_ng_role.arn
  subnet_ids      = local.ap_subnet_id
  version         = local.eks_node_version
  ami_type        = "AL2_ARM_64"

  instance_types = ["t4g.medium"]
  disk_size = 30

  scaling_config {
    desired_size = 1  # 노드 그룹 내 현재 생성할 노드 수 
    min_size     = 1  # 노드 그룹이 유지할 최소 노드 수
    max_size     = 1  # 노드 그룹이 제한할 최대 노드 수
  }

  update_config {
    max_unavailable = 1
  }

  tags = {
    Name       = "${local.company_name}-${local.server_env}-gravition-eksng"
    Production = local.server_env
  }

  # Ensure that IAM Role permissions are created before and deleted after EKS Node Group handling.
  # Otherwise, EKS will not be able to properly delete EC2 Instances and Elastic Network Interfaces.
  depends_on = [
    aws_eks_cluster.cluster,
    aws_iam_role_policy_attachment.eks-AmazonEKSWorkerNodePolicy,
    # aws_iam_role_policy_attachment.eks-AmazonEKS_CNI_Policy,
    aws_iam_role_policy_attachment.eks-AmazonEC2ContainerRegistryReadOnly
  ]
}

 

  • taints 셋팅
aws eks update-nodegroup-config --cluster-name junho-test-eks --nodegroup-name junho-test-gravition-eksng --taints "addOrUpdateTaints=[{key=frontend, value=true, effect=NO_EXECUTE}]"

 

aws eks describe-nodegroup --cluster-name junho-test-eks --nodegroup-name junho-test-gravition-eksng | jq .nodegroup.taints

 

 

  • Run pods on Graviton
    • taint 설정을 통해 pod가 스케줄링 될때 ARM 기반 graviton 프로세서를 사용한 노드그룹인 junho-test-gravition-eksng에 생성되도록 합니다.
    • nodeAffinity 는 어느 자원에 붙을 지 명시하는 것이고, taint 는 toleration (용인) 이 적혀있는 리소스만 붙을 수 있도록 접근을 제한합니다.
    • NoExecute 옵션은 toleration이 없으면 파드가 스케줄링 되지 않으며, 기존에 실행되는 파드도 toleration이 없으면 종료시키게 됩니다.

 

728x90
반응형

'EKS' 카테고리의 다른 글

[EKS] Security  (0) 2024.04.14
[EKS] Autoscaling  (0) 2024.04.06
[EKS] Observability  (0) 2024.03.28
[EKS] AWS EKS의 Networking (VPC CNI, LB Controller)  (0) 2024.03.16
[EKS] AWS EKS의 특징과 Cluster Endpoint 통신 방식  (0) 2024.03.09