Add CI/CD with GitHub Actions and migrate to Deployment

- Migrate from DaemonSet to Deployment for better efficiency
- Add GitHub Actions for automatic build and deploy
- Add Blue-Green deployment strategy with health checks
- Add scripts for development and production workflows
- Update documentation with CI/CD flow
This commit is contained in:
2025-09-25 17:20:38 -03:00
parent 4e57a896fe
commit 3a6875a80e
12 changed files with 1344 additions and 13 deletions

View File

@@ -133,8 +133,10 @@
.validation-item {
padding: 1rem;
border-left: 4px solid #ccc;
margin: 0.5rem 0;
margin: 0.75rem 0;
background: #f8f9fa;
border-radius: 6px;
border: 1px solid #dee2e6;
}
.validation-item.error {
@@ -231,7 +233,7 @@
border: 1px solid #ddd;
border-radius: 8px;
margin-bottom: 1rem;
overflow: hidden;
overflow: visible;
}
.accordion-header {
@@ -272,14 +274,27 @@
}
.accordion-content {
padding: 0;
padding: 1rem 1.5rem;
max-height: 0;
overflow: hidden;
transition: max-height 0.3s ease;
background: white;
border-top: 1px solid #dee2e6;
}
.accordion-content.active {
max-height: 1000px;
max-height: none;
overflow: visible;
padding: 1rem 1.5rem;
}
/* Garantir que o conteúdo não seja cortado */
.accordion-content .validation-item:last-child {
margin-bottom: 0;
}
.accordion-content .historical-validation:last-child {
margin-bottom: 0;
}
.pod-list {
@@ -367,6 +382,74 @@
cursor: not-allowed;
}
/* Historical Analysis Styles */
.historical-summary {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 1rem;
margin-bottom: 1rem;
}
.historical-summary h3 {
margin: 0 0 1rem 0;
color: #495057;
}
.historical-stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
}
.historical-stat {
background: white;
padding: 0.75rem;
border-radius: 6px;
border-left: 4px solid #007bff;
}
.historical-stat h4 {
margin: 0 0 0.5rem 0;
font-size: 0.9rem;
color: #6c757d;
}
.historical-stat .value {
font-size: 1.5rem;
font-weight: bold;
color: #007bff;
}
.historical-validation {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 1rem;
margin-bottom: 0.75rem;
}
.historical-validation.error {
background: #f8d7da;
border-color: #f5c6cb;
}
.historical-validation.warning {
background: #fff3cd;
border-color: #ffeaa7;
}
.historical-validation.info {
background: #d1ecf1;
border-color: #bee5eb;
}
.historical-validation.critical {
background: #f8d7da;
border-color: #f5c6cb;
border-left: 4px solid #dc3545;
}
.pagination button.active {
background: #cc0000;
color: white;
@@ -470,6 +553,7 @@
<div style="display: flex; gap: 1rem; flex-wrap: wrap;">
<button class="btn" onclick="loadClusterStatus()">Atualizar Status</button>
<button class="btn btn-secondary" onclick="loadValidationsByNamespace()">Ver Validações</button>
<button class="btn btn-secondary" onclick="loadHistoricalValidations()">Análise Histórica</button>
<button class="btn btn-secondary" onclick="loadVPARecommendations()">Ver VPA</button>
</div>
</div>
@@ -494,6 +578,39 @@
</div>
</div>
<!-- Análise Histórica -->
<div class="card" id="historicalCard" style="display: none;">
<h2>Análise Histórica com Prometheus</h2>
<!-- Filtros para análise histórica -->
<div class="filters">
<div class="filter-group">
<label for="timeRangeFilter">Período:</label>
<select id="timeRangeFilter">
<option value="1h">1 hora</option>
<option value="6h">6 horas</option>
<option value="24h" selected>24 horas</option>
<option value="7d">7 dias</option>
<option value="30d">30 dias</option>
</select>
</div>
<div class="filter-group">
<label for="historicalSeverityFilter">Severidade:</label>
<select id="historicalSeverityFilter">
<option value="">Todas</option>
<option value="error">Erro</option>
<option value="warning">Aviso</option>
<option value="info">Info</option>
<option value="critical">Crítico</option>
</select>
</div>
<button class="btn" onclick="loadHistoricalValidations()">Aplicar Análise</button>
</div>
<div id="historicalSummary" class="historical-summary"></div>
<div id="historicalValidationsList"></div>
</div>
<!-- Validações -->
<div class="card" id="validationsCard" style="display: none;">
<h2>Validações de Recursos</h2>
@@ -567,7 +684,10 @@
const data = await response.json();
currentData = data;
updateStats(data);
showSuccess('Status do cluster carregado com sucesso');
showSuccess('Status do cluster carregado com sucesso. Carregando validações...');
// Carregar automaticamente as validações após o scan inicial
await loadValidationsByNamespace();
} catch (error) {
showError('Erro ao carregar status do cluster: ' + error.message);
@@ -957,6 +1077,148 @@
document.getElementById('error').classList.add('hidden');
document.getElementById('success').classList.add('hidden');
}
// Funções para análise histórica
async function loadHistoricalValidations() {
showLoading();
try {
const timeRange = document.getElementById('timeRangeFilter').value;
const severity = document.getElementById('historicalSeverityFilter').value;
// Carregar resumo histórico
const summaryResponse = await fetch(`/api/v1/cluster/historical-summary?time_range=${timeRange}`);
if (summaryResponse.ok) {
const summaryData = await summaryResponse.json();
displayHistoricalSummary(summaryData.summary);
}
// Carregar validações históricas
const params = new URLSearchParams({
time_range: timeRange
});
if (severity) {
params.append('severity', severity);
}
const response = await fetch(`/api/v1/validations/historical?${params}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const data = await response.json();
displayHistoricalValidations(data);
document.getElementById('historicalCard').style.display = 'block';
} catch (error) {
showError('Erro ao carregar análise histórica: ' + error.message);
} finally {
hideLoading();
}
}
function displayHistoricalSummary(summary) {
const container = document.getElementById('historicalSummary');
if (!summary || Object.keys(summary).length === 0) {
container.innerHTML = '<p>Não foi possível obter dados históricos do Prometheus.</p>';
return;
}
const cpuUtilization = summary.cpu_utilization || 0;
const memoryUtilization = summary.memory_utilization || 0;
container.innerHTML = `
<h3>Resumo Histórico do Cluster (${summary.time_range})</h3>
<div class="historical-stats">
<div class="historical-stat">
<h4>CPU Utilization</h4>
<div class="value">${cpuUtilization.toFixed(1)}%</div>
</div>
<div class="historical-stat">
<h4>Memory Utilization</h4>
<div class="value">${memoryUtilization.toFixed(1)}%</div>
</div>
<div class="historical-stat">
<h4>CPU Usage</h4>
<div class="value">${summary.cpu_usage ? summary.cpu_usage.toFixed(3) : '0'} cores</div>
</div>
<div class="historical-stat">
<h4>Memory Usage</h4>
<div class="value">${summary.memory_usage ? (summary.memory_usage / (1024*1024*1024)).toFixed(2) : '0'} GiB</div>
</div>
</div>
`;
}
function displayHistoricalValidations(data) {
const container = document.getElementById('historicalValidationsList');
if (!data.validations || data.validations.length === 0) {
container.innerHTML = '<p>Nenhuma validação histórica encontrada.</p>';
return;
}
// Filtrar por severidade se especificado
let validations = data.validations;
const severity = document.getElementById('historicalSeverityFilter').value;
if (severity) {
validations = validations.filter(v => v.severity === severity);
}
// Agrupar por namespace
const groupedByNamespace = {};
validations.forEach(validation => {
if (!groupedByNamespace[validation.namespace]) {
groupedByNamespace[validation.namespace] = [];
}
groupedByNamespace[validation.namespace].push(validation);
});
let html = '';
Object.keys(groupedByNamespace).forEach(namespace => {
const namespaceValidations = groupedByNamespace[namespace];
const errorCount = namespaceValidations.filter(v => v.severity === 'error').length;
const warningCount = namespaceValidations.filter(v => v.severity === 'warning').length;
const infoCount = namespaceValidations.filter(v => v.severity === 'info').length;
const criticalCount = namespaceValidations.filter(v => v.severity === 'critical').length;
html += `
<div class="accordion">
<div class="accordion-header" onclick="toggleAccordion(this)">
<div>
<strong>${namespace}</strong>
<span class="badge">${namespaceValidations.length} validações</span>
</div>
<div class="severity-badges">
${criticalCount > 0 ? `<span class="badge severity-critical">${criticalCount} crítico</span>` : ''}
${errorCount > 0 ? `<span class="badge severity-error">${errorCount} erro</span>` : ''}
${warningCount > 0 ? `<span class="badge severity-warning">${warningCount} aviso</span>` : ''}
${infoCount > 0 ? `<span class="badge severity-info">${infoCount} info</span>` : ''}
</div>
<span class="accordion-icon">▼</span>
</div>
<div class="accordion-content">
${namespaceValidations.map(validation => `
<div class="historical-validation ${validation.severity}">
<div class="validation-header">
<strong>${validation.pod_name}</strong> - ${validation.container_name}
<span class="severity-badge severity-${validation.severity}">${validation.severity}</span>
</div>
<div class="validation-message">${validation.message}</div>
<div class="validation-recommendation">
<strong>Recomendação:</strong> ${validation.recommendation}
</div>
</div>
`).join('')}
</div>
</div>
`;
});
container.innerHTML = html;
}
</script>
</body>
</html>