Upgrade to Pro — share decks privately, control downloads, hide ads and more …

The Road to Continuous Deployment: a case study (IPC Berlin 2017)

The Road to Continuous Deployment: a case study (IPC Berlin 2017)

It’s a situation many of us are familiar with: a large legacy, monolithic application, limited or no tests, slow and manual release process, low velocity, no confidence… A lot of refactoring is required, but management keeps pushing for new features. How to proceed? Using examples and lessons learned from a real-world case, I’ll show you how to replace a legacy application with a modern service-oriented architecture and build a continuous integration and deployment pipeline to deliver value from the first sprint. On the way, we’ll take a look at the process, automated testing, monitoring, master/trunk based development and various tips and best practices.

Michiel Rook

May 31, 2017
Tweet

More Decks by Michiel Rook

Other Decks in Programming

Transcript

  1. FRONTEND MYSQL DB BACKEND LOAD BALANCERS / VARNISH JOB SITE

    JOB SITE JOB SITE FRONTEND FRONTEND FRONTEND BACKEND BACKEND BACKEND MEMCACHE FTP EXT. SERVICES SOLR @michieltcs
  2. APPROACH ▸ API first ▸ Services per domain object (job,

    jobseeker, ...) ▸ Migrate individual pages @michieltcs
  3. CD?

  4. UNIT TESTS INTEGRATION
 TESTS ACCEPTANCE UI TESTS public function testJobCannotBeFound()

    {
 $jobRepository = $this->prophesize(JobRepository::class);
 $jobRepository->getById(EXPECTED_JOB_ID)
 ->shouldBeCalled()
 ->willReturn(false);
 
 $jobService = new JobService($jobRepository->reveal());
 
 $this->assertFalse($jobService->getById(EXPECTED_JOB_ID));
 } DEFENSE IN DEPTH @michieltcs
  5. UNIT TESTS INTEGRATION
 TESTS ACCEPTANCE
 TESTS UI TESTS public function

    testFindJob() {
 $expectedJob = $this->loadFixture('active_job.yml');
 $actualJob = $this->repository->getById($expectedJob->getId());
 
 self::assertInstanceOf(Job::class, $actualJob);
 self::assertEquals($expectedJob->getId(), $actualJob->getId());
 } DEFENSE IN DEPTH @michieltcs
  6. DEFENSE IN DEPTH UNIT TESTS INTEGRATION
 TESTS ACCEPTANCE
 TESTS UI

    TESTS Scenario: Link to related job
 Given a job exists
 And there are related jobs available
 When that job is viewed
 Then a list of related jobs is shown
 And each related job links to the detail page of the related job @michieltcs
  7. UNIT TESTS INTEGRATION TESTS ACCEPTANCE TESTS UI TESTS SMOKE
 TESTS

    Cost Speed Exploratory
 testing Monitoring @michieltcs
  8. PIPELINE AS CODE node {
 stage('Run tests') {
 sh "phpunit"


    sh "behat"
 }
 
 stage('Build docker image') {
 sh "docker build -t jobservice:${env.BUILD_NUMBER} ."
 sh "docker push jobservice:${env.BUILD_NUMBER}"
 }
 
 stage('Deploy staging') {
 sh "ansible-playbook -e BUILD=${env.BUILD_NUMBER}
 -i staging deploy.yml"
 }
 
 stage('Deploy production') {
 sh "ansible-playbook -e BUILD=${env.BUILD_NUMBER}
 -i prod deploy.yml"
 }
 } @michieltcs
  9. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER @michieltcs
  10. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER docker pull @michieltcs
  11. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER docker run @michieltcs
  12. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER wait_for: port=8080 delay=5 timeout=15 @michieltcs
  13. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER uri:
 url: http://localhost:8080/health
 status_code: 200
 timeout: 30 @michieltcs
  14. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER template: src=haproxy.cfg.j2
 dest=/etc/haproxy/haproxy.cfg service: name=haproxy state=reloaded @michieltcs
  15. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER template: src=haproxy.cfg.j2
 dest=/etc/haproxy/haproxy.cfg service: name=haproxy state=reloaded @michieltcs
  16. PULL IMAGE START NEW CONTAINER WAIT FOR PORT SMOKE TESTS

    / HEALTH CHECKS ADD NEW CONTAINER TO LB REMOVE OLD CONTAINER FROM LB STOP OLD CONTAINER docker stop docker rm @michieltcs
  17. Build per service
 < 10 min. 1 50+ deploys per

    day Reduced number of issues 2 3 @michieltcs
  18. Build per service
 < 10 min. 1 50+ deploys per

    day Reduced number of issues Improved page load times 2 3 4 @michieltcs