feat(bot): 🚧 Start migrating to k8s.

This commit is contained in:
Nyeki.py
2024-10-30 15:38:00 -03:00
parent 01c4a95ff3
commit 345e44137a
17 changed files with 304 additions and 40 deletions
+1
View File
@@ -123,6 +123,7 @@ celerybeat.pid
# Environments # Environments
.env .env
.env.shared
.venv .venv
env/ env/
venv/ venv/
+4 -1
View File
@@ -1,3 +1,6 @@
{ {
"conventionalCommits.scopes": ["bot", "db"] "conventionalCommits.scopes": ["bot", "db"],
"yaml.schemas": {
"kubernetes://schema/apps/v1%40deployment": "file:///c%3A/Users/rafab/Documents/Repos/Payphone-1/k8s.yml"
}
} }
-1
View File
@@ -8,4 +8,3 @@ RUN uv sync --no-dev
RUN uv add uvloop RUN uv add uvloop
ENTRYPOINT [ "/bin/bash" ] ENTRYPOINT [ "/bin/bash" ]
CMD [ "run.sh" ]
+7
View File
@@ -1 +1,8 @@
# Payphone # Payphone
Payphone is deployed using Kubernetes. To set up a local kubernetes cluster, you can use `minikube`.
```sh
# To create a local cluster of 3 nodes:
minikube start --network-plugin=cni --cni=calico --nodes=3
```
+1
View File
@@ -10,6 +10,7 @@ services:
depends_on: depends_on:
- database - database
- redis - redis
command: run.sh
restart: always restart: always
database: database:
+40
View File
@@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: discord-bot
labels:
app: discord-bot
spec:
replicas: 11
selector:
matchLabels:
app: discord-bot
template:
metadata:
labels:
app: discord-bot
spec:
containers:
- name: bot
image: nekidev/payphone:latest
env:
- name: IS_DEBUG
value: "false"
- name: SHARD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TOTAL_SHARDS
value: "11"
- name: REDIS_HOST
value: "redis"
- name: REDIS_PORT
value: "6379"
resources:
limits:
cpu: "500m"
memory: "512Mi"
requests:
cpu: "250m"
memory: "256Mi"
restartPolicy: Always
+57
View File
@@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: citus-coordinator
spec:
replicas: 1
selector:
matchLabels:
app: citus
role: coordinator
template:
metadata:
labels:
app: citus
role: coordinator
spec:
containers:
- name: coordinator
image: citusdata/citus:latest
env:
- name: POSTGRES_DB
value: "payphone"
- name: POSTGRES_USER
value: "payphone"
- name: POSTGRES_PASSWORD
value: "payphone"
volumeMounts:
- name: citus-coordinator-storage
mountPath: /var/lib/postgresql/data
ports:
- containerPort: 5432
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "2Gi"
cpu: "2"
volumes:
- name: citus-coordinator-storage
persistentVolumeClaim:
claimName: citus-coordinator-pvc
---
apiVersion: v1
kind: Service
metadata:
name: citus-coordinator
spec:
selector:
app: citus
role: coordinator
ports:
- protocol: TCP
port: 5432
targetPort: 5432
type: ClusterIP
+22
View File
@@ -0,0 +1,22 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: citus-coordinator-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi # Change this size as needed
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: citus-worker-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 10Gi # Change this size as needed
+63
View File
@@ -0,0 +1,63 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: citus-worker
spec:
replicas: 2 # Increase as needed for more workers
selector:
matchLabels:
app: citus
role: worker
serviceName: citus-worker-headless
template:
metadata:
labels:
app: citus
role: worker
spec:
containers:
- name: worker
image: citusdata/citus:latest
env:
- name: POSTGRES_DB
value: "payphone"
- name: POSTGRES_USER
value: "payphone"
- name: POSTGRES_PASSWORD
value: "payphone"
ports:
- containerPort: 5432
resources:
requests:
memory: "2Gi"
cpu: "1"
limits:
memory: "2Gi"
cpu: "2"
volumeMounts:
- name: citus-worker-storage
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: citus-worker-storage
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "standard" # Update based on your storage class
resources:
requests:
storage: 5Gi # Size per worker
---
apiVersion: v1
kind: Service
metadata:
name: citus-worker-headless
spec:
selector:
app: citus
role: worker
ports:
- protocol: TCP
port: 5432
targetPort: 5432
clusterIP: None # Defines the service as headless, allowing direct pod DNS entries
+40
View File
@@ -0,0 +1,40 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
spec:
containers:
- name: redis
image: redis:7.4.1-alpine
ports:
- containerPort: 6379
resources:
requests:
memory: "1Gi"
cpu: "1"
limits:
memory: "2Gi"
cpu: "1"
---
apiVersion: v1
kind: Service
metadata:
name: redis
spec:
selector:
app: redis
ports:
- protocol: TCP
port: 6379
targetPort: 6379
type: ClusterIP
+2
View File
@@ -0,0 +1,2 @@
45.134.39.59
2.56.246.33
+29 -21
View File
@@ -28,9 +28,6 @@ from payphone.core import (
) )
logging.setup()
class Payphone(commands.AutoShardedBot): class Payphone(commands.AutoShardedBot):
shutdown: managers.ValueProxy[bool] shutdown: managers.ValueProxy[bool]
"""A value proxy that connects with all other shard processes. """A value proxy that connects with all other shard processes.
@@ -74,11 +71,34 @@ class Payphone(commands.AutoShardedBot):
) )
async def on_ready(self) -> None: async def on_ready(self) -> None:
"""Runs when the bot has finished initializing. """Runs when the bot has finished initializing."""
"""
self.log.info("Ready! Logged in as %s.", self.user.display_name) self.log.info("Ready! Logged in as %s.", self.user.display_name)
async def set_presence(self, shard_id: int) -> None:
"""Sets the presence of the bot back to online.
Args:
shard_id (int): The ID of the shard to set the status for.
"""
await self.change_presence(
activity=discord.CustomActivity(
name=f"Listening to p.help | Shard {shard_id + 1}"
),
status=discord.Status.online,
shard_id=shard_id,
)
async def on_shard_connect(self, shard_id: int) -> None:
"""Runs when the shard connects.
Args:
shard_id (int): The ID of the shard that connected.
"""
self.log.info("Shard has connected.")
async def on_shard_ready(self, shard_id: int) -> None: async def on_shard_ready(self, shard_id: int) -> None:
"""Sets the activity for the shard. """Sets the activity for the shard.
@@ -86,15 +106,9 @@ class Payphone(commands.AutoShardedBot):
shard_id (int): The ID of the initialized shard. shard_id (int): The ID of the initialized shard.
""" """
await self.change_presence( self.log.info("Shard is ready.")
activity=discord.CustomActivity(
name=f"Listening to p.help | Shard {shard_id + 1}"
),
status=discord.Status.online,
shard_id=shard_id,
)
self.log.info("Shard %s is ready!", shard_id) await self.set_presence(shard_id)
async def on_shard_resumed(self, shard_id: int) -> None: async def on_shard_resumed(self, shard_id: int) -> None:
"""Sets the activity for the shard. """Sets the activity for the shard.
@@ -103,15 +117,9 @@ class Payphone(commands.AutoShardedBot):
shard_id (int): The ID of the resumed shard. shard_id (int): The ID of the resumed shard.
""" """
await self.change_presence( self.log.info("Shard has resumed.")
activity=discord.CustomActivity(
name=f"Listening to p.help | Shard {shard_id + 1}"
),
status=discord.Status.online,
shard_id=shard_id,
)
self.log.info("Shard %s was resumed!", shard_id) await self.set_presence(shard_id)
async def get_or_fetch_channel( async def get_or_fetch_channel(
self, channel_id: int self, channel_id: int
+29 -10
View File
@@ -3,17 +3,36 @@ import logging
from rich import logging as rlogging from rich import logging as rlogging
FORMAT = "%(message)s"
handler = rlogging.RichHandler(level=logging.INFO, rich_tracebacks=True)
formatter = logging.Formatter(FORMAT)
handler.setFormatter(formatter)
logger = logging.getLogger("payphone") logger = logging.getLogger("payphone")
def setup(): def _get_handler(process_name: str, module: str | None = None) -> None:
"""Sets up pycord logging.""" """Generates a handler for the given process name and module.
logging.basicConfig(level=logging.DEBUG, handlers=[handler]) Args:
process_name (str): The process name.
module (str | None, optional): The module inside the process. Defaults to None.
"""
fmt = f"[blue][{process_name}{f": [green]{module}[/green]" if module is not None else ""}][/blue] %(message)s"
formatter = logging.Formatter(fmt)
handler = rlogging.RichHandler(
level=logging.INFO, markup=True, rich_tracebacks=True
)
handler.setFormatter(formatter)
return handler
def setup(process_name: str):
"""Sets up pycord logging.
Args:
process_name (str): The name of the process.
"""
logging.basicConfig(level=logging.DEBUG, handlers=[_get_handler(process_name)])
logging.getLogger("asyncio").addHandler(_get_handler(process_name, "asyncio"))
logging.getLogger("discord").addHandler(_get_handler(process_name, "discord"))
+3
View File
@@ -117,9 +117,12 @@ async def loop() -> None:
import asyncio import asyncio
from payphone.db import setup from payphone.db import setup
from payphone.core import logging
await setup.init() await setup.init()
logging.setup("Flushing")
try: try:
while True: while True:
await asyncio.sleep(30) await asyncio.sleep(30)
-1
View File
@@ -7,7 +7,6 @@ requires-python = ">=3.12"
dependencies = [ dependencies = [
"aerich==0.7.1", "aerich==0.7.1",
"aiohttp>=3.10.10", "aiohttp>=3.10.10",
"click>=8.1.7",
"colorthief>=0.2.1", "colorthief>=0.2.1",
"httpx>=0.27.2", "httpx>=0.27.2",
"orjson>=3.10.7", "orjson>=3.10.7",
+5 -3
View File
@@ -5,8 +5,6 @@ from datetime import datetime, timezone
from payphone.core import logging from payphone.core import logging
logging.setup()
log = logging.logger log = logging.logger
@@ -28,13 +26,15 @@ def shard(
from payphone.db import setup from payphone.db import setup
from payphone.bot import bot from payphone.bot import bot
from payphone.core import config from payphone.core import config, logging
bot.shard_ids = [shard_id] bot.shard_ids = [shard_id]
bot.shard_count = shard_count bot.shard_count = shard_count
bot.shutdown = shutdown bot.shutdown = shutdown
bot.scheduled_shutdown = scheduled_shutdown bot.scheduled_shutdown = scheduled_shutdown
logging.setup(f"Shard {shard_id}")
log.info("Running shard %s with default loop.", shard_id) log.info("Running shard %s with default loop.", shard_id)
bot.loop.set_debug(True) bot.loop.set_debug(True)
@@ -84,6 +84,8 @@ if __name__ == "__main__":
from payphone.db import setup, models from payphone.db import setup, models
from payphone.core import config from payphone.core import config
logging.setup("Payphone")
redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis") redis = aredis.Redis(host="localhost" if config.IS_DEBUG else "redis")
async def init(): async def init():
Generated
-2
View File
@@ -392,7 +392,6 @@ source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "aerich" }, { name = "aerich" },
{ name = "aiohttp" }, { name = "aiohttp" },
{ name = "click" },
{ name = "colorthief" }, { name = "colorthief" },
{ name = "httpx" }, { name = "httpx" },
{ name = "orjson" }, { name = "orjson" },
@@ -407,7 +406,6 @@ dependencies = [
requires-dist = [ requires-dist = [
{ name = "aerich", specifier = "==0.7.1" }, { name = "aerich", specifier = "==0.7.1" },
{ name = "aiohttp", specifier = ">=3.10.10" }, { name = "aiohttp", specifier = ">=3.10.10" },
{ name = "click", specifier = ">=8.1.7" },
{ name = "colorthief", specifier = ">=0.2.1" }, { name = "colorthief", specifier = ">=0.2.1" },
{ name = "httpx", specifier = ">=0.27.2" }, { name = "httpx", specifier = ">=0.27.2" },
{ name = "orjson", specifier = ">=3.10.7" }, { name = "orjson", specifier = ">=3.10.7" },