diff --git a/README.md b/README.md index d0d6590b..61e243ec 100644 --- a/README.md +++ b/README.md @@ -85,3 +85,24 @@ curl http://localhost:8080/register ```bash make dockertravisbuild ``` + +## Test Zipkin + +To test with Zipkin + +``` +make +docker-compose -f docker-compose-zipkin.yml build +docker-compose -f docker-compose-zipkin.yml up +``` +It takes about 10 seconds to seed data + +you should see it at: +[http://localhost:9411/](http://localhost:9411) + +be sure to hit the "Find Traces" button. You may need to reload the page. + +when done you can run: +``` +docker-compose -f docker-compose-zipkin.yml down +``` diff --git a/api/endpoints.go b/api/endpoints.go index 093afd91..369997b2 100644 --- a/api/endpoints.go +++ b/api/endpoints.go @@ -6,8 +6,10 @@ package api import ( "github.com/go-kit/kit/endpoint" + "github.com/go-kit/kit/tracing/opentracing" "github.com/microservices-demo/user/db" "github.com/microservices-demo/user/users" + stdopentracing "github.com/opentracing/opentracing-go" "golang.org/x/net/context" ) @@ -27,24 +29,28 @@ type Endpoints struct { // MakeEndpoints returns an Endpoints structure, where each endpoint is // backed by the given service. -func MakeEndpoints(s Service) Endpoints { +func MakeEndpoints(s Service, tracer stdopentracing.Tracer) Endpoints { return Endpoints{ - LoginEndpoint: MakeLoginEndpoint(s), - RegisterEndpoint: MakeRegisterEndpoint(s), - HealthEndpoint: MakeHealthEndpoint(s), - UserGetEndpoint: MakeUserGetEndpoint(s), - UserPostEndpoint: MakeUserPostEndpoint(s), - AddressGetEndpoint: MakeAddressGetEndpoint(s), - AddressPostEndpoint: MakeAddressPostEndpoint(s), - CardGetEndpoint: MakeCardGetEndpoint(s), - DeleteEndpoint: MakeDeleteEndpoint(s), - CardPostEndpoint: MakeCardPostEndpoint(s), + LoginEndpoint: opentracing.TraceServer(tracer, "GET /login")(MakeLoginEndpoint(s)), + RegisterEndpoint: opentracing.TraceServer(tracer, "POST /register")(MakeRegisterEndpoint(s)), + HealthEndpoint: opentracing.TraceServer(tracer, "GET /health")(MakeHealthEndpoint(s)), + UserGetEndpoint: opentracing.TraceServer(tracer, "GET /customers")(MakeUserGetEndpoint(s)), + UserPostEndpoint: opentracing.TraceServer(tracer, "POST /customers")(MakeUserPostEndpoint(s)), + AddressGetEndpoint: opentracing.TraceServer(tracer, "GET /addresses")(MakeAddressGetEndpoint(s)), + AddressPostEndpoint: opentracing.TraceServer(tracer, "POST /addresses")(MakeAddressPostEndpoint(s)), + CardGetEndpoint: opentracing.TraceServer(tracer, "GET /cards")(MakeCardGetEndpoint(s)), + DeleteEndpoint: opentracing.TraceServer(tracer, "DELETE /")(MakeDeleteEndpoint(s)), + CardPostEndpoint: opentracing.TraceServer(tracer, "POST /cards")(MakeCardPostEndpoint(s)), } } // MakeLoginEndpoint returns an endpoint via the given service. func MakeLoginEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "login user") + span.SetTag("service", "user") + defer span.Finish() req := request.(loginRequest) u, err := s.Login(req.Username, req.Password) return userResponse{User: u}, err @@ -54,6 +60,10 @@ func MakeLoginEndpoint(s Service) endpoint.Endpoint { // MakeRegisterEndpoint returns an endpoint via the given service. func MakeRegisterEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "register user") + span.SetTag("service", "user") + defer span.Finish() req := request.(registerRequest) id, err := s.Register(req.Username, req.Password, req.Email, req.FirstName, req.LastName) return postResponse{ID: id}, err @@ -63,8 +73,16 @@ func MakeRegisterEndpoint(s Service) endpoint.Endpoint { // MakeUserGetEndpoint returns an endpoint via the given service. func MakeUserGetEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "get users") + span.SetTag("service", "user") + defer span.Finish() + req := request.(GetRequest) + + userspan := stdopentracing.StartSpan("users from db", stdopentracing.ChildOf(span.Context())) usrs, err := s.GetUsers(req.ID) + userspan.Finish() if req.ID == "" { return EmbedStruct{usersResponse{Users: usrs}}, err } @@ -78,7 +96,9 @@ func MakeUserGetEndpoint(s Service) endpoint.Endpoint { return users.User{}, err } user := usrs[0] + attrspan := stdopentracing.StartSpan("attributes from db", stdopentracing.ChildOf(span.Context())) db.GetUserAttributes(&user) + attrspan.Finish() if req.Attr == "addresses" { return EmbedStruct{addressesResponse{Addresses: user.Addresses}}, err } @@ -92,6 +112,10 @@ func MakeUserGetEndpoint(s Service) endpoint.Endpoint { // MakeUserPostEndpoint returns an endpoint via the given service. func MakeUserPostEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "post user") + span.SetTag("service", "user") + defer span.Finish() req := request.(users.User) id, err := s.PostUser(req) return postResponse{ID: id}, err @@ -101,8 +125,14 @@ func MakeUserPostEndpoint(s Service) endpoint.Endpoint { // MakeAddressGetEndpoint returns an endpoint via the given service. func MakeAddressGetEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "get users") + span.SetTag("service", "user") + defer span.Finish() req := request.(GetRequest) + addrspan := stdopentracing.StartSpan("addresses from db", stdopentracing.ChildOf(span.Context())) adds, err := s.GetAddresses(req.ID) + addrspan.Finish() if req.ID == "" { return EmbedStruct{addressesResponse{Addresses: adds}}, err } @@ -116,6 +146,10 @@ func MakeAddressGetEndpoint(s Service) endpoint.Endpoint { // MakeAddressPostEndpoint returns an endpoint via the given service. func MakeAddressPostEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "post address") + span.SetTag("service", "user") + defer span.Finish() req := request.(addressPostRequest) id, err := s.PostAddress(req.Address, req.UserID) return postResponse{ID: id}, err @@ -125,8 +159,14 @@ func MakeAddressPostEndpoint(s Service) endpoint.Endpoint { // MakeUserGetEndpoint returns an endpoint via the given service. func MakeCardGetEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "get cards") + span.SetTag("service", "user") + defer span.Finish() req := request.(GetRequest) + cardspan := stdopentracing.StartSpan("addresses from db", stdopentracing.ChildOf(span.Context())) cards, err := s.GetCards(req.ID) + cardspan.Finish() if req.ID == "" { return EmbedStruct{cardsResponse{Cards: cards}}, err } @@ -140,6 +180,10 @@ func MakeCardGetEndpoint(s Service) endpoint.Endpoint { // MakeCardPostEndpoint returns an endpoint via the given service. func MakeCardPostEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "post card") + span.SetTag("service", "user") + defer span.Finish() req := request.(cardPostRequest) id, err := s.PostCard(req.Card, req.UserID) return postResponse{ID: id}, err @@ -149,6 +193,10 @@ func MakeCardPostEndpoint(s Service) endpoint.Endpoint { // MakeLoginEndpoint returns an endpoint via the given service. func MakeDeleteEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "delete entity") + span.SetTag("service", "user") + defer span.Finish() req := request.(deleteRequest) err = s.Delete(req.Entity, req.ID) if err == nil { @@ -161,6 +209,10 @@ func MakeDeleteEndpoint(s Service) endpoint.Endpoint { // MakeHealthEndpoint returns current health of the given service. func MakeHealthEndpoint(s Service) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { + var span stdopentracing.Span + span, ctx = stdopentracing.StartSpanFromContext(ctx, "health check") + span.SetTag("service", "user") + defer span.Finish() health := s.Health() return healthResponse{Health: health}, nil } diff --git a/api/transport.go b/api/transport.go index 2f522d8e..df7cb8a1 100644 --- a/api/transport.go +++ b/api/transport.go @@ -10,9 +10,11 @@ import ( "strings" "github.com/go-kit/kit/log" + "github.com/go-kit/kit/tracing/opentracing" httptransport "github.com/go-kit/kit/transport/http" "github.com/gorilla/mux" "github.com/microservices-demo/user/users" + stdopentracing "github.com/opentracing/opentracing-go" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/net/context" ) @@ -22,7 +24,7 @@ var ( ) // MakeHTTPHandler mounts the endpoints into a REST-y HTTP handler. -func MakeHTTPHandler(ctx context.Context, e Endpoints, logger log.Logger) http.Handler { +func MakeHTTPHandler(ctx context.Context, e Endpoints, logger log.Logger, tracer stdopentracing.Tracer) http.Handler { r := mux.NewRouter().StrictSlash(false) options := []httptransport.ServerOption{ httptransport.ServerErrorLogger(logger), @@ -38,70 +40,70 @@ func MakeHTTPHandler(ctx context.Context, e Endpoints, logger log.Logger) http.H e.LoginEndpoint, decodeLoginRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "GET /login", logger)))..., )) r.Methods("POST").Path("/register").Handler(httptransport.NewServer( ctx, e.RegisterEndpoint, decodeRegisterRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "POST /register", logger)))..., )) r.Methods("GET").PathPrefix("/customers").Handler(httptransport.NewServer( ctx, e.UserGetEndpoint, decodeGetRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "GET /customers", logger)))..., )) r.Methods("GET").PathPrefix("/cards").Handler(httptransport.NewServer( ctx, e.CardGetEndpoint, decodeGetRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "GET /cards", logger)))..., )) r.Methods("GET").PathPrefix("/addresses").Handler(httptransport.NewServer( ctx, e.AddressGetEndpoint, decodeGetRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "GET /addresses", logger)))..., )) r.Methods("POST").Path("/customers").Handler(httptransport.NewServer( ctx, e.UserPostEndpoint, decodeUserRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "POST /customers", logger)))..., )) r.Methods("POST").Path("/addresses").Handler(httptransport.NewServer( ctx, e.AddressPostEndpoint, decodeAddressRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "POST /addresses", logger)))..., )) r.Methods("POST").Path("/cards").Handler(httptransport.NewServer( ctx, e.CardPostEndpoint, decodeCardRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "POST /cards", logger)))..., )) r.Methods("DELETE").PathPrefix("/").Handler(httptransport.NewServer( ctx, e.DeleteEndpoint, decodeDeleteRequest, encodeResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "DELETE /", logger)))..., )) r.Methods("GET").PathPrefix("/health").Handler(httptransport.NewServer( ctx, e.HealthEndpoint, decodeHealthRequest, encodeHealthResponse, - options..., + append(options, httptransport.ServerBefore(opentracing.FromHTTPRequest(tracer, "GET /health", logger)))..., )) r.Handle("/metrics", promhttp.Handler()) return r diff --git a/docker-compose-zipkin.yml b/docker-compose-zipkin.yml new file mode 100644 index 00000000..279db4a5 --- /dev/null +++ b/docker-compose-zipkin.yml @@ -0,0 +1,57 @@ +version: '2' + +services: + user: + build: + context: . + image: weaveworksdemos/user + hostname: user + restart: always + read_only: true + environment: + - MONGO_HOST=user-db:27017 + - ZIPKIN=http://zipkin:9411/api/v1/spans + - reschedule=on-node-failure + ports: + - "8084:8084" + user-db: + build: + context: ./docker/user-db/ + image: weaveworksdemos/user-db + hostname: user-db + restart: always + cap_drop: + - all + cap_add: + - CHOWN + - SETGID + - SETUID + read_only: true + tmpfs: + - /tmp:rw,noexec,nosuid + environment: + - reschedule=on-node-failure + ports: + - "27017:27017" + zipkin: + image: openzipkin/zipkin + hostname: zipkin + restart: always + cap_drop: + - all + cap_add: + - CHOWN + - SETGID + - SETUID + read_only: true + tmpfs: + - /tmp:rw,noexec,nosuid + environment: + - reschedule=on-node-failure + ports: + - "9411:9411" + zipkinseed: + image: alpine + command: /bin/sh -c 'sleep 10 ; wget http://user:8084/health ; wget http://user:8084/customers ; wget http://user:8084/customers/57a98d98e4b00679b4a830af ; wget http://user:8084/cards' + + diff --git a/glide.lock b/glide.lock index 85e932e8..fc740b54 100644 --- a/glide.lock +++ b/glide.lock @@ -1,36 +1,81 @@ -hash: c5965c301f7206beca5d0b4a1a9954295a6151e365e3d3323ec081d7a591cd8e -updated: 2016-09-22T22:39:17.110450335+01:00 +hash: 4ee4713d6c89f9f5c8bb53a9d7eda94c14d69ebc70a0f30f48801c416e002740 +updated: 2016-12-22T08:28:36.69998899+01:00 imports: +- name: github.com/apache/thrift + version: 0c27352179e0463bde1f68757f2d77e3c222f530 + subpackages: + - lib/go/thrift - name: github.com/beorn7/perks version: 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 subpackages: - quantile +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew +- name: github.com/eapache/go-resiliency + version: b86b1ec0dd4209a588dc1285cdd471e73525c0b3 + subpackages: + - breaker +- name: github.com/eapache/go-xerial-snappy + version: bb955e01b9346ac19dc29eb16586c90ded99a98c +- name: github.com/eapache/queue + version: 44cc805cf13205b55f69e14bcb69867d1ae92f98 - name: github.com/go-kit/kit version: 988c05d06d8ee3a9c13782f0e49b2c6e4726388d subpackages: - endpoint - log - metrics + - metrics/internal/lv - metrics/prometheus + - tracing/opentracing - transport/http - name: github.com/go-logfmt/logfmt version: d4327190ff838312623b09bfeb50d7c93c8d9c1d - name: github.com/go-stack/stack version: 100eb0c0a9c5b306ca2fb4f165df21d80ada4b82 +- name: github.com/gogo/protobuf + version: 06ec6c31ff1bac6ed4e205a547a3d72934813ef3 + subpackages: + - proto - name: github.com/golang/protobuf version: 1f49d83d9aa00e6ce4fc8258c71cc7786aec968a subpackages: - proto +- name: github.com/golang/snappy + version: d9eb7a3d35ec988b8585d4a0068e462c27d28380 - name: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/mux version: cf79e51a62d8219d52060dfc1b4e810414ba2d15 +- name: github.com/klauspost/crc32 + version: cb6bfca970f6908083f26f39a79009d608efd5cd - name: github.com/kr/logfmt version: b84e30acd515aadc4b783ad4ff83aff3299bdfe0 - name: github.com/matttproud/golang_protobuf_extensions version: c12348ce28de40eed0136aa2b644d0ee0650e56c subpackages: - pbutil +- name: github.com/opentracing/opentracing-go + version: 0c3154a3c2ce79d3271985848659870599dfb77c + subpackages: + - ext + - log +- name: github.com/openzipkin/zipkin-go-opentracing + version: e877b8e9d1069eaedfe4f27e9b6a51cfe20073c9 + subpackages: + - _thrift/gen-go/scribe + - _thrift/gen-go/zipkincore + - flag + - types + - wire +- name: github.com/pierrec/lz4 + version: 5c9560bfa9ace2bf86080bf40d46b34ae44604df +- name: github.com/pierrec/xxHash + version: 5a004441f897722c627870a981d02b29924215fa + subpackages: + - xxHash32 - name: github.com/prometheus/client_golang version: 5636dc67ae776adf5590da7349e70fbb9559972d subpackages: @@ -44,22 +89,30 @@ imports: version: 9a94032291f2192936512bab367bc45e77990d6a subpackages: - expfmt - - model - internal/bitbucket.org/ww/goautoneg + - model - name: github.com/prometheus/procfs version: abf152e5f3e97f2fafac028d2cc06c1feb87ffa5 +- name: github.com/rcrowley/go-metrics + version: 1f30fe9094a513ce4c700b9a54458bbb0c96996c +- name: github.com/Shopify/sarama + version: 9418d7c7189af6ea2df537692f9428efe1368368 - name: golang.org/x/net version: 7394c112eae4dba7e96bfcfe738e6373d61772b4 subpackages: - context - context/ctxhttp +- name: google.golang.org/grpc + version: 09aecb094ef6b9ebe49dd999f52c505beeeb402e + subpackages: + - metadata - name: gopkg.in/mgo.v2 version: 3f83fa5005286a7fe593b055f0d7771a7dce4655 subpackages: - bson + - internal/json - internal/sasl - internal/scram - - internal/json - name: gopkg.in/tomb.v2 version: 14b3d72120e8d10ea6e6b7f87f7175734b1faab8 -devImports: [] +testImports: [] diff --git a/glide.yaml b/glide.yaml index e5fea30e..03e5c555 100644 --- a/glide.yaml +++ b/glide.yaml @@ -6,8 +6,11 @@ import: - log - metrics - metrics/prometheus + - tracing/opentracing - transport/http - package: github.com/gorilla/mux +- package: github.com/opentracing/opentracing-go +- package: github.com/openzipkin/zipkin-go-opentracing - package: github.com/prometheus/client_golang subpackages: - prometheus @@ -19,4 +22,3 @@ import: subpackages: - bson - package: gopkg.in/tomb.v2 - diff --git a/main.go b/main.go index 701f82a5..d820e379 100644 --- a/main.go +++ b/main.go @@ -15,15 +15,26 @@ import ( "github.com/microservices-demo/user/api" "github.com/microservices-demo/user/db" "github.com/microservices-demo/user/db/mongodb" + stdopentracing "github.com/opentracing/opentracing-go" + zipkin "github.com/openzipkin/zipkin-go-opentracing" stdprometheus "github.com/prometheus/client_golang/prometheus" "golang.org/x/net/context" ) -var dev bool -var port string -var acc string +var ( + dev bool + port string + acc string + zip string +) + +const ( + ServiceName = "user" +) func init() { + + flag.StringVar(&zip, "zipkin", os.Getenv("ZIPKIN"), "Zipkin address") flag.StringVar(&port, "port", "8084", "Port on which to run") db.Register("mongodb", &mongodb.Mongo{}) } @@ -42,6 +53,32 @@ func main() { logger = log.NewContext(logger).With("ts", log.DefaultTimestampUTC) logger = log.NewContext(logger).With("caller", log.DefaultCaller) } + + var tracer stdopentracing.Tracer + { + if zip == "" { + tracer = stdopentracing.NoopTracer{} + } else { + logger := log.NewContext(logger).With("tracer", "Zipkin") + logger.Log("addr", zip) + collector, err := zipkin.NewHTTPCollector( + zip, + zipkin.HTTPLogger(logger), + ) + if err != nil { + logger.Log("err", err) + os.Exit(1) + } + tracer, err = zipkin.NewTracer( + zipkin.NewRecorder(collector, false, fmt.Sprintf("localhost:%v", port), ServiceName), + ) + if err != nil { + logger.Log("err", err) + os.Exit(1) + } + } + stdopentracing.InitGlobalTracer(tracer) + } dbconn := false for !dbconn { err := db.Init() @@ -81,12 +118,12 @@ func main() { } // Endpoint domain. - endpoints := api.MakeEndpoints(service) + endpoints := api.MakeEndpoints(service, tracer) // Create and launch the HTTP server. go func() { logger.Log("transport", "HTTP", "port", port) - handler := api.MakeHTTPHandler(ctx, endpoints, logger) + handler := api.MakeHTTPHandler(ctx, endpoints, logger, tracer) errc <- http.ListenAndServe(fmt.Sprintf(":%v", port), handler) }()