VPC内でLambda to Athenaを実現する

# はじめに
VPC内で全体的に終わらせる方法がまとまってなくてめちゃくちゃハマったので、ここでまとめておきます。

# 今回のゴール
VPC内にあるAthenaに対してLabmda上からクエリを正常に投げて結果を取得する。
Security面の細かい話はここでは触れないので、ご了承ください。

# Athenaを使えるようにする

## Ahtenaの前にS3の準備
AthenaはS3上のファイルに対してSQLを投げれる仕組みみたいな感じのサービスになっています。
なので、まずはS3の準備を行います。

と言っても普通にバケットを作成するだけです。
ただし、今回はVPC内で全てを完結させたいので、「パブリックアクセスをすべてブロック」で作成するものとします。

## S3のVPCエンドポイントを作成する
S3にVPCから繋ぐにはエンドポイントが必要なので、VPCサービスからVPCエンドポイントを作成します。
この際のVPCには今回閉じ込めておきたいVPCを指定しておいてください（この後特に言及しませんが、VPC指定する際は全てこのVPCを設定してください）。

サブネットは分かるまで全部設定しておけば良いと思います。

## Athenaのテーブルを作成する
繋ぐ先ができたので、Athenaの設定をやっていきます。
Athenaの設定は基本的にAthenaの画面上から可能です。

Athenaの画面上からテーブルの作成に移りたいのですが、初期はよく分からないのと色んなリンクが足りてないため（一度作ると困らないので、その画面はもう見えませんが）、右上のチュートリアルから作成するのが良いと思います。

テーブルの中身は今回は特に問わないので、適当に作ってください。
ファイル形式もCSVとかTSVとかで大丈夫です。

### AthenaのPartitionの話
RDBでいうIndex付きのカラムみたいな感じで考えると良いかなと思います。
S3内ではこのPartitionに沿ってフォルダが構成され、ファイルが配置されます。

Athenaの料金体系的にスキャンしたサイズによってちゃりんちゃりんするという仕組みになっています。
Partitionをつけなかったり多くのPartitionを検索しまくると多くのお金がかかる形になるため、プロダクションで使うには必須と言える機能となっています。

しかし、Partitionに分けすぎるとSELECT時に多くのファイルを読み込むため性能が劣化すると言われていたり、INSERT時に制約に引っかかる可能性（1回のINSERTで１００個のPartitionにしかInsertできない）もあるため、ここはしっかり設計する必要があります。

## INSERTしてSELECTしてみる
あとはデータ入れて取得してみてください。
普通にINSERTとSELECTが出来るので、少量のデータを入れて取り出せます。

ここで失敗するのであればVPCエンドポイントが作られていないくらいしか考えられません。

# Lambdaの設定をやっていく
まずは関数を設定してください。
言語はどれでも問題ありませんが、私がいつもPythonで作っているため、コード上の説明はPythonで説明します。
ロールについては新設している前提で書きます。

# Lambdaの設定（タブ編）
セキュリティタブの設定は下記に書くIAMのロール設定の箇所なので割愛します。

## 基本情報
ここは実行してからいじっていけば良いと思います。
IAMの説明だけしておきます。

### Athenaを使うためのIAMロール
既存のポリシーをアタッチする前提とすると`AmazonAthenaFullAccess`と`AWSLambdaVPCAccessExecutionRole`を追加しておけばOKです。
本当なら（特に）Ahtena周りはLambdaで使用する権限のみを渡した方が良いとは思いますが、とりあえずの設定ということでフルにして話を進めます。

## VPC
今回使っているVPCを選択してサブネットは全部選択しておいてください。
セキュリティグループの設定は以下に書いておきます。

### セキュリティグループの設定
ちょっと通信がどのポートなのか微妙にわかってないので、とりあえずフルオープンの設定。
（多分443でやっているとは思っているのですが、まだ試せてません。）

本当はポート範囲とかはしっかり設定した方が良いです。

