emahiro/b.log

Drastically Repeat Yourself !!!!

DeNA を退職しました

※ 個人の記録です。

本日 2/28 が現職 DeNA の最終出社日でした。

単体で3年弱、iemo の時代を含めると3年半お世話になりました。

入社当時、何の武器も持ってなかった自分にエンジニアとしての武器を授けてくれた本当にいい会社でした。

次の職場は東京タワーの近くです。
引き続き Gopher をやっていく予定です。
GCP(App Engine)とはしばらくお別れです。

また働き始めたタイミングで何か書きます。

障害対応時のTODOリストをまとめてみる

Overview

先日久しぶりの障害対応を行ったときに、色々鈍っていたなと痛感したのと、いくつか改めて気づいたこともあるのでその振り返りもかねての記録です。
なおTODOリストとエントリ名はしてますが、実態はかなりポエムに近いし、勢いで書いたところがほとんどなので拙い文章であることや、ところどころ話が逸れてること自体はご容赦ください。

動機

この記録を書こうと思った動機は、障害対応(インシデント対応)に関しては、危機管理側に立った情報はいくつか出揃ってきてるなという印象を持っている一方で、実際に現場で障害対応するときの手順だったり、自分自身まず何からやってるっけ?どういう順序で情報を出してるっけ?と言った実業務に即した内容はあまりないので、いい機会なのでいくつかまとめて、記録として残しておこうと思ったことです。
以下でリストとして出して、それぞれどんなことしてるっけ?って言うのをまとめてますが、もしかしたら今後増えていくかもしれません。

障害が起きたときにやることリスト

  • 障害を報告する。
    • エスカレーション。
    • 切り戻し。
      • 直近のリリース影響などが疑われる場合。
  • 一次対応
    • ケースバイケース。
    • だいたい原因調査と並行して行う。
  • 原因調査。
    • 事象の把握。
      • 期待する動作の確認。
      • 期待する動作になっていないときに、どこまでが正常に動作していて、どこからが異常なのかを把握する。
  • 原因の切り分け。
    • どこに原因があったのか?
    • コードを追って原因を特定する。
  • 障害報告書を書く。
    • 事実を集めること。
      • 書くことリスト
        • 何が起きたか?
        • どれくらい継続したか?
        • どれくらいの影響があったか?
        • 何が原因か?
          • 対応はどうするのか?(暫定・恒久)
          • 人に目を向けない。仕組みに目を向ける。
    • エンジニア以外の人が読む文章であることを意識する。
      • 専門用語は正しく伝える最低限でなるべくツールの名前などは一般化する(もちろん限界はある)。
  • 修正対応
    • 原因が特定して、修正できるのであれば修正してしまう。
    • 時間がかかるようであれば別のタスクとして切り出す。

障害を報告する

まずやるべきはこれだと思っていますが、この初動が一番難しいと思います。
というのもエンジニアなら、何かしら意図しない挙動があったときに、この振る舞いは自分が混入したものなのかどうかが気になってしまいますし、何より、障害を発生させてしまった場合に、そう報告するのって実際かなりストレスがあるなと感じてます。

ただし、そんな自分の気持ちなんかユーザーには一ミリも関係ないので、とりあえず「ヤバみ」を感じたら「障害です」と上に投げちゃう方がいいのかなと考えてます。
もちろん、ちゃんと調査したら違った、みたいな話は出てくると思いますし、そうなったらそうなったらで「拙速だったすまん」と謝ればいいかと思います。その辺の見極めって本当に難しいと思うので。自分もできてないし、「障害か?」の裏をとってる間にも状況は悪化してるかもしれない、と言うことを考えると迷ったら安全に倒す、くらいのメンタリティが大事なのではないかと思っています。
そしてこの辺の初動のスピード感については、すぐにエスカレーションが上がってくるようなチームになってることと密接に関わっていそうなので、よく言われる「組織における心理的安全性」や「人ではなく仕組みに目を向ける」といったことが体現してる組織やチームは障害対応の側面から見ても強いと思います。

話が逸れましたが、この障害かどうかの報告については、とりあえず15分程度ガッと調べて裏が取れれば、そう報告するし、迷ってもとりあえず「障害かどうかの判断に迷ってる」と言う前提と一緒に上に投げちゃうのがいいのかなと思います。
調べる時には自分は大体開発環境でも同様の事象が発生するのか?(再現手順踏まえて ※)と言うところを調べてます。
これは商用環境と開発環境で同じコードが動いてることが前提だったりするので、日頃から同じコードになるようにしておくことはこう言う時にきっと活きます。
実際のところ開発環境の検証で見つかると儲けもんなのですが、開発環境では再現しないみたいなこともしばしばあるので、その場合は再現できていない前提で、上述したように報告します。

次に直帰何かリリースがあったかを確認します。
リリースがある場合は、そのリリースによる変更が原因であることが疑われるので、まずはユーザー影響を最小限に抑えるために切り戻します。トラフィックの向き先を1つ前のバージョンに戻すか、そう言う仕組みになっていない場合は、リリースのコミットごと revert して再デプロイするのか。とはいえ、リリースによってデータに変更が生じてる場合などは...どうするんですしょうね...そのオペレーションは経験がないのでよくわからないです。
生じている変更が切り戻してもサービスに影響があるかないかをまずは確認しますし、それで影響が出る、となったらそれこそ、切り戻せないので、原因を早急に突き止めて爆速でパッチ当てる...とかになるんですかね...。 これもやったことはないので自信ないです。

※ 再現手順ってまじで大事なので、もしおかしな挙動を上げる時は再現手順とセットにして欲しい、と言うのはエンジニアとしては密かな願いでもあります。何でもかんでもバグ!障害だ!として上げられたら心折れます。

一次対応

ユーザー影響をひとまず止めるために最初にどう言う対応をするのかエスカレーションしたタイミングで何かしら決定があるはずなので、その作業を先に行います。 エンジニアで現場で実際に作業をする場合を考えると、上述した切り戻し作業を行うことになると思います。
このタイミングではビジネス側で何かしら決まって、すぐに次の原因調査に移行できることもあるでしょうし、ここは原因調査と並行で進むものですし、ケースバイケースだとは思います。

原因調査

