Deploying NestJS to Google Kubernetes Engine (GKE)
Learn how to build, deploy and serve NestJS application on Google Kubernetes Engine and secure traffic over HTTPS.
Table of contents
Introduction
Kubernetes, an open-source platform designed by Google and now maintained by the Cloud Native Computing Foundation, revolutionizes the way we deploy, scale, and manage containerized applications across clusters of machines.
It automates various aspects of application deployment, scaling, and operations, enabling organizations to focus on their core product without worrying about the underlying infrastructure. NestJS, on the other hand, is a progressive Node.js framework for building efficient, reliable, and scalable server-side applications. By leveraging TypeScript and combining elements of Object-Oriented Programming, Functional Programming, and Functional Reactive Programming, NestJS provides an out-of-the-box application architecture that allows developers to create highly testable, scalable, loosely coupled, and easily maintainable applications.
Prerequisites
gcloud
installed. More info here.- GKE cluster created. More info here. Make sure to authenticate
kubectl
against the cluster that has been created.gcloud container clusters get-credentials <clustername> --zone <zone> --project <gcp-project>
- Setup Google Artifact Registry repository created. More info here.
Dockerize
Firstly you'd need a package your project into a docker container. As a starting point you can use the containerfile below. Place this file in the root of your project.
ARG NODE_VERSION=18.12.1-alpine
FROM node:$NODE_VERSION AS development
WORKDIR /usr/src/app
COPY package.json ./
COPY package-lock.json ./
COPY tsconfig.json tsconfig.json
COPY nest-cli.json nest-cli.json
RUN npm install
COPY . .
RUN npm run build
FROM node:alpine as production
ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}
WORKDIR /usr/src/app
COPY package.json ./
COPY package-lock.json ./
RUN npm install --prod
COPY --from=development /usr/src/app/dist ./dist
EXPOSE 3000
CMD ["node", "dist/main"]
Now open your terminal and cd
into the directory and build this container by running.
docker build -t LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE -f .
after build has successfully completed push image to Artifact Registry by running
docker push LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE
Deployment
To deploy to Kubernetes deployment file is required. It tells cluster how to create and manage your pods (containerized application) in the cluster.
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
selector:
matchLabels:
app: whatabot
template:
metadata:
labels:
app: myapp
spec:
terminationGracePeriodSeconds: 10
containers:
- name: myapp
image: LOCATION-docker.pkg.dev/PROJECT-ID/REPOSITORY/IMAGE #replace this with image URL
imagePullPolicy: Always
ports:
- containerPort: 3000
protocol: TCP
Apply with kubectl apply -f <deployment-file>.yaml
This is very generic deployment file. Ways it can be improved:
- Add health check
- Add readiness check
- Resource constraints
They are not added this time for simplicity sake.
Service
Service is a method for exposing a network application that is running as one or more Pods in your cluster. Entry point for the application in short. Service file is required to tell Kubernetes how to route traffic internally across its services.
apiVersion: v1
kind: Service
metadata:
name: myapp
spec:
type: ClusterIP
ports:
- targetPort: 3333
port: 3000
selector:
app: myapp
apply by running kubectl apply -f <service-file>.yaml
To test if everything is working correctly on cluster side you can port forward and in browser if NestJS app is responding by running:
kubectl port-forward svc/myapp 3333:3000
Now you can use browser or any Rest API client on port 3333 to test your application. All in all this step could be considered as done if you'd like to to consume your service internally. To make it publicly available on Google Cloud we'd need to create a LoadBalancer, create SSL and point the domain to newly created cluster. Feel free to follow along.
Ingress
To expose your application (service) over the internet you should use the ingress object.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp #name of ingress
spec:
rules:
- http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: myapp #name of your service object we created above.
port:
number: 3000
And apply
kubectl apply -f <ingress>.yaml
This will provision load balancer behind the scenes and it will take some time.
Run this command to get IP address of a load balancer.
kubectl get ingress myapp
Once you see an IP adress in the "ADDRESS" section you can visit browser. Traffic is served over HTTP and not HTTPS, so it's not much use for this. Let's expose our app via HTTPS.
HTTPS
Before starting delete the ingress created before.
For this we will need a global IP address. We can get one by running gcloud
command.
Remember the name of IP address.
To get IP actual address run following:
Create a managed SSL certificate by applying following manifest.
and adjust ingress object by adding annotations.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp
annotations:
kubernetes.io/ingress.global-static-ip-name: myapp-ip #name of ip
networking.gke.io/managed-certificates: myapp-cert #name of certificate
spec:
rules:
- http:
paths:
- path: /*
pathType: ImplementationSpecific
backend:
service:
name: myapp #name of service to route traffic to
port:
number: 3000
Apply managed certificate and ingress manifests. While everything is provisioning make sure to point your domain A
record to the global IP address you've created. After couple minutes visit your domain name and it should be secured by Google's Managed Certificate and be serving your traffic.
Conclusion
In this short tutorial we managed to build and deploy NestJS application to the Google Kubernetes Engine and secure it over HTTPS. Make sure to delete all resources if not used, because you will get charged.
You can find code here. If any questions - feel free to reach out!