Prometheus를 사용하여 Golang 애플리케이션에서 지표 수집

46793 단어 grafanagoprometheus
본고는 내가 하고 있는 일련의 문장의 일부분이다. 이 문장들은 깨끗한 구조를 사용하는 응용 프로그램의 예시와 관련된다.이 시리즈의 추가 게시물은 다음과 같습니다.
  • Clean Architecture using Golang
  • Golang: using build tags to store configurations
  • Continuous integration in projects using monorepo
  • Data Migration with Golang and MongoDB
  • Using Golang as a scripting language
  • Creating test mocks using GoMock
  • 본고에서 나는 우리가 매일 처리하는 복잡한 프로젝트 중의 매우 중요한 특성인 지표를 토론할 것이다.시장에서 이 목적을 위한 각종 해결 방안 중에서 더욱 두드러진 것은duoPrometheus+Grafana이다.
    위키백과에 따르면

    Prometheus is a free software application used for event monitoring and alerting. It records real-time metrics in a time series database built using a HTTP pull model, with flexible queries and real-time alerting.


    우리는 Grafana를 다음과 같이 설명할 수 있습니다.

    Grafana is a multi-platform open source analytics and interactive visualization software available since 2014. It provides charts, graphs, and alerts for the web when connected to supported data sources.


    요컨대 프로메테우스가 데이터를 수집한 것은 그라파나 덕분에 우리는 정보의 시각화를 편리하게 하기 위해 아름다운 도형과 계기판을 만들 수 있었다.

    용례 레이어 생성하기


    이 기능을 이용하기 위해서는 프로메테우스가 수집하고 처리할 데이터를 제공할 수 있도록 코드를 수정해야 한다.우리가 깨끗한 구조를 사용할 때, 우리가 해야 할 첫 번째 단계는 예시층으로 새로운 가방을 만드는 것이다.저장소 기반: https://github.com/eminetto/clean-architecture-go, 다음과 같은 내용으로 pkg/metric/interface file .go 만들기 시작합니다.
    package metric
    
    import "time"
    
    //CLI define a CLI app
    type CLI struct {
        Name       string
        StartedAt  time.Time
        FinishedAt time.Time
        Duration   float64
    }
    
    // NewCLI create a new CLI app
    func NewCLI(name string) *CLI {
        return &CLI{
            Name: name,
        }
    }
    
    //Started start monitoring the app
    func (c *CLI) Started() {
        c.StartedAt = time.Now()
    }
    
    // Finished app finished
    func (c *CLI) Finished() {
        c.FinishedAt = time.Now()
        c.Duration = time.Since(c.StartedAt).Seconds()
    }
    
    //HTTP application
    type HTTP struct {
        Handler    string
        Method     string
        StatusCode string
        StartedAt  time.Time
        FinishedAt time.Time
        Duration   float64
    }
    
    //NewHTTP create a new HTTP app
    func NewHTTP(handler string, method string) *HTTP {
        return &HTTP{
            Handler: handler,
            Method:  method,
        }
    }
    
    //Started start monitoring the app
    func (h *HTTP) Started() {
        h.StartedAt = time.Now()
    }
    
    // Finished app finished
    func (h *HTTP) Finished() {
        h.FinishedAt = time.Now()
        h.Duration = time.Since(h.StartedAt).Seconds()
    }
    
    //UseCase definition
    type UseCase interface {
        SaveCLI(c *CLI) error
        SaveHTTP(h *HTTP)
    }
    
    이 파일에서 우리는 명령행 응용 프로그램과 API에서 수집하고자 하는 두 가지 중요한 구조를 정의했다.우리는 또한 CLI 인터페이스 (잠시 후 실현될 것) 와 구조를 초기화하는 함수 HTTPUseCase 를 정의했다.앞에서 언급한 바와 같이, 이러한 깨끗한 구조 전략은 우리가 도량 집합의 세부 사항을 응용 프로그램의 다른 층으로 추상화할 수 있도록 한다.만약 우리가 언제든지 프로메테우스의 도량 집합 해결 방안을 다른 해결 방안으로 변경한다면, 우리는 아무런 문제가 없을 것이다. 왜냐하면 다른 층은 실현 NewCLI 인터페이스를 받기를 원하기 때문이다.
    이제 우리는 인터페이스를 실현하여 파일을 만들 것이다NewHTTP:
    package metric
    
    import (
        "github.com/prometheus/client_golang/prometheus"
        "github.com/prometheus/client_golang/prometheus/push"
        "github.com/eminetto/clean-architecture-go/config"
    )
    
    //Service implements UseCase interface
    type Service struct {
        pHistogram           *prometheus.HistogramVec
        httpRequestHistogram *prometheus.HistogramVec
    }
    
    //NewPrometheusService create a new prometheus service
    func NewPrometheusService() (*Service, error) {
        cli := prometheus.NewHistogramVec(prometheus.HistogramOpts{
            Namespace: "pushgateway",
            Name:      "cmd_duration_seconds",
            Help:      "CLI application execution in seconds",
            Buckets:   prometheus.DefBuckets,
        }, []string{"name"})
        http := prometheus.NewHistogramVec(prometheus.HistogramOpts{
            Namespace: "http",
            Name:      "request_duration_seconds",
            Help:      "The latency of the HTTP requests.",
            Buckets:   prometheus.DefBuckets,
        }, []string{"handler", "method", "code"})
    
        s := &Service{
            pHistogram:           cli,
            httpRequestHistogram: http,
        }
        err := prometheus.Register(s.pHistogram)
        if err != nil && err.Error() != "duplicate metrics collector registration attempted" {
            return nil, err
        }
        err = prometheus.Register(s.httpRequestHistogram)
        if err != nil && err.Error() != "duplicate metrics collector registration attempted" {
            return nil, err
        }
        return s, nil
    }
    
    //SaveCLI send metrics to server
    func (s *Service) SaveCLI(c *CLI) error {
        gatewayURL := config.PROMETHEUS_PUSHGATEWAY
        s.pHistogram.WithLabelValues(c.Name).Observe(c.Duration)
        return push.New(gatewayURL, "cmd_job").Collector(s.pHistogram).Push()
    }
    
    //SaveHTTP send metrics to server
    func (s *Service) SaveHTTP(h *HTTP) {
        s.httpRequestHistogram.WithLabelValues(h.Handler, h.Method, h.StatusCode).Observe(h.Duration)
    }
    
    이 파일에서, 우리는 UseCase 함수를 사용하여 pkg/metric/prometheus.go 인터페이스를 실현했고, 우리는 다음 단계에서 그것을 사용할 것이다.각 함수에 대한 자세한 내용은 공식 Go 클라이언트 documentation 에서 확인할 수 있습니다.
    이 파일의 또 다른 중요한 점은 함수 NewPrometheusService 내부의 행 UseCase 이다.프로메테우스는 도량 수집기이기 때문에 수집이 끝날 때까지 데이터를 메모리에 저장하는 방법이 필요하다.우리가 지속적으로 실행되는 응용 프로그램에 대해 이야기할 때, 예를 들어 API와 같은 데이터는 메모리에 남아 있다.그러나 실행 후 종료된 CLI 애플리케이션의 경우 이러한 데이터를 어디에 저장해야 합니다.프로메테우스 프로젝트에는 Push Gateway의 해결 방안이 있습니다.이것은 프로메테우스가 데이터를 수집할 때까지 어떤 서버에서 실행해야 하는 작은 응용 프로그램입니다.응용 프로그램의 gatewayURL: = config.PROMETHEUS_PUSHGATEWAY 를 구성할 때 PushGateway에 대해 다시 한 번 논의하겠습니다.이 구성에는 PushGateway 주소가 있습니다.나는 파일에 이 변수를 포함했다. SaveCLI, docker-compose.yml, config / config_testing.go, config / config_staging.go.post to understand 파일이 존재하는 이유를 확인하십시오.예를 들어, 파일config / config_prod.go에는 다음이 포함됩니다.
    // +build dev
    
    package config
    
    const (
        MONGODB_HOST            = "mongodb://127.0.0.1:27017"
        MONGODB_DATABASE        = "bookmark"
        MONGODB_CONNECTION_POOL = 5
        API_PORT                = 8080
        PROMETHEUS_PUSHGATEWAY = "http://localhost:9091/"
    )
    

    CLI 애플리케이션에서 지표 수집


    이제 CLI 애플리케이션에서 지표를 수집하기 위해 이 서비스를 시작합니다.이것은 config / config_dev.go 파일의 새 코드입니다.
    package main
    
    import (
        "errors"
        "fmt"
        "github.com/eminetto/clean-architecture-go/pkg/metric"
        "log"
        "os"
    
        "github.com/eminetto/clean-architecture-go/config"
        "github.com/eminetto/clean-architecture-go/pkg/bookmark"
        "github.com/eminetto/clean-architecture-go/pkg/entity"
        "github.com/juju/mgosession"
        mgo "gopkg.in/mgo.v2"
    )
    
    func handleParams() (string, error) {
        if len(os.Args) < 2 {
            return "", errors.New("Invalid query")
        }
        return os.Args[1], nil
    }
    
    func main() {
        metricService, err := metric.NewPrometheusService()
        if err != nil {
            log.Fatal(err.Error())
        }
        appMetric := metric.NewCLI("search")
        appMetric.Started()
        query, err := handleParams()
        if err != nil {
            log.Fatal(err.Error())
        }
    
        session, err := mgo.Dial(config.MONGODB_HOST)
        if err != nil {
            log.Fatal(err.Error())
        }
        defer session.Close()
    
        mPool := mgosession.NewPool(nil, session, config.MONGODB_CONNECTION_POOL)
        defer mPool.Close()
    
        bookmarkRepo := bookmark.NewMongoRepository(mPool, config.MONGODB_DATABASE)
        bookmarkService := bookmark.NewService(bookmarkRepo)
        all, err := bookmarkService.Search(query)
        if err != nil {
            log.Fatal(err)
        }
        if len(all) == 0 {
            log.Fatal(entity.ErrNotFound.Error())
        }
        for _, j := range all {
            fmt.Printf("%s %s %v \n", j.Name, j.Link, j.Tags)
        }
        appMetric.Finished()
        err = metricService.SaveCLI(appMetric)
        if err != nil {
            log.Fatal(err)
        }
    }
    
    
    config / config_dev.go 함수의 시작 부분에서 우리는 프로메테우스의 실현을 이용하여 서비스를 만들었다.
    metricService, err := metric.NewPrometheusService()
    if err != nil {
        log.Fatal(err.Error())
    }
    
    그런 다음 데이터 수집을 시작하여 어플리케이션 이름을 지정합니다. Grafana의 시각화에서 이 어플리케이션을 사용합니다.
    appMetric := metric.NewCLI("search")
    appMetric.Started()
    
    파일 끝에 수집 및 전송을 완료했습니다cmd/main.go.
    appMetric.Finished()
    err = metricService.SaveCLI(appMetric)
    if err != nil {
        log.Fatal(err)
    }
    

    API 지표 수집


    이제 API 지표를 수집합니다.우리가 모든 단점에서 지표를 수집하고 싶을 때, 우리는 middlewares의 개념을 이용할 수 있다.따라서 파일main을 만듭니다.
    package middleware
    
    import (
        "net/http"
        "strconv"
    
        "github.com/eminetto/clean-architecture-go/pkg/metric"
    
        "github.com/codegangsta/negroni"
    )
    
    //Metrics to prometheus
    func Metrics(mService metric.UseCase) negroni.HandlerFunc {
       return func(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
          appMetric := metric.NewHTTP(r.URL.Path, r.Method)
          appMetric.Started()
          next(w, r)
          res := w.(negroni.ResponseWriter)
          appMetric.Finished()
          appMetric.StatusCode = strconv.Itoa(res.Status())
          mService.SaveHTTP(appMetric)
       }
    }
    
    이 중간부품은 PushGateway 인터페이스의 실현을 수신하고 요청 상세 정보(실행 시간과 상태 코드)를 수집하기 시작하며 향후 수집을 위해 데이터를 저장합니다.우리가 API를 이야기할 때, 그것은 프로메테우스가 그것을 수집하고 처리할 때까지 메모리에 저장된다.
    새로운 중간부품을 활용하기 위해 API의 pkg/middleware/metrics.go 를 변경하고 지표를 수집하는 데 사용할 Prometheus 단점을 만들어야 합니다.파일metric. UseCase을 다음과 같이 변경합니다.
    package main
    
    import (
       "github.com/prometheus/client_golang/prometheus/promhttp"
       "log"
       "net/http"
       "os"
       "strconv"
       "time"
    
       "github.com/codegangsta/negroni"
       "github.com/eminetto/clean-architecture-go/api/handler"
       "github.com/eminetto/clean-architecture-go/config"
       "github.com/eminetto/clean-architecture-go/pkg/bookmark"
       "github.com/eminetto/clean-architecture-go/pkg/middleware"
       "github.com/eminetto/clean-architecture-go/pkg/metric"
       "github.com/gorilla/context"
       "github.com/gorilla/mux"
       "github.com/juju/mgosession"
       mgo "gopkg.in/mgo.v2"
    )
    
    func main() {
       session, err := mgo.Dial(config.MONGODB_HOST)
       if err != nil {
          log.Fatal(err.Error())
       }
       defer session.Close()
    
       r := mux.NewRouter()
    
       mPool := mgosession.NewPool(nil, session, config.MONGODB_CONNECTION_POOL)
       defer mPool.Close()
    
       bookmarkRepo := bookmark.NewMongoRepository(mPool, config.MONGODB_DATABASE)
       bookmarkService := bookmark.NewService(bookmarkRepo)
    
       metricService, err := metric.NewPrometheusService()
       if err != nil {
          log.Fatal(err.Error())
       }
    
       //handlers
       n := negroni.New(
          negroni.HandlerFunc(middleware.Cors),
          negroni.HandlerFunc(middleware.Metrics(metricService)),
          negroni.NewLogger(),
       )
       //bookmark
       handler.MakeBookmarkHandlers(r, *n, bookmarkService)
    
       http.Handle("/", r)
       http.Handle("/metrics", promhttp.Handler())
       r.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
          w.WriteHeader(http.StatusOK)
       })
    
       logger := log.New(os.Stderr, "logger: ", log.Lshortfile)
       srv := &http.Server{
          ReadTimeout:  5 * time.Second,
          WriteTimeout: 10 * time.Second,
          Addr:         ":" + strconv.Itoa(config.API_PORT),
          Handler:      context.ClearHandler(http.DefaultServeMux),
          ErrorLog:     logger,
       }
       err = srv.ListenAndServe()
       if err != nil {
          log.Fatal(err.Error())
       }
    }
    
    CLI에서 설명한 것처럼 가져오기 및 서비스 시작 외에도 첫 번째 중요한 변화는 다음과 같은 새로운 미들웨어를 실행 스택에 포함시키는 것입니다.
    n := negroni.New(
      negroni.HandlerFunc(middleware.Cors),
      negroni.HandlerFunc(middleware.Metrics(metricService)),
      negroni.NewLogger(),
    )
    
    두 번째 변화는 하나의 단점을 만드는 것이다. 프로메테우스는 이 단점을 이용하여 데이터를 수집할 것이다.
    http.Handle("/metrics", promhttp.Handler())
    
    프로메테우스가 사용할 데이터를 만들기 위해 응용 프로그램에서 필요한 모든 변경 사항입니다.우리는 지금 테스트를 촉진하기 위해 현지 환경을 세울 것이다.

    Grafana 구성


    이제 Grafana를 사용하여 프로메테우스가 수집한 데이터의 시각화를 만들 것입니다.
    액세스 링크main.go, 사용자 api/main.go 및 암호 http://localhost:3000/login 로 로그인하고 인터페이스 요구에 따라 새 암호를 생성합니다.
    로그인하면 인터페이스의 옵션을 사용하여 새 admin 를 생성해야 합니다.admin 옵션을 선택하려면 다음 정보를 입력해야 합니다.

    옵션data source에서 표준 대시보드를 가져와야 합니다.

    이제 첫 번째 대시보드를 만듭니다.

    옵션 Prometheus 을 선택하면 데이터를 채울 수 있습니다.

    검색 필드에서 다음을 사용합니다.
    http\u 요청\u 지속 시간\u 초수 {job="bookmark"}>0Dashboards 필드에 표시할 정보를 입력했습니다.
    {{handler}}-{{method}}-{{code}}
    이런 방식을 통해 접근한 URL 외에 방법과 상태 코드가 무엇인지 볼 수 있다.
    일반 옵션에서는 시각적 이름을 지정합니다.

    경고를 만들지 않으므로 업데이트된 대시보드를 보려면 복귀 (페이지 상단에 화살표가 있는 버튼) 를 클릭합니다.
    이제 CLI에 대한 정보가 포함된 새 패널을 추가합니다.

    새 질의를 작성합니다.

    질의에서 다음 값을 입력합니다.
    pushgateway_cmd_duration_seconds_sum
    
    전설로서 우리는 다음과 같이 사용한다.
    {{name}}
    일반 옵션에서 새 패널의 이름을 지정하고 대시보드로 돌아갈 수 있습니다.

    응용 프로그램이 지표를 수집할 때, 대시보드의 데이터를 업데이트합니다.고급 질의가 있는 다른 패널을 추가할 수 있습니다.프로메테우스와 그라파나 문헌에는 더 높은 예가 있다.

    결론


    이 글에서 제 목표는 Go 응용 프로그램에 도량 기능을 추가하는 것이 얼마나 간단한지 보여주는 것입니다.또 하나는 우리가 깨끗한 체계 구조를 사용하고 있다는 것이다. 이것은 우리가 창설 Add query 의 새로운 실현을 통해 프로메테우스에서 다른 해결 방안으로 옮기고 몇 개의 설정 줄만 바꾸면 된다는 것이다.이러한 지표는 우리가 응용 프로그램의 행위를 더욱 잘 이해하고 의사결정과 개선을 추진하는 데 도움을 준다.나는 이 문장이 유용해서 더 많은 항목에도 이런 좋은 점이 있기를 바란다.
    본고에서 제공한 모든 코드는 저장소에 있다https://github.com/eminetto/clean-architecture-go

    좋은 웹페이지 즐겨찾기