쿠버네티스 노드 관제 커스터마이즈

IT/Python3 2020. 11. 6. 09:09
반응형

쿠버네티스 노드에 대한 이슈가 자꾸 불거지기에, 계속 요즘따라 관제를 커스터마이징 하고 있습니댜

이전에 터졌던 EKS PLEG이슈에 이어 간혹 산발적으로 노드가 하나씩 다운되는 사태까지..

 

머리속으로는 이해하지만, 막상 시간이 부족하여 잘 만들지 못했던 관제를 이제야 꺼내보는군여.....

내 오른손의 파이썬과 엘라스틱서치

사실 고대의 존재들이 만들어두었던 방식을 제가 그대로 싹 흡수해서 만들게 된 케이스이기도 합니댜

아주 먼 옛날, 이때 만들면서 시간대역 때문에 시간 허비했던 그 관제시스템의 템플릿 버전이기도 하죠.

 

한편으론 감사합니다. 머나먼 고대의 존재들에게 이어받은 모든 지식을 토대로, 이렇게 제가 활용하고 있따는 것을 보면 훗훗..

(앙몬드가 가을타다보니 별 생각이 다 든댜)

 

devloper-angmond.tistory.com/6

 

AWS Elasticsearch & Lambda & Kibana 트러블슈팅 일지

AWS AZ 간 시간때문에 애를 먹었던 일이 있었는데 이번에 또 발생했어요. 클라우드 개발 시의 시간 참조별.. 까먹지 않게..! 1. AWS Lambda 실행 시간  - 기본적으로는 UTC 0시 (영국) 기준으로 돌아가요.

devloper-angmond.tistory.com

 

상기 환경과는 다르게 지금의 환경은, 클라우드 리소스 접근이 어렵기 때문에, 젠킨스의 크론 빌드를 이용하여, 관제를 걸기로 하였습니댜

따라서, 준비되는 리소스는 다음과 같습니댜

0. 환경작업 패스

1. EC2 머신 (젠킨스 설치 / 크론탭으로 빌드 유발)

2. 크론탭 빌드 유발 시, 준비 되어야하는 파이썬3 스크립트

3. ES & 키바나 비주얼라이즈 with S3 Serverless Host

4. 노드가 비정상으로 감지되었을 시, 텔레그램으로 관제 발송

간단한 아키텍처는 이와 같습니댜

텔레그램에 대한 관제는 정말 별거 없지만, 가이드가 없으면 갈피잡기가 너무 어렵기 떄문에, 훗날 서브로 다시 작성해둘게요.

"""
텔레그램 메세지 전송 헬퍼

@author 클라우드 개발자 앙몬드
@since 2020-11-02

[2020-11-02] 최초생성
"""

import telegram

토큰 = '봇에게 접근하기 위한 토큰값입니다. 숫자:문자 로 이루어져있어요'
#텔레그램_채팅방_아이디 = "채팅방마다 시퀀스가 존재합니다"  #내 개인방
텔레그램_채팅방_아이디 = "채팅방마다 시퀀스가 존재합니다"   #데브옵스 엔지니어 방 
봇 = telegram.Bot(token=토큰)

#채팅방 아이디를 얻어내기 위한, json값 획득
#for i in 봇.getUpdates():
#    print(i.message)

def 텔레그램_메세지보내기(메세지) :
    봇.sendMessage(chat_id=텔레그램_채팅방_아이디, text=메세지) 
# -*- coding: utf-8 -*- 
import json
import time
import os
import decimal
import sys
import subprocess
import yaml
import boto3 
from requests_aws4auth import AWS4Auth
from elasticsearch import Elasticsearch, RequestsHttpConnection
from datetime import datetime, timedelta
from os import rename, listdir
from angmond_es_helper import *
import telegram_helper

"""
쿠버네티스 클러스터 노드 상태 체크의 백엔드 로직

@since 2020-10-29
@author 클라우드 개발자 앙몬드

[2020-10-29] 최초생성
"""

비정상노드리스트 = []
ES입력리스트 = []

