Chapter 21 of 27 · Part 9: Deployment and scaling

Continuous integration and delivery pipelines

From commit to production: continuous integration, build pipelines, artifacts and how code maps to services.

key idea / coordinatorservice / good pathin progress / watch outproblem / failureneutral

Continuous integration, honestly

CI keeps everyone in sync: a CI server notices a commit, checks the code out, compiles it, runs the tests and, if all is well, builds an artifact for later stages. Because the build is scripted and versioned, you can rebuild any artifact and trace a deployed one back to its code.

What a CI run doesCommitto mainlineCI servercheckout, buildVerifystatic, testsArtifactstored onceFast feedback on every change; infrastructure code can live in the same repository.

Owning a CI tool is not the same as doing CI. Jez Humble's three questions are a quick test:

Are you really doing CI?Daily to mainline?merge at least once a dayTests that check?not just "it compiles"Broken build?fix it before anything elseIf any answer is no, you have a CI tool, not CI. A red build left for days grows costly to fix.

From Building Microservices, ch. 7, "A Brief Introduction to Continuous Integration" and "Are You Really Doing CI?".

Branching: integrate early, integrate often

A long-lived feature branch delays integration, so the merge at the end is bigger and riskier. Newman prefers trunk-based development: everyone commits to one trunk and hides unfinished work behind feature flags (which also help progressive delivery, lesson 19).

Long-lived branches versus trunkFeature branchTrunk-basedWork in isolation for weeksmerge later: large, painfulmergemergemergemergemergemergeSmall, frequent merges keep the trunk always integrated.
Evidence: the 2016 State of DevOps report linked high performance to branches living under a day, fewer than three active branches, and daily merges to trunk. Later reports found small patches and early merging help open source projects too (Building Microservices, ch. 7).

Build pipelines and continuous delivery

Split a build into stages: fast tests first, slow tests after. If the fast stage fails there is no point running the slow one. Each commit becomes a release candidate that moves through the pipeline, so you can always see how far a version has got.

One pipeline, three buildsFast testsbuild-120 failedSlow testsnot runPerformancenot runProductionnot runFast testsbuild-121 passedSlow testspassedPerformancefailed hereProductionnot reachedFast testsbuild-122 passedSlow testspassedPerformancepassedProductiondeployedSame pipeline, three builds: one version of the Catalog service reaches production.
TermMeaning
Continuous deliveryEvery check-in is a release candidate; you know how production-ready each one is. Deploying stays a decision.
Continuous deploymentEvery change that passes automated checks is deployed with no human step. Builds on continuous delivery.
Delivery versus deploymentContinuous deliveryContinuous deploymentCommitPipeline passesHuman decisionrelease when readyProductionCommitPipeline passesAutomaticno manual stepProductionDelivery makes every build releasable; deployment also removes the gate.

Pick a tool that models pipelines directly, including manual stages such as user acceptance testing, instead of bending a plain CI tool into one. Human steps in the path to production are usually the bottleneck to automate.

From Building Microservices, ch. 7, "Build Pipelines and Continuous Delivery" and "Tooling".

CI/CD pipelines and GitHub Actions

Are you really doing CI?Three questionsDo you commit to mainline at least daily?Do tests validate every change?Is a broken build the top priority?From Building Microservices, ch. 7. Short-lived branches and small changes keep merging easy.

A build pipeline splits the build into stages: fast small tests first, then slower ones, then deployment. A good CD tool models the whole path to production, even a manual acceptance step. Trading fast feedback against production-like checks is a choice you revisit.

A continuous deployment workflowPushto mainTestnpm testBuildDocker imagePublishto registryDeploykubectl applyGitHub Actions starts it on each push. Shell scripts do the work.

From Bootstrapping Microservices, ch. 8. Values such as the registry address and version come from environment variables, and secrets live in GitHub secrets, not in the repo. The tool envsubst fills them into the Kubernetes file before it is piped to kubectl. Keep deployment code simple, since failures in the cloud are harder to debug. Reproduce an error locally by copying the workflow environment.

