Summary

Index

Docker and Kubernetes


Docker


Docker Compose


Maven Docker Plugin


Kubernetes

Understanding Docker Concepts

Why Docker?

  • To pack an application with all the dependencies it needs into a single, standardized unit for the deployment.
  • Packaging all of this into a complete image guarantees that it is portable.
  • It will always run in the same way, no matter what environment it is deployed in.
  • Tool that helps in solving the common problems such as installing, distributing, and managing the software.

Docker images

  • Think of an image as a read-only template which is a base foundation for creating container.
  • Docker images are executable packages that include everything needed to run an application.
  • It includes  the code, a runtime, libraries, environment variables, and configuration files.
  • It can also include an application server like Tomcat, Netty and/or application itself.
  • Images are created using a series of commands, called instructions.
  • Instructions are placed in the Dockerfile which we will learn later.

Docker Containers

  • A running instance of an image is called a container.
  • Docker containers are a runtime instance of an image.
  • What the image becomes in memory when executed.
  • It is an image with state, or a user process.
+-----------------------+ +-----------------------+
|       Container       | |       Container       |
|   +---------------+   | |                       |
|   |  Application  |   | |                       |
|   +---------------+   | |                       |
|   +---------------+   | |   +---------------+   |
|   |     Java      |   | |   |     MySQL     |   |
|   +---------------+   | |   +---------------+   |
|   +---------------+   | |   +---------------+   |
|   |    Alpine     |   | |   |    Ubuntu     |   |
|   +---------------+   | |   +---------------+   |
|                       | |                       |
+-----------------------+ +-----------------------+

Basic Commands


Running container

docker run @image

  • Let's start with simple image hello-world.
  • To run that image execute docker run hello-world.
  • Here's a output you will get
➜ docker run hello-world   
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
1b930d010525: Pull complete 
Digest: sha256:2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
Status: Downloaded newer image for hello-world:latest
	
Hello from Docker!

This message shows that your installation appears to be working correctly.
...
...
  • If you look at the output you will see that docker does few things for us.
  • It will check if the image is available locally.
  • If not, it will be pulled down from the remote repository.
  • It initializes the image's name and resources containers needs like CPU, IP, Memory etc.
  • After that it will run the container.
  • When container is started, it will execute a command.
  • Command is defined in image itself. We will talk about this later.

Listing local images

docker image list

  • Or docker images
  • Lists the images that are available in your local system.
  • You can use any version of command you like.
➜ docker image list                 
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
hello-world         latest              fce289e99eb9        6 weeks ago         1.84kB

Listing container

  • When we ran the image with docker run it creates a container. Duh!! it is the definition of container.
  • How can you see list of your container?

docker container list

  • Or docker ps
  • Use any of the command above and you will see, list of running container.
➜ docker container list 
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

  • I don't see our container here, why?
  • Because hello-world is a simple image.
  • It executes one command and exits, hence container is stopped.

--all

  • If you want to see container that are stopped you can use --all option.
  • docker container list --all OR docker ps --all
➜ docker container list --all
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
bb11cd638c55        hello-world         "/hello"            9 minutes ago       Exited (0) 9 minutes ago                       stoic_shtern

  • Now we are able to see our container in the list.

Override default image command

  • After docker run @image we can pass @command and @argument it accepts.
  • Like docker run @image @command @arguments.
  • For this we will use another simple image busybox.
  • busybox has simple unix command which we can use.

docker run busybox sleep 5000

  • docker run busybox sleep 5000
  • This will run a busybox and at the end it will execute the command sleep 5000.
  • This kind of emulates a long running process, as container will not stop unless command exits.
  • If you list the container right now (in new terminal of course), you will see status is different.
  • It is not Exited.
➜ docker container list      
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
d7acdce83ad6        busybox             "sleep 5000"        23 seconds ago      Up 21 seconds                           gallant_hodgkin

Stopping running containers

docker container stop @id / @name

  • Or docker stop @id / @name
  • To stop the container you can either use name or you can use name of the container.
  • If you list the image again (don't forget --all) you will see updated status for the container.
  • If you pass space separated @id / @name to stop more than one container.

Running container in background

  • When we ran docker run busybox sleep 5000, it blocked out terminal.
  • If you don't want that to happen, we have an option to run the container in background mode.

-d option

  • If you pass -d option, when running the container it will run in background.
  • docker run @options @image @command @arguments
  • docker run -d busybox sleep 5000
  • If you run above command, it will echo container id and resume it's process in background.

Start the stopped container

docker start @name/@id

  • It will start the existing container instead of creating new one.
  • Not recommended, as container are supposed to be disposable.

Running additional command inside container

Attach to running container terminal

-it option

  • -it option will attach container terminal to our host
  • After that we can issue command directly to container.
  • After that every command you issue will be inside the container.
  • (Play around with some command)
  • If you want to get out of the container you can exit the terminal.
  • If default command in image is not shell, we can pass shell as @argument.
  • docker run -it busybox /bin/sh

docker container exec

  • docker container exec is used to execute command inside running container.
  • docker container exec @id @command
  • But we can use same option here -it and pass in shell as command. (Which is default for busybox)
  • So docker container exec -it @id will attach container terminal to host.
  • If you run ps aux inside the container, you can see sleep 5000 is a running process.
➜ docker container exec -it @id
/ # ps aux
PID   USER     TIME  COMMAND
    1 root      0:00 sleep 5000
    9 root      0:00 /bin/sh
   16 root      0:00 ps aux


Attach when starting stopped container

docker start -ia @name/@id

  • If you want to attach the terminal, the option is bit different i.e -ia

Deleting Container

Delete all stopped container

  • To delete all stopped container, we can use above command.
  • Note: it will not delete the container that is running.

Deleting single container

  • To delete the container we use docker container rm @id/@name
  • If you were following the tutorial you might have one container still running in background. Let's stop and delete this container.

Deleting Image

Deleting all dangling images

  • To delete all dangling images we can use docker image prune
  • This will delete all dangling images.
  • Dangling image = Images that doesn't have any containers.

Delete single image

  • To delete an image we can run docker image rm @id.

What is Dockerfile

  • Dockerfile is just a plain text file.
  • The plain text file contains series of instruction.
  • Docker will read Dockerfile when you start the process of building an image.
  • It executes the instruction one by one in orderly fashion.
  • At the end final read-only image is created.
  • It's important to know that Docker uses images to run your code, not the Dockerfile.
  • Dockerfile is just a convenient way to build your docker images.
  • Default file name for Dockerfile is Dockerfile.

 


Building an image

docker build

  • Even if you have single instruction FROM you can build your image.
  • We can simply execute docker build -f @pathToDockerfile -t @name:@tag @contextPath.
  • @contextPath is the path where you want docker build context path.
  • -f takes the path to Dockerfile. If your file exist within the docker build context path and your file name is Dockerfile you don't need to use -f flag.
  • -t represents tag. This is so that you can name the image you are building.
  • If you omit the tag part, docker will add by default latest tag.
  • Notice when it builds an image, if you don't have the image that you used locally it will download it first.
➜ docker build -f docker/web-basic -t amantuladhar/doc-kub-ws:web-basic .
Sending build context to Docker daemon  17.02MB
Step 1/1 : FROM openjdk:8-jre-alpine
8-jre-alpine: Pulling from library/openjdk
6c40cc604d8e: Pull complete 
e78b80385239: Pull complete 
f41fe1b6eee3: Pull complete 
Digest: sha256:712abc7541d52998f14b43998227eeb17fd895cf10957ad5cf195217ab62153e
Status: Downloaded newer image for openjdk:8-jre-alpine
 ---> 1b46cc2ba839
Successfully built 1b46cc2ba839
Successfully tagged amantuladhar/docker-kubernetes:dockerfile-basics
  • Run docker image list and you will see your image on the list
  • (Psst!! You can go inside your image an play around with it)

FROM

  • FROM is the first instruction in Dockerfile.
  • Sets the base image for every subsequent instruction.
  • If you skip tag, docker will use latest tag.
  • Latest tag may not be latest image.
  • If we provide invalid tag, docker will complain.
FROM openjdk:8-jre-alpine

WORKDIR

  • Sets starting point from where you want to execute subsequent instructions.
  • We can set both absolute / relative path
  • We can have multiple WORKDIR in images
  • If relative path is used, it will be relative to previous WORKDIR
  • Think of this as changing the directory
FROM openjdk:8-jre-alpine
WORKDIR /myApp

COPY

  • COPY @source @destination
  • Copies files from source to container file system
  • Source can be path or URL
  • Path is relative to where build process was started.
  • Both can contain wildcards like * and ?
  • Let's add our app executable inside docker image.
  • Before that you need app.
  • You can create one yourself or I have a simple spring app here.
  • For this exercise lets switch to branch dockerfile-initial.
  • Let's build jar for our app. ./mvnw clean install
  • Maven creates jar inside /target folder.

web-basic is a Dockerfile has complete instructions.

FROM openjdk:8-jre-alpine
WORKDIR /myApp
COPY ["onlyweb/target/*.jar", "./app.jar"]
  • That's it. Build your image. Go inside an run the app
Sending build context to Docker daemon  17.03MB
Step 1/3 : FROM openjdk:8-jre-alpine
 ---> 1b46cc2ba839
Step 2/3 : WORKDIR /myApp
 ---> Running in 529b938e7bde
Removing intermediate container 529b938e7bde
 ---> a40643187675
Step 3/3 : COPY ["onlyweb/target/*.jar", "./app.jar"]
 ---> 63722ad415fe
Successfully built 63722ad415fe
Successfully tagged amantuladhar/doc-kub-ws:web-basic

 


Running your app

docker run -it amantuladhar/doc-kub-ws:web-basic

➜ docker run -it amantuladhar/doc-kub-ws:web-basic
/myApp # ls
app.jar

/myApp # java -jar app.jar 

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.2.RELEASE)
...
...
2019-02-13 15:02:09.789  INFO 9 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
  • You still can't access the app from outside as you haven't asked docker to export the port the app is listening to.

ADD

  • ADD @source @destination
  • Exactly as COPY
  • Has few features like, archive extractions. (Like extracting achieve automatically)
  • Best practice is to use COPY if you don’t need those additional features.

EXPOSE

  • EXPOSE @port
  • EXPOSE tells Docker the running container listens on specific network ports.
  • This acts as a kind of port mapping documentation that can then be used when publishing the ports.
  • EXPOSE will not allow communication via the defined ports to containers outside of the same network or to the host machine.
  • To allow this to happen you need to publish the ports using -p options when running container
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
COPY ["onlyweb/target/*.jar", "./app.jar"]

Port Publishing

  • If you want to access your app from the host (outside the container), you need to publish the port where your app is expecting a connection.
  • To do that we have -p @hostPort:@containerPort option.
  • To access your app on localhost:9090
➜ docker run -it -p 9090:8080 amantuladhar/doc-kub-ws:web-basic

# Running your app inside the container
/myApp # java -jar app.jar 
....
....
2019-02-14 04:47:05.036  INFO 10 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
2019-02-14 04:47:05.046  INFO 10 --- [           main] c.e.cloudnative.CloudNativeApplication   : Started CloudNativeApplication in 4.73 seconds (JVM running for 5.685)
  • Here's a response that we get when we call localhost:9090/test
➜ http localhost:9090/test  
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Date: Thu, 14 Feb 2019 04:49:07 GMT
Transfer-Encoding: chunked

{
    "app.version": "v1-web",
    "host.address": "172.17.0.2",
    "message": "Hello Fellas!!!"
}

RUN

  • RUN @command
  • RUN is central executing instruction for Dockerfile.
  • RUN command will execute a command or list of command in a new layer on top of current image
  • Resulting image will be new base image for subsequent instruction
  • To make your Dockerfile more readable and easier to maintain, you can split long or complex RUN statements on multiple lines separating them with a backslash ( \ )
  • Lets install curl in our image. (We will need to use curl later)
  • RUN apk add --no-cache curl
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
RUN apk add --no-cache curl
COPY ["onlyweb/target/*.jar", "./app.jar"]
  • If you build your app now, you will have curl command when you run the container.
  • You can test if curl exist if you want.
➜ docker build -f docker/web-basic -t amantuladhar/doc-kub-ws:web-basic          
...
Step 4/5 : RUN apk add --no-cache curl
 ---> Running in 883ac8f78866
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.9/community/x86_64/APKINDEX.tar.gz
(1/4) Installing nghttp2-libs (1.35.1-r0)
(2/4) Installing libssh2 (1.8.0-r4)
(3/4) Installing libcurl (7.63.0-r0)
(4/4) Installing curl (7.63.0-r0)
...

ENTRYPOINT

  • The ENTRYPOINT specifies a command that will always be executed when the container starts.
  • Docker has a default ENTRYPOINT which is /bin/sh -c
  • ENTRYPOINT ["executable", "param1", "param2"] is the exec form, preferred and recommended.
  • ENTRYPOINT command param1 parm2 is a shell form.
  • ENTRYPOINT is the runtime instruction, but RUN, ADD are build time instructions.
  • We can use --entrypoint option to override when you run the container.
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
RUN apk add --no-cache curl
COPY ["onlyweb/target/*.jar", "./app.jar"]
ENTRYPOINT ["java", "-jar", "app.jar"]
  • Build the image and run the image, without -it option.
  • docker run -p 9090:8080 amantuladhar/doc-kub-ws:web-basic
  • You app will run, you didn't needed to go inside the container and execute the command yourself.
  • -p 9090:8080 was added so that you can access your app from host.

CMD

  • CMD also specifies a command that will execute when container starts.
  • The CMD specifies the arguments that will be fed to the ENTRYPOINT.
  • CMD ["executable","parameter1","parameter2"] this is a so called exec form. (Preferred)
  • CMD command parameter1 parameter2 this a shell form of the instruction.
  • Like ENTRYPOINT CMD is a runtime instruction as well.
  • You already know how to override the CMD, just pass it after the image name when you run the container.
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
RUN apk add --no-cache curl
COPY ["onlyweb/target/*.jar", "./app.jar"]
ENTRYPOINT ["java", "-jar"]
CMD ["app.jar"]
  • As you can see, ENTRYPOINT defines the command that gets executed when container starts.
  • CMD is passing argument to ENTRYPOINT.
  • Build and run the app. docker run -p 9090:8080 amantuladhar/doc-kub-ws:web-basic.

Overriding CMD

  • docker run @image@command @arguments.
  • docker run -p 9090:8080 amantuladhar/doc-kub-ws:web-basic test.jar.
  • Of course above command won't run because we don't have test.jar in our image.
➜ docker run -p 9090:8080 amantuladhar/doc-kub-ws:web-basic test.jar
Error: Unable to access jarfile test.jar
  • How about we try to attach container terminal to host terminal using -it
  • docker run -it amantuladhar/doc-kub-ws:web-basic won't work as this will run the app.
  • docker run -it amantuladhar/doc-kub-ws:web-basic sh won't work as it internally run java -jar sh.
  • If you really want to attach container terminal to host terminal you need to override the ENTRYPOINT.
  • docker run --entrypoint sh -it amantuladhar/doc-kub-ws:web-basic
➜ docker run --entrypoint sh  -it amantuladhar/doc-kub-ws:web-basic        
/myApp #

Understand Docker Layer

 


