20210908のAWSに関する記事は12件です。

AWS CloudTech ハンズオン1に必要な費用の調べ方についてまとめました

この記事を書いている私について インフラ系PMO歴半年でAWSクラウドプラクティショナーを取ったばかりの私が AWS CloudTechという動画学習サービスに参加し、AWSエンジニアを目指すための備忘録となります この記事を書こうと思った経緯 構築の練習をしたいけど、実際どのくらい費用がかかるの!?と思い書きました。 AWS CloudTechで費用をまとめてくれていますが、AWSの料金体系は変わってきますし、 自分で確認したい初学者の参考になれば嬉しいです。 この記事で対象としている課題 AWSが公開している料金表を参考におおよその費用を算出します。 対象:AWS CloudTech ハンズオン1_基本的なブログサービスを構築する(シングル構成) ハンズオン1の構成図 インターネットゲートウェイ、EC2、RDSとシンプルな構成です。 ハンズオン1の構築に必要な費用の一覧 (2021.9.8時点) ■EC2(オンデマンド):Amazon Linux2 *無料利用枠あり  タイプ:t2.micro   時間単価:$0.0116   月額:$0.0116 X 730H → $8.352 ■データ転送   月あたりリクエスト数:25000   平均レスポンス(byte):51200   1GBデータ量単価(OUT):$0.09   月額:(25000 X 51200 X $0.09)/1000000000byte → $0.1152 ■RDS(オンデマンド)  タイプ:db.t2.micro   時間単価:$0.017   SSDストレージ (GBあたり):$0.115   GB量:5   月額:$0.017 X 730H + $0.115 X 5 → $12.985 ◆月額(計):$21.4522 ◆時間単価(計):$21.4522 / 730H →  $0.0294        シンプルな構成なら時間当たり3円ほど、安いですね! 公式ページで見るべき場所の紹介 ■Amazon EC2 オンデマンド料金  https://aws.amazon.com/jp/ec2/pricing/on-demand/ ■Amazon EC2 データ転送  https://aws.amazon.com/jp/ec2/pricing/on-demand/ ■旧世代インスタンスの価格 オンデマンド DB インスタンス https://aws.amazon.com/jp/rds/previous-generation/ 学習していない時に費用が発生しないようにするためには ■EC2(オンデマンド)→停止中にすること 料金は、インスタンスが開始してから、それが終了または停止するまでの、各インスタンスについて使用されるインスタンス時間当たりの料金となるため、使わないときは必ず停止中にしましょう。 ■データ転送 Amazon EC2 受信 (イン)」/「送信 (アウト)」されるデータ転送量を基に請求されますので、データ転送が無ければ費用は発生しません。 ■RDS(オンデマンド) 時間単位で、計算処理能力に対して料金が発生します。使わないときには必ず停止しましょう。 最後に 想定していない料金が発生しないよう気けて これからも勉強していきたいと思います。 内容に不備がございましたら、ご指摘いただけますと幸いです。 この記事はAWS初学者を導く体系的な動画学習サービス「AWS CloudTech」の課題カリキュラムで作成しました。 https://aws-cloud-tech.com
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Serverless Frameworkで運用しているAWSアプリケーションのデプロイをGithub Actionで管理する方法

概要 Serverless Frameworkを使用する時、ローカルからデプロイするだけで特にCIを回していなかったのでGithub Actionで開発フローを組もうと思い調べてみたので設定手順をまとめておきたいと思います。 この記事でセットアップする開発フローは、Github Pushトリガーによって、Serverless Serviceをデプロイするアクションを起動する非常にシンプルなものです。 目次 構成 運用フロー 準備 設定 Serverless Framework Github Action 動作確認 参考URL 構成 Lambdaのトリガーとして設定するAPI Gatewayには、開発用(dev)・本番用(prod)の2つのステージを用意し、Functionを起動した際はそれぞれのステージのLambdaが実行されるようにします。 開発用Lambda(dev) Latestバージョンを常に参照します。LatestバージョンはAWSによって、デプロイ毎にデフォルトで割り当てられる最新のバージョンの事です。 本番用Lambda(prod) 指定したバージョンを切り替える事で、本番用にバージョニング設定する事が出来ます。 運用フロー GithubのマスターブランチへのPushをトリガーに、Serverless Serviceをデプロイするアクションが実行されるようにします。 トリガー 1.開発環境(開発用Lambda)へのデプロイ GithubのマスターブランチへのPushをトリガーとします。 2.本番環境(本番用Lambda)へのデプロイ Githubのマスターブランチへタグ付きのPushをトリガーとします。 準備 Serverless Frameworkのインストール npm install -g serverless プロバイダーセットアップ 今回はAWSアプリケーションを作成するので、Serverless FrameworkのプロバイダーとしてAWSを設定します。Serverless Frameworkに、AWSの機能権限を与えたIAMユーザーを割り当てるために下記を実行する必要があります。 1.AWSでServerless用のIAMユーザを発行。 2.IAMユーザにAdministratorAccessの管理ポリシーを与える。 設定 Serverless Framwork 1.サービス(Serverless Frameworkにおける実行環境の単位)の作成 serverless create --template aws-nodejs --name greeting-service --path greeting-service 2.環境変数の設定(env.yml) prod: appName: "Serverless App" default: appName: "DEV Serverless APP" 3.サービス全体の設定(serviceless.yml) 今回はLambdaとAPI Gatewayで構成するアプリケーションです。serverless.ymlに諸々のAWSに関する設定を記述していきます。 サービス内のLambdaファンクション群 Getリクエストで、挨拶を返すHiファンクションの定義。 Lambdaファンクションごとのトリガーとなるイベントの定義 Functionのトリガーとして、Getリクエストを定義。 環境変数の定義 serverless.yml内で使用する変数を定義。 Function内で使用する変数を定義。 providerの設定 AWSの地域設定。 変数の中身は、GithubのPushトリガーで設定出来るように定義します。この変数がServerlessのデプロイ先をルーティングする役割を担当します。 service: greeting-service frameworkVersion: '2' //自身で定義した変数 => serviceless.ymlで参照 custom: defaultStage: dev // 開発環境へのデプロイ → prod.appName変数が参照される。 // 本番環境へのデプロイ → default.appName変数が参照される。 environment: ${file(env.yml):${self:provider.stage}, file(env.yml):default} provider: name: aws runtime: nodejs12.x lambdaHashingVersion: 20201221 stage: ${opt:stage, self:custom.defaultStage} // lambdaで使用する変数 environment: appName: ${self:custom.environment.appName} // 地域の指定 region: ap-northeast-1 functions: greeting: handler: handler.greeting events: - http: path: / method: get 4.Lambda functionの設定 'use strict'; module.exports.greeting = async (event) => { console.log(process.env.appName); console.log(event.requestContext.stage); let html = ` <p>Hi!</p> `; return { statusCode: 200, headers: { 'Content-Type':'text/html' }, body: html }; }; Github Action 1.AWSのシークレットキーと環境変数をGithubで設定する。 2.workflowの設定 dev workflow マスターPushをトリガーにしたワークフローを記述します。このワークフローが走ることで、1.開発環境(開発用Lambda)へのデプロイが実行されます。 name: Deploy Dev Lambda Function by Serverless Framework on: push: branches: - master jobs: deploy-dev: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: '12.x' - name: Install Serverless Framework run: npm install -g serverless - name: Serverless AWS authentication run: sls config credentials --provider aws --key ${{ secrets.AWS_KEY }} --secret ${{ secrets.AWS_SECRET }} - name: Create env file run: | cat > env.yml << EOF ${{ secrets.ENV }} EOF - name: Install NPM dependencies run: npm install - name: Deploy Lambda functions // ステージを開発環境に指定し、デプロイ run: sls deploy -s dev prod workflow タグ付きのマスターPushをトリガーにしたワークフローを記述。このワークフローが走ることで、2.本番環境(本番用Lambda)へのデプロイが実行されます。 name: Deploy Production Lambda Function by Serverless Framework on: push: tags: # Deploy tag (e.g. v1.0) to production - 'v**' jobs: deploy-prod: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - uses: actions/setup-node@v1 with: node-version: '12.x' - name: Install Serverless Framework run: npm install -g serverless - name: Serverless AWS authentication run: sls config credentials --provider aws --key ${{ secrets.AWS_KEY }} --secret ${{ secrets.AWS_SECRET }} - name: Create env file run: | cat > env.yml << EOF ${{ secrets.ENV }} EOF - name: Install NPM dependencies run: npm install // ステージを本番環境に指定し、デプロイ - name: Deploy Lambda functions run: sls deploy -s prod 動作確認 設定したGithub Actionで、Serverless Applicationのデプロイが実行されるか確認したいと思います。まずは開発環境にデプロイします。 git push origin master Actions画面で確認してみると、開発ステージにデプロイされた事が確認出来ます。 curlコマンドでも確認してみます。 開発環境(https://***.execute-api.***.amazonaws.com/dev) curl https://***.execute-api.***.amazonaws.com/dev/ <p>Hi!</p> 開発環境にアプリケーションがデプロイされている事が確認出来ました。 続いて、タグを切って本番環境にデプロイしてみます。差分を確認するために、Function関数を変更します。 'use strict'; module.exports.greeting = async (event) => { console.log(process.env.appName); console.log(event.requestContext.stage); let html = ` <p>Chao!</p> `; return { statusCode: 200, headers: { 'Content-Type':'text/html' }, body: html }; }; git push origin Tag<version> こちらもcurlコマンドでも確認してみます。 本番環境(https://***.execute-api.***.amazonaws.com/prod) curl https://***.execute-api.***.amazonaws.com/prod/ <p>Chao!</p> 本番環境にアプリケーションがデプロイされている事が確認出来ました。 まとめ Serverless Frameworkを使えば、AWSアプリケーションの構築管理が便利に出来ますが環境の切り替えをActionに任せる事でよりサービスの運用が楽になる事が分かりました。 今回の運用フローでは、AWS Lambda関数を、開発用と本番用の2つに分けていますが、エイリアスでバージョニングを行えば1つの関数で環境をスイッチング出来るみたいなのでそちらの方法も試してみたいと思います。 参考URL Serverless Frameworkの使い方まとめ How to deploy Serverless applications using Github Actions APIGateway+Lambdaによるアプリサーバをバージョン管理する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

インターネットゲートウェイと仮想プライベートゲートウェイの違い

勉強前イメージ なんだっけ・・・ぱっと違いが出てこない 調査 インターネットゲートウェイとは インターネットゲートウェイはインターネットと接続するもので、 ネットワーク内はインターネットゲートウェイを通じてインターネットとやり取りを行います。 VPC内とインターネットを通信する際は以下を行います。 VPCにインターネットゲートウェイとアタッチする ルートテーブルでデフォルトルートにインターネットゲートウェイの設定を追加 仮想プライベートゲートウェイとは Virtual Private Gateway とも言い、 VPN接続の際にVPC側に配置するもの 複数のカスタマーゲートウェイ(ユーザ側に配置されるVPN装置)を受け付けることが出来る。 違い インターネットゲートウェイ → VPC内とインターネットの接続の際に使用 仮想プライベートゲートウェイ → VPN接続の際にVPC側に接続するもの 勉強後イメージ 全然違うやん!!! 特に仮想プライベートゲートウェイ忘れがち 参考 4. インターネットゲートウェイとルートテーブルでパブリックサブネットをインターネットにつなげる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】RDSとEC2を紐づける方法

はじめに 今回はRDSとEC2を紐づける方法について記述します。 本来であればEC2やRDSの作成の際に紐付けお行うのですが、 その工程を飛ばしてしまい、後から紐づける方法について記述いたします。 今回は自分自身のセキュリティの都合上、スクショはなく文字のみでご案内させていただきます。 AWSやEC2、RDSについては下記の記事でまとめているので詳しく知りたい方は、 ぜひ参照してください EC2とRDSの紐付け方法 ●EC2のセキュリティグループをコピーする AWSのEC2を開き、紐付けたいインスタンスを選択します。 そしてメニューの中の「セキュリティ」を選択すると、セキュリティグループが表示されます。 こちらのコピーをしてください。 ●RDSで接続を許可する ①RDSの左のメニューか「データベース」を選択 ②紐付けたいデータベースを選択し、真ん中のメニュー「接続とセキュリティ」を選択 ③右端の「VPC セキュリティグループ」を選択し、画面が切り替わると「インバウンドルール」を押す ④「Edit inbound rules」が右端にあるのでそちらを選択し、インバウンドルールの編集を行う ⑤左下の「ルールを追加」を選択し、データベースのタイプを選択 ⑥ソースを「カスタム」に選択し、ここに先程のEC2のセキュリティグループを貼り付けるとOKです! 終わりに 今回は文字のみのご案内となり、非常にわかりにくい方も多かったかもしれません?‍♂️ ご不明点はお気軽にコメントいただけると幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Workshops攻略記:AppSync Immersion Day(+VTLへの愚痴)

はじめに AWSのWorkshop(ハンズオン)がまとまったサイト「AWS Workshops」使ってますか? 今回はAppsyncが4時間ほどで学べる「Appsync Immersion Day」を試してみました。英語に抵抗感ある方向けに、布教がてらのレポを書きます。 概要(ネタバレ注意) 各章立てと、概要を。※準備、片付けは割愛。 Lab 1. Hands-on with AppSync マネジメントコンソールでAppsyncを作成し、GUI上のエディタでスキーマ定義、クエリ実行。 CDKでAppsyncを作成し、GUI上のエディタでスキーマ定義、クエリ実行。 サンプルアプリを立ち上げ、データを投入する実践的な動きを試せます。 Lab 2. Security スキーマ・リゾルバを編集して、APIキーでの認証 → Cognitoユーザープールでの認証に置き換え。 複数認証として、APIキー+Cognitoの設定。 試せませんでしたが、「AWS_IAM」「OPENID_CONNECT」も選択できる様子。 AWS WAFによるIPアドレス制御の設定 Lab 3. Common Design Patterns Direct Lambda Resolversによるリゾルバの置き換え VTLファイルで定義する必要のあるリゾルバが、Lambda(≒好きな言語)で記述できる! Pipeline Resolverの構築 ≒リゾルバの多段設定 Lab 4. Operations and Management X-Ray&CloudWatch Logsを有効化し、モニタリングを試せます。 ※ボリュームはちょっと少な目。 4時間ほどのボリュームで、完全に理解した!とまでは行かずとも、サービスの雰囲気が掴めてくるのが良いですね。 GraphQLを実案件で採用したことがないので、blackbeltを一読したけど何も分からん、な人向きです。 英語分からんな場合でも、キャプチャやコマンドが多いので、Chrome翻訳で補完できる範囲かと思われます。というより私はそうでした。 ハマったところ サンプルアプリのConfig(src/aws-exports.js)設定時の、相対パスが違う。 もう一か所あったので、私が作業ディレクトリを読み違えていたのかも。 # NG cat << EOF > src/aws-exports.js const awsmobile = { aws_appsync_graphqlEndpoint: '`jq -r .AppsyncWorkshopStack.GraphQLAPIURL ../appsync-workshop/output.json `', aws_appsync_apiKey: '`jq -r .AppsyncWorkshopStack.GraphQLAPIKEY ../appsync-workshop/output.json`', aws_appsync_authenticationType: 'API_KEY', } export default awsmobile EOF # OK(../→ ../../) cat << EOF > src/aws-exports.js const awsmobile = { aws_appsync_graphqlEndpoint: '`jq -r .AppsyncWorkshopStack.GraphQLAPIURL ../../appsync-workshop/output.json `', aws_appsync_apiKey: '`jq -r .AppsyncWorkshopStack.GraphQLAPIKEY ../../appsync-workshop/output.json`', aws_appsync_authenticationType: 'API_KEY', } export default awsmobile EOF サンプルアプリがうまく立ち上がらない エラーメッセージ抜粋 > application@0.1.0 start /home/ec2-user/environment/appsync-workshop/application > react-scripts start There might be a problem with the project dependency tree. It is likely not a bug in Create React App, but something you need to fix locally. The react-scripts package provided by Create React App requires a dependency: "jest": "26.6.0" ~~ 以下略 ~~ appsync-workshop/application 自体のnpmパッケージをいくら見てもわからん。。。と思いきや、一段上の階層のCDK実行環境 (appsync-workshop)のnode_modulesが悪さしてることが判明。 エラーメッセージの通りにjestのバージョンを指定したら直った。※以下npm全然分からん人向け 該当のnode_modulesディレクトリを削除 package.jsonを以下のように書き換え { "name": "appsync-workshop", "version": "0.1.0", "bin": { "appsync-workshop": "bin/appsync-workshop.js" }, "scripts": { "build": "echo \"The build step is not required when using JavaScript!\" && exit 0", "cdk": "cdk", "test": "jest" }, "devDependencies": { "@aws-cdk/assert": "1.86.0", "aws-cdk": "1.86.0", "jest": "26.6.0" }, "dependencies": { "@aws-cdk/aws-appsync": "1.86.0", "@aws-cdk/aws-cognito": "1.86.0", "@aws-cdk/aws-dynamodb": "1.86.0", "@aws-cdk/aws-lambda": "1.86.0", "@aws-cdk/aws-wafv2": "1.86.0", "@aws-cdk/core": "1.86.0" } } "npm i"コマンドを実行することで、node_modulesを再設定される。 Lab2の”Fine-Grained Access Control”設定以降から"listDataPoints"的なクエリが通らなくなる。 後続のWAFの章でのクエリはうまくいくので、何らかの認証設定回り、リゾルバ周りの設定もれorコピペミスにも思えるのですが、時間切れでそのまま走り切ることに。 成功した方はコメントお願いします!もう一回流す気力ないので CDKで作成したCognitoユーザプールのサインアップは認証コード受け取れるメアドの用意が必要なので、適当なのを入れてもダメな点に注意。 登録先のユーザプールは自分管理、かつハンズオン後は削除するのでgmail登録する分にはそこまでシビアに扱わずとも良さそう。 おまけ この章は、Workshopを試した結果生まれたポエムです。 AppSync(というかResolver周り)への所感 大量のRESTへのクエリを、手前でプロキシ的に管理する仕組みは確かに便利そう。 スキーマは良い。リゾルバの設定がVTL(Velocity Template Language)なの辛すぎないか。 ノーコードと言い張るには無理がある。API Gateway設定の辛い記憶が…… ここやここにサンプルはあるが、独特のつらみがありそう。 公式のコミュニティはしっかり見ておきたい。 Direct Lambda Resolversで置き換えられるのが救いっちゃ救いなのだが、Lambda採用による課金が複雑になる&スロットリング管理が必要なのがペインポイントになりそう。 せめてJSで書かせて……とGitHubのissuesを覗くと、計画はあった様子。過去形なのは、この記事を書く一週間ほど前に「やっぱダメかも(意訳)」とコメントを見つけたので……望み薄か…… 想定されるアンチパターン 「APIはインフラ担当だし、AWSなんだからインフラ屋に振ろう!という軽い感覚で分業すると失敗する予感。 この感覚が養えただけで本Workshopを試した意義がある。 VTLファイルは単なるConfというより、簡易スクリプトなのでテスト戦略が試されそう。仮にチームのDev/Opsが分業されているとして、感覚的にはDev側が、少なくともResolverまで責任を持たないとダメそう。具体的には、下のような動きはかなりお勧めしません…… とりあえず正常系だけ通る大量のVTLをOps側に押し付ける。 簡単なスクリプトなら大丈夫だろ!と、数行~数十行のbashしか書いたことないメンバをAppSync担当にする。 そもそもVTLでResolver書くのAppSyncだけなので、ここでしかノウハウが得られなさそう。少なくともapolloはJSっぽいし。参考:Resolvers 私一人の愚痴でないことが、この画像でわかってちょっと安心 まとめ 後半に謎の愚痴が入りましたが、GraphQLバリバリやりたいぜ!開発者はもちろん、チームで採用の機運がありそうで気になってるインフラエンジニアにもおススメなWorkshopなので、一度は入門してみることをおススメします。それでは。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DatabricksにおけるAWS PrivateLinkの有効化

