寝て起きて寝る

過ぎたるは及ばざるが如し

OpenTracingチュートリアルをやってみた(その1)

OpenTracingチュートリアルをやったメモ

OpenTracingとは

OpenTracingは、API仕様とそれを実装したフレームワークとライブラリ、およびプロジェクトのドキュメントで構成されています。 OpenTracingを使用すると、開発者は特定の製品やベンダーにロックされていないAPIを使用してアプリケーションコードにトレーシング機能を追加できます。

What is OpenTracing?

OpenTracingがサポートしている言語

OpenTracing Language Support

OpenTracingチュートリアル

このチュートリアルではTracerとしてJaegerを使用しています。

github.com

Jaegerとは

DapperとOpenZipkinにインスパイヤーされUber TechnologiesによってリリースされたオープンソースのOpenTracing互換の分散トレースシステムです。

www.jaegertracing.io

アーキテクチャはこのような感じです。

f:id:yasu7ri:20181125234256p:plain

Architecture — Jaeger documentation

Client以外の起動に関してはAll-in-oneのDockerイメージが公開されているのでそれを使用すれば起動できます。 Getting started — Jaeger documentation

今回は必要最低限のポートのみを指定して起動します。

docker run --rm -p 6831:6831/udp -p 6832:6832/udp -p 16686:16686 jaegertracing/all-in-one:1.8 --log-level=debug

簡単なトレースを作成してみる

まずはLesson 1 - Hello Worldから。

  • TracerはJaegerを初期化しJaegerTracerを取得します。
  • buildSpan()を使用してオペレーション名を設定しSpanBuilderを生成します。(今回は"say-hello"と設定)
  • spanstart()を使用して生成しfinish()を使用し終了する必要があります。
  • spanの開始と終了のタイムスタンプは、Tracerの実装(Jaeger)によって自動的に取得されます。
import io.jaegertracing.Configuration;
import io.jaegertracing.Configuration.ReporterConfiguration;
import io.jaegertracing.Configuration.SamplerConfiguration;
import io.jaegertracing.internal.JaegerTracer;
import io.opentracing.Span;
import io.opentracing.Tracer;

public class Hello {
    private final Tracer tracer;

    public Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    public static void main(String[] args) {
        if (args.length != 1) throw new IllegalArgumentException();
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(args[0]);
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();
        String helloStr = String.format("Hello, %s", helloTo);
        System.out.println(helloStr);
        span.finish();
    }

    private static JaegerTracer initTracer(String service) {
        SamplerConfiguration samplerConfiguration = SamplerConfiguration.fromEnv().withType("const").withParam(1);
        ReporterConfiguration reporterConfiguration = ReporterConfiguration.fromEnv().withLogSpans(true);
        Configuration configuration = new Configuration(service).withSampler(samplerConfiguration).withReporter(reporterConfiguration);
        return configuration.getTracer();
    }
}

上記で分かるように、Tracer、Spanに関してはOpenTracingによって仕様が切られている為OpenTracingのinterfaceを使用し実装に関してはJaegerを使用していることが分かります。

f:id:yasu7ri:20181125204156p:plain

トレースにTagとLogを付ける
  • Tag
    • Spanに関するkeyとvalueで設定するメタデータ
    • Spanの全期間に適用される属性を記述するためのも。たとえば、SpanがHTTPリクエストを表す場合は要求したURLはTagとして記録する
  • Log

OpenTracing仕様には、推奨されるTagとLogフィールドの セマンティック規約と呼ばれるガイドラインがあります。

  • Hello, Hoge!の場合に引数の"Hoge"はSpan全体に適用される為、Tagとして記録します。
  • このプログラムでは引数をフォーマットしてからprintlnしています。この両方の操作には一定の時間がかかるので、その完了をLogとして記録します。
public class Hello {
    private final Tracer tracer;

    public Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    public static void main(String[] args) {
        if (args.length != 1) throw new IllegalArgumentException();
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(args[0]);
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();
        // Tagの記録
        span.setTag("hello-to", helloTo);
        String helloStr = String.format("Hello, %s", helloTo);
        // Logの記録
        span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
        System.out.println(helloStr);
        // Logの記録
        span.log(ImmutableMap.of("event", "println"));
        span.finish();
    }

    private static JaegerTracer initTracer(String service) {
        SamplerConfiguration samplerConfiguration = SamplerConfiguration.fromEnv().withType("const").withParam(1);
        ReporterConfiguration reporterConfiguration = ReporterConfiguration.fromEnv().withLogSpans(true);
        Configuration configuration = new Configuration(service).withSampler(samplerConfiguration).withReporter(reporterConfiguration);
        return configuration.getTracer();
    }
}

f:id:yasu7ri:20181125210334p:plain

個別機能をトレースする

次はLesson 2 - Context and Tracing Functionsを。

  • 文字列のフォーマットと出力を個別のメソッドに分けます。
  • buildSpan()の追加オプションasChildOf()を使用し、個別に分けたメソッドのSpanをmain()メソッドのSpanの子Spanにします。
public class Hello {
    private final Tracer tracer;

    public Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    public static void main(String[] args) {
        if (args.length != 1) throw new IllegalArgumentException();
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(args[0]);
    }

    private void sayHello(String helloTo) {
        Span span = tracer.buildSpan("say-hello").start();
        span.setTag("hello-to", helloTo);
        String helloStr = formatString(span, helloTo);
        printHello(span, helloStr);
        span.finish();
    }

    private String formatString(Span rootSpan, String helloTo) {
        Span span = tracer.buildSpan("formatString").asChildOf(rootSpan).start();
        try {
            String helloStr = String.format("Hello, %s", helloTo);
            span.log(ImmutableMap.of("event", "string-format", "value", helloStr));
            return helloStr;
        } finally {
            span.finish();
        }
    }

    private void printHello(Span rootSpan, String helloStr) {
        Span span = tracer.buildSpan("printHello").asChildOf(rootSpan).start();
        try {
            System.out.println(helloStr);
            span.log(ImmutableMap.of("event", "println"));
        } finally {
            span.finish();
        }
    }

    private static JaegerTracer initTracer(String service) {
        Configuration.SamplerConfiguration samplerConfiguration = Configuration.SamplerConfiguration.fromEnv().withType("const").withParam(1);
        Configuration.ReporterConfiguration reporterConfiguration = Configuration.ReporterConfiguration.fromEnv().withLogSpans(true);
        Configuration configuration = new Configuration(service).withSampler(samplerConfiguration).withReporter(reporterConfiguration);
        return configuration.getTracer();
    }
}

f:id:yasu7ri:20181125220511p:plain

"active span"を使用

上記のコードで幾つか面倒なところが有りました。

  • 各関数の最初の引数としてSpanオブジェクトを渡さなければなりませんでした
  • スパンを完成させるためにやや冗長なtry / finallyコードを書く必要もありました

OpenTracing API for Javaではスレッドローカルと"active span"という概念を使用することで、Spanをコードに渡すことなくTracer経由でアクセスすることができます。

  • start()ではなくbuildSpan()startActive()メソッドを使用して、スレッドローカルに格納してSpanをアクティブにします。
  • startActive()SpanではなくScopeオブジェクトを返します。Scopeは現在アクティブなSpanのコンテナです。
  • scope.span()を介してアクティブなSpanにアクセスします。Scopeを閉じると以前のScopeが現在のScopeになり、現在アクティブなスレッドのSpanを再びアクティブにします。
  • Scopeは自動クローズ可能で、try-with-resource構文を使用できます。
  • startActive(true)はスコープを閉じるとスパンを終了します。
  • startActive()はアクティブSpanへのChildOf参照を自動的に作成するため、buildSpanasChildOf()を使用する必要はありません。
public class Hello {
    private final Tracer tracer;

    public Hello(Tracer tracer) {
        this.tracer = tracer;
    }

    public static void main(String[] args) {
        if (args.length != 1) throw new IllegalArgumentException();
        Tracer tracer = initTracer("hello-world");
        new Hello(tracer).sayHello(args[0]);
    }

    private void sayHello(String helloTo) {
        try (Scope scope = tracer.buildSpan("say-hello").startActive(true)) {
            scope.span().setTag("hello-to", helloTo);
            String helloStr = formatString(helloTo);
            printHello(helloStr);
        }
    }

    private String formatString(String helloTo) {
        try (Scope scope = tracer.buildSpan("formatString").startActive(true)) {
            String helloStr = String.format("Hello, %s", helloTo);
            scope.span().log(ImmutableMap.of("event", "string-format", "value", helloStr));
            return helloStr;
        }
    }

    private void printHello(String helloStr) {
        try (Scope scope = tracer.buildSpan("printHello").startActive(true)) {
            System.out.println(helloStr);
            scope.span().log(ImmutableMap.of("event", "println"));
        }
    }

    private static JaegerTracer initTracer(String service) {
        Configuration.SamplerConfiguration samplerConfiguration = Configuration.SamplerConfiguration.fromEnv().withType("const").withParam(1);
        Configuration.ReporterConfiguration reporterConfiguration = Configuration.ReporterConfiguration.fromEnv().withLogSpans(true);
        Configuration configuration = new Configuration(service).withSampler(samplerConfiguration).withReporter(reporterConfiguration);
        return configuration.getTracer();
    }
}

次回は残りのLesson 03 、Lesson 04をしたいと思います。

Sigfox Callbackで温度、湿度、気圧をSlackへ通知してみた

10月12日にSigfoxハンズオン広島(お土産付き)へ参加しArduino用Sigfoxシールド(Sigfox Shield for Arduino : UnaShield)を頂いたので、温度、湿度、気圧をSlackへ通知してみました。

