Zulipのdockerイメージ「docker-zulip」を使ってみた

環境
====

```sh
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.5 LTS
Release:        14.04
Codename:       trusty
```
```sh
$ uname -srvmo
Linux 4.4.0-45-generic #66~14.04.1-Ubuntu SMP Wed Oct 19 15:05:38 UTC 2016 x86_64 GNU/Linux
```
```sh
$ cat /proc/meminfo | grep ^Mem
MemTotal:       16306348 kB
MemFree:         4981548 kB
MemAvailable:   12620380 kB
```

参考
====

* [galexrt/zulip - Docker Hub](https://hub.docker.com/r/galexrt/zulip/)
* [galexrt/docker-zulip: Zulip Server as a Docker Image](https://github.com/galexrt/docker-zulip)
* [Installation on Ubuntu - Docker](https://docs.docker.com/engine/installation/linux/ubuntulinux/)
* [Install Docker Compose - Docker](https://docs.docker.com/compose/install/)


準備
====

### dockerインストール

リポジトリ設定

```sh
$ sudo aptitude update
$ sudo aptitude install apt-transport-https ca-certificates
$ sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
$ echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee /etc/apt/sources.list.d/docker.list
```

インストール

```sh
$ sudo aptitude update
$ apt-cache policy docker-engine
$ sudo aptitutde install docker-engine
```

サービス起動

```sh
$ sudo service docker start
```

ユーザをdockerグループに追加

```sh
$ sudo gpasswd -a $USER docker
```

### docker-composeインストール

docker-composeを使用するのでインストールする。
※ホームディレクトリをコピーするだけでサーバの引越しができるように、バイナリを置くだけで使えるようなツールはホームディレクトリに置くようにしています。

```sh
$ curl -L https://github.com/docker/compose/releases/download/1.8.1/docker-compose-`uname -s`-`uname -m` > ~/bin/docker-compose
$ chmod +x ~/bin/docker-compose
```
```sh
$ docker-compose -v
docker-compose version 1.8.1, build 878cff1
```


インストール
============

### docker-zulipコード取得

```sh
$ cd ~/git
$ git clone https://github.com/galexrt/docker-zulip.git
```

### docker-zulip設定変更

SETTING_NOREPLY_EMAIL_ADDRESS が `noreply@example.com` のままだとインストールスクリプトが中断されるけど、他は何もチェックされないようです。
試しに使ってみるだけなので example.com を home.local に置き換えるのとポート番号の変更だけにしました。

```sh
$ cd docker-zulip
$ sed -i 's/example\.com/home.local/g' docker-compose.yml
$ sed -i 's/80:80/10080:80/g' docker-compose.yml
$ sed -i 's/443:443/10443:443/g' docker-compose.yml
```
```diff
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -23,8 +23,8 @@ redis:
 zulip:
   image: "quay.io/galexrt/zulip:1.4.2"
   ports:
-    - "80:80"
-    - "443:443"
+    - "10080:80"
+    - "10443:443"
   links:
     - database
     - memcached
@@ -41,17 +41,17 @@ zulip:
     SECRETS_email_password: "123456789"
     SECRETS_rabbitmq_password: "zulip"
     SECRETS_secret_key: "REPLCAE_WITH_SECURE_SECRET_KEY"
-    SETTING_EXTERNAL_HOST: "example.com"
-    SETTING_ZULIP_ADMINISTRATOR: "admin@example.com"
-    SETTING_ADMIN_DOMAIN: "example.com"
-    SETTING_NOREPLY_EMAIL_ADDRESS: "noreply@example.com"
-    SETTING_DEFAULT_FROM_EMAIL: "Zulip <noreply@example.com>"
-    SETTING_EMAIL_HOST: "smtp.example.com"
-    SETTING_EMAIL_HOST_USER: "noreply@example.com"
+    SETTING_EXTERNAL_HOST: "home.local"
+    SETTING_ZULIP_ADMINISTRATOR: "admin@home.local"
+    SETTING_ADMIN_DOMAIN: "home.local"
+    SETTING_NOREPLY_EMAIL_ADDRESS: "noreply@home.local"
+    SETTING_DEFAULT_FROM_EMAIL: "Zulip <noreply@home.local>"
+    SETTING_EMAIL_HOST: "smtp.home.local"
+    SETTING_EMAIL_HOST_USER: "noreply@home.local"
     SETTING_ALLOWED_HOSTS: "[ '*', '127.0.0.2' ]"
     ZULIP_AUTH_BACKENDS: "EmailAuthBackend"
-    ZULIP_USER_EMAIL: "example@example.com"
+    ZULIP_USER_EMAIL: "example@home.local"
     ZULIP_USER_PASS: "zulip"
-    ZULIP_USER_DOMAIN: "example.com"
+    ZULIP_USER_DOMAIN: "home.local"
   volumes:
     - "/opt/docker/zulip/zulip:/data:rw"
```


### docker-zulip実行

```sh
$ docker-compose up
```
```shell-session
Creating dockerzulip_redis_1
Creating dockerzulip_memcached_1
Creating dockerzulip_rabbitmq_1
Creating dockerzulip_database_1
Creating dockerzulip_zulip_1
Attaching to dockerzulip_redis_1, dockerzulip_memcached_1, dockerzulip_rabbitmq_1, dockerzulip_database_1, dockerzulip_zulip_1

≪省略≫

rabbitmq_1   |
rabbitmq_1   | =INFO REPORT==== 6-Nov-2016::01:46:20 ===
rabbitmq_1   | accepting AMQP connection <0.513.0> (172.17.0.5:40066 -> 172.17.0.4:5672)
rabbitmq_1   |
rabbitmq_1   | =INFO REPORT==== 6-Nov-2016::01:46:20 ===
rabbitmq_1   | accepting AMQP connection <0.522.0> (172.17.0.5:40068 -> 172.17.0.4:5672)
zulip_1      | 2016-11-06 01:46:21,104 INFO exited: zulip-deliver-enqueued-emails (exit status 1; not expected)
zulip_1      | 2016-11-06 01:46:21,151 INFO spawned: 'zulip-deliver-enqueued-emails' with pid 306
zulip_1      | 2016-11-06 01:46:21,481 INFO exited: zulip-postsetup-create_user (exit status 0; expected)
zulip_1      | 2016-11-06 01:46:21,903 INFO exited: zulip-deliver-enqueued-emails (exit status 1; not expected)
zulip_1      | 2016-11-06 01:46:22,906 INFO spawned: 'zulip-deliver-enqueued-emails' with pid 311
zulip_1      | 2016-11-06 01:46:23,872 INFO exited: zulip-deliver-enqueued-emails (exit status 1; not expected)
zulip_1      | 2016-11-06 01:46:26,644 INFO spawned: 'zulip-deliver-enqueued-emails' with pid 316
zulip_1      | 2016-11-06 01:46:27,639 INFO exited: zulip-deliver-enqueued-emails (exit status 1; not expected)
zulip_1      | 2016-11-06 01:46:31,643 INFO spawned: 'zulip-deliver-enqueued-emails' with pid 321
zulip_1      | 2016-11-06 01:46:32,626 INFO exited: zulip-deliver-enqueued-emails (exit status 1; not expected)
zulip_1      | 2016-11-06 01:46:33,628 INFO gave up: zulip-deliver-enqueued-emails entered FATAL state, too many start retries too quickly
```

FATALが出ているのが気になるが、とりあえず動きました。
https://192.168.0.200:10443/
![image](https://qiita-image-store.s3.amazonaws.com/0/14474/396e78ea-a352-adf8-9435-175a6ee9b4f1.png)

docker-compose.ymlで設定したZULIP_USER_EMAIL、ZULIP_USER_PASSでユーザ登録されているはずですが、ログインできませんでした。
少し調べてみたところ、どうやら設定ファイルに記述したユーザは作成されていないよう。レルムを作成しておかないとユーザ作成が失敗するのですが、レルムを作成している処理がどこにも見当たりませんでした。
**レルムの作成と管理者ユーザの登録は手動で行う必要があります。**

### レルムの作成と管理者ユーザの登録

```sh
# zulipコンテナに接続
$ docker exec -it dockerzulip_zulip_1 /bin/bash
root# su - zulip
$ cd deployments/current

# レルム作成
$ ./manage.py create_realm --domain=home.local --name='home'

# ユーザ作成
$ ./manage.py create_user --this-user-has-accepted-the-tos --domain home.local zulip@home.local 'Zulip'

# パスワード設定
$ ./manage.py changepassword zulip@home.local

# 管理者権限付与
$ ./manage.py knight -f zulip@home.local
```

作成した管理者ユーザでログインすることができました。

https://192.168.0.200:10443/
![image](https://qiita-image-store.s3.amazonaws.com/0/14474/82d55cb6-578d-ea27-f7ff-6c59aaf85e21.png)

### docker-zulip削除

docker-composeは管理対象の複数のコンテナの停止から削除までをコマンド一発で行ってくれるので便利です。データはホスト側に保管するようになっているため、これの削除も行います。

```sh
$ docker-compose down
$ sudo rm -rf /opt/docker/zulip
```

感想
====

データをホスト上に保管するようにしてあるため、zulipをアップデートする場合はコンテナを入れ替えるだけで済み、運用がとても楽になると思います。
インターネットに接続できないネットワーク環境でzulipを使用する場合はこれしかないといった感じ。


おまけ
======

zulipにログインできない原因を調べたときのメモとか。

### データベース内容確認

```sh
# データベース接続
$ docker exec -it dockerzulip_zulip_1 /bin/bash
root# psql -h database -U zulip
```

```psql
# テーブル一覧
zulip=> \d
```
```shell-session
WARNING: terminal is not fully functional
                           List of relations
 Schema |                    Name                    |   Type   | Owner
--------+--------------------------------------------+----------+-------
 zulip  | auth_group                                 | table    | zulip
 zulip  | auth_group_id_seq                          | sequence | zulip
 zulip  | auth_group_permissions                     | table    | zulip
 zulip  | auth_group_permissions_id_seq              | sequence | zulip
 zulip  | auth_permission                            | table    | zulip
 zulip  | auth_permission_id_seq                     | sequence | zulip
 zulip  | confirmation_confirmation                  | table    | zulip
 zulip  | confirmation_confirmation_id_seq           | sequence | zulip
 zulip  | confirmation_realmcreationkey              | table    | zulip
 zulip  | confirmation_realmcreationkey_id_seq       | sequence | zulip
 zulip  | django_content_type                        | table    | zulip
 zulip  | django_content_type_id_seq                 | sequence | zulip
 zulip  | django_migrations                          | table    | zulip
 zulip  | django_migrations_id_seq                   | sequence | zulip
 zulip  | django_session                             | table    | zulip
 zulip  | django_site                                | table    | zulip
 zulip  | django_site_id_seq                         | sequence | zulip
 zulip  | fts_update_log                             | table    | zulip
 zulip  | fts_update_log_id_seq                      | sequence | zulip
 zulip  | guardian_groupobjectpermission             | table    | zulip
 zulip  | guardian_groupobjectpermission_id_seq      | sequence | zulip
 zulip  | guardian_userobjectpermission              | table    | zulip
 zulip  | guardian_userobjectpermission_id_seq       | sequence | zulip
 zulip  | third_party_api_results                    | table    | zulip
 zulip  | zerver_attachment                          | table    | zulip
 zulip  | zerver_attachment_id_seq                   | sequence | zulip
 zulip  | zerver_attachment_messages                 | table    | zulip
 zulip  | zerver_attachment_messages_id_seq          | sequence | zulip
 zulip  | zerver_client                              | table    | zulip
 zulip  | zerver_client_id_seq                       | sequence | zulip
 zulip  | zerver_defaultstream                       | table    | zulip
 zulip  | zerver_defaultstream_id_seq                | sequence | zulip
 zulip  | zerver_huddle                              | table    | zulip
 zulip  | zerver_huddle_id_seq                       | sequence | zulip
 zulip  | zerver_message                             | table    | zulip
 zulip  | zerver_message_id_seq                      | sequence | zulip
 zulip  | zerver_preregistrationuser                 | table    | zulip
 zulip  | zerver_preregistrationuser_id_seq          | sequence | zulip
 zulip  | zerver_preregistrationuser_streams         | table    | zulip
 zulip  | zerver_preregistrationuser_streams_id_seq  | sequence | zulip
 zulip  | zerver_pushdevicetoken                     | table    | zulip
 zulip  | zerver_pushdevicetoken_id_seq              | sequence | zulip
 zulip  | zerver_realm                               | table    | zulip
 zulip  | zerver_realm_id_seq                        | sequence | zulip
 zulip  | zerver_realmalias                          | table    | zulip
 zulip  | zerver_realmalias_id_seq                   | sequence | zulip
 zulip  | zerver_realmemoji                          | table    | zulip
 zulip  | zerver_realmemoji_id_seq                   | sequence | zulip
 zulip  | zerver_realmfilter                         | table    | zulip
 zulip  | zerver_realmfilter_id_seq                  | sequence | zulip
 zulip  | zerver_recipient                           | table    | zulip
 zulip  | zerver_recipient_id_seq                    | sequence | zulip
 zulip  | zerver_referral                            | table    | zulip
 zulip  | zerver_referral_id_seq                     | sequence | zulip
 zulip  | zerver_scheduledjob                        | table    | zulip
 zulip  | zerver_scheduledjob_id_seq                 | sequence | zulip
 zulip  | zerver_stream                              | table    | zulip
 zulip  | zerver_stream_id_seq                       | sequence | zulip
 zulip  | zerver_subscription                        | table    | zulip
 zulip  | zerver_subscription_id_seq                 | sequence | zulip
 zulip  | zerver_useractivity                        | table    | zulip
 zulip  | zerver_useractivity_id_seq                 | sequence | zulip
 zulip  | zerver_useractivityinterval                | table    | zulip
 zulip  | zerver_useractivityinterval_id_seq         | sequence | zulip
 zulip  | zerver_usermessage                         | table    | zulip
 zulip  | zerver_usermessage_id_seq                  | sequence | zulip
 zulip  | zerver_userpresence                        | table    | zulip
 zulip  | zerver_userpresence_id_seq                 | sequence | zulip
 zulip  | zerver_userprofile                         | table    | zulip
 zulip  | zerver_userprofile_groups                  | table    | zulip
 zulip  | zerver_userprofile_groups_id_seq           | sequence | zulip
 zulip  | zerver_userprofile_id_seq                  | sequence | zulip
 zulip  | zerver_userprofile_user_permissions        | table    | zulip
 zulip  | zerver_userprofile_user_permissions_id_seq | sequence | zulip
(74 rows)
```

ユーザ情報を管理していると思われるテーブルの内容を確認する。（フィールドは `\d zerver_userprofile`で調べた）

```psql
# ユーザ一覧
zulip=> select email,domain from zerver_userprofile,zerver_realm where zerver_userprofile.realm_id=zerver_realm.id;
```
```shell-session
            email             |   domain
------------------------------+------------
 emailgateway@zulip.com       | zulip.com
 welcome-bot@zulip.com        | zulip.com
 notification-bot@zulip.com   | zulip.com
 nagios-send-bot@zulip.com    | zulip.com
 nagios-receive-bot@zulip.com | zulip.com
 feedback@zulip.com           | zulip.com
(6 rows)
```

レルムの一覧を確認する。

```psql
zulip=> select domain,name from zerver_realm;
```
```shell-session
   domain   | name
------------+------
 zulip.com  |
(1 rows)
```

docker-compose.ymlで設定したZULIP_USER_DOMAIN、ZULIP_USER_EMAILがデータベースに登録されていないことが分かった。

docker-zulipのリポジトリ内でZULIP_USER_DOMAIN、ZULIP_USER_EMAILを使用している箇所を調べる。

```sh
$ grep -R '\(ZULIP_USER_DOMAIN\|ZULIP_USER_EMAIL\)' .
```
```shell-session
./includes/createZulipAdmin.sh:if [ ! -z "$ZULIP_USER_CREATION_ENABLED" ] && ([ -z "$ZULIP_USER_DOMAIN" ] || [ -z "$ZULIP_USER_EMAIL" ]); then
./includes/createZulipAdmin.sh:    realm = get_realm('$ZULIP_USER_DOMAIN')
./includes/createZulipAdmin.sh:    create_user('$ZULIP_USER_EMAIL', '$ZULIP_USER_PASS', realm, '$ZULIP_USER_FULLNAME', email_to_username('$ZULIP_USER_EMAIL'))
./includes/createZulipAdmin.sh:User = get_user_profile_by_email(email='$ZULIP_USER_EMAIL')
./kubernetes/zulip-rc.yml:        - name: ZULIP_USER_EMAIL
./kubernetes/zulip-rc.yml:        - name: ZULIP_USER_DOMAIN
./docker-entrypoint.sh:export ZULIP_USER_DOMAIN="${ZULIP_USER_DOMAIN:-$(echo $SETTING_EXTERNAL_HOST)}"
./docker-entrypoint.sh:export ZULIP_USER_EMAIL="${ZULIP_USER_EMAIL:-}"
./docker-compose.yml:    ZULIP_USER_EMAIL: "example@home.local"
./docker-compose.yml:    ZULIP_USER_DOMAIN: "home.local"
```

`./includes/createZulipAdmin.sh`でユーザ作成を行っていることが分かる。
`manager.py` でユーザ作成する際に`--domain`を指定しないとエラーになることから、ユーザ作成の前にドメイン（realm）を作成しておく必要があるはずだが、`realm = get_realm('$ZULIP_USER_DOMAIN')`でドメインを参照している処理があるだけで、ドメインを作成している個所がない。

ドメイン取得に失敗したらドメインを作成するという処理が必要と思われる。