Istio入門:既存のGKE上アプリケーションにIstioを導入するまでの流れ


社内でIstioを導入した際に、チュートリアルなどではわかりにくいハマりポイントや、既存GKEクラスタの導入にあたって留意すべき点がけっこうあるなと思ったのでまとめます。

対象読者は

  • GKEですでにサービスなどを運用している
  • Istioの概要は知っている、またはチュートリアルは終わった

くらいの方を想定しています。

なお、Istioとはなにかを解説する記事はすでにたくさんあるので、できるだけ作業レベルで導入の仕方を解説していきます。
Istioを知るハンズオンとしては、Googleの中の方のIstio 1.0 を試してみた!などが良いです。

移行したいアプリの構成

もともとの構成はこんな感じでした。

  • GCP上でglobal IPを取得し、FQDNと紐づけて外部流入をさせる
  • トラフィックはIngressで受けており、SSL terminationを行う
  • Ingressの次にNginxがあり、バックエンドのpodへkube-dnsを用いてリバースプロキシさせる
  • バックエンドのpodでは、GCSにアクセスしてリソースを取得するなどの処理をしつつレスポンスを返すサーバーが動作

なお、下記の手順は、クラスタを別で新しく作成して、そこにIstioを導入する想定で進めます。

既存クラスタ上で作業するとダウンタイムが出る危険性が高い気がします。
(namespaceを既存と別に切ればいいような気もしますが、既存namespaceに影響が出ないかを自分は確認していません)

やることの流れ

1. クラスタ作成
2. クラスタへのIstio導入と、namespaceへの有効化
3. namespace内へのリソースのデプロイ

準備

本家のチュートリアルに沿って、Istioを導入します。
利用するのは

$ kubectl apply -f install/kubernetes/istio-demo.yaml

です。
istio-demo-auth.yamlでは、mutualTLSといってpod間の通信が全てTLSになって最高!に見えるのですが、readiness probeとliveness probeが使えなくなります
サービスが動いているportとは別にprobe専用のportを空けるという話もありますが、それってReadinessの意味がないような?

無事にIstioが導入できたら(サンプルアプリのデプロイまで行ってしっかり確認しましょう)、既存アプリをデプロイするためのnamespaceを作成します。

それが終わったら、下記コマンドでnamespace全体に対してIstioを有効化します。

$ kubectl label namespace my-app istio-injection=enabled

この操作によって、このnamespace内で以後作成されるpodは全て、
Istioで利用されているEnvoyをsidecarとして自動的に注入された状態で立ち上がることになります。
この機能はkubernetes1.9以上じゃないと使えません(kubernetesのpod initializerを利用しているため)

知るべきこと1. IstioではIngressは使わない

最初にして最大の難関。
IstioではIngressは使わず、Istio独自のGatewayとVirtualServiceというリソースを用います。(後述)

(0.8からこうなったらしく、2017年の記事を読むと混乱します。後方互換性のためか公式にもまだIngress関係のリソースが残っていますが、使わないべきです。参考)

その理由としては、IngressではIstioのもつ機能が全部活用できない、とのこと。

In a Kubernetes environment, the Kubernetes Ingress Resource is used to specify services that should be exposed outside the cluster. In an Istio service mesh, a better approach (which also works in both Kubernetes and other environments) is to use a different configuration model, namely Istio Gateway. A Gateway allows Istio features such as monitoring and route rules to be applied to traffic entering the cluster.

本家より

で、そのアオリというわけではないんですが、GCPのglobal IPがIstioでは使えません。しばらくはサポートする予定もないみたいです。

暫定では、ServiceのLoadbalancerIPを用いてくれ、という回答。

