AGAIN: NOT A GUIDE, BUT MAY BE HELPFULL TO......SOMEONE.....MAYBE.

I left this portion out from my docker/portainer to kubernetes/rancher post because it seemed (and still does) more complicated and warranted its own post. The reading time will probably be lengthy because of all the yml files, but hey ho, let's go.

So, as always, I have a semblence of a plan that I'll probably not follow to the letter. Step 1, pick a docker-compose.yml from my collection to migrate over. Easy, I pick bookstack as that's what I use for my internal notes and stuff. Here's the file.

version: "2"
services:
  bookstack:
    image: linuxserver/bookstack
    container_name: bookstack
    environment:
      - PUID=1000
      - PGID=1000
      - DB_HOST=bookstack_db
      - DB_USER=bookstack
      - DB_PASS=<PASSWORD>
      - DB_DATABASE=bookstackapp
    volumes:
      - bookstack:/config
    ports:
      - 6875:80
    restart: unless-stopped
    depends_on:
      - bookstack_db
  bookstack_db:
    image: linuxserver/mariadb
    container_name: bookstack_db
    environment:
      - PUID=1000
      - PGID=1000
      - MYSQL_ROOT_PASSWORD=<PASSWORD>
      - TZ=Europe/London
      - MYSQL_DATABASE=bookstackapp
      - MYSQL_USER=bookstack
      - MYSQL_PASSWORD=<PASSWORD>
    volumes:
      - bookstack:/config
    restart: unless-stopped

Pretty much copied exactly from the fine folks over at linuxserver.io. For those unfamiliar with docker-compose....well, wrong post. For those familiar with docker-compose, you'll see that there are 2 distinct things going on here. The actual bookstack server, and a mariadb instance for storing the data. There's also the small matter of persistant storage in the form of the shared volume.

Step 2. What I would like is to magically convert this to a file/a collection of files to pump into rancher and have everything just work. I don't think this is fully possible (for my specific weirdness), but there's a tool that  will get us part of the way. Introducing Kompose. Easy to install by following the instructions on that page so I'll skip over that.

With it installed, we need to dump a copy of the docker-compose.yml file we want to convert somewhere we can work on it. Then, in the same directory as that file, run

$ kompose convert

In my instance, it complained about the restart policy, but still made 4 files for us to look at. Here's the directory:

deploy@webdev: ~/docker-compose-files/bookstack
$ tree
.
├── bookstack-db-deployment.yaml
├── bookstack-deployment.yaml
├── bookstack-persistentvolumeclaim.yaml
├── bookstack-service.yaml
└── docker-compose.yml

0 directories, 5 files

And here's what each generated file looks like:

bookstack-db-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack-db
  name: bookstack-db
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack-db
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack-db
    spec:
      containers:
        - env:
            - name: MYSQL_DATABASE
              value: bookstackapp
            - name: MYSQL_PASSWORD
              value: <PASSWORD>
            - name: MYSQL_ROOT_PASSWORD
              value: <PASSWORD>
            - name: MYSQL_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
            - name: TZ
              value: Europe/London
          image: linuxserver/mariadb
          name: bookstack-db
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack
      restartPolicy: Always
      volumes:
        - name: bookstack
          persistentVolumeClaim:
            claimName: bookstack
status: {}

bookstack-deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack
    spec:
      containers:
        - env:
            - name: DB_DATABASE
              value: bookstackapp
            - name: DB_HOST
              value: bookstack_db
            - name: DB_PASS
              value: <PASSWORD>
            - name: DB_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
          image: linuxserver/bookstack
          name: bookstack
          ports:
            - containerPort: 80
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack
      restartPolicy: Always
      volumes:
        - name: bookstack
          persistentVolumeClaim:
            claimName: bookstack
status: {}

bookstack-persistentvolumeclaim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 100Mi
status: {}

bookstack-service.yaml

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  ports:
    - name: "6875"
      port: 6875
      targetPort: 80
  selector:
    io.kompose.service: bookstack
status:
  loadBalancer: {}

Well. That's a lotta yaml. BUT with the structure split like this, it has made things simpler for me to understand. The way I see it, we have a seperate deployment file for the bookstack app itself, and the associated mariadb. Then we have another file for setting up the persistant volume. Finally, the service file contains our port mapping.

Ok, yeah, see, things are now lining up. The first thing I want to do is modify the persistant volume claim because I know I want to use longhorn to store the data. According to the official documentation, a simple claim looks like this.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: longhorn-simple-pvc
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 1Gi

This looks very similar to the file dumped out by Kompose. I'll make some changes to the bookstack-persistentvolumeclaim.yaml file so it looks like this:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi
status: {}

Easy enough. Next up, the db deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack-db
  name: bookstack-db
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack-db
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack-db
    spec:
      containers:
        - env:
            - name: MYSQL_DATABASE
              value: bookstackapp
            - name: MYSQL_PASSWORD
              value: <PASSWORD>
            - name: MYSQL_ROOT_PASSWORD
              value: <PASSWORD>
            - name: MYSQL_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
            - name: TZ
              value: Europe/London
          image: linuxserver/mariadb
          name: bookstack-db
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack-data
      restartPolicy: Always
      volumes:
        - name: bookstack
          persistentVolumeClaim:
            claimName: bookstack-data
