Ansible入門する

現在のプロジェクトでAnsibleが使われていて、ちょっとセットアップ法や概念を知る必要があったので、この際一からやってみます。
弊社の江川先生の例を参考に、とりあえずの練習用のお題として以下を目標にします。

  • (1)ubuntu環境のdockerコンテナ/イメージをビルドし、sshd立ち上げたコンテナを2台作る。
  • (2)実行中のコンテナ群に対して同じモジュールのインストールをansibleでやってみる。
     

(2)の作業をAnsible側でplaybookを作ることで試してみます。

(Ansible2.0からはDocker Connection Pluginによってコンテナ側でsshdを立ち上げる必要ないっぽいんですが、あくまで通常のLinuxインスタンスのデプロイのようにやってみます。)

Ansible is 構成管理ツール

Ansibleは Chef , Puppet のような 構成管理ツールの一種となります。
「非ruby」、「デプロイ先でのエージェント不要」、「設定ファイルが少ない/記述がシンプル」などの特徴がChefとの大きな違いのようです。

なんでシェルスクリプトでのデプロイだとダメなんですか?

chefやAnsible等の構成管理ツールの利点は、デプロイ手順の冪等性の担保だそうです。つまり、同じデプロイコードを何度叩いても常に結果が同じになるようにしようぜ、ということ。
シェルスクリプト等でのデプロイと違い、「現在の状態」に対する判定処理を自前で書くことなく、DSLを通してデプロイ済みの状態と新しい状態の差分を吸収させることを目的としています。
「サーバを構築する」という以上に「サーバのあるべき姿を規定する」ことがこの手の構成管理ツールの役割なんだそうです。

サーバー多くなんないと意味ないのと、そもそも世間的にはコンテナとかimageでデプロイ毎にdisposableにしようぜって流れかとは思うんですが、やってみます。

テスト用Dockerコンテナのセットアップ

今回はこんな感じのイメージからコンテナを複数立てます。
pythonとSSH環境だけは必要なので入れておきます。
「siteuser」にSUDO権つけときます。

Dockerfile

From ubuntu:14.04

MAINTAINER fushimi <xxxx@xxx.co.jp>

RUN apt-get update
RUN apt-get install -y software-properties-common
RUN apt-get install -y ssh \
  python-apt

RUN /etc/init.d/ssh start

ENV username siteuser
ENV home /home/${username}
ENV password hogehoge

RUN useradd -G sudo -m ${username}
RUN echo ${username}':'${password} | chpasswd

CMD /etc/init.d/ssh start  && tail -f /dev/null

image作ります。ansible_testってimage名にします。

docker build -t ansible_test/latest ./  

web1,web2って名前で2つばかりコンテナを立ててみます。とりあえずSSHのポート用にdocker-machineの8001,8003をコンテナの22番に当てておきます。

docker run -i -t -d --name web1 -p 8000:80 -p 8001:22  ansible_test/latest 
docker run -i -t -d --name web2 -p 8002:80 -p 8003:22  ansible_test/latest 

動いてます。

docker ps -a
CONTAINER ID        IMAGE                 COMMAND                  CREATED             STATUS              PORTS                                        NAMES
c3c562a5a54f        ansible_test/latest   "/bin/sh -c '/etc/ini"   6 minutes ago       Up 6 minutes        0.0.0.0:8001->22/tcp, 0.0.0.0:8000->80/tcp   web1
14c038eb3f54        ansible_test/latest   "/bin/sh -c '/etc/ini"   6 minutes ago       Up 6 minutes        0.0.0.0:8003->22/tcp, 0.0.0.0:8002->80/tcp   web2

docker-machineのIPが192.168.99.100 で出来てるので、これで各ポートからパスワード認証でsshができるようになりました。
 

ssh -p 8001 siteuser@192.168.99.100  
ssh -p 8003 siteuser@192.168.99.100  

 
テスト環境のセットアップはここまでです。
これらを「稼働中のコンピュータ」と想定して、ansibleによるプロビジョニングの実験を行いたいと思います。

Ansible 設定

Inventryファイル

Ansibleがつなぎに行くサーバ群の設定yml。慣習として「hosts」というファイル名にするようです。
普通は公開鍵認証でつなぎに行って、アイパスもここに書いたりしないですがとりあえず実験として目をつぶっておきます。

 
hosts

[web]
web  ansible_connection=ssh ansible_sudo_pass=hogehoge ansible_ssh_pass=hogehoge ansible_user=siteuser ansible_port=8001 ansible_host=192.168.99.100
web2 ansible_connection=ssh ansible_sudo_pass=hogehoge ansible_ssh_pass=hogehoge ansible_user=siteuser ansible_port=8003 ansible_host=192.168.99.100

 

PlayBook

hostsで規定したサーバ群に対してデプロイを実行します。
Ansibleの肝です。 ここのDSLを通すことによってAnsibleはサーバーの状態の差分管理を行います。
vimだけインストールするタスクを書きました。

 

- name: TEST
  hosts: all
  become: yes
  remote_user: "{{ ansible_user }}"
  tasks:
    - apt  : pkg=vim state=installed force=yes

実行

playbookを走らせます。

ansible-playbook task.yml  -i hosts 

 
結果
 

PLAY [TEST] *********************************************************************

TASK [setup] *******************************************************************
ok: [web]
ok: [web2]

TASK [apt] *********************************************************************
changed: [web]
changed: [web2]

