[EKS]Nodeless Concept

xgro·2023년 5월 23일
0

EKS

목록 보기
2/4
post-thumbnail

이 블로그는 정태환-Linuxer님의 경험발표 및 리눅서의 기술술블로그 - Nodeless 시리즈를 참조하여 작성하였습니다.

인프라를 구축하는 실습 과정은 Cloudneta의 AEWS Study에서 제공한 내용을 참조하였습니다.

📌 Summary

  • K8s를 구성하는 중요한 리소스를 AWS fargate로 운영하여 Nodeless Concept으로 인프라를 구축합니다.

  • 운영하고자 하는 리소스(파드, 디플로이먼트)는 Karpenter를 이용하여 배포되는 노드를 동적으로 관리합니다.

  • Karpenter의 ConsolidationttlSecondsUntilExpired 기능을 조합하여 노드를 항상 최신의 상태로 운영할 수 있습니다.



📌 Nodeless

EKS의 중요한 서비스를 Fargate로 배포하고, 서비스를 운영하기 위한 파드는 Karpenter로 노드를 동적으로 관리하여, 쿠버네티스를 구성하는 노드를 서버리스로 운영할 수 있습니다.

📕 Diagram


📘 Fargate

AWS Fargate는 Amazon EC2 인스턴스의 서버나 클러스터를 관리할 필요 없이 컨테이너를 실행하기 위해 Amazon ECS에 사용할 수 있는 기술입니다.

Fargate를 사용하면 더 이상 컨테이너를 실행하기 위해 가상 머신의 클러스터를 프로비저닝, 구성 또는 조정할 필요가 없습니다. 따라서 서버 유형을 선택하거나, 클러스터를 조정할 시점을 결정하거나, 클러스터 패킹을 최적화할 필요가 없습니다.

출처 - AWS Fargate

EKS(컨트롤 플레인) + Fargate(데이터 플레인)의 완전한 서버리스화(=AWS 관리형)를 구현하며, Nodeless 컨셉을 달성 할 수 있습니다.

AWS Fargate는 다음과 같은 특징을 가지고 있습니다.

  • 별도의 Cluster Autoscaler 불필요

  • VM 수준의 격리 가능(VM isolation at Pod Level)
    출처 - https://www.eksworkshop.com/docs/fundamentals/fargate/

  • 파게이트 프로파일(파드가 사용할 서브넷, 네임스페이스, 레이블 조건)을 생성하여 지정한 파드가 파게이트에서 동작하도록 설정할 수 있습니다.

  • EKS는 스케줄러가 특정 조건을 기준으로 어느 노드에 파드를 동작시킬지 결정, 혹은 특정 설정으로 특정 노드에 파드를 배포합니다.

Fargate 제약 사항

  • K8S 의 일부 리소스나 기능을 사용 할 수 없습니다.

  • EKS에서 연동되는 AWS 서비스들의 일부는 사용 할 수 없습니다. (예, Classic Load balancer)

  • Priviliged 컨테이너는 호스트 권한을 갖기 때문에 사용할 수 없습니다.

  • HostNetwork, HostPort는 호스트쪽 네트워크와 포트를 파드에서 이용하는 기능이기 때문에 지원하지 않습니다.

  • HostPath 또한 사용할 수 없습니다.

  • Daemonset을 사용할 수 없습니다. 파게이트에서는 파드와 노드(?)가 1:1로 동작하기 때문에 노드 각각에 파드가 1개씩 동작하는 구조이기 때문입니다.

Fargate 사용 사례

  • 배치 처리 등 일시적이거나 정기적으로 많은 리소스를 사용해야 하는 경우

  • 동시에 병렬 처리를 하기 위해 많은 파드를 동시에 동작시켜야 하는 경우


📗 Karpenter

Karpenter는 Kubernetes 클러스터를 확장하는 데 사용되는 노드 수명 주기 관리 솔루션입니다.
들어오는 포드를 관찰하고 상황에 맞는 인스턴스를 시작합니다.
인스턴스 선택 결정은 인텐트 기반이며 리소스 요청 및 스케줄링 제약 조건을 포함하여 들어오는 포드의 사양에 따라 결정됩니다.

EKS와 카펜터

Nodeless Concept의 가장 중요한 노드 수명 주기 관리 솔루션입니다.

