20200922のAWSに関する記事は20件です。

Amazon Forecastで(自分の)電気代を予測してみる

はじめに

4月ごろから個人的な興味で機械学習の勉強をはじめました。
普段は本やオンライン教材に出てくるサンプルコードを写経しながら理解を深めつつ、たまにKaggleに挑戦し挫折したりしています。

この記事では、とりあえず何か成果を出してモチベーションを上げたい私が、マネージドの機械学習サービス(Amazon Forecast)にデータの前処理やモデル設計などをお任せして、電気料金の予測モデルを構築、簡単な評価までを記載します。

※1 本文内に記載する学習データ内の数値はサンプルです
※2 初学者のため関連用語の用法が誤っている可能性があります、ご容赦ください

Amazon Forecastとは

AWS公式ウェブサイト1より抜粋

Amazon Forecast は、機械学習を使用して精度の高い予測を行うフルマネージドサービスです。
Amazon.com と同じテクノロジーをベースとし、機械学習を使って時系列データを付加的な変数に結びつけて予測を立てます。
必要なのは過去のデータと、予測に影響を与える可能性があるその他の追加データだけです。
ユーザーがデータを提供すると、それを自動的に精査し、何が重要かを識別して、予測を立てるための予測モデルを作成します。

目標

2020年9月の1日ごとの電気料金を予測する
精度が高いともちろん嬉しいですが最低限それっぽい予測ができること

学習に使用するデータ

今回は予測したい電気料金の情報に加えて、追加データとして同期間の天候に関する情報を与えます。

  • 過去の電気料金
    • 2019年1月1日〜2020年8月31日の電気料金 - elec_data.csv
elec_data.csv
# CSVサンプル

id,ymd,cost
0,2019-01-01,11
0,2019-01-02,22
0,2019-01-03,33
︙
0,2020-08-29,44
0,2020-08-30,55
0,2020-08-31,66
  • 自宅付近の過去の天候データ(気象庁公開のデータ2を使用) - weather_data.csv
    • 最高気温、平均気温、最低気温 [℃]
    • 降水量 [mm]
    • 日照時間 [時間]
weather_data.csv
# CSVサンプル

id,ymd,max_temp,avg_temp,min_temp,precip,day_length
0,2019-01-01,10.0,4.0,-4.0,0.0,7.0
0,2019-01-02,10.5,4.5,-3.0,5.0,7.5
0,2019-01-03,11.0,5.0,-2.0,10.0,7.2
︙
0,2020-08-29,34.0,30.0,25.0,5.0,10.0
0,2020-08-30,34.5,30.5,25.5,10.0,7.3
0,2020-08-31,35.0,31.0,26.0,15.0,8.5

学習

ここから上記データを使用して実際にモデル構築のための学習に入ります。
インポートの際はS3上のパスで指定するため、事前にCSVファイルをアップロードしておきます。

データセットグループの作成

データをインポートする際、データセットグループに対して使用データを指定する形となります。
電気料金の予測モデル構築がゴールのため、Forecasting domainCustomとしました。
(上記設定についての詳細は こちら をご覧ください)

データセットのインポート

予測対象データのインポート画面に移るので、一部設定を更新します

  • Frequency of your data - 1 day
  • Data schema - CSVのスキーマに合わせて修正
  • Timestamp format - yyyy-MM-dd
  • Data location - S3上のelec_data.csvのパスを入力

続けて、Related time series data > Import から天候データのインポートをします

  • Data locationにS3上のweather_data.csvのパスを入力

以上でデータセットのインポートが完了します。

学習の開始

両方のデータセットの状態が Active になったのを確認し、Predictor training > Start から学習を開始します。
要件が 2020年9月の1日ごとの電気料金を予測する であることを考慮し、30日分のデータ返却する予測器を作成します。
- Forecast horizon - 30
- Forecast frequency - 1 day
- Algorithm selection - AutoML
- Country for holidays - Japan

各項目の詳細は、これまたAWSの公式サイト1を参照ください
(なおAutoMLについては 数種類のアルゴリズムの中から最適なものを選定してモデル構築してくれる くらいの理解しかありません)

image1

予測の作成と取得

予測の作成

少し待つと学習が完了し、Predictor trainingの状態が Active となります
AutoMLによって作成されたモデルを使用し、予測結果を取得します。

Forecast generation > Start

  • Predictor - 前のステップで作成した予測器を選択

上記設定後 Create Forecast を選択し、予測結果を出力できるようになるまで待ちます
Lookup forecastCreate a forecastが選択できるようになっていればOK)
スクリーンショット 2020-09-22 21.31.27(2).png

予測の取得

実際に予測結果を見てみます
Lookup forecastから必要な情報を入力し、1ヶ月分の予測結果を表示します(Get Forecast

  • Forecast - 前のステップで作成した予測
  • Start date - 2020/08/31 00:00:00
  • End date - 2020/09/30 00:00:00
  • Forecast key - item_id:0

ap-northeast-1.console.aws.amazon.com_forecast_home_region=ap-northeast-1 (3).png
出ました

評価

最後、上記の予測と実際の電気料金(~9/15)を比較してみたいと思います
example.png
Oh... 軸名を書くのすら面倒になるほど外れてました…

一旦まとめ

  • 管理画面上でポチポチするだけ、(とりあえず)ノンコーディングで回帰モデルが作れました

反省

前提条件の異なるデータが訓練データに含まれていた
3月以降、在宅勤務がメインとなり、その分自宅にいる時間が大幅に増えました
それ以前は平日には出社していたので、データを準備しながらうすうす 去年のデータ全然参考にならないんじゃ… と思ってましたが、少なくとも曜日による重みはほぼ無意味になっているはず…

予測対象とは別に与えた関連データ(天候データ)が不足していた
こちらの記事にありますが、予測する期間も含めたデータが必要だったようです、ウワー
上のよりこちらのほうが影響大きそう

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

Amazon Forecastで自分の電気代を予測してみる

はじめに

4月ごろから個人的な興味で機械学習の勉強をはじめました。
普段は本やオンライン教材に出てくるサンプルコードを写経しながら理解を深めつつ、たまにKaggleに挑戦し挫折したりしています。

この記事では、とりあえず何か成果を出してモチベーションを上げたい私が、マネージドの機械学習サービス(Amazon Forecast)にデータの前処理やモデル設計などをお任せして、電気料金の予測モデルを構築、簡単な評価までを記載します。

※1 本文内に記載する学習データ内の数値はサンプルです
※2 初学者のため関連用語の用法が誤っている可能性があります、ご容赦ください

Amazon Forecastとは

AWS公式ウェブサイト1より抜粋

Amazon Forecast は、機械学習を使用して精度の高い予測を行うフルマネージドサービスです。
Amazon.com と同じテクノロジーをベースとし、機械学習を使って時系列データを付加的な変数に結びつけて予測を立てます。
必要なのは過去のデータと、予測に影響を与える可能性があるその他の追加データだけです。
ユーザーがデータを提供すると、それを自動的に精査し、何が重要かを識別して、予測を立てるための予測モデルを作成します。

目標

2020年9月の1日ごとの電気料金を予測する
精度が高いともちろん嬉しいですが最低限それっぽい予測ができること

学習に使用するデータ

今回は予測したい電気料金の情報に加えて、追加データとして同期間の天候に関する情報を与えます。

  • 過去の電気料金
    • 2019年1月1日〜2020年8月31日の電気料金 - elec_data.csv
elec_data.csv
# CSVサンプル

id,ymd,cost
0,2019-01-01,11
0,2019-01-02,22
0,2019-01-03,33
︙
0,2020-08-29,44
0,2020-08-30,55
0,2020-08-31,66
  • 自宅付近の過去の天候データ(気象庁公開のデータ2を使用) - weather_data.csv
    • 最高気温、平均気温、最低気温 [℃]
    • 降水量 [mm]
    • 日照時間 [時間]
weather_data.csv
# CSVサンプル

id,ymd,max_temp,avg_temp,min_temp,precip,day_length
0,2019-01-01,10.0,4.0,-4.0,0.0,7.0
0,2019-01-02,10.5,4.5,-3.0,5.0,7.5
0,2019-01-03,11.0,5.0,-2.0,10.0,7.2
︙
0,2020-08-29,34.0,30.0,25.0,5.0,10.0
0,2020-08-30,34.5,30.5,25.5,10.0,7.3
0,2020-08-31,35.0,31.0,26.0,15.0,8.5

学習

ここから上記データを使用して実際にモデル構築のための学習に入ります。
インポートの際はS3上のパスで指定するため、事前にCSVファイルをアップロードしておきます。

データセットグループの作成

データをインポートする際、データセットグループに対して使用データを指定する形となります。
電気料金の予測モデル構築がゴールのため、Forecasting domainCustomとしました。
(上記設定についての詳細は こちら をご覧ください)

データセットのインポート

予測対象データのインポート画面に移るので、一部設定を更新します

  • Frequency of your data - 1 day
  • Data schema - CSVのスキーマに合わせて修正
  • Timestamp format - yyyy-MM-dd
  • Data location - S3上のelec_data.csvのパスを入力

続けて、Related time series data > Import から天候データのインポートをします

  • Data locationにS3上のweather_data.csvのパスを入力

以上でデータセットのインポートが完了します。

学習の開始

両方のデータセットの状態が Active になったのを確認し、Predictor training > Start から学習を開始します。
要件が 2020年9月の1日ごとの電気料金を予測する であることを考慮し、30日分のデータ返却する予測器を作成します。
- Forecast horizon - 30
- Forecast frequency - 1 day
- Algorithm selection - AutoML
- Country for holidays - Japan

各項目の詳細は、これまたAWSの公式サイト1を参照ください
(なおAutoMLについては 数種類のアルゴリズムの中から最適なものを選定してモデル構築してくれる くらいの理解しかありません)

image1

予測の作成と取得

予測の作成

少し待つと学習が完了し、Predictor trainingの状態が Active となります
AutoMLによって作成されたモデルを使用し、予測結果を取得します。

Forecast generation > Start

  • Predictor - 前のステップで作成した予測器を選択

上記設定後 Create Forecast を選択し、予測結果を出力できるようになるまで待ちます
Lookup forecastCreate a forecastが選択できるようになっていればOK)
スクリーンショット 2020-09-22 21.31.27(2).png

予測の取得