status: {}

Ok, all I changed here was the claim names to match what I changed previously. Easy enough. Next up, the bookstack deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack
    spec:
      containers:
        - env:
            - name: DB_DATABASE
              value: bookstackapp
            - name: DB_HOST
              value: bookstack_db
            - name: DB_PASS
              value: <PASSWORD>
            - name: DB_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
          image: linuxserver/bookstack
          name: bookstack
          ports:
            - containerPort: 80
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack-data
      restartPolicy: Always
      volumes:
        - name: bookstack
          persistentVolumeClaim:
            claimName: bookstack-data
status: {}

Ok, yeah, same again. This is feeling too easy. Just changed the volume name again. Obviously the passwords will need changing, but that's a different problem that I'll get back to in a bit.

Final part of the puzze is the service file. From my previous forrays into these shenanigans, I know that I want to access bookstack through a nodeport. Easy enough to find a good example of this to see what changes I need to make. Here's the example. Even has handy comments :D

apiVersion: v1
kind: Service
metadata:
  name: svc-nodeport-httpd
spec:
  type: NodePort
  ports:
    - port: 3050      # This is the port to use by other pods to reach target port
      targetPort: 80     # This is the port the destination pod is listening on.
      nodePort: 31000     # port to use from your macbook
  selector:
    component: httpd   # this service will forward traffic to any pods with this label.

Here's my completed file.

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  type: NodePort
  ports:
    - name: "bookstack-port"
      port: 30060
      targetPort: 80
  selector:
    io.kompose.service: bookstack

The changes I made here were adding type: NodePort, changing the name to match the naming scheme I've been using, providing a nodeport number that's free, and removing "status:  loadBalancer: {}"  from the end, as I don't think I need that.

Ok, finally, I just want to touch on the topic of passwords and how they "should" be stored. Best practice is to use secrets and then pass those secrets as and when needed. However, the secrets stored in rancher seem to only be base64 encoded to prevent them from becoming human readable, but is by no means secure. There is a method to add this in properly, and it doesn't seem overly complicated, but, I'm meandering. The point is, I'm not going to use secrets just yet because my cluster is not exposed to the world and only I have access internally.

So, with these changes, and the files and concatenated into one, and passwords added, I can try deploying it. Let's see what happens.

Go to Rancher UI -> Minions Cluster -> Import YAML

We have our first error! Wooooo.....

persistentvolumeclaim/bookstack-data created
service/bookstack created 

Error from server (Invalid): error when creating "management-state/tmp/yaml-809813384": Deployment.apps "bookstack-db" is invalid: spec.template.spec.containers[0].volumeMounts[0].name: Not found: "bookstack-data" 

Error from server (Invalid): error when creating "management-state/tmp/yaml-809813384": Deployment.apps "bookstack" is invalid: spec.template.spec.containers[0].volumeMounts[0].name: Not found: "bookstack-data"

So, what I'm gathering from this is that the persistant volume claim was created, but for some reason the bookstack app and the db can't find the volumes, meaning it wasn't mounted? Maybe. Bare with.

Ok, so I made shmoll error by not changing the name properly, as the error suggested. Made the volume section of both the app and db look like this:

volumes:
  - name: bookstack-data
    persistentVolumeClaim:
      claimName: bookstack-data

So the db got deployed, but the app itself failed to start. Looking at the event log shows the following:

Warning 	FailedMount 	Unable to attach or mount volumes: unmounted volumes=[bookstack-data], unattached volumes=[bookstack-data default-token-xx4xk]: timed out waiting for the condition 	2 minutes ago

Warning 	FailedMount 	Unable to attach or mount volumes: unmounted volumes=[bookstack-data], unattached volumes=[default-token-xx4xk bookstack-data]: timed out waiting for the condition 	4 minutes ago

Warning 	FailedAttachVolume 	Multi-Attach error for volume "pvc-d9021720-348d-4f79-a7c5-693c8747e6cb" Volume is already used by pod(s) bookstack-db-76d6767d5f-tmwgh 	6 minutes ago

Normal 	Scheduled 	Successfully assigned bookstack/bookstack-7bfd6c558d-rfzck to k8s-minion-03 	6 minutes ago

Warning 	FailedScheduling 	0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims. 	6 minutes ago

The interesting part is "Volume is already used by pod(s) bookstack-db-76d6767d5f-tmwgh". I think I know how to fix this....

In the interest of open-ness, I tried

accessModes:
    - ReadWriteMany

In the persistant volume claim, but that caused the issue where it wouldn't be mounted by anything for some reason. Similar to when I was trying to get Nextcloud up and running in other post. I did find this but I don't think that helps my current situation.

