GAミント至上主義

Web Monomaniacal Developer.

Google Container Engineでdeploymentとserviceをコピーするために設定を出力する

Google Container Engineで運用しているアプリケーションをそろそろ東京リージョンに移そうと思った。

これまで、

kubectl get service -o yaml
kubectl get deployment -o yaml

で出力されるyamlは手作業で必要な項目消したり、書き換えていたりしてから、kubectl -f {ファイル名}で新しいコンテナクラスタに読み込んでいた。


でも、それぞれ数が増えてきて、やってられないので自動化しようと思った。

JSONで出力して、Pythonなり、何らかのプログラムでパースするしかないかなと思ってたけど、Kubernetesのドキュメントを漁っていたらtempleteというのを見つけた。

公式で探したけど、過渡期なのかリンク切れがひどくて見つけられない。

ともかく使い方としては下記のようなコマンド。

※kcは.zshrcで設定しているkubectlのエイリアス

% kc get service -o=go-template-file=service-backup-template.txt
% kc get deploy -o=go-template-file=deploy-backup-template.txt

go-template-file=には、Go言語の表示パッケージであるtemplate形式でファイルを作る。

template - The Go Programming Language

何度もエラーと戦いながら2時間以上書けて書いた。環境によっては足りない項目等で、そのままでは使えない場合がある。

deploy-backup-template.txt

apiVersion: {{.apiVersion}}
items:{{with .items}}{{range . }}
- apiVersion: {{.apiVersion}}
  kind: {{.kind}}
  metadata:
    name: {{.metadata.name}}
  spec:
    replicas: {{.spec.replicas}}
    template:
      metadata:
        labels:
          run: {{.spec.template.metadata.labels.run}}
      spec:{{with .spec.template.spec.containers}}{{range .}}
        containers:
        - name: {{.name}}
          {{with .env}}env:{{range .}}
          - name: {{.name}}
            value: {{.value}}{{- end}}{{end}}
          {{with .command}}command:{{range .}}
          - {{.}}{{end}}{{end}}
          image: {{.image}}
          {{with .ports}}{{range .}}
          ports:
            - containerPort: {{.containerPort}}
              protocol: {{.protocol}}
          {{end}}{{end}}
          {{with .volumeMounts}}{{range .}}
          volumeMounts:
            - mountPath: {{.mountPath}}
              name: {{.name}}
        {{end}}{{end}}
      {{end}}{{end}}
      {{with .spec.template.spec.volumes}}{{range .}}
        volumes:
        - gcePersistentDisk:
            fsType: {{.gcePersistentDisk.fsType}}
            pdName: {{.gcePersistentDisk.pdName}}
          name: {{.name}}{{end}}{{end}}
{{end}}{{end}}
kind: List
metadata: {}
resourceVersion: ""
selfLink: ""

この可読性保持のために出力されたファイルには不要な空白行が含まれるけど、読み込みには問題ない。

出力後、いくつか手作業が必要。

  • envのvalueに数字のみの値がある場合、出力した後に手作業で""で囲む
  • Volumeに使う永続ディスクを変更する。


service-backup-template.txt

apiVersion: {{.apiVersion}}
items:{{with .items}}{{range .}}{{ if ne .metadata.name "kubernetes"}}
- apiVersion: {{.apiVersion}}
  kind: {{.kind}}
  metadata:
    name: {{.metadata.name}}
  spec:
    ports:{{with .spec.ports}}{{range .}}
    - port: {{.port}}
      {{if .nodePort}}nodePort: {{.nodePort}}{{end}}
      protocol: {{.protocol}}
      targetPort: {{.targetPort}}{{end}}{{end}}
    selector:
      run: {{.spec.selector.run}}
    sessionAffinity: {{.spec.sessionAffinity}}
    type: {{.spec.type}}
{{end}}{{end}}{{end}}
kind: List

