えいのうにっき

a-knowの日記です

Dockerfile の代わりに Packer を使って Docker に再々入門してみている

最近また、Docker に入門しなおしている。今回で3回目。Docker for Mac がずいぶん良いらしいので、Docker で Rails アプリを動かしてみた - えいのうにっき が前回入門したときの記事。

blog.a-know.me

さすがに3回目ともなると「あぁー!はいはい、そうでしたそうでした!」ということも多くて、まぁこれはこれでアリか、と思い直してみたりもしている。

今回の入門にあたっては、「プログラマのためのDocker教科書 インフラの基礎知識&コードによる環境構築の自動化」という本を使っている。Docker・コンテナのことのみに留まらず、コンテナを扱うに際しておさえておきたいインフラ・ネットワークについての話などについても触れられていて、まだ読んでる途中ではあるのだけどなかなかいい感じ。

ちなみに本の中では扱われてはいないのだけど、僕が手元で実践する際には Docker for Mac を使っている。

さて、この本を読み進めていくと「それでは実際にコンテナ(コンテナイメージ)を作ってみましょう」というところがあるのだけど、その方法は Dockerfile。それはまぁ当然って感じだし、僕自身も「郷に入っては郷に従え」的な意味でも Dockerfile で進める気まんまんだったんだけど、「本に書いてある通りのコンテナを作ってみてもつまんないなー」などと考えてしまったのが運の尽き(?)。

現在 grass-graph をホストしているサーバ構成を題材・目標にして、コンテナを作ってみようと考えた。のだけど、これを Dockerfile にするのが辛くって。普段 Chef(knife-solo)を使ってるからなおさらなんだと思う。RubyDSL で書ける Chef recipe で実施しているような各種セットアップを、Dockerfile の ADD ... RUN ... といった命令だけで再現するのは、僕には途方もない作業に思えた。

Packer よさそう

何かいい方法はないか、というところで Packer の存在を思い出した。「AMI を作るときに使うツール」みたいな雑な認識はもともと持っていて、今回も「VMに対して knife-solo でプロビジョニングしたものをそのまま docker のイメージに変換できたりしないかなー」などといった想像をしつつ、調べはじめてみた。

すると、詳しい仕組みはよくわからないのだけど、「Packer による仮想マシンやビルドの際にプロビジョニングをどのように行うかを指定できる provisioners というプロパティがある」「 provisioners にはシェルスクリプトや Ansible に加えて、Chef solo も指定できる」ということがわかった。Vagrantfile に書ける provision みたいなものっぽい。

これはもしかしてとんでもない俺得ツールなのでは、と思い、書きかけの Dockerfile を放り投げ(最低である)、Packer のインストールをはじめた。

Packer と Chef recipe を使って Docker イメージのビルドを行うまで

以下の作業は全て作業マシン・Mac上で実施している(Docker for Mac のバージョンは 17.03.0-ce-mac2 (15654) )。まずはこちらで Packer をダウンロード。

ダウンロードできるものは zip なので、解凍したものを /usr/local/bin に配置。

$ mv ~/Downloads/packer /usr/local/bin
$ packer version
Packer v0.12.3

ok!

Packer では、何を・どのような方法でビルドするかということを json 形式の設定ファイルで指定するらしい。てことで、まずは、このリポジトリで管理している Chef recipe(berkshelf(community cookbooks)は基本使ってない/ site-cookbooks ディレクトリに自作レシピを突っ込んでいる/もっぱら knife-solo で使っている)を使って「git がインストールされた Docker イメージ」を作れるかどうか、試してみる。

{
  "builders":[
    {
      "type": "docker",
      "image": "centos:centos7",
      "export_path": "image.tar"
    }
  ],
  "provisioners":[
    {
      "type": "shell",
      "inline": [
          "yum -y update",
          "yum install -y sudo which"
      ]
    },
    {
      "type": "chef-solo",
      "cookbook_paths": ["site-cookbooks"],
      "run_list": [
        "git"
      ]
    }
  ],
  "post-processors": [
    {
      "type": "docker-import",
      "repository": "a-know/a-know-server-base-container",
      "tag": "0.0.1"
    }
  ]
}

