動画を探して自動ツイートしてくれるPython製botをAWSに載せてみた(前編)

TL;DR

  • YouTubeから動画を拾ってTweetするbotをPythonで開発し、AWS Lambdaに載せてみました
  • 全2記事です。前編のこちらでは、主にPythonでの開発周りのトピックにフォーカスします
    • TwitterAPIを使ってプログラムからツイートしてみます
    • YouTubeのページを構文解析し、文字列操作を使って動画URLを抽出してみます

動機

新しい職場にて初めてAWSを触ることになったので、これを機にと個人アカウントを取ってみました。チュートリアルだけというのももったいないので、何か自分のためのサービスを作って載せると面白そうです。

で、Twitterのbot開発にはもともと興味があったので、これも前から興味を持ちつつ触ってなかったPythonでbotを作り、lambdaを使って運用してみようと思い立ちました。AWS lambdaは2017年4月からPython3系を扱えるようになったので、心置き無く最新バージョンで書けそうだなー、というのも狙いです。

ユーザーストーリー

毎日の退勤をもう少し楽しみにするために、定時になると自分が興味ありそうなYouTube動画をbotが勝手に検索して、自分のTwitterアカウントに届けてくれるようにしたい。
スクリーンショット 2017-12-06 23.30.33.png

前提

  • 開発にはMacを使用します
  • Pythonは3.6系を使用します
  • pyenvもvirtualenvも使用しません。議論はあろうかと思いますが、個人開発なので。。
  • で、開発環境構築はこちらの記事等を参照しました
  • bot化したいTwitterアカウントはあらかじめ用意してあるものとします

TwitterAPIを使ってプログラムに呟かせる

アクセスキーの取得

bot化したいアカウントでTwitter Application Managementにログインすると、アプリケーションの作成とConsumer Key、及びAccess Tokenの取得ができます。

なお、Appの作成にはTwitterアカウントが電話番号認証済みである必要があります。認証済みでないと怒られるので、エラーメッセージ中のリンクからさらっと済ませておきましょう。

  • Consumer Key
  • Consumer Key Secret
  • Access Token
  • Access Token Secret

以上の4パラメータがあればプログラムからのツイートができます。コピーしてこんな感じのファイルを作っておきましょう。

config.py
CONSUMER_KEY        = "xxxxxxxxxxxxxxxxx"
CONSUMER_SECRET     = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
ACCESS_TOKEN        = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
ACCESS_TOKEN_SECRET = "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

複数の外部ユーザーからアクセスがあるようなアプリケーションの場合(=「このアプリケーションとの連携を許可しますか?」など出るやつ)はそれぞれの役割についてもう少し説明が必要ですが、今回はある程度一緒くたに考えてしまっても実装に支障ありません。

PythonでOAuth認証

ライブラリの導入と管理

Pythonのライブラリは、パッケージ管理ツールであるpipでインストールできます。仮想環境がない場合、オプション無しで勝手にglobalに入るのがうーん、という感じですがまあそれは置いておいて。

PythonでHttp通信を行うライブラリとしては、requestsがポピュラーなようです。また、今回はTwitterAPIを使うための認証が必要なので、OAuth認証を扱えるライブラリも必須です。ここはrequestsと同じところが公開しているrequests_oauthlibを使用しました。

pip3 install requests requests_oauthlib

さて、インストールはできましたが、今度は開発するプロジェクトがこれらのライブラリに依存していることを表明しておくのがマナーです。js界隈で言うところのpackage.jsonですね。

Pythonでは依存関係を記したrequirements.txtなどを作っておくケースが多いようです。

requirements.txt
requests==2.18.4
requests-oauthlib==0.8.0

ちなみに、pip3 freeze > requirements.txtでインストールされた依存関係をrequirements.txtに吐き出せます。

逆に.txtファイルを元に一括インストールする場合は、-rオプションを用いてpip3 install -r requirements.txtなどと書けます。結構便利です。

つぶやいてみる

first_tweet.py
from requests_oauthlib import OAuth1Session
import config, json

twAuth = OAuth1Session(
  config.CONSUMER_KEY,
  config.CONSUMER_SECRET,
  config.ACCESS_TOKEN,
  config.ACCESS_TOKEN_SECRET)
apiURL = "https://api.twitter.com/1.1/statuses/update.json"
params = { "status": "プログラムにツイートさせてみるテスト" }

res = twAuth.post(apiURL, params = params)
print(json.loads(res.text))

先ほど作ったconfig.pyimportして、これだけ。思ったよりだいぶ手軽です。Twitterにアクセスして実際にツイートされたことを確認しましょう!

また、せっかくなのでレスポンスをjsonライブラリでロードして吐き出してみます。

{'created_at': 'Wed Dec 06 14:00:00 +0000 2017', 'id': 9384076800000000, 'id_str': '9384076800000000', 'text': 'プログラム
にツイートさせてみるテスト', 'truncated': False, 

...(中略)...

'retweeted': False, 'lang': 'ja'}

思ったよりいろんな属性があることがわかりますね。深掘りは公式のリファレンスにて。

YouTubeから動画のURLを拾ってくる

続いて、YouTubeから動画を探してくるパートです。

Webクローリング

この分野では、「クローリング」や「スクレイピング」と言った言葉が有名です。

クローリングとスクレイピング

クローリングはウェブサイトからHTMLや任意の情報を取得する技術・行為で、 スクレイピングは取得したHTMLから任意の情報を抽出する技術・行為のことです。

たとえば、あるブログの特徴を分析したい場合を考えてみましょう。
この場合、作業の流れは

  1. そのブログサイトをクローリングする。
  2. クローリングしたHTMLからタイトルや記事の本文をスクレイピングする。
  3. スクレイピングしたタイトルや記事の本文をテキスト解析する。

というようになります。

今回は、YouTubeをクローリングし、その中から動画のURLをスクレイピングすることになりますね。

Webページのクローリングとスクレイピングを行う際は、それがどんな目的のものであれ、HTMLを構文解析することが必須となります。Pythonでは、これを強力に支援するBeautifulSoupと言うライブラリがあります。執筆時点で最新のbeautifulsoup4を導入してみます。

pip3 install beautifulsoup4

早速使ってみましょう。Qiitaのトップページから<a>タグを探し、その中に含まれるhref属性の値を取得してみます。

crawling.py
import requests
from bs4 import BeautifulSoup

URL = "https://qiita.com/"
resp = requests.get(URL)

soup = BeautifulSoup(resp.text)

# aタグの取得
a_tags = soup.find_all("a", href=True)
for a in a_tags:
    print(a["href"])

結果

/about
https://qiita.com/sessions/forgot_password
https://oauth.qiita.com/auth/github?callback_action=login_or_signup
https://oauth.qiita.com/auth/twitter?callback_action=login_or_signup

・・・(中略)

https://qiita.com/api/v2/docs
https://teams.qiita.com/
http://kobito.qiita.com

いい感じです!

HTMLパーサーについて

さて、先のコードを実際に試すと、HTMLパーサーが明示されていないために警告が出ます。これは実際の解析時に使われるパーサーが実行時の環境に依存するためです。異なる環境下で同じ振る舞いを期待するには、使用するHTMLパーサーを明示してあげる必要があります。

デフォルトではhtml.parserが使われますが、lxmlかhtml5libを導入してこちらを明示してあげるのが無難なようです。このあたりの情報は下記の記事をだいぶ参考にさせていただきました。パーサーの選択だけでなくスクレイピング全般の情報が非常によくまとまっているエントリなので、オススメです。

PythonでWebスクレイピングする時の知見をまとめておく – Stimulator

パーサの良し悪しを考えるとlxmlでチャレンジしてダメならhtml5libを試すのが良さそう。

今回はこの1文に愚直に従ってみます。事前にpip3 install lxml html5libも忘れずに。


import requests
from bs4 import BeautifulSoup

URL = "https://qiita.com/"
resp = requests.get(URL)

+try:
+  soup = BeautifulSoup(resp.text, "lxml")
+except:
+  soup = BeautifulSoup(resp.text, "html5lib")
-soup = BeautifulSoup(resp.text)

# ...以下は先ほどと同様

Crawlerクラスを作ってみる

すでにPythonでオブジェクト指向な書き方を経験している方はこの辺りを飛ばしていただいて構いません。せっかくHTMLを解析してくれるコードができたので、クラスとして書き換えてみます。

crawler.py
import requests
from bs4 import BeautifulSoup

