해당 포스팅은 가시다님의 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
- 파드와 노드의 아이피 대역이 같으니까 터닐링 이런 거 없이 바로 직접 통신 가능
- 별도 처리과정 없으니까 빠르고 캡슐화를 안 하니 패킷 사이즈도 추가되지 않음
노드와 파드의 아이피 대역이 같음을 확인할 수 있다.
노드의 네트워크 정보 확인
명령어로 각 노드에 네트워크를 확인해 보면 각 노드당 랜카드가 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개의 아이피 주소를 가질 수 있다.
노드 간 파드 통신
파드 간 통신이 진행될 때 별도의 오버레이 기술 없이 직접 통신이 가능하다
원본 패킷이 그대로 통신된다.
파드에서 외부 통신
파드가 외부와 통신할 때는 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레코드에 자동 등록 및 삭제를 해줌
기존의 도메인 레코드
테트리스 파드 배포 및 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보다 더 알기 쉬운 도메인 주소로 변환되어 파드에 쉽게 접근이 가능해짐
또 현재 도메인 서비스가 전세계 어디에 퍼져있는지 아래 사이트에서 확인이 가능함
Network Policies with VPC CNI
네트워크의 보안 그룹?
서로 다른 네임스페이스의 파드가 각각 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
'인프라 > AWS' 카테고리의 다른 글
EKS Observability (0) | 2024.03.31 |
---|---|
EKS Storage & NodeGroup (2) | 2024.03.23 |
Amazon EKS 설치 및 기본 사용 (0) | 2024.03.10 |
Route53 외부 도메인 연결 (0) | 2024.03.02 |
IAM AdministratorAccess 부여 (0) | 2024.03.02 |
하고 싶은 걸 하고 되고 싶은 사람이 되자!
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!