【Railsチュートリアル】S3に画像をアップロードする設定【13章課題】

S3に画像をアップロードする

13.4.4 本番環境での画像アップロード

13.4.3で実装した画像アップローダーは、開発環境で動かす分には問題ないのですが、本番環境には適していません。これはリスト 13.67のstorage :fileという行によって、ローカルのファイルシステムに画像を保存するようになっているからです23。本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにしてみましょう24。


世の中には多くのクラウドストレージサービスがありますが、今回は有名で信頼性も高いアマゾンの「Simple Storage Service (S3)25」を使います。セットアップの手順は次のとおりです。

Amazon Web Servicesアカウントにサインアップする
AWS Identity and Access Management (IAM)でユーザーを作成し、AccessキーとSecretキーをメモする
AWS ConsoleからS3 bucketを作成し (bucketの名前はなんでも大丈夫です)、2.で作成したユーザーに対してRead権限と> Write権限を付与する
(S3のセットアップはやや高度です。コラム 1.1の考え方をうまく活用してみてください。)


特に、Herokuのファイルストレージは一時的なので、デプロイする度にアップロードした画像が削除される仕様になっています。

Railsチュートリアルで、画像を扱うアプリケーションを本番環境でデプロイする項目があります。

S3とIAMの連携で検索しても、画面キャプチャが古いものしか見つからなかったので、自分で記事を書くことにします。

IAMユーザーの作成

IAM_Management_Console.png

AWSの管理コンソールにログインしたら、IAMというサービスのコンソールを開きましょう。

IAM_Management_Console.png

「ユーザー」>「ユーザーを追加」を選択します。

IAM_Management_Console.png

ユーザー名を入力します。
アクセス権限は「プログラムによるアクセス」を選択します。

IAM_Management_Console.png

グループ名を入力します。
ポリシーは「AmazonS3FullAccess」のみを選択します。

IAM_Management_Console.png

設定があってるか確認しましょう。

IAM_Management_Console.png

最後にアクセスキーとシークレットキーのcsvをダウンロードしましょう。
csvの再ダウンロードはできないので、無くさないように

S3の設定

S3_Management_Console.png

IAMの設定が完了したら、次はS3の設定です。
S3に管理コンソール画面を開いたら、「バケットを作成する」を選択します。

S3_Management_Console.png

名前を入力します。既に使われている名前は使用することができません。
リージョンは東京を選択します。

S3_Management_Console.png

ここはデフォルト設定で問題ありません。

S3_Management_Console.png

「オブジェクト」「オブジェクトのアクセス許可」の読み込み、書き込みにチェックを入れましょう。
他はデフォルト設定です。

S3_Management_Console.png

最後に設定があっているか確認しましょう。

Herokuの環境変数を設定

ここまで完了したら、あと一息です。
Herokuの環境変数を設定しましょう。

$ heroku config:set S3_ACCESS_KEY="ココに先ほどダウンロードしたAccessキーを入力"
$ heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力"
$ heroku config:set S3_BUCKET="S3のBucketの名前を入力"
$ heroku config:set S3_REGION="ap-northeast-1"

これでデプロイしたRailsアプリケーションを起動して見ましょう。
画像を投稿して、S3バケットを確認して、新しく画像が保存されていたら、成功です!
お疲れ様でした!!

参考資料

Amazon S3 再入門 – AWS IAMでアクセスしてみよう!(Cyberduck 編)
35歳だけどRailsチュートリアルやってみた。[第4版 13章 13.4 マイクロポストの画像投稿 まとめ&解答例]

続きを読む

開発用サーバーを作る on AWS(Ruby on Rails 5)

はじめに

まぁそのまんま。
こちらのRubyonRails編。

Ruby 2.4.2
Ruby on Rails 5.1.4
なり。

Amazon Linux AMI+Nginx+Unicorn
やで。

インストールなど

sudo yum update
sudo yum -y install git
sudo yum install nodejs --enablerepo=epel
git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
sudo yum install gcc openssl-devel readline-devel sqlite-devel
rbenv install 2.4.2
rbenv global 2.4.2
gem update --system
gem install --no-ri --no-rdoc rails
gem install bundler
gem install sqlite3
gem install unicorn
rbenv rehash
rails -v
rails new /var/www/html/sample
sudo mkdir /var/run/unicorn
sudo chmod 777 /var/run/unicorn
sudo chown -R nginx:ec2-user /var/www/html
sudo chmod 2777 /var/www -R

まぁ、だいぶ時間がかかりまっせ。

sample/config/unicorn.rbを作成

application = 'sample'

worker_processes 2
timeout 15

pid "/var/run/unicorn/unicorn_#{application}.pid"
listen "/var/run/unicorn/unicorn_#{application}.sock"

preload_app true

before_fork do |server, worker|
  Signal.trap 'TERM' do
  puts 'Unicorn master intercepting TERM and sending myself QUIT instead'
  Process.kill 'QUIT', Process.pid
  end

  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.connection.disconnect!end

after_fork do |server, worker|
  Signal.trap 'TERM' do
  puts 'Unicorn worker intercepting TERM and doing nothing. Wait for master to send QUIT'
  end

  defined?(ActiveRecord::Base) and
  ActiveRecord::Base.establish_connection
end

stderr_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])
stdout_path File.expand_path('log/unicorn.log', ENV['RAILS_ROOT'])

起動

bundle exec unicorn_rails -c config/unicorn.rb -E development -D

OSの再起動対応

~/.bashrcに追記

sudo mkdir -p /var/run/unicorn && sudo chmod 777 /var/run/unicorn

/etc/nginx/conf.d/sample.conf作成

upstream unicorn {
  server unix:/var/run/unicorn/unicorn_sample.sock;
}
server {
    listen 8080;
    server_name localhost;
    root /var/www/html/sample;

    access_log /var/log/nginx/myapp_access.log;
    error_log /var/log/nginx/myapp_error.log;

    try_files $uri/index.html $uri @unicorn;
    location @unicorn {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Host $http_host;
        proxy_pass http://unicorn;
    }
}

PHPを80で動かしてる都合上8080でごわす。

Nginx再起動

sudo service nginx restart

いじょー
Security Groupで8080はあけておきましょうね。

by 株式会社Arrvis

続きを読む

[Rails] Carrierwave で S3 にファイルをアップロードしようとしたときにハマった(ArgumentError: wrong number of arguments)

Rails5 で Carrierwave 使って S3 にファイルをアップロードしようとしたら、モデルを保存する際に決まったエラーが出てハマった。

> model = Model.new([画像やらなんやら])
> model.save
 => ArgumentError: wrong number of arguments (given 3, expected 1)

Carrierwaveの中身を見たりしたけどよくわからず、設定を見直すことに。

config/initializers/carrierwave.rb の設定は、ネットからパクったやつなんでちゃんと見てなかったけど、こんな行があった。

  config.cache_storage = :fog   

キャッシュも fog を通して S3 に上げますよ。っていう設定らしい。

ここで原因に気づきました。テストでファイルのアップロードを試していたのですが、テスト環境では S3 ではなくローカルに保存するよう設定を書いていた。。。

(app/uploader以下のuploaderクラスのファイル)

if Rails.env.development?
  storage :file
elsif Rails.env.test?
  storage :file
else
  storage :fog
end

結局、development と test 環境では ローカル にファイルを保存するって書いてんのに、キャッシュは fog で S3 にとるぜ、っていう設定になっているためでした。

対策としては、config/initializers/carrierwave.rb の設定を環境ごとにちゃんと分けるコト。(あたりまえのことかもしれませんが…)

if Rails.env.development?                                                                                                                   
  CarrierWave.configure do |config|
   # developmentのもろもろの設定
  end 
elsif Rails.env.test?
  CarrierWave.configure do |config|
    # testのもろもろの設定
  end 
else
  CarrierWave.configure do |config|
    # productionのもろもろの設定
    config.cache_storage = :fog # fogを使う環境だけに適用!
  end 
end

こっちに storage の設定も一緒に書いた方がいいかもしれないとも思った。

コピペで済ませようとしたせいで起きた初歩的なミスでした。

続きを読む

AWS Elasticsearch Serviceが律儀にgzipを返すようになって死んだ [解決済み]

エラー内容

今朝起きたら、 Elasticsearch + Rails にこんなエラーが起こっていた。

MultiJson::ParseError: unexpected character at line 1, column 1 [parse.c:664]

なんと、いままで Accept-Encoding を無視していた AWS Elasticsearch Service が、急に仕事をしだして、 gzip 形式で返すようになった模様。

追記: なお、 elasticsearch-rails で利用されている faraday は、 Accept-Encoding: gzip がデフォなようです。

バージョン

