рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕рд╕рд╣ рдлреНрд▓рд╛рд╕реНрдХ рдорд╛рдпрдХреНрд░реЛрд╕рд░реНрд╡реНрд╣рд┐рд╕реЗрд╕рдЪреЗ рдирд┐рд░реАрдХреНрд╖рдг рдХрд░рдгреЗ

рдХреЛрдбрдЪреНрдпрд╛ рджреЛрди рдУрд│реА рдЖрдгрд┐ рддреБрдордЪрд╛ рдЕрдиреБрдкреНрд░рдпреЛрдЧ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рд╡реНрдпреБрддреНрдкрдиреНрди рдХрд░рддреЛ, рд╡реНрд╡рд╛!

prometheus_ рдХрд╕реЗ рдХрд╛рд░реНрдп рдХрд░рддреЗ рд╣реЗ рд╕рдордЬреВрди рдШреЗрдгреНрдпрд╛рд╕рд╛рдареАрдлреНрд▓рд╛рд╕реНрдХ_рдирд┐рд░реНрдпрд╛рддрджрд╛рд░ рдПрдХ рдХрд┐рдорд╛рди рдЙрджрд╛рд╣рд░рдг рдкреБрд░реЗрд╕реЗ рдЖрд╣реЗ:

from flask import Flask
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)
metrics = PrometheusMetrics(app)

@app.route('/')
def main():
    return 'OK'

рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдкреНрд░рд╛рд░рдВрдн рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЗрддрдХреЗрдЪ рдЖрд╡рд╢реНрдпрдХ рдЖрд╣реЗ! рдкреНрд░рд╛рд░рдВрдн рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдЖрдпрд╛рдд рдЖрдгрд┐ рдПрдХ рдУрд│ рдЬреЛрдбреВрди PrometheusMetrics, рддреБрдореНрд╣рд╛рд▓рд╛ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдорд┐рд│рддреАрд▓ рд╡рд┐рдирдВрддреА рдХрд╛рд▓рд╛рд╡рдзреА ╨╕ рд╡рд┐рдирдВрддреА рдХрд╛рдЙрдВрдЯрд░, рдПрдВрдбрдкреЙрдЗрдВрдЯрд╡рд░ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХреЗрд▓реЗ рдЬрд╛рддреЗ /рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдлреНрд▓рд╛рд╕реНрдХ рдНрдкреНрд▓рд┐рдХреЗрд╢рди рдЬреНрдпрд╛рд╡рд░ рддреЗ рдиреЛрдВрджрдгреАрдХреГрдд рдЖрд╣реЗ, рддрд╕реЗрдЪ рддреБрдореНрд╣рд╛рд▓рд╛ рдмреЗрд╕рдордзреВрди рдорд┐рд│рдгрд╛рд░реЗ рд╕рд░реНрд╡ рдбреАрдлреЙрд▓реНрдЯ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕ рдХреНрд▓рд╛рдпрдВрдЯ рд▓рд╛рдпрдмреНрд░рд░реА.

рдЖрдкрдг рд╢реЛрдзреВ рд╢рдХрддрд╛ рд╡рд╛рдкрд░рдгреНрдпрд╛рд╕ рд╕реЛрдкреЗ рдЙрджрд╛рд╣рд░рдг GitHub рд░реЗрдкреЙрдЬрд┐рдЯрд░реАрдордзреНрдпреЗ рдЬреЗ рдЙрджрд╛рд╣рд░рдг рдЪрд╛рд▓рд╡рддреЗ Prometheus ╨╕ рдЧреНрд░рд╛рдлрд╛рдирд╛ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рд╡реНрдпреБрддреНрдкрдиреНрди рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рдбреЗрдореЛ рдНрдкреНрд▓рд┐рдХреЗрд╢рдирд╕рд╣ рдЬреЗ рдЕрд╕реЗ рдХрд╛рд╣реАрддрд░реА рджрд┐рд╕реЗрд▓:

рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕рд╕рд╣ рдлреНрд▓рд╛рд╕реНрдХ рдорд╛рдпрдХреНрд░реЛрд╕рд░реНрд╡реНрд╣рд┐рд╕реЗрд╕рдЪреЗ рдирд┐рд░реАрдХреНрд╖рдг рдХрд░рдгреЗ

рдордзреНрдпреЗ рдЖрдкрд▓реНрдпрд╛рд▓рд╛ рдирд┐рд░реНрджреЗрд╢рдХрд╛рдВрдЪреА рд╕реВрдЪреА рджреЗрдЦреАрд▓ рдорд┐рд│реЗрд▓ рдорд▓рд╛ рд╡рд╛рдЪрд╛ рдбреЕрд╢рдмреЛрд░реНрдбрдордзреНрдпреЗ рджрд┐рд╕рдгрд╛рд░реА рдЙрджрд╛рд╣рд░рдгреЗ, рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕ рдХреНрд╡реЗрд░реАрдВрд╕рд╣ рдЬреЗ рдбреЕрд╢рдмреЛрд░реНрдб рднрд░рддрд╛рдд.

рд╕рдорд╛рдпреЛрдЬрди

рд▓рд╛рдпрдмреНрд░рд░реАрдордзреНрдпреЗ рдЕрдиреЗрдХ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдкрд░реНрдпрд╛рдп рдЖрд╣реЗрдд, рдкрд╣рд╛ рдорд▓рд╛ рд╡рд╛рдЪрд╛ рдереЛрдбрдХреНрдпрд╛рдд рд╕реНрдкрд╖реНрдЯреАрдХрд░рдгрд╛рд╕рд╣ рддреНрдпрд╛рдВрдЪреА рдкреНрд░рдХрд▓реНрдк рдЙрджрд╛рд╣рд░рдгреЗ.

рдореВрд▓рднреВрдд рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рд╡рд░ рджрд░реНрд╢рд╡рд┐рд▓реЗ рдЖрд╣реЗ. рдлрдХреНрдд рдПрдХ рдЙрджрд╛рд╣рд░рдг рддрдпрд╛рд░ рдХрд░рд╛ PrometheusMetrics, рдЪрд▓рд╛ рдХреЙрд▓ рдХрд░реВрдпрд╛ рдореЗрдЯреНрд░рд┐рдХреНрд╕, рдЖрдгрд┐ рдирдВрддрд░ рдлрдВрдХреНрд╢рдиреНрд╕ рд╕рдЬрд╡реВрди рддреБрдореНрд╣реА рдЧреЛрд│рд╛ рдХрд░реВ рдЗрдЪреНрдЫрд┐рдд рдЕрддрд┐рд░рд┐рдХреНрдд рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА рддреНрдпрд╛рдЪрд╛ рд╡рд╛рдкрд░ рдХрд░рд╛:

  • @metrics.counter(..)

  • @metrics.gauge(..)

  • @metrics.summary(..)

  • @metrics.histogram(..)

рдХрд╛рдЙрдВрдЯрд░ рдХреЙрд▓ рдореЛрдЬрддрд╛рдд рдЖрдгрд┐ рдЗрддрд░ рддреНрдпрд╛ рдХреЙрд▓рдЪреНрдпрд╛ рдХрд╛рд▓рд╛рд╡рдзреАрд╡рд░ рдЖрдзрд╛рд░рд┐рдд рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдЧреЛрд│рд╛ рдХрд░рддрд╛рдд. рд╡рд┐рдирдВрддреА рдХрд┐рдВрд╡рд╛ рдкреНрд░рддрд┐рд╕рд╛рдж рдЧреБрдгрдзрд░реНрдо рд╡рд╛рдкрд░реВрди рддреБрдореНрд╣реА рдпрд╛ рдкреНрд░рддреНрдпреЗрдХрд╛рд╕рд╛рдареА рд▓реЗрдмрд▓реЗ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░реВ рд╢рдХрддрд╛. рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде:

from flask import Flask, request
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)

# group by endpoint rather than path
metrics = PrometheusMetrics(app, group_by='endpoint')

