MoTLab -Mobility Technologies Engineering Blog-MoTLab -Mobility Technologies Engineering Blog-

Skaffold v2でCloud Runの開発を効率化する

ServerSide
December 19, 2022

タクシーアプリ「GO」のバックエンドを開発している柳浦です。最近リリースされたSkaffold v2がCloud Runに対応したので、調べて使ってみました。


この記事はMobility Technologies Advent Calendar 2022の17日目の記事です。

SkaffoldはGoogleが開発した、コンテナ化されたアプリケーションの継続的な開発とデリバリーをサポートするコマンドラインツールです。2018年に公開されました。元々はKubernetesをターゲットとしたツールでしたが、2022年10月に発表されたv2でCloud Runに対応したので、Cloud Runでの使い方を紹介します。

Skaffoldのコンセプト

公式ドキュメント曰く、

Skaffold is a command line tool that facilitates continuous development for container based & Kubernetes applications. Skaffold handles the workflow for building, pushing, and deploying your application, and provides building blocks for creating CI/CD pipelines. This enables you to focus on iterating on your application locally while Skaffold continuously deploys to your local or remote Kubernetes cluster, local Docker environment or Cloud Run project.
<抄訳> Skaffoldはコンテナベースの開発とKubernetesアプリケーションの継続的開発を円滑に行うための手助けをするコマンドラインツールです。ビルド、プッシュそしてデプロイを行うワークフローを管理し、CI/CDパイプラインを構築するためのビルディングブロックを提供します。ローカルあるいはリモートKubernetesクラスタ、ローカルDocker環境またはCloud Runプロジェクトへ継続敵にデプロイしながら、ローカルでのイテレーションに集中することができます。

との事です。これを鵜呑みにすれば、開発以外の面倒な作業を良い感じにやってくれる(ものを構築できる)可能性をひしひしと感じます。もっと具体的に見ていきましょう。

Skaffoldの提供するfeature

Skaffoldのfeatureは以下の通りに喧伝されています。

  • Fast local Kubernetes Development
    • optimized “Source to Kubernetes” - Skaffold detects changes in your source code and handles the pipeline to buildpushtest and deploy your application automatically with policy-based image tagging and highly optimized, fast local workflows
    • continuous feedback - Skaffold automatically manages deployment logging and resource port-forwarding
  • Skaffold projects work everywhere
    • share with other developers - Skaffold is the easiest way to share your project with the world: git clone and skaffold run
    • context aware - use Skaffold profiles, local user config, environment variables, and flags to easily incorporate differences across environments
    • platform aware - use cross-platform and multi-platform build support, with automatic platform detection, to easily handle operating system and architecture differences between the development machine and Kubernetes cluster nodes.
    • CI/CD building blocks - use skaffold buildskaffold test and skaffold deploy as part of your CI/CD pipeline, or simply skaffold run end-to-end
    • GitOps integration - use skaffold render to build your images and render templated Kubernetes manifests for use in GitOps workflows
  • skaffold.yaml - a single pluggable, declarative configuration for your project
    • skaffold init - Skaffold can discover your build and deployment configuration and generate a Skaffold config
    • multi-component apps - Skaffold supports applications with many components, making it great for microservice-based applications
    • bring your own tools - Skaffold has a pluggable architecture, allowing for different implementations of the build and deploy stages
  • Lightweight
    • client-side only - Skaffold has no cluster-side component, so there’s no overhead or maintenance burden to your cluster
    • minimal pipeline - Skaffold provides an opinionated, minimal pipeline to keep things simple

なるほど。幾分特徴が明確になった気がします。特に気になったものを列挙します。

  • ソースコードの変更を検出して、自動的にポリシーベースでイメージにタグ付けを行う
  • 共有可能、つまりgitで構成管理できる
  • プラットフォームの差異を検出して、開発環境とKubernetesクラスタノードとのアーキテクチャの差分をうまいことどうにかしてくれる
  • CI/CDパイプラインのビルディングブロックを提供する。skaffold buildskaffold test、そしてskaffold deploy をCI/CDパイプラインの一部として使うか、skaffold runで一連のワークフローを全て実行できる
  • skaffold renderを使ってイメージをビルドして、テンプレート化されたKubernetesマニフェストをGitOpsワークフローのために提供する
  • プラガブルアーキテクチャで作られているため、自分の好きなツールを使ってビルド・デプロイを行える
  • クライアントサイドのコンポーネントのみ
  • ミニマルな構成のパイプライン—opinionatedなミニマル構成によりシンプルさを保つ

