環境構築

VagrantとDockerを使用した開発環境構築

こんにちは、インフラエンジニアのトシです。

先日、とあるプロジェクトの開発環境をVirtualBoxのイメージ配布からVagrant + Dockerへ移行しました。そこで、今回はその内容を紹介いたします。

経緯などの説明に入る前に、VagrantとDockerについて簡単に説明しましょう。

Vagrantは仮想開発環境を簡単に構築・共有するためのソフトウェアで、
DockerはNginxやMySQLなどのサーバーソフトウェアを簡単に立ち上げることができるソフトウェアです。

Docker移行の経緯

以前は開発環境の構築手順書を作成し、仮想マシンのイメージをファイル共有の方法でメンバーに配布していました。
しかし、(このような環境構築ではよくある話ですが)開発期間中に構築手順が”秘伝のタレ”化してしまい、構築手順書通りに行なっても環境構築に失敗してしまうことが多発していました。
原因を探ると、開発途中で変更があったにもかかわらず構築手順書への反映漏れによるものだとわかりました。

それ以外にも、新規で構築する際にコマンドの実行忘れ、チャットで周知された必須コマンドが実行されないなど個人でVMを管理していることによる問題が顕在化していました。

これを解決すべく、VagrantとDockerを使用することで、開発者間での開発環境の差異を無くしたいと考えたのです。

カタリストシステムの開発環境は、主に以下の構成でした。

  • 個人PC
    • VirtualboxでCentOS環境イメージをファイル共有から取得
    • 手順書作成しながら構築
  • 社内VMサーバー(検証環境)
    • 社内の物理サーバー上にKVMでCentOS環境を構築
    • 手順書に従って構築

上記を下記の構成へ再編成しました。

  • 個人PC
    • Virtualbox + Vagrant でCentOS環境を構築
  • 社内VMサーバー(検証環境)
    • 社内の物理サーバー上にKVMでCentOS環境を構築
    • Dockerfileから手順書を書き起こし構築

個人PCの開発環境は、VirtualBoxにCPU2コア、メモリ2GBのVMが立ち上がるようにVagrantfileで設定しています。
将来的にスペックが不足してきたら柔軟に対応できるよう心がけてました。

移行の方針と内容

さらに、開発環境のDocker移行は、以下の方針としました。

  • 個人PC上にVagrantでCentOSを立ち上げその上にDockerでサービスを構築する
  • Vagrant Boxを自作するとブラックボックスとなりがちなので最少機能のVagrant Boxからプロビジョニングする
  • 社内VMサーバー(検証環境)はDocker移行しない

個人のVM上に環境をプロビジョニングするということ。
この場合、Vagrant + Dockerでイチから開発環境を構築すると約1時間かかります。
最近ではWindowsでDockerを使用する方法ならばDocker for Windowsも選択肢となりますが、現状Virtualboxを使用していることも考慮して、環境を変えすぎると開発者が大変だという判断をしました。
次の改善の際にはVirtualboxを廃してオーバーヘッドを減らす方向で考えています。

また、これまでは検証サーバー上のDBサーバーを全員で共有して開発を行なっていましたが、開発DBは個人のVM上に配置することとしました。

アーキテクチャ

構成

下記の図は現在開発中のWEBアプリケーションのコンテナ群の構成図になります。
本番環境でロードバランサーを利用することを考慮してアプリケーションサーバーはproxyを介してアクセスするようにしています。
また、コンテナを停止した際にログが失われてしまうので、Fluentdで集約して保存するようにしています。
下記は構成するDockerコンテナ一覧です。

  • fluentd – ログ集約コンテナ
  • proxy – WEBサーバーの前段Proxyコンテナ
  • redis – Redisキャッシュコンテナ
  • mysql – MySQLコンテナ
  • nginx – Node.jsサーバーの前段Proxyコンテナ
  • nodejs – Node.jsコンテナ
  • apache – Apache Httpd WEBサーバーコンテナ