PLAY RECAP *********************************************************************
web                        : ok=2    changed=1    unreachable=0    failed=0   
web2                       : ok=2    changed=1    unreachable=0    failed=0   

 
もう一回走らせてもvimのapt-getは実行されません。

PLAY [TEST] ********************************************************************

TASK [setup] *******************************************************************
ok: [web]
ok: [web2]

TASK [apt] *********************************************************************
ok: [web]
ok: [web2]

PLAY RECAP *********************************************************************
web                        : ok=2    changed=0    unreachable=0    failed=0   
web2                       : ok=2    changed=0    unreachable=0    failed=0   

 
確認すると、vimが入ってます。

ssh -p 8003 siteuser@192.168.99.100  
which vim
/usr/bin/vim

まとめ

こんな感じでデプロイができました。
頻繁にデプロイがあったりサービスが複雑になってこないと「それshellのif文でよくね?」となってメリットが薄いですが。。
Docker用プラグインやAWS用のプラグインで面白いことそれなりにできそうですね。
(今回、Ansibleと言うより、すぐに試用環境をぶっ壊せるコンテナ環境の良さに改めて目が向いた感じです)

Read More

Google先生が考えているモバイル体験2017(Web/ネイティブ)

Googleは「モバイルWebの読み込み負荷の問題」や「ネイティブアプリのユーザー体験の冗長さ」を解消するために、昨今いろんな施策を出している。
今年に入ってからもいろいろな実装をリリース/プレビューとして入れ込んできていて、今現在の状況が追いかけづらいので何が何なのか一旦整理しようと思う。

  • AMP (Accelerated Mobile Pages)
  • PWA (Progressive Web App)
  • WebAPK
  • Instant Apps

今回取り上げるのは以上の4つである。


AMP (Accelerated Mobile Pages)

AMPは、WebサイトをGoogleのキャッシュサーバーから配信することで高速な表示を目指そう、という技術の総称である。すでに日本でも実装済み。

たまにモバイル検索結果で出てくるこの⚡マークが目印。

 
 

 
 
基本的には記事メディアでの用途になる。現行ですでに実装されており、対応サイトも多い。

Pros

高速

これに尽きる。Googleがキャッシュしているのでムッチャ速い。
データ転送量が従来の1/10にもなるという。

あくまでGoogle検索上での実装なので、もちろんiOS/Android問わずその恩恵に預かることができる。

Cons

基本的には開発コスト/広告周りがデメリットとしてよく挙げられる。
AMPに対応したHTMLファイルを別途別ページとして用意する手間はがっつりかかってしまい、それなりの構造化作業を必要とする。

タグ制限/独自タグへの置き換え

script , frame,input , labelなど禁止されているタグもいっぱいある。
このへんはChromeでデバッグが可能。
MediaElements系は<amp-image />, <amp-video />などの独自タグに置き換えなければいけないなど、制限が多い。
ソーシャル連携のための<amp-twitter /> などもあって、対応には独自の知識が何かと必要になりそう。

AMP JS以外のスクリプトの制限

jsによる実行ブロックを防ぎ、ページの高速化を図るために amp HTMLでは規定のjavascriptファイル以外の実行を許されていない。
当ブログもAMPのプラグイン入れたけど、コードブロックの表示にhighlight.jsを使っているため、特にその辺はまともに動いていないので、この辺はがっつりCSSで描画をなおす必要がありそう。

広告

js/iframeなどの実行が許されていないので、当然既存の広告プラグインもそのままでは動作しない。
このへんはGoogleが <amp-ad /> などの専用タグを使った施策を紹介している。

 
 
 


PWA (Progressive Web App)


Webアプリ(サイト)をネイティブアプリみたいに使おうぜ、というGoogle先生の施策の一つである。
プログラムやデータをバックグラウンドプロセス上でキャッシュすることで、ページの高速化やオフラインでの動作が可能になる。
また付随技術としてWebアプリでもPush通知が使えるようになる。

ここにサンプルになりそうなサイトが溜まっている。
 
PWA Rocks
 
特にAndroidだと、対応サイトは「ホーム画面に追加」してネイティブアプリの感覚で扱うことができる。

Pros

ServiceWorker/ ホーム画面アプリモードによるスタンドアローン

W3Cで策定されている、ServiceWorker API というWebページとは別に実行されるスクリプトを使う。バックグラウンドプロセスみたいなものかな?
(WebWorkerは別スレッドみたいなもんだから間違えないように。)

Android Chromeの場合、サイトのルートに規定のjsonファイルを置けば、GoogleがPWA対応アプリとして認識し、ホーム画面への登録を促すToastを出してくれる。
タップすると、すぐにホームにアイコンが追加されるはず。

Push通知

Push API(WebPush)も基本このServiceWorkerのプロセス上に乗っているはずなので、PUSH通知ができる。
対応サイトでは、通知の認可を求めてくるようになる。
使える場面は結構多いはず。

Cons

iOSとかいうやつ

iOS Safariは ServiceWorker/WebPushなどのAPIが2017.1 現在非対応である。
要するにオフラインでの プログラム/APIからのデータのキャッシュや PUSH通知機能を使うことができない。

iOSは現状でもホーム画面アプリモードとしてWebアプリを登録自体は可能である。(Safariが立ち上がらず、ヘッダーとか隠せるようになるやつ。)
できないこともないが、もちろんオフラインで操作することはできない。 (あるいはAppCacheなどで頑張る)
 
