Skip to content

Commit 6f094cf

Browse files
committed
[DOCS] APM Example: Adds APM integration example
1 parent 878c935 commit 6f094cf

File tree

11 files changed

+1344
-0
lines changed

11 files changed

+1344
-0
lines changed

examples/apm/Gemfile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
source 'https://rubygems.org' do
2+
gem 'elastic-apm'
3+
gem 'elasticsearch'
4+
gem 'sinatra'
5+
end

examples/apm/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Observability Example
2+
3+
This example provides a `docker-compose` file based on the [Quick start development environment](https://www.elastic.co/guide/en/apm/get-started/current/quick-start-overview.html) for APM. It gets the default distributions of Elasticsearch, Kibana and APM Server up and running in Docker.
4+
5+
The docker-compose file also includes a basic Sinatra App and a script to ping the web app with different endpoints.
6+
7+
Run `docker-compose up` on the root folder of this example and you'll get everything set up. Follow the steps on the full documentation [at elastic.co](https://www.elastic.co/guide/en/apm/get-started/current/quick-start-overview.html) to get APM set up in your Kibana instance.
8+
9+
[Install Docker Compose](https://docs.docker.com/compose/install/), cd into this directory and run it:
10+
11+
```bash
12+
$ cd docs/examples/apm
13+
$ docker-compose up
14+
```
15+
16+
The following services will be available:
17+
18+
- Kibana: http: //localhost:5061
19+
- Elasticsearch: http: //localhost:9200
20+
- APM Server: http: //localhost:8200
21+
- Example Sinatra app: http: //localhost:9292
22+
23+
Use your web browser or `curl` against http://localhost:9292/ to check that everything is working. You should see a JSON response from `cluster.health`.
24+
25+
The docker-compose file will also run a `pinger` script. This script will make requests irregularly to the web app to simulate proper traffic and fill your APM Dashboard with data, even errors. You can comment the pinger container from `docker-compose.yml` if you want to have all the services running and test the different endpoints (or add your own) by yourself.
26+
27+
# Screenshot
28+
29+
![Kibana APM Dashboard](screenshot.jpg)
30+
31+
# Routes in the example Sinatra app
32+
33+
Once the app is running, you can open the following routes in your web browser or via `curl`. The responses are in JSON:
34+
35+
* `/` - The root path returns the response from `cluster.health`
36+
* `/ingest` - This will bulk insert 1,000 documents in the `games` index in slices of 250 at a time.
37+
* `/search/{param}` - Returns search results for `param`.
38+
* `/error` - This route will trigger an error.
39+
* `/delete` - This route will delete all the data in the `games` index.
40+
* `/delete/{id}` - This route will delete a document with the given id from the `games` index.
41+
* `/update/` - This route will update the `modified` field on some docs in the `games` index.
42+
* `/doc/{id}` - This route will return a document with a given ID from Elasticsearch.
43+
44+
# Data Source
45+
46+
Data is based on a DB dump from February 25, 2020 of [TheGamesDB](https://thegamesdb.net/) game data:
47+
https://cdn.thegamesdb.net/json/database-latest.json

examples/apm/config.ru

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
require 'elastic-apm'
2+
require_relative './elastic_sinatra_app.rb'
3+
4+
ElasticAPM.start(app: ElasticSinatraApp)
5+
6+
run ElasticSinatraApp
7+
8+
at_exit { ElasticAPM.stop }

examples/apm/config/elastic_apm.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Set service name - allowed characters: a-z, A-Z, 0-9, -, _ and space
2+
# Defaults to the name of your Rack app's class.
3+
service_name: 'ruby-client-apm'
4+
5+
# Use if APM Server requires a token
6+
# secret_token: ''
7+
8+
# Set custom APM Server URL (default: http://localhost:8200)
9+
# In this example, we're using the URL from the docker-compose apm-server instance
10+
server_url: 'http://apm-server:8200'

examples/apm/docker-compose.yml

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
version: '2.2'
2+
services:
3+
apm-server:
4+
image: docker.elastic.co/apm/apm-server:7.6.2
5+
depends_on:
6+
elasticsearch:
7+
condition: service_healthy
8+
kibana:
9+
condition: service_healthy
10+
cap_add: ["CHOWN", "DAC_OVERRIDE", "SETGID", "SETUID"]
11+
cap_drop: ["ALL"]
12+
ports:
13+
- 8200:8200
14+
networks:
15+
- elastic
16+
command: >
17+
apm-server -e
18+
-E apm-server.rum.enabled=true
19+
-E setup.kibana.host=kibana:5601
20+
-E setup.template.settings.index.number_of_replicas=0
21+
-E apm-server.kibana.enabled=true
22+
-E apm-server.kibana.host=kibana:5601
23+
-E output.elasticsearch.hosts=["elasticsearch:9200"]
24+
healthcheck:
25+
interval: 30s
26+
retries: 12
27+
test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:8200/
28+
29+
elasticsearch:
30+
image: docker.elastic.co/elasticsearch/elasticsearch:7.6.2
31+
environment:
32+
- bootstrap.memory_lock=true
33+
- cluster.name=docker-cluster
34+
- cluster.routing.allocation.disk.threshold_enabled=false
35+
- discovery.type=single-node
36+
- ES_JAVA_OPTS=-XX:UseAVX=2 -Xms1g -Xmx1g
37+
ulimits:
38+
memlock:
39+
hard: -1
40+
soft: -1
41+
volumes:
42+
- esdata:/usr/share/elasticsearch/data
43+
ports:
44+
- 9200:9200
45+
networks:
46+
- elastic
47+
healthcheck:
48+
interval: 30s
49+
retries: 10
50+
test: curl -s http://localhost:9200/_cluster/health | grep -vq '"status":"red"'
51+
52+
kibana:
53+
image: docker.elastic.co/kibana/kibana:7.6.2
54+
depends_on:
55+
elasticsearch:
56+
condition: service_healthy
57+
environment:
58+
ELASTICSEARCH_URL: http://elasticsearch:9200
59+
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
60+
ports:
61+
- 5601:5601
62+
networks:
63+
- elastic
64+
healthcheck:
65+
interval: 30s
66+
retries: 20
67+
test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:5601/api/status
68+
69+
ruby_app:
70+
container_name: ruby_app
71+
build:
72+
dockerfile: ${PWD}/dockerfiles/ruby/Dockerfile
73+
context: ${PWD}
74+
depends_on:
75+
- apm-server
76+
environment:
77+
ELASTICSEARCH_HOSTS: http://elasticsearch:9200
78+
ports:
79+
- 9292:9292
80+
networks:
81+
- elastic
82+
healthcheck:
83+
interval: 30s
84+
retries: 20
85+
test: curl --write-out 'HTTP %{http_code}' --fail --silent --output /dev/null http://localhost:9292/
86+
87+
pinger:
88+
container_name: pinger
89+
build:
90+
dockerfile: ${PWD}/dockerfiles/pinger/Dockerfile
91+
context: ${PWD}
92+
depends_on:
93+
- ruby_app
94+
networks:
95+
- elastic
96+
97+
volumes:
98+
esdata:
99+
driver: local
100+
101+
networks:
102+
elastic:
103+
driver: bridge
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
From ruby:2.7
2+
3+
WORKDIR /usr/src/app
4+
5+
COPY ./pinger.rb .
6+
7+
CMD ruby pinger.rb
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
From ruby:2.7
2+
3+
RUN bundle config --global frozen 1
4+
5+
WORKDIR /usr/src/app
6+
7+
COPY . .
8+
RUN bundle install
9+
10+
EXPOSE 9292
11+
12+
CMD RACK_ENV=production bundle exec rackup -p 9292 --host 0.0.0.0 config.ru

examples/apm/elastic_sinatra_app.rb

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
require 'sinatra'
2+
require 'json'
3+
require 'elasticsearch'
4+
require_relative './games_data.rb'
5+
6+
class ElasticSinatraApp < Sinatra::Base
7+
use ElasticAPM::Middleware
8+
9+
before do
10+
@client = Elasticsearch::Client.new(
11+
hosts: ENV['ELASTICSEARCH_HOSTS'] || 'localhost:9200'
12+
)
13+
end
14+
15+
get '/' do
16+
response = @client.cluster.health
17+
json_response(response)
18+
end
19+
20+
get '/ingest' do
21+
unless @client.indices.exists(index: 'games')
22+
@client.indices.create(index: 'games')
23+
end
24+
25+
ElasticAPMExample::GAMES.each_slice(250) do |slice|
26+
@client.bulk(
27+
body: slice.map do |game|
28+
{ index: { _index: 'games', data: game } }
29+
end
30+
)
31+
end
32+
json_response(status: 'ok')
33+
end
34+
35+
get '/search/:query' do
36+
query = sanitize(params[:query])
37+
response = search_elasticsearch(query)
38+
json_response(response['hits']['hits'])
39+
end
40+
41+
get '/delete' do
42+
response = @client.delete_by_query(
43+
index: 'games',
44+
body: {
45+
query: { match_all: {} }
46+
}
47+
)
48+
json_response(response)
49+
end
50+
51+
get '/delete/:id' do
52+
id = sanitize(params[:id])
53+
54+
begin
55+
response = @client.delete(index: 'games', id: id)
56+
json_response(response)
57+
rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
58+
json_response(e, 404)
59+
end
60+
end
61+
62+
get '/update' do
63+
response = []
64+
docs = search_elasticsearch
65+
66+
docs['hits']['hits'].each do |doc|
67+
response << @client.update(
68+
index: 'games',
69+
id: doc['_id'],
70+
body: {
71+
doc: {
72+
modified: DateTime.now
73+
}
74+
}
75+
)
76+
end
77+
json_response(response)
78+
end
79+
80+
get '/error' do
81+
begin
82+
@client.delete(index: 'games', id: 'somerandomid')
83+
rescue Elasticsearch::Transport::Transport::Errors::NotFound => e
84+
json_response(e, 404)
85+
end
86+
end
87+
88+
get '/doc/:id' do
89+
response = @client.get(index: 'games', id: sanitize(params[:id]))
90+
json_response(response)
91+
end
92+
93+
private
94+
95+
def json_response(response, code = 200)
96+
[
97+
code,
98+
{ 'Content-Type' => 'application/json' },
99+
[response.to_json]
100+
]
101+
end
102+
103+
def sanitize(params)
104+
Rack::Utils.escape_html(params)
105+
end
106+
107+
def search_elasticsearch(query = '')
108+
@client.search(
109+
index: 'games',
110+
body:
111+
{
112+
query: { multi_match: { query: query } }
113+
}
114+
)
115+
end
116+
end

0 commit comments

Comments
 (0)