class Crawler:
    def hrefs_from(self, URL):
        a_tags = self.soup_from(URL).find_all("a", href=True)
        return set(map(lambda a:a["href"], a_tags))

    def soup_from(self, URL):
        res_text = requests.get(URL).text
        try:
            return BeautifulSoup(res_text, "lxml")
        except:
            return BeautifulSoup(res_text, "html5lib")

個人的にはインスタンスメソッドの第1引数が常にselfでなければならないのは書く量が増えるので少しもどかしいですね。ハマりポイントにもなりかねない…。

ちなみに、ここではラムダ式を使用し、hrefs_fromメソッドの戻り値の型をsetにしてみました。これは、今回のユースケースを鑑みてリンク先URLの重複を排除した方が便利と判断したためです。出現頻度など解析したい場合はまた改めて設計を考える必要があるでしょう。

継承と、YouTubeへのアクセス

YouTubeをクローリングするにあたって、「検索文字列を与えたら検索結果のページをクローリングし、動画を探してくる」などの機能があると便利そうです。先ほどのクラスを継承して、実装してみます。

tube_crawler.py
import random
import re
from crawler import Crawler

class TubeCrawler(Crawler):

    URLBase = "https://www.youtube.com"

    def hrefs_from_query(self, key_phrase):
        """
        検索文字列を与えると検索結果ページに含まれるhref属性の値を全て返す
        """
        return super().hrefs_from(self.URLBase + 
            "/results?search_query=" + key_phrase.replace(" ", "+"))



    def movies_from_query(self, key_phrase, max_count = 10):
        """
        検索文字列を与えると検索結果ページに含まれる動画のビデオIDを返す
        """
        return self.__select_movies(self.hrefs_from_query(key_phrase), max_count)



    def __select_movies(self, hrefs, max_count):
        """
        privateメソッド。href属性の値のsetからビデオIDのみを返す
        """
        filtered = [ re.sub( "^.*/watch?v=", "", re.sub( "&(list|index)=.*$", "", href )) 
            for href in hrefs if "/watch?v=" in href ]
        return filtered[:min(max_count, len(filtered))]



    def choose(self, movie_ids, prefix = "https://youtu.be/"):
        """
        渡した文字列のリスト(ビデオIDのリストを想定)から1つを選び、prefixをつけて返す
        """
        return prefix + random.choice(movie_ids)

文法的には継承とprivateメソッドの書き方あたりが新しい話題となります。この記事の主題ではないので特段の説明は省きます。

実際に試すとわかるのですが、検索結果のページにノイズとなるリンクが多いばかりか、再生リストへのリンクなど紛らわしいものも多く、その辺を適切に弾いていくのに手こずりました。おかげでfilter関数や正規表現に少し強くなれた気がします。

正規表現についてはこちらの記事をだいぶ参考にしました。

Pythonの正規表現の基本的な使い方

繋げてみる

準備が整ったので検索->ツイートの流れを試してみます。

main.py
#!/usr/bin/env python3
# -*- coding:utf-8 -*-

from tube_crawler import TubeCrawler
from tweeter import Tweeter
import config

def main():
    t = TubeCrawler()
    movies = t.movies_from_query("Hybrid Rudiments")
    chosen = t.choose(movies)

    # ツイートする部分をクラス化したもの
    tw = Tweeter()
    tw.reply(config.REPLY_TO, chosen)

if __name__ == '__main__':
    main()

エントリーポイントとなる関数が必要かなー、と思ったので何気なく(そう、本当に何気なく。これで良いと思っていたんですLambdaを使うまでは…)main関数を作成。

直接./main.pyでも呼べるようにこの辺からShebangを記述し始めました。また、末尾はファイル名で直接実行した場合にmain()を呼ぶためのおまじない。Rubyにも似たやつがありますね。あとはターミナルから呼んで動作確認するだけです。

$ ./main.py

実行したところ問題なく動きそうだったので、次回はAWS Lambdaに載せていきます。それなりの尺となったのでこのページはここまでです。お読みいただきありがとうございました。

リンク

続きを読む

はじめてのServerless ✕ Webpack ✕ TypeScript


このエントリーはaratana Advent Calendar 201712日目のエントリーです。

こんばんは!最近Google Home MiniAmazon echo dotを購入したはいいが置き場所に困っている蔭山です。
みなさんはどのような場所に置かれているのでしょうか。。。

前日は新卒エンジニアには決して見えない安定感をお持ちの猿渡くんの「NoSQLについて何か。」という記事でした!
NoSQL?あぁ、聞いたことはある。
みたいな僕でもわかりやすい記事でした!
最近AWSに興味が出始めたところでしたので、ぜひDynamoDBを使って軽い画像投稿サービスでも作ってみます!

さて今回はServerless ✕ Webpack ✕ TypeScriptの組み合わせで使えるように手順をまとめてみたいと思います!

動作環境

今回の動作環境は以下になります!

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G1114

$ node -v
v8.2.1

$ serverless -v
1.24.0

$ webpack -v
3.5.6

環境準備

nodeやserverlessのインストールについては下記記事を参考に。。。

とりあえずやってみる

まずはServerlessのプロジェクトを作ってみましょう

$ serverless create -t aws-nodejs -p hogehoge
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/fugafuga/hogehoge"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.24.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
$ cd ./hogehoge

JSer御用達npmを使って必要なパッケージをダウンロードしましょう。

$ npm init
$ npm install --save-dev serverless-webpack serverless-offline ts-loader typescript webpack

インストールが終わり次第、各種設定を行います。
今回はTypescript -> ES2015へのコンパイルを目的に設定させていただきます。
細かい設定内容に関しては割愛します。

./serverless.yml
service: hogehoge

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: ap-northeast-1

plugins:
- serverless-webpack
- serverless-offline

functions:
  hello:
    handler: handler.hello
    events:
     - http:
         path: hello
         method: get
./webpack.config.js
module.exports = {
  entry: './handler.ts',
  target: 'node',
  module: {
    loaders: [{
      test: /\.ts$/,
      loader: 'ts-loader'
    }]
  },
  resolve: {
    extensions: ['.ts']
  },
  output: {
    libraryTarget: 'commonjs',
    path: __dirname + '/.webpack',
    filename: 'handler.js'
  }
};
./tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs"
  },
  "exclude": [
    "node_modules"
  ]
}

準備はできたので、次はTypeScriptでコーディングしてみましょう!

./handler.ts
export * from './src/ts/functions/hello';
./src/ts/functions/hello.ts
export function hello(event, context, callback): void {
  const response = {
    statusCode: 200,

    headers: {
    },

    body: JSON.stringify({
      "message": "Hello Serverless x Webpack x TypeScript!!"
    })
  };

  callback(null, response);
};

コードが書けたらローカル環境で動作確認

$ sls offline
・・・・・・・・・・・・・・・・
途中は割愛。m(__)m
・・・・・・・・・・・・・・・・
Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000

きちんと動作するか確認。

$ curl -X GET http://localhost:3000/hello
{"message":"Hello Serverless x Webpack x TypeScript!!"}

動作が問題なければ、早速デプロイしてみましょう!

$ sls deploy
・・・・・・・・・・・・・・・・
途中は割愛。m(__)m
・・・・・・・・・・・・・・・・
api keys:
  None
endpoints:
  GET - https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: hogehoge-dev-hello

デプロイが完了したようです。
では早速動作確認。

$ curl -X GET https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Hello Serverless x Webpack x TypeScript!!"}

ちゃんと動きましたね!

最後に

無理やりTypeScriptを使った感が凄まじいですね。。。申し訳ありません><
僕個人がTypeScriptを使ったことがなかったため使ってみたかったんです

明日は新卒田村くんの「Ctagsで自由な翼を得たVimについて」です!
お楽しみに!

参考

主にこちらの記事を参考にさせて頂きました!ありがとうございますm(__)m

続きを読む

AWS S3上のオブジェクトのメタデータを取得する

// Groovy Version: 2.4.11 JVM: 1.8.0_144 Vendor: Oracle Corporation OS: Mac OS X
@Grab('com.amazonaws:aws-java-sdk-s3:1.11.184')
import com.amazonaws.regions.Regions
import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.AmazonS3ClientBuilder

AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
        .withRegion(Regions.AP_NORTHEAST_1)
        .build()

def bucketName = 'your-bucket-name'
def key = 'some/s3/key'

def s3metadata = s3Client.getObjectMetadata(bucketName, key)
s3metadata.properties.each { k, v ->
    if(k in ["metaClass", "class"]) return
    println "$k: $v"
}

結果

