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

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
Tweet

More Decks by Nicolas Grekas

Other Decks in Programming

Transcript

  1. @nicolasgrekas Configuring a Symfony app •DI parameters and PHP constants

    •Environment variables •Secrets •(High Availability)
  2. @nicolasgrekas config/services.yaml # Put parameters here that don't need to

    change on each machine where the app is deployed # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 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: [email protected]
  3. @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 '[email protected]', ($this->privates['App\\Repository\\UserRepository'] ?? $this->getUserRepositorySer ); $instance->setName('app:list-users'); return $instance; } var/cache/…/srcApp...Container.php
  4. @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()), '[email protected]' ); }
  5. @nicolasgrekas Parameters are static! •Updates require recompilation! •Available at built

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

    id •framework.cache.prefix_seed Anything commitable (this excludes secrets!)
  7. @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',
  8. @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 httpoxy.org getenv() is not thread safe variable_order=EGPCS Access to fastcgi_params, env vars, etc.
  9. @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
  10. @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
  11. @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!
  12. @nicolasgrekas Delegated to devs: for mostly static parts •Commit defaults

    in .env •rsync .env.local composer dump-env prod (creates .env.local.php)
  13. @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
  14. @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.
  15. @nicolasgrekas class ConsulEnvVarLoader implements EnvVarLoaderInterface { // ... public function

    loadEnvVars(): array { // $this->consulUrl = 'http://127.0.0.1:8500/v1/kv/website-config'; $consulVars = $this->httpClient ->request('GET', $this->consulUrl) ->toArray()[0]['value']; return json_decode(base64_decode($consulVars), true); } }
  16. @nicolasgrekas Security threats – entropy •Remote access •Insecure deployment network

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

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

    •bin/console secrets:* •(Consul|Docker|Kubernetes) Vault
  19. @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
  20. @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" \ );')
  21. @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)
  22. @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!