CubicLouve

Spring_MTの技術ブログ

サービスを作る

雑多にまとめておく

MVPとかPocとか

InVisionやFigmaで作る。

それで、プロダクトのコアコンセプトのテスト、仮説検証できるところまでやる。

最初の文化

なぜこのサービスをやるのかを最初からチームで話す

www.ryuzee.com

blog.agile.esm.co.jp

すべてのタイミングで見直すようにする。

最初のチーム

メンバー

少人数でチームを作る。5〜6人くらいがMax。

人を増やして対応するのではなく、作業効率を上げること、やることの取捨選択によってチーム規模を維持する。

リリース後に余裕が出てきたら、改めてメンバー構成を考える。

QAメンバー

QAをメインで担当する人は最初からチームに入れる。

品質をどう作り上げていくかを最初からチームで考える。

テスト観点から仕様の漏れを突っ込む人

CI

最初からやる。 CIのパイプラインを作り、その後に最初のQAをする。

最初のリリース

なんでもいいからQAチームへ受け渡し確認してもらう。

Hello, World! だけ表示される何かだけでもよい

セキュリティ

最初からやる。 専門家がいるなら早めに相談。

評価

目標設定などはチーム全員でやる。 管理方法はなんでもいいけど、チーム全員でやるのが重要。

ユビキタス言語

scrapboxで管理が相性が良さそうと思いつつやってない。

ドメイン

ライブラリとか使わずにPOROとかPOJOみたいなもので作り上げる。

www2.slideshare.net

www2.slideshare.net

モノレポ

最初はまとめておく。

分割はあとで

DB

難しいが、DB分割は最初のほうでしっかりやっておく。

ツール群

お金使って便利なものを積極的に使う

IDEとか。

文化と暗黙知と属人性

属人性は排除していく。

ドキュメントで解決する部分としない部分がある。

解決しない部分として、根底に流れるコンセプトは一回書いただけでは浸透しないとかそういう部分。

これはちゃんと資料に起こしつつ、繰り返し事ある毎に言及し、文化として醸成していく。

暗黙知として知識ベースのものは、ちゃんとドキュメント化する

最初の一歩

仮説段階

開発者として考えることはドメインモデルの構築にどこまで時間を割けるか

  • プロトはあくまで PO デザイナーで InVisionやFigma でのmockだけでできる限りやる
  • その間に開発者は、プロトのではなく、サービス自体のドメインモデルを構築する

プロトを作る工数に意外と時間が取られる その時間をドメインモデル構築に当てたほうが軸が定まってサービス開発がうまくいくのでは?という感触がある

読み切れていない資料

ドメイン関連が多い

blog.masuqat.net

www.graat.co.jp

little-hands.hatenablog.com

speakerdeck.com

creators-note.chatwork.com

qiita.com

masuda220.hatenablog.com

blog.shibayu36.org

docker build時のキャッシュの判定

メモ書き

スタートはdispatchersから https://github.com/moby/moby/blob/cf0ce96eb129ebcc7d07f0f47a8683c16f228c7d/builder/dockerfile/dispatchers.go

ADD や COPY

https://github.com/moby/moby/blob/cf0ce96eb129ebcc7d07f0f47a8683c16f228c7d/builder/dockerfile/dispatchers.go#L94-L133

createCopyInstructionでキャッシュなどの判定も含めて行う

performCopyがファイルの変更を行う本体 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/internals.go#L160

createCopyInstruction

https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/copy.go#L110-L132

getCopyInfosForSourcePathsからgetCopyInfoForSourcePathがよばれて、copyInfoを作成する。 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/copy.go#L134-L151

getCopyInfoForSourcePathでもろもろ生成される。 https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/copy.go#L40-L45

キャッシュの判定で使われるハッシュ値はここで生成される

https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/remotecontext/lazycontext.go#L37-L85

https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/remotecontext/filehash.go

moby/versioning.go at 46cdcd206c56172b95ba5c77b827a722dab426c5 · moby/moby · GitHub

