Running Drone on Kubernetes for the Raspberry Pi

In this post, we will deploy the Drone Server on Kubernetes, which will be based on ARM since my Kubernetes Cluster is running on Raspberry Pi's using k3s.

Gitea

I will be using Gitea with Drone. I already have a Gitea Server running, if you don't and you want to follow along, you can visit Gitea's Documentation to get your server up and running.

So the first thing we need to do is to create a new OAuth2 Application on Gitea.

From Gitea, hit "Settings", then select "Applications", then select, "Create a new OAuth2 Application", provide an identifiable name, and the redirect URI, which should be to your Drone URL + /login

image

You will receive a Client ID and Client Secret, keep this in a safe place for now.

Kubernetes Secrets

I will be creating 3 secrets:

  • DRONE_RPC_SECRET
  • DRONE_GITEA_CLIENT_ID
  • DRONE_GITEA_CLIENT_SECRET

First we need to generate a RPC Secret:

$ openssl rand -hex 16
7dfda1f55c255194f723cd3bae9b83fd  

There's multiple ways to create secrets, but I will write the values to file, then create the secrets from the files:

$ echo -n "7dfda1f55c255194f723cd3bae9b83fd" > env.DRONE_RPC_SECRET
$ echo -n "my-gitea-client-id-xxxxxxxxxxxxx" > env.DRONE_GITEA_CLIENT_ID
$ echo -n "my-gitea-client-secret-xxxxxxxxx" > env.DRONE_GITEA_CLIENT_SECRET

Now that we've written the values to our files, we can create the secrets:

$ kubectl create secret generic drone-rpc-secret --from-file=env.DRONE_RPC_SECRET="env.DRONE_RPC_SECRET"

$ kubectl create secret generic drone-gitea-client-id --from-file=env.DRONE_GITEA_CLIENT_SECRET="env.DRONE_GITEA_CLIENT_ID"

$ kubectl create secret generic drone-gitea-client-secret --from-file=env.DRONE_GITEA_CLIENT_SECRET="env.DRONE_GITEA_CLIENT_SECRET"

Kubernetes Deployment

Our persistent volume using NFS:

---
apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: drone-pv-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
  labels:
    app: drone-server
spec:  
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi

Our Deployment, here you will see we are referencing our secret's as environment variables:

---
apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: drone-server
  labels:
    app: drone-server
    category: cicd
spec:  
  selector:
    matchLabels:
      app: drone-server
      tier: drone-server
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: drone-server
        tier: drone-server
    spec:
      containers:
      - image: drone/drone:1.9-linux-arm
        name: drone-server
        env:
        - name: DRONE_LOGS_TRACE
          value: "true"
        - name: DRONE_ADMIN
          value: "gitadmin"
        - name: DRONE_USER_CREATE
          value: "username:gitadmin,admin:true"
        - name: DRONE_SERVER_PORT
          value: ":80"
        - name: DRONE_DATABASE_DRIVER
          value: sqlite3
        - name: DRONE_DATABASE_DATASOURCE
          value: /var/lib/drone/database.sqlite
        - name: DRONE_GIT_ALWAYS_AUTH
          value: "true"
        - name: DRONE_GITEA_SERVER
          value: "https://gitea.my-domain.com"
        - name: DRONE_RPC_SECRET
          valueFrom:
            secretKeyRef:
              name: drone-rpc-secret
              key: env.DRONE_RPC_SECRET
        - name: DRONE_SERVER_HOST
          value: "drone.my-domain.com"
        - name: DRONE_HOST
          value: "http://drone-server:80"
        - name: DRONE_SERVER_PROTO
          value: "https"
        - name: DRONE_TLS_AUTOCERT
          value: "false"
        - name: DRONE_AGENTS_ENABLED
          value: "true"
        - name: DRONE_GITEA_CLIENT_ID
          valueFrom:
            secretKeyRef:
              name: drone-gitea-client-id
              key: env.DRONE_GITEA_CLIENT_ID
        - name: DRONE_GITEA_CLIENT_SECRET
          valueFrom:
            secretKeyRef:
              name: drone-gitea-client-secret
              key: env.DRONE_GITEA_CLIENT_SECRET
        ports:
        - containerPort: 80
          name: drone-server
        volumeMounts:
        - name: drone-server-persistent-storage
          mountPath: /var/lib/drone
      volumes:
      - name: drone-server-persistent-storage
        persistentVolumeClaim:
          claimName: drone-pv-claim

Next our service and ingress:

---
apiVersion: v1  
kind: Service  
metadata:  
  name: drone-service
  labels:
    app: drone-server
    category: cicd
spec:  
  ports:
  - name: drone-http
    port: 80
    targetPort: 80
  selector:
    app: drone-server
---
apiVersion: extensions/v1beta1  
kind: Ingress  
metadata:  
  name: drone-ingress
  namespace: default
  labels:
    app: drone-server
    category: cicd
  annotations:
    kubernetes.io/ingress.class: traefik
    ingress.kubernetes.io/ssl-redirect: "true"
    traefik.backend.loadbalancer.stickiness: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:  
  tls:
    - secretName: drone-my-domain-com-tls
      hosts:
        - drone.my-domain.com
  rules:
  - host: drone.my-domain.com
    http:
      paths:
      - path: /
        backend:
          serviceName: drone-service
          servicePort: drone-http

And our final concatenated deployment manifest will look like this:

---
apiVersion: v1  
kind: PersistentVolumeClaim  
metadata:  
  name: drone-pv-claim
  annotations:
    volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
  labels:
    app: drone-server
spec:  
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
---
apiVersion: v1  
kind: Service  
metadata:  
  name: drone-service
  labels:
    app: drone-server
    category: cicd
spec:  
  ports:
  - name: drone-http
    port: 80
    targetPort: 80
  selector:
    app: drone-server
---
apiVersion: apps/v1  
kind: Deployment  
metadata:  
  name: drone-server
  labels:
    app: drone-server
    category: cicd
spec:  
  selector:
    matchLabels:
      app: drone-server
      tier: drone-server
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: drone-server
        tier: drone-server
    spec:
      containers:
      - image: drone/drone:1.9-linux-arm
        name: drone-server
        env:
        # https://docs.drone.io/server/reference/
        - name: DRONE_LOGS_TRACE
          value: "true"
        - name: DRONE_ADMIN
          value: "gitadmin"
        - name: DRONE_USER_CREATE
          value: "username:gitadmin,admin:true"
        - name: DRONE_SERVER_PORT
          value: ":80"
        - name: DRONE_DATABASE_DRIVER
          value: sqlite3
          # https://docs.drone.io/installation/reference/drone-database-driver/
        - name: DRONE_DATABASE_DATASOURCE
          value: /var/lib/drone/database.sqlite
        # https://discourse.drone.io/t/fatal-could-not-read-username-for/6198
        - name: DRONE_GIT_ALWAYS_AUTH
          value: "true"
        - name: DRONE_GITEA_SERVER
          value: "https://gitea.my-domain.com"
        - name: DRONE_RPC_SECRET
          valueFrom:
            secretKeyRef:
              name: drone-rpc-secret
              key: env.DRONE_RPC_SECRET
        - name: DRONE_SERVER_HOST
          value: "drone.my-domain.com"
        - name: DRONE_HOST
          value: "http://drone-server:80"
        - name: DRONE_SERVER_PROTO
          value: "https"
        - name: DRONE_TLS_AUTOCERT
          value: "false"
        - name: DRONE_AGENTS_ENABLED
          value: "true"
        - name: DRONE_GITEA_CLIENT_ID
          valueFrom:
            secretKeyRef:
              name: drone-gitea-client-id
              key: env.DRONE_GITEA_CLIENT_ID
        - name: DRONE_GITEA_CLIENT_SECRET
          valueFrom:
            secretKeyRef:
              name: drone-gitea-client-secret
              key: env.DRONE_GITEA_CLIENT_SECRET
        ports:
        - containerPort: 80
          name: drone-server
        volumeMounts:
        - name: drone-server-persistent-storage
          mountPath: /var/lib/drone
      volumes:
      - name: drone-server-persistent-storage
        persistentVolumeClaim:
          claimName: drone-pv-claim
---
apiVersion: extensions/v1beta1  
kind: Ingress  
metadata:  
  name: drone-ingress
  namespace: default
  labels:
    app: drone-server
    category: cicd
  annotations:
    kubernetes.io/ingress.class: traefik
    ingress.kubernetes.io/ssl-redirect: "true"
    traefik.backend.loadbalancer.stickiness: "true"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:  
  tls:
    - secretName: drone-my-domain-com-tls
      hosts:
        - drone.my-domain.com
  rules:
  - host: drone.my-domain.com
    http:
      paths:
      - path: /
        backend:
          serviceName: drone-service
          servicePort: drone-http

Deploy

Deploy Drone Server to your Kubernetes Cluster:

$ kubectl apply -f drone-server-deployment.yml

After some time verify that the deployment has checked into it's desired state:

$ kubectl get deployments -l app=drone-server
NAME           READY   UP-TO-DATE   AVAILABLE   AGE  
drone-server   1/1     1            1           1d  

Access Drone

To access drone, we need to authorize drone with our configured oauth2 application that we created.

Access drone on the configured url, in this demonstration (drone.my-domain.com) then it will redirect to Gitea to login with your Gitea credentials and once successful authentication you will be redirected back to drone and you should be logged on.

In the next post we will setup a example CI pipeline with Drone.