カテゴリー
Uncategorized

ギークな人のためのSNS

やっぱり面白いサービスを作りたいという気持ちはずっと持っています。それを実現するために必要な技術、知識を幅広く学んでいきたい、それがずっと持っている仕事へのモチベーションです。

まぁそんなことはさておき、日々なにか面白いサービスを作りたいと考えているわけですけれども、最近考えているのがギークな人のためのSNSってわけです。まぁおおよそ、自分の考えるサービスっていうのは収益性を度外視したものが多いです。

インターネットは飽和状態で、誰かが求めているものはどこかで提供されている世の中になっています。そんな中、これから必要とされるのは万人受けするサービスではなく、誰かにバチっとハマるニッチなサービスなんじゃないかと。

そこで自分もITエンジニアなだけあって、ギークな人間にハマるサービスを考えているわけです。

ギークな人のためのSNSって何っていうと、Webフロントが用意されていなくて、自分でAPIコールしないとアクセスできないサービスみたいのを想像しています。入口が制限されているので、本当に興味のある人しか集まらない、しかもそこにハードルがあるのが、革新的じゃないですか?

んなわけあるかって。

まぁ、現時点では妄想のレベルを抜けていなくて、なんか自分が面白いと思うものを作りたい、それだけのモチベーションです。

サービス創りは楽しい。自分にとってのライフワークです。

無いもの創りたいよね。

カテゴリー
Uncategorized

AWSでWeb3層アーキテクチャを組んだ

今までなんとなくでやっていたことを理解しながらモヤっとした部分をクリアにするために改めて取り組んでみました。

今回はVPCの作成から行い、プライベートサブネットにアプリケーションを展開し、ブラウザからアクセスできるようになることを目標としました。

やることの洗い出し

なにかに取り組むとき、以前はとりあえず手を動かせというノリでやっていましたが、なにか躓くとその度調査に時間がかかり、結果的にものすごい時間がかかってしまいます。なので最近はある程度事前にやることをまとめておくようにしています。するとより効率的に進めることができると思うからです。

  • VPCの作成
    • CIDRは10.1.0.0/16とします。CIDRで気をつけることは、オンプレ等のネットワークとVPCを繋ぐ場合に既存のネットワークで利用できるIPであるかという観点になるそうですが、今回は気にしなくていいでしょう。
    • 利用できるIP数とかの計算は正直よく分かっていませんが、/16なら大体枯渇するようなこともないですし、今回はそこまでIPを使うことも想定していません。
  • サブネットの作成
    • パブリック用にサブネットを1aと1cに配置します。
    • パブリックのサブネットはLBの配置に利用するのと、プライベートサブネットに配置したサーバーがインターネットに出ていくためのNATゲートウェイの配置に利用します。
    • プライベート用にサブネットを1aと1cに配置します。
    • プライベートサブネットに立てるEC2インスタンス内でRailsアプリケーションを動かす想定です。
  • インターネットゲートウェイの作成
    • 作成したVPCのインターネットへの出入り口として、IGWを一つ作成します。パブリック用サブネットを紐付けると、そのサブネットに出入りできるようになる認識です。
  • NATゲートウェイの作成
    • プライベート用サブネットはIGWを紐付けません。つまりインターネットへの出入りが出来ない環境になります。サーバー管理上、出ていく通信は必要なため、この場合パブリックサブネットに配置したNATゲートウェイを経由して出ていくよう設定します。
    • 料金が高いので、1a用のみ作成しました。
  • ルートテーブルの作成
    • ルートテーブルは新たに3つ作成します。
    • パブリック用サブネットのルートテーブルは、パブリックに設置したEC2インスタンスがVPCのインターネットゲートウェイ(IGW)を経由するよう0.0.0.0/0の通信をIGWに向けます。
    • プライベート1a用サブネットのルートテーブルでは、0.0.0.0/0の通信をパブリック1aのNATゲートウェイに向けます。
    • プライベート1c用サブネットのルートテーブルでは、0.0.0.0/0の通信をパブリック1cのNATゲートウェイに向けます。
  • セキュリティグループ
    • 基本はリソースの作成と同時に作成しますが、最終的には下記の通りに作成されています。
    • パブリックに配置するEC2用SG
      • インバウンドはpingを通すためのICMPを開放、SSH接続するための22番ポートを開放。
    • プライベートに配置するEC2用SG
      • インバウンドはpingを通すためのICMPを開放、SSH接続するための22番ポートを開放。
      • LBからのhttp通信を許可するための3000番ポートの開放。(LB→Nginxは3000番で公開するため)
    • ロードバランサ用のSG
      • 80番と443番を開放。80番は443番にリダイレクトするために受け入れています。
    • RDS用のSG
      • RDSの作成と同時に作成されるSG。MySQLなので3306を接続元であるプライベートサブネットのSGのみに絞り開放。

