home..

EKS 스토리지, 노드그룹

Kubernetes EKS AEWS Gasida Cloudnet Storage CSI Driver Ephemeral Instance Store

EKS Storage, NodeGroup

프로비저닝은 새로운 CloudFormation 파일로 진행합니다.

# YAML 파일 다운로드
curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick2.yaml

# CloudFormation 스택 배포
예시) aws cloudformation deploy --template-file eks-oneclick2.yaml --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2
 

로컬 환경에서 aws cli로 configure 가 진행되어있는 상황에서 프로비저닝을 진행합니다.

이번 파일은 기존과 조금 다른것들이 LB Controller등 기본적으로 더 설정이 되어 있는 것 들이 있어 기존 원클릭 배포 파일을 이용하지 않습니다.

기본 설정

# default 네임스페이스 적용
kubectl ns default

# EFS 확인 : AWS 관리콘솔 EFS 확인해보자
echo $EfsFsId
mount -t efs -o tls $EfsFsId:/ /mnt/myefs
df -hT --type nfs4

echo "efs file test" > /mnt/myefs/memo.txt
cat /mnt/myefs/memo.txt
rm -f /mnt/myefs/memo.txt

# 스토리지클래스 및 CSI 노드 확인
kubectl get sc
kubectl get sc gp2 -o yaml | yh
kubectl get csinodes

# 노드 정보 확인
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
eksctl get iamidentitymapping --cluster myeks

# 노드 IP 확인 및 PrivateIP 변수 지정
N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
echo "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 보안그룹 ID 확인
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

# 워커 노드 SSH 접속
for node in $N1 $N2 $N3; do ssh ec2-user@$node hostname; done

LB Controller, External DNS, Kube-ops-view 설치

# AWS LB Controller
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ExternalDNS
MyDomain=<자신의 도메인>
MyDomain=gasida.link
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId

curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
sed -i "s/0.13.4/0.14.0/g" externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

Storage

컨테이너는 종료되면 안에있던 파일 스토리지가 증발하는 ephemeral 한 스토리지를 갖고있습니다.

하지만 상태의 저장을 위해 stateful 한 어플리케이션을 보장하기위해 파드의 라이프사이클과 무관하게 지속되는 스토리지가 필요했습니다.

이를위해 컨테이너 표준이 적립되면서 컨테이너에서의 스토리지 표준에 대해 정립이된게 바로 CSI 입니다.

https://github.com/container-storage-interface/spec

쿠버네티스에서의 이러한 CSI 동작방식에 대해서는

https://montkim.com/csi-md

CSI Driver의 개념에 대해선 일전에 다룬적이 있습니다.

쿠버네티스에서는 볼륨에 대한 선언인 Persistent Volume Claim 과 실제 볼륨인 Persistent Volume 을 구분해서 사용합니다.

이러한 각 스토리지에서 제공되는 CSI Driver를 이용하여 쿠버네티스 pod에서 디바이스들을 마운트 할 수 있습니다.

https://aws.amazon.com/ko/blogs/tech/persistent-storage-for-kubernetes/

Untitled

해당구조로 진행이되며, CSI Driver를 통해 Mount Propagation을 이용해 외부 공급 스토리지를 마운트합니다.

그중에서도 AWS에서 제공되는 유형의 볼륨과 CSI드라이버에 대한 설명을 드리겠습니다.

Untitled

AWS에서 제공되는 CSI 드라이버들 또한 동일하며

실제로 온프레미스 기준으로도 container runtime 에 컨테이너가 생성되는 위치에 맵핑이 되어

/dev/mapper 밑에 외부 스토리지 제공된것들이 맵핑되고

Untitled

/dev/dm-@ 형태로 마운트되는 형태입니다.

Untitled

모든 CSI Driver가 동일한 형태로 마운트가 되지는 않겠지만, CSI 표준을 만족한다면 실제로 마운트가 어떤식으로 이루어지는지 찾아보면 좋을것같습니다.

https://kubernetes-csi.github.io/docs/

