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

Taming AEM deployments

Taming AEM deployments

Continuous delivery model sounds great to almost everyone, but is not that easy to implement when real life comes into play. On the one hand, AEM architecture feels like a good fit for this strategy (everything is content after all, right?), but on the other hand we all know how challenging even individual deployment can get. Content management system itself is often just a fraction of your concern - you have to build it first, test it, stitch all the elements up and then figure out how to deliver your code in a repeatable and controlled way to production.

Throughout the last couple of years we’ve been trying really hard at Cognifide to make continuous release and delivery possible for AEM. It was a bumpy and twisty road, but we finally made it. During my talk I’d like to show you how we build AEM platforms these days using open source tools, including:

- Terraform to define and manage infrastructure as code
- Chef to describe deployments in an approachable way, which enforces consistency & repeatability across environments
- Consul that helps us discover services around and mitigate failures
- Jenkins & GoCD to orchestrate delivery model

Avatar for Jakub Wądołowski

Jakub Wądołowski

September 26, 2017
Tweet

More Decks by Jakub Wądołowski

Other Decks in Technology

Transcript

  1. APACHE SLING & FRIENDS TECH MEETUP BERLIN, 25-27 SEPTEMBER 2017

    Taming AEM deployments Jakub Wądołowski, Cognifide
  2. The road to efficient AEM delivery 2 https://flic.kr/p/z8g25R ▪ Starts

    on your computer ▪ Ends in production ▪ Includes everything in between
  3. Local environment 4 https://flic.kr/p/b93YGx ▪ Everyone runs the same ▪

    It contains the entire technology stack ▪ Start over any time you need it
  4. What’s in the box? 6 https://flic.kr/p/cMBbTu ▪ AEM Author &

    AEM Publish ▪ Preinstalled packages ▪ Dispatcher ▪ Solr Master & Slave ▪ knot.x
  5. Vagrant in practice (1/3) 7 # Install prerequisites: # *

    Vagrant # * VirtualBox # * ChefDK $ git clone [email protected]:xyz/vagrant.git $ cd project-vagrant $ rake init $ rake up
  6. Vagrant in practice (2/3) 8 ▪ http://localhost:4502 (AEM Author) ▪

    http://localhost:4503 (AEM Publish) ▪ https://example.vagrant (Dispatcher) ▪ http://localhost:8983 (Solr) ▪ etc
  7. Vagrant in practice (3/3) 9 $ rake provision:publish ... Chef

    Client finished, 8/196 resources updated in 02 minutes 02 seconds $ rake provision:httpd ... Chef Client finished, 4/41 resources updated in 19 seconds
  8. Use case (1/4) 14 ▪ 17 Chef cookbook repos ▪

    2 CI related repos ▪ 3 config repos ▪ 4 app repos ▪ 2 infrastructure repos https://flic.kr/p/bF3Nwm
  9. Use case (2/4) 15 ▪ Each repository has its own

    lifecycle ▪ Simple Git flow https://flic.kr/p/peZfvA
  10. Use case (3/4) 16 $ tree -L 1 . ├──

    aem ├── domain ├── knotx-autocomplete ├── knotx-download ├── knotx-filters ├── knotx-index ├── knotx-search ├── pom.xml └── solr 8 directories, 1 file https://flic.kr/p/fnPQuz
  11. Use case (3/4) 17 $ tree -L 1 . ├──

    aem ├── domain ├── knotx-autocomplete ├── knotx-download ├── knotx-filters ├── knotx-index ├── knotx-search ├── pom.xml └── solr 8 directories, 1 file https://flic.kr/p/fnPQuz
  12. Use case (3/4) 18 $ tree -L 1 . ├──

    aem ├── domain ├── knotx-autocomplete ├── knotx-download ├── knotx-filters ├── knotx-index ├── knotx-search ├── pom.xml └── solr 8 directories, 1 file https://flic.kr/p/fnPQuz
  13. Use case (3/4) 19 $ tree -L 1 . ├──

    aem ├── domain ├── knotx-autocomplete ├── knotx-download ├── knotx-filters ├── knotx-index ├── knotx-search ├── solr └── pom.xml 8 directories, 1 file https://flic.kr/p/fnPQuz
  14. Use case (3/4) 20 $ tree -L 1 . ├──

    aem ├── domain ├── knotx-autocomplete ├── knotx-download ├── knotx-filters ├── knotx-index ├── knotx-search ├── solr └── pom.xml 8 directories, 1 file https://flic.kr/p/fnPQuz
  15. Use case (4/4) 21 ▪ Compatibility matrix is no longer

    needed ▪ End-to-end testing ▪ One release contains multiple artifacts
  16. Pipelines 22 https://flic.kr/p/qG26gA ▪ Variety of choices ▪ Jenkins ▪

    GoCD ▪ Bamboo ▪ etc ▪ Pipeline as code (Jenkinsfile)
  17. 23 node('master') { stage('Checkout') { dir('app') { git url: 'git://example.com/aem-project.git',

    branch: 'master' } dir('tests') { git url: 'git://example.com/tests.git', branch: 'master' } } stage('Build') { node('aem') { dir('app/aem') { sh 'mvn clean install' } } } stage('Test') { dir('tests') { sh './gradlew clean contractTests' } } }
  18. 24 node('master') { stage('Checkout') { dir('app') { git url: 'git://example.com/aem-project.git',

    branch: 'master' } dir('tests') { git url: 'git://example.com/tests.git', branch: 'master' } } stage('Build') { node('aem') { dir('app/aem') { sh 'mvn clean install' } } } stage('Test') { dir('tests') { sh './gradlew clean contractTests' } } }
  19. 25 node('master') { stage('Checkout') { dir('app') { git url: 'git://example.com/aem-project.git',

    branch: 'master' } dir('tests') { git url: 'git://example.com/tests.git', branch: 'master' } } stage('Build') { node('aem') { dir('app/aem') { sh 'mvn clean install' } } } stage('Test') { dir('tests') { sh './gradlew clean contractTests' } } }
  20. 26 node('master') { stage('Checkout') { dir('app') { git url: 'git://example.com/aem-project.git',

    branch: 'master' } dir('tests') { git url: 'git://example.com/tests.git', branch: 'master' } } stage('Build') { node('aem') { dir('app/aem') { sh 'mvn clean install' } } } stage('Test') { dir('tests') { sh './gradlew clean contractTests' } } }
  21. 28

  22. Pipeline improvements 40 ▪ Dynamically defined stages ▪ Local &

    global configuration files ▪ Custom shared library https://flic.kr/p/juzu1W
  23. 41 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  24. 42 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  25. 43 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  26. 44 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  27. 45 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  28. 46 #!groovy @Library('cognifide-library@master') import com.cognifide.jenkinslib.Pipeline def pipeline = new Pipeline(this)

    node('master') { withMaven(maven: 'Maven 3') { stage('Quality Gate: Sonarqube') { sonarqube pipeline.CFG.sonarqube } stage('Build') { // ... } stage('Clear dispatcher cache') { knifeExec query: "chef_environment:${pipeline.CFG.chef.environmentName} AND tags:dispatcher" } } }
  29. 47 notifications: enabled: true recipients: - '[email protected]' deploy: profiles: aem-author:

    'dev-author' aem-publish1: 'dev-publish1' aem-publish2: 'dev-publish2' tests: bobcat: profiles: - 'dev' - 'grid' mvnParams: '-Dfork.count=4' chef: environmentName: 'dev'
  30. Testing 48 ▪ Unit tests ▪ Bobcat ▪ github.com/Cognifide/bobcat ▪

    Zalenium & Docker ▪ github.com/zalando/zalenium ▪ AET ▪ github.com/Cognifide/aet https://flic.kr/p/juzgT9
  31. Service discovery 53 ▪ Interconnectivity ▪ Hardcoded IPs are not

    an option ▪ Adapts to changes by itself https://flic.kr/p/qr4XXW
  32. Chef 59 ▪ Configuration management ▪ Each service has its

    own cookbook ▪ Deployment steps written in DSL https://flic.kr/p/W25Lmv
  33. Chef example 60 ▪ Install a package ▪ Restart AEM

    ▪ Remove default flush agent https://flic.kr/p/9qsZDC
  34. 61 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy end
  35. 62 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy end
  36. 63 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy end
  37. 64 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy end
  38. 65 cq_package 'Author: Core app' do username node['cq']['author']['credentials']['login'] password node['cq']['author']['credentials']['password']

    instance "http://localhost:#{node['cq']['author']['port']}" source "https://artifacts.example.com/xyz/#{ver_dir}/"\ "xyz-#{ver}-full.zip" http_user node['xyz-webapp']['nexus']['user'] http_pass node['xyz-webapp']['nexus']['password'] action :deploy end
  39. 66 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy notifies :restart, 'service[cq62-author]', :immediately end service 'cq62-author' do action :nothing end
  40. 67 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy notifies :restart, 'service[cq62-author]', :immediately end service 'cq62-author' do action :nothing end
  41. 68 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy notifies :restart, 'service[cq62-author]', :immediately end service 'cq62-author' do action :nothing end
  42. 69 cq_package 'Author: Core app' do username 'admin' password 'admin'

    instance 'http://localhost:4502' source 'https://artifacts.example.com/xyz/1.0/xyz-1.0.3-full.zip' http_user 'basic_auth_user' http_pass 'basic_auth_password' action :deploy notifies :restart, 'service[cq62-author]', :immediately end service 'cq62-author' do action :nothing end
  43. 74 $ chef-client # ... Chef Client finished, 39/652 resources

    updated in 09 minutes 46 seconds $ chef-client # ... Chef Client finished, 0/652 resources updated in 01 minutes 58 seconds
  44. 75 $ chef-client # ... Chef Client finished, 39/652 resources

    updated in 09 minutes 46 seconds $ chef-client # ... Chef Client finished, 0/652 resources updated in 01 minutes 58 seconds
  45. 76 $ grep cq_package recipes/author*.rb | wc -l 70 $

    grep cq_jcr recipes/author*.rb | wc -l 20 $ grep cq_package recipes/publish*.rb | wc -l 67 $ grep cq_jcr recipes/publish*.rb | wc -l 11
  46. What’s currently available? 77 ▪ cq_package ▪ cq_osgi_config ▪ cq_osgi_bundle

    ▪ cq_osgi_component ▪ cq_user ▪ cq_jcr ▪ groovy_script ▪ apm_script ▪ … https://flic.kr/p/qhRjAF
  47. 81

  48. 82 $ knife ssh \ 'chef_environment:xyz-prod AND tags:aem AND tags:author'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/maintenance.lock'
  49. 83 $ knife ssh \ 'chef_environment:xyz-prod AND tags:aem AND tags:author'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/maintenance.lock'
  50. 84 $ knife ssh \ 'chef_environment:xyz-prod AND tags:aem AND tags:author'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/maintenance.lock'
  51. 85

  52. 86 $ knife ssh \ 'chef_environment:xyz-prod AND tags:dispatcher AND tags:us-east-1a'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/deployment.lock'
  53. 87 $ knife ssh \ 'chef_environment:xyz-prod AND tags:dispatcher AND tags:us-east-1a'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/deployment.lock'
  54. 88 $ knife ssh \ 'chef_environment:xyz-prod AND tags:dispatcher AND tags:us-east-1a'

    \ 'sudo -u apache /bin/touch /path/to/maintenance/deployment.lock'
  55. 89

  56. 90 $ knife ssh \ 'chef_environment:xyz-prod AND ( (tags:dispatcher AND

    tags:us-east-1a) OR (tags:aem AND tags:publish AND tags:us-east-1a) )' \ 'sudo /usr/bin/chef-client'
  57. 91 $ knife ssh \ 'chef_environment:xyz-prod AND ( (tags:dispatcher AND

    tags:us-east-1a) OR (tags:aem AND tags:publish AND tags:us-east-1a) )' \ 'sudo /usr/bin/chef-client'
  58. 92 $ knife ssh \ 'chef_environment:xyz-prod AND ( (tags:dispatcher AND

    tags:us-east-1a) OR (tags:aem AND tags:publish AND tags:us-east-1a) )' \ 'sudo /usr/bin/chef-client'
  59. 93 $ knife ssh \ 'chef_environment:xyz-prod AND tags:dispatcher AND tags:us-east-1a'

    \ 'sudo -u apache /usr/bin/rm -f /path/to/maintenance/deployment.lock'
  60. 94

  61. 95

  62. 96

  63. 97

  64. Lessons learned 98 ▪ Fail fast ▪ Enforce correct exit

    codes ▪ How to resume failed pipeline? https://flic.kr/p/jzAyNF