Utku Kamacı
Sep 10, 2018 · 12 min read

ELK Stack ile gerçek hayat senaryolarında sistemin ürettiği veya yapılan istekler sonucu toplanan logların belirleyeceğimiz konfigürasyonlar ile işlenebilmesini sağlayacağız. Filtreleme tekniklerini kullanarak üretilen loglardan istediğimiz bilgileri elde edip indekslenmesini sağlayacağız. Sonuç olarak filtrelediğimiz logların analizini ve görselleştirmesini sağlayan, her yeni logu işleyip sürekli otomatik bir şekilde çalışan sistem çatısı kurmuş olacağız.


Uygulamalardan kendi başlarına kısaca bahsedecek olursak:

Beats tek amaçlı veri iletme platformudur. Sisteme ek yükte bulunmadan agent görevi gören yüzlerce veya binlerce makinadan Logstash veya Elasticsearch’e veri gönderimini sağlayan ‘Beat’leri içerir.

ElasticSearch Java ile geliştirilmiş açık kaynaklı, lucene tabanlı, ölçeklenebilir bir tam metin arama motoru ve veri analiz aracıdır.

Logstash log toplayan, konfigürasyonlara göre logları işleyen ve Elasticsearch’e bu logları indekslenmek üzere gönderen sistemdir.

Kibana veri görselleştirme platformudur. Logstash aracılığıyla iletilen loglar bu platform üzerinde metrik veya grafik yapılar ile analiz edilmek üzere görselleştirilir.

Genel Bilgi:

Nodeların her birinde birer tane ElasticSearch çalışabilir. Her bir node, tüm indexleme ve okuma işlerinde diğer nodelar ile haberleşir. Tüm nodelar, “Cluster” dediğimiz sunucu kümeleri içinde bulunur.

Örnek senaryo için 3 nodedan oluşan bir cluster oluşturacağız.

Cluster Centos7 üzerinde oluşturulmuştur.

İşlemlerin root yetkisine sahip kullanıcı tarafından yapıldığı varsayılmıştır.


Kurulum

ELK Java’ya bağımlı olduğu için bütün nodelar için Java 1.8 kurulumu yapacağız.

yum install -y java

komutu ile java-1.8.0-openjdk-1.8.0 kurulumunu tamamlıyoruz.

rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch

komutu ile elasticsearch anahtarını ekliyoruz.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

Not: Tercihen aşağıdaki gibi kaynak repository eklenerek de kurulum işlemleri gerçekleştirilebilir. Bu yöntem uygulamaların en güncel versiyonlarını kurmak için idealdir. Bu yazı içerisinde rpm paket yöneticisi ile kurulumlar gerçekleştirilmiştir.

