can improve the design of your code in subtle, but remarkable Continuous Deployment can improve the design of your code in subtle, but remarkable ways. ways. 2 2
For the purposes of this talk ... For the purposes of this talk ... Continuous Deployment is a release strategy: any mainline change that passes testing, is Continuous Deployment is a release strategy: any mainline change that passes testing, is automatically released into production. automatically released into production. 3 3
Meet our heroine, Gita the Gopher. Gita is an innovator, with a passion for technology and Gita is an innovator, with a passion for technology and melted cheese. melted cheese. 6 6
Gita builds her own machinery, it's software-driven. Gita builds her own machinery, it's software-driven.We We - - you and I you and I - - build the software. build the software. 7 7
codebase: An overview of the codebase: A monorepo. A monorepo. 2 HTTP services, jafflr+chillybin, which interact. 2 HTTP services, jafflr+chillybin, which interact. Tests, Docker things, scripts and tooling. Tests, Docker things, scripts and tooling. A CI/CD pipeline. A CI/CD pipeline. See See github.com/laher/toasties-galore github.com/laher/toasties-galore (https://github.com/laher/toasties-galore) (https://github.com/laher/toasties-galore) 8 8
Invoking Jafflr: `POST /toastie` Invoking Jafflr: `POST /toastie` Gita Jafflr HTTP Request Toastie Chillybin Postgres (DB) This simple deployment powers a 24-hour, high-throughput toasting service, with some This simple deployment powers a 24-hour, high-throughput toasting service, with some agressive expectations. agressive expectations. Gophers are demanding. Gophers are demanding. 9 9
in production. e.g. in a middleware ... Use metrics to know what's happening in production. e.g. in a middleware ... func TracingMiddleware(in http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { t := time.Now().UTC() wsr := &responseStatusRecorder{ResponseWriter: w} in.ServeHTTP(wsr, r) // the wrapped handler is still invoked here labelsMap := map[string]string{ "method": r.Method, "status": strconv.Itoa(wsr.status), "version": version, // <- we can use this to compare error rates } someTracingObserve(time.Since(t), labelsMap) // maybe honeycomb/prometheus/statsd ... }) } 17 17
Stop listening, keep handling ... e.g. Stop listening, keep handling ... e.g. Continues processing in-flight requests, while making way for a newer deployment. Continues processing in-flight requests, while making way for a newer deployment. func GracefulShutdown(server *http.Server) { osInterrupt := make(chan os.Signal) signal.Notify(osInterrupt, os.Interrupt) <-osInterrupt // subscribe to Ctrl+C or kill/SIGTERM log.Println("Received OS interrupt - stop listening but avoid interrupting existing connections...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() server.SetKeepAlivesEnabled(false) if err := server.Shutdown(ctx); err != nil { // keeps existing connections open until ctx.Done() log.Fatalf("Server shutdown error: %v", err) } } 18 18
Gophers Toasties Galore is now a highly successful operation. Gophers love love the recipes. Jafflr is busy the recipes. Jafflr is busy 24-7. 24-7. 21 21
... Some gophers aren't happy ... We're super busy, and some orders are going to the wrong gophers. We're super busy, and some orders are going to the wrong gophers. Gwenda got pineapple instead of vegemite. Gwenda got pineapple instead of vegemite. Gary received cheese instead of Sheese (soy cheese). Gary received cheese instead of Sheese (soy cheese). Not good. Not good. 22 22
to do? Decisions ... Decisions ... Gita: Gita: Track orders with customers' names. Track orders with customers' names. Us: Us: alter the Chillybin API to support richer request data. alter the Chillybin API to support richer request data. 23 23
A 3-step dance: A 3-step dance: Add new implementation. Add new implementation. Migrate comfortably. Migrate comfortably. Delete old implementation. Delete old implementation. 27 27
Add new feature implementation Serve the new approach under a different URL, /v2/pick Serve the new approach under a different URL, /v2/pick Now there are 2 versions of Now there are 2 versions of pick pick, side-by-side. , side-by-side. v2 can be tested manually, but isn't invoked by any other service yet. v2 can be tested manually, but isn't invoked by any other service yet. No changes to calling service (jafflr). Just chillybin. No changes to calling service (jafflr). Just chillybin. See See github.com/laher/toasties-galore/pull/1/files github.com/laher/toasties-galore/pull/1/files (https://github.com/laher/toasties-galore/pull/1/files) (https://github.com/laher/toasties-galore/pull/1/files) 29 29
new implementation Use Feature Flags? Use Feature Flags? func HasFeature(customer string, feature string) bool { switch feature { case "pick.v2": if customer == "gita" { return true } return false default: panic("unknown feature") } } See See github.com/laher/toasties-galore/pull/2/files github.com/laher/toasties-galore/pull/2/files (https://github.com/laher/toasties-galore/pull/2/files) (https://github.com/laher/toasties-galore/pull/2/files) HasFeature could be backed by a service, a db table, or by configuration. HasFeature could be backed by a service, a db table, or by configuration. Alternatives: Canary Deploys, Shadowing, Blue-Green deploys Alternatives: Canary Deploys, Shadowing, Blue-Green deploys 30 30
old implementation Do it soon. Do it soon. Line it up in advance. Plan for it, otherwise you'll forget. Line it up in advance. Plan for it, otherwise you'll forget. See See github.com/laher/toasties-galore/pull/3/files github.com/laher/toasties-galore/pull/3/files (https://github.com/laher/toasties-galore/pull/3/files) (https://github.com/laher/toasties-galore/pull/3/files) It's the easy part, but it's also easy to forget it, or fear it. It's the easy part, but it's also easy to forget it, or fear it. 31 31
non-event: When deployment is a non-event: you can make your releases safer. you can make your releases safer. you code remains cleaner. you code remains cleaner. When you have more viable options within easy reach, you're empowered to make better When you have more viable options within easy reach, you're empowered to make better decisions. decisions. 32 32
config files... Configurable-everything is a popular pattern Configurable-everything is a popular pattern Makes it easier to reconfigure while an on-call emergency? Makes it easier to reconfigure while an on-call emergency? Knobs and twiddles are nice. Knobs and twiddles are nice. 34 34
can simplify Use $ENV variables for DSNs and such. [see 12-factor Apps] Use $ENV variables for DSNs and such. [see 12-factor Apps] Use constants more often. Use constants more often. Deployment is fast. Just edit code/artifact Deployment is fast. Just edit code/artifact 35 35
advantages Monorepos offer some challenges and some advantages Benefit: easier to maintain CI/CD pipelines centrally. Benefit: easier to maintain CI/CD pipelines centrally. Benefit: in-house dependencies are all local. Benefit: in-house dependencies are all local. Benefit: testing component combinations is easier. Benefit: testing component combinations is easier. Challenge: identifying what to build, test, deploy on each commit. Challenge: identifying what to build, test, deploy on each commit. Challenge: speed. Challenge: speed. Challenge: 3p deps: _See toasties-galore for top-level go-mod w Docker_ Challenge: 3p deps: _See toasties-galore for top-level go-mod w Docker_ 36 36
Q: Do we really redeploy everything on every change? Q: Do we really redeploy everything on every change? A: Depends on language and tooling available. A: Depends on language and tooling available. 37 37
monorepo service - Go See See publish.sh publish.sh (./scripts/publish.sh) (./scripts/publish.sh) and and deploy.sh deploy.sh (./scripts/deploy.sh) (./scripts/deploy.sh) #!/bin/bash echo "BUILD/PUBLISH: begin" echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin function docker_tag_exists() { echo "CHECK: does $1 docker tag exist for commit $2 ?" curl --silent -f -lSL https://index.docker.io/v1/repositories/$1/tags/$2 > /dev/null } for SVC in jafflr chillybin do export SVC_COMMIT=$(./scripts/last_commit.sh "$SVC") if docker_tag_exists "$DOCKER_USERNAME/$SVC" "$SVC_COMMIT"; then echo "SKIP: Docker tag already exists for $DOCKER_USERNAME/$SVC: $SVC_COMMIT" else echo "PUBLISH: publishing image for $DOCKER_USERNAME/$SVC: $SVC_COMMIT" docker build -t $SVC -f ./$SVC/Dockerfile . # rebuild just in case docker tag $SVC $DOCKER_USERNAME/$SVC docker tag $SVC $DOCKER_USERNAME/$SVC:$SVC_COMMIT docker push $DOCKER_USERNAME/$SVC:$SVC_COMMIT fi done echo "BUILD/PUBLISH: done" 38 38
Calculate changes affecting a service [in a monorepo] Calculate changes affecting a service [in a monorepo] #!/bin/sh if [ -z $1 ]; then echo '"$1" should be service-name' >&2 exit 1 fi export PKG=$1 cd $PKG # Use go-list to dependencies of a package IMPORTS=$(go list -deps|grep 'github.com/laher/toasties-galore'|sed 's,github.com/laher/toasties-galore/ cd .. # Use git-rev-list to find last commit affecting dirs LAST_COMMIT=$(git rev-list -1 HEAD -- $IMPORTS $PKG ./go.mod ./go.sum) echo $LAST_COMMIT 39 39
challenging Migrations will always be challenging Altering big tables - time-consuming, performance concerns. Altering big tables - time-consuming, performance concerns. Rollbacks are awkward. Rollbacks are awkward. Where possible: Where possible: Use transactions (Postgres has transactional DDL) to Use transactions (Postgres has transactional DDL) to test test them in advance. them in advance. Make them idempotent (`if not exists`) to Make them idempotent (`if not exists`) to run run them in advance. them in advance. Split them up - 'create table' is cheap. Isolate the tricky stuff. Split them up - 'create table' is cheap. Isolate the tricky stuff. 41 41
a slackbot, @overlord, which offers a number of CD-related utilities. e.g. At Vend we have a slackbot, @overlord, which offers a number of CD-related utilities. e.g. canary deploys canary deploys rollbacks rollbacks feature flagging feature flagging service status service status 44 44
they're all different. I've tried heaps. They all work, and they're all different. I've tried heaps. My examples are only chosen to communicate some ideas. My examples are only chosen to communicate some ideas. Just choose one. Just choose one. 45 45
my code? More than anything else, CD affects More than anything else, CD affects how how you you change change your code. your code. CD forces you to deal with Important Things, up front. Most of these have their own CD forces you to deal with Important Things, up front. Most of these have their own payoff. payoff. CD encourages good practices. CD encourages good practices. CD enables safe and easy changes. CD enables safe and easy changes. Impact: Impact: Simpler: fewer contraptions in the code. Simpler: fewer contraptions in the code. Tidyier: fewer cobwebby corners. Tidyier: fewer cobwebby corners. More dynamic: change is simpler -> more, better options. More dynamic: change is simpler -> more, better options. 47 47
early or add it incrementally. It can be hard to retrofit CD. Start early or add it incrementally. For research and business speak, see For research and business speak, see continuousdelivery.com continuousdelivery.com (https://continuousdelivery.com) (https://continuousdelivery.com) . . 48 48
See Toasties Galore on See Toasties Galore on github github (https://github.com/laher/toasties-galore) (https://github.com/laher/toasties-galore) or or travis travis (https://travis-ci.org/laher/toasties-galore) (https://travis-ci.org/laher/toasties-galore) to try this stuff to try this stuff out - maybe it'll help you. out - maybe it'll help you. 49 49