Edited at

Skaffoldを利用してGKEクラスタでの高速Docker開発を試す


GKE (Google Kubernetes Engine) とは

Kubernetes (k8sと略す) という、Dockerコンテナのオーケストレーションツールが広く使われ始めています。

k8sをGCPのマネージドサービスとして提供されているものがGKEです。

当社では、GKEを2年以上本番運用しています。

当社の技術基盤に興味がある方は、詳しくはまず下記 Tech Blog の記事を参照いただければ幸いです。

https://allabout-tech.hatenablog.com/entry/2017/12/08/080800


Skaffoldとは

CI/CDワークフローを高速に回すk8s用のツールです。

ローカルで開発したアプリケーション用コードを、k8sクラスタにデプロイするまでの、

いずれの段階においても自動化を行ってくれるものです。

今はアルファからベータ版になったところですので、本番で実用できるのはまだ先かと思います。

https://github.com/GoogleContainerTools/skaffold

Docker環境で開発を行う際にいつも問題になるのが、コードを書いてから

サーバへ成果物を反映するまでの手順が、非常に煩雑だということです。

これには、以下のような手順を踏む必要があります。

・Dockerイメージをビルドしてコンテナを作成

・コンテナイメージをDockerリポジトリにプッシュする
・リポジトリのイメージをサーバにデプロイする

Skaffoldでは、skaffold devという開発用コマンドを1つ使うだけで、

上記の3ステップが自動実行されます。


本項での紹介内容

当社は現在、CI/CDツールの見直しを検討しております。

その一環として、Skaffoldを少し触ってみました。

基本的には公式のチュートリアルを少し変えてなぞりつつ、GKEのローンチを含めて

サービス用のクラスタを想定し、当社の運用ノウハウを一部取り入れながら、

ハンズオン形式で紹介していきます。

本項では、以下のことの確認を目指します。

git cloneしてきた想定の開発リポジトリのコードを変更すると、

開発GKEクラスタにコード変更結果が反映される。


ローカル作業環境

MacまたはLinuxを想定しています。

利用できるGCPアカウントがあり、Googleログイン認証されて、

そちらが使える状態となっている必要があります。

また、GKEに関する管理者権限/GCRへの開発者権限が必要です。

ビルド時に必要ですので、Dockerをインストール/起動して下さい。

こちらは、マルチステージビルドに対応したバージョンでないといけません。

また、Google Cloud SDK(コマンドベースの管理ツール)もインストールして下さい。

https://cloud.google.com/sdk/docs/?hl=ja

こちらをインストールすることで、gcloudkubectlといった

GKE運用に必要なコマンドが利用可能になります。


ハンズオン!


安定版のSkaffoldコマンドをインストール


  • MacならHomebrewが簡単!

$ brew install skaffold

==> Installing dependencies for git: gettext and pcre2
==> Installing git dependency: gettext
==> Downloading https://homebrew.bintray.com/bottles/gettext-0.19.8.1.el_capitan.bottle.tar.gz
Updating Homebrew...


  • Linuxならバイナリを配置!

$ curl -Lo skaffold https://storage.googleapis.com/skaffold/releases/latest/skaffold-linux-amd64

$ chmod +x skaffold
$ sudo mv skaffold /usr/local/bin


GKEクラスタの準備


  • ローカル環境で参照するGCPプロジェクトの向け先を変えます

    → 下記では、test01-01というプロジェクトIDと、台湾リージョンのbゾーンを選択する指定を行なっています。

$ export PROJECT_ID=test01-01

$ gcloud config set project ${PROJECT_ID}
$ gcloud config set compute/zone asia-east1-b


  • コマンドでGKEを構築します

    → 下記では、標準的インスタンスでクラスタ数が2、k8sのバージョンが1.9.7のクラスタが作成されます。

      クラスタ名はskaffold-test

$ gcloud container clusters create skaffold-test \