Docker layers?

  • Layer is basically a change on an image, or an intermediate image.
  • Every instructions you specify (FROM, RUN, COPY, etc.) in your Dockerfile causes the previous image to change, thus creating a new layer.
  • That image layer then becomes the parent for the layer created by the next instruction.
  • Final image and/or image that we use consists of a series of layers which are stacked, one on top of the another.
  • You can think of it as staging changes when you're using git. You add a file's change, then another one, then another one.
  • When you build your image before, you also created lots of images.
  • docker build . -t docker-kubernetes:dockerfile-basics
                            +---------------------------------+
             +------------> |          6e384ad670e7           |
             |              | CMD ["java", "-jar", "app.jar"] |
             |              +---------------------------------+
             +------------> |          adfedcd08e78           |
             |              |COPY ["target/*.jar", "./app.jar"|
             |              +---------------------------------+
             +------------> |          aae885b8525e           |
Layer Id+----+              |   RUN apk add --no-cache curl   |
             |              +---------------------------------+
             +------------> |          14446213dae8           |
             |              |           EXPOSE 8080           |
             |              +---------------------------------+
             +------------> |          a40643187675           |
             |              |          WORKDIR /myApp         |
             |              +---------------------------------+
             +------------> |          1b46cc2ba839           |
                            |    FROM openjdk:8-jre-alpine    |
                            +---------------------------------+

Union File System

 


How Union Filesystem works?

  • By using the union filesystem, Docker combines all these layers into a single image entity.
  • Filesystem structure of the top layer will merge with the structure of the layer beneath.
  • Files and directories which have the same path as in the previous layer will override those beneath.
        +-----------------+
        |    FILE_4.txt   |----+
        +-----------------+    |
                               |       +---------------+
                               |       |               |
        +-----------------+    |       |  FILE_1.txt   |
        |    FILE_2.txt   |--------->  |  FILE_2.txt   |
        |    FILE_3.txt   |    |       |  FILE_3.txt   |
        +-----------------+    |       |  FILE_4.txt   |
                               |       |               |
                               |       +---------------+
        +-----------------+    |
        |    FILE_1.txt   |----+
        |    FILE_2.txt   |
        +-----------------+

 


How are layers connected?

  • To maintain the order of layers, Docker utilizes the concept of layer IDs and pointers.
  • Each layer contains the ID and a pointer to its parent layer.
  • A layer without a pointer referencing the parent is the first layer in the stack, a base.
  • Layers are reusable and cacheable. If same kind of layers are found, docker will reuse it.
  • Reusable layers is also a reason why Docker is so lightweight in comparison to full virtual machines, which don't share anything. It is thanks to layers that when you pull an image, you eventually don't have to download all of its filesystem
            +---------------------------+
            |         Layer Id          |
            |                           |
            |         Parent Id         |
            +------------|--------------+
                         |
            +------------v--------------+
            |         Layer id          |
            |                           |
            |         Parent Id         |
            +------------|--------------+
                         |
            +------------v--------------+
            |         Layer id          |
            |                           |
            |         Parent Id         |
            +------------+--------------+

Health Check

 


What is HEALTHCHECK

  • The HEALTHCHECK instruction tells Docker how to test a container to check that it is still working.
  • There could be a scenario where our application process is still running, but may not respond to our request.
  • HEALTHCHECK instruction helps us to identify this issue.
  • We can have multiple HEALTHCHECK instruction in our Dockerfile but only last one will take an effect.
  • When a container has a HEALTHCHECK specified, it has a health status in addition to its normal status.

Note: Kubernetes doesn't support this HEALTHCHECK instruction. Kubernetes has its own way to checking if containers are healthy.

HEALTHCHECK

  • The HEALTHCHECK instruction can be used in two forms.
    • HEALTHCHECK [OPTIONS] CMD command. Which checks container health by running a command inside the container.
    • HEALTHCHECK NONE. This will disable any health check inherited from the base image.
  • If you are using the app from Simple Spring App, make sure you are in dockerfile-initial branch.
  • We have couple of REST endpoints in the app
    • /test/ which kind of hello-world endpoint for us
    • /test/status-5xx which will return status 200 for first 5 times, but after that it will throw exception. Our app will still be running.
    • /test/exit-1 to exit the app with non-zero status code.
    • /test/exit-0 to exit the app with zero status code.
  • Let's add HEALTHCHECK in our image.
  • HEALTHCHECK CMD curl --fail http://localhost:8080/test || exit 1
  • We are making a curl request to our app and if it fails we are exiting our app with status code 1.
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
RUN apk add --no-cache curl
COPY ["onlyweb/target/*.jar", "./app.jar"]
CMD ["java", "-jar", "app.jar"]
HEALTHCHECK CMD curl --fail http://localhost:8080/test || exit 1
  • Let's build and run this image.
IMAGE                                 STATUS                    
amantuladhar/doc-kub-ws:web-basic     Up XX seconds (healthy)
  • We will play around with HEALTHCHECK more, but first let's learn about different options HEALTHCHECK supports.

--interval

  • --interval=@duration
  • Default duration time is 30s
  • This configures time HEALTHCHECK needs to wait before it runs next HEALTHCHECK CMD.

--timeout

  • --timeout=@duration
  • Default timeout is 30s
  • This configures the time it should take a HEALTHCHECK CMD to finish.
  • If a single CMD run takes longer than timeout seconds then the check is considered to have failed.

--retries

  • --retries=@numberOfRetries
  • Default value for retry is 3.
  • This configures how many times HEALTHCHECK needs to fail before it determines out container is unhealthy.

--start-period

  • --start-period=@duration
  • Default value for start period duration is 0s.
  • With is option we can configure time it will take for app to run.
  • If we run pre-mature HEALTHCHECK our app might not have initialized and it may result in our container be in unhealthy state.
  • Basically it will run our first HEALTHCHECK CMD after time specified.

 


Playing with HEALTHCHECK

  • Let's update our HEALTHCHECK instruction.
FROM openjdk:8-jre-alpine
WORKDIR myapp/
COPY ["onlyweb/target/*.jar", "app.jar"]
EXPOSE 8080
RUN apk add --no-cache curl
CMD ["java", "-jar", "app.jar"]
HEALTHCHECK  --start-period=10s \
             --interval=5s \
             --timeout=2s  \
             --retries=3 \
             CMD curl --fail http://localhost:8080/test/status-5xx || exit 1
  • Build you image and run it. And keep an eye on health status.
  • Remember status-5xx will return status 200 for first 5 calls
  • So for a minute or so our container health will be healthy.
  • But because the HEALTHCHECK is called frequently, and for our app at exactly 6th call it will start to fail.
  • On a 6th call it won't flag our container as unhealthy as we defined retries.
  • Docker makes sure that three consecutive failure happens before it flags the container as unhealthy.
IMAGE                                  CREATED             STATUS                          
amantuladhar/doc-kub-ws:web-basic      10 seconds ago      Up 10 seconds (health: starting)
IMAGE                                 CREATED             STATUS                 
amantuladhar/doc-kub-ws:web-basic     24 seconds ago      Up 24 seconds (healthy)
IMAGE                                 CREATED             STATUS                  
amantuladhar/doc-kub-ws:web-basic     2 minutes ago       Up 2 minutes (unhealthy)

Complete dockerfile for with health check web-healthcheck web-healthcheck

Restart Policies

 


  • Restart policies is used to control whether the containers starts automatically when they exit, or even when Docker restarts.
  • Restart policies tells Docker how to react when a container shuts down.
  • A restart policy only takes effect after a container starts successfully.
  • Starting successfully means that the container is up for at least 10 seconds and Docker has started monitoring it.
  • This prevents a container which does not start at all from going into a restart loop.
  • By using the --restart option with the docker run command you can specify a restart policy.
  • Types of restart policies
    • no
    • always
    • no-failure
    • unless-stopped

no

  • The no policy is the default restart policy and simply will not restart a container under any case.

always

  • If we wanted the container to be restarted no matter what exit code the command has, we can use the always restart policy.
  • The restart policy will always restart the container.
  • Whenever the Docker service is restarted, containers using the always policy will also be restarted.

on-failure

  • Restart your container whenever it exits with a non-zero exit status and not restart otherwise.
  • You can optionally provide a number of times for Docker to attempt to restart the container.
  • --restart=on-failure:@number
  • Docker uses a delay between restarting the container, to prevent flood-like protection.
  • This is an increasing delay; it starts with the value of 100 milliseconds, then Docker will double the previous delay.
  • If a container is successfully restarted, the delay is reset to the default value of 100 milliseconds

unless-stopped

  • Again, similar to always, if we want the container to be restarted regardless of the exit code, we can use unless-stopped.
  • It will restart the container regardless of the exit status, but do not start it on daemon startup if the container has been put to a stopped state before.
  • If you are using the app from Simple Spring App, make sure you are in dockerfile-initial branch.
  • We have couple of REST endpoints in the app
    • /test/ which kind of hello-world endpoint for us
    • /test/status-5xx which will return status 200 for first 5 times, but after that it will throw exception. Our app will still be running.
    • /test/exit-1 to exit the app with non-zero status code.
    • /test/exit-0 to exit the app with zero status code.

Restart Policies in Action

  • Let's start our app using on-failure
  • Here's a Dockerfile content
  • Run the image with --restart option.
docker run -d -p 9090:8080 --restart\=on-failure amantuladhar/doc-kub-ws:web-basic
IMAGE                                 STATUS              
amantuladhar/doc-kub-ws:web-basic     Up About a minute
  • Let's call /test/exit-1, which will end the process with non zero exit code.
IMAGE                                 STATUS                     
amantuladhar/doc-kub-ws:web-basic     Restarting (1) 1 second ago
  • Now let's call /test/exit-0, which will end the process with zero exit code.
  • If you list the running container, you won't see it now.

Update restart policies of running container

  • docker update --restart=new-policies @id/@name
  • Where @id/@name is of container

Docker Volume

 


  • Volumes are directories (or files) that are outside of the default Union File System and exist as normal directories and files on the host filesystem.

Why do we need volume?

  • To understand why we need volumes lets go back to simple image busybox.

  • docker run -it busybox

    • Create a directory and add a file. touch test.txt
    • Run new container with same image, list the files and you won't see the file you created.
    • Why not run same container? If we run same container we will still see that file.
    • Remember container are ephemeral and disposable.
    • We should not depend on starting same container.
  • How do we persist changes, so that when we run new container we see that file?

Named Volume

  • The most direct way is declare a volume at run-time with the -v @name:@path flag.
  • @name is name of the volume.
  • @path is the path inside container.

-v @name:@path

docker run -v myVolume:/my-volume --it busybox
  • This will make the directory /my-volume inside the container live outside the UFS and directly accessible on the host.
  • Any files that the image held inside the /my-volume directory will be copied into the volume.
  • If the container path specified doesn't exist docker will create one for you.

Where can I find the file in host system?

  • We can see where docker is storing our file by inspecting the container.
  • docker inspect @container_id
  • If you see "Mounts" property, you will see properties like
    • "Source" is path in the host
    • "Destination" is path in the container
  • If you try to navigate to path specified by "Source" in you computer, you won't find it. Why?
  • For docker that host path is inside Virtual Machine.
  • We can also see docker volume list to list all volumes.
  • As you can see volume name is big hash at the moment

Manually create a named volume

  • To manually create a named volume, you can use docker volume create command.
docker volume create --name @volume\_name

Reattaching same volume

  • If you attach a same volume when running new container, you will find all previous files.
docker run -v myVolume:/my-volume -it busybox

# Create a file inside my-volume directory
> touch my-volume/test.txt

# List content of my-volume directory
> ls my-volume/
test.txt
  • Running new container with same volume
docker run -v myVolume:/my-volume -it busybox
# List content of my-volume directory
> ls my-volume/
test.txt

Mount host path

 


  • -v has yet another major use case, which can only be accomplished through -v option.
  • Mount the host directory into container.
  • docker run -v @host_path:@container_path -it busybox
    • host path must be absolute.
    • container path is absolute as well.
docker run -v $(pwd)/volume/:/my-volume -it busybox
  • We can use $(pwd) to get absolute path for current directory.
  • With above command, we are mounting /volume directory that relative to where build process was started.

Simple example

  • Let's try this with busybox first.
  • docker run -v $(pwd)/volume/:/my-volume -it busybox
  • Create a file under /my-volume inside the container.
  • You will see that file in host directory as well, under "CURRENT_DIR"/volume.

Using volume for persistence storage

  • Using volume on these case doesn't make sense.
  • We are creating a file and checking if it exists in subsequent runs.
  • But consider this scenario when you run mysql image.
  • You would want to persist the data that you created inside container.
  • It doesn't matter how many times you restart your container, we must not lose the data.

Using to persist mysql data

  • mysql stores its data inside /var/lib/mysql/.
  • So if we map our host path with /var/lib/mysql/, we will be able to persist mysql data.
  • If you try to run the mysql image it expects couple of Environment Variable.
    • MYSQL_ROOT_PASSWORD
    • MYSQL_DATABASE
docker run \
       -v $(pwd)/volume/mysql-data:/var/lib/mysql/ \
       -e MYSQL_ROOT_PASSWORD=test \
       -e MYSQL_DATABASE=test \
       mysql:5.7

I am using -e options to pass in Environment Variables to container.

  • I am deliberately not passing -it when running container.
  • Default CMD for mysql image is used for starting mysql
  • If we override it my passing Shell executable, to attach the pseudo tty, it won't start the mysql.
  • After container is running, we can attach container tty into host terminal.
  • You already learned this but in case you forgot. To attach the pseudo tty for a running container we use docker container exec -it @container(id/name) sh
docker container exec -it 750d0ddbbe51 sh
  • Login to mysql from container terminal.
  • Create a table and insert some data into that table.
> mysql -u root -p

mysql> USE test;
Database changed

mysql> CREATE TABLE student(id INT(11), name VARCHAR(255));
Query OK, 0 rows affected (0.02 sec)

mysql> INSERT INTO student VALUES (1, 'Aman');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM student;
+------+------+
| id   | name |
+------+------+
|    1 | Aman |
+------+------+
1 row in set (0.00 sec)
  • Now you stop the running container and run a new one, with same path ($(pwd)/volume/mysql-data) mapped to /var/lib/mysql/
  • You will see data is still present.
docker run \
     -v $(pwd)/volume/mysql-data:/var/lib/mysql/ \
     -e MYSQL_ROOT_PASSWORD=test \
     -e MYSQL_DATABASE=test \
     mysql:5.7

Docker Network


Why do we need network?

  • To see why we need inter network communication, let's run two container (in background).
➜ docker run \
         -p 9090:8080
         -d amantuladhar/doc-kub-ws:web-basic
  • Let's try to access our app from different container
➜ docker run \
         -it amantuladhar/network-tools sh

network-tools image have few tools like curl, http, ping, nc.

  • From this container, let's call http localhost:8080/test.
  • It won't work because there is no connection between those two containers.
  • Consider scenario where our app needs to talk with container that is running mysql.

Listing network

docker network list
  • If you do docker network list, you will see three networks is already created
    • bridge
    • host
    • none
  • These are networks created by different network drivers docker supports. (We have few more drivers).
  • Default driver for network is bridge.

Create a network

  • To create a network you just issue docker network create @name.
docker network create myNet
  • This will create network with name myNet with driver bridge.

 


Using network

Define which network to connect to

  • --net=@network_name syntax.
  • To use the network we created we can use --net option.
➜ docker run \
         --name=myApp
         --net=myNet
         -p 9090:8080
         -d amantuladhar/doc-kub-ws:web-basic
  • Notice I am naming my container myself using --name option. You will know in a bit why I am doing this.
  • This will run our container in our network myNet.
  • Now lets run network-tools image on same network.
➜ docker run \
         --name=myTester \
         --net=myNet
         -it amantuladhar/network-tools sh
  • Try to execute curl on localhost:8080/test. or localhost:9090/test
  • It won't work. Why? We explicitly ran both of your container on same network.

Container Discovery

  • To access the app running on same network, we cannot use localhost.
  • I think this is obvious. From the application standpoint they are not running on same computer*.
  • If we can't use localhost what do we use?
  • Turns out we have to use container name i.e myApp in our case.
  • If you do curl on myApp:8080/test it will work.

Docker has builtin DNS server that converts container name to its IP.

Auto connect to specified container network

  • --net has a syntax that supports auto network discovery i.e --net=container:name.
  • If you use this syntax, docker will automatically connect network that is used by specified container.
  • If you use this syntax, you will be able to use localhost instead of container_name .
  • Run the image that has our app like before.
➜ docker run \
         --name=myApp
         --net=myNet
         -p 9090:8080
         -d amantuladhar/doc-kub-ws:web-basic
  • Run the tester again with container syntax.
docker run \
     --name=myTester \
     --net=container:myApp \
     -it \
     amantuladhar/network-tools \
     sh 
  • Test using localhost, and it just works.
/ # curl localhost:8080/test | jq .
{
  "message": "Hello Fellas!!!",
  "host.address": "172.23.0.3",
  "app.version": "v1-web"
}
  • As exercise, you can try to create application that stores some values in database.
  • You can use amantuladhar/docker-kubernetes:v1-db image.
  • Simple Web App at branch initial-db
  • App expects that you will set DB_URL, DB_USER and DB_PASS environment variable, which is a full connection URL for mysql.

Docker Compose


What is docker compose?

  • docker compose is tool for defining and running complex applications with Docker.
  • With Compose, we define a multi-container application in a single file.
  • It allows you to spin your application up in a single command.
  • Orchestration between two containers are easy with docker compose.
  • Default file name for docker-compose is is docker-compose.yml.

Docker Compose works in the scope of a single host, where Kubernetes works on multi cluster environment.

Simple compose file

  • This is what a simple compose file looks like.
version: '3'                                       # 1    
services:                                          # 2
  myApp:                                             # 3
    image: amantuladhar/docker-kubernetes:v1-web     # 4 
  • #1 specifies what version of compose API you want to use.
  • #2 is a root property. It includes list of all services (containers) compose will manage.
  • #3 is name of the service (container)
  • #4 specifies image you want to run.

If you want to build you app yourself you can find the app in Spring Simple Web branch compose-initial.

  • You noticed that in compose file containers are called services.
  • Compose can manage multiple services, but for now we are dealing with only one.
  • We defined the service called myApp which will run the image.

Build your image

  • If you want to build your own image instead of using one, you can do that easily too
version: '3'
services:
  myApp:
    build:                     # 1  
      context: .               # 2
      dockerfile: Dockerfile   # 3
  • #1 build tag will let compose know that you want to build your image.
  • #2 context defines where your docker file are stored.
  • #3 Dockerfile takes the file name for Dockerfile. If the file name is Dockerfile you don't seed to specify the property.
  • You use docker-compose build to build your image. (docker compose build in the latest version)
  • If you make any changes to Dockerfile you need to explicitly build the image again.

Any docker compose command can now be used without - i.e as part of docker command

Running services

  • To run the services defined in compose file is super easy.
  • You run any number of services defined inside compose file with single command.
  • i.e docker-compose up. On latest docker for desktop installation you can just docker compose up.
  • If you want to run it in background add -d option.
  • If you run the app right now, it will run but it won't be accessible from our host yet.
  • We need to publish ports first in order to access the app.

Stopping services

  • Starting the services were easy. How about stopping them?
  • It is super easy as well. docker-compose stop. (docker compose stop in new version).
  • It will stop all running services at once.

Docker Compose Publishing Ports


Publishing Ports

  • We made our app run with single command, but we were not able to access them.
  • To access our app we needed to publish the ports.
  • We know for docker run command we use -p option.
  • On compose we use ports property
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
  • You can publish any number of ports you want as you would with -p option.
  • You may have a hint on what docker-compose is trying to achieve.
  • If we publish ports with docker run, we need to specify that in every run.
  • But with compose, you don't need it.
  • We define the options in yaml file, and we simply use docker-compose up.
  • If you run your application now, you will be able to access it from the host.

Docker Compose Logs


Accessing logs

  • If you run the app using -d option, you won't see the outputs (logs) on the screen.
  • If you want to access them or see them, you use docker-compose logs @option @service command.
  • If you run docker-compose logs it will show you all the logs for all services, but it won't show you new ones.
  • If you want to follow the logs in realtime you use -f option. docker-compose logs -f.
  • If you have multiple services, and you want to see logs for only single service, you can use: docker-compose logs @options @serviceName syntax.

Multiple Services


Defining Multiple Services

  • Docker compose shine when you want to run multi-container app.
  • Even if you want to run multiple container, you can run your app using single command.
  • Let's add mysql service in our compose file.

We will define mysql service but our app won't try to interact with database at the moment.

services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
  myDb:                                    #  1
    image: mysql:5.7                       #  2
    environment:                           #  3
      - MYSQL_ROOT_PASSWORD=password       #  4
      - MYSQL_DATABASE=docker_kubernetes   #  4
  • #1 name of new service.
  • #2 image you want to run. You can use build if you want to.
  • #3 Passing environment variables to image. Like we did with -e option.
  • #4 List of environment variables
  • One new thing in this service is that we are setting environment variables in the file.
  • Remember when you had to do this in command, those are old days now :).

