AWS SDKを使ったアプリケーションを効率よく開発する方法

前田です。
先日、AWS認定デベロッパー試験に無事に合格することができました。
得点が69%で結構ギリギリな点数だったと思いますが、なんとか合格出来てよかったです。

今日は、AWS SDKを使ったアプリケーションを開発する時に自分なりに行っている効率がよいと思っている方法をまとめたいと思います。
例として、下記ケースを題材に開発してみたいと思います。

題材

先日、「AWS Batchで非同期ジョブシステムが簡単に構築出来た話」という記事を書きました。
その時に下記のようなシステムを作った、という話をさせて頂いたのですが、改善したい箇所が1点ありました。

それは②のGoで作ったAPIからAWS Batchにジョブをサブミットする箇所です。
AWS Batchにジョブをサブミットするのは下記のようなコマンドでサブミットできます。

aws batch submit-job \
  --job-name submit_job_test \
  --job-queue arn:aws:batch:ap-northeast-1:99999999999999:job-queue/test-job-queue \
  --job-definition arn:aws:batch:ap-northeast-1:9999999999999:job-definition/test-job-definition:1 \
  --parameters Hoge=test!

Goアプリケーション側でGoのAWS SDKを使用して上記のような処理を実装しています。
サブミットする時にジョブキューやジョブディフィニジョンのARNを指定しているのですが、ジョブディフィニジョンを更新した時はジョブディフィニジョンのARNが変わるのでGoアプリケーションのジョブディフィニジョンのARNを変えなければいけません。
Goのアプリケーション内ではジョブディフィニジョンは定義してある環境変数から読み込むようにしておりますので、ソースコードではなくジョブディフィ二ジョンの環境変数を変更すればOKなのですが、AWS Batch側のジョブディフィニジョンを更新した時に、Goアプリケーション側のジョブディフィ二ジョンARNの環境変数を変え忘れる、という危惧があります。
ジョブディフィニジョンを更新した時にGo側の環境変数を更新するのを忘れなければいいので現在の設計でも問題はないかもしれませんが、Go側の変更漏れなどのミスを事前に防ぐ為に出来ればジョブディフィニジョンの名前だけで最新のARNを自動取得してAWS Batchにサブミットするようにしておきたいと考えていました。

まとめると、やりたいことは下記のとおりです。

  • Goアプリケーションで、ジョブディフィニジョンの最新のARNをジョブディフィニジョン名で取得する

この題材を下記のステップで実装していきます。

  1. フィジビリティチェック
  2. SDKのリファレンスを見ながら実装
  3. 実行して検証

1. フィジビリティチェック

いきなりGoのアプリケーションをコーディングしていくのではなく、まずフィジビリティ(実行可能性)をチェックします。

  • Goアプリケーションで、ジョブディフィニジョンの最新のARNをジョブディフィニジョン名で取得する

は、なんとなく出来そうだ、と思っていますが、AWS側が対応出来ていなくて実現出来ない可能性もあります。
なので実現可能かどうか、というのをまず最初にチェックしておくことが重要です。

ここで私はaws cliを使います。
AWSのGoのSDKのリファレンスを見ながら実際にソースコードを書いてみてレスポンスを見たりして、、、という風にチェックしてもいいかもしれませんが、時間がかかるし面倒です。
フィジビリティチェックなので簡単に実現可能かどうかだけをチェックしたい、というのもあります。

また、aws cliに用意されているAPIはSDKにもほとんど用意されています。(と思っています)
なのでaws cliで実現可能ならほとんどのSDKでも実現可能だと思います。
まれにSDK側が追いついていないこともあるかと思いますが、基本的にはaws cliにSDKが追従していっていると思っています。
また、もしSDK側の機能がまだ実装されていないとしても、最悪Goアプリケーションからaws cliコマンドを直接実行することによって可能になるので、aws cliで実現可能、ということが保証されればフィジビリティ的にはOKということになります。
できればGoアプリケーションからaws cliコマンドを叩く、という実装はやりたくありませんが。。

