20200808のAWSに関する記事は13件です。

[AWS] AWS上に、安価で簡単にRedmineの環境を作成・管理できるよ!そうLightsailならね

Redmine

今更説明する必要もありませんが、Webベースのプロジェクト管理ツールです。
AWSには、DB(PostgreSQL、MySQL)や全文検索(Elasticsearch)、あるいはコンテナ管理(Kubernetes)など、様々なOSSをマネージドサービスとして提供されていますが、残念ながらプロジェクト管理や課題管理をしてくれるサービスがありません(2020年8月現在)。

これらのサービスを利用しようと思ったら、

  • EC2インスタンス上で起動
  • ECSでRedmineのコンテナを起動

少なくともAWS上で、ということを考えると、この辺がすぐに思い浮かぶと思います。

などが考えられると思います。
が、しかし、もっと簡単な方法があるのです。それも定額で。

Amazon Lightsail

AWSでの仮装サーバといえば、すぐに思い浮かぶのは、やはりEC2だと思います。
EC2は、多種多様なインスタンスやOSの選択ができる一方、課金がインスタンスの稼働時間に依存するため、気がつくと高額になっている、なんてことも実はない話ではありません。
しかし、Lightsailは、なんと定額利用できる、仮装サーバなのです。
EC2でもリザーブドインスタンスなどを利用すれば、かなりコストを抑えることができますが、それも及ばないほど、しかも定額で利用することができるのです。

価格

いくつか、インスタンスタイプが用意されています。
これはEC2と同様で、メモリ、CPUコア数、ディスク容量、データ転送量の組み合わせです。
あと、OSは、LinuxとWindowsが選択できますが、価格面ではWindowsの方がやや高い設定になっています。

  • Linux (Amazon Linux / Ubuntu / Debian / FreeBSD / OpenSUSE)
    • 3.5$/月 〜 160$/月
  • Windows Server
    • 8$/月 〜 240$/月

※USドル

なお、データ転送量のみ、それぞれのプランの上限を超過した場合に、超過分のみの追加料金が発生します。

「Amazon Lightsail 料金表」

用意されているOSS

もう一つ特徴的なのが、いくつかのOSSが自動設定可能になっています。
そう、自分でインストールして初期設定して、といった作業が不要になるのです。
提供されているOSSは、以下の通りです。

  • WordPress
  • WordPress Multisite
  • LAMP
  • Node.js
  • Joomla!
  • Magento
  • MEAN
  • Drupal
  • GitLab CE
  • Remine
  • Nginx
  • Ghost
  • Django
  • Plesk
  • cPanel & WHM

Redmineを使えるようにするまで

Lightsailでのインスタンス作成

まず、サービス一覧より、Lightsailを選択します。

Lightsail1.png

ここで「インスタンスの作成」を押します。

次の画面で、

  • リージョン(アベイラビリティゾーン)
  • OS
  • OSS
  • インスタンスプラン

などを選択します。

lightsail2.png

今回は、東京リージョン上に、Lunux+Redmineで、月額3.5$のインスタンス作成してます。
すると、下記のように「保留中」という状態になり、1分もしないうちに「実行中」に変わります。

Lightsail3.png

Lightsail4.png

これだけで、RedmineがセットアップされたLinux環境ができあがりました。

Redmineユーザの確認

まず、インスタンスに接続してみます。
インスタンスの枠の右上にあるメニューをクリックします。

Lightsail50.png

その中から「接続」を選択してください。

Lightsail5.png

ステータスが「実行中」になった直後だと、まだインスタンスに接続できないことがあります。
その場合は、1、2分経ってから接続してみてください。

接続できると、ブラウザで別のウィンドウが開き、コンソールが表示されます。

console1.png

どんなファイルがあるか、見てみましょう。

bitnami@ip-172-26-4-233:~$ ls -la
total 40
drwxr-xr-x 4 bitnami bitnami 4096 Aug  8 12:35 .
drwxr-xr-x 3 root    root    4096 Feb  6  2020 ..
lrwxrwxrwx 1 bitnami bitnami   17 Feb  6  2020 apps -> /opt/bitnami/apps
-rw-r--r-- 1 bitnami bitnami  220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 bitnami bitnami 4139 Feb  6  2020 .bashrc
-rw------- 1 bitnami bitnami   13 Aug  8 12:35 bitnami_application_password
-r-------- 1 bitnami bitnami  422 Aug  8 12:35 bitnami_credentials
drwx------ 2 bitnami bitnami 4096 Feb  6  2020 .cache
lrwxrwxrwx 1 bitnami bitnami   27 Feb  6  2020 htdocs -> /opt/bitnami/apache2/htdocs
-rw-r--r-- 1 bitnami bitnami  655 Jul 12  2019 .profile
drwx------ 2 bitnami bitnami 4096 Aug  8 12:33 .ssh
lrwxrwxrwx 1 bitnami bitnami   12 Feb  6  2020 stack -> /opt/bitnami

はい、この中のbitnami_credentialsに、Redmineの管理用ログインIDとパスワードが記載されているのです。

console2.png

このファイルにある赤枠部分がログインID、青枠部分がパスワードになります。

Redmineに接続してみる

インスタンスに対してパブリックIPアドレスが割り当てられています。

address.png

上記例ですとhttp://54.92.33.40にアクセスしてみましょう。

Redmine.png

Redmineの画面が出てきました!

管理者でログインする

では、bitnami_credentialsに記載されていた情報でログインしてみましょう。
画面右上にある「ログイン」をクリックします。

Redmine2.png

そして、ログインIDとパスワードを入力し、ログインします。

Redmine3.png

ログインできました!

Redmine5.png

あとは、「Administration」から、各種設定を行ってください。
初期状態は英語表示ですが、日本語表示も問題なく動作します。

ネットワークの設定を変更したい

実はLightsailのネットワークはVPCで構成されていません。
ある意味、これが管理を簡略化できる要因の一つだったりもしますが、例えば、IPアドレスはインスタンス起動ごとに変更になってしまうので固定したい、だとか、IPアドレスではなくホスト名(ドメイン名)でアクセスしたい、などといったことが、実は可能です。
その場合は、最初の画面にある「ネットワーキング」をクリックしてみましょう。

networking1.png

networking2.png

バックアップをとる

スナップショット

インスタンスに接続して所定の方法でバックアップを取得する方法もありますが、最も簡単なのはスナップショットをとってしまうことです。
インスタンスのメニューから「管理」を選択します。

Lightsail5.png

管理画面が表示されるので、この中から「スナップショット」を選択します。

manage1.png

今回は手動スナップショットを作成してみましょう。

snapshot1.png

スナップショットの名称を入力します。
今回はデフォルト設定のまま作成してみます。

snapshot2.png

間も無く「スナップショット作成中」の状態になります。

snapshot4.png

しばらくたつと、完成します。

snapshot6.png

今回は手動でスナップショットを取得しましたが、自動スナップショットの場合、定期的に取得してくれるようになります。

スナップショットの使い道

作成済みのスナップショットは、ホーム画面で確認することができます。
ホーム画面の「スナップショット」をクリックしてみましょう。

snapshot1.png

先ほど取得したスナップショットが確認できました。
では、スナップショットをどう使うかというと、スナップショットの右側にあるメニューを開いてみましょう。

snapshot8.png

snapshot9.png

スナップショットを使ってできることは

  • 新しくインスタンスを作成して、そこに復元する
  • 別のリージョンにスナップショットをコピーする
  • EC2にエクスポートする
  • スナップショットを削除する

です。RDSのスナップショットなどとできることはほぼ同じようなものですが、EC2にエクスポートできるのが特徴的です。

EC2にエクスポートしてみた

せっかくここまでやってみたので、EC2にエクスポートするとどうなるか見てみたいと思います。
メニューから選択すると、確認画面が表示されます。
ここで「はい、続行します」を選択します。

ec21.png