Listing the running services

  • If you want to list the running services you can use docker-compose ps

Attach mysql terminal

  • docker-compose exec @servicename @cmd
  • If you want to attach mysql pseudo tty to host, we can do that too.
  • docker-compose exec by default attaches the pseudo tty so we don't need to add -it option.
  • If you don't want to attach pseudo-tty you can use -T option.
  • For now let's use docker-compose exec myDb sh
  • Play around with the service, but remember data won't be persisted in next run

Volumes


  • We leaned how we can attach volumes using docker run command.
  • There was one problem, we had to use very long command.
  • Using big command is not a problem, if we use it only once or twice.
  • But we want to be able to run container multiple times.
  • You already know docker-compse tries to simply process of running container.
  • Compose supports volumes as well.
  • We simply use volumes properties for a service
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
  myDb:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=docker_kubernetes
    volumes:                                       # 1
      - "./volume/mysql-compose/:/var/lib/mysql/"  # 2
  • #1 we use volumes properties to map the volumes
  • #2 In this case we are mapping host volume to /var/lib/mysql.
  • Play around with the service.
  • Add a table, insert a data then, dispose all services and start up again.
  • Check if all data are still there.

Different ways to map volumes

volumes:
  # Just specify a path and let the Engine create a volume
  - /var/lib/mysql

  # Specify an absolute path mapping
  - /opt/data:/var/lib/mysql

  # Path on the host, relative to the Compose file
  - ./cache:/tmp/cache

  # Named volume
  - datavolume:/var/lib/mysql

Named volumes

  • If you want to use named volume in docker-compose, you don't need to create one yourself.
  • You have volumes root property where you can define your volumes.
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
  myDb:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=docker_kubernetes
    volumes:
      - "./volume/mysql-compose/:/var/lib/mysql/"
      - "namedVolume:/test/path"                   # 3                        
volumes:                                           # 1
  namedVolume:                                     # 2
  • #1 root volumes property, where we can define n number for volumes.
  • #2 name of the volume. You can add some properties inside namedVolume too.

Network


  • Networks are also easy concept, we are not going to learn new stuff here.
  • We will just learn how to do same stuff, but more efficiently.
  • But at the end you will create an app that talks with database.
  • And most of all database values will be persisted.

Setting Up Network

  • If you were paying attention to logs when we run our services, you saw that docker-compose creates a default network for us.
  • So all services defined on same file are already in same ( default ) network.
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
  myTester:                               # 1
    image: amantuladhar/network-tools     # 2
    command: sleep 50000                  # 3
  myDb:
    image: mysql:5.7
...
...
  • #1 Define one more service myTester
  • #2 Use the image amantuladhar/network-tools. Remember when we learned networks, we used this to test connection between two containers.
  • #3 Overriding default command for an image, as this image doesn't have long-running process. This is so that it runs for a long period of time, and we can go inside the service and test our network.
  • I believe this is nothing new, we already did this before.
  • Only difference is that, now we are using compose, and it's convenience.

Testing Network

  • Attach pseudo-tty of myTester.
docker-compose exec myTester sh
  • Test the network like we did before.
  • We have to use service_name to communicate with other container.
  • localhost will not work. We will learn how we can achieve this later.
/ # curl myApp:8080/test | jq .
{
  "message": "Hello Fellas!!!",
  "host.address": "192.168.48.2",
  "app.version": "v1-web"
}

Named Network

  • While relying on default network works, you may want to name your network yourself.
  • You may even want to create multiple networks.
  • To achieve that we have networks root properties.
  • If we use it as a root property, we can define any number custom networks we want.
  • If used inside services property, it will make that particular service reside in the network.
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
    networks:                              # 3     
      - myNet                              # 4    
  myTester:
    image: amantuladhar/network-tools
    command: sleep 50000
    networks:                              # 3    
      - myNet                              # 4    
  myDb:
    image: mysql:5.7
...
...
networks:                                  # 1
  myNet:                                   # 2
...
... 
  • #1 this is a root property. This includes list of custom network you want to create.
  • #2 name of network you want to create.
  • #3 this is used inside services. This defines which network particular service communicates with.
  • #4 Name of networks service will use. You can add multiple networks
  • You can check if two containers can talk with each other like before.

Using network_mode

  • With help of network_mode property we can define the way container network are configured.
  • Remember --net=container:@name syntax. network_mode can achieve something like this.
  • network_mode supports
    • "bridge"
    • "host"
    • "none"
    • "service:@name"
    • "container:@id/@name"
  • For now, we will use serrvice:@name mode. Which I think will often be used than others.
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-web
    ports:
      - 9090:8080
    networks:                                   
      - myNet                                 
  myTester:
    image: amantuladhar/network-tools
    command: sleep 50000
    network_mode: service:myApp           # 1
  myDb:
    image: mysql:5.7
...
...
...
  • #1 This defines that we want to use same network that is being used by service myApp.
  • We are using networks properties for **myApp**, to define network for service.
  • Notice we didn't even have to define networks properties for myTester.
  • When defining a service you cannot use ports and network_mode properties at the same time.
  • Testing using localhost in myTester
/ # curl localhost:8080/test
{
"message": "Hello Fellas!!!",
"host.address": "192.168.112.2",
"app.version": "v1-web"
}

Web App With Database

  • You now have knowledge of how docker-compose and its component works.
  • Let's create a web app that connects with database.
  • You can use amantuladhar/docker-kubernetes:v1-db image to get the app that tries to connect with database.
    • This tag expects three environment variable: DB_URL, DB_USER, DB_PASS.
  • Web App With Database Example branch compose-db-initial if you want to build it yourself.
  • Here's a compose file we are initially working with
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-db
    ports:
      - 9090:8080
  myDb:
    image: mysql:5.7
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=docker_kubernetes
    volumes:
      - "./volume/mysql-compose/:/var/lib/mysql/"
networks:
  myNet:
  • First step is to add network on both the services. (Or you can use default network).
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-db
    ports:
      - 9090:8080
    networks:                                 # 1
      - myNet                                 # 1
  myDb:
    image: mysql:5.7
    networks:                                 # 1
      - myNet                                 # 1
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=docker_kubernetes
    volumes:
      - "./volume/mysql-compose/:/var/lib/mysql/"
networks:
  myNet:
  • #1 added network to our services.
  • If you are using image above, it expected three stuff so that it can successfully connect with database.
    • DB_URL Connection URL
    • DB_USER Database User
    • DB_PASS Database User Password
version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-db
    ports:
      - 9090:8080
    networks:
      - myNet
    environment:                                                       # 1
      - DB_URL=jdbc:mysql://myDb:3306/docker_kubernetes?useSSL=false   # 2
      - DB_USER=root                                                   # 2
      - DB_PASS=password                                               # 2   
  myDb:
    image: mysql:5.7
    networks:
      - myNet
    environment:
      - MYSQL_ROOT_PASSWORD=password
      - MYSQL_DATABASE=docker_kubernetes
    volumes:
      - "./volume/mysql-compose/:/var/lib/mysql/"
networks:
  myNet:
  • #1 Using environment property to set our environment variable.
  • #2 Setting necessary environment variables.
  • If you run the services now you should be able to start your app and run it.

Managing service start order

  • When using above compose file, it is not necessary that database starts first and web later.
  • Docker compose doesn't know which service to start first.
  • If our web app starts first and then database, clearly our app will fail.
  • To minimize such issues docker-compose has depends_on property that we can use.
  • We can add list of services particular service depends on and docker-compose will take care of starting them in order.

depends_on does not wait for mysql to be ready before starting web only until they have been started.

version: '3'
services:
  myApp:
    image: amantuladhar/docker-kubernetes:v1-db
...
...
    depends_on:            <-------------
      - myDb
  myDb:
    image: mysql:5.7
    networks:
      - myNet
...
...
networks:
  myNet:

Restart Policy


  • restart property accepts the same values we learned before.
    • on-failure
    • unless-stopped
    • always
    • none
version: '3'
services:
  myApp:
...
    restart: on-failure
... 

Health Check


healthcheck

  • We also have healthcheck property if we want to override the healthcheck of Image.
  • Like HEALTHCHECK instruction, we can provide different option to configure our healthcheck
    • interval
    • timeout
    • retries
    • start_period
version: '3'
services:
  myApp:
...
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/test"]
      interval: 10s
      timeout: 5s
      retries: 3
      start_period: 20s

Maven Docker Plugin


Docker with Maven Basics

  • We learned docker and docker-compose basics.
  • We can easily containerize our app, even if it needs database support.
  • While creating images manually by yourself can be done, more human interaction leads to more errors.
  • Delegating the image build process to maven gives you a lot of flexibility and also saves a lot of time.
  • This can be especially useful, if you have continuous integration flow set up, using Jenkins for example.
  • There are multiple maven plugins for docker. We will be working with
    • fabric8io/docker-maven-plugin
    • I like this one as this has lots of options and is highly configurable.
    • So much that you don’t even need a Dockerfile to build your own image when using this plugin.

For this chapter we will use Spring Simple Web App - dockerfile-initial-complete branch.

New Maven Goals

  • If we use fabric8io/docker-maven-plugin, we get few new maven goals.
  • Some important one that we will cover
    • docker:build: This will build the docker image from the configuration we add to pom.xml.
    • docker:start/stop: Run/Stop the image built from configuration.
    • docker:remove: This is for cleaning up the images and containers.
    • docker:logs: This prints out the output of the running containers.

Using Plugin

<build>
    <plugins>
        <plugin>
            <groupId>io.fabric8</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>0.28.0</version>
        </plugin>
    </plugins>
