SlideShare a Scribd company logo
Readying your Go webservice
Ralph Ligtenberg
September 12, 2019
https://www.pexels.com/photo/web-close-up-photography-2632059/
Ralph Ligtenberg
Tech Lead @ Travix
C#, Go, Google Cloud Platform
twitter.com/prutswonder
medium.com/@prutswonder
linkedin.com/in/ralphligtenberg
Done
❏ “Works on my machine”
❏ Merged to master
❏ Unit & Integration tests
❏ Works as expected
Ready
❏ “Works anywhere”
❏ Deployed & tested
❏ Acceptance & load tests
❏ Performs as expected
https://www.pexels.com/photo/photo-of-green-data-matrix-1089438/
● CI/CD: build & deployment automation
● Observability: logging, monitoring & tracing
● Resilience: Circuit breaker & panic recovery
● Lifecycle management: warm-up & shutdown
● Security: authentication & authorization
From “Done” to “Ready”
https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
CI/CD: Kubernetes & Estafette
https://www.pexels.com/photo/red-and-gray-industrial-machinery-2569842/
Meetup talk: Readying your Go Webservice
Estafette build stage
# https://estafette.io/usage/manifest/#build-stages
stages:
test-lint-build:
image: golang:1.13.0
env:
CGO_ENABLED: 0
GOOS: linux
GO111MODULE: "on"
GOGC: off
commands:
- go get -u golang.org/x/lint/golint
- CGO_ENABLED=1 go test -race -cover ./...
- golint -set_exit_status ./...
- go build -a -installsuffix cgo -o ./publish/${ESTAFETTE_LABEL_APP} .
Estafette docker image creation
# https://estafette.io/usage/extensions/estafette-extensions/#extensions-docker
bake:
image: extensions/docker:stable
action: build
path: ./publish
push:
image: extensions/docker:stable
action: push
Estafette deployment
# https://estafette.io/usage/manifest/#releases
releases:
development:
stages:
deploy:
image: extensions/gke:stable
container:
port: 8080
env:
CORS_ORIGINS: "*"
cpu:
request: 10m
limit: 100m
memory:
request: 25Mi
limit: 100Mi
# Continued on the right -->
liveness:
path: /live
readiness:
path: /ready
sidecar:
healthcheckpath: /live
slack-notify:
image: extensions/slack-build-status:stable
workspace: travix
channels:
- '#{ESTAFETTE_LABEL_TEAM}-builds'
when:
status == 'failed'
Logging: ELK Stack & zerolog
https://www.pexels.com/photo/bark-brown-cut-daylight-296333/
Logging
import (
"os"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
)
// Logger is used to write log messages.
type Logger struct {
level zerolog.Level
logger zerolog.Logger
}
// NewLogger sets and returns a new global-level logger.
func NewLogger(appGroup, appName, logLevel string) Logger {
zLogLevel := ToZeroLogLevel(logLevel)
logger := log.Output(v3FormatWriter{Out: os.Stderr}).With().
Str("appgroup", appGroup).Str("appname", appName).
Logger().Level(zLogLevel)
log.Logger = logger
return Logger{level: zLogLevel, logger: logger}
}
Monitoring: Prometheus & Grafana
https://www.pexels.com/photo/black-and-white-business-chart-computer-241544/
Custom metrics
import p8s "github.com/prometheus/client_golang/prometheus"
var (
labelNames = []string{"affiliate", "status"}
responseCounter = p8s.NewCounterVec(p8s.CounterOpts{Name: "app_response_count"}, labelNames)
)
func init() {
p8s.MustRegister(responseCounter)
}
func countFailedResponse(affiliate string, reason string) {
responseCounter.WithLabelValues(affiliate, reason).Inc()
}
func countSuccessResponse(affiliate string) {
responseCounter.WithLabelValues(affiliate, "success").Inc()
}
Meetup talk: Readying your Go Webservice
Tracing: Jaeger
https://www.pexels.com/photo/white-airplane-728824/
Tracing middleware
// WithTracing creates a span for each request
func WithTracing(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// retrieve span context from upstream caller if available
tracingCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders,
opentracing.HTTPHeadersCarrier(r.Header))
span := opentracing.StartSpan(fmt.Sprintf("%v %v", r.Method, r.URL.Path),
ext.RPCServerOption(tracingCtx))
defer span.Finish()
ext.HTTPMethod.Set(span, r.Method)
ext.HTTPUrl.Set(span, r.URL.String())
tw := tracedResponseWriter{ResponseWriter: w}
rc := r.WithContext(opentracing.ContextWithSpan(r.Context(), span))
next.ServeHTTP(&tw, rc)
ext.HTTPStatusCode.Set(span, uint16(tw.Status()))
})
}
Tracing: Jaeger
Circuit breakers
https://www.pexels.com/photo/2-man-on-construction-site-during-daytime-159306/
Circuit breaker
// Try wraps a function with circuit breaking
func (b *Breaker) Try(cmdKey string, tryFunc func() (
interface{}, error)) (interface{}, error) {
type result struct {
response interface{}
err error
}
startTime := time.Now()
cmd := b.getOrAddCommand(cmdKey)
if !cmd.allowRequest() {
cmd.registerShortCircuited()
return nil, ErrRequestShortCircuited
}
// Continued on the right -->
ch := make(chan result, 1)
go func(ch chan result) {
defer close(ch)
response, err := tryFunc()
ch <- result{response: response, err: err}
}(ch)
timer := time.NewTimer(cmd.timeout)
defer timer.Stop()
select {
case res := <-ch:
if res.err != nil {
cmd.registerFailure(time.Since(startTime))
return nil, res.err
}
cmd.registerSuccess(time.Since(startTime))
return res.response, nil
case <-timer.C:
cmd.registerTimeout(time.Since(startTime))
return nil, &ErrRequestTimeout{commandKey: cmdKey,
timeout: cmd.timeout}
}
}
Panic recovery middleware
// WithPanicRecoveryFunc recovers any panics that occur in the next http handler.
func WithPanicRecoveryFunc(logger logging.Logger, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rec := recover(); rec != nil {
logger.Error("PanicRecovered").
Bytes("stacktrace", debug.Stack()).
LogErr(fmt.Errorf("%s", rec))
w.WriteHeader(http.StatusInternalServerError)
}
}()
next(w, r)
}
}
Application start-up
https://www.pexels.com/photo/close-up-engine-landscape-locomotive-533608/
Server & start-up
type shutdownFunc func() error
type server struct {
ctx context.Context
version versionInfo
shutdown shutdownFunc
}
func (s *server) Serve() {
// (...)
mux := http.NewServeMux()
svr := http.Server{
ReadTimeout: time.Second * 3,
ReadHeaderTimeout: time.Second * 2,
WriteTimeout: time.Second * 10,
Addr: ":8080",
Handler: mux,
}
// Continued on the right -->
mux.HandleFunc("/ready", s.ready)
mux.HandleFunc("/live", s.live)
// Put your own endpoints here
err = s.setControllers(mux)
if err != nil {
log.Error("ServerControllerError").LogErr(err)
return
}
_ = svr.ListenAndServe() // Blocks until server stops
}
func (s *server) live(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "Alive!")
}
func (s *server) ready(w http.ResponseWriter, r *http.Request) {
//TODO: Determine readiness
_, _ = io.WriteString(w, "Ready!")
}
Graceful shutdown
https://www.pexels.com/photo/bulb-close-up-conceptual-current-207420/
Server with graceful shutdown
func (s *server) Serve() {
// (...)
s.shutdown = func() error {
cancelCtx, cancel := context.WithTimeout(s.ctx,
time.Second * 10)
defer cancel()
err := svr.Shutdown(cancelCtx)
return err
}
signalCh := make(chan os.Signal, 1)
go s.listenToShutdownSignals(signalCh)
_ = svr.ListenAndServe() // Blocks until server stops
}
func (s *server) listenToShutdownSignals(
signalCh chan os.Signal) {
signal.Notify(signalCh,
syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c
syscall.SIGTERM, // kill -SIGTERM XXXX
syscall.SIGQUIT) // kill -SIGQUIT XXXX
for {
sig := <-signalCh
switch sig {
case syscall.SIGINT:
fallthrough
case syscall.SIGTERM:
fallthrough
case syscall.SIGQUIT:
s.shutdown()
return
}
}
}
Security
https://www.pexels.com/photo/door-handle-key-keyhole-279810/
Estafette & cloud credentials
releases:
development:
stages:
deploy:
image: extensions/gke:stable
visibility: public-whitelist
container:
# (...)
sidecar:
healthcheckpath: /live
useGoogleCloudCredentials: true
Estafette secrets
production:
# https://estafette.io/usage/manifest/#release-actions
actions:
- name: deploy-canary
- name: deploy-stable
- name: rollback-canary
stages:
deploy:
image: extensions/gke:stable
visibility: public
container:
port: 8080
env:
# AES-256-encrypted value
AUTH_BASIC_TOKEN: estafette.secret(6J1RtwwpvMIhMtbE.QuRJhnTgO8FcHP-cEtJy72pTzaTw2L5-1zeT0A==)
Authorization middleware
import "net/http"
// WithAuthBasic performs a basic authorization check
func WithAuthBasic(token string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Header.Get("Authorization") {
case "Basic " + token:
next.ServeHTTP(w, r)
case "":
w.Header().Add("WWW-Authenticate", "Basic")
w.WriteHeader(http.StatusUnauthorized)
default:
w.WriteHeader(http.StatusForbidden)
}
})
}
CORS middleware
import "github.com/rs/cors"
// WithCORS wraps the handler with CORS options.
func WithCORS(handlerFunc http.HandlerFunc) http.Handler {
corsOptions := cors.Options{
AllowedOrigins: []string{os.Getenv("CORS_ORIGINS")},
AllowedMethods: []string{"HEAD", "OPTIONS", "GET", "PUT", "POST"},
AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-CSRF-Token"},
ExposedHeaders: []string{
"Access-Control-Allow-Headers",
"Access-Control-Allow-Methods",
"Access-Control-Max-Age",
},
MaxAge: 0,
}
return cors.New(corsOptions).Handler(handlerFunc)
}
● Automate builds & deployments for your webservice
● Observe your webservice
● Make your webservice resilient
● Warm up your webservice
● Shut down your webservice gracefully
● Secure your webservice
Getting “Ready”
https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
Estafette: https://estafette.io/
Zerolog: https://github.com/rs/zerolog
Prometheus: https://github.com/prometheus/client_golang
Grafana: https://grafana.com/
Jaeger: https://www.jaegertracing.io/
CORS: https://github.com/rs/cors
Circuit breaker: https://martinfowler.com/bliki/CircuitBreaker.html
References
https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
Thank you!
Ralph Ligtenberg - @prutswonder
stock images downloaded from Pexels.com
https://www.pexels.com/photo/silhouette-of-airplanes-47044/