セキュリティの案内が出るので「承諾」。

ec22.png

なんか、歯車がクルクル回り出した!!

ec23.png

しばらく経つと、完了します。

ec25.png

EC2

EC2側での確認

では、どのような状態でエクスポートされているか、ですが、インスタンスとしてエクスポートされるわけではなく、EC2のスナップショットとして復元されます。
EC2の管理画面より、EBSのスナップショットを確認してみましょう。

あったー!!

ebs1.png

では、せっかくなんで、ここからインスタンスとして復元してみましょう。

ebs2.png

「アクション」から「ボリュームの作成」を選択します。

ebs7.png

そのままの状態で「ボリュームの作成」ボタンを押します。

ebs9.png

少し経つと、ボリュームが出来上がります。

ebs10.png

EC2インスタンスに復元

ちょっと長くなったので、省略しますが、手順はオーソドックスです。

  1. EC2インスタンスを作成
    • Redmineに接続したいので、Public Subnetを割り当ててください
    • EBSの設定は適当でよいです(サイズもあってなくてよいです)
  2. インスタンスが起動したら停止(終了ではないですよ!)
    • この時、インスタンスのルートデバイス(/dev/xvdaなど)を確認しておいてください
  3. インスタンスのステータスが「stopping」から「stopped」になったら、ボリューム一覧の画面で、インスタンスからデタッチする。
  4. LightsailからエクスポートしたボリュームをEC2インスタンスにアタッチする
    • ルートデバイスをちゃんと指定しましょう
    • インスタンスのアベイラリティゾーンとあっていないとアタッチできないので、あっていない場合はボリューム作成時のアベイラリティゾーンをEC2インスタンスにあわせて作り直しましょう
  5. 停止中だったEC2インスタンスを開始する

ここまできたら、インスタンスのパブリックIPアドレスにブラウザでアクセスするだけです。

ec28.png

きたー!!!!
もちろん、Lightsailでアクセスした、管理者のログインIDとパスワードでログインもできます。
もしアクセスできない場合は、EC2インスタンスにアタッチされているセキュリティグループにHTTP(80)のポートをインバウンドルールに追加してみてください。

まとめ

ご覧のように、AWS上でOSSを安価にさくっと立ち上げる方法として、Lightsailという選択肢が有効であることがおわかりいただけたかと思います。
また、スケーリングの必要性などが出てきた場合は、EC2インスタンスとして移行することも、非常に簡単に行えることがわかったかと思います。
これまで、EC2やECSの影に隠れがちで、あまりメジャーな感じがしなかったLightsailですが、こんな簡単に導入できるのです。
結構おすすめです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS] 安価で簡単にRedmineの環境を作成・管理できるよ!そうLightsailならね

Redmine

今更説明する必要もありませんが、Webベースのプロジェクト管理ツールです。
AWSでは、DB(PostgreSQL、MySQL)や全文検索(Elasticsearch)、あるいはコンテナ管理(Kubernetes)など、様々なOSSがマネージドサービスとして提供されていますが、残念ながらプロジェクト管理や課題管理をしてくれるサービスがありません(2020年8月現在)。

これらのサービスを利用しようと思ったら、

  • EC2インスタンス上で起動
  • ECSでRedmineのコンテナを起動

少なくともAWS上で、ということを考えると、この辺がすぐに思い浮かぶと思います。

などが考えられると思います。
が、しかし、もっと簡単な方法があるのです。それも定額で。

Amazon Lightsail

AWSでの仮想サーバといえば、すぐに思い浮かぶのは、やはりEC2だと思います。
EC2は、多種多様なインスタンスやOSの選択ができる一方、課金がインスタンスの稼働時間に依存するため、気がつくと高額になっている、なんてことも実はない話ではありません。
しかし、Lightsailは、なんと定額利用できる、仮想サーバなのです。
EC2でもリザーブドインスタンスなどを利用すれば、かなりコストを抑えることができますが、それも及ばないほど、しかも定額で利用することができるのです。

価格

いくつか、インスタンスタイプが用意されています。
これはEC2と同様で、メモリ、CPUコア数、ディスク容量、データ転送量の組み合わせです。
あと、OSは、LinuxとWindowsが選択できますが、価格面ではWindowsの方がやや高い設定になっています。

  • Linux (Amazon Linux / Ubuntu / Debian / FreeBSD / OpenSUSE)
    • 3.5 USドル/月 - 160.0 USドル/月
  • Windows Server
    • 8.0 USドル/月 - 240.0 USドル/月

なお、データ転送量のみ、それぞれのプランの上限を超過した場合に、超過分のみの追加料金が発生します。

「Amazon Lightsail 料金表」

用意されているOSS

もう一つ特徴的なのが、いくつかのOSSが自動設定可能になっています。
そう、自分でインストールして初期設定して、といった作業が不要になるのです。
提供されているOSSは、以下の通りです。

  • WordPress
  • WordPress Multisite
  • LAMP
  • Node.js
  • Joomla!
  • Magento
  • MEAN
  • Drupal
  • GitLab CE
  • Remine
  • Nginx
  • Ghost
  • Django
  • Plesk
  • cPanel & WHM

Redmineを使えるようにするまで

Lightsailでのインスタンス作成

まず、サービス一覧より、Lightsailを選択します。

Lightsail1.png

ここで「インスタンスの作成」を押します。

次の画面で、

  • リージョン(アベイラビリティゾーン)
  • OS
  • OSS
  • インスタンスプラン

などを選択します。

lightsail2.png

今回は、東京リージョン上に、Linux+Redmineで、月額3.5$のインスタンス作成してます。
すると、下記のように「保留中」という状態になり、1分もしないうちに「実行中」に変わります。

Lightsail3.png

Lightsail4.png

これだけで、RedmineがセットアップされたLinux環境ができあがりました。

Redmineユーザの確認

まず、インスタンスに接続してみます。
インスタンスの枠の右上にあるメニューをクリックします。

Lightsail50.png

その中から「接続」を選択してください。

Lightsail5.png

ステータスが「実行中」になった直後だと、まだインスタンスに接続できないことがあります。
その場合は、1、2分経ってから接続してみてください。

接続できると、ブラウザで別のウィンドウが開き、コンソールが表示されます。

console1.png

どんなファイルがあるか、見てみましょう。

bitnami@ip-172-26-4-233:~$ ls -la
total 40
drwxr-xr-x 4 bitnami bitnami 4096 Aug  8 12:35 .
drwxr-xr-x 3 root    root    4096 Feb  6  2020 ..
lrwxrwxrwx 1 bitnami bitnami   17 Feb  6  2020 apps -> /opt/bitnami/apps
-rw-r--r-- 1 bitnami bitnami  220 Aug 31  2015 .bash_logout
-rw-r--r-- 1 bitnami bitnami 4139 Feb  6  2020 .bashrc
-rw------- 1 bitnami bitnami   13 Aug  8 12:35 bitnami_application_password
-r-------- 1 bitnami bitnami  422 Aug  8 12:35 bitnami_credentials
drwx------ 2 bitnami bitnami 4096 Feb  6  2020 .cache
lrwxrwxrwx 1 bitnami bitnami   27 Feb  6  2020 htdocs -> /opt/bitnami/apache2/htdocs
-rw-r--r-- 1 bitnami bitnami  655 Jul 12  2019 .profile
drwx------ 2 bitnami bitnami 4096 Aug  8 12:33 .ssh
lrwxrwxrwx 1 bitnami bitnami   12 Feb  6  2020 stack -> /opt/bitnami

はい、この中のbitnami_credentialsに、Redmineの管理用ログインIDとパスワードが記載されているのです。

console2.png

このファイルにある赤枠部分がログインID、青枠部分がパスワードになります。

Redmineに接続してみる

インスタンスに対してパブリックIPアドレスが割り当てられています。

address.png

上記例ですとhttp://54.92.33.40にアクセスしてみましょう。

Redmine.png