그중에서도 AWS에서 제공해주는 스토리지는 크게 세가지 유형이 있습니다.

  1. EBS (Elastic Block Storage):
    • EC2 인스턴스에 연결되는 블록 레벨 스토리지입니다.
    • 주로 EC2 인스턴스의 주 스토리지(부트 볼륨)로 사용됩니다.
    • 데이터베이스, 파일 시스템, 애플리케이션에 필요한 지속적인 스토리지를 제공합니다.
    • 스냅샷을 통한 백업 및 복제 기능을 지원합니다.
  2. EFS (Elastic File System):
    • 여러 EC2 인스턴스에서 동시에 사용할 수 있는 관리형 파일 스토리지입니다.
    • 파일 시스템 인터페이스를 제공하며, NFS(Network File System)를 지원합니다.
    • 데이터를 자동으로 확장 및 축소하며, 스토리지 용량 관리가 필요하지 않습니다.
    • 공유 데이터 액세스가 필요한 애플리케이션에 적합합니다.
  3. S3 (Simple Storage Service):
    • 객체 스토리지 서비스로, 데이터를 객체 형태로 저장합니다.
    • 웹에서 직접 액세스 가능하며, 데이터는 URL을 통해 접근할 수 있습니다.
    • 무제한의 데이터 저장과 높은 내구성을 제공합니다.
    • 웹사이트 호스팅, 백업, 아카이브, 빅데이터 분석 등 다양한 사용 사례에 적합합니다.

이 내용들을 기반으로 성능을 기준으로 다시 정리를 해보았습니다.

Untitled

S3의 경우 명확한 성능기준표에 대해서 공개된것이 없기도하고, EBS EFS에 비해 성능이 부족하지만, 무한한 확장성을 제공합니다

EBS Controller

EBS CSI Driver의 형태로 제공되는 EKS Addon의 일부입니다.

Untitled

출처 : https://malwareanalysis.tistory.com/598

CSI Driver중에서 이렇게 깔끔하게 동작방식을 요약해본 그림은 처음입니다.

더 친절하게 설명할 필요없이 그림에서 보여주는 컴포넌트들과 흐름이 EBS Controller의 모든 역할입니다.

다음과 같은 기능을 하며 IRSA를 이용해 쿠버네티스 위에 떠있는 CSI Driver가 내 EKS, AWS에 자원 생성을 요청 할 수 있는 권한을 부여 받아 EBS 프로비저닝을 진행 후 마운트 까지 담당해줍니다.

# 아래는 aws-ebs-csi-driver 전체 버전 정보와 기본 설치 버전(True) 정보 확인
aws eks describe-addon-versions \
    --addon-name aws-ebs-csi-driver \
    --kubernetes-version 1.28 \
    --query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" \
    --output text

# ISRA 설정 : AWS관리형 정책 AmazonEBSCSIDriverPolicy 사용
eksctl create iamserviceaccount \
  --name ebs-csi-controller-sa \
  --namespace kube-system \
  --cluster ${CLUSTER_NAME} \
  --attach-policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy \
  --approve \
  --role-only \
  --role-name AmazonEKS_EBS_CSI_DriverRole

# ISRA 확인
eksctl get iamserviceaccount --cluster myeks
NAMESPACE	    NAME				            ROLE ARN
kube-system 	ebs-csi-controller-sa		arn:aws:iam::@@@@:role/AmazonEKS_EBS_CSI_DriverRole
...

# Amazon EBS CSI driver addon 추가
eksctl create addon --name aws-ebs-csi-driver --cluster ${CLUSTER_NAME} --service-account-role-arn arn:aws:iam::${ACCOUNT_ID}:role/AmazonEKS_EBS_CSI_DriverRole --force
kubectl get sa -n kube-system ebs-csi-controller-sa -o yaml | head -5

# 확인
eksctl get addon --cluster ${CLUSTER_NAME}
kubectl get deploy,ds -l=app.kubernetes.io/name=aws-ebs-csi-driver -n kube-system
kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
kubectl get pod -n kube-system -l app.kubernetes.io/component=csi-driver

# ebs-csi-controller 파드에 6개 컨테이너 확인
kubectl get pod -n kube-system -l app=ebs-csi-controller -o jsonpath='{.items[0].spec.containers[*].name}' ; echo
ebs-plugin csi-provisioner csi-attacher csi-snapshotter csi-resizer liveness-probe

# csinodes 확인
kubectl get csinodes

해당 과정은 EKSCTL 명령어로 IAM을 생성해 맵핑하는 과정을 담았습니다.

