Centralised logging with Elasticsearch, Logstash, and Kibana

In this tutorial, we will go over the installation of Elasticsearch, Logstash, and Kibana, the so-called ELK stack. Logstash is an open source tool for collecting, parsing, and storing logs for future use. Kibana is a web interface that can be used to search and view the logs that Logstash has indexed. Both Logstash and Kibana rely on Elasticsearch for powerful storage and retrieval of information. The ELK combination provides an effective service that allow system administrators to consolidate logs from various parts of their information systems, and to dig into global logs visually when required.

Centralized logging can be very useful when attempting to identify problems with your servers or applications, as it allows you to search through all of your logs in a single place. It is also useful because it allows you to identify issues that span multiple servers by correlating their logs during a specific time frame.

It is possible to use Logstash to gather logs of all types, but we will limit the scope of this tutorial to syslog gathering. We will demonstrate in this tutorial how a remote server can be equipped to export logs automatically and securely to the ELK facility. In the fittings plan below, this is named logstash client.

Requirements for this use case

  • Add multiple Network Domains and Ethernet networks to support the distribution of nodes at several data centres
  • Deploy one Linux server for the ELK node, and one for each remote node
  • Add a virtual disk of 500 GB to the ELK node
  • Monitor all nodes in the real-time dashboard provided by Dimension Data
  • Assign public IPv4 addresses for ssh access over the Internet
  • Add address translation to ensure end-to-end IP connectivity
  • Add firewall rule to accept TCP traffic on port 22 (ssh)
  • Add firewall rule to allow web traffic to Kibana
  • Allow IPv6 traffic between remote nodes and the ELK node
  • Expand file system of the ELK node with added disk (LVM)
  • Install a new SSH key to secure remote communications across all nodes
  • Update etc/hosts to bind IPv6 addresses to host names
  • Manage keys to suppress passwords in SSH connections
  • Install Elasticsearch, Logstash and Kibana to the ELK node
  • Install Logstash to every other node
  • Create a private key and self-signed certificate at the ELK node to secure Logstash operations over IPv6
  • Install the certificate at every other node to secure communications from Logstash client software

Fittings plan

The plan below demonstrates multiple interesting tips and tricks:

  • Provide SSH access to all nodes via public IPv4, NAT, and firewall settings
  • Management of SSH keys to enable secured communications without passwords
  • Allow private IPv6 communications between remote nodes and the ELK node
  • Automatic registration of all nodes to the monitoring services provided by Dimension Data
  • Update of etc/hosts with IPv6
  • Remove Apache, and install Nginx instead
  • Install the full ELK stack
  • Configure Nginx as efficient and secured proxy to Kibana
  • Orchestrate generation and configuration of web password to the Kibana dashboard
  • Automate the installation of Oracle 8 JDK
  • User documentation of the infrastructure is put directly in the fittings plan