CA와 차별화된 강력한 프로비저닝 기능을 가지고 있습니다.

Karpenter는 다음의 기능을 수행합니다.

  • Kubernetes 스케줄러가 예약 불가능으로 표시한 파드(Pod) 감시

  • Pod에서 요청한 일정 제약 조건(리소스 요청, 노드 선택기, 친화도, 허용 오차 및 토폴로지 확산 제약 조건) 평가

  • 포드의 요구 사항을 충족하는 프로비저닝 노드

  • 노드가 더 이상 필요하지 않을 때 노드 제거



📌 EKS Cluster

👉 Step 00. EKS 클러스터 배포

EKS 클러스터를 생성합니다.


EKS 클러스터를 컨트롤 하기 위한 Bastion host를 생성합니다.

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

# CloudFormation 스택 배포
예시) 
aws cloudformation deploy \
  --template-file karpenter-preconfig.yaml \
  --stack-name myeks \
  --parameter-overrides \
  KeyName=<SSH 키페어 이름> \
  SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  \
  MyIamUserAccessKeyID=AKIA5... \
  MyIamUserSecretAccessKey='CVNa2...' \
  ClusterBaseName=myeks2 \
  --region ap-northeast-2

# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text

# 작업용 EC2 SSH 접속
ssh -i ~/.ssh/<등록한 키페어> ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

Karpenter를 운영하기 위한 IAM Policy, Role, EC2 Instance Profile를 CloudFormation 스택으로 생성합니다.

# 환경변수 정보 확인
export | egrep 'ACCOUNT|AWS_|CLUSTER' | egrep -v 'SECRET|KEY'

# 환경변수 설정
export KARPENTER_VERSION=v0.27.5
export TEMPOUT=$(mktemp)
export AWS_ACCOUNT_ID=$ACCOUNT_ID
echo $KARPENTER_VERSION $CLUSTER_NAME $AWS_DEFAULT_REGION $AWS_ACCOUNT_ID $TEMPOUT

# CloudFormation 스택으로 IAM Policy, Role, EC2 Instance Profile 생성 : 3분 정도 소요
curl -fsSL https://karpenter.sh/"${KARPENTER_VERSION}"/getting-started/getting-started-with-karpenter/cloudformation.yaml  > $TEMPOUT \
&& aws cloudformation deploy \
  --stack-name "Karpenter-${CLUSTER_NAME}" \
  --template-file "${TEMPOUT}" \
  --capabilities CAPABILITY_NAMED_IAM \
  --parameter-overrides "ClusterName=${CLUSTER_NAME}"

EKS Cluster를 생성합니다.

# EKS 클러스터 생성
eksctl create cluster -f - <<EOF
---
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
  name: ${CLUSTER_NAME}
  region: ${AWS_DEFAULT_REGION}
  version: "1.24"
  tags:
    karpenter.sh/discovery: ${CLUSTER_NAME}

iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: karpenter
      namespace: karpenter
    roleName: ${CLUSTER_NAME}-karpenter
    attachPolicyARNs:
    - arn:aws:iam::${AWS_ACCOUNT_ID}:policy/KarpenterControllerPolicy-${CLUSTER_NAME}
    roleOnly: true
  - metadata:
      name: external-dns
      namespace: kube-system
    wellKnownPolicies:
      externalDNS: true
  - metadata:
      name: aws-load-balancer-controller
      namespace: kube-system
    wellKnownPolicies:
      awsLoadBalancerController: true
  - metadata:
      name: efs-csi-controller-sa
      namespace: kube-system
    wellKnownPolicies:
      efsCSIController: true

addons:
  - name: vpc-cni # no version is specified so it deploys the default version
    version: latest # auto discovers the latest available
    attachPolicyARNs:
      - arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy
  - name: kube-proxy
    version: latest
  - name: coredns
    version: latest

iamIdentityMappings:
- arn: "arn:aws:iam::${AWS_ACCOUNT_ID}:role/KarpenterNodeRole-${CLUSTER_NAME}"
  username: system:node:{{EC2PrivateDNSName}}
  groups:
  - system:bootstrappers
  - system:nodes

fargateProfiles:
- name: karpenter
  selectors:
  - namespace: karpenter
