プログラミング学習日記

エンジニア転職への道

EC2+ApacheでRailsアプリを動かすまでの手順

そもそもApacheとは

Apacheとは、HTTPレスポンスを返すウェブサーバーの1つです。 AWSのEC2を作成しただけでは、IPアドレスにアクセスしても何も表示されません。
EC2にApacheを入れることで、HTTPリクエストに対してHTTPレスポンスを返せるようになります。

Apacheの設定手順

ステップ0がすでに済んでいる方は、ステップ1から始めてください。

ステップ0:VPCの作成、EC2の作成、Railsアプリの作成
ステップ1:Apacheのインストールおよび初期設定
ステップ2:ブラウザでApacheのページを表示する
ステップ3:ブラウザで自分のアプリを表示する

ステップ0

Apacheの設定の前に、4つの準備が必要です。
ここでは、VPCやEC2の説明は割愛します。

  1. VPCを作成する
  2. EC2を作成する
  3. ローカルでRailsアプリを作成する
  4. EC2インスタンスにログイン

VPCの作成

以下を行ってください。

  • VPCの作成
  • サブネットの作成
  • ルートテーブルの作成およびサブネットとの関連づけ
  • インターネットゲートウェイの作成およびVPCへのアタッチ

EC2の作成

Railsアプリの作成

ローカル上の任意のディレクトリで、rails new [アプリ名]を実行します。

EC2インスタンスSSHログイン

EC2インスタンス作成時にダウンロードしたpemファイル(拡張子が.pemのファイル)が必要です。

ssh -i [pemファイルのパス] ec2-user@パブリックIPアドレス

ステップ1:Apacheのインストールおよび初期設定

EC2にApacheを入れる

EC2にログインしたら、Apacheをインストールしましょう。
httpdApacheのことです。dは「デーモン」の略で、バックグラウンドで動くプロセスのことを言います。
Apacheの初期バージョンはhttpdと呼ばれており、その名残で以下のコマンドになっています。
-yオプションをつけると質問にいちいちyesと回答しなくて済みます。

sudo yum install -y httpd

Apacheを起動する

一般ユーザーではApacheを起動できません。
Apacheを起動するにはsudoをつけて特権ユーザーで実行する必要があります。

sudo systemctl start httpd

Apacheが起動しているかは、以下で確認できます。 緑でactive (running)と表示されれば起動できています。

sudo systemctl status httpd

EC2を起動したらApacheも起動する設定にする

EC2に入って毎回Apacheを起動するのは面倒なので、EC2を起動したらApacheも自動で起動する設定をしておくと便利です。

sudo systemctl enable httpd

この設定が有効になっているか以下で確認できます。
enabledと表示されればOKです。

sudo systemctl is-enabled httpd

ステップ2:ブラウザでApacheのページを表示する

いまの状態で、検索バーにパブリックIPアドレスを入力してもApacheのページは表示されません。
なぜならEC2がHTTPのリクエストを許可していないからです。

EC2がHTTPリクエストを受け入れるには、EC2のセキュリティグループを編集します。
インバウンドルールでは、外部からEC2へアクセス可能な通信を指定できます。
現状インバウンドルールでSSHしか許可していないので、HTTPを追加すればOKです。

具体的には、以下のとおりです。

  • 「インバウンドルールの編集」→「ルールを追加」→タイプ「HTTP」を選択→ソース「Anywhere-IPv4
  • 「インバウンドルールの編集」→「ルールを追加」→タイプ「HTTP」を選択→ソース「Anywhere-IPv6

最後に「ルールを保存」

これでApacheのページが表示されるはずです。
検索窓にパブリックIPアドレスを打ち込んで、「It works!」と表示されればOKです。

ステップ3:ブラウザでRailsアプリを表示する

ローカル上で作成したRailsアプリをGitHubに上げてください。

EC2にgitをインストールします。

sudo yum install -y git

GitHubからRailsアプリを取得します。

git clone https://github.com/naota7118/apache-app.git

