Kubernetes Notes

Download as pdf or txt
Download as pdf or txt
You are on page 1of 17
At a glance
Powered by AI
The key takeaways from the document are that Kubernetes is used to manage containerized applications and services at scale. It discusses various Kubernetes components like the API server, scheduler, kubelet and their roles. It also covers concepts like Pods, ReplicaSets and how to interact with resources using kubectl.

The main components of Kubernetes and their roles discussed are: API server - runs on the master node and interacts with all other components. Scheduler - watches for unassigned pods and assigns them to nodes. Kubelet - runs on each node and ensures that assigned pods are running. It also pulls Pod definitions to create containers.

We can execute a command inside a Pod using the kubectl exec command. For example, kubectl exec -it -c <container_name> <pod_name> <command> will allow us to run a command like ps aux inside the specified container of the Pod.

kubectl describe pod db -- ​describe sub-command returned details of the specified resource​.

In
this case, the resource is the Pod named db.

Scheduler -- I​ ts job is to watch for unassigned pods and assign them to a node which has available
resources (CPU and memory) matching Pod requirements.
API server -- ​ runs on the master node
All other components interact with API server and keep watch for changes.
Kubelet​ ​runs on each node​. make sure that assigned pods are running on the node. It watches for any
new Pod assignments for the node. If a Pod is assigned to the node Kubelet is running on, it will pull
the Pod definition and use it to create containers through Docker or any other supported container
engine.

● kubectl​ allows us to execute a process in a container running in any node inside a cluster
● docker container exec​ is limited to containers running on a specific node.
● --container (or -c) argument can be set to specify which container should be used​.
kubectl exec -it db pkill mongod
A Pod cannot be split across multiple nodes. ​Pods are designed to run multiple cooperative processes
kubectl get -f pod/go-demo-2.yml -o json
kubectl get -f pod/go-demo-2.yml \ -o jsonpath="{.spec.containers[*].name}" ---> spcfc
o/p = db api
kubectl exec -it -c db go-demo-2 ps aux​ -- ​display the processes inside the db container
kubectl create -f pod/go-demo-2.yml

kubectl get -f pod/go-demo-2.yml


We created a new Pod defined in the go-demo-2.yml file and retrieved its information from
Kubernetes. The output of the latter command is as follows:
NAME READY STATUS RESTARTS AGE
go-demo-2 2/2 Running 0 2m
The get command that would filter the output and retrieve only the names of the containers is as
follows:
kubectl get -f pod/go-demo-2.yml \
-o jsonpath="{.spec.containers[*].name}"
The output is as follows:
db api
kubectl exec -it -c db go-demo-2 ps aux -- ​How would we execute a command inside the Pod?
kubectl config current-context​ -​We created a single-node cluster and configured kubectl to use
it.
How about logs from a container?​kubectl logs go-demo-2 -c db
kubectl get rs -->replica sets
Instead of retrieving all the replicas in the cluster, we can retrieve those specified in the
rs/go-demo-2.yml file.
kubectl get -f rs/go-demo-2.yml
Finally, if you are not yet convinced that the ReplicaSet created the missing Pods, we can list all those
running in the cluster and confirm it:
kubectl get pods --show-labels​ ​--show-labels argument so that we can verify that the Pods in the
cluster match those created by the ReplicaSet.
when we explore how to scale Pods (not containers)
If, for example, we scale the Pod to three, we'd have three APIs and three DBs. Instead, we should
have defined two Pods, one for each container (db and api). That would give us enough flexibility to
treat each independently from the other.
livenessProbe​ can be used to confirm whether a container should be running. If the probe fails,
Kubernetes will kill the container and ​apply restart policy which defaults to Always​.
you will not create Pods directly. Instead, you'll use higher level constructs like Controllers.
If a Pod fails, gets destroyed, or gets evicted from a Node, it will not be rescheduled. At least, not
without a Controller. Similarly, if a whole node is destroyed, all the Pods on it will cease to exist. Pods
do not heal by themselves. Excluding some special cases, Pods are not meant to be created directly.
Most applications should be scalable and all must be fault tolerant. Pods do not provide those
features, ReplicaSets do.
The first Controller we'll explore is called ​ReplicaSet​. Its primary, and pretty much only function, is to
ensure that a specified number of replicas of a Pod ​matches the actual state (almost) all the time.​ That
means that ReplicaSets make Pods scalable.
Pods associated with a ReplicaSet are guaranteed to run. They provide fault-tolerance and high
availability.
ReplicaSet's primary function is to ensure that the specified number of replicas of a service are
(almost) always running.
the spec section is mandatory as well

