1
2
AWS EKS Workshop Study (=AEWS)는 EKS Workshop 실습 스터디입니다.
CloudNet@ Gasida(가시다)님이 진행하시며,공개된 AWS EKS Workshop을 기반으로 진행하고 있습니다.

EKS VPC CNI

CNI(Container Network Interface) 란 네트워크 플러그인 인터페이스로 k8s 네트워크 환경을 구성해준다. EKS 에서는 기본 CNI로 VPC를 사용한다. 여기서 VPC는 AWS 네트워크 서비스인 AWS VPC로 VPC CIDR을 이용하여 파드 IP 할당 및 통신을 지원한다.

vpc1.png

EKS 에서 CNI를 VPC로 사용하면 대표적으로 얻을 수 있는 이점은 두 가지이다.

  • 노드 대역과 파드 대역이 동일하여 다른 노드간 통신시 오버레이가 없어 통신 오버헤드가 감소한다.

    vpc2.png

    다른 CNI(Caclico)는 노드와 파드 IP 대역이 달라 노드 간 통신에 오버레이를 사용하는 반면 VPC CNI는 대역이 같아 오버레이없이 원본 패킷 그대로 통신한다.

  • AWS VPC 의 연동 서비스 (VPC 서브넷, 보안 그룹)를 사용하여 트래픽 제어가 가능하다.

네트워크 세부 정보 확인하기

VPC CNI에 대해 세부적으로 확인해보겠다. VPC CNI는 aws-cni 파드를 통해 각 노드에 배포된다. 이를 확인하여 네트워크 기본 정보를 확인하겠다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# CNI 버전 정보 확인
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
---
amazon-k8s-cni-init:v1.12.6-eksbuild.1
amazon-k8s-cni:v1.12.6-eksbuild.1

# VPC MODE 확인 
kubectl describe cm -n kube-system kube-proxy-config | grep mode
---
mode: "iptables"

# 노드 IP 및 PUBLIC IP 확인
aws ec2 describe-instances --query "Reservations[*].Instances[*].{PublicIPAdd:PublicIpAddress,PrivateIPAdd:PrivateIpAddress,InstanceName:Tags[?Key=='Name']|[0].Value,Status:State.Name}" --filters Name=instance-state-name,Values=running --output table
---
-------------------------------------------------------------------------
|                           DescribeInstances                           |
+------------------------+----------------+------------------+----------+
|      InstanceName      | PrivateIPAdd   |   PublicIPAdd    | Status   |
+------------------------+----------------+------------------+----------+
|  hanhorang-ng1-Node    |  192.168.3.162 |  15.164.220.246  |  running |
|  hanhorang-ng1-Node    |  192.168.2.16  |  3.34.30.94      |  running |
|  hanhorang-bastion-EC2 |  192.168.1.100 |  13.125.121.220  |  running |
|  hanhorang-ng1-Node    |  192.168.1.5   |  3.36.127.19     |  running |
+------------------------+----------------+------------------+----------+ 

# 
kubectl get pods -n kube-system -o wide 
---
NAME                                            READY   STATUS    RESTARTS   AGE    IP              NODE                                               NOMINATED NODE   READINESS GATES
aws-load-balancer-controller-7857849d69-qc9nr   1/1     Running   0          146m   192.168.1.190   ip-192-168-1-5.ap-northeast-2.compute.internal     <none>           <none>
aws-load-balancer-controller-7857849d69-qjvkk   1/1     Running   0          146m   192.168.3.186   ip-192-168-3-162.ap-northeast-2.compute.internal   <none>           <none>
aws-node-l2twq                                  1/1     Running   0          170m   192.168.1.5     ip-192-168-1-5.ap-northeast-2.compute.internal     <none>           <none>
aws-node-tj9xl                                  1/1     Running   0          170m   192.168.3.162   ip-192-168-3-162.ap-northeast-2.compute.internal   <none>           <none>
aws-node-v9m8g                                  1/1     Running   0          170m   192.168.2.16    ip-192-168-2-16.ap-northeast-2.compute.internal    <none>           <none>
coredns-6777fcd775-2qq92                        1/1     Running   0          168m   192.168.2.227   ip-192-168-2-16.ap-northeast-2.compute.internal    <none>           <none>
coredns-6777fcd775-rhqdp                        1/1     Running   0          168m   192.168.1.254   ip-192-168-1-5.ap-northeast-2.compute.internal     <none>           <none>
external-dns-6b5bbbf9d-l7ms2                    1/1     Running   0          143m   192.168.2.221   ip-192-168-2-16.ap-northeast-2.compute.internal    <none>           <none>
kube-proxy-bbgr9                                1/1     Running   0          168m   192.168.3.162   ip-192-168-3-162.ap-northeast-2.compute.internal   <none>           <none>
kube-proxy-nm8ls                                1/1     Running   0          169m   192.168.2.16    ip-192-168-2-16.ap-northeast-2.compute.internal    <none>           <none>
kube-proxy-xm2qq                                1/1     Running   0          169m   192.168.1.5     ip-192-168-1-5.ap-northeast-2.compute.internal     <none>           <none>**
  • VPC CNI Mode로 userspace ,iptables , ipvs 모드가 존재하나, 보통 iptable 모드를 사용한다. 이유는 userspace 경우, 느리고 비효율적이여서 거의 사용되지 않은 상태이다. 반면 ipvs 의 경우, L4 기반의 로드밸런서로 매우 빠른 이점이 있지만 호환성 문제로 추가 설정이 필요하고 커뮤니티 규모가 적어 트러블슈팅이 동반되어 기본적으로 사용하지 않는다.

이어서 각 노드에 접속하여 네트워크 정보를 확인해보겠다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# 각 노드 IP 확인 및 변수 설정 
kubectl get nodes -o wide 
---
NAME                                               STATUS   ROLES    AGE     VERSION                INTERNAL-IP     EXTERNAL-IP      OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
ip-192-168-1-5.ap-northeast-2.compute.internal     Ready    <none>   3h10m   v1.24.11-eks-a59e1f0   192.168.1.5     3.36.127.19      Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19
ip-192-168-2-16.ap-northeast-2.compute.internal    Ready    <none>   3h10m   v1.24.11-eks-a59e1f0   192.168.2.16    3.34.30.94       Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19
ip-192-168-3-162.ap-northeast-2.compute.internal   Ready    <none>   3h10m   v1.24.11-eks-a59e1f0   192.168.3.162   15.164.220.246   Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19

# 위 External IP 확인하여 노드별 변수 설정
N1=3.36.127.19
N2=3.34.30.94
N3=15.164.220.246

# 각 노드에 툴 설치
ssh ec2-user@$N1 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N2 sudo yum install links tree jq tcpdump -y
ssh ec2-user@$N3 sudo yum install links tree jq tcpdump -y

# 네트워크 CNI 로그 경로 확인
ssh ec2-user@$N1 tree /var/log/aws-routed-eni
---
/var/log/aws-routed-eni
├── egress-v4-plugin.log # 이그래스 트래픽 관련 로그 파일 
├── ipamd.log # eni IP 주소 할당, 해제, 그리고 오류 등과 관련된 이벤트와 상태 변경 로그 파일 
└── plugin.log # 파드의 네트워크 인터페이스 설정, 트래픽 관리, 오류 처리 등과 관련된 이벤트와 상태 변경 로그 파일**

