GCRにPUSHしたイメージをSpinnaker経由でGKEにデプロイするところまでのまとめ (1)

k8s自体も実際の業務用途で使ったことはないし、今回業務で本格利用するにあたって一度理解しておきたくやったことをまとめておく。

長いので２つに分けたうちの１つ目。

# やったこと

1. GKE基本を学習
2. 自作のgoのイメージをContainer Registryに登録し、クラスタにデプロイ、ローリングアップデートまで試す

（今回はここまで）

（以降は次の記事に書く）
3. Spinnaker on Google Cloud Platformのインストール
4. Pub/Subに作られたTopicとSubscription経由で、Container RegistryへのイメージPUSHを検出できることの確認
5. SpinnakerがPub/Sub経由でMessageをSubscribeし、GKEのイメージを更新するためのPipelineを作る


# gcloudコマンド初期設定

まずプロジェクトとZoneをセットする。

```
% gcloud config set project ***
Updated property [core/project].
% gcloud config set compute/zone us-central1-c
```

# 基本を覚えるためGKEクイックスタートをやってみる

[クイックスタート](https://cloud.google.com/kubernetes-engine/docs/quickstart?hl=ja)


クラスタの構成はデフォルトで一旦進める。

```
$ gcloud container clusters create test-cluster-1
$ gcloud container clusters list
NAME            LOCATION       MASTER_VERSION  MASTER_IP    MACHINE_TYPE   NODE_VERSION    NUM_NODES  STATUS
test-cluster-1  us-central1-c  1.14.10-gke.27  [MASTER-IP]  n1-standard-1  1.14.10-gke.27  3          RUNNING
```

とりあえず出来た。

この時点ではkubectlの操作対象が未設定。

```
% kubectl config current-context
error: current-context is not set
```

作成したclusterについて認証情報を取得する。

```
$ gcloud container clusters get-credentials test-cluster-1
Fetching cluster endpoint and auth data.
kubeconfig entry generated for test-cluster-1.
```

この状態で改めて以下を実行。

```
% kubectl config current-context
gke_[PROJECT-ID]_us-central1-c_test-cluster-1
```

このように操作対象clusterがセットされた。
実際には [こういう事](https://qiita.com/sonots/items/f82912367693d717ff06) が行われているらしいですね。

次にサンプルにあるDeploymentを作成してみる。

```
% kubectl create deployment hello-server --image=gcr.io/google-samples/hello-app:1.0
deployment.apps/hello-server created
```

この状態ではservicesが外にされされていないのでこんな感じになっている。

```
% kubectl get deployments
NAME           READY   UP-TO-DATE   AVAILABLE   AGE
hello-server   1/1     1            1           7s

% kubectl get services
NAME         TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.27.240.1   <none>        443/TCP   10m
```

ここで出てくるCLUSTER-IPはなんのIPだろうと思うわけだが、gcloudでみたclusterの情報が↓。

```
% gcloud container clusters describe test-cluster-1

addonsConfig:
  kubernetesDashboard:
    disabled: true
  networkPolicyConfig:
    disabled: trueclusterIpv4Cidr: 10.24.0.0/14
(略)
servicesIpv4Cidr: 10.27.240.0/20 <--これ
shieldedNodes: {}
status: RUNNING
subnetwork: default
zone: us-central1-c
```

service側のCIDRの中からIPが自動アサインされているのだろうと予測しました。
podとserviceがそれぞれ使うレンジなんですね。 
[こちら](https://cloud.google.com/kubernetes-engine/docs/how-to/routes-based-cluster?hl=ja#pod_address_range) に書かれていました。

ただ現在はserviceが外にさらされていないので、外部からは叩けない。

ということで、↑で作ったものを外に公開。

```
% kubectl expose deployment hello-server --type LoadBalancer --port 80 --target-port 8080
service/hello-server exposed
```

この状態で以下を実行してみる。

```
% kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
hello-server   LoadBalancer   10.27.244.156   <pending>     80:31116/TCP   9s
kubernetes     ClusterIP      10.27.240.1     <none>        443/TCP        3m
```

EXTERNAL-IPが　`<pending>` の場合は数分かかることもあるらしいので何回か実行してみる。

```
% kubectl get services
NAME           TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)        AGE
hello-server   LoadBalancer   10.27.244.156   [払い出されたIP]  80:31116/TCP   99s
kubernetes     ClusterIP      10.27.240.1     <none>            443/TCP        4m30s
```

払い出されたので外からつないでみる。

```
% curl [払い出されたIP]
Hello, world!
Version: 1.0.0
Hostname: hello-server-7f8fd4d44b-rch6q
```

上手くつながった。

ということで一旦クラスタごと消しておく。

```
% gcloud container clusters delete 
```

# 自作のイメージをContainer Registryに登録

## 簡単なgoアプリケーション

上記のクイックスタートにあるようなシンプルなWebアプリケーションを作るイメージをContainer Registryに登録してみます。
コードは以下とします。

main.go

```go
package main

import (
	"net/http"
)

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

func handler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Hello, world!\n"))
	w.Write([]byte("Version: 1.0.0\n"))
}
```

`go run main.go` してから実際に接続するとこんな感じ。

```
% curl localhost:8080
Hello, world!
Version: 1.0.0
```

## Dockerイメージ化する

次にmain.goと同じディレクトリにDockerfileを作る。

```Dockerfile
FROM golang:latest

RUN mkdir /go-practice
COPY main.go /go-practice
CMD ["go", "run", "/go-practice/main.go"]
```

一旦適当な名前でビルドして動作確認。

```
% docker build -t my-go-practice  .
% docker run -it -p 8080:8080 my-go-practice
```

この状態でlocalhostにcurl。

```
% curl localhost:8080
Hello, world!
Version: 1.0.0
```

ちなみに作られたイメージは以下。
ローカル的には `my-go-practice:latest` としてある。

```
% docker images
REPOSITORY        TAG     IMAGE ID       CREATED         SIZE
my-go-practice    latest  2bdb240e98e4   6 minutes ago   809MB
```


## Container Registryに登録(PUSH)

これで一旦イメージとして成立することはわかったのでContainer RegistryにPUSHしてみる。
[クイックスタート](https://cloud.google.com/container-registry/docs/quickstart?hl=ja) をみながら実施。

まず認証を取る。

```
% gcloud auth configure-docker
```

次にContainer RegistryにPUSH。

```
% docker push gcr.io/[PROJECT-ID]/my-go-practice:1.0
The push refers to repository [gcr.io/[PROJECT-ID]/my-go-practice]
An image does not exist locally with the tag: gcr.io/[PROJECT-ID]/my-go-practice
```

イメージにtagをつけないとこうなるらしい。
ということでそれをやる。タグは `1.0` にする。

latestタグ運用はk8s上推奨されてないんですね。（実際ローリングアップデートできなかったので気づいた）
[こちら](https://qiita.com/theoden9014/items/6e1ba5294c6d6bd37538) が参考になりました。

```
% docker tag my-go-practice gcr.io/[PROJECT-ID]/my-go-practice:1.0
% docker images
REPOSITORY                             TAG                 IMAGE ID            CREATED             SIZE
my-go-practice                         latest              115573622dc3        3 minutes ago       809MB
gcr.io/[PROJECT-ID]/my-go-practice     1.0                 115573622dc3        3 minutes ago       809MB
```

よし、タグが付いたのでContainer RegistryにPUSH。

```
% docker push gcr.io/[PROJECT-ID]/my-go-practice:1.0
The push refers to repository [gcr.io/[PROJECT-ID]/my-go-practice]
a55b08a710fd: Pushed
ccd59ad1b6c1: Pushed
feae5ce26f02: Layer already exists
29a23d067e48: Layer already exists
1af88e92b20b: Layer already exists
d35c5bda4793: Layer already exists
a3c1026c6bcc: Layer already exists
f1d420c2af1a: Layer already exists
461719022993: Layer already exists
latest: digest: sha256:df8c5ff70adf2232844e2789db8365a01bc7aa7c34b15776166f34963665ec19 size: 2209
```

無事PUSH出来ているかを確認してみる。

```
% gcloud container images list
NAME
gcr.io/[PROJECT-ID]/my-go-practice
Only listing images in gcr.io/[PROJECT-ID]. Use --repository to list images in other repositories.
```

大丈夫そうなので次にこれを使ってデプロイをしてみる。

# Container Registryのイメージを利用したDeploymentを作成

では新しくそれを使ったデプロイを試してみる。
今回は以下のようにDeploymentを作って更に外部公開する。


```
% kubectl create deployment hello-server --image=gcr.io/[PROJECT-ID]/my-go-practice:1.0
% kubectl expose deployment hello-server --type LoadBalancer --port 80 --target-port 8080
```

上手く行ったのでcurlしてみる。

```
% curl 34.71.197.192
Hello, world!
Version: 1.0.0
```

想定通りの動作をしたのでOK。


# Container Registryのイメージを更新してからローリングアップデート

では次にContainer Registryのイメージ更新がかかった場合に、Deploymentが勝手にローリングアップデートをしてくれることを確認する。

Deploymentを作った時のイメージ指定は `--image=gcr.io/[PROJECT-ID]/my-go-practice:1.0` なので、このタグのイメージが置き換われば切り替わるはず・・・と言う想定。

まずmain.goを以下のように修正。

```go
package main

import (
	"net/http"
)

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

func handler(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusOK)
	w.Write([]byte("Hello, world!\n"))
	w.Write([]byte("Version: 2.0.0\n")) // <-- ここ
}
```

この状態でローカルのイメージを削除し作り直す。更にタグを追加。

```
% docker build -t my-go-practice  .
% docker tag my-go-practice gcr.io/[PROJECT-ID]/my-go-practice:2.0
```

GCRにPUSHする。

```
% docker push gcr.io/[PROJECT-ID]/my-go-practice:2.0
% gcloud container images list
NAME
gcr.io/[PROJECT-ID]/my-go-practice
```

次にserviceのイメージを変更して、ローリングアップデートがかかるのをみておく。

```
% kubectl set image deployments/hello-server my-go-practice=gcr.io/[PROJECT-ID]/my-go-practice:2.0
deployment.extensions/hello-server image updated
```

ちなみにコマンドの書式は、
```
% kubectl set image <deployment> <container name>=<image>
```

であり、 **container name** の部分は、 `kubectl describe deployments hello-server` とかで取れる。

```
% kubectl describe deployments hello-server
Name:                   hello-server
Namespace:              default
CreationTimestamp:      Tue, 21 Apr 2020 09:28:06 +0900
Labels:                 app=hello-server
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=hello-server
Replicas:               1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
Pod Template:
  Labels:  app=hello-server
  Containers:
   my-go-practice: <-- ここ
    Image:        gcr.io/[PROJECT-ID]/my-go-practice:2.0

(略)
```

試しに接続してみるとこうなったのでアップデートOK。

```
% curl [EXTERNAL-IP]
Hello, world!
Version: 2.0.0
```

とりあえず今回はここまで。
次回以降Spinnakerを入れてPipelineを試していく。