この json ファイルに関するメモは以下。

  • builders
    • typedocker にすれば docker image をビルドできる
    • image には、ベースにしたい Docker イメージを指定する。Dockerfile でいうところの FROM
      • とりあえず今のサーバが CentOS7 なので踏襲し、これをベースにすることにする。
        • CentOS に依存した書き方をしてしまっているレシピがいくつかある...。。
        • Docker に慣れたら CoreOS とかを使ってイメージのスリム化とかにも挑戦したい...。。
  • provisioners
    • Docker イメージのプロビジョニング方法を指定できる。
    • 複数の type でのプロビジョニング方法を指定することができて、今回はシェルスクリプトと chef-solo の二種類を指定している。
    • ベースとした CentOS7 の Docker image には sudo や which すら入っていなかった & さすがにそんなレシピは用意していなかったので、これらだけはシェルスクリプトで入れることにした。
    • Chef recipe を使う場合は、 typechef-solo を、cookbook_path にレシピのディレクトリを、run_list に適用したいレシピ名を、それぞれ指定する。
  • post-processors
    • 今回は docker image としてビルドしたいので docker-import とした。
    • box(virtualbox の)にもできるらしい。

ちなみに、git をインストールするためのレシピは↓こんなん。

package 'git'

もっともシンプルな部類のレシピ。

この json ファイルの内容で docker image をビルドしてみる。

$ packer build container_base.json
docker output will be in this color.

==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: centos:centos7
    docker: centos7: Pulling from library/centos

(中略)

    docker: Installed:
    docker: sudo.x86_64 0:1.8.6p7-21.el7_3            which.x86_64 0:2.20-7.el7
    docker:
    docker: Complete!
==> docker: Provisioning with chef-solo
    docker: Installing Chef...
    docker: % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
    docker: Dload  Upload   Total   Spent    Left  Speed
    docker: 100 20500  100 20500    0     0  60410      0 --:--:-- --:--:-- --:--:-- 60471
    docker: el 7 x86_64
    docker: Getting information for chef stable  for el...

(中略)

    docker: [2017-03-14T12:43:13+00:00] INFO: Loading cookbooks [git@0.1.0]
    docker: [2017-03-14T12:43:13+00:00] INFO: Storing updated cookbooks/git/recipes/default.rb in the cache.
    docker: [2017-03-14T12:43:13+00:00] INFO: Storing updated cookbooks/git/metadata.rb in the cache.
    docker: [2017-03-14T12:43:13+00:00] INFO: Processing yum_package[git] action install (git::default line 1)
    docker: [2017-03-14T12:43:14+00:00] INFO: yum_package[git] installing git-1.8.3.1-6.el7_2.1 from base repository
    docker: [2017-03-14T12:43:37+00:00] INFO: yum_package[git] installed git at 1.8.3.1-6.el7_2.1
    docker: [2017-03-14T12:43:37+00:00] INFO: Chef Run complete in 23.88294074 seconds

(中略)

==> docker: Running post-processor: docker-import
    docker (docker-import): Importing image: Container
    docker (docker-import): Repository: a-know/a-know-server-base-container:0.0.1
    docker (docker-import): Imported ID: sha256:144967a22d8c87cd233232b8533dc250660eb506d8e82fc6fa76714c6fb0bd9c
Build 'docker' finished.

==> Builds finished. The artifacts of successful builds are:
--> docker: Imported Docker image: a-know/a-know-server-base-container:0.0.1

おお。ちゃんと sudowhich 、あと Chef がインストールされて、レシピ( run_list )に指定した git もインストールされたっぽい!

$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZE
a-know/a-know-server-base-container   0.0.1               144967a22d8c        11 minutes ago      482 MB