こちらはデフォルトで存在するkubernetesのサービスを{{ if ne .metadata.name "kubernetes"}}で飛ばすようにしてある。
HTTPSロードバランサーを使っているので、KubernetesのロードバランサーのServiceは使っている場合は、変更が必要だと思う。

これを作ったことで、今後のバックアップと複製から大幅に手作業をカットできた。

GKEでPODが立ち上がらなくなったときの調査方法

Google CONTAINER ENGINE(以下GKE)ではreplicasの値で簡単にPODを増やせる。


https://cloud.google.com/container-engine/cloud.google.com


でも、なぜかStatusがPendingのままのPODが出るときがある。

※kcはkubectlのエイリアス

% kc get po
NAME READY STATUS RESTARTS AGE
arms-2765276764-o4c8l 1/1 Running 0 21d
arms-2765276764-owzgw 1/1 Running 0 21d
arms-2765276764-tkvdw 1/1 Running 0 21d
gopub-3058232056-emfwd 0/1 Pending 0 9m
gopub-3058232056-h6aan 1/1 Running 0 9m
gopub-3058232056-qiz9u 1/1 Running 0 11m
r2bq-95534634-4ptzf 1/1 Running 0 19d
rabbitmq-3083250092-8jj79 1/1 Running 0 75d
redis-pd-1996650620-p1o6f 1/1 Running 0 160d
revelation-749222209-qqguw 1/1 Running 0 16m
revelation-worker-3204269062-t1vyn 1/1 Running 0 15m
table-manager-1264182560-txkwf 1/1 Running 1 21d

今回、gopub-3058232056-emfwdがいつまで経ってもPendingだった。

おそらくリソース不足なんだけど、詳細が欲しい。

そんなときは、kubectlでイベントを取得する。

kubectl get events
また省略形の
kubectl get ev
で表示できる

% kc get ev (git)-[master]
LASTSEEN FIRSTSEEN COUNT NAME KIND SUBOBJECT TYPE REASON SOURCE MESSAGE15m 15m 1 gopub-2970282743-agna1 Pod spec.containers{gopub} Normal Killing {kubelet gke-oceanus-asia-b-default-pool-0eeece87-38ht} Killing
container with docker id f23ac3bcf97a: Need to kill pod.
15m 15m 1 gopub-2970282743 ReplicaSet Normal SuccessfulDelete {replicaset-controller } Deleted
pod: gopub-2970282743-agna1
44s 13m 39 gopub-3058232056-emfwd Pod Warning FailedScheduling {default-scheduler } pod (go
pub-3058232056-emfwd) failed to fit in any node
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-38ht): Insufficient cpu
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-m6i1): Insufficient cpu

20s 13m 10 gopub-3058232056-emfwd Pod Warning FailedScheduling {default-scheduler } pod (gopub-3058232056-emfwd) failed to fit in any node
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-m6i1): Insufficient cpu
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-38ht): Insufficient cpu

13m 13m 1 gopub-3058232056-h6aan Pod Normal Scheduled {default-scheduler } Successfully assigned gopub-3058232056-h6aan to gke-oceanus-asia-b-default-pool-0eeece87-38ht
13m 13m 1 gopub-3058232056-h6aan Pod spec.containers{gopub} Normal Pulled {kubelet gke-oceanus-asia-b-default-pool-0eeece87-38ht} Container image "asia.gcr.io/oceanus-dev/gopub:v20170328-02" already present on machine
13m 13m 1 gopub-3058232056-h6aan Pod spec.containers{gopub} Normal Created {kubelet gke-oceanus-asia-b-default-pool-0e


今回は下記の内容から、Insufficient cpu→CPUが不足という原因がわかった。

44s 13m 39 gopub-3058232056-emfwd Pod Warning FailedScheduling {default-scheduler } pod (go
pub-3058232056-emfwd) failed to fit in any node
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-38ht): Insufficient cpu
fit failure on node (gke-oceanus-asia-b-default-pool-0eeece87-m6i1): Insufficient cpu