배포 방법은 다양해 HELM 또는 Terraform 코드를 통한 생성도 가능합니다.

Helm 설치

버전 호환성 검토는 공식 DOC을 기준으로 검토합니다.

https://github.com/kubernetes-sigs/aws-ebs-csi-driver/tree/release-1.20

helm repo add aws-ebs-csi-driver https://kubernetes-sigs.github.io/aws-ebs-csi-driver
helm repo update
 
helm upgrade --install aws-ebs-csi-driver --namespace kube-system aws-ebs-csi-driver/aws-ebs-csi-driver

AWS CLI로 EBS CSI Driver 설치

필요한 플랫폼 버전 확인


`aws eks describe-addon-versions --addon-name aws-ebs-csi-driver`

AWS EBS CSI 기능 추가

aws eks create-addon --cluster-name @@@@ --addon-name aws-ebs-csi-driver \
  --service-account-role-arn arn:aws:iam::#####:role/AmazonEKS_EBS_CSI_DriverRole

EBS CSI 기능 추가 업데이트

aws eks describe-addon --cluster-name @@@@ --addon-name aws-ebs-csi-driver --query "addon.addonVersion" --output text

클러스터 버전에 사용 가능한 Amazon EBS CSI 추가 기능 버전 확인

aws eks describe-addon-versions --addon-name aws-ebs-csi-driver --kubernetes-version 1.24 \ --query "addons[].addonVersions[].[addonVersion, compatibilities[].defaultVersion]" --output text

추가 기능을 이전 단계의 출력에서 True가 반환된 버전으로 업데이트

aws eks update-addon --cluster-name @@@@ --addon-name aws-ebs-csi-driver --addon-version v1.11.4-eksbuild.1 \
  --resolve-conflicts PRESERVE

Terraform 코드 설치


# aws_iam_role_policy.Amazon_EBS_CSI_Driver:
resource "aws_iam_role_policy" "Amazon_EBS_CSI_Driver" {
    name   = "Amazon_EBS_CSI_Driver"
    policy = jsonencode(
        {
            Statement = [
                {
                    Action   = [
                        "ec2:AttachVolume",
                        "ec2:CreateSnapshot",
                        "ec2:CreateTags",
                        "ec2:CreateVolume",
                        "ec2:DeleteSnapshot",
                        "ec2:DeleteTags",
                        "ec2:DeleteVolume",
                        "ec2:DescribeInstances",
                        "ec2:DescribeSnapshots",
                        "ec2:DescribeTags",
                        "ec2:DescribeVolumes",
                        "ec2:DetachVolume",
                        "ec2:ModifyVolume",
                    ]
                    Effect   = "Allow"
                    Resource = "*"
                },
            ]
            Version   = "2012-10-17"
        }
    )
    role   = aws_iam_role.NodeInstanceRole.id
}

# aws-ebs-csi-driver
data "aws_eks_addon_version" "aws_ebs_csi_driver_latest" {
  addon_name         = "aws-ebs-csi-driver"
  kubernetes_version = aws_eks_cluster.@@@.version
  most_recent        = true
}

# Resource blocks for ebs-csi-driver and efs-csi-driver
resource "aws_eks_addon" "ebs_csi_driver" {
  cluster_name      = aws_eks_cluster.@@@.name
  addon_name        = "aws-ebs-csi-driver"
  addon_version     = data.aws_eks_addon_version.aws_ebs_csi_driver_latest.version
  resolve_conflicts_on_create = "OVERWRITE"
  resolve_conflicts_on_update = "OVERWRITE"
  configuration_values = jsonencode({
    "controller": {
      "nodeSelector": {
        "system-node" : "enabled"
      }
    }
    "node": {
      "resources": {
        "requests": {
          "cpu" : "30m"
        }
      }
    }
  })
}

output "AmazonEKS_EBS_CSI_DriverRole-arn" {
    value = aws_iam_role.AmazonEKS_EBS_CSI_DriverRole.arn
}

다음과 같이도 Role을 Terraform으로 생성하여 진행할수도 있습니다.

테라폼 에서는 most_recent 파라미터를 이용해 현재 클러스터 버전에 맞는 addon 버전을 관리해 설치를 진행하는 기능을 제공 하고 있습니다.

