20220111のAWSに関する記事は15件です。

[Tips] AWS WAF を紐付けた API Gateway で 10KB を超えるリクエストを送信できない

はじめに AWS CDK で構築していた API Gateway で一定サイズ以上のリクエストボディを指定して API を呼び出したときに、そのリクエストに対するレスポンスが常に 403 になる問題に遭遇しました。 具体的には "x-amzn-ErrorType" = "ForbiddenException" というレスポンスヘッダと共に 403 が返却されていました。リンク先の フィルタリングされた AWS WAF の項目に該当します。 上記の問題は、何も考えずに WAF を設定していたことに起因していました。本記事ではその解決方法について簡単にまとめたものになります。 コアルールセットからリクエストボディ制限に関するルールを除外する API Gateway に紐づけていた WAF の設定に AWSManagedRulesCommonRuleSet がありました。下記の通り、AWSManagedRulesCommonRuleSet については AWS 公式でも設定が推奨されているような文言があったためです。 すべての AWS WAF ユースケースでこのルールグループを使用することを検討してください。 設定しておくことで OWASP が公表している一般的な脆弱性に対処可能になリます。しかし、AWSManagedRulesCommonRuleSet には SizeRestrictions_BODY というルールが設定されているため、10KB 以上のリクエストボディを指定したリクエストが送信不可になります。 今回は API Gateway でファイルを添付して利用する API を構築していました。そのため、今回のプロジェクトでは SizeRestrictions_BODY というルールだけ除外したいと考えました。そこで調査したところ、AWS WAF には特定のルールのみ例外が設定可能なことをこの記事で知りました。 そこで AWS CDK で構築した AWS WAF の定義を下記の変更を加えました。 //... new wafv2.CfnWebACL(root, `${projectName}-web-acl`, { defaultAction: { allow: {} }, scope: "REGIONAL", visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "websiteWafV2WebAcl", }, rules: [ { name: "AWSManagedRulesCommonRuleSet", priority: 1, statement: { managedRuleGroupStatement: { vendorName: "AWS", name: "AWSManagedRulesCommonRuleSet", // 追記箇所: リクエストボディの制限に関するルールのみ除外する excludedRules: [ { name: "SizeRestrictions_BODY" } ] }, }, overrideAction: { none: {} }, visibilityConfig: { cloudWatchMetricsEnabled: true, sampledRequestsEnabled: true, metricName: "AWSManagedRulesCommonRuleSet", }, }, ], }); //... ExcludedRule を指定することで特定のルールのオプションを Block から Count に変更することが可能です。詳細はこちら これで、10KB 以上のリクエストボディを指定して SizeRestrictions_BODY のルールに引っかかってしまっても、AWS WAF + API Gateway の環境でリクエストが通るようになりました。 おわりに 本記事の内容は、リクエストボディに関するルールのみならず、他の除外したいルールに対しても同様の変更で対処可能です。 結局本記事の内容は、ちゃんとドキュメント見て中身を理解して上で AWS WAF のルールグループを設定しようということだと思うのですが、、失敗を通じて理解できたので良かったです。 本記事の内容がどなたかのお役に立てれば幸いです 参考リンク Amazon API Gateway(規模に応じた API の作成、維持、保護)| AWS AWS WAF(ウェブアプリケーションファイアウォール)| AWS AWS管理ルールルールグループリスト - AWS WAF、AWS Firewall Manager、および AWS Shield Advanced OWASPとは?意味・定義 | ITトレンド用語 | NTTコミュニケーションズ AWS WAF マネージドルールで使用するルールグループの例外の発表
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MySQLのmysqldumpでS3にバックアップを保存する(EC2+S3)

