I've tended to use the modification time of the manifest or hiera file which you are declaring the host entries in and to convert that into a suitable timestamp for the serial. (You can also use whichever is the newest from a set of files if it's split across multiple files, or a timestamp for the most recent change if it's via some other route like a database)
Unfortunately the maximum serial number is a 32-bit unsigned integer, so you can only use numbers up to 2147483647. This doesn't allow us to simply use seconds since unix epoch as the serial number, unfortunately. Instead the default format is to use YYYYMMDDxx, but this requires you to have the current serial number as state if you have set it already on the same date.
As an alternative, and one which doesn't require you to read in a file and increment the number, I use the following inline template:
$serial_mtime_file = '/etc/puppetlabs/code/environments/production/site/profile/manifests/dns_dhcp_pxe.pp'
$serial_secs = inline_template("<%= File.mtime(@serial_mtime_file).strftime(\"%y%j\").to_s + (File.mtime(@serial_mtime_file).to_i % 86400).to_s %>")
notify { "Created magical serial number ${serial_secs}": }
validate_numeric($serial_secs)
This gets you a YYDDDsssss format (2 digit year, 3 digit day-of-year, 5 digit second-in-day), which will work until 2099 (if you start at 2000 as I did above) and allows an update every second until then. This allows using this variable as an argument to any existing module which you want to use to create the bind configs, rather than needing a template which you can read back the existing serial (to increment) from.
So templates are OK if you get a bit creative about where you get the time from to set the serial number with :)
I have used the above with the camptocamp/bind puppetforge module and this works correctly