Блог

Инфраструктура 9 мин. четене

Docker за PHP в производство: практическо ръководство за настройка на бизнес приложения

Docker кара PHP приложение да работи по един и същи начин на лаптоп, при тестване и в производство, и прави изданията и връщанията рутинни, вместо стресиращи. Той също така добавя движещи се части и небрежната настройка е по-лоша от обикновен сървър. Това ръководство показва готова за производство базова линия с реална конфигурация и кога не трябва да се притеснявате.

Корица на статията: Docker за PHP в производство: практическо ръководство за настройка на бизнес приложения

Накратко

  • Docker си струва, когато искате идентични среди, бързи връщания назад и лесно включване. За един малък сайт с брошури на споделен хостинг това обикновено не е необходимо.
  • Създайте неизменно изображение в CI: многоетапно изграждане, само производствени зависимости, не-root потребител, OPcache включен, грешки регистрирани и никога не показвани.
  • Освобождаване чрез таг на изображението (хешът на Git commit), проверка на здравето след стартиране и автоматично връщане към предишния таг, ако проверката е неуспешна.
  • Контейнерите са за еднократна употреба, данните не са: съхранявайте базата данни и качванията в томове или управлявани услуги и ги архивирайте извън Docker.
  • Най-често срещаните производствени грешки: тайни в изображението, публикувани портове на база данни, неограничени регистрационни файлове и ограничения на php-fpm, които не отговарят на паметта, която имате.

За много бизнес приложения реалната цена на хостинг не е сървърът, а хората. Настройка, която само един разработчик разбира, внедряване, извършено ръчно през FTP, и промеждутъчна среда, която се държи различно от производствената, са скъпи в дългосрочен план. Контейнерите се справят с това, като опаковат приложението с неговата точна PHP версия и разширения. Ние управляваме нашите собствени PHP сайтове и клиентски системи на стек, базиран на Docker, и това ръководство описва базовата линия, от която започваме.

Кога Docker си заслужава и кога не

  • Заслужава си: няколко услуги (уеб, PHP, база данни, опашка), повече от един разработчик, необходимост от повтарящи се издания и бързи връщания назад или план за преместване между доставчици.
  • Не е необходимо: прост сайт със съдържание, който рядко се променя на управляван хостинг. Добавената сложност изисква отплата.
  • Не е заместител на добрите операции: все още се нуждаете от корекции, архивиране и наблюдение. Вижте нашето ръководство за архивиране и наблюдение.

Целевата архитектура

Солидна, проста настройка за PHP приложение на един сървър:

  • Обратно прокси и TLS на ръба ( Nginx или Caddy на хоста или CDN като Cloudflare, вижте нашето ръководство за настройка на Cloudflare).
  • Уеб контейнер ( Nginx), който обслужва статични файлове и предава PHP заявки.
  • PHP-FPM контейнер с вашия код, вграден в изображението.
  • База данни в собствен контейнер с постоянен обем или по-добре управлявана услуга за база данни за критични данни.
  • Томове само за данни, които трябва да оцелеят: файлове на бази данни и потребителски качвания.

Многоетапен Dockerfile

Многоетапното компилиране инсталира зависимости на един етап и копира само резултата в тънко изображение за изпълнение. Крайното изображение няма Composer, компилатори или инструменти за тестване, което го прави по-малък и дава на нападателите по-малко за използване:

# ---- stage 1: PHP dependencies ----
FROM composer:2 AS vendor
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --prefer-dist --no-interaction --optimize-autoloader

# ---- stage 2: runtime ----
FROM php:8.3-fpm-alpine

RUN apk add --no-cache icu-libs libzip \
 && apk add --no-cache --virtual .build icu-dev libzip-dev $PHPIZE_DEPS \
 && docker-php-ext-install -j"$(nproc)" pdo_mysql intl zip opcache \
 && apk del .build

COPY docker/php.ini  /usr/local/etc/php/conf.d/zz-app.ini
COPY docker/www.conf /usr/local/etc/php-fpm.d/zz-www.conf

WORKDIR /var/www/app
COPY --from=vendor /app/vendor ./vendor
COPY --chown=www-data:www-data . .

USER www-data
EXPOSE 9000

Изберете PHP версията съзнателно и проверете дали все още се поддържа. Нашето ръководство за надграждане на PHP 8 обяснява как да планирате това. Закачете версии на базови изображения и ги актуализирайте по график, тъй като изображение, което никога не възстановявате, натрупва известни уязвимости. Запазете .dockerignore, който изключва .git, .env, тестове и локални данни, така че нищо чувствително да не попадне в изображението.

