AWS EKS
EKS는 AWS의 관리형 Kubernetes 서비스로 Kubernetes의 Control Plane을 AWS에서 관리해주고, EKS를 사용하는 고객은 Data Plane을 사용하는 형태로 제공됩니다. AWS EKS의 특징으로 비용은 시간당 0.1 달러로 매우 저렴하고, VPC CNI가 Addon으로 제공됩니다. VPC CNI는 Flat 네트워크라고 표현하기도 하는데, Pod 네트워크 대역이 VPC CIDR를 사용하기 때문입니다. 같은 네트워크를 사용하기 때문에 VXLAN과 같은 오버레이(Overlay) 기술을 통해 캡슐화를 할 필요가 없어 속도가 빠르다는 장점도 있습니다.
Pod에 IP를 할당하기 위해 Node에서 생성 가능한 최대 Pod 개수(IP)가 존재하는데, ConfigMap의 설정을 통해 생성 가능 개수를 늘릴 수 있습니다. (AWS Nitro 기반의 EC2 인스턴스 유형만 지원되며, ENI가 할당할 수 있는 접두사의 수가 한계에 다다르면, 보조 ENI가 할당됩니다.)
테스트를 위해 Deployment를 통해 Pod를 생성합니다.
cat <<EOF | kubectl apply -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
Pod와 Node의 네트워크 대역이 같은 것을 알 수 있습니다.
같은 네트워크 대역을 사용하므로 직접 통신이 가능합니다.
라우팅 테이블을 통해 직접 통신한다는 것을 알 수 있습니다.
서로 다른 Node에 스케줄링된 Pod 간 통신은 아래와 같은 흐름으로 통신됩니다.
Pod에서 외부 통신 시, 노드의 IP로 SNAT 되어 통신합니다.
Custom Netwoking with VPC CNI
VPC CNI를 사용하면, Pod와 Node가 같은 네트워크 대역을 사용한다고 설명했습니다. 만약, VPC CIDR 크기가 너무 작아 Pod에 할당할 IP 자체가 부족하면 어떻게 해야 할까요? 이런 경우를 위해 AWS에서는 Custom Networking 설정을 지원합니다. 다른 CNI의 IPAM과 같이 다른 네트워크 대역을 Pod에 할당하도록 하는 기능입니다. 아래 이미지는 VPC CIDR로 10.0.0.0/16을 사용하고, Pod 네트워크 대역으로 100.64.0.0/16을 사용합니다.
작업을 위해 아래 설정을 true로 변경해주면 됩니다.
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
설정 변경 후 aws-node Pod가 재시작 되었습니다.
설정을 변경하면, eniconfigs.crd.k8s.amazonaws.com 라는 CRD가 생성됩니다.
아래 AWS 문서에 테스트를 위한 코드가 제공되서 아래 문서에 있는대로 진행해보려고 합니다.
환경 구축을 위한 코드입니다.
export cluster_name=my-custom-networking-cluster
account_id=$(aws sts get-caller-identity --query Account --output text)
export AWS_PAGER=""
aws cloudformation create-stack --stack-name my-eks-custom-networking-vpc \
--template-url https://s3.us-west-2.amazonaws.com/amazon-eks/cloudformation/2020-10-29/amazon-eks-vpc-private-subnets.yaml \
--parameters ParameterKey=VpcBlock,ParameterValue=192.168.0.0/24 \
ParameterKey=PrivateSubnet01Block,ParameterValue=192.168.0.64/27 \
ParameterKey=PrivateSubnet02Block,ParameterValue=192.168.0.96/27 \
ParameterKey=PublicSubnet01Block,ParameterValue=192.168.0.0/27 \
ParameterKey=PublicSubnet02Block,ParameterValue=192.168.0.32/27
CloudFormation 스택의 배포 상태를 확인하는 명령어 입니다.
aws cloudformation describe-stacks --stack-name my-eks-custom-networking-vpc --query Stacks\[\].StackStatus --output text
EKS 클러스터를 생성하기 위해 Policy, Role을 생성합니다.
cat >eks-cluster-role-trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "eks.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role --role-name myCustomNetworkingAmazonEKSClusterRole --assume-role-policy-document file://"eks-cluster-role-trust-policy.json"
aws iam attach-role-policy --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy --role-name myCustomNetworkingAmazonEKSClusterRole
EKS 클러스터를 생성하는 명령어인데, 에러가 발생해 수동으로 생성했습니다.
aws eks create-cluster --name my-custom-networking-cluster \
--role-arn arn:aws:iam::$account_id:role/myCustomNetworkingAmazonEKSClusterRole \
--resources-vpc-config subnetIds=$subnet_id_1","$subnet_id_2
EKS 상태 확인 명령어 입니다.
aws eks describe-cluster --name my-custom-networking-cluster --query cluster.status
VPC를 추가로 구성합니다.
vpc_id=$(aws eks describe-cluster --name my-custom-networking-cluster --query "cluster.resourcesVpcConfig.vpcId" --output text)
aws ec2 describe-vpcs --vpc-ids $vpc_id \
--query 'Vpcs[*].CidrBlockAssociationSet[*].{CIDRBlock: CidrBlock, State: CidrBlockState.State}' --out table
추가 CIDR 블록을 VPC에 연결합니다.
aws ec2 associate-vpc-cidr-block --vpc-id $vpc_id --cidr-block 192.168.1.0/24
VPC에 연결된 CIDR는 아래와 같습니다.
aws ec2 describe-vpcs --vpc-ids $vpc_id --query 'Vpcs[*].CidrBlockAssociationSet[*].{CIDRBlock: CidrBlock, State: CidrBlockState.State}' --out table
신규 서브넷을 생성합니다.
new_subnet_id_1=$(aws ec2 create-subnet --vpc-id $vpc_id --availability-zone $az_1 --cidr-block 192.168.1.0/27 \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-eks-custom-networking-vpc-PrivateSubnet01},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
--query Subnet.SubnetId --output text)
new_subnet_id_2=$(aws ec2 create-subnet --vpc-id $vpc_id --availability-zone $az_2 --cidr-block 192.168.1.32/27 \
--tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=my-eks-custom-networking-vpc-PrivateSubnet02},{Key=kubernetes.io/role/internal-elb,Value=1}]' \
--query Subnet.SubnetId --output text)
aws ec2 describe-subnets --filters "Name=vpc-id,Values=$vpc_id" \
--query 'Subnets[*].{SubnetId: SubnetId,AvailabilityZone: AvailabilityZone,CidrBlock: CidrBlock}' \
--output table
신규로 연동한 192.168.1.0/24 대역에 대한 서브넷이 각 가용영역에 생성되었습니다.
EKS 리소스를 구성합니다. (위에서 살펴본 CNI 설정 변경입니다.)
kubectl set env daemonset aws-node -n kube-system AWS_VPC_K8S_CNI_CUSTOM_NETWORK_CFG=true
cluster_security_group_id=$(aws eks describe-cluster --name $cluster_name --query cluster.resourcesVpcConfig.clusterSecurityGroupId --output text)
ENIConfig 커스텀 리소스를 생성합니다.
cat >$az_1.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: $az_1
spec:
securityGroups:
- $cluster_security_group_id
subnet: $new_subnet_id_1
EOF
cat >$az_2.yaml <<EOF
apiVersion: crd.k8s.amazonaws.com/v1alpha1
kind: ENIConfig
metadata:
name: $az_2
spec:
securityGroups:
- $cluster_security_group_id
subnet: $new_subnet_id_2
EOF
kubectl apply -f $az_1.yaml
kubectl apply -f $az_2.yaml
Worker Node를 배포하기 위한 Node IAM Role을 생성합니다.
cat >node-role-trust-relationship.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
export node_role_name=myCustomNetworkingAmazonEKSNodeRole
node_role_arn=$(aws iam create-role --role-name $node_role_name --assume-role-policy-document file://"node-role-trust-relationship.json" \
--query Role.Arn --output text)
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSWorkerNodePolicy \
--role-name $node_role_name
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly \
--role-name $node_role_name
aws iam attach-role-policy \
--policy-arn arn:aws:iam::aws:policy/AmazonEKS_CNI_Policy \
--role-name $node_role_name
Node Group을 생성합니다.
aws eks create-nodegroup --cluster-name $cluster_name --nodegroup-name my-nodegroup \
--subnets $subnet_id_1 $subnet_id_2 --instance-types t3.medium --node-role $node_role_arn
aws eks describe-nodegroup --cluster-name $cluster_name --nodegroup-name my-nodegroup --query nodegroup.status --output text
테스트를 위해 Pod를 배포합니다.
cat <<EOF | kubectl apply -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
Pod의 IP를 확인해보면, Node IP와 다른 네트워크 대역인 것을 알 수 있습니다.