Php with fpm systemd

Заставляем сервис php-fpm 5.6, запущенный через systemd, читать глобальные переменные окружения

Запускать сервис, микросервисы и зависимые приложения в рамках одной экосистемы, конфигурируемой с помощью переменных окружения.

Проблема

В этой статье слишком много раз повторяется «переменные окружения».
Из коробки php-fpm игнорирует глобальные переменные окружения (getenv function), в то время как php cli их может получать.

Предыстория
  • session.global.php
  • session.local.php.dist
  • session.unittest.php.dist
  • db.global.php
  • db.local.php.dist
  • db.unittest.php.dist
  • .

Я добавил к проекту библиотеку, которая умеет считывать окружение из .env файла и загружать его в $_ENV (упрощённо).

composer require vlucas/phpdotenv

Открыть public/index.php
После require ‘init_autoloader.php’ добавить:

$dotenv = new Dotenv\Dotenv(__DIR__ . '/../'); // $dotenv->required('SOME_IMPORTANT'); // можно сделать некоторые переменные обязательными $dotenv->load(); // можно использовать overload(), тогда файл .env станет более важен, чем глобальные переменные окружения (каскадный принцип) 

Кроме того (это совершенно необязательно), добавил helper-функцию env() из laravel, которая является обёрткой над php getenv().

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; > > 

В composer.json добавить в секцию «autoload»:

Этот шаг позволил выбросить из репозитория все лишние дубликаты конфиг-файлов (local.php, unittest.php, *.php.dist). Вместо этого в корне проекта появился .env.global со списком всех доступных переменных, которые задействованы в конфигах.

Не окружение знает о переменных сервиса, а сервис учитывает окружение, в котором он запущен.
1. Т.к. на рабочей машине в переменных окружения может ничего и нет, да и обмениваться проектом неудобно между разными машинами, библиотека phpdotenv перед запуском приложения считывает .env файл и загоняет его переменные в $_ENV[$name] = $value.
2. Конфигурационные файлы вызывают метод env(), который является обёрткой над php-функцией getenv(), и читает переменные окружения, подставляя значение по-умолчанию по необходимости.

// Примеры использования: $config['emails']['from'] = env('APP_EMAIL', 'info@myemail.com'); $config['is_production'] = ( 'production' == env('APP_ENV') ); if (env('ZF_DEBUG_TOOLS', false))

3. Файл .env не обязательно заполнять. Можно использовать глобальные переменные окружения или значения по-умолчанию в конфигурации. При отсутствии .env файла бросается exception (особенность библиотеки, не самая правильная), на production сервере её можно вообще не подключать. Для избежания exception, файл необходимо просто создать в корне проекта (touch .env).
4. Файл .env не обязательно должен хранить все доступные переменные проекта. Если в конфигах устанавливаются значения по-умолчанию, достаточно записывать в .env только переменные, отличающиеся в данном окружении.
5. Файл .env не нужно коммитить в репозиторий. Его следует добавить в ignore для системы контроля версий.
6. Чтобы сделать переменную окружения обязательной, в index.php необходимо добавить такую конструкцию:

$dotenv->required('APP_ENV'); // Переменная APP_ENV после этого должна в обязательном порядке быть установленной через .env или через окружение

7. В репозитории проекта можно коммитить файлы вида .env.* (.env.phpunit, .env.develop). Это не что иное, как закладки с набором переменных для разного окружения. Оркестратор или CI-система просто копирует шаблон (или переменные из него) при разворачивании проекта там, где проект разворачивается в нескольких копиях в рамках одной системы или нет возможности оперировать глобальными переменными окружения. Закладки удобно сравнивать друг с другом. Эти закладки никак не участвуют в логике сервиса.

Важно: .env.production не должен храниться в репозитории проекта.
Удобно создать .env.default – файл, который содержит все переменные окружения, поддерживаемые в проекте на текущий момент (максимально-возможный template для .env).

Так что, теперь все конфиги нужно дублировать в .env? Когда добавлять новую переменную окружения?

Читайте также:  Python module compiled with module

Выносить конфигурацию в окружение стоит в том случае, если эта опция может отличаться на разных хостах.
Нет ничего плохого в том, чтобы записать нечто в переменные среды.

