見出し画像

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です。

スクリーンショット 2021-09-17 14.11.35

環境変数の定義の仕方

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ではエンジニア、デザイナー、プロダクトマネージャーを大募集してます!お話しだけでも構いませんのでお気軽にお声がけください!!!



この記事が気に入ったらサポートをしてみませんか?