いわりょのBlog

IT関連で学んだことを書いていきます。

scpで.ssh/configに登録しているhostを使う EC2ファイル編

EC2の内のバックアップファイルとかをローカルのコピーしたかったので調べてみました。

ローカルの~/.ssh/config内である一つのEC2インスタンスに接続するための設定があったとします。↓

~/.ssh/config

Host sample_key_rsa
  Hostname xx.xxx.xx.xx
  Port 22
  User hoge
  IdentityFile ~/.ssh/sample_key_rsa

ssh接続時のコマンド↓

$ ssh sample_key_rsa

scpコマンドでEC2内のファイルをコピーする。

$ scp ホスト名:EC2内のパス ローカルのパス

使うときはこんな感じ↓

$ scp sample_key_rsa:~/test.txt ~/desktop

ローカルのファイルをEC2内のディレクトリにコピーする。

$ scp ローカルのパス ホスト名:EC2内のパス 

使うときはこんな感じ↓

$ scp ~/desktop sample_key_rsa:~/test.txt 

Rails+PostgreSQL+Docker+AWSで作成したポートフォリオの概要

記事の概要

この記事では、私が作成したポートフォリオの解説をします。

コンテンツ投稿、管理、その他の営業に利用できるサービス機能をつけた、お店の個人ホームページです。

管理者のみ、コンテンツのCRUD機能やその他の機能が使える仕様にしています。

一般閲覧者は、コンテンツの閲覧のみです。

ポートフォリオ本体です。実際に運用しているため、こちらのソース公開や機能を利用することは開発者しかできません。↓

ポートフォリオサイト

公開用のコピーアプリがこちら。投稿機能が体験できます。ソースも公開しております。↓↓

公開用サンプルサイト

GitHub

https://github.com/Ryo10Leo/post_sample_app


自己紹介

岩田 凌(いわたりょう) 1994/05/07生

2019年4月からIT関連の勉強をはじめ、2019年10月から本格的にプログラミングの学習を開始。

取得資格
基本情報技術者試験合格/ Javaプログラミング能力検定二級/ Java SE Programer Ⅰ/ 三級Webデザイン技能士/ 日商簿記検定二級/ 英検二級

学習言語、インフラ関連
Ruby, Ruby on Rails, HTML, CSS, Javascript, AWS, Docker

背景

私の実家で母が美容室を経営しており、独立20周年を迎えようとしています。

それを迎えるに当たって、私はプログラミング初心者ながら今学習していることで「実用的な貢献」ができないかと思っていました。

ヒアリングにより以下のような要望がありました。

  • 20周年を迎えるにあたり、改めて自身の美容室の活動サービスを様々な方に知っていただきたい。
  • 美容の専門的な知識を使って、お客様のお役に立つような美容関連の商品を紹介したい。
  • 自身のスタイリング写真を見ていただきたい、かつアルバム(資産?)として残したい


以上のことを踏まえ、美容室の公式ホームページがまだないことから、

コンテンツ投稿、管理機能、その他営業に利用できる機能を備えた公式HPを作成することとなりました。


また、Web系の開発企業のエンジニアになろうと考えていたので、このアプリケーションを転職活動用のポートフォリオとすることにしました。


今回は、作業の一部(写真関係)を駆け出しのエンジニア初学者仲間、SNSで写真活動を行っている方、一般のモデルなどに協力をお願いしています。(協力者の紹介は後述)

皆と連携することで本格的なHPを作成する一つのプロジェクトとして活動しました。

目的

  • Rubyやその他の言語、フレームワークなどを用いたWebアプリケーション開発を経験する
  • インフラ構築を経験して、運用も学ぶ
  • Web系企業さんで働けるような技術の取得
  • 小規模なプロジェクトの簡単なマネージメント経験
  • 実用的なアプリを作成し、実際の美容室の営業に利用してもらう。
  • 運用しながら改善点を見つけ、バージョンアップを経験する

スペック

言語
  Ruby 2.6.3

データベース
  PostgreSQL 9.2.24

Webサーバ
   Nginx 1.16.1

フレームワーク
  Ruby on Rails 5.1.6

開発環境
  Docker 19.03.5
  docker-compose 1.25.2

バージョン管理
  Git 2.21.0

本番環境
  AWS(VPC, EC2, ALB, Elastic IP, Route53, Cloud Front, S3)

機能一覧 (一部抜粋)

  • ログイン機能(Remember me機能付)

ログインすることで「ブログ」,「スタイル」,「商品」の各ページで投稿編集削除機能が利用できます。


  • 写真などコンテンツ投稿機能(Ajax通信, 画像アップロード先 S3)

ログインすることでコンテンツの投稿ができます。


  • 投稿編集機能(Ajax通信)

既存の投稿コンテンツのキャプションなどが編集できます。


  • 投稿削除機能

コンテンツの削除


  • 記事投稿機能(Ajax通信, Ckeditor, 画像アップロード先 S3)

記事には、「タイトル」「内容(画像付)」「カテゴリ」が追加できます。「日付」は投稿時間から動的にフォーマットが変換され表示されます。