はじめに こんにちは、山田です。 今回は、MySQLのmysqldumpでバックアップをS3に保存する方法を記載していきます。 全体構成図 今回の全体構成図はシンプルですが以下の通りです。 全体の流れを以下に記載します。 ① EC2-①にSSH接続します。 ② mysqldumpコマンドを実行します。 ③ S3にバックアップファイルが保存されます。 構築手順 以下により構築手順について記載していきます。 前提条件 EC2に関しては、作成済みとします。 S3に関しては作成済みとし、バケット名「yamada-backup-s3」とします。 IAMロールの作成 ① IAMロールにアタッチする、IAMポリシーの作成します 特定のS3バケットにアクセスできるように、次のようなJSONポリシー設定します。 IAMポリシー名 アクセスできるS3バケット s3-backup-policy yamada-backup-s3 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": [ "arn:aws:s3:::yamada-backup-s3", "arn:aws:s3:::yamada-backup-s3/*" ] } ] } ②IAMロールを作成します。 IAMロール名 アタッチするIAMポリシー s3-backup-policy s3-backup-role AWS CLIのインストール ① AWS CLIをインストールします。 yum -y install python38 sudo update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 pip install awscli --upgrade --user export PATH="$PATH:/root/.local/bin" aws --version #以下のように表示されればOKです。 aws-cli/1.22.31 Python/3.8.8 Linux/4.18.0-348.7.1.el8_5.x86_64 botocore/1.23.31 mysqldumpコマンドの実行 ① EC2-①にSSH接続します。 ② 以下のコマンドを実行します。 #DBごとバックアップを取得 mysqldump -u root -pP@ssw0rd mysql | aws s3 cp - s3://yamada-backup-s3/test.dump #mysqldump -u ユーザ名 -pパスワード DB名 | aws s3 cp - s3://バケット名/ファイル名 #テーブルを指定する場合は以下のように記載する。 mysqldump -u root -pP@ssw0rd mysql users | aws s3 cp - s3://yamada-backup-s3/test.dump #mysqldump -u ユーザ名 -pパスワード DB名 テーブル名 | aws s3 cp - s3://バケット名/ファイル名 S3にバックアップが保存されているか確認 ① yamada-backup-s3にバックアップが保存されていれば完了です! 中身は以下のような感じです。 -- MySQL dump 10.13 Distrib 8.0.26, for Linux (x86_64) -- -- Host: localhost Database: mysql -- ------------------------------------------------------ -- Server version 8.0.26 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!50503 SET NAMES utf8mb4 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Table structure for table `users` -- DROP TABLE IF EXISTS `users`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!50503 SET character_set_client = utf8mb4 */; CREATE TABLE `users` ( `id` int DEFAULT NULL, `name` varchar(10) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `users` -- LOCK TABLES `users` WRITE; /*!40000 ALTER TABLE `users` DISABLE KEYS */; INSERT INTO `users` VALUES (1,'Yamada'); /*!40000 ALTER TABLE `users` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2022-01-11 10:54:04 dumpファイルについて調べてみると、dumpファイルとは元のデータベースにあったテーブルを作り直すためのコマンドが記載してあるファイルのことだそうです。 参考URLは以下です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【自分用】AWS認定クラウドプラクティショナー資格試験勉強①

はじめに AWS認定クラウドプラクティショナーの資格勉強を始めました。 毎日朝の1時間、夜の2時間を勉強時間に充てています。 確実に定着させるために、勉強したことをアウトプットしていきます。 項目 1.AWSの長所と利点 2.クラウドアーキテクチャの設計原理 ※ 『AWS認定資格試験テキスト AWS認定クラウドプラクティショナー』 の第2章に相当 1.AWSの長所と利点 AWSクラウドコンピューティングの6つのメリット (1)固定費(設備投資費)が変動費に変わる 従来のオンプレミスシステム(自社)では、事前にデータセンターやサーバーに多額の投資をする必要がありました。 それをクラウドに変更することで、 "利用した分だけ費用を支払う「従量課金制」"となります。 既存のシステムを移行する際、既存システムのトータルコストを削減しつつ、新規システムをクラウド上で利用するといったこともできます。 そうすれば、コストパフォーマンスを改善することにつながるのです。 (2)スケールによる大きなコストメリット クラウドを利用することで、オンプレミスよりも低い変動コストを実現できます。 なぜなら、数十万単位のユーザーが利用することで、規模の経済を活かしつつ、従量課金制の料金を低く提供することができるからです。 AWSは、2018年まで数十回にもわたり値下げを行ってきました。 今後もこの流れが継続すれば、従来よりも利用料金が安くなっていくことも考えられます。 (3)キャパシティ予測が不要になる AWSでは、必要に応じてリソースの増減を行うことができるので、インフラ容量を予測する必要がなくなります。 クラウドならば、必要に応じてアクセスするだけで、リソースの調整やスケールアップ・スケールダウンの実行を数分で行うことができます。 柔軟にスケーリングを行えるところも利点の一つです。 (4)速度と俊敏性 AWSでは、新しいITリソースを数分単位で簡単に利用することができます。 迅速にリソース調達が可能になり、システムの負荷やサービスの拡充に応じて柔軟な構成変更が可能となります。 結果として、開発現場におけるコストと時間が大幅に削減でき、組織内の俊敏性も向上します。 (5)データセンターの運用と保守への投資が不要に 従来のオンプレミスシステムの各種費用には、サーバー代、ライセンス費用、データセンター利用料、ラックの利用料、電気代、ネットワーク利用料などです。 それらは全てAWSの各種利用料として請求されます。 クラウドを利用すれば、サーバーの設置や各種連携などの作業を行う必要がなくなるといったメリットがあります。 (6)わずか数分で世界中にデプロイ 「数クリック・数分」でアプリケーションを展開できます。 2.クラウドアーキテクチャの設計原理 (1) コンポーネントの分離 クラウドは、サービス指向アーキテクチャの原理を利用しています。 「サービス指向アーキテクチャ」とは、複数のサービス・機能(コンポーネント)を集めて大きなまとまり(システム)として用いること。 システムのコンポーネント(部品・要素)をまとめて使っているというわけですね。 各コンポーネントは、他のコンポーネントと非同期に相互作用します。 例えば、WEBアプリケーションシステムでは、「アプリケーションサーバー」「WEBサーバー」「データベース」などに分類できます。 それぞれが相互に作用しあって、一つのWEBアプリケーションシステムを動作させています。 また、バッチ処理のアーキテクチャにおいては、互いに独立している非同期のコンポーネントを作成できます。 そのコンポーネントの分離を実現するためには、Amazon SQS(Simple Queue Service)を使ったキューイングチェーンを利用すれば可能です。 「Amazon SQS」は、サービス間をゆるやかに「つなぐ」ことで影響範囲を限定し、開発コストの大幅な削減が期待できるサービスです。 キューイングを利用することでシステム間を疎結合にでき、分散アプリケーションの拡張性、可用性を大幅に改善できます。 サーバーレスで分散アプリケーションをつなぐため、高い負荷がかかった場合でも柔軟なスケールができます。 「キューイング」とは、日本語にすると「待ち行列」という意味で、非同期に処理が進めることができるという意味です。"処理の順番待ち"といった認識ですね。 キューイング中に障害が起こっても、そのデータは残り続けるため、他のコンピューティングリソースによって処理を継続することができます。 また、マイクロサービスアーキテクチャを利用することで、システムを複数の小規模なサービスの集合体として構成し、コンポーネントの分離を図ることができます。 (2) 弾力性の実装 クラウドは、インフラに"弾力性"という新しい概念をもたらしました。 リソースの性能を柔軟にスケールアウトしたり、スケールインしたりすることができます。 次の3つの方法で実現できます。 ①巡回スケーリング 一定間隔(毎日・週ごと・月ごと・四半期ごと)に発生するスケーリングです。 ②イベントベーススケーリング 予定されているイベントのために、トラフィックリクエストが急増すると予想されるときに実施するスケーリングです。 期間限定商品のキャンペーンや、新製品のプロモーションといった特別なビジネスイベントで実施します。 イベントベーススケーリングは、「スケジュールドスケールアウトパターン」を利用することで弾力性を実現できます。 「スケジュールドスケールアウトパターン」は、オートスケーリング(AutoScaling)により、定時起動や定時停止を行います。 ③オンデマンドの自動スケーリング 関しサービスを利用することにより、監視項目(CPUの平均利用率など)に基づいてトリガーを送信し、スケールアウト/スケールインすることです。 「〇〇を上限値とし、上限値を上回ったらスケールアウトする」などです。 オンデマンドの自動スケーリングでは、「スケールアウトパターン」を利用することで弾力性を実現できます。 「スケールアウトパターン」は、クラウドウォッチ(CloudWatch)をトリガーとして、オートスケーリング(AutoScaling)で起動します。停止は手動です。 (3)並列化 クラウドでアーキテクチャを設計する場合、並列化の概念を取り入れる必要があります。 繰り返し利用可能なプロセスを簡単に構築できるためです。 WEBアプリケーションを例にすると、ロードバランサーを使用して、複数の非同期のWEBサーバー全体で受信リクエストを分散させることができます。 (4)コンテンツデータの保管 従来のオンプレミスシステムでは、伝送遅延を回避するためにデータをコンピュータ、または、処理要素のできるだけ近くに保管することが良いと言われていました。 クラウドでは、データを"ネットワーク経由"で利用するため、クラウド外の大量のデータ処理する必要がある場合、データをクラウド上に移してから処理を実行します。 リレーショナルデータベースを用いてデータを保管、検索するWEBアプリケーションの場合、データベースとアプリケーションサーバーをクラウドにまとめて移行するのが良いでしょう。 静的コンテンツは、CDNなどのサービスを利用するといいかもしれません。 CDNとは、「Content Delivery Network(コンテンツデリバリーネットワーク)」の略で、ウェブコンテンツを効率的かつスピーディーに配信できるように工夫されたネットワークのことです。 アクセスが集中したりコンテンツが大容量化したりしても、ホームページの表示やコンテンツの配信に問題が起こらないようにすることが可能です。 参考書籍 ※ 『AWS認定資格試験テキスト AWS認定クラウドプラクティショナー』 ● Amazonはこちら ● 楽天はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS認定クラウドプラクティショナー資格】勉強備忘録① AWSクラウドの概念

はじめに AWS認定クラウドプラクティショナーの資格勉強を始めました。 毎日朝の1時間、夜の2時間を勉強時間に充てています。 確実に定着させるために、勉強したことをアウトプットしていきます。 項目 1.AWSの長所と利点 2.クラウドアーキテクチャの設計原理 ※ 『AWS認定資格試験テキスト AWS認定クラウドプラクティショナー』 の第2章に相当 1.AWSの長所と利点 AWSクラウドコンピューティングの6つのメリット (1)固定費(設備投資費)が変動費に変わる 従来のオンプレミスシステム(自社)では、事前にデータセンターやサーバーに多額の投資をする必要がありました。 それをクラウドに変更することで、 "利用した分だけ費用を支払う「従量課金制」"となります。 既存のシステムを移行する際、既存システムのトータルコストを削減しつつ、新規システムをクラウド上で利用するといったこともできます。 そうすれば、コストパフォーマンスを改善することにつながるのです。 (2)スケールによる大きなコストメリット クラウドを利用することで、オンプレミスよりも低い変動コストを実現できます。 なぜなら、数十万単位のユーザーが利用することで、規模の経済を活かしつつ、従量課金制の料金を低く提供することができるからです。 AWSは、2018年まで数十回にもわたり値下げを行ってきました。 今後もこの流れが継続すれば、従来よりも利用料金が安くなっていくことも考えられます。 (3)キャパシティ予測が不要になる AWSでは、必要に応じてリソースの増減を行うことができるので、インフラ容量を予測する必要がなくなります。 クラウドならば、必要に応じてアクセスするだけで、リソースの調整やスケールアップ・スケールダウンの実行を数分で行うことができます。 柔軟にスケーリングを行えるところも利点の一つです。 (4)速度と俊敏性 AWSでは、新しいITリソースを数分単位で簡単に利用することができます。 迅速にリソース調達が可能になり、システムの負荷やサービスの拡充に応じて柔軟な構成変更が可能となります。 結果として、開発現場におけるコストと時間が大幅に削減でき、組織内の俊敏性も向上します。 (5)データセンターの運用と保守への投資が不要に 従来のオンプレミスシステムの各種費用には、サーバー代、ライセンス費用、データセンター利用料、ラックの利用料、電気代、ネットワーク利用料などです。 それらは全てAWSの各種利用料として請求されます。 クラウドを利用すれば、サーバーの設置や各種連携などの作業を行う必要がなくなるといったメリットがあります。 (6)わずか数分で世界中にデプロイ 「数クリック・数分」でアプリケーションを展開できます。 2.クラウドアーキテクチャの設計原理 (1) コンポーネントの分離 クラウドは、サービス指向アーキテクチャの原理を利用しています。 「サービス指向アーキテクチャ」とは、複数のサービス・機能(コンポーネント)を集めて大きなまとまり(システム)として用いること。 システムのコンポーネント(部品・要素)をまとめて使っているというわけですね。 各コンポーネントは、他のコンポーネントと非同期に相互作用します。 例えば、WEBアプリケーションシステムでは、「アプリケーションサーバー」「WEBサーバー」「データベース」などに分類できます。 それぞれが相互に作用しあって、一つのWEBアプリケーションシステムを動作させています。 また、バッチ処理のアーキテクチャにおいては、互いに独立している非同期のコンポーネントを作成できます。 そのコンポーネントの分離を実現するためには、Amazon SQS(Simple Queue Service)を使ったキューイングチェーンを利用すれば可能です。 「Amazon SQS」は、サービス間をゆるやかに「つなぐ」ことで影響範囲を限定し、開発コストの大幅な削減が期待できるサービスです。 キューイングを利用することでシステム間を疎結合にでき、分散アプリケーションの拡張性、可用性を大幅に改善できます。 サーバーレスで分散アプリケーションをつなぐため、高い負荷がかかった場合でも柔軟なスケールができます。 「キューイング」とは、日本語にすると「待ち行列」という意味で、非同期に処理が進めることができるという意味です。"処理の順番待ち"といった認識ですね。 キューイング中に障害が起こっても、そのデータは残り続けるため、他のコンピューティングリソースによって処理を継続することができます。 また、マイクロサービスアーキテクチャを利用することで、システムを複数の小規模なサービスの集合体として構成し、コンポーネントの分離を図ることができます。 (2) 弾力性の実装 クラウドは、インフラに"弾力性"という新しい概念をもたらしました。 リソースの性能を柔軟にスケールアウトしたり、スケールインしたりすることができます。 次の3つの方法で実現できます。 ①巡回スケーリング 一定間隔(毎日・週ごと・月ごと・四半期ごと)に発生するスケーリングです。 ②イベントベーススケーリング 予定されているイベントのために、トラフィックリクエストが急増すると予想されるときに実施するスケーリングです。 期間限定商品のキャンペーンや、新製品のプロモーションといった特別なビジネスイベントで実施します。 イベントベーススケーリングは、「スケジュールドスケールアウトパターン」を利用することで弾力性を実現できます。 「スケジュールドスケールアウトパターン」は、オートスケーリング(AutoScaling)により、定時起動や定時停止を行います。 ③オンデマンドの自動スケーリング 関しサービスを利用することにより、監視項目(CPUの平均利用率など)に基づいてトリガーを送信し、スケールアウト/スケールインすることです。 「〇〇を上限値とし、上限値を上回ったらスケールアウトする」などです。 オンデマンドの自動スケーリングでは、「スケールアウトパターン」を利用することで弾力性を実現できます。 「スケールアウトパターン」は、クラウドウォッチ(CloudWatch)をトリガーとして、オートスケーリング(AutoScaling)で起動します。停止は手動です。 (3)並列化 クラウドでアーキテクチャを設計する場合、並列化の概念を取り入れる必要があります。 繰り返し利用可能なプロセスを簡単に構築できるためです。 WEBアプリケーションを例にすると、ロードバランサーを使用して、複数の非同期のWEBサーバー全体で受信リクエストを分散させることができます。 (4)コンテンツデータの保管 従来のオンプレミスシステムでは、伝送遅延を回避するためにデータをコンピュータ、または、処理要素のできるだけ近くに保管することが良いと言われていました。 クラウドでは、データを"ネットワーク経由"で利用するため、クラウド外の大量のデータ処理する必要がある場合、データをクラウド上に移してから処理を実行します。 リレーショナルデータベースを用いてデータを保管、検索するWEBアプリケーションの場合、データベースとアプリケーションサーバーをクラウドにまとめて移行するのが良いでしょう。 静的コンテンツは、CDNなどのサービスを利用するといいかもしれません。 CDNとは、「Content Delivery Network(コンテンツデリバリーネットワーク)」の略で、ウェブコンテンツを効率的かつスピーディーに配信できるように工夫されたネットワークのことです。 アクセスが集中したりコンテンツが大容量化したりしても、ホームページの表示やコンテンツの配信に問題が起こらないようにすることが可能です。 参考書籍 ※ 『AWS認定資格試験テキスト AWS認定クラウドプラクティショナー』 ● Amazonはこちら ● 楽天はこちら
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS] Cloudwatch_LogsからLambda経由で軽いETLしつつOpenSearchでApacheログを可視化する方法

やりたい事 2022/01/13開催の SB Tech Festival で登壇した内容、「ビッグデータ活用の第一歩AWS環境での大容量ログ可視化」の詳細手順(後半)ページです。 このエントリで解説するのは以下のイメージ 前半部分のFargate+Firelens+CloudwatchLogsについてはこちらの記事を参照してください。 手順 2022/01/11時点でECSコンソールは新しいエクスペリエンスが提供されていますが、本エントリでは旧UIをベースに解説しています。 1.OpenSearchクラスターを作成する AWSコンソールからOpenSearchダッシュボードを開き、新しいドメインを作成します。 項目名 設定内容 ドメイン名 <任意の名前> カスタムエンドポイント チェック無し デプロイタイプ 開発及びテスト バージョン 1.0 ※より新しいバージョンがリリースされていたとしても、Lambdaコードの後方互換が切られる可能性があります。自身でコード改修出来ない場合は、1.0選択をお願いします 自動調整 無効化 データノード t3.small.search ※インスタンスサイズを大きくするのは任意ですが、課金に直結するのでご注意ください。t3.smallは無料利用枠有り ノードの数 1 ストレージタイプ EBS EBS ボリュームタイプ 汎用(SSD) ノード当たりのEBSストレージサイズ 20GiB きめ細かなアクセスコントロール チェック無し アクセスポリシー ドメインレベルのアクセスポリシーの設定(ビジュアルエディタでダッシュボードへのアクセスを許可するIPv4/v6アドレスを指定します) 暗号化 全てチェック AWS KMSキーを選択する AWS 所有キーを使用する 作成後、10分ほど時間をおいてデプロイが完了する事を確認してください。 (クラスターのヘルスは黄色or緑のいずれかであれば、次の手順へ進んで問題ありません。) 2.CloudWatch Logs用IAMロールを作成する IAM管理ポリシーを新規作成し、 JSONタブを開き 以下のJSONをコピペする。これはLambdaからCloudwatchLogs及びElasticSearchの必要な操作が出来るように権限を付与する内容。 より権限を絞ったり、リソースを指定するのがより良いですが、今回はその他リソースが無い前提で広く権限を持たせます。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "logs:*", "Resource": "*" }, { "Effect": "Allow", "Action": "es:*", "Resource": "*" } ] } 続いてIAMロールを新規作成し、 以下の通りにロールの利用方法を指定。 次の画面で、作成済みのIAM管理ポリシーを指定しLambda向けロール作成完了。 IAM管理ポリシー、IAMロールのタグや名前は任意の内容でOK。 3.CloudWatch Logsサブスクリプションフィルターを作成する 前半で作成したCloudWatch Logグループ /apache-dummy-logを選択し、図示の通りOpenSearch向けのサブスクリプションフィルターを作成 項目名 設定内容 アカウントを選択 This account Amazon OpenSearch Service cluster <手順1で作成したOpenSearchドメインを指定> ログの形式 JSON サブスクリプションフィルターのパターン <未記入のまま> サブスクリプションフィルター名 <任意の名前を記載> 以下のようにサブスクリプションフィルターが作成された事を確認してください。 4.Lambdaで軽いETL処理を実装する 今回はApache2のダミーログを発生させていますが、ご存じの通りApache2のログはスペース区切りでフィールド名がありません。 OpenSearchへ投入する前に、Lambda上でパースしてフィールド名を割り当てましょう。 Lambdaコンソールを開き、関数一覧の中にLogsToElasticsearch_apachedummylog-opensearchがある事を確認し、クリックします。 設定タブを開き、実行ロールの編集をクリックします。 手順#2で作成したIAMロールを指定し、保存をクリックします。 続いてコードタブを開き、関数の76行目以降に以下を追加します。 Node.js var messageConvJson = logEvent.message.replace(/"/g,'\"'); messageConvJson = messageConvJson.replace(/{\\"/,'{"'); messageConvJson = messageConvJson.replace(/\\"\s*:\s*\\"/g,'":"'); messageConvJson = messageConvJson.replace(/\\"\s*,\s*\\"/g,'","'); messageConvJson = messageConvJson.replace(/\\"}/,'"}'); var messageJson = JSON.parse(messageConvJson); let [ remote_host, remote_logname, remote_user, ts, tz, method, resource, protocol, statusCode, size, referer, ...userAgent ] = messageJson.log.split(' '); statusCode = +statusCode; size = +size; ts = ts.substring(1); tz = tz.substring(0, tz.length - 1); const accesstime = ts + tz; userAgent = userAgent.join(''); source['@remote_host'] = remote_host; source['@accesstime'] = accesstime; source['@method'] = method; source['@resource'] = resource; source['@protocol'] = protocol; source['@statusCode'] = statusCode; source['@size'] = size; source['@referer'] = referer; source['@userAgent'] = userAgent; CloudWatch Logsから自動デプロイされるコードに、上記を含めたコード全体も載せておきます。 こちらをコピペしていただくのが簡単です。 コード全体 Node.js // v1.1.2 var https = require('https'); var zlib = require('zlib'); var crypto = require('crypto'); var endpoint = 'search-apachedummylog-opensearch-amepwsh24gynwv63reszss5u4e.ap-northeast-1.es.amazonaws.com'; // Set this to true if you want to debug why data isn't making it to // your Elasticsearch cluster. This will enable logging of failed items // to CloudWatch Logs. var logFailedResponses = false; exports.handler = function(input, context) { // decode input from base64 var zippedInput = new Buffer.from(input.awslogs.data, 'base64'); // decompress the input zlib.gunzip(zippedInput, function(error, buffer) { if (error) { context.fail(error); return; } // parse the input from JSON var awslogsData = JSON.parse(buffer.toString('utf8')); // transform the input to Elasticsearch documents var elasticsearchBulkData = transform(awslogsData); // skip control messages if (!elasticsearchBulkData) { console.log('Received a control message'); context.succeed('Control message handled successfully'); return; } // post documents to the Amazon Elasticsearch Service post(elasticsearchBulkData, function(error, success, statusCode, failedItems) { console.log('Response: ' + JSON.stringify({ "statusCode": statusCode })); if (error) { logFailure(error, failedItems); context.fail(JSON.stringify(error)); } else { console.log('Success: ' + JSON.stringify(success)); context.succeed('Success'); } }); }); }; function transform(payload) { if (payload.messageType === 'CONTROL_MESSAGE') { return null; } var bulkRequestBody = ''; payload.logEvents.forEach(function(logEvent) { var timestamp = new Date(1 * logEvent.timestamp); // index name format: cwl-YYYY.MM.DD var indexName = [ 'cwl-' + timestamp.getUTCFullYear(), // year ('0' + (timestamp.getUTCMonth() + 1)).slice(-2), // month ('0' + timestamp.getUTCDate()).slice(-2) // day ].join('.'); var source = buildSource(logEvent.message, logEvent.extractedFields); source['@id'] = logEvent.id; source['@timestamp'] = new Date(1 * logEvent.timestamp).toISOString(); source['@message'] = logEvent.message; source['@owner'] = payload.owner; source['@log_group'] = payload.logGroup; source['@log_stream'] = payload.logStream; //console.log("logEvent.message = " + logEvent.message); var messageConvJson = logEvent.message.replace(/"/g,'\"'); messageConvJson = messageConvJson.replace(/{\\"/,'{"'); messageConvJson = messageConvJson.replace(/\\"\s*:\s*\\"/g,'":"'); messageConvJson = messageConvJson.replace(/\\"\s*,\s*\\"/g,'","'); messageConvJson = messageConvJson.replace(/\\"}/,'"}'); //console.log("messageConvJson = " + messageConvJson); var messageJson = JSON.parse(messageConvJson); //console.log("messageJson.log = " + messageJson.log); let [ remote_host, remote_logname, remote_user, ts, tz, method, resource, protocol, statusCode, size, referer, ...userAgent ] = messageJson.log.split(' '); statusCode = +statusCode; size = +size; ts = ts.substring(1); tz = tz.substring(0, tz.length - 1); const accesstime = ts + tz; userAgent = userAgent.join(''); source['@remote_host'] = remote_host; source['@accesstime'] = accesstime; source['@method'] = method; source['@resource'] = resource; source['@protocol'] = protocol; source['@statusCode'] = statusCode; source['@size'] = size; source['@referer'] = referer; source['@userAgent'] = userAgent; console.log(source) var action = { "index": {} }; action.index._index = indexName; action.index._type = payload.logGroup; action.index._id = logEvent.id; bulkRequestBody += [ JSON.stringify(action), JSON.stringify(source), ].join('\n') + '\n'; }); return bulkRequestBody; } function buildSource(message, extractedFields) { if (extractedFields) { var source = {}; for (var key in extractedFields) { if (extractedFields.hasOwnProperty(key) && extractedFields[key]) { var value = extractedFields[key]; if (isNumeric(value)) { source[key] = 1 * value; continue; } var jsonSubString = extractJson(value); if (jsonSubString !== null) { source['$' + key] = JSON.parse(jsonSubString); } source[key] = value; } } return source; } var jsonSubString = extractJson(message); if (jsonSubString !== null) { return JSON.parse(jsonSubString); } return {}; } function extractJson(message) { var jsonStart = message.indexOf('{'); if (jsonStart < 0) return null; var jsonSubString = message.substring(jsonStart); return isValidJson(jsonSubString) ? jsonSubString : null; } function isValidJson(message) { try { JSON.parse(message); } catch (e) { return false; } return true; } function isNumeric(n) { return !isNaN(parseFloat(n)) && isFinite(n); } function post(body, callback) { var requestParams = buildRequest(endpoint, body); var request = https.request(requestParams, function(response) { var responseBody = ''; response.on('data', function(chunk) { responseBody += chunk; }); response.on('end', function() { var info = JSON.parse(responseBody); var failedItems; var success; var error; if (response.statusCode >= 200 && response.statusCode < 299) { failedItems = info.items.filter(function(x) { return x.index.status >= 300; }); success = { "attemptedItems": info.items.length, "successfulItems": info.items.length - failedItems.length, "failedItems": failedItems.length }; } if (response.statusCode !== 200 || info.errors === true) { // prevents logging of failed entries, but allows logging // of other errors such as access restrictions delete info.items; error = { statusCode: response.statusCode, responseBody: info }; } callback(error, success, response.statusCode, failedItems); }); }).on('error', function(e) { callback(e); }); request.end(requestParams.body); } function buildRequest(endpoint, body) { var endpointParts = endpoint.match(/^([^\.]+)\.?([^\.]*)\.?([^\.]*)\.amazonaws\.com$/); var region = endpointParts[2]; var service = endpointParts[3]; var datetime = (new Date()).toISOString().replace(/[:\-]|\.\d{3}/g, ''); var date = datetime.substr(0, 8); var kDate = hmac('AWS4' + process.env.AWS_SECRET_ACCESS_KEY, date); var kRegion = hmac(kDate, region); var kService = hmac(kRegion, service); var kSigning = hmac(kService, 'aws4_request'); var request = { host: endpoint, method: 'POST', path: '/_bulk', body: body, headers: { 'Content-Type': 'application/json', 'Host': endpoint, 'Content-Length': Buffer.byteLength(body), 'X-Amz-Security-Token': process.env.AWS_SESSION_TOKEN, 'X-Amz-Date': datetime } }; var canonicalHeaders = Object.keys(request.headers) .sort(function(a, b) { return a.toLowerCase() < b.toLowerCase() ? -1 : 1; }) .map(function(k) { return k.toLowerCase() + ':' + request.headers[k]; }) .join('\n'); var signedHeaders = Object.keys(request.headers) .map(function(k) { return k.toLowerCase(); }) .sort() .join(';'); var canonicalString = [ request.method, request.path, '', canonicalHeaders, '', signedHeaders, hash(request.body, 'hex'), ].join('\n'); var credentialString = [ date, region, service, 'aws4_request' ].join('/'); var stringToSign = [ 'AWS4-HMAC-SHA256', datetime, credentialString, hash(canonicalString, 'hex') ] .join('\n'); request.headers.Authorization = [ 'AWS4-HMAC-SHA256 Credential=' + process.env.AWS_ACCESS_KEY_ID + '/' + credentialString, 'SignedHeaders=' + signedHeaders, 'Signature=' + hmac(kSigning, stringToSign, 'hex') ].join(', '); return request; } function hmac(key, str, encoding) { return crypto.createHmac('sha256', key).update(str, 'utf8').digest(encoding); } function hash(str, encoding) { return crypto.createHash('sha256').update(str, 'utf8').digest(encoding); } function logFailure(error, failedItems) { if (logFailedResponses) { console.log('Error: ' + JSON.stringify(error, null, 2)); if (failedItems && failedItems.length > 0) { console.log("Failed Items: " + JSON.stringify(failedItems, null, 2)); } } } コピペが完了したら、デプロイボタンをクリックします。 最後にCloudWatch Logsコンソール上に、本Lambda関数のログストリームが作成され、エラー発生が無い事を確認します。 5.OpenSearchの初期設定を実行する OpenSearchコンソールから、OpenSearch Dashboards の URLを確認し、アクセスします。 ※正常にアクセスできない場合、アクセス元IPアドレスの指定が不完全な可能性が大きいです。  ドメインの「セキュリティ設定>編集」 からアクセスポリシーを開き、ビジュアルエディタから許可したいIPアドレスを設定してください。 OpenSearchにアクセスできたら、左上のハンバーガーメニューから、[Index Management>Indices]と遷移し、「cwl-yyyy.mm.dd」という名前のインデックスが作成されている事を確認します。 確認が出来たら、Index Patternsを登録します。 左上のハンバーガーメニューから、[Stack Management>Index patterns]と遷移し、「Create index pattern」をクリックします。 「Index pattern name」に「cwl-*」を指定し、Next Step。 「Time Field」に「@timestamp」を指定し、「Create index pattern」をクリック。 46のFieldを持つインデックスパターンが作成された事を確認します。 6.OpenSearchでダッシュボードを作る OpenSearch左上のハンバーガーメニューから、[Discover]へ遷移し、CHANGE INDEX PATTERNメニューから手順5で作成した、[cwl-*]を選択し、正しく転送されたログが表示される事を確認してください。 続いて左上のハンバーガーメニューから、[Visualize]へ遷移し、「Create Visualize」をクリック。 以下3パターンのグラフを作成します。 ■Line(単位時間あたりの応答サイズ数合計) → Name:Apache_SizeChart ■Vertical Bar(UA TOP30) → Name: Apache_StatusCode ■Pie(UA TOP30) → Name:Apache_UA 最後に左上のハンバーガーメニューから、[DashBoard]へ遷移し、今作成したVizualizeを並べます。 簡単ですが、Apacheログの可視化が完了しました。 番外編 OpenSearchで用意されているダミーデータで遊んでみる OpenSearchダッシュボードへ最初に接続した段階でお気づきだと思いますが、OpenSearchには予め用意されているダミーデータがあります。 ECサイトのオーダーデータ、フライトデータ、WEB Logの3点セットになっていて、インデックスファイルのみではなくVizualizeやDashBoardも含まれています。 OpenSearchでどんなことが出来るのか?や、ダッシュボード作成のコツをつかむ事が出来ると思います。 是非活用してみてください。 セッションの中でお話した通り、OpenSearchだけを試してみるアプローチならば、後編手順の #1とこの番外編のみでも触り始める事が出来ます。 全行程終了 今回はダミーログを生成してテストをしてみましたが、ここまでの道のりをマスターすれば、あとはデータを本番に切り替えるだけです。 集めたデータで何が出来るか、どんなデータが足りないか、そもそも何を可視化したいかが、ずいぶん明確になったのではないでしょうか。 一旦このような環境を作ってしまえば、例えば以下のように他のAWSサービスを利用して、検証を進めるのもずいぶんとハードルが低くなった事と思います。 このエントリが、ビッグデータ利用の推進や、ログ分析基盤を構築する一助になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UiPath Orchestratorで発生したイベントをTeamsに通知する

はじめに UiPath Orchestratorで発生するイベント(例:ジョブの開始・終了、キューアイテムの追加など)の通知を受け取りたいケースってよくあると思います。今回はTeamsで通知を受け取る方法にチャレンジしてみます。 ※今回はRobotの処理中で発生したイベント(業務エラーなど)の通知ではありません。その場合は、ワークフローにメール送信機能などを組み込むケースが多いと思います。 使うもの UiPath Orchestrator(Clould) ※2021/12時点 AWS API Gateway AWS Lambda Microsoft Teams 仕組み 大まかな仕組みは、こんな感じです。 Orchestratorで発生したイベントはWebhookで外部アプリケーション(AWS)へ送信します。 Webhookで発信されたイベントはAPI Gatewayで受け取り、Lambdaで解析され、Teamsに送られます。 TeamsではIncoming Webhookを使い、チャネルにポストします。 作ってみる その1 Teamsの設定 Teamsでは通知先としたいチャネルからコネクタ「Incoming Webhook」を有効化させます。 その際、URLが表示されるのでメモしておきます。 その2 Lambdaの設定 Orchestratorから受け取ったWebhookを解析し、TeamsのAPIを使ってチャネルにポストするプログラムを実装します。 言語は何でもいいのですが、今回はPython 3.8とします。 以下は、ジョブの開始・終了・失敗のイベントを処理するソースコードです。 Pythonはかじった程度なので、いいコードではないかもしれません。また最低限しか実装していませんので、参考程度としてください。 lambda_function.py import json import datetime import dateutil.parser import urllib.request import traceback import sys def lambda_handler(event, context): # TeamsのIncoming WebhookのURL teams_url = "https://..." data = {} err_flg = 0 try: # イベント別にメッセージとテーマカラーを設定する if event["Type"] == "job.started": msg_title = "ジョブが開始されました" theme_color = "17a2b8" elif event["Type"] == "job.faulted": msg_title = "ジョブが異常終了しました" theme_color = "dc3545" elif event["Type"] == "job.completed": msg_title = "ジョブが正常終了しました" theme_color = "28a745" else: # 指定以外のイベント発生時 err_flg = 9 if err_flg == 0: # メッセージの内容 teams_message = [ {"name" : "発生日時", "value" : (dateutil.parser.parse(event["Timestamp"]) + datetime.timedelta(hours=9)).strftime("%Y/%m/%d %H:%M:%S")}, {"name" : "ジョブID", "value" : str(event["Job"]["Id"])}, {"name" : "プロセス名", "value" : event["Job"]["Release"]["ProcessKey"]}, {"name" : "マシン名", "value" : event["Job"]["Robot"]["MachineName"]} ] # ジョブ異常終了の場合はエラー情報を付加 if event["Type"] == "job.faulted": teams_message.append({"name" : "情報", "value" : event["Job"]["Info"]}) data = { "themeColor" : theme_color, "summary" : msg_title, "sections" : [{ "activityTitle" : msg_title, "facts" : teams_message }], } except Exception as e: # Lambdaエラー時 err_flg = 1 traceback.print_exc() data = { "themeColor" : "dc3545", "summary" : "AWS Lambda Error", "sections" : [{ "activityTitle" : "AWS Lambda Error", "facts" : [ {"name" : "発生日時", "value" : datetime.datetime.now(datetime.timezone(datetime.timedelta(hours=9))).strftime("%Y/%m/%d %H:%M:%S")}, {"name" : "情報", "value" : str(e)} ] }], } if err_flg != 9: # メッセージをTeamsに送る(Incoming Webhook) req = urllib.request.Request(teams_url, json.dumps(data).encode()) urllib.request.urlopen(req) if err_flg == 0: print("[Success] " + event["Type"] + ": " + event["Job"]["Release"]["ProcessKey"] + "(" + str(event["Job"]["Id"]) + ")") elif err_flg == 1: print("[Error] AWS Lambda Error") else: print("[Warn] Other OC Event(Don't notify Teams)"); return 200 Orchestrator Webhookのメッセージ(JSON)フォーマットやTeamsメッセージの装飾などは下記参考をご覧ください。 その3 API Gatewayを設定する REST APIを作成し、OrchestratorのWebhookの受け口を作成します。 今回はリソース「jobs」、メソッドは「POST」とします。 メソッドを作成の際、上記作成したLambda関数を指定します。 設定が終わったら、任意のステージにデプロイします。その際、URLが表示されるのでメモしておきます。 その4 Orchestratorを設定する 最後にOrchestratorのWebhookを有効化します。 URLにはAPI Gatewayの設定でメモしたURLにリソースを追加したものを指定します。 イベントは通知したいものを選択します。全選択もできますが、その分、Lambdaのプログラムが複雑になるのでお勧めしません。必要最小限に絞った方が無難です。 動かしてみる Orchestratorでジョブを動かしてみると、無事Teamsに通知できました。 参考 UiPath Orchestrator Webhook イベントの種類 Microsoft Teams Incoming Webhook メッセージの送信
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSクライアントVPNとCertificate Manager(ACM)を用いて証明書を発行・再発行・更新・失効する

概要 無料でSSL証明書が利用できるAWS Certificate Manager(以下ACM)を使用してサーバ証明書とクライアント証明書の発行手順を解説します。 前提 クライアントVPNエンドポイントをVPC上に設定し、クライアントとVPCとの間でVPNを作る。 クライアントからのアクセスに対しては認証に利用するサーバ証明書とクライアント証明書をACMに登録する。 クライアントにはOpenVPNクライアントをインストールし、OpenVPN公式のeasy-rsaを利用し、クライアント証明書をセットする。 ALB(アプリケーションロードバランサー)などにACMで発行した証明書をセットし、HTTPS化するという方法は今回は説明しない。 手順 easy-rsaインストール PKI環境の初期化 認証機関(CA)の作成し、CA証明書を作成 サーバ・クライアント証明書とキーを作成 ACMにCA証明書・証明書とキーをインポート 1. 証明書発行作業 VPN接続時に使用するクライアント証明書の発行・設定は、使用者が増える度に実施する作業です。 基本的に以下の公式ドキュメントに準じて説明いたします。 ルートユーザーになっておいてください。(一応) sudo su - easy-rsaインストール ここからが本番です。 まずは、easy-rsaをインストールします。 git clone https://github.com/OpenVPN/easy-rsa.git easyrsa3まで移動します。 cd easy-rsa-master/easyrsa3 vars.exampleという証明書の有効期限の設定ファイルがあるので、リネームしてvarsを作成しておきます。 cp -p vars.example vars 証明書の有効期限の中を確認します。 # cat vars (略) # In how many days should certificates expire? #set_var EASYRSA_CERT_EXPIRE 825 # How many days until the next CRL publish date? Note that the CRL can still be # parsed after this timeframe passes. It is only used for an expected next # publication date. #set_var EASYRSA_CRL_DAYS 180 # How many days before its expiration date a certificate is allowed to be # renewed? #set_var EASYRSA_CERT_RENEW 30 # Random serial numbers by default, set to no for the old incremental serial numbers (略) デフォルトでは、 証明書の有効期限が#set_var EASYRSA_CERT_EXPIRE 825で825日 期限の何日前から証明書を更新できるかの日数が#set_var EASYRSA_CERT_RENEW 30で30日 となっているので、変更する場合はコメントアウト外して数値を変更します。 変更する場合はviなどで変更しましょう。 PKI環境を初期化 以下のコマンドを入力してPKI環境を初期化します。 ./easyrsa init-pki 成功するとpkiディレクトリができあがります。 CA(認証機関)証明書を発行 ./easyrsa build-ca nopass 成功するとpkiディレクトリ配下にca.crtができあがります。 このファイルがCA(認証機関)のルート証明書です。 サーバ証明書とクライアント証明書発行 CA証明書ができると証明書とキーが発行できるようになります。 それではそれぞれの証明書とキーを発行します。 サーバ証明書の発行 ./easyrsa build-server-full server nopass 成功すると、pki/issued/配下に証明書が、 pki/private/配下にキーが作成されます。 クライアント証明書の発行 ./easyrsa build-client-full client1.domain.tld nopass 同様にpki/issued/配下に証明書が、 pki/private/配下にキーが作成されます。 ACMに証明書をインポートする AWS Certificate Managerコンソールから、「インポート」を選択します。 証明書の詳細入力画面において3つの入力欄があります。 証明書本文 証明書のプライベートキー 証明書チェーン それぞれについて説明します。 証明書本文 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/issued/server.crt server.crtファイル内の「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」までをコピーし貼り付けます。 証明書のプライベートキー 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/private/server.key server.keyファイル内の「-----BEGIN PRIVATE KEY-----」から「-----END PRIVATE KEY-----」をコピーし貼り付けます。 証明書チェーン 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/ca.crt ca.crtファイル内の「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」までをコピーし貼り付けます。 タグの追加は必要に応じて入力してください。(必須ではありません) 次へ押すと確認画面が表示されるので、問題なければインポートを押下してインポート完了です。 2. 証明書更新作業 サーバ証明書とクライアント証明書の有効期限が切れてしまった場合の更新作業です。 有効期限設定ファイルの確認 以下のコマンドで有効期限ファイルの設定を確認します。 # cat vars (略) # In how many days should certificates expire? set_var EASYRSA_CERT_EXPIRE 365 # How many days until the next CRL publish date? Note that the CRL can still be # parsed after this timeframe passes. It is only used for an expected next # publication date. #set_var EASYRSA_CRL_DAYS 180 # How many days before its expiration date a certificate is allowed to be # renewed? set_var EASYRSA_CERT_RENEW 30 (略) ※有効期限が365日、変更可能日が期限から30日の場合。 有効期限を変える必要があれば、任意の数値に変更し、 問題なければ、そのまま更新作業に入ります。 証明書の更新 easyrsa3ディレクトリで以下のコマンドを実行し、証明書の更新を行います。 ./easyrsa renew <更新対象の証明書ドメイン名> nopass ※<更新対象の証明書ドメイン名>はACMのコンソール画面よりドメイン名が記載されていますので、そちらに置き換えてください。 ※renewコマンドを実行すると途中で「Continue with renew: 」とプロンプトが出るので、「yes」と入力 更新後の期限の確認 cat /easy-rsa-master/easyrsa3/pki/issued/<更新対象の証明書ドメイン名>.crt コマンドを実行すると、 # cat /opt/easy-rsa-master/easyrsa3/pki/issued/test.server.crt Certificate: Data: Version: 3 (0x2) Serial Number: de:***************** Signature Algorithm: sha256WithRSAEncryption Issuer: CN=Easy-RSA CA Validity Not Before: Jan 7 06:16:54 2022 GMT Not After : Jan 7 06:16:54 2023 GMT (略) Not Before:とNot After :で開始日と期限日を確認することができます。 証明書の再インポート 更新した証明書はACMコンソール画面より、再インポートが可能です。 対象の証明書のIDをクリックすると、再インポートがあるのでそれを選択します。 あとは、発行作業と同様の手順です。 3. 証明書再発行作業 新しい証明書を再度発行する場合に発生する作業です。 基本的には新規で発行する手順と同様ですが、再発行しようとしている証明書が失効していることが前提です。 easyrsa3ディレクトリで以下のコマンドを実行してください。 サーバ証明書とキーの再発行 ./easyrsa build-server-full <失効した証明書のドメイン名> nopass クライアント証明書とキーの再発行 ./easyrsa build-client-full <失効した証明書のドメイン名> nopass 再発行した証明書とキーは再インポートを行います。 手順は2. 更新作業の再インポートと同様です。 4. 証明書失効作業 VPN接続で使用している証明書が紛失や漏洩してしまったり、クライアントVPNを使用しなくなったユーザがいた場合に実施する作業です。 クライアント失効リストを生成 以下のコマンドを実行し、証明書削除とクライアント失効リストを生成します。 ./easyrsa revoke <失効対象のクライアント証明書のドメイン名> ※revokeコマンドを実行すると途中で「Continue with revoke: 」とプロンプトが出るので、「yes」と入力します。 成功すると、「Revocation was successful.」と表示が出ます。※下図は一例 失効リストは「easyrsa-master/easyrsa3/pki/crl.pem」に作成されます。※下図は一例 証明書失効リスト(CRL)を更新 以下コマンドを実行し、証明書失効リスト(CRL)を更新します。 ./easyrsa gen-crl 成功すると、更新が成功した旨のメッセージが表示されます。 証明書失効リストをクライアントVPNエンドポイントにインポート 以下コマンドを実行し、上記の作業で生成した証明書失効リストをクライアントVPNエンドポイントにインポートしましょう。 aws ec2 import-client-vpn-client-certificate-revocation-list --certificate-revocation-list file:///easy-rsa-master/easyrsa3/pki/crl.pem --client-vpn-endpoint-id <クライアントVPNエンドポイントID> クライアントVPNエンドポイントIDは「VPC」コンソール内の「クライアントVPNエンドポイント」に記載されています。 成功すると、以下のメッセージになります。 { "Return": true } まとめ クライアントVPNエンドポイント経由でのアクセスに関しての、証明書の発行などについて説明しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSクライアントVPNエンドポイントとCertificate Manager(ACM)を用いて証明書を発行・再発行・更新・失効する

概要 無料でSSL証明書が利用できるAWS Certificate Manager(以下ACM)を使用してサーバ証明書とクライアント証明書の発行手順を解説します。 状況に応じて、証明書の更新や再発行、失効させる作業もあると想定し、手順を説明します。 前提 クライアントVPNエンドポイントをVPC上に設定し、クライアントとVPCとの間でVPNを作る。 クライアントからのアクセスに対しては認証に利用するサーバ証明書とクライアント証明書をACMに登録する。 クライアントにはOpenVPNクライアントをインストールし、OpenVPN公式のeasy-rsaを利用し、クライアント証明書をセットする。 ALB(アプリケーションロードバランサー)などにACMで発行した証明書をセットし、HTTPS化するという方法は今回は説明しない。 手順 easy-rsaインストール PKI環境の初期化 認証機関(CA)の作成し、CA証明書を作成 サーバ・クライアント証明書とキーを作成 ACMにCA証明書・証明書とキーをインポート 1. 証明書発行作業 VPN接続時に使用するクライアント証明書の発行・設定は、使用者が増える度に実施する作業です。 基本的に以下の公式ドキュメントに準じて説明いたします。 ルートユーザーになっておいてください。(一応) sudo su - easy-rsaインストール ここからが本番です。 まずは、easy-rsaをインストールします。 git clone https://github.com/OpenVPN/easy-rsa.git easyrsa3まで移動します。 cd easy-rsa-master/easyrsa3 vars.exampleという証明書の有効期限の設定ファイルがあるので、リネームしてvarsを作成しておきます。 cp -p vars.example vars 証明書の有効期限の中を確認します。 # cat vars (略) # In how many days should certificates expire? #set_var EASYRSA_CERT_EXPIRE 825 # How many days until the next CRL publish date? Note that the CRL can still be # parsed after this timeframe passes. It is only used for an expected next # publication date. #set_var EASYRSA_CRL_DAYS 180 # How many days before its expiration date a certificate is allowed to be # renewed? #set_var EASYRSA_CERT_RENEW 30 # Random serial numbers by default, set to no for the old incremental serial numbers (略) デフォルトでは、 証明書の有効期限が#set_var EASYRSA_CERT_EXPIRE 825で825日 期限の何日前から証明書を更新できるかの日数が#set_var EASYRSA_CERT_RENEW 30で30日 となっているので、変更する場合はコメントアウト外して数値を変更します。 変更する場合はviなどで変更しましょう。 PKI環境を初期化 以下のコマンドを入力してPKI環境を初期化します。 ./easyrsa init-pki 成功するとpkiディレクトリができあがります。 CA(認証機関)証明書を発行 ./easyrsa build-ca nopass 成功するとpkiディレクトリ配下にca.crtができあがります。 このファイルがCA(認証機関)のルート証明書です。 サーバ証明書とクライアント証明書発行 CA証明書ができると証明書とキーが発行できるようになります。 それではそれぞれの証明書とキーを発行します。 サーバ証明書の発行 ./easyrsa build-server-full server nopass 成功すると、pki/issued/配下に証明書が、 pki/private/配下にキーが作成されます。 クライアント証明書の発行 ./easyrsa build-client-full client1.domain.tld nopass 同様にpki/issued/配下に証明書が、 pki/private/配下にキーが作成されます。 ACMに証明書をインポートする AWS Certificate Managerコンソールから、「インポート」を選択します。 証明書の詳細入力画面において3つの入力欄があります。 証明書本文 証明書のプライベートキー 証明書チェーン それぞれについて説明します。 証明書本文 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/issued/server.crt server.crtファイル内の「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」までをコピーし貼り付けます。 証明書のプライベートキー 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/private/server.key server.keyファイル内の「-----BEGIN PRIVATE KEY-----」から「-----END PRIVATE KEY-----」をコピーし貼り付けます。 証明書チェーン 以下のコマンドを実行してください。 cat /easy-rsa-master/easyrsa3/pki/ca.crt ca.crtファイル内の「-----BEGIN CERTIFICATE-----」から「-----END CERTIFICATE-----」までをコピーし貼り付けます。 タグの追加は必要に応じて入力してください。(必須ではありません) 次へ押すと確認画面が表示されるので、問題なければインポートを押下してインポート完了です。 2. 証明書更新作業 サーバ証明書とクライアント証明書の有効期限が切れてしまった場合の更新作業です。 有効期限設定ファイルの確認 以下のコマンドで有効期限ファイルの設定を確認します。 # cat vars (略) # In how many days should certificates expire? set_var EASYRSA_CERT_EXPIRE 365 # How many days until the next CRL publish date? Note that the CRL can still be # parsed after this timeframe passes. It is only used for an expected next # publication date. #set_var EASYRSA_CRL_DAYS 180 # How many days before its expiration date a certificate is allowed to be # renewed? set_var EASYRSA_CERT_RENEW 30 (略) ※有効期限が365日、変更可能日が期限から30日の場合。 有効期限を変える必要があれば、任意の数値に変更し、 問題なければ、そのまま更新作業に入ります。 証明書の更新 easyrsa3ディレクトリで以下のコマンドを実行し、証明書の更新を行います。 ./easyrsa renew <更新対象の証明書ドメイン名> nopass ※<更新対象の証明書ドメイン名>はACMのコンソール画面よりドメイン名が記載されていますので、そちらに置き換えてください。 ※renewコマンドを実行すると途中で「Continue with renew: 」とプロンプトが出るので、「yes」と入力 更新後の期限の確認 cat /easy-rsa-master/easyrsa3/pki/issued/<更新対象の証明書ドメイン名>.crt コマンドを実行すると、 # cat /opt/easy-rsa-master/easyrsa3/pki/issued/test.server.crt Certificate: Data: Version: 3 (0x2) Serial Number: de:***************** Signature Algorithm: sha256WithRSAEncryption Issuer: CN=Easy-RSA CA Validity Not Before: Jan 7 06:16:54 2022 GMT Not After : Jan 7 06:16:54 2023 GMT (略) Not Before:とNot After :で開始日と期限日を確認することができます。 証明書の再インポート 更新した証明書はACMコンソール画面より、再インポートが可能です。 対象の証明書のIDをクリックすると、再インポートがあるのでそれを選択します。 あとは、発行作業と同様の手順です。 3. 証明書再発行作業 新しい証明書を再度発行する場合に発生する作業です。 基本的には新規で発行する手順と同様ですが、再発行しようとしている証明書が失効していることが前提です。 easyrsa3ディレクトリで以下のコマンドを実行してください。 サーバ証明書とキーの再発行 ./easyrsa build-server-full <失効した証明書のドメイン名> nopass クライアント証明書とキーの再発行 ./easyrsa build-client-full <失効した証明書のドメイン名> nopass 再発行した証明書とキーは再インポートを行います。 手順は2. 更新作業の再インポートと同様です。 4. 証明書失効作業 VPN接続で使用している証明書が紛失や漏洩してしまったり、クライアントVPNを使用しなくなったユーザがいた場合に実施する作業です。 クライアント失効リストを生成 以下のコマンドを実行し、証明書削除とクライアント失効リストを生成します。 ./easyrsa revoke <失効対象のクライアント証明書のドメイン名> ※revokeコマンドを実行すると途中で「Continue with revoke: 」とプロンプトが出るので、「yes」と入力します。 成功すると、「Revocation was successful.」と表示が出ます。※下図は一例 失効リストは「easyrsa-master/easyrsa3/pki/crl.pem」に作成されます。※下図は一例 証明書失効リスト(CRL)を更新 以下コマンドを実行し、証明書失効リスト(CRL)を更新します。 ./easyrsa gen-crl 成功すると、更新が成功した旨のメッセージが表示されます。 証明書失効リストをクライアントVPNエンドポイントにインポート 以下コマンドを実行し、上記の作業で生成した証明書失効リストをクライアントVPNエンドポイントにインポートしましょう。 aws ec2 import-client-vpn-client-certificate-revocation-list --certificate-revocation-list file:///easy-rsa-master/easyrsa3/pki/crl.pem --client-vpn-endpoint-id <クライアントVPNエンドポイントID> クライアントVPNエンドポイントIDは「VPC」コンソール内の「クライアントVPNエンドポイント」に記載されています。 成功すると、以下のメッセージになります。 { "Return": true } まとめ クライアントVPNエンドポイント経由でのアクセスに関しての、証明書の発行などについて説明しました。 ご指摘等ありましたらよろしくお願いいたします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2インスタンスの自動停止起動

経緯 EC2インスタンスを、使用しない時間帯のみ止めてコスト削減をしたい。 Lambdaのみでの実装と、Instance Schedulerでの実装を行い、それぞれの複数インスタンス・複数スケジュール設定の場合やコスト、注意などをまとめる。 【参考】 Lambda のみで実装する 〇手動方式〇 1. IAM ポリシーと IAMロール作成 ポリシーの作成。CloudWatch Logの書き込み、EC2書き込み権限保有(以下JSON) { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Effect": "Allow", "Action": [ "ec2:Start*", "ec2:Stop*" ], "Resource": "*" } ] } 上記ポリシーをアタッチしたロールを作成 2. EC2 インスタンスを停止および起動する Lambda 関数の作成 AWS Lambda コンソールで、「関数の作成」 クリック 「一から作成」クリック [基本的な情報] に次の事項を追加 [関数名] に EC2 インスタンスを開始/停止させる関数の名前を入力 [ランタイム] : [python 3.8] を選択 [アクセス権限] : [デフォルトの実行ロールの変更] を展開 [実行ロール] : [既存のロールを使用する] を選択 1で作成したロールを登録 「関数の作成」 「コード」でlambda_functionに以下貼り付け 上の手順で開始させる関数と停止させる関数をそれぞれ作成し、以下を記述する インスタンスの開始 import boto3 region = 'ap-northeast-1' instances = ['InstanceID'] ec2 = boto3.client('ec2', region_name=region) def lambda_handler(event, context): ec2.start_instances(InstanceIds=instances) print('started your instances: ' + str(instances)) インスタンスの停止 import boto3 region = 'ap-northeast-1' instances = ['InstanceID'] ec2 = boto3.client('ec2', region_name=region) def lambda_handler(event, context): ec2.stop_instances(InstanceIds=instances) print('stopped your instances: ' + str(instances)) 3. CloudWatch Events(Eventblidge) のルールを作成 Amazon CloudWatch (Eventblidge)コンソールを開く 左ナビゲーションペインの [イベント] の下にある [ルール] をクリック [ルールの作成] を選択 [イベントソース] の [スケジュール] をクリック [Cron expression] に、Lambda にインスタンスを起動/停止させる時刻を表すスケジュール式を入力(UTC) [ターゲットの追加] をクリック [Lambda関数] でEC2 インスタンスを停止させる関数(Start,Stop)を選択 [詳細の設定] を選択、名前など設定 「ルールの作成」 ※StartとStopそれぞれ作成 設定した時間になり、スケジュール通り起動・停止していることを確認 〇テンプレートを使用〇 AWS CloudFormation テンプレート を YAML ファイルとして保存 AWS CloudFormationより、スタックを作成して上記のテンプレートを使用 名前や対象インスタンスID、スケジュールをパラメータに設定し、「作成」 以下のLambda関数とCloudwatchルールが作成 Lambda関数 ・TestEC2Scheduler-StartEC2Instances-* ・TestEC2Scheduler-StopEC2Instances-* CloudWatchルール ・TestEC2Scheduler-StartScheduledRule-* ・TestEC2Scheduler-StopScheduledRule- * 設定した時間になり、スケジュール通り起動・停止していることを確認 複数インスタンス・複数スケジュールでの設定 1 >Lambda、Eventblidgeを手動でスケジュールの数×2(StartとStop)作成 ※自動起動停止したいインスタンスの指定は、Lambdaに直接書くか、対象を一覧CSVにしてLambdaで指定する。 2>CloudFormation テンプレートを使用してスケジュールの数だけスタックを作成(LambdaとEventblidgeはStartとStopが自動作成) ※インスタンスの指定は、スタックのパラメータ(Lambdaの環境変数)に登録するか、対象を一覧CSVにしてLambdaで指定する。 コスト Amazon EventBridge の料金 カスタムイベント :百万件の公開済みカスタムイベントごとに 1.00USD サードパーティー (SaaS) のイベント :百万件の公開済みイベントごとに 1.00USD イベントからもう一つのバスへ :百万件の送信済みイベントごとに 1.00USD AWS Lambda 料金 リクエスト:リクエスト100万件あたり0.20USD 実行時間:GB-秒あたり0.0000166667USD 注意 Amazon EventBridgeでのスケジュール設定は、UTCで行われるためマイナス9時間で設定する。 Instance Schedulerで実装する 1. スタックの作成 「 AWSコンソールで起動する 」>サインイン(入り直す必要あり)、リージョンを東京へ変更 名前、Account Id、TimeZoneなどの パラメータ を設定 「スタックの作成」 2. スケジュール定義、ピリオド定義 「DynamoDB」<「テーブル」にて「InstanceScheduler-ConfigTable-*」クリック 「項目を表示」 ピリオド定義 (type 属性が period) の office-hours のチェックボックスを選択し、「アクション」から「コピー」をクリック 下記設定し、「項目の作成」をクリック(例:月曜日の9:00~18:00のみ起動する設定) フィールド名 値 name mon-start-9am begintime 9:00 endtime 18:00 description Office hours on Monday weekdays[0] mon seattle-office-hours のチェックボックスを選択し、アクションから「コピー」をクリック 下記設定し、「項目の作成」をクリック フィールド名 値 name mon-9am-18pm timezone Asia/Tokyo description Monday 9am to 18pm in Japan (JST) periods[0] mon-start-9am 3. インスタンスへのタグ付け マネジメントコンソールのインスタンス一覧画面 から対象のインスタンスを選択し、タグのタブから「タグを管理」をクリック 「タグを追加」をクリックし、キーに CloudFormationのInstance Scheduler tag name で指定した値 (初期値は Schedule)、値に適用するスケジュール定義の name の値 (今回は mon-9am-18pm) を入力 「保存」 4. リモートスタックの作成 「 リモートスタックの作成 」>サインイン(入り直す必要あり)、リージョンを東京へ変更 InstanceSchedulerAccount、Namespace(1のNamespaceと同じになるように設定)などパラメータを設定 設定した時間になり、スケジュール通り起動・停止していることを確認 複数インスタンス・複数スケジュールでの設定 起動・停止したいスケジュールをDynamoDBのpiriodとscheduleで設定し、対象インスタンスにschedule名をタグ付けする。 問題なく起動・停止した場合はタグにScheduleMessageが作成され、実行時間が確認できる。 →スタック2つ、Lambda1つ、Eventblidge1つ、DynamoDBのScheduleとpiriodはスケジュールの数分作成すればよい。  ただ、どの対象インスタンスがどのスケジュールで設定されているか一覧で分からないため、別途管理する必要があるかも。 コスト 例:月額約 33.24 ドル Instance Schedulerの費用 ※5 つのアカウント * 2 リージョン * 3 サービス (Amazon EC2/Amazon RDS/Aurora クラスター) * 2 つのスケジュール数 * 1 日あたりのスケジューラーあたりのアクションの最小数 2 * 各 Runbook の 4 つのステップの場合 →コストは起動停止対象のインスタンス数は関係せず、実行数の合計によって決まる。 注意 「2. スケジュール定義、ピリオド定義」の際、 config と schedule のTimeZoneを「Asia/Tokyo」にする。 3までの手順で実行するとオートメーションのステップ1でエラーとなる。「4. リモートスタックの作成」で別途スタックを立てることで、1つめのスタックのAutomationExecutionRoleのロールが作成され、正常に動作する。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS]Fargate+Firelens(Fluentbit)でapache-loggenを実行しCloudwatchLogsへログ転送する

やりたい事 2022/01/13開催の SB Tech Festival で登壇した内容、「ビッグデータ活用の第一歩AWS環境での大容量ログ可視化」の詳細手順(前半)ページです。 このエントリで解説するのは以下のイメージ 後半部分のLambda+OpenSearchについてはこちらの記事を参照してください。 手順 2022/01/11時点でECSコンソールは新しいエクスペリエンスが提供されていますが、本エントリでは旧UIをベースに解説しています。 1.Fargate実行の為のクラスターを用意する まず、FargateはECSの機能の一部という扱いなので、サーバレスアーキテクチャではあるものの最初にクラスターを用意する必要があります。 あまり深く考えずに以下のようにECSコンソールから新しいクラスターを作成しましょう。 クラスターテンプレートはFargateの場合ネットワーキングのみとします。 IAM管理ポリシー、IAMロールのタグや名前は任意の内容でOKです。本手順上では便宜上「apache-loggen」とします。 2.実行に必要なIAMロールを作成しておく IAM管理ポリシーを新規作成し、 JSONタブを開き、 以下のJSONをコピペします。これはFargateコンテナからCloudwatchLogsの必要な操作が出来るように権限を付与する内容です。 { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "logs:CreateLogGroup", "logs:CreateLogStream", "logs:DescribeLogGroups", "logs:DescribeLogStreams", "logs:PutLogEvents", "logs:GetLogEvents", "logs:FilterLogEvents" ], "Resource": "*" } ] } 続いてIAMロールを新規作成し、 以下の通りにロールの利用方法を指定します。 次の画面で、作成済みのIAM管理ポリシーを指定しCloudwatchLogs操作関連のロール作成は完了です。 続いて、既成のIAM管理ポリシーであるAmazonECSTaskExecutionRolePolicyを指定したロールも作成しておきましょう。 IAM管理ポリシー、IAMロールのタグや名前は任意の内容でOKです。 3.タスク定義を作成する クラスターが用意出来たら、Fargate用のタスク定義を新規に作成します。 以下の通りに設定を進めます。 項目名 設定内容 定義名 apache-loggen-task タスクロール <(2)で作成したAmazonECSTaskExecutionRolePolicyを指定したロールを指定> 互換性が必要 Fargateにチェック オペレーティングシステムファミリー Linux タスク実行ロール <(2)で作成したCloudwatchLogs操作関連のIAMロールを指定> タスクメモリ (GB) 0.5GB タスク CPU (vCPU) 0.25vCPU FireLens の統合を有効にする チェック タイプ Fluentbit イメージ 自動選択のまま「適用」ボタンをクリック 上記を進めると、図示のように log_routerコンテナのみが作成されている状況になります。 log_routerコンテナ名部分をクリックして、ヘルスチェックのみ以下の内容通りに編集しておきます。 項目名 設定内容 コマンド echo '{"health": "check"}' | nc 127.0.0.1 8877 || exit 1 間隔 180 タイムアウト 30 開始期間 180 再試行 3 続いて本体のコンテナを作成します。 項目名 設定内容 コンテナ名 apache-loggen イメージ public.ecr.aws/t9o0n2i8/kedamari:latest Dockerイメージは個人的に用意したものをパブリック公開しておきましたので、上記内容のまま使っていただけます。 中身はCentOS7のイメージに、Rubyとapache_loggen gemを導入しただけのものなので、慣れている方はもちろんご自身で作成いただいてもOKです。 コンテナのヘルスチェックは以下の通りに設定します。 項目名 設定内容 コマンド /root/.rbenv/shims/apache-loggen --limit 1 間隔 10 タイムアウト 5 開始期間 5 再試行 3 環境設定は以下の通り、PATHを通してください。 項目名 設定内容 CPU ユニット数 0 エントリポイント /usr/bin/bash コマンド /root/.rbenv/shims/apache-loggen,--rate=20 作業ディレクトリ / 環境変数(PATH) /root/.rbenv/shims:/root/.rbenv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/root/bin また、スタートアップ順序依存はコンテナ名に log_routerを指定し、状態を HEALTHY指定としましょう。 (念の為Firelensのサイドカーコンテナが起動したのちに、ログを生成させる挙動にする為です。) 最後にストレージとログを以下のように設定します。 項目名 設定内容 ログドライバー awsfirelens ■ログオプション(全てValue指定) 項目名 設定内容 log_group_name /apache-dummy-log auto_create_group true log_stream_prefix fargate- region ap-northeast-1 Match * Name cloudwatch_logs 4.サービスを起動する 事前にVPCの作成が必要です。任意のVPCを作成したい場合は thak4さんの「AWS VPC作成手順」あたりを参考にしてください。 項目名 設定内容 クラスター apache-loggen <実際には手順1で作成した任意の名称> 起動タイプ FARGATE オペレーティングシステムファミリー Linux タスク定義 apache-loggen-task:X <末尾の数字はリビジョンなので、タスク定義の更新都度インクリメントされます> プラットフォームのバージョン LATEST サービス名 apache-loggen-service サービスタイプ REPLICA タスクの数 1 最小ヘルス率 100 最大率 200 デプロイサーキットブレーカー 無効 デプロイメントは初回選択項目から変更無しです。 次のステップへ進み、ネットワークは以下の通り設定します。 項目名 設定内容 クラスターVPC 事前に作成したVPCを指定 サブネット 事前に作成したサブネットを指定 セキュリティグループ は新しいものを作成し、インバウンドルールは必ず全削除する パブリック IP の自動割り当て ENABLED ロードバランシングは初回選択項目から変更無しです。 次のステップ、Auto Scaling (オプション)も初回選択項目から変更無しです。 確認画面で、内容を確認し、サービスの作成をクリックします。 5.正常稼働の確認 クラスター : apache-loggen のステータス ACTIVEと、タスクのステータスがRunningになっている事を確認します。 CloudWatch Logsのロググループに /apache-dummy-log が作成されている事を確認します。 実際にログエントリを確認し、以下のようなApache Logが流れている事を確認します。 前半終了 Fargateコンテナ環境でApacheのダミーログを生成し、CloudWatch Logsへ転送するところまでが完了しました。 マネジメントコンソールGUIでこのようにステップバイステップの手順を解説しているエントリは少ないので、初学者向けに役立つエントリになれば幸いです。 実際の運用シーンでは、CLI+JSONで管理する事になると思いますので、うまく動いた方はそのようなエントリを確認して、ぜひ次のステージへステップアップしてください。 引き続き後半のエントリでは以下の内容へと進みます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

S3バケット内の情報取得スクリプト

経緯 S3バケット内のオブジェクト(ファイル)について、名前・最終更新日・サイズを取得してCSVファイルに出力したい。 Pythonでスクリプトを書いていく。不慣れなため無駄が多いかと思うが、ご容赦を。 参考:AWS Lambda Python S3でフォルダ以下のファイル一覧を取得する スクリプト作成 以下の構成において、xxx配下のファイル一覧(名前、最終更新日、サイズ)を取得する。 また、オブジェクトは名前(ooooo)で絞って出力する。 s3://aaa.backup/xxx/ooooo aaa.backup:バケット xxx:プリフィックス ooooo**:オブジェクト 出力するCSVの中身は以下のようにしたい。 見出し: Name,Data,Size データ: oooooabcd,[JST]2022/01/10 12:05:10,20.5 KB オブジェクトキーの出力 参考:Boto 3 で Amazon S3 上の key を取得する方法、実装例、注意点 Boto3でS3 Buckets上の情報を取得するときは、list_objects() を使用する。 今回は対象が多いため、プリフィックスとオブジェクト名の一部を指定して条件を絞る。 最終更新日をJSTに変更 S3の最終更新日時(content['LastModified'])はUTCで保存されている。今回はJSTで出力したいため、変換を行う。 参考:PythonでUTCからJSTへの時刻の変換 この方法で行うとS3で保持している最終更新日の最後の6字「+00:00」が邪魔になり、UTCのまま出力されてしまう。 そのため、文字列へ変換して該当文字を削除することでJSTで出力することができる。 datetimeを使用してJSTへの変換を指定 文字列にし、「+00:00」を省く(後方から6文字削除) JSTへの時刻の変換 import datetime def utc_to_jst(timestamp_utc): datetime_utc = datetime.datetime.strptime(timestamp_utc + "+0000", "%Y-%m-%d %H:%M:%S%z") datetime_jst = datetime_utc.astimezone(datetime.timezone(datetime.timedelta(hours=+9))) timestamp_jst = datetime.datetime.strftime(datetime_jst, '%Y-%m-%d %H:%M:%S') return timestamp_jst for page in page_iterator: for content in page['Contents']: date = content['LastModified'] Last = str(date) Last = Last[:-6] print(Last) print("[JST]" + utc_to_jst(Last)) (結果) 2022/01/10 12:05:10 +00:00 [JST]2022/01/10 12:05:10 オブジェクト名からプリフィックスの表示を除く そのままオブジェクトキー(content['Key'])を出力すると、プリフィックスを含んだものが出力される。 見にくいため、プリフィックス名を指定してオブジェクト名のみを出力する。 参考:完全一致する文字列を削除: replace() name = content['Key'] print(name) print(name.replace('xxx/','')) (結果) xxx/oooooabcd oooooabcd サイズをKB表示にする サイズ(content['Size'])をそのまま出力すると、バイトのため大きなサイズの際は見にくい。 また、単位もつけたいため、以下のように記述。 1024で割ってKBへ変換>小数点以下第2位を四捨五入>データ型を文字列に変換>単位をつける n = content['Size']/1024 Size = round(n,1) Size = str(Size) + "KB" print(content['Size']) print(Size) (結果) 22837 22.3KB 1000件以上のデータ出力に対応 Python で Amazon S3 バケットの全オブジェクト名を取得するジェネレータ 今回はV2不使用のため不要だったが、使用する際は考慮が要るかも。 CSV出力 Pythonでcsvファイルにデータを書き込みをする基本中の基本 Excelでも使用したいので文字コードを shift-jis で保存 ヘッダ行をつける(Name,Date,Size) ※実際のスクリプトでは1度開いてヘッダをつけ、再度開き直してオブジェクトキーなどの入力を行う ファイル名がオブジェクト名になるよう変数を設定 import csv wwith open(Object + '_JST.csv','a',newline='',encoding = "shift-jis") as f: writer = csv.writer(f) writer.writerow(["Name","Date","Size"]) with open(Object + '_JST.csv','a',newline='',encoding = "shift-jis") as f: writer = csv.writer(f) writer.writerow([content['Key'].replace('xxx/',''),Last,Size]) スクリプト 以下、完成したスクリプト import boto3 import datetime import csv MY_REGION = 'MY_REGION'; MY_BUCKET = 'MY_BUCKET'; TARGET_PATH = 'TARGET_PATH/'; Object = 'Object'; client = boto3.client('s3', region_name=MY_REGION) paginator = client.get_paginator('list_objects') # フィルタリング設定 operation_parameters = { 'Bucket': MY_BUCKET, 'Prefix': TARGET_PATH + Object } page_iterator = paginator.paginate(**operation_parameters) # UTCからJSTへの時刻の変換 def utc_to_jst(timestamp_utc): datetime_utc = datetime.datetime.strptime(timestamp_utc + "+0000", "%Y-%m-%d %H:%M:%S%z") datetime_jst = datetime_utc.astimezone(datetime.timezone(datetime.timedelta(hours=+9))) timestamp_jst = datetime.datetime.strftime(datetime_jst, '%Y-%m-%d %H:%M:%S') return timestamp_jst # 出力CSVを作成し、ヘッダをつける with open(Object + '_JST.csv','a',newline='',encoding = "shift-jis") as f: writer = csv.writer(f) writer.writerow(["Name","Date","Size"]) for page in page_iterator: for content in page['Contents']: # 最終更新日をUTCからJSTへ変更 Last = content['LastModified'] Last = str(Last) Last = Last[:-6] Last = utc_to_jst(Last) # サイズをキロバイトへ変更 n = content['Size']/1024 Size = round(n,1) Size = str(Size) + "KB" with open(Object + '_JST.csv','a',newline='',encoding = "shift-jis") as f: writer = csv.writer(f) # CSVへ情報出力 writer.writerow([content['Key'].replace('xxx/',''),Last,Size]) (出力結果) Name,Data,Size oooooabcd,[JST]2022/01/10 12:05:10,20.5KB oooooefgh,[JST]2022/01/11 10:03:18,13.2KB …
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者向けAWS Client VPN入門

AWSには幾つかの接続方法があります。 普通に接続するのはインターネット接続になります。 ただAWSにセキュアに接続したい場合は幾つかの方法があります。 AWS社の資料「複数拠点からセキュアにVPCに接続」を参照して頂ければ解りやすいのですが、代表的な接続方法として以下の3パターンがあります。 # 方法 回線 内容 1 Direct Connect 専用線 お客様のデータセンターやオフィスを専用線を介してAWSへプライベートに接続するサービス。 2 Site-to-Site VPN インターネット回線 お客様のデータセンターやオフィスをIPsec VPNを介してAWSへプライベートに接続するサービス。 3 Client VPN インターネット回線 お客様のクライアントをOpen VPNベースのVPNを介してAWSへプライベートに接続するサービス。 Direct Connect Direct Connectは専用線を利用して接続します。メリットは専用線という事で安定した通信が可能という事です。残り2つの方法はインターネット回線の為、帯域が保障されていません。そのため、クリティカルな業務を構築する場合などに利用されます。ただし回線費用が残りの2つの方法と比較すると高額であり、専用線の敷設に3か月程度かかるなど、なかなか大規模な対応が必要になります。 Site-to-Site VPN Site-to-Site VPNはインターネット回線を利用して接続します。専用線と比較すると回線の安定性は劣ります。オンプレ環境側にルーターを設置してIPsec VPNによる暗号化を実現しています。価格の安さと接続の容易さからDirect Connectのバックアップ回線として利用される場合もあります。 Client VPN Client VPNはインターネット回線を利用して接続します。名前の通りクライアントPCからOpen VPNベースのVPN接続を可能にしています。Site-to-Site VPNはルータの準備が必要でしたが、こちらはツールをダウンロードするだけで接続できるため、安価に即日で接続が可能です。テレワークで自宅環境から会社のAWS環境に接続したい場合などに利用できます。 今回の記事では、誰でも簡易に利用できる「Client VPN」の手順について説明したいと思います。なお、記載内容は個人的なものであり、所属する企業や組織、団体を代表する見解その他ではありませんので、ご承知おきください。 ■ClientVPN接続を実際にやってみる ClientVPNの接続方法をイメージする為には、一度実際に接続してみることをお薦めします。ここからは必要な手順を順を追って説明していきます。 まず今回記載する構成の最終系は以下の通りです。 今回はクライアントPCからAWSにClientVPNで接続することを目標とします。また、TeratermからのSSH接続でEC2(Linux)に接続しますが、その接続をVPN経由でのみ可能とする設定を実施します。 当初はインターネットからパブリックIPで接続する以下の構成が出来ていることを前提とします。 また、もう1点、今回はClinetVPN接続に証明書を利用する為、証明書設定の流れが重要です。証明書に関しては以下の関連図を前提に説明します。 (1)サーバーおよびクライアント証明書とキーの生成 ClientVPNでは3つの認証の最低1つの認証を実施する必要があります。 今回は「相互認証」を実施します。 ・Active Directory 認証 (ユーザーベース) ・相互認証 (証明書ベース) ・シングルサインオン (SAML ベースのフェデレーション認証) (ユーザーベース) 相互認証では、ClientVPN は証明書を使用してクライアントPCとサーバー間の認証を実行します。このステップでは認証機関 (CA) を構築して証明書を発行する必要があります。 なお、ここで実施してる作業は以下の図の通りです。 まず、前提として証明書サーバ用のEC2環境を起動させる必要があります。 「Amazon Linux 2 AMI (HVM) 」で起動させてみましょう。 その後は「Linux/macOS」の手順に従って証明書を作成していきます。 ただし、最初に「git clone」コマンドを実施してますが、gitがインストールされていないので失敗します。開始前に以下のコマンドを実行して、gitのインストールを実施してください。 sudo yum install git 上記でインストール後に「git clone」以降の手順が実施可能です。 「5.」の「クライアント証明書とキーを生成」手順は必要とするエンドユーザ単位で実施する必要があります。例えば3台のクライアントからClientVPNを利用する場合は、以下のような形で3つ作成する必要がある訳です。 $ ./easyrsa build-client-full client1.domain.tld nopass $ ./easyrsa build-client-ful2 client1.domain.tld nopass $ ./easyrsa build-client-ful3 client1.domain.tld nopass 「7.」でCLIで「AWS Certificate Manager」に対して証明書をアップロードする手順が記載されています。CLIの初期設定が必要なのですが、ここでは環境から一旦ファイルをダウンロードして、その後「AWS Certificate Manager」に手動でアップロードする手順を(2)に記載します。 ファイルの取り出し手順 Linuxあまり利用したことが無い人の為に、抽出方法を記載しておきます。Linuxサーバへの接続を「Tera Term」で接続している事を前提とします。 まずは「Tera Term」で「ファイル>SSH SCP」を選択してください。 その後、以下の画面が表示されるので、以下の条件で入力して「Receive」ボタンを押してください。 ファイルが「To」で指定した場所に出力されます。 まず、「①」に出力するファイルのフルパスを入力します。出力するファイルのフルパスは「Amazon Linux 2 AMI (HVM) 」であれば以下になると思います。 /home/ec2-user/custom_folder/ca.crt /home/ec2-user/custom_folder/client1.domain.tld.crt /home/ec2-user/custom_folder/client1.domain.tld.key /home/ec2-user/custom_folder/server.crt /home/ec2-user/custom_folder/server.key ただし「No such file or directory」と表示された場合はパスが間違っているので、ファイルの場所をもう一度確認するようにしてください。以下の様に対象ファイルの存在するフォルダ階層で「pwd」と打てば確認できます。 [ec2-user@ip-10-1-0-202 custom_folder]$ ls -t client2.domain.tld.key client1.domain.tld.key server.key server.crt client2.domain.tld.crt client1.domain.tld.crt ca.crt [ec2-user@ip-10-1-0-202 custom_folder]$ pwd /home/ec2-user/custom_folder ②にクライアントPC側の出力先を指定します。画面ではディスクトップを指定しています。 そして③の「Recive」ボタンを押すことで出力が可能です。 (2)AWS Certificate Managerへの証明書のインポート AWS Certificate Managerへの証明書のインポートは「証明書のインポート」の通り実施します。 作業の内容は以下の図の部分となります。 この証明書登録の際に、何を設定したら良いか悩むと思いますので、貼り付ける必要がある情報を以下に記載します。中身すべてコピーして貼り付ける様にしてください。 # 項目名 サーバ証明書 クライアント証明書 ① 証明書本文 server.crt client1.domain.tld.crt ① 証明書のプライベートキー server.key client1.domain.tld.key ① 証明書チェーン ca.crt ca.crt 実際に入力すると以下のようになります。 クライアント証明書は台数分登録してください。 結果として以下のような状況となります。以下は「サーバ証明書」と2台分の「クライアント証明書」を登録した状態となります。 証明書について注意が必要なのは「有効期限」です。証明書IDを選択して詳細を確認すると「有効期限」が設定されていることが確認できます。証明書が失効しないように定期的な更新が必要になります。 (3)クライアント VPN エンドポイントを作成する クライアント VPN エンドポイントを作成するとき、VPN 接続を確立するためにクライアントが接続できる VPN 構造を作成します。手順は「クライアント VPN エンドポイントを作成する」の通りです。 以下の図の部分の作業となります。 また、エンドポイント作成時には証明書を定義します。証明書関連図では以下の部分の作業となります。 この際にエンドポイント用のCIDRを設定する必要がありますが、IPアドレスの設定は注意が必要です。 IP アドレス範囲は、ターゲットネットワークまたはクライアント VPN エンドポイントに関連するいずれかのルートと重複できません。クライアント CIDR は、/12~/22 の範囲のブロックサイズが必要で、VPC CIDR またはルートテーブル内のその他のルートと重複できません。クライアント VPN エンドポイントの作成後にクライアント CIDR を変更することはできません。 エンドポイント作成画面では以下の登録画面が表示されるので、先ほどAWS Certificate Managerにインポートしたサーバ証明書をクライアント証明を設定します。 エンドポイントを作成すると「保留中」と表示されます。そのまま(4)の作業に進んでください。 また、エンドポイントの作成は接続するクライアント単位で作成します。クライアント2台が接続する場合は、以下のクライアント証明書単位で2つ作成する訳です。 (4)ターゲットネットワークをクライアント VPN エンドポイントに関連付ける クライアントが VPN セッションを確立できるようにするため、ターゲットネットワークをクライアント VPN エンドポイントに関連付ける必要があります。ターゲットネットワークは、VPC のサブネットとなります。手順は「ターゲットネットワークをクライアント VPN エンドポイントに関連付ける」の通りです。 以下の図の部分の作業となります。この関連付けを実施することで、関連付けしたサブネット内にENIで作成されるのがポイントです。 関連付け実施後は「保留中-関連付け」と表示されます。 最終的に時間が経過すると「使用可能」という状態になります。 この後ネットワークインタフェース画面を確認すると新規に2つのENIが追加されていることが解ります。これがサブネットとClientVPNゲートウェイが通信するためのENIとなります。どのClientVPCエンドポイントと関連しているかは、ENIの「説明」欄で確認することができます。 (5)クライアント VPN エンドポイントへの承認ルールの追加 承認ルールは、ネットワークへのアクセス許可を与えるファイアウォールルールとして機能します。承認ルールを追加することで、特定のクライアントに対し、特定のネットワークへのアクセス許可を与えます。手順は「クライアント VPN エンドポイントへの承認ルールの追加」の通りです。 承認設定後は「認証中」となります。 最終的に時間が経過すると「アクティブ」になります。 (6)クライアント VPN エンドポイントの分割トンネルに関する考慮事項 クライアント VPN エンドポイントで分割トンネルを使用する場合、VPN が確立されると、クライアント VPN ルートテーブル内のすべてのルートがクライアントルートテーブルに追加されます。VPN の確立後にルートを追加する場合は、新しいルートがクライアントに送信されるように接続をリセットする必要があります。今回の作業では割愛しますが、必要に応じて設定してください。 (7)クライアント設定ファイルをエクスポートして設定する クライアントPCの設定に必要なクライアント設定ファイルをエクスポートして、設定を実施します。手順は「クライアント設定ファイルをエクスポートして設定する」の通りです。 以下の図の部分の作業となります。 「downloaded-client-config.ovpn」というファイルを開いて修正する必要があるのですが、以下の通り修正すれば問題ありません。 client dev tun proto udp remote asdf.cvpn-endpoint-0011abcabcabcabc1.prod.clientvpn.eu-west-2.amazonaws.com 443 remote-random-hostname resolv-retry infinite nobind remote-cert-tls server cipher AES-256-GCM verb 3 <ca> ※ここはファイルをエクスポートした時点で設定されているので設定不要。 </ca> <cert> ※ここに「client1.domain.tld.crt」の内容を張り付ける。 </cert> <key> ※ここに「client1.domain.tld.key」の内容を張り付ける。 </key> reneg-sec 0 (8)AWS クライアント VPN をダウンロードしてインストールする VPN接続を実施したいクライアントPC(Windows)にClientVPNクライアントをインストールします。インストーラーはAWS Client VPN downloadからダウンロードしてインストールしてください。 以下の図の部分の作業となります。 (9)AWS VPN クライアントで接続する クライアントPCのClientVPNクライアントにプロファイルを登録して接続を実施します。手順は「AWS VPN クライアントで接続する」の通りです。 以下の図の部分の作業となります。 証明書関連図では以下の部分の作業となります。接続用のプロファイルに証明書情報が含まれており、それによって認証されていることが理解できます。 これが接続できている状態です。この状態になると通常のインターネット通信はできなくなります。 通常ではPingが飛ばないプライベートIPにPingを飛ばして応答があることで、正常に接続出来ていることが確認できます。 ping 10.1.0.202 10.1.0.202 に ping を送信しています 32 バイトのデータ: 10.1.0.202 からの応答: バイト数 =32 時間 =118ms TTL=63 10.1.0.202 からの応答: バイト数 =32 時間 =123ms TTL=63 10.1.0.202 からの応答: バイト数 =32 時間 =276ms TTL=63 10.1.0.202 からの応答: バイト数 =32 時間 =293ms TTL=63 10.1.0.202 の ping 統計: パケット数: 送信 = 4、受信 = 4、損失 = 0 (0% の損失)、 ラウンド トリップの概算時間 (ミリ秒): 最小 = 118ms、最大 = 293ms、平均 = 202ms 敢えて誤った設定で接続してみましたが、その場合は以下の様なメッセージが表示されて接続できません。 実際にTeraTermからもプライベートIPでEC2にSSHで接続できました。 (10)EC2へのSSH接続をClinetnVPN経由に制限する ただ、現状だとパブリックIP経由でもプライベートIP経由でも接続できる状態となっています。 例えばプライベートIP経由での接続のみをSSHで許可したい場合は、セキュリティグループで以下の様に設定変更すれば制御可能です。インターネットからの接続を可能にする場合はソースが「0.0.0.0/0」となっている場合があると思いますが、ここをサブネットの「10.1.0.0/24」に設定変更すれば、プライベートIPでの接続のみが可能となり、ClientVPN経由のみでSSH接続が可能となります。 ■まとめ 以上がAWSにおけるClientVPNの接続手順になります。 誰でも簡単に設定できるので、一度自分の手で実施してみることをお薦めします。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Cloud Practitioner について勉強してわからないことをまとめた ~モジュール3~

はじめに 「AWS Cloud Practitioner について勉強してわからないことをまとめた ~モジュール2~」の続きです。 過去投稿分 「AWS Cloud Practitioner について勉強してわからないことをまとめた ~モジュール1~」 参考:AWS Cloud Practitioner Essentials(Japanese) AWSグローバルインフラストラクチャ リージョンを選択する サービス、データ、アプリケーションに適したリージョンを決定する際は、以下の4つのビジネス要素を考慮する。 データガバナンスと法的要件の遵守 会社や場所によっては、ある特定の地域からデータを実行する必要がある場合があります。 例えば、自社のすべてのデータを英国内に保管するという要件がある場合は、ロンドンリージョンを選択する。 ユーザーとの近接性 お客様に近いリージョンを選択すると、コンテンツをお客様に素早く提供できる。 リージョン内で利用可能なサービス 場合によって、お客様に提供しようとするすべての機能が、最も近いリージョンでは利用できない場合がある。 料金 アプリケーションを米国とブラジルの両方で実行しようとしているとする。ブラジルの課税体系上、あるワークロードをサンパウロリージョンから実行する場合、オレゴンリージョンと比較して料金が50%高くなる可能性がある。 アベイラビリティーゾーン リージョン内の1つのデータセンターまたはデータセンターのグループで構成されている。アベイラビリティーゾーンは互いに数十マイル離れた位置にある。これは、アベイラビリティーゾーン間の低レイテンシーを実現するために十分な距離。 災害が起きても複数のアベイラビリティーゾーンが影響を受ける可能性を減らすことのできる十分な距離である。 エッジロケーション Amazon CloudFrontで、今絵tンツのキャッシュされたコピーをお客様の近くに保存するために使用する場所 Amazon CloudFront の主な特徴 AWSリソースをプロビジョニングする方法 AWS マネジメントコンソール AWSのサービスにアクセスし、サービスを管理するためのウェブベースのインターフェイス。 リソースのモニタリング、アラームの表示、請求情報の確認と言ったタスクを実行することもできる。 AWS コマンドラインインターフェイス APIリクエストを作成する際の時間を短縮するには、AWSコマンドラインインターフェイス(AWS CLI)を使用できる。サービスやアプリケーションをスクリプトで自動化できる。 ソフトウェア開発キット プログラミング言語やプラットフォーム用に設計されたAPIを使用して、AWSのサービスを簡単に使用できる。 AWS Elastic Beanstalk ユーザーがコードと設定情報を提供することによって、以下のようなタスクの実行に必要なリソースが自動的にデプロイされる。 - キャパシティーの調整 - 負荷分散 - 自動スケーリング - アプリケーションの状態モニタリング AWS CloudFormation インフラストラクチャをコードとして扱うことができる。つまり、AWSマネジメントコンソールを使用してリソースを個別にプロビジョニングする代わりに、コード行を記述して環境を構築できる。 注意 自己学習のために割愛している部分があるので 公式も合わせてご確認ください。 AWS Cloud Practitioner Essentials(Japanese) 参考書籍: 図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS] OpenSearch Service(Elasticsearch)で商品検索APIを作る

1.はじめに みなさんOpenSearch(Elasticsearch)使ってますか? OpenSearchはオープンソースの検索エンジンで、全文検索や商品検索、さらにはデータ分析まで、検索用途なら何でも幅広く活用することができます。今回はそのOpenSearchを使って、ECサイトで使うような商品検索APIを作る方法を紹介してみたいと思います。 2.OpenSearchとElasticsearchの関係 OpenSearchという言葉自体を聞き慣れない人もいるかもですが、これはElasticsearch(ES)とほぼ同じものです。 ESは元々Apache License 2.0に準拠したオープンソースソフトウェア(OSS)で、AWSも以前はこのOSS版のESをサービスとして提供していました。 しかし2021年1月、開発元のElasitic社はESのライセンスを変更し、OSSではない独自ライセンスにしてしまいました。これによりAWSはElasticsearchをサービスとして提供できなくなるため、最終のOSSバージョンであるES7.10をフォークしてOpenSearchというOSSの検索エンジンを新たに作成、2020年7月に公開しました。OpenSearchの開発にはAWSだけでなく色々な企業が参画しており、Apache License 2.0の純粋なOSSとなってます。 こういった経緯があるので、現時点ではOpenSearchの中身はESとほぼ同じです。今後は別物として進化していくので少しずつ機能に差異が出てくる可能性はありますが、個人的には特殊なライセンス体系となってしまい色々縛りがあるESよりも、純粋なOSSとして提供されているOpenSearchの方がよいかなと思っています。 3.今回のAPI作成にあたり前提となる情報 まずは前提となる情報(システム構成など)を記載しておきます。 3-1.システム構成 API基盤にAPI Gateway+Lambdaを使用し、ここからOpenSearchに対して検索を行う形になります。簡単な構成図は以下の通りです。 ちなみに今回はAPIの構築にサーバレス向けフレームワークのChaliceを使います。言語はPythonです。Chaliceって何よ?という方向けに、本記事の末尾に参考のリンクを貼ってますので興味があればご覧ください。 3-2.前提とするデータ 今回検索対象とするデータは以下の通りです。商品の属性値(ブランド、性別、カテゴリ、価格、・・・etc)で絞り込みを行い、結果をJSONで返却するイメージになります。 商品コード 商品名 ブランド 性別 カテゴリ 価格(円) 色 サイズ A001 無地スキニーパンツ ブランドA メンズ パンツ 2000 ホワイト S A002 無地スキニーパンツ ブランドA メンズ パンツ 2000 ホワイト M A003 無地スキニーパンツ ブランドA メンズ パンツ 2000 ホワイト L A004 無地スキニーパンツ ブランドA メンズ パンツ 2000 ブラック S A005 無地スキニーパンツ ブランドA メンズ パンツ 2000 ブラック M A006 無地スキニーパンツ ブランドA メンズ パンツ 2000 ブラック L B001 デニムパンツ ブランドB レディース パンツ 2500 ブラウン S B002 デニムパンツ ブランドB レディース パンツ 2500 ブラウン M B003 デニムパンツ ブランドB レディース パンツ 2500 ブラウン L B004 デニムパンツ ブランドB レディース パンツ 2500 ホワイト S B005 デニムパンツ ブランドB レディース パンツ 2500 ホワイト M B006 デニムパンツ ブランドB レディース パンツ 2500 ホワイト L C001 長袖Tシャツ ブランドC メンズ Tシャツ 1500 ホワイト S C002 長袖Tシャツ ブランドC メンズ Tシャツ 1500 ホワイト M C003 長袖Tシャツ ブランドC メンズ Tシャツ 1500 ホワイト L C004 長袖Tシャツ ブランドC キッズ Tシャツ 1000 グレー S C005 長袖Tシャツ ブランドC キッズ Tシャツ 1000 グレー M C006 長袖Tシャツ ブランドC キッズ Tシャツ 1000 グレー L 4.商品検索APIの構築手順 前置きが長くなりましたが、ここからは実際の構築手順を書いていきます。早速やってみましょう。 4-1.OpenSearchクラスタの作成 まずはOpenSearchのクラスタを作成します。 AWSコンソール上でOpenSearchの画面を開き、右側上部にある「Create domain」ボタンを押下します。 続く画面でOpenSearchの名前やインスタンスタイプ、ストレージ容量等を指定します。今回はテスト用なので専用マスタノードは指定していません。この場合はデータノードがマスタノードを兼ねる形になり、検索パフォーマンスにいくらか影響が出る可能性があるので、本番用途であれば別途専用マスタノードを立てることを検討ください。 続いてアクセス権とセキュリティの設定を行います。OpenSearchの配置先はVPCまたはパブリック領域のどちらかを選べます。VPCだとセキュリティ的に固いのですが、ネットワークの設定が若干煩雑なので今回はパブリックに配置することにしました。その代わりに特定IPアドレスのみアクセスできるように制限を加えています。自分の開発用マシンなど、OpenSearchへアクセスする必要があるPCやネットワークのIPを指定してください。 続いてタグの追加画面が表示されます。必要に応じて設定しましょう。 最後に確認画面上で「確認」を押すとクラスタが作成されます。ステータスが「読み込み中」から「アクティブ」に変われば作成完了です。 4-2.OpenSearchのインデックス作成 続いて検索に使用するインデックスの作成を行います。 (1)Kibana(OpenSearch Dashboards)へのアクセス KibanaはElasticsearchに同梱されているGUIの分析ツールです。直感的な操作で様々なグラフを生成できるためデータ分析などに活用できますが、これ以外にもElasticsearchに対して各種クエリ(データ検索や登録など)を実行したりできるので何かと便利です。 OpenSearchではKibanaから名前が変わって「OpenSearch Dashboards」になりましたが、今回はクラスタ作成時にElasticsearch 7.10を選択したので画面上でも「Kibana」という表現になります。若干ややこしいですがご了承ください。 Kinabaへアクセスするには以下ドメインの詳細画面を開き、「Kibana URL」のリンクをクリックします。 するとKibanaが開きます。画面左のバーガーメニューから「Dev Tools」を選択します。 以下の通り、Dev Toolsのコンソールが開きます。左側のエリアにOpenSearch用のクエリを入力して実行すれば、結果が右側のエリアに表示されます。以降の手順でOpenSearchのAPIを叩くときはこの画面を使用することを前提にしています。 (2)インデックス作成 OpenSearchのインデックスは、通常のデータベース(RDB)でいうところのテーブルにあたります。 前段に記載したサンプルデータ格納用のインデックスを作成してみましょう。クエリは以下の通りです。 ※kibana上で実行 PUT /test-products 今回はインデックス名をtest-productsにしてみました。この時点では定義も何もない空のインデックスができるだけなので、続く手順でデータ構造を定義していきます。 (3)Mapping作成 Mappingとは、インデックス内のフィールド等の構成を予め事前に定義したものです。RDBでいうところの、Create Table時に指定する中身(カラム名やデータ型等)に相当します。 OpenSearchには自動マッピング機能が備わっており、何もしなくてもデータ登録時に自動でMappingが生成されますが、その内容は最初に登録されるデータに依存します。場合によっては想定外の型が勝手に指定されてしまう恐れもあるため、基本的にはMappingは自分で作成した方が望ましいです。(この辺はESも同じ) 今回データ用のマッピング生成クエリは以下の通りです。 ※kibana上で実行 PUT /test-products/_mapping { "properties" : { "productCode" : {"type" : "keyword"}, "productName" : {"type" : "keyword"}, "brand" : {"type" : "keyword"}, "gender" : {"type" : "keyword"}, "category" : {"type" : "keyword"}, "price" : {"type" : "integer"}, "color" : {"type" : "keyword"}, "size" : {"type" : "keyword"} } } データ構造がネストしていないのでシンプルですね。今回は基本的に全文検索は不要なので、文字列型は全てkeyword、数値型はintegerにしました。 (4)インデックスへのデータ登録 続いてインデックスへのデータ登録を行います。今回は複数件を一括で登録したいのでBulk APIを使用します。 クエリは以下の通りで、データの内容は前段で紹介したものと同じです。インデックスのID(RDBのテーブルでいうところの主キーみたいなもの)は商品コードと同一としました。 ※kibana上で実行 POST /test-products/_doc/_bulk { "index" : {"_id" : "A001" } } {"productCode": "A001","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ホワイト","size": "S"} { "index" : {"_id" : "A002" } } {"productCode": "A002","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ホワイト","size": "M"} { "index" : {"_id" : "A003" } } {"productCode": "A003","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ホワイト","size": "L"} { "index" : {"_id" : "A004" } } {"productCode": "A004","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ブラック","size": "S"} { "index" : {"_id" : "A005" } } {"productCode": "A005","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ブラック","size": "M"} { "index" : {"_id" : "A006" } } {"productCode": "A006","productName": "無地スキニーパンツ","brand": "ブランドA","gender": "メンズ","category": "パンツ","price": "2000","color": "ブラック","size": "L"} { "index" : {"_id" : "B001" } } {"productCode": "B001","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ブラウン","size": "S"} { "index" : {"_id" : "B002" } } {"productCode": "B002","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ブラウン","size": "M"} { "index" : {"_id" : "B003" } } {"productCode": "B003","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ブラウン","size": "L"} { "index" : {"_id" : "B004" } } {"productCode": "B004","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ホワイト","size": "S"} { "index" : {"_id" : "B005" } } {"productCode": "B005","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ホワイト","size": "M"} { "index" : {"_id" : "B006" } } {"productCode": "B006","productName": "デニムパンツ","brand": "ブランドB","gender": "レディース","category": "パンツ","price": "2500","color": "ホワイト","size": "L"} { "index" : {"_id" : "C001" } } {"productCode": "C001","productName": "長袖Tシャツ","brand": "ブランドC","gender": "メンズ","category": "Tシャツ","price": "1500","color": "ホワイト","size": "S"} { "index" : {"_id" : "C002" } } {"productCode": "C002","productName": "長袖Tシャツ","brand": "ブランドC","gender": "メンズ","category": "Tシャツ","price": "1500","color": "ホワイト","size": "M"} { "index" : {"_id" : "C003" } } {"productCode": "C003","productName": "長袖Tシャツ","brand": "ブランドC","gender": "メンズ","category": "Tシャツ","price": "1500","color": "ホワイト","size": "L"} { "index" : {"_id" : "C004" } } {"productCode": "C004","productName": "長袖Tシャツ","brand": "ブランドC","gender": "キッズ","category": "Tシャツ","price": "1000","color": "グレー","size": "S"} { "index" : {"_id" : "C005" } } {"productCode": "C005","productName": "長袖Tシャツ","brand": "ブランドC","gender": "キッズ","category": "Tシャツ","price": "1000","color": "グレー","size": "M"} { "index" : {"_id" : "C006" } } {"productCode": "C006","productName": "長袖Tシャツ","brand": "ブランドC","gender": "キッズ","category": "Tシャツ","price": "1000","color": "グレー","size": "L"} 4-3.API作成 続いてChaliceを使った検索用APIの作成方法を解説します。 (1)ライブラリのインストールとプロジェクト作成 まずは開発環境のマシンにChaliceやOpenSearchクライアントなど、必要なライブラリをインストールします。 ※開発マシン上で実行 $ pip install chalice $ pip install opensearch-py $ pip install requests $ pip install requests-aws4auth 続いて以下のコマンドでChaliceの新規プロジェクトを作成します。 ※開発マシン上で実行 $ chalice new-project product_search_sample そうすると以下の構成でディレクトリとファイルが生成されます。 product_search_sample/ ├── app.py ├── .chalice │ └── config.json └── requirements.txt (2)検索API作成 続いて検索API用のコードを書いていきます。 今回使用するOpenSearch用のクエリはこんな感じです。_searchエンドポイントを使用しており、ブランドや性別などの属性値を指定することで条件に一致する商品の一覧が返ってくるようになっています。 GET /test-products/_search { "from" : 0, "size": 50, "track_total_hits" : true, "sort" : [ {"productName" : {"order" : "asc"}} ], "query" : { "bool" : { "must" : [ { "terms" : { "brand" : ["ブランドA", "ブランドB", "ブランドC"] } }, { "terms" : { "gender" : ["メンズ"] } }, { "terms" : { "category" : ["Tシャツ"] } }, { "terms" : { "color" : ["ホワイト"] } }, { "terms" : { "size" : ["L"] } }, { "range" : { "price" : { "gte" : 0, "lt" : 3000 } } } ] } } } クエリ内で指定している各条件の意味は以下の通りです。 項目 意味 from 指定した数字(番号)以降の検索結果を取得する。ページネーションなどで使用。 size 検索結果として取得する件数を指定する。50の場合は上位50件だけ取得。 track_total_hits trueに設定すると、1万件以上のデータを検索対象にするときに正確なcountが取れる。 sort ソート順を指定する。 query この中に検索条件を指定する。 bool, must 複数の検索条件を連結する際に使用。mustの場合はAND条件になる。 terms keyword型のフィールドに対する検索に使用。条件に完全一致するデータを抽出する。条件は配列で複数指定可能。 range 数値型のフィールドに対する検索に使用。範囲指定できる。gteは"以上"、ltは"未満"の扱いになる。 以下、最終的なChaliceのコードを記載していきます。まずディレクトリ構成はこのような形です。新たにcustom-policy_dev.jsonを追加しています。 product_search_sample/ ├── app.py ※コード修正 ├── .chalice │   ├── config.json ※コード修正 │   └── custom-policy_dev.json ※ファイル追加 └── requirements.txt ※コード修正 app.pyはこんな感じです。リクエストボディからJSON形式で検索条件を受け取り、OpenSearch用のクエリを生成して実行、結果を整形して返却します。今回はざっくりしたサンプルコードなので、モジュール分割などはせず全ての処理をapp.pyに押し込んでいます。またデータチェックやエラー制御などは端折ってますのでご了承ください。 app.py import json import traceback import boto3 from opensearchpy import OpenSearch, RequestsHttpConnection from requests_aws4auth import AWS4Auth from chalice import Chalice from chalice import Response app = Chalice(app_name='product_search_sample') # CloudFrontの制約で、GETではリクエストボディが使えないためPOSTにする @app.route('/productSearch', methods=['POST'], cors=True) def index(): try: # 検索条件取得 searchCondition = getSearchCondition() # 検索クエリの組み立て query = createQuery(searchCondition) # OpenSearchへの接続と検索 searchResultsFromOs = executeQuery(query) # 返却用JSONの生成 responseData = createResponseData(searchResultsFromOs) # API応答値の返却 return Response( body = json.dumps(responseData, ensure_ascii=False), headers = {'Content-Type': 'application/json'}, status_code = 200 ) except Exception as e: # スタックトレース出力とエラー応答 print(traceback.format_exc()) responseData = {"message" : "内部エラーが発生しました"} return Response( body = json.dumps(responseData, ensure_ascii=False), headers = {'Content-Type': 'application/json'}, status_code = 500 ) def getSearchCondition(): ''' リクエストボディから検索条件を抽出する ※入力チェックなどは特に行ってないので、必要に応じて実装 ''' body = app.current_request.json_body # 念のためリクエストボディの内容を組み換え searchCondition = dict() if body.get("brand"): searchCondition["brand"] = body["brand"] if body.get("gender"): searchCondition["gender"] = body["gender"] if body.get("category"): searchCondition["category"] = body["category"] if body.get("price"): searchCondition["price"] = body["price"] if body.get("color"): searchCondition["color"] = body["color"] if body.get("size"): searchCondition["size"] = body["size"] return searchCondition def createQuery(searchCondition): ''' OpenSearchに対して投げるクエリを生成する ''' # ベースとなるクエリ query = { "from" : 0, "size": 50, "track_total_hits" : True, "sort" : [ {"productName" : {"order" : "asc"}} ], "query" : { "bool" : { "must" : [] } } } # 検索条件が存在する場合、MUST句(AND)に検索条件を詰め込む if searchCondition: for key in searchCondition.keys(): searchParameKey = key searchParamValue = searchCondition.get(key) if key == "price": # 検索条件がpriceの場合は数値での条件を指定 query["query"]["bool"]["must"].append( { "range" : { searchParameKey : { "gte" : searchParamValue[0], "lt" : searchParamValue[1] } } } ) else: # price以外は文字列検索 query["query"]["bool"]["must"].append( { "terms" : {searchParameKey : searchParamValue} } ) return query def executeQuery(query): ''' OpennSearchへ接続し、検索クエリを投げて結果を返す ''' # 接続文字列 host = 'xxxxxx.ap-northeast-1.es.amazonaws.com' port = 443 region = 'ap-northeast-1' service = 'es' credentials = boto3.Session().get_credentials() awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) indexName = 'test-products' try: # ES接続とクエリ実行 osClient = OpenSearch( hosts = [{'host':host, 'port': port}], http_auth = awsauth, use_ssl = True, verify_certs = True, connection_class = RequestsHttpConnection ) searchResultsFromOs = osClient.search(index=indexName, body=query) return searchResultsFromOs except Exception as e: # スタックトレース出力 print(traceback.format_exc()) raise e def createResponseData(searchResultsFromOs): ''' OpenSearchの検索結果を受け取り、APIの応答値を生成する ''' # 最終的な応答値の雛形を定義 responsData = { "status" : "success", "totalCount" : 0, "results" : [], } # 検索結果が存在しない場合は早々に処理終了 totalCount = searchResultsFromOs["hits"]["total"]["value"] if totalCount == 0: return responsData # 応答値の生成 responsData["totalCount"] = totalCount for result in searchResultsFromOs["hits"]["hits"]: source = result["_source"] responsData["results"].append( { "productCode" : source["productCode"], "productName" : source["productName"], "brand" : source["brand"], "gender" : source["gender"], "category" : source["category"], "price" : source["price"], "color" : source["color"], "size" : source["size"] } ) return responsData config.jsonはこんな感じ。autogen_policyとiam_policy_fileを追加して、Lambadに割り当てるロールを自分で指定できるようにしています。ロールに割り当てる権限はcustom-policy_dev.jsonで定義します。 .chalice/config.json { "version": "2.0", "app_name": "product_search_sample", "stages": { "dev": { "api_gateway_stage": "api", "autogen_policy": false, ※追加 "iam_policy_file": "custom-policy_dev.json" ※追加 } } } custom-policy_dev.jsonはこんな感じ。CloudWatch LogsとOpenSearch(Elasticsearch)へのアクセス権限を追加しています。今回はサンプルなのでOpenSearchの権限を広めにとっていますが、セキュリティが甘くなるのも微妙なので必要に応じて調整が必要です。 .chalice/custom-policy_dev.json { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowAccessCloudWatchLogs", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:*:*:*" }, { "Sid": "AllowElasticsearch", "Effect": "Allow", "Action": [ "es:*" ], "Resource": "*" } ] } requirements.txtはこんな感じ。Lambda実行時に必要になるライブラリを定義しています。 requirements.txt requests==2.26.0 requests-aws4auth==1.1.1 opensearch-py==1.0.0 (3)APIのデプロイと実行 コードができたら「chalice deploy」でデプロイしましょう。以下の通り、コマンド一発で必要なLambda関数とAPI Gateway定義が全て作成されます。 ※開発マシン上で実行 $ chalice deploy Creating deployment package. Creating IAM role: product_search_sample-dev Creating lambda function: product_search_sample-dev Creating Rest API Resources deployed: - Lambda ARN: arn:aws:lambda:ap-northeast-1:xxxxxxxxx:function:product_search_sample-dev - Rest API URL: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/ ではcurlコマンドで今回デプロイしたAPIを叩いてみましょう。まずは商品カテゴリが「Tシャツ」の商品一覧を取得してみます。クエリはこちら。 ※開発マシン上で実行 $ curl -H "Content-Type: application/json" \ -d '{"category" : ["Tシャツ"]}' \ -XPOST https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/productSearch 結果はこうなりました。ちゃんと取れましたね。 { "status": "success", "totalCount": 6, "results": [ { "productCode": "C001", "productName": "長袖Tシャツ", "brand": "ブランドC", "gender": "メンズ", "category": "Tシャツ", "price": "1500", "color": "ホワイト", "size": "S" }, { "productCode": "C006", "productName": "長袖Tシャツ", "brand": "ブランドC", "gender": "キッズ", "category": "Tシャツ", "price": "1000", "color": "グレー", "size": "L" }, ~~~(以下略)~~~ さらにその他の属性(ブランド、性別、価格)で試してみましょう。 ※開発マシン上で実行 $ curl -H "Content-Type: application/json" \ -d '{"brand": ["ブランドA", "ブランドB", "ブランドC"], "gender" : ["メンズ"], "price" : [0, 3000]}' \ -XPOST https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/productSearch 結果はこちら。ちゃんと取れてますね。 { "status": "success", "totalCount": 9, "results": [ { "productCode": "A001", "productName": "無地スキニーパンツ", "brand": "ブランドA", "gender": "メンズ", "category": "パンツ", "price": "2000", "color": "ホワイト", "size": "S" }, { "productCode": "A002", "productName": "無地スキニーパンツ", "brand": "ブランドA", "gender": "メンズ", "category": "パンツ", "price": "2000", "color": "ホワイト", "size": "M" }, ~~~(以下略)~~~ 5.さいごに 今回はAWSのOpenSearch Serviceを使って簡単な商品検索APIを作ってみました。これだけだとRDB使った方が楽じゃんと思われるかもしれませんが、OpenSearchは大量データを取り扱った時にその真価を発揮します。商品点数が100万、1,000万と増えていっても安定した性能を出せるのがOpenSearchのいいところで、実際に使ってみた感じ、非力なマシンを使ってもRDBに比べて性能は出やすいように思えます。 さらにAggregation等の機能を使えば検索結果に対する集計や分析なんかもできるので、RDBだと処理が重くて諦めてしまいがちな高度な検索機能も実現できたりします。この辺りについてはまた別の記事に書けたらいいなと思っています。 この記事が誰かのお役に立てると幸いです。 おまけ:Chaliceについて ChaliceはAWSが開発したサーバレスアプリケーション向けのフレームワークで、Python向けのライブラリとして提供されています。API Gateway+LambdaによるオーソドックスなREST APIを始め、色々なサーバレスアプリを爆速で作ることができます。開発効率は他のフレームワーク(SAM、Amplify等々)と比較しても圧倒的です。(個人的感想ですが) 詳しい解説については以下の記事を参照ください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RX65N Cloud KitのFreeRTOS OTA(Over-The-Air)サンプルプログラムをデバッグする

環境準備 Renesas RX65N Cloud KitでFreeRTOS OTA(Over-The-Air)サンプルプログラムをデバッグする方法を確認してみました。 FreeRTOS OTAサンプルプログラムを動作させるためにはルネサス公式サイトの以下のアプリケーションノートが参考になります。 しかし、以下のアプリケーションノートはRenesas Flash ProgrammerでOTAサンプルプログラムをフラッシュメモリに書き込んで、プログラムを起動させるだけなので、OTAサンプルプログラムをe2 studioを使用してプログラムをデバッグすることができません。 e2 studioの機能でブートローダとファームウェアのプログラムを同時にRenesas RX65N Cloud Kitにダウンロードする機能もありますが、ブートローダがファームウェアを起動するために必要な署名検証データはRenesas Secure Flash Programmerを使用して付加されるものなので、同じくデバッグすることはできません (署名検証でFailします)。 そのため、今回は以下のアプリケーションノートの手順を変更してOTAサンプルプログラムをデバッグする方法を検討しました。 RX ファミリ RX65N における Amazon Web Services を利用したFreeRTOS OTA の実現方法 Renesas ルネサス MCU におけるファームウェアアップデートの設計方針 準備した環境は以下の通りです。 1. Renesas RX65N Cloud Kit (評価ボード) 2. e2 studio 2021-07 (統合開発環境) 3. GCC for Renesas 8.3.0.202004-GNURX Windows (RXマイコン用コンパイラ) 4. Win64 OpenSSL v3.0.1 (署名検証に必要) 5. Renesas Secure Flash Programmer = mot-file-converter v1.0.1 (署名検証データの付加に必要) 6. RX版 FreeRTOS v202002.00-rx-1.0.5 ファームウェアの初期バージョンを作成する 下記アプリケーションノートのPage.26まで進めてRenesas Secure Flash Programmerを使用して、userprog.motを作成します。 この時点でuserprog.motに署名検証データが付加されます。 RX ファミリ RX65N における Amazon Web Services を利用したFreeRTOS OTA の実現方法 アプリケーションノートはCC-RXコンパイラで説明されていますが、GCCコンパイラでも手順はほぼ同じです。 セクション設定の手順のみ異なるので、linker_script.ldを開き、linker_script.ldタブで以下のように設定します。 ファームウェアの初期バージョンをデバッグ可能な形式に変換する ここからアプリケーションノートとは手順が異なります。 作成したuserprog.motをe2 studioでデバッグ可能なファイル形式に変換します。 コマンドプロンプトを起動して、e2 studioから出力されたデバッグサポート用のファイルが格納されているディレクトリに移動します。 通常であれば、C:\Users\%Username%\.eclipse\com.renesas.platform_xxxxxxxxxx\Utilities\ccrxに格納されています。 com.renesas.platform_xxxxxxxxxxのxxxxxxxxxはe2 studioのバージョンに依存すると思います。 移動したディレクトリにuserprog.motをコピーしておき、エンディアンによって以下のコマンドを実行します。 リトルエンディアンの場合 rx-elf-objcopy.exe userprog.mot -O elf32-rx-le -I srec userprog.elf ビッグエンディアンの場合 rx-elf-objcopy.exe userprog.mot -O elf32-rx-be -I srec userprog.elf もしCC-RXコンパイラを使用している場合は、追加で以下のコマンドを実行します。 CC-RXコンパイラを使用している場合 renesas_cc_converter.exe userprog.elf userprog.x e2 studioを使用してデバッグを開始する e2 studioの「デバッグ構成」からboot_loader HardwareDebugを選択して、以下のように設定を変更します。 「メイン」タブで「C/C++アプリケーション」の参照を押して「userprog.elf」を指定する。 CC-RXコンパイラを使用している場合は、「userprog.x」を指定してください。 「Debugger」タブの「Connection Setting」で「起動バンク」を「バンク0」に設定する。 「デバッグツール設定」で「内蔵プログラムROMを書き換えるプログラムをデバッグする」を「はい」に設定する。 「Startup」タブを開いて「イメージとシンボルをロード」を以下のように変更する。 「userprog.elf」の「ロード・タイプ」を「イメージのみ」に変更する。 「追加」から「aws_demos.elf」を追加して「ロード・タイプ」を「シンボルのみ」に変更する。 「追加」から「boot_loader.elf」を追加して「ロード・タイプ」を「シンボルのみ」に変更する。 CC-RXコンパイラを使用している場合は、それぞれ.xファイルを指定してください。 デバッグボタンを押してデバッグを開始してください。 ブートローダの「boot_loader.c」のファームウェアを起動する処理でブレークポイントを貼って、プログラムを実行させると設定したブレークポイントでプログラムが停止することを確認します。 これでブートローダが署名検証データを使用して、ファームウェアの署名検証をPASSされたことが確認できます。 次にファームウェアの「main.c」内のmain関数にブレークポイントを貼って、プログラムを実行させると設定したブレークポイントでプログラムが停止することを確認します。 これでブートローダが正常にファームウェアを起動したことがわかります。 ファームウェア更新後の更新ファームウェアをデバッグする 最後にアプリケーションノートのPage.32以降の作業を進めて、ファームウェア更新後の更新ファームウェアをデバッグします。 ブートローダの「boot_loader.c」のファームウェアを起動する処理でブレークポイントを貼っておきます。 ファームウェア更新後に設定したブレークポイントでプログラムが停止することを確認したら、下記GDBコマンドを実行します。 GDBコマンドの実行はe2 studio下部の「Debugger Console」で実施します。 本GDBコマンドを実行することで、更新ファームウェアのシンボル情報がロードされます。 実行するGDBコマンド(更新ファームウェアの.elfファイルがTempフォルダ内にある場合) symbol-file C:\/Temp\/aws_demos.elf -readnow CC-RXコンパイラを使用している場合は、.xファイルを指定してください。 これでプログラムを再開させると更新ファームウェアのソースコードにプログラムカウンタが停止するはずです。 さいごに 今回はルネサスエレクトロニクス社が提供しているOTAサンプルプログラムをデバッグする方法について検討しました。 RXファミリのFreeRTOSにOTA機能を実装したい場合に参考になると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む