EKS 네트워크(VPC CNI)
CNI( Container Network Interface )란?
CNCF(Cloud Native Computing Foundation) 프로젝트의 하나로 컨테이너 네트워크를 지원하는 여러 플러그인의 사실상 표준이다.
- https://github.com/containernetworking/cni
- https://www.cncf.io/projects/container-network-interface-cni/
Kubernetes에서는 이런 CNI를 통해 각 POD간의 통신을 지원하고 있으며
기본적인 CNI로 kubenet을 제공하지만 kubenet은 정말 간단하고 기본적인 기능만 제공하다보니
일반적으로 CNI 표준을 지키는 다른 3rd-party CNI를 사용하게된다.
EKS도 Kubernetes인만큼 CNI를 사용하는데, AWS VPC CNI라는 CNI를 제공한다.
AWS VPC CNI 특징?
POD와 NODE가 같은 네트워크 대역을 가진다.
Kubernetes에서 많이 사용하는 Calico CNI나 기타 여러 CNI는 일반적으로 POD와 NODE가 다른 네트워크 대역을 가지는데 반해, AWS VPC CNI는 POD가 NODE와 같은 네트워크 대역을 가지는 특징이 있다.
POD간 통신이 직접 이루어짐
또한 AWS VPC CNI는 이처럼 같은 네트워크 대역을 가지는 특성을 활용해 POD간 통신이 직접 이루어진다.
다른 일반적인 Kubernetes CNI의 경우 overlay network를 사용한다.
(overlay network 참고 : https://coffeewhale.com/k8s/network/2019/04/19/k8s-network-01/ )
이런 직접적인 통신으로 인해 다른 CNI에 비해 더 빠른 통신 속도를 제공한다.
(추가) NODE와 POD간 대역을 분리하는 AWS VPC CNI Custom networking도 가능하다고 한다.
- https://docs.aws.amazon.com/eks/latest/userguide/cni-custom-network.html)
EC2 타입 별 생성할 수 있는 POD 개수 제한이 존재
POD에 ENI를 가지고 IP를 할당하기 때문에 NODE와 같은 네트워크 대역대를 가지지만,
이로인해 NODE 당 보유할 수 있는 IP제한이 발생해 POD생성에 제한이 발생한다.
인스턴스 타입별로 POD를 생성할 수 있는 최대 개수 제한이 있으며,
이는 AWS VPC CNI Custom Networking이나 Prefix Delegation를 통해서 부분적으로 해소할 수 있다.
(Prefix Delegation : https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/cni-increase-ip-addresses.html)
EKS를 구성할 때 이런 POD 개수 제한을 아래 링크들을 통해 미리 확인해보면 좋다.
- https://github.com/awslabs/amazon-eks-ami/blob/master/files/max-pods-calculator.sh
- https://github.com/awslabs/amazon-eks-ami/blob/master/files/eni-max-pods.txt
(추가) Amazon Linux 2 외의 리눅스 시스템과의 호환성
아무래도 AWS에서 제공하는 CNI인 만큼 Amazon Linux 2와 사용할 때 잘 동작하는 것 같으며,
다른 리눅스 배포판과 사용할 때는 IPAMD 관련 이슈가 있는 것 같았다.
내 경우에는 Amazon Linux 2를 사용하지 않은 적이 없어서 따로 겪어본적은 없었다.
참고 : https://github.com/aws/amazon-vpc-cni-k8s/blob/master/docs/troubleshooting.md#cni-compatibility
EKS에서의 POD 보안그룹 적용
AWS VPC CNI에서는 POD에서 직접 통신이 이루어지다보니 다른 CNI의 overlay network에 비해 POD에 대한 접근이 쉬워 POD에서 동작하는 어플리케이션의 보안이 취약할 수 있다.
AWS에서는 이런 취약사항을 보완하기 위해 POD 별 보안그룹 기능을 제공한다.
POD 보안그룹을 사용하기 위한 제한사항
AWS에서는 POD 보안그룹을 사용하기 위한 몇가지 제한사항이 존재한다.
- EKS외의 환경(KOPS 등)에서는 제공되지 않는 것 같다.
- t 타입 인스턴스(t3.medium 등)는 지원되지 않는다.
(지원하는 인스턴스 목록 : https://github.com/aws/amazon-vpc-resource-controller-k8s/blob/release-1.1.4/pkg/aws/vpc/limits.go , IsTrunkingCompatible 값이 false 이면 지원되지 않는다.) - POD 보안 그룹은 Windows 노드에서는 지원하지 않는다.
- AWS VPC CNI 버전 1.7.7 이상에서 지원한다.
그 외의 제한사항이 더 있으며 상세한 내용은 아래의 페이지를 참고한다.
(https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/security-groups-for-pods.html)
EKS 구축 완료 후 POD 보안그룹을 위한 설정
Amazon VPC CNI plugin for Kubernetes 버전확인
# 버전이 1.7.7 이상인지 확인
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl describe daemonset aws-node --namespace kube-system | grep amazon-k8s-cni: | cut -d : -f 3
v1.12.6-eksbuild.1
생성된 EKS Cluster에 ENI를 컨트롤하기 위한 AmazonEKSVPCResourceController 정책이 있는지 확인하고 없다면 추가
# EKS Cluster Role 조회
# aws eks describe-cluster --name <cluser-name> --query cluster.roleArn --output text | cut -d / -f 2
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# aws eks describe-cluster --name myeks --query cluster.roleArn --output text | cut -d / -f 2
eksctl-myeks-cluster-ServiceRole-D66NZVC2SO6N
# EKS Cluster Role에 정책 확인
# aws iam list-attached-role-policies --role-name <cluster-role-name>
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# aws iam list-attached-role-policies --role-name eksctl-myeks-cluster-ServiceRole-D66NZVC2SO6N
{
"AttachedPolicies": [
{
"PolicyName": "AmazonEKSClusterPolicy",
"PolicyArn": "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
},
{
"PolicyName": "AmazonEKSVPCResourceController",
"PolicyArn": "arn:aws:iam::aws:policy/AmazonEKSVPCResourceController"
}
]
}
# 만약 정책이 없을 경우 아래 명령어로 추가
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSVPCResourceController --role-name <cluster-role-name>
aws-node daemonset 의 ENABLE_POD_ENI 옵션 확인 후 true로 변경
# ENABLE_POD_ENI 설정값 조회
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl describe daemonset aws-node -n kube-system | grep ENABLE_POD_ENI
ENABLE_POD_ENI: false
# ENABLE_POD_ENI 설정값 활성화
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl set env daemonset aws-node -n kube-system ENABLE_POD_ENI=true
daemonset.apps/aws-node env updated
# ENABLE_POD_ENI 설정값 확인
# 값을 설정하면 NODE에 vpc.amazonaws.com/has-trunk-attached lable이 활성화 됨
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl describe daemonset aws-node -n kube-system | grep ENABLE_POD_ENI
ENABLE_POD_ENI: true
ENABLE_POD_ENI: true
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# 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-141.ap-northeast-2.compute.internal Ready <none> 26m v1.24.11-eks-a59e1f0 192.168.1.141 3.36.50.175 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
ip-192-168-2-6.ap-northeast-2.compute.internal Ready <none> 26m v1.24.11-eks-a59e1f0 192.168.2.6 13.124.50.54 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
ip-192-168-3-139.ap-northeast-2.compute.internal Ready <none> 26m v1.24.11-eks-a59e1f0 192.168.3.139 13.125.206.106 Amazon Linux 2 5.10.178-162.673.amzn2.x86_64 containerd://1.6.19
POD용 보안그룹 생성
POD용 보안그룹은 아래의 내용을 충족해야 한다.
The security group or groups that you specify for your pods must meet the following criteria:
- They must exist. If they don't exist, then, when you deploy a pod that matches the selector, your pod remains stuck in the creation process. If you describe the pod, you'll see an error message similar to the following one: An error occurred (InvalidSecurityGroupID.NotFound) when calling the CreateNetworkInterface operation: The securityGroup ID 'sg-05b1d815d1EXAMPLE' does not exist.
- They must allow inbound communication from the security group applied to your nodes (for kubelet) over any ports that you've configured probes for.
- They must allow outbound communication over TCP and UDP ports 53 to a security group assigned to the pods (or nodes that the pods run on) running CoreDNS. The security group for your CoreDNS pods must allow inbound TCP and UDP port 53 traffic from the security group that you specify.
- They must have necessary inbound and outbound rules to communicate with other pods that they need to communicate with.
- They must have rules that allow the pods to communicate with the Kubernetes control plane if you're using the security group with Fargate. The easiest way to do this is to specify the cluster security group as one of the security groups.
Security group policies only apply to newly scheduled pods. They do not affect running pods.
CoreDNS 53 Port나 여러가지 것들이 있지만 테스트용이므로 NodeGroup과 EKS Cluster 및 ControlPlain의 보안그룹에서 오는 모든 포트를 허용했고 테스트 앱으로 배포할 80포트를 추가했다.
NodeGroup에서 사용하는 보안그룹에도 80포트를 추가했다.
POD 보안그룹을 위한 Amazon EKS SecurityGroupPolicy 생성
# 테스트용 namespace 생성
# kubectl create namespace <sg-test>
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl create namespace sg-test
namespace/sg-test created
# Amazon EKS SecurityGroupPolicy 생성
cat >my-security-group-policy.yaml <<EOF
apiVersion: vpcresources.k8s.aws/v1beta1
kind: SecurityGroupPolicy
metadata:
name: my-security-group-policy
namespace: <sg-test>
spec:
podSelector:
matchLabels: ## 해당 label을 통해 POD 보안그룹을 적용함
app: <sg-test-app>
securityGroups:
groupIds:
- <my_pod_security_group_id> ## 해당 보안그룹은 사전에 생성
EOF
# 생성한 정책 배포
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl apply -f my-security-group-policy.yaml
securitygrouppolicy.vpcresources.k8s.aws/my-security-group-policy created
POD 보안그룹 테스트용 APP 배포
POD보안그룹을 적용할 label은 sg-test-app으로 설정했으며, 일반적인 NodeGruop의 보안그룹을 적용받을 POD는 normal-test-app으로 설정했다.
# POD 보안그룹을 적용할 앱 배포
cat >sg-application.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment
namespace: sg-test
labels:
app: sg-test-app
spec:
replicas: 4
selector:
matchLabels:
app: sg-test-app
template:
metadata:
labels:
app: sg-test-app
spec:
terminationGracePeriodSeconds: 120
containers:
- name: nginx
image: public.ecr.aws/nginx/nginx:1.23
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: sg-test-app
namespace: sg-test
labels:
app: sg-test-app
spec:
selector:
app: sg-test-app
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
# POD 보안그룹을 적용하지 않을 앱 배포
cat >normal-application.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-deployment2
namespace: sg-test
labels:
app: normal-test-app
spec:
replicas: 4
selector:
matchLabels:
app: normal-test-app
template:
metadata:
labels:
app: normal-test-app
spec:
terminationGracePeriodSeconds: 120
containers:
- name: nginx
image: public.ecr.aws/nginx/nginx:1.23
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: normal-test-app
namespace: sg-test
labels:
app: normal-test-app
spec:
selector:
app: normal-test-app
ports:
- protocol: TCP
port: 80
targetPort: 80
EOF
# 앱 배포
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl apply -f sg-application.yaml
deployment.apps/my-deployment created
service/sg-test-app created
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl apply -f normal-application.yaml
deployment.apps/my-deployment2 created
service/normal-test-app created
# POD 확인
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# kubectl get pod -n sg-test -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-deployment-786cb5854-6cp87 1/1 Running 0 25s 192.168.3.219 ip-192-168-3-139.ap-northeast-2.compute.internal <none> <none>
my-deployment-786cb5854-f5mdh 1/1 Running 0 25s 192.168.1.162 ip-192-168-1-141.ap-northeast-2.compute.internal <none> <none>
my-deployment-786cb5854-qglxn 1/1 Running 0 25s 192.168.2.24 ip-192-168-2-6.ap-northeast-2.compute.internal <none> <none>
my-deployment-786cb5854-vswnj 1/1 Running 0 25s 192.168.2.128 ip-192-168-2-6.ap-northeast-2.compute.internal <none> <none>
my-deployment2-5ccf5c77bf-gn966 1/1 Running 0 17s 192.168.3.109 ip-192-168-3-139.ap-northeast-2.compute.internal <none> <none>
my-deployment2-5ccf5c77bf-js4wr 1/1 Running 0 17s 192.168.2.80 ip-192-168-2-6.ap-northeast-2.compute.internal <none> <none>
my-deployment2-5ccf5c77bf-s8wg4 1/1 Running 0 17s 192.168.2.37 ip-192-168-2-6.ap-northeast-2.compute.internal <none> <none>
my-deployment2-5ccf5c77bf-t8jhk 1/1 Running 0 17s 192.168.1.69 ip-192-168-1-141.ap-northeast-2.compute.internal <none> <none>
POD로의 통신 확인 및 보안그룹에서 정책 제거 후 테스트
App 배포 후 EKS를 생성한 bastion에서 각각 통신 테스트를 수행한다.
# POD IP 확인
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# SG_POD=$(kubectl get pods -n sg-test --selector=app=sg-test-app -o jsonpath='{.items[*].status.podIP}')
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# echo $SG_POD
192.168.3.219 192.168.1.162 192.168.2.24 192.168.2.128
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# NORMAL_POD=$(kubectl get pods -n sg-test --selector=app=normal-test-app -o jsonpath='{.items[*].status.podIP}')
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# echo $NORMAL_POD
192.168.3.109 192.168.2.80 192.168.2.37 192.168.1.69
# 설치한 앱의 nginx 80 포트로의 통신확인
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl $SG_POD
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl $NORMAL_POD
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
...
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
2개의 App 모두 정상 통신을 확인하였으므로, POD 보안그룹 설정을 테스트한다.
POD 보안그룹에서 80포트 제거
통신 테스트
# curl timeout의 기본 timeout이 5분가량 되어 빠른 테스트를 위해 10초로 설정
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl --max-time 10 $SG_POD
curl: (28) Connection timed out after 10000 milliseconds
curl: (28) Connection timed out after 10000 milliseconds
curl: (28) Connection timed out after 10001 milliseconds
curl: (28) Connection timed out after 10001 milliseconds
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl --max-time 10 $NORMAL_POD
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
...
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
POD의 보안그룹만 적용되는 것을 볼 수 있다.
반대로 NodeGruop의 보안그룹만 80포트를 제거할 경우엔 normal-test-app의 경우만 정상 통신되는 것을 확인할 수 있다.
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl --max-time 10 $SG_POD
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
<!DOCTYPE html>
<html>
...
...
(test_admin@myeks:N/A) [root@myeks-bastion-EC2 ~]# curl --max-time 10 $NORMAL_POD
curl: (28) Connection timed out after 10000 milliseconds
curl: (28) Connection timed out after 10000 milliseconds
curl: (28) Connection timed out after 10000 milliseconds
curl: (28) Connection timed out after 10001 milliseconds
직관적으로 보기 위해 보안그룹 제어를 AWS Console을 통해 수행했지만
아래의 AWS CLI 명령어를 통해서도 제어가 가능하다.
# 보안그룹에 규칙 추가
aws ec2 authorize-security-group-ingress \
--group-id <sg-1234567890abcdef0> \
--protocol <tcp> \
--port <22> \
--cidr <203.0.113.0/24>
# 보안그룹에 규칙 제거
aws ec2 revoke-security-group-ingress \
--group-id <my_cluster_security_group_id> \
--security-group-rule-ids <rule_id>
추가사항
DISABLE_TCP_EARLY_DEMUX 설정
만약 버전 1.11.0 이전의 Amazon VPC CNI 버전을 사용할 때 해당 POD 보안그룹을 적용할 경우
DISABLE_TCP_EARLY_DEMUX 설정을 true로 설정(기본값 : false)해줘야 한다고 한다.
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"}]}}}}'
DISABLE_TCP_EARLY_DEMUX 설정이 필요한 이유
- 공식문서 설명 : https://github.com/aws/amazon-vpc-cni-k8s#disable_tcp_early_demux-v173
- 관련 이슈 : https://github.com/aws/amazon-vpc-cni-k8s/pull/1212#issuecomment-693540666
POD_SECURITY_GROUP_ENFORCING_MODE 설정
만약 Cluster가 NodeLocal DNSCache를 사용하거나
Calico 네트워크 정책을 사용하거나
NodePort나 LoadBalancer 유형의 서비스가 있는 경우
POD 보안그룹을 사용하려면 AWS VPC CNI 버전이 1.11.0 이상인 상태에서 POD_SECURITY_GROUP_ENFORCING_MODE 설정을 standard로 설정해줘야 한다고 한다.
(strict, standard 값이 존재하며 기본은 strict 값이다.)
kubectl set env daemonset aws-node -n kube-system POD_SECURITY_GROUP_ENFORCING_MODE=standard
- 공식문서 설명 : https://github.com/aws/amazon-vpc-cni-k8s/blob/master/README.md#pod_security_group_enforcing_mode-v1110
- Calico 네트워크 정책 엔진 추가 기능 설치 : https://docs.aws.amazon.com/ko_kr/eks/latest/userguide/calico.html
참고자료
- 쿠버네티스(Kubernetes) 네트워크 정리 : https://medium.com/finda-tech/kubernetes-네트워크-정리-fccd4fd0ae6
- amazon-vpc-cni-k8s github : https://github.com/aws/amazon-vpc-cni-k8s
- AWS re:Invent 2021 - Integrate Amazon EKS with your networking pattern : https://youtu.be/V8DidcYmNmU
- [번역] 쿠버네티스 네트워킹 이해하기#1: Pods : https://coffeewhale.com/k8s/network/2019/04/19/k8s-network-01/
- 튜토리얼: 팟(Pod)의 보안 그룹 : https://docs.aws.amazon.com/eks/latest/userguide/security-groups-for-pods.html#security-groups-pods-considerations