SkaffoldのGetting StartedをMinikubeでサクッと試す

[Cloud Native Online #01](https://cnjp.connpass.com/event/129658/)に登壇した際の[スライド](https://speakerdeck.com/loftkun/the-strongest-tag-between-minikube-and-skaffold)もご覧ください。

## Skaffoldとは

[Skaffold](https://skaffold.dev/)は`継続的なKubernetesアプリケーション開発`を促進するツールで、
ソースコードの変更をトリガとして以下を自動で行ってくれることで、我々開発者はアプリケーションの開発に注力できるようになります。

- コンテナイメージのビルド
- コンテナレジストリへのpush
- k8sクラスタへのデプロイ
  - `kubectl`の他、`helm`や`kustomize`も記述できる

画像は[skaffold.dev/docs](https://skaffold.dev/docs/)より引用
ソースコードの変更をトリガとしてk8sクラスタにデプロイされるまでの流れが示されています。
![skaffold.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/21106/a937f8bb-0fb0-2d73-57e6-9cceba6f8fc7.png)


`Forerver（Node)`や`Rails`や`Django`がソースコードの変更を検出してサーバを再起動する機能のKubernetes版と考えて良さそうです。

本エントリにはSkaffoldの[Getting Started](https://skaffold.dev/docs/getting-started/)をMinikubeで実施する場合に必要となる手順を記載します。

### 環境

`Ubuntu 17.10`で`Minikube v1.0.0`を使用しています。

```
$ uname -a
Linux mina 4.13.0-46-generic #51-Ubuntu SMP Tue Jun 12 12:36:29 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=17.10
DISTRIB_CODENAME=artful
DISTRIB_DESCRIPTION="Ubuntu 17.10"
$ minikube version
minikube version: v1.0.0
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.0", GitCommit:"641856db18352033a0d96dbc99153fa3b27298e5", GitTreeState:"clean", BuildDate:"2019-03-25T15:53:57Z", GoVersion:"go1.12.1", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"14", GitVersion:"v1.14.0", GitCommit:"641856db18352033a0d96dbc99153fa3b27298e5", GitTreeState:"clean", BuildDate:"2019-03-25T15:45:25Z", GoVersion:"go1.12.1", Compiler:"gc", Platform:"linux/amd64"}
$
```

## Minikubeの準備

MinikubeとSkaffoldを組み合わせて使用する場合、留意事項があります。

Skaffoldは、k8sクラスタがMinikubeであると認識した場合、
`docker build`は行いますが、`docker push`は行いません。

これは、`docker build`コマンドが、MinikubeのVM内での`docker build`となるよう設定されていることが前提となっていると考えられます。

これには、minikubeの`docker-env`コマンドを活用できます。
このコマンドは以下のように、ローカルのdockerコマンドをMinikubeのVM内のdockerに接続するための環境変数設定を表示してくれます。

```
$ minikube docker-env
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/home/loft/.minikube/certs"
export DOCKER_API_VERSION="1.35"
# Run this command to configure your shell:
# eval $(minikube docker-env)
$
```

以下のようにevalで実行すると環境変数が設定されます。

```
$ eval $(minikube docker-env)
```

以降、dockerコマンドはMinikubeのVM内のdockerに対する操作になることに留意する必要はありますが、
この機構によりコンテナイメージをわざわざレジストリにpushしなくても
minikubeにアプリケーションをデプロイできるので素早い開発サイクルを回せるメリットがあります。

これを行なっていない場合、dockerイメージはMinikubeのVMの外のローカルでビルドされるため、当然PodのStatusは`ImagePullBackOff`となります。

## Skaffoldのインストール

[installing-skaffold](https://skaffold.dev/docs/getting-started/#installing-skaffold)を参照

例) Linuxの場合

``` bash
$ curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64
$ chmod +x skaffold
$ sudo mv skaffold /usr/local/bin
```

## Skaffold サンプルの入手

リポジトリをcloneし、`examples/getting-started`ディレクトリに移動しましょう。
構成物については後述します。

``` bash
$ git clone https://github.com/GoogleContainerTools/skaffold
$ cd examples/getting-started
$ ls -Fla
total 28
drwxrwxr-x  2 loft loft 4096  5月  7 21:00 ./
drwxrwxr-x 19 loft loft 4096  5月  7 21:00 ../
-rw-rw-r--  1 loft loft  144  5月  7 21:00 Dockerfile
-rw-rw-r--  1 loft loft  153  5月  7 21:00 k8s-pod.yaml
-rw-rw-r--  1 loft loft  131  5月  7 21:00 main.go
-rw-rw-r--  1 loft loft  734  5月  7 21:00 README.adoc
-rw-rw-r--  1 loft loft  158  5月  7 21:00 skaffold.yaml
$
```

## 動作確認

ターミナルを2つ(`terminal-1`と`terminal-2`)用意し、以下を実行します。

### terminal-1

`skaffold dev`コマンドを実行します。
以下が行われる様子を確認できます。

- docker build ( MinikubeのVM内でbuild )
- docker push ( Minikubeの場合は行わない )
  - `Found [minikube] context, using local docker daemon.`より、Minikubeを認識していることが分かる
- kubectl apply
- 標準出力ログの表示
- ソースコードの変更の監視

``` bash
$ skaffold dev
Generating tags...
 - gcr.io/k8s-skaffold/skaffold-example -> gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Tags generated in 7.774075ms
Starting build...
Found [minikube] context, using local docker daemon.
Building [gcr.io/k8s-skaffold/skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM golang:1.10.1-alpine3.7 as builder
 ---> 52d894fca6d4
Step 2/6 : COPY main.go .
 ---> Using cache
 ---> 04e5f4a5d5e2
Step 3/6 : RUN go build -o /app main.go
 ---> Using cache
 ---> eb45bd0c0ce1
Step 4/6 : FROM alpine:3.7
 ---> 6d1ef012b567
Step 5/6 : CMD ["./app"]
 ---> Using cache
 ---> da76805ab2e2
Step 6/6 : COPY --from=builder /app .
 ---> Using cache
 ---> 9c5ffabae1d9
Successfully built 9c5ffabae1d9
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Build complete in 72.162899ms
Starting test...
Test complete in 5.744µs
Starting deploy...
kubectl client version: 1.14
pod/getting-started created
Deploy complete in 301.514962ms
Watching for changes every 1s...
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
( 略 : )
```

### terminal-2

アプリケーションのソースコードを書き換えてみましょう。

``` bash
# 書き換え前
$ cat main.go
( 略 : )
		fmt.Println("Hello world!")
( 略 : )

# 書き換え
$ vim main.go

# 書き換え後
$ cat main.go
( 略 : )
		fmt.Println("Hello skaffold!")
( 略 : )

```

### terminal-1

再び`terminal-1`を確認してみると、ソースコードの変更をトリガとして以下が行われる様子を確認できます。

- docker build ( MinikubeのVM内でbuild )
- docker push ( Minikubeの場合は行わない )
- kubectl apply
- 標準出力ログの表示
- ソースコードの変更の監視

手作業でイメージをビルドし、レジストリにpushし、クラスタにデプロイする手間が省かれていますね。

``` bash
( 略 : )
[getting-started] Hello world!
[getting-started] Hello world!
[getting-started] Hello world!
Generating tags...
 - gcr.io/k8s-skaffold/skaffold-example -> gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Tags generated in 21.094584ms
Starting build...
Found [minikube] context, using local docker daemon.
Building [gcr.io/k8s-skaffold/skaffold-example]...
Sending build context to Docker daemon  3.072kB
Step 1/6 : FROM golang:1.10.1-alpine3.7 as builder
 ---> 52d894fca6d4
Step 2/6 : COPY main.go .
 ---> 4a61f315455a
Step 3/6 : RUN go build -o /app main.go
 ---> Running in 30ee9d0e606f
Removing intermediate container 30ee9d0e606f
 ---> 6a9d5e79631a
Step 4/6 : FROM alpine:3.7
 ---> 6d1ef012b567
Step 5/6 : CMD ["./app"]
 ---> Using cache
 ---> da76805ab2e2
Step 6/6 : COPY --from=builder /app .
 ---> 16dbf70b67d6
Successfully built 16dbf70b67d6
Successfully tagged gcr.io/k8s-skaffold/skaffold-example:v0.28.0-89-gf25b09fa-dirty
Build complete in 1.75830858s
Starting test...
Test complete in 11.741µs
Starting deploy...
kubectl client version: 1.14
pod/getting-started configured
Deploy complete in 296.352796ms
Watching for changes every 1s...
[getting-started] Hello skaffold!
[getting-started] Hello skaffold!
( 略 : )
```

## 構成物の確認

### main.go

アプリケーションのソースコード。
1sec毎に標準出力に`Hello world!`を表示する。

``` bash
$ cat main.go
package main

import (
	"fmt"
	"time"
)

func main() {
	for {
		fmt.Println("Hello world!")
		time.Sleep(time.Second * 1)
	}
}
```

### Dockerfile

main.goをビルドしコンテナ起動時のコマンドとしている。

``` bash
$ cat Dockerfile
FROM golang:1.10.1-alpine3.7 as builder
COPY main.go .
RUN go build -o /app main.go

FROM alpine:3.7
CMD ["./app"]
COPY --from=builder /app .
$
```

### k8s-pod.yaml

`gcr.io/k8s-skaffold/skaffold-example`というイメージを使用するPodの定義。

``` bash
$ cat k8s-pod.yaml
apiVersion: v1
kind: Pod
metadata:
  name: getting-started
spec:
  containers:
  - name: getting-started
    image: gcr.io/k8s-skaffold/skaffold-example
$
```

### skaffold.yaml

Skaffoldの設定ファイル。

- `build`設定
  - ビルドするイメージ名として`gcr.io/k8s-skaffold/skaffold-example`を指定している
- `deploy`設定
  - `kubectl`コマンドを使用している
    - `k8s-`で始まるマニフェストファイル(yaml)を指定している

``` bash
$ cat skaffold.yaml
apiVersion: skaffold/v1beta9
kind: Config
build:
  artifacts:
  - image: gcr.io/k8s-skaffold/skaffold-example
deploy:
  kubectl:
    manifests:
      - k8s-*
$
```

## まとめ

いかがでしたか？
Skaffoldを使うと、クラスタにデプロイされるまでのルーチンが自動化されるので効率良く開発できそうですね。
特にMinikubeを開発環境として使用している場合は恩恵が大きそうです。

今回はSkaffoldのさわりである[Getting Started](https://skaffold.dev/docs/getting-started/)を試してみましたが、[Tutorials](https://skaffold.dev/docs/tutorials/)も用意されています。
本記事がSkaffold導入の参考になりましたら幸いです。