とんちゃんといっしょ

Cloudに関する技術とか日常とかについて書いたり書かなかったり

Macで `ip` コマンド

Linuxip コマンドになったのにMacはifconfigだったりするのでなかなか ip に慣れないところ、知り合いに以下のツールを教えてもらった。

github.com

% /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
% brew install iproute2mac
% ip a
lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 16384
    inet 127.0.0.1/8 lo0
    inet6 ::1/128
    inet6 fe80::1/64 scopeid 0x1
...

これでMacでも ip コマンドが使える様になるのでようやく自分の中で ip コマンドが根付きそうである。

Error 1298: Unknown or incorrect time zone: 'UTC' の解決

なんかタイトルのエラーが出た。

Error 1298: Unknown or incorrect time zone: 'UTC'

ググったところ簡単に解決策が見つかった。

dba.stackexchange.com

以下のコマンドで解決できるらしい。

$ mysql_tzinfo_to_sql /usr/share/zoneinfo/|sudo mysql -u root mysql

mysql_tzinfo_to_sql プログラムは、mysql データベースに、タイムゾーンテーブルをロードします。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 4.4.6 mysql_tzinfo_to_sql — タイムゾーンテーブルのロード

ということらしい。 つまりMySQLタイムゾーンが入っていないことが原因だったらしいのでこれで解決。

GCPを使ってサーバレスでSlackの統計データを取れるようにした話

この記事はSlack Advent Calendar 2018の7日目の記事です。

(8日目を書いた後に7日目が空いていたので書くことにしたため日付を前後して書いております)

TL;DR

  • SlackのEvent APIGoogle Cloud Platformを利用するとサーバレスかつ簡単にSlackのPublicなPostデータが管理できる
  • 溜まったデータもサーバレスかつ簡単に統計データを得ることができる
  • 個人でやるにはちょっとお高めかも

背景

800人近い人がワイワイしているSlackコミュニティに所属しているのだが、 昨年まで年間の発言数ランキングなどを作ってくれていた人がいなくなったこともあり、 今年の発言ランキングをどうしようかと考えていたが、 自分でつくろう、なんなら週間ランキングも出してしまおうと思い作ることにした。

ゴール

まずは1週間の発言ランキングを毎週月曜日の9時に #general にBOTがPostすること。

全体像

f:id:mazinlabs:20181212014400p:plain
SlackのデータをBigQueryに転送

f:id:mazinlabs:20181212014434p:plain
Cloud SchedulerからのイベントをもとにBigQueryから統計データを取得してSlackにPost

BigQueryにデータを投入する際にやったこと

1. Cloud Pub/Subの設定

Cloud FunctionsからBigQueryに転送するために利用するトピックを作成

2. Cloud Functionsの設定

SlackのEvent APIから送られてくるデータをCloud Pub/Subに送るためのFunctionを作成。

今回はPythonを選択。

  • main.py
from flask import make_response
from google.cloud import pubsub_v1
import os
import json


def _event_handler(data, event_type, subtype=None):
    if event_type == 'message' and subtype == None:
        publisher = pubsub_v1.PublisherClient()
        topic_name = os.environ['TOPIC_NAME']
        payload = {
            'channel': data['channel'],
            'user': data['user'],
            'ts': data['ts'],
        }
        publisher.publish(topic_name, json.dumps(payload).encode('utf-8'))
        
    return make_response('OK', 200)

def get_slack_event(request):
    slack_event = request.get_json()

    if 'challenge' in slack_event:
        return slack_event.get("challenge")

    if "event" in slack_event:
        event = slack_event.get("event")
        event_type = event.get("type")
        subtype = event.get("subtype")
        return _event_handler(event, event_type, subtype)

    return make_response("[NO EVENT IN SLACK REQUEST] These are not the droids\
            you're looking for.", 404, {"X-Slack-No-Retry": 1})
  • requirements.txt
flask
google-cloud-pubsub
  • 環境変数 TOPIC_NAMEの設定(2で作成したCloud PubSubのTopic名)

3. SlackのEvent APIを設定

  • SlackのIntegration枠を一つ使ってCreate New Appをする。
  • 「Subscribe to Workspace Events」に「message.channels」を設定

f:id:mazinlabs:20181216225630p:plain
Subscribe to Workspace Events に設定

Event SubscriptionsのRequest URL に対して3で作成したCloud Functionsのアクセス先を設定。

4. BigQueryの設定

今回はTimestamp, Channel ID, User IDを取得することにしているのでこの3つを設定。

f:id:mazinlabs:20181216233149p:plain
BigQueryで設定したテーブルのスキーマ

5. GCSの設定

