---
title: "client_golang: первый /metrics endpoint"
description: "Установка client_golang и первый /metrics endpoint: go get, promhttp.Handler() в net/http, бесплатные Go-метрики и проверка curl-ом за пять минут."
course: "Мониторинг Go с Prometheus: метрики, Grafana, алерты"
url: "https://golangmaster.ru/courses/prometheus/lessons/03-ustanovka-client-golang/"
published: "2026-09-19"
updated: "2026-09-24"
minutes: 18
---

# client_golang: первый /metrics endpoint

*Курс «Мониторинг Go с Prometheus: метрики, Grafana, алерты», урок 3 из 25. ~18 минут. Обновлено 24.09.2026. Источник: https://golangmaster.ru/courses/prometheus/lessons/03-ustanovka-client-golang/*

client_golang — официальный клиент Prometheus для Go: после go get и одной строки с promhttp.Handler() ваш сервис отдаёт страницу /metrics с десятками готовых метрик. Разберём установку, минимальный сервер и что уже видно на endpoint до первой собственной метрики.

Порог входа в мониторинг — то, что продаёт его скептикам: пять строк, и сервис наблюдаем. Никаких агентов, брокеров и отдельных баз: библиотека ставится одной командой, регистрируется в стандартном net/http, а на /metrics уже живёт дюжина метрик, которые вам не надо писать. План урока такой: сначала установка client_golang и минимальный сервер, потом смотрим, что отдаёт endpoint из коробки, и разбираем, как promhttp устроен внутри, чтобы при проблемах знать, куда смотреть. Все примеры — обычные программы net/http, поэтому в сервис на chi или gin они переносятся без единой правки.

### Установка client_golang: две команды

Начинаем с нового модуля. Первая команда — go mod init с именем модуля, вторая — go get github.com/prometheus/client_golang/prometheus: она добавит библиотеку в go.mod и подтянет зависимости. В коде пригодятся два пакета: prometheus — сами типы метрик и реестр, prometheus/promhttp — готовый HTTP-хендлер. Требования скромные: актуальный стабильный Go, никаких переменных окружения и внешних сервисов настраивать не надо. Про версии: библиотека живёт в первой мажорной версии, API стабилен, и код из документации компилируется годами — примеры не протухнут, даже если вернётесь к ним через год. После go get загляните в go.mod: на старте это одна запись, без десятков транзитивных зависимостей — клиент собран на стандартной библиотеке.

### Минимальный сервис с /metrics

```go
package main

import (
        "net/http"

        "github.com/prometheus/client_golang/prometheus/promhttp"
)

func main() {
        http.Handle("/metrics", promhttp.Handler())

        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("привет"))
        })

        http.ListenAndServe(":8080", nil)
}
```

Весь секрет — строка http.Handle с promhttp.Handler(): это обычный http.Handler из стандартной библиотеки, без адаптеров и фреймворков. Хендлер отдаёт содержимое реестра по умолчанию, где уже зарегистрированы коллекторы Go-процесса и процесса операционной системы. Запуск — go run ., после чего страница доступна на localhost:8080/metrics.

### Что уже видно на /metrics без вашего кода

Разберём инцидент, с которого начинается половина историй про внедрение мониторинга. Сервис выставил /metrics, страница открывается в браузере, всё выглядит готовым — и команда уверена, что наблюдаемость есть. Через месяц падает база, заказы встают, а дежурный узнаёт об этом от поддержки: сервер Prometheus никто не настраивал, за метриками никто не приходил, страница просто молчала в пустоту. Ошибка тонкая: живой /metrics — половина контракта, вторая половина — кто-то, кто регулярно забирает данные. Поэтому первый чек после запуска сервиса — не браузер, а настройка цели: запись job в prometheus.yml и зелёная метрика up на стороне сервера. До урока про [скрейпинг](/courses/prometheus/lessons/23-scrape-config/) достаточно curl-а: страница отвечает на localhost — кодовая половина контракта выполнена, дальше её должен кто-то читать. А теперь откройте страницу и найдите строки, начинающиеся с go_ и process_ — это работа дефолтных коллекторов, которые client_golang регистрирует автоматически. go_goroutines — текущее число горутин; семейство go_memstats_* — память из runtime: выделено, занята куча, сколько отдал сборщику; go_gc_duration_seconds — длительности сборок мусора в виде гистограммы; process_cpu_seconds_total — процессорное время процесса; process_open_fds — открытые файловые дескрипторы; process_resident_memory_bytes — резидентная память. Для половины штатных вопросов — «не текут ли горутины», «сколько ест память», «не выжрали ли дескрипторы» — этих метрик хватает без единой строчки вашего кода. Остальное — собственные метрики домена: заказы, очереди, латентность вашей бизнес-логики. Их заведём со следующего урока, а пока важно другое: endpoint живой, формат читается, и это уже работающий мониторинг.

