name: logging # Centralized logging - We want to send all our container logs to a central place - If that place could offer a nice web dashboard too, that'd be nice -- - We are going to deploy an ELK stack - It will accept logs over a GELF socket - We will update our services to send logs through the GELF logging driver --- # Setting up ELK to store container logs *Important foreword: this is not an "official" or "recommended" setup; it is just an example. We used ELK in this demo because it's a popular setup and we keep being asked about it; but you will have equal success with Fluent or other logging stacks!* What we will do: - Spin up an ELK stack with services - Gaze at the spiffy Kibana web UI - Manually send a few log entries using one-shot containers - Set our containers up to send their logs to Logstash --- ## What's in an ELK stack? - ELK is three components: - ElasticSearch (to store and index log entries) - Logstash (to receive log entries from various sources, process them, and forward them to various destinations) - Kibana (to view/search log entries with a nice UI) - The only component that we will configure is Logstash - We will accept log entries using the GELF protocol - Log entries will be stored in ElasticSearch,
and displayed on Logstash's stdout for debugging --- class: elk-manual ## Setting up ELK - We need three containers: ElasticSearch, Logstash, Kibana - We will place them on a common network, `logging` .exercise[ - Create the network: ```bash docker network create --driver overlay logging ``` - Create the ElasticSearch service: ```bash docker service create --network logging --name elasticsearch elasticsearch:2.4 ``` ] --- class: elk-manual ## Setting up Kibana - Kibana exposes the web UI - Its default port (5601) needs to be published - It needs a tiny bit of configuration: the address of the ElasticSearch service - We don't want Kibana logs to show up in Kibana (it would create clutter)
so we tell Logspout to ignore them .exercise[ - Create the Kibana service: ```bash docker service create --network logging --name kibana --publish 5601:5601 \ -e ELASTICSEARCH_URL=http://elasticsearch:9200 kibana:4.6 ``` ] --- class: elk-manual ## Setting up Logstash - Logstash needs some configuration to listen to GELF messages and send them to ElasticSearch - We could author a custom image bundling this configuration - We can also pass the [configuration](https://github.com/jpetazzo/orchestration-workshop/blob/master/elk/logstash.conf) on the command line .exercise[ - Create the Logstash service: ```bash docker service create --network logging --name logstash -p 12201:12201/udp \ logstash:2.4 -e "$(cat ~/orchestration-workshop/elk/logstash.conf)" ``` ] --- class: elk-manual ## Checking Logstash - Before proceeding, let's make sure that Logstash started properly .exercise[ - Lookup the node running the Logstash container: ```bash docker service ps logstash ``` - Connect to that node ] --- class: elk-manual ## View Logstash logs .exercise[ - Get the ID of the Logstash container: ```bash CID=$(docker ps -q --filter label=com.docker.swarm.service.name=logstash) ``` - View the logs: ```bash docker logs --follow $CID ``` ] You should see the heartbeat messages: .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] --- class: elk-auto ## Deploying our ELK cluster - We will use a stack file .exercise[ - Build, ship, and run our ELK stack: ```bash docker-compose -f elk.yml build docker-compose -f elk.yml push docker stack deploy elk -c elk.yml ``` ] Note: the *build* and *push* steps are not strictly necessary, but they don't hurt! Let's have a look at the [Compose file]( https://github.com/jpetazzo/orchestration-workshop/blob/master/stacks/elk.yml). --- class: elk-auto ## Checking that our ELK stack works correctly - Let's view the logs of logstash (Who logs the loggers?) .exercise[ - Stream logstash's logs: ```bash docker service logs --follow --tail 1 elk_logstash ``` ] You should see the heartbeat messages: .small[ ```json { "message" => "ok", "host" => "1a4cfb063d13", "@version" => "1", "@timestamp" => "2016-06-19T00:45:45.273Z" } ``` ] --- ## Testing the GELF receiver - In a new window, we will generate a logging message - We will use a one-off container, and Docker's GELF logging driver .exercise[ - Send a test message: ```bash docker run --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 \ --rm alpine echo hello ``` ] The test message should show up in the logstash container logs. --- ## Sending logs from a service - We were sending from a "classic" container so far; let's send logs from a service instead - We're lucky: the parameters (`--log-driver` and `--log-opt`) are exactly the same! .exercise[ - Send a test message: ```bash docker service create \ --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 \ alpine echo hello ``` ] The test message should show up as well in the logstash container logs. -- In fact, *multiple messages will show up, and continue to show up every few seconds!* --- ## Restart conditions - By default, if a container exits (or is killed with `docker kill`, or runs out of memory ...), the Swarm will restart it (possibly on a different machine) - This behavior can be changed by setting the *restart condition* parameter .exercise[ - Change the restart condition so that Swarm doesn't try to restart our container forever: ```bash docker service update `xxx` --restart-condition none ``` ] Available restart conditions are `none`, `any`, and `on-error`. You can also set `--restart-delay`, `--restart-max-attempts`, and `--restart-window`. --- ## Connect to Kibana - The Kibana web UI is exposed on cluster port 5601 .exercise[ - Connect to port 5601 of your cluster - if you're using Play-With-Docker, click on the (5601) badge above the terminal - otherwise, open http://(any-node-address):5601/ with your browser ] --- ## "Configuring" Kibana - If you see a status page with a yellow item, wait a minute and reload (Kibana is probably still initializing) - Kibana should offer you to "Configure an index pattern":
in the "Time-field name" drop down, select "@timestamp", and hit the "Create" button - Then: - click "Discover" (in the top-left corner) - click "Last 15 minutes" (in the top-right corner) - click "Last 1 hour" (in the list in the middle) - click "Auto-refresh" (top-right corner) - click "5 seconds" (top-left of the list) - You should see a series of green bars (with one new green bar every minute) --- ## Updating our services to use GELF - We will now inform our Swarm to add GELF logging to all our services - This is done with the `docker service update` command - The logging flags are the same as before .exercise[ - Enable GELF logging for the `rng` service: ```bash docker service update dockercoins_rng \ --log-driver gelf --log-opt gelf-address=udp://127.0.0.1:12201 ``` ] After ~15 seconds, you should see the log messages in Kibana. --- ## Viewing container logs - Go back to Kibana - Container logs should be showing up! - We can customize the web UI to be more readable .exercise[ - In the left column, move the mouse over the following columns, and click the "Add" button that appears: - host - container_name - message ] --- ## .warning[Don't update stateful services!] - What would have happened if we had updated the Redis service? - When a service changes, SwarmKit replaces existing container with new ones - This is fine for stateless services - But if you update a stateful service, its data will be lost in the process - If we updated our Redis service, all our DockerCoins would be lost --- ## Important afterword **This is not a "production-grade" setup.** It is just an educational example. We did set up a single ElasticSearch instance and a single Logstash instance. In a production setup, you need an ElasticSearch cluster (both for capacity and availability reasons). You also need multiple Logstash instances. And if you want to withstand bursts of logs, you need some kind of message queue: Redis if you're cheap, Kafka if you want to make sure that you don't drop messages on the floor. Good luck. If you want to learn more about the GELF driver, have a look at [this blog post]( http://jpetazzo.github.io/2017/01/20/docker-logging-gelf/).