GKEのれんしゅう

入門+αくらい
作ったら壊すので無料であることに全くこだわってません。

## 事前にやっておく
NAT構築
踏み台作成(externalIP有)(subnet1)
GKE用subnet2作成
GKEから利用するGCEインスタンス作成(externalIPなし)(subnet1)※1
ドメイン取得済み
グローバル静的IPを1つ取得
firebase側設定にドメイン許可を出しておく
firebase用ブラウザキーにGKE用subnet2のip範囲を許可しておく
GCS管理者権限を持つサービスアカウントを発行しておく(GCR用)

※1 これは自分の要件に必要なのでやるだけで、必須かそうでないかでいうと無くても良いです

## 前提
gcloud/kubectlｲﾝｽｺ済みの環境があること(この記事だと踏み台にインスコ済み)

踏み台に飲ませてあるサービスアカウントはGKE用の権限ありましたが、VM自体にAPIの実行権限を付与しないと`get-credentials`が403を返してきました。多分両方必要？

## 構成
LB(インターネット) -> 限定公開GKE(nginx(ビルド済みvueアプリ設置) -> golang製api)

## 達成したいこと
インターネットに浮かぶLBから限定公開GKEにあるvueアプリ(nginx)にアクセス
vueアプリから同じくGKE内のgolang製apiにアクセス
nginxの設定ファイルをconfigMapから読んでファイルマウント
vueアプリからはfirebase authに触る
golang製apiからはsubnet1に浮かぶGCEインスタンスにHTTPアクセスする(後述)
golang製apiにはfirebase用サービスアカウント情報、単一環境変数をsecretから食わせる。

## やらなかったこと
vueアプリ -> goのところ、内部LB仕掛けたかったけどどうにもスンナリ行く気がしなかったので一旦断念。

## ｿｰﾇ(参考)
https://github.com/syanhaiD/gke_prac_front
https://github.com/syanhaiD/gke_prac_back

実運用時private repoになることを想定。
設定値関係は適宜置換してください。
そのまま動くかどうかはあんまり確認してないのであくまで参考。

実施内容がymlに残るってことで、基本全部マニフェストにしてます。

## GKEクラスタ構成
特筆部分はないので要所だけ

g1-small * 3
1.14.10-gke17

ノードプール作成時、NAT用のタグを付与しておきます。
自分が調べた範囲だと、作成時につけなかったらもう付与できる機会がない感。
ノードプールの再作成自体は出来るので、ミスったらプールごと作り直しましょう。

限定公開クラスタ
外部IPマスターアクセス許可/マスター操作で利用するip範囲を入力
踏み台から操作する予定なので、踏み台の「外部IP」を許可。外部IPがついてるインスタンスの場合、内部IPだとダメっぽい。

他は今回やることに影響しないので割愛。

## vue(フロント側)
https://github.com/syanhaiD/gke_prac_front

### こんなかんじ
ほぼvue-cliまんま
firebaseでリダイレクト認証
認証済みトークンをAuthorization: Bearerに突っ込んでgoapiにぶんなげ
なんでcontent typeがjson…？とかは気にしたら負け
`getRedirectResult`ドチャクソ遅くねえ？って問題は自分も相当カスだと思ってるのでｸﾞｰｸﾞﾙやる気出してくれ

### Dockerfile補足
chalkを別途入れないとうまくいかないのなんとかしたいんだけど…どうするの…。

### namespace
https://github.com/syanhaiD/gke_prac_front/blob/master/k8s/prac.yaml

namespaceを作るマニフェスト。
無くても困りはしませんが、実運用を視野に入れた場合、どうせ必要になる系なので訓練のために。

### deployment
https://github.com/syanhaiD/gke_prac_front/blob/master/k8s/deployment.yaml

コンテナ(pod)本体です。なんか色々設定ファイルあるけど、とりあえずこやつが本体。

トップレベルのmetadata.labelsはあくまで検索用であってデプロイ挙動に何か影響があるのかと言われると無いらしいので、わざとspec側と一致しないようにしてみた。

spec側matchLabelsとtemplate.metadata.labelsに関して、selector側のラベルが全てtemplateに含まれていればapplyが通り、逆は通らない。`selector does not match template labels`的なエラー出ます。
また、k8sに食べさせてしまうとtemplate側のlabelは変更が出来ないようです。delete->applyならいけるけど。
template側に設定したラベルをmatchLabels側に自動適用してくれれば良いのにねーと思った。

