This repository contains a sample Spring Boot microservice implemented with Kotlin.
Builds further upon the ideas and implementation in this article on the codecentric blog.
Also serves as a proving ground to explore and compare the relative levels of effort to get a service and database configured and deployed to multiple container orchestration platforms:
- Docker
- Pivotal Application Service
- Kubernetes ( minikube | GKE | Azure | Pivotal Container Service (PKS) )
- jq 1.5 or better
- Java JDK 1.8u162 or better
- Note: as of 2018-07-28 this project does not yet work with Java 10 or 11.
- CF CLI 6.35.2 or better if you want to push the application to a Cloud Foundry (CF) instance
- Postman 6.0.10 or better to simplify interaction with API endpoints
- An instance of Postgres 10.3 or better
- Docker (Community or Enterprise Editions for Windows | Mac | Linux) 18.03.0 or better
- Google Cloud SDK 195.0.0 or better
- Azure CLI 2.0 or better
- Kubernetes if you want to deploy the application to minikube, GKE, Azure or PKS
git clone https://github.com/pacphi/spring-boot-with-kotlin-and-jpa-example.git
cd spring-boot-with-kotlin-and-jpa-example
./mvnw package
cd spring-boot-with-kotlin-and-jpa-example
rm -Rf cities-web/build
mkdir -p cities-web/build
touch cities-web/build/oauth2accesstoken
./gradlew build
An artifact is produced with a version
which is managed in both the pom.xml
and gradle.properties
files in the root of this project. Where you see x.x.x
below, replace with the a real version (e.g., 1.0.0-SNAPSHOT
).
Setup
minikube start
minikube dashboard
Teardown
minikube stop
minikube delete
Distilled from Getting Started with kops on GCE
Create environment variables
export BUCKET_SUFFIX=<replace_with_bucket_suffix>
export PROJECT=`gcloud config get-value project`
export KOPS_FEATURE_FLAGS=AlphaAllowGCE
export ZONE=<replace_with_zone>
export KOPS_STATE_STORE=gs://kubernetes-clusters-${BUCKET_SUFFIX}/
Create storage bucket
gsutil mb gs://kubernetes-clusters-${BUCKET_SUFFIX}
Create cluster configuration
kops create cluster simple.k8s.local --zones ${ZONE} --project=${PROJECT} --ssh-public-key=<replace_with_ssh_public_key>
Check configuration
kops get cluster
kops get cluster simple.k8s.local -oyaml
kops get instancegroup --name simple.k8s.local
Create cluster
kops update cluster simple.k8s.local --yes
kops validate cluster
Use
kubectl get nodes --show-labels
Teardown
kops delete cluster simple.k8s.local --yes
Distilled from Using the Microsoft Azure public cloud and Setting up Kubernetes with Juju
Initialize juju
juju update-clouds
Authenticate and list subscription(s)
az login
az account list
Create service principal (if one does not already exist)
export SUB_ID=`az account list | jq '.[0].id' -r`
export APP_PASSWORD=b00tMe
az ad sp create-for-rbac --name "my.k8s.io" --password $APP_PASSWORD --role Owner
Notes: (1) We're setting a subscription id variable to the first id returned from the acccount list. If you have more than one account; be sure choose one with elevated privileges. (2) The
APP_PASSWORD
value should be replaced. (3) The--name
option from the create service principal command above is arbitrary. (4) CaptureappId
andtenantId
from the output. Export two additional environment variables based on these values.
export APP_ID=...
export TENANT_ID=...
Login with service principal
az login --service-principal -u $APP_ID -p $APP_PASSWORD --tenant $TENANT_ID
List environment variables with ID
env | grep ID
Add service principal credentials to juju
juju add-credential azure
When prompted enter an arbitrary credential name, select
service-principal-secret
as theAuth type
, and employ$APP_ID
,$SUB_ID
and$APP_PASSWORD
environment variables values respectively forapplication-id
,subscription-id
andapplication-password
.
Create controller and deploy cluster
juju bootstrap azure/{REGION} {CLUSTER_NAME}
juju deploy canonical-kubernetes
Replace {REGION} and {CLUSTER_NAME} above with a valid region within Azure and an arbitrary name for your cluster. Creating the controller will take 5-10 minutes. Deploying the cluster will take another 10-15 minutes.
Monitor deployment
juju status
When all states are green and Idle, the cluster is ready to be used
Create the kubectl config directory
mkdir -p ~/.kube
Copy the kubeconfig file to the default location
juju scp kubernetes-master/0:/home/ubuntu/config ~/.kube/config
Query the cluster
kubectl cluster-info
Teardown
export CLUSTER_CONTROLLER=`juju switch`
juju destroy-controller $CLUSTER_CONTROLLER --destroy-all-models
- Delete the service principal you created.
Assuming you've installed PKS on vSphere or GCP, consult Using PKS. Also see PKS CLI.
Have a look at the following guides to get acquainted with Google Cloud Registry
Be sure to initialize your application default credentials
gcloud auth application-default login
Distilled from the Quickstart
export AZ_GROUP=containers-dc64336a2
export AZ_REGISTRY=cr0dc64336a2
az group create --name $AZ_GROUP --location westus2
az acr create --resource-group $AZ_GROUP --name $AZ_REGISTRY --sku Basic
Feel free to modify the
AZ_GROUP
andAZ_REGISTRY
environment variable values above.
You must authorize minikube to work with your private registry.
See Google Container Registry Advanced Authentication.
The easiest way to do this is to:
gcloud auth application-default print-access-token
minikube ssh
sudo docker login -u oauth2accesstoken -p "<replace_me_with_token>" https://us.gcr.io
exit
This project supports building, tagging, and deploying a Docker container image to a private registry via Maven plugin configuration or standard Docker commandline.
Consult the following table and replace occurences of bracketed variables appearing below
Hostname | Project Id | Version | Cloud |
---|---|---|---|
{HOSTNAME} | {PROJECT_ID} | {VERSION} | |
us.gcr.io | fe-cphillipson | latest | |
cr0dc64336a2.azurecr.io | fe-cphillipson | latest | Azure |
Start here
cd cities-web
docker login -u oauth2accesstoken -p "$(gcloud auth application-default print-access-token)" https://us.gcr.io
./mvnw install -Ddocker.image.prefix={HOSTNAME}/{PROJECT_ID}
Note: if you do not specify
docker.image.prefix
as above it will default topivotalio
.
gcloud auth application-default print-access-token > build/oauth2accesstoken
cat build/oauth2accesstoken
docker login -u oauth2accesstoken -p "<replace_me_with_token>" https://us.gcr.io
./gradlew build pushDockerToGcr -PdockerImagePrefix={HOSTNAME}/{PROJECT_ID}
Note: if you do not specify
dockerImagePrefix
as above it will default topivotalio
.
docker build -t cities-web .
docker tag cities-web {HOSTNAME}/{PROJECT_ID}/cities-web:{VERSION}
gcloud docker -- push {HOSTNAME}/{PROJECT_ID}/cities-web:{VERSION}
az acr login --name $AZ_REGISTRY
export AZ_REGISTRY_HOSTNAME=`az acr list --resource-group $AZ_GROUP --query "[].{acrLoginServer:loginServer}" --output json | jq '.[0].acrLoginServer'`
docker tag pivotalio/cities-web $AZ_REGISTRY_HOSTNAME/fe-cphillipson/cities-web:latest
docker push $AZ_REGISTRY_HOSTNAME/fe-cphillipson/cities-web:latest
Start
docker run -it --rm -p 5432:5432 -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=geo_data -d postgres:10.3
Stop and cleanup
docker ps -a
docker stop {container_id}
docker rm {container_id}
`{container_id} is the identifer for the running/stopped postgres instance
cd cities-web
Deploy postgres with a persistent volume claim
kubectl create -f specs/k8s/postgres.yml
Create a config map with the hostname of Postgres
kubectl create configmap hostname-config --from-literal=postgres_host=$(kubectl get svc postgres -o jsonpath="{.spec.clusterIP}")
Delete the hostname config map
kubectl delete cm hostname-config
Delete Postgres
kubectl delete -f specs/k8s/postgres.yml
Assuming a Postgres option is available from the Marketplace, like ElephantSQL on Pivotal Web Services
cf create-service elephantsql panda my-pgdb
Note: this plan will cost you $19/month, so if you're merely evaluating, please remember to shutdown the service with
cf delete-service my-pgdb
.
cd cities-web
java -Dspring.profiles.active=postgres,seeded -jar target/cities-web-x.x.x.jar
or
./mvnw -p cities-web spring-boot:run -Dspring.profiles.active=postgres,seeded
cd cities-web
java -Dspring.profiles.active=postgres,seeded -jar build/libs/cities-web-x.x.x-exec.jar
or
./gradlew cities-web:bootRun -Dspring.profiles.active=postgres,seeded
Press
Ctrl+C
to exit.
This setup uses ephemeral storage. When the db
container is stopped all data will be lost!
Optional: edit the
docker-compose.yml
file to swap thewebapp
image.
to startup (from root directory)
cd specs/docker
docker-compose up -d
to shutdown
docker-compose down
Edit specs/k8s/cities-web.yml
and replace image
with appropriate private registry image, then deploy the app with
kubectl create -f specs/k8s/cities-web.yml
Note: pulling an image from Azure Container service requires an image pull secret. So the yml file mentioned above will need to modified to accommodate this constraint.
Get the external IP address of service
minikube service cities-web --url
Create an external load balancer for your app
kubectl expose deployment cities-web --type=LoadBalancer --port=8080
Note: It may take a few minutes for the load balancer to be created
kubectl get svc cities-web
Get the external IP address of service, then the app will be accessible at
http://<External IP Address>:8080
View logs for troubleshooting purposes
kubectl logs deployment/cities-web
Scale your application
kubectl scale deployment cities-web --replicas=3
To update the image that the containers in your deployment are using
kubectl set image deployment/cities-web cities-web={HOSTNAME}/{PROJECT_ID}/cities-web:{VERSON}
Delete the Spring Boot app deployment
kubectl delete -f specs/k8s/cities-web.yml
Delete the service for the app
kubectl delete svc cities-web
cf login -a {CF_INSTANCE_URL}
E.g., to deploy to Pivotal Web Services
cf login -a https://api.run.pivotal.io
when prompted, supply your account credentials.
then to target a new organization and space, execute
cf target -o {ORG} -s {SPACE}
where
{ORG}
is an organization name and{SPACE}
is an environment; e.g.,cf target -o catepillar -s test
with manifest.yml
cf push -p {PATH/TO/ARTIFACT}
Note:
{PATH/TO/ARTIFACT}
is the path to an executable JAR. If Maven was used to build project, specifytarget/cities-web-x.x.x.jar
. If Gradle was used, specifybuild/libs/cities-web-x.x.x-exec.jar
.
./gradlew cf-push -Pcf.ccHost={CF_INSTANCE_URL} -Pcf.ccUser={CF_USER} -Pcf.ccPassword={CF_PASSWORD} -Pcf.org={ORG} -Pcf.space={SPACE}
or if you want to orchestrate a blue-green deployment, try
./gradlew cf-push-blue-green -Pcf.ccHost={CF_INSTANCE_URL} -Pcf.domain={CF_DOMAIN} -Pcf.ccUser={CF_USER} -Pcf.ccPassword={CF_PASSWORD} -Pcf.org={ORG} -Pcf.space={SPACE}
Consult pivotalservices/ya-cf-app-gradle-plugin for detailed configuration options.
cf delete cities-web
Edit the .sql file in cities-web/src/main/resources/db/sql/test
and add INSERT
statements, like:
INSERT INTO city (id, name, description, latitude, longitude, updated_at, created_at) VALUES ('SFO', 'San Francisco', '', 37.781555, -122.393990, '2018-03-25 15:00:00', '2018-03-25 15:00:00');
Application endpoints
GET /cities
GET /cities/{id}
PUT /cities/{id}
POST /cities/{id}
DELETE /cities/{id}
and of course there are the actuator endpoints provided by Spring Boot