performCopy

キャッシュの判定には上記のcreateCopyInstructionで作られたcopyInstructionを元に行う

probeCacheが担う https://github.com/moby/moby/blob/46cdcd206c56172b95ba5c77b827a722dab426c5/builder/dockerfile/internals.go#L428-L437

でキャッシュがあればそれを使い、そうでなければ新しくlayerを作る。

"プログラミングの基礎" を読んだ

2ヶ月かけてプログラミングの基礎という本を読みました。

この本を読んだきっかけ

この本は2013年くらいに @kis におすすめされた本でした。

購入日は2013/05/29

f:id:Spring_MT:20201208095045p:plain

買ってすぐ読んだ記憶があるのですが、2章くらいまでで読むのをやめた気がします。

その後、数回本を開いた記憶があるのですが、どの場合も途中で読むのを止めていました。

で、2020年になってLeetCodeなどをやり始めたのですが、解法やアルゴリズムは理解できるがプログラムに落とし込めない(特に再帰が想像できない)という課題がでてきました。

問題に一対一対応させて覚えていくという力技よりは、もっと根本となるコードを書く能力を高めないと先がないなあと思って、色々本を読んでみました。

最初はSICPとかを眺めて見ましたが、理解が進まず挫折。。。

その後、SICPより読みやすくてなにかないかと探してみたところ、再度この本を取ることにしました。

きっかけもやっぱり @kis

nowokay.hatenablog.com

(タイトルは若干あおり気味ですが、、、)

このブログを読んで改めてプログラミングの基礎を手にとりました。

読み進め方

今までなぜ読みきれなかったかを振り返ってみると、単に読んでいるだけだと何が問題なのか実感が得にくく、だんだんモチベーションが下がるという感じだった気がしました。

そこで、今回は下記を実践しました。

  • 演習は全部やる
  • 時間を決めてその時間を集中して手を動かしながら本を読む

今は夜に時間が取れないので、業務開始前 9:00〜10:00 を確保し、その間に手を動かしながらやっていきました。

それで、大体2ヶ月で読み切りました。

この本から学んだこと

課題に思っていたプログラムに落とし込めない問題(特に再帰)に関しては、自分が何がわかっていなかったかがわかるようになりました。

  • 再帰的なデータ構造(型)を知り理解できた(気がする)
  • 再帰的なデータ構造(型)とプログラムの構造が対応できることを理解できた(気がする)
  • 構造に従わない再帰であっても、部分問題に分割すること、停止性を考えることでプログラムに落とし込みができるようになった

データ構造が重要なのはなんとなく思っていたのですが、この本を読むまではデータ構造をプログラムの構造に対応させる方法がわからなかったからだと気が付きました。

この本ではOCamlを使っていくのですが、パターンマッチの力をやっと理解できました。

この本の良かった点

  • 日本語がわかりやすい
  • 章の構成がよく、読み進めやすい
  • 説明が丁寧で理解しやすい
  • 単語の説明がすごく平易な表現になっていてわかりやすい(多相性 -> どのような型でもよい性質 などなど)
  • 演習の答えが全て揃っている

次に

同じく @kis に勧められていたプログラミング言語の基礎概念を読みつつ、再度プログラムを書く練習をしていきます。

LeetCodeはOCamlサポートしてないけど、他の言語でトライしてみる予定です。

雑感

もっと早く読んでおけばよかったと思う反面、課題がないまま読んでもまた読むのを止めると思うのでこのタイミングでよかったんだろうと思っています。

自分はCSの勉強を大学でしてこなかったで、こういう本があると自力でCSの勉強をする取っ掛かりとしてはとてもいいのではないかなと思いました。

2020/07から2020/10までのGKEのリリースノートからGKEの機能で気になる部分をまとめる

セキュリティ関連は書いてないです。

kubernetesのアップデート内容には触れていません。

July 2, 2020

https://cloud.google.com/kubernetes-engine/docs/release-notes#july_2_2020

NodeLocal DNSCacheがGA

