kanayamaのブログ

twitter: @tkanayama_

Kaggleの学習から投稿までをAWS, GitHub Actionsを使って自動化する

金山(@tkanayama_)です。先日終了したKaggleの"M5 Forecasting"というコンペに参加した際、クラウドやCI/CDの勉強も兼ねて、AWS, GitHub Actionsを使って遊んでみました。

免責

  • N番煎じだったらすみません。一応、同じことをやっているネット記事は見つかりませんでした。
  • 私はクラウドなど勉強中の身分ですので、もっといいやり方がある or 説明が間違っている、などありましたら教えてください。
  • 私がこのシステムを使って参加したコンペの順位は5,558チーム中1,000,000,000位だったので、Kaggleで勝てるかどうかは別問題のようです :pien:

この記事のゴール

下記のようなシステムを構築することをゴールとします。

ユーザーがやることは2つ(図中でユーザーから伸びている黄色矢印)で、

  1. 実装したコードをgit pushし、
  2. AWSコンソールからEC2の実行ボタンを押す

です。その他はシステムによって自動化されていることを目指します。また、実行中のログやメモリ使用率・CPU使用率などはCloudWatchを用いてリアルタイム*1で可視化します。

f:id:tepppei:20200707144217p:plain
構築したシステム

この記事で扱うこと・扱わないこと

扱うこと

  • 前述のシステムを構築するために、GitHubAWSのどんな機能を使ったのか説明することをメインで扱います。

扱わないこと

  • 細かい実現方法(AWSコンソールの操作方法など)は、すぐに変わってしまう可能性が高い・他の多くのブログですでに紹介されていることから、適宜省略します。特に、AWS CLI周りの設定は済んでいる前提で話を進めていきます。

本題

必要なことは、

  1. クラウド上で実行したいコードを用意する
  2. Dockerfileを書く
  3. s3 bucketを用意する
  4. ECRのレポジトリを用意する
  5. GitHub Actionsを設定する
  6. AMIを作成する
  7. EC2の起動テンプレートを作成する

です。順を追って説明していきます。

1. クラウド上で実行したいコードを用意する

まずは、submitしたら優勝間違いなしの最強のアルゴリズムを実装します。

実装できましたでしょうか。例として、Titanicのsample submissionを出力するだけのコードを置いておきます。(GitHub)

このコードではMLパイプラインとしてgokartを使っています。(所属組織の影響を受けています。)

gokartは出力先としてAWS s3やGoogle Cloud strageのバケットのurlを下記のように指定するだけで、中間ファイルや最終結果ファイルの出力先を変えることができるので、今回実現したいパイプラインには都合が良いです。

TASK_WORKSPACE_DIRECTORY=s3://kaggle-titanic/

2. Dockerfileを書く

Dockerfileは下記のように記述しました。

FROM python:3.6.8-stretch

COPY ./Pipfile /app/Pipfile
COPY ./Pipfile.lock /app/Pipfile.lock

WORKDIR /app

RUN pip install --upgrade pip &&\
    pip install pipenv  &&\
    pipenv install --system --deploy &&\
    rm -rf ~/.cache

WORKDIR /
COPY ./conf /app/conf
COPY ./titanic /app/titanic
COPY ./main.py /app/main.py
COPY ./script /app/script

WORKDIR /app
VOLUME "/app"

ENV TASK_WORKSPACE_DIRECTORY s3://titanic-example/
CMD ["bash", "script/endpoint.sh"]

最後の2行で

  • 必要な環境変数のセット
  • taskを実行するためのshell scriptの指定

を行なっていますが、これはdokcer-composeを使ってdocker-compose.yaml側に書くほうが綺麗かもしれないなと今思いました。

3. s3 bucketを用意する

  1. のDockerfileに記述した出力先bucketを作成します。特筆事項はありません。

4. ECRのレポジトリを用意する

AWSのECRにアクセスし、レポジトリを作成します。これもコンソール上で10秒でできるので特に言及すべきポイントはありません。今回は 'titanic' という名前で作ります。

5. GitHub Actionsを設定する

GitHub Actionsは、公式が用意しているtemplateがとてもわかりやすいです。今回は、"Deploy to Amazon ECS" というテンプレートを元に下記のように作成しました。

on:
  push:
    branches: [ master ]

name: Deploy to Amazon ECS

jobs:
  deploy:
    name: Deploy
    runs-on: ubuntu-latest

    steps:
    - name: Checkout
      uses: actions/checkout@v2

    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v1
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-2

    - name: Login to Amazon ECR
      id: login-ecr
      uses: aws-actions/amazon-ecr-login@v1

    - name: Build, tag, and push image to Amazon ECR
      id: build-image
      env:
        ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
        ECR_REPOSITORY: titanic
        IMAGE_TAG: latest
      run: |
        # Build a docker container and
        # push it to ECR so that it can
        # be deployed to ECS.
        docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
        docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
        echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"