Производствен PHP и php-fpm конфигурация

# docker/php.ini
expose_php = 0
display_errors = 0
log_errors = 1
memory_limit = 256M
upload_max_filesize = 10M
post_max_size = 12M

opcache.enable = 1
opcache.memory_consumption = 128
opcache.interned_strings_buffer = 16
opcache.max_accelerated_files = 20000
opcache.validate_timestamps = 0   ; the code in an image never changes while it runs

# docker/www.conf
[www]
pm = dynamic
pm.max_children = 12
pm.start_servers = 3
pm.min_spare_servers = 2
pm.max_spare_servers = 5
pm.max_requests = 500
request_terminate_timeout = 30s
catch_workers_output = yes
decorate_workers_output = no

Задаването на opcache.validate_timestamps = 0 дава най-добра производителност за неизменно изображение, но работи само ако никога не редактирате файлове в работещ контейнер: вместо това пускате ново изображение. Оразмерете пула от реални числа: измерете средната памет на един PHP работник под натоварване, решете колко памет PHP може да използва на сървъра и разделете. Ако работният файл използва 60 MB и можете да дадете на PHP 1 GB, pm.max_children е около 16. Задаването му на много по-високо ниво просто прави размяната на сървъра или процесите на ядрото убиват по време на пик на трафика. Твърде ниско и опашка от заявки. Проверете дневника на php-fpm за предупреждения „max_children достиган“.

Съставен файл с правилните настройки по подразбиране

x-logging: &default-logging
  driver: json-file
  options: { max-size: "10m", max-file: "5" }     # logs cannot fill the disk

services:
  web:
    image: ghcr.io/your-org/app-web:${TAG}
    ports: ["127.0.0.1:8080:80"]                  # only the local reverse proxy can reach it
    depends_on: [php]
    restart: unless-stopped
    logging: *default-logging
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1/health.php"]
      interval: 15s
      timeout: 3s
      retries: 3

  php:
    image: ghcr.io/your-org/app:${TAG}
    env_file: .env                                 # secrets stay on the server, not in the image
    restart: unless-stopped
    read_only: true
    tmpfs: [/tmp]
    mem_limit: 512m
    volumes: [uploads:/var/www/app/storage/uploads]
    logging: *default-logging

  db:
    image: mysql:8.4
    env_file: .env.db
    restart: unless-stopped
    volumes: [db_data:/var/lib/mysql]              # no "ports:" - the database is not public
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
      interval: 15s
      retries: 5
    logging: *default-logging

volumes:
  uploads:
  db_data:

web изображението е малко изображение Nginx с копирана вашата public/ папка и конфигурация на сайта, вградено в същия конвейер като PHP изображението. Ключови точки: обвържете публикуваните портове с 127.0.0.1, никога не публикувайте базата данни, задайте правила за рестартиране, ограничете паметта, така че един контейнер да не може да свали хоста, и ограничете размера на журнала. Не забравяйте, че портовете, публикувани от Docker могат да заобиколят правилата на защитната стена на хоста, както обясняваме в ръководството Cloudflare. Никога не монтирайте Docker сокета в контейнер на приложение, защото той дава на този контейнер контрол над хоста.

Освобождавания и връщания назад

Най-добрата характеристика на настройката, базирана на изображение, е, че всяко издание е етикет, а връщането назад е „стартиране на предишния етикет“. Маркирайте изображения с хеша на Git commit, никога не разчитайте на latest и накарайте скрипта за освобождаване да проверява изправността:

#!/usr/bin/env bash
# /srv/app/release.sh <git-sha>
set -euo pipefail
TAG="${1:?usage: release.sh <git-sha>}"
cd /srv/app
PREV="$(cat .current_tag 2>/dev/null || true)"

export TAG
docker compose pull php web
docker compose up -d --no-deps php web

for i in $(seq 1 30); do
  if curl -fsS http://127.0.0.1:8080/health.php > /dev/null; then
    echo "$TAG" > .current_tag
    echo "healthy: $TAG"; exit 0
  fi
  sleep 2
done

echo "unhealthy after release, rolling back to ${PREV:-none}" >&2
if [ -n "$PREV" ]; then TAG="$PREV" docker compose up -d --no-deps php web; fi
exit 1

Промените в базата данни се нуждаят от внимание: напишете миграции, така че предишната версия на кода все още да работи с новата схема (първо добавете колони, премахнете ги в по-късна версия). Това прави бързото връщане назад безопасно. Направете резервно копие на база данни преди всяка версия, която променя схемата.

