Server Monitoring Best Practices: Keeping Your Laravel App and MongoDB Clusters Alive on OVH
Proactive Monitoring for Laravel & MongoDB on OVH: Beyond Basic Uptime
Maintaining the health and performance of a Laravel application backed by a MongoDB cluster on OVH infrastructure demands a robust, multi-layered monitoring strategy. Simply checking if a server is “up” is insufficient. We need to delve into application-level metrics, database performance, and infrastructure resource utilization to preemptively identify and resolve issues before they impact end-users. This guide focuses on actionable, production-ready techniques and configurations.
1. Application Performance Monitoring (APM) with Laravel Telescope & Prometheus
Laravel Telescope provides invaluable insights into your application’s behavior during development and staging. For production, we’ll leverage its data export capabilities and integrate with a more scalable monitoring solution like Prometheus.
1.1. Enabling and Configuring Telescope for Production
While Telescope is often associated with development, it can be safely enabled in production with proper authorization and configuration. We’ll restrict access to authorized IP addresses and disable features that might incur significant overhead.
// config/telescope.php
return [
/*
|--------------------------------------------------------------------------
| Watchers
|--------------------------------------------------------------------------
|
| This array lists the Telescope watchers that will be registered.
| You may disable any watchers you do not need.
|
*/
'watchers' => [
// ... other watchers
\Laravel\Telescope\Watchers\QueryWatcher::class => env('TELESCOPE_WATCHER_QUERY', true),
\Laravel\Telescope\Watchers\EventWatcher::class => env('TELESCOPE_WATCHER_EVENT', true),
\Laravel\Telescope\Watchers\JobWatcher::class => env('TELESCOPE_WATCHER_JOB', true),
\Laravel\Telescope\Watchers\LogWatcher::class => env('TELESCOPE_WATCHER_LOG', true),
\Laravel\Telescope\Watchers\NotificationWatcher::class => env('TELESCOPE_WATCHER_NOTIFICATION', true),
\Laravel\Telescope\Watchers\CacheWatcher::class => env('TELESCOPE_WATCHER_CACHE', true),
\Laravel\Telescope\Watchers\ScheduleWatcher::class => env('TELESCOPE_WATCHER_SCHEDULE', true),
\Laravel\Telescope\Watchers\CommandWatcher::class => env('TELESCOPE_WATCHER_COMMAND', true),
\Laravel\Telescope\Watchers\ExceptionWatcher::class => env('TELESCOPE_WATCHER_EXCEPTION', true),
\Laravel\Telescope\Watchers\ModelWatcher::class => env('TELESCOPE_WATCHER_MODEL', false), // Often too verbose for prod
\Laravel\Telescope\Watchers\RequestWatcher::class => env('TELESCOPE_WATCHER_REQUEST', true),
\Laravel\Telescope\Watchers\ViewWatcher::class => env('TELESCOPE_WATCHER_VIEW', false), // Can be heavy
],
/*
|--------------------------------------------------------------------------
| Storage Driver
|--------------------------------------------------------------------------
|
| Telescope will store its data in an application-specific way. The
| `driver` may be `database`, `eloquent`, `redis`, or `custom`.
|
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
/*
|--------------------------------------------------------------------------
| Database Configuration
|--------------------------------------------------------------------------
|
| If the `driver` is set to `database`, Telescope will use the
| following configuration to connect to the database.
|
*/
'database' => [
'connection' => env('TELESCOPE_DB_CONNECTION', config('database.default')),
],
/*
|--------------------------------------------------------------------------
| Authorization
|--------------------------------------------------------------------------
|
| Telescope provides two gates: `authorize` and `can_install`.
| The `authorize` gate determines who can access Telescope in the
| application. The `can_install` gate determines who can install
| Telescope's database migrations.
|
*/
'middleware' => [
// ... other middleware
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'path' => env('TELESCOPE_PATH', 'telescope'),
'domain' => env('TELESCOPE_DOMAIN', null),
'middleware' => [
'web',
// Add your custom authorization middleware here
// Example: Restrict by IP
\App\Http\Middleware\AuthorizeTelescopeAccess::class,
],
'storage' => [
'database' => [
'connection' => env('TELESCOPE_DB_CONNECTION', config('database.default')),
'table' => 'telescope_entries',
],
],
'enabled' => env('TELESCOPE_ENABLED', true),
];
Create the authorization middleware:
// app/Http/Middleware/AuthorizeTelescopeAccess.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class AuthorizeTelescopeAccess
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle(Request $request, Closure $next)
{
// Allow access only from specific IP addresses or networks
if ($request->ip() === 'YOUR_ADMIN_IP_ADDRESS' || $request->ip() === 'ANOTHER_ADMIN_IP') {
return $next($request);
}
// Optionally, allow access if authenticated as a super admin
// if (auth()->check() && auth()->user()->is_super_admin) {
// return $next($request);
// }
abort(403, 'Access denied.');
}
}
In your .env file, ensure Telescope is enabled and configure the database connection if it differs from your default:
TELESCOPE_ENABLED=true TELESCOPE_PATH=/telescope TELESCOPE_DRIVER=database TELESCOPE_DB_CONNECTION=mysql # Or your specific connection name
After deploying these changes, run the migrations:
php artisan telescope:install php artisan migrate
1.2. Exporting Telescope Data to Prometheus
Telescope’s data is stored in your application’s database. To integrate with Prometheus, we’ll use a custom exporter or a tool that can query the database and expose metrics in Prometheus format. A simple approach is to create a scheduled task that aggregates key metrics and exposes them via a custom endpoint.
Alternatively, for more direct integration, consider using a library like Prometheus Client for PHP. This allows you to instrument your Laravel application directly.
1.3. Prometheus & Grafana Setup (OVH Context)
On your OVH servers, you’ll need to deploy Prometheus and Grafana. This can be done using Docker for easier management.
Prometheus Configuration (prometheus.yml):
global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
scrape_configs:
# Scrape Prometheus itself
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
# Scrape Node Exporter for host metrics
- job_name: 'node_exporter'
static_configs:
- targets: ['localhost:9100'] # Assuming node_exporter is running on the same host
# Scrape your Laravel application's metrics endpoint
- job_name: 'laravel_app'
static_configs:
- targets: ['your_laravel_app_server_ip:8000'] # Replace with your app's IP and port
metrics_path: '/metrics' # The endpoint where your app exposes metrics
Grafana Configuration:
1. Install Grafana (e.g., via Docker or package manager).
2. Add Prometheus as a data source in Grafana (Configuration -> Data Sources -> Add data source -> Prometheus). Set the URL to http://localhost:9090 (if Grafana and Prometheus are on the same host).
3. Import pre-built Grafana dashboards for Node Exporter and potentially custom dashboards for your Laravel app metrics. You can find many on Grafana.com/dashboards.
2. MongoDB Cluster Monitoring with Percona Monitoring and Management (PMM)
For MongoDB, a dedicated monitoring solution like Percona Monitoring and Management (PMM) is highly recommended. PMM provides deep insights into MongoDB performance, query analysis, and cluster health.
2.1. Deploying PMM Server
Deploy the PMM Server on a dedicated OVH instance or within a Docker environment. The official documentation provides detailed instructions for various deployment methods.
# Example using Docker docker run -d \ --name pmm-server \ -p 80:80 \ -p 443:443 \ -p 3307:3307 \ perconalab/pmm-server:latest
Access the PMM UI via your browser at http://your_pmm_server_ip.
2.2. Adding MongoDB Instances to PMM
PMM uses agents to collect data. For MongoDB, you’ll typically deploy the mongodb_exporter. This can be done by adding your MongoDB instances directly through the PMM UI or by deploying the agent on the MongoDB hosts.
Adding via PMM UI:
- Navigate to “MongoDB” in the PMM UI.
- Click “Add MongoDB Instance”.
- Provide the MongoDB connection string (e.g.,
mongodb://user:password@host1:27017,host2:27017/admin?replicaSet=rs0). - PMM will automatically deploy and configure the necessary exporter.
Deploying Agent on Host (if preferred):
Follow the PMM documentation to install the PMM client and then register your MongoDB instance with the PMM server.
2.3. Key MongoDB Metrics to Monitor
Within PMM, focus on these critical metrics:
- Query Performance: Slow queries (count, duration), query execution time, query patterns.
- Connections: Active connections, connection pool usage, connection errors.
- Replication Lag: For replica sets, monitor the oplog window and replication lag between members.
- Disk I/O: Read/write operations per second, I/O wait times.
- Memory Usage: Resident Set Size (RSS), cache hit rates, page faults.
- CPU Usage: System and user CPU time.
- Network Traffic: Bytes sent/received.
- Locking: Global lock percentage, read/write lock contention.
3. Infrastructure Monitoring with OVH Control Panel & Node Exporter
OVH provides a comprehensive control panel for managing your instances, but for real-time, granular infrastructure metrics, we rely on standard tools like Node Exporter.
3.1. Node Exporter Deployment
Node Exporter collects hardware and OS metrics. Deploy it on each of your OVH instances (web servers, database servers).
# Download the latest version wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz tar xvfz node_exporter-1.7.0.linux-amd64.tar.gz cd node_exporter-1.7.0.linux-amd64 # Run Node Exporter (in foreground for testing, use systemd for production) ./node_exporter # For production, create a systemd service file (e.g., /etc/systemd/system/node_exporter.service) [Unit] Description=Node Exporter Wants=network-online.target After=network-online.target [Service] User=prometheus # Create a dedicated user ExecStart=/path/to/node_exporter/node_exporter --collector.textfile.directory=/path/to/node_exporter/textfile_collector [Install] WantedBy=multi-user.target # Then enable and start: # sudo systemctl daemon-reload # sudo systemctl enable node_exporter # sudo systemctl start node_exporter
Ensure Node Exporter is accessible by your Prometheus server (usually on port 9100). Configure Prometheus to scrape these targets as shown in the prometheus.yml example above.
3.2. Essential Infrastructure Metrics
Monitor the following via Grafana dashboards connected to Node Exporter:
- CPU Usage: Overall CPU utilization, per-core usage, load average.
- Memory Usage: Total, free, buffered, cached memory. Swap usage.
- Disk I/O: Read/write operations, I/O wait times, disk space utilization.
- Network: Bandwidth usage (in/out), network errors, packet drops.
- System Uptime: Ensure instances haven’t unexpectedly rebooted.
4. Log Aggregation and Analysis
Centralized logging is crucial for debugging and auditing. For a Laravel and MongoDB stack, consider using ELK Stack (Elasticsearch, Logstash, Kibana) or a managed service.
4.1. Laravel Logging Configuration
Configure Laravel’s Monolog to send logs to a central logging system. You can use a custom handler or a pre-built package.
// config/logging.php
'channels' => [
// ... other channels
'elastic' => [
'driver' => 'custom',
'via' => \App\Logging\ElasticsearchLogHandler::class, // Custom handler
'level' => env('LOG_LEVEL', 'debug'),
],
'stderr' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
],
// Set default channel to 'elastic' or 'stderr' as needed
'default' => env('LOG_CHANNEL', 'stderr'),
Create the custom handler (example for sending to Logstash via TCP):
// app/Logging/ElasticsearchLogHandler.php
namespace App\Logging;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\Logger;
use GuzzleHttp\Client; // Assuming you'll use Guzzle to send to Logstash/Elasticsearch
class ElasticsearchLogHandler extends AbstractProcessingHandler
{
protected $client;
protected $logstashUrl;
public function __construct(array $config = [])
{
$level = $config['level'] ?? Logger::DEBUG;
parent::__construct($level, true);
$this->logstashUrl = $config['url'] ?? 'http://your_logstash_host:8080/_bulk'; // Or your Elasticsearch endpoint
$this->client = new Client();
}
protected function write(array $record): void
{
// Format the log record for Logstash/Elasticsearch
$formattedRecord = [
'message' => $record['message'],
'level' => $record['level_name'],
'context' => $record['context'],
'extra' => $record['extra'],
'datetime' => $record['datetime']->format('Y-m-d\TH:i:s.uP'),
'channel' => $record['channel'],
];
try {
$this->client->post($this->logstashUrl, [
'json' => $formattedRecord,
'headers' => [
'Content-Type' => 'application/json',
],
]);
} catch (\Exception $e) {
// Handle exceptions, e.g., log to stderr if Logstash is down
error_log("Failed to send log to Logstash: " . $e->getMessage());
}
}
}
Configure your .env file:
LOG_CHANNEL=elastic LOG_LEVEL=info LOG_ELASTICSEARCH_URL=http://your_logstash_host:8080/_bulk
4.2. MongoDB Logging
MongoDB’s own logs are critical. Ensure they are being collected. You can configure MongoDB to log to files and then use a log shipper like Filebeat or Fluentd to send them to your central logging system. Alternatively, if using PMM, it often provides access to MongoDB logs.
5. Alerting Strategy
Proactive monitoring is only effective if it triggers alerts when thresholds are breached or anomalies are detected. Integrate Prometheus Alertmanager with your monitoring stack.
5.1. Prometheus Alertmanager Configuration
Configure Alertmanager rules in Prometheus (e.g., in a separate alert.rules.yml file) and define receivers (e.g., Slack, PagerDuty, email).
# alert.rules.yml
groups:
- name: laravel_alerts
rules:
- alert: HighErrorRate
expr: sum(rate(laravel_exceptions_total[5m])) by (job, instance) > 10
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate detected on {{ $labels.instance }}"
description: "The application on {{ $labels.instance }} is experiencing a high rate of errors (more than 10 per 5 minutes)."
- alert: HighQueryLatency
expr: avg_over_time(mongodb_mongod_operation_latency_seconds{operation="query"}[5m]) > 0.5
for: 10m
labels:
severity: warning
annotations:
summary: "High MongoDB query latency on {{ $labels.instance }}"
description: "Average query latency on {{ $labels.instance }} has been above 0.5 seconds for 10 minutes."
- alert: HighReplicationLag
expr: mongodb_replset_member_state{state="SECONDARY", replica_set="rs0"} == 1 and mongodb_replset_oplog_window_seconds > 300 # 5 minutes lag
for: 5m
labels:
severity: critical
annotations:
summary: "MongoDB replication lag on {{ $labels.instance }}"
description: "Replica set member {{ $labels.instance }} is lagging by more than 5 minutes."
- name: infrastructure_alerts
rules:
- alert: HighCpuUsage
expr: 100 - avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100 > 90
for: 10m
labels:
severity: warning
annotations:
summary: "High CPU usage on {{ $labels.instance }}"
description: "CPU utilization on {{ $labels.instance }} is above 90% for 10 minutes."
- alert: LowDiskSpace
expr: (node_filesystem_avail_bytes / node_filesystem_size_bytes) * 100 < 10
for: 15m
labels:
severity: critical
annotations:
summary: "Low disk space on {{ $labels.instance }}"
description: "Filesystem {{ $labels.mountpoint }} on {{ $labels.instance }} has less than 10% free space."
# alertmanager.yml
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'cluster', 'service']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'default-receiver' # Default receiver if no specific route matches
routes:
- match:
severity: 'critical'
receiver: 'pagerduty-critical'
continue: true # Allows matching other routes
- match:
severity: 'warning'
receiver: 'slack-warning'
continue: true
receivers:
- name: 'default-receiver'
slack_configs:
- api_url: 'YOUR_SLACK_WEBHOOK_URL'
channel: '#alerts-general'
- name: 'pagerduty-critical'
pagerduty_configs:
- service_key: 'YOUR_PAGERDUTY_INTEGRATION_KEY'
- name: 'slack-warning'
slack_configs:
- api_url: 'YOUR_SLACK_WEBHOOK_URL'
channel: '#alerts-warning'
Ensure Prometheus is configured to load these rules and Alertmanager is correctly set up to receive alerts from Prometheus.
6. OVH Specific Considerations
When operating on OVH, keep the following in mind:
- Network Segmentation: Utilize OVH’s network features to isolate your database cluster from your web servers, enhancing security and performance. Ensure monitoring agents can communicate across segments if necessary.
- Instance Types: Choose appropriate instance types for your web servers and MongoDB nodes. Monitor resource utilization closely to identify bottlenecks and scale accordingly.
- Backup Strategy: While not strictly monitoring, ensure your OVH instance snapshots and MongoDB backup strategy are robust and regularly tested. Monitoring should alert you if backups fail.
- OVH API Monitoring: For critical infrastructure management, consider monitoring OVH API health and quotas if you heavily rely on them for automated tasks.
Conclusion
A comprehensive monitoring strategy for a Laravel and MongoDB stack on OVH involves integrating application-level insights (Telescope, APM tools), database-specific metrics (PMM), infrastructure health (Node Exporter), and centralized logging. By setting up proactive alerting with Prometheus and Alertmanager, you can significantly reduce downtime and ensure the stability and performance of your critical applications.