인프라에 대한 관리 방법을 어떤 식으로 가져갈 것 인가에 대한 고민은 각자 다르기 때문에, 각 환경에 맞는 방법을 선택 하는 것이 옳지 않을까 싶습니다.

EBS StorageClass 설정

EBS CSI Driver를 설정하면 Default로 GP2 type의 Storageclass가 생성됩니다.

이 Storageclass를 이용해 Block Storage를 생성 할수도 있지만, 조금 더 성능이 좋은 GP3을 이용한 StorageClass 를 만들어보겠습니다.

# gp3 스토리지 클래스 생성
cat <<EOT > gp3-sc.yaml
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp3
allowVolumeExpansion: true
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer
parameters:
  type: gp3
  allowAutoIOPSPerGBIncrease: 'true'
  encrypted: 'true'
  fsType: xfs # 기본값이 ext4
EOT
kubectl apply -f gp3-sc.yaml
kubectl get sc

Untitled

기존의 storgeclass와 별개로 ebs csi driver를 provisioner를 이용한 gp3 storageclass를 생성했습니다.

이제 GP3 volume을 이용한 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

Untitled

Untitled

생성된 PV를 Describve 해보면 “Node Affinity” 로 AZ가 정의되어있습니다.

EBS가 생성된 가용 영역에서 해당 볼륨이 생성되어 Node Affinity로 불필요한 네트워크 이용을 방지하기 위한 방법이라고 생각됩니다.

Untitled

스토리지 볼륨 확장

스토리지 볼륨 확장에 대한 자세한 내용은 외부 리사이저 도입 시 언급되었으므로 더 이상 설명하지 않습니다. 사용자는 PVC의 .Spec.Resources.Requests.Storage 필드만 편집하면 됩니다. 볼륨은 확장만 가능합니다.

PV 확장이 실패하면 spec 필드의 저장 값을 PVC의 원래 값으로 편집할 수 없습니다(확장만 허용됨).

구체적인 방법은 해당 DOC에서도 가능합니다https://kubernetes.io/docs/concepts/storage/persistent-volumes/#recovering-from-failure-when-expanding-volumes

kubectl patch pvc ebs-claim -p '{"spec":{"resources":{"requests":{"storage":"10Gi"}}}}'

명령어를 통해 볼륨 증가를 할 수 있습니다.

Untitled

생성된 볼륨에 들어가 mount type을 직접 조회할수도 있습니다.

xfs type로 생성된 볼륨입니다.

Untitled

Volumesnapshot Controller

CSI Driver 표준에 Snapshot controller가 같이 배포됩니다.

# (참고) EBS CSI Driver에 snapshots 기능 포함 될 것으로 보임
kubectl describe pod -n kube-system -l app=ebs-csi-controller

# 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
# 파일 내용 추가 저장 확인
kubectl exec app -- tail -f /data/out.txt

# 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

아까 생성한 pod에서 /data/out.txt에 현재시간이 계속 찍히는 코드가 실행중입니다.

Untitled

VolumeSnapshot의 볼륨은 10G였고, 원복시 다시 생성하려했던 볼륨이 4G라서 다운사이징을 지원하지 않아 발생된 문제입니다. 볼륨사이즈를 다시 10G로 증설해 복구를 완료했습니다

Untitled

Untitled

Untitled

EFS CSI

efs 시스템은 AZ구분이 없는 스토리지로, 다중마운트를 제공하는 “공용 파일시스템” 에 적합한 종류의 파일시스템입니다.

Untitled

파드에서도 RWX 마운트 볼륨을 지원해 공용 작업공간의 역할도 가능합니다.

CSI 드라이버는 다음과 같이 설치를 진행합니다.

# EFS 정보 확인 
aws efs describe-file-systems --query "FileSystems[*].FileSystemId" --output text

# IAM 정책 생성
curl -s -O https://raw.githubusercontent.com/kubernetes-sigs/aws-efs-csi-driver/master/docs/iam-policy-example.json
aws iam create-policy --policy-name AmazonEKS_EFS_CSI_Driver_Policy --policy-document file://iam-policy-example.json

