How to implement a microservices architecture for an AI learning platform using Python and Kubernetes
Tags: python, kubernetes, docker, websocket
Published: 2024-01-04 09:44:32

The microservices architecture of the PAIA system, an AI learning platform that encourages student competition through AI game code writing, offers three key solutions: 

1. Seamless integration and independent control of Python language with containerized apps in our AI training system.
2. Independent microservices that can be updated separately.
3. Rapid configuration of environment parameters based ondiverse development scenarios.

Our PAIA system is built on the Django framework using Python, where Django is primarily responsible for managing the Application Programming Interface (API), encompassing authentication, authorization, and accounting features connected to a PostgreSQL database. PAIA aims to provide an Artificial Intelligence (AI) learning and competitive environment based on gaming or edutainment methodology. Users can code their AI using Blockly, a block-based visual programming language, or Python, and then upload it to run their AI models and training data on our platform.

The entire system is divided into three components:



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ahbgbsskdyt98po77vu5.png)

[Frontend (Vue)] <-> [Backend (Django)] <-> [MLGame (Python/Pygame)]

Both the backend and MLGame, excluding the frontend, are developed using Python and related skills. The details of their implementation and architecture are as follows:



![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8s1mysitas6qa5xx53hl.png)
Backend:
- API design
- Serializer and folder structure in Django
- Database creation and migration
- Redis and cache mechanism
- Pika and WebSocket integration
- AAA (authentication, authorization, accounting) design


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irieedom62j7xuy4caki.png)

MLGame (Game Core, Game, AI, and Game Data):
- MLGame, authored by Kun-Yi Li and available on [GitHub](https://github.com/LanKuDot/MLGame)
- RabbitMQ consumer (Pika) with WebSocket
- Kubernetes client API in Python
- Python multiple inheritance and design patterns in AI fields

The main purpose of MLGame is to run games created by creators through the Pygame framework. For a game to be executable within the MLGame framework, it needs to adhere to the structure specified by MLGame. In other words, MLGame can be considered as a game engine with a focus on machine learning.

It all starts with the program MLGame.py.
```
def __init__(self, propty: GameMLModeExecutorProperty):
    self._execution_cmd = propty.execution_cmd
    
    # Get the active ml names from the created ml processes
    self._active_ml_names = list(self._comm_manager.get_ml_names())
    self._recorder = get_recorder(self._execution_cmd, self._ml_names)
    self._progress_recorder = get_progress_recorder(self._execution_cmd, self._ml_names)
```

By executing the following command in the command prompt (cmd), a game can be launched.
`python MLGame.py -i ml_play_template.py easy_game  --score 10 --color FF9800 --time_to_play 1200 --total_point 15`

Among them, ml_play_template.py is an AI program written by the player. The game to be executed is named "easy_game," followed by the configuration parameters for that game.

Before running the game, we use an external program to create the MLGame container. The program is responsible for using Pika to listen to commands from the API (Backend).
```
connection = pika.BlockingConnection(
    pika.ConnectionParameters(host=MQ_HOST, credentials=MQ_CREDENTIAL),
)
channel = connection.channel()

channel.queue_declare(queue=queue_name, durable=True)
channel.basic_publish(exchange='', routing_key=queue_name, body=game_cmd,
            properties=pika.BasicProperties(delivery_mode=2))

```

After packaging the game to be executed as a Docker container, the container will be deleted upon completion of the execution. 
```
import docker
container_client.from_env()
container = container_client.run(target_image, 
                                 ["tail","-f","/dev/null"], 
                                 name=container_name, 
                                 detach=True, 
                                 auto_remove=False,
                                 mem_limit=memory_limit,
                                 network="bridge",
                                 stdin_open=True,tty=True)

container.exec_run(mlgame_cmd,detach=True,stdin=True,tty=True)
```


However, it has been modified to use pods in Kubernetes to encapsulate containers with auto-deletion after execution. 
```
from kubernetes import client, config as k8s_config, utils
from kubernetes.client import Configuration
from kubernetes.client.api import core_v1_api
api_response = api_instance.read_namespaced_pod_log(name=POD_NAME, namespace='default')

with open(os.path.join(os.path.dirname(__file__), "/root/pod.yaml")) as f:
    pod_body = yaml.safe_load(f)
    api_instance.create_namespaced_pod(namespace='default', body=pod_body)

api_instance.patch_namespaced_pod(name=POD_NAME, namespace='default', body={"metadata":{"labels":{"name": "mlgame-completed-pod“}}})

api_instance.delete_namespaced_pod(name=POD_NAME, namespace='default')
```

Following this, we proceed to download the relevant AI program, ml_play_template.py. After downloading the AI program, we use the subprocess to open another thread to execute the game.
`process = subprocess.Popen(mlgame_cmd, stdout=subprocess.PIPE, universal_newlines=True)`

We can monitor MLGame containers through RabbitMQ to ensure that their tasks have been completed.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ldpfptm7m9url5bwdu3s.png)
ASGI in Django API service facilitates the handling of messages from MLGame through websocket.

```
"websocket": TokenAuthMiddlewareStack(
    URLRouter(
        [
          path("ws/game_room/<str:room_id>",GameRoomConsumer.as_asgi())
        ]
    )
),

from channels.generic.websocket import AsyncWebsocketConsumer

self.accept()
self.send(text_data=message)
async def receive(self, text_data)
await self.channel_layer.group_send(self.group_name, {'type': 'chat_message’, 'message': recv_data})
```

Within the **GameMLModeExecutor**, the game loop is executed. The **send_game_progress** function is employed to send data to the backend via WebSocket. This data includes the coordinates, dimensions, colors, status (player info), and time of the foreground, background, and objects within the game. The backend then forwards this information to the client, rendering it in the browser.
```
def _loop(self):
        game = self._game_cls(*self._execution_cmd.game_params)
        assert isinstance(game, PaiaGame), "Game " + str(game) + " should implement a abstract class : PaiaGame"
        scene_init_info_dict = game.get_scene_init_data()
        # game_view = PygameView(scene_init_info_dict)
        while not quit_or_esc():
            cmd_dict = game.get_keyboard_command()
            result = game.update(cmd_dict)
            view_data = game.get_scene_progress_data()
            # game_view.draw(view_data)
            if result == "QUIT":
                scene_info_dict = game.game_to_player_data()
                self._recorder.record(scene_info_dict, {})
                self._recorder.flush_to_file()
                game.reset()
                break
```

In the end, after deploying a Kubernetes Cluster using Jenkins for product deployment, we can play the game and observe its animation in the frontend.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hmanwmqgpoz0knhjgvf2.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5lti4ix1gdhhmj2zw25t.png)