heroku-buildpack-monorepoでちょっとハマった話

こんにちは、エンジニアの @akase244 です。

最近、Heroku(読み方は「ヘロク」)を触る機会があり、ちょっとハマったので今回はその話を書いてみようと思います。

久しぶりなHeroku環境

「Herokuとは」でググると山程有用な記事が見つかるので説明についてはそちらにお譲りしますが、いわゆるPaaSと呼ばれるものでインフラ環境をいい感じに準備してくれてアプリ開発に集中させてくれる頼れるやつです。

私がHerokuを触っていたのは3年ほど前で、Hubotを使ったSlackボットをHeroku環境で動かすといったことを趣味でやってたんですが、今回仕事で初めてプロダクション環境を構築する機会を得ました。

そうそう、3年前といえば当社のCTOがPHPカンファレンス福岡2015でHerokuについて発表したスライドをアップしてくれているので、そちらも参考になるかと思います。

Heroku buildpackとは

Herokuでアプリケーション環境を構築する際になくてはならない存在がHeroku buildpackです。 このHeroku buildpackを利用することで、アプリケーション環境を簡単に準備できたり、様々なカスタマイズを可能にしてくれます。

f:id:akase244:20181021132905p:plain:w500
buildpackの追加画面

heroku-buildpack-monorepoとは

今回Heroku環境にデプロイしたいのは以下のようにGitのルートディレクトリ直下ではなく、srcディレクトリ直下にあるLaravelのアプリケーションが対象です。

Gitのルートディレクトリ

$ tree ./ -a -L 1
./
├── .git
├── .gitignore
├── README.md
├── docker
├── docker-compose.yml
└── src

デプロイ対象のsrcディレクトリ(Laravelのアプリケーションディレクトリ)

$ tree ./src/ -a -L 1
./src/
├── .gitignore
├── Procfile
├── app
├── artisan
├── bootstrap
├── composer.json
├── composer.lock
├── config
├── database
├── heroku
├── package-lock.json
├── package.json
├── phpunit.xml
├── public
├── readme.md
├── resources
├── routes
├── server.php
├── storage
├── tests
└── webpack.config.js

こういったサブディレクトリをデプロイ対象として選択できるようにしてくれるのがheroku-buildpack-monorepoというbuildpackです。(※heroku-buildpack-monorepo以外にもいくつか同じ機能のbuildpackがあるようです)

環境構築(失敗編)

さて、ここからはheroku-buildpack-monorepoを利用した環境構築時のコマンドを見ながら、実際にデプロイ時にハマったところを再現してみます。

Heroku CLIについては事前にインストールしておきましょう。
※今回の記事内でDB接続部分については特に説明しないこととします。

Herokuにコマンドラインからログイン。

$ heroku login
heroku: Enter your login credentials
Email: ◯◯◯@innovator.jp.net
Password: **********
Logged in as ◯◯◯@innovator.jp.net

アプリケーションの作成。

$ heroku create アプリ名
Creating ⬢ アプリ名... done
https://アプリ名.herokuapp.com/ | https://git.heroku.com/アプリ名.git

今回はPHP製のフレームワークであるLaravelのアプリケーションがデプロイ対象なのでHerokuでPHPを利用可能にするためのbuildpackを追加。

$ heroku buildpacks:add heroku/php -a アプリ名
Buildpack added. Next release on アプリ名 will use heroku/php.
Run git push heroku master to create a new release using this buildpack.

先程説明したとおり、srcディレクトリ直下をデプロイしたいのでheroku-buildpack-monorepoを追加。

$ heroku buildpacks:add https://github.com/lstoll/heroku-buildpack-monorepo -a アプリ名
Buildpack added. Next release on アプリ名 will use:
  1. heroku/php
  2. https://github.com/lstoll/heroku-buildpack-monorepo
Run git push heroku master to create a new release using these buildpacks.

buildpackのインストール状況を確認。

$ heroku buildpacks -a アプリ名
=== アプリ名 Buildpack URLs
1. heroku/php
2. https://github.com/lstoll/heroku-buildpack-monorepo

heroku-buildpack-monorepoを利用してどのディレクトリをデプロイするかをHerokuに教えてあげるためにAPP_BASEという環境変数をセット。

$ heroku config:add APP_BASE=src -a アプリ名
Setting APP_BASE and restarting ⬢ アプリ名... done, v3
APP_BASE: src

環境変数の設定状況を確認。

$ heroku config -a アプリ名
=== アプリ名 Config Vars
APP_BASE: src

デプロイのためにリモートリポジトリを追加。
※「heroku create」コマンド実行時に表示されるHerokuのGitのURLを指定します。

$ git remote add アプリ名 https://git.heroku.com/アプリ名.git

