Skip to main content

In this article

SnykとTiltでKubernetesアプリケーション開発を安全に

2021年1月11日

0 分で読めます

Kubernetesアプリケーションの開発は簡単ではありません。多くの場合、多くの要素が絡み合うマイクロサービスアーキテクチャを扱い、それらを連携させるためのクラスター設定も開発する必要があります。さらに、迅速な反復とテストのワークフローが複雑になり、エンジニアリングチームにとって管理が難しくなることもあります。

そこで役立つのがTiltのようなツールです。

Tiltとは?

Tiltは、Kubernetesアプリケーションを開発するデベロッパーに最適なツールです。高度にカスタマイズできるローカルデプロイワークフローを提供し、作業中もリアルタイムで更新されます。すべてのデベロッパーが共有する一元管理された環境とは異なり、Tiltでは各デベロッパーが独自のローカル環境を利用できます。デプロイは非常に高速で、各自のワークフローをカスタマイズできます。

TiltはStarlarkベースのTiltfileで設定します。カスタマイズ可能なGUIとCLI環境に加え、充実したAPIも提供します。これにより、ワークフローやアプリケーション開発中に表示する情報を柔軟に定義できます。また、Tiltはカスタム拡張機能にも対応しており、開発環境の設定や実行方法をさらに自由に選べます。

同僚のShai Mendelは、Tiltfile内でSnykを実行できるTilt拡張機能の開発に貢献しました。最近、私もこの拡張機能を更新し、SnykをTiltのワークフローに組み込むさまざまな方法を試してみました。仕組みを見ていきましょう。

始めるには

まず、レジストリとともにKubernetesクラスターをローカルにデプロイします。Tiltには、これを簡単に行うためのツールが用意されています。Ctlptlは、miniKube、Kind、Docker Desktopなど、Kubernetesをローカルで実行するさまざまな方法に対応しています。ここではKindを使います。事前にインストールしておきましょう。

matt@Mattbook kind % brew install tilt-dev/tap/ctlptl
matt@Mattbook kind % ctlptl create cluster kind --registry=ctlptl-registry

このコマンドを実行すると、Kindを使ってKubernetesクラスターをデプロイし、ローカルのDockerにレジストリもデプロイして、Kubernetesから利用するために必要な接続設定をすべて行います。コマンドの完了後、Dockerで両方がデプロイされていることを確認できます。

matt@Mattbook 2-optimized-dockerfile % docker ps
CONTAINER ID   IMAGE                  COMMAND                  CREATED         STATUS         PORTS                       NAMES
2f38ce48a6c9   kindest/node:v1.19.1   "/usr/local/bin/entr…"   2 minutes ago   Up 2 minutes   127.0.0.1:61091->6443/tcp   kind-control-plane
e61df5496e19   registry:2             "/entrypoint.sh /etc…"   3 minutes ago   Up 3 minutes   0.0.0.0:60923->5000/tcp     ctlptl-registry

次に、テスト用のサンプルアプリケーションをクローンします。Tiltが提供するNode.jsのサンプルを使います。このアプリケーションには、Tiltを使う際のベストプラクティスを示すTiltfileのサンプルが複数含まれており、それぞれを解説するブログ記事もあります。

この記事では、最適化されたDockerfileのサンプルを使います。用意されているTiltfileを見てみましょう。

matt@Mattbook 2-optimized-dockerfile % cat Tiltfile 
# -*- mode: Python -*

k8s_yaml('kubernetes.yaml')
k8s_resource('example-nodejs', port_forwards=8000,
    resource_deps=['deploy']
)

# Records the current time, then kicks off a server update.
# Normally, you would let Tilt do deploys automatically, but this
# shows you how to set up a custom workflow that measures it.
local_resource(
    'deploy',
    'python now.py > start-time.txt',
)

docker_build('example-nodejs-image', '.')