実際に予測結果を見てみます
Lookup forecastから必要な情報を入力し、1ヶ月分の予測結果を表示します(Get Forecast

  • Forecast - 前のステップで作成した予測
  • Start date - 2020/08/31 00:00:00
  • End date - 2020/09/30 00:00:00
  • Forecast key - item_id:0

ap-northeast-1.console.aws.amazon.com_forecast_home_region=ap-northeast-1 (3).png
出ました

評価

最後、上記の予測と実際の電気料金(~9/15)を比較してみたいと思います
example.png
Oh... 軸名を書くのすら面倒になるほど外れてました…

一旦まとめ

  • 管理画面上でポチポチするだけ、(とりあえず)ノンコーディングで回帰モデルが作れました

反省

前提条件の異なるデータが訓練データに含まれていた
3月以降、在宅勤務がメインとなり、その分自宅にいる時間が大幅に増えました
それ以前は平日には出社していたので、データを準備しながらうすうす 去年のデータ全然参考にならないんじゃ… と思ってましたが、少なくとも曜日による重みはほぼ無意味になっているはず…

予測対象とは別に与えた関連データ(天候データ)が不足していた
こちらの記事にありますが、予測する期間も含めたデータが必要だったようです、ウワー
上のよりこちらのほうが影響大きそう

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

【試験合格記】AWS 認定 データベース – 専門知識(DBS-C01)

お疲れさまです。
表題の試験に合格したため記録として残したいと思います。

結果

合格(816点)

AWS-Certified-Database-Specialty_Exam-Guide.pdf

分野 名称 割合
1 ワークロード固有のデータベース設計 26%
2 展開および移行 20%
3 管理および運用 18%
4 監視およびトラブルシューティング 18%
5 データベースセキュリティ 18%

所有資格

資格名 取得年月日
AWS Certified Solutions Architect - Associate (SAA) 2018-06-14
AWS Certified SysOps Administrator - Associate (SOA) 2018-06-22
AWS Certified Developer - Associate (DVA) 2018-06-25
AWS Certified Solutions Architect - Professional (SAP) 2018-07-19
AWS Certified DevOps Engineer - Professional (DOP) 2020-02-19
AWS Certified Big Data - Speciality (BDS) 2020-03-23
AWS Certified Security - Speciality (SCS) 2020-04-01
AWS Certified Machine Learning - Speciality (MLS) 2020-04-13
AWS Certified Advanced Networking - Speciality (ANS) 2020-04-27
AWS Certified Cloud Practitioner (CLF) 2020-04-27
AWS Certified Database - Specialty (DBS) 2020-09-22

事前知識

AuroraやElastiCacheを業務で建てることはよくあるので、そのへんのパラメータ周りは知ってます。
あとは過去に「エキスパートのためのMySQL[運用+管理]トラブルシューティングガイド」という書籍を読破したり、レンサバ運用で泣き叫んでるMySQLサーバのスロークエリを抽出したりパフォーマンスチューニングをしたり・・・といった程度で正直ひよっこレベルでした。

所感

学習期間は1ヶ月半くらいだったと思います。
難易度としては普段からDBサーバを触っているかいないかで大きく変動するのではないでしょうか。

また、この試験に限らず、DBリソース選定や面接などで「RDSとAuroraはどう違うのか」といった話はよく出るので体系的に理解しておくのがオススメです。

試験内容についてはどなたかのブログで書いてましたが、「セキュリティ専門」と「ネットワーク専門」の知識が正しく頭にあればそれだけで30%くらいのスコアを稼げると思います。
あとはRDSとAuroraの違いやディザスタリカバリ要件に対するアプローチ、それにDMSを使ったマイグレーションなどの最適解を導き出せると60%が見えてくるはずです。
残りの40%はNeptune、DynamoDB、Aurora Serverless、ElastiCacheといった雑多な周辺知識の特徴を抑えておけば合格ラインを超えられる可能性が出てくると思います。

学習範囲

受験者に向けたアドバイス

  • セキュリティとネットワークのAWS認定で学んだ内容をしっかり振り返る
  • RDS(MySQL, PostgreSQL, SQL Server, Oracle)それぞれができること、できないことを理解する
  • Auroraも同じ
  • DMSを使ったマイグレーションの手順やそれに頼らない別のアプローチを把握しておく
  • DynamoDBはそれなりに出題された記憶があるので要復習
  • IAMを使った権限管理やDBをふっ飛ばしたので巻き戻したい系のトラブルシューティングも重要

まとめ

Kubernetesの資格を取ったあたりで試験勉強に飽きてしまいました。
データベース資格に関しては試験勉強をすることで身近な運用時のミスをカバーできる知識を得ることができるので、DBを触ったことがないという方も学んでみると色々と役に立つことがあるかもしれません。
特にインフラ屋にとってはDBが安定していないと睡眠時間が減ってしまうという問題もあるので是非取得を目指すとよいと思います。

年内に最後の資格であるAlexaを取得できるよう引き続き頑張ります。

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

【AWS】Amazon SESを用いてRuby on Railsのdeviseでメールを送信する

目次

・経緯
・今回の前提条件
・注意事項
・AWS SES(Simple Email Service)とは
・料金
・大体の流れ(イメージ)
・1. AWS SESの設定
・2. AWSのCSに制限解除のメールを送る
・3. IAMユーザーの設定
・4. railsに設定
・用語解説
・最後に

経緯

私がPortfolioを作成している段階でして、divise上のメールが飛んでいないことに気づきました。
改善策を模索している中、AWSSES(有料)なるものを見つけGmail(無料?)でも実装できることが分かったが、AWSでEC2なども利用していたので
・AWSでまとめて管理ができるのですっきりしそう
・いい経験になるかも
上記の理由で実装してみた。

私自身も初学者なので、完全に鵜呑みにせずイメージをつかむ感覚で読んでいただければと!

今回の前提条件

  • Ruby on railsでappがありdeviseを導入している
  • AWSのアカウントを所持している
  • Route53の設定が完了していて、ドメインでのアクセス可能なサイトがある。

くらいですかね、、

注意事項

私がだいぶ躓いたところなので、、
2020/09/22現在AWSSESを東京リージョン(ap-northeast-1)で本番環境で実装するとメールが飛びません!!!!!
最近のリリースなのでまだバグが多いから?!
解決方法わかる方教えていただきたいです
祝!Amazon SESが東京リージョンにやってきた!
私はアイルランドリージョン(eu-west-1)で実装しました。
AWS SES以外の設定は東京リージョンで済ませております。(EC2,Route53など)

あとサンドボックスの制限解除に1日かかります!
ご利用は計画的に!

AWS SES(Simple Email Service)とは

AWSで提供されているクラウドベースの電子メール送信サービスです。

料金

AWS SESは有料で、一定量送信毎での従量課金制ぽいです。

SES の料金は、送信する各 1,000 通の E メールにつき、0.10USD であり、非常に低コストです。

上記の通り、高額な金額が発生するわけではなさそうなので試しに利用してみるのもありでしょう!

大体の流れ(イメージ)

  1. AWS SESの設定
  2. AWSのCSに制限解除のメールを送る
  3. IAMユーザーの設定
  4. railsに設定

1. AWS SESの設定

AWS-マネジメントコンソール (2).png
1-1. AWSにログイン
1-2. ヘッダーのサービスをクリック
1-3. SESと検索してエンター

SES-Management-Console.png
1-4. IdentityManagement>Domainをクリック
1-5. Varufy a NeW Domianをクリック
この段階で東京リージョン以外のリージョンを選択しておいたほうが良いです

SES-Management-Console (2).png
1-6. Domainに接続したいドメイン名を入れる
1-7. Generate DKIM Settingsにチェック入れる
1-8. Varufy This Domianをクリック

SES-Management-Console (3).png
1-9. User Route53をクリック
Route53を登録している場合上記表示になる。
登録している場合レコードの設定までを自動でやってくれるぽい

SES-Management-Console (4).png
1-10. Create Record Setsをクリック
AWS SWSのsettingは一旦こちらで終了です!

SES-Management-Console (5).png
しばらくすると各項目がverifiedに代わると思います。

2. AWSのCSに制限解除のメールを送る

AWS SESをsettingをしただけではメールは送信されません。
設定初期ProductionAcsessがSandboxになっており、これを解除する必要があります。

SES-Management-Console (7).png
2-1. Email Sending > Sending Statistics をクリック  
2-2. ProductionAcsessがSandboxになっていることを確認する。

SES-Management-Console (6).png
2-3. 右上のサポートをクリック
2-4. サポートセンターをクリック

AWS-サポートダッシュボード.png
2-5. 中央にあるCreate Caseをクリック

以降メールの送信手順に関しては下記記事でご丁寧にまとめられておりましたので
メールを送るまでは参考にしてみてください。
2-6.メール送信の手順
※2.解除申請を送るフォームに入力から行ってみてください。

S__635158530.jpg
メール送信して一日後、上記のような解除しましたとメールが来れば
下記がProductionAcsessがSandbox⇒Enabledに代わります。

SES-Management-Console (8).png

SES-Management-Console (10).png
2-7.IdentityManagement>Domain>SendaTestEmailをクリック
2-8.メールを送ってみる
最後にテストです。
メールを送り受信ボックスに届くか確認します。

3. IAMユーザーの設定

AWS SESのsettingと制限解除だけではrailsにつなぎこめません。
最後にIAMユーザーを新規登録しなくてはいけません。
※筆者はrootアカウントでキーとIDを参照する方法を探してみましたがありませんでした。
※本来rootアカウントでは接続管理は好ましくなく、各アクションごとにIAMユーザー(権限の範囲)を指定して接続したほうがいいと参考書に書いてありました。
のであるかはわかりませんがとりあえずIAMユーザーで作成しておけってことですね。

AWS-マネジメントコンソール (3).png
3-1. ヘッダーのサービスをクリック
3-2. IAMと検索してエンター

IAM-Management-Console (7).png
3-3. ユーザーをクリック
3-4. ユーザーを追加をクリック

IAM-Management-Console (8).png
3-5. ユーザー名を入れる(※ここはわかりやすければなんでもOKです)
3-6. プログラムによるアクセスにチェック
3-7. 次のステップ:アクセス権限をクリック

IAM-Management-Console (10).png
3-8. 既存ポリシーを直接アタッチをクリック
3-9. ポリシーのフィルタにSESと入れる
3-10. AmazonSESFullAccessにチェックを入れる
3-11. 次ぐのステップ:タグをクリック

IAM-Management-Console (11).png
3-12. 次ぐのステップ:確認をクリック
キーと値は何も入れなくてOKです。

IAM-Management-Console (12).png
3-13. ユーザーの作成をクリック

IAM-Management-Console (13).png
こちらでIAMユーザーの作成が完了です。
※アクセスキーIDとシークレットアクセスキーは必ず控えておきましょう。
※アクセスキーIDは後程確認できますが、シークレットアクセスキーはこのページでしか確認ができず消したらもう見れません。

こちらでAWS上での設定は完了です。

4. railsに設定

お疲れ様です。
最後はrailsに設定して終わりです。
大体の作業は下記の流れです。
1. gemのインストール
2. ActionMailerの設定
3. 本番環境、開発環境の設定
4. deviseの設定変更
5. Mailerクラスのfromの設定
6. .envをアップロードする(本番環境用)

4-1. gemのインストール

gem 'aws-ses'

Gemfileにて上記を記載してbundle install

4-2. ActionMailerの設定

ActionMailer::Base.add_delivery_method :ses,
  AWS::SES::Base,
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  server: 'email.リージョンID.amazonaws.com'

config/initializers/aws.rbを新規で作成
リージョンIDと記載されているところはSESを作成したリージョンIDを入れてください。
※分からない場合はAWSにログインをするとURLの末尾にリージョンIDが記載されてます。


東京:ap-northeast-1
アイルランド:eu-west-1

IDとKEYはpushしてしまうとまずいので.envに新規で追加します。

AWS_ACCESS_KEY_ID = IDを入力
AWS_SECRET_ACCESS_KEY = KEYを入力

4-3 開発環境(のみ)の設定

config.action_mailer.default_url_options = { host: 'ドメイン名' }
config.action_mailer.delivery_method = :ses

config/environments/development.rbに上記を追加
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-3 本番環境(のみ)の設定

config.action_mailer.default_url_options = { host: 'ドメイン名' }
config.action_mailer.delivery_method = :ses

config/environments/production.rbに上記を追加
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-4. deviseの設定変更

# config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
config.mailer_sender = 'Testrooper <noreply@ドメイン名>'

config/initializers/devise.rbを変更します
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-5. Mailerクラスのfromの設定

class ApplicationMailer < ActionMailer::Base
  default from: 'MyDomain <noreply@ドメイン名>'
  layout 'mailer'
end

Mailerクラスのfromの設定をします。
mailers/application_mailer.rbに上記を追加します。
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。
開発環境であればこちらで終了となります^^

4-6. .envをアップロードする(本番環境のみの設定)

開発環境では正常に動き、本番環境で動かなかった際、こちら完全に忘れておりました、、
.envはpushされないのでEC2上にアップロードします
でないと本番環境ではSESの参照ができません!
流れは下記のとおりです。
1.EC2にログイン
2.アプリケーションのディレクトリに移動
3.viで.envにアクセスキーIDとシークレットアクセスキーの上書き保存
4.PUMAの再起動

用語解説

後程追加いたします。

最後に

現在ポートフォリオを作成しておりますので
ご意見などいただければうれしいです!
NotePro

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

【AWS】AWS SESを用いてRuby on Railsのdeviseでメールを送信する

目次

・経緯
・今回の前提条件
・注意事項
・AWS SES(Simple Email Service)とは
・料金
・大体の流れ(イメージ)
・1. AWS SESの設定
・2. AWSのCSに制限解除のメールを送る
・3. IAMユーザーの設定
・4. railsに設定
・用語解説
・最後に

経緯

私がPortfolioを作成している段階でして、divise上のメールが飛んでいないことに気づきました。
改善策を模索している中、AWSSES(有料)なるものを見つけGmail(無料?)でも実装できることが分かったが、AWSでEC2なども利用していたので
・AWSでまとめて管理ができるのですっきりしそう
・いい経験になるかも
上記の理由で実装してみた。

私自身も初学者なので、完全に鵜呑みにせずイメージをつかむ感覚で読んでいただければと!

今回の前提条件

  • Ruby on railsでappがありdeviseを導入している
  • AWSのアカウントを所持している
  • Route53の設定が完了していて、ドメインでのアクセス可能なサイトがある。

くらいですかね、、

注意事項

私がだいぶ躓いたところなので、、
2020/09/22現在AWSSESを東京リージョン(ap-northeast-1)で本番環境で実装するとメールが飛びません!!!!!
最近のリリースなのでまだバグが多いから?!
解決方法わかる方教えていただきたいです
祝!Amazon SESが東京リージョンにやってきた!
私はアイルランドリージョン(eu-west-1)で実装しました。
AWS SES以外の設定は東京リージョンで済ませております。(EC2,Route53など)

あとサンドボックスの制限解除に1日かかります!
ご利用は計画的に!

AWS SES(Simple Email Service)とは

AWSで提供されているクラウドベースの電子メール送信サービスです。

料金

AWS SESは有料で、一定量送信毎での従量課金制ぽいです。

SES の料金は、送信する各 1,000 通の E メールにつき、0.10USD であり、非常に低コストです。

上記の通り、高額な金額が発生するわけではなさそうなので試しに利用してみるのもありでしょう!

大体の流れ(イメージ)

  1. AWS SESの設定
  2. AWSのCSに制限解除のメールを送る
  3. IAMユーザーの設定
  4. railsに設定

1. AWS SESの設定

AWS-マネジメントコンソール (2).png
1-1. AWSにログイン
1-2. ヘッダーのサービスをクリック
1-3. SESと検索してエンター

SES-Management-Console.png
1-4. IdentityManagement>Domainをクリック
1-5. Varufy a NeW Domianをクリック
この段階で東京リージョン以外のリージョンを選択しておいたほうが良いです

SES-Management-Console (2).png
1-6. Domainに接続したいドメイン名を入れる
1-7. Generate DKIM Settingsにチェック入れる
1-8. Varufy This Domianをクリック

SES-Management-Console (3).png
1-9. User Route53をクリック
Route53を登録している場合上記表示になる。
登録している場合レコードの設定までを自動でやってくれるぽい

SES-Management-Console (4).png
1-10. Create Record Setsをクリック
AWS SWSのsettingは一旦こちらで終了です!

SES-Management-Console (5).png
しばらくすると各項目がverifiedに代わると思います。

2. AWSのCSに制限解除のメールを送る

AWS SESをsettingをしただけではメールは送信されません。
設定初期ProductionAcsessがSandboxになっており、これを解除する必要があります。

SES-Management-Console (7).png
2-1. Email Sending > Sending Statistics をクリック  
2-2. ProductionAcsessがSandboxになっていることを確認する。

SES-Management-Console (6).png
2-3. 右上のサポートをクリック
2-4. サポートセンターをクリック

AWS-サポートダッシュボード.png
2-5. 中央にあるCreate Caseをクリック

以降メールの送信手順に関しては下記記事でご丁寧にまとめられておりましたので
メールを送るまでは参考にしてみてください。
2-6.メール送信の手順
※2.解除申請を送るフォームに入力から行ってみてください。

S__635158530.jpg
メール送信して一日後、上記のような解除しましたとメールが来れば
下記がProductionAcsessがSandbox⇒Enabledに代わります。

SES-Management-Console (8).png

SES-Management-Console (10).png
2-7.IdentityManagement>Domain>SendaTestEmailをクリック
2-8.メールを送ってみる
最後にテストです。
メールを送り受信ボックスに届くか確認します。

3. IAMユーザーの設定

AWS SESのsettingと制限解除だけではrailsにつなぎこめません。
最後にIAMユーザーを新規登録しなくてはいけません。
※筆者はrootアカウントでキーとIDを参照する方法を探してみましたがありませんでした。
※本来rootアカウントでは接続管理は好ましくなく、各アクションごとにIAMユーザー(権限の範囲)を指定して接続したほうがいいと参考書に書いてありました。
のであるかはわかりませんがとりあえずIAMユーザーで作成しておけってことですね。

AWS-マネジメントコンソール (3).png
3-1. ヘッダーのサービスをクリック
3-2. IAMと検索してエンター

IAM-Management-Console (7).png
3-3. ユーザーをクリック
3-4. ユーザーを追加をクリック

IAM-Management-Console (8).png
3-5. ユーザー名を入れる(※ここはわかりやすければなんでもOKです)
3-6. プログラムによるアクセスにチェック
3-7. 次のステップ:アクセス権限をクリック

IAM-Management-Console (10).png
3-8. 既存ポリシーを直接アタッチをクリック
3-9. ポリシーのフィルタにSESと入れる
3-10. AmazonSESFullAccessにチェックを入れる
3-11. 次ぐのステップ:タグをクリック

IAM-Management-Console (11).png
3-12. 次ぐのステップ:確認をクリック
キーと値は何も入れなくてOKです。

IAM-Management-Console (12).png
3-13. ユーザーの作成をクリック

IAM-Management-Console (13).png
こちらでIAMユーザーの作成が完了です。
※アクセスキーIDとシークレットアクセスキーは必ず控えておきましょう。
※アクセスキーIDは後程確認できますが、シークレットアクセスキーはこのページでしか確認ができず消したらもう見れません。

こちらでAWS上での設定は完了です。

4. railsに設定

お疲れ様です。
最後はrailsに設定して終わりです。
大体の作業は下記の流れです。
1. gemのインストール
2. ActionMailerの設定
3. 本番環境、開発環境の設定
4. deviseの設定変更
5. Mailerクラスのfromの設定
6. .envをアップロードする(本番環境用)

4-1. gemのインストール

gem 'aws-ses'

Gemfileにて上記を記載してbundle install

4-2. ActionMailerの設定

ActionMailer::Base.add_delivery_method :ses,
  AWS::SES::Base,
  access_key_id: ENV['AWS_ACCESS_KEY_ID'],
  secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'],
  server: 'email.リージョンID.amazonaws.com'

config/initializers/aws.rbを新規で作成
リージョンIDと記載されているところはSESを作成したリージョンIDを入れてください。
※分からない場合はAWSにログインをするとURLの末尾にリージョンIDが記載されてます。


東京:ap-northeast-1
アイルランド:eu-west-1

IDとKEYはpushしてしまうとまずいので.envに新規で追加します。

AWS_ACCESS_KEY_ID = IDを入力
AWS_SECRET_ACCESS_KEY = KEYを入力

4-3 開発環境(のみ)の設定

config.action_mailer.default_url_options = { host: 'ドメイン名' }
config.action_mailer.delivery_method = :ses

config/environments/development.rbに上記を追加
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-3 本番環境(のみ)の設定

config.action_mailer.default_url_options = { host: 'ドメイン名' }
config.action_mailer.delivery_method = :ses

config/environments/production.rbに上記を追加
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-4. deviseの設定変更

# config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com'
config.mailer_sender = 'Testrooper <noreply@ドメイン名>'

config/initializers/devise.rbを変更します
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。

4-5. Mailerクラスのfromの設定

class ApplicationMailer < ActionMailer::Base
  default from: 'MyDomain <noreply@ドメイン名>'
  layout 'mailer'
end

Mailerクラスのfromの設定をします。
mailers/application_mailer.rbに上記を追加します。
ドメイン名と書いてあるところは接続しようとしているドメイン名に変えてください。
開発環境であればこちらで終了となります^^

4-6. .envをアップロードする(本番環境のみの設定)

開発環境では正常に動き、本番環境で動かなかった際、こちら完全に忘れておりました、、
.envはpushされないのでEC2上にアップロードします
でないと本番環境ではSESの参照ができません!
流れは下記のとおりです。
1.EC2にログイン
2.アプリケーションのディレクトリに移動
3.viで.envにアクセスキーIDとシークレットアクセスキーの上書き保存
4.PUMAの再起動

用語解説

後程追加いたします。

最後に

現在ポートフォリオを作成しておりますので
ご意見などいただければうれしいです!
NotePro

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

マネジメントコンソールの呼び方は?

呼び方は?

みなさん、AWS(アマゾン ウェブ サービス)使ってますか?
使っている方はおなじみの マネジメントコンソール ですが、何と呼んでいるでしょうか。

略さず、 マネジメントコンソール
略して、 マネコン
のどちらかが多いのではないでしょうか。

私の職場では

私の職場では、前者の「マネジメントコンソール」と呼ぶメンバーばかりです。
実は、私もずっと「マネジメントコンソール」と略さずに呼んでいたのですが、
半年(?)ほど前に「マネコン」と略すのをネットで見かけて、
これは言いやすいと「マネコン」に鞍替えしたのですが、なかなか浸透せず。。。
誰も「マネコン」と言わないので、ちょっと恥ずかしくもあり。。。

Windowsの「コントロールパネル」を「コンパネ」と呼ぶのと同じ感じなんですけどね。

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

【合格体験記】半年かけて確実に合格! AWSソリューションアーキテクト合格までの道のり 〜アプリケーションエンジニアがSAAを取得した話〜

はじめに

 2020年9月21日、AWSソリューションアーキテクトアソシエイト(以下SAA)に合格しました。自分自身の勉強の方法やスケジュールに関してシェアする記事です。
 ネットには「3週間で合格しました!」などと短期間の学習での合格体験記もありますが、
「一回の受験でで確実に合格する」
「知識だけでは意味がないので、ハンズオンもちゃんと行う」
という方針の下、じっくり6ヶ月の時間をかけて勉強しました。

筆者のバックグラウンド

基本的にはフロントエンド(React)とバックエンド(Java,Go)ができるアプリケーションエンジニアです。AWSの経験としては、EC2、RDS、S3、Lambdaなどを少しだけ仕事で触ったことがある程度でした。その他のサービスに関しては、実務経験がゼロの状況で学習を開始しました。

受験の動機

IT系の資格としては応用情報技術者の資格を既に持っていました。
「何かIT系の資格もう1つくらいほしいなー」と思い、
情報処理安全確保支援士かSAAかで迷った結果、SAA取得を決意しました。

SAAを選んだ理由としては、
・ AWSの資格はグローバルに通用する資格だから。
・ インフラ系やアーキテクチャの設計とかにも仕事を広げられるかもしれないと思ったから。
・ なんかのランキングでAWSの資格が最近人気と聞いたから。
・ 「AWSの資格持ってます!」と周りの人に自慢してみたかったから。

下位の資格であるクラウドプラクティショナーを受ける選択肢もあったのですが、
頑張ればいけそうだったので、いきなりSAAの学習を開始しました。

学習教材

①【書籍】Amazon Web Services 基礎からのネットワーク&サーバー構築
SAAに特化した書籍というわけではないです。VPC,EC2の一般的なサーバー構成に関して実際に手を動かして学習できる教材です。資格学習の導入として、書籍の内容を一通りハンズオンしてみました。

②【書籍】AWS認定アソシエイト3資格対策
SAAの範囲を一通り学習できる書籍です。インプット用の書籍とした何周か読みました。また、試験直前の問題演習期間に、練習問題の復習をする際にリファレンス的な使い方ができたので、非常に役に立ちました。
同書籍には練習問題が掲載されているのですが、その量は少ないのでこの書籍だけで合格は難しいと思いますが、情報のインプットとしては最適な書籍だと感じました。

③【Udemy】これだけでOK! AWS 認定ソリューションアーキテクト – アソシエイト試験突破講座
SAAの対策教材として有名な動画教材です。
サービス概要・ハンズオン・練習問題とバランス良く掲載されているので、時間のある人は一通りこなすのが良いと思います。
私は知識の取得だけでは意味がないと思い、こちらのハンズオンパートを一通りこなしました。実際にハンズオンをこなした方が、問題を解く際にもイメージしやすかったです。

④【Udemy】AWS 認定ソリューションアーキテクト アソシエイト模擬試験問題集(6回分390問)
一番頼りにした教材です。かなり難易度が高く、かつ分量も多いです。ですが、こちらの問題をマスターすればほぼ合格は確実、だと感じました。
一度間違えた問題をチェックしつつ何度も何度も解き直しました。

⑤【Web問題集】AWS WEB問題集で学習しよう
問題集に関しては④でも十分なのですが、ダメ押しでこちらのサイトも有料プランを契約して利用しました。
SAAに関しては問題が1000問近くも掲載されています。細かすぎるような知識を問う問題もいくつか掲載されているのですが、よくできた問題も多数掲載されています。実際に試験を受けた際に出題された問題とほぼ同様の問題がこちらのサイトにも掲載されており、非常に感謝しているサービスの1つです。

スケジュール

段階 時期 学習方法
導入 2020年3月上旬 ・学習教材①を使ってハンズオン
インプット 2020年3月中旬〜5月末 ・学習教材②を使って知識のインプット
 →iPadで学習ノートを作成しつつ3週程度読む

・学習教材③を使ってハンズオン
問題演習① 2020年6月〜8月 ・学習教材④を使って問題演習
 →間違えた問題を中心に復習し、繰り返し演習

・オリジナルの一問一答集を作成・暗記
問題演習② 2020年9月 ・学習教材⑤を使って問題演習
 →時間がなくて一周しかこなせなかった。。。
本番 2020年9月21日 ・テストセンターにて受験
 → 合格!

学習ペース

試験直前の9月はほぼ毎日勉強していました(平日1〜2時間、休日1〜4時間程度)が、
それ以外時期に関しては勉強したり、勉強しなかったりという感じでした^^;

3月~8月は一週間の内、半数程度の日は勉強していたかと思います。
(これも半年くらい時間がかかった理由の1つです。。。。)

もうちょっと集中して勉強すれば短期間で合格できたのかな、、、とも思います。
AWSの資格はテストセンターの予約する形式なので、直前まで日程を決めずダラダラ勉強してしまったように思います。

学習に活用したツール

  1. iPad
    Apple PencilとGoodNoteというアプリを使って学習ノートを作成していました。何か勉強する際にはノートをとらないと気がすまない性格なので、この組み合わせは個人的には最強でした。

  2. SmaTan
    単語帳を自作できるアプリです。こちらのアプリで一問一答集を作成して暗記をしていました。

  3. Qiita
    Qiitaの記事に暗記事項とかをまとめていました。こちらのページなどに自分なりの視点で試験に出そうな部分をまとめました。試験直前になってからやりはじめたので、もうちょっと早めにやっておけば良かったなー、と思いました。

合格の目安

学習教材④ または 学習教材⑤ にて8~9割程度の正答率が安定する、ことだと思います。

8割9割という数字は完全に私の主観的な意見ですが、演習問題を解くことは非常に重要です。
紛らわしい選択肢が非常に多いので、問題を解きまくって正解を探す訓練をするのは大事だと感じています。

試験を終えての感想

  1. 実務経験なくても(実際にAWS触ってなくても)試験には合格できる
    知識だけでも合格はできます、、、が実際に触ったことがあると試験勉強はかどります。なので、主要なサービスだけでも良いのでハンズオンをこなしておくのがおすすめです。EC2,RDS,ELB(AutoScaling),S3くらいはやっておいて損はないかと思います。

  2. 最新の情報を取得するように心がけるべき
    AWSのサービスは日々更新しているので、古い情報で覚えてしまうと、間違った選択肢を選んでしまいがちです。Qiitaやブログで調べ物をすることも良いですが、なるべくAWS公式のドキュメントを読むようにすると良いです。

  3. 試験の日程は最初に決める
    自分で試験の日程を決めるスタイルなので、日程を決めずに学習をはじめてしまうとダラダラ勉強しがちです。
    最悪、日程は後で変更も可能なので、一度日程を決めて、スケジュールドリブンで学習をする、というのが効果的だと思いました。

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

【axios+SAM+API Gateway】localhostからapiを叩けるようになるために苦労した話 (3/3)ローカルから先程作成した`API Gateway`の`POST`メソッドを叩く

はじめに

本稿は下記流れに沿って、axios+SAM+API Gatewayを利用して、GET及びPOSTメソッドのAPIを叩けるところまでを目標としています。

  1. SAMを利用して、API Gateway及びlambdaを構築
  2. ローカルから先程作成したAPI GatewayのGETメソッドを叩く
  3. ローカルから先程作成したAPI GatewayのPOSTメソッドを叩く

前回「ローカルから先程作成したAPI GatewayのGETメソッドを叩く」ということを試しました。その際に、「ドメインが異なる場合はCORSを意識する必要がある」ということを学びました。具体的には、API側でドメイン間でのリソースを共有を許可するような内容をheaderに含んで返却する必要がある、ということでした。

今回は、「ローカルから先程作成したAPI GatewayのPOSTメソッドを叩く」ということを試していきたいと思います。

3. ローカルから先程作成したAPI GatewayPOSTメソッドを叩く

ではPOSTメソッドを叩けるようにしていきたいと思います。まずはAPI側の実装をしていきます。
POSTのAPIとして以下のlambdaを作成します。

// post_item.py
import json

# import requests


def lambda_handler(event, context):
    print(event)
    msg = json.loads(event['body'])['message']
    return {
        "statusCode": 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        },        
        "body": json.dumps({
            "message": "you posted this message: " + msg
        }),
    }

