Amazon Lightsail for Researchとのファイルのやり取り

# はじめに

先日、分析によくつかわれるアプリケーションがインストール済みのインスタンスがすぐに使えるサービス Amazon Lightsail for Research が発表されました。

https://aws.amazon.com/jp/about-aws/whats-new/2023/03/amazon-lightsail-research/

JupyterLabやR Studioなどが簡単に使えるようですが、そこにデータを持ってくる方法が気になったので調べてみました。

# 概要

- 小さいデータファイルなら、GUIが楽
    - 公式のSCPの方法は面倒
- 大きいデータは、アクセスキーを発行して(Python)SDKでのやり取りがよさそう
    - 「IAMロールをインスタンスにアタッチして」というようなのは無理そうです
- （おまけ）Polarisも動きそう

# やったこと
## インスタンス作成

以下のページが参考になります。

https://dev.classmethod.jp/articles/amazon-lightsail-research/

LightsailのTopの上部から専用の画面に遷移します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/43467de3-0e42-ff92-e434-fc90bc75375c.png)

リージョンと使いたいアプリケーションを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/fd8b3127-f828-31b3-9985-e16891e5759c.png)

あとは名前を付ければ作成開始です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/9a517b6c-870a-e9de-e40a-202cb3b200df.png)

最低インスタンスで、起動までには6分くらいかかりました。ちょっと長いです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/ce970c96-2f2b-5b49-c6e4-821290ed03dc.png)

起動すると以下のようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/590d184f-ff26-aba0-643f-3878d87c3a4e.png)

「JupyterLabを起動」を押すことで、ブラウザ上にJupyterLabの画面が表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/877818f8-7388-97de-6ad4-05c4a5855fd2.png)

## コピペを確認する
まずはコピペがちゃんとできるか確認します。

https://docs.aws.amazon.com/ja_jp/dcv/latest/userguide/using-copy-paste.html

うまくコピペできるようになると、画面左上のアイコンが以下のように吹き出しを表示するようになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/f941c7a8-46d3-a226-3e02-088282f55179.png)

最初にうまくいかない場合はインスタンスを再起動しました。

## SCP
公式の以下のページをなぞりました。

https://docs.aws.amazon.com/ja_jp/lightsail-for-research/latest/ug/tutorial-jupyter-notebook.html#jupyter-upload-download

:::note warn
大変面倒でした。この後に試した方法をお勧めします。
:::

### ペアキーの取得
CloudShellにてLightsailのキーペアを取得します。
```bash
aws lightsail download-default-key-pair --region ap-northeast-1 > dkp-details.json
cat dkp-details.json | jq -r '.privateKeyBase64' > dkp_rsa
```
自PCにダウンロードします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/079f4289-ea32-2206-ff71-9e9ac2906e33.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/d2d9c084-d9f0-d4b5-cd47-8ac83ebed888.png)

キーファイルの権限を変更する必要がありますが、Windowsで扱うならユーザ以下のディレクトリで作業すれば、権限変更は不要ですので、そこを作業場所としました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/dc83d860-d41b-8ce5-9f09-3f5b50e642db.png)

### 一旦、Lightsailにアップロード
CloudShellで、インスタンスの接続情報を取得します。
```bash
# 2か所にでてくるので、変数格納して使う
REGNAME=ap-northeast-1 && echo $REGNAME
INSTNAME=test202303 && echo $INSTNAME

aws lightsail get-instance --region $REGNAME --instance-name $INSTNAME \
 | jq -r ".instance.username" \
 & aws lightsail get-instance --region $REGNAME --instance-name $INSTNAME \
 | jq -r ".instance.publicIpAddress"
```

以下のCSVファイルのアップを試みます。
```csv:sampleData001.csv
source,destination
ANA,全日本空輸
全日空,全日本空輸
全日本空輸,全日本空輸
```
SCPでコピーですが、ユーザや権限などで、ubuntuユーザの以下の場所くらいにしか置けませんでした。
```bash
scp -i dkp_rsa sampleData001.csv ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu/hoge.csv
```

