docker-compose up を Ctrl+c(SIGINT) で停止させた時に ERROR: 2 になってしまう問題の対処法

:::note warn
警告
本記事は仮説を提示しているだけで、根本的なエラー原因を断定しているわけではありません。
:::

# 問題の発生状況

## 環境
Rails, PostgreSQL構成のWebアプリケーションをDockerで包んで開発していました。
それぞれのバージョンは下記の通りです。
```plaintext
$ docker-compose -v
docker-compose version 1.29.2, build 5becea4c
$ docker -v
Docker version 20.10.12, build e91ed57

$ ruby -v
ruby 3.1.0p0 (2021-12-25 revision fb4df44d16) [x86_64-linux]
$ rails -v
Rails 7.0.3.1
$ postgres --version
postgres (PostgreSQL) 14.4 (Debian 14.4-1.pgdg110+1)
```

## 遭遇した問題
`docker-compose up`をフォアグラウンドで実行後、`Ctrl+c`でコンテナを停止しようとしたところ、タイトルにある`ERROR: 2`が発生しコンテナが**KILL**されてしまいました。
このエラーは本記事執筆時の半年くらい前（2021/11/01くらい？）から発生していました。
KILLされても特に問題なく放置していたのですが、終了ステータスが2となるのがなんだか気持ち悪いので対処することにしました。

<details><summary>詳しいターミナルの出力</summary>

```
~/devs/workspace $ docker-compose up
Recreating workspace-postgres-1 ... done
Recreating workspace-api-1      ... done
Attaching to workspace_postgres_1, workspace_api_1
postgres_1  | 
postgres_1  | PostgreSQL Database directory appears to contain a database; Skipping initialization
postgres_1  | 
postgres_1  | 2022-07-16 12:59:10.901 UTC [1] LOG:  starting PostgreSQL 14.4 (Debian 14.4-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
postgres_1  | 2022-07-16 12:59:10.901 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
postgres_1  | 2022-07-16 12:59:10.902 UTC [1] LOG:  listening on IPv6 address "::", port 5432
postgres_1  | 2022-07-16 12:59:10.907 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
postgres_1  | 2022-07-16 12:59:10.920 UTC [27] LOG:  database system was shut down at 2022-07-16 12:57:11 UTC
postgres_1  | 2022-07-16 12:59:10.927 UTC [1] LOG:  database system is ready to accept connections
api_1       | => Booting Puma
api_1       | => Rails 7.0.3.1 application starting in development 
api_1       | => Run `bin/rails server --help` for more startup options
api_1       | Puma starting in single mode...
api_1       | * Puma version: 5.6.4 (ruby 3.1.0-p0) ("Birdie's Version")
api_1       | *  Min threads: 5
api_1       | *  Max threads: 5
api_1       | *  Environment: development
api_1       | *          PID: 1
api_1       | * Listening on http://0.0.0.0:3000
api_1       | Use Ctrl-C to stop
^CGracefully stopping... (press Ctrl+C again to force)
Killing workspace_api_1         ... done
Killing workspace_postgres_1    ... done
ERROR: 2
```
</details>

# 対処法
早い話、`Docker Compose　V1`で発生しているエラーなので、`docker compose`コマンドを使用して`Docker Compose　V2`でコンテナを起動するとエラーがなくなり正常終了します。
```bash:V2の使用方法
docker compose up # docker-composeの"-"がいらない！
```
`Docker Compose`のV1,V2間の具体的な変更点はここでは取り扱わないので下記記事を参考にしてください。

https://qiita.com/zembutsu/items/d82b2ae1a511ebd6a350

https://zenn.dev/miroha/articles/whats-docker-compose-v2

# 原因
[V2に移行後は正常終了するようになった](https://github.com/docker/compose/issues/9518)みたいですが、`Docker Compose`の内部実装を読む気力はない（ごめんなさい🙇‍♂️）ので、**あくまで仮説を示します。仮説が根本原因だと鵜呑みにしないように注意してください。**
詳しい方はご指摘いただけますと幸いです。

## `docker-compose` で起動されるプロセス
`Docker Compose V1`でコンテナを起動（`docker-compose up`）すると内部的に`/usr/local/bin/docker-compose-v1 up`が子プロセスで実行されます。
```bash:docker-compose up実行後のプロセス
$ ps -o pid,ppid,pgid,command | grep docker-compose 
89200 85167 89199 grep docker-compose
89141 84878 89141 docker-compose up
89142 89141 89141 /usr/local/bin/docker-compose-v1 up
```
2つプロセスが立ち上がっているところが気になったので一つずつ`kill`コマンドで`SIGINT`を送ってみることにしました。
※便宜上、pid=89141(docker-compose up)を**プロセス1**、pid=89142(/usr/local/bin/docker-compose-v1 up)を**プロセス2**と呼ぶことにします。

## `kill`コマンドで`SIGINT`を送ってみる
```bash:プロセス1に対して
$ kill -s INT 89141
# docker-compose upしている端末で
Gracefully stopping... (press Ctrl+C again to force)
Stopping workspace_api_1        ... done
Stopping workspace_postgres_1   ... done
# 期待通り正常終了
$ ps -eo pid,ppid,pgid,command | grep docker-compose
89537 85167 89536 grep docker-compose
# プロセスも残っていない
```

```bash:プロセス2に対して
$ kill -s INT 89142
# docker-compose upしている端末で
Gracefully stopping... (press Ctrl+C again to force)
Stopping workspace_api_1        ... done
Stopping workspace_postgres_1   ... done
# 期待通り正常終了
$ ps -eo pid,ppid,pgid,command | grep docker-compose
89551 85167 89550 grep docker-compose
# プロセスも残っていない
```

1プロセスずつ`SIGINT`を送ると **正常に停止(Stop)** しました。
`Ctrl+C`では異常にKillされる一方で、`kill`では正常にStopされるのはなんだかおかしいですね。

## `Ctrl+C`の挙動
`Ctrl+C`を入力すると端末から実行プロセスの所属する**プロセスグループ**に対してシグナル`SIGINT`が送られます。
つまり、プロセス1,2両方に対して`SIGINT`が送られることになります。

`kill`コマンドで`Ctrl+C`の挙動を再現してみます。

```bash:プロセス1,2両方に対して
$ kill -s INT 89141　89142
# docker-compose upしている端末で
Gracefully stopping... (press Ctrl+C again to force)
Killing workspace_api_1       ... done
Killing workspace_postgres_1  ... done
ERROR: 2
# 異常終了！！！！
```

`Ctrl+C`の時と同様に異常終了しました。
ここまでの各プロセスの終了とコンテナの正常終了可否を表にすると下記のようになります。
| プロセス1のみ | プロセス2のみ | プロセス1,2両方同時(Ctrl+C) |
|:-:|:-:|:-:|
| ✅正常 | ✅正常 | ❌異常 |

## 結論(仮説)：`Docker Compose V1`ではコンテナの停止処理が2プロセスで依存し合っているのが原因！
おそらく、下図のように一方のプロセスで`SIGINT`を受け取ったら、もう一方のプロセスに`SIGINT`を送るようになっていたのでしょう。
![一方のプロセスにSININTを送った図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619726/e3a00fb2-89ba-f992-ca1b-b24c1b4984bd.png)
この実装の上で`Ctrl+C`で止められると内部的には2回`SIGINT`を受け取ることになり、コンテナの強制終了（Kill）が走ってしまったと考えられます。
![スクリーンショット 2022-07-17 16.22.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/619726/fde03338-32b5-ba16-1f49-6257255ebf8f.png)
また、半年ほど前からエラーが発生し出した原因は、元々1プロセスで動いていた`docker-compose up`が
`Docker Compose V2`導入に伴ってユーザーがV1とV2の実装を切り替えられるように、2プロセスに分離したのが原因だと思われます。

# 対処法（再掲）
`Docker Compose V1`で発生しているエラーなので、`docker compose`コマンドを使用して`Docker Compose V2`でコンテナを起動するとエラーがなくなり正常終了します。
```bash:V2の使用方法
docker compose up # docker-composeの"-"がいらない！
```

# 終わりに
議論の余地ありありの記事を書くことに抵抗はありますが、対処法のみを示すよりは仮説まで示した方が建設的だろうと思い、記事にさせていただきました。
詳しい方は是非マサカリをお願いします！！！

# 参考
- `Ctrl+C`と`SIGINT`の違いについて
    - https://qiita.com/toshihirock/items/bc6a9a4091afa9bb61f1
    - http://equj65.net/tech/linuxprocessgroup/