alpine linuxベースのdocker imageに移行したはなし

f:id:vasilyjp:20180927112657j:plain

こんにちは、神崎(@tknzk)です。ElasticBeanstalk w/ multi-container Docker で構成しているad-serverのdocker image を alpine linuxベースのimageに置き換えました。

alpine linuxは、非常に軽量なdistributionで、DockerHubに登録されているmiddlewareなどの公式のdocker imageでも採用が進んでいるOSです。

http://www.alpinelinux.org/ f:id:tknzk:20160624101417p:plain

以前のブログにも書いたとおり、ad-serverは ElasticBeanstalkで管理された multi-containerなdockerでクラスタを組んで、アプリケーションを稼働させています。その構成は、下記のようになっています。

  • 本体のアプリケーションがはいったContainer (ad-server)
  • webのリクエストを受け付けるためのnginx
  • logコレクタとしてのtd-agent
  • 監視用のmackerel-agent

とあるタイミングの docker imageのサイズは下記のようになっており、docker imageの肥大化がすすんでいました。

image size base os
ad_server 924.6MB centos:6
nginx 134.1MB debian:jessie
td-agent 448.4MB centos:6
mackerel-agent 423.1MB ubuntu:14.04

肥大化を抑制するための方針として、できるだけ軽量なOSをベースにすること、不必要なパッケージをインストールしないことやbuildするときにだけ必要なパッケージを適宜削除することとして、imageを作成することにしました。

nginx

まずは、オフィシャルのimageが対応していたnginxをalpineベースのものに変更しました。 Dockerfileは下記のようになり、FROMとしてオフィシャルのalpineベースのものを指定しています。

FROM nginx:1.11.1-alpine
MAINTAINER Takumi Kanzaki

COPY nginx.conf /etc/nginx/nginx.conf

ad-server

ad-sereverはベースとなるruby, supervisord, mysqlをbuildしたimageに ad-server として必要なGemをinstallする imageをbuildするという構成になっていました。alpineをベースにするにあたり、下記のような調整を行いました。

  • 前段のベースとなるdocker image

    • ruby
      • buildに必要なpackageはtemporaryとしてinstallしてuninstall
    • supervisord
  • Dockerfile

FROM alpine:3.4

ENV HOME     /root
WORKDIR /tmp

# skip installing gem documentation
RUN mkdir -p /usr/local/etc \
  && { \
    echo 'install: --no-document'; \
    echo 'update: --no-document'; \
  } >> /usr/local/etc/gemrc

# versions
ENV RUBY_MAJOR 2.3
ENV RUBY_VERSION 2.3.1
ENV RUBY_DOWNLOAD_SHA256 b87c738cb2032bf4920fef8e3864dc5cf8eae9d89d8d523ce0236945c5797dcd
ENV RUBYGEMS_VERSION 2.6.3
ENV BUNDLER_VERSION 1.12.5

# some of ruby's build scripts are written in ruby
# we purge this later to make sure our final image uses what we just built
RUN set -ex \
  && apk add --no-cache --virtual .ruby-builddeps \
    autoconf \
    bison \
    bzip2 \
    bzip2-dev \
    ca-certificates \
    coreutils \
    curl \
    gcc \
    gdbm-dev \
    glib-dev \
    libc-dev \
    libffi-dev \
    libxml2-dev \
    libxslt-dev \
    linux-headers \
    make \
    ncurses-dev \
    openssl-dev \
    procps \