Изграждане и доставка от CI

Изградете изображението веднъж в CI и разположете точно този артефакт. Минимален работен процес GitHub Actions:

name: build-and-release
on:
  push:
    branches: [main]
jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ghcr.io/your-org/app:${{ github.sha }}
      - name: Release over SSH
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          install -m 600 /dev/null key && echo "$DEPLOY_KEY" > key
          ssh -i key -o StrictHostKeyChecking=accept-new deploy@your-server "/srv/app/release.sh ${{ github.sha }}"

Изпълнете вашите тестове и статичен анализ в същия конвейер, преди изображението да бъде изградено, и използвайте ключ за внедряване, който може да изпълнява само скрипта за освобождаване. Добавете сканиране за уязвимост на изображение, ако вашият рисков профил го изисква.

Дневници, данни и сигурност

  • Регистрационните файлове отиват в stdout и stderr, ротират се от Docker и за нещо важно се изпращат на централно място, така че да оцелеят в контейнера.
  • Данните живеят извън контейнера. Томът не е резервно копие. Използвайте подхода в нашето ръководство за архивиране: криптиран, извън сайта, тестван за възстановяване.
  • Тайните никога не се изпичат в образи. Прекарайте ги по време на изпълнение през env файлове или мениджър на тайни и ги пазете от Git.
  • Стартирайте като не-root потребител, използвайте файлова система само за четене, където е възможно, и изтрийте това, което не ви трябва.
  • Поддържайте свежи изображения. Преизграждайте редовно, за да вземете поправки за сигурност в базовото изображение и PHP.
  • Приложете контролите на ниво приложение от нашия контролен списък за сигурност PHP, тъй като контейнерите не поправят несигурен код.

Контролен списък за готовност за производство

  1. Многоетапно изображение само с производствени зависимости, фиксирано основно изображение, не-root потребител.
  2. OPcache активиран, регистрираните грешки не се показват, php-fpm е оразмерен от измерена памет.
  3. Създайте файл с правила за рестартиране, проверки на състоянието, ограничения на паметта, ротация на регистрационни файлове и без порт на публична база данни.
  4. Издава чрез commit-hash таг с автоматична проверка на състоянието и връщане назад.
  5. Архивиране и тествано възстановяване за базата данни и качвания.
  6. Мониторинг и предупреждения за здраве, диск, грешки и сертификати.

Как можем да помогнем

Ние контейнеризираме PHP приложения, настройваме CI канали и версии и ги свързваме с плащания, CRM и съобщения. Вижте нашата услуга за интегриране или резервирайте обаждане и ние ще прегледаме текущия ви процес на хостинг и освобождаване.

Често задавани въпроси

Добър ли е Docker за стартиране PHP в производство?

Да, когато се използва добре. Предоставя идентични среди, повтарящи се издания и бързи връщания назад. Нуждае се от правилна конфигурация: неизменни изображения, проверки на състоянието, ограничения на ресурсите, ротация на регистрационни файлове, управление на тайни и архивиране на данни извън контейнера.

Трябва ли базата данни да работи в Docker в производството?

Може за малки и средни системи, ако използва постоянен том, никога не се излага публично и има тествани резервни копия. За критични данни много екипи предпочитат услуга за управлявана база данни, която обработва отказ, актуализации и архивиране вместо вас.

Как да върна версия на Docker ?

Маркирайте всяко изображение с хеша на Git commit и запазете предишния етикет. За да се върнете назад, стартирайте контейнерите с предишния етикет. Напишете миграции на бази данни, така че по-старият код все още да работи с по-новата схема.

Колко php-fpm работници трябва да конфигурирам?

Измерете средната памет на един работник под натоварване, решете колко памет PHP може да използва и разделете. Задаването на твърде висока стойност причинява размяна, докато твърде ниската причинява заявки на опашка. Гледайте регистъра на php-fpm за предупреждения за max_children.

Коя е най-голямата грешка в сигурността Docker с PHP приложения?

Поставяне на тайни в изображението или Git и публикуване на портове на база данни в интернет. Също така избягвайте да работите като root, да монтирате Docker сокета в контейнери и никога да не актуализирате базовото изображение.

Безплатно обаждане за откриване

Донесете разхвърлян стек или блокирала интеграция - Integrono ще набележи най-умния първи ход.

Получавате експертна оценка на бюджетни капани, зависимости и дали да купите, слепите или изградите - преди да започне платената работа. CEO остава в контура.