Docker Composeを使用してコンテナを管理する

1つのサービスはほとんどの場合複数のコンテナが必要となります。
docker runコマンドで管理することも可能ですが、コンテナ間の依存関係を記述していくと途端に複雑化してしまいます。
Docker Composeを使用することでサービス全体をコードの形で定義することが可能となります。
今回は最新のversion3で記述していますが、社内にはversion1と2のコードがまだ残っているので折を見て更新中です。
記述法についてはリファレンスを確認しながら作成すると良いかと思います。

docker-compose.yml

version: '3'
services:
  # ログ集約コンテナ
  fluentd:
    container_name: fluentd
    build: ../fluentd
    volumes:
      - /var/log/fluentd:/fluentd/log
    ports:
      - 24224:24224

  # proxyコンテナ
  proxy:
    container_name: proxy
    depends_on:
      - fluentd
    build: ../proxy
    privileged: true
    environment:
      ENABLE_IPV6: "true"
    ports:
      - 80:80
      - 443:443
    volumes:
      - /etc/nginx/htpasswd
      - /etc/nginx/vhost.d
      - /usr/share/nginx/html
      - /var/run/docker.sock:/tmp/docker.sock:ro
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.proxy

  # Redisコンテナ
  redis:
    container_name: redis
    depends_on:
      - fluentd
    build: ../redis
    volumes:
      - redis_db:/var/lib/redis
    ports:
      - 6379:6379
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.redis

  # MySQLコンテナ
  mysql:
    container_name: mysql
    depends_on:
      - fluentd
    build: ../mysql
    environment:
      - MYSQL_ROOT_USER=root
      - MYSQL_ROOT_PASSWORD=foobar
      - MYSQL_DATABASE=hoge_db
      - MYSQL_USER=user
      - MYSQL_PASSWORD=hogehoge
    volumes:
      - /app/doc/create:/docker-entrypoint-initdb.d
      - /app/docker/mysql/conf:/etc/mysql/conf.d
      - db:/var/lib/mysql
    ports:
      - 3306:3306
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.mysql

  # Node.jsコンテナ
  nodejs:
    container_name: nodejs
    depends_on:
      - nginx
    build: ../nodejs
    volumes:
      - /app:/app
      - /var/run/docker.sock:/var/run/docker.sock:ro
    links:
      - mysql
    ports:
      - 10081
      - 10082
      - 10081/udp
      - 10082/udp
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.nodejs

  # Node.js用proxyコンテナ
  nginx:
    container_name: nginx
    depends_on:
      - mysql
    build: ../nginx
    hostname: vm.example.com
    volumes:
      - /app:app
    ports:
      - 10080:10080
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.nginx

  # アプリケーションコンテナ
  apache:
    container_name: apache
    depends_on:
      - proxy
      - redis
      - mysql
    build: ../apache
    hostname: vm.example.com
    environment:
      VIRTUAL_HOST: vm.example.com
      VIRTUAL_PORT: 80
    volumes:
      - /app:/app
      - /var/run/docker.sock:/var/run/docker.sock:ro
    links:
      - mysql
      - redis
    ports:
      - 80
      - 443
    logging:
      driver: "fluentd"
      options:
        fluentd-address: localhost:24224
        tag: docker.apache

volumes:
  db:
    driver: local
  redis_db:
    driver: local

構築トラブルの解決

VagrantのBoxファイルのダウンロード速度問題

VagrantでVMを構築する際には、元となるVagrant Boxを指定する必要があります。
当初必要最低限の自作Boxを作成し、Github Releasesから各自ダウンロードする形で考えていました。
しかし、実際に使用してみたところダウンロード速度が低速であったため、公式CentOS Boxを使用することにしました。

VirtualBox Guest Addition のバージョン違い