</build>
  • This plugin supports lots of configuration, and we put the configuration inside <configuration> tag.
<build>
    <plugins>
        <plugin>
            <groupId>io.fabric8</groupId>
            <artifactId>docker-maven-plugin</artifactId>
            <version>0.28.0</version>
            <configuration>
                <verbose>true</verbose>
                <images>
                    <image>

                    </image>
                </images>
            </configuration>
        </plugin>
    </plugins>
</build>
  • The important part in the plugin definition is the <configuration> element.
  • It has <images> element which allows us to add multiple <image>.
  • There are two main elements in the <image>:
    • A <build> configuration specifying how images are built.
    • A <run> configuration describing how containers should be created and started. Think of this as option that you pass on docker run command.
  • If you are using the app from GitHub you should have a Dockerfile
FROM openjdk:8-jre-alpine
WORKDIR /myApp
EXPOSE 8080
RUN apk add --no-cache curl
COPY ["target/*.jar", "./app.jar"]
CMD ["java", "-jar", "app.jar"]

Using Dockerfile

  • Let's add some configuration using build tag.
<image>
    <name>docker-kubernetes:${project.version}</name>
    <build>
        <dockerFileDir>${project.basedir}</dockerFileDir>
        <dockerFile>Dockerfile</dockerFile>
    </build>
</image>
  • To build the dockerfile you just need to run ./mvnw clean package docker:build. It will build your app.
  • Default value for <dockerFileDir> is /src/main/docker.
  • We have our Dockerfile inside project root directory, so we are overriding the value.

Run using docker:start

  • It is as simple as ./mvnw package docker:start.
...
[INFO] --- docker-maven-plugin:0.28.0:start (default-cli) @ cloudnative ---
[INFO] DOCKER> [docker-kubernetes:v1-web]: Start container b9cbcab4c5a6
... 
  • If you list the running container you can see that one container is running.
  • Of course, we cannot access the app, we haven't published the port yet.

Stop using docker:stop

  • To stop the running container you can use ./mvnw package docker:stop
...
[INFO] --- docker-maven-plugin:0.28.0:stop (default-cli) @ cloudnative ---
[INFO] DOCKER> [docker-kubernetes:v1-web]: Stop and removed container b9cbcab4c5a6 after 0 ms
...

Port Publishing

  • We can publish the port easily as well.
  • For this plugin we add configuration inside <run> tag.
  • Inside <run> tag we use <ports> tag to expose ports.
  • Every port mapping are put inside <port> tag.
<image>
  <name>docker-kubernetes:${project.version}</name>
  <build>
    <dockerFileDir>${project.basedir}</dockerFileDir>
    <dockerFile>Dockerfile</dockerFile>
  </build>
  <run>
    <ports>
      <port>9090:8080</port>
    </ports>
  </run>
</image> 
  • build and run you app again and you will be able to access it from the host this time.

Logs

  • If you want to see the logs just use docker:logs goal.

Removing images

./mvnw clean package docker:remove
  • This goal can be used to clean up images and containers.
  • By default, all images with a build configuration are removed.

Build image without Dockerfile

  • The plugin we are using supports creating Dockerfile in memory.
  • Inside the <build> tag, we can write Dockerfile like instructions.
<image>
  <name>docker-kubernetes:${project.version}</name>
  <build>
    <from>openjdk:8-jre-alpine</from>
    <assembly>
      <descriptorRef>artifact</descriptorRef>
    </assembly>
    <cmd>java -jar maven/${project.name}-${project.version}.jar</cmd>
  </build>
  <run>
    <ports>
      <port>9090:8080</port>
    </ports>
  </run>
</image> 
  • <from> is same as FROM instruction.

  • <assembly> defines how build artifacts and other files can enter the Docker image. Using this we don't need to use COPY to copy our build artifact.

  • Inside <assembly> we are using <descriptorRef> which has some predefined ways to copy files to image.

    • artifact-with-dependencies: Attaches project’s artifact and all its dependencies.
    • artifact: Attaches only the project’s artifact but no dependencies.
    • project: Attaches the whole Maven project but without the target/ directory.
    • rootWar: Copies the artifact as ROOT.war to the exposed directory. I.e. Tomcat will then deploy the war under the root context.
  • We are using artifact pre-defined descriptor in our configuration, because we want to copy our jar file only.

  • <cmd> is same as CMD instruction.

  • Build and run your app again. You will be able to access your app from the host.

Health Check


Run Command

  • How to add RUN instruction when using in-memory Dockerfile.
<build>
    ...
    <runCmds>
        <run>apk add --no-cache curl</run>
    </runCmds>
    <cmd>java -jar maven/${project.name}-${project.version}.jar</cmd>
</build>
  • <runCmds> can have multiple <run> tag.
  • Here we are installing curl.

Restart Policy

  • If you want to restart policies for your images, you can use <restartPolicy> tag.
<run>
    <restartPolicy>
        <name>on-failure</name>
        <retry>3</retry>
    </restartPolicy>
    ...
</run> 
  • <restartPolicy> can is added inside of <run> tag.
  • It has <name> and <retry> children tag.
  • <name> take the type of policy you want to enforce.
  • <retry> how many times you want to restart your image, when on-failure policy is used.

Health check

  • We can add a health check in our image as well.
  • We use <healthCheck> tag, which has few child tags.
<build>
    ...
    <healthCheck>
        <interval>10s</interval>
        <timeout>5s</timeout>
        <startPeriod>10s</startPeriod>
        <retries>3</retry>
        <cmd>curl --fail http://localhost:8080/test/status-5xx || exit 1</cmd>
    </healthCheck>
</build>
  • We use <healthCheck> tag inside <build> tag.
  • It has few children tags, which represents the option health check supports.
  • /test/status-5xx will throw Internal Server Error after 6th request.
  • So for time being image will be healthy and after couple of minutes it will be tagged as unhealthy.

Multiple Container


Maven Plugin Running Multiple Container

  • With fabric8/docker-maven-plugin we can run multiple images.
  • Let's run mysql, but we won't run the app that talk with this database (for now).
<images>
    <image>
        <name>docker-kubernetes:${project.version}</name>
        ...
    </image>
    <image>
        <name>myDb:${project.version}</name>
        <alias>myDb</alias>
        <build>
            <from>mysql:5.7</from>
        </build>
        <run>
            <env>
                <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD>
                <MYSQL_DATABASE>docker_kubernetes</MYSQL_DATABASE>
            </env>
        </run>
    </image>
</images>
  • You just need to add another <image> tag inside <images> tag.
  • Here I am using mysql:5.7 image.
  • Remember mysql expects that you pass some environment variable.
  • With <env> tag you can set the environment variable.
  • If you docker:start now, you will run both containers.

Volume


Maven Plugin Volumes

  • To mount the volume we have <volumes> tag.
  • <volumes> tag has children tag <from> and <bind>.
    • <from>: List of <image> elements which specify image names or aliases of containers whose volumes should be imported.
    • <bind>: List of <volume> that maps host folder to container path.
<images>
    <image>
        <name>docker-kubernetes:${project.version}</name>
        ...
    </image>
    <image>
        <name>mydb:${project.version}</name>
       ...
        <run>
            <volumes>
                <bind>
                    <volume>${project.basedir}/volume/mysql-plugin/:/var/lib/mysql/</volume>
                </bind>
            </volumes>
            ...
        </run>
    </image>
</images>
  • I am saving mysql data in ${project.basedir}/volume/mysql-plugin/.
  • Start the container, attach pseudo-tty of myDb container and then insert some data to database.
  • Stop and run the container again to see if data is saved.

Network


Maven Plugin Networks

For this chapter we will use Simple App With DB - from plugin-db-initial branch.

Auto Create Network

  • When dealing with network, we may need to create a network.
  • If we create a custom network, we need to create it manually.
  • Plugin that we are using can be configured so that custom network can be created automatically.
  • Just set <docker.autoCreateCustomNetworks> value to true and you will be all set.
<properties>
    ...
    <docker.autoCreateCustomNetworks>true</docker.autoCreateCustomNetworks>
</properties>

Create a network

  • To create a network we use <network> tag.
  • <network> has few children.
    • mode: The network mode, which can be one of the following values:
    • name: For mode container this is the container name, which is this image alias. For Mode custom this is the name of the custom network.
    • alias: One or more alias element can be provided which gives a way for a container to be discovered by alternate names by any other container within the scope of a particular network. This configuration only has effect for when the network mode is custom. More than one alias can be given by providing multiple entries.
