とんちゃんといっしょ

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

Display Nameが設定されてなくてもユーザ名が出るように修正

Display Nameが設定されていないとBOTの名前になってしまうバグが有った。

github.com

毎回Display Nameを設定してねっていうのが面倒になったし、 そもそも設定しなくても出るようにしてたはずだったのだがそうなっていなかったので修正した。

github.com

これでDisplay Nameが設定されてなくてもユーザ名が出るようになった。

あー、Docker imageのダイエットしたいな・・・

子育てエンジニアのインプット・アウトプット方法

2019/2/16 22:42 ながらに追記

きっかけ

続々とコメントが寄せられているのでとりあえず昼休みにまとめてみた。

寄せられたコメント

家事時間の短縮・削減

  • やること/やらないことの明確化
  • 家事の自動化
    • ルンバ
    • 食洗機
    • 乾燥機能つき洗濯機
    • 浴室乾燥
  • 料理キットの活用
  • 外食の活用

空き時間の活用

  • 通勤時間
  • ビルドの待ち時間
  • 子供の習い事の付き添い時間

時間の捻出

  • 仕事の量を減らす
  • テレビを見る時間を削る
  • 子供と早い時間に寝て朝早くに
  • 夜更かし可能な日に(ex.土曜)
  • 有給
  • 子供も自分もそれぞれ一人で本を読む時間を設ける
  • リモートワーク
  • 職場に近づく引っ越し
  • 子供をYoutubeで釘付けにする
  • シッターさんを雇う

ながら

  • 寝かせつけ前に競技プログラミング等の問題を見て寝かせつけをしながら脳内で解く
  • 家事をしながらTVにChromecast経由でYoutubeに上がっているカンファレンスの動画を流す
    • 海外のカンファレンスの場合英語字幕をつけると英語の勉強にもなる

効率化

  • アウトプット(ゴール)を先に決めて集める情報を効率化
  • 仕事をチャレンジングなものにする
    • 場合によっては転職も視野に

番外編

こういう勉強会もあるらしい

ちなみに我が家

  • 夫婦共働き
  • 子供2人
    • 上5歳、下1歳
    • 共に保育園
  • 通勤 1.5h

来てるコメントの中ではまだこれぐらいしかまだやってないのでもう少しいろいろやってみようと思う。

  • ルンバ
  • 食洗機
  • 乾燥機能つき洗濯機
  • 通勤時間
  • 夜更かし
  • 子供をYoutubeで釘付けにする
  • リモートワーク(週1〜2程度)

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

参考情報