[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.3.2.rpm

komutu ile elasticsearch 6.3.2 versiyonunu sunucumuza indiriyoruz.

rpm --install elasticsearch-6.3.2.rpm

komutu ile indirdiğimiz paketin kurulumunu yapıyoruz.

Daha sonra aşağıdaki komutları sırasıyla girerek elasticsearch servisini etkinleştiriyoruz.

systemctl daemon-reload
systemctl enable elasticsearch

Servisi etkinleştirdikten sonra /etc/elasticsearch dizininde bulunan elasticsearch.yml dosyasını text editörümüzle düzenliyoruz.

Aşağıdaki satırları sunucu bilgilerimize ve senaryoya göre değiştiriyoruz.

Bu senaryoda ilk node için node adını master-1 belirliyoruz ve master node olacağı için node.data değerini false olarak yml dosyamıza ekliyoruz.

node.name, node.data

yml dosya içeriğinde bulunan Network alanına ise master node’un sahip olduğu localhost ve private ip bilgisini tutan aşağıdaki görseldeki gibi bir array oluşturuyoruz.

localhost, private ip address

Diğer ayarları bu senaryo için değiştirmemize ihtiyaç yok. Fakat genel konfigürasyonda heap size — jvm değerini ayarlamamız uygulamamızın kararlı (stable) çalışması açısından önemlidir.

Not: Heap size 32gb üzeri olması önerilmez.

Aşağıdaki start komutu ile elasticsearch’ü başlatıyoruz ve status komutu ile uygulamanın düzgün çalışıp çalışmadığını kontrol ediyoruz.

systemctl start elasticsearch
systemctl status elasticsearch

Not: logları “/var/log/elasticsearch/elasticsearch.log” dizininden takip edebilirsiniz.

Data nodeları olarak kullanacağımız diğer 2 sunucuya geçiş yaparak elasticsearch kurulum tamamlama adımına kadar yukarıdaki adımları tekrarlıyoruz.

Birinci data node’u için Node kısmında isim tanımı yaptıktan sonra master node fonksiyonunu devre dışı bırakmak için aşağıdaki görseldeki tanımı uyguluyoruz:

Network kısmındaki ayarları master nodeda ayarladığımız gibi [localhost, private ip address] şeklinde birinci data node için de uyguluyoruz.

Ek olarak birinci data node’un master node ile konuşabilmesi için Discovery alanında bulunan discovery.zen.ping.unicast.hosts: [“host1”, “host2”] içeriğini master node’un private ip adresi ile değiştiriyoruz.

değişiklikleri kaydettikten sonra birinci data node’un bulunduğu sunucuda elasticsearch servisini çalıştırıyoruz.

Birinci data node için uyguladığımız adımları ikinci data node için de uyguluyoruz.

Master node’un bulunduğu sunucunun elasticsearch loglarına baktığımızda her iki data node ile bağlantı kurduğunu aşağıdaki görselde görebiliyoruz:

curl localhost:9200

komutu ile cluster hakkında detaylı bilgileri yazdırabiliriz.

curl localhost:9200/_cluster/health?pretty=true

komutu ile cluster status, node, shard detaylarını listeleyebiliriz.

Logstash Kurulum ve Konfigürasyon

Kurulumu Master node’un bulunduğu sunucuya gerçekleştireceğiz.

Elasticsearch kurulumunda GPG-key import işlemini ve Java kurulumunu tamamladığımız için Logstash’i indirmekle kurulumuza başlıyoruz.

Aşağıdaki komut ile Logstash 6.3.2 rpm kurulum dosyasını indiriyoruz:

wget https://artifacts.elastic.co/downloads/logstash/logstash-6.3.2.rpm

Elasticsearch kurulumunda izlediğimiz adımdaki gibi rpm ile aşağıdaki komutu girerek kurulumu gerçekleştiriyoruz:

rpm --install logstash-6.3.2.rpm

Kurulum tamamlandıktan sonra aşağıdaki komut ile Logstash servisini aktifleştiriyoruz. Böylelikle server yeniden başlatıldığında Logstash de otomatik olarak sistem tarafından çalıştırılacaktır.

systemctl enable logstash

İlk olarak Logstash /etc/logstash dizininde bulunan jvm.options dosyasını text editör ile düzenliyoruz. Bu dosya içerisinde tanımlı Heap değerlerini production ortamımız için ihtiyacımız doğrultusunda düzenliyoruz.

Benim kullanmış olduğum test ortamı için değerleri değiştirmedim, default olarak tanımlı değerler aşağıdaki gibiydi:

-Xms1g
-Xmx1g

Aynı dizin üzerinde bulunan pipelines.yml dosyasını editleyerek birden fazla pipeline konfigürasyonunu ve worker tanımlarını ekleyebiliriz. Sistem tarafından oluşturulan konfigürasyon ile pipeline dizini ve dosya konumu

/etc/logstash/conf.d/*.conf

şeklindedir. Farklı kullanım senaryoları için pipelineları ayrı konumlandırarak birbirlerinin kaynak kullanımının önüne geçmiş olur, bağımsız çalışmalarını sağlayabiliriz.

conf.d dizinin altına örnek olarak beats, grok ve diğer pluginleri içeren adı apache.conf olacak şekilde apache konfigürasyon dosyası ekliyorum.

Beats input pluginleri spesifik kaynaklı eventlerın Logstash tarafından okunmasına olanak sağlar.

Örnek: http input plugini sadece http veya https üzerinde gerçekleşen eventlerı alıp işlenmesini sağlar.

Dosya içeriği:

input {
beats {
port => 5044
host => "0.0.0.0"
}
}
filter {
if [fileset][module] == "apache2" {
if [fileset][name] == "access" {
grok {
match => { "message" => ["%{IPORHOST:[apache2][access][remote_ip]} - %{DATA:[apache2][access][user_name]} \[%{HTTPDATE:[apache2][access][time]}\] \"%{WORD:[apache2][access][method]} %{DATA:[apache2][access][url]} HTTP/%{NUMBER:[apache2][access][http_version]}\" %{NUMBER:[apache2][access][response_code]} %{NUMBER:[apache2][access][body_sent][bytes]}( \"%{DATA:[apache2][access][referrer]}\")?( \"%{DATA:[apache2][access][agent]}\")?",
"%{IPORHOST:[apache2][access][remote_ip]} - %{DATA:[apache2][access][user_name]} \\[%{HTTPDATE:[apache2][access][time]}\\] \"-\" %{NUMBER:[apache2][access][response_code]} -" ] }
remove_field => "message"
}
mutate {
add_field => { "read_timestamp" => "%{@timestamp}" }
}
date {
match => [ "[apache2][access][time]", "dd/MMM/YYYY:H:m:s Z" ]
remove_field => "[apache2][access][time]"
}
useragent {
source => "[apache2][access][agent]"
target => "[apache2][access][user_agent]"
remove_field => "[apache2][access][agent]"
}
geoip {
source => "[apache2][access][remote_ip]"
target => "[apache2][access][geoip]"
}
}
else if [fileset][name] == "error" {
grok {
match => { "message" => ["\[%{APACHE_TIME:[apache2][error][timestamp]}\] \[%{LOGLEVEL:[apache2][error][level]}\]( \[client %{IPORHOST:[apache2][error][client]}\])? %{GREEDYDATA:[apache2][error][message]}",
"\[%{APACHE_TIME:[apache2][error][timestamp]}\] \[%{DATA:[apache2][error][module]}:%{LOGLEVEL:[apache2][error][level]}\] \[pid %{NUMBER:[apache2][error][pid]}(:tid %{NUMBER:[apache2][error][tid]})?\]( \[client %{IPORHOST:[apache2][error][client]}\])? %{GREEDYDATA:[apache2][error][message1]}" ] }
pattern_definitions => {
"APACHE_TIME" => "%{DAY} %{MONTH} %{MONTHDAY} %{TIME} %{YEAR}"
}
remove_field => "message"
}
mutate {
rename => { "[apache2][error][message1]" => "[apache2][error][message]" }
}
date {
match => [ "[apache2][error][timestamp]", "EEE MMM dd H:m:s YYYY", "EEE MMM dd H:m:s.SSSSSS YYYY" ]
remove_field => "[apache2][error][timestamp]"
}
}
}
}
output {
elasticsearch {
hosts => localhost
manage_template => false
index => "%{[@metadata][beat]}-%{[@metadata][version]}-%{+YYYY.MM.dd}"
}

Yukarıdaki örnek konfigürasyonu basitçe tanımlamak gerekirse; beats input plugini olarak kullanıldı. 5044 portunu dinleyecek ve bütün host bağlantılarına izin verecek.

Filter alanına gelirsek; koşul yapısı ile apache2 modülünün kullanılıp kullanılmadığını kontrol ediyor. Daha sonrasında access log kontrolü gerçekleştiriliyor. İki koşul da sağlandığında en popüler filtre pluginlerden biri olan grok, konfigürasyonda tanımlı olan alanları ayrışıtıyor(parse). Örnek konfigürasyonda message alanı REGEX tanımından oluşuyor. Tanımlı alanlardaki ayrıştırma işlemi bitince message alanı siliniyor.

Diğer filtre pluginleri de logları daha kolay anlaşılabilir formatlara dönüştürme işlevini görmektedir.

Access logları dışında kullanılan örnekte karşımıza çıkmayacak olsa da error logları da koşul yapısının else if bloğunda tanımlanmıştır.

Son olarak output plugin kısmında loglar Elasticsearch’e yönlendirilecektir. Oluşturulan logları iletebilmesi için filebeat kullanılacak ve logların belirlenen tarih formatı ile depolanması sağlanacak.

Aşağıdaki start komutu ile logstash’i başlatıyoruz ve status komutu ile uygulamanın düzgün çalışıp çalışmadığını kontrol ediyoruz:

systemctl start logstash
systemctl status logstash

/var/log/logstash/logstash-plain.log dizininden logstash loglarını takip edebiliriz.

Acces Log

Master node üzerinde apache2 kurulumu yapıldığını ve access log oluşturulan bir websitesinin var olduğu farz ediliyor. Apache2 bulunmadığı senaryo için /var/log dizinine apache2 adında bir klasör oluşturuyorum.

Access Log web sunucumuza dışarıdan sağlanan erişim istekleri sonucu oluşturulmuş log parçalarını içermektedir. Bu demo makalesinde kullanacağımız daha önceden web sunucusunda oluşturulmuş log dosyalarını upload ediyorum.

Örnek log dosyasını text formatında indirmek isteyenler için link:

Filebeat Kurulumu

Kurulumu Master node üzerinde yapıyoruz. Aşağıdaki komut ile Filebeat 6.3.2 rpm kurulum dosyasını indiriyoruz

wget https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-6.3.2-x86_64.rpm

Elasticsearch ve Logstash kurulumlarında izlediğimiz adımlardaki gibi rpm ile aşağıdaki komutu yazarak kurulumu gerçekleştiriyoruz.

rpm --install filebeat-6.3.2.rpm

Kurulum tamamlandıktan sonra aşağıdaki komut ile Filebeat servisini aktif ediyoruz. Böylelikle server yeniden başlatıldığında Filebeat otomatik olarak sistem tarafından çalıştırılacaktır.

systemctl enable filebeat

filebeat setup komutu ile index şablonunu oluşturuyoruz. Karşımıza çıkan hataları kurulum esnasında düzenleyeceğimiz ayarlar ile kullanmayacağımız için görmezden geliyoruz.

İlk olarak /etc/filebeat/ dizininde bulunan filebeat.yml dosyasını text editör ile açıyoruz.

Filebeat başladığında path değişkenine tanımlanan dizindeki dosyaya bağlanarak oluşturulan her logu pipelineda belirlediğimiz output dosyasına gönderecektir.

Filebeat aynı zamanda multiline çalışan bir uygulamadır. Bu dosya üzerinde belirleyebileceğimiz şablonlar ile yönetebiliriz. Multiline tanımı ile birden fazla satır içeren loglar arasında satırların belirlediğimiz kısımlarını filtreleyebiliriz.

Örneğin:

multiline.pattern: '^\['
multiline.negate: true
multiline.match: after

Filebeat kapalı parantez ([) ile başlamayan bütün satırları alır ve aynı kurala sahip kendinden önce gelen satır ile birleştirir. Sonuç olarak birden fazla satırdan oluşan logları tek bir evente dönüştürüp incelemesini gerçekleştirebiliriz.

multiline.pattern değişkenine tanımlayacağımız şablon içeriğine yazılan değerler ile başlayan çok satılırların birer event olduğu tanımı yapabiliriz.

Aşağıdaki tanımda şablona yazdığımız yıl-ay-gün şablonu ile başlayan loglar ve sonrasında gelen satırların bu şablona uymayacağı koşullarını belirtmektedir.

Fields alanı ile birden fazla uygulamanın loglarını ayırabilmek için kullanabileceğimiz log gösterim konfigürasyonu alanıdır. Buradaki konfigürasyonu app1 uygulamamızı applications alanında gösterebilmesi için değiştirebiliriz.

Oluşacak eventleri Logstash’e yönlendirme işlemini Output tabından ayarlıyoruz.

Dosyamızı kaydettiktan sonra aşağıdaki komut ile apache modülünü aktif ediyoruz:

filebeat modules enable apache2

Aşağıdaki komut ile Filebeat servisini başlatıyoruz:

systemctl start filebeat

/var/log/filebeat dizininden logları takip edebilirsiniz.

Shardlar depolanmış olan documentların tutulduğu küçük birimlerdir. Her bir shard aslında birer lucene instance’ına eşittir. Her node için en az 1 primary shard ve 0 replica shard bulunması zorunludur. Documentlar ise Index içerisindeki veri tiplerinin içinde barındıracağımız ve üzerlerinde CRUD işlemleri gerçekleştireceğimiz birimlerdir.

Her bir Shard’ın Replica’sının(kopyası) başka bir Node’da bulunması gerekir. Eğer Shard ve Replica Shardlar aynı Node üzerinde bulunursa ve o node bir şekilde çalışmayı durdurursa, oluşturulan bütün indexlerin kaybolması gibi istenmeyen senaryolar ile karşılaşılabilir.

Aşağıdaki komutu tekrar çalıştırdığımızda aldığımız çıktılar ile aktif shard ve replica shard sayılarını görebilmekteyiz:

curl localhost:9200/_cluster/health?pretty=true

Kibana Kurulumu ve Görselleştirme

Kurulumu Master node üzerinde yapıyoruz. Aşağıdaki komut ile Kibana 6.3.2 rpm kurulum dosyasını indiriyoruz:

wget https://artifacts.elastic.co/downloads/kibana/kibana-6.3.2-x86_64.rpm

Elasticsearch, Logstash ve Filebeat kurulumlarında izlediğimiz adımlardaki gibi rpm ile aşağıdaki komutu yazarak kurulumu gerçekleştiriyoruz:

rpm --install kibana-6.3.2-x86_64.rpm

Kurulum tamamlandıktan sonra aşağıdaki komut ile Kibana servisini aktif ediyoruz. Böylelikle server yeniden başlatıldığında Kibana otomatik olarak sistem tarafından çalıştırılacaktır.

systemctl enable kibana

Kibana’yı diğer kurulumlarla aynı sunucu üzerinde yaptığımız için konfigürasyonu değiştirmiyoruz ve aşağıdaki komut ile Kibana’yı çalıştırıyoruz:

systemctl start kibana

/var/log/messages dizininden Kibana loglarını inceleyebilirsiniz.

Apache/Nginx ile reverse proxy ve ssl,basic auth tanımları yapılmadığı zaman Xpack pluginin içerdiği güvenlik adımlarını da tamamlayamayacağımız için sistem güvenliği tehlikede olacaktır. Bu senaryoda public ip üzerinden Kibana konsoluna erişimi ve clusterların silinebilmesi gibi güvenlik tehditleri oluşmaktadır. Bu sebeple sistemde oluşabilecek güvenlik açıklarını önceden engellenmesini tavsiye ederim.

İzleyeceğimiz adımlar ile remote tunnel ssh uygulayacağız.

Terminal’e aşağıdaki komutu kendi public ip adresimizi yazarak Kibana’nın çalıştığı 5601 portunu kendi kullandığımız bilgisayarın/sunucunun localhost:5601 portuna bağlıyoruz. Böylelikle localde çalıştığımız makine ile arayüze erişebileceğiz. Eğer bir public ip üzerinden erişilmek isteniyorsa Apache veya Nginx gibi bir web sunucu kurulumunun gerçekleştirilmesi gerekmektedir. Ayrıca SSL tanımlarının yapılması veri güvenliği açısından da önemlidir. Fakat bu makalede örnek loglama kullanıldığı için bu adımları izlememize gerek yoktur.

ssh user@52.17.94.84 -L 5601:localhost:5601

Internet tarayıcımız ile localhost:5601 adresine gittiğimizde Kibana arayüzü bizi karşılayacaktır.

Sol taraftaki barda bulunan Management sekmesine tıklıyoruz. Açılan pencerede Kibana tabı altında bulunan Index Patterns’ı seçiyoruz.

Açılan ekranda ilk adım olarak aşağıdaki gibi “filebeat-” tagini girerek index şablonunu belirliyoruz.

İkinci adımda ise zamana dayalı veri tuttuğumuz için timestamp verilerini barındıran timestamp alanını seçiyoruz.

Şablon oluşturma işlemi tamamlandığında sonuçlar aşağıdaki gibi listelenmektedir:

Filtre içeriğine “bytes” yazarak apache metadatalarını görüntüleyebiliriz.

Örnek olarak: Filtre sonucu listelenen apache2.access.body_sent.bytes alanı için satırın sonunda bulunan kalem resmine tıklayarak ayarlar sayfasını açabiliriz.

Bu sayfada alanın format tipini bytes olarak değiştirdiğimizde apache2.access.body_sent.bytes veri rakamları dosya formatı boyutunda gösterilecektir.

Ekranın sol tarafında bulunan bardan “Discover” sekmesini seçiyoruz.

Örnek olarak yüklediğim log dataları 2015 yılına ait olduğu için Discover ekranında tarih filtresini değiştirmemiz gerekmektedir. Ekranın sağ üst tarafından tarih ayarlamasını yapıyoruz.

Ekranın sol tarafında bulunan barda seçeceğimiz filtreler ile log dosyalarını daha kolay okunabilir halde istediğimiz sonuçları çıktı olarak alabiliyoruz.

Her bir sonucun başında bulunan “>” işaretine tıkladığımızda aşağıdaki gibi sonuçların detaylarını görüntüleyebiliriz.

Örneğin sadece Linux işletim sistemi ile yapılan isteklerin listelenmesi için filtre oluşturmak istediğimizde ortasında + işareti bulunan büyüteç simgesine tıklayıp filtre oluşturabiliriz. Sonuç olarak oluşturduğunuz filtre, filtre barının en üstünde listelenir ve burada filtreyi düzenleyip daha sonra kullanmak üzere label ekleyerek kaydedebiliriz. Filtre üzerinde yapabileceğimiz değişiklikler ile Linux işletim sistemi hariç yapılan istekleri listeleme gibi çeşitli opsiyonlarda bulunmaktadır.

Aynı zamanda sonuçları JSON formatında da alabiliriz:

Kibana elde ettiğimiz sonuçları ekranın üst tarafında bulunan save butonu ile kaydedebilir, var olan kayıtlı sonuçları tekrar yükleyebilir ve paylaşabilmek gibi çeşitli opsiyonları bize sunuyor.

Ekranın sol barında bulunan Visualize sekmesi ile istenilen sonuçların görselleştirilmesi sağlanabilmektedir.

Bunun için Visualize sekmesine tıklayarak açılan ekranda “Create a visualization” butonuna tıklıyoruz.

Karşımıza çeşitli görselleştirme seçenekleri çıkacaktır. Bu seçeneklerden Data segmentinde bulunan “Metric” seçeneğini seçiyoruz:

Metric data seçeneğini seçtikten sonra filebeat bütün sonuçları görselleştirebileceğimiz ve kayıtlı filtre sonuçlarını gösterebileceğimiz iki seçenek sunulmaktadır. Bunlardan filebeat bütün sonuçları görselleştirmeyi seçiyoruz.

Sol barda metrik sayısı arttırılabilir ve veri alanları seçilerek label oluşturulabilir.

Ben toplam yapılan istekleri ve toplan unique ip adrese sahip kullanıcıları yazdıracak bir düzenleme yaptım ve ekranın üstünde bulunan Save butonu ile çıktıyı kaydettim.

Bir başka görselleştirme yöntemlerinden Pie Chart opsiyonunu seçerek yapılan client isteklerinin internet tarayıcı dağılımını aşağıdaki gibi görselleştirdim. Filtreler kullanılarak daha fazla metric ile istenilen veriler görselleştirilerek daha karmaşık görseller oluşturulabilir.

Bir başka görselleştirme yöntemlerinden Coordinate Map opsiyonunu seçerek unique iplerin hangi enlem/boylam üzerinden toplam yapılan istek sayıları, hangi sıklıkla gerçekleştiği gibi çeşitli bilgileri görselleştirebildim.

Aynı şekilde aşağıdaki gibi Histgoram oluşturabildim:

Son olarak oluşturduğumuz görselleri kaydettiğimiz zaman ekranın sol tarafında bulunan bardan “Dashboard” sekmesini seçerek kendi özel alanımızı oluşturabilir başkaları ile paylaşabilir ve belirleyeceğimiz tarih aralığındaki değişimleri kolaylıkla gösterebiliriz.

Benim oluşturduğum son Dashboard aşağıdaki gibi gözükmektedir:

Kod Gemisi

Kod Gemisi

Thanks to elifcan cakmak

Utku Kamacı

Written by

Linux — DevOps Tools — Atlasian Partner

Kod Gemisi

Kod Gemisi

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade