AWS Fargateでストレージを共有するバインドマウントとデータボリュームの違いを理解する

# はじめに

アプリケーションをコンテナ化してAWSのFargateで運用する場合、Django+Nginxの場合だとスタティックファイルの共有が必要となるが、仕組みをよく理解していなくて混乱していた。
後から考えればそういうことかと思えたけどもそのあたりを整理してみた。

# やりたいこと

ざっくり構成図を描くとこんな感じ。本当はRDSとかECRとか使っているが省略。

![test.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691494/bec4b6e0-85c0-67e5-39d0-4f877d4fbf29.png)

２つのアベイラビリティゾーンにパブリックサブネットを配置してFargateをロードバランシングして運用。
1つのタスク内にDjangoとNginxの2コンテナを置く。
DjangoとNginx間でファイル(実際はディレクトリ)を共有する必要があり、左右のFargate間はロードバランシングしているため共有が必要となる。

結論からいうと

* タスク内のコンテナ間での共有 → バインドマウント
* サービス内のロードバランシングしているタスク間での共有 → データボリューム

となる。そのためタスク内に1コンテナしかない場合、バインドマウントは要らないし、ロードバランシングしないのであればデータボリュームは不要だ。

# タスク内のコンテナ間での共有(バインドマウント)

理解を深めるためにもう少しシンプルにしてみるとこんな感じ。
そしてDjangoとNginxでディレクトリ(/var/local/django)を共有したい。

![test1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/691494/02321b58-305f-faf2-6ca4-6c4166d7c2cd.png)

詳しくは以下の説明、特に「Dockerfile 内のパスとその内容をコンテナに公開する」の箇所にある。

https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/bind-mounts.html#bind-mount-examples

まずDockerfileだがそれぞれにVOLUME指定がいる。

```dockerfile:django/Dockerfile
# pull official base image
FROM python:3.10-slim-bullseye

# get arg
ARG APP_DIR=/usr/src/app
ARG SHARED_DIR=/var/local/django
# UIDとGIDはnginxコンテナのnginxユーザに合わせる
ARG UID=101
ARG GID=101

# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
ENV DEBIAN_FRONTEND noninteractive
ENV LANG=ja_JP.UTF-8

# set work directory
WORKDIR ${APP_DIR}

# copy
COPY ./app .
COPY ./requirements.txt .

# install dependencies
RUN apt-get update && apt-get install -y locales && \
    sed -i -E 's/# (ja_JP.UTF-8)/\1/' /etc/locale.gen && \
    locale-gen && \
    pip install --upgrade pip && \
    pip install -r requirements.txt && \
    rm requirements.txt && \
    groupadd nginx -g ${GID} && \
    useradd nginx -u ${UID} -g ${GID} --home=${APP_DIR} && \
    mkdir -p ${SHARED_DIR} && \
    chown -R nginx.nginx ${SHARED_DIR}

USER nginx

VOLUME ["${SHARED_DIR}"]
```

```dockerfile:nginx/Dockerfile
FROM nginx:1.23-alpine

# get arg
ARG SHARED_DIR=/var/local/django

# ひょっとしたらmkdir不要かもしれない。
RUN rm /etc/nginx/conf.d/default.conf && \
    mkdir -p ${SHARED_DIR} && chown nginx:nginx ${SHARED_DIR}

COPY nginx.conf /etc/nginx/conf.d

VOLUME ["${SHARED_DIR}"]
```

次AWSのタスク定義のvolumesセクションで、名前を efs としてバインドマウントを定義。

```json
  "volumes": [
    {
      "name": "efs",
    }
  ]
```

> 名前はなんでもいいが後でefsの設定をするのでこうしておいた。

そしてコンテナ定義

```json:container_definition.json(必要部分だけ抜粋)
[
    {
      "name" : "nginx",
      },
      "mountPoints" : [
        {
          "containerPath" : "/var/local/django",
          "sourceVolume" : "efs"
        }
      ],
      "portMappings" : [
        {
          "protocol" : "tcp",
          "hostPort" : 80,
          "containerPort" : 80
        }
      ]
    },
    {
      "name" : "django",
      },
      "command" : ["gunicorn", "-w", "3", "-b", ":8000", "--access-logfile", "-", "--capture-output", "--log-level", "debug", "mysite.wsgi:application"],
      "mountPoints" : [
        {
          "containerPath" : "/var/local/django",
          "sourceVolume" : "efs"
        }
      ]
    }
```

余談だが、よく両方のportMappingsが記載されている例を見るがNginx側だけでよい。
またDjango、Nginx側の設定も忘れずに

Django側

```python:mysite/settings.py(必要部分だけ抜粋)
SHARED_DIR = '/var/local/django'
STATIC_ROOT = os.path.join(SHARED_DIR, 'static')
```

Nginx側

```nginx:nginx.conf(必要部分だけ抜粋)
  location /static/ {
    alias /var/local/django/static/;
  }
```

# サービス内のタスク間での共有(データボリューム)

上記でコンテナ同士の共有ができたならば次はタスク間での共有を行う。
いくつか手法はあるようだがterraform使いの私は以下を参考にしてEFSを使った。もちろんAWSコンソールからやってもいい。

https://beyondjapan.com/blog/2020/04/fargate-supported-efs/#efstf

上記では`aws_efs_file_system`とアベイラビリティゾーンごとに`aws_efs_mount_target`を2つ設定しているが、このままだとEFSのアクセスポイントが無かったので私は以下を追加した。

```terraform
resource "aws_efs_access_point" "access" {
  file_system_id = aws_efs_file_system.efs.id
}
```

# 最後に

完成品を公開するわけにも行かないため断片的な情報なのでどこまで伝わるかわからないが、少しでも手助けになれば幸いである。
またコンテナ間の共有はバインドマウントは使わずにS3を使うように設計するほうがAWSらしいかもしれない。