次に設定するDataflowで利用するためのディレクトリを作成

6. Dataflowの設定

  • 「テンプレートからジョブを作成」を選択
  • ジョブ名を設定
  • 「Cloud Pub/Sub to BigQuery」を選択
  • 「Cloud Pub/Sub input topic」1で作成したTopicを設定
  • 「BigQuery output table」に4で設定したテーブル名を設定
  • 「一時的なロケーション」に5で設定したGCSのパスを設定

7. 動作確認

BigQueryでデータが取れることを確認。

Query

SELECT user, count(ts) as post FROM `table_name` 
group by user
order by post desc
LIMIT 10 

f:id:mazinlabs:20181216233515p:plain
BigQueryで集計したユーザごとの発言数

BigQueryから情報を出す際にやったこと

1. SlackのBOT用のトークンを取得

SlackのページからBOT用のトークンを取得してくる

  • SlackアプリのOAuth & Permissionsのページに移動
  • Bot User OAuth Access Token」のトークンを取得

2. Cloud Functionsの設定

Cloud Schedulerから起動するCloud Functionsを設定

今回は user_weekly_post_count という名前で作成

BigQueryにはユーザIDしか入っていないのでこちらでusers.infoから名前を取得しています。

import json
import os

from datetime import date, timedelta
from flask import make_response
from google.cloud import bigquery
from slackclient import SlackClient

def create_query_string():
    query_string = """
        SELECT user, count(ts) as post 
        FROM `cloudtu-dev-02962067.ogura_bigdata_counter.messages` 
        where TIMESTAMP('{} 00:00:00') <= ts and ts < TIMESTAMP('{} 00:00:00')
        group by user
        order by post desc
        LIMIT 10"""
    
    today = date.today()
    start = (today - timedelta(days=8)).isoformat()
    end = (today - timedelta(days=1)).isoformat()
    return (start, end, query_string.format(table_name, start, end))
   
def query_user_weekly_post_count(request):    
    request_json = request.get_json()
    if not (request_json and 'key' in request_json):
        return make_response("Authentication error", 403)
    
    client = bigquery.Client()
    start, end, query = create_query_string()
    query_job = client.query(query)

    results = query_job.result()  # Waits for job to complete.

    slack_token = os.environ["SLACK_API_TOKEN"]
    key = os.environ["KEY"]
    sc = SlackClient(slack_token)
    result = '1週間({}~{})のPost数ランキング\n'.format(start, end)
    rank = 0
    for row in results:
        user = sc.api_call(
            "users.info",
            user=row.user
        )
        user_name = user["user"]["name"]
        rank += 1
        result += '{} : {} ({} posts)\n'.format(rank, user_name, row.post)
        
    sc.api_call(
        "chat.postMessage",
        channel="general",
        text=result
    )    
    return make_response(result, 200)
flask
google-cloud-bigquery
slackclient

3. Cloud Schedulerの設定

先月11月にCloud Schedulerというスケーラブルなcronのサービスが出ていたのでGAEなどを使わずCloud SchedulerからCloud Functionsが多々けるのではないかと思い試してみた。

なお、Cloud Schedulerはベータサービスということもあってか のWeb GUIではBodyの設定をしてもうまく反映されないことがわかったので、CLIから操作した。

コードは以下の通りで、URIとmessage-bodyは各自で設定が必要。

#!/bin/bash
gcloud beta scheduler jobs create http user_weekly_post_ranking    \
  --schedule="0 9 * * 1" \
  --uri="https://asia-northeast1-$PROJECT_ID.cloudfunctions.net/user_weekly_post_count" \
  --time-zone="Asia/Tokyo" \
  --headers="Content-Type=application/json" \
  --message-body="{\"key\":\"$KEY\"}"

4. 動作確認

Cloud Schedulerに「Run now」のボタンがあるので押して動作を確認。

f:id:mazinlabs:20181217002014p:plain
動作確認結果

まとめ

SlackのEvent APIGCPのサービスを利用することで簡単にSlackデータを蓄積を実現し、また定期的にSlackに統計データを通知する事が可能になりました。

ただ、Dataflowは利用は無料枠がなく使い始めると即お金がかかるのでもう少し安くするためにはCloud FunctionsからストリームでBigQueryにデータを入れる or 一時的に何処かに蓄積したものをBigQueryに投入するなどの工夫が必要になってくると思います。なので、また何か改善した際はブログに書きたいと思います。

Slackチームつなげるワームホールを開発した話

この記事はSlack Advent Calendar 2018の8日目の記事です。

