1W - EKS 설치 및 액세스 엔드포인트 변경 실습
개요
본 글은 다음의 내용을 설명하고, 이를 위한 실습을 진행한다.
- EKS 클러스터 구조
- api 서버 통신 방식(클러스터 액세스 엔드포인트)
- 보안 그룹 구조
- 로컬 환경에서 eksctl을 통한 EKS 설치
- 간단한 환경 분석
- 액세스 엔드포인트 변경
이를 통해 기본적으로 EKS를 세팅하는 방법을 익히고, 전체적인 구조가 어떻게 돼있는지를 익히는 시간을 가질 것이다.
특히 액세스 엔드포인트는 EKS 사용의 안전성과 비용 효율성을 위해 꼭 알아야 할 포인트이다.
사전 지식
EKS란?
EKS는 Elastic Kubernetes Service의 약자로, 4.RESOURCE/KNOWLEDGE/AWS/AWS에서 제공하는 관리형 쿠버네티스 서비스이다.[1]
관리형이라 하면 컨트롤 플레인이라는 하나의 큰 운영 영역을 AWS에서 관리해주는 것을 말한다.
eks를 만들면 컨트롤 플레인 영역은 사용자의 vpc와 분리된 영역에 생기게 된다.[2]
그리고 컨트롤 플레인은 aws에서 관리되는 vpc에 배치되고, 사용자들은 별도의 vpc를 통해 데이터 플레인을 관리할 수 있게 된다.
그럼 이 플레인 간의 통신은 어떻게 이뤄지는가?
플레인 간 통신이 필요한 경우는 크게 2가지이다.
- 컨트롤 플레인 to 데이터 플레인
- 데이터 플레인 to 컨트롤 플레인
- kube-proxy, kubelet의 웬만한 모든 요청!
- 허브앤스포크 방식으로 api서버에게서 희망 상태를 받아와서 실제 노드에 반영을 해야 하는 컴포넌트들의 요청
- kube-proxy, kubelet의 웬만한 모든 요청!
이때 전자의 경우에는, 사진에 보이는 ENI를 통해 이뤄지게 된다.
것은 사용자가 만드는 게 아니라 aws 측에서 알아서 만들어서 관리해주는 것으로 EKS owned ENI라고 부른다.
참고로 이렇게 vpc 간에, 혹은 다른 리소스에 네트워크 엔드포인트를 뚫는 것을 프라이빗 링크라고 부른다.[3]
원래는 이것도 비용이 나가는 놈인데, 이 비용은 그래도 클러스터 비용에 포함되어 있어 별도 지불은 하지 않는다.
클러스터 액세스 엔드포인트
후자, 데이터 플레인에서 컨트롤 플레인으로 가는 요청의 경우의 통신은 api 서버에 대한 액세스 포인트를 어떻게 설정하는가에 따라 방식이 달라진다.
api 서버와 통신하는 주체는 크게 두 가지로 분류할 수 있다.
- 쿠버네티스를 사용하고자 하는 우리같은 사용자
- kubelet과 같은 각종 컴포넌트
여기에 대해서 3가지 모드를 사용할 수 있다.
퍼블릭
모든 요청이 퍼블릭 ip를 통해 이뤄진다.
즉, kubelet에서 api 서버로 가는 요청도 퍼블릭을 나갔다 들어온다..
위 그림의 데이터 플레인을 보면 kube-proxy의 트래픽인 파란 줄이 igw를 타고 나가서 다시 igw를 타고 api 서버로 접근하는 게 보인다.
달리 말해서, igw 트래픽 아웃바운드 요금이 발생하게 된다.
여기에 노드들이 프라이빗 서브넷에 있었다면 nat를 타는 비용까지 발생할 것이다.
프라이빗
모든 통신은 프라이빗 ip를 통해서만 이뤄진다.
워커 노드의 요청인 분홍 줄이 eni를 타고 간다.
그래서 만약 해당 요청이 다른 az로 가는 경우에 발생하는 크로스존 비용만 발생할 것이다.
그런데 클라이언트의 퍼블릭 요청도 막힌다.
즉, 클라이언트가 접근을 하고 싶더라도 무조건 바스티온 같은 별도의 세팅을 해야만 한다.
Private hosted zone에서 오는 요청에 대해 dns 서버가 내부 ip를 리턴하기 때문에 가능한 방식인데, 바로 아래 혼용모드에서 조금 더 다루겠다.
혼용
위의 짬뽕 버전으로, 클라이언트만 퍼블릭 ip를 사용하는 구조이다.
정말 필요한 외부 트래픽만 퍼블릭 ip를 사용하니 상대적으로 안전하고, 비용도 효율적이다.
이 방식은 DNS를 이용해서 구현된다.
그림처럼 46~.aws.com
의 주소에 대해서, Amazon Route53이 데이터 플레인의 vpc에서는 프라이빗 ip를 반환해준다.
그 외의 장소에서의 요청은 전부 퍼블릭 ip를 반환하는 것이다.
이러한 동작 방식을 스플릿 호라이즌 DNS라고 부른다.
이를 통해 vpc 내의 컴포넌트들은 내부 eni를 타고 api 서버와 통신을 할 수 있게 된다.
이 기술의 구현은 Amazon Route53을 통해 이뤄진다.
Route53에서는 소스 ip에 따라 질의의 결과를 달리 할 수 있도록 호스팅 영역이란 개념을 가지고 있다.
프라이빗, 혼용 모드가 설정된다면 53은 vpc를 위한 프라이빗 호스팅 영역을 마련해준다(aws 관리 영역이라 우리 눈에는 안 보임).
그래서 데이터 플레인 vpc에서 오는 api 서버 엔드포인트 질의에 대해서 내부 ip를 리턴해준다.
이를 위해서는 vpc에 해당 세팅들이 활성화돼있어야 한다(eksctl, 콘솔로 만들 때는 알아서 세팅됨).
전자는 route53을 통해 dns 질의를 하게 하는 기능이고, 후자는 퍼플릭 ip를 받은 인스턴스에게 퍼블릭 전용 도메인을 부여해주는 기능이다(aws 설명임..).
후자는 구체적으로 호스트 네임을 기반으로 dns 해석을 할 수 있도록 설정하는 기능으로, 이게 설정돼야 프라이빗 호스팅 영역 질의 반환을 받을 수 있게 된다.
api 서버 도메인을 보고, 이건 호스팅 영역 질의를 받아야한다고 판단하기 위한 기능이라고 보면 되시겠다.
보안 그룹
eks의 보안 그룹은 어떻게 구성될까?
크게 보면 구조는 이렇게 되는데, 구체적으로 각 vpc에 속하는 리소스, eni가 해당 보안그룹들을 가지게 된다는 것이다.
- 클러스터 보안 그룹(clutser SG)
- 클러스터의 모든 eni(컨트롤 플레인 포함)가 받게 되는 보안 그룹으로, 자기 자신과 노드 그룹에 대한 인바운드를 허용한다.
- 그래서 사실 이것만 있으면 클러스터 내에서 이뤄지는 요청이 전부 허용된다.
- 이 보안그룹은 eks가 만들어질 때 aws가 만들어주는 보안 그룹으로, 초기 커스텀이 불가능하다.
- 컨트롤 플레인 보안 그룹(control plane SG)
- 컨트롤 플레인에서 튀어나온 eni들이 받게 되는 보안 그룹이다.
- 443, 즉 api 서버로 오는 인바운드 트래픽을 허용한다.
- 해당 ec2를 직접 확인할 수는 없기 때문에 추정이나, 아래 [[#네트워크 인터페이스 확인]] 부분에서 특정 eni가 이걸 받는 것이 보인다.
- 클러스터 공유 노드 보안 그룹(cluster shared node SG)
- 사용자가 노드들을 묶을 때 사용할 수 있는 보안 그룹이다.
- 인바운드로 자기 자신과, 위의 클러스터 보안 그룹을 두고 있다.
- 관리형 노드 그룹으로 기본 설치를 한다면 이 보안 그룹은 만들어지기만 하고, 사실 어떤 노드에도 할당되지 않는다..
- 노드 유형이 관리형이던, 직접 조작하는 노드던, 이 보안 그룹을 가지면 결국 클러스터와 통신이 가능해진다.
- 사실 클러스터 보안 그룹만 있어도 서로 모든 노드끼리 통신이 되기에, 막상 쓰는 케이스가 많이 없는 것 같다.
문서[4]를 보니, 다른 보안 그룹들은 일단 최소 옛 버전 기준으로 더 이상 사용되지 않으며 제거될 수 있다고 한다.
내 생각에는 클러스터 보안 그룹 이외의 보안 그룹들은 전부 deprecated된 채 껍데기만 남아있는 게 아닌가 한다.
이게 조금 더 명확한 사진[5]인 것 같기는 한데, 막상 만들어진 클러스터를 보면 이렇게 생겨먹지 않더라.
실습 진행
이번 실습은 먼저 구성된 vpc가 있는 상황에서 진행한다.
실습을 통해 구축할 클러스터의 정보는 이러하다.
- 편의와 비용 효율을 위해 퍼블릭 서브넷에 워커 노드 구축
- 관리형 노드 그룹 활용
그리고 eks를 편리하게 관리할 수 있는 도구인 eksctl을 활용하여 로컬에서 yaml 파일로 클러스터를 구축한다.
사전 준비
먼저 클라우드 포메이션을 이용해 vpc를 구성하자.
해당 양식은 클라우드넷팀에서 만들어준 것을 조금 변형한 것으로, 호스트 ec2를 만드는 부분을 없앴다.
ㅇㅇㅇAWSTemplateFormatVersion: '2010-09-09'
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "<<<<< EKSCTL MY EC2 >>>>>"
Parameters:
- ClusterBaseName
- MyInstanceType
- LatestAmiId
- Label:
default: "<<<<< Region AZ >>>>>"
Parameters:
- TargetRegion
- AvailabilityZone1
- AvailabilityZone2
- Label:
default: "<<<<< VPC Subnet >>>>>"
Parameters:
- VpcBlock
- PublicSubnet1Block
- PublicSubnet2Block
- PrivateSubnet1Block
- PrivateSubnet2Block
Parameters:
ClusterBaseName:
Type: String
Default: myeks
AllowedPattern: "[a-zA-Z][-a-zA-Z0-9]*"
Description: must be a valid Allowed Pattern '[a-zA-Z][-a-zA-Z0-9]*'
ConstraintDescription: ClusterBaseName - must be a valid Allowed Pattern
MyInstanceType:
Description: Enter t2.micro, t2.small, t2.medium, t3.micro, t3.small, t3.medium. Default is t2.micro.
Type: String
Default: t3.medium
AllowedValues:
- t2.micro
- t2.small
- t2.medium
- t3.micro
- t3.small
- t3.medium
LatestAmiId:
Description: (DO NOT CHANGE)
Type: 'AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>'
Default: '/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2'
AllowedValues:
- /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
TargetRegion:
Type: String
Default: ap-northeast-2
AvailabilityZone1:
Type: String
Default: ap-northeast-2a
AvailabilityZone2:
Type: String
Default: ap-northeast-2c
VpcBlock:
Type: String
Default: 192.168.0.0/16
PublicSubnet1Block:
Type: String
Default: 192.168.1.0/24
PublicSubnet2Block:
Type: String
Default: 192.168.2.0/24
PrivateSubnet1Block:
Type: String
Default: 192.168.3.0/24
PrivateSubnet2Block:
Type: String
Default: 192.168.4.0/24
Resources:
# VPC
EksVPC:
Type: AWS::EC2::VPC
Properties:
CidrBlock: !Ref VpcBlock
EnableDnsSupport: true
EnableDnsHostnames: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-VPC
# PublicSubnets
PublicSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref PublicSubnet1Block
VpcId: !Ref EksVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnet1
- Key: kubernetes.io/role/elb
Value: 1
PublicSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref PublicSubnet2Block
VpcId: !Ref EksVPC
MapPublicIpOnLaunch: true
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnet2
- Key: kubernetes.io/role/elb
Value: 1
InternetGateway:
Type: AWS::EC2::InternetGateway
VPCGatewayAttachment:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
InternetGatewayId: !Ref InternetGateway
VpcId: !Ref EksVPC
PublicSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PublicSubnetRouteTable
PublicSubnetRoute:
Type: AWS::EC2::Route
Properties:
RouteTableId: !Ref PublicSubnetRouteTable
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref InternetGateway
PublicSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet1
RouteTableId: !Ref PublicSubnetRouteTable
PublicSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PublicSubnet2
RouteTableId: !Ref PublicSubnetRouteTable
# PrivateSubnets
PrivateSubnet1:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone1
CidrBlock: !Ref PrivateSubnet1Block
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnet1
- Key: kubernetes.io/role/internal-elb
Value: 1
PrivateSubnet2:
Type: AWS::EC2::Subnet
Properties:
AvailabilityZone: !Ref AvailabilityZone2
CidrBlock: !Ref PrivateSubnet2Block
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnet2
- Key: kubernetes.io/role/internal-elb
Value: 1
PrivateSubnetRouteTable:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref EksVPC
Tags:
- Key: Name
Value: !Sub ${ClusterBaseName}-PrivateSubnetRouteTable
PrivateSubnet1RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet1
RouteTableId: !Ref PrivateSubnetRouteTable
PrivateSubnet2RouteTableAssociation:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref PrivateSubnet2
RouteTableId: !Ref PrivateSubnetRouteTable
클라우드 포메이션 페이지에 들어가서 직접 양식을 넣어서 입맛에 맞게 세팅해주면 된다.
curl -sL "https://github.com/eksctl-io/eksctl/releases/latest/download/eksctl_Linux_amd64.tar.gz" | tar xz -C /tmp
mv /tmp/eksctl /usr/local/bin
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip >/dev/null 2>&1
./aws/install
complete -C '/usr/local/bin/aws_completer' aws
echo 'export AWS_PAGER=""' >>/etc/profile
export AWS_DEFAULT_REGION=${AWS::Region}
echo "export AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION" >> /etc/profile
eksctl, aws cli를 설치한다.
자격 증명은 알아서.. aws configure
로 해줍시당.
export VPCID=$(aws ec2 describe-vpcs | jq -r '.Vpcs[] | select(has("Tags") and .Tags[].Value == "myeks-VPC").VpcId')
export PubSubnet1=$( aws ec2 describe-subnets | jq -r '.Subnets[] | select(.VpcId == env.VPCID) | select(.Tags[].Value == "myeks-PublicSubnet1").SubnetId')
export PubSubnet2=$( aws ec2 describe-subnets | jq -r '.Subnets[] | select(.VpcId == env.VPCID) | select(.Tags[].Value == "myeks-PublicSubnet2").SubnetId')
만들어진 vpc의 정보들을 환경변수로 등록하고 활용하자.
여기에서 이름과 관련된 값은 당연히 본인이 만들 때 넣은 값으로 바꿔줘야 한다.
구축
aws eks describe-addon-versions --kubernetes-version 1.32 --query 'addons[].{MarketplaceProductUrl: marketplaceInformation.productUrl, Name: addonName, Owner: owner Publisher: publisher, Type: type}' --output table
쿠버네티스 버전에 따라 존재하는 애드온 목록을 출력할 수 있다.
어차피 eks를 콘솔로 설치한다면 이렇게 볼 필요는 없겠지만, 만약 테라폼이나 eksctl로 사전 세팅을 하고자 하는 경우에는 이 이름들을 사용해야 하기에 유용하다.
ADDON_NAME=coredns
aws eks describe-addon-versions --addon-name $ADDON_NAME | jq -r ' .addons[] | .addonVersions[] | select(.architecture[] | index("amd64")) | "AddonVersion:\t" + .addonVersion, "ClusterVersion:\t" + ([.compatibilities[].clusterVersion] | join("\t")), "DefaultVersion:\t" + ([.compatibilities[].defaultVersion | tostring] | join("\t")), "" '
이런 식으로 애드온 별로 호환되는 버전에 대한 정보도 확인할 수 있다.
최근이면서 그나마 많은 애드온을 지원하고 있는 1.31버전을 설치하도록 한다.
eksctl yaml 파일 설정
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: aews-eks
region: ap-northeast-2
version: "1.31"
vpc:
id: ""
subnets:
public:
ap-northeast-2a:
id: ""
ap-northeast-2c:
id: ""
managedNodeGroups:
- name: myeks-nodegroup
instanceType: t3.medium
subnets:
- ""
- ""
volumeSize: 20
ssh:
allow: true
publicKeyPath: ../pki/pub.pub
# new feature for restricting SSH access to certain AWS security group IDs
#sourceSecurityGroupIds: ["sg-00241fbb12c607007"]
labels: {org: aews}
iam:
withAddonPolicies:
externalDNS: true
양식은 이런 식으로 만들면 되는데, 예제가 많다.[6]
존재하는 vpc와 설치할 서브넷을 넣어준다.
안에 활용하게 될 서브넷도 같이 명시하지 않으면 에러가 발생한다.
중간에 ssh 관련 필드가 있는데, 이걸 설정해주면 추후에 ssh로 워커 노드에 편하게 접근할 수 있게 된다.
해당 노드에 원하는 ssh키를 사용하는 동시에 ssh를 허용하는 보안 그룹을 자동으로 만들어주기 때문이다.
eksctl create cluster -f eks-config.yaml
준비가 완료됐다면, yaml을 적용하여 설치를 진행한다!
eks 클러스터는 클라우드포메이션으로 관리되며, 나중에 클러스터가 구축되면 클포 콘솔에서도 확인이 가능하다.
구축에는 대략 15분 정도의 시간이 소요된다.
클러스터 기본 분석
구축이 완료됐다면, 본격적으로 어떤 식으로 만들어진 것인지 확인해보자.
콘솔을 통한 분석
eks 확인
제대로 됐다면 이런 식으로 표시가 될 것이다.
iam 계정으로 노드들을 확인할 수 없어 잠시 root 계정으로 들어가서 노드를 찍었다.
아직 조금 잘 모르겠는데, 내가 eks를 만들 때 실수로 root 신원으로 만들어버려서 그런 것 같다.
폴리시들을 추가해봐도 iam 유저로 볼 수가 없는데, 이건 나중에 추가적으로 더 확인해봐야할 듯하다.
보안 그룹 확인
생성된 보안 그룹들도 확인해보자.
여기에서 추가 보안 그룹으로 표시된 것이 컨트롤 플레인 [[#보안 그룹]]이다.
아예 보안 그룹 창으로 가보면 ssh 허용을 하여 생긴 보안 그룹 말고 3개의 보안 그룹이 있는 것이 확인된다.
네트워크 인터페이스 확인
5개의 인터페이스가 만들어진 것이 확인된다.
이중에서 기본으로 이름 붙여진 것들은 워커 노드들이 기본적으로 가지게 되는 네트워크 인터페이스이다.
퍼블릭 서브넷에 노드를 배치했기에 퍼블릭 ip를 받은 것도 확인할 수 있다.
클러스터 보안 그룹에 속해 있는 것을 볼 수 있다.
재밌게 볼 것은 이 녀석이다.
내가 바로 이름을 컨트롤로 붙여버렸는데, 이 친구가 바로 컨트롤 플레인에서 우리의 vpc로 연결된, EKS owned eni되시겠다.
그래서 컨트롤 플레인 보안 그룹도 붙어 있는 것이 보인다.
인스턴스 소유자가 우리 계정 id가 아닌데, eks에서 소유한 eni이기 때문에 그렇다.
마찬가지로 우리의 인스턴스가 아닌, eks가 관리하는 인스턴스라 어떤 인스턴스인지도 보여주지 않는다.
완전 다른 인터페이스가 하나 더 만들어져 있는데, 이 친구는 잘 모르겠어서 일단 패스.
내가 설명 부분을 보고 k8s라 이름 붙였는데, 정확하게 무엇일까?
아래에서 ip 주소를 이용해서 노드에 직접 들어가서 확인해보자.
오토스케일링 그룹
노드 스케일링은 오토 스케일링을 통해 이뤄진다.
처음 클러스터가 생길 때 인스턴스를 두 개로 잡았기에 이렇게 2개가 만들어지게 됐다.
cli를 통한 분석
eks 구축이 완료되는 시점에면 이렇게 알아서 로컬에 kubeconfig 파일을 넣어준다.
k config view
들어간 정보를 한번 본다.
aws eks 명령을 통해 토큰을 받아오고, 이를 이용해서 클러스터와 통신을 하는 방식이다.
구체적으로는 sts를 활용하는 방식으로 로컬의 사용자 정보를 활용하여 aws cli가 동작한다.
달리 말해서 직접 이걸 쳐볼 수 있는데, 유효기간이 길지는 않다.
이 토큰이 클러스터의 인증을 통과하도록 돼있다는 것은 api 서버의 인증 모듈에 aws iam과 관련된 것이 존재한다는 것이다.
아마 관련한 내용은 이후 주차에 더 자세히 다룰 가능성이 높으므로 여기까지만 탐구하도록 한다.
그럼 본격적으로 클러스터 정보들을 확인해보도록 하자.
# api 서버 도메인 조회
kubectl cluster-info
aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint
# 도메인에 대한 ip 확인
APIDNS=$(aws eks describe-cluster --name $CLUSTER_NAME | jq -r .cluster.endpoint | cut -d '/' -f 3)
dig +short $APIDNS
다양한 방법으로 현재 클러스터 api 서버의 도메인 주소를 파악할 수 있다.
해당 도메인을 통해 받게 되는 ip 주소를 확인해보면 퍼블릭 ip가 들어오는 것이 보인다.
내 api 서버로의 통신을 위한 도메인, 이 도메인에 대한 ip는 3.36.162.138, 3.34.117.193
이다.
잠시 Amazon Route53에서 보면 vpc 관련 설정이 들어간 것을 확인할 수 있다.
kube-system 네임스페이스에는 온프레미스 클러스터와는 다르게 정적 파드에 대한 정보가 하나도 없다.
aws에서 관리하는 만큼, 아예 접근조차 할 수 없도록 만들어둔 듯하다.
아예 정적 파드 기능 자체가 비활성화돼있다고 들었는데, 과연 실제로도 사용하지 않는 모습을 보이는 듯하다.
Metric API를 기본으로 지원해주고 있고, aws-node라 적힌 것은 뜯어보니 aws-cni였다.
k ns kube-system
k get node --show-labels -o wide
노드에는 어떤 라벨이 있고 어떤 정보가 들어있는가?
음.. 라벨이 어무막지하게 많이 붙어있는데, 나중에 활용할 구석을 찾을 수 있을 것 같다.
노드 접속 후 정보 확인
kubectl get node -o jsonpath='{.items}' | jq -r '.[] | .status.addresses[] | select( .type == "ExternalIP").address'
워커 노드들의 공인 ip를 꺼내본다.
처음 클러스터를 구성할 때 ssh에 대한 설정을 했기 때문에 별다른 설정 없이도 현재 호스트에서 ssh 접속이 가능하다.
인터페이스 정보를 보면, 콘솔에서 확인했던 eni 관련 값들이 보인다.
중간에 eni뭐라 붙은 녀석들은 aws cni가 관리하는 인터페이스로 보인다.
근데 저 7번 인터페이스는 콘솔에서 볼 때도 궁금했는데, 대체 왜 있는지 아직 잘 모르겠다.
추가적인 실습을 하다가 알게 되면 이 부분은 다시 정리하겠다.
라우팅 테이블도 한번 보자.
차주에 배우겠지만, cni가 완벽히 vpc의 ip 대역을 활용하고 있는 것을 알 수 있다.
달리 말해 각종 컴포넌트나 파드에 그냥 노드에서 접근하는 것이 가능하다는 것.
containerd를 기본 oci로 활용하고 있다.
처음에 설정했던 스토리지 20기비바이트 짜리가 붙어있는 것이 보인다.
컨트롤 플레인과의 통신 분석
이제 본격적으로 어떻게 컨트롤 플레인과 연결돼있는지 추적해보자.
ss -tnp
현재 열려 있는 프로세스들이 어떤 소켓으로, 어떤 주소와 연결되고 있는지 확인한다.
kubelet, kube-proxy가 퍼블릭 ip로 어딘가에 연결되고 있는 것이 보인다!
이 ip는 위에서도 봤지만, api 서버로 통신하는 퍼블릭 ip이다.
즉, 클러스터 바깥으로 트래픽이 나갔다 다시 들어오고 있다는 뜻이다..
ipv6와 매핑된 ipv4는 노드의 kubelet가 노출하고 있는 10250포트로 연결되고 있는 소켓이다.
peer address를 보면 프라이빗 ip가 있는데, 이 친구는 뭘까?
cni에서 연결해주는 어떤 인터페이스로 빠져나가는 것을 볼 수 있다.
iptables -t nat -L | egrep -A5 -B5 114
이러고 싶지 않았지만.. cni가 관리하는 인터페이스로 빠져나가는 이상 iptables 규칙도 확인해봐야한다.
정답은 메트릭 서버 파드의 ip였답니다!
apiVersion: v1
kind: Pod
metadata:
name: test
namespace: default
spec:
containers:
- image: nicolaka/netshoot
name: test
command:
- sh
- -c
- "sleep infinity"
restartPolicy: Always
nodeName: ip-192-168-1-70.ap-northeast-2.compute.internal
디버깅용 파드를 하나 만들어서 조금 더 분석을 해보자.
현재 접속한 노드에서 정보를 확인하기 위해 아예 노드 이름을 지정해버렸다.
k exec -ti test -- zsh
exec 명령에 대해서는 api서버가 웹소켓으로 클라이언트를 노드에 연결해주므로, 소켓 정보를 추가적으로 확인할 수 있다.
그러자 내부 ip로 연결된 소켓이 보이는데, 이 ip는 무엇일까?
한 워커 노드에서 다른 워커 노드로도 접속해보았다.
파드가 띄워진 노드에서 똑같은 ip로 열린 소켓 정보가 하나 더 보인다.
eni를 다시 찾아보니, 해당 ip는 컨트롤 플레인의 eni가 가지고 있다.
클러스터 액세스 엔드포인트가 어떤 모드이던 간에, 컨트롤 플레인에서 워커 노드로 보내는 트래픽은 eni를 타고 간다는 것을 확인할 수 있다.
엔드포인트 액세스 모드 변경
마지막으로 해볼 것은 api 서버로의 통신 경로 변경이다.
그냥 만든 eks는 기본적으로 api 서버에 대한 접근이 퍼블릭으로 설정돼있다.
그래서 워커 노드의 kube-proxy, kubelet이 전부 외부로 나갔다가 들어오는 것을 확인할 수 있었다.
혼용(퍼블릭, 프라이빗) 모드
이것을 퍼블릭, 프라이빗 모드로 수정해본다.
실제로 완료되는 데에는 대충 10분 가량이 걸린다.
while true; do dig +short 74EA06D41A0AECDFF9DFA2A30A8854A3.gr7.ap-northeast-2.eks.amazonaws.com ; echo "------------------------------" ; date; sleep 3; done
그동안 워커 노드에서는 어떻게 dns가 변경되는지 확인할 수 있게 한무 루프를 만들었다.
어느 순간부터 dns 조회가 안 되는 것이 보인다.
총 9분을 기다리다보니.. 드디어 프라이빗 ip가 뜨는 것이 확인된다!
위에서 보았던, eni의 ip이다.
그러나 막상 보면 아직 컴포넌트들은 퍼블릭 ip로 통신하고 있다!
이건 컴포넌트들이 이전에 통신한 내용을 바탕으로 계속 통신을 하고 있기 때문에 그렇다.
귀찮게도, 이렇게 클러스터 엔드포인트 모드를 수정하더라도 제대로 반영하기 위해서는 직접 각 컴포넌트들을 재시작해줘야만 한다;
systemctl restart kubelet
직접 재시작을 해주면..
드디어 통신이 내부로 이어지게 된다.
데몬셋으로 배포된 kube-proxy는 rollout restart
를 해주면 반영될 것이다.
참고로 내 로컬에서는 그대로 똑같이 퍼블릭 ip가 표기되는데, 이것은 스플릿 호라이즌 dns 기능을 이용했기 때문이다.
프라이빗 모드
마지막으로 프라이빗 모드로 변경해본다.
이제부터는 내 로컬에서는 아예 접근하는 것이 불가능해진다.
업데이트가 완료된 후, 내 로컬에서 dns 조회를 해보니 이렇게 내부 ip만 리턴된다.
외부에서는 이 ip를 가지고 할 수 있는 게 당연히 아무것도 없다..
이제부터 외부에서 클러스터를 사용하고 싶다면 바스티온 호스트를 하나 파던가 해야 한다.
ssh -L 2222:74EA06D41A0AECDFF9DFA2A30A8854A3.gr7.ap-northeast-2.eks.amazonaws.com:443 ec2-user@$N1 -i ../pki/pub.pem
나는 로컬 SSH 터널링을 쓰기로 마음 먹었다.
내 로컬에는 이제 listen 상태의 2222번 tcp 소켓이 있다.
그럼 내 로컬에서 이렇게 통신을 할 수 있게 된다.
실습 자원 삭제
eksctl delete cluster --name aews-eks
aws cloudformation delete-stack --stack-name myeks
모든 것을 마친 후에는 반드시 정리!를 해야 하는데, 프라이빗 모드로 변경한 이후에는 로컬에서 eksctl을 통한 삭제가 되지 않는다.
그래서 직접 콘솔에서 삭제를 하던가, 모드를 변경하던가 해야 한다.
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - EKS 설치 및 액세스 엔드포인트 변경 실습 | 1 | published | 2025-02-03 |
2W - 테라폼으로 환경 구성 및 VPC 연결 | 2 | published | 2025-02-11 |
2W - EKS VPC CNI 분석 | 3 | published | 2025-02-11 |
2W - ALB Controller, External DNS | 4 | published | 2025-02-15 |
3W - kubestr과 EBS CSI 드라이버 | 5 | published | 2025-02-21 |
3W - EFS 드라이버, 인스턴스 스토어 활용 | 6 | published | 2025-02-22 |
4W - 번외 AL2023 노드 초기화 커스텀 | 7 | published | 2025-02-25 |
4W - EKS 모니터링과 관측 가능성 | 8 | published | 2025-02-28 |
4W - 프로메테우스 스택을 통한 EKS 모니터링 | 9 | published | 2025-02-28 |
5W - HPA, KEDA를 활용한 파드 오토스케일링 | 10 | published | 2025-03-07 |
5W - Karpenter를 활용한 클러스터 오토스케일링 | 11 | published | 2025-03-07 |
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 | 12 | published | 2025-03-15 |
6W - api 구조와 보안 1 - 인증 | 13 | published | 2025-03-15 |
6W - api 보안 2 - 인가, 어드미션 제어 | 14 | published | 2025-03-16 |
6W - EKS 파드에서 AWS 리소스 접근 제어 | 15 | published | 2025-03-16 |
6W - EKS api 서버 접근 보안 | 16 | published | 2025-03-16 |
7W - 쿠버네티스의 스케줄링, 커스텀 스케줄러 설정 | 17 | published | 2025-03-22 |
7W - EKS Fargate | 18 | published | 2025-03-22 |
7W - EKS Automode | 19 | published | 2025-03-22 |
8W - 아르고 워크플로우 | 20 | published | 2025-03-30 |
8W - 아르고 롤아웃 | 21 | published | 2025-03-30 |
8W - 아르고 CD | 22 | published | 2025-03-30 |
8W - CICD | 23 | published | 2025-03-30 |
9W - EKS 업그레이드 | 24 | published | 2025-04-02 |
10W - Vault를 활용한 CICD 보안 | 25 | published | 2025-04-16 |
11W - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-04-18 |
11주차 - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-05-11 |
12W - VPC Lattice 기반 gateway api | 27 | published | 2025-04-27 |
관련 문서
이름 | noteType | created |
---|---|---|
EKS, ECS 비교 | knowledge | 2024-11-03 |
Amazon Fargate | knowledge | 2024-11-03 |
Amazon Elastic Kubernetes Service | knowledge | 2025-02-01 |
eksctl | knowledge | 2025-02-06 |
Karpenter | knowledge | 2025-03-04 |
EKS Automode | knowledge | 2025-04-23 |
E-파드 마운팅 recursiveReadOnly | topic/explain | 2025-02-27 |
S-vpc 설정이 eks 액세스 엔드포인트에 미치는 영향 | topic/shooting | 2025-02-07 |
T-마운트 전파 Bidirectioal | topic/temp | 2025-02-28 |
참고
https://docs.aws.amazon.com/ko_kr/vpc/latest/privatelink/what-is-privatelink.html ↩︎
https://docs.aws.amazon.com/eks/latest/userguide/sec-group-reqs.html ↩︎
https://github.com/terraform-aws-modules/terraform-aws-eks/blob/master/docs/network_connectivity.md ↩︎
https://github.com/eksctl-io/eksctl/blob/main/examples/01-simple-cluster.yaml ↩︎