first commit
This commit is contained in:
commit
01d7cb1a80
34 files changed
+5723
No files matched your search
@@ -0,0 +1,8 @@
|
||||
FROM node:20-alpine
|
||||
WORKDIR /app
|
||||
COPY package.json ./
|
||||
RUN npm install --production
|
||||
COPY server.js ./
|
||||
RUN apk add --no-cache wget
|
||||
EXPOSE 3000
|
||||
CMD ["node", "server.js"]
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "sockmap-demo-app",
|
||||
"version": "1.0.0",
|
||||
"description": "App Node.js de demonstração para eBPF sockmap",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"mysql2": "^3.6.0",
|
||||
"pg": "^8.11.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
// ─────────────────────────────────────────────────────────
|
||||
// server.js — Servidor Node.js de demonstração
|
||||
//
|
||||
// Endpoints:
|
||||
// GET /health → Health check
|
||||
// GET / → Página principal com info
|
||||
// GET /mysql/users → Lista usuários do MySQL
|
||||
// GET /postgres/products→ Lista produtos do PostgreSQL
|
||||
// GET /benchmark → Teste de latência para ambos os DBs
|
||||
//
|
||||
// Este servidor se conecta ao MySQL e PostgreSQL.
|
||||
// Com o eBPF sockmap ativo, a comunicação com os bancos
|
||||
// é acelerada (dados redirecionados no nível do socket).
|
||||
// ─────────────────────────────────────────────────────────
|
||||
|
||||
const http = require('http');
|
||||
const url = require('url');
|
||||
|
||||
// ─── Configuração MySQL ───
|
||||
const mysql = require('mysql2/promise');
|
||||
const mysqlConfig = {
|
||||
host: process.env.MYSQL_HOST || 'mysql',
|
||||
port: parseInt(process.env.MYSQL_PORT || '3306'),
|
||||
user: process.env.MYSQL_USER || 'app',
|
||||
password: process.env.MYSQL_PASSWORD || 'app_password',
|
||||
database: process.env.MYSQL_DATABASE || 'app_db',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
};
|
||||
|
||||
// ─── Configuração PostgreSQL ───
|
||||
const { Pool } = require('pg');
|
||||
const pgPool = new Pool({
|
||||
host: process.env.POSTGRES_HOST || 'postgres',
|
||||
port: parseInt(process.env.POSTGRES_PORT || '5432'),
|
||||
user: process.env.POSTGRES_USER || 'app',
|
||||
password: process.env.POSTGRES_PASSWORD || 'app_password',
|
||||
database: process.env.POSTGRES_DATABASE || 'app_db',
|
||||
max: 10,
|
||||
});
|
||||
|
||||
let mysqlPool;
|
||||
|
||||
// ─── Inicializa conexão MySQL ───
|
||||
async function initMySQL() {
|
||||
try {
|
||||
mysqlPool = mysql.createPool(mysqlConfig);
|
||||
const [rows] = await mysqlPool.query('SELECT 1');
|
||||
console.log('✅ MySQL conectado!');
|
||||
} catch (err) {
|
||||
console.error('❌ Erro ao conectar no MySQL:', err.message);
|
||||
setTimeout(initMySQL, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Inicializa conexão PostgreSQL ───
|
||||
async function initPostgres() {
|
||||
try {
|
||||
const res = await pgPool.query('SELECT 1');
|
||||
console.log('✅ PostgreSQL conectado!');
|
||||
} catch (err) {
|
||||
console.error('❌ Erro ao conectar no PostgreSQL:', err.message);
|
||||
setTimeout(initPostgres, 3000);
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Mede o tempo de execução de uma query (em ms) ───
|
||||
async function measureQuery(fn) {
|
||||
const start = process.hrtime.bigint();
|
||||
const result = await fn();
|
||||
const end = process.hrtime.bigint();
|
||||
const durationMs = Number(end - start) / 1_000_000;
|
||||
return { result, durationMs };
|
||||
}
|
||||
|
||||
// ─── Handlers de rotas ───
|
||||
const routes = {
|
||||
// Health check
|
||||
'/health': async () => ({
|
||||
status: 'ok',
|
||||
timestamp: new Date().toISOString(),
|
||||
uptime: process.uptime(),
|
||||
}),
|
||||
|
||||
// Página principal
|
||||
'/': async () => ({
|
||||
app: 'eBPF Sockmap Demo',
|
||||
message: 'Servidor Node.js funcionando! 🚀',
|
||||
endpoints: {
|
||||
'/health': 'Health check',
|
||||
'/mysql/users': 'Listar usuários (MySQL)',
|
||||
'/postgres/products': 'Listar produtos (PostgreSQL)',
|
||||
'/benchmark': 'Benchmark de latência nos dois bancos',
|
||||
},
|
||||
info: {
|
||||
description: 'Com eBPF sockmap ativo, a comunicação entre este servidor e os bancos é acelerada.',
|
||||
como_funciona: 'Os dados são redirecionados diretamente entre sockets, pulando a pilha TCP/IP do kernel.',
|
||||
},
|
||||
}),
|
||||
|
||||
// Listar usuários do MySQL
|
||||
'/mysql/users': async () => {
|
||||
const { result, durationMs } = await measureQuery(async () => {
|
||||
const [rows] = await mysqlPool.query('SELECT * FROM users');
|
||||
return rows;
|
||||
});
|
||||
return {
|
||||
source: 'MySQL',
|
||||
query_time_ms: durationMs.toFixed(3),
|
||||
count: result.length,
|
||||
data: result,
|
||||
};
|
||||
},
|
||||
|
||||
// Listar produtos do PostgreSQL
|
||||
'/postgres/products': async () => {
|
||||
const { result, durationMs } = await measureQuery(async () => {
|
||||
const res = await pgPool.query('SELECT * FROM products');
|
||||
return res.rows;
|
||||
});
|
||||
return {
|
||||
source: 'PostgreSQL',
|
||||
query_time_ms: durationMs.toFixed(3),
|
||||
count: result.length,
|
||||
data: result,
|
||||
};
|
||||
},
|
||||
|
||||
// Benchmark: faz várias queries e mede a latência
|
||||
'/benchmark': async () => {
|
||||
const iterations = 100;
|
||||
const results = {
|
||||
mysql: { times: [], avg: 0, min: Infinity, max: 0 },
|
||||
postgres: { times: [], avg: 0, min: Infinity, max: 0 },
|
||||
};
|
||||
|
||||
// Benchmark MySQL
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const { durationMs } = await measureQuery(async () => {
|
||||
const [rows] = await mysqlPool.query('SELECT 1');
|
||||
return rows;
|
||||
});
|
||||
results.mysql.times.push(durationMs);
|
||||
results.mysql.min = Math.min(results.mysql.min, durationMs);
|
||||
results.mysql.max = Math.max(results.mysql.max, durationMs);
|
||||
}
|
||||
results.mysql.avg = results.mysql.times.reduce((a, b) => a + b, 0) / iterations;
|
||||
|
||||
// Benchmark PostgreSQL
|
||||
for (let i = 0; i < iterations; i++) {
|
||||
const { durationMs } = await measureQuery(async () => {
|
||||
await pgPool.query('SELECT 1');
|
||||
});
|
||||
results.postgres.times.push(durationMs);
|
||||
results.postgres.min = Math.min(results.postgres.min, durationMs);
|
||||
results.postgres.max = Math.max(results.postgres.max, durationMs);
|
||||
}
|
||||
results.postgres.avg = results.postgres.times.reduce((a, b) => a + b, 0) / iterations;
|
||||
|
||||
// Limpa arrays de tempos individuais
|
||||
delete results.mysql.times;
|
||||
delete results.postgres.times;
|
||||
|
||||
return {
|
||||
benchmark: `${iterations} queries SELECT 1 em cada banco`,
|
||||
dica: 'Compare os tempos com e sem eBPF sockmap!',
|
||||
mysql: {
|
||||
avg_ms: results.mysql.avg.toFixed(3),
|
||||
min_ms: results.mysql.min.toFixed(3),
|
||||
max_ms: results.mysql.max.toFixed(3),
|
||||
},
|
||||
postgres: {
|
||||
avg_ms: results.postgres.avg.toFixed(3),
|
||||
min_ms: results.postgres.min.toFixed(3),
|
||||
max_ms: results.postgres.max.toFixed(3),
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
// ─── Servidor HTTP ───
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const pathname = url.parse(req.url).pathname;
|
||||
const handler = routes[pathname];
|
||||
|
||||
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
||||
|
||||
if (!handler) {
|
||||
res.writeHead(404);
|
||||
res.end(JSON.stringify({ error: 'Rota não encontrada', path: pathname }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = await handler();
|
||||
res.writeHead(200);
|
||||
res.end(JSON.stringify(data, null, 2));
|
||||
} catch (err) {
|
||||
console.error(`Erro em ${pathname}:`, err.message);
|
||||
res.writeHead(500);
|
||||
res.end(JSON.stringify({ error: err.message }));
|
||||
}
|
||||
});
|
||||
|
||||
// ─── Inicialização ───
|
||||
const PORT = 3000;
|
||||
|
||||
async function main() {
|
||||
console.log('🔧 Iniciando servidor Node.js...');
|
||||
await initMySQL();
|
||||
await initPostgres();
|
||||
|
||||
server.listen(PORT, '0.0.0.0', () => {
|
||||
console.log(`🚀 Servidor rodando na porta ${PORT}`);
|
||||
console.log(`📊 Acesse http://localhost:${PORT}/benchmark para testar latência`);
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(console.error);
|
||||
@@ -0,0 +1,25 @@
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- MySQL: Script de inicialização do banco de dados
|
||||
-- Cria tabelas de exemplo para teste
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
name VARCHAR(100) NOT NULL,
|
||||
email VARCHAR(150) NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS requests_log (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
endpoint VARCHAR(255) NOT NULL,
|
||||
method VARCHAR(10) NOT NULL,
|
||||
response_time_ms FLOAT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Dados de exemplo
|
||||
INSERT INTO users (name, email) VALUES
|
||||
('Alice Silva', 'alice@exemplo.com'),
|
||||
('Bob Santos', 'bob@exemplo.com'),
|
||||
('Carol Oliveira', 'carol@exemplo.com');
|
||||
@@ -0,0 +1,28 @@
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
-- PostgreSQL: Script de inicialização do banco de dados
|
||||
-- Cria tabelas de exemplo para teste
|
||||
-- ─────────────────────────────────────────────────────────
|
||||
|
||||
CREATE TABLE IF NOT EXISTS products (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(200) NOT NULL,
|
||||
price NUMERIC(10, 2) NOT NULL,
|
||||
stock INT DEFAULT 0,
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS orders (
|
||||
id SERIAL PRIMARY KEY,
|
||||
user_name VARCHAR(100) NOT NULL,
|
||||
product_id INT REFERENCES products(id),
|
||||
quantity INT NOT NULL DEFAULT 1,
|
||||
total NUMERIC(10, 2),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Dados de exemplo
|
||||
INSERT INTO products (name, price, stock) VALUES
|
||||
('Notebook Dell', 4500.00, 10),
|
||||
('Mouse Logitech', 150.00, 50),
|
||||
('Teclado Mecânico', 350.00, 30),
|
||||
('Monitor 27"', 2200.00, 15);
|
||||
@@ -0,0 +1,113 @@
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# Docker Compose - Stack completa para testar eBPF Sockmap
|
||||
#
|
||||
# Serviços:
|
||||
# nginx → Proxy reverso (porta 80 → Node.js)
|
||||
# app → Servidor Node.js (porta 3000)
|
||||
# mysql → Banco de dados MySQL (porta 3306)
|
||||
# postgres → Banco de dados PostgreSQL (porta 5432)
|
||||
#
|
||||
# Todos os containers ficam na mesma rede bridge,
|
||||
# e o eBPF sockmap acelera a comunicação entre eles.
|
||||
# ─────────────────────────────────────────────────────────
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
# ─── NGINX Proxy Reverso ───
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: sockmap-nginx
|
||||
ports:
|
||||
- "8080:80" # Acesse http://localhost:8080
|
||||
volumes:
|
||||
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||
depends_on:
|
||||
app:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- sockmap-net
|
||||
restart: unless-stopped
|
||||
|
||||
# ─── Aplicação Node.js ───
|
||||
app:
|
||||
build:
|
||||
context: ./app
|
||||
dockerfile: Dockerfile
|
||||
container_name: sockmap-app
|
||||
environment:
|
||||
NODE_ENV: production
|
||||
MYSQL_HOST: mysql
|
||||
MYSQL_PORT: 3306
|
||||
MYSQL_USER: app
|
||||
MYSQL_PASSWORD: app_password
|
||||
MYSQL_DATABASE: app_db
|
||||
POSTGRES_HOST: postgres
|
||||
POSTGRES_PORT: 5432
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: app_password
|
||||
POSTGRES_DATABASE: app_db
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- sockmap-net
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
restart: unless-stopped
|
||||
|
||||
# ─── MySQL ───
|
||||
mysql:
|
||||
image: mysql:8.0
|
||||
container_name: sockmap-mysql
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: root_password
|
||||
MYSQL_DATABASE: app_db
|
||||
MYSQL_USER: app
|
||||
MYSQL_PASSWORD: app_password
|
||||
volumes:
|
||||
- mysql-data:/var/lib/mysql
|
||||
- ./db/mysql-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
networks:
|
||||
- sockmap-net
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot_password"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
restart: unless-stopped
|
||||
|
||||
# ─── PostgreSQL ───
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: sockmap-postgres
|
||||
environment:
|
||||
POSTGRES_DB: app_db
|
||||
POSTGRES_USER: app
|
||||
POSTGRES_PASSWORD: app_password
|
||||
volumes:
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
- ./db/postgres-init.sql:/docker-entrypoint-initdb.d/init.sql:ro
|
||||
networks:
|
||||
- sockmap-net
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U app -d app_db"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 30
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mysql-data:
|
||||
postgres-data:
|
||||
|
||||
networks:
|
||||
sockmap-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.28.0.0/16
|
||||
@@ -0,0 +1,78 @@
|
||||
# ─────────────────────────────────────────────────────────
|
||||
# nginx.conf — Proxy reverso para o servidor Node.js
|
||||
#
|
||||
# Nginx recebe requisições na porta 80 e encaminha para
|
||||
# o container do Node.js na porta 3000.
|
||||
#
|
||||
# Com o eBPF sockmap ativo, a comunicação nginx→app
|
||||
# é acelerada (dados pulam a pilha de rede do kernel).
|
||||
# ─────────────────────────────────────────────────────────
|
||||
|
||||
worker_processes auto;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
use epoll;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Formato de log com tempo de resposta
|
||||
log_format main '$remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'rt=$request_time';
|
||||
|
||||
access_log /var/log/nginx/access.log main;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
sendfile on;
|
||||
tcp_nopush on;
|
||||
tcp_nodelay on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
keepalive_requests 100;
|
||||
|
||||
# Compressão gzip
|
||||
gzip on;
|
||||
gzip_types text/plain application/json application/javascript text/css;
|
||||
|
||||
# ─── Upstream: servidor Node.js ───
|
||||
upstream nodejs_app {
|
||||
server sockmap-app:3000;
|
||||
keepalive 32; # Conexões persistentes (importante para sockmap!)
|
||||
}
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
# Página de status do Nginx
|
||||
location /nginx-status {
|
||||
stub_status on;
|
||||
access_log off;
|
||||
}
|
||||
|
||||
# Todas as outras requisições vão para o Node.js
|
||||
location / {
|
||||
proxy_pass http://nodejs_app;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
# Headers para proxy reverso
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# Manter conexão viva (keepalive)
|
||||
proxy_set_header Connection "";
|
||||
|
||||
# Timeouts
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 30s;
|
||||
proxy_send_timeout 30s;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user