Skip to content
This repository has been archived by the owner on Oct 27, 2020. It is now read-only.

Commit

Permalink
don't wait for metrics to be written before ending request
Browse files Browse the repository at this point in the history
  • Loading branch information
GRECO, FRANK committed Aug 26, 2017
1 parent 829a997 commit 399e65b
Show file tree
Hide file tree
Showing 6 changed files with 31 additions and 56 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- If connection to the k8s apiserver is lost, it will be reattempted after 5 seconds.
- When TPRs are created, the server will not start until it is confirmed that they are created.
- Kanali process will not terminate if a connection to Jaeger cannot be made.
- Response will not wait for metrics to be written to InfluxDB before completing.
### Removed
- Status server.
- Deprecated `enable-tracing` flag. Like InfluxDB, a best effort at a connection will be made.
Expand Down
27 changes: 0 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,6 @@ Kanali is an extremely efficient [Kubernetes](https://kubernetes.io/) ingress co
* [Tutorial](#tutorial)
* [Documentation](#documentation)
* [Plugins](#plugins)
* [Alternatives](#alternatives)
* [Native Kubernetes Ingress](#native-kubernetes-ingress)
* [Apigee](#apigee)
* [Traefik](#traefik)
* [Analytics, Monitoring, and Tracing](#analytics-monitoring-and-tracing)
* [Installation](#installation)
* [Helm](#helm)
Expand Down Expand Up @@ -59,29 +55,6 @@ Looking for documentation for the custom Kubernetes resources that Kanali create

While Kanali has its own built in plugins, it boasts a decoupled plugin framework that makes it easy for anyone to write and integrate their own custom and version controlled plugin! The guided tutorial can be found [here](./PLUGIN_GUIDE.md).

# Alternatives

Kanali describes itself as *an extremely efficient Kubernetes ingress controller with robust API management capabilities*. There are no doubt both open source and vendor products that accomplish similar things.... so why use Kanali? Let's compare some obvious alternatives:

#### Native Kubernetes Ingress

Kubernetes provides a native [`Ingress`](https://kubernetes.io/docs/concepts/services-networking/ingress/) resource spec. The implementation of this resource, along with other Kubernetes resources such as the `NetworkPolicy` resource, are left as an exercise.

The [NGINX](https://github.com/kubernetes/ingress/tree/master/controllers/nginx#limitations) ingress controller for Kubernetes is a popular implementation for this native resource. However, it has no concept of features Kanali provides such as dynamic service discovery, mutual TLS for upstream services, API management, or a version controlled plugin system. In addition, every time an ingress is create or updated, the NGINX process is required to restart which could affect performance.

#### Apigee

[Apigee](https://apigee.com/api-management/#/homepage) provides a complete API management appliance. They provide multiple solutions including both self and cloud hosted solutions. While their solution is extremely robust, it is heavyweight, expensive, decoupled from Kubernetes, and requires a complex infrastructure setup when using their self hosted solution. In addition, given its *appliance* nature, it is next to impossible to develop with it locally, a feature that is a non-negotiable amongst developers.

#### Traefik

[Traefik](https://docs.traefik.io/user-guide/kubernetes/) is a modern HTTP reverse proxy and load balancer made to deploy microservices with ease. One of the projects it provides is an open source implementation for native Kubernetes ingress resources.

Traefik was not built to be as focused and native to Kubernetes as Kanali strives to be. Because of this, it has a lot of shortcomings with regards to Ingress definitions.

While Traefik does provide a few API management features, Kanali offers so much more such as a robust API key management ecosystem, decoupled and extensible plugin system, Opentracing compatibility, and more.


# Analytics, Monitoring, and Tracing

Kanali leverages [Grafana](https://grafana.com/) and [InfluxDB](https://www.influxdata.com/) for analytics and monitoring. It also uses [Jaeger](http://jaeger.readthedocs.io/en/latest/) for tracing. If you are using Helm to deploy Kanali, these tools are deployed and configured for you.
Expand Down
4 changes: 2 additions & 2 deletions flow/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ func (f *Flow) Add(steps ...Step) {

// Play executes all step in a flow in the order they were added.
func (f *Flow) Play(ctx context.Context, metrics *metrics.Metrics, ctlr *controller.Controller, w http.ResponseWriter, r *http.Request, resp *http.Response, trace opentracing.Span) error {
logrus.Infof("a flow with %d step is about to play", len(*f))
logrus.Debugf("flow with %d step about to play", len(*f))
for _, step := range *f {
logrus.Infof("playing step %s", step.GetName())
logrus.Debugf("playing step %s", step.GetName())
if err := step.Do(ctx, metrics, ctlr, w, r, resp, trace); err != nil {
trace.SetTag("error", true)
trace.LogKV(
Expand Down
30 changes: 26 additions & 4 deletions handlers/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@ import (
"fmt"
"net/http"
"strconv"
"strings"
"time"

"github.com/Sirupsen/logrus"
"github.com/northwesternmutual/kanali/config"
"github.com/northwesternmutual/kanali/controller"
"github.com/northwesternmutual/kanali/metrics"
"github.com/northwesternmutual/kanali/monitor"
"github.com/northwesternmutual/kanali/utils"
"github.com/opentracing/opentracing-go"
"github.com/spf13/viper"
Expand All @@ -39,12 +42,31 @@ import (
// Handler is used to provide additional parameters to an HTTP handler
type Handler struct {
*controller.Controller
*metrics.Metrics
*monitor.InfluxController
H func(ctx context.Context, m *metrics.Metrics, c *controller.Controller, w http.ResponseWriter, r *http.Request, trace opentracing.Span) error
}

func (h Handler) serveHTTP(w http.ResponseWriter, r *http.Request) {

t0 := time.Now()
m := &metrics.Metrics{}

defer func() {
m.Add(
metrics.Metric{Name: "total_time", Value: int(time.Now().Sub(t0) / time.Millisecond), Index: false},
metrics.Metric{Name: "http_method", Value: r.Method, Index: true},
metrics.Metric{Name: "http_uri", Value: r.URL.EscapedPath(), Index: false},
metrics.Metric{Name: "client_ip", Value: strings.Split(r.RemoteAddr, ":")[0], Index: false},
)
go func() {
if err := h.InfluxController.WriteRequestData(m); err != nil {
logrus.Warnf("error writing metrics to InfluxDB: %s", err.Error())
} else {
logrus.Debugf("wrote metrics to InfluxDB")
}
}()
}()

// start a global trace
sp := opentracing.StartSpan(fmt.Sprintf("%s %s",
r.Method,
Expand Down Expand Up @@ -72,7 +94,7 @@ func (h Handler) serveHTTP(w http.ResponseWriter, r *http.Request) {

defer sp.Finish()

err = h.H(context.Background(), h.Metrics, h.Controller, w, r, sp)
err = h.H(context.Background(), m, h.Controller, w, r, sp)

// handle request errors
if err != nil {
Expand All @@ -92,7 +114,7 @@ func (h Handler) serveHTTP(w http.ResponseWriter, r *http.Request) {
"uri": r.URL.EscapedPath(),
}).Error(e.Error())

h.Metrics.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(e.Status()), Index: true})
m.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(e.Status()), Index: true})

errStatus, err := json.Marshal(utils.JSONErr{Code: e.Status(), Msg: e.Error()})
if err != nil {
Expand All @@ -119,7 +141,7 @@ func (h Handler) serveHTTP(w http.ResponseWriter, r *http.Request) {
"uri": r.URL.EscapedPath(),
}).Error("unknown error")

h.Metrics.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(http.StatusInternalServerError), Index: true})
m.Add(metrics.Metric{Name: "http_response_code", Value: strconv.Itoa(http.StatusInternalServerError), Index: true})

errStatus, err := json.Marshal(utils.JSONErr{Code: http.StatusInternalServerError, Msg: "unknown error"})
if err != nil {
Expand Down
23 changes: 1 addition & 22 deletions handlers/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,46 +23,25 @@ package handlers
import (
"net/http"
"strings"
"time"

"github.com/Sirupsen/logrus"
"github.com/northwesternmutual/kanali/metrics"
"github.com/northwesternmutual/kanali/monitor"
)

// Logger creates a custom http.Handler that logs details around a request
// along with creating contextual request metrics. When the request is complete
// these metrics will be writtin to Influxdb
func Logger(influxCtlr *monitor.InfluxController, inner Handler) http.Handler {
func Logger(inner Handler) http.Handler {

return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

inner.Metrics = &metrics.Metrics{}

t0 := time.Now()
inner.serveHTTP(w, r)
t1 := time.Now()

logrus.WithFields(logrus.Fields{
"client ip": strings.Split(r.RemoteAddr, ":")[0],
"method": r.Method,
"uri": r.URL.EscapedPath(),
"totalTime": int(t1.Sub(t0) / time.Millisecond),
}).Info("request details")

inner.Metrics.Add(
metrics.Metric{Name: "total_time", Value: int(t1.Sub(t0) / time.Millisecond), Index: false},
metrics.Metric{Name: "http_method", Value: r.Method, Index: true},
metrics.Metric{Name: "http_uri", Value: r.URL.EscapedPath(), Index: false},
metrics.Metric{Name: "client_ip", Value: strings.Split(r.RemoteAddr, ":")[0], Index: false},
)

if err := influxCtlr.WriteRequestData(inner.Metrics); err != nil {
logrus.Warnf("error writing request details to InfluxDB: %s", err.Error())
} else {
logrus.Debugf("successfully wrote request details to InfluxDB")
}

})

}
2 changes: 1 addition & 1 deletion server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func Start(c *controller.Controller, influxCtlr *monitor.InfluxController) {
var lerr error
var scheme string

router := h.Logger(influxCtlr, h.Handler{Controller: c, H: h.IncomingRequest})
router := h.Logger(h.Handler{Controller: c, InfluxController: influxCtlr, H: h.IncomingRequest})

address := fmt.Sprintf("%s:%d",
viper.GetString(config.FlagBindAddress.GetLong()),
Expand Down

0 comments on commit 399e65b

Please sign in to comment.