@app.route('/collection/:collection_id/item/:item_id')
@metrics.counter(
    'cnt_collection', 'Number of invocations per collection', labels={
        'collection': lambda: request.view_args['collection_id'],
        'status': lambda resp: resp.status_code
    })
def get_item_from_collection(collection_id, item_id):
    pass

рд╡рд░реАрд▓ рдЙрджрд╛рд╣рд░рдгрд╛рдд, рдПрдВрдбрдкреЙрдЗрдВрдЯрд╡рд░ рдХреНрд▓рд┐рдХ рдХрд░рдгреЗ /collection/10002/item/76 рдХрд╛рдЙрдВрдЯрд░ рд╡рд╛рдврдгреНрдпрд╛рд╕ рдХрд╛рд░рдгреАрднреВрдд рдард░реЗрд▓, рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде cnt_collection{рд╕рдВрдХрд▓рди = "10002", рд╕реНрдерд┐рддреА = "200"}, рддрд╕реЗрдЪ рддреБрдореНрд╣рд╛рд▓рд╛ рдбреАрдлреЙрд▓реНрдЯ рд▓рд╛рдпрдмреНрд░рд░реАрдордзреВрди рдбреАрдлреЙрд▓реНрдЯ рдореЗрдЯреНрд░рд┐рдХреНрд╕ (рдпрд╛ рдЙрджрд╛рд╣рд░рдгрд╛рддреАрд▓ рдкреНрд░рддреНрдпреЗрдХ рдПрдВрдбрдкреЙрдЗрдВрдЯрд╕рд╛рдареА) рдорд┐рд│рддреАрд▓:

  • flask_http_request_duration_seconds тАФ рдкрджреНрдзрдд, рдорд╛рд░реНрдЧ рдЖрдгрд┐ рд╕реНрдерд┐рддреАрдиреБрд╕рд╛рд░ рд╕рд░реНрд╡ рдлреНрд▓рд╛рд╕реНрдХ рд╡рд┐рдирдВрддреНрдпрд╛рдВрд╕рд╛рдареА HTTP рд╡рд┐рдирдВрддреА рдХрд╛рд▓рд╛рд╡рдзреА рд╕реЗрдХрдВрджрд╛рдд

  • flask_http_request_total тАФ рдкрджреНрдзрддреА рдЖрдгрд┐ рд╕реНрдерд┐рддреАрдВрдиреБрд╕рд╛рд░ HTTP рд╡рд┐рдирдВрддреНрдпрд╛рдВрдЪреА рдПрдХреВрдг рд╕рдВрдЦреНрдпрд╛

рд╡рд┐рд╢рд┐рд╖реНрдЯ рдПрдВрдбрдкреЙрдЗрдВрдЯреНрд╕рдЪрд╛ рдорд╛рдЧреЛрд╡рд╛ рдШреЗрдгреЗ рд╡рдЧрд│рдгреЗ, рдЕрддрд┐рд░рд┐рдХреНрдд рдбреАрдлреЙрд▓реНрдЯ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рд▓реЙрдЧ рдХрд░рдгреЗ рдХрд┐рдВрд╡рд╛ рд╡рд░ рд╕реВрдЪреАрдмрджреНрдз рдХреЗрд▓реЗрд▓реЗ рддреЗ рд╡рдЧрд│рдгреЗ рдХрд┐рдВрд╡рд╛ рдПрдХрд╛рдзрд┐рдХ рдПрдВрдбрдкреЙрдЗрдВрдЯреНрд╕рд╡рд░ рд╕рдорд╛рди рдХрд╕реНрдЯрдо рдореЗрдЯреНрд░рд┐рдХ рд▓рд╛рдЧреВ рдХрд░рдгреНрдпрд╛рдЪреЗ рдкрд░реНрдпрд╛рдп рдЖрд╣реЗрдд. рддрдкрд╛рд╕рд╛ рдорд▓рд╛ рд╡рд╛рдЪрд╛ рдХрд╛рдп рдЙрдкрд▓рдмреНрдз рдЖрд╣реЗ рд╣реЗ рдкрд╛рд╣рдгреНрдпрд╛рд╕рд╛рдареА рдкреНрд░рдХрд▓реНрдк.

