映画.comにおけるDocker導入事例のご紹介

はじめに

こんにちは。映画.comのシステム開発を担当している中村です。

映画.com」は1998年にスタートした(株)エイガ・ドット・コムが運営する総合映画情報サイトです。2007年に(株)カカクコムのグループサイトに入り、さまざまな機能やサービスを追加しながら16年が経過しました。現在は、映画.com以外にも、アニメの総合情報サイト「アニメハック」、ホラー映画専門配信サービス「オソレゾーン」、オンライン試写サービス「スクリーニングマスター」、映画のオンライン配信サービス「シネマ映画.com」などを運営しています。

ここでは、2020年に映画.comの開発環境をDocker環境に移行した経緯と、より開発現場のニーズに即した環境構築のコツを説明いたします。

Docker導入までの経緯

弊社ではセキュリティの観点から、仮想マシンも含めてローカル端末に開発環境を構築していません。原則としてリモートサーバー上に開発環境を構築し、そこで開発作業を行なうことになります。

当初、映画.comの開発メンバーは少人数であったため、以下のような運用ルールで開発作業をやり繰りしていました。

  • 開発環境はリモートの開発サーバー1つ。
  • 各開発メンバーは開発サーバー上のホームディレクトリにリポジトリの作業コピーを置く。
  • 開発用のWebサーバーへはメンバーごとにポートを分けてアクセスする。
  • 開発用のDBサーバーへはメンバーごとにスキーマを作成してアクセスする。

この運用ルールには開発のやりにくさという点で多くの問題がありました。

  • 自由にミドルウェアを更新、評価できない。
  • 個人の開発環境に対してhttpsが使えない。
  • 各開発メンバーがどのポートを使用しているか管理が必要。
  • DB追加、権限付与など開発メンバーごとのスキーマ管理が必要。
  • 誰かの開発環境に負荷が掛かると他のメンバーの環境にも影響が及ぶ。

その後、開発メンバーが増えていくにつれ、この運用ルールを続けていくには限界が見えてきました。上記の問題を解決するため、開発環境をDocker仮想化環境へ移行することを決めました。

Docker環境の構成

社内からアクセス可能な場所にDockerサーバー(mydocker.dev)を設置し、開発端末からは仮想ホスト(*.mysite.dev)で各開発メンバーのコンテナにアクセスする構成を考えます。

ここでは開発メンバーをxxxさん、yyyさん、zzzさんの3名、Webサーバーとしてnginx、DBサーバーとしてMySQL、KVSサーバーとしてRedisを使うものとします。その場合、ネットワーク構成と稼働させるコンテナは以下のようになります。

コンテナ名 用途 使用するDockerイメージ
nginx-proxy リバースプロキシ jwilder/nginx-proxy
xxx-nginx xxxさん専用のnginxサーバー OSイメージをベースにビルド
xxx-mysql xxxさん専用のMySQLサーバー mysql
xxx-redis xxxさん専用のRedisサーバー redis
yyy-nginx yyyさん専用のnginxサーバー OSイメージをベースにビルド
yyy-mysql yyyさん専用のMySQLサーバー mysql
yyy-redis yyyさん専用のRedisサーバー redis
zzz-nginx zzzさん専用のnginxサーバー OSイメージをベースにビルド
zzz-mysql zzzさん専用のMySQLサーバー mysql
zzz-redis zzzさん専用のRedisサーバー redis

実際には、各メンバーの開発環境に対して直感的にアクセスできるように、xxx/yyy/zzzにはメンバーの名前頭3文字を割り当ててます(例えば中村であればnak)。

nginxコンテナでは、nginxサーバー以外にもWebアプリを動作させるために必要なMySQLクライアントや各種ライブラリなどが必要なため、ubuntucentosなどのOSイメージをベースに各開発メンバー専用のイメージをビルドします。

極力、イメージは本番環境で稼働中のソフトウェア&バージョンに合わせます。1つのコンテナ=1つの役割を原則とし、コンテナ内に複数のサーバーを立てないようにします。

Dockerサーバーの構築

DockerサーバーにはDocker本体、docker-compose、gitクライアントをインストールします。開発メンバーの人数と開発モードでのアプリケーションのCPU使用、メモリ消費を考慮したサーバースペックが必要です。

以下にディレクトリ構成の例を示します。

/
└ home
   ├ nginx-proxy                    nginx-proxyのホームディレクトリ
   │ ├ docker-compose.yml           リバースプロキシのコンテナを定義
   │ └ certs
   │    ├ myserver.crt              *.mysite.dev公開鍵
   │    └ myserver.key              *.mysite.dev秘密鍵
   │
   ├ xxx                            xxxさんのホームディレクトリ
   │ ├ docker
   │ │ ├ setup_env.sh               環境設定用シェル
   │ │ ├ docker-compose.yml         開発環境に必要なコンテナを定義 
   │ │ └ Dockerfile                 nginxコンテナビルド用
   │ └ workspace                    xxxさんの作業ディレクトリ
   │
   ├ yyy                            yyyさんのホームディレクトリ
   │
   ├ zzz                            zzzさんのホームディレクトリ
   │