追記しました

elasticsearch (5.0.0)
elasticsearch-api (5.0.0)
elasticsearch-transport (5.0.0)
elasticsearch-dsl (0.1.4)
elasticsearch-model (0.1.9)
elasticsearch-rails (0.1.9)
elasticsearch-transport (5.0.0)
faraday (0.9.2)
faraday_middleware (0.12.2)
faraday_middleware-aws-signers-v4 (0.1.5)

最新版からは若干古いです。

解決法

faraday_middleware gemを使って、gzipを受け取れるように変更する。

Gemfile

...
gem 'faraday_middleware'
...

config/initializers/aws.rb


Elasticsearch::Model.client = Elasticsearch::Client.new(
  host: ENV.fetch('ELASTICSEARCH_ENDPOINT'),
  port: 80
) do |faraday|
  faraday.use FaradayMiddleware::Gzip # ここを追加
  faraday.request :aws_signers_v4,
                  credentials: Aws::Credentials.new(ENV.fetch('AWS_KEY_ID'), ENV.fetch('AWS_ACCESS_KEY')),
                  service_name: 'es',
                  region: 'ap-northeast-1'
  faraday.adapter Faraday.default_adapter
end

被害者の会

他にも被害者はいる模様。
https://forums.aws.amazon.com/thread.jspa?threadID=223784

Posted on: Sep 26, 2017 10:56 AM

Just my personal experience –
Without any client changes, one day the ES responses stopped working. When I inspected the response, it was gzipped. The client library I was using requested gzip. Since I didn’t make any changes to my client code, I am guessing that the following things are true, but none of them are really proven:

a) the client library had always been requesting gzipped responses ( evidence: https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326204925 )
b) the client library was not equipped to receive gzipped responses ( evidence: my code doesn’t work )
c) the AWS ES service used to ignore the request for gzipped response, and now it honors it. ( evidence: my code used to work )

I was not able to figure out how to get my client library to stop requesting gzipped responses (seems it would require modifying the faraday library), but by changing my code to look like https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326020618 , I was able to get my client library to understand the gzipped response.

Hope those data points help.

ただ、時系列がずれているので、AWSが順次アップデートをしていったのだろうか?
詳細はAWSにコンタクトする予定。

こちらは、ほぼ同時刻の被害者の方
https://qiita.com/s_nakamura/items/49b19f2544aa61229bbc

コメント

gzip対応してないのにAccept-encodingでgzip投げてた利用者側が悪いのでは?

仰る通りです。。
OSSのライブラリを使っている以上、そのライブラリの挙動の責任は利用者にありますね。この記事のタイトルから「AWSやべー」という声が聞こえてきそうですが、どちらかと言うと、「いままでgzip無視しててごめんね、今朝直しといたから」と言った感じですね。

Amazon Elasticsearchのgzipの件、全ノードでgzipが返ってくるわけじゃなくてあるノードでだけgzip返ってくる罠
挙動が戻ってまたエラーになったら…

レスポンスがgzipでない場合、何もせず通過させるので、この変更を入れておけば、仮に挙動がもとに戻っても大丈夫なはずです。

https://github.com/lostisland/faraday_middleware/blob/master/lib/faraday_middleware/gzip.rb#L25

追記 10/4 11:15 p.m. JST

とここまで書いてて気づいたが、 faraday_middleware によると、 Faraday.default_adapter == :net_http を使うと、Gzipは自動解凍されるよう。。。なんだこれ

  # This middleware is NOT necessary when these adapters are used:
  # - net_http on Ruby 1.9+
  # - net_http_persistent on Ruby 2.0+
  # - em_http

追記 10/5 12:10 a.m. JST

真の原因は、これかな?(未検証)

https://github.com/elastic/elasticsearch-ruby/issues/457#issuecomment-326204925
faraday_middleware-aws-signers-v4 は IAM Role から Elasticsearch Service の認証通す middleware

Can this be the reason why compression is enabled by default ?
https://github.com/winebarrel/faraday_middleware-aws-signers-v4/blob/588b8c1086a72e2762ea3834bfff3d9f71b66024/lib/faraday_middleware/request/aws_signers_v4.rb#L70

if Net::HTTP::HAVE_ZLIB
  env.request_headers['Accept-Encoding'] ||= 'gzip;q=1.0,deflate;q=0.6,identity;q=0.3'
end

続きを読む

EC2サーバにRuby環境構築