何かしら期待しない動作がある、そしてユーザーに不利益がある、というような事象に遭遇したときに、どうしてその状況になってるのかを調査を行います。
調査を行うにあたっては手順は主に「事象の把握」と「原因の切り分け」の2つの工程を経て、原因を突き止めるまでに至ると考えています。
そしてこの原因調査のタイミングで最初にやってはいけないと思ってるのは コードを最初から追いかけること だと思います。
もちろん、障害の発生したポイントがすでにドメイン知識を持っている範囲で、かつ障害の発生に関連した変更に関わっていた場合などはすぐに原因を特定してパッチを当てたり、迅速な報告ができる、などもあるとは思います。
しかし、ここでは、自分が深く知らないドメインの領域や、触ったこともないマイクロサービスで発生した障害を想定します。
自分の知らないドメインであれ、サービスに関わっている以上は当事者として関わっていかないといけない、しそう言うところの障害が結局多い。と思っているからです。

話を戻します。
まず最初のプロセスとして、事象の把握で何をしてるかですが、ここではまずどう言う症状が起きてるのか?から確認していきます。そもそもどういうことを期待していて、今そうなっていないのか、がわからないと調べようも対応のしようもありません。
そのため、まずはプロダクトの振る舞いとして「何を期待するのか?」を確認するのが大事です。
ときには、この段階で「そんな風には作ってない」と言う想定外の使われ方、みたいなケースがサッと出てきて、次のステップに進めることもあります。これができればラッキーです。あとはその裏を取るためにコードを追う作業にいけるので。
次に期待結果を得るためにはシステムがどう言う風に動く必要があるのかの見通しを立てます。ここまでできてから実際に現状のシステムに置いて「どこまでが正常に動作していて、どこからが期待した動作をしていないのか?」の切り分け作業を進めながら原因の切り分けと特定を行なっています。
実際に原因を調べるときは データの状態やり、リクエストログなりを手がかりにして、1つ1つ切り分けを行なっていくと思います。
そしてこの切り分けを行うときの注意点としては、意図した振る舞いになっていないからといって、すぐにUI側の実装を疑ったり、みたいなことをしないことです。(僕はこの間やりました。完全にミスでした。)
UIはAPIから返したデータを描画するように作ってあるかもしれないし、そうした場合にはAPIのレスポンスが意図した振る舞いをするためのレスポンスになっていない、と言うことになります。

ここで初めてコードを追いかけることになると思います。
結局ここで伝えたかったのは、すぐにコードを追うな。コードを追っかけ始めるのは一番最後、と言うことをルールとして決めるだけでも障害対応の進め方は変わってくるのではないかな?ということでした。

障害報告書を書く

原因調査と並行して障害報告を書きます。 ここでは、原因調査でわかっている事実を書きます。逆を言えば、原因が見つかっていない場合は 調査中。判明次第更新します と書いて「...に原因がありそうです」みたいな憶測は書きません。 この文章はエンジニア以外の人向けに書かないといけないので、専門用語やツール名は正確な情報を記述するための最低限に留めます。

書く内容としては以下かなと思います。あくまで僕の考えるところが大きいので、チームごとに内容は変わってしかるべきかと思います。

  • 障害内容
    • 何が起きたか?
  • 影響範囲
    • どれくらいのユーザーに影響があったか?
  • 障害発生時刻
    • どれくらいの時間続いたのか?
    • すでに収束してるのか?現在も継続中なのか?
  • 対応について -1
  • 原因
    • なぜ障害が発生したのか?
  • 対応について - 2
    • 恒久対応
      • 原因となった事象に対してどう言う対応を取るのか?
        • 多くはコードを修正することになると思います。
        • または既存の何かしらの仕組みを変更することになると思います。

繰り返しですが、ここでは 事実のみ を記載することがポイントです。そして、原因は Blemeless である必要があります。原因は人に求めず、仕組みに求めます。

修正対応

原因を特定できた、修正できるのであれば修正しましょう。この修正をどのタイミングでリリースするかは、すぐの時もあるでしょうし、翌営業日、と言うようなこともあると思います。障害が発生したのがピークタイムの時間の場合には、安全を期して翌営業日リリースと言うパターンが多いと思います。
すぐには改善できない原因もあると思いますし、その場合は別途イシュー化して対応の優先順位を意思決定層と相談することになると思います。
まぁ優先度周りについては現場に居る人間としては、あまり意識するところではないかもしれません。

振り返り(と言うかポエム)

大なり小なり、障害って日々発生してるものだと思っていて、ユーザーに対して期待する価値を提供できていない、もしくはサービスが壊れていると言う状態がままある、サービスに関わるエンジニアとしては常に向かい合わないといけないことだと思います。そんなしょっちゅう起きてても、オイオイ...って思いますが、成長して、常に何かしらの変更を加えているサービスでは、障害は日々発生する可能性があります。

正直このエントリで書いたことが自分で「ちゃんと」できてるかと言われると、できてないことの方が多いです。
障害対応ってまじでストレスのかかる仕事だし、起こした当人が一番頭混乱してるので、二次災害をどう起こさないか、みたいなところで最低限の冷静さを保ちながらチームをうまくまとめるのは実はめちゃくちゃハイスキルな対応です。
こればっかりは完全に場数勝負、経験が物を言う世界なのかなと。。。
昔ある人にエンジニアの戦闘力をどこで測ってます?と言う質問をしたときに「障害対応を行ってきた経験があること」と言うニュアンスのことを言っていて、改めてなるほどと思わされます。

そして、いくつかエントリの中で触れたリストの中に「...と並行して」と書きましたが、この言葉の指す通り障害対応はチームで行う作業です。
誰かが原因調査をしてるときに並行して、エスカレーションする内容をまとめたり、それを障害報告の文章に起こしたり、と言うことは協同して行います。

最後に、原因の切り分けプロセスにおいて、技術に詳しいことはそれ自体がアドバンテージになります。
例えばバックエンドのエンジニアが、サービスで使われてるフロントエンドの技術を少しでも触ったり、理屈をわかっていれば原因の切り分けの速度は上がります。
土日に勉強しろ!とは思いませんし、土日に勉強してる人が偉いとも、評価されるべきとも考えてはいませんが、日頃の技術的な研鑽はいざというときに間違いなく活きる、と言うことは事実として存在すると思います。

