Vagrant・CoreOS・Dockerでインフラ素人が自宅サーバを立てた話

今年の春くらいにWEB+DBの「[サーバ/インフラ徹底攻略 (WEB+DB PRESS plus)](http://www.amazon.co.jp/dp/4774167681/)」を読み、イミュータブルインフラストラクチャというものに興味が湧いた。そこで、これまでは[Bitnami Stacks](https://bitnami.com/stacks)でOSXに直接インストールしていたWebアプリケーションを、VagrantとDockerで不変なものに置き換えてみることにした。

※スライド版資料もあります [→Dockerで楽しむ自宅サーバ](http://media-massage.net/works/docs/dockerhomeserver/)

**2016-02-15追記**

紹介しているサーバ構成の簡易版をgithubにアップロードしました。自宅と全く同じ構成ではありませんが(非SSL・内部ネットワーク化など)、VagrantとOSXさえあれば動作するようになっています。 [→デモ用Vagrantfile + docker-compose.yml](https://github.com/hokkey/vagrant-coreos-docker-demo)

# 最終的に出来上がったシステムの俯瞰図

![スクリーンショット 2015-09-04 23.27.03.png](https://qiita-image-store.s3.amazonaws.com/0/48238/9c32686e-875c-1a17-46a2-93ff746a369e.png)

# 設定ファイルの一覧

```
├── Vagrantfile
├── config.rb                 
├── docker-compose.yml
├── user-data
├── /proxy
│   ├── Dockerfile
│   ├── /conf
│   │   └── nginx.conf
│   ├── /html
│   │   └── index.html
│   └── /ssl
│       ├── server.crt
│       └── server.csr
├── /lychee
│   ├── Dockerfile
│   └── /conf
└── /resourcespace
    ├── Dockerfile
    └── /conf
```

## 筆者スペック

インフラについてはほぼ素人。Vagrantはちょっと使ったことある。普段の制作でCLIを使うことには慣れているものの、LinuxやDockerに関する事前知識はほとんどない。

* 本業はフロントエンドやデザイン
* OSXのコマンドラインは少し使えるが、Linuxは触ったことない
* ITパスポートなら持ってる

## やりたかったこと

* 複数のWebアプリケーションを自宅サーバ内に構築し、どこからでも使いたい
* 1つのIPアドレスに複数のWebアプリケーションを集約したい
* 必要なデータは1つのファイルに隔離し、バックアップを簡単にしたい
* すべての設定を1つのGitリポジトリに集約させ、データ以外はそこから全て復旧できるようにしたい
* 以下のアプリケーションをインストールしたい

| アプリケーション | 機能 |
|:--|:--|:--|
| [Redmine](http://www.redmine.org) | Issueトラッキング・システム |
| [ResourceSpace](http://www.resourcespace.org) | DAMシステム |
| [Gitbucket](https://github.com/gitbucket/gitbucket) | GitHubクローン |
| [Libreboard](http://libreboard.com) | Trelloクローン |
| [manet](https://www.npmjs.com/package/manet) | Webサイトのスクリーンショット作成|
| [Lychee](http://lychee.electerious.com) | 写真管理アプリケーション |
| [Etherpad](http://etherpad.org) | リアルタイム同期テキストエディタ |
| [EtherCalc](https://ethercalc.org) | リアルタイム同期スプレッドシート |

※Redmine, Gitbucket, Libreboard, manet, EtherCalcについては「[Dockerで即実行できる、社内・自宅向けオープンソースWebアプリ](http://qiita.com/y_hokkey/items/406b5a8c4bc15354d069)」でも詳しく紹介

※ResourceSpaceについては「[オープンソースのDAMシステム、ResourceSpaceのまとめ](http://qiita.com/y_hokkey/items/20336a3adc8672a41a4f)」でも詳しく紹介

## どうやって実現するか

##### **自宅サーバは直接公開せず、VPN経由でアクセスする**
セキュリティのことを考えると話が複雑になるので、今回はパス。サーバにはMacOSX ServerのVPN機能を使ってアクセスする。

##### **VirtualBox + Vagrantで仮想化する**
仮想マシンの設定はVagrantfile内で完結させる。

##### **CoreOSを使う**
UbuntuやCentOSのような普通のLinuxよりも今回の目的に向いていそうに思ったので、これを使うことにした。

* 公式でVagrantfileが配布されていてインストールが簡単
* OS自体が仮想環境上で短時間に生成→破棄のサイクルを繰り返すこと前提に作られており、システムの設定が1ファイルに集約されている
* Dockerがデフォルトでインストールされている
* 余計なツールが一切インストールできないので、問題の切り分けが楽そう

##### **Dockerコンテナのオーケストレーションにdocker-compseを使用する**
[docker-composeを使うと複数コンテナの管理が便利に](http://qiita.com/y_hokkey/items/d51e69c6ff4015e85fce)

##### **リバースプロキシを構成し、1つのURLへ複数のWebサービスをまとめる**
リバースプロキシというものを使うことで、複数のWebサービスを束ねて1つのURL化に構成することができる。nginx(エンジンエックス)というWebサーバがリバースプロキシとしての機能を持っているため、これを使う

## 「ホスト」が指す対象の定義

今後の文章では、OSX環境、仮想マシン内、仮想マシン内のDockerコンテナのことを次のように呼び分ける。
OSXから見たLinuxのゲストマシンが、コンテナ側からはDockerホストになることに注意。

| VirtualBoxが動作するMacOSX環境 | VirtualBox内のCoreOS環境 | CoreOS内のDockerコンテナ内の環境 |
|:--|:--|:--|
| ホスト | ゲスト 又は Dockerホスト | Dockerコンテナ | 

# 感想

記事が長いので先に感想を記載する。

## 調べるべきことがすごく多かった

Dockerだけやれば済むくらいのつもりだったが、思った以上に調べないといけないことが多かった。インフラエンジニアってすごい!

1. docker, docker-composeの使い方の理解
2. VirtualBox特有の機能・概念の理解
3. Vagrantfileの書き方・簡単なrubyの構文の意味の理解
4. シェルスクリプトの書き方
5. systemdの設定ファイル(user-data)の書き方
6. nginxの設定ファイル(nginx.conf)の書き方
7. DockerHub上にある各イメージの使い方
8. 各Webアプリケーションの設定の書き方

## 永続化データをいかに分離するかが鍵かも

Vagrantfile・docker-compose・user-dataにすべての設定が集約されている為、このgitリポジトリさえあればサーバ環境を簡単に作り直すことができる。ミドルウェアのバージョンアップをしたくなったら、dockerfileでpullしてくるバージョン番号を上げるだけで完了してしまう。

問題は消えてしまっては困る永続化データの部分で、これをちゃんと分離しないと気軽に環境の破棄ができず、vagrantやdockerの魅力が半減してしまう。

はじめは、dockerコンテナからVMの共有フォルダを通して直接OSXのディレクトリにファイルを書き込む運用を考えたが、パーミッションが弱く使えなかった。ほとんどのコンテナでは初期化時にchownを必要とする。

しかし、普通に`docker run -v`オプションを使うと、仮想マシン内へ直接データが蓄積されてしまう。

これは嫌なので、データ保存先を専用のVirtualBoxのディスクイメージに変更。ディスクイメージは`vagrant up`時に自動で作成・初期化、又は、既に存在する場合は自動で仮想マシンにアタッチするようにVagrantfileを記述した。

## リバースプロキシの設定がはまりどころ

最後の方の段階での、リバースプロキシ(nginx)でWebアプリケーションをサブディレクトリにマップする設定ははまりどころが多かった。次のようなことが起こり泣きそうになった。

* Webアプリケーション自体にはアクセスできているのに、フロントエンドのパスが通っておらずCSSが適用されない
* 一見問題なく動作しているが、ログインやログアウトのリダイレクト時に、サブディレクトリを無視したアドレスへ飛ばされる

Webアプリケーション側の設定が必要な場合もあれば、リバースプロキシ側で正しくヘッダ情報を飛ばしていないことが原因の場合もあった。

---

# OSX側での事前準備

##### **1.VirtualBoxとVagrantをインストール**  
[Vagrant](https://www.vagrantup.com/), [Oracle VM VirtualBox](https://www.virtualbox.org/)
これがないと始まらない。

##### **2. `visudo`でvagrantにsudo権限を与える (オプション)**  
[メモ：Vagrant の NFS synced folder でいちいちパスワード聞いてこなくするやつ](http://qiita.com/yungsang/items/68f0ac35cd2774b3b957)
仮想マシンとホストマシンのファイル共有にNFSを使用すると、Vagrantで仮想マシンを立ち上げる度に管理者パスワードを求められる。Virtualboxにsudoの権限を与えておくと、シェルスクリプトでの自動起動ができるので使いやすい。visudoコマンドによってsudo可能なユーザーを編集することができる。

##### **3. 仮想マシンの保存場所を差分バックアップに対応させる(オプション)**  
[仮想マシンをTimeMachineで効率的にバックアップする方法](http://qiita.com/y_hokkey/items/673193c8992e800d3e29)
ディスクイメージは巨大なバイナリデータなので、そのままだとTimemMachineのバックアップ容量を無駄に消費してしまう。
スパース・バンドル形式で適当な可変サイズのディスクイメージを作成し、VirtualBoxの仮想マシン作成先をその中へ変更。このディスクイメージはスタートアップ時に自動でマウントする様にOSXの起動項目へ追加しておく。後のバックアップはTimeMachineにまかせる。

#### **4. CoreOS公式のVagrantfileリポジトリをダウンロードする**
[coreos-vagrant/Vagrantfile at master · coreos/coreos-vagrant](https://github.com/coreos/coreos-vagrant/blob/master/Vagrantfile)
この後、リポジトリ内の`vagrantfile`と`user-config`を編集していく。

# Vagrantfileのカスタマイズ

CoreOSの公式Vagrantfileは、`Vagrantfile`本体と`config.rb`に分離している。`config.rb`にカスタマイズ部分を書き、Vagrantfile自体にはあまり手を加えなくとも使えるというのがその意図だろう。なるべくその意図を尊重してカスタマイズを行なったが、結局両方のファイルに変更を加えているため、1つにまとめてしまっても良いかもしれない。

以下、変更・追記箇所をポイントごとに解説。ファイル全体はgist記事を参照。
https://gist.github.com/hokkey/f29492503b485ea8d414

---
### **1. ホスト・ゲスト間のファイル共有を設定**
```ruby:config.rb
# Share additional folders to the CoreOS VMs
# For example,
# $shared_folders = {'/path/on/host' => '/path/on/guest', '/home/foo/app' => '/app'}
# or, to map host folders to guest folders of the same name,
# $shared_folders = Hash[*['/home/foo/app1', '/home/foo/app2'].map{|d| [d, d]}.flatten]
$shared_folders = {'.' => '/home/core/dockerfiles'}
```
config.rbが含まれるディレクトリへ、CoreOSの内部からアクセスできるようにする。これでdockerの設定ファイルをゲストマシンから直接参照できるようになる。

このファイル共有にはMacOSXのNFSファイル共有機能が使われる為、マウントされた共有フォルダはNFS特有の制約を受ける。
> 結論から先に言うと、NFS経由ではrootはすごく弱くなる。
> [NFSの設定と動作 - HHeLiBeXの日記 正道編](http://hhelibex.hatenablog.jp/entry/20111205/1323111243)

NFSはアクセスする側の制約がかなり大きいので、この場所に直接Dockerコンテナからファイルを書き込んだりする運用は難しいのではないかと思う。やろうと思ったけど上手くいかなかった顛末をStackoverflowにまとめてある。

[mysql - 仮想マシン上のCoreOSで、dockerの永続化データをホストとの共有フォルダに作成できず、権限エラーとなってしまう - スタック・オーバーフロー](http://ja.stackoverflow.com/questions/9320/%e4%bb%ae%e6%83%b3%e3%83%9e%e3%82%b7%e3%83%b3%e4%b8%8a%e3%81%aecoreos%e3%81%a7-docker%e3%81%ae%e6%b0%b8%e7%b6%9a%e5%8c%96%e3%83%87%e3%83%bc%e3%82%bf%e3%82%92%e3%83%9b%e3%82%b9%e3%83%88%e3%81%a8%e3%81%ae%e5%85%b1%e6%9c%89%e3%83%95%e3%82%a9%e3%83%ab%e3%83%80%e3%81%ab%e4%bd%9c%e6%88%90%e3%81%a7%e3%81%8d%e3%81%9a-%e6%a8%a9%e9%99%90%e3%82%a8%e3%83%a9%e3%83%bc%e3%81%a8%e3%81%aa%e3%81%a3%e3%81%a6%e3%81%97%e3%81%be%e3%81%86)

---

### **2. ディスクイメージの自動作成とアタッチ**

通常は1つの仮想ディスク内に全てのデータが格納される。しかし、データ管理の都合からDockerイメージと永続化データは通常のディスクイメージから分離することにした。

##### **Dockerイメージを専用のディスクイメージに隔離する**

DockerがDockerHubからイメージをpullしてくる時の保存場所を、外部ディスクとして分離する。これにより、仮想マシンを破棄しても再度大容量のイメージをダウンロードしてくる必要がなくなり、仮想マシンの再構築が手軽に行えるようになる。
このイメージ自体はなくなっても困るものではないので、バックアップしなくても構わない。

##### **永続化データを専用のディスクイメージへ隔離する**

永続化データを単独のディスクイメージに隔離することで、仮想マシンの破棄を手軽に行うことができるようになる。究極的にはこのディクイメージだけバックアップしておけば、他の部分はいつでも再構築ができる。

```ruby:config.rb
# 外部ストレージ
$attached_storages = [
  {:file => './internal_data.vdi', :size => 100*1024, :port => 1, :device => 0, :dev => 'sdb', :dest => '/mnt/data'},
  {:file => './internal_image.vdi', :size => 100*1024, :port => 1, :device => 1, :dev => 'sdc', :dest => '/var/lib/docker'}
]

# ストレージをフォーマットしてマウント
def format_storage(dev, dest)
  fdisk = <<-EOF
    (echo n; echo p; echo 1; echo ; echo ; echo w) | fdisk /dev/#{dev}
    mkfs -t ext4 /dev/#{dev}1
    mount -t ext4 /dev/#{dev}1 #{dest}
  EOF
  return fdisk
end
```
```ruby:Vagrantfile
  (1..$num_instances).each do |i|
  # 途中省略
      # 外部ストレージの作成と追加
      if not File.exist?($attached_storages[0][:file]) then
        init_storage = format_storage($attached_storages[0][:dev])
        config.vm.provision :shell, :inline => init_storage, :privileged => true
      end
      if not File.exist?($attached_storages[1][:file]) then
        init_storage = format_storage($attached_storages[1][:dev])
        config.vm.provision :shell, :inline => init_storage, :privileged => true
      end

      config.vm.provider :virtualbox do |vb|
        for data in $attached_storages
          # 新規ストレージ作成
          if not File.exist?(data[:file]) then
            vb.customize ['createhd', '--filename', data[:file], '--size', data[:size]]
          end
          # ストレージをアタッチ
          vb.customize ['storageattach', :id, '--storagectl', 'IDE Controller', '--port', data[:port], '--device', data[:device], '--type', 'hdd', '--medium', data[:file], '--setuuid', '']
        end
      end
```
Vagrantfileをカスタマイズして、ディスクイメージが在しない場合は自動的に作成されるようにした。

* イメージが存在しない場合は自動的にVDI形式の可変ディスクイメージを作成して仮想マシンに接続
* 初回起動時にフォーマットして初期化
* イメージが存在する場合は何もしない(アタッチされたまま)

ただし、マウントを設定しないとCoreOS側からはまだアクセスできない。CoreOS側の設定は後述のuser-data内に記述する。

#### **`vagrant destroy`するときの注意**

ディスクイメージの分離はとても便利だが、一つだけ重要な問題がある。それは、vagrant destoryした時に、その仮想マシンに紐付く外部ディスクのイメージも同時に消されてしまうことだ。

仮想マシンをdestroyする時には、かならずディスクイメージの紐付けを解除してから行うようにしないと大変なことになってしまう。このあたりを上手くやれるようにする方法は現在も模索中。

### **3. 仮想マシンのメモリ・CPU数・チャンネルを設定**

チャンネルとはcoreOSの更新状況を通知するもので、alpha版、beta版、stable版が選べる。alpha,betaはかなり頻繁に更新されている。筆者はリブートストラテジをoffにしているため、stable版を使うことにしている。リブートストラテジについてはuser-dataの項を参照。

```ruby:config.rb
# Customize VMs
$vm_gui = false
$vm_memory = 3072
$vm_cpus = 2
# Official CoreOS channel from which updates should be downloaded
$update_channel='stable'
```

* メモリが少なすぎるとMySQLが落ちたりする。中で動かすコンテナが多い場合はそれなりに確保する


### **4.ネットワーク設定**

今回は公開ネットワークへブリッジし、仮想マシン自体のMACアドレスを使って外部のDHCPサーバからIPを固定する運用にした。

```ruby:config.rb
# 公開ネットワーク設定
$bridge = 'en0: Ethernet'
$mac_address = '000000000000'
```

```ruby:Vagrantfile
  (1..$num_instances).each do |i|
  # 途中省略
      # ネットワーク設定
      config.vm.network :public_network, bridge: $bridge, mac: $mac_address
```

### **5.プロビジョニング**

初回のプロビジョニング時には`docker-compose`を自動でインストールするようにした。

```ruby:config.rb
# docker_composeをインストール
$install_docker_compose = <<-EOF
  curl -L https://github.com/docker/compose/releases/download/1.2.0/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
  sudo mkdir /opt
  sudo mkdir /opt/bin
  sudo mv ~/docker-compose /opt/bin/docker-compose
  sudo chown root:root /opt/bin/docker-compose
  sudo chmod +x /opt/bin/docker-compose
EOF
```
```ruby:Vagrantfile
  (1..$num_instances).each do |i|
  # 途中省略
      # docker-composeをインストール
      config.vm.provision :shell, :inline => $install_docker_compose, :privileged => true
```

OS起動後、常に`docker-compose-up -d`が走るスクリプトも記載した。

```ruby:config.rb
# docker_composeを起動する
$docker_compose_up = <<-EOF
  cd /home/core/dockerfiles
  docker-compose up -d
EOF
```
```ruby:Vagrantfile
  (1..$num_instances).each do |i|
  # 途中省略
      # docker-composeを実行(常に)
      config.vm.provision :shell, :inline => $docker_compose_up, :run => 'always'
```

---

# user-dataの構成

CoreOSのシステム環境設定はcloud-configというYAMLファイルへ記述するルールになっている。(Vagrant版ではuser-dataという名前になっているが、役割は同じ)

CoreOSではsystemdというサービスが動いていて、それがこのYAMLファイルの設定に沿ってシステムの設定をしてくれる仕組みらしい。

> [Systemd入門(1) - Unitの概念を理解する - めもめも](http://enakai00.hatenablog.com/entry/20130914/1379146157)
> [systemd超入門 ｜ Developers.IO](http://dev.classmethod.jp/cloud/aws/systemd-getting-started/)


自宅サーバとして使うにあたり、次のような設定を記述した。

```yaml:user-data
#cloud-config

hostname: coreos

coreos:
  update:
    reboot-strategy: off
  etcd:
    addr: $public_ipv4:4001
    peer-addr: $public_ipv4:7001
  fleet:
    public-ip: $public_ipv4
#  flannel:
#   interface: $public_ipv4
  units:
    - name: etcd.service
      command: start
    - name: fleet.service
      command: start
    # タイムゾーン
    - name: timezone.service
      command: start
      content: |
        [Unit]
        Description=timezone
        [Service]
        Type=oneshot
        RemainAfterExit=yes
        ExecStart=/usr/bin/ln -sf ../usr/share/zoneinfo/Japan /etc/localtime
    # マウント
    - name: mnt-data.mount
      command: start
      content: |
        [Mount]
        What=/dev/sdb1
        Where=/mnt/data
        Type=ext4
    - name: var-lib-docker.mount
      command: start
      content: |
        [Unit]
        Before=docker.service
        [Mount]
        What=/dev/sdc1
        Where=/var/lib/docker
        Type=ext4
#    - name: docker-tcp.socket
#      command: start
#      enable: true
#      content: |
#        [Unit]
#        Description=Docker Socket for the API
#
#        [Socket]
#        ListenStream=2375
#        Service=docker.service
#        BindIPv6Only=both
#
#        [Install]
#        WantedBy=sockets.target

write_files:
  - path: /etc/resolv.conf
    permissions: 0644
    owner: root
    content: |
      nameserver 192.168.1.1
```

* **リブートストラテジをoffに**  
本当は自動アップデートしたいのだが、Vagrant側の操作で再起動させないと、ホストとのファイル共有が失われてしまう。アップデートが必要なことに気付いたら`vagrant reload`することにした。 

仮想マシンの内部から再起動コマンドを叩いて、Vagrantのファイル共有を維持する方法があったら是非知りたい……。

```yaml:user-data
reboot-strategy: off
```

* **マウントの追加**  
先程説明した外部ディスクイメージをマウントするための設定を記述する。
nameとマウント先の名前が整合していないとルールが適用されない。

例として`/mnt/data`へマウントしたい場合、設定の名前は`name: mnt-data.mount`となる。なぜそんなルールなのかは分からなかったけど……

```yaml:user-data
 # マウント
    - name: mnt-data.mount
      command: start
      content: |
        [Mount]
        What=/dev/sdb1
        Where=/mnt/data
        Type=ext4
    - name: var-lib-docker.mount
      command: start
      content: |
        [Unit]
        Before=docker.service
        [Mount]
        What=/dev/sdc1
        Where=/var/lib/docker
        Type=ext4
```


* **DNSの追加**  
LAN内のDNSサーバを参照するようにした

```yaml:user-data
write_files:
  - path: /etc/resolv.conf
    permissions: 0644
    owner: root
    content: |
      nameserver 192.168.1.1
```
---
# Dockerコンテナの構成

次のような内容のdocker-composeを作成した。

```YAML:docker-compose.yml
#
# data volume containers
#
data00mongo:
  image: busybox:buildroot-2014.02
  volumes:
    - /data/mongo:/data/db
data00manet:
  image: busybox:buildroot-2014.02
  volumes:
    - /tmp/manet
data00redis:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/redis:/data
data00gitbucket:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/gitbucket:/gitbucket
data00lychee:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/lychee/data:/data
    - /mnt/data/lychee/uploads:/uploads
    - /mnt/host/:/uploads/import
data00mysql:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/mysql:/var/lib/mysql
data00redmine:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/redmine/files:/home/redmine/data/files
    - /mnt/data/redmine/plugins:/home/redmine/data/plugins
    - /mnt/data/redmine/themes:/home/redmine/data/themes
data00resourcespace:
  image: busybox:buildroot-2014.02
  volumes:
    - /mnt/data/resourcespace/filestore:/var/www/html/resourcespace/filestore
    - /mnt/data/resourcespace/homeanim:/var/www/html/resourcespace/gfx/homeanim
#
# reverse proxy container
#
proxy:
  build: proxy
  links:
    - manet:manet
    - gitbucket:gitbucket
    - etherpad:etherpad
    - redmine:redmine
    - resourcespace:resourcespace
    - lychee:lychee
    - ethercalc:ethercalc
    - libreboard:libreboard
  ports:
    - "80:80"
    - "443:443"
#
# db containers
#
mysql:
  image: mysql:5.6.24
  environment:
    MYSQL_ROOT_PASSWORD: yourSecurePasswordHere
  volumes_from:
    - data00mysql
  ports:
    - "3306:3306"
redis:
  image: redis:3.0.3
  volumes_from:
    - data00redis
  command: redis-server --appendonly yes
mongo:
  image: mongo:3.1.5
  volumes_from:
    - data00mongo
  command: mongod --smallfiles
#
# application containers
#
etherpad:
  image: tvelocity/etherpad-lite
  links:
    - mysql:mysql
  environment:
    ETHERPAD_ADMIN_PASSWORD: yourSecurePasswordHere
gitbucket:
  image: f99aq8ove/gitbucket
  ports:
    - "29418:29418"
  volumes_from:
    - data00gitbucket
  command: java -jar /opt/gitbucket.war --prefix=/gitbucket
lychee:
  build: lychee
  links:
    - mysql:mysql
  volumes_from:
    - data00lychee
manet:
  image: earlyclaim/docker-manet
  volumes_from:
    - data00manet
  command: manet --storage /tmp/manet
redmine:
  image: sameersbn/redmine:2.6.6-1
  volumes_from:
    - data00redmine
    - data00gitbucket
  links:
    - mysql:mysql
  environment:
    REDMINE_HTTPS: true
    REDMINE_RELATIVE_URL_ROOT: /redmine
    DB_USER: root
    DB_NAME: redmine
    DB_PASS: yourSecurePasswordHere
    #SMTP_USER: hoge@hoge
    #SMTP_PASS: hoge
resourcespace:
  build: resourcespace
  links:
    - mysql:mysql
  volumes_from:
    - data00resourcespace
ethercalc:
  image: audreyt/ethercalc:latest
  links:
    - redis:redis
  command: ["sh", "-c", "REDIS_HOST=$REDIS_PORT_6379_TCP_ADDR REDIS_PORT=$REDIS_PORT_6379_TCP_PORT pm2 start -x `which ethercalc` -- --cors --basepath /ethercalc/ && pm2 logs"]
libreboard:
  image: miurahr/libreboard:20150503
  environment:
    MONGO_URL: mongodb://mongo:27017/libreboard
    ROOT_URL: https://service.home.private/
  links:
    - mongo:mongo
  ports:
    - 8080:5555
```

## データボリュームコンテナの作成

ファイル冒頭部で作成している「data00〜」がデータボリュームコンテナ。Dockerホスト側のマッピング先を永続化データ用のマウントポイントに設定することで、永続化データをディスクイメージファイルに隔離している。

## 自分でDockerfileを作成したコンテナ

これら以外のアプリケーションはDockerHubのイメージをそのまま使っている。

### ResourceSpace

Michael Harrisさんが公開しているdockerfile一式をダウンロードし、少しカスタマイズして使用した。
https://github.com/michael-harris/resourcespace-docker/blob/master/Dockerfile
カスタマイズの内容はResourceSpaceに関係することなので割愛する。

### Lychee

neosarさんが公開しているdockerfile一式をダウンロードし、少しカスタマイズして使用した。
https://hub.docker.com/r/neosar/lychee-docker/~/dockerfile/
カスタマイズの内容はLycheeに関係することなので割愛する。

---

### nginx

複数のWebアプリケーションを同時に動作させたい場合、1つの仮想マシン内に複数のポート番号でサーバを立ち上げることになる。そのままでも良いのだが、デフォルトのポート番号でアクセスし、各アプリケーションへサブディレクトリでアクセスできる構成の方がそれらしい。そのような仕組みにする為にリバースプロキシを導入することにした。リバースプロキシ自体もdockerコンテナとして動作している。

#### dockerfile

```go:Dockerfile
# https://registry.hub.docker.com/_/nginx/
FROM nginx:1.9

# SSL証明書をコピー
COPY ssl/server.crt /usr/local/nginx/conf/server.crt
COPY ssl/server.key /usr/local/nginx/conf/server.key

# 設定ファイルをコピー
COPY conf/nginx.conf /etc/nginx/nginx.conf

# HTMLファイルをコピー
COPY html /usr/share/nginx/html
```

#### 設定ファイル

```nginx.conf
user  nginx;
worker_processes  1;

error_log  /var/log/nginx/error.log warn;
pid        /var/run/nginx.pid;

events {
  worker_connections  1024;
}

http {
  include  /etc/nginx/mime.types;
  default_type  application/octet-stream;

  log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
  access_log  /var/log/nginx/access.log  main;

  sendfile        on;
  keepalive_timeout  65;

  server {
    listen 80;
    server_name www.home.private;
    charset koi8-r;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;
    server_name service.home.private;
    charset koi8-r;

    ssl on;
    ssl_certificate /usr/local/nginx/conf/server.crt;
    ssl_certificate_key /usr/local/nginx/conf/server.key;

    client_max_body_size 1024M;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-Proto https;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-NginX-Proxy true;

    # static-html
    location / {
      index index.html;
      root /usr/share/nginx/html;
    }
    # manet
    location /manet {
      rewrite ^/manet/?(.*) /$1 break;
      proxy_pass http://manet:8891;
      proxy_redirect off;
    }
    # gitbucket
    location /gitbucket {
      proxy_pass http://gitbucket:8080/gitbucket;
      proxy_redirect http:// https://;
    }
    # redmine
    location /redmine {
      proxy_pass http://redmine/redmine;
      proxy_redirect http:// https://;
    }
    # lychee
    location /lychee {
      proxy_pass http://lychee;
      proxy_redirect http:// https://;
      rewrite ^/lychee/(.*) /$1 break;
    }
    # resourcespace
    location /resourcespace {
      proxy_pass http://resourcespace/resourcespace;
      proxy_redirect http:// https://;
    }
    # ethercalc
    location /ethercalc {
      rewrite ^/ethercalc/?(.*) /$1 break;
      proxy_pass http://ethercalc:8000;
      proxy_redirect default;
    }
    # libreboard
    location /libreboard {
      rewrite ^/libreboard/?(.*) http://$host:8080/$1 break;
    }
    # etherpad settings
    location ~ ^/(locales/|locales.json|admin/|static/|pluginfw/|javascripts/|socket.io/|ep/|minified/|api/|ro/|error/|jserror/|favicon.ico|robots.txt) {
      proxy_buffering off;
      proxy_pass http://etherpad:9001;
    }
    location /p {
      rewrite ^/p/(.*) /etherpad/p/$1 redirect;
    }
    location /etherpad {
      proxy_pass http://etherpad:9001/;
      rewrite ^/etherpad/?(.*) /$1 break;
      rewrite  ^/etherpad$ /etherpad/ permanent;
      proxy_pass_header Server;
      proxy_buffering off;
      proxy_redirect / /etherpad/;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
     location /static {
      rewrite /static/(.*) /static/$1 break;
      proxy_pass http://etherpad:9001/;
    }
  }
}

```

* **proxy_pass http://(サービス名)/ の意味**  
dockerでコンテナ間のlinkを設定すると、コンテナの中からリンク先のコンテナ名でIPアドレスの名前解決ができる。最初は意味が分からなかった…
* **SSLの導入**  
特に深い意味はないが、せっかくリバースプロキシを導入したのでSSLも使ってみることにした。80番ポートでのアクセスは全てhttpsにリダイレクトされる
* **Etherpadはちょっと特殊**  
https://github.com/ether/etherpad-lite/wiki/How-to-put-Etherpad-Lite-behind-a-reverse-Proxy
* **静的HTMLの追加**   
Webアプリケーションをサブディレクトリにマップしたので、ルートでアクセスした時に表示するものが何もなくなってしまった。ちょっと寂しいので、各アプリケーションへアクセスするためのホームページをnginxのコンテナに追加した。デザインはこんな感じ：

![スクリーンショット 2015-12-20 4.24.47.png](https://qiita-image-store.s3.amazonaws.com/0/48238/476ef0e2-dd23-6000-59bc-cd8819a93e02.png)