Skip to content

Commit

Permalink
Add redis in helm chart
Browse files Browse the repository at this point in the history
  • Loading branch information
oeway committed Sep 8, 2024
1 parent 7b557b0 commit 002c622
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 36 deletions.
27 changes: 27 additions & 0 deletions helm-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,33 @@ To uninstall the chart:
helm uninstall hypha-server --namespace=hypha
```

## Install Redis for scaling

Hypha can use an external Redis to store global state and pub/sub messages shared between the server instances.

If you want to scale Hypha horizontally, you can install Redis as a standalone service, and this will allow you to run multiple Hypha server instances to serve more users.

First, install the Redis helm chart:
```bash
helm install redis ./redis --namespace=hypha
```

Now change the `values.yaml` file to add the `redis-uri` to the `startupCommand`:
```yaml
startupCommand:
command: ["python", "-m", "hypha.server"]
args:
- "--host=0.0.0.0"
- "--port=9520"
- "--public-base-url=$(PUBLIC_BASE_URL)"
- "--redis-uri=redis://redis.hypha.svc.cluster.local:6379/0"
```
Now, upgrade the helm chart:
```bash
helm upgrade hypha-server ./hypha-server --namespace=hypha
```

## Setting up on Azure Kubernetes Service (AKS)

If you are deploying Hypha on Azure Kubernetes Service (AKS), you will need to configure the ingress to use the Azure Application Gateway. You can follow the instructions in the [aks-hypha.md](aks-hypha.md) file.
25 changes: 24 additions & 1 deletion helm-chart/aks-hypha.md
Original file line number Diff line number Diff line change
Expand Up @@ -203,11 +203,34 @@ startupCommand:
- "--host=0.0.0.0"
- "--port=9520"
- "--public-base-url=$(PUBLIC_BASE_URL)"
# - "--redis-uri=redis://redis.hypha.svc.cluster.local:6379/0"
```

Replace all instances of `hypha.my-company.com` with your own domain.

#### Step 5: Install Hypha Using Helm
#### Step 4: Install Redis for Scaling

If you want to scale Hypha horizontally, you can install Redis as a standalone service. This will allow you to run multiple Hypha server instances to serve more users.

First, install the Redis Helm chart:

```bash
helm install redis ./redis --namespace hypha
```

This will install Redis in the `hypha` namespace, make sure you update the `values.yaml` file to add the `redis-uri` to the `startupCommand`:

```yaml
startupCommand:
command: ["python", "-m", "hypha.server"]
args:
- "--host=0.0.0.0"
- "--port=9520"
- "--public-base-url=$(PUBLIC_BASE_URL)"
- "--redis-uri=redis://redis.hypha.svc.cluster.local:6379/0"
```

#### Step 6: Install Hypha Using Helm

Now that the configuration is set, install Hypha using Helm (make sure you are in the `helm-chart` directory):

Expand Down
2 changes: 2 additions & 0 deletions helm-chart/hypha-server/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,5 @@ startupCommand:
- "--host=0.0.0.0"
- "--port=9520"
- "--public-base-url=$(PUBLIC_BASE_URL)"
# - "--redis-uri=redis://redis.hypha.svc.cluster.local:6379/0"

