סקירה כללית
במדריך הזה מוסבר איך לפרוס את Elastic Stack (Elasticsearch ו-Kibana) מקצה לקצה באשכול משותף של Google Distributed Cloud (GDC) עם air gap, ואיך לשלב אותו עם שירות של מודל שפה גדול (LLM) כדי להעריך את התכונות מבוססות-AI גנרטיבי של Elastic.
הפריסה משתמשת במפעיל הרשמי של Elastic Cloud on Kubernetes (ECK) שזמין ב-Marketplace של GDC עם air gap. האופרטור מותקן במרחב שמות של פרויקט באשכול Kubernetes משותף, והוא הופך את ההקצאה ואת ניהול מחזור החיים של משאבי Elasticsearch ו-Kibana לאוטומטיים. אחרי הפריסה, Kibana מתחבר לנקודת קצה (endpoint) של הסקת LLM (כמו Gemini מנוהל ב-GDC או שירות בניהול עצמי שתואם ל-OpenAI כמו Ollama או vLLM). השילוב הזה מפעיל את יכולות מבוססות-AI גנרטיבי של Elastic, כולל Elastic AI Agent, סוכנים בהתאמה אישית שנוצרו באמצעות Agent Builder ו-Attack Discovery.
המדריך הזה נכתב ל-Elastic v9.4.0, ל-Elastic Kubernetes Operator v3.4.0 ול-GDC v1.16.2. יכול להיות שחלק מההוראות יהיו שונות קצת אם אתם משתמשים בגרסאות אחרות.
ארכיטקטורה
הארכיטקטורה מורכבת מפריסת Elastic Stack שמנוהלת על ידי מפעיל ECK בתוך מרחב שמות של פרויקט באשכול משתמשים משותף של GDC. Kibana מתקשרת דרך הרשת הפנימית של GDC עם נקודת קצה (endpoint) להסקת מסקנות של LLM.

