Crystal WebSockets demo sample for Cloud Run
kemal-redis-chat
This sample demonstrates how to use WebSockets on Cloud Run with Crystal. The demo is a web app, that supports a single chat room. Crystal is a compiled language that has a syntax similar to Ruby. It is known to be very fast (as fast as Go), and the binaries are very small. The web server is known as Kemal, and supports WebSockets out of the box.
In order to have a consistent chat message queue, especially when there are multiple web servers due to auto-scaling, we must use a database for persistence. We use Redis, since it is fast and simple to set-up.
Running locally
Before you can run the code locally, you need to do the following:
-
Install
crystal
and its dependency managershards
. -
Install dependencies:
With
shards
:shards install
-
Run locally
With
crystal
:crystal run src/kemal-redis-chat.cr
[!NOTE] Unless you have a local redis DB running, the program will crash.
Deploying to Cloud Run
-
Create a Redis instance on Cloud Memorystore. Make sure to choose the VPC network you will use (default). After it’s created, note its IP address.
export REGION=us-central1 gcloud redis instances create redis-chat --size=1 --region=$REGION \ --connect-mode=PRIVATE_SERVICE_ACCESS
-
Deploy to Cloud Run, with Direct VPC egress:
export PROJECT_ID=$(gcloud config list --format 'value(core.project)' 2>/dev/null) export REDISHOST=$(gcloud redis instances describe redis-chat --region $REGION --format "value(host)") gcloud beta run deploy websockets \ --source . \ --allow-unauthenticated \ --region $REGION \ --max-instances 10 \ --concurrency 100 \ --timeout 3600 \ --network=default \ --subnet=default \ --vpc-egress=private-ranges-only \ --set-env-vars REDIS=${REDISHOST} \ --set-env-vars DEBUG="false"
Deploying via Cloud Run button
Click on the button below to:
- Launch a Cloud Shell machine
- Download this GitHub repository
- Build a container using Docker
- Push the container into Google Artifact Registry
- Launch Cloud Run
All automatically...
[!IMPORTANT] Since this code requires a redis DB, and the appropriate ENV variable set, it will not work directly via the Cloud Run button. However, it should be easy to re-deploy a new revision with the proper ENV variables and network settings.
Describe Cloud Run service
gcloud beta run services describe websockets \
--region=$REGION
Clean up
gcloud run services delete websockets --region $REGION
gcloud redis instances delete redis-chat --region $REGION
Development
I added a Makefile to simply building and deploying the executable. I also created a Docker compose file for local testing.
Available make commands
$ make
Available targets:
build/cloud Build docker container in Cloud
build/docker Build docker container locally
build/local Compiles source using shards command
clean/cloud Delete Cloud Run service and container
deploy Deploy container to Cloud Run
docker/login Configures Docker to authenticate to GCR
help This help screen
logs Examine the logs from the Cloud container
logs/stream Stream Cloud Run logs
watch Locally run program with dynamic recompile
Resources
Cloud Run
- https://github.com/GoogleCloudPlatform/cloud-run-button
GNU Make
Notes
Send authorization token, if unauthenticated is not allowed
curl --header "Authorization: Bearer $(gcloud auth print-identity-token)" [URL]
It is now possible to send requests via a local proxy, even if the service is authenticated.
gcloud beta run services proxy
Running Redis locally
docker run --rm -d -p 6379:6379 --name myredis redis:alpine
Proxy into GCP Redis via jumphost
https://cloud.google.com/memorystore/docs/redis/connect-redis-instance?hl=en#connecting_from_a_local_machine_with_port_forwarding
gcloud compute instances create NAME --machine-type=f1-micro --zone=ZONE
gcloud compute ssh COMPUTE_VM_NAME --zone=ZONE -- -N -L 6379:REDIS_INSTANCE_IP_ADDRESS:6379
Appendix
- https://cloud.google.com/run/docs/tutorials/websockets
- https://github.com/GoogleCloudPlatform/nodejs-docs-samples/tree/main/run/websockets
- https://serdardogruyol.com/building-a-realtime-chat-app-with-crystal-and-kemal
- https://www.tlbx.app/blog/dispatch-kemal-websocket-messages-with-redis-pub-sub
- https://kemalcr.com/
- https://crystal-lang.org/