Kubernetes上のアプリケーション開発を加速させるツール(2) Telepresence

2020年10月6日(火)
五十嵐 綾(いがらし・あや)

はじめに

Kubernetesアプリケーションの開発フローイメージ

非コンテナ環境と比べると、コンテナ化したアプリケーションは1開発サイクルを回すために、より多くの時間と手数が要求されます。この問題の解決策の1つとして、第4回ではSkaffoldを紹介しました。今回は、Skaffoldとは別のアプローチで開発を効率化するTelepresenceについて紹介します。

Telepresenceを使った開発

Telepresenceは、ローカルからKubernetes用のマイクロサービスを開発するためのツールです。Datawireによって開発が始まり、現在はCNCFのSandboxプロジェクトの1つとなっています。

開発効率改善のアプローチとしては、下図のようにコンテナイメージのビルド*1・アップロード・デプロイといった開発ステップを省略する方法を取っています。これは、Kubernetes上のTelepresence Podから、ローカルに起動したアプリケーションへ通信や各種データをプロキシすることで実現しています。

Telepresenceを使った開発フローのイメージ

*1:--docker-runオプションを利用する場合は必要になります。

これにより、ローカルで起動したアプリケーションはあたかもKubernetes上にデプロイされたかのように動作します。サポートされている機能は、主にコンテナにマウントされたボリュームデータ(ConfigMapやSecret等)へのアクセス、環境変数の読み込み、Service名を使った他のアプリケーションとの通信です。

また、ローカルへのプロキシ方法はinject-tcpと、デフォルトのvpn-tcpの2つが提供されています。それぞれの概要と、主な制約を表にまとめました*2。実行環境や、アプリケーションの言語等に制約がありますので、適切な方を選択してください。

2つのプロキシ方法とその制約

inject-tcp vpn-tcp
概要 アプリケーションの共有ライブラリを上書きする方法*3 sshuttleを使ったVPNライクな接続方法
主な制約事項
  • Goアプリケーション*4など、静的リンクしているバイナリでは利用できない
  • suidしたバイナリはTelepresence Shell内で動作しない
  • /etc/resolv.confをパースするようなカスタムDNSリゾルバ、自身に対するDNS lookupは動かない
  • マシンにつき、1アプリケーションのプロキシのみ可能
  • 他のVPNと併用できない
  • AWS RDSなど、外部クラウドリソースへの自動ルーティングができない*5
*2:全制約事項についてはa href="https://www.telepresence.io/reference/methods" target="blank">公式ページを参照してください。
*3:LinuxのLD_PRELOADとmacOSのDYLD_INSERT_LIBRARIESを利用した方法で、詳細はこちらのブログで詳しく解説されています。
*4go buildではなくgccgoによるビルドやGODEBUG環境変数でnetdnsのリゾルバをcgoに変更するなどのワークアラウンドは存在しますが、非推奨です。
*5--also-proxyフラグによる手動設定は可能です。

第4回と同じく、今回もk8s-sample-applicationを利用します。このアプリケーションはGo言語で書かれていますので、Go言語をサポートするvpn-tcpを使った開発方法を紹介します。

開発の下準備

Telepresenceコマンドのインストール

それでは、実際に検証してみます。macOSの場合はHomebrewを使って下記のようにインストールします。その他の環境へのインストール方法は公式ドキュメントを参照してください。

# telepresenceと依存ツールのインストール
$ brew cask install osxfuse && brew install datawire/blackbird/telepresence
# バージョンの確認
$ telepresence --version
0.108

サンプルアプリケーションのデプロイ

まずは、開発用アプリケーションを通常通りkubectl applyで適用します。前回Gitクローンしたsample-k8s-appディレクトリに移動し、manifestsディレクトリ内のマニフェストを適用してください。念のためkubectl getで正常に動作していることも確認しましょう。DeploymentのREADYカラムが1/1になっていれば大丈夫です。

# (1) 該当ディレクトリに移動
$ cd sample-k8s-app
# (2) アプリケーションマニフェストの適用
$ kubectl apply -f manifests
deployment.apps/myapp created
service/myapp created
# (3) デプロイしたリソースの確認
$ kubectl get deploy,svc myapp
NAME                    READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/myapp   1/1     1            1           33s