Selector - ​It does not distinguish between the Pods created by a ReplicaSet or some other process.
They must match the labels defined in the spec.template. In our case, ReplicaSet
will look for Pods with type set to backend and service set to go-demo-2. If Pods with those labels do
not already exist, it'll create them using the spec.template section.
At a minimum, the labels of the spec.template.metadata.labels section must match those specified in
the spec.selector.matchLabels.
spec.template.spec.containers field is mandatory. It defines a Pod with two containers (db and api)
two containers should not belong to the same Pod. The same is true for the containers in Pods
managed by the ReplicaSet.

Operating ReplicaSets
ReplicaSets and Pods are loosely coupled objects with matching labels
We can, for example, remove the ReplicaSet we created while leaving the two Pods intact.
kubectl delete -f rs/go-demo-2.yml \
--cascade=false
We used the --cascade=false argument to prevent Kubernetes from removing all the downstream
objects. As a result, we got the confirmation that replicaset "go-demo-2" was deleted. Let's confirm
that it is indeed removed from the system.
kubectl get pods
The two Pods created by the ReplicaSet are indeed still running in the cluster even though we removed
the ReplicaSet.
We'll get to them shortly. For now, the important thing is that we are about to create the same
ReplicaSet we had before.
kubectl create -f rs/go-demo-2.yml \
--save-config
The output states that the replicaset "go-demo-2" was created. Let's see what happened with the Pods.
The apply command automatically saves the configuration so that we can edit it later on. The create
command does not do such thing by default so we had to save it with --save-config.
When we applied the new configuration with replicas set to 4 instead of 2, Kubernetes updated the
ReplicaSet which, in turn, evaluated the current state of the Pods with matching labels. It found two
with the same labels and decided to create two more so that the new desired state can match the actual
state.
Let's see what happens when a Pod is destroyed.
POD_NAME=$(kubectl get pods -o name \
| tail -1)

kubectl delete $POD_NAME


We retrieved all the Pods and used -o name to retrieve only their names. The result was piped to tail -1
so that only one of the names is output. The result is stored in the environment variable POD_NAME.
The latter command used that variable to remove the Pod as a simulation of a failure.
Let's take another look at the Pods in the cluster:
However, since we have a ReplicaSet with replicas set to 4, as soon as it discovered that the number of
Pods dropped to 3, it created a new one. We just witnessed self-healing in action. As long as there are
enough available resources in the cluster, ReplicaSets will make sure that the specified number of Pod
replicas are (almost) always up-and-running.
The total number of Pods increased to five. The moment we removed the service label from one of the
Pods, the ReplicaSet discovered that the number of Pods matching the selector labels is three and
created a new Pod. Right now, we have four Pods controlled by the ReplicaSet and one running freely
due to non-matching labels.
The previous few examples showed, one more time, that ReplicaSets and Pods are loosely coupled
through matching labels and that ReplicaSets are using those labels to maintain the parity between
the actual and the desired state. So far, self-healing worked as expected.
The good news is that ReplicaSets are relatively straightforward. They provide a guarantee that the
specified number of replicas of a Pod will be running in the system as long as there are available
resources. That's the primary and, arguably, the only purpose.
You will almost never create a ReplicaSet directly just as you're not going to create Pods. Instead, we
tend to create ReplicaSets through Deployments. In other words, we use ReplicaSets to create and
control Pods, and Deployments to create ReplicaSets (and a few other things).

Using Services to Enable Communication between Pods


Applications that cannot communicate with each other or are not accessible to end-users are
worthless. Only once the communication paths are established, can applications fulfill their role.
So far, only containers inside a Pod can talk with each other through localhost
Kubernetes Services provide addresses through which associated Pods can be accessed.

Creating Services by exposing ports


Before we dive into services, we should create a ReplicaSet similar to the one we used in the previous
chapter. It'll provide the Pods we can use to demonstrate how Services work.
kubectl expose rs go-demo-2 \
--name=go-demo-2-svc \
--target-port=28017 \
--type=NodePort
We specified that we want to expose a ReplicaSet (rs) and that the name of the new Service should be
go-demo-2-svc. The port that should be exposed is 28017 (the port MongoDB interface is listening to).
Finally, we specified that the type of the Service should be NodePort. As a result, the target port will be
exposed on every node of the cluster to the outside world, and it will be routed to one of the Pods
controlled by the ReplicaSet.
ClusterIP (the default type) exposes the port only inside the cluster. Such a port would not be
accessible from anywhere outside. ClusterIP is useful when we want to enable communication
between Pods and still prevent any external access. If NodePort is used, ClusterIP will be created
automatically. The LoadBalancer type is only useful when combined with cloud provider's load
balancer. ExternalName maps a service to an external address (for example, kubernetes.io).
The processes that were initiated with the creation of the Service are as follows:

