Heroku Logo

この記事は heroku Advent Calendar 2018 の 21 日目の記事です。

20 日目は @pukka さんの『【Heroku検討者向け】デプロイ方法5選! 』でした。(4)マニフェストでの heroku.yml の使い方は初めて知りました。デプロイした ところ、heroku18 Stack の Docker Image が使われているようでした。Slug を作る従来の非 Docker デプロイも Docker に寄せられていくのかな?

今日はそんな Slug に注目した記事をお届けします。

Slug とは

みなさん、Heroku の Slug はご存知でしょうか?

ダッシュボードの Settings タブからサイズを確認できます。

Heroku Slug Dyno

でもこれしか情報がないのですよね。恥ずかしがり屋さんかな?

Slug Compiler のドキュメントによると、

Slugs are compressed and pre-packaged copies of your application optimized for distribution to the dyno manager . When you git push to Heroku, your code is received by the slug compiler which transforms your repository into a slug. Scaling an application then downloads and expands the slug to a dyno for execution.

だそうです。git push すると Slug Compiler によって作られるみたいです。

自分なりの解釈を図にしてみました。

Heroku Slug Compiler

Heroku では Dyno という軽量コンテナ上で、アプリケーションが動作します。アプリケーションは ↑ のように Slug というパッケージに固められます。Slug にはアプリケーションコードの他に Buildpack や、Ruby なら bundle install されたライブラリ等が含められています。Dyno が起動する時は Slug がダウンロードされ、展開して使われます。

という理解です。

Slug が何者かは分かりました。では実際にダウンロードして確認してみましょう。

Slug をダウンロードしてみよう

Slug Info API を使うとダウンロードできます。APP_NAME はご自分の Heroku App に置き換えてください。

$ APP_NAME=github-organization-watcher
$ LATEST_SLUG_ID=$(heroku releases --json -a $APP_NAME | jq -r '.[0].slug.id')
$ SLUG_URL=$(curl -n https://api.heroku.com/apps/${APP_NAME}/slugs/${LATEST_SLUG_ID} \
  -H "Accept: application/vnd.heroku+json; version=3" | jq -r '.blob.url')
$ curl -Lo ${LATEST_SLUG_ID}.tar.gz $SLUG_URL
$ tar xzf ${LATEST_SLUG_ID}.tar.gz

heroku run と同じ光景が広がっていますね。

$ tree -L 2 .
.
├── 50d9bdde-3ea4-42c0-8b05-d063164c0081.tar.gz
└── app
    ├── Dockerfile
    ├── Gemfile
    ├── Gemfile.lock
    ├── Procfile
    ├── README.md
    ├── Rakefile
    ├── app
    ├── app.json
    ├── bin
    ├── config
    ├── config.ru
    ├── db
    ├── docker-compose.yml
    ├── lib
    ├── log
    ├── public
    ├── spec
    ├── tmp
    └── vendor

11 directories, 10 files

Heroku slugs プラグイン を使うともっと簡単です。

$ heroku plugins:install heroku-slugs
$ APP_NAME=github-organization-watcher
$ heroku slugs:download -a $APP_NAME

Slug を手動で作ってリリースしてみよう

前述のとおり、Slug は Slug Compiler が作ってくれます。ですが、Heroku Dev Center > Creating Slugs from Scratch に従えば、自分で作ることが出来ます。

Ruby の場合だとこんな感じです。Node.js と Go の例もあります。

  1. ruby buildpack をダウンロードして展開する
  2. server.rb を追加して、tar で固め直す。実はこの tar ball がもう Slug
  3. Heroku App を作る
  4. Slug Create API を使って、Heroku App に Slug アップロード用の箱を作る
  5. 4 で PUT 用の URL が返るので、2 の tar ball をアップロードする
  6. Release Create API を使って、手動でリリースする

4 で process_types を指定することにより、Procfile を使わないのは面白いと思いました。

あと、curl の -n オプションを初めて知りました。

-n, –netrc Must read .netrc for user name and password

Heroku CLI と同様、認証のために ~/.netrc を参照します。Heroku CLI と curl は相性が良かったのですね・・・!

Slug のサイズを減らしてみよう

現在の上限は 500MB なのでほとんどのアプリは下回るはずですが、300MB を超えると Heroku は警告を出すそうです。Dyno の起動時間が遅くなる可能性があるので、サイズを減らすと良いと思います。

1 つは単純に Slug に含めるファイルを減らすことです。git リポジトリから不要なファイルを削除したり、Slug に含めたくないファイルを .slugignore に書くことでサイズを減らすことが出来ます。

もう 1 つは heroku-repo プラグイン を使う方法です。前述の図にあるキャッシュのサイズが減るため、Slug サイズも減ります。

$ heroku plugins:install heroku-repo
$ heroku repo:purge_cache -a $APP_NAME
$ git commit --allow-empty -m 'Decrease slug size'
$ git push heroku master