# eni IP 주소 할당, 해제, 그리고 오류 등과 관련된 이벤트와 상태 변경 로그 파일
ssh ec2-user@$N1 cat /var/log/aws-routed-eni/ipamd.log | jq
---
{
  "level": "info",
  "ts": "2023-05-08T07:16:00.757Z",
  "caller": "ipamd/ipamd.go:1599",
  "msg": "Adding 192.168.1.190/32 to DS for eni-0d79c0daf04234458"
}
{
  "level": "info",
  "ts": "2023-05-08T07:16:00.757Z",
  "caller": "ipamd/ipamd.go:1599",
  "msg": "IP already in DS"
}
{
  "level": "info",
  "ts": "2023-05-08T07:16:00.757Z",
  "caller": "ipamd/ipamd.go:1475",
  "msg": "Trying to add 192.168.1.145"
}
{
  "level": "info",
  "ts": "2023-05-08T07:16:00.757Z",
  "caller": "ipamd/ipamd.go:1599",
  "msg": "Adding 192.168.1.145/32 to DS for eni-0d79c0daf04234458"
}
...

# 파드의 네트워크 인터페이스 설정, 트래픽 관리, 오류 처리 등과 관련된 이벤트와 상태 변경 로그 파일
ssh ec2-user@$N1 cat /var/log/aws-routed-eni/plugin.log | jq
---
{
  "level": "debug",
  "ts": "2023-05-08T05:59:41.741Z",
  "caller": "driver/driver.go:281",
  "msg": "Successfully disabled IPv6 RA and ICMP redirects on hostVeth eni25c78048487"
}
{
  "level": "debug",
  "ts": "2023-05-08T05:59:41.741Z",
  "caller": "driver/driver.go:297",
  "msg": "Successfully setup container route, containerAddr=192.168.1.190/32, hostVeth=eni25c78048487, rtTable=main"
}
{
  "level": "debug",
  "ts": "2023-05-08T05:59:41.741Z",
  "caller": "driver/driver.go:297",
  "msg": "Successfully setup toContainer rule, containerAddr=192.168.1.190/32, rtTable=main"
}
...

직접 라우터 및 네트워크 인터페이스를 확인하여 파드 IP정보를 확인할 수 있다.

vpc5.png

  • 빨간 네모는 노드 IP로 프라이빗 IPv4 주소가 할당되어 있음을 확인할 수 있다. 또한 노란 네모 및 선이 파드 할당이 되면 생기는 IP로 보조 프라이빗 IPv4 주소가 할당됨을 알 수 있다.
  • 노드에 파드가 할당되면 네트워크인터페이스가 eniY@N 의 네트워크 인터페이스가 할당된다.
  • 라우터 정보를 확인했을 때 노드 1의 CIDR이 192.168.1.0/24 로 해당 IP 대역의 트래픽을 받으면 192.168.1.5 로 통신됨을 확인할 수 있다.

통신 흐름 확인하기

이렇게 할당된 파드의 IP가 어떻게 통신하는지 파드간 통신과 외부 통신을 나눠 통신 흐름을 확인해보자. 먼저 클러스터 내부 파드간 통신을 확인하겠다. 테스트 파드를 3개 배포하여 파드 1과 파드2의 IP 통신흐름을 확인할 것이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 테스트 파드 배포 
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 3
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
EOF

# 배포 확인 
kubectl get pods -A 
----
NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
default       netshoot-pod-7757d5dd99-9dr8p                   1/1     Running   0          90s
default       netshoot-pod-7757d5dd99-jw4z4                   1/1     Running   0          90s
default       netshoot-pod-7757d5dd99-tgwj7                   1/1     Running   0          90s

내부 통신은 tcpdump 의 패킷 덤프를 통해 통신 과정을 확인할 수 있다. 파드1에 접속하여 파드2에 직접 트래픽을 쏴서 통신 흐름을 확인하자

vpc6.png

  • 패킷에서와 같이 오버레이없이 파드에 할당된 IP로 직접 통신함을 알 수 있다.

이어서 외부 통신을 진행해보자. 외부 통신은 SNAT을 통해 진행하게 되는데 SNAT의 경우 iptable의 룰을 통해 확인할 수 있다.

Vpc7.png

  • Iptable 룰에 따라 AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1 에 매칭되어, 목적지가 192.168.0.0/16 아니고 외부 빠져나갈때 SNAT 192.168.1.5로 변경되어 나간다

파드 안에서 외부 네트워크로 통신을 하여 통신 흐름을 확인하자

vpc8.png

  • 통신 패킷을 통해 확인하면 iptable의 룰과 같이 192.168.1.5로 IP가 SNAT되어 통신됨을 알 수 있다.

네트워크 Addon 설치(ALB, External DNS)

이어서 EKS에서 제공하는 네트워크 애드온 중 ALB Controller와 External DNS를 살펴보고 배포하겠다. 각 addon을 간략하게 요약해서 표로 나타내면 다음과 같다.

Addon 서비스 사용 AWS 서비스 목적
Load Balancer Controller AWS Elastic Load Balancer(ELB) 네트워크 로드밸런서(L4, L7) 사용
External DNS Controller AWS Route53 DNS 도메인 연동 및 사용

표로 확인할 수 있겠지만 네트워크 addon들은 AWS 네트워크 서비스(ELB, Route53)을 연동해서 사용한다. 그렇기 때문에 Addon 설치시 AWS 서비스를 사용하기 위한 보안 정책(IAM) 연동이 필요하다. 이번 글에서는 IAM 정책 연동 방법으로 IAM OIDC를 통해 EKS 서비스어카운트에 IAM 정책을 연동을 하겠다.

ALB Controller 설치

IAM 정책 생성 및 서비스 어카운트 연동하겠다. 다음의 스크립트를 통해 진행하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# ALB controller 정책 설치
curl -o iam_policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.4.7/docs/install/iam_policy.json
aws iam create-policy --policy-name AWSLoadBalancerControllerIAMPolicy --policy-document file://iam_policy.json

# 정책 arn 생성 확인 
ACCOUNT_ID={AWS 계정 넘버} 
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'
---
"arn:aws:iam::955963799952:policy/AWSLoadBalancerControllerIAMPolicy" 

# OIDC 서비스 어카운트 생성
CLUSTER_NAME={EKS 클러스터 이름} 
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=aws-load-balancer-controller \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --override-existing-serviceaccounts --approve

# OIDC 서비스 어카운트 확인 
kubectl edit sa/aws-load-balancer-controller  -n kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations: # annoation 에 role-arn이 추가된 것을 확인! 
    eks.amazonaws.com/role-arn: arn:aws:iam::955963799952:role/eksctl-hanhorang-addon-iamserviceaccount-kub-Role1-1MPK8ATNJVXQH
  creationTimestamp: "2023-05-08T05:32:22Z"
  labels:
    app.kubernetes.io/managed-by: eksctl
  name: aws-load-balancer-controller
  namespace: kube-system
  resourceVersion: "1236"
  uid: 5fe3e69b-8cfc-426c-a16a-509e1f7c4a86

서비스 어카운트 연동이 확인이 되었으면 ALB 을 설치하겠다. 설치는 Helm을 통해 진행했다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# ALB 설치 
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 \
  --set serviceAccount.create=false --set serviceAccount.name=aws-load-balancer-controller