ということで何はともあれフィジビリティをaws cliでチェックしていきます。

AWS Batchのaws cliのドキュメンテーションはこちらです。

http://docs.aws.amazon.com/cli/latest/reference/batch/index.html#cli-aws-batch

Available Commandsは下記のとおりですが、

  • cancel-job
  • create-compute-environment
  • create-job-queue
  • delete-compute-environment
  • delete-job-queue
  • deregister-job-definition
  • describe-compute-environments
  • describe-job-definitions
  • describe-job-queues
  • describe-jobs
  • list-jobs
  • register-job-definition
  • submit-job
  • terminate-job
  • update-compute-environment
  • update-job-queue

該当しそうなものはdescribe-job-definitionsと分かります。

describe-job-definitionsの詳細を見てみると、--job-definition-nameというオプションで絞り込むことも出来そうだし、なんとなく実現できそう、ということが分かりました。
AWS Batch上にジョブキューやジョブディフィニジョンを作成し、実際にaws cliコマンドを叩いてみます。


ここで少し話は逸れますが、aws cliコマンドを試す時に手軽に試せるようになるテクニックを紹介します。
開発者の皆様は複数のAWSアカウントをお持ちの方が多いかと思います。
私もプライベート・会社用など複数のアカウントを所持しています。
複数のアカウントを所持している場合、AWSアクセスキーとAWSシークレットキーを~/.aws/config~/.aws/credentialsで定義し、aws cliコマンドに--profileオプションを付加してアカウントを使い分けているかと思います。
私もそうなのですが、aws cliコマンドを叩く時に毎回--profileオプションを設定するのが面倒だな、と思っていました。
AWS_DEFAULT_PROFILEの環境変数に設定すれば、そのセッション中は--profileオプションを付加せずにawsコマンドを実行できるので便利なのですが、頻繁にアカウントを変えて実行したい時はまだちょっと不便に感じます。
なので、pecoを使用し、下記alias設定をしておくことで簡単に切り替えられるようにしたら便利になりました。

alias p='export AWS_DEFAULT_PROFILE=$(grep -iE "^[]+[^*]" ~/.aws/credentials|tr -d []|peco)'

これで、pと叩けばインタラクティブに実行したいアカウントに切り替えでき、そのあとは--profileオプション無しにawsコマンドを実行出来るようになります。


話が逸れましたが本題に戻ります。
describe-job-definitions--job-definition-nameオプションを付与して実行してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
$ aws batch describe-job-definitions --job-definition-name test-job-definition
JOBDEFINITIONS arn:aws:batch:ap-northeast-1:999999999999:job-definition/test-job-definition:2 test-job-definition 2 INACTIVE container
CONTAINERPROPERTIES busybox 2000 1
COMMAND echo
COMMAND 'hello world'
JOBDEFINITIONS arn:aws:batch:ap-northeast-1:999999999999:job-definition/test-job-definition:3 test-job-definition 3 ACTIVE container
CONTAINERPROPERTIES busybox 2000 1
COMMAND echo
COMMAND Ref::Hoge
JOBDEFINITIONS arn:aws:batch:ap-northeast-1:999999999999:job-definition/test-job-definition:1 test-job-definition 1 INACTIVE container
CONTAINERPROPERTIES busybox 2000 2
COMMAND echo
COMMAND 'hello world'

簡単に取得出来ました。
ちゃんとARNの情報も取得出来ているので大丈夫そうです。
ACTIVEINACTIVEがあるのでACTIVEのものだけに絞り込めそうです。

1
2
3
4
5
$ aws batch describe-job-definitions --job-definition-name test-job-definition --status ACTIVE
JOBDEFINITIONS arn:aws:batch:ap-northeast-1:999999999999:job-definition/test-job-definition:3 test-job-definition 3 ACTIVE container
CONTAINERPROPERTIES busybox 2000 1
COMMAND echo
COMMAND Ref::Hoge

出来ました。
しかし、もしACTIVEが複数ある場合は複数取得してしまいます。
また、ジョブディフィニジョンの作成日、などもレスポンスとして返ってこないため、作成が一番新しいもの、で取得することもできないようです。
が、ARNを見ると、arn:aws:batch:ap-northeast-1:999999999999:job-definition/test-job-definition:3となっており、この最後の3という数値が一番大きいものが最新のジョブディフィニジョンになるので、Goアプリケーションのほうでその判定をしてやればいけそうです。

Goのテストアプリケーションを実装して、ということだとちょっと面倒そうでしたが、aws cliであれば簡単にフィジビリティを確認することが出来ました。
ということで次のステップにいきます。

2. SDKのリファレンスを見ながら実装

ここでSDKのリファレンスを見ます。
GoのAWS Batchのリファレンスはこちらです。

https://docs.aws.amazon.com/sdk-for-go/api/service/batch/

ずばりDescribeJobDefinitionsというメソッドがありました。
Exampleのコードを基にコーディングしました。

main.go

package main

import (
    "errors"
    "fmt"
    "github.com/aws/aws-sdk-go/aws"
    "github.com/aws/aws-sdk-go/aws/awserr"
    "github.com/aws/aws-sdk-go/aws/session"
    "github.com/aws/aws-sdk-go/service/batch"
    "os"
    "strconv"
    "strings"
)

func main() {
    result, err := getJobDefinitionARNByName(os.Getenv("JOB_DEFINITION_NAME"))
    if err != nil {
        println(err.Error())
    } else {
        fmt.Println(result)
    }
}

func getJobDefinitionARNByName(jobDefinitionName string) (string, error) {
    svc := batch.New(session.New())
    input := &batch.DescribeJobDefinitionsInput{
        Status:            aws.String("ACTIVE"),
        JobDefinitionName: aws.String(jobDefinitionName),
    }

    result, err := svc.DescribeJobDefinitions(input)
    if err != nil {
        if aerr, ok := err.(awserr.Error); ok {
            switch aerr.Code() {
            case batch.ErrCodeClientException:
                fmt.Println(batch.ErrCodeClientException, aerr.Error())
            case batch.ErrCodeServerException:
                fmt.Println(batch.ErrCodeServerException, aerr.Error())
            default:
                fmt.Println(aerr.Error())
            }
        } else {
            fmt.Println(err.Error())
        }
        return "", err
    }

    ARN, err := getLatestARN(*result)
    if err != nil {
        return "", err
    }

    return ARN, nil
}

func getLatestARN(output batch.DescribeJobDefinitionsOutput) (string, error) {
    if len(output.JobDefinitions) == 0 {
        return "", errors.New("No defenitions")
    }
    comparition := 0
    ARN := ""
    for _, d := range output.JobDefinitions {
        arn := strings.Split(*d.JobDefinitionArn, ":")
        c := arn[len(arn)-1]
        i, _ := strconv.Atoi(c)
        if comparition < i {
            comparition = i
            ARN = *d.JobDefinitionArn
        }
    }
    return ARN, nil
}

3. 実行して検証

下記環境変数をセットして実行します。

1
2
3
4
AWS_REGION
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
JOB_DEFINITION_NAME

期待通りに取得できました。

まとめ

結局、aws cliでフィジビリティを確認する、というところがポイントで、もし無理なら実装しなくていいし、実装できるならaws cliで呼び出した方法を参考に簡単にSDKで実装できる、ということでした。
実は今回はAWS Batch上に検証用のジョブキューとジョブディフィニジョンがすでにあったのでaws cliコマンドですぐに確認できましたが、AWS側に環境がない場合は構築するのは面倒なのでリファレンスだけでフィジビリティを判定する場合もあるかと思います。
なのですべてのケースにおいて、この方法が最良かどうかは分かりませんが、aws cliで実際に返ってきたレスポンスを見ながらフィジビリティチェックしておくと、その後の実装はかなり安心です。
以上、お役に立てれば幸いです。

このエントリーをはてなブックマークに追加