投稿順に表示されます。

サイドバーに最新記事5件のタイトルを動的表示(個別記事閲覧可)。

本体ポートフォリオの HOMEページでは、最新三件の記事を動的表示。

f:id:Ryo10Leo:20200201203140p:plain

解説記事↓
ryo10leo.hatenablog.com


  • 記事カテゴリー付機能(UI Tag it)

f:id:Ryo10Leo:20200201204042p:plain
記事にはカテゴリーが追加できます。

カテゴリ別の閲覧が可能です。

f:id:Ryo10Leo:20200201204340p:plain

サイドバーには記事で使われているカテゴリだけを表示。
使われていないカテゴリーは記事更新時にデータベースか自動削除されます。


  • 営業カレンダー更新機能(FullCalender, Google カレンダー API, iPhoneカレンダーアプリ同期)

管理者のスマホのカレンダーアプリに休日や予約などを追加することで、自動でホームページの営業カレンダーに追加した予定が同期します。

ryo10leo.hatenablog.com


  • ページネーション機能(Will_Pagenate)

f:id:Ryo10Leo:20200201210329p:plain

コンテンツのページネーション機能です。

開発手順

1.要件定義

作成する主な機能

  • 管理者ログイン機能
  • コンテンツ(記事, スタイル, 商品 )CRUD機能
  • コンテンツ一覧表示機能
  • 営業スケジュールカレンダー更新機能

必要となるもの

  • ユーザー (今回は一件のみ)」「記事」「スタイル」「商品」の情報を保存するためのデータベース
  • データベースの内容を表示する「動的なビュー
  • 投稿」「編集」「削除」画面
  • 動的な営業カレンダーを表示するための、「ライブラリ」と「 API

2.環境選定

アプリケーション

言語は学習中の「Ruby」とし、フレームワークは「Ruby on Rails」としました。

フロントエンドはこだわりが強かったので、Bootstrapは使っていません。
検索した美容室さんのサイトやCSSデザインの記事などを参考に、HTMLとCSSをほば手書きで作成しました。

アプリの動きは「JavaScript」で動作させ、「jQuery」などのライブラリも使っています。

開発環境

f:id:Ryo10Leo:20200131182635p:plain
最初の方はローカル環境で開発していたのですが、途中で「docker」と「docker-compose」の仮想環境に切り替えました。
何やら現場ではよく使われていて、開発効率を上げるものだと教わったので勉強だと思って導入しました。
今後デプロイ時にコンテナを起動する構成にしていくつもりです。

ryo10leo.hatenablog.com

インフラ

本番環境では「AWS」。EC2やRoute53などを使います。

Webサーバーには資料の多い「Nginx」を使用します。結果的にRailsの「Puma」との連携が容易にできたため。

データベースは「PostgreSQL」を選択。(MySQLを使ったことがないので...)

3. データベース設計

f:id:Ryo10Leo:20200202215057p:plain
使用ソフト cocoo

基本情報技術者を勉強していたおかげで、データベースやSQLの基礎知識は多少ありました。

そのためアプリの機能でどんな情報を保存するか、どんな関連付けを行うと良いかを構想するのはそこまで難しくなかったです。

あとはアプリ作成時のフレームワークでどのようなコードを書いたり、コマンドを打てば、

設計通りに構築できるのか、ビューで取得したいデータを抽出できるのか。

それらを意識することで学びながら構築はできました。

4.ワイヤーフレーム(プロトタイプ)の作成

デザインのイメージがありましたが、いきなりコーディングすると作業効率が悪いと思ったので、「cacoo」というブラウザツールを使うことで「ワイヤーフレーム」を作成し、フロントエンドの作業も円滑に行えるようにしました。

これを行って最もよかった点としては、
プロトタイプ」があることで、クライアントからデザインの感想をいただき、要望や変更を円滑に反映することができたことです。

5.コーディング

Git管理

コードは「Git」で管理し、こまめに「GitHub」にプッシュしてバージョン管理を行いました。
Gitの扱いに慣れてきたら機能ごとにブランチを切って作業し、Masterブランチにマージするなど、実践を意識したコーディング練習も行いました。

フロントエンド

序盤はワイヤーフレームの内容に沿って、「HTML」「CSS」を利用したコーディングを行いました。
静的なページができたら 「JavaScript」で動きをつけ、視覚的に楽しめるように意識しました。

バックエンドは並行して行っていなかったので、バックエンドの作業に入る際は少し冗長的なコードが多かったと思います。(データベースから抽出やRailsのヘルパーが使えないので仕方ないことなのですが、、、)

バックエンド

Railsプロジェクトを立ち上げ、フロントエンドの作業で完成したビュー(.html)を一つずつ動的なページ(html.erb)に変更していきました。

今回のポートフォリオの主な動的ページは「News」「Style」「Product」「Login」です。

大まかな作業手順としては、

  1. データベース作成(バリデーション付与)
  2. コントローラーの作成
  3. ルーティング設定
  4. Gem」や「ヘルパーメソッド」を適宜追加。
  5. 冗長的なビューのコードの削除」です。

