Skip to content

Dip it – Deploy Apache Guacamole on Kubernetes

With Apache Guacamole, you can use all kind of different remote desktop protocols (like SSH, VNC, RDP) to access your remote machines. Guacamole can run fine on Docker, but it’s a bit tricky to deploy on a Kubernetes environment. I haven’t found an existing deployment for the current version of Guacamole, so I decided to create my own.

Guacamole needs two containers running in coexistance, one is the Guacamole daemon and the other the actual application. Another needed component is an existing PostgreSQL database. I used the Postgres cluster which I have deployed also on Kubernetes, managed by the Zalando Postgres Operator (see here for an article on that topic). As always, if you want to quickstart, you can use the Kustomization you can find on my Github repository. I also put the disclaimer in this article, I reused some of the code of thomas-illiet/k8s-guacamole. But he hasn’t updated his deployment for Guacamole 1.4 (which is the latest version as by the time of writing this) and I found it a little bit complicated.

Guacamole has a bit of a complicated way to prepare a Postgres database for Guacamole. In shortness of time, I did the database preparation manually. First, you need to create a guacamole user on your database and second the database itself.

create user guacamole password 'supersecret';
create database guacamole owner guacamole;

Now you need to generate an initialization script out of a Docker container and apply this script against your database.

docker run --rm guacamole/guacamole /opt/guacamole/bin/initdb.sh --postgres > initdb.sql
psql -U guacamole -d guacamole < initdb.sql

With that done, you are free to proceed. I plan to create a helper container image in the future, which will do the checking if a database initialization is needed and applies the initialization. This will then be started as an init container in the pod.

So off to the Kubernetes side of things. I created a Kubernetes deployment which will create a pod with the two containers in it. It will expose all needed ports on the pods / containers.

deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: guacamole
  namespace: guacamole
spec:
  replicas: 1
  selector:
    matchLabels:
      app: guacamole
  template:
    metadata:
      labels:
        app: guacamole
    spec:
      containers:
        - name: guacd
          image: guacamole/guacd:1.4.0
          imagePullPolicy: IfNotPresent
          ports:
            - containerPort: 4822
          readinessProbe:
            tcpSocket:
              port: 4822
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 4822
            initialDelaySeconds: 15
            periodSeconds: 20
        - name: guacamole
          image: guacamole/guacamole:1.4.0
          imagePullPolicy: IfNotPresent
          env:
            - name: GUACD_HOSTNAME
              value: "localhost"
            - name: GUACD_PORT
              value: "4822"
            - name: POSTGRES_PORT
              valueFrom:
                secretKeyRef:
                  name: guacamole-database-credentials
                  key: postgres-port
            - name: POSTGRES_HOSTNAME
              valueFrom:
                secretKeyRef:
                  name: guacamole-database-credentials
                  key: postgres-hostname
            - name: POSTGRES_DATABASE
              valueFrom:
                secretKeyRef:
                  name: guacamole-database-credentials
                  key: postgres-database
            - name: POSTGRES_USER
              valueFrom:
                secretKeyRef:
                  name: guacamole-database-credentials
                  key: postgres-user
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: guacamole-database-credentials
                  key: postgres-password
          ports:
            - name: guacamole-app
              containerPort: 8080
          readinessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
          livenessProbe:
            tcpSocket:
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 20

As you can see, all database connection information and credentials are to be read out of a Kubernetes secret guacamole-database-credentials. So, this is the next step then to create this manifest:

secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: guacamole-database-credentials
  namespace: guacamole
data:
  postgres-user: Z3VhY2Ftb2xl
  postgres-password: U3VwZXJzZWNyZXQ=
  postgres-hostname: cG9zdGdyZXMtaG9zdA==
  postgres-port: NTQzMg==
  postgres-database: Z3VhY2Ftb2xl
type: Opaque

All values are base64 encoded, adjust them to your needs but don’t forget to re-encode them before applying it. Alternatively, you can use a secret with stringData.

We want to connect to Guacamole using a webbrowser, so we need a Service which handles the inbound traffic. In my case, I have an ingress controller on my Kubernetes cluster (see below), if this is not the case for you, you will probably want to change the service type to LoadBalancer or NodePort and access the given IP directly (see some examples on services here). But this will not handle SSL for you, so I don’t recommend you to do so.

service.yaml
apiVersion: v1
kind: Service
metadata:
  name: guacamole
  namespace: guacamole
  labels:
    app: guacamole
spec:
  ports:
    - name: guacamole-app
      port: 8080
      targetPort: 8080
      protocol: TCP
  selector:
    app: guacamole

As mentioned above, I use an ingress controller for handling my http / https traffic and SSL termination. So here is an example for the ingress manifest:

ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: guacamole
  namespace: guacamole
  labels:
    app: guacamole
  annotations:
    # the name of the nginx-ingress-controller class
    kubernetes.io/ingress.class: "nginx"
    cert-manager.io/cluster-issuer: "letsencrypt-staging"
spec:
  tls:
  - hosts:
    - guacamole.mydomain.de
    secretName: guacamole.mydomain.de
  rules:
  - host: guacamole.mydomain.de
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: guacamole
            port:
              number: 8080

This probably will vary from your implementation, so prepare for some changes here. With that we nearly have all needed resource manifests prepared. As last one, we have to create the namespace:

namespace.yaml
kind: Namespace
apiVersion: v1
metadata:
  name: guacamole

Let’s apply the created manifests in the following order (or use the kustomization from my repository).

kubectl apply -f namespace.yaml
kubectl apply -f secret.yaml
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl apply -f ingress.yaml

With some waiting and checking the Guacamole pod for errors, you should be able to connect to the Guacamole WebUI. Your first logon (with an empty database) needs to be done with the user guacadmin and the password guacadmin. It’s obviously a security risk to leave it that way. Create your own user (using the WebUI) ASAP and disable / remove the guacadmin user afterward.

Guacamole

You are now able to manage your connections, provide credentials and finally, access the remote system by Guacamole.

Philip

3 thoughts on “Dip it – Deploy Apache Guacamole on Kubernetes”

  1. Thanks for this article, it helped me put together my own manifests for deploying guacamole on kubernetes using flux.

    I’ve expanded on your work to also include the postgres deployment, along with the creation and initialization of the database. I thought you might be interested in including this in your repository, if so, let me know and I will send you the script (kustomize) file.

      1. PR created, adapted from my production environment to fit your manifests. I’ve also removed the manual steps from the readme.md since these are no longer relevant.

Leave a Reply

Your email address will not be published. Required fields are marked *