How to Deploy a Scalable Web Application with Aptible and Kubernetes

Written by abrahamdahunsi | Published 2023/10/13
Tech Story Tags: devops | aptible | aptible-guide | web-app-development | kubernetes-deployment | deploying-your-app-to-aptible | build-scalable-applications | scalable-web-application

TLDRThis comprehensive guide takes you through the steps of deploying a secure and scalable web application using Aptible and Kubernetes. It covers everything from configuring environment variables to creating Kubernetes deployments and services. Learn how to expose your app to the internet with Aptible endpoints, test its deployment, and apply best practices for health checks, rolling updates, and autoscaling.via the TL;DR App

Deploying a web application can be a challenging task, especially if you want to ensure that your application can handle high traffic, provide high availability, and comply with security and regulatory standards. Fortunately, there are tools and platforms that can help you achieve these goals, such as Aptible and Kubernetes.

Aptible is a platform that simplifies the deployment and management of secure and compliant web applications. Aptible provides features such as automated provisioning, encryption, backups, logging, monitoring, and auditing for your web applications. Aptible also helps you comply with various standards and regulations, such as HIPAA, SOC 2, ISO 27001, and GDPR. Learn More

Learn why Aptible's Engineering-Led Support and Production-Ready SLAs makes them a great choice for you. Read here

Kubernetes is an open-source system that automates the deployment, scaling, and management of containerized applications. Kubernetes allows you to run your web application in a cluster of servers, called nodes, that can be distributed across different regions and providers. Kubernetes also provides features such as load balancing, service discovery, self-healing, horizontal scaling, rolling updates, and more.

In this article, you will learn how to deploy a scalable web application with Aptible and Kubernetes. You will use Aptible to create a secure and compliant environment for your web application and Kubernetes to orchestrate the deployment and scaling of your web application across multiple nodes. You will also learn how to use some of the key concepts and components of Aptible and Kubernetes, such as:

  • Pods: The smallest unit of deployment in Kubernetes. A pod is a group of one or more containers that share the same network and storage resources.

  • Services: An abstraction that defines a logical set of pods and a policy to access them. A service provides a stable endpoint for your pods, regardless of their location or state.

  • Deployments: An abstraction that manages the creation and update of pods. A deployment allows you to specify the desired state of your pods, such as the number of replicas, the image version, the update strategy, etc. Kubernetes will ensure that your pods match the desired state.

  • Aptible Environments: A logical grouping of resources in Aptible. An environment consists of one or more apps, which are the web applications that you deploy with Aptible; one or more databases, which are the data stores that you provision with Aptible; and one or more endpoints, which are the entry points for accessing your apps and databases.

Content Overview

  • Prerequisites

  • Step 1: Create an Aptible app and configure your environment variables

  • Step 2: Build and push your web application image to a container registry

  • Step 3: Create a Kubernetes deployment and service for your web application

  • Step 4: Expose your web application service to the internet with Aptible endpoints

  • Step 5: Test and verify your web application deployment

  • Conclusion

Prerequisites

To follow this article, you will need:

  • An Aptible account. You can sign up for a free trial here.
  • A Kubernetes cluster. You can use any provider that supports Kubernetes, such as Google Cloud Platform (GCP), Amazon Web Services (AWS), Microsoft Azure, etc. You can also use a local cluster for testing purposes, such as Minikube or Docker Desktop.
  • A web application codebase. You can use any programming language or framework that supports web development, such as Ruby on Rails, Django, Node.js, etc. You can also use an existing web application or clone one from GitHub.
  • A Dockerfile. A file that defines how to build a Docker image for your web application. You can learn how to write a Dockerfile here.
  • A kubectl command-line tool. A tool that allows you to interact with your Kubernetes cluster. You can install kubectl here.
  • A Git command-line tool. A tool that allows you to manage your code versioning and deployment with Aptible.

By the end of this article, you will have deployed a scalable web application with Aptible and Kubernetes and learned how to use some of the best practices and tools for web development.

Let's get started!

Step 1: Create an Aptible app and configure your environment variables

Before you can deploy your web application with Aptible, you need to create an app and configure your environment variables. An app is a logical unit of deployment in Aptible that represents your web application. Environment variables are key-value pairs that store configuration settings for your app, such as database credentials, API keys, etc.

