- 投稿日:2021-08-29T23:17:54+09:00
【AWS】windowsサーバを準備⇒リモートデスクトップ⇒日本語化
はじめに 勉強したことや、やりたいシナリオの作業記録を残していきます。 できるだけすべての画像を残していきます。 用語系は都度補足を入れていきます。 無料利用枠内で実施 今回のシナリオ AWSアカウントは作成済みとし、AWSダッシュボードから作業開始します。 AWSでwindowsサーバを準備 リモートデスクトップでサーバに接続 日本語化 【用語解説】 ・AWS(Amazon Web Services) Amazonが提供している100以上のクラウドコンピューティングサービスの総称。 クラウド上にサーバやDBを構築したりできる。 シナリオスタート 1.windowsサーバを準備 ①AWSマネジメントコンソールを表示し、EC2画面に移動 【用語解説】 ・EC2(Elastic Compute Cloud) 仮想サーバーを作れるサービス Elasticは「弾力のある、しなやかな、柔軟性のある」という意味 ②インスタンスの起動をクリック ③無料利用枠の「Microsoft Windows Server 2019 Base」をクリック ④インスタンスタイプに「t2 micro」を選択し、「確認と作成をクリック」 ⑤以下設定でEC2インスタンスが作成されます。右下の「起動」をクリック 【用語解説】 ・セキュリティグループ インスタンスの仮想ファイアウォールとして機能し、インバウンドトラフィックとアウトバウンドトラフィックをコントロールします。 EC2をデフォルト設定で作成した場合は、新たにセキュリティグループが作成され、RDPプロトコル(リモートデスクトップ)が許可されています。 ⑥キーペア名に好きな文字列を入れてキーペアをダウンロードした後、インスタンスの作成をクリック ⑦作成ステータスが表示 ⑧以下作成ステータス画面になったらインスタンスの表示をクリック ⑨EC2のインスタンスが作成されたことを確認 作成したWindowsサーバへリモートデスクトップ接続 ①作詞したインスタンスを選択して、接続をクリック ②RDPクライアントタブを選択し、リモートデスクトップファイルのダウンロードをクリック。 リモートデスクトップファイルをダウンロードしたら、パスワードの取得をクリック。 ③キーペアを参照するためにBrowseをクリック ④上で作成ダウンロードしたpemファイルを選択し開くをクリック ⑤パスワードを復号化をクリック ⑥リモートデスクトップ接続時に使用するパスワードが表示されるのでメモしておき、キャンセルをクリック ⑦エクスプローラ上で先ほどダウンロードしたリモートデスクトップファイルをダブルクリック ⑧警告が表示されたら接続をクリック ⑨上でメモしたパスワードを入力し、OKをクリック ⑩はいをクリック ⑪作成したWindowsサーバのデスクトップが表示されます。 【メモ】 リモートデスクトップ先にYes,Noの選択が表示されます。放置してもいいですが気になる場合はNoを選択しておけばOKです。 Do you want to allow your PC to be discoverable by other PCs and devices on this network? (このネットワーク上の他のPCやデバイスが自分のPCを検出できるようにしますか?) We recommend allowing this on your home and work networks,but not public ones. (自宅や職場のネットワークではこれを許可することをお勧めしますが、公共のネットワークでは許可しないでください。) 言語を日本語に変更する システムロケールを変更 ①左したのWindowsメニューから歯車アイコンをクリック ②設定画面からTime & Languageをクリック ③Time zoneをUTC+09:00) Osaka, Sapporo, Tokyoを選択 【メモ】 システムロケールを変更すると右下の時刻が日本時間に変更されます。 Windowsの表示言語を日本語に変更 ①LanguageメニューのAdd a language横の+をクリック ②japanで検索し日本語を選択し、Nextをクリック ③installをクリック ④ダウンロードが開始されます。(ダウンロード完了まで15分ほどかかります。) ⑤Windows display languageが日本語になっていること、下部の日本語がリストの上に来ていることを確認 ⑥再起動後にWindows表示言語は反映されるため、Windowsメニューから電源マークをクリックし、Restartをクリック ⑦Continueをクリック ⑧再起動が開始され、リモートデスクトップが自動的に終了します。 「Unicode対応ではないプログラムの言語」の設定を日本語に設定 ①リモートデスクトップで再度Windowsサーバに接続 ②画面左下の検索ボタンをクリックし、コントロールと入力。コントロール パネルが表示されるのでクリック ③表示方法を小さいアイコンに変更し、地域をクリック ④管理タブをクリックしシステムロケールの変更をクリック ⑤現在のシステムロケールを日本語(日本)に変更しOKをクリック ⑥今すぐ再起動をクリック 【メモ】 「Unicode対応ではないプログラムの言語」の設定を日本語に設定しないと、コマンドプロンプトやパワーシェルで日本語が文字化けします。 ⑦再起動が完了したら再度リモートデスクトップでWindowsサーバに接続して通常通り使用可能です。 最後に AWSを使う上で基本となるEC2の最初の一歩といった記事です。 特に使ってないようなら今回作成したいインスタンスは停止しておくことをお勧めします。 次回の記事では作成したサーバをADサーバとして設定するシナリオを進めていきます。
- 投稿日:2021-08-29T23:10:24+09:00
Renesas RX72N Envision KitでAWSに"Hello World"を送信する
環境準備 Renesas RX72N Envision KitでAWSに"Hello World"を送信してAWS側で送信されたメッセージを確認してみました。 今回はAWS IoT Coreで必要な設定を済ませてから、Renesasの統合開発環境e² studioでMQTTデモプログラムを作成/起動します。 準備した環境は以下の通りです。 RX72N Envision Kit (評価ボード) e² studio 2021-07 (統合開発環境) GCC for Renesas 8.3.0.202102-GNURX Windows (RXマイコン用コンパイラ) AWS IoT Coreの設定 まずはAWS IoT CoreでRX72N Envision Kitを登録して、AWSにRX72N Envision Kitが接続できるようにします。 AWSにログインして、AWSマネジメントコンソールを開きます。 その後、「すべてのサービス」->「IoT」->「IoT Core」をクリックしてAWS IoT Coreの設定画面を開きます。 ポリシーの作成 AWSと通信するための接続許可等を定義するポリシーを作成します。 「安全性」->「ポリシー」->「作成」をクリックして、ポリシーの作成画面を開きます。 ポリシーの名前を入力したら「アドバンストモード」をクリックして、下記のコードをエディタ画面に貼り付けます。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "iot:Connect", "Resource": "*" }, { "Effect": "Allow", "Action": "iot:Publish", "Resource": "*" }, { "Effect": "Allow", "Action": "iot:Subscribe", "Resource": "*" }, { "Effect": "Allow", "Action": "iot:Receive", "Resource": "*" } ] } コードを貼り付けたら以下のようになります。 貼り付けたら「作成」をクリックして、ポリシーの作成を完了します。 モノの作成 次にRX72N Envision KitをAWSに登録します。 「管理」->「モノ」->「モノを作成」をクリックして、モノの作成画面を開きます。 「1つのモノを作成」にチェックを入れたら「次へ」をクリックします。 次の画面で「モノの名前」を入力して更に「次へ」をクリックします。 (ほかにも色々設定できますが、特に設定は行わず、このまま進めます) 更に次の画面で「新しい証明書を自動生成 (推奨)」にチェックを入れて「次へ」をクリックします。 アタッチするポリシーを選択する画面で、作成したポリシーを選択して「モノを作成」をクリックします。 「証明書とキーをダウンロード」で「証明書」、「プライベートキー」、「パブリックキー」をダウンロードしておきます。 (ここでダウンロードする「証明書」、「プライベートキー」は後で使用します) Renesas e² studioの起動およびプログラムの作成 AWS IoT Coreの設定が完了したら次にe² studioを起動して、"Hello World"を送信するプログラムを作成します。 FreeRTOSプロジェクトの作成 e² studioを起動したら、「ファイル」から「新規」->「Renesas C/C++ Project」->「Renesas RX」をクリックします。 「GCC for Renesas RX C/C++ Executable Project」を選択して「次へ」をクリックします。 (今回はGCCコンパイラを使用するので、こちらを選択します) プロジェクトの名前を入力したら「次へ」をクリックします。 次の画面で、以下のようにFreeRTOS (with IoT libraries) + RX72N Envision Kitを使用するプロジェクトに設定します。 (RX72N Envision Kitはボードに実装されたE2 Liteをデバッグに使用するので、E2 Liteを選択しておきます) (FreeRTOSは2021/08時点で最新版であるv202002.00-rx-1.0.5を使用します) 設定が完了したら、「終了」をクリックしてFreeRTOSプロジェクトを作成します。 AWS接続情報の設定 プロジェクトの作成が完了したら、プロジェクトにAWS接続情報を設定します。 「application_code」->「demos」->「include」から「aws_clientcredential.h」をダブルクリックで開きます。 「clientcredentialMQTT_BROKER_ENDPOINT」にエンドポイント名を入力します。 (エンドポイント名はIoT Coreの「設定」->「デバイスデータエンドポイント」から確認することができます) 「clientcredentialIOT_THING_NAME」に作成したモノの名前を入力します。 次に「証明書」と「プライベートキー」の情報をプロジェクトに設定します。 以下の場所に格納されている「CertificateConfigurator.html」をダブルクリックで開きます。 C:\Users\%username%.eclipse\com.renesas.platform_download\RTOS\afr-v202002.00-rx-1.0.5\tools\certificate_configuration ブラウザが開くので、「Certificate PEM file」にAWSからダウンロードした「証明書」を指定しています。 「Private Key PEM file」にAWSからダウンロードした「プライベートキー」を指定します。 「Generate and save aws_clientcredential_keys.h」をクリックしてヘッダーファイルを適当な場所に保存します。 ダウンロードしたヘッダーファイルをe² studio上の「application_code」->「demos」->「include」フォルダにドラッグアンドドロップしてコピーします。 FreeRTOSプロジェクトのビルドおよび起動 まずRX72N Envision KitのECN1コネクタとPCをUSBケーブルを介して接続して、Ethernetケーブルを介して使用しているルーターにRX72N Envision Kitを接続しておきます。 (ルーターのDHCP機能を有効にしておいてください、正常にプログラムがビルドできません) e² studio上部のトンカチマークをクリックして、FreeRTOSプロジェクトをビルドします。 ビルドログでエラーメッセージが表示されなければビルド完了です。 その後、同じくe² studio上部の虫マーク横の下三角マークをクリックして、「デバッグの構成」をクリックします。 下記のように、メインクロック/EXTAL周波数、接続タイプ、エミュレータからの電源供給のデバッグ設定を変更します。 設定が完了したら画面下部のデバッグをクリックして、プログラムのダウンロード/デバッグを開始します。 デバッグ開始後、下記の画面でプログラムが停止するので、e² studio上部のRestartボタンを1回押して、main関数内でプログラムが停止するのを確認します。 AWS IoT Coreで送信されたメッセージの確認 AWS側でRX72N Envision Kitが送信した"Hello World"メッセージを確認できるように設定を行います。 IoT Core左側のメニューから「テスト」をクリックします。 トピックのフィルターに「#」を入力して「サブスクライブ」をクリックします。 (「#」はワイルドカードでこの設定ではすべてのトピックを表示することを意味します) e² studio上部の再開ボタンを押してプログラムを実行します。 メッセージの送信が開始されるので、以下のようにAWS側でメッセージを受信していることを確認します。 さいごに RX72N Envision Kitを使用してAWSに"Hello World"メッセージを送信するまでの手順を紹介しました。 今回は紹介しませんでしたが、TeraTermを使用すればFreeRTOSの実行ログを確認しながらデバッグを行えるので便利です。
- 投稿日:2021-08-29T22:35:28+09:00
基本情報技術者試験勉強0829
本日の積み上げ結果 基本情報技術者試験勉強 令和元年秋過去問 パーフェクトラーニング(午後対策) 間違えた問題の復習 AWSの本を読む 情報セキュリティにおいてバックドアに該当するものはどれか。 アクセスする際にパスワード認証などの正規の手続が必要なWebサイトに,当該手続を経ないでアクセス可能なURL コードから商品の内容が容易に分かるようにしたいとき,どのコード体系を選択するのが適切か。 表意コード アクセス制御を監査するシステム監査人の行為のうち,適切なものはどれか。 データに関するアクセス制御の管理規程を閲覧した。 マルウェアの動的解析に該当するものはどれか。 検体をサンドボックス上で実行し,その動作や外部との通信を観測する。 サービスマネジメントシステムにPDCA方法論を適用するとき,Actに該当するものはどれか。 サービスマネジメントシステム及びサービスのパフォーマンスを継続的に改善するための処置を実施する。 技術経営におけるプロダクトイノベーションの説明として,適切なものはどれか。 新たな商品や他社との差別化ができる商品を開発すること 自社の経営課題である人手不足の解消などを目標とした業務革新を進めるために活用する,RPAの事例はどれか。 業務システムなどのデータ入力,照合のような標準化された定型作業を,事務職員の代わりにソフトウェアで自動的に処理する。 FS関係(Finish-to-Start,終了-開始) 「受付が終了してから試験を開始する」というように、あるアクティビティの終了が、他方のアクティビティの開始条件になっている関係。 バランススコアカードの内部ビジネスプロセスの視点における戦略目標と業績評価指標の例はどれか。 製品の製造の生産性向上が目標であるので,製造期間短縮日数を指標とする。 バックアップ方式の説明のうち,増分バックアップはどれか。ここで,最初のバックアップでは,全てのファイルのバックアップを取得し,OSが管理しているファイル更新を示す情報はリセットされるものとする。 直前に行ったバックアップの後,ファイル更新を示す情報があるファイルだけをバックアップし,ファイル更新を示す情報はリセットする。 単一の入り口をもち,入力項目を用いた複数の判断を含むプログラムのテストケースを設計する。命令網羅と判定条件網羅の関係のうち,適切なものはどれか。 判定条件網羅を満足するならば,命令網羅も満足する。 WPA3はどれか。 無線LANのセキュリティ規格
- 投稿日:2021-08-29T22:19:21+09:00
TerraformでDMSレプリケーションインスタンスを自動構築する(ロギング編)
はじめに DMS記事第2弾。 DMSは便利だが、マネージドに色々やってくれる分、中身がブラックボックスで、いざ何かが起きたときにログがないと不安である。 そういったケースに備えて、CloudWatch Logsへのログ出力の設定をしておこう。 DMSのインスタンスやタスクについては、基本編の構成を変更していくので、未読の方はそちらからご覧いただきたい。 IAMロールの作成 いきなりハマりどころである。DMSのサービスロールをテキトーに作ってCloudWatch Logsに出力する権限をつけておけばいいんだろ、と思いたくなるが、ロール名をdms-cloudwatch-logs-role固定で作らないといけない。ユーザーガイドに書かれたロール名は例として書かれているわけではないのである。 ということで、以下のように作成しておこう。 resource "aws_iam_role" "dms_cloudwatch" { name = "dms-cloudwatch-logs-role" assume_role_policy = data.aws_iam_policy_document.dms_cloudwatch_assume.json } data "aws_iam_policy_document" "dms_cloudwatch_assume" { statement { effect = "Allow" actions = [ "sts:AssumeRole", ] principals { type = "Service" identifiers = [ "dms.amazonaws.com", ] } } } resource "aws_iam_role_policy_attachment" "dms_cloudwatch" { role = aws_iam_role.dms_cloudwatch.name policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonDMSCloudWatchLogsRole" } CloudWatch Logsのロググループ DMSが勝手に作ってくれるが、勝手に作られるのが気に入らない場合は、以下の通りロググループ名が決められるので、あらかじめ作っておこう。 なお、ログストリーム名は、dms-task-[レプリケーションタスクID]が設定される。 resource "aws_cloudwatch_log_group" "dms_replication_task" { name = "dms-tasks-[レプリケーションインスタンス名]" } ロギングの設定 ロギングの設定は、基本編のレプリケーションのタスクを変更する。 具体的には、Loggingのセクションだ。CloudWatchLogGroupとCloudWatchLogStreamはDMS側で勝手に設定してくれる(というか、設定するとエラーになる)ので、EnableLoggingだけ変更しよう。LogComponentsはエラーレベルを変更できるので、必要に応じて変更しよう。 なお、この項目はなぜかインスタンス作成時には有効化できない。 一度作成後に、EnableLoggingをfalseからtrueに変更して、再度terraform applyしよう。 dms_replication_task_setting.json(抜粋) (前略) "EnableLogging": true, "CloudWatchLogGroup": null, "CloudWatchLogStream": null, "LogComponents": [ { "Id": "SOURCE_UNLOAD", "Severity": "LOGGER_SEVERITY_DEFAULT" }, { "Id": "SOURCE_CAPTURE", "Severity": "LOGGER_SEVERITY_DEFAULT" }, { "Id": "TARGET_LOAD", "Severity": "LOGGER_SEVERITY_DEFAULT" }, { "Id": "TARGET_APPLY", "Severity": "LOGGER_SEVERITY_DEFAULT" }, { "Id": "TASK_MANAGER", "Severity": "LOGGER_SEVERITY_DEFAULT" } ] }, (後略) これでDMSのログが出力できるようになった!
- 投稿日:2021-08-29T20:02:59+09:00
Dockerでwordpress構築
はじめに 下記を参考にDockerを使ってWordPressを立ち上げようとしてみたらDB接続確立エラーが発生しました。 その時の対処を備忘録として記載しておきます。 「CentOS7にDockerでWordPressを入れる」 作成環境 Amazon Linux release 2 (Karoo) 作成結果 作業工程 ・Dockerのインストール、Dockerの起動 ・コンテナを作成し、wordpressインストール →DB接続エラーが発生し、中断 ・コンテナ内でvimのインストールとwp-config.phpの編集 →wordpressインストール完了 Dockerのインストール、Docker daemonの起動 ・dockerのインストール yum install docker ・インストール後のバージョン確認 docker -v Docker version 20.10.7, build f0df350 ・Dockerの起動 systemctl start docker ・Dockerの状態確認 systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled) Active: active (running) since Sun 2021-08-29 04:34:09 UTC; 5h 43min ago Docs: https://docs.docker.com Process: 7585 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS) 以下略 ・Dockerの実行 上記サイトではエラーが出ていますが出ませんでした。 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES コンテナを作成し、wordpressインストール コンテナ作成 docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysqlpassword -d mysql:5.7 docker run --name wordpress --link mysql:mysql -d -p 8080:80 wordpress 起動しているコンテナの確認 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5200b39bef3a wordpress "docker-entrypoint.s…" 32 minutes ago Up 32 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp wordpress 3ed4556afe3c mysql:5.7 "docker-entrypoint.s…" 33 minutes ago Up 33 minutes 3306/tcp, 33060/tcp mysql プラウザでを以下URLを叩くとWordPressの画面が表示され、インストール時にDBの接続情報を正しいのを入力しているのにDB接続確立エラーが出てインストールが進まない エラーの対処として以下3点が記載されていた。 「http://VMのIPアドレス:8080」 コンテナ内でvimのインストールとwp-config.phpの編集 エラーの対処としてwp-config.phpにDB接続情報を記載してみたら解決した。 しかしコンテナ内でファイル編集のためにviをするとエラーが発生。 docker exec -it 5200b39bef3a bash vi wp-config.php bash: vi: command not found そこでコンテナ内でvimのインストールの実施 apt-get update apt-get install vim wp-config.phpの編集をして、インストールが進みました vi wp-config.php 以下の箇所に記載 define( 'DB_NAME', 'wpdb' ); /** MySQL database username */ define( 'DB_USER', 'wpadmin' ); /** MySQL database password */ define( 'DB_PASSWORD', 'xxxxxx' );
- 投稿日:2021-08-29T20:02:59+09:00
Dockerでwordpressセットアップ
はじめに 下記を参考にDockerを使ってWordPressを立ち上げようとしてみたらDB接続確立エラーが発生しました。 その時の対処を備忘録として記載しておきます。 「CentOS7にDockerでWordPressを入れる」 作業環境 ・AWS Amazon Linuxサーバ Amazon Linux release 2 (Karoo) ・Docker20.10.7 作業結果 作業工程 ・Dockerのインストール、Dockerの起動 ・コンテナを作成し、wordpressインストール →DB接続エラーが発生し、中断 ・コンテナ内でvimのインストールとwp-config.phpの編集 →wordpressインストール完了 Dockerのインストール、Docker daemonの起動 ・dockerのインストール yum install docker ・インストール後のバージョン確認 docker -v Docker version 20.10.7, build f0df350 ・Dockerの起動 systemctl start docker ・Dockerの状態確認 systemctl status docker ● docker.service - Docker Application Container Engine Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled) Active: active (running) since Sun 2021-08-29 04:34:09 UTC; 5h 43min ago Docs: https://docs.docker.com Process: 7585 ExecStartPre=/usr/libexec/docker/docker-setup-runtimes.sh (code=exited, status=0/SUCCESS) 以下略 ・Dockerの実行 上記サイトではエラーが出ていますが出ませんでした。 docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES コンテナを作成し、wordpressインストール コンテナ作成 docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysqlpassword -d mysql:5.7 docker run --name wordpress --link mysql:mysql -d -p 8080:80 wordpress 起動しているコンテナの確認 docker ps -a CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 5200b39bef3a wordpress "docker-entrypoint.s…" 32 minutes ago Up 32 minutes 0.0.0.0:8080->80/tcp, :::8080->80/tcp wordpress 3ed4556afe3c mysql:5.7 "docker-entrypoint.s…" 33 minutes ago Up 33 minutes 3306/tcp, 33060/tcp mysql プラウザでを以下URLを叩くとWordPressの画面が表示され、セットアップ時にDBの接続情報を正しいのを入力しているのにDB接続確立エラーが出てセットアップが進まない。 エラーの対処として以下3点が記載されていた。 「http://VMのIPアドレス:8080」 コンテナ内でvimのインストールとwp-config.phpの編集 エラーの対処としてwp-config.phpにDB接続情報を記載してみたら解決した。 しかしコンテナ内でファイル編集のためにviをするとエラーが発生。 docker exec -it 5200b39bef3a bash vi wp-config.php bash: vi: command not found そこでコンテナ内でvimのインストールの実施 apt-get update apt-get install vim wp-config.phpの編集をして、セットアップが完了しました。 vi wp-config.php 以下の箇所に記載 define( 'DB_NAME', 'wpdb' ); /** MySQL database username */ define( 'DB_USER', 'wpadmin' ); /** MySQL database password */ define( 'DB_PASSWORD', 'xxxxxx' );
- 投稿日:2021-08-29T17:36:32+09:00
TerraformのModuleの仕組みを図解してみた
Terraformを学び始めた頃、最初は同じディレクトリにすべてのtfファイルを置いて、Resourceブロックの書き方を中心に学び、作っては簡単に削除できる"全能感"がとても楽しかったのを覚えています。 でも、しばらくするとDRYに書きたくなって、Moduleという機能を知りました。 プログラミングでいうところの関数(Function)のようなものですね。 今でこそModuleをある程度読み書きできるようになりましたが、昔はプログラミングの関数のようなものだ、という例えも知らずイメージが全く掴めませんでしたし、DRYに書きたいのに変数や出力の記述が冗長になってしまったりして、使いこなすのが難しいイメージをずっと持っていました。 Moduleを使いこなすには、ディレクトリ構造とか、モジュール化の粒度など、設計の要素も大切なのですが、そういった話はいったん置いておいて、Moduleに変数を渡す、出力を受け取る、といったInput/Outputまわりの仕組みについて整理してみます。 インフラ構成 サンプルのインフラ構成(AWS)はvpcとsubnet1つだけの超シンプル構成です。Moduleの仕組み理解が目的なので、余計なコードは省いてModuleとの受け渡しにフォーカスして書いていきます。 図解してみた 変数を渡すInputの流れが水色の矢印。 出力を受け取るOutputの流れが紫の矢印。 ディレクトリ構造 envディレクトリで環境ごとのフォルダを分けるよくあるパターン。 Moduleのディレクトリは機能別にrootに作成。vpcとsubnetを分けることは普通はありませんが、今回はあくまでサンプルということで。 ソースコードはGithubに置いています。 https://github.com/m-oka-system/terraform-module-sample . ├── env │ ├── dev │ │ ├── main.tf │ │ ├── outputs.tf │ │ ├── provider.tf │ │ ├── terraform.tfvars │ │ └── variables.tf │ └── prd │ :(省略) ├── subnet │ ├── main.tf │ ├── outputs.tf │ └── variables.tf └── vpc ├── main.tf ├── outputs.tf └── variables.tf Root Moduleの変数 /env/dev/ をRootModuleとして開発環境のVPCを構築するケースで考えます。 まず、変数部分。環境ごとに異なる変数はterraform.tfvarsに記述します。 ./env/dev/variable.tf variable "region" { type = string default = "ap-northeast-1" } variable "env" { type = string } variable "cidr" { type = string } ./env/dev/terraform.tfvars env = "dev" cidr = "172.16.0.0/16" モジュールの呼び出し部分 main.tfでvpcとsubnetのModuleを呼んでいます。 sourceはModuleの置いてあるディレクトリを相対パスで記述ですね。 後述しますが、Moduleの中では環境ごとに異なる値を変数で抽象化しているので、Moduleを呼ぶ時に変数の値を渡してあげる必要があります。module.vpc.vpc_idは、VPCモジュールのOutputから受け取った値です。他のModuleで作ったリソースの値を利用したい場合、Outputして受け取る必要があります。プログラミングでいうところの戻り値(Return)みたいなもんですかね。 ./env/dev/main.tf module "vpc" { source = "../../vpc" env = var.env cidr = var.cidr } module "subnet" { source = "../../subnet" vpc_id = module.vpc.vpc_id cidr = var.cidr } VPCモジュール Module側にはリソースを作成するためのResourceブロックを記述します。 繰り返しになりますが、cidr_blockやtag:Nameなど、環境ごとに異なる値を変数化しているので、RootModuleから受け取る変数の値によって動的にリソースを作成できるわけですね。 ./vpc/main.tf resource "aws_vpc" "vpc" { cidr_block = var.cidr tags = { Name = "${var.env}-${var.cidr}" } } 変数はModule側でも宣言しておく必要があります。 ./vpc/variables.tf variable "env" {} variable "cidr" {} VPCIDはサブネットの作成に必要なので、OutputでRootModuleのmain.tfに返してあげます。 ./vpc/outputs.tf output "vpc_id" { value = aws_vpc.vpc.id } Subnetモジュール VPCモジュールでOutputしたVPCのIDをここで使っています。 本筋からは少し逸れますが、cidrsubnet 関数を使って、VPCのCIDRブロックを元にサブネットのCIDRを求めています。 ./subnet/main.tf resource "aws_subnet" "public" { vpc_id = var.vpc_id cidr_block = cidrsubnet(var.cidr, 8, 11) # 172.16.0.0/16 -> 172.16.11.0/24 } Moduleで使う変数の宣言をお忘れなく。 ./subnet/variables.tf variable "vpc_id" {} variable "cidr" {} ここでは、SubnetIDを出力して、RootModuleのmain.tfに返しています。 ./subnet/outputs.tf output "subnet_id" { value = aws_subnet.public.id } Root Moduleの出力 ターミナルの画面に出力したいものはRootModule側でOutputします。サンプルで出力しているのがSubnetのIDですが、これ自体に意味はありません。 ./env/dev/outputs.tf output "public_subnet_id" { value = module.subnet.subnet_id } ここで意識すべきなのは、次の2通りの使い方があるという点だと思います。 Moduleからの戻り値として呼び出し元(main.tf)に返すOutput ターミナルの画面に出力させるためのOutput まとめ Moduleを駆使してリソースを使いまわしできるような設計を学習中ですが、Module化しようとした時にだいたい引っかかるのが 変数の宣言し忘れ 出力のし忘れ なんですよね。なので、冒頭のイメージ図を頭に入れながらInput/Outputの流れを意識しつつ、コーディングするように心がけたいと思います!
- 投稿日:2021-08-29T14:56:42+09:00
LambdaでDropbox内にフォルダを作成する
LambdaでDropbox内にフォルダを作成したときの個人メモ 前提 LambdaでDropbox APIを実行するができていること 実行手順 実行前のDropboxの状態を確認 /create_folderを利用ために必要な権限が「files.content.write」であることを確認 アプリ情報から権限を確認、「files.content.write」にチェックが入れて「submit」を押下 権限変更後リファレンスを再読み込み、アクセストークンを取得する ※権限変更後に再度アクセストークンを取得しない場合、「missing_scope」エラーとなる EXAMPLEからcurlコマンドをコピーしLambdaにペースト、「Test」を押下し、実行 レスポンスを確認 フォルダ(/Homework/math)が作成されていることを確認 感想 アクセストークンの取得をリファレンス上から行えるのはすごく楽 ファイルのアップロードもできるようになりたい
- 投稿日:2021-08-29T14:46:59+09:00
VueのSPAでの動的OGPをS3+Cloudfront+Lambda@Edgeの構成でcdkを使って実現したメモ
概要 VueでSPAを作ってS3に配置し、CloudFront経由で公開できるようにした。 この時、以下の問題が発生する。 ブラウザで直接SPAが生成したURL(例:http://oursite.com/user/id)にアクセスすると404となる (参考:Vue Router HTML5 History モード) SPAのページごとにヘッダを変えられない。 OGPを設定できない。 TwitterにURLをシェアしてもトップのOGPやタイトルしか表示されず残念 OGP画像はTwitterなどのサイトからJSで読み込まれるため、CORSの同一オリジンポリシーにより画像のリクエストはブロックされる S3とCloudFrontにCORSの許可設定が必要 今回はこれを解決するための設定をCloudFront+Lambda@Edgeを利用して行う。 設定にはAWS CDKを利用する。 完了時点のソース 概要図 ユーザからのアクセスにはS3に置かれたSPAのリソースを返す OGPのアクセス(Twitterなど)にはOGP用のレスポンスを作成して返す 検討 この構成にするまでに検討した事項について記載する 使用するCloudFrontイベント CloudFrontイベントは以下の4つが存在する。 今回は、ユーザとTwitterbotの区別をUser-agentを使って行う予定のため、ビューワーリクエストイベントに関数を紐づける。 イベント タイミング キャッシュがある場合 備考 ビューワーリクエスト ビューワーからリクエストを受け取った時 実行する オリジンリクエスト リクエストをオリジンに転送したとき 実行しない 特に設定しないとUser-agentがAmazon CloudFrontに書き換えられた状態で関数に渡される オリジンレスポンス オリジンからのレスポンスを受け取った後 実行しない レスポンス内のオブジェクトをキャッシュする前に関数が実行される。関数は、オリジンからエラーが返された場合でも実行されることに注意。 ビューワーレスポンス リクエストされたファイルがビューワーに返される前 実行する ビューワーリクエストイベントによってトリガーされた関数からレスポンスが生成された場合には実行しない 図はLambda 関数をトリガーできる CloudFront イベントから抜粋 CloudFront Functions vs Lambda@Edge ビューワーリクエストに設定できる関数はCloudFront Functions と Lambda@Edgeの2種類が存在する。 (参考: CloudFront Functions と Lambda@Edge の選択) 今回はOGP用の文言を取得するのにHTTP通信を利用するため、最大実行時間が1msのCloudFront Functionsは不適と考えLambda@Edgeを利用する。 特徴 CloudFront Functions Lambda@Edge プログラミング言語 JavaScript (ECMAScript 5.1 準拠) Node.js と Python 関数コードと含まれるライブラリの最大サイズ 10 KB 1 MB 最大メモリ 2 MB 128MB 最大実行時間 1ミリ秒 * 5秒 CDK CloudFrontのDistributeionについて cloudfrontを行えるクラスがDistributionとCloudFrontWebDistributionの2種類あったため、検討する。 @aws-cdk/aws-cloudfront moduleを見ると、DistributionはCloudFrontWebDistributionを置き換えるもの。そのため、今回はDistributionを利用する。 設定 ここからは設定項目について記載する。 環境 Windows 10 Home nodejs v14.17.5 @aws/cdk 1.120.0 ローカルのフォルダ構造 + client ... SPAのVueファイルが格納されたフォルダ + dist ... SPAのVueファイルのビルド成果物が格納されたフォルダ - cdk - bin - index.ts ... CDKの起点ファイル - lib - cdk-stack.ts ... CDKStackファイル - dist ... lambda@Edgeのデプロイ用ソース。git管理外 - ogp - index.js - src - ogp - index.ts ... lambda@Edgeのソース - build.js ... lambda@Edgeをトランスパイルするためのesbuildの呼び出しファイル - .env ... lambda@Edgeのトランスパイル時に定数を埋め込むための環境変数ファイル - package.json - cdk.json - tsconfig.json S3の構造 - data + background-images ... OGP用の画像フォルダ - whitemap ... SPA用のフォルダ - index.html https://ドメイン/whitemap/scene/:idのパスで、各記事のページにアクセスするようにしている。 ブラウザで直接SPAが生成したURLにアクセスすると404となる件の対応 カスタムレスポンスを利用する。 cdk/lib/cdk-stack.ts return new cf.Distribution(this, 'Distribution', { defaultRootObject: '/index.html', priceClass: cf.PriceClass.PRICE_CLASS_200, defaultBehavior: { origin, allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD, cachedMethods: cf.CachedMethods.CACHE_GET_HEAD, cachePolicy: myCachePolicy, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS }, errorResponses: [ + { + httpStatus: 404, + responseHttpStatus: 200, + responsePagePath: "/whitemap/index.html", + ttl: core.Duration.seconds(0), + } ] }) cdk全文 cdk/lib/cdk-stack.ts import * as core from '@aws-cdk/core' import * as s3 from '@aws-cdk/aws-s3' import * as cf from '@aws-cdk/aws-cloudfront' import * as iam from '@aws-cdk/aws-iam' import * as s3deploy from '@aws-cdk/aws-s3-deployment' import { basePath } from '../../vite.config' import * as lambda from "@aws-cdk/aws-lambda"; import * as origins from '@aws-cdk/aws-cloudfront-origins'; interface Props extends core.StackProps { bucketName: string } export class AWSWhiteMapClientStack extends core.Stack { constructor(scope: core.Construct, id: string, props: Props) { super(scope, id, props) // CloudFront オリジン用のS3バケットを作成 const bucket = this.createS3(props.bucketName) // CloudFront で設定する オリジンアクセスアイデンティティ を作成 const identity = this.createIdentity(bucket) // S3バケットポリシーで、CloudFrontのオリジンアクセスアイデンティティを許可 this.createPolicy(bucket, identity) // lambda edge作成 const f = this.createLambdaEdge() // CloudFrontディストリビューションを作成 const distribution = this.createCloudFront(bucket, identity, f) // 指定したディレクトリをデプロイ this.deployS3(bucket, distribution, '../dist') // 確認用にCloudFrontのURLに整形して出力 new core.CfnOutput(this, 'CFTopURL', { value: `https://${distribution.distributionDomainName}/`, }) } private createS3(bucketName: string) { const bucket = new s3.Bucket(this, 'S3Bucket', { bucketName, accessControl: s3.BucketAccessControl.PRIVATE, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: core.RemovalPolicy.DESTROY, cors: [{ allowedMethods: [s3.HttpMethods.GET], allowedOrigins: ['*'], allowedHeaders: ['*'] }] }) return bucket } private createIdentity(bucket: s3.Bucket) { const identity = new cf.OriginAccessIdentity(this, 'OriginAccessIdentity', { comment: `${bucket.bucketName} access identity`, }) return identity } private createPolicy(bucket: s3.Bucket, identity: cf.OriginAccessIdentity) { const myBucketPolicy = new iam.PolicyStatement({ effect: iam.Effect.ALLOW, actions: ['s3:GetObject', "s3:ListBucket"], principals: [ new iam.CanonicalUserPrincipal( identity.cloudFrontOriginAccessIdentityS3CanonicalUserId, ), ], resources: [bucket.bucketArn + '/*', bucket.bucketArn], }) bucket.addToResourcePolicy(myBucketPolicy) } private createCloudFront( bucket: s3.Bucket, identity: cf.OriginAccessIdentity, f: cf.experimental.EdgeFunction ) { const defaultPolicyOption = { cachePolicyName: 'MyPolicy', comment: 'A default policy', defaultTtl: core.Duration.days(2), minTtl: core.Duration.seconds(0), // core.Duration.minutes(1), maxTtl: core.Duration.days(365), // core.Duration.days(10), cookieBehavior: cf.CacheCookieBehavior.all(), headerBehavior: cf.CacheHeaderBehavior.none(), queryStringBehavior: cf.CacheQueryStringBehavior.none(), enableAcceptEncodingGzip: true, enableAcceptEncodingBrotli: true, } const myCachePolicy = new cf.CachePolicy(this, 'myDefaultCachePolicy', defaultPolicyOption); const imgCachePolicy = new cf.CachePolicy(this, 'myImageCachePolicy', { headerBehavior: cf.CacheHeaderBehavior.allowList('Access-Control-Request-Headers', 'Access-Control-Request-Method', 'Origin'), }); const origin = new origins.S3Origin(bucket, { originAccessIdentity: identity }) return new cf.Distribution(this, 'Distribution', { defaultRootObject: '/index.html', priceClass: cf.PriceClass.PRICE_CLASS_200, defaultBehavior: { origin, allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD, cachedMethods: cf.CachedMethods.CACHE_GET_HEAD, cachePolicy: myCachePolicy, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS }, additionalBehaviors: { 'whitemap/scene/*': { origin, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, edgeLambdas: [ { eventType: cf.LambdaEdgeEventType.VIEWER_REQUEST, functionVersion: f.currentVersion, includeBody: true, }, ], }, 'data': { origin, cachePolicy: imgCachePolicy, allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD_OPTIONS, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS } }, errorResponses: [ { httpStatus: 404, responseHttpStatus: 200, responsePagePath: "/whitemap/index.html", ttl: core.Duration.seconds(0), }, { httpStatus: 403, responseHttpStatus: 200, responsePagePath: "/index.html", ttl: core.Duration.seconds(0), } ] }) } private deployS3( siteBucket: s3.Bucket, distribution: cf.Distribution, sourcePath: string, ) { // Deploy site contents to S3 bucket new s3deploy.BucketDeployment(this, 'DeployWithInvalidation', { sources: [s3deploy.Source.asset(sourcePath)], destinationBucket: siteBucket, distribution, distributionPaths: ['/*'], destinationKeyPrefix: basePath, }) } private createLambdaEdge() { const f = new cf.experimental.EdgeFunction(this, "lambda-edge", { code: lambda.Code.fromAsset("dist/ogp"), handler: "index.handler", runtime: lambda.Runtime.NODEJS_14_X, }); return f; } } SPAのページごとにヘッダを変えられない。 Lambda@Edgeを利用する。 Botからのアクセスでない場合は通常のCloudFrontのフロー Botからのアクセスの場合、FireStoreからデータを取得してOGP用のHTMLを作成する FireStoreからの読込については認証の制限をかけていないパスなので、Cloud Firestore REST API を使用してHTTPSアクセスを行い、データを取得している。 ハンドラ cdk/src/ogp/index.ts export const handler: CloudFrontRequestHandler = async (event) => { const request = event.Records[0].cf.request; const userAgent = request.headers['user-agent'][0].value; // Botからのアクセスでない場合は通常のCloudFrontのフロー if (!bots.some((bot) => userAgent.includes(bot))) { return request; } // Botからのアクセスの場合、FireStoreからデータを取得してOGP用のHTMLを作成する const matches = /scene\/([^/]+)/.exec(request.uri) if (!matches) { return request; } const [, sceneId] = matches; const scene = await httpGet<FireStoreScene>(`https://${firestoreApiPath}/scenes/${sceneId}`); const sceneBgUrl = getBgUrl(scene); const url = sceneBgUrl ? sceneBgUrl : defaultBg const title = getTitle(scene); // Create OGP response const botResponse = { status: '200', headers: { 'content-type': [{ value: 'text/html;charset=UTF-8' }] }, body: getHTML(title, url, DOMAIN + request.uri) }; return botResponse; }; HTML作成処理 cdk/src/ogp/index.ts const getHTML = (title: string, ogImage: string, url: string) => { return ` <!doctype html> <html lang="ja" prefix="og: http://ogp.me/ns#"> <head prefix="og: http://ogp.me/ns#"> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>${title}|${SERVICE_NAME}</title> <meta name="description" content="${DESCRIPTION}" /> <meta name="author" content="hibohiboo"> <meta name="keywords" content="TRPG,白地図と足跡,紙芝居" /> <meta property="og:type" content="article" /> <meta property="og:locale" content="ja_JP" /> <meta property="og:site_name" content="${SERVICE_NAME}"> <meta property="og:title" content="${title}|${SERVICE_NAME}" /> <meta property="og:description" content="${DESCRIPTION}" /> <meta property="og:image" content="${ogImage}" /> <meta property="og:url" content="https://${url}" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@hibohiboo" /> <meta name="twitter:creator" content="@hibohiboo" /> </head> <body></body> </html> `; }; lambda全文 cdk/src/ogp/index.ts import * as https from 'https'; import type { CloudFrontRequestHandler } from 'aws-lambda'; import { FireStoreScene, SceneBg } from './types'; declare var DEFINE_DOMAIN: string; declare var DEFINE_SERVICE_NAME: string; declare var DEFINE_DESCRIPTION: string; declare var DEFINE_FIREBASE_PROJECT_ID: string; declare var DEFINE_DEFAULT_OGP_IMAGE_URL: string; const DOMAIN = DEFINE_DOMAIN; const SERVICE_NAME = DEFINE_SERVICE_NAME; const DESCRIPTION = DEFINE_DESCRIPTION; const projectId = DEFINE_FIREBASE_PROJECT_ID; const defaultBg = DEFINE_DEFAULT_OGP_IMAGE_URL; const firestoreApiPath = `firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents`; // OGP を返したい User-Agent をリストで定義しておく。 const bots = [ 'Twitterbot', 'facebookexternalhit', 'Slackbot-LinkExpanding' ]; const httpGet = <T>(url: string): Promise<T> => new Promise((resolve, reject) => { https.get(url, (res) => { const { statusCode } = res; const contentType = res.headers['content-type']; let error; if (statusCode !== 200) { error = new Error('Request Failed.\n' + `Status Code: ${statusCode}`); } else if (!contentType || !/^application\/json/.test(contentType)) { error = new Error('Invalid content-type.\n' + `Expected application/json but received ${contentType}`); } if (error) { console.error(error.message); res.resume(); reject(error); return; } res.setEncoding('utf8'); let rawData = ''; res.on('data', (chunk) => { rawData += chunk; }); res.on('end', () => { try { const parsedData = JSON.parse(rawData); resolve(parsedData) } catch (e) { console.error(e.message); reject(e); } }); }).on('error', (e) => { console.error(`Got error: ${e.message}`); reject(e) }); }) const getBgUrl = (scene: FireStoreScene) => { const bg = scene.fields.bg as SceneBg if (!bg.mapValue) { return null; } return bg.mapValue.fields.url.stringValue } const getTitle = (scene: FireStoreScene) => { return scene.fields.title.stringValue; } const getHTML = (title: string, ogImage: string, url: string) => { return ` <!doctype html> <html lang="ja" prefix="og: http://ogp.me/ns#"> <head prefix="og: http://ogp.me/ns#"> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>${title}|${SERVICE_NAME}</title> <meta name="description" content="${DESCRIPTION}" /> <meta name="author" content="hibohiboo"> <meta name="keywords" content="TRPG,白地図と足跡,紙芝居" /> <meta property="og:type" content="article" /> <meta property="og:locale" content="ja_JP" /> <meta property="og:site_name" content="${SERVICE_NAME}"> <meta property="og:title" content="${title}|${SERVICE_NAME}" /> <meta property="og:description" content="${DESCRIPTION}" /> <meta property="og:image" content="${ogImage}" /> <meta property="og:url" content="https://${url}" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:site" content="@hibohiboo" /> <meta name="twitter:creator" content="@hibohiboo" /> </head> <body></body> </html> `; }; export const handler: CloudFrontRequestHandler = async (event) => { const request = event.Records[0].cf.request; const userAgent = request.headers['user-agent'][0].value; const isBotAccess = bots.some((bot) => userAgent.includes(bot)); if (!isBotAccess) { return request; } const matches = /scene\/([^/]+)/.exec(request.uri) if (!matches) { return request; } const [, sceneId] = matches; const scene = await httpGet<FireStoreScene>(`https://${firestoreApiPath}/scenes/${sceneId}`); const sceneBgUrl = getBgUrl(scene); const url = sceneBgUrl ? sceneBgUrl : defaultBg const title = getTitle(scene); // Create OGP response const botResponse = { status: '200', headers: { 'content-type': [{ value: 'text/html;charset=UTF-8' }] }, body: getHTML(title, url, DOMAIN + request.uri) }; return botResponse; }; cdk/src/ogp/types.ts interface NullValue { nullValue: null } interface StringValue { stringValue: string } interface TimestampValue { mapValue: { fields: { seconds: StringValue nanoseconds: StringValue } } } export interface SceneBg { mapValue: { fields: { url: StringValue } } } export interface FireStoreScene { name: string, fields: { bg: SceneBg | NullValue title: StringValue } } ドメインなど、環境によって変化する部分はesbuildを使って環境変数を定数に置き換える処理を行っている。(参考:define) cdk/src/ogp/index.ts declare var DEFINE_DOMAIN: string; declare var DEFINE_SERVICE_NAME: string; declare var DEFINE_DESCRIPTION: string; declare var DEFINE_FIREBASE_PROJECT_ID: string; declare var DEFINE_DEFAULT_OGP_IMAGE_URL: string; const DOMAIN = DEFINE_DOMAIN; const SERVICE_NAME = DEFINE_SERVICE_NAME; const DESCRIPTION = DEFINE_DESCRIPTION; const projectId = DEFINE_FIREBASE_PROJECT_ID; const defaultBg = DEFINE_DEFAULT_OGP_IMAGE_URL; cdk/build.js require('dotenv').config(); require('esbuild').build({ entryPoints: ['src/ogp/index.ts'], bundle: true, platform: 'node', outfile: 'dist/ogp/index.js', define: { "DEFINE_DOMAIN": `'${process.env.ENV_DEFINE_DOMAIN}'`, "DEFINE_SERVICE_NAME": `'${process.env.ENV_DEFINE_SERVICE_NAME}'`, "DEFINE_DESCRIPTION": `'${process.env.ENV_DEFINE_DESCRIPTION}'`, "DEFINE_FIREBASE_PROJECT_ID": `'${process.env.ENV_DEFINE_FIREBASE_PROJECT_ID}'`, "DEFINE_DEFAULT_OGP_IMAGE_URL": `'${process.env.ENV_DEFINE_DEFAULT_OGP_IMAGE_URL}'`, }, minify: false, sourcemap: false, target: ['node14.17'], }).catch(() => process.exit(1)) cdk/.env ENV_DEFINE_DOMAIN=hoge.cloudfront.net ENV_DEFINE_SERVICE_NAME=サイト名 ENV_DEFINE_DESCRIPTION=説明 ENV_DEFINE_FIREBASE_PROJECT_ID=xxx ENV_DEFINE_DEFAULT_OGP_IMAGE_URL=https://piyo/fuga.png package.jsonのスクリプトで、デプロイする前にビルドが走るように設定している。(参考: npm scripts) cdk/package.json { "scripts": { + "predeploy": "node build.js", + "deploy": "cdk deploy --all" }, "devDependencies": { "dotenv": "^10.0.0", "esbuild": "^0.12.24", "typescript": "~4.4.2" } } CDKではCloudFrontのscene以下のパスが来た時にLambda@Edgeを動かすようにする。 cdk/lib/cdk-stack.ts return new cf.Distribution(this, 'Distribution', { additionalBehaviors: { 'whitemap/scene/*': { origin, viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS, + edgeLambdas: [ + { + eventType: cf.LambdaEdgeEventType.VIEWER_REQUEST, + functionVersion: f.currentVersion, + includeBody: true, + }, ], }, Lambda@Edgeの作成には experimental.EdgeFunctionクラスを使用する。 これを使うと、US-East-1 (バージニア北部) リージョン (us-east-1) にStackを作成してLambdaを配置してくれる。 cdk/lib/cdk-stack.ts const f = new cf.experimental.EdgeFunction(this, "lambda-edge", { code: lambda.Code.fromAsset("dist/ogp"), handler: "index.handler", runtime: lambda.Runtime.NODEJS_14_X, }); デプロイの初回でエラーが出た。 新しくUS-East-1にStackが作られるため、そのリージョンでのcdk bootstrapを忘れていたのが原因。 Error $npm run deploy > cdk@0.1.0 deploy cdk > cdk deploy --all edge-lambda-stack-xxx edge-lambda-stack-xxx: deploying... ❌ edge-lambda-stack-xxx failed: Error: This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/us-east-1") at Object.addMetadataAssetsToManifest at Object.deployStack at processTicksAndRejections (internal/process/task_queues.js:95:5) at CdkToolkit.deploy at initCommandLine This stack uses assets, so the toolkit stack must be deployed to the environment (Run "cdk bootstrap aws://unknown-account/us-east-1") npm ERR! code ELIFECYCLE npm ERR! errno 1 npm ERR! cdk@0.1.0 deploy: `cdk deploy --all` npm ERR! Exit status 1 npm ERR! npm ERR! Failed at the cdk@0.1.0 deploy script. npm ERR! This is probably not a problem with npm. There is likely additional logging output above. CORSの同一オリジンポリシーにより画像のリクエストがブロックされる S3のCORS設定。(*) cdk/lib/cdk-stack.ts private createS3(bucketName: string) { const bucket = new s3.Bucket(this, 'S3Bucket', { bucketName, accessControl: s3.BucketAccessControl.PRIVATE, blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, removalPolicy: core.RemovalPolicy.DESTROY, + cors: [{ allowedMethods: [s3.HttpMethods.GET], allowedOrigins: ['*'], allowedHeaders: ['*'] }] }) return bucket } cloud frontのcors設定。(*) data以下のパスの場合にCORSのヘッダを許可する。 cdk/lib/cdk-stack.ts return new cf.Distribution(this, 'Distribution', { additionalBehaviors: { + 'data': { + origin, + cachePolicy: imgCachePolicy, + allowedMethods: cf.AllowedMethods.ALLOW_GET_HEAD_OPTIONS, + viewerProtocolPolicy: cf.ViewerProtocolPolicy.REDIRECT_TO_HTTPS + } }, 動作確認 lambda@Edgeの確認 実際のレスポンスの確認 User Agentの有無でレスポンスが変わることを、直接URLを叩いて確認する。 GET https://hoge.cloudfront.net/whitemap/scene/fuga/ HTTP/1.1 ### GET https://hoge.cloudfront.net/whitemap/scene/fuga/ HTTP/1.1 user-agent: Twitterbot ログの確認 Lambda@EdgeはCloudFrontの実行されたリージョンにログが出力される。 図はRun Your Code at the Edge with Low Latency at Any Scale より まずはCloudFrontのモニタリングから動作ログを確認。 どのリージョンで実行されているかを確認できる。 Us-eastで動作していることを確認したので、そのリージョンのログを確認する。 OGPの確認 【配布】OGP画像のテンプレートと作成方法を参考にした。 twitter - Card Validator ... 実際のOGP確認 OGP画像シミュレータ ... OGP画像確認 OGP確認 ... OGP表示できず。各種OGP確認できるらしいが、今回は失敗した。 参考 設定前のソース 完了時点のソース OGP Lambda@EdgeをCDKで書いてみる @AWS 環境の SPA で動的 OGP を実現する CloudFront+S3なSPAにLambda@EdgeでOGP対応する AmplifyでOGP対応はできない。でもLambda@edgeを使えば大丈夫! Lambda@EdgeでSPAのOGPを動的に設定する Firebase + SPA + CloudFront + Lambda で SSR なしに OGP 対応 AWS Lambda@Edgeのログはどこ?AWS Lambda@Edgeのログ出力先について AWS CDK — A Beginner’s Guide with Examples 【配布】OGP画像のテンプレートと作成方法 AWS CDKでLambdaのTypescriptをトランスパイルしてローカル実行したメモ esbuild 話題のesbuildをさっくりと調査してみた build api define dotenv そのほか CloudFront FunctionsはLambda@Edgeより安い。それ本当?! [AWS CDK] CloudFront Functionでレスポンスにセキュリティヘッダーを追加する @aws-cdk/aws-cloudfront module class CachePolicy node.js https Cloud Firestore REST API を使用する Cloudfront+S3で最近話題のSPAを構築する(設定項目の解説有) Vue Router HTML5 History モード OGP OGP 和訳 twitter - 公式: ツイートをカードで最適化する aws公式 CloudFront ソリューションの概要 リクエストトリガーでの HTTP レスポンスの生成 Lambda@Edge Lambda@Edge イベント構造 Lambda@Edge 関数の例 CloudFront Functions と Lambda@Edge の選択 Lambda 関数をトリガーできる CloudFront イベント カスタムエラーレスポンスの生成
- 投稿日:2021-08-29T13:55:57+09:00
LambdaでDropbox APIを実行する
Dropbox APIをLambdaで実行したときの個人メモ 準備 Dropbox API Dropboxのアカウントがあることが前提 Dropboxのホーム画面から「App Center」を選択 「アプリの開発」を選択 「アプリを作成」を押下 以下設定にて「Create app」を押下 アプリを作成したことを確認 Lambda AWSマネジメントコンソールからLambdaを選択、「関数の作成」を押下 カスタムランライムとしてAmazon Linux 1を選択、「関数の作成」を押下 生成された「hello.sh」を一部変更して「Deploy」を押下 hello.sh function handler () { EVENT_DATA=$1 DATE=`date` RESPONSE="{\"statusCode\": 200, \"body\": \"$DATE\"}" echo $RESPONSE } テスト実行前に、テストイベントを作成 インプットを利用していないため、空でも動作に影響なし 「Test」を押下し、実行 echoした内容がレスポンスに出力されていることを確認 「hello.sh」を一部変更して「Deploy」を押下、「Test」を押下し、実行 curlコマンドを利用してHTTPリクエストを送信、レスポンスをechoするように変更 今回はhttpbin.orgを利用 hello.sh function handler () { EVENT_DATA=$1 HTTP=`curl -X GET "http://httpbin.org/get"` RESPONSE="{\"statusCode\": 200, \"body\": \"$HTTP\"}" echo $RESPONSE } HTTP通信できていることを確認 Dropbox API実行 APIの権限を確認(今回は/get_current_accountを実行) 利用するAPIに必要な権限が「account_info.read」であることを確認 アプリ情報から許可されている権限を確認 「account_info.read」にチェックが入っていることを確認 EXAMPLEからcurlコマンドをコピーしLambdaにペースト、「Test」を押下し、実行 レスポンスにアカウント情報が含まれていることを確認 感想 curlをたたきたいだけなら、LambdaでPythonと思っていたが カスタムランライムを使うと楽そう Dropbox APIの権限まわり含めて理解していきたい
- 投稿日:2021-08-29T12:20:47+09:00
MFA 登録する
ユーザ作成 ユーザを追加 まずrootアカウントでユーザを作成します。 ユーザの詳細設定 ユーザを作成します。 ユーザ名とパスワードどうするか、コンソールへのアクセスどうするかなど設定します。 アクセス許可の設定 今回は一旦こちらをアタッチします。 タグの追加 タグはなし。 最終確認 問題なければ作成します。 パスワード確認 qiita_testユーザのパスワードを確認します。 ログイン ログイン パスワードの再設定 ログインが完了する前に、パスワードの再設定を行います。 ログインできました MFAの設定 MFAの設定 マイセキュリティ資格情報へ進む。 MFAの割り当て MFAデバイスの割り当てを行います。 MFAデバイスの管理 仮想 MFA デバイスの設定 「Google認証システム」 というスマホのアプリでQRコードを読み込みます QRコードを表示 をクリックするとQRコードが表示されます また、「連続する 2 つの MFA コードを以下に入力」 で MFAコード1,2にスマホで表示された数字を入れます。 MFAコード1に1回目。数字が変わったらMFAコード2に2回目を入れます。 再度ログイン 先ほどと同様にログインします。 MFA認証の数字を入れて、送信を行うと ログインします。
- 投稿日:2021-08-29T06:49:09+09:00
AWS勉強ノート
はじめに 以下の本で、AWSを勉強した際のメモ書き。 基本概念 リージョン 仕様 リージョンは、1つ以上のAZを持つ。 Tips アベイラビリティゾーン AZ 仕様 AZは、電源、地理的に独立している。 Tips AZのレイテンシーは、小さい(2ミリ秒以下程度)。 サービス VPC 仕様 VPCは、最大で1つのIGWを持つ。 VPCは、最大で1つのVGWを持つ。 VPCのCIDRのサイズは、/16 ~ /28の間でなければならない。 VPCは、1 ~ 200個のサブネットを持つ。 Tips VPCのCIDRのサイズは、最も大きいサイズ /16 にしたほうが良い。 サブネット 仕様 サブネットのCIDRのサイズは、/16 ~ /28の間でなければならない。 サブネットは、ただ1つのAZに属する。変更はできない。 サブネットは、ただ1つのルートテーブルを持つ。 サブネットは、ただ1つのネットワークACLを持つ。 Tips サブネットのCIDRのサイズは、VPCの推奨サイズ /16 より8ビット小さいサイズ /24 にしたほうが良い。 IGWを接続したサブネットをパブリックサブネット、接続していないサブネットをパブリックサブネットと呼ぶ。 ルートテーブル 仕様 1.ルートテーブルは、1つ以上のサブネットとリレーションを持つ。 Tips セキュリティグループ 仕様 セキュリティグループは、複数のインバウンドの制御項目を持つ。 セキュリティグループは、複数のアウトバウンドの制御項目を持つ。 制御項目は、プロトコル、ポート範囲、CIDRまたはセキュリティグループ の3つの値を持つ。 セキュリティグループは、ステートフルに通信を制御する。 Tips セキュリティグループは、デフォルトでは許可されていないすべてのアクセスを拒否する。 ネットワークACL 仕様 ネットワークACLは、複数のインバウンドの制御項目を持つ。 ネットワークACLは、複数のアウトバウンドの制御項目を持つ。 制御項目は、ポート範囲、CIDR の2つの値を持つ。 ネットワークACLは、ステートレスに通信を制御する。 Tips ACLは、アクセスコントロールリストの略。 ネットワークACLは、デフォルトではすべてのアクセスを許可する。 EC2 仕様 EC2は、1つ以上のインスタンスを持つ。 インスタンスは、ただ1つの AMI (Amazon Machine Image) から作成される。 インスタンスは、ただ1つのインスタンスタイプに属する。 インスタンスタイプは、ただ1つのインスタンスファミリーに属する。インスタンスファミリーには以下の種類がある。 汎用:M5, T3... コンピューティング最適化:C5... メモリ最適化:R5、X1... 高速コンピューティング:P、G... ストレージ最適化:D2、I3... インスタンスは、ただ1つの セキュリティグループ を持つ。 インスタンスは、1つ以上のEBS (Elastic Block Store) を持つ。 EBS 最適化インスタンスは、通常の帯域とEBS専用の帯域を分けるオプションである。 インスタンスは、起動中、停止中、削除済みの何れかの状態に属する。 インスタンスは、起動中のみ課金される。 インスタンスの利用形態は、以下の種類がある。 インスタンス数契約 オンデマンドインスタンス スポットインスタンス リザーブドインスタンス 定額契約 ( Savings Plans ) Compute Savings Plans EC2 Instance Savings Plans Tips Labmda 仕様 Lambdaは、実行数の課金と、メモリ量の課金の2つで課金される。 Tips ELB : Elastic Load Balancer 仕様 ELBには、以下のタイプがある。 CLB: Classic Load Balancer : L4/L7 レイヤ ALB: Application Load Balancer : L7 レイヤ NLB: Network Load Balancer : L4 レイヤ ELBは、マネージドサービスである。 ELBは、ヘルスチャック機能を有する。ヘルスチェック機能は、定期的に配下のEC2にリクエストを送る機能である。 Tips ALBは、CLBより安価で機能(WebSocket, HTTP/2に対応)も豊富。 NLBは、LB自体が固定IPを持つ。 Auto Scaling 仕様 Auto Scaling は、ELBに紐付くEC2を自動的に増減する機能である。 Auto Scaling は、動的、予測、スケジュールの3つのスケーリング方式を持つ。 Auto Scaling は、簡易、ステップ、ターゲット追跡の3つのスケーリングポリシーを持つ。 Auto Scaling は、スケーリング後に猶予期間(デフォルト5分)を経過するまでヘルスチェックしない。 Auto Scaling は、スケーリング直後のインスタンス追加を抑制するクールダウン機能を持つ。 Auto Scaling は、スケーリング直後のインスタンス追加を制限するウォームアップ機能を持つ。ウォームアップ期間中は、次のアラートまでインスタンスは追加されず、次のアラートでは前のアラートとの差分のインスタンスだけが追加される。 Auto Scaling は、インスタンスの起動、削除時にカスタムアクションを実行するライフサイクルフック機能を持つ。 Tips ELBのスケーリングは数分かかる。急激な負担増が予測される場合は、事前にELBプレウォーミングを申請すること。 AWSは、簡易スケーリングよりその上位互換のステップスケーリングを推奨している。 デフォルトのインスタンス終了ポリシーでは、インスタンスがAZに均等に配分されるように設定されている。 Container ECS : Elastic Container Service 仕様 ECSは、1 つのClusterを持つ。 Clusterは、1つの EC2 インスタンスで構成される。 Clusterは、1つ以上の Task を持つ。 Task はコンテナの別名である。Taskは、Task Definitionで定義される。 Serviceは、1つの Task Definition から生成された1つ以上のTaskを持つ。 Tips EKS : Elastic Container Service for Kubernetes 仕様 Tips Fargate 仕様 Tips Fargateには、EC2は不要である。 Fargateは、Task のメモリとCPUの利用量にたいして課金される。 ECR : Elastic Container Registry 仕様 ECRは、コンテナをレジストリで管理する Tips CloudFront 仕様 CloudFrontは、拡張子、URLごとにキャッシュ期間を指定することができる。 Tips Network ゲートウェイ GW 仕様 GWには、IGW、NATゲートウェイ、VGWがある。 GWは、ただ1つだけのVPCと接続する。 Tips インターネットゲートウェイ IGW 仕様 IGWは、マネージドサービスである。 Tips NATゲートウェイ 仕様 Tips VGW 仕様 Tips VPCエンドポイント 仕様 Tips VPCエンドポイントは、AWSサービスに接続する際に使用される。 VPCピアリング 仕様 VPCピアリングは、1つ以上のAWSアカウントが保有する2つのVPCを接続する。 Tips Route 53 仕様 Route 53は、ただ1つのルーティングポリシーを持つ。 ルーティングポリシーは、7種類ある。 Route 53は、複数のホストゾーンを持つ。 ホストゾーンは、複数のレコード情報を持つ。 Route 53は、ヘルスチェック機能を持つ。 ヘルスチェック機能は、3種類のヘルスチェック方式を持つ。 DNSフェイルオーバーは、ヘルスチェックの結果により発動する。 Tips Log CloudWatch 仕様 CloudWatchは、Logs、Events、Syntheticsなどの多用な機能を持つ。 CloudWatchは、各リソースのメトリクスを監視する機能である。 CloudWatch Logsは、ログの中のWarn、Errorなどの文字列を監視する。 Tips CloudTrail 仕様 CloudTrailは、S3にログを残す機能を持つ。 CloudTrailが監視するイベントには、管理イベントとデータイベントがある。 Tips Storage Snowball ストレージ 仕様 Tips EBS ストレージ ブロックストレージ 仕様 EBSは、異なるAZのEC2にあタッチすることはできない。 EBSは、以下のいずれかのボリュームタイプに属する。 汎用SSD : gp2 一時的にIOPSを高めるバースト機能がある。 プロビジョンドIOPS SSD : io1 最高性能のボリュームタイプである。 スループット最適化HDD : st1 Cold HDD : sc1 ボリュームサイズは、最大出16TB。サイズは何度でも拡張可能である。 EBSのSLAは、99.99%/月。 EBSは、スナップショット機能を持つ。 暗号化オプションを持つ。スナップショットも暗号化される。 EBSは、同一AZ内の複数のインスタンスで共有できる。 Tips EBSの設定変更してから、次の変更が可能になるまで6時間以上の間隔が必要である。 サイズ拡張後は、OSでファイルシステムの拡張作業を要する。 IOPSの変更には最大24以上かかる場合がある。 EFS ストレージ ファイルストレージ 仕様 NFSで接続可能。 EFSは、3か所以上のAZにファイルシステムを持つ。 ファイルシステムは、ただ1つのマウントターゲットを持つ。 マウントターゲットは、ただ1つのマウントターゲットとサブネットに属する。 EFSには、2つのパフォーマンスモードがある。 汎用パフォーマンスモード 最大I/Oパフォーマンスモード EFSには、2つのスループットモードがある。 バーストスループットモード 1TBを超えると、1日12時間だけ1TBあたり100MB/秒までバーストできるバーストクレジットが付与される。 ベースラインスループットは、ファイルシステムサイズに応じて逓増する。 プロビジョニングスループットモード Tips EFS接続の際は、amazon-efs-utilsを使うと良い。 パフォーマンスモードの選定時には、CloudWatchのPercentIOLimitというメトリクスをみると良い。 S3 ストレージ オブジェクトストレージ 仕様 S3は、バケットを持つ。 バケットは、複数のオブジェクトを持つ。 オブジェクトは、ただ1つのキーを持つ。 オブジェクトは、複数のAZ、AZ内の複数の物理ストレージに複製される。 耐久性は、イレブン・ナインである。 可用性は、フォー・ナインである。ただし、S3 1ゾーン IAを除く。 S3は、結果整合性を採用する。 S3は、ライフサイクル管理機能、バージョニング機能、Webサイトホスティング機能などの多用な機能を持つ。 S3へのアクセス制御は、バケットポリシー、ACL、IAMで実現できる。ただし、それぞれ制御できる項目が異なる。 Tips Webサイトホスティングサービスを独自ドメインで利用する場合は、バケット名とドメイン名を一致させること。 S3 Glacier ストレージ オブジェクトストレージ 仕様 データ保存領域をボールトと呼ぶ。S3バケットに相当。 ボールトに格納されるデータをアーカイブと呼ぶ。S3オブジェクトに相当。 アーカイブの情報をインベントリと呼ぶ。1日1回の頻度で更新される。 標準で、ボールトへの転送はSSLで暗号化され、アーカイブも暗号化されて保存される。 ボールとロック機能は、削除を禁止する機能。ロックポリシーを設定できる。 Tips Database RDS データベース マネージドサービス リレーショナルデータベース 仕様 複数のデータベースエンジンから選択する。 Auroraはデータベースエンジンの一種。 ストレージは、EBS。 Tips マルチAZは書き込み速度が遅くなる。 フェイルオーバーに1~2分程度かかる。 Redshift データベース マネージドサービス 列指向データベース 仕様 Redshiftは、1つのリーダーノードを持つ。 リーダーノードは、複数のコンピュートノードを持つ。 コンピュートノードは、1つの専用ストレージとスライスを持つ。 スライスは、分散並行処理を実行する最小単位である。 Redshiftは、データをブロック単位で格納する。 ブロックの容量は1MBである。 ブロックは、ゾーン内のデータの最大・最小値を格納したゾーンマップを持つ。 Tips DynamoDB データベース マネージドサービス Key-Valueストア 仕様 Read Capacity Unit は、読取りのスループットキャパシティである。 Write Capacity Unit は、読取りのスループットキャパシティである。 キャパシティユニットの増加は制限がなく、減少は1日9回までという制限がある。 データをパーティションという単位で管理する。 プライマリキーは、「パーティションキー単独+(ソートキー)」で定義される。 セカンダリインデックスには、ローカル(LSI)とグローバル(GSI)の2種類がある。 TTL(Time to Live)による期限切れデータ自動削除は、キャパシティユニットを消費しない。 DynamoDBは、Streamsと呼ばれる直近24時間の変更履歴を保持する機能を持つ。 Tips 通常はミリ秒単位での読み取りレスポンスを保証する。 Elasticache データベース マネージドサービス インメモリデータベース 仕様 エンジンは、Memcached と Redisを選択できる。 Tips 通常はミリ秒単位での読み取りレスポンスを保証する。 ユースケース オンプレミスとの接続 仕様 AWS Direct Connectは、AWSと物理拠点を専用回線で接続する。 SnowBallでデータ移行する。 Tips
- 投稿日:2021-08-29T00:53:23+09:00
AWS 触ってみた
AWSの流れが来ているのでAWS触ってみた とりあえず「アカウント作成」してWEBアプリ作成の準備をしてみた。 やりたいこと AWS上でWEBアプリを作成して,サービスとして公開してみる。 使うもの 1nginx 2python(Flask) 3mysql 若干、古い気もするが、調べ物を極力抑えるため、自分の知っている技術を使うことにした。 概要 WEBアプリ作成 -- Hello Worldを出すだけ ひとおとりの流れ 1 外部通信の可能にするため、VPCとかセキュリティグループとか設定 2 EC2作成 3 docokerインストール⇒ngixnのコンテナ作成⇒hello World! を出力するHTML作成 4 docker run 5 localからhttpアクセス⇒「hello World!」が出力 6 以上! その他 1Amazon RDS作成 とりあえず[MySQL]のインスタンス立ててみたが、インスタンス設定の最後のページで 予測請求額[500USD]的なでた [USD]⇒アメリカ合衆国ドル https://ja.wikipedia.org/wiki/アメリカ合衆国ドル 日本円にして約「5万」 ・・・ 無理 Next pythonインストール Flaskインストール 目的 ・AWSの知見あつめ ・WEBアプリ作成