Per our chat, it is possible to set the load balancer IP in a LoadBalancer service (search for loadBalancerIP on https://kubernetes.io/docs/concepts/services-networking/service/).
This can be set to a regional static IP but not global static IP – this is limited in Arcus and there’s no plan to support global.
The missing part is that you have to use the actual IP address instead of a nice label like you can with an Ingress (kubernetes.io/ingress.global-static-ip-name: my-static-ip).

Issue

なので、Ingressで行っていた下記のような書き方はできません。SSL terminationも別のところでやる必要があります。

ということで、Ingressで構成を作ってしまっていた場合、IPアドレスの受け先を変更する必要があるため、blue-greenデプロイで既存GKEアプリを移行することになるかと思います。

以下、これを実行していきます。

Regional IPを使って、IstioのServiceのLoadBalancerIPにpatchを当てる

k8sのServiceでは、LoadBalancerIPを既存IPに張り替えることで、そのServiceでそのIPアドレスのトラフィックを受けることができます。

Istioをクラスタに導入すると、istio-systemというnamespaceにistio-ingressgatewayというk8sのServiceリソースが作成されます。

Istioを導入したクラスタ1つにつき静的IPアドレスを一つ用意して、そのIPをistio-ingressgatewayに割り当てます。
具体的には

$ kubectl patch svc istio-ingressgateway –namespace istio-system \
–patch ‘{“spec”: { “loadBalancerIP”: “” }’

です。KnativeのReadmeを参考にしました。

これで、IPアドレス宛に来たトラフィックは全てIstioのLBを通って来ます。

SSL termination周りのリソースをsecretとしてデプロイ

Ingressで実行していたterminationは、前述のGatewayで行うことになります。
流れとしては、まずsecretをデプロイします。

これで証明書などをIstioのnamespaceにデプロイして、Gatewayから呼び出します。
Gatewayは下記のように記述して、 $ kubectl apply -f gateway.yaml でデプロイします。

このあたりは本家を参照すると出てきます。

Gatewayはあくまでもトラフィックを受けるためだけのリソースで、受けたリソースをどこに流すかはVirtualServiceが担当します。

知るべきこと2. Istio内部ではHTTP/1.0は利用できない

「まだ1.0とかありえないしw」と思われる方もいるかもしれませんが、Nginxのproxy_passのデフォルト設定では1.0になっています。
1.0のままだと、Nginxからの疎通自体はできるものの、Statusコードが426になって返ってきます。
該当Issue

Istio公式のQuickstartより。

Note: The application must use HTTP/1.1 or HTTP/2.0 protocol for all its HTTP traffic because HTTP/1.0 is not supported.

なので、Nginxの場合は

proxy_http_version 1.1;

などを定義してやる必要があります。

なお、kube-dnsが適切に動作していれば、

proxy_pass http://<service-name>.<cluster-namespace>.svc.cluster.local

などは引き続きそのまま利用できます。
本家チュートリアル内でデプロイされるサンプルアプリも参考になります。

知るべきこと3. APIなど外部通信は全てHostnameなどで穴あけが必要

個人的に一番詰まったのはここでした。
Istioでは、通信する外部ホストの全てをリストアップして記述してやる必要があります。
例えば、pip installをする場合はpypi.org、google apiを利用する場合はwww.googleapi.comなど。
詳細はこちらの本家記事を読まれることをおすすめします。

今回想定しているアプリでは、Google Cloud Storageと通信するので、
利用するAPIのホストとプロトコルは下記の2つ。

しかしながら、metadata.google.internalには罠があり、生IPを記述しないとcloud storage apiが使えないという問題が。
該当Issue

一方で、生IPではなくA recordを要求する場合もあるようです。
自分は GKEのVMのdefault credentialsを取得する際に、そこで

503 Failed to retrieve
http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/?recursive=true

のようなエラーが表示されてしまったのですが、これはFQDNを併記することで解決しました。
これらを用いて、下記のように記述してkubectl apply -f external.yaml などでデプロイします。

これでようやく、最初の構成のアプリケーションがIstioを導入した状態で動作するようになりました。
まだまだ初心者ですが、これから知見を貯めていきます。

シェアする

  • このエントリーをはてなブックマークに追加

フォローする