ここまででVPC周りの環境が整いました。

パブリックネットワーク

プライベートネットワーク

次にアプリケーション関係を整理していきます。

  • まずはRailsアプリケーションを動かすために必要なものを整理
    • そういえばRDS必要だなと考えたり
    • EC2の作成
    • Gitインストール
    • Rubyインストール
    • Nginxインストール
    • curlコマンドでPumaおよびNginx経由でアクセスできることの確認
  • RDSの作成
    • 今回はMySQLを使用します。
    • パブリックIPは設定しません。パブリックアクセス不可。
  • ブラウザからのアクセスに必要なものを整理
    • ドメインの用意
    • SSL証明書はACMで取得(簡単だし)
    • ネームサーバーはRoute53を使う
    • Route53からLBに向けてAレコード作成
    • LBの準備
      • EC2インスタンスをターゲットグループに追加
      • ヘルスチェックの確認

諸々設定してブラウザからのアクセスができるようになりました!しかし、問題を孕んでいました。

ブラウザでアクセスすると、レスポンスに10秒かかるときと即帰ってくるときがある問題

これにはハマりました。自分では解決できず2,3日経過したとことでチームメンバー(取引先ですが…)の方に聞いてみたところ原因が発覚…。

結論、LBはパブリックサブネットを2つ指定する必要がありますが、片方プライベートサブネットを指定していたためでした。

10秒かかっていた理由は、DNSからプライベートのサブネットのほうにトラフィックが流れ、レスポンスが帰ってこないのでパブリックのサブネットにトラフィックがリダイレクト(?)されるようです。運良くパブリックのサブネットにトラフィックが流れたときは即時帰ってきていたんですね。

まとめ!

躓きポイントはあったものの、なんとか3層アーキテクチャのインフラ環境を構築することができました。初めてまともにVPCを触ったので、調べつつではありましたが、CIDRであったりサブネット、ルートテーブルの概念の理解が進み、もやもやを払拭できた気がします。

あと、割とインフラ方面の概念の理解が思ったより出来ていることに驚きました。インフラ方面でも手応えを感じられたのは自信にも繋がった気がします。

やっぱりマインクラフトサーバを運用していただけあるかも知れません。笑

今度はECSにデプロイを試してみようと思います。
その後はサーバレス関連かなあ。

カテゴリー
Uncategorized

Gatsbyチュートリアルやってみた

マークダウンで文章を書けるのが結構好きで、普段使いのメモアプリはInkdropを使っています。

このサイトはWordpressなんですが、マークダウンで記事を書いていきたいのと、新しいこと学びたいってことでGatsby使ってブログサイトを構築してみました。

チュートリアルざっとやっただけで無機質だけど、今後デザイン入れたりプラグインいれてカスタマイズしていきたいです。

https://blog.hazm.jp/

カテゴリー
Uncategorized

Rails7におけるdata-confirm

Railsではフォームやリンクを生成するヘルパーを使う時、共通して data-confrm という属性をつけると、確認アラートを表示することができました。しかし、Rails7においてはこれだけでは動きませんでした。

そもそも、どのようにこの動作を実現しているかといえば、 rails-ujs というgem(中身はjs)のおかげです。rails-ujs はrails5.1.0以降、rails本体に取り込まれました。

https://github.com/rails/rails/tree/main/actionview/app/assets/javascripts

