digdagサーバをコンテナで動かしてジョブもコンテナで動かす際の設定例

# はじめに

この記事は[MicroAd Advent Calendar 2017](https://qiita.com/advent-calendar/2017/microad)の1日目の記事です。

Digdagはジョブをコンテナで実行する事ができ、スケールが容易なワークフローエンジンです。
本エントリではそんなDigdagサーバ自体もコンテナで動かしながらジョブもコンテナで動かす際の設定やハマり所を紹介します。

##### 使用ソフトウェアとバージョン

- digdag: 0.9.21
- docker: 1.12.6

# 全体像

先にどんな感じの構成を実現しようとしているか、全体像を図示します。


```
               _export:
                 docker:
                   image: python:3

               +-------------+
               |             |
               |             |
           +---+----+   +----v---+
           |        |   |        |
docker run | digdag |   | python |
 +---------> server |   | (job)  |
 |         |        |   |        |
 |         +--------+   +--------+
 |
 |       +-------------------------+
 |       |         docker          |
 |       +-------------------------+
 |       +-------------------------+
 +-------+         HostOS          |
         +-------------------------+
```

こんな感じです。
digdagサーバ自体もコンテナとして動きつつ、ジョブのコンテナも兄弟コンテナとして立ち上げます。
だもんでdigdagサーバは`docker.sock`をマウントする感じになります。
digdagサーバとジョブのどちらもコンテナ化する事により、スケールなど容易になります。

ちなみに今回紹介するサンプルは以下に突っ込んでいます。

https://github.com/kanga333/digdag-server
https://hub.docker.com/r/kanga333/digdag-server/

# digdagサーバーのコンテナを作る

digdagサーバのDockerイメージはこんな感じになります。

```bash:Dockerfile
FROM openjdk:8-jdk

ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64

RUN apt-get update && apt-get install -y \
  curl gettext-base postgresql-client \
  && rm -rf /var/lib/apt/lists/*

# Installin docker client
ENV DOCKER_CLIENT_VERSION=1.12.6 \
    DOCKER_API_VERSION=1.24
RUN curl -fsSL https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_CLIENT_VERSION}.tgz \
  | tar -xzC /usr/local/bin --strip=1 docker/docker

# Installing digdag server
RUN curl -o /usr/local/bin/digdag --create-dirs -L "https://dl.digdag.io/digdag-latest" && \
  chmod +x /usr/local/bin/digdag

# Environment variable for default setting
ENV POSTGRES_USER=digdag \
    POSTGRES_PASSWORD=digdag \
    POSTGRES_HOST=postgresql \
    POSTGRES_PORT=5432 \
    POSTGRES_DB=digdag \
    LOG_TYPE=local \
    ENCRYPTION_KEY=MDEyMzQ1Njc4OTAxMjM0NQ==
  
COPY files/entrypoint.sh /usr/local/bin
COPY files/server.properties /etc/digdag/server.properties
RUN chmod +x /usr/local/bin/entrypoint.sh

EXPOSE 65432 65433
  
ENTRYPOINT ["/usr/local/bin/entrypoint.sh","/usr/local/bin/digdag","server","--config","/etc/digdag/server.properties"]
```

```properties:server.properties
server.bind = 0.0.0.0
server.port = 65432
server.admin.bind = 0.0.0.0
server.admin.port = 65433
server.access-log.pattern = json

database.type = postgresql
database.user = $POSTGRES_USER
database.password = $POSTGRES_PASSWORD
database.host = $POSTGRES_HOST
database.port = $POSTGRES_PORT
database.database = $POSTGRES_DB

log-server.type = $LOG_TYPE
log-server.s3.bucket = $LOG_S3_BUCKET
log-server.s3.path = $LOG_S3_PATH
log-server.s3.credentials.access-key-id = $AWS_ACCESS_KEY_ID
log-server.s3.credentials.secret-access-key = $AWS_SECRET_ACCESS_KEY

digdag.secret-encryption-key = $ENCRYPTION_KEY
```

```bash:entrypoint.sh
#!/usr/bin/env bash
set -e

# rendering server.properties
envsubst < /etc/digdag/server.properties > /etc/digdag/server.properties

# rendering pgpass file
echo "$POSTGRES_HOST:$POSTGRES_PORT:$POSTGRES_DB:$POSTGRES_USER:$POSTGRES_PASSWORD" > ~/.pgpass
chmod 600 ~/.pgpass

# wait for postgresup
until psql -h "$POSTGRES_HOST" -U "$POSTGRES_USER" -p "$POSTGRES_PORT" -c '\l'; do
  >&2 echo "Postgres is unavailable - sleeping"
  sleep 10
done

>&2 echo "Postgres is up - executing command"

exec "$@"
```

ポイントは以下３点です。

- Dockerクライアントをインストールする
- DB接続先などの設定をある程度環境変数で差し替えれるようにする
- PostgreSQLの立ち上がりを待つ

それぞれについての詳細です。

## Dockerクライアントをインストールする

digdagサーバを動かすコンテナはジョブ実行コンテナを起動させる必要があります。
今回の方法ではマウントしたdocker.sockを通じてホストのDockerデーモンとやり取りすることを想定しています。

そのためDockerクライアントのインストールをしています。
Dockerクライアントのインストールについては以下の記事に詳しく書かれており参考になります。
https://qiita.com/minamijoyo/items/c937fb4f646dc1ff064a

## DB接続先などの設定をある程度環境変数で差し替えれるようにする

Dockerイメージを作るに際し、DB接続先やパスワードなんかの設定は環境変数で変更できるようにしておくと、手元の実行時や本番での実行時に接続先を簡単に切り替えることができて何かと便利です。
Digdagサーバは現状では設定を環境変数で変更できるようにはなってはいないので、entrypoint.shみたいinitスクリプトとenvsubstを使ってコンテナ起動時に設定ファイルを環境変数からレンダリングするようにしました。

ちなみに先程参考にした記事と同じ人の記事ですが、envsubstについては以下に詳しく書かれており参考になります。
https://qiita.com/minamijoyo/items/63ae57b99d4a4c5d7987

シグナルのハンドルを真面目に考えるとinitはもう少しちゃんと考えないといけないのですが、一旦まぁこれで。

## PostgreSQLの立ち上がりを待つ

docker-composeとかでdigdagとPostgreSQLを同時に起動したりすると、PostgreSQLの起動が完了する前にdigdagがPostgreSQLに接続に行き、例外を起こしてダウンしてしまうケースがあります。

本番で使う場合はPostgreSQLは別立てするなりマネージド使ったりするケースが殆どだと思うので、あまり気にはならないですが、手元で確認するときに若干面倒なので、initスクリプトにdigdagの起動を待つ処理を入れています。

# docker-composeのサンプル

作ったDockerイメージをdocker-composeで起動するならこんな感じになります。

### 手元で動かす場合

```yaml:docker-compose.yml
version: '2'
services:
  digdag:
    image: kanga333/digdag-server
    ports:
      - "65432:65432"
      - "65433:65433"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /tmp:/tmp
  postgresql:
    image: postgres:9
    environment:
      - POSTGRES_USER=digdag
      - POSTGRES_PASSWORD=digdag
      - POSTGRES_DB=digdag
    volumes:
     - data:/var/lib/postgresql/data
    command: 
     - postgres
     - -c
     - superuser_reserved_connections=30
     - -c
     - max_connections=1000
     
volumes:
  data:
```

### 本番で動かすなら

```yaml:docker-compose.yml
version: '2'
services:
  digdag:
    image: kanga333/digdag-server
    ports:
      - "65432:65432"
      - "65433:65433"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /tmp:/tmp
    environment:
      - POSTGRES_USER=digdag
      - POSTGRES_DB=digdag
      - POSTGRES_HOST=production_pg
      - LOG_TYPE=s3
      - LOG_S3_BUCKET=hogehoge-digdag
      - LOG_S3_PATH=logs/
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
      - POSTGRES_PASSWORD
      - ENCRYPTION_KEY
    
```

クレデンシャルな情報はホストの環境変数から渡す想定です。

## /tmpのマウント意図

最後にこれが一番重要というかハマり所なんですが、Digdagサーバもジョブもコンテナで動かす場合、digdagサーバはホストの/tmpをマウントする必要があります。

digdagはPyオペレータなどのジョブをコンテナで動かそうとした場合、一旦digdagサーバの/tmp配下にワークディレクトリを作ってPythonスクリプトを出力し、ジョブコンテナはそのワークディレクトリをマウントすることで必要なPythonスクリプトを参照する、といった動きをします。

digdagサーバもコンテナで起動している場合は、コンテナ内部で動いているdigdagサーバのファイルシステム空間と、ジョブコンテナを実際に起動させるdockerデーモンがいるホストのファイルシステム空間が異なるためスクリプトがうまく渡せないという事象が発生します。

/tmpをマウントすることでコンテナ内のDigdagサーバもホストのファイルシステムの/tmpを認識するので上手くファイルを受け渡す事ができるようになります。

ちなみにこの問題を[issue](https://github.com/treasure-data/digdag/issues/649)に上げた所、回避策を@hiroysatoさんと@mapk0yさんに教えて頂きました。ありがとうございました。

# 終わりに

以上、Digdagサーバをコンテナ化する際の一例でした。
余裕があれば、いつか他のDigdag運用TIPSも記事にできたらなと思います。