はじめに
こんにちは。映画.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クライアントや各種ライブラリなどが必要なため、ubuntuやcentosなどの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へプロキシしてくれます。 これを実現するために以下の手順が必要です。
- *.mysite.devのエイリアスをmydocker.devとするCNAMEレコードをDNSに登録する。
- ドメイン名のパターンを開発環境のdocker-compose.ymlの環境変数VIRTUAL_HOSTに設定する。
- httpsでアクセスするためには、ワイルドカード証明書をnginx-proxyコンテナ内に配備する。
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の導入を考えてみてはいかがでしょうか。
カカクコムでは、ともにサービスをつくる仲間を募集しています!
カカクコムのエンジニアリングにご興味のある方は、ぜひこちらをご覧ください!