More Related Content

PDF
OpenNebula and SaltStack - OpenNebulaConf 2013
PDF
Devinsampa nginx-scripting
PDF
How to Develop Puppet Modules: From Source to the Forge With Zero Clicks
PDF
The SaltStack Pub Crawl - Fosscomm 2016
PDF
Puppet User Group Presentation - 15 March 2012
PDF
Using ngx_lua in UPYUN
PDF
THE RED METHOD: HOW TO INSTRUMENT YOUR SERVICES
PDF
Using ngx_lua in UPYUN 2
OpenNebula and SaltStack - OpenNebulaConf 2013
Devinsampa nginx-scripting
How to Develop Puppet Modules: From Source to the Forge With Zero Clicks
The SaltStack Pub Crawl - Fosscomm 2016
Puppet User Group Presentation - 15 March 2012
Using ngx_lua in UPYUN
THE RED METHOD: HOW TO INSTRUMENT YOUR SERVICES
Using ngx_lua in UPYUN 2

What's hot (20)

PDF
Puppet and Openshift
PDF
Quay 3.3 installation
PDF
Bootstrapping multidc observability stack
PDF
Roll Your Own API Management Platform with nginx and Lua
PDF
Using ngx_lua in upyun 2
PDF
Anton Moldovan "Load testing which you always wanted"
PPTX
OpenShift4 Installation by UPI on kvm
PDF
Puppet and the HashiStack
PDF
Puppet Module Reusability - What I Learned from Shipping to the Forge
PDF
Puppet Camp Düsseldorf 2014: Continuously Deliver Your Puppet Code with Jenki...
PDF
OlinData Puppet Presentation for MOSC 2012
KEY
Railsconf2011 deployment tips_for_slideshare
PDF
Fun with Ruby and Cocoa
KEY
Trac/Subversion/JUnit/Maven/Jenkinsで構築する開発スタイル
PDF
Lua tech talk
KEY
Gevent what's the point
PPTX
openATTIC using grafana and prometheus
PPT
Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...
PPTX
2012 coscup - Build your PHP application on Heroku
Puppet and Openshift
Quay 3.3 installation
Bootstrapping multidc observability stack
Roll Your Own API Management Platform with nginx and Lua
Using ngx_lua in upyun 2
Anton Moldovan "Load testing which you always wanted"
OpenShift4 Installation by UPI on kvm
Puppet and the HashiStack
Puppet Module Reusability - What I Learned from Shipping to the Forge
Puppet Camp Düsseldorf 2014: Continuously Deliver Your Puppet Code with Jenki...
OlinData Puppet Presentation for MOSC 2012
Railsconf2011 deployment tips_for_slideshare
Fun with Ruby and Cocoa
Trac/Subversion/JUnit/Maven/Jenkinsで構築する開発スタイル
Lua tech talk
Gevent what's the point
openATTIC using grafana and prometheus
Montreal On Rails 5 : Rails deployment using : Nginx, Mongrel, Mongrel_cluste...
2012 coscup - Build your PHP application on Heroku
Ad