1. Kubernetes client (kubectl) sent a request to the API server requesting the creation of the
Service based on Pods created through the go-demo-2 ReplicaSet.
2. Endpoint controller is watching the API server for new service events. It detected that there is
a new Service object.
3. Endpoint controller created endpoint objects with the same name as the Service, and it used
Service selector to identify endpoints (in this case the IP and the port of go-demo-2 Pods).
4. kube-proxy is watching for service and endpoint objects. It detected that there is a new Service
and a new endpoint object.
5. kube-proxy added iptables rules which capture traffic to the Service port and redirect it to
endpoints. For each endpoint object, it adds iptables rule which selects a Pod.
6. The kube-dns add-on is watching for Service. It detected that there is a new service.
7. The kube-dns added db container's record to the dns server (skydns).

Since the Service is associated with the Pods created through the ReplicaSet, it inherited all their
labels. The selector matches the one from the ReplicaSet. The Service is not directly associated with
the ReplicaSet (or any other controller) but with Pods through matching labels.
The Port is set to 28017. That is the port that the Pods can use to access the Service.
NodePort was generated automatically since we did not set it explicitly. It is the port which we can use
to access the Service and, therefore, the Pods from outside the cluster. In most cases, it should be
randomly generated, that way we avoid any clashes.
Let's see whether the Service indeed works:
PORT=$(kubectl get svc go-demo-2-svc \
-o jsonpath="{.spec.ports[0].nodePort}")

IP=$(minikube ip)

open "http://$IP:$PORT"

The selector is used by the Service to know which Pods should receive requests. It works in the same
way as ReplicaSet selectors. In this case, we defined that the service should forward requests to Pods
with labels type set to backend and service set to go-demo. Those two labels are set in the Pods spec of
the ReplicaSet.

Services can be discovered through two principal modes; environment variables and DNS.
Every Pod gets environment variables for each of the active Services. They are provided in the same
format as what Docker links expect, as well with the simpler Kubernetes-specific syntax.
No matter which set of environment variables you choose to use (if any), they all serve the same
purpose. They provide a reference we can use to connect to a Service and, therefore to the related
Pods.
The key is in the IP field. That is the IP through which this service can be accessed and it matches the
values of the environment variables GO_DEMO_2_DB_* and GO_DEMO_2_DB_SERVICE_HOST.
The code inside the containers that form the go-demo-2-api Pods could use any of those environment
variables to construct a connection string towards the go-demo-2-db Pods. For example, we could
have used GO_DEMO_2_DB_SERVICE_HOST to connect to the database. And, yet, we didn't do
that. The reason is simple. It is easier to use DNS instead. Page 58
Let's go through the sequence of events related to service discovery and components involved:
1. When the api container go-demo-2 tries to connect with the go-demo-2-db Service, it looks at
the nameserver configured in /etc/resolv.conf. kubelet configured the nameserver with the
kube-dns Service IP (10.96.0.10) during the Pod scheduling process.
2. The container queries the DNS server listening to port 53. go-demo-2-db DNS gets resolved to
the service IP 10.0.0.19. This DNS record was added by kube-dns during the service creation
process.
3. The container uses the service IP which forwards requests through the iptables rules. They
were added by kube-proxy during Service and Endpoint creation process.
4. Since we only have one replica of the go-demo-2-db Pod, iptables forwards requests to just
one endpoint. If we had multiple replicas, iptables would act as a load balancer and forward
requests randomly among Endpoints of the Service.

Services are indispensable objects without which communication between Pods would be hard and
volatile. They provide static addresses through which we can access them not only from other Pods but
also from outside the cluster. This ability to have fixed entry points is crucial as it provides stability to
otherwise dynamic elements of the cluster. Pods come and go, Services stay.

Deploying Releases with Zero-Downtime


If we are to survive in the face of competition, we have to release features to production as soon as
they are developed and tested. The need for frequent releases fortifies the need for zero-downtime
deployments.

We learned how to deploy our applications packaged as Pods, how to scale them through ReplicaSets,
and how to enable communication through Services. However, all that is useless if we cannot update
those applications with new releases. That is where Kubernetes Deployments come in handy.
Failures caused by circumstances outside of our control are things which, by definition, we can do
nothing about. However, failures caused by obsolete practices or negligence are failures which
should not happen. Kubernetes Deployments provide us with the tools we need to avoid such failures
by allowing us to update our applications without downtime.
Let's explore how Kubernetes Deployments work and the benefits we gain by adopting them.

Creating a Cluster

Deploying new releases


