- 投稿日:2020-12-27T23:19:10+09:00
サブネットをインターネットと接続する方法
■ インターネットに接続するための回線を引き込む
Amazon VPCにおいて、あるサブネットをインターネットに接続するには、「インターネットゲートウェイ(Internet Gateway)」を用いる。
これは、「自分のネットワークにインターネット回線を引き込む」というイメージの作業となる。
■ 作業手順
① メニューから[インターネットゲートウェイ]をクリックして開き、[インターネットゲートウェイの作成]をクリックする。
② [作成]をクリックする。
③ 作成したインターネットゲートウェイにチェックを付けて、[アクション]をクリックし、[VPCにアタッチ]メニューを選択する。
④ 結びつける先のVPC領域を尋ねられるので、使用可能なVPCに「前回作成したVPC領域」を選択して、[インターネットゲートウェイのアタッチ]をクリックする。
■ ルーティング情報
ネットワークにデータを流すためには、「ルーティング情報」と呼ばれる設定が必要となる。この設定は、「ルートテーブル(Route Table)」などと呼ばれる。
インターネットで使われる「TCP/IP」というプロトコルでは、データを細切れにした「パケット(Packet)」という単位で、データを送受信されている。
パケットは、さまざまな「ヘッダー情報」と「データの実体」を含んでいる。ヘッダー情報の1つに、「宛先IPアドレス」がある。
TCP/IPでは、ネットワーク機器である「ルーター」が、この「宛先IPアドレス」を見ながら、「もっとも宛先IPアドレスに近い方のネットワーク」へと、次々とパケットを送信していき、最終目的地までパケットを到達させる。
パケットをうまく到達させるために、「宛先IPアドレスの値が、いくつのときには、どのネットワークに流すべきか」設定する必要がある。この設定のことを「ルートテーブル」という。
ルートテーブルは、宛先アドレスを流すべきネットワークの入り口となるルーターという書式で設定する。
宛先アドレスのことを「ディスティネーション」という。「流すべきネットワーク先」は「ターゲット」などという名称でよぶ。
■ Amazon VPCでルートテーブルを設定する。
◯ デフォルトのルートテーブル
VPC領域を作成したあとは、デフォルトのルートテーブルが作られる。そしてサブネットを作成したときには、そのデフォルトのルートテーブルが適用される。サブネットを作成した後に、どのようなルートテーブルが設定されているのかは次のように確認できる。
■ 作業手順
サブネットに対して設定されているルートテーブルを確認する。
① [サブネット]メニューを選択して、確認したいサブネットにチェックを入れて[詳細]から[ルートテーブル]の値を確認する。
② [ルートテーブル]メニューを選択して、先ほど確認した[ルートテーブル]にチェックをつける。その後に[ルート]タブを選択して[設定値]を確認する。
■ デフォルトゲートウェイをインターネットに向けて設定する。
このままでは、「10.0.0.0/16以外の宛先のパケット」は全て破棄されるため、インターネットに接続することができません。
インターネットに接続するために、インターネットゲートウェイを作成しました。そこで、「10.0.0.0/16以外の宛先のパケット」をインターネットゲートウェイに転送するように、ルートテーブルを変更することで、インターネットと通信できるようになります。
具体的には、「0.0.0.0/0の範囲の宛先のパケットは、インターネットゲートウェイに転送する」という設定をルートテーブルに追加する。
「0.0.0.0/0」は、すべてのIPアドレス範囲を示している。つまり、「0.0.0.0/0に対するターゲットの設定」は、「転送先が何も設定されていない時の、デフォルトの転送先」を示している。このデフォルトの転送先を「デフォルトゲートウェイ」をよぶ。
■ 作業手順
パブリックサブネットをインターネットに接続する。
① [ルートテーブル]メニューを選択して、[ルートテーブルの作成]をクリックする。
② 名前タグに「パブリックルートテーブル」を、VPCに「前回作成したVPC領域」をそれぞれ入力・選択して[作成]ボタンをクリックする。
③ 先ほど作成したルートテーブルにチェックを入れて[サブネットの関連付けの編集]タグを選択し、[サブネットの関連付け]をクリックする。
④ 割り当てたいサブネットにチェックを入れて、[保存]ボタンをクリッっくする。
⑤ [ルート]タグを選択して、[ルートの編集]をクリックする。
⑥ [ルートの追加]をクリックして、送信先に「0.0.0.0/0」を、ターゲットに「igwから始まる宛先(インターネットゲートウェイ)」をそれぞれ入力・選択する。
⑦ [サブネット]メニューを選択して、関連付けしたサブネットにチェックを入れる。その後に[ルートテーブル]タグを選択して、[ルートテーブル]が先ほど作成したパブリックルートテーブルに変わっていることを確認する。また、[0.0.0.0/0]がインターネットゲートウェイに設定されていることも確認する。
- 投稿日:2020-12-27T22:47:56+09:00
AWSでのドメイン取得からhttps化まで
はじめに
AWSで独自ドメインを取り、証明書を発行しALBをhttps化するまでのメモ
やり方
ドメイン取得
- AWSコンソールからRoute53を開き、右側の「ドメイン」をクリックします。
- 上部にある「ドメインの登録」ボタンをクリックします。
- 好きなドメインを検索し、「カートにいれる」→「続行」→「注文を完了」と進めます。
ドメインの購入には最大で1日ほど時間がかかるようですが、自分の場合は30分ほどで手続きが完了しました。手続きが完了すると、登録してあるメールアドレスにメールが届きます。
証明書の作成
- AWSコンソールからCertificate Managerを開きます。
- 「証明書をリクエスト」をクリックし、パブリック証明書にチェックを入れ次のページへ進みます。
- ドメイン名に先ほど購入したドメインを入力します。この時「*.<ドメイン名>」という形式で入力してください。このようにしないと、サブドメインを切った時に、この証明書を使い回すことができなくなります。
- 「検証方法」ではEメールにチェックを入れます。(DNSの登録では、自分はうまく行きませんでした。。。)
- しばらく待つと、登録してあるメールアドレスに検証用のメールが届くので、案内にしたがって検証を完了します。
ALBの作成
- AWSコンソールからロードバランサー」のページを開き、上部の「ロードバランサーの作成」をクリックします。
- 「Application Load Balancer」を選択し、「ロードバランサーのプロトコル」をhttpsにします。
![]()
- セキュリティ設定の構成で、「証明書タイプ」を「ACM から証明書を選択する」にし、「証明書の名前」で先ほど作成した証明書を選択します。
Route53の設定
今のままでは、ALBのドメイン名と証明書のドメイン名が異なっているので、証明書のエラーが発生するので、ALBのドメインにエイリアスを貼ります。
- AWSコンソールからRoute53を開き、左側の「ホストゾーン」
- 先ほど取得したドメインをクリックし、「レコードを作成」ボタンをクリックします。
- ルーティングポリシーから「シンプルルーティング」を選択し、次へ進みます。この画面にならない時は、「ウィザードへ切り替える」をクリックしてください。
5. 「シンプルなレコードを定義」をクリックし、サブドメインを入力します。「エンドポイント」は「Application Load BalancerとClassic Load Balancerへのエイリアス」にし、先ほど作成したALBを選択して保存します。
![]()
以上で作業は終わりです。
Route53に登録したレコードの値で、httpsでアクセスできます。
- 投稿日:2020-12-27T21:45:33+09:00
ALBにCognitoの認証をかける
はじめに
ベーシック認証のようなものを、ALBとCognitoを使って実現する話です。
こんなやつ。やり方
userpoolの作成
AWSコンソールからcognitoを開き、右上の「ユーザープールを作成する」ボタンをクリックします。
プール名を入力し、「デフォルトを確認する」をクリックし、「プールの作成を」クリックします。
設定を変更したい方は、お好みで変更してから作成してください。
続いて、アプリクライアントを作成します。左側のメニューのアプリクライアントから作成します。設定内容はデフォルトのままで大丈夫です。
続いて、ALB認証時に使うドメインの登録をします。同じく左メニューの「ドメイン名」から好きなドメインを登録してください。
最後に、左メニューから「アプリクライアントの設定」をクリックし、以下のようにします。は実際に認証をかけるALBの物に書き換えてください。
- 有効なIDプロバイダの「Cognito User Pool」にチェック
- 認証されているOAuthフローの「Authorization code grant」にチェック
- 許可されている OAuth スコープの「openid」にチェック
- コールバックURLに「https:///oauth2/idpresponse」を入力
ALBの設定
httpsで接続できるALBが必要です。作成の仕方はこちらを参考にしてください。https://qiita.com/ggg-mzkr/items/9924f729df15762dda28
AWSのALB一覧から、認証をかけるALBを選択し、したのメニューから「リスナー」タブを開きます。そして、認証をかけたいリスナーに対して「ルールの表示/編集」をクリックします。
詳細画面が出てくるので、上にある鉛筆マークをクリックすると、リスナーの横に鉛筆マークが現れるので、そちらをクリックします。
「アクションを追加」 → 「認証」 を選択し、先ほど作ったuserpoolとアプリクライアントを選択して、右上の「更新」ボタンを押します。
画面表示
この時点で、ALBへアクセスを行うと、冒頭のような画面にリダイレクトされます。
ログインするためのユーザーは、cognitoの「ユーザーとグループ」から作成することができます。以上。
- 投稿日:2020-12-27T21:44:43+09:00
【非公式】re:Invent 2020 IoT関連発表リスト
はじめに
re:Invent 2020では大量の発表がありましたが、その中からIoT関連のものを収集しました。
元データ
元データとなったリストはこちらにあります。元データが更新されたらこちらも随時更新します。
ご注意
このリストは公式のものではありません。記載されている情報の正確さについては保証しません。サービスや機能の利用に当たっては、必ずAWS公式の資料を参照してください。
関連情報
re:Invent 2020で行われたIoT関連の講演はAWSから公式にリストが公開されています。ぜひこちらもご覧ください。
協力のお願い
見逃し、勘違いなどあるかと思います。何か気付いたことがあればこちらからIssue、PRなどお寄せください。
re:Invent 2020 IoT関連発表リスト
- 投稿日:2020-12-27T21:02:55+09:00
【AWS】WordPressのサイトをSSL化した際のエラー対処
はじめに
ドメインをお名前.comで取得して、AWSで作成したWordpressのサーバーでSSLを試みた際に
いくつかエラーに遭遇したので備忘録として残します。環境
- Route53
- ACM( AWS Certificate Manager)
- ALB( Application Load Balancer)
- EC2( Elastic Compute Cloud)
- Apache
- WordPress
エラー内容
①リダイレクトループのエラー
②wordPress管理画面の権限エラー
修正内容
Route53の設定変更
- AレコードにELBのDNS名を設定
お名前.comの設定変更
- NSレコードをRoute53と同様のNSを設定
※digコマンドでDNS情報を確認
WordPress管理画面の一般設定変更
- WordPressアドレスとサイトアドレスを
http
からhttps
のURLへ変更wp-config.phpの編集
以下を
require_once ABSPATH . 'wp-settings.php';
より上に追加if (empty($_SERVER['HTTPS'])) { $_SERVER['HTTPS'] = 'on'; $_ENV['HTTPS'] = 'on'; }最後に
リダイレクト設定は行なっていなかったのに、リダイレクトループのエラーが出たので
最初Apache(.htaccess)を確認していきましたが、特にそちらには問題ありませんでした。
おそらくWordPressにプラグイン等をインストールしていたのでそちらの設定が影響していたと思われます。
環境によって対応方法は異なりますので参考程度に見ていただければ幸いです。
- 投稿日:2020-12-27T19:46:44+09:00
AWS Protonのメモ
メモ
AWSが用意してくれた、ECSのサンプルをベースに自分なりに整理
(注:どんなサービスも環境側とサービス側で定義可能)解釈が微妙な奴もあるかも(特にサービスとサービスインスタンスがまだ微妙)
Proton概要
AWS Proton:コンテナおよびサーバーレスデプロイメント向けの管理の自動化
Protonのサンプル(READMEがチュートリアルみたくなってる)
aws-samples/aws-proton-sample-templates
ProtonのECSのサービスサンプル
aws-samples/aws-proton-sample-fargate-service
ProtonのLambdaのサービスサンプル
aws-samples/aws-proton-sample-lambda-crud-service
Protonのロードマップ
Protonのクォータ
- 投稿日:2020-12-27T19:26:57+09:00
DocumentDBの初期値が不親切な件
DocumentDBとは
MongoDB互換のAWSマネージドサービスです。ドキュメント指向型のNoSQLでJSONやXMLをそのまま保管できます。
DocumentDBのインスタンス料金
(参考:https://aws.amazon.com/jp/documentdb/pricing/)
料金は、インスタンスの作成から終了あるいは削除までのインスタンス時間単位で計算されます。
一番小さいもので1日あたり0.119USD×24=2.856USDです。
インスタンスクラスの初期値が不親切な件
以下は、AWSでDocumentDBを作成する時の最初の画面です。
赤字枠の部分が「db.r5.large」になっています。一番小さい「db.t3.medium」ではありません。これ罠ですね。デフォルトのまま作成すると1日あたり8.016USDとなり「db.t3.medium」の約3倍になるのでご注意を!
- 投稿日:2020-12-27T18:23:27+09:00
Aurora Serverlessの接続数について調べた
はじめに
Aurora Serverlessへ接続するアプリで「Too many connections」というエラーが頻発したので接続数について調べたことのメモ。
接続数はいくつに設定されているか
これはパラメータグループで確認できる。以下のようにキャパシティー(ACU)に応じて変化する設定になっている。
各ACUごとの接続数は以下の通り
1ACU:90
2ACU:180
4ACU:270
8ACU:1000
(参考:https://qiita.com/kaba-chan/items/bfd1d8d333d277d6e11a)現状ACUの最大値を1に設定しているので、最大90接続までということになる。
ACUに対する接続数を増やすことができるか
例えば以下のように設定すれば1ACUでも180の接続を許可できるのではないか
GREATEST({log(DBInstanceClassMemory/805306368)*90},{log(DBInstanceClassMemory/8187281408)*2000})AWSのサポートに問い合わせてみたところ、ACUを増やす以外ないと回答あり。
質問
max_connection のパラメータをデフォルトより増やし、かつスケールさせる設定としたい。
回答
max_connections を大きくするには現状 ACU を大きくとっていただくほかない状況であり、お客さま>ご想定のようにデフォルトより大きくした状態でかつスケールさせるといった設定値には対応いたしておりません。
接続数が足りない場合ACUは増えるか
ACUの最大値を増やしたとして、接続数だけが足りない場合にACUは増えてくれるのか。
マニュアルには以下のように記載されている。Aurora Serverless DB クラスターに割り当てられたキャパシティーは、クライアントアプリケーションで生成される負荷(CPU 使用率と接続数)に基づいてシームレスにスケールアップ/ダウンされます。また、DB クラスターの容量設定で、一時停止と再開のオプションを有効にすると、接続がないときに容量を 0 にスケールできます。詳細については、「Aurora Serverless の自動的な一時停止と再開」を参照してください。
接続数が足りなければスケールアップするように見えるが、詳細な条件は公開されてない模様。
- 投稿日:2020-12-27T17:37:38+09:00
SSMオートメーションでDocDB自動停止をやってみた
はじめに
AWSのRDSは停止しても7日間経過すると自動で起動されてしまいます。知らないうちに起動されて課金されることを防ぐためにRDS(今回はDocDB)を自動停止する仕組みをSSMオートメーションで試してみました。
準備
まずSSMドキュメントを実行するロールを作ります。
AmazonEC2FullAccessは無くても良いですがEC2も停止するケースも考慮して含めています。
作成したロールの信頼関係を編集をします。
作成したロールを選択して信頼関係の編集から
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": [ "events.amazonaws.com", "ssm.amazonaws.com" ] }, "Action": "sts:AssumeRole" } ] }保存すると信頼されたエンティティに2つ表示されます。
events.amazonaws.comはCloudWatch Eventsからcron的に呼び出す際に必要、ssm.amazonaws.comはSSMのマネージメントコンソールからテスト実行する際に必要になります。
次にPassRole用インラインポリシーを作成してアタッチします。
作成したロールのアクセス権限 > インラインポリシーの追加から
これでSSMサービスへこのロールを渡して使えるようになります。
自動化用SSMドキュメントを作る
SSM > ドキュメント > オートメーションを作成する と進んでエディタタブから編集します。
description: stop docdb schemaVersion: '0.3' assumeRole: '{{ AutomationAssumeRole }}' parameters: AutomationAssumeRole: type: String description: (Optional) The ARN of the role that allows Automation to perform the actions on your behalf. default: 'arn:aws:iam::xxxxxxxxxxxx:role/xxxxxxxx-ssm-automation-role' mainSteps: - name: "stop_xxxxxxxx_docdb" action: 'aws:executeAwsApi' maxAttempts: 3 onFailure: Continue inputs: Service: docdb Api: StopDBCluster DBClusterIdentifier: xxxxxxxx-docdb - name: "stop_xxxxxxxx_docdb" action: 'aws:executeAwsApi' maxAttempts: 3 onFailure: Continue inputs: Service: docdb Api: StopDBCluster DBClusterIdentifier: xxxxxxxx-docdbテスト実行
SSM > ドキュメント > 自己所有 > 作成したドキュメント > オートメーションを実行する
後はこのSSMドキュメントをCloudWatch Eventsからcronスケジュールで呼び出せば自動停止の完成です。
- 投稿日:2020-12-27T16:47:22+09:00
AWS KMS (Key Management Service) に関する私的メモ
「AWS認定 セキュリティ – 専門知識」を受験するにあたって、KMSについて個人的にまとめたメモです。
各CMKの比較表
Envelope enryption
「S3データ格納時のサーバ側暗号化(SSE-S3、SSE-KMS、SSE-C)と、クライアント側暗号化(CSE-KMS、CSE-C)」を参照。
BYOK (Bring Your Own Key)のインポート
BYOKのポイント
- 外部キーのインポートは対称暗号のみ可能。
- 外部キーは自動ローテーション不可。
- 再インポートするキーマテリアルは、前回インポートしたものと同じキーマテリアルである必要がある。前回と異なるキーマテリアルをインポートすると
IncorrectKeyMaterialException
が発生する。- 前回と同じキーマテリアルを再インポートできて何が嬉しいの?と思うかもしれないが、再インポートは誤って削除したキーマテリアルを復旧したり、有効期限を過ぎたキーマテリアルを延命する目的で行う。
- BYOKの手動ローテーションを実現する手段はCMKの再作成になる。
以下に、AWS CLIを使用してBYOKを作成する手順を示します。
(作成するCMKのキーIDは、仮に12345678-abcd-1234-abcd-123456789012
とします)1. CMKの作成
マネージメントコンソールを使用したCMKの作成
AWSマネージメントコンソールのKMSから、キーマテリアルオリジンに外部を指定してCMKを作成します。
AWS CLIを使用したCMKの作成
AWS CLIを使用する場合、以下のコマンドラインを実行します。標準出力の内容に含まれるキーIDを取得します。
KEY_ID=$(aws kms create-key --origin EXTERNAL | jq -r '.KeyMetadata.KeyId') echo ${KEY_ID} # 12345678-abcd-1234-abcd-123456789012なお、残念ながら、AWS CLIではCMK作成時にエイリアスを作成・アタッチすることはできません。
2. 公開鍵とインポートトークンのダウンロード
公開鍵のアルゴリズムによって、以下のいずれかを実行します。
- RSAES_PKCS1_V1_5
- RSAES_OAEP_SHA_1
- RSAES_OAEP_SHA_256
RSAES_PKCS1_V1_5# 公開鍵とインポートトークンのダウンロード(RSAES_PKCS1_V1_5) aws kms get-parameters-for-import \ --key-id 12345678-abcd-1234-abcd-123456789012\ --wrapping-algorithm RSAES_PKCS1_V1_5 \ --wrapping-key-spec RSA_2048 \ >kms_get-parameters-for-import.txtRSAES_OAEP_SHA_1# 公開鍵とインポートトークンのダウンロード(RSAES_OAEP_SHA_1) aws kms get-parameters-for-import \ --key-id 12345678-abcd-1234-abcd-123456789012\ --wrapping-algorithm RSAES_OAEP_SHA_1 \ --wrapping-key-spec RSA_2048 \ >kms_get-parameters-for-import.txtRSAES_OAEP_SHA_256# 公開鍵とインポートトークンのダウンロード(RSAES_OAEP_SHA_256) aws kms get-parameters-for-import \ --key-id 12345678-abcd-1234-abcd-123456789012\ --wrapping-algorithm RSAES_OAEP_SHA_256 \ --wrapping-key-spec RSA_2048 \ >kms_get-parameters-for-import.txt3. 公開鍵とインポートトークンのBASE64デコード
cat kms_get-parameters-for-import.txt | jq -r '.PublicKey' >PublicKey.b64 cat kms_get-parameters-for-import.txt | jq -r '.ImportToken' >ImportToken.b64 openssl enc -d -a -A -in PublicKey.b64 -out PublicKey.bin openssl enc -d -a -A -in ImportToken.b64 -out ImportToken.bin4. キーマテリアルの生成
キーマテリアルの生成printf "0123456789ABCDEF0123456789ABCDEF" > PlaintextKeyMaterial.bin5. キーマテリアルの暗号化
「2. 公開鍵とインポートトークンのダウンロード」で選択した公開鍵のアルゴリズムによって、以下のいずれかを実行します。
RSAES_PKCS1_V1_5# 公開鍵でキーマテリアルの暗号化(RSAES_PKCS1_V1_5) openssl rsautl -encrypt \ -in PlaintextKeyMaterial.bin \ -pkcs \ -inkey PublicKey.bin \ -keyform DER \ -pubin \ -out EncryptedKeyMaterial.binRSAES_OAEP_SHA_1# 公開鍵でキーマテリアルの暗号化(RSAES_OAEP_SHA_1) openssl rsautl -encrypt \ -in PlaintextKeyMaterial.bin \ -oaep \ -inkey PublicKey.bin \ -keyform DER \ -pubin \ -out EncryptedKeyMaterial.binRSAES_OAEP_SHA_256# 公開鍵でキーマテリアルの暗号化(RSAES_OAEP_SHA_256) openssl pkeyutl -encrypt \ -in PlaintextKeyMaterial.bin \ -pkeyopt rsa_padding_mode:oaep \ -pkeyopt rsa_oaep_md:sha256 \ -inkey PublicKey.bin \ -keyform DER \ -pubin \ -out EncryptedKeyMaterial.bin6. キーマテリアルのインポート
# 暗号化後のキーマテリアルのインポート aws kms import-key-material \ --key-id 12345678-abcd-1234-abcd-123456789012 \ --encrypted-key-material fileb://EncryptedKeyMaterial.bin \ --import-token fileb://ImportToken.bin \ --expiration-model KEY_MATERIAL_EXPIRES \ --valid-to "2020-12-31T12:00:00-08:00"7. キーマテリアルの削除
キーマテリアルの削除aws kms delete-imported-key-material --key-id 12345678-abcd-1234-abcd-123456789012
- 投稿日:2020-12-27T15:35:09+09:00
【AWS】WordPress削除の備忘録
はじめに
WordPressをAWS上で立ち上げたものの、その後あまりブログを更新することもなく。。。
EC2使っていたので料金も月数千円ほどかかってしまい、維持が大変だったのでWordPressを削除したいと思います。
特にこれといったAWS上のWordPress削除方法に関する記事もなかったので、普通にAWS上のサーバー等を消したいと思います。もしやり方など間違っていたらご了承ください。今回削除するWordPressの構成は、以下のURL通り。
https://qiita.com/blackpeach7/items/be28e2249f308bac7592
ドメインはお名前.comで購入。
ドメインの廃止
お名前.comでドメインの登録をしたが「ドメインの廃止の手続きは必要なのか?」と疑問に思い調べてみると、以下の通りだった。
「※ドメイン廃止をご希望の場合、自動更新設定が解除済みであれば、
更新期限日を迎えると自動的に廃止されますので、特にお手続などを行う必要はございません。」自分の場合、自動更新設定はされてなかったので、特に設定などは不要であるということがわかった。
手順
EC2
Amazon EC2 リソースを削除または終了する方法を教えてください。
上記に従って削除・終了する。
※終了保護設定している場合は解除を事前にしておく。インスタンスの削除。
ロードバランサーの削除。
Auto Scalingの削除。
Elastic IPアドレスの削除。
AMIの削除。
RDS
Amazon RDS リソースを削除または終了するにはどうすればよいですか?
上記に従って削除・終了する。
※終了保護設定している場合は解除を事前にしておく。VPC
Amazon VPC を削除しようとしたのですが、依存関係エラーが発生しました。Amazon VPC を削除する方法を教えてください。
上記に従って削除・終了する。VPCを削除しようとすると、下記のようなエラーメッセージが出ることがあるので、その場合メッセージに従って実行する。
エラーに従い、今回の場合ネットワークインターフェイスの削除を行う。
その後VPCに戻り、無事に削除できた。
Route53
Route 53 ホストゾーンを削除する方法を教えてください。
上記にに従ってサービスを削除・終了する。Route 53ホストゾーンを削除する為、SOAとNS以外のホストゾーンに関連付けられている全てのレコードを削除する。
パブリックホストゾーンの削除。
上記のようにしてWordPressを終了させた。
- 投稿日:2020-12-27T11:55:34+09:00
AWS SAMでAPIGatewayからLambdaをエイリアス指定で呼び出す
はじめに
AWS CloudFormationの拡張機能であるAWS SAMを使って、APIGatewayとLambdaを管理するユースケースは多いと思います。SAMのメリットのひとつとしては、少ない記述量で済むことが挙げられると思います。
さて、このSAM。APIGatewayとLambdaを素直に管理するのには良いのですが、プロジェクトが成熟したときに出てくるかもしれない、このような要望には少々難しいところがあります。
私「できた。SAM最高。」
SRE「そういえば、LambdaをバージョニングしてAPIGatewayからはエイリアス指定のLambdaを呼び出すようにできたりしますかー?」
私「Lambdaをデプロイするタイミングで最新バージョンに向いてしまうのはアレですもんね。エイリアスLambdaをトリガーするようにしつつ、エイリアスは手動で張替えしたいということですよね。バージョニングは何かあったときにROLLBACKもできますし。」
私「調査します!」
... 数時間後 ...
私「SAMだけでは、できなくないかい...これ...?」
Versioningなし & AliasなしのLambda
template.yaml
GET /hello
というAPIを作成するtemplateを例として書いていきます。AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Lambda handler for API Gateway. Globals: Function: Runtime: go1.x Timeout: 30 Resources: ApiGateway: Type: AWS::Serverless::Api Properties: EndpointConfiguration: REGIONAL Name: MyAPI StageName: prod MyLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: myFunction CodeUri: functions/hello/ Handler: app Events: GetHelloApi: Type: Api Properties: RestApiId: !Ref ApiGateway Path: /hello Method: GETはい、これだけです。
template.yamlをvalidationする
$ sam validate template.yaml is a valid SAM Template
注意点として、
sam validate
は、あくまでテンプレートの構文チェックをしてくれるだけです。つまり、記述内容がデプロイ先のAWS環境と整合性がとれているかといったチェックまではしてくれません。
ですので、初回は、試行錯誤しながら、CloudFormationから吐かれるエラーを潰しながらデプロイしていきました。build & deploy
$ GOOS=linux GOARCH=amd64 go build -o functions/hello/app ./functions/hello $ sam package --template-file template.yaml --output-template-file packaged.yaml --s3-bucket $DEPLOYMENT_TARGET_BUCKET $ sam deploy --template-file packaged.yaml --stack-name $STACK_NAME --debug --capabilities CAPABILITY_IAM --no-fail-on-empty-changesetAWS APIGatewayのマネジメントコンソールで確認
GET /hello
がmyFunction関数を呼び出すように設定されました。
Versioningあり & AliasありのLambda
追加要件はざっくりこのような感じです。
- Lambda関数のバージョニングをする。
- Lambda関数には、開発用と本番用のエイリアスを用意する。
- 開発用エイリアスは常に最新バージョンを参照する
- 本番用エイリアスは手動でエイリアスの張替えをする。デプロイする度にバージョン変更されたくない
- APIGatewayからは本番用エイリアスのLambdaを呼び出す。
APIGatewayから呼び出されるLambdaをエイリアス指定しようとすると厳しい現実が突きつけられました。
TypeAWS::Serverless::Function
ではLambda関数を指定するんですが、FunctionName
にはARNの指定ができないため、エイリアスを指し示すLambda関数の指定ができません。結論、ほぼSAMの記述(
AWS::Serverless
)は使えずにCloudFormationの記述方法で記載することになりました。template.yaml
AWSTemplateFormatVersion: 2010-09-09 Transform: AWS::Serverless-2016-10-31 Description: Lambda handler for API Gateway. Globals: Function: Runtime: go1.x Timeout: 30 Resources: # ApiGateway: # Type: AWS::Serverless::Api # Properties: # EndpointConfiguration: REGIONAL # Name: MyAPI # StageName: dev ApiGateway: Type: AWS::ApiGateway::RestApi Properties: EndpointConfiguration: Types: [ REGIONAL ] Name: MyAPI ApiGatewayResourceHello: Type: AWS::ApiGateway::Resource Properties: ParentId: !GetAtt ApiGateway.RootResourceId PathPart: 'hello' RestApiId: !Ref ApiGateway ApiGatewayMethodGetHello: Type: AWS::ApiGateway::Method Properties: AuthorizationType: NONE HttpMethod: GET ResourceId: !Ref ApiGatewayResourceHello RestApiId: !Ref ApiGateway Integration: IntegrationHttpMethod: POST Type: AWS_PROXY Uri: !Join [ '' , [ 'arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/', !GetAtt MyLambdaFunction.Arn, ':PROD', '/invocations' ] ] ApiGatewayDeployment: Type: AWS::ApiGateway::Deployment DependsOn: [ ApiGatewayMethodGetHello ] Properties: RestApiId: !Ref ApiGateway ApiGatewayStage: Type: AWS::ApiGateway::Stage Properties: DeploymentId: !Ref ApiGatewayDeployment RestApiId: !Ref ApiGateway StageName: prod MyLambdaFunctionAliasProd: Type: AWS::Lambda::Alias Properties: FunctionName: !GetAtt MyLambdaFunction.Arn FunctionVersion: $LATEST Name: PROD MyLambdaFunction: Type: AWS::Serverless::Function Properties: FunctionName: myFunction CodeUri: functions/hello/ Handler: app AutoPublishAlias: DEV # Events: # GetHelloApi: # Type: Api # Properties: # RestApiId: !Ref ApiGateway # Path: /hello # Method: GET MyLambdaFunctionPermission: Type: AWS::Lambda::Permission DependsOn: MyLambdaFunctionAliasProd Properties: FunctionName: !Join [ '' , [ !GetAtt MyLambdaFunction.Arn, ':PROD' ] ] Action: lambda:InvokeFunction Principal: apigateway.amazonaws.com SourceArn: !Join [ '' , [ 'arn:aws:execute-api:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':', !Ref ApiGateway, '/*/GET/hello' ] ]結果、あらたな要件に対応したこともあるのですが、step数が27→63stepと倍以上になりました。
いかにSAMが暗黙的に色々なリソースを作成してくれていたか。SAMへのありがたみを感じると同時にCloudFormationの辛みを感じる経験でした。build & deploy
さきほどと同様にdeployすると、Cloudformationでエラーになります。
ApiGatewayResourceHello
リソースを作成する際に、CREATE_FAILED
になってしまいました。Another resource with the same parent already has this name: hello (Service:AmazonApiGateway; Status Code: 409; Error Code: ConflictException; RequestID: f36f9b8b-8275-4fba-bc73-0d3920203333; Proxy:null)これはつまり、stack内で/helloというリソースが競合しているとのこと。
MyLambdaFunction
リソースに定義していたPath: /hello
は削除しているのに競合になってしまうので、一度stackを削除しました。AWS APIGatewayのマネジメントコンソールで確認
GET /hello
がPRODエイリアスのmyFunction関数を呼び出すように設定される。
AWS Lambdaのマネジメントコンソールで確認
PRODエイリアスのLambdaに向けてトリガーが設定されている。
まとめ
一度stackを削除してデプロイするということは、「リソース削除→新規作成」される挙動になるため、API IDが変わってしまいます。API IDが変わってしまうということは、APIGatewayのエンドポイントに変更が生じてしまうということです。
https://{API_ID}.execute-api.{REGION}.amazonaws.com/{STAGE}/{PATH}
要件次第ですが、すべてをSAMで対応することはできないと考えたほうが良いです。VersioningやAlias周りを細かく設定したいのであれば、現時点では素のCloudFormationで書くのが良さそうです。
SAMを書いているのか、CloudFormationを書いているのか迷子になることがありました。そんなときはリソースのTypeを見て、
AWS::Serverless::
と定義していればSAMなので、「今は(SAM|CloudFormation)を書いている」と確認してました。参考
- 投稿日:2020-12-27T11:33:06+09:00
Go言語でAWS Lambdaの開発をサポートするパッケージを作った
仕事でGo言語 + AWS Lambdaを用いる機会が多く、特にセキュリティ監視関連基盤のバックエンド処理を開発しています(これとかこれとかこれ)。
開発をすすめる中で「こうすると便利だな」というちょっとしたtipsはいろいろあったのですが、あまりに細切れな処理すぎるのでプロジェクト間でコピーするなどして開発に利用していました。とはいえ管理しているプロジェクトが多くなってきたことで挙動がまちまちになってしまったり、ある程度tipsの数が溜まってきたのもあって、パッケージとして切り出してみました。
https://github.com/m-mizutani/golambda
AWSが公式で提供しているPowertools(Python版、Java版)を意識してはいますが、完全に再現する目的では作っていません。また、全てのGo + Lambdaの開発者が「この方法に従うべき!」とも思っていません。例えば、API gatewayによって呼び出されるLambdaは各種Web Application Frameworkで同じような機能がサポートされていることもあり、あまりこのパッケージの恩恵は受けられないと思います。なので「こういう処理をまとめておくと便利」ぐらいな話として見ていただければと思います。
基本的にはデータ処理のパイプラインやちょっとしたインテグレーションなどのためのLambdaを想定しており、以下の4つの機能を実装しています。
- イベントの取り出し
- 構造化ロギング
- エラー処理
- 秘匿値の取得
実装している機能
イベントの取り出し
AWS Lambdaはイベントソースを指定して、そこからの通知をトリガーに起動させることができます。この際、Lambda functionはSQSやSNSといったイベントソースのデータ構造が渡されて起動します。そのため、各種構造データから自分で使うデータを取り出す作業が必要です。
golambda.Start()
という関数にcallback(以下の例ではHandler
)を指定するとgolambda.Event
に必要な情報が格納され、そこから取り出すことができます。package main import ( "strings" "github.com/m-mizutani/golambda" ) type MyEvent struct { Message string `json:"message"` } // SQSのメッセージをconcatして返すHandler func Handler(event golambda.Event) (interface{}, error) { var response []string // SQSのbodyを取り出す bodies, err := event.DecapSQSBody() if err != nil { return nil, err } // SQSはメッセージがバッチでうけとる場合があるので複数件とみて処理する for _, body := range bodies { var msg MyEvent // bodyの文字列をmsgにbind(中身はjson.Unmarshal) if err := body.Bind(&msg); err != nil { return nil, err } // メッセージを格納 response = append(response, msg.Message) } // concat return strings.Join(response, ":"), nil } func main() { golambda.Start(Handler) }このサンプルコードは ./example/deployable ディレクトリにおいてあり、デプロイして実行を試すことができます。
データを取り出す処理を実装している
DecapXxx
という関数とは逆に、データを埋め込む処理をEncapXxx
として用意しています。これによって上記のLambda Functionに対して、以下のようにテストを書くことができます。package main_test import ( "testing" "github.com/m-mizutani/golambda" "github.com/stretchr/testify/require" main "github.com/m-mizutani/golambda/example/decapEvent" ) func TestHandler(t *testing.T) { var event golambda.Event messages := []main.MyEvent{ { Message: "blue", }, { Message: "orange", }, } // イベントデータの埋め込み require.NoError(t, event.EncapSQS(messages)) resp, err := main.Handler(event) require.NoError(t, err) require.Equal(t, "blue:orange", resp) }現状はSQS、SNS、SNS over SQS(SNSをsubscribeしているSQSキュー) の3つをサポートしていますが、後々DynamoDB stream、Kinesis streamも実装しようと考えています。
構造化ロギング
Lambdaの標準的なログ出力先はCloudWatch Logsになりますが、LogsあるいはLogsのビュワーであるInsightsはJSON形式のログをサポートしています。そのため、Go言語標準の
log
パッケージを使うのではなく、JSON形式で出力できるロギングツールを用意するのが便利です。ログの出力形式も含めて、Lambda上でのロギングは概ね要件が共通化されています。多くのロギングツールは出力方法や出力形式について様々なオプションがありますが、Lambda functionごとに細かく設定を変えるということはあまりしません。また出力する内容についてもほとんどの場合はメッセージ+文脈の説明に必要な変数だけで事足りるため、そのような単純化をしたwrapperを
golambda
では用意しました。実際の出力部分では zerolog を利用しています。本当はzerologで作成したロガーをそのまま露出させるというのでも良かったのですが、できることを絞っておいたほうが自分にとってもわかりやすいなと思い、あえてwrapする形にしました。
Logger
というグローバル変数をexportしており、Trace
,Debug
,Info
,Error
というログレベルごとのメッセージを出力できるようにしています。任意の変数を永続的に埋め込めるSet
と、メソッドチェインで値を継ぎ足していけるWith
を用意しています。// ------------ // 一時的な変数を埋め込む場合は With() を使う v1 := "say hello" golambda.Logger.With("var1", v1).Info("Hello, hello, hello") /* Output: { "level": "info", "lambda.requestID": "565389dc-c13f-4fc0-b113-xxxxxxxxxxxx", "time": "2020-12-13T02:44:30Z", "var1": "say hello", "message": "Hello, hello, hello" } */ // ------------ // request ID など、永続的に出力したい変数を埋め込む場合はSet()を使う golambda.Logger.Set("myRequestID", myRequestID) // ~~~~~~~ snip ~~~~~~ golambda.Logger.Error("oops") /* Output: { "level": "error", "lambda.requestID": "565389dc-c13f-4fc0-b113-xxxxxxxxxxxx", "time": "2020-11-12T02:44:30Z", "myRequestID": "xxxxxxxxxxxxxxxxx", "message": "oops" } */また、CloudWatch Logsはログの書き込みに対する料金が比較的高価であり、詳細なログを常に出力しているとコストに大きく影響します。そのため通常は最低限のログだけを出力し、トラブル対応やデバッグの時だけ詳細な出力ができるようにしておくと便利です。
golambda
ではLOG_LEVEL
環境変数を設定することで、ログ出力レベルを外部からいじることができるようにしています。(環境変数だけならAWSコンソールなどから容易に変更可能なため)エラー処理
AWS Lambdaはfunctionごとになるべく単機能になるよう実装し、複雑なワークフローを実現する場合にはSNS、SQS、Kinesis Stream、Step Functionsなどを使って複数のLambdaを組み合わせるようにしています。そのため処理の途中でエラーが起きた場合はLambdaのコード内で無理にリカバリしようとせず、なるべく素直にそのままエラーを返すことで外部からの監視で気づきやすくなったり、Lambda自身のリトライ機能の恩恵を受けやすくなったりします。
一方でLambda自身はエラーをあまり丁寧に処理してくれるわけではないので、自前でエラー処理を用意する必要があります。 先述したとおり、Lambda functionは何かあった場合はそのままエラーを返して落ちる、という構成にしておくと便利です。なので、殆どのケースにおいてエラーが発生した場合はメインの関数(後述する例だと
Handler()
)がエラーを返した場合に一通りまとめてエラーに関する情報を出力してくれると、あちこちのエラー発生箇所でログを出力したりどこかへエラーを飛ばすという処理を書く必要がなくなります。
golambda
では、golambda.Start()
で呼び出した主に以下の2つのエラー処理をしています。
golambda.NewError
あるいはgolambda.WrapError
で生成したエラーの詳細なログの出力- エラー監視サービス(Sentry)へエラーを送信
それぞれ詳しく説明します。
エラーの詳細なログ出力
経験上、エラーが起きたときにデバッグのため知りたいのは大きく分けて「どこで起きたのか」「どのような状況で起きたのか」の2つです。
どこでエラーが起きたのかを知る方法としては、
Wrap
関数を使いコンテキストを追記していく、あるいは github.com/pkg/errors パッケージのようにスタックトレースを持つ、などの戦略があります。Lambdaの場合、なるべく単純な処理になるよう実装する方針であれば、ほとんどの場合はスタックトレースでエラー発生箇所とどのように発生したかを知ることができます。また、エラーの原因となった変数の中身を知ることでエラーの再現条件を把握できます。これはエラーが発生したら関連しそうな変数を都度ログ出力することでも対応できますが、出力行が複数にわたってログの見通しが悪くなってしまいます(特に呼び出しが深い場合)。また、単純にログ出力のコードを繰り返し書かねばならず冗長になり、単純に書くのも大変だしログ出力に関する変更をしたいときに面倒です。
そこで、
golambda.NewError()
あるいはgolambda.WrapError()
1で生成したエラーは、With()
という関数でエラーに関連する変数を引き回せるようにしました。実体は中にmap[string]interface{}
の変数にkey/valueの形で格納しているだけです。golambda.NewError()
あるいはgolambda.WrapError()
によって生成されたエラーをメインロジック(以下の例のHandler()
)が返すと、With()
によって格納した変数と、エラーが生成された関数のスタックトレースをCloudWatch Logsに出力します。以下、コードの例です。package main import ( "github.com/m-mizutani/golambda" ) // Handler is exported for test func Handler(event golambda.Event) (interface{}, error) { trigger := "something wrong" return nil, golambda.NewError("oops").With("trigger", trigger) } func main() { golambda.Start(Handler) }これを実行すると、以下のように
error.values
の中にWith
で格納した変数が、error.stacktrace
にスタックトレースが含まれるログが出力されます。スタックトレースは github.com/pkg/errors の%+v
フォーマットでもテキストで出力されますが、構造化ログの出力に合わせてJSON形式に対応しているのもポイントです。{ "level": "error", "lambda.requestID": "565389dc-c13f-4fc0-b113-f903909dbd45", "error.values": { "trigger": "something wrong" }, "error.stacktrace": [ { "func": "main.Handler", "file": "xxx/your/project/src/main.go", "line": 10 }, { "func": "github.com/m-mizutani/golambda.Start.func1", "file": "xxx/github.com/m-mizutani/golambda/lambda.go", "line": 127 } ], "time": "2020-12-13T02:42:48Z", "message": "oops" }エラー監視サービス(Sentry)へエラーを送信
Sentryでないといけない理由は特にないのですが、APIに限らずLambda functionもWebアプリケーションなどと同様に何らかのエラー監視サービスを使うのが望ましいです。理由は以下のようなものがあります。
- CloudWatch Logsにデフォルトで出力されるログからは正常終了したか異常終了したかの判定ができないため、異常終了した実行のログだけ抽出するというのが難しい
- CloudWatch Logsではエラーをグルーピングするような機能はないため、エラー100件のうち1件だけ種類の違うエラーがある、みたいなやつを見つけ出すのが難しい
両方ともエラーログの出力方法を工夫することである程度解決できなくはないですが、色々気をつけて実装しないとならないため素直にエラー監視サービスを使うのがオススメです。
golambda
ではSentryのDSN (Data Source Name) を環境変数SENTRY_DSN
として指定することでメインロジックが返したエラーをSentryに送信します(Sentry + Goの詳細)。送るのはどのエラーでも問題ありませんが、golambda.NewError
やgolambda.WrapError
で生成したエラーは github.com/pkg/errors と互換性のあるStackTrace()
という関数を実装しているため、スタックトレースがSentry側にも表示されます。これはCloudWatch Logsに出力されるものと同じですが、Sentry側の画面でも確認できるため「通知を見る」→「Sentryの画面を見る」→「CloudWatch Logsでログを検索し詳細を確認する」の2番目のステップでエラーの見当をつけられる場合もあります。あとCloudWatch Logsの検索はまあまあもっさりしているので、検索しないですむならそのほうがよい、というのもあります…。
ちなみにSentryにエラーを送信すると
error.sentryEventID
としてSentryのevent IDをCloudWatch Logsのログに埋め込むので、Sentryのエラーから検索ができるようになっています。秘匿値の取得
Lambdaでは実行環境によって変更するようなパラメータは環境変数に格納して利用することが多いです。個人で使っているAWSアカウントであれば全て環境変数に格納するでよいのですが、複数人で共有して使うようなAWSアカウントでは秘匿値と環境変数を分離しておくことで、Lambdaの情報のみを参照できる人(あるいはRole)と秘匿値も参照できる人(あるいはRole)を分離することができます。これは個人で使っていても真にヤバい情報をあつかうのであれば何らかのアクセスキーが漏れても即死しないように権限を分離しておくケースもあるかもしれません。
自分の場合は権限を分離するため、AWS Secrets Manager を利用することが多いです2。Secrets Managerからの値の取り出しはAPIを呼び出せば比較的簡単ではあるのですが、それでも同じような処理を100回くらい書いて飽きたのでモジュール化しました。構造体のフィールドに
json
メタタグをつければそれで値が取得できます。type mySecret struct { Token string `json:"token"` } var secret mySecret if err := golambda.GetSecretValues(os.Getenv("SECRET_ARN"), &secret); err != nil { log.Fatal("Failed: ", err) }実装しなかった機能
便利そうかなと思いつつ、実装を見送ったものたちです。
- タイムアウト直前に任意の処理を実行:Lambdaは設定された最大実行時間をすぎると無言で死ぬためパフォーマンス分析の情報を出すためにタイムアウト直前に何らかの処理を呼び出すというテクニックがあります。ただ自分の場合は Lambda function がタイムアウトによって無言で死んで困った経験がほとんどないので、なんか便利そうと思いつつ特に手を付けませんでした。
- Tracing:Pythonの Powertools では、アノテーションなどを使ってAWS X-Rayでパフォーマンス計測するための機能が提供されています。Goでこれをやろうとすると現状だと普通に公式SDKを使う以上に楽ができる方法があまり浮かばなかったので、特に取り組みませんでした。
まとめ
ということで、Go言語でのLambda実装における自分なりのベストプラクティスのまとめと、それをコード化したものの紹介でした。冒頭に書いたとおりあくまで自分が必要だったものを作っただけなので、万人に使えるものではないかなと思いますが、なにかの参考になれば幸いです。
こういったエラー生成のメソッドは
errors.New()
やerrors.Wrap()
などとする習わしがあるかと思いますが、個人的にはどのパッケージを使っているのか直感的にわかりにくくなるので、あえて命名法則を変えました。 ↩他にも AWS Systems Manager Parameter Store に秘匿値を入れるというやり方もあります。RDSパスワードなどのローテーション機能がある Secrets Manager の方がサービスの思想としては適切かと思い、個人的にはそちらを使っています。ただコストやAPIレートリミットも違ったりするので、本来であれば要件によって使い分けたほうが良さそうです。 ↩
- 投稿日:2020-12-27T09:02:30+09:00
CloudFormationでEC2&RDSの構築
概要
【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみようを拝見し、EC2+RDSの構築を学びました。
次のステップとして、CloudFormationを使いEC2とRDSを構築したので共有しようと思います。
対象者
- AWSの管理コンソールからRDSを構築された経験がある方
- CloudFormationの知見がある方
構築されるもの
- VPC
- InternetGateway
- Subnet
- RouteTable
- EC2
- RDS
- SecurityGroup
- SubnetGroup
- ElasticIP
構成図
セクション説明
セクション 意味 備考 AWSTemplateFormatVersion テンプレートバージョン 2010-09-09 であり、現時点で唯一の有効な値 Description テンプレートを説明するテキスト ---- Metadata テンプレートバージョン ---- Parameters パラメーターの定義 ---- Resources スタックに含める AWS リソースの宣言 ---- Outputs 出力値を宣言 他のスタックでインポートできる 組み込み関数説明
組み込み関数 意味 備考 Ref 指定したパラメータまたはリソースの値 --- Sub 特定した値の入力文字列にある変数の代わりになる 文字列内に変数を挿入する GetAtt テンプレートのリソースから属性の値を返す --- テンプレートファイル
下記においてあります。ご自由にお使いください
https://github.com/toyoyuto/cloudformation_rdsテンプレートファイル(解説付き)
ec2-rds.ymlAWSTemplateFormatVersion: "2010-09-09" Description: Server and DB construction Metadata: # コンソールでパラメータをグループ化およびソートする方法を定義するメタデータキー "AWS::CloudFormation::Interface": # パラメーターグループとそのグループに含めるパラメーターの定義 ParameterGroups: # Project名に関するグループ - Label: default: "Project Name Prefix" Parameters: - PJPrefix # ネットワーク設定に関するグループ - Label: default: "Network Configuration" # 記述された順番に表示される Parameters: - VPCCIDR - PublicSubnetCIDR - PrivateSubnet1CIDR - PrivateSubnet2CIDR - KeyName # パラメーターのラベル ParameterLabels: VPCCIDR: default: "VPC CIDR" PublicSubnetCIDR: default: "PublicSubnet CIDR" PrivateSubnet1CIDR: default: "PrivateSubnet1 CIDR" PrivateSubnet2CIDR: default: "PrivateSubnet2 CIDR" # ------------------------------------------------------------# # Input Parameters # ------------------------------------------------------------# Parameters: PJPrefix: Type: String VPCCIDR: Type: String Default: "10.0.0.0/16" PublicSubnetCIDR: Type: String Default: "10.0.0.0/24" PrivateSubnet1CIDR: Type: String Default: "10.0.1.0/24" PrivateSubnet2CIDR: Type: String Default: "10.0.2.0/24" KeyName: Type: "AWS::EC2::KeyPair::KeyName" Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# # VPC Create VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: !Ref VPCCIDR # VPC に対して DNS 解決がサポートされているか EnableDnsSupport: "true" # VPC 内に起動されるインスタンスが DNS ホスト名を取得するか EnableDnsHostnames: "false" # VPC 内に起動されるインスタンスの許可されているテナンシー InstanceTenancy: default Tags: - Key: Name Value: !Sub "${PJPrefix}-vpc" # InternetGateway Create InternetGateway: Type: "AWS::EC2::InternetGateway" Properties: Tags: - Key: Name Value: !Sub "${PJPrefix}-igw" # IGW Attach InternetGatewayAttachment: Type: "AWS::EC2::VPCGatewayAttachment" Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# # Public Subnet Create PublicSubnet: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PublicSubnetCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public-subnet" # Private Subnet Create PrivateSubnet1: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PrivateSubnet1CIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-private-subnet1" PrivateSubnet2: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1c" CidrBlock: !Ref PrivateSubnet2CIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-private-subnet2" # ------------------------------------------------------------# # RouteTable # ------------------------------------------------------------# # Public RouteTable Create PublicRouteTable: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${PJPrefix}-public-route" # ------------------------------------------------------------# # Routing # ------------------------------------------------------------# # PublicRoute Create PublicRoute: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # ------------------------------------------------------------# # RouteTable Associate # ------------------------------------------------------------# # PublicRouteTable Associate PublicSubnet PublicSubnetRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PublicSubnet RouteTableId: !Ref PublicRouteTable # ------------------------------------------------------------# # ElasticIP # ------------------------------------------------------------# ElasticIP: Type: "AWS::EC2::EIP" Properties: Domain: vpc ElasticIPAssociate: Type: AWS::EC2::EIPAssociation Properties: AllocationId: !GetAtt ElasticIP.AllocationId InstanceId: !Ref WebServer # ------------------------------------------------------------# # EC2 # ------------------------------------------------------------# # WebServerインスタンス WebServer: Type: AWS::EC2::Instance Properties: ImageId: ami-00f045aed21a55240 KeyName: !Ref KeyName InstanceType: t2.micro # 属するサブネット SubnetId: !Ref PublicSubnet # 属するセキュリティグループ SecurityGroupIds: - !Ref WebServerSecurityGroup # インスタンスの作成時に実行するコマンドなどを記述 UserData: !Base64 | #!/bin/bash sudo yum -y update sudo yum -y install mysql sudo yum -y install httpd sudo systemctl start httpd.service sudo systemctl enable httpd.service Tags: - Key: Name Value: !Sub "${PJPrefix}-web-server" # WebServerセキュリティグループ WebServerSecurityGroup: Type: AWS::EC2::SecurityGroup Properties: GroupName: web-sg-cf GroupDescription: web server sg VpcId: !Ref VPC SecurityGroupIngress: # ssh - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 - IpProtocol: tcp FromPort: 80 ToPort: 80 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub "${PJPrefix}-web-server-sg" # # ------------------------------------------------------------# # # DBInstance MySQL # # ------------------------------------------------------------# DBInstance: Type: "AWS::RDS::DBInstance" Properties: DBInstanceIdentifier: !Sub "${PJPrefix}-db-instance" Engine: MySQL # version # EngineVersion: !Sub "${MySQLMajorVersion}.${MySQLMinorVersion}" # DB インスタンスのコンピューティング性能とメモリ容量 (例: db.m4.large) DBInstanceClass: "db.t2.micro" # データベースインスタンスに最初に割り当てるストレージの容量 AllocatedStorage: "20" # スレレージタイプ StorageType: "gp2" # ssd DBName: !Sub "${PJPrefix}_db" MasterUsername: "admin" MasterUserPassword: "password" DBSubnetGroupName: !Ref DBSubnetGroup # DB インスタンスがインターネットに接するインスタンスかどうかを示します PubliclyAccessible: false # データベースインスタンスが複数のアベイラビリティーゾーンに配置されているかどうか MultiAZ: false # 自動バックアップが有効になっている場合に、自動バックアップが作成される毎日の時間範囲 PreferredBackupWindow: "18:00-18:30" # 週 1 回のシステムメンテナンスを実行できる時間帯、世界標準時 (UTC)。 PreferredMaintenanceWindow: "sat:19:00-sat:19:30" AvailabilityZone: "ap-northeast-1a" # マイナーバージョン自動アップグレードの有効化 AutoMinorVersionUpgrade: false VPCSecurityGroups: - !Ref RDSSecurityGroup # タグを DB インスタンスから DB インスタンスのスナップショットにコピーするかどうか CopyTagsToSnapshot: false # 自動バックアップを保管する日数 0だと自動バックアップ無効 BackupRetentionPeriod: 7 Tags: - Key: "Name" Value: !Sub "${PJPrefix}-db" DeletionPolicy: "Delete" # # ------------------------------------------------------------# # # RDSSecurityGroup # # ------------------------------------------------------------# RDSSecurityGroup: Type: "AWS::EC2::SecurityGroup" Properties: VpcId: !Ref VPC GroupName: !Sub "${PJPrefix}-db-sg" GroupDescription: "-" Tags: - Key: "Name" Value: !Sub "${PJPrefix}-db-sg" # Rule RDSSecurityGroupIngress: Type: "AWS::EC2::SecurityGroupIngress" Properties: IpProtocol: tcp FromPort: 3306 ToPort: 3306 # 許可するセキュリティグループ SourceSecurityGroupId: !GetAtt [ WebServerSecurityGroup, GroupId ] # 紐付けるセキュリティグループ GroupId: !GetAtt [ RDSSecurityGroup, GroupId ] # # ------------------------------------------------------------# # # DBSubnetGroup # # ------------------------------------------------------------# DBSubnetGroup: Type: "AWS::RDS::DBSubnetGroup" Properties: DBSubnetGroupName: !Sub "${PJPrefix}-db-subnet" # サブネットグループの説明 DBSubnetGroupDescription: "-" # 紐づけるサブネット(2こ以上) SubnetIds: - !Ref PrivateSubnet1 - !Ref PrivateSubnet2 # # # ------------------------------------------------------------# # # # Output Parameters # # # ------------------------------------------------------------# Outputs: # VPC VPC: Value: !Ref VPC Export: Name: !Sub "${PJPrefix}-vpc" VPCCIDR: Value: !Ref VPCCIDR Export: Name: !Sub "${PJPrefix}-vpc-cidr" # Subnet PublicSubnet: Value: !Ref PublicSubnet Export: Name: !Sub "${PJPrefix}-public-subnet" PublicSubnetCIDR: Value: !Ref PublicSubnetCIDR Export: Name: !Sub "${PJPrefix}-public-subnet-cidr" PrivateSubnet1: Value: !Ref PrivateSubnet1 Export: Name: !Sub "${PJPrefix}-private1-subnet" PrivateSubnet1CIDR: Value: !Ref PrivateSubnet1CIDR Export: Name: !Sub "${PJPrefix}-private-subnet1-cidr" PrivateSubnet2: Value: !Ref PrivateSubnet1 Export: Name: !Sub "${PJPrefix}-private2-subnet" PrivateSubnet2CIDR: Value: !Ref PrivateSubnet2CIDR Export: Name: !Sub "${PJPrefix}-private-subnet2-cidr" # Route PublicRouteTable: Value: !Ref PublicRouteTable Export: Name: !Sub "${PJPrefix}-public-route" # EC2 WebServer: Value: !Ref WebServer Export: Name: !Sub "${PJPrefix}-web-server" # RDS DBInstance: Value: !Ref DBInstance Export: Name: !Sub "${PJPrefix}-db-instance"参考
【AWS 再入門】EC2 + RDS によるミニマム構成なサーバー環境を構築してみよう
CloudFormationを使ってMySQLのRDSを構築する
Amazon RDS for MySQLインスタンス作成手順
- 投稿日:2020-12-27T02:43:35+09:00
作ったポートフォリオを簡単に解説してみる。
何を作ったのか?
アプリ名 Dig Memo
思考を深掘る事を意識して作ったメモアプリです。
下記URLで公開しております!
http://www.digmemo-app.xyz/なぜ作ろうと思ったのか?
思考をサポートできるサービスを作りたいと思ったからです。
プログラミング学習をしている時に、ただ勉強するよりも思考して勉強する方が、知識が定着しやすいと感じました。しかし、ただのメモだと楽をしてインプットした情報をそのまま吐き出すこともできてしまうなと思いました。そこを制限して思考をサポートすることができるサービスがあったらいいのではないか?と思い、作ることにしました!
どんな機能を実装したのか?
機能一覧
・メモ投稿・編集・削除機能
・カテゴリー機能
・全文検索機能
・お気に入り機能
・ページネーション機能
・メール認証機能
・パスワードリセット機能
・ゲストログイン機能ER図
こだわった・工夫したポイント
ポイント1
問いを設定し、それに対して答える事で思考を促せるようにしました。
このアプリでは完全自由なメモはしにくいようになっています。言葉では説明が難しい部分もあるので動画を載せます。
動画のようにこのアプリはタイトルを設定し、そのタイトルに対して自問自答を行います。こうする事で反強制的に思考することができます。ポイント2
カテゴリーや問い作成機能にajax処理を用いました。
文字を打っている時「あ、この問いも追加したい」ってなった場合に、何も対策しなければ問いを作った後、それまで打っていた文字まで消えてしまいます。それを防ぐためにajax処理を用いて、UXを向上させました。
ポイント3
テーブル結合で全文検索を実現させました。
検索機能ではメモのタイトルからだけではなく、メモの内容からも検索を行えるように実装しました。
articles
テーブルのtitle
カラムと、article_items
テーブルのbody
カラムという二つのテーブルから取り出したデータを結合させることによって機能を実現させました。使用技術等
- 言語: Ruby 2.7.1
- フレームワーク:Ruby on Rails 6.0.3.2
- テスト:RSpec
- フロントエンド:HTML、SCSS、bootstrap4、javascript(jQuery)
- インフラ:AWS(VPC・EC2・Route53・SES)
- コード管理:GitHub
- DB: MySQL
- その他:docker-compose(開発環境のDBで使用)
追加実装予定
- ELBを導入して冗長化する
- ACMでhttps化する
最後に
ここまでご覧いただきありがとうございました!