app = Flask(__name__)
metrics = PrometheusMetrics(app)

@app.route('/')
def main():
    pass  # requests tracked by default

@app.route('/skip')
@metrics.do_not_track()
def skip():
    pass  # default metrics are not collected

# custom metric to be applied to multiple endpoints
common_counter = metrics.counter(
    'by_endpoint_counter', 'Request count by endpoints',
    labels={'endpoint': lambda: request.endpoint}
)

@app.route('/common/one')
@common_counter
def endpoint_one():
    pass  # tracked by the custom and the default metrics

@app.route('/common/two')
@common_counter
def endpoint_two():
    pass  # also tracked by the custom and the default metrics

# register additional default metrics
metrics.register_default(
    metrics.counter(
        'by_path_counter', 'Request count by request paths',
        labels={'path': lambda: request.path}
    )
)

рд▓рд╛рдпрдмреНрд░рд░реАрдордзреНрдпреЗ рд▓реЛрдХрдкреНрд░рд┐рдп рдорд▓реНрдЯреАрдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧ рд▓рд╛рдпрдмреНрд░рд░реА рдЬрд╕реЗ рдХреА uWSGI рдЖрдгрд┐ Gunicorn рд╕рд╛рдареА рд╕реЛрдпреАрд╕реНрдХрд░ рд╡рд┐рд╕реНрддрд╛рд░ рдЖрд╣реЗрдд. рдЖрдкрдг рдорд▓реНрдЯреАрдкреНрд░реЛрд╕реЗрд╕рд┐рдВрдЧрд╕рд╣ рд▓рдХреНрд╖реНрдпрд┐рдд рд╡рд╛рдкрд░ рдкреНрд░рдХрд░рдгрд╛рдВрдЪреА рдЫреЛрдЯреА рдЙрджрд╛рд╣рд░рдгреЗ рджреЗрдЦреАрд▓ рд╢реЛрдзреВ рд╢рдХрддрд╛.

рдореЗрдЯреНрд░рд┐рдХреНрд╕ рд╕рдВрдЧреНрд░рд╣

рд╡рд░ рдирдореВрдж рдХреЗрд▓реНрдпрд╛рдкреНрд░рдорд╛рдгреЗ, рд▓рд╛рдпрдмреНрд░рд░реА рдбреАрдлреЙрд▓реНрдЯрдиреБрд╕рд╛рд░ рдПрдВрдбрдкреЙрдЗрдВрдЯ рдкреНрд░рджрд╛рди рдХрд░рддреЗ /рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдлреНрд▓рд╛рд╕реНрдХ рдНрдкреНрд▓рд┐рдХреЗрд╢рдирдордзреНрдпреЗ, рдЬреЗ рд▓рдХреНрд╖реНрдп рдореНрд╣рдгреВрди рдХрд╛рдо рдХрд░реВ рд╢рдХрддреЗ рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕ рдмрд┐рд▓реНрдбрд░.

рд╡рд░реАрд▓ рдбреЕрд╢рдмреЛрд░реНрдб рдЙрджрд╛рд╣рд░рдгрд╛рдордзреНрдпреЗ, рддреБрдореНрд╣реА рддреБрдордЪреНрдпрд╛ рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕рд▓рд╛ рдпрд╛ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рдирд╕рд╣ рдбреАрдлреЙрд▓реНрдЯ рд╕реЗрдЯрд┐рдВрдЧреНрдЬрд╕рд╣ рдлреНрд▓рд╛рд╕реНрдХ рдНрдкреНрд▓рд┐рдХреЗрд╢рдирд╡рд░ рд▓рдХреНрд╖реНрдп рдХрд░реВ рд╢рдХрддрд╛:

scrape_configs:
  - job_name: 'example'

    dns_sd_configs:
      - names: ['app']
        port: 5000
        type: A
        refresh_interval: 5s