取り掛かった順番ですが、

  1. Style」「Product」は画像と文字列のデータを動的表示するだけで、さほど難しくないので序盤に、、、
  2. News」は記事内容に「リッチテキストエディタ」を導入しており、「カテゴリー機能」など複雑なデータベース関連の処理を用いるため中盤に、、、
  3. Login」では「セッション」や「クッキー」などの概念を理解することが難しく、実装に慎重になる必要があったため終盤

となりました。

ページ作成順 レベル「Style」→「Product」→「News」→「Login」

ここまででログインした管理者のみがコンテンツを投稿できるシステムが完成しました。

営業カレンダー

Google API」と「Full Calendar」の同期後、
各種カレンダーの見た目や動的な表示の設定などは、Javascript,CSSに書き込んで編集。

6.デプロイ

本番環境にはAWSを用いており、全体の構成図は以下の通りです。↓

f:id:Ryo10Leo:20200202141717p:plain

EC2インスタンス」を立ち上げ、「Elastic IP」でグローバルIPを固定。

お名前.comでドメインを購入し、「Route53独自ドメインの設定。

インターネットからの通信は、管理者が投稿送信の際に情報が盗聴、改ざんされないように「ALB」を噛ませることで、HTTPS通信で暗号しています。

画像コンテンツの保存には「S3」を使用して、「Cloud Front」で配信を高速化しています。


EC2内で構成した環境は以下の通りです↓

f:id:Ryo10Leo:20200115230205p:plain

構成はWeb3層構成です。

Webサーバー層には、「Nginx

Webブラウザからのアクセス要求を処理する層を示します。必要に応じて、Webアプリケーション層へリクエストを要求します

アプリケーションサーバー層には、「Puma」「Rails

Webサーバから受けたリクエストをもとに、バックエンドで動作するRubyなどを実行したり、データベースへのアクセスを行ない、処理を行ないます。

データベース層には、「PostgreSQL

入力したデータや、アプリケーションで参照するデータを保管する役割を持ちます。

今回のデプロイではWeb3層構成を構築する際、EC2にSSHでログインし、必要なソフトウェアをインストールして適宜設定をした流れです。
ryo10leo.hatenablog.com

今後の改善点、追加実装について

フロントエンド

  • トップページにアクセスしたときに、おしゃれなローディング画面を作りたいと思います。
  • 投稿の送信時間がかかるとき「送信中」だとわかるloading.gifをつけたいです。

投稿コンテンツが増えたときの対策

  • 管理画面」を作ってコンテンツの検索をできるようにして、CRUD機能がより簡単にできるようにする。

構築したインフラ環境の改善

  • 一つのEC2インスタンス内にWeb3層構成を構築したので、ベストプラクティスではないと思います。「RDS」を追加してデータベースを切り離すか、または「docker」と「docker-compose」を使ってコンテナで運用するのか、様々なデプロイ方法を検討して取り入れたいと思います。

インフラの障害の対策

  • データベースのバックアップを定期的にできる仕組みを勉強し、コンテンツデータの紛失を防ぐ。

追記」 データベースのバックアップを定期的に行う仕組みを作成してみました。↓
EC2で構築したPostgreSQLデータベースを定期的にバックアップ。ローカルにもバックアップファイルをコピーする - りょ〜Blog

ポートフォリオ作成の協力者様をご紹介

今回はホームページ作成ということで、そこに掲載する「写真」も重要なテーマになっております。
f:id:Ryo10Leo:20200202191104j:plain


f:id:Ryo10Leo:20200202191114j:plain

そこで写真撮影や編集に関わっていただいた方達をご紹介します。

『写真撮影』 奥出航介(おくでこうすけ)様

f:id:Ryo10Leo:20200202150754j:plain

独特なセンスが自分の中のポートフォリオのイメージとマッチしているため、今回ご依頼させていただきました。

彼はSNSを中心に写真活動を行っております。

コンセプトは「日常の中で揺れ動いた瞬間

現在のTwitterフォロワー数は3000人近く。

Twitterアカウント↓
おくで (@photo_okina) | Twitter


写真編集』 阿部翔次郎(あべしょうじろう)様

f:id:Ryo10Leo:20200202152315j:plain

私と同じ駆け出しエンジニア仲間です。デザインやフロントエンドを中心に勉強されています。

Photoshopの技術を習得されていたため、今回写真の編集に携わっていただきました。

主な編集は「ぼかし加工」「背景の入れ替え」などです。

ご依頼からの「イメージ共有」「納品」が早く、大変助かりました。

阿部様のポートフォリオサイト↓
For employment 阿部翔次郎


ご紹介したお二人とその他協力していただけたモデルの方々のおかげもあり、当初イメージしていた通りのものが出来上がりました。

大変ありがとうございました!

既存のRailsアプリのローカル開発環境をDockerの仮想環境に切り替える

最近までRailsのアプリの開発はローカル環境で行っていました。

Dockerを学び初めて使ってみたいなと思ったので、開発環境をDockerとdocker-composeで作ってみることにしました。

開発環境の完成図