opinionatedな構成というのがどれほどのものなのか気になります。ここを受け入れられるかが肝ではなかろうかと推測します。そしてここまで見てきた上で公式ドキュメントにあるデモ動画を見ると得心が行きました。動画上ではskaffold devコマンドを使ってローカル環境への継続的な自動デプロイを実現していますが、更にそれを推し進めて行くのが想像できます。更にドキュメントを読み進めてみます。

ワークフローとアーキテクチャ

skaffold devを実行すると以下の一連の作業を(一部並行して)Skaffoldは行います。またいずれのステップもスキップ可能です。

  • ソースコードの変更をウォッチする
  • 同期可能とマークされていればファイルを直接podに同期する
  • ソースコードから成果物をビルドする
  • 成果物をタグ付けする
  • 成果物をプッシュする
  • 成果物をデプロイする
  • デプロイされた成果物をモニタリングする
  • 終了時にはデプロイされた成果物をクリーンアップする

もう一つ重要な概念としてprofilesがあります。詳しくは後述しますが、このprofilesを使うと各ステージで利用するツールをフラグによって切り替えることが容易にできます。例えば、開発時にはローカルのDockerを使ってビルドし、kubectlを使ってminikubeへデプロイして、プロダクション環境へはCloud BuildでビルドしたイメージをHelmでリモートのKubernetesクラスタにデプロイするという具合にツールを切り替える事が可能です。各ステージ毎に利用可能なツールを示したを公式ドキュメントから引用します。

An image from Notion

なるほど、正直この図だけで今までの話はよかったのではないかという位わかりやすいですね。少し気分を入れ替えてskaffoldコマンドを見てみます。

skaffoldコマンド

何はともあれskaffoldコマンドを叩いてみます。

❱ skaffold
A tool that facilitates continuous development for Kubernetes applications.

  Find more information at: <https://skaffold.dev/docs/getting-started/>

End-to-end Pipelines:
  run               Run a pipeline
  dev               Run a pipeline in development mode
  debug             Run a pipeline in debug mode

Pipeline Building Blocks:
  build             Build the artifacts
  test              Run tests against your built application images
  deploy            Deploy pre-built artifacts
  delete            Delete any resources deployed by Skaffold
  render            Generate rendered Kubernetes manifests
  apply             Apply hydrated manifests to a cluster
  verify            Run verification tests against skaffold deployments

Getting Started With a New Project:
  init              Generate configuration for deploying an application

Other Commands:
  completion        Output shell completion for the given shell (bash or zsh)
  config            Interact with the global Skaffold config file (defaults to `$HOME/.skaffold/config`)
  diagnose          Run a diagnostic on Skaffold
  fix               Update old configuration to a newer schema version
  schema            List JSON schemas used to validate skaffold.yaml configuration
  survey            Opens a web browser to fill out the Skaffold survey
  version           Print the version information

Usage:
  skaffold [flags] [options]

Use "skaffold <command> --help" for more information about a given command.
Use "skaffold options" for a list of global command-line options (applies to all commands).

なるほど。パイプラインを実行するサブコマンドがrun/dev/debugで、builddeployなどでパイプラインを構成するステップを実行できるようです。また、initサブコマンドが設定を自動生成してくれるようです。ここから始めてみます。

Cloud Runで使ってみる

まずはskaffold initから始めます。

❱ skaffold init
one or more valid build configuration must be present to build images with Skaffold; please provide at least one build config and try again, or run `skaffold init --skip-build`

怒られました。ヘルプを参照します。

❱ skaffold init --help
Generate configuration for deploying an application

Options:
      --analyze=false: Print all discoverable Dockerfiles and images in JSON format to stdout
  -a, --artifact=[]: '='-delimited Dockerfile/image pair, or JSON string, to generate build artifact