What I think I'll do then, is to create a seperate pvc for the bookstack database, and mount it. However, for some reason, the docker-compose has both the db and app using data that's mounted in /config. Let me just have a quick rummage around the container to see what's actually inside the /config folder.

Ok, so inside the data folder for bookstack, I see the following things:

root@dh2:/var/lib/docker/volumes/bookstack/_data# ls
BOOKSTACK_APP_KEY.txt  custom.cnf  databases  keys  log  nginx  php  www

Maybe if I make a pvc, and mount it to /config/databases ?

Lets give it a go.

Ok, that fixed the pvc issue, I think..... but for some reason my service section is being ignored and the port isn't being mapped correctly. Wonder why......

Ok, I think I got that bit working. Here's the fixed services file.

apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  type: NodePort
  ports:
    - name: bookstack-port
      port: 80
      nodePort: 30060
      protocol: TCP
  selector:
    io.kompose.service: bookstack

I can see it's partially working as the address:nodeport resolves, I get a 504 error, but the bookstack icon appears in the tab......so it's trying. Lets see what the logs say about this issue.

Exception trace:

  1   Doctrine\DBAL\Driver\PDOException::("SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed: Try again")

      /var/www/html/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:31

  2   PDOException::("PDO::__construct(): php_network_getaddresses: getaddrinfo failed: Try again")

      /var/www/html/vendor/doctrine/dbal/lib/Doctrine/DBAL/Driver/PDOConnection.php:27

From what I gather, there's a failure in connecting to the db.....why tho?


Figured it out :3

Answer was here. What did I learn? Communication between anything within a deployment requires a service. Not just when you're trying to expose stuff. I needed to add one more service in my case for the communication between the app and the db. Here it is.

apiVersion: v1
kind: Service
metadata:
  name: bookstack-db
spec:
  selector:
    io.kompose.service: bookstack-db
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

The important thing here is the io.kompose.service: bookstack-db line. That tells the service what it's for. I also amended the pvc and the db deployments to use the same label. Finally, a small aside. You will also want to add the following as an environment variable so everything is displayed correctly if you're going to be doing any reverse proxy/ssl stuff.

- name: APP_URL
  value: "https://fqdmn"
:D

For completeness, here is the full deployment in one file that I easily imported into Rancher.

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack-db
  name: bookstack-db
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi
status: {}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack-data
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 2Gi
status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack-db
  name: bookstack-db
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack-db
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack-db
    spec:
      containers:
        - env:
            - name: MYSQL_DATABASE
              value: bookstackapp
            - name: MYSQL_PASSWORD
              value: <PASSWORD_1>
            - name: MYSQL_ROOT_PASSWORD
              value: <PASSWORD_2>
            - name: MYSQL_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
            - name: TZ
              value: Europe/London
          image: linuxserver/mariadb
          name: bookstack-db
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack-db
      restartPolicy: Always
      volumes:
        - name: bookstack-db
          persistentVolumeClaim:
            claimName: bookstack-db
status: {}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  replicas: 1
  selector:
    matchLabels:
      io.kompose.service: bookstack
  strategy:
    type: Recreate
  template:
    metadata:
      annotations:
        kompose.cmd: kompose convert
        kompose.version: 1.22.0 (955b78124)
      creationTimestamp: null
      labels:
        io.kompose.service: bookstack
    spec:
      containers:
        - env:
            - name: DB_DATABASE
              value: bookstackapp
            - name: DB_HOST
              value: bookstack-db
            - name: DB_PASS
              value: <PASSWORD_1>
            - name: DB_USER
              value: bookstack
            - name: PGID
              value: "1000"
            - name: PUID
              value: "1000"
            - name: APP_URL
              value: "https://fqdn"
          image: linuxserver/bookstack
          name: bookstack
          ports:
            - containerPort: 80
          resources: {}
          volumeMounts:
            - mountPath: /config
              name: bookstack-data
      restartPolicy: Always
      volumes:
        - name: bookstack-data
          persistentVolumeClaim:
            claimName: bookstack-data
status: {}
---
apiVersion: v1
kind: Service
metadata:
  annotations:
    kompose.cmd: kompose convert
    kompose.version: 1.22.0 (955b78124)
  creationTimestamp: null
  labels:
    io.kompose.service: bookstack
  name: bookstack
spec:
  type: NodePort
  ports:
    - name: bookstack-port
      port: 80
      nodePort: 30060
      protocol: TCP
  selector:
    io.kompose.service: bookstack
---
apiVersion: v1
kind: Service
metadata:
  name: bookstack-db
spec:
  selector:
    io.kompose.service: bookstack-db
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306

I did wonder if a 2GB per pvc would be enough, so I had a quick peek within my docker-host that's hosting my bookstack at the moment with all my things. Total usage is showing as 211M. Almost all of which is the database. I may increase the size of each claim to 5GB just to be on the safe side, but yeah.

Also, there does seem to be a helm chart to do this, but this was a pretty good learning experience. The final result is a mish mash, for sure, but with the knowledge I've gained, I should be able to implement one wholly from scratch with a bit more practice.