You can use either the Aptible CLI or the Aptible dashboard to create an app and configure your environment variables. In this article, we will use the Aptible CLI, which is a command-line tool that allows you to interact with Aptible.

To create an app with the Aptible CLI, you need to:

  • Log in to your Aptible account using the aptible login command. You will be prompted to enter your email and password, and then a verification code sent to your email.

  • Choose an environment for your app using the aptible switch command. An environment is a logical grouping of resources in Aptible, such as apps, databases, and endpoints. You can have multiple environments for different purposes, such as development, staging, or production. In this article, we will use the production environment, which is created by default when you sign up for Aptible.

  • Create an app using the aptible apps:create command. You need to provide a name for your app, which must be unique within your environment. In this article, we will name our app web-app.

Please you can use any name other than “web-app"

Here is an example of the commands to create an app with the Aptible CLI:

$ aptible login
Email: [email protected]
Password: ********
Verification code: 123456
Logged in as [email protected]

$ aptible switch production
Switched to production

$ aptible apps:create web-app
App web-app created!

To configure your environment variables with the Aptible CLI, you need to:

- List the existing environment variables for your app using the aptible config command. You will see some default environment variables that are set by Aptible, such as APTIBLE_APP_ID, APTIBLE_APP_HANDLE, etc.

- Add new environment variables for your app using the aptible config:set command. You need to provide the key and value for each environment variable, separated by a space. You can add multiple environment variables at once by separating them with a space as well. In this article, we will add some environment variables that are required by our web application, such as DATABASE_URL, SECRET_KEY_BASE, RAILS_ENV, etc.

- Verify that your environment variables are set correctly using the aptible config command again.

Here is an example of the commands to configure your environment variables with the Aptible CLI:

$ aptible config --app web-app
APTIBLE_APP_ID=123
APTIBLE_APP_HANDLE=web-app
...

$ aptible config:set --app web-app 
DATABASE_URL=postgres://user:password@host:port/db SECRET_KEY_BASE=abcdefg 
RAILS_ENV=production
DATABASE_URL=postgres://user:password@host:port/db
SECRET_KEY_BASE=abcdefg
RAILS_ENV=production

$ aptible config --app web-app
APTIBLE_APP_ID=123
APTIBLE_APP_HANDLE=web-app
DATABASE_URL=postgres://user:password@host:port/db
SECRET_KEY_BASE=abcdefg
RAILS_ENV=production
...

You have now created an app and configured your environment variables with Aptible.

Step 2: Build and push your web application image to a container registry

Next, you need to build and push your web application image to a container registry. A web application image is a file that contains all the code, dependencies, and configuration of your web application. A container registry is a service that stores and distributes your web application images.

You can use Docker or any other similar tool to build your web application image and tag it with a version number. Docker is a software that allows you to create, run, and share containers. A container is an isolated environment that runs your web application image. You can install Docker here.

To build your web application image with Docker, you need to:

- Navigate to the directory where your web application codebase and Dockerfile are located using the cd command.

- Build your web application image using the docker build command. You need to provide a name and a tag for your image, which must be unique within your container registry. The name and tag are separated by a colon (:). In this article, we will name our image web-app and tag it with v1.

- Verify that your web application image is built correctly using the docker images command. You will see your image listed along with its name, tag, size, and creation date.

Here is an example of the commands to build your web application image with Docker:

$ cd web-app
$ docker build -t web-app:v1 .
Sending build context to Docker daemon  2.048kB
Step 1/8 : FROM ruby:2.7
...

Successfully built 123456789abc
Successfully tagged web-app:v1

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
web-app      v1        123456789abc   10 seconds ago   1.23GB

To push your web application image to a container registry, you need to:

- Log in to your container registry account using the docker login command. You will be prompted to enter your username and password, and then a verification code if required.

- Push your web application image using the docker push command. You need to provide the name and tag of your image, as well as the name of your container registry. The name of your container registry is usually prefixed to the name of your image, separated by a slash (/). In this article, we will use Docker Hub as our container registry, which is a public service that hosts and distributes Docker images. We will prefix our image name with our Docker Hub username, which is user.