# ALB 파드 확인 
kubectl get pods -A
---
NAMESPACE     NAME                                            READY   STATUS    RESTARTS   AGE
kube-system   aws-load-balancer-controller-7857849d69-qc9nr   1/1     Running   0          24m
kube-system   aws-load-balancer-controller-7857849d69-qjvkk   1/1     Running   0          24m

# ALB 로그 확인
kubectl logs pods/aws-load-balancer-controller-7857849d69-qc9nr -n kube-system
---
{"level":"info","ts":"2023-05-08T05:59:45Z","msg":"version","GitVersion":"v2.5.1","GitCommit":"06abaed66e17a411ba064f34e6018b889780ac66","BuildDate":"2023-04-17T22:36:53+0000"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.metrics","msg":"Metrics server is starting to listen","addr":":8080"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"setup","msg":"adding health check for controller"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-v1-pod"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-v1-service"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-elbv2-k8s-aws-v1beta1-ingressclassparams"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/mutate-elbv2-k8s-aws-v1beta1-targetgroupbinding"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-elbv2-k8s-aws-v1beta1-targetgroupbinding"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"controller-runtime.webhook","msg":"Registering webhook","path":"/validate-networking-v1-ingress"}
{"level":"info","ts":"2023-05-08T05:59:45Z","logger":"setup","msg":"starting podInfo repo"}
{"level":"info","ts":"2023-05-08T05:59:47Z","logger":"controller-runtime.webhook.webhooks","msg":"Starting webhook server"}
{"level":"info","ts":"2023-05-08T05:59:47Z","msg":"Starting server","kind":"health probe","addr":"[::]:61779"}
...

External DNS 설치

External DNS 설치 과정도 앞 ALB 설치 과정과 동일하다. IAM OIDC 연동을 진행하고, 배포를 진행하겠다.

먼저, 스크립트를 기반으로 OIDC 정책 연동부터 진행하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# vi iam_external_policy.json 생성
cat <<EOT > iam_external_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "route53:ChangeResourceRecordSets"
      ],
      "Resource": [
        "arn:aws:route53:::hostedzone/*"
      ]
    },
    {
      "Effect": "Allow",
      "Action": [
        "route53:ListHostedZones",
        "route53:ListResourceRecordSets"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}
EOT

aws iam create-policy --policy-name "AllowExternalDNSUpdates" --policy-document file://iam_external_policy.json.json

# 정책 arn 생성 확인 
aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --query 'Policy.Arn'
---
"arn:aws:iam::955963799952:policy/AllowExternalDNSUpdates"

# OIDC 서비스 어카운트 생성 
eksctl create iamserviceaccount --cluster=$CLUSTER_NAME --namespace=kube-system --name=external-dns \
--attach-policy-arn=arn:aws:iam::$ACCOUNT_ID:policy/AllowExternalDNSUpdates --override-existing-serviceaccounts --approve

# OIDC 서비스 어카운트 확인 
kubectl edit sa/external-dns -n kube-system
---
apiVersion: v1
kind: ServiceAccount
metadata: 
  annotations: # 마찬가지로 연동 확인이 가능하다. 
    eks.amazonaws.com/role-arn: arn:aws:iam::955963799952:role/eksctl-hanhorang-addon-iamserviceaccount-kub-Role1-499RATDKJVJU
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"ServiceAccount","metadata":{"annotations":{},"labels":{"app.kubernetes.io/name":"external-dns"},"name":"external-dns","namespace":"kube-system"}}
  creationTimestamp: "2023-05-08T06:02:38Z"
  labels:
    app.kubernetes.io/managed-by: eksctl
    app.kubernetes.io/name: external-dns
  name: external-dns
  namespace: kube-system
  resourceVersion: "13937"
  uid: 67ce20cc-6545-486c-b525-450be6311d65

external DNS을 연동하기 위해서는 도메인이 필요하다. 필자는 Route53 도메인을 구입했다. 도메인 구매 후 도메인 HOST ID를 변수로 지정하여 external DNS 설치시 파라미터로 사용하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# MyDomain=<자신의 도메인>
MyDomain=hanhorang.link 

# 자신의 Route 53 도메인 ID 조회 및 변수 지정
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)

# ExternalDNS 배포
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
cat externaldns.yaml | yh
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -

# 로그 확인 
kubectl get pods -A
---
kube-system   external-dns-6b5bbbf9d-l7ms2                    1/1     Running   0          4

kubectl logs pods/external-dns-6b5bbbf9d-l7ms2 -n kube-system
---
time="2023-05-08T06:02:43Z" level=info msg="config: {APIServerURL: KubeConfig: RequestTimeout:30s DefaultTargets:[] ContourLoadBalancerService:heptio-contour/contour GlooNamespace:gloo-system SkipperRouteGroupVersion:zalando.org/v1 Sources:[service ingress] Namespace: AnnotationFilter: LabelFilter: FQDNTemplate: CombineFQDNAndAnnotation:false IgnoreHostnameAnnotation:false IgnoreIngressTLSSpec:false IgnoreIngressRulesSpec:false GatewayNamespace: GatewayLabelFilter: Compatibility: PublishInternal:false PublishHostIP:false AlwaysPublishNotReadyAddresses:false ConnectorSourceServer:localhost:8080 Provider:aws GoogleProject: GoogleBatchChangeSize:1000 GoogleBatchChangeInterval:1s GoogleZoneVisibility: DomainFilter:[hanhorang.link] ExcludeDomains:[] RegexDomainFilter: RegexDomainExclusion: ZoneNameFilter:[] ZoneIDFilter:[] TargetNetFilter:[] ExcludeTargetNets:[] AlibabaCloudConfigFile:/etc/kubernetes/alibaba-cloud.json AlibabaCloudZoneType: AWSZoneType:public AWSZoneTagFilter:[] AWSAssumeRole: AWSAssumeRoleExternalID: AWSBatchChangeSize:1000 AWSBatchChangeInterval:1s AWSEvaluateTargetHealth:true AWSAPIRetries:3 AWSPreferCNAME:false AWSZoneCacheDuration:0s AWSSDServiceCleanup:false AzureConfigFile:/etc/kubernetes/azure.json AzureResourceGroup: AzureSubscriptionID: AzureUserAssignedIdentityClientID: BluecatDNSConfiguration: BluecatConfigFile:/etc/kubernetes/bluecat.json BluecatDNSView: BluecatGatewayHost: BluecatRootZone: BluecatDNSServerName: BluecatDNSDeployType:no-deploy BluecatSkipTLSVerify:false CloudflareProxied:false CloudflareDNSRecordsPerPage:100 CoreDNSPrefix:/skydns/ RcodezeroTXTEncrypt:false AkamaiServiceConsumerDomain: AkamaiClientToken: AkamaiClientSecret: AkamaiAccessToken: AkamaiEdgercPath: AkamaiEdgercSection: InfobloxGridHost: InfobloxWapiPort:443 InfobloxWapiUsername:admin InfobloxWapiPassword: InfobloxWapiVersion:2.3.1 InfobloxSSLVerify:true InfobloxView: InfobloxMaxResults:0 InfobloxFQDNRegEx: InfobloxNameRegEx: InfobloxCreatePTR:false InfobloxCacheDuration:0 DynCustomerName: DynUsername: DynPassword: DynMinTTLSeconds:0 OCIConfigFile:/etc/kubernetes/oci.yaml InMemoryZones:[] OVHEndpoint:ovh-eu OVHApiRateLimit:20 PDNSServer:http://localhost:8081 PDNSAPIKey: PDNSTLSEnabled:false TLSCA: TLSClientCert: TLSClientCertKey: Policy:sync Registry:txt TXTOwnerID:/hostedzone/Z08463751O7YNWD79KKIX TXTPrefix: TXTSuffix: Interval:1m0s MinEventSyncInterval:5s Once:false DryRun:false UpdateEvents:false LogFormat:text MetricsAddress::7979 LogLevel:info TXTCacheInterval:0s TXTWildcardReplacement: ExoscaleEndpoint:https://api.exoscale.ch/dns ExoscaleAPIKey: ExoscaleAPISecret: CRDSourceAPIVersion:externaldns.k8s.io/v1alpha1 CRDSourceKind:DNSEndpoint ServiceTypeFilter:[] CFAPIEndpoint: CFUsername: CFPassword: RFC2136Host: RFC2136Port:0 RFC2136Zone: RFC2136Insecure:false RFC2136GSSTSIG:false RFC2136KerberosRealm: RFC2136KerberosUsername: RFC2136KerberosPassword: RFC2136TSIGKeyName: RFC2136TSIGSecret: RFC2136TSIGSecretAlg: RFC2136TAXFR:false RFC2136MinTTL:0s RFC2136BatchChangeSize:50 NS1Endpoint: NS1IgnoreSSL:false NS1MinTTLSeconds:0 TransIPAccountName: TransIPPrivateKeyFile: DigitalOceanAPIPageSize:50 ManagedDNSRecordTypes:[A CNAME] GoDaddyAPIKey: GoDaddySecretKey: GoDaddyTTL:0 GoDaddyOTE:false OCPRouterName: IBMCloudProxied:false IBMCloudConfigFile:/etc/kubernetes/ibmcloud.json TencentCloudConfigFile:/etc/kubernetes/tencent-cloud.json TencentCloudZoneType: PiholeServer: PiholePassword: PiholeTLSInsecureSkipVerify:false PluralCluster: PluralProvider:}"
time="2023-05-08T06:02:43Z" level=info msg="Instantiating new Kubernetes client"
time="2023-05-08T06:02:43Z" level=info msg="Using inCluster-config based on serviceaccount-token"
time="2023-05-08T06:02:43Z" level=info msg="Created Kubernetes client https://10.100.0.1:443"
time="2023-05-08T06:02:45Z" level=info msg="Applying provider record filter for domains: [hanhorang.link. .hanhorang.link.]"
time="2023-05-08T06:02:45Z" level=info msg="All records are already up to date"

