본문 바로가기
K8s

PKOS-2주차) AWS VPC CNI와 Kubernetes 네트워크 구성

by 너무앵 2023. 7. 9.

 

Kubernetes의 네트워크 구성

쿠버네티스의 네트워크 구성에서 중요한 4가지 항목은 다음과 같다.

  1. 고도로 결합된(Highly-coupled) 컨테이너 간의 통신
  2. 파드와 파드 사이의 통신
  3. 파드와 서비스 사이의 통신
  4. 외부와의 통신

이 중 파드와 파드 사이의 통신을 위한 CNI의 역할에 대해 정리해보자

CNI 플러그인이란?

컨테이너 간 통신을 위한 인터페이스를 설정하는 플러그인이다.
쿠버네티스는 kubenet이라는 자체 CNI 플러그인을 기본제공하고
Calico, Weave, Cilium 등과 같은 다양한 서드파티 CNI 플러그인들이 존재함
→ 보통 서드 파티 플러그인을 사용하는데, 기본 제공 kubenet의 기능이 상당히 제한적이기 때문임

본 스터디 실습에서는 AWS 기반 클러스터를 사용하므로 AWS에서 제공하는 VPC CNI를 사용한다.

VPC CNI가 다른 CNI와 다른 점이 있다면,
네트워크의 원활한 성능 보장을 위해 노드와 파드가 같은 대역대의 IP 주소를 사용한다는 점이다.

위 그림은 Calico CNI와 VPC CNI의 통신 과정을 보여준다.

Calico CNI의 경우 오버레이 네트워크(VXLAN, IP-IP)을 사용한다.
오버레이 네트워크란 노드끼리 통신할 때 데이터를 캡슐화하여 같은 LAN처럼 통신하는 기술이다.

기존 네트워크 환경에 영향 없이 CNI 환경을 구성할 수 있지만, 캡슐화를 위해 사용하는 영역 만큼 패킷 당 송/수신 할 수 있는 데이터의 양이 줄어들고 캡슐화 자체에도 리소스가 투입되므로 상대적으로 비효율적이다

AWS VPC CNI 의 경우 동일 노드와 파드가 동일 대역의 주소를 사용, 바로 통신할 수 있다.
별도의 캡슐화 과정이 없으므로 좀 더 나은 네트워크 성능을 보여주지만 반대로 동일 대역대를 사용하므로 사용 가능한 IP 주소가 부족할 수도 있다.

노드의 기본 네트워크 정보 확인

AWS VPC CNI의 경우 파드와 노드가 같은 IP 대역을 공유한다.
하지만 그렇다고 해서 파드의 인터페이스 == 노드의 인터페이스 인 것은 아님
같은 노드에서도 파드가 위치한 네임스페이스와 노드만 사용하는 네임스페이스는 분리되어 있음. 파드는 가상 인터페이스(veth)를 통해 노드 인터페이스와 연결되어 통신하게 됨
(위 그림의 eniY@ifN와 노드 인터페이스인 ENI0은 연결되어 있음)이 때 인터페이스 하나 당 여러개의 보조 주소를 가질 수 있다.

클러스터 워커 노드의 네트워크 정보를 확인해보자
현재 실행 중인 워커 노드에 해당하는 EC2의 네트워크 정보를 콘솔에서 확인한다

두 개의 인터페이스에 각각 IP 주소 총 2개와 보조 IP 주소가 5개씩 10개가 할당된 것을 확인할 수 있다!

워커 노드의 IP는 172.30.79.119 이다.

NAME                  STATUS   ROLES          AGE   INTERNAL-IP     EXTERNAL-IP       KERNEL-VERSION    CONTAINER-RUNTIME
i-00201bbb4579776d8   Ready    node           45m   172.30.79.119   54.180.159.208    5.15.0-1026-aws   containerd://1.6.10
i-012d887d296a74bb7   Ready    node           45m   172.30.55.175   43.201.63.165     5.15.0-1026-aws   containerd://1.6.10
i-06895778bbf804dac   Ready    control-plane  46m   172.30.59.239   3.36.90.199       5.15.0-1026-aws   containerd://1.6.10

노드의 IP 주소와 EC2 인스턴스의 주소가 같은 것을 확인할 수 있다

