wolfcw / libfaketime

libfaketime modifies the system time for a single application
https://github.com/wolfcw/libfaketime
GNU General Public License v2.0
2.62k stars 319 forks source link

How to speed up and synchronize the time of docker containers on the same host? #455

Closed curry7313 closed 5 months ago

curry7313 commented 5 months ago

I'm working on an application related to time. In order to simulate a real environment, I need a virtual environment that is 1440 times faster than real time.

I was able to speed up the time of a container via libfaketime, but I need to run multiple time-accelerated containers on the same virtual machine and require their times to be synchronized.

Can libfaketime do it?

wolfcw commented 5 months ago

You can sync the current faked time / libfaketime settings across processes, containers, and machines as long as they have common access to one (shared) file (see, e.g., FAKETIME_FOLLOW_FILE).

However, all libfaketime does is reporting faked times back to applications that use one of the intercepted time-related system calls. It does not stall or halt execution of any process until some other process is in the same state.

curry7313 commented 5 months ago

@wolfcw Thank you for your reply. In addition, I want to use cron in the time environment faked by libfaketime, such as executing it at 0 o'clock every day. Are there any relevant examples?

wolfcw commented 5 months ago

Please see section "Faking the date and time system-wide" in libfaketime's README file. It works for bare metal, VMs as well as containers. It's probably the easiest solution in your containers / Dockerfile. Otherwise, you can also selectively start the cron daemon with libfaketime.

curry7313 commented 5 months ago

@wolfcw Hi, As you said, I tried to change the time system-wide with using /etc/faketimerc. However, the result was not what I expected. This is my cron script file hello-cron

0 0 * * *   echo "Hello $(date)" >>/var/log/cron.log 2>&1
#

Dockerfile:

FROM debian
RUN apt-get update && apt-get install -y cron

COPY --from=forlabs/libfaketime /faketime.so /faketime.so
ENV LD_PRELOAD=/faketime.so
# Push back 1 year and speed up a real day to one minute
RUN echo "+1y x1440" > /etc/faketimerc

COPY hello-cron /etc/cron.d/hello-cron
RUN crontab /etc/cron.d/hello-cron

CMD ["cron", "-f"]

In the running container, enter date, the time offset and speed did change:

root@a3b68672eb9a:/# date
Tue Feb  4 20:42:28 UTC 2025
root@a3b68672eb9a:/# date
Wed Feb  5 21:04:55 UTC 2025
root@a3b68672eb9a:/# date
Wed Feb  5 21:56:22 UTC 2025

However, the date output to the file has not changed, although the output frequency is indeed one line per minute (x1440 causes 0 0 * * * to become every minute)

root@a3b68672eb9a:/# cat /var/log/cron.log
Hello Sun Feb  4 15:50:23 UTC 2024
Hello Sun Feb  4 15:51:23 UTC 2024
Hello Sun Feb  4 15:52:23 UTC 2024

I am expecting one line per minute and the output should be:

Hello Sun Feb  4 00:00:00 UTC 2025
Hello Sun Feb  5 00:00:00 UTC 2025

What do you think is going on?

wolfcw commented 5 months ago

Well, since libfaketime apparently does not apply to the executed cronjob, it seems like environment variables are getting sanitized. I'd recommend setting up a cronjob which executes a shell script, in which you set LD_PRELOAD again.

curry7313 commented 5 months ago

@wolfcw Thanks. I reset up my cronjob:

0 0 * * *   /app/main.sh >>/app/cron.log 2>&1
#

main.sh:

#! /bin/bash

export LD_PRELOAD=/faketime.so
echo "Hello $(date)"

The final result like this:

root@ba89bc3f0f51:/app# date
Fri Feb  7 05:37:53 UTC 2025
root@ba89bc3f0f51:/app# date
Fri Feb  7 08:17:18 UTC 2025
root@ba89bc3f0f51:/app# cat cron.log
Hello Tue Feb  4 15:39:38 UTC 2025
Hello Tue Feb  4 15:40:38 UTC 2025
Hello Tue Feb  4 15:41:38 UTC 2025

