Configuring Symfony - from localhost to High Availability

Configuring Symfony - from localhost to High Availability

All apps need configuration: the database host, background color of your client's theme, API token to some service, number of items per page on the blog, etc. Where should all this configuration live? Operating systems, PHP and Symfony provide many options: environment variables, .env files, plain PHP constants, dependency injection parameters, key-value databases, "secrets" vaults, etc. You have many choices! And with great choices comes great responsibility... and complexity!

In this talk, we'll review all the ways to store configuration, which is best for each "type" of configuration and the benefits and drawbacks of each. We'll also talk about Symfony 4.4's new "secrets vault" as one of the ways to store sensitive settings. By the end, you'll be ready to configure anything from localhost up to a cluster of high resiliency microservices.


Nicolas Grekas

November 21, 2019


  1. Configuring Symfony from localhost to High Availability #SymfonyCon @nicolasgrekas

  2. @nicolasgrekas @nicolasgrekas

  3. None
  4. @nicolasgrekas Configuring a Symfony app •DI parameters and PHP constants

    •Environment variables •Secrets •(High Availability)
  5. What is a Symfony Application? @nicolasgrekas

  6. A Symfony app Uses a compiled container @nicolasgrekas

  7. None
  8. @nicolasgrekas config/services.yaml # Put parameters here that don't need to

    change on each machine where the app is deployed # parameters: locale: 'en' # This parameter defines the codes of the locales (languages) enabled in the application app_locales: en|fr|de|es|cs|nl|ru|uk|ro|pt_BR|pl|it|ja|id|ca|sl|hr|zh_CN|bg|tr|lt app.notifications.email_sender:
  9. @nicolasgrekas /** * Gets the private 'App\Command\ListUsersCommand' shared autowired service.

    * * @return \App\Command\ListUsersCommand */ protected function getListUsersCommandService() { $this->privates['App\\Command\\ListUsersCommand'] = $instance = new \App\Command\ListU ($this->services['swiftmailer.mailer.default'] ?? $this->getSwiftmailer_Mailer_Def '', ($this->privates['App\\Repository\\UserRepository'] ?? $this->getUserRepositorySer ); $instance->setName('app:list-users'); return $instance; } var/cache/…/srcApp...Container.php
  10. @nicolasgrekas var/cache/…/srcApp...Container.php /** * Gets the private 'App\EventSubscriber\CommentNotificationSubscriber' shared autowired

    s * * @return \App\EventSubscriber\CommentNotificationSubscriber */ protected function getCommentNotificationSubscriberService() { return $this->privates['App\\EventSubscriber\\CommentNotificationSubscriber'] = new \A ($this->services['swiftmailer.mailer.default'] ?? $this->getSwiftmailer_Mailer_Def ($this->services['router'] ?? $this->getRouterService()), ($this->services['translator'] ?? $this->getTranslatorService()), '' ); }
  11. @nicolasgrekas Parameters are static! •Updates require recompilation! •Available at built

    time •Cluster-wide settings Fit configuring client requirements!
  12. @nicolasgrekas Parameter examples •Locale(s) •Facebook app id •Google Analytics tracker

    id •framework.cache.prefix_seed Anything commitable (this excludes secrets!)
  13. Best Practice Define business- related settings in config/services.yaml

  14. Best Practice Define rarely changing settings in PHP constants

  15. None
  16. A Symfony app Can be configured dynamically via env vars

  17. @nicolasgrekas config/packages/mailer.yaml framework: mailer: dsn: '%env(MAILER_DSN)%'

  18. @nicolasgrekas config/packages/doctrine.yaml doctrine: dbal: url: '%env(resolve:DATABASE_URL)%'

  19. @nicolasgrekas var/cache/…/srcApp...Container.php /** * Gets the public 'doctrine.dbal.default_connection' shared service.

    * * @return \Doctrine\DBAL\Connection */ protected function getDoctrine_Dbal_DefaultConnectionService() { // ... return $this->services['doctrine.dbal.default_connection'] = (new \Doctrine\Bundle\DoctrineBundle\ConnectionFactory([])) ->createConnection([ 'driver' => 'pdo_sqlite', 'charset' => 'utf8mb4', 'url' => $this->getEnv('resolve:DATABASE_URL'), 'host' => 'localhost', 'port' => NULL, 'user' => 'root', 'password' => NULL, 'driverOptions' => [], 'serverVersion' => '3.15',
  20. @nicolasgrekas vendor/dependency-injection/… if (isset($_ENV[$name])) { $env = $_ENV[$name]; } elseif

    (isset($_SERVER[$name]) && 0 !== strpos($name, 'HTTP_')) { $env = $_SERVER[$name]; } elseif (false === ($env = getenv($name)) || null === $env) { // ... Check getenv() is not thread safe variable_order=EGPCS Access to fastcgi_params, env vars, etc.
  21. @nicolasgrekas Environment Variables

  22. @nicolasgrekas

  23. @nicolasgrekas $ SYMFONY='<3' php -S localhost:8000

  24. @nicolasgrekas Anything coming from the runtime environment •OS-level env vars

    •SAPI-level env vars – e.g. FastCGI parameters •.env files •File contents •FUSE mount points •Key/value services – Consul / DNS
  25. @nicolasgrekas Reconfiguring the runtime environment? •Per-process, immutable: restart •Per-vhost/URL: reload

    the server •Committed: redeploy •Files : reupload •Virtual filesystem: live updates •Network service: live updates
  26. @nicolasgrekas Environment variables •Don’t require recompiling the app •Can be

    updated live •Shouldn’t be used at built time •Can vary by deployment target Fit configuring hosting requirements!
  27. Best Practice Define infrastructure- related settings in env vars

  28. But how should we configure env vars?! @nicolasgrekas

  29. @nicolasgrekas Delegated to devs: for mostly static parts •Commit defaults

    in .env •rsync .env.local composer dump-env prod (creates .env.local.php)
  30. @nicolasgrekas Delegated to ops: enables auto-scaling •Ansile, chef, Dockerfile, etc.

    •Server vhosts •The web-UI of your hoster •Don’t forget about the CLI! symfony var:set FOO=bar
  31. @nicolasgrekas Delegated to bots: unlocks high availability! •File rsync •Virtual

    filesystems •Key/value stores •Consul <3 (Consul Template too) Next: health checks, routing layer hot- reconfiguration with istio/envoy/traeffic/etc.
  32. Best Practice Document all env vars in .env

  33. @nicolasgrekas class ConsulEnvVarLoader implements EnvVarLoaderInterface { // ... public function

    loadEnvVars(): array { // $this->consulUrl = ''; $consulVars = $this->httpClient ->request('GET', $this->consulUrl) ->toArray()[0]['value']; return json_decode(base64_decode($consulVars), true); } }
  34. None
  35. Best Practice Increase complexity only when needed!

  36. What about secrets? @nicolasgrekas

  37. @nicolasgrekas

  38. @nicolasgrekas $ SYMFONY='<3' php -S localhost:8000

  39. Fact: .env.local.php is more secure than real env vars @nicolasgrekas

  40. @nicolasgrekas Security threats – entropy •Remote access •Insecure deployment network

    •Shared hosting •Bad document root / php.ini •Employees leaving •Technical responsibilities in the wrong hands
  41. @nicolasgrekas Security threats mitigation •Rotate secrets •Store and deploy secrets

    encrypted •Use .env.local.php •Use asymmetric cryptography •Make things convenient
  42. @nicolasgrekas Secrets management tools •Pet .env.local file deployed via SSH

    •bin/console secrets:* •(Consul|Docker|Kubernetes) Vault
  43. @nicolasgrekas

  44. @nicolasgrekas

  45. @nicolasgrekas $ cat config/secrets/dev/dev.GITHUB_API_TOKEN.aebb6a.php <?php // dev.GITHUB_API_TOKEN.aebb6a on Thu, 21

    Nov 2019 14:31:29 +0100 return "d\x9B\x5D2\xABV\x0B\xE6\xB1\xC2\x25\xCB\x2B\xDAkA\xE4\xC7\x9Cu\x
  46. @nicolasgrekas

  47. @nicolasgrekas class SodiumVault implements EnvVarLoaderInterface public function loadEnvVars(): array {

    return $this->list(true); }
  48. @nicolasgrekas Configure secrets framework: secrets: vault_directory: '%kernel.project_dir%/config/secrets/%kernel.environment%' local_dotenv_file: '%kernel.project_dir%/.env.local' decryption_env_var:

    'base64:default::SYMFONY_DECRYPTION_SECRET' export SYMFONY_DECRYPTION_SECRET=$(php -r 'echo base64_encode( \ require "config/secrets/prod/prod.decrypt.private.php" \ );')
  49. @nicolasgrekas Deploy secrets •Same workflow as code – git blame

    <3 •But the decryption key •Share the keys with the appropriate persons •Rotation is yours to build •Decrypt live – or at build time bin/console secrets:decrypt-to-local (populates .env.local)
  50. Takeaways? @nicolasgrekas

  51. @nicolasgrekas Key takeaways •Use parameters for business requirements •Use env

    vars for infra-related configuration •Use .env files first, increase complexity later •Use bin/console secrets:* •Use Consul/etc. when the need is here You’re on the path to high-availability!
  52. Merci Thank you Gracias ﺍﺮﻜﺷ 多謝 Ευχαριστώ ありがとう 감사합 니다

  53. None