AWS CDK で類似学生計算エンジンを定義して、 IaC の良さを感じる②
はじめに
こんにちは!株式会社POLでエンジニアをやっている @mejihabenatawa です!
POLは「研究者の可能性を最大化するプラットフォームを創造する」をビジョンに、理系学生に特化した採用サービス、および研究開発者・技術者に特化した転職/採用サービスの2サービスを運営しています。
前回のテックブログで AWS CDK で類似学生計算エンジンの全体構成について紹介したので、今回は CDKにおける AWS Glue 部分の書き方を紹介していきたいと思います。
1. 全体構成編
2. AWS Glue 編(今回)
3. AWS Batch 編
4. AWS Step Functions etc 編
CDK プロジェクトの始め方
こちらのリンクのワークショップがわかりやすいと思います。
詳細は説明しませんが、cdk init でプロジェクトを初めて、cdk deploy でアプリケーションを deploy することができます。
cdk init sample-app --language typescript
デフォルトの主なファイル構成は以下のような感じで、lib 配下を主に触ります。今回は bin 配下の cdk.ts や cdk.json も触りました。
(ディレクトリ名:今回はcdk)
|
|-- bin
|-- lib
|-- node_modules
|-- test
|-- cdk.json
|-- README.md
|--
|--
事前に用意するスクリプト
・calculate_vector.py(アプリケーションのRDSに接続してデータを取得し、特徴量からベクトルの計算を行う)
ベクトルの計算処理を行う Python スクリプトは事前に用意し、project のglue/src 配下においておきます。
今回使用する主なファイルの構成は以下になります。
-- project
|
|-- batch -- AWS Batch 関連のディレクトリ
|
|-- cdk -- AWS CDK 関連のディレクトリ
| |-- bin
| | |-- cdk.ts
| |
| |-- lib
| | |-- cdk-stack.ts
|
|-- glue -- AWS Glue 関連のディレクトリ
| |-- src
| |-- calculate_vector.py -- ベクトルを計算する Python スクリプト
|
|-- README.md
|
|-- cdk.json
AWS Console にて、事前に用意するもの
・Glue の script ファイルをアップロードする s3 バケット
dev : "sample.dev"
stg : "sample.stg"
prd : "sample.prd"
・Glue の script をアップロードするためのフォルダ
dev/stg/prd : "application/glue/src"
各環境に以下のようなフォルダを作ればOKです。
環境変数の定義の仕方
AWS Glue ではそんなにハマったポイントはなかったので、環境変数の定義の仕方を説明したいと思います。弊社では Deploy する環境が開発環境、ステージング環境、本番環境の3種類あります(それぞれ、dev, stg, prd)。AWS CDK ではいくつか環境変数の定義の仕方があるのですが、今回は cdk.json と デプロイ時の --context オプションを利用しました。
デフォルトの cdk.json は以下です。
{
"app": "npx ts-node --prefer-ts-exts bin/devio.ts",
"context": {
"@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true,
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
"@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
"@aws-cdk/aws-kms:defaultKeyPolicies": true,
"@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
"@aws-cdk/aws-ecs-patterns:removeDefaultDesiredCount": true,
"@aws-cdk/aws-rds:lowercaseDbIdentifier": true,
"@aws-cdk/aws-efs:defaultEncryptionAtRest": true
}
}
デプロイ環境に合わせて設定できるように修正します。今回はデフォルトの変数以外に "ENV", "DataBucketName", "GlueScriptLocation", "GlueConnectionName" というものを設定しました。
今回のアプリケーションでは、Glue から RDS に接続しているので、"GlueConnectionName" という変数も設定していますが、Python スクリプトから RDS に接続することがなければ、必要ないので詳細は省きます。
{
"app": "npx ts-node --prefer-ts-exts bin/devio.ts",
"context": {
"dev": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
"@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
"@aws-cdk/aws-kms:defaultKeyPolicies": true,
"@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
"Env": "dev"
"DataBucketName": "sample.dev",
"GlueScriptLocation": "application/glue/src",
"GlueConnectionName": "hogehoge"
},
"stg": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
"@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
"@aws-cdk/aws-kms:defaultKeyPolicies": true,
"@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
"Env": "stg",
"DataBucketName": "sample.stg",
"GlueScriptLocation": "application/glue/src",
"GlueConnectionName": "hogehoge"
},
"prd": {
"@aws-cdk/core:enableStackNameDuplicates": "true",
"aws-cdk:enableDiffNoFail": "true",
"@aws-cdk/core:stackRelativeExports": "true",
"@aws-cdk/aws-ecr-assets:dockerIgnoreSupport": true,
"@aws-cdk/aws-secretsmanager:parseOwnedSecretName": true,
"@aws-cdk/aws-kms:defaultKeyPolicies": true,
"@aws-cdk/aws-s3:grantWriteWithoutAcl": true,
"Env": "prd",
"DataBucketName": "sample.prd",
"GlueScriptLocation": "application/glue/src",
"GlueConnectionName": "hogehoge"
},
}
}
以下のように deploy 時の context オプションを利用することで変数をアプリケーションに渡すことができます。
cdk deploy -c env="dev"
アプリケーションで変数を使う際には、App のインスタンスを作成する cdk.ts の中で tryGetContextメソッドで 環境名 を取得し、さらにその環境名を使って tryGetContext メソッドで、その他の環境変数をオブジェクトに格納します。そのオブジェクトを CdkStack オブジェクトにわたせば、利用できるようになります。
cdk.ts
import "source-map-support/register";
import * as cdk from "@aws-cdk/core";
import { CdkStack } from "../lib/cdk-stack";
const app = new cdk.App();
const env = app.node.tryGetContext("env") || "dev";
const tmp = app.node.tryGetContext(env);
const context = {
ENVStage: env,
DataBucketName: tmp.DataBucketName,
GlueScriptLocation: tmp.GlueScriptLocation,
GlueLibraryLocation: tmp.GlueLibraryLocation,
GlueConnectionName: tmp.GlueConnectionName,
}
new CdkStack(app, `RecommendEngineStack-${env}`, context, {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: "ap-northeast-1",
},
});
CDK上で作成するもの
最後に今まで用意したものをCDK上でどのように記述するのかを説明します。用意したものを用いて、s3に Python スクリプトをアップロードし、 IAM ロールを定義して、最後にそれらすべてを用いて、Glue job を定義します。
cdk.json
import * as cdk from '@aws-cdk/core';
import * as glue from "@aws-cdk/aws-glue";
import * as s3 from '@aws-cdk/aws-s3'
import * as iam from "@aws-cdk/aws-iam";
import * as s3Deploy from '@aws-cdk/aws-s3-deployment'
export interface Context {
ENVStage: string;
DataBucketName: string,
GlueScriptLocation: string;
GlueLibraryLocation: string;
GlueConnectionName: string;
}
export class CdkStack extends cdk.Stack {
constructor(
scope: cdk.Construct,
id: string,
context: Context,
props?: cdk.StackProps
) {
super(scope, id, props);
// #region Glue
// Glue の script をアップロードする先のS3バケット
const dataBucket = s3.Bucket.fromBucketAttributes(this, 'DevDataBucket', {
bucketArn: `arn:aws:s3:::${context.DataBucketName}`
});
// Glue の script を S3 にアップロードする
new s3Deploy.BucketDeployment(this, 'GlueScriptDeploy', {
// アップロードするデータの場所を指定します。事前に用意した calculate-vectors.py のパスを指定する
sources: [s3Deploy.Source.asset('../glue/src')],
// アップロード先のバケットは一つ前で読み込んだものです
destinationBucket: dataBucket,
destinationKeyPrefix: context.GlueScriptLocation
})
// Glue 用の IAM を作成
// Glue のサービスロールを付与
// 必要であれば、S3へのアクセス権限を付与
const glueIAMRole = new iam.Role(
this,
`CalculateVectorsIAMRole-${context.ENVStage}`,
{
assumedBy: new iam.CompositePrincipal(
new iam.ServicePrincipal("glue.amazonaws.com")
),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName(
"service-role/AWSGlueServiceRole"
),
],
inlinePolicies: {
inlinePolicies: iam.PolicyDocument.fromJson({
Version: "2012-10-17",
Statement: [
{
Sid: "ListObjectsInBucket",
Effect: "Allow",
Action: ["s3:ListBucket"],
Resource: [`arn:aws:s3:::${context.DataBucketName}`],
},
{
Sid: "AllObjectActions",
Effect: "Allow",
Action: "s3:*Object",
Resource: [`arn:aws:s3:::${context.DataBucketName}/*`],
},
],
}),
},
}
);
// Glue Job
const glueJob = new glue.CfnJob(
this,
`CalculateVectors-${context.ENVStage}`,
{
name: `CalculateVectors-${context.ENVStage}`,
command: {
name: "glueetl",
pythonVersion: "3",
scriptLocation: `s3://${context.DataBucketName}/${context.GlueScriptLocation}/calculate-vectors.py`,
},
role: glueIAMRole.roleArn,
numberOfWorkers: 2,
connections: {
connections: [context.GlueConnectionNa
},
glueVersion: "2.0",
workerType: "G.1X",
defaultArguments: {
"--additional-python-modules": "torch==1.8.1 などの python スクリプトで必要なライブラリ名",
"--python-modules-installer-option": "--upgrade",
"--data_bucket_name": context.DataBucketName,
"--connection_name": context.GlueConnectionName,
}
}
);
// #endregion Glue
}
}
注意点はコメントに記載しましたが、Glue のスクリプトを S3 にアップロードする際のパスの指定や Iam にどんな権限を付与するかなどがあると思います。
また、Glue Job についてもオプションでいろんなパラメータを渡せるので、公式ドキュメントを見ながら書くのが良いと思います。
まとめ
今回は AWS CDK における AWS Glue の定義の仕方を説明しました。ちょっとごちゃごちゃしてきたので、次回までにサンプルコード を用意したいと思います(計算処理のスクリプトの中身は空になりますが。)
今まで Glue のスクリプトのコード管理などが、少し煩わしかったのですが、CDK で定義して github などで管理するようになり、ストレスが減ったのを実感しているので、是非試してみてください。
次回は AWS Batch の書き方を説明していきたいと思います。
株式会社POLではエンジニア、デザイナー、プロダクトマネージャーを大募集してます!お話しだけでも構いませんのでお気軽にお声がけください!!!