# https://bugs.ruby-lang.org/issues/11869 and https://github.com/docker-library/ruby/issues/75
    readline-dev \
    ruby \
    yaml-dev \
    zlib-dev \
  && curl -fSL -o ruby.tar.gz "http://cache.ruby-lang.org/pub/ruby/$RUBY_MAJOR/ruby-$RUBY_VERSION.tar.gz" \
  && echo "$RUBY_DOWNLOAD_SHA256 *ruby.tar.gz" | sha256sum -c - \
  && mkdir -p /usr/src \
  && tar -xzf ruby.tar.gz -C /usr/src \
  && mv "/usr/src/ruby-$RUBY_VERSION" /usr/src/ruby \
  && rm ruby.tar.gz \
  && cd /usr/src/ruby \
  && { echo '#define ENABLE_PATH_CHECK 0'; echo; cat file.c; } > file.c.new && mv file.c.new file.c \
  && autoconf \
  # the configure script does not detect isnan/isinf as macros
  && ac_cv_func_isnan=yes ac_cv_func_isinf=yes \
    ./configure --disable-install-doc \
  && make -j"$(getconf _NPROCESSORS_ONLN)" \
  && make install \
  && runDeps="$( \
    scanelf --needed --nobanner --recursive /usr/local \
      | awk '{ gsub(/,/, "\nso:", $2); print "so:" $2 }' \
      | sort -u \
      | xargs -r apk info --installed \
      | sort -u \
  )" \
  && apk add --virtual .ruby-rundeps $runDeps \
    bzip2 \
    ca-certificates \
    curl \
    libffi-dev \
    openssl-dev \
    yaml-dev \
    procps \
    zlib-dev \
  && apk del .ruby-builddeps \
  && gem update --system $RUBYGEMS_VERSION \
  && rm -r /usr/src/ruby

# SETUP pip supervisord
RUN apk add --virtual .supervisord-deps --update \
  python \
  py-pip && \
   pip install -q --upgrade "meld3==1.0.0" "supervisor" 2> /dev/null

# SETUP bundler
RUN gem install bundler --version "$BUNDLER_VERSION"

# SETUP ssl certificatate file
RUN ln -s /etc/ssl/certs/ca-certificates.crt /etc/ssl/cert.pem
  • 後段のad-serverのアプリケーション用の docker image

    • Gemのinstall
      • native extension の build に必要なpackage を install/uninstall
    • mysqlの必要ものだけ残して不要なバイナリは削除
  • Dockerfile

#vim: set ft=ruby
FROM quay.io/vasilyjp/ruby:2.3.1-alpine_3_4-build

ENV LANG ja_JP.UTF-8

# --- SETUP: rubygems ---
ADD Gemfile /tmp/Gemfile
ADD Gemfile.lock /tmp/Gemfile.lock
ENV GEM_HOME /tmp/ad_server/bundle


# SETUP middleware deps
# build-base     : native exetension build
# libgsasl       : gem memcached
# cyrus-sasl-dev : gem memcached
# mariadb-dev    : gem mysql2
# linux-headers  : gem raindrops
RUN apk add --virtual .middleware-deps --update \
      mariadb-dev \
      libgsasl \
      cyrus-sasl-dev && \
    apk add --virtual .gem-build-deps --update \
      build-base \
      linux-headers && \
    cd /tmp && \
      bundle install --clean --jobs=4 && \
    apk del .gem-build-deps && \
    rm /usr/lib/libmysqld* && \
    rm /usr/bin/mysql*

# すべての.bundle/configを無効化して、環境変数によって設定を反映させる
ENV BUNDLE_IGNORE_CONFIG 1
ENV BUNDLE_GEMFILE /var/app/Gemfile
ENV BUNDLE_DISABLE_SHARED_GEMS 1
ENV BUNDLE_JOBS 4
ENV BUNDLE_PATH /tmp/ad_server/bundle

VOLUME /var/app
WORKDIR /var/app

EXPOSE 3000

CMD ["supervisord"]

td-agent

alpineをベースにすることを検討しましたが、td-agentのbuildが難しく断念しましたが、CentOS:7 にすることで、多少のimage sizeの削減ができました。

# vim: ft=Dockerfile
FROM centos:7

ADD td.repo /etc/yum.repos.d/treasuredata.repo

RUN rpm --import https://packages.treasuredata.com/GPG-KEY-td-agent && \
    yum -q -y install --enablerepo=treasuredata td-agent && \
    yum update -q -y \
      nss-tools \
      nss-util \
      nss-softokn-freebl \
      nss-softokn \
      nss \
      bind \
      bind-libs \
      bind-utils \
      openldap \
      libuser \
      pam \
      libssh2 \
      libxml2 \
      openssl \
      sqlite && \
    yum clean all

CMD [ "td-agent", "-c", "/etc/td-agent/td-agent.conf", "--use-v1-config" ]

mackerel-agent