```
タイプ: すべてのトラフィック
プロトコル: 全て
ポート範囲: 全て 
ソース: 自分自身を設定しておいてください。
```

# コードをローカルで作成する
実行環境はlambda-dockerのようにDocker上で実行できる環境もありますが、ライブラリ読み込めなかったことがあったり別でlambda-docker用の環境用意したりが面倒という理由であんまり使ってません。
もちろんLambdaをテストでも実行するとちゃりんちゃりんしないといけないので、そこらへんはご自身の状況と照らしわせてください。

【参考URL】
https://qiita.com/anfangd/items/bb448e0dd30db3894d92

## Lambda環境でライブラリを使用する
Lambda環境でpipは使えないため、ローカルでインストールしたものをコードと一緒にアップロードしてあげる必要があります。
今回はよくある`boto3`ではなく、`lambda-pyathena`というLambda環境でAthenaを簡単に使用するためのライブラリがありそれを使います。
ローカル上では以下のコマンドように`-t`オプションでソースコードと同じ場所にインストールしておけばOKです。

```
pip install lambda-pyathena -t .
```

【参考URL】
https://pypi.org/project/lambda-pyathena/

## 実コード部分
pythonコード部分はシンプルです。

```
from pyathena import connect
import os

def lambda_handler(event, context):
    cursor = connect(aws_access_key_id="YOUR ACCESS KEY",
                     aws_secret_access_key="YOUR SECRET ACCESS KEY",
                     s3_staging_dir="S3 PATH FOR RESULT",
                     region_name=os.environ['AWS_REGION'],
                     work_group="YOUR WORK GROUP",
                     schema_name="YOUR SCHEMA").cursor()
    cursor.execute("SELCT * FROM USERS;")
    print(cursor.description)
    print(cursor.fetchall())

    return {}
```

### aws_access_key_idとaws_secret_access_keyについて
Athenaに繋ぐにはLambdaが実行する際に設定される環境変数ではダメでした(なぜかは分からない)。
なので、セキュリティ認証情報メニューからアクセスキーを発行して設定してあげる必要があります。

region_nameについては文字列で設定できますが、大抵は同じreasionにあると思うので、環境変数からとるコードにしています。

### work_groupとschema_nameについて
これはAthena見ればすぐ分かります。

### execute出来るSQL
SELECTとINSERTは可能ですが、他は（多分）出来ません。
SELECTはきちんとPartition設定して設定しないと破産するとかINSERTが遅いとか注意点はありますが、知ってるやつで取得できるのは楽ですね。

`CREATE TABLE`も出来ますが、あんまりLambdaからはやらないかなと思って省きました。
細かいところはLambdaの仕様書やlambda-pyathenaの仕様書読んでください。

## コード書き終わったあと
コードを丸ごとZip化してアップロードします。
ただし、アップロードしてもまだ実行できません。
正確にいうと、実行してもAthenaへの接続部分でタイムアウトします。

エラーが出てくれないので、ここでマジハマりました。

# AthenaとGlueのVPCエンドポイントを設定する
Athenaはデータの管理をGlueでやっています。
Glueで見ると上で作ったデータベースやテーブルがあるかと思います。

この2つにVPC内から繋ぐにはVPCエンドポイントを設定する必要があるため作成しておきます。

```
サービスカテゴリ: AWS
サービス名: com.amazonaws.ap-northeast-1.athena, com.amazonaws.ap-northeast-1.glue
VPC: 今回使用しているVPC
サブネット: 全部チェック
プライベート DNS 名を有効にする: チェックする
セキュリティグループ: Lambdaと同じセキュリティグループ
```

# 実行
これで全ての設定が終わったのでLambda上から実行しましょう。
パラメータを渡すことができますが、簡単なのでここでは割愛します。

無事思った通りの動作をしたら設定は完了です。

# 終わりに
VPC内のLambdaの話が全然なくてめちゃくちゃハマりましたが、とりあえず出来た良かった。