URL遷移をやろうと思ったら、普通に遷移するとネイティブのSafariが立ち上がってしまうので、シングルページアプリケーション的なルーティングが必要になってくる。  
また、Webアプリの実行状態から抜けるたびにレンダリングやルーティングの状態がリセットされるので、状態を再度復元しておこうと思ったら、
localStorageなどを駆使して、がっつりアプリケーションの状態を保存するアーキテクチャを用意しないといけない。
ネイティブアプリ/SPA的な知識がかなり要求されてくる。
 
 
個人的には面白そうな分野なのでiOS SafariにはぜひServiceWorker,WebPUSH等の技術に対応してもらいたい。
だけど、Appleが考える(Appleが胴元の)ユーザーエクスペリエンスのあり方と微妙に違う気もするので、ホーム画面アプリとしてのPWAには及び腰になりそうな感じもする。

バックグラウンド動作でのバッテリー消費とかメモリ食いとか

PCのChromeアプリが裏で連れてくる、元気に跳ね回るプロセスのみなさんを彷彿とさせて若干不安になる。
Facebookアプリのバッテリー消費プロセスと同様のことがWebサイト開発者側の実装次第で起きるとしたら、携帯端末の状態が結構カオスになりそうな気もする。
このへんは技術要件をがっつり調べたわけではないので、確認しておきたい。
 
 
 


WebAPK(Android)


ここから先はAndroid独自の話となる。

WebAPK というのは先日実装が開始された機能である。   
Google、Android用ChromeにWEBサイトをアプリとして利用可能にする「WebAPK」を実装開始 -ガジェット通信-
 
 
これによると、先のPWAをパッケージングして、ネイティブアプリと同じようなchromium上で動作させる仕組みらしい。要するにWebView上のアプリである。
WebサイトをAndroidアプリのパッケージ形式であるAPKに変換して端末にインストールさせる。  
具体的にPWAのホーム画面登録時とユーザー体験としてどのように違うのかは、資料が足りないのでよくわかっていない。  

(規定のWebView上で動作させるようになるとすれば、Webアプリケーション側から専用のAPIとか叩けるようになったりするのかな?)
ただ、この方式のアプリの配布はどうやらGooglePlay側からできるようになるそうで、そこは面白いかな、と思う。 
(WebAPKの配布は既にGooglePlay側で機能しているそうである。QRコードからもインストールできるようになるとか。)
 
 
 


Instant Apps (Android)

Chrome Nightly Buildから実験的に実装が始まった機能。
これに関してはHTML5のWebアプリを動かそう!という文脈ではなく、コンパイル済みのAndroidのネイティブアプリをChrome上で一時的に呼び出して動作させようという施策のようである。

公式の先のVideoや、下のgifを見ると使用感がわかりやすい。
 
 
公式
事前のインストールなしでアプリを実行できる「Android Instant Apps」の実地試験が開始される -Gigazine
Android Instant Apps starts initial live testing
 
 

 
 

既存のAndroidネイティブのコードがそのまま利用可能で、Android4.2から対応できるようになるそうだ。(やめろ)
呼び出しの重さや対応できるAPIが気になるところ。SDKが提供されたら触ってみたい。
 
 
 


まとめ

AMPのようなGoogle独自の高速化施策を打つ一方、Webアプリとネイティブの垣根を取り払っていくようなアプローチが続々出てきて、大変だと思いつつものによっては面白いこともできそう。
全部の実装が果たして軌道に乗るのか、どれに乗っかるべきなのかは注視していきたい。

Read More

DynamoDB使ったり調べたまとめ (2017Early)

書くこと

サ〜バ〜レスな構成にお安く気軽なデータストアが欲しかったのでちょっと調べた。
世間的にはDynamoDBを割と併用してるっぽいので。


1.小規模用途で安いデータストアが欲しいだけ

初期コスト以外の要素をあまり気にしない方向で考える場合。

基本

  • 最小は1読みキャパ/1書きキャパで約0.6$ / 月
  • でもアカウントごとに無料枠は25ユニット無料
  • この確保量が月額かかるので別に使用量従属課金というわけでは全然ない

読み込み

Get

  • まあKVSなので、基本はユニークなHashKeyのみでアクセスできるようにする
  • プライマリキーはHashKey(順不同),かHashKey + RangeKey(範囲指定、Sort可)から成る
  • ユーザーごとに独立してるデータとか。
  • indexは グローバルセカンダリインデックスと HashKeyに従属するローカルセカンダリインデックスがある。
  • 安く済ませたいならグローバルセカンダリインデックス(ユニークキーを含まないindex)は諦めろ

Scan、Query

  • 小規模ってわかってるなら理性捨ててScan(index全件取得)するのもあり
  • 読み込み時に強い読み込み整合性モードを使えば、直前の書き込み全て反映されたレコードとして取得できる、(ただしRCU消費2倍)
  • 範囲指定やソートができるのはRange Keyのみ。
  • 基本検索はそこまで強くないと思っておく

書き込み

  • 普通に update column += 1 とか使える (これと先の”強い整合性”があるだけでもS3にJSON突っ込んでデータストア代わりにするのと話は違ってそれなりに意味はある気がする。)
  • 条件付きアップデートも可能
  • レコード間でのトランザクションはもちろん保証されない
  • 特に複数テーブル書き込みに関してはオレオレトランザクション化要るので基本諦めろ

