koukiblog

たぶんweb系の話題

Pumaはあるサイズ以上のデータをPOSTされると一時ファイルを作成する

表題の通り。Pumaはあるサイズ以上のデータをPOSTされると一時ファイルを作成します。

puma/server.rb at 482ea5a24abaccf33c49dc9238a22e2a9affe288 · puma/puma · GitHub

      # Use a Tempfile if there is a lot of data left
      if remain > MAX_BODY
        stream = Tempfile.new(Const::PUMA_TMP_BASE)
        stream.binmode
      else
        # The body[0,0] trick is to get an empty string in the same
        # encoding as body.
        stream = StringIO.new body[0,0]
      end

GKE上で書き込み禁止にして運用していたところ、ある条件下でファイル作成しようとしてエラーになったので調べてみたらこれが原因でした。

Node.jsでsleep相当のことをする

非同期処理が意図通り動いているのか確認するときに便利です。 resolveAfter2Secondsという関数を定義し、完了を待ちます。

async function slowSomething(){
   await resolveAfter2Seconds()
}

function resolveAfter2Seconds() {
  console.log('starting slow promise')
  return new Promise(resolve => {
    setTimeout(function() {
      resolve('slow')
      console.log('starting slow promise')
    }, 2000)
  })
}

GKEでスティッキーセッション(Session Affinity)を利用する

GKE環境でスティッキーセッションを利用するときの方法です。WebSocketなど利用していて、ブラウザからPodと接続を確立したあとはそのPodと通信し続けたい場合に必要になると思います。

k8s環境でスティッキーセッションってそもそもできるのか?と最初不安だったのですが、思ったより簡単にできました。 GCPやそのほかのドキュメントではSessionAffinityと呼ばれていたので以降SessionAffinityにします。

解決策

GKEデフォルトのIngress Controllerには、SessionAffinityの機能があり、この機能を有効にすることで実現することができます。接続元の判断は、クライアントIP、Cookieのどちらかの方法を選ぶことができます。

Configuring a backend service through Ingress  |  Kubernetes Engine Documentation  |  Google Cloud

ドキュメントに書いてある通りなのですが、まずbackend configを作成し

apiVersion: cloud.google.com/v1beta1
kind: BackendConfig
metadata:
  name: my-bsc-backendconfig
spec:
  timeoutSec: 40
  connectionDraining:
    drainingTimeoutSec: 60
  sessionAffinity:
    affinityType: "CLIENT_IP"

サービス側でそのbackend configを利用することで実現できます。

ind: Service
metadata:
  name: my-bsc-service
  labels:
    purpose: bsc-config-demo
  annotations:
    cloud.google.com/neg: '{"ingress": true}'
    beta.cloud.google.com/backend-config: '{"ports": {"80":"my-bsc-backendconfig"}}'
...

サービス側で指定するNEGは必須です。 また、ServiceがNEGを利用していなかった場合、NEGに切り替わるまでの間通信断が発生するので本番環境に反映する場合は注意が必要です。

仕組み

