echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Serverless Framework】package設定について&パッケージが終わらない場合の対策

概要

以前、簡単デプロイとserverless.ymlの記載についてでServerless Framework(以下SFW)のserverless.ymlファイルについての記事を書きました。
今回はその中の「package」項目(=デプロイパッケージの設定)に関する内容です。

Serverless Framework公式サイト

共通設定

全functionsで共通のpackage設定は、ルートの「package」項目内に設定します。
主に、下記の設定項目があります。

項目名 意味 備考
exclude デプロイパッケージに含めないファイル/フォルダを指定する 基本的に、ここに含めなかったファイル/フォルダはすべてパッケージに含まれる
include excludeで指定したフォルダの中で、パッケージに含めたいファイル/フォルダを指定する
artifact デプロイファイルとして設定したい*.zipファイルを指定する 指定すると、SFWのパッケージ処理は無視される(設定も含め)?
※ここはイマイチよくわかってません。別ツールでデプロイパッケージを作成する場合に指定する?
individually functionsの関数について、個別にパッケージを行う(=*.zipファイルを作成する)かどうか(true/false) デフォルトはfalse
excludeDevDependencies パッケージに含める全フォルダ内のpackage.jsonの「devDependencies」に定義したモジュールを自動でパッケージから除外するかどうか(true/false) デフォルトはtrue。
詳細は「Excluding development dependenciesが終わらない場合」を参照

関数別設定(functions)

「共通設定」に記載した設定項目ですが、これはfunctionsの各関数で個別に設定することも可能です。(基本は両方を組み合わせて使用する...のかな?)

(例)

