Создание CLI-приложения с Laravel и Docker

Источник: «Creating a CLI Application With Laravel and Docker»
Хотя Laravel традиционно используется для создания веб-приложений, некоторые приложения нуждаются в надёжных командах CLI, запускаемых через Docker в продакшн-среде. Узнайте, как настроить образ Docker для запуска Artisan CLI, благодаря чему вы получите основу для распределения CLI-приложений на PHP.

Laravel предоставляет CLI-фреймворк, построенный на основе популярного компонента Symfony Console, позволяющий перенести лучшие возможности Laravel в командную строку. Хотя Laravel традиционно используется для создания веб-приложений, некоторые приложения нуждаются в надёжных командах CLI, выполняемых через Docker в продакшене.

Если вы создаёте проект только для CLI, то можете также рассмотреть возможность использования проекта сообщества Laravel Zero. Всё, что обсуждается в этой статье, будет работать с Laravel или Laravel Zero (с некоторыми изменениями в образе Docker).

Настройка проекта

Мы создадим небольшой CLI для проверки акций (с использованием Polygon.io API), запускаемый через Docker, предоставляющий некоторые подкоманды для выполнения таких действий, как проверка акций. Мы создадим команду stock:check, выполняющую поиск акций за указанную дату с использованием идентификатора акции:

php artisan stock:check AAPL

На момент написания этой статьи Polygon.io предоставляет бесплатный базовый план API, позволяющий выполнять 5 вызовов API в минуту, и работает уже два года. Если хотите следить за развитием событий, вам понадобится ключ API. Использование ключа API также позволит продемонстрировать, как настраивать секреты для использования с образом Docker.

Первое, что сделаем, — создадим проект Laravel. Если планируете следовать за нами, то понадобится установить PHP, Composer и программу установщика Laravel. Теперь это можно сделать одной командой команду:

В macOS:

/bin/bash -c "$(curl -fsSL https://php.new/install/mac)"

В Windows (да, под Windows это выглядит далеко не одной командой):

Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://php.new/install/windows'))

И в Linux:

/bin/bash -c "$(curl -fsSL https://php.new/install/linux)"

Затем создадим проект stock-checker:

laravel new stock-checker --git --no-interaction

Нам не нужны никакие стартовые наборы, поскольку наше приложение — это просто CLI, поэтому используем флаг --no-interaction, чтобы просто принять все настройки по умолчанию. Если после создания проекта stock-checker выполняется команда php artisan inspire, то вы готовы приступить:

php artisan inspire

“ I begin to speak only when I am certain what I will say is not better left unsaid. ”
— Cato the Younger

Наконец, необходимо создать несколько файлов для работы с Docker во время разработки, но пользователям для работы с приложением необязательно использовать что-то, кроме среды выполнения контейнера:

mkdir build/
touch \
.dockerignore \
compose.yaml \
build/Dockerfile

Мы создали файл Dockerfile в папке build. Я предпочитаю хранить файлы конфигурации Docker в подкаталоге, чтобы аккуратно организовать такие вещи, как INI файлы конфигурации и любые связанные с Docker файлы проекта.

Теперь у нас есть всё необходимое для начала работы. В следующем разделе создадим команду и настроим приложение для запуска с помощью Docker.

Создание команды CLI

Наше приложение начнётся с одной команды check, выполняющей поиск информации об акциях США для заданного идентификатора акции. Мы не будем заострять внимание на содержимом этого файла, но если вы решите продолжить, создайте файл команды с помощью Artisan:

php artisan make:command CheckStockCommand

Добавьте следующий код в только что созданный файл CheckStockCommand.php, находящийся в папке app/Console/Commands:

<?php

namespace App\Console\Commands;

use Illuminate\Http\Client\PendingRequest;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Illuminate\Console\Command;