またこのlambdaSAMで管理できるよう、template.yamlを修正します。

Resources:
  PostItemFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: post_item/
      Handler: post_item.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /post_items
            Method: post

そしてこれらをビルド・デプロイしてAPI側の準備は完了です。
続いてアプリ側の修正を行います。今回はPOSTメソッドに対応した処理を追加します。

// App.js
import React, {Component} from 'react';
import axios from 'axios';

class App extends Component {
    constructor(props){
        super(props);
        this.state = {
            'appMessage': 'hello,app',
            postApiMessage: 'before post'
        };
        this.doChange = this.doChange.bind(this);
        this.getApi = this.getApi.bind(this);
        this.postApiMessage = this.postApiMessage.bind(this);        
    }

    instance = axios.create({
        baseURL: 'https://flz1roclul.execute-api.ap-northeast-1.amazonaws.com/Prod'
    });

    doChange(e){
        this.setState({
            message: e.target.value
        })
    }

    getApi(){
        this.instance.get('/hello')
            .then((response) => {
                this.setState({
                    'appMessage': response.data['message']
                })
            })
            .catch(() => {
                this.setState({
                    'appMessage': 'faild get message from button'
                })                
            })
    }

    postApiMessage(){
        this.instance.post('/post_items', {'message': this.state.message})
          .then((results)=>{
            //console.log(results.json())
            console.log(results)
            this.setState({
              'postApiMessage': 'postApiSuccess! ' + results.data['message']
            })
          })
          .catch((results) => {
            console.log(results)
            this.setState({
              'postApiMessage': 'postApiFailed' + results
            })        
          })
      }

    render(){
        return(
            <div>
                <p>{this.state.postApiMessage}</p>
                <form>
                    <input type="text" value={this.state.message} onChange={this.doChange}/>
                    <input type="button" value="post api" onClick={this.postApiMessage} />
                </form>
                <div>{this.state.appMessage}</div>
                <input type='button' onClick={this.getApi} value="button" />
            </div>
        )
    }
}

export default App;

今回の処理は、アプリ側でポストした内容をAPI側でくっつけて返却するという単純なものです。
前回の反省を活かし、今回はAPI側に必要なheaderを予め追加しておいたので、大丈夫だと思います!

image.png
うーん…

何がだめったのか?

今回のエラーメッセージは以下のとおりです。

Access to XMLHttpRequest at 'https://{APIのURL}/Prod/post_items' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

'Access-Control-Allow-Origin'はつけたはずなのになんで?と思っていたのですが、今回重要なのは次の箇所でした

Response to preflight request doesn't pass access control check

調べてみると、特定の条件を満たすと、実際のメソッドが実行される前にOPTIONSメソッドが実行される(だからpreflight)ようです。で今回はそれに対応するメソッドが用意されてなかったので、エラーとなったと。
というわけで対策をしようと思います。メソッド部分は必要なheaderを返すだけでよいので以下のようにしました。

// post_item_options.py
import json

# import requests


def lambda_handler(event, context):
    return {
        "statusCode": 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        },        
        "body": json.dumps({
            "message": "this is the method to avoid cors for post method"
            # "location": ip.text.replace("\n", "")
        }),
    }

そしてこれをtemplate.yamlに追加します。

Resources:
  PostItemOptionsFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: post_item_options/
      Handler: post_item_options.lambda_handler
      Runtime: python3.7
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /post_items
            Method: options

大事なのは、POSTメソッドとPathは同じにすることです。これは、preflightメソッドは送信しようとしたメソッドに対して飛ぶためです。

もう一度やってみた

API側の修正を行ったので、もう一度チャレンジしてみました。
image.png
無事postが成功して、メッセージが返却されました!

終わりに

無事API環境を構築し、アプリからGET及びPOSTAPIを叩けるようになりました。「ただURLとメソッドを指定して叩けばいい」と思っていたので、まさかheader云々でこんなにハマるとは思わなかったです。もし本稿が同じような悩みに直面している人の助けになれば幸いです。

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