2.大規模用途での可用性や整合性の担保についてメモ

読み込み

Get

  • 基本はこっちでもユニークなキーのみでのget,シンプルなKVSとしての利用にすることを心がける
  • HASH KEY(の頭文字)は散らす。データサイズがでかくなるとHASH KEYをもとに自動でパーティションが切られるから。
  • グローバルセカンダリインデックス多用しそう。この場合インデックスごとにキャパシティユニット設定する。
  • データサイズは小さくしよう。

Scan , Queryについて

重要

  • Scanはセカンダリインデックス全件取得、QueryはRangeKeyやIndexの内容に従って走査する
  • 1度のレコード取得クエリに対してデータが1MB上限あるので、テーブルがでかくなると一度にScanできない可能性はある
  • LIMITやフィルタ機能はRCU消費を何ら抑えることはできない,あくまでSQLのWHERE文的にクエリ書きやすくなるだけっぽい
  • Queryはともかく特に大規模用途だとScanは使わないに越したことがない。

クエリキャッシュ

  • 同じテーブルが参照されまくるとRCU消費するのでElastiCacheとか前に置いてるケースを散見。結局そうなるんか。

書き込み

DynamoDB ベストプラクティス 参考にした
DynamoDBでのポイントまとめ

複数テーブル同時更新

  • テーブル分割時、複数テーブル書き込み必要な時は自前でACID整備することとなる。NoSQL…
  • 更新依頼書テーブル的な実装/タスクキュー的な実装の上でupdateという形をとる(辛そう)
  • スループット確保のためどうしてもテーブル分けたり非正規化しなければ場面も出てくるので、全く触れなくて済むというわけでもないかも????
  • DynamoDB Stream も使えるそう。

同じレコード(というよりテーブル)に対する大量のupdate

  • 同じレコードに同時に大量にincrement指定がくるような場合、そのパーティションのスループットが非常に下がる。
  • ここでも更新リクエストをタスクキュー的に実装しておいて、読み込み時にそれらをSUMするか、1つのレコードにまとめる実装をする必要がある。

感想

  • 単なるKVS以上の何かを求め始めた場合、結構留意することありそう。(うまい話はない)
  • トランザクションって素敵だね…
  • 他のNoSQLとしてAWS SimpleDBってのもあったけど死んでしまったの?
  • GCP周りのサービスも調べたい

Read More

TypeScriptのテストをパッと書く / 特に何も入れずにスクレイプのテストをパッと書く

TypeScriptのテストをパッと書く

typescriptのユニットテストしたいんじゃー的な時。余計なビルド結果を出力したくないのでプリプロセッサ機能必須。
ts-jest で書いていたが なんかコレジャナイ感あったので 一周回って mocha + power-assert + espower-typescript の王道構成に戻ってきたらメンテされて使いやすくなってた。

 

モジュールインストール

yarn add mocha power-assert espower-typescript --dev

型定義インストール

yarn add @types/power-assert @types/mocha --dev

実行

./node_modules/.bin/mocha --compilers ts:espower-typescript/guess test/spec/**/*-test.(ts|tsx)

とかでプロジェクトのtsconfig.jsonの設定を読んでいい感じにやってくれる。

package.jsonにシェル書いとけば npm test とか yarn test で動く

"scripts": {
    "test": "./node_modules/.bin/mocha --compilers ts:espower-typescript/guess test/spec/ --timeout 30000",
}

test.ts

tsconfigの設定によってはES7 async や デコレータも使えて調子いいっすね

import "mocha";
import assert = require("power-assert");

const sleep = (time : number)=> new Promise(r => setTimeout(r , time))

describe("section", ()=>{

    it("非同期テスト", async ()=>{
        await sleep(2000)
        assert.equal(3 ,3)
    });

    it("高階関数テスト" , ()=>{

        function decorate(clazz) {
            return class extends clazz {
                constructor() {
                    super();
                }
                prop : number = 100;
            }
        }

        @decorate
        class BaseClass {
            prop : number = 0;
        }

        const base = new BaseClass()
            assert.equal(base.prop , 100)
        });
    });
});

特に何も入れずにスクレイプのテストをパッと書く

スクレイプ / WebサイトE2Eテストの構成がほしい時。

TypeScript勢は上記の構成に好きにヘッドレスブラウザなりを入れて自由にしたらいいと思うが、それ以外の方。

結論から言うとこちらのボイラープレートを参考にするのが一番早い
nightmare-ava-example

 

ava はmochaみたいなテストフレームワークで、デフォルトでasyncをサポートしていたりとシンプル。
Nightmareはヘッドブラウザphantomjsのラッパー。(casperjsみたいなもん)…だったが、最近はElectron(chromium)が裏で立ち上がるようになってる。

こんな感じ

import test from 'ava';
import Nightmare from 'nightmare';

const nightmare = Nightmare({ show: true });

test.serial('Async/Await!', async (t) => {
    const result = await nightmare
       .goto('http://yahoo.com')
       .screenshot('output/png/hoge.jpg')
       .type('form[action*="/search"] [name=p]', 'github nightmare');

    const result2 = await nightmare.click('form[action*="/search"] [type=submit]')
       .wait('#main')
       .evaluate(function () {
           return document.querySelector('#main .searchCenterMiddle li a').href;
       });
    t.true(result2.includes('images.search.yahoo.com'));
});

実行

./node_modules/.bin/ava --verbose spec/