As you can see, although the offset of the time in the log has changed, the printed time is still not 0 o'clock every day. Is there a way for me to simulate a virtual environment(bare metal, VMs, containers are all acceptable) so that, except for time acceleration, the execution effects of other commands, programs, scheduled tasks, etc. are exactly the same as in the real world?

wolfcw commented 5 months ago

It's some progress, because apparently libfaketime now is loaded for the executed cronjobs (unlike before).

In the cronjob (main.sh), can you check / write to the logfile whether the environment variable FAKETIME_SHARED is set?

If it's not set, does starting cron -f via libfaketime's faketime wrapper change anything about that?

What certainly always should work is using the mentioned FAKETIME_FOLLOW_FILE instead of a relative time offset in the faketimerc file, but this requires another process/program (e.g., another cronjobs) to always adjust the given file's timestamp. However, I assume this will be needed for your cross-device/-container setup anyway sooner or later. You can have that process/cronjob running at x1440 speed and simply touch the file as often as required.

curry7313 commented 5 months ago

@wolfcw Thank you very much for your patient explanation. I realized that the above result is because every time $(date) is executed in the shell, it is faked based on my local time, so I need to synchronize the previously faked time throughFAKETIME_FOLLOW_FILE, right?

curry7313 commented 5 months ago

However, there are still two problems here:

  1. In the above results:

    root@ba89bc3f0f51:/app# cat cron.log
    Hello Tue Feb  4 15:39:38 UTC 2025
    Hello Tue Feb  4 15:40:38 UTC 2025
    Hello Tue Feb  4 15:41:38 UTC 2025

    In fact, even if you don't care about the printed content, the printed time is not 0 o'clock every day of the faked time. Printing the time is just for testing. In fact, what I am more concerned about is that the execution time needs to occur at 0 o'clock every day, so as to simulate the real environment.

  2. The file specified by FAKETIME_FOLLOW_FILE is being written to 1440 times per second, is this really okay?

wolfcw commented 5 months ago

Yeah, it looks like time settings are not synchronized between your cron daemon and the cronjobs. That's why you might want to check whether FAKETIME_SHARED is set for the cronjobs, and if it's not, use the faketime wrapper when starting the cron daemon. :-)

FAKETIME_FOLLOW_FILE is the easiest way to make multiple independent processes use the exactly same faked time. When modifying the file quite often (such as 1440 times per second), things are usually cached by the file system in RAM and nothing really hits the disk. So yes, it should be OK, since it's not stressing your hardware.

curry7313 commented 5 months ago

@wolfcw Your answer is very useful to me, thanks.

curry7313 commented 5 months ago

@wolfcw I tried make multiple independent processes use the exactly same faked time by FAKETIME_FOLLOW_FILE. This is the Dockerfile I use as a test, just install libfaketime:

FROM centos/python-36-centos7:latest
USER root
WORKDIR /app

RUN git clone https://github.com/wolfcw/libfaketime.git
RUN cd libfaketime && make && make install

CMD ["/bin/bash"]

I run this container as -i -t, and I did the same operation as in the Readme about FAKETIME_FOLLOW_FILE:

touch -t 0912241234.56 /tmp/my-demo-file.tmp
LD_PRELOAD=/usr/local/lib/faketime/libfaketime.so.1 \
  FAKETIME='%' FAKETIME_FOLLOW_FILE=/tmp/my-demo-file.tmp \
  FAKETIME_DONT_RESET=1 \
  bash -c 'while true ; do date ; sleep 1 ; done'

However the return I get is:

libfaketime: Cannot get timestamp of file /tmp/my-demo-file.tmp as requested by % operator.

What did I do wrong?

wolfcw commented 5 months ago

It's hard to tell... it's either a filename (file does not exist) or permission issue (e.g., file created by root and not accessible by other users, eventually related to specific settings of your /tmp directory). Try FAKETIME_FOLLOW_TIME with any file certainly available and accessible on the system (e.g., something like /bin/bash), and if this still does not work, please try outside a container (e.g., on a VM or bare metal machine).

curry7313 commented 5 months ago

It was my mistake to ignore the above. Finally, thank you.