рдпреЗрдереЗ рд╕рдВрдкреВрд░реНрдг рдЙрджрд╛рд╣рд░рдг рдкрд╣рд╛ GitHub рднрд╛рдВрдбрд╛рд░. рд╣реЗ рдЕрд╕реЗ рдЧреГрд╣реАрдд рдзрд░рддреЗ рдХреА рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕ рдЖрдкрд▓реНрдпрд╛ рдлреНрд▓рд╛рд╕реНрдХ рдЕрдиреБрдкреНрд░рдпреЛрдЧрд╛рдЪреА рдЙрджрд╛рд╣рд░рдгреЗ рд╢реЛрдзреВ рд╢рдХрддреЛ http://app:5000/metrics, рдЬреЗрдереЗ рдНрдкреНрд▓рд┐рдХреЗрд╢рди рдбреЛрдореЗрди рдирд╛рд╡ рд╕рдВрднрд╛рд╡реНрдпрдкрдгреЗ рдПрдХрд╛рдзрд┐рдХ IP рдкрддреНрддреНрдпрд╛рдВрдЪреЗ рдирд┐рд░рд╛рдХрд░рдг рдХрд░реВ рд╢рдХрддреЗ, рдЙрджрд╛рд╣рд░рдгрд╛рд░реНрде рдЪрд╛рд▓реВ рдЕрд╕рддрд╛рдирд╛ рдХреБрдмреЗрд░рдиреЗрдЯреНрд╕ рдХрд┐рдВрд╡рд╛ рдбреЙрдХрд░ рдЭреБрдВрдб.

рдЕрд╢рд╛ рдкреНрд░рдХрд╛рд░реЗ рдореЗрдЯреНрд░рд┐рдХреНрд╕ рдПрдВрдбрдкреЙрдЗрдВрдЯ рдЙрдШрдб рдХрд░рдгреЗ рдЖрдкрд▓реНрдпрд╛рд╕ рдЕрдиреБрдХреВрд▓ рдирд╕рд▓реНрдпрд╛рд╕, рдХрджрд╛рдЪрд┐рдд рдЖрдкрдг рддреНрдпрд╛рд╕ рдмрд╛рд╣реНрдп рдкреНрд░рд╡реЗрд╢рд╛рд╕ рдЕрдиреБрдорддреА рджреЗрдК рдЗрдЪреНрдЫрд┐рдд рдирд╕рд▓реНрдпрд╛рдореБрд│реЗ, рдЖрдкрдг рдкрд╛рд╕ рдХрд░реВрди рддреЛ рд╕рд╣рдЬрдкрдгреЗ рдЕрдХреНрд╖рдо рдХрд░реВ рд╢рдХрддрд╛ рдорд╛рд░реНрдЧ = рдХрд╛рд╣реАрд╣реА рдирд╛рд╣реА рдПрдХ рдЙрджрд╛рд╣рд░рдг рддрдпрд╛рд░ рдХрд░рддрд╛рдирд╛ PrometheusMetrics.

from flask import Flask, request
from prometheus_flask_exporter import PrometheusMetrics

app = Flask(__name__)
metrics = PrometheusMetrics(app, path=None)

...

metrics.start_http_server(5099)