Redmineの画面が出てきました!

管理者でログインする

では、bitnami_credentialsに記載されていた情報でログインしてみましょう。
画面右上にある「ログイン」をクリックします。

Redmine2.png

そして、ログインIDとパスワードを入力し、ログインします。

Redmine3.png

ログインできました!

Redmine5.png

あとは、「Administration」から、各種設定を行ってください。
初期状態は英語表示ですが、日本語表示も問題なく動作します。

ネットワークの設定を変更したい

実はLightsailのネットワークはVPCで構成されていません。
ある意味、これが管理を簡略化できる要因の一つだったりもしますが、例えば、IPアドレスはインスタンス起動ごとに変更になってしまうので固定したい、だとか、IPアドレスではなくホスト名(ドメイン名)でアクセスしたい、などといったことが、実は可能です。
その場合は、最初の画面にある「ネットワーキング」をクリックしてみましょう。

networking1.png

networking2.png

バックアップをとる

スナップショット

インスタンスに接続して所定の方法でバックアップを取得する方法もありますが、最も簡単なのはスナップショットをとってしまうことです。
インスタンスのメニューから「管理」を選択します。

Lightsail5.png

管理画面が表示されるので、この中から「スナップショット」を選択します。

manage1.png

今回は手動スナップショットを作成してみましょう。

snapshot1.png

スナップショットの名称を入力します。
今回はデフォルト設定のまま作成してみます。

snapshot2.png

間も無く「スナップショット作成中」の状態になります。

snapshot4.png

しばらくたつと、完成します。

snapshot6.png

今回は手動でスナップショットを取得しましたが、自動スナップショットの場合、定期的に取得してくれるようになります。

スナップショットの使い道

作成済みのスナップショットは、ホーム画面で確認することができます。
ホーム画面の「スナップショット」をクリックしてみましょう。

snapshot1.png

先ほど取得したスナップショットが確認できました。
では、スナップショットをどう使うかというと、スナップショットの右側にあるメニューを開いてみましょう。

snapshot8.png

snapshot9.png

スナップショットを使ってできることは

  • 新しくインスタンスを作成して、そこに復元する
  • 別のリージョンにスナップショットをコピーする
  • EC2にエクスポートする
  • スナップショットを削除する

です。RDSのスナップショットなどとできることはほぼ同じようなものですが、EC2にエクスポートできるのが特徴的です。

EC2にエクスポートしてみた

せっかくここまでやってみたので、EC2にエクスポートするとどうなるか見てみたいと思います。
メニューから選択すると、確認画面が表示されます。
ここで「はい、続行します」を選択します。

ec21.png

セキュリティの案内が出るので「承諾」。

ec22.png

なんか、歯車がクルクル回り出した!!

ec23.png

しばらく経つと、完了します。

ec25.png

EC2

EC2側での確認

では、どのような状態でエクスポートされているか、ですが、インスタンスとしてエクスポートされるわけではなく、EC2のスナップショットとして復元されます。
あと、カスタムAMIとしても作成されますが、今回は、スナップショットから復元する方法で進めてみようと思います。

まずは、EC2の管理画面より、EBSのスナップショットを確認してみましょう。

あったー!!

ebs1.png

では、せっかくなんで、ここからインスタンスとして復元してみましょう。

ebs2.png

「アクション」から「ボリュームの作成」を選択します。

ebs7.png

そのままの状態で「ボリュームの作成」ボタンを押します。

ebs9.png

少し経つと、ボリュームが出来上がります。

ebs10.png

EC2インスタンスに復元

ちょっと長くなったので、省略しますが、手順はオーソドックスです。

  1. EC2インスタンスを作成
    • Redmineに接続したいので、Public Subnetを割り当ててください
    • EBSの設定は適当でよいです(サイズもあってなくてよいです)
  2. インスタンスが起動したら停止(終了ではないですよ!)
    • この時、インスタンスのルートデバイス(/dev/xvdaなど)を確認しておいてください
  3. インスタンスのステータスが「stopping」から「stopped」になったら、ボリューム一覧の画面で、インスタンスからデタッチする。
  4. LightsailからエクスポートしたボリュームをEC2インスタンスにアタッチする
    • ルートデバイスをちゃんと指定しましょう
    • インスタンスのアベイラリティゾーンとあっていないとアタッチできないので、あっていない場合はボリューム作成時のアベイラリティゾーンをEC2インスタンスにあわせて作り直しましょう
  5. 停止中だったEC2インスタンスを開始する

ここまできたら、インスタンスのパブリックIPアドレスにブラウザでアクセスするだけです。

ec28.png

きたー!!!!
もちろん、Lightsailでアクセスした、管理者のログインIDとパスワードでログインもできます。
もしアクセスできない場合は、EC2インスタンスにアタッチされているセキュリティグループにHTTP(80)のポートをインバウンドルールに追加してみてください。

まとめ

ご覧のように、AWS上でOSSを安価にさくっと立ち上げる方法として、Lightsailという選択肢が有効であることがおわかりいただけたかと思います。
また、スケーリングの必要性などが出てきた場合は、EC2インスタンスとして移行することも、非常に簡単に行えることがわかったかと思います。
これまで、EC2やECSの影に隠れがちで、あまりメジャーな感じがしなかったLightsailですが、こんな簡単に導入できるのです。
結構おすすめです!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メモ:Elastic IPアドレスの一覧を取得する

バージョン

$ aws --version
aws-cli/2.0.25 Python/3.7.4 Darwin/19.5.0 botocore/2.0.0dev29
$ jq --version
jq-1.6

コマンド

aws ec2 describe-addresses | jq '.Addresses[].PublicIp' > eip.csv
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS AmplifyのAuthで取得した認証情報をAWS SDKのCredentialProviderChainを利用して埋め込む

前回からの続き

AWS Amplify Libraryで取得した認証をAWS SDKで利用する

CredentialProviderChainから認証が通ってAWS SDKの処理が動いた時点で書き込んでおり、肝心の認証情報が失効したあとの自動更新はまだ確認していません。確認できたら追記します。

とりあえず、調べてもほとんど情報が出てこない、CredentialProviderChainの使い方メモ的にでも残しておく。

やること

Amplifyの Auth.currentUserCredentials() で取得した認証情報は1時間で失効する。この方法で取得した認証情報をAWS SDKに引き継いで利用する場合などにおいて、1時間以上時間が空いたあとに処理を再開すると使えなくなる。

( SPAなどでアプリケーションを提供していて、1時間以上画面を放置した場合とか)

それを回避するために、自前で認証情報が失効するタイムスタンプを見て、切れていたら認証情報を取得し直すというコードを書いていると煩雑になるので、AWS SDKの CredentialProviderChain を利用して、認証情報の再発行を任せる。

仕組みについて

前回の記事から続いてS3を例とする。前回は初期化時の引数について、credentialフィールドに認証情報(アクセスキーなど)を直接渡していたが、今回はcredentialProviderを使う。

const s3 = new AWS.S3({
    credentialProvider: provider,
    region: '任意のリージョン',
});

このcredentialProviderはAWS SDK側で下記のように定義されている。

aws-sdk/lib/credentials/credential_provider_chain.d.ts
import {Credentials} from '../credentials';
import {AWSError} from '../error';
export class CredentialProviderChain {
    /**
     * Creates a new CredentialProviderChain with a default set of providers specified by defaultProviders.
     */
    constructor(providers?: provider[])
    /**
     * Resolves the provider chain by searching for the first set of credentials in providers.
     */
    resolve(callback:(err: AWSError, credentials: Credentials) => void): CredentialProviderChain;
    /**
     * Return a Promise on resolve() function
     */
    resolvePromise(): Promise<Credentials>;
    /**
     * Returns a list of credentials objects or functions that return credentials objects. If the provider is a function, the function will be executed lazily when the provider needs to be checked for valid credentials. By default, this object will be set to the defaultProviders.
     */
    providers: Credentials[]|provider[];

