본문 바로가기

DevOps

[AWS EKS] EKS 중요한건 꺽이지 않는 안정성

CloudNet@팀의 EKS 스터디 AEWS 2기에 작성된 자료를 토대로 작성합니다.

 

이번 시간에 다뤄볼 내용은 EKS가 어떻게 보안과 고가용성 두마리의 토끼를 잡는지 입니다 !! 

 

 

 

먼저 EKS를 설치하는 간단한 방법을 제공해주시기 때문에, 바로 설치를 하면서 설명을 하는 순서이다.

  CloudFormation으로 EKS 설치

✔️ 사전 준비 : AWS 계정, EC2 접속을 위한 SSH 키 페어, IAM 계정 생성 후 액세스 키 발급

✔️ 전체 구성도 : VPC 1개(퍼블릭 서브넷 3개, 프라이빗 서브넷 3개), EKS 클러스터(Control Plane), 관리형 노드 그룹(EC2 3대), Add-on

  CloudFormation > 스택생성 > 템플릿 업로드 

✔️  1번째 방식. eks workshop에서 제공하는 template을 받아두고, aws cli로 cloudformation 배포

curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick-new.yaml

✔️  2번째 방식. cloudformation에 템플릿 업로드

  • CloudFormation 스택 실행 시 파라미터를 기입하면, 해당 정보가 반영되어 배포됩니다.
1. <<<<< **Deploy EC2** >>>>>
    1. **KeyName** : bastion ec2에 SSH 접속을 위한 **SSH 키페어** 선택 *← 미리 SSH 키 생성 해두자!*
    2. **MyIamUserAccessKeyID** : **관리자** 수준의 권한을 가진 IAM User의 액세스 키ID 입력
    3. **MyIamUserSecretAccessKey** : **관리자** 수준의 권한을 가진 IAM User의 **시크릿 키ID** 입력 **← 노출되지 않게 보안 주의**
    4. **SgIngressSshCidr** : bastion 작업용 EC2에 **SSH 접속 가능한 IP** 입력 (**집 공인IP**/32 입력), 보안그룹 인바운드 규칙에 반영됨
    5. MyInstanceType:  bastion 작업용 EC2 인스턴스의 타입 (기본 **t2.micro**) ⇒ 변경 가능
    6. LatestAmiId : bastion 작업용 EC2에 사용할 AMI는 아마존리눅스2 최신 버전 사용
2. <<<<< **EKS Config** >>>>>
    1. **ClusterBaseName** : EKS **클러스터 이름**이며, **myeks** 기본값 사용을 권장 → 이유: 실습 리소스 태그명과 실습 커멘드에서 사용
    2. **KubernetesVersion** : EKS 호환, 쿠버네티스 버전 (기본 v1.25, 실습은 **1.24** 버전 사용) ⇒ 변경 가능
    3. **WorkerNodeInstanceType**: 관리형 노드 그릅 - 워커 노드 EC2 인스턴스의 타입 (기본 **t3.medium**) ⇒ 변경 가능
    4. **WorkerNodeCount** : 관리형 노드 그릅 - 워커노드의 갯수를 입력 (기본 3대) ⇒ 변경 가능
    5. **WorkerNodeVolumesize** : 관리형 노드 그릅 - 워커노드의 EBS 볼륨 크기 (기본 80GiB) ⇒ 변경 가능
3. <<<<< **Region AZ** >>>>> : 리전과 가용영역을 지정, 기본값 그대로 사용

 

  • 실습 환경을 위한 VPC 1개가 생성되고, 퍼블릭 서브넷 3개와 프라이빗 서브넷 3개(+NATGW)가 생성됩니다.
  • CloudFormation 에 EC2의 UserData 부분(Script 실행)으로 Amazon EKS 설치를 진행합니다.
  • 관리형 노드 그룹(워커 노드)는 AZ1~AZ3를 사용하여, 기본 3대로 구성됩니다.
  • Add-on 같이 설치됩니다 : 최신 버전 - kube-proxy, coredns, aws vpc cni - 링크
  • 다양한 컨트롤러등의 설치에 편리를 위해서 EC2 Instance Profile(IAM Role)에 다양한 Policy를 연동해두었습니다 → 물론 보안상 IRSA 설정을 권장합니다.

  Cloudformation 동작 20분  웨이팅 후 접속! 

