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 for Yandex Cloud and create a billing account:
- Navigate to the management console
and log in to Yandex Cloud or create a new account. - On the Yandex Cloud Billing
page, make sure you have a billing account linked and it has theACTIVEorTRIAL_ACTIVEstatus. 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 here.
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: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:brew install node@16 brew install nvm node -v npm -vIf 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
namefield, enter a name for the bot, e.g.,Serverless Game With WebSockets. This is the name users will see when chatting with the bot. -
In the
usernamefield, enter a username for the bot, e.g.,ServerlessGameWithWebSocketsBot. You can use it to find the bot in Telegram. The username must end with...Botor..._bot. -
You will get the
t.me/ServerlessGameWithWebSocketsBotaddress and token in response. -
Save the username to the
TG_BOT_LOGINvariable:echo "export TG_BOT_LOGIN=<bot_username>" >> ~/.bashrc && . ~/.bashrc echo $TG_BOT_LOGIN -
Save the token to the
TG_BOT_TOKENvariable: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-deployand save its name to theSERVICE_ACCOUNT_GAMEvariable: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
idline in the command output will show the service account ID. Save it to theSERVICE_ACCOUNT_GAME_IDvariable:echo "export SERVICE_ACCOUNT_GAME_ID=<service_account_ID>" >> ~/.bashrc && . ~/.bashrc -
Assign the
editorrole 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_IDResult:
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_idvalue to theAWS_ACCESS_KEY_IDvariable and thesecretvalue to theAWS_SECRET_ACCESS_KEYvariable: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 configureSpecify the following:
AWS Access Key ID: Service accountkey_idyou got earlier.AWS Secret Access Key: Service account secret key (secret) you got earlier.Default region name: Use theru-central1value.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 serverless database named
game-data:yc ydb database create game-data --serverlessResult:
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
endpointvalue from the previous command output to theYDB_ENDPOINTvariable. 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
databasevalue from the previous command output to theYDB_DATABASEvariable. In our example, it is/ru-central1/b1gia87mbaom********/etn0ejcvmjm4********.echo "export YDB_DATABASE=<table_name>" >> ~/.bashrc && . ~/.bashrc echo $YDB_DATABASE -
Create a serverless database named
data-streams:yc ydb database create data-streams --serverlessResult:
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
endpointvalue from the previous command output to theYDB_DATA_STREAMS_ENDPOINTvariable. 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
databasevalue from the previous command output to theYDB_DATA_STREAMS_DATABASEvariable. 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 \ --groupsResult:
User SID: ajeien4d11sc043******** Group SIDs: all-users@well-known
Create a table
-
Navigate to the
filesdirectory in theyc-serverless-gamefolder. -
Create a table using the
db-example.sqlfile: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 ConfigResult:
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-secretsand inject theYDB_ENDPOINTandYDB_DATABASEvalues 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_IDvariable: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_TOKENvalue 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
filesdirectory in theyc-serverless-gamefolder. -
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.tsin thescriptsdirectory:Bucket: 'sls-game-files',For example, specify
sls-game-files-exampleinstead ofsls-game-files.
-
-
Set the
APP_ENVvariable to build your project:echo "export APP_ENV=production" >> ~/.bashrc && . ~/.bashrc echo $APP_ENV -
Build and deploy the project. In the
yc-serverless-gameroot 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-stateget-configmovecapturestate-changeloginauthws-connectws-messagews-disconnect
-
Service accounts:
functions-sawith theeditorroletriggers-sawith theserverless.functions.invokerroleyds-reader-sawith theyds.adminroleyds-writer-sawith theyds.writerroleymq-reader-sawith theymq.readerroleymq-writer-sawith theymq.writerroleapigw-s3-viewerwith thestorage.viewerroleapigw-fn-callerwith theserverless.functions.invokerrole
-
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-sawith theyds.writerrole to write data to Data Streams.ymq-writer-sawith theymq.writerrole to write data to Message Queue.
-
Create a static access key for the
yds-writer-saservice 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_IDResult:
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_idvalue and save it to theYDS_WRITER_KEY_IDvariable:echo "export YDS_WRITER_KEY_ID=<key_ID>" >> ~/.bashrc && . ~/.bashrc echo $YDS_WRITER_KEY_ID -
Copy the
secretvalue and save it to theYDS_WRITER_KEY_SECRETvariable:echo "export YDS_WRITER_KEY_SECRET=<secret>" >> ~/.bashrc && . ~/.bashrc echo $YDS_WRITER_KEY_SECRET -
Create a static access key for the
ymq-writer-saservice 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_idvalue and save it to theYMQ_WRITER_KEY_IDvariable:echo "export YMQ_WRITER_KEY_ID=<key_ID>" >> ~/.bashrc && . ~/.bashrc echo $YMQ_WRITER_KEY_ID -
Copy the
secretvalue and save it to theYMQ_WRITER_KEY_SECRETvariable:echo "export YMQ_WRITER_KEY_SECRET=<secret>" >> ~/.bashrc && . ~/.bashrc echo $YMQ_WRITER_KEY_SECRET
Create a new secret version and deploy your project again
-
Inject new values into 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_URLvariable: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-gameroot folder and deploy your project again:npm run deploy
Create an API Gateway
During project deployment, the system created these service accounts:
apigw-s3-viewerwith thestorage.viewerrole to read objects from the Object Storage bucket.apigw-fn-callerwith thefunctions.functionInvokerrole to invoke Cloud Functions.
-
Save the IDs of the
apigw-s3-viewerandapigw-fn-callerservice accounts to theAPIGW_S3_VIEWER_IDandAPIGW_FN_CALLER_IDvariables: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
filesdirectory in theyc-serverless-gamefolder 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.ymlfile as follows:- In all
bucket: serverless-game-fileslines, 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_IDvalue. - In all
service_account_id: <sa-id-for-functions>lines, replace<sa-id-for-functions>with the$APIGW_FN_CALLER_IDvalue. - In line
58, replace<yandex-cloud-nodejs-dev-get-state-function-id>with theyandex-cloud-nodejs-dev-get-statefunction ID. - In line
65, replace<yandex-cloud-nodejs-dev-get-config-function-id>with theyandex-cloud-nodejs-dev-get-configfunction ID. - In line
72, replace<yandex-cloud-nodejs-dev-move-function-id>with theyandex-cloud-nodejs-dev-movefunction ID. - In line
79, replace<yandex-cloud-nodejs-dev-login-function-id>with theyandex-cloud-nodejs-dev-loginfunction ID. - In line
101, replace<yandex-cloud-nodejs-dev-ws-message-function-id>with theyandex-cloud-nodejs-dev-ws-messagefunction ID. - In line
106, replace<yandex-cloud-nodejs-dev-ws-connect-function-id>with theyandex-cloud-nodejs-dev-ws-connectfunction ID. - In line
111, replace<yandex-cloud-nodejs-dev-ws-disconnect-function-id>with theyandex-cloud-nodejs-dev-ws-disconnectfunction ID. - In line
118, replace<yandex-cloud-nodejs-dev-auth-function-id>with theyandex-cloud-nodejs-dev-authfunction 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
domainfield of the previous command output. -
In Telegram, find BotFather
and type the/setdomaincommand. -
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.