설치 이후 반드시 로그를 확인하는 것이 중요하다. 필자의 경험에서 도메인 연동이 잘못되었다던가, IAM 정책 미스로 addon 연동이 잘못되면 로그를 통해 상세 확인이 가능하기 떄문이다.

EKS VPC 운영

EKS Workshop Networking 를 토대로 필자가 실습한 내용들을 공유한다.

Prefix Delegation을 통한 노드별 할당 IP 확장하기

EKS 노드는 EC2 인스턴스로 운영된다. 문제는 EC2 인스턴스 타입별로 할당되는 IP 개수의 제한이 존재한다. 이유는 인스턴스 타입당 할당가능한 네트워크 인터페이스의 제한이 있기 때문이다. 인스턴스별 IP 개수 제한 관련하여 다음의 명령어를 통해서도 확인할 수 있다.

1
2
3
4
5
kubectl get nodes -o jsonpath="{range .items[*]}{.metadata.labels['beta\.kubernetes\.io\/instance-type']}{'\t'}{.status.capacity.pods}{'\n'}{end}"
---
t3.medium       17
t3.medium       17
t3.medium       17

기존에는 IP 할당 개수를 늘리기 위해서 인스턴스 타입을 변경해야 했지만, Amazon VPC CNI 추가 기능 버전 1.9.0 이상부터 Prefix Delegation 를 통해 최대 파드 개수까지 IP 할당 개수를 확장할 수 있다. Prefix Delegation 란 IPv6 기능으로 하위 IP에 접두사를 위임하여 최대 IP를 할당할 수 있게 만들어 주는 기능이다. 이를 VPC CNI에도 사용할 수 있다. IP Mode와 Prefix Mode 를 비교하면 다음과 같이 나타낼 수 있다.

prefix_subnets-cba22c2ff364463d6d2f8b42585004f1.png

  • 오른쪽이 Prefix Mode로 Secondary ENI에 접두사가 위임된 것을 확인할 수 있다. 비교하자면 개별 보조 IP 주소를 할당하는 대신 접두사를 할당하여 최대 IP 개수늘 늘릴 수 있다.

좋은 기능이지만 사용하기에 몇 가지 제한이 존재한다. 기능 도입 전 제한 사항을 꼭 확인하자!

  • VPC CNI version 1.9.0 이상
  • Nitro 기반의 인스턴스에서만 가능
  • /28 접두사를 생성할 수 있는 사용 가능한 IP 주소가 충분하지 않은 경우

바로 VPC CNI 파드를 통해 설정해보겠다. 먼저, 버전 확인과 VPC CNI의 옵션을 살펴보겠다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# VPC CNI 버전 확인 1.9.0 이상 
kubectl describe daemonset aws-node --namespace kube-system | grep Image | cut -d "/" -f 2
---
amazon-k8s-cni-init:v1.12.6-eksbuild.1
amazon-k8s-cni:v1.12.6-eksbuild.1

# Prefix 관련 옵션 확인 (주석 처리 파라미터 확인) 
kubectl describe daemonsets.apps aws-node -n kube-system | grep ADDITIONAL_ENI_TAGS: -B1 -A26
    Environment:
      ADDITIONAL_ENI_TAGS:                    {}
      ANNOTATE_POD_IP:                        false
      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
      CLUSTER_ENDPOINT:                       https://65CBAF476ED2A8986CC4BAFE19F86C44.yl4.ap-northeast-2.eks.amazonaws.com
      CLUSTER_NAME:                           hanhorang
      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 관련 옵션
      VPC_ID:                                 vpc-0cc614fae36493a12
      WARM_ENI_TARGET:                        1      # PREFIX 관련 옵션
      WARM_PREFIX_TARGET:                     1      # PREFIX 관련 옵션
      MY_NODE_NAME:                            (v1:spec.nodeName)
      MY_POD_NAME:                             (v1:metadata.name)
     #MINIMUM_IP_TARGET: # PREFIX 관련 옵션

Prefix 관련 파라미터의 옵션은 다음과 같다.

  • ENABLE_PREFIX_DELEGATION : Prefix Delegation 활성화 여부
  • WARM_PREFIX_TARGET : 현재 초과하여 할당할 접두사 수
  • WARM_IP_TARGET, MINIMUM_IP_TARGET : (WARM_PREFIX_TARGET를 설정하면 오버라이드 됨) 할당할 IP 수와 최소 IP 주소 수

파라미터에서 WARM이라는 표현이 등장하는데, CNI 는 WARM Pool(웜풀) 이라고 하여 더 빠른 파드 시작을 위해 IP 및 접두사를 사전 할당하는 기능이다. 이 웜풀을 통해서 IP와 접두사를 파드에 할당받는다.

<a href="https://aws.github.io/aws-eks-best-practices/networking/prefix-mode/">https://aws.github.io/aws-eks-best-practices/networking/prefix-mode/</a>