    static defaultProviders: provider[]
}

type provider = () => Credentials;

コンストラクタにproviderの定義を満たす実装を渡しておくと、渡した順番に認証情報を評価し、利用可能なものがあればそれを使う動きをするらしい。providerはCredentialsを返す関数型となっている。そのため、Credentialsの定義を満たしたクラスを渡せばいいらしい。

Credentialsの定義が長いので省略するが、この定義を満たす実装がAWS SDKからいくつか提供されているのでそれを使っても良さそうだが、今回の記事のテーマのようなものは用意されていないので自作することとなった。事前に用意されているものは、JSONファイルから認証情報をパースしたりするものなどで、例としては下記。

import {Credentials} from '../credentials';
export class FileSystemCredentials extends Credentials {
    /**
     * Creates a new FileSystemCredentials object from a filename.
     * @param {string} filename - The path on disk to the JSON file to load.
     */
    constructor(filename: string);
    /**
     * The path to the JSON file on disk containing the credentials.
     */
    filename: string
}

↑はTypeScriptの定義だけなので、JavaScript側の実装まで追いかけると、Credentialクラスのメソッドをオーバーライドして、必要箇所の実装を満たすようになっている。実装をしておくと、各種AWS SDK側のコードから、認証情報を読み取って利用してくれる仕組みになっている。

AmplifyのAuthをproviderとして渡すクラスを自作する

実装がまだ荒削りかもしれないが(コンストラクタの中身とか)、↓を実装した。必要最低限のメソッドをオーバーライドしている。認証情報が失効しているかをチェックするメソッドなどもあり、最初はそれをオーバーライドして中身でDateの比較を実装していたが、this.expireTimeに渡すと、スーパークラス側(Credentialsクラス)で判定してくれるようなので、値のセットだけにした。

Promiseで取り扱うためのメソッドと、そうではないメソッドで2つ別れていて、それがどういう条件で使い分けされるかわわからないが(refreshとrefreshPromise)、両方に対応できるようにしておいた。また、片言のコメントに書いてあるように、Auth.currentUserCredentials()の返却値の定義にはexpirationは定義されていないが、実態には含まれる。

import { Auth } from '@aws-amplify/auth';
import * as AWS from 'aws-sdk';

export class AmplifyAuthCredentials extends AWS.Credentials {
    constructor() {
        super('', '', '');
    }

    refresh(callback: (err?: AWS.AWSError) => void) {
        this.setCredentials().then(() => callback()).catch(callback);
    }

    async refreshPromise() {
        return this.setCredentials();
    }

    private async setCredentials() {
        try {
            const credentials = await Auth.currentUserCredentials();
            this.accessKeyId = credentials.accessKeyId;
            this.secretAccessKey = credentials.secretAccessKey;
            this.sessionToken = credentials.sessionToken;
            this.expired = false;
            // Amplify's Auth.currentUserCredentials contains expiration date time with string object.
            this.expireTime = new Date((credentials as any).expiration);
        } catch (error) {
            return Promise.reject(error);
        }
    }
}

このクラスを下記のように使う。ログにS3のファイル一覧が出るはず。

import * as AWS from 'aws-sdk';

const provider = new AWS.CredentialProviderChain([
    () => new AmplifyAuthCredentials(),
]);
const s3 = new AWS.S3({
    credentialProvider: provider,
    region: region,
});
s3.listObjectsV2({ Bucket: '任意のバケット名'})
    .promise()
    .then(console.log)
    .catch(console.error);

まとめ

CredentialProviderChain に 自作のクラスを渡す方法はかなり探しても見つからなかったが、上記のような方法で実現は可能。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FlutterとAWSで始めるサービス開発 (8)Cognitoの認証情報を使ってAPIを呼び出す

はじめに

(5)AWS Cognitoでログイン」や、「(7)AWS Cognito Googleでログイン」で、Cognitoを使ったログイン処理を一通り実装完了しました。今回はそれら認証情報がないと呼び出せないAPIを実装していきたいと思います。前回まででCognito ユーザープールからID Tokenを取得するところまではできています。APIを呼び出すために、ID Tokenを要求し、それを検証してから実行するAPIを作っていきます。つまり、ログインした場合だけサービスのAPIを呼び出せるというモデルを実現するための構成です。認証した上でさらに特定の権限を持っているユーザーだけが利用できるといったAPIも考えられますが、今回はそこまでは踏みこみません。

参考文献

APIの作成

早速APIを作成していきます。AWSマネージメントコンソールからLambdaを開き、関数の作成を押下します。
lambda1.jpg

まず、設計図の使用を選択し、httpでフィルタします。microservice-http-endpoint-pythonを選択します。python版の選択理由は単に筆者の職場では、Lambdaのアプリをpythonで書いていて慣れているからです。
lambda2.jpg

関数名には任意の名前を入れてください。実行ロールは、基本的なLambdaアクセス権限で新しいロールを作成を選択します。
lambda3.jpg

API Gaeway トリガーではAPICreate an APIAPI TypeRESTセキュリティオープンを選択します。API名は任意に決めてください。入力が終わったら追加ボタンを押下します。
lambda4.jpg

Lambda関数ができるので、関数のコードを書き換えます。
lambda5.jpg

以下が、変更後のコードです。一部元のソースを流用していますが、ほぼ書き換えてしまっています。本実装では、呼び出し時に渡されたeventの一部(event['requestContext']['authorizer']['claims'])を返却することだけをしています。event['requestContext']['authorizer']['claims']に関しては後ほど解説したいとおもいます。

import boto3
import json

print('Loading function')


def respond(err, res=None):
    return {
        'statusCode': '400' if err else '200',
        'body': err.message if err else json.dumps(res),
        'headers': {
            'Content-Type': 'application/json',
        },
    }


def lambda_handler(event, context):

    response =  {
            "claims" : event['requestContext']['authorizer']['claims']
        }

    return respond(None, response)

以上で、APIの骨格が完成しましたが、次に、APIにCognitoとの認証連携を設定していきます。マネージメントコンソールでAPI Gatewayを開くと、先ほど作成したAPIが表示されるので選択します。

lambda6.jpg

オーソライザーを選択し、新しいオーソライザーの作成を押下します。名前には任意の名前を入力し、タイプCognitoを選択します。Cognitoユーザープールでは、作成してあるプールを選択し、トークンのソースAuthorizationを入力し、作成ボタンを押下します。
apigw0.jpg
リソースを選択し、作成したAPIのANYを選択し、メソッドリクエストを選択します。
apigw1.jpg

認可Cognitoユーザープールオーソライザーで先ほど作成したオーソライザーを選択します。
apigw2.jpg

アクションからAPIのデプロイを選択し、デプロイされるステージdefaultを選択しデプロイボタンを押下します。
apigw3.jpg

apigw4.jpg

以上でサーバー側の設定が完了です。

APIの呼び出し

ここまでで作ったAPIをFlutterのアプリケーションから呼び出します。
(5)AWS Cognitoでログインで作成したトップ画面を修正します。
まず、画面にAPIの呼び出し結果をテキストで表示する機能を追加します。非同期処理になるので、FutureBuilder<>を利用します。FutureにAPIを呼び出し、レスポンスを文字列で返却する関数である_invokeApi()を設定します。builderではデータを受け取ったら、API呼び出し結果をTextで返却、データを受け取らない間は、CircularProgressIndicator()で処理中を表示するようにしています。
引き続き、_invokeApi()の中身を確認します。先ほど作成したAPIの呼び出しコードです。Autorizationヘッダに、(5)AWS Cognitoでログイン(7)AWS Cognito Googleでログインで取得した、CognitoUserSessionインスタンスの、JWT形式IDトークンを設定しGET呼び出しを行っています。また、APIの呼び出し結果のResponse.bodyを戻り値としています。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('トップページ'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('ログイン成功'),
            Divider(color: Colors.black),
            FutureBuilder<String>(
                future: _invokeApi(session),
                builder: (context, AsyncSnapshot<String> snapshot) {
                  if (snapshot.hasData) {
                    return Text(snapshot.data);
                  } else {
                    return CircularProgressIndicator();
                  }
                })
          ],
        ),
      ),
    );
  }

  Future<String> _invokeApi(CognitoUserSession session) async {
    String url =
        "https://2wmzck1189.execute-api.ap-northeast-1.amazonaws.com/default/testCognito";
    final response = await http.get(url,
        headers: {'Authorization': session.getIdToken().getJwtToken()});
    if (response.statusCode != 200) {
      throw Exception("Received bad status code from API:" +
          response.statusCode.toString() +
          "; body: " +
          response.body);
    }

    return response.body;
  }
}
``

