Systemdとdocker-composeでカジュアルにdockerを運用する

# はじめに

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

コンテナ運用はk8sがデファクトにほぼなったような感じはしますが、オンプレのようなマネージドなk8sが無い環境で、コンテナ化を進めるにあたり、いきなりk8sクラスタを作るというのは敷居が高いです。

そこで、少しづつコンテナ化を始める繋ぎとしてdocker-composeとsystemdを使ってカジュアルにdocker運用してみたので、その際のTipsについて記載します。

ちなみにdocker-composeなので当然サーバ間をまたいだオーケストレーションの層は考えず、単に既存サーバのプロセスをコンテナ化して運用することで、まずはサービスの移植性を高めることを目的としています。

# docker-composeだけで運用する際の問題点

サーバ単体で複数のコンテナを連携させるにはdocker-composeを使うのが容易な方法です。
`-d`をつけるとバックグラウンド実行も可能で、一見これだけでも十分なように思えますが、systemdを使うのは以下のような問題を解決するためです。

- docker-composeだけだと自動起動できない
- どこのdocker-compose.ymlが動いているか不明になる
- up/downを無作為に繰り返すと不要なゴミvolumeが貯まる

# Systemdと組み合わせる

以下のようなsystemdのユニットファイルを用意して、systemd経由でdocker-composeを運用することで前述の問題を解消します。

```shell:/etc/systemd/system/dc@.service

[Unit]
Description=docker-compose %i service
Requires=docker.service

[Service]
User=core
Type=simple

EnvironmentFile=-/etc/sysconfig/compose.d/%i.env
Environment=COMPOSE_FILE=/home/core/compose/%i/docker-compose.yml

ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} kill
ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} rm -v -f
ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} down

ExecStart=/opt/bin/docker-compose -f ${COMPOSE_FILE} up --force-recreate --abort-on-container-exit

ExecStop=/opt/bin/docker-compose -f ${COMPOSE_FILE} stop
ExecStopPost=-/opt/bin/docker-compose -f ${COMPOSE_FILE} rm -v -f
ExecStopPost=-/opt/bin/docker-compose -f ${COMPOSE_FILE} down

Restart=always
RestartSec=180s

[Install]
WantedBy=multi-user.target
```

## 使い方

### ディレクトリ構造を決めてSystemdをtemplate化することで汎用性を持たせる

ファイル名がdc@.serviceとなっていますが、systemdにはUnitファイルをテンプレート化する機能が備わっています。
このユニットファイルを置いた状態で、`systemctl start dc@jenkins.service`とかコマンドを打つと、dc@.serviceファイル内の%iの箇所に@から.までの文字が入ってサービスが起動します。つまりレンダリング後のユニットファイルは以下のようになります。

```shell:dc@jenkins.service

[Unit]
Description=docker-compose jenkins service
Requires=docker.service

[Service]
User=core
Type=simple

EnvironmentFile=-/etc/sysconfig/compose.d/jenkins.env
Environment=COMPOSE_FILE=/home/core/compose/jenkins/docker-compose.yml

ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} kill
ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} rm -v -f
ExecStartPre=-/opt/bin/docker-compose -f ${COMPOSE_FILE} down

ExecStart=/opt/bin/docker-compose -f ${COMPOSE_FILE} up --force-recreate --abort-on-container-exit

ExecStop=/opt/bin/docker-compose -f ${COMPOSE_FILE} stop
ExecStopPost=-/opt/bin/docker-compose -f ${COMPOSE_FILE} rm -v -f
ExecStopPost=-/opt/bin/docker-compose -f ${COMPOSE_FILE} down

Restart=always
RestartSec=180s

[Install]
WantedBy=multi-user.target
```

またtemplate化に際し、docker-composeのファイル置き場を`/home/core/compose/＄｛サービス名｝/docker-compose.yml`とか特定のパスに集約することでcomposeファイルが散らばることを防ぐことができます。
環境変数の読み込み先も、同じくtemplate化によって`/etc/sysconfig/compose.d/${サービス名}.env`に体系を統一することで、systemdの起動templateは同じものを使いながらも、外部から環境変数の注入なdocker-composeサービスだけ、環境変数ファイルを注入するだけで事足りるようになります。


## Systemdなので自動起動も可能
systemd化することでenable/disableによる自動起動の設定が可能になります。

## ExecStartPre/ExecStopPostによるゴミを消す

systemdではExecStartPreとExecStopPostなどを使って起動前、停止後になどに任意の処理をはさめます。
そこでクリーンアップ用のコマンドを挟むことで、docker起動時に生成される稼働用のvolumeがゴミとして残らないようにしています。
ちなみに、上記のユニットファイルで消されるのはdocker起動時にoverlayfsの一番上の層として使われるvolume(なんて言えば良いのか。。。)とanonymous volumeだけで、いわゆるDBなどでデータを永続化するために使うようなnamed volumeは残るようになっています。

# 終わりに

以上、Systemdを使ったカジュアルなdocker運用についてでした。
数個サクッとコンテナを運用するならこんな感じでも運用できるんですが、スケールとか使いやすさとか諸々考えるとオンプレでも最終的にはk8sでコンテナ運用した方が良いと思います。