rails-ujs の役割として、confirmの制御、非getリクエストの実行、非同期リクエストの実行、サブミットボタンの非活性制御を行うライブラリとなっています。railsの便利機能を実現するためのフロントエンドで動く小さなjavascriptです。

Rails7においては、このgemはデフォルトでインストールされていません。つまり rails-ujs を単体でインストールすれば動くということです。

npm install @rails/ujs --save

実態としては小さなjsであるため、npm経由でインストール可能です。このjsをassetsパイプライン経由などで読み込ませることで今までの開発体験を取り戻すことができます。

しかし、なぜRails7ではデフォルトでインストールされていないか、そこを知っておく必要があるでしょう。

Rails7では、新しいフロントエンドフレームワークとして、TurboとStimlus(2つ合わせてhotwiredと呼ばれます)が導入されました。Turbo自体は rails-ujs の置き換えという立場ではありませんが、rails-ujs の機能を一部取り込んでいます。それが今回、data-confirm が動作しな理由の一つです。

Turboを前提とした確認ダイアログの実装

Turboを使用する場合、data-confirm の代わりに data-turbo-confirm を使用します。

form_tag "#action", method: "post" ,data: { turbo_confirm: "送信しますか?" }

そしてこの属性は常にformタグに設定する必要があります。これが一癖。例えば、formを生成するbutton_toの場合は、下記のように指定する必要があります。

= button_to "削除", "#button", method: "delete", form: { data: { turbo_confirm: "削除しますか?" } }

link_toはどうなるの?

こちらも data-turbo_confirm に置き換えです。

data-turbo_method を指定すると、data-turbo_confirm が有効になるようです。

= link_to "削除リンク", "#button",  data: { turbo_method: "get", turbo_confirm: "削除しますか?" }
カテゴリー
Uncategorized

[css/js]bundling-railsを使用したRails7プロジェクトをDockerCompose化する

[css/js]bundling-railsの使用を前提としているため、開発環境のサーバー立ち上げは ./bin/dev を使用します。今回の環境の構成は下記の通りです。

Rails 7.0.3.3 / Ruby 3.0.0 / Bundler 2.3.9 / Nodejs 12 / MySQL 8.0

FROM ruby:3.0.0-buster

# for nodejs
RUN curl -fsSL https://deb.nodesource.com/setup_12.x | bash -

# for yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN apt-get update -qq && apt-get install -y build-essential nodejs yarn vim zlib1g-dev liblzma-dev patch

RUN node -v
RUN yarn -v

RUN gem uninstall bundler
RUN gem install bundler -v 2.3.9

RUN mkdir /tmp/app-init
WORKDIR /tmp/app-init
ADD Gemfile .
ADD Gemfile.lock .
RUN bundle install

RUN mkdir /app
WORKDIR /app
services:
  mysql:
    image: mysql:8.0
    restart: always
    cap_add:
      - SYS_NICE
    environment:
      TZ: Asia/Tokyo
      MYSQL_ROOT_PASSWORD: password
    volumes:
      - ./mysql-confd:/etc/mysql/conf.d
  rails:
    build: .
    tty: true
    command: >
      sh -c "
        rails db:create && rails db:migrate &&
        rm -f tmp/pids/server.pid &&
        yarn install
        ./bin/dev
      "
    environment:
      TZ: Asia/Tokyo
      NODE_ENV: development
      RAILS_ENV: development
    volumes:
      - .:/app
    ports:
      - "3000:3000"
    depends_on:
      - mysql
[mysqld]
default_authentication_plugin=mysql_native_password
web: bin/rails server -p 3000 -b 0.0.0.0
js: yarn build --watch
css: yarn build:css --watch
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  host: mysql

Dockerfileの内容

ruby-3.0.0の公式イメージをベースとして、nodejs12、yarn、その他サーバーライブラリのインストールを行い、bundle installを実行してイメージ化しておきます。

こうするとbundle installをキャッシュしてくれるので起動が早いはずです。

docker-compose.ymlの内容

mysql8.0コンテナと、railsコンテナの2つを実行します。