https://aws.github.io/aws-eks-best-practices/networking/prefix-mode/

  • 더 많은 파드가 예약되면 ENI에 프리픽스를 요청하고, 만약에 ENI가 사용량에 도달하면 새 ENI를 할당하려고 시도하여 연결한다. 새 ENI는 최대 ENI 제한에 도달할 때까지 연결된다.

살펴본 파라미터를 통해 접두사 모드를 활성해보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

kubectl set env daemonset aws-node -n kube-system ENABLE_PREFIX_DELEGATION=true
---
daemonset.apps/aws-node env updated

kubectl describe daemonsets.apps aws-node -n kube-system | grep ADDITIONAL_ENI_TAGS: -B1 -A26
---
    Environment:
      ADDITIONAL_ENI_TAGS:                    {}
      ANNOTATE_POD_IP:                        false
      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
      CLUSTER_ENDPOINT:                       https://65CBAF476ED2A8986CC4BAFE19F86C44.yl4.ap-northeast-2.eks.amazonaws.com
      CLUSTER_NAME:                           hanhorang
      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  # 변경되었다! 
      VPC_ID:                                 vpc-0cc614fae36493a12
      WARM_ENI_TARGET:                        1
      WARM_PREFIX_TARGET:                     1
      MY_NODE_NAME:                            (v1:spec.nodeName)
      MY_POD_NAME:                             (v1:metadata.name)

설정이 확인되면, 테스트용 파드를 150개 배포해보자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
  name: netshoot-pod
spec:
  replicas: 150
  selector:
    matchLabels:
      app: netshoot-pod
  template:
    metadata:
      labels:
        app: netshoot-pod
    spec:
      containers:
      - name: netshoot-pod
        image: nicolaka/netshoot
        command: ["tail"]
        args: ["-f", "/dev/null"]
      terminationGracePeriodSeconds: 0
EOF

# 이상하다 파드가 43개밖에 할당되지 않았다. 
kubectl get deploy 
---
NAME           READY    UP-TO-DATE   AVAILABLE   AGE
netshoot-pod   43/150   150          43          19s

Pending 된 파드의 이벤트를 확인하니 노드 제한으로 파드 할당이 되지 않았다.

1
2
3
4
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  88s   default-scheduler  0/3 nodes are available: 3 Too many pods. preemption: 0/3 nodes are available: 3 No preemption victims found for incoming pod.

확인해보니 노드에서 MaX 파드 수치가 존재하기 때문이였다. 앞서 본 17개가 해당 인스턴스의 맥스 파드 수였다. 이 MAX 파드 제한을 늘리기 위해서는 워커 노드의 kubelet의 옵션을 수정해야 한다. 방법은 두 가지로 1. EKS 구축시 MAX POD 설정 및 2. 실행 중인 노드 kubelet을 수정하여 MAX POD를 설정할 수 있다.

  1. Eksctl 파라미터를 통한 MAX POD 설정
1
2
3
4
5
6
7
eksctl create nodegroup \
 --cluster hanhorang \
 --region ap-northeast-2 \
 --name ng-1 \
 --node-type t3.medium \
 --managed \
 --max-pods-per-node 100 # 해당 파라미터 
  1. 실행중인 노드 kubelet 수정 ( kimalram 님 블로그 글을 참고하였습니다! )

실행 중인 노드에 접속하여 kubelet 옵션의 MAX 파드를 수정할 것이다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 노드 IP 확인 
kubectl get nodes -o wide 
---
ssNAME                                               STATUS   ROLES    AGE     VERSION                INTERNAL-IP     EXTERNAL-IP     OS-IMAGE         KERNEL-VERSION                  CONTAINER-RUNTIME
ip-192-168-1-209.ap-northeast-2.compute.internal   Ready    <none>   3h16m   v1.24.11-eks-a59e1f0   192.168.1.209   52.78.121.165   Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19
ip-192-168-2-134.ap-northeast-2.compute.internal   Ready    <none>   3h17m   v1.24.11-eks-a59e1f0   192.168.2.134   3.36.17.42      Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19
ip-192-168-3-250.ap-northeast-2.compute.internal   Ready    <none>   3h17m   v1.24.11-eks-a59e1f0   192.168.3.250   54.180.101.37   Amazon Linux 2   5.10.178-162.673.amzn2.x86_64   containerd://1.6.19

# 노드 접속 
ssh ec2-user@52.78.121.165
---
The authenticity of host '52.78.121.165 (52.78.121.165)' can't be established.
ECDSA key fingerprint is SHA256:gr23rumKlP2+B0Dn0wYN+UPxaCgoyrL/uyhjBZxPCug.
ECDSA key fingerprint is MD5:7c:d8:29:c9:5c:09:66:b6:7c:64:62:e6:85:1d:46:ac.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '52.78.121.165' (ECDSA) to the list of known hosts.
Last login: Mon May  1 20:27:30 2023 from 205.251.233.237

       __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

https://aws.amazon.com/amazon-linux-2/
3 package(s) needed for security, out of 12 available
Run "sudo yum update" to apply all updates.

# 노드 eks 설정 부분 경로 접속 
cd /etc/eks 

# kubelet 재실행 
sudo /etc/eks/bootstrap.sh hanhorang --use-max-pods false --kubelet-extra-args '--max-pods=110'
---
2023-05-09T11:31:57+0000 [eks-bootstrap] INFO: starting...
2023-05-09T11:31:57+0000 [eks-bootstrap] INFO: --use-max-pods='false'
2023-05-09T11:31:57+0000 [eks-bootstrap] INFO: --kubelet-extra-args='--max-pods=110'
2023-05-09T11:31:57+0000 [eks-bootstrap] INFO: Using kubelet version 1.24.11
2023-05-09T11:31:57+0000 [eks-bootstrap] INFO: Using containerd as the container runtime
2023-05-09T11:31:58+0000 [eks-bootstrap] INFO: --cluster-ca or --api-server-endpoint is not defined, describing cluster...
2023-05-09T11:31:59+0000 [eks-bootstrap] INFO: Using IP family: ipv4
[Service]
Slice=runtime.slice
‘/etc/eks/containerd/kubelet-containerd.service’ -> ‘/etc/systemd/system/kubelet.service’
2023-05-09T11:32:01+0000 [eks-bootstrap] INFO: complete!

# kubelet 로그 확인 ( 수정이 안됨) 
sudo ps -ef | grep kubelet 
--
root      2860     1  1 08:11 ?        00:02:29 /usr/bin/kubelet --config /etc/kubernetes/kubelet/kubelet-config.json --kubeconfig /var/lib/kubelet/kubeconfig --container-runtime-endpoint unix:///run/containerd/containerd.sock --image-credential-provider-config /etc/eks/image-credential-provider/config.json --image-credential-provider-bin-dir /etc/eks/image-credential-provider --node-ip=192.168.1.209 --pod-infra-container-image=602401143452.dkr.ecr.ap-northeast-2.amazonaws.com/eks/pause:3.5 --v=2 --cloud-provider=aws --container-runtime=remote --node-labels=eks.amazonaws.com/sourceLaunchTemplateVersion=1,alpha.eksctl.io/cluster-name=hanhorang,alpha.eksctl.io/nodegroup-name=ng1,eks.amazonaws.com/nodegroup-image=ami-0da378ed846e950a4,eks.amazonaws.com/capacityType=ON_DEMAND,eks.amazonaws.com/nodegroup=ng1,eks.amazonaws.com/sourceLaunchTemplateId=lt-0a84643ab8f551110 --max-pods=17 
ec2-user  6100  4064  0 11:34 pts/0    00:00:00 grep --color=auto kubelet

