🛠️

TiltとTelepresenceによるKubernetes開発ループの高速化

に公開

前回の記事「k3sでリモートアクセス可能なKubernetesホームラボを構築」では、インターネット経由でアクセス可能なk3sベースのホームラボを構築しました。今回は、その環境の上に高速開発ループを構築します。

English

https://youtu.be/109FD3bN71s

前提条件

  • Kubernetes環境 (前回記事を参照)
  • ホームサーバーへのSSHアクセス
  • kubectlとHelmの基本知識

開発用コンテナレジストリ

まずコンテナイメージをビルドしてプッシュする環境を整えます。

docker push failure

自宅サーバー上のk3sクラスター内にプライベートレジストリがある前提です。

自宅ネットワークの外からでもCloudflare Tunnelで多くのサービスにアクセスできますが、レジストリには向きません。Cloudflare Tunnel越しに大きなデータをpushすることはできないからです。イメージを公開する場合はパブリックレジストリが必要です。一方、開発用途ではSSH経由のリモートDocker(DOCKER_HOST)を使えば、クラスター内のプライベートレジストリへプッシュできます。

docker push over SSH

前回記事でホームネットワークへのSSHアクセスを用意しました。これを使ってホームサーバー上のDockerデーモンに接続します。~/.ssh/configの例 (buun.devは自分のホスト名に置き換え):

Host buun.dev
  Hostname ssh.buun.dev
  ProxyCommand /opt/homebrew/bin/cloudflared access ssh --hostname %h

内部的にはbuun.devへのSSHセッションが開かれ、Docker Engine APIがトンネルされます。CLIの視点ではdocker build, docker push, docker runはすべてリモートエンジンに対する操作です。ビルドをオフロードできるのでノートPCのバッテリー節約にもなります。

イメージはlocalhost:30500/プレフィックスでタグ付けします。これはホームサーバーにとってクラスターの内外から到達可能なアドレスです。docker pushはリモートホストのネットワーク視点で実行されるため、そのホストからレジストリに到達できれば、ノートPCから直接レジストリに届かなくてもプッシュは成功します。

サンプルアプリのビルド、プッシュ、デプロイ

sample-web-appを使います。これはNext.js + Drizzle ORM + PostgreSQLのシンプルなアプリです。

git clone https://github.com/buun-ch/sample-web-app
cd sample-web-app
export DOCKER_HOST=ssh://buun.dev
docker build -t localhost:30500/buun-ch/sample-web-app:latest .
docker push localhost:30500/buun-ch/sample-web-app:latest

これでイメージをビルドし、プライベートレジストリへプッシュできます。先述の通り、localhost:30500/...タグはクラスターの内外から到達できます。

次にDBとユーザーを作成します。buun-stackを使っている場合は次のコマンドを実行します。

cd /path/to/buun-stack
just postgres::create-user-and-db

開発用のvalues.yamlを作成します。

image:
  imageRegistry: localhost:30500/buun-ch
  repository: sample-web-app
  tag: latest
  pullPolicy: Always

env:
  - name: DATABASE_URL
    value: postgresql://todo:todopass@postgres-cluster-rw.postgres:5432/todo

migration:
  enabled: true
  env:
    - name: DATABASE_URL
      value: postgresql://todo:todopass@postgres-cluster-rw.postgres:5432/todo

Helmチャートをデプロイします。

kubectl create namespace sample
helm upgrade --install sample-web-app ./charts/sample-web-app -n sample --wait -f values.yaml

リリースの状態を確認します。

kubectl get all -n sample

Telepresence: ポートフォワードなしでクラスター内DNS/ルーティング

毎回kubectl port-forwardしてブラウザを開くのは手間で、DNSも統合されません。TelepresenceはローカルプロセスをKubernetes内で動いているかのようにクラスターのネットワークへ参加させます。接続後はノートPCがサービス名を解決し、Telepresence経由でトラフィックをルーティングできるため、DNS名で直接アクセスできます。

  • 初回のみ: Traffic Managerをインストール
telepresence helm install
  • 接続:
telepresence connect