(example: --artifact='{"builder":"Docker","payload":{"path":"/web/Dockerfile.web"},"image":"gcr.io/web-project/image"}')
      --assume-yes=false: If true, skaffold will skip yes/no confirmation from the user and default to yes
      --compose-file='': Initialize from a docker-compose file
      --default-kustomization='': Default Kustomization overlay path (others will be added as profiles)
  -f, --filename='skaffold.yaml': Path or URL to the Skaffold config file
      --force=false: Force the generation of the Skaffold config
      --generate-manifests=false: Allows skaffold to try and generate basic kubernetes resources to get your project
started
  -k, --kubernetes-manifest=[]: A path or a glob pattern to kubernetes manifests (can be non-existent) to be added to
the kubectl deployer (overrides detection of kubernetes manifests). Repeat the flag for multiple entries. E.g.: skaffold
init -k pod.yaml -k k8s/*.yml
  -m, --module=[]: Filter Skaffold configs to only the provided named modules
      --remote-cache-dir='': Specify the location of the git repositories cache (default $HOME/.skaffold/repos)
      --skip-build=false: Skip generating build artifacts in Skaffold config
      --sync-remote-cache='always': Controls how Skaffold manages the remote config cache (see `remote-cache-dir`). One
of `always` (default), `missing`, or `never`. `always` syncs remote repositories to latest on access. `missing` only
clones remote repositories if they do not exist locally. `never` means the user takes responsibility for updating remote
repositories.

Usage:
  skaffold init [flags] [options]

Use "skaffold options" for a list of global command-line options (applies to all commands).

go.modがあればいいんでしょうか。単純なWebアプリケーションxを作ってgo.modを追加します。x/go.modを用意して再チャレンジです。

one or more valid Kubernetes manifests are required to run skaffold

やっぱり怒られたので心を落ち着かせてソースコードを読みます。……現在はまだCloud Run対応していないようですので一旦skaffold initを使うのは諦めます。skaffold.yamlを準備しましょう。skaffold.yamlのスペックを見てやっていきます。とりあえずskaffold.yamlはこうしました。

apiVersion: skaffold/v3
kind: Config
metadata:
  name: skaffold-test
  labels: {}
  annotations: {}
requires:
- configs: []
profiles:
- name: dev
  manifests:
    rawYaml:
    - run-dev.yaml
build:
  tagPolicy:
    sha256: {}
  artifacts:
  - image: x
    ko:
      dir: ./x
      main: .
      dependencies:
        paths:
        - "**/*.go"
deploy:
  cloudrun:
    projectid: PROJECT_ID_YOU_USE
    region: asia-northeast1

とりあえずこんな感じで簡単に作ってみました。Goのソースは極めて単純なHTTPサーバです。metadata などはとりあえず適当に、profilesもとりあえずdevだけで作りました。ビルドには時間がかかると嫌なのでkoを使っています。また、tagPolicyはとりあえずsha256にして、latestをpushしてそのdigestを使ってイメージを指定する形にしました。run-dev.yamlはこんな感じです。

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: x
spec:
  template:
    spec:
      containers:
      - image: x

また、現在のファイルツリーはこんな感じです。

.
├── run-dev.yaml
├── skaffold.yaml
└── x
    ├── go.mod
    └── main.go

実際のところ、xというコンテナイメージはGoogle Cloud Artifact Registryにpushした上で、更にそれをデプロイしたいです。SkaffoldドキュメントのImage Repository Handlingページに解決策が書いてあり、default-repo機能というものを使えばよさそうです。default-repoを使うには3つ方法があり、skaffoldコマンドにフラグとして渡すか、SKAFFOLD_DEFAULT_REPO環境変数で指定するか、またはSkaffoldのグローバルコンフィグ(skaffold configコマンドで管理される)を使うかです。最終的に書き換えられるイメージ名は、default-repoで指定したドメイン名とイメージの名前によって生成戦略が変わります。詳しくは先述のドキュメントを参照してください。さて、今回はdirenvを使って手軽にdefault-repoを指定します。.envrcに以下の内容を書き込みます。