# ISRA 설정 : 고객관리형 정책 AmazonEKS_EFS_CSI_Driver_Policy 사용
eksctl create iamserviceaccount \
  --name efs-csi-controller-sa \
  --namespace kube-system \
  --cluster ${CLUSTER_NAME} \
  --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AmazonEKS_EFS_CSI_Driver_Policy \
  --approve

# ISRA 확인
kubectl get sa -n kube-system efs-csi-controller-sa -o yaml | head -5
eksctl get iamserviceaccount --cluster myeks

# EFS Controller 설치
helm repo add aws-efs-csi-driver https://kubernetes-sigs.github.io/aws-efs-csi-driver/
helm repo update
helm upgrade -i aws-efs-csi-driver aws-efs-csi-driver/aws-efs-csi-driver \
    --namespace kube-system \
    --set image.repository=602401143452.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/eks/aws-efs-csi-driver \
    --set controller.serviceAccount.create=false \
    --set controller.serviceAccount.name=efs-csi-controller-sa

# 확인
helm list -n kube-system
kubectl get pod -n kube-system -l "app.kubernetes.io/name=aws-efs-csi-driver,app.kubernetes.io/instance=aws-efs-csi-driver"

Terraform 코드 설치


# aws_iam_role_policy.Amazon_EFS_CSI_Driver:
resource "aws_iam_role" "AmazonEKS_EFS_CSI_DriverRole" {
    assume_role_policy    = jsonencode(
        {
            Statement = [
                {
                    Action    = "sts:AssumeRoleWithWebIdentity"
                    Condition = {
                        StringLike = {
                            "oidc.eks.ap-northeast-2.amazonaws.com/id/@@@@@:aud" = "sts.amazonaws.com"
                            "oidc.eks.ap-northeast-2.amazonaws.com/id/@@@@@:sub" = "system:serviceaccount:kube-system:efs-csi-*"
                        }
                    }
                    Effect    = "Allow"
                    Principal = {
                        Federated = "arn:aws:iam::#####:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/@@@@"
                    }
                },
            ]
            Version   = "2012-10-17"
        }
    )
    force_detach_policies = false
    managed_policy_arns   = [
        data.aws_iam_policy.AmazonEFSCSIDriverPolicy.arn,
    ]
    max_session_duration  = 3600
    name                  = "AmazonEKS_EFS_CSI_DriverRole"
    path                  = "/"
    tags                  = {}
    tags_all              = {}
}

# aws-efs-csi-driver
data "aws_eks_addon_version" "aws_efs_csi_driver_latest" {
  addon_name         = "aws-efs-csi-driver"
  kubernetes_version = aws_eks_cluster.@@@.version
  most_recent        = true
}

# Resource blocks for ebs-csi-driver and efs-csi-driver
resource "aws_eks_addon" "efs_csi_driver" {
  cluster_name      = aws_eks_cluster.@@@.name
  addon_name        = "aws-efs-csi-driver"
  addon_version     = data.aws_eks_addon_version.aws_efs_csi_driver_latest.version
  resolve_conflicts_on_create = "OVERWRITE"
  resolve_conflicts_on_update = "OVERWRITE"
  configuration_values = jsonencode({
    "controller": {
      "nodeSelector": {
        "system-node" : "enabled"
      }
    }
  })
}

output "AmazonEKS_EFS_CSI_DriverRole-arn" {
    value = aws_iam_role.AmazonEKS_EFS_CSI_DriverRole.arn
}

다중 mount 가능한 RWX Storage Class 생성하기


# 실습 코드 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

cat pv.yaml | yh
apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: fs-05699d3c12ef609e2

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
kubectl df-pv

# 파드 정보 확인 : PV에 5Gi 와 파드 내에서 확인한 NFS4 볼륨 크리 8.0E의 차이는 무엇? 파드에 6Gi 이상 저장 가능한가?
kubectl get pods
kubectl exec -ti app1 -- sh -c "df -hT -t nfs4"
kubectl exec -ti app2 -- sh -c "df -hT -t nfs4"
Filesystem           Type            Size      Used Available Use% Mounted on
127.0.0.1:/          nfs4            8.0E         0      8.0E   0% /data

# 공유 저장소 저장 동작 확인
tree /mnt/myefs              # 작업용EC2에서 확인
tail -f /mnt/myefs/out1.txt  # 작업용EC2에서 확인
kubectl exec -ti app1 -- tail -f /data/out1.txt
kubectl exec -ti app2 -- tail -f /data/out2.txt

