AWES[1기](Amzaon EKS Study)

[AWES] 1주차 - Amzaon EKS 설치 및 기본 사용

Joon0464 2023. 4. 27. 01:05

참여 소감

 현재 자바, 파이썬 애플리케이션을 SCP(Samsung Cloud Platform)의 Kubernetes 환경으로 마이그레이션 업무를 하면서 Kubernetes를 다룰 수 있다는 것에 대체로 만족하고 있지만, 마이그레이션 업무 특성상 생각보다 Kubernetes를 깊게 다뤄보기는 어려웠습니다.(이미 구축된 클러스터/네임스페이스에 워크로드만 구성하기 때문에...) CKA 자격증을 준비할 때 VirtualBox에 개인적으로 K8S Cluster를 구축해보긴 했지만.. AWS와 같은 클라우드 환경에서 K8S를 제대로 다뤄보고 싶다는 마음이 항상 있었고 이번 스터디를 통해 차근차근 배워나가고자 합니다. 

 아쉽게도 개인 일정이 있어 1주차 스터디는 실시간으로 참여할 수는 없었습니다. 영상을 녹화해서 올려주신 가시다님과 스터디를 도와주시는 분들께 감사드리면서 블로깅 시작해보겠습니다.

EKS 구성도

좌측 이미지: EKS Controlplane 구성도 / 우측 이미지: EKS Data Plane(Worker node) 구성도 

EKS와 일반적인 Kubernetes 와의 차이점

  • EKS는 AWS에서 관리해주는 AWS Managed VPC 내에 Controlplane이 존재하고 EKS 사용자(고객)의 VPC에 DataPlane(워커노드) 가 존재합니다.
  • EKS는 Kubernetes의 ControlPlane 리소스인 API 서버, Controller, Scheduler, etcd 등을 관리해줍니다.
  • EKS onwned ENI 존재
    • 사용자의 VPC 내에 자체적으로 관리되는 Elastic Network Interface(ENI)를 가지고 있습니다.
    • DataPlane와 VPC 사이의 네트워크 트래픽을 처리하며, EKS 클러스터의 ControlPlane과 DataPlane 간의 통신에 사용
  • DataPlane에 사용자(고객)의 파드가 배포됩니다.

EKS DataPlane 구성 타입

  1. Managed node groups(관리형 노드 그룹)
    1. AWS에서 관리하는 EC2 인스턴스 그룹으로, 최신 EKS Optimized AMI를 사용하며, AWS에서 AMI 관리와 Capacity(On-Demand, Spot)를 자동으로 처리
    2. 이 방법을 사용하면 EC2 인스턴스 관리를 간편하게 할 수 있으며, 높은 확장성과 안정성을 제공
  2. Self-managed nodes(자체 관리 노드)
    1. Custom AMI를 사용하며, Auto Scaling Group(ASG)을 직접 관리하여 DataPlane을 구성
    2. OS 기본 구성과 패치 역시 고객이 직접 관리 -> 더 많은 제어권, 유연성을 가질 수 있지만, 관리적인 부담 증가
  3. AWS Fargate(서버리스)
    1. AWS Fargate 환경에서 제공하는 Micro VM을 이용하여 Pod 단위로 VM을 할당
    2. EC2 인스턴스를 직접 관리할 필요 없이, 컨테이너 실행 환경만 구성하면 되므로 높은 간편성을 제공
    3. firecracker/design.md at main · firecracker-microvm/firecracker · GitHub

EKS Cluster Endpoint

1) Public

Puendpoint만 사용하는 경우

실제 통신이 되는 과정

  • API 서버 → (EKS owned ENI) → 워커노드의 Kubelet
  • 워커노드의 kube-proxy→ (퍼블릭 도메인) → API Server
  • 사용자(Local PC)의 kubectl → (퍼블릭 도메인) → API Server

2) Public, Private

Public, Private endpoint 사용하는 경우

실제 통신이 되는 과정

  • API 서버 → (EKS owned ENI) → 워커노드의 Kubelet
  • 워커노드의 kube-proxy→ (ENI의 Private hosted domain) → API Server
  • 사용자(Local PC)의 kubectl → (퍼블릭 도메인) → API Server