なお、↑この操作は「heroku create」時に「--remote」をつけることで省略可能です。

$ heroku create アプリ名 --remote アプリ名

あと、事前にProcfileを準備しておく必要がありますので、srcディレクトリ直下に以下のような感じで作成しておきます。

$ cat src/Procfile 
web: vendor/bin/heroku-php-nginx public/

はい、これで失敗する準備が整いましたのでデプロイしてみましょう。

$ git push アプリ名 master
Counting objects: 2030, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1023/1023), done.
Writing objects: 100% (2030/2030), 36.78 MiB | 3.85 MiB/s, done.
Total 2030 (delta 1117), reused 1559 (delta 835)
remote: Compressing source files... done.
remote: Building source:
remote: 
remote: -----> App not compatible with buildpack: https://buildpack-registry.s3.amazonaws.com/buildpacks/heroku/php.tgz
remote:        
remote:  !     ERROR: Application not supported by this buildpack!
remote:  !     
remote:  !     The 'heroku/php' buildpack is set on this application, but was
remote:  !     unable to detect a PHP codebase.
remote:  !     
remote:  !     A PHP app on Heroku requires a 'composer.json' at the root of
remote:  !     the directory structure, or an 'index.php' for legacy behavior.
remote:  !     
remote:  !     If you are trying to deploy a PHP application, ensure that one
remote:  !     of these files is present at the top level directory.
remote:  !     
remote:  !     If you are trying to deploy an application written in another
remote:  !     language, you need to change the list of buildpacks set on your
remote:  !     Heroku app using the 'heroku buildpacks' command.
remote:  !     
remote:  !     For more information, refer to the following documentation:
remote:  !     https://devcenter.heroku.com/articles/buildpacks
remote:  !     https://devcenter.heroku.com/articles/php-support#activation
remote: 
remote: 
remote:        More info: https://devcenter.heroku.com/articles/buildpacks#detection-failure
remote: 
remote:  !     Push failed
remote: Verifying deploy...
remote: 
remote: !   Push rejected to アプリ名.
remote: 
To https://git.heroku.com/アプリ名.git
 ! [remote rejected] master -> master (pre-receive hook declined)
error: failed to push some refs to 'https://git.heroku.com/アプリ名.git'

見事に失敗しました。エラーメッセージを確認するとcomposer.jsonを見つけることができず、PHPアプリケーションであることを判別できなかったようです。なぜか?

環境構築(解決編)

まず、デプロイが成功している環境のbuildpackの設定を見てみます。 f:id:akase244:20181021151236p:plain:w500

次に今回失敗している環境の設定を見てみます。パッと見は問題なさそうに思えます。。。 f:id:akase244:20181021151304p:plain:w500

ん?ちょっと待て、こいつ動くぞ???ということでピンと来ました。 f:id:akase244:20181021151447p:plain:w500

どうやらbuildpackは動作時に優先順位があるようです。
そういえば、buildpack追加時に以下のようなメッセージが表示されていましたね。つまり、この「1. heroku/php」という数字は動作時の優先順位だったわけです。

Buildpack added. Next release on アプリ名 will use:
  1. heroku/php
  2. https://github.com/lstoll/heroku-buildpack-monorepo

なので、うまく動いている環境と同じように「heroku-buildpack-monorepo」を「heroku/php」より上に持ってきます。すると「Your new buildpack configuration will be used when this app is next deployed.」というメッセージが表示されました。これでうまくいきそうです。

f:id:akase244:20181021151717p:plain:w500

それでは再度デプロイしてみましょう。

$ git push アプリ名 master
・
・
省略
・
・
remote: -----> Monorepo app detected
remote:       Copied src to root of app successfully
remote: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Installing platform packages...
remote:        - php (7.2.11)
remote:        - ext-mbstring (bundled with php)
remote:        - nginx (1.8.1)
remote:        - apache (2.4.34)
・
・
省略
・
・
remote:        Package manifest generated successfully.
・
・
省略
・
・
remote: -----> Launching...
remote:        Released v4
remote:        https://アプリ名.herokuapp.com/ deployed to Heroku
remote: 
remote: Verifying deploy... done.
To https://git.heroku.com/アプリ名.git
 * [new branch]      master -> master

今度は「Monorepo app detected」、「PHP app detected」というメッセージが表示されていますね!やはりbuildpackは動作時に設定した際の順序が関係しているようです。

まとめ(いつもの)

いかがだったでしょうか。私がHeroku環境に慣れてないことがよくわかっていただけたんじゃないかと思います。 ということで、Heroku、Laravelとか(AWS、Vue.jsもあるよ)に興味がある人がいれば、ぜひこちらまで。