### JupyterLabで見える場所に移動

ubuntuユーザの場所から、JupyterLabで見える場所に移すためには画面操作が必要です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0a688e63-3c6c-cf81-b075-e82a9c620790.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3786900f-27ca-6e6b-164e-7b79f8b06ec3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/9aebc462-b438-c8e5-725f-2117039a2109.png)
やっとJupyterLabで開けるようになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/ff8abaf5-0983-fe66-f0cd-31699d696342.png)

逆方向も同様の面倒さでした。
```bash
scp -i dkp_rsa ubuntu@xxx.xxx.xxx.xxx:/home/ubuntu/hoge.csv moge.csv 
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/8783c34a-ad9e-b9ab-b1ec-288c67645bb3.png)

## GUI

:::note
こちらのほうが楽です。ファイルが小さい場合はこちらをオススメします。
:::

左上のアイコンでアップロードできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/8c2b9a3a-cf05-98ff-2fc2-f090e4db8957.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/dfe61305-0c6a-7890-e3ca-78fd96c18f0e.png)
すぐに見れました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/f4e22401-a232-8c94-aafa-4fea72f11a36.png)

ダウンロードも、ブラウザのダウンロード機能で落ちてきます、楽です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0f8ca07a-12fc-8dfc-06a0-5a21eead6403.png)

## S3経由
### 準備
以下のCloudFormationで、やり取り用のバケットとユーザを作成します。
```yaml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  DataBucket:
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub for-lightsail-${AWS::AccountId}-${AWS::Region}
      BucketEncryption: 
          ServerSideEncryptionConfiguration: 
            - 
              ServerSideEncryptionByDefault: 
                  SSEAlgorithm: 'AES256'
              BucketKeyEnabled: false
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  AccessUser:
    Type: AWS::IAM::User
    Properties:
      Policies:
        - PolicyName: accesss3
          PolicyDocument:
            Version: 2012-10-17
            Statement:
              - Effect: Allow
                Action: s3:ListAllMyBuckets
                Resource: arn:aws:s3:::*
              - Effect: Allow
                Action: s3:ListBucket
                Resource: !Sub arn:aws:s3:::${DataBucket}
              - Effect: Allow
                Action: s3:*Object
                Resource: !Sub arn:aws:s3:::${DataBucket}/*
```

アクセスキーを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/17d4a971-8421-d1a5-da66-0a1640c50e14.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/54cf914c-fa44-d349-4115-a0d836894217.png)

次はインスタンス上で操作します。boto3は入っていないので、そのインストールからです。
```python
!pip list
!pip install boto3

import boto3
```
### ダウンロード
```python
# clientなら
s3client = boto3.client('s3', aws_access_key_id='YOURACCESSKEY', aws_secret_access_key='YOURSECRETKEY')

s3client.get_object( Bucket='mybucket', Key='sampleData001.csv' )

body = response['Body'].read()
print(body.decode('utf-8'))

# resourceなら
s3resource = boto3.resource('s3',aws_access_key_id='YOURACCESSKEY', aws_secret_access_key='YOURSECRETKEY')

s3resource.meta.client.download_file('mybucket', 'sampleData001.csv', '/home/lightsail-user/hello.csv')
```
### アップロード
```python
# clientなら
response = s3client.put_object(
    Body='/home/lightsail-user/hello.csv',
    Bucket='mybucket',
    Key='hello2.csv',
)
print(response)

# resourceなら
s3resource.meta.client.upload_file('/home/lightsail-user/hello.csv', 'mybucket', 'hello3.csv')
```
S3にアップされていることを確認しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/c69a4ee8-2216-5eb5-d85b-12fbd8579071.png)


## Athena
S3でやり取りするのが現実的でないサイズのデータの場合を想定して、wranglerでAthenaを用いた方法も確認しました。

https://qiita.com/yomon8/items/9b401aca30f0d230e98c

### テーブル作成

以下のCloudFormationを実行します。

<details><summary>クリックで表示</summary><div>

アクセス用のユーザを作成します。出来合いのマネージドポリシーをあてがいます。

```yaml
AWSTemplateFormatVersion: 2010-09-09

Resources:
  AWSUser:
    Type: AWS::IAM::User
    Properties:
      ManagedPolicyArns: 
        - arn:aws:iam::aws:policy/AmazonAthenaFullAccess
```
作成したらS3の時と同様、アクセスキーを作成します。

次に格納バケット、出力バケット、ワークグループ、データベースを作成します。Envパラメータには環境名を名付けて入れてください。
バケット名の接頭辞は、ユーザにあてがったポリシーで許可されている文字列ですので変更しないでください。
```yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  Env:
    Type: String

Resources:
  RawDataBucket:
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub athena-examples-${AWS::AccountId}-${AWS::Region}-${Env}
      BucketEncryption: 
          ServerSideEncryptionConfiguration: 
            - 
              ServerSideEncryptionByDefault: 
                  SSEAlgorithm: 'AES256'
              BucketKeyEnabled: false
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  AthenaQueryResultBucket:
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub aws-athena-query-results-${AWS::AccountId}-${AWS::Region}-${Env}
      BucketEncryption: 
          ServerSideEncryptionConfiguration: 
            - 
              ServerSideEncryptionByDefault: 
                  SSEAlgorithm: 'AES256'
              BucketKeyEnabled: false
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      LifecycleConfiguration: 
        Rules: 
          - 
            Id: 'auto-delete'
            Status: 'Enabled'
            ExpirationInDays: 7

  AthenaWorkGroup:
    Type: AWS::Athena::WorkGroup
    Properties:
      Name: !Sub athena-work-group-${Env}
      RecursiveDeleteOption: true
      WorkGroupConfiguration:
        ResultConfiguration:
          OutputLocation: !Sub s3://${AthenaQueryResultBucket}/data
          EncryptionConfiguration: 
            EncryptionOption: 'SSE_S3'
        EnforceWorkGroupConfiguration: true
        PublishCloudWatchMetricsEnabled: true

  GlueDatabase:
    Type: AWS::Glue::Database
    Properties: 
      CatalogId: !Ref AWS::AccountId  
      DatabaseInput:
        Name: !Sub glue-database-${Env}

Outputs:
  RawDataBucket:
    Value: !Ref RawDataBucket
    Export:
      Name: !Sub "${Env}-RawDataBucket-Name"
  AthenaQueryResultBucket:
    Value: !Ref AthenaQueryResultBucket
    Export:
      Name: !Sub "${Env}-AthenaQueryResultBucket-Name"
  AthenaWorkGroup:
    Value: !Ref AthenaWorkGroup
    Export:
      Name: !Sub "${Env}-AthenaWorkGroup-Name"
  GlueDatabase:
    Value: !Ref GlueDatabase
    Export:
      Name: !Sub "${Env}-GlueDatabase-Name"
````

最後に環境上にテーブルを作成します。Envは先と同じ文字列を入れます。
```yaml
AWSTemplateFormatVersion: '2010-09-09'

Parameters:
  Env:
    Type: String
  TableName:
    Type: String
    Description: Table name must not contain uppercase characters.

Resources:
  GlueTable:
    Type: AWS::Glue::Table
    Properties:
      CatalogId: !Ref AWS::AccountId
      DatabaseName:
        Fn::ImportValue:
          !Sub "${Env}-GlueDatabase-Name"
      TableInput:
        Name: !Ref TableName
        TableType: EXTERNAL_TABLE
        Parameters:
          skip.header.line.count: 1
          has_encrypted_data: false
          serialization.encoding: utf-8
          EXTERNAL: true
        StorageDescriptor:
          OutputFormat: org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat
          Columns:
            - Name: source
              Type: string
            - Name: destination
              Type: string
          InputFormat: org.apache.hadoop.mapred.TextInputFormat
          Location:
            Fn::Join:
              - ''
              - - 's3://'
                - Fn::ImportValue: !Sub "${Env}-RawDataBucket-Name"
                - '/data' 
          SerdeInfo:
            Parameters:
              field.delim: ","
              serialization.format: ","
            SerializationLibrary: org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe
````

</div></details>

dataフォルダ以下をテーブルの場所としていますので、フォルダを作って先のCSVを置きます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/c508cfb5-30dd-6d63-8a5d-e29a9f15705f.png)

後はwranglerを使って、アクセスキーを指定して、クエリを実行するだけです。
```python
!pip install awswrangler 

import awswrangler 

session = boto3.session.Session(
    aws_access_key_id=ACCESS_KEY,
    aws_secret_access_key=SECRET_ACCESS_KEY,
    region_name='ap-northeast-1',
)

df = awswrangler.athena.read_sql_query(
    sql = 'SELECT * FROM sampletable' ,
    database='glue-database-lfr',
    boto3_session=session,
)
```
ちゃんと読めました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/d925da34-2a9a-50d0-f553-b26953d3832d.png)




# （おまけ）Polarisも動きました
昨今話題のPolarisも確認してみました。

https://qiita.com/_jinta/items/fac13f09e8e8a5769b79#10%E8%A1%8C%E3%81%A7%E6%8A%8A%E6%8F%A1%E3%81%99%E3%82%8Bpolars

以前やったように、10行サンプルと、100本ノックの最初の10問を動かしてみました。

https://qiita.com/a_b_/items/ccebe5e41aea8911cfe9

```python
!pip install polars
```
## 10行の例

```python
import polars as pl

df = pl.read_csv("https://j.mp/iriscsv")                        # データ読み込み
df_agg = (
    df
    .select([pl.col("^sepal_.*$"), pl.col("species")])          # 列の選択
    .with_columns((pl.col("sepal_width") * 2).alias("new_col"))  # 列の追加 with_column から変更
    .filter(pl.col("sepal_length") > 5)                         # 行の選択
    .groupby("species")                                         # グループ化
    .agg(pl.all().mean())                                       # 全列に対して平均を集計
)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/eaf8968d-c8b4-7c68-7e76-c9a29a5dd14c.png)

# 100本ノックから

データの読み込みは、以前の記事をそのまま使っています。

https://qiita.com/a_b_/items/ccebe5e41aea8911cfe9#100%E6%9C%AC%E3%83%8E%E3%83%83%E3%82%AF%E3%81%8B%E3%82%89

以下、実行結果の画像です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/9835928e-1b49-5270-1bed-2e6e8d79409f.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/5e78fe8f-a770-f1b5-7d2f-3eb7c6ba0e48.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/ab71c6e5-e058-0f78-7439-10d682068682.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/7d08971d-74dc-14b8-4eeb-46499b7e5737.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/dc33145a-a058-e6bf-cdd7-bd7c91a0fd3c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/0a613ad6-0708-81ea-a873-e074e883d106.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/8f8fe299-4db4-63c9-d931-0f6907e70eeb.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3ecac98c-85ea-b279-a264-225d3e715a88.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/eda534b9-1e1a-9dff-097a-2ca1d63175da.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/553680/3a229a22-0366-ca23-5f70-0330d1e73d6f.png)

特にエラーもなく動きました。

# おわりに
Amazon Lightsail for Researchとどうやってファイルをやり取りするのかを数例やってみました。
インスタンスのスペックが高いため、扱うデータのサイズも大きいものになると思われましたので、いつか使う際の予習のつもりで調べました。どなたかのご参考になれば幸いです。