Dockerの環境構築に知っておいた方が良いこと

Dockerについてまとめるシリーズの第２弾です。  

今回は、DockerやDocker Composeで環境を構築する時に私がはまったことについてまとめます。  

前回の投稿は、<a href="https://www.northtorch.co.jp/archives/1125">こちら</a>です。

※ 本記事は、2022/04/08に[弊社スタッフブログ](https://www.northtorch.co.jp/archives/1155)に投稿した記事をQiitaに転載しています。

## TL;DR

- Volumeをマウントした時のOwner問題
    - イメージビルドした環境と実行する環境が同一の場合
        - イメージをビルドした時にユーザ作成しておく
    - イメージビルドした環境と実行する環境が異なる場合
        - dockerfile の entrypoint でユーザ作成する
- 特定のバージョンのpythonの仮想環境を構築する
    - マルチステージビルド使用するとイメージのビルドが早い
- Docker Compose で特定のコンテナが立ち上がらない
    - ```docker logs <コンテナID>```でログを確認する
- Docker Compose で「Pool overlaps with other one on this address space」が出る
    - ```docker system prune```で不要なリソースをクリアする

## Volumeをマウントした時のOwner問題

Dockerコマンドでコンテナを動作させる際、ホスト側のディレクトリをコンテナにマウントすることができます。  
コンテナから読み書き可能・読み取り専用のいずれもアクセス制御ができますので、なかなか便利です。  
Docker Composeの場合は、docker-compose.yml に volumes 指定することで、同様の機能が使用できます。  

Volumeのマウントを使用する主な場面は、下記のケースがあります。  
- 開発用コンテナでソースコードを共有
- 設定ファイルをホスト側と共有
- コンテナ内のログファイルや中間生成ファイルをホスト側と共有

コンテナからホスト側ディレクトリに新規ファイルを書き込むときに困るのが、作成したファイルの所有者がコンテナ内のユーザ（多くの場合、root）になってしまう、という問題です。  
例えば、コンテナ内の処理で中間生成ファイルを出力していた場合、処理完了後にホスト側でログを確認しようとしたら```sudo```しないとファイルが編集できない、といったような事が起きます。  
dockerコマンドはオプションで実行ユーザとグループを指定できるのですが、コンテナ内にホスト側と同じIDのユーザとグループが必要になります。  

解決策としては[こちらの記事](https://qiita.com/yohm/items/047b2e68d008ebb0f001)が詳しいのですが、普段私がやっている方法をまとめます。  

### イメージビルドした環境と実行する環境が同一の場合

dockerfile内でユーザとグループを作成します。  
ユーザ名、ユーザID、グループIDはARGSとして定義しておき、ビルドする際に上書きします。  

Dockerfileは下記のようになります。  

```Dockerfile
FROM python:3.9-slim-bullseye

# 実行ユーザのuid, gid, ユーザ名
ARG USER_NAME=myuser
ARG USER_ID=1000
ARG GROUP_ID=1000

RUN addgroup -S -g "${GROUP_ID}" "${USER_NAME}" && \
  adduser -u "${USER_ID}" -G "${USER_NAME}" -D "${USER_NAME}"
USER ${USER_NAME}

# ～～　以降、必要な処理を記述する　～～
```

イメージをビルドする時に、ビルドを実行するユーザの情報をARGSに上書きします。  
イメージをビルドするユーザと実行するユーザが同一なら、下記のようにビルドします。  

```bash
$ docker build --build-arg USER_NAME=`whoami` --build-arg USER_ID=`id -u` --build-arg GROUP_ID=`id -g` .
```

これでコンテナ内にイメージビルドしたユーザと同じユーザが作成され、そのユーザでコンテナが立ち上がるようになります。  

### イメージビルドした環境と実行する環境が異なる場合

必要になったケースがないので実験レベルでしか試していませんが、[参照URL](https://qiita.com/yohm/items/047b2e68d008ebb0f001)の方法でうまく動作するようです。

### 参照URL

- [バインドマウントの利用](https://matsuand.github.io/docs.docker.jp.onthefly/storage/bind-mounts/)
- [Compose ファイル・リファレンス - volumes, volume_driver](http://docs.docker.jp/v1.11/compose/compose-file.html#volumes-volume-driver)
- [dockerでvolumeをマウントしたときのファイルのowner問題](https://qiita.com/yohm/items/047b2e68d008ebb0f001)

## 特定のバージョンのpythonの仮想環境を構築する

pythonの環境をalpine linuxで動作させると、パフォーマンスの問題が発生します。  
詳細は[参照URL](https://future-architect.github.io/articles/20200513/)に譲りますが、イメージのビルドも、コンテナの動作もデメリットがたくさんです。  

コンテナの動作についてはdebianベースのイメージを使用する事で解決するとして、ここではイメージのビルドの速度向上について考えます。  

pythonの環境では必要なアプリケーションを動作させる前に```pip```等を使用してパッケージのインストールをいます。
ソースコードからビルドする必要のあるパッケージを使用していたりすると、そのためにビルド用のソフトウェア（gccとかlib*-devのような開発用のパッケージ）をインストールする必要が出てきます。

Dockerfileのベースイメージでは、一般的に余計なソフトウェアがインストールされていないイメージ（-slimとかついてるやつ）を使用しますが、上記の理由により、結局開発環境をインストールする羽目になったりします。
イメージのビルドに必要な時間は増えるわ、サイズは大きくなるわ、本末転倒です。

Dockerのマルチステージビルドを使用すると、上記の問題が解決できます。  
マルチステージビルドをざっくり説明すると、  

ビルドする際に複数のベースイメージからイメージを作成し、そのイメージ間でファイルがコピーできる  

となります。  

サンプルのDockerfileを提示します。  

```Dockerfile
# ===== package build container =====
FROM python:3.9-bullseye as builder
WORKDIR /opt/app

# full版でpip installしておく
COPY requirements.txt /opt/app
RUN pip install -r requirements.txt

# ===== main container =====
FROM python:3.9-slim-bullseye as runner

# pip install完了したfull版から、installの結果だけコピーしてくる
# これで、pip installが完了した状態になる
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages

# ～～　以降、必要な処理を記述する　～～
```

```COPY --from=builder```の行が、```pip install```した結果ファイルを、slimイメージ側にコピーする行です。  
ビルド用のソフトウェアのインストールが無くなるので、イメージのビルドにかかる時間が劇的に短くなります。  

### 参照URL

- [仕事でPythonコンテナをデプロイする人向けのDockerfile (1): オールマイティ編](https://future-architect.github.io/articles/20200513/)
- [仕事でPythonコンテナをデプロイする人向けのDockerfile (2): distroless編](https://future-architect.github.io/articles/20200514/)

## Docker Compose で特定のコンテナが立ち上がらない

Docker環境のデバッグの基本中の基本かもしれません。  
```docker logs <コンテナID>```で、コンテナの標準出力を確認できます。  

Docker Composeする環境を作成していると、```docker-compose up -d```してるのに特定のコンテナが立ち上がらない、といった現象に遭遇します。  
例えば、nginx, MySql, uwsgi+django, redis, celeryを組み合わせたDocker Compose環境を作成しようとすると、設定ファイルの記述が間違っていて uwsgi や celery が立ち上がらない、ということが良くあります。  

大抵、コンテナ標準出力でエラーを出して止まっていたりするので、まずは確認してみる癖をつけましょう。  

### 参照URL

- [dockerでコンテナが立ち上がらないときやってみること](https://qiita.com/mom0tomo/items/35dfacb628df1bd3651e)

## Docker Compose で「Pool overlaps with other one on this address space」が出る

何度も```docker-compose up```を繰り返していると、発生します。  
下記コマンドで不要なリソースをクリアすると解消します。  

```
$ docker system prune

  - all stopped containers
  - all networks not used by at least one container
  - all dangling images
  - all dangling build cache

Are you sure you want to continue? [y/N] y
Deleted Containers:
```

### 参照
- [docker-compose upしたら、Pool overlaps with other one on this address space というエラーが発生した](https://www.danroo.com/infra/post-108/)