본문 바로가기
DevOps

PKOS 스터디 2주차: 쿠버네티스 네트워크

by Canary_카나리아 2023. 1. 29.

지난 1월부터 가시다님이 운영하시는 쿠버네티스 스터디에 참여하게 되었는데, 스터디 2주차의 내용은 쿠버네티스 네트워크에 대해 실습을 통해 알아보는 것이다.

1. AWS VPC CNI

  • CNI(Container Network Interface): 쿠버네티스의 네트워크 환경을 구성해주는 요소이며, 서로 다른 노드의 파드간 통신 등을 지원해준다. 기본 쿠버네티스의 네트워크 플러그인은 제약사항이 많아 서드파티의 플러그인을 별도로 설치해 클러스터를 운영하게 된다.

CNI에는 다양한 종류가 있는데, 이번 실습에서는 AWS VPC CNI를 활용해서 실습을 진행했다. AWS VPC CNI의 특징은 여러 가지가 있지만, 기본적으로 파드의 IP 대역과 워커노드의 IP대역이 같아 일반적으로 오버레이 통신(VXLAN, IP-IP)을 하는 다른 CNI와 달리, 직접 통신이 가능하다는 특징이 있다(별도 구성으로 파드-노드 IP대역을 다른 대역으로 설정도 가능). AWS에서 만든 CNI인 만큼 VPC의 다른 기능들(VPC Flow Logs, VPC routing policy, Security Group for Pod)과 호환이 가능하다.

(참고: kOps로 쿠버네티스 클러스터를 구축한 경우는 Security Group for Pod 기능을 지원하지 않는다)

AWS 환경에서 운영되는 쿠버네티스 클러스터의 경우 EC2 인스턴스 유형에 따라 해당 워커노드(EC2 인스턴스) 생성가능한 최대 파드 수가 한정되어있으며, 아래 두 가지 요소에 의해 최대 할당 파드 수를 계산할 수 있다.

  • Secondary IPv4 addresses: 인스턴스 유형별 최대 ENI 개수 및 할당 가능 IP 수 조합해 산정
  • IPv4 Prefix Delegation: IPv4 28bit 서브넷(prefix)를 위임하여 할당 가능. IP수와 인스턴스 유형에서 권장하는 최대 개수로 선정.

💡 최대 파드 생성 갯수 : (Number of network interfaces for the instance type × (the number of IP addressess per network interface - 1)) + 2

t3 인스턴스 타입 정보 확인

```bash
(canary:default) [root@kops-ec2 ~]# aws ec2 describe-instance-types --filters Name=instance-type,Values=t3.* \
>  --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
>  --output table
--------------------------------------
|        DescribeInstanceTypes       |
+----------+----------+--------------+
| IPv4addr | MaxENI   |    Type      |
+----------+----------+--------------+
|  15      |  4       |  t3.xlarge   |
|  15      |  4       |  t3.2xlarge  |
|  2       |  2       |  t3.nano     |
|  6       |  3       |  t3.medium   |
|  12      |  3       |  t3.large    |
|  2       |  2       |  t3.micro    |
|  4       |  3       |  t3.small    |
+----------+----------+--------------+
```

```bash
(canary:default) [root@kops-ec2 ~]# aws ec2 describe-instance-types --filters Name=instance-type,Values=c5.* \
>  --query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
>  --output table
---------------------------------------
|        DescribeInstanceTypes        |
+----------+----------+---------------+
| IPv4addr | MaxENI   |     Type      |
+----------+----------+---------------+
|  30      |  8       |  c5.4xlarge   |
|  15      |  4       |  c5.xlarge    |
|  50      |  15      |  c5.24xlarge  |
|  50      |  15      |  c5.metal     |
|  30      |  8       |  c5.12xlarge  |
|  15      |  4       |  c5.2xlarge   |
|  10      |  3       |  c5.large     |
|  30      |  8       |  c5.9xlarge   |
|  50      |  15      |  c5.18xlarge  |
+----------+----------+---------------+
```

파드 사용 가능 갯수 계산 예시

  • 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개

과제 1: 파드간 통신 시 tcpdump 내용 확인