mysqlコンテナは、mysql-confd/default_authentication.cnf に設置した設定ファイルの入ったディレクトリをコンテナ内にマウントします。設定ファイルは、パスワード認証を許可する設定が書かれています。

railsコンテナは、db:create db:migrate を実行したのち、./bin/dev を実行します。このとき、tty: true オプションをつけなれけばならないようです。

Procfile.devの内容

Dockerコンテナの外からHTTPアクセスできるようにする必要があります。Railsサーバーの起動オプションに -b 0.0.0.0 を追加することで、127.0.0.1/localhost以外からのアクセスを許可します。

config/database.yml

コンテナtoコンテナのホスト名はdocker-compose.ymlで定義した名前でアクセスできるようになります。hostmysqlを指定します。

コンテナの起動

docker-compose build
docker-compose up

Gemfileの追加があったら、buildとupをやり直します。(startではボリュームが作り直されない)

カテゴリー
Uncategorized

cssbundling-railsのtailwindcssがslimで効かない

時間を相当無駄にした。

tailwind.config.jsのcontentを確認してみましょう。

'./app/views/**/*.html.erb'
↓
'./app/views/**/*.html.slim'

言い訳だけど、知ってたんです。本当に。
知ってたのに思い出せなかった。

カテゴリー
Uncategorized

Rails7時代のJS/CSSバンドルの新常識

Rails7では、jsをバンドルせずにimportmapでjsを直接読み込む方式がデフォルトとなりました。しかしwebpack等でのバンドル方式の選択肢を完全に失ったわけではありません。特定のターゲット(ES5など)にトランスパイルすることができます。レガシーブラウザをサポートする要件下ではバンドル方式を選択することもあるでしょう。そこで来たるRails7時代に、我々アプリケーションエンジニアにとってどのような手段があるのか、Railsの基礎知識をアップデートしていくのがこの記事の目的になります。早速始めましょう!

JSの読み込ませ方

かつてのRailsではSprocketsでバンドルして単一のjsファイルとして配信する方法が常識でした。Sprocketsはトランスパイルはしないものの、minifyする仕組みを持っていました。

Rails7からは、importmapまたはwebpack/esbuild/rollupでのバンドルの方法を採用できます。rails newする際にオプションを明示しない限り、デフォルトでimportmapがが使用されます。railsとimportmapの組み合わせはimportmap-railsというGemが受け持ちます。もしくは、-j [webpack/esbuild/rollup]を指定すると、jsbundling-railsというGemがよしなにしてくれます。

importmapはトランスパイルを行いませんので、最新のブラウザをターゲットとする場合以外には採用が難しいかもしれません。その場合はwebpackやesbuildなどのバンドラーでES6やES5などをターゲートとしてトランスパイルしたものを配信する式を採用するのが良いでしょう。

importmap-rails

importmapを使用するには、bundlerでimportmap-railsをインストールし、コマンドを実行します。

./bin/rails importmap:install

サードパーティのESModuleを追加するには、コマンドを実行します。Railsの場合、app/javascript/application.jsもまた、sprocketsでビルドされたものをimportmap経由でロードされます。

./bin/importmap pin react react-dom

レイアウトやビューファイルではjavascript_importmap_tagsヘルパーがjsを呼び出す起点となります。

<%= javascript_importmap_tags %>

jsbundling-rails

jsbundlingを使用するには、bundlerでjsbundling-railsをインストールし、インストールコマンドを実行します。

./bin/rails javascript:install:[esbuild|rollup|webpack]

注)npx -vが7.1以下の場合はpackage.jsonにbuild設定が自動で追加されないので、手動で追記する必要があります。

"scripts": {
    "build": "esbuild app/javascript/*.* --bundle --outdir=app/assets/builds"
}

サードパーティのESModuleを追加するには、yarnやnpmコマンドでプロジェクトルートにインストールしたライブラリをimportして使用します。

yarn add react react-dom

開発中には、./bin/devコマンドを使用することで、RailsサーバーとJSビルドウォッチャーの両方を同時に起動できます。cssbundling-railsを使用している場合、cssビルドウォッチャーも同時に起動します。事前にgemをインストールしておきましょう。(foremanがなければ自動でgemがインストールされます)

