Skip to content

Commit

Permalink
Feature/database (#22)
Browse files Browse the repository at this point in the history
* Add db migration with flyway

* Add Exposed library to manage log persistence
Improve frontend error displaying

* Add postgresql, persistent volume, pvc as k8s deployment

* Add postgres to kustomization.yaml, bump image versions

* Update README.md

* Update README.md

* Adjust docker-compose.yml

* Review fixes

* Add postgres k8s network policy

* Add runner resource requests and limits

* Revert "Add runner resource requests and limits"

This reverts commit 4dc7ed2.

* Revert "Add postgres k8s network policy"

This reverts commit 4c31b1b.
  • Loading branch information
mherda64 committed Jun 8, 2023
1 parent c3f18d1 commit 76f2b7b
Show file tree
Hide file tree
Showing 37 changed files with 412 additions and 73 deletions.
28 changes: 19 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# Kubexecutor
To build:
```shell
./gradlew jar
```

To build container image:
```shell
docker build -t kubexecutor:$(git rev-parse --short HEAD) -t kubexecutor:latest .
```
A code execution sandbox running natively on Kubernetes. Written in Kotlin.
The project leverages the power of Kubernetes orchestration and the security capabilities
of Kata containers to create a ~~robust and secure~~(one day) environment for executing untrusted code.

The project uses Kata containers as a runtime environment, which provide lightweight, secure and isolated containerized
virtual machines that offer an additional layer of protection compared to traditional container runtimes.

[Try it out here.](https://kube.skni.edu.pl/)

## Building images
To build service images, use defined Github Actions.

## Setting up dev cluster with k3s and Kata Containers
We'll be using kata-containers stable-3.1.
Expand Down Expand Up @@ -38,12 +41,19 @@ Source:
kubectl port-forward svc/argocd-server -n argocd 8080:443
```

## Accessing PostgreSQL

```shell
kubectl port-forward svc/postgresdb -n postgres 5432:5432
```
Then log into database using any database client and secrets available on the cluster (namespace `postgres`).

## Deploying my version of Kubexecutor on dev env

In order to run my version of Kubexecutor on dev cluster:
1. Create a feature branch with desired changes.
2. Modify image tag version in `k8s/overlays/dev/kustomization.yaml`
3. Run Build & push Github workflow with version specified in the step above.
3. Run Build & push Github Actions workflow of the modified service.
4. Connect to ArgoCD, enter kubexecutor app -> app details -> edit.
5. Change *Target Revision* to name of Your feature branch, i.e. `feature/test-deploy-from-branch`.
6. Sync the state of the application in ArgoCD.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package pl.edu.prz.kod.common.adapters.http.dto

data class CodeResponse(
val stdout: String,
val stdOut: String,
val stdErr: String,
val exitCode: Int
)
34 changes: 28 additions & 6 deletions docker-compose/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,24 +1,36 @@
services:
kubexecutor-frontend:
image: kubexecutor-frontend:latest
image: musiek/frontend:0.05
ports:
- "3000:3000"
networks:
- kubexecutor-network
environment:
- EXECUTE_ENDPOINT=http://kubexecutor-mediator:8081/execute
- AUTHENTICATE_ENDPOINT=http://localhost:8081/authenticate # localhost required for redirection
kubexecutor-mediator:
image: kubexecutor-mediator:latest
# ports:
# - "8081:8081"
image: musiek/mediator:0.28
ports:
- "8081:8081"
networks:
- kubexecutor-network
environment:
- RUNNER_POD_NAME=kubexecutor-runner
- PATH_FORMAT=http://%s:8080
- RUNNER_INSTANCES=2
- FRONTEND_HTTP_URL=http://localhost:3000 # localhost required for redirection
- BACKEND_HTTP_URL=http://localhost:8081 # localhost required for redirection
- JDBC_URL=jdbc:postgresql://postgres:5432/mediator
- OAUTH_CLIENT_ID=client_id # You can get it from our K8s secrets or generate your own
- OAUTH_CLIENT_SECRET=client_secret # You can get it from our K8s secrets or generate your own
- JWT_SECRET=jwt_secrets
- DATABASE=mediator
- DB_USER=admin
- DB_PASSWORD=admin
depends_on:
- postgres
kubexecutor-runner-0:
image: kubexecutor-runner:latest
image: musiek/runner:0.24
# ports:
# - "8080:8080"
restart: unless-stopped
Expand All @@ -27,13 +39,23 @@ services:
environment:
- SYSTEM_COMMAND_TIMEOUT=20000
kubexecutor-runner-1:
image: kubexecutor-runner:latest
image: musiek/runner:latest
# ports:
# - "8080:8080"
restart: unless-stopped
networks:
- kubexecutor-network
environment:
- SYSTEM_COMMAND_TIMEOUT=20000
postgres:
image: "postgres:15.2"
environment:
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=admin
- POSTGRES_DB=mediator
ports:
- "5432:5432"
networks:
- kubexecutor-network
networks:
kubexecutor-network: {}
5 changes: 5 additions & 0 deletions frontend/src/app/api/execute/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,10 @@ export async function POST(request: NextRequest) {
} else if (res.status == 200) {
const response = await res.json();
return NextResponse.json(response);
} else if (res.status == 503) {
const response = await res.json();
return NextResponse.json(response, {
status: 503
});
}
}
26 changes: 21 additions & 5 deletions frontend/src/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ const CodeEditor = ({ authToken }: CodeEditorProps) => {
const editorRef = useRef<EditorType.IStandaloneCodeEditor | null>(null);
const [language, setLanguage] = useState("nodejs");
const [monacoInstance, setMonacoInstance] = useState<Monaco | null>(null);
const [output, setOutput] = useState<{ stdout: string; stdErr: string; exitCode: number } | null>(null);
const [output, setOutput] = useState<{ stdOut: string; stdErr: string; exitCode: number } | null>(null);
const [error, setError] = useState<{ message: string } | null>(null);

const endpoint = "/api/execute"
const getValue = async () => {
Expand All @@ -40,13 +41,21 @@ const CodeEditor = ({ authToken }: CodeEditorProps) => {
console.log('redirect')
signIn()
} else if (response.status == 200) {
response.json()
.then(data => {
console.log(data)
setOutput(data)
setError(null)
});
} else if (response.status == 503) {
response.json()
.then(data => {
console.log(data);
setOutput(data);
setOutput(null)
setError(data)
});
}
};
}

const signIn = async () => {
const endpoint = "/api/authenticationUrl"
Expand Down Expand Up @@ -113,15 +122,22 @@ const CodeEditor = ({ authToken }: CodeEditorProps) => {
/>
{output && (
<div className="p-4 bg-gray-900 text-white">
<h1>Wynik</h1>
<h1>Result</h1>
<h2 className="font-bold">Output:</h2>
<pre className="whitespace-pre-wrap">{output.stdout}</pre>
<pre className="whitespace-pre-wrap">{output.stdOut}</pre>
<h2 className="font-bold">Error:</h2>
<pre className="whitespace-pre-wrap">{output.stdErr}</pre>
<h2 className="font-bold">Exit Code:</h2>
<pre className="whitespace-pre-wrap">{output.exitCode}</pre>
</div>
)}
{error && !output && (
<div className="p-4 bg-gray-900 text-white">
<h1>Error</h1>
<h2 className="font-bold">Message:</h2>
<pre className="whitespace-pre-wrap">{error.message}</pre>
</div>
)}
</div>
);
};
Expand Down
4 changes: 4 additions & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ koinVersion=3.4.0
jacksonVersion=2.14.2
googleApiClientVersion=2.2.0
javaJwtVersion=3.3.0
flywayVersion=9.19.3
postgresqlVersion=42.6.0
hikariVersion=5.0.1
exposedVersion=0.40.1
kotlin.code.style=official
3 changes: 2 additions & 1 deletion k8s/base/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ resources:
- runner.yaml
- mediator.yaml
- frontend.yaml
- network_policy.yaml
- network_policy.yaml
- postgresql.yaml
2 changes: 2 additions & 0 deletions k8s/base/mediator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ spec:
value: "https://kube.skni.edu.pl"
- name: BACKEND_HTTP_URL
value: "https://kube.skni.edu.pl:4748"
- name: JDBC_URL
value: "jdbc:postgresql://postgresdb.postgres.svc.cluster.local:5432/mediator"
envFrom:
- secretRef:
name: mediator-secret
Expand Down
1 change: 1 addition & 0 deletions k8s/base/network_policy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ spec:
matchLabels:
app: mediator
#---
# TODO: allow traffic from traefik load balancer pods
#apiVersion: networking.k8s.io/v1
#kind: NetworkPolicy
#metadata:
Expand Down
74 changes: 74 additions & 0 deletions k8s/base/postgresql.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
apiVersion: v1
kind: PersistentVolume
metadata:
name: postgresdb-persistent-volume
namespace: postgres
labels:
type: local
app: postgresdb
spec:
storageClassName: manual
capacity:
storage: 2Gi
accessModes:
- ReadWriteMany
hostPath:
path: "/data/db"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: db-persistent-volume-claim
namespace: postgres
spec:
storageClassName: manual
accessModes:
- ReadWriteMany
resources:
requests:
storage: 2Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgresdb
namespace: postgres
spec:
replicas: 1
selector:
matchLabels:
app: postgresdb
template:
metadata:
labels:
app: postgresdb
spec:
containers:
- name: postgresdb
image: postgres
ports:
- containerPort: 5432
envFrom:
- secretRef:
name: db-secret-credentials
volumeMounts:
- mountPath: /var/lib/postgres/data
name: db-data
volumes:
- name: db-data
persistentVolumeClaim:
claimName: db-persistent-volume-claim
---
apiVersion: v1
kind: Service
metadata:
name: postgresdb
namespace: postgres
labels:
app: postgresdb
spec:
type: ClusterIP
ports:
- port: 5432
selector:
app: postgresdb
6 changes: 3 additions & 3 deletions k8s/overlays/dev/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ resources:

images:
- name: musiek/runner
newTag: '0.23'
newTag: '0.24'
- name: musiek/mediator
newTag: '0.27'
newTag: '0.28'
- name: musiek/frontend
newTag: '0.04'
newTag: '0.05'

replicas:
- name: runner
Expand Down
12 changes: 12 additions & 0 deletions mediator/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ val jacksonVersion: String by project
val http4kVersion: String by project
val googleApiClientVersion: String by project
val javaJwtVersion: String by project
val flywayVersion: String by project
val postgresqlVersion: String by project
val hikariVersion: String by project
val exposedVersion: String by project

plugins {
kotlin("jvm") version "1.8.10"
Expand Down Expand Up @@ -51,6 +55,14 @@ dependencies {
implementation("ch.qos.logback.contrib:logback-json-classic:0.1.5")
implementation("ch.qos.logback.contrib:logback-jackson:0.1.5")
implementation("com.fasterxml.jackson.core:jackson-databind:$jacksonVersion")

// Database
implementation("org.flywaydb:flyway-core:$flywayVersion")
implementation("org.postgresql:postgresql:$postgresqlVersion")
implementation("com.zaxxer:HikariCP:$hikariVersion")
implementation("org.jetbrains.exposed:exposed-core:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-dao:$exposedVersion")
implementation("org.jetbrains.exposed:exposed-jdbc:$exposedVersion")
}

tasks.getByName<Test>("test") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import org.http4k.core.Response
import org.http4k.core.Status
import org.http4k.format.Jackson
import pl.edu.prz.kod.common.adapters.http.dto.ErrorResponse
import pl.edu.prz.kod.mediator.domain.ExecuteRequestResult
import pl.edu.prz.kod.mediator.domain.result.ExecuteRequestResult

class ErrorHandler {
private val errorResponseLens = Jackson.autoBody<ErrorResponse>().toLens()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ data class TokenAssignedEvent(val email: String) : Event
data class TokenVerifiedEvent(val email: String) : Event
data class ExecutionFailedEvent(val message: String) : Event
data class ExceptionEvent(val exception: Throwable) : Event
data class LogInsertedEvent(val email: String) : Event
data class FailedToInsertLogEvent(val email: String) : Event
data class ApplicationStartedEvent(val port: Int, val message: String = "Application started") : Event
Loading

0 comments on commit 76f2b7b

Please sign in to comment.