이 블로그는
정태환-Linuxer
님의 경험발표 및 리눅서의 기술술블로그 - Nodeless 시리즈를 참조하여 작성하였습니다.
인프라를 구축하는 실습 과정은
Cloudneta의 AEWS Study
에서 제공한 내용을 참조하였습니다.
K8s를 구성하는 중요한 리소스를 AWS fargate
로 운영하여 Nodeless Concept으로 인프라를 구축합니다.
운영하고자 하는 리소스(파드, 디플로이먼트)는 Karpenter
를 이용하여 배포되는 노드를 동적으로 관리합니다.
Karpenter의 Consolidation
및 ttlSecondsUntilExpired
기능을 조합하여 노드를 항상 최신의 상태로 운영할 수 있습니다.
EKS의 중요한 서비스를 Fargate로 배포하고, 서비스를 운영하기 위한 파드는 Karpenter로 노드를 동적으로 관리하여, 쿠버네티스를 구성하는 노드를 서버리스로 운영할 수 있습니다.
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/
파게이트 프로파일(파드가 사용할 서브넷, 네임스페이스, 레이블 조건)을 생성하여 지정한 파드가 파게이트에서 동작하도록 설정할 수 있습니다.
Fargate 제약 사항
K8S 의 일부 리소스나 기능을 사용 할 수 없습니다.
EKS에서 연동되는 AWS 서비스들의 일부는 사용 할 수 없습니다. (예, Classic Load balancer)
Priviliged 컨테이너는 호스트 권한을 갖기 때문에 사용할 수 없습니다.
HostNetwork
, HostPort
는 호스트쪽 네트워크와 포트를 파드에서 이용하는 기능이기 때문에 지원하지 않습니다.
HostPath
또한 사용할 수 없습니다.
Daemonset
을 사용할 수 없습니다. 파게이트에서는 파드와 노드(?)가 1:1로 동작하기 때문에 노드 각각에 파드가 1개씩 동작하는 구조이기 때문입니다.
Fargate 사용 사례
배치 처리 등 일시적이거나 정기적으로 많은 리소스를 사용해야 하는 경우
동시에 병렬 처리를 하기 위해 많은 파드를 동시에 동작시켜야 하는 경우
Karpenter는 Kubernetes 클러스터를 확장하는 데 사용되는 노드 수명 주기 관리 솔루션입니다.
들어오는 포드를 관찰하고 상황에 맞는 인스턴스를 시작합니다.
인스턴스 선택 결정은 인텐트 기반이며 리소스 요청 및 스케줄링 제약 조건을 포함하여 들어오는 포드의 사양에 따라 결정됩니다.
Nodeless Concept의 가장 중요한 노드 수명 주기 관리 솔루션입니다.
CA와 차별화된 강력한 프로비저닝 기능을 가지고 있습니다.
Karpenter는 다음의 기능을 수행합니다.
Kubernetes 스케줄러가 예약 불가능으로 표시한 파드(Pod) 감시
Pod에서 요청한 일정 제약 조건(리소스 요청, 노드 선택기, 친화도, 허용 오차 및 토폴로지 확산 제약 조건) 평가
포드의 요구 사항을 충족하는 프로비저닝 노드
노드가 더 이상 필요하지 않을 때 노드 제거
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
CoreDNS는 Kubernetes 클러스터 DNS로 사용할 수 있는 유연하고 확장 가능한 DNS 서버입니다.
하나 이상의 노드가 있는 Amazon EKS 클러스터를 시작하면 클러스터에 배포된 노드 수에 관계없이 CoreDNS 이미지의 복제본 2개가 기본적으로 배포됩니다.
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
AWS Load Balancer Controller는 Kubernetes 클러스터의 AWS Elastic Load Balancer를 관리합니다.
이 컨트롤러는 다음 리소스를 프로비저닝합니다.
Application Load Balancer(ALB)
Kubernetes의Ingress
를 생성할 때 ALB를 생성합니다.AWS Network Load Balancer(NLB)
LoadBalancer
유형의 Kubernetesservice
를 생성할 때 NLB를 생성합니다.과거에는 인스턴스 대상에 대해 Kubernetes Network Load Balancer를 사용했지만 IP 대상에 대해서는 AWS Load Balancer Controller를 사용했습니다.
AWS Load Balancer Controller 버전
2.3.0
이상에서 대상 유형 중 하나를 사용하여 NLB를 생성할 수 있습니다.
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
ExternalDNS는 kubernetes dns(kube-dns)와 상반되는 개념으로 내부 도메인서버가 아닌 Public한 도메인서버(AWS Route53, GCP DNS 등)를 사용하여 쿠버네티스의 리소스를 쿼리할 수 있게 해주는 오픈소스 솔루션입니다.
ExternalDNS를 사용하면 public도메인 서버가 무엇이든 상관없이 쿠버네티스 리소스를 통해서 DNS레코드를 동적으로 관리 할 수 있습니다.
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 -
쿠버네티스의 파드와 노드가 운영되고 있는 상태를 웹페이지를 통해 확인 할 수 있는 리소스 입니다.
최종 목표인 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"
Karpenter를 사용하기 위한 기본적인 내용을 이미 Step 00. EKS 클러스터 배포 과정을 통해 생성하였습니다.
Provisioner
와AWSNodeTemplate
를 생성하여 카펜터의 기능을 이용할 수 있습니다.# 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
카펜터를 이용하여 인프라를 운영할때 ttlSecondsUntilExpired
와 consolidation
를 사용하여 노드를 항상 최신의 상태로 운영할 수 있습니다.
그 과정에서 파드의 가용성을 보장하기 위한 방법으로 파드를 배포하는 우선권을 이용합니다.
우선권이 낮은 파드를 통해 일정 부분의 파드의 공간을 미리 선점하여 노드가 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
AWS에서 컨테이너 환경의 인프라를 구축, 운영할때 서버리스 서비스와 Karpenter를 조합하여 Nodeless(노드를 관리할 필요가 없는) Concept을 달성할 수 있습니다.
Node의 선택에서 보다 자유로워지며, Spot 인스턴스를 적극적으로 사용할 수 있었습니다.
EKS Cluster 업그레이드시, 매니지드 노드에 대해서 전체 cordon 하는 과정이 사라지므로 EKS 버전 업그레이드를 보다 수월하게 진행할 수 있습니다.