OpenShift:アプリケーションの構成と運用
今回は、デプロイしたアプリケーションの運用に関わる設定を中心に紹介していきたいと思います。デプロイメントのヘルスチェックの設定や環境依存情報の取り扱い、永続ストレージの設定など、デプロイメントの「チューニングポイント」といった内容です。今回紹介する機能のほとんどが、Kubernetesで実装されているものなので、すでにご存知の方もいるかもしれませんが、OpenShiftのコマンドを使った設定方法と一緒に確認していきましょう。
アプリケーションのヘルスチェック
Readiness Probe と Liveness Probe
Readiness ProbeとLiveness Probeは、OpenShift(Kubernetes)が提供しているアプリケーションへのヘルスチェックの機能です。2つのヘルスチェックの違いですが、Readinessには「準備」、Livenessには「生存」という意味があり、ヘルスチェックが失敗した際の挙動が異なります。Readiness Probeが失敗した場合には、そのPodへのトラフィックを流さなくなります。一方Liveness Probeが失敗した場合には、そのPodの再起動を行います。
またReadiness ProbeとLiveness Probeのいずれも、以下に示す3種類のヘルスチェックの方法を選択できます。
種類 | 意味 |
---|---|
exec | コマンドの実行結果によるヘルスチェック |
tcpSocket | TCPソケットによるヘルスチェック |
httpGet | HTTP Getリクエストによるヘルスチェック |
これらの設定の詳細なオプションは、explainコマンドで確認が可能です。
# oc explain pod.spec.containers.livenessProbe
Readiness ProbeとLiveness Probeのどちらにも、インターバルの値やタイムアウト値、失敗とみなす条件など、多くの設定項目がありますが、設定値に関する詳細な解説は省略します。まずは、実際の動きを確認してみましょう。
Liveness Probeの検証準備
最初は、前回の記事で紹介したビルドの方法のおさらいです。Dockerビルドストラテジーを使って、検証用アプリケーションのビルドとデプロイをしてみましょう。
# ls Dockerfile main.go
FROM golang:alpine WORKDIR /app COPY main.go . CMD go run main.go
package main import ( "fmt" "net/http" "time" ) func rootHandler(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello, World") } func healthHandler(w http.ResponseWriter, r *http.Request) { fmt.Printf("%v: request from: %v\n", time.Now(), r.RemoteAddr) } func main() { http.HandleFunc("/", rootHandler) http.HandleFunc("/health", healthHandler) http.ListenAndServe(":8080", nil) }
アプリケーションと呼べるほどのものではありませんが、/healthにヘルスチェックを受け付けるエンドポイントを実装しています。今回は、検証目的のため、リクエストが発生した時刻を表示し、Liveness/Readiness Probeが定期的にヘルスチェックを実行していることを確認します。コード(main.go)とDockerfileの準備が整ったら、ビルドとデプロイを実行します。
$ oc new-build --name=go-httpd --strategy=docker --binary $ oc start-build go-httpd --from-dir=. --follow $ oc new-app go-httpd
oc get podでPodのRunning状態が確認できれば、次からLiveness Probeの動きを確認していきます。
Liveness Probeの確認
Liveness Probeのテストを実施してみましょう。DeploymentConfigに書き込むこともできますが、oc set probeコマンドが用意されているので、ここではそれを使って設定してみます。
$ oc set probe dc/go-httpd --liveness --get-url=http://:8080/health
--livenessでLiveness Probeを指定し、--get-urlでヘルスチェックのリクエストを受け付けるアクセスポイントを指定しています。設定後、再デプロイされたPodのログを見てみましょう。デフォルトのヘルスチェック間隔である10秒ごとにリクエストが届いていれば正常です。
# oc logs go-httpd-4-vxwjp 2019-01-06 07:47:45.798994545 +0000 UTC m=+4.165327161: request from: 10.128.0.1:54808 2019-01-06 07:47:55.897736149 +0000 UTC m=+14.264069102: request from: 10.128.0.1:54878 2019-01-06 07:48:05.799532904 +0000 UTC m=+24.165865675: request from: 10.128.0.1:54988 2019-01-06 07:48:15.7983669 +0000 UTC m=+34.164699506: request from: 10.128.0.1:55078 2019-01-06 07:48:25.798541175 +0000 UTC m=+44.164873661: request from: 10.128.0.1:55142 2019-01-06 07:48:35.798272671 +0000 UTC m=+54.164605630: request from: 10.128.0.1:55244
正常時のヘルスチェックは問題がなさそうです。Liveness Probeはヘルスチェックが失敗すると、Podの再起動が実施されると前述しました。そこで、プロセスにSTOPシグナルを送って確認してみましょう。
コンテナ内のgoプロセスを停止させるコマンド:
$ oc exec go-httpd-4-vxwjp -- pkill -STOP go
Liveness Probeのログを確認する場合には、eventログを参照します。`oc get event`でイベントログを確認してみましょう。
$ oc get event -w 0s 0s 1 go-httpd-4-k7kc7.157c67383af6b0b8 Pod spec.containers{go-httpd} Warning Unhealthy kubelet, knakayam-ose311-all Liveness probe failed: Get http://10.128.1.138:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 0s 10s 2 go-httpd-4-k7kc7.157c67383af6b0b8 Pod spec.containers{go-httpd} Warning Unhealthy kubelet, knakayam-ose311-all Liveness probe failed: Get http://10.128.1.138:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 0s 20s 3 go-httpd-4-k7kc7.157c67383af6b0b8 Pod spec.containers{go-httpd} Warning Unhealthy kubelet, knakayam-ose311-all Liveness probe failed: Get http://10.128.1.138:8080/health: net/http: request canceled (Client.Timeout exceeded while awaiting headers) 0s 0s 1 go-httpd-4-k7kc7.157c673d04c21ac6 Pod spec.containers{go-httpd} Normal Killing kubelet, knakayam-ose311-all Killing container with id docker://go-httpd:Container failed liveness probe.. Container will be killed and recreated. 0s 58s 2 go-httpd-4-k7kc7.157c672f9672fdb6 Pod spec.containers{go-httpd} Normal Pulling kubelet, knakayam-ose311-all pulling image "docker-registry.default.svc:5000/ch05/go-httpd@sha256:2643d3821c64bfd0a51d5f7753e4fa3f03cbe703e33058f354a3b7210da1a3a8" 0s 58s 2 go-httpd-4-k7kc7.157c672fb212efda Pod spec.containers{go-httpd} Normal Pulled kubelet, knakayam-ose311-all Successfully pulled image "docker-registry.default.svc:5000/ch05/go-httpd@sha256:2643d3821c64bfd0a51d5f7753e4fa3f03cbe703e33058f354a3b7210da1a3a8" 0s 58s 2 go-httpd-4-k7kc7.157c672fb75df174 Pod spec.containers{go-httpd} Normal Created kubelet, knakayam-ose311-all Created container 0s 58s 2 go-httpd-4-k7kc7.157c672fccab6180 Pod spec.containers{go-httpd} Normal Started kubelet, knakayam-ose311-all Started container
Liveness Probeが失敗して、期待通りPodが再起動されていることを確認できました。
init Containerを使った起動時の外部サービスチェック
さて、少し寄り道になりますが、Liveness Probe/Readiness Probeの紹介をしたついでに、init Containerの機能についても簡単に触れておきます。init Containerを設定した場合、アプリケーションコンテナは、init Containerの正常終了後に起動することが保証されます。この機能を利用することで、「別のサービスが起動していることを確認してから、アプリケーションを立ち上げる」といったことが可能になります。
例えば、myserviceというサービスへの名前解決ができるようになってから、アプリケーションPodを立ち上げる場合には、次のようにinit Containerを設定します。
initContainers: - name: init-myservice image: busybox command: ['sh', '-c', 'until nslookup myservice; do echo waiting for myservice; sleep 2; done;'] containers: ...略 ...
上の例では、busyboxコンテナを立ち上げ、nslookupコマンドでmyserviceの名前解決を確認しています。Readiness Probeとは異なり、init Containerは起動時のみという制約もありますが、これでPodの別サービスへのアクセス確認などが可能です。
またinit Containerには、この他にもデータの初期化、ファイルや静的コンテンツのダウンロードといった使い道もあります。
永続ストレージの設定
さて、次の設定ポイントは永続ストレージです。まずは各ストレージの性質を理解し、ストレージの選定方法について話しをしてから、後半で設定方法を見ていきます。
永続ストレージの全体像
まず、OpenShiftで永続ストレージをする際の全体像を理解します。
永続ストレージを利用する際には、PVC(PersistentVolumeClaim)、PV(PersistentVolume)といったオブジェクトを作成します。PVCは、ストレージを要求するために利用するオブジェクトで、アプリケーション開発者が作成しPodと紐づけます。一方PVは、バックエンドのストレージを抽象化したオブジェクトで、クラスタ管理者側で作成します。アプリケーション開発者がPVCを作成することで、ストレージの要求が発生します。ストレージの要求が発生すると、OpenShiftがバインド可能なPVを見つけてバインドするという仕組みです。
ストレージの種類と選定
OpenShiftのPVで指定可能なストレージには、ブロックデバイスタイプのストレージとファイル共有タイプのストレージ(以下、簡単に「ブロックストレージ」「ファイル共有ストレージ」と表記)があります。ブロックストレージとは、例えばAmazon EBS、Gluster Block、iSCSIといったストレージです。一方ファイル共有ストレージは、NFSやGlusterFSといったストレージです。基本的にブロックストレージを利用した場合には、複数のコンテナからの同時書き込みができませんが、ファイル共有ストレージでは、それが可能です。OpenShiftやKubernetesでは、前者のことを「ReadWriteOnce(複数クライアントからの書き込みは不可で読み込みは可能な場合は、ReadOnlyMany)」、後者を「ReadWriteMany」と表記しています。ストレージを選択する際には、アプリケーションのストレージアクセスの性質やスケールアウトを想定しているかなど、性質を見極めることが必要です。
またストレージの選定には、ReadWrieManyやReadWriteOnceといった性質の他にも、デフォルトでStorageClassをサポートしているか否かも検討材料の一つになるかもしれません。StorageClassとは、PersistentVolumeで利用するボリュームをオンデマンドに切り出す仕組みです。StorageClassを利用すれば、クラスタの管理者がPersistentVolume用に予め準備しておく必要がなくなり、PVCが作成されたタイミングでPVがStorageClassによって作成されることになります。
永続ストレージの設定方法
それでは、実際にNFSをバックエンドにしたPV/PVCを利用してみます。今回はStorageClassを利用せず、PersistentVolumeを直接使います。上で述べた通り、PVの作成はクラスタ管理者の作業になりますので、PVがcreateできる権限、例えばcluster-admin権限が必要になります。
cat <<EOF > ./pv1.yaml apiVersion: "v1" kind: "PersistentVolume" metadata: name: "pv0001" spec: capacity: storage: "1Gi" accessModes: - "ReadWriteMany" nfs: path: "/nfs" server: "192.168.133.4" # ホスト名/IPはご利用の環境に合わせて変更する必要があります。 persistentVolumeReclaimPolicy: "Recycle" EOF
oc create -f pv1.yaml
今回はNFSを利用し複数クライアントからの書き込みができるので、上で触れたReadWriteManyをPVに設定しています。クラスタ管理者側でのPV準備作業が済んだら、次はアプリケーション開発者側の作業であるPVCを作成します。
cat <<EOF > ./pvc.yaml apiVersion: "v1" kind: "PersistentVolumeClaim" metadata: name: "claim1" spec: accessModes: - "ReadWriteMany" resources: requests: storage: "1Gi" EOF
oc create -f pvc.yaml
PVCがPVにバインドされたかどうかは、oc get pvcで確認ができます。STATUSがBoundとなっていることを確認してみましょう。
$ oc get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE claim1 Bound pv0001 1Gi RWX 2s
これで、PVC(claim1)が利用できる状態となりました。あとは、アプリケーションからこのclaim1を利用するだけです。oc set volumeコマンドを使って、DeploymentConfigを更新しましょう。
$ oc set volume dc/go-httpd --add -t pvc --name=test-vol --claim-name=claim1 --mount-path=/mnt
また、pod/<POD名>を引数にしてoc set volumeコマンドを実行してみましょう。mountされているvolumeとマウントポイントが確認できます。
$ oc set volume pod/go-httpd-5-5892n deploymentconfigs/go-httpd pvc/claim1 (allocated 1GiB) as test-vol mounted at /mnt
さて、oc get podコマンドでPodがRunning状態になっていれば良いのですが、もし、Podがpending状態になってしまった場合には、oc get eventでイベントログを確認してみてください。
$ oc get event
「マウントの実行に失敗している」というログがある場合には、OpenShiftのNodeにsshでログインして、ローカルでNFSがマウントできるかを確認してください。OpenShift Nodeは、内部でmountコマンドを発行してマウントを実行しています。まずは、ホスト上のマウントコマンドで同様のエラーが発生しないことを確認してみてください。
最後に、Podを削除しても永続ストレージ上のファイルが残っていることを確認しておきましょう。
# oc rsh go-httpd-5-mnkzk touch /mnt/test-file # oc delete pod go-httpd-5-mnkzk pod "go-httpd-5-mnkzk" deleted # oc rsh go-httpd-5-z62nj ls /mnt test-file
Podが再作成されても、/mnt以下にストレージがマウントされ、ファイルもそのまま残っているようです。以上が、永続ストレージの簡単な使い方でした。
連載バックナンバー
Think ITメルマガ会員登録受付中
全文検索エンジンによるおすすめ記事
- Oracle Cloud Hangout Cafe Season 4 #5「Kubernetesのオートスケーリング」(2021年8月4日開催)
- Kubernetesの基礎
- StatefulSetとPersistentVolumeを使ってステートフルアプリケーションを動かす
- KubernetesのConfig&Storageリソース(その1)
- Oracle Cloud Hangout Cafe Season7 #1「Kubnernetes 超入門」(2023年6月7日開催)
- CSIによるKubernetesのストレージ機能
- 認定Kubernetesアプリケーション開発者を目指そう!
- kustomizeやSecretを利用してJavaアプリケーションをデプロイする
- Podのリソース割り当ての推奨値を提案するKRR(Kubernetes Resource Recommender)
- kustomizeで復数環境のマニフェストファイルを簡単整理