HTTPレスポンスでRailsのページを返すように、Apacheの設定ファイルを編集していきます。 EC2の/etc/httpd/confディレクトリにhttpd.confファイルがあります。

その中で、ポート番号80で受け付けているのがわかります。

Listen 80

やろうとしていること

インターネットからEC2にHTTPリクエストがあったとき、ポート番号80で受け入れます(さきほどセキュリティグループで許可したからです)。
その通信はEC2内部のApacheが待ち受けています。

現状Apacheのデフォルトのページをレスポンスで返しているのを、Railsのページを返すようにしたいです。
どうすればいいでしょうか?

ローカルのRailsアプリを起動すると、Listening on http://127.0.0.1:3000と表示されます。
これは127.0.0.1は自分のパソコンのIPアドレスで、3000はポート番号です。
つまり、Railsアプリは自分のパソコンの3000番窓口で待ち受けています。

通信がApacheまできたら、自分のパソコンの3000番窓口につなげれば、Railsアプリがレスポンスを返します。
Apacheへのリクエストを別のサーバーに行くようにするにはどうすればいいでしょうか?

ここで用いるのが、リバースプロキシです。

リバースプロキシとは

「プロキシ」とは「代理」という意味です。 今回、返したいのはRailsアプリで、RailsにはPumaというWebサーバーが入っていますが、PumaだけではApacheにきたリクエストをRailsに受け渡すことはできません。
そこで、クライアント(HTTPリクエストを送る側)とRails(レスポンスを返すサーバー)の間にリバースプロキシをかませます。
Apacheがリバースプロキシとして機能するよう、/etc/httpd/conf/httpd.confに追加設定を書きます。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

ProxyRequests Off
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/

ここに出てくるLoadModuleProxyRequestsのことを「ディレクティブ」と言います。
「ディレクティブ」は、指示のことです。 1つ1つのディレクティブがApacheに対して「〇〇してね」と指示を出しています。

Apacheをリバースプロキシ化する

mod_proxymod_proxy_httpという2つのモジュールを用います。

LoadModuleディレクトリで、mod_proxyを読み込んでいます。

LoadModule proxy_module modules/mod_proxy.so

/etc/httpd/modulesディレクトリにApacheのモジュールがあります。
LoadModuleディレクティブは、LoadModule [モジュール名] [モジュールのパス]でモジュールを読み込みます。

ProxyRequests Off

ProxyRequestsは、フォワードプロキシ設定のON/OFFを決めます。
今回はリバースプロキシ化するのでOFFにします。

いよいよリクエストを転送する部分です。

ProxyPass / http://127.0.0.1:3000/

ProxyPassディレクトリは、ProxyPass [Apacheのパス] [ローカルサーバーのパス]と指定することで、Apacheに来たリクエストをローカルサーバーに転送します。
これによって、クライアントからIPアドレス宛にHTTPリクエストが送られたら、Apacheを経由してRailsサーバーがレスポンスとして返されるようになります。

EC2上でRailsサーバーを動かすには、以下の準備が必要です。

  • rbenvのインストール
  • ruby-buildのインストール
  • ライブラリのインストール
  • 空き容量の確保
  • Rubyのインストール

rbenvのインストール

rbenvはRubyのバージョン管理ツールです。
Rubyのバージョンを指定してインストールしたいときやバージョンを新しくしたいときに使います。

git clone https://github.com/rbenv/rbenv.git ~/.rbenv  
~/.rbenv/bin/rbenv init
source ~/.bash_profile

github.com

ruby-buildのインストール

ruby-buildは、Unixシステム上でRubyをソースからインストールするためのコマンドラインツールです。
rbenvのプラグインとして使うこともできますし、スタンドアロンとして使うこともできます。

git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build

github.com

ライブラリのインストール

Rubyは様々なライブラリを使っているので、ライブラリを先にインストールしておかないと、Rubyをインストールするときに「〇〇のライブラリがありません」とエラーが出ます。
逐一追加するのも面倒なので、さきにインストールしておきましょう。

