木崎湖ツーリング行ってきた

今日は天気が良かったので木崎湖へツーリングしてきた。
山の上の方にはまだ雪が積もっているけど、下の方は初夏のような気温で、ツーリングするには最適だった。

f:id:hkou:20180421125910j:plain

ゆーぷる木崎湖で昼ごはんを食べる。休日のお昼時だというのに自分と他一組ぐらいしか客がいなくてガラガラだった。スキーの季節終わったから客が少なくなったのだろうか。木崎湖行くときはいつも利用するので潰れてしまうと困る。 f:id:hkou:20180421132319j:plain

今日は雲ひとつ無い快晴なので、パラグライダー場の方へ行ってみることにした。
通行止めの看板が立ててあったけど脇にずらされていた(これはゆるきゃんで見た通行できる通行止めというやつでは) f:id:hkou:20180421144613j:plain

こんな感じの道をずーっと登っていく。路面には砂利や木の枝、石や岩が転がってて何度かタイヤが滑りかけて怖かった。しかもこの道ガードレールないし。
途中ロードバイクで登っている人を何人か見かけた。 f:id:hkou:20180421143335j:plain

登っているとたまにこんな感じで切り開けた場所に出くわす。景色を見ながら小休止したりしていた。 f:id:hkou:20180421143326j:plain

40分ぐらいかかって、やっと頂上へ到着した。予想通り見渡す限りの絶景だった。 f:id:hkou:20180421141529j:plain

景色を堪能したので下に降りてきて湖畔を歩き回っていた。気温が高くなっているからかいつもよりも人が多いような気がする。 f:id:hkou:20180421150725j:plain

歩き周って温泉入りたくなってきたので、近くの「白馬八方温泉 みみずくの湯」へ行ってきた。ここの温泉は露天風呂から白馬三山を一望できて良い。 f:id:hkou:20180421153704j:plain

まとめ

  • 総走行距離465km
  • 木崎湖はいつ来ても良い
  • 久しぶりに長距離ツーリングしたので尻が痛くなった

f:id:hkou:20180421235041p:plain

API GatewayでAPIキー認証付きのHTTP APIを作る

やりたいこと

  • 以下の記事で作成したAPIは誰でも呼び出せる状態なので、ある特定のクライアントからしか呼び出せないように認証をかける

hkou.hatenablog.com

serverless.ymlを編集する

providerの項目にAPIキーと使用量プランの記述を追加する。

  • APIキー
    • とりあえず名前だけ指定
  • 使用量プラン
    • レート
    • バースト
    • クォーター
      • 1 日あたり 5,000 リクエスト数
provider:
  name: aws
  runtime: nodejs6.10
  region: ap-northeast-1
  apiKeys:
    - myFirstKey
  usagePlan:
    quota:
      limit: 5000
      offset: 0
      period: DAY
    throttle:
      burstLimit: 200
      rateLimit: 100

上記で設定したAPIキーを利用するAPIに対してprivate: trueを指定する。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: api/hello
          method: get
          private: true
  add:
    handler: handler.add
    events:
      - http:
          path: api/add
          method: post
          private: true

デプロイする

deployコマンドでデプロイする。
作成されたAPIキーの値が出力されるので控えておく

sls deploy -v

(省略)

service: test-service
stage: dev
region: ap-northeast-1
stack: test-service-dev
api keys:
  test-api-key: (APIキーの値)
endpoints:
  GET - https://(URL).ap-northeast-1.amazonaws.com/dev/api/hello
  POST - https://(URL).ap-northeast-1.amazonaws.com/dev/api/add

動作確認

APIキーなしで呼び出す

403 ForbiddenがレスポンスされAPIが呼び出せなくなった。

f:id:hkou:20180419215925p:plain

APIキーありで呼び出す

APIキーをヘッダーに付加するとちゃんと呼び出せる。

f:id:hkou:20180419220121p:plain

ServerlessFrameworkでHTTP API (API Gateway + Lambda (Node.js) )を作成&デプロイする

やりたいこと

  • serverless frameworkを利用してHTTP APIを作成、デプロイする。
  • HTTP APIAWSのApiGateway + Lambdaの構成で作成する。

環境

serverless frameworkをインストールする

公式ページを参考にする。npmのグローバルインストールでいれる。

serverless.com

npm install -g serverless

インストールされたか確認

sls -version
1.26.1

AWSのアクセスキー、シークレットキーを作成する

AWSの管理コンソールからIAMを選択、サイドメニューのユーザーからユーザーの追加を押下 f:id:hkou:20180417224624p:plain

「AdministratorAccess」ポリシーを付加した任意のユーザーを作成する。
ユーザーを作成したら認証情報が記載されたCSVファイルがダウンロードできるので開いておく。