Just as we are not supposed to create Pods directly but using other controllers like ReplicaSet, we are
not supposed to create ReplicaSets either.​ Kubernetes Deployments​ will create them for us. If you're
wondering why, you'll have to wait a little while longer to find out. First, we'll create a few
Deployments and, once we are familiar the process and the outcomes, it'll become obvious why they
are better at managing ReplicaSets than we are.
ince, in this case, both the Deployment and the ReplicaSet are the same, you might be wondering what
the advantage of using one over the other is.
We will regularly add --record to the kubectl create commands. This allows us ​to track each change
to our resources​ such as a Deployments.
The real advantage of Deployments becomes evident if we try to change some of its aspects. For
example, we might choose to upgrade MongoDB to version 3.4.
Deployment and its cascading effect that creates a ReplicaSet and, though it, Pods

We can see that it created a new ReplicaSet and that it scaled the old ReplicaSet to 0.
Instead of operating directly on the level of Pods, the ​Deployment created a new ReplicaSet​ which, in
turn, produced Pods ​based on the new image​. Once they became fully operational, it scaled the old
ReplicaSet to 0. Since we are running a ReplicaSet with only one replica, it might not be clear why it
used that strategy. When we create a Deployment for the API, things will become more evident.
You'll notice that contained within the name of the Pod is a hash which matches the hash in the name
of the new ReplicaSet, namely f8d4b86ff. Even though it might look like it is a random value, it is not.
If you destroy the Deployment and create it again, you'll notice that the hash in the Pod name and
ReplicaSet name remain consistent. This value is generated by hashing the PodTemplate of the
ReplicaSet
As long as the PodTemplate is the same, the hash value will be the same as well. That way a
Deployment can know ​whether anything related to the Pods has changed​ and, if it does, will create a
new ReplicaSet.
applications are supposed to have very high availability​.
We cannot avoid all failures, but we can reduce them to acceptable limits
Scaled applications can not only spread the load across various instances but ensure that a failure of
one replica will not produce downtime. Healthy instances are handling the load until the scheduler
recreates failed ones.
High availability is accomplished through fault tolerance and scalability. If either is missing, any
failure might have disastrous effects.
If a Pod is unchangeable, the only way to update it with a new release is to destroy the old ones and
put the Pods based on the new image in their place​. Destruction of Pods is not much different from
failures. In both cases, they cease to work. On the other hand, ​fault tolerance (re-scheduling) is a
replacement of failed Pods.
frequency of new releases-- Part of the reasons behind such infrequent releases was due to the
downtime they produce. If we can reach zero-downtime deployments, the frequency can change
and we can aim for continuous deployment.

that new releases result in Pods being replaced with new ones based on the new image. As long as the
process is controlled, new releases should not result in any downtime
when multiple replicas of an application are running and when they are adequately designed.
If we can reach zero-downtime deployments, the frequency can change, and we can aim for
continuous deployment.
Zero-downtime deployment is a prerequisite for higher frequency releases.
minReadySeconds​ --- defines the ​minimum number of seconds before Kubernetes starts considering
the Pods healthy.​ We put the value of this field to 1 second. The default value is 0, meaning that the
Pods will be considered available as soon as they are ready and, when specified, livenessProbe returns
OK. If in doubt, omit this field and leave it to the default value of 0. We defined it mostly for
demonstration purposes.

revisionHistoryLimit. It defines the number of old ReplicaSets we can rollback. Like most of the fields,
it is set to the sensible default value of 10. We changed it to 5 and, as a result, we will be able to
rollback to any of the previous five ReplicaSets.
The strategy can be either the RollingUpdate or the Recreate type.

A bug will sneak in and put our production cluster at risk. What should we do in such a case​?
d​epends on the size of the changes and the frequency of deployments​.
we are deploying small chunks. In such cases, fixing a problem might be just as fast as rolling back
Rolling back a release that introduced database changes is often not possible. Even when it is, rolling
forward is usually a better option when practicing continuous deployment with high-frequency
releases limited to a small scope of changes.
We'll imagine that we just discovered that the latest release of the vfarcic/go-demo-2 image is faulty
and that we should roll back to the previous release. The command that will do just that is as follows:
kubectl rollout undo \
-f deploy/go-demo-2-api.yml
kubectl describe \
-f deploy/go-demo-2-api.yml
It started increasing the replicas of the older ReplicaSet, and decreasing those from the latest one.
Once the process is finished, the older ReplicaSet became active with all the replicas, and the newer
one was scaled down to zero.
Since new deployments do no destroy ReplicaSets but scale them to 0, all it had to do to undo the last
change was to scale it back to the desired number of replicas and, at the same time, scale the current
one to zero.
We can undo the rollout by moving to the last revision that worked correctly. Assuming that we want
to revert to the image vfarcic/go-demo-2:2.0, reviewing the change causes listed in the history tells us
we should roll back to revision 2. That can be accomplished through the --to-revision argument. The
command is as follows:
kubectl rollout undo \
-f deploy/go-demo-2-api.yml \
--to-revision=2
kubectl rollout history \
-f deploy/go-demo-2-api.yml
We undid the rollout by moving to revision 2. We also retrieved the history.