Setting up NodeLocal DNSCache  |  Kubernetes Engine Documentation

既存のクラスタでもonにできる

名前解決が速くなるのonにしておくのがよいかと

GKE Node System ConfigurationがGA

https://cloud.google.com/sdk/gcloud/reference/beta/container/node-pools/create#--system-config-from-file

Nodeの設定を指定できる機能

Linux kernel parameters (sysctls) や kubelet configsもある程度設定できる

net.core.somaxconn とかも設定できる

Nodeを作成するときに、gcloud コマンド経由でファイル(JSON or YAML)を指定する

July 17, 2020

https://cloud.google.com/kubernetes-engine/docs/release-notes#july_17_2020

SSL Policiesがexternal Ingress and multi-cluster Ingressでβ 、Custom health checks が external, internal, and multi-cluster Ingressでβ(1.17.6-gke.11以上かな)

下記のリンクに全部まとまっている

https://cloud.google.com/kubernetes-engine/docs/how-to/ingress-features

SSLPolicyを予めGCP上で作成しておき、それをFrontendConfigで設定して、それをIngressの設定で指定する

https://cloud.google.com/load-balancing/docs/ssl-policies-concepts

apiVersion: networking.gke.io/v1beta1
kind: FrontendConfig
metadata:
  name: my-frontend-config
spec:
  sslPolicy: gke-ingress-ssl-policy # 

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  annotations:
    networking.gke.io/v1beta1.FrontendConfig: "my-frontend-config"

health checkのほうはBackendConfigで設定する

BackendConfig CRDがGKE 1.16-gke.3以上でGA

IAP, timeouts, affinity, user-defined request headerなどの機能が追加されている

IAPもか

GKE バージョン 1.16-gke.3 以降を使用している場合は、cloud.google.com/backend-config アノテーションを使うようにすること。

GKE 1.17.6-gke.7以上のクラスタで、新規 Serviceをデプロイすると、NEGを使うContainer-native Ingress が デフォルトに

cloud.google.com/neg: '{"ingress": true}' のannotaionがデフォルトでつくので、自分で書く必要がなくなる

あくまで 新規 Serviceのみなので要注意

https://cloud.google.com/kubernetes-engine/docs/concepts/ingress#container-native_load_balancing

CMEKがGKEでGA

July 28, 2020 (R25)

https://cloud.google.com/kubernetes-engine/docs/release-notes#july_28_2020_r25

デフォルトのマシンタイプがE2に

https://cloud.google.com/compute/docs/machine-types?hl=ja#machine_types

Google Cloudに安価な汎用仮想マシン「E2ファミリー」を追加 | TechCrunch Japan

E2は継続利用割引がないので注意してください。

https://cloud.google.com/compute/docs/machine-types?hl=ja#machine_type_comparison

August 21, 2020

https://cloud.google.com/kubernetes-engine/docs/release-notes#august_21_2020

Internal load balancer Serviceが GKE 1.17.9-gke.600以上でGA

https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing

type: LoadBalancer のServiceのannotationで cloud.google.com/load-balancer-type: "Internal" で使えるようになる

この internal load balancer ServiceではGlobal access と configurable subnets が使える

https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing#global_access

https://cloud.google.com/kubernetes-engine/docs/how-to/internal-load-balancing#lb_subnet

リージョンまたいでinternal load balancer Serviceが使える

GKE versions 1.17.9-gke.600以上で Dataplane V2がβ

https://cloud.google.com/blog/products/containers-kubernetes/bringing-ebpf-and-cilium-to-google-kubernetes-engine

Dataplane V2があれば、Network policy loggingも使える

https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy-logging

RFC 1918以外のプライベートIPアドレスが利用可能に

https://cloud.google.com/kubernetes-engine/docs/how-to/alias-ips#enable_reserved_ip_ranges

使える範囲は下記を参照

https://cloud.google.com/vpc/docs/vpc#valid-ranges

August 28, 2020

https://cloud.google.com/kubernetes-engine/docs/release-notes#august_28_2020

