Build and populate a database using Kubernetes init containers

You can build and populate a database that is running in Red Hat OpenShift in a number of ways. Kubernetes init containers are one way that makes it easy to work with a new database.

This learning path, created by Don Schenck, demonstrates the use of init containers.

Your first step in building and populating your database is to create the PostgreSQL instance, ConfigMap, and Deployment objects.

Step 0: Log in to your Developer Sandbox at the command line

If you’re unsure of how to do this, you can find instructions here: Access your Developer Sandbox from the command line.

Step 1: Get the code from GitHub

Download or clone the following repo: https://github.com/redhat-developer-demos/getfood 

Then, move it into the directory where the code lands.

Step 2: Create the PostgreSQL instance

Run the following command to create an instance of PostgreSQL running in your sandbox. Note: This is an ephemeral instance; the data is lost when the pod is stopped:

oc new-app openshift/postgresql-ephemeral -p DATABASE_SERVICE_NAME=postgresql -e POSTGRESQL_USER=postgres -e POSTGRESQL_PASSWORD=postgres --labels=app.kubernetes.io/part-of=foods,systemname=foods,tier=database,database=postgresql,foods=database

Step 3: Make the files available

There are several pieces of information that need to be available inside your OpenShift cluster in order to create a database, create a table, and fill that table with data. The method used to get the scripts and data into the OpenShift cluster is by using a ConfigMap object. The ConfigMap is then mounted as a volume in the init containers. In other words, the files appear as files on a drive, in a given file path. For example, /etc/scripts/build_database.sql.

The files we will use are in the /configmap directory of this activity. Here’s a listing:

Directory: C:\Users\dschenck\src\getfood\configmap

Mode                 LastWriteTime         Length Name

----                 -------------         ------ ----

-a---          11/29/2023  1:29 PM            631 build_tables.sql

-a---          11/29/2023  1:07 PM             22 create_database.sql

-a---          11/17/2023  2:13 PM         421163 fooddisplaytable.csv

The *.sql files are PostgreSQL commands to create the database and the table. The file fooddisplaytable.csv contains the data that will be imported into the table. These three files need to be combined into one ConfigMap object. Fortunately, there’s a command to do that.

Move into your Git repo top-level directory (getfood) and run the following command to create the ConfigMap object YAML file on your local PC:

oc create configmap db-config --from-file=./configmap --dry-run=client -o yaml > db-configmap.yaml

The result is the file db-configmap.yaml that contains the contents of all three of the files in the ./configmap subdirectory. Those files are in the data section of the created ConfigMap YAML. Here’s a snippet:

apiVersion: v1

data:

  build_tables.sql: "CREATE TABLE IF NOT EXISTS food (\r\n\tfood_code int,\r\n\tdisplay_name

    vaRcHaR( 80 ),\r\n\tportion_default int,\r\n\tportion_amount numeric,\r\n\tportion_display_name

    vaRcHaR( 40 ),\r\n\tfactor numeric,\r\n\tincrement numeric,\r\n\tmultiplier numeric,\r\n\tgrains

    numeric,\r\n\twhole_grains numeric,\r\n\tvegetables numeric,\r\n\torange_vegetables

    numeric,\r\n\tdrkgreen_vegetables numeric,\r\n\tstarchy_vegetables