sudo dnf group install -y "Development Tools"
sudo yum install -y ruby-devel openssl-devel libyaml-devel libffi-devel

空き容量の確保

No space left on deviceというエラーが出たら、デバイス上の空き容量不足でRubyのインストールが完了していません。

/usr/bin/ar: unable to copy file 'libruby-static.a'; reason: No space left on device
make: *** [Makefile:318: libruby-static.a] Error 1

Rubyのインストール

容量不足を解消するために、一時ディレクトリを作成したうえでインストールします。
環境変数TMPDIRを定義すると、一時ディレクトリが作られます。
このディレクトリはインストールプロセス中のみ使われます。
Rubyのインストール先は~/.rbenv/versions/3.3.0で、一時ディレクトリではありません。

TMPDIR="${PWD}/tmp" rbenv install -v 3.3.0

rubyインストールでtmpの容量不足 #Ruby - Qiita

Railsアプリの起動

EC2内でRailsアプリのディレクトリに移動して、bundle installを実行します。
その後、rbenv rehashで、gemのコマンドが使えるようになり、rails sで起動できるようになります。

bundle install
rbenv rehash

http://パブリックIPアドレスでアクセスすると、Railsのロゴが表示されれば完了です。

本番環境で最初のエラー

AWSでデプロイして、さっそくエラーになった。

問題

本番環境でアップロードしたファイルが読み取れない。
ローカル環境だとエラーにならないのに、本番環境だとエラーになる。

No such file or directory @ rb_sysopen - /home/ec2-user/ocr_check_app/public/uploads/sample_data.xlsx

def create
  uploaded_files = params[:uploads]
  uploaded_files.shift # 最初の要素を削除
  uploaded_files.each do |uploaded_file|
    file_path = Rails.root.join("public/uploads/#{uploaded_file.original_filename}")
    File.open(file_path, 'w+b') do |file|
      file.write(uploaded_file.read)
    end
  redirect_to moca_result_path
end

確認したこと

uploaded_filesは取得できている。

わかったこと

ローカル環境だとpublicディレクトリの下にuploadsディレクトリがあって、それをgit cloneしたのに本番だとない。

一時的な解決法

手動でuploadsディレクトリを作成した。

オリプロで直面した問題:見当識の得点が取得できない

問題

見当識の得点が取得できない。
取得したい得点は11個あるが、そのうち1つが取得できていない。

やったこと

  1. binding.pryで処理を止めて、テキストファイルの出力結果を確認。
    → その結果、1/6116と出力されていることがわかる。

  2. 1161/6を変換する処理を書く

詰まったところと解決法

メソッドを呼び出してるのに、返り値が反映されていない。

メソッドを呼び出してその結果を変数に代入すればいいのでは?

それでも反映されない。。 返り値を明示する必要があった。

1161/6に変換されている。

1/6で取得できた。

各行の末尾の\r\nを除去したい

やりたいこと

テキストファイルには1行ごとにデータが入っている。 これらのデータを改行文字や半角スペースなしでそのまま取得したい。

お 
あ 
おわり 
5 
2 
10 
110 

何もしない場合

何もしないと、各行の末尾に\r\nが入ってしまう。

def get_id_from_text
  @pdf_id = []
  File.open('./tmp/txt/sample.txt', 'r') do |f|
    f.each_line do |line|
        puts line.inspect
        @pdf_id.push(string) if string.include?('CHIBA')
    end
  end
end
"お \r\n"
"あ \r\n"
"おわり \r\n"
"5 \r\n"
"2 \r\n"
"10 \r\n"
"110 \r\n"

chompメソッドを使った場合

\r\nは取れたが半角スペースが残ってしまう。

"お "
"あ "
"おわり "
"5 "
"2 "
"10 "
"110 "
def get_id_from_text
  @pdf_id = []
  File.open('./tmp/txt/sample.txt', 'r') do |f|
    f.each_line do |line|
        string = line.chomp
        puts string.inspect
        @pdf_id.push(string) if string.include?('CHIBA')
    end
  end