gem install foreman

esbuildの場合、デフォルトのターゲットはesnext(es6)です。変更したい場合は、package.jsonのbuildコマンドでtargetを指定できます。

webpackの場合はwebpack.config.json、rollupの場合はrollup.config.jsでビルド設定を行うことができます。

esbuildでのトランスパイルは、ターゲットがデフォルトでesnextのため、es6をターゲットにしておくとよさそうでした。なお、esbuildではes5向けのトランスパイルをサポートしていないため、IEをターゲットとする場合はwebpackを使うとよさそうです。

"build": "esbuild app/javascript/*.* --target=es6 --bundle --outdir=app/assets/builds",

CSSの読み込ませ方

使い慣れたSprocketsのアセットパイプラインを使用できます。Rails7では加えてバンドルの必要なTailwind CSSBootstrapBulmaPostCSS,Dart Sassなどを組み込むためにcssbundling-railsというGemを使用できます。cssbundling-railsでバンドルされたCSSはassets/buildsに出力され、アセットパイプラインを介して配信されます。

cssbundling-railsを使用するには、bundlerでcssbundling-railsをインストールし、コマンドを実行します。

./bin/rails css:install:[tailwind|bootstrap|bulma|postcss|sass]

注)npx -vが7.1以下の場合はpackage.jsonにbuild設定が自動で追加されないので、手動で追記する必要があります。

"scripts": {
    "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css"
}

開発中には、./bin/devコマンドを使用することで、RailsサーバーとCSSビルドウォッチャーの両方を同時に起動できます。jsbundling-railsを使用している場合、jsビルドウォッチャーも同時に起動します。事前にforeman gemをインストールしておきましょう。(foremanがなければ自動でgemがインストールされます)

tailwindcssを導入し@importを使用して別のcssファイルをバンドルするには、postcss-importyarn addします。詳細はこちら

カテゴリー
Uncategorized

Rails7 rails/importmap-railsの概要

Rails7からWebpack等のBundlerを使用しないアプリケーション開発手法が主軸となるようです。その中でESModuleをブラウザ上で直接フェッチするような形がとられています。ImportMapについて学びましょう!

ImportMapとは

ImportMapとは、import句でモジュールを取得する際の取得先URLを制御する仕組みです。webpackやrollupなどのバンドラーを使用せずにESModuleを読み込むことのできるブラウザ環境で使用します。

import moment from "moment";
import { partition } from "lodash";

バンドラーを使用しない環境では上記のような記載をした場合エラーをスローします。それを回避するにはURLを指定する必要があるでしょう。

import moment from "/node_modules/moment/src/moment.js";
import { partition } from "/node_modules/lodash-es/lodash.js";

そこでImportMapを提供することで、モジュール名を指定するだけでどこから取得すべきかをマッピングすることができます。

<script type="importmap">
{
  "imports": {
    "moment": "/node_modules/moment/src/moment.js",
    "lodash": "/node_modules/lodash-es/lodash.js"
  }
}
</script>

WICG/import-maps

Rails7におけるImportMapの利用

Rails7ではconfig/importmap.rbの設定ファイルを介してImportMapの設定を行います。

Rails.application.importmap do
end

ImportMapは<%= javascript_importmap_tags%>を介して<head>タグに挿入されます。

<%= javascript_importmap_tags%>

これにより、application.jsなどのjavascriptからモジュールを参照できるようになります。application.jsもまた、これを介してインポートされます。

ESModule非対応ブラウザの場合

Rails7ではES Module Shimsをインポートします。これによりESModule非対応ブラウザの場合でも動作が保証されます。

Rails7でのimportmapの使い方

インストールコマンドでimportmapを使う準備を行います。

$ ./bin/rails importmap:install

Add Importmap include tags in application layout
      insert  app/views/layouts/application.html.erb
Create application.js module as entrypoint
      create  app/javascript/application.js
Ensure JavaScript files are in the Sprocket manifest
      append  app/assets/config/manifest.js
Configure importmap paths in config/importmap.rb
      create  config/importmap.rb
Copying binstub
      create  bin/importmap