TL;DR

  • Slack有料版のゲスト機能相当を無料で実現する、複数Slackチーム間のチャンネルをつなげるプログラムを開発(下図参照)
  • 環境は紆余曲折を経て現在はGCP上でGCE, Cloud PubSub, Cloud Datastore, docker, slack-ruby-client, Slack RTM APIを使って実現
  • 使い方はレポジトリのREADMEを参照
  • 今後も機能拡張や安定稼働に向けて開発は続けていくつもりだけど実装ペースは気分次第

f:id:mazinlabs:20181204155652p:plainf:id:mazinlabs:20181204155726p:plain
左:Slackチーム A、右:Slackチーム B

レポジトリ

github.com

背景

とあるグループ会社に所属するエンジニア有志のSlackがあり、 グループ会社を卒業するとそのSlackチームも卒業し、OB/OGのSlackチームに移動する決まりになっている。

しかしながら、卒業した人から「困ったときに聞ける人がいるのが良かった」や「情報交換の機会が減った」などという声が上がっていたため、なにか方法がないかと検討したが

私「あるWorkspaceの特定のチャンネルにだけ別Workspaceの人達を入れたいな!」

Slack「できるよ!お金払えば!!」

  • とあるSlackチームのユーザ数(2018/12/4時点):772人
  • 現在のアクティブユーザ数(2018/12/4時点): 396人
  • Slackのスタンダードプランの1userあたりのお値段: 850円 / 月

f:id:mazinlabs:20181204151227p:plain
とあるSlackチームが課金する場合の額

払えるか!!!

というわけで、無料で有料相当の機能を実現すべくSlack APIを使ったメッセージ転送に思い至る。

コンセプト

「あるSlackチームの特定のチャンネルで発言したものが、別のチームの同じチャンネルに転送されて会話ができる」

これだけです。

某Slackチームには #blackhole という発言が一定時間で消えていく人気チャンネルがあるので、2点間をつなぐ、つまりwormholeだなと安直な感じで名前をつけてみた。なおwikipediaで見ても大体あってる。

ワームホール (wormhole) は、時空構造の位相幾何学として考えうる構造の一つで、時空のある一点から別の離れた一点へと直結する空間領域でトンネルのような抜け道である。 ワームホール - Wikipedia

ただ、2点間ではなく複数Slackチームがつながるような設計にしてあったりする。

現在実装されている機能

  • メッセージ処理(CRUD)の転送
  • リアクション転送(文字として転送される)
  • Thread対応

実装されていない機能

  • ファイル転送
  • join / left を検知した通知
  • member listの共有

実装1

最初期の実装は以下を使って実装していた

このときのメッセージ転送の設計こうなっていたらしい

f:id:mazinlabs:20181204160426p:plain
wormholeの初期のメッセージング設計

雑な発表資料

speakerdeck.com

レポジトリ

github.com

当初はBOTの名前とアイコンで転送していたのだが、postMessage APIにユーザ名やアイコンを指定できることがわかったので、発言したユーザの名前とアイコンを使うようにしたことで一気に使いやすくなった。 そして当初うまく動いているように見えたのだが、数日ごとにRabbitMQのプロセスが張り付いて再起動を余儀なくされることに。。。

RabbitMQなんて捨ててやる!時代はマネージドなメッセージングサービスだという思いにいたりCloudnを捨ててマネージドなメッセージングサービスかつ無料で使えそうなGCPにお引越しをして実装2に移行。

実装2

GCP上で以下を使って実装

  • Hubot(CoffeeScript)
  • Docker
  • Cloud Compute Engine
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Build
  • Cloud Container Registry
  • Slack RTM API

f:id:mazinlabs:20181204163142p:plain
実装2と実装4のメッセージングの設計

Githubのレポジトリを更新するとCloud Buildが動いてContainer Registryにコンテナを登録してくれるように。 RTM APIで受けたメッセージをCloud PubSubにPublishし、wormholeとして接続される側がそれをSubscribeする。 Cloud Datastoreでメタデータを管理し、Postされたメッセージが更新、削除された際にその変更削除を転送先に伝搬させる際に使われる。 だいたい$1/月ぐらいで運用できている。

github.com

これはかなり安定して動かせていたものの、やはり時おりHubotが謎のエラーで落ちる。 あとCofeeScriptで書いていたので書きづらい。 毎度コンテナを復旧させるのが面倒なので、リクエストに応じて処理をするEvent drivenなCloud Functionsのほうが良いのではと思い実装3を検討

実装3

  • Nodejs
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Functions

f:id:mazinlabs:20181204163740p:plain
実装3のメッセージング設計

