KubernetesのPodでniceを効かせたい場合はそのPodのresource指定を外すべし

ノードに割り当てられたCPUが1つで、2つのPodがCPUのresource limitをそれぞれ1000m指定してオーバーコミット状態の時、一方でniceを掛けたかったらそのPodマニフェストからlimit指定を外さないとniceが効かない。という話とその検証メモ。

シチュエーションとしてあり得るのは、１つのクラスターでオンラインAPI処理とバッチ処理を２つデプロイして、処理の優先度としてはオンラインAPIを高くして、それ以外の空いたCPU時間をバッチ処理に回したいという場合。クラスタ分けろよ、というのはクラウドなら成立しやすいのだが、まあ、オンプレで資源の有効利用は重要課題である。空いたサーバーにビットコインを発掘させるだけでも経営の一助となり得るかもしれないですし？

バッチ処理側でresource limitを外さないと、なのは分かる様な気がするが、意味が分からない挙動としては、優先度を持たせたいオンラインAPI処理にはきちんとresource指定しないと、バッチ側のniceが有効に働かないというところ。このあたりの挙動はわかりやすようにその内修正されるのかもしれないが。

# 検証
オンラインAPI風のPodと、バッチ処理風のPodを以下のように作成する。

## オンラインAPI風機能
Python/FastAPIで、GETリクエストが来るたびにsecrets.token_urlsafe()を10万回呼び出した後応答するモックプログラム。ほかの負荷がない場合、リクエストの応答には大体0.3秒掛かる。

```python:main.py
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は以下の通り。

```Dockerfile: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万回ごとに１行、現在時刻をprintする。ほかの負荷がない場合、大体３秒に１行メッセージが出力される。
一応SIGETRMを受け取ったらメッセージ出力のタイミングで終了するようにしてある。

```python:
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は以下。

```Dockerfile: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としてCPU２個、メモリ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が２つなので、オンラインAPI風機能2 Pod、バッチ風機能が2 Podで検証する。

オンラインAPI風機能のマニフェストが以下。
resource.requests.cpuの値指定は、指定しないとlimitsの1000mが反映されて、2つPodが起動しなくなるので仕方なく。

```yaml:api.yaml
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指定を外した場合の例。

```yaml:load.yaml
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並列でリクエストを投げた完了時間。
以下の４パターンで検証する。

1. APIのみ実行、バッチの負荷は掛けない
2. APIでLimit指定、バッチでもLimit指定
3. APIでLimit指定、バッチのLimitは指定しない
4. 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のプロセス）が大部分を占めていて、まあ問題ない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/8e925db3-9b6e-8dcb-a502-61fd530f1c8f.png)

## 検証結果（パターン2）

APIでLimit指定、バッチでもLimit指定するパターン。
どちらも、なるべくCPU使ってねとCPU limitに1000mを指定しているケース。バッチのプロセスにniceは掛かっているが。

abコマンドの実行時間は大体16秒。バッチの負荷が無い場合に比べて約２倍。

```
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まで及んでいないことが分かる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/1f153ab1-86a0-ac44-545c-b126f01303ff.png)

## 検証結果（パターン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%近くに張り付いている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/c6db4fc0-f418-ee5d-3ba8-b682ddbe1921.png)

## 検証結果（パターン4）

APIでLimit指定しない、バッチでもLimitは指定しないパターン。
まあ、ついでに実施。。。と思ったのだが、結果は割と興味深く、この場合もバッチのniceがあまり効かない。ナニコレ？

abコマンドの実行時間は大体18.4秒。バッチの負荷が無い場合に比べて約２倍で、パターン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%あたりと均衡している。いや、この結果は何か良い説明が思い浮かばない。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/146381/bcb851cd-74b3-4c46-09b9-6de5da4d5287.png)