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

Elephants in the Clouds: Mastering PHP on Heroku

David Zuelke
November 06, 2014

Elephants in the Clouds: Mastering PHP on Heroku

Workshop presented at True North PHP conference 2014 in Toronto, Canada.

David Zuelke

November 06, 2014
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. ELEPHANTS IN THE CLOUDS
    Mastering PHP on Heroku

    View full-size slide

  2. David Zuelke

    View full-size slide

  3. 5 Billion

    requests/day
    4+ Million

    apps created
    150+

    add-on partners

    View full-size slide

  4. “The Twelve-Factor App”

    !
    !
    !

    View full-size slide

  5. “The Twelve-Factor App”

    is

    a manifesto,

    a methodology,

    a condensed collection of experiences.

    View full-size slide

  6. Its goals are

    scalability,

    maintainability,

    portability.

    View full-size slide

  7. 12factor.net

    View full-size slide

  8. 4/12 MAIN FACTORS,

    CODE ACCORDINGLY
    I. Codebase

    From one codebase, perform many deploys to staging, prod, ...

    II. Dependencies

    Your application explicitly declares userland and platform deps.

    III. Configuration

    Read from $_ENV: API keys, database credentials, SMTP hosts, ...

    XI. Logging

    file_put_contents("php://stderr",  "Yay  log");

    View full-size slide

  9. II. DEPENDENCIES
    Applications have explicitly declared dependencies.

    View full-size slide

  10. II. DEPENDENCIES
    Applications have explicitly declared dependencies.
    $  cat  composer.json  
    {  
       "require":  {  
           "php":  ">=5.3.3",  
           "ext-­‐mcrypt":  "*",  
           "symfony/symfony":  "~2.4.6",  
           "twig/extensions":  "~1.0",  
           "symfony/monolog-­‐bundle":  "~2.4"  
       }  
    }
    $  cat  package.json  
    {  
       "dependencies":  {  
           "express":  "~4.9.x",  
           "cool-­‐ascii-­‐faces":  "~1.3.x"  
       },  
       "engines":  {  
           "node":  "0.10.x"  
       }  
    }

    View full-size slide

  11. III. CONFIGURATION
    Store config in the environment.

    View full-size slide

  12. III. CONFIGURATION
    Store config in the environment.
    Assumption:

    same code but different configuration per deployment target

    View full-size slide

  13. III. CONFIGURATION
    Store config in the environment.
    $transport  =  Swift_SmtpTransport::newInstance(

           getenv('EMAIL_HOST'),  getenv('EMAIL_PORT')?:25

    )

           -­‐>setUsername(getenv('EMAIL_USERNAME'))

           -­‐>setPassword(getenv('EMAIL_PASSWORD'))

    ;
    Assumption:

    same code but different configuration per deployment target
    $  heroku  config:set  EMAIL_HOST=blah.com  EMAIL_PORT=827  
    EMAIL_USERNAME=joecool  EMAIL_PASSWORD=itwasadarkandstormynight

    View full-size slide

  14. TIME FOR A DEPLOY!
    It's as easy as pie.

    View full-size slide

  15. $  heroku  create  your-­‐php-­‐app  
    Creating  your-­‐php-­‐app...  done,  stack  is  cedar-­‐14  
    http://example.herokuapp.com/  |  [email protected]:example.git  
    $  git  push  heroku  master  
    -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected  
    -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...  
                 -­‐  PHP  5.5.16  
                 -­‐  Apache  2.4.10  
                 -­‐  Nginx  1.6.0  
    -­‐-­‐-­‐-­‐-­‐>  Installing  PHP  extensions:  
                 -­‐  opcache  (automatic;  bundled)  
                 -­‐  memcached  (composer.json;  downloaded)  
                 -­‐  intl  (composer.json;  bundled)  
    -­‐-­‐-­‐-­‐-­‐>  Installing  dependencies...  
                 Composer  version  05d991  2014-­‐10-­‐28  12:36:19  
                 Loading  composer  repositories  with  package  information  
                 Installing  dependencies  from  lock  file  
                     -­‐  Installing  monolog/monolog  (1.9.1)  
                         Loading  from  cache            
                 Generating  optimized  autoload  files  
    -­‐-­‐-­‐-­‐-­‐>  Discovering  process  types  
                 Procfile  declares  types  -­‐>  web  
    -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  57.4MB  
    -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3  
                 http://your-­‐php-­‐app.herokuapp.com/  deployed  to  Heroku

    View full-size slide

  16. what happened there?

    View full-size slide

  17. V. BUILD, RELEASE, RUN
    A build step vendors dependencies, prepares assets, etc.

    A release step creates a package from build and config.

    A runtime step executes, without special knowledge.

    View full-size slide

  18. V. BUILD, RELEASE, RUN
    A build step vendors dependencies, prepares assets, etc.

    A release step creates a package from build and config.

    A runtime step executes, without special knowledge.

    View full-size slide

  19. LET'S TRY IT!

    View full-size slide

  20. PREREQUISITES
    • Git installed on your system

    • A Heroku account, created via http://signup.heroku.com/

    • The Heroku toolbelt installed from http://toolbelt.heroku.com/

    • $  heroku  login

    View full-size slide

  21. http://start.heroku.com/php

    &

    http://devcenter.heroku.com/categories/php

    View full-size slide

  22. $  mkdir  tnphp-­‐heroku  
    $  cd  tnphp-­‐heroku  
    $  git  init  
    Initialized  empty  Git  repository  in  tnphp-­‐heroku/.git/  
    $  curl  -­‐sS  https://getcomposer.org/installer  |  php  
    $  php  composer.phar  require  silex/silex  monolog/monolog  
    ...  
    ./composer.json  has  been  created  
    Loading  composer  repositories  with  package  information  
    Updating  dependencies  (including  require-­‐dev)  
    ...  
    $  echo  "/vendor/"  >  .gitignore  
    $  echo  "hi"  >  index.php  
    $  git  add  .gitignore  composer.*  index.php  
    $  git  commit  -­‐m  "first  rough  version"  
    [master  (root-­‐commit)  82b60dd]  first  rough  version  
     5  files  changed,  498  insertions(+)  
    ...  
    $  heroku  create  tnphp-­‐$(whoami)  
    Creating  tnphp-­‐dzuelke...  done,  stack  is  cedar-­‐14  
    https://tnphp-­‐dzuelke.herokuapp.com/  |  [email protected]:tnphp-­‐
    dzuelke.git  
    Git  remote  heroku  added  
    $  git  push  heroku  master

    View full-size slide

  23. X. DEV/PROD PARITY
    Keep dev, stage and prod envs as similar as possible.

    View full-size slide

  24. X. DEV/PROD PARITY
    Keep dev, stage and prod envs as similar as possible.
    SQLite ≠ MySQL

    Apache ≠ Nginx

    File based sessions ≠ Redis based sessions

    View full-size slide

  25. X. DEV/PROD PARITY
    Keep dev, stage and prod envs as similar as possible.
    SQLite ≠ MySQL

    Apache ≠ Nginx

    File based sessions ≠ Redis based sessions

    View full-size slide

  26. X. DEV/PROD PARITY
    Keep dev, stage and prod envs as similar as possible.
    SQLite ≠ MySQL

    Apache ≠ Nginx

    File based sessions ≠ Redis based sessions
    If apt-­‐get or brew don't get the job done on your box:

    Vagrant is always your friend!

    View full-size slide

  27. VI. PROCESSES

    View full-size slide

  28. heroku-­‐python-­‐app  $  cat  Procfile  
    worker:  python  worker.py  
    web:  gunicorn  hello:app

    View full-size slide

  29. heroku-­‐node-­‐app  $  cat  Procfile  
    worker:  node  worker.js  
    web:  node  index.js

    View full-size slide

  30. heroku-­‐ruby-­‐app  $  cat  Procfile  
    worker:  env  TERM_CHILD=1  bundle  exec  rake  resque:work  
    web:  bundle  exec  unicorn  -­‐p  $PORT  -­‐c  ./config/unicorn.rb

    View full-size slide

  31. heroku-­‐java-­‐app  $  cat  Procfile  
    worker:  sh  worker/target/bin/worker  
    web:  java  -­‐jar  target/dependency/jetty-­‐runner.jar  -­‐-­‐port  $PORT  
    target/*.war

    View full-size slide

  32. heroku-­‐php-­‐app  $  cat  Procfile  
    worker:  php  background.php  
    web:  php  -­‐S  0.0.0.0:$PORT

    View full-size slide

  33. PHP needs a dedicated web server

    View full-size slide

  34. heroku-­‐php-­‐app  $  cat  Procfile  
    worker:  php  background.php  
    web:  vendor/bin/heroku-­‐php-­‐apache2  #  or  heroku-­‐php-­‐nginx
    automatically injected into the app during build

    View full-size slide

  35. RUNNING PHP APPS LOCALLY
    heroku-­‐php  $  composer  require  -­‐-­‐dev  heroku/heroku-­‐buildpack-­‐php  
    ./composer.json  has  been  updated  
    Loading  composer  repositories  with  package  information  
    Updating  dependencies  (including  require-­‐dev)  
       -­‐  Installing  heroku/heroku-­‐buildpack-­‐php  (v43)  
           Loading  from  cache  
    !
    Writing  lock  file  
    Generating  autoload  files  
    !
    heroku-­‐php  $  foreman  start  
    14:30:45  web.1  |  started  with  pid  11175  
    14:30:46  web.1  |  Booting  on  port  5000...  
    14:30:46  web.1  |  Starting  php-­‐fpm...  
    14:30:46  web.1  |  Starting  nginx...

    View full-size slide

  36. (or you use your good old VirtualHost setup)

    View full-size slide

  37. LET'S IMPROVE OUR APP

    View full-size slide

  38. http://silex.sensiolabs.org/doc/intro.html

    View full-size slide

  39. $  mkdir  web  
    $  git  mv  index.php  web/  
    $  echo  "web:  vendor/bin/heroku-­‐php-­‐nginx  web/"  >  Procfile  
    $  git  add  Procfile  
    $  git  commit  -­‐m  "use  a  custom  document  root"  
    [master  e87d03e]  use  a  custom  document  root  
     2  files  changed,  1  insertion(+)  
     create  mode  100644  Procfile  
     rename  index.php  =>  web/index.php  (100%)  
    $  echo  'require_once  __DIR__."/../vendor/autoload.php";  
    !
    $app  =  new  Silex\Application();  
    !
    $app-­‐>get("/",  function  ()  use  ($app)  {  
           return  "Hello  World!";  
    });  
    !
    $app-­‐>run();'  >  web/index.php  
    $  git  add  web/index.php  
    $  git  commit  -­‐m  "use  Silex"  
    [master  fe86acb]  use  Silex  
     1  file  changed,  10  insertions(+),  1  deletion(-­‐)  
    $  git  push  heroku  master

    View full-size slide

  40. http://silex.sensiolabs.org/doc/providers/monolog.html

    View full-size slide

  41. $  vi  web/index.php  #  or  whatever  your  editor  is  
    $  git  diff  #  edit  the  file  like  in  this  diff:  
    diff  -­‐-­‐git  a/web/index.php  b/web/index.php  
    index  4272703..02b453d  100644  
    -­‐-­‐-­‐  a/web/index.php  
    +++  b/web/index.php  
    @@  -­‐3,7  +3,12  @@  require_once  __DIR__."/../vendor/
    autoload.php";  
       
     $app  =  new  Silex\Application();  
       
    +$app-­‐>register(new  Silex\Provider\MonologServiceProvider(),  
    array(  
    +        "monolog.logfile"  =>  "php://stderr",  
    +));  
    +  
     $app-­‐>get("/",  function  ()  use  ($app)  {  
    +        $app["monolog"]-­‐>addNotice("The  world  is  not  enough!");  
             return  "Hello  World!";  
     });  
    $  git  add  web/index.php  
    $  git  commit  -­‐m  "log  something"  
    [master  72c4ea3]  log  something  
     1  file  changed,  5  insertions(+)  
    $  git  push  heroku  master

    View full-size slide

  42. $  heroku  logs  -­‐-­‐tail  
    2014-­‐11-­‐06T06:35:07.223192+00:00  app[web.1]:  
           [2014-­‐11-­‐06  06:35:07]  myapp.INFO:  
               Matched  route  "GET_"  
               (parameters:  "_controller":  "{}",  "_route":  "GET_")  []  []  
    2014-­‐11-­‐06T06:35:07.223499+00:00  app[web.1]:  
           [2014-­‐11-­‐06  06:35:07]  myapp.INFO:  >  GET  /  []  []  
    2014-­‐11-­‐06T06:35:07.224280+00:00  app[web.1]:  
           [2014-­‐11-­‐06  06:35:07]  myapp.NOTICE:  
               The  world  is  not  enough!  []  []  
    2014-­‐11-­‐06T06:35:07.225503+00:00  app[web.1]:  
           [2014-­‐11-­‐06  06:35:07]  myapp.INFO:  <  200  []  []  
    2014-­‐11-­‐06T06:35:07.226102+00:00  app[web.1]:  
           10.12.50.108  -­‐  -­‐  [06/Nov/2014:06:35:07  +0000]  
           "GET  /  HTTP/1.1"  200  12  "-­‐"  
           "Mozilla/5.0  (Macintosh;  Intel  Mac  OS  X  10_9_5)  AppleWebKit  
           /600.1.17  (KHTML,  like  Gecko)  Version/7.1  Safari/537.85.10"  
    2014-­‐11-­‐06T06:35:07.230159+00:00  heroku[router]:  
           at=info  method=GET  path="/"  
           host=blooming-­‐spire-­‐8023.herokuapp.com  
           request_id=57502c87-­‐4b60-­‐472e-­‐8aa2-­‐3e1977f8a086  
           fwd="199.36.244.11"  
           dyno=web.1  connect=1ms  service=18ms  status=200  bytes=245

    View full-size slide

  43. $  heroku  addons:add  heroku-­‐postgresql

    View full-size slide

  44. $  heroku  addons:add  redistogo

    View full-size slide

  45. $  heroku  addons:add  mongolabs

    View full-size slide

  46. $  heroku  addons:add  memcachier

    View full-size slide

  47. $  heroku  addons:add  foundelasticsearch

    View full-size slide

  48. $  heroku  addons:add  cloudamqp

    View full-size slide

  49. $  heroku  addons:add  papertrail

    View full-size slide

  50. $  heroku  addons:add  newrelic

    View full-size slide

  51. http://addons.heroku.com/

    View full-size slide

  52. $  heroku  addons:add  heroku-­‐postgresql  
    Adding  heroku-­‐postgresql  on  tnphptest...  done,  v6  (free)  
    Attached  as  HEROKU_POSTGRESQL_AQUA_URL  
    Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…  
    !
    $  heroku  addons:add  heroku-­‐postgresql  
    Adding  heroku-­‐postgresql  on  tnphptest...  done,  v7  (free)  
    Attached  as  HEROKU_POSTGRESQL_AMBER_URL  
    Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…  
    !
    $  heroku  addons:add  newrelic  
    Adding  newrelic  on  tnphptest...  done,  v8  (free)  
    Use  `heroku  addons:docs  newrelic`  to  view  documentation.  
    !
    $  heroku  addons:add  papertrail  
    Adding  papertrail  on  tnphptest...  done,  v9  (free)  
    Use  `heroku  addons:docs  papertrail`  to  view  documentation.  
    !
    $  heroku  config  
    ===  tnphptest  Config  Vars  
    DATABASE_URL:                                postgres://u:p@host12:5432/db18172  
    HEROKU_POSTGRESQL_AMBER_URL:  postgres://u:p@host42:5198/db24438  
    HEROKU_POSTGRESQL_AQUA_URL:    postgres://u:p@host12:5432/db18172  
    NEW_RELIC_LICENSE_KEY:              c9eecdcf9523862f981e  
    PAPERTRAIL_API_TOKEN:                13Ii1sjmchTboDS24I

    View full-size slide

  53. IV. BACKING SERVICES
    Treat backing services as attached resources.

    View full-size slide

  54. SCALING
    $  heroku  ps:scale  web=10  
    Scaling  dynos...  done,  now  running  web  at  10:1X.  
    !
    $  heroku  ps  
    ===  web  (1X):  `vendor/bin/heroku-­‐php-­‐apache2  web/`  
    web.1:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    web.2:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    web.3:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    web.4:  starting  2014/11/05  20:36:38  (~  4s  ago)  
    web.5:  starting  2014/11/05  20:36:38  (~  4s  ago)  
    web.6:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    web.7:  starting  2014/11/05  20:36:38  (~  4s  ago)  
    web.8:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    web.9:  starting  2014/11/05  20:36:38  (~  4s  ago)  
    web.10:  starting  2014/11/05  20:36:39  (~  4s  ago)  
    !
    $  heroku  ps:scale  web=1  
    Scaling  dynos...  done,  now  running  web  at  1:1X.

    View full-size slide

  55. VIII. CONCURRENCY
    Scale out via the process model.

    View full-size slide

  56. XII. ADMIN PROCESSES
    Management tasks like DB migrations are one-off processes.

    View full-size slide

  57. XII. ADMIN PROCESSES
    Management tasks like DB migrations are one-off processes.
    They run in isolation, just like all other "dynos".

    With the same release: same code, same config!

    View full-size slide

  58. XII. ADMIN PROCESSES
    Management tasks like DB migrations are one-off processes.
    $  heroku  run  "php  app/console  doctrine:migrations:migrate"  
    Running  `php  app/console…`  attached  to  terminal...  up,  run.4062  
    Migrating  up  to  20100416130452  from  0  
    !
       >>  migrating  20100416130452  
    !
             -­‐>  CREATE  TABLE  users  (username  VARCHAR(255)  NOT  NULL,  
                   password  VARCHAR(255)  NOT  NULL)  ENGINE  =  InnoDB  
    !
       >>  migrated
    They run in isolation, just like all other "dynos".

    With the same release: same code, same config!

    View full-size slide

  59. $  heroku  config:set  FOO=bar  
    Setting  config  vars  and  restarting  tnphptest...  done,  v11  
    FOO:  bar  
    !
    #  OH  MY  GOD  EVERYTHING  IS  SUDDENLY  ON  FIRE  
    !
    $  heroku  releases  |  head  -­‐n5  
    ===  tnphptest  Releases  
    v11    Set  FOO  config  vars          [email protected]    2014/11/05  22:46:08  
    v10    Deploy  7dad871                    [email protected]    2014/11/05  22:34:14  
    v9      Add  papertrail  add-­‐on      [email protected]    2014/11/05  20:44:19  
    v8      Add  newrelic  add-­‐on          [email protected]    2014/11/05  20:44:05  
    !
    $  heroku  rollback  
    Rolling  back  tnphptest...  done,  v10  
     !        Warning:  rollback  affects  code  and  config  vars;  it  
    doesn't  add  or  remove  addons.  To  undo,  run:  heroku  rollback  v11  
    !
    $  heroku  releases  |  head  -­‐n5  
    ===  tnphptest  Releases  
    v12    Rollback  to  v10                  [email protected]    2014/11/05  22:48:28  
    v11    Set  FOO  config  vars          [email protected]    2014/11/05  22:46:08  
    v10    Deploy  7dad871                    [email protected]    2014/11/05  22:34:14  
    v9      Add  papertrail  add-­‐on      [email protected]    2014/11/05  20:44:19

    View full-size slide

  60. now you know it all

    View full-size slide

  61. go have fun!

    View full-size slide

  62. PIPELINES
    $  heroku  labs:enable  pipelines  
    $  heroku  plugins:install  git://github.com/heroku/heroku-­‐pipeline  
    !
    $  heroku  pipeline:add  your-­‐app-­‐production  -­‐-­‐app  your-­‐app-­‐staging  
    Pipeline:  your-­‐app-­‐staging  -­‐-­‐-­‐>  your-­‐app-­‐production  
    !
    $  heroku  pipeline:diff  
    Comparing  your-­‐app-­‐staging  to  your-­‐app-­‐production...done,  
     your-­‐app-­‐staging  ahead  by  1  commit:  
    73ab415    2014-­‐11-­‐05    Fix  checkout  for  IE  users    (Joe)  
    !
    $  heroku  pipeline:promote  
    Promoting  myapp-­‐staging  to  myapp-­‐production...done,  v817

    View full-size slide

  63. ONE MORE THING...

    View full-size slide

  64. heroku-­‐php  $  git  rm  Procfile  
    heroku-­‐php  $  hhvm  `which  composer`  require  hhvm  ~3.2  
    ./composer.json  has  been  updated  
    Loading  composer  repositories  with  package  information  
    Updating  dependencies  (including  require-­‐dev)  
    Nothing  to  install  or  update  
    Generating  autoload  files  
    heroku-­‐php  $  git  add  composer.*  
    heroku-­‐php  $  git  ci  -­‐m  'use  HHVM'  
    heroku-­‐php  $  git  push  heroku  master  
    !
    -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected  
    -­‐-­‐-­‐-­‐-­‐>  Detected  request  for  HHVM  3.2.0  in  composer.json.  
    -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...  
                 -­‐  HHVM  3.2.0  
                 -­‐  Apache  2.4.10  
                 -­‐  Nginx  1.6.0  
    -­‐-­‐-­‐-­‐-­‐>  Building  runtime  environment...  
                 NOTICE:  No  Procfile,  defaulting  to  'web:  vendor/bin/
    heroku-­‐hhvm-­‐apache2'  
    -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  77.4MB  
    -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3  
                 http://your-­‐hhvm-­‐app.herokuapp.com/  deployed  to  Heroku

    View full-size slide

  65. ELEPHANTS IN
    THE CLOUDS
    Thanks for listening! Contact:
    @dzuelke & [email protected].
    rate my talk
    on joind.in!

    View full-size slide