AWSのサービスや用語や仕組みについてややこしい部分を自分用メモとして残すだけ
自分用メモとしてややこしかったりする部分を比較などして残すものです。
EC2
インスタンスの種類
リザーブドインスタンス:
「1年間」「3年間」と期間を決めてEC2インスタンスを予約することで安く使える。
スケジュールドリザーブドインスタンス:
日次、週次、月次と3パターンのスケジューリングされた買い方ができ、月一回だけバッチ処理で利用したいなどの要望に利用できる。
スポットインスタンス:
オンデマンド価格より低価で利用できる未使用の EC2 インスタンスで、供給と需要に基づいて徐々に調整される。
テナンシー
ハードウェア専有インスタンス(Dedicated Instance):
それを利用している AWS アカウントが起動した別のインスタンスが動作することはあっても、他の AWS アカウントの起動したインスタンスが動作することはない。
Dedicated Host:
他の AWS アカウントのインスタンスはもちろん、利用者の AWS アカウントの別のインスタンスが起動されることもない。
CloudWatch
メトリクス
標準メトリクス:
CPU利用率、通信トラフィック、ディスクIOバイト数、ステータス、etc...
カスタムメトリクス:
メモリ使用率、ディスク使用率、etc...
S3
使い分け
S3 スタンダード:
デフォルトで99.999999999%の耐久性。
S3 標準低頻度アクセス(Standard-Infrequent Access):
スタンダードと同等の耐久性で安価。ただし、読み込みにも課金されるのでアクセス頻度が低い場合が適している。
S3 Glacier:
最も安価。データの取り出しに時間がかかりいくつか種類がある。
「迅速」は緊急時に少数のファイルを1〜5分で復元可能。「標準」は取り出しに3〜5時間かかる。「大容量」は取り出しに5〜12時間かかるが最も安価。データ保存後に編集不可にするためにValut Lockという設定が可能。
S3 低冗長化ストレージ(Reduced Redundancy Storage):
耐久性は99.99%で低め。非推奨。
S3 1ゾーン低頻度アクセス(One Zone-Infrequent Access):
1つのAZのみにデータを保存する分安くなる。データへのアクセス頻度が低く、高い耐久性を必要とせず、かつ必要に応じてすぐに取り出したい場合に適している。
低頻度アクセスのログデータなどはOne Zone-IAで良いがマスターデータなどはより耐久性の高いStandard-IAが良い。
S3暗号化キーの方式
SSE-S3:
Amazon S3管理キーでサーバー側の暗号化を使用する(AES-256暗号化方式)
SSE-KMS:
AWS KMS管理キーを使用したサーバーサイド暗号化の使用
SSE-C:
顧客提供のキーを使用してサーバー側の暗号化を使用する
補足
S3バケットのデフォルト暗号化を有効化することで、ログの暗号化も実施され、後から変更も可能。
ELB
オプション
クロスゾーン負荷分散:
各ロードバランサーノードは、有効なすべてのアベイラビリティーゾーンの登録されたインスタンスにリクエストを均等に分散します。クロスゾーン負荷分散が無効の場合は、各ロードバランサーノードは、そのアベイラビリティーゾーンの登録されたインスタンスにのみリクエストを均等に分散します。
Connection Draining:
ロードバランサーがインスタンスの登録解除を報告するまで接続を保持する最大時間を指定できます。最大タイムアウト値は 1 ~ 3,600 秒の間で設定できます (デフォルトは 300 秒です)。
スティッキーセッション:
ロードバランサーがユーザーのセッションを特定のアプリケーションインスタンスにバインドするように設定できます。これにより、ユーザーのセッション中のすべてのリクエストが同じインスタンスに送信されます。
ストレージ
EBS vs EFS
Amazon EBS:
単一インスタンスから接続可。単一AZで冗長化。自動拡張不可。より安価。
Amazon EFS:
複数インスタンスから接続可。複数AZで冗長化。自動拡張可。
EBSの補足
Amazon Data Lifecycle Manager (Amazon DLM)を使用するとEBS ボリュームに保存されたデータをバックアップする自動化された手順です。Amazon DLM を使用してライフサイクルポリシーを作成し、スナップショット管理を自動化します。
データベース
Aurora:
(行指向データベース、)3AZにレプリケーション。
DynamoDB:
キーバリューストア、3AZにレプリケーション。
Redshift:
列指向データベース、単一AZのみ。
DynamoDBの補足
Amazon DynamoDB Accelerator (DAX) は、フルマネージド型高可用性インメモリキャッシュで、DynamoDB 用に特化しています。
DynamoDB ストリームは、DynamoDB テーブルの項目レベルの変更に対して時間順序のシーケンスをキャプチャし、その情報を最高 24 時間まで保存します。
Amazon Kinesis
Amazon Kinesisは主にストリーミングデータの収集・処理・リアルタイム分析に利用されます。
種類
Amazon Kinesis Data Streams:
ストリーミングデータをほぼリアルタイムで保存することができ、登録されたデータはEMRやLambdaなどで処理することができます。シャード単位で分割して並列処理を行うことができます。
Amazon Kinesis Data Firehose:
ストリームデータをS3やRedshiftに簡単に配信・保存できるサービスです。変換等も合わせて行ってくれる。
Amazon Kinesis Data Analytics:
ストリーミングデータに対してSQLクエリを実行しリアルタイム分析を行うサービスです。
AWS Storage Gateway
保管型ボリューム vs キャッシュ型ボリューム
保管型ボリューム:
新規にアップロードされたデータをローカルのディスクに保存した上で、非同期的にAWSへとバックアップを行います。ローカルサーバーをプライマリーストレージとして利用することが主な要件となります。
キャッシュ型ボリューム:
頻繁にアクセスされるデータはキャッシュとしてローカルのストレージゲートウェイに保持しながら、Amazon S3 をプライマリデータストレージとして使用できます。
AWS SQS
キューの種類
スタンダード (標準) キュー:
デフォルトのキュータイプ。できる限りメッセージの順序を保持し少なくとも 1 回は確実に配信されますが、複数のメッセージのコピーが順不同で配信される場合があり、全般的にメッセージが送信順に配信されるベストエフォート型の順序を提供します。
FIFO (先入れ先出し) キュー:
スタンダードキューを改良したもので、メッセージが送信または受信される順序は厳密に保持されます。また、メッセージは 1 回のみ配信され、コンシューマーが処理して削除するまで使用可能な状態で残り続けます。
補足
SQS 可視性タイムアウト:
この時間内に他のコンシューマーが同じメッセージを受信したり処理したりすることはできません。メッセージのデフォルトの可視性タイムアウトは 30 秒です。最小値は 0 秒、最大スケールは 12 時間です。
なんか似てるシリーズ
Opsworks vs Elastic Beanstalk
Opsworks:
Chef や Puppet のマネージド型インスタンスを利用できるようになる構成管理サービス。サーバーのパッチ適用、アップデート、バックアップが自動的に実行され、Chef サーバーが管理される。
Elastic Beanstalk:
Java、.NET、PHP、Node.js、Python、Ruby、Go、Docker で開発されたウェブアプリケーションとウェブサービスをデプロイし、自動的にデプロイメントの詳細を処理する。
WEBアプリケーションがホストされているインフラ管理にはElastic Beanstalk、インフラ環境そのものを自動で展開したり管理するためにはOpsWorksやCloudFormationが適しています。
色々
認証情報レポート:
IAMユーザーが最後にAWSサービスにアクセスした⽇付と時刻を表⽰する機能を提供してくれます。
AWS CloudTrail:
AWS アカウントのガバナンス、コンプライアンス、および運用とリスクの監査を行えるように支援する AWS のサービスです。コンソールでの操作と AWS API コールを記録します。
AWS Config:
AWS上のリソース状況のスナップショットを記録できる構成管理サービスです。
AWS Trusted Advisor:
AWS ベストプラクティスに従ってリソースをプロビジョニングするのに役立つ、リアルタイムガイダンスを提供します。
AWS Systems Manager:
AWS で利用しているインフラストラクチャを可視化し、制御するためのモニタリングサービスです。
Amazon Inspector:
自動的にアプリケーションを評価し、露出、脆弱性、ベストプラクティスからの逸脱がないかどうかを確認できます。
GuardDuty:
AWS 環境内のネットワークアクティビティとアカウントの動作を継続的にモニタリングすることによって、脅威を識別します。
独自ドメインを持たずともEC2とALBとACMを使ってオレオレ証明書でHTTPSを実現してみる
はじめに
AWSを使えば、EC2で簡単にWebアプリケーションを公開でき、ALBとACMを使えば証明書の発行から管理まで任せた上でHTTPS化することも簡単です。
これらに関するやり方を調べると、基本的に独自ドメインを使った方法が紹介されています。
お金やタイミングの問題から独自ドメインが用意できないものの、オレオレ証明書(自己署名証明書)で問題ないのでHTTPS化したいという場面があったので、独自ドメインの代わりにALBから払い出されるDNS名を使ってやってみました。
一部、以下の記事を参考にしました。
ozuma.hatenablog.jpakiyoko.hatenablog.jp
前提
EC2とALBは既に用意されているとします。
以降のEC2は Ubuntu Server 18.04 LTS (HVM)
で、動作確認済みです。
また、ALBのDNS名は xxxxxxxxxx-9999999999.ap-northeast-1.elb.amazonaws.com
であるとします。
オレオレ証明書の用意
EC2上で以下を実行します。
まず、秘密鍵を生成します。
$ openssl genrsa 2048 > server.key (略)
続いて、秘密鍵から証明書署名要求ファイルを生成します。
入力は全てエンターで進むと紹介されていることが多いですが、今回は「Common Name」にALBのDNS名を登録しておきます。
これをしないとACMに証明書をインポートしても、ALBから選択できません。
$ openssl req -new -key server.key > server.csr (略) Country Name (2 letter code) [AU]: State or Province Name (full name) [Some-State]: Locality Name (eg, city) []: Organization Name (eg, company) [Internet Widgits Pty Ltd]: Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:xxxxxxxxxx-9999999999.ap-northeast-1.elb.amazonaws.com Email Address []: Please enter the following 'extra' attributes to be sent with your certificate request A challenge password []: An optional company name []:
最後に、秘密鍵で証明書署名要求ファイルに署名してサーバ証明書を生成します。
$ openssl x509 -days 3650 -req -signkey server.key < server.csr > server.crt Signature ok subject=C = AU, ST = Some-State, O = Internet Widgits Pty Ltd, CN = xxxxxxxxxx-9999999999.ap-northeast-1.elb.amazonaws.com Getting Private key
ACMに証明書のインポート
AWSコンソール上で、ACMを開き、「証明書のインポート」を選択します。
証明書本文、証明書のプライベートキー、証明書チェーンを入力するように促されるので、先程生成したファイル文字列をコピペします。
server.crtの中身を「証明書本文」にコピペします。
$ cat server.crt -----BEGIN CERTIFICATE----- (略) -----END CERTIFICATE-----
server.keyの中身を「証明書のプライベートキー」にコピペします。
$ cat server.key -----BEGIN RSA PRIVATE KEY----- (略) -----END RSA PRIVATE KEY-----
「証明書チェーン」は何もせずそのままで大丈夫です。
ALBのリスナーを設定
AWSコンソール上で、ALBを開き、リスナーを追加します。
プロトコルの設定でHTTPSを選択すると、「デフォルトの SSL 証明書」という項目が増えるので、「ACMから」を選び先程インポートした証明書を選択します。
証明書署名要求ファイルを生成する際にALBのDNS名を入力しないと、インポートした証明書が選択肢に出てきません。
確認
これで、 https://xxxxxxxxxx-9999999999.ap-northeast-1.elb.amazonaws.com にアクセスすると、初回は「この接続ではプライバシーが保護されません」と出ますが、良しなに許可してやると見れるようになります。
Rocket (Rust) を nginx と systemd を使ってデプロイしてみる
今回も前回の続きでRustの話が続きます。
ohshige.hatenablog.com
クロスドメインも気にせずAPIが使えるようになったので、そろそろ cargo run
での実行ではなく、いい感じでデプロイしたいところです。
そこで、今回はリバースプロキシとしてnginxを、デーモン化としてsystemdを使ってみます。
こちらを参考にしました。
medium.com
nginx
nginxはインストール済みだとして、適当な設定ファイルに以下を書きます。(例えば /etc/nginx/conf.d/rocket.conf
)
必要であれば適宜 server_name
等を書きます。
server { listen 80 default_server; location / { proxy_pass http://0.0.0.0:8000; proxy_buffering off; proxy_set_header X-Real-IP $remote_addr; } }
sudo nginx -t
でシンタックスをチェックし、問題なければ sudo nginx -s start
等で起動しておきます。
systemd
続いてsystemdの設定です。
以下の通り、設定をユニットファイルに記述します。(例えば /etc/systemd/system/rocket.service
)
今回はアプリケーションが /home/ohshige/rocket/
配下にある場合を想定していて、既に cargo build
済みであるとします。
[Unit] Description=RocketAPI [Service] User=www-data Group=www-data WorkingDirectory=/home/ohshige/rocket/ Environment="ROCKET_ENV=prod" Environment="ROCKET_ADDRESS=0.0.0.0" Environment="ROCKET_PORT=8000" Environment="ROCKET_LOG=critical" ExecStart=/home/ohshige/rocket/target/debug/rocket [Install] WantedBy=multi-user.target
本番ではないので ExecStart
はデバッグのバイナリを指しています。( ROCKET_ENV=prod
ではありますが)
しっかりやるなら cargo build --release
でビルドしたリリース版を指すようにすると良いと思います。
そして、 sudo systemctl start rocket.service
でサービスを起動します。
また、 サーバ起動時にサービスも起動するように sudo systemctl enable rocket.service
も実行しておきます。
これでデーモン化できました。
.
実際にアクセスしてみると問題無く動作するはずです。
アプリケーションに変更がある場合には、再度ビルドした上で、 sudo systemctl restart rocket.service
で再起動する必要があります。
Rocket (Rust) で CORS の設定をする
今回も前回の軽い続きです。
ohshige.hatenablog.com
APIは作れるようになったので、今度はこれを呼び出したい思いです。
同じドメインなら問題無いですが、異なるドメインから呼び出したい場合に設定しないといけないがCORSです。
前回の状態で適当にJSからXHRを試して http://localhost:8000/users
を呼び出そうとすると、以下のようにエラーとなります。
Access to XMLHttpRequest at 'http://localhost:8000/users' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
実際にレスポンスヘッダーを見ても、当たり前ですがCORSの設定は何もありません。
HTTP/1.1 200 OK Content-Type: application/json Server: Rocket Content-Length: 73 Date: Mon, 24 Feb 2020 19:00:00 GMT
というわけで、 rocket_cors
を使います。
docs.rs
Cargo.toml
rocket_cors = "*"
を追記します。
[package] name = "demo" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rusqlite = "*" rocket = "*" rocket_contrib = { version = "*", features = ["json"] } rocket_cors = "*" serde = "*" serde_derive = "*"
main.rs
CorsOptions::default()
をアタッチすることでCORSを設定できます。
デフォルトだとオールオッケーな感じなので、適宜必要な範囲に絞る必要があります。
#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; #[macro_use] extern crate serde_derive; extern crate rusqlite; extern crate rocket_contrib; extern crate rocket_cors; use rusqlite::{Connection, NO_PARAMS}; use rocket_contrib::json::Json; use rocket_cors::CorsOptions; (略) fn main() { rocket::ignite() .mount("/", routes![get_users, get_user]) .attach(CorsOptions::default().to_cors().expect("error")) .launch(); }
上記を実行すると以下のようになります。
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.10s Running `target/debug/demo` 🔧 Configured for development. (略) 🛰 Mounting /: => GET /users (get_users) => GET /users/<user_id> (get_user) 🛰 Mounting /cors: => GET /cors/<status> 📡 Fairings: => 1 request: CORS => 1 response: CORS 🚀 Rocket has launched from http://localhost:8000
それらしい起動ができている気がします。
この状態で新たにJSからXHRを試してみると、エラーは起きず無事にできていることがわかります。
レスポンスヘッダーを見てみると、 null
ではありますが Access-Control-Allow-Origin
と Vary
が増えています。
HTTP/1.1 200 OK Content-Type: application/json Server: Rocket Access-Control-Allow-Origin: null Vary: Origin Content-Length: 73 Date: Mon, 24 Feb 2020 19:00:00 GMT
RustでRocketを使ってAPIを作ってみる
なんとなくの前回の続きで、だいたい自分用メモです。
ohshige.hatenablog.com
RustでAPIを実現するためのフレームワークはいくつかあるようですが、今回はRocketを使います。
基本的にはチュートリアルの通りにやっています。
rocket.rs
やりたいことは前回作ったDBからデータを取得するメソッドをAPI化して、JSONを返却するようにするというものです。
以降は、rustc 1.42.0-nightly
で動作確認済みです。
Cargo.toml
[package] name = "demo" version = "0.1.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] rusqlite = "*" rocket = "*" rocket_contrib = { version = "*", features = ["json"] } serde = "*" serde_derive = "*"
main.rs
#![feature(proc_macro_hygiene, decl_macro)] #[macro_use] extern crate rocket; #[macro_use] extern crate serde_derive; extern crate rusqlite; extern crate rocket_contrib; use rusqlite::{Connection, NO_PARAMS}; use rocket_contrib::json::Json; #[derive(Debug, Serialize)] struct User { user_id: usize, name: String, } #[get("/users")] fn get_users() -> Json<Vec<User>> { let conn = Connection::open("my.db").unwrap(); let mut stmt = conn.prepare(" SELECT user_id, name FROM users ORDER BY user_id ASC ", ).unwrap(); let users = stmt.query_map(NO_PARAMS, |row| { Ok(User { user_id: row.get::<_, i64>(0).unwrap() as usize, name: row.get::<_, String>(1).unwrap(), }) }).unwrap(); let mut result: Vec<User> = vec![]; for user in users { result.push(user.unwrap()); } Json(result) } #[get("/users/<user_id>")] fn get_user(user_id: usize) -> Option<Json<User>> { let conn = Connection::open("my.db").unwrap(); let mut stmt = conn.prepare(" SELECT user_id, name FROM users WHERE user_id = ? ", ).unwrap(); let id = user_id as i64; let mut users = stmt.query_map(&[&id], |row| { Ok(User { user_id: row.get::<_, i64>(0).unwrap() as usize, name: row.get::<_, String>(1).unwrap(), }) }).unwrap(); let user = users.next(); match user { Some(user) => Some(Json(user.unwrap())), None => None, } } fn main() { rocket::ignite().mount("/", routes![get_users, get_user]).launch(); }
メインの処理はほぼ変わっておらず、User構造体をシリアライズ可能にしたのとレスポンスをJSONにしたくらいです。
上記を実行すると以下のようになります。
$ cargo run Finished dev [unoptimized + debuginfo] target(s) in 0.09s Running `target/debug/demo` 🔧 Configured for development. => address: localhost => port: 8000 => log: normal => workers: 32 => secret key: generated => limits: forms = 32KiB => keep-alive: 5s => tls: disabled 🛰 Mounting /: => GET /users (get_users) => GET /users/<user_id> (get_user) 🚀 Rocket has launched from http://localhost:8000
curlで確認してみると、問題なく動いています。
$ curl http://localhost:8000/users [{"user_id":1,"name":"田中太郎"},{"user_id":2,"name":"鈴木次郎"}] $ curl http://localhost:8000/users/1 {"user_id":1,"name":"田中太郎"} $ curl http://localhost:8000/users/999 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>404 Not Found</title> </head> <body align="center"> <div align="center"> <h1>404: Not Found</h1> <p>The requested resource could not be found.</p> <hr /> <small>Rocket</small> </div> </body> </html>
現状だと、存在しないユーザIDを指定した場合でも404は返りますが、デフォルトのままHTMLになってしまっているので、しっかりやるならエラーハンドリングが必要です。