numeric,\r\n\tother_vegetables

    numeric,\r\n\tfruits numeric,\r\n\tmilk numeric,\r\n\tmeats numeric,\r\n\tsoy

    numeric,\r\n\tdrybeans_peas numeric,\r\n\toils numeric,\r\n\tsolid_fats numeric,\r\n\tadded_sugars

    numeric,\r\n\talcohol numeric,\r\n\tcalories numeric,\r\n\tsaturated_fats numeric\r\n);"

  create_database.sql: CREATE DATABASE foods;

  fooddisplaytable.csv: "Food_Code,Display_Name,Portion_Default,Portion_Amount,Portion_Display_Name,Factor,Increment,Multiplier,Grains,Whole_Grains,Vegetables,Orange_Vegetables,Drkgreen_Vegetables,Starchy_vegetables,Other_Vegetables,Fruits,Milk,Meats,Soy,Drybeans_Peas,Oils,Solid_Fats,Added_Sugars,Alcohol,Calories,Saturated_Fats\r\n12350000,Sour

    cream dip,1,.25000,cup,.25000,.25000,1.00000,.04799,.00000,.04070,.00000,.00000,.00000,.04070,.00000,.00000,.00000,.00000,.00000,.00000,105.64850,1.57001,.00000,133.65000,7.36898\r\n13110100,\"Ice

    cream, regular\",1,1.00000,cup,1.0000

This means we can reference the files by name—e.g., build_tables.sql from our init containers. With this YAML file created, we can create the ConfigMap object in our cluster. Run the following command to create the ConfigMap:

oc create -f db-configmap.yaml

Here’s an example:

> oc create -f .\db-configmap.yaml

configmap/db-config created

At this point, the ConfigMap object exists in your cluster. It doesn’t do anything; it simply exists. But, it exists with the files we need inside of it. That is, the files are available for us to use. Referencing those files from the init containers is how we make use of it.

The next step is to create a Deployment object that includes init container objects that reference these files.

Create a Deployment object

In the Git repo home directory, this Deployment object is defined in the file mydeploy.yaml. Rather than recreate the contents here, you can view the Kubernetes documentation for Init Containers to see how they work. Likewise, you can view the Kubernetes documentation related to configuring a pod to use a ConfigMap.

When those two pieces of knowledge are combined, you get a deployment that, in our case, includes the following (an explanation follows this section of the YAML file):

spec:
      initContainers:

      - name: init-createdb

        image: postgres:14

        command: ["psql", "postgresql://postgres:postgres@postgresql", "-f", "/etc/rsalbums/create_database.sql"]

        volumeMounts:

        - name: albums-volume

          mountPath: /etc/rsalbums

      - name: init-builddb

        image: postgres:14

        command: ["psql", "postgresql://postgres:postgres@postgresql/foods", "-f", "/etc/rsalbums/build_tables.sql"]

        volumeMounts:

        - name: albums-volume

          mountPath: /etc/rsalbums

      - name: init-populatedb

        image: postgres:14

        command: ["psql", "postgresql://postgres:postgres@postgresql/foods", "-c", "\\copy food FROM '/etc/rsalbums/fooddisplaytable.csv' CSV HEADER;"]

        volumeMounts:

        - name: albums-volume

          mountPath: /etc/rsalbums

      containers:

        - name: getfood

          image: quay.io/rhdevelopers/food-service:latest

          ports:

            - containerPort: 8080

              protocol: TCP

            - containerPort: 8443

              protocol: TCP

      restartPolicy: Always

      terminationGracePeriodSeconds: 30

      dnsPolicy: ClusterFirst

      securityContext: {}

      schedulerName: default-scheduler

      strategy:

        type: RollingUpdate

        rollingUpdate:

          maxUnavailable: 25%

          maxSurge: 25%

      revisionHistoryLimit: 10

      progressDeadlineSeconds: 600

      volumes:

      - name: albums-volume

        configMap:

          name: db-config

In this deployment (mydeploy.yaml) are three init containers and the container we want, eventually, to be up and running as our microservice (getfood). The init containers use the image postgres:14 to run their commands. Why? Because the command being run is the PostgreSQL command-line tool, psql, which allows us to run scripts and other commands against the PostgreSQL database instance that we created in Step 2.

Once those three init containers run, the getfood container is started using the image specified. The food-service program reads from the database and returns a JSON array representing the entire table.


Congratulations! You've created an instance, a ConfigMap, and deployment objects. Now it’s time to deploy the application.

Previous resource
Overview: Build and populate a database using Kubernetes init containers
Next resource
Deploy and view the application