AWS VPC CNI 플러그인을 사용하는 쿠버네티스 클러스터를 배포한 뒤, 파드 간 통신 내역을 tcpdump로 확인하면, NAT 동작 없이 통신이 가능함을 확인할 수 있다.

ubuntu@i-010267eb4ee7c4c34:~$ sudo tcpdump -i ens5 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on ens5, link-type EN10MB (Ethernet), capture size 262144 bytes
11:59:28.906370 IP 172.16.37.110 > 172.16.78.194: ICMP echo request, id 35039, seq 1, length 64
11:59:28.906580 IP 172.16.78.194 > 172.16.37.110: ICMP echo reply, id 35039, seq 1, length 64
11:59:29.907513 IP 172.16.37.110 > 172.16.78.194: ICMP echo request, id 35039, seq 2, length 64
11:59:29.907626 IP 172.16.78.194 > 172.16.37.110: ICMP echo reply, id 35039, seq 2, length 64
^C
4 packets captured
4 packets received by filter
0 packets dropped by kernel
  • 내용을 확인해보면 출발지 Pod IP 172.16.37.110, 목적지 Pod IP 172.16.78.194를 그대로 달고 나가는 것을 확인할 수 있다.

과제 2: 워커 노드 1대에 100대이상의 파드가 배포되도록 설정

워커노드 1대에 100대의 파드가 배포가 되게 하려면 아래의 방법들을 활용할 수 있다.

  • Prefix Delegation
  • WARM & MIN IP/Prefix Targets
  • Worker Node Instance scale-up (스펙 올리기)

이 중 Prefix Delegation을 사용해 100대의 파드가 배포가 되도록 설정해보자.

워커노드 인스턴스 타입을 c5.large타입으로 변경하고, 워커노드 개수 1개로 변경해 배포한다.

aws cloudformation deploy --template-file **kops-oneclick.yaml** --stack-name **mykops** --parameter-overrides KeyName=**kp-canary** SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32  MyIamUserAccessKeyID=(액세스키) MyIamUserSecretAccessKey=**'(시크릿액세스키)'** ClusterBaseName=**'canary.k8s.local'** S3StateStore=**'**canary-k8s-s3**'** **WorkerNodeInstanceType=c5.large** **WorkerNodeCount=1** --region ap-northeast-2

배포된 노드의 인스턴스 타입을 확인한다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe nodes | grep "node.kubernetes.io/instance-type"
                    node.kubernetes.io/instance-type=c5.large
                    node.kubernetes.io/instance-type=c5.large

노드 Allocatable 정보를 확인해 최대 생성 가능한 파드 개수를 확인한다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe node | grep Allocatable: -A6
Allocatable:
  cpu:                2
  ephemeral-storage:  119703055367
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3698676Ki
  pods:               29
--
Allocatable:
  cpu:                2
  ephemeral-storage:  59763732382
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3698676Ki
  pods:               29

