sitateru tech blog: Golang

sitateru tech blog

シタテルの技術やエンジニアの取り組みを紹介するテックブログです。

ラベル Golang の投稿を表示しています。 すべての投稿を表示
ラベル Golang の投稿を表示しています。 すべての投稿を表示

2018年12月18日火曜日

FirebaseのSDKのプルリクを追ってみた

12月 18, 2018

こんにちは、シタテルの鶴巻です。 devops&インフラ担当です。

最近、firebase/firebase-admin-goの実装で気になったところがあり、プルリクエストから経緯を知ることができました。

GitHub上のソースコードで気になった箇所の経緯を追うことは今後もあると思うので、整理がてら方法を紹介します。

要約

  • firebase/firebase-admin-goの実装で気になったところがあった
  • git blameコマンドで、ソースコードが最後に編集されたhashを特定
  • 対象のソースコードが追加されたプルリクエストで経緯が分かった

firebase-admin-sdk-goの実装で気になった箇所

firebase-admin-sdk-goは、名前の通りFirebaseサービスをGolangから扱うためのSDKです。

Firebase Authenticationを試してみた際に、弊社で使用しているRubyのSDKがなかったため、GolangのSDKの実装を少し読んでみました。

そこで、Googleのサーバから公開鍵を取得するメソッドの中で排他制御が行われていることに着目しました。
私はここで排他制御を実施している理由がわからなかったので、経緯を調べることにしました。

該当ソースコードは以下です。
https://github.com/firebase/firebase-admin-go/blob/master/auth/token_verifier.go#L66-L76

func (k *httpKeySource) Keys(ctx context.Context) ([]*publicKey, error) {
    k.Mutex.Lock()
    defer k.Mutex.Unlock()
    if len(k.CachedKeys) == 0 || k.hasExpired() {
        err := k.refreshKeys(ctx)
        if err != nil && len(k.CachedKeys) == 0 {
            return nil, err
        }
    }
    return k.CachedKeys, nil
}

プルリクエストを見つける手順

1. git blameコマンドで、対象のソースコードが最後に編集されたcommitのHash値を特定

  • 対象のソースコードが最後に変更されたcommiのHash値はf3174984ということが特定できます
    $ git blame auth/token_verifier.go | grep -e "k.Mutex.Lock()" -e "defer k.Mutex.Unlock()"
    f3174984 auth/crypto.go         (Hiranya Jayathilaka 2017-05-18 13:45:30 -0700  67)     k.Mutex.Lock()
    f3174984 auth/crypto.go         (Hiranya Jayathilaka 2017-05-18 13:45:30 -0700  68)     defer k.Mutex.Unlock()

2. GitHubの対象レポのプルリクエストをHash値で検索

実装の理由は?

  • プルリクエストのコメントにありました

  • 複数のスレッドが共有している公開鍵のキャッシュを同時に更新するのを防ぐためにmutexを導入したみたいです。

Introduced a mutex to prevent multiple threads from updating the shared cache concurrently.

  • ソースコードを追うと、公開鍵取得のメソッドは httpKeySource構造体に定義されており、さらにhttpKeySourceはClient構造体に定義されています。SDKを使う際に最初にClientの初期化を行いますが、初期化した同一のClientで公開鍵取得メソッドがconcurrencyに実行されても安全なように実装されているのかなと考察しました。

firebase-admin-sdk-goはgit-flow

firebase-admin-sdk-goはgit-flowで開発されているようです。

そのため、masterのソースコードのcommitのHash値がプルリクエストがマージされた時のHash値と同じで、プルリクエストが見つけやすかったです。

おまけ(nodejsの実装)

Node.jsのSDKの同じ箇所の実装についても少し覗いてみました。

Node.jsを書いたことはありませんが、見る限り排他制御はしていないように見えます。

Node.jsはシングルスレッド推奨だから排他制御しなくていいのか・・・など思い、言語の特性の違いが垣間見えて面白いですね。