リバースプロキシの構築

nginx-proxy1とはブラウザとnginxコンテナの間に入るリバースプロキシです。例えば、社内ネットワーク内の端末からxxx.mysite.devへアクセスすると、Dockerネットワーク内のxxx-nginxへプロキシしてくれます。 これを実現するために以下の手順が必要です。

docker-compose.yml(抜粋)

version: '3'

services:
  nginx-proxy:
    image: jwilder/nginx-proxy:latest
    container_name: nginx-proxy
    privileged: true
    ports:
      - 443:443
    volumes:
      - ./certs:/etc/nginx/certs/
      - /var/run/docker.sock:/tmp/docker.sock:ro
    restart: always

networks:
  default:
    name: common_link
    external: true

開発環境の構築

必要な作業を簡素化するため、開発メンバーは以下の手順で開発環境を構築できるようにします。

  • setup_env.sh
    ホスト側のユーザー情報を.envに記録し、コンテナ側のユーザー情報と統一させる。
  • docker-compose build --no-cache
    各開発メンバー専用のnginxコンテナのイメージをビルドする。
  • docker-compose up
    開発環境となるコンテナ一式を起動する。

setup_env.sh

echo "UID=$(id -u)" > .env
echo "GID=$(id -g)" >> .env
echo "UNAME=$(id -un)" >> .env
echo "GNAME=$(id -gn)" >> .env
echo "MYDOMAIN=${UNAME:0:3}" >> .env

docker-compose.yml(抜粋)

version: '3'

services:
  nginx:
    container_name: ${MYDOMAIN}-nginx
    hostname: ${MYDOMAIN}.mysite.dev
    build:
      context: .
      args:
        UID: ${UID}
        GID: ${GID}
        UNAME: ${UNAME}
        GNAME: ${GNAME}
        MYDOMAIN: ${MYDOMAIN}
    image: ${UNAME}/nginx:1.0
    volumes:
      - ../workspace:/var/workspace
    ports:
      - 8443
    environment:
      VIRTUAL_HOST: ~^(\w+-)?${MYDOMAIN}\.mysite\.dev
      VIRTUAL_PORT: 8443
      CERT_NAME: myserver
    depends_on:
      - mysql
      - redis
    tty: true
    stdin_open: true

  mysql:
    container_name: ${MYDOMAIN}-mysql
    image: mysql:latest
    volumes:
      - mysql-volume:/var/lib/mysql

  redis:
    container_name: ${MYDOMAIN}-redis
    image: redis:latest
    volumes:
      - redis-volume:/data

volumes:
  mysql-volume:
  redis-volume:

networks:
  default:
    name: common_link
    external: true

Dockerfile(抜粋)

FROM any-os-image

ARG UID
ARG GID
ARG UNAME
ARG GNAME
ARG MYDOMAIN

ENV UID=${UID}
ENV GID=${GID}
ENV UNAME=${UNAME}
ENV GNAME=${GNAME}
ENV MYDOMAIN=${MYDOMAIN}
ENV HOSTNAME=${MYDOMAIN}.mysite.dev
ENV container=${MYDOMAIN}-nginx

RUN groupadd -g ${GID} ${GNAME}
RUN useradd -l -g ${GID} -u ${UID} -m ${UNAME}

COPY nginx_build.sh /tmp/
RUN /tmp/nginx_build.sh

CMD ["nginx","-g","daemon off;"]

これらのファイルはリポジトリに登録しておき、開発メンバーは自身のホームディレクトリにいつでもコピー、更新できる状態にしておきます。

開発環境の構築は以上ですが、このままだと各開発メンバーのコンテナ名が衝突してしまうため、ログインスクリプトでdocker-compose用の環境変数を設定しておきます。

/etc/profile.d/docker.sh

export COMPOSE_PROJECT_NAME=$USER
export COMPOSE_FILE=~/docker/docker-compose.yml

まとめ

Dockerを導入してからは、それまで行なっていた開発環境構築の手間が減り、開発メンバー間の環境を統一できました。また現在は、開発環境以外にも、継続的インテグレーションAndroidアプリのビルドなどでもDockerを使用しています。

また、ここでは開発環境へのDocker導入について説明しましたが、本番環境への導入についてはパフォーマンス、セキュリティ、耐障害性能などを十分に検討しなければなりません。

最初の手間は掛かりますが、一度Dockerを導入すればさまざまな用途に使えますので、同じような問題を抱えている組織がありましたらDockerの導入を考えてみてはいかがでしょうか。

カカクコムでは、ともにサービスをつくる仲間を募集しています!

カカクコムのエンジニアリングにご興味のある方は、ぜひこちらをご覧ください!

カカクコム採用サイト