こんな感じにキャッシュサイズを減らしています。
https://github.com/heroku/heroku-repo/blob/v1.0.13/commands/purge_cache.js

  1. GET https://api.heroku.com/apps/{app-name}/build-metadata して、キャッシュ GET 用 URL を取得する
  2. ダウンロードすると tar ball が得られる
  3. heruku run して、ダウンロードした tar ball の中から vendor/heroku 以外をすべて削除する
  4. tar ball に固めて、1 で得られた PUT 用 URL にアップロードする
  5. 次回のデプロイでは新規に bundle install 等行われる

ちなみに 1 の API は Platform API Reference に載っていません。

呼び出し箇所はここです。
https://github.com/heroku/heroku-repo/blob/v1.0.13/lib/repo.js#L6-L9

こんなレスポンスです。Heroku は AWS 上に構築されていることがよく分かります。

$ curl -n https://api.heroku.com/apps/{app-name}/build-metadata \
-H "Accept: application/vnd.heroku+json; version=3.build-metadata"
{
  "app":{
    "id":"<UUID>",
    "name":"{app-name}"
  },
  "cache_delete_url":"<CACHE_URL>",
  "cache_get_url":"<CACHE_URL>",
  "cache_put_url":"<CACHE_URL>",
  "repo_delete_url":"<REPO_URL>",
  "repo_get_url":"<REPO_URL>",
  "repo_put_url":"<REPO_URL>"
}

# <CACHE_URL>
# https://s3-external-1.amazonaws.com/heroku_repos/heroku.com/cache/<NUMBERS>.tgz?AWSAccessKeyId=<SECRET>&Signature=<SECRET>&Expires=<NUMBERS>

# <REPO_URL>
# https://s3-external-1.amazonaws.com/heroku_repos/heroku.com/<NUMBERS>.tgz?AWSAccessKeyId=<SECRET>&Signature=<SECRET>&Expires=<NUMBERS>

何がキャッシュされているか

Ruby だったらこんな感じです。

$ tree -L 5 .
.
├── heroku-18
│   └── vendor
│       └── bundle
│           ├── bin
│           │   ├── bundler
│           │   ├── erubis
│           │   ├── nokogiri
│           │   ├── puma
│           │   ├── pumactl
│           │   ├── rackup
│           │   ├── rails
│           │   ├── rake
│           │   ├── sass
│           │   ├── sass-convert
│           │   ├── scss
│           │   ├── slimrb
│           │   ├── sprockets
│           │   ├── thor
│           │   └── tilt
│           └── ruby
│               └── 2.4.0
├── public
│   └── assets
│       ├── application-2669f77215d52f68e22d0c5cf91d20a0833601d2eb3185776f03a6f59d7c3d2e.js
│       ├── application-2669f77215d52f68e22d0c5cf91d20a0833601d2eb3185776f03a6f59d7c3d2e.js.gz
│       ├── application-2bee43f63707bcd2903a160f8816cb729632b6d80a17add90f928f975b446312.css
│       └── application-2bee43f63707bcd2903a160f8816cb729632b6d80a17add90f928f975b446312.css.gz
├── tmp
│   └── cache
│       └── assets
│           └── sprockets
│               └── v3.0
└── vendor
    └── heroku
        ├── buildpack_ruby_version
        ├── buildpack_version
        ├── bundler_version
        ├── ruby_version
        ├── rubygems_version
        ├── secret_key_base
        └── stack

↑ は Rails アプリです。rails assets:precompile は毎回フルビルドしますが、Heroku は tmp/cache/assets をキャッシュすることで、ビルドを高速化しています。

Heroku Dev Center > Rails 4+ Asset Pipeline on Heroku > Caching

tmp/cache/assets は 50MB までキャッシュされます。50MB というのはストレージのブロックサイズを考慮しない、実際のサイズらしいです。GNU du だと –apparent-size オプションで分かります。

キャッシュサイズ削減で登場した vendor/heroku 以下のファイルはこんな感じです。メタデータですね。

ファイル名 ファイルの中身
buildpack_ruby_version ruby-2.4.5
buildpack_version v196
bundler_version 1.15.2
ruby_version ruby 2.4.5p335 (2018-10-18 revision 65137) [x86_64-linux]
rubygems_version 2.6.14.3
secret_key_base [SECRET]
stack heroku-18

リポジトリサイズも減らしてみよう

heroku-repo プラグインを使うと、前述の図にあるリポジトリサイズも減らすことが出来ます。

$ heroku repo:gc -a $APP_NAME

ただし、Slug には .git ディレクトリは含まれないため、Slug サイズは減りません。Slug のコンパイル時間が減るでしょう。

Docker デプロイ使っていると、Slug はどうなる?

[2018-11-21-1] でも紹介した Docker デプロイでは、Slug はどうなるでしょう?

$ heroku releases --json -a aqueous-everglades-51851 | jq -r '.[0]'
{
  (snip)
  "slug": null,
  (snip)
}

そりゃそうかあー。ダッシュボード上だとこのように見えます。

Heroku Slug Container

まとめ

Slug をダウンロードしたり、自分で作ったり、サイズを減らしたりすることで、グッと身近に感じられました。もう友達ですね!

明日の heroku Advent Calendar 2018@sho7650 さんが Private Space について書くようです。Private Space は全く使ったことがないので、楽しみです。