SinatraアプリをHerokuへデプロイする手順


## はじめに
  CODE BASE OKINAWAでのプログラミング教室ではSinatraを使ってWebApp開発を行うカリキュラムを採用している。
ただ、それを外部に公開する手順はちょっと複雑であり、教室のカリキュラム内でもあまり触れていない内容なので、今回記事にするか！ ・・・となったわけだ。
  
## 対象
  - Gitの基礎がわかる人 (init, add, commit, push程度)
  - Sinatraの基礎を理解できている人
  - DBを使ったことがある人
  - (あると望ましい)Herokuを触った事がある。

## 流れ
  今回は 2通りのSinatraアプリ をデプロイする。
  なぜ2つなのか？ それは・・・
  簡単なWebAppを外部公開する手順を通して、`Herokuへのデプロイの流れ`を掴む。

  そして次に、`DB`と`画像保存場所`を外部に用意したパターンのアプリを作成していくなかで、よりHerokuを理解していって欲しいから。

## Herokuとは
  Heroku createというコマンドを打つと、Herokuサーバーに空間を用意してくれる。
  その空間にWebAppのコードをPushすると、勝手にビルドして起動してくれる。
  めちゃくちゃ良いサービス。

  ※詳しくは説明しない。他に分かりやすい記事があるはずだ。

## 簡単なWebApp
  さて1つ目のアプリを軽く作っていこう！

  DBや画像保存の機能が付いていないWebAppであれば簡単にデプロイできる。
  まずは、Sinatraによって作成したAppをHerokuを使ってどうデプロイするのか知って欲しい。


## ざっくり
  1. Gitで管理する (git init)
  2. ローカルで動作する簡単なWebAppを作成
  3. 使うgemはGemfileにまとめる
  4. Procfile作成 (Herokuにデプロイする為に必要な手順)
  5. Heroku create
  6. Herokuへデプロイ (git push)

  ではやっていこう！

## 簡単なWebAppを作る
### フォルダを作る！

今回は適当に `deploy-sample` という名前にした。

### gitで管理する
  ```$ git init```
  
  さて、今回は沖縄の天気を表示するWebAppを作成してみる

### app.rbを作成
  ```ruby
  require 'sinatra'
  require 'sinatra/reloader'
  require 'json'
  require 'open-uri'

  get '/' do
    res = OpenURI.open_uri('http://weather.livedoor.com/forecast/webservice/json/v1?city=471010')
    @res = JSON.parse(res.read)
    erb :index
  end
  ```

### viewsフォルダ作成
  app.rbと同じ階層にviewsフォルダを作成する
  ※Sinatraの基本的な使い方は省略する

### views/layout.erbを作成
  ```erb
  <!DOCTYPE html>
  <html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Deploy-Sample</title>
  </head>
  <body>
    <%= yield %>
  </body>
  </html>
  ```

### views/index.erbを作成
  ```erb
  <div id="res">
    <p>
      <%= @res['location']['prefecture'] %>
      (<%= @res['location']['city'] %>)
    </p>
    <p>
      天気：
      <span id="weather-res">
        <%= @res['description']['text'] %>
      </span>
    </p>
  </div>
  ```

