Вопрос или проблема
Мы активно используем ELK stack и используем Kibana для исследовательского анализа наших логов. Мы ищем возможность перейти с Elasticsearch на Cloudwatch в качестве хранилища для наших операционных логов. Существует ли какое-либо программное обеспечение, похожее на Kibana, для Cloudwatch?
Я знаю, что есть Grafana, и мы активно её используем, но есть случаи, когда Kibana превосходит Grafana, и многие наши разработчики предпочитают Kibana.
Я предпочел бы что-то, что можно разместить на собственном сервере, но готов рассмотреть облачное/SaaS решение, если оно того стоит.
Amazon предлагает сервис Elasticsearch, который включает Kibana. Настройка ES автоматически предоставляет вам Kibana. Вам это не понравится. Очень ограничено. Но – Есть способ получить логи CloudWatch в ES. Ниже приведен код, установленный как функция Lambda, которая отправляет данные в Amazon, размещенный ES. Вы можете сделать то же самое для вашего собственного экземпляра EC2 с установленным ES.
// v1.1.2
var https = require('https');
var zlib = require('zlib');
var crypto = require('crypto');
var endpoint="search-edge-logs-73mf7h7wbvf6pjhsxexqksba6q.us-west-2.es.amazonaws.com";
exports.handler = function(input, context) {
// декодируем ввод из base64
var zippedInput = new Buffer(input.awslogs.data, 'base64');
// разжимаем ввод
zlib.gunzip(zippedInput, function(error, buffer) {
if (error) { context.fail(error); return; }
// парсим ввод из JSON
var awslogsData = JSON.parse(buffer.toString('utf8'));
// преобразуем ввод в документы Elasticsearch
var elasticsearchBulkData = transform(awslogsData);
// пропускаем контрольные сообщения
if (!elasticsearchBulkData) {
console.log('Получено контрольное сообщение');
context.succeed('Контрольное сообщение успешно обработано');
return;
}
// отправляем документы в сервис Amazon Elasticsearch
post(elasticsearchBulkData, function(error, success, statusCode, failedItems) {
console.log('Ответ: ' + JSON.stringify({
"statusCode": statusCode
}));
if (error) {
console.log('Ошибка: ' + JSON.stringify(error, null, 2));
if (failedItems && failedItems.length > 0) {
console.log("Неудачные элементы: " +
JSON.stringify(failedItems, null, 2));
}
context.fail(JSON.stringify(error));
} else {
console.log('Успешно: ' + JSON.stringify(success));
context.succeed('Успешно');
}
});
});
};
function transform(payload) {
if (payload.messageType === 'CONTROL_MESSAGE') {
return null;
}
var bulkRequestBody = '';
payload.logEvents.forEach(function(logEvent) {
var timestamp = new Date(1 * logEvent.timestamp);
var indexPrefix = payload.logGroup
// формат имени индекса: cwl-YYYY.MM.DD
var indexName = [
indexPrefix + timestamp.getUTCFullYear(), // год
('0' + (timestamp.getUTCMonth() + 1)).slice(-2), // месяц
('0' + timestamp.getUTCDate()).slice(-2) // день
].join('.');
console.log('Индекс установлен на ' + indexName);
var source = buildSource(logEvent.message, logEvent.extractedFields);
source['@id'] = logEvent.id;
source['@timestamp'] = new Date(1 * logEvent.timestamp).toISOString();
source['@message'] = logEvent.message;
source['@owner'] = payload.owner;
source['@log_group'] = payload.logGroup;
source['@log_stream'] = payload.logStream;
var action = { "index": {} };
action.index._index = indexName;
action.index._type = payload.logGroup;
action.index._id = logEvent.id;
bulkRequestBody += [
JSON.stringify(action),
JSON.stringify(source),
].join('\n') + '\n';
});
return bulkRequestBody;
}
function buildSource(message, extractedFields) {
if (extractedFields) {
var source = {};
for (var key in extractedFields) {
if (extractedFields.hasOwnProperty(key) && extractedFields[key]) {
var value = extractedFields[key];
if (isNumeric(value)) {
source[key] = 1 * value;
continue;
}
jsonSubString = extractJson(value);
if (jsonSubString !== null) {
source['$' + key] = JSON.parse(jsonSubString);
}
source[key] = value;
}
}
return source;
}
jsonSubString = extractJson(message);
if (jsonSubString !== null) {
return JSON.parse(jsonSubString);
}
return {};
}
function extractJson(message) {
var jsonStart = message.indexOf('{');
if (jsonStart < 0) return null;
var jsonSubString = message.substring(jsonStart);
return isValidJson(jsonSubString) ? jsonSubString : null;
}
function isValidJson(message) {
try {
JSON.parse(message);
} catch (e) { return false; }
return true;
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function post(body, callback) {
var requestParams = buildRequest(endpoint, body);
var request = https.request(requestParams, function(response) {
var responseBody = '';
response.on('data', function(chunk) {
responseBody += chunk;
});
response.on('end', function() {
var info = JSON.parse(responseBody);
var failedItems;
var success;
if (response.statusCode >= 200 && response.statusCode < 299) {
failedItems = info.items.filter(function(x) {
return x.index.status >= 300;
});
success = {
"attemptedItems": info.items.length,
"successfulItems": info.items.length - failedItems.length,
"failedItems": failedItems.length
};
}
var error = response.statusCode !== 200 || info.errors === true ? {
"statusCode": response.statusCode,
"responseBody": responseBody
} : null;
callback(error, success, response.statusCode, failedItems);
});
}).on('error', function(e) {
callback(e);
});
request.end(requestParams.body);
}
function buildRequest(endpoint, body) {
var endpointParts = endpoint.match(/^([^\.]+)\.?([^\.]*)\.?([^\.]*)\.amazonaws\.com$/);
var region = endpointParts[2];
var service = endpointParts[3];
var datetime = (new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, '');
var date = datetime.substr(0, 8);
var kDate = hmac('AWS4' + process.env.AWS_SECRET_ACCESS_KEY, date);
var kRegion = hmac(kDate, region);
var kService = hmac(kRegion, service);
var kSigning = hmac(kService, 'aws4_request');
var request = {
host: endpoint,
method: 'POST',
path: '/_bulk',
body: body,
headers: {
'Content-Type': 'application/json',
'Host': endpoint,
'Content-Length': Buffer.byteLength(body),
'X-Amz-Security-Token': process.env.AWS_SESSION_TOKEN,
'X-Amz-Date': datetime
}
};
var canonicalHeaders = Object.keys(request.headers)
.sort(function(a, b) { return a.toLowerCase() < b.toLowerCase() ? -1 : 1; })
.map(function(k) { return k.toLowerCase() + ':' + request.headers[k]; })
.join('\n');
var signedHeaders = Object.keys(request.headers)
.map(function(k) { return k.toLowerCase(); })
.sort()
.join(';');
var canonicalString = [
request.method,
request.path, '',
canonicalHeaders, '',
signedHeaders,
hash(request.body, 'hex'),
].join('\n');
var credentialString = [ date, region, service, 'aws4_request' ].join("/");
var stringToSign = [
'AWS4-HMAC-SHA256',
datetime,
credentialString,
hash(canonicalString, 'hex')
] .join('\n');
request.headers.Authorization = [
'AWS4-HMAC-SHA256 Credential=" + process.env.AWS_ACCESS_KEY_ID + "/" + credentialString,
'SignedHeaders=" + signedHeaders,
"Signature=" + hmac(kSigning, stringToSign, "hex')
].join(', ');
return request;
}
function hmac(key, str, encoding) {
return crypto.createHmac('sha256', key).update(str, 'utf8').digest(encoding);
}
function hash(str, encoding) {
return crypto.createHash('sha256').update(str, 'utf8').digest(encoding);
}
Ответ или решение
Альтернативы Kibana для анализа логов CloudWatch
Вопрос о том, как найти подходящее решение для анализа логов в Amazon CloudWatch, с учётом предпочтений пользователей Kibana, является актуальным для многих команд, использующих ELK-стек. Kibana привлекает разработчиков своей продуманной визуализацией данных, хотя Grafana также заслуживает внимания. Давайте рассмотрим имеющиеся альтернативы, их возможности и подходы к интеграции с CloudWatch.
1. Варианты программного обеспечения для визуализации
Amazon Managed Grafana: Хотя вы уже используете Grafana, стоит упомянуть, что Amazon предлагает управляемую версию этой платформы. Она позволяет легко интегрироваться с различными источниками данных, включая CloudWatch. Grafana предоставляет множество плагинов и визуальных компонентов, но изначально она не была разработана для глубокого поиска и анализа логов, что делает её менее гибкой по сравнению с Kibana.
OpenSearch Dashboards: OpenSearch — это проект с открытым исходным кодом, который возник после разделения ElasticSearch. OpenSearch включает в себя Visual Dashboard, аналогичный Kibana. Данная платформа позволяет обрабатывать логи и данные, а также имеет структуру плагинов для расширения функциональности. Она может быть развернута на собственных серверах или использоваться в виде управляемого сервиса.
Grafana Loki: Хотя вы уже знакомы с Grafana, стоит отметить, что Loki представляет собой систему для сбора логов с интеграцией в Grafana. Это решение может быть совместимо с привычной экосистемой Grafana и работает с временными метками, упрощая анализ логов. Для пользователей, уже работающих с Grafana, использование Loki может быть удобным решением.
2. Интеграция CloudWatch с Elasticsearch
Поскольку вы всё же рассматриваете возможность ведения логов в Elasticsearch, стоит упомянуть, что вы можете настроить процесс передачи логов из CloudWatch в Elasticsearch. Для этого, как указано в предоставленном коде Lambda, можно использовать AWS Lambda для обработки логов и их последующей отправки в Elasticsearch.
Процесс выглядит так:
- Логи CloudWatch активируются через AWS Lambda.
- Lambda функция обрабатывает сжатые данные в формате JSON.
- Далее происходит преобразование данных в соответствующий формат для записи в Elasticsearch.
- В завершение данные отправляются в выбранный кластер Elasticsearch.
Эти шаги обеспечивают получение всей необходимой информации в реальном времени и позволяют использовать возможности Kibana для анализа.
3. Открытые решения
Если вы хотите использовать решение на основе открытого кода, рассмотрите использование таких инструментов, как Prometheus и Loki. Комплексное использование этих двух инструментов с Grafana предоставляет возможность визуализировать не только метрики, но и логи, что может быть полезным для вашей команды.
Заключение
Подводя итог, вы можете выбрать, исходя из предпочтений своих разработчиков и потребностей вашей команды, между уже упомянутыми решениями. OpenSearch Dashboards может стать наиболее близким аналогом Kibana с точки зрения функциональности, в то время как интеграция с Elasticsearch через AWS Lambda предоставит вам гибкость и расширенные возможности анализа логов.
Предоставленный план по интеграции и ряд альтернатив помогут вашей команде найти эффективное решение, которое сможет удовлетворить ваши требования к анализу данных в CloudWatch.