k get pod -n kube-system -owide | grep .239
aws-cloud-controller-manager-ks52l               172.30.59.239   i-06895778bbf804dac   <none>           <none>
aws-node-7ng2c                                   172.30.59.239   i-06895778bbf804dac   <none>           <none>
dns-controller-5bb6b86f54-96dp5                  172.30.59.239   i-06895778bbf804dac   <none>           <none>
etcd-manager-events-i-06895778bbf804dac          172.30.59.239   i-06895778bbf804dac   <none>           <none>
etcd-manager-main-i-06895778bbf804dac            172.30.59.239   i-06895778bbf804dac   <none>           <none>
kops-controller-qsw57                            172.30.59.239   i-06895778bbf804dac   <none>           <none>
kube-apiserver-i-06895778bbf804dac               172.30.59.239   i-06895778bbf804dac   <none>           <none>
kube-controller-manager-i-06895778bbf804dac      172.30.59.239   i-06895778bbf804dac   <none>           <none>
kube-proxy-i-06895778bbf804dac                   172.30.59.239   i-06895778bbf804dac   <none>           <none>
kube-scheduler-i-06895778bbf804dac               172.30.59.239   i-06895778bbf804dac   <none>           <none>

컨트롤플레인 kube-system에 있는 일부 파드는 컨트롤 플레인과 같은 주소를 공유하기도 한다

테스트용 파드 생성

https://github.com/nicolaka/netshoot

  • 도커와 쿠버네티스용 네트워크 디버깅용 컨테이너 이미지

테스트를 위해 추가 터미널을 띄운 뒤 아래 명령어로 로그를 계속 확인한다

# 워커 노드 Public IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value}" --filters Name=instance-state-name,Values=running --output table

****# 워커 노드 Public IP 변수 지정
W1PIP=<워커 노드 1 Public IP>
W2PIP=<워커 노드 2 Public IP>

#[터미널1] 워커 노드 1 모니터링
ssh -i ~/.ssh/id_rsa ubuntu@$W1PIP
**watch -d "ip link | egrep 'ens5|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"**

#[터미널2] 워커 노드 2 모니터링
ssh -i ~/.ssh/id_rsa ubuntu@$W2PIP
**watch -d "ip link | egrep 'ens5|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"**

# 컨트롤플레인에서 테스트 파드 yaml 파일을 수정한다
# 설정이 없는 경우 클러스터는 노드 1에 파드 2개를 모두 설치하기 때문에 노드를 지정한다
apiVersion: v1
kind: Pod
metadata:
  name: pod-1
  labels:
    app: pod
spec:
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0
---
apiVersion: v1
kind: Pod
metadata:
  name: pod-2
  labels:
    app: pod
spec:
  nodeName: i-012d887d296a74bb7 # 워커 노드 2의 id
  containers:
  - name: netshoot-pod
    image: nicolaka/netshoot
    command: ["tail"]
    args: ["-f", "/dev/null"]
  terminationGracePeriodSeconds: 0

# 작업용 EC2 파드 2개 생성
kubectl create -f ~/pkos/2/netshoot-2pods.yam
NAME    READY   STATUS    RESTARTS   AGE     IP              NODE                  NOMINATED NODE   READINESS GATES
pod-1   1/1     Running   0          15m     172.30.76.167   i-00201bbb4579776d8   <none>           <none>
pod-2   1/1     Running   0          4m36s   172.30.54.244   i-012d887d296a74bb7   <none>           <none>
  • 정상적으로 각 워커 노드 당 1개씩 파드가 생성된 것을 확인한다
  • 파드가 생성되면, 워커 노드에 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가된다

각 파드에 접속하여 네트워크 정보를 확인해보자

# 테스트용 파드 접근
**kubectl exec -it pod-1 -- zsh**

# 네트워크 정보 확인
**ip -c addr**
>>1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
      link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
      inet 127.0.0.1/8 scope host lo
         valid_lft forever preferred_lft forever
      inet6 ::1/128 scope host
         valid_lft forever preferred_lft forever
>>3: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
      link/ether 6e:cb:ea:3a:fa:e7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
      inet **172.30.76.167/32** scope global eth0
         valid_lft forever preferred_lft forever
      inet6 fe80::6ccb:eaff:fe3a:fae7/64 scope link
         valid_lft forever preferred_lft forever