当時はまだCloud FunctionsにPythonが出てきていなかったのでNodeで書いてみるものの、 Functionごとの環境変数の設定や同じ関数名が動かせない(といううろ覚えの記憶)と、 やりたいことができないのでCoud Functionsを断念。(コードもレポジトリ管理する前に見切った)

結果として自分が得意なRubyにしたほうが一から実装してもあとの機能追加がきっと楽だと思って実装そのものを乗り換えることに。

実装4

現在動かしているのがこのバージョン

  • slack-ruby-client (Ruby)
  • Docker
  • Cloud Compute Engine
  • Cloud PubSub
  • Cloud Datastore
  • Cloud Build
  • Cloud Container Registry
  • Slack RTM API

github.com

メッセージングの設計自体は実装2からさほぼ変更はなし。

slack-ruby-clientのRTM clientが定期的にログも出さずにコネクションが切断されるという問題(下記リンク参照)があったものの、エラーハンドリングなどで対応して現在安定稼働中。

Slack-side disconnects · Issue #208 · slack-ruby/slack-ruby-client · GitHub

ただ、やはりRTM clientの動作が不安定なのとCloud Functionsの機能追加などで実装3のEvent APIベースに戻そうかと悩んでいるところ。

今後の予定

直近はreactionのremoveの挙動が多分不思議な事になってるのでそれの修正をするが、 slack-wormholeのissuesにあげてることぐらいはやりたいなと思ってる。

もし使う人がいてバグ報告や要望があれば暇を見て実装したり直したりしたいところ。

まとめ

紆余曲折を経たもののSlackチーム間をSlack APIを使って無料でつなぐプログラムを開発した。

今後も改良や修正は続けていくつもりなので利用者からのフィードバックお待ちしております!

Spinnaker の pipeline-template を試してみた

モチベーション

Spinnakerの pipeline をUIではなくレビューができるコード形式にしたい

やったこと

これを参考に

pipeline-templateの有効化

$ hal config features edit --pipeline-templates true
+ Get current deployment
  Success
+ Get features
  Success
+ Edit features
  Success
+ Successfully updated features.
$ hal deploy apply
+ Get current deployment
  Success
+ Prep deployment
  Success
Problems in
  default.provider.dockerRegistry.spinnaker-gcr-account:
- WARNING Your docker registry has no repositories specified, and
  the registry's catalog is empty. Spinnaker will not be able to deploy any images
  until some are pushed to this registry.
? Manually specify some repositories for this docker registry to
  index.

Problems in default.security:
- WARNING Your UI or API domain does not have override base URLs
  set even though your Spinnaker deployment is a Distributed deployment on a
  remote cloud provider. As a result, you will need to open SSH tunnels against
  that deployment to access Spinnaker.
? We recommend that you instead configure an authentication
  mechanism (OAuth2, SAML2, or x509) to make it easier to access Spinnaker
  securely, and then register the intended Domain and IP addresses that your
  publicly facing services will be using.

+ Preparation complete... deploying Spinnaker
+ Get current deployment
  Success
+ Apply deployment
  Success
+ Deploy spin-clouddriver
  Success
+ Deploy spin-front50
  Success
+ Deploy spin-orca
  Success
+ Deploy spin-deck
  Success
+ Deploy spin-echo
  Success
+ Deploy spin-gate
  Success
+ Deploy spin-igor
  Success
+ Deploy spin-kayenta
  Success
+ Deploy spin-rosco
  Success
+ Run `hal deploy connect` to connect to Spinnaker.

テンプレートの作成

テンプレートは現状HTTP(S) 経由でしか使えない。

サンプルのテンプレート(本家のほうは動かない. metadataが必要らしい)

schema: "1"
id: barebones
metadata:
  name: barebones
  description: this is test.
  scopes: [global]
variables: []
stages:
- id: wait
  type: wait
  config:
    waitTime: 5

roer のDL

githubのページからDL

$ wget https://github.com/spinnaker/roer/releases/download/v0.11.3/roer-linux-amd64

バイナリが落ちてくるのであとは実行権限を付与。 ついでに名前も変えておく。

$ file roer-linux-amd64
roer-linux-amd64: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$ chmod +x roer-linux-amd64
$ mv roer-linux-amd64 roer

CLI (roer)を使ったtemplateの作成

GKEでSPINNAKER_APIを設定するのはこっち 参照

$ roer pipeline-template publish template.yml

参考情報

11/18 若潮 - くもり

午後から友人たちと釣りに

今回はチニングを試してみた。

Arukazik Japanの舞シュリンプと躍ハゼをBM.キールの5gと7gで投げてみたが、今回の場所は最初5gにしてみたが流されたので、途中から7gにしたがそれも結構流されていたのでもう1ランクぐらい重くしないとダメかも。