aws cliにアクセスキー、シークレットキーを登録する

docs.aws.amazon.com

$ aws configure
AWS Access Key ID [None]: アクセスキーを入力する
AWS Secret Access Key [None]: シークレットキーを入力する
Default region name [None]: ap-northeast-1 とりあえず東京リージョン
Default output format [None]: json

プロジェクトを作成する

Serverless Frameworkのcreateコマンドでサービスのテンプレートを作成する

sls create --template aws-nodejs --path test-service

カレントディレクトリにtest-serviceディレクトリが作成される。

handler.jsを編集する

先程のcreateコマンドでhandler.jsの雛形が作成されたので、これを開き編集していく。
作成するAPIは以下の2つ(内容に特に意味はない)

  • GETするとHelloWorldの文字列を返す簡単なAPI
  • 数値配列をPOSTすると合計値を返すAPI
'use strict';
module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hello World'
    }),
  };
  callback(null, response);
};
module.exports.add = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      result: JSON.parse(event.body).reduce((acc, x) => acc + x)
    }),
  };
  callback(null, response);
};

serverless.ymlを編集する

先程のcreateコマンドでserverless.ymlの雛形が作成されたので、これを開き編集していく。

東京リージョンにデプロイしてほしいのでproviderの下にregion指定の記述をする。

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

APIのエンドポイントを定義する。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: api/hello
          method: get
  add:
    handler: handler.add
    events:
      - http:
          path: api/add
          method: post

デプロイする

以下のコマンドを実行する。
AWSにデプロイされ、エンドポイント情報が出力される。

sls deploy -v

(省略)

Serverless: Stack update finished...
Service Information
service: test-service
stage: dev
region: ap-northeast-1
stack: test-service-dev
api keys:
  None
endpoints:
  GET - https://(API Gatewayのパス).ap-northeast-1.amazonaws.com/dev/api/hello
  POST - https://(API Gatewayのパス).ap-northeast-1.amazonaws.com/dev/api/add
functions:
  hello: test-service-dev-hello
  add: test-service-dev-add

APIを呼び出してみる

GET /api/hello

f:id:hkou:20180417230326p:plain

POST /api/add

f:id:hkou:20180417230158p:plain

Groovy, Spockを使ってJavaのテストを作成する

Gradleをインストールする

公式のインストールガイドを読むと、Chocolateyでインストールできるようなのでこちらでインストールした。 gradle.org

choco install gradle

プロジェクト作成

プロジェクト用のディレクトリを作成してその中で以下のコマンドを実行し、プロジェクトの雛形を作成する。

gradle init --type java-application
  • build.gradleファイルにSpockを追加する。
  • plugins, dependenciesにGroovyを追加する。
plugins {
    id 'java'
    id 'groovy'
}

dependencies {
    compile "org.codehaus.groovy:groovy-all:2.4.15"
    testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
}

テスト対象のクラスを追加

Integerのコレクションを受け取り、合計値を返却する関数を作成した。中身は未実装なのでとりあえず0を返却する。

package app;

import java.util.Collection;

public class Calculator {
    public int sum(Collection<Integer> list) {
        return 0;
    }
}

Specificationを追加

import app.Calculator
import spock.lang.*

class CalculatorSpec extends Specification {
  def "リストの各要素を合計した値を返すこと"() {
    given:
      def calculator = new Calculator()
    when:
      def actual = calculator.sum(list)
    then:
      actual == expected
    where:
      list                   | expected
      Arrays.asList(1)       | 1
      Arrays.asList(1, 2, 3) | 6
  }
}

テストを実行

以下のコマンドでテストを実行する。

gradlew test

未実装なので当然テストが失敗する。

CalculatorSpec > リストの各要素を合計した値を返すこと FAILED
    org.spockframework.runtime.SpockComparisonFailure at CalculatorSpec.groovy:11
Condition not satisfied:

ret == expected
|   |  |
0   |  1
    false

Condition not satisfied:

ret == expected
|   |  |
0   |  1
    false

    at CalculatorSpec.リストの各要素を合計した値を返すこと(CalculatorSpec.groovy:11)

テストが通るように実装

public class Calculator {
    public int sum(Collection<Integer> list) {
        return list.stream()
                .reduce((x, y) -> x + y)
                .orElse(0);
    }
}

テストが通るようになった。

gradlew test
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date

例外のテスト

素数が0のリストは受け付けないようにしたい。まずは要素数0の場合のテストを追加する。

  def "リストが空の場合はエラー"() {
    given:
      def calculator = new Calculator()
    when:
      calculator.sum(Arrays.asList())
    then:
      thrown(IllegalArgumentException)
  }