このTiltfileの内容を詳しく見ていきましょう。docker_build関数で、このフォルダー内のDockerfileからexample-node-js-imageをビルドします。また、kubernetes.yamlファイルを使ってk8s_yamlリソースを作成し、ポート8000へのポートフォワードを設定したk8s_resourceを関連付けます。さらに、現在時刻をファイルに出力するlocal_resourceを定義し、これとk8s_resourceの間に依存関係を作成しています。これにより、リソースの更新前に必ず実行され、デプロイにかかる時間を測定できます。Tiltfile APIでサポートされる関数の詳細は、ドキュメントをご覧ください。

また、アプリに必要な依存関係をyarnでインストールするDockerfileもあります。

matt@Mattbook 2-optimized-dockerfile % cat Dockerfile 
FROM node:10

WORKDIR /app

ADD package.json .
ADD yarn.lock .
RUN yarn install

ADD . .

ENTRYPOINT [ "node", "/app/index.js" ]

アプリケーション自体は非常にシンプルなNode.jsアプリケーションで、ポート8000にウェブページを表示するだけです。

matt@Mattbook 2-optimized-dockerfile % cat index.js 
/*
 Gratefully adapted from https://github.com/HemingwayLee/sample-mustache-express
*/const fs = require('fs');

const express = require('express');
const app = express();
const mustacheExpress = require('mustache-express');

let timeSince = 'N/A';

app.engine('mustache', mustacheExpress());

app.set('view engine', 'mustache');
app.set('views', __dirname + '/views');
app.use(express.static('public'));

app.get('/', (req, res) => {
    res.render('index.mustache', {
        time: timeSince,
    });
});

app.listen(8000, () => {
    timeSince = getSecsSinceDeploy();
    console.log('Server running at http://localhost:8000/');
});