どのリージョンからもprivate clusterのマスター( Contol plane )へアクセス可能にする

デフォルトは同じリージョンからしかアクセスできない

https://cloud.google.com/kubernetes-engine/docs/how-to/private-clusters#cp-global-access

September 8, 2020

https://cloud.google.com/kubernetes-engine/docs/release-notes#september_8_2020

Error状態のclusterを自動的に削除する

Errorの定義は下記を参照

https://cloud.google.com/kubernetes-engine/docs/reference/rest/v1/projects.locations.clusters#status

TaintBasedEvictionsがGKEの 1.18 でGA

あれ、このタイミングなのか

Kubernetes 1.13: SIG Scheduling の変更内容 - チェシャ猫の消滅定理

September 25, 2020 (R31)

https://cloud.google.com/kubernetes-engine/docs/release-notes#september_25_2020_r31

Node Auto-Provisioningで CMEK、Secure Boot and Integrity Monitoring、Boot disk type and sizeをデフォルトでセットしてくれるようになった

今まではセットしてくれてなかったってことか。。

October 02, 2020 (R32)

https://cloud.google.com/kubernetes-engine/docs/release-notes#october_02_2020_r32

GKE 1.18以上でオートスケールのプロファイルのロジックが optimize-utilization になる

クラスタをより積極的にスケールダウンしてくれるようになる。

リソースの使用率を最大化するぽい。

https://cloud.google.com/kubernetes-engine/docs/concepts/cluster-autoscaler#autoscaling_profiles

macOSでKarabiner-Elementsが認識されなくなったら試すこと

  1. リカバリモードで立ち上げる
    • Mac の電源を入れた直後に、「command (⌘)」と「R」の 2 つのキーを押し続ける
  2. ユーティリティは選択せずにメーニューバーからユーティリティ -> ターミナルを選択
    • ターミナルが立ち上がるはず
  3. spctl kext-consent add G43BCU2T37 を実行する

Rails 5.2以降のRails 5系でDBに都度接続している場合においてはdatabase.ymlで `reaping_frequency: null` を設定する

Rails 5.2以降でDBに都度接続している場合においてはdatabase.ymlで reaping_frequency: null を設定しないと、スレッドが枯渇してサーバーにsshすらできなくなりサーバーの再起動が必要になります。

sshはこんなエラーになります。

shell request failed on channel 0

またサーバーに入っていると下記のようなエラーがでて何もできなくなります。

-bash: fork: retry: 子プロセスがありません
-bash: fork: リソースが一時的に利用できません

スレッドが枯渇しているサーバーは外部からみると、CPU、メモリなどの負荷がかかっているようには見えず、再起動して入ってみてもOOM-Killerなどが発動しておらず、何が起こっているかわかりにくいのが特徴です。

1プロセスあたりのファイルディスクリプタ数の上限も十分にあり、tcp周りの設定( net.ipv4.tcp_tw_reuse とか) もしてあり、file descriptorの枯渇もみられなかったので、原因がわからず焦りました。

ちなみに tcp_tw_recycle は廃止されたそうです。

qiita.com

git.kernel.org

原因

下記のPRにより、 reaping_frequency が設定されていないとデフォルトの60秒が設定されるようになりました。

github.com

これはReaperがDBへの接続がハングしているか死んでいるかをチェックする間隔になります。

api.rubyonrails.org

Reaperは別スレッドを立てて定期的に起き上がって確認する実装になっています。

Reaperは、establish_connectionが実行され、コネクションプールが生成されるたびにスレッドを作成します。

がこのスレッドは特にkillもされずに残る実装になっていそうです。

https://github.com/rails/rails/blob/1f9e308695556aae16872b54d9a0f0e1d7b3d055/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L359

2020/08/31時点のedgeを見ると、ちゃんと止める実装も入っている気がします。

https://github.com/rails/rails/blob/1f9e308695556aae16872b54d9a0f0e1d7b3d055/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb#L331-L356

