Posted on
Artificial Intelligence

Artificial Intelligence Linux Operations

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Linux Operations: Bring AIOps to Your Bash Toolbox

If your pager has ever gone off at 2 a.m. because “the server is slow” and all you had was a wall of logs and top(1), this post is for you. Modern Linux fleets produce more telemetry than any human can sift through in time. Artificial Intelligence for IT Operations (AIOps) adds just enough machine learning to your existing Bash workflows to spot anomalies early, triage root causes faster, and even trigger safe auto-remediation.

In this article you’ll:

  • See why AI in Linux ops is more than hype.

  • Install lightweight tooling on Debian/Ubuntu, Fedora/RHEL, or openSUSE/SLES.

  • Build a tiny anomaly detector around your logs and metrics.

  • Automate alerts and add guardrailed self-healing.

  • Walk through a realistic “5xx spike” example.

Why AIOps on Linux now?

  • Scale and complexity: One admin can manage hundreds of instances with containers, services, and timers. Humans are great at reasoning, not at scanning 50k lines of logs per minute.

  • Signals are already there: systemd-journald, sysstat (sar/mpstat), load averages, disk usage—all easy to pull from Bash.

  • Light ML goes a long way: Unsupervised anomaly detection can flag “not normal” behavior without a huge labeled dataset.

  • Better MTTR: Early warnings and safe, automated responses cut downtime without taking control away from humans.

Prerequisites and installation

You’ll need sudo on a Linux host with systemd and an outbound network connection for pip wheels.

Install the base tools (sysstat for metrics, curl/jq for webhooks, Python 3 + pip/venv for ML, and a mailer for local alerts):

  • Debian/Ubuntu (apt)
sudo apt update
sudo apt install -y sysstat curl jq python3 python3-pip python3-venv mailutils
  • Fedora/RHEL/CentOS Stream (dnf)
sudo dnf install -y sysstat curl jq python3 python3-pip mailx
  • openSUSE/SLES (zypper)
sudo zypper refresh
sudo zypper install -y sysstat curl jq python3 python3-pip mailx

Enable sysstat collection:

  • Debian/Ubuntu:
sudo sed -i 's/^ENABLED="false"/ENABLED="true"/' /etc/default/sysstat || true
sudo systemctl enable --now sysstat
  • Fedora/RHEL/openSUSE/SLES:
sudo systemctl enable --now sysstat

Create a Python virtual environment for your detector (recommended):

python3 -m venv ~/aiops-venv || python3 -m pip install --user virtualenv && python3 -m virtualenv ~/aiops-venv
source ~/aiops-venv/bin/activate
pip install --upgrade pip
pip install numpy pandas scikit-learn
deactivate

The plan at a glance

1) Collect cheap signals: load, CPU idle, disk use, error counts in the last minute. 2) Run a tiny ML model (Isolation Forest) to score “weirdness.” 3) Alert via email or Slack; log context for postmortems. 4) Optional, safe self-healing after repeated anomalies.

Below are fully copy-pastable snippets.

1) Collect signals with Bash

Create a script to compute a small feature vector:

sudo install -d -o root -g root -m 755 /usr/local/bin
sudo tee /usr/local/bin/aiops_watch.sh >/dev/null <<'EOF'
#!/usr/bin/env bash
set -euo pipefail

# Config
STATE_DIR="/var/lib/aiops"
LOG_DIR="/var/log/aiops"
HISTORY="${STATE_DIR}/feature_history.csv"
ANOM_CT_FILE="${STATE_DIR}/anomaly_count"
THRESHOLD_RESTARTS=3
TARGET_SERVICE="${TARGET_SERVICE:-nginx}"   # override with env var
MAIL_TO="${MAIL_TO:-root@localhost}"
SLACK_WEBHOOK_URL="${SLACK_WEBHOOK_URL:-}"  # optional

mkdir -p "$STATE_DIR" "$LOG_DIR"