### Метрики, на которые стоит взглянуть первыми

- go_goroutines — число горутин прямо сейчас; медленный рост к вечеру — первый симптом утечки.
- go_memstats_heap_inuse_bytes — занятая куча: живая память под ваши данные.
- go_gc_duration_seconds — гистограмма длительностей сборок мусора; хвосты растут под нагрузкой.
- process_cpu_seconds_total — суммарное процессорное время процесса; счётчик, из которого считается загрузка.
- process_open_fds — открытые дескрипторы; если не убывает после закрытия соединений, есть куда смотреть.
- process_resident_memory_bytes — резидентная память процесса, как её видит операционная система.

### Триста горутин — это много? Проверка curl-ом за пять минут

Ошибка интерпретации, которая ждёт на самих числах. Человек открывает /metrics, видит go_goroutines 340 и пишет в чат: «горутин триста сорок, это утечка». Число без контекста не значит ничего: под пиком триста горутин — штатная работа пула воркеров, а в другом сервисе пятьдесят — катастрофа, если обычно их двадцать. Метрика становится сигналом только в динамике и относительно базы сравнения: рост на порядок за ночь при неизменном трафике — повод смотреть; стабильные триста при живом потоке — норма. Откуда берутся горутины и как их считать — отдельная наука, у нас для неё есть [урок про конкурентность](/courses/go-advanced/lessons/04-gorutiny/), а здесь достаточно правила: сырые значения дефолтных метрик — материал для графиков, а не для вердиктов. Сравнивайте значение с собственным прошлым — вчерашним утром, прошлым пик-часом — и только потом делайте выводы. Практическая вариация для закрепления — минимальный чек-лист после установки. Первый шаг — curl с ключом silent и просмотр первых строк: должны прийти комментарии HELP и TYPE, а не HTML-страница ошибки. Второй — grep по go_goroutines: если строки нет, что-то съело хендлер — чаще всего middleware, который отдал 404, или прокси, переписавший путь. Третий — убедиться, что страница отдаётся и с внешнего адреса, если Prometheus стоит на другой машине: localhost и имя хоста — разные адреса, и новички неделями ищут «потерянные» метрики, хотя сервис просто слушал внутренний интерфейс. Четвёртый — посмотреть Content-Type: текст с версией формата подтверждает, что отвечает именно promhttp, а не случайный хендлер. Пять минут, и у сервиса есть гарантированная точка, куда можно стучаться мониторингом.

> **Песочница не поднимет HTTP-сервер.** Интерпретатор сайта исполняет чистый Go без сторонних модулей и TCP-сокетов, поэтому примеры урока запускаются только локальным Go. Схема одна на весь курс: go mod init, go get github.com/prometheus/client_golang/prometheus, go run . — и curl на localhost. Пять минут на настройку, дальше всё живёт в вашем терминале.

### promhttp.Handler(): что происходит на каждый запрос

Разберём хендлер по слоям, чтобы при проблемах понимать, где искать. На каждый GET /metrics promhttp собирает значения: собирает все метрики реестра через Gatherer — по умолчанию DefaultGatherer, куда попадает всё, зарегистрированное через prometheus.MustRegister. Затем сериализует в текстовый формат экспозиции и отвечает с Content-Type text/plain; более компактный protobuf включается согласованием через заголовок Accept, но для человека и curl текст — вариант по умолчанию. Сбор происходит в момент запроса, а не в фоне: значение go_goroutines на странице — это значение прямо сейчас, поэтому scrape-интервал задаёт и разрешение ваших графиков. Отсюда практика: держите /metrics на том же порту, что и сервис, но следите за весом реестра — тысяча метрик с лейблами замедлит каждый скрейп; кардинальности посвящён отдельный урок второй половины курса.

### Отдельный реестр: когда нужен контроль

```go
reg := prometheus.NewRegistry()

reg.MustRegister(collectors.NewGoCollector())

http.Handle("/metrics", promhttp.HandlerFor(reg, promhttp.HandlerOpts{}))

// процессные метрики появятся, только если
// зарегистрировать их отдельно:
reg.MustRegister(collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}))
```

NewRegistry создаёт пустой реестр вместо дефолтного, а HandlerFor связывает хендлер именно с ним. Так собирают аккуратные страницы: только свои метрики, только Go-коллектор или несколько эндпоинтов с разным содержимым. Пакет collectors входит в client_golang; тему реестров и имён до конца разберёт урок про namespace, а дефолтные метрики — урок о встроенных метриках процесса.

