Developing with Kubernetes
Posted on Fri 20 November 2020 in Software • 5 min read
Following on with previous posts on this blog. This post will be going through how to develop & later on deploy our fibonacci application we previously built in a multi-container context. To reiterate we will be using the following technologies:
|Docker||Docker will be used to containerization of the services inside our app|
|Express||Express is responsible for the API between Redis, PostgreSQL and Vue|
|Redis||Redis will store/retrieve any local data used by our users|
|PostgreSQL||PostgreSQL will be our database|
|Nginx||Nginx will handle the routing between our services|
Previously we made use of services provided by AWS for Redis & PostgreSQL, in this post these services will be run inside their own pods.
This post is apart of a series on Docker/Kubernetes, find the other posts at:
- Intro to Docker
- Develop and Develop with Docker
- Develop and Develop Multi Container Applications
- Intro to Kubernetes
- Developing with Kubernetes
- Deploying with Kubernetes
- Deploy a Node Web App to AWS Elastic Beanstalk with Docker
To make our application run on Kubernetes, we need to make a few changes. In essence though the architecture will
The above chart was made with mermaid.js.
For each of the deployments (except the worker) we will be creating a ClusterIP service for maintaining the connection between each of the deployments. The PostgreSQL PVC is for a Persistent Volume Claim, which allows our node to consume abstract storage resources (we'll go into this further later on).
We need to set up a ClusterIP service for each of our deployments except the worker deployment. This will allow our services to communicate with others inside the node.
To do this, we create a configuration
1 2 3 4 5 6 7 8 9 10
Note that we keep the same selector as our deployments.
ClusterIP is the default ServiceType in kubernetes. We can create multiple objects in a single yaml file by separating with
Persistent Volume Claim
A persistent volume allows a pod to share memory and read/write data on the host PC. The use-case for this are if our PostgreSQL database pod had crashed without a PVC, the data would essentially be lost as it was entirely contained within the pod, but with a persistent volume claim, our pod can restart by using the data that is stored on the host PC.
We use a PVC over a persistent volume or a volume, as this allows us to declare the requirement that our pod needs storage at some point, rather than create an instance of storage prematurely, and gives more control to Kubernetes to solve our storage problem for us.
Volumes on their lonesome are tied directly to pods, and thus if the pod crashes, the volume will be lost. Hence why we are not using volumes in this case. A volume is also different between Kubernetes and Docker. A persistant volume is not tied directly to pods, but is tied to the node overall, and thus if the node as a whole crashes, the data will be lost.
Similar to how we attach a ClusterIP service to a pod, let's attach a PVC to a pod. What this will do, will instruct Kubernetes to 'advertise' storage space for pods to consume. If a pod consumes this claim, then it'll go and create a persistent volume or point to an already created persistent volume for us automatically. There is also many access modes we can define for our PVC:
|ReadWriteOnce||Can be used by a single node|
|ReadOnlyMany||Multiple nodes can read|
|ReadWriteMany||Can be read/written to by many nodes|
1 2 3 4 5 6 7 8 9 10
This configuration will allow a single node access to 2 gigabytes of storage space available for both read & write operations.
Ensure that the pods that will access this volume claim have provided it under the
spec tag in the pod configuration.
Similarly, we can specify resource requirements/restraints in pods (eg, CPU resources).
Some of our pods depend on environment variables being set to work correctly (eg, REDIS_HOST, PGUSER, etc). We add using the
env key to our
For example, for our worker to connect to the redis deployment:
1 2 3 4 5 6 7 8 9
Note that for the value of the
REDIS_HOST we are stating the name of the ClusterIP service we had previously set up. Kubernetes will automatically resolve this for us to be the correct IP, how neat!
Secrets are another type of object inside of Kubernetes that are used to store sensitive information we don't want to live in the plain text of the configuration files. We do this through a
There are 3 types of secret types,
tls, most of the time we'll be making use of the
generic secret type. Similar to how we consume other services, we will be consuming the secret from the
secret_name parameter. The names (but not the value) can always be retrieved through
kubectl get secrets.
Secrets are pertained only on the current machine, so this will not be transferred when moving to production or another machine, so be sure to repeat the process.
Consuming Secrets as Environment Variable
Consuming a secret as an environment variable for a container is a little different to other environment variables. As secrets can contain multiple key value pairs, we need to specify the secret and the key to retrieve the value from:
1 2 3 4 5
The ingress service allows us to connect to other Kubernetes cluster from outside, and thus maintains how we should treat incoming requests and how to route them.
The entirety of our configuration for the ingress service is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
Note that we make use of rewrite-target, this means that:
Any requests for
test.com/api get routed to the specific server service for handling, while any others get sent to the front end.
Now we are ready to deploy our Kubernetes cluster onto a cloud provider, this was originally detailed in this part of the post, but grew far longer than expected so another post was created!