6 changes: 6 additions & 0 deletions helm-chart/redis/Chart.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v2
name: redis
description: A Helm chart for deploying Redis to Kubernetes
type: application
version: 0.1.0
appVersion: "5.0.8"
60 changes: 60 additions & 0 deletions helm-chart/redis/templates/deployment.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: redis
labels:
app: redis
app.kubernetes.io/managed-by: "Helm"
annotations:
meta.helm.sh/release-name: "{{ .Release.Name }}"
meta.helm.sh/release-namespace: "{{ .Release.Namespace }}"
spec:
replicas: 1
selector:
matchLabels:
app: redis
template:
metadata:
labels:
app: redis
annotations:
meta.helm.sh/release-name: "{{ .Release.Name }}"
meta.helm.sh/release-namespace: "{{ .Release.Namespace }}"
spec:
securityContext:
fsGroup: 1001
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
containers:
- name: redis
image: redis:5.0.8
command:
- "sh"
- "-c"
- |
redis-server --dir /data --port 6379 --bind 0.0.0.0 --appendonly yes --protected-mode no --pidfile /data/redis-6379.pid
ports:
- containerPort: 6379
volumeMounts:
- name: redis-data
mountPath: /data
resources:
limits:
cpu: "2"
memory: "4Gi"
requests:
cpu: "1"
memory: "3Gi"
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
runAsNonRoot: true
runAsUser: 1001
seccompProfile:
type: RuntimeDefault
volumes:
- name: redis-data
emptyDir: {}
17 changes: 17 additions & 0 deletions helm-chart/redis/templates/service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
apiVersion: v1
kind: Service
metadata:
name: redis
labels:
app: redis
app.kubernetes.io/managed-by: "Helm"
annotations:
meta.helm.sh/release-name: "{{ .Release.Name }}"
meta.helm.sh/release-namespace: "{{ .Release.Namespace }}"
spec:
type: ClusterIP
ports:
- name: redis
port: 6379
selector:
app: redis
2 changes: 1 addition & 1 deletion hypha/core/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ async def report_login(
user_info = user_info.model_dump(mode="json")
await redis.setex(key, MAXIMUM_LOGIN_TIME, json.dumps(user_info))

logger.info(f"To preview the login page, visit: {login_service_url}")
logger.info(f"To preview the login page, visit: {login_service_url.replace('/services/', '/apps/')}")
return {
"name": "Hypha Login",
"id": "hypha-login",
Expand Down
4 changes: 2 additions & 2 deletions hypha/core/store.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ async def load_or_create_workspace(self, user_info: UserInfo, workspace: str):
workspace_info = await self.register_workspace(
{
"name": workspace,
"description": f"Auto-created workspace by {user_info.id}",
"persistent": False,
"description": f"Workspace for user {user_info.id}",
"persistent": not user_info.is_anonymous,
"owners": [user_info.id],
"read_only": user_info.is_anonymous,
}
Expand Down
11 changes: 0 additions & 11 deletions hypha/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -568,17 +568,6 @@ async def get_app_info(
user_info = Depends(store.login_optional),
):
"""Route for checking details of an app."""
if user_info.check_permission(workspace, UserPermission.read):
raise JSONResponse(
status_code=403,
content={
"success": False,
"detail": (
f"{user_info['username']} has no"
f" permission to access {workspace}"
),
},
)
service_type = await self.store.get_service_type_id(
workspace, service_id
)
Expand Down
66 changes: 45 additions & 21 deletions hypha/templates/ws/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -203,18 +203,14 @@
<i className="fas fa-bars"></i>
<span className="ml-2">Menu</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('dashboard')}>
<i className="fas fa-users"></i>
<span className="ml-2">Dashboard</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('workspaces')}>
<i className="fas fa-building"></i>
<span className="ml-2">Workspaces</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('services')}>
<i className="fas fa-cogs"></i>
<span className="ml-2">Services</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('clients')}>
<i className="fas fa-users"></i>
<span className="ml-2">Clients</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('apps')}>
<i className="fas fa-th-large"></i>
<span className="ml-2">Apps</span>
Expand All @@ -223,6 +219,10 @@
<i className="fas fa-folder"></i>
<span className="ml-2">Files</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('workspaces')}>
<i className="fas fa-building"></i>
<span className="ml-2">Workspaces</span>
</div>
<div className="menu-item" onClick={() => setActivePanel('development')}>
<i className="fas fa-code"></i>
<span className="ml-2">Development</span>
Expand All @@ -239,9 +239,10 @@
const ClientCard = ({ client }) => {
return (
<div className="flex flex-col items-center p-6 bg-gray-800 rounded-lg shadow-lg mb-6">
<i className="fas fa-user fa-3x text-white mb-4"></i>
<i className="fas fa-users fa-3x text-white mb-4"></i>
<div className="text-center">
<h3 className="text-xl font-bold">{client.split("/")[1]}</h3>
<p className="text-gray-400 whitespace-pre-wrap break-words">client id: {client}</p>
</div>
</div>
);
Expand All @@ -251,8 +252,11 @@ <h3 className="text-xl font-bold">{client.split("/")[1]}</h3>
return (
<div className="service-card">
<i className={`fas fa-cog fa-3x text-white mb-4`}></i>
<div className="text-center">
<div>
<h3 className="text-xl font-bold">{service.id.split(":")[1]}</h3>
<p className="text-gray-400">type: {service.type}</p>
<p className="text-gray-400 whitespace-pre-wrap break-words ">id: {service.id}</p>
<p className="text-gray-400 whitespace-pre-wrap break-words ">{service.description}</p>
<button
className="mt-4 bg-blue-600 hover:bg-blue-500 text-white font-bold py-2 px-4 rounded-full transition duration-300"
onClick={() => onShowDetails(service)}
Expand Down Expand Up @@ -448,6 +452,7 @@ <h2 className="text-2xl font-bold">{workspaceInfo.name}</h2>
const [description, setDescription] = React.useState('');
const [persistent, setPersistent] = React.useState(true);
const [readOnly, setReadOnly] = React.useState(false);
const [overwrite, setOverwrite] = React.useState(false);
const [owners, setOwners] = React.useState('');

const handleSubmit = async (event) => {
Expand All @@ -457,7 +462,7 @@ <h2 className="text-2xl font-bold">{workspaceInfo.name}</h2>
return;
}
const ownersArray = owners.split(',').map(owner => owner.trim());
await onCreateWorkspace({ name, description, owners: ownersArray, persistent, read_only: readOnly });
await onCreateWorkspace({ name, description, owners: ownersArray, persistent, read_only: readOnly }, overwrite);
onClose(); // Close the modal after creating the workspace
};

Expand All @@ -471,7 +476,9 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
<div className="modal-body">
<form onSubmit={handleSubmit}>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-200">Name</label>
<label className="block text-sm font-medium text-gray-200">Name
<p className="text-gray-500">A workspace name must contain at least one hyphen (e.g. my-workspace), in lowercase letters, numbers and hyphens only.</p>
</label>
<input
type="text"
value={name}
Expand All @@ -481,7 +488,9 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-200">Description</label>
<label className="block text-sm font-medium text-gray-200">Description
<p className="text-gray-500">A brief description of the workspace, this will be displayed in the workspace list.</p>
</label>
<input
type="text"
value={description}
Expand All @@ -491,7 +500,9 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-200">Owners (comma-separated)</label>
<label className="block text-sm font-medium text-gray-200">Owners (comma-separated)
<p className="text-gray-500">A list of owners who can manage the workspace, it must be a comma-separated list of user ids or email addresses.</p>
</label>
<input
type="text"
value={owners}
Expand All @@ -500,7 +511,9 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-200">Persistent</label>
<label className="block text-sm font-medium text-gray-200">Persistent
<p className="text-gray-500">If checked, the workspace will be persistent and will not be deleted after a period of inactivity.</p>
</label>
<input
type="checkbox"
checked={persistent}
Expand All @@ -517,6 +530,17 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
className="ml-2"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-200">Overwrite
<p className="text-gray-500">If checked, the workspace will be overwritten if it already exists.</p>
</label>
<input
type="checkbox"
checked={overwrite}
onChange={(e) => setOverwrite(e.target.checked)}
className="ml-2"
/>
</div>
<button
type="submit"
className="bg-blue-600 text-white font-bold py-2 px-4 rounded"
Expand Down Expand Up @@ -547,9 +571,9 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
fetchWorkspaces();
}, [panel, ws]);

const handleCreateWorkspace = async (workspaceData) => {
const handleCreateWorkspace = async (workspaceData, overwrite) => {
try {
await ws.createWorkspace({ ...workspaceData });
await ws.createWorkspace({ ...workspaceData }, overwrite);
setWorkspaces(await ws.listWorkspaces());
} catch (error) {
alert(`Failed to create workspace: ${error.message}`);
Expand Down Expand Up @@ -579,10 +603,10 @@ <h2 className="text-2xl font-bold">Create New Workspace</h2>
<div className={`main-content transition-all duration-300 ${isCollapsed ? 'ml-16' : 'ml-64'}`}>
<h1 className="text-4xl font-bold capitalize">{panel}</h1>

{panel === 'dashboard' && (
{panel === 'clients' && (
<>
<p className="text-xl text-gray-400 mt-4">
Dashboard for workspace: <code>{workspaceName}</code>
Clients in workspace: <code>{workspaceName}</code>
</p>
<div className="grid grid-cols-1 gap-8 mt-6">
<h2 className="text-2xl font-bold">Clients</h2>
Expand Down Expand Up @@ -711,7 +735,7 @@ <h2 className="text-2xl font-bold">Workspaces</h2>

const App = () => {
const [isCollapsed, setIsCollapsed] = React.useState(false);
const [activePanel, setActivePanel] = React.useState('dashboard');
const [activePanel, setActivePanel] = React.useState('services');
const [clients, setClients] = React.useState([]);
const [services, setServices] = React.useState([]);
const [apps, setApps] = React.useState([]);
Expand Down Expand Up @@ -740,7 +764,7 @@ <h2 className="text-2xl font-bold">Workspaces</h2>
if (!ws) {
return;
}
if (activePanel === 'dashboard') {
if (activePanel === 'clients') {
setClients(await ws.listClients());
} else if (activePanel === 'services') {
setServices(await ws.listServices(config.workspace));
Expand Down

0 comments on commit 002c622

Please sign in to comment.