Working on Content

Workshop content will either be packaged up as an OCI image artifact, hosted in a Git repository, or on a web server, with the conent downloaded when the workshop session is created. Alternatively it could be built into a custom workshop image. To speed up the iterative loop of editing and testing a workshop when developing workshop content, the educates CLI provides various commands to faciliate development of workshop content in a local environment. When using the educates CLI many of the details of how everything is configured to achieve this is hidden. This document covers some details of using the educates CLI, but also aims to explain what some of the underlying configuraton is in case you need to work on workshop content in a hosted cluster and the educates CLI cannot be used.

Publishing of workshop content

When using the educates CLI to create a local Kubernetes cluster using Kind, it will also deploy an image registry for you automatically and link it into the Kubernetes cluster. This registry can be used to hold published workshops.

When using this capability the workshop definition would need to be in the form:

spec:
  publish:
    image: $(image_repository)/{name}-files:$(workshop_version)
  workshop:
    files:
    - image:
        url: $(image_repository)/{name}-files:$(workshop_version)
      includePaths:
      - /workshop/**
      - /exercises/**
      - /README.md

where {name} would be the name of the workshop.

When publishing this workshop using the command:

educates publish-workshop

the value of $(image_repository) in the destination for publishing the workshop as specified by publish.image will be replaced with localhost:5001 and $(workshop_version) will be replaced with latest.

If you subsequently run:

educates deploy-workshop

to deploy the workshop to the local Kubernetes cluster, when evaluating workshop.files.image.url, the value of $(image_repository) will be replaced with registry.default.svc.cluster.local, which is the internal hostname for the local registry when accessed from inside of the Kubernetes cluster.

In the case of accessing the registry from inside of the cluster using tools such as imgpkg, they will allow insecure access because the image registry has a .local address. Some other tools may require you to tell them to access it in an insecure way. The latter may be the case when accessing the registry as localhost:5001 from your local system also.

If working against a remote Kubernetes cluster it will not have access to the local registry so you can instead use a remote registry to host public workshops instead.

To publish to a remote registry you can use a command like:

educates publish-workshop --image-repository docker.io/username

Depending on the remote registry type, you should either be logged in using docker login, or you need to supply credentials on the command line to educates publish-workshop.

With the workshop published to the registry, if the registry was publicly accessible and you wanted someone else to be able to deploy the workshop, you could run:

educates publish-workshop --image-repository docker.io/username --workshop-version 1.0 --export-workshop published-workshop.yaml

When this is done a modified version of the workshop defintion will be written to the file publish-workshop.yaml with any references to $(image_repository) being replaced with the remote registry address. Supplying --workshop-version allows you to specify a specific version for the publish workshop rather than latest.

If you need to generate the modified workshop definition separate to publishing it, you can run:

educates export-workshop --image-repository docker.io/username --workshop-version 1.0 > published-workshop.yaml

The recipient of the modified workshop definition could use:

educates deploy-workshop -f published-workshop.yaml

to publish the workshop, or use kubectl to apply it to a cluster and register the workshop with a training portal.

Local build of workshop image

When using the local Kubernetes cluster created using the educates CLI, the local image registry created can also be used to hold a custom workshop base image if used. In this case the workshop definition can be defined as:

spec:
  workshop:
    image: $(image_repository)/{name}-image:$(workshop_version)

As with the published workshop files, the $(image_repository) and $(workshop_version) variable references will be automatically replaced with appropriate values when a workshop is deployed to the local Kubernetes cluster.

The address of the local image registry will be localhost:5001 when accessed from your local machine. Access to the registry will be insecure, but local tools like docker will allow insecure access since the host is localhost.

The host of the registry when accessed from inside of the local cluster will be registry.default.svc.cluster.local. When accessed from inside of the cluster the default port 80 will be used and access is still not secure, however, the containerd process in Kubernetes will be configured to trust the registry.

When using educates publish-workshop --export-workshop or educates export-workshop with a remote image registry the variable references for $(image_repository) and $(workshop_version) in workshop.image will also be replaced. This means the custom workshop image would need to be hosted under the same image registry as the workshop was published.

Disabling reserved sessions

An instance of a training portal should be used when developing content where reserved sessions are disabled.

apiVersion: training.educates.dev/v1beta1
kind: TrainingPortal
metadata:
  name: lab-sample-workshop
spec:
  portal:
    sessions:
      maximum: 1
  workshops:
  - name: lab-sample-workshop
    reserved: 0
    expires: 120m
    orphaned: 15m

If you don’t disable reserved sessions then a new session will always be created ready for the next workshop session when there is available capacity to do so. If you are modifying workshop content while testing the current workshop session, then terminate the session and start a new one it will pickup the reserved session, which will still have a copy of the old content.

By disabling reserved sessions a new workshop session will always be created on demand, ensuring the latest workshop content is used.

Note that there can be a slight delay in being able to create a new workshop as the existing workshop session will need to be shutdown first. The new workshop session may also take some time to start if an updated version of the workshop image has to be pulled down.

Accessing workshop error logs

If workshop content is not able to be downloaded due to an error, a setup script included with the workshop content fails, or workshop instructions cannot be rendered when using the Hugo renderer, an error dialog will be displayed when the workshop session dashboard is displayed. The dialog will point you at the error logs for details. You have two options for finding details of the errors in these cases.

The first way is to determine the name of the deployment for the workshop session and which namespace it is in, and use the kubectl logs command to access the logs.

The second and easier way if you have access to the workshop session dashboard, is to use the embedded terminal to look at the log files located under the directory $HOME/.local/share/workshop.

The two main log files are:

  • $HOME/.local/share/workshop/download-workshop.log

  • $HOME/.local/share/workshop/setup-scripts.log

You can tell in which phase the error occurred, as there will be a corresponding marker file in the same directory, with same basename, but with .failed extension.

Live updates to the content

If workshop content is being downloaded as an OCI image artifact, from a Git repository, or a web server, and you are only doing simple updates to workshop instructions or scripts or files bundled with the workshop, you can update the content in place without needing to restart the workshop session. To perform an update, after you have pushed back any changes to the hosted Git repository or updated the content available via the web server, from the workshop session terminal run:

update-workshop

This command will download any workshop content from the OCI artifact registry, Git repository or web server, then unpack it into the live workshop session and re-run any script files found in the workshop/setup.d directory.

Once the workshop content has been updated you can reload the current page of the workshop instructions by clicking on the reload icon on the dashboard while holding down the <SHIFT> key.

When running this command, any errors which occur in downloading the workshop content, running the setup script, or rendering the workshop instructions, will be output to the terminal so this command can be used instead of consulting the logs to look for errors. Do be aware though that since this can be the second time setup scripts have run, the behaviour may not be the same as the first time they have run. So for original details of errors you will still need to look at the log files.

Note that if using the classic renderer and additional pages were added to the workshop instructions, or pages renamed, you will need to restart the workshop renderer process. This can be done by running:

restart-workshop

If using the hugo renderer you do not not need to run the restart-workshop and doing so will have no affect.

So long as you hadn’t renamed or deleted the current page you are viewing, you can trigger a reload of the current page. If you have eliminated the current page by deleting it or renaming it, click on the home icon or refresh the whole browser window.

If action blocks within the workshop instructions are broken and you wanted to make a change to the workshop instructions within the live workshop session to test, edits can be made to the appropriate page under /opt/workshop/content. If using the classic renderer you can then navigate to the modified page or reload it to verify the change. If using the hugo renderer, you will first need to run the command:

rebuild-content

If wanting to make a change to setup scripts which create files specific to a workshop session and re-run them, make the edit to the script under /opt/workshop/setup.d. To trigger running of any setup scripts, then run:

rebuild-workshop

If local changes to the workshop session check out okay then you can modify the file back in the original Git repository where you are keeping content.

Note that updating workshop content in a live session in this way isn’t going to undo any deployments or changes you make in the Kubernetes cluster for that session. So if wanting to retest parts of the workshop instructions you may have to manually undo changes in the cluster in order to replay them. This will depend on your specific workshop content.

Custom workshop image changes

If your workshop is using a custom workshop image because of the need to provide additional tools, and you have as a result also included the workshop instructions as part of the workshop image, during development of workshop content always use an image tag of main, master, develop or latest, do not use a version image reference.

apiVersion: training.educates.dev/v1beta1
kind: Workshop
metadata:
  name: lab-sample-workshop
spec:
  title: Sample Workshop
  description: A sample workshop
  workshop:
    image: ghcr.io/vmware-tanzu-labs/lab-sample-workshop:latest

When an image tag of main, master, develop or latest is used, the image pull policy will be set to Always ensuring that the custom workshop image will be pulled down again for a new workshop session if the remote image had changed. If the image tag was for a specific version, it would be necessary to change the workshop definition every time there was a change to the workshop image.

Custom workshop image overlay

Even where you have a custom workshop image, setup the workshop definition to also pull down the workshop content from the hosted Git repository or web server.

apiVersion: training.educates.dev/v1beta1
kind: Workshop
metadata:
  name: lab-sample-workshop
spec:
  title: Sample Workshop
  description: A sample workshop
  workshop:
    image: ghcr.io/vmware-tanzu-labs/lab-sample-workshop-image:latest
    files:
    - image:
        url: ghcr.io/vmware-tanzu-labs/lab-sample-workshop-files:latest

By pulling down the workshop content as an overlay when the workshop session started, on top of the contents of the custom workshop image, you only need to rebuild the custom workshop image when needing to make changes to what additional tools are needed, or when you want to ensure the latest workshop instructions are also a part of the final custom workshop image.

Using this method, as the location of the workshop files is known you can then also do live updates of workshop content in the session as described previously.

If the additional set of tools required for a workshop is not too specific to a workshop, it is recommended to create a standalone workshop base image where just the tools are added. Content for a specific workshop would then always be pulled down when the workshop session is started.

apiVersion: training.educates.dev/v1beta1
kind: Workshop
metadata:
  name: lab-sample-workshop
spec:
  title: Sample Workshop
  description: A sample workshop
  workshop:
    image: ghcr.io/vmware-tanzu-labs/custom-environment:latest
    files:
    - image:
        url: ghcr.io/vmware-tanzu-labs/lab-sample-workshop-files:latest

This separates generic tooling from specific workshops and allows the custom workshop base image to be used for multiple workshops on different, but related topics, which require the same tooling.

Proxy to local workshop content

The educates CLI provides a way to serve workshop instructions from your local host system and have it embedded in the workshop session hosted within the cluster. This is triggered by running from the local copy of the workshop files the command:

educates serve-workshop --patch-workshop

NOTE: For this to work, you need hugo to be available on your development machine.

The --patch-workshop command in this case will cause the workshop definition for the workshop to be patched so that workshop instructions will be sourced from a HTTP server run by the educates serve-workshop command.

Under the covers what the --patch-workshop command is doing is injecting the following configuration into the workshop definition.

spec:
  workshop:
    enabled: true
    proxy:
      changeOrigin: false
      headers:
      - name: X-Session-Name
        value: $(session_name)
      host: localhost.$(ingress_domain)
      port: 10081
      protocol: http

Rather than relying on the --patch-workshop option, you could instead add this to the workshop definition yourself and then run the educates CLI as just:

educates serve-workshop

Note that when the --patch-workshop is used, in addition to the above configuration, it is also automatically configuring a shared secret token so that access from the workshop session is trusted. This will not be present if you inject this specific configuration yourself and anyone could access the local workshop instructions.

If you want to secure access via a shared access token you should instead use:

spec:
  workshop:
    enabled: true
    proxy:
      changeOrigin: false
      headers:
      - name: X-Session-Name
        value: $(session_name)
      - name: X-Access-Token
        value: secret-token
      protocol: http
      host: localhost.$(ingress_domain)
      port: 10081

When this is done you should then run the CLI as:

educates serve-workshop --access-token secret-token

In the example above and when using the educates CLI to patch the workshop definition, the Kubernetes cluster needs to be running on the local system under Kind. If manually modifying the workshop definition to add the configuration, you can override the protocol, host and port to specify a different target. This would allow you for example to use a Cloudflare Tunnel to expose port 10081 from your local system via a public hostname. You could then set the configuration to access the locally served content from your your system via the Cloudflare Tunnel, even when using a distinct hosted Kubernetes cluster.

spec:
  workshop:
    enabled: true
    proxy:
      changeOrigin: false
      headers:
      - name: X-Session-Name
        value: $(session_name)
      - name: X-Access-Token
        value: secret-token
      protocol: https
      host: tunnel.example.com
      port: 443

If using the educates CLI to deploy the workshop to a separate hosted cluster, you can still have it patch the workshop definition when using such a tunnel by running:

educates serve-workshop --patch-workshop --proxy-protocol https --proxy-host tunnel.example.com --proxy-port 443

Changes to workshop defintion

By default, if you need to modify the definition for a workshop, you would need to delete the training portal instance, update the workshop definition in the cluster, and recreate the training portal.

During development of workshop content, when working on the workshop definition itself to change things like resource allocations, role access, or what resource objects are automatically created for the workshop environment or a specific workshop session, you can in the training portal definition enable automatic updates on changes to the workshop definition.

apiVersion: training.educates.dev/v1beta1
kind: TrainingPortal
metadata:
  name: lab-sample-workshop
spec:
  portal:
    sessions:
      maximum: 1
    updates:
      workshop: true
  workshops:
  - name: lab-sample-workshop
    expires: 120m
    orphaned: 15m

With this option enabled, whenever the workshop definition in the cluster is modified, the existing workshop environment managed by the training portal for that workshop, will be shutdown and replaced with a new workshop environment using the updated workshop definition.

In the case there is still an active workshop session running, the actual deletion of the old workshop environment will be delayed until that workshop session is terminated.