# SSH 접속
ssh -i koo-seoul.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)

 

✔️ 접속 후 잘 설치되었는지 확인

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

# EKS 클러스터 설치 확인
kubectl cluster-info
eksctl get cluster
eksctl get nodegroup --cluster $CLUSTER_NAME


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

# krew 플러그인 확인
kubectl krew list

# 모든 네임스페이스에서 모든 리소스 확인
kubectl get-all

✔️ 워커노드 통신 및 접속 확인! 

# 노드 IP 확인 및 PrivateIP 변수 지정
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
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 $N1, $N2, $N3

# 보안그룹 ID와 보안그룹 이름(Name아님을 주의!) 확인
aws ec2 describe-security-groups --query 'SecurityGroups[*].[GroupId, GroupName]' --output text

# 노드 보안그룹 ID 확인
aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
echo $NGSGID

# 노드 보안그룹에 eksctl-host 에서 노드(파드)에 접속 가능하게 룰(Rule) 추가 설정
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

# eksctl-host 에서 노드의IP로 ping 테스트
ping -c 2 $N1
ping -c 2 $N2
ping -c 2 $N3

# 워커 노드 SSH 접속 : '-i ~/.ssh/id_rsa' 생략 가능
ssh ec2-user@$N1 
exit
ssh ec2-user@$N2
exit
ssh ec2-user@$N3
exit

ssh ec2-user@$N1 hostname
ssh ec2-user@$N2 hostname
ssh ec2-user@$N3 hostname

 

✔️ 삭제 방법

  • Amazon EKS 클러스터 삭제(10분 정도 소요):
  • (클러스터 삭제 완료 확인 후) AWS CloudFormation 스택 삭제 : 
# Amazon EKS 클러스터 삭제(10분 정도 소요):  
eksctl delete cluster --name $CLUSTER_NAME

# (클러스터 삭제 완료 확인 후) AWS CloudFormation 스택 삭제 : 
aws cloudformation delete-stack --stack-name myeks

 

✅  EKS 아키텍처의 특징   

Amazon Managed 라는 의미는 무엇을 뜻할까?

우선 kubernetes는 기능에 의해서 두가지로 나뉜다.

 

  • 컨트롤 플레인 
    • ETCD
    • API server
    • 컨트롤러, 스케쥴러
  • 데이터 플레인
    • EKS owned ENI
    • 노드 그룹
    • kubelet
    • kube-proxy

✅  컨트롤 플레인

  • 쿠버네티스를 제어 하기 위한 컴포넌트
  • AWS가 관리한다 
  • 고가용성을 고려한 설계가 되어있다. ( ELB, NLB, AZ, AutoScalingGroup )
  • API 서버, 컨트롤러, 스케쥴러, ETCD 등의 컴포넌트가 있다.

생성되는 VPC는 AWS에서 관리하는 Account가 관리하는 VPC 입니다.

  • 가용성 보장을 위해 가용영역을 나누고 etcd만 별개의 인스턴스로 분리하여 구성합니다.
  • 오토스케일링 그룹으로 각 컴포넌트들을 묶어 다수의 자원들이 안정적으로 유지됩니다.
  • 또한 각 가용영역 별로 트래픽 분산을 하는 환경을 구성하기 위해 Amazon CLB와 Amazon NLB를 사용합니다. 따라서 통칭 하여 ELB라고 표기된 자료들이 많습니다.

✅  데이터 플레인

  • 워커노드를 구성하기 위한 데이터 플레인 컴포넌트
  • 사용자 VPC에 생성됨 ( 사용자 어카운트에 배치 되기 때문에 사용자가 직접적으로 관리 및 운영)
  • POD, kubelet , Kube-proxy , Container-Runtime 등 
  • 노드 구성 방식들이 다양함 
    • 관리형
    • 자체형 : 사용자 정의 AMI를 사용해서 구성 , 유지관리 및 버전관리를 직접 수행
    • AWS Fargate : 컨테이너 형태의 구조가 아닌 마이크로 VM 형태의 가상머신으로 이루어짐
  • EKS Owned ENI 
    • 해당 네트워크 인터페이스는 소유자와 대상자가 서로 다른 어카운트로 이루어짐
    • == 크로스 어카운트 ENI
    • 해당 인터페이스를 통해 데이터 플레인과 컨트롤 플레인이 통신

 

 

  도전과제  1

