Redash用のdocker-compose.yml

http://qiita.com/uraura/items/a07e520f861f53c41fbf

1年経ち，Redashもバージョンアップして構成も変わり使えなくなってきたので書き直し．

RedashのGithubリポジトリにあがってるdocker-compose.ymlをそのまま使いつつ，SSLで運用できるような内容にしてみます．

# 準備物
```yaml:docker-compose.yml
version: '3'
services:
  server:
    image: redash/redash:${REDASH_VERSION:-latest}
    command: server
    depends_on:
      - postgres
      - redis
    ports:
      - "5000:5000"
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      REDASH_COOKIE_SECRET: e6f7212a1b469019f1a526d59c6c52bc9a803c20a54ed17988b9145a615e1631
      REDASH_GOOGLE_APPS_DOMAIN: foobar.com
      REDASH_GOOGLE_CLIENT_ID: 000000000000-bmexamplee1vklsnexamp1emm0hkl4ga.apps.googleusercontent.com
      REDASH_GOOGLE_CLIENT_SECRET: 6PExamPLEbQ4dMPexAmpLenh
      REDASH_HOST: redash.foobar.com
      REDASH_PASSWORD_LOGIN_ENABLED: "false"
      REDASH_ALLOW_SCRIPTS_IN_USER_INPUT: "true"
      REDASH_DATE_FORMAT: YY/MM/DD
    volumes:
      - /opt/redash/redash_version:/opt/redash/current:ro
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
  worker:
    image: redash/redash:${REDASH_VERSION:-latest}
    command: scheduler
    environment:
      PYTHONUNBUFFERED: 0
      REDASH_LOG_LEVEL: "INFO"
      REDASH_REDIS_URL: "redis://redis:6379/0"
      REDASH_DATABASE_URL: "postgresql://postgres@postgres/postgres"
      QUEUES: "queries,scheduled_queries,celery"
      WORKERS_COUNT: 2
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
  redis:
    image: redis:3.0-alpine
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
  postgres:
    image: postgres:9.5.6-alpine
    volumes:
      - /opt/redash/postgres-data:/var/lib/postgresql/data
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
  letsencrypt:
    image: certbot/certbot:latest
    command: renew -n
    tty: true
    volumes:
      - ssl:/etc/letsencrypt
      - ssl:/var/log/letsencrypt
      - ssl:/var/lib/letsencrypt
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
  nginx:
    image: redash/nginx:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /opt/redash/redash_ssl.conf:/etc/nginx/conf.d/default.conf
      - ssl:/etc/letsencrypt
      #- /opt/letsencrypt/etc/letsencrypt:/etc/letsencrypt
    depends_on:
      - server
    links:
      - server:redash
      - letsencrypt
    logging:
      driver: "json-file"
      options:
        max-size: 3m
        max-file: "5"
# 最初に1度だけ実行してSSL証明書をゲットする
#  letsencrypt-certonly:
#    image: certbot/certbot:latest
#    ports:
#      - "443:443"
#    command: certonly -n -d redash.foobar.com --agree-tos -m ✉️ --standalone
#    volumes:
#      - ssl:/etc/letsencrypt
#      - ssl:/var/log/letsencrypt
#      - ssl:/var/lib/letsencrypt

volumes:
  ssl:
```

docker-composeは`.env`ファイルが存在すればそれを読みこんでくれるので，使用したいRedashのバージョン(Dockerイメージのタグ)を書いておきます．

```bash:.env
REDASH_VERSION=2.0.0.b2963
```
最新を攻めていくスタイル😶

公式のredash/nginxはhttpでの設定しかないため，SSLの設定を入れたnginx.confを用意し，docker-compose.ymlでマウントして差し替えるようにしておきます．

```nginx:redash_ssl.conf
upstream redash {
  server redash:5000;
}

server {
  listen 80;
  server_name redash.foobar.com;
  return 301 https://$host$request_uri;
}

server {
  listen 443 ssl;

  ssl_certificate     /etc/letsencrypt/live/redash.foobar.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/redash.foobar.com/privkey.pem;

  gzip on;
  gzip_types *;
  gzip_proxied any;

  location ^~ /.well-known/acme-challenge/ {
    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 $http_x_forwarded_proto;

    set $le letsencrypt;
    proxy_pass http://$le:80;
    break;
  }

  location / {
    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 $http_x_forwarded_proto;

    proxy_pass       http://redash;
  }
}
```

SSLの証明書はletsencryptで取得するので，その場所を指定．
また，証明書を更新するときはcertbotをstandaloneで起動してそこへアクセスを流すために`~/.well-known/acme-challenge/`へのアクセスはcertbotへ流れるようにしました．

```nginx
    set $le letsencrypt;
    proxy_pass http://$le:80;
```

になっているのは，nginxがconfigを読むときに記述されてるホストが存在するか確認するため．certbotは更新の時しか起動しないため，普通に書いてしまうとそのチェックにひっかかって`configtest`に失敗してしまいます．
ワークアラウンドとして変数に代入しておくと，このチェックはされなくなります．

# やること
## 1．SSL証明書を取得する
docker-compose.ymlの下のほうのコメントになっている部分を戻します．