- Verify that your web application image is pushed correctly using the docker images command again. You will see a message indicating that your image was pushed successfully.

Here is an example of the commands to push your web application image to Docker Hub:

$ docker login
Username: user
Password: ********
Verification code: 123456
Login Succeeded

$ docker push user/web-app:v1
The push refers to repository 
[docker.io/user/web-app]
...
123456789abc: Pushed 
v1: digest: sha256:abcdefg size: 1234

$ docker images
REPOSITORY   TAG       IMAGE ID       CREATED          SIZE
user/web-app v1        123456789abc   10 seconds ago   1.23GB

Alternatively, you can use another container registry of your choice, such as Google Container Registry (GCR), Amazon Elastic Container Registry (ECR), Microsoft Azure Container Registry (ACR), etc. Each container registry may have different requirements and steps for logging in, pushing, and pulling images. You can refer to their respective documentation for more details.

You have now built and pushed your web application image to a container registry.

Step 3: Create a Kubernetes deployment and service for your web application

After you have pushed your web application image to a container registry, you need to create a Kubernetes deployment and service for your web application. A deployment is an object that manages the creation and update of your pods, which are the smallest unit of deployment in Kubernetes. A service is an object that defines a logical set of pods and a policy to access them, such as load balancing, service discovery, etc.

You can use YAML files to define your Kubernetes deployment and service objects for your web application. YAML is a human-readable data format that allows you to specify the configuration and properties of your Kubernetes objects. You can use any text editor to create and edit YAML files.

To define your Kubernetes deployment object with a YAML file, you need to:

- Create a file with a .yaml or .yml extension, such as deployment.yaml.

- Specify the apiVersion, kind, metadata, and spec fields for your deployment object.

These fields are required for any Kubernetes object:

  • The apiVersion field indicates the version of the Kubernetes API that you are using. In this article, we will use apps/v1, which is the latest stable version for deployments.

  • The kind field indicates the type of the Kubernetes object. In this case, it is Deployment.

  • The metadata field contains information about the name, labels, annotations, etc. of your deployment object. Labels are key-value pairs that can be used to identify and group your Kubernetes objects. Annotations are key-value pairs that can be used to store additional information or metadata about your Kubernetes objects.

  • The spec field contains the desired state and behavior of your deployment object, such as the number of replicas, the pod template, the update strategy, etc.

  • Specify the selector, template, and eplicas fields within the spec field for your deployment object. These fields are required for any deployment object.

  • The selector field defines how to select the pods that belong to your deployment object. You need to provide a matchLabels field that contains one or more labels that match the labels of your pods.

  • The emplate field defines the pod template that will be used to create new pods for your deployment object. You need to provide a metadata field that contains the labels of your pods, and a spec field that contains the configuration and properties of your pods, such as the containers, volumes, environment variables, etc.

  • The replicas field defines the number of pods that you want to run for your deployment object. You can specify any positive integer value. In this article, we will use 3 replicas for our deployment object.

- Specify the containers, image, and ports fields within the spec field of the pod template for your deployment object.

These fields are required for any pod that runs a containerized application.

  • The containers field defines one or more containers that will run in your pod. You need to provide a name and an image for each container. The name can be any string that identifies your container within your pod. The image is the name and tag of your web application image that you pushed to your container registry in the previous step. You need to prefix the image name with the name of your container registry, separated by a slash (/). In this article, we will prefix our image name with our Docker Hub username, which is user.

  • The image field defines the name and tag of your web application image that you pushed to your container registry in the previous step. You need to prefix the image name with the name of your container registry, separated by a slash (/).

- The ports field defines one or more ports that will be exposed by your container. You need to provide a name and a containerPort for each port. The name can be any string that identifies your port within your container. The containerPort is the port number that your web application listens on within your container. In this article, we will use 3000 as our containerPort for our web application.

Here is an example of a YAML file that defines a Kubernetes deployment object for our web application:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: web-app-deployment
 labels:
   app: web-app
 spec:
  selector:
   matchLabels:
     app: web-app
  template:
   metadata:
    labels:
     app: web-app
   spec:
    containers:
    - name: web-app-container
      image: user/web-app:v1
      ports:
      - name: http
        containerPort: 3000
 replicas:3

To define your Kubernetes service object with a YAML file, you need to:

- Create a file with a .yaml or .yml extension, such as service.yaml.

- Specify the apiVersion, kind, metadata, and spec fields for your service object. These fields are required for any Kubernetes object.

• The mapiVersion field indicates the version of the Kubernetes API that you are using. In this article, we will use v1, which is the latest stable version for services.

• The kind field indicates the type of the Kubernetes object. In this case, it is Service.

• The metadata field contains information about the name, labels, annotations, etc. of your service object. Labels are key-value pairs that can be used to identify and group your Kubernetes objects. Annotations are key-value pairs that can be used to store additional information or metadata about your Kubernetes objects.

• The spec field contains the desired state and behavior of your service object, such as the type, ports, selector, etc.

- Specify the type, ports, and selector fields within the spec field for your service object. These fields are required for any service object.

• The type field defines the type of your service object, which determines how your service will be exposed to the outside world. There are four types of services: ClusterIP, NodePort, LoadBalancer, and ExternalName. In this article, we will use ClusterIP as our service type, which is the default type that creates a virtual IP address within the cluster that can be used to access your pods.

• The ports field defines one or more ports that will be exposed by your service. You need to provide a name, a port, and a targetPort for each port. The name can be any string that identifies your port within your service. The port is the port number that your service will expose to the outside world. The targetPort is the port number that your service will forward the traffic to within your pod. In this article, we will use 80 as our port and 3000 as our targetPort for our web application.

• The selector field defines how to select the pods that belong to your service object. You need to provide one or more labels that match the labels of your pods.

Here is an example of a YAML file that defines a Kubernetes service object for our web application:

apiVersion: v1
kind: Service
metadata:
 name: web-app-service
 labels:
  app: web-app
spec:
 type: ClusterIP
 ports:
 - name: http
   port: 80
   targetPort: 3000
   selector:
    app: web-app

To apply your YAML files to your Kubernetes cluster, you need to use kubectl .

To apply your YAML files with kubectl, you need to:

- Navigate to the directory where your YAML files are located using the cd command.

- Apply your YAML files using the kubectl apply command. You need to provide the name of your YAML file or a directory that contains multiple YAML files as an argument. You can also use the -f flag to specify the file or directory name. In this article, we will use a directory named k8s that contains our YAML files for our deployment and service objects.

- Verify that your deployment and service objects are created correctly using the kubectl get command. You can provide the name of your object or a type of object as an argument. You can also use the -o wide flag to get more details about your objects.

Here is an example of the commands to apply and verify our YAML files with kubectl:

$ cd k8s
$ kubectl apply -f .
deployment.apps/web-app-deployment created

$ kubectl get deployment web-app-deployment -o wide
NAME                READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS          IMAGES           SELECTOR
web-app-deployment  3/3     3            3           10s   web-app-container   user/web-app:v1  app=web-app

$ kubectl get service web-app-service -o wide
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE   SELECTOR
web-app-service  ClusterIP   10.100.200.300   <none>        80/TCP    10s   app=web-app

You have now created a Kubernetes deployment and service for your web application.

Step 4: Expose your web application service to the internet with Aptible endpoints

After you have created a Kubernetes service for your web application, you need to expose your web application service to the internet with Aptible endpoints. An endpoint is an object that defines an entry point for accessing your web application service from the outside world. Aptible provides features such as SSL encryption, custom domains, path routing, etc. for your endpoints.

You can use the Aptible CLI to create an endpoint for your web application service.

To create an endpoint with the Aptible CLI, you need to:

- Create an endpoint using the aptible endpoints:create command. You need to provide a name for your endpoint, which must be unique within your environment. You also need to provide some options for your endpoint, such as the protocol, port, hostname, path, etc.

• The --type option defines the type of your endpoint, which determines how your endpoint will be exposed to the outside world. There are two types of endpoints: TCP and HTTPS. In this article, we will use HTTPS as our endpoint type, which is the default type that creates a secure and encrypted connection between your web application service and your clients.

• The --internal option defines whether your endpoint will be internal or external. An internal endpoint can only be accessed from within your Aptible environment, while an external endpoint can be accessed from anywhere on the internet. In this article, we will use an external endpoint, which is the default option.