--machine-type=n1-standard-1 --num-nodes=2 --node-version=1.9.7


  • 構築したGKEを、skaffold作業の向け先にします

$ gcloud config set container/cluster skaffold-test

$ gcloud container clusters get-credentials skaffold-test


  • 構築したノードの状態を確認

    → 名前は、ハッシュやGCP側で付加される修飾子がつくので長くなります。

$ kubectl get nodes

NAME STATUS ROLES AGE VERSION
gke-skaffold-test-default-pool-7cc93770-fvdx Ready <none> 21h v1.9.7-gke.11
gke-skaffold-test-default-pool-7cc93770-x5bv Ready <none> 21h v1.9.7-gke.11


skaffoldするためのyaml準備


  • 適当な作業ディレクトリにskaffoldをクローンして、デプロイ元を作ります

    → getting-startedというディレクトリにチュートリアルが入っているので、

      主旨に沿うように変更していきます。

$ git clone https://github.com/GoogleContainerTools/skaffold

$ cd skaffold/examples/getting-started
$ ls -l
total 40
-rw-r--r-- 1 numa_numa staff 144 12 12 19:30 Dockerfile
-rw-r--r-- 1 numa_numa staff 734 12 12 19:30 README.adoc
-rw-r--r-- 1 numa_numa staff 153 12 12 19:30 k8s-pod.yaml
-rw-r--r-- 1 numa_numa staff 128 12 12 19:30 main.go
-rw-r--r-- 1 numa_numa staff 158 12 12 19:30 skaffold.yaml

・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

 → 消してください。単一のpod(サーバのコンテナ)をデプロイするための設定内容になっています。

   今回は当社の本番構成を模したような環境としたいので、podの冗長(クラスタ)を実装します。

・main.go

 → Global IPを付与し、ブラウザで文字列が確認できる最低限の内容に変えておきます。

   ブラウザ上にHello, World!を表示します。


main.go

package main

import (
"fmt"
"net/http"
)

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

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


・skaffold.yaml

 → skaffoldのメインコンフィグです。GCR(Google Container Registry)をイメージの格納先とするよう、

   内容を変更しておきます。


skaffold.yaml

apiVersion: skaffold/v1beta1

kind: Config
build:
artifacts:
- image: asia.gcr.io/test01-01/skaffold-example
deploy:
kubectl:
manifests:
- k8s-*
profiles:
- name: gcb
build:
googleCloudBuild:
projectId: test01-01

・k8s-deployment.yaml

 → 新規作成します。k8s-pod.yamlの代わりであり、クラスタ構成のWEBサーバpodを構築します。

   当社の本番運用しているyamlを、最低限まで単純化したような内容としました。

   通常、本番用のdeployment.yamlでは、podが持つべきCPU/Memのリソースなども定義します。


k8s-deployment.yaml

apiVersion: apps/v1beta1

kind: Deployment
metadata:
name: go-pod
spec:
replicas: 2
revisionHistoryLimit: 5
template:
metadata:
labels:
name: go-pod
spec:
containers:
- name: go-pod-container
image: asia.gcr.io/test01-01/skaffold-example
ports:
- containerPort: 8080

・k8s-service.yaml

 → 新規作成します。podが利用するport情報を定義します。

   こちらも、当社の本番運用しているyamlを、最低限まで単純化したような内容としました。


k8s-service.yaml

apiVersion: v1

kind: Service
metadata:
name: go-service
spec:
type: NodePort
ports:
- port: 8080
protocol: TCP
targetPort: 8080
selector:
name: go-pod

・k8s-ingress.yaml

 → 新規作成します。現状、当社では利用していないリソースです。

   Ingressは、Serviceに関連付けるロードバランサを自動作成し、サービス公開してくれるリソースです。

   当社ではGLB(Google Load Balancer)を使用していますが、今回はWEB画面がすぐに確認できるよう、

   最も単純なものを書きました。


k8s-ingress.yaml

apiVersion: extensions/v1beta1