package:
  individually: true
  exclude:
    - lambda/**
    - src/**
  include:
    - lambda/common/mysql2  
  
functions:
 func1:
    handler: lambda/func1/index.handler
  package:
    individually: true
    include:
      - lambda/func1/*
    exclude:
      - lambda/func1/*.json  
  
 func2:
    handler: lambda/func2/index.handler
  package:
    individually: true
    include:
      - lambda/func2/*
    exclude:
      - lambda/func2/*.json

※上記の例だと、こんな感じでファイルができます。(デフォルトでは「.serverless」フォルダ内にできます)

  • func1.zip
    • lambda
      • common
        • mysql2
      • func1
        • index.js
  • func2.zip
    • lambda
      • common
        • mysql2
      • func2
        • index.js

勘違いしてたこと

共通設定だけではだめ

最初は「共通設定だけ設定すればいいんだな」と、共通部分の設定のみ行っていたのですが、これだと個別の*.zipファイルはできますが、

  • 各*.zipファイルに、全functionが格納されている

という、変な状態になります。

(例)下記serverless.ymlでパッケージを行うと...

package:
  individually: true
  exclude:
    - src/**
  include:
    - lambda/**  

本当は「 関数別設定(functions) 」記載した構成の個別*.zipファイルができてほしいのですが、実際はこうなります。(commonフォルダは省略)

  • func1.zip
    • lambda
      • func1
        • index.js
      • func2
        • index.js
  • func2.zip
    • lambda
      • func1
        • index.js
      • func2
        • index.js

...そりゃあ、パッケージ処理が遅くなりますよね。

functions格納フォルダは共通設定でexcludeする

また、始めは「(上記の例の場合)共通設定でlambdaフォルダは含めないとダメだろ」と、serverless.ymlに下記の設定をしていました。
※(全functionsの格納フォルダである、lambdaフォルダをパッケージに含めている)

package:
  individually: true
  exclude:
    - src/**
  
functions:
 func1:
    handler: lambda/func1/index.handler
  package:
    individually: true
    include:
      - lambda/func1/*
    exclude:
      - lambda/func1/*.json  
  
 func2:
    handler: lambda/func2/index.handler
  package:
    individually: true
    include:
      - lambda/func2/*
    exclude:
      - lambda/func2/*.json

が、これで作成される*.zipファイルは、先述の「共通設定だけではだめ」でできた構成と同じになります。

なので、「関数別設定(functions)」に記載したserverless.ymlファイルのように、

  • 全functions共通で必要なもの以外は、すべて共通設定でexcludeし、functionsで個別にincludeする

ということでしょうね。

Excluding development dependenciesが終わらない場合

AWS CLIの「serverless package」コマンドで実際にデプロイパッケージ作成処理を行った際、

  • 「Excluding development dependencies...」が全然終わらない

という現象が発生する場合があります。

これは、SFWがデプロイパッケージ作成の際、

  • (パッケージ対象フォルダの)全package.json内のdevDependenciesのモジュールを再帰的にチェックし、パッケージ対象から外す

処理を行っているからです。

一見よくわからないかもしれませんが、例えばどこかでpackage.jsonがあったら、

  • そのpackage.jspnのdevDependenciesをパッケージから外す
  • そのpackage.jsonの全dependenciesモジュールについて、一つずつ
    • そのモジュールのpackage.jsonを見に行く
    • そのモジュールのpackage.jsonのdevDependenciesをパッケージから外す
    • そのモジュールのpackage.jsonの全dependenciesモジュールについて、一つずつ...(以下繰り返し)

を全package.json、及びそのdependencies&devDependenciesについて行うので、設定次第ではめちゃくちゃ遅くなります。

で、対策ですが「共通設定」で説明した「excludeDevDependencies」を「false」に設定すればOKです。

※ただし、その場合devDependenciesのパッケージが除外されないので、パッケージサイズが大きくなるケースもあります。
わかるなら事前にexcludeするなどの対策が必要です。

package:
  individually: true
  exclude:
    - lambda/**
    - src/**
  include:
    - lambda/common/mysql2  
  excludeDevDependencies: false

参考:“Excluding Development Dependencies” Takes Forever

まとめ

今回は久々にSFWの内容になりました。
SFWは本当に便利&奥が深いので、時間が許す限り、もっともっと突き詰めてみたいところです。

ただ、公式サイトが最近リニューアルしましたが、その結果なんか記事が見にくく(探しにくく)なった感じがしますが...

てか、AWS Cognitoの記事はどうなった...

【Xamarin】JXUGC #25 最新情報アップデート&LT 大会に参加しました

概要

昨日(2019/8/31)、マイクロソフト東京本社で開催された、「JXUGC #25 最新情報アップデート&LT 大会」に参加してきました。
その内容になります。

(参考までに)僕のXamarin履歴

  • 2016(4月か10月くらい) : Xamarinイベント&JXUG初参加
    • 3年~3年半のキャリア
  • プライベートでは、Xamarin.Formsをちょいちょい触ってる。
  • C#大好き
  • 仕事では全く縁がない

発表者&内容

下記の通りになります。(詳細は、公式ページ参照。敬称略)

※公式ページから、LT資料を見ることができます。興味があればぜひ。

発表者 内容
@amay077 Xamarin.Forms Hot Reload のススメ
@ailen0ada 1200万ユーザーを抱えるToDoアプリの作り方
@muak_x アプリ「復習帳」開発にまつわるエトセトラ
@himarin269 モバイルのVUI対応とXamarin・Azure・C#
@futa_ttjh Xamarin.Formsで自分用家計簿アプリを作ってみた
@gnk_f327 2年で6個のアプリをリリースした感想 with Xamarin.Forms
@setoazusa Xamarin.Formsでアプリの設定情報を管理するには
@TANY_FMPMD Xamarin.Forms Shellを調べてみた
@hiro128_777 App Center 推奨のOneSignal使ってみた
@omanuke Fabulous is Fabulous
@mishi_cs ARCore で遊ぼう
@nakasho_dev Azure Spatial AnchorsをXamarinで使ってみた
@masatoru AppCenterのAuthとDataを使ってみた

印象に残った内容

※さすがに全部は追いきれないので、概要だけ

HotReloadについて
  • 今、フロントはHotReloadが熱い。
    • Flutterとか、Vue.jsなんかはHotReload対応。
  • でも、XamarinはHotReload未対応。
    • てか、HotReloadっぽい機能は出るんだけど、日の目を見ないままBANしてばかり。
    • マイクロソフト的にはあまり力を入れてない?
  • XAML Hot Reload For Xamarin.FormsがHotReloadを頑張ってくれるみたい。
    • 個人的にデモを見た限り、好感触♪
  • (あめいさんも言ってたけど)正式リリースまで油断は禁物。
    • てか、Xamarinのサービスって、そのパターンが多いよね...
クロスプラットフォーム開発について その1
  • (モバイルに限らず)フロント側は、とにかく進歩が速い!
  • 下記のことが日常茶飯事に起こる
    • 前バージョンで採用した技術が、今はもう古い
    • 前バージョンではいまいちだった技術が、今では超イケてる感じに
  • 大事なのは「前バージョンの技術に固執しないこと」
    • 前回採用した技術でも、今回はお祈りメールを送る事態も十分にある
    • というか、クロスプラットフォームを捨てる(ネイティブ言語開発)ことも大いにある
  • 大事なのは、「状況・環境に応じ、臨機応変に対応すること」
クロスプラットフォーム開発について その2
  • クロスプラットフォーム開発環境は「銀の弾丸」ではない。
    • もしそうなら、誰もswiftやkotlinに手を出さないよね...
  • クロスプラットフォーム開発環境は万能だけど、細部ではネイティブ開発言語には劣る。
    • 「痒いところ」的な部分は、やはりネイティブ言語にはかなわない。
    • 「時間・リソース分散」を気にしないなら、ネイティブ言語の方が良い。
  • むしろXamarinのウリは、「クライアント&サーバー(クラウド)を一つの言語(C#)で開発できる」ところかな?
    • クラウドはこれがあるから、開発の敷居はそんなに高くない(作法さえ覚えれば、あとはプラットフォーム&インフラ次第)
設定情報(てかトークンとか)の管理
  • 各種トークンの管理、本当に面倒。
    • manifest.xalファイルやInfo.plistファイルに設定する
    • *.csprojの設定を変える
    • 個別に用意して、ビルド時に適用するようにする。(gitの管理からは外す)
  • てか、本当にトークン管理は悩ましい。
    • 現在、Vue.jsのサーバーレスSPAで絶賛悩み中

※あめいさんがこんな記事を書いてくれてます。
Xamarin プロジェクトにおける API key など秘密情報管理のベスト?プラクティス

※本家のStackOverFlowでトークン管理はクライアント?サーバー?みたいなページがあって、参考にしてたのですが...失念。(わかったらリンク載せます)     

App CenterのAuthについて
  • App Centerに「Auth」という認証機能基盤が追加された模様。
  • AWSの「Amplify/Auth」みたいなものかな?
    • 認証関係は本当に厄介なので、こういう基盤が提供されるのはうれしい限り。
良いアウトプットを生む条件

※良いアウトプットは、良い定時退社から生まれる(名言)

良いアウトプットは、良い定時退社から生まれる(大事なことなので(ry))

その他
  • 愛知県民の参加率高スギィ!!!
    • 東京のイベントですよね?
    • 「お前が言うな」と言われたらそれまでですが...

総評

いや、Xamarinイベントは久々の参加だったのですが、参加してよかったです。
やっぱりXamarinはいいですよね。

てか、他の参加者との方とも話していたのですが、ぜひ定期的に開催されると嬉しいですね。
次回も都合が合えば、ぜひ参加したいです。
(10月末までは、2週間に一度のペースで東京に来る予定ですので。)

あと個人的には、おやつタイムがあった&他の参加者とお話しできたのが、すごい良かったなあ、と思いました。

あと、そろそろ私も(Xamarinじゃなくてもよいので)何かのネタで登壇したいなあ、と考えてます。(※)

それでは、今日はこの辺で。

※実は今回Serverless Framework&Azure&Xamarinネタで登壇しようと、管理者の田淵さんとTwitterでやり取りしてたのですが、最終的に「それってXamarinってか、むしろ翌週のAzureイベントで話す内容じゃね?」って結果となり、お蔵入りとなりました(Azureイベントは私用のため不参加)。

【Serverless Framework】Azureにサービスをデプロイする

概要

以前、【Serverless Framework】簡単デプロイとserverless.ymlの記載についてで、Serverless Framework(以下SFW)を使ってAWSにサービスをデプロイする方法を書きました。

今回はその続きとして、最近おざなりになっていたSFWを使用した、Azureへのサービスのデプロイ方法について記載します。

触れないこと

  • SFWの詳細
    • これは「概要」内のAWS版の記事を参照してください。
  • Azureの詳細

インストール&サービス作成

インストール&サービス作成は、AWS版と大差ありません。
インストールして、テンプレートを指定して、サービス作成(create)する...という感じです。

ただAzureでは「serverless-azure-functions」というモジュールが追加で必要のようなので、別途npm installして、「plugins」定義に追加しておきます。

# SFWインストール
> npm install serverless -g  
  
# serverless-azure-functionsのインストール  
> npm install serverless-azure-functions  --save-dev  
  
# サービス作成(templateは他にもいろいろ。--helpで一覧を表示可能)  
> serverless create --template azure-nodejs --name azure-sample

定義の作成

定義の作成ですが、「百聞は一見に如かず」なので、まずはserverless.ymlファイルの一例を。

なお、下記serverless.ymlファイルを含むプロジェクト一式を下記レポジトリで公開していますので、興味があればご一読ください。

service: azure-sample-new-20190825

provider:
  name: azure
  location: japanwest

plugins:
  - serverless-azure-functions

package:
  exclude:
    - README.md
  include:
    - lambda/**

## azure functions dedinition.
## azure functions works at ([endpointurl]/api/[route])
## e.g. https://your-function-app-name.azurewebsites.net/api/hello
## this is able to change at congigration of host.json
## https://docs.microsoft.com/ja-jp/azure/azure-functions/functions-host-json
functions:
  hello:
    handler: lambda/Hello/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: req
          methods: 
            - get
          route: hello
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: res
  goodbye:
    handler: lambda/Goodbye/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: request
          methods: 
            - get
          route: goodbye
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: res
  goodbye2:
    handler: lambda/Morning/index.handler
    events:
      - http: true
        x-azure-settings:
          # direction: in
          name: request2
          methods: 
            - post
          route: posthello
          authLevel : anonymous
      - http: true
        x-azure-settings:
          direction: out
          name: response

service

サービス(デプロイするアプリの管理単位。CloudFormationみたいなもの)の名前、という点ではAWSと同じです。

  • Azureの場合、「[サービス名]-rg」というのがリソースグループの名前になります。(AWSと違い、「serverless-deployment-bucket」などを使用して任意の名前を付ける...みたいなことはできないみたい)
  • リソースグループ名は、サブスクリプション内での重複はNGなので、必ず重複がない名前にしてください。

provider

  • name:デプロイ先クラウドの名前。create時に--templateを指定した場合、自動で設定されています。
  • location: デプロイ先のリージョンを指定します。(japaneast, japanwestなど)

plugins

先述の通り、ここには先程インストールした「serverless-azure-functions」を指定します。

include/exclude

AWSと同じく、パッケージファイルに含める/除外するファイルの設定です。

functions

Azure Functionの定義。今回の中心となる部分です。(ただ「定義名」「handler」「[events] - [http]」の部分は、AWSと同じなので省略)

※eventsはhttp(http通信)以外にもいろいろありますが、今回はhttpのみ紹介。(それ以外のイベントはSFW公式サイトを参照)

Azureの場合、下記のルールがあるようです。

  • (イベントの種類以外の)定義は「x-azure-settings」以下に定義
  • リクエスト/レスポンスで個別に定義が必要
  • AWSとは違い、API Management(AWSで言うAPI Gateway)には紐づかない

なお、ここ項目の説明はこちら。

項目名 説明 備考
direction 通信の向き(?)
inはリクエスト、outはレスポンス。
inを定義したらエラーになった。in側は定義不要かも。
name Azure Functionファイル内でのリクエスト、およびレスポンスオブジェクトの名前。
methods リクエストメソッドの種類(get, postなど)を配列形式で in側のみ。
定義とメソッドが1:1ではないのが、AWSとの違い
route リクエストURLに適用される、エンドポイントURLの後に付与される文字 in側のみ。
デフォルトは「定義名(helloなど)」
AuthLevel リクエスト時の認証に適用される認証レベル。 in側のみ。
function, anonymous, adminから選択

デプロイ

デプロイですが、これはAWSと同じく「deploy」コマンドを実施するだけです。

下記の処理が行われた後、デプロイされます。問題がなければ、デプロイ完了するはずです。

  • [関数定義名]-function.jsonの作成
  • host.jsonの作成
  • .serverlessフォルダに、パッケージしたファイル(*.zip)の作成
# デプロイ
> serverless deploy -v  

あとはAzureにアクセスし、リソースグループ&Azure Functionが作成されており、各Azure Functionに「[エンドポイントURL] + [route]の設定値」のURLで正常にレスポンスが返ってくればOKです。

※デフォルトのエンドポイントは、https://[サービス名].azurewebsites.net/api/[routeの設定値] です。(「api」を忘れやすい)

デプロイできない場合

デプロイ時ができない場合、下記事項を確認してください。

「At least one resource deployment operation failed」というエラーが発生する。

この場合「[service]に指定した名前-rg」という名前のリソースグループがすでに存在している可能性が高いです。
コンソール操作や「serverless remove」コマンドで該当のリソースグループを削除してから、再度実施してください。

※deployコマンドがエラーになってもリソースグループ自体は作成されますので、そのリソースグループの消し忘れ...というのが、よくあるケースです

エラーは発生しないが、デプロイ中に「Creating Function App」から一切処理が進まない

この場合、AzureのActivityログに下記「Failed」ログが無いか確認してください。
下記ログがあった場合、過去のCreating Function App処理が何故か生き続けていることが原因である可能性があります。

項目名 内容
Error Code Conflict
Message Cannot modify this web hosting plan because another operation is in progress. (中略) OperationName: Create

対策としては、「service名を変更して、別リソースグループ名でデプロイする」になります。(これ、バグなのかも...)

SFWがAzure対応したのは2017年と比較的新しいからか、一部AWSで対応している機能が機能しなかったりなどありますが(※)、サービスのデプロイは十分可能です。
本格的にサーバーレスアプリを作る場合はもちろん、「WPF、Xamarin.Formsなどでサーバーレスアプリを作りたい!」という場合に、サクッとAzureの環境を作るような場合にも、非常に有用だと思います。

では、今回はこの辺で。

※例えば「package」コマンドや「--package」オプションなどは機能しないようです。(公式サイトにも記載がない)

【AWS】Web ACLを設定する方法&CloudFormationでの注意点

概要

前回前々回と、CloudFrontに関する記事でしたが、今回もCloudFrontです。
今回は前々回に触れられなかった、WAF Web ACL(※)の作成について。

※Web Application Firewall - Web Access Control Listの略。WAFと同じもの...という認識でOK。

やること

  • Web ACLの前提条件作成
  • 条件の作成
  • ルールの作成
  • Web ACLの作成
  • CloudFrontへの適用

Webの前提条件作成

AWS WAF and AWS Shield」から「AWS WAF」を選択し、「Configure Web ACL」を選択します。
※「Concepts overview」では、条件(Condition)・ルール(Rules)・Web ACLについての説明があるので、読んでおくとよいかも。

「Next」ボタンをクリックすると「Name web ACL」画面でWeb ACLの前提条件を設定します。

項目は以下の通り。

項目名 説明
Web ACL name(必須) Web ACL nameの名前
CloudWatch metric name(必須) Web ACLのCloudWatch上でのメトリクス名。(Web ACL Nameを設定すると自動で設定されます。任意設定も可能)
Region(必須) Web ACL nameを設定するWeb ACLのリージョン(下記参照)
AWS resource to associate Web ACLを関連付けするAWSリソース。(後から設定することも可能)

【Regionについて】

  • CloudFrontに関連付けを行う場合、「Global」を選択してください。
  • 個別リージョンは、CloudFront以外のリソース(API Gatewayなど)に関連付けする場合に設定します。

f:id:Makky12:20190812181252p:plain

条件(Condition)の作成

「Create conditions」で、Web ACLに適用する条件(=フィルタ)を設定します。
設定可能なのは、下記の通り。

項目名 説明
Cross-site scripting match conditions XSS攻撃に対応するフィルタ
Geo match conditions アクセスされた地域(国)でのフィルタ
IP match conditions アクセス元のIPアドレスに対応するフィルタ
Size constraint conditions リクエストデータのデータ量でのフィルタ
SQL injection match conditions SQLインジェクション攻撃に対応するフィルタ
String and regex match conditions リクエストデータに含まれる文字列に対応するフィルタ

個々項目の詳細設定については、公式サイトを参考。
(項目名を見ればわかるようなものばかりだけど)

とりあえず、ちょっと分かりにくそうなものだけ抜粋。

項目名 説明
Part of the request to filter on フィルタ対象の項目(ヘッダ、クエリ文字列、など)
Transformation チェック前にフィルタ対象項目の値に実施させる処理。
(「Simplify Command Line」は、コマンドラインと思われる文字列を無効化する処理。OSコマンドインジェクション対応用です)
Comparison operator データ量比較の不等号の設定(=、<=、< など)
Match type 文字列比較のパターン(部分一致、完全一致、など)

f:id:Makky12:20190812181323p:plain

ルール(Rule)の作成

「Create rules」でルールを作成します。
「条件とルールって、何が違うの?」と思うかもしれませんが、Web ACLでの「ルール」は、「1つ以上の条件をまとめて管理する入れもの」となります。

「Create Rule」ボタンをクリック後、ルールの内容、及び「Add Conditions」で関連付けする条件を設定します。

  • 「Add Conditions」は、「Geo Match」及び「IP Address」が「指定した条件と一致」、それ以外は「指定した条件と少なくとも1つが一致」です。
  • 「Rule Type」は、下記の通り。
項目名 説明
Regular Rule 「Add Conditions」で設定した内容を満たしたとき、ルールを適用する
Rate-Based Rule 「Add Conditions」で設定した内容を満たし、かつ5分間のリクエスト回数が「Rate Limit」以上のとき、ルールを適用する

「Add rules to a web ACL」で作成したルールを選んで、「Add Rule to web ACL」をクリックすると、そのルールをWeb ACLへ採用できます(複数選択可能です)。
その後、以下の2項目&その際の動作を設定します。

項目名 説明
If a request matches all of the conditions in a rule 個々のルールについて、設定した条件をすべて満たした場合の動作(上から順に評価)
If a request doesn't match any rules どのルールも、ルールに設定したどの条件も満たさなかった場合(=デフォルトの動作)

最後に「Review And Create」をクリックします。

f:id:Makky12:20190812181348p:plain

Web ACLの作成

最後の「Web ACLの作成」ですが、これは「Review and create」で「Confirm and Create」ボタンをクリックするだけです。
また各項目の「Edit」ボタンをクリックすると、その項目の再設定ができます。

f:id:Makky12:20190812181405p:plain

CloudFrontへの適用

最後にCloudFrontへの適用ですが、これはCloudFrontの[Distribution Settings] - [AWS WAF Web ACL]で、作成したWeb ACLを選択するだけです。

f:id:Makky12:20190812181419p:plain

CloudFromationでWeb ACLを作成する際の注意

ここからは、CloudFormationでWeb ACLを作成する際の注意になります。

※CloudFormationの詳細はここでは説明しないので「CloudFormation?よくわからないし、関係ないな」って人は、スルーでOKです。

WAFリソースの違い

CloudFormationのWAFリソースには、下記2種類があります。

これの違いですが、WAFのリージョンになります。(下表参照)

リソース名 リージョン
AWS::WAF Global(CloudFront)
AWS::WAFRegional Regional WAF Resource(リージョン別WAF)
  • CloudFrontに適用するWAFは「AWS::WAF」で定義しなければなりません。
  • AWS::WAFRegionalで定義すると、デプロイした際のCloudFrontとの紐づけ時に「指定されたリソースにアクセスできない(「アクセス権限がない」だったかも)」というエラーが発生します。
「Geo match conditions」設定時の注意

条件の「Geo match conditions」(=地域(国)でのフィルタ条件)ですが、CloudFromationの場合、下記の事項があるので注意しましょう。

  • AWS::WAFRegionalでのみ設定可能。(AWS::WAFでは設定不可)
    • AWS::WAFで作成すると、ルールとの紐づけ時に「指定されたIDのリソースがありません」というエラーが発生します。
  • 公式の「AWS WAF Regional リソースタイプのリファレンス」ページを表示しても、表示言語を「日本語」にしていると「AWS::WAFRegional::GeoMatchSet」へのリンクが表示されない。
    • 表示言語を「English」にすれば表示されます。
  • 今のところ、CloudFrontに「Geo match conditions」を適用する場合、コンソールで行うしかない?

まとめ

と、またちょっと長くなりましたが、今回はここまで。

最近、Cognito&AWS Amplifyを使った認証コードをガッツリ書いてたので、次のネタはそれにしようかな?

てか、Azureは...

【AWS】API Gatewayのエンドポイントに独自ドメインでアクセスする

概要  

前回CloudFrontを使った独自ドメインによるSPAの公開を記事にしましたが、今回はその第二弾で、API Gateway独自ドメインでアクセス方法に関する記事です。  

触れること

  • CloudFrontとAPI Gatewayの連携方法(てか、これしかない)  

触れないこと  

  • API Gatewayの細かい手順(作成方法・設定・Lambdaなどとの連携など)  
  • 前回触れた手順  

やること一覧  

API Gatewayの各種設定  

API Gatewayの作成や各種などについては、今回は触れません。
※公式サイトなどを参考に作成&デプロイしてください。そこまで難しくはないと思います)

とりあえず、動作確認用のLambda関数の正常動作が「テスト」で確認できればOKです。(「サンプルから作成」でOKです。)

重要なのは、「ステージ」で表示される「エンドポイントURL」で、これをコピーしておいて下さい。   f:id:Makky12:20190802185133p:plain

SSL証明書の作成  

これは前回の記事そのままなので、そちらを参考に独自ドメインの証明書を作成してください。(リージョン:北バージニアじゃないとダメな点に注意!)  

※本当によく忘れるんです、この設定... 

CloudFrontの設定  

で、一番重要なCloudFrontの設定です。
ただこれも前回との共通部分が多いので、違う部分だけ説明します。
(下記に記載していない項目は、前回と同じでOKです。)

項目名 設定値 説明 備考
Original Domain Name API Gatewayの各種設定」でコピーしたエンドポイントURL CloudFrontで制御を行うAWSリソース ※1
Origin Protocol Policy HTTPS Only CloudFrontから各種リソース(今回はAPI Gateway)に接続する際のプロトコル API Gatewayは、HTTPSのみ許可しているので
Origin Response Timeout 4~60のいずれか レスポンスを待つ時間(秒)。これを過ぎたらレスポンスタイムアウトと判断
Origin Keep-alive Timeout 1~60のいずれか API Gatewayとの接続がアイドル状態になったときに待つ待ち時間(秒)
HTTPS Port 任意 API Gatewayへの接続ポート デフォルトは443
Origin Custom Headers 任意 API Gatewayに送信したいヘッダ 任意にヘッダ情報を送信したい場合に設定する。(x-api-keyなど)
Query String Forwarding and Caching 下記説明に従い、適切な値を クエリ文字列の転送とキャッシュ ※2
CNAME API Gateway接続用の独自ドメイン CloudFront→API Gatewayに接続するURLの別名

※1:Original Domain NameにURLをコピペすると、ステージ部分(dev/stgなど)が自動で「Origin Path」にカット&ペーストされます。

※2:文字通り「クエリ文字列の転送とキャッシュ」なのですが、非常に重要な部分です。

項目名 説明 クエリ文字列のオリジン(=API Gateway)への転送
None クエリ文字列はすべて無視し、キャッシュは実行しない 一切転送されない
Forward all, cache based on whitelistl Policy ホワイトリストに指定したパラメータは、値が同値ならキャッシュから使用。違っていれば取得してキャッシュに格納する。
※順番もチェックするので注意。
すべて転送される
Forward all, cache based on all クエリ文字列部分のすべてをチェック。順番、値をすべて含め、1文字でも違ったら値を取得してキャッシュに格納する。
完全一致の場合のみキャッシュから使用
すべて転送される(クエリ文字列として)

つまり「None」だと、クエリパラメータが一切API Gatewayに渡りませんAPI Gatewayへのパラメータ転送はクエリ文字列で行うことも多いと思うので、この点は要注意です。(てか、実際ハマった...)

【参考】

Route53の設定  

最後にRoute53ですが、これは「SSL証明書の作成」同様、前回記事そのままなので、そちらを参考にホストゾーン&レコードセット(エイリアスあり)を設定してください。

あとは、ブラウザのURLなりcURLなりで動作確認してみて、正しいレスポンスが返ってこればOKです。

結構ざっくりな感じになってしまいましたが、今回はここまで。

 

【AWS】CloudFrontを用いてサーバーレスSPAを独自ドメインで公開する

概要

Vue.jsによるサーバーレスSPAをCloudFront(AWSCDNサービス)を用いて、独自ドメインで公開する方法です。

※必要最低限しか書いてないので、後日追加するかもしれません。
※2019/7/29 画像&一部説明を追加しました。

前提条件

  • Vue.jsのサービスをデプロイできる状態にしておく。
  • ドメインの取得を行っておく

※ここではこれらには(あまり)触れません

参考サイト

やること

最低限、下記のことが必要になります(多分)。

※下記事項は、別記事で触れる予定

S3バケットのWebホスティング 

Vue.jsサービスのデプロイ用S3バケットをパブリックに公開して、Webホスティングする必要があります。
手順は下記のとおりです。

  • Vue.jsサービスデプロイ用のS3バケットを作成する
  • [プロパティ]タブ - [Static Website Hosting]で、
    • 「このバケットを使用してウェブサイトをホストする」を選択する。
    • 「インデックスドキュメント」に「index.html」を指定する。
  • [アクセス権限]タブ - [アクセスコントロールリスト]で、
    • 「Everyone」の「オブジェクトの一覧」を許可する。

f:id:Makky12:20190729173019p:plain
f:id:Makky12:20190729173046p:plain
f:id:Makky12:20190729173059p:plain

SSL証明書の発行

独自ドメイン(≒CNAME)で公開するには、先にそのドメインSSL証明書の発行が必要です。
手順は以下の通りです。

  • 「Certificate Manager」にて、「証明書のプロビショニング」の「今すぐ始める」をクリック。
  • 「証明書のリクエスト」で、「パブリック証明書のリクエスト」を選択。
  • ドメイン名の追加」で、ドメイン名を入力。
    • ワイルドカード指定も可能なので、例えば「*.makky12.com」みたいな指定もできる。
  • 「検証方法の選択」では「DNS検証」と「Eメール検証」、いずれかを選択。
    • 画面に記載があるが、「DNS 設定を変更するアクセス許可」があるならDNS検証、ないならEメール認証を選択。
    • AWS的には、権限があるならDNS検証を推奨している。
  • 「証明書」画面で「状況」が「認証完了」になればOK。

※なお、SSL証明書は必ず「リージョン:北バージニア(us-east-1)」で作成しないとCloudFront設定できないので注意!!!

f:id:Makky12:20190729174113p:plain

CloudFrontの設定

で、やってCloudFrontの設定になります。やり方ですが、まず「CloudFront」で「Create Distribution」-「[Web]のGet Started」をクリックします。

設定画面での設定値は、下表を参照。

項目名 設定値 説明 備考
Original Domain Name S3バケットのWebホスティング」でWebホスティングをしたバケット CloudFrontで制御を行うAWSリソース これを選ぶと「Origin ID」が自動で設定される
Viewer Protocol Policy Redirect HTTP to HTTPS ブラウザなどからアクセスされた際に適用するプロトコル 厳密には何でもよいが、セキュリティを考えると、HTTPは避けたほうが。
Object Caching Customoize キャッシュの設定 個別設定を適用するならCustomize
TTL関連の3つ(Maximum, Minimum,Default) すべて0 キャッシュの保存期間(秒) 1以上だと、デプロイしたコンテンツの反映に時間がかかる(らしい)
Alternate Domain Names(CNAMES) 公開するドメイン ここで設定したデストリビューションの別名 証明書で証明を受けないと、設定できない
SSL Certificate 「Custom SSL Certificate」を選択し、「SSL証明書の発行」で作成したSSL証明書を選択 使用するSSL証明書 証明書を「北バージニア」で作成しないと、ここでリストに表示されない。
Default Root Object index.html デフォルトで表示するコンテンツ名 Vue.jsの場合、ルートのindex.htmlを表示する(はず)

で、「Create Distribution」をクリックし、該当のデストリビューションの「Status」が「Deployed」になればOK。
ただし5~15分程度かかるので、気長に待ちましょう。(In Progressの場合、デプロイ処理中です。)

また、以下の設定もデプロイ処理中に行っておきます。

  • 該当のデストリビューションの「ID」をクリック。
  • 「Error Pages」タブの「Create Custom Error Response」をクリック。
  • 「HTTP Error Code」の403、404について、下記設定で「Create」を行う。
項目名 設定値 説明 備考
Customize Error Response Yes レスポンスのカスタマイズを行うか
Response Page Path /index.html エラー発生時に表示するコンテンツ 詳細は下記参照
HTTP Response Code 200 OK 設定したレスポンスに設定するステータスコード

※Vue.jsだと、本来どのURLを参照してもルートのindex.html(/index.html)が表示されないといけませんが、例えば「/hoge」というパスを指定した場合も、
/hoge/index.htmlを参照します。

当然そんなページはないので404 Not Foundが返されますが、その際に代わりに表示するページです。(Vue.jsのRouter側で適切な処理を実施する必要があります)

f:id:Makky12:20190729181635p:plain
f:id:Makky12:20190729181649p:plain
f:id:Makky12:20190729181808p:plain

Route53によるエイリアス設定

最後はRoute53によるエイリアス設定です。
下記手順で行います。

  • Route53の「DNS管理」を選択。
  • 「ホストゾーンの作成」で、サブドメイン名を含まないドメイン名(「makky12.com」など)を入力。
    • 「タイプ」は「パブリックゾーン」
  • 作成したホストゾーンの「ドメイン名」をクリックし、「レコードセットの作成」をクリックする。
  • 「名前」に、サブドメイン名を入力する。(「hogehohe.(makky12.com)」など)
  • エイリアス」を「はい」に設定し、「エイリアス先」に「CloudFrontの設定」で作成したデストリビューションの「Domain Name」を入力する。

f:id:Makky12:20190729182150p:plain

長々と記載してしまいましたが、あとは「S3バケットのWebホスティング」で作成したバケットにVue.jsをデプロイすれば、これで独自ドメインでSPAを起動できるはずです。

【Vue.js】子コンポーネントの埋め込み方法+各種プロパティ設定方法(2021/12/03:一部加筆修正)

経緯

最近、仕事でVue.jsを触っており(AWSによるサーバーレスSPA)、何とか一週間で他のメンバが開発をできる土台(フレームワーク的なもの)をVue.jsで作成する...程度はできるようになりました。

その中で、コンポーネント(*.vue)の扱いでよく使いそうな、子コンポーネントの扱いについて便利そうな事項をまとめました。

【2021/12/03追記】:最初(2019/7/27)に公開した内容を元に、加筆修正を行いました

参考ページ

そもそも、子コンポーネントの埋め込み方は?

単に親コンポーネント内に子コンポーネントを埋め込む場合は、下記でOKです。

/* これが子コンポーネント */
<template>
  <div id="body_header">
    <router-link to="/">page1</router-link>
    <router-link to="/page2">page2</router-link>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  data: function () {
    return {}
  },
}
</script>  
  
