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

PackerでDocker Imageを作る時に知ったこと

最近はAWSのAMIやECSでコンテナたてる時に使うDocker ImageをPackerで作成している @hatappiです
Packerを使うとAMIにしてもDocker Imageにしても自分の好きなプロビジョニングを使用することが出来るので好きです

今回は例えば元となるdocker imageにはrbenv/rbenvがインストールされており .bashrcなり/etc/profile.d/rbenv.shなりに下記のようにPATHが定義されているものがあるとします

export RBENV_ROOT=/usr/local/rbenv
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init -)"

このImageをPackerで使ってbundle installしたイメージを作るとする

bundle.manifest.json

{
  "builders":[{
    "type": "docker",
    "image": "hatappi/rbenv-app",
  }],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "bundle install --gemfile=/app/Gemfile"
      ]
    }
  ],
  "post-processors": [
    [
      {
        "type": "docker-import",
        "repository": "hatappi/hoge",
        "tag": "latest"
      }
    ]
  ]
}

これを単純に実行してしまうと bundle: command not found というエラーで起きてしまう
これは単にパスが通ってないだけなので下記のようにsourceでPATHが定義されたものを読みこんであげれば解決する

"provisioners": [
    {
      "type": "shell",
      "inline": [
        "source /etc/profile.d/rbenv.sh",
        "bundle install --gemfile=/app/Gemfile"
      ]
    }
  ],

ここからが本題でこれの別解としてPackerのオプションを利用していく

まず

   {
      "type": "shell",
      "inline": [
        "bundle install --gemfile=/app/Gemfile"
      ]
    }

を行うとPackerではどうなるかというとまずコンテナ上に/tmp/script_6464.shのような一時ファイルを作成します
中身としては

#!/bin/sh -e
bundle install --gemfile=/app/Gemfile

となっておりこれをPacker側で下記のようにdocker execを使って実行している

docker exec -i aaaaa /bin/sh -c (chmod +x /tmp/script_6464.sh; PACKER_BUILDER_TYPE='docker' PACKER_BUILD_NAME='docker'  /tmp/script_6464.sh):

ここまでくるとなんとなく見えてくる
一時ファイルとして作成されたシェルファイルの1行目のshebangを変更してあげれば良い
type: “shell"のドキュメントを見るとinline_shebangというそれらしいオプションがある!!
これを使って

   {
      "type": "shell",
      "inline": [
        "bundle install --gemfile=/app/Gemfile"
      ],
      "inline_shebang": "/bin/sh -le"
    }

こんな感じで書いてあげる
-lを追加することでログインしたときのようにbashで実行することが出来るのでPATHなども読み込まれた状態で実行することが出来る

どちらを使っても実現は出来たがPackerの挙動が知れたので良かったとします