• The --port option defines the port number that your endpoint will listen on. You need to provide a port number that matches the port number that your web application service exposes. In this article, we will use 80 as our port number for our web application service.

• The --hostname option defines the hostname that your endpoint will use. You can provide a custom domain name that you own and have configured with DNS records, or use a default domain name that is provided by Aptible. In this article, we will use a custom domain name that we own and have configured with DNS records, which is web-app.example.com.

If you don't want to buy a custom domain name, please note that Aptible will give you a free subdomain that you can use for your application and you can change it later to a custom domain anytime you want.

• The --path option defines the path that your endpoint will use. You can provide a specific path that you want to route to your web application service, or use a wildcard path that matches any path. In this article, we will use a wildcard path that matches any path, which is /.

- Verify that your endpoint is created correctly using the aptible endpoints:list command. You will see your endpoint listed along with its name, type, internal flag, port, hostname, path, etc.

Here is an example of the commands to create an endpoint with the Aptible CLI:

$ aptible switch production
Switched to production

$ aptible endpoints:create --type https --internal false --port 80 --hostname web-app.example.com --path / web-app
Endpoint web-app created!

$ aptible endpoints:list --app web-app
NAME     TYPE   INTERNAL   PORT   HOSTNAME             PATH
web-app  HTTPS  false      80     web-app.example.com  /

Aptible will automatically provide and manage SSL certificates for your endpoint using Let's Encrypt, which is a free and open certificate authority that provides domain validation certificates. You do not need to do anything to enable SSL encryption for your endpoint. However, you need to make sure that your custom domain name is configured with DNS records that point to your Aptible environment. You can find the DNS records for your environment on the Aptible dashboard under the Domains tab.

You have now exposed your web application service to the internet with Aptible endpoints.

Step 5: Test and verify your web application deployment

After you have exposed your web application service to the internet with Aptible endpoints, you need to test and verify your web application deployment. You can use curl to send requests to your web application endpoint and check the responses. You can also use kubectl to monitor the status and logs of your web application pods and service.

You can use curl to send requests to your web application endpoint and check the responses. Curl is a command-line tool that allows you to transfer data from or to a server using various protocols, such as HTTP, HTTPS, FTP, etc.

To send requests to your web application endpoint with curl, you need to:

- Use the curl command with the URL of your web application endpoint as an argument. The URL is composed of the protocol, hostname, and path of your endpoint. In this article, we will use https://web-app.example.com/ as our URL for our web application endpoint.

- Check the output of the curl command on the terminal window. You will see the status code, headers, and body of the response from your web application endpoint. The status code indicates the result of your request, such as 200 for success, 404 for not found, 500 for internal server error, etc. The headers contain information about the server, content type, date, etc. The body contains the actual content of the response, such as HTML, JSON, XML, etc.

Here is an example of how to send requests to your web application endpoint with curl:

$ curl https://web-app.example.com/
HTTP/1.1 200 OK
Server: nginx/1.19.10
Date: Tue, 10 Oct 2023 19:19:51 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 1234
Connection: keep-alive
X-Powered-By: Aptible
<html>
<head>
  <title>Web App</title>
</head>
<body>
  <h1>Welcome to Web App!</h1>
  <p>This is a sample web application deployed with Aptible and Kubernetes.</p>
</body>
</html>

You can also use kubectl to monitor the status and logs of your web application pods and service. Kubectl is a command-line tool that allows you to interact with your Kubernetes cluster. You can install kubectl here.

To monitor the status of your web application pods and service with kubectl, you need to:

- Open a terminal window on your computer.

- Use the kubectl get command with the name or type of your object as an argument. You can also use the `-o wide` flag to get more details about your object, such as the node name, pod IP, labels, etc.

- Check the output of the kubectl get command on the terminal window. You will see information about your object, such as the name, ready state, up-to-date state, available state, age, etc.

Here is an example of how to monitor the status of your web application pods and service with kubectl:

$ kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS   AGE     IP              NODE       NOMINATED NODE   READINESS GATES
web-app-deployment-f4c6d9b4f-4jw9m   1/1     Running   0          5m23s   10.100.200.301  node-1     <none>           <none>
web-app-deployment-f4c6d9b4f-hx7kq   1/1     Running   0          5m23s   10.100.200.302  node-2     <none>           <none>
web-app-deployment-f4c6d9b4f-z9v7n   1/1     Running   0          5m23s   10.100.200.303  node-3     <none>           <none>

