オブジェクトの広場はオージス総研グループのエンジニアによる技術発表サイトです

コンテナ・マイクロサービス

Kubernetes活用への道のり

第2回 開発効率向上編 Skaffoldを活用して開発効率を向上しよう
オージス総研 技術部 アドバンストテクノロジセンター
齋藤 宗範
2021年9月21日

本連載ではKubernetesやマイクロサービスを活用するにあたりどんな準備を進めておけばいいか整理します。第2回は開発効率を向上させることができるSkaffoldを紹介します。

1. はじめに

アプリケーションの開発においてKubernetesを活用する事により様々な恩恵を受けられる一方で、コンテナのビルドやレジストリへのイメージの登録など開発の手順は増えてしまいます。開発作業の中ですべての手順に手動で対応してしまうと、開発効率が悪く非常に面倒に感じることでしょう。連載2回目は、Kubernetessを活用したアプリケーション開発における問題を解決し、開発効率を向上させるSkaffoldを紹介します。

本記事では各技術要素(Kubernetes/Docker/Minikube等)の基本的な概要などについては記載しておりません。適宜各公式ドキュメントを参照してください

2. Skaffold

Skaffoldは継続的、効率的にKubernetes-nativeなアプリケーション開発を行うためにGoogleによって開発された開発ツールです。2021年9月時点での最新バージョンは1.31.0です。

2.1 開発の流れとSkaffoldの自動化

以下の図はローカル環境でコードの編集からアプリケーションをデプロイして動作確認するまでの主な流れにります。Skaffoldはソースコードの変更を自動検知して、図中の青枠の処理を開発者に代わって実行してくれます。開発者は煩雑なコマンドの実行などから開放され、コードの開発に集中し、開発効率を向上させることができます。

architecture

図中の各ステップについて以下に説明します。

  • コンテナイメージのビルド
    開発したソースコードからコンテナイメージのビルドを行います。SkaffoldはDockerfileによるビルド実行だけでなく、JibやBazelなどのビルドツールをサポートしています。独自スクリプトを実行することも可能で、必要に応じてイメージビルドの前処理など、ユーザーのニーズに合わせたビルドを実行することができます。

  • テスト
    テストの実行を行います。Skaffoldでのテストは2種類定義されています。一つはGoogleのcontainer-structure-testを利用したコンテナのテストの実行です。container-structure-testの詳細については公式を参照してください。もう一つはCustome Testと呼ばれ、アプリケーションのソースコードのテストの実行をサポートします。

  • コンテナイメージのpush
    設定ファイルに定義されているコンテナレジストリにビルドしたイメージをpushします。ローカルでMinikubeを利用して開発している場合はMinikubeのDockerレジストリを利用することもできます。

  • デプロイ
    Kubernetesのマニフェストファイルを適用しビルドしたコンテナイメージをデプロイします。SkaffoldはHelmKustomizeをサポートしているので、柔軟なマニフェストの管理と実行ができます。複数のマニフェストをまとめて管理することができるので、例えばローカルで開発を行う場合にAWSのlocalstackをアプリケーションと合わせてデプロイすることもできます。

  • リソースの掃除
    Skaffoldを停止すると、Skaffoldは自身がデプロイしたリソースを削除します。動作確認のためデプロイしたコンテナなどを手動で削除する必要がなくなります。

2.2 利用シーン

2.1章に記載した通り、コードの編集から動作確認までの一連の開発作業をローカル環境で繰り返し行う場合にSkaffoldを最大限活用することができます。そのため、実装、単体テストフェーズなどでは利用することはなく、単体テストより後のフェーズ、例えば以下のシーンで利用します。

ローカル環境にアプリケーションをデプロイして
・機能の疎通確認を行う場合
・障害調査・対応を行う場合
・デバッグを行う場合
・機能改修を行う場合

2.3 Skaffoldの実行とモード

Skaffoldは以下のコマンドを実行することで利用することができます。起動時のパラメータを変更することで、開発目的に応じた制御を行う事ができます。

  • skaffold init ・・・実行ディレクトリ配下を解析してSkaffoldの定義ファイルskaffold.yamlを生成します。
  • skaffold run ・・・ビルドからデプロイまでを1回実行します。
  • skaffold dev ・・・ソースコードの変更を検知して、ビルドからデプロイを繰り返し実行します。
  • skaffold debug ・・・デバッグポートを有効化してデプロイする。他はskaffold devと同じ。

