Spring Boot
Kubernetes
Billy Korando
Developer Advocate
IBM
Kubernetes Popularity and Growth
✅ Flexible
✅ Extendible
❌ Steep Learning Curve
@BillyKorando
Perspective on Learning Kubernetes
👩💻
🧑💻 🔎
👨💻
@BillyKorando
Learning Objectives
@BillyKorando
Kubernetes Architecture
@BillyKorando
Kubernetes Architecture
@BillyKorando
@BillyKorando
Containers Advantages
@BillyKorando
Containers - Docker
@BillyKorando
Containers - Dockerfile
FROM adoptopenjdk:11-jre-hotspot
COPY HelloWorld.class /
CMD ["java", "HelloWorld"]
Base image, image built from
@BillyKorando
Containers - Dockerfile
FROM adoptopenjdk:11-jre-hotspot
COPY HelloWorld.class /
CMD ["java", "HelloWorld"]
Resources to copy into Docker image
@BillyKorando
Containers - Dockerfile
FROM adoptopenjdk:11-jre-hotspot
COPY HelloWorld.class /
CMD ["java", "HelloWorld"]
Command to execute when Docker container starts
@BillyKorando
Containers - Build Image
$ docker build . -t wkorando/hello-java-on-kube-101:1
Execute docker daemon build
@BillyKorando
Containers - Build Image
$ docker build . -t wkorando/hello-java-on-kube-101:1
Set context for docker build, “.” current folder
@BillyKorando
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
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
Containers - Run Container
$ docker run hello-java-on-kube-101:1
Hello World!
@BillyKorando
Containers - Common Terms
@BillyKorando
Containers - Layers
REPOSITORY TAG IMAGE ID CREATED SIZE
hello-java-on-kube-101 1 3850eb09129b 17 minutes ago 239MB
@BillyKorando
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
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
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
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
Container Registry - Flow
🖥
👩💻🧑💻
👨💻
Push
Pull
☁️
@BillyKorando
Container Registry - Flow
👩💻🧑💻
👨💻
☁️
Push
Pull
@BillyKorando
Container Registry - Services
☁️
@BillyKorando
Container Registry
Docker Hub
Cloud Platforms
@BillyKorando
Container Orchestration
Handles container lifecycle, scaling, load balancing
@BillyKorando
Kubernetes
Just a Container Orchestrator
Extendible API allows other needs to be filled
@BillyKorando
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.”
Software Engineering Daily link
@BillyKorando
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
Kubernetes API
Version of api used
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
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
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
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
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
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
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
Control Loop
$ kubectl apply -f deployment.yaml
@BillyKorando
Control Loop
@BillyKorando
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
Kubernetes API
...
spec:
replicas: 2
selector:
matchLabels:
app: hello-service
...
Kubernetes works works to maintain desired state
@BillyKorando
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
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
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
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
Common Kubernetes Resources
$ kubectl get services
NAME TYPE ... PORT(S) ...
hello-service-port NodePort ... 8080:30112/TCP ...
@BillyKorando
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
Common Kubernetes Resources
$ curl 169.51.203.32:30112
Hello World!
@BillyKorando
Common Kubernetes Resources
@BillyKorando
Kubectl
@BillyKorando
Kubectl
@BillyKorando
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
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
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
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
Find Resources
Deployment
Pods
ReplicaSet
@BillyKorando
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
Find Resources
Namespaces:
Virtual clusters that provide isolation within same Kubernetes cluster
@BillyKorando
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
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
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
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
View Resource Details
$ kubectl get service hello-service-port -o yaml
apiVersion: v1
kind: Service
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
...
@BillyKorando
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
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
Inspect Pods
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-service-deployment
labels:
app: hello-service
Look up logs that match metadata
@BillyKorando
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
Inspect Pods
$ kubectl exec hello-service-deployment-55cb9b6745-572m7 -- ls
bin
boot
dev
etc
hello-service-java-on-kube-101.jar
@BillyKorando
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
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
Inspect Kubernetes Cluster
$ kubectl cluster-info dump
/*
Enormous amount of diagnostic and debugging information
*/
@BillyKorando
Inspect Kubernetes Cluster
$ kubectl cluster-info dump --output-directory=/path/to/directory
$ ls /path/to/directory
default
kube-system
nodes.json
@BillyKorando
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
Update Resources
$ kubectl edit deployment hello-service-deployment
@BillyKorando
Update Resources
@BillyKorando
Design Applications for Kubernetes
@BillyKorando
Design Applications for Kubernetes
@BillyKorando
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
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
Containerizing Applications
@BillyKorando
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
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
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
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
Containerizing Applications
@BillyKorando
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
Containerizing Applications
@BillyKorando
Containerizing Applications
@BillyKorando
Liveness and Readiness Probes
Liveness != Readiness
Liveness && Readiness != health
@BillyKorando
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
Liveness and Readiness Probes
Liveness = Terminal��Readiness = Temporal
@BillyKorando
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
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
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
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
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
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
Liveness and Readiness Probes
Cluster
Status
User
Requests
Changing Cluster State
@BillyKorando
Liveness and Readiness Probes
@BillyKorando
Liveness and Readiness Probes
Tying liveness to downstream service
Cascading Failure
DB
Hello-service-1
Hello-service-2
Hello-service-n
🔥
🔥
🔥
🔥
↻
↻
↻
@BillyKorando
Liveness and Readiness Probes
Tying readiness to downstream service
Bulkhead
DB
Hello-service-1
Hello-service-2
Hello-service-n
🔥
🤚
🤚
🤚
@BillyKorando
Liveness and Readiness Probes
Not configuring liveness could lead to zombie cluster
Blocked Threads
Hello-service-1
Hello-service-2
Hello-service-n
🧟♂️
🧟♂️
🧟♂️
@BillyKorando
Graceful Shutdown
Graceful Shutdown:�Block traffic and allow transactions time to complete before terminating container.
@BillyKorando
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
Liveness and Readiness Probes
Shutting Down
Pods
Pod Logging
Output
User
Requests
@BillyKorando
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
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
Graceful Shutdown
server.shutdown=graceful
spring.lifecycle.timeout-per-shutdown-phase=5s
@BillyKorando
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
Liveness and Readiness Probes
Shutting Down
Pods
Pod Logging
Output
User
Requests
@BillyKorando
Persistence and Configuring Applications in Kubernetes
@BillyKorando
Persistence and Configuring Applications in Kubernetes
@BillyKorando
Working with Kubernetes Volumes
Volume:�Provides persistent storage outside of a pod or container
@BillyKorando
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
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
Working with Kubernetes Volumes
Controlling Pod Status
Pod Status
User
Requests
@BillyKorando
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
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
Working with Kubernetes Volumes
Controlling Pod
Status
Pod Status
User
Requests
@BillyKorando
Configure Applications in Kubernetes
Volumes:
@BillyKorando
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
Working with Kubernetes Volumes
Environment Variables:�Pass configuration info to container at startup.
@BillyKorando
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
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
Working with Kubernetes Volumes
$ curl http://169.51.203.32:30112/config/environment
Hello from ENVIRONMENT_MESSAGE!
@BillyKorando
Working with Kubernetes ConfigMaps
ConfigMap:�Externalize configuration information, and store it in Kubernetes
@BillyKorando
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
Working with Kubernetes Volumes
$ kubectl apply -f configMap.yaml
@BillyKorando
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
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
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
Working with Kubernetes ConfigMaps
$ curl http://169.51.203.32:30112/config/configMap
Reading properties from config map!
@BillyKorando
Working with Kubernetes Secrets
Secret:�Store and provide sensitive information; keys, passwords, etc., in an encrypted format on Kubernetes
@BillyKorando
Working with Kubernetes Secrets
$ echo -n "password" | base64
cGFzc3dvcmQ=
$ echo -n "Super Secret Message" | base64
U3VwZXIgU2VjcmV0IE1lc3NhZ2U=
@BillyKorando
Working with Kubernetes Secrets
apiVersion: v1
kind: Secret
metadata:
name: datasource-connection-info
type: Opaque
data:
password: cGFzc3dvcmQ=
my-secret: U3VwZXIgU2VjcmV0IE1lc3NhZ2U=
@BillyKorando
Working with Kubernetes Volumes
$ kubectl apply -f secret.yaml
@BillyKorando
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
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
Working with Kubernetes Secrets
$ curl http://169.51.203.32:30112/config/mySecretMessage
Super Secret Message
@BillyKorando
Q&A
@BillyKorando
william.korando@ibm.com
Code & Slides: https://github.com/wkorando/java-on-kube-101