**ip -c route**
>> default via 169.254.1.1 dev eth0
   169.254.1.1 dev eth0 scope link

**route -n**
>> Kernel IP routing table
   Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
   0.0.0.0         169.254.1.1     0.0.0.0         UG    0      0        0 eth0
   169.254.1.1     0.0.0.0         255.255.255.255 UH    0      0        0 eth0

**ping -c 1 172.30.54.244 # Pod-2주소**
>> PING 172.30.54.244 (172.30.54.244) 56(84) bytes of data.
   64 bytes from 172.30.54.244: icmp_seq=1 ttl=62 time=1.25 ms

   --- 172.30.54.244 ping statistics ---
   1 packets transmitted, 1 received, 0% packet loss, time 0ms
   rtt min/avg/max/mdev = 1.253/1.253/1.253/0.000 ms

**ps**
>> PID   USER     TIME  COMMAND
   1     root     0:00  tail -f /dev/null
   7     root     0:01  zsh
   84    root     0:00  ps

**cat /etc/resolv.conf**
>> search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
	 nameserver 100.64.0.10
	 options ndots:5

exit
----------------------------

# 파드2 Shell 실행
**kubectl exec -it pod-2 -- ip -c addr**
>> 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
       link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
       inet 127.0.0.1/8 scope host lo
          valid_lft forever preferred_lft forever
       inet6 ::1/128 scope host
          valid_lft forever preferred_lft forever
   3: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
       link/ether 1e:00:f5:9b:b2:16 brd ff:ff:ff:ff:ff:ff link-netnsid 0
       inet **172.30.54.244/32** scope global eth0
          valid_lft forever preferred_lft forever
       inet6 fe80::1c00:f5ff:fe9b:b216/64 scope link
          valid_lft forever preferred_lft forever

노드-파드 간 통신 구성

AWS VPC CNI에서 파드와 노드는 모든 같은 대역대를 사용하므로 캡슐화 없이 바로 통신이 가능하다

파드 간 통신 실습

# 각 파드 IP를 변수로 선언한다
POD1=$(kubectl get pod pod-1 -o jsonpath={.status.podIP})
POD2=$(kubectl get pod pod-2 -o jsonpath={.status.podIP})

터미널을 추가로 열어놓고 node로 접근하여 tcpdump를 확인한다

# 각각의 워커 노드에 터미널로 접속한 뒤 TCPDUMP 확인
sudo tcpdump -i any -nn icmp 
# 파드 1에서 파드2로 PING을 찍어본다
kubectl exec -it pod-1 -- ping -c 2 $POD2

파드1에서 파드2로 핑이 찍히는 것을 TCPDUMP로 확인할 수 있다.

위 그림을 보면 ens6(그림상 ENI1)을 통하지 않고 ens5(ENI0)을 통해 패킷이 다른 노드로 전달된다.

ens6은 물리 인터페이스가 있음에도 불구하고 실제로는 사용하지 않는다

파드에서 외부로 통신

파드가 보낸 패킷은 veth를 통해 노드의 물리 인터페이스로 향한다. 이때 파드의 IP는 노드의 IP로 SNAT 된다.

VPC CNI의 경우에는 설정에 따라 SNAT 없이 통신도 가능함

 

 

PKOS-2주차) 파드 생성 제한 해제하기

파드 생성 갯수 제한

노드의 인터페이스(ENI)와 보조 IP 주소 수에 따라 최대 생성할 수 있는 파드의 갯수가 달라진다.

  • aws-node 와 kube-proxy 파드는 호스트의 IP를 사용함으로 최대 갯수에서 제외
kubectl describe node | grep Allocatable: -A6
>> Allocatable:
    cpu:                2
    ephemeral-storage:  59763732382
    hugepages-1Gi:      0
    hugepages-2Mi:      0
    memory:             3854320Ki
    pods:               17
  --
  Allocatable:
    cpu:                2
    ephemeral-storage:  119703055367
    hugepages-1Gi:      0
    hugepages-2Mi:      0
    memory:             3854328Ki
    pods:               17
  --
  Allocatable:
    cpu:                2
    ephemeral-storage:  119703055367
    hugepages-1Gi:      0
    hugepages-2Mi:      0
    memory:             3854320Ki
    pods:               17
# 총 갯수가 17개임을 확인할 수 있다

Deployment를 이용한 파드 생성 실습

# deployment를 생성한
kubectl apply -f ~/pkos/2/nginx-dp.yaml

# 파드를 30개로 늘려본다
kubectl scale deployment nginx-deployment --replicas=30

# 30개가 전부 생성되지 못하는 것을 확인
kubectl get pods | grep Pending
nginx-deployment-6fb79bc456-8f7hj   0/1     Pending   0          93s
nginx-deployment-6fb79bc456-fpd26   0/1     Pending   0          93s
nginx-deployment-6fb79bc456-g4xln   0/1     Pending   0          93s
nginx-deployment-6fb79bc456-lp6t8   0/1     Pending   0          93s
nginx-deployment-6fb79bc456-z949p   0/1     Pending   0          93s

IPv4 Prefix Delegation : IPv4 28bit 서브넷(prefix)를 위임하여 할당 가능 IP 수와 인스턴스 유형에 권장하는 최대 갯수로 선정

AWS VPC CNI prefix assignment mode : IPv4 Prefix Delegation 대역의 IP를 파드가 사용할 수 있다…고 한다

공식 문서를 보고 이것저것 찾아보는데 이해하기가 어려워서 조금 고생중이다.
일단 AWS Nitro 베이스 가상 머신이 필요하다고 하는데 Nitro가 뭘까?

AWS Nitro System

  • 차세대 EC2 인스턴스를 위한 기본 플랫폼
  • NVMe와 ENA(Elastic Network Adapter) 도입
  • 노드당 더 많은 파드를 실행할 수 있도록 “접두사(prefix) 할당 모드”를 추가
    • ENI의 최대 수는 동일, 대신 개별 IPv4 주소 대신에 /28 (16개의 IP주소) IPv4 주소를 사용 가능함

파드에 사용할 수 있는 단일 IP를 부여하지 않고 /28 로 되어있는 IP 주소 범위를 할당해서 더 많은 파드를 생성할 수 있다는 의미!

일단 현재 t3.medium인 클러스터 인스턴스를 c5 large 인스턴스로 교체한다

# C5.Large 인스턴스 1개 생성
aws cloudformation deploy --template-file **kops-oneclick.yaml** --stack-name **mykops2** --parameter-overrides KeyName=**daso** SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=AKI~~~ MyIamUserSecretAccessKey=**'v~~~'** ClusterBaseName=**'play-board.net'** S3StateStore=**'pkos-daso-s3'** **WorkerNodeInstanceType=c5.large WorkerNodeCount=1** --region ap-northeast-2

# 가용한 파드 생성 갯수 확인하기
kubectl describe node | grep Allocatable: -A6
Allocatable:
  cpu:                2
  ephemeral-storage:  59763732382
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3854328Ki
  pods:               17
--
Allocatable:
  cpu:                2
  ephemeral-storage:  119703055367
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3698676Ki
  pods:               29
# deployment를 생성한 뒤 30개로 늘리기
kubectl apply -f ~/pkos/2/nginx-dp.yaml
kubectl scale deployment nginx-deployment --replicas=30

# 생성된 파드 갯수 확인
k get deployments.apps nginx-deployment
>NAME               READY   UP-TO-DATE   AVAILABLE   AGE
 nginx-deployment   16/30   30           16          38s

30개 파드를 생성하는 deployment를 생성하고 세부 내용을 확인해보자

# 노드 세부내용 확인하기