どういうやつを作ろうか迷ったんですが、、、

Railsアプリをデプロイする際に、AWSで以下のようなWeb3層構成を作りました。↓

f:id:Ryo10Leo:20200115230205p:plain

Webサーバーには「nginx」、アプリケーションサーバーには「puma」「Rails」、データベースには「PostgreSQL」を使っています。

今回は、これと同じ構成にしようと思います。

ということで簡単な完成図はこんな感じ↓

f:id:Ryo10Leo:20200131182635p:plain
Webコンテナ」、「Appコンテナ」、「DBコンテナ」を立ち上げて、それをdocker-composeで管理していく構成で作成していきます。

前提

開発環境の切り替えということを踏まえ、前提は以下となります。

  • ローカルですでにRailsアプリ(作業用ディレクトリなど)を作成済
  • 作業用ファイルのプロジェクトファイル名はmy_appとする
  • Docker,Docker-composeインストール済

ディレクトリ構成

-my_app
  -app
  -db
  -config
    -database.yml
    -puma.rb
  ...
  -Docker
    -app
      -Dockerfile
    -web
      -Dockerfile
      -nginx.conf
    -db
      -Dockerfile
      -initdb.sql
  -docker-compose.yml
  -Gemfile
  -Gemfile.lock
 ...

Railsアプリはすでに作成済(ファイル名my_app)だったので、Railsアプリのプロジェクトファイル内に上記のような構成で、Dockerファイルdocker-compose.yml を追加します。

Dockerファイル内は、それぞれのコンテナのDockerイメージを作成するためのDockerfileと必要な設定ファイル(*.confや*.sql)を追加します。

Railsアプリコンテナの設定

Dockerfile

Docker/app/Dockerfile

#バージョンを指定してRubyイメージを取得
FROM ruby:2.6.3

#必要なライブラリを取得
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs

ENV RAILS_ROOT /my_app

Railsアプリのルートディレクトリ作成
RUN mkdir -p $RAILS_ROOT

WORKDIR $RAILS_ROOT

#ホストのGemfileとGemfile.lockをRailsコンテナにコピー
COPY Gemfile Gemfile

COPY Gemfile.lock Gemfile.lock

#コピーされたGemfileを参照してbundle install
RUN bundle install --jobs 20 --retry 5 --without production

#ホストのアプリケーションディレクトリ内をすべてコンテナにコピー
COPY . .

RUN mkdir -p tmp/sockets

Nginxコンテナの設定

NginxとRailsは「 UNIXソケット通信」を使います。

Dockerfile

Docker/web/Dockerfile

FROM nginx

