ノードに割り当てられたCPUが1つで、2つのPodがCPUのresource limitをそれぞれ1000m指定してオーバーコミット状態の時、一方でniceを掛けたかったらそのPodマニフェストからlimit指定を外さないとniceが効かない。という話とその検証メモ。
シチュエーションとしてあり得るのは、1つのクラスターでオンラインAPI処理とバッチ処理を2つデプロイして、処理の優先度としてはオンラインAPIを高くして、それ以外の空いたCPU時間をバッチ処理に回したいという場合。クラスタ分けろよ、というのはクラウドなら成立しやすいのだが、まあ、オンプレで資源の有効利用は重要課題である。空いたサーバーにビットコインを発掘させるだけでも経営の一助となり得るかもしれないですし?
バッチ処理側でresource limitを外さないと、なのは分かる様な気がするが、意味が分からない挙動としては、優先度を持たせたいオンラインAPI処理にはきちんとresource指定しないと、バッチ側のniceが有効に働かないというところ。このあたりの挙動はわかりやすようにその内修正されるのかもしれないが。
検証
オンラインAPI風のPodと、バッチ処理風のPodを以下のように作成する。
オンラインAPI風機能
Python/FastAPIで、GETリクエストが来るたびにsecrets.token_urlsafe()を10万回呼び出した後応答するモックプログラム。ほかの負荷がない場合、リクエストの応答には大体0.3秒掛かる。
import secrets
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
async def root():
for i in range(100000):
token = secrets.token_urlsafe()
return {"token": token}
Dockerfileは以下の通り。
# syntax=docker/dockerfile:1
FROM python:3.10
WORKDIR /work
RUN pip install fastapi[all]
COPY main.py .
CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0"]
EXPOSE 8000
一応、モノはDocker Hubの以下。
https://hub.docker.com/repository/docker/rk05231977/api
バッチ風機能
Pythonで乱数を延々計算し続けるプログラム。secrets.token_urlsafe()の呼び出し100万回ごとに1行、現在時刻をprintする。ほかの負荷がない場合、大体3秒に1行メッセージが出力される。
一応SIGETRMを受け取ったらメッセージ出力のタイミングで終了するようにしてある。
import secrets
import datetime
import signal
flag = False
def handler(signum, frame):
global flag
flag = True
signal.signal(signal.SIGTERM, handler)
while True:
for i in range(1000000):
s = secrets.token_urlsafe(16)
print(datetime.datetime.now())
if flag:
print("SIGTERM")
break
Dockerfileは以下。
# syntax=docker/dockerfile:1
FROM python:3.10
WORKDIR /work
COPY load.py .
CMD ["python3", "load.py"]
出来たDockerイメージはDocker Hubの以下。
https://hub.docker.com/repository/docker/rk05231977/load
環境
クラウドでやると大変迷惑なのでローカルのVMware WorkstationにVMとしてCPU2個、メモリ8GBのシングルノードクラスタを構築する。
OSはUbuntu 20.04をインストール、Kubernetesはv1.24、ランタイムはcontainerd。
Kubernetesクラスタの構築手順はおおむね先日Azureに作ったセルフインストールのものと同じ。
https://qiita.com/rk05231977/items/ae0a3382aa45e8dea02f
なお、CPシングルノードなのでtaintを外す必要がある。
# kubectl taint nodes --all node-role.kubernetes.io/control-plane- node-role.kubernetes.io/master-
Kubernetesマニフェスト
ノードのCPUが2つなので、オンラインAPI風機能2 Pod、バッチ風機能が2 Podで検証する。
オンラインAPI風機能のマニフェストが以下。
resource.requests.cpuの値指定は、指定しないとlimitsの1000mが反映されて、2つPodが起動しなくなるので仕方なく。
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
labels:
app: api
spec:
replicas: 2
selector:
matchLabels:
app: api
template:
metadata:
labels:
app: api
spec:
containers:
- name: api
image: docker.io/rk05231977/api
resources:
requests:
cpu: "0m"
limits:
cpu: "1000m"
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
type: NodePort
selector:
app: api
ports:
- protocol: TCP
port: 8000
nodePort: 30000
バッチ風機能のマニフェストが以下。
コマンドでniceを利かせている。優先度を上げるときにはSYS_NICEのPrivilegeが必要になるが、下げる分には不要な様だ。
ファイルは、resource指定を外した場合の例。
apiVersion: apps/v1
kind: Deployment
metadata:
name: load
labels:
app: load
spec:
replicas: 2
selector:
matchLabels:
app: load
template:
metadata:
labels:
app: load
spec:
containers:
- name: load
image: docker.io/rk05231977/load
command: ["nice"]
args: ["python3", "load.py"]
# resources:
# requests:
# cpu: "0m"
# limits:
# cpu: "1000m"
検証パターン
計測するのは、KubernetesのVMの外からabコマンドでAPIに50回/10並列でリクエストを投げた完了時間。
以下の4パターンで検証する。
- APIのみ実行、バッチの負荷は掛けない
- APIでLimit指定、バッチでもLimit指定
- APIでLimit指定、バッチのLimitは指定しない
- APIでLimit指定しない、バッチでもLimitは指定しない
検証結果(パターン1)
APIのみ実行、バッチの負荷は掛けないパターン。
オンラインAPI風機能の本来のパフォーマンスである。
abコマンドの総実行時間は大体8.2秒。
# ab -n 50 -c 10 192.168.0.102:30000/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.102 (be patient).....done
Server Software: uvicorn
Server Hostname: 192.168.0.102
Server Port: 30000
Document Path: /
Document Length: 55 bytes
Concurrency Level: 10
Time taken for tests: 8.204 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 9000 bytes
HTML transferred: 2750 bytes
Requests per second: 6.09 [#/sec] (mean)
Time per request: 1640.822 [ms] (mean)
Time per request: 164.082 [ms] (mean, across all concurrent requests)
Transfer rate: 1.07 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 1.9 1 14
Processing: 230 1490 671.6 1406 3029
Waiting: 230 1490 671.4 1405 3029
Total: 231 1491 671.8 1406 3030
Percentage of the requests served within a certain time (ms)
50% 1406
66% 1904
75% 1920
80% 1921
90% 2496
95% 2497
98% 3030
99% 3030
100% 3030 (longest request)
root@gehenna:~# ab -n 50 -c 10 192.168.0.102:30000/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.102 (be patient).....done
Server Software: uvicorn
Server Hostname: 192.168.0.102
Server Port: 30000
Document Path: /
Document Length: 55 bytes
Concurrency Level: 10
Time taken for tests: 7.869 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 9000 bytes
HTML transferred: 2750 bytes
Requests per second: 6.35 [#/sec] (mean)
Time per request: 1573.719 [ms] (mean)
Time per request: 157.372 [ms] (mean, across all concurrent requests)
Transfer rate: 1.12 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.4 1 2
Processing: 236 1434 696.2 1510 2948
Waiting: 235 1433 696.1 1510 2947
Total: 238 1435 696.2 1511 2949
Percentage of the requests served within a certain time (ms)
50% 1511
66% 1715
75% 1851
80% 2171
90% 2336
95% 2786
98% 2949
99% 2949
100% 2949 (longest request)
実行中、Kubernetesマシンのtopコマンド出力画面は以下。python(APIのプロセス)が大部分を占めていて、まあ問題ない。
検証結果(パターン2)
APIでLimit指定、バッチでもLimit指定するパターン。
どちらも、なるべくCPU使ってねとCPU limitに1000mを指定しているケース。バッチのプロセスにniceは掛かっているが。
abコマンドの実行時間は大体16秒。バッチの負荷が無い場合に比べて約2倍。
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.102 (be patient).....done
Server Software: uvicorn
Server Hostname: 192.168.0.102
Server Port: 30000
Document Path: /
Document Length: 55 bytes
Concurrency Level: 10
Time taken for tests: 16.208 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 9000 bytes
HTML transferred: 2750 bytes
Requests per second: 3.08 [#/sec] (mean)
Time per request: 3241.571 [ms] (mean)
Time per request: 324.157 [ms] (mean, across all concurrent requests)
Transfer rate: 0.54 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.4 1 2
Processing: 353 2830 1268.1 2441 5814
Waiting: 351 2829 1268.2 2441 5814
Total: 353 2831 1268.1 2441 5814
Percentage of the requests served within a certain time (ms)
50% 2441
66% 3159
75% 3457
80% 3458
90% 5420
95% 5425
98% 5814
99% 5814
100% 5814 (longest request)
topコマンド画面は以下。
ちょっとわかりずらいが、「python3」がバッチのワークロードで、「python」がAPIのワークロードである。
バッチにniceが掛かっているにもかかわらずCPU使用率はすべて50%あたりと均衡している。これは単純にlimitから計算されたshare値でそれぞれのPodにcgroupsのCPU limitが掛かっているため、だと思う、多分。niceの比較は他のcgroupまで及んでいないことが分かる。
検証結果(パターン3)
APIでLimit指定、バッチのLimitは指定しないパターン。
abコマンドの実行時間は大体9.8秒。バッチの負荷が無い場合に比べて約1.2倍で、ちょい重くらいである。
# ab -n 50 -c 10 192.168.0.102:30000/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.102 (be patient).....done
Server Software: uvicorn
Server Hostname: 192.168.0.102
Server Port: 30000
Document Path: /
Document Length: 55 bytes
Concurrency Level: 10
Time taken for tests: 9.785 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 9000 bytes
HTML transferred: 2750 bytes
Requests per second: 5.11 [#/sec] (mean)
Time per request: 1956.950 [ms] (mean)
Time per request: 195.695 [ms] (mean, across all concurrent requests)
Transfer rate: 0.90 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.4 1 2
Processing: 292 1750 869.8 1814 3124
Waiting: 292 1750 869.5 1814 3121
Total: 292 1751 869.8 1815 3124
Percentage of the requests served within a certain time (ms)
50% 1815
66% 2038
75% 2491
80% 2628
90% 3122
95% 3123
98% 3124
99% 3124
100% 3124 (longest request)
topコマンド画面は以下。
python(オンラインAPI処理)がCPU 90%くらいの消費に対して、python3(バッチ処理)が10%程度である。つまり、バッチ処理側のniceが効いている。バッチもcgroupの中には入っていると思うが、CPU limitが掛かっていないとnice計算はノードのOS全体に対して効いてくるのだろうか。
なお念のため、abコマンドの負荷がない状態ではバッチ処理は元気にCPU使用率100%近くに張り付いている。
検証結果(パターン4)
APIでLimit指定しない、バッチでもLimitは指定しないパターン。
まあ、ついでに実施。。。と思ったのだが、結果は割と興味深く、この場合もバッチのniceがあまり効かない。ナニコレ?
abコマンドの実行時間は大体18.4秒。バッチの負荷が無い場合に比べて約2倍で、パターン2のniceが効いていない場合と同じである。
# ab -n 50 -c 10 192.168.0.102:30000/
This is ApacheBench, Version 2.3 <$Revision: 1843412 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking 192.168.0.102 (be patient).....done
Server Software: uvicorn
Server Hostname: 192.168.0.102
Server Port: 30000
Document Path: /
Document Length: 55 bytes
Concurrency Level: 10
Time taken for tests: 18.399 seconds
Complete requests: 50
Failed requests: 0
Total transferred: 9000 bytes
HTML transferred: 2750 bytes
Requests per second: 2.72 [#/sec] (mean)
Time per request: 3679.742 [ms] (mean)
Time per request: 367.974 [ms] (mean, across all concurrent requests)
Transfer rate: 0.48 [Kbytes/sec] received
Connection Times (ms)
min mean[+/-sd] median max
Connect: 0 1 0.3 0 1
Processing: 608 3389 1535.6 3767 5468
Waiting: 607 3388 1534.8 3765 5462
Total: 608 3389 1535.7 3767 5469
ERROR: The median and mean for the initial connection time are more than twice the standard
deviation apart. These results are NOT reliable.
Percentage of the requests served within a certain time (ms)
50% 3767
66% 4298
75% 4575
80% 4730
90% 5464
95% 5469
98% 5469
99% 5469
100% 5469 (longest request)
topコマンドは以下。
バッチにniceが掛かっているにもかかわらずCPU使用率はすべて50%あたりと均衡している。いや、この結果は何か良い説明が思い浮かばない。