{"id":443900,"date":"2025-01-02T15:04:40","date_gmt":"2025-01-02T15:04:40","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=443900"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2025-01-02T15:04:40","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=443900","title":{"rendered":"<span>\u041a\u0442\u043e \u00ab\u0435\u0441\u0442\u00bb \u0442\u0440\u0430\u0444\u0438\u043a \u0432 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438? \u0413\u043e\u0442\u043e\u0432\u0438\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440 \u0434\u043b\u044f Prometheus, \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043c \u0441\u0435\u0442\u044c<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/978\/161\/5a3\/9781615a39c3e501dd946a02ff8545d8.jpg\" width=\"765\" height=\"567\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/978\/161\/5a3\/9781615a39c3e501dd946a02ff8545d8.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041d\u0435\u0434\u0430\u0432\u043d\u043e \u043c\u0435\u043d\u044f \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u043f\u043e\u043c\u043e\u0447\u044c \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0443\u0442\u0435\u0447\u043a\u0438 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0439. \u0417\u0430\u0434\u0430\u0447\u0443 \u0443\u0441\u0443\u0433\u0443\u0431\u043b\u044f\u043b\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0432 \u043e\u0434\u043d\u043e\u043c \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u0434\u043e\u043c\u0435\u043d\u0435, \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043d\u0435\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0445 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043b\u044e\u0431\u043e\u0439 \u043a\u0430\u0440\u0442\u044b \u0441\u0435\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u0442\u0430\u0440\u0435\u043d\u044c\u043a\u0438\u0439 \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435. \u0412 \u043e\u0431\u0449\u0435\u043c, \u044d\u0442\u043e \u0431\u044b\u043b\u0438 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 &#171;\u0410\u0432\u0433\u0438\u0435\u0432\u044b \u043a\u043e\u043d\u044e\u0448\u043d\u0438&#187;, \u043d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0431\u044b\u043b\u0430 \u0440\u0435\u0448\u0435\u043d\u0430, \u0438 \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0430 \u043c\u0435\u0442\u043e\u0434\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b. \u041a\u0442\u043e \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0432\u0438\u043d\u043e\u0432\u043d\u0438\u043a\u043e\u043c, \u044f \u0440\u0430\u0441\u043a\u0440\u043e\u044e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u043e\u0440\u0442\u0438\u0442\u044c \u0438\u043d\u0442\u0440\u0438\u0433\u0443.<\/p>\n<div class=\"persona\"><img decoding=\"async\" class=\"image persona__image\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/ecb\/798\/b18\/ecb798b18a2260d47f68d808093e702d.jpg\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ecb\/798\/b18\/ecb798b18a2260d47f68d808093e702d.jpg\" data-blurred=\"true\"\/><\/p>\n<h5 class=\"persona__heading\">\u041c\u043e\u0439 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u043a\u0430\u043d\u0430\u043b &#8212; \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e, \u0433\u0434\u0435 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c<\/h5>\n<p><a href=\"https:\/\/t.me\/IT_Chuyana\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/t.me\/IT_Chuyana<\/u><\/a> <\/p>\n<\/div>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u044f \u043f\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u043e\u0432:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/c85\/cc6\/da8\/c85cc6da86e6b1b542a9f58fc9ae258a.jpg\" width=\"826\" height=\"407\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c85\/cc6\/da8\/c85cc6da86e6b1b542a9f58fc9ae258a.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0430\u043d\u043e\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u044f \u0440\u0430\u0437\u0431\u0438\u043b \u0437\u0430\u0434\u0430\u0447\u0443 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u0437\u0430\u0434\u0430\u0447:<\/p>\n<ol>\n<li>\n<p>\u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u044f\u0445 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0441 \u0446\u0435\u043b\u044c\u044e \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0445\u043e\u0441\u0442\u043e\u0432, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0449\u0438\u0445 \u0435\u0433\u043e \u0432 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0435\u043c \u043e\u0431\u044a\u0435\u043c\u0435.<\/p>\n<\/li>\n<li>\n<p>\u0410\u043d\u0430\u043b\u0438\u0437 \u0445\u043e\u0441\u0442\u043e\u0432, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u0439 \u0442\u0440\u0430\u0444\u0438\u043a, \u0441 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c.<\/p>\n<\/li>\n<\/ol>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/f6a\/eb4\/cc1\/f6aeb4cc13454c18f759da72378cbcea.jpg\" alt=\"\u0417\u0430\u0447\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0438 \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0438\u0447\u044c\" title=\"\u0417\u0430\u0447\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0438 \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0438\u0447\u044c\" width=\"757\" height=\"656\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f6a\/eb4\/cc1\/f6aeb4cc13454c18f759da72378cbcea.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>\u0417\u0430\u0447\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0438 \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0438\u0447\u044c<\/figcaption><\/div>\n<\/figure>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0430 1: \u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0443\u0433\u043e\u043b\u043a\u0430\u0445 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/ac5\/5fb\/909\/ac55fb909defdbe712e267c498a52cdc.jpg\" alt=\"\" title=\"\" width=\"807\" height=\"667\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/ac5\/5fb\/909\/ac55fb909defdbe712e267c498a52cdc.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041d\u0430 \u0443\u0437\u043b\u044b \u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440\u044b \u043d\u0430 Python, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 Speedtest CLI \u0432 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0432 Prometheus.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/7cc\/7b8\/4a3\/7cc7b84a310adc03d4d31563ff20f130.jpg\" width=\"820\" height=\"668\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7cc\/7b8\/4a3\/7cc7b84a310adc03d4d31563ff20f130.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u042d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440 \u0432\u044b\u0434\u0430\u0435\u0442 \u0434\u043b\u044f Prometheus \u0442\u0430\u043a\u0438\u0435 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u043a\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>internet_download_speed_mbps     # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>internet_upload_speed_mbps         # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442<\/p>\n<\/li>\n<li>\n<p>internet_ping_ms                             # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0438\u043d\u0433\u0430<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0438\u0436\u0435 &#8212; speedtest_exporter.py:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\"># metrics prometheus # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 # avg(internet_download_speed_mbps) by (instance) # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 # avg(internet_upload_speed_mbps) by (instance) # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0438\u043d\u0433\u0430 # avg(internet_ping_ms) by (instance)  from flask import Flask, Response import speedtest import logging import time import threading  app = Flask(__name__)  # \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')  # \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u0435\u0448\u0430 metrics_data = {     \"download_speed\": 0.0,     \"upload_speed\": 0.0,     \"ping\": 0.0 } last_test_time = 0 data_lock = threading.Lock() cache_duration = 60  # \u0418\u0437\u043c\u0435\u0440\u044f\u0435\u043c \u0440\u0430\u0437 \u0432 \u043c\u0438\u043d\u0443\u0442\u0443  def run_speedtest():     global metrics_data, last_test_time     while True:         try:             logging.info(\"\u0417\u0430\u043f\u0443\u0441\u043a \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.\")             st = speedtest.Speedtest()             st.get_best_server()                          download_speed = st.download() \/ 1e6  # \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432 \u041c\u0431\u0438\u0442\/\u0441             upload_speed = st.upload() \/ 1e6      # \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432 \u041c\u0431\u0438\u0442\/\u0441             ping = st.results.ping               # \u041f\u0438\u043d\u0433 \u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445                          with data_lock:                 metrics_data[\"download_speed\"] = download_speed                 metrics_data[\"upload_speed\"] = upload_speed                 metrics_data[\"ping\"] = ping                 last_test_time = time.time()              logging.info(f\"\u0418\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u044b: \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430: {download_speed:.2f} Mbps, \"                          f\"\u0412\u044b\u0433\u0440\u0443\u0437\u043a\u0430: {upload_speed:.2f} Mbps, \"                          f\"\u041f\u0438\u043d\u0433: {ping:.2f} ms.\")         except Exception as e:             logging.error(f\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0445: {str(e)}\")                  time.sleep(cache_duration)  @app.route('\/metrics') def metrics():     with data_lock:         response = f\"\"\" # HELP internet_download_speed_mbps Download speed in Mbps # TYPE internet_download_speed_mbps gauge internet_download_speed_mbps {metrics_data[\"download_speed\"]} # HELP internet_upload_speed_mbps Upload speed in Mbps # TYPE internet_upload_speed_mbps gauge internet_upload_speed_mbps {metrics_data[\"upload_speed\"]} # HELP internet_ping_ms Ping latency in milliseconds # TYPE internet_ping_ms gauge internet_ping_ms {metrics_data[\"ping\"]} \"\"\"     return Response(response, mimetype=\"text\/plain\")  if __name__ == '__main__':     # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0435\u0441\u0442 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435     speedtest_thread = threading.Thread(target=run_speedtest, daemon=True)     speedtest_thread.start()          # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c Flask     app.run(host='0.0.0.0', port=9101)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0430 2: \u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0445\u043e\u0441\u0442\u043e\u0432, \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0441\u0438\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0442 \u0442\u0440\u0430\u0444\u0438\u043a \u0440\u0430\u0441\u0445\u043e\u0434\u0443\u044e\u0449\u0438\u0445<\/h3>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/b6c\/f02\/f6f\/b6cf02f6f0dae380110408709214c4a0.jpg\" width=\"799\" height=\"662\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b6c\/f02\/f6f\/b6cf02f6f0dae380110408709214c4a0.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041c\u044b \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0432 \u043c\u0435\u0441\u0442\u0430\u0445 \u0435\u0433\u043e \u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f. \u0423\u0434\u043e\u0431\u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430 \u0448\u043b\u044e\u0437\u0435, \u0430 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u043e\u0443\u0442\u0435\u0440\u0435,  \u043b\u0438\u0431\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044f \u0437\u0435\u0440\u043a\u0430\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043d\u0430 \u043f\u043e\u0440\u0442, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. \u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b Orange Pi, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u0438 \u0434\u0435\u0448\u0435\u0432\u044b\u0435 \u0438 \u043c\u043d\u043e\u0433\u043e\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0434\u043d\u043e\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u044b \u043f\u0440\u043e\u0441\u0442\u044b \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u0443\u0434\u043e\u0431\u043d\u044b \u0432 \u0440\u0430\u0431\u043e\u0442\u0435. \u041e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0438 Orange Pi \u044f \u043f\u0438\u0441\u0430\u043b \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 <a href=\"https:\/\/habr.com\/p\/828556\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a> \u0438 <a href=\"https:\/\/habr.com\/p\/851834\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/f1d\/127\/24c\/f1d12724c549aea9658e49d9dcdbeab6.jpg\" width=\"803\" height=\"657\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f1d\/127\/24c\/f1d12724c549aea9658e49d9dcdbeab6.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u041a\u043e\u0434 \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0438\u0436\u0435 &#8212; traffic_monitor.py:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import time import logging from logging.handlers import RotatingFileHandler from collections import defaultdict from scapy.all import sniff, IP from threading import Thread import datetime import ipaddress   # ======================= # \u0421\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 # =======================  # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 5 \u043c\u0438\u043d\u0443\u0442) DELAY_SECONDS = 60  # 300 \u0441\u0435\u043a\u0443\u043d\u0434 = 5 \u043c\u0438\u043d\u0443\u0442  # \u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 NETWORKS = ['192.168.2.0\/24', '192.168.10.0\/24']  # \u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u044b # \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0435\u0442\u0438 NETWORK_BROADCAST_ADDRS = [str(ipaddress.IPv4Network(net).broadcast_address) for net in NETWORKS] # \u0428\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e BROADCAST_IP = '255.255.255.255'  # \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 traffic_stats = defaultdict(int) broadcast_traffic_stats = defaultdict(int)  # ======================= # \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u0440\u043e\u0442\u0430\u0446\u0438\u0435\u0439 # =======================  LOG_FILENAME = 'top_ip_traffic.log'  # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0440\u043e\u0442\u0430\u0446\u0438\u0438 \u043b\u043e\u0433\u043e\u0432 rotating_handler = RotatingFileHandler(     LOG_FILENAME,      # \u0418\u043c\u044f \u0444\u0430\u0439\u043b\u0430     maxBytes=5 * 1024 * 1024,  # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 (5 MB)     backupCount=3      # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0445 \u043a\u043e\u043f\u0438\u0439 (3 \u0444\u0430\u0439\u043b\u0430) )  # \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0444\u043e\u0440\u043c\u0430\u0442 \u043b\u043e\u0433\u043e\u0432 rotating_handler.setFormatter(logging.Formatter('%(message)s'))  # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043b\u043e\u0433\u0433\u0435\u0440 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0435\u043c\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0440\u043e\u0442\u0430\u0446\u0438\u0438 logger = logging.getLogger() logger.setLevel(logging.INFO) logger.addHandler(rotating_handler)   def packet_callback(packet):     \"\"\"Callback \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0430\u043a\u0435\u0442\u043e\u0432.\"\"\"     if IP in packet:         src_ip = packet[IP].src         dst_ip = packet[IP].dst         packet_length = len(packet)          # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u0430\u0434\u0440\u0435\u0441 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c         is_broadcast = False         if dst_ip == BROADCAST_IP or dst_ip in NETWORK_BROADCAST_ADDRS:             is_broadcast = True         # else:         #     for prefix in NETWORK_PREFIXES:         #         if dst_ip.startswith(prefix):         #             is_broadcast = True         #             break          if is_broadcast:             # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u0444\u0438\u043a \u0434\u043b\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0438 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             broadcast_traffic_stats[src_ip] += packet_length             broadcast_traffic_stats[dst_ip] += packet_length         else:             # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u0444\u0438\u043a \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             traffic_stats[src_ip] += packet_length             traffic_stats[dst_ip] += packet_length  def log_top_ips():     \"\"\"\u041b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u043c \u0442\u0440\u0430\u0444\u0438\u043a\u043e\u043c.\"\"\"     while True:         time.sleep(DELAY_SECONDS)         try:             # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438             now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')              # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u043e\u043f IP \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             top_ips = sorted(traffic_stats.items(), key=lambda x: x[1], reverse=True)[:10]             # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u043e\u043f IP \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             top_broadcast_ips = sorted(broadcast_traffic_stats.items(), key=lambda x: x[1], reverse=True)[:10]              # \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e             logging.info(\".\" * 37)             logging.info(f\"  \u0422\u043e\u043f 10 IP \u043d\u0430 {now}\")             logging.info(\"            \u041e\u0431\u0449\u0438\u0439 \u0442\u0440\u0430\u0444\u0438\u043a \")             # \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b             logging.info(f\"{'IP-\u0430\u0434\u0440\u0435\u0441':&lt;20} {'\u0411\u0430\u0439\u0442\u044b':&gt;15}\")             logging.info(\"-\" * 35)             for ip, bytes_used in top_ips:                 logging.info(f\"{ip:&lt;20} {bytes_used:&gt;15}\")              logging.info(\"      \u0428\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0442\u0440\u0430\u0444\u0438\u043a \")             logging.info(f\"{'IP-\u0430\u0434\u0440\u0435\u0441':&lt;20} {'\u0411\u0430\u0439\u0442\u044b':&gt;15}\")             logging.info(\"-\" * 35)             for ip, bytes_used in top_broadcast_ips:                 logging.info(f\"{ip:&lt;20} {bytes_used:&gt;15}\")             logging.info(\".\" * 37)              # \u041e\u0447\u0438\u0449\u0430\u0435\u043c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 \u043e\u0431\u043e\u0438\u0445 \u0442\u0438\u043f\u043e\u0432 \u0442\u0440\u0430\u0444\u0438\u043a\u0430             traffic_stats.clear()             broadcast_traffic_stats.clear()         except Exception as e:             logging.error(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438: {e}\")  def main():     \"\"\"\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0440\u0430\u0444\u0438\u043a\u0430.\"\"\"     try:         # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f         logger_thread = Thread(target=log_top_ips, daemon=True)         logger_thread.start()          # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0437\u0430\u0445\u0432\u0430\u0442 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0441 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u043c         # \u0424\u0438\u043b\u044c\u0442\u0440 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e IP-\u043f\u0430\u043a\u0435\u0442\u044b \u0434\u043b\u044f \u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438         sniff(iface=None, prn=packet_callback, store=0, filter=\"ip\")      except KeyboardInterrupt:         logging.info(\"\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c...\")     except Exception as e:         logging.error(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: {e}\")  # \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b if __name__ == \"__main__\":     main() <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0441\u043b\u0443\u0436\u0431 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0432\u044b\u0448\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432 \u0437\u0430\u0439\u043c\u0435\u0442\u0441\u044f Ansible. \u041f\u043b\u0435\u0439\u0431\u0443\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u043b, \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0438\u0436\u0435: ansible_speedtest.yml.<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">--- - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 Python \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Speedtest Exporter   hosts: localhost   tasks:     - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432       apt:         update_cache: true       become: true      - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0432       apt:         name:           - curl           - python3-venv           - python3.11-dev           - libffi-dev           - libssl-dev           - nmap         state: present       become: true      - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f       stat:         path: \"\/etc\/apt\/sources.list.d\/ookla_speedtest-cli.list\"       register: speedtest_repo      - name: \u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u044f speedtest-cli       shell: curl -s https:\/\/packagecloud.io\/install\/repositories\/ookla\/speedtest-cli\/script.deb.sh | bash       args:         executable: \/bin\/bash       become: true       when: not speedtest_repo.stat.exists      - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 speedtest       apt:         name: speedtest         state: present       become: true      - name: \u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f       stat:         path: \"{{ playbook_dir }}\/.venv\"       register: venv_directory      - name: \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f       command: python3 -m venv .venv       args:         chdir: \"{{ playbook_dir }}\"       when: not venv_directory.stat.exists      - name: \u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c pip \u0438 setuptools \u0432 \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u043c \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u0438       pip:         name:           - pip           - setuptools         state: latest         virtualenv: \"{{ playbook_dir }}\/.venv\"      - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438\u0437 requirements.txt       pip:         requirements: \"{{ playbook_dir }}\/requirements.txt\"         virtualenv: \"{{ playbook_dir }}\/.venv\"          # \u0441\u043b\u0443\u0436\u0431\u044b     # speedtest exporter     - name: \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0441\u043b\u0443\u0436\u0431\u044b \u0434\u043b\u044f speedtest exporter       become: true       copy:         dest: \/etc\/systemd\/system\/speedtest_exporter.service         content: |           [Unit]           Description=\u0421\u043b\u0443\u0436\u0431\u0430 Speedtest Exporter           After=network.target            [Service]           User={{ ansible_env.USER }}           WorkingDirectory={{ playbook_dir }}           ExecStart={{ playbook_dir }}\/.venv\/bin\/python3 {{ playbook_dir }}\/speedtest_exporter.py           Restart=always           Environment=PYTHONUNBUFFERED=1            [Install]           WantedBy=multi-user.target      - name: \u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0441\u043b\u0443\u0436\u0431       become: true       command: systemctl daemon-reload      - name: \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u043b\u0443\u0436\u0431\u044b speedtest_exporter       become: true       systemd:         name: speedtest_exporter         state: started         enabled: yes      ## traffic monitor     - name: \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u0444\u0430\u0439\u043b\u0430 \u0441\u043b\u0443\u0436\u0431\u044b \u0434\u043b\u044f traffic monitor (\u043f\u0440\u0438\u0432\u0438\u043b\u0435\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 \u0440\u0435\u0436\u0438\u043c)       become: true       copy:         dest: \/etc\/systemd\/system\/traffic_monitor.service         content: |           [Unit]           Description=\u0421\u043b\u0443\u0436\u0431\u0430 Traffic Monitor           After=network.target            [Service]           User=root           WorkingDirectory={{ playbook_dir }}           ExecStart={{ playbook_dir }}\/.venv\/bin\/python3 {{ playbook_dir }}\/traffic_monitor.py           Restart=always           RestartSec=5s           TimeoutSec=30           StandardOutput=journal           StandardError=journal           Environment=PYTHONUNBUFFERED=1            [Install]           WantedBy=multi-user.target      - name: \u041f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0430 \u0441\u0438\u0441\u0442\u0435\u043c\u043d\u044b\u0445 \u0441\u043b\u0443\u0436\u0431       become: true       command: systemctl daemon-reload      - name: \u0417\u0430\u043f\u0443\u0441\u043a \u0441\u043b\u0443\u0436\u0431\u044b traffic_monitor       become: true       systemd:         name: traffic_monitor         state: restarted         enabled: yes<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0430 3: \u0418\u0437\u0443\u0447\u0435\u043d\u0438\u0435 \u0445\u043e\u0441\u0442\u043e\u0432, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0449\u0435\u0433\u043e \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u0442\u0440\u0430\u0444\u0438\u043a, \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c<\/h3>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c Grafana \u043a Prometheus \u0438 \u0438\u0437\u0443\u0447\u0430\u0435\u043c \u043f\u0435\u0440\u0438\u043e\u0434\u044b \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0439 \u0441\u0440\u0435\u0434\u044b.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/c8c\/cef\/2fd\/c8ccef2fdba359ff157fd91651276bb7.jpg\" alt=\"\u0421\u0440\u0435\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u0430\u044f, \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0438\u0445 \u0445\u043e\u0441\u0442\u043e\u0432 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0435\u0440\u043a\u0430\u043b\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432, \u0434\u043b\u044f \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430\" title=\"\u0421\u0440\u0435\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u0430\u044f, \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0438\u0445 \u0445\u043e\u0441\u0442\u043e\u0432 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0435\u0440\u043a\u0430\u043b\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432, \u0434\u043b\u044f \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430\" width=\"2501\" height=\"1039\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/c8c\/cef\/2fd\/c8ccef2fdba359ff157fd91651276bb7.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>\u0421\u0440\u0435\u0434\u0430 \u0442\u0435\u0441\u0442\u043e\u0432\u0430\u044f, \u043d\u0430 \u043e\u0434\u043d\u043e\u043c \u0438\u0445 \u0445\u043e\u0441\u0442\u043e\u0432 \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0437\u0435\u0440\u043a\u0430\u043b\u0430 \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0435\u0432, \u0434\u043b\u044f \u0438\u043b\u043b\u044e\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a\u0436\u0435 Alertmanager \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u043e \u043f\u043e\u0440\u043e\u0433\u043e\u0432\u044b\u043c \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f\u043c \u043c\u0435\u0442\u0440\u0438\u043a. \u0412 \u043c\u043e\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u043f\u0440\u0438 \u043f\u0430\u0434\u0435\u043d\u0438\u0438 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043d\u0438\u0436\u0435 \u0434\u043e\u043f\u0443\u0441\u0442\u0438\u043c\u043e\u0433\u043e, \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u0442 \u043e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c. <\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0432 \u043c\u043e\u043c\u0435\u043d\u0442\u044b \u043f\u0430\u0434\u0435\u043d\u0438\u044f \u0433\u0440\u0430\u0444\u0438\u043a\u0430 \u043c\u044b \u0438\u0437\u0443\u0447\u0430\u0435\u043c \u043b\u043e\u0433 \u043d\u0430\u0448\u0435\u0433\u043e \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c ip \u0430\u0434\u0440\u0435\u0441\u0430 \u0443\u0437\u043b\u043e\u0432, \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0449\u0438\u0445 \u0442\u0440\u0430\u0444\u0438\u043a:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/7ff\/7e6\/5ff\/7ff7e65ff04732226dcd388c3905ada1.jpg\" alt=\"\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0443\u0437\u0435\u043b, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430\" title=\"\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0443\u0437\u0435\u043b, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430\" width=\"554\" height=\"679\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7ff\/7e6\/5ff\/7ff7e65ff04732226dcd388c3905ada1.jpg\" data-blurred=\"true\"\/><\/p>\n<div><figcaption>\u041d\u0430 \u043f\u0435\u0440\u0432\u043e\u043c \u043c\u0435\u0441\u0442\u0435 \u0443\u0437\u0435\u043b, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0449\u0438\u0439 \u0431\u043e\u043b\u044c\u0448\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430<\/figcaption><\/div>\n<\/figure>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u043f\u043e\u0434\u043e\u0437\u0440\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0439 ip \u043d\u0430\u0439\u0434\u0435\u043d, \u043c\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e \u043c\u044b \u0445\u043e\u0442\u0438\u043c \u0443\u0437\u043d\u0430\u0442\u044c \u043e \u043d\u0435\u043c:<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w780q1\/getpro\/habr\/upload_files\/3af\/832\/70c\/3af83270c91c8acf9eef120f5671844e.jpg\" width=\"823\" height=\"668\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/3af\/832\/70c\/3af83270c91c8acf9eef120f5671844e.jpg\" data-blurred=\"true\"\/><\/figure>\n<p>\u0418\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u044f, \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0443\u0442\u0438\u043b\u0438\u0442\u0443 nmap, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u0442\u043e\u0440\u043e\u0439 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u043c \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c \u043e\u0431\u044b\u0447\u043d\u043e\u0435, \u0430 \u0437\u0430\u0442\u0435\u043c \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u041d\u0430\u043f\u0438\u0448\u0435\u043c \u043d\u0443\u0436\u043d\u044b\u0439 \u0441\u043a\u0440\u0438\u043f\u0442 \u043d\u0430 python:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import sys import socket import requests from pythonping import ping import re import nmap import datetime import time import subprocess  def validate_ip(ip):     pattern = re.compile(r\"^(?:(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(?:25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$\")     return pattern.match(ip) is not None  def reverse_dns(ip):     try:         return socket.gethostbyaddr(ip)[0]     except socket.herror:         return \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043d\u0430\u0439\u0442\u0438 \u0434\u043e\u043c\u0435\u043d\u043d\u043e\u0435 \u0438\u043c\u044f (reverse DNS lookup).\"        def check_ping(ip):     \"\"\"     \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e\u0441\u0442\u044c \u0445\u043e\u0441\u0442\u0430 \u043f\u043e IP \u0430\u0434\u0440\u0435\u0441\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b ping.      :param ip: IP-\u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430     :return: \u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043a\u043b\u0438\u043a\u0430 \u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 (float) \u0435\u0441\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d, \u0438\u043d\u0430\u0447\u0435 False     \"\"\"     try:         # \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 ping \u0441 1 \u043f\u0430\u043a\u0435\u0442\u043e\u043c         output = subprocess.check_output(             [\"ping\", \"-c\", \"1\", \"-W\", \"1\", ip],             stderr=subprocess.STDOUT,             universal_newlines=True         )         # \u041f\u0430\u0440\u0441\u0438\u043c \u0432\u044b\u0432\u043e\u0434 ping \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u0440\u0435\u0434\u043d\u0435\u0433\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043e\u0442\u043a\u043b\u0438\u043a\u0430         for line in output.splitlines():             if \"time=\" in line:                 # \u041f\u0440\u0438\u043c\u0435\u0440 \u0441\u0442\u0440\u043e\u043a\u0438: \"64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=14.2 ms\"                 time_ms = float(line.split(\"time=\")[1].split(\" \")[0])                 return time_ms         return False     except subprocess.CalledProcessError:         return False  def wait_for_host(ip, timeout=3600, interval=60):     \"\"\"     \u0416\u0434\u0435\u0442, \u043f\u043e\u043a\u0430 \u0445\u043e\u0441\u0442 \u0441\u0442\u0430\u043d\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u043b\u0438 \u0438\u0441\u0442\u0435\u0447\u0435\u0442 \u0442\u0430\u0439\u043c\u0430\u0443\u0442.      :param ip: IP-\u0430\u0434\u0440\u0435\u0441 \u0445\u043e\u0441\u0442\u0430     :param timeout: \u0422\u0430\u0439\u043c\u0430\u0443\u0442 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 1 \u0447\u0430\u0441)     :param interval: \u0418\u043d\u0442\u0435\u0440\u0432\u0430\u043b \u043c\u0435\u0436\u0434\u0443 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u043c\u0438 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 60 \u0441\u0435\u043a\u0443\u043d\u0434)     :return: True \u0435\u0441\u043b\u0438 \u0445\u043e\u0441\u0442 \u0441\u0442\u0430\u043b \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d, False \u0435\u0441\u043b\u0438 \u0442\u0430\u0439\u043c\u0430\u0443\u0442 \u0438\u0441\u0442\u0435\u043a     \"\"\"     start_time = datetime.datetime.now()     end_time = start_time + datetime.timedelta(seconds=timeout)     while datetime.datetime.now() &lt; end_time:         ping_result = check_ping(ip)         if isinstance(ping_result, float):             print(f\"\u0425\u043e\u0441\u0442 {ip} \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043a\u043b\u0438\u043a\u0430: {ping_result} ms\")             return True         else:             remaining = end_time - datetime.datetime.now()             remaining_seconds = int(remaining.total_seconds())             hours, remainder = divmod(remaining_seconds, 3600)             minutes, seconds = divmod(remainder, 60)             print(f\"\u0425\u043e\u0441\u0442 {ip} \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d. \u041e\u0436\u0438\u0434\u0430\u043d\u0438\u0435... \u041e\u0441\u0442\u0430\u043b\u043e\u0441\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438: {hours}\u0447 {minutes}\u043c\u0438\u043d {seconds}\u0441\")             time.sleep(interval)     print(f\"\u0422\u0430\u0439\u043c\u0430\u0443\u0442 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0445\u043e\u0441\u0442\u0430 {ip} \u0438\u0441\u0442\u0435\u043a \u043f\u043e\u0441\u043b\u0435 {timeout \/ 3600} \u0447\u0430\u0441\u0430(\u043e\u0432).\")     return False  def geolocation(ip):     try:         response = requests.get(             f\"http:\/\/ip-api.com\/json\/{ip}?fields=status,country,regionName,city,zip,lat,lon,timezone,isp,org,as\",             timeout=10         )         data = response.json()         if data.get('status') == 'success':             return data         else:             return {\"error\": \"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0433\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044e.\"}     except Exception as e:         return {\"error\": str(e)}  def print_geolocation(geo):     if 'error' in geo:         print(f\"\u0413\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044f: {geo['error']}\")     else:         print(\"\u0413\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044f:\")         print(f\"  \u0421\u0442\u0440\u0430\u043d\u0430: {geo.get('country')}\")         print(f\"  \u0420\u0435\u0433\u0438\u043e\u043d: {geo.get('regionName')}\")         print(f\"  \u0413\u043e\u0440\u043e\u0434: {geo.get('city')}\")         print(f\"  \u041f\u043e\u0447\u0442\u043e\u0432\u044b\u0439 \u0438\u043d\u0434\u0435\u043a\u0441: {geo.get('zip')}\")         print(f\"  \u0428\u0438\u0440\u043e\u0442\u0430: {geo.get('lat')}\")         print(f\"  \u0414\u043e\u043b\u0433\u043e\u0442\u0430: {geo.get('lon')}\")         print(f\"  \u0427\u0430\u0441\u043e\u0432\u043e\u0439 \u043f\u043e\u044f\u0441: {geo.get('timezone')}\")         print(f\"  \u041f\u0440\u043e\u0432\u0430\u0439\u0434\u0435\u0440: {geo.get('isp')}\")         print(f\"  \u041e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u044f: {geo.get('org')}\")         print(f\"  \u0410\u0432\u0442\u043e\u043d\u043e\u043c\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430: {geo.get('as')}\")   def scan_ip(ip):     \"\"\"     \u0421\u043a\u0430\u043d\u0438\u0440\u0443\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441 \u0438 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.          \u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b:     ip (str): IP-\u0430\u0434\u0440\u0435\u0441 \u0434\u043b\u044f \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f.     \"\"\"     scanner = nmap.PortScanner()     try:         # \u041f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u043e\u043f\u0446\u0438\u044f\u043c\u0438:         # -sS : TCP SYN scan         # -sV : \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0432\u0435\u0440\u0441\u0438\u0439 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432         # -O   : \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b         scanner.scan(ip, arguments='-sS -sV -O')          result = {}         host_info = scanner[ip]                  # \u0418\u043d\u0438\u0446\u0438\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f         result['hostname'] = \"\u041d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e\"         result['mac_address'] = \"\u041d\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d\"         result['open_ports'] = []         result['services'] = {}         result['os'] = \"\u041d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0430\"          # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043c\u0435\u043d\u0438 \u0445\u043e\u0441\u0442\u0430         if 'hostnames' in host_info and host_info['hostnames']:             hostname = host_info['hostnames'][0]['name']             result['hostname'] = hostname if hostname else \"\u041d\u0435 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043e\"          # \u041f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u0435 MAC-\u0430\u0434\u0440\u0435\u0441\u0430         if 'addresses' in host_info:             if 'mac' in host_info['addresses']:                 result['mac_address'] = host_info['addresses']['mac']          # \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 TCP-\u043f\u043e\u0440\u0442\u043e\u0432         if 'tcp' in host_info:             for port in host_info['tcp']:                 state = host_info['tcp'][port]['state']                 if state == 'open':                     result['open_ports'].append(port)                     service_name = host_info['tcp'][port]['name']                     service_product = host_info['tcp'][port].get('product', '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e')                     service_version = host_info['tcp'][port].get('version', '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e')                     result['services'][port] = f\"{service_name} ({service_product} {service_version})\"          # \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 UDP-\u043f\u043e\u0440\u0442\u043e\u0432 (\u043e\u043f\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u043e)         if 'udp' in host_info:             for port in host_info['udp']:                 state = host_info['udp'][port]['state']                 if state == 'open':                     result['open_ports'].append(port)                     service_name = host_info['udp'][port]['name']                     service_product = host_info['udp'][port].get('product', '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e')                     service_version = host_info['udp'][port].get('version', '\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u043e')                     result['services'][port] = f\"{service_name} ({service_product} {service_version})\"          # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b         if 'osmatch' in host_info and host_info['osmatch']:             result['os'] = host_info['osmatch'][0]['name']          # \u0412\u044b\u0432\u043e\u0434 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u0432         print(f\"\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f {ip}:\")         print(f\"  \u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430: {result['hostname']}\")         print(f\"  MAC-\u0430\u0434\u0440\u0435\u0441: {result['mac_address']}\")         print(f\"  \u041e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0438\u0441\u0442\u0435\u043c\u0430: {result['os']}\")                  if result[\"open_ports\"]:         # \u0421\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u043a\u0430 \u043f\u043e\u0440\u0442\u043e\u0432 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430             open_ports_sorted = sorted(result[\"open_ports\"])             print(\"  \u041e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u043f\u043e\u0440\u0442\u044b:\", \", \".join(map(str, open_ports_sorted)))             print(\"  \u0414\u0435\u0442\u0430\u043b\u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u043e\u0432:\")             for port in open_ports_sorted:                 service_info = result[\"services\"].get(port, \"\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043d\u0435\u0434\u043e\u0441\u0442\u0443\u043f\u043d\u0430\")                 print(f\"    \u041f\u043e\u0440\u0442 {port}: {service_info}\")         else:             print(\"  \u041e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043f\u043e\u0440\u0442\u043e\u0432 \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e.\")      except Exception as e:         print(f\"\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043b\u044f {ip}:\")         print(f\"  \u041e\u0448\u0438\u0431\u043a\u0430: {str(e)}\")           def aggressive_scan(ip):     \"\"\"     \u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442 \"\u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0435\" \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 ip \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e Nmap.          :param ip: IP-\u0430\u0434\u0440\u0435\u0441 (\u0438\u043b\u0438 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043f\u0440\u043e\u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c.     :return: \u0421\u043b\u043e\u0432\u0430\u0440\u044c \u0441 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0435\u0439 \u043e\u0431 \u0443\u0437\u043b\u0435:         {             'hostname': str (\u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 \u0438\u043b\u0438 None),             'mac_address': str (MAC-\u0430\u0434\u0440\u0435\u0441 \u0438\u043b\u0438 None),             'open_ports': \u0441\u043f\u0438\u0441\u043e\u043a \u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043f\u043e\u0440\u0442\u043e\u0432,             'services': \u0441\u043b\u043e\u0432\u0430\u0440\u044c {\u043f\u043e\u0440\u0442: {\u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b, \u0441\u0435\u0440\u0432\u0438\u0441, \u043f\u0440\u043e\u0434\u0443\u043a\u0442, \u0432\u0435\u0440\u0441\u0438\u044f}},             'os': \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0445 \u041e\u0421 (\u0441 \u0438\u0445 \u0431\u0430\u043b\u043b\u0430\u043c\u0438 \u0441\u043e\u0432\u043f\u0430\u0434\u0435\u043d\u0438\u044f) \u0438\u043b\u0438 None         }     \"\"\"     scanner = nmap.PortScanner()          # \u0410\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b:     #   -A  \u2014 \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 (\u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0432\u0435\u0440\u0441\u0438\u0439 \u0438 \u041e\u0421)     #   -T4 \u2014 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f (T4 \u043e\u0431\u044b\u0447\u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u0430\u044f)     #   1-65535 \u2014 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d \u0432\u0441\u0435\u0445 \u043f\u043e\u0440\u0442\u043e\u0432 (\u043c\u043e\u0436\u043d\u043e \u0441\u0443\u0437\u0438\u0442\u044c \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438)     scanner.scan(hosts=ip, ports=\"1-65535\", arguments=\"-A -T4\")      results = {         'hostname': None,         'mac_address': None,         'open_ports': [],         'services': {},         'os': None     }      if ip not in scanner.all_hosts():         return results  # \u0425\u043e\u0441\u0442 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d \u0438\u043b\u0438 \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b          host_info = scanner[ip]      # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0438\u043c\u044f \u0445\u043e\u0441\u0442\u0430 (\u0435\u0441\u043b\u0438 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c)     hostname = host_info.hostname()     if hostname:         results['hostname'] = hostname          # MAC-\u0430\u0434\u0440\u0435\u0441 (\u0435\u0441\u043b\u0438 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c)     if 'addresses' in host_info and 'mac' in host_info['addresses']:         results['mac_address'] = host_info['addresses']['mac']      # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u041e\u0421     if 'osmatch' in host_info:         results['os'] = [             {                 'name': os_item['name'],                 'accuracy': os_item['accuracy'],                 'os_family': os_item.get('osclass', [{}])[0].get('osfamily')             }             for os_item in host_info['osmatch']         ]          # \u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b\u043e\u0432 (tcp, udp \u0438 \u0442.\u0434.)     for proto in host_info.all_protocols():         ports = host_info[proto].keys()         for port in ports:             port_state = host_info[proto][port]['state']             if port_state == 'open':                 results['open_ports'].append(port)                                  # \u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u043e \u0441\u0435\u0440\u0432\u0438\u0441\u0435                 service_name = host_info[proto][port].get('name', '')                 service_product = host_info[proto][port].get('product', '')                 service_version = host_info[proto][port].get('version', '')                                  results['services'][port] = {                     'protocol': proto,                     'service': service_name,                     'product': service_product,                     'version': service_version                 }      return results   def print_scan_results(scan_data):     \"\"\"     \u041f\u0435\u0447\u0430\u0442\u0430\u0435\u0442 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0432 \u043a\u043e\u043d\u0441\u043e\u043b\u044c.     \"\"\"     print(\"\\n\u0420\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u044b \u0430\u0433\u0440\u0435\u0441\u0441\u0438\u0432\u043d\u043e\u0433\u043e \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f:\")     if not scan_data['hostname'] and not scan_data['open_ports']:         print(\"\u0425\u043e\u0441\u0442 \u043d\u0435 \u043e\u0442\u0432\u0435\u0442\u0438\u043b \u0438\u043b\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d.\")         return      print(f\"\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430: {scan_data['hostname']}\")     print(f\"MAC-\u0430\u0434\u0440\u0435\u0441: {scan_data['mac_address']}\")          if scan_data['os']:         print(\"\u0412\u043e\u0437\u043c\u043e\u0436\u043d\u044b\u0435 \u041e\u0421:\")         for os_info in scan_data['os']:             print(f\"  - {os_info['name']} (\u0442\u043e\u0447\u043d\u043e\u0441\u0442\u044c: {os_info['accuracy']}%), \u0441\u0435\u043c\u0435\u0439\u0441\u0442\u0432\u043e: {os_info['os_family']}\")     else:         print(\"\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u041e\u0421.\")      if scan_data['open_ports']:         print(\"\\n\u041e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u043f\u043e\u0440\u0442\u044b \u0438 \u0441\u0435\u0440\u0432\u0438\u0441\u044b:\")         for port in sorted(scan_data['open_ports']):             service_info = scan_data['services'][port]             print(f\"  \u041f\u043e\u0440\u0442 {port}\/{service_info['protocol']}: {service_info['service']} \"                   f\"(\u043f\u0440\u043e\u0434\u0443\u043a\u0442: {service_info['product']}, \u0432\u0435\u0440\u0441\u0438\u044f: {service_info['version']})\")     else:         print(\"\u041e\u0442\u043a\u0440\u044b\u0442\u044b\u0445 \u043f\u043e\u0440\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e.\")               def main():     if len(sys.argv) != 2:         print(\"\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435: python py_ip_monitor.py &lt;IP-\u0430\u0434\u0440\u0435\u0441&gt;\")         sys.exit(1)          ip = sys.argv[1]          if not validate_ip(ip):         print(\"\u041d\u0435\u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u044b\u0439 IP-\u0430\u0434\u0440\u0435\u0441.\")         sys.exit(1)          print(f\"\\n\u0418\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f \u0434\u043b\u044f IP: {ip}\\n{'='*40}\")          # \u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 DNS     dns = reverse_dns(ip)     print(f\"\u041e\u0431\u0440\u0430\u0442\u043d\u044b\u0439 DNS: {dns}\")          # \u0413\u0435\u043e\u043b\u043e\u043a\u0430\u0446\u0438\u044f     geo = geolocation(ip)     print_geolocation(geo)          # Ping     ping_result = check_ping(ip)     if isinstance(ping_result, float):         print(f\"\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043a\u043b\u0438\u043a\u0430 (ping): {ping_result} ms\")     else:         print(f\"Ping: {ping_result}\")          # \u0421\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432     scan_ip(ip)          # 4. \u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 (aggressive_scan + \u0432\u044b\u0432\u043e\u0434)     scan_result = aggressive_scan(ip)     print_scan_results(scan_result)  if __name__ == \"__main__\":     main()<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u043c \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0438 \u043f\u043e\u043b\u0443\u0447\u0438\u043c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0434\u0430\u043d\u043d\u044b\u0435:<\/p>\n<p> mac-\u0430\u0434\u0440\u0435\u0441<\/p>\n<p> \u0442\u0438\u043f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439 \u0441\u0438\u0441\u0442\u0435\u043c\u044b<\/p>\n<p>\u043e\u0442\u043a\u0440\u044b\u0442\u044b\u0435 \u043f\u043e\u0440\u0442\u044b<\/p>\n<p>\u0412\u0441\u0435 \u044d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u043f\u043e\u043d\u044f\u0442\u044c \u043a\u0430\u043a\u0438\u0435 \u0441\u0435\u0440\u0432\u0438\u0441\u044b \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u044b \u043d\u0430 \u0443\u0437\u043b\u0435 \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0435\u0433\u043e \u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/33d\/7ea\/5e7\/33d7ea5e7837f0f0f85ba19bbe788933.png\" width=\"1039\" height=\"772\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/33d\/7ea\/5e7\/33d7ea5e7837f0f0f85ba19bbe788933.png\"\/><\/figure>\n<p>\u0415\u0441\u043b\u0438 \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0430\u043d\u043e\u043c\u0430\u043b\u044c\u043d\u044b\u043c \u0443\u0437\u043b\u0430\u043c \u0435\u0441\u0442\u044c, \u0438 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430 \u043d\u0438\u0445 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0442\u043e \u0438\u0437\u0443\u0447\u0430\u0435\u043c \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b \u0438 \u0438\u0449\u0435\u043c \u0442\u0435, \u0447\u0442\u043e \u0430\u043a\u0442\u0438\u0432\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0441\u0435\u0442\u0435\u0432\u043e\u0435 \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 c \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u044b <strong>nethogs<\/strong>.<\/p>\n<h3>\u0418\u0442\u043e\u0433<\/h3>\n<p>\u0427\u0442\u043e \u0442\u0435\u043f\u0435\u0440\u044c \u0441 \u044d\u0442\u0438\u043c \u0434\u0435\u043b\u0430\u0442\u044c? \u0417\u0430\u0432\u0438\u0441\u0438\u0442 \u043e\u0442 \u0432\u0430\u0448\u0438\u0445 \u0437\u0430\u0434\u0430\u0447.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u0443\u0437\u0435\u043b \u0440\u0430\u0441\u0445\u043e\u0434\u0443\u0435\u0442 \u0442\u0440\u0430\u0444\u0438\u043a \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043d\u0435 \u0434\u043e\u043b\u0436\u0435\u043d \u0440\u0430\u0441\u0445\u043e\u0434\u043e\u0432\u0430\u0442\u044c, \u0442\u043e \u043f\u0440\u043e\u0449\u0435 \u0432\u0441\u0435\u0433\u043e \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c MAC-\u0430\u0434\u0440\u0435\u0441 \u043d\u0430 \u0440\u043e\u0443\u0442\u0435\u0440\u0435, \u0434\u043e\u0431\u0430\u0432\u0438\u0432 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0435\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e, \u0438\u043b\u0438 \u0432\u043d\u0435\u0441\u0442\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0444\u0430\u0439\u0440\u0432\u043e\u043b (\u043f\u0440\u0438 \u0435\u0433\u043e \u043d\u0430\u043b\u0438\u0447\u0438\u0438).<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u0438\u0437\u0443\u0447\u0430\u044f arp \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 \u043c\u0435\u0441\u0442\u043e\u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.<\/p>\n<p>\u0412 \u043c\u043e\u0435\u043c \u0436\u0435 \u0441\u043b\u0443\u0447\u0430\u0435 \u0432\u0438\u043d\u043e\u0432\u043d\u0438\u043a\u043e\u043c \u0431\u044b\u043b \u0441\u043c\u0430\u0440\u0442-\u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440, \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0432\u0435\u0441\u044c\u043c\u0430 \u0447\u0443\u0432\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043b\u044f \u0441\u0435\u0442\u0438, \u043f\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c\u043e\u0439 \u0447\u0435\u0440\u0435\u0437 VPN. \u0425\u043e\u0437\u044f\u0438\u043d \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0431\u044b\u043b \u043d\u0430\u0439\u0434\u0435\u043d, \u0430 \u0442\u0435\u043b\u0435\u0432\u0438\u0437\u043e\u0440 \u0438\u0437\u044a\u044f\u0442.<\/p>\n<p>\u0412 \u0434\u0430\u043d\u043d\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u043c\u044b \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440\u043e\u0432 \u0434\u043b\u044f prometheus, \u0441\u043f\u043e\u0441\u043e\u0431\u044b \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433\u0430 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0432 \u0441\u0435\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u0431\u044b\u0441\u0442\u0440\u043e\u0433\u043e \u0440\u0430\u0437\u0432\u0435\u0440\u0442\u044b\u0432\u0430\u043d\u0438\u044f \u043d\u0430\u0448\u0438\u0445 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432 \u043d\u0430 \u0443\u0437\u043b\u0430\u0445 \u0441 \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u043c\u0438 Ansible.<\/p>\n<p> \u0412\u0441\u0435\u043c \u0441\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/870998\/\"> https:\/\/habr.com\/ru\/articles\/870998\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<figure class=\"full-width\"><\/figure>\n<p>\u041d\u0435\u0434\u0430\u0432\u043d\u043e \u043c\u0435\u043d\u044f \u043f\u043e\u043f\u0440\u043e\u0441\u0438\u043b\u0438 \u043f\u043e\u043c\u043e\u0447\u044c \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0438 \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u0432 \u0443\u0442\u0435\u0447\u043a\u0438 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0432 \u043e\u0434\u043d\u043e\u0439 \u0438\u0437 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0439. \u0417\u0430\u0434\u0430\u0447\u0443 \u0443\u0441\u0443\u0433\u0443\u0431\u043b\u044f\u043b\u043e \u0431\u043e\u043b\u044c\u0448\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432 \u0432 \u043e\u0434\u043d\u043e\u043c \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u043c \u0434\u043e\u043c\u0435\u043d\u0435, \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043d\u0435\u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u043c\u044b\u0445 \u043a\u043e\u043c\u043c\u0443\u0442\u0430\u0442\u043e\u0440\u043e\u0432, \u043e\u0442\u0441\u0443\u0442\u0441\u0442\u0432\u0438\u0435 \u043b\u044e\u0431\u043e\u0439 \u043a\u0430\u0440\u0442\u044b \u0441\u0435\u0442\u0438, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u0442\u0430\u0440\u0435\u043d\u044c\u043a\u0438\u0439 \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0430 \u0432\u0445\u043e\u0434\u0435. \u0412 \u043e\u0431\u0449\u0435\u043c, \u044d\u0442\u043e \u0431\u044b\u043b\u0438 \u043d\u0430\u0441\u0442\u043e\u044f\u0449\u0438\u0435 &#171;\u0410\u0432\u0433\u0438\u0435\u0432\u044b \u043a\u043e\u043d\u044e\u0448\u043d\u0438&#187;, \u043d\u043e \u0432 \u0438\u0442\u043e\u0433\u0435 \u0437\u0430\u0434\u0430\u0447\u0430 \u0431\u044b\u043b\u0430 \u0440\u0435\u0448\u0435\u043d\u0430, \u0438 \u0434\u0430\u043d\u043d\u0430\u044f \u0441\u0442\u0430\u0442\u044c\u044f \u043f\u043e\u0441\u0432\u044f\u0449\u0435\u043d\u0430 \u043c\u0435\u0442\u043e\u0434\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b. \u041a\u0442\u043e \u043e\u043a\u0430\u0437\u0430\u043b\u0441\u044f \u0432\u0438\u043d\u043e\u0432\u043d\u0438\u043a\u043e\u043c, \u044f \u0440\u0430\u0441\u043a\u0440\u043e\u044e \u0432 \u043a\u043e\u043d\u0446\u0435 \u0441\u0442\u0430\u0442\u044c\u0438, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u043f\u043e\u0440\u0442\u0438\u0442\u044c \u0438\u043d\u0442\u0440\u0438\u0433\u0443.<\/p>\n<div class=\"persona\">\n<h5 class=\"persona__heading\">\u041c\u043e\u0439 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c\u043c \u043a\u0430\u043d\u0430\u043b &#8212; \u0441\u043e\u043e\u0431\u0449\u0435\u0441\u0442\u0432\u043e, \u0433\u0434\u0435 \u0434\u0435\u043b\u044f\u0442\u0441\u044f \u043e\u043f\u044b\u0442\u043e\u043c<\/h5>\n<p><a href=\"https:\/\/t.me\/IT_Chuyana\" rel=\"noopener noreferrer nofollow\"><u>https:\/\/t.me\/IT_Chuyana<\/u><\/a> <\/p>\n<\/div>\n<p>\u0412 \u0442\u0435\u043a\u0443\u0449\u0435\u0439 \u0437\u0430\u0434\u0430\u0447\u0435 \u044f \u043f\u0440\u0438\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u043b\u0441\u044f \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u043f\u0440\u0438\u043d\u0446\u0438\u043f\u043e\u0432:<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u0414\u043b\u044f \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0430\u043d\u043e\u043c\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u044f \u0440\u0430\u0437\u0431\u0438\u043b \u0437\u0430\u0434\u0430\u0447\u0443 \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u043e\u0434\u0437\u0430\u0434\u0430\u0447:<\/p>\n<ol>\n<li>\n<p>\u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0447\u0430\u0441\u0442\u044f\u0445 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0441 \u0446\u0435\u043b\u044c\u044e \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u0445\u043e\u0441\u0442\u043e\u0432, \u043f\u043e\u0442\u0440\u0435\u0431\u043b\u044f\u044e\u0449\u0438\u0445 \u0435\u0433\u043e \u0432 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0435\u043c \u043e\u0431\u044a\u0435\u043c\u0435.<\/p>\n<\/li>\n<li>\n<p>\u0410\u043d\u0430\u043b\u0438\u0437 \u0445\u043e\u0441\u0442\u043e\u0432, \u0433\u0435\u043d\u0435\u0440\u0438\u0440\u0443\u044e\u0449\u0438\u0445 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u0439 \u0442\u0440\u0430\u0444\u0438\u043a, \u0441 \u043f\u043e\u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u0443\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u0435\u043c \u0432\u044b\u044f\u0432\u043b\u0435\u043d\u043d\u044b\u0445 \u043f\u0440\u043e\u0431\u043b\u0435\u043c.<\/p>\n<\/li>\n<\/ol>\n<figure class=\"full-width\">\n<div><figcaption>\u0417\u0430\u0447\u0435\u043c \u0432\u0441\u0435 \u044d\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0438 \u043a\u0430\u043a \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0438\u0447\u044c<\/figcaption><\/div>\n<\/figure>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0430 1: \u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u043a \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0443 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0443\u0433\u043e\u043b\u043a\u0430\u0445 \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u0430\u0446\u0438\u0438<\/h3>\n<figure class=\"full-width\"><\/figure>\n<p>\u041d\u0430 \u0443\u0437\u043b\u044b \u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440\u044b \u043d\u0430 Python, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 Speedtest CLI \u0432 \u0437\u0430\u0434\u0430\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043a\u0438 \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0438 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u044e\u0442 \u043c\u0435\u0442\u0440\u0438\u043a\u0438 \u0432 Prometheus.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u042d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440 \u0432\u044b\u0434\u0430\u0435\u0442 \u0434\u043b\u044f Prometheus \u0442\u0430\u043a\u0438\u0435 \u043c\u0435\u0442\u0440\u0438\u043a\u0438, \u043a\u0430\u043a:<\/p>\n<ul>\n<li>\n<p>internet_download_speed_mbps     # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430<\/p>\n<\/li>\n<li>\n<p>internet_upload_speed_mbps         # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442<\/p>\n<\/li>\n<li>\n<p>internet_ping_ms                             # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0438\u043d\u0433\u0430<\/p>\n<\/li>\n<\/ul>\n<p>\u041a\u043e\u0434 \u044d\u043a\u0441\u043f\u043e\u0440\u0442\u0435\u0440\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0438\u0436\u0435 &#8212; speedtest_exporter.py:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\"># metrics prometheus # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 # avg(internet_download_speed_mbps) by (instance) # \u0421\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0432\u044b\u0433\u0440\u0443\u0437\u043a\u0438 \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442 # avg(internet_upload_speed_mbps) by (instance) # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043f\u0438\u043d\u0433\u0430 # avg(internet_ping_ms) by (instance)  from flask import Flask, Response import speedtest import logging import time import threading  app = Flask(__name__)  # \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')  # \u041f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0434\u043b\u044f \u043a\u0435\u0448\u0430 metrics_data = {     \"download_speed\": 0.0,     \"upload_speed\": 0.0,     \"ping\": 0.0 } last_test_time = 0 data_lock = threading.Lock() cache_duration = 60  # \u0418\u0437\u043c\u0435\u0440\u044f\u0435\u043c \u0440\u0430\u0437 \u0432 \u043c\u0438\u043d\u0443\u0442\u0443  def run_speedtest():     global metrics_data, last_test_time     while True:         try:             logging.info(\"\u0417\u0430\u043f\u0443\u0441\u043a \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442-\u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u044f.\")             st = speedtest.Speedtest()             st.get_best_server()                          download_speed = st.download() \/ 1e6  # \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432 \u041c\u0431\u0438\u0442\/\u0441             upload_speed = st.upload() \/ 1e6      # \u041a\u043e\u043d\u0432\u0435\u0440\u0442\u0438\u0440\u0443\u0435\u043c \u0432 \u041c\u0431\u0438\u0442\/\u0441             ping = st.results.ping               # \u041f\u0438\u043d\u0433 \u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445                          with data_lock:                 metrics_data[\"download_speed\"] = download_speed                 metrics_data[\"upload_speed\"] = upload_speed                 metrics_data[\"ping\"] = ping                 last_test_time = time.time()              logging.info(f\"\u0418\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u044b: \u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430: {download_speed:.2f} Mbps, \"                          f\"\u0412\u044b\u0433\u0440\u0443\u0437\u043a\u0430: {upload_speed:.2f} Mbps, \"                          f\"\u041f\u0438\u043d\u0433: {ping:.2f} ms.\")         except Exception as e:             logging.error(f\"\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0438\u0437\u043c\u0435\u0440\u0435\u043d\u0438\u044f\u0445: {str(e)}\")                  time.sleep(cache_duration)  @app.route('\/metrics') def metrics():     with data_lock:         response = f\"\"\" # HELP internet_download_speed_mbps Download speed in Mbps # TYPE internet_download_speed_mbps gauge internet_download_speed_mbps {metrics_data[\"download_speed\"]} # HELP internet_upload_speed_mbps Upload speed in Mbps # TYPE internet_upload_speed_mbps gauge internet_upload_speed_mbps {metrics_data[\"upload_speed\"]} # HELP internet_ping_ms Ping latency in milliseconds # TYPE internet_ping_ms gauge internet_ping_ms {metrics_data[\"ping\"]} \"\"\"     return Response(response, mimetype=\"text\/plain\")  if __name__ == '__main__':     # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0442\u0435\u0441\u0442 \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u043c \u043f\u043e\u0442\u043e\u043a\u0435     speedtest_thread = threading.Thread(target=run_speedtest, daemon=True)     speedtest_thread.start()          # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c Flask     app.run(host='0.0.0.0', port=9101)<\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<h3>\u0417\u0430\u0434\u0430\u0447\u0430 2: \u041a\u0440\u0443\u0433\u043b\u043e\u0441\u0443\u0442\u043e\u0447\u043d\u043e\u0435 \u0441\u043a\u0430\u043d\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0432\u0441\u0435\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0438 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0445\u043e\u0441\u0442\u043e\u0432, \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0441\u0438\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0442 \u0442\u0440\u0430\u0444\u0438\u043a \u0440\u0430\u0441\u0445\u043e\u0434\u0443\u044e\u0449\u0438\u0445<\/h3>\n<figure class=\"full-width\"><\/figure>\n<p>\u041c\u044b \u043f\u0440\u043e\u0432\u043e\u0434\u0438\u043c \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u0432 \u043c\u0435\u0441\u0442\u0430\u0445 \u0435\u0433\u043e \u0441\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f. \u0423\u0434\u043e\u0431\u043d\u0435\u0435 \u0432\u0441\u0435\u0433\u043e \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0430 \u0448\u043b\u044e\u0437\u0435, \u0430 \u0432 \u043d\u0430\u0448\u0435\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0440\u043e\u0443\u0442\u0435\u0440\u0435,  \u043b\u0438\u0431\u043e \u043d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u044f \u0437\u0435\u0440\u043a\u0430\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043d\u0430 \u043f\u043e\u0440\u0442, \u043a \u043a\u043e\u0442\u043e\u0440\u043e\u043c\u0443 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0430\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0443\u044e\u0449\u0435\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e. \u0414\u043b\u044f \u044d\u0442\u0438\u0445 \u0446\u0435\u043b\u0435\u0439 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043b Orange Pi, \u0442\u0430\u043a \u043a\u0430\u043a \u044d\u0442\u0438 \u0434\u0435\u0448\u0435\u0432\u044b\u0435 \u0438 \u043c\u043d\u043e\u0433\u043e\u0444\u0443\u043d\u043a\u0446\u0438\u043e\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043e\u0434\u043d\u043e\u043f\u043b\u0430\u0442\u043d\u044b\u0435 \u043a\u043e\u043c\u043f\u044c\u044e\u0442\u0435\u0440\u044b \u043f\u0440\u043e\u0441\u0442\u044b \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0438 \u0443\u0434\u043e\u0431\u043d\u044b \u0432 \u0440\u0430\u0431\u043e\u0442\u0435. \u041e \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d\u0438\u0438 Orange Pi \u044f \u043f\u0438\u0441\u0430\u043b \u0432 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0430\u0442\u044c\u044f\u0445 <a href=\"https:\/\/habr.com\/p\/828556\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a> \u0438 <a href=\"https:\/\/habr.com\/p\/851834\/\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a>.<\/p>\n<figure class=\"full-width\"><\/figure>\n<p>\u041a\u043e\u0434 \u0430\u043d\u0430\u043b\u0438\u0437\u0430\u0442\u043e\u0440\u0430 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d \u043d\u0438\u0436\u0435 &#8212; traffic_monitor.py:<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"python\">import time import logging from logging.handlers import RotatingFileHandler from collections import defaultdict from scapy.all import sniff, IP from threading import Thread import datetime import ipaddress   # ======================= # \u0421\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 # =======================  # \u0417\u0430\u0434\u0435\u0440\u0436\u043a\u0430 \u043c\u0435\u0436\u0434\u0443 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f\u043c\u0438 \u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445 (\u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e 5 \u043c\u0438\u043d\u0443\u0442) DELAY_SECONDS = 60  # 300 \u0441\u0435\u043a\u0443\u043d\u0434 = 5 \u043c\u0438\u043d\u0443\u0442  # \u0421\u043f\u0438\u0441\u043e\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u043e\u0432 \u0441\u0435\u0442\u0435\u0432\u044b\u0445 \u0430\u0434\u0440\u0435\u0441\u043e\u0432 \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430 NETWORKS = ['192.168.2.0\/24', '192.168.10.0\/24']  # \u0414\u043e\u0431\u0430\u0432\u044c\u0442\u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0435 \u043f\u0440\u0435\u0444\u0438\u043a\u0441\u044b # \u0412\u044b\u0447\u0438\u0441\u043b\u044f\u0435\u043c \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0439 \u0441\u0435\u0442\u0438 NETWORK_BROADCAST_ADDRS = [str(ipaddress.IPv4Network(net).broadcast_address) for net in NETWORKS] # \u0428\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441 \u043f\u043e \u0443\u043c\u043e\u043b\u0447\u0430\u043d\u0438\u044e BROADCAST_IP = '255.255.255.255'  # \u0425\u0440\u0430\u043d\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0438 \u0442\u0440\u0430\u0444\u0438\u043a\u0430 traffic_stats = defaultdict(int) broadcast_traffic_stats = defaultdict(int)  # ======================= # \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0441 \u0440\u043e\u0442\u0430\u0446\u0438\u0435\u0439 # =======================  LOG_FILENAME = 'top_ip_traffic.log'  # \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0440\u043e\u0442\u0430\u0446\u0438\u0438 \u043b\u043e\u0433\u043e\u0432 rotating_handler = RotatingFileHandler(     LOG_FILENAME,      # \u0418\u043c\u044f \u0444\u0430\u0439\u043b\u0430     maxBytes=5 * 1024 * 1024,  # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u044b\u0439 \u0440\u0430\u0437\u043c\u0435\u0440 \u0444\u0430\u0439\u043b\u0430 (5 MB)     backupCount=3      # \u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e\u0435 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0440\u0435\u0437\u0435\u0440\u0432\u043d\u044b\u0445 \u043a\u043e\u043f\u0438\u0439 (3 \u0444\u0430\u0439\u043b\u0430) )  # \u041d\u0430\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0435\u043c \u0444\u043e\u0440\u043c\u0430\u0442 \u043b\u043e\u0433\u043e\u0432 rotating_handler.setFormatter(logging.Formatter('%(message)s'))  # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u043a\u043e\u0440\u043d\u0435\u0432\u043e\u0439 \u043b\u043e\u0433\u0433\u0435\u0440 \u0438 \u0443\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c \u0435\u043c\u0443 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a \u0440\u043e\u0442\u0430\u0446\u0438\u0438 logger = logging.getLogger() logger.setLevel(logging.INFO) logger.addHandler(rotating_handler)   def packet_callback(packet):     \"\"\"Callback \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0430\u043a\u0435\u0442\u043e\u0432.\"\"\"     if IP in packet:         src_ip = packet[IP].src         dst_ip = packet[IP].dst         packet_length = len(packet)          # \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c, \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u043b\u0438 \u0430\u0434\u0440\u0435\u0441 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u043c         is_broadcast = False         if dst_ip == BROADCAST_IP or dst_ip in NETWORK_BROADCAST_ADDRS:             is_broadcast = True         # else:         #     for prefix in NETWORK_PREFIXES:         #         if dst_ip.startswith(prefix):         #             is_broadcast = True         #             break          if is_broadcast:             # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u0444\u0438\u043a \u0434\u043b\u044f \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u0430 \u0438 \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             broadcast_traffic_stats[src_ip] += packet_length             broadcast_traffic_stats[dst_ip] += packet_length         else:             # \u0423\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u043c \u0442\u0440\u0430\u0444\u0438\u043a \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             traffic_stats[src_ip] += packet_length             traffic_stats[dst_ip] += packet_length  def log_top_ips():     \"\"\"\u041b\u043e\u0433\u0438\u0440\u0443\u0435\u0442 IP-\u0430\u0434\u0440\u0435\u0441\u0430 \u0441 \u043d\u0430\u0438\u0431\u043e\u043b\u044c\u0448\u0438\u043c \u0442\u0440\u0430\u0444\u0438\u043a\u043e\u043c.\"\"\"     while True:         time.sleep(DELAY_SECONDS)         try:             # \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u043c \u0442\u0435\u043a\u0443\u0449\u0443\u044e \u043c\u0435\u0442\u043a\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438             now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')              # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u043e\u043f IP \u0434\u043b\u044f \u043e\u0431\u044b\u0447\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             top_ips = sorted(traffic_stats.items(), key=lambda x: x[1], reverse=True)[:10]             # \u041e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0435\u043c \u0442\u043e\u043f IP \u0434\u043b\u044f \u0448\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430             top_broadcast_ips = sorted(broadcast_traffic_stats.items(), key=lambda x: x[1], reverse=True)[:10]              # \u041b\u043e\u0433\u0438\u0440\u0443\u0435\u043c \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e             logging.info(\".\" * 37)             logging.info(f\"  \u0422\u043e\u043f 10 IP \u043d\u0430 {now}\")             logging.info(\"            \u041e\u0431\u0449\u0438\u0439 \u0442\u0440\u0430\u0444\u0438\u043a \")             # \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0437\u0430\u0433\u043e\u043b\u043e\u0432\u043a\u0438 \u0442\u0430\u0431\u043b\u0438\u0446\u044b             logging.info(f\"{'IP-\u0430\u0434\u0440\u0435\u0441':&lt;20} {'\u0411\u0430\u0439\u0442\u044b':&gt;15}\")             logging.info(\"-\" * 35)             for ip, bytes_used in top_ips:                 logging.info(f\"{ip:&lt;20} {bytes_used:&gt;15}\")              logging.info(\"      \u0428\u0438\u0440\u043e\u043a\u043e\u0432\u0435\u0449\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0442\u0440\u0430\u0444\u0438\u043a \")             logging.info(f\"{'IP-\u0430\u0434\u0440\u0435\u0441':&lt;20} {'\u0411\u0430\u0439\u0442\u044b':&gt;15}\")             logging.info(\"-\" * 35)             for ip, bytes_used in top_broadcast_ips:                 logging.info(f\"{ip:&lt;20} {bytes_used:&gt;15}\")             logging.info(\".\" * 37)              # \u041e\u0447\u0438\u0449\u0430\u0435\u043c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443 \u043e\u0431\u043e\u0438\u0445 \u0442\u0438\u043f\u043e\u0432 \u0442\u0440\u0430\u0444\u0438\u043a\u0430             traffic_stats.clear()             broadcast_traffic_stats.clear()         except Exception as e:             logging.error(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438: {e}\")  def main():     \"\"\"\u041e\u0441\u043d\u043e\u0432\u043d\u0430\u044f \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0434\u043b\u044f \u0437\u0430\u0445\u0432\u0430\u0442\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0438 \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u0442\u0440\u0430\u0444\u0438\u043a\u0430.\"\"\"     try:         # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u043f\u043e\u0442\u043e\u043a \u0434\u043b\u044f \u043b\u043e\u0433\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f         logger_thread = Thread(target=log_top_ips, daemon=True)         logger_thread.start()          # \u0417\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u043c \u0437\u0430\u0445\u0432\u0430\u0442 \u043f\u0430\u043a\u0435\u0442\u043e\u0432 \u0441 \u043e\u043f\u0442\u0438\u043c\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u043c         # \u0424\u0438\u043b\u044c\u0442\u0440 \u0437\u0430\u0445\u0432\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e IP-\u043f\u0430\u043a\u0435\u0442\u044b \u0434\u043b\u044f \u043f\u043e\u0432\u044b\u0448\u0435\u043d\u0438\u044f \u044d\u0444\u0444\u0435\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u0438         sniff(iface=None, prn=packet_callback, store=0, filter=\"ip\")      except KeyboardInterrupt:         logging.info(\"\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u043c...\")     except Exception as e:         logging.error(f\"\u041f\u0440\u043e\u0438\u0437\u043e\u0448\u043b\u0430 \u043e\u0448\u0438\u0431\u043a\u0430 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438: {e}\")  # \u0417\u0430\u043f\u0443\u0441\u043a \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u044b if __name__ == \"__main__\":     main() <\/code><\/pre>\n<\/p>\n<\/div>\n<\/details>\n<p>\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435\u043c \u0432\u0438\u0440\u0442\u0443\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u043e\u043a\u0440\u0443\u0436\u0435\u043d\u0438\u044f, \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u043e\u0439 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e\u0441\u0442\u0435\u0439 \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u043e\u0439 \u0441\u043b\u0443\u0436\u0431 \u0434\u043b\u044f \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0432\u044b\u0448\u0435 \u0441\u043a\u0440\u0438\u043f\u0442\u043e\u0432 \u0437\u0430\u0439\u043c\u0435\u0442\u0441\u044f Ansible. \u041f\u043b\u0435\u0439\u0431\u0443\u043a, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u044f \u043f\u0440\u0438\u043c\u0435\u043d\u044f\u043b, \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d \u043d\u0438\u0436\u0435: ansible_speedtest.yml.<\/p>\n<details class=\"spoiler\">\n<summary>\u0421\u043a\u0440\u044b\u0442\u044b\u0439 \u0442\u0435\u043a\u0441\u0442<\/summary>\n<div class=\"spoiler__content\">\n<pre><code class=\"yaml\">--- - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 Python \u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Speedtest Exporter   hosts: localhost   tasks:     - name: \u041e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0430\u043a\u0435\u0442\u043e\u0432       apt:         update_cache: true       become: true      - name: \u0423\u0441\u0442\u0430\u043d\u043e\u0432\u043a\u0430 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u044b\u0445 \u043f\u0430\u043a\u0435\u0442\u043e\u0432       apt:         name:     <\/code><\/pre>\n<\/div>\n<\/details>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-443900","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/443900","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=443900"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/443900\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=443900"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=443900"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=443900"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}