# kubelet 재시작 
sudo systemctl restart kubelet
  • kubelet 은 Static 파드로 동작하므로 수동으로 재시작을 해줘야 기능이 적용된다.

kubelet1.png

맥스 파드가 적용되었으면 전에 배포한 150개 파드 동작을 확인할 수 있다.

1
2
3
4
kubectl get deploy 
---
NAME           READY     UP-TO-DATE   AVAILABLE   AGE
netshoot-pod   150/150   150          150         38m

CUSTOM Network를 통한 IP 확장하기

서브넷 대역에 IP가 부족했을 때 추가 서브넷 CIDR을 부여하여 IP 할당 개수를 추가로 확장할 수 있다.

custom-networking-intro-995b094f310d9d4d93fc9139a5dfc106.png

공식 문서에 따르면 Custom CIDR 제약 사항도 존재한다. 적용시 반드시 참고하자

  • 사용자 지정 네트워킹을 사용 설정하면 기본 네트워크 인터페이스에 할당된 IP 주소가 pods에 할당되지 않는다. 보조 네트워크 인터페이스의 IP 주소만 pods에 할당된다.
  • 클러스터에서 IPv6 패밀리를 사용하는 경우 사용자 지정 네트워킹을 사용할 수 없다.
  • 사용자 지정 네트워킹을 사용하여 IPv4 주소 소모를 완화하려는 경우 대신 IPv6 패밀리를 사용하여 클러스터를 생성할 수 있다.

그러면 서브넷 CIDR 을 추가하겠다. 추가 과정은 다음의 4가지로 진행된다.

  • VPC 서브넷 생성
  • aws-node 파드 파라미터 수정(VPC CNI)
  • ENIConfig (CRD) 배포 생성 및 EKS 노드에 연결
  • 노드 그룹 재배포

VPC 할당 및 서브넷 생성은 AWS 콘솔에서 진행하였다. AWS 콘솔 [VPC] 에서 CIDR 및 서브넷 조작이 가능하다. 다음과 같이 10.64.0.0/16 의 VPC CIDR 과 대역에 맞는 서브넷을 생성하였다.

vpc11.png

vpc12.png

생성한 서브넷 ID는 다음의 명령어로 조회가 가능하다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
#aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" --query 'Subnets[*].{SubnetId: SubnetId, AvailabilityZone: AvailabilityZone, CidrBlock: CidrBlock}' --output table
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-0fc5c162640e67e66" --query 'Subnets[*].{SubnetId: SubnetId, AvailabilityZone: AvailabilityZone, CidrBlock: CidrBlock}' --output table
---------------------------------------------------------------------
|                          DescribeSubnets                          |
+------------------+-------------------+----------------------------+
| AvailabilityZone |     CidrBlock     |         SubnetId           |
+------------------+-------------------+----------------------------+
|  ap-northeast-2a |  100.64.1.0/24    |  subnet-06daaaa9fa3990545  |
|  ap-northeast-2c |  192.168.3.0/24   |  subnet-0020334582f221a4e  |
|  ap-northeast-2b |  100.64.2.0/24    |  subnet-0a498cbb63e30d085  |
|  ap-northeast-2c |  100.64.3.0/24    |  subnet-03de86c554113fec7  |
|  ap-northeast-2b |  192.168.2.0/24   |  subnet-0808f8970f303632f  |
|  ap-northeast-2a |  192.168.11.0/24  |  subnet-0bc63c89a9965b672  |
|  ap-northeast-2c |  192.168.13.0/24  |  subnet-0f229c12c2c595c5b  |
|  ap-northeast-2b |  192.168.12.0/24  |  subnet-06229a20d32b499de  |
|  ap-northeast-2a |  192.168.1.0/24   |  subnet-0de74f64866c7a8e5  |
+------------------+-------------------+----------------------------+

이어서 VPC CNI 파드(aws-node)에 custom network 를 사용하겠다는 파라미터를 추가하자.

1
2
3
4
5
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true

kubectl describe daemonsets.apps aws-node -n kube-system | grep CUSTOM
----
      AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG:  true # 설정 확인 

추가 작업으로 서브넷 배포 설정에 대한 eniconfig 파일을 작성하여 배포하도록 하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# eniconfigs.yaml
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: ap-noratheast-2a
spec:
  securityGroups:
    - sg-0db212244a3e72ae1
  subnet: subnet-06daaaa9fa3990545
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: ap-noratheast-2b
spec:
  securityGroups:
    - sg-0db212244a3e72ae1
  subnet: subnet-0a498cbb63e30d085 
---
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
  name: ap-noratheast-2c
spec:
  securityGroups:
    - sg-0db212244a3e72ae1
  subnet: subnet-03de86c554113fec7
  • 추가할 서브넷만 작성하도록 하자. 밑의 노드에 annotation을 추가할 것이기 때문에 AND 로 서브넷이 추가된다.

  • 보안 그룹은 배포된 워크 노드의 보안 그룹으로 작성하였다.

  • metadata.name 에 되도록 AZ 이름을 입력하도록 하자, AZ 이름이 아니면 노드에 적용시키기 위해서는 아래와 같이 추가 주석이 필요하다.

    1
    2
    3
    
    kubectl annotate node ip-192-168-1-84.ap-northeast-2.compute.internal k8s.amazonaws.com/eniConfig=ap-noratheast-2a-custom 
    kubectl annotate node ip-192-168-2-219.ap-northeast-2.compute.internal k8s.amazonaws.com/eniConfig=ap-noratheast-2b-custom
    kubectl annotate node ip-192-168-3-140.ap-northeast-2.compute.internal k8s.amazonaws.com/eniConfig=ap-noratheast-2c-custom
    

배포 및 노드에 연결하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
kubectl apply -f eniconfigs.yaml 
---
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2a created
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2b created
eniconfig.crd.k8s.amazonaws.com/ap-northeast-2c created

kubectl get eniconfig
---
NAME               AGE
ap-northeast-2a   16s
ap-northeast-2b   16s
ap-northeast-2c   16s

# VPC 서브넷 업데이트
kubectl set env daemonset aws-node -n kube-system ENI_CONFIG_LABEL_DEF=topology.kubernetes.io/zone

업데이트된 내용이 노드에는 바로 적용되지 않는다. 새로 추가된 노드에만 적용된다. 새로 노드 그룹을 추가하고 확인하도록 하자.

1
2
3
4
5
6
eksctl create nodegroup \
 --cluster hanhorang \
 --region ap-northeast-2 \
 --name ng-2 \
 --node-type t3.medium \
 --managed

AWS 콘솔을 통해 확인이 가능하다.

vpc15.png

SG for Pod 설정

VPC 보안 그룹을 파드에도 설정할 수 있는 기능이다. 파드에 보안 그룹을 적용하면 파드 접근에 세부적인 보안 관리가 가능해진다.

<a href="https://www.eksworkshop.com/docs/networking/security-groups-for-pods/">https://www.eksworkshop.com/docs/networking/security-groups-for-pods/</a>

