20211012のAWSに関する記事は15件です。

【AWS】クラウドプラクティショナー 勉強メモ①

AWSクラウドの概要 クラウドとは クラウドとは、インターネットを利用して仮想サーバーやデータベースなどのITリソースをオンデマンドで利用できるサービスの総称のこと。 オンデマンドとは利用したい時に、利用したい分だけ費用がかかる従量課金体型のこと。 オンプレミスとは 自らサーバーやソフトウェア、ネットワークを導入・運用を行うこと。 しかし下記のような問題点がある。 最大利用時を予測して機器を導入するため、普段の利用ではキャパシティが余る。 あくまで予測のため、予測を超えるとキャパシティを超えて業務に支障が出る可能性がある。 頻繁に機器を変更をするとコストがかかるため、1年後や3年後を正確に考慮する必要がある。 AWSとは Amazon Web Servicesとは社内のビジネス課題を解決するために生まれたサービスであり、 Amazonが総合オンラインストアとして培っていたノウハウを基に提供を始めたサービスである。 AWSを利用する6つのメリット AWSクラウドコンピューティングを利用すると6つのメリットがある。 1. 固定費(設備投資費)が柔軟な変動費へ オンプレミスでは事前にデータセンターとの契約や、サーバーなどの機器を購入が必要であるが、クラウドを利用することで利用した分だけ支払う事ができる。 スタートアップ企業にとっては小額から始められることができる所が良い。 2. スケールによる大きなコストメリット 利用者が多いため、従量課金制の料金を低くできる。 AWSは2018年時点で60回を超える値下げを行っている。 3. キャパシティ予測が不要 AWSでは必要に応じてリソースを増減できるため予め○年後を予想をする必要はない。 4. 速度と俊敏性の向上 オンプレミスでは新規ITリソースの導入に何ヶ月もかかるが、クラウドなら分単位の時間で新たなITリソースを用意できるため、 新しい開発に素早く着手することができる。 5. 運用管理費の減額が可能 オンプレミスではサーバー代や電気代、機器故障時の対応等が必要であるが、クラウドを利用することにより、その業務に割く時間とコストを大幅に減らすことができる。 6. デプロイが容易になる。 クラウドなら数分、数クリックで海外拠点にサーバーを建てることができる。 現地へ向かい作業を行う時間とコストを大幅に減らすことができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】クラウドプラクティショナー 勉強メモ

AWSクラウドの概要 クラウドとは クラウドとは、インターネットを利用して仮想サーバーやデータベースなどのITリソースをオンデマンドで利用できるサービスの総称のこと。 オンデマンドとは利用したい時に、利用したい分だけ費用がかかる従量課金体型のこと。 オンプレミスとは 自らサーバーやソフトウェア、ネットワークを導入・運用を行うこと。 しかし下記のような問題点がある。 最大利用時を予測して機器を導入するため、普段の利用ではキャパシティが余る。 あくまで予測のため、予測を超えるとキャパシティを超えて業務に支障が出る可能性がある。 頻繁に機器を変更をするとコストがかかるため、1年後や3年後を正確に考慮する必要がある。 AWSとは Amazon Web Servicesとは社内のビジネス課題を解決するために生まれたサービスであり、 Amazonが総合オンラインストアとして培っていたノウハウを基に提供を始めたサービスである。 AWSを利用する6つのメリット AWSクラウドコンピューティングを利用すると6つのメリットがある。 1. 固定費(設備投資費)が柔軟な変動費へ オンプレミスでは事前にデータセンターとの契約や、サーバーなどの機器を購入が必要であるが、クラウドを利用することで利用した分だけ支払う事ができる。 スタートアップ企業にとっては小額から始められることができる所が良い。 2. スケールによる大きなコストメリット 利用者が多いため、従量課金制の料金を低くできる。 AWSは2018年時点で60回を超える値下げを行っている。 3. キャパシティ予測が不要 AWSでは必要に応じてリソースを増減できるため予め○年後を予想をする必要はない。 4. 速度と俊敏性の向上 オンプレミスでは新規ITリソースの導入に何ヶ月もかかるが、クラウドなら分単位の時間で新たなITリソースを用意できるため、 新しい開発に素早く着手することができる。 5. 運用管理費の減額が可能 オンプレミスではサーバー代や電気代、機器故障時の対応等が必要であるが、クラウドを利用することにより、その業務に割く時間とコストを大幅に減らすことができる。 6. デプロイが容易になる。 クラウドなら数分、数クリックで海外拠点にサーバーを建てることができる。 現地へ向かい作業を行う時間とコストを大幅に減らすことができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSの強い権限のクレデンシャル管理から転生した件

クレデンシャルをパスワードみたい使ってない?  開発チームには最低限の権限付けたクレデンシャルを払い出し、その点ではセキュリティは確保しているものの、運用系には強い権限を持ち、かつトラブル対応時に期限が切れないように永続的な権限を払い出してはいないでしょうか。権限が強いので更新などを所有者に移譲しているため、定期更新なんかしないで使い続けてる管理になりがち・・ これではパスワードと何も変わらない! このツールはAWSでのクレデンシャル管理の概念を変えるため作られました 作ったわ! 詳しくはリポジトリで。バイナリをダウンロードして即使いたいならこっち どんなん? このツールを使うと・・ 強い権限を持つクレデンシャルを作らなくて済みます 更新期限を意識してクレデンシャルを運用しなくてすみます えええええええええ!?!? どういう事だってばよ!? GitのArchitectureより SSL & AES encrypted Credential by Token _____________ __________ ___________ |Server mode | ----> |Proxy mode| <---- |Client mode| ------------- ---------- ----------- |Cloudshell | Tokenized Data SSL & Dencrypt by Token ------------- このツールはだいぶトリッキーなAWSの仕組みを利用しています。 前提としてAWS CloudShellを起動し、以下を叩くと短期で強い権限のクレデンシャルが払い出されています。 curl -H"Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" $AWS_CONTAINER_CREDENTIALS_FULL_URI このクレデンシャルを中継してクライアント側で使っちまおう!というのがこのツールの発想 動きとか分かんないと怖いんすけど、、、 CloudShellとクレデンシャルを使いたい端末の両方からアクセス可能なネットワークでProxyモードでツールを起動させます。両方からのHTTPSアクセスを待ち続けます CloudShell上にこのツールをアップします(またはgitからダウンロードします)。ServerモードとしてProxyモードでツールが動いている端末に通信を開始します。この際、クレデンシャルはトークンを使い、AESで暗号化されるのでProxyに送られても生クレデンシャルは見えません クレデンシャルを使いたい端末からClientモードでProxyへアクセスします。この際にトークンを指定し、取得した文字列からクレデンシャルを複合します。複合できたらユーザープロファイル配下のクレデンシャルファイルを更新します CloudShellのクレデンシャルには期限があるので、期限が近づくとServerはProxyに新しいクレデンシャルを送り、ClientはProxyに向けて新しいクレデンシャルをリクエストします。これによりクレデンシャルは永続なのにも関わらず、中身は更新されていくのでセキュリティも強固になります。かつ、自動更新なのでユーザーは透過的に使用し続けられます やったね! は?CloudShellのブラウザはタイムアウトすんじゃん!  ここがこのツールのミソで、超簡易的なRPAの機能を内包しています。そのため、CloudShellを開いているブラウザに定期的にキーボードでエンターを叩いたのと同じ操作を行います。これがあるのでブラウザはタイムアウトせずにCloudShellへのアクセスは途絶えません。テストで二時間は確認した。 (ただ、RPA的な動きなので現時点でWindowsでしかこれは機能しないです。。) あとがき CloudShellがVPCに接続できないのでちょっち怖いなと思うかもだけど https://aws.amazon.com/jp/blogs/news/aws-cloudshell-command-line-access-to-aws-resources/ 現在、セッションはプライベート VPC サブネット内のリソースに接続できませんが、近日中に接続できるようになる予定です。  という事なのでこれが接続できるようになれば社内VPCで完全に閉じたネットワークでクレデンシャルを作成しないで運用できるってことになるわけです!そしたら、ちっこいWindowsインスタンスでこのツール動かしておけば強いクレデンシャル問題はマジで解決しそう・・
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