例えば Vue のコンポーネントにおいて、props の受け渡しがどう行われるのかを知ってると知らないとでは、APIから正しいデータを返してるかもしれないときに UI 側の原因だと突き止めることはできません。
それを作った人に聞ける、と言う恵まれた環境であればそれはそれでラッキーですが、現実はすでに作った人がいない、なんてことはザラにあります。
ドメインの知識はないけど、技術を理解していれば作った人ほど速くとは行かないまでも、切り分けを行いながら原因にたどり着ける可能性は増します。

正しく技術を理解していること、そしてそれを使えるように日頃から素振りをしてるとそれが思っていないときに日の目を見たりするので、自分ももう少し素振りの時間は取りたいなと思いました。本当は業務でそれができるとベストではありますが。

余談: カオスエンジニアリング

カオスエンジニアリングとは、雑に言うと障害対応訓練(雑すぎて刺されそうですが)で、マイクロサービスアーキテクチャのような小さなサービスが複数で協調して1つのシステムを作り上げてるケースの障害対応だったり、サービスの安定運用のために、それこそ日頃の素振りとして行われる類のものなのですが、例えば、仕様の漏れとか想定外の使われた方を原因とした障害って実際訓練しようがなくないですかね?なんてことを考えました。

そもそも想定してない使われ方をできてしまうことが問題ではありますが、訓練項目に入らないようなことが発生したら、訓練でやったこと全ては活きることなく、その時々で最善を尽くす他ないのかなと。 もちろん、訓練の中でログの見方だったり、APIの仕様だったりをシュッと調べられて原因特定のスピードは改善していくと思うので訓練や素振りが全て無駄にはなりませんが、訓練項目書を作る、と言うか仕様を把握するって難しいなと....。

と言う単なるぼやきでした。

まとめ

まとまってないけど、障害対応はエンジニアとして成長するいい機会だと思います。

ドメイン名からIPアドレスを調べる

Linux を触っていた頃に覚えてたはずの dig コマンドをすっかり忘れてました。

$ dig www.google.com

; <<>> DiG 9.10.6 <<>> www.google.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 54506
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.google.com.          IN  A

;; ANSWER SECTION:
www.google.com.       66  IN  A   216.58.197.164

;; Query time: 32 msec
;; SERVER: 2001:268:fd07:4::1#53(2001:268:fd07:4::1)
;; WHEN: Tue Feb 04 02:01:03 JST 2020
;; MSG SIZE  rcvd: 59

こういうことができるやつね。 インフラ触れなくなって久しいので遠い過去の記憶になっていました。

CLOUD SDK で python2 系が切られて aetest が落ちるようになった問題

Overview

Update to Pyhon3 にて Goole Cloud SDKを同梱した Docker Image 本体にて Python2 のサポートが切られたために、Python2 系でのみ動作する App Engine を起動するコマンドの dev_appserver.py が動作せず、本家の Google Cloud SDK - Dockerfile を使用して aetest を走らせてる場合(CI上などで)は、App Engine のインスタンスの起動に失敗して CI が落ちる、という挙動を確認しました。

対応方法

  1. 上記の変更が入る前の dockerfile image を使って自前でビルドしたイメージを使う。
  2. dev_appserver.py を起動するときに CLOUDSDK_PYTHON=pyhon2.7 を指定する。

調査方法

調査した方法をログでまとめました。

エラーを観測する

Traceback (most recent call last):
     File "/usr/lib/google-cloud-sdk/platform/google_appengine/dev_appserver.py", line 95, in <module>
       assert sys.version_info[0] == 2
   ... unable to find admin server URL

どうやら dev_appserver.py の実行に失敗し、App Engine のインスタンスが起動せず、App Engine の内部で実行するべきテストが落ちている。

なぜ起動に失敗するようになったのか調べる

本家の golang/appengine を見に行くが、何かめぼしい変更が入った様子はない。
そもそも dev_appserver.py の実行に失敗してるので dev_appserver.py を確認する。
sys.version_info[0] == 2 を見る限りどうやら python2 系が docker コンテナの中で使われなくなった模様?という仮説を立てる。

本当か?と思って本家の Docker Image で動作検証する

$ docker pull gcr.io/google.com/cloudsdktool/cloud-sdk:latest
# 古いバージョンを入れるには以下
$ docker run gcr.io/google.com/cloudsdktool/cloud-sdk:260.0.0

# docker のコンテナ内に入る
$ docker run --rm -it -v $PWD gcr.io/google.com/cloudsdktool/cloud-sdk:latest

$  python --version
Python 2.7.16    # 2.7 じゃん...
    
$ which python2.7
/usr/bin/python2.7 # 2.7 も入ってるじゃん....
   
$ which dev_appserver.py
/usr/bin/dev_appserver.py
   
$ python /usr/bin/dev_appserver.py
Traceback (most recent call last):
     File "/usr/lib/google-cloud-sdk/platform/google_appengine/dev_appserver.py", line 95, in <module>
       assert sys.version_info[0] == 2

# python 2系で dev_appserver.py が起動できなくなっている。
# エラーが発生している箇所でバージョンを出力してみる
   
$ vi /usr/lib/google-cloud-sdk/platform/google_appengine/dev_appserver.py 
# sys.version_info[0] を print(sys.version_info[0]) に書き換える
   
$ dev_appserver.py 
3 
# 3 ?! 3?!....さん?????

どうやらコンテナ内部には python2 入っているが、dev_appserver.py を起動するときには python3 系が使われるようである。(なぜ....?)

