この記事は自作している強化学習フレームワークの解説記事です。
この記事のコード場所:examples/kubernetes
無料編の続きです。
1:【強化学習】クラウドサービスを利用した分散強化学習(無料編)
2:ここ
3:【強化学習】クラウドサービスを利用した分散強化学習(GKE/有料編)
アーキテクチャ等の概要は前の記事を見てください。
まずはローカルでkubernetesを作成してみます。
全体イメージ
今回作成したい構成は複数のサービスが入り混じっています。
こういった複数のサーバを一元管理する方法の1つに kubernetes(k8s) があります。
k8sは今回初めて触るのである程度は大目に見てください…。
k8sの概念を簡単に言うと、Podが1つの仮想的なPCで、Podの中に複数のDockerコンテナがあるイメージです。
今回作成する構成の全体イメージは以下です。
大きく3つのPodを作成します。
- RedisPod
- 全体で1Pod
- RedisサーバコンテナとRedis-commander(GUI用)の2つのコンテナが起動
- TaskBoard/ParameterBoard/Queueの3つの役割を兼任
- 外部/内部を通してアクセスできるのはこのPodに対してのみを想定
- TrainerPod
- 全体で1Pod
- Trainer用のコンテナが1つ動く想定
- ActorPod
- 全体でN個のPodが起動
- 1Podに対してActor用のコンテナが1つ動く想定
ActorですがPodをレプリカという機能で複製します。(正しい使い方か分かりませんが…)
各PodはServiceという概念でそれぞれ接続します。(図でいう矢印)
※Pod内のコンテナはlocalhost扱いでそのまま通信できます
※Redisの各役割は別Podに切り出し可能です。最初はQueueの役割を別PodにRabbitMQを使っていましたが煩雑だったので統合しました。
DockerDesktop + kubernetes
k8s は DockerDesktop が公式で機能を提供しているので勉強もかねてこれを利用します。
DockerDesktopがない場合は以下からインストールします。
https://www.docker.com/products/docker-desktop/
k8sの有効化は以下です。
https://matsuand.github.io/docs.docker.jp.onthefly/desktop/kubernetes/
詳細は省略しますが、有効にすれば立ち上がってきます。
k8sはコンテキストという単位でプロジェクトを扱っているようで、docker-desktopというものが出来てるはずです。
"kubectl" コマンドが使えるかの確認も兼ねて、以下でコンテキストを確認しておきます。
# コンテキスト変更
> kubectl config use-context docker-desktop
> kubectl config get-contexts
CURRENT NAME CLUSTER AUTHINFO NAMESPACE
* docker-desktop docker-desktop docker-desktop
Podの概念
ここが結構悩んだので備忘録で書いておきます。
k8sには Pod,ReplicaSet,Deployment という概念があり、以下の意味になります。
- Podはk8sの最小単位で1つのPodに複数のコンテナが入り、Pod内は同じリソースが共有される(CPU,メモリやネットワーク等が共通)
- ReplicaSetは同じPodを複製する機能
- DeploymentはReplicaSetをラップし、デプロイ・更新・ロールバック等も含めて管理する
包含関係としては、Deployment ⊃ ReplicaSet ⊃ Pod となります。
基本はDeploymentを使えば問題ないですが、今回のバッチ処理みたいな要件だと相性が悪いようで、次の記事のGKEでリソースの確保で失敗するわするわ…。
Deploymentは少々高機能かもしれないのでPodを基本利用します。
一般的にDeploymentを使わずにPodを使う場合は以下のようです。(ChatGPT曰く)
- 学習やテスト:Deploymentは複数のPodを管理するためのもので、単一のPodを使う場合は直接Podを定義することがあります
- 一時的なジョブやタスク:例えばバッチ処理やデータのクリーニングなどの一時的な作業を行う場合は、そのジョブ専用のPodを直接作成することがあります
- 特定の制御が必要な場合:Deploymentはデプロイや更新の管理に便利なツールですが、特定の制御が必要な場合はPodを直接作成する場合があります
ほぼ今回の場合ですね。
私の解釈ですが、永続的なサービスはDeployment、バッチ処理系はPod(or ReplicaSet)で扱った方が安定するイメージです。
1. Redisの立ち上げ
まずはWeb上のコンテナイメージをそのまま使えるRedisを立ち上げてみます。
マニフェストは以下です。
GEA側もあるので作業ディレクトリは分けたいと思います。
ディレクトリ構成は以下です。
./
└ docker_desktop/ # new
└ redis.yaml # new
apiVersion: v1
kind: Service
metadata:
name: redis-internal-service
spec:
type: ClusterIP
selector:
app: redis
ports:
- name: redis-internal-port
protocol: TCP
port: 6379
targetPort: 6379
---
apiVersion: v1
kind: Service
metadata:
name: redis-external-service
spec:
type: NodePort
selector:
app: redis
ports:
- name: redis-external-port
protocol: TCP
port: 6379
nodePort: 30001
- name: redis-web-port
protocol: TCP
port: 8081
nodePort: 30002
---
apiVersion: v1
kind: ConfigMap
metadata:
name: redis-config
data:
redis.conf: |-
bind 0.0.0.0
port 6379
---
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
labels:
app: redis
spec:
containers:
- name: redis-server
image: redis:7.2-alpine
ports:
- containerPort: 6379
command: ["redis-server", "/etc/redis/redis.conf"]
volumeMounts:
- name: redis-config
mountPath: /etc/redis
resources:
limits:
cpu: "900m"
- name: redis-commander
image: rediscommander/redis-commander:latest
env: [{"name": "REDIS_HOSTS", "value": "local:localhost:6379"}]
ports:
- containerPort: 8081
resources:
limits:
cpu: "200m"
memory: "64Mi"
volumes:
- name: redis-config
configMap:
name: redis-config
内容はRedisのPodを作成し、クラスタ内部に6379ポートを、外部に向けて30001ポート(Redis)、30002ポート(Redis管理画面)を開放する内容になっています。
ファイルを作成したらプロンプトで以下を実行し、起動させます。
> kubectl apply -f ./docker_desktop/redis.yaml
以下で確認できます。
> kubectl get pods
NAME READY STATUS RESTARTS AGE
redis-pod 2/2 Running 0 14s
起動ができていれば以下から管理画面に入ることができます。
削除する場合は以下です。
> kubectl delete -f ./docker_desktop/redis.yaml
2. コンテナイメージの作成
Actor/Trainerで使用するコンテナイメージを作成します。
このイメージはGKEでも同じものを使います。
ここで作成するファイルは以下です。
./
├ docker_desktop/
| └ redis.yaml
|
├ dockerfile # new
├ server_actor.py # new
└ server_trainer.py # new
まずはエントリーポイント用のコードを作成します。
from srl.runner.distribution import RedisParameters, actor_run_forever
from srl.utils.common import logger_print
logger_print()
actor_run_forever(RedisParameters(host="redis-internal-service"), None)
from srl.runner.distribution import RedisParameters, trainer_run_forever
from srl.utils.common import logger_print
logger_print()
trainer_run_forever(RedisParameters(host="redis-internal-service"),None)
k8s内部からのアクセスになるので "redis-internal-service" でアクセスします。
(これはredis.yamlで定義しています)
次にDockerfileを作成します。
Tensorflowのイメージはサイズが大きいです…
# syntax=docker/dockerfile:1
# --- select image CPU(1.76GB) or GPU(7.38GB)
FROM tensorflow/tensorflow:2.14.0-gpu
#FROM tensorflow/tensorflow:2.14.0
WORKDIR /code
RUN apt-get update \
&& apt install -y --no-install-recommends git \
&& apt-get install -y --no-install-recommends libgl1-mesa-dev libglib2.0-0 \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir git+https://github.com/pocokhc/simple_distributed_rl@v0.13.3 \
# 必要なライブラリを適宜入れる
&& pip install --no-cache-dir opencv-python pygame gymnasium redis async_timeout
# エントリーポイントのファイルをコピー
COPY server_*.py /code/
以下でイメージ化します。(最後の.も必要)
> docker build --pull --rm -t srl_qiita:latest .
3. Actor/Trainerマニフェストファイル
最後にActor/Trainer用のマニフェストファイルを作成します。
./
├ docker_desktop/
| ├ redis.yaml
| ├ actor.yaml # new
| └ trainer.yaml # new
|
├ dockerfile
├ server_actor.py
└ server_trainer.py
apiVersion: v1
kind: Pod
metadata:
name: trainer-pod
spec:
containers:
- name: trainer-node
image: srl_qiita:latest
imagePullPolicy: Never # この行でローカルのimageを見るようにしている
#command: ["sh", "-c", "while true; do sleep 3600; done"]
command: ["python", "-u", "/code/server_trainer.py"]
resources:
limits:
cpu: "950m"
※DockerDesktopのk8sはGPUには対応していないとの事
※pythonは"-u"オプションを追加し、printがk8sのlogに出力されるようにしています
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: actor-pods
spec:
replicas: 1 # ここがそのままActor数になる
selector:
matchLabels:
app: actor
template:
metadata:
labels:
app: actor
spec:
containers:
- name: actor-node
image: srl_qiita:latest
imagePullPolicy: Never # この行でローカルのimageを見るようにしている
#command: ["sh", "-c", "while true; do sleep 3600; done"]
command: ["python", "-u", "/code/server_actor.py"]
resources:
limits:
cpu: "950m"
ActorはReplicaSetで作成しています。
replicasの数が複製するPodの数を表し、そのままActor数になります。
ただ、その数だけCPUも必要になりますが…。
以下で起動します。
> kubectl apply -f ./docker_desktop/trainer.yaml
> kubectl apply -f ./docker_desktop/actor.yaml
以下で確認します。
> kubectl get pods
NAME READY STATUS RESTARTS AGE
actor-pods-2h47z 1/1 Running 0 61s
redis-pod 2/2 Running 0 19m
trainer-pod 1/1 Running 0 13s
もし起動しない場合は command をデバッグ用に変えて起動し、以下で中に入って調べてみてください。
([NAME]には get pods の NAME を入力)
> kubectl exec -it [NAME] -- bash
また、各Podのログを見るのは以下です。
-fはオプションを追加するとリアルタイムでログが見れます。
> kubectl logs -f [NAME]
4. 学習の実行
これで準備ができたので学習します。
Redisのポート30001に向けてアクセスします。
import srl
from srl.algorithms import dqn
from srl.runner.distribution import RedisParameters
rl_config = dqn.Config()
rl_config.hidden_block.set_mlp((64, 64))
rl_config.memory.set_proportional_memory()
rl_config.memory.capacity = 5000
runner = srl.Runner("Pendulum-v1", rl_config)
runner.train_distribution(
RedisParameters(host="localhost", port=30001),
actor_num=1,
max_train_count=20_000,
)
print(runner.evaluate())
実行結果
> python main.py
18:45:35 ACTIVE 1.00s( - left), 0tr (-1451.4eval)
trainer 573db05d-d1e4-4178-a2c0-b36f03fbf303 0.9s: 0tr/s, 0recv/s, tr, 0recv
actor0 not assigned
18:46:36 ACTIVE 1.0m( 3.5m left), 4487tr (-1244.9eval)
trainer 573db05d-d1e4-4178-a2c0-b36f03fbf303 10.2s: 74tr/s, 440recv/s, 4487tr, 26578recv
actor0 b456bf97-deda-4839-8a83-fb26977abe18 10.2s: 372st/s, 371send/s, 22461st, 22424send
18:47:36 ACTIVE 2.0m( 1.7m left), 10291tr (-0.968eval)
trainer 573db05d-d1e4-4178-a2c0-b36f03fbf303 9.5s: 96tr/s, 455recv/s, 10291tr, 54000recv
actor0 b456bf97-deda-4839-8a83-fb26977abe18 9.5s: 457st/s, 457send/s, 50006st, 49987send
18:48:36 ACTIVE 3.0m(43.33s left), 15941tr (-127.4eval)
trainer 573db05d-d1e4-4178-a2c0-b36f03fbf303 9.8s: 93tr/s, 444recv/s, 15941tr, 80832recv
actor0 b456bf97-deda-4839-8a83-fb26977abe18 9.8s: 446st/s, 447send/s, 76966st, 76950send
18:49:11 END 3.6m( 0.00s left), 20000tr ( -13.4eval)
trainer 573db05d-d1e4-4178-a2c0-b36f03fbf303 1.0s: 115tr/s, 436recv/s, 20000tr, 96217recv
actor0 b456bf97-deda-4839-8a83-fb26977abe18 5.0s: 509st/s, 509send/s, 94930st, 94915send
[-13.179330630227923, -302.80894811078906, -12.675792848691344, -128.10729961842299, -247.43346573412418, -12.799946028739214, -365.9429765045643, -132.10430204682052, -14.009112168103456, -134.4337202552706]
終わったら削除しておきます。
この構成はDBへの保存は全くないので、削除すればすべて初期化されます。
# 一括削除
> kubectl delete -f ./docker_desktop
# 一括起動
> kubectl apply -f ./docker_desktop
次はこのk8s環境を Google Kubernetes Engine (GKE) に作成してみたいと思います。
【強化学習】クラウドサービスを利用した分散強化学習(GKE/有料編)