Download this fittings plan if you want to hack it for yourself. This is part of the demonstration directory of the plumbery project at GitHub. Alternatively, you can copy the text below and put it in a text file named fittings.yaml.

  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
 ---

 information:
   - "Centralised logging with Elasticsearch, Logstash, and Kibana."

 links:
   documentation: https://plumbery.readthedocs.org/en/latest/tutorial.elk.html
   credit: http://www.exonet3i.net/evernote/How%20To%20Install%20Elasticsearch,%20Logstash,%20and%20K%20%5B3%5D.html

 defaults:

   # the same network domain is used at various facilities
   #
   domain:
     name: ELKFox
     description: "Demonstration of Elasticsearch, Logstash and Kibana"
     ipv4: auto

   # the same ethernet configuration is used at various facilities
   #
   ethernet:
     name: ELKNetwork
     subnet: 10.0.0.0

   # default settings for a remote logger
   #
   logstashForwarder:

     description: "#logstash-forwarder #ubuntu"

     information:
       - "a remote server with logstash-forwarder"

     appliance: 'Ubuntu 14'

     cpu: 2
     memory: 4

     glue:
       - internet 22

     monitoring: essentials

     cloud-config:

       hostname: "{{ node.name }}"

       packages:
         - ntp

       write_files:

         - path: /root/hosts.awk
           content: |
             #!/usr/bin/awk -f
             /^{{ node.private }}/ {next}
             /^{{ node.ipv6 }}/ {next}
             /^{{ dd-au::AU10::logstashServer.ipv6 }}/ {next}
             {print}
             END {
              print "{{ node.private }}    {{ node.name }}"
              print "{{ node.ipv6 }}    {{ node.name }}"
              print "{{ dd-au::AU10::logstashServer.ipv6 }}    logstashServer"
             }

         - path: /root/logstash-forwarder.conf
           content: |
             {
               "network": {
                 "servers": [ "{{ dd-au::AU10::logstashServer.ipv6 }}:5000" ],
                 "timeout": 15,
                 "ssl ca": "/etc/logstash-pki/logstash-forwarder.crt"
               },

               "files": [
                 {
                   "paths": [
                     "/var/log/syslog",
                     "/var/log/auth.log"
                    ],
                   "fields": { "type": "syslog" }
                 }
               ]
             }

       runcmd:

         - echo "===== Handling ubuntu identity"
         - cp -n /etc/ssh/ssh_host_rsa_key /home/ubuntu/.ssh/id_rsa
         - cp -n /etc/ssh/ssh_host_rsa_key.pub /home/ubuntu/.ssh/id_rsa.pub
         - chown ubuntu:ubuntu /home/ubuntu/.ssh/*
         - sed -i "/StrictHostKeyChecking/s/^.*$/    StrictHostKeyChecking no/" /etc/ssh/ssh_config

         - echo "===== Updating /etc/hosts"
         - cp -n /etc/hosts /etc/hosts.original
         - awk -f /root/hosts.awk /etc/hosts >/etc/hosts.new && mv /etc/hosts.new /etc/hosts

         - echo "===== Installing logstash-forwarder"
         - cd /root
         - wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
         - echo "deb http://packages.elastic.co/logstashforwarder/debian stable main" | sudo tee /etc/apt/sources.list.d/logstashforwarder.list
         - apt-get update
         - apt-get install logstash-forwarder

         - echo "===== Securing logstash-forwarder"
         - sleep 1m
         - mkdir /etc/logstash-pki
         - rsync -zhave "ssh -i /home/ubuntu/.ssh/id_rsa" ubuntu@logstashServer:/var/rsync/logstash-forwarder.crt /etc/logstash-pki

         - echo "===== Configuring logstash"
         - cp -n /etc/logstash-forwarder.conf /etc/logstash-forwarder.conf.origin
         - cp /root/logstash-forwarder.conf /etc/logstash-forwarder.conf
         - service logstash-forwarder restart


   # default settings for all nodes created by plumbery
   #
   cloud-config:

     # plumbery generates a random key pair
     #
     ssh_keys:
       rsa_private: |
         {{ key.rsa_private }}
       rsa_public: "{{ key.rsa_public }}"

     users:
       - default

       - name: ubuntu
         sudo: 'ALL=(ALL) NOPASSWD:ALL'
         ssh-authorized-keys:
           - "{{ key.rsa_public }}"
           - "{{ local.rsa_public }}"

       - name: root
         ssh-authorized-keys:
           - "{{ key.rsa_public }}"
           - "{{ local.rsa_public }}"

     disable_root: false
     ssh_pwauth: false

 ---
 locationId: AU10
 regionId: dd-au

 blueprints:

   - logstash:

       ethernet:
         accept:
           - dd-ap::AP5::ELKNetwork

       nodes:

         - logstashServer:  # a combo of logstash, elasticsearch, and kibana

             description: "#logstash #elasticsearch #dashboard #kibana #ubuntu"

             information:
               - "a web dashboard to visualize logs:"
               - "http://{{ node.public }}"
               - "authenticate with 'dashboard' and '{{ dashboard.secret }}'"
               - "troubleshoot with:"
               - "$ ssh ubuntu@{{ node.public }}"
               - "check the feeding of elasticsearch with:"
               - "$ curl 'http://localhost:9200/_cat/indices?v'"
               - "validate the configuration of logstash with:"
               - "$ service logstash configtest"

             appliance: 'Ubuntu 14'

             cpu: 2
             memory: 4

             disks:
               - 1 500 standard

             glue:
               - internet 22 80

             monitoring: essentials

             cloud-config:

               hostname: "{{ node.name }}"

               bootcmd:

                 # remove apache
                 - apt-get remove apache2 -y
                 - apt-get autoremove -y

                 # automate acceptance of oracle licence
                 - echo "oracle-java8-installer shared/accepted-oracle-license-v1-1 select true" | sudo debconf-set-selections
                 - echo "oracle-java8-installer shared/accepted-oracle-license-v1-1 seen true" | sudo debconf-set-selections

               apt_sources:
                 - source: "ppa:webupd8team/java"

               packages:
                 - ntp
                 - oracle-java8-installer
                 - nginx
                 - apache2-utils
                 - python-pip

               write_files:

                 - path: /root/hosts.awk
                   content: |
                     #!/usr/bin/awk -f
                     /^{{ node.private }}/ {next}
                     /^{{ node.ipv6 }}/ {next}
                     /^{{ logstash-AU10.ipv6 }}/ {next}
                     /^{{ dd-ap::AP5::logstash-AP5.ipv6 }}/ {next}
                     {print}
                     END {
                      print "{{ node.private }}    {{ node.name }}"
                      print "{{ node.ipv6 }}    {{ node.name }}"
                      print "{{ logstash-AU10.ipv6 }}    logstash-AU10"
                      print "{{ dd-ap::AP5::logstash-AP5.ipv6 }}    logstash-AP5"
                     }

                 - path: /root/nginx-sites-available-default
                   content: |
                     server {
                         listen 80;

                         server_name {{ node.public }};

                         auth_basic "Restricted Access";
                         auth_basic_user_file /etc/nginx/htpasswd.users;

                         location / {
                             proxy_pass http://localhost:5601;
                             proxy_http_version 1.1;
                             proxy_set_header Upgrade $http_upgrade;
                             proxy_set_header Connection 'upgrade';
                             proxy_set_header Host $host;
                             proxy_cache_bypass $http_upgrade;
                         }
                     }

                 - path: /root/logstash-conf.d-01-lumberjack-input.conf
                   content: |
                     input {
                       lumberjack {
                         port => 5000
                         type => "logs"
                         ssl_certificate => "/etc/pki/tls/certs/logstash-forwarder.crt"
                         ssl_key => "/etc/pki/tls/private/logstash-forwarder.key"
                       }
                     }

                 - path: /root/logstash-conf.d-10-syslog.conf
                   content: |
                     filter {
                       if [type] == "syslog" {
                         grok {
                           match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
                           add_field => [ "received_at", "%{@timestamp}" ]
                           add_field => [ "received_from", "%{host}" ]
                         }
                         syslog_pri { }
                         date {
                           match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
                         }
                       }
                     }

                 - path: /root/logstash-conf.d-30-lumberjack-output.conf
                   content: |
                     output {
                       elasticsearch { hosts => localhost }
                       stdout { codec => rubydebug }
                     }

                 - path: /root/etc-cron.d-elasticsearch_curator
                   content: |
                     @midnight     root        curator delete --older-than 120 >> /var/log/curator.log 2>&1

               runcmd:

                 - echo "===== Growing LVM with added disk"
                 - pvcreate /dev/sdb
                 - vgextend rootvol00 /dev/sdb
                 - lvextend -l +100%FREE /dev/mapper/rootvol00-rootlvol00
                 - resize2fs /dev/mapper/rootvol00-rootlvol00

                 - echo "===== Handling ubuntu identity"
                 - cp -n /etc/ssh/ssh_host_rsa_key /home/ubuntu/.ssh/id_rsa
                 - cp -n /etc/ssh/ssh_host_rsa_key.pub /home/ubuntu/.ssh/id_rsa.pub
                 - chown ubuntu:ubuntu /home/ubuntu/.ssh/*
                 - sed -i "/StrictHostKeyChecking/s/^.*$/    StrictHostKeyChecking no/" /etc/ssh/ssh_config

                 - echo "===== Updating /etc/hosts"
                 - cp -n /etc/hosts /etc/hosts.original
                 - awk -f /root/hosts.awk /etc/hosts >/etc/hosts.new && mv /etc/hosts.new /etc/hosts

                 - echo "===== Installing logstash, elasticsearch, kibana"
                 - cd /root
                 - wget -qO - https://packages.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
                 - echo "deb http://packages.elastic.co/elasticsearch/2.x/debian stable main" | sudo tee -a /etc/apt/sources.list.d/elasticsearch-2.x.list
                 - echo "deb http://packages.elastic.co/kibana/4.4/debian stable main" | sudo tee -a /etc/apt/sources.list.d/kibana-4.4.x.list
                 - echo "deb http://packages.elastic.co/logstash/2.2/debian stable main" | sudo tee -a /etc/apt/sources.list.d/logstash-2.2.x.list
                 - apt-get update
                 - apt-get install logstash elasticsearch kibana
                 - update-rc.d elasticsearch defaults 95 10
                 - service elasticsearch start
                 - update-rc.d kibana defaults 96 9
                 - service kibana start

                 - echo "===== Securing logstash"
                 - cp -n /etc/ssl/openssl.cnf /etc/ssl/openssl.cnf.origin
                 - sed -i "/# subjectAltName=email:copy/s/^.*$/subjectAltName = IP:{{ node.ipv6 }}/" /etc/ssl/openssl.cnf
                 - mkdir /etc/logstash-pki
                 - cd /etc/logstash-pki
                 - openssl req -config /etc/ssl/openssl.cnf -x509 -days 3650 -batch -nodes -newkey rsa:2048 -keyout logstash-forwarder.key -out logstash-forwarder.crt
                 - openssl x509 -in logstash-forwarder.crt -text -noout

                 - echo "===== Sharing certificate for remote access"
                 - mkdir /var/rsync
                 - cp /etc/logstash-pki/logstash-forwarder.crt /var/rsync
                 - chown -R ubuntu:ubuntu /var/rsync

                 - echo "===== Configuring logstash"
                 - cp /root/logstash-conf.d-01-lumberjack-input.conf /etc/logstash/conf.d/01-lumberjack-input.conf
                 - cp /root/logstash-conf.d-10-syslog.conf /etc/logstash/conf.d/10-syslog.conf
                 - cp /root/logstash-conf.d-30-lumberjack-output.conf /etc/logstash/conf.d/30-lumberjack-output.conf
                 - service logstash restart

                 - echo "===== Securing web access to Kibana"
                 - cp -n /etc/nginx/sites-available/default /etc/nginx/sites-available/default.original
                 - cp /root/nginx-sites-available-default /etc/nginx/sites-available/default
                 - htpasswd -cb /etc/nginx/htpasswd.users dashboard {{ dashboard.secret }}
                 - service nginx restart

                 - echo "===== Installing Curator to purge old logs"
                 - pip install elasticsearch-curator
                 - cp /root/etc-cron.d-elasticsearch_curator /etc/cron.d/elasticsearch_curator


         - logstash-AU10:
             default: logstashForwarder

 ---
 locationId: AP5
 regionId: dd-ap

 blueprints:

   - bees:

       ethernet:
         accept:
           - dd-au::AU10::ELKNetwork

       nodes:
         - logstash-AP5:
             default: logstashForwarder

Deployment commands

For this tutorial, plumbery has to connect separately to multiple data centres and to apply several changes in multiple waves.

$ python -m plumbery fittings.yaml deploy

This command will build fittings as per the provided plan, and start servers as well. Look at messages displayed by plumbery while it is working, so you can monitor what’s happening.

Follow-up commands

At the end of the deployment, plumbery will display on screen some instructions to help you move forward. You can ask plumbery to display this information at any time with the following command:

$ python -m plumbery fittings.yaml information

What’s coming next? You may want to connect to the ELK node in ssh and check the stream of records coming from remote nodes via Logstash.

$ ssh ubuntu@<IPv4 of ELK node>
$ curl 'http://localhost:9200/_cat/indices?v'

Repeat the command multiple times and check the increment of documents indexed by Elasticsearch.

If everything is looking fine at this stage, then you are allowed to move to the configuration of the Kibana interactive dashboard. In a browser window, type the public IPv4 address of the ELK node. When asked for it, provide the name and the password that were mentioned by plumbery during the deployment of the fittings plan.

From there you can configure the dashboard as per your very specific needs.

Destruction commands

Launch following command to remove all resources involved in the fittings plan:

$ python -m plumbery fittings.yaml dispose