Google Kubernetes Engine (GKE)のProduction環境でIstioを動かす
Kubernetesで動いていた既存のサービスを、GKE上のIstioで動くようにしました。プロダクション環境のためHTTPSや監視もいれてあります。
Istioはインストール時のオプションで、PrometheusとかGrafanaも一緒にインストールされて動きます。構築が楽になりそうなので気になっていました。今回新しいGKE環境を作る機会があったので、監視もととのっているIstioを導入しました。ほとんどの部分はIstioの公式ドキュメントを見れば構築できます。ただ各種クラウドプラットフォームに対応するためにドキュメントが散らばっていたり、どの手順を適用したらいいのかわかりずらかったりするので、自分はこのパターンでやりましたというのを残しておきます。HTTPSを使うためLet's Encryptで証明書を取得していたのですが、dns-01 challengeが使えずにhttp-01 challengeを使って、certbotで証明書を更新する形にしたのでそこはドキュメントにないのでそのような状況にある場合は参考になるかもしれません。
要件
- IstioがGKE上で動作
- サーバ監視(今回はPrometheusとGrafanaでやります)
- 既存WebサービスをGKE上のIstioで動作
- HTTPSで通信
各種作業はmacOS上からやっています。
IstioをGKE上で動かす
まずはIstioがGKE上で動作するようにします。手順は各種飛ばしているところもあるので、以下で補いながら実施するといいと思います。
- https://cloud.google.com/kubernetes-engine/docs/tutorials/istio-on-gke
- https://istio.io/docs/setup/kubernetes/
クラスタの準備
GCPのWebコンソール上のKubernetes Engineからクラスタを作ります。
以下以外はデフォルトで作成します。ノードは3台だとリソースがなくてpodが作れなくなりがちなので4としておきます。
- Number of nodes: 4
- Machien type: n1-standard-1
なお、開発環境などで費用を節約したい場合は、Enable preemptible nodes
を選択するといいと思います。これは最大24時間稼働し続けることができるノードがわりあてられるもので、いつノードが削除されて再生成されるかわからないかわりに1/3とか安い費用で利用できます。
Kubernetesはポッドが削除・再生される前提なので、そのテストの意味でも強制的に削除されるノードがあるのはいいかもしれません。
ローカル環境でkubectlが使えるようにする
gcloud auth login
などでログインしておきます。
以下を実行し、kubectlが使えるように設定します。プロジェクト名やリージョンは各自かえてください。
gcloud config set project wapa5pow
gcloud config set container/cluster wapa5pow
gcloud config set compute/zone asia-northeast1-a
gcloud container clusters get-credentials wapa5pow
gcloud auth configure-docker --quiet
kubectl cluster-info
がエラーなく実行できたら大丈夫です。
認証系のエラーがでたら以下が参考になりそうです。
- http://doi-t.hatenablog.com/entry/2018/03/21/213715
- https://stackoverflow.com/questions/49075723/what-does-unknown-user-client-mean?rq=1
Helmのインストール
今回、Istioインストール時にHelmを使うのでここを参考にインストールしてください。
helm version
が正しく実行できれば大丈夫です。
Istioのインストール
ここを参考にして、リリース版のistioを取得します。
記事を書いている時点では、1.0.1でした。パスを通すと、istioctl
が使えるようになります。
IstioをHelm経由でインストールする際、Helmのインストール時に入れたTillerのポッド経由でIstioを入れるか、HelmのコマンドラインでKubernetesのマニフェストを出力し、それを使うかの2種類あります。今回は、マニフェストをgitで管理したかったので、マニフェストに書き出す形にします。
リリース版のistio-1.0.1のフォルダ直下に行き、以下のコマンドを打ちます。
helm template install/kubernetes/helm/istio --name istio --namespace istio-system
--set grafana.enabled=true \
> /var/tmp/istio.yaml
/var/tmp/istio.yaml
にKubernetesのマニフェストできるので確認します。
なお、インストールオプションで--set <key>=<value>
で設定できるオプションが確認できます。
若干ここに書いていないもの(cert-managerのemailなど)もあるので、HelmのChartを見ながら確認するといいです。
istio.yaml
をKubernetesに適用する前に、現在使用しているアカウントにRBACベースのアクセス権をあたえておきます。(参考)
kubectl create clusterrolebinding cluster-admin-binding \
--clusterrole=cluster-admin \
--user="$(gcloud config get-value core/account)"
次に、Istioの管理で使われるネームスペースを作成します。
kubectl create namespace istio-system
次に、最初に作成したistio.yaml
を適用します。
kubectl apply -f /var/tmp/istio.yaml
しばらくしたら、Istioが正しく起動できているか確認します。ここにあるように、istio-systemのネームスペースを指定して確認してください。ドキュメントの結果はやや古いので結果がやや違うこともありますが、ポッドがエラーなく起動していれば大丈夫です。
kubectl get svc -n istio-system
kubectl get pods -n istio-system
これでIstioがインストールできました。Istioはサービスのistio-ingressgatewayがLoad Balancerとなってインターネットからアクセスを受け付けており、kubectl get svc -n istio-system
で見てみたときに、EXTERNAL-IPがあります。ドメインを設定する場合は、ここであらかじめDNSに設定しておいてください。また、IPがかわらないように、GCPのコンソール上でStatic IPにしておくといいかなと思います。
サーバ監視
せっかくGrafanaを入れたので、こちらの手順でGrafanaにアクセスできるかどうか確認してみてください。
Prometheusもこちらの手順で確認できます。
Istioを使っていない時は、Helmも使わないでPrometheusとGrafanaを入れて設定が非常に大変でしたが、Istioだとオプションを設定するだけでするっとはいってしまいます。その時は、ノードプールをわけたりストレージ分けたりしていましたが、今回のは、ひとまずデフォルトとしておきます。
注意としてPrometheusは、デフォルトの設定では6時間しかデータを保存しないのと、保存先もとくに分けらていません。
長期的なデータもみたい場合は、Prometheus用にpoolをわけるとか個別に設定したほうがいいと思います。
既存WebサービスをGKE上のIstioで動かす
Istio上で、既存のサービスを動かせるようにします。既存のサービスがKubernetesですでに動いている前提で書きます。まだKubernetesで動いていない場合は、docker-composeを使って、ローカルで動かせれるところまでいってからKubernetesで動かすと楽です。
IstioのEnvoy sidecarのInject
KubernetesのマニフェストをIstio上で動作させるには、既存のマニフェストにEnvoyを通して通信するように変更する必要があります。その変更方法には、以下の2通りのやり方があります。
- istioctl kube-inject: このコマンドを使うと、既存のマニフェストに対してEnvoyが適用されたマニフェストが出力されるのでそれを
kubectl apply -f ...
で適用させます。 - Istio-sidecar-injector: Istioを使っていない時と同じマニフェストを
kubectl apply -f ...
で適用すると、hookがかかり、自動でEnvoyの設定を適用してくれます。ネームスペース単位で制御でき、istio-injection=enabled
を適用したネームスペースだけEnvoyが適用されます。
前者のはいちいちマニフェストファイルを変換しなければならず面倒なのでIstio-sidecar-injectorを使ったやりかたで設定します。
defaultのネームスペースにIstio-sidecar-injectorを適用しないで新しいネームスペースを作ってからそちらに既存Webサービスの設定を適用します。
以下のファイルを作成し、kubectl apply -f namespace.yaml
で適用します。
apiVersion: v1
kind: Namespace
metadata:
name: wapa5pow-namespace
labels:
istio-injection: enabled
metadataに先程作成したネームスペースを記載してから、既存Webサービスのマニフェストをいままでと同じようにkubectl apply -f ...
で適用させます。
IngressとEgressの設定
外部からIstio内部への既存ebサービスへのアクセスできるようにするための設定をします。Istioでは、GatewayとVirtualServiceでIstioのIngress設定をします。参考
注意として、既存のWebサービスでKubernetesのIngressを使用していたとしてもIstioでは使わず、すべてingressgateway経由でアクセスされます。もし既存のIngressがあった場合は、IstioのIngressを設定し終わったあとに削除しても大丈夫です。
今回、Gatewayは以下のように設定しました。後ほど、HTTPSの設定もしますがその設定も、あらかじめ入れておきます。
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: wapa5pow-gateway
namespace: wapa5pow
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "www.wapa5pow.com"
# - port:
# number: 443
# protocol: HTTPS
# name: https-default
# tls:
# mode: SIMPLE
# serverCertificate: "/etc/istio/ingressgateway-certs/tls.crt"
# privateKey: "/etc/istio/ingressgateway-certs/tls.key"
# hosts:
# - "www.wapa5pow.com"
VirtualServiceは以下のように設定しました。webというサービスのポートらへんは各自自分のアプリに読み替えて設定してください。
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: wapa5pow-service
namespace: wapa5pow
spec:
hosts:
- "www.wapa5pow.com"
gateways:
- wapa5pow-gateway
http:
- match:
- uri:
prefix: /.well-known/acme-challenge/
route:
- destination:
port:
number: 8089
host: certbot
- match:
- uri:
prefix: /
route:
- destination:
port:
number: 3000
host: web
上記のマニフェストをkubectl apply -f マニフェスト名
で適用します。
指定したドメインでアクセスできるか確認してみます。うまくできない場合は、kubectl get pod --all-namespaces
でistio-ingressgatewayのポッドの名前を確認して、以下のような形でログを確認すつつアクセスしてみます。
kubectl logs istio-ingressgateway-6fd6575b8b-wpkzp -n istio-system -f
ほとんどのサービスがそうだと思いますが、データベースなどのクラスタ外部のサービスにアクセスする必要がある場合は、Egressの設定であるServiceEntryを設定する必要があります。Istio内部から外部にアクセスするサービスを定義します。今回は簡易的に、HTTP/HTTPSとPostgreSQLのポートをすべて許可するようにしておきます。
apiVersion: networking.istio.io/v1alpha3
kind: ServiceEntry
metadata:
name: wapa5pow
namespace: wapa5pow
spec:
hosts:
- "*"
ports:
- number: 80
name: http
protocol: HTTP
- number: 443
name: https
protocol: HTTPS
- number: 5432
name: postgres
protocol: TCP
ポッドの中から外部ドメイン経由でアクセスできれば正しく設定されています。
HTTPSで通信する
今回は、Let's EncryptでHTTPS通信を実現します。
Istioの設定で一番つらかったのはこれです。HTTPSの設定は、はまると時間かかって大変です。
通常、Kubernetesだと、Ingressのアノテーションに、cert-manager用の設定をすればさくっと、いけます。しかしIstioは、Ingressではなく、istio-ingressgatewayを通して外部からの通信を行うので、Ingress経由では証明書を取得できません。
辛いといってもdns-01 challengeでリンクにあげられているDNSプロバイダを使っていた場合簡単にできます。Istioにはオプションを設定するとcert-managerを入れてくれて、dns-01の設定ができるのでその場合はすぐできます。
使っているDNSがDNSプロバイダにのっていない場合は、http-01 challengeで行います。cert-managerだとできなかったので、certbotを使って設定していきます。
以下のような形でhttp-01 challengeに成功するようにします。2の部分は/.well-known/acme-challenge/
だけcertbotにリクエストがいくようにします。VirtualServiceですでに設定済みです。
http-01 challenge == 1 ==> istio-ingressgateway == 2 ==> certbot
certbotを適当に設定し、ポッドとしてcertbotを動かして証明書を取得します。以下みたいなシェルスクリプトを走らせるDockerfileを作ってビルドすればいいかなと思います。
#!/usr/bin/env bash
set -e
echo START: `date "+%Y-%m-%d %H:%M:%S"`
certbot certonly --standalone --preferred-challenges http -d ${CERT_DOMAIN} -n --config-dir /var/tmp --agree-tos --email=${CERT_EMAIL}
cp /var/tmp/live/${CERT_DOMAIN}/fullchain.pem tls.crt
cp /var/tmp/live/${CERT_DOMAIN}/privkey.pem tls.key
kubectl delete secret istio-ingressgateway-certs --ignore-not-found -n istio-system
kubectl create -n istio-system secret tls istio-ingressgateway-certs --key ./tls.key --cert ./tls.crt
Securing Gateways with HTTPSに書いてあるのですが、注意点として証明書と秘密鍵は、istio-systemネームスペースのistio-ingressgateway-certsのシークレットに保存する必要があります。
そうするとistio-ingressgatewayが自動的に証明書と秘密鍵を読んでくれて、/etc/istio/ingressgateway-certs/tls.crt
と/etc/istio/ingressgateway-certs/tls.key
に展開してくれます。
RBACが有効になっているクラスタの場合、シークレットを作成するのに権限がいるので、ServiceAccount/ClusterRole/ClusterRoleBindingを設定してcertbotが書き込みできるようにしてあげてください。
証明書が正しくとれたら、以前設定したGatewayのコメント部分をはずし、kubectl apply -f ...
で適用してください。
これでHTTPSで通信できるはずです。