あすたぴのブログ

astap(あすたぴ)のブログ

CircleCI2.0でRailsアプリをdocker multi stage buildをする

※2017/05/29 時点くらいの話しです。

現在は改善されているかもしれません。

multi stage buildとは

https://speakerdeck.com/toricls/understanding-dockers-multi-stage-builds こちらが詳しい。

build環境と、実行環境を別イメージとしてbuildしたい。 それを簡単に出来るようになったよ。という話し。

なぜ別イメージにしたいかと言うと、Goの例がわかりやすいが、 Goはワンバイナリで実行可能な為、 Goをコンパイルするために必要なライブラリ等は、実行環境のイメージには必要がない。 dockerのイメージは小さいほど、(docker pull含めて)実行が早いので軽いほうがいい。

CircleCI2.0 で実行する方法

CircleCI2.0では、build時に自分で好きなDockerのvesionを使えるよ。とうたっている。 https://circleci.com/docs/2.0/building-docker-images/

      - run:
          name: Install Docker client
          command: |
            set -x
            VER="17.03.0-ce"
            curl -L -o /tmp/docker-$VER.tgz https://get.docker.com/builds/Linux/x86_64/docker-$VER.tgz
            tar -xz -C /tmp -f /tmp/docker-$VER.tgz
            mv /tmp/docker/* /usr/bin

ここで 17.03.0-ce を指定しているのですが、multi-stage buildを行うためには、17.05.0-ce が必要になる。 そのため、ここで 17.05.0-ce と指定すると、そのバージョンのdockerをinstalできる。 出来るのだが、そのバージョンは使えない。 なぜかと言うと docker は client 側と docker daemon側で2つのversionがあり、 setup_remote_docker では、docker daemon側のバージョンは 17.03.0-ce で固定されている。 daemon側が低いとそちらに合わせられるため、multi-stage buildは行うことができない。

では、どうするのが良いか。 machine実行で、自分で docker を install する。

  deploy:
    working_directory: ~/app
    machine: true
    steps:
      - checkout
      - run:
          name: update docker
          command: |
            docker version
            sudo service docker stop
            curl -fsSL https://get.docker.com/ | sudo sh
            docker version

上記では、(おそらく)最新のdockerを自分で入れている。

Client:
 Version:      17.05.0-ce
 API version:  1.29
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:06 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.05.0-ce
 API version:  1.29 (minimum version 1.12)
 Go version:   go1.7.5
 Git commit:   89658be
 Built:        Thu May  4 22:06:06 2017
 OS/Arch:      linux/amd64
 Experimental: false

build時に、debugでversionを出すとこんな感じに 17.05.0-ce が入っていることがわかる。

あとは普通に、docker imageをbuildして push したりすればよい。 雑な例を出すとこんな感じ。

      - deploy:
          name: run build & deploy
          command: |
            $(aws ecr get-login --region ap-northeast-1)
            $REPO={{ECRのリポジトリとか}}
            $PUSH_TAG=latest
            docker build -t hoge:latest .
            docker tag hoge:latest $REPO:$PUSH_TAG
            docker push $REPO

最後に

Railsでmulti-stage buildに使っているDockerfileを載せておく。 あんまり詰めれていないので、もっと良く出来るとは思う。 考えとしては、gemをbuildするために必要なものを、最初のイメージで入れて 実行に必要なものだけど後半のイメージにいれている。

FROM ruby:2.4.1-alpine as builder

RUN apk --update add --virtual build-dependencies build-base curl-dev linux-headers
RUN apk --update add mariadb-dev
RUN echo 'gem: --no-document' > /etc/gemrc

WORKDIR /app
ADD . /app
RUN bundle install --jobs=4
RUN apk del build-dependencies


FROM ruby:2.4.1-alpine

ENV LANG ja_JP.UTF-8
COPY --from=builder /usr/local/bundle /usr/local/bundle

RUN apk --update add tzdata imagemagick
RUN apk --update add mariadb-dev && rm /usr/lib/libmysqld*
RUN apk del openssl-dev mariadb-client-libs mariadb-common

ADD . /app
RUN chown -R nobody:nogroup /app
USER nobody

WORKDIR /app
EXPOSE 9292
CMD [ "bundle", "exec", "unicorn", "-c", "config/unicorn.rb" ]