すぐに書き始められていいかと思う。

Read More

serverless frameworkでのLambdaファンクション縮小/デプロイ速度向上

serverless framework (v1.4)使用。

include/exclude

普通にデプロイすると、node_modules以下が全てパッケージングされ、各Functionのサイズが大きくなってしまったり、デプロイに時間がかかったりする。
結論としては、パッケージングの設定をきちんとすること。
 
公式ドキュメント参照のこと。
 
この設定はもちろん各Functionごとに独立させることもできるみたい。
 
開発時にwebpackを使っている方なら、(作り方にもよるが)そもそも初めから各エンドポイントに必要なものだけrequireしてbundleする構成にはなってるかと思うので、
一括でnode_modules以下をexcludeしても構わないかもしれない。

serverless.yml

service: hogehoge

package:
  exclude:
    - node_modules/**

...

serverless-webpack排除

2016年末段階ではserverless-webpackプラグインを常用していたが、

serverless deploy function -f <functionName>

に対応していない?ため、全functionの一括デプロイしか手段がなかった。
普通に自前のwebpackでjsを吐いた方が個別にアップロードできるため何かと融通がきくかもしれない。

このプラグインは、

serverless webpack serve

でローカルのテストが簡単にできる利点があったが、serverless-offlineに移行すること。

Read More

Serverless FrameworkでCognito UserPoolの認証をしばくときのメモ

  • 社内向けのサーバーレスなサービスが想定で、API GatewayでセキュアなAPIを作ることを考える。
  • そこでCognito UserPoolを使った場合のメモ

Severless Framework

  auth:
    handler: auth.default
    timeout : 5
    memorySize : 256

  secureCheck:
    handler: handler.secureCheck # 動かすlambda関数名
    timeout : 5
    memorySize : 128
    events:
      - http:
          method: get
          path: secureCheck
          cors : true
          authorizer: auth #authorizer: xxx:xxx:Lambda-Name も可能
          response:
            headers:
              Content-Type: "'application/json'"
            template: $input.path('$')

/secureCheck と云うAPIは,authと云うLambda Functionの認証を経てsecureCheck Functionに至るようになる。
そうでなければ403が返る。

この形式でsecureCheck APIにアクセスするとき、リクエストのAuthorizationヘッダーに
Cognito UserPoolでログイン認証したアクセストークンをつけること。
クライアント側の処理は今回は書かない。

auth.ts

const aws = require("aws-sdk");

var cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18',
    region: 'ap-northeast-1'
});

const auth = (event, context, callback)=> {

    const token = event.authorizationToken != null ? event.authorizationToken : event.headers.Authorization

    var params = {
        AccessToken: token
    };
    cognitoidentityserviceprovider.getUser(params, function (err, data) {
        if (err) {
            console.log(err)
            callback(null, generatePolicy('user', 'Deny', event.methodArn , token));
            //callback(null, generatePolicy('user', 'Allow', event.methodArn, data));
        } else {
            console.log(data)
            callback(null, generatePolicy('user', 'Allow', event.methodArn , token));
        }
        return;
    });
};

//"methodArn":"arn:aws:execute-api:<regionId>:<accountId>:<apiId>/<stage>/<method>/<resourcePath>"
const generatePolicy = function generatePolicy(principalId, effect, resource , token) {
  return {
    principalId:principalId,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [{
        Action: 'execute-api:Invoke',
        Effect: effect,
        Resource: "arn:aws:execute-api:ap-northeast-1:*:<API Gateway Id>/*” //CHECK!! =>  http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/permissions.html#api-gateway-calling-api-permissions
      }]
    }
  };
};

export default  auth;

generatePolicyメソッドで、ユーザーに対して必要なAWSの権限をつけている。
ここでは、該当のAPI GateWayの各エンドポイントのinvoke権がつくように組んだ。

他メモ

Cognito UserPoolのAccessTokenやIdTokenのexpireはぴったり1時間なので、
APIを叩くたびに Cognateアクセストークンのexpireチェック => 必要ならばCognitoのrefresh叩いてToken更新 => セキュアなAPI gateway叩く、というフローを経ることになる。
Dynamoにセッションとか入れとくよりは楽なのだろうけど割とラフに非同期チェーンが入ってめんどくさい。

毎回リクエストヘッダにアクセストークンつけるのも、Ajax以外でのエンドポイントのアクセスが制限されてしまうので、なんとかCookie使う方向で今後模索してみる。

Read More

AWS CodeBuildで分課金のコンテナ実行環境を手に入れる

伏見です。この記事はWanoグループアドベントカレンダー2016の21日目の記事になります。年末感。

書くこと

  • NightmareでのWebサイトE2Eテスト実行環境を、Dockerで構築・CodeBuildで実行してみた
  • 実行時に環境変数も渡せるし割とCodeBuildには使いどころありそう
  • CodeBuild Tokyoリージョン はよ

AWS CodeBuild発表、着想

先日のAWSのイベントre:invent 2016 にて『CodeBuild』 の発表が行われた。
モバイル/Webアプリケーションのビルド/テストのCIのパイプライン上に載せることが想定されているようだ。
現在(2016/12/20) Tokyoリージョンはなく、バージニア、オレゴン、アイルランドでのサービス提供になる。

気になったポイントは2つ。

  • 自分で用意したDockerの実行環境が使える。
  • (ビルド)タスクの実行時間に応じた「分単位での課金体系」である。

自作Dockerイメージが使える

AWS Lambdaのようにコードをサクッと動かしたい時に自作dockerコンテナが使えないかなあ、というのはたまに思っていた。
CodeBuildで実行環境自作のDockerイメージが使えることにより、インスタント環境として AWS Lambda より便利そうな点がある。

  • 使用言語の制限がなくなる。
  • あらかじめカスタマイズ済のイメージを構築しておける。

0.005$~ /分の課金

AWS CodeBuild 料金
CodeBuildはビルド実行時間(分単位 , 1分あたり 0.005$)での課金と転送量での課金になる。
インスタンスのプランをあげると0.02$/分まで上がる。

課金体系がこの通りなら、用途によってはいろいろ捗りそう。

リリース予定の AWS Batch も優秀そうだし、実行環境に信頼度がおけそうな感じがあるが、
あっちは(多分)普通にEC2借りてて1時間単位での課金だし、スポットインスタンス狙っていく作業がちょっと大げさな時もあるかと思う。

何ができそうか考える

  • 普通にAndroidアプリとかのビルド/ DeviceFarmにテスト投げる
  • 最悪止まっても問題なさそうなE2Eテストやスクレイプ作業
  • apiなどの監視タスク

いわゆるCIの用途以外でも考えようとしたけど…。
まあ他にも即時性なくてもいいけどDockerでサクッと動かしたいタスクで何かしら使いどころはあるんじゃないだろうか。

動かしてみるもの

ちょうどWebサイト周りのE2Eテストについて勉強中なので、その実行環境にCodeBuildを是非使ってみたい。
ブラウザE2Eテスト実行環境を整えたDockerイメージを用意し、AWSコンソール上でCodeBuildを実行/テストを走らせてみるとこまでやってみる。

E2Eテストを仕込んだDockerイメージの用意

Nightmare基盤

テストフレームワークNightmareをdockerのコンテナ上で動かす上で,レンダリングのためにxvfbなどの仮想フレームをインストールしたり、その他必要なモジュールをとってこなければならない。
この辺のインストール作業は冗長で、そもそもの興味範囲とは違うところであれがない、これがない、とハマりがち。
その辺をあらかじめ織り込んだDockerfileを公開してくれている人はガンガンいるので、それを参考にイメージを構築することにする。

いざとなったらDockerfileにはlinux上でのモジュールのインストール手順がそのまま記載されているので,別にdocker使わずとも学ぶことが多い。

nightmareのDockerfileを参考に、以下のようなDockerfileを構築する。

FROM node:7.2
MAINTAINER mr.hoge <xxxx.co.jp>

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd --system nightmare && useradd --system --create-home --gid nightmare nightmare
ENV HOME "/home/nightmare"

ENV DEBUG=nightmare
ENV ARGUMENTS=()

RUN apt-get update && apt-get install -y \
  vim  gcc make cmake g++ \
  software-properties-common vim wget curl unzip zip \
  xvfb \
  x11-xkb-utils \
  xfonts-100dpi \
  xfonts-75dpi \
  xfonts-scalable \
  xfonts-cyrillic \
  x11-apps \
  clang \
  libdbus-1-dev \
  libgtk2.0-dev \
  libnotify-dev \
  libgnome-keyring-dev \
  libgconf2-dev \
  libasound2-dev \
  libcap-dev \
  libcups2-dev \
  libxtst-dev \
  libxss1 \
  libnss3-dev \
  gcc-multilib \
  g++-multilib && \
    rm -rf /var/lib/apt/lists/* && \
        find /usr/share/doc -depth -type f ! -name copyright | xargs rm || true && \
        find /usr/share/doc -empty | xargs rmdir || true && \
        rm -rf /usr/share/man/* /usr/share/groff/* /usr/share/info/* && \
        rm -rf /usr/share/lintian/* /usr/share/linda/* /var/cache/man/*
RUN apt-get clean

WORKDIR ${HOME}
COPY ./package.json ./
RUN npm install

VOLUME ${HOME}

オリジナルと違うところは、イメージのベースをNodeJS7系にしているところや、とりあえずvimやwgetも入れているところくらい。

E2Eテストに使うモジュールの準備

package.json

{
  "name": "docker-my-nightmare",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/.bin/mocha .test.js --timeout 6000000",
    "e2e_with_debug" : "DEBUG=nightmare:*,electron:* xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000",
    "e2e_on_docker" : "xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mocha": "^3.2.0",
    "mocha-co": "^1.17.2",
    "nightmare": "^2.8.1",
    "power-assert": "^1.4.2"
  }
}

ついでにテスト発火用コマンドをpackage.jsonに記載しておく。

テストスクリプトの用意

ブラウザテストそのものや、Nightmare周りについては今回は触れない。
簡単なjavascriptのコードだけ載せる。

test.js

const mocha = require('mocha-co');
const Nightmare = require('nightmare');
const assert = require('power-assert');

const sleep = (ms)=>{
  return new Promise((resolve , err)=>{
    setTimeout(()=>{
      resolve();
    },ms);
  });
};

describe("1st Test", function() {  

  let nightmare;

  before(function*() {
    console.log("Start Testing... #####################");
    nightmare = Nightmare();
  });

  it('googleでhogeを検索', function*() {
    const title = yield nightmare
      .viewport(1024,768)
      .goto('https://www.google.co.jp/?gfe_rd=cr&ei=qTBaWIX5OYqL8QeryomIDg#q=hoge&btnK=Google+%E6%A4%9C%E7%B4%A2')
      .title();

    assert(title === 'Google');
  });
});

ローカルのdocker上でちゃんと動くようであれば、おもむろにコンテナをcommitして、オールインワンなイメージを作っておく。
1.3GBほどになってしまった。

AWS ECRへの dockerリポジトリの登録

DockerHubのAWSバージョンと言えるAmazon EC2 Container Registry にDockerイメージを置くことにする。
ECRは、dockerのプライベートリポジトリを置く上で、割と安いサービス。

CodeBuildをOregon(us-nor)で立てているため、リポジトリもOregonに置く。
Tokyoリージョンに置いたリポジトリがCodeBuild上でうまく指定できなかったためだ。
この辺は後でIAM設定見直すが、できればCodeBuildが早くTokyoリージョンに来てほしい。

先ほどのビルド済イメージをECRにpushしておく。

docker push xxxxxxx.us-west-2.amazonaws.com/yyyyy:latest

ビルドソース準備

CodeBuildはビルドソースとしてS3/GitHub/CodeCommit(AWSのGitホスティング)が使える。
今回はアプリケーションのビルドをするわけじゃないのでソース設定は正直なんでもいいのだが、勉強のためにS3にする。
CodeBuildの設定ファイルであるbuildspec.ymlをS3に置き、そこに書かれているコマンドをdockerで実行する、という構成にしてみる。

buildspec.yml

version: 0.1
phases:
  install:
    commands:
  pre_build:
    commands:
      - echo $MY_VAR
  build:
    commands:
      - cd /home/nightmare && npm run e2e_on_docker
  post_build:
    commands:
      - echo Done!

中でディレクトリ移動しているのは、Dockerファイルに記載されているWORK_DIRがCodeBuildでの実行時に適用されずにnpmの実行エラーを起こしたため。

S3へのビルド対象のアップロード

先ほどのbuildspec.ymlをzipとしてS3にアップロードする。

zip buildspec.yml > buildspec.zip
aws s3 cp buildspec.zip s3://~~~~oregon-bucket/codebuild/

ここで注意点があって、S3のbucketのオリジンもCodeBuildと同じリージョンになければならない。
(IAMの設定割と見直したんだけど、CodeBuildでのビルド実行時にエラーが出てうまくいかなかった。)

CodeBuildのプロジェクトを作る

今回はConsole上で設定する。
regeonはオレゴン(us-west-2)を選択。

実行環境指定

Source: What to build

  • Source provider
    • Oregonに立てたbucketを指定

Environment: How to build

  • Environment image
    • Specify a Docker image
  • Custom image type
    • AmazonECR
  • Amazon ECR repository
    • ECRのリポジトリを指定
  • Amazon ECR image
    • イメージのタグ指定。履歴を残さなければlatestだけでいいはず
  • Build specification
    • ここに直接コマンドも書けるが、buildspec.ymlの設定を使うことにする。

Artifacts: Where to put the artifacts from this build project

  • Artifacts type
    • 本来ならここでビルド成果の置き場所を指定するが、No artifactsにする。
    • 今回のようなテスト用途なら、E2Eテストの結果やサイトのキャプチャをまとめてzipにするといいかもしれない。

Service role

  • ECR,S3に接続するための実行権限をここで付与する。

role周りについての設定は
Amazon ECR Sample for AWS CodeBuild
を参照。

continue -> OK を押してプロジェクト完成!

ビルド設定

“Start Build”ボタンでビルド設定画面に入る。
S3をビルドソースにしている場合、Source versionとしてs3 objectのバージョニング機能が使えるが、これで運用するのもあんまりうまくない気がする。

Environment variablesでコンテナ実行時に環境変数が渡せるため、これを積極的に使っていくことになりそう。(もちろんこの機能はaws-cliのcodebuildからも使える。)
name,valueは実際には

export MY_VAR=hogehoge

されているようなので、普通に$MY_VARとかで受け取れる。
コンテナの条件は変えずに、「動的にスクリプトやコマンドをダウンロードしてきてテストを実行する」基盤を整えるとなかなか便利になりそう。

実行

待つ。待つ。

上が実行ログの例。
テスト実行中のログはコンソール上で見れる他,CloudWatchのログとしても残る。

試算

1.3GBほどのDockerイメージで、実行開始までに1分半ほどかかった。
E2Eのjavascriptコード自体は短いので20秒ほど。
2分 * 0.005$ で 1回あたりの実行料金1円くらいになる。
これ自体はまあ大したことない。
一方のECRの転送料金がいまいち読めないが、こっちは1.3GB転送しているのでこれは金額的に大きそう。
EC2でECRを使うとき、同じリージョンの場合に EC2 <=> ECRの転送自体は無料なので、 CodeBuildでもこのプランが適用されているといいなーというか、してないときついと思う。

まとめ

CodeBuildはいろんな用途に使えそうですが、東京リージョン早くほしいです。
ついでにbitbucket対応もしてほしい。
(最後雑)

Read More

Android Deeplinkを特定のパスにのみ対応させる

Wanoグループ Advent Calendar 2016の11日目の記事になります。11日目の44時くらいです。決して12日目ではない。

AndroidのDeepLink設定のメモ。

https://xxx.com/release/***

https://xxx.com/item/***

のような2つのパスのみにアプリが反応するように以下のように組んでいた。

 <activity android:name=".Container.Routing.RoutingActivity">
   <intent-filter>
   <action android:name="android.intent.action.VIEW" />

   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />

   <data android:scheme="http" />
   <data android:scheme="https" />
   <data android:host="xxxx.com" />
   <data android:pathPrefix="/artist/" />
   <data android:pathPrefix="/item/" />
   </intent-filter>
 </activity>

  
しかしこれでは該当のパス以外のURLにも反応してしまっていた。
  
そこでリンクを以下のように修正。

...
 <intent-filter>
   <action android:name="android.intent.action.VIEW" />
   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />
   <data
     android:scheme="https"
     android:host="xxxx.com"
     android:pathPrefix="/artist/"
   />
 </intent-filter>

 <intent-filter>
   <action android:name="android.intent.action.VIEW" />
   <category android:name="android.intent.category.DEFAULT" />
   <category android:name="android.intent.category.BROWSABLE" />
   <data
     android:scheme="https"
     android:host="xxxx.com"
     android:pathPrefix="/item/"
   />
 </intent-filter>
 </activity>

当面はこれで該当のパスにのみDeepLinkが適用されるようになった。

Read More

AndroidのWebViewの思い出

Wano の 伏見です。
この記事はWanoグループ Advent Calendar 2016の9日目の記事になります。

この記事に関しては死亡事故のたびに随時追記するかも。

webViewのCookieの同期で死亡

Android WebViewのCookieの扱いで激ハマりした話
この記事と違って4.4系では死ななかった。4.2ではものも言わずアプリは死にました。

そもそも4.4までのCookieマネージャがdeprecatedなので分岐大変

最新のOSのWebViewでアブないサイトに対応する

http/https混在環境で mixed contentsの設定をガバガバにします。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
    webView.getSettings().setMixedContentMode( WebSettings.MIXED_CONTENT_ALWAYS_ALLOW );
}

アブないOSのWebView でセキュアなサーバーに接続する

デフォルトで SSlv3 で通信するようになっている OS の場合、
OkHttp や HttpUrlConnection などの通信系ライブラリは、通信前にGooglePlayServicesから動的にパッチを当てれば、TLS1 ~ 1.2系が使えるようになる場合があります。

同期的にパッチを当てる場合

try {
    ProviderInstaller.installIfNeeded(getActivity());
} catch (GooglePlayServicesRepairableException e) {
    e.printStackTrace();
} catch (GooglePlayServicesNotAvailableException e) {
    e.printStackTrace();
}

それでも4.3以前の デフォルトWebView では SSLv3 を 使ってしまうことがある。対応できません

通信の上書きにも限界があるでしょう。

学び

  • アプリサイズがかなり大きくなることと引き換えに、代替WebViewのCrossWalkを使おう
  • でも基本的にしょうもないものはどんどん捨てよう
  • 要件定義で殺そう
  • iOS8や4.3系以下はセキュリティ的にもはや危ないし、シェア的にも対応コストの方が高いという認識を周知していこう

それはそうと

ECサイトとかのトラッキング & 広告系 のタグ , ページのロードをブロックしながら document.write()しててChrome先生のコンソールで怒られまくってるんですが、そろそろ消されますよ?

Read More

AWS Lambdaで Slackのslashコマンドを作る(1)

伏見です。
表題の通りやっていきたいと思います。

(1)セットアップ

まずはおもむろにサーバーレスフレームワークのセットアップをします。
2016年9月現在、Serverless Frameworkは1.0@Beta3くらいまで出てますが、
サクッとAWSのlambdaとAPI Gatewayを使う限り、regeonやprofileなどを対話形式で選んでくれるバージョン0.5.6の方が
今の所使いやすいのでこっちを使います。
0.5系のdocumentはこちら 

入れます。

npm install -g serverless

プロジェクト立てます。

serverless project create

対話形式でいろいろ聞いてくるので、聞かれるままに言語やAWSのプロファイルの設定をします。
あ、aws-cliは入っているものとします。

次に、lambdaのファンクションやAPI gatewayのエンドポイントをサクッと立てます。
ここでは、command01と云う名前にしてみます。

serverless function create functions/command01

また対話形式で、言語や作るものを聞かれます。
ここではnodejs4.3を選んでおきます。

Serverless: Please, select a runtime for this new Function
  > nodejs4.3
    python2.7
    nodejs (v0.10, soon to be deprecated)

設定を終えると、functions/command01以下にlambdaファンクションの本体となる handler.jsや、API gateway、lambdaの設定を司るs-function.jsonなどができています。
ファンクションを作ったディレクトリに移動して、npmの設定を行います。
これは、lambdaにdeployする時に、node_modulesが独立しているためです。

cd functions/command01
npm init

ちなみにwebpackを使う方はファンクションごとのこの設定、いりません。babelやtypescriptを使ったり、jsonやimageをバンドルして扱いやすくしたい…あるいは複数のfunctionごとにコードを共有したい…という方もこっちですね。
0.5系向けならwebpack plugin  がとにかく簡単でオススメです。
サクッとやるだけならいらないです。

(2)まずは繋いでみる

とりあえずデプロイします。

lambda。

serverless function deploy

api-gateway。

serverless endpoint deploy

endpoint deploy後に表示されたURLにブラウザで確認します。

ほげ

表示されました。
これでとりあえずのひな形ができました。

実際の機能作成は次の記事で。

Read More