# インクルード用のディレクトリ内を削除
RUN rm -f /etc/nginx/conf.d/*

# Nginxの設定ファイルをコンテナにコピー
COPY /Docker/web/nginx.conf /etc/nginx/conf.d/my_app.conf

# ビルド完了後にNginxを起動
CMD /usr/sbin/nginx -g 'daemon off;' -c /etc/nginx/nginx.conf

Nginx設定ファイル

Docker/web/nginx.conf

# プロキシ先の指定
# Nginxが受け取ったリクエストをバックエンドのpumaに送信
upstream myapp {
  # ソケット通信したいのでpuma.sockを指定
  server unix:///my_app/tmp/sockets/puma.sock;
}

server {
  listen 80;
  # ドメインもしくはIPを指定
  server_name localhost;

  access_log /var/log/nginx/access.log;
  error_log  /var/log/nginx/error.log;

  # ドキュメントルートの指定
  root /my_app/public;

  client_max_body_size 100m;
  error_page 404             /404.html;
  error_page 505 502 503 504 /500.html;
  try_files  $uri/index.html $uri @my_app;
  keepalive_timeout 5;

  # リバースプロキシ関連の設定
  location @my_app {
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_pass http://myapp;
  }
}

puma.rbの設定

confing/puma.rb

threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
threads threads_count, threads_count
port        ENV.fetch("PORT") { 3000 }
environment ENV.fetch("RAILS_ENV") { "development" }
plugin :tmp_restart

app_root = File.expand_path("../..", __FILE__)
bind "unix://#{app_root}/tmp/sockets/puma.sock"

stdout_redirect "#{app_root}/log/puma.stdout.log", "#{app_root}/log/puma.stderr.log", true

PostgreSQLコンテナの設定

Dockerfile

#postgresqlのイメージ取得
FROM postgres
#初期化で追加したい処理を書いたファイルを追加
COPY Docker/db/initdb.sql /docker-entrypoint-initdb.d/.

initdb.sqlの設定

Docker/db/initdb.sql

ALTER ROLE postgres WITH PASSWORD 'password';
CREATE DATABASE sample_app;

initdb.sqlを必ず初期化で追加する必要なのかはわかりません。笑
なぜ追加したのかは後述。

database.ymlの設定

・
・
development:
  <<: *default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  host: db
  database: sample_app
  username: postgres
  password: password
・
・

コンテナを管理するためのdocker-composeの設定

version: '3'
volumes:
  postgres_data:
  public_data:
  tmp_data:
  log_data:
services:
  app:
    build:
      context: .
      dockerfile: ./Docker/app/Dockerfile
    command: bundle exec puma -C config/puma.rb
    volumes:
      - .:/my_app
      - public_data:/my_app/public
      - tmp_data:/my_app/tmp
      - log_data:/my_app/log
    depends_on:
      - db
  db:
    build:
        context: .
        dockerfile: ./Docker/db/Dockerfile
    environment:
      - POSTGRES_PASSWORD=password
    volumes:
      - postgres_data:/var/lib/postgresql/data
  web:
    build:
      context: .
      dockerfile: ./Docker/web/Dockerfile
    volumes:
      - public_data:/my_app/public
      - tmp_data:/my_app/tmp
    ports:
      - 80:80
    depends_on:
      - app

volumesではappの場合、ホスト側のソースコードの変更を即時に反映させるために、ホストのファイルをマウントしています。

dbではデータを永続化するためのマウントです。

depends_on」はコンテナの作成順序と依存関係を決めるものです。
webコンテナはappコンテナ、appコンテナはdbコンテナって感じです。

docker-composeでイメージをビルド、コンテナ起動

必要な物が揃ったのでコンテナを動かして見ます。

my_app $ docker-compose build

コンテナのイメージをビルドします。

my_app $ docker-compose up -d

コンテナをデタッチで起動

my_app $ docker-compose exec app bin/rails db:migrate 

マイグレーションファイルをコンテナ内のRailsアプリに適用させる

これでブラウザでlocalhostに接続するとアプリが表示されました!

最後にinitdb.sqlを追加した理由

initdb.sqlにはCREATE文のみ追加していたんですが、

Docker/db/initdb.sql

CREATE DATABASE sample_app;

その状態でコンテナを起動させてrails db:migrateなどを行おうとすると以下のようなエラーが出ました。

PG::ConnectionBad: FATAL: password authentication failed for user "postgres"

どうやらデフォルトユーザのpostgreにパスワードを与えれば解決できるようだったので、
DBの初期化時にパスワードを与える処理を実行することでうまくRails側でDBを操作することができました。

Docker/db/initdb.sql

ALTER ROLE postgres WITH PASSWORD 'password';
CREATE DATABASE sample_app;

こうすることでいちいちDBコンテナに入って設定しなくても、すぐにrailsアプリにマイグレーションファイルを反映させることができます。

ってだけのことでした。


Docker簡単なようで難しいな、、、
まだ始めたばっかだし、ゆっくり勉強してこう

dockerコマンドオプションの「-it」を理解する

dockerを学び始めてオプションのところでつまづきました。

dockerのrunコマンドを打つときに、よく「-it」オプションを入力しているのをよく見かけるので今回はこの意味を理解して行こうと思います。

オプションを分割して理解する

「 -i」とは

iは「interactive」の略で、これは簡単に説明すると「双方向のやりとりができるようになること」

ホストのターミナルからの入力がコンテナの標準入力につなげる役割」なのだそう。

この動作を方向でイメージすると。

ホスト(ターミナルとか) → コンテナ

かな?

「-t」とは

tは「tty」の略でした。動作としては、「コンテナの標準出力をホストの標準出力につなげること」らしいです。

なるほど。コンテナをホストで使っている端末デバイス(ターミナルとか)につなげることで、コンテナの入出力がそのデバイスで操作したり結果を表示したりできるんですね

この動作を方向でイメージすると。

コンテナ → ホスト(ターミナルとか)

かな?

「-it」をふわりと理解する

まとめると、-iでターミナルからの入力をコンテナが受け付けて、-tでコンテナの標準出力をホストのターミナルでつなげることで、ホストターミナルからコンテナ内部の操作かができますよーってことでいいのかな?

え?違う?

本番環境でのrails db:migrate:reset

ローカルでDBをリセットするとき

$ rails db:migrate:reset
$ rails db:seed

などの手順を行ってきた。

しかしAWSでデプロイするなどしたときに、本番環境ではやり方が違うらしいです。

今回はそれを調べて試していきます。

参考にさせていただいた記事↓

qiita.com

本番環境でのDBのリセットを実践

railsサーバーは停止させた方が良さそうです。

[ec2-user| ~]$ ps ax | grep rails
[ec2-user| ~]$ kill -9 ~~~~
[ec2-user| ~]$ cd /var/www/rails/myapp
[ec2-user| myapp]$ RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop
[ec2-user| myapp]$ rake db:create RAILS_ENV=production

[ec2-user| myapp]$ rake db:migrate RAILS_ENV=production

[ec2-user| myapp]$ rake db:seed RAILS_ENV=production

成功です!めんどいですね笑

自作railsアプリをデプロイする 番外編 サイトをHTTPS化して通信を暗号化する

ryo10leo.hatenablog.com

上の記事を通して、以下のようなインフラを作成しました。↓

f:id:Ryo10Leo:20200113011243p:plain

EC2の中身はこんな感じです。↓

f:id:Ryo10Leo:20200115230205p:plain

今回はRailsアプリケーションをHTTPSで通信ができるようにして、セキュリティ面を向上させていきたいと思います。

HTTPS通信を軽く理解する

概要

HTTPS(Hypertext Transfer Protocol Secure)通信とは、ホームページなどの閲覧や、クライアントとサーバーと間でのデータをやりとりの際に「通信内容を暗号化して第三者からの盗聴や漏洩、改ざんを防止すること」です。

HTTPとは何が違うの?

HTTPは簡単にいうと、封がされていない手紙のような状態です。そのため誰でも閲覧することができ、改ざんも行えてしまう可能性があります。

HTTPと HTTPSの違いを図で理解する

f:id:Ryo10Leo:20200121141216p:plain

HTTP通信だと通信の内容が筒抜けなのに対して、、、

f:id:Ryo10Leo:20200121141332p:plain

HTTPS通信を使うことで「封がされた手紙のような状態」になり、内容を見られたり改ざんされる可能性が極めて低くなります。

HTTPS通信だけではセキュリティは強化できない!?

例えHTTPSで通信が暗号化されていたとしても、通信先が何かの拍子でネット犯罪者のパソコンやサーバーに誘導されてしまうことがあります。

その場合、結局のところ通信内容は盗まれてしまいます。

f:id:Ryo10Leo:20200121142155p:plain

そのため通信相手が本物であることが重要になっていきます。

そこで利用されるのが「SSL証明書」です。ここでは詳しくは書きませんが、これがあることによって通信相手が本物のサーバーであると保証されるようになっているわけです。

HTTPSをまとめると

HTTPS通信SSL証明書があることで、「通信相手が本物であると保証し、その通信を暗号化すること」でセキュリティを強化できます。

AWSでの実現方法

HTTPS通信の実現方法としては以下の記事を参考にさせていただくと、いくつかパターンがあります。↓

recipe.kc-cloud.jp

このなかで以下の方法が一番手軽にできるパターンなようです。↓

f:id:Ryo10Leo:20200121225146p:plain

簡単に説明すると、クライアントからHTTPS通信でWebサーバーのあるEC2インスタンスに通信しようとするとします。

そうすると、まずクライアントとALB(後述)間では「HTTPS通信」になります。

そして、ALBが「HTTP通信」に変換してからEC2インスタンスに通信内容を送るという動き(その逆も)です。

どうやら「ALB」というサービスを使えばHTTPS通信が実現できるようです。

これが手軽に実現できる理由としては、HTTPS通信に必要なリソースが全部 AWSで手に入れることができるからです。これは便利です。

今回はこちらのパターン使って実現して行こうと思います。

しかし実際にこの環境を作っていく前に、ある程度これから使うAWSサービスや用語などを理解してから作業に移っていきます。

ALBを軽く理解する

概要

ALB(Application Load Balancer)」とは、 AWS が提供するサービスの一つです。
主な役割としては、「複数の Web サーバーにアクセスを分散する」ことです。

ALB には複数の EC2 インスタンスを紐づけることができ、アクセスが来るたびに別のサーバに振り分けることで、負荷分散を実現します。

こう聞くと以下のように思ってしまいました。

「アクセスを分散する?それがHTTPS通信と関係ある?サーバーなんて複数運用してないし!」

しかし使いようによっては以下のようなことにも使えるようです。

HTTPS の終端として ALB を利用

Web サイトを HTTPS 化するときにも、ALB が使えます。

ALB で HTTPS を受け取り、ALB と Web サーバー間は HTTP 通信とする方法です。

このメリットは、Webサーバー側のHTTPS の暗号化処理が不要となり Web サーバーの負荷が下がることです。

なるほど、、、

Webサーバーの余計な負担も減らしてくれるんですね!

AWS Certificate Managerを軽く理解する

概要

AWS Certificate Manager(以下、ACM)とは、AWS上でSSLを利用することのできるサービスです。

普通だとSSLを利用するために、外部からSSL証明書を発行しようとすると手間やコストがかかります。

しかし AWSでは、ACMを利用することで無料で手軽にSSL証明書が発行できるため簡単に暗号化通信を実現することができます。

え?ということは、、、

「それならALB使わなくてEC2インスタンスのセキュリティグループをHTTPSにすればいいんじゃ...?」

しかしそうはいかないようです。↓

ELB,CloudFrontでのみ利用可

ACMの証明書の利用範囲は、ELB,CloudFrontでのみだそうです。

CloudFrontだと以下の記事でSSL証明書を利用しています。↓
ryo10leo.hatenablog.com

したがって、EC2インスタンスやオンプレミスサーバーに紐づけることもできません。

なるほど、、、もう一度、実現パターンの図を見ると、、、

f:id:Ryo10Leo:20200121225146p:plain

この構成なのも納得しました!

作成するものを図で理解する

実現パターンの紹介のところで何度か図を見たのでだいたいわかっていますが、インフラを全体的に見てに作るもの想像しておきます。↓

f:id:Ryo10Leo:20200121232011p:plain

ALBを設置してクライアントとの暗号化通信を担ってもらい、さらにHTTP通信に変換して、インスタンスに通信を送るのでした。

実際にHTTPS通信の設定をしてみる

前提

1. SSL証明書の発行手順は省略

SSL証明書の発行方法については、以前の「Cloud Front」の記事で取りあげたので今回は書略します。

SSL証明書については以下が参考になります。↓
blog.serverworks.co.jp

2.ドメイン取得済

今回はRoute53でドメインの設定も今回行っていきますが、ドメインも取得済でサイトとの紐付けも済んでいるので省略します。

こちらの記事のようにドメインは設定しました。↓
ryo10leo.hatenablog.com

3.VPC内にアベイラビリティーゾーンが異なるサブネットが2つ以上あること

ALBの設定で必須なので、1つしかない場合は作成しておきましょう。

ALBの設定

ダッシュボードから「ロードバランサー」→「ロードバランサーの作成」を選択。

f:id:Ryo10Leo:20200121234814p:plain

ALBの方の「作成」を選択。

f:id:Ryo10Leo:20200121235005p:plain

ALBの名前を入力。

f:id:Ryo10Leo:20200121235118p:plain

リスナーは「HTTPS」にします。ポート番号は自動で切り替わります。

f:id:Ryo10Leo:20200122000554p:plain

アベイラビリティーゾーンで2つ以上のサブネットを選択して、次へ

f:id:Ryo10Leo:20200122000741p:plain

証明書タイプは「ACM から証明書を選択する (推奨)」にして、
証明書の名前で「ACMで発行した証明書」を選択します。

f:id:Ryo10Leo:20200122004011p:plain

セキュリティグループは、「HTTPS」通信を許可します。

f:id:Ryo10Leo:20200122004359p:plain

ターゲットグループ名前を入力して次へ。

f:id:Ryo10Leo:20200122004739p:plain

ターゲットに作成したEC2インスタンスを登録します。
登録するインスタンスにチェックを入れて「登録済みに追加

次の確認画面で問題なければ「作成」をクリック。

f:id:Ryo10Leo:20200122005210p:plain

作成完了です。

Route53の設定

ryo10leo.hatenablog.com

上の記事ではホストゾーンを作成し、すでにAレコードも作成しています。

その状態を前提とし、そのAレコードに変更を加えることで設定は完了します。

Aレコードの編集画面を開きます。

f:id:Ryo10Leo:20200122010109p:plain

エイリアスを「はい」に変更し、エイリアスから先ほど作成したALBのターゲットを選択します。

そして「レコードの保存」。

全ての設定は完了です!!

自作railsアプリをデプロイする 番外編 Cloud Frontを使って画像を高速配信する

ryo10leo.hatenablog.com
ryo10leo.hatenablog.com

上の記事を通して、以下のようなインフラを作成しました。↓

f:id:Ryo10Leo:20200118134334p:plain

EC2の中身はこんな感じです。↓

f:id:Ryo10Leo:20200115230205p:plain

Railsアプリケーションから投稿した画像を「S3」のバケットに保存することができました。

今回は「Cloud Front」を使って、画像配信をより効率的にしていこうと思います。

Cloud Frontを軽く理解する

概要

Cloud Frontとは、高速にコンテンツを配信するサービス。(CDNサービス)

オリジンサーバー( 配信元のサーバー、ここでいうS3)上にあるコンテンツを世界100カ国以上あるエッジロケーションにコピーして、そこから配信する。

簡単にいうと、配信に関する遅延は少ない状態にすることができます。

特徴

・高速

ユーザー(クライアント)から最も近いエッジサーバーからコンテンツを配信するため速い。

・効率的

エッジサーバーで配信をするため、オリジンサーバーへの負荷をかけることなく配信できる。

導入事例

Amazon Prime Video、Huluなど

作成するものを図で理解する

全体から見た設置図はこんな感じ↓

f:id:Ryo10Leo:20200121005208p:plain

動作としては以下のような仕組みで動いています。↓

f:id:Ryo10Leo:20200121010254p:plain

Cloud Frontがキャッシュからコンテンツを配信することで「高速」になり、コンテンツをS3から取得しておくことでクライアントからのリクエストによる「S3の負荷を軽減」することができます。

CloudFrontを設定する

ディストリビューションの作成

コンソールから「 Cloud Front」を選択。

f:id:Ryo10Leo:20200121011541p:plain

Create Distribution」を選択。

f:id:Ryo10Leo:20200121011727p:plain

Webの方で「Get Started」を選択。

ディストリビューションの各種設定をしていきます。

f:id:Ryo10Leo:20200121012154p:plain

Origin Domain Name

S3で作成したキャッシュさせたいバケットを選択。

Origin Path

指定したディレクトリのみキャッシュさせたい場合は指定。
今回は空。

Origin ID

今回はこのまま

Restrict Bucket Access

Cloud Frontのみにアクセスさせたい場合は「Yes」を選択。

Origin Custom Headers

今回は空

下の「Default Cache Behavior Settings」はいじりません。

さらに下の「Distribution Settings」は、
Price Class」が「Use All Edge Locations(Best Performance)」であればOK。

設定が終わったら「Create Distribution

作成完了です。

この時点でS3に保存されているコンテンツはCloudFrontから配信されるようになります。

しかし、Cloud Frontから配信を行う場合、画像のURLのドメインが「Cloud Frontのドメイン」になります。

f:id:Ryo10Leo:20200121013744p:plain

上記の画像のように「~~~.cloudfront.net」がドメインになります。

今回は独自のドメインをCloud Frontに紐付けて配信ができるようにしていきます。

例えばドメインを「static.myapp.com」としたら、そのドメインで「~~~.cloudfront.net」に接続して画像を配信できます。

ドメイン設定のための準備

独自ドメインを紐付けるためには、「SSL証明書」を発行する必要があります。(ここでは詳しく書きません)

なのでSSL証明書をまずは発行していきます。

CloudFront Distributions」の画面から、先ほど作成したディストリビューションの「ID」をクリック。

その先の画面で「Edit」を選択します。

f:id:Ryo10Leo:20200121015043p:plain

移動した画面で「Request or Import a Certificate With ACM」を選択。

ドメインを追加していきます。↓

f:id:Ryo10Leo:20200121015337p:plain

「*.myapp.com」「myapp.com」などのように、サブドメイン全てを許容するものとドメイン本体を追加しておきましょう。↓

f:id:Ryo10Leo:20200121015946p:plain

次へ」を選択。

f:id:Ryo10Leo:20200121020117p:plain

DNS」の検証にチェックして「次へ

タグ」は何もしません。

設定を確認します。↓

f:id:Ryo10Leo:20200121020509p:plain

間違いがなければ「確定とリクエス

続いて検証作業をやっていきます。↓

f:id:Ryo10Leo:20200121021357p:plain

Route 53でのレコード作成」を選択して、出た画面でさらに「作成」をクリック。

以下の画面が出たら「続行」を選択。

f:id:Ryo10Leo:20200121021749p:plain

SSL証明書」が発行されるまで待ちます。

発行された準備は完了です。

Cloud Frontの独自ドメインの設定

発行されたらEdit Distributionの画面に戻ります。↓

f:id:Ryo10Leo:20200121022331p:plain

以下のように設定していきます。

Alternate Domain Names

Cloud Frontで使われる独自のドメインを入力します。
「myapp.com」がドメインの本体だった場合、「static.myapp.com」や「image.myapp.com」などの名前を入力します。(写真ではstatic.~~~にしています)

SSL Certificate

SSLが発行されると、「Custom SSL Certificate」が選択可能になるのでそちらを選択します。

写真の赤丸内の空欄のところをクリックし、表示されたものを選択します。

残りの設定はいじらずに、「Yes,Edit」を選択。

ディストリビューションの設定が完了しました。

これで「static.~~~.com」のドメイン名も、Cloud Frontのドメイン名として使えるよ。という段階まできました。

しかしあとやることが以下のように2つあります。

  1. Route 53で独自ドメインとCloud Frontの紐付け
  2. Rails側のCarirrWaveの設定

終わらせます。

Route 53で独自ドメインとCloud Frontの紐付け

まずはRoute 53の設定です。

「static.~~~.com」の接続先はCloud Frontの「~~~cloudfront.net」だという認識をRoute53で設定します。

Route 53」の「ホストゾーン」を選択して、使っているドメインをクリックします。

レコードセットの作成」を選択。

f:id:Ryo10Leo:20200121024812p:plain

名前

ディストリビューションで設定した独自ドメイン名にします。

タイプ

CNAME -正規名

ディストリビューション作成時に、発行されたCloud Frontのドメイン

ルーティングポリシー

シンプル

作成」を選択。

AWS側の設定はこれにて終了です。

Rails側のCarrierWaveの設定

RailsでCarrierWaveを使用していれば、設定ファイルは以下のようになっているはずです。↓

config/carrier_wave.rb

    CarrierWave.configure do |config|
      config.fog_credentials = {
        # Amazon S3用の設定
        :provider              => 'AWS',
        :region                => ENV['S3_REGION'],   
        :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
        :aws_secret_access_key => ENV['S3_SECRET_KEY']
      }
      config.fog_directory     =  ENV['S3_BUCKET']
    end

ここにAWS側で設定したドメインでコンテンツのURLを表示するように設定します。

以下のように追加すれば完了です。

config/carrier_wave.rb

    CarrierWave.configure do |config|
      config.fog_credentials = {
        # Amazon S3用の設定
        :provider              => 'AWS',
        :region                => ENV['S3_REGION'],   
        :aws_access_key_id     => ENV['S3_ACCESS_KEY'],
        :aws_secret_access_key => ENV['S3_SECRET_KEY']
      }
      config.fog_directory     =  ENV['S3_BUCKET']
     #以下を追加↓
      config.asset_host = 'https://static.~~~.com'
    end

設定できているか確認する

f:id:Ryo10Leo:20200121030639p:plain

ディベロッパーツールで確認したところ、配信したコンテンツのソースが独自ドメインで表示されていることが確認できました。

これで「独自ドメインでCloud Frontにアクセスして S3にあるコンテンツを取得する」ことができました!