1 of 141

Spring Boot

Kubernetes

Billy Korando

Developer Advocate

IBM

2 of 141

Kubernetes Popularity and Growth

✅ Flexible

✅ Extendible

❌ Steep Learning Curve

@BillyKorando

3 of 141

Perspective on Learning Kubernetes

👩‍💻

🧑‍💻 🔎

👨‍💻

@BillyKorando

4 of 141

Learning Objectives

  • Kubernetes Architecture
  • Kubectl
  • Design Applications for Kubernetes
  • Configure Applications in Kubernetes

@BillyKorando

5 of 141

Kubernetes Architecture

@BillyKorando

6 of 141

Kubernetes Architecture

  • Containers
  • Container Registry
  • Container Orchestration
  • Kubernetes Architecture
    • API
    • Control Loop
    • Network
    • Common Kubernetes Resources

@BillyKorando

7 of 141

@BillyKorando

8 of 141

Containers Advantages

  • Encapsulates all application run dependencies
  • Portable
  • Shareable
  • Lightweight
    • Make use of native OS functions

@BillyKorando

9 of 141

Containers - Docker

  • GA in 2013
  • Defacto choice for containerization

@BillyKorando

10 of 141

Containers - Dockerfile

FROM adoptopenjdk:11-jre-hotspot

COPY HelloWorld.class /

CMD ["java", "HelloWorld"]

Base image, image built from

@BillyKorando

11 of 141

Containers - Dockerfile

FROM adoptopenjdk:11-jre-hotspot

COPY HelloWorld.class /

CMD ["java", "HelloWorld"]

Resources to copy into Docker image

@BillyKorando

12 of 141

Containers - Dockerfile

FROM adoptopenjdk:11-jre-hotspot

COPY HelloWorld.class /

CMD ["java", "HelloWorld"]

Command to execute when Docker container starts

@BillyKorando

13 of 141

Containers - Build Image

$ docker build . -t wkorando/hello-java-on-kube-101:1

Execute docker daemon build

@BillyKorando

14 of 141

Containers - Build Image

$ docker build . -t wkorando/hello-java-on-kube-101:1

Set context for docker build, “.” current folder

@BillyKorando

15 of 141

Containers - Build Image

$ docker build . -t wkorando/hello-java-on-kube-101:1

-t provide a tag for the image being built

Name of the repository where image is stored

Image tag

Namespace

@BillyKorando

16 of 141

Containers - Build Image

Sending build context to Docker daemon 4.096kB

Step 1/3 : FROM adoptopenjdk:11-jre-hotspot

---> 0a5a185d02f7

Step 2/3 : COPY HelloWorld.class /

---> 2b341354b80f

Step 3/3 : CMD ["java", "HelloWorld"]

---> Running in ad32c6179809

Removing intermediate container ad32c6179809

---> 3850eb09129b

Successfully built 3850eb09129b

Successfully tagged hello-java-on-kube-101:1

@BillyKorando

17 of 141

Containers - Run Container

$ docker run hello-java-on-kube-101:1

Hello World!

@BillyKorando

18 of 141

Containers - Common Terms

  • Image: like a Java class
  • Container: like an instance of a class
  • Layer: Save time and space

@BillyKorando

19 of 141

Containers - Layers

REPOSITORY TAG IMAGE ID CREATED SIZE

hello-java-on-kube-101 1 3850eb09129b 17 minutes ago 239MB

@BillyKorando

20 of 141

Containers - Layers

3850eb09129b: 0B - 16 minutes ago

2b341354b80f: 426B - 16 minutes ago

0a5a185d02f7: 0B - 2 weeks ago

<missing>: 141MB - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 33.5MB - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 7B - 2 weeks ago

<missing>: 745B - 2 weeks ago

<missing>: 987kB - 2 weeks ago

<missing>: 63.2MB - 2 weeks ago

Base Image Layers

Layers I added

@BillyKorando

21 of 141

Containers - Layers

3850eb09129b: 0B - 16 minutes ago

2b341354b80f: 426B - 16 minutes ago

0a5a185d02f7: 0B - 2 weeks ago

<missing>: 141MB - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 33.5MB - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 0B - 2 weeks ago

<missing>: 7B - 2 weeks ago

<missing>: 745B - 2 weeks ago

<missing>: 987kB - 2 weeks ago

<missing>: 63.2MB - 2 weeks ago

Only part that will need to be downloaded

If image already cached

@BillyKorando

22 of 141

Container Registry - Push

code-examples:$ docker image push wkorando/hello-java-on-kube-101:1

The push refers to repository [docker.io/wkorando/hello-java-on-kube-101]

c4d391574a0a: Pushed

b4430120f68c: Pushed

bc20fb4767f7: Pushed

001e4a80973b: Pushed

2ba5b91ca2b0: Pushed

2f37d1102187: Pushed