で、activerecord-refresh_connectionなどを使って都度接続などを行っていると、コネクションを貼るたびにスレッドが作られ増え続けます。

github.com

スレッドが増える様子は pstree コマンドで確認できます。(下記の中で数字がスレッド数です)

# pstree
sh---bundle-+-bundle---73*[{bundle}]
            |-bundle---49*[{bundle}]
            |-bundle---178*[{bundle}]
            |-bundle---106*[{bundle}]
            |-bundle---122*[{bundle}]
            |-bundle---98*[{bundle}]
            |-bundle---130*[{bundle}]
            |-bundle---138*[{bundle}]
            `-74*[{bundle}]
// アクセスする
# pstree
sh---bundle-+-bundle---73*[{bundle}]
            |-bundle---49*[{bundle}]
            |-bundle---186*[{bundle}]
            |-bundle---106*[{bundle}]
            |-bundle---122*[{bundle}]
            |-bundle---98*[{bundle}]
            |-bundle---130*[{bundle}]
            |-bundle---138*[{bundle}]
            `-74*[{bundle}]

そうすると、スレッド数がもりもり増えていき、thread-maxに到達して、サーバーにアクセスできなくなります。

対応方法

reaping_frequency を明示的に null ないしは 0で指定すればそもそもReaperはスレッドを作らなくなります。

さらに都度接続しているのであれば、そのリクエスト内でコネクションがおかしくなったらエラーになりますが、次のリクエストを処理するときは再接続するのでエラーが出続けることはないので、上記の設定でも問題ないと考えています。

参照

thread-maxの設定

https://github.com/torvalds/linux/blob/7c2a69f610e64c8dec6a06a66e721f4ce1dd783a/Documentation/admin-guide/sysctl/kernel.rst#threads-max

bcryptの仕組み(とbcrypt-rubyの中身を見る)

Ruby(主にRails)でパスワードを扱う際のハッシュ化には bcrypt-ruby を使うことがほとんどだと思います。

github.com

bcryptについて勉強しつつ、さらに実装を確認してみようと思います。

bcrypt-rubyv3.1.15 を利用しています。

bcrypt

説明はwikipediaにあります。

ja.wikipedia.org

https://www.usenix.org/legacy/event/usenix99/provos/provos.pdf

Blowfishの鍵セットアップ関数に Eksblowfish を使っていて、このセットアップにcostとソルトとパスワードを利用しています。

セットアップ後、 OrpheanBeholderScryDoubt という24 byteの文字列を64回繰り返し暗号化しバイト列を生成します。

bcrypt-ruby ではここで定義されています。

https://github.com/codahale/bcrypt-ruby/blob/4279fa01f083b48de9a18c54423453059074ba77/ext/mri/crypt_blowfish.c#L80-L87

そして、costとソルトと暗号化して生成されてバイト列を結合したものがbcryptがハッシュ値として生成する文字列になります。

bcrypt-ruby でハッシュ化された文字列

bcrypt-ruby でハッシュ化に使われるバージョンは 2a のみぽいです。

生成されるハッシュは接頭辞は $2a$ になっています。

irb(main):002:0> my_password = BCrypt::Password.create("my password")
=> "$2a$12$zafAQ3jKUpR7TXwYRm8nmeO.3p/1iZN9a09F7cFs7M6kNZxnbvf9C"
irb(main):003:0> my_password.version 
=> "2a"

ただし、他のバージョンでハッシュ化されたものも読み込めそうです。

irb(main):006:0> new_password = BCrypt::Password.new("$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e")
=> "$2y$05$/OK.fbVrR/bpIqNJ5ianF.CE5elHaaO4EbggVDjb8P19RukzXSM3e"
irb(main):007:0> new_password.version
=> "2y"

https://github.com/codahale/bcrypt-ruby/blob/2121626ba841b4c1f0f3947d2f703cfe71fb977f/ext/mri/wrapper.c#L216-L234

ハッシュ化されている文字列はいくつかの区分に分けられます。

まず 最初の $ $ で 囲われている部分はバージョンです。