Arukazik Japan(アルカジックジャパン) BM.キール 5.0g #1/0

Arukazik Japan(アルカジックジャパン) BM.キール 5.0g #1/0

Arukazik Japan(アルカジックジャパン) BM.キール 7.0g #1/0

Arukazik Japan(アルカジックジャパン) BM.キール 7.0g #1/0

Arukazik Japan(アルカジックジャパン) ワーム 舞シュリンプ 3.2インチ ダッピ #112 ルアー

Arukazik Japan(アルカジックジャパン) ワーム 舞シュリンプ 3.2インチ ダッピ #112 ルアー

Arukazik Japan(アルカジックジャパン) ワーム 躍ハゼ 1.6インチ イシガニ #103 ルアー

Arukazik Japan(アルカジックジャパン) ワーム 躍ハゼ 1.6インチ イシガニ #103 ルアー

2度ほどフォール中にあたりっぽい感じがあったが結局それ以外のアタリはなし。 最後はリールがバックラッシュしたので諦めて終了。

釣果はちょい投げの友人がハゼを1匹釣っただけでした。

寒かった・・・

Spinnakerのupdate方法

7月から別件で忙しくて触れてなかったのでとりあえずアップデートのやり方を調べて実施したのでメモ。

1. halyardのupdate

$ sudo update-halyard
$ hal --version
1.12.0-20181024113436

2. 利用可能なバージョンの確認

$ hal version list
+ Get current deployment
  Success
+ Get Spinnaker version
  Success
+ Get released versions
  Success
+ You are on version "1.7.6", and the following are available:
 - 1.7.8 (Ozark):
   Changelog: https://gist.github.com/spinnaker-release/75f98544672a4fc490d451c14688318e
   Published: Wed Aug 29 19:09:57 UTC 2018
   (Requires Halyard >= 1.0.0)
 - 1.8.7 (Dark):
   Changelog: https://gist.github.com/spinnaker-release/ebb5e45e84de5b4381b422e3c8679b5a
   Published: Fri Sep 28 17:58:52 UTC 2018
   (Requires Halyard >= 1.0.0)
 - 1.9.5 (Bright):
   Changelog: https://gist.github.com/spinnaker-release/d24a2c737db49dda644169cf5fe6d56e
   Published: Mon Oct 01 17:15:37 UTC 2018
   (Requires Halyard >= 1.0.0)
 - 1.10.1 (Maniac):
   Changelog: https://gist.github.com/spinnaker-release/9a46f497a6e081e1ef8f12867b0ee3c6
   Published: Wed Oct 24 17:04:36 UTC 2018
   (Requires Halyard >= 1.11)

1.10.1が使えるらしいので最新版にしてみる

3. バージョンの設定

$ hal config version edit --version 1.10.1
+ Get current deployment
  Success
+ Edit Spinnaker version
  Success
+ Spinnaker has been configured to update/install version "1.10.1".
  Deploy this version of Spinnaker with `hal deploy apply`.

4. 設定のアプライ

$ hal deploy apply
+ Get current deployment
  Success
+ Prep deployment
  Success
Problems in
  default.provider.dockerRegistry.spinnaker-gcr-account:
- WARNING Your docker registry has no repositories specified, and
  the registry's catalog is empty. Spinnaker will not be able to deploy any images
  until some are pushed to this registry.
? Manually specify some repositories for this docker registry to
  index.

Problems in default.security:
- WARNING Your UI or API domain does not have override base URLs
  set even though your Spinnaker deployment is a Distributed deployment on a
  remote cloud provider. As a result, you will need to open SSH tunnels against
  that deployment to access Spinnaker.
? We recommend that you instead configure an authentication
  mechanism (OAuth2, SAML2, or x509) to make it easier to access Spinnaker
  securely, and then register the intended Domain and IP addresses that your
  publicly facing services will be using.

+ Preparation complete... deploying Spinnaker
+ Get current deployment
  Success
+ Apply deployment
  Success
+ Deploy spin-clouddriver
  Success
+ Deploy spin-front50
  Success
+ Deploy spin-orca
  Success
+ Deploy spin-deck
  Success
+ Deploy spin-echo
  Success
+ Deploy spin-gate
  Success
+ Deploy spin-igor
  Success
+ Deploy spin-kayenta
  Success
+ Deploy spin-rosco
  Success
+ Run `hal deploy connect` to connect to Spinnaker.

5. バージョンの確認

$ hal version list
+ Get current deployment
  Success
+ Get Spinnaker version
  Success
+ Get released versions
  Success
+ You are on version "1.10.1", and the following are available:

おわり