インフラ未経験エンジニアのソリューションアーキテクトアソシエイト (SAA-C002) 合格体験記

はじめに

  • 標記の通り、インフラ未経験エンジニアによる AWS ソリューションアーキテクトアソシエイトの試験対策をまとめた記事です。1
  • インフラ構築についてはオンプレミスを含めても未経験のため、ハンズオンの経験が大事だと思いました。
  • 2回目の受験でギリギリで合格できました。

受験動機

  • クラウドの強みとの一つに、スピーディーかつスケーラブルに、開発環境の構築からサービスの展開ができることだと思います。世の中のスピード感に対応するための一つの手段としてクラウドがあるのかなと思い勉強し始めました。2
  • その勉強の中で知識・技術のレベルを客観的に把握できるのがAWS認定の資格試験と思い受験しました。
  • あとは、ソリューションアーキテクトアソシエイトに合格すると会社から奨励金がもらえるからです。

勉強方法

その他資料

  • AWS 認定セッション
    • AWS Summit 2020 の認定試験に関する資料集です。ソリューションアーキテクトアソシエイトに関する資料があります。
  • Get Certified 2020
    • AWS提供の無料の認定試験に関するワークショップです。定期的に開催している様子で、2020/09/25に開催予定。

模擬試験

公式

  • AWS training and certification
    • AWSが提供する公式の模試です。あくまで基礎的な内容で、本番の試験はもっと難易度が上がります。

Udemy

結果

  • 753点 / 1000点 (合格ラインは720点)
    • ちなみに1回目は689点でした。2回目の受験は、1回目の受験の2ヶ月後です。
    • ギリギリなので偉そうなことは言えないですね。精進します。
  • 出題範囲の分野ごとの理解の度合い(再学習の必要あり or 十分な知識を有する)も教えてくれます。
    • 分野1:レジリエントアーキテクチャ ・・・ 十分な知識を有する
    • 分野2:高パフォーマンスアーキテクチャの設計 ・・・ 十分な知識を有する
    • 分野3:セキュアなアプリケーションとアーキテクチャの設計 ・・・ 再学習の必要あり
    • 分野4:コスト最適化アーキテクチャの設計 ・・・ 十分な知識を有する

おわりに

  • 理論と実践のバランスが必要な資格試験だと思いました。
    • オンプレミスで経験がある人はさておき、未経験者は実際に手を動かして、簡単な内容でも一通り自分の手で動かすことが大事だと思いました。
  • インフラ未経験だけど、クラウド (AWS) の活用に興味があって、資格取得をしたい方、スキルアップしたい方への一助となれば幸いです。
  • 次はデベロッパーアソシエイトの認定を目指します。

  1. 画像処理及びコンピュータビジョンのエンジニアでソフトウェア及びアルゴリズム開発に従事しており、クラウド歴は1年半ほどです。 

  2. もちろんクラウドはあくまで手段であって、目的ではないです。何をするかの実現手段の一つではあるものの、スピーディーかつスケーラブルにサービスを提供できるのが強みなので、習得して損はないと思っています。 

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

AWS SAA(C02)に1か月で合格するはずが3回受験してしまった話

コロナで在宅ワークに励んでいたころふと気づいた...。「このままだと今年の実績で大したものがない!」かねてより資格を取ろうと勉強していた某試験が、コロナ禍で無期限延期になってしまったのです。(余談ですが、自分は1年に一つ人生初をやっていこうというのをここ数年のタスクにしています。)

さて、それに気づいたのが6月頭だったので、何とかその資格とレベル感のあった別の資格無いもんかな、ということで自分が目を付けたのが表題にある通りAWSのSAAです。実は以前からQiitaの合格体験記を読んで人知れず狙ってはいたのですが、前述の資格のこともあり後回しにしていたのです。本当は自分はGoogle推し(特に何の根拠もなし。ロゴがカラフルだから?)なのでGCPも考えたのですが、やはりAWSがシェアNo.1ということもあり、体験談や教材が豊富ということがAWSを選択した一つの理由でした。それに皆さんご存じの通りクラウドは3強(AWS、GCP、Azure)に加え、競合他社が多いので、本当に必要な機能であれば競合他社がどんどんキャッチアップしていくので、よほど強い拘りがないのであればNo.1を選択するのが無難だと思いますし、1社をしっかり勉強・経験すれば応用効くような気がしています。

記事の前提となる知識・経験

インフラ業務 → 0
クラウド業務 → 0 
クラウド経験 → GCPの無料枠を使い切ったくらいでヘタレて解約。
趣味的な経験 → サクラVPSに自分のブログを立てて2年くらい運用していた。

こんな感じなのでおそらく「あんまり経験はないけど受けてみよう」という人たちのレベル感に近いのではないかと思います。

AWS試験で越えなければならないハードル

これについてはおおむね以下の2つに絞られると思います。
①AWSで提供するサービスの数がメチャクチャ多いので単語を覚えること自体が疲れる。
②試験問題を読むのに疲れるし、問題数も結構あるので疲れる。

このあたりについては大体どの体験談でも触れられています。ただ、単語から類推される機能イメージとかが英語ネイティブの人であれば多少違ってくるのかもしれず、この記事読んでる人は関係ないかもしれませんが日本のエンジニアにとってハードルを少し上げているのかもしれません。

試験にあたって利用した有料の試験対策

経験談によれば、メジャーなものはUdemyのハンズオン講座黒本AWS WEB問題集の3つです。今回はUdemyのハンズオン講座を購入して取り組んでみました。この時点でテスト代も含めて20000円ほど。まあこんなもんかなと。

勉強の進めかた(序盤)

AWSはとにかくサービスの数が多いので、全部について覚えるのは無理なわけですが、試験に出るのはあくまでも主要な機能とセキュリティ・コスト的に正しい使い方(ベストプラクティス)なわけです。素人はまずそのあたりが絞りこめず黒本を片手に途方に暮れるのですが、Udemyのハンズオンはそのあたりをちゃんと順序立てて重要な機能に絞って説明してくれるのです...。と言いたかったけど、長いよ!!1.5倍で聞いてもメチャクチャ時間かかるぞ!20時間って書いてあるけどハンズオンってそのままの時間では進まないし、1.5倍だけに資料進むのが早くてメモが追い付かない(等速で見ればいいじゃんとは言わないでください)ただ、講習で話していることはタメになることが多かったし、もともとある動画に手を加えて日々情報を最新化してくれているので、書籍の参考書にはないメリットを感じることができました。(後述の模擬試験も誤字が結構あったのですが、評価コメントで指摘したらちゃんと対応してくれました。)

直前の試験対策

ハンズオンに関してはおよそ6割くらいやった状態で、試験日が迫ってきたので、あとはハンズオン講座におまけでついているテストをスマホで何回も繰り返して覚えました。見直しした際に解説がついているので親切ですが、たまたま正解したやつだと解説読めないのがちょっと不満かなと。問題数はそれほど多くないので正直何回か繰り返すうちに答えを暗記してしまいます。それでも極力問題を読むようには心掛けました。初回は65問の試験やるのに1時間以上はかかるので、結構疲れます。

試験(1回目) 2020/6/28

というわけで第一回目の試験です。正直あんまり受かる気はしなかったのですが、まぐれで1発合格もあるんじゃないの?って気持ちは多少ありました。
結果は「不合格」(まあ3回受けたってタイトルに書いてますし...)
問題は勉強したところ以外で多く出てきました。ロードバランサとか暗号化、パフォーマンスの最適化について正しいものはどれか、とか聞かれましたが、そもそもそんなのどうすればいいのかなんて聞いてないよ!(逆ギレ)この辺は暗中模索で自信ないまま選択しています。
翌日来た得点を見ると、1000点満点の660点(5問不足)でした。まあまあなのか....?

2020-09-22 (2).png

2020-09-22 (3).png

反省と対策

試験受けた感想ですが、やはり問題文に対する慣れが圧倒的に足りないなというのは実感としてありました。もう一つ、ロードバランサとかネットワークセキュリティに関しては業務経験もないし、Udemyの試験対策でもそれほどカバーしていないのでわからないところが多かったです。この辺については公式資料であるホワイトペーパーやブラックベルトを見た方がいいのかもしれません。あと、試験対策をする公式動画もあったのですが、すべて英語だったので挫折しました。(これについては合格後に活用方法見つけたので時間あれば記事にしてみます)勉強に関してお手軽にできる追加の試験対策が思い浮かばなかったのでUdemyの模擬試験問題集を追加でポチリました。(3600円)

試験(2回目) 2020/7/12

あまり長々と勉強したくなかったので記憶がフレッシュなうちにと臨んだ2回目の試験ですが、試験3日前に恐ろしいことに気づきました。前回受けたのがC01だったのですが、今回受験したのがC02だったのです。自分の不注意ですが...。まあUdemyでも大筋は変わってないというし、C01もうすぐ終わるからこっちでもいいか、と思って受験しました。まあ当然不合格だったわけですが。ただ、試験問題にそこまでの違いは感じませんでした。要は実力で落ちたと。

翌日来た得点を見ると、700/1000(2問不足)でした。採点の大きい問題が通ってればあと一問くらいで合格していたようなレベル。結構残念な感じですが、直前の勉強状況に関しては1回目の方が時間取れていたので、少ないなりに知識は少しずつついてきているのかなと思いました。今回の試験で16500円+3600円、AWS2か月の使用料が6000円で、総額46000円くらい、馬鹿にならない出費になってきました....。ちなみにAWSの使用料に関してはEC2の落とし忘れ等、テキトーにやっていたからです。マメにシャットダウンすれば100円くらいに抑えることも可能なはず。ただ、Udemyのハンズオン再開するときに前回やめたときと同じ環境をセットアップするのがめんどくさいんですよね。クラウドフォーメーション(※)とかうまく使えればいいのかもしれませんが。まとまった勉強時間取れる人なら一気にやってしまうことをお勧めします。

2020-09-22 (6).png

2020-09-22 (7).png

反省と対策(その2)

正直なところ、勉強以外のことが忙しすぎてあまり勉強時間が取れなかったこともありますが、そろそろ追い詰められてきたことで真面目に勉強することにしました。とはいってもモロモロの事情からあまりまとまった勉強時間取れないので、上述のUdemyの模擬試験問題集をスマホでポチポチ解いていくのがメインでした。全部合わせて390問(65問×6種類)もあるので答えを暗記するには自分の脳が少々キャパオーバーだったこともあり、間違えるところが都度違ったり苦労しました。間違えた問題に関しては都度ノートに取ったりした方が記憶が整理されていいかもしれませんね。それでも、試験直前にはだいたい6種類の模試で、どれも90%くらいは安定して取れるようになっていました。

試験(3回目)2020/8/23

今回に関しては万全とはいえないもののそれなりに勉強して、体調に関してもベストコンディションだったため、自信はそれなりにありました。ただ、問題を解いていて確信して答えを選択したものはそれほどなかったです。試験問題を読むストレスも3回目ということもあってほとんどかんじず。しっかりと読みつつ、問題で問われていることを理解できたと思います。
結果は....合格!!(タイトルに書いてあるので溜める必要はないですが)
正直ホッとはしましたが、自信もあったので、これで落ちたら次の勉強どうやろうかな~って思いながら試験やってたので左程感動はありませんでした。850くらい取れただろうという手ごたえを感じていました。ちなみにテストと勉強にかかったコストの総額は65000円くらいです。会社の補助もあるので8割くらいは返ってきますが、コスト以上に得たものは大きいのかなと思います。勉強したという証明にもなるし、最新の開発トレンドについて得られた知見も多かったですし。

衝撃の採点結果と3回受験して分かったこと

しかし、翌日の採点結果を見たところ...732/1000!! ギリギリでした(笑)
分野ごとの評価を見る限り、やはりネットワークとか安全性のところに関してイマイチだったのは最後まで改善していませんでした。

2020-09-22 (8).png

2020-09-22 (9).png

今更ですが、500点~600点とるのは固有名詞さえ知っていればそれほど難しくないですが、そこから点を上げていくにはしっかり知識を積み重ねていく必要があると思いました。試験の感想にも書いていますが、確信して答えを選べるものがあまりない状態だとせいぜい7割くらいしか取れなさそです。体験談によく出てる800点とか行くには45問くらいは確信して答えを選択できていないと届かないんじゃないかと思いますが、みんなかなりしっかり勉強していたのではないでしょうか。自分の場合は時間の制約もあって結局Udemyの講座一本でやりましたが、それだけで一発合格するには結構AWS触ってるかインフラのバックボーンがある人でないと厳しい気がします。ただ、何回かチャレンジする気があるのであればUdemyの試験対策2本プラス以下の2点やっておけば3回以内に受かる可能性は結構あるのではないかと思いました。

①試験問題をよく読む。特に但し書きに注目。(コストが何とか、とか顧客の要望が何とか)
②苦手箇所は公式資料で補足する。(インフラ未経験の人は、ロードバランサ、ネットワークACL、セキュリティグループ、各サービスの暗号化、クラウド移行に関する資料だけでも最低限読み込む)

資格を取って思うこと

試験勉強は想像以上に大変でしたし、AWS自身が猛烈なスピードで進化しているので、資格更新するころには今持ってる知識はかなりの部分が不要になってきそうではあります。ただ、クラウドは官公庁でも導入事例出てきていますし、これからさらに伸びる業界であるのは間違いないので、技術者がどんどん増えて日本のクラウド業界が発展することを夙に期待していますし、自分の投稿が微力ながらも資格試験にチャレンジしようとしているけど、その一歩が踏み出せない方の背中を一押しすることができればと思っています。
自分は当面仕事でAWSに触る機会がなさそうなので、このまま座学でAWS SAPに挑戦してみようと思います。最後まで読んでいただき本当にありがとうございました。

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

【axios+SAM+API Gateway】localhostからapiを叩けるようになるために苦労した話 (2/3)ローカルから先程作成した`API Gateway`の`GET`メソッドを叩く

はじめに

本稿は下記流れに沿って、axios+SAM+API Gatewayを利用して、GET及びPOSTメソッドのAPIを叩けるところまでを目標としています。

  1. SAMを利用して、API Gateway及びlambdaを構築
  2. ローカルから先程作成したAPI GatewayGETメソッドを叩く
  3. ローカルから先程作成したAPI GatewayPOSTメソッドを叩く

前回SAMを利用して、API Gateway及びlambdaを構築し、作成したAPIに対してcurlコマンドを利用し叩けるところまでを検証しました。
今回は、「ローカルから先程作成したAPI GatewayGETメソッドを叩く」ということを試していきたいと思います。

image.png

2. ローカルから先程作成したAPI GatewayGETメソッドを叩く

localhostを起動し、先程のURLを叩いてみます。
デモ用のソースは以下としました(実処理部分のみ)。

import React, {Component} from 'react';
// import ReactDOM from 'react-dom';
import axios from 'axios';

class App extends Component {
    constructor(props){
        super(props);
        this.state = {
            'appMessage': 'hello,app'
        };
        this.getApi = this.getApi.bind(this);
    }

    instance = axios.create({
        baseURL: 'https://{メソッドのURL}//Prod'
    });