class CheckStockCommand extends Command
{
protected $signature = 'stock:check {symbol} {--d|date= : The date to check the stock price for}';

protected $description = 'Check stock price for a given symbol.';

public function handle()
{
$symbol = Str::upper($this->argument('symbol'));

// Get the most recent trading weekday.
$date = now()->previousWeekday();

if ($dateOption = $this->option('date')) {
$date = Carbon::parse($dateOption);
if ($date->isToday() || $date->isFuture()) {
$this->error('Date must be in the past.');
return;
}
}

if ($date->lt(now()->subYear())) {
$this->error('Date must be within the last year.');
return;
}

// Find the Ticker Details
$ticker = $this->getClient()
->withUrlParameters(['symbol' => $symbol])
->withQueryParameters(['date' => $date->toDateString()])
->throw()
->get("https://api.polygon.io/v3/reference/tickers/{symbol}")
->json('results');

$openClose = $this->getClient()
->withUrlParameters([
'symbol' => $symbol,
'date' => $date->toDateString()
])
->get("https://api.polygon.io/v1/open-close/{symbol}/{date}?adjusted=true");

if ($openClose->failed()) {
$this->error("Could not retrieve stock data.\nStatus: " . $openClose->json('status') . "\nMessage: " . $openClose->json('message') . "\n");
return;
}


$this->info("Stock: {$ticker['name']} ({$ticker['ticker']})");
$this->info("Date: {$date->toDateString()}");
$this->info("Currency: {$ticker['currency_name']}");
$this->table(['Open', 'Close', 'High', 'Low'], [
[
number_format($openClose['open'], 2),
number_format($openClose['close'], 2),
number_format($openClose['high'], 2),
number_format($openClose['low'], 2),
],
]);
}

protected function getClient(): PendingRequest
{
return Http::withToken(config('services.polygon.api_key'));
}
}

Консольная команда ищет идентификатор акции за определённую дату в течение последнего года и возвращает основную информацию об акции на этот день. Чтобы команда работала, необходимо определить конфигурацию сервиса и настроить валидный ключ. Добавьте следующую конфигурацию в файл config/services.php, который команда будет использовать для настройки ключа API:

// config/services.php
return [
// ...
'polygon' => [
'api_key' => env('POLYGON_API_KEY'),
],
];

Обязательно добавьте POLYGON_API_KEY в файл .env и добавьте переменную среды в .env.example в виде пустого значения:

# .env
POLYGON_API_KEY="<your_secret_key>"

# .env.example
POLYGON_API_KEY=

Если запустить команду локально, то при вводе правильного идентификатора акции вы получите что-то вроде этого:

php artisan stock:check AAPL
Stock: Apple Inc. (AAPL)
Date: 2024-10-25
Currency: usd
+--------+--------+--------+--------+
| Open | Close | High | Low |
+--------+--------+--------+--------+
| 229.74 | 231.41 | 233.22 | 229.57 |
+--------+--------+--------+--------+

Мы удостоверились, что команда работает, и пришло время посмотреть, как создать образ Docker для размещения CLI. Docker позволяет любому использовать команду CLI без каких-либо сложных знаний о настройке среды для её выполнения.

Последнее, что необходимо сделать перед созданием образа Docker, — добавить файл .env в файл .dockerignore, созданный нами во время настройки. Добавьте следующую строку в файл .dockerignore, чтобы во время сборки не копировать секреты, хранящиеся локально:

.env

Создание образа Docker для CLI

Мы готовы к настройке CLI для работы с Docker. Есть несколько типовых случаев использования образа Docker на основе CLI:

  1. Запуск CLI с помощью Docker во время разработки
  2. Запуск CLI как части развёртывания в контейнерах
  3. Дистрибуция CLI для конечных пользователей

Все вышеперечисленные варианты использования применяются к тому, как настроить Dockerfile, и в этой статье мы продемонстрируем несколько способов структурирования образа для CLI. Рассмотрим, как запустить CLI в виде одной единственной команды или предоставить пользователям возможность запускать несколько подкоманд.

