EKSからSageMakerを動かす

# はじめに
re:Invent 2019の直前にAmazon SageMaker Operators for Kubernetesというものがひっそりと(?)リリースされています。
https://aws.amazon.com/jp/blogs/news/introducing-amazon-sagemaker-operators-for-kubernetes/
KubernetesからSageMakerをコントロールできるよーっていうものみたいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3246/9d49236f-d82f-c019-3089-3712e81c3053.png)
https://sagemaker.readthedocs.io/en/stable/amazon_sagemaker_operators_for_kubernetes.html


おっ！と思ったけど誰も試してないっぽいので誰にもささらなかったんでしょうか？
Sagemakerあんまり分かってないですが、ちょっと試しにやってみたところ、ブログに書いてあるとおりではなかなかうまくいかなかったので、手順まとめときます。

# EKSセットアップ
## EKSクラスタの作成
EKSクラスタを作ります。

```cluster.yaml
apiVersion: eksctl.io/v1alpha5
kind: ClusterConfig

metadata:
  name: sagemaker-cluster
  region: ap-northeast-1

iam:
  withOIDC: true
  serviceAccounts:
  - metadata:
      name: sagemaker-k8s-operator-default
      namespace: sagemaker-k8s-operator-system
    attachPolicyARNs:
    - "arn:aws:iam::aws:policy/AmazonSageMakerFullAccess"

nodeGroups:
  - name: "ng-1"
    desiredCapacity: 1
```

```bash:
$ eksctl create cluster -f cluster.yaml
```
インスタンスはなんでもいいです。
MLで使うインスタンスはクラスタのWorkerNodeではなく、SageMakerで作成されるので、EKSで考慮する必要はないです。




## SageMaker Operatorsのインストール

```
$ curl -O https://raw.githubusercontent.com/aws/amazon-sagemaker-operator-for-k8s/master/release/rolebased/installer.yaml
```

`eks.amazonaws.com/role-arn` を作成したものに変えます。

```
$ eksctl get iamserviceaccount --cluster sagemaker-cluster
NAMESPACE                       NAME                            ROLE ARN
sagemaker-k8s-operator-system   sagemaker-k8s-operator-default  arn:aws:iam::123456789012:role/eksctl-sagemaker-cluster-addon-iamserviceacc-Role1-NH9RC551YX3B
```



```installer.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  annotations:
    eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/eksctl-sagemaker-cluster-addon-iamserviceacc-Role1-NH9RC551YX3B
```

```bash:
$ kubectl apply -f installer.yaml
```

SageMaker関連のCRDが作成されます。

```
$ kubectl get crd | grep sagemaker
batchtransformjobs.sagemaker.aws.amazon.com         2019-12-18T07:23:42Z
endpointconfigs.sagemaker.aws.amazon.com            2019-12-18T07:23:42Z
hostingdeployments.sagemaker.aws.amazon.com         2019-12-18T07:23:42Z
hyperparametertuningjobs.sagemaker.aws.amazon.com   2019-12-18T07:23:42Z
models.sagemaker.aws.amazon.com                     2019-12-18T07:23:42Z
trainingjobs.sagemaker.aws.amazon.com               2019-12-18T07:23:42Z
```

EKS側の準備はここまでです。

# TrainingJobを動かす