Similar to Meetup talk: Readying your Go Webservice (20)

PDF
Server Side Swift: Vapor
PDF
How to Leverage Go for Your Networking Needs
PDF
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
PDF
Job Queue in Golang
PDF
Rhebok, High Performance Rack Handler / Rubykaigi 2015
PDF
From Node to Go
KEY
Writing robust Node.js applications
PPTX
Web sockets in Java
PDF
如何透過 Go-kit 快速搭建微服務架構應用程式實戰
PPT
Server side JavaScript: going all the way
PDF
Go for the Paranoid Network Programmer, 2025 Edition
PDF
Architecting Alive Apps
PDF
Vladimir Vorontsov - Splitting, smuggling and cache poisoning come back
PPTX
Angular js security
PDF
REST made simple with Java
ODP
Implementing Comet using PHP
PPT
JS everywhere 2011
PDF
Debugging: Rules And Tools - PHPTek 11 Version
PDF
Building Web APIs that Scale
PDF
Presto anatomy
Server Side Swift: Vapor
How to Leverage Go for Your Networking Needs
GDG Devfest 2019 - Build go kit microservices at kubernetes with ease
Job Queue in Golang
Rhebok, High Performance Rack Handler / Rubykaigi 2015
From Node to Go
Writing robust Node.js applications
Web sockets in Java
如何透過 Go-kit 快速搭建微服務架構應用程式實戰
Server side JavaScript: going all the way
Go for the Paranoid Network Programmer, 2025 Edition
Architecting Alive Apps
Vladimir Vorontsov - Splitting, smuggling and cache poisoning come back
Angular js security
REST made simple with Java
Implementing Comet using PHP
JS everywhere 2011
Debugging: Rules And Tools - PHPTek 11 Version
Building Web APIs that Scale
Presto anatomy
Ad

