Node.js+Serverless+AWSでお手軽サーバレスアプリを作る: 事前準備

Serverlessフレームワークをもちると、手軽にサーバレスアプリを構築することができます。 今回はNode.js v12 と AWSをもちいてサーバレスアプリを作る事前準備について説明します。

The Serverless Application Framework | Serverless.com

Serverlessアプリケーションの作成

AWS上にNode.jsのServerlessアプリケーションを作成するには以下のコマンドで作成できます。

mkdir sls-sample
$ npm install -g serverless
$ sls create --template aws-nodejs

handler.js, serverless.yml の2つが作成されます。

API経由でLambda関数が動作するようにする

初期の設定だとLambda関数とHttpイベントが紐付いていないので serverless.ymlを開いてコメントアウトされているHttpイベントの部分を以下の様にコメント外します。

functions:
  hello:
    handler: handler.hello
#    The following are a few example events you can configure
#    NOTE: Please make sure to change your handler code to work with those events
#    Check the event documentation for details
    events:
      - http:
          path: users/create
          method: get

AWS認証情報の設定

AWSのWebコンソール上でIAMユーザを作成してAdministrator Accessのポリシーをアタッチします。

AWS アカウントでの IAM ユーザーの作成 - AWS Identity and Access Management

次に、以下のリンクを参考にローカル環境にAWSの認証情報を設定します。

docs.aws.amazon.com

これでローカル環境からすべてのAWSサービスの操作ができるようになります。

デプロイ

以下のコマンドを実行することで、Serverlessアプリケーションがデプロイされます。

$ sls deploy

ServerlessアプリケーションはAWSではCloudFormationとしてデプロイされます。

sls deployの出力結果は以下のようになっています。

Service Information
service: sls-sample
stage: dev
region: us-east-1
stack: sls-sample-dev
resources: 12
api keys:
  None
endpoints:
  GET - https://xxx.execute-api.us-east-1.amazonaws.com/dev/users/create
functions:
  hello: sls-sample-dev-hello
layers:
  None

ここに表示されている GET - https://xxx.execute-api.us-east-1.amazonaws.com/dev/users/create を実際にリクエストしてみます。