以下はskaffold devを実行した結果になります。コンテナのイメージビルドからデプロイまで実行し、最後にソースコードの変更の監視を開始しています。この状態でソースコードを変更すると自動的にビルドからデプロイまで実行されます。また、Ctrl+Cを実行すると、デプロイ済みのコンテナを削除してからSkaffoldは停止します。

$ skaffold dev
Listing files to watch...
 - skaffold-example
Generating tags...
 - skaffold-example -> skaffold-example:v1.29.0-31-ga45330606
Checking cache...
 - skaffold-example: Not found. Building
Starting build...
Found [minikube] context, using local docker daemon.
Building [skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/8 : FROM golang:1.15 as builder
1.15: Pulling from library/golang
627b765e08d1: Pulling fs layer
~略(DockerImageのpull)~
5946d17734ce: Pull complete
Digest: sha256:e23b6a841e3491a06d4513667b158a0cba90e313da3deea1cbe021fda417444f
Status: Downloaded newer image for golang:1.15
 ---> 40349a2425ef
Step 2/8 : COPY main.go .
 ---> 9a7e8353b545
~略(Docker build)~
Step 8/8 : COPY --from=builder /app .
 ---> 8f7d0b413ea7
Successfully built 8f7d0b413ea7
Successfully tagged skaffold-example:v1.29.0-31-ga45330606
Starting test...
Tags used in deployment:
 - skaffold-example -> skaffold-example:8f7d0b413ea78528606b984556cdf04e0508a05c904b5e8e04a684f874ec11a8
Starting deploy...
 - pod/getting-started created
Waiting for deployments to stabilize...
Deployments stabilized in 126.71ms
Press Ctrl+C to exit
Watching for changes...

2.4 Skaffoldの定義ファイル

Skaffoldは起動ディレクトリ直下にあるskaffold.yaml定義ファイルを読み込み、設定内容に従って動作します。ここでは設定の中核となる一部の項目についてしか触れていませんが、Skaffoldにはたくさんの便利な設定を行う事ができます。詳細は公式ドキュメントで確認してください。

apiVersion: skaffold/v2beta20
kind: Config
build:
  artifacts:
  - image: skaffold-example # Docker image名。この名称でビルドしタグ付けします。
                            # Dockerfileの指定を省略した場合はSkaffoldがDockerファイルを自動的に検出します。
deploy:
  kubectl:
    manifests:
      - k8s-* # デプロイ時に実行するマニフェストを指定します。ワイルドカード指定可。

2.5 便利な機能

  • Port-fowardオプション
    Skaffoldのコマンド引数にport-forwardオプションを付与して実行すると、Skaffoldがアプリケーションのポートやデバッグポートを自動でport-forwardしてくれます。複数のPODを起動した場合などポート番号が重複する場合はSkaffoldが自動的にポート番号をインクリメントして重複しないように調整してから公開してくれます。

  • Debugモード
    skaffold debugコマンドでSkaffoldを起動することで、デバッグを有効にしてアプリケーションを起動することができます。ただし、debugオプションを付与するだけでは、デバッグモードが有効になりません。例えばJavaの場合は特定の環境変数が設定されていないとデバッグが有効にならないなど利用する言語によって前提条件があるので注意が必要です。 詳細についてはこちらを参照してください。

  • Profile
    複数の環境を利用する場合などにプロファイルを活用して接続先の変更や環境差異を吸収することなどができます。環境変数、Kubernetesの接続先、ビルドパラメータなど様々な定義を行う事ができます。詳細については公式を参照してください。

Skaffoldには今回紹介した機能以外にもたくさんの機能があります。皆さんの開発内容・スタイルに合う機能を探してみてください。開発における問題の解決や開発効率を向上させることができるかもしれません。

2.6 Skaffoldを使ってみよう!

それでは実際にSkaffoldを使ってみましょう。今回は機能改修を行う利用シーンを想定して、Golangで「Hello World!」を出力するWebアプリケーションを準備、Skaffoldをdevモードで起動、最後に機能改修して動作確認するところまで実践してみましょう。なお、マニフェストの管理にはkustomizeを利用します。

環境準備

Skaffoldを利用するための環境準備を行います。今回はコンテナの実行環境としてMinikube、コンテナレジストリはMinikube付属のdockerレジストリを利用します。Kubectl、Minikube、Skaffoldを以下の公式ドキュメントの手順に従いインストールしてください。

ソースコードの準備

  • コードとフォルダ構成
skaffold-hands-on
├ manifest                # Kubernetesのマニフェスト
│  ├ k8s-deploy.yaml      # Deploymentのマニフェスト
│  ├ k8s-svc.yaml         # Serviceのマニフェスト
│  └ kustomization.yaml   # アプリケーションのマニフェストを管理するkustomizeの設定ファイル
├ Dockerfile              # コンテナイメージをビルドするためのDockerfile。
├ main.go                 # Hello Worldを応答するGolangのWebアプリケーションコード。
└ skaffold.yaml           # Skaffoldの設定ファイル。skaffold initで自動生成。
  • コードの内容

main.go
パス「/」にhttpでアクセスすると「Hello World!」を返却するGolangのWebアプリケーションコードです。

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello World!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Dockerfile
GolangのWebアプリケーションをコンテナイメージとしてビルドするためのDockerfileです。

FROM golang:1.17-buster as builder
WORKDIR /app
COPY main.go ./
RUN go mod init app
COPY . ./
RUN go build -v -o server
FROM debian:buster-slim
RUN set -x && apt-get update
COPY --from=builder /app/server /app/server
CMD ["/app/server"]

k8s-deploy.yaml
コンテナイメージ「skaffold-hands-on」のPODを1つ起動するデプロイメントの定義です。ポート番号は8080を公開しています。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: skaffold-hands-on
  name: skaffold-hands-on
spec:
  replicas: 1
  selector:
    matchLabels:
      app: skaffold-hands-on
  template:
    metadata:
      labels:
        app: skaffold-hands-on
    spec:
      containers:
      - image: skaffold-hands-on # コンテナイメージはskaffold-hands-on
        name: skaffold-hands-on
        ports:
        - containerPort: 8080    # 8080ポートを公開する

k8s-svc.yaml
デプロイされたskaffold-hands-onをServiceとして公開する定義です。

apiVersion: v1
kind: Service
metadata:
  labels:
    app: skaffold-hands-on
  name: skaffold-hands-on
spec:
  ports: # 8080ポートを公開
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: skaffold-hands-on # skaffold-hands-onのPODを指定。

kustomization.yaml
アプリケーションで利用するマニフェストを管理します。このハンズオンではkustomizeの機能については掘り下げませんが、環境差異の吸収ができるなど、便利な機能がたくさんあります。興味がある方は公式を見てください。

resources:
  - k8s-deploy.yaml  # Deploymentのマニフェスト
  - k8s-svc.yaml     # Serviceのマニフェスト

skaffold initの実行

skaffold-hands-onディレクトリにてinitコマンドを実行して設定ファイルskaffold.yamlを生成します。SkaffoldがDockerfile、マニフェストファイルを探して設定ファイル(skaffold.yaml)を生成してくれます。今回は生成された定義ファイルをそのまま利用します。

$ skaffold init
apiVersion: skaffold/v2beta20
kind: Config
metadata:
  name: skaffold-hands-on
build:
  artifacts:
  - image: skaffold-hands-on
    docker:
      dockerfile: Dockerfile
deploy:
  kustomize:
    paths:
    - manifest
? Do you want to write this configuration to skaffold.yaml? Yes
Configuration skaffold.yaml was writtenon to skaffold.yaml? (y/N) y
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

skaffold devの実行

skaffold-hands-onディレクトリにてskaffold devコマンドを実行してskafflodを実行します。また、--porg-fowardオプションをつける事でMinikubeにデプロイしたServiceをPort-forwardします。

$ skaffold dev --port-forward
Listing files to watch...
 - skaffold-hands-on
~略~
Tags used in deployment:
 - skaffold-hands-on -> skaffold-hands-on:3805bf074eba16d126c72d5a6cd79776f27647af0c55822f4ce1a6562266bf88
Starting deploy...
 - service/skaffold-hands-on created
 - deployment.apps/skaffold-hands-on created
Waiting for deployments to stabilize...
 - deployment/skaffold-hands-on is ready.
Deployments stabilized in 2.375 seconds
Press Ctrl+C to exit
Watching for changes...
Port forwarding service/skaffold-hands-on in namespace default, remote port 8080 -> http://127.0.0.1:8080

Minikubeに「skaffold-hands-on」のPodとServiceがデプロイされている事を確認しましょう。

$ kubectl get all
NAME                                     READY   STATUS    RESTARTS   AGE
pod/skaffold-hands-on-7f74bd5444-67vgd   1/1     Running   0          57s

NAME                        TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
service/skaffold-hands-on   ClusterIP   10.97.94.104   <none>        8080/TCP   57s
~略~

動作確認のためcurlでアプリケーションにアクセスしてみましょう。「Hello World!」が表示されたら、Skaffoldによってアプリケーションがビルドからデプロイまで実行し、port-fowardされたエンドポイントにアクセスしてアプリケーションの動作確認ができたことになります。

$ curl localhost:8080
Hello World!

アプリケーションの機能改修

それでは、main.goファイルの「Hello World!」を「Hello Skaffold!」に編集して保存してみましょう。Skaffoldが変更を検知して、再ビルドしてデプロイまで実行してくれます!

~略~
Press Ctrl+C to exit
Watching for changes...
Generating tags...
 - skaffold-hands-on -> skaffold-hands-on:latest
Some taggers failed. Rerun with -vdebug for errors.
Checking cache...
 - skaffold-hands-on: Not found. Building
Starting build...
Found [minikube] context, using local docker daemon.
Building [skaffold-hands-on]...
Sending build context to Docker daemon  10.24kB
~略~
Successfully built 23ee30dbb341
Successfully tagged skaffold-hands-on:latest
Starting test...
Tags used in deployment:
 - skaffold-hands-on -> skaffold-hands-on:23ee30dbb341d2ff7a24b506bf7a444c04a4caeecc8c12625aa77f4b154231cd
Starting deploy...
 - deployment.apps/skaffold-hands-on configured
Waiting for deployments to stabilize...
 - deployment/skaffold-hands-on is ready.
Deployments stabilized in 2.314 seconds
Port forwarding service/skaffold-hands-on in namespace default, remote port 8080 -> http://127.0.0.1:8080
Watching for changes...

curlでアプリケーションにアクセスしてみましょう。「Hello World!」ではなく、「Hello Skaffold!」が表示されたら、修正後のアプリケーションの動作確認ができた事になります。

$ curl localhost:8080
Hello Skaffold!

Skaffoldの停止

最後にCtrl+CでSkaffoldを停止してみましょう。ServiceとDeploymentを削除してからSkaffoldが停止しています。

~略~
Starting deploy...
 - service/skaffold-hands-on created
 - deployment.apps/skaffold-hands-on created
Waiting for deployments to stabilize...
 - deployment/skaffold-hands-on is ready.
Deployments stabilized in 1.297 second
Port forwarding service/skaffold-hands-on in namespace default, remote port 8080 -> http://127.0.0.1:8080
Press Ctrl+C to exit
Watching for changes...
Cleaning up...
 - service "skaffold-hands-on" deleted
 - deployment.apps "skaffold-hands-on" deleted

3. おわりに

今回はKubernetesを基盤としたシステムを開発していく上で課題となる開発環境の複雑化や手順が増えることに対して、Skaffoldを活用した開発方法を紹介しました。Kubernetesを活用していくにはアプリケーションのコンテナ化やローカル環境構築の難しさなど、これまでにはなかった課題も出てきますが、今回のようなプロダクトをうまく活用していけば負荷の軽減は可能です。

次回はサービス間の疎通やテスト、デバッグなどを効率よく行うためのツールであるTelepresenceを紹介します。Skaffoldだけでなく、必要に応じて適切なツールを活用し、Kubernetesを基盤としたシステムの開発をより簡単に、より快適に行えるようになりましょう。