AWSの各サービスを舐める (3) - Amazon S3
AWSのサービス舐めるシリーズその3。今回はS3です。
Kubernetesの検証の時にお世話になったので、今回はちょっとしたメモ程度で。
以前どんなことに使ったかは過去記事をご参照ください。
特徴と制約
ほぼ無限にオブジェクトを格納できる
- ただし、バケット数の上限は100個/アカウント.
- 1オペレーション(PUT)で格納できるファイルサイズ上限は5GB.
- 複数回に分けて格納した場合は格納できるファイルサイズ(1ファイル)の上限は5TB.
ストレージのタイプ
高頻度(ミリ秒単位でパフォーマンスが求められる)のアクセス
- STANDARD ... デフォルトの設定
- RRS ... 非推奨
低頻度のアクセス
- STANDARD_IA ... AZにコピーして保存
- ONEZONE_IA ...
ほとんどアクセスしないアーカイブ
- Amazon Glacierを使う. S3のライフサイクル管理で一定期間経ったらS3 --> Glacierに移動させることができる.
イベント通知
ファイルの格納はもちろんですが、静的ファイルホスティングやイベント通知はかなり使い勝手が良さそうですね。 イベント通知のとこは別のアプリ(オンライン, 定刻起動のジョブとか)でS3にファイル置く -> Lambdaキックとかパイプライン組めそうなので、次回にやってみて記事に載せようと思います
AWSの各サービスを舐める (2) - DynamoDB
AWS舐めるシリーズその2。 今回はAmazon DynamoDBです。前回は文字ばかりだったので、チュートリアルを元にプログラム(Node.js)からも操作してみます。
ちなみに、ローカルマシンのTerminalから弄るので、アクセスキーとシークレットキーを使ってます。Elastic BeanstalkやECS, Lambadaから弄る時はIAMでロールを作ってから操作しましょう。
DynamoDB
テーブル作成
テーブルを作成してみます。
aws configure
で設定していない時は認証失敗して以下のように弾かれるんですね。うまくいったときはdata
に結果が全て入っていると。
正直プログラムからテーブル作るよりもCloudFormationでテーブル作成したほうが冪等性の担保とソース管理がしやすいので個人的にはこっちの方が好みです。
// 認証失敗 { "message": "The security token included in the request is invalid.", "code": "UnrecognizedClientException", "time": "2018-08-19T09:03:53.473Z", "requestId": "SVANMBETVBO11DHOKS8A45GRTVVV4KQNSO5AEMVJF66Q9ASUAAJG", "statusCode": 400, "retryable": false, "retryDelay": 41.39762357028852 } // 認証成功 Created table. Table description JSON: { "TableDescription": { "AttributeDefinitions": [ { "AttributeName": "title", "AttributeType": "S" }, { "AttributeName": "year", "AttributeType": "N" } ], "TableName": "Movies", "KeySchema": [ { "AttributeName": "year", "KeyType": "HASH" }, { "AttributeName": "title", "KeyType": "RANGE" } ], "TableStatus": "CREATING", "CreationDateTime": "2018-08-19T09:04:50.440Z", "ProvisionedThroughput": { "NumberOfDecreasesToday": 0, "ReadCapacityUnits": 10, "WriteCapacityUnits": 10 }, "TableSizeBytes": 0, "ItemCount": 0, "TableArn": "arn:aws:dynamodb:ap-northeast-1:284677944563:table/Movies", "TableId": "b5086ea3-11b7-4f0c-b0aa-933e2be38079" } }
テーブルに初期データをロード
次にサンプルのデータをロードします。WCU=10なので、1件ずつデータをロードするときは理論値としては10 * 1KB 書き込み/sec できるはず。
全部で4612件(3632KB)のJSONを1件ずつArray.forEach
で突っ込んでいると、結果的には途中まで進んだところで ProvisionedThroughputExceededException
で弾かれるようになりました。200件ほどエラーで弾かれていますね。
$ node MovieLoadData.js ... PutItem succeeded: Of Human Bondage Unable to add movie 200 Cartas . Error JSON: { "message": "The level of configured provisioned throughput for the table was exceeded. Consider increasing your provisioning level with the UpdateTable API.", "code": "ProvisionedThroughputExceededException", "time": "2018-08-19T09:21:50.335Z", "requestId": "OG9EONR7I6GNIRCSL57FKR8CTRVV4KQNSO5AEMVJF66Q9ASUAAJG", "statusCode": 400, "retryable": true } $ aws dynamodb describe-table --table Movies # ロードされた項目数が表示される. ... "TableSizeBytes": 1991251, "ItemCount": 4365, ...
CRUD操作
SDKにはCRUD用のAPIが用意されているので、ありがたく使わせてもらいます。 実際のプロジェクトで使うなら生のSDKをそのまま書くんじゃなくて、プロジェクト共通のUtilityとしてラップした関数を作って提供してあげたほうが各開発者には優しいかも。
以下チュートリアルのコードほぼそのままですが、 UpdateExpression
, ExpressionAttributeValues
のところが自然言語のようなJPAっぽくて気持ちわるいですね。
const AWS = require("aws-sdk"); // ここのconfigは環境変数で外出ししたほうがベター AWS.config.update({ region: "ap-northeast-1", endpoint: "https://dynamodb.ap-northeast-1.amazonaws.com" }); const docClient = new AWS.DynamoDB.DocumentClient() const table = "Movies"; const year = 2015; const title = "The Big New Movie"; const params = { TableName: table, Key: { "year": year, "title": title }, UpdateExpression: "set info.rating = :r, info.plot=:p, info.actors=:a", ExpressionAttributeValues: { ":r": 5.5, ":p": "Everything happens all at once.", ":a": ["Larry", "Moe", "Curly"] }, ReturnValues: "UPDATED_NEW" }; console.log("Updating the item..."); docClient.update(params, (err, data) => { if (err) { console.error("Unable to update item. Error JSON:", JSON.stringify(err, null, 2)); } else { console.log("UpdateItem succeeded:", JSON.stringify(data, null, 2)); } });
検索操作
Query
Scan
- テーブル全体の項目を返す。
- デフォルトだとテーブル全体で1MB読みこむ。1MB以上あっても1MBしか読みこまない(全部返すんじゃないんかい)
- 結果セットのフィルタリングができますが、全部読み込んでからフィルタリングで不要なデータを捨てる挙動みたいですね。
- ベストプラクティスとしては、
Limit
宣言で読みこむ件数にキャップをかけること。
使ってみた感想としては、AWS上で使うNoSQLとしては面白い選択肢ですね。
テーブルサイズに上限がないとはいえキャパシティユニットのキャパプラをしておかないとすぐスロットルするから、AutoScalingを有効化するのが最初は無難かもしれません。 あとはユースケースを見極めてテーブル設計すればRDBよりも素早いレスポンスで使えそう。
AWSの各サービスを舐める (1) - IAM, EC2, RDS
今年の目標としてAWS 認定デベロッパー - アソシエイト(2018 June)を取ることを目標にしたので、 A CLOUD GURUのコースに沿って各サービスを舐めて回ることにしました。
気になるやつはGetting Startedをなぞって「へー( ˙꒳˙ )」となるばかりだったので、これを機に使いこなしたいし。 最初の方から割と基本的なことが分かってなかったなと思うことが多いので、しばらくはブログ記事が備忘録になるかと思います笑
今回はタイトルにある、IAM, EC2, RDSのまとめです。
IAM
用語
ユーザー
- AWSを利用する人
グループ
- ユーザーの論理的なまとまり
- グループに所属するユーザー全員に対してアクセス権限(できること/できないことを規定するもの)を設定・適用できる。
ポリシー
ロール
ベストプラクティス
個々の利用者にユーザー/アクセスキーを作る
- ルートキーは漏れるとやばいので、まずは消せ。
- 1つのアクセスキーは使いまわさない。使い回していると万が一キーを漏れた時に影響範囲が全員になってしんどい。
- 1User, 1AccessKey / person
権限はロールを使う
- 同時に言うべきは、アクセスキー/シークレットキーを直接使わないこと。
- ロールに適切な権限をつけることで、ユーザーだけでなくリソースそのものが他のリソースにアクセスするのも安全。
最低限の権限から始める
- いきなりFull Accessが必要か?
- 必要に応じて権限をゆるくするのが安全。
EC2
永続ストレージ(EBS)
RDS
- RDBのマネージドサービス
対象は6種類。Aurora以外はFree Teerもあるみたい。
可用性を高めるには、MultiAZ or Backup or SnapShot
パフォーマンスを高めるには Read Replica
RDSで作られるDBのインスタンスはリージョン固有になる。
- セキュリティグループもリージョンにひもづく
- EC2等の他のリソースからアクセスする場合はセキュリティグループのInBound/OutBoundで穴を開けておくこと。
次はS3やLambdaあたりを舐めまわそうかな。
AWS Lambda with Node.js を叩いてKubernetesにアプリをデプロイしてみる
Kubernetesへのアプリのデプロイ方法を検討したことがあったのでちょっとまとめてみる。
AWS CodePipelineからKubernetesにビルドしたDockerイメージをデプロイするときは、Lambdaでやるパターンがあるみたいですね。
また、ECSだとCodeDeployでデプロイ先にECSクラスターを選べるので楽チン。ただ、AWSのマネージドサービスではKubernetesを直接選ぶことができなさそうだったので、今回もLambdaで実行する方法を検討してみました。
※ 注釈 -----------------------------------------------
上記のデプロイ方法を検討したのが2018年5月ごろなので、EKSのことは考えていません。 2018年6月初めにAmazon EKSが出てきて、公式ではリソース作成にCloudFormationを多用する方法を用いているので、CodeBuildでCloudFormationのテンプレートを作る --> CodeDeployでCloudFormation経由でデプロイ実行の流れもできそうな気がします。
----------------------------------------------- 注釈 ここまで
いろんな記事を舐めてみた限りクライアントライブラリはPythonのを使うって投稿多いけど、Node.jsでやった例が少なそう。(Node.jsのユースケースとして適しているかは別の話)
ただ、公式のNode.jsのKubernetesクライアントライブラリはドキュメントが貧弱なのが難点です。。。
クライアントライブラリを使えないとすれば、別のAPIを使う他ない。Kubernetesにはオブジェクト操作用にREST APIが用意されているので、これを利用するやり方にしてます。
どう実装したかソースを見たい方はGitHubのリンクからどうぞ。
Amazon EKSがGAになったので触った所感
AWSでKubernetesのマネージドサービス Amazon EKS がGAになりました。 今まではAWSでk8sをいじるときはkops経由でクラスターを作って、権限設定してと手間がかかっていましたが、EKSはどれくらい楽になるのかなという観点で触ってみました。
全体的に
AWSのWebコンソール側はめっちゃシンプルで発展途上とさえ感じられるレベル。
今の所、使えるのは US West (Oregon) (us-west-2) or US East (N. Virginia) (us-east-1) のみ。
- 東京リージョン早く来てほしい!
リソースの用意は基本はCLI(aws-cli/kubectl) & CloudFormationでやる。
- クラスターのみWebから作成できる。
クラスターの作成
ネットワーク関連は自分で用意しないといけない。
Get Startedの例だとVPC/SecurityGroupをCloudFormation経由でやらせていました。クラスターの作成も含めてCloudFormationでやったほうが早そう...
ノードの作成
ワーカーノードも自分で作成する。
- クラスターとワーカーの紐付けを後から実施する形みたい。Configmapを編集して適用する。
Auto Scaling グループを設定してあげるので、最小・最大・希望ノード数の範囲で自動で増減できます。
- KubernetesのPodのスケールは HolizontalPodAutoscalerで出来るけど、ノードのスケールまで出来るのは可用性を高めるのに良さげ。
Kubernetesにアプリケーションを乗せる
通常のKubernetesの使い方同様に、ReplicationController, Service ect...を作成すればOK。
LoadBalancerはアクセスできるようになるまで10minくらい待つので気長にお茶でも飲んで待つ。
ハマりポイント
自分がハマったポイント集。
それとMFAでカッチリユーザー権限を設定している人は、事前に
aws sts get-session-token --serial-number arn-of-the-mfa-device --token-code code-from-token
でtokenを取得すること- An error occurred (InvalidClientTokenId) when calling the GetSessionToken operation: The security token included in the request is invalid.
もしInvalidClientTokenIdで弾かれる場合はkubeconfigから-r 行および <role-arn> 行を消すと通る。Roleをここで決めておきたいのに。。。
- could not get token: InvalidClientTokenId: The security token included in the request is invalid.
雑感
認証・認可を上手にIAMとKubernetesのRBACを統合しようとしているなと感じます。AWSでKubernetesを立てるメリットの1つではないでしょうか。
ただ、ドキュメントを見る限りEKSでのIAMの立ち位置は"認証"であって、認可はまだRBAC側で設定するみたいです。
Kubernetesのリソースアクセスの制御は、ConfigMapにIAMユーザー/ロール/アカウントを追加する必要がありますが、新しいユーザーやロールが追加されるたびに毎度毎度ConfigMapを手で書き換えるのは中々メンテナンスが大変そう。。。ここをGUIや自動化出来るとさらに使いやすいですね。
他にも最初からリソース操作はかなり CloudFormationやkubectl に頼っています。
- kopsと比較するとVPC/セキュリティグループ/Auto Scaling/Nodeの設定を細かく出来る一方で手作業となる部分が多いので、"とりあえず作って壊す" には少しハードルが高い印象を受けます。
認可の仕組みやVPC、CloudFormationあたりをもう少し探っていきたいと思います。ここら辺が明確になれば、ECSとの差別化要素も見えてきそうです。
npm v6が自動的に脆弱性を見つけるようになった
Node 10.0と合わせて自分のMacのnpmもv6にバージョンアップさせました。 色々いじってた時に脆弱性を教えてくれるようになったので、メモとして残します。
例えばnpm install
でnodemonを入れると、以下のように脆弱性があるよと警告を親切に出してくれます。
画像のインストラクション通り、npm audit
で詳細が見られるようです。
素直にコマンドを叩くとこんな図のようにどのパッケージの何がマズイのか表示されるようになります。
ガイドはNPM公式から出てます。
ガイドを見る限り、npm install
を叩くと同時に npm audit
も叩かれるみたいです。
もしパッケージインストール時に npm audit
を叩かないようにしたい場合は、--no-audit
フラグをつければOK。
今回のnodemonの例でやると、以下のようになる。
npm install nodemon --no-audit
また、npmのインストール全体を通して npm audit
させないようにするためには以下のように設定してあげればいいみたいです。
npm set audit false
Nodeだけじゃなくてnpmも進化してるって発見ですね。 セキュリティ周りをこうやって補強するのを助けてくれるのは嬉しい限りです。最近はGitHubもpackage.jsonから脆弱性アラートを出してくれるようになっているみたいなので、ちゃんとセキュリティ周りは固めておきたいね。
Goで簡易Webアプリ作成&Dockerコンテナにまとめる
ご無沙汰してます。約2ヶ月ぶりの投稿になります。
ここ2ヶ月くらいは社内のイベント向けのアプリを作ったり、Kubernetes勉強会に参加して初めてAWS触ったり、コンテナ周りで新人向けに講師としてスピーカーやったりと技術(特にDockerコンテナ)漬けの日々を過ごしておりました。(Kubernetes@AWSは今度記事にする予定です)
お陰様で今日の健康診断で2ヶ月で10kg体重落ちてました汗
技術だけじゃなく美味しいご飯どころも探さなきゃね。。。
さて、4月末から連休で時間ができたので新しいものに挑戦しようとGoに触り始めました。 折角なら馴染みのあるWebアプリを作成して、Dockerコンテナにまとめるくらいはやろうと全速力でやったので記事投下です。
ソースコード見たいよって方は以下のリンクからどうぞ。
Go出発点
今まで手を出そうと思いつつ何も手をつけていなかったので、Goについては知識0からスタート。 比較的最近出版された本ならばバージョンの違いでつまづくことは少ないはずなので、とりあえず『スターティングGo言語』買って3日ほどで手を動かしつつ読了。あとは作りたいものを完成させます。
さっと書店で手に取った本にしてはアタリだった気がします。 全7章建てで、基本で小分けに5章割いて、残りはツールやパッケージの話で2章。1つ1つのGoの仕組みを分解してコード例も沢山載せてくれているので、写経しているだけでもかなり書けるようになります。
Go製のWebアプリを作る
サーバー側は最低限文字列とJSON返すだけ返せればよかったので、簡易なものとしてピッタリな Echo を選択。 公式のドキュメントは分かりやすいし、サイトも見やすいので取っ付きやすかったです。
"minimalist Go web framework" と謳うくらいだから、Node.jsでいうExpress.jsと同じような位置付けなのかな?
以下、公式の例を使いつつルーティングとミドルウェアを設定した main.go
例です。
ルーティングのミドルウェアはデフォルトの設定を使うとJSON形式の見辛いログが出力されるので、Apacheの Common Log Format に似せて出力されるように設定してます。ログ出力は出力内容は柔軟に変えられるみたいですね。
package main import ( "net/http" "github.com/labstack/echo" "github.com/labstack/echo/middleware" "github.com/sasaken555/ponz_goecho_server/routes" ) func main() { /* Echoインスタンスの作成 */ e := echo.New() /* Root Level Middleware */ // ログ出力は Apache Common Log Format っぽく設定すると読みやすい e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${host} [${time_rfc3339_nano}] \"${method} ${uri}\" ${status} ${bytes_in} ${bytes_out}\n", })) e.Use(middleware.Recover()) /* ルーティングの設定 */ // 第2引数の値は別パッケージに外出しすると分かりやすい e.GET("/users/:id", routes.GetUser) e.GET("/users/json", routes.GetJSONUser) e.Logger.Fatal(e.Start(":1323")) // ポート1323で起動。 }
また、ルーティングの設定・関数は main に全部突っ込むと後で見づらくなるので、外出ししてあげます。
今回は routes/user.go
としてルーティングしたときに返す関数をまとめてみました。
package routes import ( "net/http" "strconv" "github.com/labstack/echo" "github.com/sasaken555/ponz_goecho_server/util" ) // GetUser ... Pathパラメータからユーザー(=ID)を取り出して返す func GetUser(c echo.Context) error { // User ID from Path Parameter `users/:id` id := c.Param("id") return c.String(http.StatusOK, id) } // Customer ... 顧客情報の構造体 type Customer struct { ID int64 `json:"id" xml:"id"` Name string `json:"name" xml:"name"` OrderNum int `json:"ordernum" xml:"ordernum"` OrderProd string `json:"orderprod" xml:"orderprod"` } // GetJSONUser ... 顧客情報のJSONを返す func GetJSONUser(c echo.Context) error { userID, err := strconv.ParseInt(c.QueryParam("userId"), 10, 0) // strconvで文字列から整数に型変換 userName := c.QueryParam("userName") orderNum := util.GetRand(100) // 別のパッケージからインポートした指定桁数で乱数を返す関数を使う // userIDが整数でない(=型変換できない)ならば500エラーを返す if err != nil { return echo.NewHTTPError(http.StatusInternalServerError, "You Should Provide userId as Integer!") } // 構造体のポインタを作成 u := &Customer{ ID: userID, Name: userName, OrderNum: orderNum, OrderProd: "Blend Coffee", } return c.JSON(http.StatusOK, u) }
Dockerコンテナにまとめる
関心があったのと同時に割と引っかかったのが、ここ。特にビルドイメージ大きすぎなのが難点でした。
GoはOS依存させないために、OSが標準で備えているようなライブラリを使わず、ランタイムとアプリで使うパッケージを全て実行ファイルの中に取り込みます。この性質があるため、少しコード量の多いアプリでも成果物が数百MBになるのもザラ。
実行ファイルを組み込んだDockerコンテナのイメージがデカイとなると、
docker pull
の度にディスクと時間が取られるのでポータビリティの点から使いづらい。
上記の問題があったため、ベースイメージを小さいものにするのに加えて、Dockerのマルチステージビルドで対応することしました。
通常だとソースコードを全てDockerコンテナに含めますが、Goは良くも悪くもソースコードからシングルバイナリ(実行ファイル)を作成するので、実行ファイルだけ入れればOK。下のコード例の(1)が実行ファイル作成のビルド、(2)が(1)で作った実行ファイルをコピーして作った最終系のコンテナイメージになります。
ベースイメージも alpine イメージを使うことでさらに軽量化してます。
# Full SDK version ... (1) FROM golang:1.10-alpine AS build RUN apk update && apk upgrade \ && apk add curl git RUN curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh WORKDIR /go/src/github.com/sasaken555/ponz_goecho_server COPY . . RUN dep ensure RUN go build -o ponz_goecho_server # Final Output ... (2) FROM golang:1.10-alpine COPY --from=build /go/src/github.com/sasaken555/ponz_goecho_server/ponz_goecho_server /bin/ponz_goecho_server CMD /bin/ponz_goecho_server
結果として、通常のベースイメージ+アプリソースを含めてビルドしたイメージ(tag: heavy)とalpineベースイメージ+マルチステージビルドのイメージでサイズは半分以下に抑えられています!やったね!
$ docker image ls ponz_goecho_server REPOSITORY TAG IMAGE ID CREATED SIZE ponz_goecho_server light 24b61e6c4f83 7 seconds ago 386MB ponz_goecho_server heavy 20bac15a0acf About a minute ago 908MB
新しい技術(今回は言語でしたが)として今回はGoを選択しましたが、謳い文句の通りシンプルかつ効率的に開発&ビルドできたのはすごい...!!
バイナリ単位でアプリはまとめられるので、マイクロサービスと相性が良さそうな印象です。しかもブロックチェーンのHyperledger fabricもGoで開発できるようなので、次はGoを使ってブロックチェーンに挑もうかなと夢が膨らんだところで締めます。