まとめ

  • オープンソース文化いいですね。今回の例だとfirebaseの中の人が書いたソースコードや、そのプルリクを見ることができています(もしかしたら中の人じゃないかもしれませんが)。エンジニアとしては当たり前な文化ですが、ブラックボックス化されている業界が多いなか改めて良い文化だと思いました。
  • シタテルはRailsを使用しているので、RubyのSDKがあればFirebase Auth導入してみたかったのですが残念です。

2018年11月14日水曜日

リリース作業を少し楽にした開発(Slack+Lambda+Golang)

11月 14, 2018

こんにちは、シタテルの鶴巻です。

devops&インフラ担当です。

シタテルのプロダクトのリリース作業を少し楽にするために、
Slack+Lambda+Golangを用いて開発した話をします。

リリースの流れ

シタテルでは、GitフローやGitHubフローを採用しています。

また、本番環境へのデプロイは、以下の流れで実施しています。

  1. developブランチからreleaseブランチを作成
  2. ステージング環境でreleaseブランチをデプロイ&動作確認
  3. 本番環境にデプロイ

ちなみに、1.でreleaseブランチを作成すると、CircleCIによって2.のステージング環境へのデプロイは自動で行われます。

地味に面倒なリリース作業

ステージング環境へのデプロイは自動化されているとはいえ、小さな手作業が合間に発生し、地味に面倒でした。

具体的には以下のような作業です。

  • developブランチからreleaseブランチの作成
    • ローカルPCでdevelopブランチを最新の状態にpullして、releaseブランチ作成してGitHubにpush
  • releaseブランチのプルリクエスト作成
    • GitHubのUIをポチポチ
  • リリースタグの作成
    • 前回のリリース以降でマージされたプルリクエストを見て、リリース内容を記述

手作業はSlackからコマンドを叩くだけにしました

Slackから以下のコマンドを叩くだけで、地味に面倒くさかった作業を自動で行うようにしました。

/release <repository> <release branch>

実際には、以下の内容を実施するGolangプログラムをLambdaで実行しています。

  • 対象のリポジトリをクローンし、developブランチからreleaseブランチを作成し、GitHubにpush
  • releaseブランチのプルリクエストを作成
  • 前回のリリース以降にマージされたプルリクエストのタイトルのリストを記述したリリースタグのドラフトを作成

構成

Slack -> API Gateway -> Lambda01(同期) -> Lambda02(非同期) -> GitHub
  1. Slackからコマンドを叩くと、Lambda01にリクエストを送信します。
  2. Lambda01は、Lambda02に対象レポジトリとブランチ名を付与してリクエストを送信します。
  3. Lambda02で作業を実行します。

Lambdaを2つ使用している理由

Slackはリクエスト送信後3秒でタイムアウトするためです。

実行したいプログラムは3秒以上かかるため、実際の作業は非同期で実施する必要がありました。

そのため、Lambda01はLambda02に処理を投げて、Slackに200を返し、実際の処理はLambda02で実施します。

Lambda01はLambda02を非同期実行で呼び出すので、リクエストをキューに入れた後は、Lambda01は関数を終了することができます。

Lambdaの呼び出しタイプ(同期・非同期)

Lambdaは、呼び出しタイプ(InvocationType)を指定することで、同期実行や非同期実行を選択することができます。

例:InvocationTypeで"Event"を指定することで、非同期実行でLambdaを呼び出せます。

input := &lambda.InvokeInput{
FunctionName: aws.String("xxxx"),
Payload: jsonBytes,
InvocationType: aws.String("Event"),
}

※同期実行は"RequestResponse"を指定します

今回使用したGitHub操作のためのGoライブラリ

その他ハマったポイント

  • APIGatewayの統合リクエストのマッピングテンプレートを使いこなせず。デバッグのやり方もわからず、使うのを諦めました。

まとめ

  • 少しの手間が自動化されるだけで、すごく楽になります。これからももっとやっていきたいです。
  • GitHub操作のライブラリはとても便利でした。
  • シタテルのサービスはRails + vue.jsが主ですが、自分の好きなGo言語を使えたので楽しかったです。

参考

slack slash-command

Lambda 呼び出しタイプ