معرفی
در حین استقرار یک سیستم دیگر، با نیاز به پردازش تعداد زیادی لاگ مختلف مواجه شدیم. ELK به عنوان ابزار انتخاب شد. این مقاله در مورد تجربه ما در راه اندازی این پشته صحبت خواهد کرد.
ما هدفی برای توصیف همه قابلیت های آن تعیین نمی کنیم، اما می خواهیم روی حل مشکلات عملی تمرکز کنیم. این به این دلیل است که با حجم کافی اسناد و تصاویر آماده، دام های زیادی وجود دارد، حداقل ما آنها را پیدا کردیم.
ما پشته را از طریق docker-compose مستقر کردیم. علاوه بر این، ما یک docker-compose.yml به خوبی نوشته بودیم که به ما امکان می داد تقریباً بدون هیچ مشکلی پشته را بالا ببریم. و به نظرمان می رسید که پیروزی از قبل نزدیک بود، حالا کمی آن را به تناسب نیازهایمان بپیچانیم و بس.
متأسفانه، تلاش برای تنظیم سیستم برای دریافت و پردازش گزارشها از برنامه ما، در همان لحظه موفقیت آمیز نبود. بنابراین، ما تصمیم گرفتیم که ارزش آن را دارد که هر جزء را به طور جداگانه مطالعه کنیم، و سپس به اتصالات آنها برگردیم.
پس بیایید با logstash شروع کنیم.
محیط، استقرار، اجرای Logstash در یک ظرف
برای استقرار، ما از docker-compose استفاده میکنیم، آزمایشهایی که در اینجا توضیح داده شد روی MacOS و Ubuntu 18.0.4 انجام شد.
تصویر logstash که در docker-compose.yml اصلی خود داشتیم docker.elastic.co/logstash/logstash:6.3.2 است.
ما از آن برای آزمایش استفاده خواهیم کرد.
برای اجرای logstash، یک docker-compose.yml جداگانه نوشتیم. البته امکان راه اندازی تصویر از خط فرمان وجود داشت، اما بالاخره ما یک کار خاص را حل کردیم، جایی که همه چیز از docker-compose برای ما راه اندازی می شود.
مختصری در مورد فایل های پیکربندی
همانطور که در توضیحات آمده است، logstash را می توان برای یک کانال اجرا کرد، در این مورد، باید فایل *.conf یا برای چندین کانال را انتقال دهد، در این صورت باید فایل pipelines.yml را منتقل کند، که به نوبه خود ، برای هر کانال به فایل های .conf اشاره می کند.
راه دوم را در پیش گرفتیم. به نظر ما همه کاره تر و مقیاس پذیرتر به نظر می رسید. بنابراین، ما pipelines.yml را ایجاد کردیم و یک دایرکتوری pipelines ساختیم که در آن فایلهای .conf را برای هر کانال قرار میدهیم.
در داخل ظرف یک فایل پیکربندی دیگر وجود دارد - logstash.yml. ما آن را لمس نمی کنیم، همانطور که هست از آن استفاده می کنیم.
بنابراین ساختار دایرکتوری ما این است:
در حال حاضر، ما فرض می کنیم که این tcp در پورت 5046 برای دریافت داده های ورودی است و از stdout برای خروجی استفاده می کنیم.
در اینجا یک پیکربندی ساده برای اولین اجرا وجود دارد. زیرا وظیفه اولیه راه اندازی است.
بنابراین ما این docker-compose.yml را داریم
version: '3'
networks:
elk:
volumes:
elasticsearch:
driver: local
services:
logstash:
container_name: logstash_one_channel
image: docker.elastic.co/logstash/logstash:6.3.2
networks:
- elk
ports:
- 5046:5046
volumes:
- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
- ./config/pipelines:/usr/share/logstash/config/pipelines:ro
اینجا چه می بینیم؟
- شبکهها و حجمها از docker-compose.yml اصلی (مجموعهای که کل پشته در آن راهاندازی میشود) گرفته شدهاند و فکر میکنم که آنها تأثیر زیادی روی تصویر کلی اینجا ندارند.
- ما یک سرویس (سرویس) logstash از تصویر docker.elastic.co/logstash/logstash:6.3.2 ایجاد می کنیم و نام آن را logstash_one_channel می دهیم.
- پورت 5046 را داخل کانتینر به همان پورت داخلی فوروارد می کنیم.
- ما فایل پیکربندی لوله ./config/pipelines.yml خود را به فایل /usr/share/logstash/config/pipelines.yml داخل ظرف، جایی که logstash آن را برمی دارد و فقط خواندنی می کند، نگاشت می کنیم.
- دایرکتوری ./config/pipelines را که در آن فایل های پیکربندی لوله را داریم به پوشه /usr/share/logstash/config/pipelines نگاشت می کنیم و همچنین آن را فقط خواندنی می کنیم.
فایل piping.yml
- pipeline.id: HABR
pipeline.workers: 1
pipeline.batch.size: 1
path.config: "./config/pipelines/habr_pipeline.conf"
این یک کانال با شناسه HABR و مسیر فایل پیکربندی آن را توصیف می کند.
و در نهایت فایل "./config/pipelines/habr_pipeline.conf"
input {
tcp {
port => "5046"
}
}
filter {
mutate {
add_field => [ "habra_field", "Hello Habr" ]
}
}
output {
stdout {
}
}
فعلاً وارد توضیحات آن نمی شویم، سعی می کنیم اجرا کنیم:
docker-compose up
ما چه می بینیم؟
ظرف شروع شده است. ما می توانیم کار آن را بررسی کنیم:
echo '13123123123123123123123213123213' | nc localhost 5046
و ما پاسخ را در کنسول کانتینر می بینیم:
اما در عین حال می بینیم:
logstash_one_channel | [2019-04-29T11:28:59,790][ERROR][logstash.licensechecker.licensereader] نمی توان اطلاعات مجوز را از سرور مجوز بازیابی کرد {:message=>"Elasticsearch غیرقابل دسترس: [http://elasticsearch:9200/][Manticore ::ResolutionFailure]elasticsearch، ...
logstash_one_channel | [2019-04-29T11:28:59,894][INFO ][logstash.pipeline ] خط لوله با موفقیت شروع شد {:pipeline_id=>".monitoring-logstash", :thread=>"# »}
logstash_one_channel | [2019-04-29T11:28:59,988][INFO ][logstash.agent ] خطوط لوله در حال اجرا {:count=>2، :running_pipelines=>[:HABR، :".monitoring-logstash"]، :non_running_pipelines=>[ ]}
logstash_one_channel | [2019-04-29T11:29:00,015][ERROR][logstash.inputs.metrics] X-Pack در Logstash نصب شده است اما در Elasticsearch نصب نشده است. لطفاً X-Pack را در Elasticsearch نصب کنید تا از ویژگی نظارت استفاده کنید. سایر ویژگی ها ممکن است در دسترس باشد.
logstash_one_channel | [2019-04-29T11:29:00,526][INFO ][logstash.agent ] نقطه پایانی Logstash API با موفقیت شروع شد {:port=>9600}
logstash_one_channel | [2019-04-29T11:29:04,478][INFO ][logstash.outputs.elasticsearch] در حال اجرای بررسی سلامت برای مشاهده اینکه آیا اتصال Elasticsearch کار می کند {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,487][WARN ][logstash.outputs.elasticsearch] تلاش برای احیای مجدد اتصال به نمونه ES مرده، اما با خطا مواجه شد. {:url=>"
logstash_one_channel | [2019-04-29T11:29:04,704][INFO ][logstash.licensechecker.licensereader] در حال اجرای بررسی سلامت برای مشاهده اینکه آیا اتصال Elasticsearch کار میکند {:healthcheck_url=>http://elasticsearch:9200/, :path=> "/"}
logstash_one_channel | [2019-04-29T11:29:04,710][WARN ][logstash.licensechecker.licensereader] تلاش کرد تا اتصال به نمونه ES مرده را دوباره زنده کند، اما با خطا مواجه شد. {:url=>"
و سیاهه ما همیشه بالا می خزد.
در اینجا پیامی که خط لوله با موفقیت شروع شد را با رنگ سبز، پیام خطا را با رنگ قرمز و پیام مربوط به تلاش برای تماس را با رنگ زرد برجسته کردم.
این به دلیل این واقعیت است که در logstash.conf موجود در تصویر، بررسی در دسترس بودن elasticsearch وجود دارد. پس از همه، logstash فرض می کند که به عنوان بخشی از پشته Elk کار می کند، و ما آن را از هم جدا کردیم.
شما می توانید کار کنید، اما این راحت نیست.
راه حل این است که این بررسی را از طریق متغیر محیطی XPACK_MONITORING_ENABLED غیرفعال کنید.
بیایید تغییری در docker-compose.yml ایجاد کنیم و دوباره آن را اجرا کنیم:
version: '3'
networks:
elk:
volumes:
elasticsearch:
driver: local
services:
logstash:
container_name: logstash_one_channel
image: docker.elastic.co/logstash/logstash:6.3.2
networks:
- elk
environment:
XPACK_MONITORING_ENABLED: "false"
ports:
- 5046:5046
volumes:
- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
- ./config/pipelines:/usr/share/logstash/config/pipelines:ro
حالا، همه چیز خوب است. ظرف برای آزمایش آماده است.
می توانیم دوباره در کنسول مجاور تایپ کنیم:
echo '13123123123123123123123213123213' | nc localhost 5046
و ببینید:
logstash_one_channel | {
logstash_one_channel | "message" => "13123123123123123123123213123213",
logstash_one_channel | "@timestamp" => 2019-04-29T11:43:44.582Z,
logstash_one_channel | "@version" => "1",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "host" => "gateway",
logstash_one_channel | "port" => 49418
logstash_one_channel | }
در یک کانال کار کنید
بنابراین ما شروع کردیم. اکنون می توانید برای پیکربندی مستقیم logstash وقت بگذارید. فعلاً به فایل pipelines.yml دست نزنیم، ببینیم با کار با یک کانال چه چیزی می توانیم بدست آوریم.
باید بگویم که اصل کلی کار با فایل پیکربندی کانال به خوبی در دفترچه راهنمای رسمی، اینجا توضیح داده شده است
اگر می خواهید به زبان روسی بخوانید، ما از این یکی استفاده کردیم
بیایید به ترتیب از قسمت Input برویم. ما قبلا کار روی tcp را دیده بودیم. چه چیز دیگری می تواند در اینجا جالب باشد؟
آزمایش پیام ها با استفاده از ضربان قلب
چنین امکان جالبی برای تولید پیام های آزمایشی خودکار وجود دارد.
برای این کار باید افزونه heartbean را در قسمت input قرار دهید.
input {
heartbeat {
message => "HeartBeat!"
}
}
ما آن را روشن می کنیم، یک بار در دقیقه شروع به دریافت می کنیم
logstash_one_channel | {
logstash_one_channel | "@timestamp" => 2019-04-29T13:52:04.567Z,
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "message" => "HeartBeat!",
logstash_one_channel | "@version" => "1",
logstash_one_channel | "host" => "a0667e5c57ec"
logstash_one_channel | }
ما می خواهیم بیشتر دریافت کنیم، باید پارامتر فاصله را اضافه کنیم.
به این ترتیب هر 10 ثانیه یک پیام دریافت خواهیم کرد.
input {
heartbeat {
message => "HeartBeat!"
interval => 10
}
}
دریافت داده از یک فایل
ما همچنین تصمیم گرفتیم به حالت فایل نگاه کنیم. اگر با فایل به خوبی کار می کند، این امکان وجود دارد که هیچ عاملی لازم نباشد، خوب، حداقل برای استفاده محلی.
با توجه به توضیحات، حالت کار باید شبیه tail -f باشد، یعنی. خطوط جدید را می خواند یا به صورت اختیاری، کل فایل را می خواند.
بنابراین آنچه می خواهیم به دست آوریم:
- ما می خواهیم خطوطی را دریافت کنیم که به یک فایل log اضافه می شوند.
- ما میخواهیم دادههایی را دریافت کنیم که در چندین فایل log نوشته شدهاند، در حالی که بتوانیم آنچه را که دریافت شده از کجا جدا کنیم.
- ما می خواهیم مطمئن شویم که وقتی logstash دوباره راه اندازی می شود، دیگر این داده ها را دریافت نخواهد کرد.
- میخواهیم بررسی کنیم که اگر logstash غیرفعال است و دادهها همچنان در فایلها نوشته میشوند، پس از اجرای آن، این دادهها را دریافت خواهیم کرد.
برای انجام آزمایش، اجازه دهید یک خط دیگر به docker-compose.yml اضافه کنیم و دایرکتوری را که فایل ها را در آن قرار می دهیم باز کنیم.
version: '3'
networks:
elk:
volumes:
elasticsearch:
driver: local
services:
logstash:
container_name: logstash_one_channel
image: docker.elastic.co/logstash/logstash:6.3.2
networks:
- elk
environment:
XPACK_MONITORING_ENABLED: "false"
ports:
- 5046:5046
volumes:
- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
- ./config/pipelines:/usr/share/logstash/config/pipelines:ro
- ./logs:/usr/share/logstash/input
و قسمت ورودی را در habr_pipeline.conf تغییر دهید
input {
file {
path => "/usr/share/logstash/input/*.log"
}
}
ما شروع:
docker-compose up
برای ایجاد و نوشتن فایل های log از دستور زیر استفاده می کنیم:
echo '1' >> logs/number1.log
{
logstash_one_channel | "host" => "ac2d4e3ef70f",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "@timestamp" => 2019-04-29T14:28:53.876Z,
logstash_one_channel | "@version" => "1",
logstash_one_channel | "message" => "1",
logstash_one_channel | "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }
بله، کار می کند!
در همان زمان می بینیم که فیلد مسیر را به صورت خودکار اضافه کرده ایم. بنابراین در آینده قادر خواهیم بود رکوردها را بر اساس آن فیلتر کنیم.
بیایید دوباره تلاش کنیم:
echo '2' >> logs/number1.log
{
logstash_one_channel | "host" => "ac2d4e3ef70f",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "@timestamp" => 2019-04-29T14:28:59.906Z,
logstash_one_channel | "@version" => "1",
logstash_one_channel | "message" => "2",
logstash_one_channel | "path" => "/usr/share/logstash/input/number1.log"
logstash_one_channel | }
و حالا به یک فایل دیگر:
echo '1' >> logs/number2.log
{
logstash_one_channel | "host" => "ac2d4e3ef70f",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "@timestamp" => 2019-04-29T14:29:26.061Z,
logstash_one_channel | "@version" => "1",
logstash_one_channel | "message" => "1",
logstash_one_channel | "path" => "/usr/share/logstash/input/number2.log"
logstash_one_channel | }
عالی! فایل برداشت شد، مسیر به درستی مشخص شد، همه چیز خوب است.
logstash را متوقف کنید و دوباره راه اندازی کنید. بیا صبرکنیم. سکوت آن ها ما دیگر این سوابق را دریافت نمی کنیم.
و اکنون جسورانه ترین آزمایش.
ما logstash را قرار داده و اجرا می کنیم:
echo '3' >> logs/number2.log
echo '4' >> logs/number1.log
دوباره logstash را اجرا کنید و ببینید:
logstash_one_channel | {
logstash_one_channel | "host" => "ac2d4e3ef70f",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "message" => "3",
logstash_one_channel | "@version" => "1",
logstash_one_channel | "path" => "/usr/share/logstash/input/number2.log",
logstash_one_channel | "@timestamp" => 2019-04-29T14:48:50.589Z
logstash_one_channel | }
logstash_one_channel | {
logstash_one_channel | "host" => "ac2d4e3ef70f",
logstash_one_channel | "habra_field" => "Hello Habr",
logstash_one_channel | "message" => "4",
logstash_one_channel | "@version" => "1",
logstash_one_channel | "path" => "/usr/share/logstash/input/number1.log",
logstash_one_channel | "@timestamp" => 2019-04-29T14:48:50.856Z
logstash_one_channel | }
هورا! همه چیز جمع شد
اما، لازم است در مورد موارد زیر هشدار داده شود. اگر محفظه logstash برداشته شود (docker stop logstash_one_channel && docker rm logstash_one_channel)، هیچ چیزی برداشت نخواهد شد. موقعیت فایلی که در آن خوانده شده بود در داخل ظرف ذخیره می شد. اگر از ابتدا شروع کنید، فقط خطوط جدید را می پذیرد.
خواندن فایل های موجود
فرض کنید ما برای اولین بار logstash را اجرا می کنیم، اما از قبل لاگ هایی داریم و می خواهیم آنها را پردازش کنیم.
اگر logstash را با قسمت ورودی که در بالا استفاده کردیم اجرا کنیم، چیزی دریافت نمی کنیم. فقط خطوط جدید توسط logstash پردازش می شود.
به منظور کشیدن خطوط از فایل های موجود، یک خط اضافی به بخش ورودی اضافه کنید:
input {
file {
start_position => "beginning"
path => "/usr/share/logstash/input/*.log"
}
}
علاوه بر این، یک تفاوت ظریف وجود دارد، این فقط بر روی فایلهای جدیدی تأثیر میگذارد که logstash هنوز آنها را ندیده است. برای همان فایل هایی که قبلاً در میدان دید logstash بودند، قبلاً اندازه آنها را به خاطر آورده و اکنون فقط رکوردهای جدید را در آنها می گیرد.
اجازه دهید با مطالعه بخش ورودی به این موضوع بسنده کنیم. گزینه های بسیار بیشتری وجود دارد، اما در حال حاضر، ما به اندازه کافی برای آزمایش های بیشتر داریم.
مسیریابی و تبدیل داده ها
بیایید سعی کنیم مشکل زیر را حل کنیم، فرض کنید پیام هایی از یک کانال داریم، برخی از آنها اطلاعاتی و برخی پیام های خطا هستند. آنها در برچسب متفاوت هستند. برخی INFO هستند، برخی دیگر ERROR هستند.
ما باید آنها را در خروجی جدا کنیم. آن ها ما در یک کانال پیام های اطلاعاتی و در کانال دیگر پیام های خطا می نویسیم.
برای این کار از قسمت ورودی به فیلتر و خروجی بروید.
با استفاده از بخش فیلتر، پیام ورودی را تجزیه می کنیم و یک هش (جفت های کلید-مقدار) از آن دریافت می کنیم، که می توانیم با آن کار کنیم، یعنی. با توجه به شرایط تجزیه کنید و در قسمت خروجی پیام ها را انتخاب کرده و هر کدام را به کانال خود ارسال می کنیم.
تجزیه پیام با grok
به منظور تجزیه رشته های متنی و دریافت مجموعه ای از فیلدها از آنها، یک پلاگین مخصوص در قسمت فیلتر وجود دارد - grok.
بدون اینکه هدف خود را از ارائه شرح مفصلی در اینجا در نظر بگیرم (برای این منظور به آن اشاره می کنم
برای انجام این کار، باید در مورد فرمت خطوط ورودی تصمیم بگیرید. من آنها را اینگونه دارم:
1 پیام اطلاعاتی 1
2 پیغام خطا 2
آن ها ابتدا شناسه، سپس INFO/ERROR، سپس چند کلمه بدون فاصله.
دشوار نیست، اما برای درک اصل عملکرد کافی است.
بنابراین، در قسمت فیلتر، در افزونه grok، باید یک الگو برای تجزیه رشته های خود تعریف کنیم.
شبیه این خواهد شد:
filter {
grok {
match => { "message" => ["%{INT:message_id} %{LOGLEVEL:message_type} %{WORD:message_text}"] }
}
}
در اصل، این یک عبارت منظم است. از الگوهای آماده مانند INT، LOGLEVEL، WORD استفاده می شود. شرح آنها و همچنین الگوهای دیگر را می توان در اینجا مشاهده کرد.
حالا با عبور از این فیلتر، رشته ما به یک هش از سه فیلد تبدیل می شود: message_id، message_type، message_text.
آنها در بخش خروجی نمایش داده می شوند.
مسیریابی پیام ها در قسمت خروجی با دستور if
در بخش خروجی، همانطور که به یاد داریم، قرار بود پیام ها را به دو جریان تقسیم کنیم. برخی - که iNFO هستند، به کنسول خروجی می دهیم و با خطا، به یک فایل خروجی می دهیم.
چگونه می توانیم این پیام ها را به اشتراک بگذاریم؟ شرایط مشکل از قبل یک راه حل را نشان می دهد - از این گذشته، ما قبلاً یک قسمت message_type اختصاصی داریم که می تواند فقط دو مقدار INFO و ERROR را بگیرد. روی آن است که با استفاده از دستور if انتخاب می کنیم.
if [message_type] == "ERROR" {
# Здесь выводим в файл
} else
{
# Здесь выводим в stdout
}
شرح کار با فیلدها و عملگرها را می توانید در این قسمت مشاهده کنید
حالا در مورد خود نتیجه گیری.
خروجی کنسول، همه چیز در اینجا واضح است - stdout {}
اما خروجی فایل - به یاد داشته باشید که همه اینها را از کانتینر اجرا می کنیم و برای اینکه فایلی که در آن نتیجه را می نویسیم از بیرون قابل دسترسی باشد، باید این دایرکتوری را در docker-compose.yml باز کنیم.
مجموع:
بخش خروجی فایل ما به شکل زیر است:
output {
if [message_type] == "ERROR" {
file {
path => "/usr/share/logstash/output/test.log"
codec => line { format => "custom format: %{message}"}
}
} else
{stdout {
}
}
}
برای خروجی، یک جلد دیگر به docker-compose.yml اضافه کنید:
version: '3'
networks:
elk:
volumes:
elasticsearch:
driver: local
services:
logstash:
container_name: logstash_one_channel
image: docker.elastic.co/logstash/logstash:6.3.2
networks:
- elk
environment:
XPACK_MONITORING_ENABLED: "false"
ports:
- 5046:5046
volumes:
- ./config/pipelines.yml:/usr/share/logstash/config/pipelines.yml:ro
- ./config/pipelines:/usr/share/logstash/config/pipelines:ro
- ./logs:/usr/share/logstash/input
- ./output:/usr/share/logstash/output
شروع می کنیم، تلاش می کنیم، تقسیم به دو جریان را می بینیم.
منبع: www.habr.com