- name: kube-system
  selectors:
  - namespace: kube-system
EOF

👉 Step 01. coredns

CoreDNS는 Kubernetes 클러스터 DNS로 사용할 수 있는 유연하고 확장 가능한 DNS 서버입니다.

하나 이상의 노드가 있는 Amazon EKS 클러스터를 시작하면 클러스터에 배포된 노드 수에 관계없이 CoreDNS 이미지의 복제본 2개가 기본적으로 배포됩니다.

출처 - CoreDNS Amazon EKS 추가 기능을 사용한 작업

Step 00. EKS 클러스터 배포를 잘 진행하였다면 coredns가 파게이트로 생성됩니다.

coredns가 fargate로 배포되지 않은 경우

EKS는 coredns에 annotations으로 compute-type 속성을 부여하여 EC2 노드에 우선적으로 파드가 배포되도록 설정합니다.

coredns 파드가 Fargate에 배포되지 않았다면, 아래 명령어를 이용하여 annotations을 제거합니다.

# coredns의 annotation 중 compute-type을 삭제
kubectl patch deployment coredns \
    -n kube-system \
    --type json \
    -p='[{"op": "remove", "path": "/spec/template/metadata/annotations/eks.amazonaws.com~1compute-type"}]'

# 파드 재시작
kubectl rollout restart -n kube-system deployment coredns

👉 Step 02. aws-load-balancer-controller

AWS Load Balancer Controller는 Kubernetes 클러스터의 AWS Elastic Load Balancer를 관리합니다.

이 컨트롤러는 다음 리소스를 프로비저닝합니다.

  • Application Load Balancer(ALB)
    Kubernetes의 Ingress를 생성할 때 ALB를 생성합니다.

  • AWS Network Load Balancer(NLB)
    LoadBalancer 유형의 Kubernetes service를 생성할 때 NLB를 생성합니다.

과거에는 인스턴스 대상에 대해 Kubernetes Network Load Balancer를 사용했지만 IP 대상에 대해서는 AWS Load Balancer Controller를 사용했습니다.

AWS Load Balancer Controller 버전 2.3.0 이상에서 대상 유형 중 하나를 사용하여 NLB를 생성할 수 있습니다.

출처 - AWS Load Balancer Controller 추가 기능 설치

EKS 클러스터 생성시 IAM 및 Service Account가 생성되도록 구성했으므로 Helm을 이용하여 간단하게 리소스를 배포할 수 있습니다.

helm을 이용하여 aws-load-balancer-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

👉 Step 03. external-dns

ExternalDNS는 kubernetes dns(kube-dns)와 상반되는 개념으로 내부 도메인서버가 아닌 Public한 도메인서버(AWS Route53, GCP DNS 등)를 사용하여 쿠버네티스의 리소스를 쿼리할 수 있게 해주는 오픈소스 솔루션입니다.

ExternalDNS를 사용하면 public도메인 서버가 무엇이든 상관없이 쿠버네티스 리소스를 통해서 DNS레코드를 동적으로 관리 할 수 있습니다.

출처 - [kubernetes] ExternalDNS란? 개념부터 설치까지 정리(AWS EKS)

EKS 클러스터 생성시 IAM 및 Service Account가 생성되도록 구성했으므로 간단하게 리소스를 배포할 수 있습니다.

# 보유중인 도메인을 $MyDomain 변수로 설정합니다.
MyDomain="xgro.be"
echo "export MyDomain=$MyDomain" >> /etc/profile

# route 53의 hostedzone id를 가져옵니다.
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId

# externaldns 배포를 위한 yaml 다운로드
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml

# 
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

👉 Step 04. (option)kube-ops-view

쿠버네티스의 파드와 노드가 운영되고 있는 상태를 웹페이지를 통해 확인 할 수 있는 리소스 입니다.

최종 목표인 kube-system에서 관리되는 파드를 모두fargate로 운영하는 것이 목적이므로, kube-ops-view도 배포를 진행합니다.

❗ Fargate는 Classic Loadbalancer에서 지원하지 않으므로, 반드시 alb 또는 nlb를 이용하여 서비스를 배포합니다.
출처 - Classic Load Balancers are not supported - stack overflow

# helm repo 추가
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/

# helm을 이용하여 배포
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system

# External-dns 연결
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"