Recently uploaded (20)

PPTX
MYSQL Presentation for SQL database connectivity
PPTX
sap open course for s4hana steps from ECC to s4
PDF
Encapsulation_ Review paper, used for researhc scholars
PPTX
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
PPTX
Programs and apps: productivity, graphics, security and other tools
PPTX
Spectroscopy.pptx food analysis technology
PPTX
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
PDF
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
PDF
Network Security Unit 5.pdf for BCA BBA.
PPTX
20250228 LYD VKU AI Blended-Learning.pptx
PDF
Spectral efficient network and resource selection model in 5G networks
PPTX
Big Data Technologies - Introduction.pptx
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
cuic standard and advanced reporting.pdf
PDF
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
PDF
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
PDF
Advanced methodologies resolving dimensionality complications for autism neur...
PPTX
Digital-Transformation-Roadmap-for-Companies.pptx
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PDF
Chapter 3 Spatial Domain Image Processing.pdf
MYSQL Presentation for SQL database connectivity
sap open course for s4hana steps from ECC to s4
Encapsulation_ Review paper, used for researhc scholars
Effective Security Operations Center (SOC) A Modern, Strategic, and Threat-In...
Programs and apps: productivity, graphics, security and other tools
Spectroscopy.pptx food analysis technology
VMware vSphere Foundation How to Sell Presentation-Ver1.4-2-14-2024.pptx
Blue Purple Modern Animated Computer Science Presentation.pdf.pdf
Network Security Unit 5.pdf for BCA BBA.
20250228 LYD VKU AI Blended-Learning.pptx
Spectral efficient network and resource selection model in 5G networks
Big Data Technologies - Introduction.pptx
The AUB Centre for AI in Media Proposal.docx
cuic standard and advanced reporting.pdf
Peak of Data & AI Encore- AI for Metadata and Smarter Workflows
How UI/UX Design Impacts User Retention in Mobile Apps.pdf
Advanced methodologies resolving dimensionality complications for autism neur...
Digital-Transformation-Roadmap-for-Companies.pptx
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
Chapter 3 Spatial Domain Image Processing.pdf