sigfox.connpass.com

ArduinoからSlackへの通知する構成図

本当ならAWS IoT を使用するのだろうけど今回は手っ取り早く下記の方法で。

  1. UnaShieldで取得した温湿度・気圧センサーデータをSigfoxで送信

  2. Sigfox CloudのCallback機能機能を利用しAmazon API GatewayへPOST送信

  3. AWS Lambdaを使用してSlackへPOST送信し通知

f:id:yasu7ri:20181104100856g:plain

Sigfoxとは

フランスの通信事業会社Sigfox社が2009年から提供しているIoT向けに特化したLPWA通信規格(Low Power、Wide Area)です。国内では京セラコミュニケーションシステム株式会社(以下、KCCSと表記)が事業者としてサービスを提供しています。

第804回:SIGFOX とは - ケータイ Watch

Arduino用Sigfoxシールドの準備

ハンズオンでお土産で頂いたUnaShieldには温湿度・気圧センサ(BME280)が搭載されているのでこのセンサーを利用して 温度、湿度、気圧を取得します。

www.switch-science.com

UnaShieldを使用する前にSigfox Backend Cloudへの登録する必要があります。登録方法は取扱説明書に記載されているのでそれに従って登録します。

Sigfox Shield for Arduino(UnaShield V2 / V2S* )取扱説明書

Arduinoのプログラミング

今回はUnaShieldに標準で搭載されている温湿度・気圧センサ(BME280)から値を取得しSigfox Cloudへ送信する為のプログラムを作成し実行します。 温湿度・気圧センサ(BME280)を利用すりプログラムに関しては下記のサンプルを元に作成します。

github.com

30分毎に温度、湿度、気圧をSigfox Cloudへ送信します。(Arduinoのソース)

#include <Wire.h>
#include <SPI.h>
#include "Adafruit_Sensor.h"
#include "Adafruit_BME280.h"

#define BME_SCK 13
#define BME_MISO 12
#define BME_MOSI 11
#define BME_CS 10

#define SEALEVELPRESSURE_HPA (1013.25)

Adafruit_BME280 bme; // I2C

#include "SIGFOX.h"

static const String device = "NOTUSED";        //  Set this to your device name if you're using SIGFOX Emulator.
static const bool useEmulator = false;         //  Set to true if using SIGFOX Emulator.
static const bool echo = true;                 //  Set to true if the SIGFOX library should display the executed commands.
static const Country country = COUNTRY_JP;     //  Set this to your country to configure the SIGFOX transmission frequencies.
static UnaShieldV2S transceiver(country, useEmulator, device, echo);  //  Assumes you are using UnaBiz UnaShield V2S Dev Kit


void setup() {
  //  Initialize console so we can see debug messages (9600 bits per second).
  Serial.begin(9600);
  Serial.println(F("Running setup..."));
  //  Initialize the onboard LED at D13 for output.
  pinMode(LED_BUILTIN, OUTPUT);
  if (!bme.begin(0x76)) stop("Bosch BME280 sensor missing");  //  Will never return.
  //  Check whether the SIGFOX module is functioning.
  if (!transceiver.begin()) stop("Unable to init SIGFOX module, may be missing");  //  Will never return.
}

void loop() {
  //  Will be called repeatedly.
  //  Read the ambient temperature, humidity, air pressure.
  float filteredTemp = bme.readTemperature();
  float filteredPressure = bme.readPressure() / 100.0F;
  float filteredAltitude = bme.readAltitude(SEALEVELPRESSURE_HPA);
  float humidity = bme.readHumidity();

  Serial.println("---");
  Serial.print("filteredTemp = ");  Serial.print(filteredTemp);  Serial.println(" degrees C");
  Serial.print("filteredPressure = ");  Serial.print(filteredPressure);  Serial.println(" hPa");
  Serial.print("filteredAltitude = ");  Serial.print(filteredAltitude);  Serial.println(" metres above sea level");
  Serial.print("humidity = ");  Serial.print(humidity);  Serial.println(" %");
  Serial.println("---");

  //  Send temperature, pressure, altitude, module temperature as a SIGFOX message.
  //  Count messages sent and failed.
  static int counter = 0, successCount = 0, failCount = 0;
  Serial.print(F("\nRunning loop #")); Serial.println(counter);

  String msg = transceiver.toHex(filteredTemp) //  4 bytes
    + transceiver.toHex(filteredPressure)      //  4 bytes
    + transceiver.toHex(humidity);             //  4 bytes

  //  Send the message.
  digitalWrite(LED_BUILTIN, HIGH);  // Turn the LED on (HIGH is the voltage level).
  if (transceiver.sendMessage(msg)) {
    Serial.println(F("successCount!"));
    successCount++;
  } else {
    Serial.println(F("failCount!"));
    failCount++;
  }
  digitalWrite(LED_BUILTIN, LOW);   // Turn the LED off (LOW is the voltage level).
  counter++;
  
  //  Wait a while before looping.
  //  ex:10000  milliseconds = 10 seconds.
  //     60000  milliseconds = 1  minute.
  //    900000  milliseconds = 15 minute.
  //   1800000  milliseconds = 30 minute.
  Serial.println(F("Waiting..."));
  delay(1800000);
}