    getApi(){
        this.instance.get('/hello')
            .then((response) => {
                this.setState({
                    'appMessage': response.data['message']
                })
            })
            .catch(() => {
                this.setState({
                    'appMessage': 'faild get message from button'
                })                
            })
    }

    render(){
        return(
            <div>
                <div>{this.state.appMessage}</div>
                <input type='button' onClick={this.getApi} value="button" />
            </div>
        )
    }
}

export default App;

これを実行したところ以下のようなエラーが出ました。
image.png

Access to XMLHttpRequest at 'https://{メソッドのURL}//Prod/hello' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

どうやらCORSに関する実装がちゃんとされてないことが原因で怒られたようです(参考にしたAWS公式ドキュメントはこちら)。
なのでresponseに以下のheaderを含むようにします。

// app.py
    return {
        "statusCode": 200,
        'headers': {
            'Access-Control-Allow-Headers': 'Content-Type',
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'OPTIONS,POST,GET'
        },        
        "body": json.dumps({
            "message": "hello world",
            # "location": ip.text.replace("\n", "")
        }),
    }

これでデプロイ後再度実行すると無事apiが叩けました!
image.png

試しにresponse部分を確認すると、先程追加したheaderが含まれているのを確認できます。
image.png

原因部分をもう少し詳しく