Rolling back failed Deployments


For example, we might be in a situation when Pods cannot be created. An easy to reproduce case
would be an attempt to deploy an image with a tag that does not exist.
kubectl set image \
-f deploy/go-demo-2-api.yml \
api=vfarcic/go-demo-2:does-not-exist \
--record
The output is as follows:
deployment "go-demo-2-api" image updated
Deployment was successfully updated. That does not mean that the Pods behind the ReplicaSet are
indeed running. For one, I can assure you that the vfarcic/go-demo-2:does-not-exist image does not
exist.
Please make sure that at least 60 seconds have passed since you executed the kubectl set image
command. If you're wondering why we are waiting, the answer lies in the ​progressDeadlineSeconds
field set in the go-demo-2-api Deployment definition. That's how much the Deployment has to wait
before it deduces that it cannot progress due to a failure to run a Pod.
I expect you to deploy new releases as part of your automated CDP pipeline. Fortunately, the status
command returns 1 if the deployment failed and we can use that information to decide what to do
next. For those of you not living and breathing Linux, any exit code different than 0 is considered an
error. Let's confirm that by checking the exit code of the last command
echo $?
The output is indeed 1, thus confirming that the rollout failed.
Now that we discovered that our last rollout failed, we should undo it. You already know how to do
that, but I'll remind you just in case you're of a forgetful nature.
kubectl rollout undo \
-f deploy/go-demo-2-api.yml

kubectl rollout status \


-f deploy/go-demo-2-api.yml
Merging everything into the same YAML definition
minReadySeconds, progressDeadlineSeconds, revisionHistoryLimit, and strategy fields are removed
from the go-demo-2-api Deployment. We used them mostly as a way to demonstrate their usage.
Let's create the objects defined in deploy/go-demo-2.yml. Remember, with ​--save-config we're making
sure we can edit the configuration later.
The alternative would be to use kubectl apply instead.
kubectl create \
-f deploy/go-demo-2.yml \
--record --save-config

kubectl get -f deploy/go-demo-2.yml


All four objects (two Deployments and two Services) were created, and we can move on and explore
ways to update multiple objects with a single command.

Updating multiple objects

Even though most of the time we send requests to specific objects, almost everything is happening
using selector labels. When we updated the Deployments, they looked for matching selectors to choose
which ReplicaSets to create and scale. They, in turn, created or terminated Pods also using the
matching selectors. Almost everything in Kubernetes is operated using label selectors. It's just that
sometimes that is obscured from us.
We do not have to update an object only by specifying its name or the YAML file where its definition
resides. We can also use labels to decide which object should be updated.
That opens some interesting possibilities since ​the selectors might match multiple objects.
Imagine that we are running several Deployments with Mongo databases and that the time has come
to update them all to a newer release.
Now that we have two deployments with the mongo:3.3 Pods, we can try to update them both at the
same time.
The trick is to find a label (or a set of labels) that uniquely identifies all the Deployments we want to
update.
Let's take a look at the list of Deployments with their labels:
kubectl get deployments --show-labels

kubectl get deployments \ #filtered result


-l type=db,vendor=MongoLabs
The output is as follows:
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
different-app-db 1 1 1 1 1h
go-demo-2-db 1 1 1 1 1h
We can see that filtering with those two labels worked. We retrieved only the Deployments we want to
update, so let's proceed and roll out the new release:
kubectl set image deployments \
-l type=db,vendor=MongoLabs \
db=mongo:3.4 --record
The output is as follows:
deployment "different-app-db" image updated
deployment "go-demo-2-db" image updated
Finally, before we move into the next subject, we should validate that the image indeed changed to
mongo:3.4:
kubectl describe \
-f deploy/go-demo-2.yml
The output, limited to the relevant parts, is as follows:
...
Containers:
db:
Image: mongo:3.4
...
As we can see, the update was indeed successful, at least with that Deployment. Feel free to describe
the Deployment defined in deploy/different-app-db.yml. You should see that its image was also
updated to the newer version.

Scaling Deployments

Using Ingress to Forward Traffic