serverSideEncryption: null
ETag: bff9fd94c2d3d672213687840a21d3d4
instanceLength: 258770
userMetadata: [:]
contentDisposition: null
rawMetadata: [Accept-Ranges:bytes, Content-Length:258770, Content-Type:image/jpeg, ETag:bff9fd94c2d3d672213687840a21d3d4, Last-Modified:Fri Nov 10 18:18:39 JST 2017]
replicationStatus: null
contentRange: null
ongoingRestore: null
SSECustomerAlgorithm: null
requesterCharged: false
expirationTimeRuleId: null
storageClass: null
restoreExpirationTime: null
contentLength: 258770
lastModified: Fri Nov 10 18:18:39 JST 2017
versionId: null
cacheControl: null
SSECustomerKeyMd5: null
SSEAwsKmsKeyId: null
contentEncoding: null
SSEAlgorithm: null
contentType: image/jpeg
contentMD5: null
httpExpiresDate: null
contentLanguage: null
partCount: null
expirationTime: null

参考

続きを読む

`ionic start`の"aws"って何なの? – ionic-angular AWS Mobile Hub Starterを使ってみた

:christmas_tree: オープンストリーム Advent Calendar 2017 の 9 日目です :santa::christmas_tree:

ionic startの”aws”って何なの?

みなさんは ionic start でIonicのプロジェクトを始めるとき、”aws”の文字を見て気になったことはありませんか?:confused:

ionic start aws

そうです、これです。
“ionic-angular AWS Mobile Hub Starter” とありますね。

今回はこの “aws” で生成されるIonicプロジェクト(Starter)について見て・触れてみようと思います。

AWS Mobile Hubとは?

先ほどの通り ionic startの”aws”で生成されるプロジェクトは AWS Mobile Hub に関係するプロジェクトです。

AWS Mobile Hubとは、ブラウザの操作だけでAWSを用いたモバイルアプリ開発がすぐスタートできるサービスになります。

画面は英語表記ですが(2017/12現在)、AWSのサービス名を知らなくとも、
:grinning:「Facebookのアカウント一つでアプリが使えるようにしたい!」
:smiley_cat:「スマートフォンで通知を受け取るアプリを作りたい!」
など、実現したいことに従ってクリックするだけで、AWSのサービスが展開されて機能がすぐに実現できます。

「モバイルアプリケーションを最短の時間で構築する方法」とある通りですね!

AWS Mobile Hubの詳細はこちらになります。

AWS Mobile Hub(モバイルアプリケーションの構築、テスト、モニタリング)| AWS
https://aws.amazon.com/jp/mobile/

なお、Mobile Hubを実際に使った説明やサンプルは他の記事に譲ります。

プロジェクト開始直後を見てみる

blank を選んだときと比べ、プロジェクトのルートには cors-policy.xml, mobile-hub-project.zip が追加されています。

mobile-hub-project.zip は、このプロジェクトで使うAWSのリソースの設定が格納されています。
このプロジェクトのためにAWSをセットアップするために必要となります。

$ ls -l
-rw-r--r--    1 ysd  staff    3622 Dec  7 14:00 README.md
-rw-r--r--    1 ysd  staff    6173 Dec  7 23:02 config.xml
-rw-r--r--    1 ysd  staff     539 Dec  7 14:00 cors-policy.xml
-rw-r--r--    1 ysd  staff     113 Dec  7 23:02 ionic.config.json
-rw-r--r--    1 ysd  staff     775 Dec  7 14:00 mobile-hub-project.zip
drwxr-xr-x  470 ysd  staff   15980 Dec  7 23:02 node_modules
-rw-r--r--    1 ysd  staff  174985 Dec  7 23:02 package-lock.json
-rw-r--r--    1 ysd  staff    1125 Dec  7 23:01 package.json
drwxr-xr-x    7 ysd  staff     238 Dec  7 23:02 resources
drwxr-xr-x   10 ysd  staff     340 Dec  7 23:01 src
-rw-r--r--    1 ysd  staff     576 Dec  7 14:00 tsconfig.json
-rw-r--r--    1 ysd  staff     178 Dec  7 14:00 tslint.json

さらに src/assets の中にあらかじめAWSのJavaScript SDKが入っていました。

$ ls -l ./src/assets
total 10712
-rw-r--r--  1 ysd  staff    43875 Dec  7 14:00 amazon-cognito-identity.min.js
-rw-r--r--  1 ysd  staff   317909 Dec  7 14:00 amazon-cognito-identity.min.js.map
-rw-r--r--  1 ysd  staff   510404 Dec  7 14:00 aws-cognito-sdk.js
-rw-r--r--  1 ysd  staff   259601 Dec  7 14:00 aws-cognito-sdk.min.js
-rw-r--r--  1 ysd  staff   346715 Dec  7 14:00 aws-cognito-sdk.min.js.map
-rw-r--r--  1 ysd  staff  2658078 Dec  7 14:00 aws-sdk.js
-rw-r--r--  1 ysd  staff  1285371 Dec  7 14:00 aws-sdk.min.js
drwxr-xr-x  3 ysd  staff      102 Dec  7 23:01 icon
drwxr-xr-x  3 ysd  staff      102 Dec  7 23:01 imgs
-rw-r--r--  1 ysd  staff    51794 Dec  7 14:00 ionic-aws-logo.png

プロジェクトのルートに配置されるファイルと package.json で導入されるパッケージから、 https://github.com/ionic-team/starters/tree/master/ionic-angular/official/aws の内容が展開されるようです。

AWS Mobile Hub Starterを使ってみる

次に、プロジェクト同梱の README.md に従ってAWS Mobile Hub Starterを使ってみましょう。

pip, AWS CLIをインストールする

この後のステップでAWS CLIが必要になります。ターミナルを開いて次のコマンドを実行します。

$ pip install --upgrade pip
$ pip install awscli

Mobile Hub を始める

このサンプルで使うAWSのリソースをセットアップするために AWS Mobile Hub を使います。Mobile Hubでプロジェクトを作成し、このプロジェクトで使う/作成するAWSのリソースをまとめます。

Mobile Hubのコンソールを開くとプロジェクトの一覧が表示されます。ここで目につく Create や + Create Project を押したくなりますが、その隣の Import ボタンをクリックします。

Mobile Hub top

Importボタンを押すと、次のようにimportするプロジェクトの設定画面が表示されます。
ここで mobile-hub-project.zip を使います!

“Import your Mobile Hub project zip file” にある “You can also drag and drop your file here” にこの mobile-hub-project.zip をドラッグします。

Mobile Hub Import

AWS Mobile Hub Starterで使うAWSのリソースが mobile-hub-project.zip にあらかじめ記述されているため、このファイルからプロジェクトをインポートすることでAWSのリソースをセットアップできます。

その他の項目は次の通りになります。

  • Enter a name for your Mobile Hub project: Mobile Hubのプロジェクト名になります。 ionic start で設定したプロジェクト名でも可能です
  • Allow AWS Mobile Hub to administer resources on my behalf: Mobile HubでAWSのリソースをセットアップできるようにチェックを入れます
    • 既にMobile Hubを使っている場合は表示されないことがあります
  • Resources for your project will be created: リージョン名をクリックすると、Mobile HubでセットアップされるAWSのリソースのリージョンを指定することができます

設定例としてこのようになります :bulb:

Mobile Hub import sample.png

設定値を入力したら Import project ボタンをクリックします。
クリックするとAWSのリソースが一括で作成されます。1-2分ほどかかります。

完了するとMobile Hubのプロジェクトが開きます。

ここで作成されたAWSのリソースを見ると、
Amazon Pinpoint のプロジェクトが作成され…………(プッシュ通知などに使います)

Amazon Cognitoのユーザープールが作成され…………(ユーザーの認証などに使います)

Amazon Cognitoのフェデレーティッドアイデンティティが作成され…………(ユーザーの認証などに使います)

Amazon S3のバケットが3つ作成され…………(アプリの配布やユーザーデータの保存などに使います)

Amazon CloudFrontのディストリビューションが作成され…………(アプリの配布などに使います)

Amazon DynamoDBのテーブルが作成され…………

IAMロールが4つ作成されました。

通常はこのMobile Hubの画面で各項目を一つずつクリックして設定するところ、このAWS Mobile Hub Starterを使うことで一度に設定できます。

恐ろし… 一通りそろって便利ですね! ※この段階では無料利用枠に収まります。

料金 – AWS Mobile Hub | AWS
https://aws.amazon.com/jp/mobile/pricing/

AWS Mobile Hub Starterでセットアップされないリソース

先ほどの方法でMobile Hubがセットアップしないリソースは次の2つです。

  • Cloud Logic: Amazon API Gateway + AWS Lambda
  • Conversational Bots: Amazon Lex