次に $ $で囲われているのは、コストの部分です。

これは、2を基数とした指数となり、この数の2の累乗がEksblowfishでのセットアップ中での繰り返し回数になります。(ストレッチング)

この回数が多ければ、ブルートフォース攻撃になどに対する対抗が強くなりますが、その分計算量が増えて計算に時間がかかるようになります。

bcrypt-ruby では BCrypt::Engine.calibrate() といいメソッドが用意されており、どれくらいのコストになるかをざっくりと計算できます。

irb(main):002:0> BCrypt::Engine.calibrate(5000) # この場合は5000ms以内で終わる最大のコストを算出してくる
=> 16

bcrypt-ruby ではハッシュ化のcostを指定できます。

irb(main):004:0> my_password = BCrypt::Password.create("my password", cost: 4)
=> "$2a$04$KV38A7SDgPKjmVi7ir59muRmquFxR/KmX/HtgWY97qC1tBryajHp."

ただし、4以下の場合は全て4に置き換えられます。

https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/engine.rb#L67-L69

irb(main):005:0> my_password = BCrypt::Password.create("my password", cost: 1)
=> "$2a$04$vLCmqS2n9tnYpnKT.G9wJuhBPjtKakH2/9zOQuU1IIv.HIDj6q27e"  # コストが04になっている

また、costが31より大きいと ArgumentError の例外が発生します。

https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L45

コスト以降の部分はソルトとチェックサムになります。

128ビットのソルトになるのですが、Radix-64エンコードされて22バイトになっています。

チェックサムも184ビットなのですが、Radix-64エンコードされて31バイトになっています。

パスワードの確認する際は、 bcrypt-ruby ではハッシュ化された文字列のバージョンやコストも含む先頭から29バイトをソルトとして渡して処理をしていきます。

ハッシュ化

bcrypt-ruby ではソルトの生成におけるランダムな16バイトを生成するために OpenSSL::Random.random_bytes() を利用しています。

docs.ruby-lang.org

https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/engine.rb#L74

生成されたソルトを利用してハッシュ化をします。

https://github.com/codahale/bcrypt-ruby/blob/master/lib/bcrypt/password.rb#L46

同じソルトを使えば、同じ文字列からは同じハッシュ値が生成されます。

irb(main):032:0> my_password = BCrypt::Password.create("my password")
=> "$2a$12$VoGypMhauXeq4LZcLMbk6uGo/moLdWs/OAGUNbq9gLe9p.YZyOqXm"
irb(main):033:0> BCrypt::Engine.hash_secret("my password", "$2a$12$VoGypMhauXeq4LZcLMbk6u")
=> "$2a$12$VoGypMhauXeq4LZcLMbk6uGo/moLdWs/OAGUNbq9gLe9p.YZyOqXm"

bcrypt-ruby の利用で気をつけること

Nullバイトの対応

bcrypt-ruby は Nullバイトの対応ができてないです。

irb(main):039:0> password = BCrypt::Password.create("foo\0bar")
=> "$2a$12$YPoekx0N24xH50Z0y9JfUeBpEBvYLNISVaSaH4oWJKAWnoTHa46RO"
irb(main):040:0> password == "foo"
=> true

github.com

PRはあるのですが、mergeされていませんね、、

なので、下記2つのブログで言及されているようなことで問題になります。

blog.tokumaru.org

qiita.com

パスワードの文字数は72文字まで

72文字以降は切り捨てられます。

irb(main):043:0> password = BCrypt::Password.create("a" * 73)
=> "$2a$12$CnOALTas/7Yj0JuzBW3GuOE1rKm3DZfN7N6OLwEGH8lnELV7H3iEC"
irb(main):044:0> password == "a" * 72
=> true

これは他の言語の実装でも同じような挙動になることが多いとのことです。

SecretSalt

bcryptでは OrpheanBeholderScryDoubt という文字列を暗号化することですが、この固定の文字列も変えてハッシュ値を取るのが SecretSalt になります。

qiita.com