実際に本アプリを実行すると以下のように画面にレスポンスが表示されます。

![app1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273926/5bae9119-23dc-8c2f-e282-6d89cbb36ef3.jpeg)


以上で、最低限のアプリケーションは完成です。

# API側での処理
API GatewayのオーソライザーでCognitoID Tokenが検証できた場合、Lambda側には検証結果の情報として、Cognitoユーザーの情報がわたってきます。
前述した``event['requestContext']['authorizer']['claims']``にID Tokenの検証結果としてログインしているユーザーの情報が入ります。以下が具体的な、cliamsの中身になります。__sub__Cognitoのユーザープール内でユーザーを一意に識別するIDがわたってくるので、アプリケーションはそのキーを使って、ユーザーごとの処理を実装していくことが可能です。

``` json
"claims": {
  "at_hash": "jC8Q3e2kh1LmzloesriHHw",
  "sub": "bdec0872-9384-453f-99f9-c8c50dee23db",
  "aud": "***********************",
  "cognito:groups": "ap-northeast-1_*********_Google",
  "identities": "{"dateCreated":"1594465378058","userId":"100299533705733233603","providerName":"Google","providerType":"Google","issuer":null,"primary":"true"}",
  "token_use": "id",
  "auth_time": "1595514490",
  "iss": "https://cognito-idp.ap-northeast-1.amazonaws.com/ap-northeast-1_*********",
  "cognito:username": "google_100299533705733233603",
  "exp": "Thu Jul 23 15:28:10 UTC 2020",
  "iat": "Thu Jul 23 14:28:10 UTC 2020"
}

まとめ

Cognitoでログインして取得したIDトークンを利用してAPIの呼び出しを実施するところまでできました。これでサービス開発の最低限の流れはできたと思います。本シリーズはいったんここまでで終わりたいと思います。ここまで実装したコードを一通りリファクタリングしたり、エラー処理を拡充して、もう少し人に見せられそうなレベルに持っていけたらソースコードを公開しようかなと思います。また、あまり技術的なことは深く突っ込まず、どう設定すれば動くのかに比重をおいて進めてきましたが、もう少し技術面を整理してその辺の情報も公開できれば名智考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2にPostgreSQLをインストールする方法(動作確認用)

インストール

sudo yum update # 省略していい
sudo yum install postgresql postgresql-libs postgresql-server # 何をインストールしているのか未調査
sudo service postgresql initdb # これをしないと起動できないらしい
sudo service postgresql start
sudo service postgresql status
sudo chkconfig postgresql on # EC2起動時にPostgreSQLを起動してくれるらしい

psqlを使う

postgresqlインストール時に作られるpostgresユーザに切り替える。
このユーザじゃないとpsqlを使えない。

sudo su - postgres
psql
create database testdb;
\l
\c testdb

メモ

postgresユーザに切り替えるのが不便で分かりにくい。
安全のための設定と思われる。

参考

AmazonLinuxにPostgreSQL9.6をインストールする
https://qiita.com/shinsaka/items/cf45cd87c2c75e35ce12

PostgreSQLの基本的なコマンド
https://qiita.com/H-A-L/items/fe8cb0e0ee0041ff3ceb

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【今日から始めるAWS】LambdaでLINEのbotをつくる

はじめに

30代未経験からエンジニア転職をめざすコーディング初学者のYNと申します。お読みいただきありがとうございます。
コーディング初学者にとってのAWS入門といえばLambda!、サーバレスアプリを作ろう!、ということでメッセージをオウム返ししてくるLINEのbotをつくりました。
下記参考記事をそのままコピーした内容になってしまったのですが、学習ログとして投稿させていただきました。

今回やったこと

下記のように、こちらが送ったテキストメッセージをそのまま返答してくれる、オウム返しbotを作ります。
スクリーンショット 2020-08-08 11.16.49.png

下記のサーバレス構造を構築します。(こちらの記事から図を拝借させていただきました。)
スクリーンショット 2020-08-08 11.09.26.png

手順

  • 事前準備
  • Lambdaの設定
  • API-gatewayの設定
  • LINEチャンネルの設定

事前準備

  • LINEデベロッパー登録

Lambdaの設定

  • 1. ローカルPCでlambda関数のフォルダを作成
$ cd ~
$ mkdir line-bot
$ cd line-bot
  • 2. index.jsを作成
$ npm install @line/bot-sdk
line-bot/index.js
"use strict";
const line = require("@line/bot-sdk");
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });
// ①SDKをインポート

const crypto = require("crypto");

exports.handler = function (event, context) {
  let body = JSON.parse(event.body);
  let signature = crypto
    .createHmac("sha256", process.env.CHANNELSECRET)
    .update(event.body)
    .digest("base64");
  let checkHeader = (event.headers || {})["X-Line-Signature"];

  if (signature === checkHeader) {
  // ②cryptoを使ってユーザーからのメッセージの署名を検証する


    if (body.events[0].replyToken === "00000000000000000000000000000000") {   
      let lambdaResponse = {
        statusCode: 200,
        headers: { "X-Line-Status": "OK" },
        body: '{"result":"connect check"}',
      };
      context.succeed(lambdaResponse);
      // ③接続確認エラーを確認する。

    } else {
      let text = body.events[0].message.text;
      const message = {
        type: "text",
        text,
      };
      client
        .replyMessage(body.events[0].replyToken, message)
        .then((response) => {
          let lambdaResponse = {
            statusCode: 200,
            headers: { "X-Line-Status": "OK" },
            body: '{"result":"completed"}',
          };
          context.succeed(lambdaResponse);
        })
        .catch((err) => console.log(err));
      // ④リクエストとして受け取ったテキストをそのまま返す

    }
  } else {
    console.log("署名認証エラー");
  }
};

下記、index.jsにおける処理を解説します。

①SDKをインポートする

const line = require("@line/bot-sdk");
const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });

githubのexampleでSDKの使い方が参照できます。

②cryptoを使ってユーザーからのメッセージの署名を検証する

const crypto = require("crypto");
exports.handler = function (event, context) {
  let body = JSON.parse(event.body);
  let signature = crypto
    .createHmac("sha256", process.env.CHANNELSECRET)
    .update(event.body)
    .digest("base64");
  let checkHeader = (event.headers || {})["X-Line-Signature"];
  if (signature === checkHeader) {
    // ここでlambdaの処理を記載
  }

cryptoはnode.jsに標準で組み込まれている暗号化に関するライブラリで、botにおける実装の詳細についてはLINE公式ドキュメントに書いてあります。
(暗号化についてはこちらの記事が分かりやすかったです)

ここでは、下記2点が一致するかを検証しています。

  • signature: ユーザーから送られてきたリクエスト(event.body)を、秘密鍵(CHANNELSECRET)を使って暗号化したもの

  • event.headers["X-Line-Signature"]:リクエストヘッダーに含まれる署名

③接続確認の場合の処理を実装する

botが接続確認を行う場合、replyToken="000..."のリクエストが来ます。

let body = JSON.parse(event.body);
if (body.events[0].replyToken === "00000000000000000000000000000000"){
      let lambdaResponse = {
        statusCode: 200,
        headers: { "X-Line-Status": "OK" },
        body: '{"result":"connect check"}',
      };
      context.succeed(lambdaResponse);
      }

④リクエストとして受け取ったテキストをそのまま返す

ユーザーから送られてきたリクエストの中身(イベントオブジェクト)はLINE公式ドキュメントにまとめられています。
リクエストに含まれるメッセージがテキストの場合、body.events[0].message.textに含まれます。
また、client.replyMessage(replyToken, message)のメソッドを使うことで、ユーザーにメッセージを返信することが出来ます。

let text = body.events[0].message.text;
   const message = {
     type: "text",
     text,
   };
   client
     .replyMessage(body.events[0].replyToken, message)
     .then((response) => {
       let lambdaResponse = {
         statusCode: 200,
         headers: { "X-Line-Status": "OK" },
         body: '{"result":"completed"}',
       };
       context.succeed(lambdaResponse);
     })
  • 3. index.jsとnode_modulesを圧縮しLambdaにアップロードする

スクリーンショット 2020-08-08 17.59.05.png

  • 4. アクセストークン(ACCESSTOKEN)とChannelSecret(CHANNELSECRET)を環境変数に登録する

スクリーンショット 2020-08-08 18.04.54.png

API-gatewayの設定

  • 1 POSTメソッドを作成する
    スクリーンショット 2020-08-08 18.13.44.png

  • 2 リクエストヘッダーによる認証を追加する
    スクリーンショット 2020-08-08 18.15.21.png

  • 3 統合リクエストに作成したLambda関数を設定する
    スクリーンショット 2020-08-08 18.18.58.png

  • 4 APIをデプロイ
    スクリーンショット 2020-08-08 19.24.39.png

LINEチャンネルの設定

LINE Official Account Managerからユーザーからのメッセージに対するあいさつや応答の設定をすることができます。
スクリーンショット 2020-08-08 19.29.07.png

最後に

Lambdaを使うことで、簡単にLINEのbotを作ることができます。
初学者でも1時間弱でつくることができました。
機械学習や外部のAPIを組み合わせれば、面白いことができそうです。
ご覧いただきありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】 押さえておくべきポイント <IAM, Security Group, AMI>

はじめに

以下のリンクがシンプルでわかりやすい説明のため、とても参考になりました。

【AWS初心者がインフラ設計/構築を理解するために、まず最初におさえるべきキーワード15選】

対象範囲

上記記事内で実践した内容には以下が含まれています。

使用した機能

  1. リージョン (Region)
  2. アベイラビリティーゾーン (AZ:Availability Zone)
  3. サブネット(Subnet)
  4. セキュリティグループ (Security Group)
  5. IAM (Identity and Access Management)
  6. ACL (Access Control List)

押さえておくべきポイント

  • IAM
  • セキュリティグループ(EC2)
  • AMI

上記の説明に対してはもう少し踏み込んで理解したいと思ったので、下記をまとめました。

IAM

【AWS】IAMまとめ:IAMとは?
【AWS】IAMまとめ:IAMにできること

以下のリンクは写真付きなので、作業工程もイメージしやすいです。

新人プログラマの為のAWS入門 ~導入編~:IAMロール・ユーザ作成

セキュリティグループ(EC2)

初学者のためのAWS入門(1):セキュリティグループとは

世界一丁寧なAWS解説:セキュリティーグループの作成

AMI

<AWS>AMIとスナップショットの違いについて

Amazon Web Services(AWS) のEC2でインスタンスをコピーする方法

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Dynamo DBへのcsvインポートを手軽に実装(Windows・無料)

はじめに

Tech Dive様の記事 を大変参考にさせて頂きました。この場を借りて御礼申し上げます。

この記事は上記内容をWindows環境で実施した際の備忘録になります。

筆者の環境

・Windows 10
・Git Bash

手順

  1. Python3 のインストール

  2. Pandasのインストール pip install pandas

  3. aws cliのインストール
    インストールしてもGit Bash で aws コマンドが使えなかったので、GitBashで
    cmd \\C aws --version
    を叩いてから再起動したら使えるようになりました

  4. アクセスキーの確認
    AWS ユーザーメニューの「マイセキュリティ資格情報」から、インポート用のアクセスキーを新規作成

  5. アクセスキーとシークレットアクセスキーの登録
    aws configure を叩き、先ほど作ったアクセスキーを登録

    aws configure
    
    AWS Access Key ID [None]: [アクセスキー]
    AWS Secret Access Key [None]: [シークレットアクセスキー]
    Default region name [None]: us-east-2 ※テーブルがあるリージョン
    Default output format [None]: json
    
  6. CSVのヘッダーを変更
    先頭行をデータベースのkeyと合わせて、型を()でくくって指定
    例) キーが「UserName」(String)なら UserName (S)

  7. import_to_dynamodb をclone
    git clone https://github.com/hidesan-xyz/import_to_dynamodb.git

  8. cloneしたディレクトリのルートにインポートするcsvファイルを置く

  9. import用shellコマンドの作成
    python create_insert_command.py importdata testtable
    ※第一引数にcsv名(拡張子は記載しない) 第二引数にテーブル名

  10. 作成されたシェルコマンドの実行 sh ./testtable_import_20200808164839.sh

上記手順で完了です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSとAzureの比較(リージョン・AZ)

※個人の調査によるものです。誤りがあればご指摘ください
※2020/8/8現在の情報をもとにまとめました。

AWSとAzureのリージョンなどの考え方を比較しました。

AWS

分類レベル 名称 定義
レベル小 データセンター 1つの物理的なデータセンター
レベル中 アベイラビリティゾーン (AZ) 1 つの AWS リージョン内でそれぞれ切り離され、冗長的な電力源、ネットワーク、そして接続機能を備えている 1 つ以上のデータセンターのことです。各 AZ はそれぞれ他の AZ から物理的に意味のある距離、つまり数キロメートル離れていますが、すべて 100 km 以内 (互いに 60 マイル) に配置されています。
レベル大 リージョン 1 つの地理的エリアにある、複数の、それぞれが隔離され物理的にも分離された AZ によって構成されています。1 つのデータセンターを 1 つのリージョンとして定義することが多い他のクラウドプロバイダーとは違い、全 AWS リージョンが採用するこのマルチ AZ デザインは、お客様にいくつかのメリットをご提供するものです。

大阪ローカルリージョンは特殊で、

  • 分離された耐障害性の高いインフラストラクチャデザインが 1 つのデータセンター内で構成されます。
  • アジアパシフィック (大阪) ローカルリージョンは 1 つのアベイラビリティーゾーンで構成されており、アジアパシフィック (東京) リージョンと組み合わせて使用することが意図されています。
  • このリージョンには、お客様のセールス担当者からのリクエストが必要です。

とのこと。

他にはLocal Zonesというものがあるようです。現在はロサンゼルスのみで提供されています。

https://aws.amazon.com/jp/about-aws/global-infrastructure/
https://aws.amazon.com/jp/about-aws/global-infrastructure/regional-product-services/
https://aws.amazon.com/jp/about-aws/global-infrastructure/regional-product-services/

Azure

分類レベル 名称 定義
Azure データセンター ネットワークに接続されたコンピューター サーバーのグループを収容する、世界中に存在する一意の物理的な建物です。
Azure Availability Zones Azure リージョン内の一意の物理的な場所であり、データセンターの障害からアプリケーションとデータを保護する高可用性を提供しています。それぞれのゾーンは、独立した電源、冷却手段、ネットワークを備えた 1 つまたは複数のデータセンターで構成されています。
Azure リージョン Azure リージョンは、待機時間で定義された境界内でデプロイされ、低遅延の専用リージョン ネットワークを使用して接続された一連のデータセンターです。
特大 Azure 地域 通常、1 つ以上のリージョンを含み、データ所在地とコンプライアンスの境界を保持します。

AWSにはない、「地域」というものが出てきました。リージョンも地域のような気もしますが、英語名はAzure リージョンがAzure regionで、Azure 地域がAzure geographyです。
東日本リージョンと西日本リージョンはペアに指定されているようで、このAzure 地域に該当するようです。

ペアになっているリージョンのメリットとしては、以下が上げらててました
* 物理的な分離
* プラットフォームに備わっているレプリケーション
* リージョン復旧順序
* 順次更新
* データ所在地

https://azure.microsoft.com/ja-jp/global-infrastructure/
https://azure.microsoft.com/ja-jp/global-infrastructure/geographies/#geographies
https://docs.microsoft.com/ja-jp/azure/best-practices-availability-paired-regions

日本国内リージョンの比較

比較項目 AWS東京リージョン AWS大阪ローカルリージョン Azure東日本リージョン Azure西日本リージョン
開設時期 2011年 2018年 2014年 2014年
AZ数 4 1 3 1

調べていると、AWS東京リージョンに4つ目のリージョンが開設されたのが、2018年、Azure東日本リージョンがAZに対応したのが2019年のようです。この間はAWS東京リージョンが4AZ、Azure東日本リージョンが1AZとかなりの差があったような形ですね。
また、AWS大阪ローカルリージョンは2021年初頭までに3つのAZをもつフルリージョンになるようです。

https://aws.amazon.com/jp/blogs/news/the-fourth-new-availability-zone-tokyo-region/
https://azure.microsoft.com/ja-jp/updates/general-availability-azure-availability-zones-in-japan-east/
https://aws.amazon.com/jp/blogs/news/in-the-works-aws-osaka-local-region-expansion-to-full-region/

まとめ

AWS側のほうが知識があるので、AWS押しのような内容となりましたが、Azure側の押しポイントがあれば教えて下さい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSサーバレス環境でSPARQLエンドポイントを作ろうとしたが上手くいかなかった話

個人的にSPARQLエンドポイントを作るときに、運用やメンテナンスが楽になればということでサーバレスでSPARQLエンドポイント作れないかと思い、AWSのサーバレス環境での構築に挑戦してみました。

結論から言うと、ちゃんと動きましたが、期待してたよりもうまくいきませんでした。

一応今回使ったコードは以下で公開していますが、利用される場合は以下を最後まで読まれることをお勧めします。

https://github.com/uedayou/quadstore-server-on-aws-serverless

環境

Lambda と API Gateway はAWS SAMでデプロイするようにしました。
サーバレス環境の設定をあらかじめ指定しておけるのでデプロイが楽ですし、ある程度の設定もコード上で細かく変更できていいですね。

SPARQLエンドポイントサーバ自体のコードは、node-quadstorelevelDB を使いました。

あらかじめRDFファイルを使ってlevelDBファイルを生成しておき、それを利用してSPARQLクエリで検索結果を得るような形です。

cd quadstore-server-on-aws-serverless/quadstore-server-lambda
npm install
npm run build:db -- ../sample/isillod.ttl

パフォーマンス

この環境でどれくらい使えるか、トリプル数と以下の3つのクエリ毎の検索時間を計ってみました。
Lambdaのメモリは1024MB、タイムアウトは30秒(API Gatewayが30秒でタイムアウトするため)で設定しています。
データセットは「図書館及び関連組織のための国際標準識別子(ISIL)」試行版LODを使いました。
上記サイトでは Turtleファイルを分割して公開しています。これらを1つずつ追加して作成したDBファイル毎に計測しています。

(1) トリプルを100件取得

select * where {?s ?p ?o} limit 100

(2) 全トリプル数を取得

select (count(*) as ?count) where {?s ?p ?o}

(3) filterを使って文字列の絞り込み

prefix schema: <http://schema.org/>
prefix org:   <http://www.w3.org/ns/org#>
prefix dbpedia: <http://dbpedia.org/ontology/>

select * where {
  ?uri dbpedia:originalName ?name;
  org:hasSite/org:siteAddress/schema:addressRegion ?pref.
  filter( regex(?pref, "東京") )
}
limit 10
トリプル数 (1) (2) (3)
21,788 0.192 秒 2.85 秒 5.20 秒
42,585 0.186 秒 4.35 秒 7.53 秒
63,448 0.193 秒 5.58 秒 11.08 秒
84,587 0.181 秒 9.74 秒 14.02 秒
104,826 0.163 秒 11.47 秒 16.19 秒
124,718 0.225 秒 14.19 秒 21.83 秒
144,669 0.244 秒 12.64 秒 12.11 秒
160,491 0.220 秒 12.91 秒 12.91 秒

若干ゆれがありますが、概ねトリプル数の増加とともに検索時間が増えてますが、(2)、(3) はその増え方がかなり大きく、トリプル数取得するだけで10秒もかかるのはどうかと思いました。API Gatewayの仕様で30秒でタイムアウトするので、これ以上のトリプル数だと結果が得られずタイムアウトする可能性が高いです。

どうやら全スキャンがかかるようなクエリの実行のより多くの時間がかかっているっぽい感じがします。例えば、(1) に order by 追加するだけで極端に時間がかかるようになりました。ちなみに、Lambdaはメモリサイズを増やすとCPUの性能もアップするらしいですが、最大の3008MBにしても大きな差はなかったです。

個人的な用途でいろいろ目をつぶるにしても、10万トリプルぐらいが限界かなと思います。

実際にその遅さを体感したい人がいれば、

鉄道駅LODのSPARQLエンドポイントを実験的に公開しました
https://qiita.com/uedayou/items/3ba823c5d3bede12af9c

こちらを使ってみてください。
30万トリプル登録していますが、複雑なクエリはタイムアウトしてしまいます。

まとめ

  • (個人的には)SPARQLエンドポイント構築は簡単になった
  • ちゃんとSPARQLで検索ができる
  • トリプル数とクエリによっては実行が遅い、最悪タイムアウトする
  • トリプル数が少ないデータセットなら一応運用は可能

今回使っているRDFストアnode-quadstoreの開発中のブランチを見てみるとSPARQLクエリ実行の高速化も現在対応しているっぽい?ので、将来的に結構使えるものになるかもしれません。ちょっとこの方法を試すには時期が早かったのかも...

今後この方法とは違う方法でも、AWSサーバレスでSPARQLエンドポイント構築挑戦してみたいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】Cross-Account CICD

000.JPG

要件(ソースアカウント)

IAM

CodePipeline用IAM RoleはターゲットアカウントのCodeBuildとCodeDeployのIAM RoleをAssumeできる必要がある。

S3

Bucket PolicyはターゲットアカウントのRootを許可する。

KMS

ソースアカウントでCMKを作成し、ターゲットアカウントのRootを許可する。

CodeCommit

通常通りに作成する。

EventBridge

CodeCommitのRepositoryの特定のBranchが作成または更新した際に、CodePipelineを起動する。

Events:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - CodeCommit Repository State Change
        resources:
          - !Sub arn:aws:codecommit:ap-northeast-1:${AWS::AccountId}:${CodeCommitRepositoryName}
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - !Ref RepositoryBranch
          repositoryName:
            - !Ref CodeCommitRepositoryName
      State: ENABLED
      Targets:
        - Arn: !Sub arn:aws:codepipeline:ap-northeast-1:${AWS::AccountId}:${CodePipeline}
          RoleArn: !ImportValue EventsInvokePipelineRole
          Id: "Pipeline"

CodePipeline

CodeCommit、CodeBuild、CodeDeployのActionを設定する。
注意点として以下2つがある。

  • 使用するKMS KeyはソースアカウントのCMKとする
  • CodeBuildとCodeDeployにはIAMを設定し、ターゲットアカウントのCodeBuildとCodeDeployのIAM Roleを指定する
CodePipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Type: S3
        Location: !Ref S3Bucket
        EncryptionKey:
          Id: !Sub arn:aws:kms:ap-northeast-1:${AWS::AccountId}:key/${KmsId}
          Type: KMS
      RestartExecutionOnUpdate: False
      RoleArn: !ImportValue CodePipelineRole
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: "1"
                Provider: CodeCommit
              OutputArtifacts:
                - Name: PreBuild_SourceCode
              Configuration:
                RepositoryName: !Ref CodeCommitRepositoryName
                BranchName: !Ref RepositoryBranch
                PollForSourceChanges: "false"
              RunOrder: 1
        - Name: Build
          Actions:
            - Name: Build
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              RunOrder: 1
              RoleArn: !Sub arn:aws:iam::${ProdAccount}:role/CrossAccountPipelineRole
              Configuration:
                ProjectName: !Ref CodeBuildProjectName
                PrimarySource: PreBuild_SourceCode
              InputArtifacts:
                - Name: PreBuild_SourceCode
              OutputArtifacts:
                - Name: PostCodeBuild_Artifact
        - Name: Deploy
          Actions:
            - Name: Deploy
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CodeDeployToECS
                Version: "1"
              Configuration:
                TaskDefinitionTemplateArtifact: PostCodeBuild_Artifact
                AppSpecTemplateArtifact: PostCodeBuild_Artifact
                AppSpecTemplatePath: "appspec.yaml"
                ApplicationName: !Ref CodeDeployApplicationName
                DeploymentGroupName: DeploymentGroup
                Image1ArtifactName: PostCodeBuild_Artifact
                Image1ContainerName: "IMAGE1_NAME"
              RunOrder: 1
              RoleArn: !Sub arn:aws:iam::${ProdAccount}:role/CrossAccountPipelineRole
              InputArtifacts:
                - Name: PostCodeBuild_Artifact

要件(ターゲットアカウント)

IAM

CodeBuildとCodeDeploy用のIAMはソースアカウントのRootからのAssumeを許可する必要がある。
CodeBuildとCodeDeploy用のIAM RoleにソースアカウントのCMKとS3の関連権限を設定する。

CodeBuild

通常通りに作成する。
TaskDefinitionのIamgeを更新の都度バージョン番号を記載しないよう、buildspec.ymlとtaskdef.jsonを以下のように記載する。

buildspec.yml
version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
  build:
    commands:
      - echo build
      - docker build -t $IMAGE_REPO_NAME:$IMAGE_TAG .
      - docker tag $IMAGE_REPO_NAME:$IMAGE_TAG $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG
      - printf '{"Version":"1.0","ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.ap-northeast-1.amazonaws.com/$IMAGE_REPO_NAME:$IMAGE_TAG > imageDetail.json
artifacts:
  files:
    - imageDetail.json
    - appspec.yaml
    - taskdef.json

taskdef.json
{
    "family": "JavaHelloWorld",
    "networkMode": "awsvpc",
    "containerDefinitions": [{
        "name": "ecs-test-codecommit-repository",
        "image": "<IMAGE1_NAME>",
        "portMappings": [{
            "containerPort": 8080,
            "hostPort": 8080,
            "protocol": "tcp"
        }],
        "essential": true
    }],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "256",
    "memory": "512",
    "executionRoleArn": "arn:aws:iam::<AccountId>:role/ecsTaskExecutionRole"
}

CodeDeploy

CloudFormationはECSのBlue/GreenのDeploymentGroupを作成できないため、Custom Resourceで作成する。

LambdaCodeDeploy:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        ZipFile: |
          import json
          import boto3
          import cfnresponse
          def lambda_handler(event, context):
            print(event)

            if event['RequestType'] == 'Delete' or event['RequestType'] == 'Update' :
                responseData = {}
                responseData['Data'] = 'test'
                cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData, "CustomResourcePhysicalID")

            client = boto3.client('codedeploy')
            applicationName = event['ResourceProperties']['applicationName']
            deploymentGroupName = event['ResourceProperties']['deploymentGroupName']
            serviceRoleArn = event['ResourceProperties']['serviceRoleArn']
            targetGroups1 = event['ResourceProperties']['targetGroups1']
            targetGroups2 = event['ResourceProperties']['targetGroups2']
            listenerArns = event['ResourceProperties']['listenerArns']
            serviceName = event['ResourceProperties']['serviceName']
            clusterName = event['ResourceProperties']['clusterName']

            response = client.create_deployment_group(
                applicationName=applicationName,
                deploymentGroupName=deploymentGroupName,
                serviceRoleArn=serviceRoleArn,
                autoRollbackConfiguration={
                    'enabled': True,
                    'events': ['DEPLOYMENT_FAILURE']
                },
                deploymentStyle={
                    'deploymentType': 'BLUE_GREEN',
                    'deploymentOption': 'WITH_TRAFFIC_CONTROL'
                },
                blueGreenDeploymentConfiguration={
                    'terminateBlueInstancesOnDeploymentSuccess': {
                        'action': 'TERMINATE',
                        'terminationWaitTimeInMinutes': 5
                    },
                    'deploymentReadyOption': {
                        'actionOnTimeout': 'CONTINUE_DEPLOYMENT',
                        'waitTimeInMinutes': 0
                    }
                },
                loadBalancerInfo={
                    'targetGroupPairInfoList': [
                        {
                            'targetGroups': [
                                {
                                    'name': targetGroups1
                                },
                                {
                                    'name': targetGroups2
                                }
                            ],
                            'prodTrafficRoute': {
                                'listenerArns': [
                                    listenerArns,
                                ]
                            }
                        },
                    ]
                },
                ecsServices=[
                    {
                        'serviceName': serviceName,
                        'clusterName': clusterName
                    },
                ]
            )
            cfnresponse.send(event, context, cfnresponse.SUCCESS, response, "CustomResourcePhysicalID")
      Handler: index.lambda_handler
      Runtime: python3.7
      Timeout: 30
      Role: !ImportValue LambdaExecutionRole

  CreateCodeDeployGroup:
    Type: Custom::LambdaCallout
    Properties:
      ServiceToken: !GetAtt LambdaCodeDeploy.Arn
      applicationName: !Ref CodeDeploy
      deploymentGroupName: DeploymentGroup
      serviceRoleArn: !ImportValue CodeDeployRole
      targetGroups1: !ImportValue targetgroup1-name
      targetGroups2: !ImportValue targetgroup2-name
      listenerArns: !ImportValue listener1-arn
      serviceName: !ImportValue ecs-servicename
      clusterName: !ImportValue ecs-clustername

appspec.yamlのTaskDefinitionは以下のように記載する。

appspec.yaml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: "<TASK_DEFINITION>"
        LoadBalancerInfo:
          ContainerName: "ecs-test-codecommit-repository"
          ContainerPort: 8080
        PlatformVersion: "LATEST"

ECS + ELB

通常通りに作成する。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2 Amazon Linux 2 + nginx で Laravel の環境構築の際によく使うコマンド

yumアップデート

sudo yum update -y

nginxのインストール

sudo amazon-linux-extras install nginx1.12 -y

// nginxの起動
sudo systemctl start nginx

// 自動起動設定(これをやっておくとEC2再起動などの際に自動で起動してくれる)
sudo systemctl enable nginx

// nginxのステータス確認(起動失敗時などに調査で使用する)
systemctl status nginx.service

PHPのインストール

sudo amazon-linux-extras install php7.3

// Laravelを動かすためには php-xmlとphp-mbstringが必要
sudo yum install php-xml php-mbstring

Composerのインストール

sudo curl -sS https://getcomposer.org/installer | php

// パスを通す
sudo mv composer.phar /usr/local/bin/composer

Laravelのインストール

// インストール先のディレクトリに権限を付与する
sudo chmod -R 777 /usr/share/nginx/html

cd /usr.share/nginx/html

composer create-project --prefer-dist laravel/laravel app_name

Laravelのセットアップ

cd app_name

cp .env.example .env

php artisan コマンドを使えるようにする

composer install
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む