3) Private -> 실제 현업에서 사용하는 방식

Private endppoint만 사용하는 경우

실제 통신이 되는 과정

  • API 서버 → (EKS owned ENI) → 워커노드의 Kubelet
  • 워커노드의 kube-proxy→ (ENI의 Private hosted domain) → API Server
  • 사용자(Local PC)의 kubectl → (ENI의 Private hosted domain) → API Server

실습 구성도

실습

1. CloudFormation 배포

Bastion Host에 접속하기 위한 ssh키를 사전에 미리 생성해야 합니다. 참고로 pem키를 생성하면 자동으로 PC의 다운로드 경로에 키가 다운받아진 것을 볼 수 있을것입니다.
가시다님께서 제공해주신 CloudFormation을 통해 기본 인프라를 배포합니다.
거의 대부분 기본값으로 진행하면 되지만 "KeyName"은 이전에 생성한 키를 선택하고 "SgIngressSshCidr"은 {공용IP}/32 로 설정하면 됩니다. 여기서 "SgIngressSshCidr"은 Bastion Host에 sg inbound 정책에 적용되어 본인 PC 이외에는 해당 bastion host에 접근하지 못하도록 설정하기 위해 입력하는 파라미터입니다.
공용 IP를 확인하려면 ipconfig 대신 curl을 통해 위 도메인에 요청을 보내면 현재 본인 pc의 공용 IP 리턴 받을 수 있습니다.
CloudFormation 생성을 마치면 스택이 정상적으로 배포된 것을 볼 수 있습니다. 출력에 접근해보면 생성된 Bastion Host의 Public IP주소를 볼 수 있습니다.

2. Bastion Host(작업용 EC2) SSH 접속 및 배포 확인

Bastion Host에 접근하기 위해 Xshell에 접근 대상 Host, User Name, Public Key(AWS KeyPair에서 생성한 키)를 설정합니다.
정상적으로 접속되는 것을 볼 수 있다.
몇가지를 확인하고 넘어가면 관리형 워커노드에 ssh 접근을 위한 키가 미리 생성되어 있습니다.
cloudformation의 user data를 통해 bastion host에 docker, kubectl, eksctl, aws cli 등이 모두 설치되어있는 것을 확인할 수 있습니다

3. AWS IAM 자격 증명

- 작업용 EC2(Bastion Host)가 eksctl로 EKS Control Plane을 설치하고 워커노드를 배포해야하기 때문에 권한이 필요합니다. 이번 실습에서는 Administrator 권한을 가진 IAM user를 통해 자격증명을 진행하였습니다.

"IAM > 사용자 > {Admin 권한을 가진 IAM 계정} > 엑세스 키 만들기" 를 통해 CLI에 대한 엑세스 키를 생성합니다.
aws configure 명령어를 통해 위 과정에서 생성한 Access Key와 Secret Access Key를 입력하고 region 정보와 output format도 구성해줍니다.
설정을 완료하면 위와같이 CLI로 AWS 리소스를 조회/생성/업데이트/삭제 등의 작업을 할 수 있습니다. 위 명령어는 Bastion Host의 Name tag 값을 불러오는 aws cli 명령어 입니다.

4. 환경 변수 설정

EKS 클러스터를 콘솔에서 배포하는 것 보다는 aws cli를 사용하기 위해 사전에 변수부터 설정해줍니다.

# EKS 배포할 VPC 정보를 변수로 설정
export VPCID=$(aws ec2 describe-vpcs --filters "Name=tag:Name,Values=$CLUSTER_NAME-VPC" | jq -r .Vpcs[].VpcId)
echo "export VPCID=$VPCID" >> /etc/profile
echo $VPCID


## 퍼블릭 서브넷 ID를 변수로 설정
export PubSubnet1=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet1" --query "Subnets[0].[SubnetId]" --output text)
export PubSubnet2=$(aws ec2 describe-subnets --filters Name=tag:Name,Values="$CLUSTER_NAME-PublicSubnet2" --query "Subnets[0].[SubnetId]" --output text)
echo "export PubSubnet1=$PubSubnet1" >> /etc/profile
echo "export PubSubnet2=$PubSubnet2" >> /etc/profile
echo $PubSubnet1
echo $PubSubnet2

