Cloud Run(サイドカー)でnginx + php-fpmの環境を作ってCloud Build対応させたメモ

Google App Engineで動作させていたLaravelアプリケーションを、Cloud Runがサイドカーに対応したので移行してみた。ついでに頑張ってCloud Buildにも対応させたメモです。
公式ドキュメント

まず、サイドカー版Cloud Runですが、いままで1コンテナで頑張っていたときは、nginx+php-fpmだとコールドスタート時に500エラーが起きる問題がありました。今だからわかるんですがnginxが1秒くらいで起動して、php-fpmは3秒くらい起動にかかるのでその間エラーになるんですよね。
それが今回はコンテナ間の依存関係が指定できるので、php-fpmの9000番ポートの疎通が確認出来たらnginxもアクティブになる。という構成ができるようになりました。

最終的に出来上がったのはこちら。YAMLで以下のようなことが設定されている
- 利用するコンテナ定義
- インスタンス定義(gen2とか最小インスタンス、最大インスタンス)
- CloudSQLへの接続定義
- SecretManagerのファイルパスへのマウント
- それぞれのコンテナのポート
- 起動時の依存関係
envがちょろっとしか書いてないのはサンプルで、下にあるけど最終的に.envをまるっとSecretManagerにつっこんでビルド時に渡すようにしました。どうもディレクトリマウント方式だとすでにあるアプリケーションフォルダにマウントするのは難しいようで、ビルド時に吐き出しています。

nginxのコンテナはシンプル。/var/publicをドキュメントルートにして、そこに静的ファイルだけコピーしてindex.phpを消してます(雑)
FROM nginx:latest
# デフォルトのサイト設定を無効化し自前の設定をコピー
RUN mv /etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf.bak
COPY ./docker-nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./public /var/public
RUN rm -rf /var/public/*.php
# nginxユーザで実行
RUN chown -R 101:0 /var/cache/nginx \
&& chmod -R g+w /var/cache/nginx \
&& chown -R 101:0 /var/public \
&& chmod -R g+w /var/public
USER 101

php-fpmのほうもそんな難しいことをやっていませんが、apcuとsentry用にexcimerは導入しています。ポイントとしては、pathがそのまま飛んでくるので、/var/public/index.phpで受けるようにするくらいですかね。
FROM php:8.2-fpm-bullseye
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git \
zip \
unzip \
libzip-dev \
libpng-dev \
libpq-dev \
libfreetype6-dev \
libjpeg-dev \
libonig-dev \
libgmp-dev \
libmagickwand-dev \
&& pecl install imagick apcu excimer \
&& docker-php-ext-configure gd \
--with-freetype=/usr/include/ \
--with-jpeg=/usr/include \
&& docker-php-ext-install -j$(nproc) bcmath exif gmp intl gd opcache pdo pdo_mysql mysqli zip \
&& docker-php-ext-enable imagick apcu excimer \
&& docker-php-source delete \
&& rm -rf /var/lib/apt/lists/*
ADD ./docker-nginx/php.ini /usr/local/etc/php/
# 記事においてはマルチステージビルドにしてない版を載せます
ENV COMPOSER_ALLOW_SUPERUSER 1
ENV COMPOSER_HOME /composer
ENV PATH $PATH:/composer/vendor/bin
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
# nginx自身のパスが飛んでくるので、合わせる
COPY ./ /var/
WORKDIR /var
RUN composer install -o --apcu-autoloader; \
chmod -R 777 /var/storage; \
chmod -R 777 /var/bootstrap/cache;

そして、これらのコンテナをArtifact RegistryにPushします。
docker build -t asia-northeast1-docker.pkg.dev/プロジェクト名/リポジトリ名/nginx:latest
docker push asia-northeast1-docker.pkg.dev/プロジェクト名/リポジトリ名/nginx:latest
docker build -t asia-northeast1-docker.pkg.dev/プロジェクト名/リポジトリ名/php-fpm:latest
docker push asia-northeast1-docker.pkg.dev/プロジェクト名/リポジトリ名/php-fpm:latest
latestになってる部分は微妙なので後述します。
あとはデプロイするだけです!
gcloud run service replace service.yaml

さて、うまくいったらお次はCloudBuildです。ここは試行錯誤しすぎて途中のエラー回避などは忘れてしまったのですが、つまずいたのは以下のような点です。
- そもそも実行されない
- secretなどのパス記述ミスで権限がついてないけど権限がないって言われ続けた
- Pushできない
- Cloud Buildの権限不足
- Secret Managerから.envをコピーしようとするも一生入らない
- パス間違い。コピー元は / で作業してたにのコピー先に合わせて/varに設置してた。あとSecretManagerのアクセサー権限がなかった。あと""で囲ってなかったので改行コードが消えた。
- デプロイ成功するも内容が更新されない
- Cloud Runのyamlは
latest
など文字列でしか変更を見ないので、コンテナの中身が変わっていようとも同じタグでは取得しなおしてくれない
- Cloud Runのyamlは
そんなこんなで、sedを入れたり頑張りました。
steps:
# install dependency
- name: node
entrypoint: npm
args: ['install']
- name: node
entrypoint: npm
args: ['run', 'production']
# Build the nginx image(この段階で↑でビルドしたjsもコピーされる)
- name: 'gcr.io/cloud-builders/docker'
args: ['build',
'-t', 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/nginx:$COMMIT_SHA',
'-t', 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/nginx:latest',
'-f', 'Dockerfile.nginx',
'.']
# Push the nginx image to Container Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '-a', 'asia-northeast1-docker.pkg.dev/dev/リポジトリ名/nginx']
# export env from secrets
- name: 'gcr.io/cloud-builders/docker'
entrypoint: 'bash'
args: ['-c', 'echo "$$SECENV" > .env']
secretEnv: ['SECENV']
# Build the php-fpm image
- name: 'gcr.io/cloud-builders/docker'
args: ['build',
'-t', 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/php-fpm:$COMMIT_SHA',
'-t', 'asia-northeast1-docker.pkg.dev/$PROJECT_ID/リポジトリ名/php-fpm:latest',
'-f', 'Dockerfile.php-fpm',
'.']
# Push the php-fpm image to リポジトリ名 Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', '-a', 'asia-northeast1-docker.pkg.dev/dev/リポジトリ名/php-fpm']
# Replace cloudrun yaml for image tag
- name: 'gcr.io/cloud-builders/gcloud'
id: Generate manifest
entrypoint: /bin/bash
args: ['-c', 'sed -ri "s/:latest/:$COMMIT_SHA/g" service.yaml']
# Deploy リポジトリ名 image to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args: ['run', 'services', 'replace', 'service.yaml']
availableSecrets:
secretManager:
- versionName: projects/プロジェクトID/secrets/SECRET名/versions/latest
env: 'SECENV'