テストが失敗することを確認

Expected exception of type 'java.lang.IllegalArgumentException', but no exception was thrown
Expected exception of type 'java.lang.IllegalArgumentException', but no exception was thrown
    at org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:85)
    at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:72)
    at CalculatorSpec.リストが空の場合はエラー(CalculatorSpec.groovy:24)

    org.spockframework.runtime.WrongExceptionThrownError at CalculatorSpec.groovy:24
2 tests completed, 1 failed
:test FAILED

リストの要素が空の場合に例外を投げるように実装を修正

public int sum(Collection<Integer> list) {
    return list.stream()
            .reduce((x, y) -> x + y)
            .orElseThrow(() -> new IllegalArgumentException("list is empty"));
}

再度テストを実行して成功することを確認

BUILD SUCCESSFUL in 3s
3 actionable tasks: 3 executed

テストレポート

テストを実行する度にHTMLのテストレポートが生成される。
生成場所は{プロジェクトディレクトリ}/build/reports/tests/test/index.html

f:id:hkou:20180415111557p:plain

docker stopしたときにjavaのShutdownHookが呼び出されない

概要

以下の記事で作ったプログラムをdockerコンテナ上で実行して、docker stopしたときにShutdownHookが呼ばれない現象が発生した。

hkou.hatenablog.com

以下のDockerfileでイメージを作成, プログラムはシェルスクリプト経由で実行している。

FROM java:openjdk-8-alpine
ADD ./SignalTest.class /work/
ADD ./entry.sh /work/
WORKDIR /work

ENTRYPOINT ["./entry.sh"]
#!/bin/sh

java SignalTest

ホストマシンで確認

docker stopと同等の挙動にするには.shにSIGTERMを投げればよいということが分かった。
.shのPIDが分かるようにスクリプト内に「echo $$」を追加した。

シェルスクリプトを実行し、表示されたPIDに対してkill -15 5227を実行したところ、シェルスクリプトの実行プロセスは終了したが子プロセスのjavaプログラムは起動したままの状態になっている。

$ ./entry.sh 
5227
exec...
exec...
exec...
Terminated
$ exec...
exec...
exec...
exec...

上記から、docker stopするとシェルスクリプトのプロセスにSIGTERMが投げられ終了したが、Javaプロセスは実行したまま経過し、一定時間後SIGKILLが投げられ強制終了したものと推測される。
SIGKILLではShutdownHookが呼び出されないのは以下の記事で確認している。

hkou.hatenablog.com

対策

#!/bin/sh

echo $$
exec java SignalTest

ホストマシンで確認(対策後)

ホストマシンでシェルスクリプト実行し、kill -15 5276したところShutdownHookが呼び出された。

$ ./entry.sh 
5276
exec...
exec...
call shutdown hook

dockerコンテナで確認

docker stopでShutdownHookが呼び出されたことが確認できた。

# イメージ作成
sudo docker build -t signal-test .

# コンテナ実行
$ sudo docker run -d -i -t signal-test
0ee5e6dc864076f07cd7e0508075474200d15edada5a81696dfa6ad0f1677de1

# ログ確認
sudo docker logs 0ee5e6dc864076f07cd7
1
exec...
exec...
exec...
exec...

# コンテナ停止
sudo docker stop 0ee5e6dc864076f07cd7

# ログ確認
$ sudo docker logs 0ee5e6dc864076f07cd7
1
exec...
exec...
exec...
exec...
exec...
exec...
call shutdown hook

ShutdownHookがどのシグナルで呼び出されるか確認した

やりたいこと

JavaプログラムでaddShutdownHookに指定したThreadがどのシグナルで終了したときに呼び出されるのかを確認する。

Runtime (Java Platform SE 8)

確認方法

  1. 以下のJavaプログラムを実行する
  2. 起動したプログラムにkillコマンドでシグナルを投げて「call shutdown hook」が標準出力に出るか確認する。
