Prometehus: мониторинг AWS Billing

Автор: | 05/03/2020
 

Задача – иметь перед глазами график в Grafana, который будет отображать расходы на AWS.

Есть два варианта – использовать експортер от Prometheus – prom/cloudwatch-exporter, или самописный от nachomillangarcia – prometheus_aws_cost_exporter.

Первый собирает метрики от AWS CloudWatch, в которые данные по биллингу поступают только на следующий день.

Второй написан на Python, ходит к AWS API, получает оттуда данные, и позволяет видеть расходы за сегодняшний день.

Сначала поднимем prometheus_aws_cost_exporter, который будет нам отображать данные по каждому сервису (EC2, RDS, S3, etc) за месяц, а потом добавим prom/cloudwatch-exporter, который будет отображать общий расход денег за сегодня.

Запуск prometheus_aws_cost_exporter

Для проверки запускаем в Docker:

[simterm]

root@monitoring-dev:/opt/prometheus# docker run -e METRIC_TODAY_DAILY_COSTS=yes -p 5000:5000 nachomillangarcia/prometheus_aws_cost_exporter:latest
...
botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the GetCostAndUsage operation: User: arn:aws:sts::534***385:assumed-role/CloudWatchAgentAccessRole-monitoring-dev/i-030d900fa25f2619b is not authorized to perform: ce:GetCostAndUsage on resource: arn:aws:ce:us-east-1:534***385:/GetCostAndUsage

[/simterm]

Ага – нет прав на выполнение API запроса GetCostAndUsage.

Используем ЕС2 IAM profiles, см. пост AWS: ротация ключей IAM пользователей, EC2 IAM Roles и Jenkins и документацию тут>>>.

EC2 instance IAM profile

Переходим к ЕС2 с сервером мониторинга, правой кнопкой – Instance Settings > Attach/Replace IAM Role:

Копируем имя роли:

Переходим в IAM, находим её:

И подключенную к ней политику:

Копируем содержимое CloudWatchAgentServerPolicy, и создаём новую политику с правом на выполнение вызова ce:GetCostAndUsage для ресурсов arn:aws:ce:*:*:/GetCostAndUsage:

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudwatch:PutMetricData",
                "ec2:DescribeVolumes",
                "ec2:DescribeTags",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams",
                "logs:DescribeLogGroups",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "ce:GetCostAndUsage"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ssm:GetParameter"
            ],
            "Resource": [
                "arn:aws:ssm:*:*:parameter/AmazonCloudWatch-*",
                "arn:aws:ce:*:*:/GetCostAndUsage"
            ]
        }
    ]
}

Назовём её monitoring-cloudwatch-access-policy:

Переходим в Roles, создаём новую роль, назовём её monitoring-cloudwatch-access-role, и к ней подключаем созданную политику:

Возвращаемся к инстансу, подключаем новую роль в качеcтве EC2 IAM профиля:

Перезапускаем контейнер с експортером:

[simterm]

root@monitoring-dev:/opt/prometheus# docker run -e METRIC_TODAY_DAILY_COSTS=yes -p 5000:5000 nachomillangarcia/prometheus_aws_cost_exporter:latest
 * Serving Flask app "app.py"
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

[/simterm]

В последней строке сказано “Running on http://127.0.0.1:5000/” – значит доступен только из контейнера.

Запускаем с опцией --host 0.0.0.0:

[simterm]

root@monitoring-dev:/opt/prometheus# docker run -e METRIC_TODAY_DAILY_COSTS=yes -p 5000:5000 nachomillangarcia/prometheus_aws_cost_exporter:latest --host 0.0.0.0

[/simterm]

И проверяем метрики:

[simterm]

admin@monitoring-dev:~$ curl -sL localhost:5000/metrics | grep -v \#
process_virtual_memory_bytes 373948416.0
process_resident_memory_bytes 39677952.0
process_start_time_seconds 1583405899.25
process_cpu_seconds_total 0.47
process_open_fds 7.0
process_max_fds 1048576.0
python_info{implementation="CPython",major="3",minor="7",patchlevel="0",version="3.7.0"} 1.0
aws_today_daily_costs 625.3538325784

[/simterm]

Окей – работает.

Docker Compose и Prometheus 