# nlb 사용하기 위해 아래 metadata와 spec에 아래 내용을 추가합니다.
kubectl edit svc -n kube-system kube-ops-view
--- 
apiVersion: v1
kind: Service
metadata:
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
    service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
    service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
---

# 주소 확인
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"

👉 Step 05. Karpenter

Karpenter를 사용하기 위한 기본적인 내용을 이미 Step 00. EKS 클러스터 배포 과정을 통해 생성하였습니다.

ProvisionerAWSNodeTemplate를 생성하여 카펜터의 기능을 이용할 수 있습니다.

# Create Provisioner
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  consolidation:
    enabled: true
  ttlSecondsUntilExpired: 120
  requirements:
    - key: karpenter.sh/capacity-type
      operator: In
      values: ["spot"]
  limits:
    resources:
      cpu: 1000
  providerRef:
    name: default
---
apiVersion: karpenter.k8s.aws/v1alpha1
kind: AWSNodeTemplate
metadata:
  name: default
spec:
  subnetSelector:
    karpenter.sh/discovery: ${CLUSTER_NAME}
  securityGroupSelector:
    karpenter.sh/discovery: ${CLUSTER_NAME}
EOF

# 확인
kubectl get awsnodetemplates,provisioners

✅ Over-Provisioning

카펜터를 이용하여 인프라를 운영할때 ttlSecondsUntilExpiredconsolidation를 사용하여 노드를 항상 최신의 상태로 운영할 수 있습니다.

그 과정에서 파드의 가용성을 보장하기 위한 방법으로 파드를 배포하는 우선권을 이용합니다.

우선권이 낮은 파드를 통해 일정 부분의 파드의 공간을 미리 선점하여 노드가 cordon 상태가 되었을때, 운영중인 리소스(ex 백엔드 서버)가 새로운 노드가 생성되기 전에, 미리 선점해두었던 파드의 공간을 차지하여 다운타임을 최소화 할 수 있습니다.

일반 리소스에 부여되는 우선권을 0으로 설정하는 PriorityClass 리소스를 배포합니다.

cat <<EOF | kubectl apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
   name: default
value: 0
globalDefault: true
description: "Default Priority class."
EOF

오버프로비저닝용 파드에 부여되는 우선권을 -1으로 설정하는 PriorityClass 리소스를 배포합니다.

cat <<EOF | kubectl apply -f -
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
   name: pause-pods
value: -1
globalDefault: false
description: "Priority class used by pause-pods for overprovisioning."
EOF

가상의 Backend 서버를 대체할 디플로이먼트를 배포합니다.

# Deploy inflate
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 0
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.7
          resources:
            requests:
              cpu: 1
EOF

pause-pods 디플로이먼트를 배포하여 우선권에 따라 오버프로비저닝 기능이 잘 동작하는지 확인합니다.

cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: pause-pods
  namespace: pause
spec:
  replicas: 2
  selector:
    matchLabels:
      run: pause-pods
  template:
    metadata:
      labels:
        run: pause-pods
    spec:
      priorityClassName: pause-pods
      containers:
      - name: reserve-resources
        image: registry.k8s.io/pause
        resources:
          requests:
            cpu: 1
EOF



📌 Conclusion

AWS에서 컨테이너 환경의 인프라를 구축, 운영할때 서버리스 서비스와 Karpenter를 조합하여 Nodeless(노드를 관리할 필요가 없는) Concept을 달성할 수 있습니다.

Node의 선택에서 보다 자유로워지며, Spot 인스턴스를 적극적으로 사용할 수 있었습니다.

EKS Cluster 업그레이드시, 매니지드 노드에 대해서 전체 cordon 하는 과정이 사라지므로 EKS 버전 업그레이드를 보다 수월하게 진행할 수 있습니다.



🔗 Reference

profile
안녕하세요! DevOps 엔지니어 이재찬입니다. 블로그에 대한 피드백은 언제나 환영합니다! 기술, 개발, 운영에 관한 다양한 주제로 함께 나누며, 더 나은 협업과 효율적인 개발 환경을 만드는 과정에 대해 인사이트를 나누고 싶습니다. 함께 여행하는 기분으로, 즐겁게 읽어주시면 감사하겠습니다! 🚀

0개의 댓글