イメージのデカさには目をつむって下さい。。

$ docker run -it 144967a22d8c /bin/bash
[root@b5d113a7be3d /]# which git
/usr/bin/git

おお。よさそう!

ちなみにここでちょっと余談なんだけど、最近の Docker のリリースによって docker コマンドの体系が一新されたらしい。上の docker images は、新しい体系だと docker image ls なんだとか。(参考:docker container / image コマンド新旧比較

で。うまく既存のレシピを使ってのイメージ作成ができそうなので、本来の目的を満たすコンテナイメージに必要そうなもの(レシピ)全てを指定してみる。timezone や crontab に関するレシピはコンテナに対して適用するものじゃなさそうなので省いた。

{
  "builders":[
    {
      "type": "docker",
      "image": "centos:centos7",
      "export_path": "image.tar"
    }
  ],
  "provisioners":[
    {
      "type": "shell",
      "inline": [
          "yum -y update",
          "yum install -y sudo which make"
      ]
    },
    {
      "type": "chef-solo",
      "cookbook_paths": ["site-cookbooks"],
      "run_list": [
        "gcc",
        "git",
        "imagemagick",
        "openssl",
        "openssl-devel",
        "patch",
        "ruby",
        "zlib::devel"
      ],
      "json": {
        "ruby": {
          "version": "2.4.0",
          "checksum": "cd0bd4e7eb8a767f44394c3cb7ebefbfb0386193898e51e533dd525da2ddcdb3"
        }
      }
    }
  ],
  "post-processors": [
    {
      "type": "docker-import",
      "repository": "a-know/a-know-server-base-container",
      "tag": "0.1.0"
    }
  ]
}

run_list の下に json っていうプロパティが増えている。これは、Chef でいうところの attributes である。

上記の json ファイルの内容で packer build してみる。

$ docker images
REPOSITORY                            TAG                 IMAGE ID            CREATED             SIZE
a-know/a-know-server-base-container   0.0.1               144967a22d8c        48 minutes ago      482 MB
a-know/a-know-server-base-container   0.1.0               9c7a8703ae10        9 minutes ago       754 MB
$ docker run -it 9c7a8703ae10 /bin/bash
[root@68aff9558c1c /]# which ruby
/usr/bin/ruby

よさそう!(754 MB...

run_list に指定するレシピも増えて、中には include_recipe を指定しているレシピもあるのだけど、それもしっかり効いているようだし。

Packer の唯一残念なところ

この時点でもう Packer 最高じゃんっていう感じなんだけど、唯一残念なところとしては、キャッシュ(スナップショット?)が効かないところ。

Dockerfile によるビルドの場合は ADD ... RUN ... といった各ステップごとにキャッシュが作られて、二回目以降のビルドでは特に変化がなければ勝手にそれが使われる。でも、Packer には今のところそれがない(多分)。

今回作ってみたイメージはそれほどストレスなく作ることができたけど、今後さらにこのイメージにあれやこれやを付け加えていくとなると、ビルド待ちの時間も馬鹿にならなくなる。となると、今後の作業はこのイメージをベースとして・"image": "a-know/a-know-server-base-container" みたいに指定できるとよさそう。

それをするには、自家製イメージをどこかに上げる必要がありそう。となると、Docker Hub か...、、と思ったのだけど、環境変数で指定してるようなヒミツな credential を含むイメージも今後作っていくことになりそうなことを考えると、「多少のお金は払ってでもプライベートレジストリがいい」「自身のインフラ環境を AWS 化させる(という今年の目標)」という2点から、Amazon ECR の利用が選択肢に上がってきた。

ってことで、Packer への入門はこれくらいにして、今度は Amazon ECR にも入門してみることにする。この時点でもう本の内容はそっちのけである...。。w

まとめ

f:id:a-know:20170316091243p:plain

可愛いすぎるでしょ

参考

follow us in feedly