Блог

Інфраструктура 8 хв чытання

Docker для PHP у прадакшні: практычнае кіраўніцтва для бізнес-прыкладанняў

Docker дазваляе PHP-дадатку працаваць аднолькава на наўтбуку, у тэставанні і ў прадакшні і ператварае рэлізы і адкаты са стрэсу ў руціну. Але ён дадае рухомых частак, а нядбайная настройка горш звычайнага сервера. Тут - гатовая да прадакшна базавая канфігурацыя з рэальнымі наладамі і выпадкі, калі ён не патрэбен.

Вокладка артыкула: Docker для PHP у прадакшні: практычнае кіраўніцтва для бізнес-прыкладанняў

Коратка

  • Docker варта выкарыстоўваць, калі патрэбны аднолькавыя асяроддзі, хуткія адкаты і просты ўваход у праект. Для аднаго невялікага сайта-візітоўкі на віртуальным хостынгу ён звычайна лішні.
  • Збірайце нязменную выяву ў CI: шматэтапная зборка, толькі прадакшн-залежнасці, карыстач без root, уключаны OPcache, памылкі пішуцца ў лог і ніколі не паказваюцца.
  • Выпускайце рэлізы па тэгу выявы (хэш комміта Git), правярайце здароўе пасля старту і аўтаматычна адкочвайцеся на папярэдні тэг, калі праверка не мінула.
  • Кантэйнеры аднаразовыя, дадзеныя - не: трымаеце базу і загрузкі ў тамах або кіраваных сэрвісах і рабіце бэкапы па-за 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 МБ, а PHP можна аддаць 1 ГБ, pm.max_children - каля 16. Нашмат большае значэнне прыводзіць да свопу або да таго, што ядро забівае працэсы пры ўсплёску трафіку. Занадта малое - запыты ўстаюць у чаргу. Сачыце ў логу php-fpm за папярэджаннямі "max_children reached".

Compose-файл з правільнымі значэннямі па змаўчанні

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, не спадзявайцеся на 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 і разгортвайце менавіта гэты артэфакт. Мінімальны workflow 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. Compose-файл з палітыкамі перазапуску, healthcheck, лімітамі памяці, ратацыяй логаў і без публічнага порта базы.
  4. Рэлізы па тэгу з хэшам комміта, аўтаматычнай праверкай здароўя і адкатам.
  5. Бэкапы і праверанае аднаўленне базы і загрузак.
  6. Маніторынг і алерты па здароўі, дыску, памылкам і сертыфікатам.

Чым мы можам дапамагчы

Мы кантэйнерызуем PHP-прыкладанні, наладжваем CI-канвееры і рэлізы і падлучальны іх да плацяжоў, CRM і месэнджарам. Паглядзіце нашу паслугу інтэграцый або запішыцеся на званок, і мы вывучым ваш бягучы хостынг і працэс рэлізаў.

Частыя пытанні

Ці падыходзіць Docker для запуску PHP у прадакшне?

Так, калі выкарыстоўваць яго правільна. Ён дае аднолькавыя асяроддзі, паўтараныя рэлізы і хуткія адкаты. Патрэбна карэктная настройка: нязменныя вобразы, healthcheck, ліміты рэсурсаў, ратацыя логаў, кіраванне сакрэтамі і бэкапы дадзеных па-за кантэйнерам.

Ці трэба запускаць базу дадзеных у Docker у прадакшні?

Для невялікіх і сярэдніх сістэм можна, калі выкарыстоўваецца пастаянны том, база не адкрыта публічна і бэкапы правераны. Для крытычных дадзеных шматлікія каманды аддаюць перавагу кіраваны сэрвіс базы, які бярэ на сябе адмоваўстойлівасць, абнаўленні і бэкапы.

Як адкаціць рэліз у Docker ?

Пазначайце кожную выяву хэшам комміта Git і захоўваеце папярэдні тэг. Для адкату запусціце кантэйнеры з папярэднім тэгам. Пішыце міграцыі базы так, каб стары код працаваў з новай схемай.

Колькі воркераў php-fpm наладжваць?

Вымерайце сярэднюю памяць аднаго воркера пад нагрузкай, вырашыце, колькі памяці можа займаць PHP, і падзеліце. Занадта вялікае значэнне выклікае своп, занадта малое - чарга запытаў. Сачыце ў логу php-fpm за папярэджаннямі max_children.

Якая самая вялікая памылка бяспекі Docker у PHP-прыкладаннях?

Сакрэты ў выяве або Git і парты базы, апублікаваныя ў інтэрнэт. Таксама пазбягайце запуску ад root, мантавання сокета Docker у кантэйнеры і неабнаўляльнай базавай выявы.

Бясплатны званок для выяўлення

Прынясіце бязладны стэк ці завіслую інтэграцыю - Integrono намячае самы разумны першы крок.

Вы атрымліваеце экспертную ацэнку бюджэтных пастак, залежнасцей і таго, ці варта купляць, склейваць ці будаваць - да пачатку платнай працы. CEO застаецца ў кантуры.