$ curl https://xxx.execute-api.us-east-1.amazonaws.com/dev/users/create
{
  "message": "Go Serverless v1.0! Your function executed successfully!",
  "input": {
    "resource": "/users/create",
    "path": "/users/create",
    "httpMethod": "GET",
    "headers": {
...

handler.jsの内容を実行することができました。

Adobe XDをつかってFlutterアプリ実装にやさしいデザインを作成する

Flutterでは、iOS/Androidクロスプラットフォームアプリを簡単に実装することができます。 ただし、デザインによっては実装コストが高まってしまうので、Adobe XDを使って実装するエンジニアにむけて心がけていること、それはFlutterで簡単に実装できるUIをデザインすることです。

Adobe XDの使い方をそもそも勉強する

そもそもUI/UXデザインの基礎については今回は書きませんが、もしAdobe XDのような、UI/UXデザインソフトを利用したことがない場合はAdobe XD公式のチュートリアル のクイックスタートの項目だけでも目をとおしておくと良いかと思います。

f:id:fly1tkg:20201129234258p:plain

特にリピートグリットはリストビューのような反復するUIを簡単に作成することができるため、必ず覚えると良いと思います。

XDことはじめStep1:まずはここから!「ワイヤーフレームを作ってみよう」 |

Material DesignのUIキットを入手する

f:id:fly1tkg:20201129234625p:plain

Flutterでは基本的にはMaterial DesignベースのUIが簡単に実装できるように設計されています。 左上のハンバーガーメニューの UIキットを入手 > マテリアルデザイン からダウンロードすることができます。

f:id:fly1tkg:20201130003254p:plain

基本的にはUIキットのコンポーネントから利用するようにして、独自のコンポーネントをつくって実装コストを高めないようにします。

Material Designに定義があっても、Flutterではコンポーネントが準備されていない場合もあります。その場合はデザインの方を修正するようにしています。

デザインに対応するPackageがあるかどうかは常にエンジニアに確認しながらデザインする

f:id:fly1tkg:20201130000609p:plain

サードパーティのパッケージを利用するようなUI/UXデザインにする際も、実装コストが高まりすぎないように常にエンジニアに確認しながら実装するようにします。

Adobe XD to Flutterは利用しないのか

Adobe XD to Flutterというプラグインを使うと、Flutter向けのコードが自動生成できますが、あまり自動生成系を信用していないのと、基本的には実装が楽になるようにデザインしているので利用していません。

うまく活用している事例知りたいです。

Heroku+Rails Netlify+Next.jsを中心としたWebアプリ構成

Heroku+Rails Netlify+Next.jsを中心とした構成でWebアプリを構築したのでメモです。

構成図

f:id:fly1tkg:20200616162313p:plain

今回利用した技術スタック

SPAアプリ

Next.jsはReactを使ってSPAを作成するシンプルなフレームワークで、今回は利用しませんが洗練されたSSRも対応しています。

API + 管理画面

PaaSについて

Netlify

フロントエンドはNetlify を利用しました。 以下の機能を容易に設定することができるためです。

  • フロントエンドのビルド機能
  • CDN配信
  • SSL設定
  • Githubとの連携 (特定ブランチへのプッシュ、Pull Request時にプレビューアプリのデプロイ)
  • パスワード設定機能(stg環境等を一部の人に公開するため)

Heroku

バックエンドのPaaSはHerokuを利用しています。 Railsのデプロイ先としては歴史があり、こちらも容易に利用できるCD機能、CLIAPIがあらかじめ揃っていることが強みです。

もしセキュリティレベル的に1段階上げたい要望がある場合、Private SpaceというVPCで動作させることができるプランもあります。

インフラやパイプラインの更新もherokuのサイクルに乗っかることで保守面のコストを下げることを狙いとしています。

またAdd-onで様々なサービスと連携が可能で、今回はメール配信に mailgun 、DNS管理にPointDNSを利用しています。

Netlify + Next.js ビルド設定

Next.jsには以下のscriptがあります。

  • next build プロダクションビルド
  • next export 静的サイトとして out フォルダに書き出し

package.jsonにnpm scriptとして以下のように設定し

"scripts": {
  ...
  "build": "next build",
  "export": "next export"
}

Netlifyのbuild設定は以下のようにしています。

f:id:fly1tkg:20200616155455p:plain

APIサーバのBASE_URLなどはNetlifyのビルドの環境変数にセットし、 webpackのDefinePluginを用いて、静的ビルド時に注入するようにしています。

Next.js では next.config.js で webpackの設定を変更することができます。

const webpack = require("webpack");

const devApiUrl = 'https://example.com';

module.exports = {   
  webpack: (config) => {
    config.plugins.push(
      new webpack.DefinePlugin({
        API_BASE_URL: JSON.stringify(process.env.API_BASE_URL || devApiUrl),
        SENTRY_DSN: JSON.stringify(process.env.SENTRY_DSN),
        SENTRY_ENVIRONMENT: JSON.stringify(process.env.SENTRY_ENVIRONMENT),
      });
      return config;
  }
};

Heroku デプロイ設定

RailsのパイプラインはDocker Containerを用いず、Herokuのスタックを利用しています。(これは好みかもしれません) デフォルトのスタックを利用することで、特に他のAPMなどを導入せずにRubyのmetricsを見ることができます。

devcenter.heroku.com

現在見れるものとしては以下です。

  • Puma Pool Usage
  • Free Memory Slots
  • Heap Objects Count

導入方法も非常にかんたんで、Heroku側は Metricsのページの設定でRuby Language MetricsをONにして、heroku/metrics のbuildpackを追加するだけです。

f:id:fly1tkg:20200616161000p:plain

Rails側は Barners gemをインストールする必要があります。

gem "barners"

Heroku Add-on設定

Heroku Add-onは追加すると、基本的に環境変数にサービスを利用するための認証情報が追加されます。 (例: Heroku Postgresであれば DATABASE_URL に接続情報が入っている)

このあたりは適当に追加して、アプリのコードで環境変数から設定値を受け取れるように設定しました。

利用したAdd-Onは以下です。

  • Heroku Postgres
  • PointDNS DNSレコード管理(Route53、Google Domainsの代わりとして)
  • Mailgun メール送信

その他利用サービス

webpack2->4とbabel6->7のアップグレード作業ログ

2017年から運用されているNode.jsのプロジェクトのwebpackとbabelのバージョンアップ作業をしたので、作業ログを残しておきます。

アップグレードしたバージョン

  • babel 6 -> 7
  • webpack 2 -> 4

Babelのアップグレード

Babel6 -> 7 は各パッケージ名が変更されていて、パッケージアップグレードで対応できません。 babel-upgrade をつかって、package.jsonと .babelrc をアップグレードすることができます。

npx babel-upgrade --write

いきなり最新のbabelの設定に生まれ変わります。

diff --git a/.babelrc b/.babelrc
index 6cfcd56..bfb063f 100644
--- a/.babelrc
+++ b/.babelrc
@@ -1,4 +1,25 @@
 {
-  "plugins": ["transform-runtime", "babel-plugin-transform-flow-strip-types"],
-  "presets": ["es2015", "stage-3"]
+  "presets": [
+    "@babel/preset-env"
+  ],
+  "plugins": [
+    "@babel/plugin-transform-runtime",
+    "@babel/plugin-transform-flow-strip-types",
+    ["@babel/plugin-proposal-class-properties", { "loose": true }],
+    ["@babel/plugin-proposal-decorators", { "regacy": true }],
+    "@babel/plugin-proposal-do-expressions",
+    "@babel/plugin-proposal-export-default-from",
+    "@babel/plugin-proposal-export-namespace-from",
+    "@babel/plugin-proposal-function-bind",
+    "@babel/plugin-proposal-function-sent",
+    "@babel/plugin-proposal-json-strings",
+    "@babel/plugin-proposal-logical-assignment-operators",
+    "@babel/plugin-proposal-nullish-coalescing-operator",
+    "@babel/plugin-proposal-numeric-separator",
+    "@babel/plugin-proposal-optional-chaining",
+    "@babel/plugin-proposal-pipeline-operator",
+    "@babel/plugin-proposal-throw-expressions",
+    "@babel/plugin-syntax-dynamic-import",
+    "@babel/plugin-syntax-import-meta"
+  ]
 }

(使ってるところはなさそうだけど) @babel/plugin-proposal-decorators@babel/plugin-proposal-pipeline-operator だけ、そのままでは動かなそうだったのでオプションを追加

diff --git a/.babelrc b/.babelrc
index bfb063f3..2a985dd4 100644
--- a/.babelrc
+++ b/.babelrc
@@ -6,7 +6,7 @@
     "@babel/plugin-transform-runtime", 
     "@babel/plugin-transform-flow-strip-types",
     ["@babel/plugin-proposal-class-properties", { "loose": true }],
-    ["@babel/plugin-proposal-decorators", { "regacy": true }],
+    ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true, "regacy": true }],
     "@babel/plugin-proposal-do-expressions",
     "@babel/plugin-proposal-export-default-from",
     "@babel/plugin-proposal-export-namespace-from",
@@ -17,7 +17,7 @@
     "@babel/plugin-proposal-nullish-coalescing-operator",
     "@babel/plugin-proposal-numeric-separator",
     "@babel/plugin-proposal-optional-chaining",
-    "@babel/plugin-proposal-pipeline-operator",
+    ["@babel/plugin-proposal-pipeline-operator", { "proposal": "minimal" }],
     "@babel/plugin-proposal-throw-expressions",
     "@babel/plugin-syntax-dynamic-import",
     "@babel/plugin-syntax-import-meta"

すべての箇所のrequireをimportに書き換えられたら良かったのですが、歴史のあるプロジェクトではそういうわけにもいきませんでした。 歴史的経緯でexport defaultに指定されているものをrequireする部分は以下のようにしました。

const lib = require('./lib.js').default // .defalutを追加

webpack.config.jsの変更

webpackを2 -> 4 に変更するので、当然のようにwebpack.config.js の設定が違います。

一旦webpack4で新規プロジェクトをつくって、ひとつひとつ同等の設定をしていきました。

はまったポイントとしては、webpack4では process.env.NODE_ENVの環境変数が最適化で文字列に置換されてしまうので。 実行時に環境変数から与えたいので以下のように設定しました。

module.exports = {
...
  optimization: {
    nodeEnv: false // process.env.NODE_ENVを文字列に置換しない
  },
...

なるべく CJSのimportのスタイルに治す

requireを全文検索してimportのスタイルに変更

Windows+WSL2+Docker+VSCodeでdocker開発環境を作る

最近 Thinkpad X1 Extreamを購入しました。

開発業務は基本Ubuntuでやっていきたいのですが、Windowsでしか動かないアプリたち(Adobe、MSOfficeなど)も使っていきたく。ホストOSはWindows 10 Home, 開発用としてWLS2 (Windows subsystem for Linux 2) を利用していくことにしました。

はじめはVirtualBoxを準備しようかと思いましたが、WSL2はプレビューながら業務で使えると同僚からちょっと聞いていたので、今回はこちらにしてみました。

また、最近VSCodeのRemote拡張がイケてるので合わせて導入していきます。

WSL2のインストール

WSL 2 のインストール | Microsoft Docs を参考にやってきます。

Insider Programに参加

Windows Insider Program | Get the latest Windows features に参加して、OSのアップデートをします。 僕はここでアップデートし忘れてはまりました。

https://i.imgur.com/rA6BLmh.png

Ubuntuのインストール

Windows StoreからUbuntuをインストールしました。

Windows Storeアプリを起動して、Ubuntu と検索してくれば出てきます。

f:id:fly1tkg:20200119195952p:plain

WSLを有効にする

Powershellを管理者権限で実行(右クリックで選べる)してWindows Subsystem for Linux仮想マシンプラットフォームのオプションコンポーネントの両方がインストールされていることを確認します。

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

実行後再起動します

WSL2に変更する

Powershellで以下のコマンドを実行しました。

wsl --set-version Ubuntu 2

ここまでスタートメニューからUbuntuをえらんで起動できます。

Dockerのインストール

ここから先はUbuntuのコンソールで実施していきます。

docs.docker.com

Ubuntuにdockerをインストールして、自分のユーザをdockerグループに入れます。

$ curl -fsSL https://get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh
$ sudo usermod -aG docker {your-user}

docker起動

$ sudo cgroupfs-mount
$ sudo service docker start

動作確認としていつものdockerのhelloworldを実施してみました。

https://hub.docker.com/_/hello-world

$ docker run --rm hello-world

VSCodeのインストール

Visual Studio Code - Code Editing. Redefined からダウンロードして、インストール。

インストール後、拡張機能 ms-vscode-remote.remote-wsl を入れます。

https://i.imgur.com/vyY99dI.png

インストール後、Ubuntuのコンソールで code コマンドが使えるようになっているので、任意のプロジェクトフォルダで以下コマンドを実行すると、Windows側でVSCodeが開き、プロジェクトフォルダ内のファイルを編集できます。(便利、、!)

$ code .

ファイル共有

WSL -> Windowsの参照は /mnt/c 配下がWindowsのCドライブがマウントされているので、WSL上からWindowsのファイルを読み書きすることができる。

逆に Windows -> WSL の参照は\wsl$ というネットワークドライブがアクセスできる(Exploreでここに移動すると見られる) ので、クイックアクセスにピン止めしておいた

そのほかやったこと

Windowsのビーブ音を消す

なんか、tabで補完するたびにビープ音が出るので、OSの設定で消しました。

qiita.com

.inputrc で制御できるみたいなのですが、sshした先とかでビープ音なっちゃうし、そもそもWindowsのビープ音腹立つのでいったんこの設定で様子見ます。

WSL向けのターミナルを入れる

複数Windowで開くなどが標準のターミナルだとできないようだったのでwslttyというターミナルを導入しました。

github.com

IoTエンジニア目指してラズパイを触り始めたメモ

突然ラズパイ(ラズベリーパイ)を使って電子工作をしてIoTエンジニア目指せる気がした、年末休暇を使って一通り勉強してみました。

ラズパイのセットアップとLチカ(LED点滅という電子工作におけるHello World的なもの)までのまとめです。

準備したもの

最近、忙しいので全部秋月電子通商の通販で取り寄せました。便利!

  • OTGケーブルは持ってたのでので実際には購入していません
名称 通販コード リンク
カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる S-10851 【書籍】カラー図解 最新 Raspberry Piで学ぶ電子工作 作って動かしてしくみがわかる: 電子工作便利商品 秋月電子通商-電子部品・ネット通販
Raspberry Piで学ぶ電子工作 パーツセット K-10852 Raspberry Piで学ぶ電子工作 パーツセット: 電子工作便利商品 秋月電子通商-電子部品・ネット通販
Raspberry Pi WH(ラズベリーパイゼロ ダブルエイチ)本体 M-12961 Raspberry Pi Zero WH (ラズベリーパイゼロ ダブルエイチ) 本体: マイコン関連 秋月電子通商-電子部品・ネット通販
ニクロムはんだこて KS-30R(30W) T-02536 ニクロムはんだこて KS−30R(30W): 電子工作便利商品 秋月電子通商-電子部品・ネット通販
はんだこて台 ST-11 T-02537 はんだこて台 ST−11: 電子工作便利商品 秋月電子通商-電子部品・ネット通販
はんだ 0.8mm T-02594 はんだ 0.8mm: 電子工作便利商品 秋月電子通商-電子部品・ネット通販
スイッチングACアダプター(USB ACアダプター) MicroBオス 5V3A M-12001 スイッチングACアダプター(USB ACアダプター) MicroBオス 5V3A: 電源一般 秋月電子通商-電子部品・ネット通販
HDMIメス⇔miniHDMIオス変換コネクタ C-13431 HDMIメス⇔miniHDMIオス変換コネクタ: パーツ一般 秋月電子通商-電子部品・ネット通販
USB OTGケーブル microB 10cm C-13430 USB OTGケーブル microB 10cm: パーツ一般 秋月電子通商-電子部品・ネット通販
TOSHIBA マイクロSDカード(microSDHC)EXCERIA 32GB 100MB/s S-14515 TOSHIBA マイクロSDカード(microSDHC)EXCERIA 32GB 100MB/s: 雑貨 秋月電子通商-電子部品・ネット通販

その他、HDMIケーブル、ディスプレイ、キーボードは必要です。

OSの準備

普段Webサーバで慣れてるので、CLIだけでいけるっしょとイキって家のネット環境がよくなく、取り急ぎDesktop無しの Raspbian Buster Lite をダウンロードしました。 Debian Busterのラズパイ向けディストリビューションのようです。

www.raspberrypi.org

イメージのダウンロード後、balenaEtcher というツールでSDカードにイメージを書き込みました。

www.balena.io

www.1ft-seabass.jp

いろいろつないで起動!

f:id:fly1tkg:20191231140140j:plain

下記のようにログインを要求されたら user名 : pi パスワード : raspberry を入力します

raspberrypi login: pi
Password: raspberry

初期設定

下記設定は下記コマンドで実施できます

sudo raspi-config

Wifiの設定

仕事部屋からルータが遠く、物理接続がつらいのでまずWifiを設定します。

2 Network Options を選択し N2 Wifi を選択します。

繋ぎたいWifiSSIDパスフレーズを入力すれば利用できるようになります。

キーボード設定

4 Locarization Options > I3 Change Keyboard Layout から設定できます。

僕はUSキーボードなので、Generic 101-key PC > Other > English (US) > English (US) を選択

修飾キーの設定も出てくるのですが、よくわからないので雰囲気でエンターを連打しました。

SSHの設定

5 Interfacing options > SSH からSSHの設定をONにする

その後、別LAN内のPCからSSHしてみる

# ユーザ名@IPアドレス
# パスワードは raspberry
ssh pi@192.168.0.177

ラズパイのIPアドレスがわからない場合、下記コマンドでwlan0 の ローカルIPアドレス( inet の値)を確認する

ifconfig

または、リモートPCから下記コマンドを実施して、ラズパイ WH のMACアドレスからも見つけられます。

arp -a
# b8:27 から始まるMacアドレスを探す

Vimの設定をする

エディタはVimでやりたいので設定をしました

sudo apt install vim

vimrcには下記を記載しました

set expandtab
set tabstop=4
set softtabstop=4
set shiftwidth=4

Lチカをする

あとは、書籍の内容に沿ってLチカをしました。

配線は+極から22ピン(GPIO 25)-> LED -> 330Ω抵抗 -> GND となるようにしています。

f:id:fly1tkg:20191231140110j:plain

書籍ではIDLEというPythonの環境を利用していましたが、僕はデスクトップをインストールしていないんので普通にコマンドラインで実施しました。

下記のように非常にシンプルなコードでGPIOと呼ばれるピンの電圧を制御できます。

"""
書籍より引用
"""
import RPi.GPIO as GPIO
from time import sleep

GPIO.setmode(GPIO.BCM)
GPIO.setup(25, GPIO.OUT)

try:
    while True:
        GPIO.output(25, GPIO.HIGH)
        sleep(0.5)
        GPIO.output(25, GPIO.LOW)
        sleep(0.5)
except KeyboardInterrupt:
    pass

GPIO.cleanup()

下記コマンドでプログラムを実行します。

python main.py

電源を消す

電源ボタンとかついてないので、下記コマンドでシャットダウンします

sudo shutdown -h now

感想

普段触っているLinuxやweb技術を使ってリアルな世界のインプット・アウトプットできるのすごく楽しいし、可能性を感じました、、!

CloudVision APIのテキスト認識をpython3から利用する

CloudVision APIのテキスト認識を使うと安価に高精度の画像のOCRが利用できるのですが、pythonのサンプルが公式ドキュメント内で整理されていない感があり、少し手間取ったので自分用のメモとして残しておきます。

やったことは以下です。

  • テキスト認識のヒントに日本語を渡す
  • 結果解析用にAPIのレスポンスをJSON形式のファイルで残す
# -*- coding: utf_8 -*-
import io
import os
import json
import glob

from pprint import pprint

# Google Cloud client libraryのインポート
from google.cloud import vision
from google.cloud.vision import types

from google.protobuf.json_format import MessageToJson

def annotate(basename):
    # 画像のファイルパス
    file_name = os.path.join(
        os.path.dirname(__file__),
        'resources/{}'.format(basename))

    # 画像のロード
    with io.open(file_name, 'rb') as image_file:
        content = image_file.read()

    image = types.Image(content=content)

    # 日本語のヒントを与える
    image_context = types.ImageContext(language_hints=['ja'])

    response = client.text_detection(image=image,image_context=image_context)

    # JSON ファイル書き出し
    serialized = MessageToJson(response)
    data = json.loads(serialized)

    f = open('results/{}.json'.format(os.path.splitext(basename)[0]), 'w')
    f.write(json.dumps(data,ensure_ascii=False))
    f.close()

# クライアント初期化
client = vision.ImageAnnotatorClient()

# resources フォルダ内の画像をすべてAPIに投げる
paths = glob.glob("resources/*.jpg")

for path in paths:
    annotate(os.path.basename(path))

リポジトリ:

github.com

余談

  • 技術検証の段階だったので、curlからやればすんなりできたんですけどね。。
  • 内部実装protobuf使ってるんですね、しかもJSONのコンバータつけてるのはおしゃれ。複数言語をターゲットにするならそっちの方が効率いいのかな。かっこいいなあ。