原因が分かれば、クラスタの数を上げるかノードのマシンタイプをアップグレードするか、またはPODを増やすのを諦めるかすれば良い。


クラスタの数は、コマンドなり、クラウドコンソールなりで簡単に数字をいじるだけで変更できるけど、マシンタイプは新しく作り直す必要があるので大変。

またノードの数が5つを超えると料金がかかったりした記憶があるので注意。

今回のgopubサーバーはGo言語で初めて作ったサーバーで、一つ消費メモリ4~7M程度で動いてるので、たくさん動かせるかと思ったけど、n1-standard-1×2ではそろそろ限界が来てしまったよう。

追記

kubectl describe pod {POD ID}

でも似たような結果を取得できる

Go言語でgRPCのエラーと戦って負ける

以前の記事からGo言語でサーバーアプリを自分で書き始めて、エラーハンドリングが必要になった。


エラーについては下記記事が参考になった。
qiita.com


この記事のように自分で作ったエラーならいいんだけど、大抵は外部のライブラリのエラーではまる。

Cloud PUB/SUBのライブラリ("cloud.google.com/go/pubsub")は、内部ではgRPCでリクエストをしており、エラーもこのライブラリのが返ってくる。


godoc.org


godoc.org

import "google.golang.org/grpc"

下記のようなコードで、起動時にはトピックがあったけど、その後に消されてしまったとき、ps.publishHandlerが返すエラーを特定したかった。

ソースコード全体はこちら

とりあえず、一番汚いやり方、err.Error()で帰ってきた文字列をstrings.Indexで単純に比較。これでも「今は」動く。

go func() {
    err = ps.publishHandler(buf[:n])
    if err != nil {
        log.Printf("publishHandler err: %v", err)
        log.Printf("err type:%v", reflect.TypeOf(err))
        log.Printf("err.Error():%v", err.Error())
        log.Println(strings.Index(err.Error(), "rpc error: code = NotFound desc = Resource not found"))

    }
}()

実行すると

2017/03/28 01:51:19 publishHandler err: rpc error: code = NotFound desc = Resource not found (resource=ml30gen9-bizocean).
2017/03/28 01:51:19 err type:*grpc.rpcError
2017/03/28 01:51:19 err.Error():rpc error: code = NotFound desc = Resource not found (resource=ml30gen9-bizocean).
2017/03/28 01:51:19 0

と0が帰ってきて判別はできる。含まれない場合は-1。


エラーの型には*grpc.rpcErrorが返ってくる。

単純にこいつと型比較しようとするとrpcErrorという名前から分かるように、最初が小文字なので外部から呼び出せなくてむかつく。

なぜ外から触れないようになってるのか、私のGo力(ごーぢから)ではソースコードから読みとれなかった。

grpc - GoDoc

grpcには下記のような関数があり、エラーを渡せば、コードが返ってくる。

func Code(err error) codes.Code

が、他のエラーも返ってくることもあり、その場合エラーになるのでまず型判定が必要。

app/main.go:212: cannot convert err (type error) to type codes.Code


reflect.TypeOfを使って

src/reflect/type.go - The Go Programming Language

返ってくるTypeをstringにして比較してみる。

if reflect.TypeOf(err).String() == "*grpc.rpcError" {
    //process
}

これは成功。

でも文字列比較はダサい。


だがさっき書いた用に呼び出して型を調べようとするとエラーになる。文字列化もできない。

if reflect.TypeOf(err) == grpc.rpcError {
  //process
}
if reflect.TypeOf(err) == *grpc.rpcError {
  //process
}

log.Printf("TypeOf(*grpc.rpcError):v", reflect.TypeOf(grpc.rpcError).String())

3つともこういうエラーが出る。

app/main.go:209: cannot refer to unexported name grpc.rpcError

今のところ下記のダサい方法しかない

if reflect.TypeOf(err).String() == "*grpc.rpcError" {
  //process
}||<


誰か助けてー