クラウド | 株式会社Altus-Five / 株式会社Altus-Five は、技術力で勝負するシステム開発会社です。 Sun, 01 Jun 2025 09:08:08 +0000 ja hourly 1 https://wordpress.org/?v=6.8.2 /wp-content/uploads/2025/01/cropped-favicon-32x32.png クラウド | 株式会社Altus-Five / 32 32 ChatOpsのススメ /blog/2020/11/02/chatops/ /blog/2020/11/02/chatops/#respond Mon, 02 Nov 2020 09:41:00 +0000 http://43.207.2.176/?p=210 Slack の Chatbot を作ってみたいなぁ・・・と、なんとなく思いながらも、でも、会話ロボットがほしいわけでもないので、手を出してなかったのですが、 AWS Chatbot で ChatOps の一端に触れる機会 […]

The post ChatOpsのススメ first appeared on 株式会社Altus-Five.

]]>
Slack の Chatbot を作ってみたいなぁ・・・と、なんとなく思いながらも、でも、会話ロボットがほしいわけでもないので、手を出してなかったのですが、 AWS Chatbot で ChatOps の一端に触れる機会があり、少し興味が出てきたので、どんなものなのかを調べてみることにしました。
うまくいけば、社内専用の Chatbot を育てながら、普段の仕事の中で ChatOps が実践できるとよいなと思ってます。
この記事では、サンプルを動かしたところまでを順を追ってご紹介します。

Chatbot のフレームワーク

普段一番使っているのが Slack なので、Slack の Bot を作ります。
できるだけ仕事で使える ChatOps にしたいので、最初から Chatbot のフレームワークを使って、先々の高機能化に耐えられるものに目指します。

そんなわけで、npm trends で比較検索してみました。

Hubot というのを見聞きしたことがありましたが Bot Builder の Download 数が一番多いようです。
Bot Builder は Microsoft の OSS で、開発が止まってしまう心配もないので、これを使うことにします。