timestamp="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

# Features:
# - 1m error count from journal (priority>=err)
errors_1m=$(journalctl --since "1 min ago" -p err..alert 2>/dev/null | wc -l || echo 0)

# - 1s CPU sample (%idle) via mpstat (sysstat)
cpu_idle=$(mpstat 1 1 | awk '/all/ {idle=$NF} END{print idle+0}')

# - 1-minute load average
load1=$(awk '{print $1}' /proc/loadavg)

# - Disk use percentage on root
disk_use=$(df -P / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')

# Compose CSV row
features="${timestamp},${load1},${cpu_idle},${disk_use},${errors_1m}"

mkdir -p "$(dirname "$HISTORY")"
if [ ! -s "$HISTORY" ]; then
  echo "timestamp,load1,cpu_idle,disk_use,errors_1m" >"$HISTORY"
fi
echo "$features" >>"$HISTORY"

# Run detector (expects exit 0=OK, 2=ANOMALY). Uses virtualenv if present.
VENV="${VENV:-$HOME/aiops-venv}"
PY="${PY:-python3}"
if [ -x "${VENV}/bin/python" ]; then
  PY="${VENV}/bin/python"
fi

out="$("$PY" /usr/local/bin/ai_detect.py --history "$HISTORY" --latest "$features" 2>&1 || true)"
echo "$timestamp $out" >>"${LOG_DIR}/detector.log"

status="OK"
echo "$out" | grep -q "ANOMALY" && status="ANOMALY"

# Track consecutive anomalies
count=0
if [ -f "$ANOM_CT_FILE" ]; then
  count=$(cat "$ANOM_CT_FILE")
fi
if [ "$status" = "ANOMALY" ]; then
  count=$((count+1))
else
  count=0
fi
echo "$count" >"$ANOM_CT_FILE"

# Build a human-readable message
msg="AIOps status: ${status}
time: ${timestamp}
load1=${load1} cpu_idle=${cpu_idle}% disk_use=${disk_use}% errors_1m=${errors_1m}
streak=${count}"

# Alert on every anomaly
if [ "$status" = "ANOMALY" ]; then
  # Email (mail or mailx)
  if command -v mail >/dev/null 2>&1; then
    printf "%s\n" "$msg" | mail -s "AIOps anomaly on $(hostname)" "$MAIL_TO" || true
  elif command -v mailx >/dev/null 2>&1; then
    printf "%s\n" "$msg" | mailx -s "AIOps anomaly on $(hostname)" "$MAIL_TO" || true
  fi
  # Slack webhook
  if [ -n "$SLACK_WEBHOOK_URL" ]; then
    payload=$(printf '{"text":"%s"}' "$(printf '%s' "$msg" | sed 's/"/\\"/g')")
    curl -fsS -X POST -H 'Content-type: application/json' --data "$payload" "$SLACK_WEBHOOK_URL" || true
  fi
fi

# Guardrailed self-healing after repeated anomalies
if [ "$status" = "ANOMALY" ] && [ "$count" -ge "$THRESHOLD_RESTARTS" ]; then
  # Only act if the target service exists and is running/failed
  if systemctl list-unit-files | grep -q "^${TARGET_SERVICE}\.service"; then
    systemctl is-active --quiet "${TARGET_SERVICE}" || true
    echo "$timestamp restarting ${TARGET_SERVICE} after ${count} anomalies" >>"${LOG_DIR}/actions.log"
    systemctl restart "${TARGET_SERVICE}" || true
    # reset streak after action
    echo 0 >"$ANOM_CT_FILE"
  fi
fi

exit 0
EOF
sudo chmod 755 /usr/local/bin/aiops_watch.sh

Notes:

  • Override TARGET_SERVICE, MAIL_TO, SLACK_WEBHOOK_URL via environment or drop-in (explained below).

  • The script keeps a rolling CSV of features in /var/lib/aiops.

2) A tiny anomaly detector (Isolation Forest)