Meetup talk: Readying your Go Webservice

  • 1. Readying your Go webservice Ralph Ligtenberg September 12, 2019 https://www.pexels.com/photo/web-close-up-photography-2632059/
  • 2. Ralph Ligtenberg Tech Lead @ Travix C#, Go, Google Cloud Platform twitter.com/prutswonder medium.com/@prutswonder linkedin.com/in/ralphligtenberg
  • 3. Done ❏ “Works on my machine” ❏ Merged to master ❏ Unit & Integration tests ❏ Works as expected Ready ❏ “Works anywhere” ❏ Deployed & tested ❏ Acceptance & load tests ❏ Performs as expected https://www.pexels.com/photo/photo-of-green-data-matrix-1089438/
  • 4. ● CI/CD: build & deployment automation ● Observability: logging, monitoring & tracing ● Resilience: Circuit breaker & panic recovery ● Lifecycle management: warm-up & shutdown ● Security: authentication & authorization From “Done” to “Ready” https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
  • 5. CI/CD: Kubernetes & Estafette https://www.pexels.com/photo/red-and-gray-industrial-machinery-2569842/
  • 7. Estafette build stage # https://estafette.io/usage/manifest/#build-stages stages: test-lint-build: image: golang:1.13.0 env: CGO_ENABLED: 0 GOOS: linux GO111MODULE: "on" GOGC: off commands: - go get -u golang.org/x/lint/golint - CGO_ENABLED=1 go test -race -cover ./... - golint -set_exit_status ./... - go build -a -installsuffix cgo -o ./publish/${ESTAFETTE_LABEL_APP} .
  • 8. Estafette docker image creation # https://estafette.io/usage/extensions/estafette-extensions/#extensions-docker bake: image: extensions/docker:stable action: build path: ./publish push: image: extensions/docker:stable action: push
  • 9. Estafette deployment # https://estafette.io/usage/manifest/#releases releases: development: stages: deploy: image: extensions/gke:stable container: port: 8080 env: CORS_ORIGINS: "*" cpu: request: 10m limit: 100m memory: request: 25Mi limit: 100Mi # Continued on the right --> liveness: path: /live readiness: path: /ready sidecar: healthcheckpath: /live slack-notify: image: extensions/slack-build-status:stable workspace: travix channels: - '#{ESTAFETTE_LABEL_TEAM}-builds' when: status == 'failed'
  • 10. Logging: ELK Stack & zerolog https://www.pexels.com/photo/bark-brown-cut-daylight-296333/
  • 11. Logging import ( "os" "github.com/rs/zerolog" "github.com/rs/zerolog/log" ) // Logger is used to write log messages. type Logger struct { level zerolog.Level logger zerolog.Logger } // NewLogger sets and returns a new global-level logger. func NewLogger(appGroup, appName, logLevel string) Logger { zLogLevel := ToZeroLogLevel(logLevel) logger := log.Output(v3FormatWriter{Out: os.Stderr}).With(). Str("appgroup", appGroup).Str("appname", appName). Logger().Level(zLogLevel) log.Logger = logger return Logger{level: zLogLevel, logger: logger} }
  • 12. Monitoring: Prometheus & Grafana https://www.pexels.com/photo/black-and-white-business-chart-computer-241544/
  • 13. Custom metrics import p8s "github.com/prometheus/client_golang/prometheus" var ( labelNames = []string{"affiliate", "status"} responseCounter = p8s.NewCounterVec(p8s.CounterOpts{Name: "app_response_count"}, labelNames) ) func init() { p8s.MustRegister(responseCounter) } func countFailedResponse(affiliate string, reason string) { responseCounter.WithLabelValues(affiliate, reason).Inc() } func countSuccessResponse(affiliate string) { responseCounter.WithLabelValues(affiliate, "success").Inc() }
  • 16. Tracing middleware // WithTracing creates a span for each request func WithTracing(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // retrieve span context from upstream caller if available tracingCtx, _ := opentracing.GlobalTracer().Extract(opentracing.HTTPHeaders, opentracing.HTTPHeadersCarrier(r.Header)) span := opentracing.StartSpan(fmt.Sprintf("%v %v", r.Method, r.URL.Path), ext.RPCServerOption(tracingCtx)) defer span.Finish() ext.HTTPMethod.Set(span, r.Method) ext.HTTPUrl.Set(span, r.URL.String()) tw := tracedResponseWriter{ResponseWriter: w} rc := r.WithContext(opentracing.ContextWithSpan(r.Context(), span)) next.ServeHTTP(&tw, rc) ext.HTTPStatusCode.Set(span, uint16(tw.Status())) }) }
  • 19. Circuit breaker // Try wraps a function with circuit breaking func (b *Breaker) Try(cmdKey string, tryFunc func() ( interface{}, error)) (interface{}, error) { type result struct { response interface{} err error } startTime := time.Now() cmd := b.getOrAddCommand(cmdKey) if !cmd.allowRequest() { cmd.registerShortCircuited() return nil, ErrRequestShortCircuited } // Continued on the right --> ch := make(chan result, 1) go func(ch chan result) { defer close(ch) response, err := tryFunc() ch <- result{response: response, err: err} }(ch) timer := time.NewTimer(cmd.timeout) defer timer.Stop() select { case res := <-ch: if res.err != nil { cmd.registerFailure(time.Since(startTime)) return nil, res.err } cmd.registerSuccess(time.Since(startTime)) return res.response, nil case <-timer.C: cmd.registerTimeout(time.Since(startTime)) return nil, &ErrRequestTimeout{commandKey: cmdKey, timeout: cmd.timeout} } }
  • 20. Panic recovery middleware // WithPanicRecoveryFunc recovers any panics that occur in the next http handler. func WithPanicRecoveryFunc(logger logging.Logger, next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { defer func() { if rec := recover(); rec != nil { logger.Error("PanicRecovered"). Bytes("stacktrace", debug.Stack()). LogErr(fmt.Errorf("%s", rec)) w.WriteHeader(http.StatusInternalServerError) } }() next(w, r) } }
  • 22. Server & start-up type shutdownFunc func() error type server struct { ctx context.Context version versionInfo shutdown shutdownFunc } func (s *server) Serve() { // (...) mux := http.NewServeMux() svr := http.Server{ ReadTimeout: time.Second * 3, ReadHeaderTimeout: time.Second * 2, WriteTimeout: time.Second * 10, Addr: ":8080", Handler: mux, } // Continued on the right --> mux.HandleFunc("/ready", s.ready) mux.HandleFunc("/live", s.live) // Put your own endpoints here err = s.setControllers(mux) if err != nil { log.Error("ServerControllerError").LogErr(err) return } _ = svr.ListenAndServe() // Blocks until server stops } func (s *server) live(w http.ResponseWriter, r *http.Request) { _, _ = io.WriteString(w, "Alive!") } func (s *server) ready(w http.ResponseWriter, r *http.Request) { //TODO: Determine readiness _, _ = io.WriteString(w, "Ready!") }
  • 24. Server with graceful shutdown func (s *server) Serve() { // (...) s.shutdown = func() error { cancelCtx, cancel := context.WithTimeout(s.ctx, time.Second * 10) defer cancel() err := svr.Shutdown(cancelCtx) return err } signalCh := make(chan os.Signal, 1) go s.listenToShutdownSignals(signalCh) _ = svr.ListenAndServe() // Blocks until server stops } func (s *server) listenToShutdownSignals( signalCh chan os.Signal) { signal.Notify(signalCh, syscall.SIGINT, // kill -SIGINT XXXX or Ctrl+c syscall.SIGTERM, // kill -SIGTERM XXXX syscall.SIGQUIT) // kill -SIGQUIT XXXX for { sig := <-signalCh switch sig { case syscall.SIGINT: fallthrough case syscall.SIGTERM: fallthrough case syscall.SIGQUIT: s.shutdown() return } } }
  • 26. Estafette & cloud credentials releases: development: stages: deploy: image: extensions/gke:stable visibility: public-whitelist container: # (...) sidecar: healthcheckpath: /live useGoogleCloudCredentials: true
  • 27. Estafette secrets production: # https://estafette.io/usage/manifest/#release-actions actions: - name: deploy-canary - name: deploy-stable - name: rollback-canary stages: deploy: image: extensions/gke:stable visibility: public container: port: 8080 env: # AES-256-encrypted value AUTH_BASIC_TOKEN: estafette.secret(6J1RtwwpvMIhMtbE.QuRJhnTgO8FcHP-cEtJy72pTzaTw2L5-1zeT0A==)
  • 28. Authorization middleware import "net/http" // WithAuthBasic performs a basic authorization check func WithAuthBasic(token string, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.Header.Get("Authorization") { case "Basic " + token: next.ServeHTTP(w, r) case "": w.Header().Add("WWW-Authenticate", "Basic") w.WriteHeader(http.StatusUnauthorized) default: w.WriteHeader(http.StatusForbidden) } }) }
  • 29. CORS middleware import "github.com/rs/cors" // WithCORS wraps the handler with CORS options. func WithCORS(handlerFunc http.HandlerFunc) http.Handler { corsOptions := cors.Options{ AllowedOrigins: []string{os.Getenv("CORS_ORIGINS")}, AllowedMethods: []string{"HEAD", "OPTIONS", "GET", "PUT", "POST"}, AllowedHeaders: []string{"Origin", "Accept", "Content-Type", "X-Requested-With", "X-CSRF-Token"}, ExposedHeaders: []string{ "Access-Control-Allow-Headers", "Access-Control-Allow-Methods", "Access-Control-Max-Age", }, MaxAge: 0, } return cors.New(corsOptions).Handler(handlerFunc) }
  • 30. ● Automate builds & deployments for your webservice ● Observe your webservice ● Make your webservice resilient ● Warm up your webservice ● Shut down your webservice gracefully ● Secure your webservice Getting “Ready” https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
  • 31. Estafette: https://estafette.io/ Zerolog: https://github.com/rs/zerolog Prometheus: https://github.com/prometheus/client_golang Grafana: https://grafana.com/ Jaeger: https://www.jaegertracing.io/ CORS: https://github.com/rs/cors Circuit breaker: https://martinfowler.com/bliki/CircuitBreaker.html References https://www.pexels.com/photo/abstract-art-circle-clockwork-414579/
  • 32. Thank you! Ralph Ligtenberg - @prutswonder stock images downloaded from Pexels.com https://www.pexels.com/photo/silhouette-of-airplanes-47044/