구름

Boto3를 활용한 Zabbix - RDS cpu/ram 사용량 모니터링 본문

Monitoring

Boto3를 활용한 Zabbix - RDS cpu/ram 사용량 모니터링

Cloudest 2021. 2. 1. 16:37
 

Cloudest - 블로그 이사했습니다

노션으로 블로그를 옮겼습니다.

흥미로운 포스팅이 올라옵니다!

cloudest.oopy.io

시작 전

  • 이번엔 Zabbix에서 RDS의 CPU와 RAM 사용률, 가용률 모니터링이 필요했다.
  • RDS는 MS-SQL DB를 설치해서 사용중이다.
  • 모니터링에 사용되는 액세스키, 비밀키의 사용자는 ReadOnlyAccess 이상의 권한을 가져야한다.
    • 사용된 모든 액세스 키 정보는 ReadOnlyAccess 권한만 가진 모니터링 전용 Server_moniroting 사용자이다.

작업

  • 1. RDS의 CPU , RAM 정보 찾기
    • 1) RDS의 CPU, RAM에 대한 정보를 boto3를 통해 가져오기 위해 구글링을 진행하던 중 RDS 확장 모니터링에 대한 문서를 찾았다.
    • 2) DB 인스턴스 확장 모니터링 탭에서 MS-SQL 및 다양한 RDB의 정보를 CloudWatchLogs를 통해 제공하는 문서를 확인했다.
      MS-SQL은 disks와 memory에 대한 세부내용을 CloudWatchLogs를 통해 제공한다.
  • 2. CloudWatchLogs를 활용하는 방법
    • 1) RDS를 생성할 때 '모니터링' 옵션을 다음과 같이 체크한 경우 이미 CloudWatch Logs로 로그를 수집하고, 체크하지 않았다면 RDS 수정을 통해 체크하면 된다. (CloudWatch에 대한 소량의 비용이 발생할 수 있다.)
    • 2) CloudWatch Logs에서 위 설정으로 아래 사진과 같은 로그 그룹이 자동 생성된다.

     

  • 3. CloudWatch Logs Insight 에서 원하는 값을 쿼리할 수 있는지 확인
    • 1) CloudWatch → 로그 → 인사이트에서 로그 그룹 'RDSOSMetrics'를 선택하고 쿼리를 실행한다.
    • 2) 쿼리 코드 : disk 잔량 비율, memory 전체량, memory 잔량을 가장 최근 log 1개만 쿼리한다.
      • fields disks.0.availPc, memory.physTotKb, memory.physAvailKb, @timestamp | sort @timestamp desc | limit 1
    • 3) 쿼리 결과 원하는 값들이 출력됐다. disks.0.availPc는 잔량 비율이기 때문에 바로 사용이 가능하고, 2개의 memory 값을 가공하여 RAM 비율 정보로 사용할 수 있을 것이다.
  • 4. boto3와 python을 통해 CloudWatchLogs 쿼리하기
    • 1) Logs Insight에서 쿼리한 값이 boto3를 통해서도 쿼리되는지 확인
      #boto3 사용환경 필요하고 이 파일과 같은 위치에 get-pip.py 파일이 있어야한다.
      #!/usr/bin/python
      import boto3
      import time
      from datetime import datetime, timedelta
      
      #boto3를 사용하기 위해 client 정보입력
      client = boto3.client('logs', aws_access_key_id="[액세스 키]", aws_secret_access_key="[비밀 키]", region_name="ap-northeast-2")
      
      #CloudWatch Logs 쿼리
      query = "fields disks.0.availPc, memory.physTotKb, memory.physAvailKb, @timestamp | sort @timestamp desc | limit 1"
      log_group = 'RDSOSMetrics'
      
      start_query_response = client.start_query(
          logGroupName=log_group,    
          startTime=int(time.time()-3600),
          endTime=int(time.time()),
          queryString=query,
      )
      query_id = start_query_response['queryId']
      response = None
      
      while response == None or response['status'] == 'Running':
          time.sleep(1)
          response = client.get_query_results(
              queryId=query_id
          )
      
      #쿼리 결과 출력
      print(response.get('results'))
      
      #------------------------------구분선-----------------------------------
      
      #출력 결과 - 리스트 내부에 리스트 내부에 딕셔너리 형태로 쿼리 반환
      [
      	[
      		{'field': 'disks.0.availPc', 'value': '99.88'},
      		{'field': 'memory.physTotKb', 'value': '8080988'}, 
      		{'field': 'memory.physAvailKb', 'value': '6170956'},
      		{'field': '@timestamp', 'value': '2021-01-26 02:30:39.000'}, 
      		{'field': '@ptr1-01-26 02:30:39.000'}, 
      		{'field': '@ptr', 'value': 'ClgKHQoZNjMwNTYzMTY3NTQ1OlJEU09TTWV0cmljcxAEEjcaGAIGAIlGRAAAAAAYZe6kAAYA9+YwAAACQiABKJixneTzLjCYsZ3k8y44AUCBPEje4AFQy6cBEAAYAQ=='}
      	]
      ]
  • 5. 쿼리 결과 중 원하는 값만 출력하기
    • 1) 앞서 작성한 코드의 #쿼리 결과 출력 부분을 다음과 같이 변경 후 실행 및 결과
      #쿼리 결과 출력 부분 변경 - 리스트 한겹 벗기기
      datapoints = response.get('results')
      print(datapoints[0])
      
      #------------------------------구분선-----------------------------------
      
      #결과 - 리스트 한겹 벗겨짐
      [
      	{'field': 'disks.0.availPc', 'value': '99.88'}, 
      	{'field': 'memory.physTotKb', 'value': '8080988'}, 
      	{'field': 'memory.physAvailKb', 'value': '6173876'}, 
      	{'field': '@timestamp', 'value': '2021-01-26 04:04:39.000'}, 
      	{'field': '@ptr', 'value': 'ClgKHQoZNjMwNTYzMTY3NTQ1OlJEU09TTWV0cmljcxAFEjcaGAIF/54AawAAAABCWqI4AAYA+UVgAAADsiABKNjP9ebzLjDYz/Xm8y44AUD8O0ik4AFQkacBEAAYAQ=='}
      ]
    • 2) 모니터링을 위해 정확히 원하는 값만 출력
      #boto3 사용환경 필요하고 이 파일과 같은 위치에 get-pip.py 파일이 있어야한다.
      #!/usr/bin/python
      import boto3
      import time
      from datetime import datetime, timedelta
      
      #boto3를 사용하기 위해 client 정보입력
      client = boto3.client('logs', aws_access_key_id="[액세스 키]", aws_secret_access_key="[비밀 키]", region_name="ap-northeast-2")
      
      #CloudWatch Logs 쿼리
      query = "fields disks.0.availPc, memory.physTotKb, memory.physAvailKb, @timestamp | sort @timestamp desc | limit 1"
      log_group = 'RDSOSMetrics'
      
      start_query_response = client.start_query(
          logGroupName=log_group,    
          startTime=int(time.time()-3600),
          endTime=int(time.time()),
          queryString=query,
      )
      query_id = start_query_response['queryId']
      response = None
      
      while response == None or response['status'] == 'Running':
          time.sleep(1)
          response = client.get_query_results(
              queryId=query_id
          )
      
      #쿼리 결과 정제
      datapoints = response.get('results')
      rds_info1 = datapoints[0]
      
      for item in rds_info1:
          if item.get('field') == 'disks.0.availPc': #disk 사용률
              CpuAvailRate = item.get('value')
          elif item.get('field') == 'memory.physTotKb': #memory 전체 용량
              TotalMemory = float(item.get('value'))
          elif item.get('field') == 'memory.physAvailKb': #memory 남은 용량
              AvailMemory = float(item.get('value'))
      
      #결과 출력
      print(CpuAvailRate,TotalMemory,AvailMemory)
      
      #------------------------------구분선-----------------------------------
      
      #출력 화면 - 성공적으로 Zabbix에서 모니터링 할 형태의 값만 출력했다.
      99.88 8080988.0 6180028.0
  • 6. 입력값에 따라 원하는 값만 출력하기
    • 1) metric 옵션을 생성하여 원하는 값을 입력받고 출력하기
      #!/usr/bin/python
      import boto3
      import time
      from datetime import datetime, timedelta
      from optparse import OptionParser
      
      ##Connection##
      #옵션 값을 지정
      parser = OptionParser()
      parser.add_option("-a", "--access-key", dest="access_key", help="AWS Access Key")
      parser.add_option("-k", "--secret-key", dest="secret_key", help="AWS Secret Access Key")
      parser.add_option("-r", "--region", dest="region", help="RDS region")
      parser.add_option("-m", "--metric", dest="metric", help="RDS cloudwatch metric")
      
      #옵션 값들을 options에 저장
      (options, args) = parser.parse_args()
      
      #필수값 받기
      if (options.metric == None):
          parser.error("-m RDS cloudwatch metric is required")
      
      #메트릭 옵션의 범위 지정
      metrics = {
          "DiskAvailRate":{"type":"float", "value":None},
          "DiskUsedRate":{"type":"float", "value":None},
          "RAMAvailRate":{"type":"float", "value":None},
          "RAMUsedRate":{"type":"float", "value":None}
          }
      
      
      # 액세스키가 있으면 not 1 -> 0 / 비밀키가 있으면 not 1 -> 0 
      # 즉 키가 모두있으면 0or0=0 -> use_roles=False / 키가 하나라도 없으면 use_roles=True
      if not options.access_key or not options.secret_key:
          use_roles = True
      else:
          use_roles = False
      
      #위의 use_roles 기반으로 키가 둘다있으면 client에 키값과 리전을 입력
      if use_roles:
         client = boto3.client('logs', region_name=options.region)
      else:
         client = boto3.client('logs', aws_access_key_id=options.access_key, aws_secret_access_key=options.secret_key, region_name=options.region)
      
      #쿼리
      query = "fields disks.0.availPc, memory.physTotKb, memory.physAvailKb, @timestamp | sort @timestamp desc | limit 1"
      log_group = 'RDSOSMetrics'
      
      start_query_response = client.start_query(
          logGroupName=log_group,
          startTime=int(time.time()-7200),
          endTime=int(time.time()),
          queryString=query,
      )
      
      query_id = start_query_response['queryId']
      
      response = None
      
      while response == None or response['status'] == 'Running':
          time.sleep(1)
          response = client.get_query_results(
              queryId=query_id
          )
      
      #쿼리 결과 정제
      datapoints = response.get('results')
      rds_info1 = datapoints[0]
      
      for item in rds_info1:
          if item.get('field') == 'disks.0.availPc':
              DskAvailRate = item.get('value')
          elif item.get('field') == 'memory.physTotKb':
              TotalMemory = float(item.get('value'))
          elif item.get('field') == 'memory.physAvailKb':
              AvailMemory = float(item.get('value'))
      
      #출력 형변환 및 소숫점 지정
      RAR=round(float(AvailMemory/TotalMemory)*100,2)
      DAR=round(float(DskAvailRate),2)
      
      #옵션 입력값에 따라 출력하기
      if options.metric in metrics.keys():
        if options.metric == 'DiskAvailRate':
            print(DAR)
        elif options.metric == 'DiskUsedRate':
            print(100-DAR)
        elif options.metric == 'RAMAvailRate':
            print(RAR)
        elif options.metric == 'RAMUsedRate':
            print(100-RAR)
    • 2) 실행 코드 및 출력 결과
      • python test.py "--metric" "RAMAvailRate" "--access-key" "액세스키" "--secret-key" "비밀키" "--region" "ap-northeast-2"입력
    •  
  • 7. 자빅스 서버에 파일 넣기
    • 1) rds_disk_ram.py 이름으로 /usr/lib/zabbix/externalscripts/경로에 넣어주기
    • 2) chown zabbix:root rds_disk_ram.py 명령으로 권한변경 (따로 zabbix 그룹이 없다면 root:root로 진행)
  • 8. Zabbix 템플릿에 Item 추가
    • 1) 템플릿에 이미 Key와 Region값이 들어가 있다. 아래 사진과 같이 입력 후 Item 생성
    • 2) 결과 확인

 

  • 에러
    1. 윈도우에선 출력되는데 Zabbix서버에서 에러가 나는 경우
      • timestamp가 python 2. 버전을 지원하지 않아서 다른 함수로 startTime, endTime을 정의해줘야함 (time.time())
    • 윈도우,리눅스에선 실행되는데 Zabbix에서 읽어오지 못하는경우 (to many arguments 오류)
      • #!/usr/bin/python파이썬 파일 시작시 선언했는지 확인

결과

  • 기존 스크립트로 모니터링 하지 못한 지표도 boto3와 AWS에서 제공하는 지표를 토대로 Python 파일을 수정하여 모니터링이 가능하다.
  • 개발자가 아니더라도 코드에 대한 기본적인 사용법을 익히면 엔지니어링, 모니터링, 솔루션과 같은 다양한 분야에서 편의성이 증가한다는 걸 체감했다. (원래도 머리로는 알고 있었는데 실제로 겪어보니 더 와닿는다.)

'Monitoring' 카테고리의 다른 글

Zabbix Macro 생성  (0) 2021.01.26
Grafana-Zabbix 연동 모니터링 환경 구성  (1) 2020.12.24
Zabbix on CentOS 8 (2)  (0) 2020.12.23
Zabbix on CentOS 8 (1)  (0) 2020.12.23
Comments