79bde4d54386: Pushed

1: digest: sha256:43fbd2a1... size: 1783

@BillyKorando

23 of 141

Container Registry - Push

simple-docker:$ docker image push wkorando/hello-java-on-kube-101:cache-test

The push refers to repository [docker.io/wkorando/hello-java-on-kube-101]

83d9ee22e748: Pushed

b4430120f68c: Layer already exists

bc20fb4767f7: Layer already exists

001e4a80973b: Layer already exists

2ba5b91ca2b0: Layer already exists

2f37d1102187: Layer already exists

79bde4d54386: Layer already exists

cache-test: digest: sha256:516cb531… size: 1783

@BillyKorando

24 of 141

Container Registry - Flow

🖥

👩‍💻🧑‍💻

👨‍💻

Push

Pull

☁️

@BillyKorando

25 of 141

Container Registry - Flow

👩‍💻🧑‍💻

👨‍💻

☁️

Push

Pull

@BillyKorando

26 of 141

Container Registry - Services

☁️

  • Vulnerability Scanner
  • Backups
  • Access Controls

@BillyKorando

27 of 141

Container Registry

Docker Hub

Cloud Platforms

@BillyKorando

28 of 141

Container Orchestration

Handles container lifecycle, scaling, load balancing

@BillyKorando

29 of 141

Kubernetes

Just a Container Orchestrator

Extendible API allows other needs to be filled

@BillyKorando

30 of 141

Kubernetes API

“The Kubernetes API is the most important piece of the Kubernetes ecosystem. That container run time, that container orchestration platform is the first system that was built using the Kubernetes API. If you understand that, you can understand that Kubernetes is moving to a direction.”

  • Kelsey Hightower

Software Engineering Daily link

@BillyKorando

31 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

@BillyKorando

32 of 141

Kubernetes API

Version of api used

  • alphav1
  • betav1

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

@BillyKorando

33 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Type of Kubernetes object

@BillyKorando

34 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Metadata for identifying and looking up object in cluster

@BillyKorando

35 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Specification for Kubernetes object

@BillyKorando

36 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Number of instances on cluster

@BillyKorando

37 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Image Kubernetes will pull and to run

@BillyKorando

38 of 141

Kubernetes API

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

template:

metadata:

labels:

app: hello-service

spec:

containers:

- name: hello-service

image: wkorando/hello-service-java-on-kube-101:1

ports:

- containerPort: 8080

Common to all Kubernetes files

@BillyKorando

39 of 141

Control Loop

$ kubectl apply -f deployment.yaml

@BillyKorando

40 of 141

Control Loop

@BillyKorando

41 of 141

Control Loop

hello-java-on-kube-101-service:$ kubectl get pods

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-2qsst 1/1 Running 0 15h

hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 15h

hello-java-on-kube-101-service:$ kubectl delete pod hello-service-deployment-55cb9b6745-2qsst

pod "hello-service-deployment-55cb9b6745-2qsst" deleted

hello-java-on-kube-101-service:$ kubectl get pod

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 15h

hello-service-deployment-55cb9b6745-qtc5k 1/1 Running 0 6s

hello-java-on-kube-101-service:$

New pod automatically spun up

@BillyKorando

42 of 141

Kubernetes API

...

spec:

replicas: 2

selector:

matchLabels:

app: hello-service

...

Kubernetes works works to maintain desired state

@BillyKorando

43 of 141

Networking

$ curl 10.144.216.134:8080

curl: (7) Failed to connect to 10.144.216.134 port 8080: Operation timed out

$ kubectl get pods -o wide

NAME IP NODE

hello-service-deployment-... 172.30.179.85 10.144.216.134

hello-service-deployment-... 172.30.179.86 10.144.216.134

$ curl 172.30.179.85:8080

curl: (7) Failed to connect to 172.30.179.85 port 8080: Operation timed out

@BillyKorando

44 of 141

Networking

$ kubectl get nodes -o wide

NAME EXTERNAL-IP

10.144.216.134 ... 169.51.203.32 …

$ curl 169.51.203.32:8080

curl: (7) Failed to connect to 169.51.203.32 port 8080: Operation timed out

@BillyKorando

45 of 141

Networking

Requests

Hello-service-

...qtc5k

Hello-service-

...572m7

172.30.179.87

172.30.179.89

Node 10.144.216.134

169.51.203.32

Internal Network

External Network

@BillyKorando

46 of 141

Common Kubernetes Resources

apiVersion: v1

kind: Service

metadata:

name: hello-service-port

spec:

type: NodePort

selector:

app: hello-service

ports:

- port: 8080

name: http

deployment.yaml

...

metadata:

name: hello-service-deployment

labels:

app: hello-service

...

@BillyKorando

47 of 141

Common Kubernetes Resources

$ kubectl get services