`readinessProbe` はヘルスチェックのパスやらスキーマを指定できるようになるらしい。
指定しないと`/`に飛んでくので、専用のパスを用意。

nginx.confをconfigMapから読み出して使うことに関して、雑に。

```yaml
          volumeMounts:
            - mountPath: /etc/nginx/custom # どこにマウントボリュームを置くか
              readOnly: true # 読んで字のごとく
              name: nginx-config # マウント対象の名前を指定
      volumes:
        - name: nginx-config # マウント対象の名前
          configMap:
            name: nginx-config # configMapの名前
            items:
              - key: nginx.conf # configMapに定義したdataのキー
                path: nginx.conf # ファイル名 この場合だと /etc/nginx/custom/nginx.conf
```
詳しく知りたいなら公式読みましょう

### service
https://github.com/syanhaiD/gke_prac_front/blob/master/k8s/service.yaml

podへのアクセス手段の面倒を見てくれます。今回はnodeport使うんで、
各種ノードはポート30000-32767のどれかを開いてserviceに開けたポートを伝え、serviceはリクエスト受け取ったらその内容使って通信さす、的な動きになる　はず。

### configmap
https://github.com/syanhaiD/gke_prac_front/blob/master/k8s/nginx.conf.configmap.yaml

これは読めばなんとなくわかると思うんで特筆することなし。

## Go側
https://github.com/syanhaiD/gke_prac_back

### Dockerfile
途中で面倒くさくなって「もうこれでいいや」感すごくなってます。
あんまり参考にしないように。

### deployment
https://github.com/syanhaiD/gke_prac_back/blob/master/k8s/deployment.yaml

vue側のconfigMapがsecretに変ったくらい。あとenvを追加しています。
今回はenvとして食わせたいキーバリューが1個しかないのでvalueFromとkeyRefを使っていますが、まとめて読み出せるenvFromとrefってのもあるようです。

### service
https://github.com/syanhaiD/gke_prac_back/blob/master/k8s/service.yaml

書くことなし

### マネージド証明書
https://github.com/syanhaiD/gke_prac_back/blob/master/k8s/certificate.yaml

DV認証でいいならこれ一択かなあ。

### ingress
https://github.com/syanhaiD/gke_prac_back/blob/master/k8s/ingress.yaml

外部からserviceへのルーティング(http/https)をしてくれます。
http/https以外はservice側でそのまま公開するようにすることが多いそうです。(そもそもingressで可能なのか…？)
完全なイコールではありませんがロードバランサって覚えておけば大きく外さないと思います。

書いてるように、パスベースのルーティングが可能です。
また、この例だとhttpアクセスを不許可にしています。この場合でもhealthcheckは普通に通るので大丈夫。
`kubernetes.io/ingress.global-static-ip-name`は事前に取得しておいたグローバル静的IPの名前を入れます。

こういう、複数repoにまたがるようなマニフェストってどう管理するのがわかりやすいんだろうね。
マニフェスト専用のrepo作るってのもなんともアレな感じするし…

# デプロイと公開
## dockerイメージをGCRに放り込む
### ログイン
docker build自体はローカルでやります。(実運用時はGCPの外で実施することになりそうなので)
前もって用意しておいたストレージ管理者権限を持つサービスアカウントのjsonを持ってきて、