$ kubectl get service -o wide
NAME             TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE     SELECTOR
web-app-service  ClusterIP   10.100.200.300   <none>       80/TCP    5m23s   app=web-app

To monitor the logs of your web application pods with kubectl, you need to:

- Open a terminal window on your computer.

- Use the kubectl logs command with the name of your pod as an argument. You can also use the -f flag to follow the logs in real time, or the --since flag to specify a time range for the logs.

- Check the output of the kubectl logs command on the terminal window. You will see the logs of your pod, which contain the messages and errors from your web application container.

Here is an example of how to monitor the logs of your web application pods with kubectl:

$ kubectl logs web-app-deployment-f4c6d9b4f-4jw9m -f --since=10m
[2023-10-10 19:19:51] INFO  WEBrick 1.6.0
[2023-10-10 19:19:51] INFO  ruby 2.7.0 (2019-12-25) [x86_64-linux]
[2023-10-10 19:19:51] INFO  WEBrick::HTTPServer#start: pid=1 port=3000
I, [2023-10-10T19:20:12.345678 #1]  INFO -- : Started GET "/" for 10.100.200.400 at 2023-10-10 19:20:12 +0000
I, [2023-10-10T19:20:12.456789 #1]  INFO -- : Processing by HomeController#index as HTML
I, [2023-10-10T19:20:12.567890 #1]  INFO -- :   Rendering home/index.html.erb within layouts/application
I, [2023-10-10T19:20:12.678901 #1]  INFO -- :   Rendered home/index.html.erb within layouts/application (Duration: 11.0ms | Allocations: 1234)
I, [2023-10-10T19:20:12.789012 #1]  INFO -- : Completed 200 OK in 33ms (Views: 22.0ms | ActiveRecord: 0.0ms | Allocations: 4567)

You have now tested and verified your web application deployment with Aptible and Kubernetes. You have learned how to use some of the best practices and tools for web development, such as Aptible, Kubernetes, Docker, curl, kubectl, etc.

Congratulations! You have successfully deployed a scalable web application with Aptible and Kubernetes!

Conclusion

In this article, you have learned how to deploy a scalable web application with Aptible and Kubernetes. You have followed these steps:

- Create an Aptible app and configure your environment variables

- Build and push your web application image to a container registry

- Create a Kubernetes deployment and service for your web application

- Expose your web application service to the internet with Aptible endpoints

- Test and verify your web application deployment

By following these steps, you have achieved these outcomes:

- You have created a secure and compliant environment for your web application with Aptible

- You have orchestrated the deployment and scaling of your web application across multiple nodes with Kubernetes

- You have exposed your web application to the internet with SSL encryption, custom domains, path routing, etc. with Aptible

- You have tested and verified your web application using curl and kubectl

Here are some tips and best practices for deploying a scalable web application with Aptible and Kubernetes:

- Use health checks to monitor the readiness and liveness of your pods and containers. Health checks are probes that can be configured to check the status of your pods and containers at regular intervals. If a pod or container fails a health check, Kubernetes will restart or replace it automatically. You can learn how to configure health checks here.

- Use rolling updates to update your pods and containers without downtime. Rolling updates are a strategy that can be configured to update your pods and containers gradually, one by one, while maintaining the availability of your service.

- Use autoscaling to adjust the number of pods and nodes according to the load of your service. Autoscaling is a feature that can be configured to scale your pods and nodes up or down automatically, based on the CPU utilization, memory usage, or custom metrics of your service.

Additional Resources

  • Aptible Documentation: The official documentation for Aptible, which covers topics such as getting started, deploying apps, managing databases, creating endpoints, etc.
  • Kubernetes Documentation: The official documentation for Kubernetes, which covers topics such as concepts, tasks, tutorials, reference, etc.
  • Docker Documentation: The official documentation for Docker.

I hope you enjoyed this article and learned something new. Happy Deployment!


Written by abrahamdahunsi | I enjoy solving problems by writing code and breaking down technical contents by writing.
Published by HackerNoon on 2023/10/13