Skaffold + IKS を使った 簡単・軽量・高速のCI/CD環境

[Skaffold](https://skaffold.dev/) は、ソースコードを変更した後に、自動的にKubernetesクラスタへデプロイしてくれる手作業を自動化するツールです。具体的には、次の作業を自動化してくれるので、コードを修正して保存するだけで、変更結果を確認できるようになります。 そのため、Jenkinsを立てるのは面倒と感じている人には、とっても良いツールと思います。

* docker build でコンテナのイメージを作る
* docker tag でタグを付与
* docker push でリポジトリに登録
* kubectl apply -f マニフェスト で Kubernetesクラスタにデプロイ
* テストして、コードを修正して、上記を繰り返し

つまり、Skaffold は、Kubernetesアプリケーションの継続的開発環境のコマンドライン・ツールです。 ローカル環境で、ソースコードを変更すると、ローカルまたはクラウド上のKubernetesクラスタへ、自動的にデプロイします。 すなわち、Skaffold は、ビルド、レジストリへのプッシュ、アプリケーションのデプロイのワークフローを実行します。 そして、CI/CDの作業工程の進行と、カスタマイズを提供します。


このツールは、Googleが開発したツールで、とてもシンプルで便利なツールです。しかも、Minikube をはじめ、どのKubenetes 環境でも動作します。そこで、IBM Cloud Kubernetes Service で動かしたメモです。


## 特徴

### 高速なローカル環境での開発

* ソースをk8sへ反映に最適化： Slaffold は、アプリケーションのソースコードの変更を検知して、ビルド、プシュ、デプロイのパイプラインを進行させる。　これには、ポリシーベースのイメージのタグ付け、高度な最適化、高速でローカルでのワークフローを伴う。
* 継続的なフィードバック: Skaffoldは、ロギング や ポートフォワーディング の自動的に管理する。


### Skaffold のプロジェクトは何処でも動作

* デベロッパー間での共有: gitクローンとskaffoldは、最も簡単にプロジェクトを世界と共有する手段である。
* コンテキストに対応: Skaffold の profiles を使うと、ユーザーレベルのコンフィグ、環境変数やフラグなど、環境の差異を記述できる。
* CI/CD工程の単位: skafffold run を最初から最後まで、または、一部の工程だけを実行できる。


###  skaffold.yaml は、プラグインで機能追加でき、宣言的な設定である。

* skaffold init はファイルを発見して、コンフィグファイルを作成する。
* 複数要素に対応: Skaffoldは複数コンポーネントから成るアプリに対応する・。
* 自己ツールを持ち込み可能: Skaffoldは、プラグイン可能なアーキテクチャなので、ステージで異なる実装が許容される。


### ライトウェイト

* クライアントサイドのみ: クラスタ側にポッドなどが不要
* 最小のパイプライン: シンプルを維持して、最適化する



## 事前準備

skaffoldを利用するための前提条件は、以下になります。 Kubernetesクラスタ、リポジトリにアクセスできて、Dockerをビルドできることが必要です。

* ローカル環境にDockerがインストールされていること
* IKSクラスタが作られており、kubectl get node できること
* コンテナのレジストリにアカウントがあり、docker login できていること

ここでは、サンプルコードに、Go言語を利用しています。このコンパイル環境は、マルチステージ・ビルド形式の Dockerfile を利用することで、ローカルにコンパイル環境を作らずに対応できるようにします。


## 適用対象のプロジェクト

これはGo言語で書いたウェブサーバーをコンテナにビルドして、レジストリに登録、Kubernetesにデプロイする簡単なコードです。
Go言語のソースコードを変更する都度、Skaffoldが監視していて、Kubernetesへデプロイしてくれます。

このコードをローカルにクローンして、skaffold を実行するだけで、CI/CDの簡易環境が実現します。

~~~console
$ git clone https://github.com/takara9/skaffold-test-1
~~~

次の部分のリポジトリ名を、ご自分のリポジトリ名に変更します。

~~~file:k8s-webserver.yaml
    spec:
      containers:
      - name: webserver
        image: <YOUR REG ID>/skaffold-example
        ports:
        - containerPort: 8080
~~~

もう一箇所も修正します。

~~~file:skaffold.YAML
skaffold.yaml
apiVersion: skaffold/v1beta10
kind: Config
build:
  artifacts:
  - image: <YOUR REG ID>/skaffold-example
deploy:
  kubectl:
    manifests:
    - k8s-webserver.yaml
~~~


これで準備は完了です。 dockerにログインして、リポジトリに登録できるようにしておき、skaffold を実行するだけです。
これで、ソースコードからコンパイル、コンテナのビルド、レジストリへのプッシュ、Kubernetesへのデプロイを完了して、アプリケーションへアクセスできます。

~~~console
$ docker login
$ skaffold dev --default-repo <your reg account id>
~~~


以下のコマンドで、ウェブサーバーをアクセスして確認します。 ワーカーノードのIPアドレスが解らないときは、`ibmcloud ks workers <CLUSTER NAME>``でパブリックIPアドレスを取得してください。

~~~console
$ curl http://<YOUR NODE IP>:31080/
~~~

プログラムを変更して、保存するごとに、ビルドからデプロイまでが自動実行されます。




## テスト

コードの修正を保存するごとに、以下のように、ビルドとレジストリへの登録、Kubernetesクラスタへのデプロイが自動実行されます。実際に動かして、main.go や index.html を修正して、自動的にデプロイされるまでを確認していただきたいと思います。


~~~console
Watching for changes every 1s...
Generating tags...
 - maho/maho_skaffold-example -> WARN[0271] Unable to find git commit: Running [git describe --tags --always]: stdout , stderr: fatal: not a git repository (or any of the parent directories): .git
, err: exit status 128: exit status 128
maho/maho_skaffold-example:dirty
Tags generated in 8.038839ms
Starting build...
Building [maho/maho_skaffold-example]...
Sending build context to Docker daemon  4.096kB
Step 1/8 : FROM golang:1.10.1-alpine3.7 as builder
 ---> 52d894fca6d4
Step 2/8 : COPY main.go .
 ---> f9ae2c270440
Step 3/8 : RUN go build -o /app main.go
 ---> Running in faf21a1ae59f
Removing intermediate container faf21a1ae59f
 ---> e55f9a80fea4
Step 4/8 : FROM alpine:3.7
 ---> 6d1ef012b567
Step 5/8 : CMD ["./app"]
 ---> Using cache
 ---> c0a8d07256ed
Step 6/8 : COPY --from=builder /app .
 ---> c9ea340c10fb
Step 7/8 : RUN mkdir /html
 ---> Running in 30b975e6d86a
Removing intermediate container 30b975e6d86a
 ---> fe5fe1d2fae8
Step 8/8 : COPY index.html /html/index.html
 ---> 9437956da99a
Successfully built 9437956da99a
Successfully tagged maho/maho_skaffold-example:dirty
The push refers to repository [docker.io/maho/maho_skaffold-example]
a74066c0f706: Preparing
963ac60d4895: Preparing
ac1354bcd7fb: Preparing
3fc64803ca2d: Preparing
3fc64803ca2d: Layer already exists
a74066c0f706: Pushed
963ac60d4895: Pushed
ac1354bcd7fb: Pushed
dirty: digest: sha256:74e583a6ed2efb205c13b680fd2e863cbab355f2ef6b6985c30fc8249cb6d34e size: 1152
Build complete in 18.951475622s
Starting test...
Test complete in 5.473µs
Starting deploy...
kubectl client version: 1.14
deployment.apps/web-deployment configured
Deploy complete in 611.256771ms
Watching for changes every 1s...
~~~


## skaffold.yamlの書き方

skaffold.yamlは、自身で書くこともできますが、`skaffold init` コマンドで自動生成されます。

~~~file:skaffold.yaml
apiVersion: skaffold/v1beta10
kind: Config
build:
  artifacts:
  - image: maho/skaffold-example
deploy:
  kubectl:
    manifests:
    - k8s-webserver.yaml
~~~

プロジェクトで利用する場合、KubernetesのYAMLやDockerfileが存在するディレクトリで、次のように簡単に skaffold.yaml を生成できます。あとは、必要に応じて、Skaffoldのドキュメント[3]を参考にしながら、YAMLを修正すれば良いでしょう。

~~~console
imac:project maho$ skaffold init
apiVersion: skaffold/v1beta10
kind: Config
build:
  artifacts:
  - image: maho/skaffold-example
deploy:
  kubectl:
    manifests:
    - k8s-webserver.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 入門, https://skaffold.dev/docs/getting-started/
[2] Docker マルチステージビルドの方法, https://docs.docker.com/develop/develop-images/multistage-build/
[3] Skaffoldの利用法、https://skaffold.dev/docs/how-tos/