MFormations
Modern PHP Engineering

Chapitre 13

13 — DevOps PHP

13 — DevOps PHP

Course : DevOps PHP

1. Introduction au DevOps pour PHP

1.1 Qu'est-ce que le DevOps ?

Le DevOps est l'intersection entre le développement (Dev) et les opérations (Ops). Il vise à automatiser et intégrer les processus entre ces deux équipes pour livrer des logiciels plus rapidement et de manière plus fiable.

1.2 Pipeline DevOps typique

Code → Build → Test → Deploy → Monitor
  │       │       │        │        │
  Git    CI/CD   PHPUnit   Docker  Pulse
         GitHub   Pest    Laravel  Envoyer
         Actions           Forge

1.3 Outils de l'écosystème PHP

  • Docker : conteneurisation de l'application
  • Laravel Forge : gestion de serveurs
  • Laravel Vapor : serverless AWS Lambda
  • Deployer : déploiement automatisé
  • GitHub Actions / GitLab CI : CI/CD
  • Laravel Pulse : monitoring des performances
  • Envoyer : déploiement zero-downtime

2. Docker

2.1 Dockerfile multi-stage

# Stage 1 : Build
FROM php:8.3-fpm-alpine AS builder

RUN apk add --no-cache \
    git \
    unzip \
    libzip-dev \
    libpng-dev \
    libjpeg-turbo-dev \
    && docker-php-ext-install pdo_mysql zip gd intl

COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

WORKDIR /var/www
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-interaction --optimize-autoloader

COPY . .
RUN php artisan optimize

# Stage 2 : Production
FROM php:8.3-fpm-alpine

RUN apk add --no-cache \
    libzip \
    libpng \
    libjpeg-turbo \
    nginx \
    supervisor

COPY --from=builder /var/www /var/www

# Configuration
COPY docker/php.ini /usr/local/etc/php/conf.d/app.ini
COPY docker/nginx.conf /etc/nginx/nginx.conf
COPY docker/supervisord.conf /etc/supervisord.conf

EXPOSE 80

CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]

2.2 docker-compose.yml

version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "80:80"
    environment:
      - APP_ENV=production
      - DB_HOST=database
      - REDIS_HOST=cache
    depends_on:
      - database
      - cache

  database:
    image: mysql:8.3
    environment:
      MYSQL_DATABASE: app
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    volumes:
      - db_data:/var/lib/mysql
    ports:
      - "3306:3306"

  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    ports:
      - "6379:6379"

  queue:
    build:
      context: .
      dockerfile: Dockerfile
    command: php artisan horizon
    depends_on:
      - database
      - cache

  scheduler:
    build:
      context: .
      dockerfile: Dockerfile
    command: php artisan schedule:work
    depends_on:
      - database
      - cache

volumes:
  db_data:
  redis_data:

2.3 Extensions PHP essentielles

# Installation d'extensions
RUN docker-php-ext-install \
    pdo_mysql \
    mbstring \
    exif \
    pcntl \
    bcmath \
    gd \
    intl \
    zip

# PECL extensions
RUN pecl install \
    redis \
    xdebug \
    apcu \
    && docker-php-ext-enable redis apcu

2.4 Optimisation des images

# Utiliser des versions Alpine
FROM php:8.3-fpm-alpine  # ~50MB vs ~300MB

