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、PythonRuby、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-OriginVary が増えています。

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になってしまっているので、しっかりやるならエラーハンドリングが必要です。