Docker Compose + alpineでサーバー一式を構築する

[Dokcerコンテナ上に開発環境を作った時の反省点](http://qiita.com/drafts/0bfb210687d521e8c253)をふまえてやりなおします。

# 基本方針

* ConoHaのDockerイメージ上にalpine[^1]をベースにしてサーバーを構築する
* この記事を分割しないでいいようにシンプルに書く

[^1]: 2022/01追記 今は各ディストリビューションがcompactをリリースしてるのでAlpineを使うメリットは少ないです。libc非互換のバグとか踏みたくなければubuntuやdebial-slimでいいでしょう。ubuntu公式で30MiBくらいです。Alpineは5MiBですがライブラリ追加したら誤差の範囲、かな。大量のcontainerを扱うので少しでも小さい方がいい、という場合はAlpineやscratchから作るのもありですね。実行ファイルをstatic linkでコンパイルしてscratchに入れてる記事を見ましたが、それならdockerにしないで実行ファイルのまま配布すればいいのでは？ギャグのつもりなんでしょうけど。


# サーバー作成

ConoHaコントールパネルで「サーバー追加-アプリケーション-Docker」を選択して「追加」ボタン押下。

![conoha-docker.png](https://qiita-image-store.s3.amazonaws.com/0/16962/ea7896bd-1901-11b4-08b3-0bd1d278ad78.png)
USキーボードを使っているので「VPS設定 - コンソールキーマップ - en-us」も選択してあります。


`# uname -a`で確認すると`Ubuntu14.04.1`ベースです。今回はrootで作業します。bashrcにキーマップを登録しておきます。

* echo loadkeys us >> ~/.bashrc
* ~/.ssh/authorized_keysに鍵を登録
* /etc/ssh/sshd_configでパスワード禁止`PermitRootLogin without-password`
* ローカルPCのLOCALEがsshで転送されるので日本語ファイルを入れておきます。` apt-get install language-pack-ja-base`

確認のためローカルのPCからログインし、その後docker-composeをインストールします。

```bash
curl -L https://github.com/docker/compose/releases/download/1.6.2/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
```

# dns

```Dockerfile:dns/Dockerfile
FROM alpine:latest
RUN apk --update add bind && rm -rf /var/cache/apk/*
EXPOSE 53
CMD ["named", "-c", "/etc/bind/named.conf", "-g", "-u", "named"]
```

named.confとdbファイルはdocker-compose.yamlでマウントします。

```yaml:docker-compose.yml
dns:
  container_name: dns
  build: dns
  volumes:
    - "./dns/bind:/etc/bind"
  ports:
    - "53:53"
    - "53:53/udp"
```

docker-compose.ymlを書いたら`docker-compose up -d`で起動します。`-d`を付けなければフォアグラウンドで起動して出力を見ることが出来ます。これに`docker exec -it dns bash`で接続して動作確認します。この後に出てくるコンテナもこの方式で確認していきます。

しかしコンテナ内で`nslookup dns.example.com 127.0.0.1`しても外部のdnsを見にいってしまいハマりました。alpineで使用しているbusyboxのnslookupにはバグがありserver指定が効かないようです。Dockerホストのnslookupから動作確認しました。
参考:[bugs.busybox.net](https://bugs.busybox.net/show_bug.cgi?id=675)

# http
```Dockerfile:http/Dockerfile
FROM alpine:latest
RUN apk --update add nginx && rm -rf /var/cache/apk/*
RUN chmod 755 /var/lib/nginx /var/lib/nginx/tmp
CMD ["nginx", "-g", "daemon off;"]
```

無駄にハマったのがapkでnginxを入れた直後は/var/lib/nginxのパーミッションが`drwx------ root root`になっており、リバースプロキシする時に4kb以上のファイルがあると

```
open() "/var/lib/nginx/tmp/proxy/1/00/0000000001" failed (13: Permission denied) while reading upstream, client:
```
のようなエラーを吐いてうまくいかないことです。Dockerfileにchmodを書いて解決しました。

```yaml:docker-compose.yaml
http:
  container_name: http
  build: http
  volumes:
    - "./http/nginx.conf:/etc/nginx/nginx.conf"
    - "./http/conf.d:/etc/nginx/conf.d"
  ports:
    - "80:80"
```

apkで取得したnginx.confではserver{}の中にlistenが指定されているため、前回のgitbucket/jenkinsの設定は読み込めませんでした。デフォルトからコメント行とlisten行を削除し、conf.dのincludeとuploadサイズの設定を入れたものがこちらです。

```nginx:nginx.conf
#user  nobody;
worker_processes  1;
events {
    worker_connections  1024;
}
http {
    include       mime.types;
    default_type  application/octet-stream;
    sendfile        on;
    keepalive_timeout  65;
    server {
        server_name  localhost;
        location / {
            root   html;
            index  index.html index.htm;
        }
        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }
    }
    # add
    include /etc/nginx/conf.d/*.conf;
    client_max_body_size 50m;
}
```

# GitBucket

Alpineベースの[f99aq8ove/docker-gitbucket](https://github.com/f99aq8ove/docker-gitbucket)を使います。

```yaml:docker-compose.yml
gitbucket:
  image: "f99aq8ove/gitbucket"
  container_name: gitbucket
  volumes:
    - "./gitbucket/data:/gitbucket"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "8080:8080"
```

# Jenkins

前回はJenkinsコンテナにXvfbやらfirefoxを入れていたのですが、それはJenkinsのスレーブノードにやらせればよさそうなので今回はJenkins単体にします。
参考:[Docker ComposeでJenkinsとSelenium Gridを一気に立ち上げる](http://qiita.com/ootaken/items/26d37cb34d3b575277e0)

```bash
mkdir -p jenkins/data
chown 1000:1000 jenkins/data
```

```yaml:docker-compose.yml
jenkins:
  image: "blacklabelops/jenkins:alpine"
  container_name: jenkins
  volumes:
    - "./jenkins/data:/jenkins"
  ports:
    - "8081:8080"
```

# mail
smtp/pop/imapを一つのコンテナに立てます。docker的には1コンテナ1サービスが原則だと思いますが、特に送信サーバーをスケールする予定もないしどうせrsyslogdもあげるのでまとめてしまいました。

```:Dockerfile
FROM alpine:latest
RUN apk --update add postfix dovecot rsyslog && rm -rf /var/cache/apk/*

ADD main.cf /etc/postfix/main.cf
ADD valias /etc/postfix/valias
ADD vmailbox /etc/postfix/vmailbox
RUN postmap /etc/postfix/valias
RUN postmap /etc/postfix/vmailbox

ADD 10-auth.conf /etc/dovecot/conf.d/10-auth.conf
ADD 10-mail.conf /etc/dovecot/conf.d/10-mail.conf
ADD auth-passwdfile.conf.ext /etc/dovecot/conf.d/auth-passwdfile.conf.ext

CMD ["sh", "-c", "rsyslogd; postfix start; dovecot; tail -f /dev/null;"]
```

alpineベースにpostfixとdovecotを入れ、バーチャルホストを使った複数ドメイン対応設定です。

```:main.cf
mydomain = example.com
inet_interfaces = all
virtual_mailbox_domains = example.com, example.jp
virtual_mailbox_base = /var/spool/mail/vhosts
virtual_mailbox_maps = hash:/etc/postfix/vmailbox
virtual_alias_maps = hash:/etc/postfix/valias
virtual_minimum_uid = 89
virtual_uid_maps = static:89
virtual_gid_maps = static:89
recipient_delimiter = -
```
```:valias
alias user@example.com
```
```:vmailbox
user@example.com example.com/user
user@example.jp example.jp/user
```
alpineではデフォルトでuid:89がvpopmailになっています。この権限でバーチャルホスト用のフォルダを用意し読み書きさせます。postfixではdbファイルの作成にpostmapコマンドを実行する必要があるので、DockerfileでRUNさせます。

以下dovecot用設定ファイルです。

```:10-auth.conf
disable_plaintext_auth = no
```
gmailからアクセスするのにSSLだと自己署名の証明書が使えないので、plaintextを許可してます。

```:10-mail.conf
mail_location = maildir:~/
namespace inbox {
  inbox = yes
}
mail_uid = 89
mail_gid = 89
first_valid_uid = 89
first_valid_gid = 89
mbox_write_locks = fcntl
```

maildirの設定とuidの指定を行います。

```:auth-passwdfile.conf.ext
passdb {
  driver = passwd-file
  args = /var/spool/mail/vhosts/passwd
}
userdb {
  driver = passwd-file
  args = /var/spool/mail/vhosts/passwd
}
```
```:passdb
user@example.com:{CRYPT}xxx::::/var/spool/mail/vhosts/example.com/user:/sbin/nologin
```

アカウントファイルの設定です。vhostsはpostfixのvirtual_mailbox_baseになっていて、postfixがここに配送したメールをdovecotが受け取ります。


```yaml:docker-compose.yml
mail:
  container_name: mail
  build: mail
  volumes:
    - "./mail/vhosts:/var/spool/mail/vhosts"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "25:25"
    - "110:110"
    - "143:143"
    - "993:993"
    - "995:995"
```

# まとめ

最終的なdocker-compose.yml

```yaml:docker-compose.yml
dns:
  container_name: dns
  build: dns
  volumes:
    - "./dns/bind:/etc/bind"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "53:53"
    - "53:53/udp"
http:
  container_name: http
  build: http
  volumes:
    - "./http/nginx.conf:/etc/nginx/nginx.conf"
    - "./http/conf.d:/etc/nginx/conf.d"
    - "./http/public_html:/usr/share/nginx/html"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "80:80"
gitbucket:
  image: "f99aq8ove/gitbucket"
  container_name: gitbucket
  volumes:
    - "./gitbucket/data:/gitbucket"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "8080:8080"
jenkins:
  image: "blacklabelops/jenkins:alpine"
  container_name: jenkins
  volumes:
    - "./jenkins/data:/jenkins"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "8081:8080"
mail:
  container_name: mail
  build: mail
  volumes:
    - "./mail/vhosts:/var/spool/mail/vhosts"
    - "/etc/localtime:/etc/localtime:ro"
  ports:
    - "25:25"
    - "110:110"
    - "143:143"
    - "993:993"
    - "995:995"
```

自分で立てたサーバーの設定資料としてまとめたのですが、dnsやmailの設定情報をちゃんと載せてないので、後からこの資料だけ見て同じサーバーを立てるのは厳しいですね。

alpineは情報が少ないのと、apkがこなれてなくてデフォルトだとうまく動かないといったことがあり注意が必要です。