Kubernetes Services provide accessibility with a usability Each application can be reached through a
different port. We cannot expect users to know the port of each service in our cluster.
Kubernetes Services provide accessibility with a usability Each application can be reached through a
different port. We cannot expect users to know the port of each service in our cluster.
We still need forwarding rules based on paths and domains, SSL termination and a number of other
features
Ingress provides an API that allows us to accomplish these things, in addition to a few other features
we expect from a dynamic cluster.
We need a mechanism that will accept requests on pre-defined ports (for example, 80 and 443) and
forward them to Kubernetes services. It should be able to distinguish requests based on paths and
domains as well as to be able to perform SSL offloading.
Ingress Controller needs to be installed separately. Instead of a Controller, kube-controller-manager
offers ​Ingress resource​ that other third-party solutions can utilize to provide requests forwarding and
SSL features. In other words, Kubernetes only provides an ​API,​ and we need to set up a Controller that
will use it.
Fortunately, the community already built a myriad of Ingress controllers.
how Ingress controllers work through the one that is already available in Minikube.
minikube addons list
The output is as follows:
- kube-dns: enabled
- registry: disabled
- registry-creds: disabled
- dashboard: enabled
- coredns: disabled
- heapster: disabled
- ingress: disabled
- addon-manager: enabled
- default-storageclass: enabled
our next action will be to enable it.
minikube addons enable ingress
kubectl get pods -n kube-system \
| grep ingress
By default, the Ingress controller is configured with only two endpoints.
If we'd like to check Controller's health, we can send a request to /healthz.
curl -i "http://$IP/healthz"
The output is as follows:
HTTP/1.1 200 OK
Server: nginx/1.13.5
Date: Sun, 24 Dec 2017 15:22:20 GMT
Content-Type: text/html
Content-Length: 0
Connection: keep-alive
Strict-Transport-Security: max-age=15724800; includeSubDomains;
It responded with the status code 200 OK, thus indicating that it is healthy and ready to serve
requests.
Now we're ready to create our first Ingress Resource. ​Creating Ingress Resources based on paths
We'll try to make our go-demo-2-api service available through the port 80. We'll do that by defining
an Ingress resource with the rule to forward all requests with the path starting with /demo to the
service go-demo-2-api.
cat ingress/go-demo-2-ingress.yml

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: go-demo-2
annotations:
ingress.kubernetes.io/ssl-redirect: "false"
​nginx.ingress.kubernetes.io/ssl-redirect: "false"
The annotations section allows us to provide additional information to the Ingress controller.
The list of general annotations and the Controllers that support them can be found in the Ingress
Annotations
page(h​ ttps://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guid
e/nginx-configuration/annotations.md)​
ssl-redirect:"false" tells the Controller that we do NOT want to redirect all HTTP requests to HTTPS.
We're forced to do so since we do not have SSL certificates for the exercises that follow.
We specified a set of rules in the spec section. They are used to configure Ingress resource. For now,
our rule is based on http with a single path and a backend. All the requests with the path starting with
/demo will be forwarded to the service go-demo-2-api on the port 8080.
create the resource.
kubectl create \
-f ingress/go-demo-2-ingress.yml

kubectl get \
-f ingress/go-demo-2-ingress.yml
create the resource.
kubectl create \
-f ingress/go-demo-2-ingress.yml

kubectl get \
-f ingress/go-demo-2-ingress.yml
We can define multiple Ingress resources that will configure a single Ingress controller.

Creating Ingress resources based on domains


cat ingress/devops-toolkit-dom.yml

Accessing host's resources through hostPath volumes

Injecting configurations from key/value literals


even when our applications need different configs to work in distinct clusters --differences are limited.
Often, they should be limited to only a few key/value entries-- easier to create ConfigMaps using
--from-literal.
kubectl create cm my-config \
--from-literal=something=else \
--from-literal=weather=sunny

kubectl get cm my-config -o yaml


The output of the latter command is as follows (metadata is removed for brevity):
apiVersion: v1
data:
something: else
weather: sunny
kind: ConfigMap
kubectl exec -it alpine -- \
ls /etc/config
The output of the latter command is as follows:
something weather
Both files are there.
Finally, let's confirm that the content of one of the files is correct.
kubectl exec -it alpine -- \
cat /etc/config/something
The output is as follows:
else
The --from-literal argument is useful when we're in need to set a relatively small set of configuration
entries in different clusters. It makes more sense to specify only the things that change, than all the
configuration options.

ontinuous Integration
Versus Continuous Delivery
Versus Continuous
Deployment
Continuous integration, continuous delivery, and continuous deployment—if you do any work in the area of software delivery, it’s impossible not to encounter these terms on a regular basis. But what does each of these processes do for your product
development and release cycles? I’ll explain what they really boil down to, what practic⁠es are associated with them, and what kinds of tooling are available to implement them in a manner that clearly depicts what they offer, how they differ, and how
they work, both separately and together, to help release software to customers frequently, reliably, and with high quality.

