Upgrade to Pro — share decks privately, control downloads, hide ads and more …

PHPコンテナのマルチステージビルドとキャッシュ戦略 / PHPerKaigi 2022

51cc44c2fe932d2469cbdd74ef60a4b0?s=47 na_it_o
April 11, 2022

PHPコンテナのマルチステージビルドとキャッシュ戦略 / PHPerKaigi 2022

PHPerKaigi 2022のLT資料

51cc44c2fe932d2469cbdd74ef60a4b0?s=128

na_it_o

April 11, 2022
Tweet

More Decks by na_it_o

Other Decks in Technology

Transcript

  1. PHPコンテナの マルチステージビルドと キャッシュ戦略 くすのき/@na_it_o

  2. 内容と話す人 PHP初心者がPHPアプリケーションのDockerイメージについて考えた話 ふだんはAWSをTerraformとGo使っていじりたおしている

  3. やりたいこと さくさくすばやくビルドされて 軽量でとりまわしやすい PHPのコンテナをつくりたい

  4. Dockerの基本

  5. レイヤーキャッシュ . ├── Dockerfile ├── app.js ├── bin/ ├── package.json

    ├── package-lock.json ├── public/ ├── routes/ └── views/ # syntax=docker/dockerfile:1 FROM node:16 # Create app directory WORKDIR /usr/src/app # Install app dependencies # copy package.json AND package-lock.json COPY package*.json ./ RUN npm install # Bundle app source COPY . . EXPOSE 8080 CMD [ "node", "server.js" ]
  6. レイヤーキャッシュ . ├── Dockerfile ├── app.js ├── bin/ ├── package.json

    ├── package-lock.json ├── public/ ├── routes/ └── views/ # syntax=docker/dockerfile:1 FROM node:16 # Create app directory WORKDIR /usr/src/app # Install app dependencies # copy package.json AND package-lock.json COPY package*.json ./ RUN npm install # Bundle app source COPY . . EXPOSE 8080 CMD [ "node", "server.js" ] update use cache re-build
  7. マルチステージビルド 中間イメージを利用可能 最終イメージ - レイヤーを少なく - 余計なファイルを排除

  8. Best Practices for writing Dockerfiles Best practices for writing Dockerfiles

    | Docker Documentation https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ Use multi-stage builds - if your build contains several layers, you can order them from the less frequently changed (to ensure the build cache is reusable) to the more frequently changed Don’t install unnecessary packages Minimize the number of layers
  9. ……ってコト!? • レイヤーを意識して効率よくキャッシュされるように • 最終イメージは少ないレイヤーで必要最低限のファイルを含むように

  10. PHPでやってみる

  11. Laravelのサンプルアプリでやってみる ローカル実行用のDockerfileと docker-composeが用意されている 1. Dockerfileの追加 2. docker-compose.ymlの変更 $ curl -s

    "https://laravel.build/example-app" | bash
  12. こうなった # syntax=docker/dockerfile:1 FROM composer:2.2 as dep COPY composer.* /app/

    RUN --mount=type=cache,target=/tmp/cache/files \ composer install \ --no-dev \ --ignore-platform-reqs \ --no-interaction \ --prefer-dist \ --no-plugins \ --no-scripts \ --no-autoloader COPY ./ /app/ RUN composer dump-autoload \ --no-dev \ --optimize FROM php:8.1-apache ENV WWWUSER www-data ENV APP_ROOT /app ENV APACHE_DOCUMENT_ROOT /app/public RUN apt-get update && apt-get install -y \ libicu-dev \ libzip-dev \ && rm -rf /var/lib/apt/lists/* RUN NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \ docker-php-ext-install -j${NPROC} intl opcache pdo_mysql zip RUN pecl install redis apcu xdebug && \ docker-php-ext-enable redis RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' \ /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' \ /etc/apache2/apache2.conf /etc/apache2/conf-available/* COPY --from=dep /app/ ${APP_ROOT} RUN chown -R ${WWWUSER}:${WWWUSER} ${APACHE_DOCUMENT_ROOT} \ ${APP_ROOT}/storage WORKDIR ${APP_ROOT} ↩
  13. 大方針 1. composerで準備する 2. 結果だけ持ってくる # stage-0 FROM composer:2.2 AS

    dep COPY composer.* /app/ RUN composer install # stage-1 FROM php:8.1-apache COPY --from=dep /app/vendor/ \ /var/www/html/vendor/ COPY . /var/www/html/
  14. レイヤーキャッシュを効かせる COPYでcomposer.json, composer.lockだけ 持ってくることでアプリケーションの変更 時はキャッシュが使われる # stage-0 FROM composer:2.2 AS

    dep COPY composer.* /app/ RUN composer install # stage-1 FROM php:8.1-apache COPY --from=dep /app/vendor/ \ /var/www/html/vendor/ COPY . /var/www/html/
  15. これだけだとERRORになる $ docker build -t app:latest . => ERROR [dep

    3/3] RUN composer install ∵ scriptが走っている # syntax=docker/dockerfile:1 FROM composer:2.2 AS dep COPY composer.* /app/ RUN composer install FROM php:8.1-apache COPY --from=dep /app/vendor/ \ /var/www/html/vendor/ COPY . /var/www/html/
  16. ダメだった対処 scriptsの依存関係を解消する ./app/の中まで見ている ……沼 # syntax=docker/dockerfile:1 FROM composer:2.2 AS dep

    COPY artisan /app/ COPY bootstrap/app.php \ /app/bootstrap/ COPY app/Console/Kernel.php \ /app/app/Console/ COPY app/Exceptions/Handler.php \ /app/app/Exceptions/ COPY composer.* /app/ RUN composer install FROM php:8.1-apache COPY --from=dep /app/vendor/ \ /var/www/html/vendor/ COPY . /var/www/html/
  17. 良さそうな対処 要はscriptを後回しにできればいい 1. --no-scripts --no-autoloaderオプション でダウンロードだけやってしまう 2. その後にまるっとファイルをCOPYして composer dump-autoload

    3. appイメージの方もvendor以外もCOPY するように変更 # syntax=docker/dockerfile:1 FROM composer:2.2 AS dep COPY composer.* /app/ RUN composer install \ --no-scripts \ --no-autoloader COPY ./ /app/ RUN composer dump-autoload FROM php:8.1-apache COPY --from=dep /app/ /var/www/html/
  18. ファイルキャッシュでもっと速く BuildKit(`--mount`) `RUN --mount=type=cache,target=/tmp/cache/files composer install`

  19. 結論 # syntax=docker/dockerfile:1 FROM composer:2.2 as dep COPY composer.* /app/

    RUN --mount=type=cache,target=/tmp/cache/files \ composer install \ --no-dev \ --ignore-platform-reqs \ --no-interaction \ --prefer-dist \ --no-plugins \ --no-scripts \ --no-autoloader COPY ./ /app/ RUN composer dump-autoload \ --no-dev \ --optimize FROM php:8.1-apache ENV WWWUSER www-data ENV APP_ROOT /app ENV APACHE_DOCUMENT_ROOT /app/public RUN apt-get update && apt-get install -y \ libicu-dev \ libzip-dev \ && rm -rf /var/lib/apt/lists/* RUN NPROC=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || 1) && \ docker-php-ext-install -j${NPROC} intl opcache pdo_mysql zip RUN pecl install redis apcu xdebug && \ docker-php-ext-enable redis RUN sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' \ /etc/apache2/sites-available/*.conf RUN sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' \ /etc/apache2/apache2.conf /etc/apache2/conf-available/* COPY --from=dep /app/ ${APP_ROOT} RUN chown -R ${WWWUSER}:${WWWUSER} ${APACHE_DOCUMENT_ROOT} \ ${APP_ROOT}/storage WORKDIR ${APP_ROOT} ↩ 本番環境向けに--no-devとか--optimizeとかい れたり、拡張とかApacheの設定とか諸々加えて いくとこんな感じになりそう (.dockerignoreも使おうね!)
  20. CI/CD上の扱いを考える

  21. 暗黙的なキャッシュはローカルだけ レイヤーキャッシュもBuildKitのmountもDockerが実行されるホストに保存される リモートのCI/CDサービス上ではどうする?

  22. CI/CDのキャッシュ • レイヤーキャッシュ ◦ 有料だったりする、、、 • ファイルキャッシュ ◦ BuildKitのmountはこれでいける ◦

    Dockerのレイヤー • アーティファクト ◦ イメージやパッケージを固めて 残しておけるか? actions/cache - https://github.com/actions/cache/blob/main/examples.md#php---composer docker/build-push-action - https://github.com/docker/build-push-action/blob/master/docs/advanced/cache.md#local-cache
  23. リモートのイメージをキャッシュ利用 BuildKit(`--cache-from`) CI/CDサービスに依存しない(好き) (でもパッケージ変更時に毎回ダウンロード走るならvendorのダウンロードと変わ らなくね?)

  24. 継続的アップデートする

  25. 変更タイミング 1. ソースコードを変更した時 2. パッケージを更新した時 3. ベースイメージが更新された時 2, 3 はツール(depedabotとか)にお任せ

    Dockerfileは 1 > 2 > 3 の頻度で更新がある想定 保守フェーズに入いると頻度が 1 < 2 になってくるかも? Dockerfileも見直せる?
  26. まとめ • レイヤーキャッシュを上手に使ってさくさくビルド ◦ composerはダウンロードとスクリプト処理を分ける • マルチステージビルドで最終イメージは小さく • ファイルキャッシュも有効 •

    CI/CDではファイルキャッシュ • BuildKitでリモートのイメージをキャッシュとして使える • ツールにまかせて継続的にイメージを更新
  27. 余談 AWS LambdaでPHP扱う方法としてもMulti-stage buildが紹介されている https://aws.amazon.com/jp/builders-flash/202106/new-lambda-container-dev elopment-3/?awsf.filter-name=*all https://aws.amazon.com/jp/blogs/compute/building-php-lambda-functions-wit h-docker-container-images/ 最初のステージでよりたくさんのことをしている 1.

    PHPのインストール 2. PHPをLambdaで動かすためのruntimeの用意 3. composer requireでのパッケージ追加 composer.jsonでの依存を解決するにはレイヤー重ねるか3 stage buildにする?
  28. おしまい ご意見ご指摘お待ちしています