[도전과제1] EKS Cluster Endpoint - Public Private : 직접 구성해보고 노드 → 제어부 연결 시 공인 도메인(IP)가 아닌, Private 도메인(EKS owned ENI) 연결 확인해보기

  • 먼저 현재 상태는 퍼블릭으로 지정되어 있음 ( IP가 퍼블릭 IP를 나타냄)
  • Public Private 상태의 Endpoint ? 
    • 노드는 프라이빗 서브넷에서 인스턴스화되고 (예: 로드 밸런서)는 퍼블릭 서브넷에서 인스턴스화 됩니다
(koo@myeks:default) [root@myeks-bastion ~]# kubectl get node -v=5

NAME                                               STATUS     ROLES    AGE   VERSION
ip-192-168-1-203.ap-northeast-2.compute.internal   NotReady   <none>   8d    v1.24.17-eks-5e0fdde
ip-192-168-2-42.ap-northeast-2.compute.internal    Ready      <none>   8d    v1.24.17-eks-5e0fdde
ip-192-168-3-125.ap-northeast-2.compute.internal   Ready      <none>   8d    v1.24.17-eks-5e0fdde
(koo@myeks:default) [root@myeks-bastion ~]# 
(koo@myeks:default) [root@myeks-bastion ~]# ss -tnp | grep kubectl
(koo@myeks:default) [root@myeks-bastion ~]# APIDNS=$(aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint | cut -d '/' -f 3)
(koo@myeks:default) [root@myeks-bastion ~]# dig +short $APIDNS
15.164.253.59
3.36.145.45

  • Private + Public (IP 주소제한) 으로 변경 
  • 변경 방법은 편한 방법을 사용하면 된다.
    • eks 명령어와 --resources-vpc-config 옵션을 통한 변경
    • UI 에서 Manage Endpoint access 클릭을 통한 변경
    • eksctl 의 설치 파일 config를 변경하여 변경
aws eks update-cluster-config --region $AWS_DEFAULT_REGION --name $CLUSTER_NAME --resources-vpc-config endpointPublicAccess=true,publicAccessCidrs="$(curl -s ipinfo.io/ip)/32",endpointPrivateAccess=true

  • 이제 부터 동일한 DNS로 질의 할 시 Private IP를 리턴함
(koo@myeks:default) [root@myeks-bastion ~]# dig +short $APIDNS
192.168.1.233
192.168.3.82

# EKS ControlPlane 보안그룹 ID 확인
aws ec2 describe-security-groups --filters Name=group-name,Values=*ControlPlaneSecurityGroup* --query "SecurityGroups[*].[GroupId]" --output text
CPSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ControlPlaneSecurityGroup* --query "SecurityGroups[*].[GroupId]" --output text)
echo $CPSGID

# 노드 보안그룹에 eksctl-host 에서 노드(파드)에 접속 가능하게 룰(Rule) 추가 설정
aws ec2 authorize-security-group-ingress --group-id $CPSGID --protocol '-1' --cidr 192.168.1.100/32



(koo@myeks:default) [root@myeks-bastion ~]# kubectl get node -v=5

NAME                                               STATUS   ROLES    AGE   VERSION
ip-192-168-1-203.ap-northeast-2.compute.internal   Ready    <none>   8d    v1.24.17-eks-5e0fdde
ip-192-168-2-42.ap-northeast-2.compute.internal    Ready    <none>   8d    v1.24.17-eks-5e0fdde
ip-192-168-3-125.ap-northeast-2.compute.internal   Ready    <none>   8d    v1.24.17-eks-5e0fdde

 

  도전과제  2,3

[도전과제 2 3] EKS Cluster를 YAML 파일로 만들어서  Spot instance를 배포해보기

  • 먼저 우리 bastion 서버에 spot-instance.yaml이라는 파일을 만들것이다. 
  • eksctl로 dry-run 옵션을 통해 다음과 같이 생성 예정인 yaml을 먼저 확인한다.
eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium \
--node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.28 --ssh-access --external-dns-access --dry-run > spot-instance.yaml
  • 기존에 설치한 eks cluster을 삭제하고 다시 배포할 것
# eks 클러스터 삭제 
eksctl delete cluster --name $CLUSTER_NAME
  • 파일을 통해서 노드그룹을 Spot instance로 새로 배포해보자.
# 위에 --dry-run으로 작성된 yaml에 다음과 같이 spot instance을 Custom nodeGroups으로 추가한다.
# node group 추가는 https://eksctl.io/usage/nodegroups/
# spot-instance.yaml

''' 중략 '''
nodeGroups:
- name: spot-1
  minSize: 2
  maxSize: 5
  instancesDistribution:
    maxPrice: 0.017
    instanceTypes: ["t3.small", "t3.medium"] 
    onDemandBaseCapacity: 0
    onDemandPercentageAboveBaseCapacity: 50
    spotInstancePools: 2

  • 스팟 인스턴스는 온디맨드 가격보다 저렴한 가격으로 제공되는 예비 EC2 용량을 사용합니다.
  •  스팟 인스턴스를 사용하면 사용하지 않는 EC2 인스턴스를 대폭 할인된 가격으로 요청할 수 있으므로 Amazon EC2 비용을 크게 낮출 수 있습니다.
  • 중단 허용 워크로드에만 사용할것을 권장
  • 배치 및 기계 학습 교육 워크로드, Apache Spark와 같은 빅 데이터 ETL, 대기열 처리 애플리케이션, 상태 비저장 API 엔드포인트가 포함

  도전과제  4

[도전과제 4 ]

  • AWS Graviton Amazon EC2에서 실행되는 클라우드 워크로드에 최고의 가격 대비 성능을 제공하도록 AWS에서 설계 한 인스턴스
  • AWS Graviton 프로세서는 AWS Nitro 시스템을 기반으로 구축되었습니다 .
  • AWS는 호스트 하드웨어의 거의 모든 컴퓨팅 및 메모리 리소스를 인스턴스에 제공하기 위해 AWS Nitro 시스템을 구축했습니다.
  • Nitro ?  하이퍼바이저 기능과 관리 기능을 호스트에서 분리하고 전용 하드웨어 및 소프트웨어로 베어 메탈과 차이가 없는 성능을 제공하는 경량 하이퍼바이저를 사용하는 차세대 EC2 인스턴스를 위한 기본 플랫폼

# 먼저 클러스터를 배포한다.
eksctl create cluster --name $CLUSTER_NAME --region=$AWS_DEFAULT_REGION --nodegroup-name=$CLUSTER_NAME-nodegroup --node-type=t3.medium \
--node-volume-size=30 --vpc-public-subnets "$PubSubnet1,$PubSubnet2" --version 1.28 --ssh-access --external-dns-access --verbose 4

# 이후에 노드그룹을 추가한다.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: myeks
  region: ap-northeast-2

managedNodeGroups:
  - name: graviton
    instanceTypes:
    - t4g.medium
    minSize: 1
    maxSize: 3
    desiredCapacity: 1
    privateNetworking: true
    subnets:
    - subnet-0db86b686270585fd
  • 이와 같이 config.yaml 을 만든후에 다음과 같이 노드그룹 추가 가능.
# 아까 만들어둔 config-gravition.yaml
eksctl create nodegroup --config-file=config-gravition.yaml
  • nodegroup 추가됨 

  • T4g 인스턴스는 대규모 마이크로서비스, 중소형 데이터베이스, 가상 데스크톱 및 비즈니스 크리티컬 애플리케이션에 주로 사용됨

 

  내가만든 도전과제  

[도전과제 9 ] EKS Cluster privatelink-access 패턴을 이용하여 접속해보기 

- 먼저 다음 블로그는 트래픽을 인터넷에 노출하지 않고 VPC와 Cross Account에 걸쳐 연결을 제공하는 다양한 서비스중 

- 엔드포인트 서비스인 PrivateLink을 사용한 연결방식을 소개합니다.

- PrivateLink 서비스는 인터페이스 VPC 엔드포인트의 인터페이스 역할  VPC 내부에 직접 생성됩니다.