function getSecsSinceDeploy() {
    let curTimeMs = new Date().getTime();
    let contents = fs.readFileSync('/app/start-time.txt', 'utf8');
    let startTimeMs  = parseInt(contents.trim()) / 10**6;
    return ((curTimeMs - startTimeMs) / 10**3).toFixed(2)

クラスターをデプロイできたので、Tiltを起動しましょう。Tiltfileがあるフォルダーで、次のコマンドを実行します。

matt@Mattbook 2-optimized-dockerfile % tilt up
Tilt started on http://localhost:10350/
v0.17.13, built 2020-12-02

(space) to open the browser
(s) to stream logs (--stream=true)
(t) to open legacy terminal mode (--legacy=true)
(ctrl-c) to exit

ターミナルでTiltを実行したら、スペースキーを押してブラウザーでTilt UIを開きます。

Kubernetes YAMLの注入、ロールアウトの追跡、サービスステータスのパネルを表示する、暗色のブラウザベースのデプロイログ

TiltはTiltfileを実行し、定義済みのすべてのリソースを起動します。この例では、イメージをビルドしてKubernetesにデプロイし、アプリケーションにアクセスするためのポートフォワードを作成します。

kubectlを使うと、アプリケーションを実行するPodが稼働していることを確認できます。

matt@Mattbook snyk % kubectl get pods
NAME                              READY   STATUS    RESTARTS   AGE
example-nodejs-7577d84888-d6q5t   1/1     Running   0          3m6s

http://localhost:8000にアクセスすると、アプリケーションを確認できます。

稲妻の上に3匹の猫のイラストと「Hello cats!」の文字、デプロイの所要時間が表示されたブラウザページ。

Tiltの実行中に監視対象のディレクトリやファイルを変更すると、Tiltfileが再実行され、アプリケーションが自動的にビルドされて再デプロイされます。

では、このワークフローにSnykを組み込む方法を見ていきましょう。まだインストールしていない場合は、Snyk CLIをインストールし、無料のSnykアカウントに登録してください。

まず、TiltfileでSnyk拡張機能を読み込みます。

# -*- mode: Python -*

load('ext://snyk', 'snyk')

k8s_yaml('kubernetes.yaml')
k8s_resource('example-nodejs', port_forwards=8000,
    resource_deps=['deploy']
)

# Records the current time, then kicks off a server update.
# Normally, you would let Tilt do deploys automatically, but this
# shows you how to set up a custom workflow that measures it.
local_resource(
    'deploy',
    'python now.py > start-time.txt',
)

docker_build('example-nodejs-image', '.')

この操作を行うと、TiltがTiltfileを自動的に再実行するのがわかります。アプリケーションのフォルダーにはSnyk拡張機能を含むtilt_modulesディレクトリが作成されます。拡張機能もTiltfileであり、メインのTiltfileに読み込まれます。

matt@Mattbook 2-optimized-dockerfile % ls tilt_modules 
extensions.json snyk

Snyk拡張機能を読み込んだら、TiltのワークフローにSnykを追加できます。この拡張機能はSnyk CLIで利用できる3つのモードすべてに対応しているため、コンテナ、パッケージの依存関係、IaCファイル(この例ではKubernetes YAML)をスキャンできます。また、できる限り柔軟に設計されており、Tilt内でさまざまなワークフローを構築できます。

まず、Kubernetes YAMLのスキャンを見てみましょう。TiltfileでSnyk拡張機能を呼び出し、kubernetes.yamlファイルをスキャンするように設定します。

snyk('kubernetes.yaml', 'iac', 'snyk-iac-manual', mode='info', trigger=’manual’)

拡張機能ではさまざまな引数を利用できます。詳しくはREADMEをご覧ください。ここでは対象をkubernetes.yamlに指定し、IaCスキャンを選択して、このリソースに名前を付け、モードをinfo、トリガーをmanualに設定しています。modeフラグにはinfoまたはgateを指定できます。infoモードでは、脆弱性が見つかっても常に終了コード0を返すため、Tilt UIで失敗として扱われません。triggerをautoにすると、Tiltがkubernetes.yamlの変更を検知するたびにスキャンが実行されます。manualモードの場合は、Tilt UIから実行する必要があります。

Tiltfileを保存すると、Tilt UIのサイドバーにsnyk-iac-manualという新しいリンクが表示されます。

Kubernetesのデプロイ手順、Podのロールアウト状況、実行中のサーバーログを表示するダークテーマのターミナルダッシュボード

この項目には更新ボタンがあり、手動で実行できることを示しています。実行してみましょう。

KubernetesのYAMLファイルをスキャンするSnyk IaCのテストを実行し、Infrastructure as Codeのセキュリティ問題を一覧表示しているターミナル

実行すると、kubernetes.yamlに対してSnyk CLIが呼び出され、スキャン結果がUIに表示されます。このファイルにいくつかの潜在的なセキュリティ問題が見つかっていますが、モードをinfoに設定しているため、テストは成功として表示されています。では、gateに設定するとどうなるか見てみましょう。Tiltfileでこのチェックの設定を変更します。

snyk('kubernetes.yaml', 'iac', 'snyk-iac-manual', mode='gate', trigger='manual')

Tiltfileの再実行後、まずUIで、手動チェックの更新ボタンが強調表示されていることがわかります。他のステージと同期していないため、更新が必要です。

Tilt UIのリソースパネルを拡大表示。ALLフィルターと、「(Tiltfile)」「example-nod...」「deploy」「snyk-iac-manu...」などの最近の項目が表示されている

変更した設定で再実行してみましょう。

Kubernetesのデプロイログ、失敗したコマンド、コンテナのステータス一覧が表示されたダークテーマのターミナル画面。

リソースをgateに設定したため、Tiltはこれをビルド失敗として検出します。

次に、Tiltのワークフローにコンテナスキャンを組み込む方法を見ていきましょう。Tiltでは不変タグが使われる点に注意が必要です。ビルドごとに一意のタグが付けられ、そのタグはKubernetesにも引き継がれます。これはデプロイを高速化するためです。詳しくはこちらをご覧ください。

デフォルトのdocker_build関数を使うと、Docker内のイメージに多くの不変タグが付いているのがわかります。

matt@Mattbook 2-optimized-dockerfile % docker images
REPOSITORY                             TAG                     IMAGE ID       CREATED          SIZE
localhost:60923/example-nodejs-image   tilt-d30fcbf90266f17c   d30fcbf90266   2 minutes ago    918MB
localhost:60923/example-nodejs-image   tilt-7285dc3648babacf   7285dc3648ba   6 minutes ago    918MB
localhost:60923/example-nodejs-image   tilt-b897a2c5ce4f7431   b897a2c5ce4f   10 minutes ago   918MB
localhost:60923/example-nodejs-image   tilt-a470f2969ce1ee4c   a470f2969ce1   11 minutes ago   918MB
localhost:60923/example-nodejs-image   tilt-9d3e699a8bfe9d37   9d3e699a8bfe   24 minutes ago   918MB
localhost:60923/example-nodejs-image   tilt-2a3065202354b6c4   2a3065202354   26 minutes ago   918MB
localhost:60923/example-nodejs-image   tilt-33de7ba78c394971   33de7ba78c39   34 minutes ago   918MB

このタグをスキャンする際には課題もあります。現在、Tiltfile内の別の場所からこれらのタグにアクセスする組み込みの方法がないため、Snykでスキャンするにはイメージタグを把握する必要があります。

ただし、これに対応する方法はいくつかあります。Tiltには独自のビルドステップを定義する関数が用意されているので、これを使ってワークフローを構築できます。

まず、手動スキャンのステップを設定してみましょう。ここではイメージタグを可変にし、タグを固定して、すべてのビルドで同じタグを使用します。その後、Tiltがデプロイ前にビルドへ不変タグを付け直します。

Tiltfileからdocker_build関数を削除し、custom_buildに置き換えます。

custom_build('example-nodejs-image', 'docker build -t $EXPECTED_REF .', ['.'], tag='dev')

ここでは、docker_build関数と同じようにDockerを呼び出しますが、特殊な環境変数$EXPECTED_REFを追加しています。また、tag=’dev’,を指定しています。Tiltはこれを使って環境変数に値を設定します。Tiltfileを保存すると、Tiltが可変タグを使ってイメージをビルドし、その後タグを付け直すことがわかります。

matt@Mattbook 2-optimized-dockerfile % docker images
REPOSITORY                             TAG                     IMAGE ID       CREATED          SIZE
example-nodejs-image                   dev                     ffea1aaff7dd   10 seconds ago   918MB
localhost:60923/example-nodejs-image   tilt-ffea1aaff7ddcc22   ffea1aaff7dd   10 seconds ago   918MB

ここでは、既知のタグを対象に指定し、モードをcontainer、スキャン名を設定し、modeをinfoにしています。また、Dockerfileがディレクトリ内にあるため、ベースイメージの推奨を得るためにextra_opts引数にも追加できます。

フィルターフィールドと、(Tiltfile)、example-nodejs、deploy、snyk-cnr-manualの4つのリソースが表示されたダークテーマのリソースダッシュボード。

これでサイドバーに手動実行のコンテナスキャンが表示され、実行できるようになりました。

テスト済みの依存関係、脆弱性の件数、深刻度を一覧表示したコンテナイメージの脆弱性スキャンレポート。

手動でコンテナスキャンを実行できました。では、Tiltのビルドごとに自動実行するにはどうすればよいでしょうか。Tiltではビルド前後の依存関係の扱いがまだ十分に成熟していないため、少し難しいケースです。custom_build関数内からSnykを直接呼び出すこともできますが、この拡張機能は、ユーザーが柔軟にSnykスキャンをワークフローに組み込めるよう設計されています。

まず、custom_build関数を変更します。tag=’dev’引数を削除すると、$EXPECTED_REFにはTiltが自動生成する不変タグが設定されます。各ビルドでそのタグを取得し、ファイルシステム上のファイルに書き込みます。タグを書き込む際に末尾に改行が入らないよう、ここではprintfを使っています。

custom_build('example-nodejs-image', 'docker build -t $EXPECTED_REF . && printf $EXPECTED_REF > /tmp/ref.txt', ['.'])

また、ファイルからタグを取得する関数も追加します。read_file関数はblobを返すため、文字列にキャストし、改行も取り除く必要があります。

def get_tag ():
return str(read_file('/tmp/ref.txt', default=''))

次にSnyk拡張機能を呼び出し、get_tag関数の出力を対象に設定し、トリガーをautoにします。さらに、ファイルシステム上のファイルへの依存関係を作成することで、ファイルが変更されるたびに拡張機能が実行されるようにします。

snyk(get_tag(), 'container', 'snyk-cnr-auto', test_deps='ref.txt', mode='info', extra_opts='--file=Dockerfile', trigger='auto')

Tiltfileをもう一度保存すると、ビルドが実行されるたびに、スキャンも自動的に

Snykのスキャンを開始します。

デプロイログを表示するターミナルダッシュボード。Tiltfileの変更により新しいスキャンが開始されています

最後に、アプリケーションのソースコードの依存関係をスキャンしてみましょう。これはパッケージマネージャーにyarnを使うNode.jsアプリケーションなので、フォルダー内に使用しているモジュールのバージョンを示すyarn.lockファイルがあります。

matt@Mattbook 2-optimized-dockerfile % ls
Dockerfile Tiltfile index.js kubernetes.yaml now.py package.json public ref.txt start-time.txt tilt_modules views yarn.lock

Snykで自動的にスキャンし、脆弱性を確認できます。

snyk('.', 'oss', 'snyk-oss-auto', mode='info', trigger='auto')
Tiltのビルドと更新が成功したことを示すダークテーマのターミナル画面。脆弱性スキャンの結果、既知の脆弱なパスは検出されていません

このアプリケーションは非常にシンプルで、依存関係に脆弱性は見つかりませんでした。問題ありません。

Tiltは、Kubernetes向けのマイクロサービスベースのアプリケーションのローカル開発を大幅に効率化できる優れたツールです。設定の自由度も高く、自分に合ったワークフローを実現できます。

この記事では、Tilt向けのSnyk拡張機能を使って、TiltのワークフローにSnykスキャンを組み込むさまざまな方法を紹介しました。クラウドネイティブアプリケーションと設定を安全に保ちましょう。無料のSnykアカウントに登録して、ぜひお試しください。

CTFを始めよう

オンデマンドのバーチャル入門ワークショップで、CTFチャレンジの解き方を学びましょう。

続きを読む

blog feature snyk container custom base recommendations
Blog

Evo Agentic AppSecのご紹介:AI開発のスピードに対応するセキュリティ

AI駆動型開発のスピードに、従来のセキュリティワークフローは追いついていません。SnykのAgentic AppSecは、セキュリティエージェントが脆弱性を継続的に検出、修正、検証し、人が基準を定められるようにします。

feature insights context
Blog

自律型攻撃はすでに始まっている。防御もそのスピードに追いつかなければならない。

自律型攻撃者によって、防御に使える時間は短くなっています。継続的な検出、修復、検証、予防で、セキュリティチームが攻撃に歩調を合わせる方法をご紹介します。

Blog

AIコーディングエージェントが不適切なアクセス制御を繰り返し実装する理由

AIコーディングエージェントは、コンパイルが通りレビューも通過する一方で、あるテナントのデータを別のテナントに公開してしまう認可ロジックを生成することがあります。不適切なアクセス制御が検出しにくい理由と、その防止策をご紹介します。