https://www.eksworkshop.com/docs/networking/security-groups-for-pods/

SG for Pod를 설정하기 전 몇가지 제한 사항이 존재한다.

  • 윈도우 노드에서 사용할 수 없다.

  • 인스턴스 유형 중 t 타입의 인스턴스에서 사용할 수 없다.

  • IPv6 패밀리용으로 구성된 클러스터에서 사용할 수 없지만 fargate에서는 사용이 가능하다.

  • 정책 제한이 있을시 사용자에 SG for Pod를 위한 섹션을 추가 해야 한다.

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    ...
    subjects:
      - kind: Group
        apiGroup: rbac.authorization.k8s.io
        name: system:authenticated
      - apiGroup: rbac.authorization.k8s.io
        kind: User
        name: eks:vpc-resource-controller
      - kind: ServiceAccount
        name: eks-vpc-resource-controller
    
  • VPC CNI 버전이 1.7.7 이상이여야 한다.

이외에도 VPC CNI 버전 별 플래그 마다 제한사항이 존재한다. 세부 내용은 공식 문서를 참고하자.

공식 문서를 참고하여 SG for Pod 설정을 진행하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
# 버전 확인 
kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3 
---
v1.12.6-eksbuild.1

# EKS VPC ResourceController Role 추가 
cluster_role=$(aws eks describe-cluster --name hanhorang --query cluster.roleArn --output text | cut -d / -f 2)
# 클러스터에 정책 연결 
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController --role-name $cluster_role

# VPC CNI 파라미터 수정(Pod eni 관리 파라미터 허용)
kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true

# 파드 eni 가능 노드 확인
# 위 명령어로 업데이트에 몇 초의 시간이 소요됩니다.
kubectl get nodes -o wide -l vpc.amazonaws.com/has-trunk-attached=true
---
NAME                                               STATUS   ROLES    AGE   VERSION                INTERNAL-IP     EXTERNAL-IP     OS-IMAGE         KERNEL-VERSION                   CONTAINER-RUNTIME
ip-192-168-1-108.ap-northeast-2.compute.internal   Ready    <none>   20m   v1.24.11-eks-a59e1f0   192.168.1.108   3.38.214.241    Amazon Linux 2   5.10.178-162.673.amzn2.aarch64   containerd://1.6.19
ip-192-168-2-45.ap-northeast-2.compute.internal    Ready    <none>   20m   v1.24.11-eks-a59e1f0   192.168.2.45    3.38.188.199    Amazon Linux 2   5.10.178-162.673.amzn2.aarch64   containerd://1.6.19
ip-192-168-3-18.ap-northeast-2.compute.internal    Ready    <none>   20m   v1.24.11-eks-a59e1f0   192.168.3.18    13.125.254.20   Amazon Linux 2   5.10.178-162.673.amzn2.aarch64   containerd://1.6.19

# 파드에서 VPC 외부 주소로 아웃바운드되는 트래픽은 기본 ENI로 가며, 노드 보안 그룹에 대한 규칙이 적용된다, 파드의 eni만 적용시키고 싶다면 아래 파라미터를 추가하자.
kubectl set env daemonset aws-node -n kube-system  AWS_VPC_K8S_CNI_EXTERNALSNAT=true

# 옵션 1. liveness & readyprobe 사용 경우, kubelet이 TCP를 사용하여 파드 eni 와 연결할 수 있도록 TCP 초기 demux 중지
kubectl patch daemonset aws-node -n kube-system \
  -p '{"spec": {"template": {"spec": {"initContainers": [{"env":[{"name":"DISABLE_TCP_EARLY_DEMUX","value":"true"}],"name":"aws-vpc-cni-init"}]}}}}'

# 옵션 2. externalTrafficPolicy: local, NodeLocal DNSCache, 자체 보안 그룹이 있는 네트워크 정책이 있는 경우 아래 명령어 추가 
# 해당 설정을 기존 Pods에 적용하려면, 노드를 재시작해야함 
kubectl set env daemonset aws-node -n kube-system POD_SECURITY_GROUP_ENFORCING_MODE=standard

SG for Pod 설정 이후 파드에 직접 보안 그룹을 설정하도록 하겠다. 활용 예제는 EKS Workshop 을 참고하였다.

home-2ef623b5837cd647adaf7e938d3ff9ee.png

<a href="https://www.eksworkshop.com/docs/introduction/getting-started/about">https://www.eksworkshop.com/docs/introduction/getting-started/about</a>

https://www.eksworkshop.com/docs/introduction/getting-started/about

Component Description
UI 프론트엔드로 사용자 인터페이스를 제공하며 다른 서비스에 대한 API을 연결한다.
Catalog 상품 목록과 상세 정보에 대한 API
Cart 고객 쇼핑카트에 대한 API
Checkout 체크아웃 프로세스를 조정하는 API
Orders 고객 주문을 받고 처리하는 API
Static assets 상품 카탈로그와 관련된 이미지와 같은 정적 파일

활용 예제는 마이크로서비스 아키텍처의 웹 스토어 어플리케이션으로 아래 Catalog의 DB를 RDS로 변경하고 Catalog Pod에 보안 그룹을 적용하여 RDS 접근 권한을 부여한다. 이렇게 하면 Catalog에서만 RDS에 접근이 가능해진다.

sg9.png

파일 배포는 다음과 같이 진행하자.

1
2
3
4
5
6
7
8
git clone https://github.com/aws-samples/eks-workshop-v2.git

cd eks-workshop-v2/environment/workspace/manifests

# public ecr 로그인 
aws ecr-public get-login-password --region us-east-1 | docker login --username AWS --password-stdin public.ecr.aws

kubectl apply -k . 

🧐 배포 트러블슈팅

EKS 노드에서 이미지 pull 이 안되어 파드 실행이 안되는 문제가 발생했다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
kubectl describe pods/catalog-mysql-0 -n catalog
---
...
Events:
  Type     Reason     Age                  From               Message
  ----     ------     ----                 ----               -------
  Normal   Scheduled  10m                  default-scheduler  Successfully assigned catalog/catalog-mysql-0 to ip-192-168-2-197.ap-northeast-2.compute.internal
  Normal   Pulling    9m19s (x4 over 10m)  kubelet            Pulling image "public.ecr.aws/docker/library/mysql:5.7"
  Warning  Failed     9m18s (x4 over 10m)  kubelet            Failed to pull image "public.ecr.aws/docker/library/mysql:5.7": rpc error: code = NotFound desc = failed to pull and unpack image "public.ecr.aws/docker/library/mysql:5.7": no match for platform in manifest: not found
  Warning  Failed     9m18s (x4 over 10m)  kubelet            Error: ErrImagePull
  Warning  Failed     9m5s (x6 over 10m)   kubelet            Error: ImagePullBackOff
  Normal   BackOff    56s (x42 over 10m)   kubelet            Back-off pulling image "public.ecr.aws/docker/library/mysql:5.7"

이상한 점은 베스천 서버에서는 이미지 풀이 되는 반면, EKS 노드에서는 이미지 풀에 대해 not found가 발생한다.

vpc17.png

확인해보니 해당 이미지의 경우(ECR) 에서 linux/arm64/v8 에 대한 이미지 레이어 대한 정보가 없어서 발생한 문제였다. linux/arm64/v8 레이어는 AWS 인스턴스 유형 (A1, M6g,C6g,R6g)에서 사용되는 반면 linux/amd64 는 (x86-64) 아키텍처로 AWS 인스턴스 유형(t2,t3 m4, m5) 기반의 프로세서에서 동작한다.