הרכיבים העיקריים בארכיטקטורה הזו:
- אשכול משותף ב-GDC: אשכול Kubernetes משותף ברמת הארגון שמקושר לכמה פרויקטים, ומארח חבילות תוכנה ועומסי עבודה של אפליקציות ב-GDC Marketplace.
- פרויקט GDC: מרחב השמות של הפרויקט באשכול המשותף שמבודד את מפעיל ה-ECK, אשכול Elasticsearch, מכונת Kibana, ו-Kubernetes Secrets ו-NetworkPolicies המשויכים.
- מפעיל Elastic Cloud on Kubernetes (ECK): מותקן מ-GDC Marketplace במרחב השמות של הפרויקט. המפעיל מנהל את המשאבים המותאמים אישית
Elasticsearchו-Kibana, ומתאים את רישיון Elastic שנדרש לתכונות מבוססות-AI גנרטיבי ולמחברים חיצוניים של LLM. - Elasticsearch: מאחסן נתונים תפעוליים ונתוני אבטחה ומוסיף אותם לאינדקס, ומריץ שאילתות חיפוש ושאילתות ES|QL שנוצרו על ידי סוכנים ותכונות מבוססות-AI של Kibana.
- Kibana: מארח את תכונות ה-AI הגנרטיבי של Elastic, כולל Elastic AI Agent (לחקירת יומנים בשפה טבעית וליצירת ES|QL), את Agent Builder (ליצירת סוכנים וכלים מותאמים אישית בממשק שיחה על סמך נתוני Elasticsearch) ואת Attack Discovery (למתאם של התראות אבטחה ולניתוח שרשרת התקפות). בנוסף, Kibana מנהל את מחברי הפעולות של ה-LLM, שמתאמים את קריאות ה-API לכלי בכמה שלבים בין המשתמש, Elasticsearch ושירות ה-LLM.
- שירות LLM: שירות LLM מנוהל (כמו Gemini ב-GDC) או שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שמספק ל-Kibana יכולות של השלמת צ'אט וקריאות API לכלי דרך הרשת הפנימית של GDC.
לפני שמתחילים
לפני שמתחילים את הפריסה, צריך לוודא שסביבת GDC עומדת בדרישות המוקדמות ובהנחות הבאות:
- אשכול Kubernetes משותף ופרויקט GDC: יש לכם גישה לאשכול משותף קיים ב-GDC ולפרויקט ב-GDC שמקושר לאשכול הזה. הפרויקט משמש כמכל של מרחב השמות לכל המשאבים ב-Kubernetes שנפרסו במדריך הזה. מוודאים שיש באשכול מספיק משאבי מחשוב ואחסון מתמיד כדי לארח את עומסי העבודה של מפעיל ה-ECK, של Elasticsearch ושל Kibana.
- שירות LLM: יש לכם גישה ברשת לשירות LLM, בין אם זה שירות Gemini מנוהל (אם הוא זמין בסביבת GDC שלכם) או שירות LLM בניהול עצמי שתואם ל-OpenAI (לדוגמה,
gemma4:31bשמוגש דרך Ollama או vLLM בפרויקט GPU). הפריסה של תשתית ה-LLM עצמה לא נכללת במדריך הזה, אבל הדרישות הספציפיות למודל ול-API מפורטות בקטע דרישות לשירות ה-LLM. מאחזרים את הגרסה של Elastic שמגיעה עם סביבת GDC:
export ELASTIC_VERSION=$(curl -sk "https://harbor.${ORG}.${ZONE}.${GDCH_DOMAIN}/api/v2.0/projects/library/repositories/elasticsearch%252Felasticsearch/artifacts" | jq -r '[.[].tags[]?.name] | sort_by(split(".") | map(tonumber)) | last')מגדירים משתני סביבה שישמשו כהפניות לאורך המדריך. משנים את הערכים האלה לפי הצורך כדי להתאים לסביבה הספציפית שלכם:
# GDC environment configuration export PROJECT_ID="your-project-id" export CLUSTER_NAME="your-shared-cluster-name" export ZONE="your-zone-name" export USER_NAME="your-gdc-user-email" # Marketplace package and Elastic Stack configuration export MARKETPLACE_SERVICE_NAME="elastic" # LLM service configuration export LLM_CONNECTOR_NAME="your-llm-connector-name" export LLM_API_URL="http://your-llm-service-host:11434/v1/chat/completions" export LLM_MODEL_NAME="your-llm-model-name" export LLM_API_KEY="your-llm-api-key"הגדרת
gdcloud:gdcloud config set project ${PROJECT_ID} gdcloud config set core/zone ${ZONE}מבקשים או מקשרים את תפקידי ה-IAM הנדרשים ברמת הפרויקט למשתמש בפרויקט היעד ב-GDC (
${PROJECT_ID}). בהתאם למסמכי התיעוד של מחזור החיים של GDC Marketplace ולדרישות הרשת של GDC, נדרשים לפחות התפקידים הבאים בפרויקט:- אדמין של מרחב שמות (
namespace-admin): הרשאת אדמין מלאה ליצירה ולניהול של עומסי עבודה, סודות, שירותים ומשאבים בהתאמה אישית של Kubernetes במרחב השמות של הפרויקט באשכול המשותף. - צפייה בפרויקט (
project-viewer): גישת קריאה כדי לצפות בפרויקט הקיים ובמשאבים שלו במסוף GDC וב-CLI. - עריכה ב-Marketplace (
marketplace-editor): הרשאה להתקין, לשדרג ולהסיר חבילות תוכנה של GDC Marketplace בפרויקט. - אדמין של Project NetworkPolicy (
project-networkpolicy-admin): הרשאה להגדיר כלליProjectNetworkPolicyגלובליים שמאפשרים לתנועה חוצת-פרויקטים ולתנועה חיצונית להגיע לשירותי Kibana ו-Elasticsearch LoadBalancer.
בקשו ממנהל חשבון ארגוני ב-IAM או מהאדמין של הפרויקט ב-IAM להקצות את התפקידים האלה (או הריצו את הפקודות הבאות אם יש לכם הרשאות אדמין ב-IAM):
gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=namespace-admin gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=project-viewer gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=marketplace-editor gdcloud projects add-iam-policy-binding ${PROJECT_ID} \ --member="user:${USER_NAME}" \ --role=project-networkpolicy-admin- אדמין של מרחב שמות (
מאחזרים את ה-kubeconfig של האשכול המשותף ומגדירים כינוי מעטפת של
kuבשבילkubectl:KUBECONFIG=kubeconfig-${CLUSTER_NAME}.yaml gdcloud clusters \ get-credentials ${CLUSTER_NAME} \ --zone ${ZONE} alias ku="kubectl --kubeconfig=kubeconfig-${CLUSTER_NAME}.yaml"
התקנת חבילת שטחי פרסום ייעודית לזירות מסחר של Elastic
בקטע הזה תתקינו את האופרטור Elastic Cloud on Kubernetes (ECK) במרחב השמות של הפרויקט מ-GDC air-gapped Marketplace, לפי המדריך למחזור החיים של GDC Marketplace.
התקנה באמצעות ה-CLI של gdcloud (או מסוף GDC)
מציגים את חבילות התוכנה הזמינות ב-Marketplace כדי לאשר את שם השירות ואת הגרסה הזמינה:
gdcloud marketplace services listהפלט אמור להיראות כך:
SERVICE LATEST VERSION confluent <CONFLUENT_VERSION> dataproc <DATAPROC_VERSION> elastic <ECK_OPERATOR_VERSION> mariadb <MARIADB_VERSION> mattermost <MATTERMOST_VERSION> mongodb <MONGODB_VERSION> neo4j <NEO4J_VERSION> redis <REDIS_VERSION> starboard <STARBOARD_VERSION> starburst <STARBURST_VERSION> tes <TES_VERSION> vault <VAULT_VERSION>מתקינים את חבילת שטחי פרסום ייעודית לזירות מסחר באשכול המשותף של היעד:
gdcloud marketplace services install ${MARKETPLACE_SERVICE_NAME} \ --cluster=${CLUSTER_NAME} \ --zone=${ZONE} \ --project ${PROJECT_ID}*(לחלופין, במסוף GDC, בוחרים את הפרויקט
${PROJECT_ID}, עוברים אל Marketplace, בוחרים את האפליקציה Elastic Cloud on Kubernetes, לוחצים על Install, בוחרים את האשכול${CLUSTER_NAME}ולוחצים על Install).*מוודאים שה-StatefulSet וה-pod של
elastic-operatorפועלים במרחב השמות של הפרויקט (${PROJECT_ID}):ku get statefulset,pods -n ${PROJECT_ID}הפלט אמור להיראות כך:
NAME READY AGE statefulset.apps/elastic-operator 1/1 3m NAME READY STATUS RESTARTS AGE pod/elastic-operator-0 1/1 Running 0 3m
פריסת Elastic Stack
יצירת המניפסט של Elastic Stack
החבילה של GDC Marketplace פורסת את מפעיל ה-ECK שמוגדר מראש לשליפת קובצי אימג' של קונטיינרים של Elasticsearch ו-Kibana מהמרשם המקומי של Harbor עם air gap.
יוצרים את elastic-stack.yaml כדי לפרוס אשכול Elasticsearch עם צומת אחד, מכונה של ממשק המשתמש של Kibana, שירותי LoadBalancer HTTP ו-allow-all NetworkPolicy במרחב השמות של הפרויקט (${PROJECT_ID}). המניפסט כולל גם רישיון Secret שתואם למפעיל ECK, שמוגדר כאן כגרסת ניסיון של 30 יום ל-Enterprise (eck-trial-license) למקרה שעדיין אין לכם רישיון Enterprise משלכם:
cat <<EOF > elastic-stack.yaml
apiVersion: v1
kind: Secret
metadata:
name: eck-trial-license
namespace: ${PROJECT_ID}
labels:
license.k8s.elastic.co/type: enterprise_trial
annotations:
elastic.co/eula: accepted
---
apiVersion: elasticsearch.k8s.elastic.co/v1
kind: Elasticsearch
metadata:
name: elastic-cluster
namespace: ${PROJECT_ID}
spec:
version: ${ELASTIC_VERSION}
http:
service:
spec:
type: LoadBalancer
nodeSets:
- name: default
count: 1
config:
# Disable mmap to avoid requiring privileged host sysctl modifications
node.store.allow_mmap: false
podTemplate:
spec:
initContainers:
- name: sysctl
securityContext:
privileged: false
allowPrivilegeEscalation: false
runAsNonRoot: true
capabilities:
drop:
- ALL
command: ["sh", "-c", "true"]
containers:
- name: elasticsearch
resources:
requests:
memory: 2Gi
cpu: 500m
limits:
memory: 4Gi
cpu: 2
volumeClaimTemplates:
- metadata:
name: elasticsearch-data
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi
---
apiVersion: kibana.k8s.elastic.co/v1
kind: Kibana
metadata:
name: kibana-ui
namespace: ${PROJECT_ID}
spec:
version: ${ELASTIC_VERSION}
count: 1
elasticsearchRef:
name: elastic-cluster
http:
service:
spec:
type: LoadBalancer
podTemplate:
spec:
containers:
- name: kibana
resources:
requests:
memory: 1Gi
cpu: 200m
limits:
memory: 2Gi
cpu: 1
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-all
namespace: ${PROJECT_ID}
spec:
podSelector: {}
policyTypes:
- Ingress
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
EOF
החלה ואימות של הפריסה
מחילים את המניפסט:
ku apply -f elastic-stack.yaml
מחילים ProjectNetworkPolicy גלובלי כדי שכללי חומת האש של GDC יאפשרו תנועה נכנסת לכתובות ה-IP של LoadBalancer וגם תנועה בין פרויקטים (כמו תקשורת עם שירות ה-LLM):
kubectl --kubeconfig=kubeconfig-global.yaml apply -f - <<EOF
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
namespace: ${PROJECT_ID}
name: allow-inbound-traffic-from-external
spec:
policyType: Ingress
subject:
subjectType: UserWorkload
ingress:
- from:
- ipBlock:
cidr: 0.0.0.0/0
---
apiVersion: networking.global.gdc.goog/v1
kind: ProjectNetworkPolicy
metadata:
namespace: ${PROJECT_ID}
name: allow-inbound-traffic-from-all-projects
spec:
policyType: Ingress
subject:
subjectType: UserWorkload
ingress:
- from:
- projects: {}
EOF
עוקבים אחרי הפודים, השירותים והמשאבים בהתאמה אישית עד ש-Elasticsearch ו-Kibana מגיעים לסטטוס Running ו-Ready ומקבלים כתובות IP של LoadBalancer ב-${CLUSTER_NAME}:
ku get pods,svc,elasticsearch,kibana -n ${PROJECT_ID}
כשהמשאבים מוכנים, הפלט אמור להיראות כך:
NAME READY STATUS RESTARTS AGE
pod/elastic-cluster-es-default-0 1/1 Running 0 3m
pod/elastic-operator-0 1/1 Running 0 15m
pod/kibana-ui-kb-869844478d-59ptw 1/1 Running 0 3m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/elastic-cluster-es-default ClusterIP None <none> 9200/TCP 11m
service/elastic-cluster-es-http LoadBalancer 10.101.18.181 <ELASTIC_EXTERNAL_IP> 9200:32159/TCP 11m
service/elastic-cluster-es-internal-http ClusterIP 10.101.18.81 <none> 9200/TCP 11m
service/elastic-cluster-es-transport ClusterIP None <none> 9300/TCP 11m
service/g-svc-g-fre-elastic-cluster-es-http-shared-clus-extern-56d5eb67 ClusterIP None <none> 9200/TCP 11m
service/g-svc-g-fre-kibana-ui-kb-http-shared-cluster-0b-extern-75a78b1f ClusterIP None <none> 5601/TCP 11m
service/kibana-ui-kb-http LoadBalancer 10.101.19.86 <KIBANA_EXTERNAL_IP> 5601:32735/TCP 11m
NAME HEALTH NODES VERSION PHASE AGE
elasticsearch.elasticsearch.k8s.elastic.co/elastic-cluster green 1 <ELASTIC_VERSION> Ready 3m
NAME HEALTH NODES VERSION AGE
kibana.kibana.k8s.elastic.co/kibana-ui green 1 <ELASTIC_VERSION> 3m
מייצאים את כתובות ה-IP החיצוניות של LoadBalancer שהוקצו ל-Elasticsearch ול-Kibana, כדי להשתמש בהן בשלבים הבאים:
export ELASTICSEARCH_IP=$(ku get svc elastic-cluster-es-http \
-n ${PROJECT_ID} \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
export KIBANA_IP=$(ku get svc kibana-ui-kb-http \
-n ${PROJECT_ID} \
-o jsonpath='{.status.loadBalancer.ingress[0].ip}')
echo "Kibana External URL: https://${KIBANA_IP}:5601"
echo "Elasticsearch External URL: https://${ELASTICSEARCH_IP}:9200"
אחזור פרטי הכניסה וגישה לממשק המשתמש של Kibana
אחזור הסיסמה של משתמש-העל ב-elastic
ECK יוצר באופן אוטומטי סוד של Kubernetes (elastic-cluster-es-elastic-user) שמכיל את סיסמת ברירת המחדל של משתמש-העל elastic. אפשר לאחזר ולפענח אותו באמצעות:
export ELASTIC_PASSWORD=$(ku get secret elastic-cluster-es-elastic-user \
-n ${PROJECT_ID} \
-o jsonpath='{.data.elastic}' | base64 -d)
echo "${ELASTIC_PASSWORD}"
אימות החיבור והתחברות
מוודאים שאפשר לגשת ל-Elasticsearch דרך HTTPS מתחנת העבודה:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" "https://${ELASTICSEARCH_IP}:9200"פותחים את
https://${KIBANA_IP}:5601בדפדפן, מאשרים את האזהרה לגבי האישור בחתימה עצמית ונכנסים באמצעות:- שם משתמש:
elastic - סיסמה:
<output from ELASTIC_PASSWORD above>
- שם משתמש:
אימות הרישיון של Elastic
כדי להשתמש בתכונות מבוססות-AI גנרטיבי של Elastic (כולל סוכן AI של Elastic, Agent Builder ו-Attack Discovery) ובמחברים חיצוניים של LLM, צריך רישיון Enterprise (או רישיון Enterprise פעיל ל-30 ימי ניסיון).
כשאשכול Elasticsearch מנוהל על ידי מפעיל ה-ECK, ה-license-controller של ECK מתאים באופן רציף את רישיון האשכול ל-Kubernetes Secrets במרחב השמות של המפעיל (${PROJECT_ID}). אם עדיין אין לכם רישיון Enterprise לסביבת ייצור, ה-Secret של eck-trial-license שכלול ב-elastic-stack.yaml (license.k8s.elastic.co/type:
enterprise_trial) מורה ל-ECK להקצות ולתחזק באופן אוטומטי רישיון Enterprise לניסיון למשך 30 יום. (שימו לב שאם תתחילו תקופת ניסיון באופן ידני דרך POST
/_license/start_trial בלי סוד רישיון ECK, ECK יחזיר את האשכול למצב basic במחזור ההתאמה הבא).
כדי לאמת את הרישיון הפעיל באמצעות ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${ELASTICSEARCH_IP}:9200/_license" | jq .
*(לחלופין, ב-Kibana, פותחים את תפריט הניווט ועוברים אל Management > Dev Tools, ואז מריצים את GET /_license בכרטיסייה Console).*
הפלט אמור להיראות כך, עם "status": "active" ו-"type": "enterprise" (או "type": "trial" אם משתמשים ברישיון לניסיון):
{
"license": {
"status": "active",
"uid": "d8c2b566-8c2b-436c-a1ea-5502d6790505",
"type": "trial",
"issue_date": "2026-09-17T23:19:25.053Z",
"issue_date_in_millis": 1789687165053,
"expiry_date": "2026-10-17T23:19:25.053Z",
"expiry_date_in_millis": 1792279165053,
"max_nodes": 1000,
"max_resource_units": null,
"issued_to": "elastic-cluster",
"issuer": "elasticsearch",
"start_date_in_millis": -1
}
}
טעינת מערך נתונים מובנה לדוגמה
כדי להעריך את תכונות ה-AI של Elastic בלי לכתוב צינורות עיבוד נתונים מותאמים אישית להטמעת נתונים או לדרוש גישה לאינטרנט החיצוני, אפשר לטעון את מערך הנתונים המובנה של Kibana, Sample web logs (kibana_sample_data_logs). מכיוון שארכיון הנתונים לדוגמה כלול בקובץ האימג' של קונטיינר Kibana, הוא מותקן תוך שניות בסביבה עם air gap. קבוצת הנתונים מאכלסת 14,074 מסמכים של יומני גישה לאינטרנט שמכילים את כתובות ה-IP של הלקוחות (clientip), קודי סטטוס של HTTP (response), מדינות המקור והיעד (geo.src, geo.dest), בייטים שהועברו (bytes), כתובות URL ומחרוזות של סוכני משתמשים.
במאמרים Add sample data in Kibana ו-elastic-datasets-builtin.md מפורטים מערכי נתונים מובְנים נוספים (flights ו-ecommerce).
טעינת מערך הנתונים
מריצים את הפקודה הבאה כדי לטעון את kibana_sample_data_logs דרך נקודת הקצה הפנימית של נתונים לדוגמה של Kibana:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
-X POST "https://${KIBANA_IP}:5601/api/sample_data/logs" \
-H "kbn-xsrf: true" \
-H "x-elastic-internal-origin: Kibana"
אימות מערך הנתונים לדוגמה
מוודאים שקבוצת הנתונים לדוגמה logs מסומנת כ-installed ב-Kibana, ומוודאים שזרם הנתונים התומך מכיל מסמכי 14,074 ב-Elasticsearch:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${KIBANA_IP}:5601/api/sample_data" \
-H "kbn-xsrf: true" \
-H "x-elastic-internal-origin: Kibana" \
| jq '.[] | select(.id == "logs") | {id, name, status}'
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${ELASTICSEARCH_IP}:9200/_cat/indices/kibana_sample_data_logs*?v"
הפלט אמור להיראות כך:
{
"id": "logs",
"name": "Sample web logs",
"status": "installed"
}
health status index uuid pri rep docs.count docs.deleted store.size pri.store.size dataset.size
yellow open .ds-kibana_sample_data_logs-2026.09.22-000001 0YivG3oFQQmb-mYqmULr_g 1 1 14074 0 8.7mb 8.7mb 8.7mb
דרישות לגבי שירות LLM
בסביבת GDC עם air gap, אי אפשר להגיע למודלים מסוג LLM שמנוהלים על ידי Elastic ומארחים בענן, ולספקי SaaS LLM ציבוריים חיצוניים. בהתאם לסביבת GDC:
- Gemini מנוהל ב-GDC: בסביבות GDC שמציעות את Gemini כשירות LLM מנוהל, אתם יכולים לחבר את Kibana ישירות לשירות Gemini המנוהל, אם הוא זמין.
- שירות LLM בניהול עצמי: לחלופין, אתם יכולים לחבר את Elastic לשירות LLM בניהול עצמי שמתארח בסביבת GDC (לדוגמה, בצומתי GPU של GDC).
המדריך הזה לא עוסק בפריסה של שירות LLM בניהול עצמי. כשמקצים או בוחרים שירות LLM בניהול עצמי ל-Elastic, צריך לוודא שהוא עומד בדרישות שנגזרות מהמדריכים הרשמיים של Elastic:
- הגדרת הגישה למודלי LLM
- מודלים מותאמים אישית של LLM בניהול עצמי (ראו גם התחברות ל-vLLM בסביבות עם air gap)
- ניטור של LLM ו-AI אקטיבי
סיכום הדרישות הטכניות
הנקודות הבאות מסכמות את הדרישות העיקריות של ה-API, יכולות המודל, חלון ההקשר וזמן הריצה שנדרשות כדי ששירות LLM בניהול עצמי יפעל בצורה מהימנה עם סוכן AI, Agent Builder ו-Attack Discovery של Elastic. שימו לב שהשמות של פרמטרים ספציפיים להגדרה שמוזכרים בקטעים הבאים (כמו num_ctx, num_predict, repeat_penalty, repeat_last_n, OLLAMA_FLASH_ATTENTION ו-OLLAMA_NUM_PARALLEL) מופיעים בדוגמה של Ollama. אם אתם משתמשים במנוע הסקה אחר כמו vLLM, צריך להחיל את ההגדרות המקבילות.
- API להשלמת צ'אט שתואם ל-OpenAI (
/v1/chat/completions) עם קריאה להפעלת פונקציות מותאמות:- כל שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שחושף נקודת קצה (endpoint) של
/v1/chat/completionsשתואמת ל-OpenAI (כולל סטרימינג של אירועים שנשלחו מהשרת דרך"stream": true) יכול להשתלב באמצעות מחבר OpenAI של Kibana, כשהספק מוגדר כ-Other (OpenAI Compatible Service). - מפעילים קריאה להפעלת פונקציות מבוססות-ענן (
"enableNativeFunctionCalling": true) במחבר של Kibana כשמשתמשים במודלים שתומכים בקריאת API לכלי מבוססי-ענן (כמוgemma4:31b). כברירת מחדל ("enableNativeFunctionCalling": false), Kibana משתמש בקריאה לפונקציות מדומות שמבוססות על פרומפטים, שהיא פחות אמינה במהלך בקשות להפעלת פונקציה מקוננות ויכולה להיכשל עם שגיאות ניתוח (כמוError: Missing name for tool use). הפעלת קריאה להפעלת פונקציות מבוססות-ענן מעבירה את עיצוב קריאת API לכלי ישירות לפרוטוקול OpenAItoolsשל שרת ההיקש.
- כל שרת הסקה בניהול עצמי (כמו Ollama או vLLM) שחושף נקודת קצה (endpoint) של
- מודל שעבר כוונון לפי הוראות, עם יכולות מובנות של כלים וקריאה להפעלת פונקציות:
- Elastic AI Agent (
elastic-ai-agent) וסוכנים בהתאמה אישית של Agent Builder מסתמכים על קריאות API לכלי בכמה שלבים (platform.core.list_indices,platform.core.get_index_mapping,platform.core.search,platform.core.execute_esqlו-platform.core.cases). הם משתמשים בכלים האלה כדי לגלות מיפויים של אינדקסים, ליצור ולהריץ שאילתות ES|QL ב-Elasticsearch ולסנתז ממצאים. - המודל שנבחר צריך להיות מכוונן לפי הוראות ומסוגל ליצור קריאות תקינות לכלים מבוססי-ענן, פלטים מובְנים של JSON ושאילתות ES|QL. כדי לבדוק את הקריטריונים להערכת המודל, עיינו במאמרים של Elastic בנושא מטריצת הביצועים של LLM ל-Observability ומטריצת הביצועים של LLM ל-Elastic Security.
- Elastic AI Agent (
- חלון הקשר של לפחות
128,000טוקנים (לדוגמה,num_ctx: 131072ב-Ollama):- במסמכי התיעוד הכלליים של Elastic בנושא מודל LLM מותאם אישית בניהול עצמי מצוין מינימום של 64,000 טוקנים, אבל מומלץ מאוד להשתמש בחלון ההקשר של 128,000 טוקנים (
131,072טוקנים) בשביל Elastic סוכן AI (elastic-ai-agent). - במהלך חקירות בכמה שלבים, הנציג משלב הוראות מערכת והגדרות של כלים עם סכימות של אינדקסים, מסמכי תיעוד של ES|QL ותוצאות חיפוש גולמיות (לדוגמה, אחזור של עד 100 מסמכים לדוגמה כדי לבדוק את ערכי השדות לפני הצבירה). בחקירה אחת, היסטוריית השיחה יכולה לחרוג מ-70,000 טוקנים, מה שגורם לחלון הקשר של 64,000 טוקנים לחתוך את ההנחיה באמצע השיחה.
- חשוב לשים לב שב-Ollama, הוראת
PARAMETER num_ctxשמוטמעת ב-Modelfileשל המודל מבטלת את משתנה הסביבהOLLAMA_CONTEXT_LENGTHשל הקונטיינר. מוודאים שגםOLLAMA_CONTEXT_LENGTH=131072וגםPARAMETER num_ctx 131072מוגדרים ב-Modelfileשל המודל (ב-GPU של NVIDIA A100 או H100 בנפח 80GB,gemma4:31bבהקשר של128Kמתאים ל-VRAM).
- במסמכי התיעוד הכלליים של Elastic בנושא מודל LLM מותאם אישית בניהול עצמי מצוין מינימום של 64,000 טוקנים, אבל מומלץ מאוד להשתמש בחלון ההקשר של 128,000 טוקנים (
- מגבלת טוקנים לפלט וקנס קל על חזרה על מילים (לדוגמה,
num_predict: 8192ו-repeat_penalty: 1.05ב-Ollama):- מתאם הסטרימינג של OpenAI ב-Kibana מתייחס ל-
"finish_reason": "length"בתור שגיאתToken limit reachedחמורה ושולח"temperature": 0(פענוח חמדני) בכל בקשה. - במודלים עם יכולת הסקת מסקנות (כמו
gemma4:31b, שיוצר אסימוני שלבים בתהליך החשיבה פנימיים לפני שהוא מפעיל קריאות API לכלי), צריך להגדיר מגבלת אסימונים של לפחות8,192אסימונים וקנס קל על חזרה על מילים. לדוגמה, ב-Ollama, מגדירים אתnum_predict: 8192,repeat_penalty: 1.05ו-repeat_last_n: 64. חשוב להימנע מקנסות אגרסיביים מדי על חזרה על מילים (כמו1.15+בחלונות גדולים), כי הם פוגעים במודל כשהוא מעתיק שאילתת ES|QL מבלוק החשיבה הפנימי שלו ל-JSON הסופי של קריאת ה-API לכלי.
- מתאם הסטרימינג של OpenAI ב-Kibana מתייחס ל-
- האצת GPU, Flash Attention ומשבצות מקבילות של מטמון KV (פחות מ-60 שניות לכל תור):
- המחבר של OpenAI ב-Kibana אוכף זמן קצוב לתפוגה של HTTP כברירת מחדל של 60 שניות לכל תור בסטרימינג.
- בגלל שפנייה אחת לנציג יכולה להפעיל קריאות משנה מקוננות ל-LLM (לדוגמה, כדי לתרגם בקשות חיפוש בשפה טבעית לשאילתות ES|QL בזמן שהשיחה הראשית עם הנציג פעילה), הפעלת שרת הסקה עם משבצת אחת בלבד של מטמון KV יכולה לגרום לקריאות המשנה למחוק את ההקשר שנשמר במטמון של השיחה הראשית ולהאט את הפניות הבאות.
- ב-Ollama, מגדירים את
OLLAMA_FLASH_ATTENTION=1ו-OLLAMA_NUM_PARALLEL=2כך ש-Flash Attention יאיץ את הערכת ההקשר הארוך, והשיחה הראשית והקריאות המשניות המקוננות ישמרו על משבצות נפרדות של מטמון KV בזיכרון ה-GPU.
הגדרת המחבר של ה-LLM ב-Kibana
אחרי ששירות ה-LLM שתואם ל-OpenAI פועל ואפשר להגיע אליו מה-Pod של Kibana, מגדירים מחבר OpenAI ב-Kibana באמצעות ה-CLI או ממשק המשתמש של Kibana.
אפשרות 1: יצירת המחבר באמצעות ה-CLI
מריצים את הפקודה הבאה של
POST /api/actions/connectorכדי ליצור מחבר OpenAI (.gen-ai) שמפנה לשירות ה-LLM (${LLM_API_URL}) עם קריאה להפעלת פונקציות מקורית ("enableNativeFunctionCalling": true). Kibana יוצר ומקצה באופן אוטומטי מזהה מחבר ייחודי:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/actions/connector" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"name\": \"${LLM_CONNECTOR_NAME}\", \"connector_type_id\": \".gen-ai\", \"config\": { \"apiProvider\": \"Other\", \"apiUrl\": \"${LLM_API_URL}\", \"defaultModel\": \"${LLM_MODEL_NAME}\", \"enableNativeFunctionCalling\": true }, \"secrets\": { \"apiKey\": \"${LLM_API_KEY}\" } }" | jq .כדי לוודא שהמחבר
${LLM_CONNECTOR_NAME}קיים ואין בו סודות חסרים, מציגים את המחברים של Kibana ומסננים לפי שם:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/actions/connectors" \ -H "kbn-xsrf: true" \ | jq ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\")"הפלט אמור להיראות כך:
{ "id": "<GENERATED_CONNECTOR_ID>", "name": "<LLM_CONNECTOR_NAME>", "config": { "apiProvider": "Other", "apiUrl": "<LLM_API_URL>", "defaultModel": "<LLM_MODEL_NAME>", "enableNativeFunctionCalling": true }, "connector_type_id": ".gen-ai", "is_preconfigured": false, "is_deprecated": false, "referenced_by_count": 0, "is_missing_secrets": false, "is_system_action": false }כדי לבדוק את הקישוריות מקצה לקצה בין Kibana לבין שירות ה-LLM מה-CLI, חפשו את מזהה המחבר שנוצר אוטומטית לפי השם שלו והפעילו את נקודת הקצה (endpoint) של
_execute:export CONNECTOR_ID=$(curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/actions/connectors" \ -H "kbn-xsrf: true" \ | jq -r ".[] | select(.name == \"${LLM_CONNECTOR_NAME}\") | .id") curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/actions/connector/${CONNECTOR_ID}/_execute" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"params\": { \"subAction\": \"invokeAI\", \"subActionParams\": { \"model\": \"${LLM_MODEL_NAME}\", \"messages\": [ { \"role\": \"user\", \"content\": \"Reply with the single word Pong.\" } ] } } }" | jq .הפלט אמור להיראות כך, עם
"status": "ok"והתשובה של המודל:{ "status": "ok", "data": { "message": "Pong", "usage": { "prompt_tokens": 23, "prompt_tokens_details": { "cached_tokens": 0 }, "completion_tokens": 38, "total_tokens": 61 } }, "connector_id": "<GENERATED_CONNECTOR_ID>" }
אפשרות 2: יצירת המחבר דרך ממשק המשתמש של Kibana
- נכנסים ל-Kibana (
https://${KIBANA_IP}:5601). - פותחים את תפריט הניווט הראשי ועוברים אל Management > Alerts and Insights > Connectors.
- לוחצים על Create connector ובוחרים באפשרות OpenAI.
- מגדירים את השדות של המחבר:
- Connector name: מזינים את הערך של
${LLM_CONNECTOR_NAME}(לדוגמה,Gemma4-31B). - Select an OpenAI provider: בוחרים באפשרות Other (OpenAI Compatible Service).
- כתובת URL: מזינים את הערך של
${LLM_API_URL}(נקודת הקצה של/v1/chat/completionsבשירות ה-LLM). - מודל ברירת המחדל: מזינים את הערך של
${LLM_MODEL_NAME}(לדוגמה,gemma4:31b). - מפתח API: מזינים את הערך של
${LLM_API_KEY}(כל מחרוזת לא ריקה אם השרת לא אוכף אימות של טוקן למוכ"ז, או את מפתח ה-API של השרת). - הפעלת קריאה להפעלת פונקציות מקוריות: מפעילים את המתג.
- Connector name: מזינים את הערך של
- לוחצים על Save & test כדי לשמור את המחבר ולפתוח מיד את הכרטיסייה Test.
- בכרטיסייה Test, מזינים הנחיה לדוגמה (למשל,
Reply with the single word Pong.) ולוחצים על Run כדי לוודא ש-Kibana מקבלת תשובה תקינה משירות ה-LLM.
בדיקת השילובים של מודל ה-LLM
אחרי שמתקינים את קבוצת הנתונים kibana_sample_data_logs ומאמתים את מחבר ה-LLM, אפשר לבדוק את יכולות ה-AI של Elastic ב-Kibana.
Elastic AI Agent
Elastic AI Agent (elastic-ai-agent) הוא סוכן ברירת המחדל בממשק שיחה שמשולב ב-Kibana, שמאפשר לחקור ולנתח נתונים ב-Elasticsearch באמצעות שפה טבעית.
הנציג מופעל באמצעות מחבר ה-LLM שהגדרתם וכלים מובנים של Agent Builder, והוא מגלה באופן אוטומטי אינדקסים ומיפויים של Elasticsearch (platform.core.list_indices, platform.core.get_index_mapping), יוצר ומריץ שאילתות ES|QL (platform.core.search, platform.core.execute_esql) ומסנתז ממצאים בלי שתצטרכו להכיר את הסכימה הבסיסית מראש. אתם יכולים גם ליצור סוכנים בהתאמה אישית עם הוראות וכלים מותאמים. פרטים נוספים זמינים במאמר סוכנים בהתאמה אישית באמצעות Agent Builder.
כדי לפתוח ולהגדיר את Elastic AI Agent בממשק המשתמש של Kibana:
- פותחים את תפריט הניווט הראשי ב-Kibana ובוחרים באפשרות סוכן AI (או לוחצים על הכפתור Ask AI בסרגל הכותרת העליון, או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/chat). - מוודאים ש-Elastic AI Agent נבחר כסוכן AI הפעיל, ושהמחבר של ה-LLM (
${LLM_CONNECTOR_NAME}) נבחר כמחבר המודל.
דוגמה 1: חקירת בקשות HTTP שנכשלו וסורקים פוטנציאליים
מבקשים מסוכן ה-AI של Elastic לזהות את כתובות ה-IP המובילות של הלקוחות שיוצרות תגובות HTTP שאינן 200 ב-kibana_sample_data_logs, את מדינות המקור שלהן, ואם לאחת מכתובות ה-IP האלה הייתה גם תנועת HTTP 200 מוצלחת:
אפשרות 1: דרך ממשק המשתמש של Kibana: מזינים את השאלה הבאה בהנחיה לצ'אט של סוכן ה-AI:
In kibana_sample_data_logs, which client IP addresses had the most failed HTTP requests (non-200 status codes), what countries did they come from, and did any of those IPs also have successful HTTP 200 requests?אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"elastic-ai-agent\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"In kibana_sample_data_logs, which client IP addresses had the most failed HTTP requests (non-200 status codes), what countries did they come from, and did any of those IPs also have successful HTTP 200 requests?\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | {tool_id: .tool_id, params: .params}], response: .response.message }'
מה קורה מאחורי הקלעים:
- הנציג קורא ל-
platform.core.get_index_mapping(indices=["kibana_sample_data_logs"])כדי לבדוק את השדות הזמינים (clientip,geo.src,response,bytes,requestוכו'). הוא קורא ל-
platform.core.searchכדי לבדוק מסמכים לדוגמה ולוודא שקודי הסטטוס של HTTP מאוחסנים ב-response, ואז מפעיל צבירות של ES|QL כדי לדרג בקשות שאינן200לפיclientipו-geo.srcולבדוק אילו מכתובות ה-IP המובילות תיעדו גם תנועה שלresponse == "200":FROM kibana_sample_data_logs | WHERE response != "200" | STATS failed_count = COUNT(*) BY clientip, geo.src | SORT failed_count DESC | LIMIT 10FROM kibana_sample_data_logs | WHERE clientip IN ("30.156.16.164", "213.235.30.99", "221.241.228.46", "209.94.234.133", "7.127.73.138", "247.240.202.244", "130.125.65.24", "164.85.94.243", "81.194.200.150", "30.145.143.210") AND response == "200" | STATS success_count = COUNT(*) BY clientip
התשובה של Elastic AI Agent אמורה להיראות כך:
כתובת ה-IP של הלקוח עם הכי הרבה בקשות HTTP שנכשלו (קודי סטטוס שאינם 200) ב-
kibana_sample_data_logsהיא 30.156.16.164, שמקורה בארצות הברית, ובה נרשמו 100 בקשות שנכשלו.כתובות IP מובילות אחרות עם בקשות שנכשלו, שכולן הגיעו גם מארצות הברית:
- 213.235.30.99: 6 כשלים
- 221.241.228.46: 6 כשלים
- 209.94.234.133: 6 כשלים
- thought 7.127.73.138: 5 כשלים
- 247.240.202.244: 5 כשלים
- 130.125.65.24: 5 כשלים
- 164.85.94.243: 4 כשלים
- 81.194.200.150: 4 כשלים
- 30.145.143.210: 4 כשלים
לגבי בקשות שהצליחו, כל כתובות ה-IP שרשומות למעלה קיבלו לפחות כמה תגובות HTTP 200 מוצלחות, חוץ מ-30.156.16.164, שלא קיבלה בקשות מוצלחות.
דוגמה 2: ניתוח נפח התנועה והבייטים של המטען הייעודי (payload) לפי קוד סטטוס של HTTP
מבקשים מסוכן ה-AI של Elastic פירוט של קודי הסטטוס של תגובות ה-HTTP בכל הבקשות ב-kibana_sample_data_logs, ואת מספר הבייטים הכולל שהועברו לכל קוד סטטוס:
אפשרות 1: דרך ממשק המשתמש של Kibana: מתחילים שיחה חדשה ב-סוכן AI ומזינים את השאלה הבאה:
In kibana_sample_data_logs, what is the breakdown of HTTP response status codes across all requests, and how much total data (in bytes) was transferred for each status code?אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"elastic-ai-agent\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"In kibana_sample_data_logs, what is the breakdown of HTTP response status codes across all requests, and how much total data (in bytes) was transferred for each status code?\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | {tool_id: .tool_id, params: .params}], response: .response.message }'
מה קורה מאחורי הקלעים:
- הנציג בודק את הסכימה של
kibana_sample_data_logsבאמצעותplatform.core.get_index_mapping, ומזהה את השדותresponse.keywordו-bytes. הוא מפעיל את
platform.core.search, שיוצר ומריץ את הצבירה הבאה של ES|QL:FROM kibana_sample_data_logs | STATS request_count = COUNT(*), total_bytes = SUM(bytes) BY response.keyword | LIMIT 100
התשובה של Elastic AI Agent אמורה להיראות כך:
פירוט קודי הסטטוס של תגובות HTTP וסך הנתונים שהועברו ב-
kibana_sample_data_logs:
| קוד סטטוס | מספר הבקשות | סה"כ נתונים שהועברו (בייטים) |
|---|---|---|
| 200 | 12,832 | 75,681,246 |
| 404 | 801 | 4,044,443 |
| 503 | 441 | 0 |
סוכנים בהתאמה אישית באמצעות Agent Builder
ב-Agent Builder אתם יכולים ליצור סוכני AI בהתאמה אישית לתהליכי עבודה תפעוליים ספציפיים. כדי לעשות את זה, אתם יכולים לשלב הוראות מערכת בהתאמה אישית עם כלים מובנים (כמו platform.core.cases, platform.core.execute_esql ו-platform.core.search) וכלים שמוגדרים על ידי המשתמש (esql, index_search, workflow ו-mcp).
בדוגמה הבאה אנחנו יוצרים סוכן Web Security Incident Responder שפועל ישירות על מערך הנתונים kibana_sample_data_logs. כשנותנים לסוכן הנחיה בשפה טבעית, הוא:
- מריץ כלי ES|QL מותאם אישית (
investigate_suspicious_web_clients) כדי לצבור תגובות HTTP שאינן200ב-kibana_sample_data_logsלפי כתובת ה-IP של הלקוח. - מבחין בין פעילות זדונית של איסוף מידע וניצול לרעה (
30.156.16.164) לבין כשלים תקינים בהורדה. - מפעיל כלי מותאם אישית של Kibana Workflow (
create_security_case) כדי לפתוח באופן אוטומטי אירוע אבטחה מובנה ב-Kibana עם הממצאים ושלבי התיקון.
ליצור כלים מותאמים אישית של ES|QL וליצירת פניות
יוצרים כלי
esqlבהתאמה אישית בשםinvestigate_suspicious_web_clientsששולח שאילתות ל-kibana_sample_data_logsכדי לקבל את כתובות ה-IP של הלקוחות המובילים שיוצרים קודי סטטוס HTTP שאינם200, יחד עם קודי הסטטוס שלהם, מדינת המקור (geo.src), סוכני המשתמש (agent) וכתובות ה-URL המבוקשות (request):אפשרות 1: דרך ממשק המשתמש של Kibana:
- ב-Kibana, פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents, ואז לוחצים על Manage all tools (או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/agent_builder/tools) ולוחצים על New tool. מגדירים את שדות הכלי:
- מזהה הכלי:
investigate_suspicious_web_clients - סוג: ES|QL
- תיאור:
Finds the top client IP addresses generating non-200 HTTP error responses in kibana_sample_data_logs, returning their error count, status codes, source country, user agents, and requested URL paths. - תוויות:
security,logs שאילתת ES|QL:
FROM kibana_sample_data_logs | WHERE response != "200" | STATS error_count = COUNT(*), status_codes = VALUES(response), country = VALUES(geo.src), user_agents = VALUES(agent), sample_requests = VALUES(request) BY clientip | SORT error_count DESC | LIMIT 5
- מזהה הכלי:
לוחצים על Save.
- ב-Kibana, פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents, ואז לוחצים על Manage all tools (או עוברים ישירות אל
אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/tools" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "investigate_suspicious_web_clients", "type": "esql", "description": "Finds the top client IP addresses generating non-200 HTTP error responses in kibana_sample_data_logs, returning their error count, status codes, source country, user agents, and requested URL paths.", "tags": ["security", "logs"], "configuration": { "query": "FROM kibana_sample_data_logs | WHERE response != \"200\" | STATS error_count = COUNT(*), status_codes = VALUES(response), country = VALUES(geo.src), user_agents = VALUES(agent), sample_requests = VALUES(request) BY clientip | SORT error_count DESC | LIMIT 5", "params": {} } }' | jq '{id: .id, type: .type}'
הכלי המובנה
platform.core.casesשל Agent Builder הוא לקריאה בלבד (משמש לחיפוש ולאחזור של פניות קיימות), לכן צריך ליצור תהליך עבודה ב-Kibana (create-security-case) שמפעיל את השלב המקוריcases.createCaseשל Kibana, ולרשום אותו ככליworkflowבשםcreate_security_case:אפשרות 1: דרך ממשק המשתמש של Kibana:
- פותחים את תפריט הניווט הראשי ועוברים אל Management > Workflows (או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/workflows), ואז לוחצים על Create a new workflow. בעורך ה-YAML, מדביקים את הגדרת תהליך העבודה הבאה ולוחצים על Save:
version: "1" name: Create Security Case description: Creates a new Kibana Case in Elastic Security enabled: true triggers: - type: manual inputs: - name: title type: string required: true description: Title of the security case - name: description type: string required: true description: Detailed markdown description of the incident findings - name: severity type: string default: high description: Severity of the case (low, medium, high, critical) steps: - name: create_case type: cases.createCase with: title: ${{ inputs.title }} description: ${{ inputs.description }} severity: ${{ inputs.severity }} owner: securitySolution tags: - agent-builder - web-logsחוזרים לדף Tools ב-Agent Builder (
https://${KIBANA_IP}:5601/app/agent_builder/tools, או עוברים אל Elasticsearch > Agents ולוחצים על Manage all tools) ולוחצים על New tool.מגדירים את השדות של כלי תהליך העבודה ולוחצים על שמירה:
- מזהה הכלי:
create_security_case - סוג: תהליך עבודה
- תהליך עבודה: בוחרים באפשרות יצירת בקשת תמיכה בנושא אבטחה (
create-security-case) - תיאור:
Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical). - תוויות:
security,cases
- מזהה הכלי:
- פותחים את תפריט הניווט הראשי ועוברים אל Management > Workflows (או עוברים ישירות אל
אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/workflows" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "workflows": [ { "yaml": "version: \"1\"\nname: Create Security Case\ndescription: Creates a new Kibana Case in Elastic Security\nenabled: true\ntriggers:\n - type: manual\ninputs:\n - name: title\n type: string\n required: true\n description: Title of the security case\n - name: description\n type: string\n required: true\n description: Detailed markdown description of the incident findings\n - name: severity\n type: string\n default: high\n description: Severity of the case (low, medium, high, critical)\nsteps:\n - name: create_case\n type: cases.createCase\n with:\n title: ${{ inputs.title }}\n description: ${{ inputs.description }}\n severity: ${{ inputs.severity }}\n owner: securitySolution\n tags:\n - agent-builder\n - web-logs\n" } ] }' | jq '.created[] | {id: .id, valid: .valid}' curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/tools" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "create_security_case", "type": "workflow", "description": "Creates a new Kibana Case in Elastic Security with a title, markdown description of the incident findings, and severity (low, medium, high, critical).", "tags": ["security", "cases"], "configuration": { "workflow_id": "create-security-case" } }' | jq '{id: .id, type: .type}'
יצירת סוכן בהתאמה אישית לטיפול באירועים
יוצרים את הסוכן web-security-incident-responder ומקצים לו את הכלים investigate_suspicious_web_clients, create_security_case ו-platform.core.cases:
אפשרות 1: דרך ממשק המשתמש של Kibana:
- פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/agent_builder/agents) ולוחצים על New agent. מגדירים את פרטי הסוכן:
- מזהה הנציג:
web-security-incident-responder - שם לתצוגה:
Web Security Incident Responder - תיאור המסך:
Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings. - תוויות:
security,incident-response הוראות מותאמות אישית:
You are an automated Web Security Incident Responder. When asked to investigate suspicious web traffic: 1. Call the `investigate_suspicious_web_clients` tool to inspect the top client IPs generating non-200 HTTP responses in `kibana_sample_data_logs`. 2. Identify any client IP exhibiting malicious reconnaissance or exploitation behavior (such as Nmap scans, WordPress login probing, `/etc/passwd` path traversal, or `/level/15/exec` command execution attempts) versus benign download errors. 3. Call the `create_security_case` tool to open a new Kibana Security Case for the malicious client IP. Set an appropriate `title`, `severity` (`high`), and a well-structured Markdown `description` summarizing the source IP, geolocation, total failed requests, observed attack categories (mapped to MITRE ATT&CK where applicable), sample malicious URLs, and recommended remediation steps. 4. Return a concise summary of your investigation and confirm the created Kibana Case details.כלים: בוחרים באפשרויות
investigate_suspicious_web_clients,create_security_caseו-platform.core.cases.
- מזהה הנציג:
לוחצים על Save.
- פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל
אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/agents" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "id": "web-security-incident-responder", "name": "Web Security Incident Responder", "description": "Investigates suspicious HTTP error activity in web access logs and opens Kibana Security Cases with the findings.", "labels": ["security", "incident-response"], "avatar_color": "#E7664C", "avatar_symbol": "IR", "configuration": { "instructions": "You are an automated Web Security Incident Responder. When asked to investigate suspicious web traffic:\n1. Call the `investigate_suspicious_web_clients` tool to inspect the top client IPs generating non-200 HTTP responses in `kibana_sample_data_logs`.\n2. Identify any client IP exhibiting malicious reconnaissance or exploitation behavior (such as Nmap scans, WordPress login probing, `/etc/passwd` path traversal, or `/level/15/exec` command execution attempts) versus benign download errors.\n3. Call the `create_security_case` tool to open a new Kibana Security Case for the malicious client IP. Set an appropriate `title`, `severity` (`high`), and a well-structured Markdown `description` summarizing the source IP, geolocation, total failed requests, observed attack categories (mapped to MITRE ATT&CK where applicable), sample malicious URLs, and recommended remediation steps.\n4. Return a concise summary of your investigation and confirm the created Kibana Case details.", "tools": [ { "tool_ids": [ "investigate_suspicious_web_clients", "create_security_case", "platform.core.cases" ] } ] } }' | jq '{id: .id, name: .name}'
הפעלת הנציג ואימות ה-Kibana Case
מנהלים שיחה עם הסוכן Web Security Incident Responder:
אפשרות 1: דרך ממשק המשתמש של Kibana:
- פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/agent_builder). - בבורר הנציגים, בוחרים באפשרות Web Security Incident Responder ומוודאים שהמחבר של ה-LLM (
${LLM_CONNECTOR_NAME}) נבחר. מזינים את ההנחיה הבאה:
Investigate the top client IPs generating HTTP errors in our web logs, identify any attacker performing reconnaissance or exploitation, and open a high-severity Kibana Security Case with your findings.
- פותחים את תפריט הניווט הראשי ועוברים אל Elasticsearch > Agents (או עוברים ישירות אל
אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/agent_builder/converse" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d "{ \"agent_id\": \"web-security-incident-responder\", \"connector_id\": \"${CONNECTOR_ID}\", \"input\": \"Investigate the top client IPs generating HTTP errors in our web logs, identify any attacker performing reconnaissance or exploitation, and open a high-severity Kibana Security Case with your findings.\" }" | jq '{ status: .status, tools_called: [.steps[] | select(.type == "tool_call") | .tool_id], response: .response.message }'הפלט אמור להיראות כך:
{ "status": "completed", "tools_called": [ "investigate_suspicious_web_clients", "create_security_case" ], "response": "I have completed the investigation into the web logs. \n\nThe analysis identified client IP **`30.156.16.164`** (located in the US) as a malicious actor. This IP generated 100 failed requests (HTTP 404) exhibiting clear patterns of reconnaissance and exploitation:\n\n* **Reconnaissance:** Extensive probing for WordPress installations and plugins, as well as Nmap scan signatures (e.g., `/nmaplowercheck...`).\n* **Path Traversal:** Attempts to access sensitive system files via `/etc/passwd`.\n* **Remote Command Execution (RCE):** Attempts to execute commands through paths such as `/level/15/exec`.\n* **Application Probing:** Targeting common management interfaces including Tomcat, Axis2, and JMX consoles.\n\nAs a result, I have opened a **high-severity** Kibana Security Case to track this incident:\n\n* **Case ID:** `1f0e00a5-cc89-4f62-b305-f286fae3d70e`\n* **Title:** Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164\n* **Recommended Action:** Immediately block the IP at the firewall/WAF and verify that no unauthorized access was achieved." }
מאמתים את בקשת התמיכה החדשה בנושא אבטחה ב-Kibana:
אפשרות 1: דרך ממשק המשתמש של Kibana:
- פותחים את תפריט הניווט הראשי ועוברים אל אבטחה > פניות (או עוברים ישירות אל
https://${KIBANA_IP}:5601/app/security/cases). - לוחצים על Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164 כדי לראות את סיכום האירוע בפורמט Markdown, ניתוח MITRE ATT&CK, כתובות URL זדוניות לדוגמה ורשימת משימות לתיקון.
- פותחים את תפריט הניווט הראשי ועוברים אל אבטחה > פניות (או עוברים ישירות אל
אפשרות 2: דרך ה-CLI:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${KIBANA_IP}:5601/api/cases/_find" \ -H "kbn-xsrf: true" \ | jq '.cases[0] | {id: .id, title: .title, severity: .severity, status: .status, tags: .tags, description: .description}'הפלט אמור להיראות כך:
{ "id": "1f0e00a5-cc89-4f62-b305-f286fae3d70e", "title": "Malicious Reconnaissance and Exploitation Attempt from 30.156.16.164", "severity": "high", "status": "open", "tags": [ "agent-builder", "web-logs" ], "description": "### Incident Summary\n**Source IP:** `30.156.16.164`\n**Geolocation:** US\n**Total Failed Requests:** 100\n**Status Codes Observed:** 404\n\n### Analysis\nThe client IP exhibited clear patterns of malicious reconnaissance and exploitation attempts:\n\n- **Reconnaissance (MITRE ATT&CK T1595):** Extensive probing for WordPress installations (`/wp-login.php`, various plugin paths) and Nmap scan signatures (`/nmaplowercheck...`).\n- **Path Traversal (MITRE ATT&CK T1083):** Attempted to access sensitive system files via `/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd`.\n- **Remote Command Execution (MITRE ATT&CK T1203):** Attempts to execute commands via paths like `/level/15/exec/-/configure/http`.\n- **Application Probing:** Targeted common management interfaces such as `/tomcat/manager/html/`, `/axis2/axis2-admin/`, and `/jmx-console/`.\n\n### Sample Malicious URLs\n- `/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd`\n- `/level/15/exec/-/configure/http`\n- `/nmaplowercheck1331872976`\n- `/wp-login.php`\n\n### Recommended Remediation\n1. **Block IP:** Immediately block `30.156.16.164` at the network firewall/WAF.\n2. **Log Review:** Verify that all requests from this IP resulted in 404 errors and no unauthorized access was gained.\n3. **Hardening:** Ensure that sensitive directories (e.g., `/cgi-bin/`, `/manager/`) are properly restricted." }
זיהוי התקפות
Attack Discovery הוא פיצ'ר מבוסס-AI ב-Elastic Security שמנתח התראות אבטחה פתוחות, ממפה אותן למסגרת MITRE ATT&CK ומקשר בין התראות, כתובות IP וחשבונות משתמשים קשורים לשרשראות תקיפה חזותיות. כדי ש-Attack Discovery יסנתז פלטים מובְנים של JSON עם כמה התראות בצורה מהימנה, צריך מודל גדול, אז השתמשו במודל מסוג 30B או גדול יותר (כמו gemma4:31b).
יצירת התראות אבטחה מיומני הדוגמה
כברירת מחדל, קבוצת הנתונים kibana_sample_data_logs מכילה יומני גישה גולמיים לאינטרנט ולא מאכלסת את אינדקס התראות האבטחה של Elastic Security (.alerts-security.alerts-default). בנוסף, מחולל הנתונים לדוגמה של Kibana מקצה ערכי @timestamp עתידיים (עד 30 יום קדימה) ל-100 בקשות הסריקה והניצול מ-30.156.16.164, מה שמציב אותן מחוץ לחלון now-30d עד now שנשאל על ידי כללי הזיהוי של Elastic Security.
כדי להדגים את Attack Discovery באמצעות היומנים לדוגמה, העבירו את @timestamp של הרשומות של 30.156.16.164 30 ימים אחורה, העשירו אותן בשדות source.ip ו-user.name של Elastic Common Schema (ECS) וצרו שני כללי זיהוי שיוצרים התראות אבטחה מהאירועים האלה:
ממפים את
clientipל-source.ip, מגדירים אתuser.name(www-data) ומזיזים את@timestampאחורה ב-30 ימים ברשומות של30.156.16.164ב-kibana_sample_data_logs*:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${ELASTICSEARCH_IP}:9200/kibana_sample_data_logs*/_update_by_query?conflicts=proceed&refresh=true" \ -H "Content-Type: application/json" \ -d '{ "query": { "term": { "clientip": "30.156.16.164" } }, "script": { "source": "ctx._source.source = [\"ip\": ctx._source.clientip]; ctx._source.user = [\"name\": \"www-data\"]; ctx._source[\"@timestamp\"] = ZonedDateTime.parse(ctx._source[\"@timestamp\"]).minusDays(30).toString();", "lang": "painless" } }' | jq '{updated: .updated, failures: .failures}'יוצרים שני כללי זיהוי של Elastic Security שמטרגטים את
kibana_sample_data_logs*. הכלל הראשון מזהה סריקה של Nmap ושל התחברות ל-WordPress (Reconnaissance), והכלל השני מזהה ניסיונות של מעבר נתיב ב-/etc/passwdוהרצת פקודות מרחוק (Initial Access):curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/detection_engine/rules" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "name": "Suspicious Web Reconnaissance and Vulnerability Scanning", "description": "Detects Nmap active scanning and WordPress login/plugin probing in web access logs.", "risk_score": 47, "severity": "medium", "type": "query", "language": "kuery", "query": "clientip: \"30.156.16.164\" and (request: *nmap* or request: *wp-login*)", "index": ["kibana_sample_data_logs*"], "from": "now-30d", "interval": "1h", "max_signals": 2, "enabled": true, "threat": [ { "framework": "MITRE ATT&CK", "tactic": { "id": "TA0043", "name": "Reconnaissance", "reference": "https://attack.mitre.org/tactics/TA0043/" }, "technique": [ { "id": "T1595", "name": "Active Scanning", "reference": "https://attack.mitre.org/techniques/T1595/" } ] } ] }' | jq '{id: .id, name: .name, enabled: .enabled}' curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ -X POST "https://${KIBANA_IP}:5601/api/detection_engine/rules" \ -H "kbn-xsrf: true" \ -H "Content-Type: application/json" \ -d '{ "name": "Web Path Traversal and Remote Command Execution Attempt", "description": "Detects path traversal attempts targeting /etc/passwd and Cisco IOS privilege level 15 remote command execution (/level/15/exec).", "risk_score": 73, "severity": "high", "type": "query", "language": "kuery", "query": "clientip: \"30.156.16.164\" and (request: *passwd* or request: *exec*)", "index": ["kibana_sample_data_logs*"], "from": "now-30d", "interval": "1h", "max_signals": 2, "enabled": true, "threat": [ { "framework": "MITRE ATT&CK", "tactic": { "id": "TA0001", "name": "Initial Access", "reference": "https://attack.mitre.org/tactics/TA0001/" }, "technique": [ { "id": "T1190", "name": "Exploit Public-Facing Application", "reference": "https://attack.mitre.org/techniques/T1190/" } ] } ] }' | jq '{id: .id, name: .name, enabled: .enabled}'ממתינים כ-60 שניות עד שהכלל יופעל בפעם הראשונה, ואז מוודאים שנוספו 4 התראות לאינדקס ב-
.alerts-security.alerts-default:curl -sk -u "elastic:${ELASTIC_PASSWORD}" \ "https://${ELASTICSEARCH_IP}:9200/.alerts-security.alerts-default/_search?size=4" \ | jq '.hits.hits[]._source | {rule: .["kibana.alert.rule.name"], source_ip: .source.ip, user_name: .user.name, request: .request}'הפלט אמור להיראות כך:
{ "rule": "Web Path Traversal and Remote Command Execution Attempt", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd" } { "rule": "Web Path Traversal and Remote Command Execution Attempt", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/cgi-bin/mj_wwwusr?passw=&list=GLOBAL&user=&func=help&extra=/../../../../../../../../etc/passwd" } { "rule": "Suspicious Web Reconnaissance and Vulnerability Scanning", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/nmaplowercheck1331872976" } { "rule": "Suspicious Web Reconnaissance and Vulnerability Scanning", "source_ip": "30.156.16.164", "user_name": "www-data", "request": "/nmaplowercheck1331873003" }
הפעלת Attack Discovery
- ב-Kibana, פותחים את תפריט הניווט הראשי ועוברים אל Security > Attack Discovery (או מנווטים ישירות אל
https://${KIBANA_IP}:5601/app/security/attack_discovery). - בתפריט המחברים שבחלק העליון של הדף, בוחרים את המחבר של ה-LLM (
${LLM_CONNECTOR_NAME}). - לוחצים על Generate כדי לנתח את ההתראות הפתוחות ב-
.alerts-security.alerts-default.
אפשר גם להפעיל ולבדוק את Attack Discovery מ-CLI:
export EXECUTION_UUID=$(curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
-X POST "https://${KIBANA_IP}:5601/api/attack_discovery/_generate" \
-H "kbn-xsrf: true" \
-H "elastic-api-version: 2023-10-31" \
-H "Content-Type: application/json" \
-d "{
\"alertsIndexPattern\": \".alerts-security.alerts-default\",
\"anonymizationFields\": [
{\"id\": \"_id\", \"field\": \"_id\", \"allowed\": true},
{\"id\": \"@timestamp\", \"field\": \"@timestamp\", \"allowed\": true},
{\"id\": \"kibana.alert.original_time\", \"field\": \"kibana.alert.original_time\", \"allowed\": true},
{\"id\": \"message\", \"field\": \"message\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.name\", \"field\": \"kibana.alert.rule.name\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.description\", \"field\": \"kibana.alert.rule.description\", \"allowed\": true},
{\"id\": \"kibana.alert.severity\", \"field\": \"kibana.alert.severity\", \"allowed\": true},
{\"id\": \"kibana.alert.risk_score\", \"field\": \"kibana.alert.risk_score\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.tactic.id\", \"field\": \"kibana.alert.rule.threat.tactic.id\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.tactic.name\", \"field\": \"kibana.alert.rule.threat.tactic.name\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.technique.id\", \"field\": \"kibana.alert.rule.threat.technique.id\", \"allowed\": true},
{\"id\": \"kibana.alert.rule.threat.technique.name\", \"field\": \"kibana.alert.rule.threat.technique.name\", \"allowed\": true},
{\"id\": \"source.ip\", \"field\": \"source.ip\", \"allowed\": true},
{\"id\": \"user.name\", \"field\": \"user.name\", \"allowed\": true}
],
\"apiConfig\": {
\"actionTypeId\": \".gen-ai\",
\"connectorId\": \"${CONNECTOR_ID}\"
},
\"replacements\": {},
\"size\": 20,
\"subAction\": \"invokeAI\"
}" | jq -r '.execution_uuid')
echo "Attack Discovery execution UUID: ${EXECUTION_UUID}"
אחרי שהיצירה מסתיימת (בערך 60-90 שניות), מאחזרים את שרשרת ההתקפה שזוהתה:
curl -sk -u "elastic:${ELASTIC_PASSWORD}" \
"https://${KIBANA_IP}:5601/api/attack_discovery/_find" \
-H "kbn-xsrf: true" \
-H "elastic-api-version: 2023-10-31" \
| jq '.data[0]'
התכונה "זיהוי התקפות" מקשרת בין ארבע ההתראות לאירוע אחד עם כתובת IP מקורית, חשבון משתמש וטקטיקות MITRE ATT&CK:
{
"alert_ids": [
"05c30118398ea2a8b70c55ce81fdb559dbe14c460a8a95253bd81719415a28b5",
"73ebaeca440b463ffa5812da5b68a2485d465a648ffaf16385a54d8bae9a53d2",
"60ae9f16b40cec1bdc27674ade0e5eb69680ccca3d956fbf2a75418843799987",
"de503ae52444db7712d062c54750f413b90a75521cdfff0de9bdf41a64141346"
],
"alert_rule_uuid": "attack_discovery_ad_hoc_rule_id",
"alert_start": "2026-10-03T01:05:00.384Z",
"alert_updated_at": "2026-10-03T01:05:03.460Z",
"alert_workflow_status": "open",
"alert_workflow_status_updated_at": "2026-10-03T01:05:03.460Z",
"connector_id": "<GENERATED_CONNECTOR_ID>",
"connector_name": "<GENERATED_CONNECTOR_ID>",
"details_markdown": "- The attack sequence began at `2026-10-01T12:43:41.756Z` when an actor using IP `30.156.16.164` performed active reconnaissance scanning against the server.\n- Following the initial scan, the attacker attempted to exploit a path traversal vulnerability at `2026-10-01T12:43:51.756Z` and again at `2026-10-01T12:44:02.756Z`, specifically targeting the `/etc/passwd` file via the `mj_wwwusr` CGI endpoint.\n- The activity concluded with further reconnaissance scanning at `2026-10-01T12:44:21.756Z` from the same source IP `30.156.16.164`.\n- All requests were associated with the user `www-data` and resulted in 404 errors, suggesting the attempts were unsuccessful.",
"entity_summary_markdown": "Activity targeting user `www-data` from source IP `30.156.16.164`.",
"generation_uuid": "<EXECUTION_UUID>",
"id": "1f71ed004c7340d5088d242ca2483525a164a9ea9a80a3f90bd1f514d43231bc",
"mitre_attack_tactics": [
"Reconnaissance",
"Initial Access"
],
"risk_score": 240,
"summary_markdown": "An attacker from `30.156.16.164` performed reconnaissance and path traversal attempts against the system.",
"timestamp": "2026-10-03T01:05:00.384Z",
"title": "Web Reconnaissance and Exploitation Attempt",
"user_id": "elastic",
"user_name": "elastic",
"users": [
{
"id": "elastic",
"name": "elastic"
}
],
"index": ".internal.adhoc.alerts-security.attack.discovery.alerts-default-000001"
}