Amazon EKS의 네트워킹에서 Kubernetes의 네트워크와 인그레스 트래픽을 AWS에서 어떻게 구현했는지 살펴보겠습니다.
지난 포스트에서는 EKS Networking Part 1으로 VPC CNI 개요와 노드 환경 구성을 살펴보고, 파드 통신 과정을 살펴봤습니다. 이번 포스트는 EKS Networking Part2로 LoadBalancer 유형의 서비스의 구성과 Ingress에 대해서 확인해 보겠습니다.
그리고 이들의 구현이 AKS(Azure Kubernetes Service)에서는 어떻게 다른지 설명을 해보겠습니다.
목차
- 실습 환경
- Kubernetes의 네트워크 개요
- AWS의 LoadBalancer 유형
- EKS의 LoadBalancer 구현
- EKS의 LoadBalancer 실습
- AKS의 LoadBalancer 구현
- EKS의 Ingress 구현 및 실습
- AKS의 Ingress 구현
- 리소스 정리
1 . 실습 환경
실습 환경
EKS Networking Part1에서 사용한 EKS를 그대로 사용하겠습니다. 아래와 같은 구성입니다.
혹시나 동일한 테스트를 진행하고자 한다면, 지난 포스트 EKS Networking Part1(https://a-person.tistory.com/30)의 3. 실습 환경 배포
절을 참고 부탁드립니다.
클러스터 관련 정보는 아래와 같습니다. EKS 1.31.5 버전입니다.
$ kubectl cluster-info
Kubernetes control plane is running at https://04559E327E31C75E5F6A835B3E0B6AED.gr7.ap-northeast-2.eks.amazonaws.com
CoreDNS is running at https://04559E327E31C75E5F6A835B3E0B6AED.gr7.ap-northeast-2.eks.amazonaws.com/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy
To further debug and diagnose cluster problems, use 'kubectl cluster-info dump'.
$ eksctl get cluster
NAME REGION EKSCTL CREATED
myeks ap-northeast-2 True
$kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
NAME STATUS ROLES AGE VERSION INSTANCE-TYPE CAPACITYTYPE ZONE
ip-192-168-1-228.ap-northeast-2.compute.internal Ready <none> 116m v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2a
ip-192-168-2-91.ap-northeast-2.compute.internal Ready <none> 116m v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2b
ip-192-168-3-155.ap-northeast-2.compute.internal Ready <none> 116m v1.31.5-eks-5d632ec t3.medium ON_DEMAND ap-northeast-2c
2. Kubernetes의 네트워크 개요
Kubernetes의 네트워크에 관련된 주요 컴포넌트와 오브젝트로 CNI, Service, Ingress 등이 있는데, 이들은 각각 어떤 역할을 하는 걸까요? 이러한 요소들은 Kubernetes의 네트워크를 구성해주기도 하고, 한편으로는 워크로드의 서비스를 노출해주는 역할을 합니다.
먼저 CNI는 컨테이너 네트워킹을 가능하도록 구성해주는 역할을 합니다. 이를 통해 파드는 통신 가능한 상태가 됩니다.
파드에 실행된 워크로드를 사용자가 접근하기 위해서는 Service가 필요합니다. Service에는 Cluster IP, Nodeport, LoadBalancer 유형이 있습니다. 간단히 클러스터 내의 다른 Micro 서비스에 대한 호출은 Cluster IP를 사용하고, 외부 통신은 Nodeport나 LoadBalancer 유형의 Service로 가능합니다.
다만 LoadBalancer 유형의 서비스는 클러스터 외부에 IP를 노출해야 하기 때문에 다른 구현체(컨트롤러)를 통해서 Loadbalancing을 구현하고, 엔드포인트 IP를 클러스터의 LoadBalancer 유형의 서비스에 노출 해야합니다. 그래서 보통은 CSP에서 Cloud Controller를 통해 구현을 하거나, On-Premise에서는 LoxiLB 와 같은 솔루션을 사용할 수 있습니다.
또한 노드 수준에서는 iptables, IPVS, eBPF 로 Service 와 접점(Endpoint)를 구성해야 하는데, kube-proxy 를 통해 iptables를 사용하는 방법이 일반적입니다.
이때 Service는 L4 수준의 통신을 가능하도록 하므로, L7 통신을 가능하게 하기 위해 Ingress 리소스를 사용할 수 있습니다.
본 포스트에서는 기본적인 Cluster IP, Nodeport에 대한 설명은 제외하고, EKS에서 LoadBalancer 유형의 서비스와 Ingress 서비스를 어떤 식으로 구현했는지를 살펴보겠습니다.
3. AWS의 LoadBalancer 유형
EKS의 Loadbalancer와 Ingress는 각 다른 유형의 Loadbalancer 로 구현되어 있습니다. EKS 실습에 앞서 AWS의 Loadbalancer 서비스 유형을 간단히 살펴보겠습니다.
Elastic Load Balancing은 다음 유형의 로드 밸런서를 지원합니다.
- Application Load Balancers (ALB)
- Network Load Balancer (NLB)
- Gateway Load Balancer (GWLB)
- Classic Load Balancer(CLB)
CLB는 가장 오래된 ELB의 형태로, L4/L7 계층의 로드밸런싱이 가능하지만, 하나의 주소에 하나의 대상 그룹 밖에 지정을 할 수 없는 것과 같은 몇가지 제한사항이 있습니다. CLB는 Legacy로 분류되어 많이 사용하지 않습니다.
ALB는 L7계층의 로드밸런싱이 가능합니다. L7의 로드밸런싱이 가능하기 때문에 HTTP 헤더 정보를 바탕으로 라우팅을 하거나 Path Based 라우팅도 가능합니다.
NLB는 L4 계층이 로드밸런서 입니다. 고성능 부하 분산을 하기에 적합하며, EIP로 공인 IP를 고정할 수 있는 장점이 있습니다. 노출된 IP를 통해 DNS를 제공해줘 접근도 가능합니다.
GWLB는 L3 계층에서 작동합니다. GWLB를 통해서 방화벽, 침입 탐지, 패킷 검사와 같은 가상 어플라인언스를 배포 확장하는데 활요될 수 있습니다. GWLB는 앞서 설명한 LoadBalancer와는 역할이 다소 다른 것 같습니다.
EKS에서는 NLB 혹은 ALB를 사용하여 워크로드의 인바운드 서비스를 가능하도록 합니다.
4. EKS의 LoadBalancer 구현
EKS에서 LoadBalancer 유형의 서비스가 생성되면 AWS의 Load Balancer가 프로비저닝 됩니다.
어떤 방식으로 구현되었지를 살펴보기 위해서 아래 문서를 바탕으로 정리했습니다.
https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
EKS에서는 로드밸런서를 프로비저닝을 하는 두 가지 방법이 있습니다.
- AWS Cloud Provider Load balancer Controller (레거시)
- AWS Load Balancer Controller (권장)
기본적으로 Kubernetes의 LoadBalancer 유형의 서비스는 kube-controller-manager 혹은 cloud-controller-manager(in-tree controller)의 Cloud Provider 컴포넌트에 내장된 Service Controller 의해서 조정됩니다.
이때 AWS Cloud Provider Load balancer Controller는 레거시로, 현재는 critical bug fix만 가능합니다. AWS Cloud Provider Load balancer Controller에서는 CLB가 기본적으로 생성되고, 알맞은 annotation을 통해 NLB를 생성할 수 있습니다.
AWS 에서는 확장된 기능을 제공하기 위해서 AWS Load Balancer Controller를 EKS에 설치하는 방식을 제공하며, 이 경우 LoadBalancerClass에서 service.beta.kubernetes.io/aws-load-balancer-type
Annotation으로 Load Balancer Controller로 명시적으로 오프로드를 해야합니다.
Note: 확인해보면 lodbalancerClass라는 리소스는 없는데, 실제 어떤 의미인지 정확하지 않습니다. 이후 실습에서 사용하는 LoadBalancer 유형의 서비스에서 loadBalancerClass: service.k8s.aws/nlb 로 지정하기는 합니다.
이제 Load Balancer 대상 유형에 따른 차이를 살펴보겠습니다.
EKS의 LoadBalancer 유형의 서비스에 service.beta.kubernetes.io/aws-load-balancer-nlb-target-type
Annotation을 통해 대상 유형을 instance 혹은 ip 유형을 지정할 수 있습니다.
먼저 instance 로 지정한 경우 아래와 같은 구성으로 연결됩니다.
출처: https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
이 구성에서는 NodePort 를 통해서 대상이 등록되는 것을 알 수 있습니다. 이로 인해서 패킷은 노드로 전달되고 이후 iptables를 따라서 파드로 패킷이 전달되는 추가 hop이 발생합니다.
이 때문에 대상 유형을 ip
로 지정하는 것이 권장되며, 이때 아래와 같이 구성됩니다.
출처: https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
이 방식은 Load Balancer의 대상이 바로 파드 IP로 등록됨에 따라, 네트워크 경로가 단순화 되고 iptables의 오버헤드가 제거되는 장점을 가지게 됩니다.
5. EKS의 LoadBalancer 실습
먼저 아무런 설정이 없이 LoadBalancer 서비스를 생성해 보겠습니다.
# 터미널1 (모니터링)
watch -d 'kubectl get pod,svc'
# 수퍼마리오 디플로이먼트 배포
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: mario
labels:
app: mario
spec:
replicas: 1
selector:
matchLabels:
app: mario
template:
metadata:
labels:
app: mario
spec:
containers:
- name: mario
image: pengbai/docker-supermario
---
apiVersion: v1
kind: Service
metadata:
name: mario
spec:
selector:
app: mario
ports:
- port: 80
protocol: TCP
targetPort: 8080
type: LoadBalancer
EOF
# 배포 확인 : CLB 배포 확인
kubectl get deploy,svc,ep mario
# 마리오 게임 접속 : CLB 주소로 웹 접속
kubectl get svc mario -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Maria URL = http://"$1 }'
배포가 되고 서비스에 EXTERNAL-IP가 할당되었습니다.
NAME READY STATUS RESTARTS AGE
pod/mario-6d8c76fd8d-kqmft 1/1 Running 0 22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 50m
service/mario LoadBalancer 10.100.40.60 ac12f1c2b92b84516b339ffeea5afd12-1085404426.ap-northeast-2.elb.amazonaws.com 80:31207/TCP 22s
이 상황에서 웹 콘솔에서 배포된 리소스를 확인해보겠습니다.
Service 오브젝트에 아무런 annotation이 없는 상황으로 CLB가 배포된 것을 확인할 수 있습니다.
AWS Load Balancer Controller 배포
이제 AWS Load Balancer Controller를 배포해 보겠습니다. Helm(https://helm.sh/docs/intro/install/)을 사용합니다.
주요 컴포넌트가 addon이 아닌 형태로 제공되면 이후에 업그레이드나 관리에 어려움이 있을텐데 이 컴포넌트는 왜 addon이 아닐지 의문이 듭니다. (Helm은 Life Cycle Management를 할 수 없음)
# 설치 전 CRD 확인
$ kubectl get crd
NAME CREATED AT
cninodes.vpcresources.k8s.aws 2025-02-15T14:39:16Z
eniconfigs.crd.k8s.amazonaws.com 2025-02-15T14:41:52Z
policyendpoints.networking.k8s.aws 2025-02-15T14:39:16Z
securitygrouppolicies.vpcresources.k8s.aws 2025-02-15T14:39:16Z
$ kubectl get ingressclass
No resources found
# 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
배포가 완료되면 2개의 CRD와 ingress class가 생성된 것을 확인할 수 있습니다.
## 설치 확인
$ kubectl get crd
NAME CREATED AT
cninodes.vpcresources.k8s.aws 2025-02-15T14:39:16Z
eniconfigs.crd.k8s.amazonaws.com 2025-02-15T14:41:52Z
ingressclassparams.elbv2.k8s.aws 2025-02-15T15:35:37Z
policyendpoints.networking.k8s.aws 2025-02-15T14:39:16Z
securitygrouppolicies.vpcresources.k8s.aws 2025-02-15T14:39:16Z
targetgroupbindings.elbv2.k8s.aws 2025-02-15T15:35:37Z
$ kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
alb ingress.k8s.aws/alb <none> 104s
# 생성된 CRD
$ kubectl explain ingressclassparams.elbv2.k8s.aws
GROUP: elbv2.k8s.aws
KIND: IngressClassParams
VERSION: v1beta1
DESCRIPTION:
IngressClassParams is the Schema for the IngressClassParams API
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <ObjectMeta>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object>
IngressClassParamsSpec defines the desired state of IngressClassParams
$ kubectl explain targetgroupbindings.elbv2.k8s.aws
GROUP: elbv2.k8s.aws
KIND: TargetGroupBinding
VERSION: v1beta1
DESCRIPTION:
TargetGroupBinding is the Schema for the TargetGroupBinding API
FIELDS:
apiVersion <string>
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
kind <string>
Kind is a string value representing the REST resource this object
represents. Servers may infer this from the endpoint the client submits
requests to. Cannot be updated. In CamelCase. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
metadata <ObjectMeta>
Standard object's metadata. More info:
https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
spec <Object>
TargetGroupBindingSpec defines the desired state of TargetGroupBinding
status <Object>
TargetGroupBindingStatus defines the observed state of TargetGroupBinding
실제 생성된 디플로이먼트도 확인해봅니다.
$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 5m59s
$ kubectl describe deploy -n kube-system aws-load-balancer-controller
Name: aws-load-balancer-controller
Namespace: kube-system
CreationTimestamp: Sun, 16 Feb 2025 00:35:40 +0900
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.11.0
helm.sh/chart=aws-load-balancer-controller-1.11.0
Annotations: deployment.kubernetes.io/revision: 1
meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
Selector: app.kubernetes.io/instance=aws-load-balancer-controller,app.kubernetes.io/name=aws-load-balancer-controller
Replicas: 2 desired | 2 updated | 2 total | 2 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 25% max unavailable, 25% max surge
Pod Template:
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/name=aws-load-balancer-controller
Annotations: prometheus.io/port: 8080
prometheus.io/scrape: true
Service Account: aws-load-balancer-controller
Containers:
aws-load-balancer-controller:
Image: public.ecr.aws/eks/aws-load-balancer-controller:v2.11.0
Ports: 9443/TCP, 8080/TCP
Host Ports: 0/TCP, 0/TCP
Args:
--cluster-name=myeks
--ingress-class=alb
Liveness: http-get http://:61779/healthz delay=30s timeout=10s period=10s #success=1 #failure=2
Readiness: http-get http://:61779/readyz delay=10s timeout=10s period=10s #success=1 #failure=2
Environment: <none>
Mounts:
/tmp/k8s-webhook-server/serving-certs from cert (ro)
Volumes:
cert:
Type: Secret (a volume populated by a Secret)
SecretName: aws-load-balancer-tls
Optional: false
Priority Class Name: system-cluster-critical
Node-Selectors: <none>
Tolerations: <none>
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: aws-load-balancer-controller-554fbd9d (2/2 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 8m35s deployment-controller Scaled up replica set aws-load-balancer-controller-554fbd9d to 2
$ 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
Name: aws-load-balancer-controller-rolebinding
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.11.0
helm.sh/chart=aws-load-balancer-controller-1.11.0
Annotations: meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
Role:
Kind: ClusterRole
Name: aws-load-balancer-controller-role
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount aws-load-balancer-controller kube-system
$ kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
targetgroupbindings.elbv2.k8s.aws [] [] [create delete get list patch update watch]
events [] [] [create patch]
ingresses [] [] [get list patch update watch]
services [] [] [get list patch update watch]
ingresses.extensions [] [] [get list patch update watch]
services.extensions [] [] [get list patch update watch]
ingresses.networking.k8s.io [] [] [get list patch update watch]
services.networking.k8s.io [] [] [get list patch update watch]
endpoints [] [] [get list watch]
namespaces [] [] [get list watch]
nodes [] [] [get list watch]
pods [] [] [get list watch]
endpointslices.discovery.k8s.io [] [] [get list watch]
ingressclassparams.elbv2.k8s.aws [] [] [get list watch]
ingressclasses.networking.k8s.io [] [] [get list watch]
ingresses/status [] [] [update patch]
pods/status [] [] [update patch]
services/status [] [] [update patch]
targetgroupbindings/status [] [] [update patch]
ingresses.elbv2.k8s.aws/status [] [] [update patch]
pods.elbv2.k8s.aws/status [] [] [update patch]
services.elbv2.k8s.aws/status [] [] [update patch]
targetgroupbindings.elbv2.k8s.aws/status [] [] [update patch]
ingresses.extensions/status [] [] [update patch]
pods.extensions/status [] [] [update patch]
services.extensions/status [] [] [update patch]
targetgroupbindings.extensions/status [] [] [update patch]
ingresses.networking.k8s.io/status [] [] [update patch]
pods.networking.k8s.io/status [] [] [update patch]
services.networking.k8s.io/status [] [] [update patch]
targetgroupbindings.networking.k8s.io/status [] [] [update patch]
해당 디플로이먼트는 aws-load-balancer-controller
라는 ServiceAccount를 사용하는데, Service/Ingerss 및 관련된 구성요소들에 대한 권한을 가진 것을 알 수 있습니다.
대상 유형별 리소스 배포 실습
AWS Load Balancer Controller 를 배포하면 Service 나 Ingress의 매니페스트에 추가된 Annotation이 다릅니다.
Note: The configuration of the provisioned load balancer is controlled by annotations that are added to the manifest for the Service or Ingress object and are different when using the AWS Load Balancer Controller than they are when using the AWS cloud provider load balancer controller.
https://docs.aws.amazon.com/eks/latest/best-practices/load-balancing.html
대상 유형(Target Type)의 차이를 확인하기 위해서 각각 instance 타입과 ip 타입으로 Loadbalancer 서비스를 생성해서 확인해보겠습니다.
# instance type을 위한 디플로이먼트 & 서비스 생성
cat << EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo1
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv1
template:
metadata:
labels:
app: deploy-websrv1
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-instance-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
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-websrv1
EOF
그 다음은 대상 유형을 IP로 배포해 보겠습니다.
# 디플로이먼트 & 서비스 생성
cat << EOF | kubectl apply -f -
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: aews-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
EOF
아래와 같이 CLB로 구성된 mario 서비스와 NLB로 구성된 instance type과 ip type으로 서비스가 각각 생성되었습니다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 74m
mario LoadBalancer 10.100.40.60 ac12f1c2b92b84516b339ffeea5afd12-1085404426.ap-northeast-2.elb.amazonaws.com 80:31207/TCP 24m
svc-nlb-instance-type LoadBalancer 10.100.73.240 k8s-default-svcnlbin-9674736b71-4aae57171be6e3b8.elb.ap-northeast-2.amazonaws.com 80:32332/TCP 14s
svc-nlb-ip-type LoadBalancer 10.100.124.220 k8s-default-svcnlbip-d503e33dc2-2ecb99f47d0bc5bf.elb.ap-northeast-2.amazonaws.com 80:30264/TCP 47s
웹 콘솔에서 두 대상 유형이 다른 NLB 구성이 어떻게 차이가 나는지 확인해보겠습니다.
먼저 instance로 생성한 NLB의 resource map을 살펴보면 Target이 NodePort로 등록되어 있는걸 알 수 있습니다.
이상한 점은 분명 서비스 엔드포인트로 호출이 되고 NodePort도 정상인데, 대상들이 모두 Unhealthy: Health checks failed
로 표시된다는 점입니다. 이 부분이 by design인지 정확하지 않습니다.
아래는 ip로 생성한 NLB의 resource map입니다. 이 경우에는 파드 IP가 직접 등록된 것을 확인할 수 있습니다.
아래로 endpoint 정보를 확인하면 동일한 것을 알 수 있습니다.
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 92m
mario LoadBalancer 10.100.40.60 ac12f1c2b92b84516b339ffeea5afd12-1085404426.ap-northeast-2.elb.amazonaws.com 80:31207/TCP 42m
svc-nlb-instance-type LoadBalancer 10.100.73.240 k8s-default-svcnlbin-9674736b71-4aae57171be6e3b8.elb.ap-northeast-2.amazonaws.com 80:32332/TCP 18m
svc-nlb-ip-type LoadBalancer 10.100.124.220 k8s-default-svcnlbip-d503e33dc2-2ecb99f47d0bc5bf.elb.ap-northeast-2.amazonaws.com 80:30264/TCP 18m
$ kubectl get ep
NAME ENDPOINTS AGE
kubernetes 192.168.2.190:443,192.168.3.91:443 92m
mario 192.168.1.136:8080 42m
svc-nlb-instance-type 192.168.2.180:8080,192.168.3.56:8080 18m
svc-nlb-ip-type 192.168.1.137:8080,192.168.3.45:8080 18m
6. AKS의 LoadBalancer 구현
Azure의 LoadBalancer 유형의 서비스는 Azure LoadBalancer를 통해 구현됩니다.
AWS의 ELB들이 실제 서브넷에 위치해 파드 IP로 접근한 것과 다르게, Azure Loadbalancer는 서브넷에 위치하지 않습니다. 이러한 이유로 Azure Loadbalancer는 파드 IP로 직접 접근을 하지 못합니다.
Note: 파드IP가 가상 네트워크에서 유효한 IP를 가지고 있다고 하더라도 파드 IP로 다른 리소스에 접근하는 것과 다른 리소스에서 파드IP로 접근하는 것은 다릅니다. 파드 IP로 다른 리소스를 접근하는 것은 SNAT로 가능하지만 보통 반대 방향의 통신은 불가합니다.
실제 대상이 가상 네트워크에 있지 않으면 리소스는 파드IP로 접근할 수 없습니다.
이러한 이유로 AKS의 Azure Loadbalancer는 노드 IP를 통한 백엔드 구성을 합니다. 이 경우 EKS의 service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
와 동일하다고 생각할 수 있지만, Azure Loadbalancer에서는 Floating IP(https://learn.microsoft.com/en-us/azure/load-balancer/load-balancer-floating-ip#floating-ip)라는 방식을 통해서 Loadbalancer에서 SNAT/DNAT이 없이 VIP로 서버로 패킷을 전달하고 iptables에서 DSR(Direct Server Return)할 수 있도록 구현되어 있습니다.
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: instance
와 동일한 구성도 가능한데, AKS에서 Loadbalancer 서비스에서 service.beta.kubernetes.io/azure-disable-load-balancer-floating-ip : "true"
을 통해 Floating IP를 disable하면 DIP로 패킷이 전달되고, 이 때는 백엔드가 Nodeport 방식으로 구성됩니다.
참고: https://cloud-provider-azure.sigs.k8s.io/topics/loadbalancer/#loadbalancer-annotations
참고로 Azure의 Application Gateway는 서브넷에 위치하는 리소스 입니다. AKS의 Ingress 구현체 중 Application gateway Ingresss controller를 사용할 수 있는데, Appilcation Gateway의 백엔드 대상은 파드 IP를 직접 대상으로 등록합니다.
7. EKS의 Ingress 구현 및 실습
EKS에서 Ingress를 생성하면 ALB가 프로비저닝 됩니다. 또한 아래 문서의 사전 조건을 살펴보면 이를 위해서 AWS Load Balancer Controller 가 있어야 하는 것을 알 수 있습니다.
https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/alb-ingress.html
아래와 같이 실습을 진행해 보겠습니다.
# 게임 파드와 Service, Ingress 배포
cat <<EOF | kubectl apply -f -
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
EOF
서비스 매니페스트를 확인해보면 앞서 AWS Load Balancer Controller에서 생성된 IngressClass인 alb
를 지정한 것을 알 수 있습니다.
ingress가 배포되었습니다.
$ kubectl get ing -n game-2048
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress-2048 alb * k8s-game2048-ingress2-70d50ce3fd-1587098903.ap-northeast-2.elb.amazonaws.com 80 3m9s
$ kubectl get svc -n game-2048
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service-2048 NodePort 10.100.251.246 <none> 80:30521/TCP 5m31s
$ kubectl get ep -n game-2048
NAME ENDPOINTS AGE
service-2048 192.168.1.239:80,192.168.3.219:80 5m23s
다시 웹 콘솔로 로드밸런서 정보를 확인해보면 ALB가 생성되었습니다.
ALB의 대상그룹을 확인해보면 ALB에서 파드 IP로 직접 전달하는 것을 알 수 있습니다. Ingress 이므로 Rules가 추가되어 시각화 되어 있는 것을 알 수 있습니다.
EKS의 Ingress에서는 Ingress Group를 지정해서 하나의 Ingress를 바라보도록 해서, 서로 다른 주체가 관리하도록 하는 개념이 있습니다.
아래와 같이 서로 다른 Ingerss를 사용하면서 하나의 ingress group으로 묶어서 배포하는 방식을 사용할 수 있는 점이 독특한 것 같습니다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: orange-purple-ingress
namespace: orange-purple-ns
labels:
app: color-2
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: app-color-lb
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /orange
pathType: Prefix
backend:
service:
name: orange-service
port:
number: 80
- path: /purple
pathType: Prefix
backend:
service:
name: purple-service
port:
number: 80
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: blue-green-ingress
namespace: blue-green-ns
labels:
app: color-1
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/group.name: app-color-lb
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /blue
pathType: Prefix
backend:
service:
name: blue-service
port:
number: 80
- path: /green
pathType: Prefix
backend:
service:
name: green-service
port:
number: 80
여기까지 EKS에서 살펴본 LoadBalancer 유형의 서비스와 Ingress가 사용하는 NLB, ALB 구성에 대한 전체적인 구성이 아래와 같다는 것을 알 수 있습니다.
출처: https://www.youtube.com/watch?v=E49Q3y9wsUo
8. AKS의 Ingress 구현
AKS에서 Ingress를 구현한 방식에는 크게 3가지가 있습니다. Application Gateway Ingress Controller, Application Gateway for Containers, Application Routing addon 입니다.
Note: 아래 문서를 보시면 Istio-based service mesh 또한 Ingress 옵션으로 이야기 하고 있지만 이는 istio Ingress API 이므로, 제외하고 설명하도록 하겠습니다.
https://learn.microsoft.com/en-us/azure/aks/concepts-network-ingress#compare-ingress-options
Application Gateway Ingress Controller는 AGIC 파드가 내부에서 API 서버를 모니터링 하다가 Azure Resource Manager로 설정 변경을 하는 아키텍처를 가지고 있습니다. Azure Load Balancer와 다르게 Applicaton Gateway는 서브넷에 위치하는 리소스로 파드 IP를 직접 백엔드로 구성합니다.
다만 Ingress의 다양한 설정을 하기에는 기존에 Application Gateway 라는 제품에서 가지고 있는 기능에서만 구현이 가능하다는 점에서 확장성이 부족한 솔루션으로 알려져 있습니다.
참고: https://learn.microsoft.com/en-us/azure/application-gateway/ingress-controller-overview
이로 인해서 비교적 최근 Application Gateway for Containers(이하 AGC) 라는 AKS를 위한 별도의 상품이 추가 되었습니다. AGC는 상대적으로 AGIC에 비해 다양한 고급 라우팅 기능이 추가 되었으며, Gateway API도 지원하고 있습니다.
참고: https://learn.microsoft.com/en-us/azure/application-gateway/for-containers/overview
다만 이러한 솔루션을 사용하지 않고 사용자가 직접 Nginx Ingress Controller 배포하여 사용하는 경우가 있습니다. 이를 위해서 AKS에서는 Application Routing addon으로 managed Nginx ingress를 제공하고 있습니다.
Managed Nginx ingress는 기존 Nginx Ingress Controller를 addon 형태로 제공할 뿐 구성되는 아키텍처는 동일합니다. 즉 Ingress controller가 파드로 실행되고, LoadBalancer 유형의 서비스로 엔드포인트를 구성합니다. Addon으로 제공하므로 설치 및 업그레이드와 같은 부분에서 Managed 서비스를 제공합니다. 다만 Manaed 서비스이기 때문에 오픈 소스 Nginx Ingress의 모든 설정을 지원하지 않으므로 사용하기 전에 평가가 필요합니다.
참고: https://learn.microsoft.com/en-us/azure/aks/app-routing
9. 리소스 정리
아래 절차로 생성된 EKS와 CloudFormation을 삭제합니다.
eksctl delete cluster --name $CLUSTER_NAME
# EKS 삭제 완료 후 실행
aws cloudformation delete-stack --stack-name myeks
마무리
EKS Networking을 두 포스트를 통해서 알아보고, 그 과정에서 EKS와 AKS의 차이를 살펴 봤습니다.
기본적으로 EKS의 VPC와 Azure의 Virtual Network에 차이가 있기 때문에 구현 방식이 달라지는 부분이 있었고, CNI를 구현하는 방식에는 유사한 듯 하면서도 서로 일부 제약사항이 있는 상황입니다.
전반적으로 EKS가 단일 CNI와 ELB 상품을 통한 Service/Ingress를 구현한 반면, AKS는 CNI나 Ingress 쪽에서 구현체가 다양한 모습을 확인할 수 있었습니다.
다음 포스트에서는 EKS의 노드 그룹과 스토리지 옵션을 살펴보도록 하겠습니다.
'EKS' 카테고리의 다른 글
[2-1] EKS Networking Part1 - VPC CNI와 파드 통신 (0) | 2025.02.16 |
---|---|
[1] EKS 생성과 리소스 살펴보기 (2) | 2025.02.07 |