これで<service>.<namespace>のようなDNS名でサービスに直接アクセスできます。DbGateなどのGUIツールもそのまま動作します。作業終了時は切断します:

telepresence quit

開発オーケストレーター: Skaffold, Tilt, DevSpace

TelepresenceでネットワークとDNSが整ったら、次のボトルネックは開発ループです。すなわち、複数サービスに対するbuild, tag, push, デプロイを何度も繰り返すワークフローです。

理想的な開発ループ:

  • インクリメンタルビルド(変更箇所だけをビルド)
  • ライブ同期が即時(ローカルの編集がコンテナに即反映)
  • フィードバックの統合(ログやフォワードを1か所に集約)

これらを満たすツールとして、Skaffold, Tilt, DevSpaceがあります。以下は最小構成の例(概念的なサンプル)です。

Skaffold

apiVersion: skaffold/v4beta6
kind: Config
metadata:
  name: sample-web-app
build:
  artifacts:
    - image: localhost:30500/sample-web-app
      context: ./apps/sample-web-app
deploy:
  helm:
    releases:
      - name: sample-web-app
        chartPath: charts/sample-web-app
        valuesFiles:
          - values.dev.yaml

Tilt

# Tiltfile (Starlark)
docker_build('localhost:30500/sample-web-app', './apps/sample-web-app')

# 速い: 編集を同期し、必要に応じてコンテナ内でコマンド実行
live_update(
    'localhost:30500/sample-web-app',
    [
        sync('./apps/sample-web-app', '/app'),
        run('npm install', trigger=['package.json', 'package-lock.json'])
    ],
)

# マニフェストのレンダリングと適用(HelmまたはYAML)
k8s_yaml(local('helm template sample-web-app charts/sample-web-app -f values.dev.yaml'))

DevSpace

version: v2beta1
name: sample-web-app
images:
  app:
    image: localhost:30500/sample-web-app
    context: ./apps/sample-web-app
deployments:
  app:
    helm:
      chart:
        path: charts/sample-web-app
      values:
        image:
          repository: localhost:30500/sample-web-app
          tag: dev
dev:
  app:
    imageSelector: localhost:30500/sample-web-app
    sync:
      - path: ./apps/sample-web-app:/app
    ports:
      - port: 3000
        forward: 3000

注: これらは実行可能な完全設定ではなく、概念を示す例です。

以下は各ツールのGitHub Starの推移です。

それぞれ特長がありますが、柔軟性と使いやすさから、ここではTiltを推奨します。

Tiltfileの概要

sample-web-appのTiltfileは次のような構成です。

allow_k8s_contexts(k8s_context())

config.define_string('registry')
config.define_bool('port-forward')
config.define_string('extra-values-file')
config.define_bool('enable-health-logs')

cfg = config.parse()

registry = cfg.get('registry', 'localhost:30500')
default_registry(registry)

docker_build(
    'sample-web-app-dev',
    '.',
    dockerfile='Dockerfile.dev',
    live_update=[
        sync('.', '/app'),
        run('pnpm install', trigger=['./package.json', './pnpm-lock.yaml']),
    ]
)

values_files = ['./charts/sample-web-app/values-dev.yaml']
extra_values_file = cfg.get('extra-values-file', '')
if extra_values_file:
    values_files.append(extra_values_file)
    print("📝 Using extra values file: " + extra_values_file)

helm_set_values = []
enable_health_logs = cfg.get('enable-health-logs', False)
if enable_health_logs:
    helm_set_values.append('logging.health_request=true')
    print("📵 Health check request logs enabled")

helm_release = helm(
    './charts/sample-web-app',
    name='sample-web-app',
    values=values_files,
    set=helm_set_values,
)
k8s_yaml(helm_release)

enable_port_forwards = cfg.get('port-forward', False)
k8s_resource(
    'sample-web-app',
    port_forwards='13000:3000' if enable_port_forwards else [],
)
if enable_port_forwards:
    print("🚀 Access your application at: http://localhost:13000")
  • いくつかのコマンドライン引数(レジストリなど)を定義
  • イメージのビルド(.dockerignoreを尊重、アプリ編集はlive_updateの同期で高速反映)
  • Helmでデプロイ(環境に応じてvaluesを切替)