Non-terminated Pods:          (22 in total)
  Namespace                   Name                                   CPU Requests  CPU Limits  Memory Requests  Memory Limits  Age
  ---------                   ----                                   ------------  ----------  ---------------  -------------  ---
  default                     nginx-deployment-6fb79bc456-4bbsv      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-6fdz7      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-8j76l      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-8l9xc      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-8wzqh      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-b2hw6      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-hppvg      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-jkj8r      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-kc7ql      100m (5%)     0 (0%)      0 (0%)           0 (0%)         107s
  default                     nginx-deployment-6fb79bc456-kt42l      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-lc66s      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-pfm5v      100m (5%)     0 (0%)      0 (0%)           0 (0%)         107s
  default                     nginx-deployment-6fb79bc456-pzxwd      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-q7gpx      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-t649w      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  default                     nginx-deployment-6fb79bc456-zhnkj      100m (5%)     0 (0%)      0 (0%)           0 (0%)         101s
  kube-system                 aws-node-xwxqp                         25m (1%)      0 (0%)      0 (0%)           0 (0%)         14m
  kube-system                 coredns-6897c49dc4-6k5d8               100m (5%)     0 (0%)      70Mi (1%)        170Mi (4%)     13m
  kube-system                 coredns-6897c49dc4-mvdz8               100m (5%)     0 (0%)      70Mi (1%)        170Mi (4%)     15m
  kube-system                 coredns-autoscaler-5685d4f67b-drblg    20m (1%)      0 (0%)      10Mi (0%)        0 (0%)         15m
  kube-system                 ebs-csi-node-z7zkh                     0 (0%)        0 (0%)      0 (0%)           0 (0%)         14m
  kube-system                 kube-proxy-i-09e153e15590ea358         100m (5%)     0 (0%)      0 (0%)           0 (0%)         13m
Allocated resources:
  (Total limits may be over 100 percent, i.e., overcommitted.)
  Resource           Requests     Limits
  --------           --------     ------
  cpu                1945m (97%)  0 (0%)
  memory             150Mi (4%)   340Mi (9%)
  ephemeral-storage  0 (0%)       0 (0%)
  hugepages-1Gi      0 (0%)       0 (0%)
  hugepages-2Mi      0 (0%)       0 (0%)

늘어나긴 헀지만 그래도 원하는 수량 30개를 다 만들어내지는 못하고 있다.
그렇다면 파드 생성 갯수를 늘리기 위한 작업을 하나씩 해보자!

링크)Limit Ranges

링크)Amazon EC2 노드에 사용 가능한 IP 주소의 양 늘리기

  1. 컨테이너 최소 CPU 제한 해제하기
# 쿠버네티스에서 컨테이너를 생성할 때 마다 최소 0.1 CPU를 보장한다(100M)
# LimitRange를 확인한다
kubectl describe limitranges
Name:       limits
Namespace:  default
Type        Resource  Min  Max  **Default Request**  Default Limit  Max Limit/Request Ratio
----        --------  ---  ---  ---------------  -------------  -----------------------
**Container**   **cpu**       -    -    **100m**             -              -

# 파드 갯수 0으로 줄이기
kubectl scale deployment nginx-deployment --replicas=0

# LimitRanges 기본 정책 삭제
kubectl delete limitranges limits
kubectl get limitranges
  1. kops 클러스터 수정하기
	# 클러스터의 aws-node 상세 정보를 확인한다
kubectl describe ds -n kube-system aws-node

>
ADDITIONAL_ENI_TAGS:                    {"KubernetesCluster":"play-board.net","kubernetes.io/cluster/play-board.net":"owned"}
      AWS_VPC_CNI_NODE_PORT_SUPPORT:          true
      AWS_VPC_ENI_MTU:                        9001
      AWS_VPC_K8S_CNI_CONFIGURE_RPFILTER:     false
      AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG:     false
      AWS_VPC_K8S_CNI_EXTERNALSNAT:           false
      AWS_VPC_K8S_CNI_LOGLEVEL:               DEBUG
      AWS_VPC_K8S_CNI_LOG_FILE:               /host/var/log/aws-routed-eni/ipamd.log
      AWS_VPC_K8S_CNI_RANDOMIZESNAT:          prng
      AWS_VPC_K8S_CNI_VETHPREFIX:             eni
      AWS_VPC_K8S_PLUGIN_LOG_FILE:            /var/log/aws-routed-eni/plugin.log
      AWS_VPC_K8S_PLUGIN_LOG_LEVEL:           DEBUG
      DISABLE_INTROSPECTION:                  false
      DISABLE_METRICS:                        false
      DISABLE_NETWORK_RESOURCE_PROVISIONING:  false
      ENABLE_IPv4:                            true
      ENABLE_IPv6:                            false
      ENABLE_POD_ENI:                         false
      **ENABLE_PREFIX_DELEGATION:               false # 네트워크 인터페이스에 Prefix를 할당하지 않은 상태**
      WARM_ENI_TARGET:                        1
      **WARM_PREFIX_TARGET:                     1 # 체크**
      MY_NODE_NAME:                            (v1:spec.nodeName)
      CLUSTER_NAME:                           play-board.net