やりたいこと

AWSのEC2サーバ上で、rbenv + Ruby最新版が動く環境を作る

インスタンス情報

  • インスタンスタイプ t2.micro
  • Amazon Linux AMI 2017.03.1.20170812 x86_64 HVM

rbenvインストール

githubのリポジトリからクローンしてきます
gitが入ってない場合は先にyumでgitをインストールします

$ sudo yum install git -y

まずは本体をインストール

$ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
Cloning into '/home/ec2-user/.rbenv'...
remote: Counting objects: 2629, done.
remote: Total 2629 (delta 0), reused 0 (delta 0), pack-reused 2629
Receiving objects: 100% (2629/2629), 488.96 KiB | 1.14 MiB/s, done.
Resolving deltas: 100% (1645/1645), done.

リポジトリをクローンしてきたらパスを通してコマンドが打てるようにします

$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile

ちゃんとコマンドが通るか確認

$ rbenv -v
rbenv 1.1.1-6-g2d7cefe

これでインストール完了っぽく見えますがまだインストールコマンドは通りません

$ rbenv install -l
rbenv: no such command `install'

ruby-buildのインストール

ruby-buildはRubyをインストールするためのrbenvのプラグインです。
これをインストールすることで、「rbenv install {バージョン}」でRubyをインストール出来るようになります。

$ git clone git://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
Cloning into '/home/ec2-user/.rbenv/plugins/ruby-build'...
remote: Counting objects: 7936, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 7936 (delta 0), reused 1 (delta 0), pack-reused 7934
Receiving objects: 100% (7936/7936), 1.67 MiB | 1.15 MiB/s, done.
Resolving deltas: 100% (4877/4877), done.

インストールスクリプトの実行

$ cd ~/.rbenv/plugins/ruby-build
$ sudo ./install.sh

これで、以下のコマンドからインストール可能なRubyのバージョン一覧が見れるはず
$ rbenv install -l
現時点でのRubyの最新バージョンは2.4.2みたい

Rubyインストール

$ rbenv install 2.4.2
Downloading ruby-2.4.2.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.2.tar.bz2
Installing ruby-2.4.2...

BUILD FAILED (Amazon Linux AMI 2017.03 using ruby-build 20170914-2-ge40cd1f)

Inspect or clean up the working tree at /tmp/ruby-build.20170930203342.4180
Results logged to /tmp/ruby-build.20170930203342.4180.log

Last 10 log lines:
The Ruby openssl extension was not compiled.
The Ruby readline extension was not compiled.
The Ruby zlib extension was not compiled.
ERROR: Ruby install aborted due to missing extensions
Try running `yum install -y openssl-devel readline-devel zlib-devel` to fetch missing dependencies.

Configure options used:
  --prefix=/home/ec2-user/.rbenv/versions/2.4.2
  LDFLAGS=-L/home/ec2-user/.rbenv/versions/2.4.2/lib 
  CPPFLAGS=-I/home/ec2-user/.rbenv/versions/2.4.2/include 

怒られました。
エラーメッセージの中に
「Try running yum install -y openssl-devel readline-devel zlib-devel to fetch missing dependencies.」
とあるのでその通りにしてみます。

$ sudo yum install -y openssl-devel readline-devel zlib-devel

もう一度先程のコマンドをトライ

$ rbenv install 2.4.2
Downloading ruby-2.4.2.tar.bz2...
-> https://cache.ruby-lang.org/pub/ruby/2.4/ruby-2.4.2.tar.bz2
Installing ruby-2.4.2...
Installed ruby-2.4.2 to /home/ec2-user/.rbenv/versions/2.4.2

成功しました
以下のコマンドでこのOSで使用するRubyのバージョンを2.4.2と宣言
$ rbenv global 2.4.2

ちゃんと切り替わってるか確認

$ ruby -v
ruby 2.4.2p198 (2017-09-14 revision 59899) [x86_64-linux]
ちなみに

以前まではrbenvを使って新しくRubyのバージョンをインストールした時やバージョンを切り替えた時は
$ rbenv rehash
というコマンドを打つお約束がありましたが、いつの間にか必要なくなっていたそうで。

参考URL
rbenvでrehashがいらなくなった

とりあえずこれでRubyが動くようになりました
最終的にはrailsアプリをEC2上で動かしてインターネットから閲覧出来るようにしたいのですがそれについてはまたいつか。

続きを読む