해결 방법으로 이미지를 직접 빌드 및 Push 할 수 있지만, 인스턴스 유형을 m5.large 로 변경했다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
kubectl get pods -A 
---
NAMESPACE     NAME                              READY   STATUS    RESTARTS       AGE
assets        assets-97576f445-fbf8m            1/1     Running   0              2m5s
carts         carts-75d5cc858f-xh474            1/1     Running   0              2m5s
carts         carts-dynamodb-69b57dddc9-5qcmb   1/1     Running   0              2m5s
catalog       catalog-6d688b9f9c-9f8sg          1/1     Running   2 (103s ago)   2m5s
catalog       catalog-mysql-0                   1/1     Running   0              2m4s
checkout      checkout-79d67d6765-r442r         1/1     Running   0              2m5s
checkout      checkout-redis-cb98f6ff7-qmsk5    1/1     Running   0              2m5s
kube-system   aws-node-h8rnr                    1/1     Running   0              7m15s
kube-system   aws-node-k7nzq                    1/1     Running   0              7m47s
kube-system   aws-node-mbc76                    1/1     Running   0              7m31s
kube-system   coredns-dc4979556-hfztx           1/1     Running   0              14m
kube-system   coredns-dc4979556-s8qj7           1/1     Running   0              14m
kube-system   kube-proxy-h2r6m                  1/1     Running   0              9m8s
kube-system   kube-proxy-jjjmx                  1/1     Running   0              9m8s
kube-system   kube-proxy-np7fj                  1/1     Running   0              9m8s
orders        orders-7fcc4fb7d8-kvb2b           1/1     Running   1 (91s ago)    2m5s
orders        orders-mysql-5d99464c58-mp6xm     1/1     Running   0              2m5s
rabbitmq      rabbitmq-0                        1/1     Running   0              2m4s
ui            ui-5b9cf4db94-54v8g               1/1     Running   0              2m4s

이어서 Catalog 의 DB를 mysql에서 AWS RDS 로 변경하기 위한 작업을 진행하겠다. 이를 위해 보안 그룹 생성 및 RDS 생성을 다음과 같이 진행하자.

  • 보안 그룹 생성(VPC는 EKS VPC로 설정)

    sg13.png

    sg14.png

  • RDS 생성 과정 중 보안 그룹 설정을 앞서 생성한 보안 그룹으로 만들고, VPC를 EKS 가 배포된 VPC로 설정하자. 추가로 필자는 비용 문제로 프리티어로 설정했고 비밀번호를 12341234 로 설정했다.

    rds10.png

rds2.png

생성한 DB 인스턴스의 엔드포인트를 환경 변수로 입력하여 catalog를 재배포 하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export CATALOG_RDS_PASSWORD=12341234
export CATALOG_RDS_ENDPOINT=catalog-db2.cnrosybtsnww.ap-northeast-2.rds.amazonaws.com:3306

kubectl apply -k /workspace/modules/networking/securitygroups-for-pods/rds
--

# 생성 이후 DB endpoint 확인 
kubectl get -n catalog cm catalog -o yaml
---
apiVersion: v1
data:
  DB_ENDPOINT: database-2.cnrosybtsnww.ap-northeast-2.rds.amazonaws.com:3306
  DB_NAME: catalog
  DB_READ_ENDPOINT: database-2.cnrosybtsnww.ap-northeast-2.rds.amazonaws.com:3306
kind: ConfigMap
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","data":{"DB_ENDPOINT":"database-2.cnrosybtsnww.ap-northeast-2.rds.amazonaws.com:3306","DB_NAME":"catalog","DB_READ_ENDPOINT":"database-2.cnrosybtsnww.ap-northeast-2.rds.amazonaws.com:3306"},"kind":"ConfigMap","metadata":{"annotations":{},"name":"catalog","namespace":"catalog"}}
  creationTimestamp: "2023-05-10T19:48:42Z"
  name: catalog
  namespace: catalog
  resourceVersion: "6645"

# 기존 catalog 파드 삭제
kubectl delete pod -n catalog -l app.kubernetes.io/component=service

# catalog 로그 확인
kubectl -n catalog logs deployment/catalog
---
2023/05/10 20:13:25 Running database migration...
2023/05/10 20:13:30 Error: Failed to prep migration dial tcp 192.168.12.123:3306: i/o timeout
2023/05/10 20:13:30 Error: Failed to run migration dial tcp 192.168.12.123:3306: i/o timeout
2023/05/10 20:13:30 dial tcp 192.168.12.123:3306: i/o timeout

결과 처럼 i/o timeout 이 발생하는데 보안 그룹을 설정하지 않았기 때문이다.

보안 정책 생성을 통해 앞서 생성한 보안 그룹(pods-connect-rds)을 catalog 파드에 적용시키자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
  name: catalog-rds-access
  namespace: catalog
spec:
  podSelector:
    matchLabels:
      app.kubernetes.io/component: mysql
  securityGroups:
    groupIds:
    - sg-07739bd847069f56e # catalog-sg

보안 그룹 적용 후 파드를 재배포하자.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export CATALOG_SG_ID=sg-08bb6f8b77a20e693

kubectl apply -k .
---
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
WARNING: This Kustomization is relying on a bug that loads values from the environment when they are omitted from an env file. This behaviour will be removed in the next major release of Kustomize.
namespace/catalog unchanged
serviceaccount/catalog unchanged
configmap/catalog unchanged
configmap/catalog-env-7gtc4hbmd2 unchanged
configmap/catalog-sg-env-5969b7c958 created
secret/catalog-db unchanged
service/catalog unchanged
service/catalog-mysql unchanged
service/ui-nlb unchanged
deployment.apps/catalog unchanged
statefulset.apps/catalog-mysql unchanged
securitygrouppolicy.vpcresources.k8s.aws/catalog-rds-access created

# 기존 catalog 파드 삭제
kubectl delete pod -n catalog -l app.kubernetes.io/component=service

# 보안 그룹 변경 확인 
kubectl get events -n catalog | grep SecurityGroupRequested
--
5m22s       Normal    SecurityGroupRequested   pod/catalog-6d688b9f9c-gjhvt    Pod will get the following Security Groups [sg-08bb6f8b77a20e693]
20s         Normal    SecurityGroupRequested   pod/catalog-6d688b9f9c-vnlgb    Pod will get the following Security Groups [sg-08bb6f8b77a20e693]
10s         Normal    SecurityGroupRequested   pod/catalog-mysql-0             Pod will get the following Security Groups [sg-08bb6f8b77a20e693]

이후 파드에 접속하여 rds에 접속하면 정상적으로 접근이 된다.(밑 사진의 빨간네모) 반면에 노드에서 rds 접근 시도시 timeout이 발생한다. (밑 사진의 파란네모)

Sg10.png

워크샵에 있는 내용을 따라했지만, catalog 파드에서 연결이 안되어 catalog-mysal 에서 RDS 연결 테스트를 진행하였다. 실습 진행에 참고하자!

마치며

이번 글에서는 못 다뤘지만, VPC 관련하여 다룰 주제가 많다! 틈틈히 정리하여 VPC Deep Dive 2탄에서 해당 내용들을 다룰 수 있도록 하겠다.