EKS 스터디를 진행하기 이전에 먼저 Native K8s와 EKS의 차이점에 대해서 알아보도록 하겠다. 여러가지 차이점이 있겠지만 이번 포스팅에서는 아래 3가지에 초점을 맞춰서 작성해보도록 한다.
컨트롤 플레인을 AWS가 관리하는점
VPC CNI를 사용하는점
AWS IAM을 통한 IRSA 인증/인가 방식을 사용한다는 점
✅ Amazon Managed 라는 의미는 무엇을 뜻할까?
우선 kubernetes는 기능에 의해서 두가지로 나뉜다.
컨트롤 플레인
ETCD
API server
컨트롤러, 스케쥴러
데이터 플레인
EKS owned ENI
노드 그룹
kubelet
kube-proxy
✅ Control 플레인
쿠버네티스를 제어 하기 위한 컴포넌트
AWS가 관리한다
고가용성을 고려한 설계가 되어있다. ( ELB, NLB, AZ, AutoScalingGroup )
API 서버, 컨트롤러, 스케쥴러, ETCD 등의 컴포넌트가 있다.
생성되는 VPC는 AWS에서 관리하는 Account가 관리하는 VPC 입니다.
가용성 보장을 위해 가용영역을 나누고 etcd만 별개의 인스턴스로 분리하여 구성합니다.
오토스케일링 그룹으로 각 컴포넌트들을 묶어 다수의 자원들이 안정적으로 유지됩니다.
또한 각 가용영역 별로 트래픽 분산을 하는 환경을 구성하기 위해 Amazon ELB와 Amazon NLB를 사용합니다.
✅ Data 플레인
워커노드를 구성하기 위한 데이터 플레인 컴포넌트
사용자 VPC에 생성됨 ( 사용자 어카운트에 배치 되기 때문에 사용자가 직접적으로 관리 및 운영)
POD, kubelet , Kube-proxy , Container-Runtime 등
노드 구성 방식들이 다양함
관리형
자체형 : 사용자 정의 AMI를 사용해서 구성 , 유지관리 및 버전관리를 직접 수행
AWS Fargate : 컨테이너 형태의 구조가 아닌 마이크로 VM 형태의 가상머신으로 이루어짐
EKS Owned ENI
해당 네트워크 인터페이스는 소유자와 대상자가 서로 다른 어카운트로 이루어짐
== 크로스 어카운트 ENI
해당 인터페이스를 통해 데이터 플레인과 컨트롤 플레인이 통신
✅ Amazon VPC CNI 소개
K8s에서는 pod간의 통신을 확장하는 규약을 CNI(Container Network Interface)로 정의합니다.
EKS는 여러가지 이유로 AWS VPC CNI를 전용으로 사용합니다.
Overlay 네트워크와 Underlay 네트워크의구성이달라관리가 어려운 일반적인 k8s 환경과 달리 관리가 수월합니다.
VPC가 제공하는 VPC Flow log나 라우팅 정책, 보안 그룹 등에 대한 기능도 함께 사용 가능합니다
일반적인 바닐라 쿠버네티스노드의 ip 대역은 10.20.2.0/24 대역이고, POD의 대역은 10.233.x.x 대역임을 확인할수 있습니다.
EKS는 Node와 Pod가 동일한 네트워크를 가집니다. 노드의 대역과 POD의 대역이 일치함을 확인 할수 있음.
그렇다면? POD가 실제적인 인터페이스를 가지고 있는것인가?
아뇨 그렇지는 않습니다. 하나의 인터페이스에 SecondaryIP를 보조 아이피로 여러개 가지고 있는것이죠
✅ 일반적인 k8s는 Calico와 같은 CNI를 사용하며 오버레이 네트워크를 사용함 ✅ EKS는 VPC CNI를 사용하며 오버레이 네트워크가 아닌 노드와 동일 대역으로 직접 통신함
VPC CNI 장점: 컴퓨팅 리소스의 사용이 줄고, 네트워크의 지연이 감소하며, 높은 대역폭을 제공할 수 있습니다.
✅ 노드당 몇개의 POD를 배포할수 있을까?
✔️ 기본적으로 노드그룹에 인스턴스 타입에 따라 노드당 배포할수 있는 IP가 정해진다고 보면 된다.
✔️ EC2 인스턴스별로 최대 ENI 수량이 정해져 있으며, ENI별로는 최대 IP 수량이 정해져 있기 때문에, 제한되는것임
예를 들어 m5.large 정도의 인스턴스를 사용한다면 ENI 당 10개의 Pod를 배포할수 있고 m5.large EC2인스턴스는 3개의 ENI를 붙일수 있으니 30개가 되겠다. 그리고 노드의 Primary IP와 AWS CNI(aws-node) Kube-proxy등 반드시 필요한 아이피를 제외하여 사용자가 서비스로 사용할수 있는 POD는 27개다.
✔️ IPv4 Prefix 위임과 같은 방법도 있음
Prefix 기능 은 작년 7월 발표된 기능으로 Nitro 인스턴스에서만 확장이 가능한 방법입니다.
✔️ 서브넷 크기를 크게 잡자 처음부터..
EC2 Node가 포함된 subnet의 크기가 작다면, 역시 IP 할당이 원할하지 않을 것이기 때문
24bit subnet 보다는 더 많은 IP를 사용할 수 있는 22bit subnet 등으로 올려서 사용하는 것이 좋습니다.
✅ POD간 내부 통신
✔️ 다른 워커노드에 통신하는 것을 보기 위해서 다음과 같이 netshoot을 배포하여 tcpdump로 패킷을 잡아 분석해보자.
✔️ 다른 워커노드에 통신하는 것을 다음 그림을 통해 step by step으로 이해해 보자.
pod 1에 접속해서 그림을 이해해보자.
$ k exec -it pod-1 /bin/sh
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
$ arp -n
? (169.254.1.1) at ca:ab:00:c7:b4:12 [ether] PERM on eth0
$ ping 192.168.3.197
64 bytes from 192.168.3.197: icmp_seq=1 ttl=125 time=1.33 ms
64 bytes from 192.168.3.197: icmp_seq=2 ttl=125 time=1.06 ms
64 bytes from 192.168.3.197: icmp_seq=3 ttl=125 time=1.11 ms
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 169.254.1.1 0.0.0.0 UG 0 0 0 eth0
169.254.1.1 0.0.0.0 255.255.255.255 UH 0 0 0 eth0
$ arp -n
? (192.168.1.203) at ca:ab:00:c7:b4:12 [ether] on eth0
? (169.254.1.1) at ca:ab:00:c7:b4:12 [ether] PERM on eth0
pod 안에는 default gateway말고는 아무런 라우팅 정보가 없다.
첫 통신 전에는 arp table에도 default gateway의 MAC정보 이외에는 아무런 정보가 없다.
즉 다시말하면, 아직 본인이 어떤 pod 나 또는 어떤 인터페이스와 통신할지 모르더라도 default gateway로 무조건 요청을 보낸다는의미이다.
하지만 통신 하고 난뒤에는 arp table에는 pod1이 위치한 노드 node1의 eni 인터페이스의 mac주소가 학습되었다.
물론 해당 인터페이스는 가상의 인터페이스 (veth-1) 이며 User space가 아닌 커널 space에 존재하며 ip-link 로 연결되어 있다.
next hop은 해당 노드의 eth0 인터페이스임
✔️ Pod는 노드의 가상 인터페이스의 mac주소만 가지고 어떻게 통신을 하지?
인터페이스 없이 어떻게 통신하지? (arp-proxy를 통한 arp 퍼블리싱)
ARP Proxy라는 기능은 라우터 자신이 알고있는 대역에 대한 ARP라면 응답으로 라우터 자신의 MAC 주소를 알려준다. 또한 목적지 대상은 라우터가 받은 패킷을 받는다.
여기서 라우터는 가상 인터페이스이며, 통신하고자 하는 목적지는 노드의 인터페이스이기 때문에, 다음과 같은 통신이 인터페이스 없이 arp 퍼블리싱을 통해 동작한다.
$ arp -n
? (192.168.1.203) at ca:ab:00:c7:b4:12 [ether] on eth0
? (169.254.1.1) at ca:ab:00:c7:b4:12 [ether] PERM on eth0
192.168.1.203은 pod1이 위치한 node1의 IP주소이다. 하지만 ARP-proxy 기능이 동작했기 때문에
라우터가 자신의 MAC 주소를 적어놨다.
✅ EKS의 인증,인가
EKS의 인증과 인가는 어떻게 이루어져 있을까?
☑️ 인증 - AWS IAM을 통해서 지원
☑️ 인가 - Kubernetes RBAC 기능을 통해서 지원
✅ kubectl 인증 과정 뜯어보기
☑️ kubectl 명령어 실행
- kubeconfig 확인 해볼것
- aws eks get-token → EKS Service endpoint(STS)에 토큰 요청 ⇒ 응답값 token과 유효기간 포함
파드는 SA 토큰을 Volume에 주입받는데 거기에 Mutation Webhook 기능이 이용됨
쿠버네티스에 SA를 만들고, SA에 annotation을 통해서 IAM ARN을 적어주고, Trust Policy를 만들어주는것이 필요하다.
# Create an iamserviceaccount - AWS IAM role bound to a Kubernetes service account
eksctl create iamserviceaccount \
--name my-sa \
--namespace default \
--cluster $CLUSTER_NAME \
--approve \
--attach-policy-arn $(aws iam list-policies --query 'Policies[?PolicyName==`AmazonS3ReadOnlyAccess`].Arn' --output text)
# 확인 >> 웹 관리 콘솔에서 CloudFormation Stack >> IAM Role 확인
# aws-load-balancer-controller IRSA는 어떤 동작을 수행할 것 인지 생각해보자!
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
# Inspecting the newly created Kubernetes Service Account, we can see the role we want it to assume in our pod.
kubectl get sa
kubectl describe sa my-sa
###
...
Annotations: eks.amazonaws.com/role-arn: arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1MJUYW59O6QGH
...
파드 생성 하고 확인
위에서 irsa로 s3 정책을 Assume 했고 pod를 생성할때 service Account의 이름만 적어주면 자동으로 mutation Webhook 기능을 통해서 pod의 볼륨에 sa가 탑재되고 ENV값이 생성된다.
# 파드3번 생성
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: eks-iam-test3
spec:
serviceAccountName: my-sa
containers:
- name: my-aws-cli
image: amazon/aws-cli:latest
command: ['sleep', '36000']
restartPolicy: Never
EOF
kubectl describe pod eks-iam-test3
...
Environment:
AWS_STS_REGIONAL_ENDPOINTS: regional
AWS_DEFAULT_REGION: ap-northeast-2
AWS_REGION: ap-northeast-2
AWS_ROLE_ARN: arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-GE2DZKJYWCEN
AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token
Mounts:
/var/run/secrets/eks.amazonaws.com/serviceaccount from aws-iam-token (ro)
/var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-69rh8 (ro)
...
Volumes:
aws-iam-token:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 86400
kube-api-access-sn467:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
...
# 되는 것고 안되는 것은 왜그런가?
kubectl exec -it eks-iam-test3 -- aws s3 ls
kubectl exec -it eks-iam-test3 -- aws ec2 describe-instances --region ap-northeast-2
kubectl exec -it eks-iam-test3 -- aws ec2 describe-vpcs --region ap-northeast-2
IAM_TOKEN과 ROLE_ARN을 탈취할 경우 Assume Token을 발급받아서 악용이 가능하다.
# AWS_WEB_IDENTITY_TOKEN_FILE 토큰 값 변수 지정
IAM_TOKEN=$(kubectl exec -it eks-iam-test3 -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
echo $IAM_TOKEN
# ROLE ARN 확인 후 변수 직접 지정
eksctl get iamserviceaccount --cluster $CLUSTER_NAME
ROLE_ARN=<각자 자신의 ROLE ARN>
ROLE_ARN=arn:aws:iam::911283464785:role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1W8J3Q0GAMA6U
# assume-role-with-web-identity STS 임시자격증명 발급 요청
aws sts assume-role-with-web-identity --role-arn $ROLE_ARN --role-session-name mykey --web-identity-token $IAM_TOKEN | jq
{
"Credentials": {
"AccessKeyId": "ASIA5ILF2FJIZLOCB36X",
"SecretAccessKey": "IvuD2BEt/TtScyv6uq3U5mF3RStuxya5gHydlz2Z",
"SessionToken": "IQoJb3JpZ2luX2VjELH//////////wEaDmFwLW5vcnRoZWFzdC0yIkYwRAIgFsxs4rNyxuWTgqIuQWONuU8lkb+S1E9rvY4YLMtAR0ACIAfRrLYpisS1Ql+2agL0meQ+iy08bLv992tTCr0vZkqVKvkECOr//////////wEQARoMOTExMjgzNDY0Nzg1IgwmJn8sjNaBM0F+L/gqzQSnX6M6BlgzqiX3Sob0R8QZo0TEumVqCsLopwdHBzIZL6VU3kFaeqIpUh9uuZ+JaR7MlFKS7FYhIq7r+fMh5f8toWojtyKwLjT9eN2yi5A5ZfWahln1MIu9fv/dASR4USMxLtbMHOGpx3BE/pCHhV+u85z/LoHSlVNaF5IrQiCXbo3f9DrJ0kHQZuQY3N0pFDlGzXuv5hCedGlJQU2IzUcmW5kHQ/jNyIf+xEO2nTSksna5iE3r9TNnO6b6v8gZc5zDUs3fGfJfP4QwKjRXOUDMnydJ9LzME+mYoYHObdeCqncGuGwJ3GIXx9qw9ZABXuAlvATuLROaYkeLsXuote1UOqPILxvETvWo1VHA2f0hYL9ZFDSF3j6yGU+GEHbFGoMRVaVFqdbpF1bMEbC9FlmR5AWbAkkYZs8kfXRcObfpZxLB4vQBXeqj9OU/yDPvvNA+CgOoA5HFI0SjeFQHOVB7S5KVm6CAOKtoMIzTeKnKpKmN07dqJzvGrpwtNMh/GhCouunvbgNG7jF/TM3jgAniDxoD9IzCQMICNgxdioOFnB6Oe1AzMkKui53MP8Af/lcDiUKTIUNrKxdm0719kuXqR88coihzrLGkdA7Eb5Gg/gCnk+SzPu7Wu5xZaYXBe6Xqh0/c1dKsN1YQLOANo5aF3B2RCGJFDwr78rOUvNWxXs84us/Uz5k6LIGZseVzZcGh5U9ztJqhzoKFvnphbtU8b1Ctg/pTrF8EnjLGR0s4QggdrsW1b7vznisMwFrHh0F+FhSy7ldvfeXmwpQgMNP666MGOpsBmNam9fm/qK6EjmllDDvf6mR9l99Vop++V2vf1GoM4ru8/TfgP25+B1N9gEbnRuhMTxQrN6VGcyaNNlKBkwxtAs+aikBcvjk3cm0jPZmiQntTkNtBw92NAJwbRmhSIQynznxN7I1FnFukP06J9V9MiuhsJGXpGXi0kWOOQnqb9u2YraRLKJdTqVfv5dGt7aM67PeJrr/0v2YKU0M=",
"Expiration": "2023-06-03T09:44:03+00:00"
},
"SubjectFromWebIdentityToken": "system:serviceaccount:default:my-sa",
"AssumedRoleUser": {
"AssumedRoleId": "AROA5ILF2FJI7UWTLJWKW:mykey",
"Arn": "arn:aws:sts::911283464785:assumed-role/eksctl-myeks-addon-iamserviceaccount-default-Role1-1W8J3Q0GAMA6U/mykey"
},
"Provider": "arn:aws:iam::911283464785:oidc-provider/oidc.eks.ap-northeast-2.amazonaws.com/id/8883A42CB049E2FA9B642086E7021450",
"Audience": "sts.amazonaws.com"
}