<image>
    <name>docker-kubernetes:${project.version}</name>
    ...
    <run>
        <network>
            <mode>custom</mode>
            <name>mynet</name>
        </network>
        ...
        <env>
            <DB_URL>
                <![CDATA[ jdbc:mysql://mysql_db:3306/docker_kubernetes?useSSL=false]]>
            </DB_URL>
            <DB_USER>root</DB_USER>
            <DB_PASS>password</DB_PASS>
        </env>
    </run>
</image>
<image>
    <name>mydb:${project.version}</name>
    <alias>mydb</alias>
    ...
    <run>
        <network>
            <mode>custom</mode>
            <name>mynet</name>
            <alias>mysql_db</alias>
        </network>
        ...
        <ports>
            <port>4406:3306</port>
        </ports>
        <env>
            <MYSQL_ROOT_PASSWORD>password</MYSQL_ROOT_PASSWORD>
            <MYSQL_DATABASE>docker_kubernetes</MYSQL_DATABASE>
        </env>
    </run>
</image>
  • To add your container in a network, you use <network> tag inside <run> tag.

  • <mode> can accept multiple value

    • bridge : Bridged mode with the default Docker bridge (default)
    • host : Share the Docker host network interfaces
    • container : Connect to the network of the specified container. The name of the container is taken from the <name> element.
    • custom : Use a custom network, which must be created before by using docker network create.
    • none : No network will be setup.
  • In our case we are using custom, because we want to create our own network.

  • <alias> is where we give a way for a container to be discovered by alternate names by any other container within the scope of a particular network.

  • Notice: we are using same network on both image configuration. But on mysql part we are giving alias.

  • If you build and run the app, and your app will just work.

mode container

  • Using mode container you can access your network container using localhost.
  • But remember you have one big limitation, you cannot combine this with <ports>.
<images>
    <image>
        <name>docker-kubernetes:${project.version}</name>
        <alias>my-app</alias>
        ...
        <run>
            <network>
                <mode>custom</mode>
                <name>mynet</name>
            </network>
            <ports>
                <port>9090:8080</port>
            </ports>
            <env>
                <DB_URL> <![CDATA[ jdbc:mysql://localhost:3306/docker_kubernetes?useSSL=false ]]> </DB_URL>
                ...
            </env>
        </run>
    </image>
    <image>
        <name>mydb:${project.version}</name>
        <alias>mydb</alias>
        <build>
            <from>mysql:5.7</from>
        </build>
        <run>
            <network>
                <mode>container</mode>
                <name>my-app</name>
            </network>
            ...
        </run>
    </image>
</images>
  • We are still using <network> with mode custom on our web app side.
  • On database side, we are using mode container but instead of network name we are using our web app name.
  • We are using container alias here but you could have used name as well.

dependsOn

  • If you want to order which container start first you can use depends_on.
  • With the plugin we have <dependsOn> tag.
<image>
    <name>docker-kubernetes:${project.version}</name>
    ...
    <run>
        ...
        <dependsOn>
            <container>mydb</container>
        </dependsOn>
    </run>
</image>
<image>
<name>mydb:${project.version}</name>
<alias>mydb</alias>
...
</image> 

Understanding Kubernetes


Why do we need Kubernetes?

  • We learned docker, docker-compose. We know how to containerize our web app. Why do we need to learn yet another tool?
  • Note that docker and docker-compose runs on single host.
  • With help of Kubernetes we can run our app in multi-host environment.
  • With Kubernetes, we can easily deploy and run your software components without having to know about the actual servers underneath.
  • It doesn't matter if it has single host, or it has multiple-host or clusters.
  • When deploying a multi-component application through Kubernetes, it selects a server for each component, deploys it.
  • It also makes it easy to find and communicate with all the other components of your application.
  • Kubernetes enables you to run your applications on thousands of computer nodes as if all those nodes were a single, enormous computer.
  • It abstracts away the underlying infrastructure and, by doing so, simplifies development, deployment.
  • Kubernetes can be thought of as an operating system for the cluster

Kubernetes Architecture As Simple As Possible

  • Kubernetes' architecture in its simplest form has two most important component.
    • Kubernetes Master
    • Kubernetes Worker
                     Cloud
                    +----------------------+
                    |   +----------------+ |
User+-----------------> |   Kubernetes   | |
                    |   |     Master     | |
                    |   +-------+--------+ |
                    |           |          |
                    |           |          |
                    |   +-------v--------+ |
                    |   |   Kubernetes   | |
                    |   |     Worker     | |
                    |   +----------------+ |
                    +----------------------+

Kubernetes Master

  • As a user we always communicate to Kubernetes Master. We will use kubectl tool to interact with Kubernetes Master.
  • Kubernetes Master (Control Plane) has a subcomponents
    • Kubernetes API Server, which you and the other Control Plane components communicate with
    • Scheduler, which schedules your apps (assigns a worker node to each deployable component of your application)
    • Controller Manager, which performs cluster-level functions, such as replicating components, keeping track of worker nodes, handling node failures, and so on
    • etcd, a reliable distributed data store that persistently stores the cluster configuration.
+-------------------------------------+
|                   +--------------+  |
|                   |   Scheduler  |  |
|                   +--------------+  |
|                                     |
|  +-----------+    +--------------+  |
|  |   API     |    |   Controller |  |
|  |   Server  |    |   Manager    |  |
|  +-----------+    +--------------+  |
|                                     |
|                   +--------------+  |
|                   |   etcd       |  |
|                   +--------------+  |
+-------------------------------------+

Kubernetes Worker

  • Kubernetes Worker the run your containerized applications.
  • The task of running, monitoring, and providing services to your applications is done Workers.
  • Kubernetes Worker have different components.
    • Container Runtime: Docker, rkt, or another container runtime, which runs your containers.
    • Kubelet, which talks to the API server and manages containers on its node.
    • Kubernetes Service Proxy (kube-proxy), which load-balances network traffic between application components.
+------------------------------------------------+
|   +------------+          +-----------------+  |
|   |   kublet   |          |    kube-proxy   |  |
|   +------------+          +-----------------+  |
|                                                |
|           +-----------------------+            |
|           |    Container Runtime  |            |
|           +-----------------------+            |
+------------------------------------------------+

Nodes

  • Within a cloud we can have lots of nodes, which may or may not be on same machine.
  • It is a part of Kubernetes to abstract out these details.
  • We always communicate with Master which may be on same node as Worker or may be on different.
Cloud
+------------------------------------+
|                    +----------+    |
|                    |  Worker  |    |
|                    |   Node   |    |
|                    +----------+    |
|                                    |
|  +-----------+     +----------+    |
|  |   Master  |     |  Worker  |    |
|  |    Node   |     |   Node   |    |
|  +-----------+     +----------+    |
|                                    |
|                    +----------+    |
|                    |  Worker  |    |
|                    |   Node   |    |
|                    +----------+    |
+------------------------------------+

Pods


  • Pods are the central, most important, concept in Kubernetes.
  • Pod represents the basic building block in Kubernetes and a place where containers are run.
  • When using Kubernetes you always deploy and operate on a pod of containers instead of deploying containers individually.
  • Pods can run more than one container, but it is good practice running one container per pod.
  • We need to remember that Kubernetes manages Pods not containers.
  • So Kubernetes cannot scale individual containers, instead, it scales whole pods.

Points when running multiple container

  • Pods with multiple containers, are always run on a single worker node — it never spans multiple worker nodes.
  • A pod of containers allows you to run closely related processes together and provide them with (almost) the same environment as if they were all running in a single container, while keeping them somewhat isolated.
  • One thing to stress here is that because containers in a pod run in the same Network namespace, they share the same IP address and port space.
  • This means processes running in containers of the same pod need to take care not to bind to the same port numbers or they’ll run into port conflicts.

Pods Configuration

  • I am using this kub-pod-initial branch for this chapter.
  • We can create a Pods using command line but let's do it using yaml file.
apiVersion: v1                                      # 1
kind: Pod                                           # 2
metadata:                                           # 3
  name: v1-web                                      # 4
spec:                                               # 5
  containers:                                       # 6
    - image: amantuladhar/docker-kubernetes:v1-web  # 7
      name: v1-web                                  # 8
      ports:                                        # 9
        - containerPort: 8080
  • #1 apiVersion conforms which version to Kubernetes API to use.
  • #2 kind describes what kind of Kubernetes Resources we want to define.
  • #3 metadata is a place where you put information about the pods
  • #4 name Setting name for pod.
  • #5 spec contains a pods specification. Specification like container template, volumes etc.
  • #6 containers list of container you want Pods to have
  • #7 image defines which image to use.
  • #8 name name of container.
  • #9 ports List of port container listens to.
  • Specifying ports in the pod definition is purely informational.
  • Omitting them has no effect on whether clients can connect to the pod through the port.
  • But it makes sense to define the ports explicitly so that everyone using your cluster can quickly see what ports each pod exposes.

Creating pods

  • To create a pod we have a simple command.
  • kubectl create -f pods.xml
kubectl create -f pods.yaml
pod/v1-web created

List Pods

  • To list the running pods, we can use
  • kubectl get pods command.
➜ kubectl get pods           
NAME     READY   STATUS    RESTARTS   AGE
v1-web   1/1     Running   0          21m

View logs

  • Viewing logs is simple too kubectl logs @pod_name
  • kubectl logs v1-web
  • If your pod includes multiple containers, you have to explicitly specify the container name by including the -c <container name>.

Access your app

  • We ran our Pod, if you check the status it states it is running as well.
  • But how do we access our app?
  • Note: Pods are not meant to be accessed directly. We create some higher labels resources that abstracts these detail.
  • For debugging purposes, Kubernetes lets us access our Pods directly using port-forwarding technique.
  • kubectl port-forwarding @pod_name @host_port:@container_pod
➜ kubectl port-forward v1-web 9090:8080

Forwarding from 127.0.0.1:9090 -> 8080
Forwarding from [::1]:9090 -> 8080
  • You need to keep this command running if you want to access your pod.
  • If you visit localhost:9090/test, you will be able to access you app.
➜ http localhost:9090/test
HTTP/1.1 200 
Content-Type: application/json;charset=UTF-8
Date: Sun, 17 Feb 2019 20:41:22 GMT
Transfer-Encoding: chunked

{
    "app.version": "v1-web",
    "host.address": "172.17.0.5",
    "message": "Hello Fellas!!!"
}

Deleting the pods

  • Deleting the pods is simple too.
  • kubectl delete pods @pod_name
➜ kubectl delete pods v1-web           
pod "v1-web" deleted

Pod Labels

  • Labels are simply a tag given to pods which may help us identify the specific pods.
  • As our application grows number of pods increases, we need to categorize them so that we can manage them efficiently.
  • Labels are a simple, yet incredibly powerful feature in Kubernetes.
  • Labels can be added to pods, but also for all other resources Kubernetes has.
  • A label is an arbitrary key-value pair you attach to a resource.
  • It can be utilized when selecting resources using label selectors.
  • Resources are filtered based on whether they include the label specified in the selector.
  • A resource can have more than one label, as long as the keys of those labels are unique within that resource.
  • You usually attach labels to resources when you create them, but you can also add additional labels or even modify the values of existing labels later without having to recreate the resource.
  • Let's see how we can add them in yaml
  • Labels are added in metadata section using labels property.
apiVersion: v1
kind: Pod
metadata:
  name: v1-web
  labels:
    version: v1
    env: dev
spec:
  containers:
    - image: amantuladhar/docker-kubernetes:v1-web
      name: v1-web
      ports:
        - containerPort: 8080
  • You can create a resource using kubectl create -f pods.yaml.

See Pods labels

  • You can see pods labels in multiple ways

--show-labels

  • If you want to see all labels use --show-labels option when using kubectl get pods.
  • kubectl get pods --show-labels.
➜ kubectl get pods --show-labels
NAME     READY   STATUS    RESTARTS   AGE     LABELS
v1-web   1/1     Running   0          2m38s   env=dev,version=v1

using -L option

  • While --show-lables options works fine.
  • What if we have lots of labels. Not that I am saying you should add many labels.
  • We can use -L options with the list of labels you want to see.
  • -L label_key1,label_key2
➜ kubectl get pods -L version,env
NAME     READY   STATUS    RESTARTS   AGE    VERSION   ENV
v1-web   1/1     Running   0          6m2s   v1        dev

Adding new label

  • If you want to change/add the labels for pads you can do it by stopping the pod and creating again.
  • But let's see how we can add new labels to running container
  • We use kubectl label @resource @resource_name @label_key=@label_value
  • For us @resource is pods.
➜ kubectl label pods v1-web value=new
pod/v1-web labeled
  • List the pods with labels and see the new value.
➜ kubectl get pods --show-labels     
NAME     READY   STATUS    RESTARTS   AGE   LABELS
v1-web   1/1     Running   0          14m   env=prod,value=new,version=v1

Updating labels

  • If we want to override the existing label, we can use the same command like above but we need to append --overwrite option as well.
  • We use kubectl label @resource @resource_name @label_key=@label_value --overwrite
  • Let's change env to dev again.
➜ kubectl label pods v1-web env=dev --overwrite
pod/v1-web labeled
  • List the labels
➜ kubectl get pods --show-labels               
NAME     READY   STATUS    RESTARTS   AGE   LABELS
v1-web   1/1     Running   0          16m   env=dev,value=new,version=v1

List the pods using labels

  • Listing pods using a label selector.
  • We use -l to add label criteria when listing the pods.

Selecting pods with specific label and specific value

➜ kubectl get pods -l env=dev    
NAME     READY   STATUS    RESTARTS   AGE
v1-web   1/1     Running   0          51m

Selecting pods that have specific label

  • We can also select pods that have specific label, ignoring value it has.
➜ kubectl get pods -l env    
NAME     READY   STATUS    RESTARTS   AGE
v1-web   1/1     Running   0          53m 

Selecting pods that don't have specific label

➜ kubectl get pods -l '!env'
No resources found.

Deleting pods using label selectors

  • We already know how to delete a specific pod with name.
  • If we want to mass delete pods, we can use label selector criteria -l option.
➜ kubectl delete pods -l env
pod "v1-web" deleted

Deleting all pods at once

  • We can delete all pods at once using kubectl delete pods --all

Deleting all resource

  • We haven't learned about other type of resources, but if we want to delete all resources at once we can use.
  • kubectl delete all --all.

Updating Kubernetes Resource


Using kubectl edit

You can also use kubectl edit rs @rs_name and edit the yaml template

kubectl edit rs v1-web

Using kubectl patch

  • We can use kubectl patch to update the template of running resource.
  • You can pass either JSON or YAML when patching the resource.
  • kubectl patch @resource_type @resource_name --patch @template_subset
k patch pods v1-web --patch '{"metadata": {"labels": {"version": "v2"}}}'
  • In above command --patch takes a Kubernetes configuration file.
  • We can pass yaml like structure to --patch but I don't think will look good on a command. Plus the spacing and tabs will be tricky.
  • We are using JSON to send the patch.
  • We define properties we want to override. Notice that it is a subset of a full Kubernetes config.
  • When patching like this, we need to structure our data from root just like we do in config file.

Using kubectl apply

  • To update Resource Template for running resource as well.
  • To update the template for running resource you use kubectl apply.
  • Change you label env to prod at the moment.
➜ kubectl apply -f pods.yaml           
pod/v1-web configured
  • List the labels again and you will see labels are changed.
➜ kubectl get pods -L version,env
NAME     READY   STATUS    RESTARTS   AGE     VERSION   ENV
v1-web   1/1     Running   0          8m51s   v1        prod

Liveness Probe


  • We learned how to add health check and restart policies in docker.
  • I also mentioned that Kubernetes as of now doesn't support these health check.
  • It cannot read the status set by docker and take action upon it.
  • For this reason, Kubernetes has concept of probes. Which is very simple but very powerful concept.
  • We will look into liveness probes for now.
  • Kubernetes can check if a container is still alive through liveness probes.
  • You can specify a liveness probe for each container in the pod’s specification.
  • Kubernetes will periodically execute the probe and restart the container if the probe fails.

Types of liveness probe

  • We can configure liveness probe for a container using one of the three method.
    • HTTP GET probe

      • This performs HTTP GET request on the container’s IP address, a port and path you specify.
      • If the probe receives a response code of 2xx or 3xx, the probe is considered successful.
      • If the server returns an error response code or if it doesn’t respond at all, the probe is considered a failure.
      • Container whose liveness probe failed, will be restarted by Kubernetes.
    • TCP Socket probe

      • This configuration tries to open a TCP connection to the specified port of the container.
      • If the connection is established successfully, the probe is successful.
      • Otherwise, the container is restarted.
    • Exec probe

      • This executes an arbitrary command inside the container and checks the command’s exit status code.
      • If the status code is 0, the probe is successful.
      • All other codes are considered failures.

Adding Liveness Probe

  • Let's see how we can add liveness probe in our app.
  • We will use httpGet probe in our case.
apiVersion: v1
kind: Pod
metadata:
  name: v1-web
  labels:
    version: v1
    env: prod
spec:
  containers:
    - image: amantuladhar/docker-kubernetes:v1-web
      name: v1-web
      livenessProbe:
        httpGet:
          path: /test
          port: 8080
      ports:
        - containerPort: 8080

Liveness Probe in Action

  • Let's create a resource using above configuration.
  • If you list the pods you will see our pod in running status.
NAME     READY   STATUS    RESTARTS   AGE   VERSION   ENV
v1-web   1/1     Running   0          32s   v1        prod
  • If you call localhost:9090/test/exit-1 you will exit our app.
  • Kubernetes when they run liveness probe doesn't respond, will tag pod status to Error.
NAME     READY   STATUS   RESTARTS   AGE   VERSION   ENV
v1-web   0/1     Error    0          21s   v1        prod
  • Then it will restart your pod.
NAME     READY   STATUS    RESTARTS   AGE    VERSION   ENV
v1-web   1/1     Running   1          101s   v1        prod
  • Notice RESTARTS column. The value indicates how many times your app restarted.

Liveness Probe Options

  • initialDelaySeconds: Number of seconds after the container has started before liveness probes are initiated.
  • periodSeconds: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.
  • timeoutSeconds: Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1.
  • failureThreshold: When a Pod starts and the probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the Pod. Defaults to 3. Minimum value is 1.
  • successThreshold: (Mostly used in readiness probe) we will discuss this later.
livenessProbe:
    httpGet:
      path: /test/
      port: 8080
    initialDelaySeconds: 10
    periodSeconds: 60
    timeoutSeconds: 5
    failureThreshold: 3

ReplicaSet


  • We now know how to create pods, i.e. manually.
  • But in real world we never want to create a pod manually.
  • We always use higher level construct to manage pods.
  • One of the higher level construct that we use to manage pods is ReplicaSet. (Although we will use one more higher level construct that manages ReplicaSet later).
  • A ReplicaSet ensures the pods it manages are always kept running.
  • If the pod disappears for any reason, ReplicaSet notices the missing pod and creates a replacement pod.

How does ReplicaSets work?

  • ReplicaSet constantly monitors the list of running pods.
  • Label selector is used to filter the pods that ReplicaSet manages.
  • ReplicaSet makes sure the actual number of pods it manages always matches the desired number.
  • If too few such pods are running, it creates new replicas from a pod template.
  • If too many such pods are running, it removes the excess replicas.

Defining ReplicaSet yaml file

apiVersion: apps/v1             # 1                           
kind: ReplicaSet                # 2                      
metadata: # 3               
  name: v1-web                  # 4                    
  labels: # 5               
    version: v1                 # 5                     
    env: prod                   # 5                   
spec: # 6           
  replicas: 3                   # 6                   
  selector: # 8                 
    matchLabels: # 9                   
      version: v1               # 10                      
      env: prod                 # 10                  
  template:
    # After this it just a Pod template we had before                                                                   
    metadata:
      name: v1-web
      labels: # 11                                                
        version: v1             # 12                                                      
        env: prod               # 12                                                    
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: v1-web
          livenessProbe:
            httpGet:
              path: /test/
              port: 8080
          ports:
            - containerPort: 8080
  • #1: ReplicaSet are defined under apiVersion apps/v1.
  • #2: We are setting our resource type to ReplicaSet.
  • #3: Defines the metadata information for our resource.
  • #4: We are setting the name for our ReplicaSet.
  • #5: labels includes label added for a ReplicaSet.
  • #6: spec includes the specification for our resource.
  • #7: replicas tells ReplicaSet how many copies of pods we want. ReplicaSet make sure that specified number of pods are always running.
  • #8: selector will have list of labels that ReplicaSet will target. These are the labels that determines which pods are managed by ReplicaSet. This selector must somehow match labels defined in #12, #13.
  • #9: matchLabels indicate that we are trying to value label key with label value.
    • We also have matchExpression which is more expressive. (More on this later).
  • template is the template for the pod our ReplicaSet manages. When starting the ReplicaSet or adding new Pods, it will use this template to start the Pod.
    • Notice: All properties below template is same as Pod yaml file we defined before.

Create ReplicaSet

  • To create a ReplicaSet we simply use kubectl create -f replicaset.yaml.
➜ kubectl create -f replicaset.yaml
replicaset.apps/v1-web created 

List ReplicaSet.

  • To list the ReplicaSet we use same command like before but different resource.
  • kubectl get replicaset or kubectl get rs.
  • rs is the short form for replicaset.
➜ kubectl get replicaset           
NAME     DESIRED   CURRENT   READY   AGE
v1-web   3         3         3       42s

➜ kubectl get rs        
NAME     DESIRED   CURRENT   READY   AGE
v1-web   3         3         3       44s

List the pods

  • If you list the running pods, you will see you have three pods running.
➜ kubectl get po
NAME           READY   STATUS    RESTARTS   AGE
v1-web-8s5zw   1/1     Running   0          3m36s
v1-web-r9pkw   1/1     Running   0          3m36s
v1-web-xk9sk   1/1     Running   0          3m36s
  • po is short form for pods.

Scaling our App

Scale Up

  • If we ever want to scale up our application, it will be very easy now.

  • There are many ways you can scale your pods.

    • kubectl patch
    • kubectl apply
    • kubectl edit
    • kubectl scale
  • We already know how to use first three options from previous section.

  • Let's use fourth one

kubectl scale

  • kubectl scale rs --replicas=@number @rs_name
  • Using this command you can quickly change the number of replicas you want to run.
➜ kubectl scale rs --replicas=10 v1-web
  • List the pod again, you will see number of running pods will increase.
➜ kubectl get po
NAME           READY   STATUS    RESTARTS   AGE
v1-web-8s5zw   1/1     Running   0          6m48s
v1-web-fdnwf   1/1     Running   0          8s
v1-web-l6dbh   1/1     Running   0          8s
v1-web-lxkp7   1/1     Running   0          8s
v1-web-n6rfh   1/1     Running   0          8s
v1-web-q9zdq   1/1     Running   0          8s
v1-web-r9pkw   1/1     Running   0          6m48s
v1-web-s455s   1/1     Running   0          8s
v1-web-xctpn   1/1     Running   0          8s
v1-web-xk9sk   1/1     Running   0          6m48s

Scale Down

  • Scaling down is super easy as well.
  • Just update the replicas value. Set it to 3 for now.
➜ kubectl scale rs --replicas=3 v1-web

ReplicaSet matchExpression

  • We saw how matchLabels works in previous example.
  • But ReplicaSet can have more expressive label selector than just matching key and value pairs.
selector:
  matchExpressions:
    - key: @label_key
      operator: @operator_value
      values:
        - @label_value
        - @label_value 
  • When we use matchExpressions we have few option that we can tweak.
  • We can have multiple matchExpression as well.
  • key will take a label key.
  • operator will take one of the pre-defined operators
    • In: Label’s value must match one of the specified values.
    • NotIn: Label’s value must not match any of the specified values.
    • Exists: Pod must include a label with the specified key. When using this operator, you shouldn’t specify the values field.
    • DoesNotExist: Pod must not include a label with the specified key. The values property must not be specified.
  • values: If we use In or NotIn list of values to look for.
  • Let's generate the scenario where we can work on.
  • First run the ReplicaSet yaml file you had before.
  • You will end up with three Pods. Override one of Pod label env=prod to env=dev
  • The Pod whose label was change is now no longer managed by our ReplicaSet.
  • Now ReplicaSet will create a new Pod to match the desired state.
  • If you list the running Pod you will see we have 4 pods. (3 managed by ReplicaSet, 1 not)
➜ kubectl get pods -L version,env                    
NAME           READY   STATUS    RESTARTS   AGE   VERSION   ENV
v1-web-8s5zw   1/1     Running   0          31m   v1        dev
v1-web-njbt9   1/1     Running   0          5s    v1        prod
v1-web-r9pkw   1/1     Running   0          31m   v1        prod
v1-web-xk9sk   1/1     Running   0          31m   v1        prod 
  • How do we tell ReplicaSet to manage Pods with label env=dev and env=prod.
  • How to not create extra Pod?

Using Operator

  • Let's update our ReplicaSet label selector to
selector:
  matchExpressions:
    - key: env
      operator: In
      values:
        - dev
        - prod 
  • Using In we can define that ReplicaSet can look for either value dev or prod for label env.
  • If we update one of the pod label env to dev now, it won't create new Pod. As updated Pod is still managed by ReplicaSet and replicas count match the desired state.
➜ kubectl label po v1-web-6bcgx env=dev --overwrite
pod/v1-web-6bcgx labeled
  • Now list the pods.
NAME           READY   STATUS    RESTARTS   AGE     VERSION   ENV
v1-web-6bcgx   1/1     Running   0          6m22s   v1        dev
v1-web-mkk79   1/1     Running   0          6m22s   v1        prod
v1-web-sxl59   1/1     Running   0          6m22s   v1        prod 

Service


  • Now we know how to manage Pods using ReplicaSet.
  • You know that ReplicaSet make sure that desired number of Pods are always alive.
  • One very important thing we should remember is that Pods are disposable, they should be easily replaceable.
  • Another thing we need to remember is that, there can be multiple Pods that serves the same content.
  • How do we keep tract of Pods IP. Pods can scale up / down, how to know new IP of pods when they are added.
  • If Pods is unhealthy, they are replaced. They may or may not get the same IP.

What is Service

  • Service is a resource that makes a single, constant point of entry to a group of pods providing the same service.
  • Each service is assigned a IP and port that never change while the service exists.
  • Using service IP we can communicate with Pods that service manages.
  • Service also manages Pods using label selector.

Service Definition

  • To create a Service, you create a resource with kind: Service
apiVersion: v1
kind: Service            # 1
metadata:
  name: v1-web-svc       # 2
spec:
  type: NodePort         # 3
  ports:
    - port: 80           # 4
      targetPort: 8080   # 5
      nodePort: 30000    # 6
  selector:              # 7
    env: prod
    version: v1 
  • #1: We are defining the type of resource to Service.
  • #3: Type of Service. Default is ClusterIP but if we use ClusterIP it be visible only inside cluster.
    • NodePort will expose set of Pod it manages to external client.
  • #4: Expose external endpoint to port 80. Type NodePort will ignore this properties.
  • #5: targetPort is the port container serves the app.
  • #6: nodePort will use this IP to expose the service to external client. It must be range from 30000-32767.
  • #7: This is the label selector that is used by Service to select the Pods.
  • minikube doesn't support LoadBalancer as of now.

Create a Service

  • You can create a service by using kubectl create -f svc.yaml.
                 +----------------------------------+
+--------+       |          +-----------+           |
|External+----------------->|  Service  |           |
| Client |       |          +-----+-----+           |
+--------+       |                |                 |
                 |    +------------------------+    |
                 |    |           |            |    |
                 |  +-v-+       +-v-+        +-v-+  |
                 |  |Pod|       |Pod|        |Pod|  |
                 |  +---+       +---+        +---+  |
                 +----------------------------------+

List Service

  • To list the services in cluster you use kubectl get svc, where svc is short form for service.

Access the Pods externally

  • Now that service is running, we can access our app from outside.
  • If you try localhost:30000/test it will not work. Why?
  • Remember Kubernetes is running inside minikube cluster, which is running inside Virtual Machine.
  • We can use minikube service @service_name to easily access your services through your browser.
    • minikube ip echos the minikube IP.
    • minikube service @service_name --url echos the IP with port instead of opening browser.

Readiness Probe


Why do we need readiness probe?

  • We learned how to use ReplicaSet, with that we also learned how to increase number of running Pods.
  • With Service we learned how to expose them, with single IP. Service allows us to access our Pods without knowing it's actual IP.
  • But when does the Service starts to send a traffic to Pod when we scale up your app?

Understanding the problem.

  • Create a ReplicaSet that runs only 1 copy of our app at the moment.
  • Run this command, this will call you app endpoint every second.
while true ; do curl http://192.168.99.106:30000/test ; echo "\\n" ; sleep 1 ; done;
  • Instead of IP above, use your own IP.
  • Now scale up your app. Run 3 replicas.
  • You will start to get some failed request.
{"message":"Hello Fellas!!!","host.address":"172.17.0.5","app.version":"v1-web"}

curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused
curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused
curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused


{"message":"Hello Fellas!!!","host.address":"172.17.0.5","app.version":"v1-web"}
{"message":"Hello Fellas!!!","host.address":"172.17.0.5","app.version":"v1-web"}

curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused
curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused
curl: (7) Failed to connect to 192.168.99.106 port 30000: Connection refused


{"message":"Hello Fellas!!!","host.address":"172.17.0.7","app.version":"v1-web"}
{"message":"Hello Fellas!!!","host.address":"172.17.0.7","app.version":"v1-web"}
{"message":"Hello Fellas!!!","host.address":"172.17.0.6","app.version":"v1-web"}
{"message":"Hello Fellas!!!","host.address":"172.17.0.6","app.version":"v1-web"}
{"message":"Hello Fellas!!!","host.address":"172.17.0.5","app.version":"v1-web"}
  • Why is this happening?
  • What happens right now is that, Service starts to send the traffic to a Pod as soon as Pod is started.
  • Kubernetes at the moment doesn't have any idea if our Pod is ready to accept connection.
  • To help with this problem we use readiness probe.

What is readiness probe?

  • We learned the liveness probe before.
  • With help of liveness probe we were able to restart the Pods that were unhealthy.
  • With the help of readiness probe, Kubernetes will be able to know if Pods are ready to accept connection.
  • Like liveness, readiness probe is also executed periodically.
  • The result determines if Pods is ready or not.

Types of readiness probe

  • Like liveness probe, we have three ways
    • Exec
    • HTTP GET
    • TCP Socket

Add readiness probe

  • To add a readiness probe, we can add it using readinessProbe properties.
# ReplicaSet Definition
apiVersion: apps/v1
kind: ReplicaSet
metadata:
  ...
spec:
  replicas: 3
  ...
  spec:
    containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: v1-web-con
          readinessProbe:            # 1
            httpGet:                 # 2
              path: /test/           # 3
              port: 8080             # 4
            initialDelaySeconds: 10  # 5
          ... 
  • #1 we use readinessProbe property to add probe in our Pod template.
  • #2 we are using httpGet way of defining probe
  • #3 Which path to ping
  • #4 Which port to expose
  • #5 How many seconds do we delay before sending first probe request.

Readiness probe options

  • Like liveness probe we have few options we can configure
    • initialDelaySeconds: Number of seconds after the container has started before liveness probes are initiated.
    • periodSeconds: How often (in seconds) to perform the probe. Default to 10 seconds. Minimum value is 1.
    • timeoutSeconds: Number of seconds after which the probe times out. Defaults to 1 second. Minimum value is 1.
    • failureThreshold: When a Pod starts and the probe fails, Kubernetes will try failureThreshold times before giving up. Giving up in case of liveness probe means restarting the Pod. Defaults to 3. Minimum value is 1.
    • successThreshold: Minimum consecutive successes for the probe to be considered successful after having failed
readinessProbe:
    httpGet:
      path: /test/
      port: 8080
    initialDelaySeconds: 10
    successThreshold: 1
    periodSeconds: 60
    timeoutSeconds: 5
    failureThreshold: 3

Readiness probe in action

  • Create a ReplicaSet that runs only 1 copy of our app at the moment.
  • Run this command, this will call you app endpoint every second.
while true ; do curl http://192.168.99.106:30000/test ; echo "\\n" ; sleep 1 ; done;
  • Instead of IP above, use your own IP.
  • Now scale up your app. Run 3 replicas.
  • You will only see response from other Pod when they are ready.

Creating Deployment


  • We learned few type of Kubernetes Resource
    • Service
    • Pod
    • ReplicaSet
  • While Service is a higher-level concept we be creating in the future, Pods and ReplicaSet are considered lower-level concept.
  • Deployment doesn't manage our app, but rather Deployment creates a ReplicaSet which in turns manages Pods.
  • Deployment is a way to define the states of our application declaratively.
  • With help of Deployment we can easily deploy our application, plus it is super easy to update them.
  • Deployment shines when we are about to upgrade the app to new version.
  • Out of the box it supports rolling updates, but other strategies are also easily achievable. We will discuss deployment strategies later.
+----------+          +----------+
|Deployment+--------->+ReplicaSet|
+----------+          +------+---+
                             |
                             |
  +------------+-------------+
  |            |             |
+-v-+        +-v-+         +-v-+
|Pod|        |Pod|         |Pod|
+---+        +---+         +---+

Creating Deployment

  • Creating Deployment is same as creating ReplicaSet
  • If you have ReplicaSet config file from before, you can easily make it Deployment resource.
  • Actually we can even delete some properties. We will delete it later, and explain why we can delete them.
  • For now let's replace the property kind: Service to kind: Deployment.
  • And there you have it. You just created your Deployment resource.
apiVersion: apps/v1
kind: Deployment       # kind: ReplicaSet
metadata:
  name: web
  labels:
    version: v1
    env: prod
spec:
  replicas: 3
  selector:
    matchLabels:
      env: prod
      version: v1
  template:
    metadata:
      name: web
      labels:
        version: v1
        env: prod
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: web
          # Liveness & readiness probe
          ports:
            - containerPort: 8080
  • If you create a resource using kubectrl create -f deployment.yaml you will create your resources.
  • If you list the resources, you can see Deployment will create ReplicaSet, Pods.
  • Number of Pods also match the replicas: @number.

Deployment doesn't create Service

  • If you listed the resources kubectl get all you may have noticed, we don't have our Service running.
  • To access Pods we need to create a Service.

Rolling out Update

Upgrade the app (V2)

containers:
  - image: amantuladhar/docker-kubernetes:v2-web
    name: web
  • Now let's create update start the update process
  • You can use kubectl apply -f deployment.yaml
  • By default Deployment favors rolling update.

Because Deployment is not tied to specific version of app, we can delete the label version label selector from our yaml file.

Rolling Update Deployment Strategy


  • Rolling Deployment is a process of replacing currently running instances of our application with newer ones.
  • When using this deployment process, there will be a point of time when two version of our app will be running.
                  +-------+      +-------+
                  |Replica|---+->+ Pod_1 |
     +----------->| Set_1 |   |  +-------+
     |            +-------+   |
     |                        |  +-------+
+----+-----+                  +->+ Pod_1 |
|Deployment|                  |  +-------+
+----------+                  |
                              |  +-------+
                              +->+ Pod_1 |
                                 +-------+

  • This is the initial state for our v1 app. Because we said we needed 3 replicas.
  • After the update, Kubernetes will start you update with rolling update strategy.

How Rolling Update is done?

                                 +-------+
                              +->+ Pod_1 |
                              |  +-------+
                              |
                  +-------+   |  +-------+
                  |Replica+----->+ Pod_1 |
     +----------->+ Set_1 |   |  +-------+
     |            +-------+   |
     |                        |  +-------+
+----+-----+                  +->+ Pod_1 |
|Deployment|                     +-------+
+----+-----+
     |            +-------+
     |            |Replica|      +-------+
     +----------->+ Set_2 +----->+ Pod_2 | |Running|!Ready|
                  +-------+      +-------+

  • After we start update process, Kubernetes will create a second ReplicaSet.
  • It will also spin up one new Pod. This can be configured, which we will discuss later.
  • At the moment, there are 4 pods running but only three are in ready state. Pod_2 was just created, and it takes time to be ready.
  • After Pod_2 is ready, it will terminate one of the Pod_1 and create a new Pod_2
                                 +-------+
                              +->+ Pod_1 |
                              |  +-------+
                              |
                  +-------+   |  +-------+
                  |Replica+----->+ Pod_1 |
     +----------->+ Set_1 |   |  +-------+
     |            +-------+   |
     |                        |  +-------+
+----+-----+                  +->+ |---| |  |Terminating|
|Deployment|                     +-------+
+----+-----+
     |            +-------+
     |            |Replica|      +-------+
     +----------->+ Set_2 +---+->+ Pod_2 |  |Running|Ready|
                  +-------+   |  +-------+
                              |
                              |  +-------+
                              +->+ Pod_2 |  |Running|!Ready|
                                 +-------+

  • After new Pod_2 is ready, it will terminate another Pod_1 and then continue like this until new ReplicaSet desired state is not met.

When using Rolling Update strategy, user won't see any downtime. Also two version of the app will run during update process

                  +-------+
                  |Replica|
     +----------->+ Set_1 |
     |            +-------+
     |
+----+-----+                     +-------+
|Deployment|                  +->+ Pod_2 |
+----+-----+                  |  +-------+
     |            +-------+   |
     |            |Replica|   |  +-------+
     +----------->+ Set_2 +----->+ Pod_2 |
                  +-------+   |  +-------+
                              |
                              |  +-------+
                              +->+ Pod_2 |
                                 +-------+
  • This will be the final state of our update process.
  • From the diagram, you can see that Deployment doesn't delete the old ReplicaSet.
  • But having old ReplicaSets may not be ideal.
  • We can configure how many ReplicaSet to save using revisionHistoryLimit property on the Deployment resource.
  • It defaults to two, so normally only the current and the previous revision are shown in the history and only the current and the previous ReplicaSet are preserved.
  • Older ReplicaSets are deleted automatically.

Status of update process

  • We have a kubectl rollout command which has lots of helper function that helps us to interact with Deployment resource.
  • One of them is to see the status of Deployment update process.
  • kubectl rollout status @resource_type @resource_name
kubectl rollout status deployment web
  • This will log the phases Kubernetes is going through when updating our app.

Controlling Rollout Speed

  • You can control the speed at which Pods are replaced using two Kubernetes properties.
  • If you inspect the Deployment property you can see default value Kubernetes sets for these properties
➜ k describe deployments.apps web              
Name:                   web
Namespace:              default
Labels:                 env=prod
                        version=v1
Annotations:            deployment.kubernetes.io/revision: 5
...
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge
... 
  • You can see we have two property

maxUnavailable

  • Specifies the maximum number of Pods that can be unavailable during the update process.
  • The value can be an absolute number (for example, 5) or a percentage of desired Pods (for example, 10%).
  • The absolute number is calculated from percentage by rounding down.
  • The value cannot be 0 if maxSurge is 0.
  • The default value is 25%.

maxSurge

  • Specifies the maximum number of Pods that can be created over the desired number of Pods.
  • The value can be an absolute number (for example, 5) or a percentage of desired Pods (for example, 10%).
  • The value cannot be 0 if maxUnavailable is 0.
  • The absolute number is calculated from the percentage by rounding up.
  • The default value is 25%.

Setting the values

  • All of these properties are defined under spec.strategy.rollingUpdates
apiVersion: apps/v1
kind: Deployment
metadata:
# metadata
spec:
  strategy:
    type: RollingUpdate # Default
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 1
  # other props

Rolling Back Update


  • From the previous section, you saw updating app with the new version was so easy.
  • You just update the image you want to use and Kubernetes does most of the work for us.
  • But what if the new version of app we just updated has some bug.
  • What is bug turned out to be severe. We need to roll back the updates as fast as possible. We can't let user use unstable app.
  • With Kubernetes, rolling back is just as easy.

Using kubectl rollout undo to roll back update

  • We can use kubectl rollout undo @resource_type @resource_name syntax to undo the update process.
  • It is very easy to do so.

History of Deployment rollout

  • You can easily see history revision history
  • kubectl rollout history @resource_type @resource_name
➜ kubectl rollout history deployment web                                    
deployment.extensions/web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none> 
  • If we do one little extra step, when creating / rolling out the update, we can see some values in CHANGE-CAUSE column.
  • If when creating / updating we use --record option. It will record the command we used to make an update.
  • The command is then visible in CHANGE-CAUSE column.
➜ kubectl rollout history deployment web                                             
deployment.extensions/web 
REVISION  CHANGE-CAUSE
2         <none>
3         <none>
4         kubectl set image deployment web web=amantuladhar/docker-kubernetes:v1-web --record=true 

Getting more detail on history

  • You can see more detail of particular revision by using --revision=@revision_number option.
➜ kubectl rollout history deployment web --revision 4
deployment.extensions/web with revision #4
Pod Template:
  Labels:       env=prod
        pod-template-hash=6445f5654d
        version=v1
  Annotations:  kubernetes.io/change-cause: kubectl set image deployment web web=amantuladhar/docker-kubernetes:v1-web --record=true
  Containers:
   web:
    Image:      amantuladhar/docker-kubernetes:v1-web
    Port:       8080/TCP
    Host Port:  0/TCP
    Liveness:   http-get http://:8080/test/ delay=10s timeout=1s period=30s #success=1 #failure=3
    Readiness:  http-get http://:8080/test/ delay=10s timeout=1s period=10s #success=1 #failure=3
    Environment:        <none>
    Mounts:     <none>
  Volumes:      <none> 

Rolling Back to specific revision

  • We can control number of revision history to retain using revisionHistoryLimit property.
  • If there are multiple revision we can jump back to particular revision as well
  • kubectl rollout undo @resource_type @resource_name --to-revision=@revision_number
➜ kubectl rollout undo deployment web --to-revision=2
deployment.extensions/web rolled back

Failed Deployment


  • When upgrading you application to new version, Kubernetes might not be able to deploy the latest version of your application.
  • This can happen due to multiple reasons
    • Insufficient resource
    • Readiness Probe failures
    • Image Pull Errors etc.
  • Kubernetes can automatically detect if upgrade revision is bad

spec.minReadySeconds

  • spec.minReadySeconds is an optional field that specifies the minimum number of seconds for which a newly created Pod should be ready.
  • Ready meaning any of its containers should not crash.
  • This defaults to 0 i.e. the Pod will be considered available as soon as it is ready.
  • If Deployment has only readiness probe, it will mark Pod as available as soon as its readiness probe succeeds.
  • With minReadySeconds we can take our readiness probe one step further.
  • If our readiness probe starts failing before minReadySeconds , the rollout of new version will be blocked.

spec.progressDeadlineSeconds

  • We can configure when Kubernetes marks Deployment as failed by propertyspec.progressDeadlineSeconds
  • spec.progressDeadlineSeconds is an optional field that specifies the number of seconds you want to wait for your Deployment to progress before the system reports back that the Deployment has failed.
  • If specified, this field needs to be greater than spec.minReadySeconds

In the future, once automatic rollback will be implemented, the deployment controller will roll back a Deployment as soon as it observes such a condition.

Rollout v1 app

  • Let's start with this Deployment file
apiVersion: apps/v1
kind: Deployment
metadata:
  #metadata
spec:
  minReadySeconds: 20
  progressDeadlineSeconds: 30
  strategy:
    type: RollingUpdate # Default
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  # replicas, selector
  template:
    #metadata
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: web
          readinessProbe:
            httpGet:
              path: /test/
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 1
          #liveness probe & ports
  • minReadySeconds is set to 20 seconds.
    • That means even if our readiness probe succeeds before 20 seconds deployment is not considered successful.
    • Kubernetes will execute readiness probe multiple times within minReadySecond time frame.
    • If within that time period readiness probe fails deployment is considered unsuccessful.
    • Remember Pod Ready status will be updated by readiness probe but Deployment has its own status to check if deployment process was success or failure.
  • progressDeadlineSeconds is set to 30 seconds.
    • Kubernetes will wait for 30 seconds before it sets the deployment process status.
  • maxSurge is set to 1
    • Relative to replicas number, Kubernetes will create only 1 extra Pod.
  • maxUnavailable is set to 0.
    • Kubernetes will always have desired replicas running.
  • Without setting maxSurge 1 and maxUnavailable 0, we are asking Kubernetes to create a new Pod and only delete existing Pod when new one is ready.
  • readinessProbe to check if our Pod is ready. We are running our probe every second.

Rollout v2

  • To roll out v2, only thing we need to do is update the image the value.
  • But for our test we will update readiness probe as well.
  • We will point readiness probe to /test/status-5xx endpoint.
  • This endpoint will return status 200 for first 5 calls, but after that 500. (Trying to emulate situation where app run for a while and after some time it starts to crash).
# other props
spec:
  # other props
  template:
    #other props
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v2-web
          name: web
          readinessProbe:
            httpGet:
              path: /test/status-5xx
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 1
          #other props
  • Of course in real world we won't change readiness probe path, but for this test we are changing it.

Rollout Status

  • After we apply our update, Kubernetes will start deployment process.
  • It will create a new Pod with new version of our app.
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-5577ff7d67-fp6ht   0/1     Running   0          1m
pod/web-697456ff84-29tm8   1/1     Running   0          17m
pod/web-697456ff84-jhd2c   1/1     Running   0          17m
pod/web-697456ff84-vvtkd   1/1     Running   0          17m
  • Initially it won't be Ready.
  • But after your app starts for a brief period of moment, new Pod will have status Ready. (Remember for first 5 calls our endpoint works.)
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-5577ff7d67-fp6ht   1/1     Running   0          2m
pod/web-697456ff84-29tm8   1/1     Running   0          23m
pod/web-697456ff84-jhd2c   1/1     Running   0          23m
pod/web-697456ff84-vvtkd   1/1     Running   0          23m
  • If we don't define minReadySeconds at this point, Kubernetes will terminate one of the older Pod and create a new one.
  • But because we did and time we defined on minReadySeconds has still not passed, readiness probes continues to check if probe is ready.
  • Because of the way we configured our readiness probe, our probe will fail before the minReadySeconds
  • After that Pod Ready status will be changed.
NAME                       READY   STATUS    RESTARTS   AGE
pod/web-5577ff7d67-fp6ht   0/1     Running   0          5m
pod/web-697456ff84-29tm8   1/1     Running   0          24m
pod/web-697456ff84-jhd2c   1/1     Running   0          24m
pod/web-697456ff84-vvtkd   1/1     Running   0          24m 
  • After progressDeadlineSeconds is passed, Kubernetes will set the status of deployment. For our case it failed as Pod is not Ready.
  • If we were running kubectl rollout status deployment web we will see following log.
➜ k rollout status deployment web
Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...
error: deployment "web" exceeded its progress deadline

Recreate Deployment Strategy


  • Recreate strategy is a simple approach of Deployment.
  • If we use this strategy, Kubernetes will delete all existing version of Pods, only then it will create a new one.
  • For a brief period of time our server will not be able to handle request.

Setting strategy

  • Setting Recreate strategy in Kubernetes is easy.
  • Just set spec.strategy.type to Recreate

How Recreate strategy works

  • When we deploy our first version, Kubernetes will make sure it matches our desired state.
                                 +-------+
                              +->+ Pod_1 |
                              |  +-------+
                              |
                  +-------+   |  +-------+
                  |Replica+----->+ Pod_1 |
     +----------->+ Set_1 |   |  +-------+
     |            +-------+   |
     |                        |  +-------+
+----+-----+                  +->+ Pod_1 |
|Deployment|                     +-------+
+----------+
  • When we start our upgrade process, Kubernetes will stop all our container.
  • It will delete the Pods that are running old version of app.
                                 +-------+
                              +->+ |-----| |Terminating|
                              |  +-------+
                              |
                  +-------+   |  +-------+
                  |Replica+----->+ |---| | |Terminating|
     +----------->+ Set_1 |   |  +-------+
     |            +-------+   |
     |                        |  +-------+
+----+-----+                  +->+ |-----| |Terminating|
|Deployment|                     +-------+
+----------+

  • After all old Pods are deleted, Kubernetes will start a new Pod with out v2 app.
                 +-------+
                  |Replica|
     +----------->+ Set_1 |
     |            +-------+
     |
+----+-----+                     +-------+
|Deployment|                  +->+ Pod_2 |
+----+-----+                  |  +-------+
     |            +-------+   |
     |            |Replica|   |  +-------+
     +----------->+ Set_2 +----->+ Pod_2 |
                  +-------+   |  +-------+
                              |
                              |  +-------+
                              +->+ Pod_2 |
                                 +-------+
  • After this our deployment process is finished.

Note: Kubernetes creates a new ReplicaSet for this deployment strategy as well. This is so that we can easily roll back to previous version.

Blue Green Deployment


Blue Green Deployment

Kubernetes doesn't have (as of now) strategy: BlueGreen like we had RollingUpdate and Recreate.

How blue-green deployment strategy works?

  • If we want to upgrade the instances of V1 (Blue), we create exactly the same number of instances of V2 (Green) alongside V1 (Blue).
  • Initially Service that we are using to expose our app will be pointing to V1 (Blue).
  • After testing that the V2 (Green) meets all the requirements the traffic is switched from V1 (Blue) to V2 (Green).
  • This technique reduces downtime by running two version of app at the same time.
    • But only one version of app is live to user.
    • For our example, Blue is currently live, and Green is idle.
    • But after upgrade Green will be live, and Blue will be idle. (Later on Blue will be deleted)

Advantages

  • With this strategy we can have instant roll back, as we still have old version of app running in background.
  • Unlike Rolling Update we don't run two version of app once.

Disadvantages

  • Blue Green deployment strategy might be expensive. When updating the app we use double resource.

Blue Green Stages

Initial State

                       +-------------+
                       |             |
               +-------+   Service   |
               |       |             |
               |       +-------------+
               |
               |
   |BLUE|      v
+---------------------------+
|   +-------------------+   |
|   |Pod_1||Pod_1||Pod_1|   |
|   --------------------+   |
| -------------+  +-------+ |
| |Deployment_1|  |Replica| |
| -------------+  | Set_1 | |
|                 +-------+ |
+---------------------------+
  • Initially we will have V1 app running, for now let's say Blue.

Rollout Update

                       +--------------+
                       |              |
               +-------+    Service   |
               |       |              |
               |       +--------------+
               |
               |
   |BLUE|      v                    |GREEN|
+---------------------------+     +---------------------------+
|   +-------------------+   |     |   +-------------------+   |
|   |Pod_1||Pod_1||Pod_1|   |     |   |Pod_2||Pod_2||Pod_2|   |
|   --------------------+   |     |   --------------------+   |
|                           |     |                           |
|                           |     |                           |
| -------------+  +-------+ |     | -------------+  +-------+ |
| |Deployment_1|  |Replica| |     | |Deployment_2|  |Replica| |
| -------------+  | Set_1 | |     | -------------+  | Set_2 | |
|                 +-------+ |     |                 +-------+ |
+---------------------------+     +---------------------------+

  • When rollout starts we create our V2 (Green), ideally we will create same number of Pods replicas.
  • Notice, we are creating totally new Deployment here.

Switch to new version

                       +--------------+
                       |              |
                       |    Service   +------+
                       |              |      |
                       +--------------+      |
                                             |
                                             |
   |BLUE|                            |GREEN| v
+---------------------------+     +---------------------------+
|   +-------------------+   |     |   +-------------------+   |
|   |Pod_1||Pod_1||Pod_1|   |     |   |Pod_2||Pod_2||Pod_2|   |
|   --------------------+   |     |   --------------------+   |
|                           |     |                           |
|                           |     |                           |
| -------------+  +-------+ |     | -------------+  +-------+ |
| |Deployment_1|  |Replica| |     | |Deployment_2|  |Replica| |
| -------------+  | Set_1 | |     | -------------+  | Set_2 | |
|                 +-------+ |     |                 +-------+ |
+---------------------------+     +---------------------------+
  • To make our new version of app V2 Green live, we will have to point Service to newly created Pod.
  • For Kubernetes Service we just change the label selector.

Clean up or roll back

  • At this stage, we can either cleanup resources or we can roll back to our previous version.
  • If we think new version of app is stable, we can delete the old Pods / Deployments
  • But if we need to do a roll back, it is very easy. Just change the Service label selector again.

Blue Green Deployment With Kubernetes

Deploy the V1 App

  • Create a Deployment with following configuration
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-v1
  labels:
    version: v1
    env: prod
spec:
  replicas: 3
  selector:
    matchLabels:
      env: prod
      version: v1
  template:
    metadata:
      name: web-v1
      labels:
        version: v1
        env: prod
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: web-v1
          #probes

  • Notice how I am appending -v1 to Deployment, ReplicaSet and Pods name. This is important because we don't want to replace the existing Deployment resource.

Create a Service

apiVersion: v1
kind: Service
metadata:
  name: web
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30000
  selector:
    env: prod
    version: v1
  • Service at the moment is selecting Pods with version: v1 label.

Update to V2 App

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-v2
  labels:
    version: v2
    env: prod
spec:
  replicas: 3
  selector:
    matchLabels:
      env: prod
      version: v2
  template:
    metadata:
      name: web-v2
      labels:
        version: v2
        env: prod
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:v2-web
          name: web-v2
          #probes
  • We create a new Deployment *-v2.
  • ReplicaSet / Pods labels are also suffixed with -v2
  • At the moment the Pods created by our new Deployment is not live. Service is still pointing to old version of the app.
  • After all the new version Pods are ready, we can switch the Service selector.

Change the Service selector

  • You can change the Service selector using multiple technique.
  • I will use kubectl patch
kubectl patch service web --patch '{"spec": {"selector": {"version": "v2"}}}'
  • After this you will see all the traffic are now redirected to new version.

Cleanup / Rollback Update

  • If you want to cleanup you can delete the old Deployment
  • If you want to Rollback the update, you can use above command and change selector to v1
kubectl patch service web --patch '{"spec": {"selector": {"version": "v1"}}}'

Environment Variable


  • Most of the apps that we develop nowadays depends on some kind of environment variables / configuration files.
  • Using configuration files on containers are a bit tricky.
  • If we push the configuration with container itself, we need to update / build the image every time we change the container.
  • Often times some configuration properties are secret. We don't want some properties to be passed on carelessly.
  • Containers normally use environment variables to get configuration.
  • Kubernetes also supports passing environment variables to containers.

Kubernetes doesn't allow Pod level environment. Environment variables are set on container level.

  • We cannot modify the environment variable for a container once it is set.
  • We can dispose the current container and re-create a new one.
  • If you think about the solution above, it make sense right!
  • Containers are supposed to be immutable, if you change configuration of one container other replicas may not have same configuration.
  • At least until you make changes to all of them.

Example

  • Let's create a Deployment that sets environment variable to container.
apiVersion: apps/v1
kind: Deployment
metadata:
  # metadata
spec:
  # replica & matchLabel selector
  template:
    metadata:
      # metadata
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:environment   #1
          name: web
          env:                     #2
            - name: GREETING       #3
              value: Namaste       #4
            - name: NAME           #3
              value: Folks!!       #4
          # probe properties
  • #1 - We are using new image i.e docker-kubernetes:environment. I have modified the endpoint so that it changes the message based on environment variables.
    • GREETING - Default is Hello
    • NAME - Default is Fellas!!
  • #2 - We use env properties to set the environment variable to container.
  • #3 - Name of environment variable you want to set
  • #4 - Value of environment variable you want to set

Calling Endpoint

➜ http $(minikube service web --url)/test
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Tue, 26 Feb 2019 05:47:37 GMT
Transfer-Encoding: chunked

{
    "app.version": "v1-web",
    "host.address": "172.17.0.7",
    "message": "Namaste Folks!!"
}

Updating environment variable

  • Note: To change the environment variable of container, either you need to restart the container
  • You can update the environment variable easily with command below, but it will restart the container.
kubectl set env deployment web GREETING="Adios"
  • kubectl set env @resource @name @env_var=@env_value
  • If you want to unset environment variable
kubectl set env deployment web GREETING-
  • If you use environment variable and use dash - it will unset the environment variable.

Config Maps


  • Setting environment variables to container template is one option to send configuration to container.
  • But if configuration differ from environment to environment like DEV, QA, PROD then setting configuration value on env may not be a good idea.
  • Kubernetes has a resource called ConfigMaps which treats configuration as a separate object.
  • So each environment can have same objects, but different values.

ConfigMaps YAML

  • Let's create simple ConfigMaps that stores our GREETING and NAME environment variable value.
apiVersion: v1
kind: ConfigMap
metadata:
  name: web
data:
  GREETING: Namaste
  NAME: Friends!!! 
  • As you can see creating ConfigMap is simple.
  • You set kind as ConfigMap.
  • You set name of ConfigMap on metadata section.
  • On data root node, you add your properties values.
  • It is just a key value pair.
  • Here we are setting GREETING as Namaste and NAME as Friends!!!

Creating ConfigMap

  • Creating a ConfigMap is simple too, you just use your handy kubectl create command.
kubectl create -f configmap.yaml

Using ConfigMap with env property

  • If you want to get specific environment variable value using ConfigMap you can use valueFrom property instead of value property.
apiVersion: apps/v1
kind: Deployment
# metadata
spec:
  # replicas & selector
  template:
    # metadata
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:environment
          name: web
          env:
            - name: GREETING
              valueFrom:
                configMapKeyRef:
                  name: web # Name of ConfigMaps
                  key: GREETING
            - name: NAME
              valueFrom:
                configMapKeyRef:
                  name: web # Name of ConfigMaps
                  key: NAME
          # probes & ports
  • In above file, you can see we are using valueFrom property instead of value.
  • Using configMapKeyRef tells Kubernetes to search in ConfigMap.
  • configMapKeyRef.name states the name of the ConfigMap resource.
  • configMapKeyRef.key states which key to look for in defined resource.
  • Run the app using above configuration and call your endpoint
➜  http $(minikube service web --url)/test
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Date: Wed, 27 Feb 2019 02:00:50 GMT
Transfer-Encoding: chunked

{
    "app.version": "v1-web",
    "host.address": "172.17.0.6",
    "message": "Namaste Friends!!!"
}
  • Now our message has Namaste Friends!!!.
  • That's the value we have in our ConfigMaps

Updating ConfigMap

Note: Updating ConfigMap doesn't update the value a running container has.

If you change value of ConfigMap, two similar container might be in inconsistent state.

  • You can update the ConfigMap values using kubectl patch command.
kubectl patch configmaps web --patch '{"data": {"GREETING": "Yo"}}'

Import All values from ConfigMap

  • While previous method gave us power to pull in value from ConfigMap.
  • It was troublesome, if we have let's say 20 properties, we have to use valueFrom 20 times.
  • Kubernetes has another property envFrom which allows you to import all values from ConfigMaps
apiVersion: apps/v1
kind: Deployment
# metadata
spec:
  # replicas & selector
  template:
    # metadata:
    spec:
      containers:
        - image: amantuladhar/docker-kubernetes:environment
          name: web
          envFrom:
            - configMapRef:
                name: web
          # probe and port
  • In above example, we are using envFrom instead of env.
  • When we use envFrom property, we import all properties from that resource. (But there is limitation)
  • We can also use prefix keyword if you want to prefix your configuration properties with something. env.prefix
  • If any of your ConfigMap property has dash -. That won't be imported.
  • Because environment variable with dash - are invalid.
  • Kubernetes doesn't do any post-processing for you in this case.

There one more way to load the ConfigMaps i.e. using volumes

We don't know how to mount the volumes yet, so this will be skipped for now.

Secrets


  • We learned how ConfigMap helps us separate our app template and properties.
  • While that combination works for almost all scenarios, Kubernetes has one more resources to save the properties i.e Secrets
  • Secrets as name suggest should be used to store properties that should be kept secret.
  • In other words, we should keep the properties in Secret Kubernetes resource.
  • Secrets are more secure and they reduces the risk of accidental exposure.

Secret YAML

  • Secret is not different than ConfigMaps.
  • Changing the kind from ConfigMap to Secret should do the work. (But there's a catch)
  • Let's update our previous ConfigMap resource file.
apiVersion: v1
kind: Secret
metadata:
  name: web
data:
  GREETING: Namaste
  NAME: Friends!!!
  • Let's create this secret and see what happens.
kubectl create -f secrets.yaml
  • You will get an error message that says something like this.
Error from server (BadRequest): error when creating "secret-base64.yaml": 
Secret in version "v1" cannot be handled as a Secret: v1.Secret: 
Data: decode base64: illegal base64 data at input byte 4, error found in #10 byte of ...|:"Namaste","NAME":"F|..., bigger context ...|{"apiVersion":"v1","data":{"GREETING":"Namaste","NAME":"Friends!!!"},"kind":"Secret","metadata":{|...
  • If you read the error message, you can see Kubernetes is expecting base64 value for a configuration data properties. But it is a plain string.

Secrets data are base64

  • Let's convert our data to base64 and add it.
➜ echo "Namaste" | base64
TmFtYXN0ZQo=
➜ echo "Friends" | base64
RnJpZW5kcwo=
  • Our new Secrets definition
apiVersion: v1
kind: Secret
metadata:
  name: web
data:
  GREETING: TmFtYXN0ZQo=
  NAME: RnJpZW5kcwo=
  • Now let's create a Secret again.
kubectl create -f secrets.yaml
➜  kubectl get secrets web -o yaml
apiVersion: v1
data:
  GREETING: TmFtYXN0ZQo=
  NAME: RnJpZW5kcwo=
kind: Secret
metadata:
  creationTimestamp: "2019-03-02T22:53:07Z"
  name: web
  namespace: default
  resourceVersion: "102112"
  selfLink: /api/v1/namespaces/default/secrets/web
  uid: f1cf2235-3d3d-11e9-aea6-025000000001
type: Opaque

Reading as env var one at a time

  • Using Secret is easy as using ConfigMaps
  • Kubernetes when loads the Secrets values to Pods, it doesn't load encrypted value but actual value.
  • So not conversion is needed.
  • We use secretKeyRef instead of configMapKeyRef.
env:
  - name: GREETING
    valueFrom:
      secretKeyRef:
        name: web # Name of Secret
        key: GREETING
  - name: NAME
    valueFrom:
      secretKeyRef:
        name: web # Name of Secret
        key: NAME

Reading all at once

  • Like ConfigMap, instead of configRef instead of secretRef.
envFrom:
  - secretRef:
      name: web 
  • And that's it, we can access the secrets as environment variable.

Secret string value

  • As you just saw, even if you want to add a string value to a Secret it's bit hard.
  • You need to convert your value to base64.
  • This useful when you load a file on a Secret. (We will get to this when we get to know volume)
  • But Kubernetes one more property stringData, which let's you add your property value as plain text.
  • Only thing you need to remember is that stringData property is write only. You cannot read from it. You will know what I mean in a bit.
apiVersion: v1
kind: Secret
metadata:
  name: web
stringData:
  GREETING: Namaste
  NAME: Friends!!!
  • You can see using stringData we can add secret values as plain text. We can mix and match with data if you want to.
  • Create a Secrets and see YAML generated by Kubernetes
➜  kubectl get secrets web -o yaml
apiVersion: v1
data:
  GREETING: TmFtYXN0ZQ==
  NAME: RnJpZW5kcyEhIQ==
kind: Secret
metadata:
  creationTimestamp: "2019-03-02T23:48:35Z"
  name: web
  namespace: default
  resourceVersion: "105712"
  selfLink: /api/v1/namespaces/default/secrets/web
  uid: b18f158f-3d45-11e9-aea6-025000000001
type: Opaque
  • Generated YAML doesn't have stringData.
  • That's why stringData is write only property.
  • We can write it as plain text in YAML file, but you cannot read it.
  • When you load a file and create a Secret and Kubernetes will convert the value for you.

Secrets can be mounted as volume. Mostly useful when you load a file on Secrets.

Environment Variables may not be a safest way to expose the Secret values.

Host Path


  • We learned how to deploy our Stateless app using Kubernetes.
  • But we may need to deploy the app that has state, that accesses the database to save the records.
  • Making a Stateful app run using Kubernetes is a bit tricky, solely because our app can run in different nodes.
  • Kubernetes out of the box has different types of volumes, but based on cloud provider you use you will have way to save your data.

Kubernetes Host Path Volume

  • Kubernetes has a simple volume type called hostPath which gives you persistence storage.
  • But hostPath has one big limitation i.e. hostPath volume mounts a file or directory from the host node’s filesystem into your Pod.
  • The main point here is that, hostPath volume type mounts the volumes to node.
  • Our cluster can have multiple nodes, and two nodes doesn't share a filesystem.
  • Pods with identical configuration may behave differently on different nodes due to different files on the nodes
  • Because we are developing our app locally we have to use hostPath, and it doesn't affect us. We are running only one node.

Using Host Path

apiVersion: apps/v1
kind: Deployment
# metadata
spec:
  #replicas & metadata
  template:
    # metadata
    spec:
      volumes:                #1
        - name: volume-name   #2
          hostPath:           #3
            path: /absolute/host/path   #4
      containers:
        - image: amantuladhar/docker-kubernetes:v1-web
          name: web-co
          volumeMounts:                 #5
              - name: volume-name       #6
                mountPath: /containerPath/    #7
  • #1In above Deployment definition, we are using volumes properties to define the volumes we want to use. (This is not a proper way to define volume, but let's go step by step)
  • #2 We are giving name to our volume so that we can use it later.
  • #3 hostPath property. This is the type of volume we are using. For local development we are using hostPath but this will be provider specific volume type.
  • #4 Child property of hostPath. This is an absolute path which points which folder from host to mount.
    • Remember for minikube host path is inside VM.
    • Here's a minikube documentation to mount your actual host path to minikube VM, then to mount that volume to Kubernetes.
    • TLDR; use minikube mount actual/host/path:/minikube/vm/path
  • #5 Using volumeMounts property we can specify which volumes our container needs to mount.
  • #6 Using name property we specify the name of the volume we want to mount.
    • For your purposes it needs to match #2
  • #7 mountPath take the path inside the container. This is the place where container will access external data.

We are using Deployment here. If you just want to create a Pod you remember, everything below from template property is basically Pod definition.

  • After you create a resource, we can go inside the Pod and play around with it.
  • Let's create a file inside the container and see if that file is available on our host.
  • And let's delete the Pod and see if new Pod can see that file.
kubectl create -f hostPath.yaml
  • Creating file inside Pod
# Get inside Pod
➜ kubectl exec -it web-dep-89fd5db9-897dr sh

# Change directory to path where we mounted the volume
/myApp # cd /containerPath/

# Create a file with some text
/containerPath # echo "Very important data" >> file.txt

# Check if file exist (inside container)
/containerPath # ls
file.txt
  • Check if file is visible in your host path. You will see the file if everything went well.
  • Now, restart the container and see if file exist.
kubectl delete pod --all 
  • This will delete all the Pods.
  • But Deployment will spawn new Pod immediately.
  • Go inside new Pod and see if file exist.
# Getting inside new Pod
➜ kubectl exec -it web-dep-89fd5db9-689sw sh

# Checking if file exist
/myApp # ls /containerPath/
file.txt

# Checking the content of file
/myApp # cat /containerPath/file.txt
Very important data
  • We learned how we can easily mount volume to Kubernetes by using hostPath.
  • Make sure if you use hostPath on production you know what you are doing.
  • There may be a scenario where we need this, but for general purposes like database this is not a solution.
  • Next we will learn how we can improve this example and what problems this solution has.