export PROJECT_ID=PROJECT_ID_YOU_USE
export KO_DOCKER_REPO=asia-northeast1-docker.pkg.dev/${PROJECT_ID}/docker-private
export SKAFFOLD_DEFAULT_REPO=${KO_DOCKER_REPO}

koも同様の設定を渡す必要があるのでついでに設定しました。Go言語でアプリケーションを作成している場合には爆速でDockerイメージが作成できるのでkoはおすすめです。なお、Cloud Artifact Registryのリポジトリは事前に作っておく必要があります。これでskaffold run --profile=devを実行してみれば、Cloud Runへのデプロイまでが実行されるはずです。

❱ skaffold run --profile=dev
WARN[0000] failed to detect active kubernetes cluster node platform. Specify the correct build platform in the `skaffold.yaml` file or using the `--platform` flag  subtask=-1 task=DevLoop
Generating tags...
 - x -> asia-northeast1-docker.pkg.dev/*****/docker-private/x:latest
Checking cache...
 - x: Not found. Building
Starting build...
Building [x]...
Using base gcr.io/distroless/static:nonroot@sha256:ed05c7a5d67d6beebeba19c6b9082a5513d5f9c3e22a883b9dc73ec39ba41c04 for github.com/yanana/x
Using build config x for github.com/yanana/x
Building github.com/yanana/x for linux/amd64
Publishing asia-northeast1-docker.pkg.dev/*****/docker-private/x:latest
Published asia-northeast1-docker.pkg.dev/*****/docker-private/x@sha256:1ccfff297255c31e78041987b3504f7fb5317d48c0f32b18d6f78fdf21649e3d
Build [x] succeeded
Starting test...
Tags used in deployment:
 - x -> asia-northeast1-docker.pkg.dev/*****/docker-private/x:latest@sha256:1ccfff297255c31e78041987b3504f7fb5317d48c0f32b18d6f78fdf21649e3d
Starting deploy...
Deploying Cloud Run service:
         x
x: Service starting: Deploying Revision. Waiting on revision x-qvmng.
x: Service starting: Deploying Revision. Waiting on revision x-qvmng.
Cloud Run Service x finished: Service started. 0/1 deployment(s) still pending
You can also run [skaffold run --tail] to get the logs

確認するとちゃんとデプロイされていました! なお、Cloud Runにデプロイされるマニフェストはskaffold renderコマンドで確認出来ます。

❱ skaffold render --profile=dev
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
  name: x
  namespace: default
spec:
  template:
    spec:
      containers:
      - image: asia-northeast1-docker.pkg.dev/*****/docker-private/x:latest@sha256:1ccfff297255c31e78041987b3504f7fb5317d48c0f32b18d6f78fdf21649e3d

ちゃんとダイジェストまで指定してくれていますね。ソースを修正して再度skaffold runを実行すれば、新しいイメージをlatestタグでプッシュして、そのダイジェスト値を指定してデプロイを行ってくれます。

落ち穂拾い

これまで見てきたようにSkaffoldはCloud Runの開発において大変有用のものですが、追い切れていない部分もあります。

  • タギングなどCDの戦略をどうするか
  • ローカル実行をどうするかについて触れませんでした。Kubernetesアプリケーションであれば、Cloud Codeを使うとskaffold.yamlを使って簡単にローカル実行ができますが、Cloud Runの場合にはどうなのかは未検証です。
  • コンテナの外の要素、たとえばインジェクトする環境変数やシークレット情報の取扱については触れられませんでした。この辺りも機会があればいずれ書きたいです。

実は2022年9月のアップデートでCloud Deployを使ってCloud Runをデプロイすることができ、なおかつCloud Deployはその裏でSkaffoldを使います。したがって今回の内容を踏まえて更に踏み込んだCDをCloud Deployを使って行う事ができます。Cloud Deploy(とSkaffold)を使ったCDパイプラインの構築については改めて紹介させてください。


We're Hiring!

📢
Mobility Technologies ではともに働くエンジニアを募集しています。

興味のある方は 採用ページ も見ていただけると嬉しいです。

Twitter @mot_techtalk のフォローもよろしくお願いします!