<style>
#body_header {
  height: 40px;
  background: white;
  /* box-shadow: 0px 3px 3px rgba(0,0,0,0.1); */
  display: flex;
  justify-content: center;
  align-items: center;
  border-bottom: 1px #000000 solid;
}
#body_header a {
  text-decoration: none;
  color: #2c3e50;
  margin: 0 10px;
  padding: 3px 10px;
  background: #5ccebf;
}
</style>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <!-- ここで埋め込む -->
    <bodyHeader></bodyHeader>
    <router-view></router-view>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む
import bodyHeader from './components/common/header'
export default {
  name: 'App',
  
  components: {
    // ここで子コンポーネント&HTMLタグ名を登録
    bodyHeader: bodyHeader,
  },
  data: function () {
    return {}
  }
}
</script>  
  
<style>
body {
  margin: 0;
}  
  
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}
</style>  

【2021/12/03追記】エントリーポイントに定義する方法

上記は各Vue.jsファイルに直接埋め込みましたが、複数のVue.jsファイルでまたがって使用する場合は、エントリポイント(main.jsなど)に定義することもできます。

※親コンポーネントのHTML内での子コンポーネントの埋め込み方法は同じです。

// main.js  
  
// 子コンポーネントの読み込み
import bodyHeader from './components/common/header'  
  
