This is a hands-on tutorial to deploy basic services with kubectl and helm on kubernetes cluster running on local machine.
👉🏼 No cloud account needed.
👉🏼 No Docker desktop needed.
This tutorial is composed on Intel based Macbook, all the commands and processes should be same for any other machine except tool installation commands.
install Docker CLI
brew install docker
install hyperkit to be used as docker run time.
brew install hyperkit
install minikube to be used as kubernetes cluster
brew install minikube
configure minikube resources (optional)
minikube config set cpus 6
minikube config set memory 12g
start minikube
minikube start --driver=hyperkit --container-runtime=docker
verify minikube
minikube kubectl get nodes
!minkube get pods](./docs/minikube_get_po.png)
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
Optionally you can open minikube dashboard
minikube dashboard
It should open a dashboard UI in browser like follow:
Install helm
brew install helm
Install Argo CD CLI
brew install argocd
start minikube
minikube start --driver=hyperkit --container-runtime=docker
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
build demo app image
cd apps/demo-api
docker build -t demo-api .
Create demo app Deployment
Create a Deployment that manages a Pod. The Pod runs a Container based on the provided Docker image. We can not set image pull policy on CLI and we want to use the local image, so we will create a simple config file to from CLI itself and add the parameter for image pull policy to “Never”.
Create a deployment file.
cd ../ # if not on the root path
cd kubectl
kubectl create deployment demo-api --image=demo-api:latest --dry-run=client -o yaml > deployment.yaml
Now Modify spec.template.spec.containers
in deployment.yaml created.
spec:
containers:
- image: demo-api:latest
name: demo-api
resources: {}
and add imagePullPolicy: Never
so that it looks like:
spec:
containers:
- image: demo-api:latest
name: demo-api
resources: {}
imagePullPolicy: Never
Apply the config file with kubectl
kubectl apply -f deployment.yaml
Output is similar to:
kubectl apply -f deployment.yaml
deployment.apps/demo-api created
View the Deployment:
kubectl get deployments
The output is similar to:
NAME READY UP-TO-DATE AVAILABLE AGE
demo-api 1/1 1 1 28s
View the Pod:
kubectl get pods
The output is similar to:
NAME READY STATUS RESTARTS AGE
demo-api-7c7d9f4689-sbkns 1/1 Running 0 100s
View cluster events:
kubectl get events
View the kubectl
configuration:
kubectl config view
By default, the Pod is only accessible by its internal IP address within the
Kubernetes cluster. To make the demo-api
Container accessible from outside.
Expose the Pod to the public internet using the kubectl expose
command:
kubectl expose deployment demo-api --type=LoadBalancer --port=3000
View the Service you created:
kubectl get services
The output is similar to:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
demo-api LoadBalancer 10.101.236.39 <pending> 3000:30490/TCP 13s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 21s
Run the following command to open the app:
minikube service demo-api
The output is similar to:
|-----------|----------|-------------|----------------------------|
| NAMESPACE | NAME | TARGET PORT | URL |
|-----------|----------|-------------|----------------------------|
| default | demo-api | 3000 | http://192.168.106.3:30490 |
|-----------|----------|-------------|----------------------------|
🎉 Opening service default/demo-api in default browser...
It should open the following page in the browser:
Now you can clean up the resources you created in your cluster:
kubectl delete service demo-api
kubectl delete deployment demo-api
Optionally, stop the Minikube virtual machine (VM):
minikube stop
Optionally, delete the Minikube VM:
minikube delete
In this we will deploy multiple services. To do so we will be using multiple config files for kubernetes and will manage the config with help of kustomize. This will deploy a proper backend, frontend and db instance to power up a full application stack and use it as we do in production. This example will use the realworld blog app API and UI example apps.
We will deploy following services:
start minikube
minikube start --driver=hyperkit --container-runtime=docker
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
build blog-api app image
cd apps/realblog/blog-api
docker build -t blog-api .
Output will be similar to:
~/dev/learn/kubernetes î‚° cd apps/realblog/blog-api
docker build -t blog-api .
Sending build context to Docker daemon 2.048kB
Step 1/10 : FROM node:16.15.0
---> 9d200cd667d5
Step 2/10 : RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
---> Using cache
---> 48145b89be5e
Step 3/10 : WORKDIR /home/node
---> Using cache
---> 280c540f8d5c
Step 4/10 : RUN git clone --depth 1 https://github.com/tanem/express-bookshelf-realworld-example-app.git app
---> Using cache
---> 34dd1b5c6ded
Step 5/10 : RUN chown -R node:node /home/node/app
---> Using cache
---> d95cb606a0de
Step 6/10 : WORKDIR /home/node/app
---> Using cache
---> 740cacc09bdd
Step 7/10 : RUN npm i
---> Using cache
---> 7b86ce450a87
Step 8/10 : EXPOSE 3000
---> Using cache
---> 1b4d3ba26c7b
Step 9/10 : USER node
---> Using cache
---> 06c0b457b04a
Step 10/10 : CMD [ "./bin/start.sh" ]
---> Using cache
---> 487c135295f6
Successfully built 487c135295f6
Successfully tagged blog-api:latest
``
build blog-ui app image
cd apps/realblog/blog-ui
docker build -t blog-ui .
Output will be similar to:
~ cd apps/realblog/blog-ui
~ docker build -t blog-ui .
Sending build context to Docker daemon 2.56kB
Step 1/11 : FROM node:14.19.3 as base
---> f3ec39298d1b
Step 2/11 : RUN mkdir -p /home/node/app && chown -R node:node /home/node/app
---> Using cache
---> 49cc9653ba42
Step 3/11 : WORKDIR /home/node
---> Using cache
---> 8668cbb0d4c8
Step 4/11 : RUN git clone --depth 1 https://github.com/angelguzmaning/ts-redux-react-realworld-example-app.git app
---> Using cache
---> 24326deaa442
Step 5/11 : RUN chown -R node:node /home/node/app
---> Using cache
---> f0a11bcbee98
Step 6/11 : WORKDIR /home/node/app
---> Using cache
---> 7b2bb3a27151
Step 7/11 : RUN npm i
---> Using cache
---> bd859e464065
Step 8/11 : RUN npm run build
---> Using cache
---> f2249c73479a
Step 9/11 : USER node
---> Using cache
---> 9a1326890a11
Step 10/11 : FROM nginx
---> 1403e55ab369
Step 11/11 : COPY --from=base /home/node/app/build /usr/share/nginx/html
---> Using cache
---> 29b831cc3064
Successfully built 29b831cc3064
Successfully tagged blog-ui:latest
enable ingress plugin
Ref: https://kubernetes.io/docs/concepts/services-networking/ingress/
minikube addons enable ingress
add domain to hosts
Get minikube IP
minikube ip
update your machine’s hosts file
sudo vi /etc/hosts
Add the host realblog.local
to the file and point it to minikube IP.
Hosts should look like:
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
192.168.106.3 realblog.local
apply config with kustomize to kubectl
cd kustomize/realblog
kubectl apply -k ./
Output will be similar to:
service/realblog-api created
service/realblog-postgres created
service/realblog-ui created
persistentvolumeclaim/postgres-pv-claim created
deployment.apps/realblog-api created
deployment.apps/realblog-postgres created
deployment.apps/realblog-ui created
ingress.networking.k8s.io/realblog-ingress created
verify deployment and services
list deployments
kubectl get deployments
Output will be similar to:
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
realblog-api 1/1 1 1 2m44s
realblog-postgres 1/1 1 1 2m44s
realblog-ui 1/1 1 1 2m44s
list pods
kubectl get pods
Output will be similar to:
NAME READY STATUS RESTARTS AGE
realblog-api-785df47759-plwjw 1/1 Running 0 4m15s
realblog-postgres-5967b7666c-5t8c4 1/1 Running 0 4m15s
realblog-ui-6fc444cb95-zqcjt 1/1 Running 0 4m15s ```
list services
```shell
kubectl get services
Output will be similar to:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 12h
realblog-api LoadBalancer 10.99.28.2 <pending> 3000:30148/TCP 5m23s
realblog-postgres ClusterIP None <none> 5432/TCP 5m23s
realblog-ui LoadBalancer 10.99.96.128 <pending> 80:32187/TCP 5m23s
list ingress
kubectl get ingress
Output will be similar to:
NAME CLASS HOSTS ADDRESS PORTS AGE
realblog-ingress nginx realblog.local 192.168.106.3 80 6m21s
Use blog app
open “http://realblog.local” in browser. A blogger site should open. Create a user and add some articles to test the app follow.
Now you can clean up the resources you created in your cluster:
Run the following command from kustomize/realblog
:
kubectl delete -k ./
Optionally, stop the Minikube virtual machine (VM):
minikube stop
Optionally, delete the Minikube VM:
minikube delete
We will learn this in two phases, first we will deploy the default nginx app and then our demo-api app, second we will deploy the complete realblog project with db, API and UI services and expose it with the ingress.
start minikube
minikube start --driver=hyperkit --container-runtime=docker
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
build demo-api app image
cd apps/demo-api
docker build -t demo-api .
cd ../../
switch to helm dir
cd helm
create helm chart with helm CLI (default template)
This command creates a directory “realblog” inside which it pusts charts for a service.
helm create demo
explore helm chart created
cd demo
ls
Result should be similar to:
Chart.yaml charts templates values.yaml
install the chart
helm install demo .
Results should be similar to:
NAME: demo
LAST DEPLOYED: Tue Jan 3 19:46:54 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=demo,app.kubernetes.io/instance=demo" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
expose the service to access from machine To access the app on http follow on screen instructions or run
kubectl --namespace default port-forward deployment/demo 8080:80
Result should be similar to:
Forwarding from 127.0.0.1:8080 -> 80
Forwarding from [::1]:8080 -> 80
Handling connection for 8080
Handling connection for 8080
verify on browser
Open http://localhost:8080 in browser and you should be able to see a nginx welcome screen.
switch to demo chart created in previous stage
cd helm/demo
update chart
Update the image repository and tag to use demo-api image and latest tag in values.yaml file, to do so in the file helm/demo/values.yaml
update following:
image.repository : demo-api
image.tag : latest
service.port : 3000
upgrade the deployment
helm upgrade demo .
Result should be similar to:
Release "demo" has been upgraded. Happy Helming!
NAME: demo
LAST DEPLOYED: Wed Jan 4 12:33:24 2023
NAMESPACE: default
STATUS: deployed
REVISION: 3
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=demo,app.kubernetes.io/instance=demo" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
expose the service to access from machine To access the app on http follow on screen instructions or run
kubectl --namespace default port-forward deployment/demo 8080:3000
Result should be similar to:
Forwarding from 127.0.0.1:8080 -> 3000
Forwarding from [::1]:8080 -> 3000
Handling connection for 8080
Handling connection for 8080
verify on browser
Open http://localhost:8080 in browser and you should be able to see express app.
delete deployments
Delete the deployent to avoid confusion while trying next/other stages.
helm uninstall demo --wait
It should return :
release "demo" uninstalled
start minikube
minikube start --driver=hyperkit --container-runtime=docker
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
build blog-api app image
cd apps/realblog/blog-api
docker build -t blog-api .
build blog-ui app image
cd apps/realblog/blog-ui
docker build -t blog-ui .
switch to helm charts directory for realblog
cd helm/realblog
install charts
helm install realblog .
Result should be similar to:
NAME: realblog
LAST DEPLOYED: Wed Jan 4 12:47:08 2023
NAMESPACE: default
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
http://realblog.local/
http://realblog.local/api
Add cluster IP to local hosts entry for dns resolution
Get minikube IP
minikube ip
update your machine’s hosts file
sudo vi /etc/hosts
Add the host realblog.local
to the file and point it to minikube IP.
Hosts should look like:
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
192.168.106.3 realblog.local
In this the IP 192.168.106.3
is the IP of cluster node returned by command minikube ip
verify on browser
Open http://realblog.local/ in browser and you should be able to see blog app UI.
Now you can clean up the resources you created in your cluster:
helm uninstall realblog --wait
Optionally, stop the Minikube virtual machine (VM):
minikube stop
Optionally, delete the Minikube VM:
minikube delete
start minikube
minikube start --driver=hyperkit --container-runtime=docker
point terminal’s Docker CLI to the Docker instance inside minikube
eval $(minikube docker-env)
build blog-api app image
cd apps/realblog/blog-api
docker build -t blog-api .
build blog-ui app image
cd apps/realblog/blog-ui
docker build -t blog-ui .
add domain to hosts
Get minikube IP
minikube ip
update your machine’s hosts file
sudo vi /etc/hosts
Add the host realblog.local
to the file and point it to minikube IP.
Hosts should look like:
127.0.0.1 localhost
255.255.255.255 broadcasthost
::1 localhost
192.168.106.3 realblog.local
In this the IP 192.168.106.3
is the IP of cluster node returned by command minikube ip
Run argo CD
kubectl create namespace argocd
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl port-forward svc/argocd-server -n argocd 8080:443
Results should be similar to:
namespace/argocd created
customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/applicationsets.argoproj.io created
customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
serviceaccount/argocd-application-controller created
serviceaccount/argocd-applicationset-controller created
serviceaccount/argocd-dex-server created
serviceaccount/argocd-notifications-controller created
serviceaccount/argocd-redis created
serviceaccount/argocd-repo-server created
serviceaccount/argocd-server created
role.rbac.authorization.k8s.io/argocd-application-controller created
role.rbac.authorization.k8s.io/argocd-applicationset-controller created
role.rbac.authorization.k8s.io/argocd-dex-server created
role.rbac.authorization.k8s.io/argocd-notifications-controller created
role.rbac.authorization.k8s.io/argocd-server created
clusterrole.rbac.authorization.k8s.io/argocd-application-controller created
clusterrole.rbac.authorization.k8s.io/argocd-server created
rolebinding.rbac.authorization.k8s.io/argocd-application-controller created
rolebinding.rbac.authorization.k8s.io/argocd-applicationset-controller created
rolebinding.rbac.authorization.k8s.io/argocd-dex-server created
rolebinding.rbac.authorization.k8s.io/argocd-notifications-controller created
rolebinding.rbac.authorization.k8s.io/argocd-redis created
rolebinding.rbac.authorization.k8s.io/argocd-server created
clusterrolebinding.rbac.authorization.k8s.io/argocd-application-controller created
clusterrolebinding.rbac.authorization.k8s.io/argocd-server created
configmap/argocd-cm created
configmap/argocd-cmd-params-cm created
configmap/argocd-gpg-keys-cm created
configmap/argocd-notifications-cm created
configmap/argocd-rbac-cm created
configmap/argocd-ssh-known-hosts-cm created
configmap/argocd-tls-certs-cm created
secret/argocd-notifications-secret created
secret/argocd-secret created
service/argocd-applicationset-controller created
service/argocd-dex-server created
service/argocd-metrics created
service/argocd-notifications-controller-metrics created
service/argocd-redis created
service/argocd-repo-server created
service/argocd-server created
service/argocd-server-metrics created
deployment.apps/argocd-applicationset-controller created
deployment.apps/argocd-dex-server created
deployment.apps/argocd-notifications-controller created
deployment.apps/argocd-redis created
deployment.apps/argocd-repo-server created
deployment.apps/argocd-server created
statefulset.apps/argocd-application-controller created
networkpolicy.networking.k8s.io/argocd-application-controller-network-policy created
networkpolicy.networking.k8s.io/argocd-applicationset-controller-network-policy created
networkpolicy.networking.k8s.io/argocd-dex-server-network-policy created
networkpolicy.networking.k8s.io/argocd-notifications-controller-network-policy created
networkpolicy.networking.k8s.io/argocd-redis-network-policy created
networkpolicy.networking.k8s.io/argocd-repo-server-network-policy created
networkpolicy.networking.k8s.io/argocd-server-network-policy created
Forwarding from 127.0.0.1:8080 -> 8080
Forwarding from [::1]:8080 -> 8080
Handling connection for 8080
Handling connection for 8080
Use Argo CD UI
Open https://localhost:8080/ in your browser and you should see login page of the argocd.
Login with username and password:
Username is admin
and password you can get from following command:
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d; echo
After login you should see the app explorer page like follow:
get CLI ready
You can create app from UI also but this guide will use CLI to create the app.
argocd login localhost:8080
the result should be similar to:
WARNING: server certificate had error: x509: “Argo CD” certificate is not trusted. Proceed insecurely (y/n)? y
Username: admin
Password:
'admin:login' logged in successfully
Context 'localhost:8080' updated
use the same username and password used to login on UI.
In this we will use the same helm charts used for deployment of services and images created in set up section to create deployment through argocd.
create app with CLI
argocd app create realblog --repo https://github.com/satyamyadav/kubernetes-101.git --path helm/realblog --dest-server https://kubernetes.default.svc --dest-namespace default
verify on browser
open https://localhost:8080/applications/argocd/realblog?view=tree&resource=kind%3ADeployment It should show the deployments.
Sync the app if it is not synced.
verify app
open http://realblog.local you should see the blog app UI home page.
Now you can clean up the resources you created in your cluster: you can delete the app from argocd UI also.
argocd app delete realblog
Optionally, stop the Minikube virtual machine (VM):
minikube stop
Optionally, delete the Minikube VM:
minikube delete