koukiblog

たぶんweb系の話題

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

readnessProve, livenessProveを設定していない場合の挙動

readnessProve, livenessProveを設定していない場合、両者ともstateのデフォルト値がSuccessになる。つまりチェックされない。

https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/

livenessProbe: Indicates whether the Container is running. If the liveness probe fails, the kubelet kills the Container, and the Container is subjected to its restart policy. If a Container does not provide a liveness probe, the default state is Success.

readinessProbe: Indicates whether the Container is ready to service requests. If the readiness probe fails, the endpoints controller removes the Pod’s IP address from the endpoints of all Services that match the Pod. The default state of readiness before the initial delay is Failure. If a Container does not provide a readiness probe, the default state is Success.

という記述があった。 何らかデフォルトのヘルスチェックが入っているのかと思ったがそうではなかったのでメモしとく

Goで任意のJSONオブジェクトを文字列のままUnmershalする

{
  "id": 1
  "payload": {
       "a":1
   }
},
{
  "id": 2
  "payload": {
       "b":2
   }
},

こういうJSONをGoで扱うときに、payload要素を、{"a": 1}, {"b": 2 } のように文字列そのままで取得したいときの方法です。

DBやBigQueryにjsonのまま保存したいみたいな用途です。

jsonstringという構造体を定義して、

type jsonstring struct {
    Body string
}

func (js jsonstring) MarshalJSON() ([]byte, error) {
    return []byte(js.Body), nil
}

func (js *jsonstring) UnmarshalJSON(data []byte) error {
    *js = jsonstring{Body: string(data)}
    return nil
}

func (js jsonstring) String() string {
    return js.Body
}

それをUnmarshalする構造体の型に指定する。

type Item struct {
    Id    string     `json:"text"`
    Payload jsonstring `json:"payload"`
}

するとUnmarshalできます

Istioを使うとLBでセットしたx-forwarded-protoが上書きされる

Istioを利用すると、LBでセットしたx-forwarded-protpがhttpに上書きされてしまうようです。

Istio give `x-forwarded-proto=http` to upstream server when using SSL termination · Issue #7964 · istio/istio · GitHub

解決策、というかworkaroundがIssue内にあり、それを適用して回避しました。 istioのingressgatewayに来たrequestのx-forwarded-protpをhttpsに差し替えるという対応です。

httpも受け付けていたりすると、この対応では不都合が発生する可能性もあるのでこれが使えるかはケースバイケースです。

apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: my-app-filter
spec:
  workloadLabels:
    istio: ingressgateway
  filters:
  - filterName: envoy.lua
    filterType: HTTP
    filterConfig:
      inlineCode: |
        function envoy_on_request(request_handle)
          request_handle:headers():replace("x-forwarded-proto", "https")
        end
        function envoy_on_response(response_handle)
        end

KubernetesのJobを定義するときに覚えておくと便利なコマンド

特定のエンドポイントが疎通したことを確認してから処理を行う

特定のエンドポイントが200返すことを確認してから処理を行うことができます。istio-proxyなどSidecarコンテナの起動を待てます。

e.g :

istio-proxyの起動を待ってから処理開始。完了したらファイル配置

          - name: job-name
            image: alpine
            command: ["/bin/sh","-c"]
            args: ["while ! wget http://127.0.0.1:15020/healthz/ready -O -; do echo 'waiting istio-proxy...' && sleep 1; done; /startjob && touch /tmp/proxy-killer/ready"]
            volumeMounts:
              - name: proxy-killer
                mountPath: /tmp/proxy-killer

特定のファイルの存在を待ってから処理を行う

volume経由で実行完了を通知して後処理を行うことができます。

e.g :

ファイルの存在を確認してからpkill

          - name: proxy-killer
            image: alpine
            command: ["/bin/sh", "-c"]
            args: ["while [ ! -f /tmp/proxy-killer/ready ]; do echo 'waiting ready to kill proxy...' && sleep 1; done; pkill -f /usr/local/bin/pilot-agent"]
            volumeMounts:
            - name: proxy-killer
              mountPath: /tmp/proxy-killer