Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Configuring time zone #284

Open
senarvi opened this issue Feb 28, 2021 · 14 comments
Open

Configuring time zone #284

senarvi opened this issue Feb 28, 2021 · 14 comments

Comments

@senarvi
Copy link

senarvi commented Feb 28, 2021

Many services display time incorrectly, unless the local time zone is configured by setting the TZ environment variable. Some of the service.yml files in IOTstack set the TZ environment variable to Etc/UTC, Europe/Paris, or Europe/Berlin - wherever the person is living who contributed that service.

Docker Compose supports substituting environment variables in Compose files. We could instead use the following in service.yml:

  environment:
    - TZ=${TZ}

And the user could configure time zone by setting the TZ environment variable, or creating a file .env in the IOTstack directory that contains something like:

TZ=Europe/Paris 
@877dev
Copy link

877dev commented Mar 29, 2021

@senarvi thanks for this post, I also recently noticed the same issue.

I temporarily fixed it by adding the timezone manually to each container in docker-compose.yml. It could be added instead to compose-override.yml if the menu is being used.

But your suggestion seems the best one!

@senarvi
Copy link
Author

senarvi commented Mar 29, 2021

I didn't know about docker-compose.override.yml. That can also be used to modify the configuration without having to modify docker-compose.yml (which will be overwritten by the menu).

@Paraphraser
Copy link

Paraphraser commented Mar 30, 2021

You can't simply add TZ to every container's definition (regardless of which method you use) and expect it to "just work". A container actually has to support it.

I've experimented with this before without getting anywhere but @877dev raising the matter on Discord made me start thinking about it again and I may've stumbled across the magic incantation.

I'm currently playing with AdGuard Home. It doesn't respect TZ so it's a good test container.

What's my Raspberry Pi's time zone?

$ cat /etc/timezone 
Australia/Sydney

At the moment, this is UTC+11.

What time does my RPi think it is?

$ date
Tue 30 Mar 2021 03:40:57 PM AEDT

The work-in-progress service definition:

$ cat docker-compose.yml
version: '3.6'

services:

  adguardhome:
    container_name: adguardhome
    image: adguard/adguardhome
    restart: unless-stopped
    environment:
      - TZ=Australia/Sydney
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "8089:8089/tcp"
      - "3001:3000/tcp"
    volumes:
       - ./volumes/adguardhome/workdir:/opt/adguardhome/work
       - ./volumes/adguardhome/confdir:/opt/adguardhome/conf

Bring it up.

$ UP
Creating adguardhome ... done

Does the container receive the TZ environment variable?

$ docker exec adguardhome ash -c 'echo $TZ'
Australia/Sydney

You betcha! What time does the container think it is?

$ docker exec adguardhome date
Tue Mar 30 04:41:51 UTC 2021

UTC. 😢

Now for the magic incantation:

$ docker exec adguardhome apk add tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/armv7/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/armv7/APKINDEX.tar.gz
(1/1) Installing tzdata (2021a-r0)
Executing busybox-1.31.1-r19.trigger
OK: 8 MiB in 17 packages

What does the container think the time is now?

$ docker exec adguardhome date
Tue Mar 30 15:42:17 AEDT 2021

😀 We're cookin' with renewables (gas being so passé).

It seems that that is all there is to it, at least for images built on top of Alpine.

The problem we face is implementing this. If we want to do it at the IOTstack level, we need to use Dockerfiles like we're doing for NodeRed. That creates problems of its own for users who find it difficult to understand that different approaches are needed to update images we use "as is" from DockerHub vs those where Dockerfiles are involved.

One step removed is trying to convince the upstream image provider (in this example that would be the AdGuard Home team) to add tzdata to their image. Probably do-able but it could easily become a bit of an albatross if IOTstack committed to trying to make sure every container respected TZ.