kind: Ingress
metadata:
name: go-ingress
spec:
backend:
serviceName: go-service
servicePort: 8080


skaffold devをためす!


  • ためす前に必要な1ステップ

$ echo {} > ~/.docker/config.json

上記のコマンドを打ちましょう。

この点、かなりハマりました…

Skaffoldは標準でDockerhubの認証を行うようで、その認証設定を保持しておくのがこのファイルです。

今回はGoogleの認証を使用して、イメージのプッシュ先としてもGCR(Google Container Registry)を

使用するので、このファイルは実質不要です。

しかし、json形式と認識され、かつファイルが存在する状態でないと、skaffoldは以下のようなエラー状態に!

$ skaffold dev

2018/12/12 11:46:15 Unable to read "/Users/numa_numa/.docker/config.json": open /Users/numa_numa/.docker/config.json: no such file or directory
2018/12/12 11:46:17 Unable to read "/Users/numa_numa/.docker/config.json": open /Users/numa_numa/.docker/config.json: no such file or directory


  • skaffold dev

$ skaffold dev

Starting build...
Building [asia.gcr.io/test01-01/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 .
---> 6eb6ec18c4f5
Step 3/6 : RUN go build -o /app main.go
---> Running in f48712f45d21
---> d5b140e4c533
Step 4/6 : FROM alpine:3.7
3.7: Pulling from library/alpine
c67f3896b22c: Pulling fs layer
c67f3896b22c: Verifying Checksum
c67f3896b22c: Download complete
c67f3896b22c: Pull complete
Digest: sha256:a52b4edb6240d1534d54ee488d7cf15b3778a5cfd0e4161d426c550487cddc5d
Status: Downloaded newer image for alpine:3.7
---> 34ea7509dcad
Step 5/6 : CMD ["./app"]
---> Running in 344b58b3b879
---> 2224d665c6f9
Step 6/6 : COPY --from=builder /app .
---> e48445fc4fc5
Successfully built e48445fc4fc5
Successfully tagged 559b2b15a7fb390531d89955043fdef7:latest
The push refers to repository [asia.gcr.io/test01-01/skaffold-example]
e55d98a8a253: Preparing
ebf12965380b: Preparing
ebf12965380b: Layer already exists
e55d98a8a253: Pushed
19097cfc-dirty-e48445f: digest: sha256:0381c9fb158f36984988e3bcd90d00538b323d6d7fead0d27cbe976cd1c97a9d size: 739
Build complete in 19.327532727s
Starting test...
Test complete in 6.753µs
Starting deploy...
kubectl client version: 1.10
kubectl version 1.12.0 or greater is recommended for use with skaffold
deployment.apps "go-pod" created
ingress.extensions "go-ingress" created
service "go-service" created
Deploy complete in 1.962841729s
Watching for changes every 1s...
Port Forwarding go-pod-f8f84fd5f-lqrjx 8080 -> 8080
Port Forwarding go-pod-f8f84fd5f-f7gsv 8080 -> 8080

ローカルのkubectlのバージョンが低く怒られていますが、実行は問題ないようです。


  • サービス状態の確認

    → podが生存し、サービスポートが設定され、IngressにGlobal IPが割り当てられていることを確認。

$ kubectl get pod,svc,ingress

NAME READY STATUS RESTARTS AGE
pod/go-pod-5875ffdc6-7v54b 1/1 Running 0 4m
pod/go-pod-5875ffdc6-frpxl 1/1 Running 0 4m

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/go-service NodePort 10.59.243.30 <none> 8080:31194/TCP 55m
service/kubernetes ClusterIP 10.59.240.1 <none> 443/TCP 21h

NAME HOSTS ADDRESS PORTS AGE
ingress.extensions/go-ingress * 35.244.132.7 80 55m


  • WEB画面を見てみる

    → ブラウザで見てみます…


見事、サービスデプロイが行えています!!


コードを変更してみる

getting-startedディレクトリは、gitからチェックアウトしてきたアプリケーションリポジトリを模しています。

Docker開発では、リポジトリ内にデプロイ用のマニュフェストファイル(yaml)を持ちます。

ただ、ここでのいわゆるプログラムコードはmain.goなので、こちらを変更することで本項の目的である

Docker開発が自動化されていることを確認します。


  • main.goの変更

    → 下記のように表示文字列を変えてみましょう。

      skaffold devを実行した画面とは別にターミナルを立ち上げて作業します。


main.goの変更

func handler(w http.ResponseWriter, r *http.Request) {

fmt.Fprintf(w, "Hello, World!")
}

 ↓ ↓ ↓ ↓ ↓

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


これを保存すると、Skaffoldはコードの変更を自動検知し下記のような画面出力が追加されます。

Starting build...

Building [asia.gcr.io/test01-01/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 .
---> e3025641eb41
Step 3/6 : RUN go build -o /app main.go
---> Running in 4d01949d36cd
---> a33cf4369023
Step 4/6 : FROM alpine:3.7
---> 34ea7509dcad
Step 5/6 : CMD ["./app"]
---> Using cache
---> 2224d665c6f9
Step 6/6 : COPY --from=builder /app .
---> 89f696d81aeb
Successfully built 89f696d81aeb
Successfully tagged 24552841ff2fd2e063dbc6f46de39c89:latest
The push refers to repository [asia.gcr.io/test01-01/skaffold-example]
1264c561e5c7: Preparing
ebf12965380b: Preparing
ebf12965380b: Layer already exists
1264c561e5c7: Pushed
19097cfc-dirty-89f696d: digest: sha256:c7270a126fd8d5db4e2a383a6c2c758a7dfdb0c502dfbdf70e8f4e36d4a9e3d4 size: 739
Build complete in 15.270532362s
Starting test...
Test complete in 7.824µs
Starting deploy...
kubectl client version: 1.10
kubectl version 1.12.0 or greater is recommended for use with skaffold
deployment.apps "go-pod" configured
Deploy complete in 4.216971279s
Watching for changes every 1s...
Port Forwarding go-pod-5cf4d974bc-dj9gg 8080 -> 8080
Port Forwarding go-pod-5cf4d974bc-k4scw 8080 -> 8080


  • 再度、WEB画面を見てみる

    → コマンドで再度サービス状態を確認してから見てみます…

podへの更新デプロイが行えているようです。

このツールを使えば、開発コードへ変更を加える場合に、ビルド・プッシュ・デプロイという手順なしに、

開発内容をほぼリアルタイムに確認できそうです。


ステージングや本番へのデプロイの想定

skaffold devはあくまで開発環境の確認用ですので、[Ctrl]+[C]でプロセスを停止すると、

同時にpodは消滅してしまいます。

消滅しないようにpodデプロイを行う場合は、

$ skaffold run

を使用します。

なお、GCRにイメージプッシュを行う場合に -t オプションを付けることで任意のタグ付けも可能です。

(skaffold devで変更を繰り返した場合は、イメージにはランダムなハッシュをもつタグが付きます。)

$ skaffold run -t atonce

※GCRのWEB画面から参照したプッシュイメージ


おわりに

いかがでしたでしょうか。

もう一度書きますと、SkaffoldではDocker開発を回すうえでの非常に煩雑な手順、

・Dockerイメージビルド

・コンテナイメージのリポジトリプッシュ
・イメージをサーバにデプロイ

を全て自動化してくれます。

また、今回は少しハードルを上げてGKEクラスタにデプロイする例を紹介しましたが、

Minikube(ローカルで運用するk8s)やローカルのDockerでも使用することもでき、

そちらは、より単純な手順で実践可能です。

開発環境からDockerを採用する事例は、より増えてきていると思います。

みなさまもお手元の環境で、「Kubernetes + Skaffold」を試してみてはいかがでしょうか。