-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathindex.html
1084 lines (1044 loc) · 64.5 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Hands On : ELK</title>
<!-- Bootstrap -->
<link href="css/bootstrap.min.css" rel="stylesheet">
<!-- home made custom CSS -->
<link href="css/tuto.css" rel="stylesheet">
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
</head>
<body>
<nav class="navbar navbar-inverse navbar-fixed-top">
<div class="container">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<a class="navbar-brand" href="./index.html">Tutoriel</a>
<ul class="nav navbar-nav">
<li><a href="./slides/index.html">Présentation</a></li>
<li><a href="./es_concept.html">Concepts ElasticSearch</a></li>
<li><a href="./novm.html">Installation sans VM</a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container">
<h1> Hands On : LogStash, ElasticSearch, Kibana, et plus encore </h1>
<p class="lead">Ce tutoriel détaillera pas à pas :
<ul>
<li><a href="#install">L'installation locale de LogStash, ElasticSearch et Kibana</a></li>
<li><a href="#logstash">Le traitement de fichiers de logs par LogStash</a></li>
<li><a href="#es">L'indexation dans ElasticSearch</a></li>
<li><a href="#kibana">L'exploitation des données via Kibana</a></li>
<li>Et pour aller plus loin : <a href="#broker">l'utilisation d'un broker de messages et d'agents distants</a> et <a href="#cluster">la mise en cluster d'ElasticSearch</a></li>
</ul>
</p>
<div class="alert alert-warning" role="alert">
Dans le but d'éviter les incidents de démarrage lors de l'atelier, merci de tester le lancement de la machine virtuelle depuis un ordinateur connecté à Internet !
</div>
<h2 id="install">Démarrage de la machine virtuelle</h2>
<div class="alert alert-warning" role="alert">
L'utilisation de la machine virtuelle demande au minimum un ordinateur avec un CPU puissant (type Intel Core i5), <strong>8Go de RAM</strong>, et un système d'exploitation (Windows, Mac ou Linux) <strong>64 bits</strong>. Si votre machine ne dispose pas de ce hardware minimum, merci de suivre avant le début de l'atelier les étapes d'installation décrites <a href="./novm.html">sur la page dédiée à l'installation sans machine virtuelle</a>.
</div>
<p>
<ul>
<li>Téléchargez VirtualBox pour votre système à cette adresse : <a href="https://www.virtualbox.org/wiki/Downloads">https://www.virtualbox.org/wiki/Downloads</a></li>
<li>
Exécutez l'installeur en conservant les paramètres par défaut.
</li>
<li>
Téléchargez la machine virtuelle de l'atelier <a href="https://drive.google.com/file/d/0B_b9KxBJMoOATENZMXU2LTF0N00/view?usp=sharing">à cette adresse</a> (fichier de 2 Go).
</li>
<li>
Le type de fichier devrait déjà être reconnu par le système comme associé à VirtualBox. Double-cliquez sur le fichier pour lancer la VM.
</li>
<li>
Le nom d'utilisateur est <code>elk</code>. Le mot de passe est <code>elk</code>.
</li>
<li>
Dans le lanceur à gauche de la fenêtre se trouvent un raccourci vers Firefox, avec en favori toutes les URL utiles de ce tutoriel, et un raccourci vers le terminal pour lancer toutes les commandes nécessaires.
</li>
<li>
Tous les logiciels qui ne sont pas packagés ont été installés dans le dossier <code>/home/elk/atelier</code>.
</li>
</ul>
</p>
<div class="alert alert-info" role="alert">
Dans la suite du tutoriel, les actions à faire sont indiquées par l'icone : <span class="glyphicon glyphicon-hand-right" />
</div>
<h2 id="logstash">Étape 1 : LogStash</h2>
<p>
Dans cette étape, nous allons lire un fichier de logs avec LogStash, découper ces logs pour leur ajouter de la sémantique, et les faire écrire dans la sortie standard.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour commencer, nous allons avoir besoin de logs à lire. Pour cela, enregistrer dans <code>/home/elk/atelier</code> <a href="./files/logstash-tutorial-small.log">ce fichier</a>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Créer ensuite dans <code>/home/elk/atelier</code> un fichier nommé <code>logstash.conf</code>. L'éditer avec l'éditeur de texte de votre choix, et y placer le code suivant en remplaçant <code>/home/elk/atelier</code> par sa valeur (par exemple <code>/home/cvillard/elk</code>) :
<pre>
# C'est un commentaire
input {
# On définit ici les sources de données à traiter
file {
path => "/home/elk/atelier/logstash-tutorial-small.log"
start_position => beginning
}
}
output {
# on définit ici les destinations pour les données traitées
stdout {
# Sortie standard : pas de paramètres
}
}
</pre>
</p>
<div class="alert alert-warning" role="alert">
<p><strong>Attention</strong></p>
<p>Pour ceux qui n'utilisent pas la machine virtuelle, le chemin indiqué dans le paramètre <code>path => ...</code> doit être adapté, ainsi que dans tous les exemples de configuration indiqués dans la suite du tutoriel.</p>
</div>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Démarrer LogStash en lui indiquant le fichier de configuration :
<pre>
$ /home/elk/atelier/logstash-2.1.0/bin/logstash agent -f '/home/elk/atelier/logstash.conf'
Settings: Default filter workers: 2
Logstash startup completed
2015-11-28T15:34:10.995Z claire-i5 83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] "GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1" 200 203023 "http://semicomplete.com/presentations/logstash-monitorama-2013/" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36"
</pre>
Vous devriez voir s'afficher dans la console le contenu du fichier, la ligne étant précédée d'un timestamp et du nom de votre PC ajoutés automatiquement par LogStash (<code>2015-11-28T15:34:10.995Z claire-i5</code> dans mon exemple).
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Interrompre LogStash avec <code>Ctrl+C</code> :
<pre>
^CSIGINT received. Shutting down the pipeline. {:level=>:warn}
Logstash shutdown completed
</pre>
</p>
<p>
Nous avons donc lu un fichier avec LogStash et l'avons recopié sur la sortie standard. C'est un bon début, mais ce n'est pas suffisant. Nous allons maintenant modifier la configuration pour découper chaque ligne lue dans le fichier en un message JSON contenant des champs nommés. Cela permettra d'ajouter de la sémantique à chaque ligne, et ensuite de faire des recherches une fois dans ElasticSearch.
</p>
<div class="alert alert-info" role="alert">
<p>
Pour éviter de relire des lignes déjà lues en cas de redémarrage de LogStash, celui-ci stocke sur le disque l'endroit où il se trouve dans les fichiers qu'il lit. Par défaut, il crée des fichiers <code>.sincedb_*</code> dans le <code>$HOME</code> de l'utilisateur qui le lance.
</p>
<p>
Ainsi, pour faire relire à LogStash un fichier déjà lu, il faut enlever la ligne contenant l'inode de ce fichier dans le fichier <code>.sincedb_*</code> correspondant (ou supprimer le fichier entier).
</p>
<p>
Il est également possible de paramétrer l'endroit où LogStash écrit ses informations et le nom du fichier correspondant pour éviter de polluer le dossier home et rendre le nom plus explicite que <code>.sincedb_1bd0462c651e913184f7efa9f40f84c6</code> (le répertoire indiqué doit avoir été créé auparavant) :
<pre>
input {
# On définit ici les sources de données à traiter
file {
path => "/home/elk/atelier/logstash-tutorial-small.log"
start_position => beginning
# on customise le sincedb_path et le nom du fichier
sincedb_path => "/home/elk/atelier/sincedb/sincedb_apache.log"
}
}
</pre>
</p>
</div>
<p>
LogStash embarque des patterns pour traiter un grand nombre de formats de logs courants. C'est le cas pour les logs Apache traités ici.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Ouvrir le fichier <code>logstash.conf</code> créé précédemment et ajouter le bloc suivant entre le bloc <code>input</code> et le bloc <code>output</code> (l'explication de cette ligne est plus loin) :
<pre>
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}"}
}
}
</pre>
<span class="glyphicon glyphicon-hand-right" ></span>
Modifier l'output <code>stdout</code> pour qu'il écrive le message complet en JSON au lieu de le résumer :
<pre>
output {
# on définit ici les destinations pour les données traitées
stdout {
codec => json
}
}
</pre>
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span> Supprimez le fichier <code>.sincedb_*</code> avec l'une des commandes suivantes, selon que vous ayez customisé le chemin de stockage du fichier :
<pre>
$ rm -f /home/elk/atelier/sincedb/sincedb_apache.log
</pre>
Ou si vous avez conservé les paramètres par défaut :
<pre>
$ rm -f /home/elk/.sincedb_*
</pre>
</p>
<div class="alert alert-info" role="alert">
Pour lister les fichiers cachés sous Linux :
<pre>
$ ls -la <repertoire>
</pre>
</div>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Redémarrez LogStash avec la même commande que précédemment.
<pre>
$ /home/elk/atelier/logstash-2.1.0/bin/logstash agent -f '/home/elk/atelier/logstash.conf'
Settings: Default filter workers: 2
Logstash startup completed
{"message":"83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"","@version":"1","@timestamp":"2015-11-28T16:10:15.811Z","host":"claire-i5","path":"/home/claire/Documents/atelier_es_mydir/logstash-tutorial-small.log","clientip":"83.149.9.216","ident":"-","auth":"-","timestamp":"04/Jan/2015:05:13:42 +0000","verb":"GET","request":"/presentations/logstash-monitorama-2013/images/kibana-search.png","httpversion":"1.1","response":"200","bytes":"203023","referrer":"\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"","agent":"\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\""}
^CSIGINT received. Shutting down the pipeline. {:level=>:warn}
Logstash shutdown completed
</pre>
</p>
<p>
Une fois mis en forme, on voit que la ligne de log a maintenant la forme suivante :
<pre>
{
"message":"83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"",
"@version":"1",
"@timestamp":"2015-11-28T16:10:15.811Z",
"host":"claire-i5",
"path":"/home/claire/Documents/atelier_es_mydir/logstash-tutorial-small.log",
"clientip":"83.149.9.216",
"ident":"-",
"auth":"-",
"timestamp":"04/Jan/2015:05:13:42 +0000",
"verb":"GET",
"request":"/presentations/logstash-monitorama-2013/images/kibana-search.png",
"httpversion":"1.1",
"response":"200",
"bytes":"203023",
"referrer":"\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"",
"agent":"\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\""
}
</pre>
Les différentes partie du log d'origine ont été découpées et placées dans des champs nommés. La ligne d'origine est elle conservée en l'état dans le champ <code>message</code>.
</p>
<div class="alert alert-info" role="alert">
<p>
<strong>Qu'avons-nous fait ?</strong>
</p>
<p>
En ajoutant ce filtre, nous avons utilisé GROK. C'est un outil utilisant une syntaxe pouvant s'apparenter aux expressions régulières, et qui dans LogStash permet de découper un champ (<code>message</code> dans notre cas) en plusieurs champs nommés.
</p>
<p>
Par exemple, le pattern prédéfini que nous avons utilisé est défini par la syntaxe suivante :
<pre>
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}
</pre>
On voit qu'il fait lui-même référence à d'autres patterns : <code>COMMONAPACHELOG</code> et <code>QS</code>. La configuration complète donne :
<pre>
WORD \b\w+\b
NOTSPACE \S+
BASE10NUM (?<![0-9.+-])(?>[+-]?(?:(?:[0-9]+(?:\.[0-9]+)?)|(?:\.[0-9]+)))
NUMBER (?:%{BASE10NUM})
INT (?:[+-]?(?:[0-9]+))
DATA .*?
MONTHDAY (?:(?:0[1-9])|(?:[12][0-9])|(?:3[01])|[1-9])
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
YEAR (?>\d\d){1,2}
HOUR (?:2[0123]|[01]?[0-9])
MINUTE (?:[0-5][0-9])
# '60' is a leap second in most time standards and thus is valid.
SECOND (?:(?:[0-5][0-9]|60)(?:[:.,][0-9]+)?)
TIME (?!<[0-9])%{HOUR}:%{MINUTE}(?::%{SECOND})(?![0-9])
IPV6 ((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?
IPV4 (?<![0-9])(?:(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2})[.](?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]{1,2}))(?![0-9])
IP (?:%{IPV6}|%{IPV4})
HOSTNAME \b(?:[0-9A-Za-z][0-9A-Za-z-]{0,62})(?:\.(?:[0-9A-Za-z][0-9A-Za-z-]{0,62}))*(\.?|\b)
IPORHOST (?:%{HOSTNAME}|%{IP})
USERNAME [a-zA-Z0-9._-]+
USER %{USERNAME}
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}
COMMONAPACHELOG %{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] "(?:%{WORD:verb} %{NOTSPACE:request}(?: HTTP/%{NUMBER:httpversion})?|%{DATA:rawrequest})" %{NUMBER:response} (?:%{NUMBER:bytes}|-)
QUOTEDSTRING (?>(?<!\\)(?>"(?>\\.|[^\\"]+)+"|""|(?>'(?>\\.|[^\\']+)+')|''|(?>`(?>\\.|[^\\`]+)+`)|``))
# QS n'est qu'un alias
QS %{QUOTEDSTRING}
COMBINEDAPACHELOG %{COMMONAPACHELOG} %{QS:referrer} %{QS:agent}
</pre>
On est plutôt contents de ne pas avoir eu à l'écrire nous-même, et on comprend également pourquoi GROK est généralement, dans une configuration LogStash, le plus gros consommateur de temps d'exécution. Les améliorations de performances devront avant tout se porter sur l'optimisation des expressions GROK au cas d'usage.
</p>
<p>
La mise au point d'un pattern GROK "maison" (non embarqué par défaut), pour traiter un format de log non standard, peut donc s'avérer ardue. Pour aider à sa mise au point, il existe un outil en ligne très pratique : <a href="http://grokdebug.herokuapp.com/">Grok Debugger</a>. Il liste également <a href="http://grokdebug.herokuapp.com/patterns">l'ensemble des patterns embarqués dans LogStash</a>.
</p>
</div>
<div class="alert alert-warning" role="alert">
<p>
L'extrait de configuration GROK mentionné au dessus contient la ligne suivante pour désigner le mois :
<pre>
MONTH \b(?:Jan(?:uary)?|Feb(?:ruary)?|Mar(?:ch)?|Apr(?:il)?|May|Jun(?:e)?|Jul(?:y)?|Aug(?:ust)?|Sep(?:tember)?|Oct(?:ober)?|Nov(?:ember)?|Dec(?:ember)?)\b
</pre>
On voit que les noms de mois sont en dur, et en anglais. On ne supporte que la version complète et la version abrégée.
</p>
<p>
De plus, le pattern de date <code>"dd/MMM/YYYY:HH:mm:ss Z"</code> utilisé juste après dans ce tutoriel dépend de la locale de la machine ou de celle précisée en paramètre du filtre <code>date</code>.
</p>
<p>
C'est pour ces 2 raisons qu'il faut éviter autant que possible les patterns de date contenant du texte pour les jours et les mois, au profit de leur écriture en chiffre qui elle n'est pas dépendante de la locale et n'est pas ambigüe.
</p>
</div>
<p>
L'ajout de sémantique à notre log a donc plutôt bien fonctionné. Il reste néanmoins une dernière anomalie au niveau du champ <code>@timestamp</code>. En effet, celui-ci est ajouté automatiquement par LogStash, et renseigné avec l'heure du moment où il a lu la ligne dans le fichier. Dans un cas comme ici où on relis des logs anciens, cette date n'a aucun intérêt : la date intéressante est celle contenue dans le log lui-même, soit le 4 janvier 2015 à 05:13:42 dans notre cas, et qui a été placée dans le champ <code>timestamp</code> (sans <code>@</code>) par le pattern GROK <code>COMBINEDAPACHELOG</code>. Nous allons donc utiliser le filtre <code>date</code> pour lire la valeur String du champ <code>timestamp</code> (sans <code>@</code>), la transformer en timestamp et la placer dans le champ <code>@timestamp</code> (avec <code>@</code>) en remplacement de la valeur automatique.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour cela, ouvrir le fichier <code>logstash.conf</code>, et ajouter un filtre après le GROK :
<pre>
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}"}
}
date {
# exemple de date: 04/Jan/2015:05:13:42 +0000
match => [ "timestamp", "dd/MMM/YYYY:HH:mm:ss Z" ]
}
}
</pre>
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
N'oubliez pas de supprimer le fichier <code>.sincedb_*</code> puis redémarrez LogStash avec la même commande que précédemment.
<pre>
$ /home/elk/atelier/logstash-2.1.0/bin/logstash agent -f '/home/elk/atelier/logstash.conf'
Settings: Default filter workers: 2
Logstash startup completed
{"message":"83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"","@version":"1","@timestamp":"2015-01-04T05:13:42.000Z","host":"claire-i5","path":"/home/claire/Documents/atelier_es_mydir/logstash-tutorial-small.log","clientip":"83.149.9.216","ident":"-","auth":"-","timestamp":"04/Jan/2015:05:13:42 +0000","verb":"GET","request":"/presentations/logstash-monitorama-2013/images/kibana-search.png","httpversion":"1.1","response":"200","bytes":"203023","referrer":"\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"","agent":"\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\""}
^CSIGINT received. Shutting down the pipeline. {:level=>:warn}
Logstash shutdown completed
</pre>
</p>
<p>
Si on compare les champs <code>@timestamp</code> et <code>timestamp</code> dans la sortie on a maintenant les valeurs suivantes :
<pre>
"@timestamp":"2015-01-04T05:13:42.000Z",
"timestamp":"04/Jan/2015:05:13:42 +0000",
</pre>
Les deux valeurs sont bien identiques, on pourra donc une fois dans ElasticSearch faire une recherche sur le champ standard <code>@timestamp</code> et avoir des résultats pertinents.
</p>
<p>
Afin d'améliorer l'exploitation future des logs, il peut également être intéressant de typer les champs numériques comme tels. Comme nous le verrons dans la suite de ce tutoriel, cela peut aider Elasticsearch lors de l'interprétation. Dans notre cas, nous avons 2 champs numériques : <code>bytes</code> et <code>response</code>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour typer ces champs, ouvrir le fichier <code>logstash.conf</code>, et ajouter un filtre après le filtre <code>date</code> :
<pre>
filter {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}"}
}
date {
# exemple de date: 04/Jan/2015:05:13:42 +0000
match => [ "timestamp", "dd/MMM/YYYY:HH:mm:ss Z" ]
}
mutate {
convert => {
"bytes" => "integer"
"response" => "integer"
}
}
}
</pre>
</p>
<div class="alert alert-warning" role="alert">
<p><strong>Attention</strong></p>
<p>
Un champ ne doit être typé directement dans LogStash que si aucune valeur invalide ne peut être présente en entrée. Si l'on n'est pas certain à 100% de cela, il ne faut pas typer le champ et laisser Logstash le traiter en tant que chaîne de caractères. En effet, si une valeur invalide se trouve dans un champ à convertir (par exemple, une lettre dans un champ à convertir en entier), LogStash émettra une erreur et cessera tout traitement sur ce message, qui ne sera jamais transmis en sortie et disparaîtra.
</p>
</div>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
N'oubliez pas de supprimer le fichier <code>.sincedb_*</code> puis redémarrez LogStash avec la même commande que précédemment.
<pre>
$ /home/elk/atelier/logstash-2.1.0/bin/logstash agent -f '/home/elk/atelier/logstash.conf'
Settings: Default filter workers: 2
Logstash startup completed
{"message":"83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"","@version":"1","@timestamp":"2015-01-04T05:13:42.000Z","host":"claire-i5","path":"/home/claire/Documents/atelier_es_mydir/logstash-tutorial-small.log","clientip":"83.149.9.216","ident":"-","auth":"-","timestamp":"04/Jan/2015:05:13:42 +0000","verb":"GET","request":"/presentations/logstash-monitorama-2013/images/kibana-search.png","httpversion":"1.1","response":200,"bytes":203023,"referrer":"\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"","agent":"\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\""}^CSIGINT received. Shutting down the pipeline. {:level=>:warn}
Logstash shutdown completed
</pre>
</p>
<p>
On voit que les champs <code>bytes</code> et <code>response</code> sont maintenant des entiers : ils ne sont pas encadrés par des doubles quotes.
<pre>
...,"response":200,"bytes":203023,...
</pre>
</p>
<p>
En cas de soucis, voici <a href="./files/logstash_p1.conf" >le fichier <code>logstash.conf</code></a> obtenu à la fin de cette partie.
</p>
<h2 id="es">Étape 2 : ElasticSearch</h2>
<p>
Dans cette étape, nous allons utiliser le résultat de l'étape précédente pour indexer les logs découpés dans ElasticSearch et les rechercher sommairement via l'API REST d'ElasticSearch. Nous apprendrons également comme configurer sommairement ElasticSearch et comment le monitorer via un plugin.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span> Le fonctionnement d'ElasticSearch étant assez particulier, il est fortement conseillé de lire la page <a href="./es_concept.html">Concepts ElasticSearch</a> de ce tutoriel avant de continuer !
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span> Démarrer ElasticSearch avec la commande :
<pre>
$ /home/elk/atelier/elasticsearch-2.1.0/bin/elasticsearch
</pre>
Au bout d'un instant, la trace suivante apparaît dans la console (<code>Kirigi</code> étant le nom du nœud, il change à chaque démarrage) :
<pre>
[2015-11-28 14:03:28,178][INFO ][node ] [Kirigi] started
</pre>
<span class="glyphicon glyphicon-hand-right" ></span> Contrôler alors à l'URL <a href="http://localhost:9200/">http://localhost:9200/</a> qu'ElasticSearch répond :
<pre>
{
"name" : "Kirigi",
"cluster_name" : "elasticsearch",
"version" : {
"number" : "2.1.0",
"build_hash" : "72cd1f1a3eee09505e036106146dc1949dc5dc87",
"build_timestamp" : "2015-11-18T22:40:03Z",
"build_snapshot" : false,
"lucene_version" : "5.3.1"
},
"tagline" : "You Know, for Search"
}
</pre>
</p>
<p>
Nous allons commencer par envoyer les logs à ElasticSearch depuis LogStash.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour cela, ouvrir le fichier <code>logstash.conf</code> et modifier le bloc <code>output</code> :
<pre>
output {
elasticsearch {}
}
</pre>
Comme nous faisons fonctionner LogStash et ElasticSearch sur la même machine et sans sécurité, les paramètres par défaut conviennent.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Supprimer le fichier <code>.sincedb_*</code> et redémarrer Logstash. Une fois la trace <code>Logstash startup completed</code> apparue, on observe du côté d'Elasticsearch 2 nouvelles traces :
<pre>
[2015-12-06 18:25:04,575][INFO ][cluster.metadata ] [Shirow Ishihara] [logstash-2015.01.04] creating index, cause [auto(bulk api)], templates [logstash], shards [5]/[1], mappings [_default_, logs]
[2015-12-06 18:25:05,342][INFO ][cluster.metadata ] [Shirow Ishihara] [logstash-2015.01.04] update_mapping [logs]
</pre>
Ces traces indiquent que des logs ont été envoyées à ElasticSearch par Logstash, et qu'ElasticSearch a créé un nouvel index nommé <code>logstash-2015.01.04</code> pour contenir ces données.
</p>
<p>
Nous allons utiliser l'API REST d'ElasticSearch pour consulter les données de ce nouvel index (donc, une seule ligne, ou un seul <em>document</em> selon la terminologie d'ElasticSearch, correspondant à notre log Apache).
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrez dans votre navigateur l'URL suivante : <a href="http://localhost:9200/logstash-2015.01.04/_search?pretty" >http://localhost:9200/logstash-2015.01.04/_search?pretty</a>. Le résultat devrait être (une fois mis en forme) :
<pre>
{
"took":226,
"timed_out":false,
"_shards":{"total":5,"successful":5,"failed":0},
"hits":{
"total":1,
"max_score":1.0,
"hits":[
{
"_index":"logstash-2015.01.04",
"_type":"logs",
"_id":"AVF4U1CkEZ3y1kCGmOik",
"_score":1.0,
"_source":{
"message":"83.149.9.216 - - [04/Jan/2015:05:13:42 +0000] \"GET /presentations/logstash-monitorama-2013/images/kibana-search.png HTTP/1.1\" 200 203023 \"http://semicomplete.com/presentations/logstash-monitorama-2013/\" \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\"",
"@version":"1",
"@timestamp":"2015-01-04T05:13:42.000Z",
"host":"claire-i5",
"path":"/home/claire/Documents/atelier_es_mydir/logstash-tutorial-small.log",
"clientip":"83.149.9.216",
"ident":"-",
"auth":"-",
"timestamp":"04/Jan/2015:05:13:42 +0000",
"verb":"GET",
"request":"/presentations/logstash-monitorama-2013/images/kibana-search.png",
"httpversion":"1.1",
"response":200,
"bytes":203023,
"referrer":"\"http://semicomplete.com/presentations/logstash-monitorama-2013/\"",
"agent":"\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36\""
}
}
]
}
}
</pre>
</p>
<div class="alert alert-info" role="alert">
Pour plus de détails sur le contenu de cette réponse, vous pouvez vous reporter à son explication sur la page <a href="./es_concept.html">Concepts ElasticSearch</a>.
</div>
<div class="alert alert-info" role="alert">
L'extension Chrome et Firefox JSONView permet d'afficher le résultat des requêtes directement formatés dans le navigateur.
</div>
<p>
Dans la réponse retournée, on voit que la donnée est de type <code>logs</code> :
<pre>
...
"_type":"logs",
...
</pre>
</p>
<p>
Si on relit de nouveau les traces produites par ElasticSearch lors de la réception du log, on voit que la seconde mentionne ce type <code>logs</code> :
<pre>
[2015-12-06 18:25:05,342][INFO ][cluster.metadata ] [Shirow Ishihara] [logstash-2015.01.04] update_mapping [logs]
</pre>
En effet, comme nous n'avons pas indiqué à ElasticSearch comment interpréter le type <code>logs</code>, il a dû deviner, et a stocké le résultat de sa tentative dans un <em>mapping</em>.
</p>
<p>
Comme cela a une grande importance sur la qualité et l'efficacité des recherches faites sur les différents champs (on peut indiquer une plage de dates en critère de recherche, cela ne fonctionnera pas si le champ est une simple chaîne de caractères), il est intéressant de voir comment ElasticSearch a deviné. Pour cela, entrer l'URL suivante dans votre navigateur : <a href="http://localhost:9200/logstash-2015.01.04/_mapping/logs/?pretty">http://localhost:9200/logstash-2015.01.04/_mapping/logs/?pretty</a>.
</p>
<p>
Le résultat est assez verbeux, nous allons donc nous intéresser à certains champs, et notamment ceux qui ne sont pas de simples chaînes de caractères.
</p>
<p>
Tout d'abord, le champ <code>@timestamp</code> : <a href="http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/@timestamp?pretty">http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/@timestamp?pretty</a>
<pre>
{
"logstash-2015.01.04":{
"mappings":{
"logs":{
"@timestamp":{
"full_name":"@timestamp",
"mapping":{
"@timestamp":{
"type":"date",
"format":"strict_date_optional_time||epoch_millis"
}
}
}
}
}
}
}
</pre>
Ce champ est standard dans LogStash, ElasticSearch l'a reconnu et lui a donc appliqué de lui même le type <code>date</code> avec au choix les 2 formats <code>strict_date_optional_time</code> et <code>epoch_millis</code> (les formats par défaut sont <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats">ici</a>, sinon il est possible d'indiquer son propre format selon la syntaxe de la librairie JodaTime).
</p>
<p>
Prenons maintenant le champ <code>bytes</code> : <a href="http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/bytes?pretty">http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/bytes?pretty</a>
<pre>
{
"logstash-2015.01.04":{
"mappings":{
"logs":{
"bytes":{
"full_name":"bytes",
"mapping":{
"bytes":{
"type":"long"
}
}
}
}
}
}
}
</pre>
Grâce au typage du champ en entier réalisé dans LogStash, ElasticSearch a décidé de traiter ce champ comme un entier, de type <code>long</code>. Nous pourrons donc directement faire des recherches concernant les logs dont le champ <code>bytes</code> est supérieur à 200 000 par exemple.
</p>
<p>
Si on observe le mapping complet, on trouve qu'ElasticSearch a mal deviné le type du champ <code>timestamp</code> : il le considère comme une <code>string</code> au lieu d'une date à un format particulier. Nous allons donc devoir indiquer à ElasticSearch la manière dont il devra être interprété via un <em>template</em> (voir <a href="./es_concept.html#mappingandtemplates">l'explication du concept</a>).
</p>
<p>
Il n'est pas possible de corriger un mapping Elasticsearch sans ré-indexer les données. Comme dans notre cas nous n'avons qu'une seule donnée, nous allons supprimer l'index, et le document et le mapping deviné avec lui, pour pouvoir le reconfigurer proprement et soumettre de nouveau notre ligne de log pour indexation.
</p>
<p>
Deux solutions existent pour supprimer un index. La première est l'installation d'un plugin de monitoring standard d'ElasticSearch, très utile pour consulter rapidement la santé de son cluster, le nombre et le contenu des index ainsi que tout le paramétrage du cluster ou des nœuds : <strong>Head</strong>.
</p>
<p>
La seconde solution pour supprimer un index dans ElasticSearch est la ligne de commande si on dispose du programme cURL installé :
<pre>
$ curl -XDELETE "http://localhost:9200/logstash-2015.01.04/"
{"acknowledged":true}
</pre>
</p>
<p>
Nous allons donc nous servir de Head pour supprimer notre index mal configuré.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Accédez à l'URL suivante : <a href="http://localhost:9200/_plugin/head/">http://localhost:9200/_plugin/head/</a>
<img src="images/es_head.png" />
</p>
<p>
On trouve sur le premier onglet les index (<code>.kibana</code>, qui n'existe que si vous avez déjà lancé Kibana, et notre index <code>logstash-2015.01.04</code>) et la répartition des shards (les boites vertes numérotées) entre les nœuds du cluster (un seul pour nous, nommé <code>Shirow Ishihara</code> sur la capture). Comme par défaut ElasticSearch essaye de répliquer les données une fois sur un second nœud qui n'existe pas dans notre configuration, ils apparaissent <code>Unassigned</code>, non assignés. Cela explique la santé du cluster qui vaut <code>yellow</code> (une partie des répliques est non assignée). Elle serait <code>green</code> si toutes les répliques étaient assignées à des nœuds, et <code>red</code> si certaines données primaires étaient en cours de récupération (après un redémarrage de notre unique nœud par exemple).
</p>
<p>
Le second onglet "Index" permet de lister les index.
</p>
<p>
L'onglet "Navigateur" permet de consulter les données en choisissant des critères de filtres dans la colonne gauche.
</p>
<p>
Les onglets "Recherche structurée" et "Autres requêtes" permettent de faire des requêtes complexes sur le cluster.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Depuis l'onglet "Aperçu", cliquer sur le bouton "Action" sous le nom de l'index <code>logstash-2015.01.04</code> et choisir "Effacer...".
</p>
<p>
<img src="images/es_effacer.png" />
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrer le texte "SUPPRIMER" dans la pop-up puis cliquer sur OK pour confirmer. Une nouvelle pop-up devrait indiquer <code>{"acknowledged":true}</code> quasiment instantanément, et l'index disparaître de l'écran après avoir cliqué sur OK.
</p>
<p>
Nous pouvons maintenant indiquer à ElasticSearch comment interpréter les futures données de type <code>logs</code> que nous allons lui envoyer. Pour cela, nous allons rédiger un <em>template</em>. C'est un élément de configuration qui contient, pour un type donné, quels champs il contient et comment les interpréter. Ce <em>template</em> sera automatiquement transformé en <em>mapping</em> lors de la création d'un index dont le nom correspondra au champ d'action du <em>template</em>.
</p>
<p>
Pour faire cela, le programme cURL est le plus pratique.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Taper la commande suivante (attention aux sauts de lignes intempestifs !) :
<pre>
curl -XPUT "http://localhost:9200/_template/logs_template" -d '
{
"template" : "logstash-*",
"mappings" : {
"logs" : {
"properties" : {
"timestamp" : { "type" : "date", "format" : "dd/MMM/YYYY:HH:mm:ss Z" }
}
}
}
}'
</pre>
</p>
<p>
La réponse à la commande doit être : <code>{"acknowledged":true}</code>
</p>
<div class="alert alert-info" role="alert">
Pour simplifier, nous ne définissons que le champ qui posait problème. Pour fiabiliser au maximum l'indexation, il est préférable dans un cas réel de définir l'ensemble des champs pour éviter qu'une valeur "bizarre" ne vienne perturber la configuration automatique des types et empêcher de futures indexations.
</div>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Ensuite, supprimer le fichier <code>.sincedb_*</code> et redémarrer LogStash.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Rafraîchir la page de Head jusqu'à voir apparaître l'index avec le même nom que précédemment.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Vérifier le type du champ <code>timestamp</code> avec l'URL suivante : <a href="http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/timestamp">http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/timestamp</a>. On voit qu'il est bien considéré comme une date avec le format indiqué.
<pre>
{
"logstash-2015.01.04":{
"mappings":{
"logs":{
"timestamp":{
"full_name":"timestamp",
"mapping":{
"timestamp":{
"type":"date",
"format":"dd/MMM/YYYY:HH:mm:ss Z"
}
}
}
}
}
}
}
</pre>
</p>
<p>
Nous avons vu comment paramétrer l'indexation d'ElasticSearch, et comment gérer son contenu via Head. Pour préparer l'étape suivante avec Kibana, nous allons maintenant lui faire indexer une volumétrie plus importante de données.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour cela, enregistrer le <a href="./files/logstash-tutorial-dataset.log">fichier suivant</a> sur votre disque.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Ouvrir le fichier <code>logstash.conf</code> et modifier le début pour lire le nouveau fichier au lieu de l'ancien.
<pre>
input {
# On définit ici les sources de données à traiter
file {
path => "/home/elk/atelier/logstash-tutorial-dataset.log"
start_position => beginning
# on customise le sincedb_path et le nom du fichier
sincedb_path => "/home/elk/atelier/sincedb/sincedb_apache.log"
}
}
</pre>
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Supprimer l'index dans ElasticSearch via Head ou cURL.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Supprimer le fichier <code>.sincedb_*</code> et redémarrer LogStash.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Rafraîchir la page de Head jusqu'à voir réapparaître l'index. Il doit contenir maintenant 100 documents et non plus 1 seul.
</p>
<p>
<img src="images/es_100.png" />
</p>
<h2 id="kibana">Étape 3 : Kibana</h2>
<p>
Dans cette étape, nous allons voir comment Kibana permet de rechercher facilement dans ElasticSearch, et nous créerons un dashboard.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Si Kibana est arrêté, lancez le avec la commande :
<pre>
$ /home/elk/atelier/kibana-4.3.0-linux-x64/bin/kibana
</pre>
Une fois la trace suivante obtenue dans la console :
<pre>
log [14:11:46.405] [info][listening] Server running at http://0.0.0.0:5601
</pre>
<span class="glyphicon glyphicon-hand-right" ></span>
Vérifier que Kibana répond à l'URL <a href="http://localhost:5601/">http://localhost:5601/</a>.
</p>
<p>
Au premier lancement de Kibana, il nous demande quelques éléments de configuration de manière à savoir quelles données contenues dans ElasticSearch il va devoir traiter.
</p>
<div class="alert alert-warning" role="alert">
<p>
<strong>Attention</strong>
</p>
<p>
Si votre cluster ElasticSearch contient un grand nombre de templates utilisés dans tous vos index (plus de 50 templates et plus de 40 index par exemple) et que sur vos nœuds la Heap disponible pour ElasticSearch est plutôt faible : l'affichage de cet écran déclenche un parsing dans Elasticsearch de tous les mappings de tous les index. Cette requête peut utiliser jusqu'à 2Go de mémoire dans ElasticSearch avec ~2000 mappings, et provoquer la mort du cluster si la mémoire disponible est insuffisante ! Cette anomalie est en cours de correction côté ElasticSearch comme côté Kibana...
</p>
</div>
<p>
Nous souhaitons traiter des données basées sur le temps (<em>time-based events</em>), situés dans des index (un seul index en fait) dont le nom commence par <code>logstash-*</code> et dont le champ normalisé contenant le timestamp est <code>@timestamp</code> (rappelez vous précédemment, nous avions configuré tout le nécessaire dans Logstash pour que ce champ soit correctement positionné).
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Remplissez les champs de l'écran de configuration de la manière suivante :
<img src="./images/kibana_settings.png" />
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquez ensuite sur <code>Create</code>.
</p>
<p>
Un second écran nous affiche la configuration dans ElasticSearch de tous les champs connus dans les index correspondant au pattern <code>logstash-*</code> configuré précédemment. Il est possible de changer l'affichage des champs dans les dashboards pour une meilleure lisibilité en cliquant sur le petit icône en forme de stylo en face de chaque ligne. Nous allons pour notre part garder la configuration par défaut des champs.
</p>
<p>
Outre la configuration des index à scruter que nous avons déjà ajustée, Kibana offre de nombreuses options de configuration via les écrans de Settings. L'avertissement sur la page Advanced est clair : <em>Caution: You can break stuff here</em>. Nous allons donc conserver les paramètres par défaut.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquez sur le menu <code>Discover</code> pour commencer à parcourir les données.
</p>
<p>
Pour l'instant, il ne trouve aucun résultat car par défaut il cherche des données datées des dernières 15 minutes et nos données de test datent du 04 janvier 2015.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour changer cela et voir nos données, cliquer sur l'icône en forme de pendule en haut à droite de l'écran, sur le menu <code>Absolute</code> en choisir en date de début <code>2015-01-04 06:00:00.000</code> et en date de fin <code>2015-01-04 07:00:00.000</code>.
</p>
<p>
<img src="./images/kibana_time_picker.png" />
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquer sur <code>Go</code>.
</p>
<p>
Cette fois, nous avons enfin des données ! Kibana nous affiche par défaut, la répartition des valeurs de <code>@timestamp</code> par minute sur la plage de dates sélectionnée. Cela peut déjà être très utile pour monitorer l'activité d'un site Web, puisque nos données de test sont des logs Apache.
</p>
<p>
La barre de recherche en haut de l'écran contient par défaut le texte <code>*</code>, qui signifie "tous les documents".
</p>
<p>
Dans la colonne à gauche de l'écran, Kibana affiche tous les champs disponible dans les données retournées par la recherche, ainsi qu'une répartition des valeurs de chaque champ. Ainsi, si on clique sur le champ <code>response</code>, on voit que 98% des requêtes ont retourné un code 200, 1% un code 404 et 1% un code 304...
</p>
<p>
<img src="./images/kibana_fields_facets.png" />
</p>
<p>
Pour affiner cette recherche, il est possible de rechercher un mot ou un ensemble de mots.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrez <code>*.html</code> et validez pour ne voir que les logs faisant référence à l'accès à des ressources HTML.
</p>
<p>
Vous pouvez tester d'autres recherches, sur les codes retour ou le contenu des URL par exemple.
</p>
<p>
Nous voulons maintenant rechercher tous les logs venant d'un client dont l'IP commence par <code>86.1</code>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrez donc <code>86.1</code> dans le champ de recherche et validez : Aucun résultat ?!
</p>
<p>
Essayons alors de préciser le champ sur lequel nous voulons rechercher. Il s'agit du champ <code>clientip</code>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrez donc <code>clientip:86.1</code> et validez... Pas mieux :(
</p>
<p>
C'est en fait ici que les paramètres d'indexation définis dans le template Elasticsearch à l'étape précédente entrent en jeu. Nous n'avons pas configuré explicitement ce champ, ElasticSearch a donc fait des déductions pour déterminer son format et la manière de l'indexer. Si nous demandons cette configuration à ElasticSearch via l'URL <a href="http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/clientip" >http://localhost:9200/logstash-2015.01.04/_mapping/logs/field/clientip</a> nous avons le résultat suivant :
<pre>
{
"logstash-2015.01.04": {
"mappings":{
"logs":{
"clientip":{
"full_name":"clientip",
"mapping":{
"clientip":{
"type":"string",
"norms":{
"enabled":false
},
"fielddata":{
"format":"disabled"
},
"fields":{
"raw":{
"type":"string",
"index":"not_analyzed",
"ignore_above":256
}
}
}
}
}
}
}
}
}
</pre>
Les parties qui nous intéressent sont : <code>"type":"string"</code> et <code>"index":"not_analyzed"</code>. Le champ est une chaîne de caractères et n'est pas analysé lors de l'indexation. Cela signifie qu'ElasticSearch va indexer la valeur exacte du champ, et lors de la recherche, si on ne lui précise pas que l'on recherche une partie seulement de cette valeur, il va chercher la valeur exacte. Ce qui explique que nous n'ayons aucun résultat avec notre recherche <code>clientip:86.1</code>.
</p>
<p>
Nous allons donc indiquer à ElasticSearch que la chaîne entrée doit correspondre au début de la valeur du champ, mais que le champ peut contenir d'autres caractères à la suite en ajoutant une étoile à la fin de la recherche.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Entrez la recherche : <code>clientip:86.1*</code> et validez.
</p>
<p>
Nous obtenons maintenant bien 3 résultats, correspondant à 3 requêtes faites depuis l'IP 86.1.76.62.
</p>
<p>
L'affichage du tableau des résultats est pour l'instant un peu brut : il affiche uniquement le timestamp et le contenu brut du document tel que stocké dans ElasticSearch. Ce n'est pas très lisible, nous allons donc configurer Kibana pour afficher les informations les plus intéressantes.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Dans la colonne à gauche de l'écran pointer la souris sans cliquer sur le champ <code>response</code> et cliquer sur le petit bouton <code>add</code> qui s'affiche.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Répéter l'opération avec les champs <code>clientip</code> et <code>request</code>. Le tableau est déjà bien plus lisible !
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
A vous maintenant de sélectionner d'autres champs, d'en supprimer, de changer l'ordre des colonnes dans le tableau puis de trier le tableau selon l'une ou l'autre des colonnes. Essayez aussi de cliquer sur certaines zones du graphique pour voir leur effet...
</p>
<img src="./images/kibana_discover_final.png" />
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquez sur l'onglet <code>Visualize</code>.
</p>
<p>
Cet onglet permet de créer des graphiques à partir des données retournées par la recherche. Par exemple, nous pouvons reproduire le graphique du nombre de documents par minute vu sur la page précédente.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Configurer les options de graphique dans la colonne de gauche comme sur la capture d'écran ci-dessous et cliquer sur la flèche verte en haut pour afficher le graphique :
</p>
<img src="./images/kibana_visualize.png" />
<p>
De nombreuses autres visualisations sont disponible, outre l'affichage en histogramme que nous avons vu. Pour les découvrir, cliquer sur l'icône <img src="./images/kibana_new_viz.png" /> à côté de la barre de recherche. Les autres icônes permettent également de sauvegarder une visualisation, d'en charger une sauvegardée précédemment et de les partager.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
N'oubliez pas de sauvegarder 2 ou 3 visualisations pour pouvoir les utiliser dans la suite du tutoriel...
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquez sur l'onglet <code>Dashboard</code>
</p>
<p>
Il permet de configurer des tableaux de bords et de les sauvegarder.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Créez un nouveau Dashboard comme indiqué en choisissant différentes recherches et visualisations enregistrées précédemment, et observez comme il évolue en fonction des recherches que vous faites.
</p>
<p>
Un exemple d'un dashboard affichant la répartition des codes retour par valeur, la répartition des timestamp par minute et la répartition de la taille des réponses regroupées par 10k bytes :
<img src="./images/kibana_dashboard.png" />
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Pour reproduire ce dashboard, vous pouvez télécharger <a href="./files/kibana_conf.json">le fichier d'export Kibana suivant</a>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Dans Kibana, cliquez sur le menu <code>Settings</code> puis <code>Objects</code>.
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Cliquez sur le bouton <code>Import</code>, choisissez le fichier JSON que vous venez de télécharger. Validez. Vous devriez voir apparaître un dashboard nommé <code>dashboard 1</code> dans la liste des dashboards, ainsi que 4 visualisations.
</p>
<p>
<img src="./images/kibana_import.png" />
</p>
<p>
<span class="glyphicon glyphicon-hand-right" ></span>
Rendez-vous ensuite dans l'onglet Dashboards. Cliquez sur le bouton <code>Load saved dashboard</code>. Choisissez le dashboard <code>dashboard 1</code>.
</p>
<div class="alert alert-warning" role="alert">
<p>Les visualisations, et a fortiori les dashboards, utilisent une fonctionnalité d'ElasticSearch nommé Agrégations. Cela permet de réaliser en une seule requête, et en profitant des performances et de la distribution des recherches, d'obtenir à la fois les documents correspondant à la recherche et un certain nombre de valeurs calculées à partir du contenu de ces documents : nombre d'occurrence de chaque valeur différente, répartition des valeurs dans le temps, etc.
</p>
<p>
Cette fonctionnalité est très efficace et très pratique dans de nombreux cas, mais elle implique par contre une charge CPU importante sur le cluster ainsi que la mise en mémoire de nombreuses données. Si ce second point a été largement amélioré dans la version 2.*, la charge CPU reste importante et l'exécution simultanée par plusieurs utilisateurs d'agrégations complexes sur une grande volumétrie de documents (plusieurs centaines de milliers) peut ralentir globalement le cluster.
</p>
</div>
<div class="alert alert-success" role="alert">
Vous voilà arrivés à la fin du tutoriel. Vous pouvez continuer à lire pour aller plus loin...
</div>
<h2 id="further">Pour aller plus loin...</h2>
<p>
Nous avons vu une infrastructure simple d'exploitation de logs. Elle souffre de nombreux points faibles : goulets d'étranglements de performances, vulnérabilité aux pannes, impact sur la charge des machines monitorées... Ces points peuvent être résolus par l'ajout de nouvelles briques et l'utilisation de clusters.
</p>
<h3 id="broker">Utilisation d'un agent et d'un broker de messages</h3>
<p>
Deux problèmes principaux sont présents dans l'architecture telle que mise en place dans ce tutoriel : le traitement des messages avec Grok peut être très gourmand en CPU, et le fait de pousser directement dans ElasticSearch implique un couplage fort entre les deux briques.
</p>
<p>
Ces deux points peuvent être problématiques en production : La charge induite par LogStash peut perturber les applications supervisées, et toute indisponibilité d'ElasticSearch peut impacter le fonctionnement de l'agent.
</p>
<p>
Pour résoudre cela, il est préférable d'installer LogStash sur les machines supervisées avec une configuration minimale, qui ne fait que lire les fichiers de logs, gérer le multi-ligne si nécessaire, et pousser les messages dans la suite de l'infrastructure, elle située sur des machines dédiées. Le traitement poussé des messages (Grok notamment) sera réalisé par une ou plusieurs autres instances de LogStash (voir le paragraphe "Clustering" plus loin) sur ces machines dédiées, et se chargera de pousser dans ElasticSearch.
</p>
<p>
Parce qu'un petit schéma vaut souvent mieux qu'un long discours, l'architecture cible est donc la suivante :
<img src="./images/archi_rabbitmq.png"/>
</p>
<p>
Pour communiquer entre le LogStash "agent" installé sur les machines clientes et le LogStash "serveur" réalisant les traitements, il faut insérer entre eux deux une brique intermédiaire de "broker de messages". Plusieurs technologies peuvent être utilisées pour cela : Redis, RabbitMQ, Apache Kafka... Chacune de ces solutions ont leurs avantages et leurs inconvénients, à adapter à chaque cas d'usage. RabbitMQ offrant une bonne simplicité de mise en œuvre et une interface d'administration pratiques dans le cadre de ce tutoriel, c'est ce que nous allons utiliser.
</p>
<div class="alert alert-info" role="alert">
Dans le cadre de ce tutoriel, il est possible de démarrer 2 fois LogStash sur votre PC en même temps, en précisant 2 fichiers de configuration différents pour la partie "agent" et la partie "serveur".
</div>
<div class="alert alert-info" role="alert">
RabbitMQ utilise des concepts spécifiques nommés <em>exchange</em>, <em>queue</em> et <em>routage</em>. Il est conseillé de lire <a href="https://www.rabbitmq.com/tutorials/amqp-concepts.html" >la documentation (en anglais)</a> de l'outil avant de commencer cette étape du tutoriel pour comprendre les objets à configurer.
</div>
<p>
Dans cette partie, vous pouvez donc :
<ol>
<li>Démarrer un serveur RabbitMQ sur votre poste via la commande (en <code>root</code>) :
<pre># service rabbitmq-server start</pre>
</li>
<li>Activer l'interface graphique d'administration via la commande (en <code>root</code>) :
<pre># rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
mochiweb
webmachine
rabbitmq_web_dispatch
amqp_client
rabbitmq_management_agent
rabbitmq_management
Applying plugin configuration to rabbit@claire-i5... started 6 plugins.
</pre>
</li>
<li>
Accéder à l'interface à l'adresse <a href="http://localhost:15672/">http://localhost:15672/</a>, se connecter avec le nom d'utilisateur <code>guest</code> et le mot de passe <code>guest</code> et configurer un exchange, une queue et un routage dans RabbitMQ (voir plus haut l'explication de ces concepts) via les écrans dédiés à chaque concept.<br/>