Untitled

단일 pv로 양쪽에 마운트를 진행해 파일시스템 마운트정보를 확인할수있습니다.

인스턴스 스토어

Untitled

인스턴스 스토어는 쿠버네티스의 EmptyDir과 비슷한 개념의 스토리지입니다.

구체적으로는 ephemeral storage와 더 가깝다고 볼 수 있을것 같습니다.

Untitled

emptydir의 경우 설정에따라 tmpfs 파일의 위치를 메모리로도 설정 할 수 있기때문에

노드가 직접 돌아가는 스토리지 영역의 일부를 일시적으로 공유하기 때문이죠

EC2 의 스토리지 (EBS) 설정에서는 보이지 않으며, 재시작 될경우 삭제됩니다.

데이터 정책에 관한것은 공식 도큐먼트에 따르는 것이 좋아보입니다.

https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/InstanceStorage.html

# 인스턴스 스토어 볼륨이 있는 c5 모든 타입의 스토리지 크기

aws ec2 describe-instance-types \
 --filters "Name=instance-type,Values=c5*" "Name=instance-storage-supported,Values=true" \
 --query "InstanceTypes[].[InstanceType, InstanceStorageInfo.TotalSizeInGB]" \
 --output table
--------------------------
|  DescribeInstanceTypes |
+---------------+--------+
|  c5d.large    |  50    |
|  c5d.12xlarge |  1800  |
...

# 신규 노드 그룹 생성
eksctl create nodegroup --help
eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
  -n ng2 -t c5d.large -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels disk=nvme --max-pods-per-node 100 --dry-run > myng2.yaml

cat <<EOT > nvme.yaml
  preBootstrapCommands:
    - |
      # Install Tools
      yum install nvme-cli links tree jq tcpdump sysstat -y

      # Filesystem & Mount
      mkfs -t xfs /dev/nvme1n1
      mkdir /data
      mount /dev/nvme1n1 /data

      # Get disk UUID
      uuid=\$(blkid -o value -s UUID mount /dev/nvme1n1 /data) 

      # Mount the disk during a reboot
      echo /dev/nvme1n1 /data xfs defaults,noatime 0 2 >> /etc/fstab
EOT
sed -i -n -e '/volumeType/r nvme.yaml' -e '1,$p' myng2.yaml
eksctl create nodegroup -f myng2.yaml

Untitled

이렇게 인스턴스 스토어를 통해 생성한 스토리지는 일반 EBS보다 성능이 빠릅니다.

Untitled

기존 ebs 스토리지의 경우 IOPS 3천을 보장받지만, 이 nvme 스토리지는 약 2만을 소폭 넘긴상태로 거의 7배에 가까운 속도를 보여줍니다.

노드그룹

쿠버네티스에서 오토스케일링이 이루어지는 노드 단위를 노드그룹으로 제어합니다.

물론 콘솔의 EC2 - AutoScaling Group 에서도 확인이 가능하지만, 이 종류에 대한것을 EKS에서 별도로 관리하는데, 워크로드의 특성에 맞게 여러가지 노드 그룹을 생성해 각각에 대해 오토스케일링을 진행 시킬 수 있습니다.

위의 인스턴스 스토어를 위해 만들었던 노드그룹을 포함해 여러가지 유형에 대해 알아보겠습니다.

Untitled

aws의 인스턴스 패밀리 / 아키텍처는 다음과같이 이루어지는데

Arrtibute에 해당되는 프로세서 패밀리는 크게 새가지가 있습니다.

M5까지는 별도로 패밀리가 부여되지않는것이 Intel (Xeon)

별도로 A가 붙어야 AMD (EPYC)

그리고 G가 붙은게 AWS ARM(Graviton) 으로 구성되어있습니다.

아키텍처가 한세대 올라갈때 M5 → M6 이런식으로 더 최신화된 세대의 장비들을 이용하게됩니다.

꼭 구형 인스턴스를 고집하며 사용하고있을이유는 없지않을까 싶네

별도의 인스턴스 패밀리는 다음 doc에서 더 상세히 확인이 가능합니다.