terraformでIAM roleを作成しEC2にアタッチする時のtips

タイトルまんまですが、terraformでIAM role及びpolicyを作成してEC2インスタンスに当てようとした時に手こずったので記事にします。 まずはEC2本体を用意 ec2_sgw.tf data "aws_ssm_parameter" "sgw_ami" { name = "/aws/service/storagegateway/ami/FILE_S3/latest" } resource "aws_instance" "sdw_test" { ami = data.aws_ssm_parameter.sgw_ami.value vpc_security_group_ids = ["sg-00000000"] subnet_id = "subnet-00000000" iam_instance_profile = aws_iam_instance_profile.instance_role.id key_name = "hoge-key" instance_type = "m4.xlarge" tags = { Name = "sgw_test" } root_block_device { volume_type = "gp3" iops = 3000 throughput = 125 volume_size = 80 delete_on_termination = true encrypted = false tags = { Name = "sgw_root" } } ebs_block_device { device_name = "/dev/sdf" volume_type = "gp3" iops = 3000 throughput = 125 volume_size = 150 delete_on_termination = true encrypted = false tags = { Name = "sgw_cache" } } } storage gatewayをterraformで構築しようとしている途中での記事なので、AMIとかEBSが余分にぶら下がっているとか権限違うんじゃね?みたいな話は本題ではないのでスルーでお願いします(後日記事にする予定。つまり未定)。 tips 1 順番的にポリシー→ロール→アタッチなのですが、先にEC2のファイルを載せたので先に書いておきます。 肝はこれ iam_instance_profile = aws_iam_instance_profile.instance_role.id EC2にroleを当てる時はインスタンスプロファイルとしてroleを当てる必要があるということです(まぁ当然のことなんですが。。。)。 ロール書いた!ロールを直接指定したのにエラー吐く!なんでや!!という初歩的なトラップに引っかかりました。 例えばですが以下のように iam_role = aws_iam_role.ec2_role.id こんな書き方でEC2.tfの中に書いてもダメですよ、ということです。 見た目的に行けそうな気がしますがちゃんとインスタンスプロファイルにしてから当ててましょう。 tips 2 では順番にポリシーから行きましょう。 公式はこれ ここだとtfファイルに直接jsonファイルを書いている例が出ていますね。 policy = jsonencode({ Version = "2012-10-17" Statement = [ { Action = [ "ec2:Describe*", ] Effect = "Allow" Resource = "*" }, ] }) } これでも勿論問題ないですが、IAMポリシーはterraformさんの書式に合わせて書くよりjsonをそのまま書いちゃったほうがなんだかんだ便利だったりします (JSONシンタックスチェックが使えたりコピペでレッツゴーしたい時とか)。 ってことでポリシー本体部分を別途hoge.jsonみたいな感じで外出しして、ポリシーのtfファイルからjsonファイルを参照してあげることが出来ます。 ./からポリシー用のディレクトリを掘ってjsonファイルを書いていきます(pathを指定すればjsonの置き場はお好みでおk)。 今回は特定のS3バケットにのみアクセスできるようにするポリシーを書いています。 policy/s3_sgw-hoge.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:*", "Resource": [ "arn:aws:s3:::sgw-hoge", "arn:aws:s3:::sgw-hoge/*" ] } ] } まぁこれはよくあるやつですね。内容については割愛します。 んで、このjsonファイルをtfファイルから参照してあげる書き方は以下 resource "aws_iam_role_policy" "s3_policy_01" { name = "s3_sgw-hoge_role" role = aws_iam_role.s3_role.id policy = file("./policy/s3_sgw-hoge.json") } ココですね policy = file("./policy/s3_sgw-hoge.json") これでポリシー本体をファイル指定しています。 jsonファイルとして外出ししてあげたほうが個人的には見やすいので割とおすすめです、jsonで書けるし。 これに関しては後述(一番手こずった) role = aws_iam_role.s3_role.id tips 3 roleについてです。 さてココが本番です。 最終的なファイルはこんな感じ resource "aws_iam_role" "s3_role" { name = "sgw_s3_role" assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF } 結論から書いてしまうと、GUIではポリシーを作成→ロールを作成してポリシーを指定ですがterraformだと逆です。 先入観というか、固定観念は非常によろしくないですね。 先にも書いたポリシー resource "aws_iam_role_policy" "s3_policy_01" { name = "s3_sgw-hoge_role" role = aws_iam_role.s3_role.id policy = file("./policy/s3_sgw-hoge.json") } ここでポリシーを紐つけるroleを指定します。 role側で policy = aws_iam_role_policy. s3_policy_01 こんな感じに書いてもダメなわけです。 terraformではポリシー側からロールを指定、です。 次にこれ assume_role_policy = <<EOF { "Version": "2012-10-17", "Statement": [ { "Action": "sts:AssumeRole", "Principal": { "Service": "ec2.amazonaws.com" }, "Effect": "Allow", "Sid": "" } ] } EOF } 最初はこれを書かないでplan叩いて怒られました。 こんな感じ resource "aws_iam_role" "s3_role" { name = "sgw_s3_role" } これだとassume_role_policyが無いよと怒られます。 GUIから作成する時はあまり意識してなかった部分だったのですが(最悪)、これの正体はこれ これのことなんですねぇ 普段は脳死でEC2をクリックしていたのが最大の敗因。AWS認定の資格持っててもこういうところで躓いてちゃ型無しなわけです、反省。 ってことでEC2に当てたいロールなのでさっきのような書き方でOKということでした。 最後に上記で作成したroleを、インスタンスプロファイル作成時に指定してあげます。 resource "aws_iam_instance_profile" "instance_role" { name = "sgw_instance_role" role = aws_iam_role.s3_role.name } ここでのロールの指定は.nameで指定する必要があるようです、 んで、最終的にec2_sgw.tfのここで指定して完了です。 iam_instance_profile = aws_iam_instance_profile.instance_role.id いかがだったでしょうか(アフィ並感(書いてみたかっただけ(これ書いてある記事ry)))Invalid syntax 何かと便利なterraform先生ですが、気を抜いてるとこういう罠に引っかかってしまうので気をつけましょう。 最後まで閲覧頂きありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】ElasticTranscoderで動画変換する仕組みを構築する

はじめに 今回、弊社運動通信社で開発しているスポーツブルアプリの中のスーパープレイ動画機能の裏側について少しご紹介させていただきます。 スーパープレイ動画機能とは様々な競技のスーパープレイの部分を切り取った縦型の動画集をスワイプしながらザッピング感覚で閲覧できるものです。 ↓動画は以下のように縦型フルスクリーンで閲覧できます。 ザッピング感覚で閲覧できるようにする上で通信環境の考慮が必要となってきます。 通信環境によって読み込み時間が長いと体験が損なわれてしまうため、通信環境に応じた複数ビットレートの動画を用意し、その点を解決しました。 具体的にはAWSのElasticTranscoderを使ってその仕組みを構築してみましたので、事例としてご紹介したいと思います。 構成イメージ ざっくりした構成と処理の流れはこんな感じになります。 手順 ElasticTranscoderの設定 Pipeline ジョブを管理するキューです。作成したキューをLambda側から指定するため必要な項目を入力し作成します。 主な設定内容は以下となります。 変換元ファイル入力先S3バケット 変換後ファイル出力先S3バケット サムネイル出力先S3バケット SNSのtopic作成 Preset 変換処理に適用される設定を定義したテンプレートです。こちらも事前に作成しておいて、Lambdaからジョブを作成する際に使用するものを指定します。 設定の詳細については公式ドキュメントで参照できます。 Lambdaの作成 まず構成イメージの①②のところにあたる変換を実行するコードを書いていきます。 変換実行のサンプルコード const aws = require('aws-sdk'); const transcoder = new aws.ElasticTranscoder({apiVersion: '2012-09-25', region: 'ap-northeast-1'}); exports.handler = async (event, context, callback) => { const key = event['Records'][0].s3.object.key; const file = key.split('/').reverse()[0]; const filename = file.split('.')[0]; new Promise((resolve, reject) => { try { const params = { // ElasticTranscoderのコンソールから作成したPipelineのID PipelineId: 'xxxxxxxxxxxxx-xxxxxx', OutputKeyPrefix: 'sample/', Input: { Key: key, FrameRate: 'auto', Resolution: 'auto', AspectRatio: 'auto', Interlaced: 'auto', Container: 'auto' }, // 複数ビットレートの動画を出力する場合はここで対応する複数のpresetを指定する Outputs: [ { Key: `preset1/${filename}.mp4`, ThumbnailPattern: `thumbnail/${filename}-{count}`, // ElasticTranscoderのコンソールから作成したPresetのID PresetId: 'xxxxxxxxxxxxx-xxxxxx', Rotate: 'auto' } ] }; // ElasticTranscoderのJob作成 transcoder.createJob( params, function(error, data) { if (error) { console.log(error); throw new Error(error); } else { console.log('Job submitted.'); } } ); } catch(e) { reject(e); return; } resolve(); return; }).catch((e) => { callback(e); }); callback(null, 'Success.'); } 変換完了後は構成イメージの④⑤のようにSNSから通知を受け取り、完了後の処理を実装できます。 変換完了後のサンプルコード const aws = require('aws-sdk'); exports.handler = async (event, context, callback) => { const snsMessage = JSON.parse(event.Records[0].Sns.Message); // メッセージの中身を見て必要な処理を行う }; 終わりに 今回は、ElasticTranscoderを使った構成についてご紹介させていただきました。実際にシステムに組み込む場合は動画をサーバーからアップロードしたり、完了後にも必要な処理を組み込むなどあるかと思いますが、動画変換については基本的には今回ご紹介したような形になるかと思いますので少しでもご参考になる方がいらっしゃれば幸いです。 それでは。 参考サイト 運動通信社は 「日本を世界が憧れるスポーツ大国にする」というビジョンを達成するべく、 一緒に働く仲間を募集しています! https://sportsbull.jp/about/#recruit
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AppSync DynamoDB リゾルバーのマッピングテンプレートで管理用メタデータを解決する

はじめに AWS AppSync では GraphQL の Resolver として Velocity によるマッピングテンプレートを書くことで、プログラムを書くことなく 1 入力を成形してデータソースにデータを渡すことができる。 ここではデータソースに DynamoDB を指定した場合、ある程度汎用的に使えそうな自己流のメタデータ管理方法をメモ書きする。 リゾルバーのマッピングテンプレートの書き方 こちらの DynamoDB Resolver のサンプルの書き方を参考にしつつ ユーティリティなどは同じカテゴリーの別ジャンルにまとめられているので、こちらを見つつ作業していく。 また、Management Console のエディタを使うと、マッピングテンプレートがどのような形になるかをテストできるので、トライアンドエラーでマッピングテンプレートを記載する場合はこちらで始めると分かりやすかった。 AppSync API を選択 > スキーマ > Resolver を選択、の順でブラウザ上での編集が行える。 以下は編集後に更新に使われるデータをテスト用に準備し、テストを実行した場合、最終的にどのように Velocity が解決されて処理されるかをテストした例。 今回実施したいこと 管理用メタデータの処理をリゾルバーレベルで処理したい。 DynamoDB へのデータ登録時にメタデータを自動的に挿入したい Put 時にデータの生成時刻 created 項目を自動で追加する Put 時/ Update 時に更新時刻 udpated 項目を自動で追加する 楽観ロックを行う 新規登録時、キーが衝突する場合は新規登録に失敗する 更新時、 テーブル上の version 項目が更新されている場合は更新に失敗する 今回データソースに使うテーブルのキーはパーティションキーのみであり、そのキー名は key とする。 また、テスト用に用意した GraphQL のスキーマは以下の通り。 interface Meta { version: Int! created: String! updated: String! } type User implements Meta { key: String! name: String! age: Int! hobby: String version: Int! created: String! updated: String! } type Mutation { createUser(key: String!, name: String!, age: Int!, hobby: String): User updateUser(key: String!, name: String, age: Int, hobby: String, version: Int!): User } 新規データ登録 公式ドキュメントの以下の例を参考に、登録処理を記載。 { "version" : "2018-05-29", "operation" : "PutItem", "key" : { "key": $util.dynamodb.toDynamoDBJson($ctx.args.key) }, #set( $attr = $util.dynamodb.toMapValues($ctx.args) ) ## Set MetaData automatically #set( $attr.version = $util.dynamodb.toNumber(1) ) #set( $attr.created = $util.dynamodb.toString($util.time.nowISO8601()) ) #set( $attr.updated = $attr.created ) "attributeValues": $util.toJson($attr), "condition": { "expression": "attribute_not_exists(#id)", "expressionNames": { "#id": "key", }, } } このマッピングテンプレートでは、API 経由で渡ってきた全ての項目をそのまま DynamoDB テーブルの項目として登録する。 その登録内容に version (登録時なので1で固定)、created = updated (登録・更新日時は$util.time モジュールを用いて取得し、値を設定する) を注入してDynamoDB の PutItem を呼び出すようにしている。 condition 部分で既に同じキーを持つ項目が存在している場合、エラーとなるようにしている。 ここで記載したものは、サンプルとバージョンが異なる。 バージョンの違いはこちら に記載されているが、大きいものはエラーが発生してもエラーにならずに null が返ってくるというところがある。 そのため、レスポンスマッピングテンプレートで自明にエラーを処理するコードを書く必要がある。 具体的には、以下の通り。 response.vtl #if($ctx.error) ## 何らかのエラーがある場合、エラーレスポンスを明示的に返す $utils.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result) これらのリゾルバーを実際に登録して createUser の mutation を行うと以下の様な挙動となり、メタデータが自動生成されて入力されていることが分かる。 DynamoDB のテーブルにもこれらの値はちゃんと登録されている。 入力 mutation MyMutation { createUser(age: 10, key: "test", name: "testuser") { key, created, updated, version } } 出力 { "data": { "createUser": { "key": "test", "created": "2021-10-12T09:20:55.099Z", "updated": "2021-10-12T09:20:55.099Z", "version": 1 } } } 同じクエリを2度発行すると、既に同一キーでデータが登録されているので、以下の様なエラーとなる。 エラー出力 { "data": { "createUser": null }, "errors": [ { "path": [ "createUser" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: AQ1HCHTEEA9S3CQS5IGHNEMHIBVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: null)" } ] } データ更新 公式ドキュメントの以下の例を参考に、更新処理を記載。 引数で渡した値だけ更新したかったので、PutItem ではなく UpdateItem を実施する。 { "version" : "2018-05-29", "operation" : "UpdateItem", "key" : { "key": $util.dynamodb.toDynamoDBJson($ctx.args.key) }, ## Set MetaData automatically #set( $nextVersion = $ctx.args.version + 1 ) #set( $updated = $util.time.nowISO8601() ) ## update 用の expression を動的に構築する #set( $expNames = {} ) #set( $expValues = {} ) #set( $expSet = {} ) #set( $expRemove = [] ) ## 更新用のキーを取得 (主キー系を除く) #foreach( $entry in $ctx.args.entrySet() ) #if( $entry.key != "key" && $entry.key != "version" ) $!{expNames.put("#${entry.key}", "${entry.key}")} #if( (!$entry.value) && ("$!{entry.value}" == "") ) ## null 値が渡された場合、該当項目は Item から削除 $util.qr($!{expRemove.add("${entry.key}")}) #else ## 非 null 値が渡された場合、該当項目を追加・更新 $!{expSet.put("${entry.key}", ":${entry.key}")} $!{expValues.put(":${entry.key}", $entry.value)} #end #end #end ## 更新用固定値 (version は +1 に上書き) $util.qr($!{expValues.put(":version", $nextVersion)}) $util.qr($!{expValues.put(":updated", $updated)}) ## 更新用の expression を構築 #set( $expression = "SET version = :version, updated = :updated" ) ## 値の追加・更新を実施 #foreach( $entry in $expSet.entrySet() ) #set( $expression = "${expression}, #${entry.key} = :${entry.key}" ) #end ## 値の削除を実施 #if( !${expRemove.isEmpty()} ) #set( $expression = "${expression} REMOVE" ) #foreach( $entry in $expRemove ) #set( $expression = "${expression} #${entry}" ) #if ( $foreach.hasNext ) #set( $expression = "${expression}," ) #end #end #end ## 構築した SET, REMOVE で値を更新する "update": { "expression" : "${expression}", "expressionNames": $util.toJson(${expNames}), "expressionValues" : $util.dynamodb.toMapValuesJson(${expValues}) }, ## 更新条件は取得時と同一のバージョンであること (楽観ロック) "condition": { "expression" : "version = :expectedVersion", "expressionValues" : { ":expectedVersion" : $util.dynamodb.toDynamoDBJson($ctx.args.version) } } } update で利用するための値を作るために色々と foreach や if で値を作っている。 実際にこのマッピングテンプレートが割り当てられた updateUser を使ってみると、 mutation MyMutation { updateUser(key: "test", hobby: "programming", version: 1) { name, age, hobby, version, created, updated } } 出力 { "data": { "updateUser": { "name": "testuser", "age": 10, "hobby": "programming", "version": 2, "created": "2021-10-12T09:20:55.099Z", "updated": "2021-10-12T09:34:45.208Z" } } } と、このように 引数で渡した値 hobby は追加されている version, updated が更新されている (created は登録時のまま) ということが確認できる。 更新後、もう一度同じ mutation を行おうとすると、保存されている version と値が異なるため、以下のようなエラーになる。 エラー出力 { "data": { "updateUser": null }, "errors": [ { "path": [ "updateUser" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: J8HR2GLEFF0F46E7E3J4NFC63NVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: null)" } ] } 登録した hobby を削除する場合、以下の様になる。 mutation MyMutation { updateUser(key: "test", hobby: null, version: 2) { name, age, hobby, version, created, updated } } { "data": { "updateUser": { "name": "testuser", "age": 10, "hobby": null, "version": 3, "created": "2021-10-12T09:20:55.099Z", "updated": "2021-10-12T09:49:21.993Z" } } } これで、DynamoDB 上からも hobby の項目は削除される。 まとめ マッピングテンプレートは割と癖があるが、マニュアルを見つつトライアンドエラーを繰り返すことでかなり高度なことができることが分かった。 個人的にはこの内容は汎用性があると考えているため、メモ書きとして残しておく。 Velocity 自体に if やループ構文があるため、Velocityによるプログラミングをしているのだが、ここではLambdaなどの外部プログラムを使わない、という意味でプログラムを書くことなく、という表現を使った。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AppSync DynamoDB リゾルバーのマッピングテンプレートで管理用メタデータ(バージョン・更新日付・TTL)を解決する

はじめに AWS AppSync では GraphQL の Resolver として Velocity によるマッピングテンプレートを書くことで、プログラムを書くことなく 1 入力を成形してデータソースにデータを渡すことができる。 ここではデータソースに DynamoDB を指定した場合、ある程度汎用的に使えそうな自己流のメタデータ管理方法をメモ書きする。 2021/10/13追記: TTL管理の方法を追加 リゾルバーのマッピングテンプレートの書き方 こちらの DynamoDB Resolver のサンプルの書き方を参考にしつつ ユーティリティなどは同じカテゴリーの別ジャンルにまとめられているので、こちらを見つつ作業していく。 また、Management Console のエディタを使うと、マッピングテンプレートがどのような形になるかをテストできるので、トライアンドエラーでマッピングテンプレートを記載する場合はこちらで始めると分かりやすかった。 AppSync API を選択 > スキーマ > Resolver を選択、の順でブラウザ上での編集が行える。 以下は編集後に更新に使われるデータをテスト用に準備し、テストを実行した場合、最終的にどのように Velocity が解決されて処理されるかをテストした例。 今回実施したいこと 管理用メタデータの処理をリゾルバーレベルで処理したい。 DynamoDB へのデータ登録時にメタデータを自動的に挿入したい Put 時にデータの生成時刻 created 項目を自動で追加する Put 時/ Update 時に更新時刻 udpated 項目を自動で追加する DynamoDB のアイテムに TTL を設置し、自動的にデータが消去される仕組みを使いたい DynamoDB の TTL 機能を使うことで期限切れになったデータを自動削除することができる (※削除時間は厳密ではない) しかし、クライアント側の時計がこちらの想定した時計になっていない場合もあるため、基準時刻はGraphQL API側のものを利用したい 今回用意したテーブルでは ttl カラムに格納された値を元にデータを消去する設定を行っている 楽観ロックを行う 新規登録時、キーが衝突する場合は新規登録に失敗する 更新時、 テーブル上の version 項目が更新されている場合は更新に失敗する 今回データソースに使うテーブルのキーはパーティションキーのみであり、そのキー名は key とする。 また、テスト用に用意した GraphQL のスキーマは以下の通り。 # TTL 設置用。 上から優先的に適用 input TTLArgs { # 有効期限を unixtime で指定 expired: Int # 有効期限を ISOFormat の時刻表現で指定 expiredISO: String # 現在時刻からの相対時間で指定 (単位: 秒) delta: Int # TTL を撤廃する (更新専用) remove: Boolean } interface Meta { version: Int! created: String! updated: String! ttl: Int } type User implements Meta { key: String! name: String! age: Int! hobby: String version: Int! created: String! updated: String! ttl: Int } type Query { hello: String getUsers: [ User! ] } type Mutation { createUser( key: String!, name: String!, age: Int!, hobby: String ttlArgs: TTLArgs ): User updateUser( key: String!, name: String, age: Int, hobby: String, ttlArgs: TTLArgs, version: Int! ): User } 新規データ登録 公式ドキュメントの以下の例を参考に、登録処理を記載。 { "version" : "2018-05-29", "operation" : "PutItem", "key" : { "key": $util.dynamodb.toDynamoDBJson($ctx.args.key) }, #set( $attr = $util.dynamodb.toMapValues($ctx.args) ) ## Set MetaData automatically #set( $attr.version = $util.dynamodb.toNumber(1) ) #set( $attr.created = $util.dynamodb.toString($util.time.nowISO8601()) ) #set( $attr.updated = $attr.created ) ## Set Item TTL #if ($!{attr.ttlArgs}) $util.qr($!{attr.remove('ttlArgs')}) #if ("$!{ctx.args.ttlArgs.expired}" != "") #set( $attr.ttl = $util.dynamodb.toNumber(${ctx.args.ttlArgs.expired}) ) #elseif ("$!{ctx.args.ttlArgs.expiredISO}" != "") #set( $ttl = $util.time.parseISO8601ToEpochMilliSeconds(${ctx.args.ttlArgs.expiredISO}) / 1000 ) #set( $attr.ttl = $util.dynamodb.toNumber($ttl) ) #elseif ("$!{ctx.args.ttlArgs.delta}" != "") #set( $ttl = $util.time.nowEpochSeconds() + ${ctx.args.ttlArgs.delta} ) #set( $attr.ttl = $util.dynamodb.toNumber($ttl) ) #end #end "attributeValues": $util.toJson($attr), "condition": { "expression": "attribute_not_exists(#id)", "expressionNames": { "#id": "key", }, } } このマッピングテンプレートでは、API 経由で渡ってきた全ての項目をそのまま DynamoDB テーブルの項目として登録する。 その登録内容に version (登録時なので1で固定)、created = updated (登録・更新日時は$util.time モジュールを用いて取得し、値を設定する) を注入する。 また、テーブルの ttl 項目に ttlArgs で指定された方法で値を登録する。 expired: Int は unixtime で有効期限を指定、expiredISO: String はISOフォーマットの日付文字列表現で期限を指定、delta: Int は現在時刻に delta の値を加えた値を自動的に生存期限として設定する。 これらの値を Resolver で解決した後、DynamoDB の PutItem を呼び出すようにしている。 この時 condition 部分で既に同じキーを持つ項目が存在している場合、エラーとなるようにしている。 ここで記載したものは、サンプルとバージョンが異なる。 バージョンの違いはこちら に記載されているが、大きいものはエラーが発生してもエラーにならずに null が返ってくるというところがある。 そのため、レスポンスマッピングテンプレートで自明にエラーを処理するコードを書く必要がある。 具体的には、以下の通り。 response.vtl #if($ctx.error) ## 何らかのエラーがある場合、エラーレスポンスを明示的に返す $utils.error($ctx.error.message, $ctx.error.type) #end $util.toJson($ctx.result) これらのリゾルバーを実際に登録して createUser の mutation を行うと以下の様な挙動となり、メタデータが自動生成されて入力されていることが分かる。 DynamoDB のテーブルにもこれらの値はちゃんと登録されている。 入力 mutation MyMutation { createUser(age: 10, key: "test", name: "testuser", ttlArgs: {delta: 3600}) { key, created, updated, version, ttl } } 出力 { "data": { "createUser": { "key": "test", "created": "2021-10-13T03:47:17.146Z", "updated": "2021-10-13T03:47:17.146Z", "version": 1, "ttl": 1634100437 } } } 同じクエリを2度発行すると、既に同一キーでデータが登録されているので、以下の様なエラーとなる。 エラー出力 { "data": { "createUser": null }, "errors": [ { "path": [ "createUser" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: AQ1HCHTEEA9S3CQS5IGHNEMHIBVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: null)" } ] } データ更新 公式ドキュメントの以下の例を参考に、更新処理を記載。 引数で渡した値だけ更新したかったので、PutItem ではなく UpdateItem を実施する。 { "version" : "2018-05-29", "operation" : "UpdateItem", "key" : { "key": $util.dynamodb.toDynamoDBJson($ctx.args.key) }, ## Set MetaData automatically #set( $nextVersion = $ctx.args.version + 1 ) #set( $updated = $util.time.nowISO8601() ) ## update 用の expression を動的に構築する #set( $expNames = {} ) #set( $expValues = {} ) #set( $expSet = {} ) #set( $expRemove = [] ) ## 更新用のキーを取得 (主キー系を除く) #foreach( $entry in $ctx.args.entrySet() ) #if( $entry.key != "key" && $entry.key != "version" && $entry.key != "ttlArgs" ) $!{expNames.put("#${entry.key}", "${entry.key}")} #if( (!$entry.value) && ("$!{entry.value}" == "") ) ## null 値が渡された場合、該当項目は Item から削除 $util.qr($!{expRemove.add("${entry.key}")}) #else ## 非 null 値が渡された場合、該当項目を追加・更新 $!{expSet.put("${entry.key}", ":${entry.key}")} $!{expValues.put(":${entry.key}", $entry.value)} #end #end #end ## TTL の更新対応 #if ($!{ctx.args.ttlArgs}) #if ("$!{ctx.args.ttlArgs.expired}" != "") $!{expNames.put("#ttl", "ttl")} $!{expSet.put("ttl", ":ttl")} $!{expValues.put(":ttl", ${ctx.args.ttlArgs.expired})} #elseif ("$!{ctx.args.ttlArgs.expiredISO}" != "") #set( $ttl = $util.time.parseISO8601ToEpochMilliSeconds(${ctx.args.ttlArgs.expiredISO}) / 1000 ) $!{expNames.put("#ttl", "ttl")} $!{expSet.put("ttl", ":ttl")} $!{expValues.put(":ttl", $ttl)} #elseif ("$!{ctx.args.ttlArgs.delta}" != "") #set( $ttl = $util.time.nowEpochSeconds() + ${ctx.args.ttlArgs.delta} ) $!{expNames.put("#ttl", "ttl")} $!{expSet.put("ttl", ":ttl")} $!{expValues.put(":ttl", $ttl)} #elseif ("$!{ctx.args.ttlArgs.remove}" != "" && $!{ctx.args.ttlArgs.remove}) $!{expNames.put("#ttl", "ttl")} $util.qr($!{expRemove.add("ttl")}) #end #end ## 更新用固定値 (version は +1 に上書き) $util.qr($!{expValues.put(":version", $nextVersion)}) $util.qr($!{expValues.put(":updated", $updated)}) ## 更新用の expression を構築 #set( $expression = "SET version = :version, updated = :updated" ) ## 値の追加・更新を実施 #foreach( $entry in $expSet.entrySet() ) #set( $expression = "${expression}, #${entry.key} = :${entry.key}" ) #end ## 値の削除を実施 #if( !${expRemove.isEmpty()} ) #set( $expression = "${expression} REMOVE" ) #foreach( $entry in $expRemove ) #set( $expression = "${expression} #${entry}" ) #if ( $foreach.hasNext ) #set( $expression = "${expression}," ) #end #end #end ## 構築した SET, REMOVE で値を更新する "update": { "expression" : "${expression}", "expressionNames": $util.toJson(${expNames}), "expressionValues" : $util.dynamodb.toMapValuesJson(${expValues}) }, ## 更新条件は取得時と同一のバージョンであること (楽観ロック) "condition": { "expression" : "version = :expectedVersion", "expressionValues" : { ":expectedVersion" : $util.dynamodb.toDynamoDBJson($ctx.args.version) } } } 複雑だが、やっていることは Velocity で expression 部分を動的に生成しているだけである。 expressionNames と expressionValues を使っているので、インジェクションなどへの対処もしている。 実際に利用する場合は以下のようになる。 mutation MyMutation { updateUser(key: "test", hobby: "coffee", ttlArgs: {expiredISO: "2022-01-01T00:00:00Z"}, version: 1) { key, hobby, created, updated, version, ttl } } # { # "data": { # "updateUser": { # "key": "test", # "hobby": "coffee", # "created": "2021-10-13T03:47:17.146Z", # "updated": "2021-10-13T03:49:56.452Z", # "version": 2, # "ttl": 1640995200 # } # } # } と、このように 引数で渡した値 hobby は追加されている version , updated , ttl が更新されている (created は登録時のまま) ということが確認できる。 更新後、もう一度同じ mutation を行おうとすると、保存されている version と値が異なるため、以下のようなエラーになる。 エラー出力 { "data": { "updateUser": null }, "errors": [ { "path": [ "updateUser" ], "data": null, "errorType": "DynamoDB:ConditionalCheckFailedException", "errorInfo": null, "locations": [ { "line": 2, "column": 3, "sourceName": null } ], "message": "The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: J8HR2GLEFF0F46E7E3J4NFC63NVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: null)" } ] } 登録した hobby や ttl を削除する場合、以下の様になる。 mutation MyMutation { updateUser(key: "test", hobby: null, ttlArgs: {remove: true}, version: 2) { key, hobby, created, updated, version, ttl } } { "data": { "updateUser": { "key": "test", "hobby": null, "created": "2021-10-13T03:47:17.146Z", "updated": "2021-10-13T03:54:33.230Z", "version": 3, "ttl": null } } } これで、DynamoDB 上からも hobby , ttl の項目は削除される。 まとめ マッピングテンプレートは割と癖があるが、マニュアルを見つつトライアンドエラーを繰り返すことでかなり高度なことができることが分かった。 個人的にはこの内容は汎用性があると考えているため、メモ書きとして残しておく。 Velocity 自体に if やループ構文があるため、Velocityによるプログラミングをしているのだが、ここではLambdaなどの外部プログラムを使わない、という意味でプログラムを書くことなく、という表現を使った。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terraformで特定のResourceのみを取り扱う

はじめに これまでの検証はこのように対象別にフォルダを分けて行っていました。 そのフォルダごとにinit、plan、applyを実行していました。 さらに面倒なのがterraform.tfvarsとvariables.tfは同じもの。 各フォルダにコピーしていました。 └terraform ├ec2 │ ├terraform.tfvars │ ├variables.tf │ └ec2.tf ├ec2ssm │ ├terraform.tfvars │ ├variables.tf │ ├ec2.tf │ └role.tf └s3 ├terraform.tfvars ├variables.tf └s3.tf でも、こういう構成のほうが管理はしやすいですし、作業フォルダも一箇所で済みます。 └terraform ├terraform.tfvars ├variables.tf ├ec2.tf ├ec2.tf ├role.tf └s3.tf terraformのオプションでなにかないのか、探してみたところ「-terget」オプションが使えそうです。 上記のフォルダ構成、init済みを前提に早速検証してみます。 参考 初めてのTerraform SSMでEC2にアクセス TerraformでS3バケットを登録 Command: plan Terraformでtarget指定の削除 手順 # terraform plan -target=s3.tf No changes. Your infrastructure matches the configuration. Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed. ╷ │ Warning: Resource targeting is in effect │ │ You are creating a plan with the -target option, which means that the result of this plan may not represent all of │ the changes requested by the current configuration. │ │ The -target option is not for routine use, and is provided only for exceptional situations such as recovering from │ errors or mistakes, or when Terraform specifically suggests to use it as part of an error message. エラーになってしまいました... 調べたところ、Targetにはファイルを指定するのではなく、Resourceを指定するようです。 であれば今度はこのコマンドで検証してみます。 まずはS3。 s3.tfに記載のある「resource」を指定してみます。 resource "aws_s3_bucket" "bucket1" { bucket = "xxxxx-tf-bucket" acl = "private" } # terraform plan -target=aws_s3_bucket.bucket1 Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create : : Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. 無事にplanが実行できました。 念の為、EC2も試してみます。 同じように、ec2.tfに記載のある「Resource」を指定してみます。 resource "aws_instance" "xxxxx_tf-ec2" { count = 1 ami = "ami-03d5c68bab01f3496" # Ubuntu 20.04 LTS official ami instance_type = "t2.micro" iam_instance_profile = aws_iam_instance_profile.systems_manager.name tags = { Name = "${format("xxxxx_tf-ec2-%02d", count.index + 1)}" } } こちらも無事にplanが実行できました。 # terraform plan -target=aws_instance.xxxxx_tf-ec2 Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create : : Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. plan同様にapplyもdestroyも実行可能です。 おわりに この検証がどういうときに役に立つか。 例えばですが、デプロイ対象のResourceがたくあんある場合です。 特定のResourceのみ実行できれば実行時間の短縮に繋がります。 planについても既に確認済みのResourceは実行不要、特定のResourceだけ確認できればいい、そういう場合に役立ちそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TerraformでS3バケットを登録

はじめに 前回、前々回と、TerraformでEC2インスタンスを構築してみました。 業務でS3を構築する必要があるとのことなので、代わり映えがありませんが、今度はS3にバケットを構築してみようと思います。 目標 Terraformを使ってS3バケットを構築。 マネジメントコンソールは一切使用しないこと。 参考 初めてのTerraform SSMでEC2にアクセス iResource: aws_s3_bucket 手順 まずは作業フォルダでTerraformコマンドが利用できるよう初期化処理を行います。 # terraform init Initializing the backend... : : Terraform has been successfully initialized! : : commands will detect it and remind you to do so if necessary. S3を構築するためのTerraformファイルを用意します。 合わせて環境変数を定義しているファイルたちも用意します。 # terraform plan Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create : : Plan: 1 to add, 0 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. 記述ミスなど無いようなので実際に構築してみます。 実は一回記述ミスをしてしまいました。 記述ミスというより、バケット名に「」を使用してしまいました。 バケット名に「」は使用できませんので「-」に変更しておきました。 aws_s3_bucket.bucket1: Creating... ╷ │ Error: Error validating S3 bucket name: only lowercase alphanumeric characters and hyphens allowed in "xxxxx_tf_bucket" │ │ with aws_s3_bucket.bucket1, │ on s3.tf line 1, in resource "aws_s3_bucket" "bucket1": │ 1: resource "aws_s3_bucket" "bucket1" { 構築ですが、今回も便宜上、「-auto-approve」オプションを使用します。 # terraform apply -auto-approve Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # aws_s3_bucket.bucket1 will be created : : } Plan: 1 to add, 0 to change, 0 to destroy. aws_s3_bucket.bucket1: Creating... aws_s3_bucket.bucket1: Creation complete after 8s [id=xxxxx-tf-bucket] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. 正常終了したようですが、Terraformの実行結果を確認します。 # terraform show # aws_s3_bucket.bucket1: resource "aws_s3_bucket" "bucket1" { acl = "private" arn = "arn:aws:s3:::xxxxx-tf-bucket" bucket = "xxxxx-tf-bucket" bucket_domain_name = "xxxxx-tf-bucket.s3.amazonaws.com" bucket_regional_domain_name = "xxxxx-tf-bucket.s3.us-west-2.amazonaws.com" force_destroy = false hosted_zone_id = "xxxxx" id = "xxxxx-tf-bucket" region = "us-west-2" request_payer = "BucketOwner" tags_all = {} versioning { enabled = false mfa_delete = false } } 実際にAWS上に構築されているのかも確認してみます。 # aws s3 ls | grep xxxxx 2021-10-12 06:27:11 xxxxx-tf-bucket aws-cliを使用して実際にAWS上に構築されたことまで確認できました。 動作検証目的なのでこのバケットは削除しておきます。 削除はもちろんdestroyです。 ここでも-auto-approveオプションを使用します。 # terraform destroy -auto-approve aws_s3_bucket.bucket1: Refreshing state... [id=c069544-tf-bucket] Note: Objects have changed outside of Terraform Terraform detected the following changes made outside of Terraform since the last "terraform apply": # aws_s3_bucket.bucket1 has been changed ~ resource "aws_s3_bucket" "bucket1" { : : } Plan: 0 to add, 0 to change, 1 to destroy. aws_s3_bucket.bucket1: Destroying... [id=xxxxx-tf-bucket] aws_s3_bucket.bucket1: Destruction complete after 0s Destroy complete! Resources: 1 destroyed. 最後の最後に、ちゃんと削除されたかを確認しておきます。 # terraform show # # aws s3 ls | grep xxxxx # 無事に後始末まで終えることができました。 おわりに 最初から最後までGUI、マネジメントコンソールは使用しませんでした。 もうGUIはいらないのでは?と思いがちですが、決してそんな事はありません。 TF内に指定するパラメータはどんな意味があるのかを理解するには、やはりマネジメントコンソール上で確認したほうが視覚情報が加味され理解が深まります。 やり方は人それぞれですが、私は、AWSはまずGUIで構築してみて、同じものをTerraformで作ってみる、そういう作り方のほうが理解しつつ構築できるのではと考えています。 ところで... これまで何度かTerraformを試してきました。 EC2、SSM EC2、そしてS3。 実は試すたびにフォルダを分けて検証してきました。 └terraform ├ec2 │ ├terraform.tfvars │ ├variables.tf │ └ec2.tf ├ec2ssm │ ├terraform.tfvars │ ├variables.tf │ ├ec2.tf │ └role.tf └s3 ├terraform.tfvars ├variables.tf └s3.tf それぞれのフォルダでinitやってplanやってapplyやって... ちょっと不便というか面倒です。 フォルダ構成を変更し、特定のtfのみを実行することができれば、もっと気軽に検証できる気がしてきました。 次はこのあたりの構成の見直しを進めてみようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BigQuery Data Transfer Service を使用した S3 から Bigquery へデータロード

BigQuery Data Transfer Serviceを使用してS3からBigqueryへのデータロードを行います。 BigQuery Data Transfer Serviceの概要 あらかじめ設定されたスケジュールに基づき、BigQueryへのデータの移動を自動化するマネージドサービスです。 今回は S3からBigqueryへデータロードするため、BigQuery Data Transfer Service for Amazon S3を使用して、S3から BigQueryへの定期的な読み込みジョブを自動的にスケジュールし、管理します。 なお、BigQuery Data Transfer Serviceのジョブ実行自体には料金はかからず、S3からの上り(AWS)、BigQueryのストレージとクエリの標準料金のみ適用されます。 アクセス方法は以下です。 Cloud Console bq コマンドライン ツール BigQuery Data Transfer Service API 制約事項 BigQuery Data Transfer Service for S3では、以下の形式のデータの読み込みをサポートしています。 カンマ区切り値(CSV) JSON(改行区切り) Avro Parquet ORC データソースのS3のURIは最上位のバケット名のみが必須となり、配下のフォルダ名は任意で指定可能です。 例えば 最上位のバケット名 s3://load-bigquery を指定した場合、配下にあるファイルは全て転送対象となります。 s3://load-bigquery/folder1 を指定した場合、folder1 配下のファイルのみ転送対象となります。 URIにはワイルドカードを使用出来ます。 例えば s3://load-bigquery/folder1/*.csv を転送対象と指定した場合、s3://load-bigquery/folder1/test.csv および、s3://load-bigquery/folder1/folder2/prod.csv は転送対象となります。 BigQuery Data Transfer Serviceでは、BigQueryの読み込みジョブに対して以下の上限事項があります。 読み込みジョブの転送実行ごとの最大サイズ 15 TB Amazon S3 URIに 0~1 個のワイルドカードを使用する場合の転送実行ごとの最大ファイル数 10,000,000 ファイル Amazon S3 URIに複数のワイルドカードを使用する場合の転送実行ごとの最大ファイル数 10,000 ファイル URI と宛先テーブルはどちらも run_time / run_date というパラメータの利用が可能です。 例えば、日付毎に格納されたバケットからデータを読み込めます。 URI内のcsvは、今日の日付で分割されたテーブルに転送されます。 更新転送では、この構成によって、最後の読み込み以降に追加されたファイルが転送対象になり特定のパーティションに追加されます。 利用箇所 値 抽出元 URI s3://load-bigquery/*.csv パラメータ化された宛先テーブル名 table${run_time | "%Y%m%d"} 評価された宛先テーブル名 table$20211007 使用順序のまとめ S3 S3側は以下の準備が必要です。 対象バケットから転送を呼び出しを行う際に使用する IAMアクセスキーIDの準備 対象バケットから転送を呼び出しを行う際に使用する シークレットアクセスキーの準備 対象のIAMから対象バケットに対して少なくとも AWS管理ポリシー AmazonS3ReadOnlyAccessの設定 転送するS3バケットURIの用意 AWS側に特段要件が無い場合、以下のようにバケットポリシー上で特定のIAMユーザーのみの操作を許可する事でアクセス権限を最小限に抑える事が可能です。 bucket-policy.json { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal":{ "AWS": "arn:aws:iam::account-id:user/xxx" }, "Action":"s3:*", "Resource": [ "arn:aws:s3:::account-id-bucket", "arn:aws:s3:::account-id-bucket/*" ] }, { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": [ "arn:aws:s3:::account-id-bucket/*" ], "Condition": { "StringNotEquals": { "aws:username": "xxxx" } } } ] } BigQuery Data Transfer Service BigQuery Data Transfer APIを有効化する必要があります。 Google ConsoleからBigqueryを選択いただき、有効化を選択します。 転送するユーザー(Google Cloud側)に bigquery.admin 権限が必要です。必要に応じて権限を持つユーザーの追加、変更をします。 作業 BigQueryのデータセット、テーブル作成を行います。 今回は bq コマンドラインから実施します。 データセットの作成 bq --location=us mk --dataset test_dataset_s3 テーブルの作成 (今回はインラインでスキーマを定義します) bq --location=us mk --table test_dataset_s3.test_table date:string,randomvalue:string BigQuery Data Transfer Serviceの転送ジョブを作成します。 ここでアクセスキーID / シークレットキー/ 転送対象の URI が必要となります bq mk \ --transfer_config \ --data_source=amazon_s3 \ --display_name=load_from_s3 \ --target_dataset=test_dataset_s3 \ --params='{ "data_path":"s3://xxx-test01", "destination_table_name_template":"test_table", "access_key_id":"xxxxxxxxxxxxx", "secret_access_key":"xxxxxxxxxxxxxx", "file_format":"CSV", "max_bad_records":"0", "ignore_unknown_values":"true", "field_delimiter":",", "skip_leading_rows":"0", "allow_quoted_newlines":"true" }' BigQuery Data Transfer Service を使用する bq コマンドのオプションは以下に記載があります。 https://cloud.google.com/bigquery-transfer/docs/s3-transfer 実行後に URLが発効されるため、ブラウザから認証コードを取得してコマンドラインに貼り付けます。 Enter your version_info here: 認証が完了すると、ジョブの実行が開始されます。 今回はテスト用の CSV のため数秒で実行が完了しました。 ロードしたデータの確認 ジョブが完了すると、bq コマンドライン ツールからロードしたテーブルにクエリを実行して、データが格納されているかを確認します。 bq query --nouse_legacy_sql 'SELECT COUNT(*) FROM test_dataset_s3.test_table' +----------+-------------+ | date | randomvalue | +----------+-------------+ | 20211004 | gsjog@wgsf | | 20211004 | b@etewr | | 20211005 | brdew@fasz | +----------+-------------+ 以下は S3 の CSV のデータソースです。ロードは問題なく完了しているようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Architecture Icons of Various Cloud Vendors

Introduction If you are a Cloud Architect using cloud services provided by single/multiple Cloud Vendor(s), then designing and drawing cloud architecture maybe a common task. Here is the links of architecture icons provided by various cloud vendors. Hope it helps a little bit. I listed some PPT/Zip files here that are free to download from their official web sites. Oracle Cloud Infrastructure (OCI) Graphics Google Cloud Solution Icons Azure Architecture Icons AWS Architecture Icons Alibaba Cloud Icons Oracle Cloud Infrastructure (OCI) Graphics Download Portal PowerPoint Graphics and Quick Start Guide (Updated on 2021/4/30) EPS, PNG, and SVG graphics (Zip) Google Cloud Solution Icons Download Portal Google Cloud Official Icons and Solution Architectures (Menu Bar->File->Download) SVG and PNG icons (Zip) Azure Architecture Icons Download Portal Azure_Public_Service_Icons_V4.zip AWS Architecture Icons Download Portal PPTx for Dark Background (Release 11.1-2021/9/21) PPTx for Light Background (Release 11.1-2021/9/21) Alibaba Cloud Icons Download Portal 2020 Alibaba Cloud Product Icons(English).pptx End
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

e-statのデータをAthenaで読み込む

データの準備 e-statからCSVファイルをダウンロードする e-stat APIの利用ガイドを参照して、アプリケーションIDを取得する API機能テストフォームの統計データ取得を使って、データを取得する。 テストフォームには以下の項目を入力します。取得できるのは、最大10万行です。 - アプリケーションID - 統計表ID(0003410379) - データ形式(CSVを選択) - セクションヘッダフラグ(出力しないを選択) 統計表IDは「男女別人口及び人口性比-全国,都道府県(大正9年~平成27年)」のものです。必要に応じて変更してください。 セクションヘッダフラグ[sectionHeaderFlg] を「出力しない」にしておくと、出力件数や調査名称などのメタデータが出力されず扱いやすくなります。「出力する」にすると、出力件数が10万行を超えるとヘッダーの行数が増えるなど扱いづらくなります。 なお、e-statからは(APIではなく)DBからダウンロードすることでユーザ登録なしでCSVファイルをダウンロードできますが、文字コードがShift-JISのため、Athenaでは扱いづらいです。「引用符あり」「ヘッダーあり」「Shift-JIS」の組み合わせがAthenaと相性が良くないようです(こちらのサイトを参照)。 APIから取得した場合は、文字コードがUTF8なので問題なしです。 S3の作業 以降の作業はすべて同じリージョン内で行う前提です。 リージョンをまたぐとうまくいかないことがあるようです。 バケットの作成 AWS マネジメントコンソールからS3を選択 → バケットを作成 → 任意のバケット名を入力し、(他はデフォルトのまま)バケットを作成 データのアップロード 上で作成しバケット名をクリックし、次の画面でファイルをドラッグ&ドロップ → アップロード Athenaの作業 AWS マネジメントコンソールからAthenaを選択 → 以下のクエリを入力し実行 データベースの作成 以下のクエリを実行してデータベースを作成します。 create database (データベース名) テーブルの作成 次に、以下のクエリを実行してテーブルを作成します。 CREATE EXTERNAL TABLE IF NOT EXISTS データベース名.テーブル名( `tab_code` STRING, `表章項目` STRING, `cat01_code` STRING, `総数,男及び女_時系列` STRING, `area_code` STRING, `都道府県_時系列(人口集中地区有り一部の市)` STRING, `time_code` STRING, `時間軸(調査年)` STRING, `unit` STRING, `value` STRING, `annotation` STRING ) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.OpenCSVSerde' WITH SERDEPROPERTIES('serialization.format' = ',', 'field.delim' = ',' ,'quoteChar' = '\"', 'escapeChar' = '\\') LOCATION 's3のURI' TBLPROPERTIES ('has_encrypted_data'='false', 'skip.header.line.count'='1') テーブル名 小文字で始める、アンダーバーは使えないなどの制約があるので、AWSのドキュメントを参照してください。 列名 e-statから出力されるものをそのまま使っています。 データの型はすべてString(文字列)で読み込みます。これは、Value等の数値項目であっても、秘匿や非該当などの理由で「-(ハイフン)」などの記号が入ってあることがあり、Athenaで読み込む際にエラーになるためです。 Row Format Serde 今回は'org.apache.hadoop.hive.serde2.OpenCSVSerde'を指定します。データ形式に応じて、前述のサイトなどを参照して適切なものを選びます。 SerdePorperties field.delimは列の区切り文字です。ここでは「,(カンマ)」を指定しています。 quoteCharは項目についている引用符です。e-statデータはすべて「"(ダブルクォート)」で囲まれているので、「"」を指定します。 locationには、データを保存したS3のフォルダのURIを指定します。Athenaではファイルごとではなく、フォルダまるごとを同一形式のデータとして読み込みます。「s3://backetName/foldername/」といった形式になります(最初のsは小文字、最後のスラッシュもお忘れなく)。 TblPropertiesのskip.header.line.countには、スキップする先頭の行数を指定します。e-stat APIからダウンロードする際に、セクションヘッダフラグ[sectionHeaderFlg] を「出力しない」としている場合は、ヘッダー行は1行なので「1」を指定します。 テーブルの確認 クエリが正常に実行できれば、左側のテーブル一覧にテーブル名が追加されるので、メニューボタン(点3つ)から「テーブルをプレビュー」を選択します。 自動的にプレビューするクエリが実行され、中身を確認することができます。 以上で、e-statデータをAthenaから読み込むことができました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaで取得した画像をKinesis及びLambda経由でS3にアップロードする

Lambdaで取得した画像をKinesis及びFirehose経由でS3にアップロードするにて 流した画像がFirehoseを経由うすると複数の画像がまとめられて出力されたので KinesisからLambda経由でS3にアップロードしたときの個人メモ (参考)S3 — Boto3 Docs 1.18.57 documentation - Amazon AWS (参考)Amazon Kinesis で AWS Lambda を使用する Kinesis Lambdaで取得した画像をKinesis及びFirehose経由でS3にアップロードするにて 作成したものを流用 Lambda Python3.9にて作成 自動生成されたロールを選択 ロールにアタッチされているポリシーを以下のように修正 S3へのPutObjectを付与 logsはデフォルトのまま ロールに既存ポリシー「AWSLambdaKinesisExecutionRole」をアタッチ { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Action": [ "s3:PutObject", "logs:CreateLogGroup" ], "Resource": [ "arn:aws:logs:(リージョン名):(AWSアカウント):*", "arn:aws:s3:::(バケット名)/*" ] }, { "Sid": "VisualEditor1", "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:PutLogEvents" ], "Resource": "arn:aws:logs:(リージョン名):(AWSアカウント):log-group:/aws/lambda/uploadToS3FromKinesis:*" } ] } ソース修正 lambda_function.py import os import logging import boto3 import base64 # ログ設定 logger = logging.getLogger(__name__) logger.setLevel(os.environ['LOG_LEVEL']) # 環境変数から取得 BACKET_NAME = os.environ['BACKET_NAME'] FILE_PATH = os.environ['FILE_PATH'] # 画像をS3にアップロードする # img:画像のバイナリデータ # return:プットした結果 def put_img_to_s3(img): logger.debug('start put_img_to_s3()') s3 = boto3.client('s3') response = s3.put_object(Bucket=BACKET_NAME, Key=FILE_PATH, Body=img) logger.debug('end put_img_to_s3()') return response def lambda_handler(event, context): logger.debug('start lambda_handler()') logger.debug('event:' + str(event)) img = base64.b64decode(event['Records'][0]['kinesis']['data']) result = put_img_to_s3(img) logger.debug('end lambda_handler()') return result 環境変数設定 動作確認 Lambdaで取得した画像をKinesis及びFirehose経由でS3にアップロードすると同様 感想 Firehose経由と異なり、連続で画像を流してもまとめてアップロードされなかった アップロード時のファイル名は環境変数で指定した固定値なので 動的に設定できるようにしたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS (CodePipeline CodeCommit CodeBuild CodeDeploy) をざっくり理解

概要 CodePipeline CodeCommit CodeBuild CodeDeploy を用いたデプロイの流れをざっくり解説しました。 細かい設定や、ファイルへの記述は最後に書いていないです。 参考にさせていただいた講座および引用させていただいた画像 Techpit 「Laravel × CircleCI × AWSで学ぶCI/CD」  shonansurvivors様 著 基盤となる図 1~7まではローカルからCircleCIを通して、featureブランチでテストをパスした後masterにマージ。そしてテストを行っている。 その後からCode〇〇を用いたデプロイの開始である。 Code〇〇のそれぞれの役割 CodePipeline デプロイをするためのパイプラインを文字通り作っている。以下の図のようにCodeCommit CodeBuild CodeDeploy をこのパイプラインに当てはめる。 これがデプロイするためのパイプライン(CodePipeline)である。 下記の図で上からCodeCommit CodeBuild CodeDeploy の順に並ぶ。 CodePipeline を作成する際にCodeCommit CodeBuild CodeDeploy という3つのパーツを当てはめる。 また、CodePipelineを作成するときにCodePipeline用のS3バケットが自動で作成される。 Codecommit CodeCommitとは、AWS上に存在するGitリポジトリのサービス(AWS版のGitHubのようなもの)。 CodeCommitに最新のmasterブランチがプッシュされると、そのコードがS3にコピーされ(図の9)、これをCodeBuildが取得します(図の10)。 下記のようにリポジトリが作成される。 Codebuild CodebuildはS3から先程コピーされたソースコードを取得。 テスト、パッケージのインストールを実行して、すぐにデプロイできるアーティファクトを生成する役割(図の10,11,12)。 例としては、yarn install yarn test rake assets:precompile等を実行してデプロイできる準備を行う。 テストをここで行うこともできるので、CircleCIでのテストを省略可能。 行いたいコマンドをbuildspec.yml に記述しアプリケーションのトップディレクトリに配置する。(詳しくは後) CodeDeploy デプロイを指示する役割(図の13)。S3からビルド後のファイル群を取り出し、EC2にデプロイします(図の14)。 また、EC2からRDSのDBに対してマイグレーションを実行します(図の15) appspec.yml というファイルをアプリケーションのトップディレクトリに配置することによってデプロイする際のコマンド(rails db:migrate など)を指定することができる(後に詳しく) 全体の流れ 全体の流れとしては ① CircleCIでのテストをパスしたソースコードをCodeCommitにpush ② そのソースコードをCodeCommitからS3にコピー ③ CodebuildがS3からソースコードを取得し、デプロイの準備のためのコマンドを実行 ④ CodeDeployがデプロイを指示。その際に、データベースのマイグレーションなど行いたいコマンドを実行。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS EC2にVSCodeでRemote-SSH接続(Windows)

AWS EC2インスタンスにTera Termではなく、VScodeで接続 とても簡単で、これするだけでGUIで感覚的な操作ができます。 EC2 OSはAmazon Linux 2の場合ユーザ名は「ec2-user」です。 (そのほかのOSのEC2の場合はAWSのリファレンスを確認) 手順 VScodeで拡張機能「Remote-SSH」インストール VScodeウィンドウの左に「リモートエクスプローラー」のアイコンが現れるのでクリック プルダウンで「SSH Targets」を選択 歯車アイコンをクリック 「C:\Users[ユーザ名].ssh\config」 を選択 以下を入力して保存 Host (任意) HostName [EC2パブリックアドレス] Port 22 IdentityFile [pemキーのパス] User ec2-user VSCodeウィンドウ左下の緑色の"><"ボタンを押す 表示されたメニューから「Connecto to Host...」を選択 configに入力したHost名を選択「ec2」 Select the platform of the remote host "ホスト名"と聞かれたら「Linux」を選択 初回接続で「Continue」と「Cancel」が聞かれたら、「Continue」を選択 「フォルダ開く」を選択してEC2上の好きなフォルダを選択 これだけでGUIの操作ができます
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む