AWS에 EKS를 띄우고, aws loadbalancer controller를 설치한 후 nlb로 테스트를 진행해보자.
kind는 docker desktop을 설치하고, 로컬에서 클러스터를 만들어주는 도구다. 아래 09/cluster.yaml 파일로 클러스터를 생성해보자.
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.33.4
- role: worker
image: kindest/node:v1.33.4
labels:
role: client
- role: worker
image: kindest/node:v1.33.4
labels:
role: server
아래 명령어로 클러스터를 생성했다.
kind create cluster --name 1-33 --config cluster.yaml
node label을 확인해보면 role=client, role=server를 확인할 수 있다.
kubectl get node --show-labels
NAME STATUS ROLES AGE VERSION LABELS
1-33-control-plane Ready control-plane 2m8s v1.33.4 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-control-plane,kubernetes.io/os=linux,node-role.kubernetes.io/control-plane=,node.kubernetes.io/exclude-from-external-load-balancers=
1-33-worker Ready <none> 119s v1.33.4 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-worker,kubernetes.io/os=linux,role=client
1-33-worker2 Ready <none> 119s v1.33.4 beta.kubernetes.io/arch=arm64,beta.kubernetes.io/os=linux,kubernetes.io/arch=arm64,kubernetes.io/hostname=1-33-worker2,kubernetes.io/os=linux,role=server
09/clinet.yaml, 09/server.yaml로 각각 다른 노드에 배포를 진행해보자.
kubectl create ns client
kubectl create ns server
kubectl apply -f client.yaml -n client
kubectl apply -f server.yaml -n server
client 파드 이름을 확인한 후, 명령어를 실행해보면
kubectl -n client exec -it client-578c5494df-brldf -- python client.py 5
요청이 잘 가는 걸 확인할 수 있다. 09/client.yaml파일에 사용한 server.server.svc.cluster.local 주소는 클러스터 내부 도메인이다.