Bot Builder は、対応している Chat が 2020年11月時点で、21 種類もあって、SDKが4つの言語用(C# / JavaScript / Python / Java)に用意されています。
そして、サンプル実装が、大量にあります。

今回は、この中から Python SDK を使って Slack のサンプルを動かすことにします。

他にも、 Slack Bot の仕組みも、読んでおいた方がよいでしょう。
こちらの記事が、判りやすかったように思います。

Slack の API は、 Slack Adapter が吸収してくれるので、当面は、Slack API の仕様は不要かと思いますが、こちらで参照することができます。

上記の Slack Adapterのサンプル は Azure Bot Service を使って Slack に接続してメッセージに応答するシンプルなエコーボットです。

でも仕事では、AWS を使うことが多いので、 Lambda にデプロイして Slack Bot をサーバーレスで運用 できるようにします。

ローカル環境を作成する

開発は、Dockerコンテナで行いますが、環境を作る前に、メッセージが、どんな感じで流れるのかを説明します。
サンプルの Bot は、Slack の Event API を使っています。

📝前提

  • コンテナ内の Bot は、Webサーバー機能が動いていて Slack の Event を受信する API が実装されてある
  • ngrok でパブリックな URL を取得して ローカルPC で動く Bot の Event 受信API にフォワードされる
  • Slack アプリの Event Subscriptions の Request URL に ngrok の URL を設定する
  • Slack アプリをチャンネルに追加する

📝メッセージの流れ

  • チャンネルでメッセージを投稿する
  • Slack が ngrok の URL にメッセージを POST する
  • ngrok がコンテナ内の Bot にメッセージをフォワードする
  • コンテナ内の Bot は POST されたメッセージを読んで、Slack に同じ文言でメッセージ送信する
  • チャンネルに投稿したものと同じメッセージがエコーされる

サンプルをコピーして作業開始

それでは、ローカルに環境を作成していきます。

# サンプルを clone
git clone https://github.com/microsoft/BotBuilder-Samples.git

# botディレクトリを作成して、Slack Adapterのサンプルだけコピーする
mkdir bot
cd bot
cp -r ../BotBuilder-Samples/samples/python/60.slack-adapter/* .

開発用の requirements.txt

開発時にのみ使うモジュールを requirements-dev.txt に追加します。

# ランタイムに必要なパッケージ
-r requirements.txt
# 開発環境にのみ必要なパッケージ
awscli
ptvsd
pylint

Dockerイメージ

Lambdaで実行するので、 Lambda の docker イメージを使います。
BotBuilder-python が Python 3.8 前提なので、Dockerイメージも Python3.8 にします。

https://github.com/microsoft/BotBuilder-python/#prerequisites

ngrok と、あとで使用する Severless Framework もインストールしておきます。

docker/Dockerfile

FROM lambci/lambda:build-python3.8

# python を lambda にデプロイするときには、 requirements.txt から依存モジュールのインストールをするが、
# slsでそれをするのは、plugin が必要で、その plugin が Docker を必要とする
COPY --from=docker:19.03 /usr/local/bin/docker /usr/local/bin/

# serverless framework
## serverless-python-requirements は最新の 5.1.0 では、
## deploy時にエラーが出るので、4.3.0 を使う
RUN curl -sL https://rpm.nodesource.com/setup_10.x | bash \
    && yum install -y nodejs \
    && npm install -g serverless \
    && npm install -g serverless-python-requirements@4.3.0

# ngrok
RUN curl -sSL -o ngrok.zip https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip \
    && unzip ngrok.zip \
    && mv ngrok /usr/local/bin

# このイメージは、ビルドの段階で、requirements-dev.txt を pip install します。
# `requirements*.txt ` が変更されたときには、ビルドし直してください。
COPY ./requirements*.txt ./
RUN pip install -r requirements-dev.txt

docker の起動オプションがいろいろあるので docker-compose にしておきます。

docker-compose.yml

version: '3'
services:

  bot:
    build:
      context: .
      dockerfile: docker/Dockerfile
    volumes:
      - '.:/opt/bot'
      # aws cofigure のディレクトリをマウント
      - '.aws:/opt/.aws'
      # serverless-python-requirements が Docker を使うので、
      # dood (Docker outside of Docker) でコンテナ内からホストのDockerを使う
      - /var/run/docker.sock:/var/run/docker.sock
    working_dir: '/opt/bot'
    environment:
      # aws cofigure の配置場所を設定
      AWS_CONFIG_FILE: /opt/.aws/config
      AWS_SHARED_CREDENTIALS_FILE: /opt/.aws/credentials
    ports:
      # ngrok 用
      - 3978:3978
      # ptvsd 用
      - 5678:5678
    command: 'sleep infinity'

ビルドとコンテナの起動

docker-compose build

# コンテナを起動
docker-compose up -d

Bot 用の Slack Application を作成する

  1. Slackにログインして、アプリ一覧 ページを開きます。
  2. ‘Create new app’ ボタンをクリック
  3. “App Name” を入力し “Development Slack Workspace” を選択
  4. “Create App” ボタンをクリック

サンプルを動かすために必要な設定は、 Basic Information と OAuth & Permissions にあります。

  1. Basic Information は、すでに設定されていますが、 “Display Information” のところを、任意に変更してください。
  2. OAuth & Permissions は、”Bot Token Scopes” で “Add an OAuth Scope” ボタンをクリックして chat:writeim:historyim:read を追加してください。
  3. 上の方にスクロールすると、”Install App to Workspace” がクリックできるようになっているので、クリックします。
  4. OAuth の権限の確認画面が表示されるので、”許可”をクリックします。

Slack Application の設定は、まだやることがあるのですが、いったん、次に進みます。

環境変数を設定する

環境変数に、Slack のアプリ情報を設定します。

# Basic Information の Verification Token
export SlackVerificationToken="xxxxxxxxxxx"

# Basic Information の Signing Secret
export SlackClientSigningSecret="xxxxxxxxxxx"

# OAuth & Permissions の Bot User OAuth Access Token
export SlackBotToken="xxxxxxxxxxx"

Ngrok でテスト用のURLを準備する

Ngrok を使ったことがない場合には、下記の記事を読んでおいてください。

Dockerイメージには、ngrok のコマンドはインストール済です。 特に、Sign in しなくても、使えますが、長時間のテストをする場合には、Sign In するとよいと思います。どっちでも、無料プランでテストできます。

botコンテナに入ります。
docker-compose exec bot bash

コンテナ内で ngrok を起動します。
ngrok http 3978 -host-header="127.0.0.1:3978"

テスト中は、起動したままにします。

Bot を起動

上記とは異なるターミナルから、コンテナ内に入ります。
docker-compose exec bot bash

botを起動します。
python app.py

この時点では、WEBサーバーが起動しただけなので、まだ何も動きはないです。

Slack Application の Event Subscriptions を有効にする

サンプルの Bot は、Slack の Event API を使っているので、そのための設定を行います。

Slack api dashboard から作成した Slack Application を選択します。

  1. Event Subscriptions を選択
  2. Enable Events を On にする
  3. ‘Request URL’ に ngrok で Forwarding される https で始まる URL を設定する
    ただし、ngrok の URL は、ホスト名だけなので、サンプルがルーティング実装している /api/messages をパスに加えます。
    (例)https://xxxxxxxxxx.ngrok.io/api/messages
  4. ‘Subscribe to bot events’ を展開して、’Add Bot User Event’ ボタンをクリックして、message.im を追加する

動かしてみる(1)

Slack のチャンネルに作成したSlackアプリを招待します。
そして、以下を試してみてください。

  • チャンネルにメッセージを書き込む
    😄同じメッセージがエコーされる
  • 添付ファイルを付けてメッセージを書き込む
    😄メッセージのエコーと、添付ファイルを受信したことがエコーされる
  • メッセージにURLを含めて送信する
    😄メッセージのエコーと、URLを受信したことがエコーされる

動かしてみる(2)

/test コマンドもサンプルに実装されています。
Slack Application の設定にコマンドを追加します。

Slack Application にコマンドを追加設定

  1. アプリ一覧 からアプリを選択
  2. “Slash Commands” を選択
  3. “Create New Command” ボタンをクリック
  4. 以下の内容を入力
    • Command: /test
    • Request URL: https://xxxxxxxxxx.ngrok.io/api/slack
      (xxxxxxxxxx 部分は、ngrok の Forwarding のURLを参照)
    • Short Description: テスト
  5. ”Save” ボタンをクリック

コマンド実行

コマンドは、Slack の Workspace 全体で使われます。

  • 任意のチャンネルで /test を送信する
    😄処理されて、ディナーのレストラン選択のメッセージが返信されます。

Lambda 用の変更

さて、Ngrok で動くことを確認したら、次には、Lambda にデプロイして、サーバーレスで動かしてみます。

LambdaのeventオブジェクトからRequestオブジェクトへの変換

Bot Builder は、WEB APIで動くことが前提になっているようで、Slack Adapter とのインターフェースで aiohttp.web_request.Request が使われています。

Lambda の handler の引数の event オブジェクトとは、違うものなので、変換してから渡すことにします。

app.py を修正します。

import sys
import traceback
import logging
import json
import base64
import asyncio
from datetime import datetime
from urllib.parse import parse_qs
from aiohttp.web_request import Request
from aiohttp.web_response import Response
from aiohttp import test_utils
from aiohttp.streams import StreamReader
from aiohttp.test_utils import make_mocked_request
from unittest import mock

from botbuilder.adapters.slack import SlackAdapterOptions
from botbuilder.adapters.slack import SlackAdapter
from botbuilder.adapters.slack import SlackClient
from botbuilder.core import TurnContext
from botbuilder.core.integration import aiohttp_error_middleware
from botbuilder.schema import Activity, ActivityTypes

from bots import EchoBot
from config import DefaultConfig

CONFIG = DefaultConfig()

# Create adapter.
SLACK_OPTIONS = SlackAdapterOptions(
    CONFIG.SLACK_VERIFICATION_TOKEN,
    CONFIG.SLACK_BOT_TOKEN,
    CONFIG.SLACK_CLIENT_SIGNING_SECRET,
)
SLACK_CLIENT = SlackClient(SLACK_OPTIONS)
ADAPTER = SlackAdapter(SLACK_CLIENT)

_logger = logging.getLogger(__name__)

# Catch-all for errors.
async def on_error(context: TurnContext, error: Exception):
    _logger.error(f"\n [on_turn_error] unhandled error: {error}", exc_info=True)

ADAPTER.on_turn_error = on_error

# Create the Bot
echo_bot = EchoBot()


# Listen for incoming requests on /api/messages
async def messages(req: Request) -> Response:
    return await ADAPTER.process(req, echo_bot.on_turn)


# Listen for incoming slack events on /api/slack
async def slack(req: Request) -> Response:
    return await ADAPTER.process(req, echo_bot.on_turn)


def handler(event, context):
    if _logger.level <= logging.DEBUG:
        _logger.debug(f'event={event}')
    try:
        result = asyncio.get_event_loop().run_until_complete(__async_handler(event, context))
        return __respond(None, result)
    except Exception as e:
        _logger.error('エラー発生', exc_info=True)
        return __respond(e)


async def __async_handler(event, context):
    request = await __conv_lambda_event_to_request(event, context)
    response = await messages(request)
    return response


def __respond(err, res=None):
    if err:
        lamda_res = {
            'statusCode': '400',
            'body': type(err),
            'headers': {
                'Content-Type': 'text/plain'
            },
        }
    else:
        lamda_res = {
            'statusCode': res.status,
            'body': res.text,
            'headers': {},
        }
        for k, v in res.headers.items():
            lamda_res['headers'][k] = v
    if _logger.level <= logging.DEBUG:
        _logger.debug(f'__respond={lamda_res}')
    return lamda_res


async def __conv_lambda_event_to_request(event, context):
    ''' lambda の event から aiohttp の Request に変換する

    この変換処理は、 aiohttp のユニットテスト用のユーティリティ
    test_utils を使っています。
    '''

    body = event.get('body', '')
    if not event.get('isBase64Encoded', False):
        data = body.encode()
    else:
        data = base64.b64decode(body)

    protocol = mock.Mock(_reading_paused=False)
    payload = StreamReader(protocol=protocol, limit=2 ** 16, loop=asyncio.get_event_loop())
    payload.feed_data(data)
    payload.feed_eof()

    req = make_mocked_request(
        method=event['httpMethod'],
        path=event['path'],
        headers=event['headers'],
        payload=payload)
    return req

Serverless Framework

デプロイは、Serverless Framework で行います。

serverless.yml

service: bot
frameworkVersion: '2'

provider:
  name: aws
  runtime: python3.8
  stage: ${opt:stage, self:custom.defaultStage}
  region: ${opt:stage, self:custom.defaultRegion}
  # ログの保存期間
  logRetentionInDays: 5

  iamRoleStatements:
    - Effect: Allow
      Action:
        - "logs:CreateLogGroup"
        - "logs:CreateLogStream"
        - "logs:PutLogEvents"
      Resource:
        - "arn:aws:logs:::"
  environment: ${self:custom.environment.${self:provider.stage}}

plugins:
  - serverless-python-requirements

custom:
  pythonRequirements:
    layer: true
  defaultStage: dev
  defaultProfile: default
  defaultRegion: ap-northeast-1
  environment:
    dev: ${file(./conf/dev.yml)}
    prod: ${file(./conf/prod.yml)}

package:
  exclude:
    - .aws/**

functions:
  handler:
    handler: app.handler
    events:
      - http:
          path: api/messages
          method: post
          cors: true
      - http:
          path: api/slack
          method: post
          cors: true
    layers:
      - {Ref: PythonRequirementsLambdaLayer}

環境変数は yml にします。

conf/dev.yml

# Basic Information の Verification Token
SlackVerificationToken: "xxxxxxxxxxx"

# Basic Information の Signing Secret
SlackClientSigningSecret: "xxxxxxxxxxx"

# OAuth & Permissions の Bot User OAuth Access Token
SlackBotToken: "xxxxxxxxxxx"

デプロイ

AWSのアカウントは .aws/ に作成しておいてください。

sls deploy

Slack Application の URL を API Gateway のものに変える

deploy コマンドが、作成された API Gateway の URL を出力するので、それをコピーして、 Slack Application の設定画面で、URLを置き換えます。

Slack api dashboard から作成した Slack Application を選択します。

  1. Event Subscriptions を選択
  2. ‘Request URL’ に API Gateway の URL を設定する

動かしてみる

  • チャンネルにメッセージを書き込む
    😄同じメッセージがエコーされる

まとめ

Slack 起点で、いろんなことができそうな気がしてきました。

例えば

  • ちょっとしたデプロイのコマンド
  • メンテナンス画面への切替コマンド
  • 常時起動してない ec2 の起動
  • 社内 wiki の横断検索
  • GitHub への Issue 投稿コマンド
  • リアクションを集計して感謝ネットワークとして可視化
  • 社内図書(自炊PDF)の検索
  • チーム内の作業時間の回収と集計

・・・。

いろいろ試してみようと思います。

みなさんも、社内にお助け Chatbot を作ってみてはいかがでしょうか?

最後に、実際に chatbot を作ってみようと思ったら、こちらを読んでみることをオススメします。

The post ChatOpsのススメ first appeared on 株式会社Altus-Five.

]]>
/blog/2020/11/02/chatops/feed/ 0
私たちの AWS Glue を使った開発のやり方 /blog/2020/05/17/aws-glue-development/ /blog/2020/05/17/aws-glue-development/#respond Sun, 17 May 2020 08:59:00 +0000 http://43.207.2.176/?p=273 弊社での AWS Glue (Spark) を使ったシステムの開発のやり方についてご紹介します。ポイントはできるだけローカルの環境を使うことです。 はじめに AWS Glue は、ジョブの実行に Spark が使われてい […]

The post 私たちの AWS Glue を使った開発のやり方 first appeared on 株式会社Altus-Five.

]]>
弊社での AWS Glue (Spark) を使ったシステムの開発のやり方についてご紹介します。
ポイントはできるだけローカルの環境を使うことです。

はじめに

AWS Glue は、ジョブの実行に Spark が使われています。

Glueの動作イメージ
Glue は、 たぶん 、こんな動作イメージです。

  • ジョブの実行要求があると、Sparkのクラスターを起動する
  • Spark に対して、スクリプトを送信する
  • Spark 内でそのスクリプトが実行される
  • 実行結果が出力される

Glue 用のスクリプトをローカルの Python インタープリターで実行するというのとはちょっと違います。

Glue のスクリプトは S3 に配置される
GlueのAWSコンソールでスクリプトを編集して、登録することもできるのだけど、編集したスクリプトは、S3に保存されます。
そのため、そのS3のパスのスクリプトを上書きしたらOKなので、デプロイは、コピーで行う感じになります。

最初のglueのジョブ登録だけはコンソールから行う方法しか、わかっていませんが、その登録時に、保存先のS3パスを設定しているので、そのパスにコピーします。

ジョブの作成

ジョブの作成手順です。

  • AWSコンソールのGlueを開く
  • サイドメニューの「ジョブ」 を選択
  • 「ジョブの追加」ボタン押下
    • 名前 任意に名前をつける。ジョブ実行時の指定の使われる。
    • IAMロール 選択する(無ければ作成)
    • Type Spark
    • Glue Version Spark 2.4 (Glue Version 1.0)
    • このジョブ実行 ユーザーが作成する新しいスクリプト
    • スクリプトファイル名 e.g.) example.py
    • スクリプトが保存されている S3 パス
      • s3://my-data/glue-scripts
    • 一時ディレクトリ
      • s3://my-data/glue-temporary
      以上を設定して、「次へ」
  • 「接続」の画面では、何も追加しないで、「ジョブを保存してスクリプトを編集する」ボタン押下
  • 「スクリプト編集」の画面では、何も書かずに、「保存」ボタンを押下

これで、ジョブだけ登録されたので、実際のスクリプトは、ローカルでデバッグしたものを、上記の S3パスとスクリプトファイル名 に上書きコピーしてデプロイします。

Glue をローカルでデバッグする

Glue を docker で実行できるようにしました。詳細は、こちらの記事「AWS Glueのローカル環境を作成する」に書いているので、読んでみてください。

作成した dockerイメージは、altus5/devcontainer-glue で公開してあります。(buildしなくて大丈夫)

docker-compose に glue というサービスで起動するようになっているので、次のようにコンテナに入って実行できます。

docker-compose exec glue bash

以降の説明は、特に指定がないものは、この glue コンテナの中で実行するものとします。

小さいテストデータの作り方

ローカルの開発では、小さいデータを用意して、サクサク開発した方がよいでしょう。

例えば、変換元の hogelog という CSV があって、それを開発時のデータとして小さく編集して使おうという場合

## s3にある変換元データを持ってくる
aws s3 cp s3://my-data/hogelog/dt=2019-09-01/hogelog_v2_20190901-000.csv.gz .
## デバッグ用なので100行に小さくする
gzip -dc hogelog_v2_20190901-000.csv.gz | head -100 > hogelog_v2_20190901-000.csv
## ./fixture/srcdata/hogelog に移動
mkdir -p ./fixture/srcdata/hogelog
mv hogelog_v2_20190901-000.csv ./fixture/srcdata/hogelog
gzip ./fixture/srcdata/hogelog/hogelog_v2_20190901-000.csv

ローカルで AWS 無しで開発する

ローカルで実行できる前提があると、最初の1歩の踏み出しが楽になります。

ローカルのデータは、 file:// スキーマで URL を指定すると Spark はローカルから読み出します。
出力も同じくローカルに出力できます。そして、最初は、 Parquet 形式ではなくて csv で出力して、出力された値を目で確認できた方がよいでしょう。
いろいろ使い分けてデバッグしてみてください。

実装例

import io
import sys
import csv
import logging
import datetime
from awsglue.job import Job
from awsglue.transforms import *
from awsglue.context import GlueContext
from awsglue.utils import getResolvedOptions
from pyspark.sql import SQLContext
from pyspark.context import SparkContext
from pyspark.sql.types import StructType
from pyspark.sql.types import StructField
from pyspark.sql.types import StringType
from pyspark.sql.types import IntegerType
from pyspark.sql.types import BooleanType
from pyspark.sql.functions import *

# @params: [JOB_NAME]
args = getResolvedOptions(sys.argv, ['JOB_NAME'])

# ジョブ初期化
sc = SparkContext()
sqlContext = SQLContext(sc)
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)
logger = glueContext.get_logger()

# ローカルのテストデータを読み込む
df = spark.read.csv(
    "file:///workspace/fixture/srcdata/hogelog/*.gz", header=True, sep=",")

# ・・・・
# ロジック
# ・・・・

# ローカルに保存する
(df
    .write
    .mode("overwrite")
    #.format("parquet")
    .format("com.databricks.spark.csv")
    .save("file:///workspace/.dstdata/hogelog/"))

# ジョブコミット
job.commit()

Glue にスクリプトを実行させる。
上記の実装例を ./glue-scripts/example.py に保存してあるものとします。

gluesparksubmit \
    ./glue-scripts/example.py \
    --JOB_NAME='dummy'

AWS を使って開発する

データの入力元、出力先を、実際の AWS 環境を使って、テストする方法です。
各自の credentials を環境変数に設定して、 Glue を実行します。

上記の example.py の file:// で指定したデータのURLは、 s3:// などの実際の環境にあわせて変えます。

export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYY
export AWS_REGION=ap-northeast-1

# Glue 実行
gluesparksubmit \
    ./glue-scripts/example.py \
    --JOB_NAME='dummy' \

これで、 S3 の 出力先に出力されたことを確認できると思います。

The post 私たちの AWS Glue を使った開発のやり方 first appeared on 株式会社Altus-Five.

]]>
/blog/2020/05/17/aws-glue-development/feed/ 0
AWS Glueのローカル環境を作成する /blog/2020/05/07/aws-glue/ /blog/2020/05/07/aws-glue/#respond Thu, 07 May 2020 09:04:00 +0000 http://43.207.2.176/?p=275 AWS 上に構築されたシステム間で、データ変換と連携処理を行う開発を行いました。 連携データは S3 上に csv と json 形式で、何種類かのデータを使います。取り込んだデータは、最終的には、 Athena で検索 […]

The post AWS Glueのローカル環境を作成する first appeared on 株式会社Altus-Five.

]]>
AWS 上に構築されたシステム間で、データ変換と連携処理を行う開発を行いました。

連携データは S3 上に csv と json 形式で、何種類かのデータを使います。
取り込んだデータは、最終的には、 Athena で検索することに使われるんですが、元データの量が大きいため、効率よく短時間で取り込むために Spark が使いたいなぁという状況でした。
ただ、24時間常時実行される仕組みではないので Spark のためだけに複数台の EC2 を使うのはモッタイないです。
夜間バッチの1~2時間くらい使えればよいので Spark そのものが、サービス化されたものってないのかしら・・・と、AWSを探したところ AWS Glue がズバリそのものでした。

AWS Glue のお試し

さっそく、 Glue をシステムに組み込めそうか、お試しすることにしました。
CSV を Parquet 形式に変換するところに Glue を使ってみます。そして、おぉ、やっぱり速い!
ついでに Parquet 形式になったデータを Athena で検索してみても、おぉ、これも速い!
・・・ということを体感して、イケそうな感触が得られたので、本格的な開発の準備に取り掛かかることにしました。

料金を気にせず開発したい

Spark の開発をやったことのある方ならわかると思うのですが Spark は 大量のメモリを使うので、そのクラスター構成を動かすサービスとなると、利用料も安いハズがないです。
バグで暴走して、ものすごい請求が来た!なんてことになると困ります。ジョブを停止する手順をあらかじめ調べておいて、いざというときには、強制停止できるように準備しておく必要があります。
でも、それでも、”ジョブを実行”する段階では、ビビります。特に開発チームに参加したばかりの人は、実行ボタンを推すときの躊躇いの時間が、初期の立ちあがりの時間を遅くします。

プログラム作りは、ちょっと実装して動かしてみて軌道修正して・・・という作業を繰り返すので、”ためらう”ことなく実行できるローカルの環境があると、安心して試行に取り組めます。

AWS Glue をローカルで動かす

そんなわけで、dockerで実行できるようにしました。

参考にしたのは、この2つの記事です。
https://dev.classmethod.jp/articles/aws-glue-local/
https://future-architect.github.io/articles/20191101/

作成した dockerイメージは、Docker Hub で公開してあります。

Dockerfile は以下のようになりました。

  • Glue 1.0 は Python 3.6
  • PySparkを使うために、python:3.6 のイメージを元にしている
  • Spark は JDK8 じゃないとダメ(デフォルトのJDK10ではエラーが出る)
  • gluepyspark を1回動かしてmavenの依存モジュールをイメージに入れておく
  • jarの参照先をglueのものではなくsparkのものに入れ替える
FROM python:3.6

# Avoid warnings by switching to noninteractive
ENV DEBIAN_FRONTEND=noninteractive

ENV PYTHONUNBUFFERED 1

# Configure apt and install packages
RUN echo 'deb http://ftp.jp.debian.org/debian sid main' >> /etc/apt/sources.list \
    && apt-get update \
    && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \
    #
    # Verify git, process tools, lsb-release (common in install instructions for CLIs) installed
    && apt-get -y install git iproute2 procps lsb-release \
    #
    # Install pylint/awscli
    && pip --disable-pip-version-check --no-cache-dir install pylint awscli \
    #
    # for glue (spark=jdk8)
    && apt-get install -y openjdk-8-jdk zip unzip less \
    #
    # Clean up
    && apt-get autoremove -y \
    && apt-get clean -y \
    && rm -rf /var/lib/apt/lists/*

# awscli
RUN curl -sSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
RUN unzip awscliv2.zip
RUN ./aws/install

# maven (for glue)
RUN curl -sSOL https://aws-glue-etl-artifacts.s3.amazonaws.com/glue-common/apache-maven-3.6.0-bin.tar.gz
RUN tar -xzvf apache-maven-3.6.0-bin.tar.gz
RUN mv apache-maven-3.6.0 /opt/
RUN ln -s /opt/apache-maven-3.6.0 /opt/apache-maven
ENV PATH $PATH:/opt/apache-maven/bin

# glue 1.0
RUN git clone -b glue-1.0 --depth 1 https://github.com/awslabs/aws-glue-libs
RUN mv aws-glue-libs /opt/
ENV chmod -R ao+wr /opt/aws-glue-libs
ENV PATH $PATH:/opt/aws-glue-libs/bin

# spark
RUN curl -OL https://aws-glue-etl-artifacts.s3.amazonaws.com/glue-1.0/spark-2.4.3-bin-hadoop2.8.tgz
RUN tar -xzvf spark-2.4.3-bin-hadoop2.8.tgz 
RUN mv spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8 /opt/
RUN ln -s /opt/spark-2.4.3-bin-spark-2.4.3-bin-hadoop2.8 /opt/spark
ENV SPARK_HOME /opt/spark

# glue setup
RUN gluepyspark

# 2020/6/30にbuildし直してみたら、jarのバージョン違いが解消されて、
# 以下のオマジナイをしなくても大丈夫になったようです。
# RUN mv /opt/aws-glue-libs/jarsv1 /opt/aws-glue-libs/jarsv1.bk
# RUN ln -s ${SPARK_HOME}/jars /opt/aws-glue-libs/jarsv

# AWSのGlueの実行環境には、以下の外部ライブラリがインストール済のようで、
# 外部ライブラリとして、zip化しなくても、使えるようです。
# https://docs.aws.amazon.com/ja_jp/glue/latest/dg/add-job-python.html#python-shell-supported-library
# 少なくとも、boto3 は使えたので、コンテナにも、インストール済にしておく。
# 他、ユニットテストに必要なライブラリも追加
RUN pip install -U boto3 pytest pandas

docker-compose.yml も作成します。 のちのち、LocalStackも入れたくなるでしょう。

version: '3'
services:
  glue:
    image: altus5/devcontainer-glue:latest
    volumes:
      - '.:/workspace'
    working_dir: '/workspace'
    environment:
      TZ: 'Asia/Tokyo'
    command: 'sleep infinity'

docker-compose up -d で起動させたら glue のコンテナ内に入ります。

docker-compose exec glue bash

実行するには、それぞれのアカウントを設定して、テストデータを配置して、 glue にスクリプトを送信します。

export AWS_ACCESS_KEY_ID=XXXXXXXXXXXXXX
export AWS_SECRET_ACCESS_KEY=YYYYYYYYYYY
export AWS_REGION=ap-northeast-1

# S3にテストデータ配置(コピーするデータは適宜自分で用意する)
aws s3 cp testdata.gz s3://glue-test/

# 実行
gluesparksubmit ./src/sample.py --JOB_NAME='dummy'

The post AWS Glueのローカル環境を作成する first appeared on 株式会社Altus-Five.

]]>
/blog/2020/05/07/aws-glue/feed/ 0