public class SignalTest {
    public static void main(String [] args) {
        Runtime.getRuntime().addShutdownHook(new Thread(
            () -> System.out.println("call shutdown hook")
        ));
        while(true) {
            try {
                Thread.sleep(5000);
                System.out.println("exec...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

確認対象のシグナルは以下の4つ

  • SIGINT(2)
  • SIGQUIT(3)
  • SIGTERM(15)
  • SIGKILL(9)

確認環境

  • OS
  • Kernel
    • 4.4.0-119-generic
  • javac
    • javac 1.8.0_162
  • java
    • openjdk version "1.8.0_162"
    • OpenJDK Runtime Environment (build 1.8.0_162-8u162-b12-0ubuntu0.16.04.2-b12)
    • OpenJDK 64-Bit Server VM (build 25.162-b12, mixed mode)

確認結果

SIGINT(2)

呼び出された。

$ java SignalTest 
exec...
call shutdown hook
$ kill -2 pid

SIGQUIT(3)

呼び出されない。
Javadumpが出力され、処理が継続している。

$ java SignalTest 
exec...
exec...
exec...
exec...
exec...
2018-04-08 10:35:05
Full thread dump OpenJDK 64-Bit Server VM (25.162-b12 mixed mode):

"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007f2b580c8000 nid=0x81e runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007f2b580c3800 nid=0x81d waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007f2b580bf000 nid=0x81c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007f2b580bd000 nid=0x81b waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007f2b580ba000 nid=0x81a waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007f2b580b8000 nid=0x819 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007f2b58090800 nid=0x818 in Object.wait() [0x00007f2b1f0ce000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000071a208ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x000000071a208ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212)

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007f2b5808c000 nid=0x817 in Object.wait() [0x00007f2b1f1cf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000071a206b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000071a206b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=0 tid=0x00007f2b5800a800 nid=0x80b waiting on condition [0x00007f2b5fe1e000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at SignalTest.main(SignalTest.java:10)

"VM Thread" os_prio=0 tid=0x00007f2b58084000 nid=0x816 runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007f2b5801f800 nid=0x80c runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007f2b58021800 nid=0x80d runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007f2b58023000 nid=0x80e runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007f2b58025000 nid=0x80f runnable 

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007f2b58026800 nid=0x810 runnable 

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007f2b58028800 nid=0x811 runnable 

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007f2b5802a000 nid=0x812 runnable 

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007f2b5802c000 nid=0x813 runnable 

"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x00007f2b5802d800 nid=0x814 runnable 

"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x00007f2b5802f000 nid=0x815 runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007f2b580ca800 nid=0x81f waiting on condition 

JNI global references: 309

Heap
 PSYoungGen      total 148992K, used 7680K [0x000000071a200000, 0x0000000724800000, 0x00000007c0000000)
  eden space 128000K, 6% used [0x000000071a200000,0x000000071a980098,0x0000000721f00000)
  from space 20992K, 0% used [0x0000000723380000,0x0000000723380000,0x0000000724800000)
  to   space 20992K, 0% used [0x0000000721f00000,0x0000000721f00000,0x0000000723380000)
 ParOldGen       total 339968K, used 0K [0x00000005ce600000, 0x00000005e3200000, 0x000000071a200000)
  object space 339968K, 0% used [0x00000005ce600000,0x00000005ce600000,0x00000005e3200000)
 Metaspace       used 3490K, capacity 4632K, committed 4864K, reserved 1056768K
  class space    used 380K, capacity 459K, committed 512K, reserved 1048576K

exec...
exec...
$ kill -3 pid

SIGTERM(15)

呼び出された。

$ java SignalTest 
exec...
exec...
exec...
call shutdown hook
$ kill -15 pid

SIGKILL(9)

呼び出されない。
最後の「強制終了」はjvmの出力?と思われる。javadocの記述通りの挙動になった。

$ java SignalTest 
exec...
exec...
exec...
強制終了
$ kill -9 pid

iPhone8Plusを購入した

iPhone8Plusを購入した。前はAndroid端末を利用していたがもう2年ぐらい経っておりバッテリの劣化も激しいので買い替えたかった。iPhone8Plusは以下の理由で購入を決定した。

  • sim free端末が欲しかった
  • 電子マネーを利用したかった
  • ARアプリで遊んでみたかった
  • 新しく出てくるアプリでiOS版のみ(Androidは後でリリース)みたいなのが稀によくある
  • バッテリーの持ちが良い

iPhoneXは高いし、次期XがTrueDepth後面に配置されるかもみたいな情報を得て今買いどきではないなと判断した、iPhone8無印は持ちやすそうだけど、バッテリの持ちに不安を覚えたので最終的にPlusに落ち着いた。

外箱

f:id:hkou:20180304164448j:plain

開封①

f:id:hkou:20180304164652j:plain

開封②

f:id:hkou:20180304164733j:plain

端末

f:id:hkou:20180311083318j:plain

Suica

Suica物理カードを持っていなかったので、Suicaアプリから新規Suicaカード(記名式)を作成、Walletに登録した。 f:id:hkou:20180311083426j:plain

コンビニで利用してみたが、iPhoneがスリープ状態でもかざすだけですぐに決済できた(Suicaをエクスプレス・カードに設定している場合)
以前はmanacaをよく利用していたが、残高の確認がいちいち面倒だったのと、チャージが駅でしかできなかったので、それらが端末上ですぐにできるのは良いと感じた。 f:id:hkou:20180311083557j:plain