Enable AWS PrivateLink | Databricks on AWS [2021/8/30時点]の翻訳です。 Databricksクイックスタートガイドのコンテンツです。 プレビュー この機能はパブリックプレビューです。 この記事では、ユーザーとお使いのDatabricksワークスペース間、Databricksワークスペースのインフラストラクチャにおおけるデータプレーンのクラスターとコントロールプレーンのコアサービス間で、プライベート接続を有効化するために、どのようにAWS PrivateLinkを活用するのかを説明します。 重要! この記事では、Databricksプラットフォームにおけるコンピュートレイヤーであるデータプレーンという単語に言及します。この記事の文脈においてデータプレーンは、お使いのAWSアカウントににおけるクラシックなデータプレーンを指します。一方、サーバーレスSQLエンドポイント(パブリックプレビュー)をサポートするサーバーレスデータプレーンは、DatabricksのAWSアカウントで動作します。詳細は、サーバーレスコンピュートを参照ください。 概要 AWS PrivateLinkを用いることで、公衆ネットワークを経由することなしに、AWS VPCやオンプレミスのネットワークからAWSサービスへのプライベート接続を実現することができます。E2バージョンプラットフォーム上のDatabricksワークスペースでは、以下の2つのタイプのPrivateLink接続をサポートしています。 フロントエンド(ユーザーからワークスペース): フロントエンドPrivateLink接続によって、ユーザーはVPCインタフェースエンドポイント経由でDatabricksのwebアプリケーション、REST API、Databricks Connect APIに接続することができます。 バックエンド(データプレーンからコントロールプレーン): 顧客管理VPCのDatabricksランタイムクラスター(データプレーン)は、DatabricksクラウドアカウントにあるDatabricksワークスペースのコアサービス(コントロールプレーン)に接続します。クラスターは2つの目的でコントロールプレーンに接続します:REST APIとセキュアクラスター接続のリレーです。このタイプのPrivateLinkでは、二つの異なるサービスを使用するため、二つの異なるVPCインタフェースエンドポイントが必要となります。 フロントエンド、バックエンドPrivateLink接続の両方を実装することもできますし、どちらか一方のみを実装することもできます。この記事では、どちらか一方、あるいは両方のPrivateLink接続タイプの設定方法を説明します。フロントエンドとバックエンドの接続に対してPrivateLinkの両方を実装した場合には、任意でワークスペースへのプライベート接続を強制することができ、この場合にはDatabricksは公衆ネットワーク経由の接続を拒否します。どちらかの接続タイプを実装しない場合には、この要件を強制することはできません。 PrivateLink接続を有効化するには、Databricks設定オブジェクトを作成し、既存の設定オブジェクトに新たなフィールドを追加する必要があります。そして、オブジェクトの登録、更新にはAccount APIを使用します。 セキュアクラスター接続を使用しているE2バージョンプラットフォーム上の既存のワークスペース、あるいは新規のワークスペースにPrivateLinkを設定することができます。 注意 このリリースでは、既存ワークスペース設定を更新するためにPrivateLinkを設定する際、Account APIを使用することができません。手順についてはDatabricks担当にお問い合わせください。 以下の表では、重要な用語を説明しています。 用語 説明 AWS PrivateLink 公衆ネットワークを経由せずに、AWS VPCやオンプレミスネットワークからAWSサービスにプライベート接続するためのAWSテクノロジーです。 AWS VPCエンドポイントサービス AWS VPCエンドポイントサービスは、PrivateLinkによって提供されるサービスです。それぞれのDatabricksのコントロールプレーン(多くの場合リージョンあたり一つ)はPrivateLinkのために2つのAWS VPCエンドポイントを公開しています。ワークスペースVPCエンドポイントサービスは、DatabricksフロントエンドPrivateLink接続、REST APIに対するDatabricksバックエンドPrivateLink接続の両方に適用されます。また、セキュアクラスター接続のリレーのために別のVPCエンドポイントサービスを公開します。 AWS VPCエンドポイント AWS VPCインタフェースエンドポイントによって、お使いのVPCとAWS PrivateLinkによって提供されるVPCエンドポイント間のプライベート接続が可能となります。AWS VPCインタフェースエンドポイントを作成し、Account APIを用いてDatabricksに登録する必要があります。VPCエンドポイントを登録することで、VPCエンドポイントを参照するDatabricks固有のオブジェクトを作成します。ステップ3: Account APIを用いてVPCエンドポイントを登録するを参照ください。 Databricksネットワーク設定 顧客管理VPCに関する重要な情報を記述するDatabricksオブジェクトです。PrivateLink接続(フロントエンド、あるいはバックエンド)を実装した際には、お使いのワークスペースでは顧客管理VPCを使用する必要があります。バックエンドのみをサポートするPrivateLinkにおいては、バックエンド接続のためのVPCエンドポイントを識別するための追加のプロパティが必要となります。ステップ7: Account APIを用いてPrivateLink設定によるワークスペースを作成、更新するをご覧ください。 Databricksプライベートアクセス設定オブジェクト ワークスペースのPrivateLink接続を記述するDatabricksオブジェクトです。Account APIを用いてプライベートアクセス設定オブジェクトを作成し、ワークスペースの作成、更新を行う際に、そのIDをワークスペース設定にリンクします。このオブジェクトにおける重要な設定では、ワークスペースは公衆ネットワークからの接続を許可、拒否するかを指定します。公衆ネットワークからの接続を拒否したい場合には、フロントエンド、バックエンドの両方のPrivateLink接続を実装する必要があります。 Databricksワークスペース設定オブジェクト ワークスペースを記述するDatabricksオブジェクトです。Account APIを用いてワークスペースを作成します。このオブジェクトにはDatabricksパーソナルアクセス設定オブジェクトのIDが含まれる必要があります。 以下の図では、典型的な実装におけるネットワークフローを示しています。 前提条件 Databricksアカウント: DatabricksアカウントはE2バージョンプラットフォームである必要があります。 DatabricksアカウントはEnterprise pricing tierである必要があります。 DatabricksアカウントIDが必要です。アカウントIDはアカウントコンソールで確認できます。 Databricksワークスペース: お使いのワークスペースは、E2バージョンのプラットフォームをサポートしているAWSリージョンにある必要があります。 (フロントエンド接続であったとしても)Databricksワークスペースでは、PrivateLink接続を追加するために顧客管理VPCを使用する必要があります。Databricks管理のVPCにある既存ワークスペースをアップデートすることはできず、顧客管理VPCに切り替える必要があることに注意してください。 バックエンドのPrivateLink接続を実装する際には、Databricksワークスペースはセキュアクラスター接続を使用している必要がありますが、これはE2バージョンのプラットフォームで作成された新規ワークスペースではデフォルトとなっています。しかし、セキュアクラスター接続を使用していない既存ワークスペースにバックエンドPrivateLinkを追加する際には、Databricks担当にコンタクトいただきガイドを受けてください。 ネットワークアーキテクチャ: オンプレミスのネットワークからワークスペースに対して、フロントエンドのPrivateLinkを実装する際には、PrivateLinkを設定する前に、Direct ConnectあるいはVPNを用いて当該ネットワークからAWSネットワークに対してプライベート接続を追加する必要があります。 AWSアカウント権限: あなたがPrivateLinkを設定するのであれば、Databricksワークスペースを配備し、ワークスペースのための新規VPCエンドポイントを配備するのに必要なAWS権限を持っている必要があります。 ステップ1: お使いのアカウントでPrivateLinkを有効化するためにDatabricks担当者にコンタクトを取る PrivateLinkのサポートはデフォルトでは有効化されていません。お使いのDatabricksアカウント、Databricksのエンドポイントサービスに対するVPCエンドポイントを設定するための適切なAWSアカウントに対する有効化をDatabricks担当に連絡する必要があります。 Databricks担当に、DatabricksワークスペースのためのVPCエンドポイントを作成しようとしているAWSアカウントIDとリージョンをお伝えください。Databricks VPCエンドポイントをホストするための複数のAWSアカウントをお持ちの場合には、全てに対してPrivateLinkのアクセスをリクエストしてください。Databricks担当にコンタクトする前に、全てのAWSアカウントIDをお持ちであることを確認してください。 以下の設定ステップを進める前に、お使いのアカウントでPrivateLinkの有効化をリクエストする必要があります。 ステップ2: AWSコンソールを用いてVPCエンドポイントを作成する 上のステップPrivateLinkが有効化されたAWSアカウントに属するVPCで、Databrikcsエンドポイントに対するVPCエンドポイントを作成する必要があります。VPCエンドポイントは、お使いのワークスペースのフロントエンド、バックエンドインタフェースに接続します。 VPCエンドポイントの要件 フロントエンド接続のネットワーク要件 フロントエンド接続は、webアプリケーション、REST API、Databricks Connectに対する、ユーザーからワークスペースのフロントエンドインタフェースへの接続です。 VPCとサブネットに関しては、ユーザー環境から到達可能なAWS VPCとサブネットを使用してください。お使いのAWS Direct Connectを遮断する通過用(バスチオン)VPC、あるいはVPNゲートウェイ接続、あるいは、このような通過用(バスチオン)VPCからルーティング可能なものを使用してください。 エンドポイントに対して新たにセキュリティグループを作成してください。セキュリティグループでは、ソースネットワークとエンドポイントサブネット両方に対して、双方向のHTTPS(443ポート)を許可する必要があります。 バックエンド接続のネットワーク要件 バックエンド接続は、データプレーン(クラスターノード)からワークスペースのバックエンドインタフェースへの接続となります。バックエンド接続においては、データプレーンが以下のコントロールプレーンのサービスの両方に接続するために、2つの異なるVPCエンドポイントがあることを理解することが重要です。 Databricks REST API Databricksのセキュアクラスター接続のリレー バックエンドのREST API VPCエンドポイントに対しては以下の作業が必要となります。 お使いのワークスペースのメインの顧客管理VPCでVPCエンドポイントをホストします。 ワークスペースで使用されていない別のサブネットでVPCエンドポイントを作成します。REST API向けのVPCエンドポイントと、セキュアクラスター接続のリレーのためのVPCエンドポイントとの間で、このサブネットを共有します。ワークスペースのサブネットにアタッチされているルートテーブルとは異なる、別のルートテーブルをお使いのサブネットにアタッチします。VPCエンドポイントのサブネットに対するルートテーブルには、ローカルVPCに対する単一のデフォルトルートのみが必要となります。 PrivateLinkのフロントエンド接続を有効化する際には、枠スペースのサブネットからネットワーク接続できる限り、フロントエンドVPCエンドポイントを再利用することができます。そうでない場合には、ワークスペースのVPCから同じVPCエンドポイントサービスに対する新たなVPCエンドポイントを作成します。 エンドポイント用に、ワークスペースのサブネットとエンドポイントのサブネット間での双方向アクセスにおいてHTTPS/443とTCP/6666を許可する別のセキュリティグループを作成してください。この設定によって、REST API(ポート443)とセキュアクラスター接続(6666)に対するアクセスをよかし、両方の目的に対してセキュリティグループを容易に共有することができます。 AWS VPCエンドポイントの作成 AWSコンソールでAWS VPCエンドポイントを作成するためには、AWSコンソールにおけるVPCエンドポイントの作成に関するAWSのドキュメントを参照ください。VPCエンドポイントの作成、管理を自動化するためのツールに関しては、AWSのドキュメントCloudFormation: Creating VPC EndpointとAWS CLI: create-vpc-endpointを参照ください。 両方のタイプのVPCエンドポイントに対して、VPCレベルでDNSホスト名とDND解決オプションを有効化してください。ステップ3: Account APIを用いてVPCエンドポイントを登録するで説明するように、DatabricksにVPCエンドポイントを登録した後に、それぞれのVPCエンドポイントのDNS名を有効化してください。それまでは、VPCエンドポイントはpendingAcceptance状態となります。 VPCエンドポイントを複数のVPCエンドポイントで共有することができます。お客様の組織におけるAWSアーキテクチャのベストプラクティス、全てのワークロードにおけるスループット要件に依存しますが、ワークスペースで共有する場合には、全てのワークスペースのサブネットからルーティングできる別のサブネットでバックエンドVPCエンドポイントを作成する必要があります。 ユーザーアクセスによって、VPCからフロントエンドのエンドポイントはスタートしますが、多くの場合、オンプレミスネットワークに接続する通過用(バスチオン)VPCとなります。一般的に、これはワークスペースのデータプレーンVPCとは別のVPCとなります。DatabricksのVPCエンドポイントサービスは、フロントエンド接続とバックエンドのREST API接続両方に対する共有サービスとなり、典型的な実装では、2つの異なるVPCから接続が開始し、それぞれのVPCを起点とする別々のAWS VPCエンドポイントが必要となります。 お使いのリージョンのVPCエンドポイントサービスのドメインとアベイラビリティゾーンを決定するために、以下の表をお使いください。 表にあるVPCエンドポイントサービスはワークスペースVPCエンドポイントサービスであり、フロントエンド接続(webアプリケーション、REST APIのためのユーザー、ワークスペース間の接続)、REST APIに対するバックエンド接続の両方で使われることを理解する必要があります。フロントエンドとバックエンド接続の両方を実装する際には、両方のユースケースで同じワークスペースVPCエンドポイントサービスを使用します。 注意 全てのリージョンに関しては原文を参照ください。 リージョン 設定 ap-northeast-1 ワークスペースVPCエンドポイントサービス: com.amazonaws.vpce.ap-northeast-1.vpce-svc-02691fd610d24fd64バックエンドSCCリレーサービス: com.amazonaws.vpce.ap-northeast-1.vpce-svc-02aa633bda3edbec0エンドポイントサービスのアベイラビリティゾーン: apne1-az4 ステップ3: Account APIを用いてVPCエンドポイントを登録する Account APIを用いて、以前のステップで作成したVPCエンドポイントのVPCエンドポイントIDとリージョンを登録します。このステップでは、以前のステップで作成した全てのAWS VPCエンドポイントに対するDatabricks固有のVPCエンドポイントオブジェクトを作成します。 フロントエンド接続においては、以前のステップで作成したAWS VPCエンドポイントをワークスペースに登録します。 バックエンド接続においては、作成したAWS VPCエンドポイントをセキュアクラスター接続リレーに登録します。REST APIに対するバックエンド接続のために、作成したAWS VPCエンドポイントをワークスペースデータプレーンのVPCに登録します。フロントエンド接続とREST APIに対するバックエンド接続の両方に対して同じVPCエンドポイントを再利用する場合(これには、お使いのワークスペースVPCから到達可能なネットワークからユーザーアクセスが行われる必要があります)には、登録を2回行う必要はありません。 重要! 現状のリリースでは、ワークスペースに対してフロントエンド接続、バックエンドREST API接続に対するDatabricksワークスペースVPCエンドポイントサービスに対するVPCエンドポイントを登録する際、Databricksは当該VPCエンドポイントから、対象のAWSリージョンにおいてお使いのDatabricks全てのアカウントのPrivateLinkが有効化された全てのワークスペースに対して、フロントエンド(webアプリケーション、REST API)アクセスを有効化します。 DatabricksにVPCエンドポイントを登録するには、REST APIエンドポイント/accounts/<account-id>/vpc-endpointsに対して、POSTリクエストを行い、リクエストのボディに以下のフィールドを含めます。 vpc_endpoint_name: DatabricksにおけるVPCエンドポイント設定名 region: AWSリージョン名 aws_vpc_endpoint_id: AWSにおけるVPCエンドポイントID。 vpce-から始まります。 例: Bash curl -X POST -n \ 'https://accounts.cloud.databricks.com/api/2.0/accounts/<account-id>/vpc-endpoints' \ -d '{ "vpc_endpoint_name": "Databricks front-end endpoint", "region": "us-west-2", "aws_vpc_endpoint_id": "<vpce-id>" }' 処理が成功すると、新規VPCエンドポイント設定は、いくつかの中間状態を経てavailable状態に移行します。 レスポンスのJSONにはvpc_endpoint_idフィールドが含まれます。バックエンドのPrivateLink接続を追加するのであれば、この値を保存しておいてください。このIDは、Databricksにおいてこの設定固有のものとなります。このIDは、後のステップ(ステップ6: Account APIを用いて新規ネットワーク設定を作成する)でネットワーク設定を作成する際に必要となります。 すべてのVPCエンドポイントの登録をした後は、ステップ4: AWSコンソールを用いてAWS VPCエンドポイント上でプライベートDNS名を有効化するで説明するように、それぞれのDNS名を有効化する必要があります。 関連するAccount APIのオペレーションには、以下のようなものがあります。 VPCエンドポイントのステータスをチェックする - レスポンスのJSONのstateフィールドにはAWSのステータスが含まれます。 お使いのアカウントの全てのVPCエンドポイントを取得する VPCエンドポイントの登録を解除する ステップ4: AWSコンソールを用いてAWS VPCエンドポイント上でプライベートDNS名を有効化する AWSコンソールを用いて、お使いのVPCエンドポイントのプライベートDNS名を有効化します。 Account APIを用いてVPCエンドポイントを登録し、AWS上のステータスがAvailableになった後で、それぞれのVPCエンドポイントでEnable DNS Nameを選択します。 AWSコンソールで、VPC > Endpointsをクリックします。 作成したVPCエンドポイントを選択します。 上にあるActionsボタンをクリックします。 Modify Private DNS Namesを選択します。この設定がグレー(利用できない)になっている場合には、エンドポイントがまだavailable状態になっていません。 Enable Private DNS Nameを選択し、Modify Private DNS Namesをクリックします。 AWSでVPCエンドポイントのステータスがavailableになるまで待ちます。 VPCエンドポイントのプライベートDNS名の有効化を自動化するためには、以下のAWSのドキュメントを参照ください。 AWS CLI : modify-vpc-endpoint ステップ5: Databricks Account APIを用いてプライベートアクセス設定を作成する プライベートアクセス設定オブジェクトを作成するために、Databricks Account APIを使用します。 プライベートアクセス設定オブジェクトは、以下のシナリオをサポートしています。 フロントエンドVPCエンドポイントのみを実装 バックエンドVPCエンドポイントのみを実装 フロントエンド、バックエンドVPCエンドポイントの両方を実装 オブジェクト定義において、最も重要なフィールドはpublic_access_enabledとなります。 false(デフォルト)、VPCエンドポイントからのみワークスペースにアクセスでき、公衆ネットワークからはアクセスできません。フロントエンド、バックエンドVPCエンドポイントの両方を実装した際にのみfalseを設定できます。公衆ネットワークからのアクセスができないため、ワークスペースに対してIPアクセスリストの機能は利用できないことに注意してください。 true、ワークスペースはVPCエンドポイントからも、公衆ネットワークからもアクセスできます。このような場合、公衆ネットワークからアクセスできるソースネットワークを制限するために、任意でワークスペースに対するIPアクセスリストを設定することができます。 実装のシナリオに応じて設定する値を決定する必要があります。 シナリオ public_access_enabledの値 フロントエンドVPCエンドポイントのみを実装 true バックエンドVPCエンドポイントのみを実装 true フロントエンド、バックエンドVPCエンドポイントの両方を実装 公衆ネットワークからのアクセスを完全に遮断したい場合はfalse 重要! プライベートアクセス設定オブジェクトを作成する前に、注意深く適切なpublic_access_enabledの値を選択してください。プライベートアクセス設定レコードを作成した後に値を編集することはできません。 任意で、以前に設定したワークスペースの既存のプライベートアクセス設定オブジェクトを再利用することができます。この場合、アクセスの挙動はpublic_access_enabledの値に依存し、それぞれのワークスペースで必要となる値は、PrivateLinkアクセスで実装しているエンドポイントで決定されることに注意してください。 プライベートアクセス設定オブジェクトを作成するには、REST APIエンドポイント/accounts/<account-id>/private-access-settingsにPOSTリクエストを行います。リクエストのボディには、以下のプロパティを含める必要があります。 private_access_settings_name: プライベートアクセス設定オブジェクト名 region: AWSリージョン名 public_access_enabled: 公衆ネットワークからのアクセスの有効化を指定。trueの場合には、PrivateLink接続に加え、公衆ネットワークからのアクセスが可能。ご自身の実装で必要な値に関しては上の表を参照ください。 Bash curl -X POST -n \ 'https://accounts.cloud.databricks.com/api/2.0/accounts/<account-id>/private-access-settings' \ -d '{ "private_access_settings_name": "Default PAS for us-west-2", "region": "us-west-2", "public_access_enabled": true, }' レスポンスのJSONにはprivate_access_settings_idフィールドが含まれます。このIDはDatabricks内でこの設定固有のものとなります。ワークスペースを作成する際に必要となりますので、この値を保存しておいてください。 関連するAPIには以下のものがあります。 ID指定でのプライベートアクセス設定オブジェクトの取得 すべてのプライベートアクセス設定オブジェクトの取得 プライベートアクセス設定オブジェクトの削除 ステップ6: Account APIを用いて新規ネットワーク設定を作成する 注意 フロントエンド接続のみを実装する場合にはこのステップをスキップしてください。顧客管理VPCが必要となるため、ネットワーク設定を作成する必要がありますが、フロントエンドのPrivateLink接続のみを実装する際には、このオブジェクトに対してPrivateLinkの設定変更は不要です。 どのようなPrivateLinkを利用する場合でも、顧客管理VPCを使用する必要があります。この機能を利用するには、VPC、サブネット、セキュリティグループのIDを含むネットワーク設定オブジェクトを作成する必要があります。 バックエンドPrivateLinkを利用する際には、PrivateLink固有の追加フィールドをネットワーク設定に含める必要があります。これは、VPCエンドポイントを登録した際に返却されるDatabricks固有のVPCエンドポイントIDを参照するvpc_endpointsフィールドです。ステップ3: Account APIを用いてVPCエンドポイントを登録するを参照ください。 オブジェクトに以下のフィールドを追加してください。 rest_api: 一つの要素のみを含むJSON配列を指定します。配列には、ステップ3: Account APIを用いてVPCエンドポイントを登録するで登録したバックエンドREST API VPCエンドポイントに対するDatabricks固有のIDを指定します。 dataplane_relay: 一つの要素のみを含むJSON配列を指定します。配列には、ステップ3: Account APIを用いてVPCエンドポイントを登録するで登録したバックエンドSCC VPCエンドポイントに対するDatabricks固有のIDを指定します。 以下の例では、VPCエンドポイントのIDを参照するネットワーク設定を作成しています。<databricks-vpce-id-for-scc>をセキュアクラスター接続リレーに対するDatabricks固有のVPCエンドポイントIDで置き換えてください。<databricks-vpce-id-for-rest-apis>をREST APIに対応するDatabricks固有のVPCエンドポイントIDで置き換えてください。 Bash curl -X POST -n \ 'https://accounts.cloud.databricks.com/api/2.0/accounts/<account-id>/networks' \ -d '{ "network_name": "Provide name for the Network configuration", "vpc_id": "<aws-vpc-id>", "subnet_ids": [ "<aws-subnet-1-id>", "<aws-subnet-2-id>" ], "security_group_ids": [ "<aws-sg-id>" ], "vpc_endpoints": { "dataplane_relay": [ "<databricks-vpce-id-for-scc>" ], "rest_api": [ "<databricks-vpce-id-for-rest-apis>" ] } }' ステップ7: Account APIを用いてPrivateLink設定によるワークスペースを作成、更新する Account APIを用いて、新規ワークスペースの作成、あるいは更新を行います。ワークスペース設定オブジェクトには、ここまでで作成した他の設定オブジェクトのIDを含めます。ワークスペース設定オブジェクトに対しては、PrivateLink向けに2つの変更を行います。 private_access_settings_idに作成したプライベートアクセス設定オブジェクトのIDを指定します。ステップ5: Databricks Account APIを用いてプライベートアクセス設定を作成するを参照ください。 ステップ6: Account APIを用いて新規ネットワーク設定を作成するで作成した新規ネットワーク設定のIDを指定します。 ワークスペースの作成 PrivateLinkを用いた新規ワークスペースを作成するには、ワークスペース作成API(POST /accounts/{account_id}/workspaces)をコールします。 網羅的な操作手順については、Account APIによる新規ワークスペースの作成を参照ください。 例はこちらとなります。 Bash curl -X POST -n \ 'https://accounts.cloud.databricks.com/api/2.0/accounts/<databricks-account-id>/workspaces' \ -d '{ "workspace_name": "my-company-example", "deployment_name": "my-company-example", "aws_region": "us-west-2", "credentials_id": "<aws-credentials-id>", "storage_configuration_id": "<databricks-storage-config-id>", "network_id": "<databricks-network-id>", "managed_services_customer_managed_key_id": "<aws-kms-managed-services-key-id>", "storage_customer_managed_key_id": "<aws-kms-notebook-workspace-storage-id>", "private_access_settings_id": "<private-access-settings-id>" }' ワークスペース作成の自動化を支援するツールに関しては、Use automation templates to create a new workspace using the Account APIを参照ください。 ワークスペースの更新 現時点でのリリースでは、Account APIを用いてワークスペース設定のPrivateLinkオブジェクトを更新することができません。手順に関しては、Databricks担当者にお問い合わせください。 ステップ8: 内部DNSを設定する(フロントエンド接続でのみ必要) 注意 バックエンド接続のみを実装する際には、このステップはスキップしてください。 フロントエンド接続において、ユーザーが内部あるいはカスタムDNSのスコープにあるオンプレミスのネットワークからDatabricksワークスペースにアクセスする際には、ワークスペースのURLがVPCエンドポイントのプライベートIPにマッピングされるように、ワークスペースの作成、更新後に以下の設定を行う必要があります。 ワークスペースURLがフロントエンドVPCエンドポイントにマッピングされるように内部DNSを設定してください。 デフォルトでは、AWSリージョンus-east-1におけるフロントエンドVPCエンドポイントに対するワークスペースのパブリックDNSマッピングは、以下のようになっています。 test-workspace.cloud.databricks.comを nvirginia.privatelink.cloud.databricks.comにマッピング nvirginia.privatelink.cloud.databricks.comを nvirginia.cloud.databricks.comにマッピング nvirginia.cloud.databricks.comをAWSのパブリックIPにマッピング お使いのフロントエンドVPCエンドポイントと同じVPCの仮想マシンからワークスペースにアクセスする際、DNSマッピングは以下のようになります。 test-workspace.cloud.databricks.comをnvirginia.privatelink.cloud.databricks.comにマッピング nvirginia.privatelink.cloud.databricks.comをVPCエンドポイントのプライベートIPにマッピング DNS解決をテストするにはnslookupコマンドを使ってください。 オンプレミスネットワークからVPCエンドポイントのプライベートIPにワークスペースURLをマッピングするには、以下のいずれかを行う必要があります。 AmazonDNSを使うために、ワークスペースURLに対する条件付きフォワーディングを設定します。 VPCエンドポイントにマッピングされるオンプレミス・内部DNSにおけるワークスペースURLのAレコードを作成します。 他の同様なPrivateLinkを活用したサービスへのアクセスを有効化するのに必要なステップを完了します。 お使いの内部DNSでAレコードを作成することで、ワークスペースURLを直接フロントエンドVPCエンドポイントプライベートIPにマッピングすることを選択することもできます。この場合、DNSマッピングは以下のようになります。 test-workspace.cloud.databricks.comをVPCエンドポイントプライベートIPにマッピング お使いの内部DNS設定を変更した後は、Databricksワークスペースのwebアプリケーション、REST APIにアクセスできるようになっているはずです。 これがお客様のネットワークアーキテクチャにどのように適用されるのかといったご質問に関しては、Databricks担当にお問い合わせください。 ステップ9: 他のAWSサービスのためのVPCエンドポイントを追加する(オプションだが推奨) PrivateLinkバックエンド接続を実装するのに必要なセキュアクラスター接続を使用しているのであれば、任意でお使いのデータプレーンVPCに他のVPCエンドポイントを追加でき、Databricksが使用するAWSのネイティブサービス(S3、STS、Kinesis)、お使いのワークスペースで必要となる他のリソースにアクセスする際、クラスターがVPCエンドポイントを利用することができます。 S3 VPCゲートウェイエンドポイント: ワークスペースのサブネットにアタッチされるルートテーブルにのみアタッチしてください。推奨されるバックエンドVPCエンドポイントに対する自身のルートテーブルを持つ別のサブネットを使用しているのであれば、S3 VPCエンドポイントを特定のルートテーブルにアタッチする必要はありません。 STS VPCインタフェースエンドポイント: 全てのワークスペースのサブネットで作成し、ワークスペースのセキュリティグループにアタッチしてください。バックエンドVPCエンドポイントのサブネットでは作成しないでください。 Kinesis VPCインタフェースエンドポイント: STS VPCインタフェースのエンドポイントと同じように、全てのワークスペースのサブネットでKinesis VPCインタフェースエンドポイントを作成し、ワークスペースのセキュリティグループにアタッチしてください。 完全なアウトバウンドロックダウンのデプロイメントを必要としない場合でも、適切なトラフィックを公衆ネットワークではなくAWSネットワークバックボーンを経由するように、上記のいずれかのエンドポイントを追加することができます。 他のアウトバウンド接続をサポートしないように、ワークスペースVPCをロックダウンしたい場合、AWSがRDSに対するJDBCトラフィックのPrivateLinkをサポートしていないため、ワークスペースからDatabricks管理のメタストア(RDSベースのHiveメタストア)にはアクセスできなくなります。一つの選択肢としては、リージョンごとにDatabricksが提供するメタストアのFQDNあるいはIPを、イグレスファイアウォール、インターネットゲートウェイに対するパブリックルートテーブル、NATゲートウェイをホスティングするパブリックサブネットに対するネットワークACLのいずれかに対して設定するというものです。この場合、Databricksが提供するメタストアへのトラフィックは、、公衆ネットワークを経由することになります。しかし、Databricks管理のメタストアに公衆ネットワーク経由でアクセスしたくない場合には、以下のいずれかの選択肢となります。 お使いのVPCに外部メタストアをデプロイします。External Apache Hive metastoreを参照ください。 メタストアとしてAWS Glueを使用できます。GlueはPrivateLinkをサポートしています。Use AWS Glue Data Catalog as the metastore for Databricks Runtimeを参照ください。 pypi(python)やCRAN(R)などの公開ライブラリリポジトリにアクセスしたいと考えるかもしれません。これらにアクセスするには、完全なアウトバウンドロックダウンモードでのデプロイモードを再考するか、自身のアーキテクチャで必要なリポジトリにアクセスできるようにイグレスファイアウォールを使用してください。ご自身のデプロイメントの全体アーキテクチャは、ご自身の要件に依存します。質問がある場合には、Databricks担当にお問い合わせください。 AWSを用いてAWS VPCエンドポイントを作成する際には、AWSのドキュメントcreating VPC endpoints in the AWS consoleを参照ください。 VPCエンドポイントの作成、管理の自動化に役立つツールには以下のようなものがあります。 Databricks Terraform providerに関するドキュメント Terraformのリソース databricks_mws_vpc_endpoint AWSのドキュメント CloudFormation: Creating VPC Endpoint AWSのドキュメント AWS CLI : create-vpc-endpoint Databricks 無料トライアル Databricks 無料トライアル
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】Django環境をEC2+ELB+RDSで構築してみた

