Managing container time with docker-compose and faketime
Why do we need to manage time?
First, a little about myself, my main occupation is quality assurance on assigned projects, I am a Senior QA at Umbrella IT. Periodically, when testing microservices, I have to deal with the need to change the time to check the operation of a particular functionality. This may be functionality that is triggered by a cron “tick”, or related to the use of system time as one of the conditions for processing/storing/transferring data by the microservice being tested.
If a microservice is deployed in Docker, the container time is taken from the host machine's system time, and without additional tools, the maximum we can change is the container's time zone, but not the system time itself. The problem is that often, for testing purposes, simply changing the time zone, as well as changing the time within 24 hours, is not enough. What if we need to test the operation of a microservice in boundary date-time values, such as the beginning and end of a month/year, or use special dates such as February 29, the last dates of months with a change in the number of days, and so on?
Of course, you can start a virtual machine, change the system time on it, reboot Docker and deploy the microservice we need. It is quite possible that in cases where a microservice needs to support multiple network connections on different ports (mail, DB, gRPC, etc.), we will have to forward these ports, possibly (depending on the choice of implementation) set up tunnels, etc. Even if such an implementation works, we need to take into account the possible risks of reducing the value of testing due to the build-up of connection “crutches” and the shortcomings of the implementation using a separate virtual machine. All this distances the testing conditions from real conditions of use.
What should we do if we need at the same time:
maximum approximation to standard test conditions
Flexible time management during deployment
no interference with microservice code
minimal impact on resource consumption
maintaining standard network interaction (as much as possible, an experienced networker will immediately notice those nuances that will be described below in this article)
Before describing the instructions, I would like to express my gratitude Nesterovich Alexander for help in implementing and debugging the approach described below.
Instructions for use
So, we are faced with the task of deploying a container with a microservice in a test environment (for example, an FT cluster) and setting it a time different from the host machine time. Let's immediately list the main required accesses for this implementation in the FT environment (this can be a dedicated machine for testing, which is in the FT cluster):
creating, editing and running files
running apt-get, docker and docker-compose
assigning rights to read and execute created files
other accesses are optional and depend on specific cases, for example, access to obtain an image from a repository, or, for example, access to the entrypoint command line.
Create docker-compose.yml
To configure the deployment of our microservice, we will create a file docker-compose.yml
version: '3.6'
services:
my-test-service-workerhost:
image: myimg.repo.com/project/my-test-service-workerhost:1.0.0-Ft
restart: always
environment:
ASPNETCORE_ENVIRONMENT: Staging
FAKED_TIME: '@2007-01-01 00:00:01' #Поле по теме статьи
cap_add:
- NET_ADMIN
volumes:
- "./appsettings.json:/app/appsettings.Staging.json"
- "./entrypoint.sh:/app/entrypoint.sh" #Поле по теме статьи
entrypoint: ["/app/entrypoint.sh"] #Поле по теме статьи
ports:
- 5335:80
All fields, except those marked with the comment “Field on the topic of the article”, do not relate to the issue of system time management and are added only to make our docker-compose.yml
the reality is a little closer to reality. Let's analyze the purposes of the marked fields:
FAKED_TIME
– this is an environment variable through which we will pass the value to the customized entrypoint when deploying the microservice. We can call it any convenient and free name, if we wish, the main thing is not to forget to rename it in the script specified below.@
” before the date means that the specified time Not will be fixed forever for the microservice, and will be the starting point for the system time. Time will run for the microservice starting from the specified starting point.entrypoint
– actually here we indicate that the standard entrypoint must be replaced with a custom one.volumes"./entrypoint.sh:/app/entrypoint.sh"
– here we specify where the file with the custom entrypoint script will be stored
Writing a custom entrypoint
In the folder with the file docker-compose.yml
(or another path that you previously specified in volumes
for entrypoint) create a file entrypoint.sh
and immediately give him the rights to read and execute the command chmod
.
In the file itself, we write the following code in bash:
#!/bin/sh
# Проверяем FAKED_TIME на наличие данных
if [ -z "$FAKED_TIME" ]; then
echo "Error: FAKED_TIME is empty or null with = $FAKED_TIME"
exit 1
else
echo "FAKED_TIME is $FAKED_TIME"
fi
# Устанавливаем faketime
apt-get update && apt-get install -y faketime
# Запускаем приложение с использованием faketime
exec faketime -f "$FAKED_TIME" dotnet /app/MyProject.TestApplication.WorkerHost.dll
To change the system time when starting a microservice, we use faketime. This is an application that replaces the system time value for the command executed with its help. For example, when executing the command faketime -f '2007-01-01 00:00:01' date
in the console we will see the output of the date-time we substituted for the command date
.
Let's launch and check
All that's left for us to do is run the deploy command docker-compose up
and check the container logs
And here we have 2007 again, as we can see the time in the container is not fixed and changes with its flow, which is reflected in the logs
Possible problems and solutions
Networking
No matter how native the approach to changing time is, we may encounter the fact that when interacting with a network using certificates (for example, simple https), we will not have a connection. The reason for this is the verification of the certificate, namely the dates associated with it. In case of such problems, you can test:
switch to interaction via an unencrypted protocol
raise the host/DB etc. you need next to it according to the same instructions with the time already changed, which is consistent with the microservice being tested
raise a mock service nearby according to the same instructions with the time already changed, which is consistent with the microservice being tested
The choice of solution depends on each specific case/project, what will be faster, more convenient, have less impact on testing efficiency or will simply be feasible.
No access to execute commands when deploying a container
It happens that for security reasons the default user is denied the ability to execute commands when building a container. In such cases, it is necessary to entrypoint.sh
Before executing commands, switch to a user with sufficient rights, or add commands for installing and using faketime inside the Docker file
No faketime among the applications available for installation
If suddenly in your case a simple installation is via apt-get install -y faketime
impossible, you can use the library libfaketime to clone the repository for its further installation
git clone https://github.com/wolfcw/libfaketime.git
cd libfaketime
make install
Instructions for using libfaketime can be found in readme repositories
Conclusion
Container system time management provides ample opportunities for testing the functionality of services that use this data in their logic. This allows you to increase test coverage, reduce the time spent on testing itself, flexibly manage the environment and, if necessary, reproduce specific cases.