Boxの変更を行なった結果、個人のPCにインストールされているVirtualBoxのバージョンとBox内のGuest Additionのバージョンが違うためにたびたびエラーが発生するようになってしまいました。
そのため、vagrant-vbguestのプラグインを導入することにしました。
これは、自分の環境のVirtualBoxのバージョンとBoxにインストールされているGuest Additionのバージョンが違う場合に、VirtualBoxのバージョンに合わせて最新化してくれるというものです。

Windows・Mac側で以下のコマンドでインストールします。

vagrant plugin install vagrant-vbguest

Windows機とMac機混在による問題

シンボリックリンク

VMとファイル共有を行なっているディレクトリ内にシンボリックリンクを作成しようとした際にWindowsでは作成に失敗してしまいます。
今回の例では、Node.jsで使用するために npm install を行なう際に、通常はシンボリックリンクを作成しようとしますが、Windowsでは失敗してしまいます。
この問題を解決するためにVagrantfileでWindowsの場合とMacの場合で実行するコマンドを変更する対応を行ないました。
Vagrantfile内はRubyによって処理を記述することができますので、簡単な判定を追加しました。
下記はVagrantfileの抜粋となります。

# Windows Mac判定用変数用意
$is_windows = RbConfig::CONFIG['host_os'] =~ /mswin|msys|mingw|cygwin|bccwin/i
$is_osx = RbConfig::CONFIG['host_os'] =~ /darwin/i
if $is_windows then
    config.vm.provision "shell", inline: <<-SHELL
    # Windowsの場合のコマンド
    docker exec -i nodejs bash -c 'cd /app/nodejs/ && npm install --no-bin-links'
    SHELL
  else
    config.vm.provision "shell", inline: <<-SHELL
    # Macの場合のコマンド
    docker exec -i nodejs bash -c 'cd /app/nodejs/ && npm install'
    SHELL
  end

Docker移行したことによるメリット・デメリット

  • メリット
    • ミドルウェアのバージョンアップ迅速化
    • コマンド実行ミスが無くなる
    • 開発環境の統一

      これまでは全員の開発環境上のサーバーソフトウェアを更新する場合には、別途手順書を作成してコマンド実行してもらうか、メンバーが更新したVM(数十GB)をネットワーク共有する必要がありましたがDocker化したことによってメンバーがDockerファイルを更新するだけで全員のソフトウェアが更新されるようになりました。それぞれのメンバーの環境が同じバージョンのソフトウェアで動作するようになりましたので、バージョンの差異による不具合が出なくなりました。
      もっとも嬉しい点はコマンドのコピペミスや、コマンドを抜かしてしまうことが無くなり、原因究明のために対応する時間が不要になったことです。

  • デメリット
    • テストデータの準備が大変に
    • Dockerについて学ぶ学習コストがかかる

      共有DBと違い、コンテナ上のDBは構成管理でリセットされることがあるため、テストデータを各自がそれぞれローカルDB上で登録する必要があります。また、リセットされることを考慮してテストデータの登録スクリプト(もしくはSQL)を用意する必要があります。
      また社内に Docker を触ったことのあるメンバーがいなかったため、それなりの学習コストが必要となりました。

まとめと予告

今回はVagrantとDockerを用いた開発環境構築について、ごく一部ではありますが、紹介させていただきました。
開発環境の構築自動化はとてもメリットが大きいのでこれからも推進していきたいと思っています。

次回はFirebaseを使ってみたお話について紹介させていただきます。

引用元・出典
Vagrant
Docker
Docker for Windows
Fluentd
Docker Compose
vagrant-vbguest

LINEで送る
Pocket

この記事を書いた人・プロフィール
toshi
ニックネーム: toshi

2013年10月からカタリストシステムに在籍。以前は独立系SIerで監視業務を行っていました。
Androidアプリ、サーバサイド開発(PHP)、サーバインフラ関連等手広く対応しています。
コミケは毎回行ってます。

趣味:自作PC、バイク、萌え系、インディーズゲーム