```bash
cat jsonファイル名 | docker login -u _json_key --password-stdin [[GCRのURL]]
```
GCRのURLは[公式](https://cloud.google.com/container-registry/docs/overview?hl=ja)見て決めましょう。

### push
dockerビルド部分は割愛

```bash
docker tag [[イメージにつけたタグ]] [[GCRのURLとプロジェクトIDを含むイメージURL]]
docker push [[GCRのURLとプロジェクトIDを含むイメージURL]]

# e.g.
docker tag sugoi/image:v1.0 gcr.io/[[project id]]/sugoi/image:v1.0
docker push gcr.io/[[project id]]/sugoi/image:v1.0
```

## gcloud/kubectl
ここからは前述の踏み台サーバで作業します
また、マニフェストは都合の良いように配置済みであるとします

流れとして、
0. 今回デプロイするアプリケーションが鎮座する名前空間を作る
1. configMap/secretを設定する(使ってる場合先にやらないとpodが困る)
2. podを起こす(deployment)
3. podへのアクセス手段を確立(service)
4. インターネッツからserviceへつなぐためにLB起こす(ingress)
ってノリでいきましょう
正確なところや詳細は後々必要になったらね

### クラスタ接続
接続用のコマンドはクラスタ概要の接続ボタンから確認できます。

```bash
gcloud container clusters get-credentials [[cluster name]] --zone [[zone]] --project [[project id]]
kubectl config get-contexts
```
ちなみにこの時にインスタンスが食ってるサービスアカウントだけだと403が返り、API権限付与したら通るようになりました。

### namespace
```bash
kubectl apply -f prac.yaml
# 確認
kubectl get namespace
```

### vue側configMap
```bash
kubectl apply -f nginx.conf.configmap.yaml
# 確認
kubectl -n prac get configmap nginx-config -o yaml
```
`-n`でnamespace指定してます。カレントのネームスペースを切り替えたりも出来ます。
自分は別に-nつけるのに抵抗ないですが、面倒な方は操作しやすいように調整すると良いかと。

### vue側コンテナ
```bash
kubectl apply -f deployment.yaml
# 確認
kubectl -n prac get pod
kubectl -n prac get deployments
```
`get pod`した時、`Creating`みたいなステータスから進まない場合、

```bash
kubectl -n prac describe pods [[pod名]]
```
で確認してみましょう。volumeマウントの指定ミスってたり、configMapの指定ミスってたりするとそうなるっぽい。
`CrashLoopBackOff`とか`Error`の場合はdescribeで取れる情報だけだと分からないので

```bash
kubectl -n prac -c [[コンテナ名]] logs [[pod名]]
```
でログ見ましょう。`CrashLoopBackOff`の方はプロセスが正常終了してしまってる場合でも出るので、エラーっぽいログが無い場合はその観点でも確認すると良いかもしれない。

### vue側service
```bash
kubectl apply -f service.yaml
# 確認
kubectl -n prac get service
```
この例だとserviceに外部IPが付与されないため、インターネットからは見れません。

### go側secret
```bash
kubectl apply -f secret.yaml
# 確認
kubectl -n prac get secret prac-secret -o yaml
```
実運用時はsecret.yamlを食わせたらファイル本体はすぐ消すとかそういうことになるかと思います。
yaml自体の管理は従来のcredentialとかと同じように扱えば良いかなと。

### go側depolyment/service
やること同じなんで割愛

### ingress/certificate
```bash
kubectl apply -f certificate.yaml
kubectl apply -f prac-ingress.yaml
# 確認
kubectl -n prac get ingress prac-ingress
# 証明書確認
kubectl -n prac describe managedcertificate
```
マネージド証明書のプロビジョンは10分15分かかります。

### podから同一VPC・異subnetのGCEに触る
```bash
kubectl -n prac exec -it [[pod名]] /bin/ash
```
これでコンテナ入れるのでコンテナ内から異subnetのGCEの内部IPをcurlなりtelnetなりで突っついてすぐにレスポンスがあればok。
うまくいかない場合、接続対象のインスタンスのFWルールに対し、ノードでなくpodのIP範囲を追加しましょう。
podのIP範囲はVPCメニューのサブネットから確認できます。

### 確認
設定がつつがなく終わっていればGCPメニューの負荷分散見に行くと、LBが鎮座してるはず。
ヘルスチェックがうまくいってることを確認しましょう。

うまくいってれば、あとはブラウザに今回設定したドメイン打ち込めばアクセスできるはずです。
firebaseログインも特にトラブルなくいけました。
SSL関係のエラー出るようでしたら`describe managedcertificate`でActiveになってるか確認しましょう。
自分の場合、Activeになってから2-3分さらにかかりました。

## ドチャクソﾊﾏｯた
### ingressが起きたのに502が返ってくる
今にして思えば間抜けすぎるというか、「そりゃそうやんな」なんですが最初なかなか気づけなかったので…

結論、
pod(deployment)のmatchLabels
serviceのselector
ingressのrules内serviceName
は良く確認しようなってオチ。

serviceのselectorに対応するpodのmatchLabelsがなく、でもingress-service間はうまくいってる場合、表題のような事象になるっぽいです。これ踏んだせいで毎回selector関連は目を皿のようにして確認するようになりました。