인프라/AWS

EKS Networking

스루나루 2024. 3. 16. 19:54
728x90
728x90

 

해당 포스팅은 가시다님의 AWES 2기 스터디 내용을 토대로 작성했습니다. 

 

네트워크 구성도 

 

 

 변수 세팅 

# 노드 IP 확인 및 PrivateIP 변수 지정
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 "export N1=$N1" >> /etc/profile
echo "export N2=$N2" >> /etc/profile
echo "export N3=$N3" >> /etc/profile
echo $N1, $N2, $N3

# 노드 보안그룹에 eksctl-host 에서 노드(파드)에 접속 가능하게 룰(Rule) 추가 설정
NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
echo "export NGSGID=$NGSGID" >> /etc/profile
aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32

# 워커 노드 SSH 접속 : '-i ~/.ssh/id_rsa' 생략 가능
for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i hostname; echo; done
yes
yes
yes

for i in $N1 $N2 $N3; do echo ">> node $i <<"; ssh ec2-user@$i hostname; echo; done

 

 

AWS VCP CNI 특징 

 

 AWS VPC CNI

 - 파드의 아이피를 할당해주고 네트워크를 세팅 

 - AWS VPC CNI는 AWS환경에 특화된 녀석으로 각 AWS 서비스와 유기적으로 연결되어 쿠버네티스의 네트워크 환경을 세팅해 줌 

 - 파드와 노드의 IP대역이 같아서 직접 통신이 가능 

 

기존 네트워킹 방식과 AWS VPC CNI와 다른점 

1) 온프레미스
 - 보통 온프레미스에서 쿠버네티스 환경을 세팅하면 노드와 파드의 IP대역이 다르다 
 - 파드와 노드의 IP대역이 다르니까 통신을 위해서 패킷에 오버레이( VXLAN  , IP-IP 등 ) 통신을 한다.  
   간단히 말하면 파드1이 파드2에 통신하고 싶은데 중간에 노드가 거쳐있음 이 노드와 파드 대역이 다르니까 통신을 위해서 패킷을 캡슐화하여 통신할 수 있는 세팅으로 맞춰준 후 보내는게 지금까지 온프레미스에 주로 사용했던 네트워크 방식이다. 
 - Ex) Calico CNI  

 2) AWS VPC CNI
 - 파드와 노드의 아이피 대역이 같으니까 터닐링 이런 거 없이 바로 직접 통신 가능 
 - 별도 처리과정 없으니까 빠르고 캡슐화를 안 하니 패킷 사이즈도 추가되지 않음 

 

 

 

AWS VPC CNI가 직접 통신을 할 수 있음

 

 

 

 노드와 파드의 아이피 대역이 같음을 확인할 수 있다. 

 

 


노드의 네트워크 정보 확인 

 

 명령어로 각 노드에 네트워크를 확인해 보면 각 노드당 랜카드가 1개 혹은 2개씩 있는 걸 확인할 수 있다. AWS 콘솔에서 확인해보면 각 랜카드당 보조 IP가 5개씩 할당되어 랜카드가 1개면 아이피 5개 , 랜카드가 2개면 아이피를 10개 가질 수 있음을 확인할 수 있다. 

 

 

 랜카드가 2개인 경우 보조 프라이빗 IP주소가 총 10개가 할당되어 있다. 

 

 랜카드가 1개인 경우 보조 프라이빗 주소가 5개가 할당되어 있다. 

 

 

네트워크 네임스페이스 

 

워커 노드 구성도

 

 - 네트워크의 네임스페이스는 호스트 ( Root )와 각각의 파드별( Per Pod )로 구분되어진다. 

 - 특정한 파드 (kube-proxy , aws-node ) 는 아래 그림처럼 네트워크 대역이 호스트 즉 노드의 IP를 그대로 사용한다. ( 파드의 Host Network 옵션 ) - 포트만 구분해서 쓰면 되니까! 같은 아이피써도 오케이 

 - Core DNS관련 파드와는 별도의 네트워크 네임스페이스를 사용하기 때문에 IP가 다르게 할당된다. 

 

 호스트 네트워크 
