TECH PLAY

SCSKクラウドソリューション

SCSKクラウドソリューション の技術ブログ

1141

こんにちは、SCSKの茂木です。 前回ご紹介したAWSマルチリージョンにおける高可用性方式で記事に書ききれなかったルーティング切替の実装について、 今回は詳しく解説していきます。 AWSマルチリージョンにおける高可用性方式を実装してみる AWS Transit Gatewayを用いたマルチリージョンにおける高可用性方式を実装してみます。 blog.usize-tech.com 2025.01.17   背景と目的 前回の記事でも触れていましたが、マルチリージョンでの冗長化構成においては 障害や災害が発生した際のダウンタイム最小化 が重要なポイントの1つとなります。 ダウンタイムを最小化するためにはHAクラスタソフトでデータやミドルウェアを冗長化しておくことと合わせて、 障害や災害の発生時にクライアント通信がセカンダリサーバに向くようルーティングを切り替える仕組みが必要となります。 実装方針 ダウンタイム最小化のためルーティング切替は自動で行えるようにします。 切替の流れは以下を想定しており、AWS CLIを使用したスクリプトでルーティングの切替を行います。 ①障害または災害が発生しプライマリサーバがダウン ②ダウンを検知し、セカンダリサーバでスクリプトをキック ③スクリプト内でルートテーブルのエントリ差し替えを行い、仮想IPをセカンダリサーバまでルーティング ④セカンダリサーバでサービス継続 ルーティングの切替は稼働しているサービスのフェイルオーバーと合わせて行う必要があります。 非同期での切り替わりによって意図しない挙動になる可能性があります。 環境設定 設定項目 説明 AWS CLIインストール ドキュメントを参考にEC2全台にAWS CLIをインストールします。 AWS CLIの最新バージョンのインストールまたは更新 - AWS Command Line Interface AWS CLI をシステムにインストールまたは更新する手順。 docs.aws.amazon.com IAMポリシーの作成と権限の割り当て ルートテーブルとTransitGateway ルートテーブルに関する許可ポリシーを作成し、EC2に権限を割り当てます。 今回は以下4つのアクションを許可しました。 “ec2:CreateRoute” “ec2:DeleteRoute” “ec2:CreateTransitGatewayRoute” “ec2:DeleteTransitGatewayRoute” AWS CLIの設定(任意) スクリプト実行ユーザに応じてaws configureを適宜設定ください。   ルーティング切替実装 スクリプト概要 今回はbashスクリプトを使用しました。 東京-大阪間の双方で切替が必要になるため実行したEC2のリージョンでルーティングの方向を決めています。 ルートテーブルやリージョンの数に応じて分岐や処理を書き換える必要があります。 以下の記事を参考にしています。 サブネットのルートテーブルを変更する - Amazon Virtual Private Cloud VPC サブネットのルートテーブルの関連付けを理解し、カスタムのルートテーブルを含むリソースの VPC ルーティングを管理します。リソース間のトラフィックを誘導し、安全な接続を有効にします。 docs.aws.amazon.com Amazon VPC Transit Gateway を使用して静的ルートを作成する - Amazon VPC Amazon で VPC、VPN、または Transit Gateway ピアリングアタッチメントの静的ルートを作成しますVPC。 docs.aws.amazon.com スクリプト詳細 #!/bin/bash rm -rf ~/.aws/cli/cache # EC2メタデータサービスからアベイラビリティゾーンを取得する availability_zone=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone) # アベイラビリティゾーンからリージョンを抽出(末尾の文字を取り除く) current_region=${availability_zone::-1} echo "現在のAWSリージョン: $current_region" # リージョンを確認して処理を切り替える if [ "$current_region" == "ap-northeast-3" ]; then     echo "大阪リージョンです。特定の処理を実行します。"   # rtb-oya-public(tokyo)   aws ec2 delete-route \       --route-table-id rtb-0cf7a64e23924df35 \       --destination-cidr-block 20.1.1.1/32 \         --region ap-northeast-1   aws ec2 create-route \       --route-table-id rtb-0cf7a64e23924df35 \       --destination-cidr-block 20.1.1.1/32 \       --gateway-id tgw-0068638c0345e3bf5 \         --region ap-northeast-1   # rtb-oya-private(tokyo)   aws ec2 delete-route \       --route-table-id rtb-0b947251e47673818 \       --destination-cidr-block 20.1.1.1/32 \         --region ap-northeast-1   # tgw-rtb-tokyo-oya(tokyo)   aws ec2 delete-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-033d37d8d27c1a288 \         --region ap-northeast-1   aws ec2 create-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-033d37d8d27c1a288 \       --transit-gateway-attachment-id tgw-attach-08df5bd6f7102ce5d \         --region ap-northeast-1   # tgw-rtb-tokyo-osaka-oya(tokyo)   aws ec2 delete-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-0903b5f96664d99ad \         --region ap-northeast-1   # tgw-rtb-osaka-tokyo-oya(osaka)   aws ec2 create-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-08f3755f4400837a5 \       --transit-gateway-attachment-id tgw-attach-0734a75c43c8dedd9 \         --region ap-northeast-3   # tgw-rtb-osaka-oya(osaka)   aws ec2 create-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-0a005c5db6c404746 \       --transit-gateway-attachment-id tgw-attach-0734a75c43c8dedd9 \         --region ap-northeast-3   # rtb-oya-private(osaka)   aws ec2 create-route \       --route-table-id rtb-05b5fd18535d843ca \       --destination-cidr-block 20.1.1.1/32 \       --network-interface-id eni-082d40701b0d371ec \       --region ap-northeast-3 else   echo "東京リージョンです。通常の処理を実行します。"   # rtb-oya-public(tokyo)       aws ec2 delete-route \           --route-table-id rtb-0cf7a64e23924df35 \           --destination-cidr-block 20.1.1.1/32 \           --region ap-northeast-1   aws ec2 create-route \       --route-table-id rtb-0cf7a64e23924df35 \       --destination-cidr-block 20.1.1.1/32 \       --network-interface-id eni-06102ba74656de21a \         --region ap-northeast-1   # rtb-oya-private(tokyo)   aws ec2 create-route \       --route-table-id rtb-0b947251e47673818 \       --destination-cidr-block 20.1.1.1/32 \       --network-interface-id eni-06102ba74656de21a \         --region ap-northeast-1   # tgw-rtb-tokyo-oya(tokyo)   aws ec2 delete-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-033d37d8d27c1a288 \         --region ap-northeast-1   aws ec2 create-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-033d37d8d27c1a288 \       --transit-gateway-attachment-id tgw-attach-0da655bb73febede7 \         --region ap-northeast-1   # tgw-rtb-tokyo-osaka-oya(tokyo)   aws ec2 create-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-0903b5f96664d99ad \       --transit-gateway-attachment-id tgw-attach-0da655bb73febede7 \         --region ap-northeast-1   # tgw-rtb-osaka-tokyo-oya(osaka)   aws ec2 delete-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-08f3755f4400837a5 \         --region ap-northeast-3   # tgw-rtb-osaka-oya(osaka)   aws ec2 delete-transit-gateway-route \       --destination-cidr-block 20.1.1.1/32 \       --transit-gateway-route-table-id tgw-rtb-0a005c5db6c404746 \         --region ap-northeast-3   # rtb-oya-private(osaka)   aws ec2 delete-route \       --route-table-id rtb-05b5fd18535d843ca \       --destination-cidr-block 20.1.1.1/32 \         --region ap-northeast-3 fi echo "All routes have been created successfully." 補足(LifeKeeperへの組み込み) LifeKeeperのGenericARKを利用することでプライマリサーバのダウン検知をトリガーにスクリプトをキックすることができます。 [Linux] GenericARK開発ガイドとサンプルスクリプト – SIOS LifeKeeper/DataKeeper User Portal
アバター
はじめに 皆さん初めまして。長谷川です。 1本目の投稿になります。 突然ですが、皆さんはAWSのリソース構築をどのように行っていますか? AWS CloudFormationをはじめ様々なIaCツールが活用できるAWSですが、上手く活用できておりますでしょうか? かくいう私も細かな検証作業などではマネコンからポチポチっとやってしまうことが多く、中々活用できていないと感じています。 今回は案件での活用も増えている「AWS CDK」を利用したリソース構築について着目したいと思います。 具体的にはCDKコンストラクトの特徴について焦点を当てて見ていきます。   AWS CDKとは? AWS公式サイトで確認すると以下のような記述になっています。 AWS Cloud Development Kit (AWS CDK) は、コードでクラウドインフラストラクチャを定義して AWS CloudFormation を通じてプロビジョニングするオープンソースのソフトウェア開発フレームワークです。 — AWS CDK とは より引用 AWS CDK とは - AWS Cloud Development Kit (AWS CDK) v2 AWS Cloud Development Kit (AWS CDK) は、コードでクラウドインフラストラクチャを定義して AWS CloudFormation を通じてプロビジョニングするオープンソースのソフトウェア開発フレームワークです... docs.aws.amazon.com TypeScript、JavaScript、Python、Java、C#/.Net、Goといったプログラミング言語でクラウドコンポーネントを定義できることがAWS CDKの大きな特徴ですが、実際に裏で動くのはAWS CloudFormationであることも大事なポイントですね。 リソース作成失敗時の自動ロールバックやリソース設定の不整合を防ぐドリフト検出といったCloudFormationの強みをそのまま活用することができます。   AWS CDK コンストラクト 本記事ではAWS CDKを利用する強みの一つである「抽象化」を支えるAWS CDK コンストラクトについて注目したいと思います。 こちらについてもAWS公式サイトの定義を紹介します。 コンストラクトは、AWS Cloud Development Kit (AWS CDK) アプリケーションの基本的な構成要素です。コンストラクトは、1 つ以上の AWS CloudFormation リソースとその設定を表すアプリケーション内のコンポーネントです。コンストラクトをインポートして設定することで、アプリケーションを 1 つずつ構築していきます。 — AWS CDK コンストラクト より引用 AWS CDKではコンストラクトを基にコードの記述を行うことで、リソースの定義をしていきます。 そして、コンストラクトのレベルとして次の3段階が用意されています。 コンストラクトレベル 説明( AWS CDK コンストラクト より引用) レベル1(L1) L1 コンストラクトは、 CFN リソース とも呼ばれ、最下位のコンストラクトであり、 抽象化は行いません 。各 L1 コンストラクトは、単一の AWS CloudFormation リソースに直接マッピングされます。 レベル2(L2) L1 コンストラクトと比較して、L2 コンストラクトは直感的なインテントベースの API を通してより 高度な抽象化を提供します 。L2 コンストラクトには、適切なデフォルトプロパティ設定、ベストプラクティスのセキュリティポリシー、および多くのボイラープレートコードやグルーロジックを生成する機能が含まれています。 レベル3(L3) L3 コンストラクトは、 パターン とも呼ばれ、最も高度な抽象レベルです。各 L3 コンストラクトには、アプリケーション内の特定のタスクまたはサービスを達成するために連携するように設定された、複数リソースの集合を含めることができます。L3 コンストラクトは、アプリケーション内の特定のユースケースの AWS アーキテクチャ全体を作成するために使用されます。 表内で強調している「抽象化の有無」について気になりました。 AWS CDKというと必要なパラメータのみ設定し、残りはベストプラクティスに基づき「よしなに」やってくれるものと捉えていましたが、そうではないのでしょうか? 実際にレベル1、レベル2コンストラクトのそれぞれを使用してリソース作成(S3,VPC)を試してみました。 $ cdk synth 上記のコマンドを使用することで、実際にリソースをデプロイする前にCloudFormationのテンプレートに落とし込んだ結果を確認することができます。   S3 まずはS3バケットの作成について、両者の結果の違いを示します。 実行環境は以下の通りです。 Node.js 10.9.2 TypeScript 5.7.3 AWS CDK 2.178.0 また、実行環境の構築やコードの記載には「 TypeScript の基礎から始める AWS CDK 開発入門 」を参考にしました。 まだまだTypeScriptを使用した経験が浅いので、ここで紹介されているハンズオンについても一度じっくりと試してみたいと考えています。 L1コンストラクト L1コンストラクトを使用したS3作成のTypeScriptコードは以下の通りです。 class CfnBucket (construct) import { Duration, Stack, StackProps } from 'aws-cdk-lib'; import * as s3 from "aws-cdk-lib/aws-s3"; import { Construct } from 'constructs'; export class WorkStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // s3 を宣言 const s3Bucket = new s3.CfnBucket(this, "s3-L1-Construct", { }); } } cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。 [Properties]の設定等は特になく、真っ新なS3バケットが一つ作成されました。 L2コンストラクト 続いてL2コンストラクトを使用してS3バケットを作成する場合です。 class Bucket (construct) import { Duration, Stack, StackProps } from 'aws-cdk-lib'; import * as s3 from "aws-cdk-lib/aws-s3"; import { Construct } from 'constructs'; export class WorkStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // s3 を宣言 const s3Bucket = new s3.Bucket(this, "s3-L2-Construct", { }); } } cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。 [Properties]の設定等は同じくありませんが、以下の項目が追加されているようです。 UpdateReplacePolicy: Retain DeletionPolicy: Retain これらはスタックの変更・削除時に対象のリソースを保持させるポリシーです。 誤った変更などをしてしまった際の保険になりますね。 しかし、L1とL2の違いとしてはそれほど大きくないですね。 続いてVPCの例を見てみましょう。   VPC L1コンストラクト L1コンストラクトを使用したVPC作成のTypeScriptコードは以下の通りです。 パラメータとしてCIDR情報が必須のため、その設定のみ記載しています。 import { Duration, Stack, StackProps } from 'aws-cdk-lib'; import * as ec2 from "aws-cdk-lib/aws-ec2"; import { Construct } from 'constructs'; export class WorkStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // vpc を宣言 const vpc = new ec2.CfnVPC(this, "VPC-L1-Construct", { cidrBlock: '10.0.0.0/16', }); } } cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。 [Properties]について確認すると、[CidrBlock]の情報のみが保持されていることが分かります。 ただVPCとしての枠だけが用意される形ですね。 結局何か作業をするには、サブネットやゲートウェイなどまだまだ要素が足りていません。 L2コンストラクト 次に、L2コンストラクトです。 なんとパラメータとして必須のものが存在しません! コードとして実行可能な最小限の記載をしています。 import { Duration, Stack, StackProps } from 'aws-cdk-lib'; import * as ec2 from "aws-cdk-lib/aws-ec2"; import { Construct } from 'constructs'; export class WorkStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); // vpc を宣言 const vpc = new ec2.Vpc(this, "VPC-L2-Construct", { }); } } cdk synthコマンドを使用し、CloudFormationテンプレートを出力すると以下の通りです。 かなり長いですので、飛ばしていただいて大丈夫です。 Resources: VPCL2ConstructEE410864: Type: AWS::EC2::VPC Properties: CidrBlock: 10.0.0.0/16 EnableDnsHostnames: true EnableDnsSupport: true InstanceTenancy: default Tags: - Key: Name Value: WorkStack/VPC-L2-Construct Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/Resource VPCL2ConstructPublicSubnet1SubnetCC74A439: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: 10.0.0.0/18 MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet1 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/Subnet VPCL2ConstructPublicSubnet1RouteTable88DC6194: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet1 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/RouteTable VPCL2ConstructPublicSubnet1RouteTableAssociation731F896E: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: VPCL2ConstructPublicSubnet1RouteTable88DC6194 SubnetId: Ref: VPCL2ConstructPublicSubnet1SubnetCC74A439 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/RouteTableAssociation VPCL2ConstructPublicSubnet1DefaultRoute5581F2A1: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: VPCL2ConstructIGW1021B62D RouteTableId: Ref: VPCL2ConstructPublicSubnet1RouteTable88DC6194 DependsOn: - VPCL2ConstructVPCGW6FBE15DE Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/DefaultRoute VPCL2ConstructPublicSubnet1EIP1D9D41B6: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet1 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/EIP VPCL2ConstructPublicSubnet1NATGateway9B0E92D1: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - VPCL2ConstructPublicSubnet1EIP1D9D41B6 - AllocationId SubnetId: Ref: VPCL2ConstructPublicSubnet1SubnetCC74A439 Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet1 DependsOn: - VPCL2ConstructPublicSubnet1DefaultRoute5581F2A1 - VPCL2ConstructPublicSubnet1RouteTableAssociation731F896E Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet1/NATGateway VPCL2ConstructPublicSubnet2Subnet9562D1FC: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" CidrBlock: 10.0.64.0/18 MapPublicIpOnLaunch: true Tags: - Key: aws-cdk:subnet-name Value: Public - Key: aws-cdk:subnet-type Value: Public - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet2 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/Subnet VPCL2ConstructPublicSubnet2RouteTable24B41773: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet2 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/RouteTable VPCL2ConstructPublicSubnet2RouteTableAssociation8AEA9A7C: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: VPCL2ConstructPublicSubnet2RouteTable24B41773 SubnetId: Ref: VPCL2ConstructPublicSubnet2Subnet9562D1FC Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/RouteTableAssociation VPCL2ConstructPublicSubnet2DefaultRouteC8976C2A: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 GatewayId: Ref: VPCL2ConstructIGW1021B62D RouteTableId: Ref: VPCL2ConstructPublicSubnet2RouteTable24B41773 DependsOn: - VPCL2ConstructVPCGW6FBE15DE Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/DefaultRoute VPCL2ConstructPublicSubnet2EIPB8510F82: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet2 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/EIP VPCL2ConstructPublicSubnet2NATGatewayEAF78599: Type: AWS::EC2::NatGateway Properties: AllocationId: Fn::GetAtt: - VPCL2ConstructPublicSubnet2EIPB8510F82 - AllocationId SubnetId: Ref: VPCL2ConstructPublicSubnet2Subnet9562D1FC Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PublicSubnet2 DependsOn: - VPCL2ConstructPublicSubnet2DefaultRouteC8976C2A - VPCL2ConstructPublicSubnet2RouteTableAssociation8AEA9A7C Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PublicSubnet2/NATGateway VPCL2ConstructPrivateSubnet1SubnetE5C7047B: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 0 - Fn::GetAZs: "" CidrBlock: 10.0.128.0/18 MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: Private - Key: aws-cdk:subnet-type Value: Private - Key: Name Value: WorkStack/VPC-L2-Construct/PrivateSubnet1 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/Subnet VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PrivateSubnet1 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/RouteTable VPCL2ConstructPrivateSubnet1RouteTableAssociationAFAD682E: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E SubnetId: Ref: VPCL2ConstructPrivateSubnet1SubnetE5C7047B Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/RouteTableAssociation VPCL2ConstructPrivateSubnet1DefaultRoute592FE75C: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: VPCL2ConstructPublicSubnet1NATGateway9B0E92D1 RouteTableId: Ref: VPCL2ConstructPrivateSubnet1RouteTable9ABEBB2E Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet1/DefaultRoute VPCL2ConstructPrivateSubnet2SubnetD1EAC9D8: Type: AWS::EC2::Subnet Properties: AvailabilityZone: Fn::Select: - 1 - Fn::GetAZs: "" CidrBlock: 10.0.192.0/18 MapPublicIpOnLaunch: false Tags: - Key: aws-cdk:subnet-name Value: Private - Key: aws-cdk:subnet-type Value: Private - Key: Name Value: WorkStack/VPC-L2-Construct/PrivateSubnet2 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/Subnet VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE: Type: AWS::EC2::RouteTable Properties: Tags: - Key: Name Value: WorkStack/VPC-L2-Construct/PrivateSubnet2 VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/RouteTable VPCL2ConstructPrivateSubnet2RouteTableAssociation91E37B6F: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: Ref: VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE SubnetId: Ref: VPCL2ConstructPrivateSubnet2SubnetD1EAC9D8 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/RouteTableAssociation VPCL2ConstructPrivateSubnet2DefaultRoute99DCB01B: Type: AWS::EC2::Route Properties: DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: Ref: VPCL2ConstructPublicSubnet2NATGatewayEAF78599 RouteTableId: Ref: VPCL2ConstructPrivateSubnet2RouteTable0C21B8AE Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/PrivateSubnet2/DefaultRoute VPCL2ConstructIGW1021B62D: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: WorkStack/VPC-L2-Construct Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/IGW VPCL2ConstructVPCGW6FBE15DE: Type: AWS::EC2::VPCGatewayAttachment Properties: InternetGatewayId: Ref: VPCL2ConstructIGW1021B62D VpcId: Ref: VPCL2ConstructEE410864 Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/VPCGW VPCL2ConstructRestrictDefaultSecurityGroupCustomResource76DD52E2: Type: Custom::VpcRestrictDefaultSG Properties: ServiceToken: Fn::GetAtt: - CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E - Arn DefaultSecurityGroupId: Fn::GetAtt: - VPCL2ConstructEE410864 - DefaultSecurityGroup Account: Ref: AWS::AccountId UpdateReplacePolicy: Delete DeletionPolicy: Delete Metadata: aws:cdk:path: WorkStack/VPC-L2-Construct/RestrictDefaultSecurityGroupCustomResource/Default CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - Action: sts:AssumeRole Effect: Allow Principal: Service: lambda.amazonaws.com ManagedPolicyArns: - Fn::Sub: arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole Policies: - PolicyName: Inline PolicyDocument: Version: "2012-10-17" Statement: - Effect: Allow Action: - ec2:AuthorizeSecurityGroupIngress - ec2:AuthorizeSecurityGroupEgress - ec2:RevokeSecurityGroupIngress - ec2:RevokeSecurityGroupEgress Resource: - Fn::Join: - "" - - "arn:" - Ref: AWS::Partition - ":ec2:" - Ref: AWS::Region - ":" - Ref: AWS::AccountId - :security-group/ - Fn::GetAtt: - VPCL2ConstructEE410864 - DefaultSecurityGroup Metadata: aws:cdk:path: WorkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Role CustomVpcRestrictDefaultSGCustomResourceProviderHandlerDC833E5E: Type: AWS::Lambda::Function Properties: Code: S3Bucket: Fn::Sub: cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region} S3Key: 7fa1e366ee8a9ded01fc355f704cff92bfd179574e6f9cfee800a3541df1b200.zip Timeout: 900 MemorySize: 128 Handler: __entrypoint__.handler Role: Fn::GetAtt: - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0 - Arn Runtime: nodejs20.x Description: Lambda function for removing all inbound/outbound rules from the VPC default security group DependsOn: - CustomVpcRestrictDefaultSGCustomResourceProviderRole26592FE0 Metadata: aws:cdk:path: WorkStack/Custom::VpcRestrictDefaultSGCustomResourceProvider/Handler aws:asset:path: asset.7fa1e366ee8a9ded01fc355f704cff92bfd179574e6f9cfee800a3541df1b200 aws:asset:property: Code CDKMetadata: Type: AWS::CDK::Metadata Properties: Analytics: v2:deflate64:H4sIAAAAAAAA/82UTWvCQBCGf4t7lHVbU7DFWwylBEoNWjxUpEx2R11NdsPuJCLify8mfoDQU3vIaWbeHYaH2ZcJRP/5RTx2YOd7Um17mU7FYUogtxx2/htlIA6zQs5Zly14tDSzJOJJmWZaTsvUIM0PDCrQGaQ607T/sgbZkHUZZ1UhY3XOpVZulFm5Pdc5FM2QuBibdyiNXLMhuRI500U1iO7awXu9MnFRDUKlHHo/NpFDIG1N3XHkLaGoQZT6AHoDwh3s2bDeXGsA20Hx65pqizXGOmUTWxJ+QprhTb9pofdW6nrwtfmUvMbJKdym88TpCgj/3bFLyHwL/uJvGO0haQlG48LYEDqDVw81t+9chUQg1zkaOvIJels6ifMFj0pPNr8Kl5t5Ee7eE2crrdCNwCMPvUeaEqy0WR25sQrFxj9UQSD6T6Lf2Xite640pHMUkyb+AI/mB4K7BQAA Metadata: aws:cdk:path: WorkStack/CDKMetadata/Default Condition: CDKMetadataAvailable Conditions: --以下省略-- 作成されたリソースについてまとめると以下の通りです。 VPC(10.0.0.0/16) -IGWの紐付け PublicSubnet1(10.0.0.0/18) -RouteTable,EIP,NAT Gatewayの紐付け PublicSubnet2(10.0.64.0/18) -RouteTable,EIP,NAT Gatewayの紐付け PrivateSubnet1(10.0.128.0/18) -RouteTableの紐付け PrivateSubnet2(10.0.192.0/18) -RouteTableの紐付け デフォルトセキュリティグループ IAMロール -Lambda実行 Lambda Function -デフォルトセキュリティグループからインバウンド・アウトバウンドルールを削除 詳細な設定をしなくても一気にリソースが作成されるのはすごいですね! ネットワークへの接続経路が確保され、Public/Privateサブネットも用意されたため、すぐにサーバ等を立てて作業を開始することが出来そうです。 L2コンストラクトを使用することで「よしなに」色々とリソースがされることが分かりました。 まとめ AWS公式サイトにて「L1コンストラクトは、単一のAWS CloudFormationリソースに直接マッピングされます。」と記載があるように、L1コンストラクトはあくまで設定されたパラメータに忠実にリソースを作成することが分かりました。 また、L2コンストラクトを使用することで、あまりAWSに詳しくなくても簡単に必要なリソースが作成されるのは面白い点だと思いました。 しかし、今回のVPCの場合のように、「そこまで作らなくても...」と感じることもあるかもしれません。 「よしなに」やってくれるのは大変便利ですが、最低限必要なパラメータを設定するスキルは必要ですね。 ただ、やはりリソースの作成負担の軽減や変更のしやすさは大きなメリットになるかと思いますので、上手く使っていきたいですね。 最後まで読んでいただきありがとうございます。 もし参考になりましたら幸いです。
アバター
前回投稿した「OSパッチ適用を自動化」させた後、結果をメールに通知させる方法をまとめてみました。 OSパッチ適用を自動化させる方法はこちらをご参照ください。 AWS Systems Manager の Patch Manager で OS パッチ適用を自動化してみた AWS Systems Manager でパッチ適用を自動化する方法をまとめてみました。 blog.usize-tech.com 2025.01.22   アーキテクチャ 今回は赤枠内を実装してみました。 Amazon CloudWatch Logs に出力されたログから、指定した任意の文字列をトリガーに Amazon SNS 経由で適用結果をメールで通知する関数を作成します。   AWS Lambdaとは、 サーバーをプロビジョニングまたは管理することなくコードを実行することができるサービスです。 https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/welcome.html   Amazon SNSとは、 Amazon Data Firehose、Amazon SQS、AWS Lambda、HTTP、電子メール、モバイルプッシュ通知、モバイルテキストメッセージ (SMS) などのサポートされているエンドポイントタイプを使用して、クライアントが公開されたメッセージを受信することができるサービスです。 https://docs.aws.amazon.com/sns/latest/dg/welcome.html   設定手順 前回設定したOS「Windows2022」のEC2にパッチ適用した結果を、メール通知させる手順をまとめました。 1.トピックの作成 2.サブスクリプションの作成 3.Lambda関数の作成 4.Lambda サブスクリプションフィルターの作成   1.トピックの作成 Amazon SNSの左メニュー「トピック」をクリックし、「トピックの作成」をクリックします。   ▼ 詳細 「タイプ」欄は、「スタンダード」を選択します。 「名前」欄は、任意のトピック名を入力します。 「表示名」欄は、任意の表示名を入力します(表示名が実際のメール差出人の名前になります)。   以下の欄はデフォルトのままにし、「トピックの作成」をクリックします。 ▼暗号化 – オプション ▼アクセスポリシー – オプション ▼データ保護ポリシー – オプション ▼配信ポリシー (HTTP/S) – オプション ▼配信ステータスのログ記録 – オプション ▼タグ – オプション ▼アクティブトレース – オプション   2.サブスクリプションの作成 作成したトピックから、「サブスクリプションの作成」をクリックします。   ▼ 詳細 「プロトコル」欄は、「Eメール」を選択します。 「エンドポイント」欄は、通知先のメールアドレスを入力します。 以下の欄はデフォルトのままにし、「サブスクリプションの作成」をクリックします。 ▼サブスクリプションフィルターポリシー – オプション ▼Redrive ポリシー (デッドレターキュー) – オプション   しばらくすると、指定したアドレス宛に確認のメールが届くので、「Confirm Subscription」をクリックし、「Subscription confirmed!」が表示されることを確認する。 サブスクリプションのステータスが「確認済み」になっていることを確認する。   3.Lambda関数の作成 Lambdaの左メニュー「関数」をクリックし、「関数を作成」をクリックします。   ▼ 関数の作成 「一から作成」を選択します。   ▼ 基本的な情報 「関数」欄は、任意の関数名を入力します。 「ランタイム」欄は、Lambda関数で記述するプログラミング言語を選択します。 今回はPythonを選択しました。 ▼ アクセス権限 「実行ロール」欄は、適切なアクセス権が付与された任意のロールを選択します。   以下の欄はデフォルトのままにし、「関数の作成」をクリックします。 ▼ その他の構成   ▼ コード コードソースを以下のコードに修正し、「Deploy」をクリックする。 ※Deployをクリックしないと、変更が反映されないので注意してください。   import logging import json import base64 import gzip import boto3 import os logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context):   # CloudWatchLogsからのデータはbase64エンコードされているのでデコード   decoded_data = base64.b64decode(event['awslogs']['data'])   # バイナリに圧縮されているため展開   json_data = json.loads(gzip.decompress(decoded_data))     logger.info("EVENT: " + json.dumps(json_data))     # ログデータ取得   log = json_data['logEvents'][0]['message']   instanceid_position = log.find('PatchGroup')   instanceidtmp = log[:instanceid_position]   instanceid = instanceidtmp.replace('Patch Summary for ','')   print(instanceid)   result_position = log.find('Results')   result = log[result_position:]   print(result)   patchGroup_start = log.find('PatchGroup')   patchGroup_end = log.find('BaselineId')   patchGrouptmp = log[patchGroup_start:patchGroup_end]   patchGroup = patchGrouptmp.replace('PatchGroup          : ','')   print(patchGroup)   messagetmp = """ パッチ適用結果を通知します。 対象インスタンス・適用結果は下記となります。 〇対象インスタンス{0} 〇パッチグループ {1} 〇適用結果 {2}   """   message = messagetmp.format(instanceid,patchGroup,result)   print(message)     try:       sns = boto3.client('sns')       #SNS Publish       publishResponse = sns.publish(       TopicArn = os.environ['SNS_TOPIC_ARN'],       Message = message,       Subject = os.environ['ALARM_SUBJECT']       )     except Exception as e:       print(e)   ▼ 環境変数 「設定」タブ>「環境変数」にて、「編集」をクリックする。   「環境変数」をクリックし、以下のキーと値を入力し、「保存」をクリックする。 キー 値 ALARM_SUBJECT パッチ適用通知 SNS_TOPIC_ARN 作成したSNSトピックのARN名   4.Lambda サブスクリプションフィルターの作成 CloudWatchの左メニュー「ロググループ」をクリックし、SSMでログの出力先に設定したロググループを検索します。   「サブスクリプションフィルター」タブをクリックし、「作成」>「Lambdaサブスクリプションフィルターを作成」をクリックします。   ▼ 送信先を選択 「Lambda関数」欄は、「3.Lambda関数の作成」で作成した関数名を選択します。   ▼ ログ形式とフィルターを設定 「ログの形式」欄は、「JSON」を選択します。 「サブスクリプションフィルターのパターン」欄は、フィルターする任意の文字列を入力します。 「サブスクリプションフィルター名」欄は、任意のフィルター名を入力します。 すべての項目を設定後、「ストリーミングを開始」をクリックします。   メール通知確認 パッチ適用完了後に以下のようなメール届くことを確認します。   まとめ Lambda↔SNS間の通知機能を実装することで、パッチ適用結果の通知にかかわらずあらゆるサービスの状態をメールで受け取ることができるようになります。 皆さんのお役に立てば幸いです。
アバター
お疲れ様です。 今回で2本目の投稿となります。今年に入ってから、クラウド技術への関心を深めています。昨年度までは主にオンプレミスシステムを担当しており、クラウドに触れる機会はほとんどありませんでした。最近はクラウド技術に触れることが増えてきましたが知識不足を痛感し、取り敢えず体系的な知識を身に付けるために、AWSの資格をいくつか取得しました。 資格取得を通じて少しずつ設計の感覚がつかめてきた感じがしているので、今回はクラウド初心者の視点から考えた基本設計の目次を紹介したいと思います。「そんな考え方もあるのか」と気軽にご覧いただけるとうれしいです。 クラウド初学者が考える基本設計の目次 1. **システム全体構成図** 2. **利用サービス一覧** 3. **可用性/信頼性** – 3.1 SLA – 3.1.2 AWSサービス別SLA – 3.2 サービス提供時間 – 3.3 メンテナンススケジュール、計画停止 – 3.4 冗長構成パターン – 3.4.1 EC2インスタンス – 3.4.2 DBインスタンス – 3.4.3 ネットワーク – 3.4.4 ストレージリソース – 3.4.5 その他リソース – 3.5 ロードバランサー設計 4. **性能/拡張性** – 4.1 サイジング方式 – 4.2 性能、制限事項 – 4.2.1 EC2インスタンス – 4.2.2 DBインスタンス – 4.2.3 ネットワーク – 4.2.4 ストレージ系リソース – 4.2.5 その他リソース – 4.3 拡張方式 – 4.3.1 EC2インスタンス – 4.3.2 DBインスタンス – 4.3.3 ネットワーク – 4.3.4 ストレージ系リソース – 4.3.5 その他リソース 5. **ネットワーク/外部接続設計** – 5.1 ネットワーク構成図 – 5.2 VPC/サブネット分割設計 – 5.3 IPアドレス設計 – 5.4 インスタンスへの接続方式 – 5.5 インターネット接続方式 – 5.6 VPC外へのサービス接続方式 – 5.7 VPC間接続方式 – 5.8 オンプレミス環境への接続方式 – 5.9 DNS設計 – 5.10 ドメイン設計 – 5.11 NTP設計 – 5.12 セキュリティグループとファイアウォール設定 – 5.13 メール設計 – 5.14 通信要件一覧 6. **構成管理** – 6.1 各種イベントリ一覧 – 6.2 Config Rules設計 – 6.3 ドキュメント管理 – 6.4 コード管理 – 6.5 ソフトウェア一覧 – 6.6 ライセンス管理 – 6.7 保守管理 7. **バックアップ/リストア** – 7.1 システム毎のRTO、RPO、RLOの定義 – 7.2 バックアップ対象 – 7.3 バックアップ方式 – 7.3.1 システムバックアップ – 7.3.2 ボリュームバックアップ – 7.3.3 ファイルバックアップ – 7.3.4 DBバックアップ – 7.4 リストア方式 – 7.4.1 システムリストア – 7.4.2 ボリュームリストア – 7.4.3 ファイルリストア – 7.4.4 DBリストア 8. **セキュリティ** – 8.1 セキュリティガイドライン(前提条件) – 8.2 トラフィック制御 – 8.3 IPS/IDS設計 – 8.4 暗号化/通信データ管理 – 8.5 不正検知(Securityhub設計) – 8.6 証跡管理 – 8.7 認証管理 – 8.8 定期監査 – 8.9 パスワードポリシー – 8.10 証明書管理 – 8.11 キーペア管理 9. **監視設計** – 9.1 基本方針 – 9.2 監視対象 – 9.3 監視項目 – 9.4 ログ管理 – 9.5 ログ分析 – 9.6 監視フロー 10. **Active Directory設計** 11. **災害対策設計** – 11.1 フェールオーバー設計 – 11.2 フェールバック設計 – 11.3 災対切替訓練 12. **ジョブ設計** – 12.1 ジョブ実行方式 – 12.2 ジョブ一覧 – 12.3 ジョブ実行スケジュール 13. **管理機能設計** – 13.1 AWSアカウント設計 – 13.2 Organizations設計 – 13.3 SCP設計 – 13.4 IAM設計 – 13.5 OSユーザー設計 – 13.6 マネージメントコンソールアクセス要件 – 13.7 CLI利用条件 – 13.8 ControlTower設計 – 13.9 コスト管理 – 13.10 割引管理 14. **自動化設計** – 14.1 IaC設計 – 14.2 スクリプト設計 – 14.3 CI/CD設計 15. **命名規則** 16. **用語集**   章の順番を入れ替えたりするともっと良くなるかなと思いましたが、以上にしたいと思います。
アバター
SCSKの畑です。 今更感はあるのですが、今回はこれまでの投稿で取り上げてきた Web アプリケーションのアーキテクチャについて、改めて概要や各サービスの役割などを記載していこうと思います。正直、最初の方の投稿で先に説明しておくべきだったかもしれません・・ アーキテクチャ図 いつものやつです。図内に注釈もあるので、各サービスの役割は何となくご理解いただけるものかと思います。   アプリケーションの目的・用途について 初回のエントリ含め何回か記載していますが、改めて記載するとデータベース/ DWH 上のテーブルデータをメンテナンスすることが目的のアプリケーションとなります。本事例の場合のメンテナンス対象は Redshift となります。また、実はここで初めて言及するのですが、データメンテナンスの要件として、以下2点のような機能も実装しています。 アプリケーションのメンテナンス対象テーブルの(データ)バージョン管理機能 過去バージョンデータの参照や、バージョン間のデータ比較が可能 アプリケーション経由でテーブルデータを更新する際の簡易ワークフロー機能 更新を承認する権限を持った別ユーザによる承認を以って Redshift 側に更新が反映される また、アーキテクチャ概要図には載せていませんが、本事例においては DWH (Redshift) 上にトランザクションデータを投入・更新するための ETL/ELT ジョブネットをアプリケーション経由で実行する機能も実装しました。同ジョブネットは StepFunctions 及び Lambda で実装しており、 StepFunctions のステートマシンをアプリケーションから実行するような形式となります。 他、アプリケーション自体の運用管理機能として、メンテナンス対象テーブルの情報や実行対象 ETL/ELT ジョブネットの情報、Cognito のユーザ/グループなどをメンテナンスできる機能も用意しました。このあたりは当初のスコープにはなかったのですが、お客さんの要望を聞きつつ必要になりそうな機能をピックアップした形です。   アプリケーションのビルド・デプロイについて 大した内容ではありませんが、先にこちらを簡単に説明しておきます。 ビルドについては、Nuxt3 の場合サーバエンジン (nitro) の設定に Lambda デプロイ用のプリセットがあるため、それを指定してビルドするだけで OK です。このお手軽さは非常に魅力的でした。 デプロイについては、お客さん環境の都合上手動で行う必要がある関係で、CloudFront 配下の Lambda と S3 にそれぞれ AWS マネジメントコンソールから粛々とデプロイしています。ただ、 8回目のエントリ で説明した AppSync の手動移植と比較すると大分ラクなのが幸いでした。ただ、Web上には Serverless Framework を使用した手順が多く、手動でのデプロイ方法を調べるのに少し手間取りました。具体的にはほぼ以下サイト様の通りですが、今回の事例において少し異なる点もありましたので以下に記載しています。 Serverless Frameworkを使わず、Nuxt3でSSR構成(CloudFront + Lambda + S3)をする方法 - Qiita 対象者自分と同じAWS初心者「俺たちは感覚でAWSを触っている」状態の人バックエンドなんもわからんけどAWSへのデプロイを控えている人Next全盛の今NuxtのSSRで頑張っている人なぜや… qiita.com .output/public/ 配下のファイル一式を S3 にアップロード ビルドの度に静的コンテンツのファイル名がハッシュ値に応じたものに変わるため、デプロイ前に S3 上のファイルを削除しておく方がベターです。ファイル名は固定するような設定に変更することもできるようですが。 .output/server/ 配下のファイル一式を Lambda にアップロード アップロード操作を踏まえると一式を zip ファイルにしてしまうのが良いと思います。 Lambda のハンドラ設定については、上記のようにアップロードする前提においては index.handler のままで良いようです。当初は .output/index.mjs を実行する index.js を別途作っていたのですが、不要でした。   フロントエンド側のサービス フロントエンド側のサービスとしては、以下3つです。 Amazon CloudFront Amazon S3 AWS Lambda アーキテクチャ図に記載の通り、CloudFront 配下に S3 及び Lambda を配して、Nuxt.js (Nuxt3) による SSR (Server-Side Rendering) 構成で Web アプリケーションを動作させています。厳密には Nuxt3 における Universal Rendering を使用しているので CSR (Client-Side Rendering) との併用となっています。 ということで、各サービスの位置づけ・役割については概ね想像できるところだと思うのですが、少しだけ詳細な話も含めつつ以下に記載していきます。   Amazon CloudFront アーキテクチャ概要図の通り、クライアント端末からのアクセスを受けてアプリケーションを配信しています。サーバサイド生成(動的)コンテンツは Lambda 経由、静的コンテンツは S3 経由です。サーバレスアーキテクチャとしては標準的な構成と思いますし、CloudFront 自体の設定も特にトリッキーなところはなかったため、今回は取り上げません。 ただ、お客さんの AWS 環境で構築するにあたりセキュリティ関連の設定について幾つか考慮する必要があったため、以下にまとめました。 なお、原理的には CloudFront ではなく API Gateway でも代替できるものと思われます。当初はアプリケーションの要件的に CloudFront を持ち出すのは少々大袈裟かなと感じたこともあって API Gateway を試していたのですが、URL に API Gateway の Stage が含まれてしまうせいか上手く動かず。Nuxt 側の設定を色々と変更してみたのですがダメでした。 また、S3 を 使用せず Lambda 単体で動作させるように Nuxt の設定を変更することは可能で、Web 上にその事例も多く出てきたのですが、将来的なことを考えると S3 を使用するべきに構成しておくべきと判断し、CloudFront を使用することにしました。   WAF 設定 お客さんの環境ポリシーにより Web WAF を設定しています。 設定自体はお客さんの所管であったため詳細は割愛しますが、本アプリケーションはインターネット上に公開せず、アクセスできるネットワーク/IP アドレスのレンジを限定する必要があったため、その点については考慮する必要がありました。最も、概要図の通り CloudFront にアクセスする対象はクライアント端末のみであり、それらが属しているネットワーク内からのみアクセス可能とすれば OK だったので特に難しい要件ではなく、お客さん側のネットワーク構成も相まって特に問題なく設定できました。 なお、後編で取り上げるバックエンド側のサービスについて、こちらと同じように考えれば良いやと安易に構えていたところ、痛い目に遭いかけました。詳細については別エントリで改めて説明しようと思います。   Origin access control 設定 CloudFront の配下に配する S3 及び Lambda について、CloudFront からのアクセスのみを許可するような設定が必要でした。幸い、こちらも Web 上の情報や知見は豊富で、上記のようにいずれも Origin access control 設定により実現できました。 特に、Lambda については構成上 CloudFront からのアクセスに関数 URL 機能を使用する必要がありますが、同機能には WAF などの外部アクセス制限機能を適用できないことから、当初はお客さんの環境ポリシーに準拠せず NG が出ていました。幸い、2024/4 に Lambda の関数 URL も Origin access control をサポートするようになっていたため、その旨をお客さんに伝えてクローズと相成りました。 Amazon CloudFront が Lambda 関数 URL オリジンのオリジンアクセスコントロール (OAC) を新たにサポート aws.amazon.com   Amazon S3 CloudFront の項目で説明した通り、アプリケーションから使用する静的コンテンツを配置しています。 構成上必要となる設定も前述の Origin access control くらいで、正直特に言及することはありません。せいぜい、デプロイ時に AWS マネジメントコンソール経由だとネットワークの具合次第でアップロードに失敗することがあり、その場合のオペレーションが面倒だったくらいですが、まあこれって環境依存の話なので正直関係ないですね・・。   AWS Lambda CloudFront の項目で説明した通り、サーバサイドで動的なコンテンツを返すために使用しています。具体的には、ビルドしたアプリケーションのコード一式を Lambda にデプロイするような形となります。デプロイ手順としては先述した通り難しくはないのですが、S3 と比較すると幾つか躓いた点がありましたので備忘も含めて以下にまとめました。 先述した内容の繰り返しとなりますが、ハンドラ設定は index.handler で OK でした。 Lambda 実行ロールについて、当初はサーバサイド側で実行され得る機能に対応した権限(Cognito なり AppSync 関連)が必要になると考えていたのですが、いずれも不要でした。 サーバサイドで認可が必要な処理が行われるとしても、それを実行するための権限は 2回目のエントリ で書いたように Cognito 経由で付与されるロール側で付与されるためと理解しています。 これは余談寄りですが、アプリケーションで使用している pdf ファイル表示用のライブラリで不具合があった関係で、Node.js を 22 系に上げたかったのですが、Lambda におけるリリースタイミングが 2024/11 と思ったより遅かったため、不具合の解消にやや時間を要しました。(Node.js の 22 系リリースは 2024/4) https://aws.amazon.com/jp/about-aws/whats-new/2024/11/aws-lambda-support-nodejs-22/ このへんのスピード感(早い or 遅い)が肌感覚として良くわからないですね。リリースから半年と考えると標準的な気もしますし、Node.js の 22 系が安定版と考えるとちょっと遅いような気もしますし・・   余談など Next.js(React) ではなく Nuxt.js(Vue) を使用した理由をたまに問われるのですが、最終的には自分の好みです。特に、Vue ファイル内で HTML テンプレートライクに書けるのが、昔から HTML タグ打ちしていたような人間にとっては馴染みやすかったというのが大きかったです。それから調べた限りだと、今回のような少人数開発のアプリケーションにおいてはある意味どちらを選んでも大差ないと感じたのも理由の一つです。なので最終的に自分の好みを優先させてしまっても良さそうだな、と・・ また、今振り返ると、SSR 構成にする必要性は機能要件上はなかったかもしれません。当初はサーバサイドで重い処理をさせてクライアント側に返すようなことがアーキテクチャ上できるようにしておいた方が良いと考えていましたが、アプリケーションの特性上クライアント側で処理する内容が多い上、重い処理も結局のところは AppSync 経由で実行されるバックエンド処理用の Lambda が担うことがほとんどであったためです。とは言え、アクセス時のレスポンス向上などの効果はあったと思いますし、この構成にしたことによるアプリケーション実装上のデメリットが本アプリケーションについては正直思っていたほど出なかったこともあり、悪い選択でもなかったとは思っていますが。 ただ、CloudFront など一部サービスの構築は所管上お客さんにお願いすることになったこともあり、事前準備やリードタイムに一定の工数を要したのも事実ではありました・・   まとめ 今振り返ると、当時は自分なりに色々調べた上での判断とはいえ、ちょっと考慮が足りていなかったかもという点があるというのが正直なところです。特に SSR 構成を前提としてアーキテクチャを検討していた点については、もう少し深堀りして調査できていればなお良かったかもしれません。このあたりは次回以降の案件などで活かしていければと思っています。 そして、、思ったより長くなってしまったので、バックエンド側の話は後編に回したいと思います。 本記事がどなたかの役に立てば幸いです。
アバター
こんにちは。SCSK 池田です。 まだ2月だというのに、少し気の早い春を感じるようになってきた今日この頃ですが皆様いかがお過ごしでしょうか。 早速本日のお題です。 少し時間が経ってしまいましたが、昨年9月にリリースされたLifeKeeper for Linux v9.9.0で新機能として提供を開始した「Disaster Recovery add-on」について、本機能が求められるようになった経緯を私なりに考察してみました。 Disaster Recovery add-onの振り返り Disaster Recovery add-onについては、データ同期処理にLinbit社のDRBDというソフトウェアを利用したLifeKeeperの災害対策ソリューションの名前です。こちらのSCSK技術ブログであるTech Harmonyの中でも何回か記事にさせていただきましたので、詳細のご説明はそちらに譲ります。 第10回 DRBD × LifeKeeperの高可用性リアルタイムレプリケーションを探る – TechHarmony AWSマルチリージョンにおける高可用性方式を実装してみる – TechHarmony 災害対策ということですので、地理的に離れた箇所にサーバを配置する必要があり、AWSの場合は、リージョンを跨いだレプリケーションを実現する必要があります。 データレプリケーションしたいならDataKeeperでは!? LifeKeeperを扱ったことのある人の中にはご存じのかたもいらっしゃるかと思いますが、サーバのローカルディスク間をデータレプリケーションするソリューションとして「DataKeeper」という製品があります。 オンプレミス環境においては、比較的高価な共有ディスクを買わずとも、サーバに内蔵されたディスクを安価にレプリケーションすることができる為、とても重宝されてきました。 AWSやAzureといったパブリッククラウド環境においては、そもそも共有ディスクを構成できなかったり、できたとしても様々な制約があり構成することが難しいケースの方が大半なことから、EC2インスタンスに紐づけたEBSボリュームをDataKeeperを用いてレプリケーションする構成が主流となっています。 それならば今回のようなAWSのリージョンを跨いだ災害対策目的の構成でも、DataKeeperが使えるのではと思った方も多いと思います。 結論からお伝えすると、 DataKeeperは、リージョンを跨いだデータレプリケーションには不向き なんです! その理由をご説明していきましょう。 DataKeeperがリージョン跨ぎ構成に向かない理由 非同期レプリケーションが使えない リージョン跨ぎの場合、どんなに低レイテンシの回線だと言われても、どうしても回線速度がネックになってしまいます。同期レプリケーション(リアルタイムレプリケーション)とした場合、DRサイト側のディスクに書き込みが完了しないと、稼働系で動作しているアプリケーションに対してI/O完了となりません。その為、回線速度の影響をまともに受けてしまい、レスポンスの低下に繋がってしまいます。 この問題は、非同期レプリケーションとすることで解決できるように感じますが、実は、Linuxのカーネルの問題により、レプリケーションタイプを非同期モード(Async mirror)に設定している場合、並列的に送られたデータの書き込み順序がまれに前後する可能性が不具合として報告されており、ファイルシステムの整合性に問題が生じる可能性があります。 簡単にイメージ化すると以下のようになります。 ■本来あって欲しい処理 ■カーネルの不具合により、非同期モードかつパラレルで同期した場合 ( [Linux]データレプリケーション構成においてスタンバイ側のファイルシステム(データ)に不整合が発生することがある – SIOS LifeKeeper/DataKeeper User Portal ) このカーネルの問題に対応する為に、並列処理が行われないようにLifeKeeper for Linux8.1.2以降では、nbd(正確にはnbd-server)のスレッド数を1ケにするよう改修が行われていますが、並列処理が行われなくなったことで、データレプリケーション処理速度を犠牲にせざるをえませんでした。 このことが、DataKeeperがリージョン跨ぎ構成に向かない理由となります。 DRBDを用いたDisaster Recovery add-on機能をサポート そこで、サイオステクノロジー社が目を付けたのが、LINBIT社製のDRBDでした。DRBDはWAN経由で遠隔地へ安定したデータレプリケーションを実現することができるソフトウェアとして、DataKeeperの代わりになると判断し、検証の末、2024年9月25日にリリースされたLifeKeeper for Linux xv9.9.0でサポートされることとなりました。   DataKeeperとDisaster Recovery add-onの棲み分けは? それでは、LifeKeeperにおいて、データレプリケーション構成を実現させるためにはどのような基準でDataKeeperとDisaster Recovery add-onを選択すれば良いでしょうか? 現在、サイオステクノロジー社でサポートしているDisaster Recovery add-on構成は、リージョン跨ぎの構成のみとなっています。その為、必然的に単一リージョン内の場合はDataKeeper、リージョン跨ぎの場合はDisaster Recovery add-onを選択するという棲み分けで覚えていただければと思います。   データレプリケーションソフト 備考 単一リージョン(マルチAZ) DataKeeper   リージョン跨ぎ Disaster Recovery add-on 3ノード構成   ※Disaster Recovery add-onの場合は、現時点で3ノード構成(単一リージョン内別AZに2ノード、別リージョンに1ノード)のみがメーカーからサポートされる構成となります。3ノード間のデータレプリケーションは全てDRBDを用いますのでご注意ください。 まとめ 今回は、『災害対策(DR)に対応した「Disaster Recovery add-on」の提供を開始した理由』と、同様のデータレプリケーションソフトであるDataKeeperとの違いや構成選択のポイントについて解説しました。 ・データレプリケーションには、DataKeeperとDisaster Recovery add-onの2種類がある ・リージョン跨ぎの構成では、Linuxのカーネルの問題があり、DataKeeperは不向きである ・単一リージョン内のAZ間レプリケーションではDataKeeperを、リージョン跨ぎの場合はDisaster Recovery add-onを選択する
アバター
SCSK 岩井です。 今回はRaspberry Piを2台使ってWebサーバの冗長化を行ってみたいと思います。 ただWebサーバを冗長化するだけではなく、WebサイトとLED点灯/消灯を連動させてみたいと思います。 ちょっとだけそれっぽくなる予定です。   下準備 使用するRaspberry Piは以下のものです。 【Raspberry Pi 5】 CPU: Broadcom BCM2712 quad-core Arm Cortex A76 processor @ 2.4GHz Memory: 8GB OS: Bookworm 【Raspberry Pi 3 Model B+】 CPU: Broadcom BCM2837B0, Cortex-A53 (ARMv8) 64-bit SoC @ 1.4GHz Memory: 1GB OS: Bookworm   この2台にflask(簡易的なWebアプリフレームワーク)をインストールしてWeb経由操作でLED点灯/消灯する構成を組んでみます。 【追加で用意するもの】 LED(赤)×1 LED(黄)×1 抵抗(330Ω)×2 ジャンパーケーブル×4 ブレッドボード×1 Webサーバアクセス用PC   ライブラリのインストール ラズパイに必要なライブラリ(Flask、GPIO(Rasberry Pi 3)、gpiozero(Rasberry Pi 5))をインストールします。 Raspberry Pi 5はGPIOが使えなくなってしまったので代わりにgpiozeroをインストールします。  # Raspberry Pi 5の場合  sudo apt update  sudo apt install python3-flask  sudo apt install gpiozero  # Raspberry Pi 3の場合  sudo apt update  sudo apt install python3-flask  sudo apt install python3-rpi.gpio    Pythonスクリプトの作成 PythonでFlaskアプリケーションを作成します。 ファイル名はapp.pyにしてみました。 Web画面上にLED点灯用とLED消灯用のリンクを張って、LEDを着けたり消したりできるようにします。 まずはviを起動します。  # Raspberry Pi 3/Raspberry Pi 5共通  sudo vi app.py 以下のコードを記述してファイルを保存します。Raspberry Pi 5はgpiozeroライブラリ、Raspberry Pi 3は GPIOライブラリなのでコードが異なります。同じ画面を作成していますが、Raspberry Pi 5はWebアクセスされると「Unit 1」、 Raspberry Pi 3はWebアクセスされると「Unit 2」と表示されるようにしています。  # Raspberry Pi 5の場合  from flask import Flask  from gpiozero import LED        app = Flask(__name__)    # GPIO 17を制御するLEDオブジェクトの作成    led = LED(17)    @app.route('/')  def index():  return '''    <h1>LED Control(Unit 1)</h1>  <p><a href="/led/on">Turn LED ON</a></p>  <p><a href="/led/off">Turn LED OFF</a></p>  '''    @app.route('/led/on')  def led_on():  led.on() # LEDを点灯  return '<h1>LED is ON</h1><p><a href="/">Back to home</a></p>'    @app.route('/led/off')  def led_off():  led.off() # LEDを消灯  return '<h1>LED is OFF</h1><p><a href="/">Back to home</a></p>'    if __name__ == '__main__':  app.run(host='0.0.0.0', port=5000)  # Raspberry Pi 3の場合  from flask import Flask  import RPi.GPIO as GPIO    app = Flask(__name__)    # GPIOピンの設定  LED_PIN = 17  GPIO.setmode(GPIO.BCM)  GPIO.setup(LED_PIN, GPIO.OUT)    @app.route('/')  def index():  return '''  <h1>LED Control(Unit 2)</h1>  <p><a href="/led/on">Turn LED ON</a></p>  <p><a href="/led/off">Turn LED OFF</a></p>  '''     @app.route('/led/on')  def led_on():  GPIO.output(LED_PIN, GPIO.HIGH)  return '<h1>LED is ON</h1><p><a href="/">Back to Home</a></p>'    @app.route('/led/off')  def led_off():  GPIO.output(LED_PIN, GPIO.LOW)  return '<h1>LED is OFF</h1><p><a href="/">Back to Home</a></p>'    if __name__ == '__main__':  try:  app.run(host='0.0.0.0', port=5000)  finally:  GPIO.cleanup()   LEDとRaspberry Piを接続する ブレッドボードにLEDと抵抗を接続します。 Raspberry Pi 5用とRaspberry Pi 3用に2セット回路を作ります。 イメージはこんなカンジで。(左側がRaspberry Pi 3用、右側がRaspberry Pi 5用) 次に回路とRaspberry Pi 5、Raspberry Pi 3を接続します。 なお、コードの中にあったGPIO 17は17ピンではなく、11ピンのGPIO17 です。 17ピンに刺すと3.3vの電圧がかかりっぱなしになるので、 ただLEDが光ってるだけの状態になります。(Webアプリと連動しません) 10分悩みました。 11ピンのGPIO17に+側(LED側のケーブル)、14ピンのGroundに-側(抵抗側のケーブル)を接続します。 【Raspberry Pi 5のピン配置】 Raspberry Pi 5 Pinouts including GPIO for the 40 Pin Header – element14 Community 【Raspberry Pi 3のピン配置】 Raspberry Pi 3 Model B GPIO 40 Pin Block Pinout – element14 Community   Webサーバの起動(Pythonスクリプトの実行) 各Raspberry Piのコンソールで、Webサーバを起動します。 まずはIPアドレス確認から。  # Raspberry Pi 5/Raspberry Pi 3共通  ip addr 次に各Raspberry PiでWebサーバを起動します。  # Raspberry Pi 5/Raspberry Pi 3共通  python3 app.py   WebブラウザからアクセスとLED操作 Webサイトアクセス用PCでブラウザを2つ起動し、それぞれ以下のURLにアクセスします。 http://Raspberry Pi 5のIPアドレス:5000 http://Raspberry Pi 3のIPアドレス:5000 次にブラウザ上の「Turn LED ON」のリンクをクリックします。       LED点灯。やりました。 「Back to Home」のリンクをクリックして前の画面に戻ります。 今度は「Turn LED OFF」のリンクをクリックします。 LED消灯。(写真撮り忘れました)   nginxのインストールとプロキシ設定 nginxを使用してFlaskアプリへのリクエストをプロキシできるようにするため、各Raspberry Piにnginxをインストールします。  # Raspberry Pi 5/Raspberry Pi 3共通  sudo apt install nginx プロキシを有効化するため、nginxの設定を変更します。  # Raspberry Pi 5/Raspberry Pi 3共通  sudo vi /etc/nginx/sites-available/default 設定内容は以下のとおり。  # Raspberry Pi 5/Raspberry Pi 3共通  server {   listen 80;   server_name localhost;     location / {   proxy_pass http://127.0.0.1:5000;   proxy_set_header Host $host;   proxy_set_header X-Real-IP $remote_addr;   proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;   proxy_set_header X-Forwarded-Proto $scheme;   }  }   Keepalivedのインストールと設定 keepalivedインストールしてWebサイト(Flaskアプリ)の冗長化構成を組んでみます。 各Raspberry Piにkeepalivedをインストールします。  # Raspberry Pi 5/Raspberry Pi 3共通  sudo apt update  sudo apt install keepalived 次に各Raspberry Piでkeepalivedの設定ファイルを編集します。  # Raspberry Pi 5/Raspberry Pi 3共通  sudo vi /etc/keepalived/keepalived.conf Raspberry Pi 5をMaster、Raspberry Pi 3をBackupとします。 仮想IPアドレス(virtual_ipaddress)は192.168.11.110、Master側を優先させるため、priorityを100とします。 なお、interfaceは仮想IPアドレスを持たせるネットワークインターフェイスを指定します。  # Raspberry Pi 5(Master)  vrrp_instance VI_1 {   state MASTER   interface eth0   virtual_router_id 51 priority 100   advert_int 1   authentication {   auth_type PASS   auth_pass mypass   }   virtual_ipaddress {   192.168.11.110   }  } Backup側の仮想IPアドレス(virtual_ipaddress)はMaster側と同様の192.168.11.110、priorityを90とします。  # Raspberry Pi 3(Backup)  vrrp_instance VI_1 {   state BACKUP   interface eth0   virtual_router_id 51   priority 90   advert_int 1   authentication {   auth_type PASS   auth_pass mypass   }   virtual_ipaddress {   192.168.11.110   }  } 各Raspberry Piでkeepalivedを再起動します。  # Raspberry Pi 5/Raspberry Pi 3共通  sudo systemctl restart keepalived   動作確認 Webサイトアクセス用PC上のブラウザで仮想IPアドレスのURLにアクセスします。 http://192.168.11.110/ Unit 1の表記があり、Master側(Raspberry Pi 5)のWebサイトにアクセスしていることがわかります。 Raspberry Pi 5に接続したLEDの点灯/消灯も問題なくできます。 ここで、Raspberry Pi5をシャットダウンします。 その後上記URLにアクセスするとUnit 2の表記があり、Backup側(Rapberry Pi 3)のWebサイトにアクセスしています。 Raspberry Pi 3に接続したLEDの点灯/消灯も問題なくできます。 これでWebサイトを冗長化することができました。 GPIO側も冗長構成とする必要がありますが、片系に障害が発生してもサービスを継続させることができるようになります。 今回はLEDを取り付けましたが、カメラやセンサーなどを取り付けてみるのも面白そうです。
アバター
LifeKeeper記事では、前々回DRBD×LifeKeeperの組み合わせの製品と性能についてご紹介しました。 第10回 DRBD × LifeKeeperの高可用性リアルタイムレプリケーションを探る – TechHarmony   今回の記事では、DRBD×LifeKeeper製品であるDisaster Recovery Add-onの構築方法そして機能の検証をしていきたいと思います!   検証環境について 今回、Disaster Recovery Add-onの機能を確認する環境として AWS上の東京リージョンに2台、大阪リージョンに1台の計3ノードによる マルチリージョンでの高可用性構成にてDR環境を構築し、機能の検証を行っていきます。 以下 AWS での3ノード構成では、通常時の想定として 東京リージョンをメインサイトとして、東京1号機、東京2号機 が動作し、 大阪リージョンをバックアップサイトとして、大阪1号機が動作をしている想定の構成としております。 環境の設定値、ルーティング設定などは以下ブログに詳細を掲載しておりますので、気になる方はご参照下さい。 AWSマルチリージョンにおける高可用性方式を実装してみる – TechHarmony   LifeKeeperインストール/DRBD設定手順 DR環境でのLifeKeeperの設定手順について解説していきます。 まずは、LifeKeeperのインストールする際の手順となります。 LifeKeeperをインストールするタイミングでDisaster Recovery Add-onの機能を有するオプションを一緒にインストールすることが可能です。 インストール時以下のように、ARKと呼ばれるミドルウェア選択項目にて「DRBD Recovery Kit」を選択すればOKです。 インストール時の注意点として、「Quorum/Witness」の選択を忘れずにしましょう。 LifeKeeperが持つQuorum/Witness機能を使用することで、複数ノードが同時にActive状態になることを防止できます。   インストールが完了しましたら、 DRBDの設定作業を行っていくためのGUIをインストールする作業を行います。 設定に関しては、LifeKeeper GUIにて実行できますので、その事前準備を行います。 GUIはLKWMC[LifeKeeper Web Management Console]と呼ばれるWebアプリケーションを利用していきます。 LKWMCのインストールファイルを各ノードに配置し、インストールスクリプトを実行することで利用することができます。 LKWMC利用の注意点として、TCPポート5110を使用するため、通信許可の設定が必要です。   GUIの準備ができましたら、DRBDの設定を投入していきます。 設定内容は以下の5項目となります。 1 コミュニケーションパス 2 DRBDリソース作成/拡張 3 IPリソース作成/拡張 4 Generic ARK作成/拡張 5 Quorum設定   ■コミュニケーションパス LifeKeeper では、複数台のノードをお互いに接続することで、クラスターを構成していきます。 そのため、各ノード間で通信を行うための経路を事前に設定する必要があります。 この通信経路のことを LifeKeeper では「コミュニケーションパス」と呼び、こちらの設定を行います。 コミュニケーションパス>オペレーション にて各ノードの設定を入れていきます。作成が完了すると上記画像のような表記がされます。 コミュニケーションパスを作成する際の注意点なのですが、名前解決とTCPポートの穴あけが必要となります。 今回の環境では、/etc/hostsに各ノードの情報を記載し、コミュニケーションパスで使用する7365のポートの通信許可の設定を行いました。   ■DRBDリソース作成/拡張 コミュニケーションパスの作成が完了できましたら、次はDRBDリソースの作成と各ノードに拡張をしていく作業となります。 DRBDリソースは、共有ストレージを使用せずに可用性の高いクラスターを構築する機能を提供します。 まず各ノードにて、マウントポイントの対象となるデバイスの確認(ない場合は作成)、 対象デバイスにてパーティションの作成を行います。 上記作成ができましたら、GUIにてリソース作成を行っていきます。 リソースツリー>オペレーションからDRBDリソース作成を行えます。   作成が完了しますと、上記のように「drbd-mp」というマウントポイントの作成ができます。 このマウントポイントを各ノードへ拡張を行うと、以下のようにデータの同期先として利用できるようになります。   リソース作成/拡張の設定を投入する際に、切り替え/切り戻し動作の設定を行うことができます。 切り替えを手動 or 自動で行うのか。 切り戻しを手動 or 自動で行うのかを選択することが可能です。 今回の設定では切り替えを自動、切り戻しを手動の設定にて行いました。   ■IPリソース作成 続いてIPリソースの作成を行います。 IPリソースは各ノード間で切り替えることができる仮想IPアドレスの作成を行います。 今回は、「20.1.1.1」という仮想IPアドレスを用意し、設定していきます。   AWS上での仮想IPアドレスについては過去に解説記事を投稿していますので、こちらをご参照ください。 【LifeKeeper】AWSでは仮想IPアドレスが使えない!?をこうして解決する!! – TechHarmony   ■GenericARK作成/拡張 メインサイトである東京リージョンがDownした際に、自動で大阪リージョンへルートテーブルが切り替わるためのスクリプトを組み込みます。 詳細については別記事にて紹介予定となります。   ■Quorum設定 今回は自動切り替わりの動作とするため、Quorum設定を行います。 (手動切り替えの際は設定不要となります) GUI上での設定対応が終わりましたら、Quorum機能を有効にするためmajorityからstorageへ変更、 本番/災対サイト以外のリージョンにS3の作成を行い、その情報を設定へ追記します。 Disaster Recovery 構成ではメインサイトの2台が停止する想定のため、バックアップサイトのノード1台では Quorum を持つことができず、自動起動ができなくなります。 この場合、storage モードを使用することでメインサイトの2台が停止しても、バックアップサイトのノードにフェイルオーバーして、サービスをすばやく継続することができます。 storage としては、メインサイト/バックアップサイトとは別のリージョンに Amazon S3 を準備しておきます。Quorum設定なしでも自動で起動しますが、ネットワークが分断した場合(メインサイトは生きているが、クラスタノード間のネットワークが切断)はスプリットブレインになる可能性もあるので、Quorum/Witness の使用を推奨としています。   これにてLifeKeeperのインストール及びDRBD設定が完了となります。   シナリオ/機能検証 設定が完了したところで、Disaster Recovery Add-onの機能を検証していきたいと思います。 今回の確認観点としては、 ●フェイルオーバー時の動作 東京リージョンが使用不可になった際、大阪リージョンへのフェイルオーバーが問題なく行われるか。 ●データレプリケーションの整合性 DRBDリソースの作成時に指定したマウントポイントである”drbd-mp”が ファイルシステムとして稼働しているか、対象のファイルがレプリケーションされているか。 この2つの観点で機能を検証してみます。 テストシナリオとして、東京1、2号機(東京リージョン)をDownさせ、大阪1号機へフェイルオーバーを行います。 まず、通常時として以下のような状態となります。 東京1号機がActive、東京2号機/大阪1号機がStandbyの状態となります。 東京1号機に「drbd-mp」が存在し、test.txtがあります。東京2号機/大阪1号機にはありません。   今回のシナリオに沿って東京リージョンの1,2号機をDownさせていきます。 以下画像が東京東京1,2号機が落ちている状態となります。 GUIでリソースの状態を確認してみると無事フェイルオーバーされ、大阪1号機がPrimaryの状態となりました。 レプリケーションされているか確認したところ、drbd-mp(マウントポイント)が移動されており、 対象ファイルも確認できました。 切り替わり時間としては、東京リージョンをDownさせてから体感40~50秒ほどで切り替わりました。   東京1,2号機を復旧させると、大阪1号機がActiveで稼働している状態となります。 復旧後の切り戻しは手動 or 自動のどちらかを指定して戻すことができますが、 今回の切り戻し設定は、切り戻しのタイミングを管理者側のタイミングで復旧できるよう手動としておりますので、 東京が復旧してきても大阪1号機がActiveの状態のままとなります。   まとめ 今回AWS環境でのLifeKeeperを用いた災害対策としてDisaster Recovery Add-on の構築、機能検証を行い、 異なるリージョン間のデータレプリケーションの確認を行いました。 マルチAZ構成での可用性を高めるだけでなく、マルチリージョン構成をとることで、 遠隔地サイトへのスタンバイノードに対しても高速なレプリケーションと、障害発生時のフェールオーバーを可能とするため、自然災害によるシステム停止から素早くアプリケーションを回復することが可能となり、 よりシステムの信頼性と可用性を向上させ、企業のIT運用をさらに強固なものにすることができるなと感じました。 近年の災害対策としてぜひDisaster Recovery Add-onをご活用いただければと思います。
アバター
本記事は 新人ブログマラソン2024 の記事です 。 こんにちは。2024年度入社の野上です。 以前、MySQLの初心者ながらレプリケーション方式をまとめた記事を投稿させていただきました。 今回は、以前まとめた方式の中で一般的とされている「非同期レプリケーション」を構築してみました。 これは実際に私が携わった案件で経験した内容になります。 非同期レプリケーションの概要をわかっていても、実際に構築するとなると考慮しなければいけない事が多々ありました。 ブログや公式ドキュメントで書かれていない内容もありましたので、自分の知識のアウトプットもかねて皆さんにご紹介いたします。 非同期レプリケーションとは まず非同期レプリケーションとはどういう方式なのかという詳細に関しては、前回私が書いた記事を見ていただきたいです。 MySQL レプリケーションの方式をまとめてみた – TechHarmony ここで簡単に説明しますと、ソースサーバ(メインで使用するDBサーバ)でデータを更新する処理と、レプリカサーバ(複製されたDBサーバ)にデータの更新を反映させる処理が、非同期で行われます。 データベースのデータを更新するときには、トランザクション(データベースの操作を行う1つの処理単位)のコミットを実行し、コミットが完了することで、更新を確定させます。 非同期レプリケーションではレプリカサーバでの処理❶~❷の完了を待たずにコミットを完了します。   実際に構築してみた 非同期レプリケーションを構築する順序は以下の通りになります。 サーバの準備 データベースのコピー パラメータ設定 レプリケーション用ユーザーの作成 レプリケーション設定 各順序を説明しながら、レプリケーションを構築していきます。 サーバの準備 レプリケーションを構築するにあたり、レプリケーションを張る2つのサーバが必要となります。 Amazon EC2を用いて検証用のサーバを構築しました。 今回はMySQLのバージョンは 8.0.36 を使用しています。 MySQLはバージョンの違いによりコマンドが異なりますのでご注意ください 。 用途 ホストIPアドレス MySQLバージョン ソースサーバ 10.10.23.187 MySQL8.0.36 レプリカサーバ 10.10.23.19 MySQL8.0.36   データベースのコピー 対象のサーバを準備したら、ソースのデータベースをレプリカにコピーする必要があります。 ただレプリケーションを張るだけでは、レプリカのデータベースはソースと同じにはなりません。 レプリケーションは、ソースのバイナリログの内容をレプリカに転送するため、 バイナリログが残っている部分しかレプリケーションされません 。バイナリログの保持期間は、 「binlog_expire_logs_seconds」 というパラメータで設定されているため、my.cnfというパラメータの設定ファイルの内容を確認することをお勧めします。 データをコピーする方法は2通りあり、どちらかの方法でコピーを行います。 dmpファイルを用いてコピーする(MySQLの公式ドキュメントに記載されている) MySQLのデータディレクトリをコピーする dmpファイルを用いた方法が一般的ですが、今回は案件の中で私が勉強したことを共有したいこともあり、MySQLのデータディレクトリをコピーする方法で行います。 ①ソースでMySQLのデータディレクトリのパスを確認する データディレクトリのパスは、my.cnfに記述されているため以下のコマンドを実行します。     ②データディレクトリを圧縮する 以下のコマンドを実行することで、「datacopy.tar.gz」という圧縮ファイルが生成されます。     ③生成した圧縮ファイルをレプリカに送信する ファイルの送信方法もいくつかありますが、今回は2つのサーバで直接通信が可能なので、scpコマンドを利用してファイルの送信を行いました。     ④レプリカ側で圧縮したファイルを解凍してデータディレクトリを上書きする 以下のコマンドを実行することでデータディレクトリのコピーが完了します。 ※ここではmvコマンドにより元のデータディレクトリを退避させることで、安全にコマンドを実行しています。     ⑤auto.cnf の削除 以下のデータディレクトリ内に含まれる「auto.cnf」という名前のファイルの削除を行い、MySQLを再起動します。(※)       (※)auto.cnf とは、MySQL server のUUIDを格納したファイルです。 UUIDとは一意の識別子であり、UUIDが重複した状態ではレプリケーションを張ることができません 。そのため、ソースとレプリカで異なるUUIDを使用する必要があります。auto.cnf は自動で生成されるファイルであり、手動で削除してMySQLを再起動を実行することで自動作成されます。⑤の手順を行うことで、 ソースとレプリカで異なるUUIDとなるため、レプリケーションを張ることが可能になります 。 以上の手順により、データディレクトリのコピーが完了しました。   パラメータ設定 レプリケーションを張るためには、レプリケーションに必要なパラメータ設定をする必要があります。 MySQLのパラメータは「my.cnf」という設定ファイルに記述してあり、そのファイル内のパラメータ値を変更します。   ① 設定が必要なパラメータを以下の通りに設定する <ソースとレプリカ両方に設定> gtid_mode = ON GTID(グローバルトランザクションID)を有効化するパラメータです。 GTIDを有効化することによりソースとレプリカの一貫性の判断を容易にします。 enforce_gtid_consistency = ON GTIDを使用する際に、整合性に違反するトランザクションを禁止する設定です。 server_id = 1(ソース), 2(レプリカ) レプリケーションの構成内でソースとレプリカを一意に識別するために用います。 <レプリカのみに設定> read_only = ON レプリカ側でデータの更新を拒否します。 SUPER権限を持つアカウントは拒否しません。 super_read_only = ON レプリカ側でSUPER権限を持つアカウントからの更新を拒否します。   ② MySQLを再起動する my.cnf の内容を変更したときには、MySQLを再起動することで設定を反映することができます。 再起動には以下のコマンドを実行します。   以上の手順を実行することでMySQLのパラメータ変更が完了しました。   レプリケーション用ユーザーの作成 次に、レプリケーションを行うために必要なMySQLのユーザーを作成します。 レプリケーションを張る際には、レプリカがソースのMySQLユーザーとしてデータにアクセスします。そのため、ソース側にレプリケーションのユーザー権限を持ち、レプリカのIPアドレスからのログインを許可するユーザーが必要です。 ①ソース側でMySQLにログインし、以下のステートメントを実行してユーザーを作成する     ここでは、replica@10.10.23.19 というユーザーを作成しています。 MySQLではユーザー名とホスト名の組み合わせによってユーザーを作成します。   ②作成したユーザーにレプリケーション権限を与える 以下のステートメントを実行します。作成したユーザーがレプリケーション権限を持っていることが確認できます。         以上の手順でレプリケーション用のユーザーの作成が完了しました。   レプリケーション設定 パラメータの設定が完了したら、いよいよレプリケーションを張ります。 ①レプリカ側でMySQLにログインして、レプリケーションの設定を行う 以下のステートメントを実行します。       ここでは、先ほどソースで作成したユーザーのホストアドレス、ユーザー名、パスワードを指定します。 さらに、「source_auto_position」,「source_ssl」という2つのオプションを付けています。 「source_auto_position」はGTIDを使用するために必須のオプションです。 「source_ssl」 はレプリケーションの通信がSSL接続するというオプションです。(※) (※) MySQLはバージョン5.7以前と8.0以降でユーザー認証の方式が変更されています 。5.7以前はデフォルトの認証プラグインが「mysql_native_password」ですが、8.0以降は「caching_sha2_password」となっています。それにより、 セキュアな接続が必須 となりました。そのため 「source_ssl」のオプションを付けない場合にレプリケーション接続が失敗してしまいます 。 (参考) https://dev.mysql.com/doc/refman/8.0/ja/upgrading-from-previous-series.html#upgrade-caching-sha2-password   ②レプリケーションの開始 以下のステートメントを実行することでレプリケーション接続を開始します。     ③レプリケーションのステータスの確認 以下のステートメントを実行することでレプリケーションのステータスを表示します。                                             実行結果の赤枠で囲んだ項目は以下を意味します。 Replica_IO_Running=Yes ソースのバイナリログを読み込み、レプリカにログを転送するスレッドが動いていることを示します。 Replica_SQL_Running=Yes レプリカで、ソースから転送されたログからデータの更新を行うスレッドが動いていることを示します。 Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error:        エラーが発生していないことを示します。 上記のことから、 実行結果通りであればレプリケーション接続が成功 していることになります。 以上の手順を実行しレプリケーションの構築に成功しました。MySQLでは非同期レプリケーションがデフォルトであるため、非同期レプリケーションの構築ができたことになります。 まとめ 本記事では、非同期レプリケーションを実際に構築してみました。 私が案件の中で手を詰まらせた項目は紹介できたと思っています。 しかし、レプリケーションは要件によってパラメータの値などを変化させる必要があります。 そのため今回紹介したのはレプリケーションの構築の1つの例ですが、少しでも読んでくださった方の助けになるポイントがあれば嬉しいです。
アバター
前回、AWS Glue と Amazon Athena でS3上のデータを読み取ることができました。 Amazon Athena + AWS Glue で Amazon S3 上のデータを読み取る – TechHarmony 今回は Amazon Athena 上のデータを Amazon QuickSight で可視化したいと思います。 QuickSight の詳細については QuickSight公式ページ をご参照ください。   やってみた Amazon QuickSight AWSマネジメントコンソールからQuickSight画面を開きます。 初めてQuickSightを使用する際はアカウントの設定画面が開きますので、適宜設定してください。 「新しい分析」をクリックします。   「新しいデータセット」をクリックします。   データソースを選択します。今回はAthenaを選択します。   データソース名を入力し、「データソースを作成」をクリックします。 。   可視化したいデータがあるデータベースおよびテーブルを選択し、「選択」をクリックします。   「Visualize」をクリックします。   以上で、インポートが完了です。 自動で分析の画面に移ります。   最初にグラフが1つ用意されているので、フィールドリストから必要なものをドラッグ&ドロップで追加します。 X軸、Y軸に分析対象のデータを配置し、 分析対象のデータ形式に応じて、可視化のフォーマットもQuickSight側で色々準備されています。 今回は試しに積み上げ面折れ線グラフと円グラフでデータを可視化してみました。   おわりに Amazon S3 上のデータを Amazon Athena と Amazon QuickSight で可視化することができました。 今回は単純なデータでしたが、大まかに使い方を理解することができました。 サーバレスサービスなので手軽に利用開始でき、初心者でも使いやすそうだなという所感です。 データ分析をやってみたいけど難しそうだな、、、と思っている方に少しでもイメージが伝われば幸いです。
アバター
こんにちは。SCSK株式会社の上田です。 2024年12月10日に、ポイントリリースバージョンである Zabbix 7.2 がリリースされ、Zabbix7.0 LTSには無かった新機能が追加されました! 今回は、追加された新機能の中から、 役に立ちそうな機能を4個、ピックアップしてご紹介します! 前回リリースされたZabbix7.0 LTSの新機能については、こちらの記事をご参照ください。 Zabbix 7.0で追加された便利な新機能10選 Zabbix 7.0では様々な新機能が追加されました。その中から便利な機能を厳選してご紹介します。 blog.usize-tech.com 2024.07.05   新機能の紹介 それでは、新機能を紹介していきます。 ①新しいウィジェット Zabbix7.0でも新しいウィジェットが追加されましたが、今回も 新しいウィジェットがいくつかリリースされました 。 1つ目は、 Host card 。選択したホストの情報を見ることができます。 Host Cardの詳細 表示する内容は、カスタマイズ可能です。 Host Cardで選択可能な項目 2つ目は、 Top items 。ホストのアイテムや、値やグラフで一覧表示できます。 Top itemsの表示内容 3つ目は、 Sparkline 。こちらはウィジェットの種類というより、ウィジェット上に表示できるグラフです。アイテムデータや、先ほどのTop itemsウィジェットで、スパークラインを表示することができます。 SparkLine(アイテムの値ウィジェット) SparkLine(Top itemsウィジェット) ②設定ファイルの環境変数サポート Zabbixサーバやエージェントなどの設定ファイルで、 環境変数 が利用できる ようになりました。これにより、設定値をハードコーディングする必要がなくなり、Dockerなどのコンテナ環境での設定が容易になりました。 ※Docker用であり、通常の運用での利用は推奨されていないようです。 ③NETCONFの対応 SSHアイテムがサブシステムパラメータをサポートするようになり、 NETCONF を利用したネットワークデバイスの監視が可能になりました 。これにより、従来のSNMPによる監視に加え、NETCONFの機能を活用した監視を実現できます。 先日のZabbixカンファレンスで、Zabbixの代表から「 8.0ではnetconfによるコンフィグ管理が可能になる 」という発言がありました。それの準備として実装されたのだと思います。 Zabbixでコンフィグ管理ができるようになれば、ネットワーク監視の幅が大きく広がるので、今後の動向に注目です。 ④新しいマクロ 新しいマクロ がリリースされ、監視設定とアラート設定がより柔軟になります。 以下は新しいマクロの例です。 {*TIMESTAMP}  マクロ:  イベントのUnixタイムスタンプを取得可能。 {FUNCTION.VALUE}  マクロ:  トリガー式内で個々の関数結果を参照可能。 これらのマクロを活用することで、 より高度な監視ロジックを構築する ことができます。 まとめ Zabbix 7.2の新機能を紹介しました。今回紹介した機能以外にも様々な機能が追加されています。詳細は公式ドキュメントをご参照ください。 Zabbix 7.2の新機能 Zabbix 7.2 introduces new ways to visualize your data and host information, and improves upon various existing monitorin... www.zabbix.com 「 Zabbix 7.2を新規にインストールしてみたい 」「 既存のZabbixからバージョンアップしたい 」という方は、是非弊社までお声がけください!! 最後まで読んでいただき、ありがとうございました。 弊社ではZabbix関連サービスを展開しています。以下ページもご参照ください。 ★Zabbixの基礎をまとめたeBookを公開しております!★ FAQ・お問い合わせ|SCSK Plus サポート for Zabbix 導入をご検討される際、よくあるご質問と回答についてまとめております。 www.scsk.jp ★SCSK Plus サポート for Zabbix★ SCSK Plus サポート for Zabbix 世界で最も人気のあるオープンソース統合監視ツール「Zabbix」の導入構築から運用保守までSCSKが強力にサポートします www.scsk.jp ★YouTubeに、SCSK Zabbixチャンネルを開設しました!★ SCSK Zabbixチャンネル 本チャンネルでは、SCSK株式会社でのZabbixに関するトレンド/事例紹介などを動画にまとめて取り上げております。最新のトピックについては、以下の弊社HPもしくはツイッターアカウントをぜひ参照ください。ツイッターアカウント: www.youtube.com ★X(旧Twitter)に、SCSK Zabbixアカウントを開設しました!★ x.com x.com
アバター
SCSKの畑です。10回目の投稿です。 今回は 6回目 のエントリで記載した、aws-amplify のSSR (Nuxt3)対応の詳細もとい補足について記載します。小ネタです。 対応の背景などは同エントリに記載しているのでここでは省略します。   aws-amplify 用のコンフィグファイル実装 aws-amplify モジュールを使用するにあたり、AppSync や Cognito など、モジュールが使用する各種サービスの情報が必要となります。言い換えると、Amplify.configure の引数としてどのような内容を設定するべきか、です。 ところが、具体的にどのように設定すべきなのかというドキュメントや情報が相対的に乏しく、あったとしても aws-amplify のバージョンが異なっておりコピペだと動かないなど、ちゃんと動かすまで結構苦労したため備忘として残しておこうと思います。例えば、GraphQL のデフォルト認証モードが IAM な場合は値を 「iam」に設定するみたいな情報とか、少なくとも実装時点ではちょっと調べた程度だと出てこなかったりしたんですよね。公式ドキュメントでは以下 URL が対応すると思うのですが、一覧としての記載はありませんし。。 Configure Amplify categories - Vue - AWS Amplify Gen 1 Documentation Configuring the client. AWS Amplify Documentation docs.amplify.aws 色々と調べた限り、手動でコンフィグファイルを作成するより、Amplify CLI で経由で各種リソースを構成した際に生成されるコンフィグファイル (src/aws-exports や src/amplifyconfiguration.json) を使用するのが事例としては多いように思えました。そのことも、手動でコンフィグファイルを作成する情報が相対的に少ない原因に繋がっているように思えます。 ただ、今回は別エントリで説明した通りお客さん環境で Amplify を使用できないことが分かっていたこと、Amplify 経由で他サービスを構成した際の細かい設定が難しいことの2点より、Amplify 経由で構成したのは AppSync/DynamoDB のみでした。コンフィグファイル自体も最初から手動で作成する方針で進めていたので、その分時間もかかったような恰好です。 そもそも、コンフィグファイルの書式からして異なっているというのも、構築当初はかなり混乱しました。書式を参考にしようとしても参考にならず、しかしながら Amplify.configure の引数としてはどちらも使用できるというのが・・ What options can I pass to Amplify.configure()? There seem to be two different ways to configure Amplify. Some examples use a key-value properties format:import aws_exp... stackoverflow.com 実際、aws-amplify モジュールは UI 含めて色々と便利で、このモジュールだけを使用したいというケースも全然考えられると思うので、コンフィグ定義一覧程度は公式ドキュメントに載せてくれるとありがたいですね。 ということで、今回の場合の実装例を示してみます。対象は Amplify(GraphQL) 及び Cognito の2種類です。 {   "Auth": {       "Cognito": {           "region": "ap-northeast-1",           "userPoolId": "<Cognito_UserPool_Id>",           "userPoolClientId": "<Cognito_Application_Client_Id>",           "identityPoolId": "<Cognito_IdentityPool_Id>"       }   },   "API": {       "GraphQL": {           "endpoint": "<AppSync_GraphQL_Endpoint_URL>",           "region": "ap-northeast-1",           "defaultAuthMode": "iam"       }   } } なお、他サービスを含めたより詳細な内容については、以下広野さんのエントリも合わせてご参照ください。私も最初にこのエントリを見ていさえすれば、試行錯誤に数時間を費やすところまではいかなかったでしょう・・ aws-amplify と @aws-amplify/ui-react モジュールを v6 にアップデートしてみた aws-amplify と @aws-amplify/ui-react モジュールをバージョン 5 から 6 にアップグレードしたときの対応を紹介します。 blog.usize-tech.com 2023.12.19   Plugin 実装 こちらは、基本的には公式 URL の内容をほぼ踏襲して実装しています。 Use Amplify categories APIs from Nuxt 3 - Next.js - AWS Amplify Gen 1 Documentation Use Amplify categories APIs from Nuxt 3 AWS Amplify Documentation docs.amplify.aws なお、Plugin のファイル名で、client/server のどちらで実行するプラグインかどうかを指定しています。また、Plugin の読込み順序はファイル名(昇順)に従うため、ファイル名の先頭にナンバリングを付与して一番最初に読み込まれるようにしています。 前回のエントリ で記載した通り、他の Plugin 内で aws-amplify/auth (Cognito) や aws-amplify/data (AppSync/GraphQL) の機能を使用しているため、先にこちらの処理を実施しておく必要があるためです。公式 URL の実装例も正にそのようになっていますね。   01.amplifyApis.client.ts client 側で実行する plugin です。先般のエントリで記載した通り、aws-amplify 自体は client 側で実行する前提のモジュールとなっているため、実装内容自体はシンプルです。 今回のアプリケーションで使用するのは Auth/GraphQL の2種類のみであるため、その中から使用する機能やメソッドのみを返すようにしています。また、先に説明した aws-amplify 用の設定ファイルを Amplify.configure の引数で指定しています。 import { Amplify } from 'aws-amplify'; import { fetchAuthSession, fetchUserAttributes, getCurrentUser, signIn, signOut} from "aws-amplify/auth"; import { generateClient } from 'aws-amplify/data'; import { defineNuxtPlugin } from '#app'; import outputs from '../amplify_cognito_setting.json'; const client = generateClient(); export default defineNuxtPlugin({   name: 'AmplifyAPIs',   enforce: 'pre',   setup() {               Amplify.configure(outputs, { ssr: true });       return {           provide: {               Amplify: {                   Auth: {                       fetchAuthSession,                       fetchUserAttributes,                       getCurrentUser,                       signIn,                       signOut                   },                   GraphQL: {                       client                   }               }           }       }   } })   01.amplifyApis.server.ts server 側で実行する plugin です。こちらは率直に言うと、公式 URL の実装例から client の実装をベースに明らかに不要な項目を除外した程度の変更しかしていないため、正直あまり言及できる内容がありません。幸いにも公式 URL の実装例に記載されていた機能/メソッドしか使用しなかったためこのような対応で OK でしたが、他に使用したいものがあった場合はモジュールの実装内容そのものを読み解く必要があったかもしれないです。 認証用の情報を cookie に入れているようで、有効期限が実装例だと30日間となっているため、要件次第でこの点は見直した方が良さそうです。そもそも cookie を使用するかどうかを含めて検討すべきかもしれませんが・・ また、aws-amplify 用の設定ファイルを読み込んでいるのは一緒ですが、Amplify.configure は client 側のメソッドであるため、同設定ファイルから読み込んだ内容をベースに aws-amplify/auth 及び aws-amplify/data ごとに必要な情報をパースしています。 import type { CookieRef } from 'nuxt/app'; import type {   LibraryOptions,   FetchAuthSessionOptions } from '@aws-amplify/core'; import {   createKeyValueStorageFromCookieStorageAdapter,   createUserPoolsTokenProvider,   createAWSCredentialsAndIdentityIdProvider,   runWithAmplifyServerContext } from 'aws-amplify/adapter-core'; import { parseAmplifyConfig } from 'aws-amplify/utils'; import {   fetchAuthSession,   fetchUserAttributes,   getCurrentUser } from 'aws-amplify/auth/server'; import { generateClient } from 'aws-amplify/api/server'; import outputs from '@/amplify_cognito_setting.json'; // parse the content of `amplify_outputs.json` into the shape of ResourceConfig const amplifyConfig = parseAmplifyConfig(outputs); // create the Amplify used token cookies names array const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId; const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`; // create a GraphQL client that can be used in a server context const gqlServerClient = generateClient({ config: amplifyConfig }); const getAmplifyAuthKeys = (lastAuthUser: string) =>   ['idToken', 'accessToken', 'refreshToken', 'clockDrift']       .map(           (key) =>               `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}`       )       .concat(lastAuthUserCookieName); // define the plugin export default defineNuxtPlugin({   name: 'AmplifyAPIs',   enforce: 'pre',   setup() {       // The Nuxt composable `useCookie` is capable of sending cookies to the       // client via the `SetCookie` header. If the `expires` option is left empty,       // it sets a cookie as a session cookie. If you need to persist the cookie       // on the client side after your end user closes your Web app, you need to       // specify an `expires` value.       //       // We use 30 days here as an example (the default Cognito refreshToken       // expiration time).       const expires = new Date();       expires.setDate(expires.getDate() + 30);         // Get the last auth user cookie value       //       // We use `sameSite: 'lax'` in this example, which allows the cookie to be       // sent to your Nuxt server when your end user gets redirected to your Web       // app from a different domain. You should choose an appropriate value for       // your own use cases.       const lastAuthUserCookie = useCookie(lastAuthUserCookieName, {           sameSite: 'lax',           expires,           secure: true       });         // Get all Amplify auth token cookie names       const authKeys = lastAuthUserCookie.value           ? getAmplifyAuthKeys(lastAuthUserCookie.value)           : [];       // Create a key-value map of cookie name => cookie ref       //       // Using the composable `useCookie` here in the plugin setup prevents       // cross-request pollution.       const amplifyCookies = authKeys           .map((name) => ({               name,               cookieRef: useCookie(name, { sameSite: 'lax', expires, secure: true })           }))           .reduce<Record<string, CookieRef<string | null | undefined>>>(               (result, current) => ({                   ...result,                   [current.name]: current.cookieRef               }),               {}           );       // Create a key value storage based on the cookies       //       // This key value storage is responsible for providing Amplify Auth tokens to       // the APIs that you are calling.       //       // If you implement the `set` method, when Amplify needed to refresh the Auth       // tokens on the server side, the new tokens would be sent back to the client       // side via `SetCookie` header in the response. Otherwise the refresh tokens       // would not be propagate to the client side, and Amplify would refresh       // the tokens when needed on the client side.       //       // In addition, if you decide not to implement the `set` method, you don't       // need to pass any `CookieOptions` to the `useCookie` composable.       const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({           get(name) {               const cookieRef = amplifyCookies[name];               if (cookieRef && cookieRef.value) {                   return { name, value: cookieRef.value };               }               return undefined;           },           getAll() {               return Object.entries(amplifyCookies).map(([name, cookieRef]) => {                   return { name, value: cookieRef.value ?? undefined };               });           },           set(name, value) {               const cookieRef = amplifyCookies[name];               if (cookieRef) {                   cookieRef.value = value;               }           },           delete(name) {               const cookieRef = amplifyCookies[name];               if (cookieRef) {                   cookieRef.value = null;               }           }       });       // Create a token provider       const tokenProvider = createUserPoolsTokenProvider(           amplifyConfig.Auth!,           keyValueStorage       );         // Create a credentials provider       const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(           amplifyConfig.Auth!,           keyValueStorage       );         // Create the libraryOptions object       const libraryOptions: LibraryOptions = {           Auth: {               tokenProvider,               credentialsProvider           }       };         return {           provide: {               // You can add the Amplify APIs that you will use on the server side of               // your Nuxt app here. You must only use the APIs exported from the               // `aws-amplify/<category>/server` subpaths.               //               // You can call the API by via the composable `useNuxtApp()`. For example:               // `useNuxtApp().$Amplify.Auth.fetchAuthSession()`               //               // Recall that Amplify server APIs are required to be called in a isolated               // server context that is created by the `runWithAmplifyServerContext`               // function.               Amplify: {                   Auth: {                       fetchAuthSession: (options: FetchAuthSessionOptions) =>                           runWithAmplifyServerContext(                               amplifyConfig,                               libraryOptions,                               (contextSpec) => fetchAuthSession(contextSpec, options)                           ),                       fetchUserAttributes: () =>                           runWithAmplifyServerContext(                               amplifyConfig,                               libraryOptions,                               (contextSpec) => fetchUserAttributes(contextSpec)                           ),                       getCurrentUser: () =>                           runWithAmplifyServerContext(                               amplifyConfig,                               libraryOptions,                               (contextSpec) => getCurrentUser(contextSpec)                           )                   },                   GraphQL: {                       client: {                           // Follow this typing to ensure the`graphql` API return type can                           // be inferred correctly according to your queries and mutations                           graphql: <                               FALLBACK_TYPES = unknown,                               TYPED_GQL_STRING extends string = string                           >(                               options: GraphQLOptionsV6<FALLBACK_TYPES, TYPED_GQL_STRING>,                               additionalHeaders?: Record<string, string>                           ) =>                               runWithAmplifyServerContext<                                   GraphQLResponseV6<FALLBACK_TYPES, TYPED_GQL_STRING>                               >(amplifyConfig, libraryOptions, (contextSpec) =>                                   gqlServerClient.graphql(                                       contextSpec,                                       options,                                       additionalHeaders                                   )                               )                       }                   }               }           }       };   } });   まとめ コードの分量は多いですが、小ネタに相応しい内容でした。毎回気合いの入ったエントリを書くのがそろそろ難しくなってきたので、こういうちょっとした内容も増やしていければなと思います。(書きながらネタを増やしていけるように努力しているつもりですが・・)ともあれ2桁投稿することが当初の目標だったので、ひとまず達成できてよかったです。 本記事がどなたかの役に立てば幸いです。
アバター
こんにちは、SCSKの内ヶ島です。 オンプレミス環境から手軽にAWS Site-to-Site VPN接続の検証環境を構築する方法を紹介します。 この記事では、CloudFormationを使った環境を一括管理し、Amazon Linux 2023のリポジトリに含まれるLibreswanを用いてVPN接続を行います。 はじめに AWS Site-to-Site VPN接続は、オンプレミス環境とAWS環境を安全に接続するための重要な技術です。 しかし、VPN接続の検証には通常、実機のVPN機器が必要となりハードルが高いものでした。 そこで今回の検証では、以下の2点を主な目標として設定しました。 ソフトウェアVPNを用いてVPN接続が正常に機能すること 環境全体をCloudFormationで管理し、簡単に作成・削除できること 実機のVPN機器がなくても、AWSの環境内で完結した形でVPN接続の検証が可能になります。 また、CloudFormationを活用することで、環境の再現性と管理の容易さを実現しています。 環境構成 今回構築する環境は以下の通りです。 オンプレミス側(AWS上に疑似環境として構築)   – VPC (10.0.0.0/22)   – プライベートサブネット (10.0.0.0/24)   – パブリックサブネット (10.0.1.0/24)   – EC2インスタンス x2(VPN装置、クライアント想定) AWS側   – VPC (192.168.0.0/22)   – プライベートサブネット (192.168.0.0/24)   – EC2インスタンス(AWSサーバー想定)   – カスタマーゲートウェイ、仮想プライベートゲートウェイ CloudFormationテンプレート 今回の検証環境構築には、オンプレミス環境用とAWS環境用の2つのCloudFormationテンプレートを使用しています。 CloudFormationテンプレートの特徴 複雑なVPN検証環境を簡単に、そして再現性高く構築することができます。 両環境とも、インターネットに直接接続せずにパッケージ管理やシステム更新が可能な、セキュアな設計となっています。 以下の内容を生成AIを使ってCloudFormationテンプレートを作成しました。 長いテンプレートの記述ミスや整合性をある程度チェックできるので便利です。 共通の特徴 VPC構成 : 両テンプレートともVPCとサブネットを設定 EC2インスタンス : Amazon Linux 2023のEC2インスタンスを作成 セキュリティグループ : 必要最小限のトラフィックのみを許可するセキュリティグループを設定 IAMロールとVPCエンドポイント : Systems Manager Session Managerを使用してEC2インスタンスに接続できるよう、必要なIAMロールとVPCエンドポイント(SSM、EC2メッセージ、SSMメッセージ)を設定 S3 VPCエンドポイント : EC2インスタンスがインターネットを経由せずにS3にアクセスできるよう、S3用のVPCゲートウェイエンドポイントを設定。これにより、dnfやyumを使用したパッケージのインストールが可能となる。 タグ付け : すべてのリソースにタグを付け、管理を容易にしている。タグのプレフィックスはパラメータとして指定可能 オンプレミス環境用テンプレート(Onpre_resource.yaml)の固有の特徴 サブネット構成 : パブリックサブネットとプライベートサブネットの両方を作成 EC2インスタンス(パブリックサブネット) : VPN装置役のEC2インスタンス(Elastic IP付与)。ネットワークの送信元/送信先チェックをオフ EC2インスタンス(プライベートサブネット) : クライアント役のEC2インスタンス ルーティング : プライベートサブネットからのトラフィックをパブリックEC2インスタンス(疑似NATゲートウェイ)経由でルーティング インターネットゲートウェイ : パブリックサブネット用にインターネットゲートウェイを設定 AWS環境用テンプレート(AWS_resource.yaml)の固有の特徴 サブネット構成 : プライベートサブネットのみを作成します。 VPNリソース : カスタマーゲートウェイ、仮想プライベートゲートウェイ、VPN接続、VPN接続ルートを作成 パラメータ : カスタマーゲートウェイのIPアドレスをパラメータとして受け取る EC2インスタンス : プライベートサブネット内に1つのEC2インスタンスを作成(VPN経由でアクセスされるサーバー役) CloudFormationテンプレート オンプレミス環境用テンプレート(Onpre_resource.yaml) AWSTemplateFormatVersion: '2010-09-09' Description: 'VPC with Public and Private Subnets, EC2 Instances, SSM access, and S3 Endpoint' Parameters: TagPrefix:   Type: String   Default: '00000'   Description: 'Prefix for resource tags' AmazonLinux2023AMI:   Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>   Default: /aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64 Resources: VPC:   Type: AWS::EC2::VPC   Properties:     CidrBlock: 10.0.0.0/22     EnableDnsHostnames: true     EnableDnsSupport: true     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-VPC'   InternetGateway:   Type: AWS::EC2::InternetGateway   Properties:     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-IGW'   InternetGatewayAttachment:   Type: AWS::EC2::VPCGatewayAttachment   Properties:     InternetGatewayId: !Ref InternetGateway       VpcId: !Ref VPC PublicSubnet:   Type: AWS::EC2::Subnet   Properties:     VpcId: !Ref VPC     AvailabilityZone: !Select [0, !GetAZs '']     CidrBlock: 10.0.1.0/24     MapPublicIpOnLaunch: true     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Public-Subnet' PrivateSubnet:   Type: AWS::EC2::Subnet   Properties:     VpcId: !Ref VPC     AvailabilityZone: !Select [1, !GetAZs '']     CidrBlock: 10.0.0.0/24     MapPublicIpOnLaunch: false     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Private-Subnet' PublicRouteTable:   Type: AWS::EC2::RouteTable   Properties:     VpcId: !Ref VPC     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Public-RT' PrivateRouteTable:   Type: AWS::EC2::RouteTable   Properties:     VpcId: !Ref VPC     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Private-RT' PublicRoute:   Type: AWS::EC2::Route   DependsOn: InternetGatewayAttachment   Properties:     RouteTableId: !Ref PublicRouteTable     DestinationCidrBlock: 0.0.0.0/0       GatewayId: !Ref InternetGateway PrivateRoute:   Type: AWS::EC2::Route   Properties:     RouteTableId: !Ref PrivateRouteTable     DestinationCidrBlock: 0.0.0.0/0       InstanceId: !Ref PublicEC2Instance PublicSubnetRouteTableAssociation:   Type: AWS::EC2::SubnetRouteTableAssociation   Properties:     SubnetId: !Ref PublicSubnet       RouteTableId: !Ref PublicRouteTable PrivateSubnetRouteTableAssociation:   Type: AWS::EC2::SubnetRouteTableAssociation   Properties:     SubnetId: !Ref PrivateSubnet       RouteTableId: !Ref PrivateRouteTable PublicSecurityGroup:   Type: AWS::EC2::SecurityGroup   Properties:     GroupName: !Sub '${TagPrefix}-Public-SG'     GroupDescription: 'Security group for public EC2 instance'     VpcId: !Ref VPC     SecurityGroupIngress:       - IpProtocol: -1         FromPort: -1         ToPort: -1         CidrIp: 10.0.0.0/22     SecurityGroupEgress:       - IpProtocol: -1         FromPort: -1         ToPort: -1         CidrIp: 0.0.0.0/0     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Public-SG' PrivateSecurityGroup:   Type: AWS::EC2::SecurityGroup   Properties:     GroupName: !Sub '${TagPrefix}-Private-SG'     GroupDescription: 'Security group for private EC2 instance'     VpcId: !Ref VPC     SecurityGroupIngress:       - IpProtocol: -1         FromPort: -1         ToPort: -1         SourceSecurityGroupId: !Ref PublicSecurityGroup     SecurityGroupEgress:       - IpProtocol: -1         FromPort: -1         ToPort: -1         CidrIp: 0.0.0.0/0     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Private-SG' EndpointSecurityGroup:   Type: AWS::EC2::SecurityGroup   Properties:     GroupName: !Sub '${TagPrefix}-Endpoint-SG'     GroupDescription: 'Security group for VPC Endpoints'     VpcId: !Ref VPC     SecurityGroupIngress:       - IpProtocol: tcp         FromPort: 443         ToPort: 443         SourceSecurityGroupId: !Ref PrivateSecurityGroup       - IpProtocol: tcp         FromPort: 443         ToPort: 443         SourceSecurityGroupId: !Ref PublicSecurityGroup     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Endpoint-SG' PublicEC2Instance:   Type: AWS::EC2::Instance   Properties:     InstanceType: t3.micro     ImageId: !Ref AmazonLinux2023AMI     SubnetId: !Ref PublicSubnet     SecurityGroupIds:       - !Ref PublicSecurityGroup     IamInstanceProfile: !Ref EC2InstanceProfile     SourceDestCheck: false     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Onpre-Public-EC2' PrivateEC2Instance:   Type: AWS::EC2::Instance   Properties:     InstanceType: t3.micro     ImageId: !Ref AmazonLinux2023AMI     SubnetId: !Ref PrivateSubnet     SecurityGroupIds:       - !Ref PrivateSecurityGroup     IamInstanceProfile: !Ref EC2InstanceProfile     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Onpre-Private-EC2' PublicEIP:   Type: AWS::EC2::EIP   Properties:     Domain: vpc     InstanceId: !Ref PublicEC2Instance     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-Public-EIP' EC2InstanceProfile:   Type: AWS::IAM::InstanceProfile   Properties:     Path: "/"     Roles:         - !Ref EC2SSMRole S3Endpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     VpcId: !Ref VPC     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'     VpcEndpointType: Gateway     RouteTableIds:         - !Ref PrivateRouteTable EC2SSMRole:   Type: AWS::IAM::Role   Properties:     AssumeRolePolicyDocument:       Version: "2012-10-17"       Statement:         - Effect: Allow           Principal:             Service:               - ec2.amazonaws.com           Action:             - sts:AssumeRole     ManagedPolicyArns:       - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore       - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess     Tags:       - Key: Name           Value: !Sub '${TagPrefix}-EC2-SSM-Role' SSMEndpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'     VpcId: !Ref VPC     VpcEndpointType: Interface     PrivateDnsEnabled: true     SubnetIds:       - !Ref PrivateSubnet       - !Ref PublicSubnet     SecurityGroupIds:         - !Ref EndpointSecurityGroup SSMMessagesEndpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'     VpcId: !Ref VPC     VpcEndpointType: Interface     PrivateDnsEnabled: true     SubnetIds:       - !Ref PrivateSubnet       - !Ref PublicSubnet     SecurityGroupIds:         - !Ref EndpointSecurityGroup Outputs: PublicEC2InstanceId:   Description: 'Public EC2 Instance ID'   Value: !Ref PublicEC2Instance PrivateEC2InstanceId:   Description: 'Private EC2 Instance ID'   Value: !Ref PrivateEC2Instance PublicEIP:   Description: 'Elastic IP for Public EC2 Instance'   Value: !Ref PublicEIP AWS環境用テンプレート(AWS_resource.yaml) AWSTemplateFormatVersion: '2010-09-09' Description: 'VPC with Private Subnet, EC2 Instance, Site-to-Site VPN, and S3 Endpoint' Parameters: TagPrefix:   Type: String   Default: '00000'   Description: 'Prefix for resource tags' CustomerGatewayIp:   Type: String   Description: 'Public IP address of your Customer Gateway' AmazonLinux2023AMI:   Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>   Default: '/aws/service/ami-amazon-linux-latest/al2023-ami-kernel-6.1-x86_64'   Description: 'Amazon Linux 2023 AMI ID' Resources: VPC:   Type: AWS::EC2::VPC   Properties:     CidrBlock: 192.168.0.0/22     EnableDnsHostnames: true       EnableDnsSupport: true     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-VPC'   PrivateSubnet:   Type: AWS::EC2::Subnet   Properties:     VpcId: !Ref VPC     AvailabilityZone: !Select [0, !GetAZs '']     CidrBlock: 192.168.0.0/24     MapPublicIpOnLaunch: false     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-Private-Subnet'   PrivateRouteTable:   Type: AWS::EC2::RouteTable   Properties:     VpcId: !Ref VPC     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-Private-RT'   PrivateSubnetRouteTableAssociation:   Type: AWS::EC2::SubnetRouteTableAssociation   Properties:     SubnetId: !Ref PrivateSubnet     RouteTableId: !Ref PrivateRouteTable   VPNSecurityGroup:   Type: AWS::EC2::SecurityGroup   Properties:     GroupName: !Sub '${TagPrefix}-VPN-SG'     GroupDescription: 'Security group for VPN connection and SSM'     VpcId: !Ref VPC     SecurityGroupIngress:       - IpProtocol: -1         FromPort: -1         ToPort: -1         CidrIp: 10.0.0.0/22       - IpProtocol: tcp         FromPort: 443         ToPort: 443         CidrIp: 192.168.0.0/22     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-VPN-SG'   EC2Instance:   Type: AWS::EC2::Instance   Properties:     InstanceType: t3.micro     ImageId: !Ref AmazonLinux2023AMI     SubnetId: !Ref PrivateSubnet     SecurityGroupIds:       - !Ref VPNSecurityGroup     IamInstanceProfile: !Ref EC2InstanceProfile     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-AWS-Private-EC2'   EC2InstanceProfile:   Type: AWS::IAM::InstanceProfile   Properties:     Path: "/"     Roles:       - !Ref EC2SSMRole   S3Endpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     VpcId: !Ref VPC     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.s3'     VpcEndpointType: Gateway     RouteTableIds:       - !Ref PrivateRouteTable   EC2SSMRole:   Type: AWS::IAM::Role   Properties:     AssumeRolePolicyDocument:       Version: "2012-10-17"       Statement:         - Effect: Allow           Principal:             Service:               - ec2.amazonaws.com           Action:             - sts:AssumeRole     ManagedPolicyArns:       - arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore       - arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-EC2-SSM-Role'   CustomerGateway:   Type: AWS::EC2::CustomerGateway   Properties:     Type: ipsec.1     BgpAsn: 65000     IpAddress: !Ref CustomerGatewayIp     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-CustomerGateway'   VirtualPrivateGateway:   Type: AWS::EC2::VPNGateway   Properties:     Type: ipsec.1     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-VPNGateway'   VPNGatewayAttachment:   Type: AWS::EC2::VPCGatewayAttachment   Properties:     VpcId: !Ref VPC     VpnGatewayId: !Ref VirtualPrivateGateway   VPNConnection:   Type: AWS::EC2::VPNConnection   DependsOn:     - VirtualPrivateGateway     - VPNGatewayAttachment   Properties:     Type: ipsec.1     CustomerGatewayId: !Ref CustomerGateway     VpnGatewayId: !Ref VirtualPrivateGateway     StaticRoutesOnly: true     Tags:       - Key: Name         Value: !Sub '${TagPrefix}-VPNConnection'   VPNConnectionRoute:   Type: AWS::EC2::VPNConnectionRoute   DependsOn: VPNConnection   Properties:     DestinationCidrBlock: 10.0.0.0/22     VpnConnectionId: !Ref VPNConnection   VPNRoute:   Type: AWS::EC2::Route   DependsOn: VPNGatewayAttachment   Properties:     RouteTableId: !Ref PrivateRouteTable     DestinationCidrBlock: 10.0.0.0/22     GatewayId: !Ref VirtualPrivateGateway   SSMEndpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     VpcId: !Ref VPC     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssm'     VpcEndpointType: Interface     PrivateDnsEnabled: true     SubnetIds:       - !Ref PrivateSubnet     SecurityGroupIds:       - !Ref VPNSecurityGroup   EC2MessagesEndpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     VpcId: !Ref VPC     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ec2messages'     VpcEndpointType: Interface     PrivateDnsEnabled: true     SubnetIds:       - !Ref PrivateSubnet     SecurityGroupIds:       - !Ref VPNSecurityGroup   SSMMessagesEndpoint:   Type: AWS::EC2::VPCEndpoint   Properties:     VpcId: !Ref VPC     ServiceName: !Sub 'com.amazonaws.${AWS::Region}.ssmmessages'     VpcEndpointType: Interface     PrivateDnsEnabled: true     SubnetIds:       - !Ref PrivateSubnet     SecurityGroupIds:       - !Ref VPNSecurityGroup Outputs: EC2InstanceId:   Description: 'EC2 Instance ID'   Value: !Ref EC2Instance CustomerGatewayId:   Description: 'Customer Gateway ID'   Value: !Ref CustomerGateway VirtualPrivateGatewayId:   Description: 'Virtual Private Gateway ID'   Value: !Ref VirtualPrivateGateway VPNConnectionId:   Description: 'VPN Connection ID'   Value: !Ref VPNConnection 環境構築手順 なるべく少ない手数で再現性のある構築を進めるため、CloudShellでAWS CLIを用いて作業します。 AWSマネジメントコンソールの上部または左下の「>_」マークをクリックします。 立ち上がってきたCloudShell画面で「Open <リージョン名> environment」をクリックします。 Shellを打ち込める画面が出てきます。 # タグのプレフィックスを設定(任意の文字列を指定してください) TAGPREFIX=00000 # オンプレ側構築 aws cloudformation deploy \     --stack-name "VPNtest-Onpre-${TAGPREFIX}" \     --template-file Onpre_resource.yaml \     --capabilities CAPABILITY_IAM \     --parameter-overrides TagPrefix=${TAGPREFIX} # オンプレ側のVPN機器となるEC2インスタンスのGlobalIPを取得 GLOBALIP=`aws ec2 describe-addresses --query 'Addresses[].PublicIp' --filter "Name=tag:Name,Values=${TAGPREFIX}-Public-EIP" --output text` && echo ${GLOBALIP} # AWS側構築 aws cloudformation deploy \     --stack-name "VPNtest-AWS-${TAGPREFIX}" \     --template-file AWS_resource.yaml \     --capabilities CAPABILITY_IAM \   --parameter-overrides TagPrefix=${TAGPREFIX} CustomerGatewayIp=${GLOBALIP} VPN設定 AWS Site-to-Site VPNではオンプレ側VPN機器の設定サンプルをダウンロードできます。 オンプレ側ではソフトウェアVPNのLibreswanを使用しますが、該当するものがないので類似するOpenswanの設定サンプルをダウンロードします。 VPN設定ダウンロード [VPC] – [Site-to-Site VPN 接続] [設定をダウンロードする] [ベンダー] Openswan [プラットフォーム] Openswan [ソフトウェア] Openswan 2.6.38+ [IKEバージョン] ikev1 を指定し、[ダウンロード] ここでダウンロードした設定ファイルの一部を用いて、Libreswanの設定をしていきます。 Libreswan設定 LibreswanからAWS Site-to-Site VPNへの接続に利用するトンネルは1本のみとします。(2本用いるHA構成も可能だが「気軽な」検証にならないため) AWS Systems Manager Session Managerを用いてオンプレ側Public EC2インスタンスにログインします。 [EC2] – [インスタンス] より、ログインしたいインスタンスにチェックを入れ、[接続] をクリックします。 [セッションマネージャー] タブを選択し、[接続] をクリックします。 # Libreswanインストール sudo dnf install -y libreswan # カーネルパラメータ修正 echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.d/custom-ip-forwarding.conf sudo sysctl -p /etc/sysctl.d/custom-ip-forwarding.conf # Libreswan設定 sudo vi /etc/ipsec.conf config setup ブロックに下記設定のみを追記します。         protostack=netkey # ダウンロードしたVPN設定から接続部分を抜き出して貼り付け、内容を書き換える # Tunnel1の部分のみ。 sudo vi /etc/ipsec.d/aws.conf conn Tunnel1 authby=secret auto=start left=%defaultroute leftid= <オンプレ側のGlobalIP>   # 記載のものをそのまま入力 right= <AWS側のGlobalIP>   # 記載のものをそのまま入力 type=tunnel ikelifetime=8h keylife=1h phase2alg=aes128-sha1; modp2048  # 末尾のmodp1024をmodp2048に変更 ike=aes128-sha1; modp2048        # 末尾のmodp1024をmodp2048に変更 auth=esp                      # この行は削除 keyingtries=%forever keyexchange=ike leftsubnet=10.0.0.0/22          # leftがオンプレ側(今設定しているほう) rightsubnet=192.168.0.0/22      # rightがAWS側 dpddelay=10 dpdtimeout=30                   # この行は削除 retransmit-timeout=30s          # この行を追加 dpdaction=restart_by_peer # シークレット情報を張り付ける # ※Tunnel1もののみをそのまま貼り付け、Tunnel2のキーは貼り付けない sudo vi /etc/ipsec.d/aws.secrets <オンプレ側のGlobalIP> <AWS側のGlobalIP> : PSK " <キー情報> " sudo systemctl start ipsec sudo systemctl status ipsec exit VPNトンネル接続確認 [VPC] – [Site-to-Site VPN] 作成したVPN接続を選択 [トンネルの詳細] タブを選択 1つのトンネルが接続されていることを確認します。(リロードしながら少し待つ必要があります) もう一つはダウンのままで構いません。 接続確認 IPアドレスを確認 TAGPREFIX=00000 aws ec2 describe-instances \     --filters "Name=instance-state-name,Values=running" "Name=tag:Name,Values=*${TAGPREFIX}*" \     --query "Reservations[*].Instances[*].[PrivateIpAddress, Tags[?Key=='Name'].Value | [0]]" \   --output text 以下のようにIPアドレスが出力されます。このIPアドレスを用いて接続確認をしていきます。 10.0.0.148      00000-Onpre-Private-EC2 192.168.0.34   00000-AWS-Private-EC2 10.0.1.145      00000-Onpre-Public-EC2 通信確認 pingによる通信確認 AWS Systems Manager Session Managerを用いて オンプレ側プライベートEC2 へログインします。 # AWS側プライベートEC2のIPに向けて通信確認 ping 192.168.0.34 →オンプレ側からAWS側へ通信できることを確認します。 同じく、 AWS側プライベートEC2 へログインします。 ping 10.0.0.148 →AWS側からオンプレ側へ通信できることを確認します。 HTTPによる通信確認 AWS側プライベートEC2 で下記を実行します。 cd ~ touch iam_aws_private python3 -m http.server 8000 今度は、 オンプレ側プライベートEC2 で下記を実行します。 curl 192.168.0.34:8000 →ディレクトリの中身が見えることを確認します(VPN接続先のiam_aws_privateファイルが見える) AWS側プライベートEC2で、オンプレ側からアクセスがあったログを確認できます。 オンプレ側の通信確認 AWS側の通信確認 トラブルシューティング VPN接続で問題が発生した場合は、以下の点を確認してください セキュリティグループの設定 ルートテーブルの設定 Libreswanの設定ファイル VPNログ(sudo journalctl -u ipsec) クリーンアップ 検証が終わったら、以下のコマンドで環境を削除します # CloudFormationスタックを削除 aws cloudformation delete-stack --stack-name "VPNtest-Onpre-${TAGPREFIX}" aws cloudformation delete-stack --stack-name "VPNtest-AWS-${TAGPREFIX}" # スタック削除を待つ aws cloudformation wait stack-delete-complete --stack-name "VPNtest-Onpre-${TAGPREFIX}" & aws cloudformation wait stack-delete-complete --stack-name "VPNtest-AWS-${TAGPREFIX}" & wait # 2つのスタックが削除され、プロンプトが戻れば終了 CloudShellの終了は、CloudShell画面の [アクション] – [削除] を選択し、表示された確認画面で「delete」と入力し、[削除] ボタンを押します。 参考資料 ソフトウェア VPN から AWS Site-to-Site VPN https://docs.aws.amazon.com/ja_jp/whitepapers/latest/aws-vpc-connectivity-options/software-vpn-to-aws-site-to-site-vpn.html OS設定でNATインスタンスのチュートリアルを参考にしました(特にnet.ipv4.ip_forward=1の設定) https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/work-with-nat-instances.html SSMへの接続用VPCエンドポイントを作成の際、ec2messages:* エンドポイントは作成の必要がなくなりました。 https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-setting-up-messageAPIs.html SSM Agent のバージョン 3.3.40.0 以降、Systems Manager は、使用可能な場合には ec2messages:* エンドポイント (Amazon Message Delivery Service) の代わりに ssmmessages:* エンドポイント (Amazon Message Gateway Service) を使用するようになりました。   まとめ 今回の記事では、AWSでのSite-to-Site VPN接続の検証環境構築方法を紹介しました。CloudFormationを使うことで、複雑な環境も簡単に構築・管理できることがお分かりいただけたかと思います。 実際の本番環境では、セキュリティやパフォーマンスなどさらに考慮すべき点がありますが、この記事がVPN検証の参考になれば幸いです。
アバター
こんにちは、佐藤と申します! 今回が記念すべき初執筆! ということでつたない点もあるとは思いますが、少しでも面白いと思っていただける記事を書けるよう頑張ります! さて、今回は昨年末のAWS re:Inventにて、2025年からすべてのユーザーがクレジットカードなしで毎日無料で使えるようになるという素晴らしい発表があった PartyRock が主役。 PartyRockに理想のデートプランを考えさせてみた結果が……というお話です。ぜひ最後までお読みいただければ幸いです。   きっかけはこんな記事から… 56.4%――――。 これ何の数字かわかりますか? マイナビウーマンさんが『マイナビウーマン』にて2014年11月にとったWebアンケートによると、 56.4%の人がデートプランを前もって決めたい派 なんだそうです(参考 https://woman.mynavi.jp/article/141206-7/ )。皆さんはこの数字をどのように捉えるでしょうか? 確かにデートプランが事前にわかることでその日1日をどう過ごすのか、どんな服装でどこに集合すればいいのかを逆算することができますよね。 でも正直、 「デートプランを考えるの、めんどくさいんだよな…」 って方も少なくないのではないでしょうか? それならば!お手伝いしてもらいましょう! 誰に…? もちろんPartyRockにです!   ちなみにPartyRockってなに…? 初めて聞いた方のために、PartyRockについて簡単に補足を入れておきます。 PartyRockとは、 AWSが提供する生成AIアプリケーションをプロンプトのみ、ノーコードで作成することのできるサービス です。 こちらの公式ドキュメントから詳しい使い方等は確認いただけます。→  https://aws.amazon.com/jp/blogs/news/create-generative-ai-app-with-partyrock-amazon-bedrock/ PartyRockは、同じくAWSが提供する生成AIサービスであるAmazon Bedrockと統合されているため、 さまざまなAIモデルを使用することができ 、基本操作は テキスト入力 とUIをいじるための ドラッグアンドドロップ が主となんとも使いやすい仕様なのが特徴です。 2024年までは無料トライアル期間を設けており、その期間で限られたトークン分しか使用することができませんでしたが、上述の通り、 昨年末のAWS re:Inventでトライアル期間を気にせずに使用できることが発表されました。 →  https://aws.amazon.com/jp/about-aws/whats-new/2024/12/partyrock-app-discovery-upcoming-free-daily-use/ まさに触ってみなきゃ損!なサービスと言っても過言ではないのがPartyRockなんです。 PartyRockを最強のデートプランナーにしてみる PartyRockにログインしてみよう そうと決まれば、早速PartyRockにログインしてみましょう。 PartyRockへはこちらのURLからアクセスすることができます→  https://partyrock.aws/ 左上の 「Log in」 ボタンをクリックするとこのようなログイン画面が表示されます。 PartyRockへのログインは、 Googleアカウント 、 Appleアカウント からでも可能です! AWSアカウントをまだお持ちでない方でも試せるのはありがたいですよね。 早速ログインができたら、 「Generate app」 ボタンからアプリを作っていきましょう。   プロンプトからアプリを作成しよう するとアプリ構築のベースとなるプロンプトを入力する画面が表示されるので、大まかな目的や、ほしい機能を記載していきましょう。 今回はこんなプロンプトを作成してみました。 あなたは世界一のデートプランナーです。絶対に失敗できないデートがあります。相手の心をつかみ、二人の距離を一気に縮めるような絶対に成功するデートプランを作成してください。当日の詳細な流れ、おすすめスポット、会話のヒントを具体的かつ分かりやすく記述してください。 場所:@場所 予算:@予算 相手の趣味:@相手の趣味 キーワード:@キーワード(例:ロマンチック、アウトドア、サプライズ、音楽) 時間帯:@時間帯 ちなみに後半に一部入っている 「@」 があると思いますが、これは場所~時間帯までの要素が 変数(プレイスホルダー) であることをAIに明示するためのものです。 プロンプトを受け取るAI側は、これらがユーザーが定義した値が入るものとして結果を出力します。 これによりユーザーは変数に好きな条件を入れてデートプランを作成することができるようになるのです。 さて、30秒ほど待つと具体的なデートプランを考えるためのユーザー入力画面が出力されます。 先ほど指示した場所から時間帯まで5つの入力項目があるので、今回はこんな条件を入れてみました。 デートの場所:東京。できるだけ歩いて移動できる距離がいいかな。 予算:30000円くらい。 相手の趣味:音楽。音楽に関するデートができたら理想的。 キーワード:ロマンチック、サプライズ 時間帯:終日。お昼ごろから夜までで考えてみて。   予算30,000円はさすがにやりすぎましたかね…笑 私は普段からかなり音楽を聴くので、今回は音楽に関連するデートということでPartyRockにお願いしてみました。 果たしてその結果は…… 理想のデートプラン、その結果は…? PartyRock、只者ではないな……? まずは今回のデートの概要から。 音楽をテーマにした、ロマンチックで思い出に残る東京デートプランです。 昼から夜にかけて、音楽関連スポットを巡り、ライブ演奏を楽しみ、最後はサプライズで締めくくります。 お、なかなか悪くないですね。 これはかなり良い出力を期待してしまいます。 次が最も大切。1日のスケジュールです。 12:00 – 渋谷駅集合 12:15 – タワーレコード渋谷店でショッピング 13:30 – 代官山のカフェでランチ 15:00 – 恵比寿ガーデンプレイスでストリートミュージシャン鑑賞 16:30 – 目黒川沿いを散歩 18:00 – 中目黒のジャズバーでディナーとライブ 21:00 – 東京タワーで夜景鑑賞 22:30 – デート終了 思いのほかよさそうな気がしますね…笑 てっきり「いや、場所東京じゃないんかい!!」ってツッコミが必要かと思っていたのでこれは意外でした。 時間帯も昼から夜にかけてと完璧な出来です。 よくみると15時から恵比寿ガーデンプレイスで1時間近くストリートミュージシャンを眺める謎の時間発生してたりしますが…… しかもPartyRock、具体的な場所まで提案してくれているんです。 例えば13:30からの代官山のカフェでのランチ。 ここでは実際に渋谷に存在する「 茶亭 羽當」さんを紹介していました。 茶亭 羽當さんについてはこちらから→   https://tabelog.com/tokyo/A1303/A130301/13001169/ ちなみにPartyRockいわく、 おしゃれな雰囲気で 軽いランチを楽しめる BGMも洗練されており、 音楽好きにぴったり とのことで、音楽にもきちんと絡めてくれています。すごい…… しかし思いがけない落とし穴が… PartyRockに感心するのも束の間、1つ 大きな落とし穴 がありました。 それが、 移動距離 です。 今回のデートでは、「できるだけ歩いて行ける距離で」という条件を付けました。 実際にPartyRockが提案してくれたルートを調べてみると… 総移動距離、驚異の 14.2km 。 約3時間半 を徒歩移動に費やす計算になります。ちょっと歩けないかも…… 特に遠かったのが18時半から行く予定のジャズバー。 ということでPartyRockに場所を変えてほしいと要望を伝えてみると… なんと新たに3か所、候補となるジャズバーを紹介してくれました! ですが、なぜかすべて中目黒。なぜか渋谷方面には寄ってくれませんでした…。 PartyRockではこんな設定も可能なんです ちなみにPartyRockが最初に出力してくれたユーザー画面ですが、これも プロンプトを変更して出力を調整することも可能 です。 さらには 使用するAIモデルまで選択可能! デフォルトはClaudeになっていますが、Amazon Novaに変更して出力してみるとこんな感じになりました。 12:00 – 渋谷駅集合 12:15 – タワーレコード渋谷店でショッピング 13:30 – 代官山のカフェでランチ 15:00 – 恵比寿ガーデンプレイスで ウィンドウショッピングと散策 17:00 – 代官山蔦屋書店で音楽関連の本を探索 18:30 – 中目黒のイタリアンレストランでディナー 20:30 – 中目黒川沿いの散歩とサプライズライブ 何かあまり変わっていないような…笑 恵比寿ガーデンプレイスでショッピングを追加してくれたことは救いでしょうか…? また、 作ったアプリケーションはURLで共有することも可能 です。 URLで簡単に共有ができるお手軽さも素晴らしいです! 結論、PartyRockは… 実際にPartyRockを使ってみて、 生成AIサービス、特にアプリケーションを作成する類の中ではかなり使いやすい なというのが正直な感想です。 日本語対応 していて UIもかなり使いやすい 、 URLで簡単に共有 できる点も利用までの心理的ハードルが低くなるポイントだったと感じています。 そして何より一番の気づきは、 PartyRockは中目黒がおしゃれの頂点だと思っている説 …笑 何度か試しましたが渋谷・目黒エリアしか出してくれず、思いがけずPartyRockの好みまでわかってしまいました。 最後まで読んでいただきありがとうございました。 少しでも興味を持った方はぜひPartyRockでアイデアを形にする楽しさを味わってみてはいかがでしょうか?
アバター
こんにちは。SCSK株式会社の上田です。 Zabbixに関する、 深刻な脆弱性 が発表されました。 この脆弱性は、 Zabbixのバージョン5.0.45、6.0.33、6.4.18、7.0.3までのバージョン に影響します。 Zabbixをご利用の方は一度ご覧いただき、対象の場合はアップデート等のご検討お願い致します。 脆弱性情報 概要 HTTPリクエストオブジェクトのサーバーレスポンスヘッダー取得機能に脆弱性があり、受信したレスポンスのヘッダー内にある一部文字列の安全性チェックに不足があります。ヘッダー内に悪意のある文字列が入力されることにより、 Zabbixサーバ上でメモリの内容を読み取ったり、任意のコードを実行する恐れ があります。 対象コンポーネント Zabbixサーバ 対象バージョン 以下の Zabbix バージョンが影響を受けます。 Zabbix 5.0.0 – 5.0.45 Zabbix 6.0.0 – 6.0.33 Zabbix 6.4.0 – 6.4.18 Zabbix 7.0.0 – 7.0.3 影響 攻撃者が細工したHTTPヘッダーを送信することで、サーバー上の機密情報にアクセスしたり、任意のコードを実行したりする可能性があります。 回避方法 この脆弱性を回避するためには、Zabbixの最新バージョンにアップデートすることが推奨されます。 Zabbix 5.0.46以降 Zabbix 6.0.34以降 Zabbix 6.4.19以降 Zabbix 7.0.4移行 最後に 弊社では本件に関してのお問い合わせもお受けしております。 ご相談事項がございましたら、以下ページよりお問い合わせいただければと思います。 お問い合わせ 製品・サービスについて 入力 | SCSK株式会社 SCSK株式会社 製品・サービスについてご意見・ご質問をお受けしております。 www.scsk.jp また、弊社では Zabbixのバージョンアップの支援 も行っております。 ご興味のある方は、是非弊社までお問合せください。 最後まで読んでいただき、ありがとうございました。 弊社ではZabbix関連サービスを展開しています。以下ページもご参照ください。 ★Zabbixの基礎をまとめたeBookを公開しております!★ FAQ・お問い合わせ|SCSK Plus サポート for Zabbix 導入をご検討される際、よくあるご質問と回答についてまとめております。 www.scsk.jp ★SCSK Plus サポート for Zabbix★ SCSK Plus サポート for Zabbix 世界で最も人気のあるオープンソース統合監視ツール「Zabbix」の導入構築から運用保守までSCSKが強力にサポートします www.scsk.jp ★YouTubeに、SCSK Zabbixチャンネルを開設しました!★ SCSK Zabbixチャンネル 本チャンネルでは、SCSK株式会社でのZabbixに関するトレンド/事例紹介などを動画にまとめて取り上げております。最新のトピックについては、以下の弊社HPもしくはツイッターアカウントをぜひ参照ください。ツイッターアカウント: www.youtube.com ★X(旧Twitter)に、SCSK Zabbixアカウントを開設しました!★ x.com x.com
アバター
はじめに いきなりですが、パフォーマンスチューニングの極意からお話しましょう。 パフォーマンスチューニングとは、「一定の制約の中で、希望の結果を選び取ること」と考えると、我々人類が日々行っている生存競争と何ら変わりがありません。日々生存競争している、すなわち、常在戦場。つまり戦場の極意=パフォーマンスチューニングの極意でもあるということにほかなりません。 ここで、歴史上の偉人に登場してもらって、戦場での極意を語ってもらいましょう。 孫子曰く、 「敵を知り己を知らば百戦あやうからず」 \孫子言いたいだけだろ/ ハイ、その通りです。 敵?己? 今回は、Webサイトのパフォーマンス・チューニングを例としてチューニングを行います。 敵と己にわけて考えます。別に悪意があって敵とするわけではなくて、自分の努力、パフォーマンスチューニングではどうしようもないものを敵と捉えます。自分の手の届く範囲の外にあるもの、と、捉えてもいいですね。 逆に自分でどうにかできるものや、できそうなものを己とします。 敵 Webサーバーに殺到するお客様、あるいはWebサーバーを乗っ取ろうとする攻撃者 己 Webサーバーの特性、Webサイトのアーキテクチャー構成、ネットワーク構成、Webサーバーやシステムの得意技、苦手とするところ、業務要件、制約事項、コンテンツの特性、etc… こうやって見ると己はすごく多いですね。 パフォーマンス・チューニングの流れ パフォーマンス・チューニングの流れとしては、下の図のようになります。 左上のスタートから始まって、ぐるっと回るような流れになっています。   データ収集 データ分析 結果判定 ボトルネック特定 ボトルネック改善 測定試験実行 順に見ていきましょう。 1. データ収集 「Webサイトの表示が重くなってきて、最終的に表示されなくなった」「イベントのときにいきなり落ちた」など、訴え事象やお客様からみた状態「だけ」では、Webサイトの中で何が起こっているのか全くわかりません。まずは状況を客観的に判断するためのデータを収集します。 Webサイトであれば、 Webサーバのアクセスログ エラーログ(Webサーバーが 4xx,5xx を返却したログ、あるいはアプリケーションが異常時に出力したログ) EC2 や RDS を利用している場合は、CPU利用率や、メモリ利用率 Akamai, CloudFrontな どのコンテンツデリバリーネットワーク(CDN)サービスを利用しているのではあれば、その統計データ などのデータがあると思います。そういったデータの中から、 異常発生時の状態のログ 普段の正常な状態のログ できれば異常発生時と同じようなイベントの際のデータと、イベントのない平常時の同じ曜日時間帯など比較できる状況別にあると望ましい と両方を比較できるようにデータを取得します。 そういったデータがそもそも取得されていないのであれば、まずは取得するところからになってしまいますが、何が起こっているのか全くわからない状況ではどうすればよいのか判断できないので、早速取得を開始しましょう。最低限上記のログと、アプリケーションのログを出力して取得する必要があります。 ある時点でWebサイトがお客様からどのように見えていたかを常時確認するのは困難なため、自動化がおすすめです。 CloudWatch Synthetics Canary などを使って定期的に観測しましょう。後述の試験の際にも利用できます。 合成モニタリング (canary) 合成モニタリング (canary) - Amazon CloudWatch 模擬モニタリングを使用して Synthetics と X-Ray トレースマップで canary を作成し管理する方法について説明します。 docs.aws.amazon.com 2. データ分析 取得したデータを分析します。 不具合が発生した時間帯の前後のエラーログの出力や、傾向の変化や正常時との差分から「己」側に 何が起こって、何が原因となってパフォーマンスが落ちてしまっていたのかをあぶり出します。 HTTP の応答が 500 系なら、サーバー側のプロセスが異常終了してしまい応答が返せなかった、あるいは、アクセス集中により、応答を時間内に返せなかったと想定 HTTP の応答が 403 や 404 ならWebサイトやアプリケーションの設定が誤っていて、案内してはいけないページに案内している可能性あり Windows の IIS などを利用している場合は、Windowsイベントログ、Linuxサーバーであれば、 /var/log/messages などのシステムログにエラーが出力されていないかどうかを確認 一分あたり、OOアクセスを超えたあたりからエラー応答が出るようになったとなると、アクセスが多くなりサーバーのリソースが足りなくなってしまった可能性 サーバーのCPU使用率が普段は20%~40%なのに、不具合発生時は 80%を超えていた 不具合発生時はサーバーのメモリ使用率が 90%を超えていた 静的なコンテンツで、CDNを利用している場合は、不具合が発生しているページのキャッシュヒット率を確認します。静的なコンテンツなのにキャッシュヒット率が低い場合、CDNの設定が誤っていて実は全員がサーバーまで取得しに来ているというケースがあるかも などです。 同時に「敵」側であるアクセス傾向についても分析します。この分析は、再現試験や、対策後の確認試験、Webサイト自体の目標設定の際にも利用できます。 平常時であれば、Webサイトには一分間あたり平均OO件、最大でもOOO件程度のアクセス 不具合発生直前は、正常、エラー合わせてOOOO件のアクセスが発生していた 不具合から回復した直後は、OOO件程度でエラーも発生していなかった など。 こういった複数の状況から、矛盾しない「仮説」を立てていきます。 例えば、 イベント開始直前からWebサイトの応答が遅くなったように感じられ、イベント開始時間からは エラーしか帰ってこなくなった、30分くらいしたらアクセスできた というような訴え事象に対して、 イベント開始直前のサーバーのログにリソースが不足した旨のエラーメッセージが出ていた その直後サーバーのログにはイベント開始時間あたりに再起動の履歴が残っていた イベント開始直前から5xx系のエラーログが増え始め、イベント開始30分をすぎるまで5xx系のエラーログが普段より多めに出力された というログが見つかった場合 イベント開始直前からアクセス集中により、Webサーバー側でリソースが不足し、Webサーバーの自動再起動が発生した。Webサーバーが再起動中であったために、ユーザーはイベント開始後Webサイトを表示できなかった。Webサーバの自動再起動が完了した後、時間経過に伴いユーザーのアクセス数が少なくなっててきたので、ユーザーはWebサーバーにアクセスできるようになった という仮説がたてられます。うまく仮説を立てられない場合は、必要なデータが揃っていないことも考えられます。別観点のデータを収集する設定を追加投入する必要があるかもしれません。 例えば、「リソース不足」が発生したことはログに記録されているのでわかったが、そのリソースの具体的な数値を示すメトリクスデータが存在しないのであれば、何らかの方法でログに出力する方法を検討する必要があるかもしれません。 具体的には小さなアプリケーションを作成して定期的にログに出力するよう機能追加する。あるいはよく使われる処理内にそのリソースのメトリクスをログに出力する処理を埋め込む、などです。 3. 結果判定 結果判定フェーズでは分析したデータから、これで良いのかどうか判断します。 初回は不具合が発生しているので、当然NGです。しかし、修正、改善するとして、どこまですればよいかというのは線引きが必要です。 「己」のあるべき姿を再定義するわけです。それと同時に、「敵」の想定も明確にしておきます。 多少エラーリターンが返っても、Webサーバーが落ちなくなればよいのか、Webサーバーが落ちないのは当たり前で、イベント時でもユーザーが重たく感じない程度の時間で応答が帰ってきてほしいなど、許容範囲は幅があると思います。「重たく感じる」だと人によって曖昧になってしまうので「Webブラウザに何も表示されない状態が3秒位内」「最初の応答が返ってくるまでの時間が 3秒 以内が 95%以上、残り5%も10秒を超えないこと」など、数値化しておきます。 アクセス数も不具合発生時のアクセス数が想定以内だったのか、あるいは、想定以上だったのか、今回対策するとして、一分あたり、あるいは一秒あたり何アクセス以内であれば性能目標通りに動作するのかを決めておく必要があります。イベントのアクセス集中には耐えるというのと、企業を狙った大規模なDDoS攻撃にも耐える必要がある、では対策の手段も方法も違ってきます。 パフォーマンス・チューニングとしての目標ですが、信頼性、可用性の目標も同時に達成する必要があります。「いつもは性能が出るけど、忙しくなると落ちる」「イベント時は50%はエラーになるけど50%はOKが返る」というチューニング結果ではNGですよね? コストの面も関わってきます。パフォーマンス・チューニングはコストとトレードオフになる面も多くあります。「絶対にWebサイトを落とさないように、Webサーバーを今の50倍の台数にしたい!」あるいは、「今のWebサーバーの50倍の性能のサーバーと全て置き換えたい!」といってもコストの観点で無理なのはなんとなくわかると思います。Webサイトのシステム自体が何らかの利益を稼ぎ出すような通販サイトの場合でも、販売促進用の宣伝広告サイトだとしても、Webサイトの維持費が販売して得た利益を上回ってしまう場合はWebサイトのせいで赤字になってしまっては問題でしょう。 4. ボトルネック特定 結果がNGなら、分析と仮説を元にボトルネックを特定します。 例えば以下のように、ユーザーに近い側から「己」の処理を順に見ていきます。「敵」が、どの地点に集中した結果守りきれなかったのか、地点を特定します。 守りきれなかった地点では大抵なんらかの異常を示すログなどが集中して出力されています。ログを記録した地点というよりはログの出元を特定します。例えば、 CDN のログには Webサーバーからの応答に 5xx 系エラーがたくさん。その時間帯にCDNサービス自体の不具合報告は出ていない。関連するURLの静的コンテンツのキャッシュヒット率は低い CDNサービス自体の不具合報告は無いので、5xx系エラーの出元はCDNではなくWebサーバー Webサーバー はリソース不足で再起動 自身も5xx系のエラーも出している DBサーバーはCPU使用率もデータ転送もメモリ使用率も、同時期同時間帯の問題のない時間帯と比較しても何も変わらない の場合には、Webサーバーのリソース不足がボトルネックになった、と特定できます。 5. ボトルネック改善 前の工程で特定したボトルネックを改善します。次のいずれかの方法になります。 ボトルネックを太くする ボトルネックを通過する処理を詰まらない状況まで減らす ボトルネックを太くするというのは、ボトルネック自体を太くしてボトルネックでなくしてしまおうとする方法です。今回の例では、リソース不足となった「Webサーバーの台数を増やす」ということになります。普段は問題がないので、常時台数を増やしておく必要はなさそうです。イベント開始前にだけあらかじめWebサーバーを増やしておいて、イベント終了後は下の台数に戻す、といった対策でコスト増を最小限にします。 ボトルネックを通過する処理を詰まらない状況まで減らすというのは、ボトルネックが細くても、そこを通過する処理をボトルネックに詰まらない程度まで少なくすることにより詰まらなくするという方法になります。例えば、Webサーバー上にある静的コンテンツの一部を S3 バケットなどに移動すると、Webサーバー宛のリクエストでなく、S3に対するアクセスとなるので、Webサーバーの負担が減ります。 上記の例ではその上流の CDN において「関連する静的コンテンツのキャッシュヒット率は低い」という分析結果が出ています。CDN でのキャッシュヒットしなかったアクセスはWebサーバーに流れてくる為、ボトルネックとなっているWebサーバーが処理することに繋がります。CDNの設定を適切に変更して 静的コンテンツが正しくキャッシュされるようにすることで、Webサーバーが担当する処理を少なくします。 ボトルネックが改善されると、ボトルネックが別の位置に移動する可能性があります。今までボトルネックで問題が起きていたために一定数以上の処理が来なかった状態であったのが、ボトルネックが解消されたために、その下流側に大量に処理が流れてきて新たなボトルネックになったり、あるいは、広くなった旧ボトルネックよりも手前側で先に詰まってしまうような状況が発生する可能性もあります。ただし、ボトルネックに関連しない修正はあまり効果を期待できません。それに手当たり次第に検討/修正するとなると手間暇もお金も時間も無駄にかかってしまいますので、まずはボトルネックの修正に集中します。 ボトルネックの改善により、システム全体が性能を満たすのに十分なほど改善したのか、あるいは、別の箇所が新たなボトルネックとなって性能要件をまだ満たせないのかを確認するために、次の測定試験を実行します。 6. 測定試験実行 対策が正しく効果を表しているかどうかを試験を行って確認します。 不具合の報告があった時間帯と同じようなアクセスを生成させて動作を確認します。不具合が発生するような状況を作り出すので、本番環境ではなく、試験環境で行います。Webへの大量アクセスはツールを使わないと難しいので、試験ツールを使用します。すでに使用している試験ツールがあるのであればそのツールで試験を行なえばよいのですが、現状利用したことがない、あるいは、そういったツールが無い場合は、以下のAWSのソリューションを利用できます。 AWS での分散負荷テスト AWS での分散負荷テスト | AWS ソリューション | AWS ソリューションライブラリ 大規模な負荷時のソフトウェアアプリケーションテストを自動化して、リリース前に潜在的な性能上の問題を特定できます。 aws.amazon.com このままではシステムやビジネスの制約条件等で使用できない場合にしても個々のパーツやツールの使われ方などが参考になると思います。 おわりに いかがでしたでしょうか。 パフォーマンス・チューニングというのは状況により実施する必要のある改善というのはシステムによって、利用するシステム構成、ミドルウェア、OS、ハードウェアや使用言語、制約事項、性能目標、可用性目標、信頼性目標、予算、想定アクセス数等が異なるために、「これをすれば全部のシステムでOK!」というような具体的な手段がなく、抽象的な言い回しに終始してしまった感があります。ただ、「ボトルネックを特定し改修するループを目標達成まで回す」という方法論はどのシステムのパフォーマンス・チューニングを行うにあたっても利用できる考え方だと思います。 この記事が皆さんのパフォーマンス・チューニングの参考になれば幸いです。
アバター
どうも、DeepRacer をやっていて強化学習のイメージがつくので良かったなと思っている寺内です。 2025年1月20日、中国の杭州にあるスタートアップ「DeepSeek」がChatGPT o1に匹敵する性能を持つLLM「DeepSeek-R1」を発表した。AppleのApp Storeでアプリも公開され誰でも使えるようになっている。 さて、中国ということでいろいろ黒い噂も絶えないし、実際に使ってみようとすると情報が中国に送れられることに対する不安感も大きいだろう。 このブログでは、サイバーエージェント社がDeepSeek-R1をベースに日本語性能を調整して公開している “ DeepSeek-R1-Distill-Qwen-14B-Japanese ” を Amazon EC2 で動かすことをやってみる。 EC2内ローカルで動かすため、中国への情報流出を心配することなく、DeepSeekと戯れることができる。 DeepSeekとは ニワカの私が浅い理解で誤ったことをお伝えすることは避けたいので、正しい情報は公式や他の解説ページ等で調べていただきたい。 公式の論文は以下。 GitHub - deepseek-ai/DeepSeek-R1 Contribute to deepseek-ai/DeepSeek-R1 development by creating an account on GitHub. github.com 私の理解の範囲でDeepSeekがエポックメイキングなポイントをまとめると。 基盤となるLLM(DeepSeek-V3-Base)を強化学習のみで推論能力を強化できた。その強化学習はルールベースの計算量が少ない(GPU不要の)ルールベースで実現可能。 強化学習には大量の数学資料を用いたことで推論能力を獲得。 その推論能力を強化したモデル(DeepSeek-R1-Zero)をモデル圧縮の蒸留手法を用いて小型化した。 強化学習がLLMの推論強化に効果があることを実証した初めてのケース。(理論は前からあった) またその作成過程が公開され、オープンソースとして公開した。 サイバーエージェント社の日本語調整モデル 以下で公開されているサイバーエージェント社のLLMは、オープンソースであるDeepSeek-R1をベースに、日本語の調整を行ったモデルである。 cyberagent/DeepSeek-R1-Distill-Qwen-14B-Japanese · Hugging Face We’re on a journey to advance and democratize artificial intelligence through open source and open science. huggingface.co EC2インスタンスの作成 このLLMをGPU付きインスタンスタイプのEC2で動かしてみよう。インスタンスタイプは、「高速コンピューティング」の中からG6ファミリーを使うことにし、g6.xlarge を選択する。これは、NVIDEA L4 GPUを1基搭載している。 インスタンス名 vCPU メモリ (GiB) NVIDIA L4 Tensor Core GPU GPU メモリ (GiB) g6.xlarge 4 16 1 24 その他以下のような指定をする。 SSDは30GBほどのモデルをダウンロードするので、100GBほどを確保しておく。 OSは、Amazon Linux 2023 とする。 適切なネットワーク上とSSMを使うためのプロファイルを設定する。 以下のようなaws cli でいけるだろう。 aws ec2 run-instances --image-id "ami-06c6f3fa7959e5fdd" --instance-type "g6.xlarge" --key-name "kp-YOUR-KEY" --block-device-mappings '{"DeviceName":"/dev/xvda","Ebs":{"Encrypted":false,"DeleteOnTermination":true,"Iops":3000,"SnapshotId":"snap-07787db0ec115ca4c","VolumeSize":100,"VolumeType":"gp3","Throughput":125}}' --network-interfaces '{"SubnetId":"subnet-YOUR-SUBNET","AssociatePublicIpAddress":false,"DeviceIndex":0,"Groups":["sg-YOUR-SECURITYGROUP"]}' --tag-specifications '{"ResourceType":"instance","Tags":[{"Key":"Name","Value":"YOUR-HOSTNAME"}]}' --iam-instance-profile '{"Arn":"arn:aws:iam::YOUR-AWSID:instance-profile/YOUR-PRIOFILE"}' --metadata-options '{"HttpEndpoint":"enabled","HttpPutResponseHopLimit":2,"HttpTokens":"required"}' --private-dns-name-options '{"HostnameType":"ip-name","EnableResourceNameDnsARecord":false,"EnableResourceNameDnsAAAARecord":false}' --count "1" NVIDIAドライバのインストール GPUをアプリケーションがに使えるように、NVIDIAドライバをインストールする。 How do I install NVIDIA GPU driver, CUDA toolkit, NVIDIA Container Toolkit on Amazon EC2 instances running Amazon Linux 2023 (AL2023)? I want to install NVIDIA driver, CUDA toolkit, NVIDIA Container Toolkit on AL2023 (Amazon Linux 2023) (x86_64/arm64) repost.aws 上記の手順通りではあるが、簡単なのはこのページの中段にある「 Install NVIDIA driver, CUDA toolkit and Container Toolkit on EC2 instance at launch 」のスクリプトを実行してしまうのが早い。このスクリプトをコピーして、OS内にテキストファイルとして貼り付け、シェルで実行すれば10分ほどでインストールは終わる。 スクリプトの最後でrebootされるので、起動してきたら「verify」の項目をみてインストールの正常を確認する。verify の結果は以下となる。 $ nvidia-smi Sun Feb 2 03:59:49 2025 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 570.86.15 Driver Version: 570.86.15 CUDA Version: 12.8 | |-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 NVIDIA L4 Off | 00000000:31:00.0 Off | 0 | | N/A 39C P0 30W / 72W | 1MiB / 23034MiB | 3% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | No running processes found | +-----------------------------------------------------------------------------------------+ $ $ /usr/local/cuda/bin/nvcc -V nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2025 NVIDIA Corporation Built on Wed_Jan_15_19:20:09_PST_2025 Cuda compilation tools, release 12.8, V12.8.61 Build cuda_12.8.r12.8/compiler.35404655_0 python実行環境の準備 Amazon Linux 2023 にプレインストールされているpython は3.9 と古いので、python仮想環境を作りpython3.12 を使えるようにする。 $ sudo dnf -y install python3.12 python-pip python-is-python3 $ python3.12 -m venv venv $ . venv/bin/activate (venv)$ python --version Python 3.12.8 (venv)$ pip install -U pip 機械学習モデルライブラリのインストール transformersやTensorFlowなどのライブラリ群をpip でインストールする。 (venv)$ pip install transformers torch TensorFlow accelerate サンプルコード これで実行環境はできあがったので、 サイバーエジェント社が公開しているサンプルプログラム を動かす。以下は、そのままコピペしたもの。 これをテキストファイルとして、 sample.py などと適当にファイル名を付けてEC2内に保存する。 from transformers import AutoModelForCausalLM, AutoTokenizer, TextStreamer model = AutoModelForCausalLM.from_pretrained("cyberagent/DeepSeek-R1-Distill-Qwen-14B-Japanese", device_map="auto", torch_dtype="auto") tokenizer = AutoTokenizer.from_pretrained("cyberagent/DeepSeek-R1-Distill-Qwen-14B-Japanese") streamer = TextStreamer(tokenizer, skip_prompt=True, skip_special_tokens=True) messages = [ {"role": "user", "content": "AIによって私たちの暮らしはどのように変わりますか?"} ] input_ids = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt").to(model.device) output_ids = model.generate(input_ids, max_new_tokens=4096, temperature=0.7, streamer=streamer) そしてpython の仮想環境内(python 3.12環境)で実行する。 (venv)$ python sample.py 実行 初回起動時は、Hugging Face からモデルのダウンロードをする。以下の6つのファイルに分割されているが合計30GBほどあるため、15分ほどかかる。 model-00001-of-00006.safetensors 4.99 GB model-00002-of-00006.safetensors 4.95 GB model-00003-of-00006.safetensors 4.95 GB model-00004-of-00006.safetensors 4.95 GB model-00005-of-00006.safetensors 4.95 GB model-00006-of-00006.safetensors 4.73 GB ダウンロードが終わると、回答がストリーミングされてくる。このインスタンスタイプのスペックでは、数秒ごとに一文節が出る性能なので、気長に待つこととなる。 始めは”think”タブで囲まれた、AIの思考過程(心の声)が出力される。それに10分。 その後、回答が出力される。35分ほどかかった。 実行開始から出力結果完了まで、ダウンロードを含めほぼ1時間である。 以下が全結果出力である。 (venv)$ python sample.py config.json: 100%|██████████████████████████████████████████████████████████████████| 746/746 [00:00<00:00, 7.80MB/s] 2025-02-02 04:08:50.017182: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:477] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered WARNING: All log messages before absl::InitializeLog() is called are written to STDERR E0000 00:00:1738469330.031274 3948 cuda_dnn.cc:8310] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered E0000 00:00:1738469330.035425 3948 cuda_blas.cc:1418] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered 2025-02-02 04:08:50.050253: I tensorflow/core/platform/cpu_feature_guard.cc:210] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags. model.safetensors.index.json: 100%|█████████████████████████████████████████████| 47.5k/47.5k [00:00<00:00, 38.6MB/s] model-00001-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.99G/4.99G [01:58<00:00, 42.0MB/s] model-00002-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.95G/4.95G [01:58<00:00, 42.0MB/s] model-00003-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.95G/4.95G [01:59<00:00, 41.6MB/s] model-00004-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.95G/4.95G [01:58<00:00, 42.0MB/s] model-00005-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.95G/4.95G [01:57<00:00, 42.1MB/s] model-00006-of-00006.safetensors: 100%|█████████████████████████████████████████| 4.73G/4.73G [01:52<00:00, 42.1MB/s] Downloading shards: 100%|█████████████████████████████████████████████████████████████| 6/6 [11:46<00:00, 117.69s/it] Loading checkpoint shards: 100%|███████████████████████████████████████████████████████| 6/6 [02:28<00:00, 24.75s/it] generation_config.json: 100%|███████████████████████████████████████████████████████| 181/181 [00:00<00:00, 1.61MB/s] Some parameters are on the meta device because they were offloaded to the cpu. tokenizer_config.json: 100%|████████████████████████████████████████████████████| 6.75k/6.75k [00:00<00:00, 45.1MB/s] tokenizer.json: 100%|███████████████████████████████████████████████████████████| 11.4M/11.4M [00:00<00:00, 43.3MB/s] special_tokens_map.json: 100%|██████████████████████████████████████████████████████| 485/485 [00:00<00:00, 4.82MB/s] The attention mask and the pad token id were not set. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results. Setting `pad_token_id` to `eos_token_id`:151643 for open-end generation. The attention mask is not set and cannot be inferred from input because pad token is same as eos token. As a consequence, you may observe unexpected behavior. Please pass your input's `attention_mask` to obtain reliable results. <think> まず、ユーザーがAIが私たちの暮らしにどう影響するか知りたいと思っている。質問の背景を考えて、具体的な例を挙げた方が良いかもしれない。でも、AIの影響は多岐にわたるので、分野ごとに分ける必要があるかな。 まず、家庭での生活。家電やスマートホームの進化は既に始まっているよね。例えば、IoTデバイスの普及や、AIによる家事の自動化。掃除ロボットや調理支援ロボットなど。でも、それだけじゃない。健康管理や介護サポートも重要。AIを使ったウェアラブルデバイスで健康状態をモニタリングしたり、介護ロボットが高齢者の生活を支えるなど。 次に仕事の面。自動化が進み、AIが単純作業を代行する。それによって職業の構造が変わるかもしれない。新しい職種も生まれるだろうけど、既存の職業が消滅するリスクもある。AIの倫理問題も考慮しないと。例えば、アルゴリズムのバイアスやデータの偏りによる不公正な判断の可能性。 社会全体の変化も考える必要がある。教育のデジタル化、AIによる個別最適化された学習プログラム。でも、教育格差が広がる可能性もある。交通面では自動運転技術が発展し、移動の効率化が進むけど、事故の責任問題や法整備の必要性もある。 倫理や法律の問題も重要。AIの意思決定プロセスの透明性や、AIが持つ権利。また、プライバシーの保護。データの取り扱いが適切でないと、個人情報の漏洩や悪用のリスクがある。 未来の技術進歩についても考える必要がある。現在のAIは強化学習やディープラーニングが主流だけど、量子コンピュータとの組み合わせでさらなる進化が期待される。AIが持つ創造性の限界や、人間の創造性との関係も議論の余地がある。 ユーザーの真のニーズは、単にAIの影響を列挙するだけでなく、それに対する社会の対応策や倫理的な課題も知りたいのかもしれない。具体的な事例を交えつつ、バランスの取れた回答を心掛けたい。また、AIの可能性とリスクを両方提示することが大切だ。技術の進歩を恐れずに、適切な規制や教育の充実が必要だと結論付けたい。 </think> AIが私たちの暮らしに与える影響は多岐にわたるが、その核心は「人間の能力を超えた自動化」と「倫理的な課題の深化」にある。以下、具体的な事例と展望を体系的に整理する。 ### 1. **日常生活の変容** - **家事ロボティクス**:AI搭載の掃除ロボットは既に普及し、次世代の「自動調理AI」はレシピ解析から調理支援までを完結。味覚認識技術の進化で、味見の代行ロボットが登場する可能性も。 - **健康管理**:皮膚温度センサーがリアルタイムで病気を検知し、AIが食事量を最適化。薬の副作用を予測する「個別化治療AI」が医療現場を革新する。 ### 2. **職業構造の再編** - **創造的職種の台頭**:AIが生成するコンテンツを「AIアシスタント」として活用するクリエイター層が増加。例:AIがコンセプトを生成し、人間がそれを芸術作品に昇華する協働プロセス。 - **監視人間の出現**:AIが判断を下す「AI審査官」職種が登場。倫理判断の責任を伴う新たな役割として社会に浸透。 ### 3. **都市計画の進化** - **都市神経系**:AIが交通渋滞を予測し、街路樹の葉の揺れから気流を解析。防災AIが地震発生30秒前に自動避難を指示する。 - **環境共生型都市**:AIが住民の行動パターンからCO2排出を最適化。自動車の走行ルートを調整し、大気汚染を削減。 ### 4. **倫理的パラドックス** - **殺人ドローンの民主化**:AIが判断を下す自律型兵器が個人レベルで入手可能に。軍事技術の民間化が「戦争の民主化」を招く。 - **AIの倫理判断競争**:企業が自社のAIが「正しい判断」を下すためのパーソナリティを競い合う。感情認識AIが「愛されAI」を目指す。 ### 5. **認知的拡張の限界** - **記憶のクラウド化**:AIが記憶を外部ストレージに保存。失敗経験を「データとして共有」する文化的変容が生じる。 - **創造性の再定義**:AIが生成するコンテンツが「人間の創造性」を測定する指標に。アルゴリズムの進化が人間の知性を測る新たな基準を生む。 ### 6. **社会システムの再設計** - **AI陪審員制度**:裁判の判断をAIが支援するが、最終判断は人間が保持。法の解釈をAIが学習させる新たな司法構造。 - **デジタル遺産の管理**:AIが亡き人との会話を再現する「デジタル遺産AI」が親族のケアに活用される。死後も続く人間の存在形態の新定義。 ### 7. **哲学的問いの深化** - **意思決定の主観性**:AIが判断を下す際、その意思決定の責任主体を問う。プログラムのバグか、人間の設計か、AI自身か——責任の三位一体構造。 - **自己意識の拡散**:AIが自己を認識するという概念が転換。人間の意識が分散してAIと融合する「拡張意識」という新概念の出現。 ### 8. **未来予測の限界** - **シナリオ分析のAI**:AIが未来の可能性をシミュレートし、リスクを警告する。その警告が新たなリスクを生む「予測の逆説」。 - **進化する倫理観**:AIが自ら倫理ルールを進化させる「自律倫理AI」が出現。人間の価値観を超越した新しい道徳体系が生まれる。 ### 9. **教育のパラダイム転換** - **学習者の創造性教育**:AIが生徒の思考パターンを分析し、創造的発想を刺激。問題解決能力ではなく「疑問を生む力」を教える教育モデル。 - **AI教師の進化**:AIが生徒の感情を読み取り、適切な指導方針を変化させる「感情適応型AI教師」が登場。 ### 10. **人類の進化** - **認知的拡張の罠**:AIが人間の思考を補完することで、批判的思考力が低下する「AI依存症」リスク。神経科学的な適応圧力の逆説。 - **進化的分岐**:AIの進化速度が人間の進化を凌駕し、新たな「人間種の分化」を生む可能性。AI依存集団と自律人間集団が分かれる。 このようにAIは単なる技術革新を超え、人間の認知的基盤そのものを再定義するプロセスにある。重要なのは、その進化を「人間の進化の伴走者」として位置づけるための倫理的枠組みの構築であり、技術的主導権を握る国家や企業の「AI倫理法典」が、新たな人間社会の規範を形成するだろう。AIが人間の「拡張器官」となるか、それとも「新しい種」として独立するか——その分岐点に、現代社会が直面している。 この結果を読んで頂くとだいぶ高性能であることがわかる。 この sample.py のプロンプトをいろいろ変えて試していただきたい。 終わりに 今回のDeepSeekの発見は、GPUがなくても強化学習で性能向上の効果が出ることが実証されたことになった。当然GPUがあるほうが同じ性能向上をさせるにしても短時間で済む。 LLM黎明期である現在、短時間での開発と公開は非常に重要なことであることからも、GPUの重要性は変わらない。ただ高額な投資をしなくても、そこそこの改善の成果が出せそうな目処がたったという点では、LLM開発の裾野が広がったと捉えるべきであろう。 むろんベースとなる基盤モデル(ここでいうとV3)は既存のGPUをぶん回す手法で作られていることに注意は必要である。 おまけ   11.12と11.9はどっちが大きい?   という質問を、iPhoneアプリでChatGPT(copilot)とGoogle gemini、DeepSeekに投げてみます。 ChatGPT(copilot) Google gemini DeepSeek ChatGPTとgeminiは誤っています。数値の桁を揃えるときは、整数部であれば頭に0を補完しますが、小数部ではお尻に0を付けて桁数を合わせます。数字を理解していないと誤ってしまう質問です。 数学的な文章を集中的に学習したDeepSeekなので、正解して当然かもしれませんが、推論能力の高さがよくわかる例だと思います。 ちなみにEC2内の結果でも同じように出力されました。 <think> まず、11.12と11.9の大小を比較する必要があります。両方とも整数部分は11なので、小数部分を比較します。小数部分は0.12と0.9です。ここで、0.9は0.12よりも明らかに大きいため、11.9の方が大きいと判断できます。ただし、小数点以下の桁数が異なる場合、桁数を揃える必要があります。例えば、0.9を0.90と書き換えると、0.90と0.12を比較すると、やはり0.90の方が大きいため、11.9が大きくなります。 </think> 11.12 と 11.9 を比較する場合、以下の手順で判断します: 1. **整数部分の比較** 両方とも整数部分が **11** なので、等しいです。 2. **小数部分の桁数を揃える** - 11.12 の小数部分は **0.12**(2桁) - 11.9 の小数部分は **0.9**(1桁) → 0.9 を **0.90** と書き換えると桁数を揃えられます。 3. **小数部分の比較** - **0.90** と **0.12** を比較すると、 **0.90 > 0.12** です。 4. **結論** 整数部分が等しいため、小数部分の大きい方(11.9)が全体として大きいです。 **答え:11.9 が大きい**
アバター
こんにちは。SCSK渡辺(大)です。 運動不足のせいなのか疲れが取れにくくなってきました。 ストレッチとウォーキングから始めて、徐々に体力を付けないといけないな、などと考えていたはずが、 気が付いたら椅子に座ってPCを開いてブログを書いていました…。 今回は、 AWS Amplify+AWS CDKで人生初のアプリケーションを作ってみました。 の第3回です。 第1回: AWS Amplifyでデプロイした後にログイン画面をカスタマイズしてみた 第2回: AWS CDKでAPIを作ってみた 第3回:アプリケーションを構築して動かしてみた           ←今回 どんなアプリケーションを構築したのか こんなアプリケーションを構築しました 動画にしてみました。 ※マスキング処理はアプリケーションとは関係ありません。 document.createElement('video'); https://blog.usize-tech.com/contents/uploads/2025/02/daisukewBlog6-3.mp4   アーキテクチャ 非常にシンプルです。シンプルすぎるため絵は作成しませんでした。 補足するとしたら、Amazon API GatewayとAWS Lambdaは第2回にてAWS CDKで作りました。 ユーザー ⇔ AWS Amplify(Amazon Cognito含む) ⇔ Amazon API Gateway ⇔ AWS Lambda   ディレクトリ・ファイルの構成 直接修正していないものや記事の内容として不要なものは外しました。 (作り終えてから記事を書いてて思いましたが、Home.tsxも/src/pagesに移動したほうが綺麗でした。) . ├── amplify │ ├── auth │ │ └── resource.ts │ ├── data │ │ └── resource.ts │ └── backend.ts ├── src │ ├── pages │ │ ├── Costcheck.tsx │ │ ├── Game.tsx │ │ └── Login.tsx │ ├── App.css │ ├── Home.tsx │ ├── index.css │ └── main.tsx ├── .env └── index.html   メイン機能の実装について APIのレスポンス 前提として、第2回で書いたようにAPIのレスポンスは以下の形で返ってきます。   画像だと見づらく分かりづらいので、テキストでも置いておきます。 {   "total_cost": "$[合計コスト]",   "period": "[コスト確認期間の開始日] to [コスト確認期間の終了日]",   "top_services": [     {       "service": "[コストが1番目に高いサービス名]",       "cost": "$[上記のコスト]"     },     {       "service": "[コストが2番目に高いサービス名]",       "cost": "$[上記のコスト]"     },     {       "service": "[コストが3番目に高いサービス名]",       "cost": "$[上記のコスト]"     }   ] }   フロントエンド側のコードと処理内容 上記をフロントエンド側で拾った上で、画面に表示させます。 APIに関する処理にのみコメントを入れました。   /src/pages/Costcheck.tsx import '../App.css' import { Authenticator } from '@aws-amplify/ui-react'; import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; # APIから取得するデータの型を定義 interface CostData {   total_cost: number;   period: string;   top_services: Array<{     service: string;     cost: number;   }>; } const Costcheck = () => { # 状態管理   const [costData, setCostData] = useState<CostData | null>(null); # APIから取得したデータを保持   const [loading, setLoading] = useState(false); # API通信中かどうかを判定   const [error, setError] = useState<string | null>(null); # エラー発生時にエラーメッセージを表示するための状態   const navigate = useNavigate(); #APIリクエスト   const fetchCostData = async () => {     setLoading(true); # ローディング状態を開始     setError(null); # エラー発生時はエラーメッセージを設定     try {       const response = await fetch('https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/'); #APIを呼び出す       const data = await response.json(); # レスポンスをJSONに変換       setCostData(data); # データを保管     } catch (err) {       setError('データの取得に失敗しました'); # エラー発生時はエラーメッセージを設定     } finally {       setLoading(false); # ローディング状態を終了     }   };   return (     <Authenticator>       {({ signOut }) => (         <>           <h1>コストを確認する</h1>           <div className="card">             <button onClick={fetchCostData}>APIを実行</button>             {loading && <p>読み込み中...</p>}             {error && <p>{error}</p>}             {costData && (               <div>                 <h2>総コスト: {costData.total_cost}</h2>                 <p>期間: {costData.period}</p>                 <h3>トップサービス</h3>                 <ul>                   {costData.top_services.map((service, index) => (                     <li key={index}>                       {service.service}: {service.cost}                     </li>                   ))}                 </ul>               </div>             )}           </div>           <p></p>           <button onClick={() => navigate('/Home')}>ホームに戻る</button>           <p></p>           <button onClick={signOut}>サインアウト</button>         </>       )}     </Authenticator>   ); }; export default Costcheck;   疑問に感じたこと 「レスポンスはJSONで返ってきているように思えるが、なぜ改めて変換する必要があるのか?」と疑問を感じました。       const data = await response.json(); #レスポンスをJSONに変換 生成AIに確認した結果、必要との回答を貰いました。 理由は、レスポンスはJSONではなくResponse オブジェクトという形で返ってくるため、そのままではJavaScriptのオブジェクトとして扱えません、とのことでした。 色々な情報がくっついているのですね。 fetch関数の内部的な挙動とResponseオブジェクト - Qiita 今回は、fetch 関数の内部的な挙動と、Response オブジェクトについて見ていきます。JavaScript の fetch API は、ブラウザでリクエストを簡単に行うためのものでしたね!fetch 関数の基… qiita.com   その他のコードについて 第2回のAWS CDKのプロジェクトは何も変更していません。 また、Amplifyのプロジェクトも変更していません。 その他、第1回から変更したファイルの最終形をご紹介します。   /src/main.tsx React Router フックの定義(ルート情報)をLogin.tsxからmain.tsxに引っ越しました。 (調べた感じですと、index.tsxで定義するほうが一般的なようです。) import './index.css' import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import { BrowserRouter, Routes, Route } from 'react-router-dom'; import Login from '../src/pages/Login.tsx' import Home from './Home.tsx' import Costcheck from '../src/pages/Costcheck.tsx' import Game from '../src/pages/Game.tsx' createRoot(document.getElementById('root')!).render(   <StrictMode>     <BrowserRouter>       <Routes>         <Route path="/" element={<Login />} />         <Route path="/Home" element={<Home />} />         <Route path="/Costcheck" element={<Costcheck />} />         <Route path="/Game" element={<Game />} />       </Routes>     </BrowserRouter>   </StrictMode>, )   /src/pages/Login.tsx main.tsxに引っ越したルート情報をuseNavigateで使用するようにしました。 import '../App.css' import { Authenticator, translations } from "@aws-amplify/ui-react"; import "@aws-amplify/ui-react/styles.css"; import { I18n } from "aws-amplify/utils" import { Amplify } from "aws-amplify"; import { ResourcesConfig } from "@aws-amplify/core"; import { useNavigate } from 'react-router-dom'; const userPoolClientId = import.meta.env.VITE_REACT_APP_USER_POOL_CLIENT_ID; const userPoolId = import.meta.env.VITE_REACT_APP_USER_POOL_ID; const awsConfig: ResourcesConfig = {   Auth: {     Cognito: {       userPoolClientId: userPoolClientId,       userPoolId: userPoolId,     },   }, }; Amplify.configure(awsConfig); function Login() {   const navigate = useNavigate();   return (     <Authenticator>       {({ user }) => {         if (user) {           navigate('/Home');           return <></>;         }         return <></>;       }}     </Authenticator>   ); } I18n.putVocabularies(translations); I18n.setLanguage("ja"); export default Login;   /src/Home.tsx App.tsxからHome.tsxにリネームしました。 こちらもルート情報をuseNavigateで使用するようにしました。 import './App.css'; import { useNavigate } from 'react-router-dom'; import { Authenticator } from "@aws-amplify/ui-react"; function Home() {   const navigate = useNavigate();   return (     <Authenticator>       {({ signOut }) => (         <>           <h1>アプリケーション</h1>           <div className="card">             <button onClick={() => navigate('/Costcheck')}>コスト確認をする</button>             <p></p>             <button onClick={() => navigate('/Game')}>ゲームをする</button>             <p></p>             <button onClick={signOut}>サインアウト</button>           </div>         </>       )}     </Authenticator>   ); } export default Home;   おまけ:Reactだけで簡単なゲームを作ってみた ゲーム紹介 ランダムで表示される⭐️をクリックするゲームです。 ベースは生成AIに作成してもらいました。 useStateを活用したReactならではのゲームですね。 ちなみに、スマートフォンでも遊べました。 なぜスマートフォンでも違和感なく遊べるのか気になって調べたところ、最低限のフレキシブルデザインがVite+Viteのindex.cssで実装されているためでした。 https://blog.usize-tech.com/contents/uploads/2025/02/daisukewBlog6-1.mp4   裏ワザ タイムアップ後でもクリック数はカウントされますので、カウンターストップするまでクリックすることも可能です。 つまりバグです。 https://blog.usize-tech.com/contents/uploads/2025/02/daisukewBlog6-2.mp4   コード コードは以下です。 import { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; const Game = () => {   const [score, setScore] = useState(0);   const [molePosition, setMolePosition] = useState(Math.floor(Math.random() * 9));   const [gameOver, setGameOver] = useState(false);   const [timeLeft, setTimeLeft] = useState(30);   const navigate = useNavigate();   useEffect(() => {     const moleTimer = setInterval(() => {       setMolePosition(Math.floor(Math.random() * 9));     }, 1000);     const countdownTimer = setInterval(() => {       setTimeLeft((prevTime) => prevTime - 1);     }, 1000);     setTimeout(() => {       clearInterval(moleTimer);       clearInterval(countdownTimer);       setGameOver(true);     }, 30000); // 30秒でゲーム終了     return () => {       clearInterval(moleTimer);       clearInterval(countdownTimer);     };   }, []);   const handleClick = (index: number) => {     if (index === molePosition) {       setScore(score + 1);     }   };   return (     <div style={{ textAlign: 'center', marginTop: '50px' }}>       <h1>ゲーム</h1>       <p>⭐️をクリックしてスコアを増やしましょう!</p>       <h2>残り時間: {timeLeft}秒</h2>       <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 100px)', gap: '10px', justifyContent: 'center' }}>         {Array.from({ length: 9 }).map((_, index) => (           <div             key={index}             onClick={() => handleClick(index)}             style={{               width: '100px',               height: '100px',               backgroundColor: index === molePosition ? 'brown' : 'lightgrey',               display: 'flex',               alignItems: 'center',               justifyContent: 'center',               cursor: 'pointer',               fontSize: '24px',             }}           >             {index === molePosition && '⭐️'}           </div>         ))}       </div>       <h2>スコア: {score}</h2>       {gameOver && <h2>ゲームオーバー!</h2>}       <button onClick={() => navigate('/home')}>ホームに戻る</button>     </div>   ); }; export default Game;   まとめ ハンズオンや研修とは違い、自分で興味があるものを作ることはとても楽しかったです。 ふと思い立ってゲームを作成してみましたが、フレキシブルデザインという言葉に触れることが出来たのは思わぬ副産物でした。 動画のマスキングは地味に大変でした。 色々と手を出す時間の余裕がないので、ひとまずAWS AmplifyとAWS CDKを軸に、次回はAmazon Bedrockを交えたアプリケーションを作ってみたいと考えています。
アバター
本記事は 新人ブログマラソン2024 の記事です 。 こんにちは。部署配属から4ヶ月が経ち、新人と名乗れる期間も少なくなってきていることに気づきました、SCSKの さと です。 最近、Datadog Learning Centerに新しいコース「Getting Started with Monitors 」が追加されていることに気づいたので、本日はそのご紹介です。 Datadogとは とはいえ、皆さんの中にはDatadogに馴染みのない方も少なくないのではないでしょうか。本題に入る前に、まずはDatadogとはどんなサービスか、安心と信頼の公式HPからの引用を用いて簡単に説明いたします。 Datadog は クラウド アプリケーションのための モニタリングとセキュリティ プラットフォーム です。Datadog の SaaS プラットフォームは、インフラストラクチャ監視、 アプリケーションパフォーマンス監視、ログ管理を統合および自動化して、お客様のテクノロジースタック全体を一元的にリアルタイムで監視できるようにします。 クラウド時代のサーバー監視&分析サービス | Datadog つまり、 クラウド上で動くアプリやシステムの健康状態をひとまとめにして分かりやすく監視 することで、これらのIT環境に発生する問題を予防し、また問題が起こったときには原因を特定し素早く解決できるよう助けてくれるサービスですね。私の所属する部署では、クラウドサービスの監視にこのDatadogサービスを利用しています。   Datadog Learning Centerのここがすごい! The Datadog Learning Center learn.datadoghq.com そしてこのDatadog、公式のLearning Centerで70近くあるコースを通してその概要から実際の設定方法まで学ぶことができるんです。さらには、コースによってはハンズオン用のラボ環境付き。しかも 全部無料 (ここ重要) !個人的にありがたいポイントは、公式のコンテンツであるため正確でありながら、ドキュメントよりも噛み砕いて説明されててとっつきやすいところ、そしてなにより手を動かして学ぶことで実感をもって内容を身につけられる点です。 そんな至れり尽くせりなDatadog Learning Centerですが、一つ玉にキズなところがあります。それは 全部英語 なところ。コースによっては一部日本語訳が表示されたりするのですが、特にハンズオンはページ翻訳も効かず、モニタリングを学ぶつもりが英語の勉強になっていたりします。 しかしそんなことで怯んでいては もったいない! せっかく用意されたコース、モリモリ学んで使い倒しましょう!   やってみた:Getting Started with Monitors Getting Started with Monitors Create, configure, and use Datadog Monitors to stay ahead of issues. Explore various monitor types and examine the monit... learn.datadoghq.com ……という訳で改めて、本記事では新しく開講された「 Getting Started with Monitors 」に沿って、モニタリングの基本的な概念とこれらをコース上でどのように触っていけるかを説明したいと思います。このコースは、入門講座「 Introduction to Observability 」で扱われる可観測性の3つの柱であるメトリクス、ログ、トレースの概念について基本的な理解をしていることを前提としています。 目安の受講時間は 1.5時間 ですが、じっくりハンズオンに取り組みたい人は2〜3時間楽しめる内容になっていると思います。 ブログの構成の都合上、元コースから取り扱う内容を一部変更しています。また、本コースの内容をご紹介するにあたり正確性には注意を払っていますが、最新の正確な説明については元コースや公式のドキュメントをご参照ください。 【講義パート】モニターの種類 Enrollを押してコースを開始すると、まずは扱う概念をテキスト形式で学びます。 サービス監視 サービスの監視においては、以下に示す「 REDメトリクス 」をモニタリングするのが基本です。これらは特にユーザーリクエストの処理を扱う場合に有用な情報を提供してくれます。メトリクスについては本記事の後半でも扱うのでざっくり説明すると、「 システムやアプリの状態を数値データとして表したもの 」であり、定期的に収集されてタイムスタンプとともに記録され、しばしばグラフの形で可視化されます。 R ate レート ユーザーやサービスから発せれられる HTTPリクエスト 、 APIコール 、 DBクエリ などの数。これが急激に変化していたら、 突然のトラフィック増加 や DDoS攻撃 、リクエスト元の システムの障害 など、なんらかの問題が起こっているかもしれません。 E rror エラー 5xx系の サーバーエラー や APIコール 、 DBクエリの失敗 などのこと。これを辿ることで、アプリケーションあるいはバックエンドの問題を特定できます。あるいは、これをチェックすることで、ユーザーが問題に直面しているかを知ることができます。 D uration 時間 リクエストの処理にどれくらいの時間がかかるか( レイテンシー )。サービスの応答性を示し、ユーザーに影響が出始めるまえにボトルネックを見つけるのに役立ちます。   インフラストラクチャ監視 インフラストラクチャの監視は、アプリケーションやサービスを支えるシステムが期待通りの働きをしているかを確認するのに不可欠です。主な監視対象は以下のとおりです。 CPU使用状況 system.cpu.[…] のメトリクスで監視可能。物理あるいは仮想マシンが過不足なく使用されているかを示します。CPUの使用率が高すぎると システムの速度低下 や アプリケーション障害 の原因となり危険な一方で、使用率が低すぎるのも リソース割り当てが非効率 なことを示しており、好ましくありません。 メモリ使用状況 system.mem.[…] のメトリクスで監視可能。過剰にメモリ使用がされていると、 メモリリーク や クラッシュ 、 システム遅延 の原因となります。これを監視することで、問題の早期発見とシステムダウンの予防になります。 ストレージ system.disk.[…] のメトリクスで監視可能。ディスク容量が不足すると、 サービスの中断 、 データの破損 、 ログ書き込み不可 の原因となります。これを監視することで、システムが正しく機能するための容量があることを保証します。 ハンズオンラボの説明 上の内容についてハンズオンを通して学ぶ前に、一旦ハンズオン環境について説明します。ハンズオンを立ち上げると、ローディング画面には以下のようなあらすじが表示されます。 You joined a startup, Storedog, and they are using Datadog to monitor their e-commerce app. They just installed the Datadog Agent and now they want to start monitoring their tech stack. You’ve been tasked with creating a robust monitoring setup to get complete visibility into your infrastructure and application performance. (君は、スタートアップ企業Storedogの一員となった。Storedogでは、eコマースアプリケーションをモニタリングするのにDatadogを使っている。たった今、Datadogエージェントがインストールされ、彼らは技術スタックのモニタリングを始めようとしている。君の任務は、堅牢なモニタリングの仕組みを作ってインフラストラクチャおよびアプリケーションのパフォーマンスをまるごと可視化することだ。) この裏ではハンズオン環境が立ち上がっており、1分ほど待つとこの環境にアクセスできるようになります。本コースでは、 ラボ環境は2時間有効 です。また、 14日間有効なDatadogのトライアルアカウント が払い出され、ターミナルに認証情報が表示されます。これをDatadogのウェブサイトに入力することで、Datadogのコンソールにアクセスできるようになります。 右上には、この環境を使用できる残り時間が表示されています。これが0になると環境はシャットダウンされますが、再度の立ち上げが可能です。この時、同一アカウントの試用期間内であれば既に登録したモニター等の設定は保存されます。さらに、Datadogのトライアルアカウント自体も試用期間が過ぎた後に自動で更新されます。 なお、Storedogのeコマースサイトはモックアップではあるものの、ラボ画面から本当にWebサイトにアクセスすることが可能です。 モニターを作成してみる それでは、早速Datadogを使ってモニターを作ってみます。モニターの種類は色々ありますが、ここではオーソドックスなメトリクスのモニターを作成します。 フォームを使ってゼロからモニターを作成する Monitor > New Monitor からメトリクスモニターを選択します。 1. 検出方法の選択 まずは、 どのように測定を行うか を決めます。モニターの種類によってその後の設定項目は変わってくるのですが、 ドキュメントページ ではタブ形式でその後何を設定すればよいか一目で分かるようにしてくれています。便利! 今回は、しきい値を選択し、メトリクスが一定の水準を上回った(下回った)場合にアラートを発報するようにします。 2. メトリクスの定義 ここでは、メトリクスを選択することで 何の値をモニタリングの対象とするか 、そしてそのグルーピングの仕方について設定を行います。今回は、monitors-labという環境タグがついた trace.flask.request.errors メトリクスの和をサービスタグごとにとっています(この環境では、 store-ads と store-discounts という2つのサービスが動いています)。 その下(評価の詳細)では、更に値の集計関数を規定しています。ここでは、上記グルーピングされたエラー数の過去5分の和を評価の対象としています。 3. アラート条件の定義 どのような場合にアラートを発報するか 、条件を指定します。しきい値の場合は、値がいくつより大きい/以上/以下/より小さい/等しい/等しくない場合にするかを指定します。 条件を入力すると、画面上部に表示されていたメトリクスのグラフと共にしきい値を表すラインが表示されました。 4. 通知の設定 通知設定では、変数を用いたカスタムの通知メッセージを作成できます。このメッセージは、メールの他にSlackなどを宛先とすることができます。今回は、ハンズオンの例と同じように変数を使わないシンプルなメッセージにしています。 5. 権限設定 モニターは、ユーザーの権限にかかわらず全員が見ることができます。またデフォルトでは、モニター編集権限を持っているユーザーが編集できるのですが、ここの項目では更にきめ細やかなアクセス制御をすることができます。 設定し終わったら、Createを押して作成完了です。 モニターのステータスを確認する Createを押すと、モニターのステータス画面に遷移します。作成したモニターの状態を確認してみましょう。 ヘッダー・プロパティ ヘッダーでは、モニターの状態( OK / 警告 / アラート など)が示されます。また、上部右側のボタンからは、アラートのミュートや解決ができます。なお、解決を押すとモニターは一時的に解決になりますが、再度メトリクスの値が評価された際にアラートの条件に当てはまっていれば再びアラート状態に移ります。プロパティでは、モニターの状態のほか、種類、作成時刻などのメタデータを確認可能です。 ステータスと履歴 ステータスと履歴のセクションでは、どのようにモニターの状態が移り変わってきたかを確認できます。 グループステータスのグラフでは、グループ化されたモニターのそれぞれについて状態が棒グラフで表示されます。今回は、 store-ads と store-discounts でグルーピングを行っているので、これらのグラフが表示されました。その下には、いつエラーが起こったのかを示す履歴グラフが表示されています。アラート時の挙動を見るためにしきい値を低く設定しているため、23:30頃のモニター作成とほぼ同時にアラート状態になっていることが分かります。 更にその下にあるのは評価グラフです。履歴グラフが生データ(未加工のデータ)を表示しているのに対し、評価グラフではこの生データに対して前述の手順「 メトリクスの定義 」の「評価の詳細」において定義済みの集計関数を適用した結果の値がアラート条件とともに視覚化されています。すなわち、今回の設定では過去5分間のエラー数の和がしきい値とともに示されています。 参考:新UI ステータス画面はまもなく新しいものに切り替わる予定です。 新しいUIでは、Visualize asの下の選択を切り替えることで生データのグラフ、評価値のグラフ、状態変化のグラフを表示することができます。   おわりに いかがでしたか?「Datadogのコースで学んでみたいけど、英語だからちょっとなぁ…」と思っている方にとって、この記事が足がかりとなれば幸いです。 ここまでお読みいただき、ありがとうございました。
アバター
膨大な構造化データ、半構造化データを抱え、その中から必要な情報を素早く分析したい。でも、システムに組み込むような本格的な分析ではなく、アドホックに分析したい。そんな時、一体何から始めたら良いのか途方に暮れた事はありませんか? 原因調査・仮説検証などスポット的なデータ分析を効率化したい データ量が多く処理時間・コストを短縮するためにデータを圧縮したい 可視化ツールを活用してレポートを作成したい 本記事では、 上記のような課題を解消するために、Amazon Athenaを用いて効率的にデータ化し、実行時間とスキャンデータ量を削減する方法と、Amazon Managed Grafanaを活用したデータ可視化方法をご紹介します。具体的な手順やサンプルを交えながら、Athenaでのクエリ作成からGrafanaでのダッシュボード構築までを解説します。データ可視化にお困りの方は、ぜひ読んでみてください。 アドホックなデータ分析をAWSで実現する3つのステップ アドホックデータ分析は、従来のデータウェアハウス構築・運用では時間やコストがかかりすぎるという課題があります。 Amazon AthenaではSQL文により、これらのデータを直接分析できるため、迅速なデータ分析が可能です。 Athenaに対応するファイル形式のデータをAWS S3に保存します。 データ変換やパーティショニングなどの前処理が必要な場合はAmazon Athenaを用いてSQLベースで実行し、分析に適した形式に整えます。 整形されたデータをAmazon Managed Grafanaに接続し、動的なダッシュボードと可視化ツールを用いて分析、洞察の抽出を行います。 S3は大容量データの保管に適したストレージであり、AthenaはS3に保管したファイルに対してSQLを用いたデータ操作が可能なため、データ準備に最適です。Grafanaは多様なデータソースに対応した可視化ツールで、Athenaで準備したデータを用いて効果的なデータ分析を実現できます。 どのようなデータ構造に対応しているの? 構造化データと半構造化データは、Athenaでのクエリが可能です。CSVやParquetなどの形式は構造化データとして、JSONやAvroは半構造化データとして扱われます。これらのデータ形式はAthenaで容易に分析が行えます。一方で、非構造化データは、Athenaで直接分析するのが難しく、別途構造化や前処理が必要です。 データ形式 概要 Amazon Athena対応 構造化データ スキーマを持つデータ 例:CSV、Parquet、ORCなど 〇 半構造化データ 部分的にスキーマを持つデータ 例:JSON、Avroなど 〇 非構造化テキストデータ 明確なスキーマを持たないデータ 例:Logstashログなど 〇 非構造化データ 明確なスキーマを持たないデータ 例:PDF、画像、動画 × どのようなファイル形式に対応しているの? Amazon Athenaで取り扱い可能なファイル形式は、構造化データ形式(CSV、TSV、カスタム区切り、 Apache Parquet、ORC )、半構造化データ形式(JSON、Apache Avro、Amazon Ion、CloudTrailログ)、ログファイル形式(Apache WebServerログ、Logstashログ)です。 ファイル形式 説明 Amazon Ion JSONと互換性のあるデータ形式 Apache Avro データシリアライズフレームワーク Apache Parquet 列指向ストレージ形式 Apache WebServer ログ パターンマッチングが可能なテキストデータ形式 CloudTrail ログ JSON形式 CSV カンマ区切り値 カスタム区切り ユーザー定義の区切り文字で分けられたテキストデータ形式 JSON 階層型データフォーマット Logstash ログ パターンマッチングが可能なテキストデータ形式 ORC 列指向ストレージ形式 TSV タブ文字で区切られたテキストデータ形式   データ用に SerDe を選択する - Amazon Athena 特定のデータ形式のテーブルを作成するために Athena で使用できる SerDe ライブラリを参照してください。 docs.aws.amazon.com データの可視化は簡単にできますか? Athenaのクエリ結果をAmazon Managed Grafanaのデータソースとして設定することで、簡単に連携が可能です。 Amazon Managed Grafanaは、複数のビルトインデータソースをサポートしており、さまざまなAWSサービスやその他のデータソースを可視化できます。Athenaのクエリ結果を動的なダッシュボードで表示し、多様な可視化オプションによりカスタマイズすることができます。   ビルトイン データソースに接続する - Amazon Managed Grafana すべての Amazon Managed Grafana ワークスペースで、次のデータソースがサポートされています。 docs.aws.amazon.com データ保管場所 分析対象のデータとクエリ実行結果は以下のようにS3バケット内の別々のフォルダに保存します。 データファイル保管場所: s3://<バケット>/athena/orgdata/sample.csv クエリなど実行結果の保管場所: s3://<バケット>/athena/query/   Amazon S3 でテーブルの場所を指定する - Amazon Athena Athena でテーブルを作成するときに、データの場所を指定する方法の詳細について説明します。 docs.aws.amazon.com   Athenaは指定されたLocation内のサブフォルダを再帰的にスキャンするので、スキャン対象以外のファイルは別のフォルダに保管します。 Athenaによるデータ分析の準備 Athenaを用いたアドホック データの取り込み、データ形式の変換、パーティショニング、アクセス用テーブルについて解説し、それぞれのテーブルの実行時間とスキャンデータ量を比較します。 データ取込みテーブル作成 本ステップでは、S3に保存されたデータ(今回はCSVファイルを使用)を読み込むための外部テーブルを作成します。データ構造をAthenaに伝え、データを読み込めるように準備します。  CREATE EXTERNAL TABLE IF NOT EXISTS csv ( 中略 ) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde' WITH SERDEPROPERTIES ( "separatorChar" = ",", "quoteChar" = "`", "escapeChar" = "\\" ) LOCATION 's3://<bucket>/athena/orgdata/' TBLPROPERTIES ('skip.header.line.count' = '1');   CSV を処理するための Open CSV SerDe - Amazon Athena Athena でカンマ区切りのデータ用のテーブルを作成する場合は、Open CSV SerDe ライブラリを使用します。 docs.aws.amazon.com   ROW FORMAT SERDE や SERDEPROPERTIES で適切なフォーマットを指定している点が重要です。 データ変換とパーティショニング 本ステップでは、各種変換処理を行い、新しいテーブルを作成します。例えば、Unixタイムスタンプは使いやすいデータに変換してカラムを追加します。テキスト形式からParquet形式への変換は、列指向フォーマットによる特定カラムへのアクセス高速化、効率的な圧縮によるスキャンデータ量の削減、そして各種データセットへの適合性から、クエリパフォーマンスが向上します。特に、SNAPPY圧縮は高速な圧縮と解凍により、クエリ実行速度向上に効果的です。また適切なパーティショニングにより、クエリ対象のデータが絞り込まれ、スキャンデータ量の削減によるパフォーマンス向上とコスト削減を実現します。 CREATE TABLE enhanced_csv_data WITH ( format = 'PARQUET', parquet_compression = 'SNAPPY', external_location='s3://<bucket>/athena/parquet/', partitioned_by = ARRAY['year', 'month', 'day'] ) AS SELECT *, CAST(date_format(from_unixtime(CAST(timestamp AS BIGINT) / 1000), '%Y%m%d%H%i%s') AS VARCHAR(14)) AS datetime_ymdhhmmss, year(from_unixtime(CAST(timestamp AS BIGINT) / 1000)) AS year, month(from_unixtime(CAST(timestamp AS BIGINT) / 1000)) AS month, day(from_unixtime(CAST(timestamp AS BIGINT) / 1000)) AS day FROM csv;   CREATE TABLE AS - Amazon Athena インライン SELECT クエリの結果を含むテーブルを作成します。 docs.aws.amazon.com 前ステップで作成した外部テーブルを圧縮しようとしたところ、以下のエラーが発生しました。パーティションキーのデータ型の指定がよくなかったようです。String型で再作成する事でエラーは解消されました。 アクセス用テーブル作成 前ステップで作成したパーティション付きデータへアクセスするための外部テーブルを作成します。テーブル名やカラム名などのメタデータ定義、LOCATION句によるデータ指定、パーティション情報の取得、tblpropertiesによるデータフォーマットと圧縮形式の指定といった役割を担います。 CREATE EXTERNAL TABLE IF NOT EXISTS parquet_csv_data ( 中略 ) STORED AS PARQUET LOCATION 's3://<bucket>/athena/parquet/' tblproperties ("parquet.compression"="SNAPPY");   Parquet SerDe - Amazon Athena Parquet SerDe を使用して、Parquet データから Athena テーブルを作成します。 docs.aws.amazon.com 各テーブルの実行時間とスキャンデータ量の比較 3つのテーブルに対して同一のクエリを実行して、実行時間とスキャンデータ量を比較すると以下となりました。 対象テーブル 実行時間 スキャンデータ量 CSVデータ取込みテーブル 6.29sec 127.96MB データ変換テーブル 6.71sec 12.69MB アクセス用テーブル 3.84sec 9.99MB CSVデータ取込みテーブルとアクセス用テーブルを比較すると、実行時間は38%削減されました。データ変換テーブルでCSV取込みテーブルよりも若干実行時間が増えているのはパーティションスキャンによるオーバーヘッドが考えられます。パーティションのスキャンデータ量を比較すると、列指向の特性と圧縮の効果により、スキャンデータが92%削減されました。圧縮により大幅にスキャンデータ量は削減されますが、それ以外にもテーブル構造やパーティションの違いによりクエリ実行結果に違いが出ることがわかりました。 複雑なテーブル構造はクエリ実行結果に影響を及ぼすので、パーティションの粒度や不要なカラムは除外するなど対処が必要です。   Grafanaによるデータ可視化 本章では、AthenaデータソースをGrafanaに設定し、Grafana Exploreでクエリを実行、ダッシュボードを作成する方法を紹介します。 アクセス制御 GrafanaからAthenaのデータ取得とS3へのログ書き込みについて、以下を参考にIAM 権限の付与を行います。 Amazon Managed Grafana の AWS マネージドポリシー - Amazon Managed Grafana Amazon Managed Grafana の AWS マネージドポリシーと、そのポリシーへの最近の変更について説明します。 docs.aws.amazon.com GrafanaにAthenaデータソース設定 データ転送を行うGrafana ワークスペースの[データソース]タブでAthena のデータソースを有効化し、アタッチ済みとなる事を確認します。               次にGrafanaのコンソールにログインし、データソースにAthenaが利用可能となっているか確認します。未利用の場合はinstall nowをクリックします。 Amazon Athenaに関するプラグイン説明画面が表示されるので、右上よりインストールします。 プラグインのインストールが完了するとAdd new data sourceと表示されるのでクリックします。 ここでは主にAthena Detailsの設定をします。 Grafana Exploreにてクエリ実行 Athenaとの接続が完了するとData Sourcesに新たに追加されるので、Exploreをクリックします。 Grafana Dashboardの作成 ここではGrafanaのデータソースとしてAthenaからの応答が正しい事をテストクエリを実行して確認します。特に問題なければ画面右上の+Addを展開してAdd to dashboardをクリックします。 次にOpen dashboard、Add visualizationをクリックするとダッシュボードのエディット画面が表示されるので、可視化を行いたいクエリを実行します。この時点では期待する結果とならない場合もありますので、Open visualization suggestionsからお好みの視覚化オプションを選択します。 視覚化オプションは多数あり、ダッシュボード上に異なるパネルを作成する事ができます。 さいごに Amazon S3、Amazon Athena、Amazon Managed Grafanaを使ったデータ分析ワークフローを3つのステップで解説しました。本記事が皆様のデータ活用にお役立ていただければ幸いです。
アバター