(※こちらの記事が大変参考になりました!なんとなく CORS がわかる...はもう終わりにする。

先述したとおり、今回の原因はCORSに関する実装がちゃんとされてなかったことです。
CORSとは、「Cross-Origin Resource Sharing」の略で、別のオリジン間でリソースを共有するためには、ちゃんと許可してあげようねというものだそうです。今回私は、

  • 叩き元:localhost
  • 叩き先:API Gateway

としたので、これらは当然ドメインが異なります。なのでAPIからのresponseにリソースの共有を許可する'Access-Control-Allow-Origin': '*'をheaderに追加する必要があったということですね。

終わりに

今回は自分で作成したAPIに対し、アプリを介して叩いてみるということを検証しました。これまでCORSは言葉を知っている程度だったので今回みたいなことは考えたことがなかったです。そのため、「なんでcurlで叩けたのに、アプリ経由だと叩けないの…?」とだいぶハマりました。
今回の経験を経て、「じゃあPOSTも同様のことを気をつける必要があるんだな!」と気をつけてたら、また別のところでハマりました…
それは次回で説明できればと思います。

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

クラウドの概念

【クラウドの概念】

ポートフォリオにAWSを本格的に組み込んでいこうと思っているので、
その予備知識としてクラウドについて簡単にまとめました。

クラウドコンピューティング

インターネット上にアクセスして、物理的に見えないコンピュータを利用すること。

ユーザーがインフラやソフトウェアを持たなくても、インターネットを通じて、サービスを必要な時に必要な分だけ利用できる特徴がもつ。

○○aaS(as a Service) と呼ばれるようなクラウドコンピューティングサービスがいくつかある。

仮想化技術

仮想化技術とは、物理的なコンポーネントを、複数の論理的コンポーネントに分割して使用する技術である。
クラウドコンピューティングは、インターネット技術や仮想化技術が基本になっている。

1台の物理的コンピュータを、あたかも何十台といったコンピュータがあるかのように、論理的に分割して、何十台といったコンピュータを共有する。
これによって、提供者と利用者のwin-win関係ができる。

○○aaS(as a Service)の主要例

・IaaS(Infrastructure as a Service)
  サーバー(インフラ)を提供するクラウドサービス

・PaaS(Platform as a Service)
  開発環境を提供するクラウドサービス

・SaaS(Software as a Service)
  ソフトウェアを提供するクラウドサービス

余談

Git,GitHubを使用せずにSVNを使用しているようなレガシーSIerでもAWSは使用されています。(私個人の範囲でのお話です。)

クラウドの知識、主にAWSの知識はITの世界では既に常識になっていると思います。
今後も引き続き、業務内外でしっかりAWSを学んでいきます。

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

terraformでEKS上にオンラインシステムを構築してみた

はじめに

最近マルチクラウドへの需要が増えている気がします。
パブリッククラウド毎のサービス特性やビジネス判断によって、その時々での最適解を選ぶ必要性に迫られている今日この頃。。

今の現場ではAWS ECS上でコンテナオーケストレーションを行っていますが、既存システムを他クラウドへ移行する際のポータビリティを考えると、ECSは完全にAWSロックインだし、Kubernetes Cluster管理だけ任せることができればEKS、GKE、AKSのように他クラウドへの移行も簡単になるのかなと漠然と考えていました。
個人的にもKubernetes Service系のマネージドサービスを使ってみたいということもあり、今回はEKSを使って簡単なオンラインシステムを構築してみたので、手順と所感を残していきます。

アーキテクチャ

  • インターネットからの通信をALBで受けて、バックエンドのnginxに接続する。
  • 名前解決にはexternal-dnsコンテナを利用する。

eks.png

フォルダ構成

terraform
│  eks.tf
│  helm_external_dns.tf
│  helm_nginx.tf
│  iam_service_account.tf
│
└─charts
    ├─external_dns
    │  │  Chart.yml
    │  │  values.yml
    │  │
    │  └─templates
    │          external-dns.yml
    │
    └─online_app
        │  Chart.yml
        │  values.yml
        │
        └─templates
                nginx-deployment.yml
                nginx-service.yml

構築手順

EKS ClusterとWorker Nodeのデプロイ

terraform公式の eksモジュールを利用。公式なだけあってよく作りこまれています。
work_groupsの設定だけカスタマイズするくらいで、あとは特に変更せずに使えました。

eks.tf
module "online_app" {
  source          = "terraform-aws-modules/eks/aws"
  version         = "v12.2.0"
  cluster_name    = "online-app"
  cluster_version = "1.17"

  // publicサブネットを含めないとexternal ALBが作成されないので注意
  subnets         = concat(data.aws_subnet.private_subnets.*.id, data.aws_subnet.public_subnets.*.id)
  vpc_id          = data.aws_vpc.vpc.id

  worker_groups = [
    {
      instance_type                 = "t3.medium"
      asg_max_size                  = 1
      // worker nodeにssh接続したい場合に指定
      key_name                      = "dummy"
      // ssh接続元IPを絞りたいなど、特殊な通信要件がある場合に指定
      additional_security_group_ids = [sg-xxxx]
      subnets                       = data.aws_subnet.private_subnets.*.id
    }
  ]
}

terraform applyを実行すればEKSクラスタ構築は完了です。構築に15分ほどかかるので気長に待ちます。

k8nのコード準備

ECSではterraformコードとして表現していたコンテナオーケストレーションのコードを、EKSではKubernetesのyamlファイルに記述する必要があります。
LoadBalancerのクラウド毎の個別設定はmetadata->annotationsで設定していきます。
LoadBalancer service

charts/templates/nginx-deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx
          ports:
            - containerPort: 80

kubernetes単独だと変数化ができないので、helmを利用して変数化しています。

charts/templates/nginx-service.yml
apiVersion: apps/v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-backend-protocol: http
    service.beta.kubernetes.io/aws-load-balancer-ssl-cert: {{ .Values.loadBalancer.sslCert }}
    service.beta.kubernetes.io/aws-load-balancer-ssl-ports: "https"
    external-dns.alpha.kubernetes.io/hostname: {{ .Values.loadBalancer.hostname }}
spec:
  selector:
    app: nginx
  ports:
    - name: https
      port: 443
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

作成されたLoad Balancerの名前解決はどうすればいいのか悩んでいたところ、externalDNSというAuto Discovery用のサービスがあるとのこと。metadata->annotationsに external-dns.alpha.kubernetes.io/hostname を指定することで、Serviceに到達するためのRoute 53 recordを自動で作成してくれます。Kubernetes yamlはexternalDNSのサイトに記載されているテンプレートを元に、変数化しました。

external-dns.yml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: external-dns
  annotations:
    eks.amazonaws.com/role-arn: {{ .Values.eks.serviceAccountRoleArn }}
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRole
metadata:
  name: external-dns
rules:
  - apiGroups: [""]
    resources: ["services","endpoints","pods"]
    verbs: ["get","watch","list"]
  - apiGroups: ["extensions","networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get","watch","list"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["list","watch"]
---
apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: external-dns-viewer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: external-dns
subjects:
  - kind: ServiceAccount
    name: external-dns
    namespace: default
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: external-dns
spec:
  strategy:
    type: Recreate
  selector:
    matchLabels:
      app: external-dns
  template:
    metadata:
      labels:
        app: external-dns
    spec:
      serviceAccountName: external-dns
      containers:
        - name: external-dns
          image: k8s.gcr.io/external-dns/external-dns:v0.7.3
          args:
            - --source=service
            - --source=ingress
            - --domain-filter={{ .Values.externalDns.domainFilter }}
            - --provider=aws
            - --aws-zone-type={{ .Values.externalDns.zoneType }}
            - --registry=txt
            - --txt-owner-id=my-hostedzone-identifier
      securityContext:
        fsGroup: {{ .Values.externalDns.securityContext.fsGroup }}

externalDNSコンテナにRoute53の操作を許可するために、Service Accountとして利用するIAM Roleを作成して、EKSのOpenID Connect Providerと連携させます。
こちらのページを参考に、terraformコードを作成しました。

iam_service_account.tf
data "aws_iam_policy_document" "eks_assume_role_policy" {
  statement {
    effect = "Allow"

    principals {
      type        = "Federated"
      identifiers = [aws_iam_openid_connect_provider.eks.arn]
    }

    actions = ["sts:AssumeRoleWithWebIdentity"]
    condition {
      test     = "StringEquals"
      variable = "${aws_iam_openid_connect_provider.eks.url}:aud"
      values   = ["sts.amazonaws.com"]
    }
  }
}

data aws_iam_policy_document AllowExternalDNSUpdates {
  statement {
    effect = "Allow"

    actions = [
      "route53:ChangeResourceRecordSets",
    ]

    resources = [
      "arn:aws:route53:::hostedzone/*",
    ]
  }

  statement {
    effect = "Allow"

    actions = [
      "route53:ListHostedZones",
      "route53:ListResourceRecordSets"
    ]

    resources = [
      "*",
    ]
  }
}

resource aws_iam_role_policy cluster_AllowExternalDNSUpdates {
  name   = "AllowExternalDNSUpdates"
  role   = aws_iam_role.external_dns_cluster_AllowExternalDNSUpdates.id
  policy = data.aws_iam_policy_document.AllowExternalDNSUpdates.json
}

resource aws_iam_role external_dns_cluster_AllowExternalDNSUpdates {
  name               = "${local.name}-external-dns"
  assume_role_policy = data.aws_iam_policy_document.eks_assume_role_policy.json
}

resource aws_iam_openid_connect_provider eks {
  url = module.online_app.cluster_oidc_issuer_url

  client_id_list = [
    "sts.amazonaws.com"
  ]
  thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]
}

helmコードの準備

AWSリソースとKubernetesコンテナの準備が整ったので、コンテナをデプロイするためのコードを作ります。helmをそのまま使ってもデプロイできますが、今回はterraform Helm providerを利用しました。

terraform Helm providerを使ってみて、下記の点で素晴らしいと感じました。
1. terraformで構築したAWSの情報を共有できるため、helm実行時に渡す変数をaws-cliなどを使って取得する必要がない。
2. helm chartsのデプロイ状態がterraform stateとして管理できる。

Helmのコードとterraform Helm Providerのコードは以下の通りです。

charts/nginx/Charts.yml
apiVersion: v1
appVersion: "1.0"
description: nginx helm chart for Kubernetes
name: nginx
version: 1.0.0
charts/nginx/values.yml
loadBalancer:
  sslCert:
  hostname:
helm_nginx.tf
provider "helm" {
  version = "~> 1.3.0"

  kubernetes {
    config_path = module.online_app.kubeconfig_filename
  }
}

resource "helm_release" "nginx" {
  name         = "nginx-chart"
  chart        = "./charts/nginx"
  // force_updateをtrueにすることで、helm chartに差分があったら更新する。
  force_update = true

  set {
    name  = "loadBalancer.sslCert"
    value = data.aws_acm_certificate.this.arn
  }
  set {
    name  = "loadBalancer.hostname"
    value = "nginx.${data.aws_route53_zone.this.name}"
  }
}

同様にexternalDNSのデプロイコードも作成していきます。

charts/external_dns/Charts.yml
apiVersion: v1
appVersion: "1.0"
description: external-dns chart for Kubernetes
name: external-dns
version: 1.0.0
charts/external_dns/values.yml
eks:
  serviceAccountRoleArn:

externalDns:
  domainFilter:
  zoneType: public
  securityContext:
    fsGroup: 65534
helm_external_dns.tf
resource "helm_release" "external_dns" {
  name         = "external-dns-chart"
  chart        = "./charts/external_dns"
  force_update = true

  set {
    name  = "eks.serviceAccountRoleArn"
    value = aws_iam_role.external_dns_cluster_AllowExternalDNSUpdates.arn
  }

  set {
    name  = "externalDns.domainFilter"
    value = data.aws_route53_zone.this.name
  }
}

動作確認

コードの準備ができたら再度terraform applyを実行します。
うまく動いているようなら external-dns.alpha.kubernetes.io/hostname で指定したホストに接続することで、nginxの画面が表示されます。
image.png
また、externalDNS podのログを見るとちゃんとRoute 53へのUPSERTに成功していることが確認できました。

kubectl logs external-dns-xxx # please use your pod id

externalDNS.png

EKSを使ってみて所感

  1. EKSクラスタ作成に時間がかかる。
    コスト節約のために毎回terraformでVPC周りから検証環境を作り直しているが、ECSと比べてEKSはクラスタ作成に時間がかかる。検証するたびに毎回15分以上待つのはツライ。。

  2. ECSよりもポータビリティが上がった (気がする…)
    Kubernetes yamlに付与するmetadataの種類によってマルチクラウドに対応可能。
    ECSでは必要なAWSリソースを自分で構築しなければいけなかったが、EKSではmetadataを付与することでALBが自動で作成されたり、DNS recordが作られたりと、特定クラウドを意識させない点はよい。

  3. リソースの統合管理
    ECSはAWS管理コンソール上で、コンテナインスタンスのリソース使用状況、各タスクの実行状態やサービスタスク起動数など確認できる統合管理画面が提供されているが、それに比べるとEKSの管理画面は情報が不足していると感じた。Kubernetesの統合管理を実現するための仕組みを検討する必要がある。

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

AWS Route 53 Resolver & Transit Gateway ~ demo

AWS Route 53 Resolver と Transit Gateway を利用して、複数の AWS VPC が異なる AWS アカウントで管理されている場合、VPC 間の名前解決には Amazon Route 53 Resolver が便利です。


1.CloudFormationによるVPC,Transit Gatewayの作成

リージョン vpc
リージョン1 vpc-A
リージョン2 vpc-BC
vpc-A

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Hands-On AWS Transit Gateway with Amazon Route 53 Resolver: Creates a Virginia VPC-A and Transit Gateway then check your TGW id for the other TGW Attachements.",
  "Outputs": {
    "VirginiaTGWId": {
      "Value": {
        "Ref": "VirginiaTGW"
      }
    },
    "vpcAId": {
      "Value": {
        "Ref": "VPCA"
      }
    },
    "vpcACIDRRange": {
      "Value": {
        "Ref": "vpcACIDRRange"
      }
    },
    "vpcAInstanceId": {
      "Value": {
        "Ref": "vpcAInstance"
      }
    }
  },
  "Parameters": {
    "Ec2ImageId": {
      "Type": "AWS::SSM::Parameter::Value<String>",
      "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    },
    "vpcACIDRRange": {
      "Description": "The IP address range for your new VPC-A.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "192.168.0.0/16",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcAEC2PublicSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-A.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "192.168.1.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcAEC2PrivateSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-A.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "192.168.0.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
  },
  "Resources": {
    "AttachGateway": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "VPCA"
        },
        "InternetGatewayId": {
          "Ref": "myInternetGateway"
        }
      }
    },
    "EIP": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "VPCA"
      }
    },
    "IAMIP3UK0E": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "IAMR3XJT6"
          }
        ]
      }
    },
    "IAMR3XJT6": {
      "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"
        ],
        "Path": "/"
      }
    },
    "myInternetGateway": {
      "Type": "AWS::EC2::InternetGateway"
    },
    "NAT": {
      "Type": "AWS::EC2::NatGateway",
      "Properties": {
        "AllocationId": {
          "Fn::GetAtt": [
            "EIP",
            "AllocationId"
          ]
        },
        "SubnetId": {
          "Ref": "vpcAPublicSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCA-NatGw"
          }
        ]
      }
    },
    "PrivateRoute": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcAPrivateRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": {
          "Ref": "NAT"
        }
      }
    },
    "PrivateRoute10": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "VirginiaTGWvpcAattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcAPrivateRouteTable"
        },
        "DestinationCidrBlock": "10.0.0.0/16",
        "TransitGatewayId": {
          "Ref": "VirginiaTGW"
        }
      }
    },
    "PrivateRoute172": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "VirginiaTGWvpcAattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcAPrivateRouteTable"
        },
        "DestinationCidrBlock": "172.16.0.0/16",
        "TransitGatewayId": {
          "Ref": "VirginiaTGW"
        }
      }
    },
    "PrivateSubnetRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcAPrivateSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcAPrivateRouteTable"
        }
      }
    },
    "PublicSubnetRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcAPublicSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcAPublicRouteTable"
        }
      }
    },
    "Route": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "AttachGateway",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcAPublicRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": {
          "Ref": "myInternetGateway"
        }
      }
    },
    "VirginiaTGW": {
      "Type": "AWS::EC2::TransitGateway",
      "Properties": {
        "AmazonSideAsn": "64512",
        "AutoAcceptSharedAttachments": "enable",
        "DefaultRouteTableAssociation": "enable",
        "DefaultRouteTablePropagation": "enable",
        "Description": "Virginia-TGW-ASN-64512",
        "DnsSupport": "enable",
        "Tags": [
          {
            "Key": "Name",
            "Value": "VirginiaTGW"
          }
        ],
        "VpnEcmpSupport": "disable"
      }
    },
    "VirginiaTGWvpcAattach": {
      "Type": "AWS::EC2::TransitGatewayAttachment",
      "Properties": {
        "SubnetIds": [
          {
            "Ref": "vpcAPrivateSubnet"
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": "Virginia-TGW-VPC-A-Attachement"
          }
        ],
        "TransitGatewayId": {
          "Ref": "VirginiaTGW"
        },
        "VpcId": {
          "Ref": "VPCA"
        }
      }
    },
    "VPCA": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": {
          "Ref": "vpcACIDRRange"
        },
        "EnableDnsSupport": true,
        "EnableDnsHostnames": true,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCA"
          }
        ]
      }
    },
    "vpcAEC2SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Public instance security group",
        "VpcId": {
          "Ref": "VPCA"
        },
        "SecurityGroupIngress": [
          {
            "IpProtocol": "icmp",
            "FromPort": 8,
            "ToPort": -1,
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "udp",
            "FromPort": "33434",
            "ToPort": "33523",
            "CidrIp": "0.0.0.0/0"
          }
        ]
      }
    },
    "vpcAInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "IamInstanceProfile": {
          "Ref": "IAMIP3UK0E"
        },
        "ImageId": {
          "Ref": "Ec2ImageId"
        },
        "InstanceType": "t2.micro",
        "PrivateIpAddress": "192.168.0.10",
        "SecurityGroupIds": [
          {
            "Ref": "vpcAEC2SecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "vpcAPrivateSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcAInstance"
          }
        ]
      }
    },
    "vpcAPrivateRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCA"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcAPrivateRouteTable"
          }
        ]
      }
    },
    "vpcAPrivateSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": {
          "Ref": "vpcAEC2PrivateSubnetCIDRRange"
        },
        "VpcId": {
          "Ref": "VPCA"
        },
        "AvailabilityZone": "us-east-1a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcAPrivateSubnet"
          }
        ]
      }
    },
    "vpcAPublicRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCA"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcAPublicRouteTable"
          }
        ]
      }
    },
    "vpcAPublicSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "CidrBlock": {
          "Ref": "vpcAEC2PublicSubnetCIDRRange"
        },
        "VpcId": {
          "Ref": "VPCA"
        },
        "AvailabilityZone": "us-east-1a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcAPublicSubnet"
          }
        ]
      }
    }
  }
}
VPC-BC

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Description": "Hands-On AWS Transit Gateway with Amazon Route 53 Resolver: Creates Oregon VPC-B/C and then creates a Transit Gateway, Attache with an existing VPC that you specify.",
  "Outputs": {
    "vpcBInstanceId": {
      "Value": {
        "Ref": "vpcBInstance"
      }
    },
    "vpcCInstanceId": {
      "Value": {
        "Ref": "vpcCInstance"
      }
    },
    "vpcCId": {
      "Value": {
        "Ref": "VPCC"
      }
    }
  },
  "Parameters": {
    "Ec2ImageId": {
      "Type": "AWS::SSM::Parameter::Value<String>",
      "Default": "/aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2"
    },
    "vpcBCIDRRange": {
      "Description": "The IP address range for your new VPC-B.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "10.0.0.0/16",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcBEC2PublicSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-B.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "10.0.1.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcBEC2PrivateSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-B.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "10.0.0.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcCCIDRRange": {
      "Description": "The IP address range for your new VPC-C.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "172.16.0.0/16",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcCEC2PublicSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-C.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "172.16.1.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    },
    "vpcCEC2PrivateSubnetCIDRRange": {
      "Description": "The IP address range for a subnet in VPC-C.",
      "Type": "String",
      "MinLength": "9",
      "MaxLength": "18",
      "Default": "172.16.0.0/24",
      "AllowedPattern": "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})",
      "ConstraintDescription": "must be a valid IP CIDR range of the form x.x.x.x/x."
    }
  },
  "Resources": {
    "EIPtoVpcBNat": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "VPCB"
      }
    },
    "EIPtoVpcCNat": {
      "Type": "AWS::EC2::EIP",
      "Properties": {
        "Domain": "VPCB"
      }
    },
    "IAMIP3UK0F": {
      "Type": "AWS::IAM::InstanceProfile",
      "Properties": {
        "Path": "/",
        "Roles": [
          {
            "Ref": "IAMR3XJT7"
          }
        ]
      }
    },
    "IAMR3XJT7": {
      "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"
        ],
        "Path": "/"
      }
    },
    "OregonTGW": {
      "Type": "AWS::EC2::TransitGateway",
      "Properties": {
        "AmazonSideAsn": "64513",
        "AutoAcceptSharedAttachments": "enable",
        "DefaultRouteTableAssociation": "enable",
        "DefaultRouteTablePropagation": "enable",
        "Description": "Oregon-TGW-ASN-64513",
        "DnsSupport": "enable",
        "Tags": [
          {
            "Key": "Name",
            "Value": "OregonTGW"
          }
        ],
        "VpnEcmpSupport": "disable"
      }
    },
    "OregonTGWvpcBattach": {
      "Type": "AWS::EC2::TransitGatewayAttachment",
      "Properties": {
        "SubnetIds": [
          {
            "Ref": "vpcBPrivateSubnet"
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": "Oregon-TGW-VPC-B-Attachement"
          }
        ],
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        },
        "VpcId": {
          "Ref": "VPCB"
        }
      }
    },
    "OregonTGWvpcCattach": {
      "Type": "AWS::EC2::TransitGatewayAttachment",
      "Properties": {
        "SubnetIds": [
          {
            "Ref": "vpcCPrivateSubnet"
          }
        ],
        "Tags": [
          {
            "Key": "Name",
            "Value": "Oregon-TGW-VPC-C-Attachement"
          }
        ],
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        },
        "VpcId": {
          "Ref": "VPCC"
        }
      }
    },
    "PrivateSubnetBRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcBPrivateSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcBPrivateRouteTable"
        }
      }
    },
    "PrivateSubnetCRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcCPrivateSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcCPrivateRouteTable"
        }
      }
    },
    "PublicRouteVPCB": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "vpcBAttachGateway",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcBPublicRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": {
          "Ref": "vpcBInternetGateway"
        }
      }
    },
    "PublicRouteVPCC": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "vpcCAttachGateway",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcCPublicRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "GatewayId": {
          "Ref": "vpcCInternetGateway"
        }
      }
    },
    "PublicSubnetBRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcBPublicSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcBPublicRouteTable"
        }
      }
    },
    "PublicSubnetCRouteTableAssociation": {
      "Type": "AWS::EC2::SubnetRouteTableAssociation",
      "Properties": {
        "SubnetId": {
          "Ref": "vpcCPublicSubnet"
        },
        "RouteTableId": {
          "Ref": "vpcCPublicRouteTable"
        }
      }
    },
    "VPCB": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": {
          "Ref": "vpcBCIDRRange"
        },
        "EnableDnsSupport": true,
        "EnableDnsHostnames": true,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCB"
          }
        ]
      }
    },
    "vpcBAttachGateway": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "VPCB"
        },
        "InternetGatewayId": {
          "Ref": "vpcBInternetGateway"
        }
      }
    },
    "vpcBEC2SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Public instance security group",
        "VpcId": {
          "Ref": "VPCB"
        },
        "SecurityGroupIngress": [
          {
            "IpProtocol": "icmp",
            "FromPort": 8,
            "ToPort": -1,
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "udp",
            "FromPort": "33434",
            "ToPort": "33523",
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "udp",
            "FromPort": "53",
            "ToPort": "53",
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "tcp",
            "FromPort": "53",
            "ToPort": "53",
            "CidrIp": "0.0.0.0/0"
          }
        ]
      }
    },
    "vpcBInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "IamInstanceProfile": {
          "Ref": "IAMIP3UK0F"
        },
        "ImageId": {
          "Ref": "Ec2ImageId"
        },
        "InstanceType": "t2.micro",
        "PrivateIpAddress": "10.0.0.10",
        "SecurityGroupIds": [
          {
            "Ref": "vpcBEC2SecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "vpcBPrivateSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcBInstance"
          }
        ]
      }
    },
    "vpcBInternetGateway": {
      "Type": "AWS::EC2::InternetGateway"
    },
    "vpcBNAT": {
      "Type": "AWS::EC2::NatGateway",
      "Properties": {
        "AllocationId": {
          "Fn::GetAtt": [
            "EIPtoVpcBNat",
            "AllocationId"
          ]
        },
        "SubnetId": {
          "Ref": "vpcBPublicSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCB-NatGw"
          }
        ]
      }
    },
    "vpcBPrivateRoute": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcBPrivateRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": {
          "Ref": "vpcBNAT"
        }
      }
    },
    "vpcBPrivateRoute172": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "OregonTGWvpcBattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcBPrivateRouteTable"
        },
        "DestinationCidrBlock": "172.16.0.0/16",
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        }
      }
    },
    "vpcBPrivateRoute192": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "OregonTGWvpcBattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcBPrivateRouteTable"
        },
        "DestinationCidrBlock": "192.168.0.0/16",
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        }
      }
    },
    "vpcBPrivateRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCB"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcBPrivateRouteTable"
          }
        ]
      }
    },
    "vpcBPrivateSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": {
          "Ref": "VPCB"
        },
        "CidrBlock": {
          "Ref": "vpcBEC2PrivateSubnetCIDRRange"
        },
        "AvailabilityZone": "us-west-2a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcBPrivateSubnet"
          }
        ]
      }
    },
    "vpcBPublicRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCB"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcBPublicRouteTable"
          }
        ]
      }
    },
    "vpcBPublicSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": {
          "Ref": "VPCB"
        },
        "CidrBlock": {
          "Ref": "vpcBEC2PublicSubnetCIDRRange"
        },
        "AvailabilityZone": "us-west-2a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcBPublicSubnet"
          }
        ]
      }
    },
    "VPCC": {
      "Type": "AWS::EC2::VPC",
      "Properties": {
        "CidrBlock": {
          "Ref": "vpcCCIDRRange"
        },
        "EnableDnsSupport": true,
        "EnableDnsHostnames": true,
        "InstanceTenancy": "default",
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCC"
          }
        ]
      }
    },
    "vpcCAttachGateway": {
      "Type": "AWS::EC2::VPCGatewayAttachment",
      "Properties": {
        "VpcId": {
          "Ref": "VPCC"
        },
        "InternetGatewayId": {
          "Ref": "vpcCInternetGateway"
        }
      }
    },
    "vpcCEC2SecurityGroup": {
      "Type": "AWS::EC2::SecurityGroup",
      "Properties": {
        "GroupDescription": "Public instance security group",
        "VpcId": {
          "Ref": "VPCC"
        },
        "SecurityGroupIngress": [
          {
            "IpProtocol": "icmp",
            "FromPort": 8,
            "ToPort": -1,
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "udp",
            "FromPort": "33434",
            "ToPort": "33523",
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "udp",
            "FromPort": "53",
            "ToPort": "53",
            "CidrIp": "0.0.0.0/0"
          },
          {
            "IpProtocol": "tcp",
            "FromPort": "53",
            "ToPort": "53",
            "CidrIp": "0.0.0.0/0"
          }
        ]
      }
    },
    "vpcCInstance": {
      "Type": "AWS::EC2::Instance",
      "Properties": {
        "IamInstanceProfile": {
          "Ref": "IAMIP3UK0F"
        },
        "ImageId": {
          "Ref": "Ec2ImageId"
        },
        "InstanceType": "t2.micro",
        "PrivateIpAddress": "172.16.0.10",
        "SecurityGroupIds": [
          {
            "Ref": "vpcCEC2SecurityGroup"
          }
        ],
        "SubnetId": {
          "Ref": "vpcCPrivateSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcCInstance"
          }
        ]
      }
    },
    "vpcCInternetGateway": {
      "Type": "AWS::EC2::InternetGateway"
    },
    "vpcCNAT": {
      "Type": "AWS::EC2::NatGateway",
      "Properties": {
        "AllocationId": {
          "Fn::GetAtt": [
            "EIPtoVpcCNat",
            "AllocationId"
          ]
        },
        "SubnetId": {
          "Ref": "vpcCPublicSubnet"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "VPCC-NatGw"
          }
        ]
      }
    },
    "vpcCPrivateRoute": {
      "Type": "AWS::EC2::Route",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcCPrivateRouteTable"
        },
        "DestinationCidrBlock": "0.0.0.0/0",
        "NatGatewayId": {
          "Ref": "vpcCNAT"
        }
      }
    },
    "vpcCPrivateRoute10": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "OregonTGWvpcCattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcCPrivateRouteTable"
        },
        "DestinationCidrBlock": "10.0.0.0/16",
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        }
      }
    },
    "vpcCPrivateRoute192": {
      "Type": "AWS::EC2::Route",
      "DependsOn": "OregonTGWvpcCattach",
      "Properties": {
        "RouteTableId": {
          "Ref": "vpcCPrivateRouteTable"
        },
        "DestinationCidrBlock": "192.168.0.0/16",
        "TransitGatewayId": {
          "Ref": "OregonTGW"
        }
      }
    },
    "vpcCPrivateRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCC"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcCPrivateRouteTable"
          }
        ]
      }
    },
    "vpcCPrivateSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": {
          "Ref": "VPCC"
        },
        "CidrBlock": {
          "Ref": "vpcCEC2PrivateSubnetCIDRRange"
        },
        "AvailabilityZone": "us-west-2a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcCPrivateSubnet"
          }
        ]
      }
    },
    "vpcCPublicRouteTable": {
      "Type": "AWS::EC2::RouteTable",
      "Properties": {
        "VpcId": {
          "Ref": "VPCC"
        },
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcCPublicRouteTable"
          }
        ]
      }
    },
    "vpcCPublicSubnet": {
      "Type": "AWS::EC2::Subnet",
      "Properties": {
        "VpcId": {
          "Ref": "VPCC"
        },
        "CidrBlock": {
          "Ref": "vpcCEC2PublicSubnetCIDRRange"
        },
        "AvailabilityZone": "us-west-2a",
        "Tags": [
          {
            "Key": "Name",
            "Value": "vpcCPublicSubnet"
          }
        ]
      }
    }
  }
}