Cloud Logicはモバイルアプリから送信されたデータを、サービス側で処理する……構造が実現できます。
一方のConversational Botsは、自動で会話できるチャットボットを作成するサービスになります。2017/12現在で米国英語のみの対応になりますが、アプリと会話して注文を受け付ける機能 :shopping_bags: などが実現できます。

Mobile Hubから aws-config.js をダウンロードする

しかし、設定はまだ終わっていません。 先ほど作成されたS3バケットが次の設定で重要になります。


(再掲)

hosting-mobilehub を含むS3バケットの名前を記録します。
そして、Ionicのプロジェクトに戻り、 BUCKET_NAME を先ほどのS3バケットの名前に置き換えて aws-config.js をダウンロードします。

aws s3 cp s3://BUCKET_NAME/aws-config.js src/assets

この例では次の通りになります。

$ aws s3 cp s3://myawsionic-hosting-mobilehub-1404708309/aws-config.js src/assets
download: s3://myawsionic-hosting-mobilehub-1404708309/aws-config.js to src/assets/aws-config.js

この、 aws-config.js はMobile Hubで作成されたAWSリソース達のARNが記録されています。
「モバイルアプリがアップロードしたファイルをどこのS3バケットに配置すべきか」などが決まります。

ユーザーがS3バケットにファイルをアップロードできるようにする

AWS Mobile Hub Starterの初期状態は、アプリを使っているユーザーがS3バケットにファイルをアップロードできる機能がありますが、この機能を使うためにはアップロード先のS3バケットで設定が必要になります。

userfiles を含むS3バケットを開きます。
次に、アクセス権限 -> CORSポリシー を開き、 README.md で説明された内容 もしくはプロジェクトルートにある cors-policy.xml の内容に差し替えて「保存」ボタンをクリックします。

userfiles S3 CORS setting

実行する

プロジェクトのルートで ionic serve を実行するとブラウザで動きを確認することができます。

ユーザーアカウントの登録

ionic serve 実行後に開くブラウザではログイン画面が表示されますが、AWSやIonic Proのアカウントを入力するわけではなく、このアプリ用のユーザーアカウントを登録します。

Login screen

“Create one.”のリンクをクリックし必要な項目を入力するとアカウントの登録ができますが、次のステップで確認コードがメールで送られてそれを入力する必要があるためメールアドレスが適当すぎると詰みます。
(詰んだ場合はブラウザをリロードするとログイン画面に戻ります)

email confirm screen

ユーザー登録完了後は特に何も表示されることなくログイン画面に遷移します。

ここで Amazon Cognitoのユーザープールを見るとユーザーが作成されていることや、確認コード入力の時点で詰んだことがわかります。

Cognito User Pool

タスクを追加する

ログインできると”Tasks”の画面が表示されます。

tasks screen

右上にある 「+」 ボタンをクリックして項目を追加できます。

input tasks screen

inputed tasks screen

項目を追加した後にAmazon DynamoDBを見ると、データベースに先ほどの内容が記録されていることが分かります。

DynamoDB Table Contents

ユーザーの写真を更新する

Settings -> Account を開き,「CHANGE PHOTO」をクリックするとユーザーの写真を更新することができます。

account edit screen

正しく更新されると即時反映されます。反映されない場合はS3バケットのCORSポリシーの設定を再度確認します。

ここでは userfiles を含むS3バケットを見るとユーザーの写真がアップロードされていることが分かります。 protected のディレクトリにあります。

結論(ionic startの”aws”って何だったの?)

AWSのサービスを用いたToDoアプリができるひな形になります。

Settings -> About this app を確認すると、実際に次のサービスが使われている旨が表示されます。

  • Cognito
  • DynamoDB
  • S3

言い換えると、AWS Mobile Hub StarterからIonicのプロジェクトをスタートすると、この3つのサービスがアプリ内ですぐに利用できます。

AWS Mobile Hub Starterを動かすだけで長くなりましたが、Ionicを使うと少し手を動かすだけで

  • ユーザー登録時に確認コードでメールアドレスの所在が保証でき
  • DynamoDBでクラウドに内容が保存できる
  • ToDoアプリ

が作れました! :smiley:

おわりに

……と言いたいのですが、動かしただけではAWS Mobile Hub Starterからどのようにアプリにしていくのか分かりにくいですね?

そこで、この先は AWS Mobile Hub Starterで用意されたAmazon PinpointとFirebaseを組み合わせてAndroidのプッシュ通知を受け取るサンプル を作ろうとしましたが、

AWSブログにて紹介されている方法でFirebaseの送信者IDを入れると、トークン取得の段階で次のエラーが出て正しく動作させることができませんでした。

console.error: Error with Push pluginError: AUTHENTICATION_FAILED

Push Notifications with Ionic and Amazon Pinpoint | AWS Mobile Blog
https://aws.amazon.com/jp/blogs/mobile/push-notifications-with-ionic-and-amazon-pinpoint/

その他の方法を調べましたが、Androidのビルド環境の問題なのかビルドができませんでした :confused:

Mobile HubでセットアップされるAWSの各サービスのドキュメントを読んでも、先ほどのAWSブログ以外にIonicを用いた実装例が見つかりませんでした。
そのため、「せっかく用意したPinpointでプッシュ通知を使ってみたい」「メールアドレスではなくFacebookとCognitoとIonicでユーザー登録を実現したい」など、IonicのAWS Mobile Hub Starterのコードで用意された範囲を超えるのは非常に難しい印象でした(個人の感想です)

紹介できなかったCloud Logicについても試してみたかったのですが、次回の機会に……

翌日 10 日目は @granoeste さんです!

セットアップしたAWSリソースのクリーンアップ

プロジェクトが不要な場合は、Mobile Hubコンソールのトップ画面から操作します :bulb:

削除したいプロジェクトの右上に「…」がありますので、カーソルを合わせて「Delete」をクリックします。

Mobile HubでセットアップされたAWSリソースが削除されアクセス拒否される旨の確認ダイアログが表示されるので、「Delete project」ボタンをクリックするとMobile Hubのプロジェクトが削除されます。

MobileHub project delete confirm dialog

しかし、完全にAWSリソースが削除されないため注意が必要です。

削除されるAWSリソース

  • Amazon Pinpoint のプロジェクト
  • Amazon Cognitoのフェデレーティッドアイデンティティ
  • Amazon S3のバケット(userdata, deployments)
  • プロジェクトに関わるIAMロール

削除されないAWSリソース

  • Amazon Cognitoのユーザープール
  • Amazon CloudFrontのディストリビューション
  • Amazon DynamoDBのテーブル
  • Amazon S3のバケット(hosting)
  • Mobile Hubのサービスが使うIAMロール

動作環境

今回の記事で使った環境はこちらになります :computer:

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G29

$ node -v
v8.1.4

$ npm -v
5.4.2

$ ionic --version
3.19.0

$ cordova --version
7.1.0

続きを読む

VistaNet-NEWS

2015/01/29 | Vistara SaaS基盤でAWSとの連携を強化. “Vistara SaaS基盤のソフトウェアを2月13日にV3.8.2にアップグレード。 本アップグレードにより、特にAWS(アマゾンウェブサービス)との連携強化が行われます。” クラウド環境においては複数のインスタンスが同じデバイス名やプライベートIPアドレス、 仮想MAC … 続きを読む

カテゴリー 未分類 | タグ

AWSのインスタンスにLAMP環境を構築する際に使用したコマンドについて解説する

AWSにインスタンスを作成したあとに・・・

AWSにインスタンスを作成したあと、SSHのキーを使ってインスタンスに入るところから解説します。

使用するインスタンス

  • 無料枠のEC2
  • OSはAmazon Linux
  • セキュリティグループはSSH

インスタンスができたら

使用する端末がwinの場合はツールをインストールしてください。macの場合はターミナルを起動してください。
macのターミナルの場所:アプリケーション>ユーティリティ>ターミナル

1インスタンスに入る

①SSHのキーの設定

インスタンス作成の際にダウンロードしたキーを使用します。
chmod 600 ~/hoge/hoge.pem
⇨このキーの権限を設定するよ!
【入力内容】
:sunny:~/hoge/hoge.pem のところには
 ダウンロードしたキーのファイルパスを入力してください。

【コマンドの解説】
:cherry_blossom: chmod
チェンジモードchange modeの意味でアクセス権を変更するときに使用します。

:cherry_blossom: 600
3桁の数字はアクセス権を示します。

600:1桁目は所有者の権限です。
600:2桁目はグループの権限です。
600:3桁目はその他の権限です。