NAME            TYPE       CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
service/myapp   NodePort   10.102.199.85   <none>        80:30216/TCP   33s

Telepresenceによるローカルへのプロキシ

Telepresenceのイメージ図

次に、アプリケーションをローカルから開発できるようにtelepresenceコマンドを実行します。--swap-deploymentフラグには置き換え対象となるDeployment名、--exposeフラグにはDeploymentで公開しているポート番号、--runフラグにはアプリケーションを起動するためのコマンドを指定します。

正常にTelepresenceのプロキシ設定が完了すると、Start server...というmyappの起動ログが出力されます。

# (1) telepresence --swap-deployment <Deployment名> --expose <ポート番号> --run <アプリケーションの起動コマンド>
$ telepresence --swap-deployment myapp --expose 8080 --run go run main.go
T: Using a Pod instead of a Deployment for the Telepresence proxy. If you experience problems, please file an issue!
T: Set the environment variable TELEPRESENCE_USE_DEPLOYMENT to any non-empty value to force the old behavior, e.g.,
T:     env TELEPRESENCE_USE_DEPLOYMENT=1 telepresence --run curl hello

T: How Telepresence uses sudo: https://www.telepresence.io/reference/install#dependencies
T: Invoking sudo. Please enter your sudo password.
Password:  # (2) sudoパスワードの入力
T: Starting proxy with method 'vpn-tcp', which has the following limitations: All processes are affected, only one telepresence can run per
T: machine, and you can't use other VPNs. You may need to add cloud hosts and headless services with --also-proxy. For a full list of method
T:  limitations see https://telepresence.io/reference/methods.html
T: Volumes are rooted at $TELEPRESENCE_ROOT. See https://telepresence.io/howto/volumes.html for details.
T: Starting network proxy to cluster by swapping out Deployment myapp with a proxy Pod
T: Forwarding remote port 8080 to local port 8080.

T: Connected. Flushing DNS cache.
T: Setup complete. Launching your command.
# (3) アプリケーションログが標準出力に表示される
2020/09/19 15:07:23 Start server...

では、実際に置き換わったことを別のターミナルから確認しましょう。myappのDeploymentを確認すると、(1)のようにPodのレプリカ数が0に変わっていました。しかし、(2)の通りmyappをプレフィックスに持つPodが起動しています。では、このPodはいったい何者でしょうか?

このPodのコンテナイメージを確認すると、(3)のようにdatawire/telepresence-k8s:0.108が返ってきました。このことから、telepresenceコマンドを実行したタイミングで、指定したmyappがTelepresenceコンテナイメージを持つPodに置き換えられたことが分かります。

また、(4)のようにmyapp ServiceのEndpointもTelepresence Podを参照しています。よって、NodePort経由でアプリケーションにリクエストを送信すると、ローカルで起動しているmyappからレスポンスが返ってきます。

# (1) Deploymentのレプリカ数が0に変わっている
$ kubectl get deploy myapp -o wide
NAME    READY   UP-TO-DATE   AVAILABLE   AGE     CONTAINERS       IMAGES                   SELECTOR
myapp   0/0     0            0           2m49s   sample-k8s-app   ladicle/sample-k8s-app   app=myapp

# (2) myappの名を持つPodが新たに立ち上がっている
$ kubectl get po -o wide
NAME                                     READY   STATUS    RESTARTS   AGE   IP               NODE      NOMINATED NODE   READINESS GATES
myapp-51b8df20eef24324959a3f3eb46e7d48   1/1     Running   0          94s   192.168.189.82   worker2   <none>           <none>

# (3) PodのコンテナイメージはTelepresenceに置き換わっている
$ kubectl get po myapp-51b8df20eef24324959a3f3eb46e7d48 -o jsonpath='{.spec.containers[0].image}'
datawire/telepresence-k8s:0.108

# (4) myappのService Endpointは、Telepresenceのコンテナイメージを持つPodのIPを参照している
$ kubectl get ep myapp
NAME    ENDPOINTS             AGE
myapp   192.168.189.82:8080   3m17s

# (5) myapp ServiceのNodePort経由で、ローカルに起動しているmyappからレスポンスを受け取る
$ curl http://:30216/hello
Hello World

開発フローを回す

サンプルアプリケーションの修正と確認

