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. 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");
  2. 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"      }   }
  3. III. CONFIGURATION Store config in the environment. Assumption: same code

    but different configuration per deployment target
  4. 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
  5. $  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
  6. 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.
  7. 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.
  8. 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
  9. $  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
  10. X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  11. X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  12. 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!
  13. 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
  14. heroku-­‐java-­‐app  $  cat  Procfile   worker:  sh  worker/target/bin/worker   web:

     java  -­‐jar  target/dependency/jetty-­‐runner.jar  -­‐-­‐port  $PORT   target/*.war
  15. 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
  16. 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...
  17. $  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  '<?php   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
  18. $  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
  19. $  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
  20. $  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
  21. 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.
  22. 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!
  23. 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!
  24. $  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
  25. 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
  26. 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