NAME TYPE ... PORT(S) ...

hello-service-port NodePort ... 8080:30112/TCP ...

@BillyKorando

48 of 141

Common Kubernetes Resources

Hello-service-

...qtc5k

Hello-service-

...572m7

172.30.179.87

172.30.179.89

10.144.216.134

169.51.203.32

NodePort

:30112

Requests

Internal Network

External Network

@BillyKorando

49 of 141

Common Kubernetes Resources

$ curl 169.51.203.32:30112

Hello World!

@BillyKorando

50 of 141

Common Kubernetes Resources

  • Deployment
  • Pod
  • Service
  • ReplicaSet
  • Volume
  • ConfigMap
  • Secret

@BillyKorando

51 of 141

Kubectl

@BillyKorando

52 of 141

Kubectl

  • Find Resources
  • View Resource Details
  • Inspect Pods
  • Update Resources

@BillyKorando

53 of 141

Find Resources

apiVersion: v1

kind: Service

metadata:

name: hello-service-port

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

Find resources by type (kind)

@BillyKorando

54 of 141

Find Resources

$ kubectl get deployment

NAME READY UP-TO-DATE AVAILABLE AGE

hello-service-deployment 2/2 2 2 46h

$ kubectl get services

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

hello-service-port NodePort 172.21.41.103 <none> 8080:30112/TCP 43h

kubernetes ClusterIP 172.21.0.1 <none> 443/TCP 2d4h

@BillyKorando

55 of 141

Find Resources

apiVersion: v1

kind: Service

metadata:

name: hello-service-port

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

Narrowing resource lookup by adding name

@BillyKorando

56 of 141

Find Resources

$ kubectl get service hello-service-port

NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE

hello-service-port NodePort 172.21.41.103 <none> 8080:30112/TCP 2d1h

@BillyKorando

57 of 141

Find Resources

Deployment

Pods

ReplicaSet

@BillyKorando

58 of 141

Find Resources

$ kubectl get deployment

NAME READY UP-TO-DATE AVAILABLE AGE

hello-service-deployment 2/2 2 2 46h

$ kubectl get replicaset

NAME DESIRED CURRENT READY AGE

hello-service-deployment-55cb9b6745 2 2 2 2d1h

$ kubectl get pods

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 2d1h

hello-service-deployment-55cb9b6745-qtc5k 1/1 Running 0 34h

@BillyKorando

59 of 141

Find Resources

Namespaces:

Virtual clusters that provide isolation within same Kubernetes cluster

@BillyKorando

60 of 141

Find Resources

$ kubectl get pods -A

NAMESPACE NAME READY STATUS RESTARTS AGE

default hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 2d1h

default hello-service-deployment-55cb9b6745-qtc5k 1/1 Running 0 34h

ibm-system addon-catalog-source-jjgx2 1/1 Running 0 2d10h

ibm-system catalog-operator-67646bfcdb-d6kqf 1/1 Running 0 2d10h

...

kube-system vpn-f66c45467-t5gd6 1/1 Running 0 2d10h

test hello-service-deployment-55cb9b6745-2qdjc 1/1 Running 0 12h

test hello-service-deployment-55cb9b6745-hm2bx 1/1 Running 0 12h

@BillyKorando

61 of 141

Find Resources

$ kubectl get pods -n test

NAMESPACE NAME READY STATUS RESTARTS AGE

test hello-service-deployment-55cb9b6745-2qdjc 1/1 Running 0 12h

test hello-service-deployment-55cb9b6745-hm2bx 1/1 Running 0 12h

@BillyKorando

62 of 141

Find Resources

$ kubectl config set-context --current --namespace=test

Context "java-on-kube-101/btch36vf0vuk0pcpu2c0" modified.

$ kubectl get pods

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-2qdjc 1/1 Running 0 13h

hello-service-deployment-55cb9b6745-hm2bx 1/1 Running 0 13h

@BillyKorando

63 of 141

View Resource Details

$ kubectl get nodes

NAME STATUS ROLES AGE VERSION

10.144.216.134 Ready <none> 2d10h v1.17.11+IKS

$ kubectl get nodes -o wide

NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP ...

10.144.216.134 Ready <none> 2d10h v1.17.11+IKS 10.144.216.134 169.51.203.32 ...

@BillyKorando

64 of 141

View Resource Details

$ kubectl get service hello-service-port -o yaml

apiVersion: v1

kind: Service

metadata:

annotations:

kubectl.kubernetes.io/last-applied-configuration: |

...

@BillyKorando

65 of 141

View Resource Details

$ kubectl describe service hello-service-port

Name: hello-service-port

Namespace: default

Labels: <none>

Annotations: kubectl.kubernetes.io/last-applied-configuration:

...

@BillyKorando

66 of 141

Inspect Pods

$ kubectl get pods

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 2d12h

