This project contains front-end (React.js code), back-end (Express js), and MongoDB (database)
React.js has a nav bar with access to home, about and articles pages. It also allows you to login as a user, which is authenticated against firebase Articles list allows you to see summary of all blogs and clicking into one, will take you to the full page. Each blog article page shows off the text and comment, which user can provide. Upvoting is also present.
Backend, written in Express js, has multiple endpoints to allow UI to communicate.
Backend is communicating with the database
Backend will also require some env variables. Please see README.md in my-blog-backend folder.
MongoDB is used for the database. Currently it is hosted via MongoDB Atlas (on AWS).
- Docker
docker login
docker image build -t kotlily/my-blog-react-express:latest .
Fill in your values
# Environment Variables
export APP_PORT=8000
export MONGO_DB_NAME=database_name
export MONGO_CONNECTION_STRING_PREFIX="mongodb+srv://"
export MONGO_CONNECTION_STRING_OPTIONS="retryWrites=true&w=majority"
# Secrets
export MONGO_USERNAME=
export MONGO_PASSWORD=
export MONGO_CONNECTION_STRING_CLUSTER_URL=
export FIREBASE_API_KEY=
export FIREBASE_CREDENTIALS=<json as a single line string: {"name1": "value1", "name2": "value2"}>
We are running here with a different port (8001) in case we are also running locally (defaulted to 8000)
docker run -dp 8001:8000 --name my-blog kotlily/my-blog-react-express:latest
You can also put env variables in .env file and source it from commandline
docker run --env-file .env -dp 8001:8000 --name my-blog kotlily/my-blog-react-express:latest
Example: docker push <account>/<name>:<label>
docker push kotlily/my-blog-react-express:latest
Please note that you can/should consider using proper version instead of tag latest
docker-compose will read docker-compose.yaml file to operate accordingly
It will remove the need to supply things via command-line when comparing to docker commands
- Change docker-compose.yaml to use secrets from .env and not secrets files
docker-componse up -d`
This docker-compose example will spin up 3 instances of the backend and have nginx loadbalancing in front of it via exposed port 4000
Check nginx.conf file for more details
docker-compose stop|down
- minikube - Please read docs/minikube.md file for details about setting up minikube cluster
- kubectl -
# Secrets for the kubernetes cluster
# Fill in your data values in b64 encoding
apiVersion: v1
kind: Secret
metadata:
name: k8s-secrets
type: Opaque
data:
MONGO_USERNAME: <b64 encoded mongodb user>
MONGO_PASSWORD: <b64 encoded mongodb password>
MONGO_CONNECTION_STRING_CLUSTER_URL: <b64 encoded mondodb cluster URL>
FIREBASE_CREDENTIALS: <b64 encoded json string>
FIREBASE_API_KEY: <b64 encoded json string>
kubectl apply -f k8s-my-blog-secrets.yaml
secret/k8s-my-blog-secrets created
kubectl describe secret/k8s-my-blog-secrets
Name: k8s-my-blog-secrets
Namespace: default
Labels: <none>
Annotations: <none>
Type: Opaque
Data
====
MONGO_CONNECTION_STRING_CLUSTER_URL: 33 bytes
MONGO_PASSWORD: 16 bytes
MONGO_USERNAME: 11 bytes
FIREBASE_CREDENTIALS: 2279 bytes
env:
- name: APP_PORT
value: "8000"
- name: MONGO_DB_NAME
value: "react-blog-db"
- name: MONGO_CONNECTION_STRING_PREFIX
value: "mongodb+srv://"
- name: MONGO_CONNECTION_STRING_OPTIONS
value: "retryWrites=true&w=majority"
# Secrets
- name: MONGO_USERNAME
valueFrom:
secretKeyRef:
name: k8s-my-blog-secrets
key: MONGO_USERNAME
- name: MONGO_PASSWORD
valueFrom:
secretKeyRef:
name: k8s-my-blog-secrets
key: MONGO_PASSWORD
- name: MONGO_CONNECTION_STRING_CLUSTER_URL
valueFrom:
secretKeyRef:
name: k8s-my-blog-secrets
key: MONGO_CONNECTION_STRING_CLUSTER_URL
- name: FIREBASE_CREDENTIALS
valueFrom:
secretKeyRef:
name: k8s-my-blog-secrets
key: FIREBASE_CREDENTIALS
- name: FIREBASE_API_KEY
valueFrom:
secretKeyRef:
name: k8s-my-blog-secrets
key: FIREBASE_API_KEY
kubectl apply -f k8s-my-blog.yaml
minikube dashboard
$ kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
my-blog 2/2 2 2 57s
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 80m
my-blog LoadBalancer 10.105.149.38 <pending> 3333:31334/TCP 62s
NOTE: Take a note of a Loadbalancer external port 31334 that will be used to access your application
$ kubectl get pods -o wide
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-blog-768748ccb9-2tf7c 1/1 Running 0 3m14s 10.244.0.8 minikube <none> <none>
my-blog-768748ccb9-wr5dw 1/1 Running 0 3m14s 10.244.0.7 minikube <none> <none>
$ kubectl logs my-blog-768748ccb9-2tf7c
> my-blog-backend@1.0.0 start
> node src/server.js
Successfully connected to database
Server is listening on port 8000
$ kubectl logs my-blog-768748ccb9-wr5dw
> my-blog-backend@1.0.0 start
> node src/server.js
Successfully connected to database
Server is listening on port 8000
minikube ip
172.21.45.218
http://172.21.45.218:31334/
Minikube will automatically fire up a browser with the correct IP:port
minikube service my-blog
minikube service my-blog
|-----------|---------|-------------|----------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|---------|-------------|----------------------------|
| default | my-blog | 3333 | http://172.21.45.218:31334 |
|-----------|---------|-------------|----------------------------|
🎉 Opening service default/my-blog in default browser...
- kubernetes cluster - already covered by minikube
- Helm - https://helm.sh/docs/intro/install/
- Run
helm version
to see that it is installed - Run
helm list -a
to see any installed charts (there won't be any from the start)
- Run
All helm related files are in my-blog-helm
folder.
Initially helm chart was created with helm create my-blog-helm
command.
Then customizations were made to the following files
Changed appVersion attribute to latest, but in real life you would specify and change a version every time app changes
repository: # changed to reflect the image name
nameOverride:
fullnameOverride: # both can be changed to reflect a new chart name
# changed type to LoadBalancer and set port (what will be exposed) and target port (application container port)
# this is similar to what was in the k8s-my-blog.yaml used by kubectl
service:
type: LoadBalancer
port: 3333
targetPort: 8000
# Made sure to set limits on resources
limits:
cpu: 250m
memory: 128Mi
# Environment variables to be passed to our template, including secrets
# These will be utilized in templates/deployment.yaml and some passed as a commandline args
APP_PORT: 8000
MONGO_DB_NAME: mongo_db_name
MONGO_CONNECTION_STRING_PREFIX: "mongodb+srv://"
MONGO_CONNECTION_STRING_OPTIONS: "retryWrites=true&w=majority"
# Secrets
MONGO_USERNAME: username
MONGO_PASSWORD: password
MONGO_CONNECTION_STRING_CLUSTER_URL: cluster-url
FIREBASE_API_KEY: firebase-api-key
FIREBASE_CREDENTIALS: firebase-credentials
# Created new file which will serve as a template to describe secrets.
# It will be mimicking how k8s-my-blog-secrets.yaml was created for kubectl deployment
Please note that due to an issue in helm to handle json strings as env parameters,
we don't base64 encode it in yaml, but will do it prior to passing to helm via CLI
MONGO_CONNECTION_STRING_CLUSTER_URL: {{ .Values.MONGO_CONNECTION_STRING_CLUSTER_URL | b64enc }}
FIREBASE_CREDENTIALS: {{ .Values.FIREBASE_CREDENTIALS }}
Env section was added under containers specs to utilize env for vars and secrets
These values will be coming from values.yaml file as well as the secrets.yaml template
Please note that we are surrounding values with quotes
Otherwise helm is trrowing this error upon installation, which does not show up in dry-run
- Deployment in version "v1" cannot be handled as a Deployment: json: cannot unmarshal number into Go struct field EnvVar.spec.template.spec.containers.env.value of type string
env:
# environment variables
- name: "APP_PORT"
value: "{{ .Values.APP_PORT }}"
- name: "MONGO_DB_NAME"
value: "{{ .Values.MONGO_DB_NAME }}"
- name: "MONGO_CONNECTION_STRING_PREFIX"
value: "{{ .Values.MONGO_CONNECTION_STRING_PREFIX }}"
- name: "MONGO_CONNECTION_STRING_OPTIONS"
value: "{{ .Values.MONGO_CONNECTION_STRING_OPTIONS }}"
# secrets
- name: "MONGO_USERNAME"
valueFrom:
secretKeyRef:
key: "MONGO_USERNAME"
name: {{ .Release.Name }}-auth
- name: "MONGO_PASSWORD"
valueFrom:
secretKeyRef:
key: MONGO_PASSWORD
name: {{ .Release.Name }}-auth
- name: MONGO_CONNECTION_STRING_CLUSTER_URL
valueFrom:
secretKeyRef:
key: MONGO_CONNECTION_STRING_CLUSTER_URL
name: {{ .Release.Name }}-auth
- name: FIREBASE_CREDENTIALS
valueFrom:
secretKeyRef:
key: FIREBASE_CREDENTIALS
name: {{ .Release.Name }}-auth
- name: FIREBASE_API_KEY
valueFrom:
secretKeyRef:
key: FIREBASE_API_KEY
name: {{ .Release.Name }}-auth
Lint (validate syntax) of the newly created/modified chart
$ helm lint my-blog-helm
==> Linting my-blog-helm
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
These will be passed to your helm installation via command-line
Make sure you specify your correct values
# Env vars
export APP_PORT=8000
export MONGO_DB_NAME=react-blog-db
export MONGO_CONNECTION_STRING_PREFIX="mongodb+srv://"
export MONGO_CONNECTION_STRING_OPTIONS="retryWrites=true&w=majority"
# Secrets
export MONGO_USERNAME=<usr>
MONGO_PASSWORD=<pwd>
MONGO_CONNECTION_STRING_CLUSTER_URL=something.mongodb.net/
FIREBASE_API_KEY=<firebase_api_key>
FIREBASE_CREDENTIALS=<firebase_cred_json_string>
If you maintained .env
file under my-blog-backend
folder and you are using linux or git-bash,
then you can use helm.sh
script which sources .env
, then converts firebase json secret to base64
and then executes helm as usual.
chmod 700 helm_install.sh # make the file executable
./helm.sh <normal helm arguments>
./helm.sh install my-blog-release my-blog-helm
$ helm install --set APP_PORT=${APP_PORT} \
--set MONGO_DB_NAME=${MONGO_DB_NAME} \
--set MONGO_CONNECTION_STRING_PREFIX=${MONGO_CONNECTION_STRING_PREFIX} \
--set MONGO_CONNECTION_STRING_OPTIONS="${MONGO_CONNECTION_STRING_OPTIONS}" \
--set MONGO_USERNAME=${MONGO_USERNAME} \
--set MONGO_PASSWORD=${MONGO_PASSWORD} \
--set MONGO_CONNECTION_STRING_CLUSTER_URL=${MONGO_CONNECTION_STRING_CLUSTER_URL} \
--set FIREBASE_API_KEY=${FIREBASE_API_KEY} \
--set FIREBASE_CREDENTIALS=${FIREBASE_CREDENTIALS} \
my-blog-release my-blog-helm --debug --dry-run
install.go:214: [debug] Original chart version: ""
install.go:231: [debug] CHART PATH: C:\Users\ilyak\src\courses\react_website\my-blog-helm
NAME: my-blog-release
LAST DEPLOYED: Sat Feb 3 13:54:03 2024
NAMESPACE: default
STATUS: pending-install
REVISION: 1
USER-SUPPLIED VALUES:
{}
...
Here you will see generated yaml corresponding to your release
Note STATUS: pending-install
...
As you run installation, you can pass env variables when running helm
For that, first make sure those variables are set.
Example
export username=iambob
helm install --set username=${username} [chart name] [chart path]
For this specific installation
$ helm install --set APP_PORT=${APP_PORT} \
--set MONGO_DB_NAME=${MONGO_DB_NAME} \
--set MONGO_CONNECTION_STRING_PREFIX=${MONGO_CONNECTION_STRING_PREFIX} \
--set MONGO_CONNECTION_STRING_OPTIONS="${MONGO_CONNECTION_STRING_OPTIONS}" \
--set MONGO_USERNAME=${MONGO_USERNAME} \
--set MONGO_PASSWORD=${MONGO_PASSWORD} \
--set MONGO_CONNECTION_STRING_CLUSTER_URL=${MONGO_CONNECTION_STRING_CLUSTER_URL} \
--set FIREBASE_API_KEY=${FIREBASE_API_KEY} \
--set FIREBASE_CREDENTIALS=${FIREBASE_CREDENTIALS} \
my-blog-release my-blog-helm
OR
$ ./helm.sh install my-blog-release my-blog-helm