내 마음대로 네트워크 시리즈 - 6

김경준·2025년 12월 7일

네트워크

목록 보기
6/8
post-thumbnail

개요

AWS에 EKS를 띄우고, aws loadbalancer controller를 설치한 후 nlb로 테스트를 진행해보자.

클러스터 만들기

eks 클러스터 생성 전, kind로 로컬에서 테스트하기 (소스코드 : 레포 09 디렉토리)

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로 eks 클러스터 만들기

클러스터를 만들었다 지웠다가 하려면 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

load balancer controller 사용하기

aws load balancer controller 설치 (링크)

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

add-on 파드 정리하기

infra 노드로 옮기기

현재 infra, client, server 노드그룹이 있는데 add-on 파드들이 정리되지 않고 모든 노드에 있다.

  • client 노드
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
  • server 노드
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 노드로 옮겨주자

  • core dns
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
  • metrics server
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

NLB 만들고 어플리케이션 연결하기

client, server 배포

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 gorup binding 만들기

먼저 아래 스크린샷을 참고하여 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>

등록이 됐는지 확인한다.

NLB 만들기

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

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

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

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

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

client에서 테스트

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 interval 25, server keep-alive 20

먼저 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인 것을 확인할 수 있다.

  • 0초 : 연결 및 GET 요청 전송
  • 20초 : server가 nlb에 FIN 전송 및 연결 해제 완료
  • 25초 : client가 없어진 연결로 GET 요청 전송 후, server에서 RST 전송

client interval 355, server keep-alive 360

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 패킷을 먼저 분석하면

  • 0초 : 연결 수립 및 GET 요청 전송
  • 355초 : interval이 지나서 다시 GET 요청을 전송, nlb가 RST 패킷을 client에게 전송

server 패킷을 분석하면

  • 0초 : nlb로 부터 연결 수립 및 GET 요청 수신
  • 360초: keep alive 360초가 지날 때까지 아무런 작업이 없어서 FIN 패킷을 전송했으나, nlb로부터 RST 패킷 수신

이러한 현상이 발생하는 이유는, nlb의 기본 idle timeout이 350초이기 때문이다. 인터넷에 검색하면 아래 내용을 쉽게 찾을 수 있다.

nlb는 idle timeout (default: 350초)가 지나면, 연결 정보를 삭제한다. 삭제할 때는 client에 삭제했다는 사실을 알리지 않고 조용히 삭제한다.

결론 및 다음 글에서는

NLB를 사용할 경우, 백엔드 서버의 keep alive 시간을 350초 보다 작게 설정해야 함을 패킷 분석을 통해 알아봤다.
글을 작성하는 시기 기준으로, NLB의 idle timeout도 변경이 가능하지만(링크), tls 포트에 대해서는 설정이 불가능해서 350초가 고정이다.

다음 글에서는 ALB로 동일하게 테스트를 진행해보려고 한다.

profile
DevOps로 일하고 있습니다

0개의 댓글