[AWSブログ](https://aws.amazon.com/jp/blogs/news/introducing-amazon-sagemaker-operators-for-kubernetes/) に書いているサンプルは説明もあんまりないし、ちょっとよくわからなかったので、こちらのドキュメントのTrainingJobを動かしてみます。

https://sagemaker.readthedocs.io/en/stable/amazon_sagemaker_operators_for_kubernetes_jobs.html#trainingjob-operator
https://github.com/aws/amazon-sagemaker-operator-for-k8s/blob/master/samples/xgboost-mnist-trainingjob.yaml

その前にデータの準備します。
このへん全然書いてないんですよね・・・

## S3バケットの作成
データソースの保存と作成したモデルのアップロード用にS3バケットを作成します。

```bash:
$ aws s3api create-bucket --bucket $BUCKET_NAME --create-bucket-configuration LocationConstraint=ap-northeast-1 
```

データはこちらのMNISTのショートバージョンを使わせてもらいました。
https://github.com/ozt-ca/tjo.hatenablog.samples/tree/master/r_samples/public_lib/jp/mnist_reproduced
(他も試したんですけど、なかなかうまくいかず・・・)

```bash:
$ curl -O https://raw.githubusercontent.com/ozt-ca/tjo.hatenablog.samples/master/r_samples/public_lib/jp/mnist_reproduced/short_prac_train.csv
$ curl -O https://raw.githubusercontent.com/ozt-ca/tjo.hatenablog.samples/master/r_samples/public_lib/jp/mnist_reproduced/short_prac_test.csv
```

そのまま使うと、こんなエラーが出て失敗します。

```
ClientError: Non-numeric value 'l' found in the header line 'label,pixel0,pixel1,pixel2,pixel3,pixel4,pixel5,pi...' of file 'short_prac_train.csv'. CSV format require no header line in it. If header line is already removed, XGBoost does not accept non-numeric value in the data.
```
ヘッダ行がいらないってことみたいなので、1行目は削除します。
で、S3にアップロードします。

```bash:
$ aws s3 cp short_prac_train.csv s3://${BUCKET_NAME}/train/  
$ aws s3 cp short_prac_test.csv s3://${BUCKET_NAME}/validation/ 
```

## SageMaker用IAMRoleを作成
SageMakerで使用するIAMRoleを作成します。
S3からデータソースを持ってくるのと、作成したモデルをS3上にアップロードするので、S3のパーミッションをつけときます。

```policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "sagemaker.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
```bash:
$ aws iam create-role --role-name eks-sagemaker-role --assume-role-policy-document file://policy.json
$ aws iam attach-role-policy --role-name eks-sagemaker-role --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess
$ aws iam attach-role-policy --role-name eks-sagemaker-role --policy-arn arn:aws:iam::aws:policy/AmazonSageMakerFullAccess
```

## TrainingJobを作成

サンプルをダウンロードします。

```bash:
curl -O https://raw.githubusercontent.com/aws/amazon-sagemaker-operator-for-k8s/m
aster/samples/xgboost-mnist-trainingjob.yaml
```

ダウンロードしたファイルを下記のように修正します。

```xgboost-mnist-trainingjob.yaml
apiVersion: sagemaker.aws.amazon.com/v1
kind: TrainingJob
metadata:
  name: xgboost-mnist
spec:
    hyperParameters:
        - name: max_depth
          value: "5"
        - name: eta
          value: "0.2"
        - name: gamma
          value: "4"
        - name: min_child_weight
          value: "6"
        - name: silent
          value: "0"
        - name: objective
          value: multi:softmax
        - name: num_class
          value: "10"
        - name: num_round
          value: "10"
    algorithmSpecification:
        trainingImage: 501404015308.dkr.ecr.ap-northeast-1.amazonaws.com/xgboost:1 # Regionに合わせて変更 アカウントIDはhttps://docs.aws.amazon.com/ja_jp/sagemaker/latest/dg/sagemaker-algo-docker-registry-paths.html を参照
        trainingInputMode: File
    roleArn: arn:aws:iam::123456789012:role/eks-sagemaker-role # 作成したSageMaker用Roleを指定
    region: ap-northeast-1  # Region変更
    outputDataConfig:
        s3OutputPath: s3://<<Bucket Name>>/output/ # 作成したS3バケット、パスを指定
    resourceConfig:
        instanceCount: 1
        instanceType: ml.m4.xlarge
        volumeSizeInGB: 5
    stoppingCondition:
        maxRuntimeInSeconds: 86400
    inputDataConfig:
        - channelName: train
          dataSource:
            s3DataSource:
                s3DataType: S3Prefix
                s3Uri: s3://<<Bucket Name>>/xgboost/train/ # 作成したS3バケット、パスを指定
                s3DataDistributionType: FullyReplicated
          contentType: text/csv
          compressionType: None
        - channelName: validation
          dataSource:
            s3DataSource:
                s3DataType: S3Prefix
                s3Uri: s3://<<Bucket Name>>/xgboost/validation/ # 作成したS3バケット、パスを指定
                s3DataDistributionType: FullyReplicated
          contentType: text/csv
          compressionType: None
    tags:
      - key: tagKey
        value: tagValue
```



実行！

```
$ kubectl apply -f xgboost-mnist-trainingjob.yaml
```

始まりました。

```
$ kubectl describe trainingjob 
Name:         xgboost-mnist
Namespace:    default
  (略)
  Sage Maker Training Job Name:  xgboost-mnist-a850b392217011ea86c606020759f870
  Secondary Status:              Starting
  Training Job Status:           InProgress
Events:                          <none>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3246/84598a09-8907-0c7f-34c4-81bc9f129470.png)

終わり！

```
$ kubectl describe trainingjob 
Name:         xgboost-mnist
Namespace:    default
  (略)
  Sage Maker Training Job Name:  xgboost-mnist-a850b392217011ea86c606020759f870
  Secondary Status:              Completed
  Training Job Status:           Completed
Events:                          <none>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3246/b5b93b41-f59d-9388-465b-b01293ed5f30.png)

モデルもできてました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3246/9026af19-aa38-db41-8cb8-e784ba0e6f6f.png)

# まとめ
MLで使うインスタンスはSageMaker側で管理になるので、EKSクラスタでは考慮しなくていいっていうのはなかなかいいんじゃないかなーと。
これスポット使えるのかな？
にしてもこのYAML書けるようになるまでは覚えること多すぎて先が遠いなーっていう印象です。