One step further removed again is trying to convince the base image providers (in this example that'd be Alpine) to do it there. I don't think that would wrong-foot any downstream user of the image who had already done it but the starting position with these base images seems to be a combination of "bare minimum" plus "no unnecessary changes" so I wouldn't hold my breath.

If anyone has any ideas on how we might go about this, I'm all ears!

@Paraphraser
Copy link

Err, hold the phone. I'm currently also building Mosquitto from a Dockerfile (long story). I added TZ to the service definition and tzdata to the local image but - nada. More investigation needed.

@Paraphraser
Copy link

I tried lots of things with Mosquitto but had zero success in getting it to behave.

Of the containers I'm running, the following respect TZ:

nodered: Tue Mar 30 20:31:16 AEDT 2021
nextcloud_db: Tue Mar 30 20:31:17 AEDT 2021 (which implies MariaDB)
grafana: Tue Mar 30 20:31:17 AEDT 2021
wireguard: Tue Mar 30 20:31:18 AEDT 2021
influxdb: Tue Mar 30 20:31:18 AEDT 2021
pihole: Tue Mar 30 20:31:18 AEDT 2021

I didn't have any luck with Mosquitto or NextCloud.

mosquitto: Tue Mar 30 09:31:16 UTC 2021
nextcloud: Tue Mar 30 09:31:17 UTC 2021

@877dev
Copy link

877dev commented Mar 30, 2021

Excellent work, quite interesting how it works. So it does not actually hurt to declare the TZ variable, it's just not used in unsupported containers.

As I just swapped back to IOTstack old menu, there are references to .env files.
I presume the TZ could be added in the .env OR in docker-compose.yml ?

You can add these to the list that respect TZ:

zigbee2mqtt: Tue Mar 30 16:21:48 BST 2021
blynk_server: Tue Mar 30 16:22:13 BST 2021

Mosquitto does not respect TZ for me either:

mosquitto: Tue Mar 30 15:27:00 UTC 2021

Portainer CE really does not like it:

$ ~/IOTstack $ docker exec portainer-ce date
OCI runtime exec failed: exec failed: container_linux.go:349: starting container process caused "exec: \"date\": executable file not found in $PATH": unknown

@Paraphraser
Copy link

I honestly don't know what plane of existence Portainer's designers inhabit but their decision to remove pretty much everything from the container mystifies me. Still, I've never seen the point of Portainer. For a long time I hoped I'd have the "ahah" moment but I've given up on that.

Yes, you can create any old environment variable you like. Docker will transport any variable you define into the container. It the container respects the variable, it will take action. If it doesn't know about the variable, it ignores it. Environment variables are just "there". No syntax checking. No spelling checking. No validation. They either work or they don't.

One odd thing I did notice yesterday was one container where I had an environment file but I added "environment" plus TZ to docker-compose.yml. That didn't work. No error. Just didn't work. When I stopped being lazy and moved the TZ into the environment file, it worked. I had been assuming you could mix and match. I've yet to check the compose-file doco and re-test so I'm 100% sure but, right now, I'd say "don't use both".

That said, it's on my to-do list to migrate all my env-files into environment directives in docker-compose.yml. I do think that's a useful improvement from new menu - even though I can't abide some other things from new menu like the explosion of networks.

I think getting Mosquitto to respect TZ will need the maintainer to undo whatever it is that he has done to break it in the first place. I've stared at the Dockerfile until my eyes have gone square but I can't spot it.

@Paraphraser
Copy link

Well, hang up the phone, switch carriers, re-dial, and hold the phone again.

The compose spec says that anything in an environment: directive overrides anything defined via an environment file. Testing shows that is true.

Testing also confirms a fragment can have both a list of variables under an environment: and one or more environment files under an env_file: directive. Mix and match as much as you like.

It also doesn't matter what order the environment: and env_file: directives appear in, inline always overrides a file.

Now, consider these:

  • environment directives:

     environment:
     - TZ=Australia/Sydney
     - TZ="Australia/Sydney"
    
  • environment file directives:

     TZ=Australia/Sydney
     TZ="Australia/Sydney"
    

Guess what? The quoted form does not work. Even though it should not matter (in theory), it does in practice. It has to do with how the variable is used under the hood:

Assume the following in InfluxDB's fragment in docker-compose.yml:

environment:
- MY_TZ_PLAIN=Australia/Sydney
- MY_TZ_QUOTED="Australia/Sydney"

We UP the container and then open a shell into the container. First step is to prove that the variables are being transported into the container (remembering that in what follows the "#" is the root user's prompt, not a comment):

# echo "MY_TZ_PLAIN=$MY_TZ_PLAIN, MY_TZ_QUOTED=$MY_TZ_QUOTED"
MY_TZ_PLAIN=Australia/Sydney, MY_TZ_QUOTED="Australia/Sydney"

Let's construct the paths that are used to set up timezones:

# PLAIN_PATH="/usr/share/zoneinfo/$MY_TZ_PLAIN"
# QUOTED_PATH="/usr/share/zoneinfo/$MY_TZ_QUOTED"

# echo $PLAIN_PATH 
/usr/share/zoneinfo/Australia/Sydney

# echo $QUOTED_PATH
/usr/share/zoneinfo/"Australia/Sydney"

I'm sure you can see where this is heading...

# ls -l "$PLAIN_PATH"
lrwxrwxrwx 1 root root 3 Feb  1 22:59 /usr/share/zoneinfo/Australia/Sydney -> ACT

# ls -l "$QUOTED_PATH"
ls: cannot access '/usr/share/zoneinfo/"Australia/Sydney"': No such file or directory

That's the problem.

But wait, there's more!

$ docker exec mosquitto ash -c 'echo "TZ=$TZ"'
TZ=Australia/Sydney

$ docker exec mosquitto date
Tue Mar 30 23:58:18 UTC 2021

$ docker exec mosquitto apk add tzdata
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/main/armhf/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.12/community/armhf/APKINDEX.tar.gz
(1/1) Installing tzdata (2021a-r0)
Executing busybox-1.31.1-r19.trigger
OK: 12 MiB in 23 packages

$ docker exec mosquitto date
Wed Mar 31 10:58:41 AEDT 2021

That's all there was to it. Dang'd quotes! 🤬

We can add Mosquitto to the list in the "providing tzdata is added" column.

I will now fetch a two-by-four and beat myself over the head.

@senarvi
Copy link
Author

senarvi commented Apr 1, 2021

I managed to set the timezone with TZ to every service that I use, where it makes a visible change to the user, i.e. in the UI. Is the wrong date in e.g. mosquitto actually a problem?

@Paraphraser
Copy link

I submitted a Pull Request for AdGuard Home to add tzdata and it was approved and incorporated within a couple of hours. Very impressive! So that's another one to add to the list. It'll likely be "TZ ready" by the time I get around to proposing an IOTstack Pull Request to add the template (I'm actually waiting on feedback for some other Human Interface issues that turned up in my testing).

The answer to your question is "yes and no". At the technical level, no. Unless someone does something seriously silly, all modern systems run on UTC internally. You could argue that timezone correction only matters to human viewers and even then it isn't really all that hard to add or subtract your offset from UTC. The "get over it" argument.

That said, a container displaying UTC can surprise users who don't understand that it's all UTC under the hood, to the point where they think something must be wrong. That is, after all, sort of how we got here, isn't it?

A UTC display can also occasionally be misleading. You run a docker logs CONTAINER and see a time which is close to "now" without realising it is one of those rare "even a stopped clock is right twice a day" situations. You think something must be working when it's been dead for hours. You kick yourself when you work out why.

I'm also sure you're aware of having looked at something like a log entry then glanced at the time-of-day clock on your screen/wrist/wall to make an instant mental assessment of "how long ago?" and "is the difference reasonable?". Those calculations are made much harder if the container is displaying UTC, especially if it's close to the top of the hour.

Plus, the very time you start looking at logs is when something stopped working. The recent unilateral changes (see Read this if your Mosquitto is broken) is a case in point. You're in a panic. The log is the only thing you have to go on. That's really not the right time to be considering whether it's the right time (awful pun intended).

So, on balance, I'd say we're building for humans so we should accommodate human needs and implement TZ where we can.

Seeing as I got such a warm reception with AdGuardHome, I might give the Mosquitto people a try. They make things a bit harder than most repos; some nonsense about signing an agreement before you're allowed to contribute. I'm not into hoop-jumping of that kind (life's too short) but an issue pointing out that simply adding tzdata would do the trick might get me over the line.

@senarvi
Copy link
Author

senarvi commented Apr 1, 2021

Right, at least it affects the timestamps in the logs. Great work!

@877dev
Copy link

877dev commented Apr 1, 2021

Mix and match as much as you like.

It also doesn't matter what order the environment: and env_file: directives appear in, inline always overrides a file.

Now, consider these:

Yes I found that to be the case too!

That's all there was to it. Dang'd quotes!

Go easy with the two by four ! :)

@Paraphraser
Copy link

Mosquitto was fixed a while back to add tzdata via Dockerfile. Most of the rest of this is either fixed or obsolete but the basic idea of TZ=${TZ} is still on the table so I'd leave this one open.

@ukkopahis
Copy link

I'd suggest TZ=${IOTSTACK_TZ:-Etc/UTC}

  • Defaults to Etc/UTC
  • IOTSTACK_TZ is based on my work on [enhancement] CLI development #505, which defines a comprehensive but simple specification for service.yml files. It's a bit different than the current %variable% templating solution, as it only uses Docker Compose variables, but has enough metadata needed to present all menu options.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants