Kubernetes

This guide will take you through the process of setting up Funnel as a kubernetes service.

Kuberenetes Resources:

Additional Funnel deployment resources can be found here: https://github.com/ohsu-comp-bio/funnel/tree/master/deployments/kubernetes

Create a Service:

funnel-service.yml

apiVersion: v1
kind: Service
metadata:
  name: funnel
spec:
  selector:
    app: funnel
  ports:
    - name: http
      protocol: TCP
      port: 8000
      targetPort: 8000
    - name: rpc
      protocol: TCP
      port: 9090
      targetPort: 9090

Deploy it:

kubectl apply -f funnel-service.yml

Get the clusterIP:

kubectl get services funnel --output=yaml | grep clusterIP

Use this value to configure the server hostname of the worker config.

Create Funnel config files

Note: The configures job template uses the image, ohsucompbio/funnel-dind:latest, which is built on docker’s official docker-in-docker image (dind). You can also use the experimental rootless dind variant by changing the image to ohsucompbio/funnel-dind-rootless:latest.

funnel-server-config.yml

Database: boltdb

Compute: kubernetes

Logger:
  Level: debug

Kubernetes:
  DisableJobCleanup: false
  DisableReconciler: false
  ReconcileRate: 5m
  Namespace: default
  Template: | 
    apiVersion: batch/v1
    kind: Job
    metadata:
      ## DO NOT CHANGE NAME
      name: {{.TaskId}}
      namespace: {{.Namespace}}
    spec: 
      backoffLimit: 0
      completions: 1
      template:
        spec:
          restartPolicy: Never
          containers: 
            - name: {{printf "funnel-worker-%s" .TaskId}}
              image: ohsucompbio/funnel-dind:latest
              imagePullPolicy: IfNotPresent
              args:
                - "funnel"
                - "worker"
                - "run"
                - "--config"
                - "/etc/config/funnel-worker-config.yml"
                - "--taskID"
                - {{.TaskId}}
              resources:
                  requests:
                    cpu: {{if ne .Cpus 0 -}}{{.Cpus}}{{ else }}{{"100m"}}{{end}}
                    memory: {{if ne .RamGb 0.0 -}}{{printf "%.0fG" .RamGb}}{{else}}{{"16M"}}{{end}}
                    ephemeral-storage: {{if ne .DiskGb 0.0 -}}{{printf "%.0fG" .DiskGb}}{{else}}{{"100M"}}{{end}}
              volumeMounts:
                - name: {{printf "funnel-storage-%s" .TaskId}}
                  mountPath: {{printf "/opt/funnel/funnel-work-dir/%s" .TaskId}}
                - name: config-volume
                  mountPath: /etc/config

              securityContext:
                privileged: true
    
          volumes: 
            - name: {{printf "funnel-storage-%s" .TaskId}}
              emptyDir: {}
            - name: config-volume
              configMap:
                name: funnel-config

I recommend setting DisableJobCleanup to true for debugging - otherwise failed jobs will be cleanup up.

funnel-worker-config.yml

Remember to modify the template below to have the actual server hostname.

Database: boltdb

BoltDB:
  Path: /opt/funnel/funnel-work-dir/funnel.bolt.db

Compute: kubernetes

Logger:
  Level: debug

RPCClient:
  MaxRetries: 3
  Timeout: 30s

EventWriters:
  - rpc
  - log

Server:
  HostName: < funnel service clusterIP >
  RPCPort: 9090

Create a ConfigMap

kubectl create configmap funnel-config --from-file=funnel-server-config.yml --from-file=funnel-worker-config.yml

Create a Service Account for Funnel

Define a Role and RoleBinding:

role.yml

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: default
  name: funnel-role
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
- apiGroups: ["batch", "extensions"]
  resources: ["jobs"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["extensions", "apps"]
  resources: ["deployments"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

role_binding.yml

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: funnel-rolebinding
  namespace: default
subjects:
- kind: ServiceAccount
  name: funnel-sa
roleRef:
  kind: Role
  name: funnel-role
  apiGroup: rbac.authorization.k8s.io

Create the service account, role and role binding:

kubectl create serviceaccount funnel-sa --namespace default
kubectl create -f role.yml
kubectl create -f role_binding.yml

Create a Deployment

funnel-deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: funnel
  labels:
    app: funnel
spec:
  replicas: 1
  selector:
    matchLabels:
      app: funnel
  template:
    metadata:
      labels:
        app: funnel
    spec:
      serviceAccountName: funnel-sa
      containers:
        - name: funnel
          image: ohsucompbio/funnel:latest
          imagePullPolicy: IfNotPresent
          command: 
            - 'funnel'
            - 'server'
            - 'run'
            - '--config'
            - '/etc/config/funnel-server-config.yml'
          resources: 
            requests: 
              cpu: 2 
              memory: 4G
              ephemeral-storage: 25G # needed since we are using boltdb
          volumeMounts:
            - name: funnel-deployment-storage
              mountPath: /opt/funnel/funnel-work-dir
            - name: config-volume
              mountPath: /etc/config
          ports:
            - containerPort: 8000
            - containerPort: 9090

      volumes:
        - name: funnel-deployment-storage
          emptyDir: {}
        - name: config-volume
          configMap:
            name: funnel-config

Deploy it:

kubectl apply -f funnel-deployment.yml

Proxy the Service for local testing

kubectl port-forward service/funnel 8000:8000

Now you can access the funnel server locally. Verify by running:

funnel task list

Now try running a task:

funnel examples hello-world > hello.json
funnel task create hello.json