続いて、プログラムを修正して変更内容が適用されていることを確認します。下記のようにmain.goのコメントアウトされている行をアンコメントしてください。

diff --git a/main.go b/main.go
@@ -7,11 +7,11 @@ import (
-   // "os"
+ "os"
 )

 func main() {
-   // log.Printf("TELEPRESENCE_ROOT: %v, TELEPRESENCE_MOUNTS: %v", os.Getenv("TELEPRESENCE_ROOT"), os.Getenv("TELEPRESENCE_MOUNTS"))
+ log.Printf("TELEPRESENCE_ROOT: %v, TELEPRESENCE_MOUNTS: %v", os.Getenv("TELEPRESENCE_ROOT"), os.Getenv("TELEPRESENCE_MOUNTS"))

この変更の適用はtelepresenceコマンドを再実行するだけです。実行するとアンコメントしたログ出力が確認できます。このように、Telepresenceではコンテナイメージのビルド・アップロード・ダウンロードというステップを省略することで、修正と動作確認をすばやく繰り返すことができます。

今回はアプリケーションをビルドするため、変更適用時にtelepresenceコマンドを再実行しましたが、Node.jsのnodemonのようなコード変更時に再起動するツールを利用すれば、コマンドの再実行は不要です。また、アプリケーション自体はローカルに立ち上がっていますので、IDE等でローカルデバッグも可能です。

$ telepresence --swap-deployment myapp --expose 8080 --run go run main.go
(省略)
2020/09/20 13:14:12 TELEPRESENCE_ROOT: /tmp/tel-fcw5e_zg/Fs, TELEPRESENCE_MOUNTS: /var/run/secrets/kubernetes.io/serviceaccount
2020/09/20 13:14:12 Start server...

コンテナボリュームのデータ参照

はじめに紹介したように、Telepresenceではコンテナボリュームもローカルから参照できます。試しにdfコマンドでローカルのファイルシステムを見ると、TELEPRESENCE_ROOT環境変数が示すパスにマウントされていました。

それでは、ServiceAccountトークンが格納されているディレクトリも確認してみましょう。(2)のように/var/run/secrets/kubernetes.io/serviceaccountディレクトリを参照すると、コンテナ内と同じ3つのファイルが確認できました。

# (1) ローカルにコンテナボリュームがマウントされていることが確認できる
$ df | grep telepresence
telepresence@127.0.0.1:/  479101704 13557996  446013480   3% /private/tmp/tel-fcw5e_zg/fs

# (2) `/var/run/secrets/kubernetes.io/serviceaccount` を参照するとコンテナ内と同様にServiceAccountトークンなどが格納されている
$ ls /tmp/tel-fcw5e_zg/fs/var/run/secrets/kubernetes.io/serviceaccount
ca.crt  namespace  token

もちろん、コンテナ内のファイルパスとは異なりますので、TELEPRESENCE_ROOTが設定されている場合は、その値をパスのルートとするような処理をアプリケーションに入れる必要があります。

おわりに

以上のように、Telepresenceはコンテナイメージのビルド・アップロード・デプロイを省略するため、非コンテナ環境と同じような流れで開発できることが分かりました。

今回は紹介しませんでしたが、コンテナ特有のバグなどを発見するための--docker-runフラグも存在します。これを利用すれば、Dockerでローカル上にアプリケーションコンテナを立ち上げ、そのコンテナに対してプロキシを通すといったこともできます。もちろん、Telepresenceにも制約は色々ありますので、第4回で紹介したSkaffoldと合わせて、ご自身の環境にあった開発を加速させる方法を模索してみてください。

著者
五十嵐 綾(いがらし・あや)
ゼットラボ株式会社/ヤフー株式会社
通信事業者でOpenStackをベースとしたIaaS/PaaSのクラウドサービス基盤を5年ほど開発したのち、2017年よりゼットラボ株式会社でヤフー社向けのKubernetes管理基盤の研究開発に従事。Kubernetes Meetup Tokyoの共同主催者で、共著書に『Kubernetes実践入門』『みんなのDocker/Kubernetes』(技術評論社)、『Docker/Kubernetes開発・運用のためのセキュリティ実践ガイド』(マイナビ出版)がある。TwitterとGitHubのアカウントはともに@Ladicle。

連載バックナンバー

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています