Slackクローンと謳われるMattermostをdocker-composeで構築したときのメモ

# はじめに
関わる人数が多い場合、Slackを導入することって月額の費用的に苦しい時ってありますよね？
弊社の某プロジェクトでは、委託やらインターンやら関わる人数が膨大でSlack導入はコスト的に難しいため、Mattermostを社内に構築して運用することになりました。
サーバー代だけで運用できるっていいですね。
インフラ構築が久々だったこともあり、色々とハマったのでメモとして残しておきます。

# Mattermostとは
MattermostはOSSのチャットサービスです。
Slackとほとんど同様の機能が提供されているので、サーバー代だけで基本的なSlackの機能を利用することができます。
2022年にSlackの無償版のポリシー変更があり、Mattermostに移行したユーザーも少なくないのではないでしょうか。

# 今回の環境情報
* AWS Lightsail
* Ubuntu 20.04 LTS
* docker compose v2.16.0
* mattermost 7.9.2

# AWS Lightsailの払い出し
Lightsailのコンソールからインスタンスを作成する。
プラットフォームはLinux/Unixで、OSのみでUbuntu（20.04 LTS）を選択。
プランはとりあえず、月5ドルの、メモリ1GB、1vCPU、ストレージ60GBのSSD、転送3TB。
今回は利用人数が多いので、一旦これで構築してからスケールアップさせていこうと思います。

# ドメイン名の紐付け
Lightsailは再起動するとパブリックIPが変わるので、静的IPをアタッチして固定IPになるようにLightsailコンソールから変更します。
次に、ドメインを管理しているサービスサイトからゾーンの情報を変更して、今回の固定IPをAレコードで追加します。
`dig <YOUR_DOMAIN> +short` などでドメインが正常に設定されたか確認することができます。

# Docker / Docker Composeのインストール
Docker Composeを用いて導入しようと思うので、DockerとDocker composeをインストールします。

まずはSSHで接続。Lighsailが提供しているブラウザのSSHクライアントは楽でいいのですが、ファイルを編集するときなど使いづらかったので、macのコンソールから接続した方が使いやすかったです。

### Dockerのインストール
aptを使ってdockerのインストール。
```
sudo apt update
sudo apt install docker.io
sudo systemctl start docker
```

確認する
```
docker -v
```


### Docker Composeのインストール

バージョンは `v2.16.0` の部分を状況に合わせて変更してください。
```
sudo curl -L "https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
```

確認する
```
docker-compose --version
```

# Mattemostのインストール
リポジトリのクローンを作成
```
git clone https://github.com/mattermost/docker
cd docker
```
`env.exsample`を編集して、`DOMAIN`の値を設定したいドメイン名に変更する

そして `.env` ファイルをコピーして生成する
```
cp env.example .env
```

必要なディレクトリを生成して、それらの権限を設定する
```
mkdir -p ./volumes/app/mattermost/{config,data,logs,plugins,client/plugins,bleve-indexes}
sudo chown -R 2000:2000 ./volumes/app/mattermost
```

# SSL証明書

httpsでアクセスするために証明書を準備します。（Let’s Encryptを利用しています）

* 新しく証明書とキーを準備します
```
bash scripts/issue-certificate.sh -d <YOUR_DOMAIN> -o ${PWD}/certs
```

メールアドレスなど聞かれたり色々ありますが、そのまま進むと正常に証明書を発行することができます。

次に、発行された証明書の権限を変更します。

```
sudo chmod +rx certs/etc/letsencrypt/live
sudo chmod +rx certs/etc/letsencrypt/archive
```

次に、`.env`ファイルのCERT_PATHとKEY_PATHをコメントアウトし、その下にあるCERT_PATH及びKEY_PATHのコメントアウトを外します。

```
#CERT_PATH=./volumes/web/cert/cert.pem
#KEY_PATH=./volumes/web/cert/key-no-password.pem
#GITLAB_PKI_CHAIN_PATH=<path_to_your_gitlab_pki>/pki_chain.pem
CERT_PATH=./certs/etc/letsencrypt/live/${DOMAIN}/fullchain.pem
KEY_PATH=./certs/etc/letsencrypt/live/${DOMAIN}/privkey.pem
```

SSL証明書の自動更新についてはできていない気がするので、また別途対応しようと思います。

# Nginxの設定

MattermostのDocker-composeには、Nginxも含まれているのですが、なぜかその方法だとうまくいかなったので、今回は自分でインストールしたNginxを利用してMattermostを構築することにしました。
ので、Nginxをインストールします。

```
sudo apt install nginx
```