### 動作チェック
  ```$ ruby app.rb```

  [localhost:4567](http://localhost:4567/)
  ブラウザ上にて沖縄県那覇市の周辺天気が情報として表示されればOK！

### コミットする
  ```$ git add *```
  ```$ git commit -m "取り敢えず完成"```

  これでアプリは完成！とする。

### 使うgemはGemfileにまとめる
  使うgemは、Gemfileへまとめる。
  bundleの機能を使う。
  ※bundle, Gemfileのことは知っている前提で進める。

  ※本来は、最初からbundleを使いながら開発した方が良いが、一応今回はbundleを使ってないパターンからの想定をしたかったので・・・

  ```$ bundle init```
  これで、Gemfileが作成される
  
  今回使うgemは、sinatra, sinatra/reloader, open-uri, jsonなので・・・

  Gemfileの中身

  ```gemfile
  source "https://rubygems.org"
  git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }

  gem 'sinatra'
  gem 'json'

  group :development do
    gem 'sinatra-contrib'
  end
  ```

  そして

  ```$ bundle install```

  それに合わせてapp.rbを編集

  ```ruby
  require "bundler/setup"
  Bundler.require
  require 'open-uri'

  if development?
    require 'sinatra/reloader'
  end

  get '/' do
    res = OpenURI.open_uri('http://weather.livedoor.com/forecast/webservice/json/v1?city=471010')
    @res = JSON.parse(res.read)
    erb :index
  end
  ```

  再度、ローカル環境で動作するのかチェック
  ```$ ruby app.rb```

  さっきと同様に動作したら成功

  コミットしとく
  ```$ git add *```
  ```$ git commit -m "bunldeを使うように変更"```

### Procfile作成
  HerokuがPushされたデータを元にどう起動するのか基本的なルールを書いとく。

  app.rbと同じ階層にProcfileという名前のファイルを作成

  ```Procfile
    web: bundle exec ruby app.rb -p $PORT
  ```

### Herokuを使っていく
  ```$ heroku create```
  これで、Herokuサーバーに空間が作られる。
  
  かつ、gitのremoteとして、herokuが登録される。
  試しに確認したければ、以下を打つ。(やらなくも良い)
  ```$ git remote -v```
  リモートリポジトリ情報が表示されるはず。Herokuという文字があればまぁ大丈夫！

  よしコミットする  
  ```$ git add *```
  ```$ git commit -m "Herokuへのデプロイ準備"```

### Herokuへデプロイ (git push)
  Pushしよう！
  ```$ git push heroku master```

  これでPushされて、
  ビルドされて、
  アプリが起動してくれるはず！

### 動作チェック
  ちゃんと動作するか確認する
  ```$ heroku open```

  正しく動作していれば成功！！
  おめでとう！

  簡単だったでしょ！

## ちょっと複雑なWebApp
  先の説明で、分かった通り
  
  「Herokuへのpushには
  gitを使い、
  そして使うgemはGemfileにまとめ、
  Procfileを作成するだけ。」
  
  簡単。

  ただ、Herokuにはデメリットがある。
  それは、画像保存はできるけど、少し時間が経つと削除される。
  -> 理由は簡単で、Herokuは定期的に`Pushされた時の状態に戻す`仕組みが動いているから。つまりは、仕様って事だね！

  よって、画像の保存は外部のサーバーに対して行った方が良い！
  -> 以下で採用するのは、`AWSのS3`。

  併せて、DBを使うWebAppであればこれも同じように、外部のDBサーバーへアクセスする仕組みを採用すべし！
  -> 以下ではHerokuが提供している`Heroku Postgres`を使う！

## まとめ
  WebAppサーバー(Webサーバー兼)と、DBサーバーはHeroku。
  静的ホスティングサービスはAWS。

## こんな感じ
[2つ目のToDoアプリ](https://hep-deploy-2.herokuapp.com/hello)

## ざっくり
  1. Gitで管理する (git init)
  2. ローカル上で動作するサンプルWebAppを作る (Todoアプリ)
  3. 使うgemはGemfileにまとめる
  4. Procfile作成 (Herokuにデプロイする為に必要な手順)
  5. 画像保存はAWSにしたい。 まずはgemインストール。
  6. ブラウザにて新規のS3バケットを作成
  7. 作成したS3へアクセスできるI AMユーザーを作成
  8. I AMユーザー作成した時に生成したトークンを環境変数として使う
  9. アプリの画像保存の記述をすべてS3への保存処理で置き換える
  10. Heroku create
  11. Heroku PostgreSQLを有効化 (ブラウザ上から行う)
  12. 有効時に入手した接続権限を使って、コード修正
  13. ローカルで動作チェックしつつ・・・
  14. Herokuへデプロイ (git push)

## ちょっと複雑なWebApp

  簡単なToDoアプリを作っていく。
  
  と言っても覚えるデータは、

  1. タイトル
  2. そのタスクを実行したかどうか
  の2種類。

  一応、編集と削除の機能も付ける。

  ユーザーのアイコン画像を編集できる機能も付ける。
  ↑ あんまりToDoアプリには必要ない機能だけど、今回は画像保存の機能を持つWebAppを例として作成したかったので、無理やりつけた。。。

### フォルダ作成とgit init
  今回は`deploy-sample-2`って名前にした。

  ```$ git init```

### Todoアプリを作成
  作る手順を解説するのも面倒なので、
  事前に僕がGitHubへあげた・・・

  [リポジトリ](https://github.com/HidetoNakasone/deploy-sample-2)
  [DLリンク](https://github.com/HidetoNakasone/deploy-sample-2/archive/master.zip)

  リポジトリのリンク先からcloneしても良いが、
  一応DLリンクを上に記載したのでそれをクリックしてもよい。
  cloneより簡単だしね。

  ※masterブランチがローカルで動作する時点のアプリのコード。この後、デプロイの為にソースをいじる。それはまた別のdeployというブランチで作業している。

### 必要なSQL文を実行する
  まずは、ローカルで動作させてみたい。

  今回はPostgreSQLと連携したWebAppを作成した。
  ので、PCのPostgreSQLコンソールへアクセスして・・・

  以下 1行ずつ を実行

  ```SQL
  create database heroku_deploy_sample_2;

  \c heroku_deploy_sample_2;

  create table users(id serial, name varchar, pass varchar, primary key(id));

  create table todos(id serial, creater_id int, title varchar, status bool);
  ```

### Gemfileうんぬん
  ```$ bundle update```

### 動作確認
  ```$ ruby app.rb```
  
  [localhost:4567](http://localhost:4567/)
  ブラウザにて、ToDoアプリが無事に動作しているなら大丈夫！

### Procfile作成
  さっきと同じ様に・・・
  Procfileを作成し、中身は以下。

  ```Procfile
    web: bundle exec ruby app.rb -p $PORT
  ```

  ここまでで、さっきまでのHerokuへのPush準備は整った。
  が、このWebAppはDBと画像保存機能があるので、、、
  
  その為に色々と変更するのだ！

### 画像保存はAWSにしたい。 まずはgemインストール。
  
  Gemfileの `gem 'pg` の下に追記

  ```
  gem 'aws-sdk'
  ```

  ```$ bundle update```

### バケット作成
  ※AWSのアカウント作成は既に終わっている前提。。。
  ↑ 作っていない人は作って欲しい。この記事ではアカウント作成の話はしない。もっと良いAWSの解説記事が他にあるはずだ・・・

<img width="1680" alt="01.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/eacbfdd6-2070-7a39-b1db-abadf9118590.png">

  バケット名はなんでも良いですが、僕は
  `deploy-sample-2`にしました。

<img width="1680" alt="02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/5c3919d9-5997-6e03-42a1-6e76bdb6abc7.png">

  作る時に、
  `パブリックアクセスをすべてブロック`
  のチェックは今回外して欲しいです。
  ※本当は良くないけど、、、まぁ今回はテストアプリなので許して。。。

### 作成したS3へアクセスできる IAMユーザー を作成
  AWS マネジメントコンソールで
  `IAM`
  と検索して、ページに移動して・・・

  ユーザーを追加して行きましょう！

  ユーザー名はなんでも良いけど、、、僕は
  `deploy-sample-2-s3`としました。

  `プログラムによるアクセス`にチェックを入れる。

 <img width="1680" alt="03.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/16396fec-7c63-b6b2-79a8-6db44c80bdcf.png"> 

  で、権限を与える時に、、、グループを作成する必要があるので、、、
  今回は、このIAMユーザーには`S3への全アクセス`を与える事にします！

  グループ名は適当で良いです。
  が、S3へのフルアクセス権限は与えてくださいね！

<img width="1680" alt="04.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/6cbb6107-6da8-caa4-d2a4-0de8e43a8511.png">

<img width="1680" alt="05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/2b89a083-3f27-3e2c-ca69-9c73a4da425b.png">


  あとは、、、特に変更せず、、、そのまま進んで・・・

  さて、
  ユーザーを追加
  が終わったら、

<img width="1680" alt="06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/04f3ae62-db09-b842-3739-fd01c31a24a8.png">

  - アクセスキー
  - シークレットアクセスキー
  
  が表示される。
  これはメモっておく。
  
  ※このキーは盗まれない様に配慮してくださいね！

### .envファイルうんぬん
  さて作成したIAMユーザーのキーを
  WebAppで使える様にする

  ルートディレクトリ直下に、
  `.env` ファイルを作成

  中身

  ```
  AWS_S3_ACCESS_KEY_ID='XXXXXXXXXXXXXXXXXXXX'
  AWS_S3_SECRET_ACCESS_KEY='XXXXXXXXXXXXXXXXXXXX'
  AWS_S3_BUCKET = 'XXXXXXXXXXX'
  ```

  この
  `XXXXXXXXXXXXXXXXXXXX`
  の部分は、さっきIAM作成した時に教えられた
  文字列に置き換える！

  BUCKET名は、さっき作成したS3のバケット名を当てる。

  さて、WebAppで、この.envファイルの内容を読み込みたいので・・・
  
  app.rbの

  ```ruby
  if development?
    require 'sinatra/reloader'
  end
  ```
  の中身に、、

  下を追加

  ```ruby
    require 'dotenv'
    Dotenv.load ".env"
  ```

  あと、Gemfileの中身を編集

  ```Gemfile
  group :development do
    gem 'sinatra-contrib'
  end
  ```

  の中に

  ```
    gem 'dotenv'
  ```
  を追加

  ```$ bundle update```
  
  さて、これで
  AWSのS3を利用する準備と権限の読み込みは大丈夫！！

  ※env、環境変数についての詳しい説明は省略する。

  ただ.envの中身は流出してはいけないので・・・
  .gitignoreファイルの末尾に、

  ```.gitignore
  .env
  ```
  を追記。

### アプリの画像保存の記述をすべてS3への保存処理で置き換える
  
  app.rbを編集する
  
  `enable :sessions`
  の下あたりに追記

  ```ruby
  # AWS S3 への接続クライアント
  def s3
    @s3 ||= Aws::S3::Client.new(
      :region => 'us-east-2',
      :access_key_id => ENV['AWS_S3_ACCESS_KEY_ID'],
      :secret_access_key => ENV['AWS_S3_SECRET_ACCESS_KEY']
    )
  end
  ```

  併せて、、、
  今まで画像保存処理の記述である

  ```ruby
  FileUtils.mv(params[:up_image][:tempfile], "./public/images/user_icon/#{session[:user_id].to_s}.jpg")
  ```

  をコメントアウトして、以下に置き換える ↓

  ```ruby
  object_key = "images/user_icon/#{session[:user_id].to_s}.jpg"
  # 保存処理
  s3.put_object(
    bucket: ENV['AWS_S3_BUCKET'],
    key: object_key,
    body: params[:up_image][:tempfile],
    content_type: "image/jpegput",
    metadata: {}
  )
  # アクセスを公開に設定する
  s3.put_object_acl({
    acl: "public-read",
    bucket: ENV['AWS_S3_BUCKET'],
    key: object_key,
  })
  ```

  これで、画像の保存がAWSのS3にされる。

### それに併せて、画像のsrc元も変更する

  ```html
  <img id="preview" src="/images/user_icon/<%= session[:user_id].to_s %>.jpg" onerror="this.src='/images/user_icon/default.png';">
  ```
  を消して・・・

  ```html
  <img id="preview" src="https://deploy-sample-2.s3.us-east-2.amazonaws.com/images/user_icon/<%= session[:user_id].to_s %>.jpg" onerror="this.src='/images/user_icon/default.png';">
  ```
  に変更する。

### 動作チェック
  ```$ ruby app.rb```

  これで、アイコンの画像保存と表示がうまくいけば安心。

  よし！
  画像の保存は成功！

### DBも外部に！
  まず、
  ```$ heroku create```
  する

  これで、Herokuサーバーがpushされる空間を用意してくれる。

  で、Pushするまえに、、、
  Herokuの Postgres機能を使いたいので、
  ブラウザ上から有効化する！！

  Herokuのapp詳細画面で、
  `Configure Add-ons`
  をクリックして、、、機能を有効化していく！
<img width="1680" alt="07.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/a4acde0b-1b95-6a01-5ade-c033af3033a5.png">

  Add-ons
  の検索で、
  `Heroku Postgres`
  と打ち込んで、有効化する。

<img width="1680" alt="08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/854e6a6a-49dd-a794-c49b-1f3e4366f3d3.png">

  有効化したら、
  Heroku Postgres
  をクリックして詳細画面へ！

  settingsの
  Heroku Postgres
  から、有効化したDBへのアクセス権限を見る事が出来る。

<img width="1680" alt="09.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/71e2385e-a93a-1b89-2eb0-f9807a94e59f.png">
  
  それを、メモる。

### 有効時に入手した接続権限を使って、コード修正
  .envに追記

  ```.env
  DATABASE_URL = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
  ```

  このxxxxxxの部分には、
  先のブラウザ上で表示された
  URIの文字列を貼り付ける。

  そして、
  app.rbも変更する。

  ```ruby
  def db
    @db ||= PG.connect(
      dbname: 'heroku_deploy_sample_2'
    )
  end
  ```
  を削除して、、、

  以下に置き換え

  ```ruby
  def db
    uri = URI.parse(ENV['DATABASE_URL'])
    @client ||= PG::connect(
      host: uri.hostname,
      dbname: uri.path[1..-1],
      user: uri.user,
      port: uri.port,
      password: uri.password
    )
  end
  ```

### DBに最低限必要なテーブルを作成する。
  ```$ heroku psql```
  で有効化したDBへアクセスできる。

  ローカルで先程実行した様に、このToDoアプリが最低限動作するため必要なテーブルを作成する。

  ```sql
  create table users(id serial, name varchar, pass varchar, primary key(id));

  create table todos(id serial, creater_id int, title varchar, status bool);
  ```

  ※ここまでの状態をGitHubのリポジトリ(さっきのリンク)の、deployブランチにcommitしてますので、参考にどうぞ。

### 動作チェック
  ```$ ruby app.rb```
  ユーザーやToDoの登録が無事に動作するなら、DBへのアクセスは良好！
  やったね！

### デプロイ
  さて、外部に静的ホスティングサービス・DBを用意した。
  そして、それらを使う様にアプリのコードも修正し、、、
  動作チェックもしたと。。。

  うん。
  デプロイしよう！

  ```$ git push heroku master```

### 最後に、ちょっと。。。
  で、1つ目のAppと同じ様に、、、

  Gemfile.lockの最後の行を編集

  ```Gemfile.lock
  BUNDLED WITH
    1.17.2
  ```

  をして。
  さらに・・・

  実はローカルでは、
  .envファイルに、トークン(アクセス権限)を保存しました。
  で、これって流出しては危険だからという理由で、.gitignoreファイルに.envと書いて、gitの管理対象外にしましたよね。

  ってことは、Herokuサーバーがソースコードを元に、WebAppサーバーを構築し、起動する際に、この.envの中身が分からないので動作できません！！

  つまり、
  ローカルでは、.envにトークンを記した様に、
  Herokuにも、トークンを設定する必要がある！

  やる。

  ブラウザで
  Appの管理画面へ！

  settingsの
  `Config Vars`
  に、

  今回必要な

  - AWS_S3_ACCESS_KEY_ID
  - AWS_S3_SECRET_ACCESS_KEY
  - AWS_S3_BUCKET
  
と、その中身の文字列を覚えさせる。

<img width="1680" alt="10.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/277987/4ad3b572-de96-9364-c722-d0ca401e11dd.png">

  これで大丈夫なはず。

### 動作チェック
  デプロイしたアプリが正しく動作しているかテストしよう！
  ```$ herou open```

  ローカル環境下と同じ様に
  ちゃんと動いていれば成功！

  お疲れ様でした！！