https://docs.aws.amazon.com/ko_kr/AWSEC2/latest/UserGuide/instance-types.html

Graviton 노드그룹

arm인스턴스이자 AWS에서 자체적으로 제공해주는 유형의 인스턴스 아키텍처인 Graviton 인스턴스를 이용하는 노드그룹을 생성해보겠습니다.

eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
  -n ng3 -t t4g.medium -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels family=graviton --dry-run > myng3.yaml
eksctl create nodegroup -f myng3.yaml

EKSCTL의 node-labels family=graviton 을 이용해 그라비톤 인스턴스를 생성했습니다

Untitled

이제 ARM 기반의 워크로드를 운용하면서 코드의 패키지, 의존성 및 이미지빌드등을 신경쓰면

보다 효율적인 아키텍처를 구성 할수도 있을거같습니다.

사실 제가 M1 맥북을 이용하면서 홈서버에 쓸 이미지 빌드를 진행할때 가끔 호환성에 신경을 써야하는 부분들이 있기도 합니다.

해당 문서를 보면서 이해를 많이 했었고, 내부적으로 쿠버네티스 플랫폼 개발을 하며 Go 기반으로 개발중인 프로젝트들의 경우

ARM 맥에서는 Dockerfile안에 별도로 아키텍처 명시를 해야 런타임에 맞는 빌드가 진행이 됩니다

# 애플리케이션 빌드
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o <프로젝트 명>

https://hyperconnect.github.io/2023/07/25/migrate-half-of-workload-in-a-year.html

해당 문서를 보며 인하우스 회사에 들어가게되면 워크로드를 arm기반으로 마이그레이션 해보고싶다는 생각을 하게되었던거같습니다.

스팟 인스턴스

AWS를 사용하면서 뜨거운 감자와도 같은 스팟 인스턴스입니다.

가격은 저렴하지만… 언제든 Eviction 이 일어날수있기때문에, 사용에 큰 주의를하는

Well Architected 에서 도입을 고민해볼법한 인스턴스지만, 활용을 해보겠습니다.

# ec2-instance-selector 설치
curl -Lo ec2-instance-selector https://github.com/aws/amazon-ec2-instance-selector/releases/download/v2.4.1/ec2-instance-selector-`uname | tr '[:upper:]' '[:lower:]'`-amd64 && chmod +x ec2-instance-selector
mv ec2-instance-selector /usr/local/bin/
ec2-instance-selector --version

# 사용
ec2-instance-selector --vcpus 2 --memory 4 --gpus 0 --current-generation -a x86_64 --deny-list 't.*' --output table-wide
Instance Type  VCPUs   Mem (GiB)  Hypervisor  Current Gen  Hibernation Support  CPU Arch  Network Performance  ENIs    GPUs    GPU Mem (GiB)  GPU Info  On-Demand Price/Hr  Spot Price/Hr (30d avg)  
-------------  -----   ---------  ----------  -----------  -------------------  --------  -------------------  ----    ----    -------------  --------  ------------------  -----------------------  
c5.large       2       4          nitro       true         true                 x86_64    Up to 10 Gigabit     3       0       0              none      $0.096              $0.04574                 
c5a.large      2       4          nitro       true         false                x86_64    Up to 10 Gigabit     3       0       0              none      $0.086              $0.02859                 
c5d.large      2       4          nitro       true         true                 x86_64    Up to 10 Gigabit     3       0       0              none      $0.11               $0.03266                 
c6i.large      2       4          nitro       true         true                 x86_64    Up to 12.5 Gigabit   3       0       0              none      $0.096              $0.04011                 
c6id.large     2       4          nitro       true         true                 x86_64    Up to 12.5 Gigabit   3       0       0              none      $0.1155             $0.02726                 
c6in.large     2       4          nitro       true         false                x86_64    Up to 25 Gigabit     3       0       0              none      $0.1281             $0.0278                  
c7i.large      2       4          nitro       true         true                 x86_64    Up to 12.5 Gigabit   3       0       0              none      $0.1008             $0.02677

#Internally ec2-instance-selector is making calls to the DescribeInstanceTypes for the specific region and filtering the instances based on the criteria selected in the command line, in our case we filtered for instances that meet the following criteria:
- Instances with no GPUs
- of x86_64 Architecture (no ARM instances like A1 or m6g instances for example)
- Instances that have 2 vCPUs and 4 GB of RAM
- Instances of current generation (4th gen onwards)
- Instances that don’t meet the regular expression t.* to filter out burstable instance types