- A service powered by PrivateLink is created directly inside your VPC as an interface VPC endpoint.

 

Amazon VPC 연결 옵션

트래픽을 공용 인터넷에 노출하지 않고 VPC와 계정 전반에 걸쳐 연결을 제공하는 다양한 AWS 기본 네트워킹 서비스가 여럿 있습니다. 

  • VPC Peering – which creates a connection between two or more VPCs that allow endpoints to communicate as if they were on the same network. It can connect VPCs that are in different accounts and in different regions.
  • AWS Transit Gateway – which acts as a highly scalable cloud router connecting multiple VPCs in a region with a hub-and spoke architecture. Transit Gateway works across accounts using AWS Resource Access Manager (AWS RAM).
  • AWS PrivateLink – which provides private connectivity to a service in one VPC that can be consumed from a VPC in the same account or from another account. PrivateLink supports private access to customer application services, AWS Marketplace partner services, and to certain AWS services.

 

git clone https://github.com/aws-ia/terraform-aws-eks-blueprints.git
cd terraform-aws-eks-blueprints/patterns/privatelink-access
  • 테라폼을 통해 private-link 배포 (엔드포인트 서비스, 엔드포인트 15종 )
terraform init
terraform apply -target=module.eventbridge -target=module.nlb --auto-approve
terraform apply --auto-approve
  • eks 명령 을 이용한 엔드포인트 private access로 전환
aws eks update-cluster-config \
--region us-west-2 \
--name privatelink-access \
--resources-vpc-config endpointPublicAccess=false,endpointPrivateAccess=true

 

EKS API 서버 엔드포인트의 프라이빗 호스팅 영역이 작동했습니다.

    • PrivateLink 액세스는 NLB를 통해 서비스 제공업체 VPC에서 프라이빗인 EKS 클러스터의 프라이빗 API 엔드포인트까지 작동했습니다.
    • EKS Kubernetes API 서버 엔드포인트가 준비되어 작동합니다.
    • AWS Session Manager을 통한 접속 

# private Endpoint
COMMAND="curl -ks https://91FDCFCB79B48A9D00D839F56B74F911.gr7.us-west-2.eks.amazonaws.com/readyz"

# AWS의 노드관리 - 명령실행에서 RunShellScript라는 Document을 찾아서 적용
COMMAND_ID=$(aws ssm send-command --region us-west-2 \
   --document-name "AWS-RunShellScript" \
   --parameters "commands=[$COMMAND]" \
   --targets "Key=instanceids,Values=i-0a77c835dd82d125c" \
   --query 'Command.CommandId' \
   --output text)


aws ssm get-command-invocation --region us-west-2 \
   --command-id $COMMAND_ID \
   --instance-id i-0a77c835dd82d125c \
   --query 'StandardOutputContent' \
   --output text

 

# 세션관리자 접속 후
aws eks update-kubeconfig --region us-west-2 --name privatelink-access

# kubectl 명령어 적용
kubectl get pods -A
NAMESPACE     NAME                       READY   STATUS    RESTARTS   AGE
kube-system   aws-node-4f8g8             1/1     Running   0          1m
kube-system   coredns-6ff9c46cd8-59sqp   1/1     Running   0          1m
kube-system   coredns-6ff9c46cd8-svnpb   1/1     Running   0          2m
kube-system   kube-proxy-mm2zc           1/1     Running   0          1m

이와 같이 세션 관리자를 통해 ssh 접근에 대한 보안성을 약화를 방지 할수 있다.

 

 

시스템 매니저로 SSH가 아닌 원격접속 - 선택이 아닌 필수일수도...!!

  • AWS System Manager - Session Manager
  • 시스템 매니저를 통한 원격 접속은 SSH가 아님 따라서 보안에 유리함
  • AWS 리소스 운영에 도움이 되는 기능을 제공.
  • 5 종류의 범주로 분류 되며 각 범주 내에 다양한 기능이 존재합니다.
더보기
  • 운영관리 Operations Management
  • 애플리케이션 관리 Application Management
  • 작업 및 변경 Actions & Change
  • 인스턴스  노드 Instances & Nodes - Session Manager
  • 공유된 리소스 Shared Resources