여담이지만 AWS CLI는 정말 편리합니다.

5. EKS 클러스터 생성

 

배포 전 다시 합 번 변수가 잘 설정되어 있는지 확인한다.

# eks 클러스터 & 관리형노드그룹 배포 전 정보 확인(실제 배포 X)
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.24 --ssh-access --external-dns-access --dry-run | yh
... 중략 ...
vpc: 
  autoAllocateIPv6: false
  cidr: 192.168.0.0/16
  clusterEndpoints: 
    privateAccess: false
    publicAccess: true
  id: vpc-04a877b4ab4b9ed72
  manageSharedNodeSecurityGroupRules: true
  nat: 
    gateway: Disable
  subnets: 
    public: 
      ap-northeast-2a: 
        az: ap-northeast-2a
        cidr: 192.168.1.0/24
        id: subnet-0e8f88439746d30a8
      ap-northeast-2c: 
        az: ap-northeast-2c
        cidr: 192.168.2.0/24
        id: subnet-0387669667f0cc26f

우선 배포전 dry run을 통해 테스트로 명령어를 수행하고 파이프라인으로 yh를 사용하면 보기 좋게 yaml 형식으로 출력하여 배포가 어떻게 이루어질 것인지 확인 가능합니다.

# 실제 EKS 클러스터 배포
$ 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.24 --ssh-access --external-dns-access --verbose 4

# 배포 모니터링 명령어
$ watch -n 1 "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"

모니터링을 하며 16분정도 기다리면 EKS 클러스터 배포가 완료됩니다.
최종적으로 배포가 완료되면 API서버 엔드포인트도 확인할 수 있습니다. 퍼블릭 도메인이기 때문에 누구나 접근 가능합니다.(실습 이후 바로 삭제 예정으로 블러처리하지 않았습니다.)
워커노드는 AutoScailing Group에 의해 관리되는 것을 볼 수 있습니다.
bastion host에서 API Server Endpoint에 대해 명령어를 수행해보면 IP주소가 반환되는데 이는 컨트롤 플레인의 NLB의 IP주소입니다.
kubectl 명령어 수행 로그 레벨을 높여서 명령어를 수행해보면 어떤 config 파일을 사용했는지 출력됩니다. "--kubeconfig /root/.kube/config" 의 argument가 명령어에 포함되어있는것과 마찬가지입니다. 해당 config를 통해 controlplane의 api서버에 인층과정을 거치고 kubectl 명령어를 요청하게 됩니다.

 

6. 워커노드 SSH  접속

 

Bastion Host -> 워커노드를 접근하기 위해서는 반드시 Security Group 설정을 해줘야합니다.

 

워커노드에 Ping을 날려보면 아직 정상적으로 통신이 되지 않는것을 볼 수 있습니다.

# Security Group ID값을 변수로 설정
$ NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*nodegroup* --query "SecurityGroups[*].[GroupId]" --output text)
$ echo $NGSGID

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

보안그룹에 인바운드 Rule을 추가한 후 다시 ping 테스트를 수행하면 정상적으로 패킷을 수신하게 됩니다.

# 워커 노드 SSH 접속 확인
$ ssh -i ~/.ssh/id_rsa ec2-user@$N1 hostname
$ ssh -i ~/.ssh/id_rsa ec2-user@$N2 hostname

SSH를 통해 hostname 조회가 정상적으로 이루어지는 것을 확인할 수 있습니다.

7. Kubectl exec 명령어 수행했을 때 네트워크 통신 과정 이해하기

$ ssh -i ~/.ssh/id_rsa ec2-user@$N2 sudo ss -tnp
State Recv-Q Send-Q Local Address:Port    Peer Address:Port Process                                               
ESTAB 0      0      192.168.2.142:59450 13.124.250.129:443   users:(("kubelet",pid=2842,fd=39))                   
ESTAB 0      0      192.168.2.142:52026 13.124.128.132:443   users:(("kube-proxy",pid=3092,fd=11))                