- 컨테이너의 네트워킹 방식중 하나로 호스트의 네트워크를 직접 사용하는 모드 
 - 컨테이너가 호스트의 ip주소와 포트를 직접 사용하니까 별도의 네트워크 격리가 필요없음

 

 

 먼저 각 노드의 라우팅 정보와 Core dns파드의 IP주소를 확인하자

 core dns의 ip주소가 core dns 파드가 돌아가는 각 워커노드2,3의 라우팅 테이블에 존재함을 확인

 라우팅 테이블을 보면 core dns의 아이피로 가려면 해당 eni~~ 인터페이스로 가라고 설정되어 있다. 

 

 기억상 보통 같은 아이피 대역이면 192.168.2.0 이렇게만 넣어도 통신이 될텐데 왜 구체적인 라우팅 경로가 추가가 되어 있을까?

 -  밑에서 확인하면 아무래도 네임스페이스마다 논리적으로 네트워크가 격리되어 있기 때문에 좀 더 구체적인 네트워크 주소로 경로를 지정해줘야 통신이 되는 거 같다. 또한 파드별 네트워크 네임스페이스가 만들어지고 격리되는 듯 하다 

 

 

 하나의 파드를 생성한 후 해당 파드의 아이피 대역을 확인 : 192.168.2.143

 

 그리고 해당하는 파드의 라우팅 테이블을 확인 

확인해보면 노드2에 있는 파드의 아이피 주소와 파드 내에 라우팅 테이블의 주소와 페어를 이루고 있음을 확인할 수 있다. 

 

 

네트워크 네임스페이스가 격리가 되어 있어서 호스트 네트워크로 쓰는게 아니라 격리된 환경에서 별도 인터페이스를 통해서 통신됨을 확인할 수 있다. 

 

 또한 위에서 사용된 파드의 아이피 주소는 보조 프라이빗 IP주소 풀에서 할당된 아이피 주소이다

 인터페이스 하나당 5개의 아이피 주소를 가질 수 있다. 

 

 

 

 

노드 간 파드 통신 

 

 파드 간 통신이 진행될 때 별도의 오버레이 기술 없이 직접 통신이 가능하다 

 원본 패킷이 그대로 통신된다. 

 

 

https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/cni-proposal.md

 

 

 

파드에서 외부 통신 

 

 파드가 외부와 통신할 때는 SNAT을 통해서 각 파드가 존재하는 워커노드의 이더넷0의 IP로 변경되어서 외부와 통신을 한다. 

 파드와 워커노드 모두 외부와 통신시 각 워커노드의 공인 ip로 통신하게 됨 

 

 

 

 워커노드에서 tcpdump결과 

 

 

 

 

노드 파드 생성 갯수 제한 

 

 현재 노드가 t3.medium이니까 네트워크 인터페이스를 총 3개 만들 수 있고 각 인터페이스당 6개씩 ip할당 가능하다고 나오지만 아래의 식으로 계산하면 총 15개 할당 가능 

 

 # 파드 사용 가능 계산 예시

aws-node 와 kube-proxy 파드는 host-networking 사용으로 IP 2개 남음

((MaxENI * (IPv4addr-1)) + 2)
t3.medium 경우 : ((3 * (6 - 1) + 2 ) = 17개 >>  aws-node 와 kube-proxy 2개 제외하면  15개

 

 

 현재 워커노드에는 총 17개의 파드를 할당할 수 있다 

 

 nginx 파드를 배포한 후 파드의 수를 30개로 늘리면 어떻게 될까?

 하나의 워커노드당 3개의 인터페이스 할당 할 수 있고 총 파드 수는 15개까지 감당가능하니까 30개는 문제없이 배포가 가능

 또한 각 노드당 못해도 10개씩 파드를 가져야하니까 네트워크 인터페이스가 증가됨을 확인할 수 있다. 

 

 

 

 워커노드의 스펙을 초과하는 50개로 배포를 하면 어떻게 될까? 

 - 초과된 수 의 파드는 배포되자 않고 pending 상태가 되어버린다. 

 

 

 파드를 삭제하면 증가했던 노드의 네트워크 인터페이스가 사라지게 된다. 

 

 


Service 

 

 파드는 언제든 휘발될 수 있으니까 파드의 IP를 타겟하는 것은 바람직하지 않다. 

 이럴 때 서비스를 사용하여 고정 진입점을 확보 ( IP 혹은 도메인 주소) 

 부하 분산할 때 자주 사용한다 = 거의 로드 밸런서 

 

  1) Cluster IP 

   - 기본 서비스 타입으로 클러스터 내부에서만 접근할 수 있는 내부 IP를 서비스에 할당 

   - 외부에서는 접근 불가 

 

 2) NodePort

 - 클러스터 내의 모든 노드에 동일한 포트를 할당하고 해당 포트로 파드에 접근하도록 설정 

 - 외부 사용자가 클러스터 내부 접근할 때 고정된 포트로 노드에 직접 접근하여 파드 접근 

 

 3) LoadBalancer : 기본모드 // NLB 인스턴스 유형 

  - 외부 로드밸런서를 사용하여 외부IP를 할당 

  - 외부 인터넷 트래픽 분산을 위해 사용 

 

 4) Service ( LoadBalancer Controller ) : AWS LoadBalancer Controller + NLB IP모드 동작 with AWS VPC CNI 

  - 별도 처리없이 AWS VPC CNI를 참조해서 바로 파드에 접근 

 

 

 

 

