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
こちらをインストールすることで、gcloud
やkubectl
といった
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!
を表示します。
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)をイメージの格納先とするよう、
内容を変更しておきます。
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のリソースなども定義します。
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を、最低限まで単純化したような内容としました。
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画面がすぐに確認できるよう、
最も単純なものを書きました。
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
見事、サービスデプロイが行えています!!
コードを変更してみる
getting-startedディレクトリは、gitからチェックアウトしてきたアプリケーションリポジトリを模しています。
Docker開発では、リポジトリ内にデプロイ用のマニュフェストファイル(yaml)を持ちます。
ただ、ここでのいわゆるプログラムコードはmain.goなので、こちらを変更することで本項の目的である
Docker開発が自動化されていることを確認します。
- main.goの変更
→ 下記のように表示文字列を変えてみましょう。
skaffold devを実行した画面とは別にターミナルを立ち上げて作業します。
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
おわりに
いかがでしたでしょうか。
もう一度書きますと、SkaffoldではDocker開発を回すうえでの非常に煩雑な手順、
・Dockerイメージビルド
・コンテナイメージのリポジトリプッシュ
・イメージをサーバにデプロイ
を全て自動化してくれます。
また、今回は少しハードルを上げてGKEクラスタにデプロイする例を紹介しましたが、
Minikube(ローカルで運用するk8s)やローカルのDockerでも使用することもでき、
そちらは、より単純な手順で実践可能です。
開発環境からDockerを採用する事例は、より増えてきていると思います。
みなさまもお手元の環境で、「Kubernetes + Skaffold」を試してみてはいかがでしょうか。