메세지내용 = ""
노드명 = ""
노드상태 = ""
노드_실행기간 = ""
노드그룹 = ""
today = datetime.today()

ES_노드상태_인덱스 = "devops-eks-node-status-"
ES_노드사용_인덱스 = "devops-eks-node-usage-"
ES_노드그룹사용_인덱스 = "devops-eks-nodegroup-usage-"

ES커넥터 = get_aws_es_connector(get_aws_es_credentials(True), "ES_호스트주소!")
ES타임스탬프 = (today - timedelta(hours=9)).isoformat()

if __name__ == "__main__" : 
    비정제_전체_노드_요약리스트 = subprocess.check_output("kubectl get node -L group | grep -v NAME", shell=True, universal_newlines=True).strip()
    노드리스트 = str(비정제_전체_노드_요약리스트).split("\n")

    노드_FO_CPU = []	#프론트 모듈중 CPU에 특화된 노드들이 구성된 노드그룹
    노드_FO_MEM = []	#프론트 모듈 중 메모리에 특화된 노드들이 구성된 노드그룹
    노드_BO_MEM = []	#백오피스/현업/ERP 모듈 중 메모리에 특화된 노드들이 구성된 노드그룹

    # (0) 비정상노드 탐지를 위한 정제부분
    완전정제된_노드리스트 = list(map(lambda x : (" ".join(x.split())).split(" "), 노드리스트))

    노드_FO_CPU = list(filter(lambda x : x[5] == "fo_cpu", 완전정제된_노드리스트))
    노드_FO_MEM = list(filter(lambda x : x[5] == "fo_mem", 완전정제된_노드리스트))
    노드_BO_MEM = list(filter(lambda x : x[5] == "bo_mem", 완전정제된_노드리스트))

    # (1) 비정상노드 상태값 판별 & 텔레그램 관제
    for 노드 in 완전정제된_노드리스트 :
        """
        노드정보는 리스트로..
        0 : NAME
        1 : STATUS
        2 : Role
        3 : AGE
        4 : EKS버전
        5 : 노드 그룹
        """  
        
        메모리부하_상태 = subprocess.check_output("kubectl describe node " + 노드[0] + " | grep '  MemoryPressure'", shell=True, universal_newlines=True).strip()
        볼륨부하_상태 = subprocess.check_output("kubectl describe node " + 노드[0] + " | grep '  DiskPressure'", shell=True, universal_newlines=True).strip()
        PID부하_상태 = subprocess.check_output("kubectl describe node " + 노드[0] + " | grep '  PIDPressure'", shell=True, universal_newlines=True).strip()
        쿠블릿_상태 = subprocess.check_output("kubectl describe node " + 노드[0] + " | grep '  Ready'", shell=True, universal_newlines=True).strip()
        
        관제메세지 = ""
        if "True" in 메모리부하_상태 :
            관제메세지 = 관제메세지 + "MemoryPressure 상태 감지 "
            메모리부하_상태 = True
        else : 메모리부하_상태 = False

        if "True" in 볼륨부하_상태 :
            관제메세지 = 관제메세지 + "DiskPressure 상태 감지 " 
            볼륨부하_상태 = True
        else : 볼륨부하_상태 = False

        if "True" in PID부하_상태 :
            관제메세지 = 관제메세지 + "PIDPressure 상태 감지 " 
            PID부하_상태 = True
        else : PID부하_상태 = False

        if "False" in 쿠블릿_상태 :
            관제메세지 = 관제메세지 + "쿠블릿 비정상 감지 " 
            쿠블릿_상태 = False
        else : 쿠블릿_상태 = True

        # 상태 이상 발견 시, 텔레그램 관제 문자 발송
        if len(관제메세지) != 0 :
            telegram_helper.텔레그램_메세지보내기(노드[0] + " -> " + 관제메세지)
        
        # ES인덱싱
        es_다큐먼트 = {
            "node" : 노드[0],
            "MemoryPressure" : 메모리부하_상태,
            "DiskPressure" : 볼륨부하_상태,
            "PIDPressure" : PID부하_상태,
            "KubeletReady" : 쿠블릿_상태,
            "@timestamp" : ES타임스탬프,
        }
        print(es_다큐먼트)
        ES인덱스 = ES_노드상태_인덱스 + str(today.year) + '.' + str(today.month)
        create_es_index(ES커넥터, ES인덱스, es_다큐먼트)       

    # (2) 그룹별 CPU/Mem 사용률 
    비정제_노드자원사용률리스트 = subprocess.check_output("kubectl top node | grep -v NAME", shell=True, universal_newlines=True).strip()
    노드자원사용률리스트 = str(비정제_노드자원사용률리스트).split("\n")

    FO_CPU_노드만 = list(map(lambda y : y[0], list(filter(lambda x : x[5] == "fo_cpu", 완전정제된_노드리스트))))
    FO_MEM_노드만 = list(map(lambda y : y[0], list(filter(lambda x : x[5] == "fo_mem", 완전정제된_노드리스트))))
    BO_MEM_노드만 = list(map(lambda y : y[0], list(filter(lambda x : x[5] == "bo_mem", 완전정제된_노드리스트))))
    FO_CPU_리소스필터링_리스트 = []
    FO_MEM_리소스필터링_리스트 = []
    BO_MEM_리소스필터링_리스트 = []
    for 노드리소스사용률 in 노드자원사용률리스트:
        정제된_노드리소스_사용정보 = (" ".join(노드리소스사용률.split())).split(" ")        
        """
        [0] 노드명
        [1] CPU 사용량
        [2] CPU 사용률
        [3] Mem 사용량
        [4] Mem 사용률
        """

        es_다큐먼트 = {
            "node" : 정제된_노드리소스_사용정보[0],
            "cpu_usage" : int(정제된_노드리소스_사용정보[1].replace("m", "")),
            "cpu_use_rate" : int(정제된_노드리소스_사용정보[2].replace("%", "")),
            "mem_usage" : int(정제된_노드리소스_사용정보[3].replace("Mi", "")),
            "mem_use_rate" : int(정제된_노드리소스_사용정보[4].replace("%", "")),
            "@timestamp" : ES타임스탬프,
        }
        ES인덱스 = ES_노드사용_인덱스 + str(today.year) + '.' + str(today.month)
        print(es_다큐먼트)
        create_es_index(ES커넥터, ES인덱스, es_다큐먼트)

        if 정제된_노드리소스_사용정보[0] in FO_CPU_노드만 :
            es_다큐먼트["node_group"] = "fo_cpu"
            FO_CPU_리소스필터링_리스트.append(es_다큐먼트)
        elif 정제된_노드리소스_사용정보[0] in FO_MEM_노드만 :
            es_다큐먼트["node_group"] = "fo_mem"
            FO_MEM_리소스필터링_리스트.append(es_다큐먼트) 
        elif 정제된_노드리소스_사용정보[0] in BO_MEM_노드만 :
            es_다큐먼트["node_group"] = "bo_mem"
            BO_MEM_리소스필터링_리스트.append(es_다큐먼트) 

    for FO_CPU_정보 in FO_CPU_리소스필터링_리스트:
        ES인덱스 = ES_노드그룹사용_인덱스 + str(today.year) + '.' + str(today.month)
        create_es_index(ES커넥터, ES인덱스, FO_CPU_정보)
    for FO_MEM_정보 in FO_MEM_리소스필터링_리스트:
        ES인덱스 = ES_노드그룹사용_인덱스 + str(today.year) + '.' + str(today.month)
        create_es_index(ES커넥터, ES인덱스, FO_MEM_정보)
    for BO_MEM_정보 in BO_MEM_리소스필터링_리스트:
        ES인덱스 = ES_노드그룹사용_인덱스 + str(today.year) + '.' + str(today.month)
        create_es_index(ES커넥터, ES인덱스, BO_MEM_정보) 

반응형

설정

트랙백

댓글