Studyplus Engineering Blog

スタディプラスの開発者が発信するブログ

MinIOを使ってローカル環境でファイルストレージを利用してみる

こんにちは。ForSchool事業部サーバーサイドエンジニアのましばです。

今回はDocker + Rails + MinIOを利用して、開発環境でもS3のようなファイルアップロードを行うことができる環境を構築します。 同様の内容を取り扱った記事は複数存在しますが、少し古いものもあったので整理も込めて記事にします。

MinIO

MinIOはS3と互換性のあるストレージサービスです。 公式サイトではクラウド上での利用を推していますが、ブラウザからストレージ内のファイルを閲覧できたりCLIツールが存在したりとローカル環境でも使いやすいサービスです。

min.io

GitHubのREADMEではいくつかの利用方法が書いてありますが、今回はDockerで利用していきます。

環境

  • Docker Desktop 4.3.2
  • minio:latest(RELEASE.2022-01-08T03-11-54Z)
  • Ruby 3.1.0
  • Ruby on Rails 7.0.1

docker-composeで環境を構築

今回はMinIOを試すことが目的なのでなるべく最小の構築を目指します。

公式のRubyのDockerfileは少し古いのでちょっと改変します。
Rails7ではNode.jsが必須ではなくなったので、Dockerfileがだいぶシンプルになった気がします。

# Dockerfile

FROM ruby:3.1

RUN mkdir /app
WORKDIR /app

COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install

docker-compose.yamlは以下のようにしました。
MinIOコンテナもここで作成しています。

# docker-compose.yaml

version: "3.9"
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - ./tmp/db:/var/lib/mysql
  web:
    build: .
    command: ["rails", "s", "-p", "3000", "-b", "0.0.0.0"]
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - db
  minio:
    image: minio/minio:latest
    ports:
      - "9000:9000"
      - "9001:9001"
    volumes:
      - ./tmp/minio/data:/data
    command: "server /data --console-address :9001"
    environment:
      MINIO_ROOT_USER: user
      MINIO_ROOT_PASSWORD: password

--console-addressはブラウザからMinIOのコンソールにアクセスする際のポートを指定します。
MINIO_ROOT_USERおよびMINIO_ROOT_PASSWORDはブラウザ、RailsアプリケーションからMinIOにアクセスする際の情報になります。
MinIO公式ガイドもありますのであわせてご確認ください。

以降はDocker公式サイトのビルド手順にほぼ従います。
簡単にですが手順を記載します。

Step1. Gemfile, Gemfile.lockを作成し、Gemfileを記述します。

touch Gemfile
touch Gemfile.lock
# Gemfile

source "https://rubygems.org"
gem "rails"

Step2. 初回のみrails newしてbuildを実行します。

docker-compose run --no-deps web rails new . --force --database=mysql

docker-compose build

Step3. config/database.yamlのpasswordとhostを修正します。

# config/database.yaml

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # MYSQL_ROOT_PASSWORDの値を追加
  host: db # 変更

Step4. docker-compose upしてmigrationを実行します。

docker-compose up -d
docker-compose exec web rails db:create

ここまで実行するとlocalhost:3000にRailsのスタートページが表示されます。
デザイン変わったんですね。

MinIOへアクセスしてみる

ブラウザからアクセスしてバケットを作成する

MinIOが起動しましたが、とりあえず起動しただけでバケットも存在しないため、ブラウザからアクセスして作成します。

localhost:9001にアクセスするとログイン情報を求められるので、docker-compose内で定義したMINIO_ROOT_USERMINIO_ROOT_PASSWORDを入力します。

サイドメニューからBucketsを選択し、Create Bucketから名前をつけて作成します。

このままではprivateな状態なので、バケット一覧からManageを選択して、Access PolicyPublicに指定します。

これでpublicなバケットが作成されました。

Railsからファイルをアップロードする

作成したバケットにRailsから画像をアップロードできるようにします。

今回はcarrierwavefog-awsを利用します。

まずはgemをinstallします。

gem "carrierwave"
gem "fog-aws"

Rails側でファイルをアップロードできる機能を作ります。

どんなのでもいいのですが、今回はUserモデルを作ってavatarとして画像をアップロードしてみることにします。

scaffoldでUserモデルと対応するコントローラーを作成します。

docker-compose exec web rails g scaffold User name:string avatar:string
docker-compose exec web rails db:migrate

AvatarUploaderを作成します。

docker-compose exec web rails g uploader avatar
# user.rb
class User < ApplicationRecord
  mount_uploader :avatar, AvatarUploader
end

carrierwaveとfogを用いてファイルをアップロードする設定をします。

# app/uploaders/avatar_uploader.rb
  # storage :file
  storage :fog
# config/initializers/carrierwave.rb
CarrierWave.configure do |config|
  config.fog_provider = "fog/aws"
  config.cache_storage = :fog

  config.asset_host = "http://localhost:9000/bucket"
  config.fog_directory  = "bucket"

  # https://github.com/carrierwaveuploader/carrierwave#using-amazon-s3 を参考
  config.fog_credentials = {
    provider:              "AWS",
    aws_access_key_id:     "user", # MINIO_ROOT_USER
    aws_secret_access_key: "password", # MINIO_ROOT_PASSWORD
    region:                "ap-northeast-1",
    endpoint:              "http://minio:9000",
    path_style:            true # これを追加します
  }
end

これで設定ができたので、user作成時にavatarを選択して作成し、詳細ページで表示されるようにします。

<%# _form.html.erb %>
<div>
  <%= form.label :avatar, style: "display: block" %>
  <%= form.file_field :avatar %>
</div>
<%# show.html.erb %>
<% if @user.avatar? %>
  <div>
    <%= image_tag @user.avatar.url %>
  </div>
<% end %>

これでユーザー作成時にアバター画像を投稿できるようになりました。

コンテナ起動時にバケットを作成する

MinIO自体はこれで使えるのですが、バケットを手作業で作るのは面倒なのでコンテナ起動時に作成されるようにします。

MinIOにはMinIO Client(mc)というCLIが存在するのでこちらを利用してバケットを作成します。

docker-composeに以下を追加します。

  createbuckets:
    image: minio/mc
    depends_on:
      - minio
    entrypoint: >
      /bin/sh -c "
      /usr/bin/mc alias set myminio http://minio:9000 user password;
      /usr/bin/mc mb myminio/bucket --region=ap-northeast-1;
      /usr/bin/mc policy set public myminio/bucket;
      exit 0;
      "

やっていることはシンプルで、myminioというエイリアスでminio:9000を登録し、bucketというバケットを作成し、ポリシーをpublicにしているだけです。
alias設定時のユーザー情報はMINIO_ROOT_USERMINIO_ROOT_PASSWORDを、バケット名やリージョンはcarrierwaveの設定と合わせておく必要があります。
こうするとコンテナ起動時にバケットの作成、ポリシーの設定までやってくれるので、コンソールからバケットを作成する必要がなくなります。

終わりに

MinIOでローカルの開発環境でストレージ機能を使ってみました。
思っていたより簡単に環境構築と設定ができ、コンソールも充実していて使いやすかったです。
今回はとりあえずバケットを作るだけでしたが、ドキュメントを見る限りもうちょっと凝った設定もできそうなので今後試してみようと思います。

*1

*1:privateバケットにファイルをアップロードして署名付きのURLを返せるようにしたい…