hello-service-deployment-55cb9b6745-qtc5k 1/1 Running 0 45h

$ kubectl logs hello-service-deployment-55cb9b6745-572m7

2020-09-10 02:47:17.097 INFO 1 --- [ main] d.h.HelloJavaOnKube101ServiceApplication : Starting HelloJavaOnKube101ServiceApplication v0.0.1-SNAPSHOT on hello-service-deployment-55cb9b6745-572m7 with PID 1 (/hello-service-java-on-kube-101.jar started by root in /)

2020-09-10 02:47:17.104 INFO 1 --- [ main] d.h.HelloJavaOnKube101ServiceApplication : No active profile set, falling back to default profiles: default

...

@BillyKorando

67 of 141

Inspect Pods

apiVersion: apps/v1

kind: Deployment

metadata:

name: hello-service-deployment

labels:

app: hello-service

Look up logs that match metadata

@BillyKorando

68 of 141

Inspect Pods

$ kubectl logs -l app=hello-service

2020-09-10 02:47:17.097 INFO 1 --- [ main] d.h.HelloJavaOnKube101ServiceApplication : Starting HelloJavaOnKube101ServiceApplication v0.0.1-SNAPSHOT on hello-service-deployment-55cb9b6745-572m7 with PID 1 (/hello-service-java-on-kube-101.jar started by root in /)

...

2020-09-10 18:06:01.000 INFO 1 --- [ main] d.h.HelloJavaOnKube101ServiceApplication : Starting HelloJavaOnKube101ServiceApplication v0.0.1-SNAPSHOT on hello-service-deployment-55cb9b6745-qtc5k with PID 1 (/hello-service-java-on-kube-101.jar started by root in /)

...

@BillyKorando

69 of 141

Inspect Pods

$ kubectl exec hello-service-deployment-55cb9b6745-572m7 -- ls

bin

boot

dev

etc

hello-service-java-on-kube-101.jar

@BillyKorando

70 of 141

Inspect Pods

$ kubectl top pod hello-service-deployment-55cb9b6745-572m7

NAME CPU(cores) MEMORY(bytes)

hello-service-deployment-55cb9b6745-572m7 2m 113Mi

$ kubectl top pod -l app=hello-service

NAME CPU(cores) MEMORY(bytes)

hello-service-deployment-55cb9b6745-572m7 1m 113Mi

hello-service-deployment-55cb9b6745-qtc5k 1m 97M

@BillyKorando

71 of 141

Inspect Kubernetes Cluster

$ kubectl cluster-info

Kubernetes master is running at https://***

CoreDNS is running at https://***/api/v1/namespaces/kube-system/services/kube-dns:dns/proxy

kubernetes-dashboard is running at https://***/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy

Metrics-server is running at https://***/api/v1/namespaces/kube-system/services/https:metrics-server:/proxy

NodeLocalDNS is running at https://***/api/v1/namespaces/kube-system/services/node-local-dns:dns/proxy

@BillyKorando

72 of 141

Inspect Kubernetes Cluster

$ kubectl cluster-info dump

/*

Enormous amount of diagnostic and debugging information

*/

@BillyKorando

73 of 141

Inspect Kubernetes Cluster

$ kubectl cluster-info dump --output-directory=/path/to/directory

$ ls /path/to/directory

default

kube-system

nodes.json

@BillyKorando

74 of 141

Update Resources

$ kubectl scale --replicas=3 deployment hello-service-deployment

deployment.apps/hello-service-deployment scaled

hello-java-on-kube-101-service:$ kubectl get pods

NAME READY STATUS RESTARTS AGE

hello-service-deployment-55cb9b6745-572m7 1/1 Running 0 2d13h

hello-service-deployment-55cb9b6745-ps4g8 1/1 Running 0 7s

hello-service-deployment-55cb9b6745-qtc5k 1/1 Running 0 46h

@BillyKorando

75 of 141

Update Resources

$ kubectl edit deployment hello-service-deployment

@BillyKorando

76 of 141

Update Resources

@BillyKorando

77 of 141

Design Applications for Kubernetes

@BillyKorando

78 of 141

Design Applications for Kubernetes

  • Containerizing Applications
  • Liveness and Readiness Probes
  • Graceful Shutdown

@BillyKorando

79 of 141

Containerizing Applications

FROM adoptopenjdk:11-jre-hotspot

COPY target/hello-service-java-on-kube-101.jar /

CMD ["java", "-jar", "hello-service-java-on-kube-101.jar"]

@BillyKorando

80 of 141

Containerizing Applications

@RestController�public class Controller {�private static final Logger LOGGER = LoggerFactory.getLogger(Controller.class);

@GetMapping

public String helloWorld() {

LOGGER.info("In helloWorld");

return "Hello World!";

}

@GetMapping("/{name}")

public String helloName(@PathVariable String name) {

LOGGER.info("In helloName: " + name);

return String.format("Hello, %s!", name);

}

}