例えばvueを使いたい場合、importmap pinコマンドでマッピングします。自動的に2.6.14がマッピングされました。

$ ./bin/importmap pin vue
Pinning "vue" to https://ga.jspm.io/npm:vue@2.6.14/dist/vue.runtime.esm.js

しかし、自動でマッピングされるvueはブラウザでは動作しないものでしたので、browser esm互換のものへ手動で書き換えました。

pin "application"
pin "vue", to: "https://ga.jspm.io/npm:vue@2.6.14/dist/dist/vue.esm.browser.js"

実際に動かしてみる。

import Vue from 'vue'
var vm = new Vue({
  el: '#example',
  data: {
    domain: 'HAZM.JP'
  }
})
<div id="example">
  <h1>{{domain}}</h1>
</div>

出力結果

importmapがhead内に提供され、vueがマッピングされた場所からインポートされたこと、そして動作を確認できました。

まとめ

HTTP/2とES6がほとんどのブラウザで標準サポートされるようになった現代では、トランスパイルをせずともあらゆるJSを動作させることができました。importmapはその一端を担います。

カテゴリー
Uncategorized

別PCで動作するRailsアプリにlocalhost:3000でアクセスしたい

Webカメラとマイクを利用するアプリケーションの開発で複数の端末で動作検証をしたいと思いました。

しかし、WebカメラとマイクはlocalhostまたはSSL化されたサイトでしか動作できないというブラウザ仕様があるため、localhost:3000で別のPCで動作するRailsアプリケーションにアクセスできるようにする必要があります。

ローカルで起動したRailsアプリケーションにアクセスする分には、localhost:3000でアクセス可能ですが、/etc/hostsにドメインを定義する方法では、localhostでアクセスする事ができません。

では、どうすれば良いか。Nginxでプロキシすることで、それが可能です。

Railsアプリケーションを起動しているPC:Ubuntu
アクセスしたいPC:Mac

まず、接続元であるPCにnginxをインストールし、nginxの設定ファイルでserverを定義します。設定内容はシンプルで、server_nameがlocalhostである3000番ポートをリッスンするserverで、別PCのローカルIPへproxyするというものです。

server {
    listen       3000;
    server_name  localhost;

    proxy_set_header    X-Forwarded-Host $http_host;

    location / {
        proxy_pass http://192.168.1.12:3000;
    }
}

注意点として、RailsはCSRF対策の仕組みがデフォルトで有効なため、適宜必要なヘッダを設定してあげる必要があります。

# プロキシ先にHostを知らせます。この設定が無いと、originと一致しないためCSRFチェックがエラーになります。
proxy_set_header    X-Forwarded-Host $http_host;

以上!

カテゴリー
Uncategorized

Daily AWS

今後、エンジニアとして社会で活躍していくには、より広い視野を持ってシステム全体を俯瞰して見る必要があると考え、インフラ面にも手を広げていこうと考えています。

業務ではAWSを使っていて、部分的な改修・調整作業は稀に行う事があるものの、必要な情報を集めて、その場凌ぎの対応することが多いです。また、システムの全体図は頭に入っているけれどそれらがどのような設定で組み合わさっているかまでは把握できていない事があります。

インフラ面の知識を叩き込めていないが故に、その領域の提案ができない事がもどかしい、とも感じます。

ということで、AWSの各種サービスがどのようなソリューションを提供し、何ができるのかを1つづつ知って行こう、という目的を持って、Daily AWSというTwitterアカウントを作りました。

このアカウントの目的は、AWSの各種サービスで何が実現できるのかを知ることです。それによって、プログラム側だけで解決しようとするのではなく、インフラ側で解決するという新しい提案ができるようになることを最終目的としています。

ツイートする前にやっていることは、ドキュメントを読み込んで、インスタンスを立ててみることです。一通り触ってみると、頭に入ってきて自分なりに解釈できるからです。本当は一つ一つの設定の挙動を検証したり、というところまでできれば良いですが、時間的な制約が大きいのと、毎日継続することを第2目標としているため、一定の解釈を持ってツイートに流しています。

継続できるようにがんばります。笑