はじめに
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を使ってどうデプロイするのか知って欲しい。
ざっくり
- Gitで管理する (git init)
- ローカルで動作する簡単なWebAppを作成
- 使うgemはGemfileにまとめる
- Procfile作成 (Herokuにデプロイする為に必要な手順)
- Heroku create
- Herokuへデプロイ (git push)
ではやっていこう!
簡単なWebAppを作る
フォルダを作る!
今回は適当に deploy-sample
という名前にした。
gitで管理する
$ git init
さて、今回は沖縄の天気を表示するWebAppを作成してみる
app.rbを作成
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を作成
<!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を作成
<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
ブラウザ上にて沖縄県那覇市の周辺天気が情報として表示されれば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の中身
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を編集
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という名前のファイルを作成
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。
こんな感じ
ざっくり
- Gitで管理する (git init)
- ローカル上で動作するサンプルWebAppを作る (Todoアプリ)
- 使うgemはGemfileにまとめる
- Procfile作成 (Herokuにデプロイする為に必要な手順)
- 画像保存はAWSにしたい。 まずはgemインストール。
- ブラウザにて新規のS3バケットを作成
- 作成したS3へアクセスできるI AMユーザーを作成
- I AMユーザー作成した時に生成したトークンを環境変数として使う
- アプリの画像保存の記述をすべてS3への保存処理で置き換える
- Heroku create
- Heroku PostgreSQLを有効化 (ブラウザ上から行う)
- 有効時に入手した接続権限を使って、コード修正
- ローカルで動作チェックしつつ・・・
- Herokuへデプロイ (git push)
ちょっと複雑なWebApp
簡単なToDoアプリを作っていく。
と言っても覚えるデータは、
- タイトル
- そのタスクを実行したかどうか
の2種類。
一応、編集と削除の機能も付ける。
ユーザーのアイコン画像を編集できる機能も付ける。
↑ あんまりToDoアプリには必要ない機能だけど、今回は画像保存の機能を持つWebAppを例として作成したかったので、無理やりつけた。。。
フォルダ作成とgit init
今回はdeploy-sample-2
って名前にした。
$ git init
Todoアプリを作成
作る手順を解説するのも面倒なので、
事前に僕がGitHubへあげた・・・
リポジトリのリンク先からcloneしても良いが、
一応DLリンクを上に記載したのでそれをクリックしてもよい。
cloneより簡単だしね。
※masterブランチがローカルで動作する時点のアプリのコード。この後、デプロイの為にソースをいじる。それはまた別のdeployというブランチで作業している。
必要なSQL文を実行する
まずは、ローカルで動作させてみたい。
今回はPostgreSQLと連携したWebAppを作成した。
ので、PCのPostgreSQLコンソールへアクセスして・・・
以下 1行ずつ を実行
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
ブラウザにて、ToDoアプリが無事に動作しているなら大丈夫!
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の解説記事が他にあるはずだ・・・
バケット名はなんでも良いですが、僕は
deploy-sample-2
にしました。
作る時に、
パブリックアクセスをすべてブロック
のチェックは今回外して欲しいです。
※本当は良くないけど、、、まぁ今回はテストアプリなので許して。。。
作成したS3へアクセスできる IAMユーザー を作成
AWS マネジメントコンソールで
IAM
と検索して、ページに移動して・・・
ユーザーを追加して行きましょう!
ユーザー名はなんでも良いけど、、、僕は
deploy-sample-2-s3
としました。
プログラムによるアクセス
にチェックを入れる。
で、権限を与える時に、、、グループを作成する必要があるので、、、
今回は、このIAMユーザーにはS3への全アクセス
を与える事にします!
グループ名は適当で良いです。
が、S3へのフルアクセス権限は与えてくださいね!
あとは、、、特に変更せず、、、そのまま進んで・・・
さて、
ユーザーを追加
が終わったら、
- アクセスキー
- シークレットアクセスキー
が表示される。
これはメモっておく。
※このキーは盗まれない様に配慮してくださいね!
.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の
if development?
require 'sinatra/reloader'
end
の中身に、、
下を追加
require 'dotenv'
Dotenv.load ".env"
あと、Gemfileの中身を編集
group :development do
gem 'sinatra-contrib'
end
の中に
gem 'dotenv'
を追加
$ bundle update
さて、これで
AWSのS3を利用する準備と権限の読み込みは大丈夫!!
※env、環境変数についての詳しい説明は省略する。
ただ.envの中身は流出してはいけないので・・・
.gitignoreファイルの末尾に、
.env
を追記。
アプリの画像保存の記述をすべてS3への保存処理で置き換える
app.rbを編集する
enable :sessions
の下あたりに追記
# 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
併せて、、、
今まで画像保存処理の記述である
FileUtils.mv(params[:up_image][:tempfile], "./public/images/user_icon/#{session[:user_id].to_s}.jpg")
をコメントアウトして、以下に置き換える ↓
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元も変更する
<img id="preview" src="/images/user_icon/<%= session[:user_id].to_s %>.jpg" onerror="this.src='/images/user_icon/default.png';">
を消して・・・
<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
をクリックして、、、機能を有効化していく!
Add-ons
の検索で、
Heroku Postgres
と打ち込んで、有効化する。
有効化したら、
Heroku Postgres
をクリックして詳細画面へ!
settingsの
Heroku Postgres
から、有効化したDBへのアクセス権限を見る事が出来る。
それを、メモる。
有効時に入手した接続権限を使って、コード修正
.envに追記
DATABASE_URL = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxx'
このxxxxxxの部分には、
先のブラウザ上で表示された
URIの文字列を貼り付ける。
そして、
app.rbも変更する。
def db
@db ||= PG.connect(
dbname: 'heroku_deploy_sample_2'
)
end
を削除して、、、
以下に置き換え
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アプリが最低限動作するため必要なテーブルを作成する。
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の最後の行を編集
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
と、その中身の文字列を覚えさせる。
これで大丈夫なはず。
動作チェック
デプロイしたアプリが正しく動作しているかテストしよう!
$ herou open
ローカル環境下と同じ様に
ちゃんと動いていれば成功!
お疲れ様でした!!