@BillyKorando

81 of 141

Containerizing Applications

@BillyKorando

82 of 141

Containerizing Applications

hello-java-on-kube-101-service:$ docker build . -t wkorando/hello-java-on-kube-101:2

Sending build context to Docker daemon 28.18MB

Step 1/3 : FROM adoptopenjdk:11-jre-hotspot

---> 2eb37d403188

Step 2/3 : COPY target/hello-service-java-on-kube-101.jar /

---> ba9594a41892

Step 3/3 : CMD ["java", "-jar", "hello-service-java-on-kube-101.jar"]

---> Running in 22d793486954

Removing intermediate container 22d793486954

---> b628ebd835ff

Successfully built b628ebd835ff

Successfully tagged wkorando/hello-java-on-kube-101:2

Application code and dependencies located in single layer

@BillyKorando

83 of 141

Containerizing Applications

<build>

<finalName>hello-service-java-on-kube-101</finalName>

<plugins>

<plugin>

<groupId>org.springframework.boot</groupId>

<artifactId>spring-boot-maven-plugin</artifactId>

<configuration>

<layers>

<enabled>true</enabled>

</layers>

</configuration>

</plugin>

</plugins>

</build>

@BillyKorando

84 of 141

Containerizing Applications

FROM adoptopenjdk:11-jre-hotspot AS builder

WORKDIR application

