Red HatのOpenJDKのサポート
更新: Red Hat Enterprise Linux 7.6でOpenJDK 11が追加されました。現在のサポート期間は2024/10までです。
【JDK 11 リリース直前】各ベンダーのJDKリリースモデル特集!というイベントがあってRed Hatさんもどうですか、ってお誘いが来たんですが予定が入っており参加できなかったのでエントリを書いておきます。
Red Hat Enterprise Linux (RHEL)には商用サポートされるOpenJDKパッケージが含まれています。RHELのサブスクリプション費用に含まれており、OpenJDKのサポートには追加の費用はかかりません。yum install java-1.8.0-openjdk
のようにパッケージをインストールすれば使えます。
OpenJDKのサポート期間についてはOracle社の影響が大きいという側面があり、RHELのサポート期間とは異なるサポート期間が定められています。
サポート期間についてOpenJDK 6は開始時点が遅かったので短くなっていますが、7も8も実績として8年はサポートしています。6と7についてはOracle社による無償アップデート終了後、UpstreamをRed Hatがリードしています。8もそうなるかな?
- OpenJDK 6 (1.6)
- 2009/01 - 2016/12 (約7年)
- 2013年Oracleによる無償Java 6アップデートの終了時にRed HatがUpstream OpenJDK 6のmaintenance leadに https://jaxenter.com/rip-se-6-a-tribute-to-javas-longest-serving-edition-105714.html
- OpenJDK 7 (1.7)
- 2012/07 - 2020/06 (約8年)
- 2015年Red HatがUpstream OpenJDK 7のmaintenance leadに https://jaxenter.com/red-hat-takes-the-reigns-of-openjdk-7-118261.html
- OpenJDK 8 (1.8)
- 2014/10 - 2023/06 (約9年)
- OpenJDK 11
- Red Hatの商用サポートについてはサポート期間未定だが長期サポート予定、UpstreamのOpenJDK 11リリース(2018年9月予定)後に発表予定
新しいOpenJDKリリースモデルで非LTSリリースとなったOpenJDK 9, 10などはRed Hatでは商用サポート予定はありません。LTSリリースのみ商用サポート予定となっていくと思います。
サポート内容はプロダクションサポートと呼ばれるもので、利用中の障害、バグの調査、バグ修正やセキュリティ修正の提供などが含まれます。開発サポートではないので、APIの使い方がわからない、といったような質問はサポート範囲外となります。
RHEL 7.5以降のOpenJDK 8ではウルトラローポーズGCであるShenandoah GCがサポートされています。-XX:+UseShenandoahGC
で有効化できます。Shenandoah GCについてはサイボウズさんのざっくりわかった気になるモダンGC入門を見ると良いと思います。OpenJDK 11にExperimentalとして含まれるZGCについても書かれています。
あとはWindowsで開発して本番はRHEL、という一般的なユーザ向けにWindows版開発用OpenJDKというのも配布しています。開発用途専用です。Mac版は現時点で残念ながらありません。
OpenJDKへのコントリビュートはOracle社が圧倒的ですが、Oracle社以外ではRed Hatが一位となっています。
Last year, 14% of all commits to #Java mainline came from external contributors. Which companies and institutions help Oracle shape the future of @OpenJDK #JDK10 #JDK11 and push to jdk/jdk?
— BellSoft (@bellsoftware) 2018年9月12日
Kudos to all the individual contributors as well! pic.twitter.com/yfXSNw88Q1
まとめ
- Red Hat Enterprise Linux (RHEL)には長期商用サポートされるOpenJDKが含まれているよ
- OpenJDK 11もOpenJDK 7, 8と同様にサポート予定だよ
- RHEL 7.5以降のOpenJDK 8でウルトラローポーズGCのShenandoah GC使えるよ
Red Hat OpenShift Service Meshをインストールしてみる
Red Hat OpenShift Service Meshがプレビューリリースしたのでインストールしてみます。OpenShift向け製品版のIstioです。
セットアップはこんな感じ。
# Enable admission webhooks in master MASTER_CONFIG_PATCH="admissionConfig: pluginConfig: MutatingAdmissionWebhook: configuration: apiVersion: v1 disable: false kind: DefaultAdmissionConfig ValidatingAdmissionWebhook: configuration: apiVersion: v1 disable: false kind: DefaultAdmissionConfig" sudo cp -a /etc/origin/master/master-config.yaml{,.prepatch} sudo oc ex config patch /etc/origin/master/master-config.yaml.prepatch -p "$MASTER_CONFIG_PATCH" | sudo tee /etc/origin/master/master-config.yaml sudo /usr/local/bin/master-restart api sudo /usr/local/bin/master-restart controllers # sysctl vm.max_map_count=262144 on each node echo "vm.max_map_count = 262144" | sudo tee -a /etc/sysctl.d/99-elasticsearch.conf sudo sysctl vm.max_map_count=262144 # Install istio curl -LO https://raw.githubusercontent.com/Maistra/openshift-ansible/maistra-0.1.0-ocp-3.1.0-istio-1.0.0/istio/istio_product_operator_template.yaml oc new-project istio-operator oc new-app -f istio_product_operator_template.yaml --param=OPENSHIFT_ISTIO_MASTER_PUBLIC_URL=https://s310.example.com:8443 cat <<EOF | oc create -n istio-operator -f - apiVersion: "istio.openshift.com/v1alpha1" kind: "Installation" metadata: name: "istio-installation" spec: jaeger: elasticsearch_memory: 1Gi EOF
セットアップ直後の状態はこうなります。
$ oc get all -n istio-operator NAME READY STATUS RESTARTS AGE pod/istio-operator-5df6cbf496-tlrfn 1/1 Running 0 18m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/istio-operator ClusterIP 172.30.186.9 <none> 60000/TCP 18m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/istio-operator 1 1 1 1 18m NAME DESIRED CURRENT READY AGE replicaset.apps/istio-operator-5df6cbf496 1 1 1 18m $ oc get all -n istio-system NAME READY STATUS RESTARTS AGE pod/openshift-ansible-istio-installer-job-j2c6m 0/1 ContainerCreating 0 58s NAME DESIRED SUCCESSFUL AGE job.batch/openshift-ansible-istio-installer-job 1 0 58s
openshift-ansible-istio-installer-job
podはAnsibleを実行しているようです。この実行が終わったらセットアップ完了で以下の状態になります。
$ oc get all -n istio-system NAME READY STATUS RESTARTS AGE pod/elasticsearch-0 1/1 Running 0 8m pod/grafana-6d5c5477-rbskl 1/1 Running 0 19m pod/istio-citadel-6f9c778bb6-trf6k 1/1 Running 0 21m pod/istio-egressgateway-957857444-dx26j 1/1 Running 0 21m pod/istio-galley-c47f5dffc-dn25p 1/1 Running 0 21m pod/istio-ingressgateway-7db86747b7-l86zp 1/1 Running 0 21m pod/istio-pilot-5646d7786b-s29kv 2/2 Running 0 21m pod/istio-policy-7d694596c6-698v5 2/2 Running 0 21m pod/istio-sidecar-injector-57466d9bb-z6vdv 1/1 Running 0 21m pod/istio-statsd-prom-bridge-7f44bb5ddb-2d75m 1/1 Running 0 21m pod/istio-telemetry-7cf7b4b77c-6vxn4 2/2 Running 0 21m pod/jaeger-agent-9f4xz 1/1 Running 0 18m pod/jaeger-collector-9c9f8bc66-7278h 1/1 Running 7 18m pod/jaeger-query-fdc6dcd74-v9t5c 1/1 Running 7 18m pod/openshift-ansible-istio-installer-job-j2c6m 0/1 Completed 0 25m pod/prometheus-84bd4b9796-wwfms 1/1 Running 0 21m NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/elasticsearch ClusterIP 172.30.38.243 <none> 9200/TCP 19m service/elasticsearch-cluster ClusterIP 172.30.117.160 <none> 9300/TCP 19m service/grafana ClusterIP 172.30.182.60 <none> 3000/TCP 19m service/istio-citadel ClusterIP 172.30.169.27 <none> 8060/TCP,9093/TCP 21m service/istio-egressgateway ClusterIP 172.30.177.77 <none> 80/TCP,443/TCP 21m service/istio-galley ClusterIP 172.30.22.227 <none> 443/TCP,9093/TCP 21m service/istio-ingressgateway LoadBalancer 172.30.253.232 172.29.93.241,172.29.93.241 80:31380/TCP,443:31390/TCP,31400:31400/TCP,15011:32718/TCP,8060:30594/TCP,15030:30606/TCP,15031:32105/TCP 21m service/istio-pilot ClusterIP 172.30.111.153 <none> 15010/TCP,15011/TCP,8080/TCP,9093/TCP 21m service/istio-policy ClusterIP 172.30.49.238 <none> 9091/TCP,15004/TCP,9093/TCP 21m service/istio-sidecar-injector ClusterIP 172.30.11.70 <none> 443/TCP 21m service/istio-statsd-prom-bridge ClusterIP 172.30.48.15 <none> 9102/TCP,9125/UDP 21m service/istio-telemetry ClusterIP 172.30.72.184 <none> 9091/TCP,15004/TCP,9093/TCP,42422/TCP 21m service/jaeger-collector ClusterIP 172.30.129.165 <none> 14267/TCP,14268/TCP,9411/TCP 18m service/jaeger-query LoadBalancer 172.30.244.29 172.29.154.76,172.29.154.76 80:32087/TCP 18m service/prometheus ClusterIP 172.30.49.188 <none> 9090/TCP 21m service/tracing LoadBalancer 172.30.115.191 172.29.75.1,172.29.75.1 80:30290/TCP 17m service/zipkin ClusterIP 172.30.202.148 <none> 9411/TCP 18m NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE daemonset.apps/jaeger-agent 1 1 1 1 1 <none> 18m NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE deployment.apps/grafana 1 1 1 1 19m deployment.apps/istio-citadel 1 1 1 1 21m deployment.apps/istio-egressgateway 1 1 1 1 21m deployment.apps/istio-galley 1 1 1 1 21m deployment.apps/istio-ingressgateway 1 1 1 1 21m deployment.apps/istio-pilot 1 1 1 1 21m deployment.apps/istio-policy 1 1 1 1 21m deployment.apps/istio-sidecar-injector 1 1 1 1 21m deployment.apps/istio-statsd-prom-bridge 1 1 1 1 21m deployment.apps/istio-telemetry 1 1 1 1 21m deployment.apps/jaeger-collector 1 1 1 1 18m deployment.apps/jaeger-query 1 1 1 1 18m deployment.apps/prometheus 1 1 1 1 21m NAME DESIRED CURRENT READY AGE replicaset.apps/grafana-6d5c5477 1 1 1 19m replicaset.apps/istio-citadel-6f9c778bb6 1 1 1 21m replicaset.apps/istio-egressgateway-957857444 1 1 1 21m replicaset.apps/istio-galley-c47f5dffc 1 1 1 21m replicaset.apps/istio-ingressgateway-7db86747b7 1 1 1 21m replicaset.apps/istio-pilot-5646d7786b 1 1 1 21m replicaset.apps/istio-policy-7d694596c6 1 1 1 21m replicaset.apps/istio-sidecar-injector-57466d9bb 1 1 1 21m replicaset.apps/istio-statsd-prom-bridge-7f44bb5ddb 1 1 1 21m replicaset.apps/istio-telemetry-7cf7b4b77c 1 1 1 21m replicaset.apps/jaeger-collector-9c9f8bc66 1 1 1 18m replicaset.apps/jaeger-query-fdc6dcd74 1 1 1 18m replicaset.apps/prometheus-84bd4b9796 1 1 1 21m NAME DESIRED CURRENT AGE statefulset.apps/elasticsearch 1 1 19m NAME DESIRED SUCCESSFUL AGE job.batch/openshift-ansible-istio-installer-job 1 1 25m NAME HOST/PORT PATH SERVICES PORT TERMINATION WILDCARD route.route.openshift.io/grafana grafana-istio-system.apps.s310.example.com grafana http None route.route.openshift.io/istio-ingressgateway istio-ingressgateway-istio-system.apps.s310.example.com istio-ingressgateway http2 None route.route.openshift.io/jaeger-query jaeger-query-istio-system.apps.s310.example.com jaeger-query jaeger-query edge None route.route.openshift.io/prometheus prometheus-istio-system.apps.s310.example.com prometheus http-prometheus None route.route.openshift.io/tracing tracing-istio-system.apps.s310.example.com tracing tracing edge None
アプリケーションを作ってみます。Istioではinit containerでiptablesを利用するのでprivilegedコンテナにする必要があるのですが、コミュニティ版IstioをOpenShiftで利用するときにinit containerをprivilegedとしてinjectionしてくれない、という問題がありました。OpenShift製品版のIstioはprivilegedとしてinjectionしてくれるようです。
oc new-project test-istio oc adm policy add-scc-to-user privileged -z default oc new-app https://github.com/nekop/hello-sinatra oc patch dc/hello-sinatra -p 'spec: template: metadata: annotations: sidecar.istio.io/inject: "true"'
namespaceのlabel istio-injection=enabled
でのinjectionも軽く試してみたのですが、そちらは動作していないように見えます。OpenShiftではbuild podにinjectionされると問題となる(ビルドに必要な通信がIstioで許可されていなくて失敗するなど)ので、その関係で無効化されていそうです。
再デプロイされたpodはinit containerとproxy sidecarがinjectionされています。
$ oc get pod NAME READY STATUS RESTARTS AGE hello-sinatra-1-build 0/1 Completed 0 9m hello-sinatra-2-9p6gx 2/2 Running 0 7m $ curl hello-sinatra.test-istio.svc:8080 hello
Prometheusを開いてhello_sinatra
と入力したときに各種メトリクスが見えるようになっていれば問題なく動作しています。
子供にBluetooth trackerのTile Mateを持たせてみた
いま子供が6才と4才と1才なんだけど、幼児はショッピングモールなどの人混みやちょっと大きい公園に行ったりすると、大人ができる限り気を付けていてもちょくちょく居なくなってしまう。最近近隣で知り合いの迷子事例もいくつかあり、これは気をつけるというレベルでどうにかできる問題ではなさそうなので、技術的なアプローチをとってみることにした。
キャリアが提供しているGPSとSIM付きのキッズケータイが恐らく一番良いのだろうけど、幼児にはオーバースペックだ。ということでとりあえず一番ポピュラーなBluetooth trackerのTile Mateを持たせてみた。いわゆる忘れ物防止タグ。
Tileを持たせてみる、とはいっても幼児は基本的にモノをすぐ失くしてしまうので、生で持たせるのは問題外だ。カバンやケータイは動くのに邪魔になる大きさなので遊ぶのに外してしまう可能性がある。もっとぴたっと身につけるもの、腕時計タイプのものがベストかなぁ、と思っていたところで二の足を踏んでいたんだけど上の子が年長クラスになって移動ポケットをつけるようになり、下の子も欲しがったのでこれにトラッカー仕込めばいいじゃん、ということになり早速Tile Mate 3個パックを買ったのが二ヶ月ほど前。Tileを移動ポケットに入れて、基本的な機能を説明して大事なものだからお出かけするときはおやつを一個入れて必ず移動ポケット付けてね、と教えたところきちんと実践するようになった。おやつが多分ポイント。移動ポケットにはついでに連絡先カードも入れておこう。
Tile以外にはmamorioやTrackRも良さそうだったんだけど、現時点ではTileユーザのほうが多くてカバー範囲が広そうであることと、ケータイからTileの音を鳴らす機能がある、という2点でTileを選択。
Tileは音を鳴らして子供に合図を送れる、子供がTileをダブルクリックして親のケータイを鳴らせる(4才ちゃんは鳴らして遊んでしまうことが簡単に予想されるのでこの機能は教えていない)、という2点は使い勝手が良い。Tileから鳴る音は比較的小さめなケータイの着信音程度なので、大きな音が鳴って迷惑になったりはしなさそうだけど場合によっては鳴っていることには気付かないかもしれない。移動ポケットごとタンスの中に入っていたときに、音を鳴らしてもタンスにほぼ接触するくらい近づかないと音が聞こえなかった。
一方、Tileのアプリで子供の位置はなんとなくの範囲しかわからないので、精度の高い探索はあまりできなさそうだ。Tileアプリを開けば子供が居る方向くらいわかるかなー、と思ったけど自分のケータイを中心とした円が表示されてこの範囲に居ます、みたいな表示がほとんどなので多くの場合方向もわからない。たぶんTileのBluetoothを拾った自分のケータイのGPSの位置が報告されるだけかな。Bluetoothの情報は位置補正まではしていない雰囲気がある。距離取って円の範囲は調整してるかな。Tileが自分のケータイのBluetooth範囲にあるかどうかの確認と、ちょっと遠くに行った場合や一定距離はぐれた場合などに、他のTileユーザ網に引っかかってくれればOK、程度に思っておいたほうがよさそうだ。それでも安心度は格段に違う。
Tile Mateは通信距離30mで、Tile Pro SportとTile Pro Styleは約4000円と値段が倍になるけど通信距離60m、防水、音が大きいといった利点も揃っているので子供の迷子防止ならProでもいいかもしれないなー、と思っているところ。
【Amazon.co.jp限定】Tile Mate 3個パック 落としモノ/失くしモノ防止トラッカー 携帯GPS[日本正規代理店品](1年保証付) EC-06001-JC-3P
- 出版社/メーカー: タイル
- 発売日: 2018/02/02
- メディア: エレクトロニクス
- この商品を含むブログを見る
OpenShiftでコンテナの詳細を調べる
OpenShift Origin(OKD)に、Node.jsアプリケーションをデプロイしていろいろ試すというエントリで「コンテナに引数を設定してみる」でargs
を利用しているところで「ですがまあ、これは不正解な気がします。」と書いていてソース方面を見にいくという方法を取っていますが、それをOpenShift上できちんと裏付け調査する方法について書いておきます。
アプリケーションは以下のサンプルを利用しています。手元の環境ではOCP 3.9を利用していますがokd 3.10でも一緒です。
oc new-app https://github.com/sclorg/nodejs-ex
現在のコンテナで何が実行されるかはコンテナイメージメタデータのEntrypointとCmdを見る必要があります。コンテナイメージメタデータはImageStreamに入っていてImageStreamTagというvirtual objectを通して参照できるようになっています。
$ oc get istag NAME DOCKER REF UPDATED IMAGENAME nodejs-ex:latest docker-registry.default.svc:5000/test-nodejs/nodejs-ex@sha256:7222ec78ef8e9f84a119fece9eeb49053718e4bd7fba6d203c23ea7fe30f99f5 36 minutes ago sha256:7222ec78ef8e9f84a119fece9eeb49053718e4bd7fba6d203c23ea7fe30f99f5 $ oc export istag nodejs-ex:latest apiVersion: v1 generation: 1 image: dockerImageLayers: - (省略) dockerImageMetadata: Config: Cmd: - /usr/libexec/s2i/run Entrypoint: - container-entrypoint Env: (以下省略)
ちなみにistagはvirtual objectであるせいか、oc export istag
やoc get istag -o yaml
というようにオブジェクト名の指定を省略した場合はコンテナメタデータなどの詳細を表示しないという挙動となるようです。上のようにoc export istag nodejs-ex:latest
と指定する必要があります。ちょっとはまりました。
OpenShiftではクライアントサイドにocコマンドさえあれば、dockerなどがインストールされていなくてもこういったコンテナの詳細が調査できるのが便利です。
次にcontainer-entrypoint
と/usr/libexec/s2i/run
は何か、という話になりますが、これはコンテナイメージ内を調査する必要があります。コンテナイメージ内の調査は場合によって以下の3種類のどれかを利用します。
- 既存のPodに
oc rsh
(Entrypint, Cmdが既に実行されている状態の調査)- 例:
oc rsh dc/node-ex
- 例:
- 既存のDeploymentConfigに
oc debug
(Entrypint, Cmd実行前の状態の調査)- 例:
oc debug dc/node-ex
- 例:
oc run
(s2iイメージなどDeploymentConfigなどが存在しないイメージの調査)- 例:
oc run foo --image=docker-registry.default.svc:5000/test-nodejs/nodejs-ex@sha256:7222ec78ef8e9f84a119fece9eeb49053718e4bd7fba6d203c23ea7fe30f99f5 --command -- tail -f /dev/null
- 例:
今回は直接rshで問題ありません。
oc rsh dc/nodejs-ex
コンテナに入ったら必要なファイルなどを調査します。
sh-4.2$ which container-entrypoint /usr/bin/container-entrypoint sh-4.2$ cat $(which container-entrypoint) #!/bin/bash exec "$@" $ cat /usr/libexec/s2i/run #!/bin/bash # S2I run script for the 'nodejs' image. # The run script executes the server that runs your application. # # For more information see the documentation: # https://github.com/openshift/source-to-image/blob/master/docs/builder_image.md # set -e if [ -e "/opt/app-root/etc/generate_container_user" ]; then source /opt/app-root/etc/generate_container_user fi # Runs the nodejs application server. If the container is run in development mode, # hot deploy and debugging are enabled. run_node() { echo -e "Environment: \n\tDEV_MODE=${DEV_MODE}\n\tNODE_ENV=${NODE_ENV}\n\tDEBUG_PORT=${DEBUG_PORT}" if [ "$DEV_MODE" == true ]; then echo "Launching via nodemon..." exec nodemon --inspect="$DEBUG_PORT" else echo "Launching via npm..." exec npm run -d $NPM_RUN fi } #Set the debug port to 5858 by default. if [ -z "$DEBUG_PORT" ]; then export DEBUG_PORT=5858 fi # Set the environment to development by default. if [ -z "$DEV_MODE" ]; then export DEV_MODE=false fi # If NODE_ENV is not set by the user, then NODE_ENV is determined by whether # the container is run in development mode. if [ -z "$NODE_ENV" ]; then if [ "$DEV_MODE" == true ]; then export NODE_ENV=development else export NODE_ENV=production fi fi # If the official dockerhub node image is used, skip the SCL setup below # and just run the nodejs server if [ -d "/usr/src/app" ]; then run_node fi # Allow users to inspect/debug the builder image itself, by using: # $ docker run -i -t openshift/centos-nodejs-builder --debug # [ "$1" == "--debug" ] && exec /bin/bash run_node
起動はNPM_RUN
環境変数を引数にしているようなので、これを調整するのが正解だとわかります。デフォルトの定義を調べます。
$ oc export istag nodejs-ex:latest | grep NPM_RUN - NPM_RUN=start
というわけで、 NPM_RUN='start arg1'
と設定するのがこのイメージのお作法である、で正解です。
s2iに限らず、どのようなイメージでもoc import-image
とoc run
してしまえばImageStreamとDeploymentConfigが出来るのでOpenShift上でイメージの調査ができるようになります。
Kubernetes/OpenShiftのバージョンアップとクラスタをどのように分けるか問題
Kubernetes/OpenShiftのバージョンアップをどのようにするか、およびクラスタをどのように分けるかという問題はk8s関連のmeetupでよく出る話題です。昨日のレッドハット on Cloud Dayでも出たので、現時点での自分が知っている情報や考えを書いておきます。
現時点で自分が一番しっくりくるそれなりな規模の構成はdev, testing, staging, prodの4種類を2クラスタずつ用意、8クラスタの構成です。規模によってはtesting, stagingは一つにしてしまってもいいと思います。
各ステージのクラスタ、たとえばprod1, prod2はBlue-green deploymentのような形で利用します。たとえばprod1はk8s 1.7, prod2は一つ新しいk8s 1.8となっていて、新規のデプロイは全てバージョンの新しいほうであるprod2に行います。各アプリケーションへのトラフィックはLBでprod1, prod2へ振り分けを切り換えられるように構成します。また、アプリケーションがクラスタを移動する際にストレージのPVのデータの移行がストレートにできないので、その部分は何らかの仕組みを用意する必要があります。
移行期間を置いてprod1のアプリが全部prod2へ移行されたあと、prod1のアップグレードを行ったり、もしくはクリーンに作り直して再度利用可能にし(もちろん事前にprod3を作っておいてprod1を破棄でも良い)、新規のデプロイメントはこちらにデプロイするようにします。このような2クラスタに分ける構成では、片方のクラスタで障害があっても、もう片方のクラスタにすぐデプロイできる体制ができているはずなので、バックアップとしても機能します。また、k8sのアップグレードがアプリケーションに影響しない、もしくは常にクリーンインストールを行うことでアップグレードという作業自体をなくすことができる構成になります。ワークロードはクラスタを分けても分けなくても変わらない、デプロイされているアプリの総数は変わらないはずなので、コンピューティングノードのノード数のオーバヘッドはほどんどありません。
逆にdev, test, prodのワークロードをnode selectorで分けて全部ひとつのクラスタにするという構成もあります。リソース共有の効率化、集約度向上、運用負荷が非常に軽くなるなどの利点も多くありますが、いくつか懸念があります。
- masterやetcdのバグで環境がダウンするリスク
- masterやetcdのワークロードはdev, test, prodで共有することになるので、たとえばdevでk8s APIやネットワーク帯域を利用するアプリの負荷テストなどで環境ダウンのリスク
- バージョンアップで動作しなくなる
k8sはソフトウェアなので当然バグはあります。最近のものを挙げるとmaster APIへのLBの数秒のメンテを行ったらノードが全てNodeNotReadyになってPodも全部NotReadyになるというようなクラスタごと使えなくなるようなバグもやはりあります。そのような状況を許容できる、泣かないのであればクラスタを分けないという選択肢もアリだとは思います。
もちろんクラスタを分けて複数クラスタを運用するコストも小さくはないので、どの程度のAvailabilityやResiliencyが必要かによって構成を選択しましょう。
バージョンアップの問題はさまざまです。master/etcdが起動しなくなるものや、Podのvalidationのルールが変わって以前のバージョンでは動いてたものが動かなくなったり、それらを消そうとしても新しいバージョンのvalidationに引っかかって消えないPodやnamespaceができたり、それら消えない中途半端なオブジェクトが残っているとバージョンアップのプロセスがエラーになって進まなかったり、というような感じです。根本的な解決にはetcdのデータを直接修正したり消したりというような、それなりの知識が必要かつ危険度の高いアクションが必要になることがあります。アップグレードの前に少なくともスナップショット取得、バックアップリストアは必ずテストしましょう。
あとはどのk8sを選択すればいいか、どのような運用体制が必要かという話があったのでついでに。
- Non-managed k8s
- OpenShift
- Managed k8s (GKE, AKS, EKS, etc)
- インフラ専任いらない
なぜか小規模なのにオンプレNon-managed k8sを選択してしまって大変、という話をたまに聞きますが、Managed使った方がいいと思います。オンプレk8sは立ち上げのコストが高いので、安易に手を出すと時間が溶けやすいので注意しましょう。
また、Managedを使うにしてもGKE, AKS, EKS, etc.どれを使えばいいのですか、という話もありましたが、既にどれかクラウド環境を使っているような方はManagedなデータベースなど他の機能への連携もありますしとりあえずそのままそちらのサービスを使う、ということが多いのではないかと思います。そのような前提が無いのであれば、どれを利用しても良いんじゃないかと思います。
Container SIG Meet-up 2018 SummerでKubernetesのServiceとかIngressの話をしました
Container SIG Meet-up 2018 SummerでKubernetes Service, Ingress and OpenShift Routerというお題でお話しました。
資料は以下に置いています。
- slide.com: https://redhat.slides.com/tkimura/k8s-service-ingress/?token=cenh3zy-
- PDF: https://drive.google.com/file/d/1ZuJiUY2MppzzTLjHRMF3L93-Emd5Yh5V/view
slide.comはRed Hatのコーポレートアカウントを使っているのですが、設定が変わったようでpublicの設定ができないようになっていて、token付きのexternalのURL共有でなんか微妙な感じに。slide.comはプレゼンテーション用に画像でインポートしたものなので、文字やリンクをクリックしたい場合はPDFを利用してください。
さて、最後にService IPでアクセスしてるとコネクションベースのヘルスチェックがないのでノードダウン時に40秒間くらいアクセスできたりできなかったりするので注意してね、という話をしたのですが、その生ログを一応載せておきます。
以下2 replicaのpodのClusterIPへのアクセスです。末尾kwgjf
とbkz58
の2つのPodが確認できます。
[cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:51 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:51 EDT 2018 hello-sinatra-3-bkz58 [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:53 EDT 2018 hello-sinatra-3-bkz58 [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:54 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:55 EDT 2018 hello-sinatra-3-bkz58 [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:56 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:22:57 EDT 2018 hello-sinatra-3-kwgjf
片方のPodが載っているノードをpoweroff -f
してダウンの状況にします。curlを再開するとハングしたりするのでCtrl-cしたりしないといけなくなったり、curl: (7) Failed connect to 172.30.81.27:8080; No route to host
が返却されたりするのが確認できます。生きているPodへ振り分けられた場合は問題なくレスポンスが返却されています。
$ date; ssh stack86-33 sudo poweroff -f Wed May 23 03:24:06 EDT 2018 Powering off. ^CKilled by signal 2. [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:26 EDT 2018 ^C [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:29 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:30 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:31 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:32 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:33 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:34 EDT 2018 ^C [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:38 EDT 2018 hello-sinatra-3-kwgjf [cloud-user@stack86-32 ~]$ date; curl 172.30.81.27:8080/hostname; echo Wed May 23 03:24:38 EDT 2018 curl: (7) Failed connect to 172.30.81.27:8080; No route to host
クライアント側で地道にタイムアウトとリトライができる場合はそれでいいのですが、既存のソフトウェアを動かしたいようなユースケースでそのあたりの設定ができない場合とか困ります。今のところこれをうまくハンドリングする定石みたいなものはないと思っているのですが、何か良い案があれば教えてください。OpenShiftのhttp/TLSトラフィックでの限定的なワークアラウンドとしては、Route作ってアクセスするとOpenShift RouterのHAProxyを通るのでHAProxyがヘルスチェックつきで接続管理とロードバランスしてくれます。将来的にはIstioとかがデフォルトでいい感じにハンドリングしてくれることを期待しています。
KubernetesのReadWriteOnceなvolume
よく聞かれるトピックでブログに書いてたと思ってたんだけど実は書いてなかったようだ。Amazon EBSとかGCEPersistentDiskとかAzureDiskとかGluster Block Storageとかを利用するReadWriteOnceのvolumeのお話。
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
access modeがReadWriteOnceというvolumeはいわゆるブロックデバイスタイプのvolumeであり、複数のホストから同時に同一のvolumeを利用することはできない(もし実際に複数ホストから同時に書き込まれるとデータが壊れる)。複数のホストから同時に利用できないので、ReplicaSetでスケールさせた場合にpodを複数ホストに分散させることはできない。このためReplicaSetで利用する場合はホスト障害やホストのメンテナンス停止への耐性はなく、ReadWriteOnceを利用するpodは必ず一旦停止してから別ノード上にRecreateされる必要があるためゼロダウンタイムでのpod再デプロイは不可能となる。後述するクラスタ対応ソフトウェアでStatefulSetであればReadWriteOnceを利用しても問題ない。
このようなReadWriteOnceのハンドリングは実際にはvolume pluginで実装されている。podを作成する時の前処理として、Amazon EBSなどのvolume pluginは、volumeをホストにattachする。逆に、podが削除されるときには、volumeをdetachするようになっている。正常な場合の再デプロイでは、コンテナ停止に伴いvolumeをコンテナが動作していたホストからdetach、コンテナ再スケジュール、作成に伴いvolumeをコンテナが動作するホストへattach、という流れで利用される。
https://github.com/kubernetes/kubernetes/tree/master/pkg/volume
volumeがattachされているホストが障害となったときは、volumeがdetach状態にならないまま利用中のステータスで残ることがある。ホスト障害とは言っても、単にネットワークが不通である可能性があるのでホストが実際にダウンしているかどうか判断することはできず、ホストの状態は「Unknown=不定」と考えなければならない。ホスト自体は実際には健康に動作していてvolumeにデータ書き込んでいるかもしれない。このような場合、volumeがdetachされずにpodが別ホストで再スケジュールされるような状況が発生するが、volumeが他ホストにattachされたままになっているのでpod作成時の前処理のvolumeのattachは失敗する。volumeを利用中のホストが本当にダウンしているかどうかもわからないため、強制的にdetachしてattachというわけにもいかない。このようなケースでは大抵マニュアルでの対処が必要となり、現在attachされているホストが停止していることを確認したのち、ブロックストレージの管理インタフェースなどからマニュアルでvolumeをdetachすることで再度そのvolumeが利用可能な状態になり、podの作成処理が進むようになる。volumeがattachされているホストが障害になったときに、自動でホストのダウンを確認してdetachしてくれるようなブロックストレージもあるかもしれないが、基本的にはそのような期待はしないほうがいい。
このように一見ReadWriteOnceはReadWriteManyより制約が多く劣っているように見えるが、劣っているのではなくストレージの性格が異なることを理解する必要がある。ReadWriteOnceのブロックデバイスはfsyncなどを利用するシビアなディスク書き込みを行うようなソフトウェア、いわゆるデータベースやデータストア系のソフトウェアのストレージとして適している。ReadWriteManyをサポートするNFSやGlusterFSのようなネットワークファイルシステムは、そのようなソフトウェアのストレージとして利用すると、一般的に不整合やデータ破損が発生しやすい。また、大抵このようなデータベース系ソフトウェアはReplicaSetでのpodのスケーリング、つまり同一のデータ領域を参照する複数インスタンスの同時起動をサポートしていないため、基本的にReadWriteManyにするユースケースがない。例えばOpenShiftのログ基盤で利用されているElasticSearchは、NFSやGlusterFSだとデータ破損が発生するので、NFSなどのネットワークストレージの利用をサポートしておらず、ReadWriteOnceのブロックデバイスを利用する必要がある。
分散を前提としていてクラスタリングをサポートしているetcd、ElasticSearch、Cassandraなどのソフトウェアでは、ネットワーク経由でのデータ複製や同期機能が備わっているので、PodごとにPVを割り当てることのできるStatefulSetを利用することにより、ReadWriteOnceのストレージを利用していてもスケール可能であり、適切に設定されている限りホスト障害などのときも必要な復旧作業と並行してリクエストに応答できるようになっているはずなのでダウンタイムなしで運用することができる。