KubernetesでResource requestsとlimitsを最適な値にする方法
Kubernetesでは、ContainerのCPUやMemoryの使用量を設定するためのResource requestsとlimitsの機能があります。
これらの値をどのように決めるかはなかなか難しいのではないかと思います。
ただKubernetesの運用において、Resource requestsとlimitsの設定は重要です。
設定値が低すぎると実際のリソース使用量は予定以上になってしまい、リソースが不足することでPodが正常に起動しなくなる可能性があります。
設定値が高すぎると実際のリソース使用量は余裕がある状態になり、リソースの無駄が発生します。
Resource requestsとlimitsを最適な値にする必要があります。
要約
- Vertical Pod Autoscaler(VPA)が提案する推奨値をPodのResource requestsとlimitsに設定する
概要
VPAはPodのリソース使用量を自動でスケールアップするための機能です。
ただ現時点では自動でPodのスケールアウトを行うHorizontal Pod Autoscaler(HPA)と併用ができないです。
Podのスケールアウトを利用しないのは可用性の観点からして現実的ではないので、HPAを利用しつつ、最適なリソース使用量を手動で設定するのが望ましいのではないかと思います。
VPAを実運用で利用するのではなく、最適なPodのリソース使用量を確認するためのものとして利用します。
方法
前提条件
- Kubernetesクラスタを操作するためのkubectlの設定が完了していること
- metrics-serverがインストールされていること
Vertical Pod Autoscalerをインストールする
VPAのリポジトリを取得する。
$ git clone https://github.com/kubernetes/autoscaler.git
VPAをインストールする。
$ ./hack/vpa-up.sh
Goldilocksをインストールする
GoldilocksはVPAによる推奨値の確認を支援するためのツールです。
Goldilocksのリポジトリを取得する。
$ git clone https://github.com/FairwindsOps/goldilocks.git
Goldilocksをインストールする。
$ kubectl create namespace goldilocks $ kubectl -n goldilocks apply -f hack/manifests/controller $ kubectl -n goldilocks apply -f hack/manifests/dashboard
Goldilocksを適用するnamespaceを設定します。
ここでは、defaultのnamespaceを指定しています。
$ kubectl label ns default goldilocks.fairwinds.com/enabled=true
Resourceの推奨値を確認する
ここでは例として、Deploymentを以下のように用意します。
hamster.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: hamster spec: selector: matchLabels: app: hamster replicas: 2 template: metadata: labels: app: hamster spec: containers: - name: hamster image: k8s.gcr.io/ubuntu-slim:0.1 resources: requests: cpu: 100m memory: 50Mi command: ["/bin/sh"] args: - "-c" - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"
applyします
$ kubectl apply -f hamster.yaml
Goldilocksのダッシュボードを表示して、推奨値を確認します。
$ kubectl -n goldilocks port-forward svc/goldilocks-dashboard 8080:80 $ open http://localhost:8080/
Suggested Changesの欄で推奨値をコピーすることができます。
※ここでは、単にPodが動いている状態での値を推奨値としていますが、WebサービスのPod等は想定される負荷をかけて推奨値を確認する必要があると思われます
Resourceの推奨値を設定する
推奨値をdeploymentのyamlにコピペします。
hamster.yaml
apiVersion: apps/v1 kind: Deployment metadata: name: hamster spec: selector: matchLabels: app: hamster replicas: 2 template: metadata: labels: app: hamster spec: containers: - name: hamster image: k8s.gcr.io/ubuntu-slim:0.1 resources: limits: cpu: 587m memory: 262144k requests: cpu: 587m memory: 262144k command: ["/bin/sh"] args: - "-c" - "while true; do timeout 0.5s yes >/dev/null; sleep 0.5s; done"
applyします
$ kubectl apply -f hamster.yaml
ダッシュボードを確認すると値が同一になっていることが確認できます。
ここまでの手順でResourceの推奨値に設定することができました。
まとめ
今回の方法は実はGoldilocksというツールの利用方法をなぞっただけです。
GoldilocksというツールはResource requestsとlimitsをどのような値に設定すれば良いかという課題から生まれたツールのようです。
現状はVPAとHPAが併用できないため、Resource requestsとlimitsを手動で設定しなければならず、今回のような方法になっています。
近い将来、Kubernetesの仕組みとして自動でResourceの値が決まるようになるかもしれません。
参考
autoscaler/vertical-pod-autoscaler at master · kubernetes/autoscaler · GitHub
GitHub - FairwindsOps/goldilocks: Get your resource requests "Just Right"
Introducing Goldilocks : A Tool for Recommending Resource Requests
2019年で学んだ技術
2019年を振り返る内容です。
半年過ぎたころからAWSメインのインフラ業務が多くなってきたので、そのあたりで学ぶことが変わったりしました。
AWS
- 基本的な理解
- Udemyのコース「手を動かしながら2週間で学ぶ AWS 基本から応用まで」 ※こちらは現在新規での受講は不可になっています
- マルチアカウント環境でのAWS利用方法
- ロールを用いたクロスアカウントアクセスを多用した
- Aurora MySQLの運用
- Auroraにおけるバックアップと復元方法
- レプリカでのオートスケーリング方法
- DBインスタンスクラスのスケールアップ方法
- メンテナンスウィンドウを使用してスケジュールで変更を適用するか、フェイルオーバーを使用して即時に変更を適用するか場合によって使い分ける
- DynamoDBの運用・スキーマ設計
- Cloud Watchによる監視環境の構築
- ダッシュボードの作成
- アラームの作成
- https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html
- AWS Chatbotを使ってSlack通知
- どういうアラームを作るべきかは入門 監視という書籍が参考になった
- EKSでのWebアプリケーション環境の構築
- EKS環境の作り方
- eksctlを使うと簡単に環境を作れた
- spot instance、cluster autoscalerを設定してある程度コストと可用性を考慮した環境にした
- ネットワークの設定
- EKS環境の作り方
Webバックエンド
- GraphQLについての知識
- パフォーマンスの良いSQLの書き方
- ISUCONに向けての学習を通して学んだ
Webフロントエンド
インフラ
- Terraformの基本的な使い方
- 実践Terraformという書籍でTerraformからAWSを利用するための方法が理解できた
コンテナ
- Kubernetes周りの知識
- Kubernetesでカナリアリリースを試す - なになれ
- Kubernetesでエラーを返さずにPodを更新するにはwaitするのが解決策だった - なになれ
- Custom Resource、Operatorについて知識レベルで理解した
チーム開発
まとめ
来年も引き続きAWS、Kubernetesあたりを中心に学習していく所存です
Kubernetesでエラーを返さずにPodを更新するにはwaitするのが解決策だった
Kubernetesでエラーを返さずにPodを更新するにはどうすれば良いか色々検証しました。
結論はPodが終了する直前にwaitするです。
以下、試したことを書きます。
結論だけ知りたい方はこちらからどうぞ。
今回はNode.jsで検証しましたが 、そのほか全般的に当てはまる内容も含まれると思います。
DockerでNode.jsを動かすルールを理解する
Node.jsのDocker imageのドキュメントにベストプラクティスが紹介されています。
これによると、 DockerでNode.jsを起動するとPID1で起動することになり、SIGTERMなどのシグナルを受け取れないとあります。
下記のようにnodeを起動して停止させてみると数秒経過しないと停止しません。
$ docker run --rm -it --name node-example node:latest $ docker stop node-example
これはシグナルを受け取れずに強制終了させているからです。
Dockerの場合、--init
オプションをつけることでこの問題を回避できます。
$ docker run --rm -it --name node-example --init node:latest $ docker stop node-example
こうするとすぐにnodeが停止します。
また、npm start
で起動した場合でもシグナルを受け取れないとあります。
KubernetesのPodが終了するときにはSIGTERMシグナルがコンテナに送られます。
Kubernetesで動かす場合でもシグナルを受け取れるようにする必要があります。
Kubernetesには dockerの --init
オプションのようなものはないのですが、PID1として起動しない方法はあります。
それが shareProcessNamespace
です。
これらを踏まえて、DockerとKubernetesをセットアップします。
Dockerfile例
FROM node:12-alpine WORKDIR /usr/src/app COPY package*.json ./ RUN npm install COPY . . EXPOSE 3000 CMD [ "node", "./bin/www" ]
nodeコマンドでDockerイメージを作ります。
apiVersion: apps/v1 kind: Deployment metadata: name: myapp spec: selector: matchLabels: ingress-app: myapp template: metadata: labels: ingress-app: myapp spec: shareProcessNamespace: true containers: - name: myapp image: hi1280/myapp:0.1 imagePullPolicy: IfNotPresent ports: - containerPort: 3000
shareProcessNamespaceをtrueにします。
SIGTERMシグナルを受け取り終了を試みる
今回はNode.jsでExpressを使用してアプリケーションを作成しています。
単純にSIGTERMシグナルを受け取る(失敗パターンその1)
ソース github.com
単純にshareProcessNamespaceをtrueにしただけです。
これでNode.jsがSIGTERMシグナルを受け取って終了するはずです。
fortioという負荷テストツールを使って、複数回アクセスしながらDeploymentリソースでローリングアップデートをしてみます。
下記がfortioの実行コマンドです。
$ fortio load -t 0 -qps 32 -c 8 http://localhost:31213
しばらく経った後でコマンド実行を止めると結果が表示されます。
ローリングアップデート後にいくつかのアクセスが失敗していることが分かります。
Code -1 : 10 (0.9 %) Code 200 : 1125 (99.1 %)
これはSIGTERMシグナルに反応してすぐにPodが終了してしまうからだと思われます。
アプリ内でSIGTERMシグナルを制御する(失敗パターンその2)
Node.jsでSIGTERMを受け取り、正常に終了するためのライブラリがあったので使用しました。
github.com
ソース github.com
fortioの結果は下記のようになり、見た感じ良さそうです。
Code 200 : 768 (100.0 %)
ただPodのログをみてみると、responseのstatusが不明なログがいくつかあります。
これも失敗していそうです。
myapp-657c67f595-sgj87 myapp GET / 200 503.329 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / 200 501.409 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - - myapp-657c67f595-sgj87 myapp GET / 200 500.461 ms - 23 myapp-657c67f595-sgj87 myapp GET / - - ms - -
Podが終了する直前にこのようなログが発生していることが分かりました。
ちなみにPodのログを見るのに stern
というツールが重宝しました。
Podの終了直前でwaitする
最終的にうまく行ったのがwaitするという方法です。
ソース github.com
preStopというPodが終了する直前に呼ばれる処理でsleepコマンドを実行しています。
これを実行したところ、statusが不明なログがなくなり、全てのアクセスが200のstatus codeを返す結果になりました。
なぜwaitが必要なのか、下記に詳しい解説があります。
Podが終了する処理とServiceがPodの宛先を切り替える処理は並列に実行されるようです。
このような挙動によって、終了しようとしているPodに対して終了直前までアクセスされる可能性があります。
Podの終了する処理において、wait時間を確保して終了直前のアクセスに対しても処理が完了するように制御する必要があります。
まとめ
色々試しつつ、waitするという方法に行き着きました。
SIGTERMプロセスを受け取るようにして正常終了しようと試みましたが見当違いでした。
Kubernetesの仕組み上こうするしかないのかと思いつつ、辛いやり方だなと思います。
参考資料
Docker で node.js を動かすときは PID 1 にしてはいけない - ngzmのブログ
Kubernetes: 詳解 Pods の終了 - Qiita
docker-node/BestPractices.md at master · nodejs/docker-node · GitHub