Наш стек мониторинга запускается из Docker Compose файла:

[simterm]

root@monitoring-dev:/opt/prometheus# cat prometheus-compose.yml | grep image
    image: prom/prometheus:v2.8.0
    image: prom/node-exporter:v0.16.0
    image: grafana/grafana:5.4.3
    image: prom/cloudwatch-exporter:latest
    image: prom/cloudwatch-exporter:latest
    image: prom/cloudwatch-exporter:latest
    image: prom/blackbox-exporter:v0.14.0
    image: prom/alertmanager:v0.16.1
    image: grafana/loki:master-125cfbf
    image: grafana/grafana:6.5.0
    image: grafana/promtail:master-2739551

[/simterm]

И експортер хочется добавить сюда же.

Но есть проблема: стек мониторинга работает в сети prometehus:

version: '2.4'

networks:
  prometheus:

services:

  prometheus-server:
    image: prom/prometheus:v2.8.0
    networks:
      - prometheus
    ports:
      - 9090:9090
...

Експортер запускается на 127.0.0.1:

[simterm]

root@monitoring-dev:/opt/prometheus# docker logs prometheus_aws-exporter_1
...
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)

[/simterm]

Но как из Prometehus достучаться к екпортеру?

Если внутри сети prometheus из контейнера с prometheus-сервером мы обратимся на 127.0.0.1:5000 – мы попадём в контейнер Prometheus, а не експортера.

А что бы попасть на контейнер с екпортером – нам надо в конфиге Prometheus использовать имя типа aws-exporter, как оно задано в Compose файле – тогда Docker сам выполнит разрешение имени aws-exporter в IP контейнера, и зароутит трафик.

Но внутри контейнера приложение-то всё-равно слушает 127.0.0.1 – и даже через внешний IP мы к нему доступ не получим.

Зная из документации експортера, что у нас там Flask – гуглим его переменные, и находим тут>>>:

Click is configured to load default values for command options from environment variables. The variables use the pattern FLASK_COMMAND_OPTION. For example, to set the port for the run command, instead of flask run –port 8000

Пробуем – добавляем в Compose файл переменную FLASK_RUN_HOST:

...
  aws-exporter:
    image: nachomillangarcia/prometheus_aws_cost_exporter
    networks:
      - prometheus
    ports:
      - 5000:5000
    environment:
      - METRIC_TODAY_DAILY_COSTS=yes
      - FLASK_RUN_HOST=0.0.0.0
    restart: unless-stopped
...

В конфиге Prometheus добавляем новый таргет, используя aws-exporter, как имя хоста, что бы зарезолвить через Docker и его сеть:

...
  - job_name: 'aws-billing'
    metrics_path: '/metrics'
    static_configs:
      - targets: ['aws-exporter:5000']
...

Перезапускаем стек, проверяем:

Проверяем метрику aws_today_daily_costs – данные пошли:

CloudWatch exporter

Запуск екпортера уже описывался:

Prometheus: CloudWatch exporter – сбор метрик из AWS и графики в Grafana

Так что тут просто быстрый пример добавления сборов метрик биллинга.

Запускаем в us-east-1, N. Virgina, т.к. метрики биллинга CloudFormation собирает там (вообще это самый первый регион AWS).

У нас настройки харнятся в файле /etc/prometheus/prometheus-cloudwatch-exporter-us-east-1.yml, добавляем в него AWS/Billing:

set_timestamp: false
delay_seconds: 60

metrics:

 - aws_namespace: "AWS/Billing"
   aws_dimensions: [Currency,ServiceName]
   aws_dimensions_select:
     Currency: [USD]
   aws_metric_name: EstimatedCharges
   aws_statistics: [Average]
   range_seconds: 86400

 - aws_namespace: AWS/RDS
   aws_metric_name: FreeStorageSpace
   aws_dimensions: [DBInstanceIdentifier]
...

Перезапускаем, и проверяем метрику aws_billing_estimated_charges_average:

Получаем значение по каждому сервису – на сколько денег сервис наработал с начала месяца.

Потом собираем всё в Grafana примерно так:

Можно выбрать только нужные метрики с регуляркой типа aws_billing_estimated_charges_average{service_name =~ "AmazonS3|AmazonRoute53"}.

Готово.