### Вариация: /metrics на отдельном порту

Разбор реестров подводит к полезной вариации примера: второй порт для метрик. Идея в двух ListenAndServe: основной сервер слушает внешний 8080 и про метрики не знает, а второй, поднятый в горутине на localhost:9090, отдаёт только /metrics. Выигрыш двойной: экспозиция физически недоступна из интернета, минуя любые правила прокси, и скрейп не попадает в статистику основного роутера — серии не метят сами себя. Цена тоже понятна: два порта в конфигурации, две проверки в readiness, дисциплина в файрволе. В оркестраторах чаще оставляют один порт и закрывают путь сетевыми политиками; на виртуалках и в монолитах второй порт — самый дешёвый способ изолировать экспозицию. Выбор не принципиален, пока он осознанный: случайно открытый наружу endpoint дороже любой схемы, о чём врезка ниже.

> **/metrics не для интернета.** promhttp не умеет авторизацию: endpoint, который торчит наружу, отдаёт постороннему устройство вашего сервиса — маршруты, размеры, поведение под нагрузкой, версии зависимостей. Рабочие варианты: держать /metrics на внутреннем интерфейсе или отдельном порту, закрывать сетевыми политиками, в крайнем случае — middleware с проверкой токена. Сканеры находят открытые /metrics за минуты.

### Спорное мнение: часть дефолтных метрик можно отключить

Спорное мнение на десерт: часть go_-метрик я отключаю в крупных сервисах, и это не ересь. Каждый скрейп собирает всё, что зарегистрировано: пятьдесят серий памяти, гистограмма сборок мусора, рантайм-бухгалтерия — на ферме из двухсот подов это тысячи серий, которые никто не открывает. В зрелых сервисах я оставляю go_goroutines, пару серий памяти и процессные дескрипторы, остальное убираю отдельным реестром, как показано выше, когда появляется конкретный потребитель. Граница, где я ограничиваю сам себя: пока сервис один и команды нет — не трогайте дефолт, бесплатная видимость важнее экономии серий. Отключение — инструмент зрелого мониторинга, у которого уже есть дашборды и алерты, а не первой недели проекта. Тему больших серий по-настоящему разберёт [урок про кардинальность](/courses/prometheus/lessons/17-kardinalnost/), а сегодня достаточно правила: сначала потребитель, потом отключение.

> **Дубликат регистрации — паника при старте.** Два пакета, зарегистрировавшие коллекторы в один реестр, уронят процесс вторым MustRegister при старте: реестр не терпит дубликатов. Лечится правилом владения: реестр создаёт main и передаёт параметром, пакеты регистрируют свои метрики в том, что получили. Падение при старте — удачный исход: хуже, когда дубликат живёт и молча портит экспозицию.

### Проверка: содержимое дефолтного хендлера

**Вопрос:** Что отдаёт promhttp.Handler() сразу после запуска сервиса, до регистрации ваших метрик?

- Пустую страницу: без своих метрик endpoint не работает
- Ошибку 404: хендлер требует хотя бы одну метрику
- ✅ Метрики Go-процесса и процесса ОС: горутины, память, дескрипторы
- Только строку ok

В реестре по умолчанию уже живут коллекторы Go и процесса: горутины, память, сборки мусора, процессорное время, дескрипторы. Endpoint работает с первой секунды — это главная приятность client_golang.

### Проверка: Content-Type

**Вопрос:** Какой Content-Type у ответа /metrics по умолчанию?

- application/json
- ✅ text/plain с версией формата экспозиции
- application/protobuf без согласования
- text/html

По умолчанию — текстовый формат экспозиции с версией формата в заголовке. Protobuf включается согласованием через Accept, но человеком читается именно текст — его и показывает curl.

### Что вернёт несуществующий путь

```go
func main() {
        http.Handle("/metrics", promhttp.Handler())
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
                w.Write([]byte("привет"))
        })
        http.ListenAndServe(":8080", nil)
}

// запрос: GET /unknown
```

**Что выведет программа?**

- Метрики: хендлер promhttp отдаёт их на любой путь
- ✅ 404 — ответ mux по умолчанию
- Пустой ответ со статусом 200
- Панику: путь не зарегистрирован

promhttp привязан ровно к пути /metrics, на остальные пути отвечает стандартный ServeMux: неизвестный маршрут — 404. Это удобная проверка при настройке скрейпинга: если Prometheus видит 404, цель прописана с неверным путём.

### Соберите каркас сервиса

Восстановите путь импорта, регистрацию маршрута и создание хендлера.