`/etc/nginx/sites-available/default` を編集します。
こちらがNginxがデフォルトで利用している設定ファイルです。
Mattemostは8065のポートで通信しているので、Nginxでリバーシプロキシしてドメインのみでhttpsでアクセスできるように以下の内容に変更します。
※ [mattermostのdocker付属の設定ファイル](https://github.com/mattermost/docker/blob/main/nginx/conf.d/default.conf)を一部変更して作成
```
# mattermost
# config can be tested on https://www.ssllabs.com/ssltest/ and a good nginx config generator
# can be found at https://ssl-config.mozilla.org/

# proxy cache
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=mattermost_cache:10m max_size=3g inactive=120m use_temp_path=off;

# upstream used in proxy_pass below
# upstream backend {
    # ip where Mattermost is running; this relies on a working DNS inside the Docker network
    # and uses the hostname of the mattermost container (see service name in docker-compose.yml)
#    server mattermost:8065;
#    keepalive 64;
#}

# vhosts definitions
server {
    server_name <YOUR_DOMAIN>;
    listen 80 default_server;
    listen [::]:80 default_server;

    # redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
    return 301 https://$host$request_uri;
}

server {
    server_name <YOUR_DOMAIN>;
    listen 443 ssl http2 default_server;
    listen [::]:443 ssl http2 default_server;

    # logging
    access_log /var/log/nginx/mm.access.log;
    error_log /var/log/nginx/mm.error.log warn;

    # gzip for performance
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript application/rss+xml application/atom+xml image/svg+xml;

    ## ssl
    # ssl_dhparam /dhparams4096.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;

    # intermediate configuration
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # certs sent to the client in SERVER HELLO are concatenated in ssl_certificate
    ssl_certificate /home/ubuntu/docker/certs/etc/letsencrypt/live/<YOUR_DOMAIN>/fullchain.pem;
    ssl_certificate_key /home/ubuntu/docker/certs/etc/letsencrypt/live/<YOUR_DOMAIN>/privkey.pem;

    # enable TLSv1.3's 0-RTT. Use $ssl_early_data when reverse proxying to prevent replay attacks.
    # https://nginx.org/en/docs/http/ngx_http_ssl_module.html#ssl_early_data
    ssl_early_data on;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    #resolver 1.1.1.1;

    # verify chain of trust of OCSP response using Root CA and Intermediate certs
    #ssl_trusted_certificate /etc/ssl/certs/ca-certificates.crt;

    ## security headers
    # https://securityheaders.com/
    # https://scotthelme.co.uk/tag/security-headers/
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy no-referrer;
    add_header Strict-Transport-Security "max-age=63072000" always;
    add_header Permissions-Policy "interest-cohort=()";

    ## locations
    # ACME-challenge
    location ^~ /.well-known {
        default_type "text/plain";
        root /usr/share/nginx/html;
        allow all;
    }

    # disable Google bots from indexing this site
    location = /robots.txt {
        add_header Content-Type text/plain;
        return 200 "User-agent: *\nDisallow: /\n";
    }

    location ~ /api/v[0-9]+/(users/)?websocket$ {
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        client_max_body_size 50M;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Frame-Options SAMEORIGIN;
        proxy_set_header Early-Data $ssl_early_data;
        proxy_buffers 256 16k;
        proxy_buffer_size 16k;
        client_body_timeout 60;
        send_timeout 300;
        lingering_timeout 5;
        proxy_connect_timeout 90;
        proxy_send_timeout 300;
        proxy_read_timeout 90s;
        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:8065;
    }

    location / {
        client_max_body_size 50M;
        proxy_set_header Connection "";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Frame-Options SAMEORIGIN;
        proxy_set_header Early-Data $ssl_early_data;
        proxy_buffers 256 16k;
        proxy_buffer_size 16k;
        proxy_read_timeout 600s;
        proxy_cache mattermost_cache;
        proxy_cache_revalidate on;
        proxy_cache_min_uses 2;
        proxy_cache_use_stale timeout;
        proxy_cache_lock on;
        proxy_http_version 1.1;
        proxy_pass http://127.0.0.1:8065;
    }
}
```

設定を変更した後はNginxを再起動します。

```
sudo systemctl restart nginx
```

# Mattermostのデプロイ

docker-composeが配置してあるフォルダに移動し、以下のコマンドでMattermostをデプロイします。
```
sudo docker-compose -f docker-compose.yml -f docker-compose.without-nginx.yml up -d
```
ドメインにhttpsで接続すると無事にMattermostの画面ができるかと思います！
（Lightsailのfirewall設定でhttpsのアクセスが通るようにしておいてね）

ちなみにMattermostを停止するときのコマンドは以下です。

```
sudo docker-compose -f docker-compose.yml -f docker-compose.nginx.yml down
```

Mattermostのバージョンを変更したい際は、`.env`のMATTERMOST_IMAGEおよびMATTERMOST_IMAGE_TAGを変更ください。
MATTERMOST_IMAGEはmattermost-enterprise-editionかmattermost-team-editionを使うことになるかと思います。
バージョンについては以下などをご確認ください。
https://hub.docker.com/r/mattermost/mattermost-enterprise-edition

# その他Mattermostの初期設定
### SMTP
こちらを設定しないとMattermost側からメールが送信できないです。
利用しているメールサービスの設定を入力して有効化しましょう。

### プッシュ通知サーバー
スマホアプリから利用できるように、TPNS接続を利用して、有効化しておきましょう。

### 言語
デフォルトで表示される言語は、サイト設定から変更することができます。
参加者が日本人の場合、参加者を招待する前に日本語にしておくと優しいかと思います。

### サイトのカスタマイズ
ログインする際のサイト名や画像などを設定することができます。
設定するとそれっぽくなると思います。

### 通知関係の設定
メールなどで通知を送る際の表示名やアドレスなどの設定もサイト設定から行うことができます。
参加者を招待する前にやっておきましょう。

### ファイルストレージ
デフォルトではローカルファイルシステムに添付するファイルや画像が保存されます。
堅牢性からS3を利用しようと思っていたのですが、うまく変更できなかったので、こちらについては別途対応しようと思っています。


# まとめ
Mattermostについては「使う予定はないけど構築してみた！」って情報が多くて、実際に運用するとなると、ポートやhttps接続やらプッシュ通知やらソケット通信やら色々と他にも設定しないといけないことが多くて、色々と調べながらやることになりました。
参考になれば幸いです。

# SSL証明書の更新

https://qiita.com/girlfellfromsky/items/2eed0c0aa11093561b0a

# 公式手順

https://docs.mattermost.com/guides/deployment.html#deploy-mattermost-for-production-use