end

stripメソッドを使った場合

改行文字も空白文字もなくなってスッキリした。

"お"
"あ"
"おわり"
"5"
"2"
"10"
"110"
def get_id_from_text
  @pdf_id = []
  File.open('./tmp/txt/sample.txt', 'r') do |f|
    f.each_line do |line|
        string = line.strip
        puts string.inspect
        @pdf_id.push(string) if string.include?('CHIBA')
    end
  end
end

参考

docs.ruby-lang.org

1 ≦ x ≦ 6, 1 ≦ y ≦ 6, `/`はスラッシュ記号のとき、`x / y`が全部で何パターンあるか調べたい

やりたいこと

1 ≦ x ≦ 6, 1 ≦ y ≦ 6, /はスラッシュ記号のとき、x / yが全部で何パターンあるか調べたい。

コード

x = [1, 2, 3, 4, 5, 6]
y = [1, 2, 3, 4, 5, 6]

count = 0
x.each do |x_num|
  y.each do |y_num|
    puts "#{x_num}/#{y_num}"
    count += 1
  end
end

puts count

出力結果

[algorithm]$ ruby six.rb
1/1
1/2
1/3
1/4
1/5
1/6
2/1
2/2
2/3
2/4
2/5
2/6
3/1
3/2
3/3
3/4
3/5
3/6
4/1
4/2
4/3
4/4
4/5
4/6
5/1
5/2
5/3
5/4
5/5
5/6
6/1
6/2
6/3
6/4
6/5
6/6
36

x ≦ yに該当するものだけで絞り込む

これらのうち、x ≦ yに該当するものだけで絞り込みたい。

x = [1, 2, 3, 4, 5, 6]
y = [1, 2, 3, 4, 5, 6]

count = 0
x.each do |x_num|
  y.each do |y_num|
    if x_num <= y_num
      puts "#{x_num}/#{y_num}"
      count += 1
    end
  end
end

puts count
[algorithm]$ ruby six.rb
1/1
1/2
1/3
1/4
1/5
1/6
2/2
2/3
2/4
2/5
2/6
3/3
3/4
3/5
3/6
4/4
4/5
4/6
5/5
5/6
6/6
21

配列から2つの要素の組み合わせを全パターン出す(重複あり)

やりたいこと

このような配列があったとして、この中から2つ選ぶ組み合わせを出したい。

["sun", "shine", "rain", "bow", "moon", "light"]

入力される値

6
sun
shine
rain
bow
moon
light

方法①:repeated_combinationメソッドを使う

n = gets.chomp.to_i

array = []
n.times do
  array << gets.chomp
end

p array.repeated_combination(2).to_a

出力結果

[["sun", "sun"], ["sun", "shine"], ["sun", "rain"], ["sun", "bow"], ["sun", "moon"], ["sun", "light"], ["shine", "shine"], ["shine", "rain"], ["shine", "bow"], ["shine", "moon"], ["shine", "light"], ["rain", "rain"], ["rain", "bow"], ["rain", "moon"], ["rain", "light"], ["bow", "bow"], ["bow", "moon"], ["bow", "light"], ["moon", "moon"], ["moon", "light"], ["light", "light"]]

方法②:productメソッドを使う

n = gets.chomp.to_i

array = []
n.times do
  array << gets.chomp
end

p array.product(array)

出力結果

[["sun", "sun"], ["sun", "shine"], ["sun", "rain"], ["sun", "bow"], ["sun", "moon"], ["sun", "light"], ["shine", "sun"], ["shine", "shine"], ["shine", "rain"], ["shine", "bow"], ["shine", "moon"], ["shine", "light"], ["rain", "sun"], ["rain", "shine"], ["rain", "rain"], ["rain", "bow"], ["rain", "moon"], ["rain", "light"], ["bow", "sun"], ["bow", "shine"], ["bow", "rain"], ["bow", "bow"], ["bow", "moon"], ["bow", "light"], ["moon", "sun"], ["moon", "shine"], ["moon", "rain"], ["moon", "bow"], ["moon", "moon"], ["moon", "light"], ["light", "sun"], ["light", "shine"], ["light", "rain"], ["light", "bow"], ["light", "moon"], ["light", "light"]]

