読者です 読者をやめる 読者になる 読者になる

DRYな備忘録

Don't Repeat Yourself.

herokuで自作buildpackを作った時に得た知見

かつてこれ↓を書いたときからまたHerokuは状況が変わってて、

otiai10.hatenablog.com

今では、buildpackっていう、いうなればインスタンスの初期化スクリプトみたいなのを定義できるっぽい。そこで依存するパッケージのインストール(に相当すること)も可能っぽい。

今回の最終目標は「Goが動いて、なおかつtesseract-ocrC++バイナリとヘッダファイルがある環境をつくる」なのだけれど、まずはミニマムなherokuのbuildpackを自作してみようということになりました。

ゴール

  • HerokuでGoのアプリケーションが動く
  • 自作buildpackにおいて定義した環境変数にGoのアプリケーションがアクセスできる

tl;dr

  1. [heroku/go] 依存パッケージはGodepsなどで管理されている必要がある
  2. [heroku] app.jsonはどうやらDeploy to Herokuボタンなどの時に必要なのであって、heroku createとpushでは用いられない
    1. したがって、heroku createでbuildpackを検証するには別途heroku buildpacks:addなどを用いる必要がある
  3. [heroku-buildpack] buildpacksのフォーマットは、detect,compile,releaseの3段階(物理的にはファイル)を満たしていればよい
    1. detectexit 0
    2. compileは出力に制限は無い
    3. releaseはvalidなyaml形式の出力を標準出力に吐く必要がある
  4. [heroku-buildpack] buildpackのdetect/compile/releaseはあるものの、環境変数を吐くスクリプト.profile.d/*.shであり、compileなどで.profile.d/foo.shexportなどを書き込んでやる必要がある

まず簡単なGoのサーバアプリケーションを作る

package main

import (
    "net/http"
    "os"

    m "github.com/otiai10/marmoset"
)

func main() {

    router := m.NewRouter()
    router.GET("/", func(w http.ResponseWriter, req *http.Request) {
        m.RenderJSON(w, http.StatusOK, m.P{
            "message": "Hello!",
            "FOO":     os.Getenv("FOO"), // ここにbuildpackで定義した環境変数出したいな
            "BAR":     os.Getenv("BAR"),
        })
    })

    // アプリケーションのポートはherokuから環境変数"PORT"として与えられる
    port := os.Getenv("PORT")
    http.ListenAndServe(":"+port, router)
}

で、プロジェクトのルートディレクトリでもってgodep saveを一発キメて、以下のような構成にします。

heroku-simple-app-go
├── Godeps
│   ├── Godeps.json
│   └── Readme
├── Procfile
├── main.go
└── vendor
    └── github.com
        └── 以下割愛

github.com

この状態で、

% heroku create
% git push heroku master
% heroku open

とすると、このGoのアプリケーションが動いていることが確認できる。

自作buildpackをつくる

devcenter.heroku.com

buildpackを配置するレポジトリは、とりあえず以下の様な構成になっていれば十分。

.
└── bin
      ├── detect
      ├── compile
      └── release
# permissionはすべて755

detect

#!/usr/bin/env bash
echo "This is detect of empty buildpatck."
# exit 0 すればdetectは成功判定
exit 0

compile

#!/usr/bin/env bash
echo "-----> [Bla Bla Bla Bla]"

# .profile.d以下に.shファイルを作ってあげる
mkdir -p $1/.profile.d
ENVVARS=$1/.profile.d/build-env.sh

# exportなどのスクリプトは.profile.d以下の
# その.shファイルに記述されている必要がある
echo "export FOO=\"Hey, this variable is set by buildpack!!\"" >> $ENVVARS

release

#!/usr/bin/env bash

cat <<EOF
config_vars:
  BAR: "This is bar"
EOF
# これでBARがexportできてるのかなーと思ったが
# 世の中そんなに甘くないようだ

できたもの

github.com

アプリケーションからこのbuildpackを使うようにする

1. 今からheroku createする場合

heroku create--buildpackを与えればよい

% heroku create --buidpack https://github.com/otiai10/heroku-buildpack-tesseract-devel.git

2. すでにheroku createしている場合

heroku buildpacks:addというのがある

% heroku buildpacks:add https://github.com/otiai10/heroku-buildpack-tesseract-devel.git

3. "Deploy to Heroku"ボタンで使いたい場合 ← これ

app.jsonbuildpacksっていうフィールドがある

{
  ...nameとかそういうの書く...,
  "buildpacks": [
    {
      "url": "https://github.com/otiai10/heroku-buildpack-tesseract-devel.git"
    }
  ]
}

これを書くと、

Deploy

これ押した時のテンプレートにこのbuildpackを使うように指定されることになる。

Deployしてみた

デプロイのログが

-----> This is detect of empty buildpatck. app detected
-----> This is empty build pack
/tmp/build_f6ebcf6d3e5e0e1a26460e09fcd1ca7a
/app/tmp/cache
/tmp/d20161104-61-gu6qsi
export FOO="Hey, this variable is set by buildpack!!"
-----> Go app detected
-----> Checking Godeps/Godeps.json file.
-----> Using go1.7.3
 !!    Installing package '.' (default)
 !!    
 !!    
-----> Running: go install -v -tags heroku . 
github.com/otiai10/heroku-simple-app/vendor/github.com/otiai10/marmoset
github.com/otiai10/heroku-simple-app
-----> Discovering process types
       Procfile declares types -> web
-----> Compressing...
       Done: 2.6M
-----> Launching...
       Released v4
       https://test-foobar.herokuapp.com/ deployed to Heroku

で良い感じに動いてる。heroku openしてみると

f:id:otiai10:20161105092816p:plain

自作のbuildpackがアプリケーションのビルドの前で実行されており、buildpackによって宣言された環境変数をアプリケーションで参照できていることが確認できた。

雑感

  • 次回は、buildpackでC++のライブラリとヘッダファイルを配置し、アプリケーションからそれを参照したい

DRYな備忘録として