Sigfox Cloud側のCALLBACK設定

Arduino用Sigfoxシールドからの送信されたデータを受信しそれをトリガーに起動されるCALLBACKを設定します。

まずは、送信してくるSigfoxシールドのDEVICEの「Device Type」を選択します。

f:id:yasu7ri:20181104131407g:plain

新規Collbackの作成します。

f:id:yasu7ri:20181104132143g:plain

今回は単純にPOST送信するだけなので「Custom collback」を選択します。

f:id:yasu7ri:20181104132423g:plain

「Custom collback」の設定

f:id:yasu7ri:20181104133432g:plain

  • Custom payload config:
temp::float:32:little-endian pressure::float:32:little-endian humidity::float:32:little-endian

Custom payload configの記述方法に関しては下記のサイトを参考にしました。 qiita.com

  • Url pattern:Amazon API Gatewayで公開されたURLを設定します

  • Use HTTP Method:POST

  • Content type:application/json

  • Body:POSTするJSONを定義します

{
    "device" : "{device}",
    "time" : {time},
    "seqNumber" : {seqNumber},
    "duplicate" : {duplicate},
    "data" : "{data}",
    "temp" : {customData#temp},
    "humidity" : {customData#humidity},
    "pressure" : {customData#pressure}
}

Slackの設定

Slackへメッセージを送信するためにIncoming Webhooksを使用します。 Slackのアプリケーション追加ページより incoming-webhooks を検索・選択しインストールします。

インストールを行ったら通知を行うチャンネルを選択します。Webhook URLはAWS Lambdaから通知を行う為に使用するURLになります。

f:id:yasu7ri:20181104140309g:plain

AWS Lambda、Amazon API Gatewayの設定

Amazon API Gatewayを使用しSigfox CollbackからAWS Lambdaを呼び出します。

今回はAWS LambdaのランタイムはGo 1.xを使用し作成しました。 f:id:yasu7ri:20181104141828g:plain

AWS Lambdaの関数になるソースコードです。

package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "github.com/aws/aws-lambda-go/lambda"
    "net/http"
    "strconv"
    "time"
)

const mySlackURL = "Slackで設定したIncoming WebhooksのWebhook URL"
const channel = "通知するSlackのチャンネル"

type SensorInfo struct {
    Device    string  `json:"device"`
    Time      int64   `json:"time"`
    SeqNumber int64   `json:"seqNumber"`
    Duplicate bool    `json:"duplicate"`
    Data      string  `json:"data"`
    Temp      float64 `json:"temp"`
    Humid     float64 `json:"humidity"`
    Press     float64 `json:"pressure"`
}

type SendSlack struct {
    Channel  string `json:"channel"`
    Username string `json:"username"`
    Text     string `json:"text"`
}

func slack(si SensorInfo) (SensorInfo, error) {
    jst := time.Unix(si.Time, 0).In(time.FixedZone("Asia/Tokyo", 9*60*60)).Format(time.RFC1123)
    message := jst +
        "\n温度:" + strconv.FormatFloat(si.Temp, 'f', 2, 64) + `℃, 湿度:` + strconv.FormatFloat(si.Humid, 'f', 2, 64) + `%` +
        "\n気圧:" + strconv.FormatFloat(si.Press, 'f', 2, 64) + `hPa`

    sendms := SendSlack{
        Channel:  channel,
        Username: "百葉箱",
        Text:     message}

    jsonBytes, err := json.Marshal(sendms)
    if err != nil {
        fmt.Println("JSON Marshal error:", err)
        return si, nil
    }
    fmt.Println(string(jsonBytes))

    req, err := http.NewRequest(
        "POST",
        mySlackURL,
        bytes.NewBuffer(jsonBytes),
    )

    if err != nil {
        fmt.Println(err)
        return si, nil
    }

    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)

    if err != nil {
        fmt.Println(err)
        return si, nil
    }
    fmt.Println(resp)

    defer resp.Body.Close()
    return si, nil
}

func main() {
    lambda.Start(slack)
}

作成したAWS Lambdaを呼び出せるようにAmazon API Gatewayの作成・設定を行います。 API GatewayにPOSTメソッドを追加し先ほど作成したLambda 関数を指定します。

f:id:yasu7ri:20181104142518g:plain

最後に

Arduinoを電源に繋ぐと30分毎にSlackに気温、湿度、気圧の通知が来るようになります。 (画像は15分毎ですが。。。)

f:id:yasu7ri:20181104143022g:plain

参考URL

Doker for WindowsでFn Projectを動かしてみた

Doker for WindowsでFn Projectを動かした時のメモ

Fn Project

github.com

特徴

  • 「JavaOne 2017」の基調講演で、Oracleが発表したJava対応のオープンソースサーバレスプラットフォーム
  • 特徴
  • 簡単
    • 複雑なfunctionを実装するためのCLIを提供
  • 構成
    • Function Server(Docker)上にFunction(Docker)が乗っかる(Docker on Docker)
    • 起動するとFunctionのコンテナが立ち上がり一定時間後に消える

Function Serverの同期処理シーケンス

(非同期処理の場合はMQが使用される)

FnProject CLI toolのDownloadと設定

windowsの場合は下記からfn.exeを適当な場所へDownloadし、PATHを通す。

github.com

PATHが通っているかの確認
> fn --version
fn version 0.5.0

Fnction Serverの起動

ここではAPIのMetadataを格納するDBのファイルを"C:\mnt\docker\fnserver\data"へ置くように設定する。 (何も指定しないとSQLighのようだ)

docker run --rm --name fnserver -it -d -v /var/run/docker.sock:/var/run/docker.sock -v /c/mnt/docker/fnserver/data:/app/data -p 8080:8080 fnproject/fnserver
Fnction Serverの起動確認
> curl http://localhost:8080
{"goto":"https://github.com/fnproject/fn","hello":"world!"}

Functionの作成

JavaでのFunctionの作成方法

> fn init --runtime java --trigger http javafn
Creating function at: /javafn
Function boilerplate generated.
func.yaml created.
> tree /f                                                       
フォルダー パスの一覧:  ボリューム Windows                                     
ボリューム シリアル番号は 00000215 2E6B:1EF1 です                             
C:.                                                             
└─javafn                                                        
    │  func.yaml                                                
    │  pom.xml                                                  
    │                                                           
    └─src                                                       
        ├─main                                                  
        │  └─java                                               
        │      └─com                                            
        │          └─example                                    
        │              └─fn                                     
        │                      HelloFunction.java               
        │                                                       
        └─test                                                  
            └─java                                              
                └─com                                           
                    └─example                                   
                        └─fn                                    
                                HelloFunctionTest.java

FunctionはデフォルトではCold StandbyだがJavaでFunctionを作成するとHot Standbyで作成される。 Functionの定義ファイル"func.yaml"のformatがhttpだとHot Standbyのようだ。ちなみにアイドルタイムはデフォルトは30秒。 docs/hot-functions.md at master · fnproject/docs · GitHub

schema_version: 20180708
name: javafn
version: 0.0.1
runtime: java
build_image: fnproject/fn-java-fdk-build:jdk9-1.0.70
run_image: fnproject/fn-java-fdk:jdk9-1.0.70
cmd: com.example.fn.HelloFunction::handleRequest
format: http
triggers:
- name: javafn-trigger
  type: http
  source: /javafn-trigger

作成したFunctionのデプロイ

作成されたFunctionのディレクトリでおこなう

> fn --verbose deploy --app javaapp --local

Functionの呼び出し

> fn invoke javaapp javafn
Hello, world!

> curl -d Java http://localhost:8080/t/javaapp/javafn-trigger
Hello, Java!

ダッシュボード

fn project用のUIが用意されている。 github.com

UIの起動
docker run --rm --name ui -it -d --link fnserver -p 4000:4000 -e "FN_API_URL=http://fnserver:8080" fnproject/ui

おまけ

Fnction ServerとUIを同時に動かすdocker-composeはこんな感じです。

docker-compose.yaml
version: '2'

services: 
  fnserver:
    image: fnproject/fnserver
    container_name: fnserver
    ports:
      - '8080:8080'
    volumes:
      - "/var/run/docker.sock:/var/run/docker.sock"
      - "/c/mnt/docker/fnserver/data:/app/data"

  fnui:
    image: fnproject/ui
    container_name: fnui
    ports:
      - '4000:4000'
    environment:
      FN_API_URL: "http://fnserver:8080"
    depends_on:
      - fnserver
docker-composeの起動
> set COMPOSE_CONVERT_WINDOWS_PATHS=1
> docker-compose up -d
Creating network "fnproject_default" with the default driver
Creating fnserver ... done
Creating ui       ... done

参考

Cloud Native Hiroshima #01で行ったLTスライド

雑に QnA Maker を触って Slack と連動させてみた

QnA Maker を使用してFAQ BotをSlack に連動させたメモ

(2018.04時点ではプレビュー版です)

QnA Maker とは

会話的な方法でユーザーの質問に回答するためのREST APIとWebベースのサービスを簡単に作成する事が出来るサービス

こんな感じの仕組み

f:id:yasu7ri:20180420154243p:plain

QnA servicesの作成

まずはQnA Makerにアクセスして

"Create new service"からQnA servicesを作成する

FAQの元となる情報の指定方法には二通りある

一つはFAQサイトのURLを指定する方法で、もう一つはFAQが書かれているファイルをアップロードする方法

今回はMazda Zoom-Zoom スタジアム広島|よくあるご質問(Q&A)のURLを指定する方法で作成してみる

f:id:yasu7ri:20180422101931p:plain

後は"Create"ボタンを押すだけ

正常に作成されるとFAQの元となる情報が表形式で表示される

f:id:yasu7ri:20180422102929p:plain

微妙に読み取れていない場合は表を修正し"Save and retrain"を押す

また、指定したFAQサイトによっては全く読み取れない場合もある

f:id:yasu7ri:20180422103417p:plain

QnA servicesのテスト

作成したservicesはTestタブからテストも可能

f:id:yasu7ri:20180422104247p:plain

ここで質問に対する回答が間違っている場合は、正解となる回答を選択し学習することも可能のようだ

QnA servicesの公開

"Publish"よりserviceを公開する

f:id:yasu7ri:20180422105514p:plain

公開されるとSample HTTP Requestが表示され、その中にQnAKnowledgebaseIdQnASubscriptionKeyが埋め込まれている

これはAzule Functions Botを作成する際に必要なのでメモっておく

メモるのを忘れた場合はMy QnA servicesからView Codeでも確認できる

ここまででQnA servicesの作成は完了

Azule Functions Bot の作成

次にQnA serviceを使用したBot アプリをAzule Functions Botで作成する

作成する際にボット テンプレートは"Question and Answer"を指定する

f:id:yasu7ri:20180422122715p:plain

アプリケーション設定

作成されたAzule Functions Botへアプリケーション設定を行う

アプリケーション設定タブのアプリ設定へQnAKnowledgebaseIdQnASubscriptionKeyを設定して保存する

値は先ほどQnA services作成時にSample HTTP Requestに埋められていた値を使用する

f:id:yasu7ri:20180422120000p:plain

"Web チャットでテスト"タブでテストを行い回答が返ってくれば完了

f:id:yasu7ri:20180422212651p:plain

Slack との連動

今回作成したBotをSlackと連動させる

Slack Appの作成

アプリ名と作成したアプリが所属するワークスペースを指定してSlack Appを作成する

f:id:yasu7ri:20180422213533p:plain

Slack Appの設定

Redirect URLの追加を行う

f:id:yasu7ri:20180422214158p:plain

Bot Userの作成

f:id:yasu7ri:20180422215853p:plain

Event Subscriptionsの設定

Request URLに https://slack.botframework.com/api/Events/{ボット ハンドル} を入力する

ボット ハンドルはAzure Functions Botの設定にある

f:id:yasu7ri:20180422231413p:plain

Subscribe to Bot Eventsへダイレクトメッセージに反応するイベントを追加する

Azure Functions Botのチャンネル設定

Azure Functions BotのチャンネルからSlackを選択

f:id:yasu7ri:20180422224154p:plain

Slack AppのBasic InformationにあるApp Credentialsの情報をSlackの資格情報へ設定する

f:id:yasu7ri:20180422224644p:plain

f:id:yasu7ri:20180422224714p:plain

まとめ

QnA Makerをつかうと簡単にSlackで上で質問に回答してくれるBotが作成出来た

f:id:yasu7ri:20180422232700p:plain

参考

kubeadmでKubernetes1.9.3のClusterを構築したメモ

CloudGarage Deep Meetup in Hiroshimaで行ったLTのスライド作成時に行った事をメモしておく

cloudgarage.connpass.com

1. まずは使用するインスタンスの作成

今回はLT用にCloudGarageさんに6Gタイプのインスタンスプランを頂いたので下記のスペックで3つ作成

2. Dockerのインストール
$ apt-get update
$ apt-get install -y docker.io
3. kubeadm, kubelet, kubectlのインストール
  • kubeadm:Kubernetesが公式に提供しているKubernetes Clusterを簡単に構築できるツールキット
  • kubelet:全てのNodeで動いているnode agent
  • kubectl:Kubernetes ClusterをコントールするためのCLI
$ apt-get update && apt-get install -y apt-transport-https
$ curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
$ cat <<EOF >/etc/apt/sources.list.d/kubernetes.list
> deb http://apt.kubernetes.io/ kubernetes-xenial main
> EOF
$ apt-get update
$ apt-get install -y kubelet kubeadm kubectl
4. Cgroup Driverの設定

Cgroup DriverをDockerとkubeletとの間で一致させておく必要がある 以下で確認

$ docker info | grep -i cgroup
WARNING: No swap limit support
Cgroup Driver: cgroupfs
$ cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf | grep KUBELET_CGROUP_ARGS

異なる場合は以下を10-kubeadm.confへ追記

Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=cgroupfs"
$KUBELET_CGROUP_ARGS

最終的に以下のようになる

$ cat /etc/systemd/system/kubelet.service.d/10-kubeadm.conf | grep KUBELET_CGROUP_ARGS
Environment="KUBELET_CGROUP_ARGS=--cgroup-driver=cgroupfs"
ExecStart=/usr/bin/kubelet $KUBELET_KUBECONFIG_ARGS $KUBELET_SYSTEM_PODS_ARGS $KUBELET_NETWORK_ARGS $KUBELET_DNS_ARGS $KUBELET_AUTHZ_ARGS $KUBELET_CADVISOR_ARGS $KUBELET_CERTIFICATE_ARGS $KUBELET_EXTRA_ARGS $KUBELET_CGROUP_ARGS
5. kubeletの自動起動設定

kubeletが自動起動になっているかの確認

$ systemctl is-enabled kubelet
enabled

自動起動になっていない場合は変更する

$ systemctl enable kubelet && systemctl start kubelet
6. OSの設定

kubeletがちゃんと動くためにはswapを無効にする必要があるとのことなので、/etc/fstabを編集してswapをコメントアウトした

/dev/mapper/centos-swap swap                    swap    defaults        0 0

で、OSリブート

※ 2 から 6の内容を各インスタンスに行う

7. Maste Node の初期化(Maste Node)

podネットワークプロバイダにflannelを使用するため、 --pod-network-cidr=10.244.0.0/16を指定する

$ kubeadm init --pod-network-cidr=10.244.0.0/16 --apiserver-advertise-address=<kube-apiserverがリクエストを待ち受けるIPアドレス>

初期化終了後に発行されるトークン kubeadm join xxx.xxx.xxx.xxx:6443 --token *** は後で使用するので記録しておく

Your Kubernetes master has initialized successfully!

To start using your cluster, you need to run the following as a regular user:

  mkdir -p $HOME/.kube
  sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
  sudo chown $(id -u):$(id -g) $HOME/.kube/config

You should now deploy a pod network to the cluster.
Run "kubectl apply -f [podnetwork].yaml" with one of the options listed at:
  https://kubernetes.io/docs/concepts/cluster-administration/addons/

You can now join any number of machines by running the following on each node
as root:

  kubeadm join xxx.xxx.xxx.xxx:6443 --token uxhdok.0z4s4lyjyb7qot36 --discovery-token-ca-cert-hash sha256:8dd4dd2fd3442174c99c67d683ac5ad03c52f0dbfec42f60c215a1336f5b5fb3
8. podネットワークのインストール(Maste Node)
$ kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/v0.9.1/Documentation/kube-flannel.yml
clusterrole.rbac.authorization.k8s.io "flannel" created
clusterrolebinding.rbac.authorization.k8s.io "flannel" created
serviceaccount "flannel" created
configmap "kube-flannel-cfg" created
daemonset.extensions "kube-flannel-ds" created

kubectlコマンドで下記のメッセージが表示された場合

The connection to the server localhost:8080 was refused - did you specify the right host or port?

ユーザの場合は.bashrcへ下記を追記する

export KUBECONFIG=/etc/kubernetes/admin.conf
9. Nodeの追加(Worker Node)
  1. で出力されたトークンをWorker Nodeで実行しNodeの追加を行う
$ kubeadm join xxx.xxx.xxx.xxx:6443 --token uxhdok.0z4s4lyjyb7qot36 --discovery-token-ca-cert-hash sha256:8dd4dd2fd3442174c99c67d683ac5ad03c52f0dbfec42f60c215a1336f5b5fb3
[preflight] Running pre-flight checks.
    [WARNING Hostname]: hostname "worker02" could not be reached
    [WARNING Hostname]: hostname "worker02" lookup worker02 on 203.174.65.3:53: no such host
    [WARNING FileExisting-crictl]: crictl not found in system path
Suggestion: go get github.com/kubernetes-incubator/cri-tools/cmd/crictl
[discovery] Trying to connect to API Server "125.6.66.132:6443"
[discovery] Created cluster-info discovery client, requesting info from "https://125.6.66.132:6443"
[discovery] Requesting info from "https://125.6.66.132:6443" again to validate TLS against the pinned public key
[discovery] Cluster info signature and contents are valid and TLS certificate validates against pinned roots, will use API Server "125.6.66.132:6443"
[discovery] Successfully established connection with API Server "125.6.66.132:6443"

This node has joined the cluster:
* Certificate signing request was sent to master and a response
  was received.
* The Kubelet was informed of the new secure connection details.

Run 'kubectl get nodes' on the master to see this node join the cluster.

Maste NodeでNodeの確認

$ kubectl get nodes
NAME       STATUS    ROLES     AGE       VERSION
master     Ready     master    4h        v1.9.3
worker01   Ready     <none>    27s       v1.9.3
worker02   Ready     <none>    2m        v1.9.3

おまけ

k8sダッシュボードのデプロイ

kubernetes-dashboardのデプロイ

$ kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml
secret "kubernetes-dashboard-certs" created
serviceaccount "kubernetes-dashboard" created
role.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" created
rolebinding.rbac.authorization.k8s.io "kubernetes-dashboard-minimal" created
deployment.apps "kubernetes-dashboard" created
service "kubernetes-dashboard" created

作成されたpodの確認

$ kubectl get pods --all-namespaces
NAMESPACE     NAME                                    READY     STATUS    RESTARTS   AGE
kube-system   etcd-master                             1/1       Running   0          37m
kube-system   kube-apiserver-master                   1/1       Running   0          37m
kube-system   kube-controller-manager-master          1/1       Running   0          37m
kube-system   kube-dns-86f4d74b45-lt994               3/3       Running   0          4h
kube-system   kube-flannel-ds-2qfg6                   1/1       Running   1          26m
kube-system   kube-flannel-ds-6b8x9                   1/1       Running   1          24m
kube-system   kube-flannel-ds-dzsd7                   1/1       Running   0          38m
kube-system   kube-proxy-jvg28                        1/1       Running   0          4h
kube-system   kube-proxy-lfm59                        1/1       Running   0          24m
kube-system   kube-proxy-tslwt                        1/1       Running   0          26m
kube-system   kube-scheduler-master                   1/1       Running   0          37m
kube-system   kubernetes-dashboard-7d5dcdb6d9-hlh2r   1/1       Running   0          7m

API ServerへローカルPCからアクセスするためにadmin.confを取得しkubectl proxyを実行する

$ scp root@xxx.xxx.xxx.xxx:/etc/kubernetes/admin.conf .
kubectl --kubeconfig ./admin.conf proxy

ダッシュボードURL

http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/#!/overview?namespace=default

このままではログイン出来ない!

ダッシュボードのPodのService Accountであるkubernetes-dashboardにAdmin権限を付けるとサインイン画面でSKIPを押すとなんでも見れるようになる cluster-adminというClusterRoleにkubernetes-dashboardにバインドするClusterRoleBindingのYAMLファイル作成する (本番ではNG)

apiVersion: rbac.authorization.k8s.io/v1beta1
kind: ClusterRoleBinding
metadata:
  name: kubernetes-dashboard
  labels:
    k8s-app: kubernetes-dashboard
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: kubernetes-dashboard
  namespace: kube-system

作成したYAMLファイルををkubectlでロールを作成する

$ kubectl --kubeconfig ./admin.conf create -f dashboard-admin.yml
clusterrolebinding "kubernetes-dashboard" created

参考

CloudGarage Deep Meetup in Hiroshimaで行ったLTスライド

そんな広島のスキー場

この記事は「そんな広島 Advent Calendar 2017」の18日目の記事です。

今年は寒くなるのが早いですね。

広島市内も先日小雪が舞いました。

広島県は瀬戸内の温かい気候にも関わらずウインタースポーツも楽しめるお得な県なのです。

突然ですが広島県のスキー場どのくらい在るのだろうか。

調べてみた。

www.hiroshima-kankou.com

広島県公式観光サイトでは、日本の南限に位置するスキー場の集積地で現在は下記の13のスキー場が県内に在るようです。

広島県のスキー場
  • りんご今日話国スキー場
  • ひろしま県民の森スキー場
  • スノーリゾート猫山
  • 道後山高原スキー場
  • スノーフィールドもみのき森林公園
  • 女鹿平温泉めがひらスキー場
  • 恐羅漢スノーパーク
  • やわたハイランド191リゾート
  • 芸北高原 大佐スキー場
  • 雄鹿原高原スキー場
  • 芸北国際スキー場
  • ユートピアサイオト
  • スキーパーク寒曳

ちなみに私がよく行くスキー場は芸北国際スキー場です。 f:id:yasu7ri:20171218130444j:plain

アクセス方法

近いスキー場であれば広島市内から車で一時間半もあれば着きます。

広島市内からであれば日帰りバスツアーも出ています。

道具に関して

各スキー場ではレンタルも行っているので大丈夫です。

ツアーによってはレンタル込みのものも有るので確認してみましょう。

よく行かれる方でお子様のレンタルにお困りの方は、スキープロショップ・ピステのシーズンレンタルを利用すると良いかと思います。

子供は成長するのが早いので私もピステのシーズンレンタルを利用しています。

そう言えば、最近はようやくヘルメットを着用する方が多くなった気がしますがヘルメットのメリットは何と言っても温かい!!

特にお子様と行かれる方はヘルメットを着用して楽しく安全に楽しみましょう。

f:id:yasu7ri:20171218131000j:plain

まとめ

広島県のスキー場数は西日本でもトップクラスです。

瀬戸内は温暖で山に行けばウインタースポーツも楽しめるお得な地域ですので、しっかり楽しみましょう!