Deploy Kotlin Spring Boot App with MySQL on Kubernetes

1. Introduction

2. Prerequisites

3. Create Spring Boot Application

3.1. Imports

implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
runtimeOnly("mysql:mysql-connector-java")

3.2. Configure Application Properties

spring:
datasource:
url: jdbc:mysql://mysql-svc:3306/${DB_NAME}?useSSL=false
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
jpa:
hibernate:
ddl-auto: update
databasePlatform: "org.hibernate.dialect.MySQL5InnoDBDialect"

3.3. Create Model

@Entity
class User(
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long = 0,

@Column
val name: String
)
class UserRequest(
val name: String
)

3.4. Create Repository

interface UserRepository : CrudRepository<User, Long>

3.5. Create Service

@Service
class UserService(
private val userRepository: UserRepository
) {

fun saveUser(request: UserRequest): User =
userRepository.save(
User(
name = request.name
)
)

fun findAllUsers(): MutableIterable<User> =
userRepository.findAll()
}

3.6. Implement Controllers

@RestController
@RequestMapping("/api/status")
class ApiStatusController {
@GetMapping
fun getStatus(): ResponseEntity = ResponseEntity.ok("OK")
}
@RestController
@RequestMapping("/api/user")
class UserController(
private val userService: UserService
) {
@PostMapping
fun createUser(@RequestBody request: UserRequest): ResponseEntity {
val user = userService.saveUser(request)

return ResponseEntity.ok(user)
}

@GetMapping
fun findAllUsers(): ResponseEntity<MutableIterable> {
val users = userService.findAllUsers()

return ResponseEntity.ok(users)
}
}

3.7. Build the JAR

./gradlew clean build -x test

3.8. Create Dockerfile

FROM openjdk
WORKDIR /work
COPY ./build/libs/k8s-mysql-0.0.1-SNAPSHOT.jar app.jar
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/work/app.jar"]

4. Deploy to Kubernetes

4.1. Create Secrets

apiVersion: v1
kind: Secret
data:
root-password: <BASE64-ENCODED-PASSWORD>
database-name: <BASE64-ENCODED-DB-NAME>
user-username: <BASE64-ENCODED-DB-USERNAME>
user-password: <BASE64-ENCODED-DB-PASSWORD>
metadata:
name: mysql-secret

4.2. Create Persistent Volume

apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
labels:
type: local
spec:
storageClassName: standard
capacity:
storage: 250Mi
accessModes:
- ReadWriteOnce
hostPath:
path: "/mnt/data"
persistentVolumeReclaimPolicy: Retain
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
labels:
app: spring-boot-mysql
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 250Mi

4.3. Create MySQL Deployment

---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment
labels:
app: spring-boot-mysql
spec:
selector:
matchLabels:
app: spring-boot-mysql
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: spring-boot-mysql
tier: mysql
spec:
containers:
- image: mysql:5.7
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: root-password
- name: MYSQL_DATABASE
valueFrom:
secretKeyRef:
name: mysql-secret
key: database-name
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-secret
key: user-username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user-password
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc

4.4. Create MySQL Service

apiVersion: v1
kind: Service
metadata:
name: mysql-svc
labels:
app: spring-boot-mysql
spec:
ports:
- port: 3306
selector:
app: spring-boot-mysql
tier: mysql
clusterIP: None

4.5. Create Spring Boot Deployment

apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-boot-deployment
labels:
app: spring-boot-mysql
spec:
replicas: 2
selector:
matchLabels:
app: spring-boot-mysql
template:
metadata:
labels:
app: spring-boot-mysql
spec:
containers:
- image: spring-boot:0.0.1
name: spring-boot-container
imagePullPolicy: Never
ports:
- containerPort: 8080
readinessProbe:
httpGet:
port: 8080
path: /api/status
initialDelaySeconds: 10
failureThreshold: 5
livenessProbe:
httpGet:
port: 8080
path: /api/status
initialDelaySeconds: 10
failureThreshold: 5
env:
- name: DB_NAME
valueFrom:
secretKeyRef:
name: mysql-secret
key: database-name
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: mysql-secret
key: user-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret
key: user-password

4.6. Create Spring Boot Service

apiVersion: v1
kind: Service
metadata:
name: spring-boot-svc
spec:
ports:
- port: 8080
targetPort: 8080
protocol: TCP
name: http
selector:
app: spring-boot-mysql
type: LoadBalancer

4.7. Apply and Verify Resources

minikube start --vm-driver=virtualbox
eval $(minikube docker-env)
docker build -t spring-boot:0.0.1 .
kubectl apply -f k8s/secrets.yaml
kubectl apply -f k8s/deployment-mysql.yaml
kubectl apply -f k8s/service-mysql.yaml
kubectl apply -f k8s/deployment-spring.yaml
kubectl apply -f k8s/service-spring.yaml
kubectl get svc
kubectl get deployments
kubectl get pods
kubectl get secrets

5. Verify the Application

minikube ip
//result
192.168.39.87


kubectl cluster-info
//result
Kubernetes master is running at https://192.168.39.87:8443
kubectl get svc spring-boot-svc 

//Example response
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
spring-boot-svc LoadBalancer 10.107.222.166 8080:30264/TCP 33s
curl -X POST -H "Content-Type: application/json" \
--data '{"name":"Piotr"}' \
192.168.39.87:30264/api/user

//response
{"id":1,"name":"Piotr"}
curl 192.168.39.87:30264/api/user

//Response:
[{"id":1,"name":"Piotr"}]

6. Summary

Hi, my name is Piotr and I am the founder of Codersee- a technical blog, where I am teaching programming by practical step by step tutorials.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store