AWS LoadBalancer Controller 배포 

 

# OIDC 확인
aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
aws iam list-open-id-connect-providers | jq

# IAM Policy (AWSLoadBalancerControllerIAMPolicy) 생성
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.5.4/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# 혹시 이미 IAM 정책이 있지만 예전 정책일 경우 아래 처럼 최신 업데이트 할 것
# aws iam update-policy ~~~

# 생성된 IAM Policy Arn 확인
aws iam list-policies --scope Local | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy | jq
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'

# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 >> 자동으로 매칭되는 IAM Role 을 CloudFormation 으로 생성됨!
# IAM 역할 생성. AWS Load Balancer Controller의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할의 이름으로 Kubernetes 서비스 계정에 주석을 답니다
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller --role-name AmazonEKSLoadBalancerControllerRole \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve

## IRSA 정보 확인
eksctl get iamserviceaccount --cluster $CLUSTER_NAME

## 서비스 어카운트 확인
kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh

# Helm Chart 설치
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

## 설치 확인 : aws-load-balancer-controller:v2.7.1
kubectl get crd
kubectl get deployment -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller
kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
  Service Account:  aws-load-balancer-controller
 
# 클러스터롤, 롤 확인
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role

 

 배포가 완료되면 아래의 디플로이먼트와 서비스를 생성 

 - 총 2개의 파드와 서비스를 생성 해당 서비스는 aws의 nlb를 지정   

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: deploy-echo
spec: 
  replicas: 2
  selector: 
    matchLabels: 
      app: deploy-websrv
  template: 
    metadata: 
      labels: 
        app: deploy-websrv
    spec: 
      terminationGracePeriodSeconds: 0
      containers: 
      - name: akos-websrv
        image: k8s.gcr.io/echoserver:1.5
        ports: 
        - containerPort: 8080
---
apiVersion: v1
kind: Service
metadata: 
  name: svc-nlb-ip-type
  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: 
  ports: 
    - port: 80
      targetPort: 8080
      protocol: TCP
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
  selector: 
    app: deploy-websrv

 

 

 

 

 로드 밸런서의 대상그룹에 보면 쿠버네티스에 있는 파드 주소가 할당되어 있다.  이것도 AWS VPC CNI덕분에 별도의 처리없이 바로 접근할 수 있다는 것이 메리트 

 

 

 

배포된 로드밸런서에 접근하면 파드의 화면이 출력된다. 

 

 더불어 부하분산까지 확인할 수 있다. 

 

 

 

Ingress 

 

 서비스는 4계층( TCP/UDP )에서 동작한다면 Ingress는 http/https 프로토콜에서만 부하분산을 해줌 L7

 

 

 

 

 

apiVersion: v1
kind: Namespace
metadata: 
  name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata: 
  namespace: game-2048
  name: deployment-2048
spec: 
  selector: 
    matchLabels: 
      app.kubernetes.io/name: app-2048
  replicas: 2
  template: 
    metadata: 
      labels: 
        app.kubernetes.io/name: app-2048
    spec: 
      containers: 
      - image: public.ecr.aws/l6m2t8p7/docker-2048:latest
        imagePullPolicy: Always
        name: app-2048
        ports: 
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata: 
  namespace: game-2048
  name: service-2048
spec: 
  ports: 
    - port: 80
      targetPort: 80
      protocol: TCP
  type: NodePort
  selector: 
    app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata: 
  namespace: game-2048
  name: ingress-2048
  annotations: 
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
spec: 
  ingressClassName: alb
  rules: 
    - http: 
        paths: 
        - path: /
          pathType: Prefix
          backend: 
            service: 
              name: service-2048
              port: 
                number: 80

 

