티스토리 뷰
안녕하세요. CloudNet@ K8S Study를 진행하며 해당 내용을 이해하고 공유하기 위해 작성한 글입니다. 해당 내용은 EKS docs와 workshop을 기본으로 정리하였습니다.
실습 환경 배포
- EKS version: EKS 1.24 (현재(23년 5월) 기준 Add-on 서비스 경우 1.25, 1.26 버전에서 지원되지 않는 경우 있음)
- instance : t3.medium type
- worker node : 3개(az 3개)
# YAML 파일 다운로드
$ curl -O https://s3.ap-northeast-2.amazonaws.com/cloudformation.cloudneta.net/K8S/eks-oneclick.yaml
# CloudFormation 스택 배포
# aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=<My SSH Keyname> SgIngressSshCidr=<My Home Public IP Address>/32 MyIamUserAccessKeyID=<IAM User의 액세스키> MyIamUserSecretAccessKey=<IAM User의 시크릿 키> ClusterBaseName='<eks 이름>' --region ap-northeast-2
예시) aws cloudformation deploy --template-file eks-oneclick.yaml --stack-name myeks --parameter-overrides KeyName=kp-gasida SgIngressSshCidr=$(curl -s ipinfo.io/ip)/32 MyIamUserAccessKeyID=AKIA5... MyIamUserSecretAccessKey='CVNa2...' ClusterBaseName=myeks --region ap-northeast-2
# CloudFormation 스택 배포 완료 후 작업용 EC2 IP 출력
$ aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text
# 마스터노드 SSH 접속
$ ssh -i ~/Downloads/kp-hayley3.pem ec2-user@$(aws cloudformation describe-stacks --stack-name myeks --query 'Stacks[*].Outputs[0].OutputValue' --output text)
# 환경변수 정보 확인
$ export | egrep 'ACCOUNT|AWS_|CLUSTER|KUBERNETES|VPC|Subnet' | egrep -v 'SECRET|KEY'
declare -x ACCOUNT_ID="90XXXXXXXXXX"
declare -x AWS_DEFAULT_REGION="ap-northeast-2"
declare -x AWS_PAGER=""
declare -x CLUSTER_NAME="kp-hayley3"
declare -x KUBERNETES_VERSION="1.24"
declare -x PrivateSubnet1="subnet-09ab368066dc94e26"
declare -x PrivateSubnet2="subnet-07b1e93dc0aadef33"
declare -x PrivateSubnet3="subnet-0e8cfb59daf114f98"
declare -x PubSubnet1="subnet-0499020cee58ff1c1"
declare -x PubSubnet2="subnet-0463c7254df499977"
declare -x PubSubnet3="subnet-01b203bf40bfc72ad"
declare -x VPCID="vpc-08ffc976e2c2d9a72"
# 노드 정보 확인
$ 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
# default 네임스페이스 적용
$ kubectl ns default
# 노드 IP 확인 및 PrivateIP 변수 지정
$ N1=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2a -o jsonpath={.items[0].status.addresses[0].address})
$ N2=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2b -o jsonpath={.items[0].status.addresses[0].address})
$ N3=$(kubectl get node --label-columns=topology.kubernetes.io/zone --selector=topology.kubernetes.io/zone=ap-northeast-2c -o jsonpath={.items[0].status.addresses[0].address})
$ echo "export N1=$N1" >> /etc/profile
$ echo "export N2=$N2" >> /etc/profile
$ echo "export N3=$N3" >> /etc/profile
$ echo $N1, $N2, $N3
192.168.1.31, 192.168.2.173, 192.168.3.94
# 노드 보안그룹 ID 확인
$ NGSGID=$(aws ec2 describe-security-groups --filters Name=group-name,Values=*ng1* --query "SecurityGroups[*].[GroupId]" --output text)
$ aws ec2 authorize-security-group-ingress --group-id $NGSGID --protocol '-1' --cidr 192.168.1.100/32
{
"Return": true,
"SecurityGroupRules": [
{
"SecurityGroupRuleId": "sgr-0d084f6e1d59f8ef2",
"GroupId": "sg-0ea3fa11b153b349d",
"GroupOwnerId": "90XXXXXXXXX",
"IsEgress": false,
"IpProtocol": "-1",
"FromPort": -1,
"ToPort": -1,
"CidrIpv4": "192.168.1.100/32"
}
]
}
# 워커 노드 SSH 접속
$ ssh ec2-user@$N1 hostname
$ ssh ec2-user@$N2 hostname
$ ssh ec2-user@$N3 hostname
# eksctl 설치/업데이트 addon 확인
$ eksctl get addon --cluster $CLUSTER_NAME
NAME VERSION STATUS ISSUES IAMROLE UPDATE AVAILABLE CONFIGURATION VALUES
coredns v1.9.3-eksbuild.3 ACTIVE 0
kube-proxy v1.24.10-eksbuild.2 ACTIVE 0
vpc-cni v1.12.6-eksbuild.1 ACTIVE 0 arn:aws:iam::90XXXXXXXXXXX:role/eksctl-eks-hayley-addon-vpc-cni-Role1-SPCCHT3KDWQX
1. AWS VPC CNI
K8S CNI : Container Network Interface는 k8s 네트워크 환경을 구성해줍니다.
AWS VPC CNI : 파드의 IP를 할당해줍니다. 파드의 IP 네트워크 대역과 노드의 IP 대역이 같아서 직접 통신이 가능합니다.
- 파드간 통신 시 일반적으로 K8S CNI는 오버레이(VXLAN, IP-IP 등) 통신을 하고, AWS VPC CNI는 동일 대역으로 직접 통신을 합니다.
- Calico CNI는 host networking namespace에 설치되고 각각의 노드는 virtual router로 역할을 합니다. AWS 네트워크 경우 ENI IP 주소만 알고 pod가 어디에 있는지 모릅니다. 기존 방식에서는 오버레이 네트워크를 통해 이를 해결하는데 Calico 경우 VXLAN, IP-IP 통신을 지원합니다.
- Cluster가 하나의 VPC에 존재할 경우, Calico CNI는 pod에서 pod 간 바로 통신되도록 지원합니다.
- 클러스터가 Multiple VPC에 존재할 경우 Calico CNI는 오버레이 네트워크를 통한 통신을 지원합니다.
- 다른 CNI(Calico 등)과 달리 AWS VPC CNI는 네트워크 통신의 최적화(성능, 지연)를 위해서 노드와 파드의 네트워크 대역을 동일하게 설정합니다.
- 각 pod 들은 underlaying VPC 서브넷의 IP 주소를 할당받고 pod ip는 instance의 ENI 상의 secondary IP로 설정됩니다. 노드 상의 pod의 수가 ENI에서 지원되는 secondary IP의 수를 초과하게된다면 ENI가 자동으로 node에 추가 할당됩니다.
- AWS VPC CNI는 오버레이 네트워크가 필요없이 이미 pod ip를 알고 있기 때문에 단일 VPC, 멀티 VPC에서 바로 pod 간 통신을 지원할 수 있게됩니다.
- Secondary IPv4 address : 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정합니다.
# 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
# kube-proxy config 확인 : 모드 iptables 사용
$ kubectl describe cm -n kube-system kube-proxy-config
...
mode: "iptables"
...
# 노드 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 |
+-------------------------+----------------+----------------+-----------+
| eks-hayley-ng1-Node | 192.168.3.94 | 13.209.74.242 | running |
| eks-hayley-ng1-Node | 192.168.2.173 | 3.38.188.24 | running |
| eks-hayley-bastion-EC2 | 192.168.1.100 | 3.36.67.161 | running |
| eks-hayley-ng1-Node | 192.168.1.31 | 3.35.19.95 | running |
+-------------------------+----------------+----------------+-----------+
# 파드 IP 확인
$ kubectl get pod -n kube-system -o=custom-columns=NAME:.metadata.name,IP:.status.podIP,STATUS:.status.phase
NAME IP STATUS
aws-node-dggfs 192.168.3.94 Running
aws-node-mxwbl 192.168.1.31 Running
aws-node-vtdbn 192.168.2.173 Running
coredns-6777fcd775-kxjnm 192.168.1.243 Running
coredns-6777fcd775-v966q 192.168.3.212 Running
kube-proxy-cg6lr 192.168.2.173 Running
kube-proxy-fhl6z 192.168.1.31 Running
kube-proxy-qqwsl 192.168.3.94 Running
# 파드 이름 확인
$ kubectl get pod -A -o name
pod/aws-node-dggfs
pod/aws-node-mxwbl
pod/aws-node-vtdbn
pod/coredns-6777fcd775-kxjnm
pod/coredns-6777fcd775-v966q
pod/kube-proxy-cg6lr
pod/kube-proxy-fhl6z
pod/kube-proxy-qqwsl
# 파드 갯수 확인
$ kubectl get pod -A -o name | wc -l
8
# 노드에 툴 설치
$ 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
└── plugin.log
# 네트워크 정보 확인 : eniY는 pod network 네임스페이스와 veth pair
$ ssh ec2-user@$N1 sudo ip -br -c addr
o UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.1.31/24 fe80::1f:5dff:fe11:d78a/64
eni57775486648@if3 UP fe80::d401:8dff:fe4e:4ba8/64
eth1 UP 192.168.1.173/24 fe80::69:fcff:fe1f:12c6/64
$ ssh ec2-user@$N1 sudo 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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 02:1f:5d:11:d7:8a brd ff:ff:ff:ff:ff:ff
inet 192.168.1.31/24 brd 192.168.1.255 scope global dynamic eth0
valid_lft 2145sec preferred_lft 2145sec
inet6 fe80::1f:5dff:fe11:d78a/64 scope link
valid_lft forever preferred_lft forever
3: eni57775486648@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether d6:01:8d:4e:4b:a8 brd ff:ff:ff:ff:ff:ff link-netns cni-20c5bd57-a3b1-c196-4e79-56da6212083b
inet6 fe80::d401:8dff:fe4e:4ba8/64 scope link
valid_lft forever preferred_lft forever
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 02:69:fc:1f:12:c6 brd ff:ff:ff:ff:ff:ff
inet 192.168.1.173/24 brd 192.168.1.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::69:fcff:fe1f:12c6/64 scope link
valid_lft forever preferred_lft forever
$ ssh ec2-user@$N1 sudo ip -c route
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.31
192.168.1.243 dev eni57775486648 scope link
2. 노드에서 기본 네트워크 정보 확인
- network 네임스페이스는 호스트(root)와 파드별(per pod)로 구분됩니다.
- 특정한 pod(kube-proxy, aws-node)는 호스트(root)의 IP를 그대로 사용합니다.
- ENI0, ENI1 으로 2개의 ENI는 자신의 IP 외 추가적으로 5개의 보조 private IP를 가질 수 있습니다.
- coredns pod는 veth로 호스트에는 eniY@ifN 인터페이스와 pod에 eth0와 연결되어 있습니다.
# coredns 파드 IP 정보 확인
$ kubectl get pod -n kube-system -l k8s-app=kube-dns -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-6777fcd775-kxjnm 1/1 Running 0 53m 192.168.1.243 ip-192-168-1-31.ap-northeast-2.compute.internal <none> <none>
coredns-6777fcd775-v966q 1/1 Running 0 53m 192.168.3.212 ip-192-168-3-94.ap-northeast-2.compute.internal <none> <none>
# 노드의 라우팅 정보 확인 >> EC2 네트워크 정보의 Secondary private IPv4 addresses'와 비교
$ ssh ec2-user@$N1 sudo ip -c route
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.31
192.168.1.243 dev eni57775486648 scope link
# [터미널1~3] 노드 모니터링
$ ssh ec2-user@$N1
$ watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
$ ssh ec2-user@$N2
$ watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
$ ssh ec2-user@$N3
$ watch -d "ip link | egrep 'eth|eni' ;echo;echo "[ROUTE TABLE]"; route -n | grep eni"
# 테스트용 파드 netshoot-pod 생성
$ 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
# 파드 이름 변수 지정
$ PODNAME1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].metadata.name})
$ PODNAME2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].metadata.name})
$ PODNAME3=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[2].metadata.name})
# 파드 확인
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
netshoot-pod-7757d5dd99-9jxxs 1/1 Running 0 99s 192.168.1.163 ip-192-168-1-31.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-7757d5dd99-ndrbt 1/1 Running 0 99s 192.168.2.75 ip-192-168-2-173.ap-northeast-2.compute.internal <none> <none>
netshoot-pod-7757d5dd99-xqnkv 1/1 Running 0 99s 192.168.3.203 ip-192-168-3-94.ap-northeast-2.compute.internal <none> <none>
$ kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
netshoot-pod-7757d5dd99-9jxxs 192.168.1.163
netshoot-pod-7757d5dd99-ndrbt 192.168.2.75
netshoot-pod-7757d5dd99-xqnkv 192.168.3.203
- 파드가 생성되면, 워커 노드에 eniY@ifN 추가되고 라우팅 테이블에도 정보가 추가됩니다.
# 노드3에서 네트워크 인터페이스 정보 확인
$ ssh ec2-user@$N3
----------------
# ip -br -c addr show
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0 UP 192.168.3.94/24 fe80::8f0:dbff:fe30:6aea/64
eni05a6151560c@if3 UP fe80::b831:13ff:fe92:8e81/64
eth1 UP 192.168.3.131/24 fe80::83e:6ff:feb2:554a/64
enic4b76dc5b9f@if3 UP fe80::e426:7eff:fead:71c/64
# ip -c link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 0a:f0:db:30:6a:ea brd ff:ff:ff:ff:ff:ff
3: eni05a6151560c@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT group default
link/ether ba:31:13:92:8e:81 brd ff:ff:ff:ff:ff:ff link-netns cni-c03c7d67-b375-61e1-1ca0-b9331dd5a50a
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 0a:3e:06:b2:55:4a brd ff:ff:ff:ff:ff:ff
5: enic4b76dc5b9f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP mode DEFAULT group default
link/ether e6:26:7e:ad:07:1c brd ff:ff:ff:ff:ff:ff link-netns cni-3212b379-e026-d013-b1da-0b64851f7c68
# 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
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 0a:f0:db:30:6a:ea brd ff:ff:ff:ff:ff:ff
inet 192.168.3.94/24 brd 192.168.3.255 scope global dynamic eth0
valid_lft 2762sec preferred_lft 2762sec
inet6 fe80::8f0:dbff:fe30:6aea/64 scope link
valid_lft forever preferred_lft forever
3: eni05a6151560c@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether ba:31:13:92:8e:81 brd ff:ff:ff:ff:ff:ff link-netns cni-c03c7d67-b375-61e1-1ca0-b9331dd5a50a
inet6 fe80::b831:13ff:fe92:8e81/64 scope link
valid_lft forever preferred_lft forever
4: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc mq state UP group default qlen 1000
link/ether 0a:3e:06:b2:55:4a brd ff:ff:ff:ff:ff:ff
inet 192.168.3.131/24 brd 192.168.3.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 fe80::83e:6ff:feb2:554a/64 scope link
valid_lft forever preferred_lft forever
5: enic4b76dc5b9f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether e6:26:7e:ad:07:1c brd ff:ff:ff:ff:ff:ff link-netns cni-3212b379-e026-d013-b1da-0b64851f7c68
inet6 fe80::e426:7eff:fead:71c/64 scope link
valid_lft forever preferred_lft forever
# ip route # 혹은 route -n
default via 192.168.3.1 dev eth0
169.254.169.254 dev eth0
192.168.3.0/24 dev eth0 proto kernel scope link src 192.168.3.94
192.168.3.203 dev enic4b76dc5b9f scope link
192.168.3.212 dev eni05a6151560c scope link
# 마지막 생성된 네임스페이스 정보 출력 -t net(네트워크 타입)
# sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1
22648
# 마지막 생성된 네임스페이스 net PID 정보 출력 -t net(네트워크 타입)를 변수 지정
MyPID=$(sudo lsns -o PID,COMMAND -t net | awk 'NR>2 {print $1}' | tail -n 1)
# sudo nsenter -t $MyPID -n 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 1a:d3:a4:14:85:28 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.3.203/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::18d3:a4ff:fe14:8528/64 scope link
valid_lft forever preferred_lft forever
# sudo nsenter -t $MyPID -n ip -c route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
# 테스트용 파드 접속(exec) 후 Shell 실행
$ kubectl exec -it $PODNAME1 -- zsh
dP dP dP
88 88 88
88d888b. .d8888b. d8888P .d8888b. 88d888b. .d8888b. .d8888b. d8888P
88' `88 88ooood8 88 Y8ooooo. 88' `88 88' `88 88' `88 88
88 88 88. ... 88 88 88 88 88. .88 88. .88 88
dP dP `88888P' dP `88888P' dP dP `88888P' `88888P' dP
Welcome to Netshoot! (github.com/nicolaka/netshoot)
# netshoot-pod-7757d5dd99-9jxxs ~ 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 ce:c9:88:d3:48:ed brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.1.163/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::ccc9:88ff:fed3:48ed/64 scope link
valid_lft forever preferred_lft forever
# netshoot-pod-7757d5dd99-9jxxs ~ ip -c route
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
# netshoot-pod-7757d5dd99-9jxxs ~ 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
# netshoot-pod-7757d5dd99-9jxxs ~ ping -c 1 192.168.2.75
PING 192.168.2.75 (192.168.2.75) 56(84) bytes of data.
64 bytes from 192.168.2.75: icmp_seq=1 ttl=62 time=1.15 ms
--- 192.168.2.75 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.153/1.153/1.153/0.000 ms
# netshoot-pod-7757d5dd99-9jxxs ~ ps
PID USER TIME COMMAND
1 root 0:00 tail -f /dev/null
7 root 0:01 zsh
115 root 0:00 ps
# netshoot-pod-7757d5dd99-9jxxs ~ cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local ap-northeast-2.compute.internal
nameserver 10.100.0.10
options ndots:5
# 파드2 Shell 실행
$ kubectl exec -it $PODNAME2 -- 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@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP group default
link/ether fa:ad:9c:bf:1d:25 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 192.168.2.75/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::f8ad:9cff:febf:1d25/64 scope link
valid_lft forever preferred_lft forever
# 파드3 Shell 실행
$ kubectl exec -it $PODNAME3 -- ip -br -c addr
lo UNKNOWN 127.0.0.1/8 ::1/128
eth0@if5 UP 192.168.3.203/32 fe80::18d3:a4ff:fe14:8528/64
3. 노드 간 파드 통신(오버헤드 없이 통신이 됨!!!)
- AWS VPC CNI 경우 별도의 오버레이(Overlay) 통신 기술 없이, VPC Native 하게 파드간 직접 통신이 가능합니다.
- 파드간 통신 테스트 및 확인 : 별도의 NAT 동작 없이 통신 가능 확인
# 파드 IP 변수 지정
PODIP1=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[0].status.podIP})
PODIP2=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[1].status.podIP})
PODIP3=$(kubectl get pod -l app=netshoot-pod -o jsonpath={.items[2].status.podIP})
# 파드1 Shell 에서 파드2로 ping 테스트
$ kubectl exec -it $PODNAME1 -- ping -c 2 $PODIP2
PING 192.168.2.75 (192.168.2.75) 56(84) bytes of data.
64 bytes from 192.168.2.75: icmp_seq=1 ttl=62 time=0.929 ms
64 bytes from 192.168.2.75: icmp_seq=2 ttl=62 time=0.820 ms
--- 192.168.2.75 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1029ms
rtt min/avg/max/mdev = 0.820/0.874/0.929/0.054 ms
# 파드2 Shell 에서 파드3로 ping 테스트
$ kubectl exec -it $PODNAME2 -- ping -c 2 $PODIP3
PING 192.168.3.203 (192.168.3.203) 56(84) bytes of data.
64 bytes from 192.168.3.203: icmp_seq=1 ttl=62 time=1.53 ms
64 bytes from 192.168.3.203: icmp_seq=2 ttl=62 time=1.24 ms
--- 192.168.3.203 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.244/1.386/1.528/0.142 ms
# 파드3 Shell 에서 파드1로 ping 테스트
$ kubectl exec -it $PODNAME3 -- ping -c 2 $PODIP1
PING 192.168.1.163 (192.168.1.163) 56(84) bytes of data.
64 bytes from 192.168.1.163: icmp_seq=1 ttl=62 time=1.36 ms
64 bytes from 192.168.1.163: icmp_seq=2 ttl=62 time=1.13 ms
--- 192.168.1.163 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1001ms
rtt min/avg/max/mdev = 1.133/1.244/1.355/0.111 ms
# 워커 노드 EC2 : TCPDUMP 확인
[ec2-user@ip-192-168-1-31 ~]$ sudo tcpdump -i any -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked), capture size 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel
[ec2-user@ip-192-168-1-31 ~]$ sudo tcpdump -i eth1 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel
[ec2-user@ip-192-168-1-31 ~]$ sudo tcpdump -i eth0 -nn icmp
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C
0 packets captured
0 packets received by filter
0 packets dropped by kernel
[워커 노드1]
# routing policy database management 확인
[ec2-user@ip-192-168-1-31 ~]$ ip rule
0: from all lookup local
512: from all to 192.168.1.243 lookup main
512: from all to 192.168.1.163 lookup main
1024: from all fwmark 0x80/0x80 lookup main
1536: from 192.168.1.163 lookup 2
32766: from all lookup main
32767: from all lookup default
# routing table management 확인
[ec2-user@ip-192-168-1-31 ~]$ ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 192.168.1.0 dev eth0 proto kernel scope link src 192.168.1.31
broadcast 192.168.1.0 dev eth1 proto kernel scope link src 192.168.1.173
local 192.168.1.31 dev eth0 proto kernel scope host src 192.168.1.31
local 192.168.1.173 dev eth1 proto kernel scope host src 192.168.1.173
broadcast 192.168.1.255 dev eth0 proto kernel scope link src 192.168.1.31
broadcast 192.168.1.255 dev eth1 proto kernel scope link src 192.168.1.173
# 디폴트 네트워크 정보를 eth0 을 통해서 빠져나간다
[ec2-user@ip-192-168-1-31 ~]$ ip route show table main
default via 192.168.1.1 dev eth0
169.254.169.254 dev eth0
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.31
192.168.1.163 dev eni04aaba534ed scope link
192.168.1.243 dev eni57775486648 scope link
4. 파드에서 외부 통신
iptable 에 SNAT 을 통하여 노드의 eth0 IP로 변경되어서 외부와 통신됩니다.
VPC CNI 의 External source network address translation (SNAT) 설정에 따라, 외부(인터넷) 통신 시 SNAT 하거나 혹은 SNAT 없이 통신을 할 수 있습니다. [참고]
# 작업용 EC2 : pod-1 Shell 에서 외부로 ping
$ kubectl exec -it $PODNAME1 -- ping -c 1 www.google.com
PING www.google.com (142.250.196.100) 56(84) bytes of data.
64 bytes from nrt12s35-in-f4.1e100.net (142.250.196.100): icmp_seq=1 ttl=104 time=28.5 ms
--- www.google.com ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 28.467/28.467/28.467/0.000 ms
$ kubectl exec -it $PODNAME1 -- ping -i 0.1 www.google.com
PING www.google.com (142.250.196.100) 56(84) bytes of data.
64 bytes from nrt12s35-in-f4.1e100.net (142.250.196.100): icmp_seq=1 ttl=104 time=28.6 ms
# 워커 노드 EC2 : TCPDUMP 확인
sudo tcpdump -i any -nn icmp
sudo tcpdump -i eth0 -nn icmp
# 워커 노드 EC2 : 퍼블릭IP 확인
$ curl -s ipinfo.io/ip ; echo
3.38.188.24
# 작업용 EC2 : pod-1 Shell 에서 외부 접속 확인 - 공인IP는 어떤 주소인가?
## The right way to check the weather - 링크
$ kubectl exec -it $PODNAME1 -- curl -s ipinfo.io/ip ; echo
3.35.19.95
$ kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul
┌─────────────┐
┌──────────────────────────────┬───────────────────────┤ Sat 06 May ├───────────────────────┬──────────────────────────────┐
│ Morning │ Noon └──────┬──────┘ Evening │ Night │
├──────────────────────────────┼──────────────────────────────┼──────────────────────────────┼──────────────────────────────┤
│ .-. Heavy rain │ .-. Moderate rain │ _`/"".-. Patchy light d…│ _`/"".-. Patchy rain po…│
│ ( ). +12(10) °C │ ( ). +12(10) °C │ ,\_( ). +12(11) °C │ ,\_( ). +12(11) °C │
│ (___(__) ↙ 22-28 km/h │ (___(__) ↙ 18-22 km/h │ /(___(__) ↙ 12-15 km/h │ /(___(__) ↙ 10-15 km/h │
│ ‚‘‚‘‚‘‚‘ 5 km │ ‚‘‚‘‚‘‚‘ 7 km │ ‘ ‘ ‘ ‘ 5 km │ ‘ ‘ ‘ ‘ 9 km │
│ ‚’‚’‚’‚’ 12.4 mm | 79% │ ‚’‚’‚’‚’ 6.4 mm | 80% │ ‘ ‘ ‘ ‘ 0.2 mm | 85% │ ‘ ‘ ‘ ‘ 0.2 mm | 77% │
└──────────────────────────────┴──────────────────────────────┴──────────────────────────────┴──────────────────────────────┘
$ kubectl exec -it $PODNAME1 -- curl -s wttr.in/seoul?format=3
seoul: 🌦 +13°C
$ kubectl exec -it $PODNAME1 -- curl -s wttr.in/Moon
.------------.
.--' o . . `--.
.-' . O . . `-.
.-'@ @@@@@@@ . @@@@@ `-
/@@@ @@@@@@@@@@@ @@@@@@@ . \
./ o @@@@@@@@@@@ @@@@@@@ . \
/@@ o @@@@@@@@@@@. @@@@@@@ O \
/@@@@ . @@@@@@@o @@@@@@@@@@ @@@
|@@@@@ . @@@@@@@@@@@@@ o @@@@|
/@@@@@ O `.-./ . @@@@@@@@@@@@ @@ Full Moon +
| @@@@ --`-' o @@@@@@@@ @@@@ | 0 13:23:08
|@ @@@ ` o . @@ . @@@@@@@ | Last Quarter -
| @@ @ .-. @@@ @@@@@@@ | 6 7:29:30
\ . @ @@@ `-' . @@@@ @@@@ o
| @@ @@@@@ . @@ . |
\ @@@@ @\@@ / . O . o .
\ o @@ \ \ / . . /
`\ . .\.-.___ . . .-. /
\ `-' `-' /
`-. o / | o O . .-
`-. / . . .-'
`--. . .--'
`------------'
$ kubectl exec -it $PODNAME1 -- curl -s wttr.in/:help
# 워커 노드 EC2
$ ip rule
0: from all lookup local
512: from all to 192.168.2.75 lookup main
1024: from all fwmark 0x80/0x80 lookup main
32766: from all lookup main
32767: from all lookup default
$ ip route show table main
default via 192.168.2.1 dev eth0
169.254.169.254 dev eth0
192.168.2.0/24 dev eth0 proto kernel scope link src 192.168.2.173
192.168.2.75 dev enie745cdee225 scope link
$ sudo iptables -L -n -v -t nat
$ sudo iptables -t nat -S
# 파드가 외부와 통신시에는 아래 처럼 'AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1' 룰(rule)에 의해서 SNAT 되어서 외부와 통신!
# 참고로 뒤 IP는 eth0(ENI 첫번째)의 IP 주소이다
# --random-fully 동작 - 링크1 링크2
sudo iptables -t nat -S | grep 'A AWS-SNAT-CHAIN'
-A AWS-SNAT-CHAIN-0 ! -d 192.168.0.0/16 -m comment --comment "AWS SNAT CHAIN" -j AWS-SNAT-CHAIN-1
-A AWS-SNAT-CHAIN-1 ! -o vlan+ -m comment --comment "AWS, SNAT" -m addrtype ! --dst-type LOCAL -j SNAT --to-source 192.168.2.173 --random-fully
## 아래 'mark 0x4000/0x4000' 매칭되지 않아서 RETURN 됨!
-A KUBE-POSTROUTING -m mark ! --mark 0x4000/0x4000 -j RETURN
-A KUBE-POSTROUTING -j MARK --set-xmark 0x4000/0x0
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -j MASQUERADE --random-fully
...
# 카운트 확인 시 AWS-SNAT-CHAIN-0, AWS-SNAT-CHAIN-1 에 매칭되어, 목적지가 192.168.0.0/16 아니고 외부 빠져나갈때 SNAT 192.168.1.251(EC2 노드1 IP) 변경되어 나간다!
$ sudo iptables -t filter --zero; sudo iptables -t nat --zero; sudo iptables -t mangle --zero; sudo iptables -t raw --zero
$ watch -d 'sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-0; echo ; sudo iptables -v --numeric --table nat --list AWS-SNAT-CHAIN-1; echo ; sudo iptables -v --numeric --table nat --list KUBE-POSTROUTING'
# conntrack 확인
$ sudo conntrack -L -n |grep -v '169.254.169'
tcp 6 89 TIME_WAIT src=192.168.2.173 dst=52.95.197.186 sport=33472 dport=443 src=52.95.197.186 dst=192.168.2.173 sport=443 dport=58782 [ASSURED] mark=128 use=1
udp 17 10 src=192.168.2.173 dst=121.162.54.1 sport=55936 dport=123 src=121.162.54.1 dst=192.168.2.173 sport=123 dport=20668 mark=128 use=1
tcp 6 431991 ESTABLISHED src=192.168.2.173 dst=52.95.194.65 sport=44400 dport=443 src=52.95.194.65 dst=192.168.2.173 sport=443 dport=37190 [ASSURED] mark=128 use=1
tcp 6 1 CLOSE src=192.168.2.173 dst=52.95.194.65 sport=37078 dport=443 src=52.95.194.65 dst=192.168.2.173 sport=443 dport=64516 [ASSURED] mark=128 use=1
tcp 6 431999 ESTABLISHED src=192.168.2.173 dst=3.37.247.124 sport=41790 dport=443 src=3.37.247.124 dst=192.168.2.173 sport=443 dport=18671 [ASSURED] mark=128 use=1
tcp 6 431985 ESTABLISHED src=192.168.2.173 dst=52.95.195.121 sport=57922 dport=443 src=52.95.195.121 dst=192.168.2.173 sport=443 dport=34794 [ASSURED] mark=128 use=1
conntrack v1.4.4 (conntrack-tools): 166 flow entries have been shown.
tcp 6 431999 ESTABLISHED src=192.168.2.173 dst=3.37.247.124 sport=34472 dport=443 src=3.37.247.124 dst=192.168.2.173 sport=443 dport=57051 [ASSURED] mark=128 use=1
5. 노드에 파드 생성 갯수 제한
- Secondary IPv4 addresses : 인스턴스 유형에 최대 ENI 갯수와 할당 가능 IP 수를 조합하여 선정합니다.
- 워커 노드의 인스턴스 타입 별 파드 생성 갯수가 제한됩니다. 인스턴스 타입 별 ENI 최대 갯수와 할당 가능한 최대 IP 갯수에 따라서 파드 배치 갯수가 결정됩니다. 단, aws-node 와 kube-proxy 파드는 호스트의 IP를 사용함으로 최대 갯수에서 제외합니다.
- 최대 파드 생성 갯수 : (Number of network interfaces for the instance type × (the number of IP addressess per network interface — 1)) + 2
노드의 인스턴스 정보 확인 : t3.medium 사용 시
# t3 타입의 정보(필터) 확인
$ 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.2xlarge |
| 6 | 3 | t3.medium |
| 12 | 3 | t3.large |
| 15 | 4 | t3.xlarge |
| 2 | 2 | t3.micro |
| 2 | 2 | t3.nano |
| 4 | 3 | t3.small |
+----------+----------+--------------+
# c5 타입의 정보(필터) 확인
$ aws ec2 describe-instance-types --filters Name=instance-type,Values=c5*.* \
--query "InstanceTypes[].{Type: InstanceType, MaxENI: NetworkInfo.MaximumNetworkInterfaces, IPv4addr: NetworkInfo.Ipv4AddressesPerInterface}" \
--output table
# 파드 사용 가능 계산 예시 : 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개
# 워커노드 상세 정보 확인 : 노드 상세 정보의 Allocatable 에 pods 에 17개 정보 확인
$ kubectl describe node | grep Allocatable: -A7
Allocatable:
attachable-volumes-aws-ebs: 25
cpu: 1930m
ephemeral-storage: 27905944324
hugepages-1Gi: 0
hugepages-2Mi: 0
memory: 3388364Ki
pods: 17
최대 파드 생성 및 확인
# 워커 노드 EC2 - 모니터링
$ while true; do ip -br -c addr show && echo "--------------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
# 작업용 EC2 - 터미널1
$ watch -d 'kubectl get pods -o wide'
# 작업용 EC2 - 터미널2
# 디플로이먼트 생성
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/nginx-dp.yaml
$ kubectl apply -f nginx-dp.yaml
# 파드 확인
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-deployment-6fb79bc456-7x4nl 1/1 Running 0 22s 192.168.3.203 ip-192-168-3-94.ap-northeast-2.compute.internal <none> <none>
nginx-deployment-6fb79bc456-l4nrr 1/1 Running 0 22s 192.168.2.235 ip-192-168-2-173.ap-northeast-2.compute.internal <none> <none>
$ kubectl get pod -o=custom-columns=NAME:.metadata.name,IP:.status.podIP
NAME IP
nginx-deployment-6fb79bc456-7x4nl 192.168.3.203
nginx-deployment-6fb79bc456-l4nrr 192.168.2.235
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
$ kubectl scale deployment nginx-deployment --replicas=8
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인
$ kubectl scale deployment nginx-deployment --replicas=15
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
$ kubectl scale deployment nginx-deployment --replicas=30
# 파드 증가 테스트 >> 파드 정상 생성 확인, 워커 노드에서 eth, eni 갯수 확인 >> 어떤일이 벌어졌는가?
$ kubectl scale deployment nginx-deployment --replicas=50
# 파드 생성 실패!
$ kubectl get pods | grep Pending
nginx-deployment-6fb79bc456-2gwnp 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-7btkh 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-gjpxx 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-h27dj 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-mvmsh 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-nvl2s 0/1 Pending 0 8s
nginx-deployment-6fb79bc456-zv2dx 0/1 Pending 0 8s
$ kubectl describe pod <Pending 파드> | grep Events: -A5
$ kubectl describe pod nginx-deployment-6fb79bc456-zv2dx | grep Events: -A5
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 51s 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.
# 디플로이먼트 삭제
kubectl delete deploy nginx-deployment
- 해결 방안 : Prefix Delegation, WARM & MIN IP/Prefix Targets, Custom Network
[도전과제1] EKS Max pod 개수 증가 - Prefix Delegation + WARM & MIN IP/Prefix Targets : EKS에 직접 설정 후 파드 150대 생성해보기 - 링크 Workshop
[도전과제2] EKS Max pod 개수 증가 - Custom Network : EKS에 직접 설정 후 파드 150대 생성해보기 - 링크 Workshop
6. Service & AWS LoadBalancer Controller
crd 로 target group binding이 생성됩니다.
- crd? 쿠버네티스에서는 오브젝트를 직접 정의해 사용할수 있으며 소스코드를 따로 수정하지 않고도 API를 확장해 사용할 수 있는 인터페이스를 제공하고 있습니다. [참고 : custom resource]
# cr 사용 예
# 아래 yaml파일을 이용해 kubernetes api server에 생성해달라고 요청
---
apiVersion: "extension.example.com/v1"
kind: Hello
metadata:
name: hello-sample
size: 3
위와 같이 LB 생성이 가능한 이유는 IRSA 매핑으로 가능합니다.
AWS VPC CNI 사용으로 NLB에서 POD IP로 바로 통신이 됩니다.
AWS LoadBalancer Controller 배포 with IRSA
# OIDC 확인
$ aws eks describe-cluster --name $CLUSTER_NAME --query "cluster.identity.oidc.issuer" --output text
https://oidc.eks.ap-northeast-2.amazonaws.com/id/43383A69A54F954C2526319D6894E277
$ aws iam list-open-id-connect-providers | jq
# IAM Policy (AWSLoadBalancerControllerIAMPolicy) 생성
$ 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
# 생성된 IAM Policy Arn 확인
$ aws iam list-policies --scope Local
$ aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy
$ aws iam get-policy --policy-arn arn:aws:iam::$ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy --query 'Policy.Arn'
# AWS Load Balancer Controller를 위한 ServiceAccount를 생성 >> 자동으로 매칭되는 IAM Role 을 CloudFormation 으로 생성됨!
# IAM 역할 생성. AWS Load Balancer Controller의 kube-system 네임스페이스에 aws-load-balancer-controller라는 Kubernetes 서비스 계정을 생성하고 IAM 역할의 이름으로 Kubernetes 서비스 계정에 주석을 답니다
$ 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
2023-05-06 16:22:24 [ℹ] 1 iamserviceaccount (kube-system/aws-load-balancer-controller) was included (based on the include/exclude rules)
2023-05-06 16:22:24 [!] metadata of serviceaccounts that exist in Kubernetes will be updated, as --override-existing-serviceaccounts was set
2023-05-06 16:22:24 [ℹ] 1 task: {
2 sequential sub-tasks: {
create IAM role for serviceaccount "kube-system/aws-load-balancer-controller",
create serviceaccount "kube-system/aws-load-balancer-controller",
} }2023-05-06 16:22:24 [ℹ] building iamserviceaccount stack "eksctl-eks-hayley-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2023-05-06 16:22:25 [ℹ] deploying stack "eksctl-eks-hayley-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2023-05-06 16:22:25 [ℹ] waiting for CloudFormation stack "eksctl-eks-hayley-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2023-05-06 16:22:55 [ℹ] waiting for CloudFormation stack "eksctl-eks-hayley-addon-iamserviceaccount-kube-system-aws-load-balancer-controller"
2023-05-06 16:22:55 [ℹ] created serviceaccount "kube-system/aws-load-balancer-controller"
## 서비스 어카운트 확인
$ kubectl get serviceaccounts -n kube-system aws-load-balancer-controller -o yaml | yh
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::90XXXXXXX:role/eksctl-eks-hayley-addon-iamserviceaccount-ku-Role1-4VRJ2BM3XXJV
creationTimestamp: "2023-05-06T07:22:55Z"
labels:
app.kubernetes.io/managed-by: eksctl
name: aws-load-balancer-controller
namespace: kube-system
resourceVersion: "22700"
uid: 67ac4a9b-c606-4f45-a1f8-888ad3f2eabd
# Helm Chart 설치
$ 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
## 설치 확인
$ kubectl get crd
NAME CREATED AT
eniconfigs.crd.k8s.amazonaws.com 2023-05-06T05:05:53Z
ingressclassparams.elbv2.k8s.aws 2023-05-06T07:23:43Z
securitygrouppolicies.vpcresources.k8s.aws 2023-05-06T05:05:55Z
targetgroupbindings.elbv2.k8s.aws 2023-05-06T07:23:43Z
$ kubectl get deployment -n kube-system aws-load-balancer-controller
NAME READY UP-TO-DATE AVAILABLE AGE
aws-load-balancer-controller 2/2 2 2 19s
$ kubectl describe deploy -n kube-system aws-load-balancer-controller
$ kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
Service Account: aws-load-balancer-controller
# 클러스터롤, 롤 확인
$ kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
Name: aws-load-balancer-controller-rolebinding
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.5.1
helm.sh/chart=aws-load-balancer-controller-1.5.2
Annotations: meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
Role:
Kind: ClusterRole
Name: aws-load-balancer-controller-role
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount aws-load-balancer-controller kube-system
$ kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
...
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
...
ingresses [] [] [get list patch update watch]
services [] [] [get list patch update watch]
...
pods [] [] [get list watch]
...
pods/status [] [] [update patch]
services/status [] [] [update patch]
targetgroupbindings/status [] [] [update patch]
ingresses.elbv2.k8s.aws/status [] [] [update patch]
...
생성된 IAM Role 신뢰 관계 확인
서비스/파드 배포 테스트 with NLB
# 모니터링
$ watch -d kubectl get pod,svc,ep
# 작업용 EC2 - 디플로이먼트 & 서비스 생성
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/2/echo-service-nlb.yaml
$ cat echo-service-nlb.yaml | yh
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
$ kubectl apply -f echo-service-nlb.yaml
# 확인
$ kubectl get deploy,pod
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-echo 2/2 2 2 16s
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-5c4856dfd6-lwcbn 1/1 Running 0 16s
pod/deploy-echo-5c4856dfd6-qkv45 1/1 Running 0 16s
$ kubectl get svc,ep,ingressclassparams,targetgroupbindings
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 146m
service/svc-nlb-ip-type LoadBalancer 10.100.217.66 k8s-default-svcnlbip-5a2706bc39-26b84645c05d8e4b.elb.ap-northeast-2.amazonaws.com 80:30155/TCP 38s
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.2.56:443,192.168.3.99:443 146m
endpoints/svc-nlb-ip-type 192.168.1.248:8080,192.168.2.140:8080 38s
NAME GROUP-NAME SCHEME IP-ADDRESS-TYPE AGE
ingressclassparams.elbv2.k8s.aws/alb 8m9s
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
targetgroupbinding.elbv2.k8s.aws/k8s-default-svcnlbip-f1a8f7cbd7 svc-nlb-ip-type 80 ip 35s
$ kubectl get targetgroupbindings -o json | jq
# AWS ELB(NLB) 정보 확인
$ aws elbv2 describe-load-balancers | jq
$ aws elbv2 describe-load-balancers --query 'LoadBalancers[*].State.Code' --output text
# 웹 접속 주소 확인
$ kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Pod Web URL = http://"$1 }'
Pod Web URL = http://k8s-default-svcnlbip-5a2706bc39-26b84645c05d8e4b.elb.ap-northeast-2.amazonaws.com
# 파드 로깅 모니터링
kubectl logs -l app=deploy-websrv -f
# 분산 접속 확인
$ NLB=$(kubectl get svc svc-nlb-ip-type -o jsonpath={.status.loadBalancer.ingress[0].hostname})
$ curl -s $NLB
$ for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
59 Hostname: deploy-echo-5c4856dfd6-qkv45
41 Hostname: deploy-echo-5c4856dfd6-lwcbn
# 지속적인 접속 시도 : 아래 상세 동작 확인 시 유용(패킷 덤프 등)
$ while true; do curl -s --connect-timeout 1 $NLB | egrep 'Hostname|client_address'; echo "----------" ; date "+%Y-%m-%d %H:%M:%S" ; sleep 1; done
2023-05-06 16:35:14
Hostname: deploy-echo-5c4856dfd6-lwcbn
client_address=192.168.2.17
----------
2023-05-06 16:35:15
Hostname: deploy-echo-5c4856dfd6-lwcbn
client_address=192.168.2.17
----------
2023-05-06 16:35:16
Hostname: deploy-echo-5c4856dfd6-lwcbn
client_address=192.168.2.17
----------
2023-05-06 16:35:17
Hostname: deploy-echo-5c4856dfd6-lwcbn
client_address=192.168.2.17
----------
2023-05-06 16:35:18
Hostname: deploy-echo-5c4856dfd6-qkv45
client_address=192.168.2.17
- AWS NLB의 대상 그룹 확인 : IP를 확인해보자
- 파드 2개 → 1개 → 3개 설정 시 동작 : auto discovery ← 어떻게 가능할까? aws lb controller가 IRSA로 적절한 권한을 가지고 있기 때문?!
# 작업용 EC2 - 파드 1개 설정
$ kubectl scale deployment deploy-echo --replicas=1
# 확인
$ kubectl get deploy,pod,svc,ep
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-echo 1/1 1 1 4m34s
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-5c4856dfd6-lwcbn 1/1 Running 0 4m34s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 150m
service/svc-nlb-ip-type LoadBalancer 10.100.217.66 k8s-default-svcnlbip-5a2706bc39-26b84645c05d8e4b.elb.ap-northeast-2.amazonaws.com 80:30155/TCP 4m34s
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.2.56:443,192.168.3.99:443 150m
endpoints/svc-nlb-ip-type 192.168.2.140:8080 4m34s
$ curl -s $NLB
$ for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
# 작업용 EC2 - 파드 3개 설정
$ kubectl scale deployment deploy-echo --replicas=3
# 확인
$ kubectl get deploy,pod,svc,ep
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/deploy-echo 2/3 3 2 5m40s
NAME READY STATUS RESTARTS AGE
pod/deploy-echo-5c4856dfd6-9rfr8 0/1 ContainerCreating 0 4s
pod/deploy-echo-5c4856dfd6-lwcbn 1/1 Running 0 5m40s
pod/deploy-echo-5c4856dfd6-pbtpz 1/1 Running 0 4s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 151m
service/svc-nlb-ip-type LoadBalancer 10.100.217.66 k8s-default-svcnlbip-5a2706bc39-26b84645c05d8e4b.elb.ap-northeast-2.amazonaws.com 80:30155/TCP 5m40s
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.2.56:443,192.168.3.99:443 151m
endpoints/svc-nlb-ip-type 192.168.1.143:8080,192.168.2.140:8080 5m40s
$ curl -s $NLB
$ for i in {1..100}; do curl -s --connect-timeout 1 $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
#
$ kubectl describe deploy -n kube-system aws-load-balancer-controller | grep 'Service Account'
Service Account: aws-load-balancer-controller
# [AWS LB Ctrl] 클러스터 롤 바인딩 정보 확인
$ kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
Name: aws-load-balancer-controller-rolebinding
Labels: app.kubernetes.io/instance=aws-load-balancer-controller
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=aws-load-balancer-controller
app.kubernetes.io/version=v2.5.1
helm.sh/chart=aws-load-balancer-controller-1.5.2
Annotations: meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
Role:
Kind: ClusterRole
Name: aws-load-balancer-controller-role
Subjects:
Kind Name Namespace
---- ---- ---------
ServiceAccount aws-load-balancer-controller kube-system
# [AWS LB Ctrl] 클러스터롤 확인
$ kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
NLB IP Target & Proxy Protocol v2 활성화 : NLB에서 바로 파드로 인입 및 ClientIP 확인 설정
# 생성
$ cat <<EOF | kubectl create -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: gasida-web
spec:
replicas: 1
selector:
matchLabels:
app: gasida-web
template:
metadata:
labels:
app: gasida-web
spec:
terminationGracePeriodSeconds: 0
containers:
- name: gasida-web
image: gasida/httpd:pp
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type-pp
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-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-proxy-protocol: "*"
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
selector:
app: gasida-web
EOF
$ kubectl get svc,ep
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 156m
service/svc-nlb-ip-type LoadBalancer 10.100.217.66 k8s-default-svcnlbip-5a2706bc39-26b84645c05d8e4b.elb.ap-northeast-2.amazonaws.com 80:30155/TCP 11m
service/svc-nlb-ip-type-pp LoadBalancer 10.100.116.100 k8s-default-svcnlbip-0808796075-1eef7615b36dbd33.elb.ap-northeast-2.amazonaws.com 80:31065/TCP 5s
NAME ENDPOINTS AGE
endpoints/kubernetes 192.168.2.56:443,192.168.3.99:443 156m
endpoints/svc-nlb-ip-type 192.168.1.143:8080,192.168.2.140:8080,192.168.3.46:8080 11m
endpoints/svc-nlb-ip-type-pp <none> 5s
$ kubectl describe svc svc-nlb-ip-type-pp
Name: svc-nlb-ip-type-pp
Namespace: default
Labels: <none>
Annotations: service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
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
Selector: app=gasida-web
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.100.116.100
IPs: 10.100.116.100
LoadBalancer Ingress: k8s-default-svcnlbip-0808796075-1eef7615b36dbd33.elb.ap-northeast-2.amazonaws.com
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31065/TCP
Endpoints: <none>
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfullyReconciled 3s service Successfully reconciled
$ kubectl describe svc svc-nlb-ip-type-pp | grep Annotations: -A5
Annotations: service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: true
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
Selector: app=gasida-web
Type: LoadBalancer
# apache에 proxy protocol 활성화 확인
$ kubectl exec deploy/gasida-web -- apachectl -t -D DUMP_MODULES
Loaded Modules:
core_module (static)
so_module (static)
http_module (static)
mpm_event_module (shared)
authn_file_module (shared)
authn_core_module (shared)
authz_host_module (shared)
authz_groupfile_module (shared)
authz_user_module (shared)
authz_core_module (shared)
access_compat_module (shared)
auth_basic_module (shared)
reqtimeout_module (shared)
filter_module (shared)
mime_module (shared)
log_config_module (shared)
env_module (shared)
headers_module (shared)
setenvif_module (shared)
version_module (shared)
remoteip_module (shared)
unixd_module (shared)
status_module (shared)
autoindex_module (shared)
dir_module (shared)
alias_module (shared)
$ kubectl exec deploy/gasida-web -- cat /usr/local/apache2/conf/httpd.conf
7. Ingress
- 인그레스 : 클러스터 내부의 서비스(ClusterIP, NodePort, Loadbalancer)를 외부로 노출(HTTP/HTTPS) — Web Proxy 역할
서비스/파드 배포 테스트 with Ingress(ALB)
# 게임 파드와 Service, Ingress 배포
$ curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/3/ingress1.yaml
$ cat ingress1.yaml | yh
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
$ kubectl apply -f ingress1.yaml
# 모니터링
$ watch -d kubectl get pod,ingress,svc,ep -n game-2048
# 생성 확인
$ kubectl get-all -n game-2048
NAME NAMESPACE AGE
configmap/kube-root-ca.crt game-2048 71s
endpoints/service-2048 game-2048 71s
pod/deployment-2048-6bc9fd6bf5-kxbn8 game-2048 71s
pod/deployment-2048-6bc9fd6bf5-q4hfg game-2048 71s
serviceaccount/default game-2048 71s
service/service-2048 game-2048 71s
deployment.apps/deployment-2048 game-2048 71s
replicaset.apps/deployment-2048-6bc9fd6bf5 game-2048 71s
endpointslice.discovery.k8s.io/service-2048-r9hz4 game-2048 71s
targetgroupbinding.elbv2.k8s.aws/k8s-game2048-service2-2f66a3a673 game-2048 67s
ingress.networking.k8s.io/ingress-2048 game-2048 71s
$ kubectl get ingress,svc,ep,pod -n game-2048
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/ingress-2048 alb * k8s-game2048-ingress2-5ebbd98053-468916540.ap-northeast-2.elb.amazonaws.com 80 91s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/service-2048 NodePort 10.100.220.43 <none> 80:31342/TCP 91s
NAME ENDPOINTS AGE
endpoints/service-2048 192.168.2.74:80,192.168.3.203:80 91s
NAME READY STATUS RESTARTS AGE
pod/deployment-2048-6bc9fd6bf5-kxbn8 1/1 Running 0 91s
pod/deployment-2048-6bc9fd6bf5-q4hfg 1/1 Running 0 91s
kubectl get targetgroupbindings -n game-2048
NAME SERVICE-NAME SERVICE-PORT TARGET-TYPE AGE
k8s-game2048-service2-e48050abac service-2048 80 ip 87s
# Ingress 확인
$ kubectl describe ingress -n game-2048 ingress-2048
Name: ingress-2048
Labels: <none>
Namespace: game-2048
Address: k8s-game2048-ingress2-5ebbd98053-468916540.ap-northeast-2.elb.amazonaws.com
Ingress Class: alb
Default backend: <default>
Rules:
Host Path Backends
---- ---- --------
*
/ service-2048:80 (192.168.2.74:80,192.168.3.203:80)
Annotations: alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfullyReconciled 103s ingress Successfully reconciled
# 게임 접속 : ALB 주소로 웹 접속
$ kubectl get ingress -n game-2048 ingress-2048 -o jsonpath={.status.loadBalancer.ingress[0].hostname} | awk '{ print "Game URL = http://"$1 }'
Game URL = http://k8s-game2048-ingress2-5ebbd98053-468916540.ap-northeast-2.elb.amazonaws.com
# 파드 IP 확인
$ kubectl get pod -n game-2048 -owide
kubectl get pod -n game-2048 -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deployment-2048-6bc9fd6bf5-kxbn8 1/1 Running 0 2m17s 192.168.2.74 ip-192-168-2-173.ap-northeast-2.compute.internal <none> <none>
deployment-2048-6bc9fd6bf5-q4hfg 1/1 Running 0 2m17s 192.168.3.203 ip-192-168-3-94.ap-northeast-2.compute.internal <none> <none>
- ALB 대상 그룹에 등록된 대상 확인 : ALB에서 파드 IP로 직접 전달
- 파드 3개로 증가
# 터미널1
$ watch kubectl get pod -n game-2048
# 터미널2 : 파드 3개로 증가
$ kubectl scale deployment -n game-2048 deployment-2048 --replicas 3
$ kubectl get pod -n game-2048 -owide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
deployment-2048-6bc9fd6bf5-kxbn8 1/1 Running 0 2m51s 192.168.2.74 ip-192-168-2-173.ap-northeast-2.compute.internal <none> <none>
deployment-2048-6bc9fd6bf5-q4hfg 1/1 Running 0 2m51s 192.168.3.203 ip-192-168-3-94.ap-northeast-2.compute.internal <none> <none>
deployment-2048-6bc9fd6bf5-qsqnk 0/1 ContainerCreating 0 4s <none> ip-192-168-1-31.ap-northeast-2.compute.internal <none> <none>
8. ExternalDNS : 참고
- K8S 서비스/인그레스 생성 시 도메인을 설정하면, AWS(Route 53), Azure(DNS), GCP(Cloud DNS) 에 A 레코드(TXT 레코드)로 자동 생성/삭제됩니다.
[도전과제3] Security Group for Pod : 파드별 보안그룹 적용해보기 - 링크 Workshop
[도전과제4] 게임서버의 트래픽(UDP)를 서비스(NLB)를 통해 인입 설정 - 링크
[도전과제5] Multiple Ingress pattern : 여러 Ingress 를 하나의 ALB에서 처리 할 수 있게 설정 - 링크
9. Istio
서비스 메시(Service Mesh)
- 마이크로서비스 간에 매시 형태의 통신이나 그 경로를 제어 하기 위한 개념입니다. — 예) 이스티오(Istio), 링커드(Linkerd), AWS App Mesh
- 파드 간 통신 경로에 프록시를 놓고 트래픽 모니터링이나 트래픽 컨트롤이 가능합니다.
Istio
- ‘구글 IBM 리프트(Lyft)’가 중심이 되어 개발하고 있는 오픈 소스 소프트웨어이며, C++ 로 만들어진 엔보이(Envoy)를 사용하여 서비스 매시를 구성합니다.
Istio 구성요소와 envoy : 컨트롤 플레인(istiod) , 데이터 플레인(istio-proxy > envoy)
- istiod : Pilot(데이터 플레인과 통신하면서 라우팅 규칙을 동기화, ADS), Gally(Istio 와 K8S 연동, Endpoint 갱신 등), Citadel(연결 암호화, 인증서관리 등)
- Envoy proxy : C++ 구현된 고성능 프록시, 네트워크의 투명성을 목표, 다양한 필터체인 지원(L3/L4, HTTP L7), 동적 configuration API 제공 — 링크
- istio는 각 파드 안에 사이드카로 Envoy Proxy가 들어가 있고 모든 마이크로서비스간 통신은 엔보이를 통과하여, 메트릭을 수집하거나 트래픽 컨트롤을 할 수 있음
- 트래픽 컨트롤을 하기위해 엔보이 프록시에 전송 룰을 설정 → 컨트롤 플레인의 istio가 정의된 정보를 기반으로 Envoy 설정을 하게 함
- 마이크로서비스 간의 통신을 mutual TLS 인증(mTLS)으로 서로 TLS 인증으로 암호화 할 수 있음
- 각 애플리케이션은 파드 내의 Envoy Proxy에 접속하기 위해 localhost 에 TCP 접속을 함
Envoy : L7 Proxy , Istio 의 Sidecar proxy 로 사용
# 작업용 EC2에 Envoy 설치
$ sudo rpm --import 'https://rpm.dl.getenvoy.io/public/gpg.CF716AF503183491.key'
$ curl -sL 'https://rpm.dl.getenvoy.io/public/config.rpm.txt?distro=el&codename=7' > /tmp/tetrate-getenvoy-rpm-stable.repo
$ sudo yum-config-manager --add-repo '/tmp/tetrate-getenvoy-rpm-stable.repo'
$ sudo yum makecache --disablerepo='*' --enablerepo='tetrate-getenvoy-rpm-stable' -y
$ sudo yum install getenvoy-envoy -y
# 확인
$ envoy --version
envoy version: d362e791eb9e4efa8d87f6d878740e72dc8330ac/1.18.2/clean-getenvoy-76c310e-envoy/RELEASE/BoringSSL
# 데모 config 적용하여 실행
$ curl -O https://www.envoyproxy.io/docs/envoy/latest/_downloads/92dcb9714fb6bc288d042029b34c0de4/envoy-demo.yaml
$ envoy -c envoy-demo.yaml
# 에러 출력되면서 실행 실패
error initializing configuration 'envoy-demo.yaml': Field 'connect_timeout' is missing in: name: "service_envoyproxy_io"
type: LOGICAL_DNS
dns_lookup_family: V4_ONLY
transport_socket {
name: "envoy.transport_sockets.tls"
typed_config {
[type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext] {
sni: "www.envoyproxy.io"
}
}
183412668: "envoy.api.v2.core.TransportSocket"
}
load_assignment {
cluster_name: "service_envoyproxy_io"
endpoints {
lb_endpoints {
endpoint {
address {
socket_address {
address: "www.envoyproxy.io"
port_value: 443
}
}
}
}
}
}
183412668: "envoy.api.v2.Cluster"
# (터미널1) connect_timeout 추가 후 다시 실행
$ sed -i'' -r -e "/dns_lookup_family/a\ connect_timeout: 5s" envoy-demo.yaml
$ envoy -c envoy-demo.yaml
## 출력 로그
[2021-12-13T12:20:25.981Z] "GET / HTTP/1.1" 200 - 0 4472 1140 1080 "-" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.93 Safari/537.36" "a6999dc1-5e5e-4029-89b7-331b081fca27" "www.envoyproxy.io" "52.220.193.16:443"
[2021-12-13T12:22:21.634Z] "GET / HTTP/1.1" 200 - 0 17228 247 121 "-" "curl/7.74.0" "39b7b07d-3f79-4e61-81b4-2944bd041535" "www.envoyproxy.io" "167.99.78.230:443"
# (터미널2) 정보 확인
$ ss -tnlp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 100 127.0.0.1:25 0.0.0.0:* users:(("master",pid=2204,fd=13))
LISTEN 0 128 127.0.0.1:36675 0.0.0.0:* users:(("containerd",pid=2818,fd=13))
LISTEN 0 128 0.0.0.0:111 0.0.0.0:* users:(("rpcbind",pid=1783,fd=8))
LISTEN 0 128 0.0.0.0:10000 0.0.0.0:* users:(("envoy",pid=31093,fd=31),("envoy",pid=31093,fd=22),("envoy",pid=31093,fd=20))
LISTEN 0 128 0.0.0.0:22 0.0.0.0:* users:(("sshd",pid=2389,fd=3))
LISTEN 0 128 [::]:111 [::]:* users:(("rpcbind",pid=1783,fd=11))
LISTEN 0 128 [::]:22 [::]:* users:(("sshd",pid=2389,fd=4))
# 접속 테스트
$ curl -s http://127.0.0.1:10000 | grep -o "<title>.*</title>"
<title>Envoy Proxy - Home</title>
# 자신의PC(웹브라우저)에서 작업용EC2 접속 확인 >> 어느 사이트로 접속이 되는가?
$ echo -e "Envoy Proxy Demo = http://$(curl -s ipinfo.io/ip):10000"
Envoy Proxy Demo = http://3.36.67.161:10000
# 연결 정보 확인
$ ss -tnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 192.168.1.100:22 121.141.152.14:56223 users:(("sshd",pid=5311,fd=3),("sshd",pid=5294,fd=3))
ESTAB 0 0 192.168.1.100:22 121.141.152.14:56767 users:(("sshd",pid=5076,fd=3),("sshd",pid=5057,fd=3))
ESTAB 0 0 192.168.1.100:39530 18.139.194.139:443 users:(("envoy",pid=31093,fd=35))
ESTAB 0 0 192.168.1.100:34296 13.228.199.255:443 users:(("envoy",pid=31093,fd=33))
ESTAB 0 0 192.168.1.100:10000 121.141.152.14:57613 users:(("envoy",pid=31093,fd=32))
ESTAB 0 0 192.168.1.100:10000 121.141.152.14:57614 users:(("envoy",pid=31093,fd=34))
# (터미널1) envoy 실행 취소(CTRL+C) 후 (관리자페이지) 설정 덮어쓰기 - 링크
$ cat <<EOT> envoy-override.yaml
admin:
address:
socket_address:
address: 0.0.0.0
port_value: 9902
EOT
$ envoy -c envoy-demo.yaml --config-yaml "$(cat envoy-override.yaml)"
# 웹브라우저에서 http://192.168.10.254:9902 접속 확인!
# 자신의PC(웹브라우저)에서 작업용EC2 접속 확인 >> 어느 사이트로 접속이 되는가?
$ echo -e "Envoy Proxy Demo = http://$(curl -s ipinfo.io/ip):9902"
Envoy Proxy Demo = http://3.36.67.161:9902
- [도전과제15] Addressing latency and data transfer costs on EKS using Istio - 링크
Istio를 사용하여 EKS 클러스터에서 AZ 간 데이터 전송 비용 해결
- 교차 AZ 데이터 전송 비용을 해결하기 위해 클러스터에서 실행되는 포드는 가용 영역을 기반으로 토폴로지 인식 라우팅을 수행할 수 있어야 합니다.
- 이 데모에서는 노드가 여러 가용 영역에 걸쳐 있는 3노드 EKS 클러스터를 생성한 다음 클러스터에 대해 Istio를 설정해야 합니다.
# eksdtoanalysis.yaml
# EKS 클러스터를 생성하고 클러스터의 일부로 Istio를 설치
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig
metadata:
name: dto-analysis-k8scluster
region: us-west-2
version: "1.24"
managedNodeGroups:
- name: appservers
instanceType: t3.xlarge
desiredCapacity: 3
minSize: 1
maxSize: 4
labels: { role: appservers }
privateNetworking: true
volumeSize: 8
iam:
withAddonPolicies:
imageBuilder: true
autoScaler: true
xRay: true
cloudWatch: true
albIngress: true
ssh:
enableSsm: true
$ eksctl create cluster -f eksdtoanalysis.yaml
# istio 컨트롤러, 수신 및 송신 게이트웨이 설정
$ export ISTIO_VERSION="1.10.0"
curl -L https://istio.io/downloadIstio | ISTIO_VERSION=${ISTIO_VERSION} sh -
cd istio-${ISTIO_VERSION}
sudo cp -v bin/istioctl /usr/local/bin/
istioctl version --remote=false
yes | istioctl install --set profile=demo
# 클러스터가 생성되고 Istio가 제대로 설정되면 애플리케이션을 EKS 클러스터에 설치해야 합니다.
# 다음 Kubernetes 매니페스트 파일을 만듭니다.
$ cat <<EOF>> namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: octank-travel-ns
EOF
cat <<EOF>> deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: zip-lookup-service-deployment
namespace: octank-travel-ns
labels:
app: zip-lookup-service
deployType: live
spec:
replicas: 3
selector:
matchLabels:
app: zip-lookup-service
template:
metadata:
labels:
app: zip-lookup-service
deployType: live
namespace: octank-travel-ns
spec:
containers:
- name: zip-lookup-service
image: public.ecr.aws/m3x5o2v9/istio-tpr-app:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
resources:
requests:
cpu: "250m"
env:
- name: XRAY_URL
value: "xray-service.default:2000"
- name: APPNAME_NAME
value: "zip-service.octank-travel"
- name: ZIPCODE
value: "94582"
- name: HTHRESHOLD
value: "1"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.labels['app']
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
EOF
cat <<EOF>> services.yaml
apiVersion: v1
kind: Service
metadata:
name: zip-lookup-service-local
namespace: octank-travel-ns
spec:
selector:
app: zip-lookup-service
ports:
- port: 80
targetPort: 8080
protocol: TCP
EOF
# EKS 클러스터에 앱을 배포합니다.
$ kubectl apply -f namespace.yaml
$ kubectl apply -f deployment.yaml
$ kubectl apply -f services.yaml
# 서비스 호출을 위한 curl 컨테이너를 배포합니다.
$ kubectl run curl-debug --image=radial/busyboxplus:curl -l "type=testcontainer" -n octank-travel-ns -it --tty sh
# curl -s 169.254.169.254/latest/meta-data/placement/availability-zone
ap-northeast-2a
# exit
# 테스트 컨테이너를 서비스로 노출합니다.
$ kubectl expose pod curl-debug -n octank-travel-ns --port=80 --target-port=8000
# 백엔드 서비스를 호출하는 테스트 스크립트를 설치하고, 서비스 호출에 응답한 포드의 가용 영역을 출력하고 스크립트를 실행합니다.
$ kubectl exec -it --tty -n octank-travel-ns $(kubectl get pod -l "type=testcontainer" -n octank-travel-ns -o jsonpath='{.items[0].metadata.name}') sh
$ cat <<EOF>> test.sh
n=1
while [ \$n -le 5 ]
do
curl -s zip-lookup-service-local.octank-travel-ns
sleep 1
echo "---"
n=\$(( n+1 ))
done
EOF
# chmod +x test.sh
# clear
# ./test.sh
CA - 94582 az - ap-northeast-2b
---
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2c---
CA - 94582 az - ap-northeast-2c---
- 테스트 스크립트 실행의 출력에서 볼 수 있듯이 백엔드 서비스에 대한 호출은 다음 다이어그램과 같이 기본 ClusterIP 기반 서비스 호출 메커니즘을 사용하여 가용 영역 전체에 분산되어 서비스 뒤의 Pod에 도달합니다.
- 토폴로지 인식 라우팅을 활성화하려면 먼저 앱에 대해 Istio를 활성화하고 대상 규칙 개체를 구성하고 앱 서비스와 연결해야 합니다. 이렇게 하면 Istio가 가용 영역 정보를 사용하여 호출을 동일한 가용 영역에서 실행 중인 포드로 라우팅할 수 있습니다.
# Istio를 활성화하려면 먼저 envoy 사이드카 삽입을 활성화하고 포드를 다시 시작해야 합니다.
# namespace를 업데이트해서 사이드카 주입을 활성화합니다.
$ kubectl label namespace octank-travel-ns istio-injection=enabled --overwrite
$ kubectl get po -n octank-travel-ns
NAME READY STATUS RESTARTS AGE
curl-debug 1/1 Running 1 (9m59s ago) 10m
zip-lookup-service-deployment-d8d57849b-dbdss 1/1 Running 0 12m
zip-lookup-service-deployment-d8d57849b-xr5m4 1/1 Running 0 12m
zip-lookup-service-deployment-d8d57849b-z4lst 1/1 Running 0 12m
$ kubectl scale deploy zip-lookup-service-deployment -n octank-travel-ns --replicas=0
$ kubectl scale deploy zip-lookup-service-deployment -n octank-travel-ns --replicas=3
# 앱 포드를 다시 시작한 후. Pod를 나열할 때 Pod당 두 개의 컨테이너가 실행되어야 합니다.
$ kubectl get po -n octank-travel-ns
NAME READY STATUS RESTARTS AGE
curl-debug 1/1 Running 1 (11m ago) 11m
zip-lookup-service-deployment-d8d57849b-5qhnm 2/2 Running 0 43s
zip-lookup-service-deployment-d8d57849b-8btds 2/2 Running 0 43s
zip-lookup-service-deployment-d8d57849b-9bp2k 2/2 Running 0 43s
$ kubectl exec -it --tty -n octank-travel-ns $(kubectl get pod -l "type=testcontainer" -n octank-travel-ns -o jsonpath='{.items[0].metadata.name}') sh
#create a startup script
$ cat <<EOF>> test.sh
n=1
while [ \$n -le 5 ]
do
curl -s zip-lookup-service-local.octank-travel-ns
sleep 1
echo "---"
n=\$(( n+1 ))
done
EOF
chmod +x test.sh
#exit the test container
exit
- 앱 및 테스트 컨테이너 포드 모두에 envoy 사이드카 프록시가 삽입되면 대상 규칙 객체를 생성하여 토폴로지 인식 라우팅을 활성화해야 합니다.
- Istio에서 DestinationRule은 라우팅이 발생한 후 서비스를 위한 트래픽에 적용되는 정책을 정의합니다. 서비스를 구성하는 포드와 같은 워크로드와 통신하는 방법을 지정합니다. 규칙의 예로는 포드 간의 로드 밸런싱 전략, 하나의 포드에 허용할 최대 연결 수 등이 있습니다.
$ cat <<EOF>> destinationrule.yaml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
name: zip-lookup-service-local
namespace: octank-travel-ns
spec:
host: zip-lookup-service-local
trafficPolicy:
outlierDetection:
consecutiveErrors: 7
interval: 30s
baseEjectionTime: 30s
EOF
$ kubectl apply -f destinationrule.yaml
destinationrule.networking.istio.io/zip-lookup-service-local created
# 마지막으로 테스트를 다시 실행합니다.
# 이번에는 서비스에 대한 호출이 테스트 컨테이너가 실행 중인 동일한 가용 영역의 포드로 라우팅되어
# 교차 AZ 데이터 전송 요금이 부과되지 않는 것을 볼 수 있습니다.
$ kubectl exec -it --tty -n octank-travel-ns $(kubectl get pod -l "type=testcontainer" -n octank-travel-ns -o jsonpath='{.items[0].metadata.name}') sh
# curl -s 169.254.169.254/latest/meta-data/placement/availability-zone
# ./test.sh
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2a---
CA - 94582 az - ap-northeast-2a---
# Exit
- 데모에서 볼 수 있듯이 Istio를 활성화하고 앱 서비스를 대상으로 하는 대상 규칙을 배포하면 토폴로지 인식 라우팅이 활성화되고 상당한 교차 AZ 비용을 피할 수 있습니다. 또한 Istio는 동일한 가용 영역에서 실행 중인 포드를 사용할 수 없는 경우 다른 가용 영역에서 실행 중인 포드로 장애 조치하여 다음 다이어그램과 같이 고가용성을 보장합니다.
written in 2023.5.6
https://medium.com/techblog-hayleyshim/aws-eks-networking-698368b77723
'IT > Infra&Cloud' 카테고리의 다른 글
[aws] EKS Observability (0) | 2023.10.29 |
---|---|
[aws] EKS Storage & Node (0) | 2023.10.29 |
[aws] EKS 설치 및 기본 사용 (0) | 2023.10.29 |
[aws] IAM identity providers(SAML) (0) | 2023.10.29 |
[aws] AppStream 2.0 (0) | 2023.10.29 |
- Total
- Today
- Yesterday
- NW
- k8s
- terraform
- operator
- VPN
- 국제 개발 협력
- k8s calico
- controltower
- 혼공파
- cloud
- IaC
- 혼공챌린지
- NFT
- PYTHON
- gcp serverless
- 파이썬
- k8s cni
- 혼공단
- 도서
- OS
- AWS
- security
- EKS
- handson
- S3
- GKE
- AI
- GCP
- cni
- SDWAN
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |