From 681dab864c27162e688b2dac29391d5da9174865 Mon Sep 17 00:00:00 2001 From: Cristian Branet Date: Sun, 22 Jun 2025 10:00:24 +0300 Subject: [PATCH] Added Kubernetes deployment files (#2719) --- kubernetes-deployment/README.md | 38 +++++++++++ .../backend/backend-deployment.yaml | 63 +++++++++++++++++++ .../backend/backend-secrets.yaml | 44 +++++++++++++ .../backend/backend-service.yaml | 16 +++++ .../frontend/frontend-deployment.yaml | 50 +++++++++++++++ .../frontend/frontend-secrets.yaml | 14 +++++ .../frontend/frontend-service.yaml | 16 +++++ kubernetes-deployment/ingress.yaml | 40 ++++++++++++ kubernetes-deployment/k8s-deploy.sh | 29 +++++++++ kubernetes-deployment/namespace.yaml | 4 ++ .../postgres/postgres-deployment.yaml | 37 +++++++++++ .../postgres/postgres-secrets.yaml | 11 ++++ .../postgres/postgres-service.yaml | 12 ++++ .../postgres/postgres-storage.yaml | 11 ++++ 14 files changed, 385 insertions(+) create mode 100644 kubernetes-deployment/README.md create mode 100644 kubernetes-deployment/backend/backend-deployment.yaml create mode 100644 kubernetes-deployment/backend/backend-secrets.yaml create mode 100644 kubernetes-deployment/backend/backend-service.yaml create mode 100644 kubernetes-deployment/frontend/frontend-deployment.yaml create mode 100644 kubernetes-deployment/frontend/frontend-secrets.yaml create mode 100644 kubernetes-deployment/frontend/frontend-service.yaml create mode 100644 kubernetes-deployment/ingress.yaml create mode 100755 kubernetes-deployment/k8s-deploy.sh create mode 100644 kubernetes-deployment/namespace.yaml create mode 100644 kubernetes-deployment/postgres/postgres-deployment.yaml create mode 100644 kubernetes-deployment/postgres/postgres-secrets.yaml create mode 100644 kubernetes-deployment/postgres/postgres-service.yaml create mode 100644 kubernetes-deployment/postgres/postgres-storage.yaml diff --git a/kubernetes-deployment/README.md b/kubernetes-deployment/README.md new file mode 100644 index 00000000..651574b0 --- /dev/null +++ b/kubernetes-deployment/README.md @@ -0,0 +1,38 @@ +# Skyvern Kubernetes Deployment + +## REMINDER: It is not recommended to deploy Skyvern on the Internet without using some form of authentication! It is recommended to use this deployment for network without exposure to the Internet. + +## General +This README has the purpose to explain the way Skyvern is deployed using Kubernetes. + +One should take into consideration how it wants to deploy the application, either by using a service type LoadBalancer, which directly exposes port 8000 and 8080 on the host IP or by using ClusterIP service type, which requires an ingress and consequently a domain name. + +This latter results in having the following endpoints: +> FRONTEND: http(s)://skyvern.example.com/ + +> BACKEND: http(s)://skyvern.example.com/api/ + +There is also a simple deploy script called `k8s-deploy.sh`, which runs the necessary commands to create the namespace and apply the `yaml` files. + +If you look to redeploy from zero, make sure to delete the folders created on the hosts: + +``` +rm -rf /app/ /data/ +``` + +## Environment variables + +Environment variables must be set before running. So, before this type of deployment, the user is recommended to do the initial Skyvern setup to generate the backend's `.env` file, then copy the values from it to `backend-secrets.yaml` and add or either removed unused variables. You also have to replace the values in the `frontend-secrets.yaml` where needed + +For the SKYVERN_API_KEY, run initially without setting any value, then copy the correct value from the `application frontend > settings > copy API key` and add it to the secrets files. Then, run `./k8s-deploy` again. If changes don't apply, delete the pods using: + +``` +kubectl delete pod -n skyvern -l app=skyvern-frontend +kubectl delete pod -n skyvern -l app=skyvern-backend +``` + +## TLS + +If you decide to use TLS, uncomment the lines from the `ingress.yaml` related to it and replace with your own values, also make sure you modify the values in the `frontend-secrets.yaml` where https needs to be used instead of http. + +> This is a basic K8s deployment of Skyvern and can be successfully used to create and run workflows. Further improvements may be made considering the use of the ports 9222 and 9090, improving the deployment scripts and integrating it with existing ones, etc. \ No newline at end of file diff --git a/kubernetes-deployment/backend/backend-deployment.yaml b/kubernetes-deployment/backend/backend-deployment.yaml new file mode 100644 index 00000000..da1de545 --- /dev/null +++ b/kubernetes-deployment/backend/backend-deployment.yaml @@ -0,0 +1,63 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: skyvern-backend + namespace: skyvern +spec: + replicas: 1 + selector: + matchLabels: + app: skyvern-backend + template: + metadata: + labels: + app: skyvern-backend + spec: + containers: + - name: skyvern-backend + image: public.ecr.aws/skyvern/skyvern:latest + ports: + - containerPort: 8000 + - containerPort: 9222 + envFrom: + - secretRef: + name: skyvern-backend-env + volumeMounts: + - name: artifacts + mountPath: /data/artifacts + - name: videos + mountPath: /data/videos + - name: har + mountPath: /data/har + - name: log + mountPath: /data/log + - name: streamlit + mountPath: /app/.streamlit + readinessProbe: + exec: + command: ["test", "-f", "/app/.streamlit/secrets.toml"] + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 5 + volumes: + - name: artifacts + hostPath: + path: /data/artifacts + type: DirectoryOrCreate + - name: videos + hostPath: + path: /data/videos + type: DirectoryOrCreate + - name: har + hostPath: + path: /data/har + type: DirectoryOrCreate + - name: log + hostPath: + path: /data/log + type: DirectoryOrCreate + - name: streamlit + hostPath: + path: /app/.streamlit + type: DirectoryOrCreate \ No newline at end of file diff --git a/kubernetes-deployment/backend/backend-secrets.yaml b/kubernetes-deployment/backend/backend-secrets.yaml new file mode 100644 index 00000000..635aefe2 --- /dev/null +++ b/kubernetes-deployment/backend/backend-secrets.yaml @@ -0,0 +1,44 @@ +apiVersion: v1 +kind: Secret +metadata: + name: skyvern-backend-env + namespace: skyvern +type: Opaque +stringData: + # Environment variables for the Skyvern backend + # You can use the ones from the .env file in the root of skyvern + ENV: local + ENABLE_OPENAI: "" + OPENAI_API_KEY: "" + ENABLE_ANTHROPIC: "false" + ANTHROPIC_API_KEY: "" + ENABLE_AZURE: "false" + AZURE_DEPLOYMENT: "" + AZURE_API_KEY: "" + AZURE_API_BASE: "" + AZURE_API_VERSION: "" + ENABLE_AZURE_GPT4O_MINI: "false" + AZURE_GPT4O_MINI_DEPLOYMENT: "" + AZURE_GPT4O_MINI_API_KEY: "" + AZURE_GPT4O_MINI_API_BASE: "" + AZURE_GPT4O_MINI_API_VERSION: "" + ENABLE_GEMINI: "false" + GEMINI_API_KEY: "" + ENABLE_NOVITA: "false" + NOVITA_API_KEY: "" + LLM_KEY: OPENAI_GPT4_1 + SECONDARY_LLM_KEY: "" + BROWSER_TYPE: chromium-headless + MAX_SCRAPING_RETRIES: "0" + VIDEO_PATH: ./videos + BROWSER_ACTION_TIMEOUT_MS: "5000" + MAX_STEPS_PER_RUN: "50" + LOG_LEVEL: INFO + LITELLM_LOG: CRITICAL + DATABASE_STRING: postgresql+psycopg://skyvern:skyvern@postgres/skyvern + PORT: "8000" + ANALYTICS_ID: "" + ENABLE_LOG_ARTIFACTS: "false" + ENABLE_OPENAI_COMPATIBLE: "false" + SKYVERN_BASE_URL: http://localhost:8000 + SKYVERN_API_KEY: "" \ No newline at end of file diff --git a/kubernetes-deployment/backend/backend-service.yaml b/kubernetes-deployment/backend/backend-service.yaml new file mode 100644 index 00000000..1b1e0cb3 --- /dev/null +++ b/kubernetes-deployment/backend/backend-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: skyvern-backend + namespace: skyvern +spec: + type: ClusterIP # Or LoadBalancer + ports: + - name: http + port: 8000 + targetPort: 8000 + - name: cdp + port: 9222 + targetPort: 9222 + selector: + app: skyvern-backend \ No newline at end of file diff --git a/kubernetes-deployment/frontend/frontend-deployment.yaml b/kubernetes-deployment/frontend/frontend-deployment.yaml new file mode 100644 index 00000000..ed1bc021 --- /dev/null +++ b/kubernetes-deployment/frontend/frontend-deployment.yaml @@ -0,0 +1,50 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: skyvern-frontend + namespace: skyvern +spec: + replicas: 1 + selector: + matchLabels: + app: skyvern-frontend + template: + metadata: + labels: + app: skyvern-frontend + spec: + containers: + - name: skyvern-frontend + image: public.ecr.aws/skyvern/skyvern-ui:latest + ports: + - containerPort: 8080 + - containerPort: 9090 + envFrom: + - secretRef: + name: skyvern-frontend-env + volumeMounts: + - name: artifacts + mountPath: /data/artifacts + - name: videos + mountPath: /data/videos + - name: har + mountPath: /data/har + - name: streamlit + mountPath: /app/.streamlit + volumes: + - name: artifacts + hostPath: + path: /data/artifacts + type: DirectoryOrCreate + - name: videos + hostPath: + path: /data/videos + type: DirectoryOrCreate + - name: har + hostPath: + path: /data/har + type: DirectoryOrCreate + - name: streamlit + hostPath: + path: /app/.streamlit + type: DirectoryOrCreate \ No newline at end of file diff --git a/kubernetes-deployment/frontend/frontend-secrets.yaml b/kubernetes-deployment/frontend/frontend-secrets.yaml new file mode 100644 index 00000000..eb5f295e --- /dev/null +++ b/kubernetes-deployment/frontend/frontend-secrets.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Secret +metadata: + name: skyvern-frontend-env + namespace: skyvern +type: Opaque +stringData: + # You need to change the values below to match your environment + VITE_API_BASE_URL: http://skyvern.example.com/api/v1 + VITE_ARTIFACT_API_BASE_URL: http://skyvern.example.com/artifacts + VITE_WSS_BASE_URL: ws://skyvern.example.com/api/v1 + VITE_SKYVERN_API_KEY: "" + VITE_ENABLE_LOG_ARTIFACTS: "false" + VITE_ENABLE_CODE_BLOCK: "false" \ No newline at end of file diff --git a/kubernetes-deployment/frontend/frontend-service.yaml b/kubernetes-deployment/frontend/frontend-service.yaml new file mode 100644 index 00000000..ded2b76b --- /dev/null +++ b/kubernetes-deployment/frontend/frontend-service.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +kind: Service +metadata: + name: skyvern-frontend + namespace: skyvern +spec: + type: ClusterIP # Or LoadBalancer + ports: + - name: http + port: 8080 + targetPort: 8080 + - name: artifact + port: 9090 + targetPort: 9090 + selector: + app: skyvern-frontend \ No newline at end of file diff --git a/kubernetes-deployment/ingress.yaml b/kubernetes-deployment/ingress.yaml new file mode 100644 index 00000000..4f014345 --- /dev/null +++ b/kubernetes-deployment/ingress.yaml @@ -0,0 +1,40 @@ +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: skyvern-ingress + namespace: skyvern + annotations: + # You may need to change the ingress data to match your environment + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" +spec: + ingressClassName: traefik + rules: + - host: skyvern.example.com + http: + paths: + - path: /api + pathType: Prefix + backend: + service: + name: skyvern-backend + port: + number: 8000 + - path: /artifacts + pathType: Prefix + backend: + service: + name: skyvern-frontend + port: + number: 9090 + - path: / + pathType: Prefix + backend: + service: + name: skyvern-frontend + port: + number: 8080 + #tls: + # - hosts: + # - skyvern.example.com + # secretName: "" \ No newline at end of file diff --git a/kubernetes-deployment/k8s-deploy.sh b/kubernetes-deployment/k8s-deploy.sh new file mode 100755 index 00000000..6e5054c6 --- /dev/null +++ b/kubernetes-deployment/k8s-deploy.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -e + +NAMESPACE=skyvern + +echo "Creating namespace..." +kubectl apply -f namespace.yaml + +echo "Deploying Postgres..." +kubectl apply -f postgres/postgres-secrets.yaml -n $NAMESPACE +kubectl apply -f postgres/postgres-storage.yaml -n $NAMESPACE +kubectl apply -f postgres/postgres-deployment.yaml -n $NAMESPACE +kubectl apply -f postgres/postgres-service.yaml -n $NAMESPACE + +echo "Deploying Skyvern Backend..." +kubectl apply -f backend/backend-secrets.yaml -n $NAMESPACE +kubectl apply -f backend/backend-deployment.yaml -n $NAMESPACE +kubectl apply -f backend/backend-service.yaml -n $NAMESPACE + +echo "Deploying Skyvern Frontend..." +kubectl apply -f frontend/frontend-secrets.yaml -n $NAMESPACE +kubectl apply -f frontend/frontend-deployment.yaml -n $NAMESPACE +kubectl apply -f frontend/frontend-service.yaml -n $NAMESPACE + +echo "Deploying Ingress..." +kubectl apply -f ingress.yaml -n $NAMESPACE + +echo "Deployment complete!" \ No newline at end of file diff --git a/kubernetes-deployment/namespace.yaml b/kubernetes-deployment/namespace.yaml new file mode 100644 index 00000000..f2b6a1f9 --- /dev/null +++ b/kubernetes-deployment/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: skyvern \ No newline at end of file diff --git a/kubernetes-deployment/postgres/postgres-deployment.yaml b/kubernetes-deployment/postgres/postgres-deployment.yaml new file mode 100644 index 00000000..52b6ba12 --- /dev/null +++ b/kubernetes-deployment/postgres/postgres-deployment.yaml @@ -0,0 +1,37 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres + namespace: skyvern +spec: + replicas: 1 + selector: + matchLabels: + app: postgres + template: + metadata: + labels: + app: postgres + spec: + containers: + - name: postgres + image: postgres:14-alpine + ports: + - containerPort: 5432 + envFrom: + - secretRef: + name: postgres-secrets + volumeMounts: + - name: postgres-data + mountPath: /var/lib/postgresql/data + readinessProbe: + exec: + command: ["pg_isready", "-U", "skyvern"] + initialDelaySeconds: 5 + periodSeconds: 5 + timeoutSeconds: 5 + failureThreshold: 5 + volumes: + - name: postgres-data + persistentVolumeClaim: + claimName: postgres-data \ No newline at end of file diff --git a/kubernetes-deployment/postgres/postgres-secrets.yaml b/kubernetes-deployment/postgres/postgres-secrets.yaml new file mode 100644 index 00000000..a7ae471c --- /dev/null +++ b/kubernetes-deployment/postgres/postgres-secrets.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: postgres-secrets + namespace: skyvern +type: Opaque +stringData: + PGDATA: /var/lib/postgresql/data/pgdata + POSTGRES_USER: skyvern + POSTGRES_PASSWORD: skyvern + POSTGRES_DB: skyvern \ No newline at end of file diff --git a/kubernetes-deployment/postgres/postgres-service.yaml b/kubernetes-deployment/postgres/postgres-service.yaml new file mode 100644 index 00000000..2005292c --- /dev/null +++ b/kubernetes-deployment/postgres/postgres-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres + namespace: skyvern +spec: + type: ClusterIP + ports: + - port: 5432 + targetPort: 5432 + selector: + app: postgres \ No newline at end of file diff --git a/kubernetes-deployment/postgres/postgres-storage.yaml b/kubernetes-deployment/postgres/postgres-storage.yaml new file mode 100644 index 00000000..ae8a263c --- /dev/null +++ b/kubernetes-deployment/postgres/postgres-storage.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: postgres-data + namespace: skyvern +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi \ No newline at end of file