배포한 파드의 아이피와 ALB대상 그룹의 아이피가 같은 것을 확인하자.

 

 

External DNS 

 

 위에서 Ingress에서 나오는 주소를 보면 좀 길고 확인하기도 힘든 주소로 되어 있었다. 

 이 접속 주소를 도메인이랑 매핑해주는 것이 External DNS

 즉 쿠버네티스에서 서비스나 인그레스 생성 시 도메인 설정하면 AWS의 Route53이랑 매핑되어 A레코드에 자동 등록 및 삭제를 해줌 

 

https://edgehog.blog/a-self-hosted-external-dns-resolver-for-kubernetes-111a27d6fc2c

 

 

 기존의 도메인 레코드 

 

 테트리스 파드 배포 및 External DNS 배포 후 도메인 레코드 변화 

 해당 A레코드와 TXT를 External DNS가 만듦 

 

# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
kubectl logs deploy/external-dns -n kube-system -f

# 테트리스 디플로이먼트 배포
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: tetris
  labels:
    app: tetris
spec:
  replicas: 1
  selector:
    matchLabels:
      app: tetris
  template:
    metadata:
      labels:
        app: tetris
    spec:
      containers:
      - name: tetris
        image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
  name: tetris
  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-cross-zone-load-balancing-enabled: "true"
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
    #service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
  selector:
    app: tetris
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  type: LoadBalancer
  loadBalancerClass: service.k8s.aws/nlb
EOF

# 배포 확인
kubectl get deploy,svc,ep tetris

# NLB에 ExternanDNS 로 도메인 연결
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$MyDomain"
while true; do aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq ; date ; echo ; sleep 1; done

# Route53에 A레코드 확인
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A']" | jq
aws route53 list-resource-record-sets --hosted-zone-id "${MyDnzHostedZoneId}" --query "ResourceRecordSets[?Type == 'A'].Name" | jq .[]

# 확인
dig +short tetris.$MyDomain @8.8.8.8
dig +short tetris.$MyDomain

# 도메인 체크
echo -e "My Domain Checker = https://www.whatsmydns.net/#A/tetris.$MyDomain"

# 웹 접속 주소 확인 및 접속
echo -e "Tetris Game URL = http://tetris.$MyDomain"

 

 

 

 Ingress보다 더 알기 쉬운 도메인 주소로 변환되어 파드에 쉽게 접근이 가능해짐 

 

 또 현재 도메인 서비스가 전세계 어디에 퍼져있는지 아래 사이트에서 확인이 가능함 

https://www.whatsmydns.net/

 

DNS Propagation Checker - Global DNS Checker Tool

Instant DNS Propagation Check. Global DNS Propagation Checker - Check DNS records around the world.

www.whatsmydns.net

 

 

 

Network Policies with VPC CNI 

 네트워크의 보안 그룹? 

 

https://aws.amazon.com/ko/blogs/containers/amazon-vpc-cni-now-supports-kubernetes-network-policies/

 

 

 서로 다른 네임스페이스의 파드가 각각 2개씩 존재하며 해당 파드들은 Demo App이라는 파드에 접근이 가능한 상태 

 

 

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata: 
  name: demo-app-deny-all
spec: 
  podSelector: 
    matchLabels: 
      app: demo-app
  policyTypes: 
  - Ingress

 

 위의 정책을 적용하니까 원래 client-one에서 DemoApp접근이 가능해졌는데 접근이 불가능해짐 

 

 

 다른 정책으로는 client-one이라는 라벨의 파드만 허용을 하고 나머지는 불가

 

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata: 
  name: demo-app-allow-samens-client-one
spec: 
  podSelector: 
    matchLabels: 
      app: demo-app
  ingress: 
  - from: 
      - podSelector: 
          matchLabels: 
            app: client-one

 

 client-two에서 demo-app 접근 불가 client-one에서는 접근 가능 

 

 another-ns 네임스페이스에 있는 파드에 접근을 허용 

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata: 
  name: demo-app-allow-another-ns
spec: 
  podSelector: 
    matchLabels: 
      app: demo-app
  ingress: 
  - from: 
      - namespaceSelector: 
          matchLabels: 
            kubernetes.io/metadata.name: another-ns

 

 

 

728x90
728x90