オンプレミスの k8s クラスタを GKE に移行した時のメモ
はじめに
社内に kubeadm
で作ったオンプレミスの k8s
クラスタがあったのですが、建物の全館停電があった際に停止に失敗して飛んでしまい、それ以降 microk8s
で作った簡易的なシングルノードクラスタで凌いできました。
この度、ようやく社長の許可を得て k8s
クラスタをクラウドに移行する事になったので、その時に調べた事や引っかかったところをメモとして残しておきます。
オンプレミスクラスタではマイクロサービスで構成された社内システムが動いており、
- microk8s
- Istio (認証/ルーティング)
- PostgreSQL (クラスタ外)
という構成で動いています。
これを
- GKE + ASM
- Cloud SQL
の構成にしていきます。
移植先のクラスタを作成する
移植元に Istio
が使われていたため、GKE
+ ASM
の構成でクラスタを作っていきます。色々と試行錯誤した結果を別記事にまとめましたので、詳しくは以下ご参照ください。
デプロイの準備
引っかかったところもありましたが、基本的に公式ドキュメントがとてもよくできているので、公式ドキュメント通りに進めたら出来る事が多いです。
データベースを Cloud SQL に移植する
現行サイトのデータを Cloud SQL
に流し込むところ+αまでを別記事にまとめたので、ご参照ください。
Artifact Registry にイメージを登録する
今回は社内システムという事でイメージがオープンに出来ないため、Artifact Registry
を利用してプロジェクト内で閉じたプライベートなレジストリにイメージを登録します。
ローカルの kubectl から GKE を参照できるようにする
サービスアカウントを使って gcloud
認証した後、kubectl
の認証情報を取得します。
この設定は CI を使う場合でも同様です。
GKE から Cloud SQL に接続できるようにする
GKE
から Cloud SQL
に接続するには、Cloud SQL Proxy
を使用する必要があります。
ドキュメントを参考にし、今回はサイドカーパターン、認証は Workload Identity
を使用しました。
Cloud SQL
を参照するサービスはシステム内に複数あったのですが、 Cloud SQL Proxy
用のサービスアカウントは1つにし、全ての Cloud SQL Proxy
サイドカーでそのアカウントを参照するようにしました。
※ cloud-sql-proxy
という名前でサービスアカウントを作っています。
kubectl apply -n <名前空間> -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
name: cloud-sql-proxy
namespace: <名前空間>
EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: <Cloud SQLプロキシ サイドカーの名前>
namespace: <名前空間>
spec:
template:
spec:
serviceAccountName: cloud-sql-proxy
containers:
- name: cloud-sql-proxy
image: gcr.io/cloudsql-docker/gce-proxy
command:
- "/cloud_sql_proxy"
- "-log_debug_stdout"
- "-instances=<接続名>"
securityContext:
runAsNonRoot: true
Workload Identity
の設定方法は上記リンクにある通りですが、GKE
のサービスアカウントが特定の名前空間内にある場合は、kubectl annotate serviceaccount
コマンド内でも同様に指定する必要があります。
kubectl annotate serviceaccount \
-n <名前空間> \
cloud-sql-proxy \
iam.gke.io/gcp-service-account=<GCPのサービスアカウント名>@<プロジェクト名>.iam.gserviceaccount.com
istio-ingressgateway を gatewayAPIに移行する
クラスタ作成の所で GatewayAPI
を使用しているのですが、旧システムは istio-ingressgateway
を使用しているため、書き替え作業が必要になりました。
ざっくりとした理解ですが、
- (新)Gateway -> (旧)Gateway
- VirtualService -> HTTPRoute
というイメージです。
旧システムでは (旧)Gateway
を複数作っていましたが、1つの (新)Gateway
から HTTPRoute
を生やした方が今回は取り回しが良さそうだったので、そのように書き換えました。
Gateway (1つだけ作成)
kubectl apply -f - <<EOF
apiVersion: gateway.networking.k8s.io/v1beta1
kind: Gateway
metadata:
namespace: <名前空間>
name: <Gatewayの名称>
annotations:
networking.gke.io/certmap: <認証マップ名>
spec:
gatewayClassName: gke-l7-gxlb
listeners:
- name: https
port: 443
protocol: HTTPS
addresses:
- type: NamedAddress
value: <予約済みのIP>
EOF
gatewayClassName
に指定している gke-l7-gxlb
は、最初に紹介した記事の設定を進めると作成されます。
HTTPRoute (アプリごとに作成)
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: <HTTPRouteの名称>
namespace: <名前空間>
spec:
hostnames:
- <ドメイン>
parentRefs:
- name: <Gatewayの名称>
rules:
- backendRefs:
- name: <アプリの名称>
port: <アプリの動作ポート>
matches:
- path:
type: PathPrefix
value: <待ち受けているパス>
Cloud Load Balancing のヘルスチェックに対応する
GatewayAPI
を使用すると、ロードバランサとして Cloud Load Balancing
を利用する事になります。
Cloud Load Balancing
を利用するためにはヘルスチェックを通す必要があるのですが、今回作ったアプリの中にはルートパス(/
)で待ち受けていないものもあり、そういったものはヘルスチェックが通らずに導通が出来ませんでした。
ヘルスチェックのパスを変更する事も出来るようなのですが、今回作ったアプリは Istio
を使ってJWT
のトークン認証を行っており、ヘルスチェックはその影響も受けるようです。
そのため、アプリが実際に待ち受けているパスでヘルスチェックを行っても結局 Istio
に阻まれて導通できず、最終的にはアプリの方を変更してルートに無条件で 200
を返すエンドポイントを設け、Istio
の設定でもルート(/
)は認証無しで通過できるようにしました。
若干強引な気もしましたが、HTTPRoute
のパスルールでルートを指定しなければ外部からここに到達する事は出来ないので、今回は良しとしました。
mTLS は使わない
移植元では強制的に mTLS
を使うように設定されていたので、以下のように有効にしたのですが…
kubectl apply -f - <<EOF
apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
name: default
namespace: <名前空間>
spec:
mtls:
mode: STRICT
EOF
Cloud Load Balancing
では mTLS
が非対応でした。
これを有効にしているとヘルスチェックが通らず、ここで1日溶かしました…
mTLSは何に使っていた?
旧環境で mTLS
を使用していたのは istio
の AuthorizationPolicy
でサービスアカウントベースの認証ポリシーを使用していたからでした。
仕様上どうしようもないので、今回は諦めて別の方法で認証するように変更しました。
デプロイする
今回は GitLab CI
+ Skaffold
でデプロイするため、 .gitlab-ci.yml
を書いてデプロイしました。ここの構成は色々な前提があったり複雑だったりするので、機会があったらまた別の記事で書こうと思います。
2023.04.19 追記: 関連記事を書きました
Skaffold
+ Kustomize
についての記事を書きました。
Skaffold
を使用した例ではないですが、GitLab Runnerを使ったデプロイについての記事を書きました。
http を強制的に https にする
このままだと https
でしか接続できないので http
で接続された際は強制的に https
にリダイレクトされるようにします。
基本的には以下の内容の通りに設定するだけで実現できます。
料金の調整をする
ここまでで基本的な動作はするようになったのですが、2~3日動かしてみると想像以上に料金がかかっている事が発覚。調べてみるとリソースの事は全く考えずにデフォルト値を使ってデプロイしているので、今回必要なリソースに対してかなり過剰なリソースが割り当てられている事が分かりました。
リソースの調整をする
今まではオンプレミスだった事もあってあまり気にしてこなかったのですが、しっかりとリソースの割り当てを調整する必要がありそうです。
Kubernetes
標準機能で割り当てリソースの調整はできますが、調べてみると GKE
の制約で好きな値に設定できるわけではない事が分かりました。
また、今回 istio-proxy
は自動的にインジェクションされる設定になっているので、 Deployment
を個別に設定する事が出来ません。しかし、以下の方法を用いる事で外部(?)から調整が出来るようです。
今回はインジェクト対象の Deployment
にアノテーションを追加する形で調整しました。
apiVersion: apps/v1
kind: Deployment
metadata:
name: <アプリ名称>
spec:
template:
metadata:
annotations:
sidecar.istio.io/proxyCPU: 125m
sidecar.istio.io/proxyMemory: 128Mi
spec:
containers:
- name: <アプリ名称>
image: <アプリのイメージ>
resources:
requests:
cpu: 500m
ephemeral-storage: 100Mi
memory: 512Mi
Cloud SQL
に関しても同様に調整しています。
apiVersion: apps/v1
kind: Deployment
metadata:
name: <Cloud SQLプロキシ サイドカーの名前>
namespace: <名前空間>
spec:
template:
spec:
serviceAccountName: cloud-sql-proxy
containers:
- name: cloud-sql-proxy
image: gcr.io/cloudsql-docker/gce-proxy
command:
- "/cloud_sql_proxy"
- "-log_debug_stdout"
- "-instances=<接続名>"
securityContext:
runAsNonRoot: true
resources:
requests:
cpu: 125m
ephemeral-storage: 200Mi
memory: 128Mi
Spot Pod が使えないか検討する
GKE
には Spot Pod
というオプションがあり、これを使うと最大9割ほど安くなるようです。
今回移行したのは社内システムですが、一瞬でも止まると会社が立ち行かなくなるようなものではないので、導入してみる事にしました。
おわりに
今回 GKE
への移行時に調べた事、引っかかった事をまとめて記事にしたのですが、記事にしていない部分も多く、全体として分かりにくくなっているかもしれません…
部分的にでも、同じような所で引っかかった人の助けになれば幸いです。
Discussion