Defining “Continuous”
Continuous here implies automation—automation in transforming source code into deliverables, in testing and validation, and even in installing and configuring software. It doesn’t imply that processes are continuously executing; rather, that when a
new change is introduced into the system it can be automatically processed quickly and thus enables more frequent builds and faster testing, packaging, and releases than without continuous practices. Further, it enables quick identification and
reporting of failures so that they can be fixed swiftly. This is commonly known as ​fail-fast​.

Continuous here also implies that a change can proceed, without human intervention, through the stages of builds, testing, packaging, and so on. Combined, these stages form a “pipeline” of processes and applications to take source code changes and
turn them into a releasable product. This pipeline can go by various names, including ​continuous delivery pipeline,​ ​deployment pipeline,​ or ​release pipeline​. Although hand-offs between stages are intended to be automatic as the sequence progresses,
there are two cases in which human intervention can be required:

● When a change breaks and should be fixed before proceeding


● When you want a human to validate something before it proceeds (e.g., user acceptance testing or choosing to manually approve deploying to customers)

With this background in mind, let’s look closer at what each of these three terms encompasses.

Continuous Integration
In the ​continuous integration​ (CI) phase, individual changes from a developer are merged and tested. The goal of CI is to quickly validate individual code changes that have been submitted for inclusion in the code base. An intended consequence of
this is quickly identifying any problems in the code and automatically notifying the developer of problems so that the code base is not broken any longer than necessary. The mechanism for CI detects that code changes have been made and then runs
targeted testing to prove that the code changes work in isolation (function inputs produce the desired outputs, bad inputs are flagged appropriately, and so on).

Unit Tests
These targeted tests are called ​unit tests,​ or ​commit tests​, and developers are responsible for creating them to exercise their code. In fact, one model (known as ​test-driven development​ [TDD]) requires unit tests to be designed first—as a basis for
clearly identifying what the code should do.

In the standard CI workflow, a developer creates or updates source in their local working environment and uses the unit tests to ensure that the newly developed function or method works. Because such changes can be frequent and numerous, and
because they are at the beginning of the pipeline, the unit tests must be fast to execute, must not depend on (or make calls to) other code that isn’t directly accessible, and should not depend on external data sources or other modules. If such a
dependency is required for the code to run, those resources can be mocked by using a stub that looks like the resource and can return values, but doesn’t actually implement any functionality.
Typically, these tests take the form of asserting that a given set of inputs to a function or method produce a given set of outputs. They generally test to ensure that error conditions are properly flagged and handled. Various unit testing frameworks,
such as ​JUnit for Java development​, are available to assist with this.

CI Tools
The developer can then push their code and unit tests into the source code repository. A variety of different source code management systems, such as ​Git​, are available for use today. If the code is pushed and does not merge cleanly with other users’
changes, the developer must manually fix the conflicts and try again.

After the code is merged in, a CI tool such as ​Jenkins​ can detect (or be notified) that a change has been made in the source code repository. It will then automatically grab the latest set of code from the repository and attempt to build it and execute the
unit tests. Jenkins can detect changes by regularly polling the source control system to check for changes or by having a scheduled, periodic build of whatever is current. Alternatively, most source control systems can have a hook set up to send a
notification to tools like Jenkins that a change has occurred.

Prechecks
A variation on this simple workflow involves adding in prechecks on the code before it makes it all the way into the source code repository. For example, a tool called ​Gerrit​ can intercept pushes to a remote Git repository and wait for a person to sign
off on a review of the code before allowing it to go into the repository. It can also initiate early Jenkins builds of the code at that point as another pass/fail check.

GitHub​, a public hosting site for Git source repositories, offers a further variation. Users wanting to contribute changes to a source repository on GitHub can submit a pull request to ask the owner of a repository to review and then merge in code
changes from the user’s copy of the project.

These variations provide additional checking and validation by mul⁠tiple people earlier in the process—at the expense of reduced frequency of integration.

Continuous Delivery
In the ​continuous delivery​ cycle, the isolated changes merged and validated during CI can be combined with the remaining product code. Then, the set is put through progressively more encompassing types of testing. The goal of continuous delivery
is not necessarily to deploy the end result, but to prove that the end result is deployable. The mechanism of continuous delivery is the ​continuous delivery pipeline​ (CDP).

