- 投稿日:2020-09-15T23:44:12+09:00
《未経験→webエンジニア》実務2日目
この記事の目的
自分がやったこと、知らなかったこと、やるべきことを明確にし
1日あたりの成長速度を速める。【今日やったこと】
APIテスト
postmanを利用して、
主に登録フォームにpost情報を送った際に
期待通りに帰ってくるかのテスト【知らなかったこと】
postmanというツール、使い方
【明日】やるべきこと、読みたい記事など
AWS資格取得のために、やるべきこと
https://qiita.com/x5dwimpejx/items/0b9b7e7b502d8bd255e9Dokerのチュートリアル記事!
https://qiita.com/Michinosuke/items/5778e0d9e9c04038903c
- 投稿日:2020-09-15T22:27:08+09:00
【自分用メモ】Ruby on Railsの環境構築(Windows)
目的
WindowsでRuby on Railsの環境構築を行った際に詰まったところを自分への備忘録として振り返ります。※Ruby6.0.0以前の手法になります。
こちらを参考にさせていただきました。
Ruby on Railsの環境構築をしてみよう!(Windows) - Progate
Devkit を Windows7 にインストールする手順 - Qiita
msys2 と GNU ツールチェーン類 (gcc, g++, gfortran, findutils ...
Windowsで異なるバージョンのRubyを使う方法(uru) - QiitaRubyのインストール
Downloads - Ruby Installer
太字になっている「Ruby+Devkit 2.6.6-1 (x64) 」のRubyInstallerをダウンロードし、インストールを進めました。
ただMSYS2をインストールする際に「Installation failed」というエラーが発生。
ちなみに、FWを落としても同じ事象が発生しました。 そのため、個別にインストールしていくことにしました。まずRubyのインストール
Downloads - Ruby Installerから Ruby 2.6.6-1 (x64)のインストーラをダウンロードし、ダブルクリック
問題なければ「I accept the License」を選択
「Add Ruby exexutables to your PATH」、「Associate .rb and .rbw files with this Ruby installation」、「Use UTF-8 as default external encoding」のチェックはつけたまま、[Install]をクリック
「Run 'ridk install' to setup MSYS2 and development toolchain.」のチェックは外して[Finish]をクリック
これでRubyのインストールは終了です。念のため、アンチウィルスソフト(私の環境ではWindows Defender)の除外欄に「C:\Ruby26-x64」を追加しておきました。
Devkitのインストール
Download Archives - Ruby Installer
上記サイトの右下のDevkitのインストーラをダウンロード
インストーラ(7zの自己解凍ファイル)をダブルクリックし適当なフォルダを選択し、[Extract]をクリック
下記コマンドを実行し、config.ymlを生成
cmd.exe> cd c:\opt\devkit > ruby dk.rb init [INFO] found RubyInstaller v2.6.6 at C:/Ruby26-x64 Initialization complete! Please review and modify the auto-generated 'config.yml' file to ensure it contains the root directories to all of the installed Rubies you want enhanced by the DevKit.config.yml をエディタで開き、Rubyのパスが指定されていなければ指定します。
Devkitのインストール
下記コマンドを実行します。cmd.exe> ruby dk.rb install [INFO] Updating existing gem override for 'C:/Ruby26-x64' [INFO] Installing 'C:/Ruby26-x64/lib/ruby/site_ruby/devkit.rb'SQLite3のインストール
Sqlite3のダウンロードページで「Precompiled Binaries for Windows」のところを確認し「sqlite-dll-win64-x64-3330000.zip」、「sqlite-tools-win32-x86-3330000.zip」ダウンロード。
ダウンロードしたフォルダを解凍し「sqlite3.dll」、「sqlite3.exe」というファイルを「C:¥Ruby26-x64¥bin」へコピーします。
(※sqlite3.exeは32bit版しかありませんが、64bitにも対応しているようです。)
ここまで来て、「gem install rails -v "5.2.3"」のコマンドを実行すると、ridk installのコマンドを実施するか、
MSYS2をインストールしてくださいという旨のメッセージが出ました。MSYS2をインストール
MSYS2のサイトよりmsys2-x86_64-20200903.exeをダウンロード。
先に[コントロールパネル]-[システムとセキュリティ]-[システム]-[環境変数(N)]より環境変数を設定します。● システム環境変数MSYSTEM: MINGW64
● システム環境変数PKG_CONFIG_PATH: /usr/lib/pkgconfig:/usr/share/pkgconfig:/lib/pkgconfig
● システム環境変数Pathの末尾に追加:
- C:\msys64\mingw64\bin
- C:\msys64\usr\local\bin
- C:\msys64\usr\bin
- C:\msys64\bin
ダウンロードしたファイルをダブルクリックしインストールを進めます。 インストールの進行状況は以下になります。 [次へ]を押して進めます。
[詳細の表示]を押すと進捗状況が確認できます。
インストールが終了したら[次へ]を押します。
インストールが終了したら[次へ]を押します。
[完了]を押します。
MSYS2のbash画面が起動しますが、一旦閉じます。
MSYS2のbashを管理者として実行します。
ただ私の環境では右クリックのメニューに[ファイルの場所を開く]しかなかったため、一旦ファイルの場所を開きスタートメニューのショートカット群から「MSYS2 MSYS」の[管理者として実行]をしました。
下記コマンドを実行し環境変数を確認。bash>echo $MSYSTEM >echo $PATH >echo $PKG_CONFIG_PATH下記コマンドを実行し、何か聞いてきたら[y]を入力しEnterキーを押します。
bash> pacman -Sy pacman
終わったら、一旦MSYSの画面を閉じます。
もう一度、MSYS2 MSYSを[管理者として実行]します。
パッケージデータベースと pacman とコアパッケージの更新
下記コマンドを実行し、何か聞いてきたら[y]を入力し、Enterキーを押します。bash>pacman -Syuパッケージの更新
再度MSYS2 MSYSを[管理者として実行]します。 下記コマンドを実行します。bash> pacman -Su私の環境では「何も行うことがありません」とメッセージが表示されました。
終わったら、MSYS画面を閉じます。Windowsのコマンドプロンプトを起動し、下記コマンドを実行します。
cmd.exe> where bash > where ls
私の環境ではwslをインストールしている影響からか「C:\Windows\System32\bash.exe」が表示されましたが、特に影響はないようです。Windows版Rubyのバージョン管理uruをインストール
Ruby on Railsを使う環境によっては違うバージョンのRubyを使い分ける場面が出てくると思いますので、uruをインストールしました。
jonforums / uru / wiki / Downloads — Bitbucketよりuru-0.8.5-windows-x86のファイルをダウンロード
※32bit版ですが、64bitにも対応しているようです。
ダウンロードされた7zを解凍し、適当なフォルダに移動します。
私はC:\opt以下に移動しました。
移動したuruフォルダをパスに通します。
コマンドプロンプトで下記コマンドを実行します。
cmd.exe>uru_rt admin install ---> Installing uru into C:\opt\uru-0.8.5-windows-x86Rubyへのパスをuruに登録します。
cmd.exe> uru admin add C:\Ruby26-x64\bin ---> Registered ruby at `C:\Ruby26-x64\bin` as `266p146`uruに登録されたか確認。
cmd.exe> uru ls 266p146 : ruby 2.6.6p146 (2020-03-31 revision 67876) [x64-mingw32]ridk installを実行
次に、ridk installを実行します。
cmd.exe> ridk install
[1]を入力し、Enterキーを押します。
すでにインストール済のようです。[2]を入力し、Enterキーを押します。
もう最新の状態のようです。もうエラーも出ていません。[3]を入力し、Enterキーを押します。
インストール進行中です。
「succeeded」と表示され正常に終了したようです。
Enterキーを押します。Ruby on Railsのインストール
再度コマンドプロンプトを起動し、下記コマンドを実行します。
cmd.exe> gem install rails -v "5.2.3"「installed」というメッセージが確認できたのでgemのインストールまで終了したようです。
railsのバージョンを確認してみます。
cmd.exe> rails -v Rails 5.2.3Railsのインストールまで正常終了しました。
Railsサンプルアプリの作成
サンプルアプリを作ってみましょう。
下記コマンドを実行します。
cmd.exe> rails new sample_app -Gsample_appはアプリ名ですので自由につけることができます、
-GオプションはGitをインストールしていない場合につけます。「Bundle complete!」というメッセージが表示され、正常に終了したようです。
NOTICEというところにchromedriver-helperはサポート終了した旨表示が出ていますが、こちらはまたの機会にしたいと思います。
sample_appという名前のフォルダがあるので、Railsアプリケーションの作成が完了したことになります。
Railsを起動
cmd.exe> cd sample_appsample_appフォルダに移動します。
下記コマンドを実行します。
cmd.exe> rails scmd.exerails s => Booting Puma => Rails 5.2.4.4 application starting in development => Run `rails server -h` for more startup options *** SIGUSR2 not implemented, signal based restart unavailable! *** SIGUSR1 not implemented, signal based restart unavailable! *** SIGHUP not implemented, signal based logs reopening unavailable! Puma starting in single mode... * Version 3.12.6 (ruby 2.6.6-p146), codename: Llamas in Pajamas * Min threads: 5, max threads: 5 * Environment: development * Listening on tcp://localhost:3000 Use Ctrl-C to stop「localhost:3000」でサーバが起動しているのがわかります。
ブラウザで「localhost:3000」にアクセスしてみます。
Railsサーバが起動したことが確認できました。
[Ctrl]+[C]キーを押下し、サーバを止めることができます。
完了
- 投稿日:2020-09-15T22:06:27+09:00
【rails】エラーメッセージ とはなんだったのか
エラーメッセージの生成と表示方法
生成
>> user = User.new >> user.save #エラーメッセージ生成 => false >> user.error.full_messages => [Failed"", "invalid"]オブジェクトを保存する際、falseが返ってきた瞬間に配列の要素としてエラーメッセージが生成される。
表示
erb<% @user.errors.full_messages.each do |msg| %> <%= msg %> <% end %>配列の要素をeach文で、それぞれ取り出し、表示させることができる
エラー時にのみ出現するClass属性
class="field_with_errors"
自動でエラー箇所の入力部品を囲んでくれる。実装
パーシャルを使いつつ、どんなオブジェクトでも対応できるように実装させている。
shared/_error_messages.html.erb<% if object.errors.any? %> <div id="error_explanation"> <div class="alert alert-danger alert-form-extend" role="alert"> <%= object.errors.count %>個のエラーがある </div> <ul> <% @user.errors.full_messages.each do |msg| %> <li><%= msg %></li> <% end %> </ul> </div> <% end %>users/new.html.erb<%= form_with(model: @user, url: users_path, local: true) do |form| %> <%= render 'shared/error_messages', object: form.object %>users_controller.rbdef create @user = User.new(user_params) if @user.save ~~ else render 'new' end endapplication.scss#error_explanation { color: red; ul { color: red; padding-bottom: 0; } } .field_with_errors .form-control { border-color: red; }エラーメッセージを日本語化
Gemfilegem 'rails-i18n'$ bundle installエラーメッセージを日本語に設定
config/application.rbmodule SampleApp class Application < Rails::Application config.i18n.default_locale = :ja config.i18n.load_path += Dir[Rails.root.join('config', 'locales', '**', '*.{rb,yml}').to_s] end endどのコードが、どの日本語に対応させるのかを設定
$ mkdir config/locales/models $ touch config/locales/models/ja.ymlja.ymlja: activerecord: models: user: ユーザ attributes: user: name: 名前 email: メールアドレス password: パスワード password_confirmation: パスワード(再入力)エラーメッセージを手動追加 errors.add
user.errors.add(:base, "追加エラー")
もちろんエラーを生成してから出ないと、追加できないので、流れとしては以下の通り。
> user = User.new > user.errors > user.errors.add(:base, "追加エラー") > user.errors.full_messages => ["追加エラー"]
- 投稿日:2020-09-15T21:15:50+09:00
(ギリ)20代の地方公務員がRailsチュートリアルに取り組みます【第9章】
前提
・Railsチュートリアルは第4版
・今回の学習は3周目(9章以降は2周目)
・著者はProgate一通りやったぐらいの初学者基本方針
・読んだら分かることは端折る。
・意味がわからない用語は調べてまとめる(記事最下段・用語集)。
・理解できない内容を掘り下げる。
・演習はすべて取り組む。
・コードコピペは極力しない。
続いて第9章、認証システムの開発・第4段回目、ログイン実装後半です。第8章では一時的なセッションだったものを、cookieを利用して永続的なものに切り替えていきます。
セキュリティに関する用語が飛び交いますが、ある程度内容は押さえていきましょう。ドコモ口座の件もありますし、セキュリティの意識を高めねば。
本日のBGMはこちら。
死んだ僕の彼女 "Aki No Hachiouji"
徐々に秋の訪れを感じますね。
【9.1.1 記憶トークンと暗号化 メモと演習】
パスワード:ユーザーが作成・管理
トークン :コンピュータが作成・管理
urlsafe_base64:Ruby標準ライブラリのSecureRandomモジュールにあるメソッド。A–Z、a–z、0–9、"-"、"_"のいずれかの文字 (64種類) からなる長さ22のランダムな文字列を返す。下記の5つの永続的セッション作成方針を頭に入れて進めましょう。
1.記憶トークンにはランダムな文字列を生成して用いる。
2.ブラウザのcookiesにトークンを保存するときには、有効期限を設定する。
3.トークンはハッシュ値に変換してからデータベースに保存する。
4.ブラウザのcookiesに保存するユーザーIDは暗号化しておく。
5.永続ユーザーIDを含むcookiesを受け取ったら、そのIDでデータベースを検索し、記憶トークンのcookiesがデータベース内のハッシュ値と一致することを確認する。1. コンソールを開き、データベースにある最初のユーザーを変数userに代入してください。その後、そのuserオブジェクトからrememberメソッドがうまく動くかどうか確認してみましょう。また、remember_tokenとremember_digestの違いも確認してみてください。
→ 下記。ハッシュ化されたremember_digestが保存されています。>> user = User.first User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]] => #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2020-09-12 09:09:50", updated_at: "2020-09-12 09:09:50", password_digest: "$2a$10$hrOEzw0faSd4yurmH8bQJOnggeNnUqTZg33yE9g7Tnk...", remember_digest: nil> >> user.remember (0.1ms) begin transaction SQL (3.0ms) UPDATE "users" SET "updated_at" = ?, "remember_digest" = ? WHERE "users"."id" = ? [["updated_at", "2020-09-13 22:33:06.439353"], ["remember_digest", "$2a$10$IQ/x1avxRSAG281J18FRi.f2icjx8Kac5y8bWua5IDVae.C.Kdwcu"], ["id", 1]] (5.9ms) commit transaction => true >> user.remember_token => "aGtKYk5iEjSHFs16uB7xTQ" >> user.remember_digest => "$2a$10$IQ/x1avxRSAG281J18FRi.f2icjx8Kac5y8bWua5IDVae.C.Kdwcu"
2. リスト 9.3では、明示的にUserクラスを呼び出すことで、新しいトークンやダイジェスト用のクラスメソッドを定義しました。実際、User.new_tokenやUser.digestを使って呼び出せるようになったので、おそらく最も明確なクラスメソッドの定義方法であると言えるでしょう。しかし実は、より「Ruby的に正しい」クラスメソッドの定義方法が2通りあります。1つはややわかりにくく、もう1つは非常に混乱するでしょう。テストスイートを実行して、ややわかりにくいリスト 9.4の実装でも、非常に混乱しやすいリスト 9.5の実装でも、いずれも正しく動くことを確認してみてください。ヒント: selfは、通常の文脈ではUser「モデル」、つまりユーザーオブジェクトのインスタンスを指しますが、リスト 9.4やリスト 9.5の文脈では、selfはUser「クラス」を指すことにご注意ください。わかりにくさの原因の一部はこの点にあります。
→ 指示通り書くと、両方ともGREENです。ここで出てくる書き方について調べてみました。最初のUser→selfに置き換えたものを特異メソッド方式、class << selfでまとめたものを特異クラス方式と呼ぶようです。詳しくはこちらの記事へ。
【9.1.2 ログイン状態の保持 メモと演習】
permanentメソッド:20年後に期限切れにする。
signedメソッド:デジタル署名と暗号化の両方の処理を行う。
1. ブラウザのcookieを調べ、ログイン後のブラウザではremember_tokenと暗号化されたuser_idがあることを確認してみましょう。
→ たしかに両方とも増えていました!
2. コンソールを開き、リスト 9.6のauthenticated?メソッドがうまく動くかどうか確かめてみましょう。
→ 演習1で確認したremember_tokenを引数に入れればOK。user = User.first 略 user.authenticated?("演習1のremember_token") => true
【9.1.3 ユーザーを忘れる メモと演習】
ユーザーを忘れる=remember_digestをnilで更新する。
1. リスト 9.16で修正した行をコメントアウトし、2つのログイン済みのタブによるバグを実際に確かめてみましょう。まず片方のタブでログアウトし、その後、もう1つのタブで再度ログアウトを試してみてください。
→ NoMethodError in SessionsController#destroy
undefined method `forget' for nil:NilClass
2. リスト 9.19で修正した行をコメントアウトし、2つのログイン済みのブラウザによるバグを実際に確かめてみましょう。まず片方のブラウザでログアウトし、もう一方のブラウザを再起動してサンプルアプリケーションにアクセスしてみてください。
→ 地味にめんどいので省略
3. 上のコードでコメントアウトした部分を元に戻し、テストスイートが red から greenになることを確認しましょう。
→ Yes, GREEN !
【9.1.4 2つの目立たないバグ 演習】
1. 8.1.4の処理の流れが正しく動いているかどうか、ブラウザで確認してみてください。特に、flashがうまく機能しているかどうか、フラッシュメッセージの表示後に違うページに移動することを忘れないでください。
→ 試してみましょう。違うページに行くとフラッシュが消えます。
【9.2. [Remember me]チェックボックス メモと演習】
三項演算子というものが出てきました。if-else文を一行で書けるようです。これでリスト8.21のコードの形がみえてきました(いまだに::はしっくりきてないけど)。つまり、下のコードは
cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.costこのようになると。
if cost = ActiveModel::SecurePassword.min_cost BCrypt::Engine::MIN_COST else BCrypt::Engine.cost end
1. ブラウザでcookies情報を調べ、[remember me] をチェックしたときに意図した結果になっているかどうかを確認してみましょう。
→ いけてます。
2. コンソールを開き、三項演算子を使った実例を考えてみてください (コラム 9.2)。
→ 至極シンプルに下記>> x = 6 => 6 >> x % 3 == 0 ? "3の倍数" : "ちゃうな" => "3の倍数"
【9.3.1 [Remember me]ボックスをテストする 演習】
1. リスト 9.25の統合テストでは、仮想のremember_token属性にアクセスできないと説明しましたが、実は、assignsという特殊なテストメソッドを使うとアクセスできるようになります。コントローラで定義したインスタンス変数にテストの内部からアクセスするには、テスト内部でassignsメソッドを使います。このメソッドにはインスタンス変数に対応するシンボルを渡します。例えばcreateアクションで@userというインスタンス変数が定義されていれば、テスト内部ではassigns(:user)と書くことでインスタンス変数にアクセスできます。本チュートリアルのアプリケーションの場合、Sessionsコントローラのcreateアクションでは、userを (インスタンス変数ではない) 通常のローカル変数として定義しましたが、これをインスタンス変数に変えてしまえば、cookiesにユーザーの記憶トークンが正しく含まれているかどうかをテストできるようになります。このアイデアに従ってリスト 9.27とリスト 9.28の不足分を埋め (ヒントとして?やFILL_INを目印に置いてあります)、[remember me] チェックボックスのテストを改良してみてください。
→ userを@userに、ログインテスト該当箇所を下記のとおりに。sessions_controller.rbdef create @user = User.find_by(email: params[:session][:email].downcase) if @user && @user.authenticate(params[:session][:password]) log_in @user params[:session][:remember_me] == '1' ? remember(@user) : forget(@user) redirect_to @user else flash.now[:danger] = "Invalid email/password combination" render 'new' end endusers_login_test.rbtest "login with remembering" do log_in_as(@user, remember_me: '1') assert_equal cookies['remember_token'], assigns(:user).remember_token endこのログインテストのcookies['remember_token']なんですが、当初はcookies[:remember_token]を試したところテスト通過。なぜだろうと思って調べると、こちらの記事が。現在はシンボルでもOKなのかな?でもチュートリアルのRailsのバージョンは古いしなあ…。
【9.3.2 [Remember me]をテストする 演習】
1. リスト 9.33にあるauthenticated?の式を削除すると、リスト 9.31の2つ目のテストで失敗することを確かめてみましょう (このテストが正しい対象をテストしていることを確認してみましょう)。
→ 該当箇所で失敗してました。
第9章まとめ
・cookiesメソッドでユーザーidと記憶トークンを永続化。
・データベースに絡む動作はUserクラスに定義。Sessionコントローラで使用するメソッドは主にSessionヘルパーに定義。前者は後者でも使用。
・remember_me機能は値が1か0かでクッキーを保持するか削除するか分けるスイッチのようなもの。
・current_userはセッションかクッキーの状態・情報によって決まる。
・情報技術用語が飛び交っていたので用語集にまとめています。
この章はややこしいですね…。webページにみえてこない部分なので、どうしてもイメージが描き切れない部分があります。とはいえ、セキュリティに関する部分は疎かにできません。時間がたってからもう一回やってもいいかも。さて次!第10章!未実装のユーザー機能を実装していきます!
⇦ 第8章はこちら
学習にあたっての前提・著者ステータスはこちら
なんとなくイメージを掴む用語集
・セッションハイジャック
通信の当事者でない第三者(攻撃者)が何らかの手段でセッションIDを知ることにより、セッションを乗っ取る攻撃手法。・パケットスニッファ
LANアナライザの俗語。LAN上を通過するトラフィックを監視したり記録するためのハードウェアやソフトウェアのこと。・クロスサイトスクリプティング(XSS)
Webサイトに利用されるアプリケーションの脆弱性もしくはその脆弱性を悪用した攻撃のこと。特にWeb閲覧者側が制作することのできる動的サイト(例:TwitterなどのSNS、掲示板等)に対して、その脆弱性を利用して悪意のある不正なスクリプトを挿入することにより発生するサイバー攻撃。・デジタル署名
書面上の手書き署名のセキュリティ特性を模倣するために用いられる公開鍵暗号技術の一種。・ソルト
パスワードやパスフレーズなどのデータをハッシュ化する際に、一方向性関数の入力に加えるランダムなデータのこと。本文中訳注にあるように、人には念をの塩ひとつまみで暗号を強化する。・assert_empty
obj.emptyはtrueであると主張する。・assert_nil
obj.nil?はtrueであると主張する。
- 投稿日:2020-09-15T20:15:24+09:00
webpackerのしくみ
webpackerについて
前回webpackerをインストールする際、こんな記事を書きました。
Webpackerとは (Rails6で rails s してエラーが出た時の話)webpackerの構成
webpackerのインストールで生成されたディレクトリapp/javascript/packsの中に
application.jsというファイルが作成されます。
webpackerはapp/javascript/packsディレクトリにあるファイルをエントリポイントとしてコンパイルを実行します。
それ以外のjsファイルはapp/javascript配下の他のディレクトリに置き、app/javascript/packs/application.jsから読み込みます。(※エントリポイント・・・コンピュータプログラムを実行する際に、一番最初に実行することになっている箇所のこと。ここでは "コンパイルを開始するファイル" の意味。)
例:
app/
├ javascript/
│ └channels/
│ └ packs/
│ └ application.js (エントリポイント)
└ example/
└ example.jsapp/javascript/packs/application.js// デフォルトの記述 require("@rails/ujs").start() require("turbolinks").start() require("@rails/activestorage").start() require("channels") // example.jsを読み込む import '../example/example'app/views/layouts/application.htmlに下記を記述することで、コンパイルしたjavascriptをビューで読み込むことができます。
app/views/layouts/application.html.slim// slimの場合 = javascript_pack_tag 'application' // ERBの場合 <%= javascript_pack_tag 'application' %>もうちょっと詳しく
加筆予定です。
RailsアプリにReactを導入してみようと思います。
- 投稿日:2020-09-15T19:29:06+09:00
Docker・Rails開発でlocalhost3000が立ち上がらない。
- 投稿日:2020-09-15T18:58:15+09:00
条件式で注意したいこと
【概要】
1.結論
2.具体例
1.結論
上から読み込まれるので書く順番に気をつける!
2.具体例
たとえば、以下の3つの条件を基に出力するプログラムを記載する場合を見てみます。
❶20以下であれば"20以下の数字"と出力
❷20より大きい数字であれば"20より大きい数字"と出力
❸20以下で0以下であれば"0以下の数字"と出力この場合で以下の記載をしています。
int = gets.to_i if int <= 20 puts "20以下の数字" #❶ elsif int <= 0 puts "0以下の数字" #❷ else puts "20より大きい数字" #❸ endこの場合、int に"-1"と記載すると❶の
if int <= 20 puts "20以下の数字"が反応してしまい、本来出したい❷の出力がされません。
これはプログラムは上から下に順番に読み込まれるためです。(defはあとから読み込み、javascriptの関数宣言は先に読み込まれるといった例外はあります。)なので❶と❷の順番を入れ替えて
int = gets.to_i if int <= 0 puts "0以下の数字" #❶ elsif int <= 20 puts "20以下の数字" #❷ else puts "20より大きい数字" #❸ endとすると本来の意図を汲み取ったプログラムになります。
- 投稿日:2020-09-15T18:47:59+09:00
【感想】TechCampを受講してみて
約2ヶ月半のTechCampを卒業して、今後受講を検討している方の参考になればと思い、感想を述べます
誤解のないように申し上げると、上から目線で評論するつもりもなく、批判するつもりもないです。まず私としては短期集中型の受講料65万円という価格ほどの価値はなかったと感じてます。
価値としては40万円程かなと。理由とコメントを述べると、
・教材が基礎に毛が生えたレベル(それでも未経験者にとっては難しいです)
・Udemyや書籍など他の教材で学習できる(自学ができない人は受講の価値あり)
・質問し放題と謳ってはいるが、割と早々に質問するまでのハードルが上がる(正しい質問の仕方を学ぶチャンスではあるが、質問フォーマットを正確に作るためと、メンターと通話が繋がるまでに1〜2学習できるぐらいの時間がとられる)
・メンターの質のレンジが広い(ほとんどが良いメンター様でしたが、さっさと終わらせようという空気を出してくるメンターもいる)
・日々のスケジュールに沿って行動することでトータルで効率のいい1日を送ることができる(ただし人による。私は自分のペースが崩れると一気に集中力が落ちるので中盤以降はチームメイト了承の上、自分のペースでやってました)
・カリキュラム外の質問には答えてもらえない(特に自作アプリ以降は質問に答えてもらえません。本当はここでたくさんのことを教わりたかった)
・毎日解くRubyの問題が面白い(Paizaのようなものです。私のようなクイズ好きだと楽しいです)
・ライフコーチのホスピタリティが高い(改善点は促しつつも、非常に上手に自信とモチベーションを上げてくれる)
・キャリアアドバイザーから得られる情報の8割方がネットで得られる(何をどのように調べればいいかわからない人にとっては有益です)
・必ずしもチームメイトは高め合える存在とは限らない(早々に進捗に差がでても最後まで同じチームメイトです。苦楽を共にする点では◯。ほぼずっとオンラインなので同じ進捗具合の別チームの方と交流する機会を設けてほしかった)こんなところでしょうか。
上記と重複しますが、受講を検討されている方は、まずはProgateでプログラミングを遊んでみて、その後Udemyなどの動画教材で自学してみましょう。
わからないところはMenta等を利用してみましょう。
限定的ではありますが、福岡市内在住の方でしたらTechCampから徒歩5分程度のところに市が運営するエンジニアカフェというコワーキングスペースが無料で利用でき、午後からは駐在のエンジニアに質問することもできます。
上記のようなことをやってみて、どうしてダメだという方は検討してみてもいいかと思います。以上になりますが、満足度は半々と行った感じです。
受講料がもう10〜15万でも安ければ満足が高かったと思います。ただ後悔はしてません。
自分で選んで、そしてやりきった自負があるからです。最後になりますが、TechCampでお世話になった方々、心から感謝しております。
そしてこの記事が誰かの参考に少しでもなれば幸いです。
- 投稿日:2020-09-15T18:45:14+09:00
Railsのエラー画面を見やすくするgem
エラー画面を見やすくするgemを導入する
group :development, :test do gem 'better_errors' gem 'binding_of_caller' end
$ bundle install
導入前
導入後
Application Frames
ここでは、自分の書いたコードを対象にエラーに関係する箇所を明示してくれていますAll Frames
自分が書いた箇所以外(gemやActiveSupportなど)まで踏み込んでエラーに関係する箇所が表示されますローカルホスト以外のIPを許可
このままではlocalhostからのアクセスにしか対応していない。
他の開発環境でも実行できるようにするには、許可するIPの設定をしなければならない。config/emvironments/development.rbRails.application.configure do . . . end # endの外に以下を追記 BetterErrors::Middleware.allow_ip! "0.0.0.0/0"
- 投稿日:2020-09-15T16:42:28+09:00
RailsでFakerを使ったエラー処理
RailsでFakerを使ってダミーのユーザーを作成する
自分が忘れないためにメモしてます。
require 'faker' # メインのサンプルユーザーを1人作成する User.create!(firstname: "Kako", lastname: "Ogura", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar") # 追加のユーザーをまとめて生成する 99.times do |n| firstname = Faker::Name.first_name lastname = Faker::Name.last_name email = Faker::Internet.email password = "password" User.create!(firstname: firstname, lastname: lastname, email: email, password: password, password_confirmation: password) endこれで
$ rails db:migrate:reset $ rails db:seedすると
ActiveRecord::RecordInvalid: Validation failed: Email has already been taken
なぜ?????
そこからデータベースを削除したり、公式ドキュメントみたりいろいろ探索したところ、
そもそもサーバーを起動したままだから重くなってるのではないかと思い、一旦サーバーを落として実行すると、、、
できた!
- 投稿日:2020-09-15T16:19:28+09:00
updateする際に画像の初期値を入れる方法
[理想]曲名と曲の画像を投稿するサイトを作っていると仮定する。
inputタグの file uploadではセキュリティの関係上、アップロードしていた画像の初期値を持つことはできないらしい。
しかし、曲名だけ変更したくて画像はそのままで良い時でも、毎回使用されていた画像を探し出してきてアップロードし直さなきゃいけないのはユーザーのストレスになる。
試しに、画像を何も入れずに曲名だけ変更すると、presence trueのvalidationに引っ掛かりエラーになる。
-> 画像を入れ直さなくても他の値が編集できるようになりたい。[現状]
item_controllerdef update @item = Item.find(params[:id]) item = ItemsTag.new(item_update_params) if item.valid? item.update @item = Item.find(params[:id]) render :show else render :edit end enditems_tag.rbdef update item = Item.find_by(id: item_id) item.update(name: name, image: image, description: description, price: price, category_id: category_id, condition_id: condition_id, shipping_fee_person_id: shipping_fee_person_id, region_id: region_id, date_ish_id: date_ish_id, user_id: user_id, id: item_id) tag = Tag.where(tag_name: tag_name).first_or_initialize tag.update(tag_name: tag_name) enditemsテーブルとtagsテーブルの値がフォームオブジェクトを使って保存しているので、
paramsはformsで定義したupdateメソッドに引き渡され、updateアクションが実行されています。[方法]
上記の状態で、paramsを見てみると、findメソッドで探した@itemの中には、画像が入っているので、@item.imageと初期値が確認できる。しかし、ItemsTagでインスタンス生成したitemの中には画像が入っていない(active storage上には保存されているがitemsテーブルにもtagsテーブルにも「image」というカラムは存在しないため。)そのため、@itemの中に入ってる画像の初期値を引っ張ってくればオッケー。if item.image == nil
item.image = @item.image
end
と、「もしitem.imageの値がnilだった時は(=画像を何もアップロードしなかった時)item.imageの値(nil) を @item.image(初期値が入ってる)に上書きする。
という条件分岐をかける。
また、@item.image.blobでは、パラメーターからではなく、activestorageに保存されているデータベースから値を引っ張ってきているのでblobまで記述する必要がある。item_controllerdef update @item = Item.find(params[:id]) item = ItemsTag.new(item_update_params) if item.image == nil item.image = @item.image.blob end if item.valid? item.update @item = Item.find(params[:id]) render :show else render :edit end end以上です
- 投稿日:2020-09-15T15:56:19+09:00
[rails] ローカル環境のみでデータを保存する方法
この記事ではrails5.2.3を使用しています
ローカルのみで保存したい画像データが、プッシュするとリモートにも反映されてしまう問題が発生しました。
解決方法
.gitgnoreでGitで管理しないファイルを指定する。
.gitgnoreとは
railsのファイルにはデフォルトで.gitgnoreというファイルが存在しています。
gitignoreとは、Gitで管理しないファイルを指定することが出来るファイルの事です。
引用:https://pikawaka.com/word/gitignore#3.%20git%20pushまで実行した場合(コミット履歴が残る)手順
ローカルで保存するデータはあくまでテスト用なので、リモート環境にpushしたくありません。
今回、public/uploads以下に保存されているimageは、全てテスト用に保存したデータなので、今回は.gitgnoreファイルにpublic/uploadsを追記します。
.gitgnorepublic/uploadsこれで、public/uploads以下のデータはローカルのみで管理されるようになります。
すでにローカルからプッシュされた画像データがリモートに保存されている状態であっても、上記の作業を行った後に、再度commit→pushするとリモートに保存されたデータが消えて、今後ローカルで保存したデータもリモートには保存されなくなります。
参考
https://pikawaka.com/word/gitignore#3.%20git%20pushまで実行した場合(コミット履歴が残る)
- 投稿日:2020-09-15T15:43:28+09:00
逐次検索機能の実装
概要
今回は、逐次検索機能の実装について
カリキュラムをみながら自分で実装した時の理解度が低かったので、アウトプットの意味も込めて投稿しようと思います。逐次検索機能とは、例えば「ruby」「python」「ruby on rails」というタグがすでにデータベースに存在する場合、rの文字が入力されると、rの文字と一致する「ruby」「ruby on rails」を候補として瞬時に画面上に表示する機能です。
一般的にインクリメンタルサーチと呼ばれます。
インクリメンタルサーチとは、
文字の入力の都度、自動的に検索が行われる検索機能です。
JSのAjaxを用いて実装します。実装
jsファイルの記述
ターボリンクスをコメントアウトしてtag.jsを読み込めるようにします。
javascript/packs/application.jsrequire("@rails/ujs").start() // require("turbolinks").start() require("@rails/activestorage").start() require("channels") require("../tag")tag.jsを作成して編集します。
javascript/tag.jsif (location.pathname.match("posts/new")){ window.addEventListener("load", (e) => { const inputElement = document.getElementById("post_tag_name"); inputElement.addEventListener("keyup", (e) => { const input = document.getElementById("post_tag_name").value; const xhr = new XMLHttpRequest(); xhr.open("GET", `search/?input=${input}`, true); xhr.responseType = "json"; xhr.send(); xhr.onload = () => { const tagName = xhr.response.keyword; const searchResult = document.getElementById('search-result') searchResult.innerHTML = '' tagName.forEach(function(tag){ const parentsElement = document.createElement('div') const childElement = document.createElement('div') parentsElement.setAttribute('id', 'parents') childElement.setAttribute('id', tag.id ) childElement.setAttribute('class', 'child' ) parentsElement.appendChild(childElement) childElement.innerHTML = tag.name searchResult.appendChild(parentsElement) const clickElement = document.getElementById(tag.id) clickElement.addEventListener("click", () => { document.getElementById("post_tag_name").value = clickElement.textContent; clickElement.remove(); }) }) } }); }) };ここからは、コードの詳細を確認していきます。
javascript/tag.jsconst input = document.getElementById("post_tag_name").value; const xhr = new XMLHttpRequest(); xhr.open("GET", `search/?input=${input}`, true); xhr.responseType = "json"; xhr.send();ここでは、post_tag_nameというID名の要素に入力された値を「input」に代入後、Ajaxの記述を行っています。
後の編集で、searchアクションと紐付けるルーティングを設定するので「openメソッド」でsearchアクションへのパスを設定します。この時、「input」に代入されたバリューをqueryパラメータとして設定します。また、レスポンスのデータ型は「json」と指定して、送信を行っています。
また、あとでsearchアクション(タグの検索)をコントローラーで行うための記述を行います。
keywordというキーに対応するバリューとしてセットして、jsonデータとして返す記述をあとでコントローラーにします。
javascript/tag.jsconst parentsElement = document.createElement('div') const childElement = document.createElement('div')ここでは、インクリメンタルサーチの結果を画面上に表示させるために、div要素を作成しています。
作成したdiv要素の中に、インクリメンタルサーチの結果を加えていきます。javascript/tag.jsparentsElement.setAttribute('id', 'parents') childElement.setAttribute('id', tag.id ) childElement.setAttribute('class', 'child' )先ほど作成したdiv要素にIDとクラス名を与えています。
childElementには、表示させるタグのIDを代入します。また、CSSを割り当てるためのクラス名も与えています。javascript/tag.jsparentsElement.appendChild(childElement) childElement.innerHTML = tag.name searchResult.appendChild(parentsElement)この部分では、parentsElementの子要素としてchildElementを加えます。次にchildElementに表示させる、タグのHTMLを生成させます。最後に、searchResultの子要素にparentsElementを加えています。
javascript/tag.jsconst searchResult = document.getElementById('search-result') searchResult.innerHTML = ''二文字目以降に重複して表示されないように、searchResultの中へ空文字の代入をしています。
javascript/tag.jsconst clickElement = document.getElementById(tag.id) clickElement.addEventListener("click", () => { document.getElementById("post_tag_name").value = clickElement.textContent; clickElement.remove();候補として表示させたタグがクリックされると、選択されたタグのテキスト要素を入力フォームのバリューとしてセットします。最後に、選択されたタグは、表示の一覧から削除します。
以上がインクリメンタルサーチの一連の動きになります。
ルーティングを設定
config/routes.rbresources :posts, only: [:index, :new, :create] do collection do get 'search' end endコントローラーを編集
controller/posts_controller.rbclass PostsController < ApplicationController def index @posts = Post.all.order(created_at: :desc) end ---省略--- def search return nil if params[:input] == "" tag = Tag.where(['name LIKE ?', "%#{params[:input]}%"]) render json:{ keyword: tag } end ---省略--- endこれで実装完了です。動作確認をしてみましょう。
エラーが起きた時
今回の実装で考えうるエラーの解決方法は、getElementByIdをした時のId名に注目してみることです。
今回の実装では、formオブジェクトのpost_tagモデルについてインクリメンタルサーチを実装しました。
また、jsの全文をみた時の一行目にパスの記述があります。
今回は新規投稿をする際のタグ入力で逐次検索をする機能を実装したかったので、posts/newとなっています。この二点以外はコピペでもいけるのではないでしょうか。
あ、ターボリンクス切り忘れとか、tag.js読み込み忘れは流石にやめましょう。
感想
javascriptはカリキュラムでも触れる機会が少なく、苦手意識があるので頑張ろうと思います。
- 投稿日:2020-09-15T14:04:15+09:00
gitの使い方(チームメンバー用)
チームメンバー向けgit,cmd,railsの使い方説明
※チームメンバー用なので雑です
ディレクトリを移動する
cd path\to\railsapp
git初期化
git init
gitのユーザー設定
git config --global user.name "ユーザー名"
git config --global user.email "メールアドレス"
リモートリポジトリをクローンする
git clone {URL}
読み込み元,書き込み先のURLを確認する
git remote -v
リモートリポジトリの最新状態を取得する
git pull origin master
現在の状態をステージに上げる(仮保存的なもの)
git add [ステージに上げるファイル]
仮保存をコミット(保存)する
git commit -m "コミットメッセージ"
リモートリポジトリに反映させる(最新状態でないとエラー)
git push origin master
DBを削除する
del db\development.sqlite3
マイグレーションする(テーブル作成)
rails db:migrate
DBにデータを挿入する
rails db:seed
Railsサーバー起動
rails s
※Ctrl+Cでサーバーを止めるPermission deniedのエラーが発生した場合はtmpフォルダを消すと治る時がある
- 投稿日:2020-09-15T10:34:10+09:00
Railsで特定のアクションだけバリデーションを有効化する
複数指定している例がなくて微妙に探したのでメモ
on
オプションを使用するclass User < ApplicationRecord # on: [:アクション名] validates :email, on: :create # 複数指定する際は配列で指定する validates :email_confirm, on: [:edit, :update] endhttps://railsguides.jp/active_record_validations.html#on
https://stackoverflow.com/questions/7947235/rails-validates-multiple-on-options
- 投稿日:2020-09-15T08:08:16+09:00
Rails 6で認証認可入り掲示板APIを構築する #10 devise_token_auth導入
←Rails 6で認証認可入り掲示板APIを構築する #9 serializer導入
構成
現在は誰でもAPIを叩けばpostできますが、これをログインしたユーザーに紐付ける構成に変えたいと思います。
devise, devise_token_authの導入
deviseはRailsにおける認証周りのデファクトスタンダードです。
これを入れて設定するだけで、ユーザー作成からログイン・ログアウト、パスワード再発行やログイン回数記録、ログイン失敗ブロック等の多岐にわたる機能が使えます。
多機能ゆえにカスタマイズが逆に難しいのが難点ではありますが…そのdeviseのトークン認証版がdevise_token_authです。
deviseの派生のため、devise_token_authにはdeviseが必要です。Gemfile... + # 認証 + gem "devise" + gem "devise_token_auth"
deviseとdevist_token_authの両方をインストールします。
$ rails g devise:install $ rails g devise_token_auth:install User auth参考:[Rails] devise token auth を使う
各種ファイルの変更
実行時にconfig/routes.rbが書き換えられますが、今回はv1のnamespace内に入れたいので直します。
config/routes.rb# frozen_string_literal: true Rails.application.routes.draw do - mount_devise_token_auth_for 'User', at: 'auth' namespace "v1" do resources :posts + mount_devise_token_auth_for 'User', at: 'auth' end endまた、自動生成したファイルがrubocopに引っかかりまくっているので修正します。
ちょっと雑ですが、migrate系やconfig系の除外設定をします。
それ以外は手動で対応していきます。.rubocop.yml... + # メソッドの長さ + Metrics/MethodLength: + Exclude: + - "db/migrate/**/*" + + # AbcSize + Metrics/AbcSize: + Exclude: + - "db/migrate/**/*" + + # 行の長さ + Layout/LineLength: + Exclude: + - "config/initializers/**/*"
model, migrationの変更
今回は必要最低限の機能のみ使うので、不要な初期値を消します。
app/models/user.rbdevise :database_authenticatable, :registerable, - :recoverable, :rememberable, :trackable, :validatable + :rememberable, :validatable include DeviseTokenAuth::Concerns::Userdb/migrate/xxxxxxxxxxxxxx_devise_token_auth_create_users.rb## Database authenticatable t.string :encrypted_password, null: false, default: "" - ## Recoverable - t.string :reset_password_token - t.datetime :reset_password_sent_at - t.boolean :allow_password_change, default: false - ## Rememberable t.datetime :remember_created_at - ## Confirmable - t.string :confirmation_token - t.datetime :confirmed_at - t.datetime :confirmation_sent_at - t.string :unconfirmed_email # Only if using reconfirmable - - ## Lockable - # t.integer :failed_attempts, :default => 0, :null => false # Only if lock strategy is :failed_attempts - # t.string :unlock_token # Only if unlock strategy is :email or :both - # t.datetime :locked_at - ## User Info t.string :name - t.string :nickname - t.string :image t.string :email ... add_index :users, :email, unique: true add_index :users, %i[uid provider], unique: true - add_index :users, :reset_password_token, unique: true - add_index :users, :confirmation_token, unique: true - # add_index :users, :unlock_token, unique: true endここまで変更を終えたらmigrateします。
$ rails db:migrateemail, password以外のカラム許可
curlで試してみます。
$ curl localhost:8080/v1/auth -X POST -H 'Content-Type: application/json' -d '{"email": "test@example.com", "password": "password", "name": "hoge"}' {"status":"success","data":{"uid":"test@example.com","id":1,"email":"test@example.com","provider":"email","name":null,"created_at":"2020-09-08T04:40:44.659Z","updated_at":"2020-09-08T04:40:44.827Z"}}無事に登録できましたね。
しかしよく見ると、nameがhogeで指定したにも関わらずnullになっています。この原因の推測ができると、Rails慣れしてきた証拠です。
他のcontroller同様、ストロングパラメータで登録可能なカラムが限られているからです。
そのため以下対応を行います。app/controllers/application_controller.rbclass ApplicationController < ActionController::API include DeviseTokenAuth::Concerns::SetUserByToken rescue_from ActiveRecord::RecordNotFound, with: :render_404 + before_action :configure_permitted_parameters, if: :devise_controller? def render_404 render status: 404, json: { message: "record not found." } end + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) + end endこれにより、nameカラムを含めた登録ができるようになります。
$ rails db:reset $ curl localhost:8080/v1/auth -X POST -H 'Content-Type: application/json' -d '{"email": "test@example.com", "password": "password", "name": "hoge"}' {"status":"success","data":{"uid":"test@example.com","id":1,"email":"test@example.com","provider":"email","name":"hoge","created_at":"2020-09-08T04:51:42.527Z","updated_at":"2020-09-08T04:51:42.698Z"}}sign_inの確認
登録はできたので、ログインの確認をしていきます。
$ curl localhost:8080/v1/auth/sign_in -X POST -H 'Content-Type: application/json' -d '{"email": "test@example.com", "password": "password"}' -i HTTP/1.1 200 OK X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Download-Options: noopen X-Permitted-Cross-Domain-Policies: none Referrer-Policy: strict-origin-when-cross-origin Content-Type: application/json; charset=utf-8 access-token: T4ZeomARybw3_o5nIHQAfw token-type: Bearer client: Fj772-EYBPnvJdETYhObyQ expiry: 1600751367 uid: test@example.com ETag: W/"8c41022d2e42ca28df0cb958a84ab2f4" Cache-Control: max-age=0, private, must-revalidate X-Request-Id: d4ff732c-f6b5-4213-8984-5d9457b39cbd X-Runtime: 0.510436 Transfer-Encoding: chunked {"data":{"id":1,"email":"test@example.com","provider":"email","uid":"test@example.com","name":"hoge"}}
-i
オプションを付けたことでヘッダ情報も一緒に返ってきます。
そして末尾に先程登録したuserの情報が載っており、ヘッダでも200 OKで返ってきていることから、正常にログインできていることが分かります。注目すべきヘッダは
access-token: T4ZeomARybw3_o5nIHQAfw
この3つです。
client: Fj772-EYBPnvJdETYhObyQ
uid: test@example.com
この3つをリクエスト時のヘッダに含めることで、認証されたアカウントのアクセスであると判別されます。余談ですが、誤った認証情報だとどうなるのか。
試しにemailやpasswordを誤った状態でログインを試行してみましょう。$ curl localhost:8OST -H 'Content-Type: application/json' -d '{"email": "test@example.com", "password": "PASSWORD"}' -i HTTP/1.1 401 Unauthorized X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block X-Content-Type-Options: nosniff X-Download-Options: noopen X-Permitted-Cross-Domain-Policies: none Referrer-Policy: strict-origin-when-cross-origin Content-Type: application/json; charset=utf-8 Cache-Control: no-cache X-Request-Id: 12832212-9797-465b-a5b1-ecaa7e88a977 X-Runtime: 0.308726 Transfer-Encoding: chunked {"success":false,"errors":["Invalid login credentials. Please try again."]}401で返ってきますね。
続き
→
【連載目次へ】
- 投稿日:2020-09-15T07:27:59+09:00
railsの本番環境で開発環境と同じようにエラーを表示させる
はじめに
・自分の知識の備忘録も兼ねています。
・間違いなどありましたらご指摘いただけますと幸いです。デプロイ後、本番環境でのエラー画面
開発環境とは異なるエラー画面なのでどこが引っかかってるかすぐにわからないですね。
エラーログを見る方法もあります。
わかりやすい記事があったので引用させてもらいます。[Rails]本番環境(EC2)でエラーログを見る方法
https://qiita.com/nkekisasa222/items/d4ef64a5e6b539ed3c37開発環境と同じエラー画面を表示させる
config/environments/deveropment.rbの13行目(各自の環境によって異なるかもしれません)
以下のとおりにします。config/environments/deveropment.rb# Show full error reports. config.consider_all_requests_local = true # falseをtrueに書き換えるこれで再度デプロイすると開発環境と同じエラー画面になるはずです。
ただ、ユーザーからもエラーの内容が見えてしまうため、エラー解消したら記述を元にもどしておいたほうが良さそうです。
- 投稿日:2020-09-15T03:54:22+09:00
【超初心者向け(スクショ多数)】Ruby2.6 × Rails6 × CircleCi でECR・ECSに自動デプロイ 【AWSチュートリアル】
対象読者
- ECSに全く触れた事が無い人
- とりあえず手を動かして雰囲気を掴みたい人
- 就活のためにポートフォリオを作成中の人
簡単なRailsアプリ(「Hello World!」と表示するだけ)をAWS ECSにデプロイするまでの手順。がっつりその後の運用まで考慮しているわけではなく、あくまで参考程度にしかならないためその点はご注意ください。
人によって多分色々なやり方がありそうなので、一度流れを掴んだ後は各自お好みで設定していただきたいです。
デプロイ用のアプリを用意する段階から全てハンズオン形式(スクショも多数)で記載しており、書いてある通りに進めれば基本的には上手くいくはず。
要所要所で任意の値(プロフィール名やアプリ名など)を設定する部分があるので、不安な場合は全て筆者と同じように「sample-app」などで統一すると良いかもです。
※初心者向けと銘打っているものの、「まずは実際に手を動かして雰囲気を掴む」という目的に徹しているため、各用語に関する説明はほとんど説明していません。理論派の方はあらかじめ他の記事でECSの概念について学習してからの方が入るのをおすすめします。(筆者は体で覚える派なので...)
仕様
- 言語: Ruby2.6
- フレームワーク: Rails6
- データベース: MySQL5.7
- アプリケーションサーバー: Puma
- Webサーバー: Nginx
下準備編
まず、ECSにデプロイするための簡単なRailsアプリを用意。
サンプル
https://github.com/kazama1209/sample-app
$ git clone https://github.com/kazama1209/sample-app.git $ cd sample-appセットアップ
$ docker-compose build $ docker-compose run web bundle exec rails webpacker:install $ docker-compose up -d $ docker-compose run web bundle exec rails db:createlocalhostにアクセス
http://localhost
にアクセスしていつもの画面が表示されれば環境構築は完了。デプロイ編
アプリの準備ができたので、ECSにデプロイしていく。
各種ツールをインストール
今回、ECSにデプロイするにあたり以下2つのツールを使用する。
$ brew install awscli $ brew install amazon-ecs-cliaws configureを設定
上記のツールを使用するためにaws configureの設定を行う。
IAMユーザーを作成
AWSのコンソールからサービス→IAMを選択し、「ユーザーの追加」をクリック。
任意のユーザー名を入力し、「プログラムによるアクセス」にチェックをつけて次のステップへ。
※画像だと「sample_app」になっていますが、実際は「sample-app」にしてください。スクショを撮り間違えてしまいました。
「既存のポリシーを直接アタッチ」から以下の2つのポリシーをアタッチして次のステップへ。
- AmazonECS_FullAccess
- AmazonEC2ContainerRegistryFullAccess
タグに関しては今回はは無視で次のステップへ。
最後に入力情報の確認画面が表示されるので、特に問題無ければ「ユーザーの作成」をクリック。
ユーザーの作成に成功すると「アクセスキー」「シークレットアクセスキー」の2つが発行されるので、メモを取るなりcsvファイルをダウンロードするなり大事に保管。
ターミナルで「aws configure」を実行
$ aws configure --profile <先ほど作成したIAMユーザー名(今回は「sample-app」)> AWS Access Key ID # 先ほど作成したアクセスキー AWS Secret Access Key # 先ほど作成したシークレットアクセスキー Default region name # ap-northeast-1 Default output format # jsonそれぞれ上記のように入力。
追加でポリシーを作成
先ほどIAMユーザーを作成した際、
- AmazonECS_FullAccess
- AmazonEC2ContainerRegistryFullAccess
2つのポリシーをアタッチしたが、これだけだとこの後に使用する「ecs-cli」というツールの中で権限エラーが発生するため、ここで別途追加しなければならない。
AWSのコンソールからサービス→IAM→ポリシーを選択し、「ポリシーの作成」をクリック。
JSONタブを開いて以下の記述を行う。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "iam:AttachRolePolicy", "iam:AddRoleToInstanceProfile", "iam:CreateInstanceProfile", "iam:CreateRole", "iam:DeleteInstanceProfile", "iam:DeleteRole", "iam:DetachRolePolicy", "iam:PassRole", "iam:RemoveRoleFromInstanceProfile", "ec2:DeleteInternetGateway", "ec2:DeleteSecurityGroup", "ec2:DeleteRouteTable" ], "Resource": "*" } ] }ポリシーの名前や説明を適当に入力し、「ポリシーの作成」をクリック。
ポリシーをユーザーにアタッチ
AWSのコンソールからサービス→IAM→ユーザーを選択し、「アクセス権限の追加」をクリック。
「既存のポリシーをアタッチ」から先ほど作成したポリシーを選択し、アクセス権限を追加。
キーペアを作成
後々EC2内へ入る際などに必要になるのでキーペアを作成しておく。
AWSのコンソールからサービス→EC2→キーペアを選択し、「キーペアの作成」をクリック。
名前とファイル形式を入力し、「キーペアを作成」をクリック。
$ mv Downloads/sample-app.pem .ssh/ $ chmod 600 ~/.ssh/sample-app.pem完了すると「.pem」形式のファイルがダウンロードされるので、「.ssh」ディレクトリに移動させて権限を変更する。
クラスターを作成
コンソールから手動でぽちぽち作成する事も可能だが、vpcやサブネットなども一緒に作る必要があるため、今回はecs-cliでまとめて作成してしまう。
次のコマンドを実行。
$ ecs-cli configure profile --profile-name <任意のプロフィール名> --access-key <先ほど作成したアクセスキー> --secret-key <先ほど作成したシークレットアクセスキー> $ ecs-cli configure --cluster <任意のクラスター名> --default-launch-type EC2 --config-name <任意の設定名> --region ap-northeast-1 $ ecs-cli up --keypair <先ほど作成したキーペア> --capability-iam --size 2 --instance-type t2.samll --cluster-config <任意の設定名> --ecs-profile <任意のプロフィール名>キーなどの各値は各自異なる。
自分の場合は下記のような感じ。$ ecs-cli configure profile --profile-name sample-app --access-key AKI***************** --secret-key dlj************************************* $ ecs-cli configure --cluster sample-app-cluster --default-launch-type EC2 --config-name sample-app-cluster --region ap-northeast-1 $ ecs-cli up --keypair sample-app --capability-iam --size 2 --instance-type t2.small --cluster-config sample-app-cluster --ecs-profile sample-app INFO[0006] Using recommended Amazon Linux 2 AMI with ECS Agent 1.44.3 and Docker version 19.03.6-ce INFO[0007] Created cluster cluster=sample-app-cluster region=ap-northeast-1 INFO[0009] Waiting for your cluster resources to be created... INFO[0009] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS INFO[0070] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS INFO[0131] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS VPC created: vpc-***************** Security Group created: sg-***************** Subnet created: subnet-***************** Subnet created: subnet-***************** Cluster creation succeeded.上手くいくと↑のようにクラスター用のVPC、セキュリティグループ、サブネットなどが自動で作成される。
AWSのコンソールからサービス→Elastic Container Service→クラスターを選択し、無事作成されていれば成功。
RDSを作成
データベースとして使うRDSを作成。
AWSのコンソールからサービス→RDSを選択し、「データベースの作成」をクリック。
- 作成方法: 標準作成
- エンジンのタイプ: MySQL
- DBインスタンスサイズ: 無料利用枠
- DBインスタンス識別子: sample-app-db
- マスターユーザー名: root
- パスワード: password
※この辺は全て任意。
vpc: 先ほど作成したvpc
サブネットグループ: 新しいDBサブネットグループの作成
パブリックアクセス: あり最初のデータベース名: 任意
※特に触れていない部分は空欄もしくはデフォルトのままでOK。
問題なければ「データベースの作成」をクリック。
↑こんな感じで作成されていれば成功。
また、セキュリティグループの設定も必要なので「VPCセキュリティグループ」の下に記載されているリンクをクリック。
「インバウンドルールの編集」から次のように設定。
- タイプ: MYSQL/Aurora
- プロトコル: TCP
- ポート範囲
- ソース: 0.0.0.0/0
$ mysql -h <RDSのエンドポイント> -u <RDSのユーザー名> -p試しにターミナルで↑のコマンドを叩き、接続できれば成功。
ロードバランサーを作成
AWSのコンソールからサービス→EC2→ロードバランサーを選択し、「ロードバランサーの作成」をクリック。
3種類あるが、「Application Load Balancer」を選択。
- 名前: 任意
- リスナー: そのままでOK
- VPC: 先ほど自動作成されたものを選択
- subnet: 同上
先に進むとセキリュティグループの設定画面になるので、「新しいセキリュティグループを作成する」から適当にセキュリティグループを作成。
ターゲットグループの設定。
- ターゲットグループ: 新しいターゲットグループ
- 名前: 任意
クラスター作成時に自動で作られたEC2を登録し、確認画面から問題なければ「作成」をクリックして完了。
ECRにdockerイメージをpush
AWSのコンソールからサービス→Amazon Elastic Container Registryを選択し、「リポジトリの作成」をクリック。
それぞれ適当なリポジトリ名を入力し、「リポジトリを作成」をクリック。
プッシュコマンドを表示し、書いてある通り上から順に4つ実行していく。
※2番目のコマンドでbuildを行う際は-fでDockerfileのコンテキストを変える。
# Rails(本番用のDockerfileを使用する) $ docker build -f ./prod.Dockerfile . -t sample-app-rails # Nginx $ cd containers/nginx $ docker build -f ./Dockerfile . -t sample-app-nginx本番用のDockerfile
$ touch prod.Dockerfile# prod.Dockerfile FROM ruby:2.6.6 ENV LANG C.UTF-8 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ apt-get install nodejs RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /sample-app WORKDIR /sample-app ADD Gemfile /sample-app/Gemfile ADD Gemfile.lock /sample-app/Gemfile.lock RUN gem install bundler:2.1.4 RUN bundle install ADD . /sample-app # Nginxと通信を行うための準備 RUN mkdir -p tmp/sockets VOLUME /sample-app/public VOLUME /sample-app/tmp RUN yarn install --check-files RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile基本的に開発用のものと同じだが、最後の数行でNginxと通信を行うための準備などを行っている。
全て打ち終わったらリポジトリを確認し、イメージが追加されていれば成功。
タスクの作成
先ほどpushしたイメージをもとに、タスクの作成を行う。
$ mkdir ecs $ touch ecs/docker-compose.yml# ecs/docker-compose.yml version: 2 services: app: image: # ECRのリポジトリURI(Rails) command: bash -c "bundle exec rails db:migrate && bundle exec rails assets:precompile && bundle exec puma -C config/puma.rb" environment: # 実際はdotenvなどで管理した方が良いかも RAILS_ENV: production RAILS_MASTER_KEY: # config/master.keyの値 DATABASE_NAME: sample_app_production DATABASE_USERNAME: root DATABASE_PASSWORD: password DATABASE_HOST: # RDSのエンドポイント TZ: Japan working_dir: /sample-app logging: driver: awslogs options: awslogs-region: ap-northeast-1 awslogs-group: sample-app-production/app awslogs-stream-prefix: sample-app-production nginx: image: # ECRのリポジトリURI(Nginx) ports: - 80:80 links: - app volumes_from: - app working_dir: /sample-app logging: driver: awslogs options: awslogs-region: ap-northeast-1 awslogs-group: sample-app-production/nginx awslogs-stream-prefix: sample-app-production次のコマンドを実行。
$ ecs-cli compose --project-name sample-app-task -f ./ecs/docker-compose.yml up --create-log-groups --cluster-config sample-app-cluster --ecs-profile sample-app上手く行った場合、実行中のタスクに「1」と表示される。
最後に、ロードバランサーのDNS名をURLに貼り付けてアクセス。
「Yay!You're on Rails!」と表示されれば成功。
※デプロイに際して何か不具合があった場合はCloudWatchのログを確認して修正。
サービスの作成
クラスターとタスクだけでもアプリは動くが、その中間に「サービス」と呼ばれるものを作成すると、コンテナが止まった際に再起動をかけてくれたりロードバランサーを通じてオートスケーリングしてくれたり何かと便利ぽいので作成しておく。
AWSのコンソールからサービス→Amazon Elastic Container Service→ クラスター名をクリックし、「サービス」タブを開いて作成ページに進む。
- 起動タイプ: EC2
- タスク定義: 先ほど作成したもの
- クラスター: 同上
- サービス名: 任意
- その他: 画像の通り
- ロードバランサーの種類: Application Load Balancer
- ロードバランサー名: 先ほど作成したもの
- その他: 画像の通り
- ターゲットグループ名: 先ほど作成したもの
- その他: 画像の通り
最後に確認画面が表示されるので、問題無ければ作成をクリック。
無事作成されれば完了。
このままだと2つのタスク(片方はecs-cliでターミナルから開始したもの、もう片方はサービスの作成により開始されたもの)が実行中になってしまっているため、前者は停止してしまってOK。
おまけCircleCiと連携して自動デプロイ)
このままだと変更点があるたびに手動で「ビルド→プッシュ→タスク再定義」といった面倒な作業が必要になるため、「CirlcleCiにプッシュ→ビルド&テスト→ECR・ECSへ自動デプロイ」といった良くある仕組みを構築していく。
Rspecを導入
まず、デプロイ前にテストを行うためにRspecを導入する。
gemをインストール
# Gemfile group :development, :test do gem 'rspec-rails' end# Gemfileを更新したので再度ビルド $ docker-compose build各種ファイルを作成&編集
$ docker-compose run web bundle exec rails generate rspec:install create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb# .rspec --format documentation↑の1行を追記しておくと、Rspecを実行した際の出力表示が見やすくなる。
# spec/rails_helper.rb Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }必須ではないが、後ほどテスト用のヘルパーメソッドを作成する事になった場合、ファイルの置き場として「spec/support」を使用するので一応設定しておく。
デフォルトではコメントアウトされているので、それを外せばOK。
# config/application.rb config.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, controller_specs: false, routing_specs: false endこのままだと
rails g
コマンドを打ち込んだ際に自動で諸々のテストファイルが作成されてしまうので、余計なものを作成したくない場合は「config/application.rb」で設定を行う。※この辺はお好みで。
rspecを実行
$ docker-compose run web bundle exec rspec No examples found. Finished in 0.00276 seconds (files took 0.12693 seconds to load) 0 examples, 0 failuresまだ何もテストを書いていないので、当然こうなる。
とりあえずRequest Specを書いてみる
手始めに、リクエストに対して正常なレスポンスが返ってくるかどうかを確認するためのRequest Specを書いてみる。
トップページを作成
# コントローラー $ touch app/controllers/home_controller.rb # ビュー $ mkdir app/views/home $ touch app/views/home/index.html.erb# app/controllers/home_controller.rb class HomeController < ApplicationController def index end end# app/views/home/index.html.erb <h1>Hello World!</h1>#config/routes.rb Rails.application.routes.draw do root 'home#index' endテストファイルを作成
$ docker-compose run web bundle exec rails g rspec:request home create spec/requests/homes_spec.rb# spec/requests/home_spec.rb require 'rails_helper' RSpec.describe "Home", type: :request do describe "GET /" do it "works successfully" do get root_path expect(response).to have_http_status(200) end end end「/」にアクセスした際、 200番のステータスコードが返ってくるかどうかのテスト。
$ docker-compose run web bundle exec rspec Home GET / works successfully Finished in 0.53664 seconds (files took 8.4 seconds to load) 1 example, 0 failures再度rspecを実行し、問題無くパスしていれば成功。
CircleCIと連携
次に、実際にCircleCiと連携するための設定を行う。
gemをインストール
group :development, :test do gem 'database_cleaner' gem 'rspec_junit_formatter' gem 'webdrivers', '~> 3.0' end# Gemfileを更新したので再度ビルド $ docker-compose build各種ファイルを作成&編集
$ mkdir .circleci $ touch .circleci/config.yml $ touch config/database.yml.ci $ docker-compose run web bundle exec rails db:schema:dump# .circleci/config.yml version: 2 jobs: build: docker: - image: circleci/ruby:2.6.6-node-browsers environment: - BUNDLER_VERSION: 2.1.4 - RAILS_ENV: 'test' - image: circleci/mysql:5.7 environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 'true' - MYSQL_ROOT_HOST: '127.0.0.1' working_directory: ~/sample_app steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} - v1-dependencies- - run: name: install dependencies command: | gem install bundler -v 2.1.4 bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} # database setup - run: mv ./config/database.yml.ci ./config/database.yml # database setup - run: name: setup database command: | bundle exec rake db:create bundle exec rake db:schema:load # install yarn - run: name: install yarn command: yarn install # install webpack - run: name: install webpack command: bundle exec bin/webpack # run tests - run: name: run rspec command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES # collect reports - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results# config/database.yml.ci (CircleCiのデータベース設定用) test: adapter: mysql2 encoding: utf8 pool: 5 username: 'root' port: 3306 host: '127.0.0.1' database: sample_app_test# spec/rails_helper.rb RSpec.configure do |config| # config DataBaseCleaner config.before(:suite) do DatabaseCleaner.strategy = :transaction DatabaseCleaner.clean_with(:truncation) Rails.application.load_seed end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end# db/schema.rb # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # This file is the source Rails uses to define your schema when running `rails # db:schema:load`. When creating a new database, `rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 0) do endCircleCiとGitHubを接続
https://app.circleci.com/projects/project-dashboard/github/GitHubのアカウント名/
↑CircleCiのダッシュボードから連携したいリポジトリを探し、「Set Up Project」をクリック。画面に表示される指示に従い設定。
これで今後GitHubへ新しいプッシュを行った際、「.circleci/config.yml」に書いた内容に基づき自動でビルド&テストが走るようになる。
特に問題が無ければ「SUCCESS」と表示されるはず。
これで初期設定は完了。自動デプロイ(ECR・ECS)
CircleCiのバージョン2.1から追加されたOrbを使い、masterブランチに変更が加えられた際、CircleCiでのビルド&テストを行い自動でイメージを作成しECRへプッシュし、ECSのサービスを更新してタスクの再定義を行うようにする。
環境変数の登録
あらかじめCircleCiの設定画面からデプロイに必要な環境変数を登録しておく。
- AWS_ACCESS_KEY_ID
- 作成したIAMユーザーのアクセスキー
- AWS_SECRET_ACCESS_KEY
- 作成したIAMユーザーのシークレットキー
- AWS_ACCOUNT_ID
- AWSのアカウントID(コンソールの「マイアカウント」から確認可能)
- AWS_REGION
- ap-northeast-1
- AWS_ECR_ACCOUNT_URL
- ECRのリポジトリURI(例: <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com)
- DATABASE_HOST
- RDSのエンドポイント(例: ********.ap-northeast-1.rds.amazonaws.com)
- DATABASE_USERNAME
- RDSのユーザー名(例: root)
- DATABASE_PASSWORD
- RDSのパスワード(例: password)
- DATABASE_NAME
- 使用するデータベース名(例: sample_app_production)
- RAILS_MASTER_KEY
- config/master.keyの値
- TZ
- JAPAN
- MY_APP_PREFIX
- 任意(例: sample-app)
- 変数を使い回してなるべくコンパクトに書けるようにクラスター名やタスク名は共通のワードを含めて作成しておいた方が良い(sample-app-cluster、sample-app-taskなど)。
.circleci/config.ymlを編集
# .circleci/config.yml version: 2.1 orbs: aws-ecr: circleci/aws-ecr@6.7.0 aws-ecs: circleci/aws-ecs@1.1.0 jobs: test: docker: - image: circleci/ruby:2.6.6-node-browsers environment: - BUNDLER_VERSION: 2.1.4 - RAILS_ENV: 'test' - image: circleci/mysql:5.7 environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 'true' - MYSQL_ROOT_HOST: '127.0.0.1' working_directory: ~/project steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} - v1-dependencies- - run: name: install dependencies command: | gem install bundler -v 2.1.4 bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} - run: mv ./config/database.yml.ci ./config/database.yml - run: name: setup database command: | bundle exec rake db:create bundle exec rake db:schema:load - run: name: install yarn command: yarn install - run: name: install webpack command: bundle exec bin/webpack - run: name: run rspec command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results workflows: version: 2 test_and_deploy: jobs: - test # ビルドした後にイメージをECRへプッシュ - aws-ecr/build-and-push-image: requires: - test account-url: AWS_ECR_ACCOUNT_URL region: AWS_REGION aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY create-repo: true dockerfile: ./prod.Dockerfile repo: "${MY_APP_PREFIX}-rails" tag: "${CIRCLE_SHA1}" filters: branches: only: - master # ECSのサービスを更新してタスクを再定義 - aws-ecs/deploy-service-update: requires: - aws-ecr/build-and-push-image family: "${MY_APP_PREFIX}-task" cluster-name: "${MY_APP_PREFIX}-cluster" service-name: "${MY_APP_PREFIX}-service" container-image-name-updates: "container=app,tag=${CIRCLE_SHA1}"各環境変数に間違いが無いか良く確認しておく事。
# app/views/home/index.html.erb <h1>Hello World!</h1> <p>Completed auto deploy with CircleCi</p>自動デプロイが上手くいったかわかりやすいようにトップページを少し変えておく。
masterブランチに変更を加える
実際にmasterブランチにプッシュして変更を加えてみると、CircleCi上でデプロイ込みのjobsが動き始める。
※全てのフローが完了するまでに大体10〜15分くらいかかるので注意。
再度ロードバランサーのDNS名にアクセスし、先ほどの変更がちゃんと更新されていれば成功。
※反映されるまで多少時間がかかるので気長に待つ。
古いタスクと新しいタスクの2つが実行されているが、時間の経過で古い方は勝手に消されるため(サービスのおかげ?)そのまま放置でOK。
お疲れ様でした。
あとがき
自分もまだまだ勉強中の身なので、何かあれば随時更新予定です。
現状、コンソール上で手を動かしながら行う作業とターミナルでコマンドを叩いて行う作業がごちゃ混ぜになってしまっているため、できれば全て後者に統一したいと考えていいます。
Terraformとかも使って一発バシっとできるようにしたい...。
どこか詰まった部分やもっとこうした方が良いなどあればコメントいただけると嬉しいです。
個人的に詰まった部分
- Nginxの設定
- nginx.confファイルの中身は各自変更しないと正常に動かない部分があるので、ググりながら適宜修正する必要がある。
appコンテナとnginxコンテナの接続が上手くいかないと
2020/09/13 20:02:57 [crit] 7#7: *456 connect() to unix:///sample-app/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: *********, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///sample-app/tmp/sockets/puma.sock:/500.html", host: "***********"↑こんな感じのエラーで延々と悩まされる。
- 投稿日:2020-09-15T03:54:22+09:00
【超初心者向け(スクショ多め)】Ruby2.6 × Rails6 × CircleCi でECR・ECSに自動デプロイ 【AWSチュートリアル】
対象読者
- ECSに全く触れた事が無い人
- とりあえず手を動かして雰囲気を掴みたい人
- 就活のためにポートフォリオを作成中の人
簡単なRailsアプリ(「Hello World!」と表示するだけ)をAWS ECSにデプロイするまでの手順。がっつりその後の運用まで考慮しているわけではなく、あくまで参考程度にしかならないためその点はご注意ください。
人によって多分色々なやり方がありそうなので、一度流れを掴んだ後は各自お好みで設定していただきたいです。
デプロイ用のアプリを用意する段階から全てハンズオン形式(スクショも多数)で記載しており、書いてある通りに進めれば基本的には上手くいくはず。
要所要所で任意の値(プロフィール名やアプリ名など)を設定する部分があるので、不安な場合は全て筆者と同じように「sample-app」などで統一すると良いかもです。
※初心者向けと銘打っているものの、「まずは実際に手を動かして雰囲気を掴む」という目的に徹しているため、各用語に関する説明はほとんど説明していません。
※あくまで「AWS超初心者でもとりあえず書いてある通りに従えばそれっぽくデプロイできる」というのがコンセプト。理論派の方はあらかじめ他の記事でECR・ECSの概念について学習してから入るのをおすすめします。(筆者は体で覚える派なので...)
※AWSの各リソース名などに関しては基本的に任意なので各自お好みで(ただし、ecs-cliでコマンドを打つ際やCircleCiのconfig内の記述をそれに合わせる必要あり)。また、特に触れていない部分に関してはとりあえずデフォルトの状態もしくは空欄で大丈夫だと思います。
※スクショ撮ったタイミングが違ったりしてリビジョンナンバーなどにバラつきがあるかもしれませんが、無視してください(汗)
仕様
- 言語: Ruby2.6
- フレームワーク: Rails6
- データベース: MySQL5.7
- アプリケーションサーバー: Puma
- Webサーバー: Nginx
下準備編
まず、ECSにデプロイするための簡単なRailsアプリを用意。
サンプル
https://github.com/kazama1209/sample-app
$ git clone https://github.com/kazama1209/sample-app.git $ cd sample-appセットアップ
$ docker-compose build $ docker-compose run web bundle exec rails webpacker:install $ docker-compose up -d $ docker-compose run web bundle exec rails db:createlocalhostにアクセス
http://localhost
にアクセスしていつもの画面が表示されれば環境構築は完了。デプロイ編
アプリの準備ができたので、ECSにデプロイしていく。
各種ツールをインストール
今回、ECSにデプロイするにあたり以下2つのツールを使用する。
$ brew install awscli $ brew install amazon-ecs-cliaws configureを設定
上記のツールを使用するためにaws configureの設定を行う。
IAMユーザーを作成
AWSのコンソールからサービス→IAMを選択し、「ユーザーの追加」をクリック。
任意のユーザー名を入力し、「プログラムによるアクセス」にチェックをつけて次のステップへ。(画像では「sample_app」となっていますが、実際は「sample-app」で作成したと仮定して話を進めます。スクショ撮り間違えました。)
「既存のポリシーを直接アタッチ」から以下の2つのポリシーをアタッチして次のステップへ。
- AmazonECS_FullAccess
- AmazonEC2ContainerRegistryFullAccess
タグに関しては今回はは無視で次のステップへ。
最後に入力情報の確認画面が表示されるので、特に問題無ければ「ユーザーの作成」をクリック。
ユーザーの作成に成功すると「アクセスキー」「シークレットアクセスキー」の2つが発行されるので、メモを取るなりcsvファイルをダウンロードするなり大事に保管。
ターミナルで「aws configure」を実行
$ aws configure --profile <先ほど作成したIAMユーザー名(今回は「sample-app」)> AWS Access Key ID # 先ほど作成したアクセスキー AWS Secret Access Key # 先ほど作成したシークレットアクセスキー Default region name # ap-northeast-1 Default output format # jsonそれぞれ上記のように入力。
追加でポリシーを作成
先ほどIAMユーザーを作成した際、
- AmazonECS_FullAccess
- AmazonEC2ContainerRegistryFullAccess
2つのポリシーをアタッチしたが、これだけだとこの後に使用する「ecs-cli」というツールの中で権限エラーが発生するため、ここで別途追加しなければならない。
AWSのコンソールからサービス→IAM→ポリシーを選択し、「ポリシーの作成」をクリック。
JSONタブを開いて以下の記述を行う。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "iam:AttachRolePolicy", "iam:AddRoleToInstanceProfile", "iam:CreateInstanceProfile", "iam:CreateRole", "iam:DeleteInstanceProfile", "iam:DeleteRole", "iam:DetachRolePolicy", "iam:PassRole", "iam:RemoveRoleFromInstanceProfile", "ec2:DeleteInternetGateway", "ec2:DeleteSecurityGroup", "ec2:DeleteRouteTable" ], "Resource": "*" } ] }ポリシーの名前や説明を適当に入力し、「ポリシーの作成」をクリック。
ポリシーをユーザーにアタッチ
AWSのコンソールからサービス→IAM→ユーザーを選択し、「アクセス権限の追加」をクリック。
「既存のポリシーをアタッチ」から先ほど作成したポリシーを選択し、アクセス権限を追加。
キーペアを作成
後々EC2内へ入る際などに必要になるのでキーペアを作成しておく。
AWSのコンソールからサービス→EC2→キーペアを選択し、「キーペアの作成」をクリック。
名前とファイル形式を入力し、「キーペアを作成」をクリック。
$ mv Downloads/sample-app.pem .ssh/ $ chmod 600 ~/.ssh/sample-app.pem完了すると「.pem」形式のファイルがダウンロードされるので、「.ssh」ディレクトリに移動させて権限を変更する。
クラスターを作成
コンソールから手動でぽちぽち作成する事も可能だが、vpcやサブネットなども一緒に作る必要があるため、今回はecs-cliでまとめて作成してしまう。
次のコマンドを実行。
$ ecs-cli configure profile --profile-name <任意のプロフィール名> --access-key <先ほど作成したアクセスキー> --secret-key <先ほど作成したシークレットアクセスキー> $ ecs-cli configure --cluster <任意のクラスター名> --default-launch-type EC2 --config-name <任意の設定名> --region ap-northeast-1 $ ecs-cli up --keypair <先ほど作成したキーペア> --capability-iam --size 2 --instance-type t2.samll --cluster-config <任意の設定名> --ecs-profile <任意のプロフィール名>キーなどの各値は各自異なる。
筆者の場合は下記のような感じ。$ ecs-cli configure profile --profile-name sample-app --access-key AKI***************** --secret-key dlj************************************* $ ecs-cli configure --cluster sample-app-cluster --default-launch-type EC2 --config-name sample-app-cluster --region ap-northeast-1 $ ecs-cli up --keypair sample-app --capability-iam --size 2 --instance-type t2.small --cluster-config sample-app-cluster --ecs-profile sample-app INFO[0006] Using recommended Amazon Linux 2 AMI with ECS Agent 1.44.3 and Docker version 19.03.6-ce INFO[0007] Created cluster cluster=sample-app-cluster region=ap-northeast-1 INFO[0009] Waiting for your cluster resources to be created... INFO[0009] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS INFO[0070] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS INFO[0131] Cloudformation stack status stackStatus=CREATE_IN_PROGRESS VPC created: vpc-***************** Security Group created: sg-***************** Subnet created: subnet-***************** Subnet created: subnet-***************** Cluster creation succeeded.上手くいくと↑のようにクラスター用のVPC、セキュリティグループ、サブネットなどが自動で作成される。
AWSのコンソールからサービス→Elastic Container Service→クラスターを選択し、無事作成されていれば成功。
RDSを作成
データベースとして使うRDSを作成。
AWSのコンソールからサービス→RDSを選択し、「データベースの作成」をクリック。
- 作成方法: 標準作成
- エンジンのタイプ: MySQL
- DBインスタンスサイズ: 無料利用枠
- DBインスタンス識別子: sample-app-db
- マスターユーザー名: root
- パスワード: password
※この辺は全て任意。
- vpc: 先ほど作成したvpc
- サブネットグループ: 新しいDBサブネットグループの作成
- パブリックアクセス: あり
最初のデータベース名: sample_app_production
※記事冒頭で用意したサンプル(sample-app)を使用する場合、※データベース名はsample-appの「config/database.yml」内で定義しているため「sample_app_production」で固定
※特に触れていない部分は空欄もしくはデフォルトのままでOK。
問題なければ「データベースの作成」をクリック。
↑こんな感じで作成されていれば成功。
また、セキュリティグループの設定も必要なので「VPCセキュリティグループ」の下に記載されているリンクをクリック。
「インバウンドルールの編集」から次のように設定。
- タイプ: MYSQL/Aurora
- プロトコル: TCP
- ポート範囲
- ソース: 0.0.0.0/0
$ mysql -h <RDSのエンドポイント> -u <RDSのユーザー名> -p試しにターミナルで↑のコマンドを叩き、接続できれば成功。
ロードバランサーを作成
AWSのコンソールからサービス→EC2→ロードバランサーを選択し、「ロードバランサーの作成」をクリック。
3種類あるが、「Application Load Balancer」を選択。
- 名前: sample-app-alb ※任意
- リスナー: そのままでOK
- VPC: 先ほど自動作成されたものを選択
- subnet: 同上
先に進むとセキリュティグループの設定画面になるので、「新しいセキリュティグループを作成する」から適当にセキュリティグループを作成。
ターゲットグループの設定。
- ターゲットグループ: 新しいターゲットグループ
- 名前: sample-app-alb-tg ※任意
クラスター作成時に自動で作られたEC2を登録し、確認画面から問題なければ「作成」をクリックして完了。
ECRにdockerイメージをpush
AWSのコンソールからサービス→Amazon Elastic Container Registryを選択し、「リポジトリの作成」をクリック。
それぞれ適当なリポジトリ名を入力し、「リポジトリを作成」をクリック。
プッシュコマンドを表示し、書いてある通り上から順に4つ実行していく。
※2番目のコマンドでbuildを行う際は-fでDockerfileのコンテキストを変える。
# Rails(本番用のDockerfileを使用する) $ docker build -f ./prod.Dockerfile . -t sample-app-rails # Nginx $ cd containers/nginx $ docker build -f ./Dockerfile . -t sample-app-nginx本番用のDockerfile
$ touch prod.Dockerfile# prod.Dockerfile FROM ruby:2.6.6 ENV LANG C.UTF-8 RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - && \ apt-get install nodejs RUN apt-get update && apt-get install -y curl apt-transport-https wget && \ curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \ echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \ apt-get update && apt-get install -y yarn RUN mkdir /sample-app WORKDIR /sample-app ADD Gemfile /sample-app/Gemfile ADD Gemfile.lock /sample-app/Gemfile.lock RUN gem install bundler:2.1.4 RUN bundle install ADD . /sample-app # Nginxと通信を行うための準備 RUN mkdir -p tmp/sockets VOLUME /sample-app/public VOLUME /sample-app/tmp RUN yarn install --check-files RUN SECRET_KEY_BASE=placeholder bundle exec rails assets:precompile基本的に開発用のものと同じだが、最後の数行でNginxと通信を行うための準備などを行っている。
全て打ち終わったらリポジトリを確認し、イメージが追加されていれば成功。
タスクの作成
先ほどpushしたイメージをもとに、タスクの作成を行う。
$ mkdir ecs $ touch ecs/docker-compose.yml# ecs/docker-compose.yml version: 2 services: app: image: # ECRのリポジトリURI(Rails) command: bash -c "bundle exec rails db:migrate && bundle exec rails assets:precompile && bundle exec puma -C config/puma.rb" environment: # 実際はdotenvなどで管理した方が良いかも RAILS_ENV: production RAILS_MASTER_KEY: # config/master.keyの値 DATABASE_NAME: sample_app_production DATABASE_USERNAME: root DATABASE_PASSWORD: password DATABASE_HOST: # RDSのエンドポイント TZ: Japan working_dir: /sample-app logging: driver: awslogs options: awslogs-region: ap-northeast-1 awslogs-group: sample-app-production/app awslogs-stream-prefix: sample-app-production nginx: image: # ECRのリポジトリURI(Nginx) ports: - 80:80 links: - app volumes_from: - app working_dir: /sample-app logging: driver: awslogs options: awslogs-region: ap-northeast-1 awslogs-group: sample-app-production/nginx awslogs-stream-prefix: sample-app-production次のコマンドを実行。
$ ecs-cli compose --project-name sample-app-task -f ./ecs/docker-compose.yml up --create-log-groups --cluster-config sample-app-cluster --ecs-profile sample-app上手く行った場合、実行中のタスクに「1」と表示される。
最後に、ロードバランサーのDNS名をURLに貼り付けてアクセス。
「Yay!You're on Rails!」と表示されれば成功。
※デプロイに際して何か不具合があった場合はCloudWatchのログを確認して修正。
サービスの作成
クラスターとタスクだけでもアプリは動くが、その中間に「サービス」と呼ばれるものを作成すると、コンテナが止まった際に再起動をかけてくれたりロードバランサーを通じてオートスケーリングしてくれたり何かと便利ぽいので作成しておく。
AWSのコンソールからサービス→Amazon Elastic Container Service→ クラスター名をクリックし、「サービス」タブを開いて作成ページに進む。
- 起動タイプ: EC2
- タスク定義: sample-app-task ※先ほど作成したもの
- クラスター: sample-app-cluster ※同上
- サービス名: sample-app-service ※任意
- その他: 画像の通り
- ロードバランサーの種類: Application Load Balancer
- ロードバランサー名: 先ほど作成したもの
- その他: 画像の通り
- ターゲットグループ名: 先ほど作成したもの
- その他: 画像の通り
最後に確認画面が表示されるので、問題無ければ作成をクリック。
無事作成されれば完了。
このままだと2つのタスク(片方はecs-cliでターミナルから開始したもの、もう片方はサービスの作成により開始されたもの)が実行中になってしまっているため、前者は停止してしまってOK。
おまけ(CircleCiと連携して自動デプロイ)
このままだと変更点があるたびに手動で「ビルド→プッシュ→タスク再定義」といった面倒な作業が必要になるため、「CirlcleCiにプッシュ→ビルド&テスト→ECR・ECSへ自動デプロイ」といった良くある仕組みを構築していく。
Rspecを導入
まず、デプロイ前にテストを行うためにRspecを導入する。
gemをインストール
# Gemfile group :development, :test do gem 'rspec-rails' end# Gemfileを更新したので再度ビルド $ docker-compose build各種ファイルを作成&編集
$ docker-compose run web bundle exec rails generate rspec:install create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb# .rspec --format documentation↑の1行を追記しておくと、Rspecを実行した際の出力表示が見やすくなる。
# spec/rails_helper.rb Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }必須ではないが、後ほどテスト用のヘルパーメソッドを作成する事になった場合、ファイルの置き場として「spec/support」を使用するので一応設定しておく。
デフォルトではコメントアウトされているので、それを外せばOK。
# config/application.rb config.generators do |g| g.test_framework :rspec, view_specs: false, helper_specs: false, controller_specs: false, routing_specs: false endこのままだと
rails g
コマンドを打ち込んだ際に自動で諸々のテストファイルが作成されてしまうので、余計なものを作成したくない場合は「config/application.rb」で設定を行う。※この辺はお好みで。
rspecを実行
$ docker-compose run web bundle exec rspec No examples found. Finished in 0.00276 seconds (files took 0.12693 seconds to load) 0 examples, 0 failuresまだ何もテストを書いていないので、当然こうなる。
とりあえずRequest Specを書いてみる
手始めに、リクエストに対して正常なレスポンスが返ってくるかどうかを確認するためのRequest Specを書いてみる。
トップページを作成
# コントローラー $ touch app/controllers/home_controller.rb # ビュー $ mkdir app/views/home $ touch app/views/home/index.html.erb# app/controllers/home_controller.rb class HomeController < ApplicationController def index end end# app/views/home/index.html.erb <h1>Hello World!</h1>#config/routes.rb Rails.application.routes.draw do root 'home#index' endテストファイルを作成
$ docker-compose run web bundle exec rails g rspec:request home create spec/requests/homes_spec.rb# spec/requests/home_spec.rb require 'rails_helper' RSpec.describe "Home", type: :request do describe "GET /" do it "works successfully" do get root_path expect(response).to have_http_status(200) end end end「/」にアクセスした際、 200番のステータスコードが返ってくるかどうかのテスト。
$ docker-compose run web bundle exec rspec Home GET / works successfully Finished in 0.53664 seconds (files took 8.4 seconds to load) 1 example, 0 failures再度rspecを実行し、問題無くパスしていれば成功。
CircleCIと連携
次に、実際にCircleCiと連携するための設定を行う。
gemをインストール
group :development, :test do gem 'database_cleaner' gem 'rspec_junit_formatter' gem 'webdrivers', '~> 3.0' end# Gemfileを更新したので再度ビルド $ docker-compose build各種ファイルを作成&編集
$ mkdir .circleci $ touch .circleci/config.yml $ touch config/database.yml.ci $ docker-compose run web bundle exec rails db:schema:dump# .circleci/config.yml version: 2 jobs: build: docker: - image: circleci/ruby:2.6.6-node-browsers environment: - BUNDLER_VERSION: 2.1.4 - RAILS_ENV: 'test' - image: circleci/mysql:5.7 environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 'true' - MYSQL_ROOT_HOST: '127.0.0.1' working_directory: ~/sample_app steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} - v1-dependencies- - run: name: install dependencies command: | gem install bundler -v 2.1.4 bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} # database setup - run: mv ./config/database.yml.ci ./config/database.yml # database setup - run: name: setup database command: | bundle exec rake db:create bundle exec rake db:schema:load # install yarn - run: name: install yarn command: yarn install # install webpack - run: name: install webpack command: bundle exec bin/webpack # run tests - run: name: run rspec command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES # collect reports - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results# config/database.yml.ci (CircleCiのデータベース設定用) test: adapter: mysql2 encoding: utf8 pool: 5 username: 'root' port: 3306 host: '127.0.0.1' database: sample_app_test# spec/rails_helper.rb RSpec.configure do |config| # config DataBaseCleaner config.before(:suite) do DatabaseCleaner.strategy = :transaction DatabaseCleaner.clean_with(:truncation) Rails.application.load_seed end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end# db/schema.rb # This file is auto-generated from the current state of the database. Instead # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # # This file is the source Rails uses to define your schema when running `rails # db:schema:load`. When creating a new database, `rails db:schema:load` tends to # be faster and is potentially less error prone than running all of your # migrations from scratch. Old migrations may fail to apply correctly if those # migrations use external dependencies or application code. # # It's strongly recommended that you check this file into your version control system. ActiveRecord::Schema.define(version: 0) do endCircleCiとGitHubを接続
https://app.circleci.com/projects/project-dashboard/github/GitHubのアカウント名/
↑CircleCiのダッシュボードから連携したいリポジトリを探し、「Set Up Project」をクリック。画面に表示される指示に従い設定。
これで今後GitHubへ新しいプッシュを行った際、「.circleci/config.yml」に書いた内容に基づき自動でビルド&テストが走るようになる。
特に問題が無ければ「SUCCESS」と表示されるはず。
これで初期設定は完了。自動デプロイ(ECR・ECS)
CircleCiのバージョン2.1から追加されたOrbを使い、masterブランチに変更が加えられた際、CircleCiでのビルド&テストを行い自動でイメージを作成しECRへプッシュし、ECSのサービスを更新してタスクの再定義を行うようにする。
環境変数の登録
あらかじめCircleCiの設定画面からデプロイに必要な環境変数を登録しておく。
- AWS_ACCESS_KEY_ID
- 先ほど作成したIAMユーザーのアクセスキー
- AWS_SECRET_ACCESS_KEY
- 先ほど作成したIAMユーザーのシークレットキー
- AWS_ACCOUNT_ID
- AWSのアカウントID(コンソールの「マイアカウント」から確認可能)
- AWS_REGION
- ap-northeast-1
- AWS_ECR_ACCOUNT_URL
- ECRのリポジトリURI(例: <アカウントID>.dkr.ecr.ap-northeast-1.amazonaws.com)
- DATABASE_HOST
- RDSのエンドポイント(例: ********.ap-northeast-1.rds.amazonaws.com)
- DATABASE_USERNAME
- RDSのユーザー名(例: root)
- DATABASE_PASSWORD
- RDSのパスワード(例: password)
- DATABASE_NAME
- 使用するデータベース名(例: sample_app_production)
- RAILS_MASTER_KEY
- config/master.keyの値
- TZ
- JAPAN
- MY_APP_PREFIX
- 任意(例: sample-app)
- 変数を使い回してなるべくコンパクトに書けるようにクラスター名やタスク名は共通のワードを含めて作成しておいた方が良い(sample-app-cluster、sample-app-taskなど)。
.circleci/config.ymlを編集
# .circleci/config.yml version: 2.1 orbs: aws-ecr: circleci/aws-ecr@6.7.0 aws-ecs: circleci/aws-ecs@1.1.0 jobs: test: docker: - image: circleci/ruby:2.6.6-node-browsers environment: - BUNDLER_VERSION: 2.1.4 - RAILS_ENV: 'test' - image: circleci/mysql:5.7 environment: - MYSQL_ALLOW_EMPTY_PASSWORD: 'true' - MYSQL_ROOT_HOST: '127.0.0.1' working_directory: ~/project steps: - checkout - restore_cache: keys: - v1-dependencies-{{ checksum "Gemfile.lock" }} - v1-dependencies- - run: name: install dependencies command: | gem install bundler -v 2.1.4 bundle install --jobs=4 --retry=3 --path vendor/bundle - save_cache: paths: - ./vendor/bundle key: v1-dependencies-{{ checksum "Gemfile.lock" }} - run: mv ./config/database.yml.ci ./config/database.yml - run: name: setup database command: | bundle exec rake db:create bundle exec rake db:schema:load - run: name: install yarn command: yarn install - run: name: install webpack command: bundle exec bin/webpack - run: name: run rspec command: | mkdir /tmp/test-results TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ circleci tests split --split-by=timings)" bundle exec rspec \ --format progress \ --format RspecJunitFormatter \ --out /tmp/test-results/rspec.xml \ --format progress \ $TEST_FILES - store_test_results: path: /tmp/test-results - store_artifacts: path: /tmp/test-results destination: test-results workflows: version: 2 test_and_deploy: jobs: - test # ビルドした後にイメージをECRへプッシュ - aws-ecr/build-and-push-image: requires: - test account-url: AWS_ECR_ACCOUNT_URL region: AWS_REGION aws-access-key-id: AWS_ACCESS_KEY_ID aws-secret-access-key: AWS_SECRET_ACCESS_KEY create-repo: true dockerfile: ./prod.Dockerfile repo: "${MY_APP_PREFIX}-rails" tag: "${CIRCLE_SHA1}" filters: branches: only: - master # ECSのサービスを更新してタスクを再定義 - aws-ecs/deploy-service-update: requires: - aws-ecr/build-and-push-image family: "${MY_APP_PREFIX}-task" cluster-name: "${MY_APP_PREFIX}-cluster" service-name: "${MY_APP_PREFIX}-service" container-image-name-updates: "container=app,tag=${CIRCLE_SHA1}"各環境変数に間違いが無いか良く確認しておく事。
# app/views/home/index.html.erb <h1>Hello World!</h1> <p>Completed auto deploy with CircleCi</p>自動デプロイが上手くいったかわかりやすいようにトップページを少し変えておく。
masterブランチに変更を加える
実際にmasterブランチにプッシュして変更を加えてみると、CircleCi上でデプロイ込みのjobsが動き始める。
※全てのフローが完了するまでに大体10〜15分くらいかかるので注意。
再度ロードバランサーのDNS名にアクセスし、先ほどの変更がちゃんと更新されていれば成功。
※反映されるまで多少時間がかかるので気長に待つ。
古いタスクと新しいタスクの2つが実行されているが、時間の経過で古い方は勝手に消されるため(サービスのおかげ?)そのまま放置でOK。
お疲れ様でした。
あとがき
自分もまだまだ勉強中の身なので、何かあれば随時更新予定です。
現状、コンソール上で手を動かしながら行う作業とターミナルでコマンドを叩いて行う作業がごちゃ混ぜになってしまっているため、できれば全て後者に統一したいと考えていいます。
Terraformとかも使って一発バシっとできるようにしたい...。
どこか詰まった部分やもっとこうした方が良いなどあればコメントいただけると嬉しいです。
個人的に詰まった部分
EC2のインスタンスタイプ
- 「t2.small」以上を推奨。無料枠での使用だと「t2.micro」が定番だと思うが、筆者の場合、t2.microだとメモリ不足になる不具合が生じた。
service sample-app-service was unable to place a task because no container instance met all of its requirements. The closest matching container-instance c4b1a3e7-3209-408c-9501-7b3ea30f97f7 has insufficient memory available. For more information, see the Troubleshooting section.参照: https://aws.amazon.com/jp/premiumsupport/knowledge-center/ecs-container-instance-cpu-error/
本記事内だと、クラスター作成時のコマンド
$ ecs-cli up --keypair sample-app --capability-iam --size 2 --instance-type t2.small --cluster-config sample-app-cluster --ecs-profile sample-app↑この部分でインスタンスタイプを指定している。
Nginxの設定
- nginx.confファイルの中身は各自変更しないと正常に動かない部分があるので、ググりながら適宜修正する必要がある。
appコンテナとnginxコンテナの接続が上手くいかないと
2020/09/13 20:02:57 [crit] 7#7: *456 connect() to unix:///sample-app/tmp/sockets/puma.sock failed (2: No such file or directory) while connecting to upstream, client: *********, server: localhost, request: "GET / HTTP/1.1", upstream: "http://unix:///sample-app/tmp/sockets/puma.sock:/500.html", host: "***********"↑こんな感じのエラーで延々と悩まされる。
各環境変数の設定
- CircleCiに登録する環境変数の値がしっかり合っているか何度も確認した方が良い。ビルドするのにやたら時間がかかるので、1回失敗するとそれだけでかなりの時間の無駄になる。
- 投稿日:2020-09-15T00:51:17+09:00
【Rails】controllerでassets下に保存されている画像を変数に格納してviewに出力する方法
controllerを使ってassets下に保存されている画像をviewに出力する方法
Railsの自作アプリ開発を進める中で、パラメーターの値によってviewに出力する写真を変えたいと考えていました。
ネットを探っている中で例がなかったため、記事にすることにしました。試したこと
バージョン:Ruby:2.6.3 Rails:5.1.7
controllerでassets/imagesに保存されている画像を変数に格納し、viewに出力するcontrollerdef image @image = URI("/assets/sample1.jpg") endコントローラーでは、変数に上記のようにURIを使って代入できることがわかりました。
初めはurl("/assets/sample1.jpg")と書いたところ、NoMethodErrorが発生。
undefined method `url' for #< SampleController:0x00007fc44b041bd8> Did you mean? URIと表示され、それを試したところ、うまく表示されたという経緯です。view.html.erb<%= image_tag @image %>あとは、image_tagを使用して変数を書けば無事に出力できます。
自作アプリではcontrollerで受け取ったパラメーターをcase文を使用することで画像を変えています。
普通はviewにてimage_tagで画像を出力できます。controllerを用いて、条件によって出力する画像を変えたいと考える方が他にもいらっしゃれば、お力になれたら幸いです。
- 投稿日:2020-09-15T00:07:25+09:00
フォームオブジェクトで結びついている要素を全て初期値として表示する方法
[前提] フォームオブジェクトを使ってitemsテーブルとtagsテーブルの値を同ページで保存している。
[現状]コントローラのeditメソッドで
@item=Item.find(params[:id])と記述しているため、itemsテーブルに保存されている値の初期値のみが表示されている。ハッシュタグのフォームにも初期値を出したい。
[理想図]
[方法]
item_controllerdef edit @item = Item.find(params[:id]) @tag = @item.tags endと一つの商品に紐つぐ複数タグの要素をもつ@tagを定義
edit.html.erb<% @tag.each do |t|%> <div class="weight-bold-text"> タグ(スペースで区切って複数投稿できます) <%= f.text_field :tag_name, class:"items-text", id:"item- tag", value:t.tag_name %> </div> <%end%>@tagという配列の中に複数のタグが入ってるので、eachメソッドで一つ一つ出す。
「t」の中のカラム名「tag_name」の値をvalue属性としてフォームの中に配置する。以上です。