さらにそれぞれの数字に権限の意味があります。
6:読み込み権限と書き込み権限
0:権限なし

つまり600というのは、
所有者の権限には読み込みと書き込みの権限を与え、
グループの権限とその他の権限には権限を何も与えない
ということになります:astonished:

:cherry_blossom: キーのファイルパス
権限を与える対象のファイルを指定することになります。

【参考サイト】
chmodについて詳しく!
chmod? chown? よくわからんって人のための、ファイル権限系まとめ
権限の数字について詳しく!
【 chmod 】 ファイルやディレクトリのアクセス権を変更する
コマンド操作を楽ラクにしたい!
ブクマ必至!Linuxコマンド一覧表【全33種】 2.2 覚えておくと便利なショートカットキー

②SSHのキーを使用して入る

①で使用したキーをここでも使用します。
ssh ec2-user@hoge -i ~/hoge/hoge.pem
⇨このキーでインスタンスに入るよ!
【入力内容】
:sunny:ec2-user@hoge のhogeにはインスタンスのIPv4 パブリック IPを入力してください。
:sunny:~/hoge/hoge.pem のところには①同様、
 ダウンロードしたキーのファイルパスを入力してください。

【コマンドの解説】
:cherry_blossom: ssh
インスタンス作成時のセキュリティグループでタイプSSHを選択しました。
それです。

:cherry_blossom: ec2-user@hoge
アクセス先を指定する。

:cherry_blossom: -i
informationのiです。付加情報がある際にはこのあとに記述します。

:cherry_blossom: ~/hoge/hoge.pem
今回の付加情報です。
「このキーでインスタンスに入るよ!」の「このキー」というのが付加情報になります。

③質問されるので答える

②を実行すると以下の文章が表示されます。

Are you sure you want to continue connecting (yes/no)?

⇨接続することは同意ですか?

同意なので
yes
と入力してください。

④入れた!

