概要
Amazon EC2 インスタンスの起動時に S3 バケットを自動的にマウントできるようになったので、もうコンテナ内のデータを永続化する構成で悩む必要がなくなりました!
しかし設定箇所や注意点が多いので、以下の環境で IaC 化の要点をまとめました:
- Amazon EC2 + Amazon Linux 2023
- Docker Compose V2
- AWS CloudFormation
- Ansible
1. コンテナのデータを永続化する方式
A. コンテナ内のディレクトリ
何もしないとデータはコンテナ内のディレクトリに出力されるので、コンテナが破棄されるとデータも消滅します。
B. ホストのディレクトリをバインドマウントする
ホストのディレクトリをコンテナにバインドマウントすれば、コンテナが破棄されてもデータはホストに残ります。
services:
App:
volumes:
- /var/app/data:/data
しかし、ホストマシンが Amazon EC2 の場合は以下の問題があります:
-
ホストの EC2 をストレージと共に破棄するとデータも消滅する -
ホスト以外からデータにアクセスできない -
EC2 のストレージ (EBS) は容量単価が高い -
ストレージの残容量やバックアップなどの管理が必要
C. S3 のバケットをマウントする
そこで EC2 に更に Amazon S3 のバケットをマウントすると、上記の問題が全て解決します。
-
データを永続化できる -
データを共有できる -
容量単価が安価 -
ストレージの管理が不要
ただし S3 の性能特性は普通のブロックデバイスと異なるので、ユースケースに適した方式を S3 のストレージクラスや EFS (Elastic File System) 等から選定する必要があります。
- S3 のパフォーマンス
- S3 のストレージクラス
- EFS (Elastic File System)
2. 設定箇所とポイント
S3 バケットへの書き込みを許可を EC2 インスタンスと S3 バケットの両方に設定する必要があります。またコンテナ内の実行ユーザーが root 以外の場合は、fstab マウント時に Linux のパーミッションも設定する必要があります。
2.1. EC2 インスタンス: S3 の操作を許可する
バケットをマウントする EC2 インスタンスの IAM インスタンスプロファイルに、S3 の操作を許可した IAM ロールをアタッチします。
2.2. S3 バケット: EC2 インスタンスからのアクセスを許可する
この IAM ロールを、S3 バケットにアタッチするポリシーの Principal に設定することで、同じ IAM ロールをアタッチした EC2 インスタンスがバケットを操作できるようになります。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::123456789012:role/xxxx-instance-iam-role"
},
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::xxxx_data_bucket",
"arn:aws:s3:::xxxx_data_bucket/*"
]
}
]
}
2.3. fstab マウント: Linux のパーミッションを設定する
ホストの /etc/fstab に、以下のように S3 マウントのエントリを追加します。このエントリは UID = 999 の Linux ユーザーに読み書き・上書き・削除の操作を許可する例です。
s3://xxxx_data_bucket/ /var/app/data mount-s3 _netdev,nosuid,nodev,nofail,rw,allow-other,allow-delete,allow-overwrite,uid=999,gid=999,dir-mode=775,file-mode=664 0 0
この例では、エントリの各フィールドに以下の値を設定しました:
| フィールド | 説明 |
|---|---|
s3://xxxx_data_bucket/ |
マウントするデバイス |
/var/app/data |
マウントポイントのパス |
mount-s3 |
ファイルシステムの種類 |
_netdev,nosuid, ... ,dir-mode=775,file-mode=664 |
マウントオプション |
0 |
dump 対象フラグ (0 で対象外) |
0 |
起動時の fsck 優先度 (0 で対象外) |
マウントオプションは以下のオプションを組み合わせました。必要に応じて変更してください。
| マウントオプション | 説明 |
|---|---|
_netdev |
マウントするためにネットワークが必要 |
nosuid |
設定されたユーザー ID ファイルを含めることができないように指定する |
nodev |
特別なデバイスを含めることができないように指定する |
nofail |
マウントに失敗してもシステムを起動する |
rw |
読み書き可能 |
allow-other |
root と他のユーザーのアクセスを許可 |
allow-delete |
削除の操作を許可 |
allow-overwrite |
上書き保存の操作を許可 |
uid=999 |
所有者の UID |
gid=999 |
所有者の GID |
dir-mode=775 |
ディレクトリのパーミッション |
file-mode=664 |
ファイルのパーミッション |
mount-s3 --help で表示される Mountpoint for Amazon S3 のマウントオプション
Mount options:
--read-only
Mount file system in read-only mode
--allow-delete
Allow delete operations on file system
--allow-overwrite
Allow overwrite operations on file system
--incremental-upload
Enable incremental uploads and support for appending to existing objects
--auto-unmount
Automatically unmount on exit
--allow-root
Allow root user to access file system
--allow-other
Allow other users, including root, to access file system
--uid <UID>
Owner UID [default: current user's UID]
--gid <GID>
Owner GID [default: current user's GID]
--dir-mode <DIR_MODE>
Directory permissions [default: 0755]
--file-mode <FILE_MODE>
File permissions [default: 0644]
2.4. Docker サービス: マウントが完了するまで起動させない
マウントポイント /var/app/data のマウントが完了するまで Docker サービスが起動しないように、docker.service のユニットファイルに以下を追加します。
[Unit]
RequiresMountsFor=/var/app/data
設定箇所とポイントは以上です
3. IaC コードの実装
前章の設定を自動構成するために、AWS 上の構成を CloudFormation で、Linux 上の構成を Ansible で実装します。
3.1. EC2 インスタンス、IAM インスタンスプロファイル、IAM ロール
S3 を操作する権限を持つ IAM ロールを作成し、その IAM ロールを IAM インスタンスプロファイルにアタッチして、更にそのプロファイルを EC2 インスタンスにアタッチします。
Resources:
# バケットをマウントする EC2 インスタンス
XxxxInstance:
Type: AWS::EC2::Instance
Properties:
# 直下で定義している IAM インスタンスプロファイルをアタッチする
IamInstanceProfile: !Ref XxxxIamInstanceProfile
# ......
# その他のプロパティは省略
# IAM インスタンスプロファイル
XxxxIamInstanceProfile:
Type: AWS::IAM::InstanceProfile
Properties:
Path: /
Roles:
# 直下で定義している IAM ロールをアタッチする
- !Ref XxxxInstanceIamRole
# IAM ロール
XxxxInstanceIamRole:
Type: AWS::IAM::Role
Properties:
RoleName: xxxx-instance-iam-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- ec2.amazonaws.com
Action:
- sts:AssumeRole
Path: /
ManagedPolicyArns:
# S3 を操作する権限を与える
- !Sub arn:${AWS::Partition}:iam::aws:policy/AmazonS3FullAccess
3.2. S3 バケット、バケットポリシー
上で作成した IAM ロールを S3 バケットポリシーに設定することで、上の EC2 インスタンスからのアクセスを許可することができます。
# EC2 インスタンスにマウントする S3 バケット
XxxxS3Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: xxxx_data_bucket # バケット名
OwnershipControls:
Rules:
- ObjectOwnership: ObjectWriter
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
# その S3 バケットにアタッチするバケットポリシー
XxxxS3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref XxxxS3Bucket # バケット名 'xxxx_data_bucket'
PolicyDocument:
Version: 2012-10-17
Statement:
Action: s3:*
Effect: Allow
Principal:
AWS:
# EC2 インスタンスの IAM インスタンスプロファイルに
# アタッチした IAM ロール
- !GetAtt XxxxInstanceIamRole.Arn
Resource:
# ${XxxxS3Bucket} はバケット名 'xxxx_data_bucket'
- !Sub arn:aws:s3:::${XxxxS3Bucket}
- !Sub arn:aws:s3:::${XxxxS3Bucket}/*
3.3. Docker と Compose のインストール
以下、Amazon Linux 2023 に Ansible を使用してインストールします
Compose プラグインは Amazon Linux 2023 のリポジトリで提供されていなかったので、GitHub からダウンロードしてインストールします。
- Install | Docker Docs
- Plugin | Docker Docs
- Amazon Linux 2023 に docker-compose plugin をインストールする方法 #AmazonLinux2023 - Qiita
- name: Docker を rpm パッケージでインストールする
ansible.builtin.dnf:
name:
- docker
state: present
update_cache: true
- name: Docker のプラグイン用ディレクトリを作成する
ansible.builtin.file:
mode: "0755"
path: /usr/local/libexec/docker/cli-plugins
state: directory
- name: Docker Compose をダウンロードしてプラグイン用ディレクトリにインストールする
ansible.builtin.uri:
dest: /usr/local/libexec/docker/cli-plugins/docker-compose
mode: "0755"
status_code: [200, 304]
url: https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-linux-x86_64
3.4. Mountpoint for Amazon S3 のインストールと自動マウント
- name: Mountpoint for Amazon S3 rpm package をインストールする
ansible.builtin.dnf:
name: "https://s3.amazonaws.com/mountpoint-s3-release/latest/x86_64/mount-s3.rpm"
state: present
- name: マウントポイントを作成する
ansible.builtin.file:
path: "{{ s3_mountpoint }}"
state: directory
mode: "0777"
- name: S3 マウントのエントリを /etc/fstab に追加する
ansible.posix.mount:
boot: true
fstype: mount-s3
opts: "_netdev,nosuid,nodev,nofail,rw,allow-other,allow-delete,allow-overwrite,uid=999,gid=999,dir-mode=775,file-mode=664"
path: "{{ s3_mountpoint }}"
src: "s3://xxxx_data_bucket/"
state: mounted
3.5. マウント完了まで Docker サービスを起動させない
Docker サービスのユニット設定を追加します。ユニットファイル自体を変更する代わりに、追加設定だけを定義したファイル requiremount.conf を /etc/systemd/system/docker.service.d/ に配置します。
- name: マウントが完了するまで Docker サービスを起動させない設定を追加する
ansible.builtin.template:
dest: /etc/systemd/system/docker.service.d/requiremount.conf
mode: "0644"
src: "{{ role_path }}/templates/requiremount.conf.j2"
notify:
- configuration updated
配置されるファイルの元になるテンプレートです
[Unit]
RequiresMountsFor={{ s3_mountpoint }}
S3 マウントポイントのパスを変数として定義します
# Ansible 用の変数
ansible_ssh_private_key_file: ~/.ssh/xxxx-ec2-ssh-key.pem
ansible_user: ec2-user
ansible_become: true
# S3 マウントポイントのパス
s3_mountpoint: /var/app/data
3.6. Compose ファイルのデプロイ
ホストのマウントポイントを compose.yaml でコンテナにバインドマウントします
- name: Compose ファイルをデプロイする
ansible.builtin.template:
dest: /path/to/project/compose.yaml
mode: "0644"
src: "{{ role_path }}/templates/compose.yaml.j2"
notify:
- configuration updated
配置されるファイルの元になるテンプレートです
---
services:
App:
volumes:
- {{ s3_mountpoint }}:/data
# ...
# その他の項目は省略
S3 への書き込み遅延が許容できる場合は - {{ s3_mountpoint }}:/data の後に :delegated を追加することで、書き込み性能の向上が期待できます。
3.7. Docker サービスの自動起動と再起動
ユニットファイルをリロードして Docker サービスを再起動し、OS 起動時のサービス自動起動を設定します。
- name: Reload unit files and restart docker service
ansible.builtin.systemd_service:
daemon_reload: true
name: docker
enabled: true
state: restarted
listen:
- configuration updated
以上で S3 バケット s3://xxxx_data_bucket が EC2 インスタンスのマウントポイント /var/app/data に自動的にマウントされ、コンテナ内のユーザー UID=999 が読み書きできるようになります。