Tiltの動作例

tilt upを実行してみましょう。CLIがブラウザのTilt UIを開くよう促し、Kubernetesリソースの状態を一覧できます。

  • sample-web-appリソースを選択すると、docker build / docker push / Helmリリースのログがまとまって見られます。
  • このデモでは前回からコード変更が無いためdocker buildは即完了。
  • アプリがデプロイされたらブラウザで到達性を確認。
  • Live Update: タイトルを「ToDo App Test」に変更して保存すると、手動操作無しで即反映されます。
  • Dockerfileを変更すると、差分レイヤーのみ再ビルド。push完了後に自動で再デプロイされます。
  • Helmのvaluesを更新すると、レンダリング・適用が自動で行われ、ロールアウト完了後に新しい設定が有効になります。

Kubernetes CLI生産性向上

日々の作業でキー入力を減らし、見やすく、すばやく状態を把握するための設定です。~/.zshrcに追記して使えます。

オートコンプリート

リソース種別・名前の打ち間違いを減らし、操作を高速化します。zshを使用している場合は次を追加します。

autoload -Uz compinit
compinit

eval "$(kubectl completion zsh)"

使い方: kubectl get pod <TAB>のようにTabで補完できます。

kubecolor + エイリアス

色付き出力でテーブルが読みやすくなります。kubectlkubecolorにエイリアスし、補完の紐付けも維持します。

alias kubectl=kubecolor
compdef kubecolor=kubectl
alias k=kubecolor
compdef kubecolor=kubectl

短いエイリアスkは頻繁な利用で有効です。

zshグローバルエイリアス

zshのグローバルエイリアスは行のどこでも展開できます。kubectlの可読性を高めます。

# YAMLをbatで強調表示
alias -g Y='-o yaml | bat -l yaml'

# ワイド表示
alias -g W='-o wide'

# Neovimの読み取り専用バッファでYAMLを開く
alias -g YE='-o yaml | nvim -c ":set ft=yaml" -R'

使用例:

# 通常(エイリアス無し)
kubectl get deploy sample-web-app -o yaml | bat -l yaml

# 強調表示付きでYAMLを表示
kubectl get deploy sample-web-app Y

# ワイド表示
kubectl get pods W

W-o wideの短縮です。YEYと同様ですが、ページャーの代わりにNeovimで開きます。

viddyで監視

KDashk9sのようなダッシュボードも便利ですが、素早い確認にはkubectl + viddyをよく使います。viddyはコマンドを再実行し、出力の変化をハイライト表示、過去のタイムスタンプの出力も遡って閲覧できます。

kubectlと組み合わせるためのエイリアス:

alias vk='viddy -dtw kubectl'

コンテキスト/ネームスペース切替

kubectxとkubensでコンテキストやネームスペースの切替を素早く行えます。

sternで複数Podのログを一括確認

sternは複数のPod(およびコンテナー)のログを同時にストリームできるKubernetes向けログテーラーです。ラベルセレクターや正規表現、ネームスペース横断、色分け・グルーピングに対応し、デプロイや障害対応の追跡に役立ちます。

# ラベルで絞り込み(名前空間を指定)
stern -n default -l app=sample-web-app

# 複数アプリを正規表現で、タイムスタンプ付き
stern -n default 'web|api' -t

# 直近5分のみ、素の行で
stern -n default -l app=sample-web-app --since 5m -o raw

# Pod 内の特定コンテナーにフォーカス
stern -n default -l app=sample-web-app -c web

まとめ

本記事では、Kubernetes上での開発ループの高速化をおこないました。

  • SSH経由のリモートDockerでプライベートレジストリへpush
  • TelepresenceによるDNS/ルーティングの統合(ポートフォワード不要)
  • Tiltによる監視・増分ビルド・ライブ同期・自動デプロイ

これらにより、Kubernetes開発はより速く、よりミスに強くなります。

リソース

Cover

Discussion