// 1. グローバルコンポーネントとして登録する方法。  
// こうすると、全Vue.jsでbodyHeaderコンポーネントが使えるようになる
Vue.component('bodyHeader', bodyHeader);  
  
// 2. 個別に設定する場合。  
// 個別に設定する場合は、Vue.component()は使用せず、
// new Vue()のcomponentsプロパティに設定する。  
// 下の場合、#fugaに対応したVue.jsでのみbodyHeaderが使える。 
 
new Vue({
  el: '#hoge',
})
  
new Vue({
  el: '#fuga',
  components: { 
    'bodyHeader': bodyHeader,
  }
})

コンポーネントの変数を変えたい

コンポーネントの使い方でよくあるのが、下記の例だと思います。

その場合、子コンポーネントの変数やスタイルシートなどを変更する必要があると思いますが、そのやり方です。

コンポーネントの変数の変更方法

コンポーネントの変数を親コンポーネントから変更する場合、下記でOKです。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <router-link to="/page1">{{ $myLink }}</router-link>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  
  data: function () {
    return {
      myLink : ''
    }
  },
  
  methods:  {
    setter(linkValue) {
      this.myLink = linkValue;
    }
  }
}
</script>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <bodyHeader ref="myBodyHeader"></bodyHeader>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
export default {
  mounted: function() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>
</style>  

(2021/12/03追記) propsを使う方法

上記では子コンポーネントのsetterを直接呼びましたが、propsを使う方法もあります。(てか、むしろこっちの方が一般的かも...)

propsの使い方ですが、子コンポーネントのscriptタグで、こんな感じで定義します。

<script>    
export default {
  // propsの定義
  props: {
    isHoge: {
      type: Boolean,
      default: false,
      required: true,
      validator: function (value) {
        return typeof value === 'boolean'
      }
    }
  },
  data()  {
    return {
      // 省略
    }
  }
}
</script>  

上のコードで言えば、「isHoge」はpropsの変数名です。
また、各プロパティの意味は以下の通りです。(すべて任意項目です)

key 意味 備考
type propsの型を表す型クラス(のコンストラクタ) 型クラスであることに注意。(プリミティブ型でも「string」「boolean」などはダメ)
default 値が指定されなかった場合の初期値
required 値の指定が必須かどうか
validator 指定された値をチェックするバリデーション関数

で、親コンポーネントでは、以下のように子コンポーネントのタグ内でpropsの値を指定します。

<template>
  <div id="app">  
    <bodyHeader isHoge="true"></bodyHeader>
  </div>
</template> 

参考:https://jp.vuejs.org/v2/api/#props

ただし、propsは初回レンダリング時は良いんですが、propsを変更してうんぬん...みたいなことをやろうとすると、途端に厄介になります。(ググってみると色々出てきますし、自分もハマりました)

このあたりは、別途独立してブログに出来ればなあ、と思っています。

参考: https://blog.hatena.ne.jp/Makky12/makky12.hatenablog.com/edit?entry=26006613378542201

コンポーネントのスタイルの変更方法

コンポーネントのスタイルを変更する場合は、ちょっと厄介ですが、下記の方法でOKです。

※なお、親コンポーネントのstyleタグにscopeを付けない方法もありますが、本来の使い方ではないですし、Vue.jsのスタイルルールA(必須)に反するので、お勧めしません。

  • 子クラスのスタイルを採用したいHTMLタグに、何かクラスを定義する。
  • 親クラスで、子クラス埋め込みタグの親要素として、何かクラス付きのタグを埋め込む
  • 親クラスのstyle内で、「(子クラス埋め込みタグの親要素クラス) (子クラス埋め込みタグクラス) >>> (子クラスのスタイルを採用したい要素のクラス)」という定義を用意する。
/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <div class="parent">
      <bodyHeader class="bh"></bodyHeader>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
export default {
  mounted: function() {
    // 子コンポーネントのpage1へのリンクに「page1へ飛びます」と表示される。
    this.$refs.myBodyHeader.setter('page1へ飛びます');
  }
}
</script>  
  
<style scoped>  
.parent .bh >>> .my_item {
  /* 子コンポーネントのmy_itemクラスの背景色が青色になる */
  background-color: #0000FF;
}
</style>  

【2021/12/03追記】
もちろん、子コンポーネントに直接cssを書いてもOKです。

/* 
  子コンポーネント 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
  
<style scoped>  
.my_item {
  /* もちろん、直書きでもOK */
  background-color: #0000FF;
}
</style>  

(2021/12/03追記) 子コンポーネントcssを動的に変えたい場合

上記とは少し違いますが、場合によっては「親の状態に応じて、子のスタイルを動的に変えたい」というケースもあると思います。

その場合、方法としては、下記のようなものがあります。

  • 親から受け取る変数を「v-if」条件にする
  • class名を変更する
  • cssクラスのプロパティの値のみ変更する

親から受け取る変数を「v-if」条件にする
これは何となくわかりやすと思います。
ただ、HTML部分の分岐が増えてしまうのがちょっと面倒ですね...

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div if="bgColor==='red'" class="bg_color_red">{{ $myItem }}</div>
    <div v-else class="bg_color_blue">{{ $myItem }}</div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data() {
    return {
        bgColor: ''
    }
  },  
  
  methods: {
    setBgColor(color) {
      this.bgColor =color;
    }
  }
}
</script>  

class名を変更する
親から取得した変数の値に応じて、class名を変更する方法です。(classはv-bindしておく)

この場合、computed関数「getBgClass」に定義したように、

  • 親から受け取ったクラス名をそのまま返す
  • 親からは状態(state)を受け取り、それに応じたクラス名を返す

などの方法があります。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div :class="getBgClass">abc</div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data(): {
    return {
        bgClass: ''
    }
  },
  
  computed: {
    getBgClass() {
       // this.bgClassに直接クラス名を埋め込む場合
       return this.bgClass;
       
       // this.bgClassにフラグ(0, 1やred, blueなどの値)を埋め込む場合  
       if (this.bgClass==='red') {
         return 'bg_color_red';
       } else if (this.bgClass==='blue') {
         return 'bg_color_blue';
       }  else {
         // その他処理
       }
    },
  },
  
  methods: {
    setBgClass(value) {
       this.bgClass = value;
    }
  }
}
</script>  

cssクラスのプロパティの値のみ変更する

<template>タグ内に<component is="style">タグを記載することで、<template>タグ内にcssを直接記述することが可能になります。

またVue.js 2.xでは、styleタグ内のcssの値は、data()変数やcomputed関数などの値をバインディングすることはできない(はず)ですが、上記のcssならば、それを行うことが可能になりますので、タグ内のclass名を直接変更せずとも、スタイルを動的に変更することができます。

この方法だと、上2つの方法と違い、

  • 条件が増えても、(<template>内の)HTMLタグが増えない
  • class名を直接変更する必要がない(=class定義を分岐数分増やさなくていい)

というメリットがあります。

※Vue.js 3.2から、styleタグ内のcssの値にdata()変数の値をバインディングできるみたいです

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div>
    <component is="style">
    .bg_class {
      background-color: {{ getBgColor }}
    };
    </component>
    <div id="body_header">
      <div class="bg_class">abc</div>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む    
export default {
  data(): {
    return {
        bgColor: ''
    }
  },
  
  computed: {
    getBgColor() {
       if (this.bgColor==='red') {
         return '#FF0000';
       } else if (this.bgColor==='blue') {
         return '#0000FF';
       }  else {
         // その他処理
       }
    },
  },
  
  methods: {
    setBgColor(color) {
       this.bgColor = color;
    }
  }
}
</script>  

ちなみに

もちろん、1つの親コンポーネント内で同じ子コンポーネントを使いまわすことも可能です。

/* これが子コンポーネント
(無関係な部分は先述のソースと同じなので省略) 
*/
<template>
  <div id="body_header">
    <div class="my_item">{{ $myItem }}</div>
  </div>
</template>  
  
<script>
export default {
  name:"body_header",
  components: {},
  
  data: function () {
    return {
      myItem : ''
    }
  },
  
  methods:  {
    setter(itemValue) {
      this.myItem = itemValue;
    }
  }
}
</script>  
  
<style scoped>  
</style>
/* これは親コンポーネント */  
<template>
  <div id="app">  
    <div class="parent">
      <bodyHeader ref="myBodyHeader" class="bh1"></bodyHeader>
    </div>  
    <div class="parent">
      <bodyHeader2 ref="myBodyHeader2" class="bh2"></bodyHeader>
    </div>
  </div>
</template>  
  
<script>  
// 上記の子コンポーネントを読み込む  
import bodyHeader from './components/common/header'  
import bodyHeader2 from './components/common/header'  
  
export default { 
    components: {
      bodyHeader : bodyHeader ,
      bodyHeader2: bodyHeader2
  },
  
  mounted: function() {
    this.$refs.myBodyHeader.setter('bodyHeader1');  
    this.$refs.myBodyHeader2.setter('bodyHeader2')
  }
}
</script>  
  
<style scoped>  
.parent .bh1 >>> .my_item {
  background-color: #0000FF;
}  
  
.parent .bh2 >>> .my_item {
  background-color: #FF0000;
}
</style>  

ちょっと急ぎ足&文字数が多くなりましたが、今回はここまで。

...てかはてなブログ、Vue.jsに対応してくれないかなあ。(HTML/CSS/javascriptうまく表示してくれるとか)