$config['csv_separator] = ' | '; // это не меняется в разном окружении, не стоит это трогать. $config['like_panel'] = true; // а это меняется, можно заменить на env('APP_LIKE_PANEL', false) $config['facebook_app_id'] = 88888888881; // рекомендуется использовать переменные среды 

А как быть с паролями и чувствительными данными?

Храните production-данные в отдельном репозитории, в хранилище паролей или, например, в защищенном хранилище оркестратора.

Итак, проект теперь учитывает окружение, но.

Пока разработка велась на рабочих машинках, проект читал .env файл и всё работало. Но когда я развернул тестовую среду, оказалось, что если задать взаправдашние системные переменные окружения, php-fpm их игнорирует. Различные рецепты из гугла и StackOverflow сводились к той или иной автоматизации использования двух известных способов:

1. Передача переменных через nginx параметром fastcgi_param SOMEENV test;
2. Установкой переменных в формате env[SOME_VAR] в конфигурации пула рабочих процессов php-fpm.

И первый, и второй вариант, удобны для каких-то особых ситуаций. Но если мыслить в парадигме «конфигурировать среду, а не приложение», то подобные способы оказываются куда труднее, чем например просто положить .env файл в папку с проектом. Но ведь оркестратор, CI-система или просто системный администратор не должен знать детали реализации проекта, это не изящно.

Предлагаемый способ решения

Скомбинировав различные рецепты из сети, я нащупал следующее рабочее решение.
Тестировалось под 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 

Этот же подход с симлинком, в теории, применим и к другим сервисам.

Читайте также:  Can java file run on android

Плюсы предложенного решения:
— Переменные, хранящиеся в /etc/environment, доступны разным приложениям. Можно вызвать echo $MYSQL_HOST в shell или getenv(‘MYSQL_HOST’) в php.
— Переменные окружения, которые явно не заданы в /etc/environment, не попадут в php-fpm. Это позволяет с помощью оркестратора контролировать окружение извне изолированной системы, в которой запущен сервис.

Минусы:
— К сожалению, у php-fpm я не нашел работающей команды для reload по аналогии с nginx, так что в случае изменения /etc/environment, обязательно нужно делать systemctl daemon-reload && service php-fpm restart.

Важно: если ваше приложение работает не в изолированной среде (сервер, виртуалка, контейнер), определение переменных окружения может непредсказуемо повлиять на соседние сервисы в системе из-за совпадений имён в глобальном пространстве.

Ссылки:
— Для тех, кто не читал статью
— Методология двенадцати факторов разработки SAAS: храните конфигурацию в окружении (англ.)
— Загрузка переменных окружения с помощью .env-файлов для development environment в php-проектах.

Источник

magnetikonline / README.md

A basic set of systemd units for starting Nginx and PHP-FPM daemons on system startup.

  • Ensures Nginx web server has started before the PHP-FPM process.
  • Nginx pid file placed at /run/nginx.pid .
  • PHP-FPM pid file placed at /run/php7/php-fpm.pid , PHP7 PHP-FPM config at /etc/php7 .
  • Based on usage with Ubuntu 16.04LTS / 18.04LTS.

Unit files are placed in /etc/systemd/system and enabled with:

$ sudo systemctl enable nginx.service $ sudo systemctl enable php-fpm.service

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

[Unit]
Description =Nginx HTTP server
After =local-fs.target
After =network.target
[Service]
PIDFile =%t/nginx.pid
ExecStart =/usr/local/sbin/nginx
ExecReload =/bin/kill -HUP $MAINPID
Type =forking
[Install]
WantedBy =multi-user.target
Читайте также:  Hashmap update value java

This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters

[Unit]
Description =PHP FastCGI process manager
After =local-fs.target
After =network.target
After =nginx.service
[Service]
PIDFile =%t/php7/php-fpm.pid
ExecStartPre =/bin/mkdir —parents %t/php7
ExecStart =/usr/local/sbin/php-fpm —fpm-config /etc/php7/php-fpm.conf —nodaemonize
ExecReload =/bin/kill -USR2 $MAINPID
Type =simple
[Install]
WantedBy =multi-user.target

Источник

Оцените статью