env 정보를 확인해보자.

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe ds -n kube-system aws-node | grep ADDITIONAL_ENI_TAGS: -A22
      ADDITIONAL_ENI_TAGS:                    {"KubernetesCluster":"canary2.k8s.local","kubernetes.io/cluster/canary2.k8s.local":"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
      WARM_ENI_TARGET:                        1
      WARM_PREFIX_TARGET:                     1
      MY_NODE_NAME:                            (v1:spec.nodeName)
      CLUSTER_NAME:                           canary2.k8s.local
  • WARM_PREFIX_TARGET은 기본값이 이미 1로 설정되어있다.

노드의 External IP를 확인한다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl get node -owide
NAME                  STATUS   ROLES           AGE     VERSION   INTERNAL-IP     EXTERNAL-IP     OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
i-039ef05788a7d072d   Ready    node            3m7s    v1.24.9   172.16.58.198   43.201.85.252   Ubuntu 20.04.5 LTS   5.15.0-1026-aws   containerd://1.6.10
i-0c286056be962013b   Ready    control-plane   4m31s   v1.24.9   172.16.58.71    3.36.109.57     Ubuntu 20.04.5 LTS   5.15.0-1026-aws   containerd://1.6.10

kOps 클러스터와 인스턴스 그룹의 파라미터를 수정한다.

**kops edit cluster**
...
  kubelet:
    anonymousAuth: false
    **maxPods: 110**
...
  networking:
    amazonvpc:
      **env:
      - name: ENABLE_PREFIX_DELEGATION
        value: "true"**
  • kubelet maxPods 110개로 수정
  • Prefix Assign 활성화

변경사항을 반영하기 위해 노드를 롤링 업데이트를 진행 해준다(필수).

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

롤링 업데이트가 진행이 잘 안될 경우 아래 명령어를 실행해준다.

(canary2:N/A) [root@kops-ec2 ~]# kops rolling-update cluster --cloudonly --yes

롤링 업데이트 시 IP주소가 변경된다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl get node -owide
NAME                  STATUS   ROLES           AGE     VERSION   INTERNAL-IP     EXTERNAL-IP      OS-IMAGE             KERNEL-VERSION    CONTAINER-RUNTIME
i-010753dfa2afbf864   Ready    node            8m46s   v1.24.9   172.16.42.44    13.125.104.216   Ubuntu 20.04.5 LTS   5.15.0-1026-aws   containerd://1.6.10
i-07dff82ae7433cd62   Ready    control-plane   11m     v1.24.9   172.16.40.100   3.39.0.88        Ubuntu 20.04.5 LTS   5.15.0-1026-aws   containerd://1.6.10

변경정보가 반영된 env 정보를 확인해보자.

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe ds -n kube-system aws-node | grep ADDITIONAL_ENI_TAGS: -A22
      ADDITIONAL_ENI_TAGS:                    {"KubernetesCluster":"canary2.k8s.local","kubernetes.io/cluster/canary2.k8s.local":"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:               true
      WARM_ENI_TARGET:                        1
      WARM_PREFIX_TARGET:                     1
      MY_NODE_NAME:                            (v1:spec.nodeName)
      CLUSTER_NAME:                           canary2.k8s.local

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe daemonsets.apps -n kube-system aws-node | egrep 'ENABLE_PREFIX_DELEGATION|WARM_PREFIX_TARGET'
      ENABLE_PREFIX_DELEGATION:               true
      WARM_PREFIX_TARGET:                     1

노드 Allocatable 정보를 확인해 최대 생성 가능한 파드 개수를 다시 확인한다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl describe node | grep Allocatable: -A6
Allocatable:
  cpu:                2
  ephemeral-storage:  119703055367
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3698676Ki
  pods:               110
--
Allocatable:
  cpu:                2
  ephemeral-storage:  59763732382
  hugepages-1Gi:      0
  hugepages-2Mi:      0
  memory:             3698676Ki
  pods:               110

추가: limit range를 삭제해 CPU 부족 현상을 방지한다.

(canary2:N/A) [root@kops-ec2 ~]# kubectl delete limitranges limits
limitrange "limits" deleted
(canary2:N/A) [root@kops-ec2 ~]# kubectl get limitranges
No resources found in default namespace.

파드를 100개 배포해보자.

(canary2:N/A) [root@kops-ec2 ~]# cat ~/pkos/2/nginx-dp.yaml | yh
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:alpine
        ports:
        - containerPort: 80

(canary2:N/A) [root@kops-ec2 ~]# kubectl apply -f ~/pkos/2/nginx-dp.yaml
deployment.apps/nginx-deployment created

100개가 잘 배포된 것을 확인할 수 있다.

Every 2.0s: kubectl get deploy                          Sun Jan 29 00:40:39 2023

NAME               READY     UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   100/100   100          100         103s

(canary2:N/A) [root@kops-ec2 ~]# kubectl get pod -A -o name | wc -l
118

과제 3: 서비스(NLB)/파드 배포 시 NLB를 통해 애플리케이션에 접속

클러스터 외부에서의 접속을 확인해보기 위해 쿠버네티스의 Service를 배포해 파드에 접속할 수 있는데, AWS Load Balancer Controller라는 컨트롤러를 클러스터에 설치하게 되면, Service 혹은 Ingress 배포 시 로드밸런서가 자동으로 프로비저닝 된다. 이번 실습에서는 Service를 생성해 NLB를 배포해 보았다. NLB 배포 시 ExternalDNS를 설정하면 해당 도메인으로 접속할 수 있는데, 나는 Gossip DNS로 실습을 진행했기 때문에 NLB주소로 바로 접속 테스트를 진행해보았다.

null
null

과제 4: NLB IP Target & Proxy Protocol v2 활성화

마지막으로는 NLB의 타겟 타입을 IP로, Proxy Protocol v2를 활성화해서 Client IP를 확인할 수 있는 설정을 진행했다. 실습에 사용한 컨테이너 이미지는 이 링크(image)를 활용했다.

Deployment와 Service를 생성한다.

(canary:vote) [root@kops-ec2 apache]# cat <<EOF> canary-deployment.yaml
> apiVersion: apps/v1
> kind: Deployment
> metadata:
>   name: canary-app
> spec:
>   replicas: 2
>   selector:
>     matchLabels:
>       app: httpd
>   template:
>     metadata:
>       labels:
>         app: httpd
>     spec:
>       containers:
>         - name: httpd
>           image: gasida/httpd:pp
>           ports:
>             - name: http
>               containerPort: 80
> EOF
(canary:vote) [root@kops-ec2 apache]# kubectl apply -f canary-deployment.yaml
deployment.apps/canary-app created
(canary:vote) [root@kops-ec2 apache]# cat <<EOF> canary-service.yaml
> apiVersion: v1
> kind: Service
> metadata:
>   name: canary-service
>   annotations:
>     service.beta.kubernetes.io/aws-load-balancer-type: external
>     service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
>     service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
>     service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
> spec:
>   ports:
>     - port: 80
>       targetPort: 80
>       protocol: TCP
>   type: LoadBalancer
>   selector:
>     app: httpd
> EOF
(canary:vote) [root@kops-ec2 apache]# kubectl apply -f canary-service.yaml
service/canary-service created
  • Service를 생성할 때, 어노테이션에 아래와 같은 설정을 추가했다.
    • service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip ⇒ NLB의 타겟 타입을 IP로 지정하는 설정
    • service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*" ⇒ Proxy-Protocol v2를 활성화 하는 설정 (상세 설정값 이 링크 참고)

Service 및 관련 리소스(NLB 및 Target Group 등)들이 잘 생성되었는지 확인한다.

(canary:vote) [root@kops-ec2 apache]# k get svc canary-service
NAME             TYPE           CLUSTER-IP      EXTERNAL-IP                                                                      PORT(S)        AGE
canary-service   LoadBalancer   100.64.119.74   k8s-vote-canaryse-622aff3ce4-34f9ff8f1f256032.elb.ap-northeast-2.amazonaws.com   80:31284/TCP   86s

null

배포한 앱의 로그를 모니터링하는 명령어를 실행해둔다.

(canary:vote) [root@kops-ec2 ~]# kubectl logs -l app=httpd -f

로컬 PC에서 curl로 앱에 접속하는 테스트를 진행한다.

(base) ➜  ~ curl -s ipinfo.io/ip
220.78.50.1
(base) ➜  ~ curl k8s-vote-canaryse-622aff3ce4-34f9ff8f1f256032.elb.ap-northeast-2.amazonaws.com
<html><body><h1>It works!</h1></body></html>
(base) ➜  ~ curl k8s-vote-canaryse-622aff3ce4-34f9ff8f1f256032.elb.ap-northeast-2.amazonaws.com
<html><body><h1>It works!</h1></body></html>

로그에 로컬 PC의 Client IP가 그대로 찍히는 것을 확인할 수 있다.

(canary:vote) [root@kops-ec2 ~]# kubectl logs -l app=httpd -f
220.78.50.1 - - [28/Jan/2023:13:27:01 +0000] "GET / HTTP/1.1" 200 45
220.78.50.1 - - [28/Jan/2023:13:27:02 +0000] "GET / HTTP/1.1" 200 45

댓글