Creating an interactive serverless application using WebSocket
- Getting started
- Set up your environment
- Create Managed Service for YDB databases
- Create a stream in Data Streams
- Create a Yandex Lockbox secret
- Deploy your project
- Create access keys for the service accounts
- Create a new secret version and deploy your project again
- Create an API Gateway
- Add your domain to the Telegram bot
- Test your application
- How to delete the resources you created
In this tutorial, you will deploy an online game based on Node.js using WebSocket.
Your game's static resources will be stored in an Object Storage bucket and its data, in Managed Service for YDB databases. You will use Data Streams to transfer the game data and Cloud Functions to process it. Message Queue will handle communication between the application components. Yandex Lockbox will securely deliver secrets to your app. An API Gateway will accept user requests and redirect them to Cloud Functions.
The game uses Telegram integration to authorize users.
To create an online game:
- Set up your environment.
- Create Yandex Managed Service for YDB databases.
- Create a stream in Yandex Data Streams.
- Create a Yandex Lockbox secret.
- Deploy your project.
- Create access keys for the service accounts.
- Create a new secret version and deploy your project again.
- Create a Yandex API Gateway.
- Add your domain to the Telegram bot.
- Test your application.
If you no longer need the resources you created, delete them.
Getting started
Sign up in Yandex Cloud and create a billing account:
- Navigate to the management console
and log in to Yandex Cloud or register a new account. - On the Yandex Cloud Billing
page, make sure you have a billing account linked and it has theACTIVE
orTRIAL_ACTIVE
status. If you do not have a billing account, create one and link a cloud to it.
If you have an active billing account, you can navigate to the cloud page
Learn more about clouds and folders.
Required paid resources
The infrastructure support cost for this tutorial includes:
- Fee for data operations and the amount of stored data (see Yandex Managed Service for YDB pricing).
- Fee for using a data stream (see Yandex Data Streams pricing).
- Fee for secret storage (see Yandex Lockbox pricing).
- Fee for data storage and data operations (see Yandex Object Storage pricing).
- Fee for requests to API gateways you create and outbound traffic (see Yandex API Gateway pricing).
- Fee for queue requests and outbound traffic (see Yandex Message Queue pricing).
- Fee for function invocations and computing resources allocated to run the functions (see Yandex Cloud Functions pricing).
Set up your environment
- Install WSL
to run a Linux environment. - Run the Linux subsystem (by default, Ubuntu).
- Next, configure the environment as described in this tutorial for Linux.
Note
If you are using a distribution other than Ubuntu, install the specified tools using your package manager.
-
Install the following tools in the specified order by running the relevant commands in your terminal:
-
WebStorm
or IntelliJ IDEA Community Edition :sudo snap install webstorm --classic
-
sudo apt-get install curl git -y
-
jq
:sudo apt-get install jq
-
curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash exec -l $SHELL yc version
-
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" --output "AWSCLIV2.pkg" sudo installer -pkg AWSCLIV2.pkg -target /
-
curl --silent --show-error --location https://storage.yandexcloud.net/yandexcloud-ydb/install.sh | bash
-
Node.js
16.16.0
or higher:sudo apt-get install curl curl --silent --location https://deb.nodesource.com/setup_16.x | sudo -E bash sudo apt-get install nodejs node -v npm -v
-
sudo npm install -g typescript
-
-
Create a Yandex Cloud CLI profile with basic settings.
-
Install the following tools in the specified order by running the relevant commands in your terminal:
-
/bin/bash -c "$(curl --fail --silent --show-error --location https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
-
WebStorm
or IntelliJ IDEA Community Edition :brew install --cask webstorm
-
brew install curl git
-
jq
:brew install jq
-
curl https://storage.yandexcloud.net/yandexcloud-yc/install.sh | bash exec -l $SHELL yc version
-
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" --output "AWSCLIV2.pkg" sudo installer -pkg AWSCLIV2.pkg -target /
-
curl --silent --show-error --location https://storage.yandexcloud.net/yandexcloud-ydb/install.sh | bash
-
Node.js
16.16.0
or higher:brew install node@16 brew install nvm node -v npm -v
If you are using
zsh
, run this command:echo 'export NVM_DIR=~/.nvm' >> ~/.zshrc echo '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"' >> ~/.zshrc source ~/.zshrc
-
npm install -g typescript
-
-
Create a Yandex Cloud CLI profile with basic settings.
Download the project
Clone the yc-serverless-game
repository
git clone https://github.com/yandex-cloud-examples/yc-serverless-game.git
Create a Telegram bot
Create a bot in Telegram and get a token.
-
To create a new bot, start BotFather
and run this command:/newbot
-
In the
name
field, enter a name for the new bot, e.g.,Serverless Game With WebSockets
. This is the name users will see when chatting with the bot. -
In the
username
field, enter a username for the bot, e.g.,ServerlessGameWithWebSocketsBot
. This will allow users to find your bot on Telegram. The username must end with...Bot
or..._bot
. -
You will get the
t.me/ServerlessGameWithWebSocketsBot
address and token in response. -
Save the username to the
TG_BOT_LOGIN
variable:echo "export TG_BOT_LOGIN=<bot_username>" >> ~/.bashrc && . ~/.bashrc echo $TG_BOT_LOGIN
-
Save the token to the
TG_BOT_TOKEN
variable:echo "export TG_BOT_TOKEN=<token>" >> ~/.bashrc && . ~/.bashrc echo $TG_BOT_TOKEN
Get an access token
Get an IAM token and save it to the YC_IAM_TOKEN
variable:
echo "export YC_IAM_TOKEN=$(yc iam create-token)" >> ~/.bashrc && . ~/.bashrc
echo $YC_IAM_TOKEN
Get an OAUTH token and save it to the OAUTH_TOKEN
variable:
echo "export OAUTH_TOKEN=$(yc config get token)" >> ~/.bashrc && . ~/.bashrc
echo $OAUTH_TOKEN
Create a service account
-
Create a service account named
sls-deploy
and save its name to theSERVICE_ACCOUNT_GAME
variable:export SERVICE_ACCOUNT_GAME=$(yc iam service-account create --name sls-deploy \ --description "service account for serverless game" \ --format json | jq -r .) echo $SERVICE_ACCOUNT_GAME
-
The
id
line in the command output will show the service account ID. Save it to theSERVICE_ACCOUNT_GAME_ID
variable:echo "export SERVICE_ACCOUNT_GAME_ID=<service_account_ID>" >> ~/.bashrc && . ~/.bashrc
-
Assign the
editor
role to the service account:echo "export YC_FOLDER_ID=$(yc config get folder-id)" >> ~/.bashrc && . ~/.bashrc echo $YC_FOLDER_ID echo "export YC_CLOUD_ID=$(yc config get cloud-id)" >> ~/.bashrc && . ~/.bashrc echo $YC_CLOUD_ID yc resource-manager folder add-access-binding $YC_FOLDER_ID \ --subject serviceAccount:$SERVICE_ACCOUNT_GAME_ID \ --role editor
-
Create an authorized key for the service account:
yc iam key create \ --service-account-name sls-deploy \ --output sls-deploy.sa echo "export SA_KEY_FILE=$PWD/sls-deploy.sa" >> ~/.bashrc && . ~/.bashrc echo $SA_KEY_FILE
-
Create a static access key for the service account:
yc iam access-key create --service-account-id $SERVICE_ACCOUNT_GAME_ID
Result:
access_key: id: ajeibet3219******** service_account_id: ajehr1tv2eo1******** created_at: "2023-03-13T10:20:58.471421425Z" key_id: YCAS33CT2mPCVFh3K******** secret: YCNhBcdvfDdssIuBa-FDl6zZz0MSky********
-
Save the
key_id
to theAWS_ACCESS_KEY_ID
variable and thesecret
to theAWS_SECRET_ACCESS_KEY
variable:echo "export AWS_ACCESS_KEY_ID=<key_ID>" >> ~/.bashrc && . ~/.bashrc echo $AWS_ACCESS_KEY_ID echo "export AWS_SECRET_ACCESS_KEY=<secret_key>" >> ~/.bashrc && . ~/.bashrc echo $AWS_SECRET_ACCESS_KEY
-
Configure the AWS CLI:
aws configure
Specify the following:
AWS Access Key ID
: Service account’s access key ID (key_id
) you got earlier.AWS Secret Access Key
: Service account secret key (secret
) you got earlier.Default region name
: Use theru-central1
value.Default output format
: Leave empty.
-
Check your configuration:
echo $AWS_ACCESS_KEY_ID echo $AWS_SECRET_ACCESS_KEY aws configure list
Create Managed Service for YDB databases
Create a database named game-data
to store the game data and a database named data-streams
for a stream in Data Streams.
-
Create a database named
game-data
in serverless mode:yc ydb database create game-data --serverless
Result:
done (8s) id: etn0ejcvmjm4******** folder_id: b1geoelk7fld******** created_at: "2023-03-30T15:01:19Z" name: game-data status: PROVISIONING endpoint: grpcs://ydb.serverless.yandexcloud.net:2135/?database=/ru-central1/b1gia87mbaom********/etn0ejcvmjm4******** serverless_database: storage_size_limit: "53687091200" location_id: ru-central1 ...
-
Save the
endpoint
value from the previous command output to theYDB_ENDPOINT
variable. In our example, it isgrpcs://ydb.serverless.yandexcloud.net:2135
.echo "export YDB_ENDPOINT=<DB_Document_API_endpoint>" >> ~/.bashrc && . ~/.bashrc echo $YDB_ENDPOINT
-
Save the
database
value from the previous command output to theYDB_DATABASE
variable. In our example, it is/ru-central1/b1gia87mbaom********/etn0ejcvmjm4********
.echo "export YDB_DATABASE=<table_name>" >> ~/.bashrc && . ~/.bashrc echo $YDB_DATABASE
-
Create a database named
data-streams
in serverless mode:yc ydb database create data-streams --serverless
Result:
done (7s) id: etn16k0e1757******** folder_id: b1geoelk7fld******** created_at: "2023-03-30T15:02:44Z" name: data-streams status: PROVISIONING endpoint: grpcs://ydb.serverless.yandexcloud.net:2135/?database=/ru-central1/b1gia87mbaom********/etn16k0e1757******** serverless_database: storage_size_limit: "53687091200" location_id: ru-central1
-
Save the
endpoint
value from the previous command output to theYDB_DATA_STREAMS_ENDPOINT
variable. In our example, it isgrpcs://ydb.serverless.yandexcloud.net:2135
.echo "export YDB_DATA_STREAMS_ENDPOINT=<DB_Document_API_endpoint>" >> ~/.bashrc && . ~/.bashrc echo $YDB_DATA_STREAMS_ENDPOINT
-
Save the
database
value from the previous command output to theYDB_DATA_STREAMS_DATABASE
variable. In our example, it it/ru-central1/b1gia87mbaom********/etn16k0e1757********
.echo "export YDB_DATA_STREAMS_DATABASE=<table_name>" >> ~/.bashrc && . ~/.bashrc echo $YDB_DATA_STREAMS_DATABASE
-
Make sure all configurations are correct:
ydb \ --endpoint $YDB_ENDPOINT \ --database $YDB_DATABASE \ --sa-key-file $SA_KEY_FILE \ discovery whoami \ --groups
Result:
User SID: ajeien4d11sc043******** Group SIDs: all-users@well-known
Create a table
-
Navigate to the
files
directory in theyc-serverless-game
folder. -
Create a table using the
db-example.sql
file:ydb \ --endpoint $YDB_ENDPOINT \ --database $YDB_DATABASE \ --sa-key-file $SA_KEY_FILE \ scripting yql --file db-example.sql
-
Add a record for the initial configuration:
ydb \ --endpoint $YDB_ENDPOINT \ --database $YDB_DATABASE \ --sa-key-file $SA_KEY_FILE \ scripting yql --file db-update.sql
-
Check the result:
ydb \ --endpoint $YDB_ENDPOINT \ --database $YDB_DATABASE \ --sa-key-file $SA_KEY_FILE \ scheme describe Config
Result:
Columns: ┌────────────────────┬─────────┬────────┬─────┐ | Name | Type | Family | Key | ├────────────────────┼─────────┼────────┼─────┤ | name | Utf8? | | K0 | | grid_cell_size | Uint32? | | | | max_active_players | Uint8? | | | | max_inactive_sec | Int32? | | | | player_size | Uint32? | | | | transport | Utf8? | | | | world_size_x | Uint32? | | | | world_size_y | Uint32? | | | └────────────────────┴─────────┴────────┴─────┘ Storage settings: Store large values in "external blobs": false Column families: ┌─────────┬──────┬─────────────┬────────────────┐ | Name | Data | Compression | Keep in memory | ├─────────┼──────┼─────────────┼────────────────┤ | default | | None | | └─────────┴──────┴─────────────┴────────────────┘ Auto partitioning settings: Partitioning by size: true Partitioning by load: false Preferred partition size (Mb): 2048 Min partitions count: 1
Create a stream in Data Streams
echo $YDB_DATA_STREAMS_DATABASE
aws kinesis create-stream \
--endpoint https://yds.serverless.yandexcloud.net \
--stream-name $YDB_DATA_STREAMS_DATABASE/notify-state-change \
--shard-count 1
Create a Yandex Lockbox secret
-
Create a secret named
game-secrets
and inject theYDB_ENDPOINT
andYDB_DATABASE
values into it:yc lockbox secret create --name game-secrets \ --description "The secrets for the serverless game" \ --payload "[{'key': 'ydb_endpoint', 'text_value': $YDB_ENDPOINT},{'key': 'ydb_db', 'text_value': $YDB_DATABASE}]"
-
Save the ID of the new secret to the
LOCKBOX_SECRET_ID
variable:echo "export LOCKBOX_SECRET_ID=$(jq -r <<< \ "$(yc lockbox secret list --format json | jq '.[]' -c | grep game-secrets)" .id)" \ >> ~/.bashrc && . ~/.bashrc echo $LOCKBOX_SECRET_ID
-
Inject the
TG_BOT_TOKEN
value into the secret:yc lockbox secret add-version --id $LOCKBOX_SECRET_ID \ --payload "[{'key': 'tg_bot_token', 'text_value': '$TG_BOT_TOKEN'}]"
-
Inject the keys into the secret (we will get their values later). For now, set them to
null
.yc lockbox secret add-version --id $LOCKBOX_SECRET_ID \ --payload "[{'key': 'ymq_writer_key_id', 'text_value': 'null'},\ {'key': 'ymq_writer_key_secret', 'text_value': 'null'},\ {'key': 'ymq_capture_queue_url', 'text_value': 'null'},\ {'key': 'yds_writer_key_id', 'text_value': 'null'},\ {'key': 'yds_writer_key_secret', 'text_value': 'null'},\ {'key': 'yds_state_change_stream', 'text_value': 'notify-state-change'},\ {'key': 'yds_state_change_database', 'text_value': '$YDB_DATA_STREAMS_DATABASE'}]"
Deploy your project
-
Navigate to the
files
directory in theyc-serverless-game
folder. -
Change the configuration for Object Storage. Since the bucket name must be unique, change it to a custom bucket name in the following files:
-
serverless.yaml
:sls-game-files: type: yc::ObjectStorageBucket
-
upload-to-s3.ts
in thescripts
directory:Bucket: 'sls-game-files',
For example, specify
sls-game-files-example
instead ofsls-game-files
.
-
-
Set the
APP_ENV
variable to build your project:echo "export APP_ENV=production" >> ~/.bashrc && . ~/.bashrc echo $APP_ENV
-
Build and deploy the project. In the
yc-serverless-game
root folder, run the following commands one by one:nvm use nvm install npm ci npm run build npm run deploy
Your working folder will contain the following resources once your project is deployed:
-
Cloud Functions:
get-state
get-config
move
capture
state-change
login
auth
ws-connect
ws-message
ws-disconnect
-
Service accounts:
functions-sa
with theeditor
roletriggers-sa
with theserverless.functions.invoker
roleyds-reader-sa
with theyds.admin
roleyds-writer-sa
with theyds.writer
roleymq-reader-sa
with theymq.reader
roleymq-writer-sa
with theymq.writer
roleapigw-s3-viewer
with thestorage.viewer
roleapigw-fn-caller
with theserverless.functions.invoker
role
-
Object Storage bucket with the name you specified in
serverless.yaml
-
Message Queue named
capturing-queue
Create access keys for the service accounts
During project deployment, the system created these service accounts:
yds-writer-sa
with theyds.writer
role to write data to Data Streams.ymq-writer-sa
with theymq.writer
role to write data to Message Queue.
-
Create a static access key for the
yds-writer-sa
service account:echo "export YDS_WRITER_SA_ID=$(jq -r <<< \ "$(yc iam service-account list --format json | jq '.[]' -c | grep yds-writer-sa)" .id)" \ >> ~/.bashrc && . ~/.bashrc yc iam access-key create --service-account-id $YDS_WRITER_SA_ID
Result:
access_key: id: ajeibet32197******** service_account_id: ajehr6tv2eoo******** created_at: "2023-03-13T10:20:58.471421425Z" key_id: YCASD3CT9mPCVFh3K******** secret: YCNhBcdvfDdssIuBa-FDl6zZz0MSky********
-
Copy the
key_id
value and save it to theYDS_WRITER_KEY_ID
variable:echo "export YDS_WRITER_KEY_ID=<key_ID>" >> ~/.bashrc && . ~/.bashrc echo $YDS_WRITER_KEY_ID
-
Copy the
secret
value and save it to theYDS_WRITER_KEY_SECRET
variable:echo "export YDS_WRITER_KEY_SECRET=<secret>" >> ~/.bashrc && . ~/.bashrc echo $YDS_WRITER_KEY_SECRET
-
Create a static access key for the
ymq-writer-sa
service account:echo "export YMQ_WRITER_SA_ID=$(jq -r <<< \ "$(yc iam service-account list --format json | jq '.[]' -c | grep ymq-writer-sa)" .id)" \ >> ~/.bashrc && . ~/.bashrc yc iam access-key create --service-account-id $YMQ_WRITER_SA_ID
-
Copy the
key_id
value and save it to theYMQ_WRITER_KEY_ID
variable:echo "export YMQ_WRITER_KEY_ID=<key_ID>" >> ~/.bashrc && . ~/.bashrc echo $YMQ_WRITER_KEY_ID
-
Copy the
secret
value and save it to theYMQ_WRITER_KEY_SECRET
variable:echo "export YMQ_WRITER_KEY_SECRET=<secret>" >> ~/.bashrc && . ~/.bashrc echo $YMQ_WRITER_KEY_SECRET
Create a new secret version and deploy your project again
-
Provide new values to the secret named
game-secrets
:-
In the management console
, select your working folder. -
Select Message Queue.
-
Select
capturing-queue
. -
Copy the value from the URL field and save it to the
YMQ_CAPTURE_QUEUE_URL
variable:echo "export YMQ_CAPTURE_QUEUE_URL=<URL>" >> ~/.bashrc && . ~/.bashrc
-
Check the variable values to inject into the secret:
echo $LOCKBOX_SECRET_ID echo $YMQ_WRITER_KEY_ID echo $YMQ_WRITER_KEY_SECRET echo $YDS_WRITER_KEY_ID echo $YDS_WRITER_KEY_SECRET echo $YMQ_CAPTURE_QUEUE_URL
-
Inject the values into the secret:
yc lockbox secret add-version --id $LOCKBOX_SECRET_ID \ --payload "[{'key': 'ymq_writer_key_id', 'text_value': '$YMQ_WRITER_KEY_ID'},\ {'key': 'ymq_writer_key_secret', 'text_value': '$YMQ_WRITER_KEY_SECRET'},\ {'key': 'ymq_capture_queue_url', 'text_value': '$YMQ_CAPTURE_QUEUE_URL'},\ {'key': 'yds_writer_key_id', 'text_value': '$YDS_WRITER_KEY_ID'},\ {'key': 'yds_writer_key_secret', 'text_value': '$YDS_WRITER_KEY_SECRET'}]"
-
-
Navigate to the
yc-serverless-game
root folder and deploy your project again:npm run deploy
Create an API Gateway
During project deployment, the system created these service accounts:
apigw-s3-viewer
with thestorage.viewer
role to read objects from the Object Storage bucket.apigw-fn-caller
with thefunctions.functionInvoker
role to invoke Cloud Functions.
-
Save the IDs of the
apigw-s3-viewer
andapigw-fn-caller
service accounts to theAPIGW_S3_VIEWER_ID
andAPIGW_FN_CALLER_ID
variables:echo "export APIGW_S3_VIEWER_ID=$(jq -r <<< \ "$(yc iam service-account list --format json | jq '.[]' -c | grep apigw-s3-viewer)" .id)" \ >> ~/.bashrc && . ~/.bashrc echo "export APIGW_FN_CALLER_ID=$(jq -r <<< \ "$(yc iam service-account list --format json | jq '.[]' -c | grep apigw-fn-caller)" .id)" \ >> ~/.bashrc && . ~/.bashrc
-
Modify the API gateway specification. Navigate to the
files
directory in theyc-serverless-game
folder and run the following command:cp apigw-example.yml apigw.yml
-
Get the IDs of the resources you created in the previous steps:
echo $APIGW_S3_VIEWER_ID echo $APIGW_FN_CALLER_ID yc storage bucket list yc serverless function list
-
Edit the
apigw.yml
file as follows:- In all
bucket: serverless-game-files
lines, replace the bucket name with the custom one. - In all
service_account_id: <sa-id-for-object-storage>
lines, replace<sa-id-for-object-storage>
with the$APIGW_S3_VIEWER_ID
value. - In all
service_account_id: <sa-id-for-functions>
lines, replace<sa-id-for-functions>
with the$APIGW_FN_CALLER_ID
value. - In line
58
, replace<yandex-cloud-nodejs-dev-get-state-function-id>
with theyandex-cloud-nodejs-dev-get-state
function ID. - In line
65
, replace<yandex-cloud-nodejs-dev-get-config-function-id>
with theyandex-cloud-nodejs-dev-get-config
function ID. - In line
72
, replace<yandex-cloud-nodejs-dev-move-function-id>
with theyandex-cloud-nodejs-dev-move
function ID. - In line
79
, replace<yandex-cloud-nodejs-dev-login-function-id>
with theyandex-cloud-nodejs-dev-login
function ID. - In line
101
, replace<yandex-cloud-nodejs-dev-ws-message-function-id>
with theyandex-cloud-nodejs-dev-ws-message
function ID. - In line
106
, replace<yandex-cloud-nodejs-dev-ws-connect-function-id>
with theyandex-cloud-nodejs-dev-ws-connect
function ID. - In line
111
, replace<yandex-cloud-nodejs-dev-ws-disconnect-function-id>
with theyandex-cloud-nodejs-dev-ws-disconnect
function ID. - In line
118
, replace<yandex-cloud-nodejs-dev-auth-function-id>
with theyandex-cloud-nodejs-dev-auth
function ID.
- In all
-
Deploy an API Gateway instance:
yc serverless api-gateway create \ --name serverless-game-api \ --spec=apigw.yml \ --description "for serverless-game-api"
In case of an error in the API gateway specification, fix it and run this command:
yc serverless api-gateway update \ --name serverless-game-api \ --spec=apigw.yml
Add your domain to the Telegram bot
-
Run this command:
yc serverless api-gateway get --name serverless-game-api
-
Copy your API gateway's service domain. You can find it in the
domain
field of the previous command output. -
In Telegram, find BotFather
and type the/setdomain
command. -
Select your bot from the list and send it your API gateway's service domain. Add
https://
before the domain name. For example, if your API gateway's service domain isd5dm1lba80md********.i9******.apigw.yandexcloud.net
, the URL will behttps://d5dm1lba80md********.i9******.apigw.yandexcloud.net
.
Test your application
Follow the link you sent to the Telegram bot, sign in, and open the game.
The game offers player statistics. If the API gateway's service domain is d5dm1lba80md********.i9******.apigw.yandexcloud.net
, the statistics for all players will be available at https://d5dm1lba80md********.i9******.apigw.yandexcloud.net/stats.html
.
How to delete the resources you created
To stop paying for the resources you created:
- Delete the Managed Service for YDB databases.
- Delete the stream in Data Streams.
- Delete the Yandex Lockbox secret.
- Delete all objects from the bucket and then delete the empty Object Storage bucket.
- Delete the API Gateway.
- Delete the Cloud Functions.
- Delete the Message Queue.