方法③:二重ループを回す

n = gets.chomp.to_i

array = []
n.times do
  array << gets.chomp
end

array.each_with_index do |word, word_i|
  array.each_with_index do |w, w_i|
    p [array[word_i], array[w_i]]
  end
end

出力結果

["sun", "sun"]
["sun", "shine"]
["sun", "rain"]
["sun", "bow"]
["sun", "moon"]
["sun", "light"]
["shine", "sun"]
["shine", "shine"]
["shine", "rain"]
["shine", "bow"]
["shine", "moon"]
["shine", "light"]
["rain", "sun"]
["rain", "shine"]
["rain", "rain"]
["rain", "bow"]
["rain", "moon"]
["rain", "light"]
["bow", "sun"]
["bow", "shine"]
["bow", "rain"]
["bow", "bow"]
["bow", "moon"]
["bow", "light"]
["moon", "sun"]
["moon", "shine"]
["moon", "rain"]
["moon", "bow"]
["moon", "moon"]
["moon", "light"]
["light", "sun"]
["light", "shine"]
["light", "rain"]
["light", "bow"]
["light", "moon"]
["light", "light"]

結果の違い

repeated_combinationメソッドの結果の要素数は21で、productメソッドと二重ループで回した場合は36だった。

以下はrepeated_combinationメソッド使った場合にだけなかった要素だ。

["shine", "sun"]
["rain", "sun"]
["rain", "shine"]
["bow", "sun"]
["bow", "shine"]
["bow", "rain"]
["moon", "sun"]
["moon", "shine"]
["moon", "rain"]
["moon", "bow"]
["light", "sun"]
["light", "shine"]
["light", "rain"]
["light", "bow"]
["light", "moon"]

repeated_combinationメソッドは["sun", "shine"]と ["shine", "sun"]は同じ組み合わせとして処理される。
一方でproductメソッドは違う組み合わせとして処理される。

追記

repeated_permutationメソッドを使っても、順序を考慮して、順番を入れ替えた場合も出力される。

array.repeated_permutation(2).to_a

6/15日報

やったこと

  • Paiza B問題を1問解いた。
  • AWSVPCを使ってネットワークの設定を行った。
  • AWSでEC2インスタンスを作り、秘密鍵でログインした。
  • PythonOCRライブラリ「pdfminer.six」で文字認識精度を確認した。ID表のテキストは抽出できたが、評価用紙では全くできなかった。写真からPDFにしたものだからか?
  • PythonOCRエンジン「pytesseract」で文字認識精度を確認した。デフォルトだと日本語設定がなく、GitHubから生のコードをダウンロードして指定の場所に置いて日本語設定でも試した。評価用紙の得点データは抽出できなかった。

つまずいたところ

  • AWSのセキュリティグループでHTTP通信をポート番号80で許可したのに、パブリックIPアドレスをたたいてもブラウザにApacheが表示されなかった。IPアドレスを直接はりつけていたら、httpではなくhttpsで要求していた。http://〜にしたら表示された。
  • Python用のOCRツールpytesseractをpin install pytesseractでインストールしたのに、FileNotFoundError: [Errno 2] No such file or directory: 'tesseract'とエラーが出た。pin install pytesseractpythonプログラムからpytesseractを呼び出せるようにしただけで、パソコンにインストールはできてなかった。MacにインストールするにはHomebrewを使ってbrew install pytesseractを実行する必要があった。
  • 先日Pythonをインストールしたのに-bash: python: command not foundと出た。source ~/.bash_profileを実行したら解消した。ターミナルを閉じると反映されなくなることもある?