2.2つ Transit Gatewayに対 し、リージョン間ピアリングを行う

「Create Transit Gateway 接続」から「Create Transit Gateway Attachment」
スクリーンショット 2020-09-19 17.56.01.png

「Transit Gateway ID」を選択して、「Attachment type」には、「Peering Connection」を指定。
「Region」にvpcーAのもの、「Transit gateway (accepter)*」にvpc-AのTransit Gateway IDを入力。
スクリーンショット 2020-09-19 18.02.14.png

vpcーAのリージョンに移動し、承認作業「Accept」を行う。(数分間要します。)
スクリーンショット 2020-09-19 18.08.03.png

承認されたら、peer-attachmentのアタッチメントが表示された状態で 、右のAssociated route tableに表示されたTransit Gateway Route TableのIDを開き、VPC-B(10.0.0.0/16)とVPC-C(172.16.0.0/16)への静的経路を追加する。
スクリーンショット 2020-09-19 18.16.52.png
※もう片方のリージョンでも同様にVPC-A(192.168.0.0/16)への静的経路を追加する。

EC2にコンソール接続し、pingで疎通確認をしておく。

3.Route 53 プライベートホストゾーンとRoute 53 インバウンド Resolverの設定をする

ドメイン名:example.jp プライベートゾーンでホストを作成する。
スクリーンショット 2020-09-19 18.40.58.png

次にレコードを「シンプルルーティング」で作成する。今回でいうとvpc-cのホストに作成することとなる。
スクリーンショット 2020-09-19 18.43.01.png

vpc-cのEC2にログインし、名前解決できているか確認する。

sh-4.2$ dig test.example.jp. +short
172.16.0.10

Route53でインバウンドエンドポイントを開き、エンドポイントを作成していく。

ステップ 1 エンドポイントの設定
インバウンドのみ:お使いのネットワークまたは別のVPCからVPCへのDNSクエリを許可するエンドポイントの設定。
スクリーンショット 2020-09-19 18.48.12.png

ステップ 2 インバウンドエンドポイントの設定
ここでは、「vpc-c」がエンドポイントとなるため、vpc-cの情報を入力する。
スクリーンショット 2020-09-19 18.50.56.png

IPアドレス#1
サブネット:vpcCPrivateSubnet
IPアドレス:自分で指定 172.16.0.5
スクリーンショット 2020-09-19 18.51.50.png

IPアドレス#2
サブネット:vpcCPublicSubnet
IPアドレス:自分で指定 172.16.1.5
スクリーンショット 2020-09-19 18.52.40.png

他の情報はデフォルトで作成を完了させる。

4.VPCで参照用DNSを変更する

最後にVPC-AとVPC-BにDHCPオプションセットを関連づけし、名前解決要求をVPC-CのRoute 53 Resolver Inbound Endpointへ振り向ける。
スクリーンショット 2020-09-19 18.57.00.png

VPC-AとVPC-BにDHCPオプションセットをアタッチする。

各VPCから名前解決ができる事を確認します。

sh-4.2$ sudo service network restart
Restarting network (via systemctl):                        [  OK  ]
sh-4.2$ cat /etc/resolv.conf
options timeout:2 attempts:5
; generated by /usr/sbin/dhclient-script
nameserver 172.16.0.5
nameserver 172.16.1.5
sh-4.2$ test.example.co.jp +short
172.16.0.10
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

藤井聡太二冠のPCをAWSと価格比較してみた

はじめに

先日このような記事を見かけました。

藤井聡太二冠「自作PC」の値段にパソコンマニアもびっくり

50万のCPUを搭載してるそうです。
普通に使うには高い!となりますが、用途がはっきりしてますからね。安い投資じゃないでしょうか。

でも、この記事見てすぐ思ってしまいました。
「ずっと使ってるわけじゃないだろうし、AWSで建てたら安くならんかな?」

比べてみた

CPU以外は分からないので、CPUにフォーカスしてざっくり比較してみます。

藤井聡太二冠のPC

記事を見るとこちらのCPUを搭載してるようですね。
Ryzen Threadripper 3990X

価格.comで調べたところ、サーバ用途を除くと最高値のCPUのようです。
https://kakaku.com/pc/cpu/itemlist.aspx?pdf_so=p2

Ryzen 5/7などとはソケット形状が異なるので、取付ける事もできません。
マザボだけでも結構する…
Socket sTRX4 マザーボード

AWSのインスタンス

単純にEC2インスタンスと比較する事にします。

CPUバウンドな処理でしょうから「コンピューティング最適化」インスタンスを
その中でもAMDのプロセッサを搭載している「C5a」ファミリーと比較します。
C5 インスタンス

先ほどの「Ryzen Threadripper 3990X」のCPU数は64なので、「c5a.16xlarge」相当…
というわけではありません。

vCPU数は(一部のインスタンスを除き)スレッド数なので、スレッド数と比較しなければなりません。
正確にはこちらのドキュメントの表に記載されていました。
https://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/instance-optimize-cpu.html

「c5a.16xlarge」も32コア64スレッドですね。
「Ryzen Threadripper 3990X」は64コア128スレッドなので、128vCPUのインスタンスと比較しなければなりません。

…なかった

仕方がないので「c5a.16xlarge」×2台と比較します。

リージョンは「ムンバイ」(ap-south-1)が安かったのでそちらにしてみます。
※バージニアやオレゴンが最安だと思ってましたが、c5aは違いました。全リージョン簡単に比較できるサイトとか無いかな…

価格算出

料金計算には、AWS Pricing Calculatorを使用します。

どのような使い方をされるか不明ですが、一旦12時間/日使う前提で試算してみます。
朝の10時から夜の10時まで使用するイメージですね。

EC2を選択して
スクリーンショット 2020-09-22 121627.png

ムンバイを選んで「Advanced estimate」を使う事にして
スクリーンショット 2020-09-22 121731.png

「c5a.16xlarge」を選択します。
スクリーンショット 2020-09-22 122047.png

12時間/日をどう入力するかですが
Workloadの欄でDaily spike trafficを選択し、以下のように入力すればOKです。
スクリーンショット 2020-09-22 122509.png
※毎日使う、普段は落としてるので0台、使ってる12時間の間はずっと2台起動しているという事です。
※念のためWorkloadがConstant usageで2台の場合と比較してみると、1/2の料金になります。

支払い方法はひとまず「On-Demand」でいきましょう。
スクリーンショット 2020-09-22 151923.png

EBSの欄がデフォルトで入っています。
純粋にインスタンスで比較したいのですがツールの仕様上、0にはできないのでテキトウに減らします。
スクリーンショット 2020-09-22 123437.png

算出結果

年間 13,188.72 USD となりました!(2020/9/22現在)
リンク

現在1ドル105円程度なので、約138万円ですね。
※ちなみに東京リージョンに変えてみると倍ぐらいになりました。こんなに変わるんだ。。t3.microだと20%増しくらいなんですけどね

毎日12時間使うというのが妥当か分かりませんが、仮にその1/3の4時間だとしても46万円くらいですね。

EC2に含まれてるマザボやメモリ、電気代をPC側に加算したとしても50万円のざっくり2割増として60万円程度でしょうか。
1年分でこれですからね。買ったほうが良さそうです。

コストを下げられないか

なんかちょっと悔しいですね(謎)
AWSのコストを下げられないか調べてみます。

リザーブドインスタンス

「c5a.16xlarge」の場合、一番割引率の高い「スタンダード 3 年間 全額前払い」でも62%引きです。
EC2 リザーブドインスタンス料金表

なので、24時間×(100%-62%)=約9時間/日以上使用する場合はリザーブドが安いですが、そうでない場合はオンデマンドの方が安いです。
これでは勝てませんね…

Savings Plans

これならどうでしょう。利用料をコミットする事で安くできます。
分かりやすくするために3年間の前払い無し、で入力してみましたが大して安くなりません。
リンク

なんでだろと思ったら「1時間毎」の一定費用の契約なんですね。(1ヵ月ではない)
Qiita - Saving Plansの特徴

それもそうか。ユーザは一定時間しか使わなくても、AWSとしてはリソースを用意しとかないといけないですもんね。(実際使った事なかったから勉強になった)

なので使わない時間に落としても意味が無くて、リザーブドと同じように使わない時間の割合より割引率の方が高ければ若干安くはなるくらいです。

スポットインスタンス

じゃぁこれしかないかな。中断対策を考えないといけないので、単純には移行できませんが。

こちらを見ると、オンデマンドの半額近くになるようです。
EC2 スポットインスタンスの料金

4時間/日で年間46万円だったのが26万円くらいになりますね。
この料金ならAWSもアリではないでしょうか。

Graviton2 のインスタンスを使う

最近出てきたこれもありました。

確認してみたら「最大40%高いコストパフォーマンス」と書かれていますので、大分コストを抑えられる可能性があります。

実際ベンチマークでもマルチコアの性能は結構高いようです。
https://gigazine.net/news/20200616-intel-vs-graviton2/

スポットインスタンスでコストを抑えるのとは違って、インスタンスの中断対策を考えなくて良いですしね。
また、併用することでさらに安くもできそうです。
※c6g.16xlargeのスポットは余ってるインスタンスが少ないのか割引率は落ちます

まとめ

単純比較すると、藤井聡太二冠のPCの勝ちではないでしょうか。
やりようによってはAWSもアリかもしれません。

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

Ruby on Railsにて、Blocked Host:"ホスト名"が出てしまう時の対処法

背景

Ruby on Railsチュートリアルの1章の演習で、ずっと「Blocked Host:自分のホスト名」というエラーが出てしまい、長時間色々なことを試した結果、ある根本的なことをしていなかったことに気づき解決できたため、この投稿をさせてもらいました。

試したこと

Web上でよく書かれていることとしては、
1.environment/development.rbにて、「config.hosts.clear」を入れることです。
しかし、これを試しても、エラーが発生したままでした。
2.environment/development.rbにて「config.hosts<<"自分のホスト名"」を入れることです。
しかし、これを試しても、またしてもエラーが発生したままでした。

原因

(Ⅰ).違うアプリケーションの中の、development.rbに1と2のプログラムを追加していた。
例えば、自分が作っているアプリケーションがhello_appだとすると、違うアプリケーション(例えば、a_app)の中のdevelopment.rbに1と2のプログラムを追加していたということです。
これでは、a_appのホストが許可されただけで、hello_appのホストは許可されません。
これを間違えた理由としましては、階層構造がenvironment/hello_app/config/environment/development.rb、とenvironmentが2つあることにより、同じ環境内であれば、一つのdevelopment.rbを書き直せば、全てに反映されると勘違いしたことによるものでした。
development.rbの階層は上の通りであり、1つのアプリケーションにつき一つのファイルとなります。
決して、共通ではありません。
自分が作成しているアプリケーション内の、config/environment内のdevelopment.rbを1と2のように書き直すようにしましょう。

(Ⅱ).development.rbを書き直した後に、「rails server」を実行しなかった。
順序としては、「development.rb内のプログラムを書き直す→rails serverを実行する」という手順を踏まないと、いくら(Ⅰ)の手順を踏んだとしましても、エラーが発生したままとなります。

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

CloudWatchメトリクス

CloudWatchメトリクス「期間の5分」について

1. はじめに

  • CloudWatchメトリクスのCPU使用率について「期間の5分」は5分毎にデータを取得しているのかな?
  • 次に取得するまで4分59秒の間はデータを取得しないのかな?と思い、実際にデータ取得の様子を確認してみました。

2. 実験

  • EC2(RHEL8.2)インスタンスにsshログインしてyesコマンドを実行し、CPU使用率を上げます。
  • 下図の様にEC2のCPU使用率のメトリクスをグラフにします。SampleCount「5」に注目すると、5分毎にメトリクスが生成される時に「5」個のデータポイントを使っていることが分かります。

image.png

  • 次にBlackBeltをよく読むと・・・。

image.png

image.png

  • 結論としては「1分間隔でデータポイントを取得し、5分毎にメトリクスを生成」している動きに観えます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】WorkSpacesを利用したテレワーク環境にHA構成のプロキシ(Squid)を構築する

はじめに

前回の記事でWorkSpacesを使ったテレワーク環境の構築方法を紹介しました。
今回は、WorkSpacesから安全なインターネットアクセスを行うため、前回構築した環境に
SquidでHA構成のプロキシを導入したいと思います。

プロキシについて

WorkSpacesのWebブラウザでインターネットにアクセスする際、プロキシを経由することで、プロキシがクライアントの代理でリクエスト先のWebサーバと通信を行ってくれます。

リクエストを受けるWebサーバからすると、接続元がプロキシとなるため、クライアントのIPアドレスを隠蔽することができます。

また、プロキシではアクセス制御やユーザ認証、URLフィルタリングなどを利用することで、よりセキュアなインターネット利用環境を実現できます。

クライアントからのアクセスログを取得できる点もメリットです。

対象者

  • WorkSpacesでプロキシを使いたい方
  • AWSの環境にSquidを導入したい方
  • Auto Scalingの使い方を知りたい方

実現したいこと

前回の記事で作成した環境をベースとし、Auto Scalingを活用したHA構成のプロキシを構築します。

運用の手間がかからないよう、Squidの設定ファイル(squid.conf)はS3バケットに格納します。
そうすることで、設定ファイルを後で変更した時に、その都度AMIの登録や起動テンプレートの作成を行わずにすみます。