はじめに Djangoの実行環境を構築する機会があったため、せっかくなので現在学習を進めているAWSに絡めて構築してみようと思います。 ※AWSがメインの内容となるため、Django実行環境についてはさらっと記載します。 ※Django実行環境については独学となるのでご容赦ください... 構成図 以下のような構成を目指します。 EC2インスタンス OS:Amazon Linux 2 Webサーバ(リバースプロキシ):Nginx APサーバ(WSGIサーバ):Gunicorn データベース(RDS):MySQL Nginx+GunicornをEC2インスタンスにインストール データベースとしてマルチAZ構成のRDS(MySQL)を使用 EC2起動時にユーザーデータによって、Django環境構築を実行 EC2インスタンスの起動テンプレートを作成し、Auto Scaling グループを作成 ELBを作成し、EC2インスタンスへのトラフィックをロードバランシング CloudWatchでAuto Scaling グループのCPU使用率に基づいたアラームを設定し、CPU使用率によってAuto Scaling グループ内のEC2インスタンスの数が増減するように設定 ※リソースはすべて東京リージョンに作成します。 作業環境 macOS Big Sur バージョン 11.4 ネットワーク/セキュリティグループ 作成 まずはAWS内でのネットワーク環境とELB、EC2インスタンス、RDSそれぞれにどういった通信を許可するかを定義するためのセキュリティグループを作成していきます。 ネットワークを作成 AWS上にネットワークを作成します。 作成するものは以下となります。 VPC x 1 インターネットゲートウェイ x 1 パブリックサブネット x 2 (同VPC、別AZ) プライベートサブネット x 2 (同VPC、別AZ) 基本的な作成・設定手順は以前作成した以下の記事をご参照ください。 セキュリティグループを作成 ELB、EC2インスタンス、RDSに適用するセキュリティグループを作成します。 ELB用セキュリティグループはインバウンドで以下を許可するように作成します。 アクセス元となるSourceの値は「マイIP」で設定します。 HTTP EC2インスタンス用セキュリティグループはインバウンドで以下を許可するように作成します。 アクセス元となるSourceの値はELB用セキュリティグループとします。 HTTP RDS用セキュリティグループはインバウンドで以下を許可するように作成します。 アクセス元となるSourceの値はEC2インスタンス用セキュリティグループとします。 MYSQL/Aurora 構成図 ネットワーク作成後の構成図は以下の通りです。 RDS 作成 RDS(Relational Database Service)とは AWSがPaaSサービスとして提供するリレーショナルデータベース MySQL、MariaDB、PostgreSQL、Oracle、SQL Serverなどの様々なデータベースエンジンに対応 他のAZ(アベイラビリティーゾーン)にレプリカを配置するマルチAZ構成を取ることができる サブネットグループを作成 RDSをVPC・サブネットに配置するためには事前にサブネットグループの作成が必要となります。 AWS Management Consoleにログインし、RDSのダッシュボードを開きます。 画面左ツリーの[サブネットグループ]をクリックし、サブネットグループ一覧を表示します。 画面右上の[DB サブネットグループを作成]をクリックします。 名前にサブネットグループの名前、VPCにサブネットグループに使用するサブネットが存在するVPCを選択します。 アベイラビリティーゾーンで追加するサブネットを含むAZを選択後、サブネットで追加するサブネットを選択し、[作成]をクリックします。 【補足】 2つ以上のアベイラビリティーゾーンでサブネットグループを構成しないと以下のエラーが発生し、作成できません。 データベースを作成 サブネットグループが作成できたら、画面左ツリーの[データベース]をクリックし、データベース一覧を表示します。 一覧画面右上の[データベースの作成]をクリックします。 データベース作成ウィザードが開きます。 今回は以下の設定でデータベースを作成します。 設定が入力・選択できたら、[データベースの作成]をクリックします。 項目 値 データベース作成方法 標準作成 エンジンのタイプ MySQL テンプレート 無料利用枠 DBインスタンス識別子 django-database マスターユーザー名 admin マスターパスワード 任意のパスワード DBインスタンスクラス db.t2.micro VPC 作成したVPC サブネットグループ 作成したサブネットグループ パブリックアクセス なし VPC セキュリティグループ 作成したRDS用セキュリティグループ アベイラビリティーゾーン ap-northeast-1a データベースポート 3306 最初のデータベース名 mysite ※記載のない項目はデフォルトとなります。 RDSが作成できたらEC2インスタンス側のDjango実行環境を構築していきます。 今回は起動テンプレート作成時にユーザーデータを使用することでDjango環境構築を行います。 まずはそのための事前準備を行っていきます。 Djangoプロジェクト 作成 基本的に以下のDjangoチュートリアルに沿って作成を進めます。 今回のAWS構成に関連する部分、チュートリアル通りに作業が進まなかった部分等を以下で補足していきます。 データベース設定を変更 settings.py内のデータベース設定を変更する際に作成したRDSの設定情報を入力します。 settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'mysite', 'USER': 'admin', 'PASSWORD': '<adminのパスワード>', 'HOST': '<RDSのエンドポイント>', 'PORT': '3306', } } 開発用サーバー起動 開発用サーバーを起動する際に以下のエラーが発生しました。 ec2 $ python3 manage.py runserver 以下のエラーが発生 django.core.exceptions.ImproperlyConfigured: Error loading MySQLdb module. Did you install mysqlclient? どうやらmysqlclietモジュールをインストールする必要がある様子。 pip3でインストールを実行してみます。 ec2 $ pip3 install mysqlclient 以下のエラーが発生 ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output. 以下を参考にしたところ、mysqlclientモジュールをインストールすることができました。 改めて開発用サーバーを起動します。 警告は出ていますが、開発用サーバーを起動することができました。 ec2 $ python3 manage.py runserver Watching for file changes with StatReloader Performing system checks... System check identified no issues (0 silenced). You have 18 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. July 18, 2021 - 08:22:04 Django version 3.2.5, using settings 'mysite.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C. ブラウザからアプリケーションにアクセス 外部からアクセスできるように開発用サーバーを起動する際にオプションを指定します。 ec2 $ python3 manage.py runserver 0:8000 EC2インスタンスのパブリックIPアドレスの8000番ポートに対してアクセスしましたが、デフォルト設定のままだと以下のエラーが発生します。 ec2 Invalid HTTP_HOST header: '<EC2インスタンスのパブリックIPアドレス>:8000'. You may need to add '<EC2インスタンスのパブリックIPアドレス>' to ALLOWED_HOSTS. settings.pyの「ALLOWED_HOSTS」の値を変更することでアクセスできるようになります。 settings.py ALLOWED_HOSTS = ["*"] GitHubにDjangoプロジェクトをアップロード チュートリアルに従って作成したDjangoプロジェクトをGitHubにアップロードしておきます。 後ほど、ユーザーデータの処理の中でDjangoプロジェクトをGitHubからダウンロードします。 Djangoパラメータファイル 作成 RDSへの接続情報などをDjangoプロジェクト内のsettings.pyに記載していましたが、別ファイル「settings_secret.py」を作成しそちらに情報を転記しました。 settings_secret.py は .gitignore を使用してGit管理対象外とします。 settings_secret.py SECRET_KEY = '<プロジェクト作成時に生成されるキー>' DB_NAME = 'mysite' DB_USER = 'admin' DB_PASS = '<adminのパスワード>' DB_HOST = '<RDSのエンドポイント>' DB_PORT = '3306' settings.py from .settings_secret import * DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': DB_NAME, 'USER': DB_USER, 'PASSWORD': DB_PASS, 'HOST': DB_HOST, 'PORT': DB_PORT, } } settings.py 設定変更 settings.py 内の「DEBUG」の値を変更し、「STATIC_ROOT」を追加します。 settings.py DEBUG = False STATIC_ROOT = '/usr/share/nginx/html/static' Gunicornサービスユニットファイル 作成 Gunicornをsystemdに登録し、サービスとして扱うためにサービスユニットファイルを作成します。 Gunicornサービスユニットファイルを作成 以下を参考にGunicornをsystemdに登録するためのサービスユニットファイルを作成します。 gunicorn.service [Unit] Description=gunicorn daemon After=network.target [Service] Type=notify PIDFile=/run/gunicorn/pid User=ec2-user Group=ec2-user RuntimeDirectory=gunicorn WorkingDirectory=/home/ec2-user/mysite ExecStart=/usr/local/bin/gunicorn --workers 3 --bind=localhost:8000 mysite.wsgi ExecReload=/bin/kill -s HUP $MAINPID ExecStop=/bin/kill -s TERM $MAINPID KillMode=mixed TimeoutStopSec=5 PrivateTmp=true Restart=always [Install] WantedBy=multi-user.target Nginx設定ファイル 作成 NginxとGunicornを連携させるためにNginxの設定を編集します。 デフォルトでは /etc/nginx/conf.d に設定ファイル(.conf)を配置すると、設定として読み込まれます。 ※/etc/nginx/nginx.conf のhttpディレクティブ内の「include /etc/nginx/conf.d/*.conf」によって読み込まれます。 以下を参考に設定ファイルを作成します。 mysite.conf server { listen 80; server_name ec2-publicip; → ユーザーデータ処理時に実際のパブリックIPアドレスに置換 location /static {  alias /usr/share/nginx/html/static; } location / { proxy_pass http://localhost:8000; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; } } Git認証情報ファイル 作成 ユーザーデータ処理の中でGitHubリポジトリからDjangoプロジェクトをダウンロードするにあたって、処理中に対話型でユーザー・パスワードを求められないようにします。 実行ユーザーのホームディレクトリに「.netrc」というファイルを作成することで、ファイルに記述した認証情報を使用することができます。 .netrc machine github.com login <GitHubのユーザー名> password <ユーザーのパスワード or Personal Access Token> ※Personal Access Tokenの作成は以下を参照しました。 S3バケット 作成・ファイル配置 S3バケットを作成し、ここまで作成した以下のファイルを配置します。 Djangoパラメータ用ファイル(settings_secret.py) Gunicornサービスファイル(gunicorn.service) Nginx設定ファイル(mysite.conf) Git認証情報ファイル(.netrc) IAMロール 作成 EC2インスタンスからS3へのアクセスを許可するためのIAMロールを作成します。 以下の設定でIAMロールを作成します。 項目 値 信頼されたエンティティ EC2 ポリシー AmazonS3FullAccess ロール名 任意の名前 これでDjango環境構築の事前準備は完了です。 起動テンプレート 作成 Auto Scaling グループで使用する起動テンプレートを作成していきます。 EC2画面で[テンプレートの起動]から起動テンプレート一覧を開きます。 画面右上の[起動テンプレートを作成]をクリックします。 起動テンプレートの作成画面となります。 今回は以下の設定で起動テンプレートを作成します。 設定が入力・選択できたら、[起動テンプレートを作成]をクリックします。 項目 値 起動テンプレート名 django-launch-template テンプレートバージョンの説明 1.0.0 Auto Scaling のガイダンス 有効 AMI Amazon Linux 2 (x86) インスタンスタイプ t2.micro キーペア 任意のキーペア ネットワーキングプラットフォーム Virtual Private Cloud (VPC) ストレージ 変更なし ネットワークインターフェイス 1つ追加 ※詳細は下に記載 IAM インスタンスプロフィール S3アクセス用IAMロール ユーザーデータ ※詳細は下に記載 【ネットワークインターフェイス】 項目 値 デバイスインデックス 0 ネットワークインターフェイス New interface セキュリティグループ EC2用セキュリティグループ パブリック IP の自動割り当て 有効化 インスタンスタイプ t2.micro キーペア 任意のキーペア ネットワーキングプラットフォーム Virtual Private Cloud (VPC) ストレージ 変更なし 【ユーザーデータ】 ユーザーデータにより必要パッケージのインストールやファイル・ディレクトリの権限設定、GitHub・S3から必要なファイルのダウンロードなどの処理を行っていきます。 ※GitHubリポジトリのURL、S3バケットの名前は置き換えて使用してください。 userdata #!/bin/bash #必要なパッケージ・モジュールをインストール yum -y update yum -y install python3 git python3-devel mysql mysql-devel gcc pip3 install django pip3 install mysqlclient pip3 install gunicorn amazon-linux-extras install nginx1 #Nginxサービスの自動起動設定・開始 systemctl enable nginx systemctl start nginx #Staicファイル用ディレクトリの作成 mkdir /usr/share/nginx/html/static #git clone用認証情報ファイルのダウンロード aws s3 cp s3://<S3のバケット名>/.netrc /root/.netrc #DjangoプロジェクトをGitHubから取得 git clone <GitHubリポジトリのURL> /home/ec2-user/mysite #Djangoプロジェクトの所有者をec2-userに変更 chown -R ec2-user:ec2-user /home/ec2-user/mysite #setting.py内で使用するパスワード等を記述した外部ファイルをダウンロード aws s3 cp s3://<S3のバケット名>/settings_secret.py /home/ec2-user/mysite/mysite/settings_secret.py #上記ファイルの所有者をec2-userに変更 chown ec2-user:ec2-user /home/ec2-user/mysite/mysite/settings_secret.py #Gunicornのサービスユニットファイルをダウンロード aws s3 cp s3://<S3のバケット名>/gunicorn.service /etc/systemd/system/gunicorn.service #Gunicornサービスユニットファイルの権限を変更 chmod 644 /etc/systemd/system/gunicorn.service #Gunicornサービスを自動起動設定・開始 systemctl enable gunicorn.service systemctl start gunicorn.service #Nginx設定ファイルをダウンロード・パブリックIPアドレスを置換 aws s3 cp s3://<S3のバケット名>/mysite.conf /etc/nginx/conf.d/mysite.conf publicip=`curl http://169.254.169.254/latest/meta-data/public-ipv4` sed -i -e "s/ec2-publicip/$publicip/g" /etc/nginx/conf.d/mysite.conf #Nginxサービスを再起動 systemctl restart nginx #StaticファイルをSTATIC_ROOTに記述したパスに配置 cd /home/ec2-user/mysite python3 manage.py collectstatic #Staticファイル用ディレクトリの所有者をec2-userに変更 chown -R ec2-user:ec2-user /usr/share/nginx/html/static Auto Scaling グループ 作成 次に Auto Scaling グループを作成します。 EC2管理画面で[Auto Scaling グループ]からAuto Scaling グループ画面を開きます。 [Auto Scaling グループの作成]をクリックします。 Auto Scaling グループ名で名前を入力後、起動テンプレートで作成した起動テンプレートを選択し、[次へ]をクリックします。 VPCで事前に作成したVPCを選択後、サブネットで2つのパブリックサブネットを選択し、[次へ]をクリックします。 ロードバランシングで[新しいロードバランサーにアタッチ]を選択後、以下の設定を行い、[次へ]をクリックします。 項目 値 ロードバランサーのタイプ Application Load Balancer ロードバランサーの名前 django-LB ロードバランサーのスキーム Internet-facing VPC 作成したVPC アベイラビリティーゾーンとサブネット 作成したパブリックサブネット プロトコル HTTP ポート 80 デフォルトルーティング (転送先) ターゲットグループを作成する 新しいターゲットグループ名 django-TG ヘルスチェックのタイプ EC2, ELB ヘルスチェックの猶予期間 300 秒 今回はEC2インスタンスは最小2台、最大4台となるようにスケーリングするため、グループサイズでは以下のように設定します。 設定が完了したら[次へ]をクリックします。 項目 値 希望する容量 2 最小キャパシティ 2 最大キャパシティ 4 通知は今回はなしで進めますので、そのまま[次へ]をクリックします。 任意でタグを設定し、[次へ]をクリックします。 設定内容を確認し、[Auto Scaling グループを作成]をクリックします。 ターゲットグループ/ロードバランサー 設定変更 このままだとターゲットグループのヘルスチェックに失敗する状態となります。 原因は以下の2つです。 ヘルスチェックのパスからのレスポンスコードが「200」でない ロードバランサーからEC2インスタンスへのHTTPが許可されていない それぞれの問題をクリアするために対応を実施していきます。 ヘルスチェック設定を変更 Auto Scaling グループ作成ウィザード内でターゲットグループを作成したことにより、ヘルスチェック設定がデフォルト設定となっています。 今回の構成に合わせて設定を変更していきます。 EC2管理画面で[ターゲットグループ]からターゲットグループ一覧を開きます。 先ほど作成されたターゲットグループを選択し、画面下部で[Health checks]タブを選択後、[Edit]をクリックします。 以下のように設定を変更し、[Save changes]をクリックします。 ※この画面では表記が英語になっています。 項目 値 Health check protocol HTTP Health check path /polls/ Port 80 Healty threshold 2 Unhealth threshold 2 Timeout 5 seconds Interval 10 seconds Success code 200 ロードバランサーのセキュティグループを変更 Auto Scaling グループ作成ウィザード内でロードバランサーを作成したことにより、VPCデフォルトのセキュリティグループが適用されています。 こちらも今回の構成に合わせて設定を変更していきます。 EC2管理画面で[ロードバランサー]からロードバランサー一覧を開きます。 先ほど作成されたロードバランサーを選択し、[アクション]-[セキュリティグループの編集]をクリックします。 VPCデフォルトのセキュリティグループの選択を外し、ELB用セキュリティグループを選択後、[保存]をクリックします。 ヘルスチェック/アクセスを確認 ターゲットグループのヘルスチェック状態を確認します。 ターゲットグループ一覧でターゲットグループを選択し、画面下部の[Targets]タブをクリックします。 登録済みのターゲットの一覧が表示されるので、すべてのEC2インスタンスでHealth statusが「healty」になっていることを確認します。 ヘルスチェックが成功しているのを確認できたら、ブラウザから Django アプリケーションへのアクセスを確認します。 ブラウザで「http://ロードバランサーのDNS名/polls」にアクセスし、Djangoアプリケーションが表示されることを確認します。 構成図 Auto Scaling グループ作成後の構成図は以下の通りです。 スケーリングポリシーを設定していないので、EC2インスタンスは希望する容量で指定した2台となっています。 RDS マルチAZ構成 RDSをマルチAZ構成とします。 RDS管理画面で[データベース]からデータベース一覧を開きます。 マルチAZ構成とするデータベースを選択し、画面上部の[変更]をクリックします。 変更画面が開くので、マルチ AZ 配置で「スタンバイインスタンスを作成する (本稼働環境向けに推奨)」を選択し、[続行]をクリックします。 変更を適用するタイミングで「すぐに適用」を選択し、[DB インスタンスを変更]をクリックします。 ステータスが「利用可能」、マルチ AZが「あり」に変更されることを確認します。 構成図 RDSマルチAZ構成後の構成図は以下の通りです。 CloudWatch 監視設定 EC2インスタンスのCPU使用率に応じて、Auto Scaling グループ内のEC2インスタンス数を増減するための監視設定を行っていきます。 アラームを作成 まずは Auto Scaling グループのCPU使用率が高い場合のアラームを作成します。 CloudWatch管理画面で[アラーム]からアラーム一覧を開きます。 [アラームの作成]をクリックします。 [メトリクスの選択]をクリックし、[EC2]-[Auto Scaling グループ別]を選択します。 先ほど作成した Auto Scaling グループのメトリクス[CPUUtilization]を選択し、[メトリクスの選択]をクリックします。 条件を設定し、[次へ]をクリックします。 項目 値 しきい値の種類 静的 CPUUtilization が次の時... より大きい ...よりも 70 アラームを実行するデータポイント 1 / 1 欠落データの処理 欠落データを不正 (しきい値を超えている) として処理 通知は今回はなしで進めますので、通知内の[削除]をクリック後、[次へ]をクリックします。 アラーム名で名前を入力後、[次へ]をクリックします。 設定内容を確認し、[アラームの作成]をクリックします。 次に Auto Scaling グループのCPU使用率が低い場合のアラームを作成します。 以下の条件設定を使用して、同様の手順でアラームを作成します。 項目 値 しきい値の種類 静的 CPUUtilization が次の時... より低い ...よりも 30 アラームを実行するデータポイント 1 / 1 欠落データの処理 欠落データを見つかりませんとして処理 スケーリングポリシーを設定 Auto Scaling グループ にスケーリングポリシーを設定していきます。 Auto Scaling グループ一覧画面で作成した Auto Scaling グループを選択し、画面下部の[自動スケーリング]タブを選択します。 [Create dynamic scaling policy]をクリックします。 まずはCPU高負荷時のスケーリングポリシーを作成します。 以下の設定を行い、[作成]をクリックします。 項目 値 ポリシータイプ シンプルなスケーリング スケーリングポリシー名 CPU_add CloudWatch アラーム 作成したCPU高負荷時のアラーム アクションを実行 追加: 1 キャパシティユニット 別のスケーリングアクティビティを許可するまでの秒数 30 秒 次にCPU低負荷時のスケーリングポリシーを作成します。 以下の設定で作成します。 項目 値 ポリシータイプ シンプルなスケーリング スケーリングポリシー名 CPU_remove CloudWatch アラーム 作成したCPU低負荷時のアラーム アクションを実行 削除: 1 キャパシティユニット 別のスケーリングアクティビティを許可するまでの秒数 30 秒 構成図 スケーリングポリシー設定後の構成図は以下の通りです。 負荷テスト EC2インスタンスに負荷をかけて、自動スケーリングをテストします。 EC2インスタンスにSSH接続します。 SSH接続については以下を参照してください。 以下のコマンドを使用してCPU負荷をかけていきます。 ※並列で負荷をかけるために複数回コマンドを実行しています。 ec2 $ yes >> /dev/null & [1] 8457 $ yes >> /dev/null & [2] 8459 $ yes >> /dev/null & [3] 8460 $ yes >> /dev/null & [4] 8461 top コマンドで確認すると、CPU使用率が100%近くになっていることが確認できます。 ec2 top - 08:24:23 up 17 min, 1 user, load average: 2.68, 0.81, 0.31 Tasks: 94 total, 6 running, 52 sleeping, 0 stopped, 0 zombie %Cpu(s): 99.7 us, 0.3 sy, 0.0 ni, 0.0 id, 0.0 wa, 0.0 hi, 0.0 si, 0.0 st KiB Mem : 1006892 total, 258452 free, 227764 used, 520676 buff/cache KiB Swap: 0 total, 0 free, 0 used. 625988 avail Mem PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 8457 ec2-user 20 0 114636 792 728 R 24.9 0.1 0:19.60 yes 8459 ec2-user 20 0 114636 756 692 R 24.9 0.1 0:17.22 yes 8461 ec2-user 20 0 114636 780 716 R 24.9 0.1 0:16.21 yes 8460 ec2-user 20 0 114636 764 700 R 24.6 0.1 0:16.70 yes 同様の手順でもう一台のEC2インスタンスにも負荷をかけ、アラームが発生するまで待機します。 CloudWatch管理画面で[アラーム]からアラーム一覧を開きます。 CPU高負荷時のアラームの状態が「アラーム状態」になっていることを確認したら、EC2インスタンス画面を開き、EC2インスタンスが自動的に増えていることを確認します。 Auto Scaling グループのアクティビティ履歴からも自動スケールアウトが実行されたログを確認することができます。 自動スケールアウトの動作が確認できたら、EC2インスタンスのCPU負荷を下げ、自動スケールインの動作を確認します。 負荷をかけているEC2インスタンスにSSH接続し、負荷をかけているコマンドのプロセスを停止します。 ec2 $ kill 8457 $ kill 8459 $ kill 8460 $ kill 8461 ※プロセス番号が連続している場合は kill {8459..8461} のようにまとめて指定することが可能です。 CloudWatch管理画面で[アラーム]からアラーム一覧を開きます。 CPU低負荷時のアラームの状態が「アラーム状態」になっていることを確認したら、Auto Scaling グループのアクティビティ履歴を確認します。 スケールインの場合には即時EC2インスタンスが削除されると利用しているユーザーが困るため、Drainingと呼ばれる期間が設けられています。 Auto Scaling グループのアクティビティ履歴からもステータスがDrainingであるログを確認することができます。 Drainingの期間はターゲットグループによって設定されています。 ※デフォルトは5分 Drainingの期間が経過すると、EC2インスタンスが削除されたことがアクティビティ履歴で確認できます。 また、EC2インスタンス画面でもEC2インスタンスが削除されていることを確認します。 あとがき この記事は AWS CloudTech の課題として作成しました。 動画やハンズオン等で学習を進めることができるので、AWS初学者にはおすすめです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

localstackでredshiftとs3を立ててみる

どういう記事か とりあえずlocalstackの環境構築を行なってredshiftとs3を使ってみた記事です。 参考 前提 m1 mac docker-compose docker-compose.yml version: '3' services: # LocalStack localstack: image: localstack/localstack:latest environment: - SERVICES=s3,redshift # 使いたいAWSサービスカンマ区切りで設定する - DEFAULT_REGION=ap-northeast-1 # リージョンを設定 - DATA_DIR=/tmp/localstack/data # データ保存するディレクトリ volumes: - ./localstack:/tmp/localstack # ローカルディレクトリをデータ保存ディレクトリへマウント ports: - 4566:4566 # サービスへのアクセスポートは4566 プロフィールの変更 $ aws configure list Name Value Type Location ---- ----- ---- -------- profile <not set> None None access_key **************** shared-credentials-file secret_key **************** shared-credentials-file region ap-northeast-1 config-file ~/.aws/config 以下のようにプロフィールを設定します。 $ aws configure --profile localstack AWS Access Key ID [None]: dummy AWS Secret Access Key [None]: dummy Default region name [None]: ap-northeast-1 Default output format [None]: これで .awsの中のcredentialsも変わっているはずです。 $ cat credentials [localstack] aws_access_key_id = dummy aws_secret_access_key = dummy バケットを作ってみる $ aws s3 mb s3://sample-bucket --endpoint-url=http://localhost:4566 --profile localstack make_bucket: sample-bucket $ aws s3 ls --endpoint-url=http://localhost:4566 --profile localstack 2021-09-07 14:50:18 sample-bucket redshiftのクラスター作成 $ aws redshift create-cluster --cluster-identifier example --node-type dw.hs1.xlarge --master-username user --master-user-password cmpass --profile localstack --endpoint-url=http://localhost:4566 // レスポンス { "Cluster": { "ClusterIdentifier": "example", "NodeType": "dw.hs1.xlarge", "ClusterStatus": "creating", "MasterUsername": "user", "DBName": "dev", "Endpoint": { "Address": "example.cg034hpkmmjt.ap-northeast-1.redshift.amazonaws.com", "Port": 5439 }, "ClusterCreateTime": "2021-09-07T05:58:36.131000+00:00", "AutomatedSnapshotRetentionPeriod": 1, "ClusterSecurityGroups": [ { "ClusterSecurityGroupName": "Default", "Status": "active" } ], "VpcSecurityGroups": [], "ClusterParameterGroups": [ { "ParameterGroupName": "default.redshift-1.0", "ParameterApplyStatus": "in-sync" } ], "ClusterSubnetGroupName": "", "AvailabilityZone": "ap-northeast-1a", "PreferredMaintenanceWindow": "Mon:03:00-Mon:03:30", "PendingModifiedValues": {}, "ClusterVersion": "1.0", "AllowVersionUpgrade": true, "NumberOfNodes": 1, "PubliclyAccessible": false, "Encrypted": false, "Tags": [], "KmsKeyId": "", "EnhancedVpcRouting": false, ....... redshiftのクラスターのリストを取得したいとき aws redshift describe-clusters --profile localstack --endpoint-url=http://localhost:4566 これでredshiftのクラスター周りは処理が可能です!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

作成したEC2にWebサーバー(nginx)を立ち上げるまで。

作成したEC2にWebサーバー(nginx)を立ち上げるまでの簡易的なメモです。 1.EC2にログイン SSHでログイン。 # XX.XXX.XX.XXXはパブリックアドレスを入力。 ssh -i ~/.ssh/xxx.pem ec2-user@XX.XXX.XX.XXX 2.EC2にnginxをインストール amazon-linux-extrasを使ってnginxをインストールします。 # マニュアルの確認 man amazon-linux-extras # 利用可能なリストを表示 amazon-linux-extras # 管理者権限でインストール sudo amazon-linux-extras install nginx1 # nginxを起動 sudo systemctl start nginx.status # nginxのステータスを確認(起動されていることを確認) systemctl status nginx.status # ポート80番の状態を確認 sudo lsof -i:80 3.Webサーバーの動作確認 EC2セキュリティグループのインバウンドルールを編集。ルールを追加。 今回は確認用なので以下に編集。保存。 タイプ→「HTTP」  ソース→「マイ IP」 ブラウザにてアドレスバーにパブリックアドレスを入力し、nginxの表示を確認。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Athenaで基礎からしっかり入門 分析SQL(Pythonコード付き) #4

今まで複雑なデータ操作・分析などはPythonでやっており、SQLは普通のアプリ開発程度のライトなものしか触って来なかったのですが、やはり分析用の長いSQLなども書けた方がやりとり等で便利・・・という印象なので、復習も兼ねて記事にしておきます。 また、SQLに加えて検算も兼ねてPythonやPandasなどを使ったコードもSQLと併記していきます(Pythonで書くとどういった記述が該当するのかの比較用として使います)。 ※長くなるのでいくつかの記事に分割します。本記事は4記事目となります。 特記事項 本記事では配列関係を良く扱っていきます。その都合、Pandasなどでapplyメソッドなどを絡めた形で触れている節もあればビルトインでの記述のみしている節もあります(本記事からPandas色が少し薄くなります)。 他のシリーズ記事 ※過去の記事で既に触れたものは本記事では触れません。 #1: 用語の説明・SELECT、WHERE、ORDER BY、LIMIT、AS、DISTINCT、基本的な集計関係(COUNTやAVGなど)、Athenaのパーティション、型、CAST、JOIN、UNION(INTERSECTなど含む)など。 #2: GROUP BY・HAVING・サブクエリ・CASE・COALESCE・NULLIF・LEAST・GREATEST・四則演算などの基本的な計算・日付と日時の各操作など。 #3: 文字列操作全般・正規表現関係など。 この記事で触れること コメント関係 配列操作全般 ラムダ式 環境の準備 以下の#1の記事でS3へのAthena用のデータの配置やテーブルのCREATE文などのGitHubに公開しているものに関しての情報を記載していますのでそちらをご参照ください。 SQLを読みやすくする: コメント SQL中にコメントを残すことで、なんのSQLなのか、どういった処理なのかを各所に追加していくことができます。引き継ぎや他人との共有時などにSQLを読む負担が減ります。 Athena(Presto)では以下のようなコメントの書き方がサポートされています。 インラインコメント --以降の文字列はインラインコメントとして扱われ、SQLには影響しません。 以下のように「ログインログを10行読み込みます。」といったような説明を入れてもそのままSQLを実行できます。 -- ログインログを10行読み込みます。 SELECT * FROM athena_workshop.login LIMIT 10; 行中にコメントを追加したい場合には、行末であれば追加することができます。 SELECT * FROM athena_workshop.login LIMIT 10 -- ログインログを10行読み込みます。 ただし、セミコロンが付いている行に関してはその後にSQL関係を入力することができなくなり、コメントなども書かれているとエラーになります。Athenaではセミコロンが無くても動くので、そういった場合はセミコロンを消してしまってもいいかもしれません。 SELECT * FROM athena_workshop.login LIMIT 10; -- ログインログを10行読み込みます。 複数行のコメントを追加したい場合には複数の--の記述を各行にするか、もしくは後の節で触れるブロックコメントを利用します。 -- ログインログを10行読み込みます。 -- 日付は2021-01-01を対象とします。 SELECT * FROM athena_workshop.login WHERE dt = '2021-01-01' ブロックコメント /**/の記述のアスタリスクの間に挟んだコメントはブロックコメント(複数行に対応できるコメント)になります。例えば以下のように書きます。 /* ログインログを10行読み込みます。 */ SELECT * FROM athena_workshop.login LIMIT 10 アスタリスクの間は自由に改行などを含めることができます。毎行--などの記述をしなくて済みますし、コメント部分のテキストのコピーなどをしたい場合などにも--などが紛れ込まないので複数行の場合にはこちらの方が快適なケースがあります。 /* ログインログを10行読み込みます。 日付は2021-01-01を対象とします。 */ SELECT * FROM athena_workshop.login WHERE dt = '2021-01-01' 配列の操作 この節以降では配列(Array)操作について色々触れていきます。 配列の固定値の扱い方 配列の固定値を作りたい場合にはARRAY [配列の値]という書き方をすることでカラムを設定することができます。例えば1, 2という値を格納した配列と3, 4という値を格納した配列の2つを得たい場合には以下のようにSQLを実行することで配列の固定値を取得することができます。 SELECT ARRAY [1, 2] AS arr_1, ARRAY [3, 4] AS arr_2 特定のテーブルと一緒に使った場合、各行に対してこの固定値が設定されます。 SELECT ARRAY [1, 2] AS arr_1, ARRAY [3, 4] AS arr_2, user_id FROM athena_workshop.login LIMIT 10 Pythonでの書き方 Pandasを初期化する際にカラムに配列などが含まれていればそのまま配列の値を持ったデータフレームが作れるのですが、途中から配列の固定値のカラムを追加する場合、固定のスカラ値などを直接代入するケースと同じようにはストレートにいきません。配列の各値を各行に設定する・・・みたいな挙動になるため、リストなどを代入しようとするとデータフレームとリストの行数が一致していないとエラーになります。 例えば以下のように[1, 2]という配列の固定値をarr_1というカラムに設定しようとすると行数の不一致によるエラーになります。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['arr_1'] = [1, 2] ValueError: Length of values (2) does not match length of index (24453) 色々対策として書き方はあると思いますが、1つの対応としてリスト内包表記などを使って行数を揃えてしまう・・・というのがシンプルかもしれません。この辺は探せばもっとシンプルなPandasのインターフェイスがあるような気がしないでもないです。 import pandas as pd import numpy as np df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/login/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['arr_1'] = [[1, 2] for _ in range(len(df))] print(df[['user_id', 'arr_1']].head()) user_id arr_1 0 8590 [1, 2] 1 568 [1, 2] 2 17543 [1, 2] 3 15924 [1, 2] 4 5243 [1, 2] ||記号による配列の連結 ※以降の節ではuser_weapon_and_armor_idsというテーブルを扱っていきます。基本的なカラムに加えてweapon_idsとarmor_idsという2つのカラムを持っており、それぞれ所持武器と所持防具のマスタIDを想定した整数の配列を格納させています。 以下のようなデータになっています(armor_idsカラムの方がスクショ内に収まらないのでスクロールして2枚スクショを貼っています)。分かりやすいようにarmor_idsの方が大きな整数値のIDを設定しています。 SELECT * FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; 配列同士は||記号で連結することができます。 例えば2つの配列のカラムを連結したい・・・といった場合には以下のように書きます(concatenated_idsという1つのカラムに変換しています)。 SELECT weapon_ids || armor_ids AS concatenated_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; 後半がarmor_idsカラムの大きな値になっていることが確認できます。 また、固定値との連結もできます。 以下のSQLでは[1, 2, 3]という配列とweapon_idsというカラムの配列を連結しています。連結されて結果の配列の先頭が1, 2, 3からスタートしていることが確認できます。 SELECT ARRAY [1, 2, 3] || weapon_ids AS concatenated_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; Pythonでの書き方 Pandasは基本的に各インターフェイスが行列前提のものが多く、リストの値を含んだデータフレームのための連結関係のインターフェイスは無い・・・?気がしますので、applyなりループを回したりで対応する必要があるかなという所感です(しっかり探せばその辺のインターフェイスもあるかもしれません)。 その辺は別の記事で書いたり、もしくはループで回したときのパフォーマンス関係をご共有いただいたりしているので必要に応じてそちらもご確認ください。 以下はループで回す場合の書き方例です。 from typing import List import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_weapon_and_armor_ids/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') weapon_ids: List[List[int]] = df['weapon_ids'].tolist() armor_ids: List[List[int]] = df['armor_ids'].tolist() concatenated_ids: List[List[int]] = [] for single_weapon_ids, single_armor_ids in zip(weapon_ids, armor_ids): single_weapon_ids.extend(single_armor_ids) concatenated_ids.append(single_weapon_ids) df['concatenated_ids'] = concatenated_ids print(df['concatenated_ids'].head()) print(df.at[0, 'concatenated_ids']) Name: concatenated_ids, dtype: object 0 [240, 65, 157, 257, 51, 266, 211, 208, 257, 35... 1 [45, 123, 75, 271, 87, 298, 156, 243, 80, 57, ... 2 [85, 30, 163, 151, 142, 93, 229, 258, 101, 6, ... 3 [7, 128, 288, 157, 277, 279, 257, 202, 230, 97... 4 [20, 216, 19, 7, 189, 283, 131, 63, 48, 281, 5... [240, 65, 157, 257, 51, 266, 211, 208, 257, 35, 187, 94, 264, 267, 18, 248, 70, 185, 156, 293, 96, 65, 143, 195, 115, 117, 107, 160, 299, 249, 85, 39, 259, 12, 207, 92, 290, 297, 152, 121, 57, 134, 182, 38, 60, 138, 244, 215, 223, 79, 208, 97, 87, 38, 160, 80, 170, 240, 206, 225, 155, 210, 245, 36, 55, 117, 14, 112, 215, 252, 236, 232, 284, ... 450, 343, 386, 425, 492, 456, 332, 475, 413, 452, 469, 477, 386, 452, 346, 444, 392, 418, 311, 466, 470, 442, 368, 319, 378, 476, 368, 491, 498, 497, 433, 337, 359, 346, 398, 446, 411, 333, 494, 500, 397, 402, 355, 317, 392, 498, 407, 499, 431, 466, 441, 458, 444, 338, 350, 440, 500, 392, 339, 447, 343, 326, 422, 345, 351] CONCAT関数による配列の連結 CONCAT関数でも||の記号を使った場合と同じように複数の配列を連結することができます。各引数に連結したい配列を指定します。2つ以上の配列も指定することができます。 以下のSQLでは[1, 2, 3]という固定値の配列とweapon_idsカラム、armor_idsカラムと3つの配列を連結しています。 SELECT CONCAT(ARRAY [1, 2, 3], weapon_ids, armor_ids) AS concatenated_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; []記号による配列の特定の位置の値へのアクセス 配列のカラムや固定値などに対して[]の括弧と整数を指定することで、配列の特定の位置の値にアクセスすることができます(添え字 / インデックスによるアクセス)。 なお、Athena(Presto)では配列のインデックスは1スタートとなります。jsやPythonのように0スタートではなく、Juliaなどのような挙動になるので注意が必要です。 SELECT weapon_ids[1] AS first_value, weapon_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; 以下のSQLでは配列の先頭の値を取得しています。 配列の固定値を使った場合にもインデックスの指定が効くようです。以下のSQLでは1, 2, 3という固定値の配列の2番目の値を取得しています。 SELECT ARRAY [1, 2, 3][2] AS second_value FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; なお、Pythonなどのように負のインデックスを指定して配列の末尾からアクセスするといったことはできません。エラーになります。 SELECT weapon_ids[-1] AS first_value, weapon_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10; ELEMENT_AT関数による配列の特定の位置の値へのアクセス []の括弧による特定の位置の配列の値へのアクセスと同じようなことがELEMENT_AT関数で出来ます。 第一引数に対象のカラムもしくは固定値、第二引数に値の位置のインデックスを指定します。こちらも値の位置のインデックスは1からスタートします。 SELECT ELEMENT_AT(weapon_ids, 1) AS first_value, weapon_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10 特筆すべき点として、[]の括弧では使えなかった負のインデックスの指定がPythonと同様に使えます。負の値を指定した場合は配列の末尾からカウントされます。例えば-1を指定すれば配列の最後の値が対象となり、-2を指定すれば配列の最後から二番目の値の位置となります。 SELECT ELEMENT_AT(weapon_ids, -1) AS last_value, weapon_ids FROM athena_workshop.user_weapon_and_armor_ids LIMIT 10 Pythonでの書き方 ※Pandasが基本的に行列を扱うという性質上、ここからしばらくはデータフレーム内の配列への処理としてPythonビルトインの処理とapplyメソッドなどを絡めた書き方が多くなると思います。 特定の配列のインデックスの値を取得する関数を定義し、データフレームのapplyメソッドでそちらの関数を指定することで同じようなことができます。applyメソッドには任意のキーワード引数で固定値も渡すことができるので、今回はindexという引数を設定しています。 from typing import List, Optional import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/user_weapon_and_armor_ids/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') def element_at(list_value: List[int], index: int) -> Optional[int]: """ 指定されたインデックス位置の値をリストから取得する。 Parameters ---------- list_value : list of int 対象のリスト。 index : int 対象のインデックス。 Returns ------- value : int or None 指定されたインデックス位置の値。インデックス範囲外の場合には Noneが返却される。 """ if index >= len(list_value): return None value: int = list_value[index] return value df['first_value'] = df['weapon_ids'].apply(element_at, index=0) print(df[['first_value', 'weapon_ids']].head()) first_value weapon_ids 0 240 [240, 65, 157, 257, 51, 266, 211, 208, 257, 35... 1 45 [45, 123, 75, 271, 87, 298, 156, 243, 80, 57, ... 2 85 [85, 30, 163, 151, 142, 93, 229, 258, 101, 6, ... 3 7 [7, 128, 288, 157, 277, 279, 257, 202, 230, 97... 4 20 [20, 216, 19, 7, 189, 283, 131, 63, 48, 281, 5... 重複を除いた形での2つの配列の連結を行う: ARRAY_UNION ARRAY_UNION関数を使うと2つの配列を重複を除いた形で連結した配列を取得することができます。第一引数に1つ目の配列、第二引数に2つ目の配列を指定します。 例えば以下のSQLでは1, 2, 3という値を含んだ配列と2, 3, 4という値を含んだ配列を指定しています。2と3の部分は重複していますが連結結果は1, 2, 2, 3, 3, 4といった値にはならずに1, 2, 3, 4という配列になります。 SELECT ARRAY_UNION(ARRAY [1, 2, 3], ARRAY [2, 3, 4]) AS union_array Pythonでの書き方 処理自体はapplyもしくはループで回すとして(以下のサンプルではループで回しています)、ビルトインのset関数にリストを渡すと一意な値が取れます。型はlistではなくなるので、setで変換後に再度listに変換しています。[*a, *b]といった記述で二つ以上のリストを連結したコピーを得られるのでそちらをsetの引数に使っています。 from typing import List import pandas as pd df: pd.DataFrame = pd.DataFrame( data=[{'a': [1, 2, 3], 'b': [2, 3, 4]}]) a_list: List[List[int]] = df['a'].tolist() b_list: List[List[int]] = df['b'].tolist() union_list: List[List[int]] = [] for a, b in zip(a_list, b_list): union_list.append(list(set([*a, *b]))) df['union_array'] = union_list print(df) a b union_array 0 [1, 2, 3] [2, 3, 4] [1, 2, 3, 4] 値を繰り返した配列を取得する: REPEAT REPEAT関数は第一引数で指定された値を第二引数に指定された回数分繰り返した配列を取得します。第一引数には色々な型の値を指定できます。第二引数には整数が必要になります。 例えば以下のSQLでは整数の3という値を5回繰り返すSQLを実行しています。 SELECT REPEAT(3, 5) AS repeated_array 整数以外でも、例えば配列の繰り返しなどもできます。以下のSQLでは[1, 2, 3]という配列を3回繰り返しています。結果は[1, 2, 3, 1, 2, 3, 1, 2, 3]という1次元の配列ではなく[[1, 2, 3], [1, 2, 3], [1, 2, 3]]という2次元の配列になります。 SELECT REPEAT(ARRAY [1, 2, 3], 3) AS repeated_array Pythonでの書き方 ※以降の節ではデータフレームを絡めない形で触れるケースも出てきますが、前節までのapplyやループなどでデータフレームに設定することでデータフレーム側にも同じようなことができます。 リストを乗算するとリストの値を繰り返した値になります。例えば以下のようにすると3の値を5回繰り返したリストになります。 print([3] * 5) [3, 3, 3, 3, 3] SQLのように[[1, 2, 3], [1, 2, 3], [1, 2, 3]]といった二次元配列を得たければ与えるリストも2次元にしておくことで対応ができます。 print([[1, 2, 3]] * 3) [[1, 2, 3], [1, 2, 3], [1, 2, 3]] ARRAY_AVERAGE や ARRAY_SUM などの配列への要約統計量用の関数について Prestoのドキュメントには配列の平均値を計算するARRAY_AVERAGEや合計値を計算するARRAY_SUMなどの関数があるのですが、これらはどうやらAthena上ではまだ利用できないようです。将来のアップデートで反映されるかもとは思いますが、本記事でもそれらの関数は触れずに進めます。 配列の各値の最大値を取る: ARRAY_MAX ※この節以降ではbattle_party_powerというテーブルを説明のために使っていきます。クエスト(バトル)ごとのパーティーメンバーのIDと装備やレベルに応じた戦闘力を想定した値を格納しています。基本的なカラムに加えて以下のようなカラムを持っています。 quest_id -> 対象のクエストのIDの整数。 user_ids -> パーティーメンバーのユーザーIDの整数の配列。 powers -> パーティーメンバーの戦闘力の整数の配列。 以下のようなデータになっています。 SELECT * FROM athena_workshop.battle_party_power LIMIT 10; ARRAY_MAX関数を使うと配列の値の中での最大値を取得することができます。 以下のSQLではパーティーの戦闘力の中で一番戦闘力が高い値を取得しています。 SELECT powers, ARRAY_MAX(powers) AS party_max_power FROM athena_workshop.battle_party_power LIMIT 10; Pythonでの書き方 シンプルにビルトインのmax関数をapplyメソッドに指定するだけで対応ができます。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/battle_party_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['max_power'] = df['powers'].apply(max) print(df[['max_power', 'powers']].head()) max_power powers 0 41013 [41013, 34983] 1 30252 [26151, 29020, 30252] 2 10269 [10269, 8786, 8179, 6633] 3 18561 [18561] 4 50078 [34067, 50078, 37768] 配列の各値の最小値を取る: ARRAY_MIN ARRAY_MIN関数を使うと配列内の最小値を取得することができます。 以下のSQLではパーティーの戦闘力の中で一番戦闘力が低い値を取得しています。 SELECT powers, ARRAY_MIN(powers) AS party_min_power FROM athena_workshop.battle_party_power LIMIT 10; Pythonでの書き方 こちらも最大値算出の時と同じように、ビルトインのmin関数をapplyメソッドの引数に指定するだけで対応ができます。 import pandas as pd df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/battle_party_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['min_power'] = df['powers'].apply(min) print(df[['min_power', 'powers']].head()) min_power powers 0 34983 [41013, 34983] 1 26151 [26151, 29020, 30252] 2 6633 [10269, 8786, 8179, 6633] 3 18561 [18561] 4 34067 [34067, 50078, 37768] 配列の特定範囲の値の配列を取得する: SEQUENCE SEQUENCE関数は特定の値の範囲の配列を取得することができます。連番生成などに使います。 第一引数には開始値となる数値(後述しますが、日付や日時でも対応ができます)、第二引数に終了値となる数値を指定します。返却値は配列となります。 例えば第一引数に1、第二引数に5を指定すると[1, 2, 3, 4, 5]という配列が返ってきます。 SELECT SEQUENCE(1, 5) AS sequence_value 終了値が開始値よりも小さい場合は減算処理が実行される形で連番が生成されます。例えば開始値に3、終了値に-3を指定すれば[3, 2, 1, 0, -1, -2, -3]という配列結果が得られます。 SELECT SEQUENCE(3, -3) AS sequence_value また、第三引数に整数を指定した場合連番のステップを設定することができます。例えは2ずつ増加させるといった制御になります。 SELECT SEQUENCE(1, 5, 2) AS sequence_value Pythonでの書き方 ビルトインのrange関数を使うと連番の値を取得できます。主にループ処理などで使われますが、list関数を通してリストにキャストすれば連番の配列としても使えます。注意点として、第二引数に指定した値の1つ手前の値が最終値となるため、最終値に対して1大きい値を第二引数に指定する必要があります(Pythonだとインデックスが0からスタートするので、range(0, 5)とすれば最終値は4となり件数も5件となる・・・といった挙動になります)。 print(list(range(1, 6))) [1, 2, 3, 4, 5] 第三引数もSQL側と同様にステップの設定となります。 print(list(range(1, 6, 2))) [1, 3, 5] 特定の日付範囲の配列を取得する: SEQUENCE SEQUENCE関数には日付を開始値と終了値に指定することもできます。開始日~終了日の範囲の配列を得られます。ただし結果の配列の値はDATEではなくTIMESTAMPになるようです。 第三引数にはINTERVALで種類(日数や月数など)や数値(日数値など)を指定します。この辺は以前の記事で触れた日時関係の操作のものと同じなのでそちらをご確認ください。 SELECT SEQUENCE(DATE('2021-01-01'), DATE('2021-01-03'), INTERVAL '1' DAY) AS sequence_value なお、第三引数は整数を指定した時とは異なり省略できません(日付を指定したから1日ずつ・・・とはなりません)。省略するとエラーになります。 SELECT SEQUENCE(DATE('2021-01-01'), DATE('2021-01-03')) AS sequence_value Pythonでの書き方 Pandasのdate_range関数を使うと日付の範囲を取れます。 import pandas as pd print(pd.date_range(start='2021-01-01', end='2021-01-03')) DatetimeIndex(['2021-01-01', '2021-01-02', '2021-01-03'], dtype='datetime64[ns]', freq='D') 返却値はDatetimeIndexという特殊な値になっているので、リストの方が良ければシリーズやndarrayなどと同じようにtolistメソッドで変換できます。 import pandas as pd print(pd.date_range(start='2021-01-01', end='2021-01-03').tolist()) [Timestamp('2021-01-01 00:00:00', freq='D'), Timestamp('2021-01-02 00:00:00', freq='D'), Timestamp('2021-01-03 00:00:00', freq='D')] 特定の日時範囲の配列を取得する: SEQUENCE 日時(TIMESTAMP)を第一引数と第二引数に指定する形でもSEQUENCE関数は使えます。使い方と挙動はほぼ日付を指定した時と同じです。 以下のSQLでは10時~13時の間で1時間ごとに4件の配列を取得しています。 SELECT SEQUENCE(CAST('2021-01-01 10:00:00' AS TIMESTAMP), CAST('2021-01-01 13:00:00' AS TIMESTAMP), INTERVAL '1' HOUR) AS sequence_value Pythonでの書き方 日付と同様にdate_range関数を使います。日付の時に加えてfreq引数にHを指定すると1時間単位で値が返ってきます。 import pandas as pd date_range: pd.DatetimeIndex = pd.date_range( start='2021-01-01 10:00:00', end='2021-01-01 13:00:00', freq='H') print(date_range) DatetimeIndex(['2021-01-01 10:00:00', '2021-01-01 11:00:00', '2021-01-01 12:00:00', '2021-01-01 13:00:00'], dtype='datetime64[ns]', freq='H') 配列の特定の位置から特定の件数の値を格納した配列を取得する: SLICE SLICE関数は配列の特定の開始位置から任意の件数分の値を格納した配列を抽出することができます。第一引数には対象の配列もしくは固定値、第二引数には配列の開始位置の整数、第三引数には値の件数を指定します。 第二引数の開始位置は1からスタートします。0ではありません。 対象が存在しない場合(1件の値しかない配列で2番目以降の値を指定した場合など)には結果の行の値は空の配列となります。 以下のSQLでは配列の2番目の位置から最大2件分の値を抽出した配列を取得しています。 SELECT SLICE(powers, 2, 2) AS second_and_third_powers, powers FROM athena_workshop.battle_party_power LIMIT 10; 第二引数の開始位置には負の値も指定できます。Pythonと同じように-1を指定した場合は配列の最後、-2を指定した場合は最後から2番目の位置・・・といった挙動になります。 以下のSQLでは配列の最後から2番目の位置から1件分の値を抽出しています。 SELECT SLICE(powers, -2, 1) AS sliced_power, powers FROM athena_workshop.battle_party_power LIMIT 10; Pythonでの書き方 以下のコードのようにビルトインのリストでスライスの記法が使えるのでそちらをapplyメソッド用の関数で設定して対応しています。SQLと異なりPythonでは開始インデックスが0なのでそちらに合わせてあります。 from typing import List import pandas as pd def get_sliced_list( list_val: List[int], start: int, length: int) -> List[int]: """ 指定されたリストの特定の位置から任意の件数の値を格納したリストを 取得する。 Parameters ---------- list_val : list of int 対象のリスト。 start : int リストの開始位置。 length : int 取得件数。 Returns ------- list_val : list of int スライス処理後のリスト。 """ list_val = list_val[start:start + length] return list_val df: pd.DataFrame = pd.read_json( 'https://github.com/simon-ritchie/athena_workshop/blob/main/workshop/battle_party_power/dt%3D2021-01-01/data.json.gz?raw=true', lines=True, compression='gzip') df['second_and_third_powers'] = df['powers'].apply( get_sliced_list, start=1, length=2) print(df[['second_and_third_powers', 'powers']].head(10)) second_and_third_powers powers 0 [34983] [41013, 34983] 1 [29020, 30252] [26151, 29020, 30252] 2 [8786, 8179] [10269, 8786, 8179, 6633] 3 [] [18561] 4 [50078, 37768] [34067, 50078, 37768] 5 [34217, 22725] [25882, 34217, 22725, 21635] 6 [] [13968] 7 [1205, 2094] [1573, 1205, 2094, 2021] 8 [] [22217] 9 [] [19061] 一意な値のみに変換した配列を取得する: ARRAY_DISTINCT ARRAY_DISTINCT関数を使うと指定されたカラムもしくは固定値の配列の値から、一意(重複の存在しない)配列を取得できます。 SELECT ARRAY_DISTINCT(ARRAY [1, 2, 2, 1, 3, 4, 2]) AS unique_array Pythonでの書き方 ビルトインのリストであれば一度set関数を通すと一意な値になるので、そちらをさらにlist関数を通してリストに戻すと対応ができます。 from typing import List unique_list: List[int] = list(set([1, 2, 2, 1, 3, 4, 2])) print(unique_list) [1, 2, 3, 4] NumPyのndarrayなどであればunique関数が用意されています。 import numpy as np arr: np.ndarray = np.array([1, 2, 2, 1, 3, 4, 2]) arr = np.unique(arr) print(arr) [1 2 3 4] 配列が特定の値を含むかどうかの真偽値を取得する: CONTAINS CONTAINS関数は配列の値の中に特定の値が含まれているかどうかの真偽値を返します。 以下のSQLではパーティーのユーザーIDの配列内に25456のIDが含まれているかどうかの真偽値を取得しています。 SELECT CONTAINS(user_ids, 25456) AS user_id_25456_contains, user_ids FROM athena_workshop.battle_party_power WHERE dt = '2021-01-01' LIMIT 3; Pythonでの書き方 ビルトインのリストなどに対してinのキーワードを使うことで真偽値を取得することができます。 print(25456 in [96607, 14806]) False print(25456 in [82053, 25456, 34646]) True 2つの配列で値の重複が存在するかの真偽値を取得する: ARRAYS_OVERLAP ARRAYS_OVERLAP関数は2つの配列の値で重複している値が存在するかどうかの真偽値を返却します。それぞれの配列内に欠損値が含まれる場合にはtrueとはなりません。もし配列内の値が欠損値のみになっていれば返却値も欠損値(null)になります。 以下のSQLでは1つ目の配列の値が1, 2, 3で2つ目の配列の値が4, 5, 6であり、重複している値は存在しないのでfalseとなります。 SELECT ARRAYS_OVERLAP(ARRAY[1, 2, 3], ARRAY[4, 5, 6]) AS is_overlap 一方で以下のSQLでは1つ目の配列が1, 2, 3、2つ目の配列の値が3, 4, 5となっており3が重複しているのでtrueとなります。 SELECT ARRAYS_OVERLAP(ARRAY[1, 2, 3], ARRAY[3, 4, 5]) AS is_overlap Pythonでの書き方 シンプルにループを使ってinで判定ができます。 from typing import List arr_1: List[int] = [1, 2, 3] arr_2: List[int] = [3, 4, 5] is_overlap: bool = False for value in arr_1: if value in arr_2: is_overlap = True break print(is_overlap) True が、それぞれのリストが巨大になってくると1件ずつリストに対してinを使うのは結構処理時間が気になるようになってきます。そのためもしも対象が巨大なリストの場合には2つ目のリストの方を辞書に変換しておいて、辞書のキーに存在するかどうかをinで判定するようにすると計算時間をぐぐっと抑えられます(メモリは食いますが)。 from typing import Dict, List arr_1: List[int] = [1, 2, 3] arr_2: List[int] = [3, 4, 5] dict_val: Dict[int, None] = {value: None for value in arr_2} is_overlap: bool = False for value in arr_1: if value in dict_val: is_overlap = True break print(is_overlap) True 配列から特定の値を取り除く: ARRAY_REMOVE ARRAY_REMOVE関数は配列から指定された値を取り除いた配列を返却します。第一引数に対象のカラム名もしくは固定値、第二引数に任意の取り除きたい値を指定します。 以下のSQLでは配列から2の値を取り除いているため、結果は[1, 3]という配列になります。 SELECT ARRAY_REMOVE(ARRAY[1, 2, 3], 2) AS result 複数の削除対象の値が含まれている場合には全て削除されます。 SELECT ARRAY_REMOVE(ARRAY[1, 2, 2, 3], 2) AS result Pythonでの書き方 ビルトインのリストであれはremoveメソッドで削除が利きます。ただしリストに含まれていないとエラーになるのと1回呼んだだけでは先頭の値しか削除されないのでSQL側と同じようなことをしようとするとwhile文などで対象の値が含まれているかといった判定が必要になります。 from typing import Dict, List arr: List[int] = [1, 2, 2, 3] while 2 in arr: arr.remove(2) print(arr) [1, 3] 配列内で指定された値の最初の出現位置を取得する: ARRAY_POSITION ARRAY_POSITION関数は配列内で指定された値が初めて出現する位置を返します。複数対象の値が存在する場合は最初の値の位置のみが返却されます。 例えば以下のSQLでは配列内で2の値が配列の何番目に存在するかを確認しています。2番目に存在するので結果は2となります。 SELECT ARRAY_POSITION(ARRAY[1, 2, 3, 3, 2], 2) AS first_position 配列内に対象の値が存在しない場合返却値は0になります。 SELECT ARRAY_POSITION(ARRAY[1, 2, 3, 3, 2], 5) AS first_position ※Prestoのドキュメントでは第三引数で何番目の値かどうかが指定できるようになっていますがAthenaではまだ使えないようです。この記事を書いている時点では値が最初に出現する位置しか取れません。将来Athena側のアップデートで変わるかもしれません。 Pythonでの書き方 ビルトインのリストであればindexメソッドで同じようなことができます。他と同様Pythonではインデックスが0からスタートするのでSQLと比べて結果が-1した値になります。 from typing import List arr: List[int] = [1, 2, 3, 3, 2] first_position: int = arr.index(2) print(first_position) 1 特定の値が含まれない配列を取得する: ARRAY_EXCEPT ARRAY_EXCEPT関数は2つの配列を用いて、1つ目の配列の中で2つ目の配列に含まれない値の配列を返します。 以下のSQLでは1つ目の配列の値は1, 2, 3, 3, 2, 4, 5, 5、2つ目の配列の値は2, 3としています。結果的に2と3が取り除かれた1, 4, 5の値が結果の配列に設定されます。結果に重複分は含まれません(1, 4, 5, 5といったような値は返却されません)。 SELECT ARRAY_EXCEPT(ARRAY[1, 2, 3, 3, 2, 4, 5, 5], ARRAY[2, 3]) AS result Pythonでの書き方 ダイレクトにARRAY_EXCEPT関数に該当するビルトイン関数などは無い(?)ためループで回しています。前節と同じようにリストが巨大になった際のことを考えて2つ目の配列は一旦辞書にしています。また、ループの回数を減らすために最初の方でset関数を使って配列を一意にしています。2つ目の配列に含まれる値は結果に含めずにループ内でcontinueしています。 from typing import Dict, List arr_1: List[int] = [1, 2, 3, 3, 2, 4, 5, 5] arr_2: List[int] = [2, 3] arr_1 = list(set(arr_1)) arr_2 = list(set(arr_2)) result_list: List[int] = [] dict_value: Dict[int, None] = {value: None for value in arr_2} for value in arr_1: if value in dict_value: continue result_list.append(value) print(result_list) [1, 4, 5] 配列の値を連結して文字列を取得する: ARRAY_JOIN ARRAY_JOIN関数は配列の値を特定の文字列で連結した文字列を取得することができます。たとえばコンマで連結すればCSV的な文字列を取得することができます。 第一引数に対象の配列のカラム名もしくは固定値、第二引数には区切り文字を指定します。 以下のSQLではコンマ区切りで配列の値を連結しています。 SELECT ARRAY_JOIN(powers, ',') AS joined_powers, powers FROM athena_workshop.battle_party_power LIMIT 10 なお、配列に欠損値(NULL)が含まれている場合、その値は結果の文字列には含まれません。 以下のSQLでは1, NULL, 3という値の配列を指定していますが、結果は1,3とNULLを除いた値になっていることを確認できます。 SELECT ARRAY_JOIN(ARRAY[1, NULL, 3], ',') AS joined 欠損値部分を別の値で埋めたい場合には第三引数を指定します。以下ではNULL部分が取り除かれてしまうのではなく、NaNという文字列で置換しています。 SELECT ARRAY_JOIN(ARRAY[1, NULL, 3], ',', 'NaN') AS joined Pythonでの書き方 ビルトインの文字列がリストを引数に取るjoinメソッドを持っているためそちらで対応ができます。ただしリストの値を文字列に先に変換しておく必要があります。 from typing import List int_arr: List[int] = [1, 2, 3] str_arr: List[str] = [str(value) for value in int_arr] joined: str = ','.join(str_arr) print(joined) 1,2,3 配列のソートを行う: ARRAY_SORT ARRAY_SORT関数を使うと配列の値の昇順ソートを行うことができます。 SELECT ARRAY_SORT(powers) AS sorted_powers, powers FROM athena_workshop.battle_party_power LIMIT 10 配列に欠損値(NULL)が含まれている場合にはその欠損値は配列の末尾に配置される形でソートされます。 SELECT ARRAY_SORT(ARRAY[5, 3, 6, NULL, 7, NULL, 10]) AS sorted Pythonでの書き方 ビルトインのリストであればsortメソッドで昇順ソートすることができます。 from typing import List, Optional arr: List[int] = [5, 3, 6, 10, 7] arr.sort() print(arr) [3, 5, 6, 7, 10] もしくはコピーされたリストが欲しければビルトインのsorted関数で取得することができます。 from typing import List, Optional arr_1: List[int] = [5, 3, 6, 10, 7] arr_2: List[int] = sorted(arr_1) print(arr_2) [3, 5, 6, 7, 10] ラムダ式(Lambda Expressions) 以降の節の関数ではラムダ式が必要になってきたりするため先に軽く触れておきます。詳細は各関数で必要な分だけ説明を加えていきます。 ラムダ式は無名関数などとも呼ばれたりしますが、特定の値を受け取ってその値を使った返却値を記述することで、独自の関数定義のようなことをすることができます。 ->の記号を使い、特定の値を引数として左側に記述し、右側に返却値を記述します。例えばxという値を引数で受け取って、それを1加算する形の返却値としてx + 1という表現をしたい場合にはx -> x + 1という書き方になります。複数の引数(xとy)を受け付けて1つの返却値(z)を返す場合には(x, y) -> zといったように書きます。 このラムダ式は関数内でしか使えません。ラムダ式を引数に取る関数があるためそれらの関数で使います。 ソート条件にラムダ式を使う ARRAY_SORT関数の第三引数にはラムダ式を指定することができ、独自のソート方法を自身で組むことができます。 引数には2つの値が渡されます。ここではxとyという引数名を使っています。このxとyの2つの値での比較条件をラムダ式で書くことで比較条件を配列の各値に反映する形でソートが実行されます。ラムダ式の返却値には1つの整数が必要になり、-1, 0, 1のいずれかの値のみを受け付けます。xとyをソート用に比較した時にxの方を前にしたい条件であれば-1、xの方を後にしたい条件であれば1、xとyが同じ位置(同値条件など)になるべき条件であれば0を返すようにします。 条件文はIF関数を使って書きます。第一引数に条件式(x > yなど)、第二引数に条件を満たす場合の返却値、第三引数に条件を満たさない場合の返却値を指定します。第二引数や第三引数にはさらにIF関数もしくは他の関数などを入れ子にすることもできます。例えばA条件を満たす場合、さらにその中でB条件を満たすか満たさないか・・・といったように複数の条件を入れ子にしていくことができます。 例としてラムダ式を使わないARRAY_SORTの昇順ソートのと同じソートの計算をラムダ式を使って書いてみましょう(第三引数を省略する形で実務では問題無いのですがシンプルな説明用として使いえます)。 以下のようなSQLとなります。 SELECT ARRAY_SORT(ARRAY[5, 3, 6, 10, 7], (x, y) -> IF(x < y, -1, IF(x = y, 0, 1))) AS sorted ラムダ式部分は(x, y) -> IF(x < y, -1, IF(x = y, 0, 1)))が該当します。まず引数部分ですがソートで2つの値が必要になるので(x, y)という記述にしています。 IF関数の最初の条件と条件を満たす場合の指定ですがIF(x < y, -1,としています。これはxがyよりも小さければ-1を返す(xがyよりも小さければxをyよりも前に配置する)という制御になります。 前述の条件を満たさない場合にはさらにIF関数を入れ子にしてIF(x = y, 0, 1)と分岐を書いています。これは、 xがyよりも小さいという条件を満たさず、且つxとyが同値(x = y)である場合には0を返す(xとyはソートで同じ位置になる)。 xがyよりも小さいという条件を満たさず、且つxとyが同値(x = y)でもない場合には1を返す(xがyよりも大きいのでxがyの後の位置にする)。 という条件になっています。 今度は降順ソートを書いてみましょう。現在Athenaでは配列を逆順にするREVERSE関数が使えない(Presto側のドキュメントには追加されているのでその内アップデートで使える形になると思われます)ため自前で書く必要があります。 やることとしてはシンプルで、x < yの箇所をx > yと比較演算子の向きを変えているだけです。これでxがyよりも大きければ前の位置に配置するという挙動になります。 SELECT ARRAY_SORT(ARRAY[5, 3, 6, 10, 7], (x, y) -> IF(x > y, -1, IF(x = y, 0, 1))) AS sorted ラムダ式と関数などを組み合わせてソートする ARRAY_SORT関数の第二引数のラムダ式には関数などを組み合わせて使うこともできます。例えば文字列の配列に対して文字数でソートしたい・・・というケースには以下のように書くことができます。xやyの部分を文字数を取得するLENGTH関数で囲っています。 SELECT ARRAY_SORT( ARRAY['ねこ', 'うさぎ', 'いぬ', 'おおかみ', 'ひつじ', 'らいおん'], (x, y) -> IF(LENGTH(x) < LENGTH(y), -1, IF(LENGTH(x) = LENGTH(y), 0, 1))) AS sorted 配列をシャッフルする: SHUFFLE SHUFFLE関数は配列の内容をランダムにシャッフルします。結果は実行する度に変わります。 SELECT SHUFFLE(powers) AS shuffled_powers, powers FROM athena_workshop.battle_party_power LIMIT 10 Pythonでの書き方 ビルトインのrandomパッケージのshuffle関数でリストなどをシャッフルすることができます。 from typing import List from random import shuffle arr: List[int] = [5, 3, 6, 10, 7] shuffle(x=arr) print(arr) [7, 6, 3, 5, 10] 2つの配列で双方に存在する値を格納した配列を取得する: ARRAY_INTERSECT ARRAY_INTERSECT関数は第一引数と第二引数に指定された2つの配列間で、両方の配列に存在する値のみを格納した配列を返却します。返却される値は重複の無い形で設定されます。 以下のSQLでは1, 2, 3という値の配列と2, 3, 4という値の配列の2つを指定しています。2と3部分がそれぞれの配列に存在するため結果は2, 3という値の配列となります。片側にしかない値の1と4は含まれません。 SELECT ARRAY_INTERSECT(ARRAY[1, 2, 3], ARRAY[2, 3, 4]) AS result 余談ですが、Prestoのドキュメントを見ていると2次元配列を渡す形でのARRAY_INTERSECT関数の記述がありましたがまだAthenaでは使えない?ようです。 Pythonでの書き方 2つのsetを&記号で繋げばそれぞれのsetで両方に値があるもののみのsetが得られるのでそちらをリストにキャストすれば結果のリストが得られます。 以下のコードでは一度リストをset関数でsetに変換しています。 from typing import List arr_1: List[int] = [1, 2, 3] arr_2: List[int] = [2, 3, 4] result: List[int] = list(set(arr_1) & set(arr_2)) print(result) [2, 3] 任意の条件を満たした値のみを格納した配列を取得する: FILTER FILTER関数では特定の条件を満たす値のみを残した配列を返却します。第一引数には対象の配列のカラムもしくは配列の固定値、第二引数にはラムダ式を指定します。ラムダ式には1つの引数(サンプルではxという名前を使います)を受け付け、返却値には(等値や大なりなどの比較表現などによって)真偽値が返るようにします。返却値でtrueが返る条件の箇所のみ結果の配列に残ります。 以下のSQLではラムダ式でx >= 0という条件を指定しており、結果的に値が0以上のもののみ結果の配列に含まれるようにしています。 SELECT FILTER(ARRAY[-5, 3, 2, -1, 0, 8, -3], x -> x >= 0) AS result Pythonでの書き方 ビルトインのfilter関数で同じようなことができます。返却値はfilterオブジェクトになるのでリストにキャストする処理を加えてあります。 from typing import List arr: List[int] = [-5, 3, 2, -1, 0, 8, -3] arr = list(filter(lambda x: x >= 0, arr)) print(arr) [3, 2, 0, 8] 2次元配列を1次元の配列に変換する: FLATTEN FLATTEN関数は以下のような行列(2次元配列)を1, 2, 3, 4, 5, 6, 7, 8, 9といったように1次元の配列に変換します。 \begin{array}{ccc} 1 & 2 & 3\\ 4 & 5 & 6\\ 7 & 8 & 9 \end{array} SQL上では第一引数に2次元配列が必要になります。 SELECT FLATTEN(ARRAY[ARRAY[1, 2, 3], ARRAY[4, 5, 6], ARRAY[7, 8, 9]]) AS result Pythonでの書き方 ビルトインのリストの場合は今まで使ったことがなかったのですがitertools.chain.from_iterableを使うとビルトインのもので完結する形でストレートにいくようです。 from typing import List import itertools arr: List[List[int]] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flattened: List[int] = list(itertools.chain.from_iterable(arr)) print(flattened) [1, 2, 3, 4, 5, 6, 7, 8, 9] NumPyなどであればflatternメソッドがあるのでそのままそれを呼ぶだけで対応ができます。こちらはデータ方面のお仕事などされている方々はビルトインよりも使う機会が結構あると思います。 import numpy as np arr: np.ndarray = np.array([ [1, 2, 3], [4, 5, 6], [7, 8, 9], ]) arr = arr.flatten() print(arr) [1 2 3 4 5 6 7 8 9] 他のカラムの値を残しつつ、配列のネストされたデータを行列にする: UNNEST ケースによっては配列のカラムではなく行列で扱った方が色々とシンプルな時があります。そういった場合にはUNNEST関数とCROSS JOINによる連結を使うと他のカラムの値を残したまま行列に変換することができます。 指定された配列の値を1行1行展開し、他のカラムの値は一通りそのまま残す(元の値を連結する)みたいな挙動になります。 例えば以下のような行列があったとします。武器のIDのカラムは配列の値を含んでいます。 \begin{array}{rr} ユーザーID & 武器のID\\ 1 & [10, 11]\\ 2 & [12, 13] \end{array} UNNEST関数と武器のIDを指定したCROSS JOINを使うと上記の行列が以下のような行列に変換されるイメージです。 \begin{array}{rr} ユーザーID & 武器のID\\ 1 & 10\\ 1 & 11\\ 2 & 12\\ 2 & 13 \end{array} CROSS JOIN自体は以前の記事で触れましたが、組み合わせによる連結を行う都合元の行数よりもぐぐっと行数が多くなります。 参考 : 左側のテーブルの行数がN行、右側のテーブルの行数がM行とすると交差結合の結果の行数は$N × M$行となります。 今回のUNNEST関数の例で言うとNが元々の行数、Mが対象とする配列の値の件数となります。例えば元々の行が10万行、配列が平均して10件の値を含んでいた場合は10万 × 10で結果は100万行となります。処理に時間がかかったり扱うデータが巨大化したりするケースがあるので扱う際には少し気を付ける必要があります。 書き方としてはCROSS JOIN UNNEST(<対象の配列のカラム名>) AS <UNNESTしたテーブルに付ける名前>(配列の値に付けるカラム名)みたいな書き方になります。少々独特です。 例えばbattle_party_powerテーブルのpowersという配列を展開したい場合に、展開後のテーブル名をunnested_powersとし、配列の値のカラム名にpowerという名前を付ける場合はCROSS JOIN UNNEST(athena_workshop.battle_party_power.powers) AS unnested_powers(power)といったような書き方になります。 実際にSQLで実行してみましょう。 SELECT * FROM athena_workshop.battle_party_power CROSS JOIN UNNEST(athena_workshop.battle_party_power.powers) AS unnested_powers(power) LIMIT 50 SQL実行結果では元々のカラムに加えて連結されたpowerというカラムが追加になっていることが分かります。また、powersの配列のカラムの各値が1行ずつに展開され、powerカラム以外のカラムの値はそのまま残っていることが確認できます。 また、UNNEST関数はCROSS JOINなどと一緒に使わない書き方もあります。書き方としてはFROM句にて<対象のテーブル>, <そのテーブルの配列のカラムに対するUNNESTの指定>といったように書きます。UNNESTの前にテーブルの指定とコンマが必要になる書き方となります。例えばathena_workshop.battle_party_powerテーブルのpowersという配列のカラムに対して使う場合には以下のようになります。 SELECT * FROM athena_workshop.battle_party_power, UNNEST(athena_workshop.battle_party_power.powers) AS unnested_powers(power) LIMIT 50 結果はCROSS JOINしたときと似たような形になります。 ただしこちらの書き方の場合、UNNESTしたものに対して他のJOIN(LEFT JOINやINNER JOINなど)をするとエラーで弾かれるようです。そういったケースではCROSS JOINとUNNESTを使ってからその後にLEFT JOINなどを使うか、もしくはWITH句などでのサブクエリで分割する必要があるようです(将来Athena内部のPrestoのアップデートでLEFT JOINなどもできるようになるかもしれません)。 Athena is based on Presto .172 LEFT JOIN with UNNEST was added in Presto 319 ... one can rewrite the left join to a cross join with coalesce: 例えばクエストでドロップした複数の武器などがクエストのログの行に配列などで格納されていて、武器のIDと武器のマスタをJOINしたい場合が該当します。今回テーブルを追加したりはしませんが、仮にlog_db.questというクエストログのテーブルがあり、dropped_weaponsという配列のカラムが含まれており、且つmst_weaponというマスタテーブルがあると過程するとCROSS JOIN UNNESTと他のJOINを使うと(JOIN部分だけ抽出すると)以下のような書き方になります。 ... CROSS JOIN UNNEST(log_db.quest.dropped_weapons) AS unnested_weapons(weapon_id) INNER JOIN mst_weapon ON unnested_weapons.weapon_id = mst_weapon.id ... 辞書を格納した配列に関してはさらに色々と触れることがありますが、そちらは別の辞書について詳しく触れる記事で書いていこうと思います。 Pythonでの書き方 この辺は大分昔の記事となりますが、Pandasのjson_normalize関数などが用意されており記事にしていますのでそちらをご確認ください。 配列をn件ずつの2次元配列に変換する: NGRAMS NGRAMS関数は1次元の配列を2次元の配列に変換します。自然言語方面で結構出てきたりします。 n-gramとはn個の連続する単位 一言で説明すると、「n-gram」とは連続するn個の単語や文字のまとまりを表します。 まとまりごとに2次元目の配列化されます。例えば[1, 2, 3, 4, 5]という配列に対してn=2でNGRAM関数を反映すると、[[1, 2], [2, 3], [3, 4], [4, 5]]といった2次元配列に変換されます。n=3で反映すると[[1, 2, 3], [2, 3, 4], [3, 4, 5]]といった値になります。 第一引数には対象の配列のカラムもしくは配列の固定値、第二引数にはnの値を指定します。 SELECT NGRAMS(ARRAY[1, 2, 3, 4, 5], 3) AS result Pythonでの書き方 Pandas自体にはn-gram関係の機能は無さそうですが、nltkなどの別のライブラリを絡めるとシンプルにいきそうな印象です。試していないのでここではリンクのみ貼っておきます。 配列の各値で計算を行い、計算結果を取得する: REDUCE REDUCE関数は配列の値に対して1つ1つ計算を行い、計算結果を単一の値(次元数が減った状態の値)を取得します。ラムダ式をがっつり使う形となります。 第一引数には対象とする配列のカラム名もしくは配列の固定値、第二引数には初期値、第三引数には各値ごとの入力のラムダ式、第四引数には全体の出力時のラムダ式が必要になります。引数が複雑なので順番に触れていきます。 まずは第二引数の初期値です。これは集計処理の初期値です。0を指定して0からスタートするケースが結構多くなると思います。 第三引数は各値ごとの入力のラムダ式となります。各値ごとに処理がされます。引数はその値までの計算結果(今回の記事ではsと表記します)と対象の配列の値(今回の記事ではxと表記します)の2つです。先頭の値に関してはsの値は第二引数に指定した初期値になります。 例えば[2, 3, 4]という配列に対して処理を行うとして、第二引数の初期値が1だった場合は 1回目の処理 -> s = 1, x = 2 2回目の処理 -> s = 1回目の処理の計算結果, x = 3 3回目の処理 -> s = 2回目の処理の計算結果, x = 4 といったように3回計算がこのラムダ式で実行されます。返却値は1つの値となる必要があります(計算結果として次の処理のsに渡されます)。 第四引数は出力用のラムダ式です。このラムダ式は第三引数とは異なり配列の各値ごとに実行されたりはしません。各値の計算結果の最後の値に対してのみ反映されます(配列の件数に関わらず1回のみ実行されます)。引数は全体の計算結果のsのみ、返却値も1つの値のみとなります。 いくつかサンプルのSQLを書いていきます。まずはシンプルに配列の値の合計値を出すという記述をREDUCE関数を使ってしてみましょう。前節までで触れた通り、配列の合計値を出す関数自体はPrestoにはARRAY_SUM関数が存在しますが、Athenaではまだこの記事を執筆時点では使えない点とサンプルとしてシンプルなので使っていきます。 まずは第二引数の初期値ですが、これは合計値を出す計算なので0を指定します。 続いて第三引数のラムダ式ですが、引数の指定は2つの値が必要になるため(s, x)といった記述になります。返却値側は配列の各値の合計を出す必要があるため、前の処理までの計算結果sに対して今回の配列の値xを加えるという形でs + xとしています。 第四引数の処理に関しては今回は制御が必要ないため、s -> sとして値をそのまま返却するようにしています。 1, 2, 3という3つの値を含んでいる配列に対して試してみます。 SELECT REDUCE(ARRAY[1, 2, 3], 0, (s, x) -> s + x, s -> s) AS result 無事配列の合計値の6を得ることができました。 今度は第四引数の出力部分のラムダ式を調整していきましょう。このラムダ式は第三引数のラムダ式とは異なり配列の値1つ1つで計算はされずに最後の計算結果にのみ(最後の1回だけ)反映されます。 以下のSQLでは計算結果に対して4で割ったときの剰余(余り)をs % 4という記述で求めています。結果が2になることが確認できます。 SELECT REDUCE(ARRAY[1, 2, 3], 0, (s, x) -> s + x, s -> s % 4) AS result 他にも比較演算子を使って比較結果を真偽値を取得したり、関数を挟んだりと色々できます。以下のSQLでは計算結果が5よりも大きいか(s > 5)という判定にして結果の真偽値を取得しています。 SELECT REDUCE(ARRAY[1, 2, 3], 0, (s, x) -> s + x, s -> s > 5) AS result Pythonでの書き方 ビルトインのfunctoolsパッケージにreduce関数があり、そちらで同じような制御を行うことができます。第一引数には反映する関数もしくはラムダ式、第二引数にリストなどの値を指定します。 from typing import List from functools import reduce arr: List[int] = [1, 2, 3] result: int = reduce(lambda s, x: s + x, arr) print(result) 6 特定の変換処理を加えた配列を取得する: TRANSFORM TRANSFORM関数もREDUCE関数のように配列の値1件1件に対して処理を加えます。ただし結果の値はそのまま配列で返却され、配列の値の件数も基本的に変動しません。「配列の全ての値に対して加工を行う」といった制御に向いています。 第一引数には対象の配列のカラム名もしくは配列の固定値、第二引数には加工処理用のラムダ式を指定します。第二引数のラムダ式には1つの引数(配列内の個々の値)と1つの返却値を受け付けます。今回はxという名前を使っています。 以下のSQLでは1, 2, 3という値の配列に対して1加算するラムダ式(x + 1)を指定しています。結果が2, 3, 4となることを確認できます。 SELECT TRANSFORM(ARRAY[1, 2, 3], x -> x + 1) AS result Pythonでの書き方 色々書き方がありますが、1つ目としてはmap関数による処理がTRANSFORM関数に近い挙動になっています。 from typing import List arr: List[int] = [1, 2, 3] arr = list(map(lambda x: x + 1, arr)) print(arr) [2, 3, 4] 他にもリスト内包表記やもちろん普通にループを回してしまっても良いと思います。 from typing import List arr: List[int] = [1, 2, 3] arr = [x + 1 for x in arr] print(arr) [2, 3, 4] 2つ以上の配列を統合した配列を取得する: ZIP ZIP関数は2つ以上の配列の値をセットにした配列を返します。返却値はROWという型の値を格納した配列になるようです。ROWは辞書のような形の型となります(1行分のデータといった形になります)。 実行後のカラム名はfield0, field1, ... と設定されます。 SELECT ZIP(ARRAY[1, 2, 3], ARRAY['猫', '犬', '兎']) AS result ROW型の値はドット記号(.)でアクセスすることができます。例えばfield1のカラムにアクセスしたい場合には.field1といった書き方でその値を参照することができます。 配列へのアクセスは[]の括弧を使えば行えるため、例えば配列の最初のROWの値のfield1にアクセスしたい場合には[1].field1といった書き方になります(括弧内のインデックスの数字はAthena(Presto)上では1からのスタートになります)。 SELECT ZIP(ARRAY[1, 2, 3], ARRAY['猫', '犬', '兎'])[1].field1 AS result Pythonでの書き方 ループなどの際にビルトインのzip関数を使うことで複数のリストなどの値をひとまとめにしつつループを行うことができます。結果はタプルで渡されます。 from typing import List arr_1: List[int] = [1, 2, 3] arr_2: List[str] = ['猫', '犬', '兎'] for tpl in zip(arr_1, arr_2): print(tpl) (1, '猫') (2, '犬') (3, '兎') タプルはコンマ区切りでそのまま展開することもできるため、それぞれを変数に分けることもできます。 from typing import List arr_1: List[int] = [1, 2, 3] arr_2: List[str] = ['猫', '犬', '兎'] for id_, name in zip(arr_1, arr_2): print(id_, name) 1 猫 2 犬 3 兎 なお、Prestoのドキュメントには2つの配列に対してラムダ式で処理を加えつつ制御が可能なZIP_WITHという関数もあったのですがこの記事の執筆時点ではまだAthena上では利用できないようです。 参考文献・参考サイトまとめ The Applied SQL Data Analytics Workshop: Develop your practical skills and prepare to become a professional data analyst, 2nd Edition [AWS]Athenaのコメントアウトの仕方とフォーマットについて Array Functions and Operators Lambda Expressions pandas.date_range Pythonでリスト(配列)の要素を削除するclear, pop, remove, del How to find list intersection? Pythonでflatten(多次元リストを一次元に平坦化) Flatten arrays into rows with UNNEST Pandasのjson_normalizeを詳しく調べてみる 自然言語処理に出てくるn-gramとはなに? From DataFrame to N-Grams Python の基本的な高階関数( map() filter() reduce() ) How to Transform List Elements with Python map() Function Data Types AWS Athena unnest with left join not working
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CloudFormationで開発環境を作ってみる

■ CloudFormation CloudFormation は AWSリソースのプロビジョニングを行えるサービスで、YAML(またはJSON)でテンプレートを記述することができます。 今回は勉強がてら自分の開発環境を作ってみたいと思います。 ◎ テンプレート dev.yml # 組み込み関数: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference.html # 疑似パラメータ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/pseudo-parameter-reference.html AWSTemplateFormatVersion: '2010-09-09' # パラメータ定義: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html Parameters: ProjectPrefix: Type: String KeyPairBastion: Description: The EC2 Key Pair for bastion server Type: AWS::EC2::KeyPair::KeyName # アウトプット: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/outputs-section-structure.html Outputs: PublicIpBastion: Value: !GetAtt BastionServer.PublicIp PrivateIpDev: Value: !GetAtt DevServer.PrivateIp Resources: # vpc: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc.html VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.91.0.0/16 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-vpc # サブネット: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet.html PublicSubnet01: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.91.10.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスが起動時にパブリックIPを設定するか(default = false) VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-public-subnet-01 PrivateSubnet01: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.91.11.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-private-subnet-01 # インターネットゲートウェイ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-internetgateway.html InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${ProjectPrefix}-igw # VPCにインターネットゲートウェイをアタッチ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-vpc-gateway-attachment.html AttachInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway # ElasticIp: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html EipNgw: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: !Sub ${ProjectPrefix}-eip-ngw01 EipBastion: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: !Sub ${ProjectPrefix}-eip-bastion # NATゲートウェイ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-natgateway.html NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt EipNgw.AllocationId SubnetId: !Ref PublicSubnet01 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ngw-01 # ルートテーブル: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route-table.html PublicRouteTable: Type: AWS::EC2::RouteTable DependsOn: AttachInternetGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-public-rt-01 PrivateRouteTable01: Type: AWS::EC2::RouteTable DependsOn: AttachInternetGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-private-rt-01 # サブネットとルートテーブルの紐づけ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-subnet-route-table-assoc.html PublicRouteAssoc01: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet01 PrivateRouteAssoc01: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable01 SubnetId: !Ref PrivateSubnet01 # ルート定義: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-route.html PublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PrivateRoute01: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable01 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway # EC2用のセキュリティグループ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group.html SecurityGroupBastion: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for bastion server VpcId: !Ref VPC SecurityGroupIngress: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 124.41.88.40/32 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-sg-bastion SecurityGroupDev: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for dev server VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 0 ToPort: 65535 CidrIp: !GetAtt VPC.CidrBlock - IpProtocol: icmp # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html FromPort: 8 ToPort: -1 CidrIp: !GetAtt VPC.CidrBlock Tags: - Key: Name Value: !Sub ${ProjectPrefix}-sg-dev # 踏み台サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html BastionServer: Type: AWS::EC2::Instance Properties: ImageId: ami-02892a4ea9bfa2192 KeyName: !Ref KeyPairBastion InstanceType: t2.micro NetworkInterfaces: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html - NetworkInterfaceId: !Ref BastionXface01 DeviceIndex: 0 BlockDeviceMappings: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/ Ebs: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html VolumeType: gp2 VolumeSize: 10 DeleteOnTermination: true Encrypted: true UserData: !Base64 | #!/bin/bash sudo yum install -y git tmux Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ec2-bastion # パブリックサブネットに踏み台サーバー用のNicを作成: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-network-interface.html BastionXface01: Type: AWS::EC2::NetworkInterface Properties: SubnetId: !Ref PublicSubnet01 GroupSet: - !Ref SecurityGroupBastion SourceDestCheck: true Tags: - Key: Name Value: !Sub ${ProjectPrefix}-xface-bastion # 踏み台サーバー用のNicにEipを紐づけ: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip-association.html EipAssocBastionXface01: Type: AWS::EC2::EIPAssociation Properties: AllocationId: !GetAtt EipBastion.AllocationId NetworkInterfaceId: !Ref BastionXface01 # 開発サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html DevServer: Type: AWS::EC2::Instance Properties: ImageId: ami-02892a4ea9bfa2192 KeyName: !Ref KeyPairBastion InstanceType: t2.micro SubnetId: !Ref PrivateSubnet01 PrivateIpAddress: 10.91.11.10 SecurityGroupIds: - !Ref SecurityGroupDev BlockDeviceMappings: - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/ Ebs: VolumeType: gp2 VolumeSize: 16 DeleteOnTermination: false Encrypted: true UserData: !Base64 | #!/bin/bash sudo yum install -y git tmux Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ec2-dev ■ テンプレート: Parametersセクション Parameters セクションでは、テンプレートで利用する変数を定義できます。 ProjectPrefix 作成リソースの名前のプレフィックスとして利用する文字列を定義 KeyPairBastion 先ほど作成したキーペアを指定するためのパラメータ dev.yml Parameters: ProjectPrefix: Type: String KeyPairBastion: Description: The EC2 Key Pair for bastion server Type: AWS::EC2::KeyPair::KeyName ■ テンプレート: Outputsセクション Outputs セクションにはスタック作成後の出力値を定義します。 PublicIpBastion 踏み台サーバーのパブリックIP。sshで利用。 PrivateIpDev 開発サーバーのプライベートIP。sshで利用。 dev.yml Outputs: PublicIpBastion: Value: !GetAtt BastionServer.PublicIp PrivateIpDev: Value: !GetAtt DevServer.PrivateIp # 組み込み関数 !GetAtt Fn::GetAtt CloudFormationにはテンプレート内で利用できる関数が定義されています。 !GetAtt はリソースのメンバとして定義されている情報を参照することができます。 BastionServer.PublicIp は BastionServer(後述)のパブリックIPを参照します。 ■ テンプレート: Resourcesセクション Resources セクションには作成するAWSリソースを定義します。 ◎ VPCの定義 CidrBlock VPCで利用するプライベートネットワーク用のIPアドレス範囲をCIDR形式で指定します。 dev.yml VPC: Type: AWS::EC2::VPC Properties: CidrBlock: 10.91.0.0/16 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-vpc # 組み込み関数 !Sub Fn::Sub !Sub は変数の内容を文字列に埋め込むことができます。 ◎ サブネット 直接インターネットと通信可能なパブリックサブネット( 10.91.10.0/24 )と、NATを介してインターネットと通信するプライベートサブネット( 10.91.10.0/24 )、2つのサブネットを定義します。 CidrBlock サブネットのIPアドレス範囲をCIDR形式で指定します。 AvailabilityZone サブネットを作成するAZを指定します MapPublicIpOnLaunch サブネットで起動したインスタンスがパブリックIPを受け取るかを指定します。 VpcId サブネットが属するVPCを指定します。 dev.yml PublicSubnet01: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.91.10.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false # このサブネットで起動されたインスタンスが起動時にパブリックIPを設定するか(default = false) VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-public-subnet-01 PrivateSubnet01: Type: AWS::EC2::Subnet Properties: CidrBlock: 10.91.11.0/24 AvailabilityZone: ap-northeast-1a MapPublicIpOnLaunch: false VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-private-subnet-01 # 組み込み関数 !Ref Ref !Ref は指定したパラメータまたはリソースの値を返します。リソースを参照した場合の戻り値はリソースのドキュメントを確認する必要があります。 ◎ インターネットゲートウェイ インターネットと通信するためのインターネットゲートウェイを定義します。 dev.yml InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: !Sub ${ProjectPrefix}-igw ◎ インターネットゲートウェイをVPCにアタッチ dev.yml AttachInternetGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref VPC InternetGatewayId: !Ref InternetGateway ◎ ElasticIPの確保 NATゲートウェイ用のグローバルIPアドレスと踏み台サーバー用のIPアドレスを取得します。 Domain VPCのインスタンスで利用する場合は vpc を指定します。 EC2-Classicのインスタンスで利用する場合は standard を指定します。 dev.yml EipNgw: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: !Sub ${ProjectPrefix}-eip-ngw01 EipBastion: Type: AWS::EC2::EIP Properties: Domain: vpc Tags: - Key: Name Value: !Sub ${ProjectPrefix}-eip-bastion ◎ NATゲートウェイ プライベートサブネット内のインスタンスがインターネットと通信するためのNATゲートウェイを作成します。 ※ インターネットゲートウェイとNATゲートウェイの説明はこちらがわかりやすかったです https://milestone-of-se.nesuke.com/sv-advanced/aws/internet-nat-gateway/ AllocationId NATゲートウェイに紐づけるElasticIPのAllocationIdを指定します。 SubnetId NATゲートウェイを配置するサブネットを指定します。 ここではパブリックサブネットを指定します。(プライベートサブネットではない!) dev.yml NatGateway: Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt EipNgw.AllocationId SubnetId: !Ref PublicSubnet01 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ngw-01 ◎ ルートテーブル パブリックサブネットとプライベートサブネットのルーティングを定義するためのルートテーブルを作成します。 VpcId ルートテーブルを配置するVPCを指定します。 dev.yml PublicRouteTable: Type: AWS::EC2::RouteTable DependsOn: AttachInternetGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-public-rt-01 PrivateRouteTable01: Type: AWS::EC2::RouteTable DependsOn: AttachInternetGateway Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub ${ProjectPrefix}-private-rt-01 ◎ ルートテーブルとサブネットの紐づけ 先ほど作成したルートテーブルとサブネットを紐づけます。 RouteTableId サブネットに紐づけるルートテーブルを指定します。 SubnetId ルートテーブルに紐づけるサブネットを指定します。 dev.yml PublicRouteAssoc01: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PublicRouteTable SubnetId: !Ref PublicSubnet01 PrivateRouteAssoc01: Type: AWS::EC2::SubnetRouteTableAssociation Properties: RouteTableId: !Ref PrivateRouteTable01 SubnetId: !Ref PrivateSubnet01 ◎ ルート定義 パブリックサブネット: デフォルトゲートウェイにインターネットゲートウェイを設定します。 プライベートサブネット: デフォルトゲートウェイにNATゲートウェイを設定します。 RouteTableId ルーティングを定義したいルートテーブルを指定します。 DestinationCidrBlock 宛先をCIDR形式で指定します。 GatewayId 宛先がDestinationCidrBlockにマッチする場合のゲートウェイを指定します。 dev.yml PublicRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PublicRouteTable DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref InternetGateway PrivateRoute01: Type: AWS::EC2::Route Properties: RouteTableId: !Ref PrivateRouteTable01 DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref NatGateway ◎ セキュリティグループ 踏み台サーバーと開発サーバーへのアクセスを制限するためのセキュリティグループを定義します。 踏み台サーバー: すべてのアクセス元からポート22へのtcpアクセスを許可します 開発サーバー: ローカルネットワークから全ポートへのtcpアクセス、icmpパケットを許可します。 ※ 外に出ていく通信に関してはデフォルトで制限はないので、特に設定する必要はありません。 VpcId セキュリティグループを定義するVPCを指定します SecurityGroupIngress https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule-1.html インバウンドルールを定義するセクションです。 IpProtocol tcp , udp , icmp など対象となる通信プロトコルを指定します。 FromPort 対象となるポート範囲の、開始ポートを指定します。 ToPort 対象となるポート範囲の、終了ポートを指定します。 CidrIp 対象となるIPアドレス範囲をCIDR形式で指定します。 SecurityGroupEgress https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-rule.html アウトバウンドルールを定義するセクションです。 dev.yml SecurityGroupBastion: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for bastion server VpcId: !Ref VPC SecurityGroupIngress: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html - IpProtocol: tcp FromPort: 22 ToPort: 22 CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub ${ProjectPrefix}-sg-bastion SecurityGroupDev: Type: AWS::EC2::SecurityGroup Properties: GroupDescription: for dev server VpcId: !Ref VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: 0 ToPort: 65535 CidrIp: !GetAtt VPC.CidrBlock - IpProtocol: icmp # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-security-group-ingress.html FromPort: 8 ToPort: -1 CidrIp: !GetAtt VPC.CidrBlock Tags: - Key: Name Value: !Sub ${ProjectPrefix}-sg-dev ◎ 踏み台サーバー 踏み台サーバーインスタンスを作成します。 踏み台サーバーは直接ログインできる必要があるため、パブリックサブネットに配置し、グローバルIPを付与します。 ImageId AMI IDを指定します。今回はAmazon Linux 2を利用。 KeyName キーペアを指定します。 InstanceType インスタンスタイプを指定します。 NetworkInterfaces インスタンスにアタッチするネットワークインターフェースを定義します。 https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html NetworkInterfaceId 既存のNetworkInterface を指定します。 DeviceIndex NetworkInterfaceのアタッチ順序です。プライマリネットワークの場合は 0 を指定する必要があります。 インスタンス起動時にNetworkInterfaceを作成する場合は必須指定です。 BlockDeviceMappings https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html インスタンスにアタッチするブロックデバイスを定義します。 DeviceName デバイス名を定義します。 ※ ルートボリュームを操作する場合は /dev/xvda を指定します。 https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/ Ebs https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html インスタンス起動時にEBSボリュームをセットアップするためのセクションです。 VolumeType ボリュームタイプを指定します VolumeSize ボリュームサイズをGB単位で指定します。 DeleteOnTermination true ならインスタンス終了時にボリュームを削除します。 Encrypted true ならボリュームを暗号化します Iops iopsの指定が可能なボリュームタイプにおいてiopsを指定します。 gp3 なら 3000 ~ 16000。 UserData インスタンス起動時に実行されるスクリプトを定義します。(base64エンコードされている必要があります) dev.yml # 踏み台サーバー: https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html BastionServer: Type: AWS::EC2::Instance Properties: ImageId: ami-02892a4ea9bfa2192 KeyName: !Ref KeyPairBastion InstanceType: t2.micro NetworkInterfaces: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-network-iface-embedded.html - NetworkInterfaceId: !Ref BastionXface01 DeviceIndex: 0 BlockDeviceMappings: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-mapping.html - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/ Ebs: # https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-blockdev-template.html VolumeType: gp2 VolumeSize: 10 DeleteOnTermination: true Encrypted: true UserData: !Base64 | #!/bin/bash sudo yum install -y git tmux Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ec2-bastion ◎ 踏み台サーバー用のNetworkInterfaceを作成 パブリックサブネットに踏み台サーバー用のNetworkInterfaceを作成します。 ※ BastionServer セクションの NetworkInterfacesId で指定されているリソースです。 SubnetId NetworkInterface を紐づけるサブネットを指定します。 GroupSet NetworkInterface に紐づけるセキュリティグループを指定します。 SourceDestCheck 受信時にインスタンスが送信元・受信先のいずれかであることをチェックする。 dev.yml BastionXface01: Type: AWS::EC2::NetworkInterface Properties: SubnetId: !Ref PublicSubnet01 GroupSet: - !Ref SecurityGroupBastion SourceDestCheck: true Tags: - Key: Name Value: !Sub ${ProjectPrefix}-xface-bastion ◎ 踏み台サーバー用のNetworkInterfaceにグローバルIPを紐づけ 先ほど作成した認証サーバー用のNetworkInterfaceにElasticIPを紐づけます。 AllocationId NetworkInterface に紐づけるElasticIPのAllocationIdを指定します。 NetworkInterfaceId ElasticIPに紐づけるNetworkInterfaceIdを指定します。 dev.yml EipAssocBastionXface01: Type: AWS::EC2::EIPAssociation Properties: AllocationId: !GetAtt EipBastion.AllocationId NetworkInterfaceId: !Ref BastionXface01 ◎ 開発サーバー 開発サーバーを作成します。 開発サーバーは踏み台サーバー経由でログインするので、グローバルIPは必要ありません。プライベートサブネットに配置し、一応ローカルIPを指定しておきます。 SubnetId インスタンスを起動するサブネットを指定します。 ※ NetworkInterfaceセクションを指定している場合は、ここではなくNetworkInterfaceセクション内でセキュリティグループを指定する必要があります。 PrivateIPAddress プライマリIPv4アドレスを指定します。IPはサブネットの範囲で指定しなければなりません。 SecurityGroupIds セキュリティグループを指定します。 ※ NetworkInterfaceセクションを指定している場合は、ここではなくNetworkInterfaceセクション内でセキュリティグループを指定する必要があります。 dev.yml DevServer: Type: AWS::EC2::Instance Properties: ImageId: ami-02892a4ea9bfa2192 KeyName: !Ref KeyPairBastion InstanceType: t2.micro SubnetId: !Ref PrivateSubnet01 PrivateIpAddress: 10.91.11.10 SecurityGroupIds: - !Ref SecurityGroupDev BlockDeviceMappings: - DeviceName: /dev/xvda # https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudformation-root-volume-property/ Ebs: VolumeType: gp2 VolumeSize: 16 DeleteOnTermination: false Encrypted: true UserData: !Base64 | #!/bin/bash sudo yum install -y git tmux Tags: - Key: Name Value: !Sub ${ProjectPrefix}-ec2-dev ■ キーペアの作成 AWSコンソールのEC2の画面からsshで利用するキーペアを作成します。 秘密鍵(hogehoge.pem)はローカル環境の ~/.ssh/ 配下に配置します。 (パーミッションを 400 に変更するのを忘れずに) ■ スタックの作成 AWSコンソールからテンプレートをアップロードして、パラメータ( ProjectPrefix KeyPairBastion ) を指定してスタックを作成します。 ■ sshの設定 Host bastion HostName xxx.xxx.xxx.xxx # Outputsで出力されるPublicIpBastionの値 User ec2-user IdentityFile ~/.ssh/secret.pem # キーペアの作成で作成した秘密鍵を指定 Host dev HostName 10.91.20.10 User ec2-user IdentityFile ~/.ssh/secret.pem # キーペアの作成で作成した秘密鍵を指定 ProxyCommand ssh -W %h:%p bastion # 開発サーバーは踏み台サーバーをプロキシして接続する ssh dev ■ 参考記事 AWS CloudFormation User Guide CloudFormationを使ってNAT Gatewayを構築する | Qiita 初学者のためのAWS入門(3)-CloudFormation入門2 | Qiita 【CloudFormation入門】5分と6行で始めるAWS CloudFormationテンプレートによるインフラ構築 | DevelopersIO CloudFormation の参照周りで意識すべきポイント・Tips | DevelopersIO CloudFormation入門 – ハマリポイント・注意点 【図解/AWS】インターネットGWとNAT-GWの違い〜各メリット、パブリックサブネットとは〜
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS Certified Cloud Practitionerを受けようと思った人へ

なぜ書くのか 自分がこの試験に興味を持った時に、「勉強法から受験方法までを流し読みできる記事」があればいいと思ったから 特に受験方法(オンライン試験の時間帯や当日のやり取り)はあまり情報がなくて不安だったから(→ 「受験してみての感想」を参照) なぜ受けたのか 仕事でAWS使いそうだけど、知識がまったくなかったから AWSサービス名となんとなくの役割を理解して、会議やドキュメントを理解できるようになるため (Djangoの勉強してたらクラウドでサーバー立ててみたくなったから) 筆者について 社会人3年目 メーカーでソフトウェア開発担当 クラウド開発経験ゼロ 勉強方法 作戦 初級の試験かつ選択式の問題ということで、とにかく過去問の数をこなす方針を立てました。 勉強時間 約2週間 平日1時間、休日3, 4時間ほど 実際にやってみて、多すぎず少なすぎずちょうどいい勉強時間だったと思います。サーバー管理やデプロイに関する知識・経験がもとからあれば、この半分くらいの勉強時間で合格できる印象です。 実際にやったこと AWSの講座(AWS Cloud Practitioner Essentials) 1周 目的としては過去問を解く土台を作ること 多少分からない説明があってもとりあえず早く終わらせて過去問に入ることを優先していました。 Udemyの過去問題集(この問題だけで合格可能!AWS 認定クラウドプラクティショナー 模擬試験問題集) 基礎と応用2週ずつ 目的は、①問題の形式に慣れることと ②合格できるレベルに達すること ここでは分からない問題についてじっくり考えるようにしました。 特に初級者にとっては似たようなサービスが多くみえ、違いが分からなくなることが多々ありました。その度に立ち止まって理解を整理するようにしていました。その方が闇雲に解くより効果があったと思います。 受験結果 810点/1000点で合格 受験してみての感想 受験の申し込みなどの準備段階でけっこうつまずきました. まず、オンライン試験(ピアソンVUE)が平日しかやってない。しかも時間帯が就業時間に丸被り(タイミングで変わるのかもしれませんが8:00~17:00くらい)でした。泣く泣く昼休みをつぶして受験する羽目に・・・ 試験時にオンラインで確認される書類が結構厳しいです。英語氏名記載の身分証明書がいるのでほぼパスポート一択です。自分はパスポートが見当たらず家中探すはめになりました。パスポート持ってない人どうなるんだろう・・ 試験時の机の確認が厳しかったです。試験開始時に机の周り試験官にカメラで確認されるのですが、自分は机にクランプ式の照明まで外すように指示されました。机の上には本当に何もない状態で準備しておく方が良いです。あせります。 当初の目的通りAWSのなんとなくの名称とサービスの役割は理解できました。実務で役に立つレベルではないですが、試験合格を目標に頑張れたのでダラダラ勉強するよりは良かったと思います。 受験料が高い。。。税込み12100円はけっこう痛い出費です。オンライン受験なら安くなるとかあればいいのに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む