# kops에서 클러스터를 수정한다: maxPods 110개로 수정, Prefix Assign 활성화
kops edit cluster
>
...
  kubelet:
    anonymousAuth: false
    maxPods: 110
...
  networking:
    amazonvpc:
      env:
      - name: ENABLE_PREFIX_DELEGATION
        value: "true"

# 클러스터를 업데이트한다
kops update cluster --yes && echo && sleep 5 && kops rolling-update cluster --yes

# Deployment 생성
kubectl scale deployment nginx-deployment **--replicas=110**

인스턴스 타입을 바꾸고, Limitrange 제한을 풀고, Prefix 할당을 허용한 후에 파드를 100개 생성할 수 있었다.

 

 

Kubernetes 서비스

서비스(Service)의 종류

파드에서 실행중인 애플리케이션을 네트워크 서비스로 노출하기 위한 것

ClusterIP

  • 클러스터 내부에서만 접근 가능하며 외부에서 접근하기 위해서는 port forwarding/proxy를 통해 접근해야 함 접근할 때는 Cluster-IP로만 접근할 수 있음
    • 서비스(ClusterIP) 생성 시, apiserver → kube-proxy → iptables 에 rule이 생성
    • iptables rule 이 설정되므로, 파드에서 접속 시 해당 노드에 존재하는 iptables rule 에 의해서 분산 접속이 됨

NodePort

  • 노드의 특정 포트(NodrPort)를 이용하여 접근할 수 있도록 설정하는 방법
  • 외부에서 노드 IP:노드포트 로 접근할 수 있다.
  • 해당 노드의 iptables 룰에 의해서 SNAT/DNAT 되어 목적지 파드와 통신 후 리턴 트래픽은 최초 인입 노드를 경유해서 외부로 되돌아감

LoadBalancer

  • 앞단에 Loadbalancer가 붙어서 살아있는 노드를 체크하여 트래픽을 전달
  • 외부에선 노드에 직접 접속할 수 없고 로드밸런서에만 접속할 수 있다
  • 파드가 있는 노드들에만 로드밸런싱을 하며 iptables 룰에서는 자신의 노드에 있는 파드만 연결 로드밸런서와 iptables에서 각각 한 번씩 DNAT가 실시된다.

EC2 instance profiles 설정

마스터/워커 노드에 EC2 IAM Role Policy (AWSLoadBalancerControllerIAMPolicy) 추가

curl -o **iam_policy.json** <https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/**v2.4.5**/docs/install/iam_policy.json>
aws iam create-policy --policy-name **AWSLoadBalancerControllerIAMPolicy** --policy-document file://**iam_policy.json**

계정 Account ID 변수 지정

ACCOUNT_ID=`aws sts get-caller-identity --query 'Account' --output text`
echo $ACCOUNT_ID

>> 859621957271

생성된 IAM Policy 확인

aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/**AWSLoadBalancerControllerIAMPolicy** --query 'Policy.Arn'

>> "arn:aws:iam::859621957271:policy/AWSLoadBalancerControllerIAMPolicy"

EC2 instance profiles 이름 확인

aws iam list-instance-profiles --query 'InstanceProfiles[*].InstanceProfileName'

>> [
    "aws-elasticbeanstalk-ec2-role",
    "ecr-full",
    "ecsInstanceRole",
    "eksworkspace-admin",
    **"masters.play-board.net",
    "nodes.play-board.net"**
]

EC2 instance profiles 에 IAM Policy 추가
– 위에 만든 로드밸런스IAM에 마스터와 워커 노드 클러스터 네임을 바인딩한다

aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name masters.$**KOPS_CLUSTER_NAME**
aws iam attach-role-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --role-name nodes.$**KOPS_CLUSTER_NAME**

kOps 클러스터 인증서 구성을 위한 cert-manager를 설치

kops edit cluster --name ${KOPS_CLUSTER_NAME}

# 아래 내용을 추가한다
spec:
  **certManager:
    enabled: true
  awsLoadBalancerController:
    enabled: true**

AWS LoadBalancer 배포

앞서 IAM을 포함한 설정이 완료되었다면 클러스터를 다시 기동하여 업데이트를 적용한다

kops update cluster --yes && echo && sleep 5 && kops rolling-update cluster

NLB 배포 테스트

테스트를 위해 서비스와 Deployment를 생성한다

kubectl apply -f ~/pkos/2/echo-service-nlb.yaml

echo-service-nlb.yaml

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 elbv2 describe-load-balancers

> "State": {
                "Code": "active"
            },

 aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text 을 치면 Provisioning인지 Active인지 알 수 있음. 스터디 자료에 이런식으로 친절하게 정리되어 있는데, 나는 아직 원시인 레벨이라서 이런 쿼리에 익숙하지 않아 더 열심히 해야겠다는 생각이 막 들었다 이상 잡담 끝

해당 로드밸런서의 웹 주소 확인

# 아래의 명령어를 입력하고 External IP를 복사하여 사용하거나
kubectl get svc svc-nlb-ip-type

# 아래 명령어를 실행한다
kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "http://"$1 }'

> <http://k8s-default-svcnlbip-cf064ae967-c9bfbe1079f59c4c.elb.ap-northeast-2.amazonaws.com>

# 로드밸런서 주소를 변수로 선언
NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})

로드밸런서로 요청(curl)을 보내고 파드의 로그를 확인해본다

while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done

1초 단위로 계속 curl을 보낼 경우 두 개의 파드에 트래픽이 정상적으로 분기되는 것을 확인할 수 있다.

만약 파드 수를 늘리거나 줄이면 어떻게될까?

# 파드 수를 줄이는 경우
kubectl scale deployment deploy-echo --replicas=1
# 테스트
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr

# 파드 수를 늘리는 경우
kubectl scale deployment deploy-echo --replicas=3
# 테스트
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr

분기가 잘 되는 것을 확인할 수 있다

 

 

ExternalDNS

ExternalDNS란?

Public한 도메인서버(AWS Route53, GCP DNS 등)를 사용하여 쿠버네티스의 리소스를 쿼리할 수 있게 해주는 오픈소스 솔루션

ExternalDNS 설치하기

기본 DNS 서버 수량을 조정한다

kubectl delete deploy -n kube-system coredns-autoscaler
kubectl scale deploy -n kube-system coredns --replicas 1

관련 IAM 정책을 생성한 뒤 마스터/워커 노드에 바인딩한다

curl -s -O <https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/AKOS/externaldns/externaldns-aws-r53-policy.json>
aws iam create-policy --policy-name AllowExternalDNSUpdates --policy-document file://externaldns-aws-r53-policy.json

export POLICY_ARN=$(aws iam list-policies --query 'Policies[?PolicyName==`AllowExternalDNSUpdates`].Arn' --output text)

EC2에 정책을 바인딩한다

aws iam attach-role-policy --policy-arn $POLICY_ARN --role-name masters.$**KOPS_CLUSTER_NAME**
aws iam attach-role-policy --policy-arn $POLICY_ARN --role-name nodes.$**KOPS_CLUSTER_NAME**

프로그램 설치(kops edit cluster) : 아래 내용을 추가한다

spec:
  externalDns:
    provider: external-dns

서버 재설정 및 업데이트 시작

kops update cluster --yes && echo && sleep 5 && kops rolling-update cluster

ExternalDNS 설정하기

# 테스트용 파드를 생성한다
kubectl apply -f ~/pkos/2/echo-service-nlb.yaml

도메인 정보 입력

# 변수로 도메인 이름을 입력한다
MyDOMAIN1=nginx.play-board.net
# 서비스 Annotation에 도메인이 추가된 것을 확인한다
kubectl annotate service svc-nlb-ip-type "external-dns.alpha.kubernetes.io/hostname=$MyDOMAIN1."

분산 접속을 확인한다

for i in {1..100}; do curl -s $MyDOMAIN1 | grep Hostname ; done | sort | uniq -c | sort -nr

>> for i in {1..100}; do curl -s $MyDOMAIN1 | grep Hostname ; done | sort | uniq -c | sort -nr
     50 Hostname: deploy-echo-5c4856dfd6-zvcnf
     50 Hostname: deploy-echo-5c4856dfd6-cqh74