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

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

はじめに

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

Kubernetesは非常に強力なアプリケーションプラットフォームです。しかし、コンテナ化したアプリケーションをデプロイするという仕組み上、変更したアプリケーションの動作を確認するまでにコンテナイメージのビルド・アップロード・デプロイというステップが必要になります。そのため、非コンテナ環境と比べると1開発サイクルを回すためにより多くの時間と手数が要求されるという問題があります。

この問題を解決するための開発支援ツールはいくつか存在しています。本記事では、その中でも多くの環境に適用できるであろうSkaffoldTelepresenceについて取り上げます。どちらも同じような目的のために開発されていますが、それぞれ異なったアプローチを取っています。1回目の今回はSkaffoldがどのように開発効率を改善するのかについて解説します。

検証に利用するアプリケーション

検証にはLadicle/sample-k8s-appのアプリケーションを利用します。手元でも確認したい場合はcloneしておいてください。このリポジトリに含まれているアプリケーションはGoで書かれた簡単なHTTPサーバです。次のように/hello/happyの2つのエンドポイントを持っています。

package main

import (
  "io/ioutil"
  "log"
  "net/http"
)

func main() {
  // /hello にアクセスすると "Hello World" を返す
  http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello World\n"))
  })

  // /happy にアクセスすると message.txt の値を返す
  http.HandleFunc("/happy", func(w http.ResponseWriter, r *http.Request) {
    // message.txtには Happy Hacking という文字か書かれている
    msg, err := ioutil.ReadFile("/opt/app/assets/message.txt")
    if err != nil {
      log.Printf("Error: %v", err)
    }
    w.Write(msg)
  })

  // 8080番ポートでHTTPサーバを起動する
  log.Printf("Start server...")
  log.Fatal(http.ListenAndServe(":8080", nil))
}

アプリケーションサーバをコンテナイメージ化するDockerfileは次のとおりです。Dockerのマルチステージビルドを利用しており、1ステージ目でGoのアプリケーションのビルド、2ステージ目でビルドしたバイナリのコピーとアセットファイルのコピーを行っています。

FROM golang:1.14.3-alpine3.11 as builder
COPY main.go .
RUN CGO_ENABLED=0 go build -o /hello main.go

FROM scratch
COPY --from=builder /hello /opt/app/bin/
COPY assets/ /opt/app/assets/
CMD ["/opt/app/bin/hello"]

Skaffoldを使った開発

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

Skaffoldは、Kubernetes上のアプリケーションをローカル上ですばやく繰り返し開発することを目的としたOSSです。開発言語はGoで、Googleによって開発されています。開発効率改善アプローチとしては、コンテナイメージのビルド・アップロード・デプロイといったステップを開発者が意識せず実施できるよう、一括で処理し手数を削減する方法を取っています。ちなみに開発だけでなくプロダクションのデプロイも同じフローでできるように作られていますが、今回は開発で利用するフローのみ解説します。

開発の下準備

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

$ brew install skaffold

Skaffoldは、開発フローの各ステップで何をすべきかを記述したskaffold.yamlファイルを元に処理します。また、この設定ファイルはskaffold initコマンドから生成できます。このコマンドをアプリケーションリポジトリのルートで実行すると、DockerfileやManifestのファイルパスが自動で検出されることが分かります*1

$ skaffold init
apiVersion: skaffold/v2beta3
kind: Config
metadata:
  name: sample-k-s-app
build:
  artifacts:
  - image: ladicle/sample-k8s-app
deploy:
  kubectl:
    manifests:
    - manifests/deploy-myapp.yaml
    - manifests/svc-myapp.yaml

Do you want to write this configuration to skaffold.yaml? [y/n]: y
Configuration skaffold.yaml was written
You can now run [skaffold build] to build the artifacts
or [skaffold run] to build and deploy
or [skaffold dev] to enter development mode, with auto-redeploy
*1:自動生成されたビルドやデプロイ設定、またそれ以外のテストやイメージのアップロード先を変更したい場合は、この段階でskaffold.yamlを編集してください。

開発フローを回す

開発用のアプリケーションデプロイにはskaffold devコマンドを利用します。また--port-forwardフラグを付与することで、アプリケーションのServiceをポートフォワードできます。このコマンドを実行すると、次の(1)~(3)のようにコンテナイメージのビルド・アップロード・デプロイが一括で行われます。

$ skaffold dev --port-forward
Listing files to watch...
 - ladicle/sample-k8s-app
Generating tags...
   # tagにはGitのコミットハッシュ値に加えて`-dirty`suffixがついている
   # (未コミットのskaffold.yamlが存在するため)
 - ladicle/sample-k8s-app -> ladicle/sample-k8s-app:50b04c5-dirty
Checking cache...
 - ladicle/sample-k8s-app: Not found. Building
# (1) コンテナイメージのビルド
Building [ladicle/sample-k8s-app]...
~~省略~~
Successfully built f4a5b4e8bcc7
Successfully tagged ladicle/sample-k8s-app:50b04c5-dirty
# (2) コンテナイメージのアップロード
The push refers to repository [docker.io/ladicle/sample-k8s-app]
2f530283605c: Pushed
d2b171c7377f: Pushed
50b04c5-dirty: digest: sha256:30dac93513c0a17c3e7ec62ca7f0fabeddac3541877a4dc7fc03662c89e01801 size: 735
# (3) アプリケーションのデプロイ (Manifestの値がビルドしたイメージに差し替わる)
Tags used in deployment:
 - ladicle/sample-k8s-app -> ladicle/sample-k8s-app:50b04c5-dirty@sha256:30dac93513c0a17c3e7ec62ca7f0fabeddac3541877a4dc7fc03662c89e01801
