使用Python将Prometheus中Pod的资源使用数据导出为csv文件
从IBM Cloud Private的包含的Prometheus中将数据导出为CSV文件的Python的备忘录。
假设希望在传统系统中,使用Excel创建并报告每个Pod的CPU使用率等性能信息报告。
我会使用ICP v3.1.0进行确认。由于在Mac上执行命令,所以在Linux上,curl和date等的行为可能会有些不同。
普罗米修斯查询
本文未提及有关Prometheus查询的内容,但建议查看以下链接。
- Prometheusクエリ道場
例如,要获取Namespace每个Pod的CPU使用率,可以使用以下查询。
sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) * 100
当我们按顺序查看这个查询的过程,它会变成以下这样。虽然数据类型和rate()函数可能不太容易理解,但这是重点。
container_cpu_usage_seconds_totalはコンテナが使用したCPU時間の積算値で、このクエリーは複数のコンテナのあるタイムスタンプにおけるデータが返ってくる(Instant vector型のデータ)
container_cpu_usage_seconds_total{namespace=”default”}のように、ラベルでデータを絞ることができる(Instant vector型のデータ)
container_cpu_usage_seconds_total{namespace=”default”}[5m]とすることで、データの期間を指定していて、複数のコンテナの複数のタイムスタンプに渡るデータが返ってくる(Range vector型のデータ)
rate(container_cpu_usage_seconds_total{namespace=”default”}[5m])とすることで、指定期間の1秒当たりの平均の増分を出している(Instant vector型のデータ)
1秒当たりCPU使用時間が0.5秒増えていたとすると、CPU使用率は0.5(50%)ということになる
sum(rate(container_cpu_usage_seconds_total{namespace=”default”}[5m])) by (pod_name)で複数のデータをPod毎に合計している(Instant vector型のデータ)
Pod内には複数のコンテナがいる場合があるので、Pod毎にこのような合計をしている
从Grafana导出
通过Grafana仪表板,可以通过GUI以面板为单位进行CSV导出。在仪表板上,您可以指定数据的时间范围,并根据仪表板设置的变量(例如interval和Namespace)来显示所需的数据。在将面板的标题部分点击一下后,即可通过CSV进行导出。浏览器上的JavaScript已将已下载的JSON数据转换为CSV文件。
在导出时,还可以进行一些设置。
使用curl
接下来尝试使用curl获取数据。
确认用户身份
要访问Prometheus API,需要使用令牌。
-
- コンポーネント API コマンドまたは管理 API コマンドを実行するための準備
- Prometheus API
虽然认为需要ID令牌,但需要访问Prometheus API的不是ID令牌,而是访问令牌,请注意。
如果本地已经使用cloudctl login登录,就可以通过cloudctl tokens获取令牌。
ACCESS_TOKEN=$(LANG=C cloudctl tokens | grep "Access token:" | awk '{print ($4)}')
还可以将用户名和密码传递给API来获取。
USERNAME="admin"
PASSWORD="admin"
ACCESS_TOKEN=$(curl -s -k -H "Content-Type: application/x-www-form-urlencoded;charset=UTF-8" \
-d "grant_type=password&username=${USERNAME}&password=${PASSWORD}&scope=openid" \
https://mycluster.icp:8443/idprovider/v1/auth/identitytoken | jq -r '.access_token')
查询确认
通过点击Grafana仪表板面板标题上的”Edit”,可以确认使用了哪些Prometheus查询。$interval和$namespace是从仪表板定义的变量组件中传递的。
此外,您还可以通过打开查询审查器来确认所使用的HTTP请求的详情。
GET请求的参数必须包括query、start、end、step。其中start和end是以Epoch时间表示的,而step表示数据点之间的间隔。
运行curl
使用curl进行请求的方法如下:使用-H选项添加用于身份验证的标头。通过–data-urlencoded选项对数据进行编码,并使用-G选项将其作为GET请求而不是POST请求发送。
curl -k -s -G -H "Authorization:Bearer $ACCESS_TOKEN" \
https://mycluster.icp:8443/prometheus/api/v1/query_range \
--data-urlencode 'query=sum(rate(container_cpu_usage_seconds_total{namespace="default"}[5m])) by (pod_name) * 100' \
--data-urlencode "start=1547517120" \
--data-urlencode "end=1547527950" \
--data-urlencode "step=30" | jq .
将参数转化为变量并放在前面,如下所示。
NAMESPACE="default"
INTERVAL="5m"
QUERY="sum(rate(container_cpu_usage_seconds_total{namespace=\"${NAMESPACE}\"}[${INTERVAL}])) by (pod_name) * 100"
START=$(date -v -1d +%s) # 1日前の時刻をエポックタイムで取得
END=$(date +%s) # 今の時刻をエポックタイムで取得
STEP=30
curl -k -s -G -H "Authorization:Bearer $ACCESS_TOKEN" \
https://mycluster.icp:8443/prometheus/api/v1/query_range \
--data-urlencode "query=${QUERY}" \
--data-urlencode "start=${START}" \
--data-urlencode "end=${END}" \
--data-urlencode "step=${STEP}" | jq .
运行后将以JSON格式返回如下。
$ curl -k -s -G -H "Authorization:Bearer $ACCESS_TOKEN" \
> https://mycluster.icp:8443/prometheus/api/v1/query_range \
> --data-urlencode "query=${QUERY}" \
> --data-urlencode "start=${START}" \
> --data-urlencode "end=${END}" \
> --data-urlencode "step=${STEP}" | jq .
{
"status": "success",
"data": {
"resultType": "matrix",
"result": [
{
"metric": {
"pod_name": "infra-test-nodeport-cust-0"
},
"values": [
[
1547537972,
"2.5491993487228775"
],
[
1547538002,
"2.300661640484626"
],
(省略)
Python 人工智能
由于将 JSON 转换为 CSV 在 Shell 脚本中有些复杂,所以尝试使用 Python 完成这个任务。
编写代码
编写以下类型的程序代码。由于还不了解编写良好的代码的方法,所以采用”让Python做无聊的事情”的思想。
import argparse
import collections
import csv
import datetime
import logging
import re
import subprocess
import pprint
import requests
import urllib3
formatter = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
logging.basicConfig(level=logging.WARNING, format=formatter)
logger = logging.getLogger(__name__)
# 警告を非表示にする
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# コマンド引数の処理
parser = argparse.ArgumentParser(description='PodのCPU使用率をcsvに出力します。')
parser.add_argument('-f', '--filename',
action='store',
type=str,
help='出力先のファイル名を指定します')
parser.add_argument('-n', '--namespace',
action='store',
type=str,
default='default',
help='Namespaceを指定します')
parser.add_argument('--interval',
action='store',
type=str,
default='5m',
help='CPU使用率計算に使用するデータの間隔を指定します(例)1h、5m')
parser.add_argument('--start',
action='store',
type=str,
help='データの開始時間を指定します(例)20190101-1000')
parser.add_argument('--end',
action='store',
type=str,
help='データの終了時間を指定します(例)20190102-1000')
parser.add_argument('--step',
action='store',
type=int,
help='データポイントの間隔(秒)を指定します')
args = parser.parse_args()
filepath = args.filename
namespace = args.namespace
interval = args.interval
step = args.step
logger.debug('filepath: {}'.format(filepath))
logger.debug('interval: {}'.format(interval))
logger.debug('step: {}'.format(step))
# 引数の開始時間と終了時間をUNIX時刻に変換
start_str = args.start
end_str = args.end
start_dt = datetime.datetime.strptime(start_str, '%Y%m%d-%H%M')
end_dt = datetime.datetime.strptime(end_str, '%Y%m%d-%H%M')
start_unix = start_dt.timestamp()
end_unix = end_dt.timestamp()
logger.debug('start_dt: {}'.format(start_dt))
logger.debug('end_dt: {}'.format(end_dt))
logger.debug('start_unix: {}'.format(start_unix))
logger.debug('end_unix: {}'.format(end_unix))
# サブプロセスでコマンドを実行し、結果からアクセストークンを抽出
completed_process = subprocess.run(['cloudctl', 'tokens'], stdout=subprocess.PIPE)
result_str = completed_process.stdout.decode('utf-8')
match = re.search(r'(.*)\s+Bearer\s+(.*)', result_str)
access_token = (match.group(2))
logger.debug('access_token: {}'.format(access_token))
# Prometheusクエリー
# 指定のNamespaceの、指定のintervalで算出したPod毎のCPU使用率を取得する
# sum(rate(container_cpu_usage_seconds_total{namespace="$namespace"}[$interval])) by (pod_name) * 100
query = 'sum(rate(container_cpu_usage_seconds_total{{namespace="{}"}}[{}])) ' \
'by (pod_name) * 100'.format(namespace, interval)
logger.debug('query: {}'.format(query))
# リクエスト
url = 'https://mycluster.icp:8443/prometheus/api/v1/query_range'
headers = {'Authorization': 'Bearer {}'.format(access_token)}
params = {'query': query,
'start': start_unix,
'end': end_unix,
'step': step}
logger.debug('url: {}'.format(url))
logger.debug('headers: {}'.format(headers))
logger.debug('params: {}'.format(params))
# リクエストを実行
response = requests.get(url, verify=False, headers=headers, params=params)
response.raise_for_status()
logger.debug('response: {}'.format(response))
# レスポンスは以下のようなデータ
# pprint.pprint(response.json())
# {'data': {'result': [{'metric': {'pod_name': 'infra-test-nodeport-cust-0'},
# 'values': [[1547528400, '2.64939279124293'],
# [1547532000, '2.5820633706497045'],
# [1547535600, '2.562417181158173'],
# [1547539200, '2.4563804665536724'],
# 意味のない部分を取り除いて中のリストを取り出す
results = response.json()['data']['result']
# 取り出したのは以下のようなデータ
# pprint.pprint(results)
# [{'metric': {'pod_name': 'infra-test-nodeport-cust-0'},
# 'values': [[1547528400, '2.64939279124293'],
# [1547532000, '2.5820633706497045'],
# [1547535600, '2.562417181158173'],
# [1547539200, '2.4563804665536724'],
#
# このデータを時刻をキーにして以下のような辞書にまとめる
#
# {1547464889.632: {'infra-test-nodeport-cust-0': '3.1518179124293577',
# 'infra-test-nodeport-cust-1': '1.530811175762711',
# 'infra-test-nodeport2-cus-0': '3.0063879859887037',
# 'infra-test-nodeport2-cus-1': '1.5241500936723127'},
# 1547468489.632: {'infra-test-nodeport-cust-0': '3.161739384943495',
# 'infra-test-nodeport-cust-1': '1.5393470943785368',
# 'infra-test-nodeport2-cus-0': '2.8831145322598943',
# 'infra-test-nodeport2-cus-1': '1.578976048757047'},
# 時刻毎のデータの辞書を用意する
time_series = collections.defaultdict(dict)
# Pod名のSetを用意する
pod_names = set()
for result in results:
# Pod名を取り出してSetに入れておく
pod_name = result['metric']['pod_name']
pod_names.add(pod_name)
for value in result['values']:
# timestampを辞書のキーにすることで同じtimestampのデータをまとめる
# defaultdictを使うことでキーがなくてもKeyErrorにならない
time_series[value[0]][pod_name] = value[1]
# pprint.pprint(time_series)
# csvのヘッダーは時刻とPod名にする
fieldnames = ['timestamp']
fieldnames.extend(pod_names)
# csvファイルに保存する
with open(filepath, 'w') as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=fieldnames)
writer.writeheader()
# 辞書から時間毎のデータを取り出してループする
for timestamp, values in time_series.items():
# 行に時間の列を追加
row = {'timestamp': datetime.datetime.fromtimestamp(timestamp)}
# valuesは以下のような辞書
# {'infra-test-nodeport-cust-0': '2.467225521553685',
# 'infra-test-nodeport-cust-1': '1.5932590068361583',
# 'infra-test-nodeport2-cus-0': '2.2811341803954917',
# 'infra-test-nodeport2-cus-1': '1.6517850743220521'},
# 事前に格納したPod名のリストの方でループする
for pod_name in pod_names:
try:
row[pod_name] = values[pod_name]
except KeyError:
# valuesにこのPodのデータがないときはKeyErrorが発生するので空データを入れる
row[pod_name] = ''
writer.writerow(row)
執行案例
需要使用 `request` 模块。
pip install requests
由於在子進程中執行了cloudctl token以獲取令牌,所以需要使用cloudctl命令,並且需要事先登錄。
运行后,您可以获取到以下csv文件。
$ python export_pod_cpu.py --help
usage: export_pod_cpu.py [-h] [-f FILENAME] [-n NAMESPACE]
[--interval INTERVAL] [--start START] [--end END]
[--step STEP]
PodのCPU使用率をcsvに出力します。
optional arguments:
-h, --help show this help message and exit
-f FILENAME, --filename FILENAME
出力先のファイル名を指定します
-n NAMESPACE, --namespace NAMESPACE
Namespaceを指定します
--interval INTERVAL CPU使用率計算に使用するデータの間隔を指定します(例)1h、5m
--start START データの開始時間を指定します(例)20190101-1000
--end END データの終了時間を指定します(例)20190102-1000
--step STEP データポイントの間隔(秒)を指定します
$ python export_pod_cpu.py -f test.csv -n default --interval 5m --start 20190115-1600 --end 20190116-1600 --step 600
$ cat test.csv
timestamp,infra-test-nodeport-cust-1,infra-test-nodeport2-cus-1,infra-test-nodeport2-cus-0,infra-test-nodeport-cust-0
2019-01-15 16:00:00,1.7765650066665255,1.5227466104164478,2.4642478804166026,2.7171226995831903
2019-01-15 16:10:00,1.4900129837500724,1.4548672362502657,2.3264455262498513,2.7998607379167124
2019-01-15 16:20:00,1.7629794254168016,1.6523557370834396,2.231762218333415,2.040916205000182
2019-01-15 16:30:00,1.87828389291667,1.8159218674998103,1.7364743683333472,2.4042730716670726
2019-01-15 16:40:00,1.6692312762499266,1.7964510670833533,2.264148609108464,2.300661640484626
2019-01-15 16:50:00,1.7293341049999826,1.7319731500000066,1.8862790512499334,2.091235875833111
(省略)
2019-01-16 15:00:00,1.4205530700000204,1.450829464166835,2.229421241249838,2.6200906879167483
2019-01-16 15:10:00,1.207286078750182,1.4032802437501837,1.8004293182339135,2.120460568511992
2019-01-16 15:20:00,1.2547518466667875,1.5286131908334255,1.8340893729164995,2.258688518749826
2019-01-16 15:30:00,1.2122690649999868,1.3929129920832868,2.2814286763049063,2.8766292608645045
2019-01-16 15:40:00,1.4557575473232511,1.2478161367336864,1.8957547083334705,2.535229864166316
2019-01-16 15:50:00,1.0475173487499962,1.2800040254167773,1.750940527916403,2.7946370483334704
2019-01-16 16:00:00,1.4323884024997824,1.3961431675001752,2.2708808758333516,2.626497514166885
$
以下是要求的句子的汉语表述:
– 请完成以下任务。
-
- 日時をローカル時刻で指定してるが、UTCで指定できる方が便利かもしれない
-
- CPUしかやってないのでメモリー使用量も必要
openpyxlモジュールとかを使ってエクセルにするところまでやってもよさそう