GCPのLoadBalncerにSession Affinity ( https://cloud.google.com/load-balancing/docs/backend-service#generated_cookie_affinity )という仕組みがあり、これをそのままGKEでも利用する形です。

通常のk8s環境の場合、Ingressとして作成されたLBのバックエンドはGCEインスタンスであり、GCEインスタンスに来たリクエストをk8sがPodに転送するので一見SessionAffinityの実現は困難なように思えます。そこで、必要になるのがNEG( https://cloud.google.com/kubernetes-engine/docs/how-to/container-native-load-balancing )です。 

NEGを利用すると、LBのバックエンドをGCEインスタンスではなく、Podにすることが可能になります。こうすることで、LoadBalancerのSession Affinityの機能をk8s環境でもそのまま利用することができます。

NEGはGKE独自の機能なので、GKE以外の環境では、Nginx Ingress Controllerを利用する必要がありそうでした

Telepresenceを使ってk8s環境の開発をより快適にする

Telepresence(https://www.telepresence.io/)をちょっと試してみたのでそのメモ

Telepresence?

ローカルPCとリモートのk8sクラスタを接続し、マイクロサービス 環境での開発を支援してくれるツールです。 出来ることは大きく2つで、

どちらも双方向のプロキシがk8sクラスタにPodとしてデプロイされ、k8sクラスタとローカルPCを接続してくれます。

Install

Macであれば

brew cask install osxfuse
brew install datawire/blackbird/telepresence

だけでインストールできます。

WindowsはWSL上で動作させる形になっています。(これをWindows Supportというのかどうかは微妙なところ)

詳しくはこちら

Installing Telepresence · Telepresence: Fast, realistic local development for Kubernetes and OpenShift Origin microservices

Deploymentの差し替え

おそらくこれがメインの用途です。 k8sクラスタ上のアプリケーションでデバッグ情報を取得する場合、これまではデバッグ用の設定を追加したコンテナを作成しそれをデプロイする必要がありましたが、その必要がなくなります。

たとえば、service_aというdeploymentのapp というcontainerをローカルに存在するapp:debug というコンテナに差し替える場合

telepresence --swap-deployment service_a:app --docker-run --rm -it app:debug

というコマンドで差し替えることができます。Containerの指定は、Pod内のContainerが1つしかないなら不要です。SidecarなどでPod内に複数のContainerが存在しているケースが多いと思うので、Containerは必ず指定しておいた方が良いと思います。

注意点としては、このDeploymentの差し替えはTelepresence利用者だけに影響があるものではありません。k8sクラスタのDeploymentのそのものに影響を与えています。複数の開発者でクラスタを共有している場合は注意が必要です。

クラスタが外部からアクセス可能な場合、外部からローカルPC上のコンテナにアクセスできてしまいます。デバッグ用のコンテナとはいえ、不用意にファイルを配置しないように気をつけましょう。特にボリュームマウントする場合は注意が必要です。

ある程度の規模のクラスタになると、CI/CDの観点から各自が自由にkubectl applyはできなくなり、CIツールのチェックが通ったものが反映されるようなフローになることが多くなってくると思います。それはそれで正しいのですが、ちょっとした検証のためにそのフローを通さないといけないのは非効率です。このdeploymentの差し替えを有効に利用すれば、別のサービスとの連携の検証などが素早く行えそうです。

k8sクラスタへの接続

引数なしでtelepresenceコマンドを実行すると、ローカルPCとk8sクラスタを接続することができます。

telepresence

デフォルトではkubectlのcurrent contextが利用されます。context パラメタとnamespaceパラメタをそれぞれ指定することができます。

telepresenceによる接続が有効な間は、ローカルPCがk8sクラスタ内に存在しているようなイメージになります。サービスの名前解決を行うことができ、port-forwardで直接Podに接続するよりもより実際の動作に近い挙動を確認できます。また、ローカルPCにインストールされている様々なツールがそのまま利用できます。

まとめ

これまでのk8sクラスタの開発ではリモートのk8sクラスタデバッグする場合、kubectl port-forward で特定のPodで接続したり、デバッグ用のPodを準備しておいてそれをデプロイしそのPodのターミナルからデバッグを行うというプラクティスが紹介されることが多かったですが、telepresenceも選択肢の1つとして用意しておくとより快適な開発を行えそうです。

GKEでノードをダウンタイムなしで更新する場合

GKEを利用していて、ノードをダウンタイムなしで更新したい場合、ノードプールを新規作成し、既存のノードプールからワークロードを移行することで実現することができます。

ノードのスペックやサービスアカウント の情報は作成後変更はできないため、本番環境で運用を始めると必要になるケースが多そうです。

ワークロードの移行は、既存のNode全てにcordonを実行しUnscheduleにしてから、既存のNode全てをdrainすることでワークロードを新しいノードプールに移行できます。 このオペーレションをダウンタイムなしで実現するには、PodDisruptionBudgetが適切に設定されている必要があるので注意が必要です。

詳しくは↓に全て書いてあります。 cloud.google.com

gitで、ある2つのブランチ間の特定のディレクトリ配下のdiffを取る

やりたいことはタイトルの通り。

特定のディレクトリ配下に変更があったかどうかを知りたい場合の方法です。調べて見たら出来ました。

git diff branchA branchB --relative=dir_name 

で出来ます。

ファイル名のみで良い場合は、

git diff branchA branchB --relative=dir_name --name-only