```shell-session
uraura@rosemary$ docker-compose run --rm letsencrypt-certonly
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for redash.foobar.com
Waiting for verification...
Cleaning up challenges

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at
   /etc/letsencrypt/live/redash.foobar.com/fullchain.pem. Your cert
   will expire on 2017-10-05. To obtain a new or tweaked version of
   this certificate in the future, simply run certbot again. To
   non-interactively renew *all* of your certificates, run "certbot
   renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le
```

成功すればこんな感じ．
失敗したら気合で．．．ドメインが間違ってるとか，ポートが開いてないとか，そのへんはletsencryptの解説を読んでなんとかしましょう．


成功すると，dockerの名前付きボリューム`ssl`にSSLの証明書が保存されます．もう`letsencrypt-certonly`に用は無いのでコメントアウトしてしまいましょう．

## 2．SSL証明書を確認する

```shell-session
uraura@rosemary$ docker-compose run --rm letsencrypt certificates
Saving debug log to /var/log/letsencrypt/letsencrypt.log
openssl not installed, can't check revocation

-------------------------------------------------------------------------------
Found the following certs:
  Certificate Name: redash.foobar.com
    Domains: redash.foobar.com
    Expiry Date: 2017-10-05 04:24:00+00:00 (VALID: 89 days)
    Certificate Path: /etc/letsencrypt/live/redash.foobar.com/fullchain.pem
    Private Key Path: /etc/letsencrypt/live/redash.foobar.com/privkey.pem
-------------------------------------------------------------------------------
```

## 3．Redashを起動する
```shell-session
uraura@rosemary$ docker-compose up -d
Creating redash_worker_1 ...
Creating redash_letsencrypt_1 ...
Creating redash_redis_1 ...
Creating redash_worker_1
Creating redash_postgres_1 ...
Creating redash_letsencrypt_1
Creating redash_postgres_1
Creating redash_redis_1 ... done
Creating redash_server_1 ...
Creating redash_server_1 ... done
Creating redash_nginx_1 ...
Creating redash_nginx_1 ... done

uraura@rosemary$ docker-compose ps
        Name                      Command               State                     Ports
---------------------------------------------------------------------------------------------------------
redash_letsencrypt_1   certbot renew -n                 Exit 0
redash_nginx_1         nginx -g daemon off;             Up       0.0.0.0:443->443/tcp, 0.0.0.0:80->80/tcp
redash_postgres_1      docker-entrypoint.sh postgres    Up       5432/tcp
redash_redis_1         docker-entrypoint.sh redis ...   Up       6379/tcp
redash_server_1        /app/bin/docker-entrypoint ...   Up       0.0.0.0:5000->5000/tcp
redash_worker_1        /app/bin/docker-entrypoint ...   Up       5000/tcp
```

`redash_letsencrypt_1`は終了した状態になっていますが問題ありません．他がUPであればRedashは起動しているはずです．

## 4．SSL証明書を更新する
```shell-session
uraura@rosemary$ docker-compose start letsencrypt
Starting letsencrypt ... done

uraura@rosemary$ docker-compose logs letsencrypt
Attaching to redash_letsencrypt_1
letsencrypt_1  | Saving debug log to /var/log/letsencrypt/letsencrypt.log
letsencrypt_1  |
letsencrypt_1  | -------------------------------------------------------------------------------
letsencrypt_1  | Processing /etc/letsencrypt/renewal/redash.foobar.com.conf
letsencrypt_1  | -------------------------------------------------------------------------------
letsencrypt_1  | Cert not yet due for renewal
letsencrypt_1  |
letsencrypt_1  | The following certs are not due for renewal yet:
letsencrypt_1  |   /etc/letsencrypt/live/redash.foobar.com/fullchain.pem (skipped)
letsencrypt_1  | No renewals were attempted.
```

さっき作ったばっかりなんで更新が無いですが．．．
`renew`は更新期限が近くなければ勝手にスキップしてくれるので，何回実行しても問題ありません．

## 5．SSL証明書の更新を自動化
```bash:update_ssl.sh
#!/bin/bash

pushd /opt/redash
/usr/local/bin/docker-compose start letsencrypt
/usr/local/bin/docker-compose logs -t -f letsencrypt
/usr/local/bin/docker-compose exec nginx nginx -s reload
popd
```

こんな感じのやつをcronとかにセットして毎日動かしておけばいい感じにSSL証明書が更新されていってくれるはずです．(作ったばっかりなので実績なし😶)
※`--force-reneal`とかやれば強制的に更新かけられますが，確認やらでやりすぎたらしくRateLimitで怒られてしまいログが取れず．．．

# まとめ
ちゃんとやるならEC2に立ててELBかましてACM．．とかがお手軽かも．Redashはバージョンアップが早いのでお手軽に運用できるようにしとくのがよさそうな気がするので，あまりお金もかけたくないのでEC2単騎でがんばろうかなと思ったのでした．

あと，certbotコマンドのオプションいろいろ指定しなければならず覚えられないので，docker-compose化しておくのはこれに限らず良いなと思いました．