Twelve Factors
Now that we created our first container, we'll now look at how our application behaves and what might help us make it better and more cloud native. For this, we will take a closer look at the Twelve-Factor App methodology.
The Twelve Factor App Methodology is a set of criteria which should ensure that applications are portable, scalable and maintainable. They were initially created by Heroku, but are widely accepted as a best practice for building modern applications and open-sourced.
For our example, the following factors are relevant:
-
Logging: The applications should not write to files, but to stdout/stderr. This allows the container engine to collect the logs and forward them to a log collector. Furthermore, this ensures that the logs are not lost when the container is restarted and the filesystem is not flooded with log files.
-
Configuration: The application should not have any hardcoded configuration, but use environment variables (or an external configuration file) to configure the application. This allows the application to be configured at runtime and makes it easier to deploy the application in different environments.
-
Backing Services: We will ensure that the database used by the application is not hardcoded, but can be configured via environment variables. Furthermore, we will ensure that the application and the database are not tightly coupled, but can be deployed and scaled independently.
At first, let's take a closer look at our application and find out how it's logging. One indicator that the service is not logging too much is that it is very quiet at the moment. If you're accessing some endpoints, you don't get any response on your shell and there is not more log output around it. Therefore, we can open up a shell in the container and inspect it a bit. Please start the container again (now in detached mode using the -d flag). Open a new shell and run the commands
docker run -d -p 8888:3000 --name learning-tracker --rm learning-tracker:v0.1
docker exec -it learning-tracker /bin/bash
With this, you should have a shell opened in your container. The easiest thing you could do now is to execute a simple ls -la to find out which files are stored in the current directory (which was /app in our case). You will find out that there is a file called app.log in the directory. Which problems could we have with this?
Writing logs to the container filesystem could cause multiple issues. At first, it could cause the filesystem to run full and therefore put the stability of the container at risk. Secondly, container filesystems are ephemeral and logs will be gone when your container gets restarted. Last but not least, you should always aim to run the root filesystem of your container read-only. The logs which are written there, will make this effort a bit more complicated.
At this point, we found out that we're logging to the local filesystem, but how could we change this behavior? Typically, there's an option in the used programming language or framework to change this to stdout/stderr.
In our case, we can inspect the app a bit and if we take a closer look in the app.js file, we'll find out that there is an option to also write log messages to the console by changing an environment variable (line 19). Fortunately, we have the option to pass over environment variables by adding the -e parameter with the variable and its value to the run command of the container.
At first, we need to stop the currently running container, as we started it in detached mode. You can do this using the command:
docker stop learning-tracker
Using docker ps you can check that the container is not running anymore. After this, we can start the container again, but this time with the environment variable set to console:
docker run -e LOG_OUTPUT=console --rm --name learning-tracker learning-tracker:v0.1
When starting this, you will experience that the container will now log directly to stdout.
At this point, you fixed logging in your application and your container will not log into the filesystem anymore
Initially, we discussed that this application is using SQLite as a backend, which is by definition a file-based database. This is a pretty cool thing as long as we're running on the local machine, but as soon as we want to scale and maybe run multiple application servers this will become a problem. The database is not replicated and therefore every instance of the application will have different data which is suboptimal in a world where we don't know which backend we'll arrive next.
Therefore, we have two options: Switch to an external database or ensure that our container is only running once. The second one should not be an option nowadays.
In the next step, we'll externalize the database and learn about how to orchestrate multiple containers on a single host.