プロキシを構築したら、WorkSpaces上でプロキシの設定(※)を行い、プロキシ経由でインターネットに接続できることを確認します。

※ADのグループポリシーでも設定できますが、今回はWorkSpacesでの設定の仕方を説明したいので手動で行います

構成

赤枠内が今回の構築範囲です。
image.png

注意点

  • 2020年9月時点の情報です。
  • AWSマネジメントコンソールを使用します。ルートユーザもしくは権限を満たすIAMユーザを利用できる前提で進めます。
  • AWS利用料にご注意ください。利用料については、AWSの公式ページをご参照ください。

環境構築

1.ネットワーク設定(サブネット作成、ルーティング設定)
2.S3バケット作成、Squid.conf配置
3.Network Load Balancer作成
4.セキュリティグループ作成
5.Auto Scaling設定
6.WorkSpacesプロキシ設定、動作確認

1.ネットワーク設定(サブネット作成、ルーティング設定)

前回作成したVPCに、プロキシを配置するためのパブリックサブネットを作成します。
サブネットはAZ-aとAZ-cにそれぞれ作成し、パブリックサブネット用のルートテーブルを割り当てます。

以下は上記の作業を行った後のサブネットの一覧を表示した状態です。
(わかりやすくするため、WorkSpacesの作成先サブネットのNameを「telework-workspaces-subnet-x」に変更しています)

image.png

2.S3バケット作成、squid.conf格納

まず、squid.confを格納するためのS3バケットを作成します。
このバケットはAuto Scalingで起動するEC2インスタンスからのアクセスしか発生しないため、パブリックアクセスは「すべてブロック」とします。その他の設定はデフォルトのままで問題ありません。
以下は作成時の確認画面です。
image.png
スクリーンショット 2020-09-21 000023.png

バケットが作成されたことを確認します。
スクリーンショット 2020-09-21 000108.png

次に、ローカルでsquidの設定ファイル(squid.conf)を作成します。
テキストエディタを開き、以下を貼り付けて「squid.conf」として保存してください。
※以下は最小限のコンフィグです。80、443以外の検証で不要なポートは閉じていますので、必要に応じて編集してください。
(参考リンク:https://qiita.com/tosier/items/30297afb6ffbd4567eb5)

squid.conf
#
# Recommended minimum configuration:

# Example rule allowing access from your local networks.
# Adapt to list your (internal) IP networks from where browsing
# should be allowed
acl localnet src 10.0.0.0/8 # RFC1918 possible internal network
acl localnet src 172.16.0.0/12  # RFC1918 possible internal network
acl localnet src 192.168.0.0/16 # RFC1918 possible internal network
acl localnet src fc00::/7       # RFC 4193 local private network range
acl localnet src fe80::/10      # RFC 4291 link-local (directly plugged) machines

acl SSL_ports port 443
acl Safe_ports port 80      # http
acl Safe_ports port 443     # https

acl CONNECT method CONNECT

#
# Recommended minimum Access Permission configuration:
#
# Deny requests to certain unsafe ports
http_access deny !Safe_ports

# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports

# Only allow cachemgr access from localhost
http_access allow localhost manager
http_access deny manager

# We strongly recommend the following be uncommented to protect innocent
# web applications running on the proxy server who think the only
# one who can access services on "localhost" is a local user
#http_access deny to_localhost

#
# INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS
#

# Example rule allowing access from your local networks.
# Adapt localnet in the ACL section to list your (internal) IP networks
# from where browsing should be allowed
http_access allow localnet
http_access allow localhost

# And finally deny all other access to this proxy
http_access deny all

# Squid normally listens to port 3128
http_port 3128

# Uncomment and adjust the following to add a disk cache directory.
#cache_dir ufs /var/spool/squid 100 16 256

# Leave coredumps in the first cache dir
coredump_dir /var/spool/squid

#
# Add any of your own refresh_pattern entries above these.
#
refresh_pattern ^ftp:       1440    20% 10080
refresh_pattern ^gopher:    1440    0%  1440
refresh_pattern -i (/cgi-bin/|\?) 0 0%  0
refresh_pattern .       0   20% 4320

squid.confを作成したら、ファイルをバケットに保存します。
AWSのマネジメントコンソールでバケットを開き、ファイルをドラッグ&ドロップして[アップロード]をクリックしてください。
スクリーンショット 2020-09-21 003509.png

3.Network Load Balancer作成

AWSのマネジメントコンソールでEC2の画面を開き、左のメニューから[ロードバランサー]を選択し、[ロードバランサーの作成]をクリックします。
作成画面が開いたら、中央のNetwork Load Balancerの[作成]をクリックします。
スクリーンショット 2020-09-21 004701.png

任意の名前を設定し、スキームは「内部」を選択します。
リスナーに設定するポートには、squid.confで設定したhttp_portを設定してください。
デフォルトのままであれば「3128」です。
image.png

アベイラビリティーゾーンでは、使用しているVPCと「1.ネットワーク設定」で作成したパブリックサブネットを選択します。
スクリーンショット 2020-09-21 010112.png

手順2は何もせずにそのまま次に進んでください。
手順3でターゲットグループを作成します。
こちらもポートには、squid.confで設定したhttp_portを設定してください。
スクリーンショット 2020-09-21 010415.png

手順4ではターゲットの登録ができますが、まだターゲットがないため、この時点では何も設定せずに次に進んでください。
手順5で設定内容を確認し、問題なければ[作成]をクリックします。

4.セキュリティグループ作成

プロキシに設定するセキュリティグループを作成します。
インバウンドルールでSSHとプロキシの設定ポートを許可してください。
セキュリティの面から、SSHは現在自分が使用しているグローバルIPのみを許可します(グローバルIPの確認
以下は作成後の画面です。
スクリーンショット 2020-09-21 012043.png

5.Auto Scaling設定

プロキシ用の起動テンプレートとAuto Scalingグループを作成します。
マネジメントコンソールでEC2の画面を開き、左のメニューから[Auto Scaling グループ]を選択し、[Auto Scaling グループを作成する]をクリックします。
作成画面が開くので、任意の名前を選択し、[起動テンプレートを作成する]をクリックします。
スクリーンショット 2020-09-22 022104.png

起動テンプレートの設定については、以下の画面を参考にしてください。
スクリーンショット 2020-09-22 022730.png
AMIはAmazon Linuxを使用していますが、別のものでも大丈夫です。
その場合はOSに合わせてユーザーデータを変更してください。
インスタンスタイプはお好みのものを設定してください。
スクリーンショット 2020-09-22 022829.png
キーペアの設定は必須ではありませんが、後ほどSSH接続してアクセスログを確認したいため、設定しておきました。
スクリーンショット 2020-09-22 022855.png
スクリーンショット 2020-09-22 022927.png
サブネットは冒頭で作成したパブリックサブネットを選択します。
パブリックIPの自動割り当ては有効化し、先ほど作成したセキュリティグループを設定してください。
スクリーンショット 2020-09-22 023104.png
IAMインスタンスプロフィールには、EC2用のIAMロールを作成して割り当ててください。
ロールには以下のIAMポリシーを作成してアタッチしています。
スクリーンショット 2020-09-22 025555.png
スクリーンショット 2020-09-22 023223.png
スクリーンショット 2020-09-22 023256.png
AMIがAmazon Linuxの場合、ユーザーデータには以下を設定します。

#!/bin/bash

sudo yum update -y
sudo yum -y install squid
chkconfig squid on
aws s3 cp s3://<バケット名>/squid.conf /etc/squid/squid.conf
sudo service squid restart

スクリーンショット 2020-09-22 023723.png

Auto Scalingグループの作成画面に戻り、作成した起動テンプレートをセットします。
次の画面に進み、設定の構成を行います。
スクリーンショット 2020-09-22 030403.png
スクリーンショット 2020-09-22 030509.png

ステップ3、5、6については、特になにも設定せずにそのままでも問題ありません。
ステップ4でグループサイズを決められるので以下を設定します。
・希望する容量…2
・最小キャパシティ…2
・最大キャパシティ…2

これで常時2台プロキシが稼働します。
※後でプロキシの台数を調整したい時は、この値を変更してください。上から全部0にするとプロキシは停止状態となります。

6.WorkSpacesプロキシ設定、動作確認

WorkSpaces上でプロキシ設定を行います。
左下のWindowsアイコンをクリックして[設定]を開き、[ネットワークとインターネット]をクリックします。
スクリーンショット 2020-09-21 013719.png

左のメニューから[プロキシ]をクリックし、手動プロキシ セットアップでプロキシの設定を行います。
・プロキシ サーバーを使う:オン
・アドレス:NLBのパブリックDNS
・ポート: プロキシの設定ポート
スクリーンショット 2020-09-21 014436.png

設定後、Webブラウザを開き、インターネットにアクセスできることを確認します。
Auto Scalingによって起動したEC2インスタンスにログインし、Squidのアクセスログ(/var/log/squid/access.log)を確認すると、WorkSpacesからのWebアクセスのログを確認できます。
image.png

構築手順は以上です。

参考元

Forward Proxy と Reverse Proxy の決定的な違い
Squidでホワイトリストプロキシを作成する
HA構成プロキシ環境をNLBを使って構築してみる

さいごに

Auto Scalingを使ったHA構成のプロキシの構築方法を紹介しました。

プロキシはWorkSpacesに限らず、インターネットアクセスが必要なインスタンスやコンテナを
運用するシーンでも役立ちますので、上手く活用していただければと思います。

今回はプロキシとしてSquidを使いましたが、BIG-IPやFortigateなどのネットワーク仮想アプライアンスを利用することも可能です。このあたりについては、次回以降の記事で取り上げたいと思います。

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

AWS Copilot でnginxを動かしてみる。

前提

  • 環境
    • Mac OS
    • AWS CLIを使うことができる
    • Dockerがインストールされている。

Copilot のインストール

curl -Lo /usr/local/bin/copilot https://github.com/aws/copilot-cli/releases/download/v0.1.0/copilot-darwin-v0.1.0 && \
  chmod +x /usr/local/bin/copilot && \
  copilot --help

Dockerfileを用意

mkdir copilot
cd copilot

copilotディレクトリの中に下記のDockerfileを作成します。

FROM nginx:alpine
EXPOSE 80
COPY . /usr/share/nginx/html

copilot init

copilot init

CLI上でいくつか質問されるので下記を入力・選択しました。

Application name: nginx-test
Service type: Load Balanced Web Service
Service name: nginx
Dockerfile: ./Dockerfile

確認

ブラウザ

先ほどのcopilot initの実行結果、最終行にURLが記載されています。
ここにアクセスします。

✔ Deployed nginx, you can access it at http://nginx-Publi-64I230BK4R******5852852.ap-northeast-1.elb.amazonaws.com.

image.png

AWS ECS

FARGATEで先ほどのnginxが実行されています。

image.png

image.png

参考

公式:
https://aws.amazon.com/jp/blogs/news/introducing-aws-copilot/

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

EC2インスタンス作成~ログインまで(画像つき)

今回は
EC2インスタンスをweb上から作成し、ログインまでの箇所を画像つきで進めていきます。

EC2インスタンスの作成

  • ログイン > EC2インスタンス

以下の画面になると思います。
そこで、インスタンスの作成 を行います。

インスタンス _ EC2 Management Console - Google Chrome 20.png

  • OS を選択。

検索窓で、CensOS7 を検索(今回はCentOS7を作成します。)
AWS Marketplaceに移動すると、CentOS7があるので、選択します。

インスタンスウィザードを起動 _ EC2 Management Console - Google C.png

  • 料金の表示

このOSはこれだけの料金かかりますよ と言ってくれます。
ですので、次へ。

インスタンスウィザードを起動 _ EC2 Management Console - Google C1.png

  • インスタンスタイプの選択

次はCPUとかメモリとか決めていきます。
特に負荷がかかることをしないのであれば、一旦t2.nanot2.microでやってみることをおすすめします(費用的に)

インスタンスウィザードを起動 _ EC2 Management Console - Google C2.png

  • インスタンスの詳細設定

このインスタンスはどこで立てますか?IPアドレスつける?など設定していきます。
今回は、自動割当パブリックIPのみ変更し、有効にします。
これはIPアドレスを自動で割り当てるというものです。

インスタンスウィザードを起動 _ EC2 Management Console - Google C3.png

  • ストレージの追加

次はストレージの容量、要するにHDDの容量どれくらいにする?SSD?HDD?など決めていきます。
特に指定がないので、サイズはデフォルトの8GBにしておきます。
また、終了時に削除にチェックを入れておくと、EC2インスタンスを削除した際に一緒に消えてくれるので手間が減って便利です!

インスタンスウィザードを起動 _ EC2 Management Console - Google C4.png

  • タグの追加

いっぱいサーバがあって料金の計算をしたい...など様々なことが出来ますが、最初は一旦nameだけでOK

インスタンスウィザードを起動 _ EC2 Management Console - Google C5.png

  • セキュリティグループをの設定

セキュリティグループの設定を行います。
どこからのアクセス(ここでいうソース)を許可するか。
どういうプロトコルを許可するか。
など設定できます。

今回は、webサーバを作る予定なので
ログインするためのSSHとアクセスするためのHTTPを開けておきます。
ソースは自分のIPアドレスを記載。

インスタンスウィザードを起動 _ EC2 Management Console - Google C6.png

  • 確認

今まで設定してきたものが表示されるので、OKだったら起動を行います。

インスタンスウィザードを起動 _ EC2 Management Console - Google C7.png

  • キーペアの作成

キーペアの選択を行います。
キーペアとはインスタンスへの接続時に使用される認証情報。
イメージ、SSHの鍵のようなものです。
既存のものを使わない場合は、新しいキーペアの作成からキーペア名を入れて、キーペアのダウンロードをする必要があります。
完了したら、インスタンスの作成 に進みます。

インスタンスウィザードを起動 _ EC2 Management Console - Google C8.png

  • EC2のページへ戻る

EC2インスタンスの作成が完了したら、EC2の画面へ戻ります。
すると、新しいインスタンスが表示されました!

無題.png

ログイン

  • 用意するもの

    • キーペア
    • パブリックIPアドレス(EC2のページへ戻るの画像の下の方にIPv4パブリックIPが書かれている)
  • キーペアの権限を変更します。

$ chmod 600 test-server.pem
$ ls -al .ssh | grep test-server.pem
-rw-------. 1 root vboxsf 1704  9月 15 20:28 test-server.pem
  • ログイン

CentOSでEC2インスタンスを作成した際のデフォルトユーザがcentos
Amazonlinuxではec2-userになり、OSごとにデフォルトユーザが違うので、注意してください

ssh -i .ssh/test-server.pem centos@XX.XX.XX.XX
[centos@ip-XX-XX-XX-XX ~]$ 

ログイン出来ました

注意

  • EC2インスタンスは使ったら落としましょう

EC2インスタンスは、起動時間がそのまま費用になってきます。
ですので、必要ないときはEC2インスタンスを落としておきましょう。

以下でインスタンスを落とすことが出来ます。
1. 落とすEC2インスタンスにチェックを入れる
2. 右クリック(アクション)インスタンスの状態停止

無題 - ペイント 2020-09-15 21.06.34.png

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