Overview で記載した差分 (https://github.com/GoogleCloudPlatform/cloud-sdk-docker/pull/188/files) において CLOUDSDK_PYTHON=python3 という環境変数が指定されている。

そのため、 dev_appserver.py では書かれていないが、コマンド実行時には環境変数で指定された python のバージョンを使うのでは?という仮説を立て、環境変数を指定してみて上記の書き換えた dev_appserver.py を再度実行する。

$ CLOUDSDK_PYTHON=python2.7 dev_appserver.py
2  
# なるほど....

うまくいった。
つまり、公式の Docker Image を使って aetest を起動するときには CLOUDSDK_PYTHON=python2.7 を指定するか、Docker のコンテナで環境変数を指定する、もしくは独自イメージを作成するなどして python2 系をコンテナ内で参照するようにする必要がある、ということがわかった。

refs

GCPUG での議論はこの辺

alias で指定して無理やり pyhon2 系を使う方法もあるっぽいけど、個人的には好きではない...

SLI/SLO について

Overview

SRE NEXT のセッション、および懇親会でお話いただいた内容を受けて自分なりに SLI/SLO についてまとめてみる。
※ あくまで自分の解釈ですのでご了承ください。

そもそも SLI / SLO とは?

SRE本 によると以下のように定義される。

  • SLI ... Service Level Indicator

    • サービスを維持するための指標。指標なので具体的な数値ではない
  • SLO ... Service Level Object

    • SLI で計測される具体的なターゲット値やターゲット値の範囲。SLO が具体的な数値。
      • SLI <= ターゲット値(SLO)
      • 下限(SLO)<= SLI <= 上限 (SLO)

SLO と Error Budget

原則として SLO は 100% にしてはいけない
100% を目指すのは好ましくない という表現も見かけたことがあるが、SRE NEXT の懇親会で聞いた話の中には、100% にしてはいけない、という表現を使っていたと記憶している。

これはなぜなのかということを考えてみた。
まず サービスの信頼性 100% - SLO = Error Budget という式が成り立つことをまず抑える必要がある。
そして、この Error Budget について「予算」という表現をしていることがポイントなのではないかなと個人的には考えるようになった。

予算は予算だし、これは使いすぎたらもちろんアウトだけど、余らせることも好ましいことではない、というのが予算の考え方における一般的な解釈だと思う。

SLO と Error Budget についても同様のアプローチで考えることができ、SLO を下回らない範囲で色々なチャレンジをすることをSRE(ここはSREに止まらずプロダクトの開発に関わるステークホルダーに対して)に課しているという捉え方ができる。予算なんだから十分に使い切らなければならない。

そう考えると SLO はこれを下回らなければ何をしてもいい、という「余裕」を持つこと と捉えることができる。
(ymotongpoo san のセッションでも「余裕」を持つこと、という話がされていたと思う。)

SLI/SLO は「どう失敗すべきか、そしてそこから何を学ぶか」という考え方によって生み出された指標とも言えそう。

Google の SRE は予算を使い切らない場合は「本当に仕事をしてるの?」ということを問われるという話もあった。
予算を余らせている = フィーチャーや改善のチャレンジをしていないと見なされるし、そのように評価される。もし予算を使い切っていなければ、そのプロダクトから SRE の工数を削られてしまうこともあるらしい。
SLI/SLO という指標と数字が実際の業務のあり方を定義している具体的なお話でこれを聞いた時には、月並みな感想だけど「Googleってやっぱりすごい...」と感じてしまった...。

まとめ

SLI/SLO、言葉だけは知っていたし、その定義や指し示すところも理解していたつもりだったけど、突き詰めて考えると、その指標が自身の行動や業務を規定するある意味強烈な指標であり、策定するにも運用するにも覚悟が求められる指標だな、ということを考えたので備忘録で残しておく。

SRE NEXT 2020 に参加してきました

概要

SRE NEXT 2020に参加してきたのでそのまとめです。

今回 SRE NEXT に参加した背景ですが、自分は専業の SRE ではなく、さらにはインフラ経験も特にない、普段の業務でもアプリケーションを開発するエンジニアです。
ただ、普段の業務で運用がメインになる中で SRE っぽい?業務にも若干関わるようになりつつある一方、手探りで色々やってるので、何かしら指針となるプラクティスや現場のノウハウを知りたいと思ったのがきっかけです。

参加したセッション

※ 参加した各セッションのスライドはすでに Twitter 上に上がってるものもありますのでこのエントリでは特にリンク等は記載してません。

早速スライド一覧をまとめてくれてる方がいらっしゃって感謝です。

qiita.com

  • [A1] 40000 コンテナを動かす SRE チームに至るまでの道
  • [A2] パフォーマンスを最大化するための SRE のオンボーディング事例
  • [A3] freee のエンジニアは障害から何を学び、どう改善しているのか?
  • [C4] SLO Review

~ ご飯休憩(何も食べずにきたので...) ~

  • [A7] サイト信頼性エンジニアリングの原則
  • [A8] Webサービスを1日10回デプロイするための取り組み
  • [A9] パネルディスカッション

まとめてみて気づきましたが、ほぼ Room A にしかいませんでした笑

感想

いいカンファレンスでした。
参加したセッションはどれもよかったですが、特にメルペイの SRE のオンボーディング事例の話は同じ悩み(オンボーディングされる側として)を持っていたので、何度もうなづきながらセッションを聞いていました。
SRE に限らず、新しい環境に置いて「ドメイン知識」と「コミュニケーション」のキャッチアップは実は個人差がある、という課題感は自分も持っていたので、それをチームとしてサポートしていく改善をしていくことはとても良い事例でした。

特にこの業務のオンボーディングチャンネルなどは、すぐにでも実践できるようコンテンツだなと思ったりしました。
懇親会でスピーカーの tkuchiki san と少しお話しさせてもらいましたが、この辺の施策の効果はこれから計測していくそうです。
ちなみにワークエンゲージメントのお話しもしていただき、少し興味が出たので、ざっと勉強してみようと思いました。

懇親会では他にもパネルディスカッションのパネラーだった tsekine san に Google のときの SRE のあれこれを質問させていただく時間を偶然にも手に入れて、SRE 本でも語られている Google の中の実態はどうなのか?っということを色々お話しさせてもらいました。
SRE 本はやはりプラクティスとしては体系的にまとまっていますが、Google でもあの本の内容に載ってることを全て実践できているわけでもないそうです。
とはいえ、実際の Google の現場でのオンコール当番制の運用方法や、チケットの管理方法、プライオリティーの付け方やチケットのアサインの仕方、そしてトリアージの仕方、Blemeless post mortem のお話などいくつか実践に活かせそうなエッセンスがありました。
特に SLI/SLO を決めないと何も始まらないな、ということはお話をさせていただく中で何度も痛感し、やはり議論の中心にすえるべき指標を先に定義するこのは大事だと思いました。どう決めていいか悩んでる場合でもなさそうです。

ちなみに余談ですが、SRE 本は Google の書いた同人誌みたいな感じで、担当者が各々別々に寄稿してるとのことで、中にいても知らない内容があったそうです。そのため、パネルディスカッションでも話されていた「SRE 本で書かれてるプラクティスのつまみ食い」は本ができた背景から考えてもアリっぽいということを思いました。
僕もこの本は読み進めながら、どうも話が繋がらないなーみたいなことは感じてはいました。

この他にも懇親会で色々な方とお話しさせていただき、楽しい会でした。

運営の皆さん、スピーカーの皆さん、会場でお話しさせていただいた皆さん、本当にありがとうございました。

Go をバージョンごとにコマンドとして入れる

こういうことができる。便利。

$ go get golang.org/dl/go1.X.X
$ go1.X.X download
$ go1.X.X version
go version go1.X.X linux/amd64

公式のインストールページにも記載してある。
https://golang.org/doc/install#extra_versions

僕の考える net/http の定数の使いどころ

Overview

Go の net/httpパッケージ には HTTP に関連する定数が予め定義されており、HTTP クライアントを実装するときなどに自前で定義したり、GET200 といった定数を実装しなくてもいいように、標準の net/http がデザインされています。
こういう実装側で毎回用意してしまいそうな定数すら、Go は標準ライブラリで用意してくれているので、基本的にはこの定数を実装でも使っていきたいところですが、実際どういう時に使うといいのか、逆に使わないべきなのか、という点について自分なりの考えをまとめてみました。

TL;DR

  • HTTP に関係する定数は自前で定義せずに net/http で予め定義されてる定数を使おう。
  • レスポンスコードを比較するケースでは、HTTP レスポンスコードの定数よりも数字の方が可読性は高いかもね。

net/http の定数

定数一覧は net/http の Godoc を見ればわかります。

検討 - net/http の定数の使いどころ

使うべきケース

1. HTTP リクエストを作成するとき

以下のように使います。

req, err := http.NewRequest(http.MethodGet, "https://emahiro.dev", nil)

これは

req, err := http.NewRequest("GET", "https://emahiro.dev", nil)

と書いても同じですが、毎回 "GET" 書いてるとそのうち定数にしたくなる時がくると思います。
その時に定数にするくらいなら最初から net/http で定義されてる定数を使っておきましょう。

2. 正常系をチェックするとき

以下のような正常系チェックの時は、http.StatusOK を書いた方が正常系と異常系のチェックを示すという点で、直感的にはわかりやすいかと思います。

resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

if resp.StatusCode != http.StatusOK {
   // 異常系のハンドリング
}

※ 一方で異常系で 400系, 500系の場合分けを行うケースでは、数字の方が比較を直感的に理解しやすいと考えたので後述の「使うべきでないケース」に追記しました。

3. HTTP のステータスコードを返すとき

これは言わずもがなかと思います。ステータスコードを指定して返すときは net/http に定数を使いましょう。

func(w http.ResponseWriter, r *http.Request){
    w.WriteHeader(http.StatusInternalServerError)
    w.Write([]byte(`hello`))
}

どのステータスコードを返すのか直感的にわかります。

4. HTTPのステータスメッセージを返すとき

これはすべき、というレベルではないのですが、HTTP のステータスコードに紐づくメッセージを返すときに Go の net/http package には StatusText というメソッドが用意されており、ステータスコードを渡すとそれにひもづくステータスコードのメッセージを返してくれるので便利です。

http.StatusText(http.StausOK) 
// Output: OK

http.StatusText(http.StatusInternalServerError) 
// Outout: Internal Server Error 

使うべきでないケース

1. レスポンスコードを比較して正常/異常系をハンドリングするとき

以下のようなケースでは数字のまま比較した方が直感的かもしれません。

resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

if 400 <= resp.StatusCode && resp.StatusCode < 500 {
   // 400系のハンドリング
}

if 500 <= resp.StatusCode {
   // 500系のハンドリング
}

以下のように書いても同じですが、

resp, err := client.Do(req)
if err != nil {
    log.Fatal(err)
}
defer resp.Body.Close()

if http.StatusBadRequest <= resp.StatusCode && resp.StatusCode < http.StatusIntenalServerError {
   // 400系のハンドリング
}

if http.StatusInternalServerError <= resp.StatusCode {
   // 500系のハンドリング
}

http.StatusBadRequest = 400 だなと頭の中で一度変換が入る、かつこのケースでは範囲で比較をしたいのであって、ステータスコード単体のチェックを行いたいわけではないので、数字のまま比較した方がより直感的な表現になると考えています。

まとめ

Go のコードを書いてて net/http 内に HTTP 関連のステータスがあらかじめ定義されてることを知った時はとても便利で多用しがちでしたが、数字の比較など逆に直感的でないケースもあるので、どういう指針でコードを書いてるのかを備忘録として記載しました。

200 記事継続の振り返りとこれから

この記事がちょうど 200 記事目です。

100 記事に到達してからも、ゆるゆると続けていたらいつの間にか 200 記事をインターネット上に放流してました。

ちなみに、100 記事目当時の投稿は以下

ema-hiro.hatenablog.com

アクセス先ランキング

全エントリのなかで、この振り返りを書いてる時点での流入(アクセス)の多いエントリ TOP5 は以下になります。

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

一番アクセスが多いのは多いのは webpack × html のエントリなのが意外です。世の中の需要がなんとなく推し量れる気がします。
書いた本人がいうのもアレですが、すでに古くなっている内容があるのであまり参考にしないほうがいいと思います。
検索に引っかかってしまうから仕方ないのかもしれませんが...

書いた本人としてイチオシなのは Go を描き始めた当時に書いた Go の template/html の値評価のチートシートのエントリです。
自分向けに書いたつもりが、マークアップエンジニアの方からも「参考にしてる」とフィードバックいただきましたし、これはまだまだ使えるエントリだと思っています。

振り返り

KPT 方式で振り返ってみます。

Keep

  • 継続してアウトプットできていること。
  • 検索エンジンでも引っかかり、それなりに見られるようになってきたこと。
  • ブログ経由で連絡をもらうようになったこと。

継続してアウトプットできていること

100 記事継続した当初の目標の1つであった「無理しない」ということをテーマにゆるゆるとアウトプットを続け、結果として習慣化していることはよかったです。
また、業務の中でも「これブログに書けそうだな」という観点で手順なり、ハマったところなりをメモるようになり、それなりに再現性のあるエントリを書けるようになってきました。

あと継続してるとトンマナのズレとかに気づけるようになってきました。
(まだ、「ですます。」調と「である。」調の混同とか、たまにやりがちですが。)

検索エンジンでも引っかかり、それなりに見られるようになってきたこと

社内の人にも補足されつつあり、無理せず、雑な内容でも書くことを大前提としつつも、変なことを書けないという妙なプレッシャーに悩まされています。
それでも見てもらえるのは嬉しいので、継続するモチベーションになっています。

ブログ経由で連絡をもらうようになったこと

これはまぁその通りですね。
上記とも被りますが「ブログ拝見しました」と言ってもらえるのは素直に嬉しいし、そういうところの話は聞いてみたくなります。

Problem

Problem というほどのものもないですが、300 エントリ目指して改善したいポイントをあげてみます。

  • エントリの題材の幅が狭い。
  • 章立て力が低い。
  • 調べてみた系・ハマった系が多い。
  • 文才がない。

エントリの題材の幅が狭い

業務に関連する技術の備忘録という側面があるのでどうしても Go, GCP に寄りがちでした。
この2つを軸にしておくことは変わりませんが、ちょうどこの三連休で Firebase にも入門してみたので、もう少し題材とする技術要素の幅は広げてみてもいいかもしれないかなと思っています。

章立て力が低い

いい感じの章立てがまだできないので、文章の構造化力が足りていないのかなと思います。

調べてみた系・ハマった系が多い

全然いいし、こういうのをどんどん書いていきたい所存なんですけど、いやそれは README 読めばわかるやん、みたいなこともあるので、単なる調べた系のエントリに閉じないオリジナリティーを出していきたいと思います。

文才がない

ないものはないのでしょうがない。

Try

300 記事に向け「これから」チャレンジしてみたいことを書いてみます。

  • 引き続きそれなりのペースで更新する。
  • 雑に書く、無理をしないことを大前提としつつ、質もそれなりに意識して書く。
  • いい感じに文章を構造化して書く。

まとめ

2017.4 頃に、このはてなブログを本格的に運用し始めたので、3年弱くらいで 200 投稿した計算になります。
始めた当初は 200 個もの拙い文章ををインターネット上に放流するなんて考えてもいませんでした。

質にもそれなりに目を向けつつ、引き続き「無理をしない」「続けることが目標」という意識低めのスタンスは変えずに 300 記事目指します。

ポートフォリオサイトを作成 -> 公開するまでにやったこと

f:id:ema_hiro:20200113023005p:plain

Overview

Nuxt + Firebase でポートフォリオサイトを作って公開するまでの過程で行なったことをまとめます。
(まだまだやらないといけないことはありますが、とりあえず最初の一歩でやったことをまとめるところまで)

なお作成したポートフォリオサイトはこちら -> emahiro.dev

やったこと

SNS の icon を手に入れる

自分へのコンタクト方法に SNS アイコンを使うケースが多いと思います。
流石にインターネットからアイコンを拾ってきて、画像として表示するみたいな過去の自分がやっていたような道は通りなくなかったので、アイコンを無償で使える何かがないかを探して、今回は Font Awesome というサービスの無償プランを使いました。

ブランドのアイコン一覧 を軽く眺めたところ、欲しいサービスのアイコンは網羅されてそうだったので、今の所無償プランで問題なさそうです。

今回は無償プランで使える SolidBrands をインストールしました。

Nuxt に Font Awesome を導入する

Nuxt(Vue) への導入方法については こちら を参照してください。Font Awesome 専用のツールのインストールが必要です。

インストール手順は こちら

npm i --save @fortawesome/fontawesome-svg-core @fortawesome/vue-fontawesome @fortawesome/free-solid-svg-icons @fortawesome/free-brands-svg-icons @fortawesome/free-regular-svg-icons

Nuxt での使用手順は こちら

ハマったところ

Font Awesome を使えるようにするには plugins 配下に fontawesome.js というファイルを作成し、Nuxt での導入方法に記載してある Component の設定を追記する必要がありました。

fontawesome.js で Font Awesomeコンポーネントをグローバルに設定したので、使いたいコンポーネントFontAwesomeIcon を指定することで <font-awesome-icon :icon="['fab', 'twitter-square']" /> で Font Awesome のアイコンが使えるようになります。

favicon を変える

Nuxt のアイコンがそのまま使われてなのでとりあえず適当な顔文字に変更しました。

そのうちちゃんとしたやつ作りたいです。
Figma とか使って。

Cloud Functions を使ってはてなブログRSS を取得する

はてなブログRSS を表示させようと考えましたので、Cloud Functions でそのエンドポイントを実装してみます。

Firebase Functions Emulator を使う

公式ドキュメント にローカルのエミュレーターの起動方法について記載してあります。

firebase emulators:start

emulators: Starting emulators: functions, firestore, database, hosting, pubsub
⚠  Your requested "node" version "8" doesn't match your global version "10"
✔  functions: Emulator started at http://localhost:5001
i  firestore: Serving ALL traffic (including WebChannel) on http://localhost:8080
...

起動した状態で local で curl を叩くと結果が返ってきます。

curl -i localhost:5001/emahiro-dev/us-central1/helloWorld

HTTP/1.1 200 OK
x-powered-by: Express
content-type: text/html; charset=utf-8
content-length: 20
etag: W/"14-z3iZXchEt5DVWZKsMncy8Wl4KSQ"
date: Sun, 12 Jan 2020 14:15:14 GMT
connection: close

Hello from Firebase!

Functions の設定の変更

ランタイムを Node 10 に変更し、標準で us-central が指定されてしまう Functions のリージョンを日本リージョンを使用するように設定を変更します。

diff --git a/functions/package.json b/functions/package.json
index 02a2f48..61f7b66 100644
--- a/functions/package.json
+++ b/functions/package.json
@@ -10,7 +10,7 @@
     "logs": "firebase functions:log"
   },
   "engines": {
-    "node": "8"
+    "node": "10"
   },
   "main": "lib/index.js",
   "dependencies": {
diff --git a/functions/src/index.ts b/functions/src/index.ts
index e6e0bb8..fa15f33 100644
--- a/functions/src/index.ts
+++ b/functions/src/index.ts
@@ -3,6 +3,10 @@ import * as functions from "firebase-functions";
 // // Start writing Firebase Functions
 // // https://firebase.google.com/docs/functions/typescript

-export const helloWorld = functions.https.onRequest((request, response) => {
-  response.send("Hello from Firebase!");
-});
+const jp = "asia-northeast1";
+
+export const helloWorld = functions
+  .region(jp) // 追加
+  .https.onRequest((request, response) => {
+    response.send("Hello from Firebase!");
+  });

新しい Functions を定義する

functions/index.ts に新しい関数を定義したら npm run build をします。
( functions/package.json 内に定義されてる build スクリプトの中身は tsc ですが )

Functions の動作確認には上述した Firebase Functions のエミュレーターを使用します。

まとめ

一旦自分が Nuxt + Firebase でポートフォリオサイトを作成するところまでやったことをまとめました。

今後、機能やコンテンツを増やしたときにはその内容を公開していきたいと思います。

See Also

Docs

Entries

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

ema-hiro.hatenablog.com

Nuxt のアプリで HTML の head タグを編集する

Overview

Nuxt のアプリケーションにおいて HTML の head タグを編集する方法を記載します。

config ファイルで head タグを編集する

nuxt.config.jshead property を更新します。

デフォルトで生成される設定ファイルは以下です。

{
  head: {
    title: process.env.npm_package_name || '', 
    meta: [
      { charset: 'utf-8' },
      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
    ],
  },
}

Bootstrap-Vue のレスポンシブ設定をしてみる

レスポンシブ設定をするために Bootstrap-VueResponsive meta tag に記載されてる内容に更新します。

diff --git a/nuxt.config.js b/nuxt.config.js
index 53fe780..fe3be60 100644
--- a/nuxt.config.js
+++ b/nuxt.config.js
@@ -8,7 +8,7 @@ module.exports = {
     title: process.env.npm_package_name || '',
     meta: [
       { charset: 'utf-8' },
-      { name: 'viewport', content: 'width=device-width, initial-scale=1' },
+      { name: 'viewport', content: 'width=device-width, initial-scale=1, shrink-to-fit=no' },
       { hid: 'description', name: 'description', content: process.env.npm_package_description || '' }
     ],
     link: [

See Also

Nuxt のアプリを Firebase Hosting を使って公開する

f:id:ema_hiro:20200112095704p:plain

Overview

Nuxt と Firebase Hosting を使ってサイトを公開したのでその記録について記載します。

Install Nuxt

公式のインストール手順 に則って進めます。

npx create-nuxt-app emahiro.dev

今回自分は以下のような設定にしました。

create-nuxt-app v2.12.0
✨  Generating Nuxt.js project in emahiro.dev
? Project name emahiro.dev
? Project description emahiro's portfolio site
? Author name emahiro
? Choose the package manager Npm
? Choose UI framework Bootstrap Vue
? Choose custom server framework Micro
? Choose Nuxt.js modules (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose linting tools (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Choose test framework Jest
? Choose rendering mode Single Page App

ただ、裏側に Firebase を使っているので必要なかったものもありました。

Build

npm run build
npm run start

# start server at localhost:3000

これで .nuxt 配下にビルドされたアプリケーションが作成され、 localhost:3000 で接続できるようになります。

Nust のアプリを Firebase Hosting で公開する

Firebase の通常設定の流れのまま firebase deploy を行なった場合、デプロイが完了しても Firebase Hosting のアプリのテンプレートが反映されて Nuxt のアプリが反映されませんでした。

原因は単純で、Firebase の通常の設定フローでぽちぽち進めていった場合に、Firebase Hosting で見る先は $FB_PROJECT_ROOT/pulic/index.html になり、 Nuxt の成果物とはディレクトリ( dist )が異なります。 そのため、Nuxt のビルドした成果物を Firebase Hosting で見れるようにする必要があります。

Nuxt の成果物を Firebase Hosting で参照するには Firebase Hosting の参照先を設定時の public から Nuxt の成果物が配置される dist に変更します。

diff --git a/firebase.json b/firebase.json
index 7b0091d..5a0db41 100644
--- a/firebase.json
+++ b/firebase.json
@@ -13,7 +13,7 @@
     ]
   },
   "hosting": {
-    "public": "public",
+    "public": "dist",
     "ignore": [
       "firebase.json",
       "**/.*",

これで再度 firebase deploy を実行すると Nuxt のページが Firebase Hosting で参照されるようになりました。

Firebase をやり始めたぞ

f:id:ema_hiro:20200112003332p:plain

Overview

Firebase を始めるに当たって、Firebase のセットアップからカスタムドメインの設定を行い、簡単な hello world を表示したところまでやったことを記載します。

Get Started Firebase

アカウント登録

Firebase にて「コンソールに移動」を選択します。

コンソールに移動するだけでアカウントは無料プラン( Sparkプラン )に登録されてました。

どのプランがいいのか

Firebase つよつよな令和のシステムアーキテクトより無料でも使えるけど、機能制限を受けるのでさっさと Blaze (従量課金プラン) にしなさいとアドバイスをもらったので従量課金プランに変更しました。

課金制限を設定する

とはいえ従量課金制で運用するのは怖いので課金アラートを設定します。

f:id:ema_hiro:20200112002349p:plain

プロジェクト名の右の「歯車」マークから「使用量と制限」を選択します。

使用量と制限 > 詳細と設定 > 予算とアラート > 「最初の予算を作成」を選択。

予算の範囲を指定できるので

  1. Firebase のどのリソースの予算を制限するのか指定します。
  2. 予算の金額を指定します。
  3. 閾値を設定します。

僕は予算制限をとりあえず ¥1000 に設定して以下のような閾値にしました。

f:id:ema_hiro:20200112002558p:plain

とりあえず実値で閾値を設定しましたが、予測値を指定することも可能です。

最終的には以下のように課金アラート一覧に表示されます。

f:id:ema_hiro:20200112012603p:plain

ドメインと紐付ける

あらかじめ取得しておいたドメインと紐付けます。 僕は Google Domainemahiro.dev というドメインを取得していたのでこちらを使います。

ドメインとの紐付けドキュメントは こちら

ドメインと紐づけるには Firebase Hosting を利用します。

Firebase の設定

Firebase Hosting にアクセスすると、まず最初に設定を始める必要があるので ドキュメント に則って Firebase の設定を進めます。

npm install -g firebase-tools
firebase login
# firebase に登録してる gmail を選択する。
cd $MYPROJECT_ROOT
firebase init

Firestore のロケーションを設定しておらずにエラーになる

プロジェクトにおいて Firebase のどのリソースを使うのかを設定する時に「全て」を選択したために、Cloud FireStore のロケーションを設定されておらずセットアップがコケることがありました。

firebase init

# 略

=== Firestore Setup

Error: Cloud resource location is not set for this project but the operation you are attempting to perform in Cloud Firestore requires it. Please see this documentation for more details: https://firebase.google.com/docs/projects/locations

これを解消するために Cloud Firestore のスタートページでロケーションを設定します。

Filestore のロケーション設定手順

  1. Firestore にアクセスします

f:id:ema_hiro:20200112004042p:plain

  1. ロケーションを選択します。日本は asia-northeast1 or 2 なので、僕は今回は asia-northeast1 (東京リージョン) を選択しました。

f:id:ema_hiro:20200112004150p:plain

これで再度 setup を実行し、最終的に以下のようなファイル群が生成されます。

.firebaserc
database.rules.json
firebase.json
firestore.indexes.json
firestore.rules
functions/
public/
storage.rules

Firebase Hosting へのデプロイ

Firebase のセットアップが完了したので Firebase Hosting へのデプロイを行います。

firebase deploy

ドメインを紐付ける

Firebase のセットアップという横道に逸れましたが、設定が完了して、deploy -> firebase のデフォルト URL でアプリケーションが表示できるところまで確認したので、emahiro.dev の紐付け作業を行います。

Firebase Hosting でドメインを追加する

Firebase Hosting のドメインでカスタムドメインを追加します。

f:id:ema_hiro:20200112004416p:plain

ドメインを追加すると IP アドレスが割り振られます。「表示」から確認できます。

f:id:ema_hiro:20200112005550p:plain

Google Domains でカスタムリソースに設定する

Google Domains の DNS > カスタムリソースレコードの項目で、上記の Firebase にカスタムドメインを追加した時に割り振られた IP アドレスを追加します。

f:id:ema_hiro:20200112005655p:plain

カスタムリソースに追加すると Firebase のドメインの欄に追加したドメインの反映が始まり「保留中」表示に変更になります。
正常に反映されると「接続されています」表示になります。

f:id:ema_hiro:20200112005750p:plain

これで紐付けは完了で、emahiro.dev でアクセスが可能になりました。

その他

Firebase の API キー丸見え問題

デプロイして喜んでたそばから Firebase つよつよおじさんが /__/firebase/init.js というファイルにアクセスしてきて API Key 丸々外部に公開されてることを教えてくれました( いじめられました

なるほどこの辺が公開されちゃってるわけなので、Firestore のセキュリティルール等を完全に理解した上で Firebase を使わないと 簡単におもちゃにされる 脆弱なアプリケーションになってしまうわけですね。

ちなみのこのエンドポイントはアプリケーション側でアクセスしないように制御できるようなものでもないとのこと。
ここで公開されてる環境変数を使うことも普通にあるらしい。ふむふむ。

まとめ

Firebase こと始めができました。
サクサク設定を進めて、簡単に公開までできてしまったので、ほんとすごいツールが出てきたな (小並) といった感じでした。
(前々から聞いてはいましたけど)

See Also

関数を引数に指定して CallBack のように振る舞わせる

Overview

関数を引数に指定してすることで、呼び出し先の関数内にて、特定の条件で指定した関数を実行する (Callback のように振る舞わせる) 実装方法をやってみたのでその記録を記載する。

Log

github.com

Usecase

R/W の競合を発生させないために sync.Mutex をフィールドに持っている struct に対してロック中に別の関数を実行させたいケースなどを考えたい。

例えば、上記のお試し実装にあるような 一度の呼び出しでオンメモリキャッシュが存在すればキャッシュを返し、なければ API の呼び出しを待ってキャッシュに書き込む、みたいなことをしたいときに、引数に関数を当てておいて呼び出し元ではロックをかけず、呼び出し先でかけてるロックを利用する、みたいなケースが考えられると思う。

この他にどんなときに使えるのか、ということはこのエントリ執筆時点では考えていないが、こういういった関数を渡して、呼び出し先でゴニョるみたいな実装方法は覚えていて損はないなと思った。

Boostnote をやめる

概要

Boostnote で作成した cson 形式のノートファイルを markdown 形式に変換する手順です。

モチベーション

お気に入りで使っていたノートアプリである Boostnote のアプリがリニューアル( Boost Note )され、有料アプリになってしまっていたので、これを機会に BoostNote をやめることにしました。
もともと Dropbox 連携だったりが出来て無料である程度データを同期することが出来ていたんですが、リニューアル & 有料化に伴い、今後今まで使っていた Boostnote の方は今後新機能の開発も止まりそうなので、これを機に乗り換えようと思いました。

ただ、これ!と言うノートアプリがあるわけではないので、しばらくは md ファイルを Dropbox に配置したまま、エディタアプリで開く、と言う手段を取ることにしました。

ただ、今まで書いたノートが全て無駄になる、といったことは避けたかったので、cson 形式のファイルを md 形式に変更して退避させないといけないのでその方法を調べたと言う話です。

やり方

weaming/boostnote2md.py を使う

weaming/boostnote2md.py を使うだけでほぼ終わりです。

手順

上記の gist コピペで使います。

# boost_note.json が存在するディレクトリが Boostnote のワーキングディレクトリです。
cd BOOSTNOTE_WORKDIR

echo "" > boostnote2md.py
# weaming/boostnote2md.py をコピペ

python3 boostnote2md.py

output/{ $TagName }/{ $Title }.md
...

同階層に output ディレクトリが作成されてそこに { $Title }.md と言う形式で md ファイルが出力されます。

以上です。