클러스터를 만들었다 지웠다가 하려면 eksctl, terraform 등으로 생성했다가 한번에 지우는 게 비용상으로 유리할 것 같다. 10/cluster.yaml 파일로 eks 클러스터를 생성해보자.
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: my-cluster
region: ap-northeast-2
version: "1.33"
availabilityZones: ["ap-northeast-2a", "ap-northeast-2c"]
vpc:
clusterEndpoints:
publicAccess: true # kubectl exec를 위해 API 서버는 Public에서 접근 허용
privateAccess: true
nat:
gateway: Single
# IAM OIDC 활성화 (LB 컨트롤러 등을 위해 필수)
iam:
withOIDC: true
managedNodeGroups:
# 1. Infra Node Group (모니터링, ingress controller 등)
- name: infra
instanceType: t3.medium
minSize: 1
maxSize: 2
desiredCapacity: 1
privateNetworking: true
availabilityZones: ["ap-northeast-2a"]
labels:
role: infra
# 2. Server Node Group (백엔드 API 등)
- name: server
instanceType: t3.medium
minSize: 1
maxSize: 1
desiredCapacity: 1
privateNetworking: true
availabilityZones: ["ap-northeast-2a"]
labels:
role: server
# 3. Client Node Group (테스트용 클라이언트 등)
- name: client
instanceType: t3.medium
minSize: 1
maxSize: 1
desiredCapacity: 1
privateNetworking: true
availabilityZones: ["ap-northeast-2a"]
labels:
role: client
현재 사용중인 profile을 확인하고
aws sts get-caller-identity --profile kyeongjun-dev
ekstctl로 설치를 진행한다.
eksctl create cluster -f cluster.yaml --profile kyeongjun-dev
policy를 다운로드 받는다. (10/iam_policy.json)
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.13.3/docs/install/iam_policy.json
iam policy를 생성한다.
aws --profile kyeongjun-dev iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json
클러스터 생성이 완료되면, kubeconfig를 가져온다.
aws eks update-kubeconfig --region ap-northeast-2 --name my-cluster --profile kyeongjun-dev
eksctl로 oidc가 연동된 iam role을 생성한다.
eksctl create iamserviceaccount \
--cluster=my-cluster \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::<AWS 계정 ID>:policy/AWSLoadBalancerControllerIAMPolicy \
--override-existing-serviceaccounts \
--region ap-northeast-2 \
--approve \
--profile kyeongjun-dev
kube-system에 설치된 service account를 확인한다.
kubectl get sa -n kube-system aws-load-balancer-controller
NAME SECRETS AGE
aws-load-balancer-controller 0 92s
helm repo를 추가하고
helm repo add eks https://aws.github.io/eks-charts
helm repo update eks
helm 차트 수정을 위해 pull 진행
helm pull eks/aws-load-balancer-controller
tar xf aws-load-balancer-controller-1.16.0.tgz
rm aws-load-balancer-controller-1.16.0.tgz
원본 유지를 위해 values 파일 복사
cp aws-load-balancer-controller/values.yaml albc-value.yaml
아래 내용대로 수정
diff albc-value.yaml aws-load-balancer-controller/values.yaml
5c5
< replicaCount: 1
---
> replicaCount: 2
32c32
< create: false
---
> create: true
37c37
< name: aws-load-balancer-controller
---
> name:
78,79c78
< nodeSelector:
< role: infra
---
> nodeSelector: {}
136c135
< clusterName: my-cluster
---
> clusterName:
수정한 albc-value.yaml로 설치
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -f albc-value.yaml -n kube-system
설치 확인
kubectl get pod -n kube-system aws-load-balancer-controller-667f7744d6-m76vg
NAME READY STATUS RESTARTS AGE
aws-load-balancer-controller-667f7744d6-m76vg 1/1 Running 0 39s
현재 infra, client, server 노드그룹이 있는데 add-on 파드들이 정리되지 않고 모든 노드에 있다.
kubectl get node -l role=client
kubectl describe node ip-192-168-78-153.ap-northeast-2.compute.internal
Non-terminated Pods: (6 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system aws-node-xbpgz 50m (2%) 0 (0%) 0 (0%) 0 (0%) 9m18s
kube-system coredns-c844dd74d-dhgnw 100m (5%) 0 (0%) 70Mi (2%) 170Mi (5%) 13m
kube-system coredns-c844dd74d-z222h 100m (5%) 0 (0%) 70Mi (2%) 170Mi (5%) 13m
kube-system kube-proxy-bltk7 100m (5%) 0 (0%) 0 (0%) 0 (0%) 9m18s
kube-system metrics-server-7645d75fbf-2slfp 100m (5%) 0 (0%) 200Mi (6%) 400Mi (12%) 13m
kube-system metrics-server-7645d75fbf-gkznr 100m (5%) 0 (0%) 200Mi (6%) 400Mi (12%) 13m
kubectl get node -l role=server
kubectl describe node ip-192-168-89-251.ap-northeast-2.compute.internal
Non-terminated Pods: (2 in total)
Namespace Name CPU Requests CPU Limits Memory Requests Memory Limits Age
--------- ---- ------------ ---------- --------------- ------------- ---
kube-system aws-node-58j4k 50m (2%) 0 (0%) 0 (0%) 0 (0%) 10m
kube-system kube-proxy-zgkbn 100m (5%) 0 (0%) 0 (0%) 0 (0%) 10m
core dns, metrics server를 infra 노드로 옮겨주자
kubectl -n kube-system edit dep
loyments.apps coredns
(변경 전)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
(변경 후)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
- key: role
operator: In
values:
- infra
kubectl -n kube-system edit deployments.apps metrics-server
(변경 전)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
(변경 후)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/os
operator: In
values:
- linux
- key: kubernetes.io/arch
operator: In
values:
- amd64
- arm64
- key: role
operator: In
values:
- infra
개수도 2개는 필요 없으니 줄여주자
kubectl -n kube-system scale deployment coredns --replicas 1
kubectl -n kube-system scale deployment metrics-server --replicas 1
namspace 생성
kubectl create ns client
kubectl create ns server
10/client.yaml, 10/server.yaml 생성
kubectl apply -f client.yaml -n client
kubectl apply -f server.yaml -n server
먼저 아래 스크린샷을 참고하여 target group을 생성한다.

생성한 target group의 arn을 입력해서 target group binding을 생성한다. (10/tgb.yaml)
apiVersion: elbv2.k8s.aws/v1beta1
kind: TargetGroupBinding
metadata:
name: server
spec:
serviceRef:
name: server
port: 8000
targetGroupARN: <arn-to-targetGroup>
등록이 됐는지 확인한다.

콘솔에서 network 타입으로 생성을 시작한다. internal로 지정해준다.

네트워크 설정은 eksctl로 클러스터를 생성할 때, 자동으로 생성된 vpc랑 subnet을 사용한다. az는 2a만 지정했고, subnet이 총 2개(Public, Private)있는데, public을 선택하면 경고 메시지가 발생한다. (두 번째 스크린샷 참고)


sg도 eksctl로 생성할 때 생성된 sg 중 eks-cluster-sg로 시작하는 걸 지정해준다. eks에 생성된 노드들에서 사용하는 sg다.

리스너는 80포트, 타겟 그룹은 위에서 생성했던 타겟 그룹을 지정해서 생성을 마무리 한다. (나머지 값은 전부 기본 값)

Active 상태가 되는 걸 확인한다.

nlb의 DNS name으로 client.yaml을 수정해서 다시 apply 한다.(10/client-nlb.yaml)
apiVersion: v1
kind: Service
metadata:
name: client
labels:
app: client
spec:
ports:
- port: 80
targetPort: 80
selector:
app: client
type: ClusterIP
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: client
labels:
app: client
spec:
replicas: 1
selector:
matchLabels:
app: client
template:
metadata:
labels:
app: client
spec:
containers:
- name: client
image: ghcr.io/kyeongjun-dev/network:dev
imagePullPolicy: Always
command: ["sleep", "infinity"]
env:
- name: HOST
value: "server-2f43e9b6339f7f95.elb.ap-northeast-2.amazonaws.com"
- name: PORT
value: "80"
- name: HOST_HEADER
value: "server-2f43e9b6339f7f95.elb.ap-northeast-2.amazonaws.com"
- name: USE_TLS
value: "false"
- name: ENDPOINT
value: "/"
# value: "/slow?wait=11"
nodeSelector:
role: client
파드 이름을 확인하고, 요청을 보내본다.
kubectl get pod -n client
NAME READY STATUS RESTARTS AGE
client-64cfc6c5b5-vm6vl 1/1 Running 0 52s
kubectl -n client exec -it client-64cfc6c5b5-vm6vl -- python client.py 5
요청이 잘 되는 것을 확인한다.

11/cmd 파일을 참고해서, 케이스마다 파드 이름을 알아내고 입력하는 과정을 간소화해보자. 그리고 nlb의 외부 포트가 80이라 tcpdump 조건에 80과 8000을 지정했다.
deployment 재시작은 pod의 .pcap 파일을 없애주기 위해 진행한다.(tcpdump가 원래 파일을 덮어쓰는 지를 모르겠다.)
export CLIENT=$(kubectl get pod -n client | grep client | awk '{print $1}')
export SERVER=$(kubectl get pod -n server | grep server | awk '{print $1}')
kubectl -n client exec -it $CLIENT -- tcpdump -i any -n 'port 80 or port 8000' -w /app/client.pcap
kubectl -n server exec -it $SERVER -- tcpdump -i any -n 'port 80 or port 8000' -w /app/server.pcap
kubectl -n client exec -it $CLIENT -- python client.py 25
kubectl -n server logs -f $SERVER
kubectl -n client cp $CLIENT:/app/client.pcap client.pcap
kubectl -n server cp $SERVER:/app/server.pcap server.pcap
kubectl -n client rollout restart deployment client
kubectl -n server rollout restart deployment server
먼저 client에서는 25초 후에 에러가 발생하는 것을 확인할 수 있다.

client 패킷을 확인해보자. client 192.168.91.27, server 192.168.72.184

server 패킷을 확인해보자.

패킷을 확인해보면, client 입장에서는 destination이 NLB이고 server 입장에서는 client가 NLB이기 때문에 nlb 192.168.79.34인 것을 확인할 수 있다.
FIN 전송 및 연결 해제 완료RST 전송kubectl edit으로 server Deployment의 --keep-alive 값을 360초로 변경한 뒤 테스트를 진행해보자. 원래 상황이라면 클라이언트의 요청 간격이 355초이고 서버의 keep alive가 360으로 더 길어서 문제가 없을 것이다.
요청을 보낸 직후 시간 기록을 위해 스크린샷

대략 400초가 지났을 때, 패킷을 확인해본다.

client, server 패킷은 아래와 같다. client 192.168.68.217, server 192.168.81.43, nlb 192.168.79.34

이전까지는 client, server 패킷이 근소한 시간차이를 두고 거의 동일했는데 nlb가 중간에 끼면서 달라진 것을 확인할 수 있다.
client 패킷을 먼저 분석하면
RST 패킷을 client에게 전송server 패킷을 분석하면
FIN 패킷을 전송했으나, nlb로부터 RST 패킷 수신이러한 현상이 발생하는 이유는, nlb의 기본 idle timeout이 350초이기 때문이다. 인터넷에 검색하면 아래 내용을 쉽게 찾을 수 있다.
nlb는 idle timeout (default: 350초)가 지나면, 연결 정보를 삭제한다. 삭제할 때는 client에 삭제했다는 사실을 알리지 않고 조용히 삭제한다.
NLB를 사용할 경우, 백엔드 서버의 keep alive 시간을 350초 보다 작게 설정해야 함을 패킷 분석을 통해 알아봤다.
글을 작성하는 시기 기준으로, NLB의 idle timeout도 변경이 가능하지만(링크), tls 포트에 대해서는 설정이 불가능해서 350초가 고정이다.
다음 글에서는 ALB로 동일하게 테스트를 진행해보려고 한다.