ARG JAR_FILE=target/*.jar

COPY ${JAR_FILE} application.jar

RUN java -Djarmode=layertools -jar application.jar extract

FROM adoptopenjdk:11-jre-hotspot

WORKDIR application

COPY --from=builder application/dependencies/ ./

COPY --from=builder application/snapshot-dependencies/ ./

COPY --from=builder application/spring-boot-loader/ ./

COPY --from=builder application/application/ ./

ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]

Name stage to reference in later stages

@BillyKorando

85 of 141

Containerizing Applications

$ docker build . -t wkorando/hello-java-on-kube-101:3

Sending build context to Docker daemon 28.22MB

Step 1/12 : FROM adoptopenjdk:11-jre-hotspot AS builder

---> 2eb37d403188

Step 2/12 : WORKDIR application

---> Using cache

---> 0ea71d734970

Step 3/12 : ARG JAR_FILE=target/*.jar

---> Using cache

---> e64d65d5c888

Step 4/12 : COPY ${JAR_FILE} application.jar

---> 2fb8321f1215

Step 5/12 : RUN java -Djarmode=layertools -jar application.jar extract

---> Running in 1cb7c665eb96

Removing intermediate container 1cb7c665eb96

---> ea204c85981b

Step 6/12 : FROM adoptopenjdk:11-jre-hotspot

---> 2eb37d403188

Step 7/12 : WORKDIR application

---> Using cache

---> 0ea71d734970

Step 8/12 : COPY --from=builder application/dependencies/ ./

---> Using cache

---> a724b739593c

Step 9/12 : COPY --from=builder application/snapshot-dependencies/ ./

---> Using cache

---> 7dee3c0b99c4

Step 10/12 : COPY --from=builder application/spring-boot-loader/ ./

---> Using cache

---> c3b4cd8a76d6

Step 11/12 : COPY --from=builder application/application/ ./

---> 4844c09ed214

Step 12/12 : CMD ["java", "org.springframework.boot.loader.JarLauncher"]

---> Running in 2e5666ded142

Removing intermediate container 2e5666ded142

---> 0f79c44be830

Successfully built 0f79c44be830

Application code and dependencies located across multiple layers

@BillyKorando

86 of 141

Containerizing Applications

@BillyKorando

87 of 141

Containerizing Applications

$ docker build . -t wkorando/hello-java-on-kube-101:4

Sending build context to Docker daemon 28.22MB

Step 1/12 : FROM adoptopenjdk:11-jre-hotspot AS builder

---> 2eb37d403188

Step 2/12 : WORKDIR application

---> Using cache

---> 0ea71d734970

Step 3/12 : ARG JAR_FILE=target/*.jar

---> Using cache

---> e64d65d5c888

Step 4/12 : COPY ${JAR_FILE} application.jar

---> 2fb8321f1215

Step 5/12 : RUN java -Djarmode=layertools -jar application.jar extract

---> Running in 1cb7c665eb96

Removing intermediate container 1cb7c665eb96

---> ea204c85981b

Step 6/12 : FROM adoptopenjdk:11-jre-hotspot

---> 2eb37d403188

Step 7/12 : WORKDIR application

---> Using cache

---> 0ea71d734970

Step 8/12 : COPY --from=builder application/dependencies/ ./

---> Using cache

---> a724b739593c

Step 9/12 : COPY --from=builder application/snapshot-dependencies/ ./

---> Using cache

---> 7dee3c0b99c4

Step 10/12 : COPY --from=builder application/spring-boot-loader/ ./

---> Using cache

---> c3b4cd8a76d6

Step 11/12 : COPY --from=builder application/application/ ./

---> 4844c09ed214

Step 12/12 : CMD ["java", "org.springframework.boot.loader.JarLauncher"]

---> Running in 2e5666ded142

Removing intermediate container 2e5666ded142

---> 0f79c44be830

Successfully built 0f79c44be830

Only part of docker image that changed

@BillyKorando

88 of 141

Containerizing Applications

@BillyKorando

89 of 141

Containerizing Applications

  • Increase Layers
    • Individual layers are smaller
    • Place layers that change more often later in build
  • Multi-Stage Docker Builds
    • Smaller final image by placing temporal tools in separate state
    • Name stages to reference in later stages

@BillyKorando

90 of 141

Liveness and Readiness Probes

Liveness != Readiness

Liveness && Readiness != health

@BillyKorando

91 of 141

Liveness and Readiness Probes

Liveness Probe:�Is this application in a normal state

Readiness Probe:�Is the application in state that it can service requests

@BillyKorando

92 of 141

Liveness and Readiness Probes

Liveness = Terminal��Readiness = Temporal

@BillyKorando

93 of 141

Liveness and Readiness Probes

livenessProbe:

httpGet:

path: /actuator/health/liveness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

readinessProbe:

httpGet:

path: /actuator/health/readiness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

@BillyKorando

94 of 141

Liveness and Readiness Probes

livenessProbe:

httpGet:

path: /actuator/health/liveness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

readinessProbe:

httpGet:

path: /actuator/health/readiness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

Endpoint and port kubernetes will call

@BillyKorando

95 of 141

Liveness and Readiness Probes

livenessProbe:

httpGet:

path: /actuator/health/liveness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

readinessProbe:

httpGet:

path: /actuator/health/readiness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

How long kubernetes waits to check endpoint after startup

@BillyKorando

96 of 141

Liveness and Readiness Probes

livenessProbe:

httpGet:

path: /actuator/health/liveness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

readinessProbe:

httpGet:

path: /actuator/health/readiness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

How long kubernetes waits to check endpoint again

@BillyKorando

97 of 141

Liveness and Readiness Probes

livenessProbe:

httpGet:

path: /actuator/health/liveness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

readinessProbe:

httpGet:

path: /actuator/health/readiness

port: 8080

initialDelaySeconds: 5

periodSeconds: 3

failureThreshold: 2

How many non-200 returns Kubernetes accepts before restarting pod

How many non-200 returns Kubernetes accepts before blocking traffic

@BillyKorando

98 of 141

Liveness and Readiness Probes

@GetMapping("/setLivenessToFalse")

public String setLivenessToFalse() {

AvailabilityChangeEvent.publish(context, LivenessState.BROKEN);

return "Application is now broken";

}

@GetMapping("/setReadinessToFalse")

public String setReadinessToFalse() {

LOGGER.info("In helloWorld");

AvailabilityChangeEvent.publish(context, ReadinessState.REFUSING_TRAFFIC);

new Thread(new Runnable() {

@Override

public void run() {

try {

Thread.currentThread().sleep(5000);

AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

return "Application is now refusing traffic";

}

@BillyKorando

99 of 141

Liveness and Readiness Probes

Cluster

Status

User

Requests

Changing Cluster State

@BillyKorando

100 of 141

Liveness and Readiness Probes

  • Cascading Failures
  • Bulkheads
  • Block Threads

@BillyKorando

101 of 141

Liveness and Readiness Probes

Tying liveness to downstream service

Cascading Failure

DB

Hello-service-1

Hello-service-2

Hello-service-n

🔥

🔥

🔥

🔥

@BillyKorando

102 of 141

Liveness and Readiness Probes

Tying readiness to downstream service

Bulkhead

DB

Hello-service-1

Hello-service-2

Hello-service-n

🔥

🤚

🤚

🤚

@BillyKorando

103 of 141

Liveness and Readiness Probes

Not configuring liveness could lead to zombie cluster

Blocked Threads

Hello-service-1

Hello-service-2

Hello-service-n

🧟‍♂️

🧟‍♂️

🧟‍♂️

@BillyKorando

104 of 141

Graceful Shutdown

Graceful Shutdown:�Block traffic and allow transactions time to complete before terminating container.

@BillyKorando

105 of 141

Graceful Shutdown

@GetMapping("/longRunningProcess")

public String longRunningProcess() throws InterruptedException {

LOGGER.info("Process Started!");

taskExecutor.execute(new Runnable() {

@Override

public void run() {

try {

Thread.currentThread().sleep(5000);

LOGGER.info("Process Completed!");

} catch (InterruptedException e) {

LOGGER.info("Process Didn't Complete!");

}

}

});

return "Process Initiated";

}

@BillyKorando

106 of 141

Liveness and Readiness Probes

Shutting Down

Pods

Pod Logging

Output

User

Requests

@BillyKorando

107 of 141

Graceful Shutdown

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:7

ports:

- containerPort: 8080

...

terminationGracePeriodSeconds: 15

How long Kubernetes will wait before killing pod

@BillyKorando

108 of 141

Graceful Shutdown

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:7

ports:

- containerPort: 8080

lifecycle:

preStop:

exec:

command:

- /bin/sh

- -c

- sleep 5

...

terminationGracePeriodSeconds: 15

Kubernetes executes command, before sending TERM

@BillyKorando

109 of 141

Graceful Shutdown

server.shutdown=graceful

spring.lifecycle.timeout-per-shutdown-phase=5s

@BillyKorando

110 of 141

Graceful Shutdown

@Bean

public TaskExecutor taskExecutor() {

ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();

taskExecutor.setCorePoolSize(2);

taskExecutor.setMaxPoolSize(2);

taskExecutor.setWaitForTasksToCompleteOnShutdown(true);

taskExecutor.setAwaitTerminationSeconds(10);

taskExecutor.initialize();

return taskExecutor;

}

@BillyKorando

111 of 141

Liveness and Readiness Probes

Shutting Down

Pods

Pod Logging

Output

User

Requests

@BillyKorando

112 of 141

Persistence and Configuring Applications in Kubernetes

@BillyKorando

113 of 141

Persistence and Configuring Applications in Kubernetes

  • Volumes
  • Environment Variables
  • ConfigMaps
  • Secrets

@BillyKorando

114 of 141

Working with Kubernetes Volumes

Volume:�Provides persistent storage outside of a pod or container

@BillyKorando

115 of 141

Working with Kubernetes Volumes

@GetMapping("/init")

public String init() {

if (repo.count() > 0) {

return "Database already initialized";

} else {

repo.save(new Person(1, "Billy", "Korando"));

repo.save(new Person(2, "Patrick", "Mahomes"));

repo.save(new Person(3, "Travis", "Kelce"));

return "Database initialized";

}

}

@GetMapping

public Iterable<Person> findAll() {

return repo.findAll();

}

@BillyKorando

116 of 141

Working with Kubernetes Volumes

spring.datasource.url=jdbc:h2:file:/volume-mount/h2-data;DB_CLOSE_ON_EXIT=FALSE

spring.datasource.driverClassName=org.h2.Driver

spring.datasource.username=sa

spring.datasource.password=password

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.hibernate.ddl-auto=update

Writing data to file located here

Only update DDL, don’t delete existing data/tables

@BillyKorando

117 of 141

Working with Kubernetes Volumes

Controlling Pod Status

Pod Status

User

Requests

@BillyKorando

118 of 141

Working with Kubernetes Volumes

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:8

ports:

- containerPort: 8080

volumeMounts:

- name: h2-data

mountPath: /volume-mount

...

volumes:

- name: h2-data

hostPath:

type: DirectoryOrCreate

path: /tmp

Mounts volume “h2-data” in container, at directory “/volume-mount”

Creates volume “h2-data” in the pod on startup

@BillyKorando

119 of 141

Working with Kubernetes Volumes

spring.datasource.url=jdbc:h2:file:/volume-mount/h2-data;DB_CLOSE_ON_EXIT=FALSE

Same directory as mount point

@BillyKorando

120 of 141

Working with Kubernetes Volumes

Controlling Pod

Status

Pod Status

User

Requests

@BillyKorando

121 of 141

Configure Applications in Kubernetes

Volumes:

  • Many options, will vary depending on cloud provider
  • Storage choice will depend upon need

@BillyKorando

122 of 141

Working with Kubernetes Volumes

@RestController

@RequestMapping("/config")

public class ConfigController {

@Value("${environment.message}")

private String environmentMessage;

@Value("${config-map.message}")

private String configMapMessage;

@Value("${my.secret.message}")

private String mySecretMessage;

@GetMapping("/environment")

public String environment() {

return environmentMessage;

}

@GetMapping("/configMap")

public String configMap() {

return configMapMessage;

}

@GetMapping("/mySecretMessage")

public String mySecretMessage() {

return mySecretMessage;

}

}

@BillyKorando

123 of 141

Working with Kubernetes Volumes

Environment Variables:�Pass configuration info to container at startup.

@BillyKorando

124 of 141

Working with Kubernetes Volumes

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:9

ports:

- containerPort: 8080

env:

- name: ENVIRONMENT_MESSAGE

value: "Hello from ENVIRONMENT_MESSAGE!"

args: ["--environment.message=$(ENVIRONMENT_MESSAGE)"]

Define environment variable and provide it a value

@BillyKorando

125 of 141

Working with Kubernetes Volumes

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:9

ports:

- containerPort: 8080

env:

- name: ENVIRONMENT_MESSAGE

value: "Hello from ENVIRONMENT_MESSAGE!"

args: ["--environment.message=$(ENVIRONMENT_MESSAGE)"]

Reference environment variable and pass in as arg

@BillyKorando

126 of 141

Working with Kubernetes Volumes

$ curl http://169.51.203.32:30112/config/environment

Hello from ENVIRONMENT_MESSAGE!

@BillyKorando

127 of 141

Working with Kubernetes ConfigMaps

ConfigMap:�Externalize configuration information, and store it in Kubernetes

@BillyKorando

128 of 141

Working with Kubernetes ConfigMaps

apiVersion: v1

kind: ConfigMap

metadata:

name: spring-config

namespace: default

data:

application.properties: |-

spring.main.banner-mode=OFF

spring.datasource.url=jdbc:h2:file:/volume-mount/h2-data;DB_CLOSE_ON_EXIT=FALSE

spring.datasource.driverClassName=org.h2.Driver

spring.datasource.username=sa

spring.datasource.password=password

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

spring.jpa.hibernate.ddl-auto=update

config-map.message=Reading properties from config map!

@BillyKorando

129 of 141

Working with Kubernetes Volumes

$ kubectl apply -f configMap.yaml

@BillyKorando

130 of 141

Working with Kubernetes ConfigMaps

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:10

ports:

- containerPort: 8080

volumeMounts:

...

- name: application-properties

mountPath: /application/config

...

volumes:

...

- name: application-properties

configMap:

name: spring-config

items:

- key: application.properties

path: application.properties

Reference name of ConfigMap created

@BillyKorando

131 of 141

Working with Kubernetes ConfigMaps

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:10

ports:

- containerPort: 8080

volumeMounts:

...

- name: application-properties

mountPath: /application/config

...

volumes:

...

- name: application-properties

configMap:

name: spring-config

items:

- key: application.properties

path: application.properties

Reference field in ConfigMap and name it for reference

@BillyKorando

132 of 141

Working with Kubernetes ConfigMaps

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:10

ports:

- containerPort: 8080

volumeMounts:

...

- name: application-properties

mountPath: /application/config

...

volumes:

...

- name: application-properties

configMap:

name: spring-config

items:

- key: application.properties

path: application.properties

Mount ConfigMap as you would a volume

@BillyKorando

133 of 141

Working with Kubernetes ConfigMaps

$ curl http://169.51.203.32:30112/config/configMap

Reading properties from config map!

@BillyKorando

134 of 141

Working with Kubernetes Secrets

Secret:�Store and provide sensitive information; keys, passwords, etc., in an encrypted format on Kubernetes

@BillyKorando

135 of 141

Working with Kubernetes Secrets

$ echo -n "password" | base64

cGFzc3dvcmQ=

$ echo -n "Super Secret Message" | base64

U3VwZXIgU2VjcmV0IE1lc3NhZ2U=

@BillyKorando

136 of 141

Working with Kubernetes Secrets

apiVersion: v1

kind: Secret

metadata:

name: datasource-connection-info

type: Opaque

data:

password: cGFzc3dvcmQ=

my-secret: U3VwZXIgU2VjcmV0IE1lc3NhZ2U=

@BillyKorando

137 of 141

Working with Kubernetes Volumes

$ kubectl apply -f secret.yaml

@BillyKorando

138 of 141

Working with Kubernetes Secrets

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:11

imagePullPolicy: Always

ports:

- containerPort: 8080

env:

- name: db-password

valueFrom:

secretKeyRef:

name: datasource-connection-info

key: password

- name: my-secret-message

valueFrom:

secretKeyRef:

name: datasource-connection-info

key: my-secret

args: ["--spring.datasource.password=$(db-password)", "--my.secret.message=$(my-secret-message)"]

Reference name of Secret created

@BillyKorando

139 of 141

Working with Kubernetes Secrets

spec:

containers:

- name: hello-service

image: wkorando/hello-java-on-kube-101:11

imagePullPolicy: Always

ports:

- containerPort: 8080

env:

- name: db-password

valueFrom:

secretKeyRef:

name: datasource-connection-info

key: password

- name: my-secret-message

valueFrom:

secretKeyRef:

name: datasource-connection-info

key: my-secret

args: ["--spring.datasource.password=$(db-password)", "--my.secret.message=$(my-secret-message)"]

Reference fields with in Secret

@BillyKorando

140 of 141

Working with Kubernetes Secrets

$ curl http://169.51.203.32:30112/config/mySecretMessage

Super Secret Message

@BillyKorando

141 of 141

Q&A

@BillyKorando

william.korando@ibm.com

Code & Slides: https://github.com/wkorando/java-on-kube-101