Save the Python detector:

sudo tee /usr/local/bin/ai_detect.py >/dev/null <<'EOF'
#!/usr/bin/env python3
import argparse, os, sys, io
from datetime import datetime
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler

def parse_args():
    ap = argparse.ArgumentParser()
    ap.add_argument("--history", required=True, help="CSV with historical features")
    ap.add_argument("--latest", required=True, help="CSV row: timestamp,load1,cpu_idle,disk_use,errors_1m")
    ap.add_argument("--min_train", type=int, default=50, help="min rows to train IF")
    ap.add_argument("--window", type=int, default=500, help="train on last N rows")
    ap.add_argument("--contamination", type=float, default=0.05, help="expected outlier fraction")
    return ap.parse_args()

def to_frame(latest_row):
    # latest_row as "ts,load1,cpu_idle,disk_use,errors_1m"
    cols = ["timestamp","load1","cpu_idle","disk_use","errors_1m"]
    parts = latest_row.strip().split(",")
    if len(parts) != 5:
        raise ValueError("latest must have 5 comma-separated values")
    d = {c:[p] for c,p in zip(cols, parts)}
    df = pd.DataFrame(d)
    df["timestamp"] = pd.to_datetime(df["timestamp"], utc=True, errors="coerce")
    for c in cols[1:]:
        df[c] = pd.to_numeric(df[c], errors="coerce")
    return df

def main():
    args = parse_args()
    # Load history (ensure exists)
    hist = pd.read_csv(args.history)
    latest = to_frame(args.latest)
    # Append the latest to history on disk if not present (idempotent-ish)
    # The caller already appended; we just ensure we score the last row.
    data = hist.copy()
    # Training window excludes the very last row to avoid trivial fit
    train = data.tail(args.window).copy()
    if len(train) < args.min_train:
        # Fallback to robust z-score on features
        features = ["load1","cpu_idle","disk_use","errors_1m"]
        mu = train[features].median(numeric_only=True)
        mad = (train[features] - mu).abs().median(numeric_only=True).replace(0, 1e-6)
        z = ((latest[features].iloc[0] - mu) / (1.4826*mad)).abs()
        score = -float(z.mean())  # more negative -> more anomalous (for consistency)
        is_anom = (z > 6).any() or (z.mean() > 4)
        status = "ANOMALY" if is_anom else "OK"
        print(f"{status} method=robust_z mean_z={z.mean():.2f} score={score:.3f} features={latest[features].to_dict(orient='records')[0]}")
        sys.exit(2 if is_anom else 0)

    # Isolation Forest on standardized features
    features = ["load1","cpu_idle","disk_use","errors_1m"]
    X = train[features].astype(float).to_numpy()
    scaler = StandardScaler()
    Xs = scaler.fit_transform(X)
    clf = IsolationForest(n_estimators=200, contamination=args.contamination, random_state=42)
    clf.fit(Xs)

    x_new = scaler.transform(latest[features].astype(float).to_numpy())
    score = clf.decision_function(x_new)[0]  # higher is more normal
    pred = clf.predict(x_new)[0]             # -1 anomaly, 1 normal
    is_anom = (pred == -1) or (score < -0.05)

    status = "ANOMALY" if is_anom else "OK"
    print(f"{status} method=isoforest score={score:.3f} features={latest[features].to_dict(orient='records')[0]}")
    sys.exit(2 if is_anom else 0)

if __name__ == "__main__":
    main()
EOF
sudo chmod 755 /usr/local/bin/ai_detect.py

Tip: The detector uses your last up-to-500 observations as “normal.” First runs may use the robust z-score fallback until enough data accumulates.

3) Automate with systemd timers (and alert settings)

Create a systemd service + timer to run every minute:

sudo tee /etc/systemd/system/aiops-anomaly.service >/dev/null <<'EOF'
[Unit]
Description=Run AIOps anomaly detection

