入門+αくらい
作ったら壊すので無料であることに全くこだわってません。
事前にやっておく
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(フロント側)
こんなかんじ
ほぼvue-cliまんま
firebaseでリダイレクト認証
認証済みトークンをAuthorization: Bearerに突っ込んでgoapiにぶんなげ
なんでcontent typeがjson…?とかは気にしたら負け
getRedirectResultドチャクソ遅くねえ?って問題は自分も相当カスだと思ってるのでグーグルやる気出してくれ
Dockerfile補足
chalkを別途入れないとうまくいかないのなんとかしたいんだけど…どうするの…。
namespace
namespaceを作るマニフェスト。
無くても困りはしませんが、実運用を視野に入れた場合、どうせ必要になる系なので訓練のために。
deployment
コンテナ(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から読み出して使うことに関して、雑に。
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
podへのアクセス手段の面倒を見てくれます。今回はnodeport使うんで、
各種ノードはポート30000-32767のどれかを開いてserviceに開けたポートを伝え、serviceはリクエスト受け取ったらその内容使って通信さす、的な動きになる はず。
configmap
これは読めばなんとなくわかると思うんで特筆することなし。
Go側
Dockerfile
途中で面倒くさくなって「もうこれでいいや」感すごくなってます。
あんまり参考にしないように。
deployment
vue側のconfigMapがsecretに変ったくらい。あとenvを追加しています。
今回はenvとして食わせたいキーバリューが1個しかないのでvalueFromとkeyRefを使っていますが、まとめて読み出せるenvFromとrefってのもあるようです。
service
書くことなし
マネージド証明書
DV認証でいいならこれ一択かなあ。
ingress
外部から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を持ってきて、
cat jsonファイル名 | docker login -u _json_key --password-stdin [[GCRのURL]]
GCRのURLは公式見て決めましょう。
push
dockerビルド部分は割愛
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. 今回デプロイするアプリケーションが鎮座する名前空間を作る
- configMap/secretを設定する(使ってる場合先にやらないとpodが困る)
- podを起こす(deployment)
- podへのアクセス手段を確立(service)
- インターネッツからserviceへつなぐためにLB起こす(ingress)
ってノリでいきましょう
正確なところや詳細は後々必要になったらね
クラスタ接続
接続用のコマンドはクラスタ概要の接続ボタンから確認できます。
gcloud container clusters get-credentials [[cluster name]] --zone [[zone]] --project [[project id]]
kubectl config get-contexts
ちなみにこの時にインスタンスが食ってるサービスアカウントだけだと403が返り、API権限付与したら通るようになりました。
namespace
kubectl apply -f prac.yaml
# 確認
kubectl get namespace
vue側configMap
kubectl apply -f nginx.conf.configmap.yaml
# 確認
kubectl -n prac get configmap nginx-config -o yaml
-nでnamespace指定してます。カレントのネームスペースを切り替えたりも出来ます。
自分は別に-nつけるのに抵抗ないですが、面倒な方は操作しやすいように調整すると良いかと。
vue側コンテナ
kubectl apply -f deployment.yaml
# 確認
kubectl -n prac get pod
kubectl -n prac get deployments
get podした時、Creatingみたいなステータスから進まない場合、
kubectl -n prac describe pods [[pod名]]
で確認してみましょう。volumeマウントの指定ミスってたり、configMapの指定ミスってたりするとそうなるっぽい。
CrashLoopBackOffとかErrorの場合はdescribeで取れる情報だけだと分からないので
kubectl -n prac -c [[コンテナ名]] logs [[pod名]]
でログ見ましょう。CrashLoopBackOffの方はプロセスが正常終了してしまってる場合でも出るので、エラーっぽいログが無い場合はその観点でも確認すると良いかもしれない。
vue側service
kubectl apply -f service.yaml
# 確認
kubectl -n prac get service
この例だとserviceに外部IPが付与されないため、インターネットからは見れません。
go側secret
kubectl apply -f secret.yaml
# 確認
kubectl -n prac get secret prac-secret -o yaml
実運用時はsecret.yamlを食わせたらファイル本体はすぐ消すとかそういうことになるかと思います。
yaml自体の管理は従来のcredentialとかと同じように扱えば良いかなと。
go側depolyment/service
やること同じなんで割愛
ingress/certificate
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に触る
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関連は目を皿のようにして確認するようになりました。