# kube-proxy와 kubelet이 API 서버의 퍼블릭 엔드포인트 IP로 통신하는 것 확인
$ nslookup CDF8079FECDC2D94BC67B74994915637.gr7.ap-northeast-2.eks.amazonaws.com
Server:		192.168.0.2
Address:	192.168.0.2#53

Non-authoritative answer:
Name:	CDF8079FECDC2D94BC67B74994915637.gr7.ap-northeast-2.eks.amazonaws.com
Address: 13.124.250.129
Name:	CDF8079FECDC2D94BC67B74994915637.gr7.ap-northeast-2.eks.amazonaws.com
Address: 13.124.128.132

테스트를 위한 exec 명령어 수행
192.168.1.240 -> 워커노드1의 IP / 192.168.2.35
AWS Console에서 확인해보면 192.168.2.35의 IP는 Description 에 Amazon EKS myeks라고 쓰여있는것을 볼 수 있습니다. 즉 워커노드1의 EKS owned ENI의 IP가 192.168.2.35라는 것을 알 수 있습니다.
ENI의 상세정보를 보면 특이한 점이 있는데 바로 소유자ID와 요청자 ID가 다르다는 것입니다. 즉 ENI의 소유자는 계정 ID와 동일하기 때문에 사용자의 소유지만 생성을 요청한 요청자(관리자)는

8. Deployment , Service 배포

# YAML 파일 다운로드
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/1/mario.yaml
# 워크로드 배포
$ kubectl apply -f mario.yaml -n default

Sevice를 배포하면 EC2 > 로드벨런서에 배포된 서비스를 확인할 수 있으며 DNS 이름을 통해 배포한 서비스에 접속 가능합니다.
마리오 게임 서비스에 정상 접근

9. 관리형 노드 Scale out

# 옵션 [터미널1] EC2 생성 모니터링
while true; do 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 text ; echo "------------------------------" ; sleep 1; done

# eks 노드 그룹 정보 확인
eksctl get nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup

# 노드 2개 → 3개 증가
eksctl scale nodegroup --cluster $CLUSTER_NAME --name $CLUSTER_NAME-nodegroup --nodes 3 --nodes-min 3 --nodes-max 6

# 노드 확인
kubectl get nodes -o wide
kubectl get nodes -l eks.amazonaws.com/nodegroup=$CLUSTER_NAME-nodegroup

# 노드 3개 → 2개 감소 : 적용까지 시간이 소요됨
aws eks update-nodegroup-config --cluster-name $CLUSTER_NAME --nodegroup-name $CLUSTER_NAME-nodegroup --scaling-config minSize=2,maxSize=2,desiredSize=2

ekctl 이외에도 awk cli를 통해서 가능합니다. 실습에서 총 3개로 Scale out 진행하였습니다.

 

10. 클러스터 및 리소스 제거

# EKS 클러스터 제거
$ eksctl delete cluster --name $CLUSTER_NAME

# AWS CloudFormation Stack 제거
$ aws cloudformation delete-stack --stack-name myeks

마지막으로 AWS에 돈을 계속 갖다 바칠 수는 없기 때문에 반드시 생성했던 리소스를 정리해줘야합니다. CloudFormation Stack을 제거하기 전에 우선 EKS 클러스터를 제거해야 합니다. 이번 실습을 마치고 EKS Cluster가 완전히 제거된 것을 확인하고  AWS CLI를 통해 CloudFormation Stack을 제거해주었습니다.

 

 

 

1주차 후기


1주차를 통해 EKS를 처음 접해보면서 EKS 클러스터가 어떻게 구성되는지, kubectl 명령어 수행 시 네트워크상으로 어떻게 통신이 이루어지는지 알 수 있어 매우 유익했던 시간이었습니다. 이번에 알게된 내용을 바탕으로 업무에 사용하는 SCP 의 Kubernetes 클러스터는 어떻게 구성되어있는지 다시 한 번 살펴봐야겠다고 느껴집니다. 스터디를 준비해주신 모든 분께 감사드립니다. 😊