```go
package main

import (
        "net/http"

        "github.com/prometheus/client_golang/___/___"
)

func main() {
        http.___("/metrics", promhttp.___())

        http.ListenAndServe(":8080", nil)
}
```

Подсказка: Хендлер живёт в подпакете promhttp полного пути prometheus; маршрут регистрирует http.Handle, а создаёт хендлер promhttp.Handler().

Правильные ответы в пропусках: `prometheus`, `promhttp`, `Handle`, `Handler`

### Порядок команд в терминале

Расставьте команды от создания модуля до проверки метрик на живом сервере.

Правильный порядок строк:

1. `mkdir metrics-demo && cd metrics-demo`
2. `go mod init example.com/metrics-demo`
3. `go get github.com/prometheus/client_golang/prometheus`
4. `go run .`
5. `curl -s localhost:8080/metrics | head`
6. `curl -s localhost:8080/metrics | grep go_goroutines`

### Карточки: client_golang

- **client_golang** — Официальный клиент Prometheus для Go: типы метрик, реестры и promhttp. Ставится через go get github.com/prometheus/client_golang/prometheus.
- **promhttp.Handler()** — Готовый http.Handler, который отдаёт содержимое реестра по умолчанию: Go-метрики, процессные и всё, что зарегистрировали вы.
- **DefaultRegisterer** — Реестр по умолчанию: MustRegister кладёт метрики сюда, promhttp.Handler() читает отсюда же. Отдельный реестр — NewRegistry и HandlerFor.
- **go_goroutines** — Метрика из коробки: текущее число горутин. Первая, на которую смотрят при подозрении на утечку.
- **Формат экспозиции** — Текстовый протокол страницы метрик: строки HELP, TYPE и имя-значение. Content-Type по умолчанию — text/plain с версией формата.
- **MustRegister** — Регистрация метрик в реестре: паникует при дубликате или невалидном описании. Отсюда правило: регистрировать один раз при старте.

Endpoint жив — теперь в него можно класть свои числа. Следующий урок — [Counter](/courses/prometheus/lessons/04-counter/): prometheus.NewCounter, MustRegister, Inc и Add на живом примере заказов, включая ловушки вроде обнуления при рестарте и паники на отрицательном Add. Позже — Gauge для текущих состояний и отдельный урок про [встроенные метрики процесса](/courses/prometheus/lessons/11-default-metriki/), которые мелькали сегодня: горутины, память и сборки мусора разберём поимённо с советами, на что смотреть. Если сервис собран на [chi](/courses/chi/), маршрут /metrics вписывается тем же http.Handle рядом с остальными — совместимость с net/http полная. Карта курса — на [его странице](/courses/prometheus/); там же видно, где начнутся PromQL и Grafana, а пока цель одна: запустите сервис локально и убедитесь, что grep по go_goroutines что-то находит.

## Коротко: что запомнить

- client_golang ставится двумя командами: go mod init и go get github.com/prometheus/client_golang/prometheus.
- promhttp.Handler() — готовый http.Handler: вешается на /metrics и отдаёт реестр по умолчанию.
- Из коробки идут метрики go_ и process_: горутины, память, сборки мусора, дескрипторы, процессорное время.
- Сбор значений происходит на каждый скрейп: значения честные «прямо сейчас», интервал задаёт разрешение графиков.
- NewRegistry и HandlerFor дают отдельный реестр, когда нужен контроль содержимого страницы.
- /metrics не должен быть доступен из интернета: у promhttp нет авторизации.

## Домашнее задание

Соберите сервис урока локально: go mod init, go get, go run. Убедитесь curl-ом, что /metrics отдаёт текст, а корень — привет; найдите на странице три метрики go_ и одну process_. Затем вынесите /metrics на отдельный порт вторым ListenAndServe в горутине и подумайте, какой вариант ближе к вашему продакшену.

## Вопросы и ответы по уроку

### Какая версия Go нужна для client_golang?

Актуальная стабильная: библиотека следует за релизами Go и не поддерживает старые. Требования видны в go.mod библиотеки; дополнительных инструментов ставить не нужно.

### Почему на /metrics столько метрик, которых я не создавал?

client_golang автоматически регистрирует коллекторы Go-runtime и процесса: горутины, память, сборки мусора, дескрипторы, процессор. Это нормально и полезно; убрать их можно отдельным реестром или опциями коллекторов.

### Как спрятать /metrics от внешних пользователей?

Внутренний интерфейс или отдельный порт, сетевые политики, reverse-proxy с запретом пути, в крайнем случае middleware с токеном. Внутри promhttp авторизации нет — открытый endpoint отдаёт устройство сервиса наружу.