Наш Dockerfile основан на официальном образе PHP CLI и использует инструкцию ENTRYPOINT, чтобы сделать команду stock:check единственной командой, выполняемой образом. Без переопределения точки входа все команды, выдаваемые образу, будут выполняться в контексте команды stock:check:

FROM php:8.3-cli-alpine

RUN docker-php-ext-install pcntl

COPY . /srv/app
WORKDIR /srv/app
ENTRYPOINT ["php", "/srv/app/artisan", "stock:check"]
CMD ["--help"]

Инструкция ENTRYPOINT определяет команду, выполняемую при запуске контейнера. В контексте нашего CLI она интересна тем, что все команды, передаваемые контейнеру при его запуске, добавляются к точке входа. Попытаюсь проиллюстрировать:

# ENTRYPOINT            #  CMD по умолчанию
php artisan stock:check --help

# Вышеописанное эквивалентно выполнению этого:
docker run --rm stock-checker

# ENTRYPOINT # Переопределение CMD
php artisan stock:check AAPL

# Вышеописанное эквивалентно выполнению этого:
docker run --rm stock-checker AAPL

Мы демонстрируем, что ENTRYPOINT позволяет выполнять одну команду, но если вы внесёте эту небольшую корректировку, то сможете выполнять любую из команд, доступных в консоли Artisan:

FROM php:8.3-cli-alpine

RUN docker-php-ext-install pcntl

COPY . /srv/app
WORKDIR /srv/app
ENTRYPOINT ["php", "/srv/app/artisan"]
CMD ["list"]

Теперь точкой входа является artisan, что даёт возможность запускать любую команду в консоли Artisan. Если вы распространяете CLI среди других пользователей, то не стоит открывать другие команды, кроме тех, которые определены вами, но в текущем виде они могут выполнять все команды, доступные приложению.

Запуск команды php artisan list теперь будет выполняться по умолчанию при запуске образа Docker без каких-либо аргументов команды. Можно запустить нашу команду проверки акций и другие команды, созданные для CLI, следующим образом:

docker build -t stock-checker -f build/Dockerfile .

# Запуск программы проверки акции
export POLYGON_API_KEY="<your_key>"
docker run --rm --env POLYGON_API_KEY stock-checker stock:check AAPL

Части stock:check и AAPL теперь составляют CMD. Раньше команда stock:check была единственной командой, выполняемой CLI без переопределения точки входа (что можно сделать с помощью флага --entrypoint= в docker run).

Обратите внимание, что CLI требует переменную среды для настройки учётных данных. Используя флаг --env, можно передать локальную переменную ENV, которую мы экспортировали. В зависимости от потребностей приложения, можно предоставить конфигурационный файл, считываемый CLI с монтируемого секретного тома. Для удобства в статье мы используем встроенные в Docker возможности ENV для запуска CLI.

Запуск веб-приложений Laravel в качестве CLI

Если необходимо запустить команду CLI в приложении Laravel через Docker, обычно используется образ php-fpm Docker, содержащий веб-приложение/API. Вместо создания отдельного образа CLI можно настроить точку входа и команду так, чтобы она запускалась в консоли Artisan, а не в веб-приложении. Всё будет выглядеть так же, как и в нашем случае, а преимущество заключается в том, что можно повторно использовать образ веб-приложения для запуска CLI:

docker run --rm my-app --entrypoint=/srv/app/artisan stock:check AAPL

Узнать больше

Это основы запуска Laravel CLI в Docker с помощью ENTRYPOINT для определения базовой команды, используемой нашим образом Docker для выполнения команд. Подробнее о ENTRYPOINT и CMD можно узнать из официальной инструкции по Dockerfile.

Комментарии


Дополнительные материалы

Предыдущая Статья

Новое в Symfony 7.2: Непустые параметры контейнеров

Следующая Статья

Хук useState в React: Полное руководство