[Service]
Type=oneshot
Environment=VENV=/home/%i/aiops-venv
Environment=TARGET_SERVICE=nginx
Environment=MAIL_TO=root@localhost
# Optional Slack webhook:
# Environment=SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXX/YYY/ZZZ
ExecStart=/usr/local/bin/aiops_watch.sh
User=%i
Group=%i
EOF

If you prefer running as root, remove User/Group lines. Otherwise, replace %i with your username in the timer unit below.

# Replace YOURUSER with the user that owns the venv (or omit %i in service and hardcode)
sudo tee /etc/systemd/system/aiops-anomaly@.timer >/dev/null <<'EOF'
[Unit]
Description=Schedule AIOps anomaly detection for %i

[Timer]
OnBootSec=2min
OnUnitActiveSec=1min
Unit=aiops-anomaly.service

[Install]
WantedBy=timers.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now aiops-anomaly@YOURUSER.timer

Alternatively, use cron:

* * * * * VENV=$HOME/aiops-venv TARGET_SERVICE=nginx MAIL_TO=root@localhost /usr/local/bin/aiops_watch.sh >/dev/null 2>&1

4) Real-world example: 5xx spike and safe restart

Scenario: Your web stack starts returning many 5xx responses; error logs explode; CPU idle drops; load climbs. Within 1–2 minutes, your detector flags an anomaly, you get a Slack message, and after three consecutive hits the app service restarts automatically.

  • Alerts: Emails via mail/mailx and optional Slack webhook.

  • Guardrails: The script restarts only after N consecutive anomalies (default 3) and only if the systemd unit exists.

  • For Nginx or your app: Export TARGET_SERVICE=myapp.service.

Optional: Count specific 5xxs from Nginx access logs to enrich features:

five_xx_last_min=$(awk -v d="$(date -u '+%d/%b/%Y:%H:%M')" '$4 ~ d && $9 ~ /^5[0-9][0-9]$/{c++} END{print c+0}' /var/log/nginx/access.log 2>/dev/null || echo 0)

You can add this as a new column in aiops_watch.sh and in ai_detect.py’s features list.

Operational tips

  • Cold start: Expect a short learning period; keep the fallback z-score thresholds conservative.

  • Persist data: /var/lib/aiops/feature_history.csv becomes your quick-and-dirty baseline for future tuning.

  • Tune contamination: If you see too many false positives, lower Isolation Forest’s contamination; too few, raise it slightly.

  • Expand signals: Add memory pressure (vmstat), TCP retransmits (ss -s), container restarts, queue lengths, or app-specific KPIs.

  • Observe-first: Run in alert-only mode for a few days before enabling auto-remediation in production.

Uninstall or disable

  • Stop the timer:
sudo systemctl disable --now aiops-anomaly@YOURUSER.timer
  • Remove files:
sudo rm -f /etc/systemd/system/aiops-anomaly.service /etc/systemd/system/aiops-anomaly@.timer
sudo systemctl daemon-reload
sudo rm -f /usr/local/bin/aiops_watch.sh /usr/local/bin/ai_detect.py
sudo rm -rf /var/lib/aiops /var/log/aiops

Conclusion and next steps

You just built a pragmatic AIOps loop—entirely on a Linux box with Bash and a few Python wheels:

  • Collect lightweight signals.

  • Score anomalies with a small unsupervised model.

  • Alert fast; remediate safely after repeated failures.

Next steps:

  • Add more domain-specific features (e.g., 5xx rate, DB latency, queue depth).

  • Ship features and decisions to a timeseries DB or object storage for audits.

  • Integrate with Prometheus (node_exporter) and use this detector as an extra safety net.

  • Wrap this into a small Ansible role or container for fleet rollout.

Got five spare minutes? Tail your logs, add one custom metric, and watch your detector get smarter every minute.