Starting deploy...
 - deployment.apps/myapp created
 - service/myapp created
Waiting for deployments to stabilize...
 - deployment/myapp: waiting for rollout to finish: 0 of 1 updated replicas are available...
 - deployment/myapp is ready.
Deployments stabilized in 6.595635449s
# --port-forwardオプションを付与したため、
# service/myappの80番ポートが127.0.0.1:4504にポートフォワードされた
Port forwarding service/myapp in namespace default, remote port 80 -> address 127.0.0.1 port 4503
Watching for changes...
# Podのログが表示されている
[myapp-796c7b8dcb-bsvzb sample-k8s-app] 2020/05/20 06:27:46 Start server...

別のターミナルを開いてポートフォワードされたポートにリクエストを送信すると、正常にHello Worldが返ることを確認できます。

$ curl 127.0.0.1:4503/hello
Hello World

続いて、プログラムを修正して変更内容が適用されていることを確認します。まずは、sedでHelloこんにちはに書き換えます。

$ sed -i.bak -e s/Hello/こんにちは/ main.go

すると、ファイルの変更を検出したskaffold devが再び同じフローを実行してアプリケーションを更新します。skaffold devコマンドを実行したターミナルに戻ると、以下のようなメッセージが流れていることを確認できます。

...
[myapp-796c7b8dcb-bsvzb sample-k8s-app] 2020/05/20 06:27:46 Start server...
Generating tags...
 - ladicle/sample-k8s-app -> ladicle/sample-k8s-app:50b04c5-dirty
~~同じことが繰り返される~~
Watching for changes...
Port forwarding service/myapp in namespace default, remote port 80 -> address 127.0.0.1 port 4503

[注意]main.goのファイルの変更を契機に再デプロイが走ったのは、Dockerfileの依存ファイルとしてmain.goが認識されていたためです。SkaffoldでDockerfileを利用する場合、依存ファイルとして認識されるのはCOPY/ADDコマンドでイメージにコピーしたファイルのみです*2。そのため、今回のようにマルチステージビルドを使ってmain.goのビルドをコンテナ内にコピーしている場合は問題ありませんが、ビルド済みバイナリをローカルから直接コピーした場合、main.goは監視対象に入らない点に注意してください。

*2https://github.com/GoogleContainerTools/skaffold/blob/f454cb5d81d9ea65643093609f4ce17252f97159/pkg/skaffold/docker/dependencies.go#L46

デプロイが完了したタイミングで再度リクエストを送信すると、修正後の内容が返ってくることを確認できます。

$ curl localhost:4503/hello
こんにちは World

再ビルドせずにファイルを同期する

ファイル同期機能を利用した開発フローのイメージ

Skaffoldでは一連のステップを一括で実施してくれるため開発時の手数を減らせますが、通常通りdockerやkubectlコマンドを利用するときと同じ時間が掛かります。これを回避する方法として、Skaffoldではファイル同期機能(Beta)が提供されています*3。この機能を利用すれば、ビルドなど一連のステップを再実行することなく変更のあったファイルのみコンテナ内に転送できます。この機能は.build.syncフィールドに対して、次のように同期設定を定義すると利用できます。ここではDockerfileから配置先を予測するinferモードを利用していますが、他にも明示的に配置元と先を指定するmanual、自動で同期ファイルを検出するautoモードもあります。

*3https://skaffold.dev/docs/pipeline-stages/filesync/
@@ -5,6 +5,9 @@
 build:
   artifacts:
   - image: ladicle/sample-k8s-app
+    sync:
+      infer:
+      - 'assets/*'
 deploy:
   kubectl:
     manifests:

再ビルドが必要なGoのソースコードの変更は確認できませんが、サンプルアプリケーションのmessage.txtのようにリクエストのたびに動的に読み込まれる値であれば、このファイル同期機能で即座に変更結果を確認できます。

[注意]ファイル同期機能は、標準入力から取得したデータをコンテナ内のtarコマンドを使って展開するという方法をとっています*4。そのため、この機能を利用するコンテナイメージにはtarコマンドが必須です。よって、サンプルアプリケーションのような空のscratchイメージを利用している場合、イメージの修正が必要になります。

*4https://github.com/GoogleContainerTools/skaffold/blob/8e49450228df458b0a505532af0b9d83a647554e/pkg/skaffold/sync/kubectl.go#L49

おわりに

以上のように、Skaffoldはコンテナイメージのビルド・アップロード・デプロイといったステップを自動化することで開発時の手間を減らしてくれることが分かりました。各ステップがなくなったわけではないため処理時間に変化はありませんが、各コマンドを実行する際の待ち時間は削減できます。また、開発者が注意すべきコマンドが1コマンドになるため、イメージの差し替えを忘れて古いイメージで検証してしまったりなどのミスを減らす効果も期待できるしょう。Skaffoldには他にもdebugコマンドなど便利な機能がいくつかありますので、気になった方は調べてみてください。

2回目の次回は、Telepresenceについて解説します。

著者
五十嵐 綾(いがらし・あや)
ゼットラボ株式会社/ヤフー株式会社
通信事業者で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メルマガ会員のサービス内容を見る

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