Artifacts
The CDP is made up of stages connected in a sequence. Each stage in turn can be made up of smaller “jobs” with specialized tasks. For example, what’s typically referred to as the ​commit stage​ usually consists of jobs to compile the code, run the unit
tests, do integrated testing, gather metrics, and publish artifacts. This then could have an automated handoff to an ​acceptance stage​ in which artifacts are retrieved and functional testing and verification are done.

An ​artifact​ refers to a file that is either a deliverable (something directly used by the final product) or included in a deliverable. For example, you might have an executable file created by compiling source that links in several other libraries. Or you
might have a compressed file such as a .war or .zip file that contains another set of files within it.

Versioning
Regardless of the type of artifact, an important dimension across the pipeline is the artifact versioning. Although deployment technology has made versioning less of a concern to users (think app updates on your phone), it is critical to work with the
desired versions of artifacts through the pipeline. This is usually managed via an artifact repository tool such as ​Artifactory​. These tools work like a source management system for artifacts, allowing multiple versions to be stored and retrieved to and
from different repositories. In some cases, these repositories can serve as a dev/test/prod (development-quality/testing-quality/production-quality) organizational structure to separate multiple versions of releases in progress.

Testing
If incorrect versions of artifacts are pulled in or functionality is broken, the automated testing in the pipeline will present some symptom of this. The goal of the various testing phases in the pipeline is to instill more and more confidence as we
progress toward the final product. There are several types of testing, including:

Integration testing

To validate that groups of components and/or services all work together


Functional testing

To validate the end result of executing functions in the product are as expected.
Acceptance testing

To measure some characteristic of the system against acceptable criteria. Examples include performance, scalability, and capacity.

Coding Metrics and Analysis


The amount of code covered by testing is one example of a metric that can be used for ensuring code quality in the stack. This metric is called ​code-coverage​ and can be measured by tools (such as ​JaCoCo​ for Java code). Many other types of metrics
exist, such as counting lines of code, measuring complexity, and comparing coding structures against known patterns. Tools such as ​SonarQube​ can run these sorts of checks, measure results against desired thresholds, and provide integrated reporting
on the results. If automatic testing or metrics gathering finds a problem, the code’s progress through the pipeline is stopped. In this way, you can ensure that only the correct version of code that passes all of your testing and other metrics makes it to
the end of the continuous delivery pipeline in a deployable state.

Infrastructure-as-Code
One other recommended practice that DevOps culture has introduced to continuous delivery is the ​infrastructure-as-code​ ideal. In this approach, configuration and setup of the tools used in the pipeline are automated and described in files that can be
stored in source control. A change to any of these files drives a reconfiguration or update of the tools used in the pipeline, and triggers a new run of the pipeline, just as a source code change for the product code would.

Continuous Deployment
Continuous deployment​ is a process that can be added on at the end of the continuous delivery pipeline. The idea is that since the continuous delivery pipeline has proven the latest changes to be deployable, we should automate the deployment process
and go ahead and deploy the result. Deployment can mean a lot of things, but for simplicity can just think of it as making the release available for the customer to use. That might mean that it’s running in the cloud, made downloadable, or updated in
some other known location.

Manual Checks
If a deployment is not automated, there are limitations on what can be done here. Even if it is automated, an organization might not choose to always deploy every release, so a manual check might be added in. More commonly, though, such a check
would take the form of a ​user acceptance test​ (UAT) earlier in the pipeline.

Staged Deployments
Even if you want to automatically deploy the latest version from the pipeline, you can still do it in a staged way. For example, you can deploy it to a location that is accessible only by a subset of users who might want to try out the new release, or you
can redirect some users to the new release transparently. Then, if any problems surface during an evaluation period, not all users are affected, and the subset can be directed back to the previous release. If all is well, the remaining users can then be
redirected to the new release. This sort of deployment is called a ​canary deployment.​

Blue/Green Deployments
A broader form of the canary deployment is called ​blue/green deployment.​ In this case, two deployment environments are always available: one labeled blue and one labeled green. At any given time, one of these is production and one is in
development or testing (nonproduction). When a new release is deployed, it is deployed to the nonproduction environment for evaluation. When it is deemed production ready, the nonproduction environment is toggled to be production (that is,
customer interaction is redirected to it). And the previous production environment is toggled to be nonproduction, ready for the next candidate release.

Conclusion
As you can see, continuous integration, continuous delivery, and continuous deployment form a powerful set of disciplines, best practices, and technologies for transforming changes from source to production quickly and reliably. The end result is
high quality and easily reproducible. This process not only benefits the customer, but also greatly simplifies the worlds of the development, testing, and deployment teams. It lets them work together in a common environment from start to finish,
helping to realize one of the core goals of the DevOps movement.

● Copy
● Add Highlight
● Add Note

You might also like