aplineベースでmackerel-agent, mackerel-agent-plugin, check-plugins をbuildするものを作成しました。 pull request を投げていますが、コメントにも書いてる通り、alpineのバグがあり一部のpluginが動かない状態です。 alpineで動かすのは厳しいことから、ubuntuベースで 不要なmackerel-agent-pluginを削除し、check-pluginは利用していないのでinstall自体をやめることにして、sizeの削減を行いました。

FROM ubuntu:14.04

# setup mackerel-agent
RUN apt-get update \
  && apt-get -y install curl sudo ruby docker.io \
  && curl -fsSL https://mackerel.io/assets/files/scripts/setup-apt.sh | sh \
  && apt-get update \
  && apt-get -y install mackerel-agent mackerel-agent-plugins \
  && apt-get clean \
  && rm -rf /usr/bin/mackerel-plugin-apache2 \
  && rm -rf /usr/bin/mackerel-plugin-conntrack \
  && rm -rf /usr/bin/mackerel-plugin-elasticsearch \
  && rm -rf /usr/bin/mackerel-plugin-gostats \
  && rm -rf /usr/bin/mackerel-plugin-haproxy \
  && rm -rf /usr/bin/mackerel-plugin-jmx-jolokia \
  && rm -rf /usr/bin/mackerel-plugin-jvm \
  && rm -rf /usr/bin/mackerel-plugin-mailq \
  && rm -rf /usr/bin/mackerel-plugin-munin \
  && rm -rf /usr/bin/mackerel-plugin-php-apc \
  && rm -rf /usr/bin/mackerel-plugin-php-opcache \
  && rm -rf /usr/bin/mackerel-plugin-plack \
  && rm -rf /usr/bin/mackerel-plugin-postgres \
  && rm -rf /usr/bin/mackerel-plugin-rabbitmq \
  && rm -rf /usr/bin/mackerel-plugin-snmp \
  && rm -rf /usr/bin/mackerel-plugin-squid \
  && rm -rf /usr/bin/mackerel-plugin-td-table-count \
  && rm -rf /usr/bin/mackerel-plugin-trafficserver \
  && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ADD startup.sh /startup.sh
RUN chmod 755 /startup.sh

# boot mackerel-agent
CMD ["/startup.sh"]

現在のproduction環境の docker images

上記のように、ベースのOSを変更したり、 Dockerfileを工夫したりをして、docker imageのsizeを削減することができました。現在のproduction環境で動かしているimageの一覧は下記の通りです。

image size base os
ad_server 342.7MB alpine:3.4
nginx 59.63MB alpine:3.4
td-agent 430.8MB centos:7
mackerel-agent 357.5MB ubuntu:14.04
[ec2-user@ip-xx-xx-xx-xxx ~]$ sudo docker images
REPOSITORY                        TAG                                        IMAGE ID            CREATED             VIRTUAL SIZE
quay.io/vasilyjp/mackerel-agent   0.31.1-ubuntu-14_04_20160617               32eca0f192e6        4 days ago          357.5 MB
quay.io/vasilyjp/ad_server        9c2aa373ff9f9c28aa162207b1c4511eb2dacf47   4d76a297c1d0        4 days ago          342.7 MB
quay.io/vasilyjp/nginx            1.11.1-alpine_3_4-build                    7a4a5e149521        8 days ago          59.63 MB
quay.io/vasilyjp/td-agent         0.12.20-centos7                            fa60e55fb621        4 weeks ago         430.8 MB
amazon/amazon-ecs-agent           latest                                     46e05d110968        5 months ago        9.097 MB

まとめ

すべてのdocker imageが450MB以下になり、トータルでは既存の6割程度のサイズに落とすことができました。 本当に必要なものだけを指定して構築することで、不要な物がなくなり、セキュリティ的にも安心できる構成が取れたかと思います。 mackerel-agentのところでも触れたように、alpineは少しbuggyなところもありますが、4月末からad-serverのproduction環境に投入し、先日3.4系への移行も行いましたが特に問題なく稼働できています。

最後に

VASILYでは、一緒に開発をしてくれる仲間を募集しています。 Dockerを使った開発/運用をしてみたい方は以下のリンクをご確認ください!

カテゴリー