사용할 인스턴스에 대한 정책을 미리 설정해 해당 설정들에 맞게 사용하는 방법입니다

Untitled

role AWSServiceRoleForAmazonEKSNodegroup 를 로컬에서 확인해

aws eks create-nodegroup \
  --cluster-name $CLUSTER_NAME \
  --nodegroup-name managed-spot \
  --subnets $PubSubnet1 $PubSubnet2 $PubSubnet3 \
  --node-role arn:aws:iam::@@@@:role/eksctl-myeks-nodegroup-ng1-NodeInstanceRole-###\
  --instance-types c5.large c5d.large c5a.large \
  --capacity-type SPOT \
  --scaling-config minSize=2,maxSize=3,desiredSize=2 \
  --disk-size 20

다음과 같이 신규 노드그룹을 생성 할 수 있습니다.

Untitled

WordPress 배포하기

CloudNet 에도 예제가있지만, 공식 DOC을 기준으로 MySQL와 연동한 워드프레스를 배포해보겠습니다.

wget https://raw.githubusercontent.com/kubernetes/website/main/content/ko/examples/application/wordpress/wordpress-deployment.yaml
wget https://raw.githubusercontent.com/kubernetes/website/main/content/ko/examples/application/wordpress/mysql-deployment.yaml

명령어로 배포에 필요한 코드들을 받아줍니다

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: gp3


apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: wp-pv-claim
  labels:
    app: wordpress
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi
  storageClassName: gp3

wordpress 와 mysql에 필요한 pvc들에 storageclass로 아까 생성한 GP3을 맵핑해 볼륨을 생성해줍니다

Untitled

정상적으로 gp3 볼륨의 pv가 생성된것을 확인할수있습니다.

Untitled

사이트 생성에 완료했습니다.

Use Multiple EBS Volumes For Containers

https://aws.github.io/aws-eks-best-practices/scalability/docs/data-plane/#use-multiple-ebs-volumes-for-containers

이 기능은 노드그룹을 생성할때 인스턴스의 기본 EBS 외에 추가적은 EBS를 생성하여 오토스케일링이 이루어지는 노드그룹의 인스턴스들에 마운트하는 기능입니다.

여기서 런타임이 이루어지는 인스턴스들은 기본 EBS 볼륨을 사용해 실행을해도, 실제 IOPS인 3000에 걸리기때문에 인스턴스 스토어가 월등히 빠를것으로 예상됩니다

eksctl create nodegroup -c $CLUSTER_NAME -r $AWS_DEFAULT_REGION --subnet-ids "$PubSubnet1","$PubSubnet2","$PubSubnet3" --ssh-access \
  -n ng2 -t c5d.large -N 1 -m 1 -M 1 --node-volume-size=30 --node-labels disk=nvme --max-pods-per-node 100 --dry-run > burst-ng.yaml
cat <<EOT > burst.yaml

  preBootstrapCommands:
    - |
      # Install Tools
      yum install nvme-cli links tree jq tcpdump sysstat -y

      # Filesystem & Mount
      "systemctl stop containerd"
      "mkfs -t ext4 /dev/nvme1n1"
      "rm -rf /var/lib/containerd/*"
      "mount /dev/nvme1n1 /var/lib/containerd/"
      "systemctl start containerd"
      # Get disk UUID
      uuid=$(blkid -o value -s UUID mount /dev/nvme1n1 /data)

      # Mount the disk during a reboot
      echo /dev/nvme1n1 /data xfs defaults,noatime 0 2 >> /etc/fstab
EOT
sed -i -n -e '/volumeType/r burst.yaml' -e '1,$p' burst-ng.yaml
eksctl create nodegroup -f burst-ng.yaml

Untitled

인스턴스 스토어의 훨씬 빠르고 저렴한 디바이스를 이용해

노드그룹의 container runtime의 디렉토리를 별도로 관리하여, 훨씬 더 빠르게 데이터를 가져갈 수 있는 시스템을 구성하였습니다.

© 2024 mont kim   •  Powered by Soopr   •  Theme  Moonwalk