Environments and artifacts

Early stages want speed; late stages want realism. Your laptop is fastest and least like production; a full production copy is realistic but slow and costly. Finding the balance is an ongoing choice, and it is one reason teams also test in production.

Fast feedback versus production-likeLaptopfast, unlike prodCI stagefast testsShared test envslower testsProd-like envperformancefaster feedbackmore production-likeEach step trades speed for confidence.

Two rules about artifacts: build it once, and deploy exactly what you tested. Rebuilding for production means you are not shipping the thing you verified. Store the artifact in a registry (Artifactory, Nexus or a container registry) and keep environment-specific settings outside it.

Build once, deploy everywhereBuild oncebuild-123Artifact storeregistrySlow testsPerformanceProductionconfig: DEBUG hereconfig: INFO hereconfig: INFO hereSame bytes everywhere; only external configuration changes per environment.

From Building Microservices, ch. 7, "Trade-Offs and Environments" and "Artifact Creation".

Promoting one artifact through environments

A microservice runs in several environments on its way to users. The same service may have one instance on a laptop and many in production. From Building Microservices, ch. 7 and 8.

One artifact travels through environmentsLaptopfast, unrealisticCIfast tests firstPre-productionslow tests, manualProductionreal usersBuild the artifact once and reuse it everywhere. Keep config for each environment outside it.

Environments trade feedback speed against realism: a laptop is fastest but least like production, a full copy is realistic but slow and costly. Some environments hold only a few services, with fakes for the rest. This is partly why teams also test in production.

Image versus containerImageImmutable snapshotTagged, e.g. video-streaming:1Kept in a registryContainerA running instanceFilesystem can changeMany from one imageImages cannot change, so tests on one stay valid. Bump the tag per deployment.

Mapping code and builds to services

How you lay out source code decides which builds a commit triggers. Three options (a fuller comparison with Git workflows is in One repository or many? below):

Three ways to map repos to buildsOne giant repo, one buildOne repoevery serviceOne buildall servicesRepo per serviceUser repoBuildCatalog repoBuildMonorepoOne repouser/catalog/build Abuild BFolders map to separate builds; shared folders can trigger many builds.
Giant repo + one buildOne repo per serviceMonorepo
GoodSimplest startClear commit-to-build map; per-repo ownershipAtomic commits, fine-grained code reuse, visibility
BadOne red build blocks everyone; encourages deploying togetherCross-repo changes are awkward; shared code needs library versionsNeeds tooling (e.g. Bazel) at scale; blurs ownership
NewmanRarely used beyond day onePreferred as you scaleFine for 10 to 20 developers or very large, tool-rich firms
Atomic commit is not atomic deploy. Changing two services in one commit does not mean they roll out together. Wanting that breaks independent deployability.

From Building Microservices, ch. 7, "Mapping Source Code and Builds to Microservices".

One repository or many?

Mapping code to buildsOne repo, one buildAny change rebuilds allOne break blocks allRare except early onRepo per serviceChange maps to one buildClear ownershipCross-repo changes hurtMonorepoAtomic commitsNeeds tooling at scaleHard to leave laterA monorepo should still map each folder to its own build, or you deploy everything together.

Newman: giants such as Google and Microsoft make monorepos work by investing heavily, and small groups of 10 to 20 developers can too. The middle suffers. He prefers one repository per microservice at scale, because the benefits of reuse and atomic commits do not outweigh cross-service changes and blurred ownership. From Building Microservices, ch. 7.

Bootstrapping Microservices keeps all FlixTube services in one monorepo, with a separate CD workflow file for each microservice.

Recap

Quick check

Why should the artifact tested in the slow-test stage be the one deployed to production?
Show answer

Otherwise you ship something you never verified. Build once, store it in a registry, and vary only external configuration.

Why build the artifact once and promote it, instead of rebuilding for each environment?
Show answer

The artifact you tested is then exactly the one that reaches production. Images are immutable, so earlier tests and security checks stay valid, and only the environment-specific configuration changes.