# Nettoyer les caches
RUN apk add ... && docker-php-ext-install ... \
    && rm -rf /var/cache/apk/* /tmp/*

# Layers minimales (regrouper les RUN)
RUN apk add --no-cache pkg1 pkg2 \
    && docker-php-ext-install ext1 ext2

3. Laravel Forge & Vapor

3.1 Laravel Forge

Forge gère l'infrastructure : provisionnement de serveurs (DigitalOcean, AWS, Linode, Vultr), installation de PHP, Nginx, MySQL, Redis.

Fonctionnalités clés :

  • Quick Deploy : déploiement automatique sur push Git
  • Daemon management : Horizon, Reverb, schedule
  • SSL : Let's Encrypt automatique
  • Queues : configuration des workers
  • Scheduling : cron jobs

3.2 Laravel Vapor

Vapor est un déploiement serverless sur AWS Lambda :

# vapor.yml
id: 12345
name: my-app
environments:
  production:
    memory: 1024
    timeout: 30
    database: postgres-production
    cache: redis-production
    queue: sqs-production
    cli:
      - php artisan migrate --force
    build:
      - 'composer install --no-dev'
      - 'php artisan optimize'
    deploy:
      - 'php artisan migrate --force'

Avantages Vapor : scalabilité automatique, pas de serveur à gérer, paiement à l'usage.


4. Deployer

4.1 Installation

composer require --dev deployer/deployer

4.2 Configuration

<?php
// deploy.php
namespace Deployer;

require 'recipe/laravel.php';

// Configuration
set('application', 'Mon Application');
set('repository', 'git@github.com:user/repo.git');
set('git_tty', true);
add('shared_files', []);
add('shared_dirs', ['storage']);
add('writable_dirs', ['storage']);
set('allow_anonymous_stats', false);

// Hôtes
host('production')
    ->setHostname('185.123.456.789')
    ->setRemoteUser('forge')
    ->setPort(22)
    ->setIdentityFile('~/.ssh/id_rsa')
    ->set('branch', 'main')
    ->set('deploy_path', '/home/forge/myapp.com');

host('staging')
    ->setHostname('staging.example.com')
    ->setRemoteUser('forge')
    ->set('branch', 'develop')
    ->set('deploy_path', '/home/forge/staging.myapp.com');

// Tâches
task('build:assets', function () {
    run('cd {{release_path}} && npm ci && npm run build');
});

task('deploy:queue:restart', function () {
    run('cd {{release_path}} && php artisan horizon:terminate');
});

after('deploy:success', 'deploy:queue:restart');
after('deploy:failed', 'deploy:unlock');

// Rollback
task('rollback:database', function () {
    run('cd {{release_path}} && php artisan migrate:rollback');
});

4.3 Déploiement

# Déploiement
dep deploy production

# Rollback (si problème)
dep rollback production

# Lister les versions
dep releases production

4.4 Structure des répertoires

/home/forge/myapp.com/
├── current -> releases/20240729120000  (symlink)
├── releases/
│   ├── 20240729120000/
│   ├── 20240728120000/
│   └── 20240727120000/
├── shared/
│   ├── .env
│   ├── storage/
│   └── vendor/
└── deploy.lock

5. CI/CD

5.1 GitHub Actions

name: CI/CD Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  quality:
    name: Code Quality
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
          coverage: xdebug
      - run: composer install --no-interaction
      - run: ./vendor/bin/pint --test     # Laravel Pint
      - run: ./vendor/bin/phpstan analyse --level=max
      - run: php artisan test --coverage --min=80

  deploy:
    name: Deploy to Production
    needs: quality
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with:
          php-version: 8.3
      - run: composer install --no-dev --optimize-autoloader
      - run: npm ci && npm run build
      - name: Deploy with Deployer
        run: php vendor/bin/dep deploy production
        env:
          SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}

5.2 GitLab CI

stages:
  - test
  - build
  - deploy

variables:
  PHP_IMAGE: php:8.3-cli-alpine

cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - vendor/
    - node_modules/

before_script:
  - docker-php-ext-install pdo_mysql
  - curl -sS https://getcomposer.org/installer | php
  - php composer.phar install --no-interaction

test:
  stage: test
  script:
    - vendor/bin/phpunit --coverage-text --colors=never
  coverage: '/^\s*Lines:\s*\d+.\d+%/'

build:
  stage: build
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

deploy:
  stage: deploy
  script:
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker-compose up -d
  environment:
    name: production

5.3 Environnements et variables

# GitHub Environments
environments:
  production:
    url: https://myapp.com
    deployment_branch: main
  staging:
    url: https://staging.myapp.com
    deployment_branch: develop

6. Laravel Pulse

6.1 Installation

composer require laravel/pulse
php artisan vendor:publish --provider="Laravel\Pulse\PulseServiceProvider"
php artisan migrate

6.2 Configuration

// config/pulse.php
return [
    'recorders' => [
        CacheInteractions::class => [
            'enabled' => env('PULSE_CACHE_ENABLED', true),
        ],
        Exceptions::class => [
            'enabled' => env('PULSE_EXCEPTIONS_ENABLED', true),
        ],
        Queues::class => [
            'enabled' => env('PULSE_QUEUES_ENABLED', true),
        ],
        Servers::class => [
            'enabled' => env('PULSE_SERVERS_ENABLED', true),
            'directories' => [
                '/',
            ],
        ],
        SlowJobs::class => [
            'enabled' => env('PULSE_SLOW_JOBS_ENABLED', true),
            'threshold' => env('PULSE_SLOW_JOBS_THRESHOLD', 1000),
        ],
        SlowOutgoingRequests::class => [
            'enabled' => env('PULSE_SLOW_OUTGOING_REQUESTS_ENABLED', true),
            'threshold' => env('PULSE_SLOW_OUTGOING_REQUESTS_THRESHOLD', 1000),
        ],
        SlowQueries::class => [
            'enabled' => env('PULSE_SLOW_QUERIES_ENABLED', true),
            'threshold' => env('PULSE_SLOW_QUERIES_THRESHOLD', 1000),
        ],
        SlowRequests::class => [
            'enabled' => env('PULSE_SLOW_REQUESTS_ENABLED', true),
            'threshold' => env('PULSE_SLOW_REQUESTS_THRESHOLD', 1000),
        ],
        UserSessions::class => [
            'enabled' => env('PULSE_USER_SESSIONS_ENABLED', true),
        ],
    ],
];

6.3 Dashboard

// routes/pulse.php
use Laravel\Pulse\Facades\Pulse;
use Illuminate\Support\Facades\Route;

Route::get('/pulse', function () {
    return view('pulse::dashboard');
})->middleware(['web', 'auth'])->can('viewPulse');

6.4 Alertes

// App\Providers\PulseServiceProvider.php
use Laravel\Pulse\Facades\Pulse;

Pulse::alert(function ($alerts) {
    foreach ($alerts as $alert) {
        if ($alert->type === 'slow_requests') {
            Notification::route('slack', env('SLACK_WEBHOOK'))
                ->notify(new PerformanceAlert($alert));
        }
    }
});

7. Envoyer (Zero-Downtime Deploy)

7.1 Fonctionnement

Envoyer est un service de déploiement zero-downtime développé par Laravel :

  1. Pull du code
  2. Installation des dépendances
  3. Build des assets
  4. Activation du nouveau déploiement
  5. Vérification de santé
  6. Basculer le trafic vers la nouvelle version

7.2 Configuration

# Envoyer recipe
servers:
  production:
    ip: 185.123.456.789
    user: forge
    path: /home/forge/myapp.com

scripts:
  - cd /home/forge/myapp.com && git pull origin main
  - composer install --no-dev --no-interaction
  - npm ci && npm run production
  - php artisan optimize
  - php artisan migrate --force
  - php artisan horizon:terminate

hooks:
  activated:
    - php artisan up
  deactivated:
    - php artisan down --retry=30
  health-check:
    url: /health
    expected: 200

8. Monitoring et Observabilité

8.1 Logs centralisés

// config/logging.php
'channels' => [
    'stack' => [
        'driver' => 'stack',
        'channels' => ['daily', 'sentry'],
    ],
    'sentry' => [
        'driver' => 'monolog',
        'handler' => \Monolog\Handler\RavenHandler::class,
        'handler_with' => [
            'client' => new \Raven_Client(env('SENTRY_DSN')),
        ],
    ],
],

8.2 Métriques personnalisées Pulse

// Enregistrer une métrique
Pulse::record('api_requests', $endpoint)->count();

// Jauges
Pulse::set('active_users', Cache::get('active_users_count'));

// Enregistrer une valeur
Pulse::record('order_value', $order->total)->avg();

9. Résumé

Le DevOps pour PHP repose sur l'automatisation et les bonnes pratiques :

  • Docker : conteneurisation reproducible (multi-stage, Alpine)
  • Forge/Vapor : gestion de serveurs et serverless
  • Deployer : déploiement automatisé avec rollback
  • CI/CD : GitHub Actions / GitLab CI pour qualité et déploiement
  • Pulse : monitoring en temps réel des performances
  • Envoyer : zero-downtime deploy

L'objectif final : un pipeline où un push sur main déclenche tests → build → déploiement → monitoring, le tout automatisé et fiable.