이번 게시글은 가시다님의 AEWS [2기] 스터디 내용을 정리한 포스트 입니다.
이번 게시글은 4 주차의 스터디 내용인 EKS Observability에 대해 살펴봅니다.
EKS 배포
실습을 위해 EKS 클러스터를 배포합니다. AWS 콘솔을 로그인 한 뒤 CloudFormation에서 아래 YAML 파일로 스택을 생성하시면 됩니다. 진행 과정은 아래 링크를 참조해 주세요.
[EKS] Networking
이번 게시글은 가시다님의 AEWS [2기] 스터디 내용을 정리한 포스트 입니다. 이번 게시글은 2 주차의 스터디 내용인 EKS Networking에 대해 살펴봅니다. EKS 원클릭 배포 사전 준비 AWS 계정 SSH 키 페어 IA
junkmm.tistory.com
확인 및 기본설정
Bastion EC2 인스턴스의 공인 IP로 SSH 접근을 시도합니다. 공인 IP는 CloudFormation의 결과에 나오는 출력값 혹은 EC2 메뉴에서 bastion 호스트의 공인 IP를 확인합니다.
# ssh -i "Keyname" ec2-user@"Public IP"
ssh -i hj42700eks.pem ec2-user@3.36.133.15
아래 명령을 통해 Bastion 호스트에서 각 워커노드로 패킷 송,수신 SSH 접속이 가능하도록 설정합니다.
# default 네임스페이스 적용
kubectl ns default
# 노드 정보 확인 : t3.xlarge
kubectl get node --label-columns=node.kubernetes.io/instance-type,eks.amazonaws.com/capacityType,topology.kubernetes.io/zone
eksctl get iamidentitymapping --cluster myeks
# 노드 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
# 노드 보안그룹 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
# 워커 노드 SSH 접속
for node in $N1 $N2 $N3; do ssh -o StrictHostKeyChecking=no ec2-user@$node hostname; done
아래 명령을 통해 AWS LB/ExternalDNS/EBS, kube-ops-view 설치를 진행합니다. MyDomain 값을 본인이 소유한 Route53 Public Domain 주소를 입력해야 합니다.
# ExternalDNS
MyDomain=<자신의 도메인>
echo "export MyDomain=<자신의 도메인>" >> /etc/profile
MyDnzHostedZoneId=$(aws route53 list-hosted-zones-by-name --dns-name "${MyDomain}." --query "HostedZones[0].Id" --output text)
echo $MyDomain, $MyDnzHostedZoneId
curl -s -O https://raw.githubusercontent.com/gasida/PKOS/main/aews/externaldns.yaml
MyDomain=$MyDomain MyDnzHostedZoneId=$MyDnzHostedZoneId envsubst < externaldns.yaml | kubectl apply -f -
# kube-ops-view
helm repo add geek-cookbook https://geek-cookbook.github.io/charts/
helm install kube-ops-view geek-cookbook/kube-ops-view --version 1.2.2 --set env.TZ="Asia/Seoul" --namespace kube-system
kubectl patch svc -n kube-system kube-ops-view -p '{"spec":{"type":"LoadBalancer"}}'
kubectl annotate service kube-ops-view -n kube-system "external-dns.alpha.kubernetes.io/hostname=kubeopsview.$MyDomain"
echo -e "Kube Ops View URL = http://kubeopsview.$MyDomain:8080/#scale=1.5"
# AWS LB Controller
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
# EBS csi driver 설치 확인
eksctl get addon --cluster ${CLUSTER_NAME}
kubectl get pod -n kube-system -l 'app in (ebs-csi-controller,ebs-csi-node)'
kubectl get csinodes
# gp3 스토리지 클래스 생성
kubectl get sc
kubectl apply -f https://raw.githubusercontent.com/gasida/PKOS/main/aews/gp3-sc.yaml
kubectl get sc
EKS Logging
Control Plain
AWS는 EKS의 컨트롤 플레인 영역을 관리 해 줍니다. 그 중 컨트롤 플레인에 대한 Logging 기능도 제공 합니다. CF(Cloud Formation)으로 배포한 EKS 스택은 기본적으로 Logging 수집을 하고 있지 않아 수동으로 로깅 옵션을 켜야만 Logging 수집을 진행할 수 있으며, Cloud Watch 대시보드에서 Logging에 대한 정보를 확인할 수 있습니다.
먼저 아래와 같이 EKS 대시보드에서 Logging이 Off된 것을 확인합니다. AWS 콘솔 -> Elastic kubernetes service -> myeks -> 관찰성 하단에서 확인 가능합니다.
로깅 관리 버튼을 통해 각 Logging 영역을 활성화 할 수도 있으나, 아래 명령을 통해 EKS의 컨트롤 플레인 Logging을 활성화 합니다.
aws eks update-cluster-config --region $AWS_DEFAULT_REGION --name $CLUSTER_NAME \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
기능이 활성화 되기까지 잠시 기다리고, 다시 확인 해보면 컨트롤 플레인 Logging이 활성화 된 것을 확인할 수 있습니다.
Logging이 활성화 되면 Cloud Watch에서 내용을 확인할 수 있습니다. 아래와 같이 Cloud Watch에 로그 그룹이 생성된 것을 확인할 수 있습니다.
해당 로그 그룹을 클릭하여 로그 스트림을 확인 해 보면 컨트롤 플레인의 API, Authenticator, Scheduler 등 구성요소에 대한 로그를 수집하고 있는 것을 확인할 수 있습니다.
각 로그 스트림을 클릭하면 상세 로그 내역을 확인할 수 있습니다.
이와 동시에 CLI 로 터미널에서 로그를 확인할 수 있습니다.
# 신규 로그를 바로 출력
aws logs tail /aws/eks/$CLUSTER_NAME/cluster --follow
# 로그 스트림이름
aws logs tail /aws/eks/$CLUSTER_NAME/cluster --log-stream-name-prefix kube-controller-manager --follow
Cloud Watch의 Log Insight 기능을 통해 컨트롤 플레인 로그의 내용을 분석할 수 있습니다.
EKS 컨트롤 플레인에 대한 Log Insight의 예시 쿼리 입니다.
# EC2 Instance가 NodeNotReady 상태인 로그 검색
fields @timestamp, @message
| filter @message like /NodeNotReady/
| sort @timestamp desc
# kube-apiserver-audit 로그에서 userAgent 정렬해서 아래 4개 필드 정보 검색
fields userAgent, requestURI, @timestamp, @message
| filter @logStream ~= "kube-apiserver-audit"
| stats count(userAgent) as count by userAgent
| sort count desc
#
fields @timestamp, @message
| filter @logStream ~= "kube-scheduler"
| sort @timestamp desc
#
fields @timestamp, @message
| filter @logStream ~= "authenticator"
| sort @timestamp desc
#
fields @timestamp, @message
| filter @logStream ~= "kube-controller-manager"
| sort @timestamp desc
아래 명령을 통해 로깅 수집을 종료 합니다.
# EKS Control Plane 로깅(CloudWatch Logs) 비활성화
eksctl utils update-cluster-logging --cluster $CLUSTER_NAME --region $AWS_DEFAULT_REGION --disable-types all --approve
# 로그 그룹 삭제
aws logs delete-log-group --log-group-name /aws/eks/$CLUSTER_NAME/cluster
General Pod
EKS 상 배포된 파드에 대한 로깅 구성을 확인 해 보겠습니다. 우선 아래의 코드를 통해 nginx 파드를 배포합니다.
# NGINX 웹서버 배포
helm repo add bitnami https://charts.bitnami.com/bitnami
# 사용 리전의 인증서 ARN 확인
CERT_ARN=$(aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text)
echo $CERT_ARN
# 도메인 확인
echo $MyDomain
# 파라미터 파일 생성 : 인증서 ARN 지정하지 않아도 가능! 혹시 https 리스너 설정 안 될 경우 인증서 설정 추가(주석 제거)해서 배포 할 것
cat <<EOT > nginx-values.yaml
service:
type: NodePort
networkPolicy:
enabled: false
ingress:
enabled: true
ingressClassName: alb
hostname: nginx.$MyDomain
pathType: Prefix
path: /
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
#alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: $CLUSTER_NAME-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
EOT
cat nginx-values.yaml | yh
# 배포
helm install nginx bitnami/nginx --version 15.14.0 -f nginx-values.yaml
# 확인
kubectl get ingress,deploy,svc,ep nginx
kubectl get targetgroupbindings # ALB TG 확인
# 접속 주소 확인 및 접속
echo -e "Nginx WebServer URL = https://nginx.$MyDomain"
curl -s https://nginx.$MyDomain
아래와 같이 https://nginx.junkmm.site 도메인으로 nginx 웹페이지 접속 가능함을 확인합니다. https 접속을 위해 인증서를 사용함으로 ACM을 통해 Route53에 등록된 도메인에 대한 인증서를 발급 해야 합니다.
Access 로그를 적재하기 위해 터미널에서 아래 명령을 통해 nginx 웹서버로 반복 접근합니다.
while true; do curl -s https://nginx.$MyDomain -I | head -n 1; date; sleep 1; done
아래 kubectl 명령을 통해 nginx 파드에 로그가 쌓이고 있는 것을 확인할 수 있습니다.
kubectl logs deploy/nginx -f
그리고 해당 로그는 nginx 파드(컨테이너)의 특정 경로(/opt/bitnami/nginx/logs/)에 적재됨을 알 수 있습니다.
kubectl exec -it deploy/nginx -- ls -l /opt/bitnami/nginx/logs/
Defaulted container "nginx" out of: nginx, preserve-logs-symlinks (init)
total 0
lrwxrwxrwx 1 1001 1001 11 Mar 30 10:22 access.log -> /dev/stdout
lrwxrwxrwx 1 1001 1001 11 Mar 30 10:22 error.log -> /dev/stderr
nginx 컨테이너 이미지의 Dockerfile을 확인해 보면 stdout, stderr에 대한 메시지를 ln 명령을 통해 특정 경로로 바로가기 링크를 생성하는 것을 확인할 수 있습니다.
# forward request and error logs to docker log collector
RUN ln -sf /dev/stdout /var/log/nginx/access.log \
&& ln -sf /dev/stderr /var/log/nginx/error.log
이를 통해 kubectl을 통한 stdout, stderr에 대한 정보를 확인할 수 있습니다.
만약 파드가 종료되면 해당 파드에 생성되어 있던 로그 정보는 kubectl logs로 조회할 수 없습니다. 그리고, kubelet의 기본 log 파일 저장 공간을 10Mi를 사용하여 10Mi가 초과되는 로그를 확인할 수 없습니다. 위와 같은 이유로 로그 시스템을 구성하여 관리하여야 합니다.
cat /etc/kubernetes/kubelet-config.yaml
...
containerLogMaxSize: 10Mi
EKS에서 제공하는 중앙화 로그 시스템은 이어지는 내용에서 확인 해 보겠습니다.
Container Insights metrics in Amazon CloudWatch & Fluent Bit (Logs)
Amazon가 제공하는 중앙 로그 솔루션으로 CloudWatch, Fluent Bit을 사용합니다. CloudWatch Agent는 EKS에 배포된 파드에 대한 Metric 값을 수집하고, Fluent Bit은 파드의 로그를 수집합니다. 그렇게 수집된 로그는 Cloud Watch로 전송되고 사용자는 대시보드를 통해 확인할 수 있습니다.
사전으로 EKS의 워커노드 내부에 저장된 로그 정보를 확인해 보겠습니다. 아래는 EKS에 배포된 application 로그를 저장하는 경로입니다. Node 1번의 /var/log/containers에 저장되고 있습니다.
ssh ec2-user@$N1 sudo tree /var/log/containers
/var/log/containers
├── aws-load-balancer-controller-5f7b66cdd5-7btgt_kube-system_aws-load-balancer-controller-ca32c22eb01f762d8d1ddd5c0bede1880c46ce0e2e089df626e180922a058dcc.log -> /var/log/pods/kube-system_aws-load-balancer-controller-5f7b66cdd5-7btgt_71a4a82b-886e-456e-a9f0-017cf8878981/aws-load-balancer-controller/0.log
├── aws-node-d4fk6_kube-system_aws-eks-nodeagent-a2b0e8d56ac58637742a59dab5a4fff21177b71144e06315e46c8fe4021dadf5.log -> /var/log/pods/kube-system_aws-node-d4fk6_b8f9ae9f-012a-40a8-8d67-93fa5f20aba2/aws-eks-nodeagent/0.log
├── aws-node-d4fk6_kube-system_aws-node-5b135332b387e4cf79905ba0928f7876d39d171900de9aac6e2b4be4dc5a8c0b.log -> /var/log/pods/kube-system_aws-node-d4fk6_b8f9ae9f-012a-40a8-8d67-93fa5f20aba2/aws-node/0.log
├── aws-node-d4fk6_kube-system_aws-vpc-cni-init-850318fb3e190237d7cc9903f720ce9ad59b6756737110a42b5e7e5c207a249e.log -> /var/log/pods/kube-system_aws-node-d4fk6_b8f9ae9f-012a-40a8-8d67-93fa5f20aba2/aws-vpc-cni-init/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_csi-attacher-28adfb38f525be82721d0b3b78024427763a231ce2a88cbb21f8c9ce81f731e6.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/csi-attacher/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_csi-provisioner-45f8ffbced53f523f38010e51a8b50b7e211a32c50d8425327610e1527d9db3e.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/csi-provisioner/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_csi-resizer-bccd55bd47cb38087f418528f2762d57edbbeb6ba0dc5834ae9d61e7c001ad4d.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/csi-resizer/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_csi-snapshotter-0ec41296eb34c161671ef0e093238a29366627422294a04e5ab468af577c5e7a.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/csi-snapshotter/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_ebs-plugin-3a251d1872d1b6d42b757d802c3c56a71647e230e3139fab20a18bbc46097c1f.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/ebs-plugin/0.log
├── ebs-csi-controller-7df5f479d4-mz4vw_kube-system_liveness-probe-c47f7a3033ce89b8f0e47a397ec17d1a3caefac79a86536c1b80c8ae803f9885.log -> /var/log/pods/kube-system_ebs-csi-controller-7df5f479d4-mz4vw_b66ba0fa-569b-4238-99a9-b1b71c4cb85d/liveness-probe/0.log
├── ebs-csi-node-nzf2m_kube-system_ebs-plugin-17a32c66baad6fef238ee55158c49c7138a4883ae9ec0dc37c8a8f7c3776384c.log -> /var/log/pods/kube-system_ebs-csi-node-nzf2m_c5081982-2fff-457a-b255-1f236a592a99/ebs-plugin/0.log
├── ebs-csi-node-nzf2m_kube-system_liveness-probe-be3083c336115f49cc69f3f297a65dafa1bf6c63cc71e5dd35180465f0d65549.log -> /var/log/pods/kube-system_ebs-csi-node-nzf2m_c5081982-2fff-457a-b255-1f236a592a99/liveness-probe/0.log
├── ebs-csi-node-nzf2m_kube-system_node-driver-registrar-5e2fee0be7c82c4d27d374f47ec902ed549d1e071014fc0b374ef195bcba5de0.log -> /var/log/pods/kube-system_ebs-csi-node-nzf2m_c5081982-2fff-457a-b255-1f236a592a99/node-driver-registrar/0.log
├── external-dns-74cd6cf798-vpzsv_kube-system_external-dns-b2523d5ac8d45d1488987fc9c696e76fe803c0239c097a6ef79cf843ed190fe0.log -> /var/log/pods/kube-system_external-dns-74cd6cf798-vpzsv_620e12ee-b6b1-4535-9e3c-a77686ec7ff3/external-dns/0.log
└── kube-proxy-z9bp5_kube-system_kube-proxy-4461b66b1b88bc58dac3bf42883297b758c8dff2ab58e757e476f7bf92a33371.log -> /var/log/pods/kube-system_kube-proxy-z9bp5_58f69d78-1b82-4fa4-be84-24a300309155/kube-proxy/0.log
host 로그 소스 경로 입니다.
ssh ec2-user@$N1 sudo tree /var/log/ -L 1
/var/log/
├── amazon
├── audit
├── aws-routed-eni
├── boot.log
├── btmp
├── chrony
├── cloud-init.log
├── cloud-init-output.log
├── containers
├── cron
├── dmesg
├── dmesg.old
├── grubby
├── grubby_prune_debug
├── journal
├── lastlog
├── maillog
├── messages
├── pods
├── sa
├── secure
├── spooler
├── tallylog
├── wtmp
└── yum.log
dataplane 로그 소스 경로 입니다. kubelet, kube-proxy, docker.service와 같은 EKS dataplane 핵심 구성 서비스 입니다.
ssh ec2-user@$N1 sudo tree /var/log/journal -L 1
/var/log/journal
├── ec23176a900a65d36ea89e5c2be53139
└── ec26a3691e607a87769db6ce5a7f6619
CloudWatch Container obserbility 설치
아래 명령어로 EKS amazon-cloudwatch-observability Add-on을 활성화 합니다.
# 설치
aws eks create-addon --cluster-name $CLUSTER_NAME --addon-name amazon-cloudwatch-observability
aws eks list-addons --cluster-name myeks --output table
아래 명령어로 EKS에 배포된 리소스를 확인합니다. daemonset 타입으로 각 워커노드에 1대씩 cloudwatch-agent, fluent-bit가 배포된 것을 확인할 수 있습니다.
kubectl get ds,pod,cm,sa,amazoncloudwatchagent -n amazon-cloudwatch
daemonset의 정보를 자세히 확인 해 보겠습니다.
kubectl describe -n amazon-cloudwatch ds cloudwatch-agent
Volumes 옵션에서 Host Path로 / 디렉토리가 마운트 된 것을 확인할 수 있습니다. 이는 해당 파드가 장악당하면, 워커 노드가 장악 된다는 것과 같아 운영 시 보안에 안전하지 않을 수 있습니다.
다음은 Fluent-bit의 Configmap을 확인 해 보겠습니다. 내용이 길지만 중요한 부분은 INPUT, FILTER, OUTPUT 입니다. INPUT은 Fluent-bit가 수집할 로그 경로를 의미하며 사전에 파악했던 경로인 /var/log/containers 가 잡혀있는 것을 확인할 수 있습니다. 즉 Fluent-bit는 워커 노드의 특정 경로 로그 파일을 수집합니다. OUTPUT 부분은 CloudWatch로 전송하는 구성에 대한 설정 값입니다.
kubectl describe cm fluent-bit-config -n amazon-cloudwatch
Name: fluent-bit-config
# 생략
----
[INPUT]
Name tail
Tag application.*
Exclude_Path /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*
Path /var/log/containers/*.log
multiline.parser docker, cri
DB /var/fluent-bit/state/flb_container.db
Mem_Buf_Limit 50MB
Skip_Long_Lines On
Refresh_Interval 10
Rotate_Wait 30
storage.type filesystem
Read_from_Head ${READ_FROM_HEAD}
# 생략
[OUTPUT]
Name cloudwatch_logs
Match application.*
region ${AWS_REGION}
log_group_name /aws/containerinsights/${CLUSTER_NAME}/application
log_stream_prefix ${HOST_NAME}-
auto_create_group true
extra_user_agent container-insights
그러면 AWS CloudWatch를 확인 해 보겠습니다. 자동적으로 로그 그룹이 생성된 것을 확인할 수 있습니다.
대시보드를 간단히 살펴보도록 하겠습니다. 우선 아래 명령을 통해 nginx 웹서버에 부하를 발생시켜 보도록 하겠습니다.
curl -s https://nginx.$MyDomain
yum install -y httpd
ab -c 500 -n 30000 https://nginx.$MyDomain/
로그 그룹 -> application -> 로그 스트림 -> nginx 필터 -> nginx 클릭을 진행합니다.
수집된 로그들에 대한 정보를 확인할 수 있습니다.
Logs Insight에선 아래 쿼리 문으로 로그를 정제하여 결과를 확인할 수 있습니다.
# Application log errors by container name : 컨테이너 이름별 애플리케이션 로그 오류
# 로그 그룹 선택 : /aws/containerinsights/<CLUSTER_NAME>/application
stats count() as error_count by kubernetes.container_name
| filter stream="stderr"
| sort error_count desc
# All Kubelet errors/warning logs for for a given EKS worker node
# 로그 그룹 선택 : /aws/containerinsights/<CLUSTER_NAME>/dataplane
fields @timestamp, @message, ec2_instance_id
| filter message =~ /.*(E|W)[0-9]{4}.*/ and ec2_instance_id="<YOUR INSTANCE ID>"
| sort @timestamp desc
# Kubelet errors/warning count per EKS worker node in the cluster
# 로그 그룹 선택 : /aws/containerinsights/<CLUSTER_NAME>/dataplane
fields @timestamp, @message, ec2_instance_id
| filter message =~ /.*(E|W)[0-9]{4}.*/
| stats count(*) as error_count by ec2_instance_id
# performance 로그 그룹
# 로그 그룹 선택 : /aws/containerinsights/<CLUSTER_NAME>/performance
# 노드별 평균 CPU 사용률
STATS avg(node_cpu_utilization) as avg_node_cpu_utilization by NodeName
| SORT avg_node_cpu_utilization DESC
# 파드별 재시작(restart) 카운트
STATS avg(number_of_container_restarts) as avg_number_of_container_restarts by PodName
| SORT avg_number_of_container_restarts DESC
# 요청된 Pod와 실행 중인 Pod 간 비교
fields @timestamp, @message
| sort @timestamp desc
| filter Type="Pod"
| stats min(pod_number_of_containers) as requested, min(pod_number_of_running_containers) as running, ceil(avg(pod_number_of_containers-pod_number_of_running_containers)) as pods_missing by kubernetes.pod_name
| sort pods_missing desc
# 클러스터 노드 실패 횟수
stats avg(cluster_failed_node_count) as CountOfNodeFailures
| filter Type="Cluster"
| sort @timestamp desc
# 파드별 CPU 사용량
stats pct(container_cpu_usage_total, 50) as CPUPercMedian by kubernetes.container_name
| filter Type="Container"
| sort CPUPercMedian desc
Cloud Watch의 Container Insight 메뉴 접속 시 아래와 같이 EKS 클러스터 리소스에 대한 상태 확인을 대시보드로 확인할 수 있습니다.
Metrics-server
Metrics-server는 kubelet으로부터 수집한 리소스 메트릭을 수집 및 집계하는 클러스터 애드온 구성 요소 입니다. 노드에 배포된 cAdvisor는 kubelet에 포함된 컨테이너 메트릭을 수집, 집계, 노출하며 요약 정보를 Metrics-server가 여러 워커 노드로 부터 중앙 수집합니다. 사용자 혹은 kube-api는 Metrics-server를 호출하여 워커 노드에 대한 메트릭 정보를 확인할 수 있습니다.
아래 명령어로 EKS 클러스터에 Metrics-server를 배포합니다.
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
배포가 되었으면 아래 명령을 통해 노드들의 리소스 사용량을 확인할 수 있습니다.
kubectl top node
---
NAME CPU(cores) CPU% MEMORY(bytes) MEMORY%
ip-192-168-1-198.ap-northeast-2.compute.internal 71m 1% 832Mi 5%
ip-192-168-2-30.ap-northeast-2.compute.internal 68m 1% 752Mi 5%
ip-192-168-3-40.ap-northeast-2.compute.internal 81m 2% 680Mi 4%
kubectl top pod -n kube-system --sort-by='cpu'
---
NAME CPU(cores) MEMORY(bytes)
kube-ops-view-9cc4bf44c-nrjkr 13m 35Mi
ebs-csi-controller-7df5f479d4-sr2ld 5m 63Mi
aws-node-ccwgp 5m 61Mi
#생략
kubectl top pod -n kube-system --sort-by='memory'
---
NAME CPU(cores) MEMORY(bytes)
ebs-csi-controller-7df5f479d4-sr2ld 5m 63Mi
aws-node-d4fk6 4m 61Mi
aws-node-fvbqx 4m 61Mi
#생략
Prometheus-Stack
Prometheus와 Grafana는 로그 데이터 수집, 시각화 해주는 오픈소스 입니다. 이 두가지를 사용하여 Observility를 구현 해 보겠습니다.
아래 명령으로 Prometheus 스택을 설치 하겠습니다.
# 모니터링
kubectl create ns monitoring
watch kubectl get pod,pvc,svc,ingress -n monitoring
# 사용 리전의 인증서 ARN 확인 : 정상 상태 확인(만료 상태면 에러 발생!)
CERT_ARN=`aws acm list-certificates --query 'CertificateSummaryList[].CertificateArn[]' --output text`
echo $CERT_ARN
# repo 추가
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
# 파라미터 파일 생성
cat <<EOT > monitor-values.yaml
prometheus:
prometheusSpec:
podMonitorSelectorNilUsesHelmValues: false
serviceMonitorSelectorNilUsesHelmValues: false
retention: 5d
retentionSize: "10GiB"
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: gp3
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 30Gi
ingress:
enabled: true
ingressClassName: alb
hosts:
- prometheus.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
grafana:
defaultDashboardsTimezone: Asia/Seoul
adminPassword: prom-operator
ingress:
enabled: true
ingressClassName: alb
hosts:
- grafana.$MyDomain
paths:
- /*
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}, {"HTTP":80}]'
alb.ingress.kubernetes.io/certificate-arn: $CERT_ARN
alb.ingress.kubernetes.io/success-codes: 200-399
alb.ingress.kubernetes.io/load-balancer-name: myeks-ingress-alb
alb.ingress.kubernetes.io/group.name: study
alb.ingress.kubernetes.io/ssl-redirect: '443'
persistence:
enabled: true
type: sts
storageClassName: "gp3"
accessModes:
- ReadWriteOnce
size: 20Gi
defaultRules:
create: false
kubeControllerManager:
enabled: false
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
alertmanager:
enabled: false
EOT
cat monitor-values.yaml | yh
# 배포
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack --version 57.1.0 \
--set prometheus.prometheusSpec.scrapeInterval='15s' --set prometheus.prometheusSpec.evaluationInterval='15s' \
-f monitor-values.yaml --namespace monitoring
아래 명령어로 잘 배포 되었는지 확인합니다.
# 확인
## alertmanager-0 : 사전에 정의한 정책 기반(예: 노드 다운, 파드 Pending 등)으로 시스템 경고 메시지를 생성 후 경보 채널(슬랙 등)로 전송
## grafana : 프로메테우스는 메트릭 정보를 저장하는 용도로 사용하며, 그라파나로 시각화 처리
## prometheus-0 : 모니터링 대상이 되는 파드는 ‘exporter’라는 별도의 사이드카 형식의 파드에서 모니터링 메트릭을 노출, pull 방식으로 가져와 내부의 시계열 데이터베이스에 저장
## node-exporter : 노드익스포터는 물리 노드에 대한 자원 사용량(네트워크, 스토리지 등 전체) 정보를 메트릭 형태로 변경하여 노출
## operator : 시스템 경고 메시지 정책(prometheus rule), 애플리케이션 모니터링 대상 추가 등의 작업을 편리하게 할수 있게 CRD 지원
## kube-state-metrics : 쿠버네티스의 클러스터의 상태(kube-state)를 메트릭으로 변환하는 파드
helm list -n monitoring
kubectl get pod,svc,ingress,pvc -n monitoring
kubectl get-all -n monitoring
kubectl get prometheus,servicemonitors -n monitoring
kubectl get crd | grep monitoring
kubectl df-pv
AWS CNI Metrics 수집을 위한 monitoring CRD 리소스를 배포합니다.
# PodMonitor 배포
cat <<EOF | kubectl create -f -
apiVersion: monitoring.coreos.com/v1
kind: PodMonitor
metadata:
name: aws-cni-metrics
namespace: kube-system
spec:
jobLabel: k8s-app
namespaceSelector:
matchNames:
- kube-system
podMetricsEndpoints:
- interval: 30s
path: /metrics
port: metrics
selector:
matchLabels:
k8s-app: aws-node
EOF
# metrics url 접속 확인
curl -s $N1:61678/metrics | grep '^awscni'
Prometheus 인그레스 정보를 확인하여 HOST에 표시된 URL로 접근합니다.
kubectl get ingress -n monitoring kube-prometheus-stack-prometheus
NAME CLASS HOSTS ADDRESS PORTS AGE
kube-prometheus-stack-prometheus alb prometheus.junkmm.site myeks-ingress-alb-1359038265.ap-northeast-2.elb.amazonaws.com 80 10m
Prometheus는 로그 수집 대상의 /metrics 엔드포인트 경로에 접근하여 http get 방식으로 메트릭을 수집하고 TSDB(시계열) 형식으로 저장합니다. 아래 명령어로 워커노드 1의 metrics 값을 확인할 수 있습니다.
ssh ec2-user@$N1 curl -s localhost:9100/metrics
그리고 Prometheus의 Status -> Target을 확인 해보면 각 워커노드의 /metrics를 호출하고 상태가 UP인 것을 확인할 수 있으며, Prometheus가 주기적으로 메트릭을 수집, 저장하고 있음을 알 수 있습니다.
이러한 Target 설정 구성 정보는 Status -> Configuration에서 확인 가능합니다.
Prometheus의 Graph 메뉴에선 수집된 Metrics을 기반으로 쿼리문 작성 하여 그래프로 시각화 하여 보여줍니다.
아래는 전체 클러스터 노드의 CPU 사용량 합계를 시각화 한 것입니다.
1- avg(rate(node_cpu_seconds_total{mode="idle"}[1m]))
Node-exporter Job 기반의 PromQL을 확인 해보겠습니다.
모든 노드들의 메모리를 확인하는 쿼리문
node_memory_Active_bytes
특정 인스턴스만 조회하고 싶은 경우
node_memory_Active_bytes{instance="192.168.1.198:9100"}
kube-state-metrics Job에 대한 PromQL을 확인 해보겠습니다.
replicas 개수 조회
kube_deployment_status_replicas
deployment 이름이 coredns인 replicas 조회
kube_deployment_status_replicas_available{deployment="coredns"}
애플리케이션 Nginx 웹서버 모니터링 설정에 대해 확인 해보겠습니다. nginx를 helm 설치 한 경우 프로메테우스 익스포터 옵션 설정 시 자동으로 nginx를 프로메테우스 모니터링에 등록할 수 있습니다. 프로메테우스 설정에서 nginx 모니터링 관련 내용을 서비스 모니터 CRD로 추가할 수 있으며, 기존 애플리케이션 파드에 프로메테우스 모니터링을 추가하면 사이드카 방식을 사용하여 Exporter 컨테인를 추가 배포합니다.
그럼 nginx 웹서버에 metrics 수집 설정을 추가 해보겠습니다.
# 파라미터 파일 생성 : 서비스 모니터 방식으로 nginx 모니터링 대상을 등록하고, export 는 9113 포트 사용
cat <<EOT > ~/nginx_metric-values.yaml
metrics:
enabled: true
service:
port: 9113
serviceMonitor:
enabled: true
namespace: monitoring
interval: 10s
EOT
# 배포
helm upgrade nginx bitnami/nginx --reuse-values -f nginx_metric-values.yaml
3분 정도 기다린 후 Promethueus의 Configuration에 nginx Job이 생성되었는지 확인합니다.
nginx의 replicas 개수를 2개로 늘리고 Prometheus에서 PromQL로 간단한 메트릭을 조회 합니다.
현재 Up된 nginx 개수
nginx가 연결 요청받은 개수
nginx의 활성 연결 개수
PromQL의 문법은 아래와 같습니다.
Label Matchers
# 예시
node_memory_Active_bytes
node_memory_Active_bytes{instance="192.168.1.188:9100"}
node_memory_Active_bytes{instance!="192.168.1.188:9100"}
# 정규표현식
node_memory_Active_bytes{instance=~"192.168.+"}
node_memory_Active_bytes{instance=~"192.168.1.+"}
# 다수 대상
node_memory_Active_bytes{instance=~"192.168.1.188:9100|192.168.2.170:9100"}
node_memory_Active_bytes{instance!~"192.168.1.188:9100|192.168.2.170:9100"}
# 여러 조건 AND
kube_deployment_status_replicas_available{namespace="kube-system"}
kube_deployment_status_replicas_available{namespace="kube-system", deployment="coredns"}
Binary Operators
# 산술 이진 연산자 : + - * / * ^
node_memory_Active_bytes
node_memory_Active_bytes/1024
node_memory_Active_bytes/1024/1024
# 비교 이진 연산자 : = = ! = > < > = < =
nginx_http_requests_total
nginx_http_requests_total > 100
nginx_http_requests_total > 10000
# 논리/집합 이진 연산자 : and 교집합 , or 합집합 , unless 차집합
kube_pod_status_ready
kube_pod_container_resource_requests
kube_pod_status_ready == 1
kube_pod_container_resource_requests > 1
kube_pod_status_ready == 1 or kube_pod_container_resource_requests > 1
kube_pod_status_ready == 1 and kube_pod_container_resource_requests > 1
Aggregation Operators
#
node_memory_Active_bytes
# 출력 값 중 Top 3
topk(3, node_memory_Active_bytes)
# 출력 값 중 하위 3
bottomk(3, node_memory_Active_bytes)
bottomk(3, node_memory_Active_bytes>0)
# node 그룹별: by
node_cpu_seconds_total
node_cpu_seconds_total{mode="user"}
node_cpu_seconds_total{mode="system"}
avg(node_cpu_seconds_total)
avg(node_cpu_seconds_total) by (instance)
avg(node_cpu_seconds_total{mode="user"}) by (instance)
avg(node_cpu_seconds_total{mode="system"}) by (instance)
#
nginx_http_requests_total
sum(nginx_http_requests_total)
sum(nginx_http_requests_total) by (instance)
# 특정 내용 제외하고 출력 : without
nginx_http_requests_total
sum(nginx_http_requests_total) without (instance)
sum(nginx_http_requests_total) without (instance,container,endpoint,job,namespace)
Grafana
그라파나는 메트릭을 시각화 해주는 오픈소스 입니다. Prometheus-stack을 배포할 때 함께 배포되었으므로 접속 URL을 확인하고 접근합니다.
kubectl get ingress -n monitoring kube-prometheus-stack-grafana
NAME CLASS HOSTS ADDRESS PORTS AGE
kube-prometheus-stack-grafana alb grafana.junkmm.site myeks-ingress-alb-1359038265.ap-northeast-2.elb.amazonaws.com 80 150m
계정 admin / prom-operator 로 로그인 합니다.
그라파나는 다양한 외부 소스로부터 메트릭 값을 끌어올 수 있습니다. 이번 실습은 Prometheus로 메트릭 수집을 진행하기에 Data Source는 Prometheus가 되어야 합니다. 이 부분도 Stack 배포 시 연동 되어 있습니다. 구성은 Home -> Connections -> Data source에서 확인할 수 있습니다.
그럼 해당 데이터소스를 기반으로 대시보드를 통해 메트릭을 시각화 해 보겠습니다. 우선 대시보드 -> New -> Import를 클릭합니다. 15757을 입력하고, 데이터 소스로 프로메테우스 선택 후 import를 클릭합니다. import는 다른 사용자가 미리 구성한 대시보드 구성을 불러와 사용하는 방식입니다.
정상적으로 생성이 된 후 대시보드를 통해 메트릭을 확인할 수 있음을 확인합니다.
아래는 16032 AWS CNI Metrics 대시보드 예시 입니다.
아래는 12708 Nginx 대시보드 예시 입니다.
그럼 새로운 대시보드를 직접 생성하고, 쿼리문을 작성하여 구성해보도록 하겠습니다. 우선 대시보드 -> New -> New Dashboard -> New Visualization 를 클릭합니다.
아래는 Time series를 사용하는 패널의 예시 입니다. 인스턴스 별 CPU 사용 변화율을 보여줍니다.
sum(rate(node_cpu_seconds_total[5m])) by (instance)
아래는 Bar Chart를 사용하는 패널의 예시 입니다. 네임스페이스 별 디플로이먼트 개수를 보여줍니다. 이때 하단의 포맷을 Table, Type을 Instant로 설정하면 현재 기준의 데이터만 보여줍니다.
count(kube_deployment_status_replicas_available) by (namespace)
아래는 Stat을 사용하는 패널의 예시 입니다. nginx 파드 수를 보여줍니다.
kube_deployment_spec_replicas{deployment="nginx"}
아래는 Gauge를 사용하는 패널의 예시 입니다. 노드별 1분간 CPU 사용률을 보여줍니다.
1 - (avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) by (instance))
아래는 Table을 사용하는 패널 예시입니다.
node_os_info
Table 유형은 Trasform data 탭에서 데이터 영역을 편집할 수 있습니다. 표의 열 부분을 조절해 보겠습니다.
Grafana Alert
그라파나에서 알람 정책에 의해 슬랙, 이메일 등에 알림을 보낼 수 있습니다.
그라파나 -> Alerting -> Alert rules -> Create alert rule 에서 nginx 웹 요청 1분 동안 누적 60 달성 시 Alert 설정을 진행 하겠습니다. 아래 체크 부분을 설정하고 생성합니다.
Contact Point에서 슬랙을 추가 합니다.
알람 정책에서 Default 정책이 Email인 부분을 Slack으로 변경합니다.
Nginx에 과 접속 후 Slack 알람이 발생하는지 확인합니다.
아래 명령어로 실습 리소스를 정리합니다.
eksctl delete cluster --name $CLUSTER_NAME && aws cloudformation delete-stack --stack-name $CLUSTER_NAME
# EKS Control Plane 로깅(CloudWatch Logs) 비활성화
eksctl utils update-cluster-logging --cluster $CLUSTER_NAME --region $AWS_DEFAULT_REGION --disable-types all --approve
# 로그 그룹 삭제 : 컨트롤 플레인
aws logs delete-log-group --log-group-name /aws/eks/$CLUSTER_NAME/cluster
---
# 로그 그룹 삭제 : 데이터 플레인
aws logs delete-log-group --log-group-name /aws/containerinsights/$CLUSTER_NAME/application
aws logs delete-log-group --log-group-name /aws/containerinsights/$CLUSTER_NAME/dataplane
aws logs delete-log-group --log-group-name /aws/containerinsights/$CLUSTER_NAME/host
aws logs delete-log-group --log-group-name /aws/containerinsights/$CLUSTER_NAME/performance