📜 ⬆️ ⬇️

We force the php-fpm 5.6 service running through systemd to read global environment variables

This is a short how-to for implementing the configuration of a php service dependent on the environment in which it is running. I will be glad if someone prompts a more elegant solution or corrects in the details.

main idea


Run a service, microservices and dependent applications within a single ecosystem, configured using environment variables .

Problem

In this article, “environment variables” are repeated too many times.
Out of the box, php-fpm ignores global environment variables ( getenv function ), while php cli can get them.

Prehistory

You can skip this section if you have already worked with .env

I'm currently working on a project written in ZF2. For configuration of the project, config files for different environments were used . This generates a large number of duplicate configurations in a project repository of the following form:

These duplicates have to constantly synchronize with each other. In addition, they store a certain php-logic within themselves, which creates duplicate code.
')
I added a library to the project that can read the environment from the .env file and load it into $ _ENV (simplified).

Connecting the library vlucas / phpdotenv to ZF2
composer require vlucas/phpdotenv

Open public / index.php
After require 'init_autoloader.php' add:
 $dotenv = new Dotenv\Dotenv(__DIR__ . '/../'); // $dotenv->required('SOME_IMPORTANT'); //      $dotenv->load(); //   overload(),   .env   ,     ( ) 


In addition (this is completely optional), I added the laravel env () helper function, which is a wrapper for php getenv ().

Add the env method ($ key, $ default = null) to ZF2
Create a file, for example library / Common / Config / env.php , with the contents:
 if ( ! function_exists('value')) { /** * Return the default value of the given value. * * @param mixed $value * @return mixed */ function value($value) { return $value instanceof Closure ? $value() : $value; } } if ( ! function_exists('env')) { /** * Gets the value of an environment variable. Supports boolean, empty and null. * * @param string $key * @param mixed $default * @return mixed */ function env($key, $default = null) { $value = getenv($key); if ($value === false) return value($default); switch (strtolower($value)) { case 'true': case '(true)': return true; case 'false': case '(false)': return false; case 'empty': case '(empty)': return ''; case 'null': case '(null)': return; } return $value; } } 


In composer.json add to the “autoload” section:
 "autoload" : { ..., "files": ["library/Common/Config/env.php"] }, 

Then run composer dumpautoload .

This step allowed to throw out from the repository all unnecessary duplicate config files (local.php, unittest.php, * .php.dist). Instead, a .env.global appeared at the root of the project with a list of all the available variables that are involved in the configs.

How to configure now?
Not the environment knows about the service variables, but the service takes into account the environment in which it is running.
1. Since on a working machine, environment variables may not have anything, and it is inconvenient to exchange a project between different machines, the phpdotenv library reads the .env file and runs its variables in $ _ENV [$ name] = $ value before launching the application.
2. Configuration files call the env () method, which is a wrapper for the getenv () php function, and reads environment variables, substituting the default value as needed.
 //  : $config['emails']['from'] = env('APP_EMAIL', 'info@myemail.com'); $config['is_production'] = ( 'production' == env('APP_ENV') ); if (env('ZF_DEBUG_TOOLS', false)) { $config['modules'][] = 'ZendDeveloperTools'; } 

3. The .env file does not have to be filled . You can use global environment variables or default values ​​in the configuration. In the absence of a .env file, exception is thrown (a feature of the library, not the most correct one), you can not connect it at all to the production server. To avoid the exception, the file just needs to be created in the project root (touch .env).
4. The .env file does not need to store all available project variables. If default values ​​are set in configs, it is enough to write only variables that are different in this environment into .env.
5. The .env file does not need to be committed to the repository. It should be added to ignore for a version control system.
6. To make the environment variable mandatory, add the following construction to index.php:
 $dotenv->required('APP_ENV'); //  APP_ENV          .env    

7. In the project repository, you can commit files of the form .env. * (.Env.phpunit, .env.develop). This is nothing but bookmarks with a set of variables for different environments. The orchestrator or CI system simply copies the template (or variables from it) when the project is deployed where the project is deployed in several copies within one system or it is not possible to operate with global environment variables. Bookmarks convenient to compare with each other. These bookmarks do not participate in the service logic.
Important: .env.production should not be stored in the project repository.
It is convenient to create .env.default - a file that contains all the environment variables supported in the project at the moment (the maximum possible template for .env).


- So, now all configs need to be duplicated in .env? When to add a new environment variable?

It is worth taking the configuration to the environment if this option may differ on different hosts.
There is nothing wrong with writing something into environment variables.
 $config['csv_separator] = ' | '; //      ,    . $config['like_panel'] = true; //   ,    env('APP_LIKE_PANEL', false) $config['facebook_app_id'] = 88888888881; //     


- What about passwords and sensitive data?

Store production data in a separate repository, in a password repository, or, for example, in an orchestra secure storage .


So, the project now takes into account the environment, but ...


While the development was done on working machines, the project read the .env file and everything worked. But when I deployed the test environment, it turned out that if you set the actual system environment variables, php-fpm ignores them. Various recipes from Google and StackOverflow boiled down to one or another automation using two well-known methods:

1. Passing variables via nginx with the parameter fastcgi_param SOMEENV test;
2. Setting variables in the env [SOME_VAR] format in the php-fpm workflow pool configuration .

Both the first and second options are convenient for some special situations. But if you think in the paradigm of “configuring an environment, not an application”, then such methods are much more difficult than, for example, simply putting an .env file in a folder with a project. But the orchestrator, the CI system, or just the system administrator should not know the details of the project, it is not elegant.

Proposed solution

Combining various recipes from the network, I groped the following working solution.
Tested under Centos 7, PHP 5.6.14.

 1.  /etc/php.ini -  variables_order = "GPCS"  variables_order = "EGPCS" #   PHP       # http://php.net/manual/ru/ini.core.php#ini.variables-order 2.  /etc/php-fpm.d/www.conf,    /etc/php-fpm.conf (       ,   www-   php-fpm. -  ( ,   ): clear_env = no #        3.      /etc/environment (  A=B) 4. ln -fs /etc/environment /etc/sysconfig/php-fpm #      php-fpm       5. systemctl daemon-reload && service php-fpm restart 


The same approach with symlink, in theory, is applicable to other services.

Pros of the proposed solution:
- Variables stored in / etc / environment are available to different applications. You can call echo $ MYSQL_HOST in the shell or getenv ('MYSQL_HOST') in php.
- Environment variables that are not explicitly set in / etc / environment will not fall into php-fpm. This allows using the orchestrator to control the environment from outside the isolated system in which the service is running.

Minuses:
“Unfortunately, I did not find a working command for reload in php-fpm by analogy with nginx, so if you change / etc / environment, you must do systemctl daemon-reload && service php-fpm restart .

Important : if your application does not work in an isolated environment (server, virtual machine, container), the definition of environment variables can unpredictably affect neighboring services in the system due to the coincidence of names in the global space.


References:
- For those who have not read the article
- The methodology of the twelve factors of the development of SAAS: keep the configuration in the environment (eng.)
- Loading environment variables using .env-files for the development environment in php-projects.

Source: https://habr.com/ru/post/270359/


All Articles