Connecting to a Database In this section we will deploy and connect a MongoDB database where the nationalparks application will store location information. Finally, we will mark the nationalparks application as a backend for the map visualization tool, so that it can be dynamically discovered by the parksmap component using the OpenShift discovery mechanism and the map will be displayed automatically. Background: Storage Most useful applications are "stateful" or "dynamic" in some way, and this is usually achieved with a database or other data storage. In this lab we are going to add MongoDB to our nationalparks application and then rewire it to talk to the database using environment variables via a secret. Exercise: Deploy MongoDB The first step is to create Kubernetes Secrets which are used to store database-related configuration and credentials. From the left navigation, choose Workloads → Secrets, then click the Create button in the upper right and select Key/value secret. In the dialog, fill in the entries shown below, by adding four key/value pairs in total. Secret Name → mongodb-credentials key: admin-usr | value: admin key: admin-pwd | value: secret key: app-usr | value: parksapp key: app-pwd | value: keepsafe Click the Create button at the bottom of the form. With the secret in place, click the Quick create (+) button in the top-right masthead and choose Container images, which will open up the Deploy Image form. Fill in the following details: Image section: Image name from external registry → mongo:6.0.4 Runtime icon → choose mongodb General section: Application → workshop Name → mongodb Deploy section: Resource type → Deployment If OpenShift Serverless is installed on the cluster, Resource type defaults to Serverless Deployment. Make sure you change it to Deployment. Advanced options: Create a route to the application → uncheck because no route is needed to the DB instance itself Still under Advanced options, click Show advanced Deployment option. Here, we’ll add the following two environment variables which will be sourced from the previously created secret named mongodb-credentials. Click Add from ConfigMap or Secret and enter MONGO_INITDB_ROOT_USERNAME as the name. For the value, choose mongodb-credentials as resource and admin-usr as key. Again, click Add from ConfigMap or Secret and enter MONGO_INITDB_ROOT_PASSWORD as the name. For the value, choose mongodb-credentials as resource and admin-pwd as key. Finally click the Create button at the bottom of the page to deploy MongoDB into your topology. The next step is to create a specific user in this MongoDB instance which the nationalparks application will use to create a connection. For this, go to Workloads → Topology, click the mongodb deployment, then in the side panel open the Resources tab and click on the corresponding Pod. You will end up in the pod details view where you can click Terminal to start a terminal session in the underlying container. Copy and paste the following snippet into the terminal window to create a database user with the proper settings and roles: mongosh -u admin -p secret --authenticationDatabase admin --eval 'use parksapp' --eval 'db.createUser({user: "parksapp", pwd: "keepsafe", roles: [{ role: "dbAdmin", db: "parksapp" },{ role: "readWrite", db: "parksapp" }]})' --quiet If the command is successful you’ll see the output below: { ok: 1 } With that, everything regarding the database is prepared and we can switch our focus back to the nationalparks application. Go back to Workloads → Topology, click the nationalparks deployment and choose Actions > Edit Deployment from the drop-down in the right view pane. The Edit Deployment page opens in Form view. Scroll down to the Environment Variables section and add the following four entries. First set the server host and the database, using Add value: MONGODB_SERVER_HOST: mongodb MONGODB_DATABASE: parksapp The other two are credentials which are sourced from the mongodb-credentials secret. Click Add from ConfigMap or Secret and enter MONGODB_USER as the name. For the value, choose mongodb-credentials as resource and app-usr as key. Again, click Add from ConfigMap or Secret and enter MONGODB_PASSWORD as the name. For the value, choose mongodb-credentials as resource and app-pwd as key. Finally hit the Save button at the bottom of the form. This will trigger a re-creation of a new pod and restart the application which should now be able to successfully talk to the MongoDB instance you just deployed earlier. Exercise: Adding Labels Next, let’s fix the labels assigned to the MongoDB deployment. From Workloads → Topology, select the mongodb deployment and choose Actions > Edit labels. Like before, we’ll add 3 labels: The name of the Application group: app=workshop Next the name of this deployment. component=nationalparks And finally, the role this component plays in the overall application. role=database Exercise: Exploring OpenShift Magic As soon as we connected our database, some magic happened. OpenShift decided that this was a significant enough change to warrant updating the internal version number of the ReplicaSet. You can verify this by looking at the output of oc get rs: NAME DESIRED CURRENT READY AGE mongodb-6d77f5dd8f 1 1 1 9m12s nationalparks-5745847899 1 1 1 113s nationalparks-865c54b745 0 0 0 21m nationalparks-867b7d84f4 0 0 0 20m parksmap-5b66975f9b 1 1 1 51m We see that the DESIRED and CURRENT number of instances for the current deployment. The desired and current number of the other instances are 0. This means that OpenShift has gracefully torn down our "old" application and stood up a "new" instance. Exercise: Data, Data, Everywhere Now that we have a database deployed, we can again visit the nationalparks web service to query for data: https://nationalparks-%PROJECT%.%CLUSTER_SUBDOMAIN%/ws/data/all And the result? [] Where’s the data? Think about the process you went through. You deployed the application and then deployed the database. Nothing actually loaded anything INTO the database, though. The application provides an endpoint to do just that: https://nationalparks-%PROJECT%.%CLUSTER_SUBDOMAIN%/ws/data/load And the result? Items inserted in database: 2893 If you then go back to /ws/data/all you will see tons of JSON data now. That’s great. Our parks map should finally work! There are some errors reported with browsers like Firefox 54 that don’t properly parse the resulting JSON. It’s a browser problem, and the application is working properly. https://parksmap-%PROJECT%.%CLUSTER_SUBDOMAIN% Hmm… There’s just one thing. The main map STILL isn’t displaying the parks. That’s because the front end parks map only tries to talk to services that have the right Label. You are probably wondering how the database connection magically started working? When deploying applications to OpenShift, it is always best to use environment variables, secrets, or configMaps to define connections to dependent systems. This allows for application portability across different environments. The source file that performs the connection as well as creates the database schema can be viewed here: http://www.github.com/openshift-roadshow/nationalparks/blob/master/src/main/java/com/openshift/evg/roadshow/parks/db/MongoDBConnection.java#L44-l48 In short summary: By referring to bindings to connect to services (like databases), it can be trivial to promote applications throughout different lifecycle environments on OpenShift without having to modify application code. Exercise: Working With Labels We explored how a Label is just a key=value pair earlier when looking at Services and Routes and Selectors. In general, a Label is simply an arbitrary key=value pair. It could be anything. pizza=pepperoni pet=dog openshift=awesome In the case of the parks map, the application is actually querying the OpenShift API and asking about the Routes and Services in the project. If any of them have a Label that is type=parksmap-backend, the application knows to interrogate the endpoints to look for map data. You can see the code that does this here. Fortunately, the command line provides a convenient way for us to manipulate labels. describe the nationalparks service: oc describe route nationalparks Name: nationalparks Namespace: %PROJECT% Created: 20 minutes ago Labels: app=workshop app.kubernetes.io/component=nationalparks app.kubernetes.io/instance=nationalparks app.kubernetes.io/name=java app.kubernetes.io/part-of=workshop app.openshift.io/runtime=java component=nationalparks role=backend Annotations: openshift.io/host.generated=true Requested Host: nationalparks-%PROJECT%.%CLUSTER_SUBDOMAIN% exposed on router default 20 minutes ago Path: <none> TLS Termination: edge Insecure Policy: <none> Endpoint Port: 8080-tcp Service: nationalparks Weight: 100 (100%) Endpoints: 10.131.0.4:8080 You see that it already has some labels. Now, use oc label: oc label route nationalparks type=parksmap-backend You will see something like: route.route.openshift.io/nationalparks labeled If you check your browser now: https://parksmap-%PROJECT%.%CLUSTER_SUBDOMAIN%/ You’ll notice that the parks suddenly are showing up. That’s really cool! Nationalparks Backend App Application Healthchecks