たそらぼ

日頃思ったこととかメモとか。

GoでS3に格納したファイルにバッチ処理するDockerイメージを作りたい。

S3に格納されたファイルがあって、バッチ処理するためのdockerコンテナが作りたかったのでやって見ました。
言語はGolangです。

バッチ処理のやりたいこと

今回はS3に格納されたファイルをダウンロードしてきて、違う名前で再アップロードするバッチ処理です。
f:id:tasotasoso:20190915201618p:plain

特に、AWS Batch(AWS Batch とは - AWS Batch)のようなサービスでHPCを行う想定で作っています。
AWS Batchだと、処理対象のデータはS3かEFSになるので、元データの落とし方と処理結果の格納方法さえ分かれば後はなんとかなるだろうという寸法です。

バッチ処理のコード

基本的にはGo SDKの公式ドキュメント(https://docs.aws.amazon.com/sdk-for-go/api/service/s3/)を見るとそのまま書いているので、愚直にやっていきます。
サンプルコードはこちらです。
github.com

セッションの確立

まず、AWSとのセッションを作成します。
session.NewSession()で普通にセッションを作ると、環境変数を読みに行ってくれます。
credentialsの認証情報でもいけるはずですが、未検証です。

// Region, AccesskeyID & SeacretAccessKey is specified in environment.
sess, err := session.NewSession()

session.NewSessionWithOptions()で認証情報などをハードコードすることも可能です。
Resionなんかはそんなに変わらないのでハードコードしちゃっても良いと思いますが、アクセスキーとシークレットアクセスキーを書き込むのは非推奨です。

// Specifying Region. AccesskeyID & SeacretAccessKey is specified in environment.
sess, err := session.NewSessionWithOptions(session.Options{
        Config: aws.Config{
            Region: aws.String("ap-northeast-1"),
        },
    })

ダウンロード

s3manager.NewDownloader()でダウンロード用のインスタンスが生成できます。

// Create a downloader with the session and default options
    downloader := s3manager.NewDownloader(sess)

    // Create a file to write the S3 Object contents to.
    downloadContent, err := os.Create(localTmpFile)
    if err != nil {
        fmt.Println(err)
        return
    }

    // Write the contents of S3 Object to the file
    n, err := downloader.Download(downloadContent, &s3.GetObjectInput{
        Bucket: aws.String(bucketName),
        Key:    aws.String(downloadTarget),
    })
    if err != nil {
		fmt.Println(err)
		return
    }
    fmt.Printf("file downloaded, %d bytes\n", n)

アップロード

s3manager.NewUploader()でアップロード用のインスタンスが生成できます。

// Create an uploader with the session and default options
    uploader := s3manager.NewUploader(sess)

    uploadContent, err  := os.Open(localTmpFile)
    if err != nil {
        fmt.Println(err)
		return
    }

    // Upload the file to S3.
    result, err := uploader.Upload(&s3manager.UploadInput{
        Bucket: aws.String(bucketName),
        Key:    aws.String(uploadTaget),
        Body:   uploadContent,
    })
    if err != nil {
        fmt.Println(err)
		return
    }
    fmt.Printf("file uploaded to, %s\n", result.Location)

Dockerfile

SDKの認証情報は、ENV で設定すると読み込んでくれました。
~/.aws/credentialsでもいけるはずですがAWSサービス内で環境変数として渡してしまうユースケースの方が多いと思うので、dockerfileのENVで渡してしまうか、docker run の-eオプションで指定するのが良いのかなと思います(私感)。

FROM golang:1.12
ENV PKG_PATH /go/src/
ENV GO111MODULE on
WORKDIR $PKG_PATH
RUN go mod download
COPY ./ $PKG_PATH
RUN go install .

FROM golang:1.12
COPY --from=0 /go/bin /go/bin/
ENV AWS_ACCESS_KEY_ID=アクセスキーID
ENV AWS_SECRET_ACCESS_KEY=シークレットアクセスキー
ENV AWS_REGION=リージョン
ENTRYPOINT ["/go/bin/main"]

ハマったところ

session.NewSession()に認証情報を渡すのが意外とハマりました。session.NewSessionWithOptions()とか色々あるのでどれを使ったらいいのか分かりにくいのですが、認証情報がなければ環境変数を読みに行ってくれるので、あまり気にせず環境変数に埋めておくのが無難かなと思います。