以下のAAが表示されたらインスタンスに入れました!おめでとう:heart_eyes:

  __|  __|_  )
  _|  (     /   Amazon Linux AMI
 ___|___|___|

2環境を整える

①インスタンスを更新する

インスタンスを最新の状態にしたいので、アップロードするものがあればアップロードするようにします。
sudo yum update -y
⇨管理者権限でアップデートするよ!
これを実行するとズラズラ文章が流れていきます。

【コマンド解説】
:cherry_blossom: sudo
superuser do の略で、管理者権限で実行することを意味します。
これを入れ忘れると、データが保存されなかったり、
実行しても権限がないと言われてしまうので、入れ忘れに注意してください。
superuserはrootのことです。

:cherry_blossom: yum update
yumはパッケージを取得してそれをどうするかの指定です。
どうするかは今回はupdateなので更新します。
つまりupdateのところをinstallにすれば、パッケージのインストールができます。

:cherry_blossom: -y
何か質問されたらyesで答えるときに使用します。
毎回質問に対して返事するのが面倒なときに使うと便利です。

【参考サイト】
rootとは?
【完全初心者向け】Linuxのrootユーザとは?

②Apache,PHP,MySqlをインストールする

LAMP環境と言われるAMPの部分です。
ちなみにLは(Amazon) LinuxのLです。
sudo yum install -y httpd24 php70 mysql56-server php70-mysqlnd
⇨管理者権限でインストールするよ!
インストールするものは
・Apache
・PHP
・MySql
・PHPのMySql用ドライバー
だよ!

これを実行するとズラズラ文章が流れていきます。

Complete!

が表示すれば成功です。
【入力内容】
:sunny: 数字はバージョンを表しています。インストールするときによって
 使用するバージョンが異なるのでチェックしてください。

【コマンド解説】
:cherry_blossom: sudo yum install -y
詳細は前述済みです。
何をインストールするかの指定を-y以降に記述します。

:cherry_blossom: httpd24
Apache2.4をインストールします。
Apacheとはwebサーバーのことです。
webサーバーがないとPHPなどのプログラムが動かないので必要です。

:cherry_blossom: php70
今回の主役、PHPです。PHP7.0をインストールします。

:cherry_blossom: mysql56-server
今回使用するデータベースはMySqlです。MySql5.6のサーバーの方をインストールします。
サーバーの方と言うだけあって、クライアント(端末)の方もあります。
今回はインスタンス(サーバー)に入れたいので、サーバーの方のMySqlをインストールします。

:cherry_blossom: php70-mysqlnd
PHPでMySqlを使うためのドライバーをインストールします。
これがないとPHPでMySqlを使うことができないです。

サーバーを起動する

力尽きたので後日書きます。

続きを読む

AWS EC2上のUbuntu16.04にデスクトップ環境を構築してMacからつなぐ

はじめに

やりたいこと

AWS EC2上にUbuntu16.04のインスタンスをたてて、そのインスタンスにMacからリモートデスクトップが接続する

なんでこの記事を書いたか

ぐぐってみると、日本語、英語で色々なドキュメントが出てきた。それらを参考に色々試したがサクッとはいかなかった。「どれを参考にすればいいねん!」って人向けに「これを実行すればサクッと構築できるよ」という情報を提供したかったから書いた。
ちなみに、X Window System、ubuntu-desktop、xrdp、Xfce4、GNOME、KDE、Unity、vncなどなど、
用語がわからなすぎたのでハードルが高く感じた。

いまはただのメモ

とりあえずメモ代わりに投稿するかってくらいのモチベーションで投稿してます。
もしここの記述がようわからんとかあれば、コメントいただければ少しずつ細かく書いていきます。

想定している読者レベル

  • EC2インスタンスをたてたことがある
  • なんとなくlinuxコマンドがつかえる

用語

X Window System

ディスプレイにウィンドウを表示したり、マウスやキーボードを使ってやりとりするためのシステム。WindowsやMacではOSとこのソフトウェアが一体化しているが、Linuxでは一体化してないのでもし先述のことを行いたい(=GUI)場合はインストールする必要がある。MacのOS Xの「X」とWindowsの「Window」がまざった「X Window System」という名称で紛らわしい。単に「X」と呼ばれることもある。

Xfce, GNOME, KDE, Unity,

デスクトップ環境を実現するためのシステム。色々あってややこしいが、デスクトップ上のデザインや機能が微妙に違うだけ。今回はXfceを使った。

vnc

ネットワーク越しに、別のコンピューターのデスクトップ画面を表示してGUI操作することができるシステム。今回Ubuntuで環境を構築するにあたって参照した記事ではvnc4serverかTightVNCのどちらかを使っていることが多かった。

手順

EC2インスタンス作成

下記参照にEC2インスタンスを作成
– AMIはクイックスタートにあるUbuntu 16.04
– セキュリティグループのインバウンドにポート5901を追加
– とりあえずt2.microでいい

デスクトップ環境構築

この記事のとおりでOK

sudo apt update && sudo apt upgrade

sudo sed -i 's/^PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
sudo /etc/init.d/ssh restart

sudo passwd ubuntu

sudo apt install xrdp xfce4 xfce4-goodies tightvncserver

echo xfce4-session> /home/ubuntu/.xsession

sudo cp /home/ubuntu/.xsession /etc/skel

sudo sed -i '0,/-1/s//ask-1/' /etc/xrdp/xrdp.ini

sudo service xrdp restart

tightvncserver

つなぐ

Finder起動->移動->サーバへ接続で、vnc://XX.XX.XX.XX:5901に接続。
XX.XX.XX.XXは作成したEC2インスタンスのIPかパブリックDNSを指定。
ただしvncはそのままでは暗号化されてないので、セキュアなことをやりたい場合はご自身で対応をお願いします。

ついでに日本語化

chromeをインストールしたら、日本語が文字化けしてたので日本語化してみた。
“Language support” icon missing in System Settings
英語環境でセットアップしてしまったUbuntuで日本語を使えるようにする

補足

  • tightvncserverでなくてvnc4serverでもOKだと思います(試してない)
  • xrdpはaptからのインストールでOK

続きを読む

[AWS]Hello Glacier! AWS Glacierの使い方を知るための始めの一歩

以前からGlacierを理解したいと思っていて軽く触ってみたのでまとめてみた。
しかしながら、現在はS3のGlacierアーカイブ機能があるためS3経由でならこの記事のように複雑なことをしなくても料金の圧縮が行える。
Glacierというサービスがどのようなものなのかという理解を深める上で手を動かして理解するにはこの記事はちょうどよいのかもしれない。

想定読者

以下のような方を想定している。

  • AWSを使っている
  • S3を使ったことがある or ある程度の知識を持っている
  • AWS Glacierを聞いたことはあるけどよくわからない
  • 使ってみたいけど使い方が分からない

Glacierとは?

おそらく知っていると思うが念のため。

一言で言うと 利便性を落とした代わりに値段が安く使えるS3
主に似ているAWSサービスのS3と比較すると特徴は以下。

  • ストレージ単価が安い
  • 取り出すまでに若干のタイムラグがある

用途

アクセス頻度が低いが消したくないデータを低価格 & 高可用性な環境で保持したい場合に使用する。

用語

Glacierを理解するために以下の用語を抑えておく。

  • Vault(ボールト)

    • S3でいうバケット
  • Archive(アーカイブ)
    • S3でいうオブジェクト
    • ただしS3と異なりIDが振られていてそれを使ってアクセスする
    • S3のようにファイル間隔で気軽にはアクセスできない
    • Jobを経由して一定期間後に取得可能になる
  • Inventory(インベントリー)
    • Archive一覧を保持している
    • 公式いわく一日間隔でArchive一覧が更新されるらしい
  • Job (ジョブ)
    • Vaultを対象にして行う処理
    • 主にArchive/Inventoryの取得作業に用いる(他にあるかは分からない)

動作環境

項目 バージョンなど
OS Mac Sierra
AWS CLI aws-cli/1.11.180 Python/3.6.3 Darwin/16.7.0 botocore/1.7.38

使い方

以下の工程を使って使い方を学ぶ。
* Vaultの作成
* Archiveのアップロード
* Archiveの取得
* Archiveの削除
* Vaultの削除

Glacierは2017-12-02現在でマネジメントコンソールから利用することが出来ない。
故に今回はAWS CLIを用いて解説していく。
ただしここではAWS CLIの使い方や導入については解説しない。

Vaultの作成

これはマネジメントコンソールからでも作成可能。
だがそれ以降はCLIを使っていくことになるのでここでもCLIを使用する。
vault-name には付けたい名前をつければ良いが今回は qiita-test という名前を使っていく。
account-id- を指定すればIAMユーザに紐付いたアカウントIDが使用される。

# {{account-id}}は伏せている
$ aws glacier create-vault --account-id - --vault-name qiita-test
{
    "location": "/{{account-id}}/vaults/qiita-test"
}

これで作成が完了した。
以下で確認して該当のVaultが作成できていれば完了していることを確認できる。

$ aws glacier list-vaults --account-id -
{
    "VaultList": [
        {
            "VaultARN": "arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test",
            "VaultName": "qiita-test",
            "CreationDate": "2017-11-02T13:46:26.012Z",
            "NumberOfArchives": 0,
            "SizeInBytes": 0
        },
    ]
}

Archiveをアップロード

次に保存させたいデータをVaultにアップロードしていく。
コマンドは upload-archive を使用する。
ここで最も重要なのが出力された archiveId である。
以降のArchive取得時には必須パラメータなので忘れずに保存しておくこと。
ArchiveIdを忘れた場合は後述するInventoryを使うことになる。

# テスト用のファイル
$ echo 'Hello Glacier' > ~/simple.txt

$ aws glacier upload-archive --account-id - --vault-name qiita-test --body ~/simple.txt
{
    "location": "/{{account-id}}/vaults/qiita-test/archives/OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w",
    "checksum": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
    "archiveId": "OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w"
}

Archiveの取得

取得Jobの発行

GlacierではS3のように即時にデータにはアクセスできず、Jobを経由してデータを取得する。
データの取得Jobには速度・価格で3種類選べるので場合によって使い分けると良い。

image.png

まずはArchive取得Jobを投げる。
先程のArchiveIdを指定してあげる必要があるのだけど、パラメータがJSONなので一度ファイルに展開しておく。
jobのパラメータの説明は以下に記載。

  • SNSTopic

    • Jobが完了したときに通知するSNSのARNを指定する
    • これがないとJobが完了したときに通知をしてくれなくなる
    • SNSについての説明はここではしない
  • Tier
    • 上記の料金表に記載されている取得方法の種類を指定する
    • 左から順に Expedited|Standard|Bulk にそれぞれ対応している
$ cat ~/job.json
{
  "Type": "archive-retrieval",
  "ArchiveId": "OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w",
  "Description": "Hello Glacier Simple Message",
  "SNSTopic": "{{sns-arn}}",
  "Tier" : "Standard"
}

あとは上記のファイルを --job-parameters オプションで指定してあげれば良い。
出力される jobId は今後必要になるので覚えておこう。

# --job-parameter 'JSON' で直接記述することも可能
$ aws glacier initiate-job --account-id - --vault-name qiita-test --job-parameters file://~/job.json
{
    "location": "/{{account-id}}/vaults/qiita-test/jobs/3o5VRlLDKI7rc3kykAzvGqfHop1AbJbX5CjDAd9_GPaFtCVAcRDp6Rjp2Wigz5iVhQSZq8VAvy4xCNhjWVV_4Ie2Syci",
    "jobId": "3o5VRlLDKI7rc3kykAzvGqfHop1AbJbX5CjDAd9_GPaFtCVAcRDp6Rjp2Wigz5iVhQSZq8VAvy4xCNhjWVV_4Ie2Syci"
}

Jobの状態は以下のコマンドで確認できる。
もしJobIdを忘れた場合はこれで調べることも出来る。

$ aws glacier list-jobs --account-id - --vault-name qiita-test
{
    "JobList": [
        {
            "JobId": "3o5VRlLDKI7rc3kykAzvGqfHop1AbJbX5CjDAd9_GPaFtCVAcRDp6Rjp2Wigz5iVhQSZq8VAvy4xCNhjWVV_4Ie2Syci",
            "JobDescription": "Hello Glacier Simple Message",
            "Action": "ArchiveRetrieval",
            "ArchiveId": "OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w",
            "VaultARN": "arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test",
            "CreationDate": "2017-12-02T14:30:24.038Z",
            "Completed": false,
            "StatusCode": "InProgress",
            "ArchiveSizeInBytes": 14,
            "SNSTopic": "{{snsのarn}}",
            "SHA256TreeHash": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
            "ArchiveSHA256TreeHash": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
            "RetrievalByteRange": "0-13",
            "Tier": "Standard"
        }
    ]
}

完了JobからArchiveをダウンロード

JOBが完了すると以下のようなメッセージがSNS経由で来る。

{
  "Action": "ArchiveRetrieval",
  "ArchiveId": "OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w",
  "ArchiveSHA256TreeHash": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
  "ArchiveSizeInBytes": 14,
  "Completed": true,
  "CompletionDate": "2017-12-02T18:24:32.585Z",
  "CreationDate": "2017-12-02T14:30:24.038Z",
  "InventoryRetrievalParameters": null,
  "InventorySizeInBytes": null,
  "JobDescription": "Hello Glacier Simple Message",
  "JobId": "3o5VRlLDKI7rc3kykAzvGqfHop1AbJbX5CjDAd9_GPaFtCVAcRDp6Rjp2Wigz5iVhQSZq8VAvy4xCNhjWVV_4Ie2Syci",
  "RetrievalByteRange": "0-13",
  "SHA256TreeHash": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
  "SNSTopic": "{{sns-arn}}",
  "StatusCode": "Succeeded",
  "StatusMessage": "Succeeded",
  "Tier": "Standard",
  "VaultARN": "arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test"
}

これでArchiveからダウンロード出来る環境は整ったのでJobIdを指定してArchiveのダウンロードを行っていく。

$ aws glacier get-job-output --account-id - --vault-name qiita-test --job-id 3o5VRlLDKI7rc3kykAzvGqfHop1AbJbX5CjDAd9_GPaFtCVAcRDp6Rjp2Wigz5iVhQSZq8VAvy4xCNhjWVV_4Ie2Syci ~/job-output.txt
{
    "checksum": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1",
    "status": 200,
    "acceptRanges": "bytes",
    "contentType": "application/octet-stream"
}
$ cat ~/job-output.txt
Hello Glacier

無事Archiveをダウンロードすることが出来た。

Archiveの削除

Archiveおいておくだけでお金は掛かるのでそれが気になるという場合はArchiveを削除できる。
削除にはArchiveIdを指定すれば良い。
ちなみに削除してもすぐにはインベントリには反映はされず最大1日掛かる。

$ aws glacier delete-archive --account-id - --vault-name qiita-test --archive-id OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_4AoGB7UP8w
# 何も出力されない

Vaultの削除

Archiveが完全になくなったらVaultを削除することが出来る。
もしArchiveが存在する場合は下記のような例外が投げられる。
消した直後でおかしいと思うが、どうやらInventoryが更新されたタイミングでArchiveが存在しなくなるまではVaultの削除は出来ないようだ。
Inventoryの更新までおとなしく待つ(ちなみにInventoryの更新を手動で行う方法は調べた感じないようだった)

$ aws glacier delete-vault --account-id - --vault-name qiita-test

An error occurred (InvalidParameterValueException) when calling the DeleteVault operation: Vault not empty or recently written to: arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test

Inentoryが更新されて空になったらVaultが削除できるようになっているので試してみよう。

$ aws glacier delete-vault --account-id - --vault-name qiita-test
# 上記の例外を除いて成功・失敗に関わらず何も表示されない

Vaultの一覧を取得してVaultがなくなっていることを確認してなくなっていれば削除完了。

$ aws glacier list-vaults --account-id -
{
    "VaultList": [
    ]
}

もしArchiveIdが分からなくなった場合

ArchiveIdがないとArchiveを取得することすら出来ない。
もしArchiveIdを保存し忘れていた場合はInventory経由でArchiveIdを知る必要がある。

以下のようにInventoryの取得Jobを流して一覧を取得することが出来る。
ただしこちらもArchive取得と同様に待たされる上にTierで速度も選べないので数時間の待ちが発生する。
そのため、基本ArchiveIdはupload時点で保存するようにしておくことをオススメする。

Inventoryを取得するJsonパラメータを以下に記載。

  • Format

    • 取得結果のファイル形式
    • JSON|CSV のどちらかが選べる
  • SNSTopic
    • Archive取得Jobと同義
  • InventoryRetrievalParameters
    • オプションで指定可能。Archiveの絞込が行える
    • .StartDate
      • 取得したいArchiveの開始期間
    • .EndDate
      • 取得したいArchiveの終了期間
    • .Limit
      • Archiveを最大何件表示するか
      • なければ限界の数を返す

以下のパラメータの場合は2017/11/23 ~ 2017/12/03 の間で1000件という条件を付けてInventoryからArchiveの検索を行っている(1件しかないから意味ないけど)。

$ cat ~/inventory_job.txt
{
  "Type": "inventory-retrieval",
  "Description": "Get Inventory",
  "Format": "JSON",
  "SNSTopic": "{{sns-arn}}",
  "InventoryRetrievalParameters": {
      "StartDate": "2017-11-23T00:00:00Z",
      "EndDate": "2017-12-03T00:00:00Z",
      "Limit": "1000"
   }
}

$ aws glacier initiate-job --account-id - --vault-name qiita-test --job-parameters file://~/inventory_job.json
{
    "location": "/{{account-id}}/vaults/qiita-test/jobs/CgRscKViRNU3XUgqxa8nu8GWVhmkw3yEWEZ089NZ0Hj-a9aL86QHHc5zav3VW2e5KUHm5AQwoMEd5QjNysg9EWYvhSin",
    "jobId": "CgRscKViRNU3XUgqxa8nu8GWVhmkw3yEWEZ089NZ0Hj-a9aL86QHHc5zav3VW2e5KUHm5AQwoMEd5QjNysg9EWYvhSin"
}

Jobが発行されていることをArchiveの時と同様に list-jobs で確認できる。
今回はオプションに --completed false を付けることで完了していないJobのみを表示するようにした。

$ aws glacier list-jobs --account-id - --vault-name qiita-test --completed false
{
    "JobList": [
        {
            "JobId": "CgRscKViRNU3XUgqxa8nu8GWVhmkw3yEWEZ089NZ0Hj-a9aL86QHHc5zav3VW2e5KUHm5AQwoMEd5QjNysg9EWYvhSin",
            "JobDescription": "Get Inventory",
            "Action": "InventoryRetrieval",
            "VaultARN": "arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test",
            "CreationDate": "2017-12-03T01:28:54.814Z",
            "Completed": false,
            "StatusCode": "InProgress",
            "SNSTopic": "{{sns-arn}}",
            "InventoryRetrievalParameters": {
                "Format": "JSON",
                "StartDate": "2017-11-23T00:00:00Z",
                "EndDate": "2017-12-03T00:00:00Z",
                "Limit": "1000"
            }
        }
    ]
}

Archiveと同様にJobが完了するとSNS経由で通知が来る。
通知を確認したらArchiveの取得と同様に JobId をパラメータに渡してあげることでInventoryからArchiveの一覧を取得することが可能。

$ aws glacier get-job-output --account-id - --vault-name qiita-test --job-id CgRscKViRNU3XUgqxa8nu8GWVhmkw3yEWEZ089NZ0Hj-a9aL86QHHc5zav3VW2e5KUHm5AQwoMEd5QjNysg9EWYvhSin ~/output-get-inventory.json
$ cat ~/output-get-inventory.json  | jq .
{
  "VaultARN": "arn:aws:glacier:ap-northeast-1:{{account-id}}:vaults/qiita-test",
  "InventoryDate": "2017-12-02T17:21:20Z",
  "ArchiveList": [
    {
      "ArchiveId": "OQBATOmxpMSNOxXPrKLkGiISpNvtZySRe-Dg_PvVbg3zxfIRGa3o1et33LvKvZSJA2_nAHW9eMhclfJAbGLzSV3owDcUvNglvAEafu67wKsECismh1uRL_-Rz0rCsGD_
4AoGB7UP8w",
      "ArchiveDescription": "",
      "CreationDate": "2017-12-02T13:57:27Z",
      "Size": 14,
      "SHA256TreeHash": "6b72933f6d83bb1294fef21c290d86ad4d5bacee89a0e34eca284c08b00e88c1"
    }
  ]
}

おわりに

S3は使い方を分かっているけどGlacierをどう使っていいか分からない、という読者の疑問はこの記事を読んで多少は解消されることを期待している。
他にも分割アップロードや分割ダウンロードなどの機能があるようですが、あくまでも触りということで記載しなかった(し、自分も試してないので分からない)。
気になる方は公式の資料などを読むことでより理解が深まると思う。

参考

続きを読む

docker-lambdaでAWS Lambda環境をお手軽に動かす

Lambdaを使ってみて

お疲れ様です。本記事は、AWS Lambda Advent Calendar 2017の3日目の記事になります。

さて、Lambdaをがっつり本番環境で動かしていますという話もチラホラ聞きますが、私はLambdaを使い始めて約1年半、まだいまひとつガッツリ本番な気分になっていません。

その理由の一つがローカル開発環境問題です。ありきたり!
少し複雑なコードを書くとなると、デバッグしながらになるのでローカルマシンの使い慣れたエディタでやりたいところ。
ではささっと環境構築してみましょう。

ローカル開発環境の整備

その :one: 各言語のSDKをインストール

Lambdaで使える言語は今のところnode.js, Python, C#, Java(今後Golang, .NET)がありますが、MacでLambdaの開発環境を構築するとなると、

  • nodejs用

    $ npm -g install aws-sdk
    
  • Python用

    $ pip install python-lambda-local
    
  • Java用
    AWS Toolkitをインストール

以下略

当然全言語を使うわけではないですが、用途によって適正言語を使おうとすると、それぞれランタイムが必要。これらを今後バージョン管理していくとなると、若干見通しが暗く。。

その :two: 必要なライブラリのインストール

さらに、モジュールをImportしたりすると、それらもそれぞれbuild && デプロイパッケージ(要はZIPで固めて)をLambdaにアップロードするという二手間作業の発生

その :three: invalid ELF header対策

Lambdaは実際にはEC2のAmazon Linuxを立ち上げて動くことになるので、macOS上でビルドされたライブラリでは動かないケースが有る。いわゆる invalid ELF header 問題である。
じゃあちょろっとEC2インスタンス立ち上げて、ビルドしてパッケージングすれば解決。

もうローカル環境じゃなくなっちゃった!

2016年まではこのツラみがありましたが、今はもうAmazon Linux Dockerイメージが出た!
これでEC2インスタンスを立ち上げるのはなんとか回避できるようになりました。

その :four: 実行&デプロイ(ざっくり)

  • nodejs用
    メイン処理を実行するファイル lambda_handler.jsを作る(ファイル名は任意で)

    $ node lambda_handler.js
    

    実装が完了したら、それらを固めてデプロイするためのgulp.taskを作る

  • Python用
    メイン処理を実行するファイル lambda_handler.pyを作る(ファイル名は任意で)

    $ python-lambda-local -l lib/ -f handler -t 30 lambda_handler.py event.json
    

以下略再び

というか、 サーバのプロビジョニングの必要がないのがLambdaの売りなので、 もうだいぶ前の時点で既に本末転倒なんじゃないか感があったのに気づかないふりをしていた自分。。

もっとこうスマートに何か!

こういった状況の一つのソリューションとしてServerless Frameworkがあります。

こちらメリットとしては、

  • slsコマンドでビルドやデプロイなど大抵のことが一発完了
  • 設定もymlでパッケージ管理しやすい

という点がありますが、一方で

  • npmインストールして使う以上、結局管理対象が増えるのでは?
  • AWSの管理者権限が付与されたIAMロールが必要
    (なので、所属組織によっては開発者全員が使うのは難しいかも)
  • 上述のネイティブライブラリ問題は据え置き

といった点が個人的には引っかかりました。

ここまでで既に勘の良い人はお気づきかと思いますが

Dockerで管理する

「これDockerで全部やれるんじゃないか?」

先程このへんで触れたAmazon Linux Docker Image、これに諸々入れる分にはローカルのマシンを基本汚さずに済む。
そして、開発が終わったらコンテナごと廃棄ドボンしてスッキリ。

あとはまた、Lambdaでの開発が必要なときにそのつど docker buildするわけですが、うまいことDockerfileが保守されていれば理想的ですよね。

何より普段Dockerでアプリケーション開発してるんなら、Dockerでまとめてしまうのが見通しがよい。

そんなLambda用のDockerイメージを作れば良い

と思いましたが、lambci/docker-lambdaというLambda用Dockerイメージがありました。

Screen Shot 2017-12-04 at 2.50.05.png
https://speakerdeck.com/hihats/aws-lambdafalsejin-xian-zai?slide=18

LambciというOSSプロジェクトなので、自作より皆で保守していったほうが間違いない。
活発にアップデートされているので、Goへの対応なども楽しみです。

さっそく使ってみる

lambci/lambda:python3.6 イメージをベースにして実行

print_json.py
# 引数で渡されたJSONをそのまま出力するだけのPythonスクリプト
import json

def lambda_handler(event, context):
    print(event)
sample.json
  {
    "type": "products",
    "id": 123,
    "attributes": {
      "name": "Fun Toy",
      "description": "Toy for infants",
      "state": "in sale",
      "slug": "4b5366e5",
      "photo": "TOY.jpg"
    }
  } 

以下のように、(拡張子なしファイル名):関数名 をコマンドとしてイメージに渡してdocker runしてあげるだけで、実行環境のbuild(イメージがなければpullも)から関数実行までやってくれます

$ docker run -v "$PWD":/var/task lambci/lambda:python3.6 print_json.lambda_handler $(printf '%s' $(cat sample.json))

Unable to find image 'lambci/lambda:python3.6' locally
python3.6: Pulling from lambci/lambda
5aed7bd8313c: Pull complete
d60049111ce7: Pull complete
216518d5352c: Pull complete
47aa6025d0bc: Pull complete
9a82bb1662ac: Pull complete
Digest: sha256:3663b89bd1f4c4d1a4f06a77fc543422c1f0cbfc3a2f491c8c7bdc98cf9cf0b6
Status: Downloaded newer image for lambci/lambda:python3.6
START RequestId: 6cf5f6b5-bf62-49de-aaa4-f05b1148b67e Version: $LATEST
{'type': 'products', 'id': 123, 'attributes': {'name': 'FunToy', 'description': 'Toyforinfants', 'state': 'insale', 'slug': '4b5366e5', 'photo': 'TOY.jpg'}}
END RequestId: 6cf5f6b5-bf62-49de-aaa4-f05b1148b67e
REPORT RequestId: 6cf5f6b5-bf62-49de-aaa4-f05b1148b67e Duration: 109 ms Billed Duration: 200 ms Memory Size: 1536 MB Max Memory Used: 19 MB 

実行ファイルとコマンドワンライナーでいけました :exclamation::sushi:

build用イメージ lambci/lambda:build-python3.6 をベースにして環境をbuild

Lambdaがデフォルトで扱ってくれないライブラリが必要な処理のケースでは、ビルド & デプロイパッケージングするためのイメージ(lambci/lambda:build-python3.6)をベースにDockerfileでゴニョゴニョする

FROM lambci/lambda:build-python3.6
ENV LANG C.UTF-8
ENV AWS_DEFAULT_REGION ap-northeast-1

WORKDIR /var/task
ADD . .

RUN /bin/cp -f /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
  pip install -r requirements.txt -t /var/task

CMD zip -9 deploy_package.zip search.py && \
  zip -r9 deploy_package.zip *

必要なモジュールはrequirements.txtに羅列

requirements.txt
twitter
pymongo
numpy
requests_oauthlib
pytz

ビルドしてみる

$ docker build -t twitter_search .

Sending build context to Docker daemon  107.5MB
Step 1/7 : FROM lambci/lambda:build-python3.6
 ---> a895020ff4f5

<中略>

Successfully installed certifi-2017.11.5 chardet-3.0.4 idna-2.6 numpy-1.13.3 oauthlib-2.0.6 pymongo-3.5.1 pytz-2017.3 requests-2.18.4 requests-oauthlib-0.8.0 twitter-1.18.0 urllib3-1.22  

必要なモジュールがdockerに入っていき、実行環境が整いました。

既に上のDockerfileのCMD文にsearch.pyとファイル名が書かれていますが、今回は 「Twitterで特定のキーワード検索した結果をmongodb(ローカルのDockerコンテナ)にINSERTする」 Lambda関数を用意。

search.py
import os
from twitter import *
from requests_oauthlib import OAuth1Session
from requests.exceptions import ConnectionError, ReadTimeout, SSLError
import json, datetime, time, pytz, re, sys,traceback, pymongo, pprint
from pymongo import MongoClient
from collections import defaultdict
import numpy as np
import csv

def lambda_handler(event, context):
    pp = pprint.PrettyPrinter(indent=4)
    mongo_host = os.environ['MONGODB_HOST']
    secrets = {
        'consumer_key': event['CONSUMER_KEY'],
        'consumer_secret': event['CONSUMER_SECRET'],
        'access_token': event['ACCESS_TOKEN'],
        'access_token_secret': event['ACCESS_SECRET']
    }
    s_pa = [
        secrets["access_token"],
        secrets["access_token_secret"],
        secrets["consumer_key"],
        secrets["consumer_secret"]
    ]
    query = event["KEYWORD"]

    t = Twitter(auth=OAuth(s_pa[0], s_pa[1], s_pa[2], s_pa[3]))

    client = MongoClient(mongo_host, 27017)
    db = client.twitter_db
    tw_collection = db.tweets
    metadata_collection = db.metadata

    while(True):
        results = t.search.tweets(q=query, lang='ja', result_type='recent', count=100, max_id=0)
        metadata_collection.insert(results['search_metadata'])
        if len(results['statuses']) == 0:
            sys.stdout.write("statuses is none. ")
            break
        for tw in results['statuses']:
            if tw_collection.find_one({'id': tw['id']}) is None:
                tw_collection.insert(tw)

        print(len(results['statuses']))
        if not 'next_results' in results['search_metadata']:
            sys.stdout.write("no more results. ")
            break
        since_id = results['search_metadata']['since_id']

secret.jsonにTwitterAPIのKEYやらmongoのホストアドレスやら検索したいクエリやらをぶちこみ :arrow_down: 実行する

$ docker run -v "$PWD":/var/task lambci/lambda:python3.6 search.lambda_handler $(printf '%s' $(cat secret.json))

START RequestId: afc3baf7-09d1-4407-85b7-e012e759053b Version: $LATEST
30 recorded
no more results. END RequestId: afc3baf7-09d1-4407-85b7-e012e759053b
REPORT RequestId: afc3baf7-09d1-4407-85b7-e012e759053b Duration: 2070 ms Billed Duration: 2100 ms Memory Size: 1536 MB Max Memory Used: 38 MB

とれました :exclamation::sushi::sushi:

注意するのは、ビルド用と関数実行用のイメージは別(`lambci/lambda:build-python3.6`はあくまでビルド&パッケージングだけで使う)ということ

無事実装が終わればパッケージング

$ docker run -v "$PWD":/var/task --name twitter_search twitter_search:latest
$ ls deploy_package.zip
deploy_package.zip

deploy_package.zipができていることが確認できました。

結論

Docker最高ですね。

LambdaのAdvent calendarなのにDockerアゲになってしまいました :bow:

参考記事

Serverless Frameworkのプラグインを利用した外部モジュールの管理
AWS Lambda 用の python パッケージをクロスコンパイルして serverless で deploy した話

続きを読む