рдордЧ рдЖрдкрдг рд╡рд╛рдкрд░реВ рд╢рдХрддрд╛ start_http_server(рдкреЛрд░реНрдЯ)рд╣рд╛ рдПрдВрдбрдкреЙрдЗрдВрдЯ рд╡реЗрдЧрд│реНрдпрд╛ HTTP рдкреЛрд░реНрдЯрд╡рд░ рдЙрдШрдбрдгреНрдпрд╛рд╕рд╛рдареА, 5099 рд╡рд░реАрд▓ рдЙрджрд╛рд╣рд░рдгрд╛рдд. рд╡реИрдХрд▓реНрдкрд┐рдХрд░рд┐рддреНрдпрд╛, рд╕рдорд╛рди рдлреНрд▓рд╛рд╕реНрдХ рдНрдкреНрд▓рд┐рдХреЗрд╢рдирдордзреНрдпреЗ рдПрдВрдбрдкреЙрдИрдВрдЯ рдЕрд╕рд▓реНрдпрд╛рдмрджреНрджрд▓ рддреБрдореНрд╣реА рдЖрдирдВрджреА рдЕрд╕рд▓реНрдпрд╛рд╕, рдкрд░рдВрддреБ рддреБрдореНрд╣рд╛рд▓рд╛ рддреНрдпрд╛рдЪрд╛ рдорд╛рд░реНрдЧ рдмрджрд▓рдгреНрдпрд╛рдЪреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рдЖрд╣реЗ /рдореЗрдЯреНрд░рд┐рдХреНрд╕, рддреБрдореНрд╣реА рдПрдХрддрд░ рджреБрд╕рд░рд╛ URI рдкрд╛рде рдкреЕрд░рд╛рдореАрдЯрд░ рдореНрд╣рдгреВрди рдкрд╛рд╕ рдХрд░реВ рд╢рдХрддрд╛ рдХрд┐рдВрд╡рд╛ рд╡рд╛рдкрд░реВ рд╢рдХрддрд╛ register_endpoint(..)рдирдВрддрд░ рд╕реНрдерд╛рдкрд┐рдд рдХрд░рдгреНрдпрд╛рд╕рд╛рдареА.

рд╕рдВрджрд░реНрдн

  • rycus86/prometheus_flask_exporter - рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕рд╕рд╛рдареА рд╣реА рдмрд╛рдЯрд▓реА рдирд┐рд░реНрдпрд╛рддрдХ

  • prometheus-flask-рдирд┐рд░реНрдпрд╛рддрдХрд░реНрддрд╛ README тАФ рд╡рд╛рдкрд░, рдЙрджрд╛рд╣рд░рдгреЗ рдЖрдгрд┐ рдХреЙрдиреНрдлрд┐рдЧрд░реЗрд╢рди рдкрд░реНрдпрд╛рдп

  • prometheus-flask-exporter рдЙрджрд╛рд╣рд░рдгреЗ тАФ рдлреНрд▓рд╛рд╕реНрдХ рдНрдкреНрд▓рд┐рдХреЗрд╢рди рдореЙрдирд┐рдЯрд░рд┐рдВрдЧ рд╡реЗрдЧрд╡реЗрдЧрд│реНрдпрд╛ рдкреНрд░рдХрд╛рд░реЗ рд╕реЗрдЯ рдХрд░рдгреНрдпрд╛рдЪреА рдЙрджрд╛рд╣рд░рдгреЗ

  • PyPI рд╡рд░ prometheus-flask-exporter тАФ рд╣рд╛ рдкреНрд░рдХрд▓реНрдк PyPI рд╡рд░ рдЖрд╣реЗ

  • prometheus/client_python - рдкрд╛рдпрдердирд╕рд╛рдареА рдЕрдзрд┐рдХреГрдд рдкреНрд░реЛрдорд┐рдерд┐рдпрд╕ рдХреНрд▓рд╛рдпрдВрдЯ рд▓рд╛рдпрдмреНрд░рд░реА

рддреБрдореНрд╣реА рдПрдХрджрд╛ рдкреНрд░рдпрддреНрди рдХрд░рдгреНрдпрд╛рдЪреЗ рдард░рд╡рд┐рд▓реНрдпрд╛рд╕, GitHub рд╡рд░ рд╕рдорд╕реНрдпрд╛ рдЙрдШрдбрдгреНрдпрд╛рд╕ рдореЛрдХрд│реНрдпрд╛ рдордирд╛рдиреЗ рдХрд┐рдВрд╡рд╛ рддреБрдордЪреНрдпрд╛ рдЯрд┐рдкреНрдкрдгреНрдпрд╛, рдЕрднрд┐рдкреНрд░рд╛рдп рдЖрдгрд┐ рд╕реВрдЪрдирд╛ рджреНрдпрд╛!

рдзрдиреНрдпрд╡рд╛рдж!

рд╕реНрддреНрд░реЛрдд: www.habr.com

рдПрдХ рдЯрд┐рдкреНрдкрдгреА рдЬреЛрдбрд╛