ここで、secrets.AWS_ACCESS_KEY_IDsecrets.AWS_SECRET_ACCESS_KEYは別途GitHubのSecretsに登録しておく必要があります。(外部に公開しないように注意です!)

また、ECR_REPOSITORY: titanicの部分は、「4. ECRのレポジトリを用意する」で作成したレポジトリ名に読み替えてください。

ここまで設定した上でgit pushすると、GitHub Actions上でdocker buildとdocker pushが走り、めでたくECRにdocker imageがpushされるはずです。

f:id:tepppei:20200707221941p:plain
ここまでできたシステム

5. AMIを作成する

次に、AWSコンソールからec2を起動します。baseとなるAMIは Amazon Linux AMI 2018.03.0 (HVM), SSD Volume Typeを指定します。

起動したら、ssh接続でサーバー内に入ります。その後、シェル上で下記3つの設定を行います。

docker関連

baseのimageにはdockerが入っていないので、dockerをinstallしておきます。

sudo yum update -y
sudo yum install -y docker
sudo usermod -a -G docker ec2-user

CloudWatch関連

CloudWatchに実行ログやメモリ使用量などの情報を送り、可視化できるようにするための作業です。公式のサイト(サイト1サイト2)を参考にしながら、下記のコマンドを実行していきます。

sudo yum install -y perl-Switch perl-DateTime perl-Sys-Syslog perl-LWP-Protocol-https perl-Digest-SHA.x86_64
curl https://aws-cloudwatch.s3.amazonaws.com/downloads/CloudWatchMonitoringScripts-1.2.2.zip -O
unzip CloudWatchMonitoringScripts-1.2.2.zip && rm CloudWatchMonitoringScripts-1.2.2.zip && cd aws-scripts-mon

次に

crontab -e

でcrontabを開き、crontabに下記を記述します。(定期的にメモリ使用量をCloudWatchに送る処理です。)

*/5 * * * * ~/aws-scripts-mon/mon-put-instance-data.pl --mem-used-incl-cache-buff --mem-util --mem-used --mem-avail --disk-space-util --disk-path=/ --from-cron

Kaggle API関連

Kaggleの自分のアカウントページからAPI tokenをダウンロードしてきます。そして、下記のようにtokenの情報をコピペしてコマンドライン上で実行します。(このあたりも、docker-composeとAWS パラメータストアを組み合わせて環境変数を外から注入できるようにした方が後からの変更に強そうだということに、ブログを書きながら気づきました。)

echo 'KAGGLE_USERNAME=tepppeikanayama' >> ~/kaggle.txt
echo 'KAGGLE_KEY=***********' >> ~/kaggle.txt

イメージの作成

上記「docker関連」「CloudWatch関連」「Kaggle API関連」の実行が終わったら、EC2のコンソール上でイメージの作成をします。

6. IAM roleを作成する

次に、権限管理を行うためのIAM roleを設定します。今回アクセス権限が必要なサービスは、S3, EC2, CloudWatchの3つです。コンソールからIAMのページを開き、この3サービスに対してFullAccessの権限を持ったIAMロールを作成します。

7. EC2の起動テンプレートを作成する

最後に、 EC2の起動テンプレートを作成します。起動テンプレートは、EC2のコンソールから簡単に設定できます。

設定項目は下記です。

  • AMI: 「5. AMIを作成する」で作成したAMIを指定します。
  • インスタンスタイプ: 実行したいコードの大きさと予算との兼ね合いで、適当なインスタンスを選択します。
  • IAMロール: 「6. IAM roleを作成する」で作成したIAMロールを選択します。
  • ユーザーデータ(インスタンス起動後に自動で実行してくれるscriptです):
#!/bin/bash
sudo service docker start
$(aws --region us-east-2 ecr get-login | sed -e 's/-e none //g')
docker pull 921126570142.dkr.ecr.us-east-2.amazonaws.com/titanic:latest
docker container run --env-file=/home/ec2-user/kaggle.txt --log-driver=awslogs --log-opt awslogs-region=us-west-2 --log-opt awslogs-group=/home/ec2-user/logfile.log 921126570142.dkr.ecr.us-east-2.amazonaws.com/titanic
echo "sudo halt" | at now + 5 minutes

ECRのpathなどは適宜読み替えてください。やっていることとしては、ECRにpushされた最新のdocker imageをpullする→実行する→実行が終わったらシャットダウンする、という流れです。

これで、最初に挙げた図のようなシステムが完成です!

今後やりたいこと

  • docker-compose, ECS, パラメータストアなどを組み合わせて、特に環境変数周りを整理する。
  • TerraformでInfrastructure as Codeを実現する。

*1:メモリ使用率・CPU使用率は5分くらいのタイムラグあり